Keyboard Layouts
With all the different languages out there, there are a massive number of keyboard layouts — QWERTY for English, QWERTZ for German/Central European, AZERTY for French, Dvorak, Colemak and more. We need to account for at least the common ones by adding multiple scancode tables and letting the user switch between them at runtime.
Layout Enum
Rather than using raw numbers, an enum makes the code readable and safe. Copy and
Clone are needed so the layout can be stored in a static and matched on:
#![allow(unused)]
fn main() {
#[allow(dead_code)]
#[repr(u8)]
#[derive(Copy, Clone)]
pub enum Layout {
Qwerty = 0,
Qwertz = 1,
Azerty = 2,
}
}
Calling Layout::Qwerty is a lot cleaner than passing around magic numbers.
Scancode Tables
Previously we just had one table called SCANCODE_TO_ASCII loosely based on QWERTZ.
Now we have three named tables, one per layout, and a static to track the current one:
#![allow(unused)]
fn main() {
static mut CURRENT_LAYOUT: Layout = Layout::Qwertz; // default
}
The key differences between the tables:
QWERTY vs QWERTZ — almost identical except y and z are swapped. On a QWERTZ
keyboard, the key where QWERTY has y sends scancode 0x15 which maps to z, and
vice versa at 0x2C. Everything else is the same.
AZERTY — more different. a/q are swapped, z/w are swapped, and m moves
to where ; was (0x27). The number row on a real AZERTY keyboard uses special French
characters for unshifted input, but since those are non-ASCII we leave those slots as 0.
#![allow(unused)]
fn main() {
static QWERTZ_TABLE: [u8; 128] = [
0, 27, b'1', b'2', b'3', b'4', b'5', b'6',
b'7', b'8', b'9', b'0', b'-', b'=', 8, b'\t',
b'q', b'w', b'e', b'r', b't', b'z', b'u', b'i', // z at 0x15
b'o', b'p', b'[', b']', b'\n', 0, b'a', b's',
b'd', b'f', b'g', b'h', b'j', b'k', b'l', b';',
b'\'', b'`', 0, b'\\', b'y', b'x', b'c', b'v', // y at 0x2C
b'b', b'n', b'm', b',', b'.', b'/', 0, b'*',
0, b' ', 0, 0, 0, 0, 0, 0,
// ... rest zeroed
];
}
QWERTY and AZERTY follow the same structure with their respective differences applied.
Keyboard Handler
The handler now matches on CURRENT_LAYOUT to pick the right table for each scancode:
#![allow(unused)]
fn main() {
if scancode < 128 && scancode != 0 {
let ascii = match CURRENT_LAYOUT {
Layout::Qwerty => QWERTY_TABLE[scancode as usize],
Layout::Qwertz => QWERTZ_TABLE[scancode as usize],
Layout::Azerty => AZERTY_TABLE[scancode as usize],
};
if ascii != 0 {
crate::shell::handle_input(ascii);
}
}
}
set_layout()
Switching layouts is a one-liner plus a confirmation message:
#![allow(unused)]
fn main() {
pub fn set_layout(layout: Layout) {
unsafe {
CURRENT_LAYOUT = layout;
let name = match layout {
Layout::Qwerty => "QWERTY",
Layout::Qwertz => "QWERTZ",
Layout::Azerty => "AZERTY",
};
println!("Layout set to: {}", name);
}
}
}
Wired up in NPS via sys kb:
> sys kb qwerty
Layout is now set to: QWERTY
A Note on QEMU Scan Codes
During testing, pressing - on a QWERTZ layout was producing /. This is because
QEMU emulates a US English keyboard at the hardware level regardless of software layout.
The scan codes sent are always US QWERTY scan codes — so key positions that differ
between physical keyboards won’t map correctly in emulation.
The y/z swap works correctly between QWERTY and QWERTZ because those keys are in
the same physical position on both layouts (just labelled differently). Keys that are
in genuinely different positions on the physical keyboard (like - on QWERTZ vs QWERTY)
will still produce the US scan code in QEMU.
This is a hardware emulation limitation, not a bug in the layout system. On real hardware with a matching physical keyboard, the layouts would work correctly.