mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-20 22:23:16 +00:00
parent
4491c070be
commit
ca850c787f
@ -22,7 +22,6 @@
|
||||
</remap>
|
||||
<mod1>
|
||||
<key name="KEY_LEFTSHIFT"/> <key name="KEY_RIGHTSHIFT"/>
|
||||
<rom name="capslock"/>
|
||||
</mod1>
|
||||
<mod2>
|
||||
<key name="KEY_LEFTCTRL"/> <key name="KEY_RIGHTCTRL"/>
|
||||
@ -30,6 +29,9 @@
|
||||
<mod3>
|
||||
<key name="KEY_RIGHTALT"/> <!-- AltGr -->
|
||||
</mod3>
|
||||
<mod4>
|
||||
<rom name="capslock"/>
|
||||
</mod4>
|
||||
<repeat delay_ms="230" rate_ms="40"/>
|
||||
<include rom="en_us.chargen"/>
|
||||
<include rom="special.chargen"/>
|
||||
|
@ -241,9 +241,9 @@ append config {
|
||||
<remap>
|
||||
<input name="usb"/>
|
||||
</remap>
|
||||
<mod1> <rom name="capslock"/> </mod1>
|
||||
<mod4> <rom name="capslock"/> </mod4>
|
||||
<map> <key name="KEY_A" char="a"/> </map>
|
||||
<map mod1="yes"> <key name="KEY_A" char="A"/> </map>
|
||||
<map mod4="yes"> <key name="KEY_A" char="A"/> </map>
|
||||
</chargen>
|
||||
</output>
|
||||
</filter_config>
|
||||
@ -268,6 +268,56 @@ append config {
|
||||
<expect_press code="KEY_A" char="a"/> <expect_release code="KEY_A"/>
|
||||
|
||||
|
||||
<message string="sequence handling"/>
|
||||
|
||||
<filter_config>
|
||||
<input label="usb"/>
|
||||
<output>
|
||||
<chargen>
|
||||
<remap>
|
||||
<input name="usb"/>
|
||||
</remap>
|
||||
<map>
|
||||
<key name="KEY_GRAVE" code="0x0300"/> <!-- dead_grave -->
|
||||
<key name="KEY_A" char="a"/>
|
||||
<key name="KEY_E" char="e"/>
|
||||
<key name="KEY_X" char="x"/>
|
||||
</map>
|
||||
<sequence first="0x0300" second="0x0061" code="0x00e0"/> <!-- LATIN SMALL LETTER A WITH GRAVE -->
|
||||
<sequence first="0x0300" second="0x0065" code="0x00e8"/> <!-- LATIN SMALL LETTER E WITH GRAVE -->
|
||||
</chargen>
|
||||
</output>
|
||||
</filter_config>
|
||||
<sleep ms="250"/>
|
||||
<usb>
|
||||
<press code="KEY_GRAVE"/> <release code="KEY_GRAVE"/> <!-- invalid char -->
|
||||
<press code="KEY_A"/> <release code="KEY_A"/> <!-- generate a-grave -->
|
||||
<press code="KEY_GRAVE"/> <release code="KEY_GRAVE"/> <!-- invalid char -->
|
||||
<press code="KEY_E"/> <release code="KEY_E"/> <!-- generate e-grave -->
|
||||
<press code="KEY_GRAVE"/> <release code="KEY_GRAVE"/> <!-- invalid char -->
|
||||
<press code="KEY_X"/> <release code="KEY_X"/> <!-- abort sequence (invalid char) -->
|
||||
<press code="KEY_X"/> <release code="KEY_X"/> <!-- generate x -->
|
||||
</usb>
|
||||
<expect_press code="KEY_GRAVE" codepoint="0xfffe"/>
|
||||
<expect_release code="KEY_GRAVE"/>
|
||||
<expect_press code="KEY_A" codepoint="0x00e0"/>
|
||||
<expect_release code="KEY_A"/>
|
||||
<expect_press code="KEY_GRAVE"/>
|
||||
<expect_release code="KEY_GRAVE"/>
|
||||
<expect_press code="KEY_E" codepoint="0x00e8"/>
|
||||
<expect_release code="KEY_E"/>
|
||||
<expect_press code="KEY_GRAVE"/>
|
||||
<expect_release code="KEY_GRAVE"/>
|
||||
<message string="1"/>
|
||||
<expect_press code="KEY_X" codepoint="0xfffe"/>
|
||||
<expect_release code="KEY_X"/>
|
||||
<message string="2"/>
|
||||
<expect_press code="KEY_X" char="x"/>
|
||||
<expect_release code="KEY_X"/>
|
||||
<message string="3"/>
|
||||
<sleep ms="250"/>
|
||||
|
||||
|
||||
<message string="button-scroll feature"/>
|
||||
|
||||
<filter_config>
|
||||
|
@ -78,14 +78,17 @@ 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>' corresponds to shift, '<mod2>' to control, '<mod3>' to altgr
|
||||
(on German keyboards), and '<mod4>' to Caps Lock. 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"/>
|
||||
! <rom name="capslock"/>
|
||||
! </mod1>
|
||||
! <mod4>
|
||||
! <rom name="capslock"/>
|
||||
! </mod4>
|
||||
|
||||
The '<rom>' node incorporates the content of the ROM module of the
|
||||
specified name into the modifier state. If the ROM module contains a
|
||||
@ -103,10 +106,48 @@ sub nodes:
|
||||
|
||||
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.
|
||||
is defined by the attributes 'ascii', 'char', 'code', or 'b0/b1/b2/b3'.
|
||||
|
||||
:'ascii': accepts an integer value between 0 and 127
|
||||
:'char': accepts a single ASCII character
|
||||
:'code': defines the Unicode codepoint as integer value
|
||||
:'b0'/'b1'/'b2'/'b3': define the individual bytes of an UTF-8 character
|
||||
|
||||
:<sequence first="..." second="..." third="..." fourth="..." code="..."/>:
|
||||
|
||||
A sequence node permits the definition of dead-key/composing
|
||||
character sequences. With such sequences the character is not
|
||||
generated instantly on key press but only after the sequence is
|
||||
completed. If an unfinished sequence can't be completed due to an
|
||||
unmatched character, the sequence is aborted and no character is
|
||||
generated. input_filter supports sequences of up to four characters.
|
||||
|
||||
For example, the French AZERTY keyboard layout [1] has a dead key
|
||||
for Circumflex Accent "^" right of the P key (which is bracket left
|
||||
"[" on US keyboards). When Circumflex is pressed no visible
|
||||
character should be generated instantly but the accent must be
|
||||
combined with a follow-up character, e.g., Circumflex plus "a"
|
||||
generates â.
|
||||
|
||||
[1] https://docs.microsoft.com/en-us/globalization/keyboards/kbdfr.html
|
||||
|
||||
Dead keys can be defined in the <key> nodes of any <map> by using
|
||||
codepoints not used for direct output, for example, Combining
|
||||
Diacritical Marks beginning at U+0300. The French Circumflex example
|
||||
can be configured like follows.
|
||||
|
||||
! <mod1>
|
||||
! <key name="KEY_LEFTSHIFT"/> <key name="KEY_RIGHTSHIFT"/>
|
||||
! </mod1>
|
||||
! <map>
|
||||
! <key name="KEY_Q" code="0x0061"/> <!-- a -->
|
||||
! <key name="KEY_LEFTBRACE" code="0x0302"/> <!-- dead_circumflex -->
|
||||
! </map>
|
||||
! <map mod1="true">
|
||||
! <key name="KEY_Q" code="0x0041"/> <!-- A -->
|
||||
! </map>
|
||||
! <sequence first="0x0302" second="0x0061" code="0x00e2"/> <!-- â -->
|
||||
! <sequence first="0x0302" second="0x0041" code="0x00c2"/> <!-- Â -->
|
||||
|
||||
:<repeat delay_ms="500" rate_ms="250">:
|
||||
|
||||
|
@ -238,6 +238,49 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
}
|
||||
};
|
||||
|
||||
struct Missing_character_definition { };
|
||||
|
||||
/**
|
||||
* Return Unicode codepoint defined in XML node attributes
|
||||
*
|
||||
* \throw Missing_character_definition
|
||||
*/
|
||||
static Codepoint _codepoint_from_xml_node(Xml_node node)
|
||||
{
|
||||
if (node.has_attribute("ascii"))
|
||||
return Codepoint { node.attribute_value<uint32_t>("ascii", 0) };
|
||||
|
||||
if (node.has_attribute("code"))
|
||||
return Codepoint { node.attribute_value<uint32_t>("code", 0) };
|
||||
|
||||
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 Codepoint { ascii };
|
||||
|
||||
warning("char attribute with non-ascii character "
|
||||
"'", value, "'");
|
||||
throw Missing_character_definition();
|
||||
}
|
||||
|
||||
if (node.has_attribute("b0")) {
|
||||
char const b0 = node.attribute_value("b0", 0L),
|
||||
b1 = node.attribute_value("b1", 0L),
|
||||
b2 = node.attribute_value("b2", 0L),
|
||||
b3 = node.attribute_value("b3", 0L);
|
||||
|
||||
char const buf[5] { b0, b1, b2, b3, 0 };
|
||||
return Utf8_ptr(buf).codepoint();
|
||||
}
|
||||
|
||||
throw Missing_character_definition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of the states of the physical keys
|
||||
*/
|
||||
@ -286,49 +329,6 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
: Key::Rule::Conditions::Modifier::RELEASED;
|
||||
}
|
||||
|
||||
struct Missing_character_definition { };
|
||||
|
||||
/**
|
||||
* Return UTF8 character defined in XML node attributes
|
||||
*
|
||||
* \throw Missing_character_definition
|
||||
*/
|
||||
static Codepoint _codepoint_from_xml_node(Xml_node node)
|
||||
{
|
||||
if (node.has_attribute("ascii"))
|
||||
return Codepoint { node.attribute_value<uint32_t>("ascii", 0) };
|
||||
|
||||
if (node.has_attribute("code"))
|
||||
return Codepoint { node.attribute_value<uint32_t>("code", 0) };
|
||||
|
||||
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 Codepoint { ascii };
|
||||
|
||||
warning("char attribute with non-ascii character "
|
||||
"'", value, "'");
|
||||
throw Missing_character_definition();
|
||||
}
|
||||
|
||||
if (node.has_attribute("b0")) {
|
||||
char const b0 = node.attribute_value("b0", 0L),
|
||||
b1 = node.attribute_value("b1", 0L),
|
||||
b2 = node.attribute_value("b2", 0L),
|
||||
b3 = node.attribute_value("b3", 0L);
|
||||
|
||||
char const buf[5] { b0, b1, b2, b3, 0 };
|
||||
return Utf8_ptr(buf).codepoint();
|
||||
}
|
||||
|
||||
throw Missing_character_definition();
|
||||
}
|
||||
|
||||
void import_map(Xml_node map)
|
||||
{
|
||||
/* obtain modifier conditions from map attributes */
|
||||
@ -368,6 +368,150 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
mod_rom.enabled(); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate characters from codepoint sequences
|
||||
*/
|
||||
class Sequencer
|
||||
{
|
||||
private:
|
||||
|
||||
Allocator &_alloc;
|
||||
|
||||
struct Sequence
|
||||
{
|
||||
Codepoint seq[4] { Codepoint::INVALID, Codepoint::INVALID,
|
||||
Codepoint::INVALID, Codepoint::INVALID };
|
||||
|
||||
unsigned len { 0 };
|
||||
|
||||
enum Match { MISMATCH , UNFINISHED, COMPLETED };
|
||||
|
||||
Sequence() { }
|
||||
|
||||
Sequence(Codepoint c0, Codepoint c1, Codepoint c2, Codepoint c3)
|
||||
: seq { c0, c1, c2, c3 }, len { 4 } { }
|
||||
|
||||
void append(Codepoint c)
|
||||
{
|
||||
/* excess codepoints are just dropped */
|
||||
if (len < 4)
|
||||
seq[len++] = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match 'other' to 'this' until first invalid codepoint in
|
||||
* 'other', completion, or mismatch
|
||||
*/
|
||||
Match match(Sequence const &o) const
|
||||
{
|
||||
/* first codepoint must match */
|
||||
if (o.seq[0].value != seq[0].value) return MISMATCH;
|
||||
|
||||
for (unsigned i = 1; i < sizeof(seq)/sizeof(*seq); ++i) {
|
||||
/* end of this sequence means COMPLETED */
|
||||
if (!seq[i].valid()) break;
|
||||
|
||||
/* end of other sequence means UNFINISHED */
|
||||
if (!o.seq[i].valid()) return UNFINISHED;
|
||||
|
||||
if (o.seq[i].value != seq[i].value) return MISMATCH;
|
||||
|
||||
/* continue until completion with both valid and equal */
|
||||
}
|
||||
return COMPLETED;
|
||||
}
|
||||
};
|
||||
|
||||
struct Rule
|
||||
{
|
||||
typedef Sequence::Match Match;
|
||||
|
||||
Registry<Rule>::Element element;
|
||||
Sequence const sequence;
|
||||
Codepoint const code;
|
||||
|
||||
Rule(Registry<Rule> ®istry, Sequence const &sequence, Codepoint code)
|
||||
:
|
||||
element(registry, *this),
|
||||
sequence(sequence),
|
||||
code(code)
|
||||
{ }
|
||||
};
|
||||
|
||||
Registry<Rule> _rules { };
|
||||
|
||||
Sequence _curr_sequence { };
|
||||
|
||||
public:
|
||||
|
||||
Sequencer(Allocator &alloc) : _alloc(alloc) { }
|
||||
|
||||
~Sequencer()
|
||||
{
|
||||
_rules.for_each([&] (Rule &rule) {
|
||||
destroy(_alloc, &rule); });
|
||||
}
|
||||
|
||||
void import_sequence(Xml_node node)
|
||||
{
|
||||
unsigned const invalid { Codepoint::INVALID };
|
||||
|
||||
Sequence sequence {
|
||||
Codepoint { node.attribute_value("first", invalid) },
|
||||
Codepoint { node.attribute_value("second", invalid) },
|
||||
Codepoint { node.attribute_value("third", invalid) },
|
||||
Codepoint { node.attribute_value("fourth", invalid) } };
|
||||
|
||||
new (_alloc) Rule(_rules, sequence, _codepoint_from_xml_node(node));
|
||||
}
|
||||
|
||||
Codepoint process(Codepoint codepoint)
|
||||
{
|
||||
Codepoint const invalid { Codepoint::INVALID };
|
||||
Rule::Match best_match { Sequence::MISMATCH };
|
||||
Codepoint result { codepoint };
|
||||
Sequence seq { _curr_sequence };
|
||||
|
||||
seq.append(codepoint);
|
||||
|
||||
_rules.for_each([&] (Rule const &rule) {
|
||||
/* early return if completed match was found already */
|
||||
if (best_match == Sequence::COMPLETED) return;
|
||||
|
||||
Rule::Match const match { rule.sequence.match(seq) };
|
||||
switch (match) {
|
||||
case Sequence::MISMATCH:
|
||||
return;
|
||||
case Sequence::UNFINISHED:
|
||||
best_match = match;
|
||||
result = invalid;
|
||||
return;
|
||||
case Sequence::COMPLETED:
|
||||
best_match = match;
|
||||
result = rule.code;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
switch (best_match) {
|
||||
case Sequence::MISMATCH:
|
||||
/* drop cancellation codepoint of unfinished sequence */
|
||||
if (_curr_sequence.len > 0)
|
||||
result = invalid;
|
||||
_curr_sequence = Sequence();
|
||||
break;
|
||||
case Sequence::UNFINISHED:
|
||||
_curr_sequence = seq;
|
||||
break;
|
||||
case Sequence::COMPLETED:
|
||||
_curr_sequence = Sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} _sequencer;
|
||||
|
||||
Owner _owner;
|
||||
|
||||
Source::Sink &_destination;
|
||||
@ -442,6 +586,8 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
/* supplement codepoint information to press event */
|
||||
key.apply_best_matching_rule(_mod_map, [&] (Codepoint codepoint) {
|
||||
|
||||
codepoint = _sequencer.process(codepoint);
|
||||
|
||||
ev = Event(Input::Press_char{keycode, codepoint});
|
||||
|
||||
if (_char_repeater.constructed())
|
||||
@ -499,8 +645,24 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
* Handle map nodes
|
||||
*/
|
||||
if (node.type() == "map") {
|
||||
_key_map.import_map(node);
|
||||
return;
|
||||
try {
|
||||
_key_map.import_map(node);
|
||||
return;
|
||||
}
|
||||
catch (Missing_character_definition) {
|
||||
throw Invalid_config(); }
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle sequence nodes
|
||||
*/
|
||||
if (node.type() == "sequence") {
|
||||
try {
|
||||
_sequencer.import_sequence(node);
|
||||
return;
|
||||
}
|
||||
catch (Missing_character_definition) {
|
||||
throw Invalid_config(); }
|
||||
}
|
||||
|
||||
/*
|
||||
@ -555,6 +717,7 @@ class Input_filter::Chargen_source : public Source, Source::Sink
|
||||
_timer_accessor(timer_accessor),
|
||||
_include_accessor(include_accessor),
|
||||
_key_map(_alloc),
|
||||
_sequencer(_alloc),
|
||||
_owner(factory),
|
||||
_destination(destination),
|
||||
_source(factory.create_source(_owner, input_sub_node(config), *this))
|
||||
|
@ -375,11 +375,14 @@ struct Test::Main : Input_from_filter::Event_handler
|
||||
ev.handle_press([&] (Input::Keycode key, Codepoint codepoint) {
|
||||
|
||||
auto codepoint_of_step = [] (Xml_node step) {
|
||||
return Utf8_ptr(step.attribute_value("char", Value()).string()).codepoint(); };
|
||||
if (step.has_attribute("codepoint"))
|
||||
return Codepoint { step.attribute_value("codepoint", 0U) };
|
||||
return Utf8_ptr(step.attribute_value("char", Value()).string()).codepoint();
|
||||
};
|
||||
|
||||
if (step.type() == "expect_press"
|
||||
&& step.attribute_value("code", Value()) == Input::key_name(key)
|
||||
&& (!step.has_attribute("char") ||
|
||||
&& ((!step.has_attribute("char") && !step.has_attribute("codepoint")) ||
|
||||
codepoint_of_step(step).value == codepoint.value))
|
||||
step_succeeded = true;
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user