// Keyboard layouts. See include/dev/keymap.h for the interface and its limits. // // Every table is indexed by set-1 scancode, with the E0-prefixed keys folded // into 0x80..0xFF by kbd.c before the lookup -- the same numbering the single // hardcoded table used, so a layout is a diff against a shape that was already // proven to work rather than a new mapping to get wrong. // // The tables are `uint32` and not `uchar`. That is the whole reason this file // exists: `ą` is U+0105 and does not fit in a byte, and a Polish layout that // cannot type `ą` is not one. #include "core/types.h" #include "core/defs.h" #include "dev/kbd.h" #include "dev/keymap.h" // Shared by every layout that does not move the navigation block, which is // all of them -- the arrow keys are not a national matter. Merged into each // level table below rather than consulted separately, because a layout is // allowed to disagree and none currently does. #define NAV_KEYS \ [0xC8] = KBD_UP, [0xD0] = KBD_DN, \ [0xC9] = KBD_PGUP, [0xD1] = KBD_PGDN, \ [0xCB] = KBD_LF, [0xCD] = KBD_RT, \ [0x97] = KBD_HOME, [0xCF] = KBD_END, \ [0xD2] = KBD_INS, [0xD3] = KBD_DEL, \ [0x3B] = KBD_F1, [0x3C] = KBD_F1 + 1, \ [0x3D] = KBD_F1 + 2, [0x3E] = KBD_F1 + 3, \ [0x3F] = KBD_F1 + 4, [0x40] = KBD_F1 + 5, \ [0x41] = KBD_F1 + 6, [0x42] = KBD_F1 + 7, \ [0x43] = KBD_F1 + 8, [0x44] = KBD_F1 + 9, \ [0x57] = KBD_F1 + 10, [0x58] = KBD_F12, \ [0x9C] = '\n', /* KP_Enter */ \ [0xB5] = '/' /* KP_Div */ // --- US --------------------------------------------------------------------- static const uint32 us_plain[256] = { NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 '7', '8', '9', '0', '-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 'o', 'p', '[', ']', '\n', NO, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30 NO, ' ', NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 NAV_KEYS }; static const uint32 us_shift[256] = { NO, 0x1B, '!', '@', '#', '$', '%', '^', // 0x00 '&', '*', '(', ')', '_', '+', '\b', '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 'O', 'P', '{', '}', '\n', NO, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 '"', '~', NO, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30 NO, ' ', NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 NAV_KEYS }; // --- Polish (programmers) --------------------------------------------------- // // US QWERTY with nine letters on AltGr. This is the layout essentially every // Polish developer uses, and the reason is that the alternative -- "Polish // (214)", a genuine national arrangement -- moves Y, Z and most of the // punctuation, which makes every shell and every editor shortcut land // somewhere else. The diacritics go on AltGr and nothing else moves. // // AltGr+x is ź and AltGr+z is ż, which looks arbitrary and is not: the letter // x is unused in Polish, so its key is free, and ź and ż both need one. static const uint32 pl_altgr[256] = { [0x1E] = 0x0105, // a -> ą [0x2E] = 0x0107, // c -> ć [0x12] = 0x0119, // e -> ę [0x26] = 0x0142, // l -> ł [0x31] = 0x0144, // n -> ń [0x18] = 0x00F3, // o -> ó [0x1F] = 0x015B, // s -> ś [0x2D] = 0x017A, // x -> ź [0x2C] = 0x017C, // z -> ż }; static const uint32 pl_shiftaltgr[256] = { [0x1E] = 0x0104, // A -> Ą [0x2E] = 0x0106, // C -> Ć [0x12] = 0x0118, // E -> Ę [0x26] = 0x0141, // L -> Ł [0x31] = 0x0143, // N -> Ń [0x18] = 0x00D3, // O -> Ó [0x1F] = 0x015A, // S -> Ś [0x2D] = 0x0179, // X -> Ź [0x2C] = 0x017B, // Z -> Ż }; // --- German (QWERTZ) -------------------------------------------------------- // // Y and Z swap, the umlauts and ß take the punctuation keys on the home and // top rows, and the key between left Shift and Z -- scancode 0x56, which a US // keyboard does not have at all -- carries < > |. // // The acute (0x0D) and circumflex (0x29) keys are dead on a real German // layout. Here they type the accent on its own. See include/dev/keymap.h. static const uint32 de_plain[256] = { NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 '7', '8', '9', '0', 0x00DF, 0x00B4, '\b', '\t', // ß ´ 'q', 'w', 'e', 'r', 't', 'z', 'u', 'i', // 0x10 'o', 'p', 0x00FC, '+', '\n', NO, 'a', 's', // ü 'd', 'f', 'g', 'h', 'j', 'k', 'l', 0x00F6, // ö 0x00E4, '^', NO, '#', 'y', 'x', 'c', 'v', // ä 'b', 'n', 'm', ',', '.', '-', NO, '*', // 0x30 NO, ' ', NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', ',', NO, NO, '<', NO, // 0x50, 0x56 = <>| NAV_KEYS }; static const uint32 de_shift[256] = { NO, 0x1B, '!', '"', 0x00A7, '$', '%', '&', // 0x00 § '/', '(', ')', '=', '?', '`', '\b', '\t', 'Q', 'W', 'E', 'R', 'T', 'Z', 'U', 'I', // 0x10 'O', 'P', 0x00DC, '*', '\n', NO, 'A', 'S', // Ü 'D', 'F', 'G', 'H', 'J', 'K', 'L', 0x00D6, // Ö 0x00C4, 0x00B0, NO, '\'', 'Y', 'X', 'C', 'V', // Ä ° 'B', 'N', 'M', ';', ':', '_', NO, '*', // 0x30 NO, ' ', NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', ',', NO, NO, '>', NO, // 0x50 NAV_KEYS }; static const uint32 de_altgr[256] = { [0x03] = 0x00B2, // 2 -> ² [0x04] = 0x00B3, // 3 -> ³ [0x08] = '{', // 7 [0x09] = '[', // 8 [0x0A] = ']', // 9 [0x0B] = '}', // 0 [0x0C] = '\\', // ß [0x10] = '@', // q [0x12] = 0x20AC, // e -> € [0x1B] = '~', // + [0x32] = 0x00B5, // m -> µ [0x56] = '|', }; // --- the catalogue ---------------------------------------------------------- // // Order matters only in that the first entry is the default. US, because a // machine whose keyboard layout has never been set should behave the way the // scancodes are printed on the majority of keyboards, and because every // installation instruction anyone follows is written in ASCII. static const struct keylayout layouts[] = { { "us", "English (US), QWERTY", { us_plain, us_shift, 0, 0 } }, { "pl", "Polish (programmers) -- US QWERTY, diacritics on AltGr", { us_plain, us_shift, pl_altgr, pl_shiftaltgr } }, { "de", "German, QWERTZ -- no dead keys, so the accent keys type the accent", { de_plain, de_shift, de_altgr, 0 } }, }; #define NLAYOUTS ((int)(sizeof(layouts) / sizeof(layouts[0]))) static const struct keylayout *active = &layouts[0]; const struct keylayout * keymap_active(void) { return active; } const struct keylayout * keymap_by_index(int i) { if (i < 0 || i >= NLAYOUTS) return 0; return &layouts[i]; } int keymap_set(const char *name) { for (int i = 0; i < NLAYOUTS; i++) { const char *a = layouts[i].name, *b = name; while (*a && *a == *b) { a++; b++; } if (*a == 0 && *b == 0) { active = &layouts[i]; return 0; } } return -1; } // A control character from the plain codepoint rather than from a table of // its own. // // The table this replaces was indexed by scancode, which quietly assumed a US // keyboard: on QWERTZ, Ctrl+Z and Ctrl+Y would have swapped with the letters, // so suspending a job would have depended on the layout. Deriving from what // the key actually types is both shorter and correct everywhere. static uint32 control_of(uint32 cp) { if (cp >= 'a' && cp <= 'z') return cp - 'a' + 1; if (cp >= 'A' && cp <= 'Z') return cp - 'A' + 1; switch (cp) { case '@': case ' ': case '2': return 0; // NUL case '[': return 0x1B; // ESC case '\\': return 0x1C; case ']': return 0x1D; case '^': case '6': return 0x1E; case '_': case '-': case '/': return 0x1F; case '?': return 0x7F; case '\n': case '\r': return '\r'; } return 0; // no control character on this key } // Upper <-> lower for the letters these layouts can produce. // // Caps Lock needs this and Shift does not: Shift has its own table, so the // layout has already said what the shifted key gives. Caps Lock has no table // -- it applies to whatever came out, including `ą` from the AltGr level -- // so the case pair has to be derived from the codepoint. // // Latin Extended-A is laid out in alternating pairs, but not all the same way // round: U+0100..U+0137 and U+014A..U+0177 have the capital on the even // codepoint, while U+0139..U+0148 and U+0179..U+017E have it on the odd one. // L-stroke is U+0141/U+0142 and Z-dot is U+017B/U+017C, so Polish lands in // both halves and getting the parity wrong would shift one of them to a // neighbouring letter rather than to its own capital. static uint32 case_toggle(uint32 cp) { if (cp >= 'a' && cp <= 'z') return cp - 'a' + 'A'; if (cp >= 'A' && cp <= 'Z') return cp - 'A' + 'a'; // Latin-1: the accented letters run C0..DE and E0..FE, with multiplication // and division signs punched out of the middle of each. Eszett (DF) has no // single-codepoint capital and stays as it is. if (cp >= 0xC0 && cp <= 0xDE && cp != 0xD7) return cp + 0x20; if (cp >= 0xE0 && cp <= 0xFE && cp != 0xF7) return cp - 0x20; if ((cp >= 0x0100 && cp <= 0x0137) || (cp >= 0x014A && cp <= 0x0177)) return (cp & 1) ? cp - 1 : cp + 1; // even is the capital if ((cp >= 0x0139 && cp <= 0x0148) || (cp >= 0x0179 && cp <= 0x017E)) return (cp & 1) ? cp + 1 : cp - 1; // odd is the capital return cp; } uint32 keymap_lookup(uint scancode, uint mods) { const struct keylayout *l = active; uint32 cp; if (scancode > 0xFF) return 0; // AltGr first. It is the only modifier that selects a whole different table, // and on a layout that has no AltGr level the lookup falls back rather than // producing nothing -- otherwise holding AltGr on a US keyboard would make // every key dead. int level = KM_PLAIN; if (mods & RALT) level = (mods & SHIFT) ? KM_SHIFTALTGR : KM_ALTGR; else if (mods & SHIFT) level = KM_SHIFT; cp = l->level[level] ? l->level[level][scancode] : 0; if (cp == 0 && level >= KM_ALTGR) { level = (mods & SHIFT) ? KM_SHIFT : KM_PLAIN; cp = l->level[level] ? l->level[level][scancode] : 0; } if (cp == 0) return 0; // Caps Lock is a letter-case toggle, not a Shift: it applies to whatever // the table produced, including a letter that only exists on AltGr. `ą` // with Caps Lock on is `Ą`, and only the layout knows that -- so the case // pair has to come from the codepoint, not from a second table lookup. if ((mods & CAPSLOCK) && cp < KBD_SPECIAL) cp = case_toggle(cp); // Ctrl is last and overrides: Ctrl+Shift+C is still Ctrl+C. if (mods & CTL) { if (cp >= KBD_SPECIAL) return cp; // Ctrl+arrow stays an arrow return control_of(cp); } return cp; }