This is a simple program to read events from a Joystick like MOCUTE Bluetooth
controller and write them back as Keyboard events. If you find the relevant docs and examples its quite simple.
It accepts no flags, if you wan't to change it's parameters just look at the file headers and re-configure them. Everything is fairly simple, adding configuration would take too much of a percentage of the total size.
// Most tricky part, uinput handing, based on the example on:
// https://www.kernel.org/doc/html/latest/input/uinput.html#keyboard-events
//
// Required definitions for useful parameters
#include <linux/uinput.h>
#include <stdlib.h>
// Parameters. Don't want to make the code complex, but this looks
// like a reasonable amount.
#define JOYSTICK_PATH "/dev/input/js0"
#define AXIS_CUTOFF 0x4000
// Input event codes: /usr/include/linux/input-event-codes.h
int AXIS[][2] = {
// {AXIS_MIN_KEY, AXIS_MAX_KEY},
{KEY_DOWN, KEY_UP},
{KEY_LEFT, KEY_RIGHT},
{0xFF, 0xFF},
};
// Reserved keys are ignored, 0xFF marks the end
int BUTTONS[] = {
KEY_K, // 0, nothing to do with it 🤷
KEY_SPACE, // 1
KEY_TAB, // 2
KEY_J, // 3
// Ignored in my joystick
KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED,
KEY_F5, // 9
// DONE
0xFF, // Reserved for AT Keyboard driver
};
// Verboseness
// 0 = quiet
// 1 = report events out of rnage
// 2 = report everything
int DEBUG = 1;
// JS interface
#include <linux/joystick.h>
// Other headers
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
/**
* Emit a UInput event and write it to it's FD.
*/
void emit(int fd, int type, int code, int val)
{
struct input_event ie;
ie.type = type;
ie.code = code;
ie.value = val;
/* timestamp values below are ignored */
ie.time.tv_sec = 0;
ie.time.tv_usec = 0;
write(fd, &ie, sizeof(ie));
}
/**
* Main loop, communicates the Joystick FD to Uinput's FD and
* does the necessary conversions.
*/
void pass_events(int joy_fd, int uinput_fd)
{
struct js_event event;
// Find maxaccepted axis
int max_axis;
for (max_axis = 0; AXIS[max_axis][0] != 0xFF; max_axis++) {
// No additional logic
}
char axis_down[max_axis][2];
for (int i=0; i < max_axis; i++) {
axis_down[i][0] = 0;
axis_down[i][1] = 0;
}
// Find max accepted button
int max_btn;
for (max_btn = 0; BUTTONS[max_btn] != 0xFF; max_btn++) {
// No additional logic
}
char btn_down[max_btn];
for (int i=0; i < max_btn; i++) {
btn_down[i] = 0;
}
ssize_t read_size = sizeof(event);
// Main loop
while (1) {
ssize_t read_cnt = read(joy_fd, &event, read_size);
if (read_cnt == 0) {
return;
}
else if (read_cnt != read_size) {
return;
}
int unknown = 0;
if (event.type & JS_EVENT_AXIS) {
if ((event.number < max_axis) && (AXIS[event.number][0] != KEY_RESERVED)) {
if (event.value > AXIS_CUTOFF) {
emit(uinput_fd, EV_KEY, AXIS[event.number][0], 1);
emit(uinput_fd, EV_SYN, SYN_REPORT, 0);
axis_down[event.number][0] = 1;
}
else if (axis_down[event.number]) {
emit(uinput_fd, EV_KEY, AXIS[event.number][0], 0);
emit(uinput_fd, EV_SYN, SYN_REPORT, 0);
axis_down[event.number][0] = 0;
}
if ((-event.value) > AXIS_CUTOFF) {
emit(uinput_fd, EV_KEY, AXIS[event.number][1], 1);
emit(uinput_fd, EV_SYN, SYN_REPORT, 0);
axis_down[event.number][1] = 1;
}
else if (axis_down[event.number][1]) {
emit(uinput_fd, EV_KEY, AXIS[event.number][1], 0);
emit(uinput_fd, EV_SYN, SYN_REPORT, 0);
axis_down[event.number][1] = 0;
}
}
else {
unknown = 1;
}
if ((DEBUG == 2) || ((DEBUG == 1) && unknown)) {
fprintf(stderr, "Axis: %i ; Val: %i\n", event.number, event.value);
}
}
else if (event.type & JS_EVENT_BUTTON) {
if ((event.number < max_btn) && (BUTTONS[event.number] != KEY_RESERVED)) {
if (event.value) {
// Button down
emit(uinput_fd, EV_KEY, BUTTONS[event.number], 1);
emit(uinput_fd, EV_SYN, SYN_REPORT, 0);
btn_down[event.number] = 1;
}
else if (btn_down[event.number]) {
// Button up
emit(uinput_fd, EV_KEY, BUTTONS[event.number], 0);
emit(uinput_fd, EV_SYN, SYN_REPORT, 0);
btn_down[event.number] = 0;
}
}
else {
unknown = 1;
}
if ((DEBUG == 2) || ((DEBUG == 1) && unknown)) {
fprintf(stderr, "Button: %i ; Val: %i\n", event.number, event.value);
}
}
else if (DEBUG != 0) {
fprintf(stderr, "Type: %i; Number: %i ; Val: %i\n", event.type, event.number, event.value);
}
}
}
/**
* Main takes care of the setup and teardown.
*/
int main(void)
{
struct uinput_setup usetup;
int joy_fd = open(JOYSTICK_PATH, O_RDONLY);
if (joy_fd == -1) {
perror(JOYSTICK_PATH);
return 1;
}
int uinput_fd = open("/dev/uinput", O_WRONLY);
if (uinput_fd == -1) {
perror("/dev/uinput");
return 1;
}
/*
* The ioctls below will enable the device that is about to be
* created, to pass key events.
*/
ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY);
// Allow to pass the axis buttons
for (int i; AXIS[i][0] != 0xFF; i++) {
if (AXIS[i][0] != KEY_RESERVED) {
ioctl(uinput_fd, UI_SET_KEYBIT, AXIS[i][0]);
ioctl(uinput_fd, UI_SET_KEYBIT, AXIS[i][1]);
}
}
// Allow to pass the buttons buttons
for (int i; BUTTONS[i] != 0xFF; i++) {
if (BUTTONS[i] != KEY_RESERVED) {
ioctl(uinput_fd, UI_SET_KEYBIT, BUTTONS[i]);
}
}
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234; /* sample vendor */
usetup.id.product = 0x5678; /* sample product */
strcpy(usetup.name, "Joy2Uinput");
ioctl(uinput_fd, UI_DEV_SETUP, &usetup);
ioctl(uinput_fd, UI_DEV_CREATE);
/*
* On UI_DEV_CREATE the kernel will create the device node for this
* device. We are inserting a pause here so that userspace has time
* to detect, initialize the new device, and can start listening to
* the event, otherwise it will not notice the event we are about
* to send. This pause is only needed in our example code!
*/
pass_events(joy_fd, uinput_fd);
/*
* Give userspace some time to read the events before we destroy the
* device with UI_DEV_DESTROY.
*/
sleep(1);
ioctl(uinput_fd, UI_DEV_DESTROY);
close(uinput_fd);
return 0;
}