| // 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 <stdint.h> |
| |
| #include "base/logging.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/touch_point.h" |
| #include "ppapi/cpp/var.h" |
| #include "remoting/proto/event.pb.h" |
| #include "remoting/protocol/input_event_tracker.h" |
| #include "remoting/protocol/input_stub.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| |
| namespace remoting { |
| |
| namespace { |
| |
| void SetTouchEventType(PP_InputEvent_Type pp_type, |
| protocol::TouchEvent* touch_event) { |
| DCHECK(touch_event); |
| switch (pp_type) { |
| case PP_INPUTEVENT_TYPE_TOUCHSTART: |
| touch_event->set_event_type(protocol::TouchEvent::TOUCH_POINT_START); |
| return; |
| case PP_INPUTEVENT_TYPE_TOUCHMOVE: |
| touch_event->set_event_type(protocol::TouchEvent::TOUCH_POINT_MOVE); |
| return; |
| case PP_INPUTEVENT_TYPE_TOUCHEND: |
| touch_event->set_event_type(protocol::TouchEvent::TOUCH_POINT_END); |
| return; |
| case PP_INPUTEVENT_TYPE_TOUCHCANCEL: |
| touch_event->set_event_type(protocol::TouchEvent::TOUCH_POINT_CANCEL); |
| return; |
| default: |
| NOTREACHED() << "Unknown event type: " << pp_type; |
| return; |
| } |
| } |
| |
| // Creates a protocol::TouchEvent instance from |pp_touch_event|. |
| // Note that only the changed touches are added to the TouchEvent. |
| protocol::TouchEvent MakeTouchEvent(const pp::TouchInputEvent& pp_touch_event) { |
| protocol::TouchEvent touch_event; |
| SetTouchEventType(pp_touch_event.GetType(), &touch_event); |
| DCHECK(touch_event.has_event_type()); |
| |
| for (uint32_t i = 0; |
| i < pp_touch_event.GetTouchCount(PP_TOUCHLIST_TYPE_CHANGEDTOUCHES); |
| ++i) { |
| pp::TouchPoint pp_point = |
| pp_touch_event.GetTouchByIndex(PP_TOUCHLIST_TYPE_CHANGEDTOUCHES, i); |
| protocol::TouchEventPoint* point = touch_event.add_touch_points(); |
| point->set_id(pp_point.id()); |
| point->set_x(pp_point.position().x()); |
| point->set_y(pp_point.position().y()); |
| point->set_radius_x(pp_point.radii().x()); |
| point->set_radius_y(pp_point.radii().y()); |
| point->set_angle(pp_point.rotation_angle()); |
| } |
| |
| return touch_event; |
| } |
| |
| // Builds the Chromotocol lock states flags for the PPAPI |event|. |
| uint32_t MakeLockStates(const pp::InputEvent& 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; |
| |
| return lock_states; |
| } |
| |
| // Builds a protocol::KeyEvent from the supplied PPAPI event. |
| protocol::KeyEvent MakeKeyEvent(const pp::KeyboardInputEvent& pp_key_event) { |
| protocol::KeyEvent key_event; |
| std::string dom_code = pp_key_event.GetCode().AsString(); |
| // Chrome M52 changed the string representation of the left and right OS |
| // keys, which means that if the client plugin is compiled against a |
| // different version of the mapping table, the lookup will fail. The long- |
| // term solution is to use JavaScript input events, but for now just check |
| // explicitly for the old names and convert them to the new ones. |
| if (dom_code == "OSLeft") { |
| dom_code = "MetaLeft"; |
| } else if (dom_code == "OSRight") { |
| dom_code = "MetaRight"; |
| } |
| key_event.set_usb_keycode(ui::KeycodeConverter::CodeToUsbKeycode(dom_code)); |
| key_event.set_pressed(pp_key_event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN); |
| key_event.set_lock_states(MakeLockStates(pp_key_event)); |
| return key_event; |
| } |
| |
| // Builds a protocol::MouseEvent from the supplied PPAPI event. |
| protocol::MouseEvent MakeMouseEvent(const pp::MouseInputEvent& pp_mouse_event, |
| bool set_deltas) { |
| protocol::MouseEvent mouse_event; |
| mouse_event.set_x(pp_mouse_event.GetPosition().x()); |
| mouse_event.set_y(pp_mouse_event.GetPosition().y()); |
| if (set_deltas) { |
| pp::Point delta = pp_mouse_event.GetMovement(); |
| mouse_event.set_delta_x(delta.x()); |
| mouse_event.set_delta_y(delta.y()); |
| } |
| return mouse_event; |
| } |
| |
| } // namespace |
| |
| PepperInputHandler::PepperInputHandler( |
| protocol::InputEventTracker* input_tracker) |
| : input_tracker_(input_tracker), |
| has_focus_(false), |
| send_mouse_input_when_unfocused_(false), |
| send_mouse_move_deltas_(false), |
| wheel_delta_x_(0), |
| wheel_delta_y_(0), |
| wheel_ticks_x_(0), |
| wheel_ticks_y_(0), |
| detect_stuck_modifiers_(false) { |
| } |
| |
| bool PepperInputHandler::HandleInputEvent(const pp::InputEvent& event) { |
| if (detect_stuck_modifiers_) |
| ReleaseAllIfModifiersStuck(event); |
| |
| switch (event.GetType()) { |
| // Touch input cases. |
| case PP_INPUTEVENT_TYPE_TOUCHSTART: |
| case PP_INPUTEVENT_TYPE_TOUCHMOVE: |
| case PP_INPUTEVENT_TYPE_TOUCHEND: |
| case PP_INPUTEVENT_TYPE_TOUCHCANCEL: { |
| pp::TouchInputEvent pp_touch_event(event); |
| input_tracker_->InjectTouchEvent(MakeTouchEvent(pp_touch_event)); |
| return true; |
| } |
| |
| 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); |
| input_tracker_->InjectKeyEvent(MakeKeyEvent(pp_key_event)); |
| return true; |
| } |
| |
| case PP_INPUTEVENT_TYPE_MOUSEDOWN: |
| case PP_INPUTEVENT_TYPE_MOUSEUP: { |
| if (!has_focus_ && !send_mouse_input_when_unfocused_) |
| return false; |
| |
| pp::MouseInputEvent pp_mouse_event(event); |
| protocol::MouseEvent mouse_event( |
| MakeMouseEvent(pp_mouse_event, send_mouse_move_deltas_)); |
| 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); |
| input_tracker_->InjectMouseEvent(mouse_event); |
| } |
| |
| return true; |
| } |
| |
| case PP_INPUTEVENT_TYPE_MOUSEMOVE: |
| case PP_INPUTEVENT_TYPE_MOUSEENTER: |
| case PP_INPUTEVENT_TYPE_MOUSELEAVE: { |
| if (!has_focus_ && !send_mouse_input_when_unfocused_) |
| return false; |
| |
| pp::MouseInputEvent pp_mouse_event(event); |
| input_tracker_->InjectMouseEvent( |
| MakeMouseEvent(pp_mouse_event, send_mouse_move_deltas_)); |
| |
| return true; |
| } |
| |
| case PP_INPUTEVENT_TYPE_WHEEL: { |
| if (!has_focus_ && !send_mouse_input_when_unfocused_) |
| return false; |
| |
| pp::WheelInputEvent pp_wheel_event(event); |
| |
| // Ignore scroll-by-page events, for now. |
| if (pp_wheel_event.GetScrollByPage()) |
| return true; |
| |
| // 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_tracker_->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::DidChangeFocus(bool has_focus) { |
| has_focus_ = has_focus; |
| } |
| |
| void PepperInputHandler::ReleaseAllIfModifiersStuck( |
| const pp::InputEvent& event) { |
| switch (event.GetType()) { |
| case PP_INPUTEVENT_TYPE_MOUSEMOVE: |
| case PP_INPUTEVENT_TYPE_MOUSEENTER: |
| case PP_INPUTEVENT_TYPE_MOUSELEAVE: |
| // Don't check modifiers on every mouse move event. |
| break; |
| |
| case PP_INPUTEVENT_TYPE_KEYUP: |
| // PPAPI doesn't always set modifiers correctly on KEYUP events. See |
| // crbug.com/464791 for details. |
| break; |
| |
| default: { |
| uint32_t modifiers = event.GetModifiers(); |
| input_tracker_->ReleaseAllIfModifiersStuck( |
| (modifiers & PP_INPUTEVENT_MODIFIER_ALTKEY) != 0, |
| (modifiers & PP_INPUTEVENT_MODIFIER_CONTROLKEY) != 0, |
| (modifiers & PP_INPUTEVENT_MODIFIER_METAKEY) != 0, |
| (modifiers & PP_INPUTEVENT_MODIFIER_SHIFTKEY) != 0); |
| } |
| } |
| } |
| |
| } // namespace remoting |