mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-08 20:05:54 +00:00
sculpt: system update and presets
The new dialog accessible via the "System" panel button hosts the system-update dialog and the preset selection. Fixes #4744
This commit is contained in:
parent
5687dc06fd
commit
105b3cd21d
@ -127,6 +127,8 @@
|
||||
report="manager -> network_dialog"/>
|
||||
<policy label="runtime -> leitzentrale -> settings_view -> dialog"
|
||||
report="manager -> settings_dialog"/>
|
||||
<policy label="runtime -> leitzentrale -> system_view -> dialog"
|
||||
report="manager -> system_dialog"/>
|
||||
<policy label="runtime -> leitzentrale -> file_browser_view -> dialog"
|
||||
report="manager -> file_browser_dialog"/>
|
||||
<policy label="runtime -> leitzentrale -> popup_view -> dialog"
|
||||
@ -141,6 +143,8 @@
|
||||
report="runtime -> leitzentrale -> network_view -> hover"/>
|
||||
<policy label="manager -> settings_view_hover"
|
||||
report="runtime -> leitzentrale -> settings_view -> hover"/>
|
||||
<policy label="manager -> system_view_hover"
|
||||
report="runtime -> leitzentrale -> system_view -> hover"/>
|
||||
<policy label="manager -> file_browser_view_hover"
|
||||
report="runtime -> leitzentrale -> file_browser_view -> hover"/>
|
||||
<policy label="manager -> runtime_view_hover"
|
||||
@ -190,6 +194,7 @@
|
||||
</vfs>
|
||||
<policy label="log" decoration="yes" motion="20"/>
|
||||
<policy label="runtime -> leitzentrale -> settings_view" decoration="no" motion="20"/>
|
||||
<policy label="runtime -> leitzentrale -> system_view" decoration="no" motion="20"/>
|
||||
<policy label="runtime -> leitzentrale -> file_browser_view" decoration="no" motion="30"/>
|
||||
<policy label="runtime -> leitzentrale -> network_view" decoration="no" motion="20"/>
|
||||
<policy label="runtime -> leitzentrale -> runtime_view" decoration="no" motion="30"/>
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include <view/popup_dialog.h>
|
||||
#include <view/panel_dialog.h>
|
||||
#include <view/settings_dialog.h>
|
||||
#include <view/system_dialog.h>
|
||||
#include <view/file_browser_dialog.h>
|
||||
#include <menu_view.h>
|
||||
#include <gui.h>
|
||||
@ -61,6 +62,9 @@ struct Sculpt::Main : Input_event_handler,
|
||||
Panel_dialog::Action,
|
||||
Popup_dialog::Action,
|
||||
Settings_dialog::Action,
|
||||
Software_presets_dialog::Action,
|
||||
Depot_users_dialog::Action,
|
||||
Software_update_dialog::Action,
|
||||
File_browser_dialog::Action,
|
||||
Popup_dialog::Construction_info,
|
||||
Depot_query,
|
||||
@ -75,6 +79,9 @@ struct Sculpt::Main : Input_event_handler,
|
||||
|
||||
Sculpt_version const _sculpt_version { _env };
|
||||
|
||||
Build_info const _build_info =
|
||||
Build_info::from_xml(Attached_rom_dataspace(_env, "build_info").xml());
|
||||
|
||||
Registry<Child_state> _child_states { };
|
||||
|
||||
Input::Seq_number _global_input_seq_number { };
|
||||
@ -223,10 +230,11 @@ struct Sculpt::Main : Input_event_handler,
|
||||
/* trigger loading of the configuration from the sculpt partition */
|
||||
_prepare_version.value++;
|
||||
|
||||
_download_queue.remove_inactive_downloads();
|
||||
_download_queue.reset();
|
||||
_deploy.restart();
|
||||
|
||||
generate_runtime_config();
|
||||
generate_dialog();
|
||||
}
|
||||
|
||||
Network _network { _env, _heap, *this, _child_states, *this, _runtime_state, _pci_info };
|
||||
@ -274,6 +282,9 @@ struct Sculpt::Main : Input_event_handler,
|
||||
|
||||
Fs_tool_version _fs_tool_version { 0 };
|
||||
|
||||
Index_update_queue _index_update_queue {
|
||||
_heap, _file_operation_queue, _download_queue };
|
||||
|
||||
|
||||
/*****************
|
||||
** Depot query **
|
||||
@ -281,6 +292,8 @@ struct Sculpt::Main : Input_event_handler,
|
||||
|
||||
Depot_query::Version _query_version { 0 };
|
||||
|
||||
Depot::Archive::User _image_index_user = _build_info.depot_user;
|
||||
|
||||
Expanding_reporter _depot_query_reporter { _env, "query", "depot_query"};
|
||||
|
||||
/**
|
||||
@ -296,6 +309,11 @@ struct Sculpt::Main : Input_event_handler,
|
||||
Timer::One_shot_timeout<Main> _deferred_depot_query_handler {
|
||||
_timer, *this, &Main::_handle_deferred_depot_query };
|
||||
|
||||
bool _system_dialog_watches_depot() const
|
||||
{
|
||||
return _system_visible && _system_dialog.update_tab_selected();
|
||||
}
|
||||
|
||||
void _handle_deferred_depot_query(Duration)
|
||||
{
|
||||
if (_deploy._arch.valid()) {
|
||||
@ -308,6 +326,17 @@ struct Sculpt::Main : Input_event_handler,
|
||||
xml.node("scan", [&] () {
|
||||
xml.attribute("users", "yes"); });
|
||||
|
||||
if (_system_dialog_watches_depot() || _scan_rom.xml().has_type("empty"))
|
||||
xml.node("scan", [&] () {
|
||||
xml.attribute("users", "yes"); });
|
||||
|
||||
if (_system_dialog_watches_depot() || _image_index_rom.xml().has_type("empty"))
|
||||
xml.node("image_index", [&] () {
|
||||
xml.attribute("os", "sculpt");
|
||||
xml.attribute("board", _build_info.board);
|
||||
xml.attribute("user", _image_index_user);
|
||||
});
|
||||
|
||||
_popup_dialog.gen_depot_query(xml, _scan_rom.xml());
|
||||
|
||||
/* update query for blueprints of all unconfigured start nodes */
|
||||
@ -380,6 +409,16 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_popup_dialog.depot_users_scan_updated();
|
||||
}
|
||||
|
||||
Attached_rom_dataspace _image_index_rom { _env, "report -> runtime/depot_query/image_index" };
|
||||
|
||||
Signal_handler<Main> _image_index_handler { _env.ep(), *this, &Main::_handle_image_index };
|
||||
|
||||
void _handle_image_index()
|
||||
{
|
||||
_image_index_rom.update();
|
||||
_system_menu_view.generate();
|
||||
}
|
||||
|
||||
Attached_rom_dataspace _launcher_listing_rom {
|
||||
_env, "report -> /runtime/launcher_query/listing" };
|
||||
|
||||
@ -443,6 +482,7 @@ struct Sculpt::Main : Input_event_handler,
|
||||
bool _log_visible = false;
|
||||
bool _network_visible = false;
|
||||
bool _settings_visible = false;
|
||||
bool _system_visible = false;
|
||||
|
||||
File_browser_state _file_browser_state { };
|
||||
|
||||
@ -459,6 +499,8 @@ struct Sculpt::Main : Input_event_handler,
|
||||
|
||||
bool settings_visible() const override { return _settings_visible; }
|
||||
|
||||
bool system_visible() const override { return _system_visible; }
|
||||
|
||||
bool inspect_tab_visible() const override { return _storage.any_file_system_inspected(); }
|
||||
|
||||
Panel_dialog::Tab selected_tab() const override { return _selected_tab; }
|
||||
@ -536,6 +578,9 @@ struct Sculpt::Main : Input_event_handler,
|
||||
{
|
||||
_main_menu_view.generate();
|
||||
_graph_menu_view.generate();
|
||||
|
||||
if (_system_visible)
|
||||
_system_menu_view.generate();
|
||||
}
|
||||
|
||||
Attached_rom_dataspace _runtime_state_rom { _env, "report -> runtime/state" };
|
||||
@ -689,6 +734,11 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_settings_menu_view.generate();
|
||||
_clicked_seq_number.destruct();
|
||||
}
|
||||
else if (_system_menu_view.hovered(seq)) {
|
||||
_system_dialog.click();
|
||||
_system_menu_view.generate();
|
||||
_clicked_seq_number.destruct();
|
||||
}
|
||||
else if (_network_menu_view.hovered(seq)) {
|
||||
_network.dialog.click(_network);
|
||||
_network_menu_view.generate();
|
||||
@ -717,6 +767,11 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_graph_menu_view.generate();
|
||||
_clacked_seq_number.destruct();
|
||||
}
|
||||
else if (_system_menu_view.hovered(seq)) {
|
||||
_system_dialog.clack();
|
||||
_system_menu_view.generate();
|
||||
_clacked_seq_number.destruct();
|
||||
}
|
||||
else if (_popup_menu_view.hovered(seq)) {
|
||||
_popup_dialog.clack(*this);
|
||||
_clacked_seq_number.destruct();
|
||||
@ -752,9 +807,14 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_try_handle_clack();
|
||||
}
|
||||
|
||||
if (_keyboard_focus.target == Keyboard_focus::WPA_PASSPHRASE)
|
||||
ev.handle_press([&] (Input::Keycode, Codepoint code) {
|
||||
_network.handle_key_press(code); });
|
||||
ev.handle_press([&] (Input::Keycode, Codepoint code) {
|
||||
if (_keyboard_focus.target == Keyboard_focus::WPA_PASSPHRASE)
|
||||
_network.handle_key_press(code);
|
||||
else if (_system_visible && _system_dialog.keyboard_needed())
|
||||
_system_dialog.handle_key(code);
|
||||
|
||||
need_generate_dialog = true;
|
||||
});
|
||||
|
||||
if (ev.press())
|
||||
_keyboard_focus.update();
|
||||
@ -774,7 +834,11 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_panel_menu_view.generate();
|
||||
}
|
||||
|
||||
void use(Storage_target const &target) override { _storage.use(target); }
|
||||
void use(Storage_target const &target) override
|
||||
{
|
||||
_download_queue.reset();
|
||||
_storage.use(target);
|
||||
}
|
||||
|
||||
void _reset_storage_dialog_operation()
|
||||
{
|
||||
@ -874,6 +938,66 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_handle_window_layout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Depot_users_dialog::Action interface
|
||||
*/
|
||||
void add_depot_url(Depot_url const &depot_url) override
|
||||
{
|
||||
using Content = File_operation_queue::Content;
|
||||
|
||||
_file_operation_queue.new_small_file(Path("/rw/depot/", depot_url.user, "/download"),
|
||||
Content { depot_url.download });
|
||||
|
||||
if (!_file_operation_queue.any_operation_in_progress())
|
||||
_file_operation_queue.schedule_next_operations();
|
||||
|
||||
generate_runtime_config();
|
||||
}
|
||||
|
||||
/**
|
||||
* Software_update_dialog::Action interface
|
||||
*/
|
||||
void query_image_index(Depot::Archive::User const &user) override
|
||||
{
|
||||
_image_index_user = user;
|
||||
trigger_depot_query();
|
||||
}
|
||||
|
||||
/**
|
||||
* Software_update_dialog::Action interface
|
||||
*/
|
||||
void trigger_image_download(Path const &path, Verify verify) override
|
||||
{
|
||||
_download_queue.remove_inactive_downloads();
|
||||
_download_queue.add(path, verify);
|
||||
_deploy.update_installation();
|
||||
generate_runtime_config();
|
||||
}
|
||||
|
||||
/**
|
||||
* Software_update_dialog::Action interface
|
||||
*/
|
||||
void update_image_index(Depot::Archive::User const &user, Verify verify) override
|
||||
{
|
||||
_download_queue.remove_inactive_downloads();
|
||||
_index_update_queue.remove_inactive_updates();
|
||||
_index_update_queue.add(Path(user, "/image/index"), verify);
|
||||
generate_runtime_config();
|
||||
}
|
||||
|
||||
/**
|
||||
* Software_update_dialog::Action interface
|
||||
*/
|
||||
void install_boot_image(Path const &path) override
|
||||
{
|
||||
_file_operation_queue.copy_all_files(Path("/rw/depot/", path), "/rw/boot");
|
||||
|
||||
if (!_file_operation_queue.any_operation_in_progress())
|
||||
_file_operation_queue.schedule_next_operations();
|
||||
|
||||
generate_runtime_config();
|
||||
}
|
||||
|
||||
/*
|
||||
* Panel::Action interface
|
||||
*/
|
||||
@ -914,6 +1038,39 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_refresh_panel_and_window_layout();
|
||||
}
|
||||
|
||||
/*
|
||||
* Panel::Action interface
|
||||
*/
|
||||
void toggle_system_visibility() override
|
||||
{
|
||||
_system_visible = !_system_visible;
|
||||
_refresh_panel_and_window_layout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Software_presets_dialog::Action interface
|
||||
*/
|
||||
void load_deploy_preset(Presets::Info::Name const &name) override
|
||||
{
|
||||
Xml_node const listing = _launcher_listing_rom.xml();
|
||||
|
||||
_download_queue.remove_inactive_downloads();
|
||||
|
||||
listing.for_each_sub_node("dir", [&] (Xml_node const &dir) {
|
||||
if (dir.attribute_value("path", Path()) == "/presets") {
|
||||
dir.for_each_sub_node("file", [&] (Xml_node const &file) {
|
||||
if (file.attribute_value("name", Presets::Info::Name()) == name) {
|
||||
file.with_optional_sub_node("config", [&] (Xml_node const &config) {
|
||||
_runtime_state.reset_abandoned_and_launched_children();
|
||||
_deploy.use_as_deploy_template(config);
|
||||
_deploy.update_managed_deploy_config();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Settings_dialog::Action interface
|
||||
*/
|
||||
@ -1144,6 +1301,7 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_deploy.update_installation();
|
||||
|
||||
generate_runtime_config();
|
||||
generate_dialog();
|
||||
}
|
||||
|
||||
void remove_index(Depot::Archive::User const &user) override
|
||||
@ -1181,6 +1339,15 @@ struct Sculpt::Main : Input_event_handler,
|
||||
Ram_quota{4*1024*1024}, Cap_quota{150},
|
||||
"settings_dialog", "settings_view_hover", *this };
|
||||
|
||||
System_dialog _system_dialog { _presets, _build_info, _network._nic_state,
|
||||
_download_queue, _index_update_queue,
|
||||
_file_operation_queue, _scan_rom,
|
||||
_image_index_rom, *this, *this, *this };
|
||||
|
||||
Menu_view _system_menu_view { _env, _child_states, _system_dialog, "system_view",
|
||||
Ram_quota{4*1024*1024}, Cap_quota{150},
|
||||
"system_dialog", "system_view_hover", *this };
|
||||
|
||||
Menu_view _main_menu_view { _env, _child_states, *this, "menu_view",
|
||||
Ram_quota{4*1024*1024}, Cap_quota{150},
|
||||
"menu_dialog", "menu_view_hover", *this };
|
||||
@ -1269,6 +1436,7 @@ struct Sculpt::Main : Input_event_handler,
|
||||
_scan_rom .sigh(_scan_handler);
|
||||
_launcher_listing_rom.sigh(_launcher_and_preset_listing_handler);
|
||||
_blueprint_rom .sigh(_blueprint_handler);
|
||||
_image_index_rom .sigh(_image_index_handler);
|
||||
_editor_saved_rom .sigh(_editor_saved_handler);
|
||||
_clicked_rom .sigh(_clicked_handler);
|
||||
|
||||
@ -1343,6 +1511,7 @@ void Sculpt::Main::_handle_window_layout()
|
||||
panel_view_label ("runtime -> leitzentrale -> panel_view"),
|
||||
menu_view_label ("runtime -> leitzentrale -> menu_view"),
|
||||
popup_view_label ("runtime -> leitzentrale -> popup_view"),
|
||||
system_view_label ("runtime -> leitzentrale -> system_view"),
|
||||
settings_view_label ("runtime -> leitzentrale -> settings_view"),
|
||||
network_view_label ("runtime -> leitzentrale -> network_view"),
|
||||
file_browser_view_label("runtime -> leitzentrale -> file_browser_view"),
|
||||
@ -1420,10 +1589,22 @@ void Sculpt::Main::_handle_window_layout()
|
||||
_with_window(window_list, Label("log"), [&] (Xml_node win) {
|
||||
gen_window(win, Rect(log_p1, log_p2)); });
|
||||
|
||||
int system_right_xpos = 0;
|
||||
_with_window(window_list, system_view_label, [&] (Xml_node win) {
|
||||
Area const size = win_size(win);
|
||||
Point const pos = _system_visible
|
||||
? Point(0, avail.y1())
|
||||
: Point(-size.w(), avail.y1());
|
||||
gen_window(win, Rect(pos, size));
|
||||
|
||||
if (_system_visible)
|
||||
system_right_xpos = size.w();
|
||||
});
|
||||
|
||||
_with_window(window_list, settings_view_label, [&] (Xml_node win) {
|
||||
Area const size = win_size(win);
|
||||
Point const pos = _settings_visible
|
||||
? Point(0, avail.y1())
|
||||
? Point(system_right_xpos, avail.y1())
|
||||
: Point(-size.w(), avail.y1());
|
||||
|
||||
if (_settings.interactive_settings_available())
|
||||
@ -1618,23 +1799,25 @@ void Sculpt::Main::_handle_update_state()
|
||||
|
||||
Xml_node const update_state = _update_state_rom.xml();
|
||||
|
||||
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);
|
||||
bool const any_completed_download = _download_queue.any_completed_download();
|
||||
_download_queue.remove_completed_downloads();
|
||||
|
||||
_index_update_queue.apply_update_state(update_state);
|
||||
|
||||
bool const installation_complete =
|
||||
!update_state.attribute_value("progress", false);
|
||||
|
||||
bool const popup_watches_downloads =
|
||||
_popup_dialog.interested_in_download();
|
||||
|
||||
if (installation_complete) {
|
||||
|
||||
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);
|
||||
|| blueprint_any_rom_missing(blueprint)
|
||||
|| any_completed_download;
|
||||
if (new_depot_query_needed)
|
||||
trigger_depot_query();
|
||||
|
||||
@ -1643,6 +1826,8 @@ void Sculpt::Main::_handle_update_state()
|
||||
|
||||
_deploy.reattempt_after_installation();
|
||||
}
|
||||
|
||||
generate_dialog();
|
||||
}
|
||||
|
||||
|
||||
@ -1789,6 +1974,13 @@ void Sculpt::Main::_handle_runtime_state()
|
||||
_file_operation_queue.schedule_next_operations();
|
||||
_fs_tool_version.value++;
|
||||
reconfigure_runtime = true;
|
||||
regenerate_dialog = true;
|
||||
|
||||
/* try to proceed after the first step of an depot-index update */
|
||||
unsigned const orig_download_count = _index_update_queue.download_count;
|
||||
_index_update_queue.try_schedule_downloads();
|
||||
if (_index_update_queue.download_count != orig_download_count)
|
||||
_deploy.update_installation();
|
||||
|
||||
/*
|
||||
* The removal of an index file may have completed, re-query index
|
||||
@ -1886,6 +2078,7 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
|
||||
_panel_menu_view.gen_start_node(xml);
|
||||
_main_menu_view.gen_start_node(xml);
|
||||
_settings_menu_view.gen_start_node(xml);
|
||||
_system_menu_view.gen_start_node(xml);
|
||||
_network_menu_view.gen_start_node(xml);
|
||||
_popup_menu_view.gen_start_node(xml);
|
||||
_file_browser_menu_view.gen_start_node(xml);
|
||||
|
50
repos/gems/src/app/sculpt_manager/model/build_info.h
Normal file
50
repos/gems/src/app/sculpt_manager/model/build_info.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* \brief Interface to obtain version info about the used system image
|
||||
* \author Norman Feske
|
||||
* \date 2022-01-24
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _BUILD_INFO_H_
|
||||
#define _BUILD_INFO_H_
|
||||
|
||||
#include <types.h>
|
||||
|
||||
namespace Sculpt { struct Build_info; }
|
||||
|
||||
|
||||
struct Sculpt::Build_info
|
||||
{
|
||||
using Value = String<64>;
|
||||
using Version = String<64>;
|
||||
|
||||
Value genode_source, date, depot_user, board;
|
||||
|
||||
Version image_version() const
|
||||
{
|
||||
return Version(depot_user, "/sculpt-", board, "-", date);
|
||||
}
|
||||
|
||||
Version genode_version() const
|
||||
{
|
||||
return Version("Genode ", genode_source);
|
||||
}
|
||||
|
||||
static Build_info from_xml(Xml_node const &info)
|
||||
{
|
||||
return Build_info {
|
||||
.genode_source = info.attribute_value("genode_version", Value()),
|
||||
.date = info.attribute_value("date", Value()),
|
||||
.depot_user = info.attribute_value("depot_user", Value()),
|
||||
.board = info.attribute_value("board", Value())
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _BUILD_INFO_H_ */
|
75
repos/gems/src/app/sculpt_manager/model/depot_url.h
Normal file
75
repos/gems/src/app/sculpt_manager/model/depot_url.h
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* \brief Utility for parsing a depot URL into download location and user name
|
||||
* \author Norman Feske
|
||||
* \date 2023-03-20
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _MODEL__DEPOT_URL_H_
|
||||
#define _MODEL__DEPOT_URL_H_
|
||||
|
||||
#include <types.h>
|
||||
|
||||
namespace Sculpt { struct Depot_url; }
|
||||
|
||||
|
||||
struct Sculpt::Depot_url
|
||||
{
|
||||
using Url = String<128>;
|
||||
using User = Depot::Archive::User;
|
||||
|
||||
Url download; /* download location w/o user sub dir */
|
||||
User user; /* name of user sub dir */
|
||||
|
||||
template <size_t N>
|
||||
static Depot_url from_string(String<N> const &url)
|
||||
{
|
||||
Url download { };
|
||||
User user { };
|
||||
|
||||
auto for_each_slash_pos = [&] (auto const &fn)
|
||||
{
|
||||
for (size_t i = 0; i < url.length(); i++)
|
||||
if (url.string()[i] == '/')
|
||||
fn(i);
|
||||
};
|
||||
|
||||
unsigned num_slashes = 0;
|
||||
size_t protocol_len = 0;
|
||||
size_t last_slash_pos = 0;
|
||||
|
||||
using Protocol = String<16>; /* "http://" or "https://" */
|
||||
Protocol protocol { };
|
||||
|
||||
for_each_slash_pos([&] (size_t i) {
|
||||
num_slashes++;
|
||||
if (num_slashes == 2) {
|
||||
protocol_len = i + 1;
|
||||
protocol = { Cstring(url.string(), protocol_len) };
|
||||
}
|
||||
if (num_slashes > 2)
|
||||
last_slash_pos = i;
|
||||
});
|
||||
|
||||
if (protocol_len && last_slash_pos > protocol_len) {
|
||||
download = { Cstring(url.string(), last_slash_pos) };
|
||||
user = { Cstring(url.string() + last_slash_pos + 1) };
|
||||
}
|
||||
|
||||
bool const valid = (protocol == "http://" || protocol == "https://")
|
||||
&& (user.length() > 1);
|
||||
|
||||
return valid ? Depot_url { .download = download, .user = user }
|
||||
: Depot_url { };
|
||||
}
|
||||
|
||||
bool valid() const { return download.valid() && user.valid(); }
|
||||
};
|
||||
|
||||
#endif /* _MODEL__DEPOT_URL_H_ */
|
@ -153,6 +153,12 @@ struct Sculpt::Download_queue : Noncopyable
|
||||
destroy(_alloc, &download); });
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
_downloads.for_each([&] (Download &download) {
|
||||
destroy(_alloc, &download); });
|
||||
}
|
||||
|
||||
void gen_installation_entries(Xml_generator &xml) const
|
||||
{
|
||||
_downloads.for_each([&] (Download const &download) {
|
||||
|
162
repos/gems/src/app/sculpt_manager/model/index_update_queue.h
Normal file
162
repos/gems/src/app/sculpt_manager/model/index_update_queue.h
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* \brief Queue for tracking the update of depot index files
|
||||
* \author Norman Feske
|
||||
* \date 2023-01-27
|
||||
*
|
||||
* The update of a depot index takes two steps. First, the index must
|
||||
* be removed. Then, the index can be requested again via the depot-download
|
||||
* mechanism.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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__INDEX_UPDATE_QUEUE_H_
|
||||
#define _MODEL__INDEX_UPDATE_QUEUE_H_
|
||||
|
||||
#include <model/download_queue.h>
|
||||
#include <types.h>
|
||||
|
||||
namespace Sculpt { struct Index_update_queue; }
|
||||
|
||||
|
||||
struct Sculpt::Index_update_queue : Noncopyable
|
||||
{
|
||||
struct Update : Interface
|
||||
{
|
||||
Path const path;
|
||||
|
||||
Verify const verify;
|
||||
|
||||
enum class State { REMOVING, DOWNLOADING, DONE, FAILED } state;
|
||||
|
||||
Update(Path const &path, Verify verify)
|
||||
:
|
||||
path(path), verify(verify), state(State::REMOVING)
|
||||
{ }
|
||||
|
||||
bool active() const { return state == State::REMOVING
|
||||
|| state == State::DOWNLOADING; }
|
||||
};
|
||||
|
||||
Allocator &_alloc;
|
||||
File_operation_queue &_file_operation_queue;
|
||||
Download_queue &_download_queue;
|
||||
|
||||
Registry<Registered<Update> > _updates { };
|
||||
|
||||
/* used for detecting the start of new downloads */
|
||||
unsigned download_count = 0;
|
||||
|
||||
Index_update_queue(Allocator &alloc,
|
||||
File_operation_queue &file_operation_queue,
|
||||
Download_queue &download_queue)
|
||||
:
|
||||
_alloc(alloc),
|
||||
_file_operation_queue(file_operation_queue),
|
||||
_download_queue(download_queue)
|
||||
{ }
|
||||
|
||||
void add(Path const &path, Verify const verify)
|
||||
{
|
||||
if (!Depot::Archive::index(path) && !Depot::Archive::image_index(path)) {
|
||||
warning("attempt to add a non-index path '", path, "' to index-download queue");
|
||||
return;
|
||||
}
|
||||
|
||||
bool already_exists = false;
|
||||
_updates.for_each([&] (Update const &update) {
|
||||
if (update.path == path)
|
||||
already_exists = true; });
|
||||
|
||||
if (already_exists) {
|
||||
warning("index update triggered while update is already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
new (_alloc) Registered<Update>(_updates, path, verify);
|
||||
|
||||
_file_operation_queue.remove_file(Path("/rw/depot/", path));
|
||||
_file_operation_queue.remove_file(Path("/rw/public/", path, ".xz"));
|
||||
_file_operation_queue.remove_file(Path("/rw/public/", path, ".xz.sig"));
|
||||
|
||||
if (!_file_operation_queue.any_operation_in_progress())
|
||||
_file_operation_queue.schedule_next_operations();
|
||||
}
|
||||
|
||||
void try_schedule_downloads()
|
||||
{
|
||||
/*
|
||||
* Once the 'File_operation_queue' is empty, we know that no removal of
|
||||
* any index file is still in progres.
|
||||
*/
|
||||
if (!_file_operation_queue.empty())
|
||||
return;
|
||||
|
||||
_updates.for_each([&] (Update &update) {
|
||||
if (update.state == Update::State::REMOVING) {
|
||||
update.state = Update::State::DOWNLOADING;
|
||||
_download_queue.add(update.path, update.verify);
|
||||
download_count++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool any_download_scheduled() const
|
||||
{
|
||||
bool result = false;
|
||||
_updates.for_each([&] (Update const &update) {
|
||||
if (update.state == Update::State::DOWNLOADING)
|
||||
result = true; });
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void with_update(Path const &path, FN const &fn) const
|
||||
{
|
||||
_updates.for_each([&] (Update const &update) {
|
||||
if (update.path == path)
|
||||
fn(update); });
|
||||
}
|
||||
|
||||
void apply_update_state(Xml_node state)
|
||||
{
|
||||
state.for_each_sub_node([&] (Xml_node const &elem) {
|
||||
|
||||
Path const path = elem.attribute_value("path", Path());
|
||||
_updates.for_each([&] (Update &update) {
|
||||
|
||||
if (update.path != path)
|
||||
return;
|
||||
|
||||
using State = String<16>;
|
||||
State const state = elem.attribute_value("state", State());
|
||||
|
||||
if (state == "done") update.state = Update::State::DONE;
|
||||
if (state == "failed") update.state = Update::State::FAILED;
|
||||
if (state == "unavailable") update.state = Update::State::FAILED;
|
||||
if (state == "corrupted") update.state = Update::State::FAILED;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void remove_inactive_updates()
|
||||
{
|
||||
_updates.for_each([&] (Update &update) {
|
||||
if (!update.active())
|
||||
destroy(_alloc, &update); });
|
||||
}
|
||||
|
||||
void remove_completed_updates()
|
||||
{
|
||||
_updates.for_each([&] (Update &update) {
|
||||
if (update.state == Update::State::DONE)
|
||||
destroy(_alloc, &update); });
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _MODEL__INDEX_UPDATE_QUEUE_H_ */
|
330
repos/gems/src/app/sculpt_manager/view/depot_users_dialog.h
Normal file
330
repos/gems/src/app/sculpt_manager/view/depot_users_dialog.h
Normal file
@ -0,0 +1,330 @@
|
||||
/*
|
||||
* \brief Dialog for selecting a depot user
|
||||
* \author Norman Feske
|
||||
* \date 2023-03-17
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _VIEW__DEPOT_USERS_DIALOG_H_
|
||||
#define _VIEW__DEPOT_USERS_DIALOG_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
#include <view/text_entry_field.h>
|
||||
#include <model/depot_url.h>
|
||||
|
||||
namespace Sculpt { struct Depot_users_dialog; }
|
||||
|
||||
|
||||
struct Sculpt::Depot_users_dialog
|
||||
{
|
||||
public:
|
||||
|
||||
using Depot_users = Attached_rom_dataspace;
|
||||
using User = Depot::Archive::User;
|
||||
using Url = Depot_url::Url;
|
||||
using Hover_result = Hoverable_item::Hover_result;
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void add_depot_url(Depot_url const &depot_url) = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
using Url_edit_field = Text_entry_field<50>;
|
||||
|
||||
Depot_users const &_depot_users;
|
||||
|
||||
Action &_action;
|
||||
|
||||
User _selected;
|
||||
|
||||
bool _unfolded = false;
|
||||
|
||||
Hoverable_item _user { };
|
||||
Hoverable_item _button { };
|
||||
|
||||
Url const _orig_edit_url { "https://" };
|
||||
|
||||
Url_edit_field _url_edit_field { _orig_edit_url };
|
||||
|
||||
Url _url(Xml_node const &user) const
|
||||
{
|
||||
if (!user.has_sub_node("url"))
|
||||
return { };
|
||||
|
||||
Url const url = user.sub_node("url").decoded_content<Url>();
|
||||
|
||||
/*
|
||||
* Ensure that the URL does not contain any '"' character because
|
||||
* it will be taken as an XML attribute value.
|
||||
*/
|
||||
for (char const *s = url.string(); *s; s++)
|
||||
if (*s == '"')
|
||||
return { };
|
||||
|
||||
User const name = user.attribute_value("name", User());
|
||||
|
||||
return Url(url, "/", name);
|
||||
}
|
||||
|
||||
static void _gen_vspacer(Xml_generator &xml, char const *name)
|
||||
{
|
||||
gen_named_node(xml, "label", name, [&] () {
|
||||
xml.attribute("text", " ");
|
||||
xml.attribute("font", "annotation/regular");
|
||||
});
|
||||
}
|
||||
|
||||
static inline char const *_add_id() { return "/add"; }
|
||||
|
||||
template <typename GEN_LABEL_FN, typename RIGHT_FN>
|
||||
void _gen_item(Xml_generator &xml, User const &name,
|
||||
GEN_LABEL_FN const &gen_label_fn,
|
||||
RIGHT_FN const &right_fn) const
|
||||
{
|
||||
bool const selected = (name == _selected);
|
||||
|
||||
gen_named_node(xml, "hbox", name, [&] () {
|
||||
gen_named_node(xml, "float", "left", [&] () {
|
||||
xml.attribute("west", "yes");
|
||||
xml.node("hbox", [&] () {
|
||||
gen_named_node(xml, "float", "button", [&] () {
|
||||
gen_named_node(xml, "button", "button", [&] () {
|
||||
|
||||
_user.gen_hovered_attr(xml, name);
|
||||
|
||||
if (selected)
|
||||
xml.attribute("selected", "yes");
|
||||
|
||||
xml.attribute("style", "radio");
|
||||
xml.node("hbox", [&] () { });
|
||||
});
|
||||
});
|
||||
gen_named_node(xml, "label", "name", [&] () {
|
||||
gen_label_fn(); });
|
||||
});
|
||||
});
|
||||
gen_named_node(xml, "hbox", "right", [&] () {
|
||||
right_fn(); });
|
||||
});
|
||||
}
|
||||
|
||||
void _gen_entry(Xml_generator &xml, Xml_node const user, bool last) const
|
||||
{
|
||||
User const name = user.attribute_value("name", User());
|
||||
bool const selected = (name == _selected);
|
||||
Url const url = _url(user);
|
||||
Url const label = Depot_url::from_string(url).valid() ? url : Url(name);
|
||||
|
||||
if (!selected && !_unfolded)
|
||||
return;
|
||||
|
||||
_gen_item(xml, name,
|
||||
[&] /* label */ { xml.attribute("text", Path(" ", label)); },
|
||||
[&] /* right */ { }
|
||||
);
|
||||
|
||||
if (_unfolded && !last)
|
||||
_gen_vspacer(xml, String<64>("below ", name).string());
|
||||
}
|
||||
|
||||
Depot_url _depot_url(Xml_node const &depot_users) const
|
||||
{
|
||||
Depot_url const result =
|
||||
Depot_url::from_string(Depot_url::Url { _url_edit_field });
|
||||
|
||||
/* check for duplicated user name */
|
||||
bool unique = true;
|
||||
depot_users.for_each_sub_node("user", [&] (Xml_node user) {
|
||||
User const name = user.attribute_value("name", User());
|
||||
if (name == result.user)
|
||||
unique = false;
|
||||
});
|
||||
|
||||
return unique ? result : Depot_url { };
|
||||
}
|
||||
|
||||
void _gen_add_entry(Xml_generator &xml, Xml_node const &depot_users) const
|
||||
{
|
||||
_gen_item(xml, _add_id(),
|
||||
|
||||
[&] /* label */ {
|
||||
xml.attribute("text", Depot_url::Url(" ", _url_edit_field));
|
||||
xml.attribute("min_ex", 30);
|
||||
xml.node("cursor", [&] () {
|
||||
xml.attribute("at", _url_edit_field.cursor_pos + 1); });
|
||||
},
|
||||
|
||||
[&] /* right */ {
|
||||
gen_named_node(xml, "float", "actions", [&] {
|
||||
xml.attribute("east", "yes");
|
||||
bool const editing = (_selected == _add_id());
|
||||
if (editing) {
|
||||
bool const url_valid = _depot_url(depot_users).valid();
|
||||
gen_named_node(xml, "button", "add", [&] {
|
||||
if (!url_valid)
|
||||
xml.attribute("style", "unimportant");
|
||||
xml.node("label", [&] {
|
||||
if (!url_valid)
|
||||
xml.attribute("style", "unimportant");
|
||||
xml.attribute("text", "Add"); });
|
||||
});
|
||||
} else {
|
||||
gen_named_node(xml, "button", "edit", [&] {
|
||||
xml.node("label", [&] {
|
||||
xml.attribute("text", "Edit"); }); });
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void _gen_selection(Xml_generator &xml) const
|
||||
{
|
||||
Xml_node const depot_users = _depot_users.xml();
|
||||
|
||||
size_t remain_count = depot_users.num_sub_nodes();
|
||||
|
||||
remain_count++; /* account for '_gen_add_entry' */
|
||||
|
||||
bool known_pubkey = false;
|
||||
|
||||
gen_named_node(xml, "frame", "user_selection", [&] () {
|
||||
xml.node("vbox", [&] () {
|
||||
depot_users.for_each_sub_node("user", [&] (Xml_node user) {
|
||||
|
||||
if (_selected == user.attribute_value("name", User()))
|
||||
known_pubkey = user.attribute_value("known_pubkey", false);
|
||||
|
||||
bool const last = (--remain_count == 0);
|
||||
_gen_entry(xml, user, last); });
|
||||
|
||||
if (_unfolded)
|
||||
_gen_add_entry(xml, depot_users);
|
||||
});
|
||||
});
|
||||
|
||||
if (!_unfolded && !known_pubkey) {
|
||||
gen_named_node(xml, "button", "pubkey warning", [&] {
|
||||
xml.attribute("style", "invisible");
|
||||
xml.node("label", [&] {
|
||||
xml.attribute("font", "annotation/regular");
|
||||
xml.attribute("text", "missing public key for verification"); });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Depot_users_dialog(Depot_users const &depot_users,
|
||||
User const &default_user,
|
||||
Action &action)
|
||||
:
|
||||
_depot_users(depot_users), _action(action), _selected(default_user)
|
||||
{ }
|
||||
|
||||
User selected() const
|
||||
{
|
||||
return (_selected == _add_id()) ? User() : _selected;
|
||||
}
|
||||
|
||||
void generate(Xml_generator &xml) const { _gen_selection(xml); }
|
||||
|
||||
bool unfolded() const { return _unfolded; }
|
||||
|
||||
struct User_properties
|
||||
{
|
||||
bool exists;
|
||||
bool download_url;
|
||||
bool public_key;
|
||||
};
|
||||
|
||||
User_properties selected_user_properties() const
|
||||
{
|
||||
User_properties result { };
|
||||
_depot_users.xml().for_each_sub_node([&] (Xml_node const &user) {
|
||||
if (_selected == user.attribute_value("name", User())) {
|
||||
result = {
|
||||
.exists = true,
|
||||
.download_url = Depot_url::from_string(_url(user)).valid(),
|
||||
.public_key = user.attribute_value("known_pubkey", false)
|
||||
};
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename SELECT_FN>
|
||||
void click(SELECT_FN const &select_fn)
|
||||
{
|
||||
/* unfold depot users */
|
||||
if (!_unfolded) {
|
||||
_unfolded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/* handle click on unfolded depot-user selection */
|
||||
|
||||
auto select_depot_user = [&] (User const &user)
|
||||
{
|
||||
_selected = user;
|
||||
select_fn(user);
|
||||
_unfolded = false;
|
||||
_url_edit_field = _orig_edit_url;
|
||||
};
|
||||
|
||||
if (_user._hovered.length() <= 1)
|
||||
return;
|
||||
|
||||
if (_user.hovered(_add_id())) {
|
||||
if (_button.hovered("add")) {
|
||||
Depot_url const depot_url = _depot_url(_depot_users.xml());
|
||||
if (depot_url.valid()) {
|
||||
_action.add_depot_url(depot_url);
|
||||
select_depot_user(depot_url.user);
|
||||
}
|
||||
} else {
|
||||
_selected = _add_id();
|
||||
}
|
||||
} else {
|
||||
select_depot_user(_user._hovered);
|
||||
}
|
||||
}
|
||||
|
||||
Hover_result hover(Xml_node const &hover)
|
||||
{
|
||||
return Dialog::any_hover_changed(
|
||||
_user .match(hover, "frame", "vbox", "hbox", "name"),
|
||||
_button.match(hover, "frame", "vbox", "hbox", "hbox", "float", "button", "name")
|
||||
);
|
||||
}
|
||||
|
||||
void reset_hover() { _user._hovered = { }; }
|
||||
|
||||
bool hovered() const { return _user._hovered.valid(); }
|
||||
|
||||
bool keyboard_needed() const { return _selected == _add_id(); }
|
||||
|
||||
void handle_key(Codepoint c)
|
||||
{
|
||||
if (_selected != _add_id())
|
||||
return;
|
||||
|
||||
/* prevent input of printable yet risky characters as URL */
|
||||
if (c.value == ' ' || c.value == '"')
|
||||
return;
|
||||
|
||||
_url_edit_field.apply(c);
|
||||
}
|
||||
|
||||
bool one_selected() const { return !_unfolded && _selected.length() > 1; }
|
||||
};
|
||||
|
||||
#endif /* _VIEW__DEPOT_USERS_DIALOG_H_ */
|
64
repos/gems/src/app/sculpt_manager/view/layout_helper.h
Normal file
64
repos/gems/src/app/sculpt_manager/view/layout_helper.h
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* \brief GUI layout helper
|
||||
* \author Norman Feske
|
||||
* \date 2022-05-20
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 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__LAYOUT_HELPER_H_
|
||||
#define _VIEW__LAYOUT_HELPER_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
|
||||
|
||||
/**
|
||||
* Arrange content in two columns, each with a minimum width of 'min_ex'
|
||||
*/
|
||||
template <typename LEFT_FN, typename RIGHT_FN>
|
||||
static void gen_left_right(Genode::Xml_generator &xml, unsigned min_ex,
|
||||
LEFT_FN const &left_fn, RIGHT_FN const &right_fn)
|
||||
{
|
||||
using namespace Sculpt;
|
||||
|
||||
auto gen_hspacer = [&] {
|
||||
gen_named_node(xml, "label", "hspacer", [&] {
|
||||
xml.attribute("min_ex", min_ex); }); };
|
||||
|
||||
xml.node("hbox", [&] {
|
||||
gen_named_node(xml, "vbox", "left", [&] {
|
||||
gen_hspacer();
|
||||
left_fn();
|
||||
});
|
||||
gen_named_node(xml, "vbox", "right", [&] {
|
||||
gen_hspacer();
|
||||
right_fn();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inflate vertical spacing using an invisble button
|
||||
*/
|
||||
template <typename ID>
|
||||
static void gen_item_vspace(Genode::Xml_generator &xml, ID const &id)
|
||||
{
|
||||
using namespace Sculpt;
|
||||
|
||||
gen_named_node(xml, "button", id, [&] () {
|
||||
xml.attribute("style", "invisible");
|
||||
xml.node("label", [&] () {
|
||||
xml.attribute("text", " ");
|
||||
xml.attribute("font", "title/regular");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#endif /* _VIEW__LAYOUT_HELPER_H_ */
|
@ -25,6 +25,14 @@ void Panel_dialog::generate(Xml_generator &xml) const
|
||||
gen_named_node(xml, "float", "left", [&] () {
|
||||
xml.attribute("west", true);
|
||||
xml.node("hbox", [&] () {
|
||||
xml.node("button", [&] () {
|
||||
_item.gen_button_attr(xml, "system");
|
||||
if (_state.system_visible())
|
||||
xml.attribute("selected", true);
|
||||
xml.node("label", [&] () {
|
||||
xml.attribute("text", "System");
|
||||
});
|
||||
});
|
||||
xml.node("button", [&] () {
|
||||
_item.gen_button_attr(xml, "settings");
|
||||
if (_state.settings_visible())
|
||||
|
@ -33,6 +33,7 @@ struct Sculpt::Panel_dialog : Dialog
|
||||
{
|
||||
virtual Tab selected_tab() const = 0;
|
||||
virtual bool log_visible() const = 0;
|
||||
virtual bool system_visible() const = 0;
|
||||
virtual bool settings_visible() const = 0;
|
||||
virtual bool network_visible() const = 0;
|
||||
virtual bool inspect_tab_visible() const = 0;
|
||||
@ -45,6 +46,7 @@ struct Sculpt::Panel_dialog : Dialog
|
||||
{
|
||||
virtual void select_tab(Tab) = 0;
|
||||
virtual void toggle_log_visibility() = 0;
|
||||
virtual void toggle_system_visibility() = 0;
|
||||
virtual void toggle_settings_visibility() = 0;
|
||||
virtual void toggle_network_visibility() = 0;
|
||||
};
|
||||
@ -66,6 +68,7 @@ struct Sculpt::Panel_dialog : Dialog
|
||||
if (_item.hovered("files")) action.select_tab(Tab::FILES);
|
||||
if (_item.hovered("inspect")) action.select_tab(Tab::INSPECT);
|
||||
if (_item.hovered("log")) action.toggle_log_visibility();
|
||||
if (_item.hovered("system")) action.toggle_system_visibility();
|
||||
if (_item.hovered("settings")) action.toggle_settings_visibility();
|
||||
if (_item.hovered("network")) action.toggle_network_visibility();
|
||||
}
|
||||
|
@ -135,4 +135,4 @@ struct Sculpt::Settings_dialog : Noncopyable, Dialog
|
||||
Settings_dialog(Settings const &settings) : _settings(settings) { }
|
||||
};
|
||||
|
||||
#endif /* _VIEW__RAM_FS_DIALOG_H_ */
|
||||
#endif /* _VIEW__SETTINGS_DIALOG_H_ */
|
||||
|
162
repos/gems/src/app/sculpt_manager/view/software_presets_dialog.h
Normal file
162
repos/gems/src/app/sculpt_manager/view/software_presets_dialog.h
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* \brief Dialog for the deploy presets
|
||||
* \author Norman Feske
|
||||
* \date 2023-01-11
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _VIEW__SOFTWARE_PRESETS_DIALOG_H_
|
||||
#define _VIEW__SOFTWARE_PRESETS_DIALOG_H_
|
||||
|
||||
#include <model/presets.h>
|
||||
#include <view/dialog.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace Sculpt { struct Software_presets_dialog; }
|
||||
|
||||
|
||||
struct Sculpt::Software_presets_dialog
|
||||
{
|
||||
Presets const &_presets;
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void load_deploy_preset(Presets::Info::Name const &) = 0;
|
||||
};
|
||||
|
||||
Action &_action;
|
||||
|
||||
using Hover_result = Hoverable_item::Hover_result;
|
||||
|
||||
Presets::Info::Name _selected { };
|
||||
|
||||
Hoverable_item _item { };
|
||||
Activatable_item _operation { };
|
||||
|
||||
Software_presets_dialog(Presets const &presets, Action &action)
|
||||
:
|
||||
_presets(presets), _action(action)
|
||||
{ }
|
||||
|
||||
using Name = Presets::Info::Name;
|
||||
|
||||
void _gen_horizontal_spacer(Xml_generator &xml) const
|
||||
{
|
||||
gen_named_node(xml, "label", "spacer", [&] {
|
||||
xml.attribute("min_ex", 35); });
|
||||
}
|
||||
|
||||
void _gen_preset(Xml_generator &xml, Presets::Info const &preset) const
|
||||
{
|
||||
gen_named_node(xml, "vbox", preset.name, [&] () {
|
||||
|
||||
gen_named_node(xml, "hbox", preset.name, [&] () {
|
||||
|
||||
gen_named_node(xml, "float", "left", [&] () {
|
||||
xml.attribute("west", "yes");
|
||||
|
||||
xml.node("hbox", [&] () {
|
||||
gen_named_node(xml, "float", "radio", [&] () {
|
||||
gen_named_node(xml, "button", "button", [&] () {
|
||||
|
||||
_item.gen_hovered_attr(xml, preset.name);
|
||||
|
||||
if (_selected == preset.name)
|
||||
xml.attribute("selected", "yes");
|
||||
|
||||
xml.attribute("style", "radio");
|
||||
|
||||
xml.node("hbox", [&] () { });
|
||||
});
|
||||
});
|
||||
|
||||
gen_named_node(xml, "label", "name", [&] () {
|
||||
xml.attribute("text", Name(" ", Pretty(preset.name))); });
|
||||
|
||||
gen_item_vspace(xml, "vspace");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (_selected != preset.name)
|
||||
return;
|
||||
|
||||
auto vspacer = [&] (auto name)
|
||||
{
|
||||
gen_named_node(xml, "label", name, [&] () {
|
||||
xml.attribute("text", " "); });
|
||||
};
|
||||
|
||||
vspacer("spacer1");
|
||||
|
||||
gen_named_node(xml, "float", "info", [&] () {
|
||||
gen_named_node(xml, "label", "text", [&] () {
|
||||
xml.attribute("text", preset.text); }); });
|
||||
|
||||
vspacer("spacer2");
|
||||
|
||||
gen_named_node(xml, "float", "operations", [&] () {
|
||||
gen_named_node(xml, "button", "load", [&] () {
|
||||
_operation.gen_button_attr(xml, "load");
|
||||
gen_named_node(xml, "label", "text", [&] () {
|
||||
xml.attribute("text", " Load "); });
|
||||
});
|
||||
});
|
||||
|
||||
vspacer("spacer3");
|
||||
});
|
||||
}
|
||||
|
||||
void generate(Xml_generator &xml) const
|
||||
{
|
||||
if (_presets.available())
|
||||
gen_named_node(xml, "float", "presets", [&] {
|
||||
xml.node("frame", [&] {
|
||||
xml.node("vbox", [&] {
|
||||
_gen_horizontal_spacer(xml);
|
||||
_presets.for_each([&] (Presets::Info const &info) {
|
||||
_gen_preset(xml, info); }); }); }); });
|
||||
}
|
||||
|
||||
Hover_result hover(Xml_node hover)
|
||||
{
|
||||
return Dialog::any_hover_changed(
|
||||
_item.match (hover, "float", "frame", "vbox", "vbox", "hbox", "name"),
|
||||
_operation.match(hover, "float", "frame", "vbox", "vbox", "float", "button", "name")
|
||||
);
|
||||
}
|
||||
|
||||
bool hovered() const { return _item._hovered.valid(); }
|
||||
|
||||
void click()
|
||||
{
|
||||
if (_item._hovered.length() > 1)
|
||||
_selected = _item._hovered;
|
||||
|
||||
if (_operation.hovered("load"))
|
||||
_operation.propose_activation_on_click();
|
||||
}
|
||||
|
||||
void clack()
|
||||
{
|
||||
if (_selected.length() <= 1)
|
||||
return;
|
||||
|
||||
_operation.confirm_activation_on_clack();
|
||||
|
||||
if (_operation.activated("load")) {
|
||||
_action.load_deploy_preset(_selected);
|
||||
_selected = { };
|
||||
}
|
||||
|
||||
_operation.reset();
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__SOFTWARE_PRESETS_DIALOG_H_ */
|
361
repos/gems/src/app/sculpt_manager/view/software_update_dialog.h
Normal file
361
repos/gems/src/app/sculpt_manager/view/software_update_dialog.h
Normal file
@ -0,0 +1,361 @@
|
||||
/*
|
||||
* \brief Dialog for software update
|
||||
* \author Norman Feske
|
||||
* \date 2023-01-23
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _VIEW__SOFTWARE_UPDATE_DIALOG_H_
|
||||
#define _VIEW__SOFTWARE_UPDATE_DIALOG_H_
|
||||
|
||||
#include <model/nic_state.h>
|
||||
#include <model/build_info.h>
|
||||
#include <model/download_queue.h>
|
||||
#include <model/index_update_queue.h>
|
||||
#include <view/depot_users_dialog.h>
|
||||
|
||||
namespace Sculpt { struct Software_update_dialog; }
|
||||
|
||||
|
||||
struct Sculpt::Software_update_dialog
|
||||
{
|
||||
using Depot_users = Depot_users_dialog::Depot_users;
|
||||
using User = Depot_users_dialog::User;
|
||||
using Image_index = Attached_rom_dataspace;
|
||||
using Url = Depot_users_dialog::Url;
|
||||
using Version = String<16>;
|
||||
using User_properties = Depot_users_dialog::User_properties;
|
||||
|
||||
Build_info const _build_info;
|
||||
|
||||
Nic_state const &_nic_state;
|
||||
Download_queue const &_download_queue;
|
||||
Index_update_queue const &_index_update_queue;
|
||||
File_operation_queue const &_file_operation_queue;
|
||||
Image_index const &_image_index;
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void query_image_index (User const &) = 0;
|
||||
virtual void trigger_image_download(Path const &, Verify) = 0;
|
||||
virtual void update_image_index (User const &, Verify) = 0;
|
||||
virtual void install_boot_image (Path const &) = 0;
|
||||
};
|
||||
|
||||
Action &_action;
|
||||
|
||||
Depot_users_dialog _users;
|
||||
|
||||
Path _last_installed { };
|
||||
Path _last_selected { };
|
||||
|
||||
Path _index_path() const { return Path(_users.selected(), "/image/index"); }
|
||||
|
||||
bool _index_update_in_progress() const
|
||||
{
|
||||
using Update = Index_update_queue::Update;
|
||||
|
||||
bool result = false;
|
||||
_index_update_queue.with_update(_index_path(), [&] (Update const &update) {
|
||||
if (update.active())
|
||||
result = true; });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Path _image_path(Version const &version) const
|
||||
{
|
||||
return Path(_users.selected(), "/image/sculpt-", _build_info.board, "-", version);
|
||||
}
|
||||
|
||||
bool _installing() const
|
||||
{
|
||||
return _file_operation_queue.copying_to_path("/rw/boot");
|
||||
};
|
||||
|
||||
Hoverable_item _check { };
|
||||
Hoverable_item _version { };
|
||||
Hoverable_item _operation { };
|
||||
|
||||
using Hover_result = Hoverable_item::Hover_result;
|
||||
|
||||
Software_update_dialog(Build_info const &build_info,
|
||||
Nic_state const &nic_state,
|
||||
Download_queue const &download_queue,
|
||||
Index_update_queue const &index_update_queue,
|
||||
File_operation_queue const &file_operation_queue,
|
||||
Depot_users const &depot_users,
|
||||
Image_index const &image_index,
|
||||
Depot_users_dialog::Action &depot_users_action,
|
||||
Action &action)
|
||||
:
|
||||
_build_info(build_info), _nic_state(nic_state),
|
||||
_download_queue(download_queue),
|
||||
_index_update_queue(index_update_queue),
|
||||
_file_operation_queue(file_operation_queue),
|
||||
_image_index(image_index),
|
||||
_action(action),
|
||||
_users(depot_users, _build_info.depot_user, depot_users_action)
|
||||
{ }
|
||||
|
||||
static void _gen_vspacer(Xml_generator &xml, char const *name)
|
||||
{
|
||||
gen_named_node(xml, "label", name, [&] () {
|
||||
xml.attribute("text", " ");
|
||||
xml.attribute("font", "annotation/regular");
|
||||
});
|
||||
}
|
||||
|
||||
void _gen_image_main(Xml_generator &xml, Xml_node const &image) const
|
||||
{
|
||||
Version const version = image.attribute_value("version", Version());
|
||||
bool const present = image.attribute_value("present", false);
|
||||
Path const path = _image_path(version);
|
||||
|
||||
struct Download_state
|
||||
{
|
||||
bool in_progress;
|
||||
bool failed;
|
||||
unsigned percent;
|
||||
};
|
||||
|
||||
using Download = Download_queue::Download;
|
||||
|
||||
auto state_from_download_queue = [&]
|
||||
{
|
||||
Download_state result { };
|
||||
_download_queue.with_download(path, [&] (Download const &download) {
|
||||
|
||||
if (download.state == Download::State::DOWNLOADING)
|
||||
result.in_progress = true;
|
||||
|
||||
if (download.state == Download::State::FAILED)
|
||||
result.failed = true;
|
||||
|
||||
result.percent = download.percent;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
Download_state const download_state = state_from_download_queue();
|
||||
|
||||
gen_named_node(xml, "float", "label", [&] {
|
||||
xml.attribute("west", "yes");
|
||||
gen_named_node(xml, "label", "label", [&] {
|
||||
xml.attribute("text", String<50>(" ", version));
|
||||
xml.attribute("min_ex", "15");
|
||||
});
|
||||
});
|
||||
|
||||
auto gen_status = [&] (auto message)
|
||||
{
|
||||
gen_named_node(xml, "float", "status", [&] {
|
||||
xml.node("label", [&] {
|
||||
xml.attribute("font", "annotation/regular");
|
||||
xml.attribute("text", message); }); });
|
||||
};
|
||||
|
||||
if (image.has_sub_node("info")) {
|
||||
if (_last_selected == path)
|
||||
gen_status("Changes");
|
||||
else
|
||||
gen_status("...");
|
||||
}
|
||||
|
||||
if (download_state.in_progress && download_state.percent)
|
||||
gen_status(String<16>(download_state.percent, "%"));
|
||||
|
||||
if (download_state.failed)
|
||||
gen_status("unavailable");
|
||||
|
||||
if (_last_installed == path) {
|
||||
if (_installing())
|
||||
gen_status("installing...");
|
||||
else
|
||||
gen_status("reboot to activate");
|
||||
}
|
||||
|
||||
gen_named_node(xml, "float", "buttons", [&] {
|
||||
xml.attribute("east", "yes");
|
||||
xml.node("hbox", [&] {
|
||||
|
||||
auto gen_button = [&] (auto id, bool selected, auto text)
|
||||
{
|
||||
gen_named_node(xml, "button", id, [&] {
|
||||
|
||||
if (version == _version._hovered)
|
||||
_operation.gen_hovered_attr(xml, id);
|
||||
|
||||
if (selected) {
|
||||
xml.attribute("selected", "yes");
|
||||
xml.attribute("style", "unimportant");
|
||||
}
|
||||
|
||||
xml.node("label", [&] {
|
||||
xml.attribute("text", text); });
|
||||
});
|
||||
};
|
||||
|
||||
if (present)
|
||||
gen_button("install", _installing(), " Install ");
|
||||
|
||||
if (!present)
|
||||
gen_button("download", download_state.in_progress, " Download ");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _gen_image_info(Xml_generator &xml, Xml_node const &image) const
|
||||
{
|
||||
gen_named_node(xml, "vbox", "main", [&] {
|
||||
|
||||
unsigned line = 0;
|
||||
|
||||
image.for_each_sub_node("info", [&] (Xml_node const &info) {
|
||||
|
||||
/* limit changelog to a sensible maximum of lines */
|
||||
if (++line > 8)
|
||||
return;
|
||||
|
||||
using Text = String<80>;
|
||||
Text const text = info.attribute_value("text", Text());
|
||||
|
||||
gen_named_node(xml, "float", String<16>(line), [&] {
|
||||
xml.attribute("west", "yes");
|
||||
xml.node("label", [&] {
|
||||
xml.attribute("text", text);
|
||||
xml.attribute("font", "annotation/regular");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _gen_image_entry(Xml_generator &xml, Xml_node const &image) const
|
||||
{
|
||||
Version const version = image.attribute_value("version", Version());
|
||||
Path const path = _image_path(version);
|
||||
|
||||
gen_named_node(xml, "frame", version, [&] {
|
||||
xml.attribute("style", "important");
|
||||
|
||||
xml.node("vbox", [&] {
|
||||
|
||||
gen_named_node(xml, "float", "main", [&] {
|
||||
xml.attribute("east", "yes");
|
||||
xml.attribute("west", "yes");
|
||||
_gen_image_main(xml, image);
|
||||
});
|
||||
|
||||
if (path == _last_selected && image.has_sub_node("info")) {
|
||||
_gen_vspacer(xml, "above");
|
||||
gen_named_node(xml, "float", "info", [&] {
|
||||
_gen_image_info(xml, image); });
|
||||
_gen_vspacer(xml, "below");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _gen_image_list(Xml_generator &xml) const
|
||||
{
|
||||
Xml_node const index = _image_index.xml();
|
||||
|
||||
index.for_each_sub_node("user", [&] (Xml_node const &user) {
|
||||
if (user.attribute_value("name", User()) == _users.selected())
|
||||
user.for_each_sub_node("image", [&] (Xml_node const &image) {
|
||||
_gen_image_entry(xml, image); }); });
|
||||
}
|
||||
|
||||
void _gen_update_dialog(Xml_generator &xml) const
|
||||
{
|
||||
gen_named_node(xml, "frame", "update_dialog", [&] {
|
||||
xml.node("vbox", [&] {
|
||||
_users.generate(xml);
|
||||
|
||||
User_properties const properties = _users.selected_user_properties();
|
||||
|
||||
bool const offer_index_update = _users.one_selected()
|
||||
&& _nic_state.ready()
|
||||
&& properties.download_url;
|
||||
if (offer_index_update) {
|
||||
_gen_vspacer(xml, "above check");
|
||||
gen_named_node(xml, "float", "check", [&] {
|
||||
gen_named_node(xml, "button", "check", [&] {
|
||||
_check.gen_hovered_attr(xml, "check");
|
||||
if (_index_update_in_progress()) {
|
||||
xml.attribute("selected", "yes");
|
||||
xml.attribute("style", "unimportant");
|
||||
}
|
||||
xml.node("label", [&] {
|
||||
auto const text = properties.public_key
|
||||
? " Check for Updates "
|
||||
: " Check for unverified Updates ";
|
||||
xml.attribute("text", text); });
|
||||
});
|
||||
});
|
||||
_gen_vspacer(xml, "below check");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
_gen_image_list(xml);
|
||||
}
|
||||
|
||||
void generate(Xml_generator &xml) const
|
||||
{
|
||||
gen_named_node(xml, "vbox", "update", [&] {
|
||||
_gen_update_dialog(xml); });
|
||||
}
|
||||
|
||||
Hover_result hover(Xml_node const &hover)
|
||||
{
|
||||
_users.reset_hover();
|
||||
|
||||
return Dialog::any_hover_changed(
|
||||
match_sub_dialog(hover, _users, "vbox", "frame", "vbox"),
|
||||
_check .match(hover, "vbox", "frame", "vbox", "float", "button", "name"),
|
||||
_version .match(hover, "vbox", "frame", "name"),
|
||||
_operation.match(hover, "vbox", "frame", "vbox", "float", "float", "hbox", "button", "name")
|
||||
);
|
||||
}
|
||||
|
||||
bool hovered() const { return _users.hovered(); }
|
||||
|
||||
void click()
|
||||
{
|
||||
Verify const verify { _users.selected_user_properties().public_key };
|
||||
|
||||
if (_users.hovered())
|
||||
_users.click([&] (User const &selected_user) {
|
||||
_action.query_image_index(selected_user); });
|
||||
|
||||
if (_check.hovered("check") && !_index_update_in_progress())
|
||||
_action.update_image_index(_users.selected(), verify);
|
||||
|
||||
if (_operation.hovered("download"))
|
||||
_action.trigger_image_download(_image_path(_version._hovered), verify);
|
||||
|
||||
if (_version._hovered.length() > 1)
|
||||
_last_selected = _image_path(_version._hovered);
|
||||
|
||||
if (_operation.hovered("install") && !_installing()) {
|
||||
_last_installed = _image_path(_version._hovered);
|
||||
_action.install_boot_image(_last_installed);
|
||||
}
|
||||
}
|
||||
|
||||
void clack() { }
|
||||
|
||||
bool keyboard_needed() const { return _users.keyboard_needed(); }
|
||||
|
||||
void handle_key(Codepoint c) { _users.handle_key(c); }
|
||||
};
|
||||
|
||||
#endif /* _VIEW__SOFTWARE_UPDATE_DIALOG_H_ */
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* \brief Dialog for showing the system version
|
||||
* \author Norman Feske
|
||||
* \date 2023-01-23
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _VIEW__SOFTWARE_VERSION_DIALOG_H_
|
||||
#define _VIEW__SOFTWARE_VERSION_DIALOG_H_
|
||||
|
||||
#include <model/build_info.h>
|
||||
#include <view/dialog.h>
|
||||
|
||||
namespace Sculpt { struct Software_version_dialog; }
|
||||
|
||||
|
||||
struct Sculpt::Software_version_dialog
|
||||
{
|
||||
Build_info const _build_info;
|
||||
|
||||
Software_version_dialog(Build_info const &info) : _build_info(info) { }
|
||||
|
||||
void generate(Xml_generator &xml) const
|
||||
{
|
||||
using Version = Build_info::Version;
|
||||
auto padded = [] (Version const &v) { return Version(" ", v, " "); };
|
||||
|
||||
gen_named_node(xml, "frame", "version", [&] {
|
||||
xml.node("vbox", [&] {
|
||||
gen_named_node(xml, "label", "image", [&] {
|
||||
xml.attribute("text", padded(_build_info.image_version())); });
|
||||
gen_named_node(xml, "label", "genode", [&] {
|
||||
xml.attribute("text", padded(_build_info.genode_version()));
|
||||
xml.attribute("font", "annotation/regular");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__SOFTWARE_VERSION_DIALOG_H_ */
|
142
repos/gems/src/app/sculpt_manager/view/system_dialog.h
Normal file
142
repos/gems/src/app/sculpt_manager/view/system_dialog.h
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* \brief System dialog
|
||||
* \author Norman Feske
|
||||
* \date 2023-04-24
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _VIEW__SYSTEM_DIALOG_H_
|
||||
#define _VIEW__SYSTEM_DIALOG_H_
|
||||
|
||||
#include <view/layout_helper.h>
|
||||
#include <view/dialog.h>
|
||||
#include <view/software_presets_dialog.h>
|
||||
#include <view/software_update_dialog.h>
|
||||
#include <view/software_version_dialog.h>
|
||||
#include <model/settings.h>
|
||||
|
||||
namespace Sculpt { struct System_dialog; }
|
||||
|
||||
|
||||
struct Sculpt::System_dialog : Noncopyable, Dialog
|
||||
{
|
||||
using Depot_users = Depot_users_dialog::Depot_users;
|
||||
using Image_index = Attached_rom_dataspace;
|
||||
|
||||
Hoverable_item _tab_item { };
|
||||
|
||||
enum Tab { PRESETS, UPDATE } _selected_tab = Tab::PRESETS;
|
||||
|
||||
Software_presets_dialog _presets_dialog;
|
||||
Software_update_dialog _update_dialog;
|
||||
Software_version_dialog _version_dialog;
|
||||
|
||||
Hover_result hover(Xml_node hover) override
|
||||
{
|
||||
Hover_result dialog_hover_result = Hover_result::UNMODIFIED;
|
||||
|
||||
hover.with_optional_sub_node("frame", [&] (Xml_node const &frame) {
|
||||
frame.with_optional_sub_node("vbox", [&] (Xml_node const &vbox) {
|
||||
switch (_selected_tab) {
|
||||
case Tab::PRESETS: dialog_hover_result = _presets_dialog.hover(vbox); break;
|
||||
case Tab::UPDATE: dialog_hover_result = _update_dialog.hover(vbox); break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return any_hover_changed(
|
||||
dialog_hover_result,
|
||||
_tab_item.match(hover, "frame", "vbox", "hbox", "button", "name"));
|
||||
}
|
||||
|
||||
void reset() override { }
|
||||
|
||||
void generate(Xml_generator &xml) const override
|
||||
{
|
||||
gen_named_node(xml, "frame", "system", [&] {
|
||||
xml.node("vbox", [&] {
|
||||
gen_named_node(xml, "hbox", "tabs", [&] {
|
||||
auto gen_tab = [&] (auto const &id, auto tab, auto const &text)
|
||||
{
|
||||
gen_named_node(xml, "button", id, [&] {
|
||||
_tab_item.gen_hovered_attr(xml, id);
|
||||
if (_selected_tab == tab)
|
||||
xml.attribute("selected", "yes");
|
||||
xml.node("label", [&] {
|
||||
xml.attribute("text", text);
|
||||
});
|
||||
});
|
||||
};
|
||||
gen_tab("presets", Tab::PRESETS, " Presets ");
|
||||
gen_tab("update", Tab::UPDATE, " Update ");
|
||||
});
|
||||
switch (_selected_tab) {
|
||||
case Tab::PRESETS:
|
||||
_presets_dialog.generate(xml);
|
||||
break;
|
||||
case Tab::UPDATE:
|
||||
_update_dialog .generate(xml);
|
||||
_version_dialog.generate(xml);
|
||||
break;
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Click_result click()
|
||||
{
|
||||
if (_tab_item._hovered.valid()) {
|
||||
if (_tab_item._hovered == "presets") _selected_tab = Tab::PRESETS;
|
||||
if (_tab_item._hovered == "update") _selected_tab = Tab::UPDATE;
|
||||
return Click_result::CONSUMED;
|
||||
};
|
||||
|
||||
switch (_selected_tab) {
|
||||
case Tab::PRESETS: _presets_dialog.click(); break;
|
||||
case Tab::UPDATE: _update_dialog .click(); break;
|
||||
}
|
||||
return Click_result::CONSUMED;
|
||||
}
|
||||
|
||||
Click_result clack()
|
||||
{
|
||||
switch (_selected_tab) {
|
||||
case Tab::PRESETS: _presets_dialog.clack(); break;
|
||||
case Tab::UPDATE: _update_dialog .clack(); break;
|
||||
};
|
||||
return Click_result::CONSUMED;
|
||||
}
|
||||
|
||||
System_dialog(Presets const &presets,
|
||||
Build_info const &build_info,
|
||||
Nic_state const &nic_state,
|
||||
Download_queue const &download_queue,
|
||||
Index_update_queue const &index_update_queue,
|
||||
File_operation_queue const &file_operation_queue,
|
||||
Depot_users const &depot_users,
|
||||
Image_index const &image_index,
|
||||
Software_presets_dialog::Action &presets_action,
|
||||
Depot_users_dialog::Action &depot_users_action,
|
||||
Software_update_dialog::Action &update_action)
|
||||
:
|
||||
_presets_dialog(presets, presets_action),
|
||||
_update_dialog(build_info, nic_state, download_queue,
|
||||
index_update_queue, file_operation_queue, depot_users,
|
||||
image_index, depot_users_action, update_action),
|
||||
_version_dialog(build_info)
|
||||
{ }
|
||||
|
||||
bool update_tab_selected() const { return _selected_tab == Tab::UPDATE; }
|
||||
|
||||
bool keyboard_needed() const { return _update_dialog.keyboard_needed(); }
|
||||
|
||||
void handle_key(Codepoint c) { _update_dialog.handle_key(c); }
|
||||
};
|
||||
|
||||
#endif /* _VIEW__SYSTEM_DIALOG_H_ */
|
75
repos/gems/src/app/sculpt_manager/view/text_entry_field.h
Normal file
75
repos/gems/src/app/sculpt_manager/view/text_entry_field.h
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* \brief Helper for implementing editable text fields
|
||||
* \author Norman Feske
|
||||
* \date 2023-03-17
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _VIEW__TEXT_ENTRY_FIELD_H_
|
||||
#define _VIEW__TEXT_ENTRY_FIELD_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
|
||||
|
||||
namespace Sculpt {
|
||||
|
||||
template <size_t> struct Text_entry_field;
|
||||
|
||||
enum {
|
||||
CODEPOINT_BACKSPACE = 8, CODEPOINT_NEWLINE = 10,
|
||||
CODEPOINT_UP = 0xf700, CODEPOINT_DOWN = 0xf701,
|
||||
CODEPOINT_LEFT = 0xf702, CODEPOINT_RIGHT = 0xf703,
|
||||
CODEPOINT_HOME = 0xf729, CODEPOINT_INSERT = 0xf727,
|
||||
CODEPOINT_DELETE = 0xf728, CODEPOINT_END = 0xf72b,
|
||||
CODEPOINT_PAGEUP = 0xf72c, CODEPOINT_PAGEDOWN = 0xf72d,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
template <Sculpt::size_t N>
|
||||
struct Sculpt::Text_entry_field
|
||||
{
|
||||
Codepoint _elements[N] { };
|
||||
|
||||
unsigned cursor_pos = 0;
|
||||
|
||||
static bool _printable(Codepoint c)
|
||||
{
|
||||
return (c.value >= 32) && (c.value <= 126);
|
||||
}
|
||||
|
||||
void apply(Codepoint c)
|
||||
{
|
||||
if (c.value == CODEPOINT_BACKSPACE && cursor_pos > 0) {
|
||||
cursor_pos--;
|
||||
_elements[cursor_pos] = { };
|
||||
}
|
||||
|
||||
if (_printable(c) && (cursor_pos + 1) < N) {
|
||||
_elements[cursor_pos] = c;
|
||||
cursor_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename STRING>
|
||||
Text_entry_field(STRING const &s)
|
||||
{
|
||||
for (Utf8_ptr utf8 = s.string(); utf8.complete(); utf8 = utf8.next())
|
||||
apply(utf8.codepoint());
|
||||
}
|
||||
|
||||
void print(Output &out) const
|
||||
{
|
||||
for (unsigned i = 0; i < N; i++)
|
||||
if (_printable(_elements[i]))
|
||||
Genode::print(out, _elements[i]);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__TEXT_ENTRY_FIELD_H_ */
|
Loading…
x
Reference in New Issue
Block a user