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