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
This commit is contained in:
Norman Feske 2018-09-11 15:17:17 +02:00 committed by Christian Helmuth
parent b51ee34b11
commit f4c55aa4db
16 changed files with 804 additions and 115 deletions

View File

@ -96,14 +96,16 @@
<policy label="decorator -> pointer" report="wm -> pointer"/>
<policy label="gui -> config" report="manager -> gui_config"/>
<policy label="gui -> menu -> dialog" report="manager -> menu_dialog"/>
<policy label="gui -> popup -> dialog" report="manager -> popup_dialog"/>
<policy label="manager -> menu_view_hover" report="gui -> menu -> hover"/>
<policy label="manager -> popup_view_hover" report="gui -> popup -> hover"/>
<policy label="manager -> window_list" report="wm -> window_list"/>
<policy label="manager -> decorator_margins" report="decorator -> decorator_margins"/>
<policy label="nitpicker -> focus" report="manager -> focus"/>
</config>
</start>
<start name="wm" caps="150">
<start name="wm" caps="200">
<resource name="RAM" quantum="2M"/>
<provides> <service name="Nitpicker"/> </provides>
<config>
@ -118,7 +120,7 @@
</route>
</start>
<start name="decorator" caps="200">
<start name="decorator" caps="300">
<binary name="themed_decorator"/>
<resource name="RAM" quantum="8M"/>
<config>
@ -166,6 +168,8 @@
<route>
<service name="Report" label="runtime_config">
<child name="config_fs_report" label="managed -> runtime"/> </service>
<service name="Report" label="deploy_config">
<child name="config_fs_report" label="managed -> deploy"/> </service>
<service name="Report" label="wifi_config">
<child name="config_fs_report" label="managed -> wifi"/> </service>
<service name="Report" label="fonts_config">
@ -185,6 +189,7 @@
<service name="ROM" label="nitpicker_hover"> <parent/> </service>
<service name="ROM" label_prefix="report ->"> <parent/> </service>
<service name="ROM" label="menu_view_hover"> <child name="report_rom"/> </service>
<service name="ROM" label="popup_view_hover"> <child name="report_rom"/> </service>
<service name="ROM" label="runtime_view_hover"> <parent/> </service>
<service name="ROM" label="window_list"> <child name="report_rom"/> </service>
<service name="ROM" label="decorator_margins"> <child name="report_rom"/> </service>
@ -206,11 +211,11 @@
<start name="gui" caps="1400">
<binary name="init"/>
<resource name="RAM" quantum="14M"/>
<resource name="RAM" quantum="20M"/>
<route>
<service name="ROM" label="config"> <child name="report_rom"/> </service>
<service name="ROM" label_last="dialog"> <child name="report_rom"/> </service>
<service name="Nitpicker"> <child name="nitpicker"/> </service>
<service name="Nitpicker"> <child name="wm"/> </service>
<service name="File_system" label="fonts"> <child name="fonts_fs"/> </service>
<service name="Report" label_last="hover"> <child name="report_rom"/> </service>
<any-service> <parent/> </any-service>

View File

@ -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_ */

View File

@ -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 '<static>' 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");
}

View File

@ -22,6 +22,7 @@
#include <children.h>
/* local includes */
#include <model/launchers.h>
#include <types.h>
#include <runtime.h>
#include <managed_config.h>
@ -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 <common_routes> from manual deploy config */
deploy.for_each_sub_node("common_routes", [&] (Xml_node node) {
append_xml_node(node); });
/*
* Copy the <start> 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<Deploy> _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<Deploy> _manual_deploy_handler {
_env.ep(), *this, &Deploy::_handle_manual_deploy };
Signal_handler<Deploy> _launcher_listing_handler {
_env.ep(), *this, &Deploy::_handle_manual_deploy };
Signal_handler<Deploy> _managed_deploy_handler {
_env.ep(), *this, &Deploy::_handle_managed_deploy };
Signal_handler<Deploy> _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);
}
};

View File

@ -24,6 +24,7 @@
#include <types.h>
#include <xml.h>
#include <model/capacity.h>
#include <model/popup.h>
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<Graph> _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();
}
};

View File

@ -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); });
}

View File

@ -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;

View File

@ -25,6 +25,7 @@
#include <model/runtime_state.h>
#include <model/child_exit_state.h>
#include <view/download_status.h>
#include <view/popup_dialog.h>
#include <gui.h>
#include <nitpicker.h>
#include <keyboard_focus.h>
@ -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<Main> _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 <file> 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<Main> _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<Main> _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 <size_t N, typename FN>
void _with_window(Xml_node window_list, String<N> const &match, FN const &fn)
{
window_list.for_each_sub_node("window", [&] (Xml_node win) {
if (win.attribute_value("label", String<N>()) == match)
fn(win); });
}
Attached_rom_dataspace _window_list { _env, "window_list" };
Signal_handler<Main> _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));
});
});

View File

@ -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 <util/list_model.h>
#include <util/avl_tree.h>
/* local includes */
#include <types.h>
#include <runtime.h>
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<Launcher>, List_model<Launcher>::Element
{
Avl_tree<Launcher> &_avl_tree;
Launcher(Avl_tree<Launcher> &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<Launcher> _sorted { };
List_model<Launcher> _launchers { };
struct Update_policy : List_model<Launcher>::Update_policy
{
Allocator &_alloc;
Avl_tree<Launcher> &_sorted;
Update_policy(Allocator &alloc, Avl_tree<Launcher> &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 <typename FN>
void for_each(FN const &fn) const { _sorted.for_each(fn); }
};
#endif /* _MODEL__LAUNCHERS_H_ */

View File

@ -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_ */

View File

@ -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<Child> _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<Registered<Abandoned_child> > _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<Registered<Launched_child> > _launched_children { };
struct Update_policy : List_model<Child>::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_child>(_abandoned_children, name);
}
void launch(Start_name const &name, Path const &launcher)
{
new (_alloc) Registered<Launched_child>(_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_ */

View File

@ -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); });
}

View File

@ -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; };

View File

@ -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<Log_session> (xml);
gen_parent_route<Timer::Session> (xml);
gen_parent_route<Report::Session> (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");
}
});
}

View File

@ -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 };
}

View File

@ -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 <types.h>
#include <model/launchers.h>
#include <view/dialog.h>
#include <view/selectable_item.h>
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<Popup_dialog> _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_ */