diff --git a/repos/gems/run/launcher.run b/repos/gems/run/launcher.run index 0eee52193..ad9a8df1f 100644 --- a/repos/gems/run/launcher.run +++ b/repos/gems/run/launcher.run @@ -66,9 +66,9 @@ install_config { - - - + + + @@ -86,14 +86,21 @@ install_config { - + - - - - - + + + + + + + + + + + + @@ -115,7 +122,7 @@ install_config { - + @@ -125,7 +132,7 @@ install_config { - + @@ -264,9 +271,9 @@ install_config { } -build { app/launcher test/nitpicker app/status_bar app/xray_trigger } +build { app/launcher test/nitpicker app/status_bar app/global_keys_handler } -build_boot_image { testnit launcher xray_trigger status_bar } +build_boot_image { testnit launcher global_keys_handler status_bar } run_genode_until forever diff --git a/repos/os/recipes/src/xray_trigger/content.mk b/repos/os/recipes/src/global_keys_handler/content.mk similarity index 59% rename from repos/os/recipes/src/xray_trigger/content.mk rename to repos/os/recipes/src/global_keys_handler/content.mk index 10ee9778a..412659229 100644 --- a/repos/os/recipes/src/xray_trigger/content.mk +++ b/repos/os/recipes/src/global_keys_handler/content.mk @@ -1,2 +1,2 @@ -SRC_DIR := src/app/xray_trigger +SRC_DIR := src/app/global_keys_handler include $(GENODE_DIR)/repos/base/recipes/src/content.inc diff --git a/repos/os/recipes/src/xray_trigger/hash b/repos/os/recipes/src/global_keys_handler/hash similarity index 100% rename from repos/os/recipes/src/xray_trigger/hash rename to repos/os/recipes/src/global_keys_handler/hash diff --git a/repos/os/recipes/src/xray_trigger/used_apis b/repos/os/recipes/src/global_keys_handler/used_apis similarity index 100% rename from repos/os/recipes/src/xray_trigger/used_apis rename to repos/os/recipes/src/global_keys_handler/used_apis diff --git a/repos/os/run/demo.run b/repos/os/run/demo.run index f3de2a071..704e6a892 100644 --- a/repos/os/run/demo.run +++ b/repos/os/run/demo.run @@ -10,7 +10,7 @@ if {[have_spec odroid_xu]} { set build_components { core init drivers/timer - server/nitpicker app/pointer app/status_bar app/xray_trigger + server/nitpicker app/pointer app/status_bar app/global_keys_handler server/liquid_framebuffer app/launchpad app/scout test/nitpicker server/nitlog drivers/framebuffer drivers/input @@ -107,9 +107,9 @@ append config { - - - + + + @@ -123,7 +123,7 @@ append config { - + - - - + + + @@ -170,13 +170,20 @@ append config { - + - - - - + + + + + + + + + + + @@ -249,7 +256,7 @@ close $launchpad_config_fd set boot_modules { core ld.lib.so init timer - nitpicker pointer status_bar report_rom rom_filter xray_trigger + nitpicker pointer status_bar report_rom rom_filter global_keys_handler liquid_fb launchpad scout testnit nitlog launchpad.config } diff --git a/repos/os/src/app/global_keys_handler/README b/repos/os/src/app/global_keys_handler/README new file mode 100644 index 000000000..9256dc946 --- /dev/null +++ b/repos/os/src/app/global_keys_handler/README @@ -0,0 +1,50 @@ +The "global-key-handler" component transforms a stream of nitpicker input +events to state reports. The states and the ways of how the user input affects +the states is configurable. Examples for such states are the system-global +capslock and numlock states, or the nitpicker X-ray mode activated by a global +secure-attention key. The configuration looks as follows: + +! +! +! +! +! +! +! +! +! +! +! +! + +A '' node declares a boolean state variable with the given name and its +initial value (default is "no"). There may by any number of such variables. + +The '' and '' nodes define how key events affect the state +variables. Each of those nodes refers to a specific state variable via the +'bool' attribute, and the operation as the 'change' attribute. Possible +'change' attribute values are "on", "off", and "toggle". + +The '' node defines a state-dependent report with the name as +specified in the 'name' attribute. The report-generation rate can be +artificially limited by the 'delay_ms' attribute. If specified, the report is +not issued immediately on a state change but after the specified amount of +milliseconds. The '' node contains a number of conditions. Whenever +one of those conditions is true, a report of the following form is generated: + +! + +Otherwise, the report's 'enabled' attribute has the value "no". Possible +conditions are '' and ''. The '' condition is true if the +named boolean state variable has the value true. The '' condition is +true if the currently hovered nitpicker client belongs to the domain as +specified in the 'domain' attribute. The latter information is obtained from a +ROM module named "hover", which correponds to nitpicker's hover reports. + +To use the global-keys-handler in practice, one needs to configure the +nitpicker GUI server such that the press/release events of the global keys of +interest are routed to the global-keys-handler. This can be achieved by +nitpicker's '' configuration nodes. For example: + +! +! diff --git a/repos/os/src/app/global_keys_handler/main.cc b/repos/os/src/app/global_keys_handler/main.cc new file mode 100644 index 000000000..da70d791c --- /dev/null +++ b/repos/os/src/app/global_keys_handler/main.cc @@ -0,0 +1,420 @@ +/* + * \brief Utility for generating state reports from global key events + * \author Norman Feske + * \date 2015-10-03 + */ + +/* + * Copyright (C) 2015-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. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Global_keys_handler { + struct Main; + using namespace Genode; +} + + +struct Global_keys_handler::Main +{ + Env &_env; + + Heap _heap { _env.ram(), _env.rm() }; + + /** + * Configuration buffer + */ + Attached_rom_dataspace _config_ds { _env, "config" }; + + /** + * Nitpicker connection to obtain user input + */ + Nitpicker::Connection _nitpicker { _env, "input" }; + + /** + * Input-event buffer + */ + Attached_dataspace _ev_ds { _env.rm(), _nitpicker.input()->dataspace() }; + + /** + * Number of pressed keys, used to distinguish primary keys from key + * sequences. + */ + unsigned _key_cnt = 0; + + /** + * Hover model as reported by nitpicker + */ + Constructible _hover_ds; + + struct Bool_state + { + Registry::Element _element; + + typedef String<64> Name; + + Name const _name; + + bool _state = false; + + Bool_state(Registry ®istry, Xml_node node) + : + _element(registry, *this), + _name(node.attribute_value("name", Name())), + _state(node.attribute_value("initial", false)) + { } + + bool enabled() const { return _state; } + + void apply_change(Xml_node event) + { + /* modify state of matching name only */ + if (event.attribute_value("bool", Bool_state::Name()) != _name) + return; + + typedef String<16> Change; + Change const change = event.attribute_value("change", Change()); + + if (change == "on") _state = true; + if (change == "off") _state = false; + if (change == "toggle") _state = !_state; + } + + bool has_name(Name const &name) const { return name == _name; } + }; + + Registry _bool_states; + + struct Report + { + Deallocator &_alloc; + + typedef String<64> Name; + Name const _name; + + Registry::Element _element; + + Registry const &_bool_states; + + struct Bool_condition + { + Registry::Element _element; + + Bool_state::Name const _name; + + Bool_condition(Registry ®istry, Xml_node node) + : + _element(registry, *this), + _name(node.attribute_value("name", Bool_state::Name())) + { } + + /** + * Return true if boolean condition is true + */ + bool satisfied(Registry const &bool_states) const + { + bool result = false; + bool_states.for_each([&] (Bool_state const &state) { + if (state.has_name(_name)) + result = state.enabled(); }); + return result; + } + }; + + struct Hover_condition + { + Registry::Element _element; + + typedef String<160> Domain; + + Domain const _domain; + + Hover_condition(Registry ®istry, Xml_node node) + : + _element(registry, *this), + _domain(node.attribute_value("domain", Domain())) + { } + + /** + * Return true if hovered domain equals expected domain + */ + bool satisfied(Domain const &hovered) const { return hovered == _domain; } + }; + + Registry _bool_conditions; + Registry _hover_conditions; + + Reporter _reporter; + + bool _initial_report = true; + + bool _curr_value = false; + + void _generate_report() + { + Reporter::Xml_generator xml(_reporter, [&] () { + xml.attribute("enabled", _curr_value ? "yes" : "no"); }); + } + + /* + * Handler used for generating delayed reports + */ + Constructible _timer; + + unsigned long const _delay_ms; + + Signal_handler _timer_handler; + + Report(Env &env, Allocator &alloc, + Registry &reports, + Registry const &bool_states, + Xml_node node) + : + _alloc(alloc), + _name(node.attribute_value("name", Name())), + _element(reports, *this), + _bool_states(bool_states), + _reporter(env, _name.string()), + _delay_ms(node.attribute_value("delay_ms", 0UL)), + _timer_handler(env.ep(), *this, &Report::_generate_report) + { + _reporter.enabled(true); + + node.for_each_sub_node("bool", [&] (Xml_node bool_node) { + new (alloc) Bool_condition(_bool_conditions, bool_node); }); + + node.for_each_sub_node("hovered", [&] (Xml_node hovered) { + new (alloc) Hover_condition(_hover_conditions, hovered); }); + + if (_delay_ms) { + _timer.construct(env); + _timer->sigh(_timer_handler); + } + } + + ~Report() + { + _bool_conditions.for_each([&] (Bool_condition &condition) { + destroy(_alloc, &condition); }); + + _hover_conditions.for_each([&] (Hover_condition &condition) { + destroy(_alloc, &condition); }); + } + + void update(Hover_condition::Domain const &hovered_domain) + { + bool old_value = _curr_value; + + _curr_value = false; + + _bool_conditions.for_each([&] (Bool_condition const &cond) { + _curr_value |= cond.satisfied(_bool_states); }); + + _hover_conditions.for_each([&] (Hover_condition const &cond) { + _curr_value |= cond.satisfied(hovered_domain); }); + + if (!_initial_report && _curr_value == old_value) + return; + + _initial_report = false; + + if (_delay_ms) + _timer->trigger_once(_delay_ms*1000); + else + _generate_report(); + } + + bool depends_on_hover_info() const + { + bool result = false; + _hover_conditions.for_each([&] (Hover_condition const &) { result = true; }); + return result; + } + }; + + Registry _reports; + + bool _reports_depend_on_hover_info() const + { + bool result = false; + _reports.for_each([&] (Report const &report) { + result |= report.depends_on_hover_info(); }); + return result; + } + + void _apply_input_events(unsigned, Input::Event const []); + + /** + * Handler that is called on config changes + */ + void _handle_config(); + + Signal_handler
_config_handler = + { _env.ep(), *this, &Main::_handle_config }; + + /** + * Handler that is called on hover changes or on the arrival of user input + */ + void _handle_input(); + + Signal_handler
_input_handler = { + _env.ep(), *this, &Main::_handle_input }; + + Main(Env &env) : _env(env) + { + _config_ds.sigh(_config_handler); + _nitpicker.input()->sigh(_input_handler); + + _handle_config(); + _handle_input(); + } +}; + + +void Global_keys_handler::Main::_apply_input_events(unsigned num_ev, + Input::Event const events[]) +{ + for (unsigned i = 0; i < num_ev; i++) { + + Input::Event const &ev = events[i]; + + if (ev.type() != Input::Event::PRESS + && ev.type() != Input::Event::RELEASE) + continue; + + if (ev.type() == Input::Event::PRESS) _key_cnt++; + if (ev.type() == Input::Event::RELEASE) _key_cnt--; + + /* ignore key combinations */ + if (_key_cnt > 1) continue; + + typedef String<32> Key_name; + Key_name const ev_key_name(Input::key_name(ev.keycode())); + + typedef Xml_node Xml_node; + + auto lambda = [&] (Xml_node node) { + + if (!node.has_type("press") && !node.has_type("release")) + return; + + if (node.has_type("press") && ev.type() != Input::Event::PRESS) + return; + + if (node.has_type("release") && ev.type() != Input::Event::RELEASE) + return; + + /* + * XML node applies for current event type, check if the key + * matches. + */ + if (node.attribute_value("name", Key_name()) != ev_key_name) + return; + + /* + * Manipulate bool state as instructed by the XML node. + */ + _bool_states.for_each([&] (Bool_state &state) { + state.apply_change(node); }); + }; + + _config_ds.xml().for_each_sub_node(lambda); + } +} + + +void Global_keys_handler::Main::_handle_config() +{ + _config_ds.update(); + + Xml_node const config = _config_ds.xml(); + + /* + * Import bool states + */ + _bool_states.for_each([&] (Bool_state &state) + { + bool keep_existing_state = false; + config.for_each_sub_node("bool", [&] (Xml_node node) { + if (state.has_name(node.attribute_value("name", Bool_state::Name()))) + keep_existing_state = true; }); + + if (!keep_existing_state) + destroy(_heap, &state); + }); + + config.for_each_sub_node("bool", [&] (Xml_node node) + { + Bool_state::Name const name = node.attribute_value("name", Bool_state::Name()); + + bool state_already_exists = false; + _bool_states.for_each([&] (Bool_state const &state) { + if (state.has_name(name)) + state_already_exists = true; }); + + if (!state_already_exists) + new (_heap) Bool_state(_bool_states, node); + }); + + /* + * Update report definitions + */ + _reports.for_each([&] (Report &report) { destroy(_heap, &report); }); + + config.for_each_sub_node("report", [&] (Xml_node report) { + new (_heap) Report(_env, _heap, _reports, _bool_states, report); }); + + /* + * Obtain / update hover info if needed + */ + if (_reports_depend_on_hover_info() && !_hover_ds.constructed()) { + _hover_ds.construct(_env, "hover"); + _hover_ds->sigh(_input_handler); + } + + /* + * Trigger initial creation of the reports + */ + _handle_input(); +} + + +void Global_keys_handler::Main::_handle_input() +{ + while (unsigned const num_ev = _nitpicker.input()->flush()) + _apply_input_events(num_ev, _ev_ds.local_addr()); + + /* determine currently hovered domain */ + typedef Report::Hover_condition::Domain Domain; + Domain hovered_domain; + if (_hover_ds.constructed()) { + _hover_ds->update(); + hovered_domain = _hover_ds->xml().attribute_value("domain", Domain()); + } + + /* re-generate reports */ + _reports.for_each([&] (Report &report) { + report.update(hovered_domain); }); +} + + +/*************** + ** Component ** + ***************/ + +void Component::construct(Genode::Env &env) { + static Global_keys_handler::Main main(env); } diff --git a/repos/os/src/app/xray_trigger/target.mk b/repos/os/src/app/global_keys_handler/target.mk similarity index 51% rename from repos/os/src/app/xray_trigger/target.mk rename to repos/os/src/app/global_keys_handler/target.mk index 3ac1383f4..cedf5d316 100644 --- a/repos/os/src/app/xray_trigger/target.mk +++ b/repos/os/src/app/global_keys_handler/target.mk @@ -1,3 +1,3 @@ -TARGET = xray_trigger +TARGET = global_keys_handler SRC_CC = main.cc LIBS += base diff --git a/repos/os/src/app/xray_trigger/main.cc b/repos/os/src/app/xray_trigger/main.cc deleted file mode 100644 index dc3b1dfd3..000000000 --- a/repos/os/src/app/xray_trigger/main.cc +++ /dev/null @@ -1,257 +0,0 @@ -/* - * \brief Policy for activating the X-Ray mode - * \author Norman Feske - * \date 2015-10-03 - */ - -/* - * Copyright (C) 2015-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. - */ - -/* Genode includes */ -#include -#include -#include -#include -#include -#include -#include - - -namespace Xray_trigger { - struct Main; - using namespace Genode; -} - - -struct Xray_trigger::Main -{ - Env &_env; - - /** - * Configuration buffer - */ - Attached_rom_dataspace _config { _env, "config" }; - - /** - * Nitpicker connection to obtain user input - */ - Nitpicker::Connection _nitpicker { _env, "input" }; - - /** - * Input-event buffer - */ - Attached_dataspace _ev_ds { _env.rm(), _nitpicker.input()->dataspace() }; - - /** - * Number of pressed keys, used to distinguish primary keys from key - * sequences. - */ - unsigned _key_cnt = 0; - - /** - * Hover model as reported by nitpicker - */ - Constructible _hover_ds; - - /** - * Reporter for posting the result of our policy decision - */ - Reporter _xray_reporter { _env, "xray" }; - - /** - * Timer to delay the xray report - */ - Timer::Connection _timer { _env }; - - /** - * X-Ray criterion depending on key events - */ - bool _key_xray = false; - - bool _key_xray_initialized = false; - - /** - * X-Ray criterion depending on hovered domain - */ - bool _hover_xray = false; - - bool _xray() const { return _key_xray || _hover_xray; } - - bool _evaluate_input(bool, unsigned, Input::Event const [], unsigned &) const; - bool _evaluate_hover(Xml_node) const; - - /** - * Handler that is called on config changes, on hover-model changes, or on - * the arrival of user input - */ - void _handle_update(); - - Signal_handler
_update_handler = - { _env.ep(), *this, &Main::_handle_update }; - - void _report_xray() - { - Reporter::Xml_generator xml(_xray_reporter, [&] () { - xml.attribute("enabled", _xray() ? "yes" : "no"); - }); - } - - /** - * Handler that is called after the xray report delay - */ - Signal_handler
_timeout_handler = - { _env.ep(), *this, &Main::_report_xray }; - - Main(Env &env) : _env(env) - { - _config.sigh(_update_handler); - - _timer.sigh(_timeout_handler); - - /* enable xray reporter and produce initial xray report */ - _xray_reporter.enabled(true); - _report_xray(); - - _nitpicker.input()->sigh(_update_handler); - _handle_update(); - } -}; - - -bool Xray_trigger::Main::_evaluate_input(bool key_xray, unsigned num_ev, - Input::Event const events[], - unsigned &key_cnt) const -{ - /* adjust '_key_xray' according to user key input */ - for (unsigned i = 0; i < num_ev; i++) { - - Input::Event const &ev = events[i]; - - if (ev.type() != Input::Event::PRESS - && ev.type() != Input::Event::RELEASE) - continue; - - if (ev.type() == Input::Event::PRESS) key_cnt++; - if (ev.type() == Input::Event::RELEASE) key_cnt--; - - /* ignore key combinations */ - if (key_cnt > 1) continue; - - typedef String<32> Key_name; - Key_name const ev_key_name(Input::key_name(ev.keycode())); - - typedef Xml_node Xml_node; - - auto lambda = [&] (Xml_node node) { - - if (!node.has_type("press") && !node.has_type("release")) - return; - - if (node.has_type("press") && ev.type() != Input::Event::PRESS) - return; - - if (node.has_type("release") && ev.type() != Input::Event::RELEASE) - return; - - /* - * XML node applies for current event type, check if the key - * matches. - */ - - Key_name const cfg_key_name = - node.attribute_value("name", Key_name()); - - if (cfg_key_name != ev_key_name) - return; - - /* - * Manipulate X-Ray mode as instructed by the XML node. - */ - - if (node.attribute("xray").has_value("on")) - key_xray = true; - - if (node.attribute("xray").has_value("off")) - key_xray = false; - - if (node.attribute("xray").has_value("toggle")) - key_xray = !_key_xray; - }; - - _config.xml().for_each_sub_node(lambda); - } - return key_xray; -} - - -bool Xray_trigger::Main::_evaluate_hover(Xml_node nitpicker_hover) const -{ - bool hover_xray = false; - - using namespace Genode; - - _config.xml().for_each_sub_node("hover", [&] (Xml_node node) { - - typedef String<160> Domain; - Domain nitpicker_domain = nitpicker_hover.attribute_value("domain", Domain()); - Domain expected_domain = node.attribute_value("domain", Domain()); - - if (nitpicker_domain == expected_domain) - hover_xray = true; - }); - return hover_xray; -} - - -void Xray_trigger::Main::_handle_update() -{ - _config.update(); - - /* define initial state once */ - if (!_key_xray_initialized) { - _key_xray = _config.xml().attribute_value("initial", false); - - _key_xray_initialized = true; - _timer.trigger_once(10000); - } - - /* remember X-Ray mode prior applying the changes */ - bool const orig_xray = _xray(); - - while (unsigned const num_ev = _nitpicker.input()->flush()) - _key_xray = _evaluate_input(_key_xray, num_ev, - _ev_ds.local_addr(), - _key_cnt); - - /* obtain / update hover model if needed */ - if (_config.xml().has_sub_node("hover")) { - - if (!_hover_ds.constructed()) { - _hover_ds.construct(_env, "hover"); - _hover_ds->sigh(_update_handler); - } - - _hover_ds->update(); - } - - try { - _hover_xray = _evaluate_hover(Xml_node(_hover_ds->local_addr(), - _hover_ds->size())); - } catch (...) { } - - /* generate new X-Ray report if the X-Ray mode changed */ - if (_xray() != orig_xray) - _timer.trigger_once(125000); -} - - -/*************** - ** Component ** - ***************/ - -void Component::construct(Genode::Env &env) { - static Xray_trigger::Main main(env); }