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