pepper_input_handler.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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#include "remoting/client/plugin/pepper_input_handler.h"
6
7#include "base/logging.h"
8#include "ppapi/c/dev/ppb_keyboard_input_event_dev.h"
9#include "ppapi/cpp/image_data.h"
10#include "ppapi/cpp/input_event.h"
11#include "ppapi/cpp/module_impl.h"
12#include "ppapi/cpp/mouse_cursor.h"
13#include "ppapi/cpp/point.h"
14#include "remoting/proto/event.pb.h"
15
16namespace remoting {
17
18PepperInputHandler::PepperInputHandler(
19    pp::Instance* instance,
20    protocol::InputStub* input_stub)
21    : pp::MouseLock(instance),
22      instance_(instance),
23      input_stub_(input_stub),
24      callback_factory_(this),
25      has_focus_(false),
26      mouse_lock_state_(MouseLockDisallowed),
27      wheel_delta_x_(0),
28      wheel_delta_y_(0),
29      wheel_ticks_x_(0),
30      wheel_ticks_y_(0) {
31}
32
33PepperInputHandler::~PepperInputHandler() {
34}
35
36// Helper function to get the USB key code using the Dev InputEvent interface.
37uint32_t GetUsbKeyCode(pp::KeyboardInputEvent pp_key_event) {
38  const PPB_KeyboardInputEvent_Dev* key_event_interface =
39      reinterpret_cast<const PPB_KeyboardInputEvent_Dev*>(
40          pp::Module::Get()->GetBrowserInterface(
41              PPB_KEYBOARD_INPUT_EVENT_DEV_INTERFACE));
42  if (!key_event_interface)
43    return 0;
44  return key_event_interface->GetUsbKeyCode(pp_key_event.pp_resource());
45}
46
47bool PepperInputHandler::HandleInputEvent(const pp::InputEvent& event) {
48  switch (event.GetType()) {
49    case PP_INPUTEVENT_TYPE_CONTEXTMENU: {
50      // We need to return true here or else we'll get a local (plugin) context
51      // menu instead of the mouseup event for the right click.
52      return true;
53    }
54
55    case PP_INPUTEVENT_TYPE_KEYDOWN:
56    case PP_INPUTEVENT_TYPE_KEYUP: {
57      pp::KeyboardInputEvent pp_key_event(event);
58      uint32_t modifiers = event.GetModifiers();
59      uint32_t lock_states = 0;
60
61      if (modifiers & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY)
62        lock_states |= protocol::KeyEvent::LOCK_STATES_CAPSLOCK;
63
64      if (modifiers & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY)
65        lock_states |= protocol::KeyEvent::LOCK_STATES_NUMLOCK;
66
67      protocol::KeyEvent key_event;
68      key_event.set_usb_keycode(GetUsbKeyCode(pp_key_event));
69      key_event.set_pressed(event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN);
70      key_event.set_lock_states(lock_states);
71
72      input_stub_->InjectKeyEvent(key_event);
73      return true;
74    }
75
76    case PP_INPUTEVENT_TYPE_MOUSEDOWN:
77    case PP_INPUTEVENT_TYPE_MOUSEUP: {
78      pp::MouseInputEvent pp_mouse_event(event);
79      protocol::MouseEvent mouse_event;
80      switch (pp_mouse_event.GetButton()) {
81        case PP_INPUTEVENT_MOUSEBUTTON_LEFT:
82          mouse_event.set_button(protocol::MouseEvent::BUTTON_LEFT);
83          break;
84        case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE:
85          mouse_event.set_button(protocol::MouseEvent::BUTTON_MIDDLE);
86          break;
87        case PP_INPUTEVENT_MOUSEBUTTON_RIGHT:
88          mouse_event.set_button(protocol::MouseEvent::BUTTON_RIGHT);
89          break;
90        case PP_INPUTEVENT_MOUSEBUTTON_NONE:
91          break;
92      }
93      if (mouse_event.has_button()) {
94        bool is_down = (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN);
95        mouse_event.set_button_down(is_down);
96        mouse_event.set_x(pp_mouse_event.GetPosition().x());
97        mouse_event.set_y(pp_mouse_event.GetPosition().y());
98        input_stub_->InjectMouseEvent(mouse_event);
99      }
100      return true;
101    }
102
103    case PP_INPUTEVENT_TYPE_MOUSEMOVE:
104    case PP_INPUTEVENT_TYPE_MOUSEENTER:
105    case PP_INPUTEVENT_TYPE_MOUSELEAVE: {
106      pp::MouseInputEvent pp_mouse_event(event);
107      protocol::MouseEvent mouse_event;
108      mouse_event.set_x(pp_mouse_event.GetPosition().x());
109      mouse_event.set_y(pp_mouse_event.GetPosition().y());
110
111      // Add relative movement if the mouse is locked.
112      if (mouse_lock_state_ == MouseLockOn) {
113        pp::Point delta = pp_mouse_event.GetMovement();
114        mouse_event.set_delta_x(delta.x());
115        mouse_event.set_delta_y(delta.y());
116      }
117
118      input_stub_->InjectMouseEvent(mouse_event);
119      return true;
120    }
121
122    case PP_INPUTEVENT_TYPE_WHEEL: {
123      pp::WheelInputEvent pp_wheel_event(event);
124
125      // Don't handle scroll-by-page events, for now.
126      if (pp_wheel_event.GetScrollByPage())
127        return false;
128
129      // Add this event to our accumulated sub-pixel deltas and clicks.
130      pp::FloatPoint delta = pp_wheel_event.GetDelta();
131      wheel_delta_x_ += delta.x();
132      wheel_delta_y_ += delta.y();
133      pp::FloatPoint ticks = pp_wheel_event.GetTicks();
134      wheel_ticks_x_ += ticks.x();
135      wheel_ticks_y_ += ticks.y();
136
137      // If there is at least a pixel's movement, emit an event. We don't
138      // ever expect to accumulate one tick's worth of scrolling without
139      // accumulating a pixel's worth at the same time, so this is safe.
140      int delta_x = static_cast<int>(wheel_delta_x_);
141      int delta_y = static_cast<int>(wheel_delta_y_);
142      if (delta_x != 0 || delta_y != 0) {
143        wheel_delta_x_ -= delta_x;
144        wheel_delta_y_ -= delta_y;
145        protocol::MouseEvent mouse_event;
146        mouse_event.set_wheel_delta_x(delta_x);
147        mouse_event.set_wheel_delta_y(delta_y);
148
149        // Always include the ticks in the event, even if insufficient pixel
150        // scrolling has accumulated for a single tick. This informs hosts
151        // that can't inject pixel-based scroll events that the client will
152        // accumulate them into tick-based scrolling, which gives a better
153        // overall experience than trying to do this host-side.
154        int ticks_x = static_cast<int>(wheel_ticks_x_);
155        int ticks_y = static_cast<int>(wheel_ticks_y_);
156        wheel_ticks_x_ -= ticks_x;
157        wheel_ticks_y_ -= ticks_y;
158        mouse_event.set_wheel_ticks_x(ticks_x);
159        mouse_event.set_wheel_ticks_y(ticks_y);
160
161        input_stub_->InjectMouseEvent(mouse_event);
162      }
163      return true;
164    }
165
166    case PP_INPUTEVENT_TYPE_CHAR:
167      // Consume but ignore character input events.
168      return true;
169
170    default: {
171      LOG(INFO) << "Unhandled input event: " << event.GetType();
172      break;
173    }
174  }
175
176  return false;
177}
178
179void PepperInputHandler::AllowMouseLock() {
180  DCHECK_EQ(mouse_lock_state_, MouseLockDisallowed);
181  mouse_lock_state_ = MouseLockOff;
182}
183
184void PepperInputHandler::DidChangeFocus(bool has_focus) {
185  has_focus_ = has_focus;
186  if (has_focus_)
187    RequestMouseLock();
188}
189
190void PepperInputHandler::SetMouseCursor(scoped_ptr<pp::ImageData> image,
191                                        const pp::Point& hotspot) {
192  cursor_image_ = image.Pass();
193  cursor_hotspot_ = hotspot;
194
195  if (mouse_lock_state_ != MouseLockDisallowed && !cursor_image_) {
196    RequestMouseLock();
197  } else {
198    CancelMouseLock();
199  }
200}
201
202void PepperInputHandler::MouseLockLost() {
203  DCHECK(mouse_lock_state_ == MouseLockOn ||
204         mouse_lock_state_ == MouseLockCancelling);
205
206  mouse_lock_state_ = MouseLockOff;
207  UpdateMouseCursor();
208}
209
210void PepperInputHandler::RequestMouseLock() {
211  // Request mouse lock only if the plugin is focused, the host-supplied cursor
212  // is empty and no callback is pending.
213  if (has_focus_ && !cursor_image_ && mouse_lock_state_ == MouseLockOff) {
214    pp::CompletionCallback callback =
215        callback_factory_.NewCallback(&PepperInputHandler::OnMouseLocked);
216    int result = pp::MouseLock::LockMouse(callback);
217    DCHECK_EQ(result, PP_OK_COMPLETIONPENDING);
218
219    // Hide cursor to avoid it becoming a black square (see crbug.com/285809).
220    pp::MouseCursor::SetCursor(instance_, PP_MOUSECURSOR_TYPE_NONE);
221
222    mouse_lock_state_ = MouseLockRequestPending;
223  }
224}
225
226void PepperInputHandler::CancelMouseLock() {
227  switch (mouse_lock_state_) {
228    case MouseLockDisallowed:
229    case MouseLockOff:
230      UpdateMouseCursor();
231      break;
232
233    case MouseLockCancelling:
234      break;
235
236    case MouseLockRequestPending:
237      // The mouse lock request is pending. Delay UnlockMouse() call until
238      // the callback is called.
239      mouse_lock_state_ = MouseLockCancelling;
240      break;
241
242    case MouseLockOn:
243      pp::MouseLock::UnlockMouse();
244
245      // Note that mouse-lock has been cancelled. We will continue to receive
246      // locked events until MouseLockLost() is called back.
247      mouse_lock_state_ = MouseLockCancelling;
248      break;
249
250    default:
251      NOTREACHED();
252  }
253}
254
255void PepperInputHandler::UpdateMouseCursor() {
256  DCHECK(mouse_lock_state_ == MouseLockDisallowed ||
257         mouse_lock_state_ == MouseLockOff);
258
259  if (cursor_image_) {
260    pp::MouseCursor::SetCursor(instance_, PP_MOUSECURSOR_TYPE_CUSTOM,
261                               *cursor_image_,
262                               cursor_hotspot_);
263  } else {
264    // If there is no cursor shape stored, either because the host never
265    // supplied one, or we were previously in mouse-lock mode, then use
266    // a standard arrow pointer.
267    pp::MouseCursor::SetCursor(instance_, PP_MOUSECURSOR_TYPE_POINTER);
268  }
269}
270
271void PepperInputHandler::OnMouseLocked(int error) {
272  DCHECK(mouse_lock_state_ == MouseLockRequestPending ||
273         mouse_lock_state_ == MouseLockCancelling);
274
275  bool should_cancel = (mouse_lock_state_ == MouseLockCancelling);
276
277  // See if the operation succeeded.
278  if (error == PP_OK) {
279    mouse_lock_state_ = MouseLockOn;
280  } else {
281    mouse_lock_state_ = MouseLockOff;
282    UpdateMouseCursor();
283  }
284
285  // Cancel as needed.
286  if (should_cancel)
287    CancelMouseLock();
288}
289
290}  // namespace remoting
291