sculpt: browse depot index files in '+' menu

This commit turns the '+' menu into a tool for the following tasks:

- Selecting and downloading of depot index files
- Browsing of the hierarchical depot index files
- Installation of packages found in the index files
- Interactive routing configuration of a selected package
- Deployment of configured component
This commit is contained in:
Norman Feske 2019-02-24 04:14:31 +01:00 committed by Christian Helmuth
parent b7f5aae64a
commit c0b93190d0
19 changed files with 1895 additions and 139 deletions

View File

@ -140,6 +140,7 @@
</vfs>
<policy label="log" decoration="yes"/>
<policy label="runtime -> leitzentrale -> runtime_view" decoration="no" motion="20"/>
<policy label_prefix="gui -> popup" decoration="no" motion="20"/>
<policy label_prefix="gui" decoration="no"/>
<default-policy/>
</config>

View File

@ -261,16 +261,22 @@ class Depot_deploy::Child : public List_model<Child>::Element
}
}
void gen_query(Xml_generator &xml) const
bool blueprint_needed() const
{
if (_configured() || _pkg_incomplete)
return;
return false;
if (_defined_by_launcher() && !_launcher_xml.constructed())
return;
return false;
xml.node("blueprint", [&] () {
xml.attribute("pkg", _blueprint_pkg_path); });
return true;
}
void gen_query(Xml_generator &xml) const
{
if (blueprint_needed())
xml.node("blueprint", [&] () {
xml.attribute("pkg", _blueprint_pkg_path); });
}
/**

View File

@ -141,6 +141,14 @@ class Depot_deploy::Children
return result;
}
bool any_blueprint_needed() const
{
bool result = false;
_children.for_each([&] (Child const &child) {
result |= child.blueprint_needed(); });
return result;
}
bool exists(Child::Name const &name) const
{
bool result = false;

View File

@ -73,6 +73,14 @@ class Depot_download_manager::Import
State state = DOWNLOAD_IN_PROGRESS;
bool in_progress() const
{
return state == DOWNLOAD_IN_PROGRESS
|| state == DOWNLOAD_COMPLETE
|| state == VERIFICATION_IN_PROGRESS
|| state == VERIFIED;
}
Item(Registry<Item> &registry, Archive::Path const &path)
:
_element(registry, *this), path(path)
@ -281,6 +289,15 @@ class Depot_download_manager::Import
});
});
}
bool in_progress() const
{
bool result = false;
_items.for_each([&] (Item const &item) {
result |= item.in_progress(); });
return result;
}
};
#endif /* _IMPORT_H_ */

View File

@ -174,6 +174,8 @@ struct Depot_download_manager::Main : Import::Download_progress
Job::Update_policy policy(_heap);
_jobs.update_from_xml(policy, _installation.xml());
_depot_query_count.value++;
_generate_init_config();
}
@ -378,7 +380,6 @@ void Depot_download_manager::Main::_handle_init_state()
_verified.update();
bool reconfigure_init = false;
bool import_finished = false;
if (!_import.constructed())
return;
@ -450,14 +451,8 @@ void Depot_download_manager::Main::_handle_init_state()
if (extract_state.exited && extract_state.code != 0)
error("extract failed with exit code ", extract_state.code);
if (extract_state.exited && extract_state.code == 0) {
if (extract_state.exited && extract_state.code == 0)
import.all_verified_archives_extracted();
import_finished = true;
/* kill extract, re-issue new depot query to start next iteration */
_depot_query_count.value++;
reconfigure_init = true;
}
}
/* flag failed jobs to prevent re-attempts in subsequent import iterations */
@ -470,9 +465,14 @@ void Depot_download_manager::Main::_handle_init_state()
if (reconfigure_init)
_update_state_report();
if (import_finished)
if (!import.in_progress()) {
_import.destruct();
/* re-issue new depot query to start next iteration */
_depot_query_count.value++;
reconfigure_init = true;
}
if (reconfigure_init)
_generate_init_config();
}

View File

@ -98,37 +98,24 @@ void Sculpt::Deploy::handle_deploy()
});
});
/* update query for blueprints of all unconfigured start nodes */
if (_arch.valid()) {
_depot_query_reporter.generate([&] (Xml_generator &xml) {
xml.attribute("arch", _arch);
xml.attribute("version", _query_version.value);
_children.gen_queries(xml);
});
}
/*
* Apply blueprint after 'gen_queries'. Otherwise the application of a
* stale blueprint may flag children whose pkgs have been installed in the
* meanwhile as incomplete, suppressing their respective queries.
*/
try {
Xml_node const blueprint = _blueprint_rom.xml();
/* apply blueprint, except when stale */
typedef String<32> Version;
Version const version = blueprint.attribute_value("version", Version());
if (version == Version(_query_version.value))
if (version == Version(_depot_query.depot_query_version().value))
_children.apply_blueprint(_blueprint_rom.xml());
}
catch (...) {
error("spurious exception during deploy update (apply_blueprint)"); }
/* update query for blueprints of all unconfigured start nodes */
if (_children.any_blueprint_needed())
_depot_query.trigger_depot_query();
/* feed missing packages to installation queue */
if (!_installation.try_generate_manually_managed())
_installation.generate([&] (Xml_generator &xml) {
xml.attribute("arch", _arch);
_children.gen_installation_entries(xml); });
update_installation();
/* apply runtime condition checks */
update_child_conditions();

View File

@ -23,10 +23,12 @@
/* local includes */
#include <model/launchers.h>
#include <model/download_queue.h>
#include <types.h>
#include <runtime.h>
#include <managed_config.h>
#include <view/dialog.h>
#include <depot_query.h>
namespace Sculpt { struct Deploy; }
@ -43,23 +45,22 @@ struct Sculpt::Deploy
Runtime_config_generator &_runtime_config_generator;
Depot_query &_depot_query;
Attached_rom_dataspace const &_launcher_listing_rom;
Attached_rom_dataspace const &_blueprint_rom;
Download_queue const &_download_queue;
typedef String<16> Arch;
Arch _arch { };
struct Query_version { unsigned value; } _query_version { 0 };
Child_state cached_depot_rom_state {
"depot_rom", Ram_quota{24*1024*1024}, Cap_quota{200} };
Child_state uncached_depot_rom_state {
"dynamic_depot_rom", Ram_quota{8*1024*1024}, Cap_quota{200} };
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'
*
@ -122,7 +123,9 @@ struct Sculpt::Deploy
bool update_needed() const
{
return _manual_installation_scheduled || _children.any_incomplete();
return _manual_installation_scheduled
|| _children.any_incomplete()
|| _download_queue.any_active_download();
}
void handle_deploy();
@ -130,14 +133,8 @@ struct Sculpt::Deploy
void _handle_managed_deploy()
{
_managed_deploy_rom.update();
_query_version.value++;
handle_deploy();
}
void _handle_blueprint()
{
_blueprint_rom.update();
handle_deploy();
_depot_query.trigger_depot_query();
}
/**
@ -191,13 +188,10 @@ struct Sculpt::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 };
void restart()
{
/* ignore stale query results */
_query_version.value++;
_depot_query.trigger_depot_query();
_children.apply_config(Xml_node("<config/>"));
}
@ -208,18 +202,41 @@ struct Sculpt::Deploy
handle_deploy();
}
void gen_depot_query(Xml_generator &xml) const
{
_children.gen_queries(xml);
}
void update_installation()
{
/* feed missing packages to installation queue */
if (_installation.try_generate_manually_managed())
return;
_installation.generate([&] (Xml_generator &xml) {
xml.attribute("arch", _arch);
_children.gen_installation_entries(xml);
_download_queue.gen_installation_entries(xml);
});
}
Deploy(Env &env, Allocator &alloc, Runtime_info const &runtime_info,
Dialog::Generator &dialog_generator,
Runtime_config_generator &runtime_config_generator,
Attached_rom_dataspace const &launcher_listing_rom)
Depot_query &depot_query,
Attached_rom_dataspace const &launcher_listing_rom,
Attached_rom_dataspace const &blueprint_rom,
Download_queue const &download_queue)
:
_env(env), _alloc(alloc), _runtime_info(runtime_info),
_dialog_generator(dialog_generator),
_runtime_config_generator(runtime_config_generator),
_launcher_listing_rom(launcher_listing_rom)
_depot_query(depot_query),
_launcher_listing_rom(launcher_listing_rom),
_blueprint_rom(blueprint_rom),
_download_queue(download_queue)
{
_managed_deploy_rom.sigh(_managed_deploy_handler);
_blueprint_rom.sigh(_blueprint_handler);
}
};

View File

@ -0,0 +1,74 @@
/*
* \brief Interface for querying information about the depot
* \author Norman Feske
* \date 2019-02-22
*/
/*
* Copyright (C) 2019 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 _DEPOT_QUERY_H_
#define _DEPOT_QUERY_H_
#include "types.h"
namespace Sculpt {
struct Depot_query;
static inline bool blueprint_missing (Xml_node, Path const &);
static inline bool blueprint_any_missing (Xml_node);
static inline bool blueprint_any_rom_missing(Xml_node);
}
struct Sculpt::Depot_query : Interface
{
struct Version { unsigned value; };
virtual Version depot_query_version() const = 0;
virtual void trigger_depot_query() = 0;
};
static inline bool Sculpt::blueprint_missing(Xml_node blueprint, Path const &path)
{
bool result = false;
blueprint.for_each_sub_node("missing", [&] (Xml_node missing) {
if (missing.attribute_value("path", Path()) == path)
result = true; });
return result;
}
static inline bool Sculpt::blueprint_any_missing(Xml_node blueprint)
{
return blueprint.has_sub_node("missing");
}
static inline bool Sculpt::blueprint_any_rom_missing(Xml_node blueprint)
{
bool result = false;
blueprint.for_each_sub_node("pkg", [&] (Xml_node pkg) {
pkg.for_each_sub_node("missing_rom", [&] (Xml_node missing_rom) {
/* ld.lib.so is always taken from the base system */
Label const label = missing_rom.attribute_value("label", Label());
if (label == "ld.lib.so")
return;
/* some ingredient is not extracted yet, or actually missing */
result = true;
});
});
return result;
}
#endif /* _DEPOT_QUERY_H_ */

View File

@ -42,7 +42,9 @@ struct Sculpt::Main : Input_event_handler,
Runtime_config_generator,
Storage::Target_user,
Graph::Action,
Popup_dialog::Action
Popup_dialog::Action,
Popup_dialog::Construction_info,
Depot_query
{
Env &_env;
@ -184,6 +186,67 @@ struct Sculpt::Main : Input_event_handler,
&& _deploy.update_needed();
}
Download_queue _download_queue { _heap };
/*****************
** Depot query **
*****************/
Depot_query::Version _query_version { 0 };
Expanding_reporter _depot_query_reporter { _env, "query", "depot_query"};
/**
* Depot_query interface
*/
Depot_query::Version depot_query_version() const override
{
return _query_version;
}
/**
* Depot_query interface
*/
void trigger_depot_query() override
{
_query_version.value++;
if (_deploy._arch.valid()) {
_depot_query_reporter.generate([&] (Xml_generator &xml) {
xml.attribute("arch", _deploy._arch);
xml.attribute("version", _query_version.value);
_popup_dialog.gen_depot_query(xml);
/* update query for blueprints of all unconfigured start nodes */
_deploy.gen_depot_query(xml);
});
}
}
/*********************
** Blueprint query **
*********************/
Attached_rom_dataspace _blueprint_rom { _env, "report -> runtime/depot_query/blueprint" };
Signal_handler<Main> _blueprint_handler {
_env.ep(), *this, &Main::_handle_blueprint };
void _handle_blueprint()
{
_blueprint_rom.update();
Xml_node const blueprint = _blueprint_rom.xml();
_runtime_state.apply_to_construction([&] (Component &component) {
_popup_dialog.apply_blueprint(component, blueprint); });
_deploy.handle_deploy();
}
/************
** Deploy **
@ -214,7 +277,8 @@ struct Sculpt::Main : Input_event_handler,
}
Deploy _deploy { _env, _heap, _runtime_state, *this, *this, _launcher_listing_rom };
Deploy _deploy { _env, _heap, _runtime_state, *this, *this, *this,
_launcher_listing_rom, _blueprint_rom, _download_queue };
Attached_rom_dataspace _manual_deploy_rom { _env, "config -> deploy" };
@ -418,7 +482,8 @@ struct Sculpt::Main : Input_event_handler,
&& !_graph.add_button_hovered()) {
_popup.state = Popup::OFF;
_popup_dialog.reset_hover();
_popup_dialog.reset();
discard_construction();
/* de-select '+' button */
_graph._gen_graph_dialog();
@ -427,15 +492,15 @@ struct Sculpt::Main : Input_event_handler,
_handle_window_layout();
}
if (_graph.hovered()) _graph.click(*this);
if (_graph.hovered()) _graph.click(*this);
if (_popup_dialog.hovered()) _popup_dialog.click(*this);
}
if (ev.key_release(Input::BTN_LEFT)) {
if (_hovered_dialog == Hovered::STORAGE) _storage.dialog.clack(_storage);
if (_graph.hovered()) _graph.clack(*this);
if (_graph.hovered()) _graph.clack(*this);
if (_popup_dialog.hovered()) _popup_dialog.clack(*this);
}
if (_keyboard_focus.target == Keyboard_focus::WPA_PASSPHRASE)
@ -469,6 +534,17 @@ struct Sculpt::Main : Input_event_handler,
_handle_window_layout();
}
void _close_popup_dialog()
{
/* close popup menu */
_popup.state = Popup::OFF;
_popup_dialog.reset();
_handle_window_layout();
/* reset state of the '+' button */
_graph._gen_graph_dialog();
}
/*
* Popup_dialog::Action interface
*/
@ -476,19 +552,57 @@ struct Sculpt::Main : Input_event_handler,
{
_runtime_state.launch(launcher, launcher);
/* close popup menu */
_popup.state = Popup::OFF;
_popup_dialog.reset_hover();
_handle_window_layout();
/* reset state of the '+' button */
_graph._gen_graph_dialog();
_close_popup_dialog();
/* trigger change of the deployment */
_deploy.update_managed_deploy_config(_manual_deploy_rom.xml());
}
Popup_dialog _popup_dialog { _env, _launchers, _runtime_state };
Start_name new_construction(Component::Path const &pkg,
Component::Info const &info) override
{
return _runtime_state.new_construction(pkg, info);
}
void _apply_to_construction(Popup_dialog::Action::Apply_to &fn) override
{
_runtime_state.apply_to_construction([&] (Component &c) { fn.apply_to(c); });
}
void discard_construction() override { _runtime_state.discard_construction(); }
void launch_construction() override
{
_runtime_state.launch_construction();
_close_popup_dialog();
/* trigger change of the deployment */
_deploy.update_managed_deploy_config(_manual_deploy_rom.xml());
}
void trigger_download(Path const &path) override
{
_download_queue.add(path);
/* incorporate new download-queue content into update */
_deploy.update_installation();
generate_runtime_config();
}
/**
* Popup_dialog::Construction_info interface
*/
void _with_construction(Popup_dialog::Construction_info::With const &fn) const override
{
_runtime_state.with_construction([&] (Component const &c) { fn.with(c); });
}
Popup_dialog _popup_dialog { _env, _heap, _launchers,
_network._nic_state, _network._nic_target,
_runtime_state, _cached_runtime_config,
_download_queue, *this, *this };
Managed_config<Main> _fb_drv_config {
_env, "config", "fb_drv", *this, &Main::_handle_fb_drv_config };
@ -591,6 +705,7 @@ struct Sculpt::Main : Input_event_handler,
_window_list .sigh(_window_list_handler);
_decorator_margins .sigh(_decorator_margins_handler);
_launcher_listing_rom.sigh(_launcher_listing_handler);
_blueprint_rom .sigh(_blueprint_handler);
/*
* Generate initial configurations
@ -886,11 +1001,33 @@ void Sculpt::Main::_handle_update_state()
_update_state_rom.update();
generate_dialog();
bool const installation_complete =
!_update_state_rom.xml().attribute_value("progress", false);
Xml_node const update_state = _update_state_rom.xml();
if (installation_complete)
if (update_state.num_sub_nodes() == 0)
return;
bool const popup_watches_downloads =
_popup_dialog.interested_in_download();
_download_queue.apply_update_state(update_state);
_download_queue.remove_inactive_downloads();
Xml_node const blueprint = _blueprint_rom.xml();
bool const new_depot_query_needed = popup_watches_downloads
|| blueprint_any_missing(blueprint)
|| blueprint_any_rom_missing(blueprint);
if (new_depot_query_needed)
trigger_depot_query();
if (popup_watches_downloads)
_deploy.update_installation();
bool const installation_complete =
!update_state.attribute_value("progress", false);
if (installation_complete) {
_deploy.reattempt_after_installation();
}
}

View File

@ -0,0 +1,83 @@
/*
* \brief Representation of a component
* \author Norman Feske
* \date 2019-02-25
*/
/*
* Copyright (C) 2019 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__COMPONENT_H_
#define _MODEL__COMPONENT_H_
#include <types.h>
#include <model/route.h>
namespace Sculpt { struct Component; }
struct Sculpt::Component : Noncopyable
{
Route::Update_policy _route_update_policy;
typedef Depot::Archive::Path Path;
typedef Depot::Archive::Name Name;
typedef String<100> Info;
typedef Start_name Service;
/* defined at construction time */
Path const path;
Info const info;
/* defined when blueprint arrives */
uint64_t ram { };
size_t caps { };
bool blueprint_known = false;
List_model<Route> routes { };
Component(Allocator &alloc, Path const &path, Info const &info)
: _route_update_policy(alloc), path(path), info(info) { }
~Component()
{
routes.update_from_xml(_route_update_policy, Xml_node("<empty/>"));
}
void try_apply_blueprint(Xml_node blueprint)
{
blueprint.for_each_sub_node("pkg", [&] (Xml_node pkg) {
if (path != pkg.attribute_value("path", Path()))
return;
pkg.with_sub_node("runtime", [&] (Xml_node runtime) {
ram = runtime.attribute_value("ram", Number_of_bytes());
caps = runtime.attribute_value("caps", 0UL);
runtime.with_sub_node("requires", [&] (Xml_node requires) {
routes.update_from_xml(_route_update_policy, requires); });
});
blueprint_known = true;
});
}
bool all_routes_defined() const
{
bool result = true;
routes.for_each([&] (Route const &route) {
if (!route.selected_service.constructed())
result = false; });
return result;
}
};
#endif /* _MODEL__COMPONENT_H_ */

View File

@ -0,0 +1,125 @@
/*
* \brief List of depot downloads that are currently in flight
* \author Norman Feske
* \date 2019-02-25
*/
/*
* Copyright (C) 2019 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__DOWNLOAD_QUEUE_H_
#define _MODEL__DOWNLOAD_QUEUE_H_
#include <base/registry.h>
#include <types.h>
namespace Sculpt { struct Download_queue; }
struct Sculpt::Download_queue : Noncopyable
{
struct Download : Interface
{
Path const path;
enum class State { DOWNLOADING, FAILED, DONE } state;
Download(Path const &path) : path(path), state(State::DOWNLOADING) { }
void gen_installation_entry(Xml_generator &xml) const
{
if (state != State::DOWNLOADING)
return;
if (Depot::Archive::index(path))
xml.node("index", [&] () {
xml.attribute("path", path); });
else
xml.node("archive", [&] () {
xml.attribute("path", path);
xml.attribute("source", "no"); });
}
};
Allocator &_alloc;
Registry<Registered<Download> > _downloads { };
Download_queue(Allocator &alloc) : _alloc(alloc) { }
void add(Path const &path)
{
log("add to download queue: ", path);
bool already_exists = false;
_downloads.for_each([&] (Download const &download) {
if (download.path == path)
already_exists = true; });
if (already_exists)
return;
new (_alloc) Registered<Download>(_downloads, path);
}
bool in_progress(Path const &path) const
{
bool result = false;
_downloads.for_each([&] (Download const &download) {
if (download.path == path && download.state == Download::State::DOWNLOADING)
result = true; });
return result;
}
void apply_update_state(Xml_node state)
{
/* 'elem' may be of type 'index' or 'archive' */
state.for_each_sub_node([&] (Xml_node elem) {
Path const path = elem.attribute_value("path", Path());
_downloads.for_each([&] (Download &download) {
if (download.path != path)
return;
typedef String<16> State;
State const state = elem.attribute_value("state", State());
if (state == "done") download.state = Download::State::DONE;
if (state == "failed") download.state = Download::State::FAILED;
if (state == "unavailable") download.state = Download::State::FAILED;
if (state == "corrupted") download.state = Download::State::FAILED;
});
});
}
void remove_inactive_downloads()
{
_downloads.for_each([&] (Download &download) {
if (download.state != Download::State::DOWNLOADING)
destroy(_alloc, &download); });
}
void gen_installation_entries(Xml_generator &xml) const
{
_downloads.for_each([&] (Download const &download) {
download.gen_installation_entry(xml); });
}
bool any_active_download() const
{
bool result = false;
_downloads.for_each([&] (Download const &download) {
if (!result && download.state == Download::State::DOWNLOADING)
result = true; });
return result;
}
};
#endif /* _MODEL__ROUTE_H_ */

View File

@ -0,0 +1,162 @@
/*
* \brief Representation of a route to a service
* \author Norman Feske
* \date 2019-02-25
*/
/*
* Copyright (C) 2019 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__ROUTE_H_
#define _MODEL__ROUTE_H_
#include <types.h>
#include <model/service.h>
namespace Sculpt { struct Route; }
struct Sculpt::Route : List_model<Route>::Element
{
typedef String<32> Id;
typedef String<80> Info;
static char const *xml_type(Service::Type type)
{
switch (type) {
case Service::Type::AUDIO_IN: return "audio_in";
case Service::Type::AUDIO_OUT: return "audio_out";
case Service::Type::BLOCK: return "block";
case Service::Type::FILE_SYSTEM: return "file_system";
case Service::Type::NIC: return "nic";
case Service::Type::NITPICKER: return "nitpicker";
case Service::Type::RM: return "rm";
case Service::Type::IO_MEM: return "io_mem";
case Service::Type::IO_PORT: return "io_port";
case Service::Type::IRQ: return "irq";
case Service::Type::REPORT: return "report";
case Service::Type::ROM: return "rom";
case Service::Type::TERMINAL: return "terminal";
case Service::Type::TRACE: return "trace";
case Service::Type::USB: return "usb";
case Service::Type::RTC: return "rtc";
case Service::Type::PLATFORM: return "platform";
case Service::Type::VM: return "vm";
case Service::Type::UNDEFINED: break;
}
return "undefined";
}
static char const *_pretty_name(Service::Type type)
{
switch (type) {
case Service::Type::AUDIO_IN: return "Audio input";
case Service::Type::AUDIO_OUT: return "Audio output";
case Service::Type::BLOCK: return "Block device";
case Service::Type::FILE_SYSTEM: return "File system";
case Service::Type::NIC: return "Network";
case Service::Type::NITPICKER: return "GUI";
case Service::Type::RM: return "Region maps";
case Service::Type::IO_MEM: return "Direct memory-mapped I/O";
case Service::Type::IO_PORT: return "Direct port I/O";
case Service::Type::IRQ: return "Direct device interrupts";
case Service::Type::REPORT: return "Report";
case Service::Type::ROM: return "ROM";
case Service::Type::TERMINAL: return "Terminal";
case Service::Type::TRACE: return "Tracing";
case Service::Type::USB: return "USB";
case Service::Type::RTC: return "Real-time clock";
case Service::Type::PLATFORM: return "Device access";
case Service::Type::VM: return "Hardware-based virtualization";
case Service::Type::UNDEFINED: break;
}
return "<undefined>";
}
static Service::Type _required(Xml_node node)
{
for (unsigned i = 0; i < (unsigned)Service::Type::UNDEFINED; i++) {
Service::Type const s = (Service::Type)i;
if (node.has_type(xml_type(s)))
return s;
}
return Service::Type::UNDEFINED;
}
Service::Type const required;
Label const required_label;
Constructible<Service> selected_service { };
Id selected_service_id { };
/**
* Constructor
*
* \param required sub node of a runtime's <requires> node
*/
Route(Xml_node node)
:
required(_required(node)),
required_label(node.attribute_value("label", Label()))
{ }
void print(Output &out) const
{
Genode::print(out, _pretty_name(required));
if (required_label.valid())
Genode::print(out, " (", required_label, ") ");
}
void gen_xml(Xml_generator &xml) const
{
if (!selected_service.constructed()) {
warning("no service assigned to route ", *this);
return;
}
gen_named_node(xml, "service", Service::name_attr(required), [&] () {
if (required_label.valid())
xml.attribute("label", required_label);
selected_service->gen_xml(xml);
});
}
struct Update_policy
{
typedef Route Element;
Allocator &_alloc;
Update_policy(Allocator &alloc) : _alloc(alloc) { }
void destroy_element(Route &elem) { destroy(_alloc, &elem); }
Route &create_element(Xml_node node)
{
return *new (_alloc) Route(node);
}
void update_element(Route &, Xml_node) { }
static bool element_matches_xml_node(Route const &elem, Xml_node node)
{
return elem.required == _required(node)
&& elem.required_label == node.attribute_value("label", Label());
}
static bool node_is_element(Xml_node node)
{
return _required(node) != Service::Type::UNDEFINED;
}
};
};
#endif /* _MODEL__ROUTE_H_ */

View File

@ -17,12 +17,15 @@
/* Genode includes */
#include <util/xml_node.h>
#include <util/list_model.h>
#include <base/registry.h>
/* local includes */
#include <types.h>
#include <model/service.h>
namespace Sculpt { class Runtime_config; }
class Sculpt::Runtime_config
{
private:
@ -52,9 +55,8 @@ class Sculpt::Runtime_config
node.with_sub_node("parent", [&] (Xml_node parent) {
typedef String<16> Service;
Service const service =
node.attribute_value("name", Service());
Service::Type_name const service =
node.attribute_value("name", Service::Type_name());
Label const dst_label =
parent.attribute_value("label", Label());
@ -123,6 +125,54 @@ class Sculpt::Runtime_config
return result;
}
struct Child_service : Service, List_model<Child_service>::Element
{
static Service::Type type_from_xml(Xml_node service)
{
auto const name = service.attribute_value("name", Service::Type_name());
for (unsigned i = 0; i < (unsigned)Type::UNDEFINED; i++) {
Type const t = (Type)i;
if (name == Service::name_attr(t))
return t;
}
return Type::UNDEFINED;
}
Child_service(Start_name server, Xml_node provides)
: Service(server, type_from_xml(provides), Label()) { }
struct Update_policy
{
typedef Child_service Element;
Start_name _server;
Allocator &_alloc;
Update_policy(Start_name const &server, Allocator &alloc)
: _server(server), _alloc(alloc) { }
void destroy_element(Element &elem) { destroy(_alloc, &elem); }
Element &create_element(Xml_node node)
{
return *new (_alloc) Child_service(_server, node);
}
void update_element(Element &, Xml_node) { }
static bool element_matches_xml_node(Element const &elem, Xml_node node)
{
return type_from_xml(node) == elem.type;
}
static bool node_is_element(Xml_node node)
{
return type_from_xml(node) != Service::Type::UNDEFINED;
}
};
};
public:
struct Component : List_model<Component>::Element
@ -177,21 +227,29 @@ class Sculpt::Runtime_config
fn(dep.to_name); });
}
List_model<Child_service> _child_services { };
Component(Start_name const &name) : name(name) { }
template <typename FN>
void for_each_service(FN const &fn) const
{
_child_services.for_each(fn);
}
struct Update_policy
{
typedef Component Element;
Allocator &_alloc;
Dep::Update_policy _dep_update_policy { _alloc };
Update_policy(Allocator &alloc) : _alloc(alloc) { }
void destroy_element(Component &elem)
{
elem.deps.update_from_xml(_dep_update_policy, Xml_node("<route/>"));
/* flush list models */
update_element(elem, Xml_node("<start> <route/> <provides/> </start>"));
destroy(_alloc, &elem);
}
@ -205,8 +263,20 @@ class Sculpt::Runtime_config
{
elem.primary_dependency = _primary_dependency(node);
node.with_sub_node("route", [&] (Xml_node route) {
elem.deps.update_from_xml(_dep_update_policy, route); });
{
Dep::Update_policy policy { _alloc };
node.with_sub_node("route", [&] (Xml_node route) {
elem.deps.update_from_xml(policy, route); });
}
{
Child_service::Update_policy policy { elem.name, _alloc };
node.with_sub_node("provides", [&] (Xml_node provides) {
elem._child_services.update_from_xml(policy,
provides); });
}
}
static bool element_matches_xml_node(Component const &elem, Xml_node node)
@ -222,6 +292,43 @@ class Sculpt::Runtime_config
List_model<Component> _components { };
struct Parent_services
{
typedef Registered_no_delete<Service> Parent_service;
typedef Service::Type Type;
Registry<Parent_service> _r { };
Parent_service const
_focus { _r, Type::NITPICKER, "keyboard focus", "focus" },
_backdrop { _r, Type::NITPICKER, "desktop background", "backdrop" },
_nitpicker { _r, Type::NITPICKER, "system GUI server" },
_config_fs { _r, Type::FILE_SYSTEM, "writeable system configuration", "config" },
_report_fs { _r, Type::FILE_SYSTEM, "read-only system reports", "report" },
_capslock { _r, Type::ROM, "global capslock state", "capslock" },
_vimrc { _r, Type::ROM, "default vim configuration", "config -> vimrc" },
_fonts { _r, Type::ROM, "system font configuration", "config -> managed/fonts" },
_pf_info { _r, Type::ROM, "platform information", "platform_info" },
_report { _r, Type::REPORT, "system reports" },
_rm { _r, Type::RM, "custom virtual memory objects" },
_io_mem { _r, Type::IO_MEM, "raw hardware access" },
_io_port { _r, Type::IO_PORT, "raw hardware access" },
_irq { _r, Type::IRQ, "raw hardware access" },
_rtc { _r, Type::RTC, "system clock" },
_block { _r, Type::BLOCK, "direct block-device access" },
_usb { _r, Type::USB, "direct USB-device access" },
_pci_wifi { _r, Type::PLATFORM, "wifi hardware", "wifi" },
_pci_net { _r, Type::PLATFORM, "network hardware", "nic" },
_pci_audio { _r, Type::PLATFORM, "audio hardware", "audio" },
_pci_acpi { _r, Type::PLATFORM, "ACPI", "acpica" },
_trace { _r, Type::TRACE, "system-global tracing" },
_vm { _r, Type::VM, "virtualization hardware" };
template <typename FN>
void for_each(FN const &fn) const { _r.for_each(fn); }
} _parent_services { };
public:
Runtime_config(Allocator &alloc) : _alloc(alloc) { }
@ -246,6 +353,15 @@ class Sculpt::Runtime_config
component.deps.for_each([&] (Component::Dep const &dep) {
fn(dep.to_name); }); } });
}
template <typename FN>
void for_each_service(FN const &fn) const
{
_components.for_each([&] (Component const &component) {
component.for_each_service(fn); });
_parent_services.for_each(fn);
}
};
#endif /* _MODEL__RUNTIME_CONFIG_H_ */

View File

@ -22,6 +22,7 @@
#include <types.h>
#include <runtime.h>
#include <model/runtime_config.h>
#include <model/component.h>
namespace Sculpt { class Runtime_state; }
@ -85,12 +86,64 @@ class Sculpt::Runtime_state : public Runtime_info
{
Start_name const name;
Path const launcher;
Constructible<Component> construction { };
bool launched;
/**
* Constructor used for child started via launcher
*/
Launched_child(Start_name const &name, Path const &launcher)
: name(name), launcher(launcher) { };
: name(name), launcher(launcher), launched(true) { };
/**
* Constructor used for interactively configured child
*/
Launched_child(Allocator &alloc, Start_name const &name,
Component::Path const &pkg_path,
Component::Info const &info)
:
name(name), launcher(), launched(false)
{
construction.construct(alloc, pkg_path, info);
}
void gen_deploy_start_node(Xml_generator &xml) const
{
if (!launched)
return;
gen_named_node(xml, "start", name, [&] () {
/* interactively constructed */
if (construction.constructed()) {
xml.attribute("pkg", construction->path);
xml.node("route", [&] () {
construction->routes.for_each([&] (Route const &route) {
route.gen_xml(xml); }); });
}
/* created via launcher */
else {
if (name != launcher)
xml.attribute("launcher", launcher);
}
});
}
};
Registry<Registered<Launched_child> > _launched_children { };
Registered<Launched_child> *_currently_constructed = nullptr;
bool _construction_in_progress() const
{
return _currently_constructed
&& _currently_constructed->construction.constructed();
}
struct Update_policy : List_model<Child>::Update_policy
{
Allocator &_alloc;
@ -133,6 +186,12 @@ class Sculpt::Runtime_state : public Runtime_info
static bool node_is_element(Xml_node node) { return node.has_type("child"); }
};
/*
* Noncopyable
*/
Runtime_state(Runtime_state const &);
Runtime_state &operator = (Runtime_state const &);
public:
Runtime_state(Allocator &alloc) : _alloc(alloc) { }
@ -151,9 +210,15 @@ class Sculpt::Runtime_state : public Runtime_info
bool present_in_runtime(Start_name const &name) const override
{
bool result = false;
_children.for_each([&] (Child const &child) {
if (!result && child.name == name)
result = true; });
_launched_children.for_each([&] (Launched_child const &child) {
if (!result && child.name == name)
result = true; });
return result;
}
@ -175,9 +240,7 @@ class Sculpt::Runtime_state : public Runtime_info
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); }); });
child.gen_deploy_start_node(xml); });
}
Info info(Start_name const &name) const
@ -243,7 +306,7 @@ class Sculpt::Runtime_state : public Runtime_info
* entry from '_launched_children'.
*/
bool was_interactively_launched = false;
_launched_children.for_each([&] (Launched_child &child) {
_launched_children.for_each([&] (Registered<Launched_child> &child) {
if (child.name == name) {
was_interactively_launched = true;
destroy(_alloc, &child);
@ -264,6 +327,55 @@ class Sculpt::Runtime_state : public Runtime_info
new (_alloc) Registered<Launched_child>(_launched_children, name, launcher);
}
Start_name new_construction(Component::Path const pkg,
Component::Info const &info)
{
/* allow only one construction at a time */
discard_construction();
/* determine unique name for new child */
Depot::Archive::Name const archive_name = Depot::Archive::name(pkg);
Start_name unique_name = archive_name;
unsigned cnt = 1;
while (present_in_runtime(unique_name))
unique_name = Start_name(archive_name, ".", ++cnt);
_currently_constructed = new (_alloc)
Registered<Launched_child>(_launched_children, _alloc,
unique_name, pkg, info);
return unique_name;
}
void discard_construction()
{
if (_currently_constructed) {
destroy(_alloc, _currently_constructed);
_currently_constructed = nullptr;
}
}
template <typename FN>
void apply_to_construction(FN const &fn)
{
if (_construction_in_progress())
fn(*_currently_constructed->construction);
}
template <typename FN>
void with_construction(FN const &fn) const
{
if (_construction_in_progress())
fn(*_currently_constructed->construction);
}
void launch_construction()
{
if (_currently_constructed)
_currently_constructed->launched = true;
_currently_constructed = nullptr;
}
void reset_abandoned_and_launched_children()
{
_abandoned_children.for_each([&] (Abandoned_child &child) {

View File

@ -0,0 +1,28 @@
/*
* \brief Sculpt version
* \author Norman Feske
* \date 2019-02-24
*/
/*
* Copyright (C) 2019 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__SCULPT_VERSION_H_
#define _MODEL__SCULPT_VERSION_H_
#include <types.h>
namespace Sculpt { struct Sculpt_version; }
struct Sculpt::Sculpt_version : String<6>
{
Sculpt_version(Env &env)
: String<6>(Attached_rom_dataspace(env, "VERSION").local_addr<char const>())
{ }
};
#endif /* _MODEL__SCULPT_VERSION_H_ */

View File

@ -0,0 +1,93 @@
/*
* \brief Representation of service that can be targeted by a route
* \author Norman Feske
* \date 2019-02-25
*/
/*
* Copyright (C) 2019 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__SERVICE_H_
#define _MODEL__SERVICE_H_
#include "types.h"
namespace Sculpt { struct Service; }
struct Sculpt::Service
{
typedef String<16> Type_name;
typedef String<32> Info;
enum class Type {
AUDIO_IN, AUDIO_OUT, BLOCK, FILE_SYSTEM, NIC, NITPICKER,
RM, IO_MEM, IO_PORT, IRQ, REPORT, ROM, TERMINAL, TRACE,
USB, RTC, PLATFORM, VM, UNDEFINED };
Start_name server { }; /* invalid for parent service */
Type type;
Label label;
Info info;
/**
* Return name attribute value of <service name="..."> node
*/
static char const *name_attr(Type type)
{
switch (type) {
case Type::AUDIO_IN: return "Audio_in";
case Type::AUDIO_OUT: return "Audio_out";
case Type::BLOCK: return "Block";
case Type::FILE_SYSTEM: return "File_system";
case Type::NIC: return "Nic";
case Type::NITPICKER: return "Nitpicker";
case Type::RM: return "RM";
case Type::IO_MEM: return "IO_MEM";
case Type::IO_PORT: return "IO_PORT";
case Type::IRQ: return "IRQ";
case Type::REPORT: return "Report";
case Type::ROM: return "ROM";
case Type::TERMINAL: return "Terminal";
case Type::TRACE: return "TRACE";
case Type::USB: return "Usb";
case Type::RTC: return "Rtc";
case Type::PLATFORM: return "Platform";
case Type::VM: return "VM";
case Type::UNDEFINED: break;
}
return "undefined";
}
/**
* Constructor for child service
*/
Service(Start_name const &server, Type type, Label const &label)
: server(server), type(type), label(label), info(server) { }
/**
* Constructor for parent service
*/
Service(Type type, Info const &info, Label const &label = Label())
: type(type), label(label), info(info) { }
void gen_xml(Xml_generator &xml) const
{
bool const parent = !server.valid();
xml.node(parent ? "parent" : "child", [&] () {
if (!parent)
xml.attribute("name", server);
if (label.valid())
xml.attribute("label", label);
});
}
};
#endif /* _MODEL__SERVICE_H_ */

View File

@ -0,0 +1,438 @@
/*
* \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.
*/
#include <view/popup_dialog.h>
using namespace Sculpt;
void Popup_dialog::_gen_pkg_info(Xml_generator &xml,
Component const &component) const
{
gen_named_node(xml, "label", "info", [&] () {
xml.attribute("text", Component::Info(" ", component.info, " ")); });
_gen_info_label(xml, "pad1", "");
_gen_info_label(xml, "path", component.path);
}
void Popup_dialog::_gen_pkg_elements(Xml_generator &xml,
Component const &component) const
{
typedef Component::Info Info;
_gen_sub_menu_title(xml, "back", Menu::Name("Add ", _construction_name));
_gen_pkg_info(xml, component);
_gen_info_label(xml, "resources", Info(Capacity{component.ram}, " ",
component.caps, " caps"));
_gen_info_label(xml, "pad2", "");
unsigned cnt = 0;
component.routes.for_each([&] (Route const &route) {
Route::Id const id(cnt++);
gen_named_node(xml, "frame", id, [&] () {
xml.node("vbox", [&] () {
bool const selected = _route_selected(id);
bool const defined = route.selected_service.constructed();
if (!selected) {
_gen_route_entry(xml, id,
defined ? Info(route.selected_service->info)
: Info(route),
defined);
}
/*
* List of routing options
*/
if (selected) {
_gen_route_entry(xml, "back", Info(route), true, "back");
unsigned cnt = 0;
_runtime_config.for_each_service([&] (Service const &service) {
Hoverable_item::Id const id("service.", cnt++);
bool const service_selected =
route.selected_service.constructed() &&
id == route.selected_service_id;
if (service.type == route.required)
_gen_route_entry(xml, id, service.info, service_selected);
});
}
});
});
});
/*
* Display "Add component" button once all routes are defined
*/
if (component.all_routes_defined()) {
gen_named_node(xml, "button", "launch", [&] () {
_action_item.gen_button_attr(xml, "launch");
xml.node("label", [&] () {
xml.attribute("text", "Add component"); });
});
}
}
void Popup_dialog::_gen_menu_elements(Xml_generator &xml) const
{
/*
* Lauchers
*/
if (_state == TOP_LEVEL || _state < DEPOT_SHOWN) {
_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_menu_entry(xml, info.path, info.path, false);
});
_gen_menu_entry(xml, "depot", "Depot ...", false);
}
/*
* Depot users with an available index
*/
if (_state == DEPOT_SHOWN || _state == INDEX_REQUESTED) {
_gen_sub_menu_title(xml, "back", "Depot");
_scan_rom.xml().for_each_sub_node("user", [&] (Xml_node user) {
User const name = user.attribute_value("name", User());
bool const selected = (_selected_user == name);
if (_index_avail(name))
_gen_menu_entry(xml, name, User(name, " ..."), selected);
});
/*
* Depot selection menu item
*/
if (_state == DEPOT_SHOWN || _state == INDEX_REQUESTED) {
if (_nic_ready())
_gen_menu_entry(xml, "selection", "Selection ...", false);
}
}
/*
* List of depot users for removing/adding indices
*/
if (_state == DEPOT_SELECTION) {
_gen_sub_menu_title(xml, "back", "Selection");
_scan_rom.xml().for_each_sub_node("user", [&] (Xml_node user) {
User const name = user.attribute_value("name", User());
bool const selected = _index_avail(name);
String<32> const suffix = _download_queue.in_progress(_index_path(name))
? " fetch... " : " ";
_gen_menu_entry(xml, name, User(name, suffix), selected, "checkbox");
});
}
/*
* Title of index
*/
if (_state >= INDEX_SHOWN && _state < PKG_SHOWN) {
Menu::Name title("Depot ", _selected_user);
if (_menu._level)
title = Menu::Name(title, " ", _menu, " ");
_gen_sub_menu_title(xml, "back", title);
}
/*
* Index menu
*/
if (_state >= INDEX_SHOWN && _state < PKG_SHOWN) {
unsigned cnt = 0;
_for_each_menu_item([&] (Xml_node item) {
Hoverable_item::Id const id(cnt);
if (item.has_type("index")) {
auto const name = item.attribute_value("name", Menu::Name());
_gen_menu_entry(xml, id, Menu::Name(name, " ..."), false);
}
if (item.has_type("pkg")) {
auto const path = item.attribute_value("path", Depot::Archive::Path());
auto const name = Depot::Archive::name(path);
auto const version = Depot::Archive::version(path);
bool selected = false;
bool const installing = _download_queue.in_progress(path);
_construction_info.with_construction([&] (Component const &component) {
if (component.path == path)
selected = true;
});
String<100> const text(name, " " "(", version, ")",
installing ? " installing... " : "...");
_gen_menu_entry(xml, id, text, selected);
if (selected && _pkg_missing && !installing) {
_construction_info.with_construction([&] (Component const &component) {
gen_named_node(xml, "float", "install", [&] () {
gen_named_node(xml, "vbox", "vbox", [&] () {
if (_nic_ready()) {
_gen_pkg_info(xml, component);
_gen_info_label(xml, "pad2", "");
gen_named_node(xml, "float", "install", [&] () {
xml.node("button", [&] () {
_install_item.gen_button_attr(xml, "install");
xml.node("label", [&] () {
xml.attribute("text", "Install");
});
});
});
} else {
_gen_info_label(xml, "pad2", "");
_gen_info_label(xml, "path", component.path);
_gen_info_label(xml, "pad3", "");
xml.node("label", [&] () {
xml.attribute("text", "not installed"); });
}
_gen_info_label(xml, "pad4", "");
});
});
});
}
}
cnt++;
});
}
/*
* Pkg configuration
*/
if (_state >= PKG_SHOWN)
_construction_info.with_construction([&] (Component const &component) {
_gen_pkg_elements(xml, component); });
}
void Popup_dialog::click(Action &action)
{
Hoverable_item::Id const clicked = _item._hovered;
_item._hovered = Hoverable_item::Id();
_action_item .propose_activation_on_click();
_install_item.propose_activation_on_click();
Route::Id const clicked_route = _route_item._hovered;
auto back_to_index = [&] ()
{
_state = INDEX_SHOWN;
action.discard_construction();
_selected_route.destruct();
};
if (_state == TOP_LEVEL) {
if (clicked == "depot") {
_state = DEPOT_REQUESTED;
_depot_query.trigger_depot_query();
} else {
action.launch_global(clicked);
}
}
else if (_state == DEPOT_SHOWN) {
/* back to top-level menu */
if (clicked == "back") {
_state = TOP_LEVEL;
} else if (clicked == "selection") {
_state = DEPOT_SELECTION;
} else {
/* enter depot users menu */
_selected_user = clicked;
_state = INDEX_REQUESTED;
_depot_query.trigger_depot_query();
}
}
else if (_state == DEPOT_SELECTION) {
/* back to depot users */
if (clicked == "back") {
_state = DEPOT_SHOWN;
} else {
if (!_index_avail(clicked))
action.trigger_download(_index_path(clicked));
}
}
else if (_state >= INDEX_SHOWN && _state < PKG_SHOWN) {
/* back to depot users */
if (_menu._level == 0 && clicked == "back") {
_state = DEPOT_SHOWN;
_selected_user = User();
} else {
/* go one menu up */
if (clicked == "back") {
_menu._selected[_menu._level] = Menu::Name();
_menu._level--;
action.discard_construction();
} else {
/* enter sub menu of index */
if (_menu._level < Menu::MAX_LEVELS - 1) {
unsigned cnt = 0;
_for_each_menu_item([&] (Xml_node item) {
if (clicked == Hoverable_item::Id(cnt)) {
if (item.has_type("index")) {
Menu::Name const name =
item.attribute_value("name", Menu::Name());
_menu._selected[_menu._level] = name;
_menu._level++;
} else if (item.has_type("pkg")) {
auto path = item.attribute_value("path", Component::Path());
auto info = item.attribute_value("info", Component::Info());
_construction_name = action.new_construction(path, info);
_state = PKG_REQUESTED;
_pkg_missing = false;
_depot_query.trigger_depot_query();
}
}
cnt++;
});
}
}
}
}
else if (_state == PKG_SHOWN) {
/* back to index */
if (clicked == "back") {
back_to_index();
} else {
/* select route to present routing options */
if (clicked_route.valid()) {
_state = ROUTE_SELECTED;
_selected_route.construct(clicked_route);
}
}
}
else if (_state == ROUTE_SELECTED) {
/* back to index */
if (clicked == "back") {
back_to_index();
} else {
/* close selected route */
if (clicked_route == "back") {
_state = PKG_SHOWN;
_selected_route.destruct();
} else {
bool clicked_on_selected_route = false;
_apply_to_selected_route(action, [&] (Route &route) {
unsigned cnt = 0;
_runtime_config.for_each_service([&] (Service const &service) {
Hoverable_item::Id const id("service.", cnt++);
if (clicked_route == id) {
bool const clicked_service_already_selected =
route.selected_service.constructed() &&
id == route.selected_service_id;
if (clicked_service_already_selected) {
/* clear selection */
route.selected_service.destruct();
route.selected_service_id = Hoverable_item::Id();
} else {
/* select different service */
route.selected_service.construct(service);
route.selected_service_id = id;
}
_state = PKG_SHOWN;
_selected_route.destruct();
clicked_on_selected_route = true;
}
});
});
/* select different route */
if (!clicked_on_selected_route && clicked_route.valid()) {
_state = ROUTE_SELECTED;
_selected_route.construct(clicked_route);
}
}
}
}
generate();
}

View File

@ -14,10 +14,21 @@
#ifndef _VIEW__POPUP_DIALOG_H_
#define _VIEW__POPUP_DIALOG_H_
/* Genode includes */
#include <os/reporter.h>
#include <depot/archive.h>
/* local includes */
#include <types.h>
#include <model/launchers.h>
#include <model/sculpt_version.h>
#include <model/component.h>
#include <model/runtime_config.h>
#include <model/download_queue.h>
#include <model/nic_state.h>
#include <view/dialog.h>
#include <view/selectable_item.h>
#include <view/activatable_item.h>
#include <depot_query.h>
namespace Sculpt { struct Popup_dialog; }
@ -26,9 +37,70 @@ struct Sculpt::Popup_dialog
{
Env &_env;
Launchers const &_launchers;
Allocator &_alloc;
Runtime_info const &_runtime_info;
Sculpt_version const _sculpt_version { _env };
Launchers const &_launchers;
Nic_state const &_nic_state;
Nic_target const &_nic_target;
bool _nic_ready() const { return _nic_target.ready() && _nic_state.ready(); }
Runtime_info const &_runtime_info;
Runtime_config const &_runtime_config;
Download_queue const &_download_queue;
Depot_query &_depot_query;
struct Construction_info : Interface
{
struct With : Interface { virtual void with(Component const &) const = 0; };
virtual void _with_construction(With const &) const = 0;
template <typename FN>
void with_construction(FN const &fn) const
{
struct _With : With {
FN const &_fn;
_With(FN const &fn) : _fn(fn) { }
void with(Component const &c) const override { _fn(c); }
};
_with_construction(_With(fn));
}
};
struct Action : Interface
{
virtual void launch_global(Path const &launcher) = 0;
virtual Start_name new_construction(Component::Path const &pkg,
Component::Info const &info) = 0;
struct Apply_to : Interface { virtual void apply_to(Component &) = 0; };
virtual void _apply_to_construction(Apply_to &) = 0;
template <typename FN>
void apply_to_construction(FN const &fn)
{
struct _Apply_to : Apply_to {
FN const &_fn;
_Apply_to(FN const &fn) : _fn(fn) { }
void apply_to(Component &c) override { _fn(c); }
} apply_fn(fn);
_apply_to_construction(apply_fn);
}
virtual void discard_construction() = 0;
virtual void launch_construction() = 0;
virtual void trigger_download(Path const &) = 0;
};
Construction_info const &_construction_info;
Expanding_reporter _dialog_reporter { _env, "dialog", "popup_dialog" };
@ -37,10 +109,85 @@ struct Sculpt::Popup_dialog
Signal_handler<Popup_dialog> _hover_handler {
_env.ep(), *this, &Popup_dialog::_handle_hover };
Hoverable_item _item { };
Hoverable_item _item { };
Activatable_item _action_item { };
Activatable_item _install_item { };
Hoverable_item _route_item { };
bool _hovered = false;
enum State { TOP_LEVEL, DEPOT_REQUESTED, DEPOT_SHOWN, DEPOT_SELECTION,
INDEX_REQUESTED, INDEX_SHOWN,
PKG_REQUESTED, PKG_SHOWN, ROUTE_SELECTED };
State _state { TOP_LEVEL };
typedef Depot::Archive::User User;
User _selected_user { };
bool _pkg_missing = false;
Component::Name _construction_name { };
Constructible<Route::Id> _selected_route { };
bool _route_selected(Route::Id const &id) const
{
return _selected_route.constructed() && id == _selected_route->string();
}
template <typename FN>
void _apply_to_selected_route(Action &action, FN const &fn)
{
unsigned cnt = 0;
action.apply_to_construction([&] (Component &component) {
component.routes.for_each([&] (Route &route) {
if (_route_selected(Route::Id(cnt++)))
fn(route); }); });
}
struct Menu
{
enum { MAX_LEVELS = 5 };
unsigned _level = 0;
typedef String<64> Name;
Name _selected[MAX_LEVELS] { };
void print(Output &out) const
{
using Genode::print;
for (unsigned i = 0; i < _level; i++) {
print(out, _selected[i]);
if (i + 1 < _level)
print(out, " ");
}
}
template <typename FN>
void _for_each_item(Xml_node index, FN const &fn, unsigned level) const
{
if (level == _level) {
index.for_each_sub_node(fn);
return;
}
index.for_each_sub_node("index", [&] (Xml_node index) {
if (index.attribute_value("name", Name()) == _selected[level])
_for_each_item(index, fn, level + 1); });
}
template <typename FN>
void for_each_item(Xml_node index, FN const &fn) const
{
_for_each_item(index, fn, 0);
}
};
Menu _menu { };
void _handle_hover()
{
_hover_rom.update();
@ -49,7 +196,11 @@ struct Sculpt::Popup_dialog
_hovered = hover.has_sub_node("dialog");
bool const changed = _item.match(hover, "dialog", "frame", "vbox", "hbox", "name");
bool const changed =
_item .match(hover, "dialog", "frame", "vbox", "hbox", "name") |
_action_item .match(hover, "dialog", "frame", "vbox", "button", "name") |
_install_item.match(hover, "dialog", "frame", "vbox", "float", "vbox", "float", "button", "name") |
_route_item .match(hover, "dialog", "frame", "vbox", "frame", "vbox", "hbox", "name");
if (changed)
generate();
@ -57,78 +208,279 @@ struct Sculpt::Popup_dialog
bool hovered() const { return _hovered; };
Attached_rom_dataspace _scan_rom { _env, "report -> runtime/depot_query/scan" };
Signal_handler<Popup_dialog> _scan_handler {
_env.ep(), *this, &Popup_dialog::_handle_scan };
void _handle_scan()
{
_scan_rom.update();
if (_state == DEPOT_REQUESTED)
_state = DEPOT_SHOWN;
if (_state != TOP_LEVEL)
generate();
}
Attached_rom_dataspace _index_rom { _env, "report -> runtime/depot_query/index" };
Signal_handler<Popup_dialog> _index_handler {
_env.ep(), *this, &Popup_dialog::_handle_index };
void _handle_index()
{
/* prevent modifications of index while browing it */
if (_state >= INDEX_SHOWN)
return;
_index_rom.update();
if (_state == INDEX_REQUESTED)
_state = INDEX_SHOWN;
generate();
}
bool _index_avail(User const &user) const
{
bool result = false;
_index_rom.xml().for_each_sub_node("index", [&] (Xml_node index) {
if (index.attribute_value("user", User()) == user)
result = true; });
return result;
};
Path _index_path(User const &user) const
{
return Path(user, "/index/", _sculpt_version);
}
void _gen_sub_menu_title(Xml_generator &xml,
Start_name const &name,
Start_name const &text) const
{
gen_named_node(xml, "hbox", name, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "button", "back", [&] () {
xml.attribute("selected", "yes");
xml.attribute("style", "back");
_item.gen_button_attr(xml, name);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "label", [&] () {
xml.attribute("font", "title/regular");
xml.attribute("text", Path(" ", text));
});
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
void _gen_menu_entry(Xml_generator &xml, Start_name const &name,
Component::Info const &text, bool selected,
char const *style = "radio") const
{
gen_named_node(xml, "hbox", name, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "button", "button", [&] () {
if (selected)
xml.attribute("selected", "yes");
xml.attribute("style", style);
_item.gen_button_attr(xml, name);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", Path(" ", text)); });
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
void _gen_route_entry(Xml_generator &xml,
Start_name const &name,
Start_name const &text,
bool selected, char const *style = "radio") const
{
gen_named_node(xml, "hbox", name, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "button", "button", [&] () {
if (selected)
xml.attribute("selected", "yes");
xml.attribute("style", style);
_route_item.gen_button_attr(xml, name);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", Path(" ", text)); });
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
template <typename FN>
void _for_each_menu_item(FN const &fn) const
{
Xml_node index = _index_rom.xml();
/*
* The index may contain duplicates, evaluate only the first match.
*/
bool first = true;
index.for_each_sub_node("index", [&] (Xml_node index) {
if (index.attribute_value("user", User()) != _selected_user)
return;
if (first)
_menu.for_each_item(index, fn);
first = false;
});
}
static void _gen_info_label(Xml_generator &xml, char const *name,
Component::Info const &info)
{
gen_named_node(xml, "label", name, [&] () {
xml.attribute("font", "annotation/regular");
xml.attribute("text", Component::Info(" ", info, " ")); });
}
void _gen_pkg_info (Xml_generator &, Component const &) const;
void _gen_pkg_elements (Xml_generator &, Component const &) const;
void _gen_menu_elements(Xml_generator &) const;
void generate()
{
_dialog_reporter.generate([&] (Xml_generator &xml) {
xml.node("frame", [&] () {
xml.node("vbox", [&] () {
_gen_menu_elements(xml); }); }); });
}
_launchers.for_each([&] (Launchers::Info const &info) {
void click(Action &action);
/* allow each launcher to be used only once */
if (_runtime_info.present_in_runtime(info.path))
return;
void clack(Action &action)
{
_action_item.confirm_activation_on_clack();
_install_item.confirm_activation_on_clack();
gen_named_node(xml, "hbox", info.path, [&] () {
if (_action_item.activated("launch")) {
action.launch_construction();
reset();
}
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "button", "button", [&] () {
xml.attribute("style", "radio");
_item.gen_button_attr(xml, info.path);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", Path(" ", info.path)); });
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
});
});
if (_pkg_missing && _install_item.activated("install")) {
_construction_info.with_construction([&] (Component const &component) {
action.trigger_download(component.path);
_install_item.reset();
generate();
});
});
}
}
struct Action : Interface
{
virtual void launch_global(Path const &launcher) = 0;
};
void click(Action &action)
{
action.launch_global(_item._hovered);
}
/*
* XXX
*
* This method should not be needed. However, in some situations,
* the popup menu_view does not receive a leave event when the popup
* is closed, to the effect of maintaining stale hover information
* instead of generating an empty hover report. So from the sculpt-
* manager's point of view, the popup is still hovered even if it is
* actually closed. The 'reset_hover' shortcuts the regular hover
* update in this situation.
*/
void reset_hover()
void reset()
{
_item._hovered = Hoverable_item::Id();
_route_item._hovered = Hoverable_item::Id();
_action_item.reset();
_install_item.reset();
_hovered = false;
_state = TOP_LEVEL;
_selected_user = User();
_selected_route.destruct();
_menu._level = 0;
}
Popup_dialog(Env &env, Launchers const &launchers,
Runtime_info const &runtime_info)
Popup_dialog(Env &env, Allocator &alloc,
Launchers const &launchers,
Nic_state const &nic_state,
Nic_target const &nic_target,
Runtime_info const &runtime_info,
Runtime_config const &runtime_config,
Download_queue const &download_queue,
Depot_query &depot_query,
Construction_info const &construction_info)
:
_env(env), _launchers(launchers), _runtime_info(runtime_info)
_env(env), _alloc(alloc), _launchers(launchers),
_nic_state(nic_state), _nic_target(nic_target),
_runtime_info(runtime_info), _runtime_config(runtime_config),
_download_queue(download_queue), _depot_query(depot_query),
_construction_info(construction_info)
{
_hover_rom.sigh(_hover_handler);
_scan_rom.sigh(_scan_handler);
_index_rom.sigh(_index_handler);
generate();
}
void gen_depot_query(Xml_generator &xml) const
{
if (_state >= TOP_LEVEL)
xml.node("scan", [&] () {
xml.attribute("users", "yes"); });
if (_state >= TOP_LEVEL)
_scan_rom.xml().for_each_sub_node("user", [&] (Xml_node user) {
xml.node("index", [&] () {
User const name = user.attribute_value("name", User());
xml.attribute("user", name);
xml.attribute("version", _sculpt_version);
if (_state >= INDEX_REQUESTED && _selected_user == name)
xml.attribute("content", "yes");
});
});
if (_state >= PKG_REQUESTED)
_construction_info.with_construction([&] (Component const &component) {
xml.node("blueprint", [&] () {
xml.attribute("pkg", component.path); }); });
}
void apply_blueprint(Component &construction, Xml_node blueprint)
{
if (_state < PKG_REQUESTED)
return;
_pkg_missing = blueprint_missing(blueprint, construction.path)
|| blueprint_any_rom_missing(blueprint);
construction.try_apply_blueprint(blueprint);
if (construction.blueprint_known)
_state = PKG_SHOWN;
generate();
}
bool interested_in_download() const
{
if (_state == DEPOT_SELECTION)
return true;
return _state >= PKG_REQUESTED && _pkg_missing;
}
};
#endif /* _VIEW__POPUP_DIALOG_H_ */

View File

@ -121,7 +121,7 @@ namespace Sculpt {
}
/**
* Query attribute value from XML sub nodd
* Query attribute value from XML sub node
*
* The list of arguments except for the last one refer to XML path into the
* XML structure. The last argument denotes the queried attribute name.