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/local_input_monitor.h" 6 7 #import <AppKit/AppKit.h> 8 #include <set> 9 10 #include "base/bind.h" 11 #include "base/compiler_specific.h" 12 #include "base/location.h" 13 #include "base/logging.h" 14 #include "base/mac/scoped_cftyperef.h" 15 #include "base/memory/ref_counted.h" 16 #include "base/single_thread_task_runner.h" 17 #include "base/synchronization/lock.h" 18 #include "base/threading/non_thread_safe.h" 19 #include "remoting/host/client_session_control.h" 20 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMCarbonEvent.h" 21 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h" 22 23 // Esc Key Code is 53. 24 // http://boredzo.org/blog/wp-content/uploads/2007/05/IMTx-virtual-keycodes.pdf 25 static const NSUInteger kEscKeyCode = 53; 26 27 namespace remoting { 28 namespace { 29 30 class LocalInputMonitorMac : public base::NonThreadSafe, 31 public LocalInputMonitor { 32 public: 33 // Invoked by LocalInputMonitorManager. 34 class EventHandler { 35 public: 36 virtual ~EventHandler() {} 37 38 virtual void OnLocalMouseMoved(const webrtc::DesktopVector& position) = 0; 39 virtual void OnDisconnectShortcut() = 0; 40 }; 41 42 LocalInputMonitorMac( 43 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, 44 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 45 base::WeakPtr<ClientSessionControl> client_session_control); 46 virtual ~LocalInputMonitorMac(); 47 48 private: 49 // The actual implementation resides in LocalInputMonitorMac::Core class. 50 class Core; 51 scoped_refptr<Core> core_; 52 53 DISALLOW_COPY_AND_ASSIGN(LocalInputMonitorMac); 54 }; 55 56 } // namespace 57 } // namespace remoting 58 59 @interface LocalInputMonitorManager : NSObject { 60 @private 61 GTMCarbonHotKey* hotKey_; 62 CFRunLoopSourceRef mouseRunLoopSource_; 63 base::ScopedCFTypeRef<CFMachPortRef> mouseMachPort_; 64 remoting::LocalInputMonitorMac::EventHandler* monitor_; 65 } 66 67 - (id)initWithMonitor:(remoting::LocalInputMonitorMac::EventHandler*)monitor; 68 69 // Called when the hotKey is hit. 70 - (void)hotKeyHit:(GTMCarbonHotKey*)hotKey; 71 72 // Called when the local mouse moves 73 - (void)localMouseMoved:(const webrtc::DesktopVector&)mousePos; 74 75 // Must be called when the LocalInputMonitorManager is no longer to be used. 76 // Similar to NSTimer in that more than a simple release is required. 77 - (void)invalidate; 78 79 @end 80 81 static CGEventRef LocalMouseMoved(CGEventTapProxy proxy, CGEventType type, 82 CGEventRef event, void* context) { 83 int64_t pid = CGEventGetIntegerValueField(event, kCGEventSourceUnixProcessID); 84 if (pid == 0) { 85 CGPoint cgMousePos = CGEventGetLocation(event); 86 webrtc::DesktopVector mousePos(cgMousePos.x, cgMousePos.y); 87 [static_cast<LocalInputMonitorManager*>(context) localMouseMoved:mousePos]; 88 } 89 return NULL; 90 } 91 92 @implementation LocalInputMonitorManager 93 94 - (id)initWithMonitor:(remoting::LocalInputMonitorMac::EventHandler*)monitor { 95 if ((self = [super init])) { 96 monitor_ = monitor; 97 98 GTMCarbonEventDispatcherHandler* handler = 99 [GTMCarbonEventDispatcherHandler sharedEventDispatcherHandler]; 100 hotKey_ = [handler registerHotKey:kEscKeyCode 101 modifiers:(NSAlternateKeyMask | NSControlKeyMask) 102 target:self 103 action:@selector(hotKeyHit:) 104 userInfo:nil 105 whenPressed:YES]; 106 if (!hotKey_) { 107 LOG(ERROR) << "registerHotKey failed."; 108 } 109 mouseMachPort_.reset(CGEventTapCreate( 110 kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, 111 1 << kCGEventMouseMoved, LocalMouseMoved, self)); 112 if (mouseMachPort_) { 113 mouseRunLoopSource_ = CFMachPortCreateRunLoopSource( 114 NULL, mouseMachPort_, 0); 115 CFRunLoopAddSource( 116 CFRunLoopGetMain(), mouseRunLoopSource_, kCFRunLoopCommonModes); 117 } else { 118 LOG(ERROR) << "CGEventTapCreate failed."; 119 } 120 if (!hotKey_ && !mouseMachPort_) { 121 [self release]; 122 return nil; 123 } 124 } 125 return self; 126 } 127 128 - (void)hotKeyHit:(GTMCarbonHotKey*)hotKey { 129 monitor_->OnDisconnectShortcut(); 130 } 131 132 - (void)localMouseMoved:(const webrtc::DesktopVector&)mousePos { 133 monitor_->OnLocalMouseMoved(mousePos); 134 } 135 136 - (void)invalidate { 137 if (hotKey_) { 138 GTMCarbonEventDispatcherHandler* handler = 139 [GTMCarbonEventDispatcherHandler sharedEventDispatcherHandler]; 140 [handler unregisterHotKey:hotKey_]; 141 hotKey_ = NULL; 142 } 143 if (mouseRunLoopSource_) { 144 CFMachPortInvalidate(mouseMachPort_); 145 CFRunLoopRemoveSource( 146 CFRunLoopGetMain(), mouseRunLoopSource_, kCFRunLoopCommonModes); 147 CFRelease(mouseRunLoopSource_); 148 mouseMachPort_.reset(0); 149 mouseRunLoopSource_ = NULL; 150 } 151 } 152 153 @end 154 155 namespace remoting { 156 namespace { 157 158 class LocalInputMonitorMac::Core 159 : public base::RefCountedThreadSafe<Core>, 160 public EventHandler { 161 public: 162 Core(scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, 163 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 164 base::WeakPtr<ClientSessionControl> client_session_control); 165 166 void Start(); 167 void Stop(); 168 169 private: 170 friend class base::RefCountedThreadSafe<Core>; 171 virtual ~Core(); 172 173 void StartOnUiThread(); 174 void StopOnUiThread(); 175 176 // EventHandler interface. 177 virtual void OnLocalMouseMoved( 178 const webrtc::DesktopVector& position) OVERRIDE; 179 virtual void OnDisconnectShortcut() OVERRIDE; 180 181 // Task runner on which public methods of this class must be called. 182 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_; 183 184 // Task runner on which |window_| is created. 185 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; 186 187 LocalInputMonitorManager* manager_; 188 189 // Invoked in the |caller_task_runner_| thread to report local mouse events 190 // and session disconnect requests. 191 base::WeakPtr<ClientSessionControl> client_session_control_; 192 193 webrtc::DesktopVector mouse_position_; 194 195 DISALLOW_COPY_AND_ASSIGN(Core); 196 }; 197 198 LocalInputMonitorMac::LocalInputMonitorMac( 199 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, 200 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 201 base::WeakPtr<ClientSessionControl> client_session_control) 202 : core_(new Core(caller_task_runner, 203 ui_task_runner, 204 client_session_control)) { 205 core_->Start(); 206 } 207 208 LocalInputMonitorMac::~LocalInputMonitorMac() { 209 core_->Stop(); 210 } 211 212 LocalInputMonitorMac::Core::Core( 213 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, 214 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 215 base::WeakPtr<ClientSessionControl> client_session_control) 216 : caller_task_runner_(caller_task_runner), 217 ui_task_runner_(ui_task_runner), 218 manager_(nil), 219 client_session_control_(client_session_control) { 220 DCHECK(client_session_control_); 221 } 222 223 void LocalInputMonitorMac::Core::Start() { 224 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 225 226 ui_task_runner_->PostTask(FROM_HERE, 227 base::Bind(&Core::StartOnUiThread, this)); 228 } 229 230 void LocalInputMonitorMac::Core::Stop() { 231 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 232 233 ui_task_runner_->PostTask(FROM_HERE, base::Bind(&Core::StopOnUiThread, this)); 234 } 235 236 LocalInputMonitorMac::Core::~Core() { 237 DCHECK(manager_ == nil); 238 } 239 240 void LocalInputMonitorMac::Core::StartOnUiThread() { 241 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 242 243 manager_ = [[LocalInputMonitorManager alloc] initWithMonitor:this]; 244 } 245 246 void LocalInputMonitorMac::Core::StopOnUiThread() { 247 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 248 249 [manager_ invalidate]; 250 [manager_ release]; 251 manager_ = nil; 252 } 253 254 void LocalInputMonitorMac::Core::OnLocalMouseMoved( 255 const webrtc::DesktopVector& position) { 256 // In some cases OS may emit bogus mouse-move events even when cursor is not 257 // actually moving. To handle this case properly verify that mouse position 258 // has changed. See crbug.com/360912 . 259 if (position.equals(mouse_position_)) { 260 return; 261 } 262 263 mouse_position_ = position; 264 265 caller_task_runner_->PostTask( 266 FROM_HERE, base::Bind(&ClientSessionControl::OnLocalMouseMoved, 267 client_session_control_, 268 position)); 269 } 270 271 void LocalInputMonitorMac::Core::OnDisconnectShortcut() { 272 caller_task_runner_->PostTask( 273 FROM_HERE, base::Bind(&ClientSessionControl::DisconnectSession, 274 client_session_control_)); 275 } 276 277 } // namespace 278 279 scoped_ptr<LocalInputMonitor> LocalInputMonitor::Create( 280 scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner, 281 scoped_refptr<base::SingleThreadTaskRunner> input_task_runner, 282 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 283 base::WeakPtr<ClientSessionControl> client_session_control) { 284 return scoped_ptr<LocalInputMonitor>( 285 new LocalInputMonitorMac(caller_task_runner, 286 ui_task_runner, 287 client_session_control)); 288 } 289 290 } // namespace remoting 291