chromium/ui/events/ash/keyboard_capability.h

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_EVENTS_ASH_KEYBOARD_CAPABILITY_H_
#define UI_EVENTS_ASH_KEYBOARD_CAPABILITY_H_

#include <memory>
#include <optional>
#include <vector>

#include "base/containers/fixed_flat_map.h"
#include "base/containers/fixed_flat_set.h"
#include "base/containers/flat_map.h"
#include "base/files/scoped_file.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/events/ash/modifier_split_dogfood_controller.h"
#include "ui/events/ash/mojom/meta_key.mojom-shared.h"
#include "ui/events/ash/mojom/modifier_key.mojom-shared.h"
#include "ui/events/ash/top_row_action_keys.h"
#include "ui/events/devices/input_device_event_observer.h"
#include "ui/events/devices/keyboard_device.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/ozone/evdev/device_event_dispatcher_evdev.h"
#include "ui/events/ozone/evdev/event_device_info.h"

namespace ui {

static const TopRowActionKey kLayout1TopRowActionKeys[] = {
    TopRowActionKey::kBack,
    TopRowActionKey::kForward,
    TopRowActionKey::kRefresh,
    TopRowActionKey::kFullscreen,
    TopRowActionKey::kOverview,
    TopRowActionKey::kScreenBrightnessDown,
    TopRowActionKey::kScreenBrightnessUp,
    TopRowActionKey::kVolumeMute,
    TopRowActionKey::kVolumeDown,
    TopRowActionKey::kVolumeUp,
};

static const TopRowActionKey kLayout2TopRowActionKeys[] = {
    TopRowActionKey::kBack,
    TopRowActionKey::kRefresh,
    TopRowActionKey::kFullscreen,
    TopRowActionKey::kOverview,
    TopRowActionKey::kScreenBrightnessDown,
    TopRowActionKey::kScreenBrightnessUp,
    TopRowActionKey::kPlayPause,
    TopRowActionKey::kVolumeMute,
    TopRowActionKey::kVolumeDown,
    TopRowActionKey::kVolumeUp,
};

static const TopRowActionKey kLayoutWilcoDrallionTopRowActionKeys[] = {
    TopRowActionKey::kBack,
    TopRowActionKey::kRefresh,
    TopRowActionKey::kFullscreen,
    TopRowActionKey::kOverview,
    TopRowActionKey::kScreenBrightnessDown,
    TopRowActionKey::kScreenBrightnessUp,
    TopRowActionKey::kVolumeMute,
    TopRowActionKey::kVolumeDown,
    TopRowActionKey::kVolumeUp,
};

// Keyboard layout1 map between top row keys to function keys.
inline constexpr auto kLayout1TopRowKeyToFKeyMap =
    base::MakeFixedFlatMap<KeyboardCode, KeyboardCode>({
        {KeyboardCode::VKEY_BROWSER_BACK, KeyboardCode::VKEY_F1},
        {KeyboardCode::VKEY_BROWSER_FORWARD, KeyboardCode::VKEY_F2},
        {KeyboardCode::VKEY_BROWSER_REFRESH, KeyboardCode::VKEY_F3},
        {KeyboardCode::VKEY_ZOOM, KeyboardCode::VKEY_F4},
        {KeyboardCode::VKEY_MEDIA_LAUNCH_APP1, KeyboardCode::VKEY_F5},
        {KeyboardCode::VKEY_BRIGHTNESS_DOWN, KeyboardCode::VKEY_F6},
        {KeyboardCode::VKEY_BRIGHTNESS_UP, KeyboardCode::VKEY_F7},
        {KeyboardCode::VKEY_VOLUME_MUTE, KeyboardCode::VKEY_F8},
        {KeyboardCode::VKEY_VOLUME_DOWN, KeyboardCode::VKEY_F9},
        {KeyboardCode::VKEY_VOLUME_UP, KeyboardCode::VKEY_F10},
    });

// Keyboard layout2 map between top row keys to function keys.
inline constexpr auto kLayout2TopRowKeyToFKeyMap =
    base::MakeFixedFlatMap<KeyboardCode, KeyboardCode>({
        {KeyboardCode::VKEY_BROWSER_BACK, KeyboardCode::VKEY_F1},
        {KeyboardCode::VKEY_BROWSER_REFRESH, KeyboardCode::VKEY_F2},
        {KeyboardCode::VKEY_ZOOM, KeyboardCode::VKEY_F3},
        {KeyboardCode::VKEY_MEDIA_LAUNCH_APP1, KeyboardCode::VKEY_F4},
        {KeyboardCode::VKEY_BRIGHTNESS_DOWN, KeyboardCode::VKEY_F5},
        {KeyboardCode::VKEY_BRIGHTNESS_UP, KeyboardCode::VKEY_F6},
        {KeyboardCode::VKEY_MEDIA_PLAY_PAUSE, KeyboardCode::VKEY_F7},
        {KeyboardCode::VKEY_VOLUME_MUTE, KeyboardCode::VKEY_F8},
        {KeyboardCode::VKEY_VOLUME_DOWN, KeyboardCode::VKEY_F9},
        {KeyboardCode::VKEY_VOLUME_UP, KeyboardCode::VKEY_F10},
    });

// Keyboard wilco/drallion map between top row keys to function keys.
// TODO(dpad): Handle privacy screen better on drallion devices.
// TODO(zhangwenyu): Both F3 and F12 map to VKEY_ZOOM for wilco. Handle edge
// case when creating the top row accelerator alias for VKEY_ZOOM key.
inline constexpr auto kLayoutWilcoDrallionTopRowKeyToFKeyMap =
    base::MakeFixedFlatMap<KeyboardCode, KeyboardCode>({
        {KeyboardCode::VKEY_BROWSER_BACK, KeyboardCode::VKEY_F1},
        {KeyboardCode::VKEY_BROWSER_REFRESH, KeyboardCode::VKEY_F2},
        {KeyboardCode::VKEY_ZOOM, KeyboardCode::VKEY_F3},
        {KeyboardCode::VKEY_MEDIA_LAUNCH_APP1, KeyboardCode::VKEY_F4},
        {KeyboardCode::VKEY_BRIGHTNESS_DOWN, KeyboardCode::VKEY_F5},
        {KeyboardCode::VKEY_BRIGHTNESS_UP, KeyboardCode::VKEY_F6},
        {KeyboardCode::VKEY_VOLUME_MUTE, KeyboardCode::VKEY_F7},
        {KeyboardCode::VKEY_VOLUME_DOWN, KeyboardCode::VKEY_F8},
        {KeyboardCode::VKEY_VOLUME_UP, KeyboardCode::VKEY_F9},
    });

// A map between six pack keys to search system keys.
inline constexpr auto kSixPackKeyToSearchSystemKeyMap =
    base::MakeFixedFlatMap<KeyboardCode, KeyboardCode>({
        {KeyboardCode::VKEY_DELETE, KeyboardCode::VKEY_BACK},
        {KeyboardCode::VKEY_HOME, KeyboardCode::VKEY_LEFT},
        {KeyboardCode::VKEY_PRIOR, KeyboardCode::VKEY_UP},
        {KeyboardCode::VKEY_END, KeyboardCode::VKEY_RIGHT},
        {KeyboardCode::VKEY_NEXT, KeyboardCode::VKEY_DOWN},
        {KeyboardCode::VKEY_INSERT, KeyboardCode::VKEY_BACK},
    });

// A map between six pack keys to function keys.
inline constexpr auto kSixPackKeyToFnKeyMap =
    base::MakeFixedFlatMap<KeyboardCode, KeyboardCode>({
        {KeyboardCode::VKEY_DELETE, KeyboardCode::VKEY_BACK},
        {KeyboardCode::VKEY_HOME, KeyboardCode::VKEY_LEFT},
        {KeyboardCode::VKEY_PRIOR, KeyboardCode::VKEY_UP},
        {KeyboardCode::VKEY_END, KeyboardCode::VKEY_RIGHT},
        {KeyboardCode::VKEY_NEXT, KeyboardCode::VKEY_DOWN},
    });

// A map between six pack keys to alt system keys.
inline constexpr auto kSixPackKeyToAltSystemKeyMap =
    base::MakeFixedFlatMap<KeyboardCode, KeyboardCode>({
        {KeyboardCode::VKEY_DELETE, KeyboardCode::VKEY_BACK},
        {KeyboardCode::VKEY_HOME, KeyboardCode::VKEY_UP},
        {KeyboardCode::VKEY_PRIOR, KeyboardCode::VKEY_UP},
        {KeyboardCode::VKEY_END, KeyboardCode::VKEY_DOWN},
        {KeyboardCode::VKEY_NEXT, KeyboardCode::VKEY_DOWN},
    });

// A keyboard util API to provide various keyboard capability information, such
// as top row key layout, existence of certain keys, what is top right key, etc.
class KeyboardCapability : public InputDeviceEventObserver {
 public:
  using ScanCodeToEvdevKeyConverter =
      base::RepeatingCallback<std::optional<uint32_t>(const base::ScopedFD& fd,
                                                      uint32_t scancode)>;
  enum class DeviceType {
    kDeviceUnknown = 0,
    kDeviceInternalKeyboard,
    kDeviceInternalRevenKeyboard,
    kDeviceExternalAppleKeyboard,
    kDeviceExternalChromeOsKeyboard,
    kDeviceExternalNullTopRowChromeOsKeyboard,
    kDeviceExternalGenericKeyboard,
    kDeviceExternalUnknown,
    kDeviceHotrodRemote,
    kDeviceVirtualCoreKeyboard,  // X-server generated events.
  };

  enum class KeyboardTopRowLayout {
    // The original Chrome OS Layout:
    // Browser Back, Browser Forward, Refresh, Full Screen, Overview,
    // Brightness Down, Brightness Up, Mute, Volume Down, Volume Up.
    kKbdTopRowLayout1 = 1,
    kKbdTopRowLayoutDefault = kKbdTopRowLayout1,
    kKbdTopRowLayoutMin = kKbdTopRowLayout1,
    // 2017 keyboard layout: Browser Forward is gone and Play/Pause
    // key is added between Brightness Up and Mute.
    kKbdTopRowLayout2 = 2,
    // Keyboard layout and handling for Wilco.
    kKbdTopRowLayoutWilco = 3,
    kKbdTopRowLayoutDrallion = 4,

    // Handling for all keyboards that support supplying a custom layout
    // via sysfs attribute (aka Vivaldi). See crbug.com/1076241
    kKbdTopRowLayoutCustom = 5,
    kKbdTopRowLayoutMax = kKbdTopRowLayoutCustom
  };

  struct KeyboardInfo {
    KeyboardInfo();
    KeyboardInfo(KeyboardInfo&&);
    KeyboardInfo& operator=(KeyboardInfo&&);
    KeyboardInfo(const KeyboardInfo&) = delete;
    KeyboardInfo& operator=(const KeyboardInfo&) = delete;
    ~KeyboardInfo();

    DeviceType device_type = DeviceType::kDeviceInternalKeyboard;
    KeyboardTopRowLayout top_row_layout =
        KeyboardTopRowLayout::kKbdTopRowLayoutDefault;
    std::vector<uint32_t> top_row_scan_codes;
    std::vector<TopRowActionKey> top_row_action_keys;
  };

  KeyboardCapability();
  explicit KeyboardCapability(ScanCodeToEvdevKeyConverter converter);
  KeyboardCapability(const KeyboardCapability&) = delete;
  KeyboardCapability& operator=(const KeyboardCapability&) = delete;
  ~KeyboardCapability() override;

  // TODO: get rid of this. Equivalent to
  // std::make_unique<KeyboardCapability>(), and it is no longer stub.
  static std::unique_ptr<KeyboardCapability> CreateStubKeyboardCapability();

  // Generates an `EventDeviceInfo` from a given input device.
  static std::unique_ptr<EventDeviceInfo> CreateEventDeviceInfoFromInputDevice(
      const KeyboardDevice& keyboard);

  // Converts from the given `key_code` to the corresponding meaning in
  // `TopRowActionKey` enum.
  static std::optional<TopRowActionKey> ConvertToTopRowActionKey(
      ui::KeyboardCode key_code);

  // Converts the given `action_key` to the corresponding `KeyboardCode` VKEY.
  static std::optional<KeyboardCode> ConvertToKeyboardCode(
      TopRowActionKey action_key);

  // Check if a key code is one of the top row keys.
  static bool IsTopRowKey(const KeyboardCode& key_code);

  // Check if a key code is one of the six pack keys.
  static bool IsSixPackKey(const KeyboardCode& key_code);

  // Find the mapped function key if the given key code is a top row key for
  // the given keyboard.
  // TODO(zhangwenyu): Support custom vivaldi layouts.
  std::optional<KeyboardCode> GetMappedFKeyIfExists(
      const KeyboardCode& key_code,
      const KeyboardDevice& keyboard) const;

  // Check if a keyboard has a launcher button rather than a search button.
  bool HasLauncherButton(const KeyboardDevice& keyboard) const;
  bool HasLauncherButtonOnAnyKeyboard() const;

  // Check if a keyboard has a six pack key.
  static bool HasSixPackKey(const KeyboardDevice& keyboard);

  // Check if any of the connected keyboards has a six pack key.
  static bool HasSixPackOnAnyKeyboard();

  // Check if the keycode is a function key.
  static bool IsFunctionKey(ui::KeyboardCode code);

  // Check if the keycode is either the F11 or F12 function key.
  static bool IsF11OrF12(ui::KeyboardCode code);

  // Returns the set of modifier keys present on the given keyboard.
  std::vector<mojom::ModifierKey> GetModifierKeys(
      const KeyboardDevice& keyboard) const;
  std::vector<mojom::ModifierKey> GetModifierKeys(int device_id) const;

  // Returns the device type of the given keyboard.
  DeviceType GetDeviceType(const KeyboardDevice& keyboard) const;
  DeviceType GetDeviceType(int device_id) const;

  // Returns the device's top row layout.
  KeyboardTopRowLayout GetTopRowLayout(const KeyboardDevice& keyboard) const;
  KeyboardTopRowLayout GetTopRowLayout(int device_id) const;

  // Returns the device's top row scan codes. If the device does not have a
  // custom top row, the returned list will be null or empty.
  const std::vector<uint32_t>* GetTopRowScanCodes(
      const KeyboardDevice& keyboard) const;
  const std::vector<uint32_t>* GetTopRowScanCodes(int device_id) const;

  // Takes a `KeyboardInfo` to use for testing the passed in keyboard.
  void SetKeyboardInfoForTesting(const KeyboardDevice& keyboard, KeyboardInfo);

  // Disables "trimming" which means the `keyboard_info_map_` will not remove
  // entries when they are disconnected.
  void DisableKeyboardInfoTrimmingForTesting();

  // InputDeviceEventObserver:
  void OnDeviceListsComplete() override;
  void OnInputDeviceConfigurationChanged(uint8_t input_device_types) override;

  // Check if a specific key event exists on a given keyboard.
  bool HasKeyEvent(const KeyboardCode& key_code,
                   const KeyboardDevice& keyboard) const;

  // Check if any of the connected keyboards has a specific key event.
  bool HasKeyEventOnAnyKeyboard(const KeyboardCode& key_code) const;

  // Check if a given `action_key` exists on the given keyboard.
  bool HasTopRowActionKey(const KeyboardDevice& keyboard,
                          TopRowActionKey action_key) const;
  bool HasTopRowActionKeyOnAnyKeyboard(TopRowActionKey action_key) const;

  // Check if the globe key exists on the given keyboard.
  bool HasGlobeKey(const KeyboardDevice& keyboard) const;
  bool HasGlobeKeyOnAnyKeyboard() const;

  // Check if the calculator key exists on the given keyboard.
  bool HasCalculatorKey(const KeyboardDevice& keyboard) const;
  bool HasCalculatorKeyOnAnyKeyboard() const;

  // Check if the browser search key exists on the given keyboard.
  bool HasBrowserSearchKey(const KeyboardDevice& keyboard) const;
  bool HasBrowserSearchKeyOnAnyKeyboard() const;

  // Check if the help key exists on the given keyboard.
  bool HasHelpKey(const KeyboardDevice& keyboard) const;
  bool HasHelpKeyOnAnyKeyboard() const;

  // Check if the settings key exists on the given keyboard.
  bool HasSettingsKey(const KeyboardDevice& keyboard) const;
  bool HasSettingsKeyOnAnyKeyboard() const;

  // Check if the given keyboard has media keys including:
  // - Media Rewind
  // - Media Fastforward
  // - Media Play
  // - Media Pause
  // These keys do not exist on any internal chromeos keyboards, but are likely
  // to potentially exist on external keyboards.
  bool HasMediaKeys(const KeyboardDevice& keyboard) const;
  bool HasMediaKeysOnAnyKeyboard() const;

  // Check if the assistant key exists on the given keyboard.
  bool HasAssistantKey(const KeyboardDevice& keyboard) const;
  bool HasAssistantKey(int device_id) const;
  bool HasAssistantKeyOnAnyKeyboard() const;

  // Check if the CapsLock key exists on the given keyboard.
  bool HasCapsLockKey(const KeyboardDevice& keyboard) const;

  // Check if the Function key exists on the given keyboard.
  bool HasFunctionKey(const KeyboardDevice& keyboard) const;
  bool HasFunctionKey(int device_id) const;
  bool HasFunctionKeyOnAnyKeyboard() const;

  // Check if the RightAlt key exists on the given keyboard.
  bool HasRightAltKey(const KeyboardDevice& keyboard) const;
  bool HasRightAltKey(int device_id) const;

  // Check if the RightAlt key exists, but only for on OOBE screen.
  bool HasRightAltKeyForOobe(const KeyboardDevice& keyboard) const;
  bool HasRightAltKeyForOobe(int device_id) const;

  // Returns the appropriate meta key present on the given keyboard.
  ui::mojom::MetaKey GetMetaKey(const KeyboardDevice& keyboard) const;
  ui::mojom::MetaKey GetMetaKey(int device_id) const;

  // Returns the meta key to display in the UI to represent the overall current
  // keyboard situation. This will only return either Launcher, Search, or
  // LauncherRefresh.
  ui::mojom::MetaKey GetMetaKeyToDisplay() const;

  // Whether or not to use the updated icons for the keyboard.
  bool UseRefreshedIcons() const;

  // Finds the keyboard with the corresponding  `device_id` and checks its
  // `DeviceType` to determine if it's a split modifier keyboard.
  bool IsSplitModifierKeyboard(const KeyboardDevice& keyboard) const;

  // Finds the keyboard with the corresponding  `device_id` and checks its
  // `DeviceType` to determine if it's a ChromeOS keyboard.
  bool IsChromeOSKeyboard(int device_id) const;

  // Gets the corresponding function key for the given `action_key` on the
  // given `keyboard`.
  std::optional<KeyboardCode> GetCorrespondingFunctionKey(
      const KeyboardDevice& keyboard,
      TopRowActionKey action_key) const;

  // Gets the corresponding action key for the given `key_code` which must be an
  // F-Key in the range of F1 to F24 for the given `keyboard`
  std::optional<TopRowActionKey> GetCorrespondingActionKeyForFKey(
      const KeyboardDevice& keyboard,
      KeyboardCode key_code) const;

  const std::vector<TopRowActionKey>* GetTopRowActionKeys(
      const KeyboardDevice& keyboard) const;
  const std::vector<TopRowActionKey>* GetTopRowActionKeys(int device_id) const;

  // Whether or not the given keyboard is a split modifier keyboard and
  // qualifies to forcibly enable features.
  bool IsSplitModifierKeyboardForOverride(const KeyboardDevice& keyboard) const;

  void SetBoardNameForTesting(const std::string& board_name);

  const base::flat_map<int, KeyboardInfo>& keyboard_info_map() const {
    return keyboard_info_map_;
  }

  bool IsModifierSplitEnabled() const {
    return modifier_split_dogfood_controller_->IsEnabled();
  }

  void ForceEnableFeature();

  void ResetModifierSplitDogfoodControllerForTesting();

 private:
  const KeyboardInfo* GetKeyboardInfo(const KeyboardDevice& keyboard) const;
  void TrimKeyboardInfoMap();

  ScanCodeToEvdevKeyConverter scan_code_to_evdev_key_converter_;

  // Stores event device info objects so they do not need to be constructed
  // multiple times. This is mutable to allow caching results from the APIs
  // which are effectively const.
  mutable base::flat_map<int, KeyboardInfo> keyboard_info_map_;

  // Whether or not to disable "trimming" which means the `keyboard_info_map_`
  // will not remove entries when they are disconnected.
  bool should_disable_trimming_ = false;

  // Board name of the current ChromeOS device.
  std::string board_name_;

  std::unique_ptr<ModifierSplitDogfoodController>
      modifier_split_dogfood_controller_;
};

}  // namespace ui

#endif  // UI_EVENTS_ASH_KEYBOARD_CAPABILITY_H_