// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "remoting/client/plugin/pepper_input_handler.h" #include "base/logging.h" #include "ppapi/c/dev/ppb_keyboard_input_event_dev.h" #include "ppapi/cpp/image_data.h" #include "ppapi/cpp/input_event.h" #include "ppapi/cpp/module_impl.h" #include "ppapi/cpp/mouse_cursor.h" #include "ppapi/cpp/point.h" #include "ppapi/cpp/var.h" #include "remoting/proto/event.pb.h" #include "ui/events/keycodes/dom4/keycode_converter.h" namespace remoting { PepperInputHandler::PepperInputHandler( pp::Instance* instance, protocol::InputStub* input_stub) : pp::MouseLock(instance), instance_(instance), input_stub_(input_stub), callback_factory_(this), has_focus_(false), mouse_lock_state_(MouseLockDisallowed), wheel_delta_x_(0), wheel_delta_y_(0), wheel_ticks_x_(0), wheel_ticks_y_(0) { } PepperInputHandler::~PepperInputHandler() { } // Helper function to get the USB key code using the Dev InputEvent interface. uint32_t GetUsbKeyCode(pp::KeyboardInputEvent pp_key_event) { const PPB_KeyboardInputEvent_Dev* key_event_interface = reinterpret_cast<const PPB_KeyboardInputEvent_Dev*>( pp::Module::Get()->GetBrowserInterface( PPB_KEYBOARD_INPUT_EVENT_DEV_INTERFACE)); if (!key_event_interface) return 0; // Get the DOM3 |code| as a string. pp::Var codevar(key_event_interface->GetCode(pp_key_event.pp_resource())); if (!codevar.is_string()) return 0; std::string codestr = codevar.AsString(); // Convert the |code| string into a USB keycode. ui::KeycodeConverter* key_converter = ui::KeycodeConverter::GetInstance(); return key_converter->CodeToUsbKeycode(codestr.c_str()); } bool PepperInputHandler::HandleInputEvent(const pp::InputEvent& event) { switch (event.GetType()) { case PP_INPUTEVENT_TYPE_CONTEXTMENU: { // We need to return true here or else we'll get a local (plugin) context // menu instead of the mouseup event for the right click. return true; } case PP_INPUTEVENT_TYPE_KEYDOWN: case PP_INPUTEVENT_TYPE_KEYUP: { pp::KeyboardInputEvent pp_key_event(event); uint32_t modifiers = event.GetModifiers(); uint32_t lock_states = 0; if (modifiers & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY) lock_states |= protocol::KeyEvent::LOCK_STATES_CAPSLOCK; if (modifiers & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY) lock_states |= protocol::KeyEvent::LOCK_STATES_NUMLOCK; protocol::KeyEvent key_event; key_event.set_usb_keycode(GetUsbKeyCode(pp_key_event)); key_event.set_pressed(event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN); key_event.set_lock_states(lock_states); input_stub_->InjectKeyEvent(key_event); return true; } case PP_INPUTEVENT_TYPE_MOUSEDOWN: case PP_INPUTEVENT_TYPE_MOUSEUP: { pp::MouseInputEvent pp_mouse_event(event); protocol::MouseEvent mouse_event; switch (pp_mouse_event.GetButton()) { case PP_INPUTEVENT_MOUSEBUTTON_LEFT: mouse_event.set_button(protocol::MouseEvent::BUTTON_LEFT); break; case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE: mouse_event.set_button(protocol::MouseEvent::BUTTON_MIDDLE); break; case PP_INPUTEVENT_MOUSEBUTTON_RIGHT: mouse_event.set_button(protocol::MouseEvent::BUTTON_RIGHT); break; case PP_INPUTEVENT_MOUSEBUTTON_NONE: break; } if (mouse_event.has_button()) { bool is_down = (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN); mouse_event.set_button_down(is_down); mouse_event.set_x(pp_mouse_event.GetPosition().x()); mouse_event.set_y(pp_mouse_event.GetPosition().y()); // Add relative movement if the mouse is locked. if (mouse_lock_state_ == MouseLockOn) { pp::Point delta = pp_mouse_event.GetMovement(); mouse_event.set_delta_x(delta.x()); mouse_event.set_delta_y(delta.y()); } input_stub_->InjectMouseEvent(mouse_event); } return true; } case PP_INPUTEVENT_TYPE_MOUSEMOVE: case PP_INPUTEVENT_TYPE_MOUSEENTER: case PP_INPUTEVENT_TYPE_MOUSELEAVE: { pp::MouseInputEvent pp_mouse_event(event); protocol::MouseEvent mouse_event; mouse_event.set_x(pp_mouse_event.GetPosition().x()); mouse_event.set_y(pp_mouse_event.GetPosition().y()); // Add relative movement if the mouse is locked. if (mouse_lock_state_ == MouseLockOn) { pp::Point delta = pp_mouse_event.GetMovement(); mouse_event.set_delta_x(delta.x()); mouse_event.set_delta_y(delta.y()); } input_stub_->InjectMouseEvent(mouse_event); return true; } case PP_INPUTEVENT_TYPE_WHEEL: { pp::WheelInputEvent pp_wheel_event(event); // Don't handle scroll-by-page events, for now. if (pp_wheel_event.GetScrollByPage()) return false; // Add this event to our accumulated sub-pixel deltas and clicks. pp::FloatPoint delta = pp_wheel_event.GetDelta(); wheel_delta_x_ += delta.x(); wheel_delta_y_ += delta.y(); pp::FloatPoint ticks = pp_wheel_event.GetTicks(); wheel_ticks_x_ += ticks.x(); wheel_ticks_y_ += ticks.y(); // If there is at least a pixel's movement, emit an event. We don't // ever expect to accumulate one tick's worth of scrolling without // accumulating a pixel's worth at the same time, so this is safe. int delta_x = static_cast<int>(wheel_delta_x_); int delta_y = static_cast<int>(wheel_delta_y_); if (delta_x != 0 || delta_y != 0) { wheel_delta_x_ -= delta_x; wheel_delta_y_ -= delta_y; protocol::MouseEvent mouse_event; mouse_event.set_wheel_delta_x(delta_x); mouse_event.set_wheel_delta_y(delta_y); // Always include the ticks in the event, even if insufficient pixel // scrolling has accumulated for a single tick. This informs hosts // that can't inject pixel-based scroll events that the client will // accumulate them into tick-based scrolling, which gives a better // overall experience than trying to do this host-side. int ticks_x = static_cast<int>(wheel_ticks_x_); int ticks_y = static_cast<int>(wheel_ticks_y_); wheel_ticks_x_ -= ticks_x; wheel_ticks_y_ -= ticks_y; mouse_event.set_wheel_ticks_x(ticks_x); mouse_event.set_wheel_ticks_y(ticks_y); input_stub_->InjectMouseEvent(mouse_event); } return true; } case PP_INPUTEVENT_TYPE_CHAR: // Consume but ignore character input events. return true; default: { VLOG(0) << "Unhandled input event: " << event.GetType(); break; } } return false; } void PepperInputHandler::AllowMouseLock() { DCHECK_EQ(mouse_lock_state_, MouseLockDisallowed); mouse_lock_state_ = MouseLockOff; } void PepperInputHandler::DidChangeFocus(bool has_focus) { has_focus_ = has_focus; if (has_focus_) RequestMouseLock(); } void PepperInputHandler::SetMouseCursor(scoped_ptr<pp::ImageData> image, const pp::Point& hotspot) { cursor_image_ = image.Pass(); cursor_hotspot_ = hotspot; if (mouse_lock_state_ != MouseLockDisallowed && !cursor_image_) { RequestMouseLock(); } else { CancelMouseLock(); } } void PepperInputHandler::MouseLockLost() { DCHECK(mouse_lock_state_ == MouseLockOn || mouse_lock_state_ == MouseLockCancelling); mouse_lock_state_ = MouseLockOff; UpdateMouseCursor(); } void PepperInputHandler::RequestMouseLock() { // Request mouse lock only if the plugin is focused, the host-supplied cursor // is empty and no callback is pending. if (has_focus_ && !cursor_image_ && mouse_lock_state_ == MouseLockOff) { pp::CompletionCallback callback = callback_factory_.NewCallback(&PepperInputHandler::OnMouseLocked); int result = pp::MouseLock::LockMouse(callback); DCHECK_EQ(result, PP_OK_COMPLETIONPENDING); // Hide cursor to avoid it becoming a black square (see crbug.com/285809). pp::MouseCursor::SetCursor(instance_, PP_MOUSECURSOR_TYPE_NONE); mouse_lock_state_ = MouseLockRequestPending; } } void PepperInputHandler::CancelMouseLock() { switch (mouse_lock_state_) { case MouseLockDisallowed: case MouseLockOff: UpdateMouseCursor(); break; case MouseLockCancelling: break; case MouseLockRequestPending: // The mouse lock request is pending. Delay UnlockMouse() call until // the callback is called. mouse_lock_state_ = MouseLockCancelling; break; case MouseLockOn: pp::MouseLock::UnlockMouse(); // Note that mouse-lock has been cancelled. We will continue to receive // locked events until MouseLockLost() is called back. mouse_lock_state_ = MouseLockCancelling; break; default: NOTREACHED(); } } void PepperInputHandler::UpdateMouseCursor() { DCHECK(mouse_lock_state_ == MouseLockDisallowed || mouse_lock_state_ == MouseLockOff); if (cursor_image_) { pp::MouseCursor::SetCursor(instance_, PP_MOUSECURSOR_TYPE_CUSTOM, *cursor_image_, cursor_hotspot_); } else { // If there is no cursor shape stored, either because the host never // supplied one, or we were previously in mouse-lock mode, then use // a standard arrow pointer. pp::MouseCursor::SetCursor(instance_, PP_MOUSECURSOR_TYPE_POINTER); } } void PepperInputHandler::OnMouseLocked(int error) { DCHECK(mouse_lock_state_ == MouseLockRequestPending || mouse_lock_state_ == MouseLockCancelling); bool should_cancel = (mouse_lock_state_ == MouseLockCancelling); // See if the operation succeeded. if (error == PP_OK) { mouse_lock_state_ = MouseLockOn; } else { mouse_lock_state_ = MouseLockOff; UpdateMouseCursor(); } // Cancel as needed. if (should_cancel) CancelMouseLock(); } } // namespace remoting