feat: atomic patch application, colored init output, XKB bridge, USB HID hardening
Build system (src/cook/fetch.rs): - Atomic patch application: applies patches to staging directory (cp -al), atomically swaps on success, discards on failure — source tree is never left in a partially-patched state - normalize_patch(): strips diff --git/index/new-file-mode headers that the build system's patch command does not recognize - cleanup_workspace_pollution(): removes orphaned recipes/Cargo.toml and recipes/Cargo.lock to prevent workspace conflicts - Added --allow-protected CLI flag to repo binary Input stack (local/patches/base/P3-*.patch): - P3-ps2d-led-feedback: PS/2 LED state handling + InputProducer migration - P3-inputd-keymap-bridge: InputProducer enum, keymap bridge query - P3-usbhidd-hardening: HID descriptor validation, static lookup table, 8-button mouse support, transfer retry with exponential backoff - P3-init-colored-output: ANSI-color coded init daemon output (green OK, red FAILED, yellow SKIP/WARN) XKB bridge (local/recipes/system/redbear-keymapd/source/src/xkb.rs): - Parses X11 xkb/symbols/* format, maps XKB keycodes to PS/2 scancodes, 80+ X11 keysym names to Unicode, 4-level key support Patch governance (local/patches/base/absorbed/README.md): - Documents consolidation of P0-P3 patches into redox.patch
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
pub mod keymap;
|
||||
pub mod scheme;
|
||||
pub mod xkb;
|
||||
@@ -0,0 +1,59 @@
|
||||
mod keymap;
|
||||
mod scheme;
|
||||
mod xkb;
|
||||
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::process;
|
||||
|
||||
use scheme::KeymapScheme;
|
||||
|
||||
fn log_msg(level: &str, msg: &str) {
|
||||
let _ = writeln!(std::io::stderr(), "[keymapd] {} {}", level, msg);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut scheme = KeymapScheme::new();
|
||||
|
||||
let builtins = keymap::BuiltinKeymaps::new();
|
||||
scheme.load_builtin(&builtins);
|
||||
|
||||
let keymap_dir = match env::var("KEYMAP_DIR") {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => "/etc/keymaps".to_string(),
|
||||
};
|
||||
if let Err(e) = scheme.load_from_dir(&keymap_dir) {
|
||||
log_msg("ERROR", &format!("failed to load keymaps from {}: {}", keymap_dir, e));
|
||||
}
|
||||
|
||||
log_msg("INFO", &format!("loaded {} keymap(s)", scheme.keymap_count()));
|
||||
|
||||
let socket = redox_scheme::Socket::nonblock("keymap")
|
||||
.expect("keymapd: failed to register scheme:keymap");
|
||||
log_msg("INFO", "registered scheme:keymap");
|
||||
|
||||
loop {
|
||||
let request = match socket.next_request(redox_scheme::SignalBehavior::Restart) {
|
||||
Ok(Some(r)) => r,
|
||||
Ok(None) => {
|
||||
log_msg("INFO", "scheme unmounted, exiting");
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
log_msg("ERROR", &format!("failed to read request: {}", e));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
match request.handle_scheme_block_mut(&mut scheme) {
|
||||
Ok(response) => {
|
||||
if let Err(e) = socket.write_response(response, redox_scheme::SignalBehavior::Restart) {
|
||||
log_msg("ERROR", &format!("failed to write response: {}", e));
|
||||
}
|
||||
}
|
||||
Err(_request) => {
|
||||
log_msg("ERROR", "unhandled scheme request");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
|
||||
use syscall::data::Stat;
|
||||
use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT};
|
||||
use syscall::flag::{MODE_DIR, MODE_FILE, SEEK_CUR, SEEK_END, SEEK_SET};
|
||||
|
||||
use crate::keymap::Keymap;
|
||||
|
||||
enum HandleKind {
|
||||
Root,
|
||||
Active,
|
||||
List,
|
||||
Keymap { name: String },
|
||||
}
|
||||
|
||||
struct Handle {
|
||||
kind: HandleKind,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
pub struct KeymapScheme {
|
||||
next_id: usize,
|
||||
handles: HashMap<usize, Handle>,
|
||||
keymaps: HashMap<String, Keymap>,
|
||||
active_keymap: String,
|
||||
}
|
||||
|
||||
impl KeymapScheme {
|
||||
pub fn new() -> Self {
|
||||
KeymapScheme {
|
||||
next_id: 0,
|
||||
handles: HashMap::new(),
|
||||
keymaps: HashMap::new(),
|
||||
active_keymap: "us".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_builtin(&mut self, builtins: &crate::keymap::BuiltinKeymaps) {
|
||||
for (name, km) in [
|
||||
("us", &builtins.us),
|
||||
("gb", &builtins.gb),
|
||||
("dvorak", &builtins.dvorak),
|
||||
("azerty", &builtins.azerty),
|
||||
("bepo", &builtins.bepo),
|
||||
("it", &builtins.it),
|
||||
] {
|
||||
self.keymaps.insert(name.to_string(), km.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_from_dir(&mut self, dir: &str) -> io::Result<()> {
|
||||
let entries = std::fs::read_dir(dir)?;
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.extension().map_or(false, |e| e == "json") {
|
||||
let name = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let json_str = std::fs::read_to_string(&path)?;
|
||||
if let Ok(km) = Keymap::from_json(&name, &json_str) {
|
||||
self.keymaps.insert(name, km);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_xkb(&mut self, xkb_dir: &str, layout: &str, variant: Option<&str>) -> io::Result<()> {
|
||||
let km = crate::xkb::load_xkb_keymap(xkb_dir, layout, variant)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
let name = match variant {
|
||||
Some(v) => format!("{}({})", layout, v),
|
||||
None => layout.to_string(),
|
||||
};
|
||||
self.keymaps.insert(name, km);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn keymap_count(&self) -> usize {
|
||||
self.keymaps.len()
|
||||
}
|
||||
|
||||
fn active_keymap(&self) -> &Keymap {
|
||||
self.keymaps
|
||||
.get(&self.active_keymap)
|
||||
.or_else(|| self.keymaps.get("us"))
|
||||
.expect("at least one keymap must be loaded")
|
||||
}
|
||||
|
||||
pub fn translate(&self, scancode: u8, shift: bool, altgr: bool) -> char {
|
||||
self.active_keymap().get_char(scancode, shift, altgr)
|
||||
}
|
||||
}
|
||||
|
||||
impl redox_scheme::SchemeBlockMut for KeymapScheme {
|
||||
fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> Result<Option<usize>> {
|
||||
let cleaned = path.trim_matches('/');
|
||||
|
||||
let kind = if cleaned.is_empty() {
|
||||
HandleKind::Root
|
||||
} else if cleaned == "active" {
|
||||
HandleKind::Active
|
||||
} else if cleaned == "list" {
|
||||
HandleKind::List
|
||||
} else if let Some(name) = cleaned.strip_prefix("keymap/") {
|
||||
let name = name.trim_end_matches('/').to_string();
|
||||
if !self.keymaps.contains_key(&name) {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
HandleKind::Keymap { name }
|
||||
} else if self.keymaps.contains_key(cleaned) {
|
||||
let name = cleaned.to_string();
|
||||
if let Some(km) = self.keymaps.get(&name) {
|
||||
let _ = km;
|
||||
}
|
||||
HandleKind::Keymap { name }
|
||||
} else if cleaned.starts_with("set/") {
|
||||
let requested = &cleaned[4..];
|
||||
if !self.keymaps.contains_key(requested) {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
self.active_keymap = requested.to_string();
|
||||
HandleKind::Active
|
||||
} else {
|
||||
return Err(Error::new(ENOENT));
|
||||
};
|
||||
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.handles.insert(id, Handle { kind, offset: 0 });
|
||||
Ok(Some(id))
|
||||
}
|
||||
|
||||
fn read(&mut self, id: usize, buf: &mut [u8]) -> Result<Option<usize>> {
|
||||
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
||||
|
||||
let content: Vec<u8> = match &handle.kind {
|
||||
HandleKind::Root => {
|
||||
let mut listing = String::new();
|
||||
listing.push_str("active\nlist\n");
|
||||
for name in self.keymaps.keys() {
|
||||
listing.push_str(&format!("keymap/{}\n", name));
|
||||
}
|
||||
listing.into_bytes()
|
||||
}
|
||||
HandleKind::Active => self.active_keymap.clone().into_bytes(),
|
||||
HandleKind::List => {
|
||||
let mut listing = String::new();
|
||||
for (i, name) in self.keymaps.keys().enumerate() {
|
||||
if i > 0 {
|
||||
listing.push('\n');
|
||||
}
|
||||
listing.push_str(name);
|
||||
}
|
||||
listing.push('\n');
|
||||
listing.into_bytes()
|
||||
}
|
||||
HandleKind::Keymap { name } => {
|
||||
let km = self.keymaps.get(name).ok_or(Error::new(ENOENT))?;
|
||||
format!(
|
||||
"name={}\nentries={}\ncompose={}\ndead_keys={}\n",
|
||||
km.name,
|
||||
km.entries.len(),
|
||||
km.compose.len(),
|
||||
km.dead_keys.len()
|
||||
)
|
||||
.into_bytes()
|
||||
}
|
||||
};
|
||||
|
||||
if handle.offset >= content.len() {
|
||||
return Ok(Some(0));
|
||||
}
|
||||
let remaining = &content[handle.offset..];
|
||||
let to_copy = remaining.len().min(buf.len());
|
||||
buf[..to_copy].copy_from_slice(&remaining[..to_copy]);
|
||||
handle.offset += to_copy;
|
||||
Ok(Some(to_copy))
|
||||
}
|
||||
|
||||
fn write(&mut self, id: usize, buf: &[u8]) -> Result<Option<usize>> {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
match &handle.kind {
|
||||
HandleKind::Active => {
|
||||
let name = String::from_utf8_lossy(buf);
|
||||
let name = name.trim();
|
||||
if self.keymaps.contains_key(name) {
|
||||
self.active_keymap = name.to_string();
|
||||
Ok(Some(buf.len()))
|
||||
} else {
|
||||
Err(Error::new(ENOENT))
|
||||
}
|
||||
}
|
||||
_ => Err(Error::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
|
||||
fn seek(&mut self, id: usize, pos: isize, whence: usize) -> Result<Option<isize>> {
|
||||
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
||||
let new_offset = match whence {
|
||||
SEEK_SET => pos as isize,
|
||||
SEEK_CUR => handle.offset as isize + pos,
|
||||
SEEK_END => pos,
|
||||
_ => return Err(Error::new(EINVAL)),
|
||||
};
|
||||
if new_offset < 0 {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
handle.offset = new_offset as usize;
|
||||
Ok(Some(new_offset))
|
||||
}
|
||||
|
||||
fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result<Option<usize>> {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
match &handle.kind {
|
||||
HandleKind::Root => {
|
||||
stat.st_mode = MODE_DIR | 0o555;
|
||||
}
|
||||
_ => {
|
||||
stat.st_mode = MODE_FILE | 0o644;
|
||||
}
|
||||
}
|
||||
Ok(Some(0))
|
||||
}
|
||||
|
||||
fn close(&mut self, id: usize) -> Result<Option<usize>> {
|
||||
self.handles.remove(&id);
|
||||
Ok(Some(0))
|
||||
}
|
||||
|
||||
fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result<Option<usize>> {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
let path = match &handle.kind {
|
||||
HandleKind::Root => "keymap:".to_string(),
|
||||
HandleKind::Active => "keymap:active".to_string(),
|
||||
HandleKind::List => "keymap:list".to_string(),
|
||||
HandleKind::Keymap { name } => format!("keymap:keymap/{}", name),
|
||||
};
|
||||
let bytes = path.as_bytes();
|
||||
let to_copy = bytes.len().min(buf.len());
|
||||
buf[..to_copy].copy_from_slice(&bytes[..to_copy]);
|
||||
Ok(Some(to_copy))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::keymap::{Keymap, KeymapEntry};
|
||||
|
||||
fn xkb_keycode_to_scancode(code: &str) -> Option<u8> {
|
||||
match code {
|
||||
"TLDE" => Some(0x29),
|
||||
"AE01" => Some(0x02),
|
||||
"AE02" => Some(0x03),
|
||||
"AE03" => Some(0x04),
|
||||
"AE04" => Some(0x05),
|
||||
"AE05" => Some(0x06),
|
||||
"AE06" => Some(0x07),
|
||||
"AE07" => Some(0x08),
|
||||
"AE08" => Some(0x09),
|
||||
"AE09" => Some(0x0A),
|
||||
"AE10" => Some(0x0B),
|
||||
"AE11" => Some(0x0C),
|
||||
"AE12" => Some(0x0D),
|
||||
"AD01" => Some(0x10),
|
||||
"AD02" => Some(0x11),
|
||||
"AD03" => Some(0x12),
|
||||
"AD04" => Some(0x13),
|
||||
"AD05" => Some(0x14),
|
||||
"AD06" => Some(0x15),
|
||||
"AD07" => Some(0x16),
|
||||
"AD08" => Some(0x17),
|
||||
"AD09" => Some(0x18),
|
||||
"AD10" => Some(0x19),
|
||||
"AD11" => Some(0x1A),
|
||||
"AD12" => Some(0x1B),
|
||||
"AC01" => Some(0x1E),
|
||||
"AC02" => Some(0x1F),
|
||||
"AC03" => Some(0x20),
|
||||
"AC04" => Some(0x21),
|
||||
"AC05" => Some(0x22),
|
||||
"AC06" => Some(0x23),
|
||||
"AC07" => Some(0x24),
|
||||
"AC08" => Some(0x25),
|
||||
"AC09" => Some(0x26),
|
||||
"AC10" => Some(0x27),
|
||||
"AC11" => Some(0x28),
|
||||
"BKSL" => Some(0x2B),
|
||||
"AB01" => Some(0x2C),
|
||||
"AB02" => Some(0x2D),
|
||||
"AB03" => Some(0x2E),
|
||||
"AB04" => Some(0x2F),
|
||||
"AB05" => Some(0x30),
|
||||
"AB06" => Some(0x31),
|
||||
"AB07" => Some(0x32),
|
||||
"AB08" => Some(0x33),
|
||||
"AB09" => Some(0x34),
|
||||
"AB10" => Some(0x35),
|
||||
"SPCE" => Some(0x39),
|
||||
"LSGT" => Some(0x56),
|
||||
"BKSP" => Some(0x0E),
|
||||
"TAB" => Some(0x0F),
|
||||
"RTRN" => Some(0x1C),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_char(sym: &str) -> char {
|
||||
if sym.len() == 1 {
|
||||
return sym.chars().next().unwrap_or('\0');
|
||||
}
|
||||
match sym {
|
||||
"space" => ' ',
|
||||
"exclam" => '!',
|
||||
"quotedbl" => '"',
|
||||
"numbersign" => '#',
|
||||
"dollar" => '$',
|
||||
"percent" => '%',
|
||||
"ampersand" => '&',
|
||||
"apostrophe" => '\'',
|
||||
"quoteright" => '\'',
|
||||
"parenleft" => '(',
|
||||
"parenright" => ')',
|
||||
"asterisk" => '*',
|
||||
"plus" => '+',
|
||||
"comma" => ',',
|
||||
"minus" => '-',
|
||||
"period" => '.',
|
||||
"slash" => '/',
|
||||
"colon" => ':',
|
||||
"semicolon" => ';',
|
||||
"less" => '<',
|
||||
"equal" => '=',
|
||||
"greater" => '>',
|
||||
"question" => '?',
|
||||
"at" => '@',
|
||||
"bracketleft" => '[',
|
||||
"backslash" => '\\',
|
||||
"bracketright" => ']',
|
||||
"asciicircum" => '^',
|
||||
"underscore" => '_',
|
||||
"grave" => '`',
|
||||
"braceleft" => '{',
|
||||
"bar" => '|',
|
||||
"braceright" => '}',
|
||||
"asciitilde" => '~',
|
||||
"nobreakspace" => '\u{00A0}',
|
||||
"exclamdown" => '¡',
|
||||
"cent" => '¢',
|
||||
"sterling" => '£',
|
||||
"currency" => '¤',
|
||||
"yen" => '¥',
|
||||
"brokenbar" => '¦',
|
||||
"section" => '§',
|
||||
"diaeresis" => '¨',
|
||||
"copyright" => '©',
|
||||
"ordfeminine" => 'ª',
|
||||
"guillemotleft" => '«',
|
||||
"notsign" => '¬',
|
||||
"hyphen" => '\u{00AD}',
|
||||
"registered" => '®',
|
||||
"macron" => '¯',
|
||||
"degree" => '°',
|
||||
"plusminus" => '±',
|
||||
"twosuperior" => '²',
|
||||
"threesuperior" => '³',
|
||||
"acute" => '´',
|
||||
"mu" => 'µ',
|
||||
"paragraph" => '¶',
|
||||
"periodcentered" => '·',
|
||||
"cedilla" => '¸',
|
||||
"onesuperior" => '¹',
|
||||
"masculine" => 'º',
|
||||
"guillemotright" => '»',
|
||||
"onequarter" => '¼',
|
||||
"onehalf" => '½',
|
||||
"threequarters" => '¾',
|
||||
"questiondown" => '¿',
|
||||
"Agrave" => 'À',
|
||||
"Aacute" => 'Á',
|
||||
"Acircumflex" => 'Â',
|
||||
"Atilde" => 'Ã',
|
||||
"Adiaeresis" => 'Ä',
|
||||
"Aring" => 'Å',
|
||||
"AE" => 'Æ',
|
||||
"Ccedilla" => 'Ç',
|
||||
"Egrave" => 'È',
|
||||
"Eacute" => 'É',
|
||||
"Ecircumflex" => 'Ê',
|
||||
"Ediaeresis" => 'Ë',
|
||||
"Igrave" => 'Ì',
|
||||
"Iacute" => 'Í',
|
||||
"Icircumflex" => 'Î',
|
||||
"Idiaeresis" => 'Ï',
|
||||
"ETH" => 'Ð',
|
||||
"Ntilde" => 'Ñ',
|
||||
"Ograve" => 'Ò',
|
||||
"Oacute" => 'Ó',
|
||||
"Ocircumflex" => 'Ô',
|
||||
"Otilde" => 'Õ',
|
||||
"Odiaeresis" => 'Ö',
|
||||
"multiply" => '×',
|
||||
"Ooblique" => 'Ø',
|
||||
"Ugrave" => 'Ù',
|
||||
"Uacute" => 'Ú',
|
||||
"Ucircumflex" => 'Û',
|
||||
"Udiaeresis" => 'Ü',
|
||||
"Yacute" => 'Ý',
|
||||
"THORN" => 'Þ',
|
||||
"ssharp" => 'ß',
|
||||
"agrave" => 'à',
|
||||
"aacute" => 'á',
|
||||
"acircumflex" => 'â',
|
||||
"atilde" => 'ã',
|
||||
"adiaeresis" => 'ä',
|
||||
"aring" => 'å',
|
||||
"ae" => 'æ',
|
||||
"ccedilla" => 'ç',
|
||||
"egrave" => 'è',
|
||||
"eacute" => 'é',
|
||||
"ecircumflex" => 'ê',
|
||||
"ediaeresis" => 'ë',
|
||||
"igrave" => 'ì',
|
||||
"iacute" => 'í',
|
||||
"icircumflex" => 'î',
|
||||
"idiaeresis" => 'ï',
|
||||
"eth" => 'ð',
|
||||
"ntilde" => 'ñ',
|
||||
"ograve" => 'ò',
|
||||
"oacute" => 'ó',
|
||||
"ocircumflex" => 'ô',
|
||||
"otilde" => 'õ',
|
||||
"odiaeresis" => 'ö',
|
||||
"division" => '÷',
|
||||
"oslash" => 'ø',
|
||||
"ugrave" => 'ù',
|
||||
"uacute" => 'ú',
|
||||
"ucircumflex" => 'û',
|
||||
"udiaeresis" => 'ü',
|
||||
"yacute" => 'ý',
|
||||
"thorn" => 'þ',
|
||||
"ydiaeresis" => 'ÿ',
|
||||
"EuroSign" => '€',
|
||||
"NoSymbol" => '\0',
|
||||
_ => '\0',
|
||||
}
|
||||
}
|
||||
|
||||
struct XkbKeyEntry {
|
||||
scancode: u8,
|
||||
normal: char,
|
||||
shifted: char,
|
||||
altgr: char,
|
||||
altgr_shifted: char,
|
||||
}
|
||||
|
||||
fn parse_keysyms(syms: &[&str]) -> (char, char, char, char) {
|
||||
let normal = syms.get(0).map(|s| keysym_to_char(s.trim())).unwrap_or('\0');
|
||||
let shifted = syms.get(1).map(|s| keysym_to_char(s.trim())).unwrap_or('\0');
|
||||
let altgr = syms.get(2).map(|s| keysym_to_char(s.trim())).unwrap_or('\0');
|
||||
let altgr_shifted = syms.get(3).map(|s| keysym_to_char(s.trim())).unwrap_or('\0');
|
||||
(normal, shifted, altgr, altgr_shifted)
|
||||
}
|
||||
|
||||
pub fn parse_xkb_symbols(content: &str, variant: Option<&str>) -> Result<Keymap, String> {
|
||||
let target_variant = variant.unwrap_or("basic");
|
||||
let mut entries: HashMap<u8, KeymapEntry> = HashMap::new();
|
||||
let mut found_variant = false;
|
||||
|
||||
let mut i = 0;
|
||||
let lines: Vec<&str> = content.lines().collect();
|
||||
while i < lines.len() {
|
||||
let line = lines[i].trim();
|
||||
|
||||
if line.starts_with("xkb_symbols") {
|
||||
let name = extract_variant_name(line).unwrap_or("basic");
|
||||
if name != target_variant {
|
||||
i = skip_brace_block(&lines, i + 1);
|
||||
continue;
|
||||
}
|
||||
found_variant = true;
|
||||
i += 1;
|
||||
while i < lines.len() {
|
||||
let inner = lines[i].trim();
|
||||
if inner.starts_with('}') {
|
||||
break;
|
||||
}
|
||||
if inner.starts_with("key <") {
|
||||
if let Some(entry) = parse_key_line(inner) {
|
||||
entries.entry(entry.scancode).or_insert_with(|| {
|
||||
KeymapEntry {
|
||||
scancode: entry.scancode,
|
||||
normal: entry.normal,
|
||||
shifted: entry.shifted,
|
||||
altgr: entry.altgr,
|
||||
altgr_shifted: entry.altgr_shifted,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if !found_variant {
|
||||
return Err(format!("variant '{}' not found in XKB symbols file", target_variant));
|
||||
}
|
||||
|
||||
Ok(Keymap {
|
||||
name: variant.unwrap_or("basic").to_string(),
|
||||
entries,
|
||||
compose: Vec::new(),
|
||||
dead_keys: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_variant_name(line: &str) -> Option<&str> {
|
||||
let start = line.find('"')?;
|
||||
let rest = &line[start + 1..];
|
||||
let end = rest.find('"')?;
|
||||
Some(&rest[..end])
|
||||
}
|
||||
|
||||
fn skip_brace_block(lines: &[&str], start: usize) -> usize {
|
||||
let mut depth = 1;
|
||||
let mut i = start;
|
||||
while i < lines.len() && depth > 0 {
|
||||
for ch in lines[i].chars() {
|
||||
match ch {
|
||||
'{' => depth += 1,
|
||||
'}' => depth -= 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
i
|
||||
}
|
||||
|
||||
fn parse_key_line(line: &str) -> Option<XkbKeyEntry> {
|
||||
let key_start = line.find('<')?;
|
||||
let key_end = line[key_start + 1..].find('>')?;
|
||||
let keycode = &line[key_start + 1..key_start + 1 + key_end];
|
||||
let scancode = xkb_keycode_to_scancode(keycode)?;
|
||||
|
||||
let syms_start = line.find('[')?;
|
||||
let syms_end = line.rfind(']')?;
|
||||
let syms_content = &line[syms_start + 1..syms_end];
|
||||
let syms: Vec<&str> = syms_content.split(',').collect();
|
||||
|
||||
let (normal, shifted, altgr, altgr_shifted) = parse_keysyms(&syms);
|
||||
|
||||
Some(XkbKeyEntry {
|
||||
scancode,
|
||||
normal,
|
||||
shifted,
|
||||
altgr,
|
||||
altgr_shifted,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_xkb_keymap(xkb_dir: &str, layout: &str, variant: Option<&str>) -> Result<Keymap, String> {
|
||||
let file_path = format!("{}/symbols/{}", xkb_dir, layout);
|
||||
let content = std::fs::read_to_string(&file_path)
|
||||
.map_err(|e| format!("failed to read XKB symbols file '{}': {}", file_path, e))?;
|
||||
parse_xkb_symbols(&content, variant)
|
||||
}
|
||||
Reference in New Issue
Block a user