// 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/chromoting_host.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 { typedef std::set > Hosts; } @interface LocalInputMonitorImpl : NSObject { @private GTMCarbonHotKey* hotKey_; CFRunLoopSourceRef mouseRunLoopSource_; base::mac::ScopedCFTypeRef mouseMachPort_; base::Lock hostsLock_; Hosts hosts_; } // 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 LocalInputMonitorImpl is no longer to be used. // Similar to NSTimer in that more than a simple release is required. - (void)invalidate; // Called to add a host to the list of those to be Shutdown() when the hotkey // is pressed. - (void)addHost:(remoting::ChromotingHost*)host; // Called to remove a host. Returns true if it was the last host being // monitored, in which case the object should be destroyed. - (bool)removeHost:(remoting::ChromotingHost*)host; @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 LocalInputMonitorImpl - (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(hostsLock_); for (Hosts::const_iterator i = hosts_.begin(); i != hosts_.end(); ++i) { (*i)->Shutdown(base::Closure()); } } - (void)localMouseMoved:(const SkIPoint&)mousePos { base::AutoLock lock(hostsLock_); for (Hosts::const_iterator i = hosts_.begin(); i != hosts_.end(); ++i) { (*i)->LocalMouseMoved(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)addHost:(remoting::ChromotingHost*)host { base::AutoLock lock(hostsLock_); hosts_.insert(host); } - (bool)removeHost:(remoting::ChromotingHost*)host { base::AutoLock lock(hostsLock_); hosts_.erase(host); return hosts_.empty(); } @end namespace remoting { namespace { class LocalInputMonitorMac : public LocalInputMonitor { public: LocalInputMonitorMac() : host_(NULL) {} virtual ~LocalInputMonitorMac(); virtual void Start(ChromotingHost* host) OVERRIDE; virtual void Stop() OVERRIDE; private: ChromotingHost* host_; DISALLOW_COPY_AND_ASSIGN(LocalInputMonitorMac); }; base::LazyInstance::Leaky monitor_lock = LAZY_INSTANCE_INITIALIZER; LocalInputMonitorImpl* local_input_monitor = NULL; } // namespace LocalInputMonitorMac::~LocalInputMonitorMac() { Stop(); } void LocalInputMonitorMac::Start(ChromotingHost* host) { base::AutoLock lock(monitor_lock.Get()); if (!local_input_monitor) local_input_monitor = [[LocalInputMonitorImpl alloc] init]; CHECK(local_input_monitor); [local_input_monitor addHost:host]; host_ = host; } void LocalInputMonitorMac::Stop() { base::AutoLock lock(monitor_lock.Get()); if ([local_input_monitor removeHost:host_]) { [local_input_monitor invalidate]; [local_input_monitor release]; local_input_monitor = nil; } } scoped_ptr LocalInputMonitor::Create() { return scoped_ptr(new LocalInputMonitorMac()); } } // namespace remoting