Last active
July 27, 2024 12:16
-
-
Save ftk/0d70e527c05cc7c7d25bd57019a829a9 to your computer and use it in GitHub Desktop.
libspnav example controlling mouse using spaceball (uinput)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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