sculpt: power options (suspend, reset, power off)

This patch enhances the sculpt manager to drive the system state
and manage the lifecycle of driver components during suspend-
resume cycles.

The new Power options can be found in the System menu. The suspend
and power-off controls are presented only when the acpi-support
option is activated.

Note that the USB controller is hard restarted when resuming from
suspend. Hence, all components that depend on USB are restarted
implicitely.

Issue #5180
Issue #5174
This commit is contained in:
Norman Feske 2024-04-16 17:25:01 +02:00 committed by Christian Helmuth
parent 29e21bff7f
commit f34bf0d19e
15 changed files with 537 additions and 76 deletions

View File

@ -1,4 +1,4 @@
<config>
<config system="yes">
<device vendor="0x8086" device="0x1606" generation="8" platform="broadwell" description="HD Graphics (BDW GT1 ULT)"/>
<device vendor="0x8086" device="0x1616" generation="8" platform="broadwell" description="HD Graphics 5500 (BDW GT2 ULT)"/>
<device vendor="0x8086" device="0x1622" generation="8" platform="broadwell" description="Iris Pro Graphics 6200 (BDW GT3e)"/>

View File

@ -46,6 +46,7 @@ struct Sculpt::Ahci_driver : private Noncopyable
gen_named_node(xml, "binary", "ahci_drv");
gen_provides<Block::Session>(xml);
xml.node("config", [&] {
xml.attribute("system", "yes");
xml.node("report", [&] { xml.attribute("ports", "yes"); });
for (unsigned i = 0; i < 6; i++)
xml.node("policy", [&] {

View File

@ -52,6 +52,7 @@ struct Sculpt::Fb_driver : private Noncopyable
gen_parent_route<Platform::Session>(xml);
gen_parent_rom_route(xml, "intel_gpu_drv");
gen_parent_rom_route(xml, "config", "config -> gpu_drv");
gen_parent_rom_route(xml, "system", "config -> managed/system");
gen_parent_route<Rm_session>(xml);
gen_common_routes(xml);
});
@ -110,10 +111,13 @@ struct Sculpt::Fb_driver : private Noncopyable
void update(Registry<Child_state> &registry, Board_info const &board_info,
Xml_node const &platform)
{
bool const suspending = board_info.options.suspending;
bool const use_intel = board_info.detected.intel_gfx
&& !board_info.options.suppress.intel_gpu;
bool const use_boot_fb = !use_intel && board_info.detected.boot_fb;
bool const use_vesa = !use_boot_fb && !use_intel && board_info.detected.vga;
&& !board_info.options.suppress.intel_gpu
&& !suspending;
bool const use_boot_fb = !use_intel && !suspending && board_info.detected.boot_fb;
bool const use_vesa = !use_boot_fb && !use_intel && !suspending && board_info.detected.vga;
_intel_gpu.conditional(use_intel,
registry, "intel_gpu", Priority::MULTIMEDIA,

View File

@ -47,8 +47,11 @@ struct Sculpt::Nic_driver : private Noncopyable
void update(Registry<Child_state> &registry, Board_info const &board_info)
{
_nic.conditional(board_info.detected.nic && board_info.options.nic,
registry, "nic", Priority::DEFAULT,
bool const use_nic = board_info.detected.nic
&& board_info.options.nic
&& !board_info.options.suspending;
_nic.conditional(use_nic, registry, "nic", Priority::DEFAULT,
Ram_quota { 20*1024*1024 }, Cap_quota { 300 });
}
};

View File

@ -46,6 +46,7 @@ struct Sculpt::Nvme_driver : private Noncopyable
gen_named_node(xml, "binary", "nvme_drv");
gen_provides<Block::Session>(xml);
xml.node("config", [&] {
xml.attribute("system", "yes");
xml.node("report", [&] { xml.attribute("namespaces", "yes"); });
xml.node("policy", [&] {
xml.attribute("label", 1);

View File

@ -53,8 +53,11 @@ struct Sculpt::Ps2_driver : private Noncopyable
void update(Registry<Child_state> &registry, Board_info const &board_info)
{
_ps2.conditional(board_info.detected.ps2 && !board_info.options.suppress.ps2,
registry, "ps2", Priority::MULTIMEDIA,
bool const use_ps2 = board_info.detected.ps2
&& !board_info.options.suppress.ps2
&& !board_info.options.suspending;
_ps2.conditional(use_ps2, registry, "ps2", Priority::MULTIMEDIA,
Ram_quota { 1*1024*1024 }, Cap_quota { 100 });
}
};

View File

@ -161,15 +161,17 @@ struct Sculpt::Usb_driver : private Noncopyable
void update(Registry<Child_state> &registry, Board_info const &board_info)
{
_hcd.conditional(board_info.usb_avail(),
bool const suspending = board_info.options.suspending;
_hcd.conditional(board_info.usb_avail() && !suspending,
registry, "usb", Priority::MULTIMEDIA,
Ram_quota { 16*1024*1024 }, Cap_quota { 200 });
_hid.conditional(board_info.usb_avail() && _detected.hid,
_hid.conditional(board_info.usb_avail() && _detected.hid && !suspending,
registry, "usb_hid", Priority::MULTIMEDIA,
Ram_quota { 11*1024*1024 }, Cap_quota { 180 });
_net.conditional(board_info.usb_avail() && _detected.net && board_info.options.usb_net,
_net.conditional(board_info.usb_avail() && _detected.net && !suspending && board_info.options.usb_net,
registry, "usb_net", Priority::DEFAULT,
Ram_quota { 20*1024*1024 }, Cap_quota { 200 });

View File

@ -92,8 +92,11 @@ struct Sculpt::Wifi_driver : private Noncopyable
void update(Registry<Child_state> &registry, Board_info const &board_info)
{
_wifi.conditional(board_info.wifi_avail() && board_info.options.wifi,
registry, "wifi", Priority::DEFAULT,
bool const use_wifi = board_info.wifi_avail()
&& board_info.options.wifi
&& !board_info.options.suspending;
_wifi.conditional(use_wifi, registry, "wifi", Priority::DEFAULT,
Ram_quota { 16*1024*1024 }, Cap_quota { 250 });
}
};

View File

@ -52,6 +52,7 @@ class Sculpt::Drivers::Instance : Noncopyable,
Action &_action;
Board_info _board_info { };
Resumed _resumed { };
Attached_rom_dataspace const _platform { _env, "platform_info" };
@ -62,6 +63,10 @@ class Sculpt::Drivers::Instance : Noncopyable,
{
_board_info.detected = Board_info::Detected::from_xml(devices,
_platform.xml());
_board_info.used = Board_info::Used::from_xml(devices);
_resumed = { devices.attribute_value("resumed", 0u) };
/*
* The decision which fb driver to start depends on information
* about available devices from both the devices ROM and the
@ -159,6 +164,10 @@ class Sculpt::Drivers::Instance : Noncopyable,
void with(With_board_info::Callback const &fn) const { fn(_board_info); }
void with(With_platform_info::Callback const &fn) const { fn(_platform.xml()); }
bool ready_for_suspend() const { return !_board_info.used.any(); }
Resumed resumed() const { return _resumed; }
};
@ -191,3 +200,7 @@ void Drivers::update_soc (Board_info::Soc soc) { _instance.update_soc(soc
void Drivers::update_options(Board_info::Options opt) { _instance.update_options(opt); }
void Drivers::gen_start_nodes(Xml_generator &xml) const { _instance.gen_start_nodes(xml); }
bool Drivers::ready_for_suspend() const { return _instance.ready_for_suspend(); };
Drivers::Resumed Drivers::resumed() const { return _instance.resumed(); };

View File

@ -72,6 +72,12 @@ class Sculpt::Drivers : Noncopyable
void with_storage_devices(auto const &fn) const { _with(With_storage_devices::Fn { fn }); }
void with_board_info (auto const &fn) const { _with(With_board_info::Fn { fn }); }
void with_platform_info (auto const &fn) const { _with(With_platform_info::Fn { fn }); }
bool ready_for_suspend() const;
struct Resumed { unsigned count; };
Resumed resumed() const;
};
#endif /* _DRIVERS_H_ */

View File

@ -38,6 +38,7 @@
#include <model/settings.h>
#include <model/presets.h>
#include <model/screensaver.h>
#include <model/system_state.h>
#include <view/download_status_widget.h>
#include <view/popup_dialog.h>
#include <view/panel_dialog.h>
@ -66,8 +67,7 @@ struct Sculpt::Main : Input_event_handler,
Panel_dialog::Action,
Network_widget::Action,
Settings_widget::Action,
Software_presets_widget::Action,
Software_update_widget::Action,
System_dialog::Action,
File_browser_dialog::Action,
Popup_dialog::Action,
Component::Construction_info,
@ -114,12 +114,27 @@ struct Sculpt::Main : Input_event_handler,
handle_input_event(ev); });
}
System_state _system_state { };
using Power_features = System_power_widget::Supported;
Power_features _power_features { .suspend = false,
.reset = (_build_info.board == "pc"),
.poweroff = false };
Managed_config<Main> _system_config {
_env, "system", "system", *this, &Main::_handle_system_config };
void _handle_system_config(Xml_node const &)
void _broadcast_system_state()
{
_system_config.try_generate_manually_managed();
_system_config.generate([&] (Xml_generator &xml) {
_system_state.generate(xml); });
}
void _handle_system_config(Xml_node const &state)
{
if (_system_state.apply_config(state).progress)
_broadcast_system_state();
}
Signal_handler<Main> _gui_mode_handler {
@ -152,6 +167,38 @@ struct Sculpt::Main : Input_event_handler,
/* hook for driving the lifetime of the display driver */
}
/**
* System_power_widget::Action
*/
void trigger_suspend() override
{
_system_state.state = System_state::DRIVERS_STOPPING;
_broadcast_system_state();
_driver_options.suspending = true;
_drivers.update_options(_driver_options);
generate_runtime_config();
}
/**
* System_power_widget::Action
*/
void trigger_reboot() override
{
_system_state.state = System_state::RESET;
_broadcast_system_state();
}
/**
* System_power_widget::Action
*/
void trigger_power_off() override
{
_system_state.state = System_state::POWERED_OFF;
_broadcast_system_state();
}
Managed_config<Main> _fonts_config {
_env, "config", "fonts", *this, &Main::_handle_fonts_config };
@ -232,6 +279,8 @@ struct Sculpt::Main : Input_event_handler,
Drivers _drivers { _env, _child_states, *this, *this };
Drivers::Resumed _resumed = _drivers.resumed();
Board_info::Options _driver_options { };
/**
@ -239,11 +288,49 @@ struct Sculpt::Main : Input_event_handler,
*/
void handle_device_plug_unplug() override
{
/* drive suspend/resume */
{
auto const orig_state = _system_state.state;
Drivers::Resumed const orig_resumed = _resumed;
_resumed = _drivers.resumed();
if (orig_resumed.count != _resumed.count)
_system_state.state = System_state::ACPI_RESUMING;
if (_system_state.drivers_stopping() && _drivers.ready_for_suspend())
_system_state.state = System_state::ACPI_SUSPENDING;
if (orig_state != _system_state.state)
_broadcast_system_state();
}
_handle_storage_devices();
network_config_changed();
generate_runtime_config();
}
Rom_handler<Main> _acpi_sleep_states {
_env, "report -> runtime/acpi_support/sleep_states",
*this, &Main::_handle_acpi_sleep_states };
void _handle_acpi_sleep_states(Xml_node const &node)
{
auto const orig_state = _system_state.state;
if (_system_state.ready_for_suspended(node))
_system_state.state = System_state::SUSPENDED;
if (_system_state.ready_for_restarting_drivers(node)) {
_system_state.state = System_state::RUNNING;
_driver_options.suspending = false;
_drivers.update_options(_driver_options);
}
if (orig_state != _system_state.state)
_broadcast_system_state();
}
/**
* Drivers::Info
*/
@ -1433,10 +1520,11 @@ struct Sculpt::Main : Input_event_handler,
Dialog_view<Panel_dialog> _panel_dialog { _dialog_runtime, *this, *this };
Dialog_view<System_dialog> _system_dialog { _dialog_runtime,
_presets, _build_info, _network._nic_state,
_presets, _build_info, _power_features,
_network._nic_state,
_download_queue, _index_update_queue,
_file_operation_queue, _scan_rom,
_image_index_rom, *this, *this };
_image_index_rom, *this };
Dialog_view<Diag_dialog> _diag_dialog { _dialog_runtime, *this, _heap };
@ -2111,6 +2199,16 @@ void Sculpt::Main::_handle_runtime_state(Xml_node const &state)
if (_dialog_runtime.apply_runtime_state(state))
reconfigure_runtime = true;
/* power-management features depend on optional acpi_support subsystem */
{
bool const acpi_support = _runtime_state.present_in_runtime("acpi_support");
Power_features const orig_power_features = _power_features;
_power_features.poweroff = acpi_support;
_power_features.suspend = acpi_support;
if (orig_power_features != _power_features)
_system_dialog.refresh();
}
if (refresh_storage)
_handle_storage_devices();

View File

@ -20,6 +20,38 @@ namespace Sculpt { struct Board_info; }
struct Sculpt::Board_info
{
enum class Pci_class : unsigned {
WIFI = 0x28000,
NIC = 0x20000,
VGA = 0x30000,
AHCI = 0x10601,
NVME = 0x10802,
UHCI = 0xc0300, OHCI = 0xc0310, EHCI = 0xc0320, XHCI = 0xc0330,
};
enum class Pci_vendor : unsigned { INTEL = 0x8086U, };
static bool _matches_class(Xml_node const &pci, Pci_class value)
{
return pci.attribute_value("class", 0U) == unsigned(value);
};
static bool _matches_vendor(Xml_node const &pci, Pci_vendor value)
{
return pci.attribute_value("vendor_id", 0U) == unsigned(value);
};
static bool _matches_usb(Xml_node const &pci)
{
return _matches_class(pci, Pci_class::UHCI) || _matches_class(pci, Pci_class::OHCI)
|| _matches_class(pci, Pci_class::EHCI) || _matches_class(pci, Pci_class::XHCI);
}
static bool _matches_ahci(Xml_node const &pci)
{
return _matches_class(pci, Pci_class::AHCI) && _matches_vendor(pci, Pci_vendor::INTEL);
}
/**
* Runtime-detected features
*/
@ -39,6 +71,23 @@ struct Sculpt::Board_info
} detected;
struct Used
{
bool wifi, nic, intel_gfx, nvme, ahci, usb;
void print(Output &out) const
{
Genode::print(out, "wifi=", wifi, " nic=", nic,
" intel_gfx=", intel_gfx, " nvme=", nvme,
" ahci=", ahci, " usb=", usb);
}
static inline Used from_xml(Xml_node const &devices);
bool any() const { return wifi || nic || intel_gfx || nvme || usb; }
} used;
/**
* Statically-known or configured features
*/
@ -72,11 +121,13 @@ struct Sculpt::Board_info
} suppress;
bool suspending;
bool operator != (Options const &other) const
{
return display != other.display || usb_net != other.usb_net
|| nic != other.nic || wifi != other.wifi
|| suppress != other.suppress;
return display != other.display || usb_net != other.usb_net
|| nic != other.nic || wifi != other.wifi
|| suppress != other.suppress || suspending != other.suspending;
}
} options;
@ -101,41 +152,15 @@ Sculpt::Board_info::Detected::from_xml(Xml_node const &devices, Xml_node const &
device.with_optional_sub_node("pci-config", [&] (Xml_node const &pci) {
enum class Pci_class : unsigned {
WIFI = 0x28000,
NIC = 0x20000,
VGA = 0x30000,
AHCI = 0x10601,
NVME = 0x10802,
UHCI = 0xc0300, OHCI = 0xc0310, EHCI = 0xc0320, XHCI = 0xc0330,
};
if (_matches_class(pci, Pci_class::WIFI)) detected.wifi = true;
if (_matches_class(pci, Pci_class::NIC)) detected.nic = true;
if (_matches_class(pci, Pci_class::NVME)) detected.nvme = true;
if (_matches_usb(pci)) detected.usb = true;
if (_matches_ahci(pci)) detected.ahci = true;
enum class Pci_vendor : unsigned { INTEL = 0x8086U, };
auto matches_class = [&] (Pci_class value)
{
return pci.attribute_value("class", 0U) == unsigned(value);
};
auto matches_vendor = [&] (Pci_vendor value)
{
return pci.attribute_value("vendor_id", 0U) == unsigned(value);
};
if (matches_class(Pci_class::WIFI)) detected.wifi = true;
if (matches_class(Pci_class::NIC)) detected.nic = true;
if (matches_class(Pci_class::NVME)) detected.nvme = true;
if (matches_class(Pci_class::UHCI) || matches_class(Pci_class::OHCI)
|| matches_class(Pci_class::EHCI) || matches_class(Pci_class::XHCI))
detected.usb = true;
if (matches_class(Pci_class::AHCI) && matches_vendor(Pci_vendor::INTEL))
detected.ahci = true;
if (matches_class(Pci_class::VGA)) {
if (_matches_class(pci, Pci_class::VGA)) {
detected.vga = true;
if (matches_vendor(Pci_vendor::INTEL))
if (_matches_vendor(pci, Pci_vendor::INTEL))
detected.intel_gfx = true;
}
});
@ -144,4 +169,33 @@ Sculpt::Board_info::Detected::from_xml(Xml_node const &devices, Xml_node const &
return detected;
}
Sculpt::Board_info::Used
Sculpt::Board_info::Used::from_xml(Xml_node const &devices)
{
Used used { };
devices.for_each_sub_node("device", [&] (Xml_node const &device) {
if (!device.attribute_value("used", false))
return;
device.with_optional_sub_node("pci-config", [&] (Xml_node const &pci) {
if (_matches_class(pci, Pci_class::WIFI)) used.wifi = true;
if (_matches_class(pci, Pci_class::NIC)) used.nic = true;
if (_matches_class(pci, Pci_class::NVME)) used.nvme = true;
if (_matches_usb(pci)) used.usb = true;
if (_matches_ahci(pci)) used.ahci = true;
if (_matches_class(pci, Pci_class::VGA)) {
if (_matches_vendor(pci, Pci_vendor::INTEL))
used.intel_gfx = true;
}
});
});
return used;
}
#endif /* _MODEL__BOARD_INFO_H_ */

View File

@ -0,0 +1,94 @@
/*
* \brief Global system state for suspend/resume support
* \author Norman Feske
* \date 2024-04-16
*/
/*
* Copyright (C) 2024 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__SYSTEM_STATE_H_
#define _MODEL__SYSTEM_STATE_H_
#include <types.h>
namespace Sculpt { struct System_state; }
struct Sculpt::System_state : private Noncopyable
{
enum State {
RUNNING,
DRIVERS_STOPPING, ACPI_SUSPENDING, SUSPENDED, ACPI_RESUMING,
POWERED_OFF, RESET
};
State state { RUNNING };
static State _state_from_xml(Xml_node const &node)
{
auto value = node.attribute_value("state", String<64>());
if (value == "driver_stop") return State::DRIVERS_STOPPING;
if (value == "s3_prepare") return State::ACPI_RESUMING;
if (value == "suspend") return State::SUSPENDED;
if (value == "s3_resume") return State::ACPI_RESUMING;
if (value == "poweroff") return State::POWERED_OFF;
if (value == "reset") return State::RESET;
return State::RUNNING;
}
static auto _state_name(State const state)
{
switch (state) {
case RUNNING: break;
case DRIVERS_STOPPING: return "driver_stop";
case ACPI_SUSPENDING: return "s3_prepare";
case SUSPENDED: return "suspend";
case ACPI_RESUMING: return "s3_resume";
case POWERED_OFF: return "poweroff";
case RESET: return "reset";
};
return "";
}
Progress apply_config(Xml_node const &node)
{
State const orig = state;
state = _state_from_xml(node);
return { orig != state };
}
void generate(Xml_generator &xml) const
{
xml.attribute("state", _state_name(state));
}
bool drivers_stopping() const { return state == State::DRIVERS_STOPPING; }
bool acpi_suspending() const { return state == State::ACPI_SUSPENDING; }
bool acpi_resuming() const { return state == State::ACPI_RESUMING; }
bool _acpi_completed(State const expected, Xml_node const &sleep_states) const
{
auto const complete = sleep_states.attribute_value("complete", String<16>());
return (state == expected) && (complete == _state_name(expected));
}
bool ready_for_suspended(Xml_node const &acpi_sleep_states) const
{
return _acpi_completed(ACPI_SUSPENDING, acpi_sleep_states);
}
bool ready_for_restarting_drivers(Xml_node const &acpi_sleep_states) const
{
return _acpi_completed(ACPI_RESUMING, acpi_sleep_states);
}
};
#endif /* _MODEL__SYSTEM_STATE_H_ */

View File

@ -15,6 +15,7 @@
#define _VIEW__SYSTEM_DIALOG_H_
#include <view/dialog.h>
#include <view/system_power_widget.h>
#include <view/software_presets_widget.h>
#include <view/software_update_widget.h>
#include <view/software_version_widget.h>
@ -25,23 +26,30 @@ namespace Sculpt { struct System_dialog; }
struct Sculpt::System_dialog : Top_level_dialog
{
using Depot_users = Depot_users_widget::Depot_users;
using Image_index = Rom_data;
using Depot_users = Depot_users_widget::Depot_users;
using Image_index = Rom_data;
using Power_features = System_power_widget::Supported;
Presets const &_presets;
Image_index const &_image_index;
Build_info const &_build_info;
Presets const &_presets;
Image_index const &_image_index;
Build_info const &_build_info;
Power_features const &_power_features;
Software_presets_widget::Action &_presets_action;
Software_update_widget::Action &_update_action;
struct Action : virtual System_power_widget::Action,
virtual Software_presets_widget::Action,
virtual Software_update_widget::Action { };
enum Tab { PRESET, UPDATE } _selected_tab = Tab::PRESET;
Action &_action;
enum Tab { POWER, PRESET, UPDATE } _selected_tab = Tab::PRESET;
Hosted<Frame, Vbox, System_power_widget> _power_widget { Id { "power" } };
Hosted<Frame, Vbox, Software_presets_widget> _presets_widget { Id { "presets" } };
Hosted<Frame, Vbox, Software_update_widget> _update_widget;
Hosted<Frame, Vbox, Software_version_widget> _version_widget { Id { "version" } };
Hosted<Frame, Vbox, Hbox, Select_button<Tab>>
_power_tab { Id { " Power " }, Tab::POWER },
_preset_tab { Id { " Presets " }, Tab::PRESET },
_update_tab { Id { " Update " }, Tab::UPDATE };
@ -52,6 +60,8 @@ struct Sculpt::System_dialog : Top_level_dialog
/* tabs */
s.sub_scope<Hbox>([&] (Scope<Frame, Vbox, Hbox> &s) {
if (_power_features.any_support())
s.widget(_power_tab, _selected_tab);
s.widget(_preset_tab, _selected_tab, [&] (auto &s) {
if (!_presets.available())
s.attribute("style", "unimportant");
@ -61,6 +71,10 @@ struct Sculpt::System_dialog : Top_level_dialog
});
switch (_selected_tab) {
case Tab::POWER:
if (_power_features.any_support())
s.widget(_power_widget, _power_features);
break;
case Tab::PRESET:
s.widget(_presets_widget, _presets);
break;
@ -76,36 +90,39 @@ struct Sculpt::System_dialog : Top_level_dialog
void click(Clicked_at const &at) override
{
_power_tab .propagate(at, [&] (Tab t) { _selected_tab = t; });
_preset_tab.propagate(at, [&] (Tab t) { _selected_tab = t; });
_update_tab.propagate(at, [&] (Tab t) { _selected_tab = t; });
_power_widget.propagate(at);
_presets_widget.propagate(at, _presets);
if (_selected_tab == Tab::UPDATE)
_update_widget.propagate(at, _update_action);
_update_widget.propagate(at, _action);
}
void clack(Clacked_at const &at) override
{
_presets_widget.propagate(at, _presets, _presets_action);
_power_widget.propagate(at, _action);
_presets_widget.propagate(at, _presets, _action);
}
void drag(Dragged_at const &) override { }
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_widget::Action &presets_action,
Software_update_widget::Action &update_action)
System_dialog(Presets const &presets,
Build_info const &build_info,
Power_features const &power_features,
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,
Action &action)
:
Top_level_dialog("system"),
_presets(presets), _image_index(image_index), _build_info(build_info),
_presets_action(presets_action), _update_action(update_action),
_power_features(power_features), _action(action),
_update_widget(Id { "update" },
build_info, nic_state, download_queue,
index_update_queue, file_operation_queue, depot_users)

View File

@ -0,0 +1,162 @@
/*
* \brief System power-control widget
* \author Norman Feske
* \date 2024-04-16
*/
/*
* Copyright (C) 2024 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_POWER_WIDGET_H_
#define _VIEW__SYSTEM_POWER_WIDGET_H_
#include <util/formatted_output.h>
#include <view/dialog.h>
namespace Sculpt { struct System_power_widget; }
struct Sculpt::System_power_widget : Widget<Vbox>
{
struct Supported
{
bool suspend, reset, poweroff;
bool any_support() const { return suspend || reset || poweroff; }
bool operator != (Supported const &other) const
{
return suspend != other.suspend
|| reset != other.reset
|| poweroff != other.poweroff;
}
};
enum class Option { UNKNOWN, SUSPEND, REBOOT, OFF };
Option _selected_option { Option::UNKNOWN };
struct Conditional_confirm : Widget<Right_floating_hbox>
{
Hosted<Right_floating_hbox, Deferred_action_button> _button { Id { } };
void view(Scope<Right_floating_hbox> &s, bool condition) const
{
s.widget(_button, [&] (Scope<Button> &s) {
if (!condition)
s.attribute("style", "invisible");
s.sub_scope<Label>("Confirm", [&] (auto &s) {
if (!condition) s.attribute("style", "invisible"); });
});
}
void click(Clicked_at const &at) { _button.propagate(at); }
void clack(Clacked_at const &at, auto const &confirmed_fn)
{
_button.propagate(at, confirmed_fn);
}
};
struct Power_options : Widget<Float>
{
struct Entry : Widget<Hbox>
{
struct Attr { bool need_confirm; };
Hosted<Hbox, Radio_select_button<Option>> _radio;
Hosted<Hbox, Conditional_confirm> _confirm { Id { "confirm" } };
Entry(Option const option) : _radio(Id { "radio" }, option) { }
void view(Scope<Hbox> &s, Option const &selected, Attr attr) const
{
s.widget(_radio, selected, s.id.value);
s.widget(_confirm, attr.need_confirm && (selected == _radio.value));
}
void click(Clicked_at const &at, Option const &selected, auto const &fn)
{
_radio.propagate(at, fn);
if (selected == _radio.value)
_confirm.propagate(at);
}
void clack(Clacked_at const &at, auto const &fn)
{
_confirm.propagate(at, [&] { fn(_radio.value); });
}
};
Hosted<Float, Frame, Vbox, Entry>
_suspend { Id { "Suspend" }, Option::SUSPEND },
_reboot { Id { "Hard reboot" }, Option::REBOOT },
_off { Id { "Hard power down" }, Option::OFF };
void view(Scope<Float> &s, Option const &selected, Supported const supported) const
{
s.sub_scope<Frame>([&] (Scope<Float, Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Float, Frame, Vbox> &s) {
Entry::Attr const attr { .need_confirm = true };
if (supported.suspend) s.widget(_suspend, selected, attr);
if (supported.reset) s.widget(_reboot, selected, attr);
if (supported.poweroff) s.widget(_off, selected, attr);
s.sub_scope<Min_ex>(35);
});
});
}
void click(Clicked_at const &at, Option const &selected, auto const &fn)
{
_suspend.propagate(at, selected, fn);
_reboot .propagate(at, selected, fn);
_off .propagate(at, selected, fn);
}
void clack(Clacked_at const &at, auto const &fn)
{
_suspend.propagate(at, fn);
_reboot .propagate(at, fn);
_off .propagate(at, fn);
}
};
Hosted<Vbox, Power_options> _power_options { Id { "options" } };
void view(Scope<Vbox> &s, Supported const supported) const
{
s.widget(_power_options, _selected_option, supported);
}
struct Action : Interface
{
virtual void trigger_suspend() = 0;
virtual void trigger_reboot() = 0;
virtual void trigger_power_off() = 0;
};
void click(Clicked_at const &at)
{
_power_options.propagate(at, _selected_option, [&] (Option const selected) {
_selected_option = selected; });
}
void clack(Clacked_at const &at, Action &action)
{
_power_options.propagate(at, [&] (Option const confirmed) {
if (confirmed == Option::SUSPEND) action.trigger_suspend();
if (confirmed == Option::REBOOT) action.trigger_reboot();
if (confirmed == Option::OFF) action.trigger_power_off();
});
}
};
#endif /* _VIEW__SYSTEM_POWER_WIDGET_H_ */