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 <ApplicationServices/ApplicationServices.h>
      8 #include <Carbon/Carbon.h>
      9 #include <algorithm>
     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 "base/strings/utf_string_conversions.h"
     19 #include "remoting/host/clipboard.h"
     20 #include "remoting/proto/internal.pb.h"
     21 #include "remoting/protocol/message_decoder.h"
     22 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
     23 #include "third_party/webrtc/modules/desktop_capture/mac/desktop_configuration.h"
     24 #include "ui/events/keycodes/dom4/keycode_converter.h"
     25 
     26 namespace remoting {
     27 
     28 namespace {
     29 
     30 void SetOrClearBit(uint64_t &value, uint64_t bit, bool set_bit) {
     31   value = set_bit ? (value | bit) : (value & ~bit);
     32 }
     33 
     34 void CreateAndPostKeyEvent(int keycode,
     35                            bool pressed,
     36                            int flags,
     37                            const base::string16& unicode) {
     38   base::ScopedCFTypeRef<CGEventRef> eventRef(
     39       CGEventCreateKeyboardEvent(NULL, keycode, pressed));
     40   if (eventRef) {
     41     CGEventSetFlags(eventRef, flags);
     42     if (!unicode.empty())
     43       CGEventKeyboardSetUnicodeString(eventRef, unicode.size(), &(unicode[0]));
     44     CGEventPost(kCGSessionEventTap, eventRef);
     45   }
     46 }
     47 
     48 // This value is not defined. Give it the obvious name so that if it is ever
     49 // added there will be a handy compilation error to remind us to remove this
     50 // definition.
     51 const int kVK_RightCommand = 0x36;
     52 
     53 using protocol::ClipboardEvent;
     54 using protocol::KeyEvent;
     55 using protocol::TextEvent;
     56 using protocol::MouseEvent;
     57 
     58 // A class to generate events on Mac.
     59 class InputInjectorMac : public InputInjector {
     60  public:
     61   explicit InputInjectorMac(
     62       scoped_refptr<base::SingleThreadTaskRunner> task_runner);
     63   virtual ~InputInjectorMac();
     64 
     65   // ClipboardStub interface.
     66   virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE;
     67 
     68   // InputStub interface.
     69   virtual void InjectKeyEvent(const KeyEvent& event) OVERRIDE;
     70   virtual void InjectTextEvent(const TextEvent& event) OVERRIDE;
     71   virtual void InjectMouseEvent(const MouseEvent& event) OVERRIDE;
     72 
     73   // InputInjector interface.
     74   virtual void Start(
     75       scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
     76 
     77  private:
     78   // The actual implementation resides in InputInjectorMac::Core class.
     79   class Core : public base::RefCountedThreadSafe<Core> {
     80    public:
     81     explicit Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
     82 
     83     // Mirrors the ClipboardStub interface.
     84     void InjectClipboardEvent(const ClipboardEvent& event);
     85 
     86     // Mirrors the InputStub interface.
     87     void InjectKeyEvent(const KeyEvent& event);
     88     void InjectTextEvent(const TextEvent& event);
     89     void InjectMouseEvent(const MouseEvent& event);
     90 
     91     // Mirrors the InputInjector interface.
     92     void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard);
     93 
     94     void Stop();
     95 
     96    private:
     97     friend class base::RefCountedThreadSafe<Core>;
     98     virtual ~Core();
     99 
    100     scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
    101     webrtc::DesktopVector mouse_pos_;
    102     uint32 mouse_button_state_;
    103     scoped_ptr<Clipboard> clipboard_;
    104     CGEventFlags left_modifiers_;
    105     CGEventFlags right_modifiers_;
    106 
    107     DISALLOW_COPY_AND_ASSIGN(Core);
    108   };
    109 
    110   scoped_refptr<Core> core_;
    111 
    112   DISALLOW_COPY_AND_ASSIGN(InputInjectorMac);
    113 };
    114 
    115 InputInjectorMac::InputInjectorMac(
    116     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
    117   core_ = new Core(task_runner);
    118 }
    119 
    120 InputInjectorMac::~InputInjectorMac() {
    121   core_->Stop();
    122 }
    123 
    124 void InputInjectorMac::InjectClipboardEvent(const ClipboardEvent& event) {
    125   core_->InjectClipboardEvent(event);
    126 }
    127 
    128 void InputInjectorMac::InjectKeyEvent(const KeyEvent& event) {
    129   core_->InjectKeyEvent(event);
    130 }
    131 
    132 void InputInjectorMac::InjectTextEvent(const TextEvent& event) {
    133   core_->InjectTextEvent(event);
    134 }
    135 
    136 void InputInjectorMac::InjectMouseEvent(const MouseEvent& event) {
    137   core_->InjectMouseEvent(event);
    138 }
    139 
    140 void InputInjectorMac::Start(
    141     scoped_ptr<protocol::ClipboardStub> client_clipboard) {
    142   core_->Start(client_clipboard.Pass());
    143 }
    144 
    145 InputInjectorMac::Core::Core(
    146     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
    147     : task_runner_(task_runner),
    148       mouse_button_state_(0),
    149       clipboard_(Clipboard::Create()),
    150       left_modifiers_(0),
    151       right_modifiers_(0) {
    152   // Ensure that local hardware events are not suppressed after injecting
    153   // input events.  This allows LocalInputMonitor to detect if the local mouse
    154   // is being moved whilst a remote user is connected.
    155   // This API is deprecated, but it is needed when using the deprecated
    156   // injection APIs.
    157   // If the non-deprecated injection APIs were used instead, the equivalent of
    158   // this line would not be needed, as OS X defaults to _not_ suppressing local
    159   // inputs in that case.
    160 #pragma clang diagnostic push
    161 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
    162   CGSetLocalEventsSuppressionInterval(0.0);
    163 #pragma clang diagnostic pop
    164 }
    165 
    166 void InputInjectorMac::Core::InjectClipboardEvent(const ClipboardEvent& event) {
    167   if (!task_runner_->BelongsToCurrentThread()) {
    168     task_runner_->PostTask(
    169         FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event));
    170     return;
    171   }
    172 
    173   // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
    174   clipboard_->InjectClipboardEvent(event);
    175 }
    176 
    177 void InputInjectorMac::Core::InjectKeyEvent(const KeyEvent& event) {
    178   // HostEventDispatcher should filter events missing the pressed field.
    179   if (!event.has_pressed() || !event.has_usb_keycode())
    180     return;
    181 
    182   ui::KeycodeConverter* key_converter = ui::KeycodeConverter::GetInstance();
    183   int keycode = key_converter->UsbKeycodeToNativeKeycode(event.usb_keycode());
    184 
    185   VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode()
    186           << " to keycode: " << keycode << std::dec;
    187 
    188   // If we couldn't determine the Mac virtual key code then ignore the event.
    189   if (keycode == key_converter->InvalidNativeKeycode())
    190     return;
    191 
    192   // If this is a modifier key, remember its new state so that it can be
    193   // correctly applied to subsequent events.
    194   if (keycode == kVK_Command) {
    195     SetOrClearBit(left_modifiers_, kCGEventFlagMaskCommand, event.pressed());
    196   } else if (keycode == kVK_Shift) {
    197     SetOrClearBit(left_modifiers_, kCGEventFlagMaskShift, event.pressed());
    198   } else if (keycode == kVK_Control) {
    199     SetOrClearBit(left_modifiers_, kCGEventFlagMaskControl, event.pressed());
    200   } else if (keycode == kVK_Option) {
    201     SetOrClearBit(left_modifiers_, kCGEventFlagMaskAlternate, event.pressed());
    202   } else if (keycode == kVK_RightCommand) {
    203     SetOrClearBit(right_modifiers_, kCGEventFlagMaskCommand, event.pressed());
    204   } else if (keycode == kVK_RightShift) {
    205     SetOrClearBit(right_modifiers_, kCGEventFlagMaskShift, event.pressed());
    206   } else if (keycode == kVK_RightControl) {
    207     SetOrClearBit(right_modifiers_, kCGEventFlagMaskControl, event.pressed());
    208   } else if (keycode == kVK_RightOption) {
    209     SetOrClearBit(right_modifiers_, kCGEventFlagMaskAlternate, event.pressed());
    210   }
    211 
    212   // In addition to the modifier keys pressed right now, we also need to set
    213   // AlphaShift if caps lock was active at the client (Mac ignores NumLock).
    214   uint64_t flags = left_modifiers_ | right_modifiers_;
    215   if (event.lock_states() & protocol::KeyEvent::LOCK_STATES_CAPSLOCK)
    216     flags |= kCGEventFlagMaskAlphaShift;
    217 
    218   CreateAndPostKeyEvent(keycode, event.pressed(), flags, base::string16());
    219 }
    220 
    221 void InputInjectorMac::Core::InjectTextEvent(const TextEvent& event) {
    222   DCHECK(event.has_text());
    223   base::string16 text = base::UTF8ToUTF16(event.text());
    224 
    225   // Applications that ignore UnicodeString field will see the text event as
    226   // Space key.
    227   CreateAndPostKeyEvent(kVK_Space, true, 0, text);
    228   CreateAndPostKeyEvent(kVK_Space, false, 0, text);
    229 }
    230 
    231 void InputInjectorMac::Core::InjectMouseEvent(const MouseEvent& event) {
    232   if (event.has_x() && event.has_y()) {
    233     // On multi-monitor systems (0,0) refers to the top-left of the "main"
    234     // display, whereas our coordinate scheme places (0,0) at the top-left of
    235     // the bounding rectangle around all the displays, so we need to translate
    236     // accordingly.
    237 
    238     // Set the mouse position assuming single-monitor.
    239     mouse_pos_.set(event.x(), event.y());
    240 
    241     // Fetch the desktop configuration.
    242     // TODO(wez): Optimize this out, or at least only enumerate displays in
    243     // response to display-changed events. VideoFrameCapturer's VideoFrames
    244     // could be augmented to include native cursor coordinates for use by
    245     // MouseClampingFilter, removing the need for translation here.
    246     webrtc::MacDesktopConfiguration desktop_config =
    247         webrtc::MacDesktopConfiguration::GetCurrent(
    248             webrtc::MacDesktopConfiguration::TopLeftOrigin);
    249 
    250     // Translate the mouse position into desktop coordinates.
    251     mouse_pos_ = mouse_pos_.add(
    252         webrtc::DesktopVector(desktop_config.pixel_bounds.left(),
    253                               desktop_config.pixel_bounds.top()));
    254 
    255     // Constrain the mouse position to the desktop coordinates.
    256     mouse_pos_.set(
    257        std::max(desktop_config.pixel_bounds.left(),
    258            std::min(desktop_config.pixel_bounds.right(), mouse_pos_.x())),
    259        std::max(desktop_config.pixel_bounds.top(),
    260            std::min(desktop_config.pixel_bounds.bottom(), mouse_pos_.y())));
    261 
    262     // Convert from pixel to Density Independent Pixel coordinates.
    263     mouse_pos_.set(mouse_pos_.x() / desktop_config.dip_to_pixel_scale,
    264                    mouse_pos_.y() / desktop_config.dip_to_pixel_scale);
    265 
    266     VLOG(3) << "Moving mouse to " << mouse_pos_.x() << "," << mouse_pos_.y();
    267   }
    268   if (event.has_button() && event.has_button_down()) {
    269     if (event.button() >= 1 && event.button() <= 3) {
    270       VLOG(2) << "Button " << event.button()
    271               << (event.button_down() ? " down" : " up");
    272       int button_change = 1 << (event.button() - 1);
    273       if (event.button_down())
    274         mouse_button_state_ |= button_change;
    275       else
    276         mouse_button_state_ &= ~button_change;
    277     } else {
    278       VLOG(1) << "Unknown mouse button: " << event.button();
    279     }
    280   }
    281   // We use the deprecated CGPostMouseEvent API because we receive low-level
    282   // mouse events, whereas CGEventCreateMouseEvent is for injecting higher-level
    283   // events. For example, the deprecated APIs will detect double-clicks or drags
    284   // in a way that is consistent with how they would be generated using a local
    285   // mouse, whereas the new APIs expect us to inject these higher-level events
    286   // directly.
    287   CGPoint position = CGPointMake(mouse_pos_.x(), mouse_pos_.y());
    288   enum {
    289     LeftBit = 1 << (MouseEvent::BUTTON_LEFT - 1),
    290     MiddleBit = 1 << (MouseEvent::BUTTON_MIDDLE - 1),
    291     RightBit = 1 << (MouseEvent::BUTTON_RIGHT - 1)
    292   };
    293 #pragma clang diagnostic push
    294 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
    295   CGError error = CGPostMouseEvent(position, true, 3,
    296                                    (mouse_button_state_ & LeftBit) != 0,
    297                                    (mouse_button_state_ & RightBit) != 0,
    298                                    (mouse_button_state_ & MiddleBit) != 0);
    299 #pragma clang diagnostic pop
    300   if (error != kCGErrorSuccess)
    301     LOG(WARNING) << "CGPostMouseEvent error " << error;
    302 
    303   if (event.has_wheel_delta_x() && event.has_wheel_delta_y()) {
    304     int delta_x = static_cast<int>(event.wheel_delta_x());
    305     int delta_y = static_cast<int>(event.wheel_delta_y());
    306     base::ScopedCFTypeRef<CGEventRef> event(CGEventCreateScrollWheelEvent(
    307         NULL, kCGScrollEventUnitPixel, 2, delta_y, delta_x));
    308     if (event)
    309       CGEventPost(kCGSessionEventTap, event);
    310   }
    311 }
    312 
    313 void InputInjectorMac::Core::Start(
    314     scoped_ptr<protocol::ClipboardStub> client_clipboard) {
    315   if (!task_runner_->BelongsToCurrentThread()) {
    316     task_runner_->PostTask(
    317         FROM_HERE,
    318         base::Bind(&Core::Start, this, base::Passed(&client_clipboard)));
    319     return;
    320   }
    321 
    322   clipboard_->Start(client_clipboard.Pass());
    323 }
    324 
    325 void InputInjectorMac::Core::Stop() {
    326   if (!task_runner_->BelongsToCurrentThread()) {
    327     task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this));
    328     return;
    329   }
    330 
    331   clipboard_->Stop();
    332 }
    333 
    334 InputInjectorMac::Core::~Core() {}
    335 
    336 }  // namespace
    337 
    338 scoped_ptr<InputInjector> InputInjector::Create(
    339     scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
    340     scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
    341   return scoped_ptr<InputInjector>(new InputInjectorMac(main_task_runner));
    342 }
    343 
    344 }  // namespace remoting
    345