| // 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. |
| |
| #include "components/exo/wayland/zcr_ui_controls.h" |
| |
| #include <stdint.h> |
| #include <ui-controls-unstable-v1-server-protocol.h> |
| #include <wayland-server-core.h> |
| |
| #include <limits> |
| #include <variant> |
| |
| #include "ash/display/screen_orientation_controller_test_api.h" |
| #include "ash/shell.h" |
| #include "base/bit_cast.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/notreached.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "components/exo/display.h" |
| #include "components/exo/wayland/server.h" |
| #include "components/exo/wayland/server_util.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/env_input_state_controller.h" |
| #include "ui/base/test/ui_controls.h" |
| #include "ui/base/wayland/wayland_display_util.h" |
| #include "ui/display/manager/managed_display_info.h" |
| #include "ui/display/test/display_manager_test_api.h" |
| #include "ui/display/types/display_constants.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/gesture_detection/gesture_configuration.h" |
| #include "ui/events/keycodes/dom/dom_code.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| #include "ui/events/keycodes/keyboard_code_conversion.h" |
| |
| namespace exo::wayland { |
| |
| struct UiControls::UiControlsState { |
| explicit UiControlsState(Server* server, const Seat* seat) |
| : server_(server), seat_(seat) {} |
| UiControlsState(const UiControlsState&) = delete; |
| UiControlsState& operator=(const UiControlsState&) = delete; |
| |
| raw_ptr<Server> server_; |
| const raw_ptr<const Seat> seat_; |
| |
| // Keeps track of the IDs of pending requests for that we still need to emit |
| // request_processed events. This is per wl_resource so that we can drop |
| // pending requests for a resource when the resource is destroyed. |
| std::map<wl_resource*, std::set<uint32_t>> pending_request_ids_; |
| |
| // Keeps track of the original display spec to be restored on destroy. |
| std::vector<display::ManagedDisplayInfo> original_displays_; |
| // Pending display info to be added with display_info_done. |
| std::optional<display::ManagedDisplayInfo> pending_display_; |
| // Pending display info lists to be committed with display_info_list_done. |
| std::vector<display::ManagedDisplayInfo> pending_display_info_list_; |
| }; |
| |
| namespace { |
| |
| using UiControlsState = UiControls::UiControlsState; |
| |
| constexpr uint32_t kUiControlsVersion = |
| ZCR_UI_CONTROLS_V1_DISPLAY_INFO_LIST_DONE_SINCE_VERSION; |
| |
| base::OnceClosure UpdateStateAndBindEmitProcessed(struct wl_resource* resource, |
| uint32_t id) { |
| auto* state = GetUserDataAs<UiControlsState>(resource); |
| auto pending_request_ids = |
| state->pending_request_ids_.try_emplace(resource).first->second; |
| pending_request_ids.insert(id); |
| |
| return base::BindLambdaForTesting([=]() { |
| // Ensure |resource| hasn't been destroyed in the meantime. |
| if (base::Contains(state->pending_request_ids_, resource)) { |
| zcr_ui_controls_v1_send_request_processed(resource, id); |
| state->pending_request_ids_[resource].erase(id); |
| |
| // It can sometimes happen that the request_processed event gets stuck in |
| // libwayland's queue without ever being sent, because the client is |
| // waiting for the event and there is nothing else generating events. To |
| // ensure the client actually receives the event, we need to flush |
| // manually. |
| state->server_->Flush(); |
| } |
| }); |
| } |
| |
| // Ensures that a crashed test doesn't leave behind pressed keys, mouse buttons, |
| // or touch points. |
| void ResetInputs(UiControlsState* state) { |
| auto* window = ash::Shell::GetPrimaryRootWindow(); |
| auto pressed_keys = state->seat_->pressed_keys(); |
| for (auto key : pressed_keys) { |
| const ui::DomCode* physical_key = std::get_if<ui::DomCode>(&key.first); |
| if (!physical_key) { |
| continue; |
| } |
| auto key_code = ui::DomCodeToUsLayoutNonLocatedKeyboardCode(*physical_key); |
| ui_controls::SendKeyEvents(window, key_code, ui_controls::kKeyRelease); |
| } |
| |
| auto* env = aura::Env::GetInstance(); |
| int button_flags = env->mouse_button_flags(); |
| if (button_flags & ui::EF_LEFT_MOUSE_BUTTON) { |
| ui_controls::SendMouseEvents(ui_controls::LEFT, |
| ui_controls::MouseButtonState::UP); |
| } |
| if (button_flags & ui::EF_MIDDLE_MOUSE_BUTTON) { |
| ui_controls::SendMouseEvents(ui_controls::MIDDLE, |
| ui_controls::MouseButtonState::UP); |
| } |
| if (button_flags & ui::EF_RIGHT_MOUSE_BUTTON) { |
| ui_controls::SendMouseEvents(ui_controls::RIGHT, |
| ui_controls::MouseButtonState::UP); |
| } |
| |
| uint32_t touch_ids_down = env->env_controller()->touch_ids_down(); |
| for (uint32_t touch_id = 0; touch_id < 32; ++touch_id) { |
| if (touch_ids_down & (1 << touch_id)) { |
| ui_controls::SendTouchEvents(ui_controls::TouchType::kTouchRelease, |
| touch_id, 0, 0); |
| } |
| } |
| |
| // TODO(crbug.com/40263572): Fix this issue and the code below should not be |
| // necessary. |
| ui_controls::SendMouseMove(0, 0); |
| } |
| |
| // Ensure that the display is returned to the default setting. |
| void ResetDisplay(UiControlsState* state) { |
| display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager()) |
| .UpdateDisplayWithDisplayInfoList(state->original_displays_); |
| ash::ScreenOrientationControllerTestApi( |
| ash::Shell::Get()->screen_orientation_controller()) |
| .UpdateNaturalOrientation(); |
| state->pending_display_.reset(); |
| state->pending_display_info_list_.clear(); |
| } |
| |
| void ui_controls_send_key_events(struct wl_client* client, |
| struct wl_resource* resource, |
| uint32_t key, |
| uint32_t key_state, |
| uint32_t pressed_modifiers, |
| uint32_t id) { |
| auto emit_processed = UpdateStateAndBindEmitProcessed(resource, id); |
| auto* window = ash::Shell::GetPrimaryRootWindow(); |
| auto dom_code = ui::KeycodeConverter::EvdevCodeToDomCode(key); |
| auto key_code = ui::DomCodeToUsLayoutNonLocatedKeyboardCode(dom_code); |
| ui_controls::SendKeyEventsNotifyWhenDone(window, key_code, key_state, |
| std::move(emit_processed), |
| pressed_modifiers); |
| } |
| |
| void ui_controls_send_mouse_move(struct wl_client* client, |
| struct wl_resource* resource, |
| int32_t x, |
| int32_t y, |
| struct wl_resource* surface, |
| uint32_t id) { |
| if (surface) { |
| LOG(WARNING) |
| << "The `surface` parameter for ui_controls.send_mouse_move should be " |
| "NULL on LaCrOS, but it isn't. Why aren't we using screen " |
| "coordinates?"; |
| } |
| |
| auto emit_processed = UpdateStateAndBindEmitProcessed(resource, id); |
| ui_controls::SendMouseMoveNotifyWhenDone(x, y, std::move(emit_processed)); |
| } |
| |
| void ui_controls_send_mouse_button(struct wl_client* client, |
| struct wl_resource* resource, |
| uint32_t button, |
| uint32_t button_state, |
| uint32_t pressed_modifiers, |
| uint32_t id) { |
| auto emit_processed = UpdateStateAndBindEmitProcessed(resource, id); |
| ui_controls::SendMouseEventsNotifyWhenDone( |
| static_cast<ui_controls::MouseButton>(button), button_state, |
| std::move(emit_processed), pressed_modifiers); |
| } |
| |
| void ui_controls_send_touch(struct wl_client* client, |
| struct wl_resource* resource, |
| uint32_t action, |
| uint32_t touch_id, |
| int32_t x, |
| int32_t y, |
| struct wl_resource* surface, |
| uint32_t id) { |
| if (surface) { |
| LOG(WARNING) |
| << "The `surface` parameter for ui_controls.send_touch should be NULL " |
| "on LaCrOS, but it isn't. Why aren't we using screen coordinates?"; |
| } |
| |
| auto emit_processed = UpdateStateAndBindEmitProcessed(resource, id); |
| ui_controls::SendTouchEventsNotifyWhenDone(action, touch_id, x, y, |
| std::move(emit_processed)); |
| } |
| |
| void ui_controls_set_display_info_id(struct wl_client* client, |
| struct wl_resource* resource, |
| uint32_t display_id_hi, |
| uint32_t display_id_lo) { |
| auto* state = GetUserDataAs<UiControlsState>(resource); |
| |
| int64_t display_id = |
| ui::wayland::FromWaylandDisplayIdPair({display_id_hi, display_id_lo}); |
| if (!state->pending_display_) { |
| state->pending_display_ = |
| display::ManagedDisplayInfo::CreateFromSpecWithID({}, display_id); |
| } else { |
| state->pending_display_->set_display_id(display_id); |
| } |
| } |
| |
| void ui_controls_set_display_info_size(struct wl_client* client, |
| struct wl_resource* resource, |
| uint32_t width, |
| uint32_t height) { |
| auto* state = GetUserDataAs<UiControlsState>(resource); |
| |
| if (!state->pending_display_) { |
| state->pending_display_ = display::ManagedDisplayInfo::CreateFromSpec( |
| base::StringPrintf("%dx%d", width, height)); |
| } else { |
| state->pending_display_->SetBounds(gfx::Rect(width, height)); |
| } |
| } |
| |
| void ui_controls_set_display_info_device_scale_factor( |
| struct wl_client* client, |
| struct wl_resource* resource, |
| uint32_t scale_factor) { |
| auto* state = GetUserDataAs<UiControlsState>(resource); |
| static_assert(sizeof(uint32_t) == sizeof(float), |
| "Sizes much match for reinterpret cast to be meaningful"); |
| // bit_cast is needed here because wayland doesn't support |
| // float as primitive type and we are using 32 bits as storage. |
| // static_cast won't work because the original value is integer. |
| float device_scale_factor = base::bit_cast<float>(scale_factor); |
| |
| if (!state->pending_display_) { |
| state->pending_display_ = display::ManagedDisplayInfo::CreateFromSpec({}); |
| } |
| state->pending_display_->set_device_scale_factor(device_scale_factor); |
| } |
| |
| void ui_controls_display_info_done(struct wl_client* client, |
| struct wl_resource* resource) { |
| auto* state = GetUserDataAs<UiControlsState>(resource); |
| |
| // Push info to pending display info list. |
| state->pending_display_info_list_.push_back(*state->pending_display_); |
| |
| // Reset the state to default values. |
| state->pending_display_.reset(); |
| } |
| |
| void ui_controls_display_info_list_done(struct wl_client* client, |
| struct wl_resource* resource, |
| uint32_t id) { |
| auto emit_processed = UpdateStateAndBindEmitProcessed(resource, id); |
| auto* state = GetUserDataAs<UiControlsState>(resource); |
| |
| display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager()) |
| .UpdateDisplayWithDisplayInfoList(state->pending_display_info_list_); |
| ash::ScreenOrientationControllerTestApi( |
| ash::Shell::Get()->screen_orientation_controller()) |
| .UpdateNaturalOrientation(); |
| state->pending_display_info_list_.clear(); |
| state->pending_display_.reset(); |
| |
| std::move(emit_processed).Run(); |
| } |
| |
| const struct zcr_ui_controls_v1_interface ui_controls_implementation = { |
| ui_controls_send_key_events, |
| ui_controls_send_mouse_move, |
| ui_controls_send_mouse_button, |
| ui_controls_send_touch, |
| ui_controls_set_display_info_id, |
| ui_controls_set_display_info_size, |
| ui_controls_set_display_info_device_scale_factor, |
| ui_controls_display_info_done, |
| ui_controls_display_info_list_done}; |
| |
| void destroy_ui_controls_resource(struct wl_resource* resource) { |
| auto* state = GetUserDataAs<UiControlsState>(resource); |
| state->pending_request_ids_.erase(resource); |
| ResetInputs(state); |
| ResetDisplay(state); |
| } |
| |
| void bind_ui_controls(wl_client* client, |
| void* data, |
| uint32_t version, |
| uint32_t id) { |
| wl_resource* resource = |
| wl_resource_create(client, &zcr_ui_controls_v1_interface, |
| std::min(version, kUiControlsVersion), id); |
| |
| wl_resource_set_implementation(resource, &ui_controls_implementation, data, |
| destroy_ui_controls_resource); |
| } |
| |
| } // namespace |
| |
| UiControls::UiControls(Server* server) |
| : state_(std::make_unique<UiControlsState>(server, |
| server->GetDisplay()->seat())) { |
| wl_global_create(server->GetWaylandDisplay(), &zcr_ui_controls_v1_interface, |
| kUiControlsVersion, state_.get(), bind_ui_controls); |
| |
| auto* const display_manager = ash::Shell::Get()->display_manager(); |
| auto& display_list = display_manager->active_display_list(); |
| for (const display::Display& display : display_list) { |
| state_->original_displays_.push_back( |
| display_manager->GetDisplayInfo(display.id())); |
| } |
| // TODO(crbug.com/324562919) This hardcodes fling gesture detection to be |
| // disabled when ui_controls is in use, so that it does not interfere with |
| // tests that don't intend to trigger fling gestures. Some future tests will |
| // intentionally trigger fling gestures, so this will need to become |
| // configurable by clients at some point. |
| ui::GestureConfiguration::GetInstance()->set_min_fling_velocity( |
| std::numeric_limits<float>::max()); |
| } |
| |
| UiControls::~UiControls() = default; |
| |
| } // namespace exo::wayland |