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