diff --git a/repos/gems/recipes/pkg/touch_keyboard/README b/repos/gems/recipes/pkg/touch_keyboard/README
new file mode 100644
index 0000000000..43d29b458b
--- /dev/null
+++ b/repos/gems/recipes/pkg/touch_keyboard/README
@@ -0,0 +1 @@
+Virtual keyboard for touch-screen devices
diff --git a/repos/gems/recipes/pkg/touch_keyboard/archives b/repos/gems/recipes/pkg/touch_keyboard/archives
new file mode 100644
index 0000000000..2da730e627
--- /dev/null
+++ b/repos/gems/recipes/pkg/touch_keyboard/archives
@@ -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
diff --git a/repos/gems/recipes/pkg/touch_keyboard/hash b/repos/gems/recipes/pkg/touch_keyboard/hash
new file mode 100644
index 0000000000..3ae15ad316
--- /dev/null
+++ b/repos/gems/recipes/pkg/touch_keyboard/hash
@@ -0,0 +1 @@
+2022-02-18 bac37029ed7e25956be0a0359c8c9c8c61120b06
diff --git a/repos/gems/recipes/pkg/touch_keyboard/runtime b/repos/gems/recipes/pkg/touch_keyboard/runtime
new file mode 100644
index 0000000000..7a69ab255f
--- /dev/null
+++ b/repos/gems/recipes/pkg/touch_keyboard/runtime
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/repos/gems/recipes/raw/touch_keyboard/content.mk b/repos/gems/recipes/raw/touch_keyboard/content.mk
new file mode 100644
index 0000000000..a67f51bc47
--- /dev/null
+++ b/repos/gems/recipes/raw/touch_keyboard/content.mk
@@ -0,0 +1,4 @@
+content: touch_keyboard_layout.config
+
+touch_keyboard_layout.config:
+ cp $(REP_DIR)/recipes/raw/touch_keyboard/$@ $@
diff --git a/repos/gems/recipes/raw/touch_keyboard/hash b/repos/gems/recipes/raw/touch_keyboard/hash
new file mode 100644
index 0000000000..a523eb4c40
--- /dev/null
+++ b/repos/gems/recipes/raw/touch_keyboard/hash
@@ -0,0 +1 @@
+2022-02-18 77a033bc47ac74edec3efa498e8b02a9e686c773
diff --git a/repos/gems/recipes/raw/touch_keyboard/touch_keyboard_layout.config b/repos/gems/recipes/raw/touch_keyboard/touch_keyboard_layout.config
new file mode 100644
index 0000000000..2f57b2404f
--- /dev/null
+++ b/repos/gems/recipes/raw/touch_keyboard/touch_keyboard_layout.config
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
diff --git a/repos/gems/recipes/src/touch_keyboard/content.mk b/repos/gems/recipes/src/touch_keyboard/content.mk
new file mode 100644
index 0000000000..dbeac215b9
--- /dev/null
+++ b/repos/gems/recipes/src/touch_keyboard/content.mk
@@ -0,0 +1,3 @@
+SRC_DIR := src/app/touch_keyboard
+
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/repos/gems/recipes/src/touch_keyboard/hash b/repos/gems/recipes/src/touch_keyboard/hash
new file mode 100644
index 0000000000..6de113affd
--- /dev/null
+++ b/repos/gems/recipes/src/touch_keyboard/hash
@@ -0,0 +1 @@
+2022-02-18 c0f888d9349c90e4fdd70d7ed8cc3954f8fedc75
diff --git a/repos/gems/recipes/src/touch_keyboard/used_apis b/repos/gems/recipes/src/touch_keyboard/used_apis
new file mode 100644
index 0000000000..52751df30d
--- /dev/null
+++ b/repos/gems/recipes/src/touch_keyboard/used_apis
@@ -0,0 +1,9 @@
+base
+sandbox
+os
+report_session
+event_session
+timer_session
+gui_session
+input_session
+framebuffer_session
diff --git a/repos/gems/src/app/touch_keyboard/README b/repos/gems/src/app/touch_keyboard/README
new file mode 100644
index 0000000000..b0cedf137f
--- /dev/null
+++ b/repos/gems/src/app/touch_keyboard/README
@@ -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.
+
+!
+
+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_.
diff --git a/repos/gems/src/app/touch_keyboard/child_state.h b/repos/gems/src/app/touch_keyboard/child_state.h
new file mode 100644
index 0000000000..712a14487a
--- /dev/null
+++ b/repos/gems/src/app/touch_keyboard/child_state.h
@@ -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
+#include
+#include
+#include
+#include
+
+/* local includes */
+#include "types.h"
+
+namespace Touch_keyboard { struct Child_state; }
+
+struct Touch_keyboard::Child_state : Noncopyable
+{
+ private:
+
+ using Start_name = String<128>;
+
+ Registry::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 ®istry, 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_ */
diff --git a/repos/gems/src/app/touch_keyboard/gui.h b/repos/gems/src/app/touch_keyboard/gui.h
new file mode 100644
index 0000000000..2934f64bb3
--- /dev/null
+++ b/repos/gems/src/app/touch_keyboard/gui.h
@@ -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
+#include
+#include
+
+/* local includes */
+#include
+
+namespace Gui {
+
+ using namespace Genode;
+
+ struct Session_component;
+}
+
+
+struct Gui::Session_component : Session_object
+{
+ 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 _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
+ 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 session) override {
+ _connection.focus(session); }
+};
+
+#endif /* _GUI_H_ */
diff --git a/repos/gems/src/app/touch_keyboard/input_event_handler.h b/repos/gems/src/app/touch_keyboard/input_event_handler.h
new file mode 100644
index 0000000000..25329dcb52
--- /dev/null
+++ b/repos/gems/src/app/touch_keyboard/input_event_handler.h
@@ -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
+#include
+
+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_ */
diff --git a/repos/gems/src/app/touch_keyboard/main.cc b/repos/gems/src/app/touch_keyboard/main.cc
new file mode 100644
index 0000000000..c5b3719f32
--- /dev/null
+++ b/repos/gems/src/app/touch_keyboard/main.cc
@@ -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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* local includes */
+#include
+#include
+#include
+#include
+
+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 _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_service;
+
+ Gui_service _gui_service { _sandbox, *this };
+
+ typedef Sandbox::Local_service Rom_service;
+
+ Rom_service _rom_service { _sandbox, *this };
+
+ typedef Sandbox::Local_service 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
+ _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 _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); }
+
diff --git a/repos/gems/src/app/touch_keyboard/report.h b/repos/gems/src/app/touch_keyboard/report.h
new file mode 100644
index 0000000000..f9e402c1ca
--- /dev/null
+++ b/repos/gems/src/app/touch_keyboard/report.h
@@ -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
+#include
+
+namespace Report {
+
+ using namespace Genode;
+
+ struct Session_component;
+}
+
+
+class Report::Session_component : public Session_object
+{
+ public:
+
+ struct Handler_base : Interface, Genode::Noncopyable
+ {
+ virtual void handle_report(char const *, size_t) = 0;
+ };
+
+ template
+ 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(),
+ min(_ds.size(), length));
+ }
+
+ void response_sigh(Signal_context_capability) override { }
+
+ size_t obtain_response() override { return 0; }
+
+ public:
+
+ template
+ 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_ */
diff --git a/repos/gems/src/app/touch_keyboard/target.mk b/repos/gems/src/app/touch_keyboard/target.mk
new file mode 100644
index 0000000000..61da68e691
--- /dev/null
+++ b/repos/gems/src/app/touch_keyboard/target.mk
@@ -0,0 +1,4 @@
+TARGET = touch_keyboard
+SRC_CC = main.cc touch_keyboard_dialog.cc
+LIBS += base sandbox
+INC_DIR += $(PRG_DIR)
diff --git a/repos/gems/src/app/touch_keyboard/touch_keyboard_dialog.cc b/repos/gems/src/app/touch_keyboard/touch_keyboard_dialog.cc
new file mode 100644
index 0000000000..47d70011ff
--- /dev/null
+++ b/repos/gems/src/app/touch_keyboard/touch_keyboard_dialog.cc
@@ -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
+
+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)
+{ }
diff --git a/repos/gems/src/app/touch_keyboard/touch_keyboard_dialog.h b/repos/gems/src/app/touch_keyboard/touch_keyboard_dialog.h
new file mode 100644
index 0000000000..73398c27af
--- /dev/null
+++ b/repos/gems/src/app/touch_keyboard/touch_keyboard_dialog.h
@@ -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
+#include
+#include
+#include
+#include
+#include
+
+/* local includes */
+#include
+#include
+
+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::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::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 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