diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..13ff817
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.d
+*.o
+uinputchars
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a8f4ffa
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,15 @@
+CC = gcc
+CFLAGS = -MMD -MP -Wall
+
+SRC=$(wildcard *.c)
+OBJ=$(SRC:.c=.o)
+DEP=$(SRC:.c=.d)
+
+uinputchars: $(OBJ)
+ $(CC) $(LDFLAGS) -o $@ $^
+
+-include $(DEP)
+
+.PHONY: clean
+clean:
+ rm -f $(OBJ) $(DEP) uinputchars
diff --git a/NOTES b/NOTES
new file mode 100644
index 0000000..7ec00f5
--- /dev/null
+++ b/NOTES
@@ -0,0 +1,42 @@
+This program reads a character string and translates it into virtual
+keystrokes that are entered into the system via /dev/uinput.
+
+The key difficulty (pun intended) is to map characters to keystrokes.
+These are the principal bits and pieces involved, along with
+highly-simplified descriptions:
+
+- Character Encoding: This defines the representation of a character
+ by a numeric value (or a sequence of numeric values) in the input
+ character string. On modern, western systems, a typical character
+ encoding is UTF-8.
+
+- Kernel Keymap: Each key on the keyboard has an associated Keycode
+ (or Event Code, defined as KEY_... macros in
+ linux/input-event-codes.h). The Kernel Keymap maps a Keycode (or a
+ combination of Keycodes, if modifier or compose keys are pressed) to
+ an Action Code representing a character (or some other action,
+ defined as K_... macros in linux/keyboard.h). This mapping is
+ determined by the active keymap (as loaded by the loadkeys utility).
+
+- The mapping from Action Code to characters is quite complicated and
+ can be traced in dumpkeys utility. It appears that for ISO-8859-1
+ characters (0..255), the lower byte of the Action Code equals the
+ ISO-8859-1 (= UTF-8) character code.
+
+ Thus, for ISO-8859-1 characters, this mapping is essentially the
+ identity mapping. This is all this program implements so far;
+ therefore, it is currently limited to ISO-8859-1 characters.
+
+ Generally, the Action Code of a character appear to be closely
+ related to their Unicode code point.
+
+To enter a character via /dev/uinput, a corresponding sequence of
+Keycodes is written to this device. This can be done via the
+following steps:
+
+1. Map the character to its corresponding Action Code.
+
+2. Map the Action Code to a corresponding (sequence of) Keycode(s) via
+ an inverse keyboard mapping.
+
+3. Write the Keycode(s) to /dev/uinput.
diff --git a/README.md b/README.md
index 305f85b..a4e996d 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,37 @@
# uinputchars
-A utility to type character strings into /dev/uinput
+A Linux utility to type character strings into `/dev/uinput`
+
+
+## Usage
+
+A typical use case is [entering
+passwords](https://github.com/clawoflight/puma). In contrast to
+classical password managers, uinputchars can type character strings
+into anything - Web forms, e-mail clients, Emacs - without any client
+support. In contrast to xdotool, it does not require X; it also works
+under Wayland or even on the console.
+
+Generally, uinputchars is run without arguments.
+
+Character strings are read from standard input. There is currently no
+option to pass them on the command line because this would make them
+appear in the process table.
+
+The current implementation only accepts ISO-8859-1 characters (encoded
+as specified by the locale). Depending on the kernel keymap in use,
+only a subset of them may be accessible.
+
+The uinputchars utility needs to retrieve the kernel keymap and to
+write into `/dev/uinput`; both will generally require root rights. It
+is recommended to configure this in `/etc/sudoers`.
+
+Applications receiving events from `/dev/uinput` must be given time to
+process them. This is achieved by brief sleeps that can be adjusted
+via command-line options.
+
+
+## Installation
+
+Under Linux, just run (GNU) make, and move or link the
+resulting executable to wherever you want. If this does not work, I
+welcome your patches.
diff --git a/invkeymap.h b/invkeymap.h
new file mode 100644
index 0000000..4579719
--- /dev/null
+++ b/invkeymap.h
@@ -0,0 +1,25 @@
+/* Copyright 2019 Justus Piater */
+/* This file is part of UINPUTCHARS.
+
+ UINPUTCHARS is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ UINPUTCHARS is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with UINPUTCHARS. If not, see . */
+
+#ifndef INVKEYMAP_H
+#define INVKEYMAP_H
+
+typedef struct {
+ unsigned char modifiers;
+ unsigned char principal;
+} keyvent;
+
+#endif
diff --git a/keymap.c b/keymap.c
new file mode 100644
index 0000000..7eda0cb
--- /dev/null
+++ b/keymap.c
@@ -0,0 +1,162 @@
+/* Copyright 2019 Justus Piater, with one exception documented below */
+/* This file is part of UINPUTCHARS.
+
+ UINPUTCHARS is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ UINPUTCHARS is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with UINPUTCHARS. If not, see . */
+
+/* Some documentation relevant to this code:
+ man 4 console_ioctl
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "keymap.h"
+
+#define MAX_KEYVENTS 0x100
+#define LINEFEED 0x0a
+
+
+/* The two functions is_a_console() and getfd() below evolved from
+ code copied from kbd/src/getfd.c (GPL; Copyright (C) 1994-1999
+ Andries E. Brouwer) from http://kbd-project.org/.
+
+ This reused GPL (v2 or later) code is one reason why all parts of
+ UINPUTCHARS are released under the GPL. */
+
+static int is_a_console(int fd) {
+ char arg = 0;
+ return (isatty(fd) &&
+ ioctl(fd, KDGKBTYPE, &arg) == 0 &&
+ ((arg == KB_101) || (arg == KB_84)) );
+}
+
+
+static int getfd() {
+ static char *conspath[] =
+ { "/proc/self/fd/0",
+ "/dev/tty",
+ "/dev/tty0",
+ "/dev/vc/0",
+ "/dev/systty",
+ "/dev/console",
+ NULL };
+
+ for (int i = 0; conspath[i]; i++) {
+ /* Permissions don't matter: */
+ int fd = open(conspath[i], O_RDWR);
+ if (fd < 0) fd = open(conspath[i], O_WRONLY);
+ if (fd < 0) fd = open(conspath[i], O_RDONLY);
+ if (fd >= 0) {
+ if (is_a_console(fd))
+ return fd;
+ close(fd);
+ }
+ }
+
+ for (int fd = 0; fd < 3; fd++)
+ if (is_a_console(fd))
+ return fd;
+
+ fprintf(stderr,
+ "Couldn't get a file descriptor referring to the console\n");
+ return -1;
+}
+
+
+static void print_keyventry(unsigned int c, unsigned char modifiers,
+ unsigned char principal) {
+ printf("[%02x=%c %02x %3u]\n", c, c, modifiers, principal);
+}
+
+static void print_keyvent(const keyvent* invkeymap, unsigned char c) {
+ print_keyventry(c, invkeymap[c].modifiers, invkeymap[c].principal);
+}
+
+
+void print_invkeymap(const keyvent* invkeymap) {
+ for (int c = 0; c < MAX_KEYVENTS; c++) {
+ if (invkeymap[c].principal) {
+ printf("%02x ", c);
+ print_keyvent(invkeymap, c);
+ }
+ }
+}
+
+
+static void get_keys(int fd, keyvent* invkeymap) {
+ /* This loop was inspired by lk_kernel_keys()
+ in kbd/src/libkeymap/kernel.c (GPL) from http://kbd-project.org/. */
+ for (int table = 0; table < MAX_NR_KEYMAPS; table++) {
+ for (int index = 0; index < NR_KEYS; index++) {
+ struct kbentry kbe;
+ kbe.kb_table = table;
+ kbe.kb_index = index;
+ kbe.kb_value = 0;
+
+ if (ioctl(fd, KDGKBENT, (unsigned long)&kbe))
+ fprintf(stderr, "KDGKBENT: %s: error at index %d in table %d\n",
+ strerror(errno), index, table);
+
+ if (!index && kbe.kb_value == K_NOSUCHMAP)
+ break;
+
+ if (KTYP(kbe.kb_value) == KT_LATIN ||
+ KTYP(kbe.kb_value) == KT_LETTER ) {
+ int charvalue = KVAL(kbe.kb_value);
+ if (!invkeymap[charvalue].principal) {
+ /* Prefer lower-valued tables */
+ invkeymap[charvalue].modifiers = table;
+ invkeymap[charvalue].principal = index;
+ }
+ }
+ else if (kbe.kb_value == K_ENTER &&
+ !invkeymap[LINEFEED].principal) {
+ invkeymap[LINEFEED].modifiers = table;
+ invkeymap[LINEFEED].principal = index;
+ }
+ }
+ }
+}
+
+
+keyvent* get_invkeymap() {
+ int fd = getfd();
+ if (fd < 0)
+ return NULL;
+
+ keyvent* invkeymap = calloc(MAX_KEYVENTS, sizeof(*invkeymap));
+ if (!invkeymap) {
+ perror("invkeymap");
+ return NULL;
+ }
+
+ get_keys(fd, invkeymap);
+ close(fd);
+
+ return invkeymap;
+}
+
+
+void release_invkeymap(keyvent* invkeymap) {
+ free(invkeymap);
+}
diff --git a/keymap.h b/keymap.h
new file mode 100644
index 0000000..09b1632
--- /dev/null
+++ b/keymap.h
@@ -0,0 +1,21 @@
+/* Copyright 2019 Justus Piater */
+/* This file is part of UINPUTCHARS.
+
+ UINPUTCHARS is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ UINPUTCHARS is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with UINPUTCHARS. If not, see . */
+
+#include "invkeymap.h"
+
+keyvent* get_invkeymap(void);
+void release_invkeymap(keyvent* invkeymap);
+void print_invkeymap(const keyvent* invkeymap);
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..8cc4aa7
--- /dev/null
+++ b/main.c
@@ -0,0 +1,120 @@
+/* Copyright 2019 Justus Piater */
+/* This file is part of UINPUTCHARS.
+
+ UINPUTCHARS is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ UINPUTCHARS is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with UINPUTCHARS. If not, see . */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "keymap.h"
+#include "uinput.h"
+
+
+static void print_help(const char* argv0, struct uinput_opts_t* uinput_opts) {
+ printf("Usage: %s [options]\n"
+ "Reads characters from stdin and types them into /dev/uinput.\n"
+ "Currently limited to ISO-8859-1 characters. Depending on the "
+ "currently-active\nkernel keymap, only a subset of these may be "
+ "available.\nMust typically be run as root.\n"
+ "Options:\n"
+ "-h --help display this help and exit\n"
+ "-p --pre-pause milliseconds to wait after /dev/uinput setup "
+ "(%lu ms)\n"
+ "-i --inter-pause milliseconds to wait between characters typed "
+ "(%lu ms)\n"
+ "-d --dump display inverse keymap and exit\n",
+ argv0,
+ uinput_opts->pre_pause / 1000, uinput_opts->inter_pause / 1000);
+}
+
+
+int main(int argc, char* argv[]) {
+ struct uinput_opts_t uinput_opts = { 200000, 10000 };
+ int dump = 0;
+
+ static struct option options[] =
+ { { "help", no_argument, NULL, 'h' },
+ { "pre-pause", required_argument, NULL, 'p' },
+ { "inter-pause", required_argument, NULL, 'i' },
+ { "dump", no_argument, NULL, 'd' },
+ { NULL, 0, NULL, 0 } };
+ while (1) {
+ int c = getopt_long(argc, argv, "hp:i:d", options, NULL);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'h':
+ print_help(argv[0], &uinput_opts);
+ exit(0);
+ case 'p':
+ uinput_opts.pre_pause = strtoul(optarg, NULL, 10) * 1000;
+ break;
+ case 'i':
+ uinput_opts.inter_pause = strtoul(optarg, NULL, 10) * 1000;
+ break;
+ case 'd':
+ dump = 1;
+ break;
+ case '?':
+ exit(1);
+ default:
+ fprintf(stderr, "unrecognized option: %c\n", c);
+ exit(1);
+ }
+ }
+
+ keyvent* invkeymap = get_invkeymap();
+ if (!invkeymap)
+ exit(2);
+
+ if (dump) {
+ print_invkeymap(invkeymap);
+ release_invkeymap(invkeymap);
+ exit(0);
+ }
+
+ int result = 0;
+
+ setlocale(LC_CTYPE, "");
+ iconv_t cd = iconv_open("ISO-8859-1", "");
+
+ const int bufsize = 1024;
+ char localebuf[bufsize];
+ while (fgets(localebuf, bufsize, stdin)) {
+ char latin1buf[1024];
+ size_t inbytesleft = strlen(localebuf);
+ size_t outbytesleft = bufsize;
+ char* inbuf = localebuf;
+ char* outbuf = latin1buf;
+ size_t converted = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
+ *outbuf = 0;
+ if (converted == (size_t)-1) {
+ perror("Error: iconv");
+ result = 3;
+ break;
+ }
+
+ if (emit_string(latin1buf, invkeymap, &uinput_opts)) {
+ result = 4;
+ break;
+ }
+ }
+
+ iconv_close(cd);
+ release_invkeymap(invkeymap);
+ exit(result);
+}
diff --git a/tuinputchars b/tuinputchars
new file mode 100755
index 0000000..a9cef44
--- /dev/null
+++ b/tuinputchars
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+for out in "$@"; do
+ echo $out | sudo ./uinputchars
+ read in
+ if [ $in != $out ]; then
+ echo "$out != $in"
+ fi
+done
diff --git a/uinput.c b/uinput.c
new file mode 100644
index 0000000..d5fa20e
--- /dev/null
+++ b/uinput.c
@@ -0,0 +1,97 @@
+/* Copyright 2019 Justus Piater */
+/* This file is part of UINPUTCHARS.
+
+ UINPUTCHARS is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ UINPUTCHARS is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with UINPUTCHARS. If not, see . */
+
+/* Some documentation relevant to this code:
+ https://www.kernel.org/doc/html/v5.1/input/uinput.html */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "uinput.h"
+
+
+static void emit(int fd, int type, int code, int val)
+{
+ struct input_event ie = {};
+ ie.type = type;
+ ie.code = code;
+ ie.value = val;
+
+ if (write(fd, &ie, sizeof(ie)) < sizeof(ie))
+ perror("emit");
+}
+
+
+int emit_string(const char* string, const keyvent* invkeymap,
+ const struct uinput_opts_t* uinput_opts) {
+ int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
+ if (fd < 0) {
+ perror("open");
+ return -1;
+ }
+
+ ioctl(fd, UI_SET_EVBIT, EV_KEY);
+ int nchars = strlen(string);
+ for (int i = 0; i < nchars; i++) {
+ unsigned char principal =
+ invkeymap[(int)(unsigned char)string[i]].principal;
+ if (principal)
+ ioctl(fd, UI_SET_KEYBIT, principal);
+ else {
+ fprintf(stderr, "Error: no key for character 0x%02x=%c\n",
+ (unsigned char)string[i], string[i]);
+ close(fd);
+ return -1;
+ }
+ }
+ ioctl(fd, UI_SET_KEYBIT, KEY_LEFTSHIFT);
+ ioctl(fd, UI_SET_KEYBIT, KEY_RIGHTALT);
+
+ struct uinput_setup usetup = {};
+ strcpy(usetup.name, "Synthesized Keyboard Input");
+ ioctl(fd, UI_DEV_SETUP, &usetup);
+ ioctl(fd, UI_DEV_CREATE);
+
+ usleep(uinput_opts->pre_pause);
+
+ for (int i = 0; i < nchars; i++) {
+ const keyvent* kev = invkeymap + (unsigned char)string[i];
+
+ if (kev->modifiers & K_SHIFTTAB)
+ emit(fd, EV_KEY, KEY_LEFTSHIFT, 1);
+ if (kev->modifiers & K_ALTTAB)
+ emit(fd, EV_KEY, KEY_RIGHTALT, 1);
+
+ emit(fd, EV_KEY, kev->principal, 1);
+ emit(fd, EV_KEY, kev->principal, 0);
+
+ if (kev->modifiers & K_ALTTAB)
+ emit(fd, EV_KEY, KEY_RIGHTALT, 0);
+ if (kev->modifiers & K_SHIFTTAB)
+ emit(fd, EV_KEY, KEY_LEFTSHIFT, 0);
+
+ emit(fd, EV_SYN, SYN_REPORT, 0);
+ usleep(uinput_opts->inter_pause);
+ }
+
+ ioctl(fd, UI_DEV_DESTROY);
+ close(fd);
+ return 0;
+}
diff --git a/uinput.h b/uinput.h
new file mode 100644
index 0000000..a760271
--- /dev/null
+++ b/uinput.h
@@ -0,0 +1,25 @@
+/* Copyright 2019 Justus Piater */
+/* This file is part of UINPUTCHARS.
+
+ UINPUTCHARS is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ UINPUTCHARS is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with UINPUTCHARS. If not, see . */
+
+#include "invkeymap.h"
+
+struct uinput_opts_t {
+ unsigned long pre_pause; /* microseconds */
+ unsigned long inter_pause; /* microseconds */
+};
+
+int emit_string(const char* string, const keyvent* invkeymap,
+ const struct uinput_opts_t* uinput_opts);