chromium/ui/events/ash/event_rewriter_ash.h

// Copyright 2014 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_EVENT_REWRITER_ASH_H_
#define UI_EVENTS_ASH_EVENT_REWRITER_ASH_H_

#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "ui/events/ash/event_rewriter_utils.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/events/ash/mojom/extended_fkeys_modifier.mojom-shared.h"
#include "ui/events/ash/mojom/modifier_key.mojom-shared.h"
#include "ui/events/ash/mojom/simulate_right_click_modifier.mojom-shared.h"
#include "ui/events/ash/mojom/six_pack_shortcut_modifier.mojom-shared.h"
#include "ui/events/event.h"
#include "ui/events/event_rewriter.h"
#include "ui/events/keycodes/dom/dom_key.h"

namespace ash {
namespace input_method {
class ImeKeyboard;
}
}  // namespace ash

namespace ui {

enum class DomCode : uint32_t;
struct KeyboardDevice;

// EventRewriterAsh makes various changes to keyboard-related events,
// including KeyEvents and some other events with keyboard modifier flags:
// - maps certain non-character keys according to user preferences
//   (Control, Alt, Search, Caps Lock, Escape, Backspace, Diamond);
// - maps Command to Control on Apple keyboards;
// - converts numeric pad editing keys to their numeric forms;
// - converts top-row function keys to special keys where necessary;
// - handles various key combinations like Search+Backspace -> Delete
//   and Search+number to Fnumber;
// - handles key/pointer combinations like Alt+Button1 -> Button3.
class EventRewriterAsh : public EventRewriter {
 public:
  // Things that keyboard-related rewriter phases can change about an Event.
  struct MutableKeyState {
    constexpr MutableKeyState() = default;
    explicit MutableKeyState(const KeyEvent* key_event);
    constexpr MutableKeyState(int input_flags,
                              DomCode input_code,
                              DomKey::Base input_key,
                              KeyboardCode input_key_code)
        : flags(input_flags),
          code(input_code),
          key(input_key),
          key_code(input_key_code) {}

    friend bool operator==(const MutableKeyState& lhs,
                           const MutableKeyState& rhs) {
      return lhs.flags == rhs.flags && lhs.code == rhs.code &&
             lhs.key == rhs.key && lhs.key_code == rhs.key_code;
    }

    friend bool operator!=(const MutableKeyState& lhs,
                           const MutableKeyState& rhs) {
      return !(lhs == rhs);
    }

    int flags = 0;
    DomCode code = DomCode::NONE;
    DomKey::Base key = 0;
    KeyboardCode key_code = KeyboardCode::VKEY_NONAME;
  };

  class Delegate {
   public:
    virtual ~Delegate() = default;

    // Returns true only if the the key event was rewritten to ALTGR. For most
    // cases, it is expected that this function returns false as most key events
    // do not involve ALTGR. Returns false if SuppressModifierKeyRewrites was
    // called to suppress modifier rewrites.
    virtual bool RewriteModifierKeys() = 0;

    // Suppresses all modifier key rewrites and makes |RewriteModifierKeys|
    // always return false if |should_suppress| is true.
    virtual void SuppressModifierKeyRewrites(bool should_suppress) = 0;

    // Returns whether or not Meta + Top Row Keys should be rewritten. Should
    // return correctly with respect to the values set in
    // |SuppressMetaTopRowKeyRewrites|. If per-device settings are enabled, it
    // should instead return the correct setting for the given `device_id`.
    virtual bool RewriteMetaTopRowKeyComboEvents(int device_id) const = 0;

    // Set whether or not Meta + Top Row Keys key events should be rewritten.
    virtual void SuppressMetaTopRowKeyComboRewrites(bool should_suppress) = 0;

    // If per-device settings is disabled, returns the remapped modifier value
    // from prefs by looking up the given |pref_name|. If per-device settings is
    // enabled, returns the remapped modifier value for |device_id| and
    // |modifier_key|.
    // TODO(dpad): Remove |pref_name| once fully transitioned to per-device
    // settings.
    virtual std::optional<mojom::ModifierKey> GetKeyboardRemappedModifierValue(
        int device_id,
        mojom::ModifierKey modifier_key,
        const std::string& pref_name) const = 0;

    // Returns true if the target would prefer to receive raw
    // function keys instead of having them rewritten into back, forward,
    // brightness, volume, etc. or if the user has specified that they desire
    // top-row keys to be treated as function keys globally.
    virtual bool TopRowKeysAreFunctionKeys(int device_id) const = 0;

    // Returns true if the |key_code| and |flags| have been resgistered for
    // extensions and EventRewriterAsh will not rewrite the event.
    virtual bool IsExtensionCommandRegistered(KeyboardCode key_code,
                                              int flags) const = 0;

    // Returns true if search key accelerator is reserved for current active
    // window and EventRewriterAsh will not rewrite the event.
    virtual bool IsSearchKeyAcceleratorReserved() const = 0;

    // Used to send a notification about Alt-Click being deprecated.
    // The notification is only sent once per user session, and this function
    // returns true if the notification was shown.
    virtual bool NotifyDeprecatedRightClickRewrite() = 0;

    // Used to send a notification about a Six Pack (PageUp, PageDown, Home,
    // End, Insert, Delete) key rewrite being deprecated. The notification
    // is only sent once per user session, and this function returns true if
    // the notification was shown.
    virtual bool NotifyDeprecatedSixPackKeyRewrite(KeyboardCode key_code) = 0;

    // Used to record when either Alt+Click or Search+Click is remapped to a
    // right click event. The `kEventRemappedToRightClick` pref will be used
    // to determine the default behavior for simulating a right click.
    virtual void RecordEventRemappedToRightClick(
        bool alt_based_right_click) = 0;

    // Used to record Alt/Search based key event rewrites for Six Pack keys.
    // `alt_based` tells us whether this "six pack" event was produced by an
    // Alt or Search/Launcher based keyboard shortcut. The corresponding
    // "six pack" key pref will be incremented when the Alt variant is used and
    // decremented when the Search/Launcher variant is used. This information
    // will determine the default behavior for rewriting a key event to a
    // "six pack" key.
    virtual void RecordSixPackEventRewrite(KeyboardCode key_code,
                                           bool alt_based) = 0;

    // Returns the modifier (Alt/Search) that must be pressed when remapping
    // an event to right click for `device_id` or `std::nullopt` if settings
    // for the device are unable to be retrieved. If the return value is
    // `SimulateRightClickModifier::kNone` or `std::nullopt`, the event
    // will not be rewritten to a right click.
    virtual std::optional<ui::mojom::SimulateRightClickModifier>
    GetRemapRightClickModifier(int device_id) = 0;

    // Returns whether the Alt or Search based shortcut variant must be used
    // to perform a Six Pack (PageUp, PageDown, Home, End, Insert, Delete) key
    // action for `device_id`. The key event will not be rewritten if the
    // return value is either std::nullopt (settings for `device_id`
    // weren't found) or the key is mapped to `SixPackShortcutModifier::kNone`.
    // `key_code` is used to look up the correct modifier for the Six Pack key.
    virtual std::optional<ui::mojom::SixPackShortcutModifier>
    GetShortcutModifierForSixPackKey(int device_id,
                                     ui::KeyboardCode key_code) = 0;

    // Used to send a notification when an incoming event would have been
    // remapped to a right click but either the user's setting is inconsistent
    // with the matched modifier key or remapping to right click is disabled.
    virtual void NotifyRightClickRewriteBlockedBySetting(
        ui::mojom::SimulateRightClickModifier blocked_modifier,
        ui::mojom::SimulateRightClickModifier active_modifier) = 0;

    // Used to send a notification when an incoming event would have been
    // remapped to a Six Pack key action but either the user's setting is
    // inconsistent with the matched modifier key or remapping to right click
    // is disabled. `key_code` is used to lookup the correct Six Pack key and
    // the `device_id` is provided to route the user to the correct remap keys
    // subpage when the notification is clicked on.
    virtual void NotifySixPackRewriteBlockedBySetting(
        ui::KeyboardCode key_code,
        ui::mojom::SixPackShortcutModifier blocked_modifier,
        ui::mojom::SixPackShortcutModifier active_modifier,
        int device_id) = 0;

    // Returns the modifier for rewriting key events to F11/F12 for ChromeOS
    // keyboards with less than 12 top row keys. `key_code` must be either
    // `ui::KeyboardCode::VKEY_F11` or `ui::KeyboardCode::VKEY_F12` and is used
    // used to determine if the setting for F11 or F12 should be retrieved for
    // the keyboard with the given `device_id`. The key event will not be
    // rewritten if the return value is either std::nullopt (settings for
    // `device_id` weren't found) or if an invalid `key_code` was passed in.
    virtual std::optional<ui::mojom::ExtendedFkeysModifier>
    GetExtendedFkeySetting(int device_id, ui::KeyboardCode key_code) = 0;

    // Used to send a notification when a income event is a shortcut with
    // arrow key and search key but could not find a matched remapped event,
    // and it's a split modifier keyboard.
    virtual void NotifySixPackRewriteBlockedByFnKey(
        ui::KeyboardCode key_code,
        ui::mojom::SixPackShortcutModifier modifier) = 0;

    // Used to send a notification when a income event is a shortcut with
    // top row key and search key but could not find a matched remapped event,
    // and it's a split modifier keyboard.
    virtual void NotifyTopRowRewriteBlockedByFnKey() = 0;
  };

  // Does not take ownership of the |sticky_keys_controller|, which may also be
  // nullptr (for testing without ash), in which case sticky key operations
  // don't happen.
  EventRewriterAsh(Delegate* delegate,
                   KeyboardCapability* keyboard_capability,
                   EventRewriter* sticky_keys_controller,
                   bool privacy_screen_supported);

  // Only explicitly use this constructor for tests. Does not take ownership of
  // |ime_keyboard|.
  EventRewriterAsh(Delegate* delegate,
                   KeyboardCapability* keyboard_capability,
                   EventRewriter* sticky_keys_controller,
                   bool privacy_screen_supported,
                   ash::input_method::ImeKeyboard* ime_keyboard);
  EventRewriterAsh(const EventRewriterAsh&) = delete;
  EventRewriterAsh& operator=(const EventRewriterAsh&) = delete;
  ~EventRewriterAsh() override;

  // Reset the internal rewriter state so that next set of tests can be ran on
  // the same rewriter, if needed.
  void ResetStateForTesting();

  // Calls RewriteMouseEvent().
  void RewriteMouseButtonEventForTesting(const MouseEvent& event,
                                         const Continuation continuation);

  void set_last_keyboard_device_id_for_testing(int device_id) {
    last_keyboard_device_id_ = device_id;
  }

  void set_privacy_screen_for_testing(bool supported) {
    privacy_screen_supported_ = supported;
  }

  // Enable/disable alt + key or mouse event remapping. For Alt + left click
  // mapping to the right click, it only applies if the feature
  // `chromeos::features::kUseSearchClickForRightClick` is not enabled.
  void set_alt_down_remapping_enabled(bool enabled) {
    is_alt_down_remapping_enabled_ = enabled;
  }

  // EventRewriter overrides:
  EventDispatchDetails RewriteEvent(const Event& event,
                                    const Continuation continuation) override;

  // Generate a new key event from an original key event and the replacement
  // state determined by a key rewriter.
  static void BuildRewrittenKeyEvent(const KeyEvent& key_event,
                                     const MutableKeyState& state,
                                     std::unique_ptr<Event>* rewritten_event);

  // Given a keyboard device, returns true if we get back the Assistant key
  // property without getting an error. Property value is stored in
  // |has_assistant_key|.
  static bool HasAssistantKeyOnKeyboard(const KeyboardDevice& keyboard_device,
                                        bool* has_assistant_key);

  // Part of rewrite phases below. These methods are public only so that
  // SpokenFeedbackRewriter can ask for rewritten modifier and function keys.

  // Returns true when the input |state| has key |DomKey::ALT_GRAPH_LATCH| and
  // is remapped.
  // TODO(crbug.com/40265877): Remove this function.
  bool RewriteModifierKeys(const KeyEvent& event, MutableKeyState* state) {
    return RewriteModifierKeys(event, last_keyboard_device_id_, state);
  }
  void RewriteFunctionKeys(const KeyEvent& event, MutableKeyState* state) {
    return RewriteFunctionKeys(event, last_keyboard_device_id_, state);
  }

 private:
  // By default the top row (F1-F12) keys are system keys for back, forward,
  // brightness, volume, etc. However, windows for v2 apps can optionally
  // request raw function keys for these keys.
  bool ForceTopRowAsFunctionKeys(int device_id) const;

  // Returns true if |device_id| is Hotrod remote.
  bool IsHotrodRemote(int device_id) const;

  // Given modifier flags |original_flags|, returns the remapped modifiers
  // according to user preferences and/or event properties.
  int GetRemappedModifierMasks(int device_id, int original_flags) const;

  // Returns true if this event should be remapped to a right-click.
  // |matched_mask| will be set to the variant (Alt+Click or Search+Click)
  // that was used to match based on flag/feature settings. |matched_mask|
  // only has a valid value when returning true. However, Alt+Click will not
  // be remapped if |is_alt_left_click_remapping_enabled_| is false.
  // |matched_alt_deprecation| is set to true if the alt variant has been
  // deprecated but otherwise would have been remapped. This is used to
  // show a deprecation notification.
  //
  // TODO(zentaro): This function can be removed once the deprecation for
  // Alt-rewrites is complete.
  bool ShouldRemapToRightClick(const MouseEvent& mouse_event,
                               int flags,
                               int* matched_mask,
                               bool* matched_alt_deprecation) const;

  // Rewrite a particular kind of event.
  EventRewriteStatus RewriteKeyEvent(const KeyEvent& key_event,
                                     std::unique_ptr<Event>* rewritten_event);
  EventDispatchDetails RewriteMouseButtonEvent(const MouseEvent& mouse_event,
                                               const Continuation continuation);
  EventDispatchDetails RewriteMouseWheelEvent(
      const MouseWheelEvent& mouse_event,
      const Continuation continuation);
  EventDispatchDetails RewriteTouchEvent(const TouchEvent& touch_event,
                                         const Continuation continuation);
  EventDispatchDetails RewriteScrollEvent(const ScrollEvent& scroll_event,
                                          const Continuation continuation);

  // Rewriter phases. These can inspect the original |event|, but operate using
  // the current |state|, which may have been modified by previous phases.
  bool RewriteModifierKeys(const KeyEvent& event,
                           int device_id,
                           MutableKeyState* state);
  void RewriteNumPadKeys(const KeyEvent& event, MutableKeyState* state);
  void RewriteFunctionKeys(const KeyEvent& event,
                           int device_id,
                           MutableKeyState* state);
  void RewriteExtendedKeys(const KeyEvent& event, MutableKeyState* state);
  int RewriteLocatedEvent(const Event& event);
  int RewriteModifierClick(const MouseEvent& event, int* flags);

  // Handle Function <-> Action key remapping for new CrOS keyboards that
  // support supplying a custom layout via sysfs.
  bool RewriteTopRowKeysForCustomLayout(const ui::KeyEvent& key_event,
                                        int device_id,
                                        bool flip_remapping,
                                        EventFlags flip_remapping_flag,
                                        MutableKeyState* state);

  // Handle Fn/Action key remapping for Wilco keyboard layout.
  bool RewriteTopRowKeysForLayoutWilco(
      const KeyEvent& key_event,
      int device_id,
      bool flip_remapping,
      EventFlags flip_remapping_flag,
      MutableKeyState* state,
      KeyboardCapability::KeyboardTopRowLayout layout);

  bool RewriteTopRowKeysForStandardLayouts(
      const KeyEvent& key_event,
      int device_id,
      bool flip_remapping,
      EventFlags flip_remapping_flag,
      bool rewrite_modifier_is_pressed,
      MutableKeyState* state,
      KeyboardCapability::KeyboardTopRowLayout layout);

  // Take the keys being pressed into consideration, in contrast to
  // RewriteKeyEvent which computes the rewritten event and event rewrite
  // status in stateless way.
  EventDispatchDetails RewriteKeyEventInContext(
      const KeyEvent& event,
      std::unique_ptr<Event> rewritten_event,
      EventRewriteStatus status,
      const Continuation continuation);

  EventDispatchDetails SendStickyKeysReleaseEvents(
      std::unique_ptr<Event> rewritten_event,
      const Continuation continuation);

  // A set of device IDs whose press event has been rewritten.
  // This is to ensure that press and release events are rewritten consistently.
  //
  // As the variable name suggests, we only care about
  // left-button-remapped-to-right events here.
  //
  // With the help of this variable, we are able to eliminate two types of edge
  // cases. (1) is that when we have more than one input sources, such as a
  // touchpad and a mouse. (2) is that when we deal with modifier(Alt or Search)
  // induced left to right button remapping.
  //
  // This variable works closely with
  // EventRewriterAsh::ShouldRemapToRightClick(). As of this writing we
  // don't have a product feature that would rewrite a mouse non-left button
  // event to a mouse left button event. This is why we only have
  // `pressed_as_right_button_device_ids_` here without a "left" counterpart.
  std::set<int> pressed_as_right_button_device_ids_;

  // The |source_device_id()| of the most recent keyboard event,
  // used to interpret modifiers on pointer events.
  int last_keyboard_device_id_;

  const raw_ptr<Delegate, DanglingUntriaged> delegate_;

  base::flat_map<internal::PhysicalKey, MutableKeyState> pressed_physical_keys_;

  // For each pair, the first element is the rewritten key state and the second
  // one is the original key state. If no key event rewriting happens, the first
  // element and the second element are identical.
  std::list<std::pair<MutableKeyState, MutableKeyState>> pressed_key_states_;

  // The sticky keys controller is not owned here;
  // at time of writing it is a singleton in ash::Shell.
  const raw_ptr<EventRewriter> sticky_keys_controller_;

  // Some drallion devices have digital privacy screens and a corresponding
  // privacy screen toggle key in the top row.
  bool privacy_screen_supported_;

  // Some keyboard layouts have 'latching' keys, which either apply
  // a modifier while held down (like normal modifiers), or, if no
  // non-modifier is pressed while the latching key is down, apply the
  // modifier to the next non-modifier keypress. Under Ozone the stateless
  // layout model requires this to be handled explicitly. See crbug.com/518237
  // Pragmatically this, like the Diamond key, is handled here in
  // EventRewriterAsh, but modifier state management is scattered between
  // here, sticky keys, and the system layer (Ozone), and could do with
  // refactoring.
  // - |pressed_modifier_latches_| records the latching keys currently pressed.
  //   It also records the active modifier flags for non-modifier keys that are
  //   remapped to modifiers, e.g. Diamond/F15.
  // - |latched_modifier_latches_| records the latching keys just released,
  //   to be applied to the next non-modifier key.
  // - |used_modifier_latches_| records the latching keys applied to a non-
  //   modifier while pressed, so that they do not get applied after release.
  int pressed_modifier_latches_;
  int latched_modifier_latches_;
  int used_modifier_latches_;

  // If a non-modifier key has been remapped to a modifier key,
  // e.g. ESCAPE -> ALT, this stores the DomCode on the KeyPress event
  // along with its associated previous modifier remap.
  // Handles the case in which the original key's remap is no longer mapped to a
  // modifier but there needs to be a way to reset the stickied modifier
  // latches. See b/216049965 for more details.
  base::flat_map<DomCode, ui::EventFlags> previous_non_modifier_latches_;

  const raw_ptr<KeyboardCapability, DanglingUntriaged> keyboard_capability_;
  const raw_ptr<ash::input_method::ImeKeyboard> ime_keyboard_;

  // True if alt + key and mouse event remapping is allowed. In some scenario,
  // such as clicking a button in the Alt-Tab UI, this remapping undesirably
  // prevents button clicking when alt + left turns into right click. Also,
  // user needs to be able to use an up arrow key to navigate and focus
  // different component, but remapping can turn alt + up arrow into PageUp.
  bool is_alt_down_remapping_enabled_ = true;
};

}  // namespace ui

#endif  // UI_EVENTS_ASH_EVENT_REWRITER_ASH_H_