feat(i18n): add Russian (ЙЦУКЕН) layout, Alt+Shift/F1 toggle, and localized shell commands

This commit is contained in:
RarDog
2026-09-02 16:06:33 +03:00
parent 9222cd639b
commit e608b6a096
2 changed files with 180 additions and 95 deletions
+161 -80
View File
@@ -1,6 +1,6 @@
//! PS/2 Keyboard Driver (IRQ 1 & Polling fallback). //! PS/2 Keyboard Driver with US / RU (ЙЦУКЕН) layout support.
//! //!
//! Полностью переписанный драйвер PS/2 клавиатуры для x86_64 (Scancode Set 1). //! Переключение раскладки: Alt+Shift, Ctrl+Shift или клавиша F1 / Scroll Lock.
use spin::Mutex; use spin::Mutex;
use x86_64::instructions::port::Port; use x86_64::instructions::port::Port;
@@ -18,7 +18,9 @@ struct KeyboardState {
shift: bool, shift: bool,
caps_lock: bool, caps_lock: bool,
ctrl: bool, ctrl: bool,
alt: bool,
extended: bool, extended: bool,
layout_ru: bool,
} }
impl KeyboardState { impl KeyboardState {
@@ -30,7 +32,9 @@ impl KeyboardState {
shift: false, shift: false,
caps_lock: false, caps_lock: false,
ctrl: false, ctrl: false,
alt: false,
extended: false, extended: false,
layout_ru: false,
} }
} }
@@ -42,6 +46,14 @@ impl KeyboardState {
} }
} }
fn push_char(&mut self, c: char) {
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
for &b in s.as_bytes() {
self.push(b);
}
}
fn pop(&mut self) -> Option<u8> { fn pop(&mut self) -> Option<u8> {
if self.head == self.tail { if self.head == self.tail {
None None
@@ -136,7 +148,7 @@ pub fn init() {
} }
} }
log::info!("[keyboard] PS/2 keyboard initialized (Set 1 translation active)"); log::info!("[keyboard] PS/2 keyboard initialized (US / RU layout support)");
} }
/// Обработчик аппаратного прерывания клавиатуры (IRQ 1). /// Обработчик аппаратного прерывания клавиатуры (IRQ 1).
@@ -180,7 +192,7 @@ pub fn pop_char() -> Option<u8> {
}) })
} }
/// Декодер Scancode Set 1. /// Декодер Scancode Set 1 (US / RU ЙЦУКЕН).
fn process_scancode(scancode: u8) { fn process_scancode(scancode: u8) {
let mut kbd = KEYBOARD.lock(); let mut kbd = KEYBOARD.lock();
@@ -191,11 +203,37 @@ fn process_scancode(scancode: u8) {
// Обработка модификаторов нажатия / отпускания // Обработка модификаторов нажатия / отпускания
match scancode { match scancode {
0x2A | 0x36 => { kbd.shift = true; return; } // Left / Right Shift press 0x2A | 0x36 => {
0xAA | 0xB6 => { kbd.shift = false; return; } // Left / Right Shift release kbd.shift = true;
0x1D => { kbd.ctrl = true; return; } // Ctrl press // Проверка переключения раскладки по Alt+Shift или Ctrl+Shift
0x9D => { kbd.ctrl = false; return; } // Ctrl release if kbd.alt || kbd.ctrl {
0x3A => { kbd.caps_lock = !kbd.caps_lock; return; } // CapsLock toggle kbd.layout_ru = !kbd.layout_ru;
}
return;
}
0xAA | 0xB6 => { kbd.shift = false; return; }
0x1D => {
kbd.ctrl = true;
if kbd.shift {
kbd.layout_ru = !kbd.layout_ru;
}
return;
}
0x9D => { kbd.ctrl = false; return; }
0x38 => {
kbd.alt = true;
if kbd.shift {
kbd.layout_ru = !kbd.layout_ru;
}
return;
}
0xB8 => { kbd.alt = false; return; }
0x3A => { kbd.caps_lock = !kbd.caps_lock; return; } // CapsLock
0x3B | 0x46 => {
// F1 или ScrollLock переключает раскладку RU / EN
kbd.layout_ru = !kbd.layout_ru;
return;
}
_ => {} _ => {}
} }
@@ -207,79 +245,122 @@ fn process_scancode(scancode: u8) {
let upper = kbd.shift ^ kbd.caps_lock; let upper = kbd.shift ^ kbd.caps_lock;
let ch = match scancode { let ch = if kbd.layout_ru {
0x02 => if kbd.shift { '!' } else { '1' }, // Русская раскладка ЙЦУКЕН
0x03 => if kbd.shift { '@' } else { '2' }, match scancode {
0x04 => if kbd.shift { '#' } else { '3' }, 0x02 => if kbd.shift { '!' } else { '1' },
0x05 => if kbd.shift { '$' } else { '4' }, 0x03 => if kbd.shift { '"' } else { '2' },
0x06 => if kbd.shift { '%' } else { '5' }, 0x04 => if kbd.shift { '№' } else { '3' },
0x07 => if kbd.shift { '^' } else { '6' }, 0x05 => if kbd.shift { ';' } else { '4' },
0x08 => if kbd.shift { '&' } else { '7' }, 0x06 => if kbd.shift { '%' } else { '5' },
0x09 => if kbd.shift { '*' } else { '8' }, 0x07 => if kbd.shift { ':' } else { '6' },
0x0A => if kbd.shift { '(' } else { '9' }, 0x08 => if kbd.shift { '?' } else { '7' },
0x0B => if kbd.shift { ')' } else { '0' }, 0x09 => if kbd.shift { '*' } else { '8' },
0x0C => if kbd.shift { '_' } else { '-' }, 0x0A => if kbd.shift { '(' } else { '9' },
0x0D => if kbd.shift { '+' } else { '=' }, 0x0B => if kbd.shift { ')' } else { '0' },
0x0E => '\x08', // Backspace 0x0C => if kbd.shift { '_' } else { '-' },
0x0F => '\t', // Tab 0x0D => if kbd.shift { '+' } else { '=' },
0x10 => if upper { 'Q' } else { 'q' }, 0x0E => '\x08',
0x11 => if upper { 'W' } else { 'w' }, 0x0F => '\t',
0x12 => if upper { 'E' } else { 'e' }, 0x10 => if upper { 'Й' } else { 'й' },
0x13 => if upper { 'R' } else { 'r' }, 0x11 => if upper { 'Ц' } else { 'ц' },
0x14 => if upper { 'T' } else { 't' }, 0x12 => if upper { 'У' } else { 'у' },
0x15 => if upper { 'Y' } else { 'y' }, 0x13 => if upper { 'К' } else { 'к' },
0x16 => if upper { 'U' } else { 'u' }, 0x14 => if upper { 'Е' } else { 'е' },
0x17 => if upper { 'I' } else { 'i' }, 0x15 => if upper { 'Н' } else { 'н' },
0x18 => if upper { 'O' } else { 'o' }, 0x16 => if upper { 'Г' } else { 'г' },
0x19 => if upper { 'P' } else { 'p' }, 0x17 => if upper { 'Ш' } else { 'ш' },
0x1A => if kbd.shift { '{' } else { '[' }, 0x18 => if upper { 'Щ' } else { 'щ' },
0x1B => if kbd.shift { '}' } else { ']' }, 0x19 => if upper { 'З' } else { 'з' },
0x1C => '\n', // Enter 0x1A => if upper { 'Х' } else { 'х' },
0x1E => if upper { 'A' } else { 'a' }, 0x1B => if upper { 'Ъ' } else { 'ъ' },
0x1F => if upper { 'S' } else { 's' }, 0x1C => '\n',
0x20 => if upper { 'D' } else { 'd' }, 0x1E => if upper { 'Ф' } else { 'ф' },
0x21 => if upper { 'F' } else { 'f' }, 0x1F => if upper { 'Ы' } else { 'ы' },
0x22 => if upper { 'G' } else { 'g' }, 0x20 => if upper { 'В' } else { 'в' },
0x23 => if upper { 'H' } else { 'h' }, 0x21 => if upper { 'А' } else { 'а' },
0x24 => if upper { 'J' } else { 'j' }, 0x22 => if upper { 'П' } else { 'п' },
0x25 => if upper { 'K' } else { 'k' }, 0x23 => if upper { 'Р' } else { 'р' },
0x26 => if upper { 'L' } else { 'l' }, 0x24 => if upper { 'О' } else { 'о' },
0x27 => if kbd.shift { ':' } else { ';' }, 0x25 => if upper { 'Л' } else { 'л' },
0x28 => if kbd.shift { '"' } else { '\'' }, 0x26 => if upper { 'Д' } else { 'д' },
0x29 => if kbd.shift { '~' } else { '`' }, 0x27 => if upper { 'Ж' } else { 'ж' },
0x2B => if kbd.shift { '|' } else { '\\' }, 0x28 => if upper { 'Э' } else { 'э' },
0x2C => if upper { 'Z' } else { 'z' }, 0x29 => if upper { 'Ё' } else { 'ё' },
0x2D => if upper { 'X' } else { 'x' }, 0x2B => if kbd.shift { '/' } else { '\\' },
0x2E => if upper { 'C' } else { 0x2C => if upper { 'Я' } else { 'я' },
if kbd.ctrl { '\x03' } else { 'c' } // Ctrl-C 0x2D => if upper { 'Ч' } else { 'ч' },
}, 0x2E => if upper { 'С' } else { 'с' },
0x2F => if upper { 'V' } else { 'v' }, 0x2F => if upper { 'М' } else { 'м' },
0x30 => if upper { 'B' } else { 'b' }, 0x30 => if upper { 'И' } else { 'и' },
0x31 => if upper { 'N' } else { 'n' }, 0x31 => if upper { 'Т' } else { 'т' },
0x32 => if upper { 'M' } else { 'm' }, 0x32 => if upper { 'Ь' } else { 'ь' },
0x33 => if kbd.shift { '<' } else { ',' }, 0x33 => if upper { 'Б' } else { 'б' },
0x34 => if kbd.shift { '>' } else { '.' }, 0x34 => if upper { 'Ю' } else { 'ю' },
0x35 => if kbd.shift { '?' } else { '/' }, 0x35 => if kbd.shift { ',' } else { '.' },
0x39 => ' ', // Space 0x39 => ' ',
0x47 => '7', _ => '\0',
0x48 => '8', }
0x49 => '9', } else {
0x4A => '-', // Английская раскладка QWERTY
0x4B => '4', match scancode {
0x4C => '5', 0x02 => if kbd.shift { '!' } else { '1' },
0x4D => '6', 0x03 => if kbd.shift { '@' } else { '2' },
0x4E => '+', 0x04 => if kbd.shift { '#' } else { '3' },
0x4F => '1', 0x05 => if kbd.shift { '$' } else { '4' },
0x50 => '2', 0x06 => if kbd.shift { '%' } else { '5' },
0x51 => '3', 0x07 => if kbd.shift { '^' } else { '6' },
0x52 => '0', 0x08 => if kbd.shift { '&' } else { '7' },
0x53 => '.', 0x09 => if kbd.shift { '*' } else { '8' },
_ => { 0x0A => if kbd.shift { '(' } else { '9' },
kbd.extended = false; 0x0B => if kbd.shift { ')' } else { '0' },
return; 0x0C => if kbd.shift { '_' } else { '-' },
0x0D => if kbd.shift { '+' } else { '=' },
0x0E => '\x08',
0x0F => '\t',
0x10 => if upper { 'Q' } else { 'q' },
0x11 => if upper { 'W' } else { 'w' },
0x12 => if upper { 'E' } else { 'e' },
0x13 => if upper { 'R' } else { 'r' },
0x14 => if upper { 'T' } else { 't' },
0x15 => if upper { 'Y' } else { 'y' },
0x16 => if upper { 'U' } else { 'u' },
0x17 => if upper { 'I' } else { 'i' },
0x18 => if upper { 'O' } else { 'o' },
0x19 => if upper { 'P' } else { 'p' },
0x1A => if kbd.shift { '{' } else { '[' },
0x1B => if kbd.shift { '}' } else { ']' },
0x1C => '\n',
0x1E => if upper { 'A' } else { 'a' },
0x1F => if upper { 'S' } else { 's' },
0x20 => if upper { 'D' } else { 'd' },
0x21 => if upper { 'F' } else { 'f' },
0x22 => if upper { 'G' } else { 'g' },
0x23 => if upper { 'H' } else { 'h' },
0x24 => if upper { 'J' } else { 'j' },
0x25 => if upper { 'K' } else { 'k' },
0x26 => if upper { 'L' } else { 'l' },
0x27 => if kbd.shift { ':' } else { ';' },
0x28 => if kbd.shift { '"' } else { '\'' },
0x29 => if kbd.shift { '~' } else { '`' },
0x2B => if kbd.shift { '|' } else { '\\' },
0x2C => if upper { 'Z' } else { 'z' },
0x2D => if upper { 'X' } else { 'x' },
0x2E => if upper { 'C' } else { 'c' },
0x2F => if upper { 'V' } else { 'v' },
0x30 => if upper { 'B' } else { 'b' },
0x31 => if upper { 'N' } else { 'n' },
0x32 => if upper { 'M' } else { 'm' },
0x33 => if kbd.shift { '<' } else { ',' },
0x34 => if kbd.shift { '>' } else { '.' },
0x35 => if kbd.shift { '?' } else { '/' },
0x39 => ' ',
_ => '\0',
} }
}; };
kbd.extended = false; kbd.extended = false;
kbd.push(ch as u8); if ch != '\0' {
kbd.push_char(ch);
}
} }
+19 -15
View File
@@ -72,13 +72,17 @@ fn read_line(buf: &mut [u8]) -> usize {
ipc_client::print_str(s); ipc_client::print_str(s);
} }
} }
Some(b) if b >= 0x20 && b < 0x7F => { Some(b) if (b >= 0x20 && b < 0x7F) || b >= 0x80 => {
if len < buf.len() - 1 { if len < buf.len() - 1 {
buf[len] = b; buf[len] = b;
len += 1; len += 1;
let slice = [b]; let slice = [b];
if let Ok(s) = core::str::from_utf8(&slice) { if let Ok(s) = core::str::from_utf8(&slice) {
ipc_client::print_str(s); ipc_client::print_str(s);
} else if len >= 2 {
if let Ok(s) = core::str::from_utf8(&buf[len - 2..len]) {
ipc_client::print_str(s);
}
} }
} }
} }
@@ -101,26 +105,26 @@ fn dispatch_command(cmdline: &str) {
let arg1 = parts.next().unwrap_or(""); let arg1 = parts.next().unwrap_or("");
match cmd { match cmd {
"help" | "?" => { "help" | "?" | "помощь" | "справка" => {
ipc_client::println("\x1b[1;36mopencoreRS Ring 3 Shell — Built-in Commands\x1b[0m"); ipc_client::println("\x1b[1;36mopencoreRS Ring 3 Shell — Команды / Commands\x1b[0m");
ipc_client::println("\x1b[90m─────────────────────────────────────────────\x1b[0m"); ipc_client::println("\x1b[90m─────────────────────────────────────────────\x1b[0m");
ipc_client::println(" \x1b[1;33mhelp, ?\x1b[0m Show this help menu"); ipc_client::println(" \x1b[1;33mhelp, помощь, ?\x1b[0m Список команд / Help menu");
ipc_client::println(" \x1b[1;33muname [-a]\x1b[0m Display OS, kernel and architecture"); ipc_client::println(" \x1b[1;33muname [-a]\x1b[0m Информация о ядре / Kernel info");
ipc_client::println(" \x1b[1;33mls [path]\x1b[0m List VFS directory (/proc, /dev, /etc)"); ipc_client::println(" \x1b[1;33mls [path]\x1b[0m Список файлов VFS (/proc, /dev, /etc)");
ipc_client::println(" \x1b[1;33mcat <file>\x1b[0m Display contents of a file"); ipc_client::println(" \x1b[1;33mcat <file>\x1b[0m Просмотр файла / Display file");
ipc_client::println(" \x1b[1;33mecho <text>\x1b[0m Print text to terminal"); ipc_client::println(" \x1b[1;33mecho, эхо <text>\x1b[0m Печать текста / Print text");
ipc_client::println(" \x1b[1;33mcpuinfo\x1b[0m Display CPUID vendor, model & features"); ipc_client::println(" \x1b[1;33mcpuinfo, инфо\x1b[0m Сведения о CPUID / CPU details");
ipc_client::println(" \x1b[1;33mlspci\x1b[0m Scan and list PCI hardware devices"); ipc_client::println(" \x1b[1;33mlspci\x1b[0m Сканирование шины PCI / PCI devices");
ipc_client::println(" \x1b[1;33mifconfig, net\x1b[0m Network interface configuration & stats"); ipc_client::println(" \x1b[1;33mifconfig, net\x1b[0m Сетевой интерфейс e1000");
ipc_client::println(" \x1b[1;33mping [ip]\x1b[0m Send ICMP Echo Request"); ipc_client::println(" \x1b[1;33mping [ip]\x1b[0m ICMP Echo запрос / Ping network");
ipc_client::println(" \x1b[1;33mclear, cls\x1b[0m Clear terminal screen"); ipc_client::println(" \x1b[1;33mclear, очистить\x1b[0m Очистка экрана / Clear screen");
ipc_client::println(" \x1b[1;33mexit\x1b[0m Exit userspace shell"); ipc_client::println(" \x1b[1;33mexit, выход\x1b[0m Выход из шелла / Exit shell");
ipc_client::println(""); ipc_client::println("");
} }
"uname" => { "uname" => {
ipc_client::println("opencoreRS 0.1.0 x86_64 microkernel SMP (Ring 3 Shell)"); ipc_client::println("opencoreRS 0.1.0 x86_64 microkernel SMP (Ring 3 Shell)");
} }
"echo" => { "echo" | "эхо" => {
let mut first = true; let mut first = true;
for p in cmdline.split_ascii_whitespace().skip(1) { for p in cmdline.split_ascii_whitespace().skip(1) {
if !first { if !first {