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