KeyboardSettingsWidget.cpp 10 KB

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