chromium/components/exo/keyboard.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "components/exo/keyboard.h"

#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/accelerators/accelerator_table.h"
#include "ash/constants/ash_features.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/keyboard/ui/keyboard_util.h"
#include "ash/public/cpp/accelerators.h"
#include "ash/public/cpp/keyboard/keyboard_controller.h"
#include "ash/shell.h"
#include "ash/wm/window_state.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/containers/flat_tree.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "base/ranges/algorithm.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/exo/input_trace.h"
#include "components/exo/keyboard_delegate.h"
#include "components/exo/keyboard_device_configuration_delegate.h"
#include "components/exo/keyboard_modifiers.h"
#include "components/exo/seat.h"
#include "components/exo/shell_surface.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
#include "components/exo/xkb_tracker.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/window.h"
#include "ui/base/ime/constants.h"
#include "ui/base/ime/events.h"
#include "ui/base/ime/input_method.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_util.h"

namespace exo {
namespace {

// This value must be bigger than the priority for DataDevice.
constexpr int kKeyboardSeatObserverPriority = 1;
static_assert(Seat::IsValidObserverPriority(kKeyboardSeatObserverPriority),
              "kKeyboardSeatObserverPriority is not in the valid range.");

// Delay until a key state change expected to be acknowledged is expired.
constexpr int kExpirationDelayForPendingKeyAcksMs = 1000;

// The accelerator keys reserved to be processed by chrome.
constexpr struct {
  ui::KeyboardCode keycode;
  int modifiers;
} kReservedAccelerators[] = {
    {ui::VKEY_F13, ui::EF_NONE},
    {ui::VKEY_I, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN},
    {ui::VKEY_Z, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN}};

bool ProcessAccelerator(Surface* surface, const ui::KeyEvent* event) {
  views::Widget* widget =
      views::Widget::GetTopLevelWidgetForNativeView(surface->window());
  if (widget) {
    views::FocusManager* focus_manager = widget->GetFocusManager();
    return focus_manager->ProcessAccelerator(ui::Accelerator(*event));
  }
  return false;
}

bool IsVirtualKeyboardEnabled() {
  return keyboard::GetAccessibilityKeyboardEnabled() ||
         keyboard::GetTouchKeyboardEnabled() ||
         (keyboard::KeyboardUIController::HasInstance() &&
          keyboard::KeyboardUIController::Get()->IsEnableFlagSet(
              keyboard::KeyboardEnableFlag::kCommandLineEnabled));
}

bool IsReservedAccelerator(const ui::KeyEvent* event) {
  for (const auto& accelerator : kReservedAccelerators) {
    if (event->flags() == accelerator.modifiers &&
        event->key_code() == accelerator.keycode) {
      return true;
    }
  }
  return false;
}

// Returns false if an accelerator is not reserved or it's not enabled.
bool ProcessAcceleratorIfReserved(Surface* surface, ui::KeyEvent* event) {
  return IsReservedAccelerator(event) && ProcessAccelerator(surface, event);
}

// Returns true if the surface needs to support IME.
// TODO(yhanada, https://crbug.com/847500): Remove this when we find a way
// to fix https://crbug.com/847500 without breaking ARC apps/Lacros browser.
bool IsImeSupportedSurface(Surface* surface) {
  aura::Window* window = surface->window();
  while (window) {
    const auto app_type = window->GetProperty(chromeos::kAppTypeKey);
    switch (app_type) {
      case chromeos::AppType::ARC_APP:
      case chromeos::AppType::CROSTINI_APP:
      case chromeos::AppType::LACROS:
        return true;
      default:
        // Do nothing.
        break;
    }
    // For notifications, billing surfaces, etc. AppType::ARC_APP is not set
    // despite them being from ARC. Ideally AppType should be added to them, but
    // there is a risk that breaks other features e.g. full restore.
    // TODO(tetsui): find a way to remove this.
    if (window->GetProperty(aura::client::kSkipImeProcessing))
      return true;

    if (aura::Window* transient_parent = wm::GetTransientParent(window)) {
      window = transient_parent;
    } else {
      window = window->parent();
    }
  }
  return false;
}

// Returns true if the surface can consume ash accelerators.
bool CanConsumeAshAccelerators(Surface* surface) {
  aura::Window* window = surface->window();
  for (; window; window = window->parent()) {
    const auto app_type = window->GetProperty(chromeos::kAppTypeKey);
    // TOOD(hidehiko): get rid of this if check, after introducing capability,
    // followed by ARC/Crostini migration.
    if (app_type == chromeos::AppType::LACROS) {
      return surface->is_keyboard_shortcuts_inhibited();
    }
  }
  return true;
}

// Returns true if an accelerator is an ash accelerator which can be handled
// before sending it to client and it is actually processed by ash-chrome.
bool ProcessAshAcceleratorIfPossible(Surface* surface, ui::KeyEvent* event) {
  // Process ash accelerators before sending it to client only when the client
  // should not consume ash accelerators. (e.g. Lacros-chrome)
  if (CanConsumeAshAccelerators(surface))
    return false;

  // If accelerators can be processed by browser, send it to the app.
  static const base::NoDestructor<std::vector<ui::Accelerator>>
      kAppHandlingAccelerators([] {
        std::vector<ui::Accelerator> result;
        for (size_t i = 0; i < ash::kAcceleratorDataLength; ++i) {
          const auto& ash_entry = ash::kAcceleratorData[i];
          if (base::Contains(base::span<const ash::AcceleratorAction>(
                                 ash::kActionsInterceptableByBrowser,
                                 ash::kActionsInterceptableByBrowserLength),
                             ash_entry.action) ||
              base::Contains(base::span<const ash::AcceleratorAction>(
                                 ash::kActionsDuplicatedWithBrowser,
                                 ash::kActionsDuplicatedWithBrowserLength),
                             ash_entry.action)) {
            result.emplace_back(ash_entry.keycode, ash_entry.modifiers);
          }
        }
        return result;
      }());
  ui::Accelerator accelerator(*event);
  if (base::Contains(*kAppHandlingAccelerators, accelerator))
    return false;

  return ash::AcceleratorController::Get()->Process(accelerator);
}

bool IsAutoRepeatEnabled(const ui::KeyEvent& event) {
  const auto* properties = event.properties();
  if (!properties) {
    return true;
  }
  return !ui::HasKeyEventSuppressAutoRepeat(*properties);
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// Keyboard, public:

Keyboard::Keyboard(std::unique_ptr<KeyboardDelegate> delegate, Seat* seat)
    : delegate_(std::move(delegate)),
      seat_(seat),
      expiration_delay_for_pending_key_acks_(
          base::Milliseconds(kExpirationDelayForPendingKeyAcksMs)) {
  seat_->AddObserver(this, kKeyboardSeatObserverPriority);
  auto* keyboard_controller = ash::KeyboardController::Get();
  keyboard_controller->AddObserver(this);
  ash::ImeControllerImpl* ime_controller = ash::Shell::Get()->ime_controller();
  ime_controller->AddObserver(this);

  delegate_->OnKeyboardLayoutUpdated(seat_->xkb_tracker()->GetKeymap().get());
  OnSurfaceFocused(seat_->GetFocusedSurface(), nullptr,
                   !!seat_->GetFocusedSurface());

  // Send the initial key repeat settings, iff it is already initialized.
  // If not, that means Profile is not yet initialized, thus skipping,
  // because when it is initialized, OnKeyRepeatSettingsChanged is called
  // by KeyboardController.
  auto key_repeat_settings = keyboard_controller->GetKeyRepeatSettings();
  if (key_repeat_settings.has_value())
    OnKeyRepeatSettingsChanged(key_repeat_settings.value());
}

Keyboard::~Keyboard() {
  RemoveEventHandler();
  for (KeyboardObserver& observer : observer_list_)
    observer.OnKeyboardDestroying(this);
  if (focus_)
    focus_->RemoveSurfaceObserver(this);

  ash::Shell::Get()->ime_controller()->RemoveObserver(this);
  ash::KeyboardController::Get()->RemoveObserver(this);
  seat_->RemoveObserver(this);
}

bool Keyboard::HasDeviceConfigurationDelegate() const {
  return !!device_configuration_delegate_;
}

void Keyboard::SetDeviceConfigurationDelegate(
    KeyboardDeviceConfigurationDelegate* delegate) {
  device_configuration_delegate_ = delegate;
  UpdateKeyboardType();
}

void Keyboard::AddObserver(KeyboardObserver* observer) {
  observer_list_.AddObserver(observer);
}

bool Keyboard::HasObserver(KeyboardObserver* observer) const {
  return observer_list_.HasObserver(observer);
}

void Keyboard::RemoveObserver(KeyboardObserver* observer) {
  observer_list_.RemoveObserver(observer);
}

void Keyboard::SetNeedKeyboardKeyAcks(bool need_acks) {
  RemoveEventHandler();
  are_keyboard_key_acks_needed_ = need_acks;
  AddEventHandler();
}

bool Keyboard::AreKeyboardKeyAcksNeeded() const {
  // Keyboard class doesn't need key acks while the spoken feedback is enabled.
  // While the spoken feedback is enabled, a key event is sent to both of a
  // wayland client and Chrome to give a chance to work to Chrome OS's
  // shortcuts.
  return are_keyboard_key_acks_needed_;
}

void Keyboard::AckKeyboardKey(uint32_t serial, bool handled) {
  auto it = pending_key_acks_.find(serial);
  if (it == pending_key_acks_.end())
    return;

  auto* key_event = &it->second.first;
  if (!handled && !key_event->handled() && focus_)
    ProcessAccelerator(focus_, key_event);
  pending_key_acks_.erase(serial);
}

////////////////////////////////////////////////////////////////////////////////
// ui::EventHandler overrides:

void Keyboard::OnKeyEvent(ui::KeyEvent* event) {
  if (!focus_ || seat_->was_shutdown())
    return;

  DCHECK(GetShellRootSurface(static_cast<aura::Window*>(event->target())) ||
         Surface::AsSurface(static_cast<aura::Window*>(event->target())));

  // Ignore synthetic key repeat events.
  if (event->is_repeat()) {
    // Clients should not see key repeat events and instead handle them on the
    // client side.
    // Mark the key repeat events as handled to avoid them from invoking
    // accelerators.
    event->SetHandled();
    return;
  }

  TRACE_EXO_INPUT_EVENT(event);

  // Process reserved accelerators or ash accelerators which need to be handled
  // before sending it to client.
  if (ProcessAcceleratorIfReserved(focus_, event) ||
      ProcessAshAcceleratorIfPossible(focus_, event)) {
    // Discard a key press event if the corresponding accelerator is handled.
    event->SetHandled();
    // The current focus might have been reset while processing accelerators.
    if (!focus_)
      return;
  }

  // When IME ate a key event, we use the event only for tracking key states and
  // ignore for further processing. Otherwise it is handled in two places (IME
  // and client) and causes undesired behavior.
  // If the window should receive a key event before IME, Exo should send any
  // key events to a client. The client will send back the events to IME if
  // needed.
  const bool consumed_by_ime =
      !focus_->window()->GetProperty(aura::client::kSkipImeProcessing) &&
      ConsumedByIme(*event);

  // Currently, physical keycode is tracked in Seat, assuming that the
  // Keyboard::OnKeyEvent is called between Seat::WillProcessEvent and
  // Seat::DidProcessEvent. However, if IME is enabled, it is no longer true,
  // because IME work in async approach, and on its dispatching, call stack
  // is split so actually Keyboard::OnKeyEvent is called after
  // Seat::DidProcessEvent.
  // TODO(yhanada): This is a quick fix for https://crbug.com/859071. Remove
  // ARC-/Lacros-specific code path once we can find a way to manage
  // press/release events pair for synthetic events.
  PhysicalCode physical_code =
      seat_->physical_code_for_currently_processing_event();
  const auto* physical_dom_code = std::get_if<ui::DomCode>(&physical_code);
  if (physical_dom_code && *physical_dom_code == ui::DomCode::NONE &&
      focused_on_ime_supported_surface_) {
    // This key event is a synthetic event.
    // Consider DomCode field of the event as a physical code
    // for synthetic events when focus surface belongs to an ARC application.
    physical_code = event->code();
  }

  switch (event->type()) {
    case ui::EventType::kKeyPressed: {
      auto it = pressed_keys_.find(physical_code);
      const bool should_handle =
          (it == pressed_keys_.end()) ||
          (event->flags() & ui::EF_IS_CUSTOMIZED_FROM_BUTTON);
      const bool is_physical_code_none =
          physical_dom_code && *physical_dom_code == ui::DomCode::NONE;
      if (should_handle && !event->handled() && !is_physical_code_none) {
        if (bool auto_repeat_enabled = IsAutoRepeatEnabled(*event);
            auto_repeat_enabled != auto_repeat_enabled_) {
          auto_repeat_enabled_ = auto_repeat_enabled;
          if (auto settings =
                  ash::KeyboardController::Get()->GetKeyRepeatSettings();
              settings.has_value()) {
            OnKeyRepeatSettingsChanged(*settings);
          }
        }

        for (auto& observer : observer_list_) {
          observer.OnKeyboardKey(event->time_stamp(), event->code(), true);
        }

        if (!consumed_by_ime) {
          // Process key press event if not already handled and not already
          // pressed.
          uint32_t serial = delegate_->OnKeyboardKey(event->time_stamp(),
                                                     event->code(), true);
          if (AreKeyboardKeyAcksNeeded()) {
            pending_key_acks_.insert(
                {serial,
                 {*event, base::TimeTicks::Now() +
                              expiration_delay_for_pending_key_acks_}});
            event->SetHandled();
          }
        }
        // Keep track of both the physical code and potentially re-written
        // code that this event generated.
        pressed_keys_[physical_code].emplace(event->code(), consumed_by_ime);
      } else if (!should_handle && !event->handled()) {
        // Non-repeate key events for already pressed key can be sent in some
        // cases (e.g. Holding 'A' key then holding 'B' key then releasing 'A'
        // key sends a non-repeat 'B' key press event).
        // When it happens, we don't want to send the press event to a client
        // and also want to avoid it from invoking any accelerator.
        if (AreKeyboardKeyAcksNeeded())
          event->SetHandled();
      }
    } break;
    case ui::EventType::kKeyReleased: {
      // Process key release event if currently pressed.
      auto key_state_set_iter = pressed_keys_.find(physical_code);
      if (key_state_set_iter == pressed_keys_.end()) {
        break;
      }

      auto& key_state_set = key_state_set_iter->second;
      auto key_state_iter = base::ranges::find(
          key_state_set, event->code(),
          [](const KeyState& key_state) { return key_state.code; });

      // If we can't find the specific key event to release, all previously
      // pressed events tied to this physical key should be released.
      auto [begin, end] =
          key_state_iter == key_state_set.end()
              ? std::pair(key_state_set.begin(), key_state_set.end())
              : std::pair(key_state_iter, key_state_iter + 1);
      for (auto iter = begin; iter != end; ++iter) {
        for (auto& observer : observer_list_) {
          observer.OnKeyboardKey(event->time_stamp(), iter->code, false);
        }

        if (!iter->consumed_by_ime) {
          // We use the code that was generated when the physical key was
          // pressed rather than the current event code. This allows events
          // to be re-written before dispatch, while still allowing the
          // client to track the state of the physical keyboard.
          uint32_t serial =
              delegate_->OnKeyboardKey(event->time_stamp(), iter->code, false);
          if (AreKeyboardKeyAcksNeeded()) {
            auto ack_it =
                pending_key_acks_
                    .insert(
                        {serial,
                         {*event, base::TimeTicks::Now() +
                                      expiration_delay_for_pending_key_acks_}})
                    .first;
            // Handled is not copied with Event's copy ctor, so explicitly copy
            // here.
            if (event->handled()) {
              ack_it->second.first.SetHandled();
            }
            event->SetHandled();
          }
        }
      }
      key_state_set.erase(begin, end);
      if (key_state_set.empty()) {
        pressed_keys_.erase(key_state_set_iter);
      }
    } break;
    default:
      NOTREACHED_IN_MIGRATION();
      break;
  }

  if (pending_key_acks_.empty())
    return;
  if (process_expired_pending_key_acks_pending_)
    return;

  ScheduleProcessExpiredPendingKeyAcks(expiration_delay_for_pending_key_acks_);
}

////////////////////////////////////////////////////////////////////////////////
// SurfaceObserver overrides:

void Keyboard::OnSurfaceDestroying(Surface* surface) {
  DCHECK(surface == focus_);
  SetFocus(nullptr);
}

////////////////////////////////////////////////////////////////////////////////
// SeatObserver overrides:

void Keyboard::OnSurfaceFocused(Surface* gained_focus,
                                Surface* lost_focused,
                                bool has_focused_surface) {
  Surface* gained_focus_surface =
      gained_focus && delegate_->CanAcceptKeyboardEventsForSurface(gained_focus)
          ? gained_focus
          : nullptr;
  if (gained_focus_surface != focus_)
    SetFocus(gained_focus_surface);
}

void Keyboard::OnKeyboardModifierUpdated() {
  // XkbTracker must be updated in the Seat, before calling this method.
  if (focus_)
    delegate_->OnKeyboardModifiers(seat_->xkb_tracker()->GetModifiers());
}

////////////////////////////////////////////////////////////////////////////////
// ash::KeyboardControllerObserver overrides:

void Keyboard::OnKeyboardEnableFlagsChanged(
    const std::set<keyboard::KeyboardEnableFlag>& flags) {
  UpdateKeyboardType();
}

void Keyboard::OnKeyRepeatSettingsChanged(
    const ash::KeyRepeatSettings& settings) {
  delegate_->OnKeyRepeatSettingsChanged(
      settings.enabled && auto_repeat_enabled_, settings.delay,
      settings.interval);
}

////////////////////////////////////////////////////////////////////////////////
// ash::ImeController::Observer overrides:

void Keyboard::OnCapsLockChanged(bool enabled) {}

void Keyboard::OnKeyboardLayoutNameChanged(const std::string& layout_name) {
  // XkbTracker must be updated in the Seat, before calling this method.
  // Ensured by the observer registration order.
  delegate_->OnKeyboardLayoutUpdated(seat_->xkb_tracker()->GetKeymap().get());
}

////////////////////////////////////////////////////////////////////////////////
// Keyboard, private:

base::flat_map<PhysicalCode, base::flat_set<KeyState>>
Keyboard::GetPressedKeysForSurface(Surface* surface) {
  // Remove system keys from being sent as pressed keys unless the window
  // can consume them.
  base::flat_map<PhysicalCode, base::flat_set<KeyState>> filtered_keys =
      pressed_keys_;
  aura::Window* top_level = surface->window()->GetToplevelWindow();
  if (top_level && !ash::WindowState::Get(top_level)->CanConsumeSystemKeys()) {
    base::EraseIf(filtered_keys, [](auto& key_state_set_pair) {
      base::EraseIf(key_state_set_pair.second, [](auto& key_state) {
        return ash::AcceleratorController::IsSystemKey(key_state.key_code);
      });
      return key_state_set_pair.second.empty();
    });
  }
  return filtered_keys;
}

void Keyboard::SetFocus(Surface* surface) {
  if (focus_) {
    RemoveEventHandler();
    delegate_->OnKeyboardLeave(focus_);
    focus_->RemoveSurfaceObserver(this);
    focus_ = nullptr;
    pending_key_acks_.clear();
  }
  if (surface) {
    pressed_keys_ = seat_->pressed_keys();
    auto enter_keys = GetPressedKeysForSurface(surface);
    delegate_->OnKeyboardModifiers(seat_->xkb_tracker()->GetModifiers());
    delegate_->OnKeyboardEnter(surface, enter_keys);
    focus_ = surface;
    focus_->AddSurfaceObserver(this);
    focused_on_ime_supported_surface_ = IsImeSupportedSurface(surface);
    AddEventHandler();
  }
}

void Keyboard::ProcessExpiredPendingKeyAcks() {
  DCHECK(process_expired_pending_key_acks_pending_);
  process_expired_pending_key_acks_pending_ = false;

  // Check pending acks and process them as if it is handled if
  // expiration time passed.
  base::TimeTicks current_time = base::TimeTicks::Now();

  while (!pending_key_acks_.empty()) {
    auto it = pending_key_acks_.begin();
    const ui::KeyEvent event = it->second.first;

    if (it->second.second > current_time)
      break;

    // Expiration time has passed, assume the event was handled.
    pending_key_acks_.erase(it);
  }

  if (pending_key_acks_.empty())
    return;

  base::TimeDelta delay_until_next_process_expired_pending_key_acks =
      pending_key_acks_.begin()->second.second - current_time;
  ScheduleProcessExpiredPendingKeyAcks(
      delay_until_next_process_expired_pending_key_acks);
}

void Keyboard::ScheduleProcessExpiredPendingKeyAcks(base::TimeDelta delay) {
  DCHECK(!process_expired_pending_key_acks_pending_);
  process_expired_pending_key_acks_pending_ = true;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&Keyboard::ProcessExpiredPendingKeyAcks,
                     weak_ptr_factory_.GetWeakPtr()),
      delay);
}

void Keyboard::AddEventHandler() {
  if (!focus_)
    return;

  // Toplevel window can be not ShellSurface, for example for a notification
  // surface.
  aura::Window* toplevel_window = focus_->window();
  if (toplevel_window->GetToplevelWindow())
    toplevel_window = toplevel_window->GetToplevelWindow();

  if (are_keyboard_key_acks_needed_)
    toplevel_window->AddPreTargetHandler(this);
  else
    toplevel_window->AddPostTargetHandler(this);
}

void Keyboard::RemoveEventHandler() {
  if (!focus_)
    return;

  // Toplevel window can be not ShellSurface, for example for a notification
  // surface.
  aura::Window* toplevel_window = focus_->window();
  if (toplevel_window->GetToplevelWindow())
    toplevel_window = toplevel_window->GetToplevelWindow();

  if (are_keyboard_key_acks_needed_)
    toplevel_window->RemovePreTargetHandler(this);
  else
    toplevel_window->RemovePostTargetHandler(this);
}

void Keyboard::UpdateKeyboardType() {
  if (!device_configuration_delegate_)
    return;

  // Ignore kAndroidDisabled which affects |enabled| and just test for a11y
  // and touch enabled keyboards. TODO(yhanada): Fix this using an Android
  // specific KeyboardUI implementation. https://crbug.com/897655.
  const bool is_physical = !IsVirtualKeyboardEnabled();
  device_configuration_delegate_->OnKeyboardTypeChanged(is_physical);
}

}  // namespace exo