Touch-screen keyboard

Fixes #4432
This commit is contained in:
Norman Feske 2022-01-12 00:42:06 +01:00 committed by Christian Helmuth
parent e35837e14b
commit 620a274c82
20 changed files with 1382 additions and 0 deletions

View File

@ -0,0 +1 @@
Virtual keyboard for touch-screen devices

View File

@ -0,0 +1,9 @@
_/raw/touch_keyboard
_/src/touch_keyboard
_/src/sandbox
_/src/libc
_/src/menu_view
_/src/init
_/src/vfs
_/src/libpng
_/src/zlib

View File

@ -0,0 +1 @@
2022-02-18 bac37029ed7e25956be0a0359c8c9c8c61120b06

View File

@ -0,0 +1,52 @@
<runtime ram="12M" caps="350" binary="init">
<requires>
<gui/>
<timer/>
<event/>
<file_system label="fonts" writeable="no"/>
</requires>
<config>
<parent-provides>
<service name="PD"/>
<service name="CPU"/>
<service name="ROM"/>
<service name="LOG"/>
<service name="Gui"/>
<service name="Timer"/>
<service name="File_system"/>
<service name="Event"/>
</parent-provides>
<start name="touch_keyboard" caps="250">
<binary name="touch_keyboard"/>
<resource name="RAM" quantum="8M"/>
<config min_width="720" min_height="480"/>
<route>
<service name="ROM" label="layout">
<parent label="touch_keyboard_layout.config"/> </service>
<service name="File_system" label="fonts">
<parent label="fonts"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</config>
<content>
<rom label="touch_keyboard"/>
<rom label="touch_keyboard_layout.config"/>
<rom label="ld.lib.so"/>
<rom label="libc.lib.so"/>
<rom label="libm.lib.so"/>
<rom label="vfs.lib.so"/>
<rom label="sandbox.lib.so"/>
<rom label="menu_view"/>
<rom label="menu_view_styles.tar"/>
<rom label="libpng.lib.so"/>
<rom label="zlib.lib.so"/>
</content>
</runtime>

View File

@ -0,0 +1,4 @@
content: touch_keyboard_layout.config
touch_keyboard_layout.config:
cp $(REP_DIR)/recipes/raw/touch_keyboard/$@ $@

View File

@ -0,0 +1 @@
2022-02-18 77a033bc47ac74edec3efa498e8b02a9e686c773

View File

@ -0,0 +1,212 @@
<config key_min_ex="2">
<map name="lower">
<row id="0">
<key id="0" label="Esc" code="27"/>
<key id="1" char="1"/>
<key id="2" char="2"/>
<key id="3" char="3"/>
<key id="4" char="4"/>
<key id="5" char="5"/>
<key id="6" char="6"/>
<key id="7" char="7"/>
<key id="8" char="8"/>
<key id="9" char="9"/>
<key id="10" char="0"/>
<key id="11" label="BS" code="8"/>
</row>
<row id="1">
<key id="0" char="q"/>
<key id="1" char="w"/>
<key id="2" char="e"/>
<key id="3" char="r"/>
<key id="4" char="t"/>
<key id="5" char="y"/>
<key id="6" char="u"/>
<key id="7" char="i"/>
<key id="8" char="o"/>
<key id="9" char="p"/>
</row>
<row id="2">
<key id="0" label="TAB" code="9" min_ex="1"/>
<key id="1" char="a"/>
<key id="2" char="s"/>
<key id="3" char="d"/>
<key id="4" char="f"/>
<key id="5" char="g"/>
<key id="6" char="h"/>
<key id="7" char="j"/>
<key id="8" char="k"/>
<key id="9" char="l"/>
<key id="11" char=":"/>
</row>
<row id="3">
<key id="0" label="ABC" min_ex="4" map="upper"/>
<key id="1" char="/" min_ex="1"/>
<key id="2" char="z"/>
<key id="3" char="x"/>
<key id="4" char="c"/>
<key id="5" char="v"/>
<key id="6" char="b"/>
<key id="7" char="n"/>
<key id="8" char="m"/>
<key id="9" label="UP" code="0xf700"/>
<key id="10" label="RETURN" code="10"/>
</row>
<row id="4">
<key id="0" min_ex="2" label=";:/" map="aux"/>
<key id="1" char="?"/>
<key id="2" char="!"/>
<key id="8" char=","/>
<key id="9" char="."/>
<key id="4" char=" " min_ex="8"/>
<key id="3" char="-"/>
<key id="5" label="LEFT" code="0xf702"/>
<key id="6" label="DOWN" code="0xf701"/>
<key id="7" label="RIGHT" code="0xf703"/>
</row>
</map>
<map name="upper">
<row id="0">
<key id="0" label="Esc" code="27"/>
<key id="1" char="1"/>
<key id="2" char="2"/>
<key id="3" char="3"/>
<key id="4" char="4"/>
<key id="5" char="5"/>
<key id="6" char="6"/>
<key id="7" char="7"/>
<key id="8" char="8"/>
<key id="9" char="9"/>
<key id="10" char="0"/>
<key id="11" label="BS" code="8"/>
</row>
<row id="1">
<key id="0" char="Q"/>
<key id="1" char="W"/>
<key id="2" char="E"/>
<key id="3" char="R"/>
<key id="4" char="T"/>
<key id="5" char="Y"/>
<key id="6" char="U"/>
<key id="7" char="I"/>
<key id="8" char="O"/>
<key id="9" char="P"/>
</row>
<row id="2">
<key id="0" label="TAB" code="9" min_ex="1"/>
<key id="1" char="A"/>
<key id="2" char="S"/>
<key id="3" char="D"/>
<key id="4" char="F"/>
<key id="5" char="G"/>
<key id="6" char="H"/>
<key id="7" char="J"/>
<key id="8" char="K"/>
<key id="9" char="L"/>
<key id="11" char=":"/>
</row>
<row id="3">
<key id="0" label="abc" min_ex="4" map="lower"/>
<key id="1" char="/" min_ex="1"/>
<key id="2" char="Z"/>
<key id="3" char="X"/>
<key id="4" char="C"/>
<key id="5" char="V"/>
<key id="6" char="B"/>
<key id="7" char="N"/>
<key id="8" char="M"/>
<key id="9" label="UP" code="0xf700"/>
<key id="10" label="RETURN" code="10"/>
</row>
<row id="4">
<key id="0" min_ex="2" label=";:/" map="aux"/>
<key id="1" char="?"/>
<key id="2" char="!"/>
<key id="8" char=","/>
<key id="9" char="."/>
<key id="4" char=" " min_ex="8"/>
<key id="3" char="-"/>
<key id="5" label="LEFT" code="0xf702"/>
<key id="6" label="DOWN" code="0xf701"/>
<key id="7" label="RIGHT" code="0xf703"/>
</row>
</map>
<map name="aux">
<row id="0">
<key id="0" label="Esc" code="27"/>
<key id="1" char="1"/>
<key id="2" char="2"/>
<key id="3" char="3"/>
<key id="4" char="4"/>
<key id="5" char="5"/>
<key id="6" char="6"/>
<key id="7" char="7"/>
<key id="8" char="8"/>
<key id="9" char="9"/>
<key id="10" char="0"/>
<key id="11" label="BS" code="8"/>
</row>
<row id="1">
<key id="0" min_ex="2" char="("/>
<key id="1" min_ex="2" char=")"/>
<key id="2" min_ex="2" char="&lt;"/>
<key id="3" min_ex="2" char="&gt;"/>
<key id="4" min_ex="2" char=""/>
<key id="5" min_ex="2" char="~"/>
<key id="6" min_ex="2" char="_"/>
<key id="7" min_ex="2" char="="/>
<key id="8" min_ex="2" char="`"/>
<key id="9" min_ex="2" char="&apos;"/>
<key id="10" min_ex="2" char="&quot;"/>
<key id="11" min_ex="2" char=";"/>
</row>
<row id="2">
<key id="0" min_ex="2" char="{"/>
<key id="1" min_ex="2" char="}"/>
<key id="2" min_ex="2" char="+"/>
<key id="3" min_ex="2" char="-"/>
<key id="4" min_ex="2" char=""/>
<key id="5" min_ex="2" code="176"/>
<key id="6" min_ex="2" code="92"/> <!-- backslash -->
<key id="7" min_ex="2" char="?"/>
<key id="8" min_ex="2" char="!"/>
<key id="9" min_ex="2" char="."/>
<key id="10" min_ex="2" char=","/>
<key id="11" min_ex="2" char=":"/>
</row>
<row id="3">
<key id="0" min_ex="2" char="["/>
<key id="1" min_ex="2" char="]"/>
<key id="2" min_ex="2" char="*"/>
<key id="3" min_ex="2" char="/"/>
<key id="4" min_ex="2" char=""/>
<key id="5" min_ex="2" char="#"/>
<key id="6" min_ex="2" char="%"/>
<key id="7" min_ex="2" char="&amp;"/>
<key id="8" min_ex="2" char="@"/>
<key id="9" min_ex="2" char="$"/>
<key id="10" min_ex="2" char="^"/>
<key id="11" min_ex="2" char="|"/>
</row>
<row id="4">
<key id="0" min_ex="2" label="abc" map="lower"/>
<key id="1" char=""/>
<key id="2" char=""/>
<key id="8" char=""/>
<key id="9" char=""/>
<key id="4" char="" min_ex="8"/>
<key id="3" char=""/>
<key id="5" char=""/>
<key id="6" char=""/>
<key id="7" label=""/>
</row>
</map>
</config>

View File

@ -0,0 +1,3 @@
SRC_DIR := src/app/touch_keyboard
include $(GENODE_DIR)/repos/base/recipes/src/content.inc

View File

@ -0,0 +1 @@
2022-02-18 c0f888d9349c90e4fdd70d7ed8cc3954f8fedc75

View File

@ -0,0 +1,9 @@
base
sandbox
os
report_session
event_session
timer_session
gui_session
input_session
framebuffer_session

View File

@ -0,0 +1,13 @@
The touch-keyboard component presents a virtual keyboard on screen and emits
input events when the user touches the virtual keys. It requires a GUI service,
a "fonts" file-system session, and an event session.
By default, the keyboard is positioned at the top-left corner of the screen
with the smallest possible size, given the used font. Those defaults can be
the overridden by the configuration as follows.
! <config min_width="720" min_height="360" xpos="10" ypos="10"/>
The layout of the virtual keyboard is defined by a ROM module requested via
the label "layout". An example can be found at
_recipes/raw/touch_keyboard/touch_keyboard_layout.config_.

View File

@ -0,0 +1,124 @@
/*
* \brief Runtime state of a child hosted in the runtime subsystem
* \author Norman Feske
* \date 2018-09-03
*/
/*
* Copyright (C) 2018 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 _CHILD_STATE_H_
#define _CHILD_STATE_H_
/* Genode includes */
#include <util/xml_node.h>
#include <util/noncopyable.h>
#include <util/string.h>
#include <base/registry.h>
#include <base/quota_guard.h>
/* local includes */
#include "types.h"
namespace Touch_keyboard { struct Child_state; }
struct Touch_keyboard::Child_state : Noncopyable
{
private:
using Start_name = String<128>;
Registry<Child_state>::Element _element;
Start_name const _name;
Ram_quota const _initial_ram_quota;
Cap_quota const _initial_cap_quota;
Ram_quota _ram_quota = _initial_ram_quota;
Cap_quota _cap_quota = _initial_cap_quota;
struct Version { unsigned value; } _version { 0 };
public:
/**
* Constructor
*
* \param ram_quota initial RAM quota
* \param cap_quota initial capability quota
*/
Child_state(Registry<Child_state> &registry, Start_name const &name,
Ram_quota ram_quota, Cap_quota cap_quota)
:
_element(registry, *this),
_name(name),
_initial_ram_quota(ram_quota), _initial_cap_quota(cap_quota)
{ }
void trigger_restart()
{
_version.value++;
_ram_quota = _initial_ram_quota;
_cap_quota = _initial_cap_quota;
}
void gen_start_node_version(Xml_generator &xml) const
{
if (_version.value)
xml.attribute("version", _version.value);
}
void gen_start_node_content(Xml_generator &xml) const
{
xml.attribute("name", _name);
gen_start_node_version(xml);
xml.attribute("caps", _cap_quota.value);
xml.node("resource", [&] () {
xml.attribute("name", "RAM");
Number_of_bytes const bytes(_ram_quota.value);
xml.attribute("quantum", String<64>(bytes)); });
}
/**
* Adapt runtime state information to the child
*
* This method responds to RAM and cap-resource requests by increasing
* the resource quotas as needed.
*
* \param child child node of the runtime'r state report
* \return true if runtime must be reconfigured so that the changes
* can take effect
*/
bool apply_child_state_report(Xml_node child)
{
bool result = false;
if (child.attribute_value("name", Start_name()) != _name)
return false;
if (child.has_sub_node("ram") && child.sub_node("ram").has_attribute("requested")) {
_ram_quota.value *= 2;
result = true;
}
if (child.has_sub_node("caps") && child.sub_node("caps").has_attribute("requested")) {
_cap_quota.value += 100;
result = true;
}
return result;
}
Ram_quota ram_quota() const { return _ram_quota; }
Start_name name() const { return _name; }
};
#endif /* _CHILD_STATE_H_ */

View File

@ -0,0 +1,141 @@
/*
* \brief GUI wrapper for monitoring the user input of GUI components
* \author Norman Feske
* \date 2020-01-12
*/
/*
* Copyright (C) 2020 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 _GUI_H_
#define _GUI_H_
/* Genode includes */
#include <input/component.h>
#include <base/session_object.h>
#include <gui_session/connection.h>
/* local includes */
#include <input_event_handler.h>
namespace Gui {
using namespace Genode;
struct Session_component;
}
struct Gui::Session_component : Session_object<Gui::Session>
{
Env &_env;
Input_event_handler &_event_handler;
Input::Seq_number &_global_seq_number;
Gui::Connection _connection;
Input::Session_component _input_component { _env, _env.ram() };
Signal_handler<Session_component> _input_handler {
_env.ep(), *this, &Session_component::_handle_input };
void _handle_input()
{
_connection.input()->for_each_event([&] (Input::Event ev) {
/*
* Augment input stream with sequence numbers to correlate
* clicks with hover reports.
*/
if (ev.touch() || ev.touch_release()) {
_global_seq_number.value++;
_input_component.submit(_global_seq_number);
}
/*
* Feed touch coordinates of primary finger as absolute motion to
* the menu view to trigger an update of the hover report.
*/
ev.handle_touch([&] (Input::Touch_id id, float x, float y) {
Input::Absolute_motion const xy { (int)x, (int)y };
if (id.value == 0)
_input_component.submit(xy); });
_event_handler.handle_input_event(ev);
});
}
template <typename... ARGS>
Session_component(Env &env, Input_event_handler &event_handler,
Input::Seq_number &global_seq_number, ARGS &&... args)
:
Session_object(args...),
_env(env), _event_handler(event_handler),
_global_seq_number(global_seq_number),
_connection(env, _label.string())
{
_connection.input()->sigh(_input_handler);
_env.ep().manage(_input_component);
_input_component.event_queue().enabled(true);
}
~Session_component() { _env.ep().dissolve(_input_component); }
void upgrade(Session::Resources const &resources)
{
_connection.upgrade(resources);
}
Framebuffer::Session_capability framebuffer_session() override {
return _connection.framebuffer_session(); }
Input::Session_capability input_session() override {
return _input_component.cap(); }
View_handle create_view(View_handle parent) override {
return _connection.create_view(parent); }
void destroy_view(View_handle view) override {
_connection.destroy_view(view); }
View_handle view_handle(View_capability view_cap, View_handle handle) override {
return _connection.view_handle(view_cap, handle); }
View_capability view_capability(View_handle view) override {
return _connection.view_capability(view); }
void release_view_handle(View_handle view) override {
_connection.release_view_handle(view); }
Dataspace_capability command_dataspace() override {
return _connection.command_dataspace(); }
void execute() override {
_connection.execute(); }
Framebuffer::Mode mode() override {
return _connection.mode(); }
void mode_sigh(Signal_context_capability sigh) override {
_connection.mode_sigh(sigh); }
void buffer(Framebuffer::Mode mode, bool use_alpha) override
{
/*
* Do not call 'Connection::buffer' to avoid paying session quota
* from our own budget.
*/
_connection.Client::buffer(mode, use_alpha);
}
void focus(Capability<Gui::Session> session) override {
_connection.focus(session); }
};
#endif /* _GUI_H_ */

View File

@ -0,0 +1,28 @@
/*
* \brief Interface for handling input events
* \author Norman Feske
* \date 2018-05-02
*/
/*
* Copyright (C) 2018 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_EVENT_HANDLER_H_
#define _INPUT_EVENT_HANDLER_H_
/* Genode includes */
#include <util/interface.h>
#include <input/event.h>
namespace Gui { struct Input_event_handler; }
struct Gui::Input_event_handler : Genode::Interface
{
virtual void handle_input_event(Input::Event const &) = 0;
};
#endif /* _INPUT_EVENT_HANDLER_H_ */

View File

@ -0,0 +1,312 @@
/*
* \brief Simple touch-screen keyboard
* \author Norman Feske
* \date 2022-01-11
*/
/*
* Copyright (C) 2022 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.
*/
/* Genode includes */
#include <base/component.h>
#include <base/session_object.h>
#include <base/attached_rom_dataspace.h>
#include <sandbox/sandbox.h>
#include <os/dynamic_rom_session.h>
#include <os/reporter.h>
#include <os/buffered_xml.h>
#include <event_session/connection.h>
/* local includes */
#include <gui.h>
#include <report.h>
#include <touch_keyboard_dialog.h>
#include <child_state.h>
namespace Touch_keyboard { struct Main; }
struct Touch_keyboard::Main : Sandbox::Local_service_base::Wakeup,
Sandbox::State_handler,
Gui::Input_event_handler,
Dialog::Event_emitter
{
Env &_env;
Heap _heap { _env.ram(), _env.rm() };
Attached_rom_dataspace _config { _env, "config" };
Attached_rom_dataspace _layout { _env, "layout" };
int _xpos = 0;
int _ypos = 0;
unsigned _min_width = 0;
unsigned _min_height = 0;
Registry<Child_state> _children { };
Child_state _menu_view_child_state { _children, "menu_view",
Ram_quota { 4*1024*1024 },
Cap_quota { 200 } };
/**
* Sandbox::State_handler
*/
void handle_sandbox_state() override
{
/* obtain current sandbox state */
Buffered_xml state(_heap, "state", [&] (Xml_generator &xml) {
_sandbox.generate_state_report(xml);
});
bool reconfiguration_needed = false;
state.with_xml_node([&] (Xml_node state) {
state.for_each_sub_node("child", [&] (Xml_node const &child) {
if (_menu_view_child_state.apply_child_state_report(child))
reconfiguration_needed = true; }); });
if (reconfiguration_needed)
_update_sandbox_config();
}
Sandbox _sandbox { _env, *this };
typedef Sandbox::Local_service<Gui::Session_component> Gui_service;
Gui_service _gui_service { _sandbox, *this };
typedef Sandbox::Local_service<Dynamic_rom_session> Rom_service;
Rom_service _rom_service { _sandbox, *this };
typedef Sandbox::Local_service<Report::Session_component> Report_service;
Report_service _report_service { _sandbox, *this };
void _handle_hover(Xml_node const &node)
{
Input::Seq_number hover_seq { node.attribute_value("seq_number", 0U) };
node.with_sub_node("dialog", [&] (Xml_node const &dialog) {
_dialog.handle_hover(hover_seq, dialog); });
}
Report::Session_component::Xml_handler<Main>
_hover_handler { *this, &Main::_handle_hover };
Dialog _dialog { _env.ep(), _env.ram(), _env.rm(), _heap, *this };
void _generate_sandbox_config(Xml_generator &xml) const
{
xml.node("report", [&] () {
xml.attribute("child_ram", "yes");
xml.attribute("child_caps", "yes");
xml.attribute("delay_ms", 20*1000);
});
xml.node("parent-provides", [&] () {
auto service_node = [&] (char const *name) {
xml.node("service", [&] () {
xml.attribute("name", name); }); };
service_node("ROM");
service_node("CPU");
service_node("PD");
service_node("LOG");
service_node("File_system");
service_node("Gui");
service_node("Timer");
service_node("Report");
});
xml.node("start", [&] () {
_menu_view_child_state.gen_start_node_content(xml);
xml.node("config", [&] () {
xml.attribute("xpos", _xpos);
xml.attribute("ypos", _ypos);
if (_min_width) xml.attribute("width", _min_width);
if (_min_height) xml.attribute("height", _min_height);
xml.node("report", [&] () {
xml.attribute("hover", "yes"); });
xml.node("libc", [&] () {
xml.attribute("stderr", "/dev/log"); });
xml.node("vfs", [&] () {
xml.node("tar", [&] () {
xml.attribute("name", "menu_view_styles.tar"); });
xml.node("dir", [&] () {
xml.attribute("name", "dev");
xml.node("log", [&] () { });
});
xml.node("dir", [&] () {
xml.attribute("name", "fonts");
xml.node("fs", [&] () {
xml.attribute("label", "fonts");
});
});
});
});
xml.node("route", [&] () {
xml.node("service", [&] () {
xml.attribute("name", "ROM");
xml.attribute("label", "dialog");
xml.node("local", [&] () { });
});
xml.node("service", [&] () {
xml.attribute("name", "Report");
xml.attribute("label", "hover");
xml.node("local", [&] () { });
});
xml.node("service", [&] () {
xml.attribute("name", "Gui");
xml.node("local", [&] () { });
});
xml.node("service", [&] () {
xml.attribute("name", "File_system");
xml.attribute("label", "fonts");
xml.node("parent", [&] () {
xml.attribute("label", "fonts"); });
});
xml.node("any-service", [&] () {
xml.node("parent", [&] () { }); });
});
});
}
/**
* Sandbox::Local_service_base::Wakeup interface
*/
void wakeup_local_service() override
{
_rom_service.for_each_requested_session([&] (Rom_service::Request &request) {
if (request.label == "menu_view -> dialog")
request.deliver_session(_dialog.rom_session);
else
request.deny();
});
_report_service.for_each_requested_session([&] (Report_service::Request &request) {
if (request.label == "menu_view -> hover") {
Report::Session_component &session = *new (_heap)
Report::Session_component(_env, _hover_handler,
_env.ep(),
request.resources, "", request.diag);
request.deliver_session(session);
}
});
_report_service.for_each_session_to_close([&] (Report::Session_component &session) {
destroy(_heap, &session);
return Report_service::Close_response::CLOSED;
});
_gui_service.for_each_requested_session([&] (Gui_service::Request &request) {
Gui::Session_component &session = *new (_heap)
Gui::Session_component(_env, *this, _global_input_seq_number, _env.ep(),
request.resources, "", request.diag);
request.deliver_session(session);
});
_gui_service.for_each_upgraded_session([&] (Gui::Session_component &session,
Session::Resources const &amount) {
session.upgrade(amount);
return Gui_service::Upgrade_response::CONFIRMED;
});
_gui_service.for_each_session_to_close([&] (Gui::Session_component &session) {
destroy(_heap, &session);
return Gui_service::Close_response::CLOSED;
});
}
Event::Connection _event_connection { _env };
/**
* Dialog::Event_emitter interface
*/
void emit_characters(Dialog::Emit const &characters) override
{
_event_connection.with_batch([&] (Event::Session_client::Batch &batch) {
Utf8_ptr utf8_ptr(characters.string());
for (; utf8_ptr.complete(); utf8_ptr = utf8_ptr.next()) {
Codepoint c = utf8_ptr.codepoint();
batch.submit( Input::Press_char { Input::KEY_UNKNOWN, c } );
batch.submit( Input::Release { Input::KEY_UNKNOWN } );
}
});
}
Input::Seq_number _global_input_seq_number { };
/**
* Gui::Input_event_handler interface
*/
void handle_input_event(Input::Event const &event) override
{
_dialog.handle_input_event(_global_input_seq_number, event);
}
void _handle_config()
{
_config.update();
_layout.update();
Xml_node const config = _config.xml();
_xpos = (int)config.attribute_value("xpos", 0L);
_ypos = (int)config.attribute_value("ypos", 0L);
_min_width = config.attribute_value("min_width", 0U);
_min_height = config.attribute_value("min_height", 0U);
_dialog.configure(_layout.xml());
}
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
void _update_sandbox_config()
{
Buffered_xml const config { _heap, "config", [&] (Xml_generator &xml) {
_generate_sandbox_config(xml); } };
config.with_xml_node([&] (Xml_node const &config) {
_sandbox.apply_config(config); });
}
Main(Env &env)
:
_env(env)
{
_config.sigh(_config_handler);
_layout.sigh(_config_handler);
_handle_config();
_update_sandbox_config();
}
};
void Component::construct(Genode::Env &env) { static Touch_keyboard::Main main(env); }

View File

@ -0,0 +1,89 @@
/*
* \brief Report session provided to the sandbox
* \author Norman Feske
* \date 2020-01-14
*/
/*
* Copyright (C) 2020 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 _REPORT_H_
#define _REPORT_H_
/* Genode includes */
#include <base/session_object.h>
#include <report_session/report_session.h>
namespace Report {
using namespace Genode;
struct Session_component;
}
class Report::Session_component : public Session_object<Report::Session>
{
public:
struct Handler_base : Interface, Genode::Noncopyable
{
virtual void handle_report(char const *, size_t) = 0;
};
template <typename T>
struct Xml_handler : Handler_base
{
T &_obj;
void (T::*_member) (Xml_node const &);
Xml_handler(T &obj, void (T::*member)(Xml_node const &))
: _obj(obj), _member(member) { }
void handle_report(char const *start, size_t length) override
{
(_obj.*_member)(Xml_node(start, length));
}
};
private:
Attached_ram_dataspace _ds;
Handler_base &_handler;
/*******************************
** Report::Session interface **
*******************************/
Dataspace_capability dataspace() override { return _ds.cap(); }
void submit(size_t length) override
{
_handler.handle_report(_ds.local_addr<char const>(),
min(_ds.size(), length));
}
void response_sigh(Signal_context_capability) override { }
size_t obtain_response() override { return 0; }
public:
template <typename... ARGS>
Session_component(Env &env, Handler_base &handler,
Entrypoint &ep, Resources const &resources,
ARGS &&... args)
:
Session_object(ep, resources, args...),
_ds(env.ram(), env.rm(), resources.ram_quota.value),
_handler(handler)
{ }
};
#endif /* _REPORT_H_ */

View File

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

View File

@ -0,0 +1,152 @@
/*
* \brief Touch-screen keyboard dialog
* \author Norman Feske
* \date 2022-01-11
*/
/*
* Copyright (C) 2022 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.
*/
/* local includes */
#include <touch_keyboard_dialog.h>
using namespace Touch_keyboard;
void Dialog::produce_xml(Xml_generator &xml)
{
auto gen_key = [&] (Key const &key)
{
xml.node("vbox", [&] () {
xml.attribute("name", key._id);
xml.node("button", [&] () {
bool const selected = (_emit_on_release.length() > 1)
&& (key.emit == _emit_on_release);
if (selected)
xml.attribute("selected", "yes");
if (key.map.length() > 1)
xml.attribute("style", "unimportant");
if (key.label.length() == 1)
xml.attribute("style", "invisible");
xml.node("vbox", [&] () {
xml.node("label", [&] () {
xml.attribute("name", "spacer");
unsigned const min_ex = key.min_ex
? key.min_ex
: _default_key_min_ex;
if (min_ex)
xml.attribute("min_ex", min_ex);
});
xml.node("label", [&] () {
xml.attribute("name", "label");
xml.attribute("text", key.label);
});
});
});
xml.node("label", [&] () {
xml.attribute("name", "spacer");
xml.attribute("font", "annotation/regular");
xml.attribute("text", "");
});
});
};
auto gen_row = [&] (Row const &row)
{
xml.node("hbox", [&] () {
xml.attribute("name", row._id);
row.keys.for_each([&] (Key const &key) {
gen_key(key); }); });
};
auto gen_map = [&] (Map const &map)
{
if (map.name != _current_map)
return;
xml.node("vbox", [&] () {
map.rows.for_each([&] (Row const &row) {
gen_row(row); }); });
};
xml.node("frame", [&] () {
_maps.for_each([&] (Map const &map) {
gen_map(map);
});
});
}
void Dialog::handle_input_event(Input::Seq_number curr_seq, Input::Event const &event)
{
if (event.touch())
_clicked_seq_number.construct(curr_seq);
if (_emit_on_release.length() > 1 && event.touch_release()) {
_event_emitter.emit_characters(_emit_on_release);
_emit_on_release = { };
rom_session.trigger_update();
}
}
void Dialog::handle_hover(Input::Seq_number seq, Xml_node const &dialog)
{
Row::Id hovered_row_id { };
Key::Id hovered_key_id { };
dialog.with_sub_node("frame", [&] (Xml_node const &frame) {
frame.with_sub_node("vbox", [&] (Xml_node const &vbox) {
vbox.with_sub_node("hbox", [&] (Xml_node const &hbox) {
hbox.with_sub_node("vbox", [&] (Xml_node const &button) {
hovered_row_id = hbox .attribute_value("name", Row::Id());
hovered_key_id = button.attribute_value("name", Key::Id());
});
});
});
});
_maps.for_each([&] (Map const &map) {
if (map.name != _current_map)
return;
map.rows.for_each([&] (Row const &row) {
if (row._id != hovered_row_id)
return;
row.keys.for_each([&] (Key const &key) {
if (key._id != hovered_key_id)
return;
if (_clicked_seq_number.constructed()) {
if (seq.value >= _clicked_seq_number->value) {
_emit_on_release = key.emit;
_clicked_seq_number.destruct();
if (key.map.length() > 1)
_current_map = key.map;
rom_session.trigger_update();
}
}
});
});
});
}
Dialog::Dialog(Entrypoint &ep, Ram_allocator &ram, Region_map &rm,
Allocator &alloc, Event_emitter &event_emitter)
:
Xml_producer("dialog"),
rom_session(ep, ram, rm, *this),
_alloc(alloc), _event_emitter(event_emitter)
{ }

View File

@ -0,0 +1,205 @@
/*
* \brief Touch-screen keyboard dialog
* \author Norman Feske
* \date 2022-01-11
*/
/*
* Copyright (C) 2022 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 _TOUCH_KEYBOARD_DIALOG_H_
#define _TOUCH_KEYBOARD_DIALOG_H_
/* Genode includes */
#include <util/list_model.h>
#include <base/component.h>
#include <base/session_object.h>
#include <sandbox/sandbox.h>
#include <os/dynamic_rom_session.h>
#include <input/event.h>
/* local includes */
#include <report.h>
#include <types.h>
namespace Touch_keyboard { struct Dialog; }
struct Touch_keyboard::Dialog : private Dynamic_rom_session::Xml_producer
{
public:
Dynamic_rom_session rom_session;
using Emit = String<8>;
struct Event_emitter : Interface
{
virtual void emit_characters(Emit const &) = 0;
};
private:
Allocator &_alloc;
Event_emitter &_event_emitter;
unsigned _default_key_min_ex = 0;
struct Key : List_model<Key>::Element
{
using Id = String<8>;
Id const _id;
static Id id_attr(Xml_node const &node) {
return node.attribute_value("id", Id()); }
using Label = String<8>;
Label label { };
Emit emit { };
using Map = String<8>;
Map map { };
unsigned min_ex = 0;
Key(Id id) : _id(id) { }
bool matches(Xml_node const &node) const { return _id == id_attr(node); }
static bool type_matches(Xml_node const &node) { return node.type() == "key"; }
void update(Xml_node const &key) {
label = { };
emit = { };
map = key.attribute_value("map", Map());
min_ex = key.attribute_value("min_ex", 0U);
if (key.has_attribute("char")) {
label = key.attribute_value("char", Label());
emit = Emit(Xml_unquoted(label));
}
if (key.has_attribute("code")) {
Codepoint c { key.attribute_value("code", 0U) };
emit = Emit(c);
label = emit;
}
if (key.has_attribute("label"))
label = key.attribute_value("label", Label());
}
};
struct Row : List_model<Row>::Element
{
Allocator &_alloc;
using Id = String<8>;
Id const _id;
static Id id_attr(Xml_node const &node) {
return node.attribute_value("id", Id()); }
List_model<Key> keys { };
Row(Allocator &alloc, Id id) : _alloc(alloc), _id(id) { }
bool matches(Xml_node const &node) const { return _id == id_attr(node); }
static bool type_matches(Xml_node const &node) { return node.type() == "row"; }
void update(Xml_node const &row)
{
update_list_model_from_xml(keys, row,
/* create */
[&] (Xml_node const &node) -> Key & {
return *new (_alloc) Key(Key::id_attr(node)); },
/* destroy */
[&] (Key &key) { destroy(_alloc, &key); },
/* update */
[&] (Key &key, Xml_node const &node) { key.update(node); });
}
};
struct Map : List_model<Map>::Element
{
Allocator &_alloc;
using Name = String<16>;
Name const name;
static Name name_attr(Xml_node const &node) {
return node.attribute_value("name", Name()); }
List_model<Row> rows { };
Map(Allocator &alloc, Name name) : _alloc(alloc), name(name) { }
bool matches(Xml_node const &node) const { return name == name_attr(node); }
static bool type_matches(Xml_node const &node) { return node.type() == "map"; }
void update(Xml_node const &map)
{
update_list_model_from_xml(rows, map,
/* create */
[&] (Xml_node const &node) -> Row & {
return *new (_alloc) Row(_alloc, Row::id_attr(node)); },
/* destroy */
[&] (Row &row) { destroy(_alloc, &row); },
/* update */
[&] (Row &row, Xml_node const &node) { row.update(node); });
}
};
List_model<Map> _maps { };
Map::Name _current_map = "lower";
Constructible<Input::Seq_number> _clicked_seq_number { };
Emit _emit_on_release { };
void produce_xml(Xml_generator &xml) override;
public:
Dialog(Entrypoint &, Ram_allocator &, Region_map &, Allocator &,
Event_emitter &);
void configure(Xml_node const &config)
{
_default_key_min_ex = config.attribute_value("key_min_ex", 0U);
update_list_model_from_xml(_maps, config,
/* create */
[&] (Xml_node const &node) -> Map & {
return *new (_alloc) Map(_alloc, Map::name_attr(node)); },
/* destroy */
[&] (Map &map) { destroy(_alloc, &map); },
/* update */
[&] (Map &map, Xml_node const &node) { map.update(node); });
}
void handle_input_event(Input::Seq_number curr_seq, Input::Event const &);
void handle_hover(Input::Seq_number, Xml_node const &hover);
};
#endif /* _TOUCH_KEYBOARD_DIALOG_H_ */

View File

@ -0,0 +1,21 @@
/*
* \brief Common types
* \author Norman Feske
* \date 2020-01-14
*/
/*
* Copyright (C) 2012 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 _TYPES_H_
#define _TYPES_H_
namespace Genode { }
namespace Touch_keyboard { using namespace Genode; }
#endif /* _TYPES_H_ */