diff --git a/repos/gems/recipes/raw/drivers_managed-pc/drivers.config b/repos/gems/recipes/raw/drivers_managed-pc/drivers.config index 27997ac861..10f5355aac 100644 --- a/repos/gems/recipes/raw/drivers_managed-pc/drivers.config +++ b/repos/gems/recipes/raw/drivers_managed-pc/drivers.config @@ -209,7 +209,7 @@ - + diff --git a/repos/gems/run/sculpt.run b/repos/gems/run/sculpt.run index 04d66f8075..c64c0af3c4 100644 --- a/repos/gems/run/sculpt.run +++ b/repos/gems/run/sculpt.run @@ -105,7 +105,6 @@ install_config { - diff --git a/repos/gems/src/app/sculpt_manager/main.cc b/repos/gems/src/app/sculpt_manager/main.cc index 6e5ad0547e..e3afd47163 100644 --- a/repos/gems/src/app/sculpt_manager/main.cc +++ b/repos/gems/src/app/sculpt_manager/main.cc @@ -109,14 +109,42 @@ struct Sculpt::Main : Input_event_handler, _font_size_px = px; }); } }); } }); }); _handle_gui_mode(); + + /* visibility of fonts section of settings dialog may have changed */ + _settings_menu_view.generate(); + + /* visibility of settings button may have changed */ + _refresh_panel_and_window_layout(); } Managed_config
_event_filter_config { _env, "config", "event_filter", *this, &Main::_handle_event_filter_config }; + void _generate_event_filter_config(Xml_generator &); + void _handle_event_filter_config(Xml_node) { - _event_filter_config.try_generate_manually_managed(); + _update_event_filter_config(); + } + + void _update_event_filter_config() + { + bool const orig_settings_available = _settings.interactive_settings_available(); + + _settings.manual_event_filter_config = + _event_filter_config.try_generate_manually_managed(); + + if (!_settings.manual_event_filter_config) + _event_filter_config.generate([&] (Xml_generator &xml) { + _generate_event_filter_config(xml); }); + + _settings_menu_view.generate(); + + /* visibility of the settings dialog may have changed */ + if (orig_settings_available != _settings.interactive_settings_available()) { + _refresh_panel_and_window_layout(); + _handle_gui_mode(); + } } @@ -347,7 +375,7 @@ struct Sculpt::Main : Input_event_handler, ** Global ** ************/ - Font_size _font_size = Font_size::MEDIUM; + Settings _settings { }; float _font_size_px = 14; @@ -378,6 +406,8 @@ struct Sculpt::Main : Input_event_handler, Panel_dialog::Tab selected_tab() const override { return _selected_tab; } + bool settings_available() const override { return _settings.interactive_settings_available(); } + /** * Dialog interface */ @@ -734,12 +764,28 @@ struct Sculpt::Main : Input_event_handler, /* * Settings_dialog::Action interface */ - void select_font_size(Font_size font_size) override + void select_font_size(Settings::Font_size font_size) override { - _font_size = font_size; + if (_settings.font_size == font_size) + return; + + _settings.font_size = font_size; _handle_gui_mode(); } + /* + * Settings_dialog::Action interface + */ + void select_keyboard_layout(Settings::Keyboard_layout::Name const &keyboard_layout) override + { + if (_settings.keyboard_layout == keyboard_layout) + return; + + _settings.keyboard_layout = keyboard_layout; + + _update_event_filter_config(); + } + Signal_handler
_fs_query_result_handler { _env.ep(), *this, &Main::_handle_fs_query_result }; @@ -974,7 +1020,7 @@ struct Sculpt::Main : Input_event_handler, Ram_quota{4*1024*1024}, Cap_quota{150}, "panel_dialog", "panel_view_hover" }; - Settings_dialog _settings_dialog { _font_size }; + Settings_dialog _settings_dialog { _settings }; Menu_view _settings_menu_view { _env, _child_states, _settings_dialog, "settings_view", Ram_quota{4*1024*1024}, Cap_quota{150}, @@ -1073,6 +1119,7 @@ struct Sculpt::Main : Input_event_handler, * Generate initial configurations */ _network.wifi_disconnect(); + _update_event_filter_config(); /* * Import initial report content @@ -1219,7 +1266,9 @@ void Sculpt::Main::_handle_window_layout() Point const pos = _settings_visible ? Point(0, avail.y1()) : Point(-size.w(), avail.y1()); - gen_window(win, Rect(pos, size)); + + if (_settings.interactive_settings_available()) + gen_window(win, Rect(pos, size)); }); _with_window(window_list, network_view_label, [&] (Xml_node win) { @@ -1330,7 +1379,9 @@ void Sculpt::Main::_handle_gui_mode() _handle_window_layout(); - if (!_fonts_config.try_generate_manually_managed()) { + _settings.manual_fonts_config = _fonts_config.try_generate_manually_managed(); + + if (!_settings.manual_fonts_config) { _font_size_px = (float)mode.area.h() / 60.0; @@ -1340,8 +1391,8 @@ void Sculpt::Main::_handle_gui_mode() */ _font_size_px = max(_font_size_px, 2.0); - if (_font_size == Font_size::SMALL) _font_size_px *= 0.85; - if (_font_size == Font_size::LARGE) _font_size_px *= 1.35; + if (_settings.font_size == Settings::Font_size::SMALL) _font_size_px *= 0.85; + if (_settings.font_size == Settings::Font_size::LARGE) _font_size_px *= 1.35; _fonts_config.generate([&] (Xml_generator &xml) { xml.attribute("copy", true); @@ -1743,6 +1794,97 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const } +void Sculpt::Main::_generate_event_filter_config(Xml_generator &xml) +{ + auto gen_include = [&] (auto rom) { + xml.node("include", [&] () { + xml.attribute("rom", rom); }); }; + + xml.node("output", [&] () { + xml.node("chargen", [&] () { + xml.node("remap", [&] () { + + auto gen_key = [&] (auto from, auto to) { + xml.node("key", [&] () { + xml.attribute("name", from); + xml.attribute("to", to); }); }; + + gen_key("KEY_CAPSLOCK", "KEY_CAPSLOCK"); + gen_key("KEY_F12", "KEY_DASHBOARD"); + gen_key("KEY_LEFTMETA", "KEY_SCREEN"); + gen_include("numlock.remap"); + + xml.node("merge", [&] () { + + auto gen_input = [&] (auto name) { + xml.node("input", [&] () { + xml.attribute("name", name); }); }; + + xml.node("accelerate", [&] () { + xml.attribute("max", 50); + xml.attribute("sensitivity_percent", 1000); + xml.attribute("curve", 127); + + xml.node("button-scroll", [&] () { + gen_input("ps2"); + + xml.node("vertical", [&] () { + xml.attribute("button", "BTN_MIDDLE"); + xml.attribute("speed_percent", -10); }); + + xml.node("horizontal", [&] () { + xml.attribute("button", "BTN_MIDDLE"); + xml.attribute("speed_percent", -10); }); + }); + }); + + gen_input("usb"); + gen_input("touch"); + }); + }); + + auto gen_key = [&] (auto key) { + gen_named_node(xml, "key", key, [&] () {}); }; + + xml.node("mod1", [&] () { + gen_key("KEY_LEFTSHIFT"); + gen_key("KEY_RIGHTSHIFT"); }); + + xml.node("mod2", [&] () { + gen_key("KEY_LEFTCTRL"); + gen_key("KEY_RIGHTCTRL"); }); + + xml.node("mod3", [&] () { + gen_key("KEY_RIGHTALT"); /* AltGr */ }); + + xml.node("mod4", [&] () { + xml.node("rom", [&] () { + xml.attribute("name", "capslock"); }); }); + + xml.node("repeat", [&] () { + xml.attribute("delay_ms", 230); + xml.attribute("rate_ms", 40); }); + + using Keyboard_layout = Settings::Keyboard_layout; + Keyboard_layout::for_each([&] (Keyboard_layout const &layout) { + if (layout.name == _settings.keyboard_layout) + gen_include(layout.chargen_file); }); + + gen_include("special.chargen"); + }); + }); + + auto gen_policy = [&] (auto label) { + xml.node("policy", [&] () { + xml.attribute("label", label); + xml.attribute("input", label); }); }; + + gen_policy("ps2"); + gen_policy("usb"); + gen_policy("touch"); +} + + void Component::construct(Genode::Env &env) { static Sculpt::Main main(env); diff --git a/repos/gems/src/app/sculpt_manager/model/settings.h b/repos/gems/src/app/sculpt_manager/model/settings.h index 4f82e6419b..4f997622d0 100644 --- a/repos/gems/src/app/sculpt_manager/model/settings.h +++ b/repos/gems/src/app/sculpt_manager/model/settings.h @@ -16,8 +16,51 @@ #include "types.h" -namespace Sculpt { enum class Font_size; } +namespace Sculpt { struct Settings; } -enum class Sculpt::Font_size { SMALL, MEDIUM, LARGE }; + +struct Sculpt::Settings +{ + enum class Font_size; + + enum class Font_size { SMALL, MEDIUM, LARGE }; + + Font_size font_size = Font_size::MEDIUM; + + bool manual_fonts_config = false; + + struct Keyboard_layout + { + using Name = String<32>; + + Name name; + Path chargen_file; + + template + static void for_each(FN const &fn) + { + static Keyboard_layout layouts[] = { + { .name = "French", .chargen_file = "fr_fr.chargen" }, + { .name = "German", .chargen_file = "de_de.chargen" }, + { .name = "Swiss French", .chargen_file = "fr_ch.chargen" }, + { .name = "Swiss German", .chargen_file = "de_ch.chargen" }, + { .name = "US English", .chargen_file = "en_us.chargen" }, + }; + + for (auto layout : layouts) + fn(layout); + } + }; + + Keyboard_layout::Name keyboard_layout { "US English" }; + + bool manual_event_filter_config = false; + + bool interactive_settings_available() const + { + return !manual_event_filter_config + || !manual_fonts_config; + } +}; #endif /* _MODEL__SETTINGS_H_ */ diff --git a/repos/gems/src/app/sculpt_manager/view/panel_dialog.cc b/repos/gems/src/app/sculpt_manager/view/panel_dialog.cc index c3988b21c8..7536c15f34 100644 --- a/repos/gems/src/app/sculpt_manager/view/panel_dialog.cc +++ b/repos/gems/src/app/sculpt_manager/view/panel_dialog.cc @@ -21,19 +21,21 @@ void Panel_dialog::generate(Xml_generator &xml) const xml.node("frame", [&] () { xml.attribute("style", "unimportant"); - gen_named_node(xml, "float", "left", [&] () { - xml.attribute("west", true); - xml.node("hbox", [&] () { - xml.node("button", [&] () { - _item.gen_button_attr(xml, "settings"); - if (_state.settings_visible()) - xml.attribute("selected", true); - xml.node("label", [&] () { - xml.attribute("text", "Settings"); + if (_state.settings_available()) { + gen_named_node(xml, "float", "left", [&] () { + xml.attribute("west", true); + xml.node("hbox", [&] () { + xml.node("button", [&] () { + _item.gen_button_attr(xml, "settings"); + if (_state.settings_visible()) + xml.attribute("selected", true); + xml.node("label", [&] () { + xml.attribute("text", "Settings"); + }); }); }); }); - }); + } gen_named_node(xml, "float", "center", [&] () { xml.node("hbox", [&] () { diff --git a/repos/gems/src/app/sculpt_manager/view/panel_dialog.h b/repos/gems/src/app/sculpt_manager/view/panel_dialog.h index 6369a3019f..c032570891 100644 --- a/repos/gems/src/app/sculpt_manager/view/panel_dialog.h +++ b/repos/gems/src/app/sculpt_manager/view/panel_dialog.h @@ -36,6 +36,7 @@ struct Sculpt::Panel_dialog : Dialog virtual bool settings_visible() const = 0; virtual bool network_visible() const = 0; virtual bool inspect_tab_visible() const = 0; + virtual bool settings_available() const = 0; }; State const &_state; diff --git a/repos/gems/src/app/sculpt_manager/view/radio_choice_dialog.h b/repos/gems/src/app/sculpt_manager/view/radio_choice_dialog.h new file mode 100644 index 0000000000..5b7e220049 --- /dev/null +++ b/repos/gems/src/app/sculpt_manager/view/radio_choice_dialog.h @@ -0,0 +1,158 @@ +/* + * \brief Radio-button dialog + * \author Norman Feske + * \date 2021-03-19 + */ + +/* + * Copyright (C) 2021 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__RADIO_CHOICE_DIALOG_H_ +#define _VIEW__RADIO_CHOICE_DIALOG_H_ + +#include + +namespace Sculpt { struct Radio_choice_dialog; } + + +struct Sculpt::Radio_choice_dialog : Noncopyable, Dialog +{ + typedef Hoverable_item::Id Id; + + Id const _id; + + struct Min_ex { unsigned left; unsigned right; }; + + Min_ex const _min_ex; + + Hoverable_item _choice_item { }; + + bool _unfolded = false; + + Hover_result hover(Xml_node hover) override + { + return any_hover_changed(_choice_item.match(hover, "hbox", "frame", "vbox", "hbox", "name")); + } + + void reset() override + { + _unfolded = false; + } + + struct Choice : Interface, Noncopyable + { + virtual void generate(Id const &option_id) const = 0; + }; + + void generate(Xml_generator &) const override { }; + + template + void generate(Xml_generator &xml, Id const &selected_id, FN const &fn) const + { + struct Choice_generator : Choice + { + Xml_generator &xml; + + Radio_choice_dialog const &dialog; + + Id const selected_id; + + Choice_generator(Xml_generator &xml, Id const &selected_id, + Radio_choice_dialog const &dialog) + : + xml(xml), dialog(dialog), selected_id(selected_id) + { } + + void generate(Id const &option_id) const override + { + bool const selected = (option_id == selected_id); + + if (!selected && !dialog._unfolded) + return; + + gen_named_node(xml, "hbox", option_id, [&] () { + + gen_named_node(xml, "float", "left", [&] () { + xml.attribute("west", "yes"); + + xml.node("hbox", [&] () { + gen_named_node(xml, "button", "button", [&] () { + + if (selected) + xml.attribute("selected", "yes"); + + xml.attribute("style", "radio"); + dialog._choice_item.gen_hovered_attr(xml, option_id); + xml.node("hbox", [&] () { }); + }); + gen_named_node(xml, "label", "name", [&] () { + xml.attribute("text", Path(" ", option_id)); }); + }); + }); + + gen_named_node(xml, "hbox", "right", [&] () { }); + }); + } + + } choice_generator { xml, selected_id, *this }; + + gen_named_node(xml, "hbox", _id, [&] () { + gen_named_node(xml, "vbox", "left", [&] () { + + gen_named_node(xml, "float", "title", [&] () { + xml.attribute("north", true); + xml.attribute("west", true); + xml.node("frame", [&] () { + xml.attribute("style", "invisible"); + + xml.node("hbox", [&] () { + xml.node("label", [&] () { + xml.attribute("text", _id); }); }); + + /* used for consistent vertical text alignment */ + gen_named_node(xml, "button", "vspace", [&] () { + xml.attribute("style", "invisible"); + xml.node("hbox", [&] () { }); + }); + }); + }); + + gen_named_node(xml, "label", "hspace", [&] () { + xml.attribute("min_ex", _min_ex.left); }); + }); + + gen_named_node(xml, "frame", "right", [&] () { + xml.node("vbox", [&] () { + + fn(choice_generator); + + gen_named_node(xml, "label", "hspace", [&] () { + xml.attribute("min_ex", _min_ex.right); }); + }); + }); + }); + } + + Click_result click() + { + /* unfold choice on click */ + if (_choice_item._hovered == "") + return Click_result::IGNORED; + + _unfolded = true; + return Click_result::CONSUMED; + } + + Id hovered_choice() const { return _choice_item._hovered; } + + Radio_choice_dialog(Id const &id, Min_ex min_ex) + : + _id(id), _min_ex(min_ex) + { } +}; + +#endif /* _VIEW__RADIO_CHOICE_DIALOG_H_ */ diff --git a/repos/gems/src/app/sculpt_manager/view/settings_dialog.h b/repos/gems/src/app/sculpt_manager/view/settings_dialog.h index e42ba10491..b0b93c6ef8 100644 --- a/repos/gems/src/app/sculpt_manager/view/settings_dialog.h +++ b/repos/gems/src/app/sculpt_manager/view/settings_dialog.h @@ -15,6 +15,7 @@ #define _VIEW__SETTINGS_DIALOG_H_ #include +#include #include namespace Sculpt { struct Settings_dialog; } @@ -22,86 +23,116 @@ namespace Sculpt { struct Settings_dialog; } struct Sculpt::Settings_dialog : Noncopyable, Dialog { - Font_size const &_current_font_size; + Settings const &_settings; - Hoverable_item _item { }; + Hoverable_item _section { }; - static Hoverable_item::Id _font_size_id(Font_size font_size) + Radio_choice_dialog::Min_ex const _ratio { .left = 10, .right = 24 }; + + Radio_choice_dialog _font_size_choice { "Font size", _ratio }; + Radio_choice_dialog _keyboard_layout_choice { "Keyboard", _ratio }; + + static Radio_choice_dialog::Id _font_size_id(Settings::Font_size font_size) { switch (font_size) { - case Font_size::SMALL: return "small"; - case Font_size::MEDIUM: return "medium"; - case Font_size::LARGE: return "large"; + case Settings::Font_size::SMALL: return "Small"; + case Settings::Font_size::MEDIUM: return "Medium"; + case Settings::Font_size::LARGE: return "Large"; } - return Hoverable_item::Id(); + return Radio_choice_dialog::Id(); + } + + static Settings::Font_size _font_size(Radio_choice_dialog::Id id) + { + if (id == "Small") return Settings::Font_size::SMALL; + if (id == "Medium") return Settings::Font_size::MEDIUM; + if (id == "Large") return Settings::Font_size::LARGE; + + return Settings::Font_size::MEDIUM; } Hover_result hover(Xml_node hover) override { return any_hover_changed( - _item.match(hover, "frame", "vbox", "hbox", "button", "name")); + _section.match(hover, "frame", "vbox", "hbox", "name"), + _font_size_choice.match_sub_dialog(hover, "frame", "vbox"), + _keyboard_layout_choice.match_sub_dialog(hover, "frame", "vbox")); } void reset() override { } struct Action : Interface, Noncopyable { - virtual void select_font_size(Font_size) = 0; + virtual void select_font_size(Settings::Font_size) = 0; + + virtual void select_keyboard_layout(Settings::Keyboard_layout::Name const &) = 0; }; void generate(Xml_generator &xml) const override { - gen_named_node(xml, "frame", "network", [&] () { + gen_named_node(xml, "frame", "settings", [&] () { xml.node("vbox", [&] () { - gen_named_node(xml, "hbox", "font_size", [&] () { - gen_named_node(xml, "label", "label", [&] () { - xml.attribute("text", " Font size "); }); - auto gen_font_size_button = [&] (Font_size font_size) - { - Label const label = _font_size_id(font_size); - gen_named_node(xml, "button", label, [&] () { - _item.gen_hovered_attr(xml, label); + using Choice = Radio_choice_dialog::Choice; - if (font_size == _current_font_size) - xml.attribute("selected", true); + if (!_settings.manual_fonts_config) { + _font_size_choice.generate(xml, _font_size_id(_settings.font_size), + [&] (Choice const &choice) { + choice.generate("Small"); + choice.generate("Medium"); + choice.generate("Large"); + }); + } - xml.node("label", [&] () { - xml.attribute("text", label); }); - }); - }; - - gen_font_size_button(Font_size::SMALL); - gen_font_size_button(Font_size::MEDIUM); - gen_font_size_button(Font_size::LARGE); - }); + if (!_settings.manual_event_filter_config) { + _keyboard_layout_choice.generate(xml, _settings.keyboard_layout, + [&] (Choice const &choice) { + using Keyboard_layout = Settings::Keyboard_layout; + Keyboard_layout::for_each([&] (Keyboard_layout const &layout) { + choice.generate(layout.name); }); + }); + } }); }); } Click_result click(Action &action) { + _font_size_choice.reset(); + _keyboard_layout_choice.reset(); + Click_result result = Click_result::IGNORED; - auto apply_font_size = [&] (Font_size font_size) + auto handle_section = [&] (Radio_choice_dialog &dialog, auto fn_clicked) { - if (_item.hovered(_font_size_id(font_size))) { - action.select_font_size(font_size); - result = Click_result::CONSUMED; - } + if (result == Click_result::CONSUMED || !_section.hovered(dialog._id)) + return; + + /* unfold radio choice */ + dialog.click(); + + auto selection = dialog.hovered_choice(); + if (selection == "") + return; + + fn_clicked(selection); + + result = Click_result::CONSUMED; }; - apply_font_size(Font_size::SMALL); - apply_font_size(Font_size::MEDIUM); - apply_font_size(Font_size::LARGE); + handle_section(_font_size_choice, [&] (auto selection) { + action.select_font_size(_font_size(selection)); }); + + handle_section(_keyboard_layout_choice, [&] (auto selection) { + using Keyboard_layout = Settings::Keyboard_layout; + Keyboard_layout::for_each([&] (Keyboard_layout const &layout) { + if (selection == layout.name) + action.select_keyboard_layout(selection); }); }); return result; } - Settings_dialog(Font_size const ¤t_font_size) - : - _current_font_size(current_font_size) - { } + Settings_dialog(Settings const &settings) : _settings(settings) { } }; #endif /* _VIEW__RAM_FS_DIALOG_H_ */