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); }