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"
8 #include <ApplicationServices/ApplicationServices.h>
9 #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 "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 #include "ui/base/keycodes/keycode_converter.h"
31 using protocol::ClipboardEvent
;
32 using protocol::KeyEvent
;
33 using protocol::MouseEvent
;
35 // A class to generate events on Mac.
36 class InputInjectorMac
: public InputInjector
{
38 explicit InputInjectorMac(
39 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
);
40 virtual ~InputInjectorMac();
42 // ClipboardStub interface.
43 virtual void InjectClipboardEvent(const ClipboardEvent
& event
) OVERRIDE
;
45 // InputStub interface.
46 virtual void InjectKeyEvent(const KeyEvent
& event
) OVERRIDE
;
47 virtual void InjectMouseEvent(const MouseEvent
& event
) OVERRIDE
;
49 // InputInjector interface.
51 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) OVERRIDE
;
54 // The actual implementation resides in InputInjectorMac::Core class.
55 class Core
: public base::RefCountedThreadSafe
<Core
> {
57 explicit Core(scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
);
59 // Mirrors the ClipboardStub interface.
60 void InjectClipboardEvent(const ClipboardEvent
& event
);
62 // Mirrors the InputStub interface.
63 void InjectKeyEvent(const KeyEvent
& event
);
64 void InjectMouseEvent(const MouseEvent
& event
);
66 // Mirrors the InputInjector interface.
67 void Start(scoped_ptr
<protocol::ClipboardStub
> client_clipboard
);
72 friend class base::RefCountedThreadSafe
<Core
>;
75 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner_
;
77 uint32 mouse_button_state_
;
78 scoped_ptr
<Clipboard
> clipboard_
;
80 DISALLOW_COPY_AND_ASSIGN(Core
);
83 scoped_refptr
<Core
> core_
;
85 DISALLOW_COPY_AND_ASSIGN(InputInjectorMac
);
88 InputInjectorMac::InputInjectorMac(
89 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
) {
90 core_
= new Core(task_runner
);
93 InputInjectorMac::~InputInjectorMac() {
97 void InputInjectorMac::InjectClipboardEvent(const ClipboardEvent
& event
) {
98 core_
->InjectClipboardEvent(event
);
101 void InputInjectorMac::InjectKeyEvent(const KeyEvent
& event
) {
102 core_
->InjectKeyEvent(event
);
105 void InputInjectorMac::InjectMouseEvent(const MouseEvent
& event
) {
106 core_
->InjectMouseEvent(event
);
109 void InputInjectorMac::Start(
110 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) {
111 core_
->Start(client_clipboard
.Pass());
114 InputInjectorMac::Core::Core(
115 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
)
116 : task_runner_(task_runner
),
117 mouse_button_state_(0),
118 clipboard_(Clipboard::Create()) {
119 // Ensure that local hardware events are not suppressed after injecting
120 // input events. This allows LocalInputMonitor to detect if the local mouse
121 // is being moved whilst a remote user is connected.
122 // This API is deprecated, but it is needed when using the deprecated
124 // If the non-deprecated injection APIs were used instead, the equivalent of
125 // this line would not be needed, as OS X defaults to _not_ suppressing local
126 // inputs in that case.
127 #pragma clang diagnostic push
128 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
129 CGSetLocalEventsSuppressionInterval(0.0);
130 #pragma clang diagnostic pop
133 void InputInjectorMac::Core::InjectClipboardEvent(const ClipboardEvent
& event
) {
134 if (!task_runner_
->BelongsToCurrentThread()) {
135 task_runner_
->PostTask(
136 FROM_HERE
, base::Bind(&Core::InjectClipboardEvent
, this, event
));
140 // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
141 clipboard_
->InjectClipboardEvent(event
);
144 void InputInjectorMac::Core::InjectKeyEvent(const KeyEvent
& event
) {
145 // HostEventDispatcher should filter events missing the pressed field.
146 if (!event
.has_pressed() || !event
.has_usb_keycode())
149 ui::KeycodeConverter
* key_converter
= ui::KeycodeConverter::GetInstance();
150 int keycode
= key_converter
->UsbKeycodeToNativeKeycode(event
.usb_keycode());
152 VLOG(3) << "Converting USB keycode: " << std::hex
<< event
.usb_keycode()
153 << " to keycode: " << keycode
<< std::dec
;
155 // If we couldn't determine the Mac virtual key code then ignore the event.
156 if (keycode
== key_converter
->InvalidNativeKeycode())
159 base::ScopedCFTypeRef
<CGEventRef
> eventRef(
160 CGEventCreateKeyboardEvent(NULL
, keycode
, event
.pressed()));
163 // We only need to manually set CapsLock: Mac ignores NumLock.
164 // Modifier keys work correctly already via press/release event injection.
165 if (event
.lock_states() & protocol::KeyEvent::LOCK_STATES_CAPSLOCK
)
166 CGEventSetFlags(eventRef
, kCGEventFlagMaskAlphaShift
);
168 // Post the event to the current session.
169 CGEventPost(kCGSessionEventTap
, eventRef
);
173 void InputInjectorMac::Core::InjectMouseEvent(const MouseEvent
& event
) {
174 if (event
.has_x() && event
.has_y()) {
175 // On multi-monitor systems (0,0) refers to the top-left of the "main"
176 // display, whereas our coordinate scheme places (0,0) at the top-left of
177 // the bounding rectangle around all the displays, so we need to translate
180 // Set the mouse position assuming single-monitor.
181 mouse_pos_
= SkIPoint::Make(event
.x(), event
.y());
183 // Fetch the desktop configuration.
184 // TODO(wez): Optimize this out, or at least only enumerate displays in
185 // response to display-changed events. VideoFrameCapturer's VideoFrames
186 // could be augmented to include native cursor coordinates for use by
187 // MouseClampingFilter, removing the need for translation here.
188 webrtc::MacDesktopConfiguration desktop_config
=
189 webrtc::MacDesktopConfiguration::GetCurrent(
190 webrtc::MacDesktopConfiguration::TopLeftOrigin
);
192 // Translate the mouse position into desktop coordinates.
193 mouse_pos_
+= SkIPoint::Make(desktop_config
.pixel_bounds
.left(),
194 desktop_config
.pixel_bounds
.top());
196 // Constrain the mouse position to the desktop coordinates.
197 mouse_pos_
= SkIPoint::Make(
198 std::max(desktop_config
.pixel_bounds
.left(),
199 std::min(desktop_config
.pixel_bounds
.right(), mouse_pos_
.x())),
200 std::max(desktop_config
.pixel_bounds
.top(),
201 std::min(desktop_config
.pixel_bounds
.bottom(), mouse_pos_
.y())));
203 // Convert from pixel to Density Independent Pixel coordinates.
204 mouse_pos_
= SkIPoint::Make(
205 SkScalarRound(mouse_pos_
.x() / desktop_config
.dip_to_pixel_scale
),
206 SkScalarRound(mouse_pos_
.y() / desktop_config
.dip_to_pixel_scale
));
208 VLOG(3) << "Moving mouse to " << mouse_pos_
.x() << "," << mouse_pos_
.y();
210 if (event
.has_button() && event
.has_button_down()) {
211 if (event
.button() >= 1 && event
.button() <= 3) {
212 VLOG(2) << "Button " << event
.button()
213 << (event
.button_down() ? " down" : " up");
214 int button_change
= 1 << (event
.button() - 1);
215 if (event
.button_down())
216 mouse_button_state_
|= button_change
;
218 mouse_button_state_
&= ~button_change
;
220 VLOG(1) << "Unknown mouse button: " << event
.button();
223 // We use the deprecated CGPostMouseEvent API because we receive low-level
224 // mouse events, whereas CGEventCreateMouseEvent is for injecting higher-level
225 // events. For example, the deprecated APIs will detect double-clicks or drags
226 // in a way that is consistent with how they would be generated using a local
227 // mouse, whereas the new APIs expect us to inject these higher-level events
229 CGPoint position
= CGPointMake(mouse_pos_
.x(), mouse_pos_
.y());
231 LeftBit
= 1 << (MouseEvent::BUTTON_LEFT
- 1),
232 MiddleBit
= 1 << (MouseEvent::BUTTON_MIDDLE
- 1),
233 RightBit
= 1 << (MouseEvent::BUTTON_RIGHT
- 1)
235 #pragma clang diagnostic push
236 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
237 CGError error
= CGPostMouseEvent(position
, true, 3,
238 (mouse_button_state_
& LeftBit
) != 0,
239 (mouse_button_state_
& RightBit
) != 0,
240 (mouse_button_state_
& MiddleBit
) != 0);
241 #pragma clang diagnostic pop
242 if (error
!= kCGErrorSuccess
)
243 LOG(WARNING
) << "CGPostMouseEvent error " << error
;
245 if (event
.has_wheel_delta_x() && event
.has_wheel_delta_y()) {
246 int delta_x
= static_cast<int>(event
.wheel_delta_x());
247 int delta_y
= static_cast<int>(event
.wheel_delta_y());
248 base::ScopedCFTypeRef
<CGEventRef
> event(CGEventCreateScrollWheelEvent(
249 NULL
, kCGScrollEventUnitPixel
, 2, delta_y
, delta_x
));
251 CGEventPost(kCGSessionEventTap
, event
);
255 void InputInjectorMac::Core::Start(
256 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) {
257 if (!task_runner_
->BelongsToCurrentThread()) {
258 task_runner_
->PostTask(
260 base::Bind(&Core::Start
, this, base::Passed(&client_clipboard
)));
264 clipboard_
->Start(client_clipboard
.Pass());
267 void InputInjectorMac::Core::Stop() {
268 if (!task_runner_
->BelongsToCurrentThread()) {
269 task_runner_
->PostTask(FROM_HERE
, base::Bind(&Core::Stop
, this));
276 InputInjectorMac::Core::~Core() {
281 scoped_ptr
<InputInjector
> InputInjector::Create(
282 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner
,
283 scoped_refptr
<base::SingleThreadTaskRunner
> ui_task_runner
) {
284 return scoped_ptr
<InputInjector
>(new InputInjectorMac(main_task_runner
));
287 } // namespace remoting