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 17 namespace remoting { 18 19 PepperInputHandler::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 34 PepperInputHandler::~PepperInputHandler() {} 35 36 // Helper function to get the USB key code using the Dev InputEvent interface. 37 uint32_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 ui::KeycodeConverter* key_converter = ui::KeycodeConverter::GetInstance(); 43 return key_converter->CodeToUsbKeycode(codestr.c_str()); 44 } 45 46 bool PepperInputHandler::HandleInputEvent(const pp::InputEvent& event) { 47 switch (event.GetType()) { 48 case PP_INPUTEVENT_TYPE_CONTEXTMENU: { 49 // We need to return true here or else we'll get a local (plugin) context 50 // menu instead of the mouseup event for the right click. 51 return true; 52 } 53 54 case PP_INPUTEVENT_TYPE_KEYDOWN: 55 case PP_INPUTEVENT_TYPE_KEYUP: { 56 pp::KeyboardInputEvent pp_key_event(event); 57 uint32_t modifiers = event.GetModifiers(); 58 uint32_t lock_states = 0; 59 60 if (modifiers & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY) 61 lock_states |= protocol::KeyEvent::LOCK_STATES_CAPSLOCK; 62 63 if (modifiers & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY) 64 lock_states |= protocol::KeyEvent::LOCK_STATES_NUMLOCK; 65 66 protocol::KeyEvent key_event; 67 key_event.set_usb_keycode(GetUsbKeyCode(pp_key_event)); 68 key_event.set_pressed(event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN); 69 key_event.set_lock_states(lock_states); 70 71 if (input_stub_) 72 input_stub_->InjectKeyEvent(key_event); 73 return true; 74 } 75 76 case PP_INPUTEVENT_TYPE_MOUSEDOWN: 77 case PP_INPUTEVENT_TYPE_MOUSEUP: { 78 if (!has_focus_ && !send_mouse_input_when_unfocused_) 79 return false; 80 81 pp::MouseInputEvent pp_mouse_event(event); 82 protocol::MouseEvent mouse_event; 83 switch (pp_mouse_event.GetButton()) { 84 case PP_INPUTEVENT_MOUSEBUTTON_LEFT: 85 mouse_event.set_button(protocol::MouseEvent::BUTTON_LEFT); 86 break; 87 case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE: 88 mouse_event.set_button(protocol::MouseEvent::BUTTON_MIDDLE); 89 break; 90 case PP_INPUTEVENT_MOUSEBUTTON_RIGHT: 91 mouse_event.set_button(protocol::MouseEvent::BUTTON_RIGHT); 92 break; 93 case PP_INPUTEVENT_MOUSEBUTTON_NONE: 94 break; 95 } 96 if (mouse_event.has_button()) { 97 bool is_down = (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN); 98 mouse_event.set_button_down(is_down); 99 mouse_event.set_x(pp_mouse_event.GetPosition().x()); 100 mouse_event.set_y(pp_mouse_event.GetPosition().y()); 101 102 // Add relative movement if the mouse is locked. 103 if (mouse_lock_state_ == MouseLockOn) { 104 pp::Point delta = pp_mouse_event.GetMovement(); 105 mouse_event.set_delta_x(delta.x()); 106 mouse_event.set_delta_y(delta.y()); 107 } 108 109 if (input_stub_) 110 input_stub_->InjectMouseEvent(mouse_event); 111 } 112 return true; 113 } 114 115 case PP_INPUTEVENT_TYPE_MOUSEMOVE: 116 case PP_INPUTEVENT_TYPE_MOUSEENTER: 117 case PP_INPUTEVENT_TYPE_MOUSELEAVE: { 118 if (!has_focus_ && !send_mouse_input_when_unfocused_) 119 return false; 120 121 pp::MouseInputEvent pp_mouse_event(event); 122 protocol::MouseEvent mouse_event; 123 mouse_event.set_x(pp_mouse_event.GetPosition().x()); 124 mouse_event.set_y(pp_mouse_event.GetPosition().y()); 125 126 // Add relative movement if the mouse is locked. 127 if (mouse_lock_state_ == MouseLockOn) { 128 pp::Point delta = pp_mouse_event.GetMovement(); 129 mouse_event.set_delta_x(delta.x()); 130 mouse_event.set_delta_y(delta.y()); 131 } 132 133 if (input_stub_) 134 input_stub_->InjectMouseEvent(mouse_event); 135 return true; 136 } 137 138 case PP_INPUTEVENT_TYPE_WHEEL: { 139 if (!has_focus_ && !send_mouse_input_when_unfocused_) 140 return false; 141 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 if (input_stub_) 181 input_stub_->InjectMouseEvent(mouse_event); 182 } 183 return true; 184 } 185 186 case PP_INPUTEVENT_TYPE_CHAR: 187 // Consume but ignore character input events. 188 return true; 189 190 default: { 191 VLOG(0) << "Unhandled input event: " << event.GetType(); 192 break; 193 } 194 } 195 196 return false; 197 } 198 199 void PepperInputHandler::AllowMouseLock() { 200 DCHECK_EQ(mouse_lock_state_, MouseLockDisallowed); 201 mouse_lock_state_ = MouseLockOff; 202 } 203 204 void PepperInputHandler::DidChangeFocus(bool has_focus) { 205 has_focus_ = has_focus; 206 if (has_focus_) 207 RequestMouseLock(); 208 } 209 210 void PepperInputHandler::SetMouseCursor(scoped_ptr<pp::ImageData> image, 211 const pp::Point& hotspot) { 212 cursor_image_ = image.Pass(); 213 cursor_hotspot_ = hotspot; 214 215 if (mouse_lock_state_ != MouseLockDisallowed && !cursor_image_) { 216 RequestMouseLock(); 217 } else { 218 CancelMouseLock(); 219 } 220 } 221 222 void PepperInputHandler::MouseLockLost() { 223 DCHECK(mouse_lock_state_ == MouseLockOn || 224 mouse_lock_state_ == MouseLockCancelling); 225 226 mouse_lock_state_ = MouseLockOff; 227 UpdateMouseCursor(); 228 } 229 230 void PepperInputHandler::RequestMouseLock() { 231 // Request mouse lock only if the plugin is focused, the host-supplied cursor 232 // is empty and no callback is pending. 233 if (has_focus_ && !cursor_image_ && mouse_lock_state_ == MouseLockOff) { 234 pp::CompletionCallback callback = 235 callback_factory_.NewCallback(&PepperInputHandler::OnMouseLocked); 236 int result = pp::MouseLock::LockMouse(callback); 237 DCHECK_EQ(result, PP_OK_COMPLETIONPENDING); 238 239 // Hide cursor to avoid it becoming a black square (see crbug.com/285809). 240 pp::MouseCursor::SetCursor(instance_, PP_MOUSECURSOR_TYPE_NONE); 241 242 mouse_lock_state_ = MouseLockRequestPending; 243 } 244 } 245 246 void PepperInputHandler::CancelMouseLock() { 247 switch (mouse_lock_state_) { 248 case MouseLockDisallowed: 249 case MouseLockOff: 250 UpdateMouseCursor(); 251 break; 252 253 case MouseLockCancelling: 254 break; 255 256 case MouseLockRequestPending: 257 // The mouse lock request is pending. Delay UnlockMouse() call until 258 // the callback is called. 259 mouse_lock_state_ = MouseLockCancelling; 260 break; 261 262 case MouseLockOn: 263 pp::MouseLock::UnlockMouse(); 264 265 // Note that mouse-lock has been cancelled. We will continue to receive 266 // locked events until MouseLockLost() is called back. 267 mouse_lock_state_ = MouseLockCancelling; 268 break; 269 270 default: 271 NOTREACHED(); 272 } 273 } 274 275 void PepperInputHandler::UpdateMouseCursor() { 276 DCHECK(mouse_lock_state_ == MouseLockDisallowed || 277 mouse_lock_state_ == MouseLockOff); 278 279 if (cursor_image_) { 280 pp::MouseCursor::SetCursor(instance_, PP_MOUSECURSOR_TYPE_CUSTOM, 281 *cursor_image_, 282 cursor_hotspot_); 283 } else { 284 // If there is no cursor shape stored, either because the host never 285 // supplied one, or we were previously in mouse-lock mode, then use 286 // a standard arrow pointer. 287 pp::MouseCursor::SetCursor(instance_, PP_MOUSECURSOR_TYPE_POINTER); 288 } 289 } 290 291 void PepperInputHandler::OnMouseLocked(int error) { 292 DCHECK(mouse_lock_state_ == MouseLockRequestPending || 293 mouse_lock_state_ == MouseLockCancelling); 294 295 bool should_cancel = (mouse_lock_state_ == MouseLockCancelling); 296 297 // See if the operation succeeded. 298 if (error == PP_OK) { 299 mouse_lock_state_ = MouseLockOn; 300 } else { 301 mouse_lock_state_ = MouseLockOff; 302 UpdateMouseCursor(); 303 } 304 305 // Cancel as needed. 306 if (should_cancel) 307 CancelMouseLock(); 308 } 309 310 } // namespace remoting 311