e9be2cb6创建于 2012年5月30日历史提交
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/local_input_monitor.h"

#import <AppKit/AppKit.h>
#include <set>

#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/lock.h"
#include "remoting/host/mouse_move_observer.h"
#include "third_party/skia/include/core/SkPoint.h"
#import "third_party/GTM/AppKit/GTMCarbonEvent.h"

// Esc Key Code is 53.
// http://boredzo.org/blog/wp-content/uploads/2007/05/IMTx-virtual-keycodes.pdf
static const NSUInteger kEscKeyCode = 53;

namespace remoting {

namespace {

class LocalInputMonitorMac : public LocalInputMonitor {
 public:
  LocalInputMonitorMac() : mouse_move_observer_(NULL) {}
  virtual ~LocalInputMonitorMac();
  virtual void Start(MouseMoveObserver* mouse_move_observer,
                     const base::Closure& disconnect_callback) OVERRIDE;
  virtual void Stop() OVERRIDE;

  void OnLocalMouseMoved(const SkIPoint& new_pos);
  void OnDisconnectShortcut();

 private:
  MouseMoveObserver* mouse_move_observer_;
  base::Closure disconnect_callback_;
  DISALLOW_COPY_AND_ASSIGN(LocalInputMonitorMac);
};

typedef std::set<remoting::LocalInputMonitorMac*> LocalInputMonitors;

}  // namespace

}  // namespace remoting

@interface LocalInputMonitorManager : NSObject {
 @private
  GTMCarbonHotKey* hotKey_;
  CFRunLoopSourceRef mouseRunLoopSource_;
  base::mac::ScopedCFTypeRef<CFMachPortRef> mouseMachPort_;
  base::Lock lock_;
  remoting::LocalInputMonitors monitors_;
}

// Called when the hotKey is hit.
- (void)hotKeyHit:(GTMCarbonHotKey*)hotKey;

// Called when the local mouse moves
- (void)localMouseMoved:(const SkIPoint&)mousePos;

// Must be called when the LocalInputMonitorManager is no longer to be used.
// Similar to NSTimer in that more than a simple release is required.
- (void)invalidate;

// Called to add a monitor.
- (void)addMonitor:(remoting::LocalInputMonitorMac*)monitor;

// Called to remove a monitor. Returns true if it was the last
// monitor, in which case the object should be destroyed.
- (bool)removeMonitor:(remoting::LocalInputMonitorMac*)monitor;

@end

static CGEventRef LocalMouseMoved(CGEventTapProxy proxy, CGEventType type,
                                  CGEventRef event, void* context) {
  int64_t pid = CGEventGetIntegerValueField(event, kCGEventSourceUnixProcessID);
  if (pid == 0) {
    CGPoint cgMousePos = CGEventGetLocation(event);
    SkIPoint mousePos = SkIPoint::Make(cgMousePos.x, cgMousePos.y);
    [static_cast<LocalInputMonitorManager*>(context) localMouseMoved:mousePos];
  }
  return NULL;
}

@implementation LocalInputMonitorManager

- (id)init {
  if ((self = [super init])) {
    GTMCarbonEventDispatcherHandler* handler =
        [GTMCarbonEventDispatcherHandler sharedEventDispatcherHandler];
    hotKey_ = [handler registerHotKey:kEscKeyCode
                             modifiers:(NSAlternateKeyMask | NSControlKeyMask)
                                target:self
                                action:@selector(hotKeyHit:)
                              userInfo:nil
                           whenPressed:YES];
    if (!hotKey_) {
      LOG(ERROR) << "registerHotKey failed.";
    }
    mouseMachPort_.reset(CGEventTapCreate(
        kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly,
        1 << kCGEventMouseMoved, LocalMouseMoved, self));
    if (mouseMachPort_) {
      mouseRunLoopSource_ = CFMachPortCreateRunLoopSource(
          NULL, mouseMachPort_, 0);
      CFRunLoopAddSource(
          CFRunLoopGetMain(), mouseRunLoopSource_, kCFRunLoopCommonModes);
    } else {
      LOG(ERROR) << "CGEventTapCreate failed.";
    }
    if (!hotKey_ && !mouseMachPort_) {
      [self release];
      return nil;
    }
  }
  return self;
}

- (void)hotKeyHit:(GTMCarbonHotKey*)hotKey {
  base::AutoLock lock(lock_);
  for (remoting::LocalInputMonitors::const_iterator i = monitors_.begin();
       i != monitors_.end(); ++i) {
    (*i)->OnDisconnectShortcut();
  }
}

- (void)localMouseMoved:(const SkIPoint&)mousePos {
  base::AutoLock lock(lock_);
  for (remoting::LocalInputMonitors::const_iterator i = monitors_.begin();
       i != monitors_.end(); ++i) {
    (*i)->OnLocalMouseMoved(mousePos);
  }
}

- (void)invalidate {
  if (hotKey_) {
    GTMCarbonEventDispatcherHandler* handler =
        [GTMCarbonEventDispatcherHandler sharedEventDispatcherHandler];
    [handler unregisterHotKey:hotKey_];
    hotKey_ = NULL;
  }
  if (mouseRunLoopSource_) {
    CFMachPortInvalidate(mouseMachPort_);
    CFRunLoopRemoveSource(
        CFRunLoopGetMain(), mouseRunLoopSource_, kCFRunLoopCommonModes);
    CFRelease(mouseRunLoopSource_);
    mouseMachPort_.reset(0);
    mouseRunLoopSource_ = NULL;
  }
}

- (void)addMonitor:(remoting::LocalInputMonitorMac*)monitor {
  base::AutoLock lock(lock_);
  monitors_.insert(monitor);
}

- (bool)removeMonitor:(remoting::LocalInputMonitorMac*)monitor {
  base::AutoLock lock(lock_);
  monitors_.erase(monitor);
  return monitors_.empty();
}

@end

namespace remoting {

namespace {

base::LazyInstance<base::Lock>::Leaky g_monitor_lock =
    LAZY_INSTANCE_INITIALIZER;
LocalInputMonitorManager* g_local_input_monitor_manager = NULL;

LocalInputMonitorMac::~LocalInputMonitorMac() {
  Stop();
}

void LocalInputMonitorMac::Start(MouseMoveObserver* mouse_move_observer,
                                 const base::Closure& disconnect_callback) {
  base::AutoLock lock(g_monitor_lock.Get());
  if (!g_local_input_monitor_manager)
    g_local_input_monitor_manager = [[LocalInputMonitorManager alloc] init];
  CHECK(g_local_input_monitor_manager);
  mouse_move_observer_ = mouse_move_observer;
  disconnect_callback_ = disconnect_callback;
  [g_local_input_monitor_manager addMonitor:this];
}

void LocalInputMonitorMac::Stop() {
  base::AutoLock lock(g_monitor_lock.Get());
  if ([g_local_input_monitor_manager removeMonitor:this]) {
    [g_local_input_monitor_manager invalidate];
    [g_local_input_monitor_manager release];
    g_local_input_monitor_manager = nil;
  }
}

void LocalInputMonitorMac::OnLocalMouseMoved(const SkIPoint& new_pos) {
  mouse_move_observer_->OnLocalMouseMoved(new_pos);
}

void LocalInputMonitorMac::OnDisconnectShortcut() {
  disconnect_callback_.Run();
}

}  // namespace

scoped_ptr<LocalInputMonitor> LocalInputMonitor::Create() {
  return scoped_ptr<LocalInputMonitor>(new LocalInputMonitorMac());
}

}  // namespace remoting