mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:17:45 +00:00
Base+WindowsServer+keymap: Store multiple keymaps in a config
This commit is contained in:
parent
5a31697bcf
commit
c7bd47c87c
12 changed files with 405 additions and 100 deletions
|
@ -1,2 +1,2 @@
|
||||||
[Mapping]
|
[Mapping]
|
||||||
Keymap=en-us
|
Keymaps=en-us
|
||||||
|
|
|
@ -5,11 +5,13 @@ serenity_component(
|
||||||
)
|
)
|
||||||
|
|
||||||
compile_gml(Keyboard.gml KeyboardWidgetGML.h keyboard_widget_gml)
|
compile_gml(Keyboard.gml KeyboardWidgetGML.h keyboard_widget_gml)
|
||||||
|
compile_gml(KeymapDialog.gml KeymapDialogGML.h keymap_dialog_gml)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
main.cpp
|
main.cpp
|
||||||
KeyboardSettingsWidget.cpp
|
KeyboardSettingsWidget.cpp
|
||||||
KeyboardWidgetGML.h
|
KeyboardWidgetGML.h
|
||||||
|
KeymapDialogGML.h
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_app(KeyboardSettings ICON app-keyboard-settings)
|
serenity_app(KeyboardSettings ICON app-keyboard-settings)
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
@GUI::GroupBox {
|
@GUI::GroupBox {
|
||||||
title: "Mapping"
|
title: "Mapping"
|
||||||
fixed_height: 200
|
fixed_height: 150
|
||||||
|
|
||||||
layout: @GUI::HorizontalBoxLayout {
|
layout: @GUI::HorizontalBoxLayout {
|
||||||
margins: [16, 8, 8]
|
margins: [16, 8, 8]
|
||||||
|
@ -25,8 +25,56 @@
|
||||||
fixed_height: 32
|
fixed_height: 32
|
||||||
icon: "/res/icons/32x32/app-keyboard-mapper.png"
|
icon: "/res/icons/32x32/app-keyboard-mapper.png"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GUI::Widget
|
@GUI::Widget {
|
||||||
|
layout: @GUI::VerticalBoxLayout {
|
||||||
|
spacing: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
@GUI::ListView {
|
||||||
|
name: "selected_keymaps"
|
||||||
|
}
|
||||||
|
|
||||||
|
@GUI::Widget {
|
||||||
|
fixed_height: 24
|
||||||
|
|
||||||
|
layout: @GUI::HorizontalBoxLayout {
|
||||||
|
spacing: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
@GUI::Button {
|
||||||
|
name: "add_keymap_button"
|
||||||
|
text: "Add keymap"
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 {
|
@GUI::Widget {
|
||||||
|
@ -35,22 +83,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@GUI::Widget {
|
@GUI::Widget {
|
||||||
layout: @GUI::HorizontalBoxLayout {
|
fixed_height: 24
|
||||||
spacing: 16
|
|
||||||
}
|
|
||||||
|
|
||||||
@GUI::Label {
|
|
||||||
text: "Character mapping file:"
|
|
||||||
fixed_width: 130
|
|
||||||
text_alignment: "CenterLeft"
|
|
||||||
}
|
|
||||||
|
|
||||||
@GUI::ComboBox {
|
|
||||||
name: "character_map_file_combo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GUI::Widget {
|
|
||||||
layout: @GUI::HorizontalBoxLayout {
|
layout: @GUI::HorizontalBoxLayout {
|
||||||
spacing: 16
|
spacing: 16
|
||||||
}
|
}
|
||||||
|
@ -68,7 +102,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@GUI::TextEditor {
|
@GUI::TextEditor {
|
||||||
fixed_height: 100
|
|
||||||
name: "test_typing_area"
|
name: "test_typing_area"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,16 +9,126 @@
|
||||||
#include <AK/JsonObject.h>
|
#include <AK/JsonObject.h>
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
#include <Applications/KeyboardSettings/KeyboardWidgetGML.h>
|
#include <Applications/KeyboardSettings/KeyboardWidgetGML.h>
|
||||||
|
#include <Applications/KeyboardSettings/KeymapDialogGML.h>
|
||||||
#include <LibConfig/Client.h>
|
#include <LibConfig/Client.h>
|
||||||
#include <LibCore/DirIterator.h>
|
#include <LibCore/DirIterator.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
#include <LibGUI/Application.h>
|
#include <LibGUI/Application.h>
|
||||||
|
#include <LibGUI/ComboBox.h>
|
||||||
|
#include <LibGUI/Dialog.h>
|
||||||
#include <LibGUI/ItemListModel.h>
|
#include <LibGUI/ItemListModel.h>
|
||||||
#include <LibGUI/Label.h>
|
#include <LibGUI/Label.h>
|
||||||
#include <LibGUI/MessageBox.h>
|
#include <LibGUI/MessageBox.h>
|
||||||
|
#include <LibGUI/Model.h>
|
||||||
|
#include <LibGUI/Widget.h>
|
||||||
|
#include <LibGUI/Window.h>
|
||||||
#include <LibKeyboard/CharacterMap.h>
|
#include <LibKeyboard/CharacterMap.h>
|
||||||
#include <spawn.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()
|
KeyboardSettingsWidget::KeyboardSettingsWidget()
|
||||||
{
|
{
|
||||||
load_from_gml(keyboard_widget_gml);
|
load_from_gml(keyboard_widget_gml);
|
||||||
|
@ -33,37 +143,51 @@ KeyboardSettingsWidget::KeyboardSettingsWidget()
|
||||||
m_current_applied_keymap = keymap_object.get("keymap").to_string();
|
m_current_applied_keymap = keymap_object.get("keymap").to_string();
|
||||||
dbgln("KeyboardSettings thinks the current keymap is: {}", m_current_applied_keymap);
|
dbgln("KeyboardSettings thinks the current keymap is: {}", m_current_applied_keymap);
|
||||||
|
|
||||||
Core::DirIterator iterator("/res/keymaps/", Core::DirIterator::Flags::SkipDots);
|
auto mapper_config(Core::ConfigFile::open("/etc/Keyboard.ini"));
|
||||||
if (iterator.has_error()) {
|
auto keymaps = mapper_config->read_entry("Mapping", "Keymaps", "");
|
||||||
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 keymaps_vector = keymaps.split(',');
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (iterator.has_next()) {
|
m_add_keymap_button = find_descendant_of_type_named<GUI::Button>("add_keymap_button");
|
||||||
auto name = iterator.next_path();
|
|
||||||
m_character_map_files.append(name.replace(".json", ""));
|
|
||||||
}
|
|
||||||
quick_sort(m_character_map_files);
|
|
||||||
|
|
||||||
size_t initial_keymap_index = SIZE_MAX;
|
m_add_keymap_button->on_click = [&](auto) {
|
||||||
for (size_t i = 0; i < m_character_map_files.size(); ++i) {
|
auto keymap = KeymapSelectionDialog::select_keymap(window(), keymaps_list_model.keymaps());
|
||||||
if (m_character_map_files[i].equals_ignoring_case(m_current_applied_keymap))
|
keymaps_list_model.add_keymap(keymap);
|
||||||
initial_keymap_index = i;
|
};
|
||||||
}
|
|
||||||
VERIFY(initial_keymap_index < m_character_map_files.size());
|
m_remove_keymap_button = find_descendant_of_type_named<GUI::Button>("remove_keymap_button");
|
||||||
|
|
||||||
m_character_map_file_combo = find_descendant_of_type_named<GUI::ComboBox>("character_map_file_combo");
|
m_remove_keymap_button->on_click = [&](auto) {
|
||||||
m_character_map_file_combo->set_only_allow_values_from_model(true);
|
auto& selection = m_selected_keymaps_listview->selection();
|
||||||
m_character_map_file_combo->set_model(*GUI::ItemListModel<String>::create(m_character_map_files));
|
for (auto& index : selection.indices()) {
|
||||||
m_character_map_file_combo->set_selected_index(initial_keymap_index);
|
keymaps_list_model.remove_at(index.row());
|
||||||
// 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) {
|
m_selected_keymaps_listview->on_selection_change = [&]() {
|
||||||
set_keymap(keymap);
|
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 = find_descendant_of_type_named<GUI::Button>("button_clear_test_typing_area");
|
||||||
m_clear_test_typing_area_button->on_click = [this](auto) {
|
m_clear_test_typing_area_button->on_click = [this](auto) {
|
||||||
m_test_typing_area->clear();
|
m_test_typing_area->clear();
|
||||||
|
@ -76,25 +200,36 @@ KeyboardSettingsWidget::KeyboardSettingsWidget()
|
||||||
|
|
||||||
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()
|
void KeyboardSettingsWidget::apply_settings()
|
||||||
{
|
{
|
||||||
String character_map_file = m_character_map_file_combo->text();
|
auto& m_keymaps_list_model = static_cast<KeymapModel&>(*m_selected_keymaps_listview->model());
|
||||||
if (character_map_file.is_empty()) {
|
set_keymaps(m_keymaps_list_model.keymaps());
|
||||||
GUI::MessageBox::show(window(), "Please select character mapping file.", "Keyboard settings", GUI::MessageBox::Type::Error);
|
m_initial_keymap_list.clear();
|
||||||
return;
|
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());
|
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;
|
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))) {
|
if ((errno = posix_spawn(&child_pid, "/bin/keymap", nullptr, nullptr, const_cast<char**>(argv), environ))) {
|
||||||
perror("posix_spawn");
|
perror("posix_spawn");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|
|
@ -6,11 +6,13 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Vector.h>
|
||||||
#include <LibGUI/Button.h>
|
#include <LibGUI/Button.h>
|
||||||
#include <LibGUI/CheckBox.h>
|
#include <LibGUI/CheckBox.h>
|
||||||
#include <LibGUI/ComboBox.h>
|
#include <LibGUI/ListView.h>
|
||||||
#include <LibGUI/SettingsWindow.h>
|
#include <LibGUI/SettingsWindow.h>
|
||||||
#include <LibGUI/TextEditor.h>
|
#include <LibGUI/TextEditor.h>
|
||||||
|
#include <LibGUI/WindowManagerServerConnection.h>
|
||||||
|
|
||||||
class KeyboardSettingsWidget final : public GUI::SettingsWindow::Tab {
|
class KeyboardSettingsWidget final : public GUI::SettingsWindow::Tab {
|
||||||
C_OBJECT(KeyboardSettingsWidget)
|
C_OBJECT(KeyboardSettingsWidget)
|
||||||
|
@ -19,16 +21,21 @@ public:
|
||||||
|
|
||||||
virtual void apply_settings() override;
|
virtual void apply_settings() override;
|
||||||
|
|
||||||
|
void window_activated(bool is_active_window);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
KeyboardSettingsWidget();
|
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;
|
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::TextEditor> m_test_typing_area;
|
||||||
RefPtr<GUI::Button> m_clear_test_typing_area_button;
|
RefPtr<GUI::Button> m_clear_test_typing_area_button;
|
||||||
RefPtr<GUI::CheckBox> m_num_lock_checkbox;
|
|
||||||
};
|
};
|
||||||
|
|
33
Userland/Applications/KeyboardSettings/KeymapDialog.gml
Normal file
33
Userland/Applications/KeyboardSettings/KeymapDialog.gml
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,6 @@
|
||||||
#include <LibCore/System.h>
|
#include <LibCore/System.h>
|
||||||
#include <LibGUI/Application.h>
|
#include <LibGUI/Application.h>
|
||||||
#include <LibGUI/SettingsWindow.h>
|
#include <LibGUI/SettingsWindow.h>
|
||||||
#include <LibGUI/WindowServerConnection.h>
|
|
||||||
#include <LibMain/Main.h>
|
#include <LibMain/Main.h>
|
||||||
|
|
||||||
// Including this after to avoid LibIPC errors
|
// 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("/res", "r"));
|
||||||
TRY(Core::System::unveil("/bin/keymap", "x"));
|
TRY(Core::System::unveil("/bin/keymap", "x"));
|
||||||
TRY(Core::System::unveil("/proc/keymap", "r"));
|
TRY(Core::System::unveil("/proc/keymap", "r"));
|
||||||
|
TRY(Core::System::unveil("/etc/Keyboard.ini", "r"));
|
||||||
TRY(Core::System::unveil(nullptr, nullptr));
|
TRY(Core::System::unveil(nullptr, nullptr));
|
||||||
|
|
||||||
auto app_icon = GUI::Icon::default_icon("app-keyboard-settings");
|
auto app_icon = GUI::Icon::default_icon("app-keyboard-settings");
|
||||||
|
|
||||||
auto window = TRY(GUI::SettingsWindow::create("Keyboard Settings"));
|
auto window = TRY(GUI::SettingsWindow::create("Keyboard Settings"));
|
||||||
window->set_icon(app_icon.bitmap_for_size(16));
|
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();
|
window->show();
|
||||||
return app->exec();
|
return app->exec();
|
||||||
|
|
|
@ -24,10 +24,12 @@ ErrorOr<int> serenity_main(Main::Arguments)
|
||||||
TRY(Core::System::unveil("/dev/keyboard0", "r"));
|
TRY(Core::System::unveil("/dev/keyboard0", "r"));
|
||||||
TRY(Core::System::unveil(nullptr, nullptr));
|
TRY(Core::System::unveil(nullptr, nullptr));
|
||||||
auto mapper_config(Core::ConfigFile::open("/etc/Keyboard.ini"));
|
auto mapper_config(Core::ConfigFile::open("/etc/Keyboard.ini"));
|
||||||
auto keymap = mapper_config->read_entry("Mapping", "Keymap", "");
|
auto keymaps = mapper_config->read_entry("Mapping", "Keymaps", "");
|
||||||
|
|
||||||
|
auto keymaps_vector = keymaps.split(',');
|
||||||
|
|
||||||
pid_t child_pid;
|
pid_t child_pid;
|
||||||
const char* argv[] = { "/bin/keymap", keymap.characters(), nullptr };
|
const char* argv[] = { "/bin/keymap", "-m", keymaps_vector.first().characters(), nullptr };
|
||||||
if ((errno = posix_spawn(&child_pid, "/bin/keymap", nullptr, nullptr, const_cast<char**>(argv), environ))) {
|
if ((errno = posix_spawn(&child_pid, "/bin/keymap", nullptr, nullptr, const_cast<char**>(argv), environ))) {
|
||||||
perror("posix_spawn");
|
perror("posix_spawn");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/JsonObject.h>
|
#include <AK/JsonObject.h>
|
||||||
|
#include <LibCore/ConfigFile.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
#include <WindowServer/KeymapSwitcher.h>
|
#include <WindowServer/KeymapSwitcher.h>
|
||||||
#include <spawn.h>
|
#include <spawn.h>
|
||||||
|
@ -14,6 +15,15 @@ namespace WindowServer {
|
||||||
|
|
||||||
KeymapSwitcher::KeymapSwitcher()
|
KeymapSwitcher::KeymapSwitcher()
|
||||||
{
|
{
|
||||||
|
m_file_watcher = MUST(Core::FileWatcher::create());
|
||||||
|
|
||||||
|
m_file_watcher->on_change = [this](auto&) {
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
MUST(m_file_watcher->add_watch(m_keyboard_config, Core::FileWatcherEvent::Type::ContentModified));
|
||||||
|
|
||||||
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
KeymapSwitcher::~KeymapSwitcher()
|
KeymapSwitcher::~KeymapSwitcher()
|
||||||
|
@ -24,15 +34,34 @@ void KeymapSwitcher::refresh()
|
||||||
{
|
{
|
||||||
m_keymaps.clear();
|
m_keymaps.clear();
|
||||||
|
|
||||||
//TODO: load keymaps from file
|
auto mapper_config(Core::ConfigFile::open(m_keyboard_config));
|
||||||
m_keymaps.append("en-us");
|
auto keymaps = mapper_config->read_entry("Mapping", "Keymaps", "");
|
||||||
m_keymaps.append("ru");
|
|
||||||
|
auto keymaps_vector = keymaps.split(',');
|
||||||
|
|
||||||
|
for (auto& keymap : keymaps_vector) {
|
||||||
|
m_keymaps.append(keymap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_keymaps.is_empty()) {
|
||||||
|
dbgln("Empty list of keymaps - adding default (en-us)");
|
||||||
|
m_keymaps.append("en-us");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto current_keymap = get_current_keymap();
|
||||||
|
|
||||||
|
// Refresh might indicate that some external program has changed the keymap,
|
||||||
|
// so better notify our clients that we may have a new keymap
|
||||||
|
if (on_keymap_change)
|
||||||
|
on_keymap_change(current_keymap);
|
||||||
|
|
||||||
|
if (m_keymaps.find(current_keymap).is_end()) {
|
||||||
|
setkeymap(m_keymaps.first());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeymapSwitcher::next_keymap()
|
void KeymapSwitcher::next_keymap()
|
||||||
{
|
{
|
||||||
refresh();
|
|
||||||
|
|
||||||
if (m_keymaps.is_empty()) {
|
if (m_keymaps.is_empty()) {
|
||||||
dbgln("No keymaps loaded - leaving system keymap unchanged");
|
dbgln("No keymaps loaded - leaving system keymap unchanged");
|
||||||
return; // TODO: figure out what to do when there is no keymap configured
|
return; // TODO: figure out what to do when there is no keymap configured
|
||||||
|
@ -77,7 +106,7 @@ String KeymapSwitcher::get_current_keymap() const
|
||||||
void KeymapSwitcher::setkeymap(const AK::String& keymap)
|
void KeymapSwitcher::setkeymap(const AK::String& keymap)
|
||||||
{
|
{
|
||||||
pid_t child_pid;
|
pid_t child_pid;
|
||||||
const char* argv[] = { "/bin/keymap", keymap.characters(), nullptr };
|
const char* argv[] = { "/bin/keymap", "-m", keymap.characters(), nullptr };
|
||||||
if ((errno = posix_spawn(&child_pid, "/bin/keymap", nullptr, nullptr, const_cast<char**>(argv), environ))) {
|
if ((errno = posix_spawn(&child_pid, "/bin/keymap", nullptr, nullptr, const_cast<char**>(argv), environ))) {
|
||||||
perror("posix_spawn");
|
perror("posix_spawn");
|
||||||
dbgln("Failed to call /bin/keymap, error: {} ({})", errno, strerror(errno));
|
dbgln("Failed to call /bin/keymap, error: {} ({})", errno, strerror(errno));
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <AK/WeakPtr.h>
|
#include <AK/WeakPtr.h>
|
||||||
|
#include <LibCore/FileWatcher.h>
|
||||||
#include <LibCore/Object.h>
|
#include <LibCore/Object.h>
|
||||||
#include <LibKeyboard/CharacterMap.h>
|
#include <LibKeyboard/CharacterMap.h>
|
||||||
#include <WindowServer/WMClientConnection.h>
|
#include <WindowServer/WMClientConnection.h>
|
||||||
|
@ -20,8 +21,6 @@ class KeymapSwitcher final : public Core::Object {
|
||||||
public:
|
public:
|
||||||
virtual ~KeymapSwitcher() override;
|
virtual ~KeymapSwitcher() override;
|
||||||
|
|
||||||
void refresh();
|
|
||||||
|
|
||||||
void next_keymap();
|
void next_keymap();
|
||||||
|
|
||||||
Function<void(String const& keymap)> on_keymap_change;
|
Function<void(String const& keymap)> on_keymap_change;
|
||||||
|
@ -29,11 +28,17 @@ public:
|
||||||
String get_current_keymap() const;
|
String get_current_keymap() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void refresh();
|
||||||
|
|
||||||
KeymapSwitcher();
|
KeymapSwitcher();
|
||||||
|
|
||||||
Vector<AK::String> m_keymaps;
|
Vector<AK::String> m_keymaps;
|
||||||
|
|
||||||
void setkeymap(AK::String const&);
|
void setkeymap(AK::String const&);
|
||||||
|
|
||||||
|
RefPtr<Core::FileWatcher> m_file_watcher;
|
||||||
|
|
||||||
|
const char* m_keyboard_config = "/etc/Keyboard.ini";
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ ErrorOr<int> serenity_main(Main::Arguments)
|
||||||
TRY(Core::System::unveil("/res", "r"));
|
TRY(Core::System::unveil("/res", "r"));
|
||||||
TRY(Core::System::unveil("/tmp", "cw"));
|
TRY(Core::System::unveil("/tmp", "cw"));
|
||||||
TRY(Core::System::unveil("/etc/WindowServer.ini", "rwc"));
|
TRY(Core::System::unveil("/etc/WindowServer.ini", "rwc"));
|
||||||
|
TRY(Core::System::unveil("/etc/Keyboard.ini", "r"));
|
||||||
TRY(Core::System::unveil("/dev", "rw"));
|
TRY(Core::System::unveil("/dev", "rw"));
|
||||||
TRY(Core::System::unveil("/bin/keymap", "x"));
|
TRY(Core::System::unveil("/bin/keymap", "x"));
|
||||||
TRY(Core::System::unveil("/proc/keymap", "r"));
|
TRY(Core::System::unveil("/proc/keymap", "r"));
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <AK/Vector.h>
|
||||||
#include <LibCore/ArgsParser.h>
|
#include <LibCore/ArgsParser.h>
|
||||||
#include <LibCore/ConfigFile.h>
|
#include <LibCore/ConfigFile.h>
|
||||||
#include <LibCore/System.h>
|
#include <LibCore/System.h>
|
||||||
|
@ -11,44 +13,96 @@
|
||||||
#include <LibMain/Main.h>
|
#include <LibMain/Main.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
int set_keymap(String const& keymap);
|
||||||
|
|
||||||
|
int set_keymap(String const& keymap)
|
||||||
{
|
{
|
||||||
TRY(Core::System::pledge("stdio setkeymap getkeymap rpath wpath cpath"));
|
auto character_map = Keyboard::CharacterMap::load_from_file(keymap);
|
||||||
TRY(Core::System::unveil("/res/keymaps", "r"));
|
|
||||||
TRY(Core::System::unveil("/etc/Keyboard.ini", "rwc"));
|
|
||||||
|
|
||||||
const char* path = nullptr;
|
|
||||||
Core::ArgsParser args_parser;
|
|
||||||
args_parser.add_positional_argument(path, "The mapping file to be used", "file", Core::ArgsParser::Required::No);
|
|
||||||
args_parser.parse(arguments);
|
|
||||||
|
|
||||||
if (path && path[0] == '/')
|
|
||||||
TRY(Core::System::unveil(path, "r"));
|
|
||||||
|
|
||||||
TRY(Core::System::unveil(nullptr, nullptr));
|
|
||||||
|
|
||||||
if (!path) {
|
|
||||||
auto keymap = TRY(Keyboard::CharacterMap::fetch_system_map());
|
|
||||||
outln("{}", keymap.character_map_name());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto character_map = Keyboard::CharacterMap::load_from_file(path);
|
|
||||||
if (character_map.is_error()) {
|
if (character_map.is_error()) {
|
||||||
warnln("Cannot read keymap {}", path);
|
warnln("Cannot read keymap {}", keymap);
|
||||||
warnln("Hint: Must be either a keymap name (e.g. 'en-us') or a full path.");
|
warnln("Hint: Must be a keymap name (e.g. 'en-us')");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rc = character_map.value().set_system_map();
|
int rc = character_map.value().set_system_map();
|
||||||
if (rc != 0) {
|
if (rc != 0) {
|
||||||
perror("setkeymap");
|
perror("setkeymap");
|
||||||
return rc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mapper_config(Core::ConfigFile::open("/etc/Keyboard.ini", Core::ConfigFile::AllowWriting::Yes));
|
|
||||||
mapper_config->write_entry("Mapping", "Keymap", path);
|
|
||||||
mapper_config->sync();
|
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
|
{
|
||||||
|
TRY(Core::System::pledge("stdio setkeymap getkeymap rpath wpath cpath"));
|
||||||
|
TRY(Core::System::unveil("/res/keymaps", "r"));
|
||||||
|
TRY(Core::System::unveil("/etc/Keyboard.ini", "rwc"));
|
||||||
|
|
||||||
|
String mapping;
|
||||||
|
String mappings;
|
||||||
|
Core::ArgsParser args_parser;
|
||||||
|
args_parser.add_option(mapping, "The mapping to be used", "set-keymap", 'm', "keymap");
|
||||||
|
args_parser.add_option(mappings, "Comma separated list of enabled mappings", "set-keymaps", 's', "keymaps");
|
||||||
|
args_parser.parse(arguments);
|
||||||
|
|
||||||
|
TRY(Core::System::unveil(nullptr, nullptr));
|
||||||
|
|
||||||
|
if (mapping.is_empty() && mappings.is_empty()) {
|
||||||
|
auto keymap = TRY(Keyboard::CharacterMap::fetch_system_map());
|
||||||
|
outln("{}", keymap.character_map_name());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mapper_config(Core::ConfigFile::open("/etc/Keyboard.ini", Core::ConfigFile::AllowWriting::Yes));
|
||||||
|
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
if (!mappings.is_empty()) {
|
||||||
|
auto mappings_vector = mappings.split(',');
|
||||||
|
|
||||||
|
if (mappings_vector.is_empty()) {
|
||||||
|
warnln("Keymaps list should not be empty");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that all specified keymaps are loadable
|
||||||
|
for (auto& keymap_name : mappings_vector) {
|
||||||
|
auto keymap = Keyboard::CharacterMap::load_from_file(keymap_name);
|
||||||
|
if (keymap.is_error()) {
|
||||||
|
warnln("Cannot load keymap {}: {}({})", keymap_name, keymap.error().string_literal(), keymap.error().code());
|
||||||
|
return keymap.error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto keymaps = String::join(',', mappings_vector);
|
||||||
|
mapper_config->write_entry("Mapping", "Keymaps", keymaps);
|
||||||
|
mapper_config->sync();
|
||||||
|
rc = set_keymap(mappings_vector.first());
|
||||||
|
if (rc != 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto keymaps = mapper_config->read_entry("Mapping", "Keymaps");
|
||||||
|
auto keymaps_vector = keymaps.split(',');
|
||||||
|
|
||||||
|
if (!mapping.is_empty()) {
|
||||||
|
if (keymaps_vector.is_empty()) {
|
||||||
|
warnln("No keymaps configured - writing default configurations (en-us)");
|
||||||
|
mapper_config->write_entry("Mapping", "Keymaps", "en-us");
|
||||||
|
mapper_config->sync();
|
||||||
|
keymaps_vector.append("en-us");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keymaps_vector.find(mapping).is_end()) {
|
||||||
|
rc = set_keymap(mapping);
|
||||||
|
if (rc != 0) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warnln("Keymap '{}' is not in list of configured keymaps ({})", mapping, keymaps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue