Skip to content

Instantly share code, notes, and snippets.

@jdinalt
Created September 12, 2019 06:56
Show Gist options
  • Save jdinalt/f0ade7e8123d574e7d7c59e2efc9cd65 to your computer and use it in GitHub Desktop.
Save jdinalt/f0ade7e8123d574e7d7c59e2efc9cd65 to your computer and use it in GitHub Desktop.
A quick hack to create a virtual "H-Shifter" from a numeric keypad
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <assert.h>
#include <stdint.h>
#include <stdbool.h>
#include <linux/uinput.h>
#include <linux/input.h>
#include <linux/input-event-codes.h>
#include <libevdev/libevdev-uinput.h>
#include <libevdev/libevdev.h>
/* Define virtual shifter input codes */
enum {
Key_first = KEY_INSERT,
Key_second = KEY_DELETE,
Key_third = KEY_HOME,
Key_fourth = KEY_END,
Key_fifth = KEY_PAGEUP,
Key_sixth = KEY_PAGEDOWN,
Key_seventh = KEY_PAUSE,
Key_reverse = KEY_BACKSPACE,
Key_neutral0 = KEY_BACKSLASH,
Key_neutral1 = KEY_BACKSLASH,
Key_neutral2 = KEY_BACKSLASH,
};
/* Define virtual shifter output codes */
enum {
Button_first = KEY_KP1,
Button_second = KEY_KP2,
Button_third = KEY_KP3,
Button_fourth = KEY_KP4,
Button_fifth = KEY_KP5,
Button_sixth = KEY_KP6,
Button_seventh = KEY_KP7,
Button_reverse = KEY_KP0,
};
/* Declare types */
typedef struct InputKB InputKB;
typedef struct VirtualKB VirtualKB;
typedef struct Event Event;
typedef struct Transition Transition;
typedef struct State State;
typedef struct Statemachine Statemachine;
/* Define types */
struct VirtualKB {
int uifd;
int fd;
struct libevdev_uinput *uidev;
char const *syspath;
char const *devnode;
};
struct InputKB {
int fd;
struct libevdev *dev;
int saved_repeat[2];
};
struct Event {
unsigned type;
unsigned code;
int value;
};
struct Transition {
char const * name;
Event const * const * event_set;
State const * next_state;
Event const * const * actions;
bool local;
};
struct State {
char const *name;
Transition const * const * transition_set;
Event const *const * entry_actions;
Event const * const * exit_actions;
};
struct Statemachine {
char const *name;
State const *state;
VirtualKB *vkb;
};
/* Helper macros for defining stuff */
#define Event_DEFINE_EVENT(_name, _type, _code, _value) \
Event const Event_ ## _name = { \
.type = (_type), \
.code = (_code), \
.value = (_value), \
}
#define Event_DEFINE_KEY_UP(_name, _code) \
Event_DEFINE_EVENT(_name, EV_KEY, _code, 0)
#define Event_DEFINE_KEY_DOWN(_name, _code) \
Event_DEFINE_EVENT(_name, EV_KEY, _code, 1)
#define Transition_DEFINE(_name, _event, _next_state) \
Transition const Transition_ ## _name = { \
.name = # _name, \
.event_set = (Event const * const []) { \
&Event_ ## _event, \
NULL \
}, \
.next_state = &State_ ## _next_state, \
}
/* Declare methods */
int InputKB_ctor(InputKB* ikb, char const *dev_path);
void InputKB_dtor(InputKB* ikb);
int InputKB_nextEvent(InputKB* ikb, Event *event);
int VirtualKB_ctor(VirtualKB *vkb, const char *name, struct libevdev *dev, Event const * const *event_set);
void VirtualKB_dtor(VirtualKB *vkb);
int VirtualKB_postEvent(VirtualKB *vkb, Event const *event);
int VirtualKB_postEventSet(VirtualKB *vkb, Event const * const * event_set);
void Statemachine_ctor(Statemachine *fsm, char const *name, VirtualKB *vkb,
Transition const *initial_transition);
void Statemachine_dtor(Statemachine *fsm);
void Statemachine_transition(Statemachine *fsm, Transition const *trans);
void Statemachine_handleEvent(Statemachine *fsm, Event const *event);
void dump_dev_info(char const *prefix, struct libevdev *dev);
void Event_print(FILE *file, Event const *event);
/* Declare states */
State const State_neutral;
State const State_first;
State const State_second;
State const State_third;
State const State_fourth;
State const State_fifth;
State const State_sixth;
State const State_seventh;
State const State_reverse;
/* Define Events */
Event_DEFINE_KEY_DOWN(selectFirst, Key_first);
Event_DEFINE_KEY_DOWN(selectSecond, Key_second);
Event_DEFINE_KEY_DOWN(selectThird, Key_third);
Event_DEFINE_KEY_DOWN(selectFourth, Key_fourth);
Event_DEFINE_KEY_DOWN(selectFifth, Key_fifth);
Event_DEFINE_KEY_DOWN(selectSixth, Key_sixth);
Event_DEFINE_KEY_DOWN(selectSeventh, Key_seventh);
Event_DEFINE_KEY_DOWN(selectReverse, Key_reverse);
Event_DEFINE_KEY_DOWN(selectNeutral0, Key_neutral0);
Event_DEFINE_KEY_DOWN(selectNeutral1, Key_neutral1);
Event_DEFINE_KEY_DOWN(selectNeutral2, Key_neutral2);
Event_DEFINE_EVENT(syn, EV_SYN, SYN_REPORT, 0);
Event_DEFINE_KEY_DOWN(enterFirst, Button_first);
Event_DEFINE_KEY_UP(exitFirst, Button_first);
Event_DEFINE_KEY_DOWN(enterSecond, Button_second);
Event_DEFINE_KEY_UP(exitSecond, Button_second);
Event_DEFINE_KEY_DOWN(enterThird, Button_third);
Event_DEFINE_KEY_UP(exitThird, Button_third);
Event_DEFINE_KEY_DOWN(enterFourth, Button_fourth);
Event_DEFINE_KEY_UP(exitFourth, Button_fourth);
Event_DEFINE_KEY_DOWN(enterFifth, Button_fifth);
Event_DEFINE_KEY_UP(exitFifth, Button_fifth);
Event_DEFINE_KEY_DOWN(enterSixth, Button_sixth);
Event_DEFINE_KEY_UP(exitSixth, Button_sixth);
Event_DEFINE_KEY_DOWN(enterSeventh, Button_seventh);
Event_DEFINE_KEY_UP(exitSeventh, Button_seventh);
Event_DEFINE_KEY_DOWN(enterReverse, Button_reverse);
Event_DEFINE_KEY_UP(exitReverse, Button_reverse);
Event const * const Event_kInputSet [] = {
&Event_selectFirst,
&Event_selectSecond,
&Event_selectThird,
&Event_selectFourth,
&Event_selectFifth,
&Event_selectSixth,
&Event_selectSeventh,
&Event_selectReverse,
&Event_selectNeutral0,
&Event_selectNeutral1,
&Event_selectNeutral2,
NULL
};
Event const * const Event_kOutputSet [] = {
&Event_enterFirst,
&Event_enterSecond,
&Event_enterThird,
&Event_enterFourth,
&Event_enterFifth,
&Event_enterSixth,
&Event_enterSeventh,
&Event_enterReverse,
NULL
};
enum Event_Match {
Event_Match_TYPE = 0x01,
Event_Match_CODE = 0x02,
Event_Match_VALUE = 0x04,
Event_Match_ALL = Event_Match_TYPE | Event_Match_CODE | Event_Match_VALUE,
Event_Match_TYPE_CODE = Event_Match_TYPE | Event_Match_CODE,
};
bool Event_isInSet(Event const *event, Event const *const * event_set, enum Event_Match match);
/* Declare initial transitions */
Transition const Transition_initial;
void usage(int argc, char **argv)
{
(void)argc;
fprintf(stdout,
"Usage: %s /dev/input/event<X>\n"
" /dev/input/event<X> : A keyboard input device\n"
"\n"
"Stack a virtual 'H-Shifter' interface onto a keyboard\n"
"\n"
"Shifters are expected to generate a 'key-down' event for \n"
"the selected 'gear' and not generate a 'key-up' event until the\n"
"taken out of gear. This makes using a normal keyboard for this \n"
"funcionality difficult, as it requires holding the key down \n"
"to stay in gear -- not unlike a broken transmission, but who \n"
"wants to emmulate driving a car with a broken transmission!?\n"
"\n"
"This takes the specified keyboard, grabs it for exclusive access, \n"
"creates a virtual keyboard, forwards normal key strokes, but \n"
"maps a H-shifter interface onto the numeric keypad.\n"
"\n"
" 1 3 5 \n"
" | | | \n"
" xxx---N------\n"
" | | | | |\n"
" R 2 4 6 7\n"
"\n"
" first --> KEY_KP7\n"
" second --> KEY_KP1\n"
" third --> KEY_KP8\n"
" fourth --> KEY_KP2\n"
" fifth --> KEY_KP9\n"
" sixth --> KEY_KP3\n"
" seventh--> KEY_KPDOT\n"
" reverse --> KEY_KP0\n"
" neutral --> KEY_KP4, KEY_KP5, KEY_KP6\n"
"\n"
"Entering and leaving reverse requires moving through neutral first\n"
"\n"
"As this driver disables key-repeat, it may fail if not run as root.\n"
"\n"
"See Also:\n"
" man 1 evtest\n"
" man 1 evemu-describe\n"
" https://www.kernel.org/doc/html/v4.12/input/input_uapi.html\n"
,
argv[0]
);
}
int
main(int argc, char **argv)
{
(void)argc;
(void)argv;
InputKB ikb;
VirtualKB vkb;
Statemachine fsm;
int rc;
if (argc != 2) {
usage(argc, argv);
exit(1);
}
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 ) {
usage(argc, argv);
exit(0);
}
/* Open the input keyboard */
rc = InputKB_ctor(&ikb, argv[1]);
/* Construct our virtual keyboard */
rc = VirtualKB_ctor(&vkb, "Virtual Shifter", ikb.dev, Event_kOutputSet);
if (rc < 0) {
fprintf(stderr, "Failed to create uinput device (%s)\n", strerror(-rc));
exit(1);
}
/* Construct the shifter state-machine */
Statemachine_ctor(&fsm, "shifter", &vkb, &Transition_initial);
while (true) {
Event event;
rc = InputKB_nextEvent(&ikb, &event);
switch (rc) {
case 0:
break;
case 1:
case -EAGAIN:
continue;
default:
fprintf(stderr, "Unexpected rc from libevdev_next_event \"%s\"; exiting\n",
strerror(-rc));
exit(-rc);
}
/* Is this event in our reserved input set? */
if (Event_isInSet(&event, Event_kInputSet, Event_Match_TYPE_CODE)) {
/* Let the state-machine handle it */
Statemachine_handleEvent(&fsm, &event);
continue;
}
/* Forward, if not in our output set */
if (!Event_isInSet(&event, Event_kOutputSet, Event_Match_TYPE_CODE)) {
VirtualKB_postEvent(&vkb, &event);
}
}
return 0;
}
void
dump_dev_info(char const *prefix, struct libevdev *dev)
{
printf("%s name: \"%s\"\n", prefix, libevdev_get_name(dev));
printf("%s ID: bus %#x vendor %#x product %#x\n", prefix,
libevdev_get_id_bustype(dev),
libevdev_get_id_vendor(dev),
libevdev_get_id_product(dev));
}
void
Event_print(FILE* file, Event const *event)
{
char const *type_name = libevdev_event_type_get_name(event->type);
char const *code_name = libevdev_event_code_get_name(event->type, event->code);
if (type_name) {
fprintf(file, "%s ", type_name);
} else {
fprintf(file, "%u ", event->type);
}
if (code_name) {
fprintf(file, "%s ", code_name);
} else {
fprintf(file, "%u ", event->code);
}
fprintf(file, "%d\n", event->value);
}
int
InputKB_ctor(InputKB* ikb, char const *dev_path)
{
int rc;
ikb->fd = open(dev_path, O_RDWR | O_NONBLOCK);
if (ikb->fd < 0) {
rc = -errno;
fprintf(stderr, "Failed to open \"%s\" (%s)\n", dev_path, strerror(-rc));
goto fail_open;
}
rc = libevdev_new_from_fd(ikb->fd, &ikb->dev);
if (rc < 0) {
fprintf(stderr, "Failed to create new input device (%s)\n", strerror(-rc));
goto fail_new_dev;
}
if (!libevdev_has_event_type(ikb->dev, EV_KEY)) {
fprintf(stderr, "This device does not look like a keyboard\n");
rc = EINVAL;
goto fail_type_check;
}
dump_dev_info("Input Device", ikb->dev);
rc = libevdev_grab(ikb->dev, LIBEVDEV_GRAB);
if (rc < 0) {
fprintf(stderr, "Failed to grab input device (%s)\n", strerror(-rc));
goto fail_grab;
}
return 0;
fail_grab:
fail_type_check:
libevdev_free(ikb->dev);
fail_new_dev:
close(ikb->fd);
fail_open:
return rc;
}
void
InputKB_dtor(InputKB* ikb)
{
libevdev_grab(ikb->dev, LIBEVDEV_UNGRAB);
libevdev_free(ikb->dev);
close(ikb->fd);
}
int
InputKB_nextEvent(InputKB* ikb, Event *event)
{
int rc;
struct input_event ev;
rc = libevdev_next_event(ikb->dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
if (rc) {
goto fail;
}
event->type = ev.type;
event->code = ev.code;
event->value = ev.value;
return 0;
fail:
return rc;
}
int
VirtualKB_ctor(VirtualKB *vkb, const char *name, struct libevdev *dev, Event const * const *event_set)
{
int rc;
struct Event const *event;
unsigned repeat[2];
/* TODO: Change name of new device */
(void)name;
rc = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &vkb->uidev);
if (rc) {
rc = -errno;
fprintf(stderr, "Failed libevdev_uinput_create_from_device (%s)\n", strerror(errno));
goto fail_create;
}
/* for each event in 'event_set' ... */
while ((event = *event_set++) != NULL) {
assert(event->type == EV_KEY);
printf("Registering Event: ");
Event_print(stdout, event);
/* Register event code */
rc = libevdev_enable_event_code(dev, EV_KEY, event->code, NULL);
if (rc) {
rc = -errno;
fprintf(stderr, "Failed libevdev_enable_event_code() \"%s\"\n", strerror(errno));
goto fail;
}
}
vkb->uifd = libevdev_uinput_get_fd(vkb->uidev);
vkb->syspath = libevdev_uinput_get_syspath(vkb->uidev);
vkb->devnode = libevdev_uinput_get_devnode(vkb->uidev);
printf("Device Node: %s\n", vkb->devnode ? vkb->devnode : "unknown");
printf("Syspath: %s\n", vkb->syspath ? vkb->syspath : "unknown");
if (!vkb->devnode) {
rc = -EINVAL;
fprintf(stderr, "Failed to get path for new device node\n");
goto fail;
}
vkb->fd = open(vkb->devnode, O_RDWR | O_NONBLOCK);
if (vkb->fd < 0) {
rc = -errno;
fprintf(stderr, "Failed to open \"%s\" (%s)\n", vkb->devnode, strerror(-rc));
goto fail;
}
rc = ioctl(vkb->fd, EVIOCGREP, &repeat[0]);
if (rc < 0) {
rc = -errno;
fprintf(stderr, "Failed EVIOCGREP \"%s\"\n", strerror(errno));
goto fail_get_repeat;
}
printf("Repeat: delay=%u repeat=%u\n", repeat[0], repeat[1]);
printf("Disabling auto-repeat\n");
/* Disable key repeat */
repeat[1] = 0;
rc = ioctl(vkb->fd, EVIOCSREP, &repeat[0]);
if (rc < 0) {
rc = -errno;
fprintf(stderr, "Failed EVIOCSREP \"%s\"\n", strerror(errno));
goto fail_set_repeat;
}
return 0;
fail_set_repeat:
fail_get_repeat:
close(vkb->fd);
fail:
libevdev_uinput_destroy(vkb->uidev);
fail_create:
return rc;
}
void
VirtualKB_dtor(VirtualKB *vkb)
{
close(vkb->fd);
libevdev_uinput_destroy(vkb->uidev);
}
int
VirtualKB_postEvent(VirtualKB *vkb, Event const *event)
{
int rc;
#if 0
struct input_event ie = {
.type = event->type,
.code = event->code,
.value = event->value,
};
printf("Posting Event: ");
Event_print(stdout, event);
#endif
rc = libevdev_uinput_write_event(vkb->uidev, event->type, event->code, event->value);
if (rc < 0) {
fprintf(stderr, "Failed write \"%s\"\n", strerror(-rc));
goto fail_write;
}
return 0;
fail_write:
return rc;
}
int
VirtualKB_postEventSet(VirtualKB *vkb, Event const * const * event_set)
{
Event const *event;
int rc;
if (!event_set) {
return 0;
}
while ((event = *event_set++) != NULL) {
rc = VirtualKB_postEvent(vkb, event);
if (rc) {
return rc;
}
}
return 0;
}
void
Statemachine_ctor(Statemachine *fsm, char const *name, VirtualKB *vkb,
Transition const *initial_transition)
{
fsm->name = name;
fsm->vkb = vkb;
fsm->state = NULL;
Statemachine_transition(fsm, initial_transition);
}
void
Statemachine_dtor(Statemachine *fsm)
{
Statemachine_transition(fsm, NULL);
}
void
Statemachine_transition(Statemachine *fsm, Transition const *trans)
{
State const * from_state = fsm->state;
State const * to_state = trans->next_state;
/*
* If the from and to states are the same and this is not flagged
* as a local transition, then skip
*/
if (from_state == to_state && !trans->local) {
return;
}
printf("%s: Executing transition \"%s\" %s => %s\n",
fsm->name,
trans->name,
from_state ? from_state->name : "initial",
to_state ? to_state->name : "final");
/* If prior state, do exit actions */
if (from_state) {
VirtualKB_postEventSet(fsm->vkb, from_state->exit_actions);
}
/* Set next state */
fsm->state = to_state;
/* Do transition actions */
VirtualKB_postEventSet(fsm->vkb, trans->actions);
/* Do entry actions for new state */
if (to_state->entry_actions) {
VirtualKB_postEventSet(fsm->vkb, to_state->entry_actions);
}
}
void
Statemachine_handleEvent(Statemachine *fsm, Event const *event)
{
/* If no state, just return */
if (!fsm->state) {
return;
}
Transition const * const * transitions = fsm->state->transition_set;
Transition const * transition;
/*
* Determine if 'event' should trigger a state transition
*
* If 'event' matches the event in any event_set of a transition, then
* execute that state transition, else ignore event.
*/
/* For each transition in the present state's transition set */
while ((transition = *transitions++) != NULL) {
if (Event_isInSet(event, transition->event_set, Event_Match_ALL)) {
Statemachine_transition(fsm, transition);
return;
}
}
}
/*
* Define transitions
*/
Transition const Transition_initial = {
.name = "initial",
.next_state = &State_neutral,
};
Transition_DEFINE(toFirst, selectFirst, first);
Transition_DEFINE(toSecond, selectSecond, second);
Transition_DEFINE(toThird, selectThird, third);
Transition_DEFINE(toFourth, selectFourth, fourth);
Transition_DEFINE(toFifth, selectFifth, fifth);
Transition_DEFINE(toSixth, selectSixth, sixth);
Transition_DEFINE(toSeventh, selectSeventh, seventh);
Transition_DEFINE(toReverse, selectReverse, reverse);
Transition const Transition_toNeutral = {
.name = "toNeutral",
.event_set = (Event const * const []) {
&Event_selectNeutral0,
&Event_selectNeutral1,
&Event_selectNeutral2,
NULL
},
.next_state = &State_neutral,
};
#define Transition_FORWARD_TRANSITIONS \
&Transition_toFirst, \
&Transition_toSecond, \
&Transition_toThird, \
&Transition_toFourth, \
&Transition_toFifth, \
&Transition_toSixth, \
&Transition_toSeventh \
/* Define transition sets */
Transition const * const forward_transitions [] = {
Transition_FORWARD_TRANSITIONS,
&Transition_toNeutral,
NULL
};
/* Define reverse set */
Transition const * const reverse_transitions [] = {
&Transition_toNeutral,
NULL
};
/*
* Define states
*/
#define State_DEFINE(_name, _entry, _exit, _trans) \
State const State_ ## _name = { \
.name = # _name, \
\
.transition_set = _trans, \
.entry_actions = (Event const * const []) { \
&Event_ ## _entry, \
&Event_syn, \
NULL \
}, \
.exit_actions = (Event const * const []) { \
&Event_ ## _exit, \
&Event_syn, \
NULL \
}, \
}
State_DEFINE(first, enterFirst, exitFirst, forward_transitions);
State_DEFINE(second, enterSecond, exitSecond, forward_transitions);
State_DEFINE(third, enterThird, exitThird, forward_transitions);
State_DEFINE(fourth, enterFourth, exitFourth, forward_transitions);
State_DEFINE(fifth, enterFifth, exitFifth, forward_transitions);
State_DEFINE(sixth, enterSixth, exitSixth, forward_transitions);
State_DEFINE(seventh, enterSeventh, exitSeventh, forward_transitions);
State_DEFINE(reverse, enterReverse, exitReverse, reverse_transitions);
State const State_neutral = {
.name = "neutral",
.transition_set = (Transition const * const []) {
Transition_FORWARD_TRANSITIONS,
&Transition_toReverse,
NULL
},
};
bool
Event_isInSet(Event const *event, Event const *const * event_set, enum Event_Match match)
{
Event const *e;
while ((e = *event_set++) != NULL) {
/* Does this event match the input? */
if (
(!(match & Event_Match_TYPE) || (e->type == event->type)) &&
(!(match & Event_Match_CODE) || (e->code == event->code)) &&
(!(match & Event_Match_VALUE) || (e->value == event->value))) {
return true;
}
}
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment