Remove dependency on content from remoting_host.
[chromium-blink-merge.git] / remoting / host / input_injector_mac.cc
blobd3b96d8c53d59ec476cc0363ac83ed1f94ce9084
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>
9 #include <algorithm>
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/dom4/keycode_converter.h"
26 namespace remoting {
28 namespace {
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,
35 bool pressed,
36 int flags,
37 const base::string16& unicode) {
38 base::ScopedCFTypeRef<CGEventRef> eventRef(
39 CGEventCreateKeyboardEvent(nullptr, keycode, pressed));
40 if (eventRef) {
41 CGEventSetFlags(eventRef, flags);
42 if (!unicode.empty())
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
50 // definition.
51 const int kVK_RightCommand = 0x36;
53 using protocol::ClipboardEvent;
54 using protocol::KeyEvent;
55 using protocol::TextEvent;
56 using protocol::MouseEvent;
58 // A class to generate events on Mac.
59 class InputInjectorMac : public InputInjector {
60 public:
61 explicit InputInjectorMac(
62 scoped_refptr<base::SingleThreadTaskRunner> task_runner);
63 ~InputInjectorMac() override;
65 // ClipboardStub interface.
66 void InjectClipboardEvent(const ClipboardEvent& event) override;
68 // InputStub interface.
69 void InjectKeyEvent(const KeyEvent& event) override;
70 void InjectTextEvent(const TextEvent& event) override;
71 void InjectMouseEvent(const MouseEvent& event) override;
73 // InputInjector interface.
74 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard) override;
76 private:
77 // The actual implementation resides in InputInjectorMac::Core class.
78 class Core : public base::RefCountedThreadSafe<Core> {
79 public:
80 explicit Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
82 // Mirrors the ClipboardStub interface.
83 void InjectClipboardEvent(const ClipboardEvent& event);
85 // Mirrors the InputStub interface.
86 void InjectKeyEvent(const KeyEvent& event);
87 void InjectTextEvent(const TextEvent& event);
88 void InjectMouseEvent(const MouseEvent& event);
90 // Mirrors the InputInjector interface.
91 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard);
93 void Stop();
95 private:
96 friend class base::RefCountedThreadSafe<Core>;
97 virtual ~Core();
99 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
100 webrtc::DesktopVector mouse_pos_;
101 uint32 mouse_button_state_;
102 scoped_ptr<Clipboard> clipboard_;
103 CGEventFlags left_modifiers_;
104 CGEventFlags right_modifiers_;
106 DISALLOW_COPY_AND_ASSIGN(Core);
109 scoped_refptr<Core> core_;
111 DISALLOW_COPY_AND_ASSIGN(InputInjectorMac);
114 InputInjectorMac::InputInjectorMac(
115 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
116 core_ = new Core(task_runner);
119 InputInjectorMac::~InputInjectorMac() {
120 core_->Stop();
123 void InputInjectorMac::InjectClipboardEvent(const ClipboardEvent& event) {
124 core_->InjectClipboardEvent(event);
127 void InputInjectorMac::InjectKeyEvent(const KeyEvent& event) {
128 core_->InjectKeyEvent(event);
131 void InputInjectorMac::InjectTextEvent(const TextEvent& event) {
132 core_->InjectTextEvent(event);
135 void InputInjectorMac::InjectMouseEvent(const MouseEvent& event) {
136 core_->InjectMouseEvent(event);
139 void InputInjectorMac::Start(
140 scoped_ptr<protocol::ClipboardStub> client_clipboard) {
141 core_->Start(client_clipboard.Pass());
144 InputInjectorMac::Core::Core(
145 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
146 : task_runner_(task_runner),
147 mouse_button_state_(0),
148 clipboard_(Clipboard::Create()),
149 left_modifiers_(0),
150 right_modifiers_(0) {
151 // Ensure that local hardware events are not suppressed after injecting
152 // input events. This allows LocalInputMonitor to detect if the local mouse
153 // is being moved whilst a remote user is connected.
154 // This API is deprecated, but it is needed when using the deprecated
155 // injection APIs.
156 // If the non-deprecated injection APIs were used instead, the equivalent of
157 // this line would not be needed, as OS X defaults to _not_ suppressing local
158 // inputs in that case.
159 #pragma clang diagnostic push
160 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
161 CGSetLocalEventsSuppressionInterval(0.0);
162 #pragma clang diagnostic pop
165 void InputInjectorMac::Core::InjectClipboardEvent(const ClipboardEvent& event) {
166 if (!task_runner_->BelongsToCurrentThread()) {
167 task_runner_->PostTask(
168 FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event));
169 return;
172 // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
173 clipboard_->InjectClipboardEvent(event);
176 void InputInjectorMac::Core::InjectKeyEvent(const KeyEvent& event) {
177 // HostEventDispatcher should filter events missing the pressed field.
178 if (!event.has_pressed() || !event.has_usb_keycode())
179 return;
181 int keycode =
182 ui::KeycodeConverter::UsbKeycodeToNativeKeycode(event.usb_keycode());
184 VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode()
185 << " to keycode: " << keycode << std::dec;
187 // If we couldn't determine the Mac virtual key code then ignore the event.
188 if (keycode == ui::KeycodeConverter::InvalidNativeKeycode())
189 return;
191 // If this is a modifier key, remember its new state so that it can be
192 // correctly applied to subsequent events.
193 if (keycode == kVK_Command) {
194 SetOrClearBit(left_modifiers_, kCGEventFlagMaskCommand, event.pressed());
195 } else if (keycode == kVK_Shift) {
196 SetOrClearBit(left_modifiers_, kCGEventFlagMaskShift, event.pressed());
197 } else if (keycode == kVK_Control) {
198 SetOrClearBit(left_modifiers_, kCGEventFlagMaskControl, event.pressed());
199 } else if (keycode == kVK_Option) {
200 SetOrClearBit(left_modifiers_, kCGEventFlagMaskAlternate, event.pressed());
201 } else if (keycode == kVK_RightCommand) {
202 SetOrClearBit(right_modifiers_, kCGEventFlagMaskCommand, event.pressed());
203 } else if (keycode == kVK_RightShift) {
204 SetOrClearBit(right_modifiers_, kCGEventFlagMaskShift, event.pressed());
205 } else if (keycode == kVK_RightControl) {
206 SetOrClearBit(right_modifiers_, kCGEventFlagMaskControl, event.pressed());
207 } else if (keycode == kVK_RightOption) {
208 SetOrClearBit(right_modifiers_, kCGEventFlagMaskAlternate, event.pressed());
211 // In addition to the modifier keys pressed right now, we also need to set
212 // AlphaShift if caps lock was active at the client (Mac ignores NumLock).
213 uint64_t flags = left_modifiers_ | right_modifiers_;
214 if (event.lock_states() & protocol::KeyEvent::LOCK_STATES_CAPSLOCK)
215 flags |= kCGEventFlagMaskAlphaShift;
217 CreateAndPostKeyEvent(keycode, event.pressed(), flags, base::string16());
220 void InputInjectorMac::Core::InjectTextEvent(const TextEvent& event) {
221 DCHECK(event.has_text());
222 base::string16 text = base::UTF8ToUTF16(event.text());
224 // Applications that ignore UnicodeString field will see the text event as
225 // Space key.
226 CreateAndPostKeyEvent(kVK_Space, true, 0, text);
227 CreateAndPostKeyEvent(kVK_Space, false, 0, text);
230 void InputInjectorMac::Core::InjectMouseEvent(const MouseEvent& event) {
231 if (event.has_x() && event.has_y()) {
232 // On multi-monitor systems (0,0) refers to the top-left of the "main"
233 // display, whereas our coordinate scheme places (0,0) at the top-left of
234 // the bounding rectangle around all the displays, so we need to translate
235 // accordingly.
237 // Set the mouse position assuming single-monitor.
238 mouse_pos_.set(event.x(), event.y());
240 // Fetch the desktop configuration.
241 // TODO(wez): Optimize this out, or at least only enumerate displays in
242 // response to display-changed events. VideoFrameCapturer's VideoFrames
243 // could be augmented to include native cursor coordinates for use by
244 // MouseClampingFilter, removing the need for translation here.
245 webrtc::MacDesktopConfiguration desktop_config =
246 webrtc::MacDesktopConfiguration::GetCurrent(
247 webrtc::MacDesktopConfiguration::TopLeftOrigin);
249 // Translate the mouse position into desktop coordinates.
250 mouse_pos_ = mouse_pos_.add(
251 webrtc::DesktopVector(desktop_config.pixel_bounds.left(),
252 desktop_config.pixel_bounds.top()));
254 // Constrain the mouse position to the desktop coordinates.
255 mouse_pos_.set(
256 std::max(desktop_config.pixel_bounds.left(),
257 std::min(desktop_config.pixel_bounds.right(), mouse_pos_.x())),
258 std::max(desktop_config.pixel_bounds.top(),
259 std::min(desktop_config.pixel_bounds.bottom(), mouse_pos_.y())));
261 // Convert from pixel to Density Independent Pixel coordinates.
262 mouse_pos_.set(mouse_pos_.x() / desktop_config.dip_to_pixel_scale,
263 mouse_pos_.y() / desktop_config.dip_to_pixel_scale);
265 VLOG(3) << "Moving mouse to " << mouse_pos_.x() << "," << mouse_pos_.y();
267 if (event.has_button() && event.has_button_down()) {
268 if (event.button() >= 1 && event.button() <= 3) {
269 VLOG(2) << "Button " << event.button()
270 << (event.button_down() ? " down" : " up");
271 int button_change = 1 << (event.button() - 1);
272 if (event.button_down())
273 mouse_button_state_ |= button_change;
274 else
275 mouse_button_state_ &= ~button_change;
276 } else {
277 VLOG(1) << "Unknown mouse button: " << event.button();
280 // We use the deprecated CGPostMouseEvent API because we receive low-level
281 // mouse events, whereas CGEventCreateMouseEvent is for injecting higher-level
282 // events. For example, the deprecated APIs will detect double-clicks or drags
283 // in a way that is consistent with how they would be generated using a local
284 // mouse, whereas the new APIs expect us to inject these higher-level events
285 // directly.
286 CGPoint position = CGPointMake(mouse_pos_.x(), mouse_pos_.y());
287 enum {
288 LeftBit = 1 << (MouseEvent::BUTTON_LEFT - 1),
289 MiddleBit = 1 << (MouseEvent::BUTTON_MIDDLE - 1),
290 RightBit = 1 << (MouseEvent::BUTTON_RIGHT - 1)
292 #pragma clang diagnostic push
293 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
294 CGError error = CGPostMouseEvent(position, true, 3,
295 (mouse_button_state_ & LeftBit) != 0,
296 (mouse_button_state_ & RightBit) != 0,
297 (mouse_button_state_ & MiddleBit) != 0);
298 #pragma clang diagnostic pop
299 if (error != kCGErrorSuccess)
300 LOG(WARNING) << "CGPostMouseEvent error " << error;
302 if (event.has_wheel_delta_x() && event.has_wheel_delta_y()) {
303 int delta_x = static_cast<int>(event.wheel_delta_x());
304 int delta_y = static_cast<int>(event.wheel_delta_y());
305 base::ScopedCFTypeRef<CGEventRef> event(CGEventCreateScrollWheelEvent(
306 nullptr, kCGScrollEventUnitPixel, 2, delta_y, delta_x));
307 if (event)
308 CGEventPost(kCGSessionEventTap, event);
312 void InputInjectorMac::Core::Start(
313 scoped_ptr<protocol::ClipboardStub> client_clipboard) {
314 if (!task_runner_->BelongsToCurrentThread()) {
315 task_runner_->PostTask(
316 FROM_HERE,
317 base::Bind(&Core::Start, this, base::Passed(&client_clipboard)));
318 return;
321 clipboard_->Start(client_clipboard.Pass());
324 void InputInjectorMac::Core::Stop() {
325 if (!task_runner_->BelongsToCurrentThread()) {
326 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this));
327 return;
330 clipboard_->Stop();
333 InputInjectorMac::Core::~Core() {}
335 } // namespace
337 scoped_ptr<InputInjector> InputInjector::Create(
338 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
339 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
340 return make_scoped_ptr(new InputInjectorMac(main_task_runner));
343 } // namespace remoting