mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-07 19:34:56 +00:00
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:
parent
859d23d92b
commit
2ce87216bc
355
repos/os/run/input_filter.run
Normal file
355
repos/os/run/input_filter.run
Normal 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
|
||||
|
110
repos/os/src/server/input_filter/README
Normal file
110
repos/os/src/server/input_filter/README
Normal 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.
|
524
repos/os/src/server/input_filter/chargen_source.h
Normal file
524
repos/os/src/server/input_filter/chargen_source.h
Normal 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> ®istry, 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> ®istry,
|
||||
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_ */
|
95
repos/os/src/server/input_filter/connection.h
Normal file
95
repos/os/src/server/input_filter/connection.h
Normal 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_ */
|
164
repos/os/src/server/input_filter/de.chargen
Normal file
164
repos/os/src/server/input_filter/de.chargen
Normal 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>
|
148
repos/os/src/server/input_filter/en_us.chargen
Normal file
148
repos/os/src/server/input_filter/en_us.chargen
Normal 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>
|
67
repos/os/src/server/input_filter/include_accessor.h
Normal file
67
repos/os/src/server/input_filter/include_accessor.h
Normal 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_ */
|
47
repos/os/src/server/input_filter/input_source.h
Normal file
47
repos/os/src/server/input_filter/input_source.h
Normal 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_ */
|
43
repos/os/src/server/input_filter/key_code_by_name.h
Normal file
43
repos/os/src/server/input_filter/key_code_by_name.h
Normal 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_ */
|
436
repos/os/src/server/input_filter/main.cc
Normal file
436
repos/os/src/server/input_filter/main.cc
Normal 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> ®istry, 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); }
|
49
repos/os/src/server/input_filter/merge_source.h
Normal file
49
repos/os/src/server/input_filter/merge_source.h
Normal 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_ */
|
132
repos/os/src/server/input_filter/remap_source.h
Normal file
132
repos/os/src/server/input_filter/remap_source.h
Normal 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_ */
|
96
repos/os/src/server/input_filter/source.h
Normal file
96
repos/os/src/server/input_filter/source.h
Normal 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_ */
|
4
repos/os/src/server/input_filter/target.mk
Normal file
4
repos/os/src/server/input_filter/target.mk
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET = input_filter
|
||||
SRC_CC = main.cc
|
||||
LIBS = base timeout
|
||||
INC_DIR += $(PRG_DIR)
|
24
repos/os/src/server/input_filter/timer_accessor.h
Normal file
24
repos/os/src/server/input_filter/timer_accessor.h
Normal 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_ */
|
23
repos/os/src/server/input_filter/types.h
Normal file
23
repos/os/src/server/input_filter/types.h
Normal 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_ */
|
425
repos/os/src/test/input_filter/main.cc
Normal file
425
repos/os/src/test/input_filter/main.cc
Normal 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); }
|
||||
|
3
repos/os/src/test/input_filter/target.mk
Normal file
3
repos/os/src/test/input_filter/target.mk
Normal file
@ -0,0 +1,3 @@
|
||||
TARGET = test-input_filter
|
||||
SRC_CC = main.cc
|
||||
LIBS += base
|
@ -85,3 +85,4 @@ reconstructible
|
||||
synced_interface
|
||||
timer_accuracy
|
||||
trace
|
||||
input_filter
|
||||
|
Loading…
x
Reference in New Issue
Block a user