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
26namespace remoting {
27
28namespace {
29
30using protocol::ClipboardEvent;
31using protocol::KeyEvent;
32using 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().
40SkIRect 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.
47class 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
99InputInjectorMac::InputInjectorMac(
100    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
101  core_ = new Core(task_runner);
102}
103
104InputInjectorMac::~InputInjectorMac() {
105  core_->Stop();
106}
107
108void InputInjectorMac::InjectClipboardEvent(const ClipboardEvent& event) {
109  core_->InjectClipboardEvent(event);
110}
111
112void InputInjectorMac::InjectKeyEvent(const KeyEvent& event) {
113  core_->InjectKeyEvent(event);
114}
115
116void InputInjectorMac::InjectMouseEvent(const MouseEvent& event) {
117  core_->InjectMouseEvent(event);
118}
119
120void InputInjectorMac::Start(
121    scoped_ptr<protocol::ClipboardStub> client_clipboard) {
122  core_->Start(client_clipboard.Pass());
123}
124
125InputInjectorMac::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
144void 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
155void 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
183void 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
265void 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
277void 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
286InputInjectorMac::Core::~Core() {
287}
288
289}  // namespace
290
291scoped_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