1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// C headers
6#include <cassert>
7#include <cstdio>
8
9// C++ headers
10#include <sstream>
11#include <string>
12
13// PPAPI headers
14#include "ppapi/cpp/completion_callback.h"
15#include "ppapi/cpp/input_event.h"
16#include "ppapi/cpp/instance.h"
17#include "ppapi/cpp/module.h"
18#include "ppapi/cpp/point.h"
19#include "ppapi/cpp/var.h"
20#include "ppapi/utility/completion_callback_factory.h"
21
22#include "custom_events.h"
23#include "shared_queue.h"
24
25#ifdef PostMessage
26#undef PostMessage
27#endif
28
29const char* const kDidChangeView = "DidChangeView\n";
30const char* const kHandleInputEvent = "DidHandleInputEvent\n";
31const char* const kDidChangeFocus = "DidChangeFocus\n";
32const char* const kHaveFocus = "HaveFocus\n";
33const char* const kDontHaveFocus = "DontHaveFocus\n";
34const char* const kCancelMessage = "CANCEL";
35
36// Convert a pepper inputevent modifier value into a
37// custom event modifier.
38unsigned int ConvertEventModifier(uint32_t pp_modifier) {
39  unsigned int custom_modifier = 0;
40  if (pp_modifier & PP_INPUTEVENT_MODIFIER_SHIFTKEY) {
41    custom_modifier |= kShiftKeyModifier;
42  }
43  if (pp_modifier & PP_INPUTEVENT_MODIFIER_CONTROLKEY) {
44    custom_modifier |= kControlKeyModifier;
45  }
46  if (pp_modifier & PP_INPUTEVENT_MODIFIER_ALTKEY) {
47    custom_modifier |= kAltKeyModifier;
48  }
49  if (pp_modifier & PP_INPUTEVENT_MODIFIER_METAKEY) {
50    custom_modifier |= kMetaKeyModifer;
51  }
52  if (pp_modifier & PP_INPUTEVENT_MODIFIER_ISKEYPAD) {
53    custom_modifier |= kKeyPadModifier;
54  }
55  if (pp_modifier & PP_INPUTEVENT_MODIFIER_ISAUTOREPEAT) {
56    custom_modifier |= kAutoRepeatModifier;
57  }
58  if (pp_modifier & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) {
59    custom_modifier |= kLeftButtonModifier;
60  }
61  if (pp_modifier & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN) {
62    custom_modifier |= kMiddleButtonModifier;
63  }
64  if (pp_modifier & PP_INPUTEVENT_MODIFIER_RIGHTBUTTONDOWN) {
65    custom_modifier |= kRightButtonModifier;
66  }
67  if (pp_modifier & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY) {
68    custom_modifier |= kCapsLockModifier;
69  }
70  if (pp_modifier & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY) {
71    custom_modifier |= kNumLockModifier;
72  }
73  return custom_modifier;
74}
75
76class InputEventInstance : public pp::Instance {
77 public:
78  explicit InputEventInstance(PP_Instance instance)
79      : pp::Instance(instance), event_thread_(NULL), callback_factory_(this) {
80    RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL |
81                       PP_INPUTEVENT_CLASS_TOUCH);
82    RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
83  }
84
85  // Not guaranteed to be called in Pepper, but a good idea to cancel the
86  // queue and signal to workers to die if it is called.
87  virtual ~InputEventInstance() { CancelQueueAndWaitForWorker(); }
88
89  // Create the 'worker thread'.
90  bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
91    event_thread_ = new pthread_t;
92    pthread_create(event_thread_, NULL, ProcessEventOnWorkerThread, this);
93    return true;
94  }
95
96  /// Clicking outside of the instance's bounding box
97  /// will create a DidChangeFocus event (the NaCl instance is
98  /// out of focus). Clicking back inside the instance's
99  /// bounding box will create another DidChangeFocus event
100  /// (the NaCl instance is back in focus). The default is
101  /// that the instance is out of focus.
102  void DidChangeFocus(bool focus) {
103    PostMessage(pp::Var(kDidChangeFocus));
104    if (focus == true) {
105      PostMessage(pp::Var(kHaveFocus));
106    } else {
107      PostMessage(pp::Var(kDontHaveFocus));
108    }
109  }
110
111  /// Scrolling the mouse wheel causes a DidChangeView event.
112  void DidChangeView(const pp::View& view) {
113    PostMessage(pp::Var(kDidChangeView));
114  }
115
116  virtual void HandleMessage(const pp::Var& var_message) {
117    std::string message = var_message.AsString();
118    if (kCancelMessage == message) {
119      std::string reply =
120          "Received cancel : only Focus events will be "
121          "displayed. Worker thread for mouse/wheel/keyboard will exit.";
122      PostMessage(pp::Var(reply));
123      printf("Calling cancel queue\n");
124      CancelQueueAndWaitForWorker();
125    }
126  }
127
128  // HandleInputEvent operates on the main Pepper thread.  Here we
129  // illustrate copying the Pepper input event to our own custom event type.
130  // Since we need to use Pepper API calls to convert it, we must do the
131  // conversion on the main thread.  Once we have converted it to our own
132  // event type, we push that into a thread-safe queue and quickly return.
133  // The worker thread can process the custom event and do whatever
134  // (possibly slow) things it wants to do without making the browser
135  // become unresponsive.
136  // We dynamically allocate a sub-class of our custom event (Event)
137  // so that the queue can contain an Event*.
138  virtual bool HandleInputEvent(const pp::InputEvent& event) {
139    Event* event_ptr = NULL;
140    switch (event.GetType()) {
141      case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START:
142      case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE:
143      case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END:
144      case PP_INPUTEVENT_TYPE_IME_TEXT:
145      // these cases are not handled...fall through below...
146      case PP_INPUTEVENT_TYPE_UNDEFINED:
147        break;
148      case PP_INPUTEVENT_TYPE_MOUSEDOWN:
149      case PP_INPUTEVENT_TYPE_MOUSEUP:
150      case PP_INPUTEVENT_TYPE_MOUSEMOVE:
151      case PP_INPUTEVENT_TYPE_MOUSEENTER:
152      case PP_INPUTEVENT_TYPE_MOUSELEAVE:
153      case PP_INPUTEVENT_TYPE_CONTEXTMENU: {
154        pp::MouseInputEvent mouse_event(event);
155        PP_InputEvent_MouseButton pp_button = mouse_event.GetButton();
156        MouseEvent::MouseButton mouse_button = MouseEvent::kNone;
157        switch (pp_button) {
158          case PP_INPUTEVENT_MOUSEBUTTON_NONE:
159            mouse_button = MouseEvent::kNone;
160            break;
161          case PP_INPUTEVENT_MOUSEBUTTON_LEFT:
162            mouse_button = MouseEvent::kLeft;
163            break;
164          case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE:
165            mouse_button = MouseEvent::kMiddle;
166            break;
167          case PP_INPUTEVENT_MOUSEBUTTON_RIGHT:
168            mouse_button = MouseEvent::kRight;
169            break;
170        }
171        event_ptr =
172            new MouseEvent(ConvertEventModifier(mouse_event.GetModifiers()),
173                           mouse_button,
174                           mouse_event.GetPosition().x(),
175                           mouse_event.GetPosition().y(),
176                           mouse_event.GetClickCount(),
177                           mouse_event.GetTimeStamp(),
178                           event.GetType() == PP_INPUTEVENT_TYPE_CONTEXTMENU);
179      } break;
180      case PP_INPUTEVENT_TYPE_WHEEL: {
181        pp::WheelInputEvent wheel_event(event);
182        event_ptr =
183            new WheelEvent(ConvertEventModifier(wheel_event.GetModifiers()),
184                           wheel_event.GetDelta().x(),
185                           wheel_event.GetDelta().y(),
186                           wheel_event.GetTicks().x(),
187                           wheel_event.GetTicks().y(),
188                           wheel_event.GetScrollByPage(),
189                           wheel_event.GetTimeStamp());
190      } break;
191      case PP_INPUTEVENT_TYPE_RAWKEYDOWN:
192      case PP_INPUTEVENT_TYPE_KEYDOWN:
193      case PP_INPUTEVENT_TYPE_KEYUP:
194      case PP_INPUTEVENT_TYPE_CHAR: {
195        pp::KeyboardInputEvent key_event(event);
196        event_ptr = new KeyEvent(ConvertEventModifier(key_event.GetModifiers()),
197                                 key_event.GetKeyCode(),
198                                 key_event.GetTimeStamp(),
199                                 key_event.GetCharacterText().DebugString());
200      } break;
201      case PP_INPUTEVENT_TYPE_TOUCHSTART:
202      case PP_INPUTEVENT_TYPE_TOUCHMOVE:
203      case PP_INPUTEVENT_TYPE_TOUCHEND:
204      case PP_INPUTEVENT_TYPE_TOUCHCANCEL: {
205        pp::TouchInputEvent touch_event(event);
206
207        TouchEvent::Kind touch_kind = TouchEvent::kNone;
208        if (event.GetType() == PP_INPUTEVENT_TYPE_TOUCHSTART)
209          touch_kind = TouchEvent::kStart;
210        else if (event.GetType() == PP_INPUTEVENT_TYPE_TOUCHMOVE)
211          touch_kind = TouchEvent::kMove;
212        else if (event.GetType() == PP_INPUTEVENT_TYPE_TOUCHEND)
213          touch_kind = TouchEvent::kEnd;
214        else if (event.GetType() == PP_INPUTEVENT_TYPE_TOUCHCANCEL)
215          touch_kind = TouchEvent::kCancel;
216
217        TouchEvent* touch_event_ptr =
218            new TouchEvent(ConvertEventModifier(touch_event.GetModifiers()),
219                           touch_kind,
220                           touch_event.GetTimeStamp());
221        event_ptr = touch_event_ptr;
222
223        uint32_t touch_count =
224            touch_event.GetTouchCount(PP_TOUCHLIST_TYPE_CHANGEDTOUCHES);
225        for (uint32_t i = 0; i < touch_count; ++i) {
226          pp::TouchPoint point =
227              touch_event.GetTouchByIndex(PP_TOUCHLIST_TYPE_CHANGEDTOUCHES, i);
228          touch_event_ptr->AddTouch(point.id(),
229                                    point.position().x(),
230                                    point.position().y(),
231                                    point.radii().x(),
232                                    point.radii().y(),
233                                    point.rotation_angle(),
234                                    point.pressure());
235        }
236      } break;
237      default: {
238        // For any unhandled events, send a message to the browser
239        // so that the user is aware of these and can investigate.
240        std::stringstream oss;
241        oss << "Default (unhandled) event, type=" << event.GetType();
242        PostMessage(oss.str());
243      } break;
244    }
245    event_queue_.Push(event_ptr);
246    return true;
247  }
248
249  // Return an event from the thread-safe queue, waiting for a new event
250  // to occur if the queue is empty.  Set |was_queue_cancelled| to indicate
251  // whether the queue was cancelled.  If it was cancelled, then the
252  // Event* will be NULL.
253  const Event* GetEventFromQueue(bool* was_queue_cancelled) {
254    Event* event = NULL;
255    QueueGetResult result = event_queue_.GetItem(&event, kWait);
256    if (result == kQueueWasCancelled) {
257      *was_queue_cancelled = true;
258      return NULL;
259    }
260    *was_queue_cancelled = false;
261    return event;
262  }
263
264  // This method is called from the worker thread using CallOnMainThread.
265  // It is not static, and allows PostMessage to be called.
266  void* PostStringToBrowser(int32_t result, std::string data_to_send) {
267    PostMessage(pp::Var(data_to_send));
268    return 0;
269  }
270
271  // |ProcessEventOnWorkerThread| is a static method that is run
272  // by a thread.  It pulls events from the queue, converts
273  // them to a string, and calls CallOnMainThread so that
274  // PostStringToBrowser will be called, which will call PostMessage
275  // to send the converted event back to the browser.
276  static void* ProcessEventOnWorkerThread(void* param) {
277    InputEventInstance* event_instance =
278        static_cast<InputEventInstance*>(param);
279    while (1) {
280      // Grab a generic Event* so that down below we can call
281      // event->ToString(), which will use the correct virtual method
282      // to convert the event to a string.  This 'conversion' is
283      // the 'work' being done on the worker thread.  In an application
284      // the work might involve changing application state based on
285      // the event that was processed.
286      bool queue_cancelled;
287      const Event* event = event_instance->GetEventFromQueue(&queue_cancelled);
288      if (queue_cancelled) {
289        printf("Queue was cancelled, worker thread exiting\n");
290        pthread_exit(NULL);
291      }
292      std::string event_string = event->ToString();
293      delete event;
294      // Need to invoke callback on main thread.
295      pp::Module::Get()->core()->CallOnMainThread(
296          0,
297          event_instance->callback_factory().NewCallback(
298              &InputEventInstance::PostStringToBrowser, event_string));
299    }  // end of while loop.
300    return 0;
301  }
302
303  // Return the callback factory.
304  // Allows the static method (ProcessEventOnWorkerThread) to use
305  // the |event_instance| pointer to get the factory.
306  pp::CompletionCallbackFactory<InputEventInstance>& callback_factory() {
307    return callback_factory_;
308  }
309
310 private:
311  // Cancels the queue (which will cause the thread to exit).
312  // Wait for the thread.  Set |event_thread_| to NULL so we only
313  // execute the body once.
314  void CancelQueueAndWaitForWorker() {
315    if (event_thread_) {
316      event_queue_.CancelQueue();
317      pthread_join(*event_thread_, NULL);
318      delete event_thread_;
319      event_thread_ = NULL;
320    }
321  }
322  pthread_t* event_thread_;
323  LockingQueue<Event*> event_queue_;
324  pp::CompletionCallbackFactory<InputEventInstance> callback_factory_;
325};
326
327class InputEventModule : public pp::Module {
328 public:
329  InputEventModule() : pp::Module() {}
330  virtual ~InputEventModule() {}
331
332  virtual pp::Instance* CreateInstance(PP_Instance instance) {
333    return new InputEventInstance(instance);
334  }
335};
336
337namespace pp {
338Module* CreateModule() { return new InputEventModule(); }
339}
340