touch_keyboard: use dialog API

Fixes #5059
This commit is contained in:
Norman Feske 2023-11-16 19:16:03 +01:00 committed by Christian Helmuth
parent 9144d47fe2
commit 769a6ce987
11 changed files with 361 additions and 1012 deletions

View File

@ -1,124 +0,0 @@
/*
* \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

@ -1,141 +0,0 @@
/*
* \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

@ -1,28 +0,0 @@
/*
* \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

@ -13,26 +13,23 @@
/* 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>
#include <dialog/runtime.h>
#include <util/color.h>
/* local includes */
#include <gui.h>
#include <report.h>
#include <touch_keyboard_dialog.h>
#include <child_state.h>
#include <touch_keyboard_widget.h>
namespace Touch_keyboard { struct Main; }
namespace Touch_keyboard {
struct Touch_keyboard::Main : Sandbox::Local_service_base::Wakeup,
Sandbox::State_handler,
Gui::Input_event_handler,
Dialog::Event_emitter
using namespace Dialog;
struct Main;
}
struct Touch_keyboard::Main : Top_level_dialog
{
Env &_env;
@ -41,238 +38,43 @@ struct Touch_keyboard::Main : Sandbox::Local_service_base::Wakeup,
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;
bool _opaque = false;
Color _background { };
Registry<Child_state> _children { };
Child_state _menu_view_child_state { _children, "menu_view",
Ram_quota { 10*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_optional_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);
if (_opaque) xml.attribute("opaque", "yes");
xml.attribute("background", String<20>(_background));
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;
});
}
Hosted<Touch_keyboard_widget> _keyboard { Id { "keyboard" }, _heap };
Event::Connection _event_connection { _env };
/**
* Dialog::Event_emitter interface
Runtime _runtime { _env, _heap };
Runtime::View _view { _runtime, *this, Runtime::View::Attr {
.opaque = true,
.initial_ram = Ram_quota { 4*1024*1024 }
} };
/*
* Top_level_dialog interface
*/
void emit_characters(Dialog::Emit const &characters) override
void view(Scope<> &s) const override { s.widget(_keyboard); }
void click(Clicked_at const &at) override { _keyboard.propagate(at); }
void clack(Clacked_at const &at) override
{
_event_connection.with_batch([&] (Event::Session_client::Batch &batch) {
_keyboard.propagate(at, [&] (Touch_keyboard_widget::Emit const &characters) {
_event_connection.with_batch([&] (Event::Session_client::Batch &batch) {
Utf8_ptr utf8_ptr(characters.string());
Utf8_ptr utf8_ptr(characters.string());
for (; utf8_ptr.complete(); utf8_ptr = utf8_ptr.next()) {
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 } );
}
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 drag (Dragged_at const &) override { }
void _handle_config()
{
@ -281,38 +83,28 @@ struct Touch_keyboard::Main : Sandbox::Local_service_base::Wakeup,
Xml_node const config = _config.xml();
_xpos = (int)config.attribute_value("xpos", 0L);
_ypos = (int)config.attribute_value("ypos", 0L);
_view.xpos = (int)config.attribute_value("xpos", 0L);
_view.ypos = (int)config.attribute_value("ypos", 0L);
_min_width = config.attribute_value("min_width", 0U);
_min_height = config.attribute_value("min_height", 0U);
_view.min_width = config.attribute_value("min_width", 0U);
_view.min_height = config.attribute_value("min_height", 0U);
_opaque = config.attribute_value("opaque", false);
_background = config.attribute_value("background", Color(127, 127, 127, 255));
_view.opaque = config.attribute_value("opaque", false);
_view.background = config.attribute_value("background", Color(127, 127, 127, 255));
_dialog.configure(_layout.xml());
_keyboard.configure(_layout.xml());
_runtime.update_view_config();
}
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)
Main(Env &env) : Top_level_dialog("touch_keyboard"), _env(env)
{
_config.sigh(_config_handler);
_layout.sigh(_config_handler);
_handle_config();
_update_sandbox_config();
}
};

View File

@ -1,89 +0,0 @@
/*
* \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

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

View File

@ -1,144 +0,0 @@
/*
* \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);
if (key.small)
xml.attribute("font", "annotation/regular");
});
});
});
});
};
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); }); });
};
_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_optional_sub_node("vbox", [&] (Xml_node const &vbox) {
vbox.with_optional_sub_node("hbox", [&] (Xml_node const &hbox) {
hbox.with_optional_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

@ -1,208 +0,0 @@
/*
* \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;
bool small = false;
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);
small = key.attribute_value("small", false);
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)
{
keys.update_from_xml(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)
{
rows.update_from_xml(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);
_maps.update_from_xml(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,77 @@
/*
* \brief Touch-screen keyboard widget
* \author Norman Feske
* \date 2022-01-11
*/
/*
* Copyright (C) 2022-2023 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.
*/
#include <touch_keyboard_widget.h>
using namespace Dialog;
void Touch_keyboard_widget::Key::view(Scope<Vbox> &s, Attr const &attr) const
{
s.widget(_button, [&] (Scope<Button> &s) {
if (map.length() > 1)
s.attribute("style", "unimportant");
if (text.length() == 1)
s.attribute("style", "invisible");
s.sub_scope<Vbox>([&] (Scope<Button, Vbox> &s) {
unsigned const ex = min_ex ? min_ex : attr.default_key_min_ex.value;
if (ex)
s.sub_scope<Min_ex>(ex);
s.sub_scope<Label>(text, [&] (auto &s) {
if (small)
s.attribute("font", "annotation/regular"); });
});
});
}
void Touch_keyboard_widget::Row::view(Scope<Hbox> &s, Attr const &attr) const
{
keys.for_each([&] (Hosted_key const &key) {
s.widget(key, Key::Attr {
.default_key_min_ex = attr.default_key_min_ex }); });
}
void Touch_keyboard_widget::view(Scope<Vbox> &s) const
{
_with_current_map(*this, [&] (Map const &map) {
map.rows.for_each([&] (Map::Hosted_row const &row) {
s.widget(row, Row::Attr {
.default_key_min_ex = _default_key_min_ex }); }); });
}
void Touch_keyboard_widget::click(Clicked_at const &at)
{
Key::Map next_map = _current_map;
_with_current_map(*this, [&] (Map &map) {
map.rows.for_each([&] (Map::Hosted_row &row) {
row.propagate(at, [&] (Key &key) {
_emit_on_clack = key.emit;
if (key.map.length() > 1)
next_map = key.map;
});
});
});
_current_map = next_map;
}

View File

@ -0,0 +1,235 @@
/*
* \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_
#include <base/allocator.h>
#include <util/list_model.h>
#include <dialog/widgets.h>
namespace Dialog { struct Touch_keyboard_widget; }
struct Dialog::Touch_keyboard_widget : Widget<Vbox>
{
public:
using Emit = String<8>;
private:
Allocator &_alloc;
struct Default_key_min_ex { unsigned value; };
Default_key_min_ex _default_key_min_ex { 0 };
static Id _id_attr(Xml_node const &node)
{
return { node.attribute_value("id", Id::Value { }) };
}
struct Key : Widget<Vbox>
{
Id const _id;
using Text = String<8>;
Text text { };
Emit emit { };
using Map = String<8>;
Map map { };
unsigned min_ex = 0;
bool small = false;
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"; }
Hosted<Vbox, Action_button> _button { Id { } };
void update(Xml_node const &key) {
text = { };
emit = { };
map = key.attribute_value("map", Map());
min_ex = key.attribute_value("min_ex", 0U);
small = key.attribute_value("small", false);
if (key.has_attribute("char")) {
text = key.attribute_value("char", Text());
emit = Emit(Xml_unquoted(text));
}
if (key.has_attribute("code")) {
Codepoint c { key.attribute_value("code", 0U) };
emit = Emit(c);
text = emit;
}
if (key.has_attribute("label"))
text = key.attribute_value("label", Text());
}
struct Attr { Default_key_min_ex default_key_min_ex; };
void view(Scope<Vbox> &s, Attr const &attr) const;
void click(Clicked_at const &at, auto const &fn)
{
_button.propagate(at, [&] { /* drive selected state */ });
fn(*this);
}
};
struct Row : Widget<Hbox>
{
Allocator &_alloc;
Id const _id;
struct Hosted_key : List_model<Hosted_key>::Element, Hosted<Hbox, Key>
{
using Hosted::Hosted;
};
List_model<Hosted_key> keys { };
Row(Allocator &alloc, Id const 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)
{
keys.update_from_xml(row,
/* create */
[&] (Xml_node const &node) -> Hosted_key & {
Id const id = _id_attr(node);
return *new (_alloc) Hosted_key { id, id }; },
/* destroy */
[&] (Hosted_key &key) { destroy(_alloc, &key); },
/* update */
[&] (Hosted_key &key, Xml_node const &node) { key.update(node); });
}
struct Attr { Default_key_min_ex default_key_min_ex; };
void view(Scope<Hbox> &, Attr const &) const;
void click(Clicked_at const &at, auto const &fn)
{
keys.for_each([&] (Hosted_key &key) {
key.propagate(at, fn); });
}
};
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()); }
struct Hosted_row : List_model<Hosted_row>::Element, Hosted<Vbox, Row>
{
using Hosted::Hosted;
};
List_model<Hosted_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)
{
rows.update_from_xml(map,
/* create */
[&] (Xml_node const &node) -> Hosted_row & {
Id const id = _id_attr(node);
return *new (_alloc) Hosted_row { id, _alloc, id }; },
/* destroy */
[&] (Hosted_row &row) { destroy(_alloc, &row); },
/* update */
[&] (Hosted_row &row, Xml_node const &node) { row.update(node); });
}
};
List_model<Map> _maps { };
Map::Name _current_map = "lower";
Emit _emit_on_clack { };
static void _with_current_map(auto &keyboard, auto const &fn)
{
keyboard._maps.for_each([&] (auto &map) {
if (map.name == keyboard._current_map)
fn(map); });
}
public:
Touch_keyboard_widget(Allocator &alloc) : _alloc(alloc) { }
void configure(Xml_node const &config)
{
_default_key_min_ex = { config.attribute_value("key_min_ex", 0U) };
_maps.update_from_xml(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 view(Scope<Vbox> &s) const;
void click(Clicked_at const &);
void clack(Clacked_at const &, auto const &fn)
{
if (_emit_on_clack.length() > 1) {
fn(_emit_on_clack);
_emit_on_clack = { };
}
}
};
#endif /* _TOUCH_KEYBOARD_DIALOG_H_ */

View File

@ -1,21 +0,0 @@
/*
* \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_ */