os: input_filter implementation and test

The input_filter is the successor of the input_merger. In addition to
merging input streams, the component applies several forms of input
transformations such as the application of keyboard layouts.

Issue #2264
This commit is contained in:
Norman Feske 2017-02-10 21:15:45 +01:00
parent 859d23d92b
commit 2ce87216bc
19 changed files with 2746 additions and 0 deletions

View File

@ -0,0 +1,355 @@
#
# Build
#
set build_components {
core init drivers/timer
server/report_rom server/input_filter test/input_filter
}
build $build_components
create_boot_directory
#
# Generate config
#
append config {
<config prio_levels="2">
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="CPU"/>
<service name="PD"/>
<service name="LOG"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="report_rom" priority="-1">
<resource name="RAM" quantum="2M"/>
<provides> <service name="ROM"/> <service name="Report"/> </provides>
<config>
<policy label_prefix="input_filter -> input_filter.config"
report="test-input_filter -> input_filter.config"/>
<policy label_prefix="input_filter -> chargen_include"
report="test-input_filter -> chargen_include"/>
<policy label_prefix="input_filter -> remap_include"
report="test-input_filter -> remap_include"/>
</config>
</start>
<start name="input_filter" priority="-1">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Input"/> </provides>
<configfile name="input_filter.config"/>
<route>
<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="Input"> <child name="test-input_filter"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="test-input_filter" priority="-1">
<resource name="RAM" quantum="4M"/>
<provides> <service name="Input"/> </provides>
<config>
<message string="test merging of two input sources"/>
<filter_config>
<input label="ps2"/>
<input label="usb"/>
<output>
<merge>
<input name="ps2"/>
<input name="usb"/>
</merge>
</output>
</filter_config>
<sleep ms="250"/>
<usb> <press code="KEY_A"/> <release code="KEY_A"/> </usb>
<expect_press code="KEY_A"/>
<expect_release code="KEY_A"/>
<ps2> <press code="KEY_B"/> <release code="KEY_B"/> </ps2>
<expect_press code="KEY_B"/>
<expect_release code="KEY_B"/>
<message string="test key remapping"/>
<filter_config>
<input label="ps2"/>
<input label="usb"/>
<output>
<remap>
<merge>
<input name="usb"/>
<remap>
<input name="ps2"/>
<key name="KEY_A" to="KEY_C"/>
</remap>
</merge>
<key name="KEY_A" to="KEY_B"/>
</remap>
</output>
</filter_config>
<sleep ms="250"/>
<usb> <press code="KEY_A"/> <release code="KEY_A"/> </usb>
<expect_press code="KEY_B"/>
<expect_release code="KEY_B"/>
<ps2> <press code="KEY_A"/> <release code="KEY_A"/> </ps2>
<expect_press code="KEY_C"/>
<expect_release code="KEY_C"/>
<message string="test deferred reconfiguration"/>
<!-- hold key while reconfiguring the filter, the remapping applies -->
<usb> <press code="KEY_A"/> </usb>
<expect_press code="KEY_B"/>
<filter_config>
<input label="usb"/>
<output> <input name="usb"/> </output>
</filter_config>
<sleep ms="250"/>
<!-- expect the remapping rules to persist until all keys are released -->
<usb> <release code="KEY_A"/> </usb>
<expect_release code="KEY_B"/>
<!-- input_filter now requests a new input session from us -->
<sleep ms="100"/>
<!-- now the default configuration is expected to take effect -->
<usb> <press code="KEY_A"/> <release code="KEY_A"/> </usb>
<expect_press code="KEY_A"/>
<expect_release code="KEY_A"/>
<message string="test emission of characters"/>
<filter_config>
<input label="usb"/>
<output>
<chargen>
<input name="usb"/>
<mod1>
<key name="KEY_LEFTSHIFT"/> <key name="KEY_RIGHTSHIFT"/>
</mod1>
<map> <key name="KEY_A" char="a"/> </map>
<map mod1="yes"> <key name="KEY_A" char="A"/> </map>
</chargen>
</output>
</filter_config>
<sleep ms="250"/>
<usb>
<press code="KEY_A"/> <release code="KEY_A"/>
<press code="KEY_LEFTSHIFT"/>
<press code="KEY_A"/> <release code="KEY_A"/>
<release code="KEY_LEFTSHIFT"/>
<press code="KEY_RIGHTSHIFT"/>
<press code="KEY_A"/> <release code="KEY_A"/>
<release code="KEY_RIGHTSHIFT"/>
</usb>
<expect_press code="KEY_A"/>
<expect_char char="a"/>
<expect_release code="KEY_A"/>
<expect_press code="KEY_LEFTSHIFT"/>
<expect_press code="KEY_A"/>
<expect_char char="A"/>
<expect_release code="KEY_A"/>
<expect_release code="KEY_LEFTSHIFT"/>
<expect_press code="KEY_RIGHTSHIFT"/>
<expect_press code="KEY_A"/>
<expect_char char="A"/>
<expect_release code="KEY_A"/>
<expect_release code="KEY_RIGHTSHIFT"/>
<message string="test character repeat"/>
<filter_config>
<input label="usb"/>
<output>
<chargen>
<input name="usb"/>
<repeat delay_ms="600" rate_ms="200"/>
<map> <key name="KEY_A" char="a"/> </map>
</chargen>
</output>
</filter_config>
<sleep ms="500"/>
<usb> <press code="KEY_A"/> </usb>
<sleep ms="1500"/>
<usb> <release code="KEY_A"/> </usb>
<!-- periodic characters should stop now -->
<sleep ms="1000"/>
<usb> <press code="KEY_B"/> <release code="KEY_B"/> </usb>
<expect_press code="KEY_A"/>
<expect_char char="a"/> <!-- original press (0 ms) -->
<expect_char char="a"/> <!-- character after delay (600 ms) -->
<expect_char char="a"/> <!-- periodic character (800 ms) -->
<expect_char char="a"/> <!-- periodic character (1000 ms) -->
<expect_char char="a"/> <!-- periodic character (1200 ms) -->
<expect_char char="a"/> <!-- periodic character (1400 ms) -->
<expect_release code="KEY_A"/>
<expect_press code="KEY_B"/>
<expect_release code="KEY_B"/>
<message string="capslock handling"/>
<filter_config>
<input label="usb"/>
<output>
<chargen>
<remap>
<input name="usb"/>
<key name="KEY_CAPSLOCK" sticky="yes"/>
</remap>
<mod1> <key name="KEY_CAPSLOCK"/> </mod1>
<map> <key name="KEY_A" char="a"/> </map>
<map mod1="yes"> <key name="KEY_A" char="A"/> </map>
</chargen>
</output>
</filter_config>
<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>
<expect_press code="KEY_A"/> <expect_char char="a"/> <expect_release code="KEY_A"/>
<expect_press code="KEY_CAPSLOCK"/>
<expect_press code="KEY_A"/> <expect_char char="A"/> <expect_release code="KEY_A"/>
<expect_release code="KEY_CAPSLOCK"/>
<expect_press code="KEY_A"/> <expect_char char="a"/> <expect_release code="KEY_A"/>
<message string="survive deeply nested config"/>
<deep_filter_config depth="50"/>
<sleep ms="100"/>
<message string="survive attempt to include non-existing ROM"/>
<remap_include> </remap_include>
<chargen_include> </chargen_include>
<filter_config>
<input label="usb"/>
<output>
<chargen>
<input name="usb"/>
<include rom="nonexisting_include"/>
</chargen>
</output>
</filter_config>
<sleep ms="100"/>
<message string="detect top-level node mismatch in included ROM"/>
<filter_config>
<input label="usb"/>
<output>
<chargen>
<input name="usb"/>
<include rom="remap_include"/>
</chargen>
</output>
</filter_config>
<sleep ms="100"/>
<message string="survive include recursion"/>
<chargen_include> <include rom="chargen_include"/> </chargen_include>
<filter_config>
<input label="usb"/>
<output>
<chargen>
<input name="usb"/>
<include rom="chargen_include"/>
</chargen>
</output>
</filter_config>
<sleep ms="100"/>
<message string="include valid chargen rules"/>
<chargen_include>
<map> <key name="KEY_A" char="a"/> </map>
</chargen_include>
<filter_config>
<input label="usb"/>
<output>
<chargen>
<input name="usb"/>
<include rom="chargen_include"/>
</chargen>
</output>
</filter_config>
<sleep ms="100"/>
<usb> <press code="KEY_A"/> <release code="KEY_A"/> </usb>
<expect_press code="KEY_A"/> <expect_char char="a"/> <expect_release code="KEY_A"/>
<message string="update included chargen ROM"/>
<chargen_include>
<map> <key name="KEY_A" char="b"/> </map>
</chargen_include>
<sleep ms="100"/>
<usb> <press code="KEY_A"/> <release code="KEY_A"/> </usb>
<expect_press code="KEY_A"/> <expect_char char="b"/> <expect_release code="KEY_A"/>
</config>
<route>
<service name="Input"> <child name="input_filter"/> </service>
<service name="Report"> <child name="report_rom"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</config>}
install_config $config
#
# Boot modules
#
set boot_modules { core ld.lib.so init timer report_rom
input_filter test-input_filter }
build_boot_image $boot_modules
append qemu_args " -nographic "
run_genode_until {.*child "test-input_filter" exited with exit value 0.*} 60

View File

@ -0,0 +1,110 @@
This component transforms input events originating from multiple sources.
Configuration
-------------
An input-filter configuration consists of two parts, a declaration of
input sources ("Input" connections) that the component should request,
and the definition of a filter chain. Each input source is defined via
an '<input>' node with the name of the input source as 'name' attribute and
the session label as 'label' attribute. The latter can be used to route
several input sources to different components, i.e, input device drivers.
The filter chain is defined via one '<output>' node. It contains exactly
one of the following filters:
:<input name="..."/>:
Refers to the input source with the matching 'name'.
:<remap>:
Applies low-level key remapping to the events produced by another filter
that is embedded as a child node.
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.
:<merge>:
Merges the results of any number of filters that appear as child nodes.
:<chargen>:
Supplements the input-event stream of another filter with artificial
'CHARACTER' events by applying character mapping rules. The originating
filter is defined as a child node.
Character generator rules
-------------------------
The character-generator ('<chargen>') rules are defined via the following
sub nodes:
:<mod1>/<mod2>/<mod3>/<mod4>:
Defines which physical keys are interpreted as modifier keys. Usually,
'<mod1>' corresponds to shift, '<mod2>' to control, and '<mod3>' to altgr
(on German keyboards). Each modifier node may host any number of '<key>'
nodes with their corresponding 'name' attribute. For example:
! <mod1>
! <key name="KEY_LEFTSHIFT"/> <key name="KEY_RIGHTSHIFT"/>
! </mod1>
:<map mod1="..." mod2="..." mod3="..." mod4="...">:
A '<map>' node contains a list of keys that emit a specified character when
pressed. Any number of '<map>' nodes can be present. For each map node, the
attributes 'mod1' to 'mod4' denote the condition, under which it is
considered. Each 'mod' attribute has three possible values. If the attribute
is not present, the state of the modifier does not matter. If set to 'yes',
the modifier must be active. If set to 'no', the modifier must not be active.
Each '<map>' may contain any number of '<key>' subnodes. Each '<key>'
must have the key name as 'name' attribute. The to-be-emitted character
is defined by the attributes 'ascii', 'char', or 'b0/b1/b2/b3'. The
'ascii' attribute accepts an integer value between 0 and 127, the
'char' attribute accepts a single ASCII character, the 'b0/b1/b2/b3'
attributes define the individual bytes of an UTF-8 character.
:<repeat delay_ms="500" rate_ms="250">:
The '<repeat>' node defines the character-repeat delay and rate that
triggers the periodic emission of the last produced character while
the corresponding key is held.
:<include rom="...">:
The '<include>' node includes further content into the '<chargen>' node
and thereby allows the easy reuse of common rules. The included ROM must
have an '<chargen>' top-level node.
Additional features
-------------------
The input filter is able to respond to configuration updates as well as updates
of included ROM modules. However, a new configuration is applied only if the
input sources are in their idle state - that is, no key is pressed. This
ensures the consistency of the generated key events (for each press event there
must be a corresponding release event), on which clients of the input filter
may depend. However, this deferred reconfiguration can be overridden by setting
the 'force' attribute of the '<config>' node to 'yes'. If forced, the new
configuration is applied immediately.
Examples
--------
An automated test that exercises various corner cases of the input filter
can be found at _os/run/input_filter.run_. For a practical example of how
to use the input filter with the terminal, please refer to the
_gems/run/terminal_echo.run_ script.

View File

@ -0,0 +1,524 @@
/*
* \brief Input-event source that generates character events
* \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 General Public License version 2.
*/
#ifndef _INPUT_FILTER__CHARGEN_SOURCE_H_
#define _INPUT_FILTER__CHARGEN_SOURCE_H_
/* Genode includes */
#include <input/keycodes.h>
/* local includes */
#include <source.h>
#include <timer_accessor.h>
#include <include_accessor.h>
namespace Input_filter { class Chargen_source; }
class Input_filter::Chargen_source : public Source, Source::Sink
{
private:
Allocator &_alloc;
Timer_accessor &_timer_accessor;
Include_accessor &_include_accessor;
/*
* Modifier definitions
*/
struct Modifier
{
enum Id { MOD1 = 0, MOD2 = 1, MOD3 = 2, MOD4 = 3, UNDEFINED };
typedef String<8> Name;
Registry<Modifier>::Element _element;
Id const _id;
Input::Keycode const _code;
static Id id(Xml_node mod_node)
{
if (mod_node.type() == "mod1") return MOD1;
if (mod_node.type() == "mod2") return MOD2;
if (mod_node.type() == "mod3") return MOD3;
if (mod_node.type() == "mod4") return MOD4;
return UNDEFINED;
}
Modifier(Registry<Modifier> &registry, Id id, Input::Keycode code)
:
_element(registry, *this), _id(id), _code(code)
{ }
Input::Keycode code() const { return _code; }
Id id() const { return _id; }
};
Registry<Modifier> _modifiers;
/*
* Key rules for generating characters
*/
enum { NUM_MODIFIERS = 4 };
/**
* Cached state of modifiers, updated when a modifier key event occurs
*/
struct Modifier_map
{
struct State { bool enabled = false; } states[NUM_MODIFIERS];
} _mod_map;
/**
* State tracked per physical key
*/
struct Key
{
enum Type { DEFAULT, MODIFIER } type = DEFAULT;
enum State { RELEASED, PRESSED } state = RELEASED;
struct Rule
{
Registry<Rule>::Element _reg_elem;
/*
* Conditions that must be satisfied to let the rule take effect
*/
struct Conditions
{
struct Modifier
{
enum Constraint { PRESSED, RELEASED, DONT_CARE };
Constraint constraint = DONT_CARE;
bool match(Modifier_map::State state) const
{
if ((constraint == RELEASED && state.enabled) ||
(constraint == PRESSED && !state.enabled))
return false;
return true;
}
};
Modifier modifiers[NUM_MODIFIERS];
/**
* Return true if current modifier state fulfils conditions
*/
bool match(Modifier_map const &mod_map) const
{
for (unsigned i = 0; i < NUM_MODIFIERS; i++)
if (!modifiers[i].match(mod_map.states[i]))
return false;
return true;
}
unsigned num_modifier_constraints() const
{
unsigned cnt = 0;
for (unsigned i = 0; i < NUM_MODIFIERS; i++)
if (modifiers[i].constraint != Modifier::DONT_CARE)
cnt++;
return cnt;
}
};
Conditions const _conditions;
Input::Event::Utf8 const _character;
Rule(Registry<Rule> &registry,
Conditions conditions,
Input::Event::Utf8 character)
:
_reg_elem(registry, *this),
_conditions(conditions),
_character(character)
{ }
/**
* Return match score for the given modifier state
*
* \return 0 if rule mismatches,
* 1 if rule matches,
* 1+N if rule with N modifier constraints matches
*/
unsigned match_score(Modifier_map const &mod_map) const
{
if (!_conditions.match(mod_map))
return 0;
return 1 + _conditions.num_modifier_constraints();
}
Input::Event::Utf8 character() const { return _character; }
};
Registry<Rule> rules;
/**
* Call functor 'fn' with the 'Input::Event::Utf8' character
* defined for the best matching rule
*/
template <typename FN>
void apply_best_matching_rule(Modifier_map const &mod_map, FN const &fn) const
{
Input::Event::Utf8 best_match { 0 };
unsigned max_score = 0;
rules.for_each([&] (Rule const &rule) {
unsigned score = rule.match_score(mod_map);
if (score <= max_score)
return;
max_score = score;
best_match = rule.character();
});
if (max_score > 0)
fn(best_match);
}
};
/**
* Map of the states of the physical keys
*/
class Key_map
{
private:
Allocator &_alloc;
Key _keys[Input::KEY_MAX];
public:
Key_map(Allocator &alloc) : _alloc(alloc) { }
~Key_map()
{
for (unsigned i = 0; i < Input::KEY_MAX; i++)
_keys[i].rules.for_each([&] (Key::Rule &rule) {
destroy(_alloc, &rule); });
}
/**
* Return key object that belongs to the specified key code
*/
Key &key(Input::Keycode code)
{
if ((unsigned)code >= (unsigned)Input::KEY_MAX)
return _keys[Input::KEY_UNKNOWN];
return _keys[code];
};
/**
* Obtain modifier condition from map XML node
*/
static Key::Rule::Conditions::Modifier::Constraint
_map_mod_cond(Xml_node map, Modifier::Name const &mod_name)
{
if (!map.has_attribute(mod_name.string()))
return Key::Rule::Conditions::Modifier::DONT_CARE;
bool const pressed = map.attribute_value(mod_name.string(), false);
return pressed ? Key::Rule::Conditions::Modifier::PRESSED
: Key::Rule::Conditions::Modifier::RELEASED;
}
struct Missing_character_definition { };
/**
* Return UTF8 character defined in XML node attributes
*
* \throw Missing_character_definition
*/
static Input::Event::Utf8 _utf8_from_xml_node(Xml_node node)
{
if (node.has_attribute("ascii"))
return Input::Event::Utf8(node.attribute_value("ascii", 0UL));
if (node.has_attribute("char")) {
typedef String<2> Value;
Value value = node.attribute_value("char", Value());
unsigned char const ascii = value.string()[0];
if (ascii < 128)
return Input::Event::Utf8(ascii);
warning("char attribute with non-ascii character "
"'", value, "'");
throw Missing_character_definition();
}
if (node.has_attribute("b0")) {
unsigned char const b0 = node.attribute_value("b0", 0UL),
b1 = node.attribute_value("b1", 0UL),
b2 = node.attribute_value("b2", 0UL),
b3 = node.attribute_value("b3", 0UL);
return Input::Event::Utf8(b0, b1, b2, b3);
}
throw Missing_character_definition();
}
void import_map(Xml_node map)
{
/* obtain modifier conditions from map attributes */
Key::Rule::Conditions cond;
cond.modifiers[Modifier::MOD1].constraint = _map_mod_cond(map, "mod1");
cond.modifiers[Modifier::MOD2].constraint = _map_mod_cond(map, "mod2");
cond.modifiers[Modifier::MOD3].constraint = _map_mod_cond(map, "mod3");
cond.modifiers[Modifier::MOD4].constraint = _map_mod_cond(map, "mod4");
/* add a rule for each <key> sub node */
map.for_each_sub_node("key", [&] (Xml_node key_node) {
Key_name const name = key_node.attribute_value("name", Key_name());
Input::Keycode const code = key_code_by_name(name);
new (_alloc) Key::Rule(key(code).rules, cond,
_utf8_from_xml_node(key_node));
});
}
} _key_map;
void _update_modifier_state()
{
/* reset */
_mod_map = Modifier_map();
/* apply state of all modifier keys to modifier map */
_modifiers.for_each([&] (Modifier const &mod) {
_mod_map.states[mod.id()].enabled |=
_key_map.key(mod.code()).state; });
}
Owner _owner;
Source::Sink &_destination;
/**
* Mechanism for periodically repeating the last character
*/
struct Char_repeater
{
Source::Sink &_destination;
Genode::Timer &_timer;
Time_source::Microseconds const _delay;
Time_source::Microseconds const _rate;
Input::Event::Utf8 _curr_character { 0 };
enum State { IDLE, REPEAT } _state;
void _handle_timeout(Time_source::Microseconds)
{
if (_state == REPEAT) {
_destination.submit_event(Input::Event(_curr_character));
_timeout.start(_rate);
}
}
One_shot_timeout<Char_repeater> _timeout {
_timer, *this, &Char_repeater::_handle_timeout };
Char_repeater(Source::Sink &destination, Genode::Timer &timer,
Xml_node node)
:
_destination(destination), _timer(timer),
_delay(node.attribute_value("delay_ms", 0UL)*1000),
_rate (node.attribute_value("rate_ms", 0UL)*1000)
{ }
void schedule_repeat(Input::Event::Utf8 character)
{
_curr_character = character;
_state = REPEAT;
_timeout.start(_delay);
}
void cancel()
{
_curr_character = Input::Event::Utf8(0);
_state = IDLE;
}
};
Constructible<Char_repeater> _char_repeater;
/**
* Sink interface (called from our child node)
*/
void submit_event(Input::Event const &event) override
{
using Input::Event;
/* forward event as is */
_destination.submit_event(event);
/* don't do anything for non-press/release events */
if (event.type() != Event::PRESS && event.type() != Event::RELEASE)
return;
Key &key = _key_map.key(event.keycode());
/* track key state */
if (event.type() == Event::PRESS) key.state = Key::PRESSED;
if (event.type() == Event::RELEASE) key.state = Key::RELEASED;
if (key.type == Key::MODIFIER) {
_update_modifier_state();
/* never emit a character when pressing a modifier key */
return;
}
if (event.type() == Event::PRESS) {
key.apply_best_matching_rule(_mod_map, [&] (Event::Utf8 utf8) {
_destination.submit_event(Event(utf8));
if (_char_repeater.constructed())
_char_repeater->schedule_repeat(utf8);
});
}
if (event.type() == Event::RELEASE)
if (_char_repeater.constructed())
_char_repeater->cancel();
}
Source &_source;
void _apply_config(Xml_node const config, unsigned const max_recursion = 4)
{
config.for_each_sub_node([&] (Xml_node node) {
_apply_sub_node(node, max_recursion); });
}
void _apply_sub_node(Xml_node const node, unsigned const max_recursion)
{
if (max_recursion == 0) {
error("too deeply nested includes");
throw Invalid_config();
}
/*
* Handle includes
*/
if (node.type() == "include") {
try {
Include_accessor::Name const rom =
node.attribute_value("rom", Include_accessor::Name());
_include_accessor.apply_include(rom, name(), [&] (Xml_node inc) {
_apply_config(inc, max_recursion - 1); });
return;
}
catch (Include_accessor::Include_unavailable) {
throw Invalid_config(); }
}
/*
* Handle map nodes
*/
if (node.type() == "map") {
_key_map.import_map(node);
return;
}
/*
* Instantiate character repeater on demand
*/
if (node.type() == "repeat") {
_char_repeater.construct(_destination,
_timer_accessor.timer(), node);
return;
}
/*
* Handle modifier-definition nodes
*/
Modifier::Id const id = Modifier::id(node);
if (id == Modifier::UNDEFINED)
return;
node.for_each_sub_node("key", [&] (Xml_node key_node) {
Key_name const name = key_node.attribute_value("name", Key_name());
Input::Keycode const key = key_code_by_name(name);
new (_alloc) Modifier(_modifiers, id, key);
});
}
public:
static char const *name() { return "chargen"; }
Chargen_source(Owner &owner,
Xml_node config,
Source::Sink &destination,
Source::Factory &factory,
Allocator &alloc,
Timer_accessor &timer_accessor,
Include_accessor &include_accessor)
:
Source(owner),
_alloc(alloc),
_timer_accessor(timer_accessor),
_include_accessor(include_accessor),
_key_map(_alloc),
_owner(factory),
_destination(destination),
_source(factory.create_source(_owner, input_sub_node(config), *this))
{
_apply_config(config);
/* assign key types in key map */
_modifiers.for_each([&] (Modifier const &mod) {
_key_map.key(mod.code()).type = Key::MODIFIER; });
}
~Chargen_source()
{
_modifiers.for_each([&] (Modifier &mod) { destroy(_alloc, &mod); });
}
void generate() override { _source.generate(); }
};
#endif /* _INPUT_FILTER__CHARGEN_SOURCE_H_ */

View File

@ -0,0 +1,95 @@
/*
* \brief Connection for incoming input events
* \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 General Public License version 2.
*/
#ifndef _INPUT_FILTER__CONNECTION_H_
#define _INPUT_FILTER__CONNECTION_H_
/* Genode includes */
#include <input_session/connection.h>
#include <base/session_label.h>
/* local includes */
#include <types.h>
namespace Input_filter { struct Input_connection; }
class Input_filter::Input_connection
{
public:
struct Avail_handler { virtual void handle_input_avail() = 0; };
private:
Session_label const _label;
Input::Connection _connection;
Attached_dataspace _events_ds;
Avail_handler &_avail_handler;
unsigned _key_cnt = 0;
Signal_handler<Input_connection> _input_handler;
void _handle_input() { _avail_handler.handle_input_avail(); }
size_t _num_ev = 0;
size_t const _max_events = _events_ds.size() / sizeof(Input::Event);
public:
static char const *name() { return "input"; }
Input_connection(Env &env, Session_label const &label,
Avail_handler &avail_handler, Allocator &alloc)
:
_label(label),
_connection(env, label.string()),
_events_ds(env.rm(), _connection.dataspace()),
_avail_handler(avail_handler),
_input_handler(env.ep(), *this, &Input_connection::_handle_input)
{
_connection.sigh(_input_handler);
}
Session_label label() const { return _label; }
template <typename FUNC>
void for_each_event(FUNC const &func) const
{
Input::Event const *event_ptr = _events_ds.local_addr<Input::Event const>();
for (size_t i = 0; i < _num_ev; i++)
func(*event_ptr++);
}
void flush()
{
_num_ev = min(_max_events, (size_t)_connection.flush());
auto update_key_cnt = [&] (Input::Event const &event)
{
if (event.type() == Input::Event::PRESS) _key_cnt++;
if (event.type() == Input::Event::RELEASE) _key_cnt--;
};
for_each_event(update_key_cnt);
}
bool idle() const { return _key_cnt == 0; }
bool pending() const { return _num_ev > 0; }
};
#endif /* _INPUT_FILTER__CONNECTION_H_ */

View File

@ -0,0 +1,164 @@
<chargen>
<map>
<key name="KEY_ESC" ascii="27"/>
<key name="KEY_1" char="1"/>
<key name="KEY_2" char="2"/>
<key name="KEY_3" char="3"/>
<key name="KEY_4" char="4"/>
<key name="KEY_5" char="5"/>
<key name="KEY_6" char="6"/>
<key name="KEY_7" char="7"/>
<key name="KEY_8" char="8"/>
<key name="KEY_9" char="9"/>
<key name="KEY_0" char="0"/>
<key name="KEY_MINUS" b0="195" b1="159"/> <!-- 'ß' -->
<key name="KEY_EQUAL" ascii="39"/> <!-- '´' -->
<key name="KEY_BACKSPACE" ascii="8"/>
<key name="KEY_TAB" ascii="9"/>
<key name="KEY_Q" char="q"/>
<key name="KEY_W" char="w"/>
<key name="KEY_E" char="e"/>
<key name="KEY_R" char="r"/>
<key name="KEY_T" char="t"/>
<key name="KEY_Y" char="z"/>
<key name="KEY_U" char="u"/>
<key name="KEY_I" char="i"/>
<key name="KEY_O" char="o"/>
<key name="KEY_P" char="p"/>
<key name="KEY_LEFTBRACE" b0="195" b1="188"/> <!-- 'ü' -->
<key name="KEY_RIGHTBRACE" char="+"/>
<key name="KEY_ENTER" ascii="10"/>
<key name="KEY_A" char="a"/>
<key name="KEY_S" char="s"/>
<key name="KEY_D" char="d"/>
<key name="KEY_F" char="f"/>
<key name="KEY_G" char="g"/>
<key name="KEY_H" char="h"/>
<key name="KEY_J" char="j"/>
<key name="KEY_K" char="k"/>
<key name="KEY_L" char="l"/>
<key name="KEY_SEMICOLON" b0="195" b1="182"/> <!-- 'ö' -->
<key name="KEY_APOSTROPHE" b0="195" b1="164"/> <!-- 'ä' -->
<key name="KEY_GRAVE" char="^"/>
<key name="KEY_BACKSLASH" char="#"/>
<key name="KEY_102ND" ascii="60"/> <!-- '<' -->
<key name="KEY_Z" char="y"/>
<key name="KEY_X" char="x"/>
<key name="KEY_C" char="c"/>
<key name="KEY_V" char="v"/>
<key name="KEY_B" char="b"/>
<key name="KEY_N" char="n"/>
<key name="KEY_M" char="m"/>
<key name="KEY_COMMA" char=","/>
<key name="KEY_DOT" char="."/>
<key name="KEY_SLASH" char="-"/>
<key name="KEY_SPACE" char=" "/>
<key name="KEY_KP7" char="7"/>
<key name="KEY_KP8" char="8"/>
<key name="KEY_KP9" char="9"/>
<key name="KEY_KPMINUS" char="-"/>
<key name="KEY_KP4" char="4"/>
<key name="KEY_KP5" char="5"/>
<key name="KEY_KP6" char="6"/>
<key name="KEY_KPPLUS" char="+"/>
<key name="KEY_KP1" char="1"/>
<key name="KEY_KP2" char="2"/>
<key name="KEY_KP3" char="3"/>
<key name="KEY_KP0" char="0"/>
<key name="KEY_KPDOT" char="."/>
<key name="KEY_KPENTER" ascii="10"/>
<key name="KEY_KPSLASH" char="/"/>
</map>
<map mod1="yes">
<key name="KEY_1" char="!"/>
<key name="KEY_2" ascii="34"/> <!-- '"' -->
<key name="KEY_3" b0="194" b1="167"/> <!-- '§' -->
<key name="KEY_4" char="$"/>
<key name="KEY_5" char="%"/>
<key name="KEY_6" ascii="38"/> <!-- '&' -->
<key name="KEY_7" char="/"/> <!-- '/' -->
<key name="KEY_8" char="("/>
<key name="KEY_9" char=")"/>
<key name="KEY_0" char="="/>
<key name="KEY_MINUS" char="?"/>
<key name="KEY_EQUAL" char="`"/>
<key name="KEY_Q" char="Q"/>
<key name="KEY_W" char="W"/>
<key name="KEY_E" char="E"/>
<key name="KEY_R" char="R"/>
<key name="KEY_T" char="T"/>
<key name="KEY_Y" char="Z"/>
<key name="KEY_U" char="U"/>
<key name="KEY_I" char="I"/>
<key name="KEY_O" char="O"/>
<key name="KEY_P" char="P"/>
<key name="KEY_LEFTBRACE" b0="195" b1="156"/> <!-- 'Ü' -->
<key name="KEY_RIGHTBRACE" char="*"/>
<key name="KEY_A" char="A"/>
<key name="KEY_S" char="S"/>
<key name="KEY_D" char="D"/>
<key name="KEY_F" char="F"/>
<key name="KEY_G" char="G"/>
<key name="KEY_H" char="H"/>
<key name="KEY_J" char="J"/>
<key name="KEY_K" char="K"/>
<key name="KEY_L" char="L"/>
<key name="KEY_SEMICOLON" b0="195" b1="150"/> <!-- 'Ö' -->
<key name="KEY_APOSTROPHE" b0="195" b1="132"/> <!-- 'Ä' -->
<key name="KEY_GRAVE" b0="194" b1="176"/> <!-- '°' -->
<key name="KEY_BACKSLASH" char="'"/>
<key name="KEY_102ND" ascii="62"/> <!-- '>' -->
<key name="KEY_Z" char="Y"/>
<key name="KEY_X" char="X"/>
<key name="KEY_C" char="C"/>
<key name="KEY_V" char="V"/>
<key name="KEY_B" char="B"/>
<key name="KEY_N" char="N"/>
<key name="KEY_M" char="M"/>
<key name="KEY_COMMA" char=";"/>
<key name="KEY_DOT" char=":"/>
<key name="KEY_SLASH" char="_"/>
</map>
<map mod2="yes">
<key name="KEY_A" ascii="1"/>
<key name="KEY_B" ascii="2"/>
<key name="KEY_C" ascii="3"/>
<key name="KEY_D" ascii="4"/>
<key name="KEY_E" ascii="5"/>
<key name="KEY_F" ascii="6"/>
<key name="KEY_G" ascii="7"/>
<key name="KEY_H" ascii="8"/>
<key name="KEY_I" ascii="9"/>
<key name="KEY_J" ascii="10"/>
<key name="KEY_K" ascii="11"/>
<key name="KEY_L" ascii="12"/>
<key name="KEY_M" ascii="13"/>
<key name="KEY_N" ascii="14"/>
<key name="KEY_O" ascii="15"/>
<key name="KEY_P" ascii="16"/>
<key name="KEY_Q" ascii="17"/>
<key name="KEY_R" ascii="18"/>
<key name="KEY_S" ascii="19"/>
<key name="KEY_T" ascii="20"/>
<key name="KEY_U" ascii="21"/>
<key name="KEY_V" ascii="22"/>
<key name="KEY_W" ascii="23"/>
<key name="KEY_X" ascii="24"/>
<key name="KEY_Y" ascii="26"/>
<key name="KEY_Z" ascii="25"/>
</map>
<map mod3="yes">
<key name="KEY_2" b0="194" b1="178"/> <!-- superscript two -->
<key name="KEY_3" b0="194" b1="179"/> <!-- superscript three -->
<key name="KEY_7" char="{"/>
<key name="KEY_8" char="["/>
<key name="KEY_9" char="]"/>
<key name="KEY_0" char="}"/>
<key name="KEY_E" b0="226" b1="130" b2="172"/> <!-- euro sign -->
<key name="KEY_MINUS" ascii="92"/> <!-- '\' -->
<key name="KEY_Q" char="@"/>
<key name="KEY_M" b0="194" b1="181"/> <!-- small micro -->
<key name="KEY_102ND" char="|"/>
<key name="KEY_RIGHTBRACE" char="~"/>
</map>
</chargen>

View File

@ -0,0 +1,148 @@
<chargen>
<map>
<key name="KEY_ESC" ascii="27"/>
<key name="KEY_1" char="1"/>
<key name="KEY_2" char="2"/>
<key name="KEY_3" char="3"/>
<key name="KEY_4" char="4"/>
<key name="KEY_5" char="5"/>
<key name="KEY_6" char="6"/>
<key name="KEY_7" char="7"/>
<key name="KEY_8" char="8"/>
<key name="KEY_9" char="9"/>
<key name="KEY_0" char="0"/>
<key name="KEY_MINUS" char="-"/>
<key name="KEY_EQUAL" char="="/>
<key name="KEY_BACKSPACE" ascii="8"/>
<key name="KEY_TAB" ascii="9"/>
<key name="KEY_Q" char="q"/>
<key name="KEY_W" char="w"/>
<key name="KEY_E" char="e"/>
<key name="KEY_R" char="r"/>
<key name="KEY_T" char="t"/>
<key name="KEY_Y" char="y"/>
<key name="KEY_U" char="u"/>
<key name="KEY_I" char="i"/>
<key name="KEY_O" char="o"/>
<key name="KEY_P" char="p"/>
<key name="KEY_LEFTBRACE" char="["/>
<key name="KEY_RIGHTBRACE" char="]"/>
<key name="KEY_ENTER" ascii="10"/>
<key name="KEY_A" char="a"/>
<key name="KEY_S" char="s"/>
<key name="KEY_D" char="d"/>
<key name="KEY_F" char="f"/>
<key name="KEY_G" char="g"/>
<key name="KEY_H" char="h"/>
<key name="KEY_J" char="j"/>
<key name="KEY_K" char="k"/>
<key name="KEY_L" char="l"/>
<key name="KEY_SEMICOLON" char=";"/>
<key name="KEY_APOSTROPHE" char="'"/>
<key name="KEY_GRAVE" char="`"/>
<key name="KEY_BACKSLASH" ascii="92"/> <!-- '\' -->
<key name="KEY_Z" char="z"/>
<key name="KEY_X" char="x"/>
<key name="KEY_C" char="c"/>
<key name="KEY_V" char="v"/>
<key name="KEY_B" char="b"/>
<key name="KEY_N" char="n"/>
<key name="KEY_M" char="m"/>
<key name="KEY_COMMA" char=","/>
<key name="KEY_DOT" char="."/>
<key name="KEY_SLASH" char="/"/>
<key name="KEY_SPACE" char=" "/>
<key name="KEY_KP7" char="7"/>
<key name="KEY_KP8" char="8"/>
<key name="KEY_KP9" char="9"/>
<key name="KEY_KPMINUS" char="-"/>
<key name="KEY_KP4" char="4"/>
<key name="KEY_KP5" char="5"/>
<key name="KEY_KP6" char="6"/>
<key name="KEY_KPPLUS" char="+"/>
<key name="KEY_KP1" char="1"/>
<key name="KEY_KP2" char="2"/>
<key name="KEY_KP3" char="3"/>
<key name="KEY_KP0" char="0"/>
<key name="KEY_KPDOT" char="."/>
<key name="KEY_KPENTER" ascii="10"/>
<key name="KEY_KPSLASH" char="/"/>
</map>
<map mod1="yes">
<key name="KEY_1" char="!"/>
<key name="KEY_2" char="@"/>
<key name="KEY_3" char="#"/>
<key name="KEY_4" char="$"/>
<key name="KEY_5" char="%"/>
<key name="KEY_6" char="^"/>
<key name="KEY_7" ascii="38"/> <!-- '&' -->
<key name="KEY_8" char="*"/>
<key name="KEY_9" char="("/>
<key name="KEY_0" char=")"/>
<key name="KEY_MINUS" char="_"/>
<key name="KEY_EQUAL" char="+"/>
<key name="KEY_Q" char="Q"/>
<key name="KEY_W" char="W"/>
<key name="KEY_E" char="E"/>
<key name="KEY_R" char="R"/>
<key name="KEY_T" char="T"/>
<key name="KEY_Y" char="Y"/>
<key name="KEY_U" char="U"/>
<key name="KEY_I" char="I"/>
<key name="KEY_O" char="O"/>
<key name="KEY_P" char="P"/>
<key name="KEY_LEFTBRACE" char="{"/>
<key name="KEY_RIGHTBRACE" char="}"/>
<key name="KEY_A" char="A"/>
<key name="KEY_S" char="S"/>
<key name="KEY_D" char="D"/>
<key name="KEY_F" char="F"/>
<key name="KEY_G" char="G"/>
<key name="KEY_H" char="H"/>
<key name="KEY_J" char="J"/>
<key name="KEY_K" char="K"/>
<key name="KEY_L" char="L"/>
<key name="KEY_SEMICOLON" char=":"/>
<key name="KEY_APOSTROPHE" ascii="34"/> <!-- '"' -->
<key name="KEY_GRAVE" char="~"/>
<key name="KEY_BACKSLASH" char="|"/>
<key name="KEY_Z" char="Z"/>
<key name="KEY_X" char="X"/>
<key name="KEY_C" char="C"/>
<key name="KEY_V" char="V"/>
<key name="KEY_B" char="B"/>
<key name="KEY_N" char="N"/>
<key name="KEY_M" char="M"/>
<key name="KEY_COMMA" ascii="60"/> <!-- '<' -->
<key name="KEY_DOT" ascii="62"/> <!-- '>' -->
<key name="KEY_SLASH" char="?"/>
</map>
<map mod2="yes">
<key name="KEY_A" ascii="1"/>
<key name="KEY_B" ascii="2"/>
<key name="KEY_C" ascii="3"/>
<key name="KEY_D" ascii="4"/>
<key name="KEY_E" ascii="5"/>
<key name="KEY_F" ascii="6"/>
<key name="KEY_G" ascii="7"/>
<key name="KEY_H" ascii="8"/>
<key name="KEY_I" ascii="9"/>
<key name="KEY_J" ascii="10"/>
<key name="KEY_K" ascii="11"/>
<key name="KEY_L" ascii="12"/>
<key name="KEY_M" ascii="13"/>
<key name="KEY_N" ascii="14"/>
<key name="KEY_O" ascii="15"/>
<key name="KEY_P" ascii="16"/>
<key name="KEY_Q" ascii="17"/>
<key name="KEY_R" ascii="18"/>
<key name="KEY_S" ascii="19"/>
<key name="KEY_T" ascii="20"/>
<key name="KEY_U" ascii="21"/>
<key name="KEY_V" ascii="22"/>
<key name="KEY_W" ascii="23"/>
<key name="KEY_X" ascii="24"/>
<key name="KEY_Y" ascii="25"/>
<key name="KEY_Z" ascii="26"/>
</map>
</chargen>

View File

@ -0,0 +1,67 @@
/*
* \brief Interface for accessing a configuration snippets
* \author Norman Feske
* \date 2017-02-10
*/
/*
* Copyright (C) 2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _INPUT_FILTER__INCLUDE_ACCESSOR_H_
#define _INPUT_FILTER__INCLUDE_ACCESSOR_H_
/* Genode includes */
#include <util/xml_node.h>
/* local includes */
#include <types.h>
namespace Input_filter { struct Include_accessor; }
class Input_filter::Include_accessor
{
public:
typedef String<64> Name;
typedef String<32> Type;
struct Include_unavailable : Exception { };
protected:
struct Functor { virtual void apply(Xml_node node) const = 0; };
/*
* \throw Include_unavailable
*/
virtual void _apply_include(Name const &name, Type const &type, Functor const &) = 0;
public:
/**
* Call functor 'fn' with the 'Xml_node' of the named include
*
* \throw Include_unavailable
*/
template <typename FN>
void apply_include(Name const &name, Type const &type, FN const &fn)
{
struct Functor : Include_accessor::Functor
{
FN const &fn;
Functor(FN const &fn) : fn(fn) { }
void apply(Xml_node node) const override { fn(node); }
} _functor(fn);
_apply_include(name, type, _functor);
}
};
#endif /* _INPUT_FILTER__INCLUDE_ACCESSOR_H_ */

View File

@ -0,0 +1,47 @@
/*
* \brief Input-event source that obtains events from input connection
* \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 General Public License version 2.
*/
#ifndef _INPUT_FILTER__INPUT_SOURCE_H_
#define _INPUT_FILTER__INPUT_SOURCE_H_
/* local includes */
#include <source.h>
#include <connection.h>
namespace Input_filter { class Input_source; }
class Input_filter::Input_source : public Source
{
private:
Input_connection &_connection;
Sink &_destination;
public:
static char const *name() { return "input"; }
Input_source(Owner &owner, Input_connection &connection, Sink &destination)
:
Source(owner), _connection(connection), _destination(destination)
{ }
void generate() override
{
_connection.for_each_event([&] (Input::Event const &event) {
_destination.submit_event(event); });
}
};
#endif /* _INPUT_FILTER__INPUT_SOURCE_H_ */

View File

@ -0,0 +1,43 @@
/*
* \brief Utility to convert key names into their corresponding key codes
* \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 General Public License version 2.
*/
#ifndef _INPUT_FILTER__KEY_CODE_BY_NAME_H_
#define _INPUT_FILTER__KEY_CODE_BY_NAME_H_
/* Genode includes */
#include <base/exception.h>
#include <input/keycodes.h>
namespace Input_filter {
struct Unknown_key : Genode::Exception { };
typedef Genode::String<20> Key_name;
/*
* \throw Unknown_key
*/
Input::Keycode key_code_by_name(Key_name const &name)
{
for (unsigned i = 0; i < Input::KEY_MAX; i++) {
Input::Keycode const code = Input::Keycode(i);
if (name == Input::key_name(code))
return code;
}
error("unknown key: ", name);
throw Unknown_key();
}
}
#endif /* _INPUT_FILTER__KEY_CODE_BY_NAME_H_ */

View File

@ -0,0 +1,436 @@
/*
* \brief Input-event 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 General Public License version 2.
*/
/* Genode includes */
#include <input/component.h>
#include <os/static_root.h>
#include <base/component.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <timer_session/connection.h>
/* local includes */
#include <input_source.h>
#include <remap_source.h>
#include <merge_source.h>
#include <chargen_source.h>
namespace Input_filter { struct Main; }
struct Input_filter::Main : Input_connection::Avail_handler,
Source::Factory
{
Env &_env;
Attached_rom_dataspace _config { _env, "config" };
Heap _heap { _env.ram(), _env.rm() };
Registry<Registered<Input_connection> > _input_connections;
typedef String<Session_label::capacity()> Label;
/*
* Mechanism to construct a 'Timer' on demand
*
* By lazily constructing the timer, the input-filter does not depend
* on a timer service unless its configuration defines time-related
* filtering operations like key repeat.
*/
struct Timer_accessor : Input_filter::Timer_accessor
{
struct Lazy
{
Timer::Connection connection;
Genode::Timer timer;
Lazy(Env &env) : connection(env), timer(connection, env.ep()) { }
};
Env &_env;
Constructible<Lazy> lazy;
Timer_accessor(Env &env) : _env(env) { }
/**
* Timer_accessor interface
*/
Genode::Timer &timer() override
{
if (!lazy.constructed())
lazy.construct(_env);
return lazy->timer;
}
} _timer_accessor { _env };
/**
* Pool of configuration include snippets, obtained as ROM modules
*/
struct Include_accessor : Input_filter::Include_accessor
{
struct Rom
{
typedef Include_accessor::Name Name;
Registry<Rom>::Element _reg_elem;
Name const _name;
Attached_rom_dataspace _dataspace;
Signal_context_capability _reconfig_sigh;
void _handle_rom_update()
{
_dataspace.update();
/* trigger reconfiguration */
Signal_transmitter(_reconfig_sigh).submit();
}
Signal_handler<Rom> _rom_update_handler;
Rom(Registry<Rom> &registry, Env &env,
Name const &name, Type const &type,
Signal_context_capability reconfig_sigh)
:
_reg_elem(registry, *this), _name(name),
_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);
}
bool has_name(Name const &name) const { return _name == name; }
/**
* Return ROM content as XML
*
* \throw Include_unavailable
*/
Xml_node xml(Include_accessor::Type const &type) const
{
Xml_node const node = _dataspace.xml();
if (node.type() == type)
return node;
error("unexpected <", node.type(), "> node " "in included "
"ROM \"", _name, "\", expected, <", type, "> node");
throw Include_unavailable();
}
};
Env &_env;
Allocator &_alloc;
Signal_context_capability _sigh;
Registry<Rom> _registry;
/**
* Return true if registry contains an include with the given name
*/
bool _exists(Rom::Name const &name)
{
bool exists = false;
_registry.for_each([&] (Rom const &rom) {
if (rom.has_name(name))
exists = true; });
return exists;
}
/**
* Constructor
*
* \param sigh signal handler that responds to new ROM versions
*/
Include_accessor(Env &env, Allocator &alloc, Signal_context_capability sigh)
:
_env(env), _alloc(alloc), _sigh(sigh)
{ }
~Include_accessor()
{
_registry.for_each([&] (Rom &rom) { destroy(_alloc, &rom); });
}
void _apply_include(Name const &name, Type const &type, Functor const &fn) override
{
/* populate registry on demand */
if (!_exists(name)) {
try { new (_alloc) Rom(_registry, _env, name, type, _sigh); }
catch (...) {
error("include \"", name, "\" unavailable");
throw Include_unavailable();
}
}
/* call 'fn' with the XML content of the named include */
Rom const *matching_rom = nullptr;
_registry.for_each([&] (Rom const &rom) {
if (rom.has_name(name))
matching_rom = &rom; });
/* this condition should never occur */
if (!matching_rom)
throw Include_unavailable();
fn.apply(matching_rom->xml(type));
}
};
/**
* Maximum nesting depth of input sources, for limiting the stack usage
*/
unsigned _create_source_max_nesting_level = 16;
/**
* Source::Factory interface
*
* \throw Source::Invalid_config
*/
Source &create_source(Source::Owner &owner, Xml_node node, Source::Sink &sink) override
{
/*
* Guard for the protection against too deep recursions while
* processing the configuration.
*/
struct Nesting_level_guard
{
unsigned &level;
Nesting_level_guard(unsigned &level) : level(level)
{
if (level == 0) {
error("too many nested input sources");
throw Source::Invalid_config();
}
level--;
}
~Nesting_level_guard() { level++; }
} nesting_level_guard { _create_source_max_nesting_level };
/* return input source with the matching name */
if (node.type() == Input_source::name()) {
Label const label = node.attribute_value("name", Label());
Input_connection *match = nullptr;
_input_connections.for_each([&] (Input_connection &connection) {
if (connection.label() == label)
match = &connection; });
if (match)
return *new (_heap) Input_source(owner, *match, sink);
error("input named '", label, "' does not exist");
throw Source::Invalid_config();
}
/* create regular filter */
if (node.type() == Remap_source::name())
return *new (_heap) Remap_source(owner, node, sink, *this);
if (node.type() == Merge_source::name())
return *new (_heap) Merge_source(owner, node, sink, *this);
if (node.type() == Chargen_source::name())
return *new (_heap) Chargen_source(owner, node, sink, *this, _heap,
_timer_accessor, _include_accessor);
error("unknown <", node.type(), "> input-source node type");
throw Source::Invalid_config();
}
/**
* Source::Factory interface
*/
void destroy_source(Source &source) override { destroy(_heap, &source); }
/*
* Flag used to defer configuration updates until all input sources are
* in their default state.
*/
bool _config_update_pending = false;
/**
* Return true if all input sources are in their default state
*/
bool _input_connections_idle() const
{
bool idle = true;
_input_connections.for_each([&] (Input_connection const &connection) {
if (!connection.idle())
idle = false; });
return idle;
}
struct Output
{
Source::Owner _owner;
Source &_top_level;
/**
* Constructor
*
* \throw Source::Invalid_config
* \throw Genode::Out_of_memory
*/
Output(Xml_node output, Source::Sink &sink, Source::Factory &factory)
:
_owner(factory),
_top_level(factory.create_source(_owner, Source::input_sub_node(output), sink))
{ }
void generate() { _top_level.generate(); }
};
Constructible<Output> _output;
/*
* Input session provided to our client
*/
Input::Session_component _input_session { _env, _env.ram() };
/* process events */
struct Final_sink : Source::Sink
{
Input::Session_component &_input_session;
Final_sink(Input::Session_component &input_session)
: _input_session(input_session) { }
void submit_event(Input::Event const &event) override {
_input_session.submit(event); }
} _final_sink { _input_session };
/*
* Input_connection::Avail_handler
*/
void handle_input_avail() override
{
for (;;) {
/* fetch events in input sources */
_input_connections.for_each([&] (Input_connection &connection) {
connection.flush(); });
bool pending = false;
_input_connections.for_each([&] (Input_connection &connection) {
pending |= connection.pending(); });
if (pending && _output.constructed())
_output->generate();
if (_config_update_pending && _input_connections_idle())
Signal_transmitter(_config_handler).submit();
/* stop if no events are pending */
if (!pending)
break;
}
}
Static_root<Input::Session> _input_root { _env.ep().manage(_input_session) };
void _handle_config()
{
_config.update();
bool const force = _config.xml().attribute_value("force", false);
bool const idle = _input_connections_idle();
/* defer reconfiguration until all sources are idle */
if (!idle && !force) {
_config_update_pending = true;
return;
}
if (!idle)
warning("force reconfiguration while input state is not idle");
_apply_config();
}
void _apply_config()
{
_input_connections.for_each([&] (Registered<Input_connection> &conn) {
destroy(_heap, &conn); });
_config.xml().for_each_sub_node("input", [&] (Xml_node input_node) {
try {
Label const label =
input_node.attribute_value("label", Label());
try {
Input_connection &conn = *new (_heap)
Registered<Input_connection>(_input_connections, _env,
label, *this, _heap);
} catch (Genode::Parent::Service_denied) {
error("parent denied input source '", label, "'");
}
} catch (Xml_node::Nonexistent_attribute) {
error("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) {
error("invalid <output> configuration");
} catch (Allocator::Out_of_memory) {
error("out of memory while constructing filter chain"); }
_config_update_pending = false;
}
Signal_handler<Main> _config_handler
{ _env.ep(), *this, &Main::_handle_config };
Include_accessor _include_accessor { _env, _heap, _config_handler };
/**
* Constructor
*/
Main(Genode::Env &env) : _env(env)
{
_input_session.event_queue().enabled(true);
_config.sigh(_config_handler);
/*
* Apply initial configuration
*/
_apply_config();
/*
* Announce service
*/
_env.parent().announce(_env.ep().manage(_input_root));
}
};
void Component::construct(Genode::Env &env) { static Input_filter::Main inst(env); }

View File

@ -0,0 +1,49 @@
/*
* \brief Input-event source that merges other sources
* \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 General Public License version 2.
*/
#ifndef _INPUT_FILTER__MERGE_SOURCE_H_
#define _INPUT_FILTER__MERGE_SOURCE_H_
/* local includes */
#include <source.h>
namespace Input_filter { class Merge_source; }
class Input_filter::Merge_source : public Source
{
private:
Owner _owner;
public:
static char const *name() { return "merge"; }
Merge_source(Owner &owner, Xml_node config, Sink &destination,
Source::Factory &factory)
:
Source(owner), _owner(factory)
{
config.for_each_sub_node([&] (Xml_node node) {
if (input_node(node))
factory.create_source(_owner, node, destination); });
}
void generate() override
{
_owner.for_each([&] (Source &source) { source.generate(); });
}
};
#endif /* _INPUT_FILTER__REMAP_SOURCE_H_ */

View File

@ -0,0 +1,132 @@
/*
* \brief Input-event source that remaps keys from another source
* \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 General Public License version 2.
*/
#ifndef _INPUT_FILTER__REMAP_SOURCE_H_
#define _INPUT_FILTER__REMAP_SOURCE_H_
/* Genode includes */
#include <input/keycodes.h>
/* local includes */
#include <source.h>
#include <key_code_by_name.h>
namespace Input_filter { class Remap_source; }
class Input_filter::Remap_source : public Source, Source::Sink
{
private:
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];
Owner _owner;
Source &_source;
Source::Sink &_destination;
/**
* Sink interface
*/
void submit_event(Input::Event const &event) override
{
using Input::Event;
bool const key_event =
event.type() == Event::PRESS || event.type() == Event::RELEASE;
bool const code_valid =
event.keycode() >= 0 && event.keycode() < Input::KEY_MAX;
/* forward events that are unrelated to the remapper */
if (!key_event || !code_valid) {
_destination.submit_event(event);
return;
}
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));
}
public:
static char const *name() { return "remap"; }
Remap_source(Owner &owner, Xml_node config, Source::Sink &destination,
Source::Factory &factory)
:
Source(owner),
_owner(factory),
_source(factory.create_source(_owner, input_sub_node(config), *this)),
_destination(destination)
{
for (unsigned i = 0; i < Input::KEY_MAX; i++)
_keys[i].code = Input::Keycode(i);
config.for_each_sub_node("key", [&] (Xml_node node) {
Key_name const key_name = node.attribute_value("name", Key_name());
try {
Input::Keycode const code = key_code_by_name(key_name);
if (node.has_attribute("to")) {
Key_name const to = node.attribute_value("to", Key_name());
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); }
});
}
void generate() override { _source.generate(); }
};
#endif /* _INPUT_FILTER__REMAP_SOURCE_H_ */

View File

@ -0,0 +1,96 @@
/*
* \brief Input-event source interface
* \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 General Public License version 2.
*/
#ifndef _INPUT_FILTER__SOURCE_H_
#define _INPUT_FILTER__SOURCE_H_
/* Genode includes */
#include <base/registry.h>
#include <input/event.h>
/* local includes */
#include <types.h>
namespace Input_filter { struct Source; }
class Input_filter::Source
{
private:
Registry<Source>::Element _owner_elem;
public:
struct Invalid_config : Exception { };
Source(Registry<Source> &owner) : _owner_elem(owner, *this) { }
static bool input_node(Xml_node node)
{
return node.type() == "input"
|| node.type() == "remap"
|| node.type() == "chargen"
|| node.type() == "merge";
return false;
}
static Xml_node input_sub_node(Xml_node node)
{
Xml_node result("<none/>");
node.for_each_sub_node([&] (Xml_node sub_node) {
if (input_node(sub_node))
result = sub_node; });
if (result.type() != "none")
return result;
error("missing <remap>/<chargen>/<merge> sub node in ", node);
throw Invalid_config { };
}
virtual void generate() = 0;
struct Owner;
struct Sink
{
virtual void submit_event(Input::Event const &) = 0;
};
struct Factory
{
/*
* \throw Invalid_config
*/
virtual Source &create_source(Owner &, Xml_node, Sink &) = 0;
virtual void destroy_source(Source &) = 0;
};
struct Owner : Registry<Source>
{
Factory &_factory;
Owner(Factory &factory) : _factory(factory) { }
~Owner()
{
for_each([&] (Source &s) { _factory.destroy_source(s); });
}
};
};
#endif /* _INPUT_FILTER__SOURCE_H_ */

View File

@ -0,0 +1,4 @@
TARGET = input_filter
SRC_CC = main.cc
LIBS = base timeout
INC_DIR += $(PRG_DIR)

View File

@ -0,0 +1,24 @@
/*
* \brief Interface for accessing a timer
* \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 General Public License version 2.
*/
#ifndef _INPUT_FILTER__TIMER_ACCESSOR_H_
#define _INPUT_FILTER__TIMER_ACCESSOR_H_
/* Genode includes */
#include <os/timer.h>
namespace Input_filter { struct Timer_accessor; }
struct Input_filter::Timer_accessor { virtual Genode::Timer &timer() = 0; };
#endif /* _INPUT_FILTER__TIMER_ACCESSOR_H_ */

View File

@ -0,0 +1,23 @@
/*
* \brief Types used by the 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 General Public License version 2.
*/
#ifndef _INPUT_FILTER__TYPES_H_
#define _INPUT_FILTER__TYPES_H_
/* Genode includes */
#include <util/string.h>
#include <base/session_label.h>
namespace Input_filter { using namespace Genode; }
#endif /* _INPUT_FILTER__TYPES_H_ */

View File

@ -0,0 +1,425 @@
/*
* \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 General Public License version 2.
*/
#include <base/heap.h>
#include <base/component.h>
#include <base/session_label.h>
#include <base/attached_rom_dataspace.h>
#include <input_session/connection.h>
#include <input/component.h>
#include <timer_session/connection.h>
#include <os/static_root.h>
#include <root/component.h>
#include <input/event.h>
#include <input/keycodes.h>
#include <os/reporter.h>
#include <base/sleep.h>
namespace Test {
class Input_from_filter;
class Input_to_filter;
class Input_root;
class Main;
using namespace Genode;
}
namespace Genode {
static inline void print(Output &output, Input::Event const &ev)
{
switch (ev.type()) {
case Input::Event::INVALID: print(output, "INVALID"); break;
case Input::Event::MOTION: print(output, "MOTION"); break;
case Input::Event::PRESS: print(output, "PRESS"); break;
case Input::Event::RELEASE: print(output, "RELEASE"); break;
case Input::Event::WHEEL: print(output, "WHEEL"); break;
case Input::Event::FOCUS: print(output, "FOCUS"); break;
case Input::Event::LEAVE: print(output, "LEAVE"); break;
case Input::Event::TOUCH: print(output, "TOUCH"); break;
case Input::Event::CHARACTER: print(output, "CHARACTER"); break;
};
if (ev.type() == Input::Event::PRESS || ev.type() == Input::Event::RELEASE)
print(output, " (", Input::key_name(ev.keycode()), ")");
}
}
class Test::Input_from_filter
{
public:
struct Event_handler
{
virtual void handle_event_from_filter(Input::Event const &) = 0;
};
private:
Env &_env;
Event_handler &_event_handler;
Input::Connection _connection;
bool _input_expected = false;
void _handle_input()
{
if (_input_expected)
_connection.for_each_event([&] (Input::Event const &event) {
_event_handler.handle_event_from_filter(event); });
}
Signal_handler<Input_from_filter> _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;
_handle_input();
}
};
class Test::Input_root : public Root_component<Input::Session_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 Root::Invalid_args();
}
/*
* 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) {
Input::Event::Type const type =
node.type() == "press" ? Input::Event::PRESS :
node.type() == "release" ? Input::Event::RELEASE :
Input::Event::INVALID;
if (type == Input::Event::PRESS || type == Input::Event::RELEASE) {
Key_name const key_name = node.attribute_value("code", Key_name());
dst.submit(Input::Event(type, _code(key_name), 0, 0, 0, 0));
}
});
}
};
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" };
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(_input_filter_config_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");
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() == "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")
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(Input::Event const &ev) override
{
typedef Genode::String<20> Value;
Xml_node const step = _curr_step_xml();
switch (ev.type()) {
case Input::Event::PRESS:
if (step.type() == "expect_press"
&& step.attribute_value("code", Value()) == Input::key_name(ev.keycode()))
break;
case Input::Event::RELEASE:
if (step.type() == "expect_release"
&& step.attribute_value("code", Value()) == Input::key_name(ev.keycode()))
break;
case Input::Event::CHARACTER:
if (step.type() == "expect_char"
&& step.attribute_value("char", Value()) == Value(Char(ev.utf8().b0)))
break;
case Input::Event::INVALID:
case Input::Event::MOTION:
case Input::Event::WHEEL:
case Input::Event::FOCUS:
case Input::Event::LEAVE:
case Input::Event::TOUCH:
error("unexpected event: ", ev);
throw Exception();
};
_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 <sleep> */
_advance_step();
_went_to_sleep_time = 0;
_execute_curr_step();
}
Signal_handler<Main> _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);
_execute_curr_step();
}
};
void Component::construct(Genode::Env &env) { static Test::Main main(env); }

View File

@ -0,0 +1,3 @@
TARGET = test-input_filter
SRC_CC = main.cc
LIBS += base

View File

@ -85,3 +85,4 @@ reconstructible
synced_interface
timer_accuracy
trace
input_filter