blob: bd4f142206960fdc0af79a540651c631baecaa0f [file] [log] [blame]
// Copyright 2018 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/input_monitor/local_hotkey_input_monitor.h"
#import <AppKit/AppKit.h>
#include <cstdint>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/lock.h"
#import "third_party/google_toolbox_for_mac/src/AppKit/GTMCarbonEvent.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.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 LocalHotkeyInputMonitorMac : public LocalHotkeyInputMonitor {
public:
// Invoked by LocalHotkeyInputMonitorManager.
class EventHandler {
public:
virtual ~EventHandler() = default;
virtual void OnDisconnectShortcut() = 0;
};
LocalHotkeyInputMonitorMac(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::OnceClosure disconnect_callback);
~LocalHotkeyInputMonitorMac() override;
private:
// The implementation resides in LocalHotkeyInputMonitorMac::Core class.
class Core;
scoped_refptr<Core> core_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(LocalHotkeyInputMonitorMac);
};
} // namespace
} // namespace remoting
@interface LocalHotkeyInputMonitorManager : NSObject {
@private
GTMCarbonHotKey* hotKey_;
remoting::LocalHotkeyInputMonitorMac::EventHandler* monitor_;
}
- (id)initWithMonitor:
(remoting::LocalHotkeyInputMonitorMac::EventHandler*)monitor;
// Called when the hotKey is hit.
- (void)hotKeyHit:(GTMCarbonHotKey*)hotKey;
// Must be called when the LocalHotkeyInputMonitorManager is no longer needed.
// Similar to NSTimer in that more than a simple release is required.
- (void)invalidate;
@end
@implementation LocalHotkeyInputMonitorManager
- (id)initWithMonitor:
(remoting::LocalHotkeyInputMonitorMac::EventHandler*)monitor {
if ((self = [super init])) {
monitor_ = monitor;
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.";
[self release];
return nil;
}
}
return self;
}
- (void)hotKeyHit:(GTMCarbonHotKey*)hotKey {
monitor_->OnDisconnectShortcut();
}
- (void)invalidate {
if (hotKey_) {
GTMCarbonEventDispatcherHandler* handler =
[GTMCarbonEventDispatcherHandler sharedEventDispatcherHandler];
[handler unregisterHotKey:hotKey_];
hotKey_ = nullptr;
}
}
@end
namespace remoting {
namespace {
class LocalHotkeyInputMonitorMac::Core
: public base::RefCountedThreadSafe<Core>,
public EventHandler {
public:
Core(scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::OnceClosure disconnect_callback);
void Start();
void Stop();
private:
friend class base::RefCountedThreadSafe<Core>;
~Core() override;
void StartOnUiThread();
void StopOnUiThread();
// EventHandler interface.
void OnDisconnectShortcut() override;
// Task runner on which public methods of this class must be called.
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
// Task runner on which |window_| is created.
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
LocalHotkeyInputMonitorManager* manager_;
// Invoked in the |caller_task_runner_| thread to report session disconnect
// requests.
base::OnceClosure disconnect_callback_;
DISALLOW_COPY_AND_ASSIGN(Core);
};
LocalHotkeyInputMonitorMac::LocalHotkeyInputMonitorMac(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::OnceClosure disconnect_callback)
: core_(new Core(caller_task_runner,
ui_task_runner,
std::move(disconnect_callback))) {
core_->Start();
}
LocalHotkeyInputMonitorMac::~LocalHotkeyInputMonitorMac() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
core_->Stop();
}
LocalHotkeyInputMonitorMac::Core::Core(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::OnceClosure disconnect_callback)
: caller_task_runner_(caller_task_runner),
ui_task_runner_(ui_task_runner),
manager_(nil),
disconnect_callback_(std::move(disconnect_callback)) {
DCHECK(disconnect_callback_);
}
void LocalHotkeyInputMonitorMac::Core::Start() {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
ui_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&Core::StartOnUiThread, this));
}
void LocalHotkeyInputMonitorMac::Core::Stop() {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
ui_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&Core::StopOnUiThread, this));
}
LocalHotkeyInputMonitorMac::Core::~Core() {
DCHECK_EQ(manager_, nil);
}
void LocalHotkeyInputMonitorMac::Core::StartOnUiThread() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
manager_ = [[LocalHotkeyInputMonitorManager alloc] initWithMonitor:this];
}
void LocalHotkeyInputMonitorMac::Core::StopOnUiThread() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
[manager_ invalidate];
[manager_ release];
manager_ = nil;
}
void LocalHotkeyInputMonitorMac::Core::OnDisconnectShortcut() {
if (disconnect_callback_) {
caller_task_runner_->PostTask(FROM_HERE, std::move(disconnect_callback_));
}
}
} // namespace
std::unique_ptr<LocalHotkeyInputMonitor> LocalHotkeyInputMonitor::Create(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::OnceClosure disconnect_callback) {
return std::make_unique<LocalHotkeyInputMonitorMac>(
caller_task_runner, ui_task_runner, std::move(disconnect_callback));
}
} // namespace remoting