blob: d23b4c587a45b2d009a79b6f72f5042ae0c9cef7 [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 <memory>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/async/cpp/time.h>
#include <lib/zx/eventpair.h>
#include "garnet/lib/ui/input/tests/util.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/lib/fxl/logging.h"
#include "lib/gtest/test_loop_fixture.h"
#include "lib/ui/input/cpp/formatting.h"
#include "lib/ui/scenic/cpp/resources.h"
#include "lib/ui/scenic/cpp/session.h"
// This test exercises the event delivery logic for hard keyboard events.
//
// Typically, hard keyboard events are sent to the Text Sync service for further
// dispatch to an IME; in contrast, the hard keyboard events are not sent
// directly to a View. This is the default behavior.
//
// Some clients may request direct delivery; the client assumes responsibility
// for correct interpretation of the HID codes.
//
// The geometry of the display and layer are contrained to a 5x5 square. Just
// one view is overlayed on top.
//
// x - - - -
// - - - - -
// - - d - -
// - - - - - x - client's view origin
// - - - - - d - add and down events, to bring focus to the View.
//
// NOTE: This test is carefully constructed to avoid Vulkan functionality.
namespace lib_ui_input_tests {
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::PointerEvent;
using fuchsia::ui::input::PointerEventType;
using fuchsia::ui::scenic::SessionListener;
using scenic_impl::Scenic;
using scenic_impl::gfx::Display;
using scenic_impl::gfx::DisplayManager;
using scenic_impl::gfx::test::GfxSystemForTest;
using scenic_impl::input::InputSystem;
using scenic_impl::test::ScenicTest;
// Class fixture for TEST_F. Sets up a 5x5 "display" for GfxSystem.
class HardKeyboardDeliveryTest : public InputSystemTest {
protected:
uint32_t test_display_width_px() const override { return 5; }
uint32_t test_display_height_px() const override { return 5; }
};
class KeyboardSessionWrapper : public SessionWrapper {
public:
KeyboardSessionWrapper(scenic_impl::Scenic* scenic)
: SessionWrapper(scenic) {}
~KeyboardSessionWrapper() = default;
void ClearEvents() { events_.clear(); }
};
TEST_F(HardKeyboardDeliveryTest, Test) {
KeyboardSessionWrapper presenter(scenic());
zx::eventpair v_token, vh_token;
CreateTokenPair(&v_token, &vh_token);
// Tie the test's dispatcher clock to the system (real) clock.
RunLoopUntil(zx::clock::get_monotonic());
// "Presenter" sets up a scene with one view.
uint32_t compositor_id = 0;
presenter.RunNow([this, &compositor_id, vh_token = std::move(vh_token)](
scenic::Session* session,
scenic::EntityNode* root_node) mutable {
// Minimal scene.
scenic::Compositor compositor(session);
compositor_id = compositor.id();
scenic::Scene scene(session);
scenic::Camera camera(scene);
scenic::Renderer renderer(session);
renderer.SetCamera(camera);
scenic::Layer layer(session);
layer.SetSize(test_display_width_px(), test_display_height_px());
layer.SetRenderer(renderer);
scenic::LayerStack layer_stack(session);
layer_stack.AddLayer(layer);
compositor.SetLayerStack(layer_stack);
// Add local root node to the scene, attach the view holder.
scene.AddChild(*root_node);
scenic::ViewHolder view_holder(session, std::move(vh_token), "View Holder");
root_node->Attach(view_holder);
RequestToPresent(session);
});
// Client sets up its content.
KeyboardSessionWrapper client(scenic());
client.RunNow(
[this, v_token = std::move(v_token)](
scenic::Session* session, scenic::EntityNode* root_node) mutable {
// Connect our root node to the presenter's root node.
scenic::View view(session, std::move(v_token), "View");
view.AddChild(*root_node);
scenic::ShapeNode shape(session);
shape.SetTranslation(2, 2, 0); // Center the shape within the View.
root_node->AddPart(shape);
scenic::Rectangle rec(session, 5, 5); // Simple; no real GPU work.
shape.SetShape(rec);
scenic::Material material(session);
shape.SetMaterial(material);
RequestToPresent(session);
});
// Scene is now set up, send in the input.
presenter.RunNow([this, compositor_id](scenic::Session* session,
scenic::EntityNode* root_node) {
PointerCommandGenerator pointer(compositor_id, /*device id*/ 1,
/*pointer id*/ 1, PointerEventType::TOUCH);
// A touch sequence that starts at the (2,2) location of the 5x5 display.
// We do enough to trigger a focus change to the View.
session->Enqueue(pointer.Add(2, 2));
session->Enqueue(pointer.Down(2, 2));
// The character 'a', pressed and released.
KeyboardCommandGenerator keyboard(compositor_id, /*device id*/ 2);
uint32_t hid_usage = 0x4;
uint32_t modifiers = 0x0;
session->Enqueue(keyboard.Pressed(hid_usage, modifiers));
session->Enqueue(keyboard.Released(hid_usage, modifiers));
RunLoopUntilIdle();
#if 0
FXL_LOG(INFO) << DumpScenes(); // Handy debugging.
#endif
});
// Verify client's inputs do *not* include keyboard events.
client.ExamineEvents([](const std::vector<InputEvent>& events) {
EXPECT_EQ(events.size(), 3u) << "Should receive exactly 3 input events.";
// ADD
{
EXPECT_TRUE(events[0].is_pointer());
const PointerEvent& add = events[0].pointer();
EXPECT_EQ(add.x, 2);
EXPECT_EQ(add.y, 2);
}
// FOCUS
EXPECT_TRUE(events[1].is_focus());
// DOWN
{
EXPECT_TRUE(events[2].is_pointer());
const PointerEvent& down = events[2].pointer();
EXPECT_EQ(down.x, 2);
EXPECT_EQ(down.y, 2);
}
});
client.ClearEvents();
// Client requests hard keyboard event delivery.
client.RunNow(
[this](scenic::Session* session, scenic::EntityNode* root_node) {
fuchsia::ui::input::SetHardKeyboardDeliveryCmd cmd;
cmd.delivery_request = true;
InputCommand input_cmd;
input_cmd.set_set_hard_keyboard_delivery(std::move(cmd));
session->Enqueue(std::move(input_cmd));
RunLoopUntilIdle();
});
// Send in the input.
presenter.RunNow([this, compositor_id](scenic::Session* session,
scenic::EntityNode* root_node) {
// Client is already in focus, no need to focus again.
KeyboardCommandGenerator keyboard(compositor_id, /*device id*/ 2);
uint32_t hid_usage = 0x4;
uint32_t modifiers = 0x0;
session->Enqueue(keyboard.Pressed(hid_usage, modifiers));
session->Enqueue(keyboard.Released(hid_usage, modifiers));
RunLoopUntilIdle();
});
// Verify client's inputs include keyboard events.
client.ExamineEvents([](const std::vector<InputEvent>& events) {
EXPECT_EQ(events.size(), 2u) << "Should receive exactly 2 input events.";
});
}
} // namespace lib_ui_input_tests