| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ui/scenic/lib/input/tests/util.h" |
| |
| #include <lib/fidl/cpp/clone.h> |
| #include <lib/fostr/fidl/fuchsia/ui/input/formatting.h> |
| #include <lib/gtest/test_loop_fixture.h> |
| #include <lib/ui/scenic/cpp/view_ref_pair.h> |
| |
| #include <unordered_set> |
| |
| #include <hid/hid.h> |
| |
| #include "src/lib/fxl/logging.h" |
| #include "src/ui/scenic/lib/scheduling/constant_frame_predictor.h" |
| #include "src/ui/scenic/lib/scheduling/default_frame_scheduler.h" |
| #include "src/ui/scenic/lib/scheduling/id.h" |
| |
| namespace lib_ui_input_tests { |
| |
| // Used to compare whether two values are nearly equal. |
| constexpr float kEpsilon = 0.000001f; |
| |
| using InputCommand = fuchsia::ui::input::Command; |
| using ScenicEvent = fuchsia::ui::scenic::Event; |
| using escher::impl::CommandBufferSequencer; |
| using fuchsia::ui::input::InputEvent; |
| using fuchsia::ui::input::KeyboardEvent; |
| using fuchsia::ui::input::KeyboardEventPhase; |
| using fuchsia::ui::input::PointerEvent; |
| using fuchsia::ui::input::PointerEventPhase; |
| using fuchsia::ui::input::PointerEventType; |
| using fuchsia::ui::input::SendKeyboardInputCmd; |
| using fuchsia::ui::input::SendPointerInputCmd; |
| using fuchsia::ui::scenic::SessionListener; |
| using scenic_impl::GlobalId; |
| using scenic_impl::ResourceId; |
| using scenic_impl::Scenic; |
| using scenic_impl::display::Display; |
| using scenic_impl::gfx::Engine; |
| using scenic_impl::gfx::GfxSystem; |
| using scenic_impl::gfx::test::ReleaseFenceSignallerForTest; |
| using scenic_impl::input::InputSystem; |
| using scenic_impl::test::ScenicTest; |
| using scheduling::ConstantFramePredictor; |
| using scheduling::DefaultFrameScheduler; |
| |
| SessionWrapper::SessionWrapper(Scenic* scenic) { |
| fuchsia::ui::scenic::SessionPtr session_ptr; |
| fidl::InterfaceHandle<SessionListener> listener_handle; |
| fidl::InterfaceRequest<SessionListener> listener_request = listener_handle.NewRequest(); |
| scenic->CreateSession(session_ptr.NewRequest(), std::move(listener_handle)); |
| session_ = std::make_unique<scenic::Session>(std::move(session_ptr), std::move(listener_request)); |
| session_->set_event_handler(fit::bind_member(this, &SessionWrapper::OnEvent)); |
| } |
| |
| SessionWrapper::SessionWrapper(SessionWrapper&& original) { |
| session_ = std::move(original.session_); |
| session_->set_event_handler(fit::bind_member(this, &SessionWrapper::OnEvent)); |
| view_koid_ = original.view_koid_; |
| events_ = std::move(original.events_); |
| } |
| |
| SessionWrapper::~SessionWrapper() { |
| if (session_) { |
| session_->Flush(); // Ensure Scenic receives all release commands. |
| } |
| } |
| |
| void SessionWrapper::OnEvent(std::vector<ScenicEvent> events) { |
| for (ScenicEvent& event : events) { |
| if (event.is_input()) { |
| events_.push_back(std::move(event.input())); |
| } |
| // Ignore other event types for these tests. |
| } |
| } |
| |
| ResourceGraph::ResourceGraph(scenic::Session* session) |
| : scene(session), |
| camera(scene), |
| renderer(session), |
| layer(session), |
| layer_stack(session), |
| compositor(session) { |
| renderer.SetCamera(camera); |
| layer.SetRenderer(renderer); |
| layer_stack.AddLayer(layer); |
| compositor.SetLayerStack(layer_stack); |
| } |
| |
| void InputSystemTest::RequestToPresent(scenic::Session* session) { |
| session->Present(/*presentation time*/ 0, [](auto) {}); |
| RunLoopFor(zx::msec(20)); // Run until the next frame should have been scheduled. |
| } |
| |
| std::pair<SessionWrapper, ResourceGraph> InputSystemTest::CreateScene() { |
| SessionWrapper root_session(scenic()); |
| ResourceGraph root_resources(root_session.session()); |
| root_resources.layer.SetSize(test_display_width_px(), test_display_height_px()); |
| return {std::move(root_session), std::move(root_resources)}; |
| } |
| |
| void InputSystemTest::SetUpTestView(scenic::View* view) { |
| scenic::Session* const session = view->session(); |
| |
| scenic::ShapeNode shape(session); |
| shape.SetTranslation(2, 2, 0); // Center the shape within the View. |
| view->AddChild(shape); |
| |
| scenic::Rectangle rec(session, 5, 5); // Simple; no real GPU work. |
| shape.SetShape(rec); |
| |
| scenic::Material material(session); |
| shape.SetMaterial(material); |
| |
| RequestToPresent(session); |
| } |
| |
| SessionWrapper InputSystemTest::CreateClient(const std::string& name, |
| fuchsia::ui::views::ViewToken view_token) { |
| SessionWrapper session_wrapper(scenic()); |
| auto pair = scenic::ViewRefPair::New(); |
| session_wrapper.SetViewKoid(scenic_impl::gfx::ExtractKoid(pair.view_ref)); |
| scenic::View view(session_wrapper.session(), std::move(view_token), std::move(pair.control_ref), |
| std::move(pair.view_ref), name); |
| SetUpTestView(&view); |
| |
| return session_wrapper; |
| } |
| |
| void InputSystemTest::InitializeScenic(Scenic* scenic) { |
| auto signaller = std::make_unique<ReleaseFenceSignallerForTest>(); |
| display_ = std::make_unique<Display>( |
| /*id*/ 0, test_display_width_px(), test_display_height_px()); |
| auto frame_scheduler = std::make_shared<DefaultFrameScheduler>( |
| display_->vsync_timing(), |
| std::make_unique<ConstantFramePredictor>(/* static_vsync_offset */ zx::msec(5))); |
| |
| engine_ = std::make_unique<Engine>(context_provider_.context(), frame_scheduler, |
| std::move(signaller), escher::EscherWeakPtr()); |
| frame_scheduler->SetFrameRenderer(engine_->GetWeakPtr()); |
| auto gfx = scenic->RegisterSystem<GfxSystem>(engine_.get(), escher::EscherWeakPtr(), |
| /* sysmem */ nullptr, |
| /* display_manager */ nullptr); |
| frame_scheduler->AddSessionUpdater(gfx->GetWeakPtr()); |
| scenic_->SetFrameScheduler(frame_scheduler); |
| |
| input_system_ = scenic->RegisterSystem<InputSystem>(engine_.get()); |
| scenic->SetInitialized(); |
| } |
| |
| void InputSystemTest::TearDown() { |
| // A clean teardown sequence is a little involved but possible. |
| // 0. All resources are released (i.e. test scope closure, ~ResourceGraph). |
| // 1. Sessions |Flush| their last resource-release cmds (i.e. test scope closure, |
| // ~SessionWrapper). |
| // 2. Scenic runs the last resource-release cmds. |
| RunLoopUntilIdle(); |
| // 3. Destroy Scenic before destroying the command buffer sequencer (CBS). |
| // This ensures no CBS listeners are active by the time CBS is destroyed. |
| ScenicTest::TearDown(); |
| engine_.reset(); |
| display_.reset(); |
| } |
| |
| PointerCommandGenerator::PointerCommandGenerator(ResourceId compositor_id, uint32_t device_id, |
| uint32_t pointer_id, PointerEventType type) |
| : compositor_id_(compositor_id) { |
| blank_.device_id = device_id; |
| blank_.pointer_id = pointer_id; |
| blank_.type = type; |
| } |
| |
| InputCommand PointerCommandGenerator::Add(float x, float y) { |
| PointerEvent event; |
| fidl::Clone(blank_, &event); |
| event.phase = PointerEventPhase::ADD; |
| event.x = x; |
| event.y = y; |
| return MakeInputCommand(event); |
| } |
| |
| InputCommand PointerCommandGenerator::Down(float x, float y) { |
| PointerEvent event; |
| fidl::Clone(blank_, &event); |
| event.phase = PointerEventPhase::DOWN; |
| event.x = x; |
| event.y = y; |
| return MakeInputCommand(event); |
| } |
| |
| InputCommand PointerCommandGenerator::Move(float x, float y) { |
| PointerEvent event; |
| fidl::Clone(blank_, &event); |
| event.phase = PointerEventPhase::MOVE; |
| event.x = x; |
| event.y = y; |
| return MakeInputCommand(event); |
| } |
| |
| InputCommand PointerCommandGenerator::Up(float x, float y) { |
| PointerEvent event; |
| fidl::Clone(blank_, &event); |
| event.phase = PointerEventPhase::UP; |
| event.x = x; |
| event.y = y; |
| return MakeInputCommand(event); |
| } |
| |
| InputCommand PointerCommandGenerator::Remove(float x, float y) { |
| PointerEvent event; |
| fidl::Clone(blank_, &event); |
| event.phase = PointerEventPhase::REMOVE; |
| event.x = x; |
| event.y = y; |
| return MakeInputCommand(event); |
| } |
| |
| InputCommand PointerCommandGenerator::MakeInputCommand(PointerEvent event) { |
| SendPointerInputCmd pointer_cmd; |
| pointer_cmd.compositor_id = compositor_id_; |
| pointer_cmd.pointer_event = std::move(event); |
| |
| InputCommand input_cmd; |
| input_cmd.set_send_pointer_input(std::move(pointer_cmd)); |
| |
| return input_cmd; |
| } |
| |
| KeyboardCommandGenerator::KeyboardCommandGenerator(ResourceId compositor_id, uint32_t device_id) |
| : compositor_id_(compositor_id) { |
| blank_.device_id = device_id; |
| } |
| |
| InputCommand KeyboardCommandGenerator::Pressed(uint32_t hid_usage, uint32_t modifiers) { |
| KeyboardEvent event; |
| fidl::Clone(blank_, &event); |
| event.phase = KeyboardEventPhase::PRESSED; |
| event.hid_usage = hid_usage; |
| event.modifiers = modifiers; |
| return MakeInputCommand(event); |
| } |
| |
| InputCommand KeyboardCommandGenerator::Released(uint32_t hid_usage, uint32_t modifiers) { |
| KeyboardEvent event; |
| fidl::Clone(blank_, &event); |
| event.phase = KeyboardEventPhase::RELEASED; |
| event.hid_usage = hid_usage; |
| event.modifiers = modifiers; |
| return MakeInputCommand(event); |
| } |
| |
| InputCommand KeyboardCommandGenerator::Cancelled(uint32_t hid_usage, uint32_t modifiers) { |
| KeyboardEvent event; |
| fidl::Clone(blank_, &event); |
| event.phase = KeyboardEventPhase::CANCELLED; |
| event.hid_usage = hid_usage; |
| event.modifiers = modifiers; |
| return MakeInputCommand(event); |
| } |
| |
| InputCommand KeyboardCommandGenerator::Repeat(uint32_t hid_usage, uint32_t modifiers) { |
| KeyboardEvent event; |
| fidl::Clone(blank_, &event); |
| event.phase = KeyboardEventPhase::REPEAT; |
| event.hid_usage = hid_usage; |
| event.modifiers = modifiers; |
| return MakeInputCommand(event); |
| } |
| |
| InputCommand KeyboardCommandGenerator::MakeInputCommand(KeyboardEvent event) { |
| // Typically code point is inferred this same way by DeviceState. |
| event.code_point = hid_map_key(event.hid_usage, |
| event.modifiers & (fuchsia::ui::input::kModifierShift | |
| fuchsia::ui::input::kModifierCapsLock), |
| qwerty_map); |
| |
| SendKeyboardInputCmd keyboard_cmd; |
| keyboard_cmd.compositor_id = compositor_id_; |
| keyboard_cmd.keyboard_event = std::move(event); |
| |
| InputCommand input_cmd; |
| input_cmd.set_send_keyboard_input(std::move(keyboard_cmd)); |
| |
| return input_cmd; |
| } |
| |
| bool PointerMatches(const PointerEvent& event, uint32_t pointer_id, PointerEventPhase phase, |
| float x, float y) { |
| using fuchsia::ui::input::operator<<; |
| |
| bool result = true; |
| if (event.pointer_id != pointer_id) { |
| FXL_LOG(ERROR) << " Actual id: " << event.pointer_id; |
| FXL_LOG(ERROR) << "Expected id: " << pointer_id; |
| result = false; |
| } |
| if (event.phase != phase) { |
| FXL_LOG(ERROR) << " Actual phase: " << event.phase; |
| FXL_LOG(ERROR) << "Expected phase: " << phase; |
| result = false; |
| } |
| if (fabs(event.x - x) > kEpsilon) { |
| FXL_LOG(ERROR) << " Actual x: " << event.x; |
| FXL_LOG(ERROR) << "Expected x: " << x; |
| result = false; |
| } |
| if (fabs(event.y - y) > kEpsilon) { |
| FXL_LOG(ERROR) << " Actual y: " << event.y; |
| FXL_LOG(ERROR) << "Expected y: " << y; |
| result = false; |
| } |
| return result; |
| } |
| |
| } // namespace lib_ui_input_tests |