Skip to content

Instantly share code, notes, and snippets.

@ftk
Last active July 27, 2024 12:16
Show Gist options
  • Save ftk/0d70e527c05cc7c7d25bd57019a829a9 to your computer and use it in GitHub Desktop.
Save ftk/0d70e527c05cc7c7d25bd57019a829a9 to your computer and use it in GitHub Desktop.
libspnav example controlling mouse using spaceball (uinput)
CFLAGS ?= -O3 -flto
simple_uinput: proto.h spnav_config.h spnav.h proto.c spnav.c simple.c
$(CC) $(CFLAGS) -o $@ $^
spnav_config.h:
touch $@
proto.h spnav.h proto.c spnav.c:
curl -L -O --compressed --url "https://github.com/FreeSpacenav/libspnav/raw/master/src/{proto.c,proto.h,spnav.c,spnav.h}"
.PHONY: clean
clean:
-rm proto.h spnav.h proto.c spnav.c spnav_config.h simple_uinput
/* libspnav uinput example
* Control mouse using spaceball (X, Z axis) + 2D mouse wheel (RX, RZ axis)
* based on https://github.com/FreeSpacenav/libspnav/blob/master/examples/simple/simple.c
*/
#include "spnav.h"
#include <linux/input-event-codes.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/uinput.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
static void print_dev_info(void);
static int fd; // uinput fd
static void sig(int s)
{
spnav_close();
ioctl(fd, UI_DEV_DESTROY);
close(fd);
exit(0);
}
static bool 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;
return write(fd, &ie, sizeof(ie)) == sizeof(ie);
}
static bool emit_motion(int fd, int x, int y, int wheel, int hwheel)
{
static struct input_event ie[5] = {
{ .type = EV_REL,
.code = REL_X },
{ .type = EV_REL,
.code = REL_Y },
{ .type = EV_REL,
.code = REL_WHEEL_HI_RES },
{ .type = EV_REL,
.code = REL_HWHEEL_HI_RES },
{ .type = EV_SYN,
.code = SYN_REPORT,
.value = 0 }
};
ie[0].value = x;
ie[1].value = y;
ie[2].value = wheel;
ie[3].value = hwheel;
return write(fd, &ie, sizeof(ie)) == sizeof(ie);
}
int main(void)
{
struct uinput_setup usetup;
const int buttons = 12;
const int button_keys[] = {
BTN_SIDE, // back // 1
BTN_FORWARD, // 2
KEY_F15, // 3
KEY_F16, // 4
KEY_F17, // 5
KEY_F18, // 6
BTN_LEFT, // 7
BTN_MIDDLE, // 8
BTN_RIGHT, // 9
KEY_F22, // A
KEY_F23, // B
KEY_LEFTCTRL // C
};
fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0) {
perror("Can't open /dev/uinput");
return 1;
}
/* enable button events */
ioctl(fd, UI_SET_EVBIT, EV_KEY);
for (int i = 0; i < 12; i++)
ioctl(fd, UI_SET_KEYBIT, button_keys[i]);
/* enable motion events */
ioctl(fd, UI_SET_EVBIT, EV_REL);
ioctl(fd, UI_SET_RELBIT, REL_X);
ioctl(fd, UI_SET_RELBIT, REL_Y);
ioctl(fd, UI_SET_RELBIT, REL_HWHEEL_HI_RES);
ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES);
spnav_event sev;
signal(SIGINT, sig);
if (spnav_open() == -1) {
fprintf(stderr, "failed to connect to spacenavd\n");
return 1;
}
print_dev_info();
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234; /* sample vendor */
usetup.id.product = 0x5678; /* sample product */
spnav_dev_name(usetup.name, sizeof usetup.name);
spnav_sensitivity(0.25);
ioctl(fd, UI_DEV_SETUP, &usetup);
ioctl(fd, UI_DEV_CREATE);
/* If the spaceball is idle wait for next event. Otherwise poll and emit motioen event every 10ms.
*/
bool idle = true;
while (true) {
int result;
if (idle)
result = spnav_wait_event(&sev);
else // no wait
result = spnav_poll_event(&sev);
if (!result) {
if (idle)
break;
// repeat last motion every 10ms (low latency)
sev.type = SPNAV_EVENT_MOTION;
for (int i = 0; i < 10 && !result; i++) {
usleep(1000); // 1ms
result = spnav_poll_event(&sev);
}
}
if (sev.type == SPNAV_EVENT_MOTION) {
if (sev.motion.x || sev.motion.z || sev.motion.rx || sev.motion.rz) {
idle = false;
if (!emit_motion(fd, sev.motion.x, -sev.motion.z, -sev.motion.rx, sev.motion.rz)) {
perror("uinput");
break;
}
} else {
idle = true;
}
} else if (sev.type == SPNAV_EVENT_BUTTON) {
printf("got button %s event b(%d)\n",
sev.button.press ? "press" : "release", sev.button.bnum);
if (sev.button.bnum < buttons) {
if (button_keys[sev.button.bnum])
emit(fd, EV_KEY, button_keys[sev.button.bnum], sev.button.press);
if (!emit(fd, EV_SYN, SYN_REPORT, 0)) {
perror("uinput");
break;
}
}
}
}
spnav_close();
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
static void print_dev_info(void)
{
int proto;
char buf[256];
if ((proto = spnav_protocol()) == -1) {
fprintf(stderr, "failed to query protocol version\n");
return;
}
printf("spacenav AF_UNIX protocol version: %d\n", proto);
spnav_client_name("simple example");
if (proto >= 1) {
spnav_dev_name(buf, sizeof buf);
printf("Device: %s\n", buf);
spnav_dev_path(buf, sizeof buf);
printf("Path: %s\n", buf);
printf("Buttons: %d\n", spnav_dev_buttons());
printf("Axes: %d\n", spnav_dev_axes());
}
putchar('\n');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment