SchOS/cmd/loadkeys/main.c
Nexoniarz 26bca020b6 licence: SchOS is GPL-2.0-or-later, no longer Apache-2.0
Nexoniarz is the sole author and SchOS was never published under
Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text,
every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE,
README.md, docs/licensing.md and the site say so. projects/schfs-linux
follows the rest of the tree.

The GPL gives copyleft: a fork has to ship its source. It also lifts the
old "Linux drivers, never" rule, since GPL-2.0-only code combines with
GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still
has to pick one version, so docs/licensing.md now keeps Apache-2.0 and
GPLv3 code out of the kernel to keep Linux code possible there. BSD
4-clause code goes from "avoid" to "no", because the advertising clause
is GPL-incompatible.

LDLS still ships no .ko. That boundary is now project policy rather than
a legal requirement, and ldls.md, devices.md and licensing.md section 6
say so. refmod declares license=GPL, and ldls-check passes (27 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:45 +02:00

184 lines
5.1 KiB
C

// 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 | <layout>]\n"
" -l list the available layouts\n"
" -c apply KEYMAP from " VCONSOLE "\n"
" <layout> 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;
}