// 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 #include #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 LocalInputMonitors; } // namespace } // namespace remoting @interface LocalInputMonitorManager : NSObject { @private GTMCarbonHotKey* hotKey_; CFRunLoopSourceRef mouseRunLoopSource_; base::mac::ScopedCFTypeRef 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(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::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::Create() { return scoped_ptr(new LocalInputMonitorMac()); } } // namespace remoting