blob: 82ddbdf8b923b4b4ad4e7cd42104f9a9ca727262 [file] [log] [blame]
// 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