chromium/ui/events/win/media_keyboard_hook_win.cc

// 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 "ui/events/win/keyboard_hook_win_base.h"

#include "ui/events/event.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/win/events_win_utils.h"

namespace ui {

namespace {

bool IsMediaKey(DWORD vk) {
  return vk == VK_MEDIA_NEXT_TRACK || vk == VK_MEDIA_PREV_TRACK ||
         vk == VK_MEDIA_PLAY_PAUSE || vk == VK_MEDIA_STOP;
}

class MediaKeyboardHookWinImpl : public KeyboardHookWinBase {
 public:
  MediaKeyboardHookWinImpl(KeyEventCallback callback,
                           bool enable_hook_registration);

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

  ~MediaKeyboardHookWinImpl() override;

  // KeyboardHookWinBase implementation.
  bool ProcessKeyEventMessage(WPARAM w_param,
                              DWORD vk,
                              DWORD scan_code,
                              DWORD time_stamp) override;

  bool Register();

 private:
  static LRESULT CALLBACK ProcessKeyEvent(int code,
                                          WPARAM w_param,
                                          LPARAM l_param);

  static MediaKeyboardHookWinImpl* instance_;

  // Tracks the last non-located key down seen in order to determine if the
  // current key event should be marked as a repeated key press.
  DWORD last_key_down_ = 0;
};

// static
MediaKeyboardHookWinImpl* MediaKeyboardHookWinImpl::instance_ = nullptr;

MediaKeyboardHookWinImpl::MediaKeyboardHookWinImpl(
    KeyEventCallback callback,
    bool enable_hook_registration)
    : KeyboardHookWinBase(
          std::optional<base::flat_set<DomCode>>(
              {DomCode::MEDIA_PLAY_PAUSE, DomCode::MEDIA_STOP,
               DomCode::MEDIA_TRACK_NEXT, DomCode::MEDIA_TRACK_PREVIOUS}),
          std::move(callback),
          enable_hook_registration) {}

MediaKeyboardHookWinImpl::~MediaKeyboardHookWinImpl() {
  if (!enable_hook_registration())
    return;

  DCHECK_EQ(instance_, this);
  instance_ = nullptr;
}

bool MediaKeyboardHookWinImpl::Register() {
  // Only one instance of this class can be registered at a time.
  DCHECK(!instance_);
  instance_ = this;

  return KeyboardHookWinBase::Register(
      reinterpret_cast<HOOKPROC>(&MediaKeyboardHookWinImpl::ProcessKeyEvent));
}

// static
LRESULT CALLBACK MediaKeyboardHookWinImpl::ProcessKeyEvent(int code,
                                                           WPARAM w_param,
                                                           LPARAM l_param) {
  return KeyboardHookWinBase::ProcessKeyEvent(instance_, code, w_param,
                                              l_param);
}

bool MediaKeyboardHookWinImpl::ProcessKeyEventMessage(WPARAM w_param,
                                                      DWORD vk,
                                                      DWORD scan_code,
                                                      DWORD time_stamp) {
  if (!IsMediaKey(vk))
    return false;

  bool is_repeat = false;
  CHROME_MSG msg = {nullptr, static_cast<UINT>(w_param), vk,
                    GetLParamFromScanCode(scan_code), time_stamp};
  EventType event_type = EventTypeFromMSG(msg);
  if (event_type == EventType::kKeyPressed) {
    is_repeat = (last_key_down_ == vk);
    last_key_down_ = vk;
  } else {
    DCHECK_EQ(event_type, EventType::kKeyReleased);
    last_key_down_ = 0;
  }

  std::unique_ptr<KeyEvent> key_event =
      std::make_unique<KeyEvent>(KeyEventFromMSG(msg));
  if (is_repeat)
    key_event->SetFlags(key_event->flags() | EF_IS_REPEAT);
  ForwardCapturedKeyEvent(key_event.get());

  // If the event is handled, don't propagate to the OS.
  return key_event->handled();
}

}  // namespace

// static
std::unique_ptr<KeyboardHook> KeyboardHook::CreateMediaKeyboardHook(
    KeyEventCallback callback) {
  std::unique_ptr<MediaKeyboardHookWinImpl> keyboard_hook =
      std::make_unique<MediaKeyboardHookWinImpl>(
          std::move(callback),
          /*enable_hook_registration=*/true);

  if (!keyboard_hook->Register())
    return nullptr;

  return keyboard_hook;
}

std::unique_ptr<KeyboardHookWinBase>
KeyboardHookWinBase::CreateMediaKeyboardHookForTesting(
    KeyEventCallback callback) {
  return std::make_unique<MediaKeyboardHookWinImpl>(
      std::move(callback),
      /*enable_hook_registration=*/false);
}

}  // namespace ui