diff --git a/repos/gems/run/sculpt/leitzentrale.config b/repos/gems/run/sculpt/leitzentrale.config
index 961048ed20..d47eb7f916 100644
--- a/repos/gems/run/sculpt/leitzentrale.config
+++ b/repos/gems/run/sculpt/leitzentrale.config
@@ -96,14 +96,16 @@
+
+
-
+
@@ -118,7 +120,7 @@
-
+
@@ -166,6 +168,8 @@
+
+
@@ -185,6 +189,7 @@
+
@@ -206,11 +211,11 @@
-
+
-
+
diff --git a/repos/gems/src/app/depot_deploy/children.h b/repos/gems/src/app/depot_deploy/children.h
index 7b76c4ec05..d438e25865 100644
--- a/repos/gems/src/app/depot_deploy/children.h
+++ b/repos/gems/src/app/depot_deploy/children.h
@@ -140,6 +140,15 @@ class Depot_deploy::Children
result |= child.incomplete(); });
return result;
}
+
+ bool exists(Child::Name const &name) const
+ {
+ bool result = false;
+ _children.for_each([&] (Child const &child) {
+ if (child.name() == name)
+ result = true; });
+ return result;
+ }
};
#endif /* _CHILDREN_H_ */
diff --git a/repos/gems/src/app/sculpt_manager/deploy.cc b/repos/gems/src/app/sculpt_manager/deploy.cc
index 5fcfe332ef..d3de409a90 100644
--- a/repos/gems/src/app/sculpt_manager/deploy.cc
+++ b/repos/gems/src/app/sculpt_manager/deploy.cc
@@ -81,14 +81,14 @@ void Sculpt::Deploy::gen_child_diagnostics(Xml_generator &xml) const
void Sculpt::Deploy::handle_deploy()
{
- Xml_node const manual_deploy = _manual_deploy_rom.xml();
+ Xml_node const managed_deploy = _managed_deploy_rom.xml();
/* determine CPU architecture of deployment */
- _arch = manual_deploy.attribute_value("arch", Arch());
+ _arch = managed_deploy.attribute_value("arch", Arch());
if (!_arch.valid())
- warning("manual deploy config lacks 'arch' attribute");
+ warning("managed deploy config lacks 'arch' attribute");
- try { _children.apply_config(manual_deploy); }
+ try { _children.apply_config(managed_deploy); }
catch (...) {
error("spurious exception during deploy update (apply_config)"); }
@@ -172,16 +172,16 @@ void Sculpt::Deploy::gen_runtime_start_nodes(Xml_generator &xml) const
xml.node("start", [&] () {
gen_depot_query_start_content(xml); });
- Xml_node const manual_deploy = _manual_deploy_rom.xml();
+ Xml_node const managed_deploy = _managed_deploy_rom.xml();
/* insert content of '' node as is */
- if (manual_deploy.has_sub_node("static")) {
- Xml_node static_config = manual_deploy.sub_node("static");
+ if (managed_deploy.has_sub_node("static")) {
+ Xml_node static_config = managed_deploy.sub_node("static");
xml.append(static_config.content_base(), static_config.content_size());
}
/* generate start nodes for deployed packages */
- if (manual_deploy.has_sub_node("common_routes"))
- _children.gen_start_nodes(xml, manual_deploy.sub_node("common_routes"),
+ if (managed_deploy.has_sub_node("common_routes"))
+ _children.gen_start_nodes(xml, managed_deploy.sub_node("common_routes"),
"depot_rom", "dynamic_depot_rom");
}
diff --git a/repos/gems/src/app/sculpt_manager/deploy.h b/repos/gems/src/app/sculpt_manager/deploy.h
index 766d31f9be..e9f87c8c9e 100644
--- a/repos/gems/src/app/sculpt_manager/deploy.h
+++ b/repos/gems/src/app/sculpt_manager/deploy.h
@@ -22,6 +22,7 @@
#include
/* local includes */
+#include
#include
#include
#include
@@ -42,6 +43,8 @@ struct Sculpt::Deploy
Runtime_config_generator &_runtime_config_generator;
+ Attached_rom_dataspace const &_launcher_listing_rom;
+
typedef String<16> Arch;
Arch _arch { };
@@ -53,14 +56,56 @@ struct Sculpt::Deploy
Child_state uncached_depot_rom_state {
"dynamic_depot_rom", Ram_quota{8*1024*1024}, Cap_quota{200} };
- Attached_rom_dataspace _manual_deploy_rom { _env, "config -> deploy" };
-
- Attached_rom_dataspace _launcher_listing_rom { _env, "report -> /runtime/launcher_query/listing" };
-
Attached_rom_dataspace _blueprint_rom { _env, "report -> runtime/depot_query/blueprint" };
Expanding_reporter _depot_query_reporter { _env, "query", "depot_query"};
+ /*
+ * Report written to '/config/managed/deploy'
+ *
+ * This report takes the manually maintained '/config/deploy' and the
+ * interactive state as input.
+ */
+ Expanding_reporter _managed_deploy_config { _env, "config", "deploy_config" };
+
+ /* config obtained from '/config/managed/deploy' */
+ Attached_rom_dataspace _managed_deploy_rom { _env, "config -> managed/deploy" };
+
+ void update_managed_deploy_config(Xml_node deploy)
+ {
+ _managed_deploy_config.generate([&] (Xml_generator &xml) {
+
+ Arch const arch = deploy.attribute_value("arch", Arch());
+ if (arch.valid())
+ xml.attribute("arch", arch);
+
+ auto append_xml_node = [&] (Xml_node node) {
+ xml.append("\t");
+ xml.append(node.addr(), node.size());
+ xml.append("\n");
+ };
+
+ /* copy from manual deploy config */
+ deploy.for_each_sub_node("common_routes", [&] (Xml_node node) {
+ append_xml_node(node); });
+
+ /*
+ * Copy the node from manual deploy config, unless the
+ * component was interactively killed by the user.
+ */
+ deploy.for_each_sub_node("start", [&] (Xml_node node) {
+ Start_name const name = node.attribute_value("name", Start_name());
+ if (!_runtime_info.abandoned_by_user(name))
+ append_xml_node(node);
+ });
+
+ /*
+ * Add start nodes for interactively launched components.
+ */
+ _runtime_info.gen_launched_deploy_start_nodes(xml);
+ });
+ }
+
bool _manual_installation_scheduled = false;
Managed_config _installation {
@@ -81,10 +126,9 @@ struct Sculpt::Deploy
void handle_deploy();
- void _handle_manual_deploy()
+ void _handle_managed_deploy()
{
- _manual_deploy_rom.update();
- _launcher_listing_rom.update();
+ _managed_deploy_rom.update();
_query_version.value++;
handle_deploy();
}
@@ -135,11 +179,8 @@ struct Sculpt::Deploy
void gen_runtime_start_nodes(Xml_generator &) const;
- Signal_handler _manual_deploy_handler {
- _env.ep(), *this, &Deploy::_handle_manual_deploy };
-
- Signal_handler _launcher_listing_handler {
- _env.ep(), *this, &Deploy::_handle_manual_deploy };
+ Signal_handler _managed_deploy_handler {
+ _env.ep(), *this, &Deploy::_handle_managed_deploy };
Signal_handler _blueprint_handler {
_env.ep(), *this, &Deploy::_handle_blueprint };
@@ -160,14 +201,15 @@ struct Sculpt::Deploy
Deploy(Env &env, Allocator &alloc, Runtime_info const &runtime_info,
Dialog::Generator &dialog_generator,
- Runtime_config_generator &runtime_config_generator)
+ Runtime_config_generator &runtime_config_generator,
+ Attached_rom_dataspace const &launcher_listing_rom)
:
_env(env), _alloc(alloc), _runtime_info(runtime_info),
_dialog_generator(dialog_generator),
- _runtime_config_generator(runtime_config_generator)
+ _runtime_config_generator(runtime_config_generator),
+ _launcher_listing_rom(launcher_listing_rom)
{
- _manual_deploy_rom.sigh(_manual_deploy_handler);
- _launcher_listing_rom.sigh(_launcher_listing_handler);
+ _managed_deploy_rom.sigh(_managed_deploy_handler);
_blueprint_rom.sigh(_blueprint_handler);
}
};
diff --git a/repos/gems/src/app/sculpt_manager/graph.h b/repos/gems/src/app/sculpt_manager/graph.h
index becb75a9ec..a10a73d845 100644
--- a/repos/gems/src/app/sculpt_manager/graph.h
+++ b/repos/gems/src/app/sculpt_manager/graph.h
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
namespace Sculpt { struct Graph; }
@@ -36,6 +37,10 @@ struct Sculpt::Graph
Storage_target const &_sculpt_partition;
+ Popup::State const &_popup_state;
+
+ Depot_deploy::Children const &_deploy_children;
+
Expanding_reporter _graph_dialog_reporter { _env, "dialog", "runtime_view_dialog" };
/*
@@ -53,7 +58,14 @@ struct Sculpt::Graph
Signal_handler _hover_handler {
_env.ep(), *this, &Graph::_handle_hover };
- Hoverable_item _node_button_item { };
+ Hoverable_item _node_button_item { };
+ Hoverable_item _add_button_item { };
+ Activatable_item _remove_item { };
+
+ /*
+ * Defined when '+' button is hovered
+ */
+ Rect _popup_anchor { };
bool _hovered = false;
@@ -93,10 +105,7 @@ struct Sculpt::Graph
bool first_route = true;
route.for_each_sub_node("service", [&] (Xml_node service) {
- if (!service.has_sub_node("child"))
- return;
-
- if (!first_route) {
+ if (!first_route && service.has_sub_node("child")) {
Xml_node const child = service.sub_node("child");
fn(child.attribute_value("name", Start_name()));
}
@@ -105,6 +114,37 @@ struct Sculpt::Graph
});
}
+ void _gen_selected_node_content(Xml_generator &xml, Start_name const &name,
+ Runtime_state::Info const &info) const
+ {
+ bool const removable = _deploy_children.exists(name);
+
+ if (removable) {
+ gen_named_node(xml, "frame", "operations", [&] () {
+ xml.node("vbox", [&] () {
+ gen_named_node(xml, "button", "remove", [&] () {
+ _remove_item.gen_button_attr(xml, "remove");
+ xml.node("label", [&] () {
+ xml.attribute("text", "Remove");
+ });
+ });
+ });
+ });
+ }
+
+ String<100> const
+ ram (Capacity{info.assigned_ram - info.avail_ram}, " / ",
+ Capacity{info.assigned_ram}),
+ caps(info.assigned_caps - info.avail_caps, " / ",
+ info.assigned_caps, " caps");
+
+ gen_named_node(xml, "label", "ram", [&] () {
+ xml.attribute("text", ram); });
+
+ gen_named_node(xml, "label", "caps", [&] () {
+ xml.attribute("text", caps); });
+ }
+
void _gen_graph_dialog()
{
Xml_node const config = _runtime_config_rom.xml();
@@ -113,6 +153,15 @@ struct Sculpt::Graph
xml.node("depgraph", [&] () {
+ gen_named_node(xml, "button", "global+", [&] () {
+ _add_button_item.gen_button_attr(xml, "global+");
+
+ if (_popup_state == Popup::VISIBLE)
+ xml.attribute("selected", "yes");
+
+ xml.node("label", [&] () {
+ xml.attribute("text", "+"); }); });
+
config.for_each_sub_node("start", [&] (Xml_node start) {
Start_name const name = start.attribute_value("name", Start_name());
@@ -142,20 +191,8 @@ struct Sculpt::Graph
});
});
- if (info.selected) {
-
- String<100> const
- ram (Capacity{info.assigned_ram - info.avail_ram}, " / ",
- Capacity{info.assigned_ram}),
- caps(info.assigned_caps - info.avail_caps, " / ",
- info.assigned_caps, " caps");
-
- gen_named_node(xml, "label", "ram", [&] () {
- xml.attribute("text", ram); });
-
- gen_named_node(xml, "label", "caps", [&] () {
- xml.attribute("text", caps); });
- }
+ if (info.selected)
+ _gen_selected_node_content(xml, name, info);
});
});
});
@@ -197,16 +234,54 @@ struct Sculpt::Graph
_hovered = (hover.num_sub_nodes() != 0);
bool const changed =
- _node_button_item.match(hover, "dialog", "depgraph", "frame", "vbox", "button", "name");
+ _node_button_item.match(hover, "dialog", "depgraph", "frame", "vbox", "button", "name") |
+ _add_button_item .match(hover, "dialog", "depgraph", "button", "name") |
+ _remove_item .match(hover, "dialog", "depgraph", "frame", "vbox",
+ "frame", "vbox", "button", "name");
+
+ if (_add_button_item.hovered("global+")) {
+
+ /* update anchor geometry of popup menu */
+ auto hovered_rect = [] (Xml_node const hover) {
+
+ auto point_from_xml = [] (Xml_node node) {
+ return Point(node.attribute_value("xpos", 0L),
+ node.attribute_value("ypos", 0L)); };
+
+ auto area_from_xml = [] (Xml_node node) {
+ return Area(node.attribute_value("width", 0UL),
+ node.attribute_value("height", 0UL)); };
+
+ if (!hover.has_sub_node("dialog")) return Rect();
+ Xml_node const dialog = hover.sub_node("dialog");
+
+ if (!dialog.has_sub_node("depgraph")) return Rect();
+ Xml_node const depgraph = dialog.sub_node("depgraph");
+
+ if (!depgraph.has_sub_node("button")) return Rect();
+ Xml_node const button = depgraph.sub_node("button");
+
+ return Rect(point_from_xml(dialog) + point_from_xml(depgraph) +
+ point_from_xml(button),
+ area_from_xml(button));
+ };
+
+ _popup_anchor = hovered_rect(hover);
+ }
if (changed)
_gen_graph_dialog();
}
- Graph(Env &env, Runtime_state &runtime_state,
- Storage_target const &sculpt_partition)
+ Graph(Env &env,
+ Runtime_state &runtime_state,
+ Storage_target const &sculpt_partition,
+ Popup::State const &popup_state,
+ Depot_deploy::Children const &deploy_children)
:
- _env(env), _runtime_state(runtime_state), _sculpt_partition(sculpt_partition)
+ _env(env), _runtime_state(runtime_state),
+ _sculpt_partition(sculpt_partition),
+ _popup_state(popup_state), _deploy_children(deploy_children)
{
_runtime_config_rom.sigh(_runtime_config_handler);
_hover_rom.sigh(_hover_handler);
@@ -214,12 +289,46 @@ struct Sculpt::Graph
bool hovered() const { return _hovered; }
- void click()
+ bool add_button_hovered() const { return _add_button_item._hovered.valid(); }
+
+ struct Action : Interface
{
+ virtual void remove_deployed_component(Start_name const &) = 0;
+ virtual void toggle_launcher_selector(Rect) = 0;
+ };
+
+ void click(Action &action)
+ {
+ if (_add_button_item._hovered.valid()) {
+ action.toggle_launcher_selector(_popup_anchor);
+ }
+
if (_node_button_item._hovered.valid()) {
_runtime_state.toggle_selection(_node_button_item._hovered);
+ _remove_item.reset();
_gen_graph_dialog();
}
+
+ if (_remove_item.hovered("remove")) {
+ _remove_item.propose_activation_on_click();
+ _gen_graph_dialog();
+ }
+ }
+
+ void clack(Action &action)
+ {
+ if (_remove_item.hovered("remove")) {
+
+ _remove_item.confirm_activation_on_clack();
+
+ if (_remove_item.activated("remove"))
+ action.remove_deployed_component(_runtime_state.selected());
+
+ } else {
+ _remove_item.reset();
+ }
+
+ _gen_graph_dialog();
}
};
diff --git a/repos/gems/src/app/sculpt_manager/gui.cc b/repos/gems/src/app/sculpt_manager/gui.cc
index 7ac52950ec..f28d80b6bc 100644
--- a/repos/gems/src/app/sculpt_manager/gui.cc
+++ b/repos/gems/src/app/sculpt_manager/gui.cc
@@ -23,7 +23,8 @@
void Sculpt::Gui::_gen_menu_view_start_content(Xml_generator &xml,
Label const &label,
- Point pos) const
+ Point pos,
+ unsigned width) const
{
xml.attribute("version", version.value);
@@ -34,7 +35,8 @@ void Sculpt::Gui::_gen_menu_view_start_content(Xml_generator &xml,
xml.node("config", [&] () {
xml.attribute("xpos", pos.x());
xml.attribute("ypos", pos.y());
- xml.attribute("width", menu_width);
+ if (width)
+ xml.attribute("width", width);
xml.node("libc", [&] () { xml.attribute("stderr", "/dev/log"); });
xml.node("report", [&] () { xml.attribute("hover", "yes"); });
xml.node("vfs", [&] () {
@@ -104,6 +106,9 @@ void Sculpt::Gui::_generate_config(Xml_generator &xml) const
});
xml.node("start", [&] () {
- _gen_menu_view_start_content(xml, "menu", Point(0, 0)); });
+ _gen_menu_view_start_content(xml, "menu", Point(0, 0), menu_width); });
+
+ xml.node("start", [&] () {
+ _gen_menu_view_start_content(xml, "popup", Point(0, 0), 0); });
}
diff --git a/repos/gems/src/app/sculpt_manager/gui.h b/repos/gems/src/app/sculpt_manager/gui.h
index 72ae2417bd..b55df6aa15 100644
--- a/repos/gems/src/app/sculpt_manager/gui.h
+++ b/repos/gems/src/app/sculpt_manager/gui.h
@@ -41,7 +41,8 @@ struct Sculpt::Gui
unsigned menu_width = 0;
- void _gen_menu_view_start_content(Xml_generator &, Label const &, Point) const;
+ void _gen_menu_view_start_content(Xml_generator &, Label const &, Point,
+ unsigned) const;
void _generate_config(Xml_generator &) const;
diff --git a/repos/gems/src/app/sculpt_manager/main.cc b/repos/gems/src/app/sculpt_manager/main.cc
index 3066694824..cad4f757e5 100644
--- a/repos/gems/src/app/sculpt_manager/main.cc
+++ b/repos/gems/src/app/sculpt_manager/main.cc
@@ -25,6 +25,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -39,7 +40,9 @@ namespace Sculpt { struct Main; }
struct Sculpt::Main : Input_event_handler,
Dialog::Generator,
Runtime_config_generator,
- Storage::Target_user
+ Storage::Target_user,
+ Graph::Action,
+ Popup_dialog::Action
{
Env &_env;
@@ -175,8 +178,49 @@ struct Sculpt::Main : Input_event_handler,
&& _network.ready()
&& _deploy.update_needed(); };
- Deploy _deploy { _env, _heap, _runtime_state, *this, *this };
+ /************
+ ** Deploy **
+ ************/
+
+ Attached_rom_dataspace _launcher_listing_rom {
+ _env, "report -> /runtime/launcher_query/listing" };
+
+ Launchers _launchers { _heap };
+
+ Signal_handler _launcher_listing_handler {
+ _env.ep(), *this, &Main::_handle_launcher_listing };
+
+ void _handle_launcher_listing()
+ {
+ _launcher_listing_rom.update();
+
+ Xml_node listing = _launcher_listing_rom.xml();
+ if (listing.has_sub_node("dir")) {
+ Xml_node dir = listing.sub_node("dir");
+
+ /* let 'update_from_xml' iterate over nodes */
+ _launchers.update_from_xml(dir);
+ }
+
+ _popup_dialog.generate();
+ _deploy._handle_managed_deploy();
+ }
+
+
+ Deploy _deploy { _env, _heap, _runtime_state, *this, *this, _launcher_listing_rom };
+
+ Attached_rom_dataspace _manual_deploy_rom { _env, "config -> deploy" };
+
+ void _handle_manual_deploy()
+ {
+ _runtime_state.reset_abandoned_and_launched_children();
+ _manual_deploy_rom.update();
+ _deploy.update_managed_deploy_config(_manual_deploy_rom.xml());
+ }
+
+ Signal_handler _manual_deploy_handler {
+ _env.ep(), *this, &Main::_handle_manual_deploy };
/************
@@ -298,11 +342,29 @@ struct Sculpt::Main : Input_event_handler,
if (_hovered_dialog == Hovered::NETWORK) _network.dialog.click(_network);
if (_hovered_dialog == Hovered::RUNTIME) _network.dialog.click(_network);
- if (_graph.hovered()) _graph.click();
+ /* remove popup dialog when clicking somewhere outside */
+ if (!_popup_dialog.hovered() && _popup.state == Popup::VISIBLE
+ && !_graph.add_button_hovered()) {
+
+ _popup.state = Popup::OFF;
+
+ /* de-select '+' button */
+ _graph._gen_graph_dialog();
+
+ /* remove popup window from window layout */
+ _handle_window_layout();
+ }
+
+ if (_graph.hovered()) _graph.click(*this);
+
+ if (_popup_dialog.hovered()) _popup_dialog.click(*this);
}
- if (ev.key_release(Input::BTN_LEFT))
- _storage.dialog.clack(_storage);
+ if (ev.key_release(Input::BTN_LEFT)) {
+ if (_hovered_dialog == Hovered::STORAGE) _storage.dialog.clack(_storage);
+
+ if (_graph.hovered()) _graph.clack(*this);
+ }
if (_keyboard_focus.target == Keyboard_focus::WPA_PASSPHRASE)
ev.handle_press([&] (Input::Keycode, Codepoint code) {
@@ -312,6 +374,49 @@ struct Sculpt::Main : Input_event_handler,
_keyboard_focus.update();
}
+ /*
+ * Graph::Action interface
+ */
+ void remove_deployed_component(Start_name const &name) override
+ {
+ _runtime_state.abandon(name);
+
+ /* update config/managed/deploy with the component 'name' removed */
+ _deploy.update_managed_deploy_config(_manual_deploy_rom.xml());
+ }
+
+ /*
+ * Graph::Action interface
+ */
+ void toggle_launcher_selector(Rect anchor) override
+ {
+ _popup_dialog.generate();
+ _popup.anchor = anchor;
+ _popup.toggle();
+ _graph._gen_graph_dialog();
+ _handle_window_layout();
+ }
+
+ /*
+ * Popup_dialog::Action interface
+ */
+ void launch_global(Path const &launcher) override
+ {
+ _runtime_state.launch(launcher, launcher);
+
+ /* close popup menu */
+ _popup.state = Popup::OFF;
+ _handle_window_layout();
+
+ /* reset state of the '+' button */
+ _graph._gen_graph_dialog();
+
+ /* trigger change of the deployment */
+ _deploy.update_managed_deploy_config(_manual_deploy_rom.xml());
+ }
+
+ Popup_dialog _popup_dialog { _env, _launchers, _runtime_state };
+
Managed_config _fb_drv_config {
_env, "config", "fb_drv", *this, &Main::_handle_fb_drv_config };
@@ -360,6 +465,14 @@ struct Sculpt::Main : Input_event_handler,
void _handle_window_layout();
+ template
+ void _with_window(Xml_node window_list, String const &match, FN const &fn)
+ {
+ window_list.for_each_sub_node("window", [&] (Xml_node win) {
+ if (win.attribute_value("label", String()) == match)
+ fn(win); });
+ }
+
Attached_rom_dataspace _window_list { _env, "window_list" };
Signal_handler _window_list_handler {
@@ -379,7 +492,10 @@ struct Sculpt::Main : Input_event_handler,
** Runtime graph **
*******************/
- Graph _graph { _env, _runtime_state, _storage._sculpt_partition };
+ Popup _popup { };
+
+ Graph _graph { _env, _runtime_state, _storage._sculpt_partition,
+ _popup.state, _deploy._children };
Child_state _runtime_view_state {
"runtime_view", Ram_quota{8*1024*1024}, Cap_quota{200} };
@@ -387,18 +503,20 @@ struct Sculpt::Main : Input_event_handler,
Main(Env &env) : _env(env)
{
+ _manual_deploy_rom.sigh(_manual_deploy_handler);
_runtime_state_rom.sigh(_runtime_state_handler);
_nitpicker_displays.sigh(_nitpicker_displays_handler);
/*
* Subscribe to reports
*/
- _update_state_rom .sigh(_update_state_handler);
- _nitpicker_hover .sigh(_nitpicker_hover_handler);
- _hover_rom .sigh(_hover_handler);
- _pci_devices .sigh(_pci_devices_handler);
- _window_list .sigh(_window_list_handler);
- _decorator_margins.sigh(_decorator_margins_handler);
+ _update_state_rom .sigh(_update_state_handler);
+ _nitpicker_hover .sigh(_nitpicker_hover_handler);
+ _hover_rom .sigh(_hover_handler);
+ _pci_devices .sigh(_pci_devices_handler);
+ _window_list .sigh(_window_list_handler);
+ _decorator_margins .sigh(_decorator_margins_handler);
+ _launcher_listing_rom.sigh(_launcher_listing_handler);
/*
* Generate initial configurations
@@ -412,6 +530,11 @@ struct Sculpt::Main : Input_event_handler,
_deploy.handle_deploy();
_handle_pci_devices();
+ /*
+ * Generate initial config/managed/deploy configuration
+ */
+ _handle_manual_deploy();
+
generate_runtime_config();
generate_dialog();
}
@@ -449,10 +572,10 @@ void Sculpt::Main::_handle_window_layout()
Framebuffer::Mode const mode = _nitpicker->mode();
- typedef Nitpicker::Rect Rect;
- typedef Nitpicker::Area Area;
- typedef Nitpicker::Point Point;
+ /* area preserved for the menu */
+ Rect const menu(Point(0, 0), Area(_gui.menu_width, mode.height()));
+ /* available space on the right of the menu */
Rect avail(Point(_gui.menu_width, 0),
Point(mode.width() - 1, mode.height() - 1));
@@ -490,43 +613,68 @@ void Sculpt::Main::_handle_window_layout()
_window_list.update();
_window_layout.generate([&] (Xml_generator &xml) {
- _window_list.xml().for_each_sub_node("window", [&] (Xml_node win) {
+ Xml_node const window_list = _window_list.xml();
- Label const label = win.attribute_value("label", Label());
-
- /**
- * Generate window with 'rect' geometry if label matches 'match'
- */
- auto gen_matching_window = [&] (Label const &match, Rect rect) {
- if (label == match && rect.valid()) {
- xml.node("window", [&] () {
- xml.attribute("id", win.attribute_value("id", 0UL));
- xml.attribute("xpos", rect.x1());
- xml.attribute("ypos", rect.y1());
- xml.attribute("width", rect.w());
- xml.attribute("height", rect.h());
- });
- }
- };
-
- gen_matching_window("log", Rect(log_p1, log_p2));
-
- if (label == runtime_view_label) {
-
- /* center runtime view within the available main (inspect) area */
- unsigned const inspect_w = inspect_p2.x() - inspect_p1.x(),
- inspect_h = inspect_p2.y() - inspect_p1.y();
-
- Area const size(min(inspect_w, win.attribute_value("width", 0UL)),
- min(inspect_h, win.attribute_value("height", 0UL)));
-
- Point const pos = Rect(inspect_p1, inspect_p2).center(size);
-
- gen_matching_window(runtime_view_label, Rect(pos, size));
+ auto gen_window = [&] (Xml_node win, Rect rect) {
+ if (rect.valid()) {
+ xml.node("window", [&] () {
+ xml.attribute("id", win.attribute_value("id", 0UL));
+ xml.attribute("xpos", rect.x1());
+ xml.attribute("ypos", rect.y1());
+ xml.attribute("width", rect.w());
+ xml.attribute("height", rect.h());
+ });
}
+ };
- if (_last_clicked == Hovered::STORAGE)
- gen_matching_window(inspect_label, Rect(inspect_p1, inspect_p2));
+ auto win_size = [&] (Xml_node win) {
+
+ unsigned const inspect_w = inspect_p2.x() - inspect_p1.x(),
+ inspect_h = inspect_p2.y() - inspect_p1.y();
+
+ return Area(min(inspect_w, win.attribute_value("width", 0UL)),
+ min(inspect_h, win.attribute_value("height", 0UL)));
+ };
+
+ _with_window(window_list, Label("gui -> menu -> "), [&] (Xml_node win) {
+ gen_window(win, menu); });
+
+ /* calculate centered runtime view within the available main (inspect) area */
+ Rect runtime_view;
+ _with_window(window_list, runtime_view_label, [&] (Xml_node win) {
+ Area const size = win_size(win);
+ Point const pos = Rect(inspect_p1, inspect_p2).center(size);
+ runtime_view = Rect(pos, size);
+ });
+
+ if (_popup.state == Popup::VISIBLE) {
+ _with_window(window_list, Label("gui -> popup -> "), [&] (Xml_node win) {
+ Area const size = win_size(win);
+
+ int const anchor_y_center = (_popup.anchor.y1() + _popup.anchor.y2())/2;
+
+ int const x = runtime_view.x1() + _popup.anchor.x2();
+ int const y = max(0, runtime_view.y1() + anchor_y_center - (int)size.h()/2);
+
+ gen_window(win, Rect(Point(x, y), size));
+ });
+ }
+
+ _with_window(window_list, Label("log"), [&] (Xml_node win) {
+ gen_window(win, Rect(log_p1, log_p2)); });
+
+ if (_last_clicked == Hovered::STORAGE) {
+ _with_window(window_list, inspect_label, [&] (Xml_node win) {
+ gen_window(win, Rect(inspect_p1, inspect_p2)); });
+ }
+
+ _with_window(window_list, runtime_view_label, [&] (Xml_node win) {
+
+ /* center runtime view within the available main (inspect) area */
+ Area const size = win_size(win);
+ Point const pos = Rect(inspect_p1, inspect_p2).center(size);
+
+ gen_window(win, Rect(pos, size));
});
});
diff --git a/repos/gems/src/app/sculpt_manager/model/launchers.h b/repos/gems/src/app/sculpt_manager/model/launchers.h
new file mode 100644
index 0000000000..36c16682ab
--- /dev/null
+++ b/repos/gems/src/app/sculpt_manager/model/launchers.h
@@ -0,0 +1,102 @@
+/*
+ * \brief Cached information about available launchers
+ * \author Norman Feske
+ * \date 2018-09-13
+ */
+
+/*
+ * 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 _MODEL__LAUNCHERS_H_
+#define _MODEL__LAUNCHERS_H_
+
+/* Genode includes */
+#include
+#include
+
+/* local includes */
+#include
+#include
+
+namespace Sculpt { class Launchers; }
+
+
+class Sculpt::Launchers : public Noncopyable
+{
+ public:
+
+ struct Info : Noncopyable
+ {
+ Path const path;
+ Info(Path const &path) : path(path) { }
+ };
+
+ private:
+
+ Allocator &_alloc;
+
+ struct Launcher : Info, Avl_node, List_model::Element
+ {
+ Avl_tree &_avl_tree;
+
+ Launcher(Avl_tree &avl_tree, Path const &path)
+ : Info(path), _avl_tree(avl_tree)
+ { _avl_tree.insert(this); }
+
+ ~Launcher() { _avl_tree.remove(this); }
+
+ /**
+ * Avl_node interface
+ */
+ bool higher(Launcher *l) {
+ return strcmp(l->path.string(), path.string()) > 0; }
+ };
+
+ Avl_tree _sorted { };
+
+ List_model _launchers { };
+
+ struct Update_policy : List_model::Update_policy
+ {
+ Allocator &_alloc;
+
+ Avl_tree &_sorted;
+
+ Update_policy(Allocator &alloc, Avl_tree &sorted)
+ : _alloc(alloc), _sorted(sorted) { }
+
+ void destroy_element(Launcher &elem) { destroy(_alloc, &elem); }
+
+ Launcher &create_element(Xml_node node)
+ {
+ return *new (_alloc)
+ Launcher(_sorted, node.attribute_value("name", Path()));
+ }
+
+ void update_element(Launcher &, Xml_node) { }
+
+ static bool element_matches_xml_node(Launcher const &elem, Xml_node node)
+ {
+ return node.attribute_value("name", Path()) == elem.path;
+ }
+ };
+
+ public:
+
+ Launchers(Allocator &alloc) : _alloc(alloc) { }
+
+ void update_from_xml(Xml_node node)
+ {
+ Update_policy policy(_alloc, _sorted);
+ _launchers.update_from_xml(policy, node);
+ }
+
+ template
+ void for_each(FN const &fn) const { _sorted.for_each(fn); }
+};
+
+#endif /* _MODEL__LAUNCHERS_H_ */
diff --git a/repos/gems/src/app/sculpt_manager/model/popup.h b/repos/gems/src/app/sculpt_manager/model/popup.h
new file mode 100644
index 0000000000..2a80366d37
--- /dev/null
+++ b/repos/gems/src/app/sculpt_manager/model/popup.h
@@ -0,0 +1,31 @@
+/*
+ * \brief State of popup menu
+ * \author Norman Feske
+ * \date 2018-09-12
+ */
+
+/*
+ * 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 _MODEL__POPUP_H_
+#define _MODEL__POPUP_H_
+
+#include "types.h"
+
+namespace Sculpt { struct Popup; }
+
+
+struct Sculpt::Popup : Noncopyable
+{
+ enum State { OFF, VISIBLE } state { OFF };
+
+ Rect anchor { };
+
+ void toggle() { state = (state == OFF) ? VISIBLE : OFF; }
+};
+
+#endif /* _MODEL__POPUP_H_ */
diff --git a/repos/gems/src/app/sculpt_manager/model/runtime_state.h b/repos/gems/src/app/sculpt_manager/model/runtime_state.h
index f5416dc358..ebd36be59e 100644
--- a/repos/gems/src/app/sculpt_manager/model/runtime_state.h
+++ b/repos/gems/src/app/sculpt_manager/model/runtime_state.h
@@ -49,18 +49,47 @@ class Sculpt::Runtime_state : public Runtime_info
Info info { false, 0, 0, 0, 0 };
+ bool abandoned_by_user = false;
+
Child(Start_name const &name) : name(name) { }
};
List_model _children { };
+ /**
+ * Child present in initial deploy config but interactively removed
+ */
+ struct Abandoned_child : Interface
+ {
+ Start_name const name;
+ Abandoned_child(Start_name const &name) : name(name) { };
+ };
+
+ Registry > _abandoned_children { };
+
+ /**
+ * Child that was interactively launched
+ */
+ struct Launched_child : Interface
+ {
+ Start_name const name;
+ Path const launcher;
+ Launched_child(Start_name const &name, Path const &launcher)
+ : name(name), launcher(launcher) { };
+ };
+
+ Registry > _launched_children { };
+
struct Update_policy : List_model::Update_policy
{
Allocator &_alloc;
Update_policy(Allocator &alloc) : _alloc(alloc) { }
- void destroy_element(Child &elem) { destroy(_alloc, &elem); }
+ void destroy_element(Child &elem)
+ {
+ destroy(_alloc, &elem);
+ }
Child &create_element(Xml_node node)
{
@@ -95,6 +124,8 @@ class Sculpt::Runtime_state : public Runtime_info
Runtime_state(Allocator &alloc) : _alloc(alloc) { }
+ ~Runtime_state() { reset_abandoned_and_launched_children(); }
+
void update_from_state_report(Xml_node state)
{
Update_policy policy(_alloc);
@@ -113,6 +144,29 @@ class Sculpt::Runtime_state : public Runtime_info
return result;
}
+ /**
+ * Runtime_info interface
+ */
+ bool abandoned_by_user(Start_name const &name) const override
+ {
+ bool result = false;
+ _abandoned_children.for_each([&] (Abandoned_child const &child) {
+ if (!result && child.name == name)
+ result = true; });
+ return result;
+ }
+
+ /**
+ * Runtime_info interface
+ */
+ void gen_launched_deploy_start_nodes(Xml_generator &xml) const override
+ {
+ _launched_children.for_each([&] (Launched_child const &child) {
+ gen_named_node(xml, "start", child.name, [&] () {
+ if (child.name != child.launcher)
+ xml.attribute("launcher", child.launcher); }); });
+ }
+
Info info(Start_name const &name) const
{
Info result { .selected = false, 0, 0, 0, 0 };
@@ -122,11 +176,57 @@ class Sculpt::Runtime_state : public Runtime_info
return result;
}
+ Start_name selected() const
+ {
+ Start_name result;
+ _children.for_each([&] (Child const &child) {
+ if (child.info.selected)
+ result = child.name; });
+ return result;
+ }
+
void toggle_selection(Start_name const &name)
{
_children.for_each([&] (Child &child) {
child.info.selected = (child.name == name) && !child.info.selected; });
}
+
+ void abandon(Start_name const &name)
+ {
+ /*
+ * If child was launched interactively, remove corresponding
+ * entry from '_launched_children'.
+ */
+ bool was_interactively_launched = false;
+ _launched_children.for_each([&] (Launched_child &child) {
+ if (child.name == name) {
+ was_interactively_launched = true;
+ destroy(_alloc, &child);
+ }
+ });
+
+ if (was_interactively_launched)
+ return;
+
+ /*
+ * Child was present at initial deploy config, mark as abandoned
+ */
+ new (_alloc) Registered(_abandoned_children, name);
+ }
+
+ void launch(Start_name const &name, Path const &launcher)
+ {
+ new (_alloc) Registered(_launched_children, name, launcher);
+ }
+
+ void reset_abandoned_and_launched_children()
+ {
+ _abandoned_children.for_each([&] (Abandoned_child &child) {
+ destroy(_alloc, &child); });
+
+ _launched_children.for_each([&] (Launched_child &child) {
+ destroy(_alloc, &child); });
+ }
};
#endif /* _MODEL__RUNTIME_STATE_H_ */
diff --git a/repos/gems/src/app/sculpt_manager/network.cc b/repos/gems/src/app/sculpt_manager/network.cc
index d6b3d6692c..21292f38f6 100644
--- a/repos/gems/src/app/sculpt_manager/network.cc
+++ b/repos/gems/src/app/sculpt_manager/network.cc
@@ -224,5 +224,6 @@ void Sculpt::Network::gen_runtime_start_nodes(Xml_generator &xml) const
if (_nic_target.type() != Nic_target::OFF)
xml.node("start", [&] () {
- gen_nic_router_start_content(xml, _nic_target); });
+ gen_nic_router_start_content(xml, _nic_target,
+ _use_nic_drv, _use_wifi_drv); });
}
diff --git a/repos/gems/src/app/sculpt_manager/runtime.h b/repos/gems/src/app/sculpt_manager/runtime.h
index a89a05b0f0..1b67410673 100644
--- a/repos/gems/src/app/sculpt_manager/runtime.h
+++ b/repos/gems/src/app/sculpt_manager/runtime.h
@@ -33,6 +33,10 @@ namespace Sculpt {
* Return true if specified child is present in the runtime subsystem
*/
virtual bool present_in_runtime(Start_name const &) const = 0;
+
+ virtual bool abandoned_by_user(Start_name const &) const = 0;
+
+ virtual void gen_launched_deploy_start_nodes(Xml_generator &) const = 0;
};
void gen_chroot_start_content(Xml_generator &, Start_name const &,
@@ -63,7 +67,7 @@ namespace Sculpt {
void gen_nic_drv_start_content(Xml_generator &);
void gen_wifi_drv_start_content(Xml_generator &);
- void gen_nic_router_start_content(Xml_generator &, Nic_target const &);
+ void gen_nic_router_start_content(Xml_generator &, Nic_target const &, bool, bool);
void gen_nic_router_uplink(Xml_generator &, char const *);
struct Prepare_version { unsigned value; };
diff --git a/repos/gems/src/app/sculpt_manager/runtime/nic_router.cc b/repos/gems/src/app/sculpt_manager/runtime/nic_router.cc
index 3b44921b31..3677b1e9c4 100644
--- a/repos/gems/src/app/sculpt_manager/runtime/nic_router.cc
+++ b/repos/gems/src/app/sculpt_manager/runtime/nic_router.cc
@@ -15,7 +15,9 @@
void Sculpt::gen_nic_router_start_content(Xml_generator &xml,
- Nic_target const &nic_target)
+ Nic_target const &nic_target,
+ bool nic_drv_present,
+ bool wifi_drv_present)
{
gen_common_start_content(xml, "nic_router",
Cap_quota{300}, Ram_quota{10*1024*1024});
@@ -36,11 +38,13 @@ void Sculpt::gen_nic_router_start_content(Xml_generator &xml,
* the NIC target.
*/
if (nic_target.wifi()) {
- gen_nic_route("wifi", "wifi_drv");
- gen_nic_route("wired", "nic_drv");
- } else {
- gen_nic_route("wired", "nic_drv");
- gen_nic_route("wifi", "wifi_drv");
+ if (wifi_drv_present) gen_nic_route("wifi", "wifi_drv");
+ if (nic_drv_present) gen_nic_route("wired", "nic_drv");
+ }
+
+ if (nic_target.wired()) {
+ if (nic_drv_present) gen_nic_route("wired", "nic_drv");
+ if (wifi_drv_present) gen_nic_route("wifi", "wifi_drv");
}
gen_parent_rom_route(xml, "nic_router");
@@ -52,5 +56,15 @@ void Sculpt::gen_nic_router_start_content(Xml_generator &xml,
gen_parent_route (xml);
gen_parent_route (xml);
gen_parent_route (xml);
+
+ /*
+ * If the NIC target is set to local, we define the route to the
+ * drivers down here be avoid presenting it as primary route in the
+ * deploy graph.
+ */
+ if (nic_target.local()) {
+ if (nic_drv_present) gen_nic_route("wired", "nic_drv");
+ if (wifi_drv_present) gen_nic_route("wifi", "wifi_drv");
+ }
});
}
diff --git a/repos/gems/src/app/sculpt_manager/types.h b/repos/gems/src/app/sculpt_manager/types.h
index 7ae5d7c0fd..a568104682 100644
--- a/repos/gems/src/app/sculpt_manager/types.h
+++ b/repos/gems/src/app/sculpt_manager/types.h
@@ -43,6 +43,8 @@ namespace Sculpt {
typedef String<64> Label;
typedef Nitpicker::Point Point;
+ typedef Nitpicker::Rect Rect;
+ typedef Nitpicker::Area Area;
enum Writeable { WRITEABLE, READ_ONLY };
}
diff --git a/repos/gems/src/app/sculpt_manager/view/popup_dialog.h b/repos/gems/src/app/sculpt_manager/view/popup_dialog.h
new file mode 100644
index 0000000000..57cd48c3c5
--- /dev/null
+++ b/repos/gems/src/app/sculpt_manager/view/popup_dialog.h
@@ -0,0 +1,116 @@
+/*
+ * \brief Popup dialog
+ * \author Norman Feske
+ * \date 2018-09-12
+ */
+
+/*
+ * 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 _VIEW__POPUP_DIALOG_H_
+#define _VIEW__POPUP_DIALOG_H_
+
+#include
+#include
+#include
+#include
+
+namespace Sculpt { struct Popup_dialog; }
+
+
+struct Sculpt::Popup_dialog
+{
+ Env &_env;
+
+ Launchers const &_launchers;
+
+ Runtime_info const &_runtime_info;
+
+ Expanding_reporter _dialog_reporter { _env, "dialog", "popup_dialog" };
+
+ Attached_rom_dataspace _hover_rom { _env, "popup_view_hover" };
+
+ Signal_handler _hover_handler {
+ _env.ep(), *this, &Popup_dialog::_handle_hover };
+
+ Hoverable_item _item { };
+
+ bool _hovered = false;
+
+ void _handle_hover()
+ {
+ _hover_rom.update();
+
+ Xml_node const hover = _hover_rom.xml();
+
+ _hovered = hover.has_sub_node("dialog");
+
+ bool const changed = _item.match(hover, "dialog", "frame", "vbox", "hbox", "name");
+
+ if (changed)
+ generate();
+ }
+
+ bool hovered() const { return _hovered; };
+
+ void generate()
+ {
+ _dialog_reporter.generate([&] (Xml_generator &xml) {
+ xml.node("frame", [&] () {
+ xml.node("vbox", [&] () {
+
+ _launchers.for_each([&] (Launchers::Info const &info) {
+
+ /* allow each launcher to be used only once */
+ if (_runtime_info.present_in_runtime(info.path))
+ return;
+
+ gen_named_node(xml, "hbox", info.path, [&] () {
+
+ gen_named_node(xml, "float", "left", [&] () {
+ xml.attribute("west", "yes");
+
+ xml.node("hbox", [&] () {
+ gen_named_node(xml, "button", "button", [&] () {
+ _item.gen_button_attr(xml, info.path);
+ xml.node("label", [&] () {
+ xml.attribute("text", " "); }); });
+ gen_named_node(xml, "label", "name", [&] () {
+ xml.attribute("text", Path(" ", info.path)); });
+ });
+ });
+
+ gen_named_node(xml, "hbox", "right", [&] () { });
+ });
+ });
+ });
+ });
+ });
+ }
+
+ struct Action : Interface
+ {
+ virtual void launch_global(Path const &launcher) = 0;
+ };
+
+ void click(Action &action)
+ {
+ action.launch_global(_item._hovered);
+ }
+
+ Popup_dialog(Env &env, Launchers const &launchers,
+ Runtime_info const &runtime_info)
+ :
+ _env(env), _launchers(launchers), _runtime_info(runtime_info)
+ {
+ _hover_rom.sigh(_hover_handler);
+
+ generate();
+ }
+};
+
+#endif /* _VIEW__POPUP_DIALOG_H_ */