genode/repos/os/src/server/input_filter/chargen_source.h

582 lines
14 KiB
C++

/*
* \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 Affero General Public License version 3.
*/
#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:
typedef Input::Event Event;
Allocator &_alloc;
Timer_accessor &_timer_accessor;
Include_accessor &_include_accessor;
/*
* Modifier definitions
*/
struct Modifier
{
enum Id { MOD1 = 0, MOD2 = 1, MOD3 = 2, MOD4 = 3, UNDEFINED };
typedef String<8> Name;
Registry<Modifier>::Element _element;
Id const _id;
Input::Keycode const _code;
static Id id(Xml_node mod_node)
{
if (mod_node.type() == "mod1") return MOD1;
if (mod_node.type() == "mod2") return MOD2;
if (mod_node.type() == "mod3") return MOD3;
if (mod_node.type() == "mod4") return MOD4;
return UNDEFINED;
}
Modifier(Registry<Modifier> &registry, Id id, Input::Keycode code)
:
_element(registry, *this), _id(id), _code(code)
{ }
Input::Keycode code() const { return _code; }
Id id() const { return _id; }
};
Registry<Modifier> _modifiers { };
struct Modifier_rom
{
typedef String<32> Name;
Registry<Modifier_rom>::Element _element;
Modifier::Id const _id;
bool _enabled = false;
Modifier_rom(Registry<Modifier_rom> &registry,
Modifier::Id id,
Include_accessor &include_accessor,
Name const &name)
:
_element(registry, *this), _id(id)
{
try {
include_accessor.apply_include(name, "capslock", [&] (Xml_node node) {
_enabled = node.attribute_value("enabled", false); }); }
catch (Include_accessor::Include_unavailable) {
warning("failed to obtain modifier state from "
"\"", name, "\" ROM module"); }
}
Modifier::Id id() const { return _id; }
bool enabled() const { return _enabled; }
};
Registry<Modifier_rom> _modifier_roms { };
/*
* Key rules for generating characters
*/
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;
Codepoint const _character;
Rule(Registry<Rule> &registry,
Conditions conditions,
Codepoint 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();
}
Codepoint character() const { return _character; }
};
Registry<Rule> rules { };
/**
* Call functor 'fn' with the codepoint of the character defined
* for the best matching rule
*/
template <typename FN>
void apply_best_matching_rule(Modifier_map const &mod_map, FN const &fn) const
{
Codepoint best_match { Codepoint::INVALID };
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 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 */
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,
_codepoint_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; });
/* supplement modifier state provided by ROM modules */
_modifier_roms.for_each([&] (Modifier_rom const &mod_rom) {
_mod_map.states[mod_rom.id()].enabled |=
mod_rom.enabled(); });
}
Owner _owner;
Source::Sink &_destination;
/**
* Mechanism for periodically repeating the last character
*/
struct Char_repeater
{
Source::Sink &_destination;
Timer::Connection &_timer;
Microseconds const _delay;
Microseconds const _rate;
Codepoint _curr_character { Codepoint::INVALID };
enum State { IDLE, REPEAT } _state { IDLE };
void _handle_timeout(Duration)
{
if (_state == REPEAT) {
_destination.submit_event(Input::Press_char{Input::KEY_UNKNOWN,
_curr_character});
_destination.submit_event(Input::Release{Input::KEY_UNKNOWN});
_timeout.schedule(_rate);
}
}
Timer::One_shot_timeout<Char_repeater> _timeout {
_timer, *this, &Char_repeater::_handle_timeout };
Char_repeater(Source::Sink &destination, Timer::Connection &timer,
Xml_node node)
:
_destination(destination), _timer(timer),
_delay(node.attribute_value("delay_ms", (uint64_t)0)*1000),
_rate (node.attribute_value("rate_ms", (uint64_t)0)*1000)
{ }
void schedule_repeat(Codepoint character)
{
_curr_character = character;
_state = REPEAT;
_timeout.schedule(_delay);
}
void cancel()
{
_curr_character = Codepoint { Codepoint::INVALID };
_state = IDLE;
}
};
Constructible<Char_repeater> _char_repeater { };
/**
* Sink interface (called from our child node)
*/
void submit_event(Event const &event) override
{
Event ev = event;
ev.handle_press([&] (Input::Keycode keycode, Codepoint /* ignored */) {
Key &key = _key_map.key(keycode);
key.state = Key::PRESSED;
if (key.type == Key::MODIFIER) _update_modifier_state();
/* supplement codepoint information to press event */
key.apply_best_matching_rule(_mod_map, [&] (Codepoint codepoint) {
ev = Event(Input::Press_char{keycode, codepoint});
if (_char_repeater.constructed())
_char_repeater->schedule_repeat(codepoint);
});
});
ev.handle_release([&] (Input::Keycode keycode) {
Key &key = _key_map.key(keycode);
key.state = Key::RELEASED;
if (key.type == Key::MODIFIER) _update_modifier_state();
if (_char_repeater.constructed())
_char_repeater->cancel();
});
/* forward filtered event */
_destination.submit_event(ev);
}
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) {
warning("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);
});
node.for_each_sub_node("rom", [&] (Xml_node rom_node) {
typedef Modifier_rom::Name Rom_name;
Rom_name const rom_name = rom_node.attribute_value("name", Rom_name());
new (_alloc) Modifier_rom(_modifier_roms, id, _include_accessor, rom_name);
});
_update_modifier_state();
}
public:
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); });
_modifier_roms.for_each([&] (Modifier_rom &mod_rom) {
destroy(_alloc, &mod_rom); });
}
void generate() override { _source.generate(); }
};
#endif /* _INPUT_FILTER__CHARGEN_SOURCE_H_ */