Files
banan-os/userspace/libraries/LibInput/KeyboardLayout.cpp
Bananymous 40f3546aca BAN: Rewrite HashMap as a wrapper around HashSet
There is really no need to have two implementation of the same thing.
Only difference now is that HashMap's value type has to be movable but
this wasn't an issue
2026-04-21 00:18:18 +03:00

473 lines
17 KiB
C++

#include <BAN/Debug.h>
#include <BAN/HashMap.h>
#include <BAN/String.h>
#include <BAN/StringView.h>
#include <LibInput/KeyboardLayout.h>
#if __is_kernel
#include <kernel/FS/VirtualFileSystem.h>
#include <kernel/Process.h>
#else
#include <fcntl.h>
#include <limits.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#include <ctype.h>
namespace LibInput
{
struct StringViewLowerComp
{
constexpr bool operator()(const BAN::StringView& a, const BAN::StringView& b) const
{
if (a.size() != b.size())
return false;
for (size_t i = 0; i < a.size(); i++)
if (tolower(a[i]) != tolower(b[i]))
return false;
return true;
}
};
struct StringViewLowerHash
{
BAN::hash_t operator()(const BAN::StringView& str) const
{
constexpr BAN::hash_t FNV_offset_basis = 0x811c9dc5;
constexpr BAN::hash_t FNV_prime = 0x01000193;
BAN::hash_t hash = FNV_offset_basis;
for (size_t i = 0; i < str.size(); i++)
{
hash *= FNV_prime;
hash ^= (uint8_t)tolower(str[i]);
}
return hash;
}
};
static BAN::UniqPtr<KeyboardLayout> s_instance;
BAN::ErrorOr<void> KeyboardLayout::initialize()
{
ASSERT(!s_instance);
s_instance = TRY(BAN::UniqPtr<KeyboardLayout>::create());
return {};
}
KeyboardLayout& KeyboardLayout::get()
{
ASSERT(s_instance);
return *s_instance;
}
KeyboardLayout::KeyboardLayout()
{
for (auto& key : m_keycode_to_key_normal)
key = Key::None;
for (auto& key : m_keycode_to_key_shift)
key = Key::None;
for (auto& key : m_keycode_to_key_altgr)
key = Key::None;
}
KeyEvent KeyboardLayout::key_event_from_raw(RawKeyEvent event)
{
KeyEvent result;
result.modifier = event.modifier;
result.scancode = event.keycode;
if (result.shift())
result.key = m_keycode_to_key_shift[event.keycode];
else if (result.ralt())
result.key = m_keycode_to_key_altgr[event.keycode];
else
result.key = m_keycode_to_key_normal[event.keycode];
return result;
}
static BAN::Optional<uint8_t> parse_keycode(BAN::StringView str)
{
if (str.size() > 3)
return {};
uint16_t keycode = 0;
for (char c : str)
{
if (!isdigit(c))
return {};
keycode = (keycode * 10) + (c - '0');
}
if (keycode > 0xFF)
return {};
return keycode;
}
static BAN::HashMap<BAN::StringView, Key, StringViewLowerHash, StringViewLowerComp> s_name_to_key;
static BAN::ErrorOr<void> initialize_name_to_key();
static BAN::Optional<Key> parse_key(BAN::StringView name)
{
if (s_name_to_key.contains(name))
return s_name_to_key[name];
return {};
}
static BAN::ErrorOr<BAN::Vector<BAN::String>> load_keymap_lines_and_parse_includes(BAN::StringView path)
{
BAN::String file_data;
BAN::String canonical_path;
#if __is_kernel
{
auto& process = Kernel::Process::current();
auto file = TRY(Kernel::VirtualFileSystem::get().file_from_absolute_path(process.root_file().inode, process.credentials(), path, O_RDONLY));
TRY(file_data.resize(file.inode->size()));
TRY(file.inode->read(0, BAN::ByteSpan { reinterpret_cast<uint8_t*>(file_data.data()), file_data.size() }));
canonical_path = file.canonical_path;
}
#else
{
char null_path[PATH_MAX];
strncpy(null_path, path.data(), path.size());
null_path[path.size()] = '\0';
struct stat st;
if (stat(null_path, &st) == -1)
return BAN::Error::from_errno(errno);
TRY(file_data.resize(st.st_size));
int fd = open(null_path, O_RDONLY);
if (fd == -1)
return BAN::Error::from_errno(errno);
ssize_t nread = read(fd, file_data.data(), st.st_size);
close(fd);
if (nread != st.st_size)
return BAN::Error::from_errno(errno);
MUST(canonical_path.append(path));
}
#endif
BAN::Vector<BAN::String> result;
auto lines = TRY(file_data.sv().split('\n'));
for (auto line : lines)
{
auto parts = TRY(line.split([](char c) -> bool { return isspace(c); }));
if (parts.empty() || parts.front().front() == '#')
continue;
if (parts.front() == "include"_sv)
{
if (parts.size() != 2)
{
dprintln("Invalid modifier instruction in keymap '{}'", line);
dprintln(" format: include \"PATH\"");
return BAN::Error::from_errno(EINVAL);
}
if (parts[1].size() < 2 || parts[1].front() != '"' || parts[1].back() != '"')
{
dprintln("Invalid modifier instruction in keymap '{}'", line);
dprintln(" format: include \"PATH\"");
return BAN::Error::from_errno(EINVAL);
}
parts[1] = parts[1].substring(1, parts[1].size() - 2);
BAN::String include_path;
TRY(include_path.append(canonical_path));
ASSERT(include_path.sv().contains('/'));
while (include_path.back() != '/')
include_path.pop_back();
TRY(include_path.append(parts[1]));
auto new_lines = TRY(load_keymap_lines_and_parse_includes(include_path));
TRY(result.reserve(result.size() + new_lines.size()));
for (auto& line : new_lines)
TRY(result.push_back(BAN::move(line)));
}
else
{
BAN::String line_str;
TRY(line_str.append(line));
TRY(result.push_back(BAN::move(line_str)));
}
}
return result;
}
BAN::ErrorOr<void> KeyboardLayout::load_from_file(BAN::StringView path)
{
if (s_name_to_key.empty())
TRY(initialize_name_to_key());
auto new_layout = TRY(BAN::UniqPtr<KeyboardLayout>::create());
bool shift_is_mod = false;
bool altgr_is_mod = false;
auto lines = TRY(load_keymap_lines_and_parse_includes(path));
for (const auto& line : lines)
{
auto parts = TRY(line.sv().split([](char c) -> bool { return isspace(c); }));
if (parts.empty() || parts.front().front() == '#')
continue;
if (parts.size() == 1)
{
dprintln("Invalid line in keymap '{}'", line);
dprintln(" format: KEYCODE KEY [MODIFIER=KEY]...");
dprintln(" format: mod MODIFIER");
dprintln(" format: include \"PATH\"");
return BAN::Error::from_errno(EINVAL);
}
if (parts.front() == "mod"_sv)
{
if (parts.size() != 2)
{
dprintln("Invalid modifier instruction in keymap '{}'", line);
dprintln(" format: mod MODIFIER");
return BAN::Error::from_errno(EINVAL);
}
if (parts[1] == "shift"_sv)
shift_is_mod = true;
else if (parts[1] == "altgr"_sv)
altgr_is_mod = true;
else
{
dprintln("Unrecognized modifier '{}'", parts[1]);
return BAN::Error::from_errno(EINVAL);
}
continue;
}
auto keycode = parse_keycode(parts.front());
if (!keycode.has_value())
{
dprintln("Invalid keycode '{}', keycode must number between [0, 0xFF[", parts.front());
return BAN::Error::from_errno(EINVAL);
}
auto default_key = parse_key(parts[1]);
if (!default_key.has_value())
{
dprintln("Unrecognized key '{}'", parts[1]);
return BAN::Error::from_errno(EINVAL);
}
new_layout->m_keycode_to_key_normal[*keycode] = *default_key;
new_layout->m_keycode_to_key_shift[*keycode] = *default_key;
new_layout->m_keycode_to_key_altgr[*keycode] = *default_key;
for (size_t i = 2; i < parts.size(); i++)
{
auto pair = TRY(parts[i].split('='));
if (pair.size() != 2)
{
dprintln("Invalid modifier format '{}', modifier format: MODIFIRER=KEY", parts[i]);
return BAN::Error::from_errno(EINVAL);
}
auto key = parse_key(pair.back());
if (!key.has_value())
{
dprintln("Unrecognized key '{}'", pair.back());
return BAN::Error::from_errno(EINVAL);
}
if (shift_is_mod && pair.front() == "shift"_sv)
new_layout->m_keycode_to_key_shift[*keycode] = *key;
else if (altgr_is_mod && pair.front() == "altgr"_sv)
new_layout->m_keycode_to_key_altgr[*keycode] = *key;
else
{
dprintln("Unrecognized modifier '{}'", pair.front());
return BAN::Error::from_errno(EINVAL);
}
}
}
for (size_t i = 0; i < new_layout->m_keycode_to_key_normal.size(); i++)
if (new_layout->m_keycode_to_key_normal[i] != Key::None)
m_keycode_to_key_normal[i] = new_layout->m_keycode_to_key_normal[i];
for (size_t i = 0; i < new_layout->m_keycode_to_key_shift.size(); i++)
if (new_layout->m_keycode_to_key_shift[i] != Key::None)
m_keycode_to_key_shift[i] = new_layout->m_keycode_to_key_shift[i];
for (size_t i = 0; i < new_layout->m_keycode_to_key_altgr.size(); i++)
if (new_layout->m_keycode_to_key_altgr[i] != Key::None)
m_keycode_to_key_altgr[i] = new_layout->m_keycode_to_key_altgr[i];
return {};
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
static BAN::ErrorOr<void> initialize_name_to_key()
{
ASSERT(s_name_to_key.empty());
TRY(s_name_to_key.insert("A_Ring"_sv, Key::A_Ring));
TRY(s_name_to_key.insert("A_Umlaut"_sv, Key::A_Umlaut));
TRY(s_name_to_key.insert("A"_sv, Key::A));
TRY(s_name_to_key.insert("Acute"_sv, Key::Acute));
TRY(s_name_to_key.insert("AltGr"_sv, Key::AltGr));
TRY(s_name_to_key.insert("Ampersand"_sv, Key::Ampersand));
TRY(s_name_to_key.insert("ArrowDown"_sv, Key::ArrowDown));
TRY(s_name_to_key.insert("ArrowLeft"_sv, Key::ArrowLeft));
TRY(s_name_to_key.insert("ArrowRight"_sv, Key::ArrowRight));
TRY(s_name_to_key.insert("ArrowUp"_sv, Key::ArrowUp));
TRY(s_name_to_key.insert("Asterix"_sv, Key::Asterix));
TRY(s_name_to_key.insert("AtSign"_sv, Key::AtSign));
TRY(s_name_to_key.insert("B"_sv, Key::B));
TRY(s_name_to_key.insert("BackSlash"_sv, Key::BackSlash));
TRY(s_name_to_key.insert("Backspace"_sv, Key::Backspace));
TRY(s_name_to_key.insert("BackTick"_sv, Key::BackTick));
TRY(s_name_to_key.insert("BrokenBar"_sv, Key::BrokenBar));
TRY(s_name_to_key.insert("C"_sv, Key::C));
TRY(s_name_to_key.insert("Calculator"_sv, Key::Calculator));
TRY(s_name_to_key.insert("CapsLock"_sv, Key::CapsLock));
TRY(s_name_to_key.insert("Caret"_sv, Key::Caret));
TRY(s_name_to_key.insert("Cedilla"_sv, Key::Cedilla));
TRY(s_name_to_key.insert("CloseCurlyBracket"_sv, Key::CloseCurlyBracket));
TRY(s_name_to_key.insert("CloseParenthesis"_sv, Key::CloseParenthesis));
TRY(s_name_to_key.insert("CloseSquareBracket"_sv, Key::CloseSquareBracket));
TRY(s_name_to_key.insert("Colon"_sv, Key::Colon));
TRY(s_name_to_key.insert("Comma"_sv, Key::Comma));
TRY(s_name_to_key.insert("Currency"_sv, Key::Currency));
TRY(s_name_to_key.insert("D"_sv, Key::D));
TRY(s_name_to_key.insert("Delete"_sv, Key::Delete));
TRY(s_name_to_key.insert("Dollar"_sv, Key::Dollar));
TRY(s_name_to_key.insert("DoubleQuote"_sv, Key::DoubleQuote));
TRY(s_name_to_key.insert("E"_sv, Key::E));
TRY(s_name_to_key.insert("End"_sv, Key::End));
TRY(s_name_to_key.insert("Enter"_sv, Key::Enter));
TRY(s_name_to_key.insert("Equals"_sv, Key::Equals));
TRY(s_name_to_key.insert("Escape"_sv, Key::Escape));
TRY(s_name_to_key.insert("Euro"_sv, Key::Euro));
TRY(s_name_to_key.insert("Exclamation"_sv, Key::ExclamationMark));
TRY(s_name_to_key.insert("ExclamationMark"_sv, Key::ExclamationMark));
TRY(s_name_to_key.insert("F"_sv, Key::F));
TRY(s_name_to_key.insert("F1"_sv, Key::F1));
TRY(s_name_to_key.insert("F10"_sv, Key::F10));
TRY(s_name_to_key.insert("F11"_sv, Key::F11));
TRY(s_name_to_key.insert("F12"_sv, Key::F12));
TRY(s_name_to_key.insert("F2"_sv, Key::F2));
TRY(s_name_to_key.insert("F3"_sv, Key::F3));
TRY(s_name_to_key.insert("F4"_sv, Key::F4));
TRY(s_name_to_key.insert("F5"_sv, Key::F5));
TRY(s_name_to_key.insert("F6"_sv, Key::F6));
TRY(s_name_to_key.insert("F7"_sv, Key::F7));
TRY(s_name_to_key.insert("F8"_sv, Key::F8));
TRY(s_name_to_key.insert("F9"_sv, Key::F9));
TRY(s_name_to_key.insert("G"_sv, Key::G));
TRY(s_name_to_key.insert("GreaterThan"_sv, Key::GreaterThan));
TRY(s_name_to_key.insert("H"_sv, Key::H));
TRY(s_name_to_key.insert("Half"_sv, Key::Half));
TRY(s_name_to_key.insert("Hashtag"_sv, Key::Hashtag));
TRY(s_name_to_key.insert("Home"_sv, Key::Home));
TRY(s_name_to_key.insert("Hyphen"_sv, Key::Hyphen));
TRY(s_name_to_key.insert("I"_sv, Key::I));
TRY(s_name_to_key.insert("Insert"_sv, Key::Insert));
TRY(s_name_to_key.insert("J"_sv, Key::J));
TRY(s_name_to_key.insert("K"_sv, Key::K));
TRY(s_name_to_key.insert("Key0"_sv, Key::_0));
TRY(s_name_to_key.insert("Key1"_sv, Key::_1));
TRY(s_name_to_key.insert("Key2"_sv, Key::_2));
TRY(s_name_to_key.insert("Key3"_sv, Key::_3));
TRY(s_name_to_key.insert("Key4"_sv, Key::_4));
TRY(s_name_to_key.insert("Key5"_sv, Key::_5));
TRY(s_name_to_key.insert("Key6"_sv, Key::_6));
TRY(s_name_to_key.insert("Key7"_sv, Key::_7));
TRY(s_name_to_key.insert("Key8"_sv, Key::_8));
TRY(s_name_to_key.insert("Key9"_sv, Key::_9));
TRY(s_name_to_key.insert("L"_sv, Key::L));
TRY(s_name_to_key.insert("LAlt"_sv, Key::LeftAlt));
TRY(s_name_to_key.insert("LControl"_sv, Key::LeftCtrl));
TRY(s_name_to_key.insert("LeftAlt"_sv, Key::LeftAlt));
TRY(s_name_to_key.insert("LeftControl"_sv, Key::LeftCtrl));
TRY(s_name_to_key.insert("LeftShift"_sv, Key::LeftShift));
TRY(s_name_to_key.insert("LessThan"_sv, Key::LessThan));
TRY(s_name_to_key.insert("LShift"_sv, Key::LeftShift));
TRY(s_name_to_key.insert("M"_sv, Key::M));
TRY(s_name_to_key.insert("MediaNext"_sv, Key::MediaNext));
TRY(s_name_to_key.insert("MediaPlayPause"_sv, Key::MediaPlayPause));
TRY(s_name_to_key.insert("MediaPrevious"_sv, Key::MediaPrevious));
TRY(s_name_to_key.insert("MediaStop"_sv, Key::MediaStop));
TRY(s_name_to_key.insert("N"_sv, Key::N));
TRY(s_name_to_key.insert("Negation"_sv, Key::Negation));
TRY(s_name_to_key.insert("None"_sv, Key::None));
TRY(s_name_to_key.insert("NumLock"_sv, Key::NumLock));
TRY(s_name_to_key.insert("Numpad0"_sv, Key::Numpad0));
TRY(s_name_to_key.insert("Numpad1"_sv, Key::Numpad1));
TRY(s_name_to_key.insert("Numpad2"_sv, Key::Numpad2));
TRY(s_name_to_key.insert("Numpad3"_sv, Key::Numpad3));
TRY(s_name_to_key.insert("Numpad4"_sv, Key::Numpad4));
TRY(s_name_to_key.insert("Numpad5"_sv, Key::Numpad5));
TRY(s_name_to_key.insert("Numpad6"_sv, Key::Numpad6));
TRY(s_name_to_key.insert("Numpad7"_sv, Key::Numpad7));
TRY(s_name_to_key.insert("Numpad8"_sv, Key::Numpad8));
TRY(s_name_to_key.insert("Numpad9"_sv, Key::Numpad9));
TRY(s_name_to_key.insert("NumpadDecimal"_sv, Key::NumpadDecimal));
TRY(s_name_to_key.insert("NumpadDivide"_sv, Key::NumpadDivide));
TRY(s_name_to_key.insert("NumpadEnter"_sv, Key::NumpadEnter));
TRY(s_name_to_key.insert("NumpadMinus"_sv, Key::NumpadMinus));
TRY(s_name_to_key.insert("NumpadMultiply"_sv, Key::NumpadMultiply));
TRY(s_name_to_key.insert("NumpadPlus"_sv, Key::NumpadPlus));
TRY(s_name_to_key.insert("O_Umlaut"_sv, Key::O_Umlaut));
TRY(s_name_to_key.insert("O"_sv, Key::O));
TRY(s_name_to_key.insert("OpenCurlyBracket"_sv, Key::OpenCurlyBracket));
TRY(s_name_to_key.insert("OpenParenthesis"_sv, Key::OpenParenthesis));
TRY(s_name_to_key.insert("OpenSquareBracket"_sv, Key::OpenSquareBracket));
TRY(s_name_to_key.insert("P"_sv, Key::P));
TRY(s_name_to_key.insert("PageDown"_sv, Key::PageDown));
TRY(s_name_to_key.insert("PageUp"_sv, Key::PageUp));
TRY(s_name_to_key.insert("Percent"_sv, Key::Percent));
TRY(s_name_to_key.insert("Period"_sv, Key::Period));
TRY(s_name_to_key.insert("Pipe"_sv, Key::Pipe));
TRY(s_name_to_key.insert("Plus"_sv, Key::Plus));
TRY(s_name_to_key.insert("Pound"_sv, Key::Pound));
TRY(s_name_to_key.insert("PrintScreen"_sv, Key::PrintScreen));
TRY(s_name_to_key.insert("Q"_sv, Key::Q));
TRY(s_name_to_key.insert("Question"_sv, Key::QuestionMark));
TRY(s_name_to_key.insert("QuestionMark"_sv, Key::QuestionMark));
TRY(s_name_to_key.insert("R"_sv, Key::R));
TRY(s_name_to_key.insert("RAlt"_sv, Key::RightAlt));
TRY(s_name_to_key.insert("RControl"_sv, Key::RightCtrl));
TRY(s_name_to_key.insert("RightAlt"_sv, Key::RightAlt));
TRY(s_name_to_key.insert("RightControl"_sv, Key::RightCtrl));
TRY(s_name_to_key.insert("RightShift"_sv, Key::RightShift));
TRY(s_name_to_key.insert("RShift"_sv, Key::RightShift));
TRY(s_name_to_key.insert("S"_sv, Key::S));
TRY(s_name_to_key.insert("ScrollLock"_sv, Key::ScrollLock));
TRY(s_name_to_key.insert("Section"_sv, Key::Section));
TRY(s_name_to_key.insert("Semicolon"_sv, Key::Semicolon));
TRY(s_name_to_key.insert("SingleQuote"_sv, Key::SingleQuote));
TRY(s_name_to_key.insert("Slash"_sv, Key::Slash));
TRY(s_name_to_key.insert("Space"_sv, Key::Space));
TRY(s_name_to_key.insert("Super"_sv, Key::Super));
TRY(s_name_to_key.insert("T"_sv, Key::T));
TRY(s_name_to_key.insert("Tab"_sv, Key::Tab));
TRY(s_name_to_key.insert("Tilde"_sv, Key::Tilde));
TRY(s_name_to_key.insert("TwoDots"_sv, Key::TwoDots));
TRY(s_name_to_key.insert("U"_sv, Key::U));
TRY(s_name_to_key.insert("Underscore"_sv, Key::Underscore));
TRY(s_name_to_key.insert("V"_sv, Key::V));
TRY(s_name_to_key.insert("VolumeDown"_sv, Key::VolumeDown));
TRY(s_name_to_key.insert("VolumeMute"_sv, Key::VolumeMute));
TRY(s_name_to_key.insert("VolumeUp"_sv, Key::VolumeUp));
TRY(s_name_to_key.insert("W"_sv, Key::W));
TRY(s_name_to_key.insert("X"_sv, Key::X));
TRY(s_name_to_key.insert("Y"_sv, Key::Y));
TRY(s_name_to_key.insert("Z"_sv, Key::Z));
return {};
}
#pragma GCC diagnostic pop
}