Serenity Operating System
1/*
2 * Copyright (c) 2021, Timur Sultanov <SultanovTS@yandex.ru>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/JsonObject.h>
8#include <LibCore/ConfigFile.h>
9#include <LibCore/DeprecatedFile.h>
10#include <LibCore/Process.h>
11#include <WindowServer/KeymapSwitcher.h>
12#include <spawn.h>
13#include <unistd.h>
14
15namespace WindowServer {
16
17KeymapSwitcher::KeymapSwitcher()
18{
19 m_file_watcher = MUST(Core::FileWatcher::create());
20
21 m_file_watcher->on_change = [this](auto&) {
22 refresh();
23 };
24
25 MUST(m_file_watcher->add_watch(m_keyboard_config, Core::FileWatcherEvent::Type::ContentModified));
26
27 refresh();
28}
29
30void KeymapSwitcher::refresh()
31{
32 m_keymaps.clear();
33
34 auto mapper_config(Core::ConfigFile::open(m_keyboard_config).release_value_but_fixme_should_propagate_errors());
35 auto keymaps = mapper_config->read_entry("Mapping", "Keymaps", "");
36
37 auto keymaps_vector = keymaps.split(',');
38
39 for (auto& keymap : keymaps_vector) {
40 m_keymaps.append(keymap);
41 }
42
43 if (m_keymaps.is_empty()) {
44 dbgln("Empty list of keymaps - adding default (en-us)");
45 m_keymaps.append("en-us");
46 }
47
48 auto current_keymap = get_current_keymap();
49
50 // Refresh might indicate that some external program has changed the keymap,
51 // so better notify our clients that we may have a new keymap
52 if (on_keymap_change)
53 on_keymap_change(current_keymap);
54
55 if (m_keymaps.find(current_keymap).is_end()) {
56 set_keymap(m_keymaps.first());
57 }
58}
59
60void KeymapSwitcher::next_keymap()
61{
62 if (m_keymaps.is_empty()) {
63 dbgln("No keymaps loaded - leaving system keymap unchanged");
64 return; // TODO: figure out what to do when there is no keymap configured
65 }
66
67 auto current_keymap_name = get_current_keymap();
68
69 dbgln("Current system keymap: {}", current_keymap_name);
70
71 auto it = m_keymaps.find_if([&](auto const& enumerator) {
72 return enumerator == current_keymap_name;
73 });
74
75 if (it.is_end()) {
76 auto first_keymap = m_keymaps.first();
77 dbgln("Cannot find current keymap in the keymap list - setting first available ({})", first_keymap);
78 set_keymap(first_keymap);
79 } else {
80 it++;
81
82 if (it.is_end()) {
83 it = m_keymaps.begin();
84 }
85
86 dbgln("Setting system keymap to: {}", *it);
87 set_keymap(*it);
88 }
89}
90
91DeprecatedString KeymapSwitcher::get_current_keymap() const
92{
93 auto proc_keymap = Core::DeprecatedFile::construct("/sys/kernel/keymap");
94 if (!proc_keymap->open(Core::OpenMode::ReadOnly))
95 VERIFY_NOT_REACHED();
96
97 auto json = JsonValue::from_string(proc_keymap->read_all()).release_value_but_fixme_should_propagate_errors();
98 auto const& keymap_object = json.as_object();
99 VERIFY(keymap_object.has_string("keymap"sv));
100 return keymap_object.get_deprecated_string("keymap"sv).value();
101}
102
103void KeymapSwitcher::set_keymap(const AK::DeprecatedString& keymap)
104{
105 if (Core::Process::spawn("/bin/keymap"sv, Array { "-m", keymap.characters() }).is_error())
106 dbgln("Failed to call /bin/keymap, error: {} ({})", errno, strerror(errno));
107
108 if (on_keymap_change)
109 on_keymap_change(keymap);
110}
111
112}