chromium/remoting/host/input_monitor/local_hotkey_input_monitor_mac.mm

// Copyright 2018 The Chromium Authors
// 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>
#import <Carbon/Carbon.h>

#include <cstdint>
#include <utility>

#include "base/apple/scoped_cftyperef.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
#include "base/synchronization/lock.h"
#import "base/task/single_thread_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"

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(const LocalHotkeyInputMonitorMac&) = delete;
  LocalHotkeyInputMonitorMac& operator=(const LocalHotkeyInputMonitorMac&) =
      delete;

  ~LocalHotkeyInputMonitorMac() override;

 private:
  // The implementation resides in LocalHotkeyInputMonitorMac::Core class.
  class Core;
  scoped_refptr<Core> core_;

  SEQUENCE_CHECKER(sequence_checker_);
};

}  // namespace
}  // namespace remoting

@interface LocalHotkeyInputMonitorManager : NSObject

- (instancetype)initWithMonitor:
    (remoting::LocalHotkeyInputMonitorMac::EventHandler*)monitor;

@end

@implementation LocalHotkeyInputMonitorManager {
  id __strong _eventMonitor;

  raw_ptr<remoting::LocalHotkeyInputMonitorMac::EventHandler> _monitor;
}

- (instancetype)initWithMonitor:
    (remoting::LocalHotkeyInputMonitorMac::EventHandler*)monitor {
  if ((self = [super init])) {
    _monitor = monitor;

    LocalHotkeyInputMonitorManager* __weak weakSelf = self;
    auto eventHandler = ^NSEvent*(NSEvent* event) {
      LocalHotkeyInputMonitorManager* strongSelf = weakSelf;
      if (!strongSelf) {
        return event;
      }

      const NSEventModifierFlags requiredModifiers =
          NSEventModifierFlagOption | NSEventModifierFlagControl;
      if ((event.keyCode == kVK_Escape) &&
          (event.modifierFlags & requiredModifiers)) {
        // Trigger the callback.
        strongSelf->_monitor->OnDisconnectShortcut();

        // Stop the event propagation.
        return nil;
      }

      // Otherwise, let the event continue propagating.
      return event;
    };

    _eventMonitor =
        [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown
                                              handler:eventHandler];
  }
  return self;
}

- (void)dealloc {
  if (_eventMonitor) {
    [NSEvent removeMonitor:_eventMonitor];
  }
}

@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);

  Core(const Core&) = delete;
  Core& operator=(const Core&) = delete;

  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* __strong manager_;

  // Invoked in the |caller_task_runner_| thread to report session disconnect
  // requests.
  base::OnceClosure disconnect_callback_;
};

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),
      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_ = 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