diff --git a/repos/gems/src/app/phone_manager/main.cc b/repos/gems/src/app/phone_manager/main.cc index a70cc48ea7..8d74ab0351 100644 --- a/repos/gems/src/app/phone_manager/main.cc +++ b/repos/gems/src/app/phone_manager/main.cc @@ -811,8 +811,8 @@ struct Sculpt::Main : Input_event_handler, Conditional_widget _graph { Id { "graph" }, _runtime_state, _cached_runtime_config, _storage._storage_devices, - _storage._selected_target, _storage._ram_fs_state, - _popup.state, _deploy._children }; + _storage._selected_target, _storage._ram_fs_state, _fb_connectors, + _fb_config, _popup.state, _deploy._children }; Conditional_widget _network_widget { Conditional_widget::Attr { .centered = true }, @@ -1994,6 +1994,30 @@ struct Sculpt::Main : Input_event_handler, } + /********************************** + ** Display driver configuration ** + **********************************/ + + Fb_connectors _fb_connectors { }; + + Fb_config _fb_config { }; + + /** + * Fb_driver::Action interface + */ + void fb_connectors_changed() override { } + + /** + * Fb_widget::Action interface + */ + void select_fb_mode (Fb_connectors::Name const &, + Fb_connectors::Connector::Mode::Id const &) override { } + void disable_fb_connector (Fb_connectors::Name const &) override { } + void toggle_fb_merge_discrete(Fb_connectors::Name const &) override { } + void swap_fb_connector (Fb_connectors::Name const &) override { } + void fb_brightness (Fb_connectors::Name const &, unsigned) override { } + + /******************* ** Runtime graph ** *******************/ diff --git a/repos/gems/src/app/sculpt_manager/driver/fb.h b/repos/gems/src/app/sculpt_manager/driver/fb.h index 6a4ab96542..94c60fe394 100644 --- a/repos/gems/src/app/sculpt_manager/driver/fb.h +++ b/repos/gems/src/app/sculpt_manager/driver/fb.h @@ -14,17 +14,44 @@ #ifndef _DRIVER__FB_H_ #define _DRIVER__FB_H_ +#include + namespace Sculpt { struct Fb_driver; } struct Sculpt::Fb_driver : private Noncopyable { + using Fb_name = String<16>; + + struct Action : Interface + { + virtual void fb_connectors_changed() = 0; + }; + + Env &_env; + Action &_action; + Constructible _intel_gpu { }, _intel_fb { }, _vesa_fb { }, _boot_fb { }, _soc_fb { }; + Constructible> _connectors { }; + + void _handle_connectors(Xml_node const &) { _action.fb_connectors_changed(); } + + Fb_name _fb_name() const + { + return _intel_fb.constructed() ? "intel_fb" + : _vesa_fb .constructed() ? "vesa_fb" + : _boot_fb .constructed() ? "boot_fb" + : _soc_fb .constructed() ? "fb" + : ""; + } + + Fb_driver(Env &env, Action &action) : _env(env), _action(action) { } + void gen_start_nodes(Xml_generator &xml) const { auto gen_capture_route = [&] (Xml_generator &xml) @@ -123,6 +150,8 @@ struct Sculpt::Fb_driver : private Noncopyable bool const use_vesa = !use_intel_fb && !suspending && board_info.detected.vga && !use_boot_fb; + Fb_name const orig_fb_name = _fb_name(); + _intel_gpu.conditional(use_intel_gpu, registry, "intel_gpu", Priority::MULTIMEDIA, Ram_quota { 32*1024*1024 }, Cap_quota { 1400 }); @@ -148,6 +177,12 @@ struct Sculpt::Fb_driver : private Noncopyable Boot_fb::with_mode(platform, [&] (Boot_fb::Mode mode) { _boot_fb.construct(registry, "boot_fb", Priority::MULTIMEDIA, mode.ram_quota(), Cap_quota { 100 }); }); + + if (orig_fb_name != _fb_name()) { + Session_label label { "report -> runtime/", _fb_name(), "/connectors" }; + _connectors.conditional((label.length() > 1), _env, label, + *this, &Fb_driver::_handle_connectors); + } } static bool suspend_supported(Board_info const &board_info) @@ -156,6 +191,12 @@ struct Sculpt::Fb_driver : private Noncopyable return board_info.detected.intel_gfx && !board_info.options.suppress.intel_gpu; } + + void with_connectors(auto const &fn) const + { + if (_connectors.constructed()) + _connectors->with_xml(fn); + } }; #endif /* _DRIVER__FB_H_ */ diff --git a/repos/gems/src/app/sculpt_manager/drivers.cc b/repos/gems/src/app/sculpt_manager/drivers.cc index 58cd1e3b98..352cf777c9 100644 --- a/repos/gems/src/app/sculpt_manager/drivers.cc +++ b/repos/gems/src/app/sculpt_manager/drivers.cc @@ -100,7 +100,7 @@ class Sculpt::Drivers::Instance : Noncopyable, Ps2_driver _ps2_driver { }; Touch_driver _touch_driver { }; - Fb_driver _fb_driver { }; + Fb_driver _fb_driver { _env, _action }; Usb_driver _usb_driver { _env, *this, *this }; Ahci_driver _ahci_driver { _env, *this }; Nvme_driver _nvme_driver { _env, *this }; @@ -168,7 +168,8 @@ class Sculpt::Drivers::Instance : Noncopyable, } void with(With_board_info::Callback const &fn) const { fn(_board_info); } - void with(With_platform_info::Callback const &fn) const { fn(_platform.xml()); } + void with_platform_info(With_xml::Callback const &fn) const { fn(_platform.xml()); } + void with_fb_connectors(With_xml::Callback const &fn) const { _fb_driver.with_connectors(fn); } bool suspend_supported() const { @@ -202,9 +203,10 @@ Sculpt::Drivers::Drivers(Env &env, Children &children, Info const &info, Action _instance(_construct_instance(env, children, info, action)) { } -void Drivers::_with(With_storage_devices::Callback const &fn) const { _instance.with(fn); } -void Drivers::_with(With_board_info::Callback const &fn) const { _instance.with(fn); } -void Drivers::_with(With_platform_info::Callback const &fn) const { _instance.with(fn); } +void Drivers::_with(With_storage_devices::Callback const &fn) const { _instance.with(fn); } +void Drivers::_with(With_board_info::Callback const &fn) const { _instance.with(fn); } +void Drivers::_with_platform_info(With_xml::Callback const &fn) const { _instance.with_platform_info(fn); } +void Drivers::_with_fb_connectors(With_xml::Callback const &fn) const { _instance.with_fb_connectors(fn); } void Drivers::update_usb () { _instance.update_usb(); } void Drivers::update_soc (Board_info::Soc soc) { _instance.update_soc(soc); } diff --git a/repos/gems/src/app/sculpt_manager/drivers.h b/repos/gems/src/app/sculpt_manager/drivers.h index 09bea3fa25..17b2853a42 100644 --- a/repos/gems/src/app/sculpt_manager/drivers.h +++ b/repos/gems/src/app/sculpt_manager/drivers.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace Sculpt { struct Drivers; } @@ -26,7 +27,7 @@ class Sculpt::Drivers : Noncopyable { public: - struct Action : Interface + struct Action : virtual Fb_driver::Action { virtual void handle_device_plug_unplug() = 0; }; @@ -53,11 +54,12 @@ class Sculpt::Drivers : Noncopyable using With_storage_devices = With; using With_board_info = With; - using With_platform_info = With; + using With_xml = With; void _with(With_storage_devices::Callback const &) const; void _with(With_board_info::Callback const &) const; - void _with(With_platform_info::Callback const &) const; + void _with_platform_info(With_xml::Callback const &) const; + void _with_fb_connectors(With_xml::Callback const &) const; public: @@ -71,7 +73,8 @@ class Sculpt::Drivers : Noncopyable void with_storage_devices(auto const &fn) const { _with(With_storage_devices::Fn { fn }); } void with_board_info (auto const &fn) const { _with(With_board_info::Fn { fn }); } - void with_platform_info (auto const &fn) const { _with(With_platform_info::Fn { fn }); } + void with_platform_info (auto const &fn) const { _with_platform_info(With_xml::Fn { fn }); } + void with_fb_connectors (auto const &fn) const { _with_fb_connectors(With_xml::Fn { fn }); } /* true if hardware is suspend/resume capable */ bool suspend_supported() const; diff --git a/repos/gems/src/app/sculpt_manager/graph.cc b/repos/gems/src/app/sculpt_manager/graph.cc index d90f52a24a..c9c86fa674 100644 --- a/repos/gems/src/app/sculpt_manager/graph.cc +++ b/repos/gems/src/app/sculpt_manager/graph.cc @@ -100,6 +100,9 @@ void Graph::_view_selected_node_content(Scope &s, if (name == "ram_fs") s.widget(_ram_fs_widget, _selected_target, _ram_fs_state); + if (name == "intel_fb") + s.widget(_fb_widget, _fb_connectors, _fb_config); + String<100> const ram (Capacity{info.assigned_ram - info.avail_ram}, " / ", Capacity{info.assigned_ram}), @@ -257,6 +260,7 @@ void Graph::click(Clicked_at const &at, Action &action) }); _ram_fs_widget .propagate(at, _selected_target, action); + _fb_widget .propagate(at, _fb_connectors, action); _ahci_devices_widget.propagate(at, action); _nvme_devices_widget.propagate(at, action); _mmc_devices_widget .propagate(at, action); diff --git a/repos/gems/src/app/sculpt_manager/graph.h b/repos/gems/src/app/sculpt_manager/graph.h index d9a850e84a..5bd5c6cb33 100644 --- a/repos/gems/src/app/sculpt_manager/graph.h +++ b/repos/gems/src/app/sculpt_manager/graph.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,8 @@ struct Sculpt::Graph : Widget Storage_devices const &_storage_devices; Storage_target const &_selected_target; Ram_fs_state const &_ram_fs_state; + Fb_connectors const &_fb_connectors; + Fb_config const &_fb_config; Popup::State const &_popup_state; Depot_deploy::Children const &_deploy_children; @@ -50,6 +53,9 @@ struct Sculpt::Graph : Widget Hosted _ram_fs_widget { Id { "ram_fs" } }; + Hosted + _fb_widget { Id { "fb" } }; + Hosted _remove { Id { "Remove" } }, _restart { Id { "Restart" } }; @@ -81,18 +87,22 @@ struct Sculpt::Graph : Widget Storage_devices const &storage_devices, Storage_target const &selected_target, Ram_fs_state const &ram_fs_state, + Fb_connectors const &fb_connectors, + Fb_config const &fb_config, Popup::State const &popup_state, Depot_deploy::Children const &deploy_children) : _runtime_state(runtime_state), _runtime_config(runtime_config), _storage_devices(storage_devices), _selected_target(selected_target), - _ram_fs_state(ram_fs_state), _popup_state(popup_state), + _ram_fs_state(ram_fs_state), _fb_connectors(fb_connectors), + _fb_config(fb_config), _popup_state(popup_state), _deploy_children(deploy_children) { } void view(Scope &) const; - struct Action : virtual Storage_device_widget::Action + struct Action : virtual Storage_device_widget::Action, + virtual Fb_widget::Action { virtual void remove_deployed_component(Start_name const &) = 0; virtual void restart_deployed_component(Start_name const &) = 0; diff --git a/repos/gems/src/app/sculpt_manager/main.cc b/repos/gems/src/app/sculpt_manager/main.cc index f32d48b67a..35d453d990 100644 --- a/repos/gems/src/app/sculpt_manager/main.cc +++ b/repos/gems/src/app/sculpt_manager/main.cc @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -1561,18 +1562,6 @@ struct Sculpt::Main : Input_event_handler, _cached_runtime_config, _file_browser_state, *this }; - Managed_config
_fb_config { - _env, "config", "fb", *this, &Main::_handle_fb_config }; - - void _handle_fb_config(Xml_node const &node) - { - _fb_config.generate([&] (Xml_generator &xml) { - xml.attribute("system", "yes"); - copy_attributes(xml, node); - node.for_each_sub_node([&] (Xml_node const &sub_node) { - copy_node(xml, sub_node, { 5 }); }); }); - } - void _update_window_layout(Xml_node const &, Xml_node const &); void _update_window_layout() @@ -1616,6 +1605,96 @@ struct Sculpt::Main : Input_event_handler, Signal_handler
_wheel_handler { _env.ep(), *this, &Main::_update_window_layout }; + /********************************** + ** Display driver configuration ** + **********************************/ + + Fb_connectors _fb_connectors { }; + + Rom_handler
_manual_fb_handler { _env, "config -> fb", *this, &Main::_handle_manual_fb }; + + Expanding_reporter _managed_fb_reporter { _env, "config", "fb_config"}; + + Fb_config _fb_config { }; + + void _generate_fb_config() + { + _managed_fb_reporter.generate([&] (Xml_generator &xml) { + _fb_config.generate_managed_fb(xml); }); + } + + void _handle_manual_fb(Xml_node const &node) + { + log("_handle_manual_fb: ", node); + + _fb_config = { }; + _fb_config.import_manual_config(node); + _fb_config.apply_connectors(_fb_connectors); + + _generate_fb_config(); + } + + /** + * Fb_driver::Action interface + */ + void fb_connectors_changed() override + { + _drivers.with_fb_connectors([&] (Xml_node const &node) { + if (_fb_connectors.update(_heap, node).progress) { + _fb_config.apply_connectors(_fb_connectors); + _generate_fb_config(); + } + }); + _graph_view.refresh(); + } + + /** + * Fb_widget::Action interface + */ + void select_fb_mode(Fb_connectors::Name const &conn, + Fb_connectors::Connector::Mode::Id const &mode) override + { + _fb_config.select_fb_mode(conn, mode, _fb_connectors); + _generate_fb_config(); + } + + /** + * Fb_widget::Action interface + */ + void disable_fb_connector(Fb_connectors::Name const &conn) override + { + _fb_config.disable_connector(conn); + _generate_fb_config(); + } + + /** + * Fb_widget::Action interface + */ + void toggle_fb_merge_discrete(Fb_connectors::Name const &conn) override + { + _fb_config.toggle_merge_discrete(conn); + _generate_fb_config(); + } + + /** + * Fb_widget::Action interface + */ + void swap_fb_connector(Fb_connectors::Name const &conn) override + { + _fb_config.swap_connector(conn); + _generate_fb_config(); + } + + /** + * Fb_widget::Action interface + */ + void fb_brightness(Fb_connectors::Name const &conn, unsigned percent) override + { + _fb_config.brightness(conn, percent); + _generate_fb_config(); + } + + /******************* ** Runtime graph ** *******************/ @@ -1623,8 +1702,8 @@ struct Sculpt::Main : Input_event_handler, Popup _popup { }; Graph _graph { _runtime_state, _cached_runtime_config, _storage._storage_devices, - _storage._selected_target, _storage._ram_fs_state, - _popup.state, _deploy._children }; + _storage._selected_target, _storage._ram_fs_state, _fb_connectors, + _fb_config, _popup.state, _deploy._children }; struct Graph_dialog : Dialog::Top_level_dialog { @@ -1655,7 +1734,6 @@ struct Sculpt::Main : Input_event_handler, _gui.input.sigh(_input_handler); _gui.info_sigh(_gui_mode_handler); _handle_gui_mode(); - _fb_config.trigger_update(); /* * Generate initial configurations diff --git a/repos/gems/src/app/sculpt_manager/model/fb_config.h b/repos/gems/src/app/sculpt_manager/model/fb_config.h new file mode 100644 index 0000000000..ce8f249b95 --- /dev/null +++ b/repos/gems/src/app/sculpt_manager/model/fb_config.h @@ -0,0 +1,401 @@ +/* + * \brief Model for the framebuffer driver configuration + * \author Norman Feske + * \date 2024-10-23 + */ + +/* + * Copyright (C) 2024 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _MODEL__FB_CONFIG_H_ +#define _MODEL__FB_CONFIG_H_ + +#include + +namespace Sculpt { struct Fb_config; }; + + +struct Sculpt::Fb_config +{ + struct Entry + { + using Name = Fb_connectors::Name; + using Mode_id = Fb_connectors::Connector::Mode::Id; + using Mode_attr = Fb_connectors::Connector::Mode::Attr; + using Brightness = Fb_connectors::Brightness; + + bool defined; + bool present; /* false if imported from config but yet unused */ + Name name; + Mode_id mode_id; + Mode_attr mode_attr; + Brightness brightness; + + static Entry from_connector(Fb_connectors::Connector const &connector) + { + Mode_attr mode_attr { }; + Mode_id mode_id { }; + connector.with_used_mode([&] (Fb_connectors::Connector::Mode const &mode) { + mode_attr = mode.attr; + mode_id = mode.id; }); + + return { .defined = true, + .present = true, + .name = connector.name, + .mode_id = mode_id, + .mode_attr = mode_attr, + .brightness = connector.brightness }; + } + + static Entry from_manual_xml(Xml_node const &node) + { + return { .defined = true, + .present = false, + .name = node.attribute_value("name", Name()), + .mode_id = node.attribute_value("mode", Mode_id()), + .mode_attr = Mode_attr::from_xml(node), + .brightness = Brightness::from_xml(node) }; + } + + void generate(Xml_generator &xml) const + { + if (!defined) + return; + + xml.node("connector", [&] { + xml.attribute("name", name); + if (mode_attr.px.valid()) { + xml.attribute("width", mode_attr.px.w); + xml.attribute("height", mode_attr.px.h); + if (mode_attr.hz) + xml.attribute("hz", mode_attr.hz); + if (brightness.defined) + xml.attribute("brightness", brightness.percent); + if (mode_id.length() > 1) + xml.attribute("mode", mode_id); + } else { + xml.attribute("enabled", "no"); + } + }); + } + + bool smaller_than(Entry const &other) const + { + return mode_attr.px.count() < other.mode_attr.px.count(); + } + }; + + static constexpr unsigned MAX_ENTRIES = 16; + Entry _entries[MAX_ENTRIES]; + + struct Manual_attr + { + Area max_px; /* upper bound of framebuffer allocation */ + Area px; /* for vesa_fb */ + + static Manual_attr from_xml(Xml_node const &node) + { + return { .max_px = { .w = node.attribute_value("max_width", 0u), + .h = node.attribute_value("max_height", 0u) }, + .px = Area::from_xml(node) }; + } + + void generate(Xml_generator &xml) const + { + if (max_px.w) xml.attribute("max_width", max_px.w); + if (max_px.h) xml.attribute("max_height", max_px.h); + if (px.w) xml.attribute("width", px.w); + if (px.h) xml.attribute("height", px.h); + } + }; + + Manual_attr _manual_attr { }; + + unsigned _num_merged = 0; + + bool _known(Fb_connectors::Connector const &connector) + { + for (Entry const &entry : _entries) + if (entry.name == connector.name) + return true; + return false; + } + + void _with_known(Fb_connectors::Connector const &connector, auto const &fn) + { + for (Entry &entry : _entries) + if (entry.name == connector.name) + fn(entry); + } + + void _insert_at(unsigned at, Entry const &entry) + { + if (at >= MAX_ENTRIES) { + warning("maximum number of ", MAX_ENTRIES, " fb config entries exeeded"); + return; + } + + for (unsigned i = MAX_ENTRIES - 1; i > at; i--) + _entries[i] = _entries[i - 1]; + + _entries[at] = entry; + } + + /* + * A new merged connector such that the smallest mode stays in front + */ + void _add_unknown_merged(Entry const &new_entry) + { + unsigned at = 0; + for (; at < _num_merged && _entries[at].smaller_than(new_entry); at++); + + _insert_at(at, new_entry); + + if (_num_merged < MAX_ENTRIES) + _num_merged++; + } + + void _add_unknown_discrete(Entry const &new_entry) + { + unsigned at = 0; + for (; at < MAX_ENTRIES && _entries[at].defined; at++); + + _insert_at(at, new_entry); + } + + void import_manual_config(Xml_node const &config) + { + _manual_attr = Manual_attr::from_xml(config); + + unsigned count = 0; + + auto add_connectors = [&] (Xml_node const &node) + { + node.for_each_sub_node("connector", [&] (Xml_node const &node) { + Entry const e = Entry::from_manual_xml(node); + if (!_known(e.name) && count < MAX_ENTRIES) { + _entries[count] = e; + count++; + } + }); + }; + + /* import merged nodes */ + config.with_optional_sub_node("merge", [&] (Xml_node const &merge) { + add_connectors(merge); }); + + _num_merged = count; + + /* import discrete nodes */ + add_connectors(config); + } + + void apply_connectors(Fb_connectors const &connectors) + { + /* apply information for connectors known from the manual config */ + connectors.for_each([&] (Fb_connectors::Connector const &conn) { + _with_known(conn.name, [&] (Entry &e) { + + if (e.present) /* apply config only once */ + return; + + if (!e.mode_attr.px.valid()) { /* switched off by config */ + e.mode_id = { }; + e.mode_attr = { }; + e.present = true; + return; + } + + conn.with_matching_mode(e.mode_id, e.mode_attr, + [&] (Fb_connectors::Connector::Mode const &mode) { + e.mode_id = mode.id; + e.mode_attr = mode.attr; + e.present = true; }); + }); + }); + + connectors._merged.for_each([&] (Fb_connectors::Connector const &conn) { + if (!_known(conn.name)) + _add_unknown_merged(Entry::from_connector(conn)); }); + + connectors._discrete.for_each([&] (Fb_connectors::Connector const &conn) { + if (!_known(conn.name)) + _add_unknown_discrete(Entry::from_connector(conn)); }); + } + + void _with_entry(Entry::Name const &name, auto const &fn) + { + for (Entry &entry : _entries) + if (entry.name == name) + fn(entry); + } + + void select_fb_mode(Fb_connectors::Name const &conn, + Fb_connectors::Connector::Mode::Id const &mode_id, + Fb_connectors const &connectors) + { + connectors.with_mode_attr(conn, mode_id, [&] (Entry::Mode_attr const &attr) { + _with_entry(conn, [&] (Entry &entry) { + entry.mode_attr = attr; + entry.mode_id = mode_id; }); }); + } + + void disable_connector(Fb_connectors::Name const &conn) + { + _with_entry(conn, [&] (Entry &entry) { + entry.mode_attr = { }; }); + } + + void brightness(Fb_connectors::Name const &conn, unsigned percent) + { + _with_entry(conn, [&] (Entry &entry) { + entry.brightness.percent = percent; }); + } + + void _with_idx(Fb_connectors::Name const &conn, auto const &fn) const + { + for (unsigned i = 0; i < MAX_ENTRIES; i++) + if (_entries[i].name == conn && _entries[i].defined) { + fn(i); + return; + } + } + + void _swap_entries(unsigned i, unsigned j) + { + Entry tmp = _entries[i]; + _entries[i] = _entries[j]; + _entries[j] = tmp; + } + + /** + * Swap connector with next present predecessor + */ + void swap_connector(Fb_connectors::Name const &conn) + { + _with_idx(conn, [&] (unsigned const conn_idx) { + + if (conn_idx < 1) /* first entry cannot have a predecessor */ + return; + + /* search present predecessor */ + unsigned prev_idx = conn_idx - 1; + while (prev_idx > 0 && !_entries[prev_idx].present) + prev_idx--; + + _swap_entries(conn_idx, prev_idx); + }); + } + + void toggle_merge_discrete(Fb_connectors::Name const &conn) + { + _with_idx(conn, [&] (unsigned const idx) { + + if (idx < _num_merged) { + + /* + * Turn merged entry into discrete entry. + * + * There may be (non-present) merge entries following idx. + * Bubble up the entry so that it becomes the last merge + * entry before turning it into the first discrete entry by + * decreasing '_num_merged'. + */ + if (_num_merged > 0) { + for (unsigned i = idx; i < _num_merged - 1; i++) + _swap_entries(i, i + 1); + _num_merged--; + } + } else { + + /* + * Turn discrete entry into merged entry + */ + if (_num_merged < MAX_ENTRIES) { + for (unsigned i = idx; i > _num_merged; i--) + _swap_entries(i, i - 1); + _num_merged++; + } + } + }); + } + + struct Merge_info { Entry::Name name; Area px; }; + + void _with_merge_info(auto const &fn) const + { + Merge_info info { }; + + /* merged screen size and name corresponds to first enabled connector */ + for (unsigned i = 0; i < _num_merged; i++) { + info = { .name = _entries[i].name, + .px = _entries[i].mode_attr.px }; + if (info.px.valid() && _entries[i].present) + break; + } + + /* if all merged connectors are switched of, use name of first one */ + if (!info.px.valid()) { + for (unsigned i = 0; i < _num_merged; i++) { + info = { .name = _entries[i].name, + .px = _entries[i].mode_attr.px }; + if (_entries[i].present) + break; + } + } + + if (info.name.length() > 1) + fn(info); + }; + + void _gen_merge_node(Xml_generator &xml) const + { + _with_merge_info([&] (Merge_info const &info) { + xml.node("merge", [&] { + xml.attribute("width", info.px.w); + xml.attribute("height", info.px.h); + xml.attribute("name", info.name); + + for (unsigned i = 0; i < _num_merged; i++) + _entries[i].generate(xml); + }); + }); + } + + void generate_managed_fb(Xml_generator &xml) const + { + _manual_attr.generate(xml); + + xml.attribute("system", "yes"); /* for screen blanking on suspend */ + + xml.node("report", [&] { xml.attribute("connectors", "yes"); }); + + _gen_merge_node(xml); + + /* nodes for discrete connectors */ + for (unsigned i = _num_merged; i < MAX_ENTRIES; i++) + _entries[i].generate(xml); + } + + void for_each_present_connector(Fb_connectors const &connectors, auto const &fn) const + { + for (Entry const &entry : _entries) + if (entry.defined && entry.present) + connectors.with_connector(entry.name, fn); + } + + unsigned num_present_merged() const + { + unsigned count = 0; + for (unsigned i = 0; i < _num_merged; i++) + if (_entries[i].defined && _entries[i].present) + count++; + return count; + } +}; + +#endif /* _MODEL__FB_CONFIG_H_ */ diff --git a/repos/gems/src/app/sculpt_manager/model/fb_connectors.h b/repos/gems/src/app/sculpt_manager/model/fb_connectors.h new file mode 100644 index 0000000000..ed8b3a0081 --- /dev/null +++ b/repos/gems/src/app/sculpt_manager/model/fb_connectors.h @@ -0,0 +1,278 @@ +/* + * \brief Representation of connectors reported by the framebuffer driver + * \author Norman Feske + * \date 2024-10-23 + */ + +/* + * Copyright (C) 2024 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _MODEL__FB_CONNECTORS_H_ +#define _MODEL__FB_CONNECTORS_H_ + +#include + +namespace Sculpt { struct Fb_connectors; }; + + +struct Sculpt::Fb_connectors : private Noncopyable +{ + using Area = Gui::Area; + using Name = String<16>; + + struct Connector; + using Connectors = List_model; + + struct Brightness + { + bool defined; + unsigned percent; + + bool operator != (Brightness const &other) const + { + return defined != other.defined || percent != other.percent; + } + + static Brightness from_xml(Xml_node const &node) + { + return { .defined = node.has_attribute("brightness"), + .percent = node.attribute_value("brightness", 0u) }; + } + }; + + struct Connector : Connectors::Element + { + Name const name; + Area mm { }; + Brightness brightness { }; + + struct Mode; + using Modes = List_model; + + Modes _modes { }; + + struct Mode : Modes::Element + { + using Id = String<16>; + + Id const id; + + struct Attr + { + Name name; + Area px; + Area mm; + bool used; + unsigned hz; + + bool operator != (Attr const &other) const + { + return name != other.name + || px != other.px + || mm != other.mm + || used != other.used + || hz != other.hz; + } + + static Attr from_xml(Xml_node const &node) + { + return { + .name = node.attribute_value("name", Name()), + .px = { .w = node.attribute_value("width", 0u), + .h = node.attribute_value("height", 0u) }, + .mm = { .w = node.attribute_value("width_mm", 0u), + .h = node.attribute_value("height_mm", 0u) }, + .used = node.attribute_value("used", false), + .hz = node.attribute_value("hz", 0u) + }; + } + }; + + Attr attr { }; + + Mode(Id id) : id(id) { }; + + static Id id_from_xml(Xml_node const &node) + { + return node.attribute_value("id", Mode::Id()); + } + + bool matches(Xml_node const &node) const + { + return id_from_xml(node) == id; + } + + static bool type_matches(Xml_node const &node) + { + return node.has_type("mode"); + } + + bool has_resolution(Area px) const { return attr.px == px; } + bool has_hz (unsigned hz) const { return attr.hz == hz; } + }; + + Connector(Name const &name) : name(name) { } + + void _with_mode(auto const &match_fn, auto const &fn) const + { + bool found = false; + _modes.for_each([&] (Mode const &mode) { + if (!found && match_fn(mode.attr)) { + fn(mode); + found = true; } }); + } + + void with_used_mode(auto const &fn) const + { + _with_mode([&] (Mode::Attr const &attr) { return attr.used; }, fn); + } + + void with_matching_mode(Mode::Id const &preferred_id, + Mode::Attr const &attr, auto const &fn) const + { + auto matches_resolution_and_id = [&] (Mode const &mode) + { + return mode.has_resolution(attr.px) && (mode.id == preferred_id); + }; + + auto matches_resolution_and_hz = [&] (Mode const &mode) + { + return mode.has_resolution(attr.px) && mode.has_hz(attr.hz); + }; + + auto matches_resolution = [&] (Mode const &mode) + { + return mode.has_resolution(attr.px); + }; + + bool matched = false; + auto with_match_once = [&] (auto const &matches_fn, auto const &fn) + { + if (!matched) + _modes.for_each([&] (Mode const &mode) { + if (matches_fn(mode)) { + fn(mode); + matched = true; } }); + }; + + with_match_once(matches_resolution_and_id, fn); + with_match_once(matches_resolution_and_hz, fn); + with_match_once(matches_resolution, fn); + + if (!matched) + with_used_mode([&] (Mode const &used) { + fn(used); + matched = true; }); + } + + bool update(Allocator &alloc, Xml_node const &node) + { + Area const orig_mm = mm; + Brightness const orig_brightness = brightness; + + mm.w = node.attribute_value("width_mm", 0u); + mm.h = node.attribute_value("height_mm", 0u); + brightness = Brightness::from_xml(node); + + bool progress = (orig_mm != mm || orig_brightness != brightness); + + _modes.update_from_xml(node, + [&] (Xml_node const &node) -> Mode & { + progress = true; + return *new (alloc) Mode(Mode::id_from_xml(node)); + }, + [&] (Mode &mode) { + progress = true; + destroy(alloc, &mode); + }, + [&] (Mode &mode, Xml_node const &node) { + Mode::Attr const orig_attr = mode.attr; + mode.attr = Mode::Attr::from_xml(node); + progress |= (orig_attr != mode.attr); + } + ); + + return progress; + } + + bool matches(Xml_node const &node) const + { + return node.attribute_value("name", Name()) == name; + } + + static bool type_matches(Xml_node const &node) + { + return node.has_type("connector") + && node.attribute_value("connected", false); + } + }; + + Connectors _merged { }; + Connectors _discrete { }; + + [[nodiscard]] Progress update(Allocator &alloc, Xml_node const &connectors) + { + bool progress = false; + + auto update = [&] (Connectors &model, Xml_node const &node) + { + model.update_from_xml(node, + [&] (Xml_node const &node) -> Connector & { + progress = true; + return *new (alloc) Connector(node.attribute_value("name", Name())); + }, + [&] (Connector &conn) { + progress = true; + conn.update(alloc, Xml_node("")); + destroy(alloc, &conn); + }, + [&] (Connector &conn, Xml_node const &node) { + progress |= conn.update(alloc, node); + }); + }; + + update(_discrete, connectors); + + connectors.with_sub_node("merge", + [&] (Xml_node const &merge) { update(_merged, merge); }, + [&] { update(_merged, Xml_node("")); }); + + return { progress }; + } + + static unsigned _count(Connectors const &connectors) + { + unsigned count = 0; + connectors.for_each([&] (Connector const &) { count++; }); + return count; + } + + unsigned num_merged() const { return _count(_merged); } + + void for_each(auto const &fn) const + { + _merged .for_each(fn); + _discrete.for_each(fn); + } + + void with_connector(Name const &conn_name, auto const &fn) const + { + for_each([&] (Connector const &connector) { + if (connector.name == conn_name) + fn(connector); }); + } + + void with_mode_attr(Name const &conn_name, Connector::Mode::Id const &id, auto const &fn) const + { + with_connector(conn_name, [&] (Connector const &connector) { + connector._modes.for_each([&] (Connector::Mode const &mode) { + if (mode.id == id) + fn(mode.attr); }); }); + } +}; + +#endif /* _MODEL__FB_CONNECTORS_H_ */ diff --git a/repos/gems/src/app/sculpt_manager/view/fb_widget.h b/repos/gems/src/app/sculpt_manager/view/fb_widget.h new file mode 100644 index 0000000000..6b1de2815f --- /dev/null +++ b/repos/gems/src/app/sculpt_manager/view/fb_widget.h @@ -0,0 +1,187 @@ +/* + * \brief Framebuffer settings + * \author Norman Feske + * \date 2024-10-23 + */ + +/* + * Copyright (C) 2024 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _VIEW__FB_WIDGET_H_ +#define _VIEW__FB_WIDGET_H_ + +#include +#include + +namespace Sculpt { struct Fb_widget; } + +struct Sculpt::Fb_widget : Widget +{ + using Connector = Fb_connectors::Connector; + using Mode = Connector::Mode; + using Hosted_choice = Hosted>; + using Mode_radio = Hosted>; + + struct Action : Interface + { + virtual void select_fb_mode(Fb_connectors::Name const &, Mode::Id const &) = 0; + virtual void disable_fb_connector(Fb_connectors::Name const &) = 0; + virtual void toggle_fb_merge_discrete(Fb_connectors::Name const &) = 0; + virtual void swap_fb_connector(Fb_connectors::Name const &) = 0; + virtual void fb_brightness(Fb_connectors::Name const &, unsigned) = 0; + }; + + Fb_connectors::Name _selected_connector { }; + + struct Bar : Widget + { + void view(Scope &s, unsigned const percent) const + { + for (unsigned i = 0; i < 10; i++) { + s.sub_scope