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 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 35 PepperInputHandler::~PepperInputHandler() { 36 } 37 38 // Helper function to get the USB key code using the Dev InputEvent interface. 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 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 198 void PepperInputHandler::AllowMouseLock() { 199 DCHECK_EQ(mouse_lock_state_, MouseLockDisallowed); 200 mouse_lock_state_ = MouseLockOff; 201 } 202 203 void PepperInputHandler::DidChangeFocus(bool has_focus) { 204 has_focus_ = has_focus; 205 if (has_focus_) 206 RequestMouseLock(); 207 } 208 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 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 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 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 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 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