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 "ppapi/cpp/var.h"
15 #include "remoting/proto/event.pb.h"
16 #include "ui/events/keycodes/dom4/keycode_converter.h"
17
18 namespace remoting {
19
PepperInputHandler(pp::Instance * instance,protocol::InputStub * input_stub)20 PepperInputHandler::PepperInputHandler(
21 pp::Instance* instance,
22 protocol::InputStub* input_stub)
23 : pp::MouseLock(instance),
24 instance_(instance),
25 input_stub_(input_stub),
26 callback_factory_(this),
27 has_focus_(false),
28 mouse_lock_state_(MouseLockDisallowed),
29 wheel_delta_x_(0),
30 wheel_delta_y_(0),
31 wheel_ticks_x_(0),
32 wheel_ticks_y_(0) {
33 }
34
~PepperInputHandler()35 PepperInputHandler::~PepperInputHandler() {
36 }
37
38 // Helper function to get the USB key code using the Dev InputEvent interface.
GetUsbKeyCode(pp::KeyboardInputEvent pp_key_event)39 uint32_t GetUsbKeyCode(pp::KeyboardInputEvent pp_key_event) {
40 const PPB_KeyboardInputEvent_Dev* key_event_interface =
41 reinterpret_cast<const PPB_KeyboardInputEvent_Dev*>(
42 pp::Module::Get()->GetBrowserInterface(
43 PPB_KEYBOARD_INPUT_EVENT_DEV_INTERFACE));
44 if (!key_event_interface)
45 return 0;
46
47 // Get the DOM3 |code| as a string.
48 pp::Var codevar(key_event_interface->GetCode(pp_key_event.pp_resource()));
49 if (!codevar.is_string())
50 return 0;
51 std::string codestr = codevar.AsString();
52
53 // Convert the |code| string into a USB keycode.
54 ui::KeycodeConverter* key_converter = ui::KeycodeConverter::GetInstance();
55 return key_converter->CodeToUsbKeycode(codestr.c_str());
56 }
57
HandleInputEvent(const pp::InputEvent & event)58 bool PepperInputHandler::HandleInputEvent(const pp::InputEvent& event) {
59 switch (event.GetType()) {
60 case PP_INPUTEVENT_TYPE_CONTEXTMENU: {
61 // We need to return true here or else we'll get a local (plugin) context
62 // menu instead of the mouseup event for the right click.
63 return true;
64 }
65
66 case PP_INPUTEVENT_TYPE_KEYDOWN:
67 case PP_INPUTEVENT_TYPE_KEYUP: {
68 pp::KeyboardInputEvent pp_key_event(event);
69 uint32_t modifiers = event.GetModifiers();
70 uint32_t lock_states = 0;
71
72 if (modifiers & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY)
73 lock_states |= protocol::KeyEvent::LOCK_STATES_CAPSLOCK;
74
75 if (modifiers & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY)
76 lock_states |= protocol::KeyEvent::LOCK_STATES_NUMLOCK;
77
78 protocol::KeyEvent key_event;
79 key_event.set_usb_keycode(GetUsbKeyCode(pp_key_event));
80 key_event.set_pressed(event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN);
81 key_event.set_lock_states(lock_states);
82
83 input_stub_->InjectKeyEvent(key_event);
84 return true;
85 }
86
87 case PP_INPUTEVENT_TYPE_MOUSEDOWN:
88 case PP_INPUTEVENT_TYPE_MOUSEUP: {
89 pp::MouseInputEvent pp_mouse_event(event);
90 protocol::MouseEvent mouse_event;
91 switch (pp_mouse_event.GetButton()) {
92 case PP_INPUTEVENT_MOUSEBUTTON_LEFT:
93 mouse_event.set_button(protocol::MouseEvent::BUTTON_LEFT);
94 break;
95 case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE:
96 mouse_event.set_button(protocol::MouseEvent::BUTTON_MIDDLE);
97 break;
98 case PP_INPUTEVENT_MOUSEBUTTON_RIGHT:
99 mouse_event.set_button(protocol::MouseEvent::BUTTON_RIGHT);
100 break;
101 case PP_INPUTEVENT_MOUSEBUTTON_NONE:
102 break;
103 }
104 if (mouse_event.has_button()) {
105 bool is_down = (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN);
106 mouse_event.set_button_down(is_down);
107 mouse_event.set_x(pp_mouse_event.GetPosition().x());
108 mouse_event.set_y(pp_mouse_event.GetPosition().y());
109
110 // Add relative movement if the mouse is locked.
111 if (mouse_lock_state_ == MouseLockOn) {
112 pp::Point delta = pp_mouse_event.GetMovement();
113 mouse_event.set_delta_x(delta.x());
114 mouse_event.set_delta_y(delta.y());
115 }
116
117 input_stub_->InjectMouseEvent(mouse_event);
118 }
119 return true;
120 }
121
122 case PP_INPUTEVENT_TYPE_MOUSEMOVE:
123 case PP_INPUTEVENT_TYPE_MOUSEENTER:
124 case PP_INPUTEVENT_TYPE_MOUSELEAVE: {
125 pp::MouseInputEvent pp_mouse_event(event);
126 protocol::MouseEvent mouse_event;
127 mouse_event.set_x(pp_mouse_event.GetPosition().x());
128 mouse_event.set_y(pp_mouse_event.GetPosition().y());
129
130 // Add relative movement if the mouse is locked.
131 if (mouse_lock_state_ == MouseLockOn) {
132 pp::Point delta = pp_mouse_event.GetMovement();
133 mouse_event.set_delta_x(delta.x());
134 mouse_event.set_delta_y(delta.y());
135 }
136
137 input_stub_->InjectMouseEvent(mouse_event);
138 return true;
139 }
140
141 case PP_INPUTEVENT_TYPE_WHEEL: {
142 pp::WheelInputEvent pp_wheel_event(event);
143
144 // Don't handle scroll-by-page events, for now.
145 if (pp_wheel_event.GetScrollByPage())
146 return false;
147
148 // Add this event to our accumulated sub-pixel deltas and clicks.
149 pp::FloatPoint delta = pp_wheel_event.GetDelta();
150 wheel_delta_x_ += delta.x();
151 wheel_delta_y_ += delta.y();
152 pp::FloatPoint ticks = pp_wheel_event.GetTicks();
153 wheel_ticks_x_ += ticks.x();
154 wheel_ticks_y_ += ticks.y();
155
156 // If there is at least a pixel's movement, emit an event. We don't
157 // ever expect to accumulate one tick's worth of scrolling without
158 // accumulating a pixel's worth at the same time, so this is safe.
159 int delta_x = static_cast<int>(wheel_delta_x_);
160 int delta_y = static_cast<int>(wheel_delta_y_);
161 if (delta_x != 0 || delta_y != 0) {
162 wheel_delta_x_ -= delta_x;
163 wheel_delta_y_ -= delta_y;
164 protocol::MouseEvent mouse_event;
165 mouse_event.set_wheel_delta_x(delta_x);
166 mouse_event.set_wheel_delta_y(delta_y);
167
168 // Always include the ticks in the event, even if insufficient pixel
169 // scrolling has accumulated for a single tick. This informs hosts
170 // that can't inject pixel-based scroll events that the client will
171 // accumulate them into tick-based scrolling, which gives a better
172 // overall experience than trying to do this host-side.
173 int ticks_x = static_cast<int>(wheel_ticks_x_);
174 int ticks_y = static_cast<int>(wheel_ticks_y_);
175 wheel_ticks_x_ -= ticks_x;
176 wheel_ticks_y_ -= ticks_y;
177 mouse_event.set_wheel_ticks_x(ticks_x);
178 mouse_event.set_wheel_ticks_y(ticks_y);
179
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
AllowMouseLock()198 void PepperInputHandler::AllowMouseLock() {
199 DCHECK_EQ(mouse_lock_state_, MouseLockDisallowed);
200 mouse_lock_state_ = MouseLockOff;
201 }
202
DidChangeFocus(bool has_focus)203 void PepperInputHandler::DidChangeFocus(bool has_focus) {
204 has_focus_ = has_focus;
205 if (has_focus_)
206 RequestMouseLock();
207 }
208
SetMouseCursor(scoped_ptr<pp::ImageData> image,const pp::Point & hotspot)209 void 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
MouseLockLost()221 void PepperInputHandler::MouseLockLost() {
222 DCHECK(mouse_lock_state_ == MouseLockOn ||
223 mouse_lock_state_ == MouseLockCancelling);
224
225 mouse_lock_state_ = MouseLockOff;
226 UpdateMouseCursor();
227 }
228
RequestMouseLock()229 void 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
CancelMouseLock()245 void 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
UpdateMouseCursor()274 void 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
OnMouseLocked(int error)290 void 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