/* * \brief Test for input filter * \author Norman Feske * \date 2017-02-01 */ /* * Copyright (C) 2017 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Test { class Input_from_filter; class Input_to_filter; class Input_root; class Main; using namespace Genode; using Input::Event; } class Test::Input_from_filter { public: struct Event_handler : Interface { virtual void handle_event_from_filter(Event const &) = 0; }; private: Env &_env; Event_handler &_event_handler; Input::Connection _connection; bool _input_expected = false; bool _handle_input_in_progress = false; void _handle_input() { _handle_input_in_progress = true; if (_input_expected) _connection.for_each_event([&] (Event const &event) { _event_handler.handle_event_from_filter(event); }); _handle_input_in_progress = false; } Signal_handler _input_handler { _env.ep(), *this, &Input_from_filter::_handle_input }; public: Input_from_filter(Env &env, Event_handler &event_handler) : _env(env), _event_handler(event_handler), _connection(env) { _connection.sigh(_input_handler); } void input_expected(bool expected) { _input_expected = expected; /* prevent nested call of '_handle_input' */ if (!_input_expected || _handle_input_in_progress) return; /* if new step expects input, process currently pending events */ _handle_input(); } }; class Test::Input_root : public Root_component { private: Input::Session_component &_usb_input; Input::Session_component &_ps2_input; public: Input_root(Entrypoint &ep, Allocator &md_alloc, Input::Session_component &usb_input, Input::Session_component &ps2_input) : Root_component(ep, md_alloc), _usb_input(usb_input), _ps2_input(ps2_input) { } Input::Session_component *_create_session(const char *args, Affinity const &) { Session_label const label = label_from_args(args); if (label.last_element() == "usb") return &_usb_input; if (label.last_element() == "ps2") return &_ps2_input; error("no matching policy for session label ", label); throw Service_denied(); } /* * Prevent the default 'Root_component' implementation from attempting * to free the session objects. */ void _destroy_session(Input::Session_component *) override { } }; class Test::Input_to_filter { private: Env &_env; Sliced_heap _sliced_heap { _env.ram(), _env.rm() }; /* * Provide the input service via an independent entrypoint to avoid a * possible deadlock between the input_filter and the test when * both try to invoke 'Input::Session::flush' from each other. */ enum { STACK_SIZE = 4*1024*sizeof(long) }; Entrypoint _ep { _env, STACK_SIZE, "input_server_ep" }; /* * Input supplied to the input_filter */ Input::Session_component _usb { _env, _env.ram() }; Input::Session_component _ps2 { _env, _env.ram() }; Input_root _root { _ep, _sliced_heap, _usb, _ps2}; typedef String<20> Key_name; Input::Keycode _code(Key_name const &key_name) { for (unsigned i = 0; i < Input::KEY_MAX - 1; i++) { Input::Keycode const code = Input::Keycode(i); if (key_name == Input::key_name(code)) return code; } error("unknown key name: ", key_name); throw Exception(); }; public: Input_to_filter(Env &env) : _env(env) { _env.parent().announce(_ep.manage(_root)); _usb.event_queue().enabled(true); _ps2.event_queue().enabled(true); } void submit_events(Xml_node step) { if (step.type() != "usb" && step.type() != "ps2") { error("unexpected argument to Input_to_filter::submit"); throw Exception(); } Input::Session_component &dst = step.type() == "usb" ? _usb : _ps2; step.for_each_sub_node([&] (Xml_node node) { bool const press = node.has_type("press"), release = node.has_type("release"); if (press || release) { Key_name const key_name = node.attribute_value("code", Key_name()); if (press) dst.submit(Input::Press {_code(key_name)}); if (release) dst.submit(Input::Release{_code(key_name)}); } bool const motion = node.has_type("motion"); bool const rel = node.has_attribute("rx") || node.has_attribute("ry"); bool const abs = node.has_attribute("ax") || node.has_attribute("ay"); if (motion && abs) dst.submit(Input::Absolute_motion{(int)node.attribute_value("ax", 0L), (int)node.attribute_value("ay", 0L)}); if (motion && rel) dst.submit(Input::Relative_motion{(int)node.attribute_value("rx", 0L), (int)node.attribute_value("ry", 0L)}); }); } }; struct Test::Main : Input_from_filter::Event_handler { Env &_env; Timer::Connection _timer { _env }; Input_from_filter _input_from_filter { _env, *this }; Input_to_filter _input_to_filter { _env }; Reporter _input_filter_config_reporter { _env, "config", "input_filter.config" }; Reporter _chargen_include_reporter { _env, "chargen", "chargen_include" }; Reporter _remap_include_reporter { _env, "remap", "remap_include" }; Reporter _capslock_reporter { _env, "capslock", "capslock" }; Attached_rom_dataspace _config { _env, "config" }; void _publish_report(Reporter &reporter, Xml_node node) { Reporter::Xml_generator xml(reporter, [&] () { xml.append(node.content_base(), node.content_size()); }); } void _gen_chargen_rec(Xml_generator &xml, unsigned depth) { if (depth > 0) { xml.node("chargen", [&] () { _gen_chargen_rec(xml, depth - 1); }); } else { xml.node("input", [&] () { xml.attribute("name", "usb"); }); } } void _deep_filter_config(Reporter &reporter, Xml_node node) { unsigned const depth = node.attribute_value("depth", 0UL); Reporter::Xml_generator xml(reporter, [&] () { xml.node("input", [&] () { xml.attribute("label", "usb"); }); xml.node("output", [&] () { _gen_chargen_rec(xml, depth); }); }); } unsigned const _num_steps = _config.xml().num_sub_nodes(); unsigned _curr_step = 0; unsigned long _went_to_sleep_time = 0; Xml_node _curr_step_xml() const { return _config.xml().sub_node(_curr_step); } void _advance_step() { _curr_step++; /* exit when reaching the end of the sequence */ if (_curr_step == _num_steps) { _env.parent().exit(0); sleep_forever(); } }; void _execute_curr_step() { for (;;) { Xml_node const step = _curr_step_xml(); log("step ", _curr_step, " (", step.type(), ")"); _input_from_filter.input_expected(step.type() == "expect_press" || step.type() == "expect_release" || step.type() == "expect_char" || step.type() == "expect_motion" || step.type() == "expect_wheel"); if (step.type() == "filter_config") { _publish_report(_input_filter_config_reporter, step); _advance_step(); continue; } if (step.type() == "deep_filter_config") { _deep_filter_config(_input_filter_config_reporter, step); _advance_step(); continue; } if (step.type() == "chargen_include") { _publish_report(_chargen_include_reporter, step); _advance_step(); continue; } if (step.type() == "remap_include") { _publish_report(_remap_include_reporter, step); _advance_step(); continue; } if (step.type() == "capslock") { Reporter::Xml_generator xml(_capslock_reporter, [&] () { xml.attribute("enabled", step.attribute_value("enabled", false)); }); _advance_step(); continue; } if (step.type() == "usb" || step.type() == "ps2") { _input_to_filter.submit_events(step); _advance_step(); continue; } if (step.type() == "message") { typedef String<80> Message; Message const message = step.attribute_value("string", Message()); log("\n--- ", message, " ---"); _advance_step(); continue; } if (step.type() == "nop") { _advance_step(); continue; } if (step.type() == "expect_press" || step.type() == "expect_release" || step.type() == "expect_char" || step.type() == "expect_motion" || step.type() == "expect_wheel") return; if (step.type() == "sleep") { if (_went_to_sleep_time == 0) { unsigned long const timeout_ms = step.attribute_value("ms", 250UL); _went_to_sleep_time = _timer.elapsed_ms(); _timer.trigger_once(timeout_ms*1000); } return; } error("unexpected step: ", step); throw Exception(); } } /** * Input_to_filter::Event_handler interface */ void handle_event_from_filter(Event const &ev) override { typedef Genode::String<20> Value; Xml_node const step = _curr_step_xml(); bool step_succeeded = false; ev.handle_press([&] (Input::Keycode key, Codepoint codepoint) { auto codepoint_of_step = [] (Xml_node step) { return Utf8_ptr(step.attribute_value("char", Value()).string()).codepoint(); }; if (step.type() == "expect_press" && step.attribute_value("code", Value()) == Input::key_name(key) && (!step.has_attribute("char") || codepoint_of_step(step).value == codepoint.value)) step_succeeded = true; }); ev.handle_release([&] (Input::Keycode key) { if (step.type() == "expect_release" && step.attribute_value("code", Value()) == Input::key_name(key)) step_succeeded = true; }); ev.handle_wheel([&] (int x, int y) { if (step.type() == "expect_wheel" && step.attribute_value("rx", 0L) == x && step.attribute_value("ry", 0L) == y) step_succeeded = true; }); ev.handle_relative_motion([&] (int x, int y) { if (step.type() == "expect_motion" && (!step.has_attribute("rx") || step.attribute_value("rx", 0L) == x) && (!step.has_attribute("ry") || step.attribute_value("ry", 0L) == y)) step_succeeded = true; }); ev.handle_absolute_motion([&] (int x, int y) { if (step.type() == "expect_motion" && (!step.has_attribute("ax") || step.attribute_value("ax", 0L) == x) && (!step.has_attribute("ay") || step.attribute_value("ay", 0L) == y)) step_succeeded = true; }); if (step_succeeded) { _advance_step(); _execute_curr_step(); } } void _handle_timer() { if (_curr_step_xml().type() != "sleep") { error("got spurious timeout signal"); throw Exception(); } unsigned long const duration = _curr_step_xml().attribute_value("ms", 0UL); unsigned long const now = _timer.elapsed_ms(); unsigned long const slept = now - _went_to_sleep_time; if (slept < duration) { warning("spurious wakeup from sleep"); _timer.trigger_once(1000*(duration - slept)); return; } /* skip */ _advance_step(); _went_to_sleep_time = 0; _execute_curr_step(); } Signal_handler
_timer_handler { _env.ep(), *this, &Main::_handle_timer }; Main(Env &env) : _env(env) { _timer.sigh(_timer_handler); _input_filter_config_reporter.enabled(true); _chargen_include_reporter.enabled(true); _remap_include_reporter.enabled(true); _capslock_reporter.enabled(true); _execute_curr_step(); } }; void Component::construct(Genode::Env &env) { static Test::Main main(env); }