Joy2Uinput

%3 cluster_4b1b22f1_08e3_4a4b_96b3_341e2ad75be8 Joy2Uinput _c041fcc9_72e6_45d4_9023_e9449bbd3f68 Wayland _d1cf01c6_3e18_4341_b55c_487f322dc262 Joy2Key _d1cf01c6_3e18_4341_b55c_487f322dc262->_c041fcc9_72e6_45d4_9023_e9449bbd3f68 __0:cluster_4b1b22f1_08e3_4a4b_96b3_341e2ad75be8->_c041fcc9_72e6_45d4_9023_e9449bbd3f68 __1:cluster_4b1b22f1_08e3_4a4b_96b3_341e2ad75be8->_d1cf01c6_3e18_4341_b55c_487f322dc262

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;
}