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::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 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 _maps { }; + + Map::Name _current_map = "lower"; + + Constructible _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_ */ diff --git a/repos/gems/src/app/touch_keyboard/types.h b/repos/gems/src/app/touch_keyboard/types.h new file mode 100644 index 0000000000..3a3ada4911 --- /dev/null +++ b/repos/gems/src/app/touch_keyboard/types.h @@ -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_ */