| // 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 "remoting/proto/event.pb.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; |
| return key_event_interface->GetUsbKeyCode(pp_key_event.pp_resource()); |
| } |
| |
| 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: { |
| LOG(INFO) << "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 |