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