Home | History | Annotate | Download | only in host
      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