Home | History | Annotate | Download | only in host
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "remoting/host/input_injector.h"
      6 
      7 #include <algorithm>
      8 #include <ApplicationServices/ApplicationServices.h>
      9 #include <Carbon/Carbon.h>
     10 
     11 #include "base/basictypes.h"
     12 #include "base/bind.h"
     13 #include "base/compiler_specific.h"
     14 #include "base/location.h"
     15 #include "base/mac/scoped_cftyperef.h"
     16 #include "base/memory/ref_counted.h"
     17 #include "base/single_thread_task_runner.h"
     18 #include "remoting/host/clipboard.h"
     19 #include "remoting/proto/internal.pb.h"
     20 #include "remoting/protocol/message_decoder.h"
     21 #include "skia/ext/skia_utils_mac.h"
     22 #include "third_party/skia/include/core/SkPoint.h"
     23 #include "third_party/skia/include/core/SkRect.h"
     24 #include "third_party/webrtc/modules/desktop_capture/mac/desktop_configuration.h"
     25 
     26 namespace remoting {
     27 
     28 namespace {
     29 
     30 using protocol::ClipboardEvent;
     31 using protocol::KeyEvent;
     32 using protocol::MouseEvent;
     33 
     34 // USB to Mac keycode mapping table.
     35 #define USB_KEYMAP(usb, xkb, win, mac) {usb, mac}
     36 #include "ui/base/keycodes/usb_keycode_map.h"
     37 #undef USB_KEYMAP
     38 
     39 // skia/ext/skia_utils_mac.h only defines CGRectToSkRect().
     40 SkIRect CGRectToSkIRect(const CGRect& rect) {
     41   SkIRect result;
     42   gfx::CGRectToSkRect(rect).round(&result);
     43   return result;
     44 }
     45 
     46 // A class to generate events on Mac.
     47 class InputInjectorMac : public InputInjector {
     48  public:
     49   explicit InputInjectorMac(
     50       scoped_refptr<base::SingleThreadTaskRunner> task_runner);
     51   virtual ~InputInjectorMac();
     52 
     53   // ClipboardStub interface.
     54   virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE;
     55 
     56   // InputStub interface.
     57   virtual void InjectKeyEvent(const KeyEvent& event) OVERRIDE;
     58   virtual void InjectMouseEvent(const MouseEvent& event) OVERRIDE;
     59 
     60   // InputInjector interface.
     61   virtual void Start(
     62       scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
     63 
     64  private:
     65   // The actual implementation resides in InputInjectorMac::Core class.
     66   class Core : public base::RefCountedThreadSafe<Core> {
     67    public:
     68     explicit Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
     69 
     70     // Mirrors the ClipboardStub interface.
     71     void InjectClipboardEvent(const ClipboardEvent& event);
     72 
     73     // Mirrors the InputStub interface.
     74     void InjectKeyEvent(const KeyEvent& event);
     75     void InjectMouseEvent(const MouseEvent& event);
     76 
     77     // Mirrors the InputInjector interface.
     78     void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard);
     79 
     80     void Stop();
     81 
     82    private:
     83     friend class base::RefCountedThreadSafe<Core>;
     84     virtual ~Core();
     85 
     86     scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
     87     SkIPoint mouse_pos_;
     88     uint32 mouse_button_state_;
     89     scoped_ptr<Clipboard> clipboard_;
     90 
     91     DISALLOW_COPY_AND_ASSIGN(Core);
     92   };
     93 
     94   scoped_refptr<Core> core_;
     95 
     96   DISALLOW_COPY_AND_ASSIGN(InputInjectorMac);
     97 };
     98 
     99 InputInjectorMac::InputInjectorMac(
    100     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
    101   core_ = new Core(task_runner);
    102 }
    103 
    104 InputInjectorMac::~InputInjectorMac() {
    105   core_->Stop();
    106 }
    107 
    108 void InputInjectorMac::InjectClipboardEvent(const ClipboardEvent& event) {
    109   core_->InjectClipboardEvent(event);
    110 }
    111 
    112 void InputInjectorMac::InjectKeyEvent(const KeyEvent& event) {
    113   core_->InjectKeyEvent(event);
    114 }
    115 
    116 void InputInjectorMac::InjectMouseEvent(const MouseEvent& event) {
    117   core_->InjectMouseEvent(event);
    118 }
    119 
    120 void InputInjectorMac::Start(
    121     scoped_ptr<protocol::ClipboardStub> client_clipboard) {
    122   core_->Start(client_clipboard.Pass());
    123 }
    124 
    125 InputInjectorMac::Core::Core(
    126     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
    127     : task_runner_(task_runner),
    128       mouse_button_state_(0),
    129       clipboard_(Clipboard::Create()) {
    130   // Ensure that local hardware events are not suppressed after injecting
    131   // input events.  This allows LocalInputMonitor to detect if the local mouse
    132   // is being moved whilst a remote user is connected.
    133   // This API is deprecated, but it is needed when using the deprecated
    134   // injection APIs.
    135   // If the non-deprecated injection APIs were used instead, the equivalent of
    136   // this line would not be needed, as OS X defaults to _not_ suppressing local
    137   // inputs in that case.
    138 #pragma clang diagnostic push
    139 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
    140   CGSetLocalEventsSuppressionInterval(0.0);
    141 #pragma clang diagnostic pop
    142 }
    143 
    144 void InputInjectorMac::Core::InjectClipboardEvent(const ClipboardEvent& event) {
    145   if (!task_runner_->BelongsToCurrentThread()) {
    146     task_runner_->PostTask(
    147         FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event));
    148     return;
    149   }
    150 
    151   // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
    152   clipboard_->InjectClipboardEvent(event);
    153 }
    154 
    155 void InputInjectorMac::Core::InjectKeyEvent(const KeyEvent& event) {
    156   // HostEventDispatcher should filter events missing the pressed field.
    157   if (!event.has_pressed() || !event.has_usb_keycode())
    158     return;
    159 
    160   int keycode = UsbKeycodeToNativeKeycode(event.usb_keycode());
    161 
    162   VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode()
    163           << " to keycode: " << keycode << std::dec;
    164 
    165   // If we couldn't determine the Mac virtual key code then ignore the event.
    166   if (keycode == InvalidNativeKeycode())
    167     return;
    168 
    169   base::ScopedCFTypeRef<CGEventRef> eventRef(
    170       CGEventCreateKeyboardEvent(NULL, keycode, event.pressed()));
    171 
    172   if (eventRef) {
    173     // We only need to manually set CapsLock: Mac ignores NumLock.
    174     // Modifier keys work correctly already via press/release event injection.
    175     if (event.lock_states() & protocol::KeyEvent::LOCK_STATES_CAPSLOCK)
    176       CGEventSetFlags(eventRef, kCGEventFlagMaskAlphaShift);
    177 
    178     // Post the event to the current session.
    179     CGEventPost(kCGSessionEventTap, eventRef);
    180   }
    181 }
    182 
    183 void InputInjectorMac::Core::InjectMouseEvent(const MouseEvent& event) {
    184   if (event.has_x() && event.has_y()) {
    185     // On multi-monitor systems (0,0) refers to the top-left of the "main"
    186     // display, whereas our coordinate scheme places (0,0) at the top-left of
    187     // the bounding rectangle around all the displays, so we need to translate
    188     // accordingly.
    189 
    190     // Set the mouse position assuming single-monitor.
    191     mouse_pos_ = SkIPoint::Make(event.x(), event.y());
    192 
    193     // Fetch the desktop configuration.
    194     // TODO(wez): Optimize this out, or at least only enumerate displays in
    195     // response to display-changed events. VideoFrameCapturer's VideoFrames
    196     // could be augmented to include native cursor coordinates for use by
    197     // MouseClampingFilter, removing the need for translation here.
    198     webrtc::MacDesktopConfiguration desktop_config =
    199         webrtc::MacDesktopConfiguration::GetCurrent(
    200             webrtc::MacDesktopConfiguration::TopLeftOrigin);
    201 
    202     // Translate the mouse position into desktop coordinates.
    203     mouse_pos_ += SkIPoint::Make(desktop_config.pixel_bounds.left(),
    204                                  desktop_config.pixel_bounds.top());
    205 
    206     // Constrain the mouse position to the desktop coordinates.
    207     mouse_pos_ = SkIPoint::Make(
    208        std::max(desktop_config.pixel_bounds.left(),
    209            std::min(desktop_config.pixel_bounds.right(), mouse_pos_.x())),
    210        std::max(desktop_config.pixel_bounds.top(),
    211            std::min(desktop_config.pixel_bounds.bottom(), mouse_pos_.y())));
    212 
    213     // Convert from pixel to Density Independent Pixel coordinates.
    214     mouse_pos_ = SkIPoint::Make(
    215         SkScalarRound(mouse_pos_.x() / desktop_config.dip_to_pixel_scale),
    216         SkScalarRound(mouse_pos_.y() / desktop_config.dip_to_pixel_scale));
    217 
    218     VLOG(3) << "Moving mouse to " << mouse_pos_.x() << "," << mouse_pos_.y();
    219   }
    220   if (event.has_button() && event.has_button_down()) {
    221     if (event.button() >= 1 && event.button() <= 3) {
    222       VLOG(2) << "Button " << event.button()
    223               << (event.button_down() ? " down" : " up");
    224       int button_change = 1 << (event.button() - 1);
    225       if (event.button_down())
    226         mouse_button_state_ |= button_change;
    227       else
    228         mouse_button_state_ &= ~button_change;
    229     } else {
    230       VLOG(1) << "Unknown mouse button: " << event.button();
    231     }
    232   }
    233   // We use the deprecated CGPostMouseEvent API because we receive low-level
    234   // mouse events, whereas CGEventCreateMouseEvent is for injecting higher-level
    235   // events. For example, the deprecated APIs will detect double-clicks or drags
    236   // in a way that is consistent with how they would be generated using a local
    237   // mouse, whereas the new APIs expect us to inject these higher-level events
    238   // directly.
    239   CGPoint position = CGPointMake(mouse_pos_.x(), mouse_pos_.y());
    240   enum {
    241     LeftBit = 1 << (MouseEvent::BUTTON_LEFT - 1),
    242     MiddleBit = 1 << (MouseEvent::BUTTON_MIDDLE - 1),
    243     RightBit = 1 << (MouseEvent::BUTTON_RIGHT - 1)
    244   };
    245 #pragma clang diagnostic push
    246 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
    247   CGError error = CGPostMouseEvent(position, true, 3,
    248                                    (mouse_button_state_ & LeftBit) != 0,
    249                                    (mouse_button_state_ & RightBit) != 0,
    250                                    (mouse_button_state_ & MiddleBit) != 0);
    251 #pragma clang diagnostic pop
    252   if (error != kCGErrorSuccess)
    253     LOG(WARNING) << "CGPostMouseEvent error " << error;
    254 
    255   if (event.has_wheel_delta_x() && event.has_wheel_delta_y()) {
    256     int delta_x = static_cast<int>(event.wheel_delta_x());
    257     int delta_y = static_cast<int>(event.wheel_delta_y());
    258     base::ScopedCFTypeRef<CGEventRef> event(CGEventCreateScrollWheelEvent(
    259         NULL, kCGScrollEventUnitPixel, 2, delta_y, delta_x));
    260     if (event)
    261       CGEventPost(kCGSessionEventTap, event);
    262   }
    263 }
    264 
    265 void InputInjectorMac::Core::Start(
    266     scoped_ptr<protocol::ClipboardStub> client_clipboard) {
    267   if (!task_runner_->BelongsToCurrentThread()) {
    268     task_runner_->PostTask(
    269         FROM_HERE,
    270         base::Bind(&Core::Start, this, base::Passed(&client_clipboard)));
    271     return;
    272   }
    273 
    274   clipboard_->Start(client_clipboard.Pass());
    275 }
    276 
    277 void InputInjectorMac::Core::Stop() {
    278   if (!task_runner_->BelongsToCurrentThread()) {
    279     task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this));
    280     return;
    281   }
    282 
    283   clipboard_->Stop();
    284 }
    285 
    286 InputInjectorMac::Core::~Core() {
    287 }
    288 
    289 }  // namespace
    290 
    291 scoped_ptr<InputInjector> InputInjector::Create(
    292     scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
    293     scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
    294   return scoped_ptr<InputInjector>(new InputInjectorMac(main_task_runner));
    295 }
    296 
    297 }  // namespace remoting
    298