mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-20 17:52:52 +00:00
input_filter: improve capslock handling
Furthermore, the patch reduces the noise in the log produced by false-positive error messages that are actually warnings. Fixes #2548
This commit is contained in:
parent
a0a7d5d165
commit
189c5fa628
@ -57,6 +57,8 @@ append config {
|
||||
report="test-input_filter -> chargen_include"/>
|
||||
<policy label_prefix="input_filter -> remap_include"
|
||||
report="test-input_filter -> remap_include"/>
|
||||
<policy label_prefix="input_filter -> capslock"
|
||||
report="test-input_filter -> capslock"/>
|
||||
</config>
|
||||
</start>
|
||||
|
||||
@ -68,6 +70,7 @@ append config {
|
||||
<service name="ROM" label="input_filter.config"> <child name="report_rom"/> </service>
|
||||
<service name="ROM" label="chargen_include"> <child name="report_rom"/> </service>
|
||||
<service name="ROM" label="remap_include"> <child name="report_rom"/> </service>
|
||||
<service name="ROM" label="capslock"> <child name="report_rom"/> </service>
|
||||
<service name="Input"> <child name="test-input_filter"/> </service>
|
||||
<service name="Timer"> <child name="timer"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
@ -228,6 +231,7 @@ append_if [test_char_repeat] config {
|
||||
<expect_release code="KEY_B"/>}
|
||||
|
||||
append config {
|
||||
|
||||
<message string="capslock handling"/>
|
||||
|
||||
<filter_config>
|
||||
@ -236,26 +240,31 @@ append config {
|
||||
<chargen>
|
||||
<remap>
|
||||
<input name="usb"/>
|
||||
<key name="KEY_CAPSLOCK" sticky="yes"/>
|
||||
</remap>
|
||||
<mod1> <key name="KEY_CAPSLOCK"/> </mod1>
|
||||
<mod1> <rom name="capslock"/> </mod1>
|
||||
<map> <key name="KEY_A" char="a"/> </map>
|
||||
<map mod1="yes"> <key name="KEY_A" char="A"/> </map>
|
||||
</chargen>
|
||||
</output>
|
||||
</filter_config>
|
||||
<!--
|
||||
Leave the 'capslock' ROM initially undefined, which prompts
|
||||
the input filter to complain about the modifier state being
|
||||
unavailable. However, as soon as 'capslock' becomes defined,
|
||||
the input filter is expected to re-processes its configuration.
|
||||
-->
|
||||
<sleep ms="250"/>
|
||||
<usb>
|
||||
<press code="KEY_A"/> <release code="KEY_A"/>
|
||||
<press code="KEY_CAPSLOCK"/> <release code="KEY_CAPSLOCK"/>
|
||||
<press code="KEY_A"/> <release code="KEY_A"/>
|
||||
<press code="KEY_CAPSLOCK"/> <release code="KEY_CAPSLOCK"/>
|
||||
<press code="KEY_A"/> <release code="KEY_A"/>
|
||||
</usb>
|
||||
<capslock enabled="no"/>
|
||||
<sleep ms="250"/>
|
||||
<usb> <press code="KEY_A"/> <release code="KEY_A"/> </usb>
|
||||
<expect_press code="KEY_A"/> <expect_char char="a"/> <expect_release code="KEY_A"/>
|
||||
<expect_press code="KEY_CAPSLOCK"/>
|
||||
<capslock enabled="yes"/>
|
||||
<sleep ms="250"/>
|
||||
<usb> <press code="KEY_A"/> <release code="KEY_A"/> </usb>
|
||||
<expect_press code="KEY_A"/> <expect_char char="A"/> <expect_release code="KEY_A"/>
|
||||
<expect_release code="KEY_CAPSLOCK"/>
|
||||
<capslock enabled="no"/>
|
||||
<sleep ms="250"/>
|
||||
<usb> <press code="KEY_A"/> <release code="KEY_A"/> </usb>
|
||||
<expect_press code="KEY_A"/> <expect_char char="a"/> <expect_release code="KEY_A"/>
|
||||
|
||||
|
||||
|
@ -25,11 +25,7 @@ one of the following filters:
|
||||
|
||||
It may contain any number of '<key>' nodes. Each of those key nodes has
|
||||
the key name as 'name' attribute, may feature an optional 'to' attribute
|
||||
with the name of the key that should be reported instead of 'name', and
|
||||
an optional 'sticky' attribute. If the latter is set to "yes", the key
|
||||
behaves like a sticky key. That means, only press events are evaluated
|
||||
and every second press event is reported as a release event. This is
|
||||
useful for special keys like capslock.
|
||||
with the name of the key that should be reported instead of 'name'.
|
||||
|
||||
:<merge>:
|
||||
|
||||
@ -57,8 +53,14 @@ sub nodes:
|
||||
|
||||
! <mod1>
|
||||
! <key name="KEY_LEFTSHIFT"/> <key name="KEY_RIGHTSHIFT"/>
|
||||
! <rom name="capslock"/>
|
||||
! </mod1>
|
||||
|
||||
The '<rom>' node incorporates the content of the ROM module of the
|
||||
specified name into the modifier state. If the ROM module contains a
|
||||
top-level node with the attribute 'enabled' set to "yes", the modifier
|
||||
is enabled. This is useful for handling a system-global capslock state.
|
||||
|
||||
:<map mod1="..." mod2="..." mod3="..." mod4="...">:
|
||||
|
||||
A '<map>' node contains a list of keys that emit a specified character when
|
||||
|
@ -71,6 +71,39 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
|
||||
Registry<Modifier> _modifiers;
|
||||
|
||||
struct Modifier_rom
|
||||
{
|
||||
typedef String<32> Name;
|
||||
|
||||
Registry<Modifier_rom>::Element _element;
|
||||
|
||||
Modifier::Id const _id;
|
||||
|
||||
bool _enabled = false;
|
||||
|
||||
Modifier_rom(Registry<Modifier_rom> ®istry,
|
||||
Modifier::Id id,
|
||||
Include_accessor &include_accessor,
|
||||
Name const &name)
|
||||
:
|
||||
_element(registry, *this), _id(id)
|
||||
{
|
||||
try {
|
||||
include_accessor.apply_include(name, "capslock", [&] (Xml_node node) {
|
||||
_enabled = node.attribute_value("enabled", false); }); }
|
||||
|
||||
catch (Include_accessor::Include_unavailable) {
|
||||
warning("failed to obtain modifier state from "
|
||||
"\"", name, "\" ROM module"); }
|
||||
}
|
||||
|
||||
Modifier::Id id() const { return _id; }
|
||||
|
||||
bool enabled() const { return _enabled; }
|
||||
};
|
||||
|
||||
Registry<Modifier_rom> _modifier_roms;
|
||||
|
||||
/*
|
||||
* Key rules for generating characters
|
||||
*/
|
||||
@ -322,6 +355,11 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
_modifiers.for_each([&] (Modifier const &mod) {
|
||||
_mod_map.states[mod.id()].enabled |=
|
||||
_key_map.key(mod.code()).state; });
|
||||
|
||||
/* supplement modifier state provided by ROM modules */
|
||||
_modifier_roms.for_each([&] (Modifier_rom const &mod_rom) {
|
||||
_mod_map.states[mod_rom.id()].enabled |=
|
||||
mod_rom.enabled(); });
|
||||
}
|
||||
|
||||
Owner _owner;
|
||||
@ -432,7 +470,7 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
void _apply_sub_node(Xml_node const node, unsigned const max_recursion)
|
||||
{
|
||||
if (max_recursion == 0) {
|
||||
error("too deeply nested includes");
|
||||
warning("too deeply nested includes");
|
||||
throw Invalid_config();
|
||||
}
|
||||
|
||||
@ -483,6 +521,16 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
|
||||
new (_alloc) Modifier(_modifiers, id, key);
|
||||
});
|
||||
|
||||
node.for_each_sub_node("rom", [&] (Xml_node rom_node) {
|
||||
|
||||
typedef Modifier_rom::Name Rom_name;
|
||||
Rom_name const rom_name = rom_node.attribute_value("name", Rom_name());
|
||||
|
||||
new (_alloc) Modifier_rom(_modifier_roms, id, _include_accessor, rom_name);
|
||||
});
|
||||
|
||||
_update_modifier_state();
|
||||
}
|
||||
|
||||
public:
|
||||
@ -515,7 +563,11 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
|
||||
~Chargen_source()
|
||||
{
|
||||
_modifiers.for_each([&] (Modifier &mod) { destroy(_alloc, &mod); });
|
||||
_modifiers.for_each([&] (Modifier &mod) {
|
||||
destroy(_alloc, &mod); });
|
||||
|
||||
_modifier_roms.for_each([&] (Modifier_rom &mod_rom) {
|
||||
destroy(_alloc, &mod_rom); });
|
||||
}
|
||||
|
||||
void generate() override { _source.generate(); }
|
||||
|
@ -34,8 +34,6 @@ namespace Input_filter {
|
||||
if (name == Input::key_name(code))
|
||||
return code;
|
||||
}
|
||||
|
||||
error("unknown key: ", name);
|
||||
throw Unknown_key();
|
||||
}
|
||||
}
|
||||
|
@ -107,9 +107,6 @@ struct Input_filter::Main : Input_connection::Avail_handler,
|
||||
_dataspace(env, name.string()), _reconfig_sigh(reconfig_sigh),
|
||||
_rom_update_handler(env.ep(), *this, &Rom::_handle_rom_update)
|
||||
{
|
||||
/* validate top-level node type */
|
||||
xml(type);
|
||||
|
||||
/* respond to ROM updates */
|
||||
_dataspace.sigh(_rom_update_handler);
|
||||
}
|
||||
@ -127,8 +124,8 @@ struct Input_filter::Main : Input_connection::Avail_handler,
|
||||
if (node.type() == type)
|
||||
return node;
|
||||
|
||||
error("unexpected <", node.type(), "> node " "in included "
|
||||
"ROM \"", _name, "\", expected, <", type, "> node");
|
||||
warning("unexpected <", node.type(), "> node " "in included "
|
||||
"ROM \"", _name, "\", expected, <", type, "> node");
|
||||
throw Include_unavailable();
|
||||
}
|
||||
};
|
||||
@ -170,11 +167,13 @@ struct Input_filter::Main : Input_connection::Avail_handler,
|
||||
{
|
||||
/* populate registry on demand */
|
||||
if (!_exists(name)) {
|
||||
try { new (_alloc) Rom(_registry, _env, name, type, _sigh); }
|
||||
catch (...) {
|
||||
error("include \"", name, "\" unavailable");
|
||||
throw Include_unavailable();
|
||||
try {
|
||||
Rom &rom = *new (_alloc) Rom(_registry, _env, name, type, _sigh);
|
||||
|
||||
/* \throw Include_unavailable on mismatching top-level node type */
|
||||
rom.xml(type);
|
||||
}
|
||||
catch (...) { throw Include_unavailable(); }
|
||||
}
|
||||
|
||||
/* call 'fn' with the XML content of the named include */
|
||||
@ -214,7 +213,7 @@ struct Input_filter::Main : Input_connection::Avail_handler,
|
||||
Nesting_level_guard(unsigned &level) : level(level)
|
||||
{
|
||||
if (level == 0) {
|
||||
error("too many nested input sources");
|
||||
warning("too many nested input sources");
|
||||
throw Source::Invalid_config();
|
||||
}
|
||||
level--;
|
||||
@ -236,7 +235,7 @@ struct Input_filter::Main : Input_connection::Avail_handler,
|
||||
if (match)
|
||||
return *new (_heap) Input_source(owner, *match, sink);
|
||||
|
||||
error("input named '", label, "' does not exist");
|
||||
warning("input named '", label, "' does not exist");
|
||||
throw Source::Invalid_config();
|
||||
}
|
||||
|
||||
@ -251,7 +250,7 @@ struct Input_filter::Main : Input_connection::Avail_handler,
|
||||
return *new (_heap) Chargen_source(owner, node, sink, *this, _heap,
|
||||
_timer_accessor, _include_accessor);
|
||||
|
||||
error("unknown <", node.type(), "> input-source node type");
|
||||
warning("unknown <", node.type(), "> input-source node type");
|
||||
throw Source::Invalid_config();
|
||||
}
|
||||
|
||||
@ -384,23 +383,23 @@ struct Input_filter::Main : Input_connection::Avail_handler,
|
||||
new (_heap)
|
||||
Registered<Input_connection>(_input_connections, _env,
|
||||
label, *this, _heap);
|
||||
|
||||
} catch (Genode::Service_denied) {
|
||||
error("parent denied input source '", label, "'");
|
||||
}
|
||||
} catch (Xml_node::Nonexistent_attribute) {
|
||||
error("ignoring invalid input node '", input_node);
|
||||
catch (Genode::Service_denied) {
|
||||
warning("parent denied input source '", label, "'"); }
|
||||
}
|
||||
catch (Xml_node::Nonexistent_attribute) {
|
||||
warning("ignoring invalid input node '", input_node); }
|
||||
});
|
||||
|
||||
try {
|
||||
if (_config.xml().has_sub_node("output"))
|
||||
_output.construct(_config.xml().sub_node("output"),
|
||||
_final_sink, *this);
|
||||
}
|
||||
catch (Source::Invalid_config) {
|
||||
warning("invalid <output> configuration"); }
|
||||
|
||||
} catch (Source::Invalid_config) {
|
||||
error("invalid <output> configuration");
|
||||
} catch (Allocator::Out_of_memory) {
|
||||
catch (Allocator::Out_of_memory) {
|
||||
error("out of memory while constructing filter chain"); }
|
||||
|
||||
_config_update_pending = false;
|
||||
|
@ -31,14 +31,6 @@ class Input_filter::Remap_source : public Source, Source::Sink
|
||||
struct Key
|
||||
{
|
||||
Input::Keycode code = Input::KEY_UNKNOWN;
|
||||
bool sticky = false;
|
||||
|
||||
enum State { RELEASED, PRESSED } state = RELEASED;
|
||||
|
||||
void toggle()
|
||||
{
|
||||
state = (state == PRESSED) ? RELEASED : PRESSED;
|
||||
}
|
||||
};
|
||||
|
||||
Key _keys[Input::KEY_MAX];
|
||||
@ -68,27 +60,10 @@ class Input_filter::Remap_source : public Source, Source::Sink
|
||||
return;
|
||||
}
|
||||
|
||||
/* remap the key code */
|
||||
Key &key = _keys[event.keycode()];
|
||||
|
||||
Key::State const old_state = key.state;
|
||||
|
||||
/* update key state, depending on the stickyness of the key */
|
||||
if (key.sticky) {
|
||||
if (event.type() == Event::PRESS)
|
||||
key.toggle();
|
||||
} else {
|
||||
key.state = (event.type() == Event::PRESS) ? Key::PRESSED
|
||||
: Key::RELEASED;
|
||||
}
|
||||
|
||||
/* drop release events of sticky keys */
|
||||
if (key.state == old_state)
|
||||
return;
|
||||
|
||||
Event::Type const type =
|
||||
key.state == Key::PRESSED ? Event::PRESS : Event::RELEASE;
|
||||
|
||||
_destination.submit_event(Event(type, key.code, 0, 0, 0, 0));
|
||||
_destination.submit_event(Event(event.type(), key.code, 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
public:
|
||||
@ -118,8 +93,6 @@ class Input_filter::Remap_source : public Source, Source::Sink
|
||||
try { _keys[code].code = key_code_by_name(to); }
|
||||
catch (Unknown_key) { warning("ignoring remap rule ", node); }
|
||||
}
|
||||
|
||||
_keys[code].sticky = node.attribute_value("sticky", false);
|
||||
}
|
||||
catch (Unknown_key) {
|
||||
warning("invalid key name ", key_name); }
|
||||
|
@ -57,7 +57,7 @@ class Input_filter::Source
|
||||
if (result.type() != "none")
|
||||
return result;
|
||||
|
||||
error("missing <remap>/<chargen>/<merge> sub node in ", node);
|
||||
warning("missing input sub node in ", node);
|
||||
throw Invalid_config { };
|
||||
}
|
||||
|
||||
|
@ -235,9 +235,10 @@ struct Test::Main : Input_from_filter::Event_handler
|
||||
|
||||
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 _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" };
|
||||
|
||||
@ -319,6 +320,13 @@ struct Test::Main : Input_from_filter::Event_handler
|
||||
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();
|
||||
@ -428,6 +436,7 @@ struct Test::Main : Input_from_filter::Event_handler
|
||||
_input_filter_config_reporter.enabled(true);
|
||||
_chargen_include_reporter.enabled(true);
|
||||
_remap_include_reporter.enabled(true);
|
||||
_capslock_reporter.enabled(true);
|
||||
_execute_curr_step();
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user