Serenity Operating System
at master 291 lines 11 kB view raw
1/* 2 * Copyright (c) 2020, Hüseyin Aslıtürk <asliturk@hotmail.com> 3 * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> 4 * Copyright (c) 2022, the SerenityOS developers. 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include "KeyboardSettingsWidget.h" 10#include <AK/JsonObject.h> 11#include <AK/QuickSort.h> 12#include <Applications/KeyboardSettings/KeyboardWidgetGML.h> 13#include <Applications/KeyboardSettings/KeymapDialogGML.h> 14#include <LibConfig/Client.h> 15#include <LibCore/DeprecatedFile.h> 16#include <LibCore/Directory.h> 17#include <LibGUI/Application.h> 18#include <LibGUI/ComboBox.h> 19#include <LibGUI/Dialog.h> 20#include <LibGUI/ItemListModel.h> 21#include <LibGUI/Label.h> 22#include <LibGUI/MessageBox.h> 23#include <LibGUI/Model.h> 24#include <LibGUI/Process.h> 25#include <LibGUI/Widget.h> 26#include <LibGUI/Window.h> 27#include <LibGfx/Font/FontDatabase.h> 28#include <LibKeyboard/CharacterMap.h> 29#include <spawn.h> 30 31class KeymapSelectionDialog final : public GUI::Dialog { 32 C_OBJECT(KeymapSelectionDialog) 33public: 34 virtual ~KeymapSelectionDialog() override = default; 35 36 static DeprecatedString select_keymap(Window* parent_window, Vector<DeprecatedString> const& selected_keymaps) 37 { 38 auto dialog = KeymapSelectionDialog::construct(parent_window, selected_keymaps); 39 dialog->set_title("Add a keymap"); 40 41 if (dialog->exec() == ExecResult::OK) { 42 return dialog->selected_keymap(); 43 } 44 45 return DeprecatedString::empty(); 46 } 47 48 DeprecatedString selected_keymap() { return m_selected_keymap; } 49 50private: 51 KeymapSelectionDialog(Window* parent_window, Vector<DeprecatedString> const& selected_keymaps) 52 : Dialog(parent_window) 53 { 54 auto widget = set_main_widget<GUI::Widget>().release_value_but_fixme_should_propagate_errors(); 55 widget->load_from_gml(keymap_dialog_gml).release_value_but_fixme_should_propagate_errors(); 56 57 set_resizable(false); 58 resize(190, 54); 59 60 set_icon(parent_window->icon()); 61 62 auto iterator_result = Core::Directory::for_each_entry("/res/keymaps/"sv, Core::DirIterator::Flags::SkipDots, [&](auto const& entry, auto&) -> ErrorOr<IterationDecision> { 63 auto basename = entry.name.replace(".json"sv, ""sv, ReplaceMode::FirstOnly); 64 if (selected_keymaps.find(basename).is_end()) 65 m_character_map_files.append(basename); 66 return IterationDecision::Continue; 67 }); 68 69 if (iterator_result.is_error()) { 70 GUI::MessageBox::show(nullptr, DeprecatedString::formatted("Error on reading mapping file list: {}", iterator_result.error()), "Keyboard settings"sv, GUI::MessageBox::Type::Error); 71 GUI::Application::the()->quit(-1); 72 } 73 74 quick_sort(m_character_map_files); 75 76 m_selected_keymap = m_character_map_files.first(); 77 78 m_keymaps_combobox = *widget->find_descendant_of_type_named<GUI::ComboBox>("keymaps_combobox"); 79 m_keymaps_combobox->set_only_allow_values_from_model(true); 80 m_keymaps_combobox->set_model(*GUI::ItemListModel<DeprecatedString>::create(m_character_map_files)); 81 m_keymaps_combobox->set_selected_index(0); 82 83 m_keymaps_combobox->on_change = [&](auto& keymap, auto) { 84 m_selected_keymap = keymap; 85 }; 86 87 auto& ok_button = *widget->find_descendant_of_type_named<GUI::Button>("ok_button"); 88 ok_button.on_click = [this](auto) { 89 done(ExecResult::OK); 90 }; 91 92 auto& cancel_button = *widget->find_descendant_of_type_named<GUI::Button>("cancel_button"); 93 cancel_button.on_click = [this](auto) { 94 done(ExecResult::Cancel); 95 }; 96 } 97 98 RefPtr<GUI::ComboBox> m_keymaps_combobox; 99 Vector<DeprecatedString> m_character_map_files; 100 DeprecatedString m_selected_keymap; 101}; 102 103class KeymapModel final : public GUI::Model { 104public: 105 KeymapModel() {}; 106 107 int row_count(GUI::ModelIndex const&) const override { return m_data.size(); } 108 int column_count(GUI::ModelIndex const&) const override { return 1; } 109 110 GUI::Variant data(GUI::ModelIndex const& index, GUI::ModelRole role) const override 111 { 112 DeprecatedString const& data = m_data.at(index.row()); 113 if (role == GUI::ModelRole::Font && data == m_active_keymap) 114 return Gfx::FontDatabase::default_font().bold_variant(); 115 116 return data; 117 } 118 119 void remove_at(size_t index) 120 { 121 m_data.remove(index); 122 invalidate(); 123 } 124 125 void add_keymap(DeprecatedString const& keymap) 126 { 127 m_data.append(keymap); 128 invalidate(); 129 } 130 131 void set_active_keymap(DeprecatedString const& keymap) 132 { 133 m_active_keymap = keymap; 134 invalidate(); 135 } 136 137 DeprecatedString const& active_keymap() { return m_active_keymap; } 138 139 DeprecatedString const& keymap_at(size_t index) 140 { 141 return m_data[index]; 142 } 143 144 Vector<DeprecatedString> const& keymaps() const { return m_data; } 145 146private: 147 Vector<DeprecatedString> m_data; 148 DeprecatedString m_active_keymap; 149}; 150 151KeyboardSettingsWidget::KeyboardSettingsWidget() 152{ 153 load_from_gml(keyboard_widget_gml).release_value_but_fixme_should_propagate_errors(); 154 155 auto proc_keymap = Core::DeprecatedFile::construct("/sys/kernel/keymap"); 156 if (!proc_keymap->open(Core::OpenMode::ReadOnly)) 157 VERIFY_NOT_REACHED(); 158 159 auto json = JsonValue::from_string(proc_keymap->read_all()).release_value_but_fixme_should_propagate_errors(); 160 auto const& keymap_object = json.as_object(); 161 VERIFY(keymap_object.has("keymap"sv)); 162 m_initial_active_keymap = keymap_object.get_deprecated_string("keymap"sv).value(); 163 dbgln("KeyboardSettings thinks the current keymap is: {}", m_initial_active_keymap); 164 165 auto mapper_config(Core::ConfigFile::open("/etc/Keyboard.ini").release_value_but_fixme_should_propagate_errors()); 166 auto keymaps = mapper_config->read_entry("Mapping", "Keymaps", ""); 167 168 auto keymaps_vector = keymaps.split(','); 169 170 m_selected_keymaps_listview = find_descendant_of_type_named<GUI::ListView>("selected_keymaps"); 171 m_selected_keymaps_listview->horizontal_scrollbar().set_visible(false); 172 m_selected_keymaps_listview->set_model(adopt_ref(*new KeymapModel())); 173 auto& keymaps_list_model = static_cast<KeymapModel&>(*m_selected_keymaps_listview->model()); 174 175 for (auto& keymap : keymaps_vector) { 176 m_initial_keymap_list.append(keymap); 177 keymaps_list_model.add_keymap(keymap); 178 } 179 180 keymaps_list_model.set_active_keymap(m_initial_active_keymap); 181 182 m_activate_keymap_button = find_descendant_of_type_named<GUI::Button>("activate_keymap_button"); 183 184 m_activate_keymap_event = [&]() { 185 auto& selection = m_selected_keymaps_listview->selection(); 186 if (!selection.is_empty()) { 187 auto& selected_keymap = keymaps_list_model.keymap_at(selection.first().row()); 188 keymaps_list_model.set_active_keymap(selected_keymap); 189 set_modified(true); 190 } 191 }; 192 193 m_activate_keymap_button->on_click = [&](auto) { 194 m_activate_keymap_event(); 195 }; 196 197 m_selected_keymaps_listview->on_activation = [&](auto) { 198 m_activate_keymap_event(); 199 }; 200 201 m_add_keymap_button = find_descendant_of_type_named<GUI::Button>("add_keymap_button"); 202 203 m_add_keymap_button->on_click = [&](auto) { 204 auto keymap = KeymapSelectionDialog::select_keymap(window(), keymaps_list_model.keymaps()); 205 if (!keymap.is_empty()) { 206 keymaps_list_model.add_keymap(keymap); 207 set_modified(true); 208 } 209 }; 210 211 m_remove_keymap_button = find_descendant_of_type_named<GUI::Button>("remove_keymap_button"); 212 213 m_remove_keymap_button->on_click = [&](auto) { 214 auto& selection = m_selected_keymaps_listview->selection(); 215 bool active_keymap_deleted = false; 216 for (auto& index : selection.indices()) { 217 if (keymaps_list_model.keymap_at(index.row()) == keymaps_list_model.active_keymap()) 218 active_keymap_deleted = true; 219 keymaps_list_model.remove_at(index.row()); 220 } 221 if (active_keymap_deleted) 222 keymaps_list_model.set_active_keymap(keymaps_list_model.keymap_at(0)); 223 set_modified(true); 224 }; 225 226 m_selected_keymaps_listview->on_selection_change = [&]() { 227 auto& selection = m_selected_keymaps_listview->selection(); 228 m_remove_keymap_button->set_enabled(!selection.is_empty() && keymaps_list_model.keymaps().size() > 1); 229 if (selection.is_empty()) { 230 m_activate_keymap_button->set_enabled(false); 231 } else { 232 auto& highlighted_keymap = keymaps_list_model.keymap_at(selection.first().row()); 233 auto& active_keymap = keymaps_list_model.active_keymap(); 234 m_activate_keymap_button->set_enabled(highlighted_keymap != active_keymap); 235 } 236 }; 237 238 m_test_typing_area = *find_descendant_of_type_named<GUI::TextEditor>("test_typing_area"); 239 m_test_typing_area->on_focusin = [&]() { 240 set_keymaps(keymaps_list_model.keymaps(), keymaps_list_model.active_keymap()); 241 }; 242 243 m_test_typing_area->on_focusout = [&]() { 244 set_keymaps(m_initial_keymap_list, m_initial_active_keymap); 245 }; 246 247 m_clear_test_typing_area_button = find_descendant_of_type_named<GUI::Button>("button_clear_test_typing_area"); 248 m_clear_test_typing_area_button->on_click = [this](auto) { 249 m_test_typing_area->clear(); 250 m_test_typing_area->set_focus(true); 251 }; 252 253 m_num_lock_checkbox = find_descendant_of_type_named<GUI::CheckBox>("num_lock_checkbox"); 254 m_num_lock_checkbox->set_checked(Config::read_bool("KeyboardSettings"sv, "StartupEnable"sv, "NumLock"sv, true)); 255 m_num_lock_checkbox->on_checked = [&](auto) { 256 set_modified(true); 257 }; 258} 259 260KeyboardSettingsWidget::~KeyboardSettingsWidget() 261{ 262 set_keymaps(m_initial_keymap_list, m_initial_active_keymap); 263} 264 265void KeyboardSettingsWidget::window_activated(bool is_active_window) 266{ 267 if (is_active_window && m_test_typing_area->is_focused()) { 268 auto& keymaps_list_model = static_cast<KeymapModel&>(*m_selected_keymaps_listview->model()); 269 set_keymaps(keymaps_list_model.keymaps(), keymaps_list_model.active_keymap()); 270 } else { 271 set_keymaps(m_initial_keymap_list, m_initial_active_keymap); 272 } 273} 274 275void KeyboardSettingsWidget::apply_settings() 276{ 277 auto& m_keymaps_list_model = static_cast<KeymapModel&>(*m_selected_keymaps_listview->model()); 278 set_keymaps(m_keymaps_list_model.keymaps(), m_keymaps_list_model.active_keymap()); 279 m_initial_keymap_list.clear(); 280 for (auto& keymap : m_keymaps_list_model.keymaps()) { 281 m_initial_keymap_list.append(keymap); 282 } 283 m_initial_active_keymap = m_keymaps_list_model.active_keymap(); 284 Config::write_bool("KeyboardSettings"sv, "StartupEnable"sv, "NumLock"sv, m_num_lock_checkbox->is_checked()); 285} 286 287void KeyboardSettingsWidget::set_keymaps(Vector<DeprecatedString> const& keymaps, DeprecatedString const& active_keymap) 288{ 289 auto keymaps_string = DeprecatedString::join(',', keymaps); 290 GUI::Process::spawn_or_show_error(window(), "/bin/keymap"sv, Array { "-s", keymaps_string.characters(), "-m", active_keymap.characters() }); 291}