Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.