// SPDX-License-Identifier: GPL-2.0-or-later // // loadkeys -- choose the keyboard layout. // // loadkeys print the active layout // loadkeys -l list the layouts the kernel has // loadkeys pl switch to Polish (programmers) // loadkeys -c apply KEYMAP from /etc/vconsole.conf // // Named after the Linux tool because that is what anyone would type, but it is // not the same program and takes no keymap files. Linux's loadkeys compiles a // text keymap and uploads it a key at a time through KDSKBENT, which means the // data lives in userspace and a machine whose /usr is not mounted has no // layout at all. The layouts here are in the kernel and this only names one. // // The setting is machine-wide, not per-terminal: there is one keyboard, and // switching to tty3 does not move anybody's fingers. #include "types.h" #include "user.h" #include "fcntl.h" #include "posix.h" // No printf, memset or strcpy in lib/; this needs a little of each. static void puts_(const char *s) { int n = 0; while (s[n]) n++; write(1, (void *)s, n); } static void zero(void *p, int n) { char *c = p; while (n-- > 0) *c++ = 0; } static void copy_bounded(char *dst, const char *src, int cap) { int i = 0; while (i < cap - 1 && src[i]) { dst[i] = src[i]; i++; } dst[i] = 0; } // /dev/console rather than fd 0: the caller may have redirected its input, // and the layout being set belongs to the machine's keyboard, not to whichever // terminal this process happens to be reading from. static int console(void) { int fd = open("/dev/console", O_RDWR); if (fd < 0) { puts_("loadkeys: no console to ask\n"); exit(); } return fd; } #define VCONSOLE "/etc/vconsole.conf" // Pull KEYMAP= out of /etc/vconsole.conf. // // Deliberately not a config parser. One key, whole file in one buffer, first // match wins -- and a missing or unreadable file is not an error, because the // kernel's default is already a working keyboard and refusing to boot over a // preference would be absurd. static int keymap_from_file(char *out, int cap) { char buf[512]; int fd, n, i, j; fd = open(VCONSOLE, O_RDONLY); if (fd < 0) return -1; n = read(fd, buf, sizeof(buf) - 1); close(fd); if (n <= 0) return -1; buf[n] = 0; for (i = 0; i < n; i++) { if (buf[i] == '#') { // skip to end of comment while (i < n && buf[i] != '\n') i++; continue; } if (buf[i] != 'K') continue; const char *k = "KEYMAP="; for (j = 0; k[j] && buf[i + j] == k[j]; j++) ; if (k[j]) continue; i += j; for (j = 0; j < cap - 1 && buf[i + j] && buf[i + j] != '\n' && buf[i + j] != ' '; j++) out[j] = buf[i + j]; out[j] = 0; return j > 0 ? 0 : -1; } return -1; } int main(int argc, char *argv[]) { struct kb_layout k; char want[16]; int fd, i; if (argc > 2 || (argc == 2 && argv[1][0] == '-' && argv[1][1] != 'l' && argv[1][1] != 'c')) { puts_("usage: loadkeys [-l | -c | ]\n" " -l list the available layouts\n" " -c apply KEYMAP from " VCONSOLE "\n" " switch to it; with no argument, print the active one\n"); return 1; } fd = console(); if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'c') { if (keymap_from_file(want, sizeof(want)) < 0) { close(fd); return 0; // no preference recorded; not a failure } zero(&k, sizeof(k)); copy_bounded(k.name, want, sizeof(k.name)); if (ioctl(fd, KB_SETLAYOUT, &k) < 0) { puts_("loadkeys: " VCONSOLE " asks for '"); puts_(want); puts_("', which this kernel does not have\n"); close(fd); return 1; } close(fd); return 0; } if (argc == 2 && argv[1][0] == '-') { // -l for (i = 0; ; i++) { zero(&k, sizeof(k)); k.index = i; if (ioctl(fd, KB_LISTLAYOUT, &k) < 0) break; // past the end puts_(k.name); puts_("\t"); puts_(k.desc); puts_("\n"); } close(fd); return 0; } if (argc == 2) { // set zero(&k, sizeof(k)); copy_bounded(k.name, argv[1], sizeof(k.name)); if (ioctl(fd, KB_SETLAYOUT, &k) < 0) { puts_("loadkeys: no layout called '"); puts_(argv[1]); puts_("' -- try loadkeys -l\n"); close(fd); return 1; } close(fd); return 0; } zero(&k, sizeof(k)); // report if (ioctl(fd, KB_GETLAYOUT, &k) < 0) { puts_("loadkeys: the kernel has no layout to report\n"); close(fd); return 1; } puts_(k.name); puts_("\t"); puts_(k.desc); puts_("\n"); close(fd); return 0; }