From f4c55aa4db54d7b067dd7f490e0ef3d64f90600c Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Tue, 11 Sep 2018 15:17:17 +0200 Subject: [PATCH] sculpt: interactive deployment This patch introduces the distinction of the manually managed config/deploy from the managed config/managed/deploy. The latter incorporates interactive changes of the system by the user. There are two user interactions supported. First, by clicking on the '+' button at the top-left of the runtime view, the user can select a component to launch. All launchers at config/launcher/ are listed in the popup menu. Each launcher can be lauched only once. While running, is not available in the popup menu. Second, when selecting a node that corresponds to a start node in config/deploy or that was interactively launched, the detailed view shows a 'remove' button, which can be used to exclude the component from the deployment. The result of the interactive manipulation is always available at config/managed/deploy. Hence, the current situation can be made persistent by using it as config/deploy. Fixes #2986 --- repos/gems/run/sculpt/leitzentrale.config | 13 +- repos/gems/src/app/depot_deploy/children.h | 9 + repos/gems/src/app/sculpt_manager/deploy.cc | 18 +- repos/gems/src/app/sculpt_manager/deploy.h | 74 ++++-- repos/gems/src/app/sculpt_manager/graph.h | 157 +++++++++-- repos/gems/src/app/sculpt_manager/gui.cc | 11 +- repos/gems/src/app/sculpt_manager/gui.h | 3 +- repos/gems/src/app/sculpt_manager/main.cc | 246 ++++++++++++++---- .../src/app/sculpt_manager/model/launchers.h | 102 ++++++++ .../gems/src/app/sculpt_manager/model/popup.h | 31 +++ .../app/sculpt_manager/model/runtime_state.h | 102 +++++++- repos/gems/src/app/sculpt_manager/network.cc | 3 +- repos/gems/src/app/sculpt_manager/runtime.h | 6 +- .../app/sculpt_manager/runtime/nic_router.cc | 26 +- repos/gems/src/app/sculpt_manager/types.h | 2 + .../app/sculpt_manager/view/popup_dialog.h | 116 +++++++++ 16 files changed, 804 insertions(+), 115 deletions(-) create mode 100644 repos/gems/src/app/sculpt_manager/model/launchers.h create mode 100644 repos/gems/src/app/sculpt_manager/model/popup.h create mode 100644 repos/gems/src/app/sculpt_manager/view/popup_dialog.h diff --git a/repos/gems/run/sculpt/leitzentrale.config b/repos/gems/run/sculpt/leitzentrale.config index 961048ed2..d47eb7f91 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 7b76c4ec0..d438e2586 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 5fcfe332e..d3de409a9 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 766d31f9b..e9f87c8c9 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 becb75a9e..a10a73d84 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 7ac52950e..f28d80b6b 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 72ae2417b..b55df6aa1 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 306669482..cad4f757e 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 000000000..36c16682a --- /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 000000000..2a80366d3 --- /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 f5416dc35..ebd36be59 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 d6b3d6692..21292f38f 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 a89a05b0f..1b6741067 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 3b44921b3..3677b1e9c 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 7ae5d7c0f..a56810468 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 000000000..57cd48c3c --- /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_ */