mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-21 10:01:57 +00:00
sculpt: add Dialog::Distant_runtime
The so-called 'Distant_runtime' implements GUI dialogs via menu_view components hosted at a distant init instance as opposed to child components (as implemented by the 'Sandboxed_runtime'). This is particular the case in Sculpt OS where the sculpt manager is not the parent of the menu_view instances. Issue #5008
This commit is contained in:
parent
9d5af71c3d
commit
0c40d52010
254
repos/gems/src/app/sculpt_manager/dialog/distant_runtime.cc
Normal file
254
repos/gems/src/app/sculpt_manager/dialog/distant_runtime.cc
Normal file
@ -0,0 +1,254 @@
|
||||
/*
|
||||
* \brief Runtime for hosting GUI dialogs in child components
|
||||
* \author Norman Feske
|
||||
* \date 2023-03-24
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 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 <dialog/distant_runtime.h>
|
||||
#include <xml.h>
|
||||
|
||||
using namespace Sculpt;
|
||||
using namespace Dialog;
|
||||
|
||||
|
||||
static bool click(Input::Event const &event)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (event.key_press(Input::BTN_LEFT))
|
||||
result = true;
|
||||
|
||||
event.handle_touch([&] (Input::Touch_id id, float, float) {
|
||||
if (id.value == 0)
|
||||
result = true; });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static bool clack(Input::Event const &event)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (event.key_release(Input::BTN_LEFT))
|
||||
result = true;
|
||||
|
||||
event.handle_touch_release([&] (Input::Touch_id id) {
|
||||
if (id.value == 0)
|
||||
result = true; });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool Distant_runtime::apply_runtime_state(Xml_node const &state)
|
||||
{
|
||||
using Name = Top_level_dialog::Name;
|
||||
|
||||
/* the dialog name is the start name with the "_view" suffix removed */
|
||||
auto with_dialog_name = [] (Start_name const &name, auto const &fn)
|
||||
{
|
||||
if (name.length() > 6) {
|
||||
size_t const dialog_name_len = name.length() - 6;
|
||||
char const * const view_suffix_ptr = name.string() + dialog_name_len;
|
||||
if (strcmp(view_suffix_ptr, "_view") == 0)
|
||||
fn(Name(Cstring(name.string(), dialog_name_len)));
|
||||
}
|
||||
};
|
||||
|
||||
bool reconfiguration_needed = false;
|
||||
state.for_each_sub_node("child", [&] (Xml_node const &child) {
|
||||
Start_name const start_name = child.attribute_value("name", Start_name());
|
||||
with_dialog_name(start_name, [&] (Name const &name) {
|
||||
_views.with_element(name,
|
||||
[&] (View &view) {
|
||||
if (view._apply_child_state_report(child))
|
||||
reconfiguration_needed = true; },
|
||||
[&] /* no view named after this child */ { });
|
||||
});
|
||||
});
|
||||
|
||||
return reconfiguration_needed;
|
||||
}
|
||||
|
||||
|
||||
void Distant_runtime::gen_start_nodes(Xml_generator &xml) const
|
||||
{
|
||||
_views.for_each([&] (View const &view) {
|
||||
view._gen_start_node(xml); });
|
||||
}
|
||||
|
||||
|
||||
void Distant_runtime::route_input_event(Event::Seq_number seq_number, Input::Event const &event)
|
||||
{
|
||||
_global_seq_number = seq_number;
|
||||
|
||||
if (event.absolute_motion()) _hover_observable_without_click = true;
|
||||
if (event.touch()) _hover_observable_without_click = false;
|
||||
|
||||
if (click(event) && !_click_seq_number.constructed()) {
|
||||
_click_seq_number.construct(_global_seq_number);
|
||||
_click_delivered = false;
|
||||
}
|
||||
|
||||
if (clack(event))
|
||||
_clack_seq_number.construct(_global_seq_number);
|
||||
|
||||
_try_handle_click_and_clack();
|
||||
}
|
||||
|
||||
|
||||
void Distant_runtime::_try_handle_click_and_clack()
|
||||
{
|
||||
auto with_hovered_view = [&] (Event::Seq_number seq_number, auto const &fn)
|
||||
{
|
||||
/* find name of dialog hovered with matching 'seq_number' */
|
||||
Top_level_dialog::Name name { };
|
||||
_views.for_each([&] (View const &view) {
|
||||
if (seq_number == view._hover_seq_number)
|
||||
name = view._dialog.name; });
|
||||
|
||||
/* apply 'fn' with (non-const) view as argument */
|
||||
if (name.valid())
|
||||
_views.with_element(name,
|
||||
[&] (View &view) { fn(view); },
|
||||
[&] { });
|
||||
};
|
||||
|
||||
Constructible<Event::Seq_number> &click = _click_seq_number,
|
||||
&clack = _clack_seq_number;
|
||||
|
||||
if (!_click_delivered && click.constructed()) {
|
||||
with_hovered_view(*click, [&] (View &view) {
|
||||
view._with_dialog_hover([&] (Xml_node const &hover) {
|
||||
Clicked_at at(*click, hover);
|
||||
view._dialog.click(at);
|
||||
_click_delivered = true;
|
||||
view.refresh();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (click.constructed() && clack.constructed()) {
|
||||
with_hovered_view(*clack, [&] (View &view) {
|
||||
view._with_dialog_hover([&] (Xml_node const &hover) {
|
||||
|
||||
/*
|
||||
* Deliver stale click if the hover report for the clack
|
||||
* overwrote the intermediate hover report for the click.
|
||||
*/
|
||||
if (!_click_delivered) {
|
||||
Clicked_at at(*click, hover);
|
||||
view._dialog.click(at);
|
||||
_click_delivered = true;
|
||||
}
|
||||
|
||||
/* use click seq number for to associate clack with click */
|
||||
Clacked_at at(*click, hover);
|
||||
view._dialog.clack(at);
|
||||
view.refresh();
|
||||
});
|
||||
|
||||
click.destruct();
|
||||
clack.destruct();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Distant_runtime::View::_gen_start_node(Xml_generator &xml) const
|
||||
{
|
||||
xml.node("start", [&] {
|
||||
|
||||
xml.attribute("name", _start_name);
|
||||
xml.attribute("version", _version);
|
||||
xml.attribute("caps", _caps.value);
|
||||
|
||||
xml.node("resource", [&] {
|
||||
xml.attribute("name", "RAM");
|
||||
Number_of_bytes const bytes(_ram.value);
|
||||
xml.attribute("quantum", String<64>(bytes)); });
|
||||
|
||||
xml.node("binary", [&] {
|
||||
xml.attribute("name", "menu_view"); });
|
||||
|
||||
xml.node("heartbeat", [&] { });
|
||||
|
||||
xml.node("config", [&] {
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
using Label = Session_label::String;
|
||||
|
||||
xml.node("route", [&] {
|
||||
gen_parent_rom_route(xml, "menu_view");
|
||||
gen_parent_rom_route(xml, "ld.lib.so");
|
||||
gen_parent_rom_route(xml, "vfs.lib.so");
|
||||
gen_parent_rom_route(xml, "libc.lib.so");
|
||||
gen_parent_rom_route(xml, "libm.lib.so");
|
||||
gen_parent_rom_route(xml, "libpng.lib.so");
|
||||
gen_parent_rom_route(xml, "zlib.lib.so");
|
||||
gen_parent_rom_route(xml, "menu_view_styles.tar");
|
||||
gen_parent_route<Cpu_session> (xml);
|
||||
gen_parent_route<Pd_session> (xml);
|
||||
gen_parent_route<Log_session> (xml);
|
||||
gen_parent_route<Timer::Session> (xml);
|
||||
|
||||
gen_service_node<Gui::Session>(xml, [&] {
|
||||
xml.node("parent", [&] {
|
||||
xml.attribute("label", Label("leitzentrale -> ", _start_name)); }); });
|
||||
|
||||
gen_service_node<Rom_session>(xml, [&] {
|
||||
xml.attribute("label", "dialog");
|
||||
xml.node("parent", [&] {
|
||||
xml.attribute("label", Label("leitzentrale -> ", _start_name, " -> dialog"));
|
||||
});
|
||||
});
|
||||
|
||||
gen_service_node<Report::Session>(xml, [&] {
|
||||
xml.attribute("label", "hover");
|
||||
xml.node("parent", [&] {
|
||||
xml.attribute("label", Label("leitzentrale -> ", _start_name, " -> hover"));
|
||||
});
|
||||
});
|
||||
|
||||
gen_service_node<::File_system::Session>(xml, [&] {
|
||||
xml.attribute("label", "fonts");
|
||||
xml.node("parent", [&] {
|
||||
xml.attribute("label", "leitzentrale -> fonts"); }); });
|
||||
});
|
||||
});
|
||||
}
|
259
repos/gems/src/app/sculpt_manager/dialog/distant_runtime.h
Normal file
259
repos/gems/src/app/sculpt_manager/dialog/distant_runtime.h
Normal file
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* \brief Runtime for hosting GUI dialogs in distant menu-view instances
|
||||
* \author Norman Feske
|
||||
* \date 2023-07-19
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
#ifndef _DIALOG__DISTANT_RUNTIME_H_
|
||||
#define _DIALOG__DISTANT_RUNTIME_H_
|
||||
|
||||
#include <os/reporter.h>
|
||||
#include <base/attached_rom_dataspace.h>
|
||||
#include <util/dictionary.h>
|
||||
#include <util/color.h>
|
||||
#include <dialog/types.h>
|
||||
|
||||
namespace Dialog { struct Distant_runtime; }
|
||||
|
||||
|
||||
class Dialog::Distant_runtime : Noncopyable
|
||||
{
|
||||
public:
|
||||
|
||||
class View;
|
||||
|
||||
struct Event_handler_base : Interface, Noncopyable
|
||||
{
|
||||
virtual void handle_event(Event const &event) = 0;
|
||||
};
|
||||
|
||||
template <typename T> class Event_handler;
|
||||
|
||||
private:
|
||||
|
||||
Env &_env;
|
||||
|
||||
using Views = Dictionary<View, Top_level_dialog::Name>;
|
||||
|
||||
Event::Seq_number _global_seq_number { 1 };
|
||||
|
||||
Views _views { };
|
||||
|
||||
/* 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 == _global_seq_number
|
||||
&& _click_delivered;
|
||||
}
|
||||
|
||||
/* true when using a pointer device, false when using touch */
|
||||
bool _hover_observable_without_click = false;
|
||||
|
||||
void _try_handle_click_and_clack();
|
||||
|
||||
public:
|
||||
|
||||
Distant_runtime(Env &env) : _env(env) { }
|
||||
|
||||
/**
|
||||
* Route input event to the 'Top_level_dialog' click/clack interfaces
|
||||
*/
|
||||
void route_input_event(Event::Seq_number, Input::Event const &);
|
||||
|
||||
/**
|
||||
* Respond to runtime-init state changes
|
||||
*
|
||||
* \return true if the runtime-init configuration needs to be updated
|
||||
*/
|
||||
bool apply_runtime_state(Xml_node const &);
|
||||
|
||||
void gen_start_nodes(Xml_generator &) const;
|
||||
};
|
||||
|
||||
|
||||
class Dialog::Distant_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 Distant_runtime;
|
||||
|
||||
using Start_name = Session_label::String;
|
||||
|
||||
Env &_env;
|
||||
Distant_runtime &_runtime;
|
||||
Top_level_dialog &_dialog;
|
||||
|
||||
Start_name const _start_name { _dialog.name, "_view" };
|
||||
Ram_quota const _initial_ram { 4*1024*1024 };
|
||||
Cap_quota const _initial_caps { 200 };
|
||||
|
||||
bool const _opaque;
|
||||
Color const _background;
|
||||
|
||||
Ram_quota _ram = _initial_ram;
|
||||
Cap_quota _caps = _initial_caps;
|
||||
|
||||
unsigned _version = 0;
|
||||
|
||||
Expanding_reporter _dialog_reporter {
|
||||
_env, "dialog", { _dialog.name, "_dialog" } };
|
||||
|
||||
Attached_rom_dataspace _hover_rom {
|
||||
_env, Session_label::String(_dialog.name, "_view_hover").string() };
|
||||
|
||||
Signal_handler<View> _hover_handler { _env.ep(), *this, &View::_handle_hover };
|
||||
|
||||
bool _dialog_hovered = false; /* used to cut hover feedback loop */
|
||||
|
||||
Event::Seq_number _hover_seq_number { };
|
||||
|
||||
template <typename FN>
|
||||
void _with_dialog_hover(FN const &fn) const
|
||||
{
|
||||
bool done = false;
|
||||
|
||||
_hover_rom.xml().with_optional_sub_node("dialog", [&] (Xml_node const &dialog) {
|
||||
fn(dialog);
|
||||
done = true; });
|
||||
|
||||
if (!done)
|
||||
fn(Xml_node("<empty/>"));
|
||||
}
|
||||
|
||||
void _handle_hover()
|
||||
{
|
||||
_hover_rom.update();
|
||||
|
||||
Xml_node const hover = _hover_rom.xml();
|
||||
|
||||
bool const orig_dialog_hovered = _dialog_hovered;
|
||||
|
||||
_hover_seq_number = { hover.attribute_value("seq_number", 0U) };
|
||||
_dialog_hovered = (hover.num_sub_nodes() > 0);
|
||||
|
||||
if (_runtime._dragged()) {
|
||||
_with_dialog_hover([&] (Xml_node const &hover) {
|
||||
Dragged_at at(*_runtime._click_seq_number, hover);
|
||||
_dialog.drag(at);
|
||||
});
|
||||
}
|
||||
|
||||
_runtime._try_handle_click_and_clack();
|
||||
|
||||
if (orig_dialog_hovered != _dialog_hovered || _dialog_hovered)
|
||||
_generate_dialog();
|
||||
}
|
||||
|
||||
Signal_handler<View> _refresh_handler { _env.ep(), *this, &View::_generate_dialog };
|
||||
|
||||
void _generate_dialog()
|
||||
{
|
||||
_dialog_reporter.generate([&] (Xml_generator &xml) {
|
||||
_with_dialog_hover([&] (Xml_node const &hover) {
|
||||
|
||||
Event::Dragged const dragged { _runtime._dragged() };
|
||||
|
||||
bool const supply_hover = _runtime._hover_observable_without_click
|
||||
|| dragged.value;
|
||||
|
||||
static Xml_node omitted_hover("<hover/>");
|
||||
|
||||
At const at { _runtime._global_seq_number,
|
||||
supply_hover ? hover : omitted_hover };
|
||||
|
||||
Scope<> top_level_scope(xml, at, dragged, { _dialog.name });
|
||||
_dialog.view(top_level_scope);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 sandbox state report
|
||||
* \return true if runtime must be reconfigured so that the changes
|
||||
* can take effect
|
||||
*/
|
||||
bool _apply_child_state_report(Xml_node const &child)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (child.attribute_value("name", Start_name()) != _start_name)
|
||||
return false;
|
||||
|
||||
if (child.has_sub_node("ram") && child.sub_node("ram").has_attribute("requested")) {
|
||||
_ram.value *= 2;
|
||||
result = true;
|
||||
}
|
||||
|
||||
if (child.has_sub_node("caps") && child.sub_node("caps").has_attribute("requested")) {
|
||||
_caps.value += 100;
|
||||
result = true;
|
||||
}
|
||||
|
||||
if (child.attribute_value("skipped_heartbeats", 0U) > 2) {
|
||||
_version++;
|
||||
_ram = _initial_ram;
|
||||
_caps = _initial_caps;
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void _gen_start_node(Xml_generator &) const;
|
||||
|
||||
public:
|
||||
|
||||
unsigned min_width = 0, min_height = 0;
|
||||
|
||||
struct Attr
|
||||
{
|
||||
bool opaque;
|
||||
Color background;
|
||||
Ram_quota initial_ram;
|
||||
};
|
||||
|
||||
View(Distant_runtime &runtime, Top_level_dialog &dialog, Attr attr)
|
||||
:
|
||||
Views::Element(runtime._views, dialog.name),
|
||||
_env(runtime._env), _runtime(runtime), _dialog(dialog),
|
||||
_initial_ram(attr.initial_ram), _opaque(attr.opaque),
|
||||
_background(attr.background)
|
||||
{
|
||||
_hover_rom.sigh(_hover_handler);
|
||||
_refresh_handler.local_submit();
|
||||
}
|
||||
|
||||
View(Distant_runtime &runtime, Top_level_dialog &dialog)
|
||||
:
|
||||
View(runtime, dialog, Attr { .opaque = false,
|
||||
.background = { },
|
||||
.initial_ram = { 4*1024*1024 } })
|
||||
{ }
|
||||
|
||||
void refresh() { _refresh_handler.local_submit(); }
|
||||
};
|
||||
|
||||
#endif /* _DIALOG__DISTANT_RUNTIME_H_ */
|
@ -18,6 +18,37 @@
|
||||
#include <gui_session/connection.h>
|
||||
#include <base/heap.h>
|
||||
|
||||
|
||||
static bool click(Input::Event const &event)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (event.key_press(Input::BTN_LEFT))
|
||||
result = true;
|
||||
|
||||
event.handle_touch([&] (Input::Touch_id id, float, float) {
|
||||
if (id.value == 0)
|
||||
result = true; });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static bool clack(Input::Event const &event)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (event.key_release(Input::BTN_LEFT))
|
||||
result = true;
|
||||
|
||||
event.handle_touch_release([&] (Input::Touch_id id) {
|
||||
if (id.value == 0)
|
||||
result = true; });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
struct Gui::Session_component : Rpc_object<Gui::Session>
|
||||
{
|
||||
Env &_env;
|
||||
@ -33,20 +64,23 @@ struct Gui::Session_component : Rpc_object<Gui::Session>
|
||||
Signal_handler<Session_component> _input_handler {
|
||||
_env.ep(), *this, &Session_component::_handle_input };
|
||||
|
||||
bool _clicked = false;
|
||||
|
||||
void _handle_input()
|
||||
{
|
||||
_connection.input()->for_each_event([&] (Input::Event ev) {
|
||||
|
||||
/*
|
||||
* Augment input stream with sequence numbers to correlate
|
||||
* clicks with hover reports.
|
||||
* Assign new event sequence number, pass seq event to menu view
|
||||
* to ensure freshness of hover information.
|
||||
*/
|
||||
bool const advance_seq_number = ev.key_press(Input::BTN_LEFT)
|
||||
|| ev.key_release(Input::BTN_LEFT)
|
||||
|| ev.touch() || ev.touch_release();
|
||||
if (advance_seq_number) {
|
||||
_global_input_seq_number.value++;
|
||||
bool const orig_clicked = _clicked;
|
||||
|
||||
if (click(ev)) _clicked = true;
|
||||
if (clack(ev)) _clicked = false;
|
||||
|
||||
if (orig_clicked != _clicked) {
|
||||
_global_input_seq_number.value++;
|
||||
_input_component.submit(_global_input_seq_number);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user