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:
Norman Feske 2017-10-25 18:00:35 +02:00 committed by Christian Helmuth
parent a0a7d5d165
commit 189c5fa628
8 changed files with 115 additions and 73 deletions

View File

@ -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"/>

View File

@ -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

View File

@ -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> &registry,
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(); }

View File

@ -34,8 +34,6 @@ namespace Input_filter {
if (name == Input::key_name(code))
return code;
}
error("unknown key: ", name);
throw Unknown_key();
}
}

View File

@ -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;

View File

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

View File

@ -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 { };
}

View File

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