1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123

ash / accelerators / accelerator_launcher_state_machine.cc [blame]

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

#include "ash/accelerators/accelerator_launcher_state_machine.h"

#include "base/containers/fixed_flat_set.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/types/event_type.h"
#include "ui/ozone/public/input_controller.h"
#include "ui/ozone/public/ozone_platform.h"

namespace ash {

constexpr auto kMetaKeys =
    base::MakeFixedFlatSet<ui::KeyboardCode>({ui::VKEY_LWIN, ui::VKEY_RWIN});

constexpr auto kShiftKeys = base::MakeFixedFlatSet<ui::KeyboardCode>(
    {ui::VKEY_SHIFT, ui::VKEY_LSHIFT, ui::VKEY_RSHIFT});

AcceleratorLauncherStateMachine::AcceleratorLauncherStateMachine(
    ui::InputController* input_controller)
    : input_controller_(input_controller) {}

AcceleratorLauncherStateMachine::~AcceleratorLauncherStateMachine() = default;

void AcceleratorLauncherStateMachine::OnKeyEvent(ui::KeyEvent* event) {
  if (event->type() != ui::EventType::kKeyReleased &&
      event->type() != ui::EventType::kKeyPressed) {
    return;
  }

  switch (current_state_) {
    // When in kStart, if anything but Meta or Shift is pressed, we move to
    // kSuppress.
    // If Shift is pressed, its a no-op.
    // If Meta is pressed, we move to kPrimed.
    //
    // kTrigger and kStart share the same logic as they are the same state
    // except kTrigger will allow the launcher to be opened while kStart will
    // not.
    case LauncherState::kTrigger:
    case LauncherState::kStart:
      if (event->type() != ui::EventType::kKeyPressed) {
        current_state_ = LauncherState::kStart;
        break;
      }

      if (kShiftKeys.contains(event->key_code())) {
        current_state_ = LauncherState::kStart;
        break;
      }

      if (!kMetaKeys.contains(event->key_code())) {
        current_state_ = LauncherState::kSuppress;
        break;
      }

      current_state_ = LauncherState::kPrimed;
      break;

    // In kPrimed, if anything besides Meta is pressed or released, we move to
    // kSuppress.
    // If Meta is released, we move to kTrigger.
    case LauncherState::kPrimed:
      if (!kMetaKeys.contains(event->key_code())) {
        current_state_ = LauncherState::kSuppress;
        break;
      }

      if (event->type() == ui::EventType::kKeyPressed) {
        break;
      }

      current_state_ = LauncherState::kTrigger;
      break;

    // While in kSuppress, if there is ever a point where no keys are being
    // pressed, we move to kStart.
    case LauncherState::kSuppress:
      if (!input_controller_->AreAnyKeysPressed()) {
        current_state_ = LauncherState::kStart;
      }
      break;
  }
}

void AcceleratorLauncherStateMachine::OnMouseEvent(ui::MouseEvent* event) {
  if (event->type() != ui::EventType::kMousePressed &&
      event->type() != ui::EventType::kMouseReleased) {
    return;
  }

  switch (current_state_) {
    // If the Mouse is pressed during any non-kSuppress state, move to
    // kSuppress.
    case LauncherState::kStart:
    case LauncherState::kTrigger:
    case LauncherState::kPrimed:
      if (event->type() == ui::EventType::kMousePressed) {
        current_state_ = LauncherState::kSuppress;
      }
      break;

    // If the mouse is released during kSuppress and there are no keys pressed,
    // move back to kStart.
    case LauncherState::kSuppress:
      if (event->type() == ui::EventType::kMouseReleased &&
          !input_controller_->AreAnyKeysPressed()) {
        current_state_ = LauncherState::kStart;
      }
      break;
  }
}

void AcceleratorLauncherStateMachine::SetCanHandleLauncherForTesting(
    bool can_handle) {
  current_state_ = can_handle ? LauncherState::kTrigger : LauncherState::kStart;
}

}  // namespace ash