// 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/protocol/input_event_tracker.h" #include "base/logging.h" #include "remoting/proto/event.pb.h" namespace remoting { namespace protocol { InputEventTracker::InputEventTracker() {} InputEventTracker::InputEventTracker(InputStub* input_stub) : input_stub_(input_stub) { } InputEventTracker::~InputEventTracker() {} bool InputEventTracker::IsKeyPressed(ui::DomCode usb_keycode) const { return pressed_keys_.find(usb_keycode) != pressed_keys_.end(); } int InputEventTracker::PressedKeyCount() const { return pressed_keys_.size(); } void InputEventTracker::ReleaseAll() { DCHECK(input_stub_); // Release all pressed keys. for (auto keycode : pressed_keys_) { KeyEvent event; event.set_pressed(false); event.set_usb_keycode(static_cast(keycode)); input_stub_->InjectKeyEvent(event); } pressed_keys_.clear(); // Release all mouse buttons. for (int i = MouseEvent::BUTTON_UNDEFINED + 1; i < MouseEvent::BUTTON_MAX; ++i) { if (mouse_button_state_ & (1 << (i - 1))) { MouseEvent mouse; // TODO(wez): EventInjectors should cope with positionless events by // using the current cursor position, and we wouldn't set position here. mouse.set_x(mouse_pos_.x()); mouse.set_y(mouse_pos_.y()); mouse.set_button((MouseEvent::MouseButton)i); mouse.set_button_down(false); input_stub_->InjectMouseEvent(mouse); } } mouse_button_state_ = 0; // Cancel all active touch points. if (!touch_point_ids_.empty()) { TouchEvent cancel_all_touch_event; cancel_all_touch_event.set_event_type(TouchEvent::TOUCH_POINT_CANCEL); for (uint32_t touch_point_id : touch_point_ids_) { TouchEventPoint* point = cancel_all_touch_event.add_touch_points(); point->set_id(touch_point_id); } input_stub_->InjectTouchEvent(cancel_all_touch_event); touch_point_ids_.clear(); } DCHECK(touch_point_ids_.empty()); } void InputEventTracker::ReleaseAllIfModifiersStuck(bool alt_expected, bool ctrl_expected, bool os_expected, bool shift_expected) { bool alt_down = pressed_keys_.find(ui::DomCode::ALT_LEFT) != pressed_keys_.end() || pressed_keys_.find(ui::DomCode::ALT_RIGHT) != pressed_keys_.end(); bool ctrl_down = pressed_keys_.find(ui::DomCode::CONTROL_LEFT) != pressed_keys_.end() || pressed_keys_.find(ui::DomCode::CONTROL_RIGHT) != pressed_keys_.end(); bool os_down = pressed_keys_.find(ui::DomCode::OS_LEFT) != pressed_keys_.end() || pressed_keys_.find(ui::DomCode::OS_RIGHT) != pressed_keys_.end(); bool shift_down = pressed_keys_.find(ui::DomCode::SHIFT_LEFT) != pressed_keys_.end() || pressed_keys_.find(ui::DomCode::SHIFT_RIGHT) != pressed_keys_.end(); if ((alt_down && !alt_expected) || (ctrl_down && !ctrl_expected) || (os_down && !os_expected) || (shift_down && !shift_expected)) { ReleaseAll(); } } void InputEventTracker::InjectKeyEvent(const KeyEvent& event) { DCHECK(input_stub_); // We don't need to track the keyboard lock states of key down events. // Pressed keys will be released with |lock_states| set to 0. // The lock states of auto generated key up events don't matter as long as // we release all the pressed keys at blurring/disconnection time. if (event.has_pressed()) { if (event.has_usb_keycode()) { if (event.pressed()) { pressed_keys_.insert(static_cast(event.usb_keycode())); } else { pressed_keys_.erase(static_cast(event.usb_keycode())); } } } input_stub_->InjectKeyEvent(event); } void InputEventTracker::InjectTextEvent(const TextEvent& event) { DCHECK(input_stub_); input_stub_->InjectTextEvent(event); } void InputEventTracker::InjectMouseEvent(const MouseEvent& event) { DCHECK(input_stub_); if (event.has_x() && event.has_y()) { mouse_pos_ = webrtc::DesktopVector(event.x(), event.y()); } if (event.has_button() && event.has_button_down()) { // Button values are defined in remoting/proto/event.proto. if (event.button() >= 1 && event.button() < MouseEvent::BUTTON_MAX) { uint32_t button_change = 1 << (event.button() - 1); if (event.button_down()) { mouse_button_state_ |= button_change; } else { mouse_button_state_ &= ~button_change; } } } input_stub_->InjectMouseEvent(event); } void InputEventTracker::InjectTouchEvent(const TouchEvent& event) { DCHECK(input_stub_); // We only need the IDs to cancel all touch points in ReleaseAll(). Other // fields do not have to be tracked here as long as the host keeps track of // them. switch (event.event_type()) { case TouchEvent::TOUCH_POINT_START: for (const TouchEventPoint& touch_point : event.touch_points()) { DCHECK(touch_point_ids_.find(touch_point.id()) == touch_point_ids_.end()); touch_point_ids_.insert(touch_point.id()); } break; case TouchEvent::TOUCH_POINT_END: case TouchEvent::TOUCH_POINT_CANCEL: for (const TouchEventPoint& touch_point : event.touch_points()) { DCHECK(touch_point_ids_.find(touch_point.id()) != touch_point_ids_.end()); touch_point_ids_.erase(touch_point.id()); } break; default: break; } input_stub_->InjectTouchEvent(event); } } // namespace protocol } // namespace remoting