diff --git a/kernel/src/arch/x86_64/keyboard.rs b/kernel/src/arch/x86_64/keyboard.rs index c6da23e..7a9a781 100644 --- a/kernel/src/arch/x86_64/keyboard.rs +++ b/kernel/src/arch/x86_64/keyboard.rs @@ -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 x86_64::instructions::port::Port; @@ -18,7 +18,9 @@ struct KeyboardState { shift: bool, caps_lock: bool, ctrl: bool, + alt: bool, extended: bool, + layout_ru: bool, } impl KeyboardState { @@ -30,7 +32,9 @@ impl KeyboardState { shift: false, caps_lock: false, ctrl: false, + alt: 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 { if self.head == self.tail { 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). @@ -180,7 +192,7 @@ pub fn pop_char() -> Option { }) } -/// Декодер Scancode Set 1. +/// Декодер Scancode Set 1 (US / RU ЙЦУКЕН). fn process_scancode(scancode: u8) { let mut kbd = KEYBOARD.lock(); @@ -191,11 +203,37 @@ fn process_scancode(scancode: u8) { // Обработка модификаторов нажатия / отпускания match scancode { - 0x2A | 0x36 => { kbd.shift = true; return; } // Left / Right Shift press - 0xAA | 0xB6 => { kbd.shift = false; return; } // Left / Right Shift release - 0x1D => { kbd.ctrl = true; return; } // Ctrl press - 0x9D => { kbd.ctrl = false; return; } // Ctrl release - 0x3A => { kbd.caps_lock = !kbd.caps_lock; return; } // CapsLock toggle + 0x2A | 0x36 => { + kbd.shift = true; + // Проверка переключения раскладки по Alt+Shift или Ctrl+Shift + if kbd.alt || kbd.ctrl { + 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 ch = match scancode { - 0x02 => if kbd.shift { '!' } else { '1' }, - 0x03 => if kbd.shift { '@' } else { '2' }, - 0x04 => if kbd.shift { '#' } else { '3' }, - 0x05 => if kbd.shift { '$' } else { '4' }, - 0x06 => if kbd.shift { '%' } else { '5' }, - 0x07 => if kbd.shift { '^' } else { '6' }, - 0x08 => if kbd.shift { '&' } else { '7' }, - 0x09 => if kbd.shift { '*' } else { '8' }, - 0x0A => if kbd.shift { '(' } else { '9' }, - 0x0B => if kbd.shift { ')' } else { '0' }, - 0x0C => if kbd.shift { '_' } else { '-' }, - 0x0D => if kbd.shift { '+' } else { '=' }, - 0x0E => '\x08', // Backspace - 0x0F => '\t', // Tab - 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', // Enter - 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 { - if kbd.ctrl { '\x03' } else { 'c' } // Ctrl-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 => ' ', // Space - 0x47 => '7', - 0x48 => '8', - 0x49 => '9', - 0x4A => '-', - 0x4B => '4', - 0x4C => '5', - 0x4D => '6', - 0x4E => '+', - 0x4F => '1', - 0x50 => '2', - 0x51 => '3', - 0x52 => '0', - 0x53 => '.', - _ => { - kbd.extended = false; - return; + let ch = if kbd.layout_ru { + // Русская раскладка ЙЦУКЕН + match scancode { + 0x02 => if kbd.shift { '!' } else { '1' }, + 0x03 => if kbd.shift { '"' } else { '2' }, + 0x04 => if kbd.shift { '№' } else { '3' }, + 0x05 => if kbd.shift { ';' } else { '4' }, + 0x06 => if kbd.shift { '%' } else { '5' }, + 0x07 => if kbd.shift { ':' } else { '6' }, + 0x08 => if kbd.shift { '?' } else { '7' }, + 0x09 => if kbd.shift { '*' } else { '8' }, + 0x0A => if kbd.shift { '(' } else { '9' }, + 0x0B => if kbd.shift { ')' } else { '0' }, + 0x0C => if kbd.shift { '_' } else { '-' }, + 0x0D => if kbd.shift { '+' } else { '=' }, + 0x0E => '\x08', + 0x0F => '\t', + 0x10 => if upper { 'Й' } else { 'й' }, + 0x11 => if upper { 'Ц' } else { 'ц' }, + 0x12 => if upper { 'У' } else { 'у' }, + 0x13 => if upper { 'К' } else { 'к' }, + 0x14 => if upper { 'Е' } else { 'е' }, + 0x15 => if upper { 'Н' } else { 'н' }, + 0x16 => if upper { 'Г' } else { 'г' }, + 0x17 => if upper { 'Ш' } else { 'ш' }, + 0x18 => if upper { 'Щ' } else { 'щ' }, + 0x19 => if upper { 'З' } else { 'з' }, + 0x1A => if upper { 'Х' } else { 'х' }, + 0x1B => if upper { 'Ъ' } else { 'ъ' }, + 0x1C => '\n', + 0x1E => if upper { 'Ф' } else { 'ф' }, + 0x1F => if upper { 'Ы' } else { 'ы' }, + 0x20 => if upper { 'В' } else { 'в' }, + 0x21 => if upper { 'А' } else { 'а' }, + 0x22 => if upper { 'П' } else { 'п' }, + 0x23 => if upper { 'Р' } else { 'р' }, + 0x24 => if upper { 'О' } else { 'о' }, + 0x25 => if upper { 'Л' } else { 'л' }, + 0x26 => if upper { 'Д' } else { 'д' }, + 0x27 => if upper { 'Ж' } else { 'ж' }, + 0x28 => if upper { 'Э' } else { 'э' }, + 0x29 => if upper { 'Ё' } else { 'ё' }, + 0x2B => if kbd.shift { '/' } else { '\\' }, + 0x2C => if upper { 'Я' } else { 'я' }, + 0x2D => if upper { 'Ч' } else { 'ч' }, + 0x2E => if upper { 'С' } else { 'с' }, + 0x2F => if upper { 'М' } else { 'м' }, + 0x30 => if upper { 'И' } else { 'и' }, + 0x31 => if upper { 'Т' } else { 'т' }, + 0x32 => if upper { 'Ь' } else { 'ь' }, + 0x33 => if upper { 'Б' } else { 'б' }, + 0x34 => if upper { 'Ю' } else { 'ю' }, + 0x35 => if kbd.shift { ',' } else { '.' }, + 0x39 => ' ', + _ => '\0', + } + } else { + // Английская раскладка QWERTY + match scancode { + 0x02 => if kbd.shift { '!' } else { '1' }, + 0x03 => if kbd.shift { '@' } else { '2' }, + 0x04 => if kbd.shift { '#' } else { '3' }, + 0x05 => if kbd.shift { '$' } else { '4' }, + 0x06 => if kbd.shift { '%' } else { '5' }, + 0x07 => if kbd.shift { '^' } else { '6' }, + 0x08 => if kbd.shift { '&' } else { '7' }, + 0x09 => if kbd.shift { '*' } else { '8' }, + 0x0A => if kbd.shift { '(' } else { '9' }, + 0x0B => if kbd.shift { ')' } else { '0' }, + 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.push(ch as u8); + if ch != '\0' { + kbd.push_char(ch); + } } diff --git a/servers/sh/src/main.rs b/servers/sh/src/main.rs index 8d57e5c..7525f4f 100644 --- a/servers/sh/src/main.rs +++ b/servers/sh/src/main.rs @@ -72,13 +72,17 @@ fn read_line(buf: &mut [u8]) -> usize { 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 { buf[len] = b; len += 1; let slice = [b]; if let Ok(s) = core::str::from_utf8(&slice) { 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(""); match cmd { - "help" | "?" => { - ipc_client::println("\x1b[1;36mopencoreRS Ring 3 Shell — Built-in Commands\x1b[0m"); + "help" | "?" | "помощь" | "справка" => { + ipc_client::println("\x1b[1;36mopencoreRS Ring 3 Shell — Команды / Commands\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;33muname [-a]\x1b[0m Display OS, kernel and architecture"); - ipc_client::println(" \x1b[1;33mls [path]\x1b[0m List VFS directory (/proc, /dev, /etc)"); - ipc_client::println(" \x1b[1;33mcat \x1b[0m Display contents of a file"); - ipc_client::println(" \x1b[1;33mecho \x1b[0m Print text to terminal"); - ipc_client::println(" \x1b[1;33mcpuinfo\x1b[0m Display CPUID vendor, model & features"); - ipc_client::println(" \x1b[1;33mlspci\x1b[0m Scan and list PCI hardware devices"); - ipc_client::println(" \x1b[1;33mifconfig, net\x1b[0m Network interface configuration & stats"); - ipc_client::println(" \x1b[1;33mping [ip]\x1b[0m Send ICMP Echo Request"); - ipc_client::println(" \x1b[1;33mclear, cls\x1b[0m Clear terminal screen"); - ipc_client::println(" \x1b[1;33mexit\x1b[0m Exit userspace shell"); + ipc_client::println(" \x1b[1;33mhelp, помощь, ?\x1b[0m Список команд / Help menu"); + ipc_client::println(" \x1b[1;33muname [-a]\x1b[0m Информация о ядре / Kernel info"); + ipc_client::println(" \x1b[1;33mls [path]\x1b[0m Список файлов VFS (/proc, /dev, /etc)"); + ipc_client::println(" \x1b[1;33mcat \x1b[0m Просмотр файла / Display file"); + ipc_client::println(" \x1b[1;33mecho, эхо \x1b[0m Печать текста / Print text"); + ipc_client::println(" \x1b[1;33mcpuinfo, инфо\x1b[0m Сведения о CPUID / CPU details"); + ipc_client::println(" \x1b[1;33mlspci\x1b[0m Сканирование шины PCI / PCI devices"); + ipc_client::println(" \x1b[1;33mifconfig, net\x1b[0m Сетевой интерфейс e1000"); + ipc_client::println(" \x1b[1;33mping [ip]\x1b[0m ICMP Echo запрос / Ping network"); + ipc_client::println(" \x1b[1;33mclear, очистить\x1b[0m Очистка экрана / Clear screen"); + ipc_client::println(" \x1b[1;33mexit, выход\x1b[0m Выход из шелла / Exit shell"); ipc_client::println(""); } "uname" => { ipc_client::println("opencoreRS 0.1.0 x86_64 microkernel SMP (Ring 3 Shell)"); } - "echo" => { + "echo" | "эхо" => { let mut first = true; for p in cmdline.split_ascii_whitespace().skip(1) { if !first {