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.
5 #include "remoting/host/input_injector.h"
7 #include <ApplicationServices/ApplicationServices.h>
8 #include <Carbon/Carbon.h>
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/dom/keycode_converter.h"
30 void SetOrClearBit(uint64_t &value
, uint64_t bit
, bool set_bit
) {
31 value
= set_bit
? (value
| bit
) : (value
& ~bit
);
34 void CreateAndPostKeyEvent(int keycode
,
37 const base::string16
& unicode
) {
38 base::ScopedCFTypeRef
<CGEventRef
> eventRef(
39 CGEventCreateKeyboardEvent(nullptr, keycode
, pressed
));
41 CGEventSetFlags(eventRef
, flags
);
43 CGEventKeyboardSetUnicodeString(eventRef
, unicode
.size(), &(unicode
[0]));
44 CGEventPost(kCGSessionEventTap
, eventRef
);
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
51 const int kVK_RightCommand
= 0x36;
53 using protocol::ClipboardEvent
;
54 using protocol::KeyEvent
;
55 using protocol::TextEvent
;
56 using protocol::MouseEvent
;
57 using protocol::TouchEvent
;
59 // A class to generate events on Mac.
60 class InputInjectorMac
: public InputInjector
{
62 explicit InputInjectorMac(
63 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
);
64 ~InputInjectorMac() override
;
66 // ClipboardStub interface.
67 void InjectClipboardEvent(const ClipboardEvent
& event
) override
;
69 // InputStub interface.
70 void InjectKeyEvent(const KeyEvent
& event
) override
;
71 void InjectTextEvent(const TextEvent
& event
) override
;
72 void InjectMouseEvent(const MouseEvent
& event
) override
;
73 void InjectTouchEvent(const TouchEvent
& event
) override
;
75 // InputInjector interface.
76 void Start(scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) override
;
79 // The actual implementation resides in InputInjectorMac::Core class.
80 class Core
: public base::RefCountedThreadSafe
<Core
> {
82 explicit Core(scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
);
84 // Mirrors the ClipboardStub interface.
85 void InjectClipboardEvent(const ClipboardEvent
& event
);
87 // Mirrors the InputStub interface.
88 void InjectKeyEvent(const KeyEvent
& event
);
89 void InjectTextEvent(const TextEvent
& event
);
90 void InjectMouseEvent(const MouseEvent
& event
);
92 // Mirrors the InputInjector interface.
93 void Start(scoped_ptr
<protocol::ClipboardStub
> client_clipboard
);
98 friend class base::RefCountedThreadSafe
<Core
>;
101 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner_
;
102 webrtc::DesktopVector mouse_pos_
;
103 uint32 mouse_button_state_
;
104 scoped_ptr
<Clipboard
> clipboard_
;
105 CGEventFlags left_modifiers_
;
106 CGEventFlags right_modifiers_
;
108 DISALLOW_COPY_AND_ASSIGN(Core
);
111 scoped_refptr
<Core
> core_
;
113 DISALLOW_COPY_AND_ASSIGN(InputInjectorMac
);
116 InputInjectorMac::InputInjectorMac(
117 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
) {
118 core_
= new Core(task_runner
);
121 InputInjectorMac::~InputInjectorMac() {
125 void InputInjectorMac::InjectClipboardEvent(const ClipboardEvent
& event
) {
126 core_
->InjectClipboardEvent(event
);
129 void InputInjectorMac::InjectKeyEvent(const KeyEvent
& event
) {
130 core_
->InjectKeyEvent(event
);
133 void InputInjectorMac::InjectTextEvent(const TextEvent
& event
) {
134 core_
->InjectTextEvent(event
);
137 void InputInjectorMac::InjectMouseEvent(const MouseEvent
& event
) {
138 core_
->InjectMouseEvent(event
);
141 void InputInjectorMac::InjectTouchEvent(const TouchEvent
& event
) {
142 NOTIMPLEMENTED() << "Raw touch event injection not implemented for Mac.";
145 void InputInjectorMac::Start(
146 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) {
147 core_
->Start(client_clipboard
.Pass());
150 InputInjectorMac::Core::Core(
151 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
)
152 : task_runner_(task_runner
),
153 mouse_button_state_(0),
154 clipboard_(Clipboard::Create()),
156 right_modifiers_(0) {
157 // Ensure that local hardware events are not suppressed after injecting
158 // input events. This allows LocalInputMonitor to detect if the local mouse
159 // is being moved whilst a remote user is connected.
160 // This API is deprecated, but it is needed when using the deprecated
162 // If the non-deprecated injection APIs were used instead, the equivalent of
163 // this line would not be needed, as OS X defaults to _not_ suppressing local
164 // inputs in that case.
165 #pragma clang diagnostic push
166 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
167 CGSetLocalEventsSuppressionInterval(0.0);
168 #pragma clang diagnostic pop
171 void InputInjectorMac::Core::InjectClipboardEvent(const ClipboardEvent
& event
) {
172 if (!task_runner_
->BelongsToCurrentThread()) {
173 task_runner_
->PostTask(
174 FROM_HERE
, base::Bind(&Core::InjectClipboardEvent
, this, event
));
178 // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
179 clipboard_
->InjectClipboardEvent(event
);
182 void InputInjectorMac::Core::InjectKeyEvent(const KeyEvent
& event
) {
183 // HostEventDispatcher should filter events missing the pressed field.
184 if (!event
.has_pressed() || !event
.has_usb_keycode())
188 ui::KeycodeConverter::UsbKeycodeToNativeKeycode(event
.usb_keycode());
190 VLOG(3) << "Converting USB keycode: " << std::hex
<< event
.usb_keycode()
191 << " to keycode: " << keycode
<< std::dec
;
193 // If we couldn't determine the Mac virtual key code then ignore the event.
194 if (keycode
== ui::KeycodeConverter::InvalidNativeKeycode())
197 // If this is a modifier key, remember its new state so that it can be
198 // correctly applied to subsequent events.
199 if (keycode
== kVK_Command
) {
200 SetOrClearBit(left_modifiers_
, kCGEventFlagMaskCommand
, event
.pressed());
201 } else if (keycode
== kVK_Shift
) {
202 SetOrClearBit(left_modifiers_
, kCGEventFlagMaskShift
, event
.pressed());
203 } else if (keycode
== kVK_Control
) {
204 SetOrClearBit(left_modifiers_
, kCGEventFlagMaskControl
, event
.pressed());
205 } else if (keycode
== kVK_Option
) {
206 SetOrClearBit(left_modifiers_
, kCGEventFlagMaskAlternate
, event
.pressed());
207 } else if (keycode
== kVK_RightCommand
) {
208 SetOrClearBit(right_modifiers_
, kCGEventFlagMaskCommand
, event
.pressed());
209 } else if (keycode
== kVK_RightShift
) {
210 SetOrClearBit(right_modifiers_
, kCGEventFlagMaskShift
, event
.pressed());
211 } else if (keycode
== kVK_RightControl
) {
212 SetOrClearBit(right_modifiers_
, kCGEventFlagMaskControl
, event
.pressed());
213 } else if (keycode
== kVK_RightOption
) {
214 SetOrClearBit(right_modifiers_
, kCGEventFlagMaskAlternate
, event
.pressed());
217 // In addition to the modifier keys pressed right now, we also need to set
218 // AlphaShift if caps lock was active at the client (Mac ignores NumLock).
219 uint64_t flags
= left_modifiers_
| right_modifiers_
;
220 if (event
.lock_states() & protocol::KeyEvent::LOCK_STATES_CAPSLOCK
)
221 flags
|= kCGEventFlagMaskAlphaShift
;
223 CreateAndPostKeyEvent(keycode
, event
.pressed(), flags
, base::string16());
226 void InputInjectorMac::Core::InjectTextEvent(const TextEvent
& event
) {
227 DCHECK(event
.has_text());
228 base::string16 text
= base::UTF8ToUTF16(event
.text());
230 // Applications that ignore UnicodeString field will see the text event as
232 CreateAndPostKeyEvent(kVK_Space
, true, 0, text
);
233 CreateAndPostKeyEvent(kVK_Space
, false, 0, text
);
236 void InputInjectorMac::Core::InjectMouseEvent(const MouseEvent
& event
) {
237 if (event
.has_x() && event
.has_y()) {
238 // On multi-monitor systems (0,0) refers to the top-left of the "main"
239 // display, whereas our coordinate scheme places (0,0) at the top-left of
240 // the bounding rectangle around all the displays, so we need to translate
243 // Set the mouse position assuming single-monitor.
244 mouse_pos_
.set(event
.x(), event
.y());
246 // Fetch the desktop configuration.
247 // TODO(wez): Optimize this out, or at least only enumerate displays in
248 // response to display-changed events. VideoFrameCapturer's VideoFrames
249 // could be augmented to include native cursor coordinates for use by
250 // MouseClampingFilter, removing the need for translation here.
251 webrtc::MacDesktopConfiguration desktop_config
=
252 webrtc::MacDesktopConfiguration::GetCurrent(
253 webrtc::MacDesktopConfiguration::TopLeftOrigin
);
255 // Translate the mouse position into desktop coordinates.
256 mouse_pos_
= mouse_pos_
.add(
257 webrtc::DesktopVector(desktop_config
.pixel_bounds
.left(),
258 desktop_config
.pixel_bounds
.top()));
260 // Constrain the mouse position to the desktop coordinates.
262 std::max(desktop_config
.pixel_bounds
.left(),
263 std::min(desktop_config
.pixel_bounds
.right(), mouse_pos_
.x())),
264 std::max(desktop_config
.pixel_bounds
.top(),
265 std::min(desktop_config
.pixel_bounds
.bottom(), mouse_pos_
.y())));
267 // Convert from pixel to Density Independent Pixel coordinates.
268 mouse_pos_
.set(mouse_pos_
.x() / desktop_config
.dip_to_pixel_scale
,
269 mouse_pos_
.y() / desktop_config
.dip_to_pixel_scale
);
271 VLOG(3) << "Moving mouse to " << mouse_pos_
.x() << "," << mouse_pos_
.y();
273 if (event
.has_button() && event
.has_button_down()) {
274 if (event
.button() >= 1 && event
.button() <= 3) {
275 VLOG(2) << "Button " << event
.button()
276 << (event
.button_down() ? " down" : " up");
277 int button_change
= 1 << (event
.button() - 1);
278 if (event
.button_down())
279 mouse_button_state_
|= button_change
;
281 mouse_button_state_
&= ~button_change
;
283 VLOG(1) << "Unknown mouse button: " << event
.button();
286 // We use the deprecated CGPostMouseEvent API because we receive low-level
287 // mouse events, whereas CGEventCreateMouseEvent is for injecting higher-level
288 // events. For example, the deprecated APIs will detect double-clicks or drags
289 // in a way that is consistent with how they would be generated using a local
290 // mouse, whereas the new APIs expect us to inject these higher-level events
292 CGPoint position
= CGPointMake(mouse_pos_
.x(), mouse_pos_
.y());
294 LeftBit
= 1 << (MouseEvent::BUTTON_LEFT
- 1),
295 MiddleBit
= 1 << (MouseEvent::BUTTON_MIDDLE
- 1),
296 RightBit
= 1 << (MouseEvent::BUTTON_RIGHT
- 1)
298 #pragma clang diagnostic push
299 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
300 CGError error
= CGPostMouseEvent(position
, true, 3,
301 (mouse_button_state_
& LeftBit
) != 0,
302 (mouse_button_state_
& RightBit
) != 0,
303 (mouse_button_state_
& MiddleBit
) != 0);
304 #pragma clang diagnostic pop
305 if (error
!= kCGErrorSuccess
)
306 LOG(WARNING
) << "CGPostMouseEvent error " << error
;
308 if (event
.has_wheel_delta_x() && event
.has_wheel_delta_y()) {
309 int delta_x
= static_cast<int>(event
.wheel_delta_x());
310 int delta_y
= static_cast<int>(event
.wheel_delta_y());
311 base::ScopedCFTypeRef
<CGEventRef
> event(CGEventCreateScrollWheelEvent(
312 nullptr, kCGScrollEventUnitPixel
, 2, delta_y
, delta_x
));
314 CGEventPost(kCGSessionEventTap
, event
);
318 void InputInjectorMac::Core::Start(
319 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) {
320 if (!task_runner_
->BelongsToCurrentThread()) {
321 task_runner_
->PostTask(
323 base::Bind(&Core::Start
, this, base::Passed(&client_clipboard
)));
327 clipboard_
->Start(client_clipboard
.Pass());
330 void InputInjectorMac::Core::Stop() {
331 if (!task_runner_
->BelongsToCurrentThread()) {
332 task_runner_
->PostTask(FROM_HERE
, base::Bind(&Core::Stop
, this));
339 InputInjectorMac::Core::~Core() {}
344 scoped_ptr
<InputInjector
> InputInjector::Create(
345 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner
,
346 scoped_refptr
<base::SingleThreadTaskRunner
> ui_task_runner
) {
347 return make_scoped_ptr(new InputInjectorMac(main_task_runner
));
351 bool InputInjector::SupportsTouchEvents() {
355 } // namespace remoting