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
26namespace remoting {
27
28namespace {
29
30void SetOrClearBit(uint64_t &value, uint64_t bit, bool set_bit) {
31  value = set_bit ? (value | bit) : (value & ~bit);
32}
33
34void 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.
51const int kVK_RightCommand = 0x36;
52
53using protocol::ClipboardEvent;
54using protocol::KeyEvent;
55using protocol::TextEvent;
56using protocol::MouseEvent;
57
58// A class to generate events on Mac.
59class 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
115InputInjectorMac::InputInjectorMac(
116    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
117  core_ = new Core(task_runner);
118}
119
120InputInjectorMac::~InputInjectorMac() {
121  core_->Stop();
122}
123
124void InputInjectorMac::InjectClipboardEvent(const ClipboardEvent& event) {
125  core_->InjectClipboardEvent(event);
126}
127
128void InputInjectorMac::InjectKeyEvent(const KeyEvent& event) {
129  core_->InjectKeyEvent(event);
130}
131
132void InputInjectorMac::InjectTextEvent(const TextEvent& event) {
133  core_->InjectTextEvent(event);
134}
135
136void InputInjectorMac::InjectMouseEvent(const MouseEvent& event) {
137  core_->InjectMouseEvent(event);
138}
139
140void InputInjectorMac::Start(
141    scoped_ptr<protocol::ClipboardStub> client_clipboard) {
142  core_->Start(client_clipboard.Pass());
143}
144
145InputInjectorMac::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
166void 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
177void 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  int keycode =
183      ui::KeycodeConverter::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 == ui::KeycodeConverter::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
221void 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
231void 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
313void 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
325void 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
334InputInjectorMac::Core::~Core() {}
335
336}  // namespace
337
338scoped_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