// 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/host/input_injector.h" #include #include #include #include "base/basictypes.h" #include "base/bind.h" #include "base/compiler_specific.h" #include "base/location.h" #include "base/mac/scoped_cftyperef.h" #include "base/memory/ref_counted.h" #include "base/single_thread_task_runner.h" #include "remoting/host/clipboard.h" #include "remoting/proto/internal.pb.h" #include "remoting/protocol/message_decoder.h" #include "skia/ext/skia_utils_mac.h" #include "third_party/skia/include/core/SkPoint.h" #include "third_party/skia/include/core/SkRect.h" #include "third_party/webrtc/modules/desktop_capture/mac/desktop_configuration.h" namespace remoting { namespace { using protocol::ClipboardEvent; using protocol::KeyEvent; using protocol::MouseEvent; // USB to Mac keycode mapping table. #define USB_KEYMAP(usb, xkb, win, mac) {usb, mac} #include "ui/base/keycodes/usb_keycode_map.h" #undef USB_KEYMAP // skia/ext/skia_utils_mac.h only defines CGRectToSkRect(). SkIRect CGRectToSkIRect(const CGRect& rect) { SkIRect result; gfx::CGRectToSkRect(rect).round(&result); return result; } // A class to generate events on Mac. class InputInjectorMac : public InputInjector { public: explicit InputInjectorMac( scoped_refptr task_runner); virtual ~InputInjectorMac(); // ClipboardStub interface. virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE; // InputStub interface. virtual void InjectKeyEvent(const KeyEvent& event) OVERRIDE; virtual void InjectMouseEvent(const MouseEvent& event) OVERRIDE; // InputInjector interface. virtual void Start( scoped_ptr client_clipboard) OVERRIDE; private: // The actual implementation resides in InputInjectorMac::Core class. class Core : public base::RefCountedThreadSafe { public: explicit Core(scoped_refptr task_runner); // Mirrors the ClipboardStub interface. void InjectClipboardEvent(const ClipboardEvent& event); // Mirrors the InputStub interface. void InjectKeyEvent(const KeyEvent& event); void InjectMouseEvent(const MouseEvent& event); // Mirrors the InputInjector interface. void Start(scoped_ptr client_clipboard); void Stop(); private: friend class base::RefCountedThreadSafe; virtual ~Core(); scoped_refptr task_runner_; SkIPoint mouse_pos_; uint32 mouse_button_state_; scoped_ptr clipboard_; DISALLOW_COPY_AND_ASSIGN(Core); }; scoped_refptr core_; DISALLOW_COPY_AND_ASSIGN(InputInjectorMac); }; InputInjectorMac::InputInjectorMac( scoped_refptr task_runner) { core_ = new Core(task_runner); } InputInjectorMac::~InputInjectorMac() { core_->Stop(); } void InputInjectorMac::InjectClipboardEvent(const ClipboardEvent& event) { core_->InjectClipboardEvent(event); } void InputInjectorMac::InjectKeyEvent(const KeyEvent& event) { core_->InjectKeyEvent(event); } void InputInjectorMac::InjectMouseEvent(const MouseEvent& event) { core_->InjectMouseEvent(event); } void InputInjectorMac::Start( scoped_ptr client_clipboard) { core_->Start(client_clipboard.Pass()); } InputInjectorMac::Core::Core( scoped_refptr task_runner) : task_runner_(task_runner), mouse_button_state_(0), clipboard_(Clipboard::Create()) { // Ensure that local hardware events are not suppressed after injecting // input events. This allows LocalInputMonitor to detect if the local mouse // is being moved whilst a remote user is connected. // This API is deprecated, but it is needed when using the deprecated // injection APIs. // If the non-deprecated injection APIs were used instead, the equivalent of // this line would not be needed, as OS X defaults to _not_ suppressing local // inputs in that case. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CGSetLocalEventsSuppressionInterval(0.0); #pragma clang diagnostic pop } void InputInjectorMac::Core::InjectClipboardEvent(const ClipboardEvent& event) { if (!task_runner_->BelongsToCurrentThread()) { task_runner_->PostTask( FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event)); return; } // |clipboard_| will ignore unknown MIME-types, and verify the data's format. clipboard_->InjectClipboardEvent(event); } void InputInjectorMac::Core::InjectKeyEvent(const KeyEvent& event) { // HostEventDispatcher should filter events missing the pressed field. if (!event.has_pressed() || !event.has_usb_keycode()) return; int keycode = UsbKeycodeToNativeKeycode(event.usb_keycode()); VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode() << " to keycode: " << keycode << std::dec; // If we couldn't determine the Mac virtual key code then ignore the event. if (keycode == InvalidNativeKeycode()) return; // We use the deprecated event injection API because the new one doesn't // work with switched-out sessions (curtain mode). #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CGError error = CGPostKeyboardEvent(0, keycode, event.pressed()); #pragma clang diagnostic pop if (error != kCGErrorSuccess) LOG(WARNING) << "CGPostKeyboardEvent error " << error; } void InputInjectorMac::Core::InjectMouseEvent(const MouseEvent& event) { if (event.has_x() && event.has_y()) { // On multi-monitor systems (0,0) refers to the top-left of the "main" // display, whereas our coordinate scheme places (0,0) at the top-left of // the bounding rectangle around all the displays, so we need to translate // accordingly. // Set the mouse position assuming single-monitor. mouse_pos_ = SkIPoint::Make(event.x(), event.y()); // Fetch the desktop configuration. // TODO(wez): Optimize this out, or at least only enumerate displays in // response to display-changed events. VideoFrameCapturer's VideoFrames // could be augmented to include native cursor coordinates for use by // MouseClampingFilter, removing the need for translation here. webrtc::MacDesktopConfiguration desktop_config = webrtc::MacDesktopConfiguration::GetCurrent( webrtc::MacDesktopConfiguration::TopLeftOrigin); // Translate the mouse position into desktop coordinates. mouse_pos_ += SkIPoint::Make(desktop_config.pixel_bounds.left(), desktop_config.pixel_bounds.top()); // Constrain the mouse position to the desktop coordinates. mouse_pos_ = SkIPoint::Make( std::max(desktop_config.pixel_bounds.left(), std::min(desktop_config.pixel_bounds.right(), mouse_pos_.x())), std::max(desktop_config.pixel_bounds.top(), std::min(desktop_config.pixel_bounds.bottom(), mouse_pos_.y()))); // Convert from pixel to Density Independent Pixel coordinates. mouse_pos_ = SkIPoint::Make( SkScalarRound(mouse_pos_.x() / desktop_config.dip_to_pixel_scale), SkScalarRound(mouse_pos_.y() / desktop_config.dip_to_pixel_scale)); VLOG(3) << "Moving mouse to " << mouse_pos_.x() << "," << mouse_pos_.y(); } if (event.has_button() && event.has_button_down()) { if (event.button() >= 1 && event.button() <= 3) { VLOG(2) << "Button " << event.button() << (event.button_down() ? " down" : " up"); int button_change = 1 << (event.button() - 1); if (event.button_down()) mouse_button_state_ |= button_change; else mouse_button_state_ &= ~button_change; } else { VLOG(1) << "Unknown mouse button: " << event.button(); } } // We use the deprecated CGPostMouseEvent API because we receive low-level // mouse events, whereas CGEventCreateMouseEvent is for injecting higher-level // events. For example, the deprecated APIs will detect double-clicks or drags // in a way that is consistent with how they would be generated using a local // mouse, whereas the new APIs expect us to inject these higher-level events // directly. CGPoint position = CGPointMake(mouse_pos_.x(), mouse_pos_.y()); enum { LeftBit = 1 << (MouseEvent::BUTTON_LEFT - 1), MiddleBit = 1 << (MouseEvent::BUTTON_MIDDLE - 1), RightBit = 1 << (MouseEvent::BUTTON_RIGHT - 1) }; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CGError error = CGPostMouseEvent(position, true, 3, (mouse_button_state_ & LeftBit) != 0, (mouse_button_state_ & RightBit) != 0, (mouse_button_state_ & MiddleBit) != 0); #pragma clang diagnostic pop if (error != kCGErrorSuccess) LOG(WARNING) << "CGPostMouseEvent error " << error; if (event.has_wheel_delta_x() && event.has_wheel_delta_y()) { int delta_x = static_cast(event.wheel_delta_x()); int delta_y = static_cast(event.wheel_delta_y()); base::mac::ScopedCFTypeRef event( CGEventCreateScrollWheelEvent( NULL, kCGScrollEventUnitPixel, 2, delta_y, delta_x)); if (event) CGEventPost(kCGSessionEventTap, event); } } void InputInjectorMac::Core::Start( scoped_ptr client_clipboard) { if (!task_runner_->BelongsToCurrentThread()) { task_runner_->PostTask( FROM_HERE, base::Bind(&Core::Start, this, base::Passed(&client_clipboard))); return; } clipboard_->Start(client_clipboard.Pass()); } void InputInjectorMac::Core::Stop() { if (!task_runner_->BelongsToCurrentThread()) { task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this)); return; } clipboard_->Stop(); } InputInjectorMac::Core::~Core() { } } // namespace scoped_ptr InputInjector::Create( scoped_refptr main_task_runner, scoped_refptr ui_task_runner) { return scoped_ptr(new InputInjectorMac(main_task_runner)); } } // namespace remoting