diff options
author | Timur Sultanov <SultanovTS@yandex.ru> | 2022-01-19 18:20:42 +0300 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-02-03 00:47:22 +0100 |
commit | c7bd47c87c002a7ee9e4484bf21781e0dbc05614 (patch) | |
tree | 70f8b90a2ca05f8b71e2d0a01cd1d6fa74cf4888 /Userland/Applications/KeyboardSettings | |
parent | 5a31697bcf44920e50b8f98c7bf1469a959a0900 (diff) | |
download | serenity-c7bd47c87c002a7ee9e4484bf21781e0dbc05614.zip |
Base+WindowsServer+keymap: Store multiple keymaps in a config
Diffstat (limited to 'Userland/Applications/KeyboardSettings')
6 files changed, 267 insertions, 53 deletions
diff --git a/Userland/Applications/KeyboardSettings/CMakeLists.txt b/Userland/Applications/KeyboardSettings/CMakeLists.txt index 8a7ccbc475..83bed80c33 100644 --- a/Userland/Applications/KeyboardSettings/CMakeLists.txt +++ b/Userland/Applications/KeyboardSettings/CMakeLists.txt @@ -5,11 +5,13 @@ serenity_component( ) compile_gml(Keyboard.gml KeyboardWidgetGML.h keyboard_widget_gml) +compile_gml(KeymapDialog.gml KeymapDialogGML.h keymap_dialog_gml) set(SOURCES main.cpp KeyboardSettingsWidget.cpp KeyboardWidgetGML.h + KeymapDialogGML.h ) serenity_app(KeyboardSettings ICON app-keyboard-settings) diff --git a/Userland/Applications/KeyboardSettings/Keyboard.gml b/Userland/Applications/KeyboardSettings/Keyboard.gml index ad1f7d65a7..261ce8513b 100644 --- a/Userland/Applications/KeyboardSettings/Keyboard.gml +++ b/Userland/Applications/KeyboardSettings/Keyboard.gml @@ -8,7 +8,7 @@ @GUI::GroupBox { title: "Mapping" - fixed_height: 200 + fixed_height: 150 layout: @GUI::HorizontalBoxLayout { margins: [16, 8, 8] @@ -25,8 +25,6 @@ fixed_height: 32 icon: "/res/icons/32x32/app-keyboard-mapper.png" } - - @GUI::Widget } @GUI::Widget { @@ -34,23 +32,59 @@ spacing: 2 } + @GUI::ListView { + name: "selected_keymaps" + } + @GUI::Widget { + fixed_height: 24 + layout: @GUI::HorizontalBoxLayout { - spacing: 16 + spacing: 4 } - @GUI::Label { - text: "Character mapping file:" - fixed_width: 130 - text_alignment: "CenterLeft" + @GUI::Button { + name: "add_keymap_button" + text: "Add keymap" } - @GUI::ComboBox { - name: "character_map_file_combo" + @GUI::Button { + name: "remove_keymap_button" + text: "Remove keymap" + enabled: false } } + } + } + + @GUI::GroupBox { + title: "Test input" + + layout: @GUI::HorizontalBoxLayout { + margins: [16, 8, 8] + spacing: 16 + } + + @GUI::Widget { + fixed_width: 32 + layout: @GUI::VerticalBoxLayout { + } + + @GUI::Label { + fixed_width: 32 + fixed_height: 32 + icon: "/res/icons/32x32/app-keyboard-settings.png" + } + } + + @GUI::Widget { + layout: @GUI::VerticalBoxLayout { + spacing: 2 + } @GUI::Widget { + fixed_height: 24 + layout: @GUI::HorizontalBoxLayout { spacing: 16 } @@ -68,7 +102,6 @@ } @GUI::TextEditor { - fixed_height: 100 name: "test_typing_area" } } diff --git a/Userland/Applications/KeyboardSettings/KeyboardSettingsWidget.cpp b/Userland/Applications/KeyboardSettings/KeyboardSettingsWidget.cpp index bc19174744..e3d95390f6 100644 --- a/Userland/Applications/KeyboardSettings/KeyboardSettingsWidget.cpp +++ b/Userland/Applications/KeyboardSettings/KeyboardSettingsWidget.cpp @@ -9,16 +9,126 @@ #include <AK/JsonObject.h> #include <AK/QuickSort.h> #include <Applications/KeyboardSettings/KeyboardWidgetGML.h> +#include <Applications/KeyboardSettings/KeymapDialogGML.h> #include <LibConfig/Client.h> #include <LibCore/DirIterator.h> #include <LibCore/File.h> #include <LibGUI/Application.h> +#include <LibGUI/ComboBox.h> +#include <LibGUI/Dialog.h> #include <LibGUI/ItemListModel.h> #include <LibGUI/Label.h> #include <LibGUI/MessageBox.h> +#include <LibGUI/Model.h> +#include <LibGUI/Widget.h> +#include <LibGUI/Window.h> #include <LibKeyboard/CharacterMap.h> #include <spawn.h> +class KeymapSelectionDialog final : public GUI::Dialog { + C_OBJECT(KeymapSelectionDialog) +public: + virtual ~KeymapSelectionDialog() override {}; + + static String select_keymap(Window* parent_window, Vector<String> const& selected_keymaps) + { + auto dialog = KeymapSelectionDialog::construct(parent_window, selected_keymaps); + dialog->set_title("Add a keymap"); + + if (dialog->exec() == GUI::Dialog::ExecOK) { + return dialog->selected_keymap(); + } + + return String::empty(); + } + + String selected_keymap() { return m_selected_keymap; } + +private: + KeymapSelectionDialog(Window* parent_window, Vector<String> const& selected_keymaps) + : Dialog(parent_window) + { + auto& widget = set_main_widget<GUI::Widget>(); + if (!widget.load_from_gml(keymap_dialog_gml)) + VERIFY_NOT_REACHED(); + + set_resizable(false); + resize(190, 54); + + set_icon(parent_window->icon()); + + Core::DirIterator iterator("/res/keymaps/", Core::DirIterator::Flags::SkipDots); + if (iterator.has_error()) { + GUI::MessageBox::show(nullptr, String::formatted("Error on reading mapping file list: {}", iterator.error_string()), "Keyboard settings", GUI::MessageBox::Type::Error); + GUI::Application::the()->quit(-1); + } + + while (iterator.has_next()) { + auto name = iterator.next_path(); + auto basename = name.replace(".json", ""); + if (!selected_keymaps.find(basename).is_end()) + continue; + m_character_map_files.append(basename); + } + quick_sort(m_character_map_files); + + m_selected_keymap = m_character_map_files.first(); + + m_keymaps_combobox = *widget.find_descendant_of_type_named<GUI::ComboBox>("keymaps_combobox"); + m_keymaps_combobox->set_only_allow_values_from_model(true); + m_keymaps_combobox->set_model(*GUI::ItemListModel<String>::create(m_character_map_files)); + m_keymaps_combobox->set_selected_index(0); + + m_keymaps_combobox->on_change = [&](auto& keymap, auto) { + m_selected_keymap = keymap; + }; + + auto& ok_button = *widget.find_descendant_of_type_named<GUI::Button>("ok_button"); + ok_button.on_click = [this](auto) { + done(Dialog::ExecOK); + }; + + auto& cancel_button = *widget.find_descendant_of_type_named<GUI::Button>("cancel_button"); + cancel_button.on_click = [this](auto) { + done(Dialog::ExecCancel); + }; + } + + RefPtr<GUI::ComboBox> m_keymaps_combobox; + Vector<String> m_character_map_files; + String m_selected_keymap; +}; + +class KeymapModel final : public GUI::Model { +public: + KeymapModel() {}; + + int row_count(GUI::ModelIndex const&) const override { return m_data.size(); } + int column_count(GUI::ModelIndex const&) const override { return 1; } + + GUI::Variant data(GUI::ModelIndex const& index, GUI::ModelRole) const override + { + return m_data.at(index.row()); + } + + void remove_at(size_t index) + { + m_data.remove(index); + invalidate(); + } + + void add_keymap(String const& keymap) + { + m_data.append(keymap); + invalidate(); + } + + Vector<String> const& keymaps() const { return m_data; } + +private: + Vector<String> m_data; +}; + KeyboardSettingsWidget::KeyboardSettingsWidget() { load_from_gml(keyboard_widget_gml); @@ -33,37 +143,51 @@ KeyboardSettingsWidget::KeyboardSettingsWidget() m_current_applied_keymap = keymap_object.get("keymap").to_string(); dbgln("KeyboardSettings thinks the current keymap is: {}", m_current_applied_keymap); - Core::DirIterator iterator("/res/keymaps/", Core::DirIterator::Flags::SkipDots); - if (iterator.has_error()) { - GUI::MessageBox::show(nullptr, String::formatted("Error on reading mapping file list: {}", iterator.error_string()), "Keyboard settings", GUI::MessageBox::Type::Error); - GUI::Application::the()->quit(-1); - } + auto mapper_config(Core::ConfigFile::open("/etc/Keyboard.ini")); + auto keymaps = mapper_config->read_entry("Mapping", "Keymaps", ""); - while (iterator.has_next()) { - auto name = iterator.next_path(); - m_character_map_files.append(name.replace(".json", "")); - } - quick_sort(m_character_map_files); + auto keymaps_vector = keymaps.split(','); - size_t initial_keymap_index = SIZE_MAX; - for (size_t i = 0; i < m_character_map_files.size(); ++i) { - if (m_character_map_files[i].equals_ignoring_case(m_current_applied_keymap)) - initial_keymap_index = i; + m_selected_keymaps_listview = find_descendant_of_type_named<GUI::ListView>("selected_keymaps"); + m_selected_keymaps_listview->horizontal_scrollbar().set_visible(false); + m_selected_keymaps_listview->set_model(adopt_ref(*new KeymapModel())); + auto& keymaps_list_model = static_cast<KeymapModel&>(*m_selected_keymaps_listview->model()); + + for (auto& keymap : keymaps_vector) { + m_initial_keymap_list.append(keymap); + keymaps_list_model.add_keymap(keymap); } - VERIFY(initial_keymap_index < m_character_map_files.size()); - - m_character_map_file_combo = find_descendant_of_type_named<GUI::ComboBox>("character_map_file_combo"); - m_character_map_file_combo->set_only_allow_values_from_model(true); - m_character_map_file_combo->set_model(*GUI::ItemListModel<String>::create(m_character_map_files)); - m_character_map_file_combo->set_selected_index(initial_keymap_index); - // This is a bit of a hack. We set the keymap to the selected one, so that it applies in the testing box below. - // But, we also keep track of the current "applied" keymap, and then revert to that when we exit. - // Ideally, we'd only use the selected keymap for the testing box without making a global system change. - m_character_map_file_combo->on_change = [this](auto& keymap, auto) { - set_keymap(keymap); + + m_add_keymap_button = find_descendant_of_type_named<GUI::Button>("add_keymap_button"); + + m_add_keymap_button->on_click = [&](auto) { + auto keymap = KeymapSelectionDialog::select_keymap(window(), keymaps_list_model.keymaps()); + keymaps_list_model.add_keymap(keymap); + }; + + m_remove_keymap_button = find_descendant_of_type_named<GUI::Button>("remove_keymap_button"); + + m_remove_keymap_button->on_click = [&](auto) { + auto& selection = m_selected_keymaps_listview->selection(); + for (auto& index : selection.indices()) { + keymaps_list_model.remove_at(index.row()); + } + }; + + m_selected_keymaps_listview->on_selection_change = [&]() { + auto& selection = m_selected_keymaps_listview->selection(); + m_remove_keymap_button->set_enabled(!selection.is_empty() && keymaps_list_model.keymaps().size() > 1); + }; + + m_test_typing_area = *find_descendant_of_type_named<GUI::TextEditor>("test_typing_area"); + m_test_typing_area->on_focusin = [&]() { + set_keymaps(keymaps_list_model.keymaps()); + }; + + m_test_typing_area->on_focusout = [&]() { + set_keymaps(m_initial_keymap_list); }; - m_test_typing_area = find_descendant_of_type_named<GUI::TextEditor>("test_typing_area"); m_clear_test_typing_area_button = find_descendant_of_type_named<GUI::Button>("button_clear_test_typing_area"); m_clear_test_typing_area_button->on_click = [this](auto) { m_test_typing_area->clear(); @@ -76,25 +200,36 @@ KeyboardSettingsWidget::KeyboardSettingsWidget() KeyboardSettingsWidget::~KeyboardSettingsWidget() { - set_keymap(m_current_applied_keymap); + set_keymaps(m_initial_keymap_list); +} + +void KeyboardSettingsWidget::window_activated(bool is_active_window) +{ + if (is_active_window && m_test_typing_area->is_focused()) { + auto& keymaps_list_model = static_cast<KeymapModel&>(*m_selected_keymaps_listview->model()); + set_keymaps(keymaps_list_model.keymaps()); + } else { + set_keymaps(m_initial_keymap_list); + } } void KeyboardSettingsWidget::apply_settings() { - String character_map_file = m_character_map_file_combo->text(); - if (character_map_file.is_empty()) { - GUI::MessageBox::show(window(), "Please select character mapping file.", "Keyboard settings", GUI::MessageBox::Type::Error); - return; + auto& m_keymaps_list_model = static_cast<KeymapModel&>(*m_selected_keymaps_listview->model()); + set_keymaps(m_keymaps_list_model.keymaps()); + m_initial_keymap_list.clear(); + for (auto& keymap : m_keymaps_list_model.keymaps()) { + m_initial_keymap_list.append(keymap); } - m_current_applied_keymap = character_map_file; - set_keymap(character_map_file); Config::write_bool("KeyboardSettings", "StartupEnable", "NumLock", m_num_lock_checkbox->is_checked()); } -void KeyboardSettingsWidget::set_keymap(String const& keymap_filename) +void KeyboardSettingsWidget::set_keymaps(Vector<String> const& keymaps) { pid_t child_pid; - const char* argv[] = { "/bin/keymap", keymap_filename.characters(), nullptr }; + + auto keymaps_string = String::join(',', keymaps); + const char* argv[] = { "/bin/keymap", "-s", keymaps_string.characters(), nullptr }; if ((errno = posix_spawn(&child_pid, "/bin/keymap", nullptr, nullptr, const_cast<char**>(argv), environ))) { perror("posix_spawn"); exit(1); diff --git a/Userland/Applications/KeyboardSettings/KeyboardSettingsWidget.h b/Userland/Applications/KeyboardSettings/KeyboardSettingsWidget.h index 9084027354..cadc87f1c2 100644 --- a/Userland/Applications/KeyboardSettings/KeyboardSettingsWidget.h +++ b/Userland/Applications/KeyboardSettings/KeyboardSettingsWidget.h @@ -6,11 +6,13 @@ #pragma once +#include <AK/Vector.h> #include <LibGUI/Button.h> #include <LibGUI/CheckBox.h> -#include <LibGUI/ComboBox.h> +#include <LibGUI/ListView.h> #include <LibGUI/SettingsWindow.h> #include <LibGUI/TextEditor.h> +#include <LibGUI/WindowManagerServerConnection.h> class KeyboardSettingsWidget final : public GUI::SettingsWindow::Tab { C_OBJECT(KeyboardSettingsWidget) @@ -19,16 +21,21 @@ public: virtual void apply_settings() override; + void window_activated(bool is_active_window); + private: KeyboardSettingsWidget(); - void set_keymap(String const& keymap_filename); + void set_keymaps(Vector<String> const& keymaps); + + Vector<String> m_initial_keymap_list; String m_current_applied_keymap; - Vector<String> m_character_map_files; - RefPtr<GUI::ComboBox> m_character_map_file_combo; + RefPtr<GUI::ListView> m_selected_keymaps_listview; + RefPtr<GUI::CheckBox> m_num_lock_checkbox; + RefPtr<GUI::Button> m_add_keymap_button; + RefPtr<GUI::Button> m_remove_keymap_button; RefPtr<GUI::TextEditor> m_test_typing_area; RefPtr<GUI::Button> m_clear_test_typing_area_button; - RefPtr<GUI::CheckBox> m_num_lock_checkbox; }; diff --git a/Userland/Applications/KeyboardSettings/KeymapDialog.gml b/Userland/Applications/KeyboardSettings/KeymapDialog.gml new file mode 100644 index 0000000000..71e1efbe62 --- /dev/null +++ b/Userland/Applications/KeyboardSettings/KeymapDialog.gml @@ -0,0 +1,33 @@ +@GUI::Widget { + fill_with_background_color: true + + layout: @GUI::VerticalBoxLayout { + margins: [4] + } + + @GUI::ComboBox { + name: "keymaps_combobox" + } + + @GUI::Widget { + fixed_height: 24 + + layout: @GUI::HorizontalBoxLayout { + } + + @GUI::Widget { + } + + @GUI::Button { + name: "ok_button" + text: "OK" + fixed_width: 75 + } + + @GUI::Button { + name: "cancel_button" + text: "Cancel" + fixed_width: 75 + } + } +} diff --git a/Userland/Applications/KeyboardSettings/main.cpp b/Userland/Applications/KeyboardSettings/main.cpp index e88765304f..046312eeeb 100644 --- a/Userland/Applications/KeyboardSettings/main.cpp +++ b/Userland/Applications/KeyboardSettings/main.cpp @@ -9,7 +9,6 @@ #include <LibCore/System.h> #include <LibGUI/Application.h> #include <LibGUI/SettingsWindow.h> -#include <LibGUI/WindowServerConnection.h> #include <LibMain/Main.h> // Including this after to avoid LibIPC errors @@ -25,13 +24,18 @@ ErrorOr<int> serenity_main(Main::Arguments arguments) TRY(Core::System::unveil("/res", "r")); TRY(Core::System::unveil("/bin/keymap", "x")); TRY(Core::System::unveil("/proc/keymap", "r")); + TRY(Core::System::unveil("/etc/Keyboard.ini", "r")); TRY(Core::System::unveil(nullptr, nullptr)); auto app_icon = GUI::Icon::default_icon("app-keyboard-settings"); auto window = TRY(GUI::SettingsWindow::create("Keyboard Settings")); window->set_icon(app_icon.bitmap_for_size(16)); - (void)TRY(window->add_tab<KeyboardSettingsWidget>("Keyboard")); + auto keyboard_settings_widget = TRY(window->add_tab<KeyboardSettingsWidget>("Keyboard")); + + window->on_active_window_change = [&](bool is_active_window) { + keyboard_settings_widget->window_activated(is_active_window); + }; window->show(); return app->exec(); |