mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-19 05:37:54 +00:00
Mobile version of Sculpt OS
This patch contains the mobile variant of Sculpt OS, which evolved at the genode-allwinner repository until now. In consists of the following parts: - gems/src/app/phone_manager plays the role of the sculpt manager - sculpt/phone-linux allows for test driving the mobile variant on base-linux - gems/src/app/dummy_modem mockup of a modem's behavior, used for GUI development and testing The parts targeting a specific device (PinePhone) remain local to the genode-allwinner repository. To give it a try: make run/sculpt_test KERNEL=linux BOARD=linux \ SCULPT=phone LOG=core DEPOT=tar Fixes #5125
This commit is contained in:
parent
ea51f1ffda
commit
b370591e64
63
repos/gems/sculpt/drivers/phone_linux
Normal file
63
repos/gems/sculpt/drivers/phone_linux
Normal file
@ -0,0 +1,63 @@
|
||||
<config>
|
||||
<parent-provides>
|
||||
<service name="ROM"/>
|
||||
<service name="PD"/>
|
||||
<service name="CPU"/>
|
||||
<service name="LOG"/>
|
||||
<service name="Timer"/>
|
||||
<service name="Report"/>
|
||||
<service name="Capture"/>
|
||||
<service name="Event"/>
|
||||
</parent-provides>
|
||||
|
||||
<start name="fb_sdl" caps="100" ld="no">
|
||||
<resource name="RAM" quantum="10M"/>
|
||||
<route>
|
||||
<service name="Event"> <child name="event_filter" label="sdl"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<!-- toggle key mappings depending on the numlock state -->
|
||||
<start name="numlock_remap_rom" caps="100">
|
||||
<binary name="rom_filter"/>
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<provides> <service name="ROM"/> </provides>
|
||||
<route>
|
||||
<service name="ROM" label="config"> <parent label="numlock_remap.config"/> </service>
|
||||
<service name="ROM" label="numlock"> <parent label="numlock"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="event_filter" caps="120">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides> <service name="Event"/> </provides>
|
||||
<route>
|
||||
<service name="ROM" label="config"> <parent label="event_filter.config"/> </service>
|
||||
<service name="ROM" label="numlock.remap"> <child name="numlock_remap_rom"/> </service>
|
||||
<service name="ROM" label="capslock"> <parent label="capslock"/> </service>
|
||||
<service name="ROM"> <parent/> </service>
|
||||
<service name="PD"> <parent/> </service>
|
||||
<service name="CPU"> <parent/> </service>
|
||||
<service name="LOG"> <parent/> </service>
|
||||
<service name="Timer"> <parent/> </service>
|
||||
<service name="Event"> <parent/> </service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="modem" caps="120">
|
||||
<binary name="dummy_modem"/>
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<route>
|
||||
<service name="ROM" label="config"> <parent label="modem.config"/> </service>
|
||||
<service name="ROM"> <parent/> </service>
|
||||
<service name="PD"> <parent/> </service>
|
||||
<service name="CPU"> <parent/> </service>
|
||||
<service name="LOG"> <parent/> </service>
|
||||
<service name="Timer"> <parent/> </service>
|
||||
<service name="Report"> <parent/> </service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
</config>
|
9
repos/gems/sculpt/event_filter/phone_linux
Normal file
9
repos/gems/sculpt/event_filter/phone_linux
Normal file
@ -0,0 +1,9 @@
|
||||
<config>
|
||||
<output>
|
||||
<touch-key>
|
||||
<input name="sdl"/>
|
||||
<tap xpos="0" ypos="400" width="25" height="600" key="KEY_DASHBOARD"/>
|
||||
</touch-key>
|
||||
</output>
|
||||
<policy label="sdl" input="sdl"/>
|
||||
</config>
|
21
repos/gems/sculpt/fonts/phone
Normal file
21
repos/gems/sculpt/fonts/phone
Normal file
@ -0,0 +1,21 @@
|
||||
<config>
|
||||
<vfs>
|
||||
<rom name="Vera.ttf"/>
|
||||
<rom name="VeraMono.ttf"/>
|
||||
<dir name="fonts">
|
||||
<dir name="title">
|
||||
<ttf name="regular" path="/Vera.ttf" size_px="32" cache="256K"/>
|
||||
</dir>
|
||||
<dir name="text">
|
||||
<ttf name="regular" path="/Vera.ttf" size_px="24" cache="256K"/>
|
||||
</dir>
|
||||
<dir name="annotation">
|
||||
<ttf name="regular" path="/Vera.ttf" size_px="20" cache="256K"/>
|
||||
</dir>
|
||||
<dir name="monospace">
|
||||
<ttf name="regular" path="/VeraMono.ttf" size_px="22" cache="256K"/>
|
||||
</dir>
|
||||
</dir>
|
||||
</vfs>
|
||||
<default-policy root="/fonts"/>
|
||||
</config>
|
6
repos/gems/sculpt/launcher/direct_nano3d
Normal file
6
repos/gems/sculpt/launcher/direct_nano3d
Normal file
@ -0,0 +1,6 @@
|
||||
<launcher pkg="nano3d">
|
||||
<route>
|
||||
<service name="Gui"> <parent/> </service>
|
||||
</route>
|
||||
<config shape="cube"/>
|
||||
</launcher>
|
15
repos/gems/sculpt/launcher/direct_system_shell
Normal file
15
repos/gems/sculpt/launcher/direct_system_shell
Normal file
@ -0,0 +1,15 @@
|
||||
<launcher pkg="system_shell">
|
||||
<route>
|
||||
<service name="Gui"> <parent label="focus"/> </service>
|
||||
<service name="File_system" label="fonts"> <child name="fonts_fs"/> </service>
|
||||
<service name="File_system" label="target"> <child name="default_fs_rw"/> </service>
|
||||
<service name="File_system" label="config"> <parent label="config"/> </service>
|
||||
<service name="File_system" label="report"> <parent label="report"/> </service>
|
||||
<service name="Report" label="clipboard"> <parent label="clipboard"/> </service>
|
||||
<service name="ROM" label="clipboard"> <parent label="clipboard"/> </service>
|
||||
<service name="ROM" label="vimrc"> <parent label="config -> vimrc"/> </service>
|
||||
<service name="ROM" label_last="cached_fs_rom">
|
||||
<parent label="cached_fs_rom"/> </service>
|
||||
<service name="RM"> <parent/> </service>
|
||||
</route>
|
||||
</launcher>
|
169
repos/gems/sculpt/leitzentrale/phone
Normal file
169
repos/gems/sculpt/leitzentrale/phone
Normal file
@ -0,0 +1,169 @@
|
||||
<config>
|
||||
<input name="leitzentrale_enabled" rom="leitzentrale" node="leitzentrale">
|
||||
<attribute name="enabled" /> </input>
|
||||
|
||||
<output node="config">
|
||||
<inline>
|
||||
<parent-provides>
|
||||
<service name="ROM"/>
|
||||
<service name="PD"/>
|
||||
<service name="RM"/>
|
||||
<service name="CPU"/>
|
||||
<service name="LOG"/>
|
||||
<service name="Report"/>
|
||||
<service name="Gui"/>
|
||||
<service name="Timer"/>
|
||||
<service name="File_system"/>
|
||||
</parent-provides>
|
||||
|
||||
<default-route> <any-service> <parent/> </any-service> </default-route>
|
||||
|
||||
<default caps="100"/>
|
||||
|
||||
<service name="Gui">
|
||||
<default-policy> <child name="wm"/> </default-policy> </service>
|
||||
|
||||
<service name="File_system">
|
||||
<default-policy> <child name="fonts_fs"/> </default-policy> </service>
|
||||
|
||||
<service name="ROM">
|
||||
<default-policy> <child name="report_rom"/> </default-policy> </service>
|
||||
|
||||
<service name="Report">
|
||||
<default-policy> <child name="report_rom"/> </default-policy> </service>
|
||||
|
||||
<start name="report_rom" caps="200">
|
||||
<resource name="RAM" quantum="4M"/>
|
||||
<provides>
|
||||
<service name="Report"/>
|
||||
<service name="ROM"/>
|
||||
</provides>
|
||||
<config verbose="no">
|
||||
<policy label="decorator -> window_layout" report="manager -> window_layout"/>
|
||||
<policy label="wm -> resize_request" report="manager -> window_layout"/>
|
||||
<policy label="wm -> focus" report="manager -> wm_focus"/>
|
||||
<policy label="decorator -> pointer" report="wm -> pointer"/>
|
||||
<policy label="manager -> window_list" report="wm -> window_list"/>
|
||||
<policy label="manager -> decorator_margins" report="decorator -> decorator_margins"/>
|
||||
<policy label="runtime -> leitzentrale -> main_view -> dialog"
|
||||
report="manager -> main_dialog"/>
|
||||
<policy label="manager -> main_view_hover"
|
||||
report="runtime -> leitzentrale -> main_view -> hover"/>
|
||||
</config>
|
||||
</start>
|
||||
|
||||
<start name="wm" caps="300">
|
||||
<resource name="RAM" quantum="4M"/>
|
||||
<provides>
|
||||
<service name="Gui"/> <service name="Report"/> <service name="ROM"/>
|
||||
</provides>
|
||||
<config>
|
||||
<policy label_prefix="decorator" role="decorator"/>
|
||||
<default-policy/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="ROM" label="resize_request"> <child name="report_rom"/> </service>
|
||||
<service name="ROM" label="focus"> <child name="report_rom"/> </service>
|
||||
<service name="Report"> <child name="report_rom"/> </service>
|
||||
<service name="Gui"> <child name="manager"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="decorator" caps="350">
|
||||
<binary name="themed_decorator"/>
|
||||
<resource name="RAM" quantum="12M"/>
|
||||
<resource name="CPU" quantum="20"/>
|
||||
<config>
|
||||
<libc/>
|
||||
<vfs>
|
||||
<dir name="theme">
|
||||
<inline name="metadata">
|
||||
<theme>
|
||||
<aura top="5" bottom="5" left="5" right="5"/>
|
||||
<decor top="6" bottom="6" left="6" right="6"/>
|
||||
</theme>
|
||||
</inline>
|
||||
<rom name="default.png"/>
|
||||
</dir>
|
||||
<dir name="dev"> <log/> </dir>
|
||||
</vfs>
|
||||
<policy label="log" decoration="yes" motion="20"/>
|
||||
<policy label="runtime -> leitzentrale -> main_view" decoration="no" motion="0"/>
|
||||
<policy label="runtime -> leitzentrale -> touch_keyboard" decoration="no" motion="20"/>
|
||||
<policy label_prefix="logo" decoration="no"/>
|
||||
<default-policy/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="ROM" label="default.png"> <parent label="drop_shadow.png"/> </service>
|
||||
<service name="ROM" label="window_layout"> <child name="report_rom"/> </service>
|
||||
<service name="ROM" label="pointer"> <child name="report_rom"/> </service>
|
||||
<service name="Report" label="decorator_margins"> <child name="report_rom"/> </service>
|
||||
<service name="Report" label="hover"> <child name="report_rom"/> </service>
|
||||
<service name="Gui"> <child name="wm"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="config_fs_report">
|
||||
<binary name="fs_report"/>
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides> <service name="Report"/> </provides>
|
||||
<config> <vfs> <fs/> </vfs> </config>
|
||||
<route>
|
||||
<service name="File_system"> <parent label="config"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="manager" caps="300">
|
||||
<binary name="phone_manager"/>
|
||||
<resource name="RAM" quantum="3M"/>
|
||||
<provides> <service name="Gui"/> </provides>
|
||||
<config ld_verbose="yes" verbose_modem="no"/>
|
||||
<route>
|
||||
<service name="Report" label="runtime_config">
|
||||
<child name="config_fs_report" label="managed -> runtime"/> </service>
|
||||
<service name="Report" label="deploy_config">
|
||||
<child name="config_fs_report" label="managed -> deploy"/> </service>
|
||||
<service name="Report" label="system_config">
|
||||
<child name="config_fs_report" label="managed -> system"/> </service>
|
||||
<service name="Report" label="wifi_config">
|
||||
<child name="config_fs_report" label="managed -> wifi"/> </service>
|
||||
<service name="Report" label="modem_config">
|
||||
<child name="config_fs_report" label="managed -> modem"/> </service>
|
||||
<service name="Report" label="audio_config">
|
||||
<child name="config_fs_report" label="managed -> audio"/> </service>
|
||||
<service name="Report" label="nic_router_config">
|
||||
<child name="config_fs_report" label="managed -> nic_router"/> </service>
|
||||
<service name="Report" label="installation_config">
|
||||
<child name="config_fs_report" label="managed -> installation"/> </service>
|
||||
<service name="Report" label="depot_query">
|
||||
<child name="config_fs_report" label="managed -> depot_query"/> </service>
|
||||
<service name="Report"> <child name="report_rom"/> </service>
|
||||
<service name="ROM" label_prefix="report ->"> <parent/> </service>
|
||||
<service name="ROM" label="nitpicker_focus"> <parent/> </service>
|
||||
<service name="ROM" label="nitpicker_hover"> <parent/> </service>
|
||||
<service name="ROM" label_suffix="_hover"> <child name="report_rom"/> </service>
|
||||
<service name="ROM" label="window_list"> <child name="report_rom"/> </service>
|
||||
<service name="ROM" label="decorator_margins"> <child name="report_rom"/> </service>
|
||||
<service name="ROM" label="clicked"> <child name="report_rom"/> </service>
|
||||
<service name="Gui"> <parent label="manager -> fader -> "/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="fonts_fs" caps="100">
|
||||
<binary name="vfs"/>
|
||||
<resource name="RAM" quantum="4M"/>
|
||||
<provides> <service name="File_system"/> </provides>
|
||||
<route>
|
||||
<service name="ROM" label="config">
|
||||
<parent label="config -> fonts"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
</inline>
|
||||
</output>
|
||||
</config>
|
28
repos/gems/sculpt/nitpicker/phone
Normal file
28
repos/gems/sculpt/nitpicker/phone
Normal file
@ -0,0 +1,28 @@
|
||||
<config focus="rom">
|
||||
<capture/> <event/>
|
||||
<report hover="yes" focus="yes" clicked="yes" keystate="no"/>
|
||||
<background color="#000000"/>
|
||||
|
||||
<domain name="overlay" layer="0" label="no" hover="always" focus="transient" content="client"/>
|
||||
<domain name="pointer" layer="0" content="client" label="no" origin="pointer" />
|
||||
<domain name="invisible" layer="1" xpos="10000" />
|
||||
<domain name="leitzentrale" layer="1" label="no" hover="always" focus="transient" content="client"/>
|
||||
<domain name="touch_keyboard" layer="2" ypos="960" label="no" hover="always" content="client" focus="transient" />
|
||||
<domain name="background" layer="4" label="no" hover="always" content="client" focus="transient" />
|
||||
<domain name="default" layer="3" label="no" hover="always" content="client" focus="transient" width="720" height="960"/>
|
||||
<domain name="follow_touch" layer="3" origin="pointer" label="no" hover="always" content="client" focus="transient" width="720" height="960"/>
|
||||
<domain name="camera" layer="2" label="no" hover="always" content="client" focus="transient" xpos="120" ypos="120"/>
|
||||
|
||||
<policy label_prefix="pointer -> " domain="invisible"/>
|
||||
<policy label_prefix="leitzentrale -> " domain="leitzentrale"/>
|
||||
<policy label_prefix="runtime -> touch_keyboard" domain="touch_keyboard"/>
|
||||
<policy label_prefix="runtime -> follow_touch" domain="follow_touch"/>
|
||||
<policy label_prefix="runtime -> camera" domain="camera"/>
|
||||
<policy label_prefix="runtime -> overlay" domain="overlay"/>
|
||||
<policy label_prefix="backdrop" domain="background"/>
|
||||
<default-policy domain="default"/>
|
||||
|
||||
<global-key name="KEY_DASHBOARD" label="global_keys_handler -> input" />
|
||||
<global-key name="KEY_PRINT" label="runtime -> screenshot -> keys -> input" />
|
||||
<global-key name="KEY_POWER" label="leitzentrale -> manager -> fader -> " />
|
||||
</config>
|
12
repos/gems/sculpt/phone-linux.sculpt
Normal file
12
repos/gems/sculpt/phone-linux.sculpt
Normal file
@ -0,0 +1,12 @@
|
||||
drivers: phone_linux
|
||||
leitzentrale: phone
|
||||
nitpicker: phone
|
||||
fonts: phone
|
||||
event_filter: phone_linux
|
||||
build: app/phone_manager server/nitpicker app/menu_view server/wm
|
||||
build: server/event_filter
|
||||
build: app/dummy_modem
|
||||
import: pkg/drivers_interactive-linux pkg/touch_keyboard
|
||||
deploy: example
|
||||
ram_fs: depot
|
||||
launcher: sticks_blue_backdrop direct_nano3d direct_system_shell
|
256
repos/gems/src/app/dummy_modem/main.cc
Normal file
256
repos/gems/src/app/dummy_modem/main.cc
Normal file
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* \brief Simulation of a modem driver
|
||||
* \author Norman Feske
|
||||
* \date 2022-06-02
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/component.h>
|
||||
#include <base/attached_rom_dataspace.h>
|
||||
#include <os/reporter.h>
|
||||
#include <timer_session/connection.h>
|
||||
|
||||
namespace Dummy_modem {
|
||||
|
||||
using namespace Genode;
|
||||
|
||||
struct Main;
|
||||
}
|
||||
|
||||
|
||||
struct Dummy_modem::Main
|
||||
{
|
||||
Env &_env;
|
||||
|
||||
Expanding_reporter _reporter { _env, "modem", "state" };
|
||||
|
||||
enum class Power_state { UNKNOWN, OFF, STARTING_UP, ON, SHUTTING_DOWN };
|
||||
|
||||
Power_state _power_state = Power_state::ON;
|
||||
|
||||
unsigned _startup_seconds = 0;
|
||||
unsigned _shutdown_seconds = 0;
|
||||
|
||||
struct Pin
|
||||
{
|
||||
enum class State { REQUIRED, CHECKING, OK, PUK_NEEDED };
|
||||
State state = State::OK;
|
||||
unsigned check_countdown_seconds = 0;
|
||||
|
||||
enum { INITIAL_REMAINING_ATTEMPTS = 3 };
|
||||
unsigned remaining_attempts = INITIAL_REMAINING_ATTEMPTS;
|
||||
|
||||
using Code = String<10>;
|
||||
Code current_code { };
|
||||
Code failed_code { };
|
||||
|
||||
Pin() { }
|
||||
};
|
||||
|
||||
Pin _pin { };
|
||||
|
||||
void _generate_report(Xml_generator &xml) const
|
||||
{
|
||||
auto power_value = [] (Power_state state)
|
||||
{
|
||||
switch (state) {
|
||||
case Power_state::OFF: return "off";
|
||||
case Power_state::STARTING_UP: return "starting up";
|
||||
case Power_state::ON: return "on";
|
||||
case Power_state::SHUTTING_DOWN: return "shutting down";
|
||||
case Power_state::UNKNOWN: break;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
xml.attribute("power", power_value(_power_state));
|
||||
|
||||
if (_power_state == Power_state::STARTING_UP)
|
||||
xml.attribute("startup_seconds", _startup_seconds);
|
||||
|
||||
if (_power_state == Power_state::SHUTTING_DOWN)
|
||||
xml.attribute("shutdown_seconds", _shutdown_seconds);
|
||||
|
||||
auto pin_value = [] (Pin::State state)
|
||||
{
|
||||
switch (state) {
|
||||
case Pin::State::REQUIRED: return "required";
|
||||
case Pin::State::CHECKING: return "checking";
|
||||
case Pin::State::OK: return "ok";
|
||||
case Pin::State::PUK_NEEDED: return "puk needed";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
if (_power_state == Power_state::ON) {
|
||||
xml.attribute("pin", pin_value(_pin.state));
|
||||
|
||||
if (_pin.remaining_attempts != Pin::INITIAL_REMAINING_ATTEMPTS)
|
||||
xml.attribute("pin_remaining_attempts", _pin.remaining_attempts);
|
||||
}
|
||||
|
||||
if (_pin.state == Pin::State::OK) {
|
||||
|
||||
enum Scenario { IDLE, INCOMING_CALL, INITIATED_CALL };
|
||||
|
||||
Scenario scenario = INITIATED_CALL;
|
||||
|
||||
if (scenario == INCOMING_CALL) {
|
||||
xml.node("call", [&] {
|
||||
xml.attribute("number", "+49123456789");
|
||||
xml.attribute("state", "incoming");
|
||||
});
|
||||
}
|
||||
|
||||
if (scenario == INITIATED_CALL) {
|
||||
xml.node("call", [&] {
|
||||
xml.attribute("number", "+4911223344");
|
||||
xml.attribute("state", "outbound");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _update_state_report()
|
||||
{
|
||||
_reporter.generate([&] (Xml_generator &xml) {
|
||||
_generate_report(xml); });
|
||||
}
|
||||
|
||||
Timer::Connection _timer { _env };
|
||||
|
||||
Signal_handler<Main> _timer_handler {
|
||||
_env.ep(), *this, &Main::_handle_timer };
|
||||
|
||||
Attached_rom_dataspace _config { _env, "config" };
|
||||
|
||||
Signal_handler<Main> _config_handler {
|
||||
_env.ep(), *this, &Main::_handle_config };
|
||||
|
||||
void _handle_timer()
|
||||
{
|
||||
/* apply time-driven rules */
|
||||
if (_power_state == Power_state::STARTING_UP) {
|
||||
_startup_seconds++;
|
||||
if (_startup_seconds > 4) {
|
||||
_power_state = Power_state::ON;
|
||||
_startup_seconds = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (_power_state == Power_state::SHUTTING_DOWN) {
|
||||
_shutdown_seconds++;
|
||||
if (_shutdown_seconds > 5) {
|
||||
_power_state = Power_state::OFF;
|
||||
_shutdown_seconds = 0;
|
||||
_pin = Pin { };
|
||||
}
|
||||
}
|
||||
|
||||
switch (_pin.state) {
|
||||
|
||||
case Pin::State::REQUIRED:
|
||||
case Pin::State::OK:
|
||||
case Pin::State::PUK_NEEDED:
|
||||
break;
|
||||
|
||||
case Pin::State::CHECKING:
|
||||
if (_pin.check_countdown_seconds > 0)
|
||||
_pin.check_countdown_seconds--;
|
||||
|
||||
if (_pin.check_countdown_seconds == 0) {
|
||||
if (_pin.current_code == "1234") {
|
||||
_pin.state = Pin::State::OK;
|
||||
} else {
|
||||
_pin.state = Pin::State::REQUIRED;
|
||||
_pin.failed_code = _pin.current_code;
|
||||
if (_pin.remaining_attempts == 0)
|
||||
_pin.state = Pin::State::PUK_NEEDED;
|
||||
else
|
||||
_pin.remaining_attempts--;
|
||||
}
|
||||
} else {
|
||||
_trigger_timer_in_one_second();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* re-apply rules dictated by the configuration */
|
||||
_handle_config();
|
||||
}
|
||||
|
||||
void _trigger_timer_in_one_second() { _timer.trigger_once(1000*1000UL); }
|
||||
|
||||
void _handle_config()
|
||||
{
|
||||
_config.update();
|
||||
|
||||
Xml_node const config = _config.xml();
|
||||
log("_handle_config: ", config);
|
||||
|
||||
auto power_from_config = [&]
|
||||
{
|
||||
auto name = config.attribute_value("power", String<10>());
|
||||
|
||||
if (name == "on") return Power_state::ON;
|
||||
if (name == "off") return Power_state::OFF;
|
||||
|
||||
return Power_state::UNKNOWN;
|
||||
};
|
||||
|
||||
Power_state const requested_power_state = power_from_config();
|
||||
|
||||
if (requested_power_state != _power_state) {
|
||||
|
||||
if (requested_power_state == Power_state::ON) {
|
||||
if (_power_state == Power_state::OFF)
|
||||
_power_state = Power_state::STARTING_UP;
|
||||
}
|
||||
|
||||
if (requested_power_state == Power_state::OFF) {
|
||||
if (_power_state == Power_state::ON)
|
||||
_power_state = Power_state::SHUTTING_DOWN;
|
||||
}
|
||||
|
||||
if (requested_power_state != Power_state::UNKNOWN)
|
||||
_trigger_timer_in_one_second();
|
||||
}
|
||||
|
||||
switch (_pin.state) {
|
||||
|
||||
case Pin::State::REQUIRED:
|
||||
if (config.has_attribute("pin")) {
|
||||
Pin::Code const code = config.attribute_value("pin", Pin::Code());
|
||||
/* don't re-submit failed pin */
|
||||
if (code != _pin.failed_code) {
|
||||
_pin.current_code = code;
|
||||
_pin.state = Pin::State::CHECKING;
|
||||
_pin.check_countdown_seconds = 2;
|
||||
_trigger_timer_in_one_second();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Pin::State::CHECKING: /* handled by _handle_timer */
|
||||
case Pin::State::OK:
|
||||
case Pin::State::PUK_NEEDED:
|
||||
break;
|
||||
}
|
||||
|
||||
_update_state_report();
|
||||
}
|
||||
|
||||
Main(Env &env) : _env(env)
|
||||
{
|
||||
_timer.sigh(_timer_handler);
|
||||
_config.sigh(_config_handler);
|
||||
_handle_config();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void Component::construct(Genode::Env &env)
|
||||
{
|
||||
static Dummy_modem::Main main(env);
|
||||
}
|
||||
|
3
repos/gems/src/app/dummy_modem/target.mk
Normal file
3
repos/gems/src/app/dummy_modem/target.mk
Normal file
@ -0,0 +1,3 @@
|
||||
TARGET := dummy_modem
|
||||
SRC_CC := main.cc
|
||||
LIBS := base
|
30
repos/gems/src/app/phone_manager/feature.h
Normal file
30
repos/gems/src/app/phone_manager/feature.h
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* \brief Compile-time feature selection
|
||||
* \author Norman Feske
|
||||
* \date 2022-11-10
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 _FEATURE_H_
|
||||
#define _FEATURE_H_
|
||||
|
||||
#include <types.h>
|
||||
|
||||
namespace Sculpt { struct Feature; };
|
||||
|
||||
|
||||
struct Sculpt::Feature
|
||||
{
|
||||
static constexpr bool PRESENT_PLUS_MENU = false;
|
||||
static constexpr bool STORAGE_DIALOG_HOSTED_IN_GRAPH = false;
|
||||
static constexpr bool INSPECT_VIEW = false;
|
||||
static constexpr bool VISUAL_HOVER = false;
|
||||
};
|
||||
|
||||
#endif /* _FEATURE_H_ */
|
2418
repos/gems/src/app/phone_manager/main.cc
Normal file
2418
repos/gems/src/app/phone_manager/main.cc
Normal file
File diff suppressed because it is too large
Load Diff
21
repos/gems/src/app/phone_manager/model/audio_volume.h
Normal file
21
repos/gems/src/app/phone_manager/model/audio_volume.h
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* \brief Audio volume
|
||||
* \author Norman Feske
|
||||
* \date 2022-12-13
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 _MODEL__AUDIO_VOLUME_H_
|
||||
#define _MODEL__AUDIO_VOLUME_H_
|
||||
|
||||
#include <types.h>
|
||||
|
||||
namespace Sculpt { struct Audio_volume { unsigned value; }; }
|
||||
|
||||
#endif /* _MODEL__AUDIO_VOLUME_H_ */
|
215
repos/gems/src/app/phone_manager/model/current_call.h
Normal file
215
repos/gems/src/app/phone_manager/model/current_call.h
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* \brief State of the current call
|
||||
* \author Norman Feske
|
||||
* \date 2022-06-29
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 _MODEL__CURRENT_CALL_H_
|
||||
#define _MODEL__CURRENT_CALL_H_
|
||||
|
||||
#include <model/modem_state.h>
|
||||
|
||||
namespace Sculpt { struct Current_call; }
|
||||
|
||||
|
||||
struct Sculpt::Current_call
|
||||
{
|
||||
using Number = Modem_state::Number;
|
||||
|
||||
Number number { };
|
||||
|
||||
enum class State {
|
||||
|
||||
NONE,
|
||||
|
||||
/*
|
||||
* Entered by user interaction
|
||||
*/
|
||||
ACCEPTED, /* picked up incoming call */
|
||||
REJECTED, /* rejected incoming or active call */
|
||||
INITIATED,
|
||||
CANCELED, /* canced outbound call */
|
||||
|
||||
/*
|
||||
* Entered by modem activity
|
||||
*/
|
||||
INCOMING,
|
||||
HUNG_UP, /* disconnected by callee */
|
||||
OUTBOUND, /* dialing */
|
||||
ALERTING, /* ring at the callee */
|
||||
ACTIVE,
|
||||
};
|
||||
|
||||
State state = State::NONE;
|
||||
|
||||
/**
|
||||
* Return true if the modem state is applicable to the current state
|
||||
*/
|
||||
bool _applicable(Modem_state const &modem) const
|
||||
{
|
||||
if (state == State::NONE)
|
||||
return true;
|
||||
|
||||
/* accept state updates when current state already reflects the modem */
|
||||
bool const entered_by_modem_activity = (state == State::INCOMING)
|
||||
|| (state == State::HUNG_UP)
|
||||
|| (state == State::OUTBOUND)
|
||||
|| (state == State::ALERTING)
|
||||
|| (state == State::ACTIVE);
|
||||
if (entered_by_modem_activity)
|
||||
return true;
|
||||
|
||||
/* an accepted incoming call became active */
|
||||
if ((state == State::ACCEPTED) && modem.active_call())
|
||||
return true;
|
||||
|
||||
/* forget last canceled initiated call when a new call comes in */
|
||||
if ((state == State::CANCELED) && modem.incoming_call())
|
||||
return true;
|
||||
|
||||
/* reset canceled state once the modem cleared the call */
|
||||
if ((state == State::CANCELED) && !modem.any_call())
|
||||
return true;
|
||||
|
||||
/* reset rejected state once the modem cleared the call */
|
||||
if ((state == State::REJECTED) && !modem.any_call())
|
||||
return true;
|
||||
|
||||
/* clear rejected state when called from a different number */
|
||||
if ((state == State::REJECTED) && modem.incoming_call() && (modem.number() != number))
|
||||
return true;
|
||||
|
||||
/* an initiated call is reflected by the modem as outbound or alerting */
|
||||
if ((state == State::INITIATED) && modem.outbound_call())
|
||||
return true;
|
||||
|
||||
/* an initiated call became active */
|
||||
if ((state == State::INITIATED) && modem.active_call())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static State _from_modem_call_state(Modem_state::Call_state call_state)
|
||||
{
|
||||
switch (call_state) {
|
||||
case Modem_state::Call_state::INCOMING: return State::INCOMING;
|
||||
case Modem_state::Call_state::ACTIVE: return State::ACTIVE;
|
||||
case Modem_state::Call_state::ALERTING: return State::ALERTING;
|
||||
case Modem_state::Call_state::OUTBOUND: return State::OUTBOUND;
|
||||
case Modem_state::Call_state::NONE: break;
|
||||
};
|
||||
return State::NONE;
|
||||
}
|
||||
|
||||
bool speaker = false;
|
||||
|
||||
bool connecting() const
|
||||
{
|
||||
return state == State::INITIATED
|
||||
|| state == State::OUTBOUND
|
||||
|| state == State::ALERTING;
|
||||
}
|
||||
|
||||
bool incoming() const { return state == State::INCOMING; }
|
||||
bool accepted() const { return state == State::ACCEPTED; }
|
||||
bool active() const { return state == State::ACTIVE; }
|
||||
bool none() const { return state == State::NONE; }
|
||||
bool canceled() const { return state == State::CANCELED; }
|
||||
|
||||
void accept()
|
||||
{
|
||||
if (state == State::INCOMING) {
|
||||
state = State::ACCEPTED;
|
||||
speaker = false;
|
||||
}
|
||||
}
|
||||
|
||||
void reject()
|
||||
{
|
||||
if (state == State::INCOMING || state == State::ACTIVE)
|
||||
state = State::REJECTED;
|
||||
speaker = false;
|
||||
}
|
||||
|
||||
void initiate(Number const &n)
|
||||
{
|
||||
number = n;
|
||||
state = State::INITIATED;
|
||||
speaker = false;
|
||||
}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
state = State::CANCELED;
|
||||
speaker = false;
|
||||
}
|
||||
|
||||
void toggle_speaker()
|
||||
{
|
||||
speaker = !speaker;
|
||||
}
|
||||
|
||||
void update(Modem_state const &modem)
|
||||
{
|
||||
if (_applicable(modem)) {
|
||||
state = _from_modem_call_state(modem.call_state());
|
||||
number = modem.number();
|
||||
}
|
||||
|
||||
if (state == State::NONE)
|
||||
speaker = false;
|
||||
}
|
||||
|
||||
void gen_modem_config(Xml_generator &xml) const
|
||||
{
|
||||
xml.attribute("speaker", speaker ? "yes" : "no");
|
||||
|
||||
switch (state) {
|
||||
case State::NONE:
|
||||
case State::INCOMING:
|
||||
break;
|
||||
|
||||
case State::ACCEPTED:
|
||||
xml.node("call", [&] {
|
||||
xml.attribute("number", number);
|
||||
xml.attribute("state", "accepted");
|
||||
});
|
||||
break;
|
||||
|
||||
case State::REJECTED:
|
||||
case State::HUNG_UP:
|
||||
case State::CANCELED:
|
||||
xml.node("call", [&] {
|
||||
xml.attribute("number", number);
|
||||
xml.attribute("state", "rejected");
|
||||
});
|
||||
break;
|
||||
|
||||
case State::INITIATED:
|
||||
case State::OUTBOUND:
|
||||
case State::ALERTING:
|
||||
case State::ACTIVE:
|
||||
xml.node("call", [&] {
|
||||
xml.attribute("number", number);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator != (Current_call const &other) const
|
||||
{
|
||||
return (number != other.number)
|
||||
|| (state != other.state)
|
||||
|| (speaker != other.speaker);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _MODEL__CURRENT_CALL_H_ */
|
84
repos/gems/src/app/phone_manager/model/dialed_number.h
Normal file
84
repos/gems/src/app/phone_manager/model/dialed_number.h
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* \brief Dialed number
|
||||
* \author Norman Feske
|
||||
* \date 2018-05-23
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _MODEL__DIALED_NUMBER_H_
|
||||
#define _MODEL__DIALED_NUMBER_H_
|
||||
|
||||
#include <base/output.h>
|
||||
#include <util/utf8.h>
|
||||
#include <types.h>
|
||||
#include <xml.h>
|
||||
|
||||
namespace Sculpt { struct Dialed_number; }
|
||||
|
||||
|
||||
struct Sculpt::Dialed_number : Noncopyable
|
||||
{
|
||||
public:
|
||||
|
||||
struct Digit
|
||||
{
|
||||
char value;
|
||||
|
||||
bool valid() const { return (value >= '0' && value <= '9')
|
||||
|| (value == '#')
|
||||
|| (value == '*'); }
|
||||
|
||||
void print(Output &out) const
|
||||
{
|
||||
if (valid())
|
||||
Genode::print(out, Char(value));
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
enum { CAPACITY = 32 };
|
||||
Digit _digits[CAPACITY] { };
|
||||
|
||||
unsigned _length = 0;
|
||||
|
||||
public:
|
||||
|
||||
void print(Output &out) const
|
||||
{
|
||||
for (unsigned i = 0; i < _length; i++)
|
||||
Genode::print(out, _digits[i]);
|
||||
}
|
||||
|
||||
void append_digit(Digit d)
|
||||
{
|
||||
/* ignore out-of-range digit values */
|
||||
if (!d.valid())
|
||||
return;
|
||||
|
||||
if (_length < CAPACITY) {
|
||||
_digits[_length] = d;
|
||||
_length++;
|
||||
}
|
||||
}
|
||||
|
||||
void remove_last_digit()
|
||||
{
|
||||
if (_length > 0) {
|
||||
_length--;
|
||||
_digits[_length].value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool suitable_for_call() const { return _length >= 3; }
|
||||
|
||||
bool at_least_one_digit() const { return _length > 0; }
|
||||
};
|
||||
|
||||
#endif /* _MODEL__DIALED_NUMBER_H_ */
|
21
repos/gems/src/app/phone_manager/model/mic_state.h
Normal file
21
repos/gems/src/app/phone_manager/model/mic_state.h
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* \brief Microphone state
|
||||
* \author Norman Feske
|
||||
* \date 2022-12-13
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 _MODEL__MIC_STATE_H_
|
||||
#define _MODEL__MIC_STATE_H_
|
||||
|
||||
#include <types.h>
|
||||
|
||||
namespace Sculpt { enum class Mic_state { OFF, PHONE, ON }; }
|
||||
|
||||
#endif /* _MODEL__MIC_STATE_H_ */
|
195
repos/gems/src/app/phone_manager/model/modem_state.h
Normal file
195
repos/gems/src/app/phone_manager/model/modem_state.h
Normal file
@ -0,0 +1,195 @@
|
||||
/*
|
||||
* \brief Modem state as retrieved from the modem driver
|
||||
* \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 _MODEL__MODEM_STATE_H_
|
||||
#define _MODEL__MODEM_STATE_H_
|
||||
|
||||
namespace Sculpt { struct Modem_state; }
|
||||
|
||||
|
||||
struct Sculpt::Modem_state
|
||||
{
|
||||
enum class Power { UNAVAILABLE, OFF, STARTING_UP, ON, SHUTTING_DOWN };
|
||||
|
||||
Power _power;
|
||||
|
||||
using Number = String<128>;
|
||||
|
||||
enum class Call_state { NONE, INCOMING, ACTIVE, OUTBOUND, ALERTING };
|
||||
|
||||
Call_state _call_state = Call_state::NONE;
|
||||
|
||||
Number _number;
|
||||
|
||||
unsigned _startup_seconds;
|
||||
unsigned _shutdown_seconds;
|
||||
|
||||
enum class Pin_state { UNKNOWN, REQUIRED, CHECKING, REJECTED, OK, PUK_NEEDED };
|
||||
Pin_state _pin_state;
|
||||
|
||||
unsigned _pin_remaining_attempts;
|
||||
|
||||
bool transient() const
|
||||
{
|
||||
return _power == Power::STARTING_UP
|
||||
|| _power == Power::SHUTTING_DOWN;
|
||||
}
|
||||
|
||||
bool on() const
|
||||
{
|
||||
return _power == Power::ON
|
||||
|| _power == Power::STARTING_UP;
|
||||
}
|
||||
|
||||
bool ready() const { return _power == Power::ON; }
|
||||
|
||||
Call_state call_state() const { return _call_state; }
|
||||
|
||||
Number number() const { return _number; }
|
||||
|
||||
bool any_call() const { return _call_state != Call_state::NONE; }
|
||||
|
||||
bool incoming_call() const { return _call_state == Call_state::INCOMING; }
|
||||
|
||||
bool outbound_call() const { return _call_state == Call_state::OUTBOUND
|
||||
|| _call_state == Call_state::ALERTING; }
|
||||
|
||||
bool active_call() const { return _call_state == Call_state::ACTIVE; }
|
||||
|
||||
bool pin_required() const { return _pin_state == Pin_state::REQUIRED
|
||||
|| _pin_state == Pin_state::REJECTED; }
|
||||
|
||||
bool pin_ok() const { return _pin_state == Pin_state::OK; }
|
||||
|
||||
bool pin_rejected() const { return _pin_state == Pin_state::REJECTED; }
|
||||
|
||||
using Power_message = String<128>;
|
||||
|
||||
Power_message power_message() const
|
||||
{
|
||||
using Msg = Power_message;
|
||||
|
||||
switch (_power) {
|
||||
case Power::STARTING_UP: return Msg(" starting up (", _startup_seconds, ") ");
|
||||
case Power::SHUTTING_DOWN: return Msg(" shutting down (", _shutdown_seconds, ") ");
|
||||
case Power::ON:
|
||||
|
||||
switch (_pin_state) {
|
||||
|
||||
case Pin_state::REQUIRED:
|
||||
return Msg(" PIN required");
|
||||
|
||||
case Pin_state::REJECTED:
|
||||
return (_pin_remaining_attempts == 1)
|
||||
? Msg(" PIN rejected (one more try) ")
|
||||
: Msg(" PIN rejected (", _pin_remaining_attempts, " more tries) ");
|
||||
|
||||
case Pin_state::CHECKING:
|
||||
return Msg(" checking PIN ... ");
|
||||
|
||||
case Pin_state::OK:
|
||||
return Msg(" ready ");
|
||||
|
||||
case Pin_state::PUK_NEEDED:
|
||||
return Msg(" PUK needed, giving up. ");
|
||||
|
||||
case Pin_state::UNKNOWN:
|
||||
break;
|
||||
}
|
||||
return Msg(" unknown PIN state ");
|
||||
|
||||
case Power::OFF: return Msg(" powered off ");
|
||||
case Power::UNAVAILABLE: break;
|
||||
}
|
||||
return Msg(" unavailable" );
|
||||
}
|
||||
|
||||
bool operator != (Modem_state const &other) const
|
||||
{
|
||||
return other._power != _power
|
||||
|| other._number != _number
|
||||
|| other._call_state != _call_state
|
||||
|| other._startup_seconds != _startup_seconds
|
||||
|| other._shutdown_seconds != _shutdown_seconds
|
||||
|| other._pin_state != _pin_state
|
||||
|| other._pin_remaining_attempts != _pin_remaining_attempts;
|
||||
}
|
||||
|
||||
static Modem_state from_xml(Xml_node const &node)
|
||||
{
|
||||
auto power = [&]
|
||||
{
|
||||
auto value = node.attribute_value("power", String<20>());
|
||||
|
||||
if (value == "on") return Power::ON;
|
||||
if (value == "starting up") return Power::STARTING_UP;
|
||||
if (value == "shutting down") return Power::SHUTTING_DOWN;
|
||||
if (value == "off") return Power::OFF;
|
||||
|
||||
return Power::UNAVAILABLE;
|
||||
};
|
||||
|
||||
auto pin_state = [&]
|
||||
{
|
||||
auto value = node.attribute_value("pin", String<20>());
|
||||
|
||||
if (value == "required")
|
||||
return node.has_attribute("pin_remaining_attempts")
|
||||
? Pin_state::REJECTED : Pin_state::REQUIRED;
|
||||
|
||||
if (value == "checking") return Pin_state::CHECKING;
|
||||
if (value == "ok") return Pin_state::OK;
|
||||
if (value == "puk needed") return Pin_state::PUK_NEEDED;
|
||||
|
||||
return Pin_state::UNKNOWN;
|
||||
};
|
||||
|
||||
auto number = [&]
|
||||
{
|
||||
Number result { };
|
||||
node.with_optional_sub_node("call", [&] (Xml_node const &call) {
|
||||
result = call.attribute_value("number", Number()); });
|
||||
return result;
|
||||
};
|
||||
|
||||
auto call_state = [&]
|
||||
{
|
||||
Call_state result = Call_state::NONE;
|
||||
|
||||
node.with_optional_sub_node("call", [&] (Xml_node const &call) {
|
||||
|
||||
auto const type = call.attribute_value("state", String<20>());
|
||||
|
||||
result = (type == "incoming") ? Call_state::INCOMING
|
||||
: (type == "active") ? Call_state::ACTIVE
|
||||
: (type == "outbound") ? Call_state::OUTBOUND
|
||||
: (type == "alerting") ? Call_state::ALERTING
|
||||
: Call_state::NONE;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return Modem_state {
|
||||
._power = power(),
|
||||
._call_state = call_state(),
|
||||
._number = number(),
|
||||
._startup_seconds = node.attribute_value("startup_seconds", 0u),
|
||||
._shutdown_seconds = node.attribute_value("shutdown_seconds", 0u),
|
||||
._pin_state = pin_state(),
|
||||
._pin_remaining_attempts = node.attribute_value("pin_remaining_attempts", 0u),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _MODEL__MODEM_STATE_H_ */
|
102
repos/gems/src/app/phone_manager/model/power_state.h
Normal file
102
repos/gems/src/app/phone_manager/model/power_state.h
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* \brief Power state as provided by the power driver
|
||||
* \author Norman Feske
|
||||
* \date 2022-11-09
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 _MODEL__POWER_STATE_H_
|
||||
#define _MODEL__POWER_STATE_H_
|
||||
|
||||
#include <types.h>
|
||||
|
||||
namespace Sculpt { struct Power_state; }
|
||||
|
||||
|
||||
struct Sculpt::Power_state
|
||||
{
|
||||
bool ac_present, battery_present, charging;
|
||||
|
||||
double voltage;
|
||||
|
||||
struct Battery
|
||||
{
|
||||
double charge_current, power_draw;
|
||||
|
||||
unsigned remaining_capacity;
|
||||
|
||||
static Battery from_xml(Xml_node const &battery)
|
||||
{
|
||||
return Battery {
|
||||
.charge_current = battery.attribute_value("charge_current", 0.0),
|
||||
.power_draw = battery.attribute_value("power_draw", 0.0),
|
||||
.remaining_capacity = battery.attribute_value("remaining_capacity", 0u)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Battery battery;
|
||||
|
||||
enum class Profile { UNKNOWN, PERFORMANCE, ECONOMIC };
|
||||
|
||||
Profile profile;
|
||||
|
||||
unsigned brightness;
|
||||
|
||||
static Power_state from_xml(Xml_node const &node)
|
||||
{
|
||||
Battery battery { };
|
||||
|
||||
node.with_optional_sub_node("battery", [&] (Xml_node const &node) {
|
||||
battery = Battery::from_xml(node);
|
||||
});
|
||||
|
||||
auto profile_from_xml = [] (Xml_node const &node)
|
||||
{
|
||||
auto value = node.attribute_value("power_profile", String<64>());
|
||||
|
||||
if (value == "performance") return Profile::PERFORMANCE;
|
||||
if (value == "economic") return Profile::ECONOMIC;
|
||||
|
||||
return Profile::UNKNOWN;
|
||||
};
|
||||
|
||||
return Power_state {
|
||||
.ac_present = node.attribute_value("ac_present", false),
|
||||
.battery_present = node.has_sub_node("battery"),
|
||||
.charging = node.attribute_value("charging", false),
|
||||
.voltage = node.attribute_value("voltage", 0.0),
|
||||
.battery = battery,
|
||||
.profile = profile_from_xml(node),
|
||||
.brightness = node.attribute_value("brightness", 0u),
|
||||
};
|
||||
}
|
||||
|
||||
using Summary = String<128>;
|
||||
|
||||
Summary summary() const
|
||||
{
|
||||
if (!battery_present)
|
||||
return "AC";
|
||||
|
||||
return Summary(battery.remaining_capacity,"%", charging ? " +": "");
|
||||
}
|
||||
|
||||
bool modem_present() const
|
||||
{
|
||||
/* assume presence of a modem before receiving the first report */
|
||||
bool const uncertain = !ac_present && !battery_present;
|
||||
if (uncertain)
|
||||
return true;
|
||||
|
||||
return battery_present;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _MODEL__POWER_STATE_H_ */
|
123
repos/gems/src/app/phone_manager/model/sim_pin.h
Normal file
123
repos/gems/src/app/phone_manager/model/sim_pin.h
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* \brief SIM pin
|
||||
* \author Norman Feske
|
||||
* \date 2018-05-23
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#ifndef _MODEL__SIM_PIN_H_
|
||||
#define _MODEL__SIM_PIN_H_
|
||||
|
||||
#include <base/output.h>
|
||||
#include <util/utf8.h>
|
||||
#include <types.h>
|
||||
#include <xml.h>
|
||||
|
||||
|
||||
namespace Sculpt { struct Bullet; }
|
||||
|
||||
struct Sculpt::Bullet
|
||||
{
|
||||
void print(Output &out) const
|
||||
{
|
||||
char const bullet_utf8[4] = { (char)0xe2, (char)0x80, (char)0xa2, 0 };
|
||||
Genode::print(out, bullet_utf8);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
namespace Sculpt { struct Blind_sim_pin; }
|
||||
|
||||
|
||||
struct Sculpt::Blind_sim_pin
|
||||
{
|
||||
virtual void print_bullets(Output &) const = 0;
|
||||
|
||||
virtual ~Blind_sim_pin() { }
|
||||
|
||||
void print(Output &out) const { print_bullets(out); }
|
||||
|
||||
virtual bool suitable_for_unlock() const = 0;
|
||||
|
||||
virtual bool at_least_one_digit() const = 0;
|
||||
};
|
||||
|
||||
|
||||
namespace Sculpt { struct Sim_pin; }
|
||||
|
||||
|
||||
struct Sculpt::Sim_pin : Blind_sim_pin
|
||||
{
|
||||
public:
|
||||
|
||||
struct Digit { unsigned value; };
|
||||
|
||||
private:
|
||||
|
||||
enum { CAPACITY = 4 };
|
||||
Digit _digits[CAPACITY] { };
|
||||
|
||||
unsigned _length = 0;
|
||||
|
||||
public:
|
||||
|
||||
bool confirmed = false;
|
||||
|
||||
void print(Output &out) const
|
||||
{
|
||||
for (unsigned i = 0; i < _length; i++)
|
||||
Genode::print(out, _digits[i].value);
|
||||
}
|
||||
|
||||
void append_digit(Digit d)
|
||||
{
|
||||
/* ignore out-of-range digit values */
|
||||
if (d.value > 9)
|
||||
return;
|
||||
|
||||
if (_length < CAPACITY) {
|
||||
_digits[_length] = d;
|
||||
_length++;
|
||||
}
|
||||
}
|
||||
|
||||
void remove_last_digit()
|
||||
{
|
||||
if (_length > 0) {
|
||||
_length--;
|
||||
_digits[_length].value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void print_bullets(Output &out) const override
|
||||
{
|
||||
for (unsigned i = 0; i < _length; i++)
|
||||
Genode::print(out, Bullet());
|
||||
}
|
||||
|
||||
bool suitable_for_unlock() const override { return _length == 4; }
|
||||
|
||||
bool at_least_one_digit() const override { return _length > 0; }
|
||||
|
||||
bool operator != (Sim_pin const &other) const
|
||||
{
|
||||
auto any_digit_differs = [&] (auto a, auto b)
|
||||
{
|
||||
for (unsigned i = 0; i < _length; i++)
|
||||
if (a[i].value != b[i].value)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
return (_length != other._length)
|
||||
|| any_digit_differs(_digits, other._digits);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _MODEL__SIM_PIN_H_ */
|
89
repos/gems/src/app/phone_manager/runtime/touch_keyboard.h
Normal file
89
repos/gems/src/app/phone_manager/runtime/touch_keyboard.h
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* \brief XML configuration for spawning the administrative touch keyboard
|
||||
* \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 _RUNTIME__TOUCH_KEYBOARD_H_
|
||||
#define _RUNTIME__TOUCH_KEYBOARD_H_
|
||||
|
||||
#include <runtime.h>
|
||||
|
||||
namespace Sculpt {
|
||||
|
||||
enum class Alpha { OPAQUE, ALPHA };
|
||||
|
||||
struct Touch_keyboard_attr
|
||||
{
|
||||
unsigned min_width, min_height;
|
||||
Alpha alpha;
|
||||
Color background;
|
||||
};
|
||||
|
||||
static inline void gen_touch_keyboard(Xml_generator &, Touch_keyboard_attr);
|
||||
}
|
||||
|
||||
|
||||
void Sculpt::gen_touch_keyboard(Xml_generator &xml, Touch_keyboard_attr const attr)
|
||||
{
|
||||
xml.node("start", [&] () {
|
||||
|
||||
gen_common_start_content(xml, "manager_keyboard",
|
||||
Cap_quota{700}, Ram_quota{18*1024*1024},
|
||||
Priority::LEITZENTRALE);
|
||||
|
||||
gen_named_node(xml, "binary", "touch_keyboard", [&] () { });
|
||||
|
||||
xml.node("config", [&] () {
|
||||
xml.attribute("min_width", attr.min_width);
|
||||
xml.attribute("min_height", attr.min_height);
|
||||
|
||||
if (attr.alpha == Alpha::OPAQUE)
|
||||
xml.attribute("opaque", "yes");
|
||||
|
||||
xml.attribute("background", String<20>(attr.background));
|
||||
});
|
||||
|
||||
xml.node("route", [&] () {
|
||||
gen_parent_rom_route(xml, "ld.lib.so");
|
||||
gen_parent_rom_route(xml, "touch_keyboard");
|
||||
gen_parent_rom_route(xml, "layout", "touch_keyboard_layout.config");
|
||||
gen_parent_rom_route(xml, "menu_view");
|
||||
gen_parent_rom_route(xml, "ld.lib.so");
|
||||
gen_parent_rom_route(xml, "vfs.lib.so");
|
||||
gen_parent_rom_route(xml, "libc.lib.so");
|
||||
gen_parent_rom_route(xml, "libm.lib.so");
|
||||
gen_parent_rom_route(xml, "libpng.lib.so");
|
||||
gen_parent_rom_route(xml, "zlib.lib.so");
|
||||
gen_parent_rom_route(xml, "sandbox.lib.so");
|
||||
gen_parent_rom_route(xml, "menu_view_styles.tar");
|
||||
|
||||
gen_parent_route<Cpu_session> (xml);
|
||||
gen_parent_route<Pd_session> (xml);
|
||||
gen_parent_route<Log_session> (xml);
|
||||
gen_parent_route<Timer::Session> (xml);
|
||||
|
||||
gen_service_node<::File_system::Session>(xml, [&] () {
|
||||
xml.attribute("label", "fonts");
|
||||
xml.node("parent", [&] () {
|
||||
xml.attribute("label", "leitzentrale -> fonts"); }); });
|
||||
|
||||
gen_service_node<Gui::Session>(xml, [&] () {
|
||||
xml.node("parent", [&] () {
|
||||
xml.attribute("label", "leitzentrale -> touch_keyboard"); }); });
|
||||
|
||||
gen_service_node<Event::Session>(xml, [&] () {
|
||||
xml.node("parent", [&] () {
|
||||
xml.attribute("label", "global"); }); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#endif /* _RUNTIME__TOUCH_KEYBOARD_H_ */
|
15
repos/gems/src/app/phone_manager/target.mk
Normal file
15
repos/gems/src/app/phone_manager/target.mk
Normal file
@ -0,0 +1,15 @@
|
||||
TARGET := phone_manager
|
||||
|
||||
SCULPT_MANAGER_DIR := $(call select_from_repositories,src/app/sculpt_manager)
|
||||
DEPOT_DEPLOY_DIR := $(call select_from_repositories,src/app/depot_deploy)
|
||||
|
||||
SRC_CC += $(notdir $(wildcard $(PRG_DIR)/*.cc))
|
||||
SRC_CC += $(addprefix view/, $(notdir $(wildcard $(SCULPT_MANAGER_DIR)/view/*.cc)))
|
||||
SRC_CC += $(addprefix runtime/, $(notdir $(wildcard $(SCULPT_MANAGER_DIR)/runtime/*.cc)))
|
||||
SRC_CC += $(addprefix dialog/, $(notdir $(wildcard $(SCULPT_MANAGER_DIR)/dialog/*.cc)))
|
||||
SRC_CC += gui.cc graph.cc deploy.cc storage.cc network.cc
|
||||
LIBS += base
|
||||
INC_DIR += $(PRG_DIR) $(SCULPT_MANAGER_DIR) $(DEPOT_DEPLOY_DIR)
|
||||
|
||||
vpath %.cc $(PRG_DIR)
|
||||
vpath %.cc $(SCULPT_MANAGER_DIR)
|
250
repos/gems/src/app/phone_manager/view/component_add_widget.h
Normal file
250
repos/gems/src/app/phone_manager/view/component_add_widget.h
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* \brief Widget for configuring a new component deployed from a depot package
|
||||
* \author Norman Feske
|
||||
* \date 2023-03-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__COMPONENT_ADD_WIDGET_H_
|
||||
#define _VIEW__COMPONENT_ADD_WIDGET_H_
|
||||
|
||||
#include <view/index_menu_widget.h>
|
||||
#include <view/pd_route_widget.h>
|
||||
#include <view/resource_widget.h>
|
||||
#include <view/debug_widget.h>
|
||||
#include <view/pd_route_widget.h>
|
||||
#include <view/component_info_widget.h>
|
||||
|
||||
namespace Sculpt { struct Component_add_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Component_add_widget : Widget<Vbox>
|
||||
{
|
||||
using Name = Start_name;
|
||||
using Action = Component::Construction_action;
|
||||
|
||||
Runtime_config const &_runtime_config;
|
||||
|
||||
using Route_entry = Hosted<Vbox, Frame, Vbox, Menu_entry>;
|
||||
using Service_entry = Hosted<Vbox, Frame, Vbox, Menu_entry>;
|
||||
|
||||
Hosted<Vbox, Index_menu_widget::Sub_menu_title> _back { Id { "back" } };
|
||||
Hosted<Vbox, Deferred_action_button> _launch { Id { "Add component" } };
|
||||
Hosted<Vbox, Frame, Vbox, Resource_widget> _resources { Id { "resources" } };
|
||||
Hosted<Vbox, Frame, Pd_route_widget> _pd_route { Id { "pd_route" }, _runtime_config };
|
||||
Hosted<Vbox, Frame, Debug_widget> _debug { Id { "debug" } };
|
||||
|
||||
Id _selected_route { };
|
||||
|
||||
template <typename FN>
|
||||
void _apply_to_selected_route(Action &action, FN const &fn)
|
||||
{
|
||||
unsigned count = 0;
|
||||
action.apply_to_construction([&] (Component &component) {
|
||||
component.routes.for_each([&] (Route &route) {
|
||||
if (_route_selected(Route::Id(count++)))
|
||||
fn(route); }); });
|
||||
}
|
||||
|
||||
bool _route_selected(Route::Id const &id) const
|
||||
{
|
||||
return _selected_route.valid() && id == _selected_route.value;
|
||||
}
|
||||
|
||||
bool _resource_widget_selected() const
|
||||
{
|
||||
return _route_selected("resources");
|
||||
}
|
||||
|
||||
void _view_pkg_elements(Scope<Vbox> &s, Component const &component) const
|
||||
{
|
||||
using Info = Component::Info;
|
||||
|
||||
s.widget(_back, Name { "Add ", Pretty(component.name) });
|
||||
|
||||
s.widget(Hosted<Vbox, Component_info_widget> { Id { "info" } }, component);
|
||||
|
||||
s.sub_scope<Annotation>(Info(Capacity{component.ram}, " ",
|
||||
component.caps, " caps"));
|
||||
s.sub_scope<Vgap>();
|
||||
|
||||
unsigned count = 0;
|
||||
component.routes.for_each([&] (Route const &route) {
|
||||
|
||||
Id const id { Id::Value { count++ } };
|
||||
|
||||
s.sub_scope<Frame>([&] (Scope<Vbox, Frame> &s) {
|
||||
s.sub_scope<Vbox>([&] (Scope<Vbox, Frame, Vbox> &s) {
|
||||
|
||||
bool const selected = _route_selected(id.value);
|
||||
bool const defined = route.selected_service.constructed();
|
||||
|
||||
if (!selected) {
|
||||
Route_entry entry { id };
|
||||
s.widget(entry, defined,
|
||||
defined ? Info(route.selected_service->info)
|
||||
: Info(route));
|
||||
}
|
||||
|
||||
/*
|
||||
* List of routing options
|
||||
*/
|
||||
if (selected) {
|
||||
Route_entry back { Id { "back" } };
|
||||
s.widget(back, true, Info(route), "back");
|
||||
|
||||
unsigned count = 0;
|
||||
_runtime_config.for_each_service([&] (Service const &service) {
|
||||
|
||||
Id const service_id { Id::Value("service.", count++) };
|
||||
|
||||
bool const service_selected =
|
||||
route.selected_service.constructed() &&
|
||||
service_id.value == route.selected_service_id;
|
||||
|
||||
if (service.type == route.required) {
|
||||
Service_entry entry { service_id };
|
||||
s.widget(entry, service_selected, service.info);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* don't show the PD menu if only the system PD service is available */
|
||||
if (_runtime_config.num_service_options(Service::Type::PD) > 1)
|
||||
s.sub_scope<Frame>([&] (Scope<Vbox, Frame> &s) {
|
||||
s.widget(_pd_route, _selected_route, component); });
|
||||
|
||||
s.sub_scope<Frame>(Id { "resources" }, [&] (Scope<Vbox, Frame> &s) {
|
||||
s.sub_scope<Vbox>([&] (Scope<Vbox, Frame, Vbox> &s) {
|
||||
|
||||
bool const selected = _route_selected("resources");
|
||||
|
||||
if (!selected) {
|
||||
Route_entry entry { Id { "resources" } };
|
||||
s.widget(entry, false, "Resource assignment ...", "enter");
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
Route_entry entry { Id { "back" } };
|
||||
s.widget(entry, true, "Resource assignment ...", "back");
|
||||
|
||||
s.widget(_resources, component);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
s.sub_scope<Frame>([&] (Scope<Vbox, Frame> &s) {
|
||||
s.widget(_debug, component); });
|
||||
|
||||
/*
|
||||
* Display "Add component" button once all routes are defined
|
||||
*/
|
||||
if (component.all_routes_defined())
|
||||
s.widget(_launch);
|
||||
}
|
||||
|
||||
Component_add_widget(Runtime_config const &runtime_config)
|
||||
:
|
||||
_runtime_config(runtime_config)
|
||||
{ }
|
||||
|
||||
void view(Scope<Vbox> &s, Component const &component) const
|
||||
{
|
||||
_view_pkg_elements(s, component);
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, Action &action, auto const &leave_fn)
|
||||
{
|
||||
_back.propagate(at, [&] { leave_fn(); });
|
||||
|
||||
_launch.propagate(at);
|
||||
|
||||
Id const route_id = at.matching_id<Vbox, Frame, Vbox, Menu_entry>();
|
||||
|
||||
/* select route to present routing options */
|
||||
if (!_selected_route.valid() && route_id.valid())
|
||||
_selected_route = route_id;
|
||||
|
||||
/*
|
||||
* Route selected
|
||||
*/
|
||||
|
||||
/* close selected route */
|
||||
if (route_id.value == "back") {
|
||||
_selected_route = { };
|
||||
|
||||
} else if (_resource_widget_selected()) {
|
||||
|
||||
bool const clicked_on_different_route = route_id.valid();
|
||||
if (clicked_on_different_route) {
|
||||
_selected_route = route_id;
|
||||
|
||||
} else {
|
||||
|
||||
action.apply_to_construction([&] (Component &component) {
|
||||
_resources.propagate(at, component); });
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
bool clicked_on_selected_route = false;
|
||||
|
||||
_apply_to_selected_route(action, [&] (Route &route) {
|
||||
|
||||
unsigned count = 0;
|
||||
_runtime_config.for_each_service([&] (Service const &service) {
|
||||
|
||||
Id const id { Id::Value("service.", count++) };
|
||||
|
||||
if (route_id == id) {
|
||||
|
||||
bool const clicked_service_already_selected =
|
||||
route.selected_service.constructed() &&
|
||||
id.value == route.selected_service_id;
|
||||
|
||||
if (clicked_service_already_selected) {
|
||||
|
||||
/* clear selection */
|
||||
route.selected_service.destruct();
|
||||
route.selected_service_id = { };
|
||||
|
||||
} else {
|
||||
|
||||
/* select different service */
|
||||
route.selected_service.construct(service);
|
||||
route.selected_service_id = id.value;
|
||||
}
|
||||
|
||||
_selected_route = { };
|
||||
|
||||
clicked_on_selected_route = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (_selected_route == _pd_route.id)
|
||||
action.apply_to_construction([&] (Component &component) {
|
||||
_pd_route.propagate(at, component); });
|
||||
|
||||
/* select different route */
|
||||
if (!clicked_on_selected_route && route_id.valid())
|
||||
_selected_route = route_id;
|
||||
}
|
||||
}
|
||||
|
||||
void clack(Clacked_at const &at, auto const &launch_fn)
|
||||
{
|
||||
_launch.propagate(at, launch_fn);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__COMPONENT_ADD_WIDGET_H_ */
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* \brief Widget for displaying component information for the add menu
|
||||
* \author Norman Feske
|
||||
* \date 2023-11-13
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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__COMPONENT_INFO_WIDGET_H_
|
||||
#define _VIEW__COMPONENT_INFO_WIDGET_H_
|
||||
|
||||
#include <model/component.h>
|
||||
|
||||
namespace Sculpt { struct Component_info_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Component_info_widget : Widget<Vbox>
|
||||
{
|
||||
void view(Scope<Vbox> &s, Component const &component) const
|
||||
{
|
||||
if (component.info.length() > 1) {
|
||||
s.sub_scope<Label>(Component::Info(" ", component.info, " "));
|
||||
s.sub_scope<Small_vgap>();
|
||||
}
|
||||
s.sub_scope<Annotation>(component.path);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__COMPONENT_INFO_WIDGET_H_ */
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* \brief Conditionally visible widget
|
||||
* \author Norman Feske
|
||||
* \date 2022-05-20
|
||||
*
|
||||
* Float widget that generates its content only if a given condition is true.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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__CONDITIONAL_FLOAT_WIDGET_H_
|
||||
#define _VIEW__CONDITIONAL_FLOAT_WIDGET_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
|
||||
namespace Sculpt { template <typename> struct Conditional_float_widget; }
|
||||
|
||||
|
||||
template <typename WIDGET>
|
||||
struct Sculpt::Conditional_float_widget : Widget<Float>
|
||||
{
|
||||
Id const id;
|
||||
|
||||
bool const _centered;
|
||||
|
||||
Hosted<Float, WIDGET> hosted;
|
||||
|
||||
void view(Scope<Float> &s, bool condition, auto &&... args) const
|
||||
{
|
||||
if (!_centered) {
|
||||
s.attribute("east", "yes");
|
||||
s.attribute("west", "yes");
|
||||
}
|
||||
if (condition)
|
||||
s.widget(hosted, args...);
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, auto &&... args) { hosted.propagate(at, args...); }
|
||||
void click(Clicked_at const &at, auto &&... args) const { hosted.propagate(at, args...); }
|
||||
void clack(Clacked_at const &at, auto &&... args) { hosted.propagate(at, args...); }
|
||||
void clack(Clacked_at const &at, auto &&... args) const { hosted.propagate(at, args...); }
|
||||
void drag (Dragged_at const &at, auto &&... args) { hosted.propagate(at, args...); }
|
||||
void drag (Dragged_at const &at, auto &&... args) const { hosted.propagate(at, args...); }
|
||||
|
||||
struct Attr { bool centered; };
|
||||
|
||||
Conditional_float_widget(Attr const &attr, Id const &id, auto &&... args)
|
||||
:
|
||||
id(id), _centered(attr.centered), hosted(id, args...)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
namespace Sculpt {
|
||||
|
||||
template <typename WIDGET>
|
||||
struct Conditional_widget : Hosted<Vbox, Conditional_float_widget<WIDGET> >
|
||||
{
|
||||
using Attr = Conditional_float_widget<WIDGET>::Attr;
|
||||
|
||||
Conditional_widget(Attr const &attr, Id const &id, auto &&... args)
|
||||
:
|
||||
/*
|
||||
* The first 'id' argument is held at 'Hosted', the second 'id'
|
||||
* argument is held by 'Conditional_float_widget'. The 'args' are
|
||||
* forwarded to 'WIDGET'.
|
||||
*/
|
||||
Hosted<Vbox, Conditional_float_widget<WIDGET> >(id, attr, id, args...)
|
||||
{ }
|
||||
|
||||
Conditional_widget(Id const &id, auto &&... args)
|
||||
:
|
||||
Conditional_widget(Attr { .centered = false }, id, args...)
|
||||
{ }
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* _VIEW__CONDITIONAL_FLOAT_WIDGET_H_ */
|
165
repos/gems/src/app/phone_manager/view/current_call_widget.h
Normal file
165
repos/gems/src/app/phone_manager/view/current_call_widget.h
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* \brief Widget for interacting with the current voice call
|
||||
* \author Norman Feske
|
||||
* \date 2022-06-30
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022-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__CURRENT_CALL_WIDGET_H_
|
||||
#define _VIEW__CURRENT_CALL_WIDGET_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
#include <model/current_call.h>
|
||||
|
||||
namespace Sculpt { struct Current_call_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Current_call_widget : Widget<Frame>
|
||||
{
|
||||
struct Action : Interface
|
||||
{
|
||||
using Number = String<128>;
|
||||
|
||||
virtual void accept_incoming_call() = 0;
|
||||
virtual void reject_incoming_call() = 0;
|
||||
virtual void hang_up() = 0;
|
||||
virtual void toggle_speaker() = 0;
|
||||
virtual void initiate_call() = 0;
|
||||
virtual void cancel_initiated_call() = 0;
|
||||
virtual void remove_last_dial_digit() = 0;
|
||||
};
|
||||
|
||||
struct Active_call : Widget<Hbox>
|
||||
{
|
||||
using State = Current_call::State;
|
||||
|
||||
Hosted<Hbox, Right_floating_hbox, Action_button>
|
||||
_accept { Id { " Accept " } },
|
||||
_reject { Id { " Reject " } },
|
||||
_cancel { Id { " Cancel " } },
|
||||
_hang_up { Id { " Hang up " } };
|
||||
|
||||
Hosted<Hbox, Right_floating_hbox, Toggle_button>
|
||||
_speaker { Id { " Speaker " } };
|
||||
|
||||
void view(Scope<Hbox> &s, Current_call const &call) const
|
||||
{
|
||||
auto message = [] (Current_call::State state)
|
||||
{
|
||||
switch (state) {
|
||||
case State::NONE: break;
|
||||
case State::INCOMING: return "Call from";
|
||||
case State::ACCEPTED: return "Connected from";
|
||||
case State::REJECTED: return "Disconnecting from";
|
||||
case State::HUNG_UP: return "Disconnected from";
|
||||
case State::INITIATED: return "Dialing";
|
||||
case State::OUTBOUND: return "Connecting to";
|
||||
case State::ALERTING: return "Alerting";
|
||||
case State::ACTIVE: return "Connected to";
|
||||
case State::CANCELED: return "Canceled call to";
|
||||
}
|
||||
return "Failed"; /* never reached */
|
||||
};
|
||||
|
||||
auto padded = [] (auto const &s) { return String<128>(" ", s, " "); };
|
||||
|
||||
s.sub_scope<Vbox>([&] (Scope<Hbox, Vbox> &s) {
|
||||
s.sub_scope<Min_ex>(15);
|
||||
s.sub_scope<Label>(padded(message(call.state)));
|
||||
s.sub_scope<Label>(padded(call.number));
|
||||
});
|
||||
|
||||
s.sub_scope<Right_floating_hbox>([&] (Scope<Hbox, Right_floating_hbox> &s) {
|
||||
|
||||
if (call.incoming()) {
|
||||
s.widget(_accept);
|
||||
s.widget(_reject);
|
||||
}
|
||||
|
||||
if (call.connecting())
|
||||
s.widget(_cancel);
|
||||
|
||||
if (call.accepted() || call.active()) {
|
||||
s.widget(_speaker, call.speaker);
|
||||
s.widget(_hang_up);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, Action &action)
|
||||
{
|
||||
_reject .propagate(at, [&] { action.reject_incoming_call(); });
|
||||
_accept .propagate(at, [&] { action.accept_incoming_call(); });
|
||||
_speaker.propagate(at, [&] { action.toggle_speaker(); });
|
||||
_hang_up.propagate(at, [&] { action.hang_up(); });
|
||||
_cancel .propagate(at, [&] { action.cancel_initiated_call(); });
|
||||
}
|
||||
};
|
||||
|
||||
struct Operations : Widget<Hbox>
|
||||
{
|
||||
struct Call_button : Action_button
|
||||
{
|
||||
void view(Scope<Button> &s, bool ready, auto text) const
|
||||
{
|
||||
Action_button::view(s, [&] (Scope<Button> &s) {
|
||||
if (!ready)
|
||||
s.attribute("style", "unimportant");
|
||||
s.sub_scope<Label>(text);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Hosted<Hbox, Float, Hbox, Call_button> _clear { Id { "clear" } },
|
||||
_initiate { Id { "initiate" } };;
|
||||
|
||||
void view(Scope<Hbox> &s, Dialed_number const &number) const
|
||||
{
|
||||
s.sub_scope<Vbox>([&] (Scope<Hbox, Vbox> &s) {
|
||||
s.sub_scope<Vgap>();
|
||||
s.sub_scope<Vgap>(); });
|
||||
s.sub_scope<Float>([&] (Scope<Hbox, Float> &s) {
|
||||
s.sub_scope<Hbox>([&] (Scope<Hbox, Float, Hbox> &s) {
|
||||
s.widget(_clear, number.at_least_one_digit(), " Clear ");
|
||||
s.sub_scope<Min_ex>(2);
|
||||
s.widget(_initiate, number.suitable_for_call(), " Initiate ");
|
||||
});
|
||||
});
|
||||
s.sub_scope<Button_vgap>();
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, Action &action)
|
||||
{
|
||||
_clear .propagate(at, [&] { action.remove_last_dial_digit(); });
|
||||
_initiate.propagate(at, [&] { action.initiate_call(); });
|
||||
};
|
||||
};
|
||||
|
||||
Hosted<Frame, Active_call> _active_call { Id { "active_call" } };
|
||||
Hosted<Frame, Operations> _operations { Id { "operations" } };
|
||||
|
||||
void view(Scope<Frame> &s, Dialed_number const &number, Current_call const &call) const
|
||||
{
|
||||
if (call.none()) {
|
||||
s.attribute("style", "invisible");
|
||||
s.widget(_operations, number);
|
||||
} else {
|
||||
s.attribute("style", "transient");
|
||||
s.widget(_active_call, call);
|
||||
}
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, Action &action)
|
||||
{
|
||||
_active_call.propagate(at, action);
|
||||
_operations .propagate(at, action);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__CURRENT_CALL_WIDGET_H_ */
|
148
repos/gems/src/app/phone_manager/view/device_controls_widget.h
Normal file
148
repos/gems/src/app/phone_manager/view/device_controls_widget.h
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* \brief Device controls (e.g., brightness) widget
|
||||
* \author Norman Feske
|
||||
* \date 2022-11-22
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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__DEVICE_CONTROLS_WIDGET_H_
|
||||
#define _VIEW__DEVICE_CONTROLS_WIDGET_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
#include <model/power_state.h>
|
||||
#include <model/mic_state.h>
|
||||
#include <model/audio_volume.h>
|
||||
|
||||
namespace Sculpt { struct Device_controls_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Device_controls_widget : Widget<Vbox>
|
||||
{
|
||||
struct Level : Widget<Frame>
|
||||
{
|
||||
struct Bar : Widget<Right_floating_hbox>
|
||||
{
|
||||
void view(Scope<Right_floating_hbox> &s, unsigned const percent) const
|
||||
{
|
||||
for (unsigned i = 0; i < 10; i++) {
|
||||
s.sub_scope<Button>(Id { i }, [&] (Scope<Right_floating_hbox, Button> &s) {
|
||||
|
||||
if (s.hovered()) s.attribute("hovered", "yes");
|
||||
|
||||
if (i*10 <= percent)
|
||||
s.attribute("selected", "yes");
|
||||
else
|
||||
s.attribute("style", "unimportant");
|
||||
|
||||
s.sub_scope<Label>(" ");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, auto const &fn)
|
||||
{
|
||||
Id const id = at.matching_id<Right_floating_hbox, Button>();
|
||||
unsigned value = 0;
|
||||
if (!ascii_to(id.value.string(), value))
|
||||
return;
|
||||
|
||||
unsigned const percent = max(10u, min(100u, value*10 + 9));
|
||||
fn(percent);
|
||||
}
|
||||
};
|
||||
|
||||
Hosted<Frame, Bar> _bar { Id { "bar" } };
|
||||
|
||||
void view(Scope<Frame> &s, unsigned const percent) const
|
||||
{
|
||||
s.attribute("style", "important");
|
||||
|
||||
s.sub_scope<Left_floating_text>(s.id.value);
|
||||
s.widget(_bar, percent);
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, auto const &fn) { _bar.propagate(at, fn); }
|
||||
};
|
||||
|
||||
Hosted<Vbox, Level> _brightness { Id { "Brightness" } },
|
||||
_volume { Id { "Volume" } };
|
||||
|
||||
struct Mic_choice : Widget<Frame>
|
||||
{
|
||||
using Mic_button = Hosted<Frame, Right_floating_hbox, Select_button<Mic_state> >;
|
||||
|
||||
Mic_button _off { Id { " Off " }, Mic_state::OFF },
|
||||
_phone { Id { " Phone " }, Mic_state::PHONE },
|
||||
_on { Id { " On " }, Mic_state::ON };
|
||||
|
||||
void view(Scope<Frame> &s, Mic_state state) const
|
||||
{
|
||||
s.attribute("style", "important");
|
||||
|
||||
s.sub_scope<Left_floating_text>(s.id.value);
|
||||
s.sub_scope<Right_floating_hbox>([&] (Scope<Frame, Right_floating_hbox> &s) {
|
||||
s.widget(_off, state);
|
||||
s.widget(_phone, state);
|
||||
s.widget(_on, state);
|
||||
});
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, auto const &fn)
|
||||
{
|
||||
_off .propagate(at, [&] (Mic_state s) { fn(s); });
|
||||
_phone.propagate(at, [&] (Mic_state s) { fn(s); });
|
||||
_on .propagate(at, [&] (Mic_state s) { fn(s); });
|
||||
}
|
||||
};
|
||||
|
||||
Hosted<Vbox, Mic_choice> _mic_choice { Id { "Microphone" } };
|
||||
|
||||
void view(Scope<Vbox> &s,
|
||||
Power_state const &power,
|
||||
Mic_state const &mic,
|
||||
Audio_volume const &audio) const
|
||||
{
|
||||
s.widget(_brightness, power.brightness);
|
||||
s.sub_scope<Vgap>();
|
||||
s.widget(_volume, audio.value);
|
||||
s.sub_scope<Vgap>();
|
||||
s.widget(_mic_choice, mic);
|
||||
}
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void select_brightness_level(unsigned) = 0;
|
||||
virtual void select_volume_level(unsigned) = 0;
|
||||
virtual void select_mic_policy(Mic_state const &) = 0;
|
||||
};
|
||||
|
||||
void _click_or_drag(Clicked_at const &at, Action &action)
|
||||
{
|
||||
_brightness.propagate(at, [&] (unsigned percent) {
|
||||
action.select_brightness_level(percent); });
|
||||
|
||||
_volume.propagate(at, [&] (unsigned percent) {
|
||||
action.select_volume_level(percent); });
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, Action &action)
|
||||
{
|
||||
_click_or_drag(at, action);
|
||||
|
||||
_mic_choice.propagate(at, [&] (Mic_state policy) {
|
||||
action.select_mic_policy(policy); });
|
||||
}
|
||||
|
||||
void drag(Dragged_at const &at, Action &action)
|
||||
{
|
||||
_click_or_drag(clicked_at(at), action);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__DEVICE_CONTROLS_WIDGET_H_ */
|
210
repos/gems/src/app/phone_manager/view/device_power_widget.h
Normal file
210
repos/gems/src/app/phone_manager/view/device_power_widget.h
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* \brief Device power-control widget
|
||||
* \author Norman Feske
|
||||
* \date 2022-11-18
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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__DEVICE_POWER_WIDGET_H_
|
||||
#define _VIEW__DEVICE_POWER_WIDGET_H_
|
||||
|
||||
#include <util/formatted_output.h>
|
||||
#include <view/dialog.h>
|
||||
#include <model/power_state.h>
|
||||
|
||||
namespace Sculpt { struct Device_power_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Device_power_widget : Widget<Vbox>
|
||||
{
|
||||
enum class Option { UNKNOWN, PERFORMANCE, ECONOMIC, 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>
|
||||
_performance { Id { "Performance" }, Option::PERFORMANCE },
|
||||
_economic { Id { "Economic" }, Option::ECONOMIC },
|
||||
_reboot { Id { "Reboot" }, Option::REBOOT },
|
||||
_off { Id { "Power down" }, Option::OFF };
|
||||
|
||||
void view(Scope<Float> &s, Option const &selected) const
|
||||
{
|
||||
s.sub_scope<Frame>([&] (Scope<Float, Frame> &s) {
|
||||
s.sub_scope<Vbox>([&] (Scope<Float, Frame, Vbox> &s) {
|
||||
s.widget(_performance, selected, Entry::Attr { .need_confirm = false });
|
||||
s.widget(_economic, selected, Entry::Attr { .need_confirm = false });
|
||||
s.widget(_reboot, selected, Entry::Attr { .need_confirm = true });
|
||||
s.widget(_off, selected, Entry::Attr { .need_confirm = true });
|
||||
s.sub_scope<Min_ex>(35);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, Option const &selected, auto const &fn)
|
||||
{
|
||||
_performance.propagate(at, selected, fn);
|
||||
_economic .propagate(at, selected, fn);
|
||||
_reboot .propagate(at, selected, fn);
|
||||
_off .propagate(at, selected, fn);
|
||||
}
|
||||
|
||||
void clack(Clacked_at const &at, auto const &fn)
|
||||
{
|
||||
_reboot.propagate(at, fn);
|
||||
_off .propagate(at, fn);
|
||||
}
|
||||
};
|
||||
|
||||
static void _view_battery_value(Scope<> &s, auto const &label, double value, auto const &unit)
|
||||
{
|
||||
s.sub_scope<Hbox>([&] (Scope<Hbox> &s) {
|
||||
s.sub_scope<Label>(label, [&] (Scope<Hbox, Label> &s) {
|
||||
s.attribute("min_ex", 23); });
|
||||
|
||||
auto pretty_value = [] (double value, auto unit)
|
||||
{
|
||||
using Text = String<64>;
|
||||
|
||||
if (value < 1.0)
|
||||
return Text((unsigned)((value + 0.0005)*1000), " m", unit);
|
||||
|
||||
unsigned const decimal = (unsigned)(value + 0.005);
|
||||
unsigned const hundredth = (unsigned)(100*(value - decimal));
|
||||
|
||||
return Text(decimal, ".",
|
||||
Repeated(2 - printed_length(hundredth), "0"),
|
||||
hundredth, " ", unit);
|
||||
};
|
||||
|
||||
s.sub_scope<Label>(pretty_value(value, unit), [&] (Scope<Hbox, Label> &s) {
|
||||
s.attribute("min_ex", 8); });
|
||||
});
|
||||
}
|
||||
|
||||
Hosted<Vbox, Power_options> _power_options { Id { "options" } };
|
||||
|
||||
void view(Scope<Vbox> &s, Power_state const &power_state) const
|
||||
{
|
||||
auto curr_selection = [&]
|
||||
{
|
||||
if (_selected_option == Option::UNKNOWN) {
|
||||
|
||||
if (power_state.profile == Power_state::Profile::PERFORMANCE)
|
||||
return Option::PERFORMANCE;
|
||||
|
||||
if (power_state.profile == Power_state::Profile::ECONOMIC)
|
||||
return Option::ECONOMIC;
|
||||
}
|
||||
return _selected_option;
|
||||
};
|
||||
|
||||
s.widget(_power_options, curr_selection());
|
||||
|
||||
if (power_state.battery_present) {
|
||||
s.sub_scope<Centered_info_vbox>([&] (Scope<Vbox, Centered_info_vbox> &s) {
|
||||
|
||||
s.as_new_scope([&] (Scope<> &s) {
|
||||
if (power_state.charging)
|
||||
_view_battery_value(s, " Battery charge current ",
|
||||
power_state.battery.charge_current, "A");
|
||||
else
|
||||
_view_battery_value(s, " Battery power draw ",
|
||||
power_state.battery.power_draw, "W");
|
||||
});
|
||||
|
||||
s.sub_scope<Min_ex>(35);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void activate_performance_power_profile() = 0;
|
||||
virtual void activate_economic_power_profile() = 0;
|
||||
virtual void trigger_device_reboot() = 0;
|
||||
virtual void trigger_device_off() = 0;
|
||||
};
|
||||
|
||||
void click(Clicked_at const &at, Action &action)
|
||||
{
|
||||
_power_options.propagate(at, _selected_option, [&] (Option const selected) {
|
||||
|
||||
_selected_option = selected;
|
||||
|
||||
if (selected == Option::PERFORMANCE) action.activate_performance_power_profile();
|
||||
if (selected == Option::ECONOMIC) action.activate_economic_power_profile();
|
||||
});
|
||||
}
|
||||
|
||||
void clack(Clacked_at const &at, Action &action)
|
||||
{
|
||||
_power_options.propagate(at, [&] (Option const confirmed) {
|
||||
|
||||
if (confirmed == Option::REBOOT) action.trigger_device_reboot();
|
||||
if (confirmed == Option::OFF) action.trigger_device_off();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__DEVICE_POWER_WIDGET_H_ */
|
86
repos/gems/src/app/phone_manager/view/dialpad_widget.h
Normal file
86
repos/gems/src/app/phone_manager/view/dialpad_widget.h
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* \brief Dialpad widget
|
||||
* \author Norman Feske
|
||||
* \date 2022-06-29
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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__DIALPAD_WIDGET_H_
|
||||
#define _VIEW__DIALPAD_WIDGET_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
#include <model/dialed_number.h>
|
||||
|
||||
namespace Sculpt { struct Dialpad_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Dialpad_widget : Widget<Centered_dialog_vbox>
|
||||
{
|
||||
Hosted<Centered_dialog_vbox, Pin_row>
|
||||
_rows[4] { { Id { "r1" }, "1", "2", "3" },
|
||||
{ Id { "r2" }, "4", "5", "6" },
|
||||
{ Id { "r3" }, "7", "8", "9" },
|
||||
{ Id { "r4" }, "*", "0", "#" } };
|
||||
|
||||
void view(Scope<Centered_dialog_vbox> &s, Dialed_number const &dialed_number) const
|
||||
{
|
||||
using Text = String<64>;
|
||||
|
||||
Text digits(dialed_number);
|
||||
|
||||
/* if number grows too large, show the tail end */
|
||||
if (digits.length() > 28)
|
||||
digits = Text("...", Cstring(digits.string() + digits.length() - 27));
|
||||
|
||||
s.sub_scope<Min_ex>(20);
|
||||
s.sub_scope<Vgap>();
|
||||
s.sub_scope<Button>([&] (Scope<Centered_dialog_vbox, Button> &s) {
|
||||
s.attribute("style", "invisible");
|
||||
s.sub_scope<Float>([&] (Scope<Centered_dialog_vbox, Button, Float> &s) {
|
||||
s.attribute("west", "yes");
|
||||
s.sub_scope<Label>(" Dial", [&] (auto &s) {
|
||||
s.attribute("font", "title/regular");
|
||||
if (digits.length() > 12)
|
||||
s.attribute("style", "invisible");
|
||||
});
|
||||
});
|
||||
s.sub_scope<Hbox>([&] (Scope<Centered_dialog_vbox, Button, Hbox> &s) {
|
||||
if (digits.length() <= 12)
|
||||
s.sub_scope<Min_ex>(16);
|
||||
s.sub_scope<Float>([&] (Scope<Centered_dialog_vbox, Button, Hbox, Float> &s) {
|
||||
s.sub_scope<Label>(Text(digits), [&] (auto &s) {
|
||||
s.attribute("min_ex", 15);
|
||||
if (digits.length() < 20)
|
||||
s.attribute("font", "title/regular"); });
|
||||
s.sub_scope<Label>(" ", [&] (auto &s) {
|
||||
s.attribute("font", "title/regular"); });
|
||||
});
|
||||
});
|
||||
});
|
||||
s.sub_scope<Vgap>();
|
||||
|
||||
for (auto const &row : _rows)
|
||||
s.widget(row);
|
||||
}
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void append_dial_digit(Dialed_number::Digit) = 0;
|
||||
};
|
||||
|
||||
void click(Clicked_at const &at, Action &action)
|
||||
{
|
||||
for (auto &row : _rows)
|
||||
row.propagate(at, [&] (auto const &label) {
|
||||
Dialed_number::Digit const digit { label.string()[0] };
|
||||
action.append_dial_digit(digit); });
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__DIALPAD_WIDGET_H_ */
|
178
repos/gems/src/app/phone_manager/view/index_menu_widget.h
Normal file
178
repos/gems/src/app/phone_manager/view/index_menu_widget.h
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* \brief Widget for browsing a depot index
|
||||
* \author Norman Feske
|
||||
* \date 2023-03-21
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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__INDEX_MENU_WIDGET_H_
|
||||
#define _VIEW__INDEX_MENU_WIDGET_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
#include <model/index_menu.h>
|
||||
|
||||
namespace Sculpt { struct Index_menu_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Index_menu_widget : Widget<Vbox>
|
||||
{
|
||||
public:
|
||||
|
||||
using User = Depot::Archive::User;
|
||||
using Index = Attached_rom_dataspace;
|
||||
using Name = Start_name;
|
||||
|
||||
struct Sub_menu_title : Widget<Left_floating_hbox>
|
||||
{
|
||||
void view(Scope<Left_floating_hbox> &s, auto const &text) const
|
||||
{
|
||||
bool const hovered = (s.hovered() && !s.dragged());
|
||||
|
||||
s.sub_scope<Icon>("back", Icon::Attr { .hovered = hovered,
|
||||
.selected = true });
|
||||
s.sub_scope<Label>(" ");
|
||||
s.sub_scope<Label>(text, [&] (auto &s) {
|
||||
s.attribute("font", "title/regular"); });
|
||||
|
||||
/* inflate vertical space to button size */
|
||||
s.sub_scope<Button>([&] (Scope<Left_floating_hbox, Button> &s) {
|
||||
s.attribute("style", "invisible");
|
||||
s.sub_scope<Label>(""); });
|
||||
}
|
||||
|
||||
void click(Clicked_at const &, auto const &fn) const { fn(); }
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
Index const &_index;
|
||||
|
||||
Index_menu _menu { };
|
||||
|
||||
bool _pkg_selected = false;
|
||||
|
||||
Hosted<Vbox, Sub_menu_title> const _back { Id { "back" } };
|
||||
|
||||
void _reset_selection() { _pkg_selected = false; }
|
||||
|
||||
public:
|
||||
|
||||
Index_menu_widget(Index const &index) : _index(index) { }
|
||||
|
||||
void view(Scope<Vbox> &s, User const &user, auto const &view_item_fn) const
|
||||
{
|
||||
if (_menu.level())
|
||||
s.widget(_back, Name { _menu });
|
||||
|
||||
unsigned count = 0;
|
||||
_menu.for_each_item(_index.xml(), user, [&] (Xml_node const &item) {
|
||||
|
||||
Id const id { { count } };
|
||||
|
||||
if (item.has_type("index")) {
|
||||
auto const name = item.attribute_value("name", Name());
|
||||
|
||||
view_item_fn(s, id, Name { name, " ..." }, "");
|
||||
}
|
||||
|
||||
if (item.has_type("pkg")) {
|
||||
auto const path = item.attribute_value("path", Depot::Archive::Path());
|
||||
auto const name = Depot::Archive::name(path);
|
||||
|
||||
view_item_fn(s, id, name, path);
|
||||
}
|
||||
count++;
|
||||
});
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, User const &user,
|
||||
auto const &enter_pkg_fn,
|
||||
auto const &leave_pkg_fn,
|
||||
auto const &pkg_operation_fn)
|
||||
{
|
||||
/* go one menu up */
|
||||
_back.propagate(at, [&] {
|
||||
_menu._selected[_menu._level] = Index_menu::Name();
|
||||
_menu._level--;
|
||||
_pkg_selected = false;
|
||||
leave_pkg_fn();
|
||||
});
|
||||
|
||||
/* enter sub menu of index */
|
||||
if (_menu._level < Index_menu::MAX_LEVELS - 1) {
|
||||
|
||||
Id const clicked = at.matching_id<Vbox, Menu_entry>();
|
||||
|
||||
unsigned count = 0;
|
||||
_menu.for_each_item(_index.xml(), user, [&] (Xml_node const &item) {
|
||||
|
||||
if (clicked == Id { { count } }) {
|
||||
|
||||
if (item.has_type("index")) {
|
||||
|
||||
Index_menu::Name const name =
|
||||
item.attribute_value("name", Index_menu::Name());
|
||||
|
||||
_menu._selected[_menu._level] = name;
|
||||
_menu._level++;
|
||||
|
||||
} else if (item.has_type("pkg")) {
|
||||
|
||||
_pkg_selected = true;
|
||||
enter_pkg_fn(item);
|
||||
}
|
||||
}
|
||||
count++;
|
||||
});
|
||||
|
||||
if (at.matching_id<Vbox, Float>() == Id { "pkg" })
|
||||
pkg_operation_fn(at);
|
||||
}
|
||||
}
|
||||
|
||||
void clack(Clacked_at const &at, auto const &pkg_operation_fn)
|
||||
{
|
||||
if (at.matching_id<Vbox, Float>() == Id { "pkg" })
|
||||
pkg_operation_fn(at);
|
||||
}
|
||||
|
||||
bool top_level() const { return (_menu.level() == 0) && !_pkg_selected; }
|
||||
|
||||
bool pkg_selected() const { return _pkg_selected; }
|
||||
|
||||
void deselect_pkg() { _pkg_selected = false; }
|
||||
|
||||
void reset()
|
||||
{
|
||||
_menu = Index_menu { };
|
||||
_reset_selection();
|
||||
}
|
||||
|
||||
void one_level_back()
|
||||
{
|
||||
if (_menu.level() == 0)
|
||||
_menu._level--;
|
||||
|
||||
_reset_selection();
|
||||
}
|
||||
|
||||
bool anything_visible(User const &user) const
|
||||
{
|
||||
if (_menu.level())
|
||||
return true;
|
||||
|
||||
bool at_least_one_item_exists = false;
|
||||
_menu.for_each_item(_index.xml(), user, [&] (Xml_node const &) {
|
||||
at_least_one_item_exists = true; });
|
||||
|
||||
return at_least_one_item_exists;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__INDEX_MENU_WIDGET_H_ */
|
106
repos/gems/src/app/phone_manager/view/index_pkg_widget.h
Normal file
106
repos/gems/src/app/phone_manager/view/index_pkg_widget.h
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* \brief Widget for presenting pkg details and the option for installation
|
||||
* \author Norman Feske
|
||||
* \date 2023-03-22
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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__INDEX_PKG_WIDGET_H_
|
||||
#define _VIEW__INDEX_PKG_WIDGET_H_
|
||||
|
||||
#include <model/nic_state.h>
|
||||
#include <view/depot_users_widget.h>
|
||||
#include <view/component_info_widget.h>
|
||||
|
||||
namespace Sculpt { struct Index_pkg_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Index_pkg_widget : Widget<Float>
|
||||
{
|
||||
using User_properties = Depot_users_widget::User_properties;
|
||||
|
||||
Hosted<Float, Vbox, Float, Deferred_action_button> _install { Id { "install" } };
|
||||
|
||||
void view(Scope<Float> &s, Component const &component,
|
||||
User_properties const &properties, Nic_state const &nic_state) const
|
||||
{
|
||||
if (!component.blueprint_info.known || component.blueprint_info.ready_to_deploy())
|
||||
return;
|
||||
|
||||
s.sub_scope<Vbox>([&] (Scope<Float, Vbox> &s) {
|
||||
|
||||
/*
|
||||
* Package is installed but content is missing
|
||||
*
|
||||
* This can happen when the pkg's runtime is inconsistent with
|
||||
* the content contained in the pkg's archives.
|
||||
*/
|
||||
if (component.blueprint_info.incomplete()) {
|
||||
s.sub_scope<Small_vgap>();
|
||||
s.sub_scope<Annotation>(component.path);
|
||||
s.sub_scope<Small_vgap>();
|
||||
s.sub_scope<Label>("installed but incomplete");
|
||||
|
||||
if (nic_state.ready()) {
|
||||
s.sub_scope<Small_vgap>();
|
||||
|
||||
auto const text = properties.public_key
|
||||
? " Reattempt Install "
|
||||
: " Reattempt Install without Verification ";
|
||||
|
||||
s.sub_scope<Float>([&] (Scope<Float, Vbox, Float> &s) {
|
||||
s.widget(_install, [&] (Scope<Button> &s) {
|
||||
s.sub_scope<Label>(text); }); });
|
||||
}
|
||||
s.sub_scope<Small_vgap>();
|
||||
}
|
||||
|
||||
/*
|
||||
* Package is missing but can be installed
|
||||
*/
|
||||
else if (component.blueprint_info.uninstalled() && nic_state.ready()) {
|
||||
|
||||
s.widget(Hosted<Float, Vbox, Component_info_widget> { Id { "info" } }, component);
|
||||
|
||||
auto const text = properties.public_key
|
||||
? " Install "
|
||||
: " Install without Verification ";
|
||||
|
||||
s.sub_scope<Vgap>();
|
||||
s.sub_scope<Float>([&] (Scope<Float, Vbox, Float> &s) {
|
||||
s.widget(_install, [&] (Scope<Button> &s) {
|
||||
s.sub_scope<Label>(text); }); });
|
||||
s.sub_scope<Vgap>();
|
||||
}
|
||||
|
||||
/*
|
||||
* Package is missing and we cannot do anything about it
|
||||
*/
|
||||
else if (component.blueprint_info.uninstalled()) {
|
||||
s.sub_scope<Vgap>();
|
||||
s.sub_scope<Annotation>(component.path);
|
||||
s.sub_scope<Vgap>();
|
||||
s.sub_scope<Label>("not installed");
|
||||
s.sub_scope<Vgap>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at)
|
||||
{
|
||||
_install.propagate(at);
|
||||
}
|
||||
|
||||
void clack(Clacked_at const &at, auto const install_fn)
|
||||
{
|
||||
_install.propagate(at, install_fn);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__INDEX_PKG_WIDGET_H_ */
|
50
repos/gems/src/app/phone_manager/view/modem_power_widget.h
Normal file
50
repos/gems/src/app/phone_manager/view/modem_power_widget.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* \brief Modem power-control widget
|
||||
* \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__MODEM_POWER_WIDGET_H_
|
||||
#define _VIEW__MODEM_POWER_WIDGET_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
#include <model/modem_state.h>
|
||||
|
||||
namespace Sculpt { struct Modem_power_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Modem_power_widget : Widget<Frame>
|
||||
{
|
||||
Hosted<Frame, Right_floating_off_on> _power_switch { Id { "power" } };
|
||||
|
||||
void view(Scope<Frame> &s, Modem_state const &state) const
|
||||
{
|
||||
s.attribute("style", "important");
|
||||
|
||||
s.sub_scope<Left_floating_text>("Modem Power");
|
||||
|
||||
s.widget(_power_switch, Right_floating_off_on::Attr {
|
||||
.on = state.on(),
|
||||
.transient = state.transient()
|
||||
});
|
||||
}
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void modem_power(bool) = 0;
|
||||
};
|
||||
|
||||
void click(Clicked_at const &at, Action &action)
|
||||
{
|
||||
_power_switch.propagate(at, [&] (bool on) { action.modem_power(on); });
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__MODEM_POWER_WIDGET_H_ */
|
46
repos/gems/src/app/phone_manager/view/outbound_widget.h
Normal file
46
repos/gems/src/app/phone_manager/view/outbound_widget.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* \brief Widget for initiating calls
|
||||
* \author Norman Feske
|
||||
* \date 2022-06-29
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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__OUTBOUND_WIDGET_H_
|
||||
#define _VIEW__OUTBOUND_WIDGET_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
|
||||
namespace Sculpt { struct Outbound_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Outbound_widget : Widget<Frame>
|
||||
{
|
||||
void view(Scope<Frame> &s) const
|
||||
{
|
||||
s.sub_scope<Hbox>([&] (Scope<Frame, Hbox> &s) {
|
||||
|
||||
s.sub_scope<Button>([&] (Scope<Frame, Hbox, Button> &s) {
|
||||
s.attribute("style", "unimportant");
|
||||
s.sub_scope<Label>("History");
|
||||
});
|
||||
|
||||
s.sub_scope<Button>([&] (Scope<Frame, Hbox, Button> &s) {
|
||||
s.attribute("style", "unimportant");
|
||||
s.sub_scope<Label>("Contacts");
|
||||
});
|
||||
|
||||
s.sub_scope<Button>([&] (Scope<Frame, Hbox, Button> &s) {
|
||||
s.attribute("selected", "yes");
|
||||
s.sub_scope<Label>("Dial");
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__OUTBOUND_WIDGET_H_ */
|
88
repos/gems/src/app/phone_manager/view/pin_widget.h
Normal file
88
repos/gems/src/app/phone_manager/view/pin_widget.h
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* \brief SIM PIN entry widget
|
||||
* \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__PIN_WIDGET_H_
|
||||
#define _VIEW__PIN_WIDGET_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
#include <model/sim_pin.h>
|
||||
|
||||
namespace Sculpt { struct Pin_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Pin_widget : Widget<Centered_dialog_vbox>
|
||||
{
|
||||
Hosted<Centered_dialog_vbox, Pin_row>
|
||||
_rows[3] { { Id { "r1" }, "1", "2", "3" },
|
||||
{ Id { "r2" }, "4", "5", "6" },
|
||||
{ Id { "r3" }, "7", "8", "9" } },
|
||||
_last_row { Id { "r4" }, "C", "0", "OK" };
|
||||
|
||||
void view(Scope<Centered_dialog_vbox> &s, Blind_sim_pin const &sim_pin) const
|
||||
{
|
||||
s.sub_scope<Min_ex>(20);
|
||||
s.sub_scope<Vgap>();
|
||||
s.sub_scope<Hbox>([&] (Scope<Centered_dialog_vbox, Hbox> &s) {
|
||||
s.sub_scope<Label>(" Enter SIM PIN ", [&] (auto &s) {
|
||||
s.attribute("min_ex", 5);
|
||||
s.attribute("font", "title/regular");
|
||||
});
|
||||
s.sub_scope<Label>(String<64>(" ", sim_pin, " "), [&] (auto &s) {
|
||||
s.attribute("min_ex", 5);
|
||||
s.attribute("font", "title/regular");
|
||||
});
|
||||
});
|
||||
s.sub_scope<Vgap>();
|
||||
|
||||
for (auto const &row : _rows)
|
||||
s.widget(row);
|
||||
|
||||
s.widget(_last_row,
|
||||
Pin_row::Visible { .left = sim_pin.at_least_one_digit(),
|
||||
.middle = true,
|
||||
.right = sim_pin.suitable_for_unlock() });
|
||||
}
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void append_sim_pin_digit(Sim_pin::Digit) = 0;
|
||||
virtual void remove_last_sim_pin_digit() = 0;
|
||||
virtual void confirm_sim_pin() = 0;
|
||||
};
|
||||
|
||||
void click(Clicked_at const &at, Blind_sim_pin const &sim_pin, Action &action)
|
||||
{
|
||||
auto for_each_row = [&] (auto const &fn)
|
||||
{
|
||||
for (auto &row : _rows) fn(row);
|
||||
fn(_last_row);
|
||||
};
|
||||
|
||||
for_each_row([&] (auto &row) {
|
||||
row.propagate(at, [&] (auto const &label) {
|
||||
|
||||
for (unsigned i = 0; i < 10; i++)
|
||||
if (String<10>(i) == label)
|
||||
action.append_sim_pin_digit(Sim_pin::Digit{i});
|
||||
|
||||
if (label == "C")
|
||||
action.remove_last_sim_pin_digit();
|
||||
|
||||
if (label == "OK" && sim_pin.suitable_for_unlock())
|
||||
action.confirm_sim_pin();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__PIN_WIDGET_H_ */
|
84
repos/gems/src/app/phone_manager/view/selectable_title_bar.h
Normal file
84
repos/gems/src/app/phone_manager/view/selectable_title_bar.h
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* \brief Section widget
|
||||
* \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__SELECTABLE_TITLE_BAR_H_
|
||||
#define _VIEW__SELECTABLE_TITLE_BAR_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
|
||||
namespace Sculpt { template <typename> struct Selectable_title_bar; }
|
||||
|
||||
|
||||
template <typename ENUM>
|
||||
struct Sculpt::Selectable_title_bar : Widget<Vbox>
|
||||
{
|
||||
ENUM const &_selected_value;
|
||||
|
||||
ENUM const value;
|
||||
|
||||
bool _minimized() const { return (_selected_value != value)
|
||||
&& (_selected_value != ENUM::NONE); }
|
||||
|
||||
Selectable_title_bar(ENUM const &selected_value, ENUM value)
|
||||
: _selected_value(selected_value), value(value) { }
|
||||
|
||||
void view(Scope<Vbox> &s, auto const &status_fn) const
|
||||
{
|
||||
Id const &id = s.id;
|
||||
|
||||
s.sub_scope<Float>([&] (Scope<Vbox, Float> &s) {
|
||||
s.attribute("east", "yes");
|
||||
s.attribute("west", "yes");
|
||||
|
||||
bool const hovered = s.hovered() && (!s.dragged() || selected());
|
||||
|
||||
s.sub_scope<Button>([&] (Scope<Vbox, Float, Button> &s) {
|
||||
|
||||
if (selected()) s.attribute("selected", "yes");
|
||||
if (hovered) s.attribute("hovered", "yes");
|
||||
|
||||
if (_minimized())
|
||||
s.attribute("style", "unimportant");
|
||||
|
||||
s.sub_scope<Hbox>([&] (Scope<Vbox, Float, Button, Hbox> &s) {
|
||||
s.sub_scope<Vbox>(id, [&] (Scope<Vbox, Float, Button, Hbox, Vbox> &s) {
|
||||
s.sub_scope<Min_ex>(12);
|
||||
s.sub_scope<Label>(id.value, [&] (auto &s) {
|
||||
if (_minimized())
|
||||
s.attribute("font", "annotation/regular");
|
||||
else
|
||||
s.attribute("font", "title/regular");
|
||||
});
|
||||
});
|
||||
|
||||
s.sub_scope<Vbox>([&] (Scope<Vbox, Float, Button, Hbox, Vbox> &s) {
|
||||
s.sub_scope<Min_ex>(12);
|
||||
status_fn(s); });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void click(Clicked_at const &, auto const &fn) { fn(); }
|
||||
|
||||
void view_status(auto &s, auto const &text) const
|
||||
{
|
||||
s.template sub_scope<Label>(text, [&] (auto &s) {
|
||||
if (_minimized())
|
||||
s.attribute("font", "annotation/regular"); });
|
||||
}
|
||||
|
||||
bool selected() const { return _selected_value == value; }
|
||||
};
|
||||
|
||||
#endif /* _VIEW__SECTION_DIALOG_H_ */
|
271
repos/gems/src/app/phone_manager/view/software_add_widget.h
Normal file
271
repos/gems/src/app/phone_manager/view/software_add_widget.h
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* \brief Dialog for adding software components
|
||||
* \author Norman Feske
|
||||
* \date 2023-03-21
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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_ADD_WIDGET_H_
|
||||
#define _VIEW__SOFTWARE_ADD_WIDGET_H_
|
||||
|
||||
#include <model/build_info.h>
|
||||
#include <model/nic_state.h>
|
||||
#include <model/index_update_queue.h>
|
||||
#include <model/index_menu.h>
|
||||
#include <view/depot_users_widget.h>
|
||||
#include <view/index_menu_widget.h>
|
||||
#include <view/index_pkg_widget.h>
|
||||
#include <view/component_add_widget.h>
|
||||
|
||||
namespace Sculpt { struct Software_add_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Software_add_widget : Widget_interface<Vbox>
|
||||
{
|
||||
using Depot_users = Depot_users_widget::Depot_users;
|
||||
using User = Depot_users_widget::User;
|
||||
using Url = Depot_users_widget::Url;
|
||||
using User_properties = Depot_users_widget::User_properties;
|
||||
using Index = Index_menu_widget::Index;
|
||||
using Construction_info = Component::Construction_info;
|
||||
|
||||
Build_info const _build_info;
|
||||
Sculpt_version const _sculpt_version;
|
||||
Nic_state const &_nic_state;
|
||||
Index_update_queue const &_index_update_queue;
|
||||
Download_queue const &_download_queue;
|
||||
Construction_info const &_construction_info;
|
||||
Depot_users const &_depot_users;
|
||||
|
||||
Hosted<Vbox, Frame, Vbox, Depot_users_widget> _users;
|
||||
Hosted<Vbox, Float, Frame, Vbox, Index_menu_widget> _menu;
|
||||
Hosted<Vbox, Float, Frame, Vbox, Component_add_widget> _component_add;
|
||||
|
||||
using Hosted_pkg_widget = Hosted<Vbox, Index_pkg_widget>;
|
||||
|
||||
Hosted_pkg_widget _pkg;
|
||||
|
||||
Path _index_path() const { return Path(_users.selected(), "/index/", _sculpt_version); }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Software_add_widget(Build_info const &build_info,
|
||||
Sculpt_version const &sculpt_version,
|
||||
Nic_state const &nic_state,
|
||||
Index_update_queue const &index_update_queue,
|
||||
Index const &index,
|
||||
Download_queue const &download_queue,
|
||||
Runtime_config const &runtime_config,
|
||||
Construction_info const &construction_info,
|
||||
Depot_users const &depot_users)
|
||||
:
|
||||
_build_info(build_info), _sculpt_version(sculpt_version),
|
||||
_nic_state(nic_state), _index_update_queue(index_update_queue),
|
||||
_download_queue(download_queue), _construction_info(construction_info),
|
||||
_depot_users(depot_users),
|
||||
_users(Id { "users" }, depot_users, _build_info.depot_user),
|
||||
_menu(Id { "menu" }, index),
|
||||
_component_add(Id { "add" }, runtime_config),
|
||||
_pkg(Id { "pkg" })
|
||||
{ }
|
||||
|
||||
struct Index_menu_entry
|
||||
{
|
||||
Download_queue const &download_queue;
|
||||
Construction_info const &construction_info;
|
||||
Hosted_pkg_widget const &pkg;
|
||||
Depot_users_widget const &users;
|
||||
Nic_state const &nic_state;
|
||||
|
||||
using Hosted_entry = Hosted<Vbox, Menu_entry>;
|
||||
|
||||
void view(Scope<Vbox> &s, Id const &id, auto const &text, Path const &pkg_path)
|
||||
{
|
||||
bool pkg_selected = false;
|
||||
bool pkg_installing = false;
|
||||
|
||||
String<100> label { text };
|
||||
|
||||
if (pkg_path.length() > 1) {
|
||||
pkg_installing = download_queue.in_progress(pkg_path);
|
||||
|
||||
construction_info.with_construction([&] (Component const &component) {
|
||||
if (component.path == pkg_path)
|
||||
pkg_selected = true; });
|
||||
|
||||
label = { Pretty(label), "(", Depot::Archive::version(pkg_path), ")",
|
||||
pkg_installing ? " installing... " : "... " };
|
||||
}
|
||||
|
||||
s.widget(Hosted_entry { id }, pkg_selected, label);
|
||||
|
||||
if (pkg_selected && !pkg_installing)
|
||||
construction_info.with_construction([&] (Component const &component) {
|
||||
s.widget(pkg, component, users.selected_user_properties(),
|
||||
nic_state); });
|
||||
}
|
||||
};
|
||||
|
||||
bool _component_add_widget_visible() const
|
||||
{
|
||||
bool result = false;
|
||||
if (_menu.pkg_selected())
|
||||
_construction_info.with_construction([&] (Component const &component) {
|
||||
if (component.blueprint_info.ready_to_deploy())
|
||||
result = true; });
|
||||
return result;
|
||||
}
|
||||
|
||||
Hosted<Vbox, Frame, Vbox, Float, Operation_button> _check { Id { "check" } };
|
||||
|
||||
void view(Scope<Vbox> &s) const
|
||||
{
|
||||
s.sub_scope<Frame>([&] (Scope<Vbox, Frame> &s) {
|
||||
s.sub_scope<Vbox>([&] (Scope<Vbox, Frame, Vbox> &s) {
|
||||
s.widget(_users);
|
||||
|
||||
User_properties const properties = _users.selected_user_properties();
|
||||
|
||||
bool const offer_index_update = _users.one_selected()
|
||||
&& _menu.top_level()
|
||||
&& _nic_state.ready()
|
||||
&& properties.download_url;
|
||||
|
||||
if (!offer_index_update)
|
||||
return;
|
||||
|
||||
s.sub_scope<Small_vgap>();
|
||||
s.sub_scope<Float>([&] (Scope<Vbox, Frame, Vbox, Float> &s) {
|
||||
s.widget(_check, _index_update_in_progress(),
|
||||
properties.public_key ? "Update Index"
|
||||
: "Update unverified Index");
|
||||
});
|
||||
s.sub_scope<Small_vgap>();
|
||||
});
|
||||
});
|
||||
|
||||
if (_users.unfolded())
|
||||
return;
|
||||
|
||||
s.sub_scope<Vgap>();
|
||||
|
||||
User const user = _users.selected();
|
||||
if (!_component_add_widget_visible() && !_menu.anything_visible(user))
|
||||
return;
|
||||
|
||||
s.sub_scope<Float>([&] (Scope<Vbox, Float> &s) {
|
||||
s.sub_scope<Frame>([&] (Scope<Vbox, Float, Frame> &s) {
|
||||
s.sub_scope<Vbox>([&] (Scope<Vbox, Float, Frame, Vbox> &s) {
|
||||
s.sub_scope<Min_ex>(35);
|
||||
|
||||
if (_component_add_widget_visible())
|
||||
_construction_info.with_construction([&] (Component const &component) {
|
||||
s.widget(_component_add, component); });
|
||||
|
||||
else {
|
||||
s.widget(_menu, user,
|
||||
[&] (Scope<Vbox> &s, auto &&... args)
|
||||
{
|
||||
Index_menu_entry entry {
|
||||
.download_queue = _download_queue,
|
||||
.construction_info = _construction_info,
|
||||
.pkg = _pkg,
|
||||
.users = _users,
|
||||
.nic_state = _nic_state };
|
||||
|
||||
entry.view(s, args...);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _reset_menu() { _menu.reset(); }
|
||||
|
||||
bool keyboard_needed() const { return _users.keyboard_needed(); }
|
||||
|
||||
struct Action : virtual Depot_users_widget::Action,
|
||||
virtual Component::Construction_action
|
||||
{
|
||||
virtual void query_index (User const &) = 0;
|
||||
virtual void update_sculpt_index(User const &, Verify) = 0;
|
||||
};
|
||||
|
||||
void click(Clicked_at const &at, Action &action)
|
||||
{
|
||||
_users.propagate(at, action, [&] (User const &selected_user) {
|
||||
action.query_index(selected_user);
|
||||
_reset_menu();
|
||||
});
|
||||
|
||||
Verify const verify { _users.selected_user_properties().public_key };
|
||||
|
||||
if (_component_add_widget_visible()) {
|
||||
_component_add.propagate(at, action,
|
||||
[&] /* leave */ {
|
||||
action.discard_construction();
|
||||
_menu.one_level_back();
|
||||
}
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
_menu.propagate(at, _users.selected(),
|
||||
|
||||
[&] /* enter pkg */ (Xml_node const &item) {
|
||||
|
||||
auto path = item.attribute_value("path", Component::Path());
|
||||
auto info = item.attribute_value("info", Component::Info());
|
||||
|
||||
action.new_construction(path, verify, info);
|
||||
},
|
||||
|
||||
[&] /* leave pkg */ { action.discard_construction(); },
|
||||
|
||||
[&] /* pkg operation */ (Clicked_at const &at) {
|
||||
_pkg.propagate(at); }
|
||||
);
|
||||
}
|
||||
|
||||
if (!_index_update_in_progress())
|
||||
_check.propagate(at, [&] {
|
||||
action.update_sculpt_index(_users.selected(), verify); });
|
||||
}
|
||||
|
||||
void clack(Clacked_at const &at, Action &action)
|
||||
{
|
||||
if (_component_add_widget_visible())
|
||||
_component_add.propagate(at, [&] /* launch */ {
|
||||
action.launch_construction();
|
||||
_reset_menu(); });
|
||||
|
||||
_menu.propagate(at, [&] /* pkg operation */ (Clacked_at const &at) {
|
||||
_pkg.propagate(at, [&] { action.trigger_pkg_download(); }); });
|
||||
}
|
||||
|
||||
void handle_key(Codepoint c, Action &action)
|
||||
{
|
||||
_users.handle_key(c, action);
|
||||
}
|
||||
|
||||
void sanitize_user_selection() { _users.sanitize_unfold_state(); }
|
||||
};
|
||||
|
||||
#endif /* _VIEW__SOFTWARE_ADD_WIDGET_H_ */
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* \brief Widget for the software options
|
||||
* \author Norman Feske
|
||||
* \date 2022-09-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__SOFTWARE_OPTIONS_WIDGET_H_
|
||||
#define _VIEW__SOFTWARE_OPTIONS_WIDGET_H_
|
||||
|
||||
#include <model/launchers.h>
|
||||
#include <view/dialog.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace Sculpt { struct Software_options_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Software_options_widget : Widget<Vbox>
|
||||
{
|
||||
Runtime_info const &_runtime_info;
|
||||
Launchers const &_launchers;
|
||||
|
||||
Software_options_widget(Runtime_info const &runtime_info,
|
||||
Launchers const &launchers)
|
||||
:
|
||||
_runtime_info(runtime_info), _launchers(launchers)
|
||||
{ }
|
||||
|
||||
struct Option : Widget<Frame>
|
||||
{
|
||||
Hosted<Frame, Right_floating_off_on> _switch { Id { "switch" } };
|
||||
|
||||
void view(Scope<Frame> &s, auto const &text, bool enabled) const
|
||||
{
|
||||
s.attribute("style", "important");
|
||||
s.sub_scope<Left_floating_text>(Pretty(text));
|
||||
s.widget(_switch, enabled);
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, auto const &fn) const
|
||||
{
|
||||
_switch.propagate(at, fn);
|
||||
}
|
||||
};
|
||||
|
||||
using Hosted_option = Hosted<Vbox, Option>;
|
||||
|
||||
void view(Scope<Vbox> &s) const
|
||||
{
|
||||
unsigned count = 0;
|
||||
_launchers.for_each([&] (Launchers::Info const &info) {
|
||||
Hosted_option option { { count++ } };
|
||||
s.widget(option,
|
||||
info.path, _runtime_info.present_in_runtime(info.path));
|
||||
});
|
||||
}
|
||||
|
||||
struct Action : Interface
|
||||
{
|
||||
virtual void enable_optional_component (Path const &launcher) = 0;
|
||||
virtual void disable_optional_component(Path const &launcher) = 0;
|
||||
};
|
||||
|
||||
void click(Clicked_at const &at, Action &action) const
|
||||
{
|
||||
Id const clicked_id = at.matching_id<Vbox, Option>();
|
||||
|
||||
unsigned count = 0;
|
||||
_launchers.for_each([&] (Launchers::Info const &info) {
|
||||
Id const id { { count++ } };
|
||||
if (clicked_id != id)
|
||||
return;
|
||||
|
||||
Hosted_option const option { id };
|
||||
option.propagate(at, [&] (bool on) {
|
||||
if (on) action.enable_optional_component(info.path);
|
||||
else action.disable_optional_component(info.path);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _VIEW__SOFTWARE_OPTIONS_WIDGET_H_ */
|
89
repos/gems/src/app/phone_manager/view/software_tabs_widget.h
Normal file
89
repos/gems/src/app/phone_manager/view/software_tabs_widget.h
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* \brief Widget for the software tabs
|
||||
* \author Norman Feske
|
||||
* \date 2022-09-l9
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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__SOFTWARE_TABS_WIDGET_H_
|
||||
#define _VIEW__SOFTWARE_TABS_WIDGET_H_
|
||||
|
||||
#include <view/dialog.h>
|
||||
#include <model/presets.h>
|
||||
|
||||
namespace Sculpt { struct Software_tabs_widget; }
|
||||
|
||||
|
||||
struct Sculpt::Software_tabs_widget : Widget<Frame>
|
||||
{
|
||||
enum class Tab { PRESETS, RUNTIME, ADD, OPTIONS, UPDATE, STATUS };
|
||||
|
||||
Tab _selected = Tab::RUNTIME;
|
||||
|
||||
struct Tab_button : Select_button<Tab>
|
||||
{
|
||||
using Select_button::Select_button;
|
||||
|
||||
void view(Scope<Button> &s, Tab selected_value, bool ready) const
|
||||
{
|
||||
bool const selected = (selected_value == _value),
|
||||
hovered = (s.hovered() && !s.dragged() && !selected && ready);
|
||||
|
||||
if (selected) s.attribute("selected", "yes");
|
||||
if (hovered) s.attribute("hovered", "yes");
|
||||
if (!ready) s.attribute("style", "unimportant");
|
||||
|
||||
s.sub_scope<Label>(s.id.value);
|
||||
}
|
||||
};
|
||||
|
||||
Hosted<Frame, Hbox, Tab_button> const
|
||||
_status { Id { "Status" }, Tab::STATUS },
|
||||
_presets { Id { "Presets" }, Tab::PRESETS },
|
||||
_runtime { Id { "Runtime" }, Tab::RUNTIME },
|
||||
_add { Id { "Add" }, Tab::ADD },
|
||||
_options { Id { "Options" }, Tab::OPTIONS },
|
||||
_update { Id { "Update" }, Tab::UPDATE };
|
||||
|
||||
void view(Scope<Frame> &s,
|
||||
Storage_target const &storage_target,
|
||||
Presets const &presets,
|
||||
bool const status_available) const
|
||||
{
|
||||
s.sub_scope<Hbox>([&] (Scope<Frame, Hbox> &s) {
|
||||
s.widget(_status, _selected, status_available);
|
||||
s.widget(_presets, _selected, storage_target.valid() && presets.available());
|
||||
s.widget(_runtime, _selected, true);
|
||||
s.widget(_add, _selected, storage_target.valid());
|
||||
s.widget(_options, _selected, storage_target.valid());
|
||||
s.widget(_update, _selected, storage_target.valid());
|
||||
});
|
||||
}
|
||||
|
||||
void click(Clicked_at const &at, auto const &fn)
|
||||
{
|
||||
_status .propagate(at, [&] (Tab t) { _selected = t; });
|
||||
_presets.propagate(at, [&] (Tab t) { _selected = t; });
|
||||
_runtime.propagate(at, [&] (Tab t) { _selected = t; });
|
||||
_add .propagate(at, [&] (Tab t) { _selected = t; });
|
||||
_options.propagate(at, [&] (Tab t) { _selected = t; });
|
||||
_update .propagate(at, [&] (Tab t) { _selected = t; });
|
||||
|
||||
fn();
|
||||
}
|
||||
|
||||
bool presets_selected() const { return _selected == Tab::PRESETS; }
|
||||
bool runtime_selected() const { return _selected == Tab::RUNTIME; }
|
||||
bool options_selected() const { return _selected == Tab::OPTIONS; }
|
||||
bool add_selected() const { return _selected == Tab::ADD; }
|
||||
bool update_selected() const { return _selected == Tab::UPDATE; }
|
||||
bool status_selected() const { return _selected == Tab::STATUS; }
|
||||
};
|
||||
|
||||
#endif /* _VIEW__SOFTWARE_TABS_WIDGET_H_ */
|
Loading…
Reference in New Issue
Block a user