dialog: use one menu_view for multiple dialogs

Issue #5170
This commit is contained in:
Norman Feske 2024-04-03 15:39:43 +02:00 committed by Christian Helmuth
parent d6cb9cf854
commit 9ce7c72c7c
3 changed files with 343 additions and 303 deletions

View File

@ -38,6 +38,8 @@ class Dialog::Sandboxed_runtime : Noncopyable
template <typename T> class Event_handler;
using Start_name = String<128>;
private:
Env &_env;
@ -96,219 +98,15 @@ class Dialog::Sandboxed_runtime : Noncopyable
Rom_service _rom_service;
Report_service _report_service;
public:
Sandboxed_runtime(Env &, Allocator &, Sandbox &);
/**
* Respond to sandbox state changes
*
* \return true if the sandbox configuration needs to be updated
*/
bool apply_sandbox_state(Xml_node const &);
void gen_start_nodes(Xml_generator &) const;
};
class Dialog::Sandboxed_runtime::Report_session : public Session_object<Report::Session>
{
public:
struct Handler : Interface, Genode::Noncopyable
{
virtual void handle_report() = 0;
};
private:
Attached_ram_dataspace _client_ds;
Attached_ram_dataspace _local_ds;
Constructible<Xml_node> _xml { }; /* points inside _local_ds */
Handler &_handler;
/*******************************
** Report::Session interface **
*******************************/
Dataspace_capability dataspace() override { return _client_ds.cap(); }
void submit(size_t length) override
{
size_t const num_bytes = min(_client_ds.size(), length);
memcpy(_local_ds.local_addr<char>(), _client_ds.local_addr<char>(),
num_bytes);
_xml.destruct();
try { _xml.construct(_local_ds.local_addr<char>(), num_bytes); }
catch (...) { }
_handler.handle_report();
}
void response_sigh(Signal_context_capability) override { }
size_t obtain_response() override { return 0; }
public:
template <typename... ARGS>
Report_session(Env &env, Handler &handler,
Entrypoint &ep, Resources const &resources,
ARGS &&... args)
:
Session_object(ep, resources, args...),
_client_ds(env.ram(), env.rm(), resources.ram_quota.value/2),
_local_ds (env.ram(), env.rm(), resources.ram_quota.value/2),
_handler(handler)
{ }
template <typename FN>
void with_xml(FN const &fn) const
{
if (_xml.constructed())
fn(*_xml);
else
fn(Xml_node("<empty/>"));
}
};
class Dialog::Sandboxed_runtime::View : private Views::Element
{
private:
/* needed for privately inheriting 'Views::Element' */
friend class Dictionary<View, Top_level_dialog::Name>;
friend class Avl_node<View>;
friend class Avl_tree<View>;
friend class Sandboxed_runtime;
Env &_env;
Allocator &_alloc;
Event::Seq_number &_global_seq_number;
Optional_event_handler &_optional_event_handler;
Top_level_dialog &_dialog;
bool _dialog_hovered = false; /* used to cut hover feedback loop */
/* sequence numbers to correlate hover info with click/clack events */
Event::Seq_number _hover_seq_number { };
Constructible<Event::Seq_number> _click_seq_number { };
Constructible<Event::Seq_number> _clack_seq_number { };
bool _click_delivered = false; /* used to deliver each click only once */
bool _dragged() const
{
return _click_seq_number.constructed()
&& *_click_seq_number == _global_seq_number
&& _click_delivered;
}
bool _hover_observable_without_click = false;
struct Rom_producer : Dynamic_rom_session::Xml_producer
{
View const &_view;
Rom_producer(View const &view)
:
Dynamic_rom_session::Xml_producer("dialog"),
_view(view)
{ }
void produce_xml(Xml_generator &xml) override
{
_view._with_dialog_hover([&] (Xml_node const &hover) {
Event::Dragged const dragged { _view._dragged() };
bool const supply_hover = _view._hover_observable_without_click
|| dragged.value;
static Xml_node omitted_hover("<hover/>");
At const at { _view._global_seq_number,
supply_hover ? hover : omitted_hover };
Scope<> top_level_scope(xml, at, dragged, { _view._dialog.name });
_view._dialog.view(top_level_scope);
});
}
} _dialog_producer { *this };
Dynamic_rom_session _dialog_rom_session {
_env.ep(), _env.ram(), _env.rm(), _dialog_producer };
template <typename T>
struct Hover_handler : Report_session::Handler
{
T &_obj;
void (T::*_member) ();
Hover_handler(T &obj, void (T::*member)())
: _obj(obj), _member(member) { }
void handle_report() override
{
(_obj.*_member)();
}
};
Constructible<Report_session> _hover_report_session { };
template <typename FN>
void _with_dialog_hover(FN const &fn) const
{
bool done = false;
if (_hover_report_session.constructed())
_hover_report_session->with_xml([&] (Xml_node const &hover) {
hover.with_optional_sub_node("dialog", [&] (Xml_node const &dialog) {
fn(dialog);
done = true; }); });
if (!done)
fn(Xml_node("<empty/>"));
}
void _handle_input_event(Input::Event const &);
void _handle_hover();
Hover_handler<View> _hover_handler { *this, &View::_handle_hover };
void _try_handle_click_and_clack();
struct Menu_view_state
{
using Start_name = String<128>;
Start_name const name;
Ram_quota const initial_ram;
Cap_quota const initial_caps;
Ram_quota const initial_ram { 4*1024*1024 };
Cap_quota const initial_caps { 100 };
Ram_quota ram = initial_ram;
Cap_quota caps = initial_caps;
int xpos = 0, ypos = 0;
unsigned min_width = 0, min_height = 0;
bool opaque = false;
Color background { };
unsigned version = 0;
void trigger_restart()
@ -348,45 +146,226 @@ class Dialog::Sandboxed_runtime::View : private Views::Element
return result;
}
void gen_start_node(Xml_generator &) const;
void gen_start_node(Xml_generator &, Views const &) const;
} _menu_view_state;
class Report_session : public Session_object<Report::Session>
{
public:
struct Handler : Interface, Genode::Noncopyable
{
virtual void handle_report() = 0;
};
private:
Attached_ram_dataspace _client_ds;
Attached_ram_dataspace _local_ds;
Constructible<Xml_node> _xml { }; /* points inside _local_ds */
Handler &_handler;
/*******************************
** Report::Session interface **
*******************************/
Dataspace_capability dataspace() override { return _client_ds.cap(); }
void submit(size_t length) override
{
size_t const num_bytes = min(_client_ds.size(), length);
memcpy(_local_ds.local_addr<char>(), _client_ds.local_addr<char>(),
num_bytes);
_xml.destruct();
try { _xml.construct(_local_ds.local_addr<char>(), num_bytes); }
catch (...) { }
_handler.handle_report();
}
void response_sigh(Signal_context_capability) override { }
size_t obtain_response() override { return 0; }
public:
template <typename... ARGS>
Report_session(Env &env, Handler &handler,
Entrypoint &ep, Resources const &resources,
ARGS &&... args)
:
Session_object(ep, resources, args...),
_client_ds(env.ram(), env.rm(), resources.ram_quota.value/2),
_local_ds (env.ram(), env.rm(), resources.ram_quota.value/2),
_handler(handler)
{ }
template <typename FN>
void with_xml(FN const &fn) const
{
if (_xml.constructed())
fn(*_xml);
else
fn(Xml_node("<empty/>"));
}
};
template <typename T>
struct Hover_handler : Report_session::Handler
{
T &_obj;
void (T::*_member) ();
Hover_handler(T &obj, void (T::*member)())
: _obj(obj), _member(member) { }
void handle_report() override
{
(_obj.*_member)();
}
};
Top_level_dialog::Name _hovered_dialog { };
Hover_handler<Sandboxed_runtime> _hover_handler {
*this, &Sandboxed_runtime::_handle_hover };
void _handle_hover();
Constructible<Report_session> _hover_report_session { };
Event::Seq_number _hover_seq_number { };
public:
struct Attr { Start_name name; };
Sandboxed_runtime(Env &, Allocator &, Sandbox &,
Attr const &attr = { "view" });
/**
* Respond to sandbox state changes
*
* \return true if the sandbox configuration needs to be updated
*/
bool apply_sandbox_state(Xml_node const &);
void gen_start_nodes(Xml_generator &) const;
};
class Dialog::Sandboxed_runtime::View : private Views::Element
{
private:
/* needed for privately inheriting 'Views::Element' */
friend class Dictionary<View, Top_level_dialog::Name>;
friend class Avl_node<View>;
friend class Avl_tree<View>;
friend class Sandboxed_runtime;
Env &_env;
Sandboxed_runtime &_runtime;
Top_level_dialog &_dialog;
bool _dialog_hovered = false;
/* sequence numbers to correlate hover info with click/clack events */
Constructible<Event::Seq_number> _click_seq_number { };
Constructible<Event::Seq_number> _clack_seq_number { };
bool _click_delivered = false; /* used to deliver each click only once */
bool _dragged() const
{
return _click_seq_number.constructed()
&& *_click_seq_number == _runtime._global_seq_number
&& _click_delivered;
}
bool _hover_observable_without_click = false;
void _with_dialog_hover(auto const &fn) const
{
bool done = false;
if (_runtime._hover_report_session.constructed())
_runtime._hover_report_session->with_xml([&] (Xml_node const &hover) {
hover.with_optional_sub_node("dialog", [&] (Xml_node const &dialog) {
fn(dialog);
done = true; }); });
if (!done)
fn(Xml_node("<empty/>"));
}
struct Rom_producer : Dynamic_rom_session::Xml_producer
{
View const &_view;
Rom_producer(View const &view)
:
Dynamic_rom_session::Xml_producer("dialog"),
_view(view)
{ }
void produce_xml(Xml_generator &xml) override
{
_view._with_dialog_hover([&] (Xml_node const &hover) {
Event::Dragged const dragged { _view._dragged() };
bool const supply_hover = _view._hover_observable_without_click
|| dragged.value;
static Xml_node omitted_hover("<hover/>");
At const at { _view._runtime._global_seq_number,
supply_hover ? hover : omitted_hover };
Scope<> top_level_scope(xml, at, dragged, { _view._dialog.name });
_view._dialog.view(top_level_scope);
});
}
} _dialog_producer { *this };
Dynamic_rom_session _dialog_rom_session {
_env.ep(), _env.ram(), _env.rm(), _dialog_producer };
void _gen_menu_view_dialog(Xml_generator &) const;
void _gen_menu_view_routes(Xml_generator &) const;
void _handle_input_event(Input::Event const &);
void _handle_hover();
void _leave();
void _try_handle_click_and_clack();
Registry<Gui_session> _gui_sessions { };
public:
int &xpos = _menu_view_state.xpos;
int &ypos = _menu_view_state.ypos;
unsigned &min_width = _menu_view_state.min_width;
unsigned &min_height = _menu_view_state.min_height;
bool &opaque = _menu_view_state.opaque;
Color &background = _menu_view_state.background;
struct Attr
{
bool opaque;
Ram_quota initial_ram;
};
View(Sandboxed_runtime &runtime, Top_level_dialog &dialog, Attr const attr)
:
Views::Element(runtime._views, dialog.name),
_env(runtime._env), _alloc(runtime._alloc),
_global_seq_number(runtime._global_seq_number),
_optional_event_handler(runtime._optional_event_handler),
_dialog(dialog),
_menu_view_state({
.name = dialog.name,
.initial_ram = attr.initial_ram,
.initial_caps = Cap_quota { 200 }
})
{ }
int xpos { };
int ypos { };
unsigned min_width { };
unsigned min_height { };
bool opaque { };
Color background { };
View(Sandboxed_runtime &runtime, Top_level_dialog &dialog)
:
View(runtime, dialog, Attr { .opaque = false,
.initial_ram = { 4*1024*1024 } })
Views::Element(runtime._views, dialog.name),
_env(runtime._env), _runtime(runtime), _dialog(dialog)
{ }
~View();

View File

@ -44,10 +44,7 @@ struct Touch_keyboard::Main : Top_level_dialog
Runtime _runtime { _env, _heap };
Runtime::View _view { _runtime, *this, Runtime::View::Attr {
.opaque = true,
.initial_ram = Ram_quota { 4*1024*1024 }
} };
Runtime::View _view { _runtime, *this };
/*
* Top_level_dialog interface

View File

@ -82,8 +82,9 @@ struct Sandboxed_runtime::Gui_session : Session_object<Gui::Session>
if (clack(ev)) _clicked = false;
if (orig_clicked != _clicked) {
_view._global_seq_number.value++;
_input_component.submit(Input::Seq_number { _view._global_seq_number.value });
Event::Seq_number &global_seq = _view._runtime._global_seq_number;
global_seq.value++;
_input_component.submit(Input::Seq_number { global_seq.value });
}
/* local event (click/clack) handling */
@ -161,12 +162,14 @@ struct Sandboxed_runtime::Gui_session : Session_object<Gui::Session>
};
Sandboxed_runtime::Sandboxed_runtime(Env &env, Allocator &alloc, Sandbox &sandbox)
Sandboxed_runtime::Sandboxed_runtime(Env &env, Allocator &alloc, Sandbox &sandbox,
Attr const &attr)
:
_env(env), _alloc(alloc), _sandbox(sandbox),
_gui_service (_sandbox, _gui_handler),
_rom_service (_sandbox, _rom_handler),
_report_service(_sandbox, _report_handler)
_report_service(_sandbox, _report_handler),
_menu_view_state { .name = attr.name }
{ }
@ -175,14 +178,8 @@ bool Sandboxed_runtime::apply_sandbox_state(Xml_node const &state)
bool reconfiguration_needed = false;
state.for_each_sub_node("child", [&] (Xml_node const &child) {
using Name = Top_level_dialog::Name;
Name const name = child.attribute_value("name", Name());
_views.with_element(name,
[&] (View &view) {
if (view._menu_view_state.apply_child_state_report(child))
reconfiguration_needed = true; },
[&] /* no view named after this child */ { });
});
if (_menu_view_state.apply_child_state_report(child))
reconfiguration_needed = true; });
return reconfiguration_needed;
}
@ -191,12 +188,10 @@ bool Sandboxed_runtime::apply_sandbox_state(Xml_node const &state)
void Sandboxed_runtime::_handle_rom_service()
{
_rom_service.for_each_requested_session([&] (Rom_service::Request &request) {
if (request.label.last_element() == "dialog") {
_views.with_element(request.label.prefix(),
[&] (View &view) {
request.deliver_session(view._dialog_rom_session); },
[&] /* no view named after this child */ { });
}
_views.with_element(request.label.last_element(),
[&] (View &view) {
request.deliver_session(view._dialog_rom_session); },
[&] { });
});
_rom_service.for_each_session_to_close([&] (Dynamic_rom_session &) {
@ -209,14 +204,10 @@ void Sandboxed_runtime::_handle_rom_service()
void Sandboxed_runtime::_handle_report_service()
{
_report_service.for_each_requested_session([&] (Report_service::Request &request) {
if (request.label.last_element() == "hover") {
_views.with_element(request.label.prefix(),
[&] (View &view) {
view._hover_report_session.construct(_env, view._hover_handler, _env.ep(),
request.resources, "", request.diag);
request.deliver_session(*view._hover_report_session);
},
[&] /* no view named after this child */ { });
if (request.label == Start_name { _menu_view_state.name, " -> hover" }) {
_hover_report_session.construct(_env, _hover_handler, _env.ep(),
request.resources, "", request.diag);
request.deliver_session(*_hover_report_session);
}
});
@ -230,16 +221,16 @@ void Sandboxed_runtime::_handle_report_service()
void Sandboxed_runtime::_handle_gui_service()
{
_gui_service.for_each_requested_session([&] (Gui_service::Request &request) {
_views.with_element(request.label.prefix(),
_views.with_element(request.label.last_element(),
[&] (View &view) {
Gui_session &session = *new (_alloc)
Gui_session(_env, view, _env.ep(),
request.resources, "", request.diag);
request.deliver_session(session);
},
[&] {
warning("unexpected GUI-sesssion request, label=", request.label);
});
[&] {
warning("unexpected GUI-sesssion request, label=", request.label);
});
});
_gui_service.for_each_upgraded_session([&] (Gui_session &session,
@ -257,12 +248,11 @@ void Sandboxed_runtime::_handle_gui_service()
void Sandboxed_runtime::gen_start_nodes(Xml_generator &xml) const
{
_views.for_each([&] (View const &view) {
view._menu_view_state.gen_start_node(xml); });
_menu_view_state.gen_start_node(xml, _views);
}
void Sandboxed_runtime::View::Menu_view_state::gen_start_node(Xml_generator &xml) const
void Sandboxed_runtime::Menu_view_state::gen_start_node(Xml_generator &xml, Views const &views) const
{
xml.node("start", [&] () {
@ -280,14 +270,6 @@ void Sandboxed_runtime::View::Menu_view_state::gen_start_node(Xml_generator &xml
xml.node("config", [&] () {
if (xpos) xml.attribute("xpos", xpos);
if (ypos) 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"); });
@ -308,15 +290,15 @@ void Sandboxed_runtime::View::Menu_view_state::gen_start_node(Xml_generator &xml
});
});
});
views.for_each([&] (View const &view) {
view._gen_menu_view_dialog(xml); });
});
xml.node("route", [&] () {
xml.node("service", [&] () {
xml.attribute("name", "ROM");
xml.attribute("label", "dialog");
xml.node("local", [&] () { });
});
views.for_each([&] (View const &view) {
view._gen_menu_view_routes(xml); });
xml.node("service", [&] () {
xml.attribute("name", "Report");
@ -336,44 +318,118 @@ void Sandboxed_runtime::View::Menu_view_state::gen_start_node(Xml_generator &xml
xml.attribute("label", "fonts"); });
});
xml.node("any-service", [&] () {
xml.node("parent", [&] () { }); });
auto parent_route = [&] (auto const &service)
{
xml.node("service", [&] {
xml.attribute("name", service);
xml.node("parent", [&] { }); });
};
parent_route("PD");
parent_route("CPU");
parent_route("LOG");
parent_route("Timer");
auto parent_rom_route = [&] (auto const &name)
{
xml.node("service", [&] () {
xml.attribute("name", "ROM");
xml.attribute("label_last", name);
xml.node("parent", [&] { }); });
};
parent_rom_route("menu_view");
parent_rom_route("ld.lib.so");
parent_rom_route("libc.lib.so");
parent_rom_route("libm.lib.so");
parent_rom_route("libpng.lib.so");
parent_rom_route("zlib.lib.so");
parent_rom_route("vfs.lib.so");
parent_rom_route("menu_view_styles.tar");
});
});
}
void Sandboxed_runtime::View::_gen_menu_view_dialog(Xml_generator &xml) const
{
xml.node("dialog", [&] {
xml.attribute("name", name);
if (xpos) xml.attribute("xpos", xpos);
if (ypos) 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));
});
}
void Sandboxed_runtime::View::_gen_menu_view_routes(Xml_generator &xml) const
{
xml.node("service", [&] {
xml.attribute("name", "ROM");
xml.attribute("label", name);
xml.node("local", [&] { });
});
}
void Sandboxed_runtime::View::_handle_input_event(Input::Event const &event)
{
if (event.absolute_motion()) _hover_observable_without_click = true;
if (event.touch()) _hover_observable_without_click = false;
Event::Seq_number const global_seq = _runtime._global_seq_number;
if (click(event) && !_click_seq_number.constructed()) {
_click_seq_number.construct(_global_seq_number);
_click_seq_number.construct(global_seq);
_click_delivered = false;
}
if (clack(event))
_clack_seq_number.construct(_global_seq_number);
_clack_seq_number.construct(global_seq);
_try_handle_click_and_clack();
_optional_event_handler.handle_event(Event { _global_seq_number, event });
_runtime._optional_event_handler.handle_event(Event { global_seq, event });
}
void Sandboxed_runtime::_handle_hover()
{
if (!_hover_report_session.constructed())
return;
using Name = Top_level_dialog::Name;
Name const orig_hovered_dialog = _hovered_dialog;
_hover_report_session->with_xml([&] (Xml_node const &hover) {
_hover_seq_number = { hover.attribute_value("seq_number", 0U) };
hover.with_sub_node("dialog",
[&] (Xml_node const &dialog) {
_hovered_dialog = dialog.attribute_value("name", Name()); },
[&] { _hovered_dialog = { }; });
});
if (orig_hovered_dialog.valid() && orig_hovered_dialog != _hovered_dialog)
_views.with_element(orig_hovered_dialog,
[&] (View &view) { view._leave(); },
[&] { });
if (_hovered_dialog.valid())
_views.with_element(_hovered_dialog,
[&] (View &view) { view._handle_hover(); },
[&] { });
}
void Sandboxed_runtime::View::_handle_hover()
{
bool const orig_dialog_hovered = _dialog_hovered;
if (_hover_report_session.constructed())
_hover_report_session->with_xml([&] (Xml_node const &hover) {
_hover_seq_number = { hover.attribute_value("seq_number", 0U) };
_dialog_hovered = (hover.num_sub_nodes() > 0);
});
if (orig_dialog_hovered != _dialog_hovered || _dialog_hovered)
_dialog_rom_session.trigger_update();
_dialog_hovered = true;
if (_click_delivered && _click_seq_number.constructed()) {
_with_dialog_hover([&] (Xml_node const &hover) {
@ -382,16 +438,24 @@ void Sandboxed_runtime::View::_handle_hover()
});
}
_dialog_rom_session.trigger_update();
_try_handle_click_and_clack();
}
void Sandboxed_runtime::View::_leave()
{
_dialog_hovered = false;
_dialog_rom_session.trigger_update();
}
void Sandboxed_runtime::View::_try_handle_click_and_clack()
{
Constructible<Event::Seq_number> &click = _click_seq_number,
&clack = _clack_seq_number;
if (!_click_delivered && click.constructed() && *click == _hover_seq_number) {
if (!_click_delivered && click.constructed() && *click == _runtime._hover_seq_number) {
_with_dialog_hover([&] (Xml_node const &hover) {
Clicked_at at(*click, hover);
_dialog.click(at);
@ -399,7 +463,7 @@ void Sandboxed_runtime::View::_try_handle_click_and_clack()
});
}
if (click.constructed() && clack.constructed() && *clack== _hover_seq_number) {
if (click.constructed() && clack.constructed() && *clack == _runtime._hover_seq_number) {
_with_dialog_hover([&] (Xml_node const &hover) {
/* use click seq number for to associate clack with click */
Clacked_at at(*click, hover);
@ -415,5 +479,5 @@ void Sandboxed_runtime::View::_try_handle_click_and_clack()
Sandboxed_runtime::View::~View()
{
_gui_sessions.for_each([&] (Gui_session &session) {
destroy(_alloc, &session); });
destroy(_runtime._alloc, &session); });
}