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