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 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 InputInjectorMac : public InputInjector { 48 public: 49 explicit InputInjectorMac( 50 scoped_refptr<base::SingleThreadTaskRunner> task_runner); 51 virtual ~InputInjectorMac(); 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 // InputInjector interface. 61 virtual void Start( 62 scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE; 63 64 private: 65 // The actual implementation resides in InputInjectorMac::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 InputInjector 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(InputInjectorMac); 97 }; 98 99 InputInjectorMac::InputInjectorMac( 100 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { 101 core_ = new Core(task_runner); 102 } 103 104 InputInjectorMac::~InputInjectorMac() { 105 core_->Stop(); 106 } 107 108 void InputInjectorMac::InjectClipboardEvent(const ClipboardEvent& event) { 109 core_->InjectClipboardEvent(event); 110 } 111 112 void InputInjectorMac::InjectKeyEvent(const KeyEvent& event) { 113 core_->InjectKeyEvent(event); 114 } 115 116 void InputInjectorMac::InjectMouseEvent(const MouseEvent& event) { 117 core_->InjectMouseEvent(event); 118 } 119 120 void InputInjectorMac::Start( 121 scoped_ptr<protocol::ClipboardStub> client_clipboard) { 122 core_->Start(client_clipboard.Pass()); 123 } 124 125 InputInjectorMac::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 InputInjectorMac::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 InputInjectorMac::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 base::ScopedCFTypeRef<CGEventRef> eventRef( 170 CGEventCreateKeyboardEvent(NULL, keycode, event.pressed())); 171 172 if (eventRef) { 173 // We only need to manually set CapsLock: Mac ignores NumLock. 174 // Modifier keys work correctly already via press/release event injection. 175 if (event.lock_states() & protocol::KeyEvent::LOCK_STATES_CAPSLOCK) 176 CGEventSetFlags(eventRef, kCGEventFlagMaskAlphaShift); 177 178 // Post the event to the current session. 179 CGEventPost(kCGSessionEventTap, eventRef); 180 } 181 } 182 183 void InputInjectorMac::Core::InjectMouseEvent(const MouseEvent& event) { 184 if (event.has_x() && event.has_y()) { 185 // On multi-monitor systems (0,0) refers to the top-left of the "main" 186 // display, whereas our coordinate scheme places (0,0) at the top-left of 187 // the bounding rectangle around all the displays, so we need to translate 188 // accordingly. 189 190 // Set the mouse position assuming single-monitor. 191 mouse_pos_ = SkIPoint::Make(event.x(), event.y()); 192 193 // Fetch the desktop configuration. 194 // TODO(wez): Optimize this out, or at least only enumerate displays in 195 // response to display-changed events. VideoFrameCapturer's VideoFrames 196 // could be augmented to include native cursor coordinates for use by 197 // MouseClampingFilter, removing the need for translation here. 198 webrtc::MacDesktopConfiguration desktop_config = 199 webrtc::MacDesktopConfiguration::GetCurrent( 200 webrtc::MacDesktopConfiguration::TopLeftOrigin); 201 202 // Translate the mouse position into desktop coordinates. 203 mouse_pos_ += SkIPoint::Make(desktop_config.pixel_bounds.left(), 204 desktop_config.pixel_bounds.top()); 205 206 // Constrain the mouse position to the desktop coordinates. 207 mouse_pos_ = SkIPoint::Make( 208 std::max(desktop_config.pixel_bounds.left(), 209 std::min(desktop_config.pixel_bounds.right(), mouse_pos_.x())), 210 std::max(desktop_config.pixel_bounds.top(), 211 std::min(desktop_config.pixel_bounds.bottom(), mouse_pos_.y()))); 212 213 // Convert from pixel to Density Independent Pixel coordinates. 214 mouse_pos_ = SkIPoint::Make( 215 SkScalarRound(mouse_pos_.x() / desktop_config.dip_to_pixel_scale), 216 SkScalarRound(mouse_pos_.y() / desktop_config.dip_to_pixel_scale)); 217 218 VLOG(3) << "Moving mouse to " << mouse_pos_.x() << "," << mouse_pos_.y(); 219 } 220 if (event.has_button() && event.has_button_down()) { 221 if (event.button() >= 1 && event.button() <= 3) { 222 VLOG(2) << "Button " << event.button() 223 << (event.button_down() ? " down" : " up"); 224 int button_change = 1 << (event.button() - 1); 225 if (event.button_down()) 226 mouse_button_state_ |= button_change; 227 else 228 mouse_button_state_ &= ~button_change; 229 } else { 230 VLOG(1) << "Unknown mouse button: " << event.button(); 231 } 232 } 233 // We use the deprecated CGPostMouseEvent API because we receive low-level 234 // mouse events, whereas CGEventCreateMouseEvent is for injecting higher-level 235 // events. For example, the deprecated APIs will detect double-clicks or drags 236 // in a way that is consistent with how they would be generated using a local 237 // mouse, whereas the new APIs expect us to inject these higher-level events 238 // directly. 239 CGPoint position = CGPointMake(mouse_pos_.x(), mouse_pos_.y()); 240 enum { 241 LeftBit = 1 << (MouseEvent::BUTTON_LEFT - 1), 242 MiddleBit = 1 << (MouseEvent::BUTTON_MIDDLE - 1), 243 RightBit = 1 << (MouseEvent::BUTTON_RIGHT - 1) 244 }; 245 #pragma clang diagnostic push 246 #pragma clang diagnostic ignored "-Wdeprecated-declarations" 247 CGError error = CGPostMouseEvent(position, true, 3, 248 (mouse_button_state_ & LeftBit) != 0, 249 (mouse_button_state_ & RightBit) != 0, 250 (mouse_button_state_ & MiddleBit) != 0); 251 #pragma clang diagnostic pop 252 if (error != kCGErrorSuccess) 253 LOG(WARNING) << "CGPostMouseEvent error " << error; 254 255 if (event.has_wheel_delta_x() && event.has_wheel_delta_y()) { 256 int delta_x = static_cast<int>(event.wheel_delta_x()); 257 int delta_y = static_cast<int>(event.wheel_delta_y()); 258 base::ScopedCFTypeRef<CGEventRef> event(CGEventCreateScrollWheelEvent( 259 NULL, kCGScrollEventUnitPixel, 2, delta_y, delta_x)); 260 if (event) 261 CGEventPost(kCGSessionEventTap, event); 262 } 263 } 264 265 void InputInjectorMac::Core::Start( 266 scoped_ptr<protocol::ClipboardStub> client_clipboard) { 267 if (!task_runner_->BelongsToCurrentThread()) { 268 task_runner_->PostTask( 269 FROM_HERE, 270 base::Bind(&Core::Start, this, base::Passed(&client_clipboard))); 271 return; 272 } 273 274 clipboard_->Start(client_clipboard.Pass()); 275 } 276 277 void InputInjectorMac::Core::Stop() { 278 if (!task_runner_->BelongsToCurrentThread()) { 279 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this)); 280 return; 281 } 282 283 clipboard_->Stop(); 284 } 285 286 InputInjectorMac::Core::~Core() { 287 } 288 289 } // namespace 290 291 scoped_ptr<InputInjector> InputInjector::Create( 292 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, 293 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { 294 return scoped_ptr<InputInjector>(new InputInjectorMac(main_task_runner)); 295 } 296 297 } // namespace remoting 298