summaryrefslogtreecommitdiff
path: root/Userland/Applications/KeyboardSettings
diff options
context:
space:
mode:
authorTimur Sultanov <SultanovTS@yandex.ru>2022-01-19 18:20:42 +0300
committerAndreas Kling <kling@serenityos.org>2022-02-03 00:47:22 +0100
commitc7bd47c87c002a7ee9e4484bf21781e0dbc05614 (patch)
tree70f8b90a2ca05f8b71e2d0a01cd1d6fa74cf4888 /Userland/Applications/KeyboardSettings
parent5a31697bcf44920e50b8f98c7bf1469a959a0900 (diff)
downloadserenity-c7bd47c87c002a7ee9e4484bf21781e0dbc05614.zip
Base+WindowsServer+keymap: Store multiple keymaps in a config
Diffstat (limited to 'Userland/Applications/KeyboardSettings')
-rw-r--r--Userland/Applications/KeyboardSettings/CMakeLists.txt2
-rw-r--r--Userland/Applications/KeyboardSettings/Keyboard.gml55
-rw-r--r--Userland/Applications/KeyboardSettings/KeyboardSettingsWidget.cpp205
-rw-r--r--Userland/Applications/KeyboardSettings/KeyboardSettingsWidget.h17
-rw-r--r--Userland/Applications/KeyboardSettings/KeymapDialog.gml33
-rw-r--r--Userland/Applications/KeyboardSettings/main.cpp8
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();