Skip to content

Instantly share code, notes, and snippets.

@sineemore
Created October 12, 2018 19:17
Show Gist options
  • Save sineemore/583c95430288338a19d3d0dea1885266 to your computer and use it in GitHub Desktop.
Save sineemore/583c95430288338a19d3d0dea1885266 to your computer and use it in GitHub Desktop.
Simple X calibrator based on `xinput`
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <linux/input-event-codes.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <poll.h>
struct input_event {
struct timeval time;
unsigned short type;
unsigned short code;
unsigned int value;
};
/* Globals */
char *event_filename = NULL; /* path to touchscreen /dev/input/event* file */
unsigned int input_timeout = 6000; /* input timeout in ms */
unsigned int sleep_between = 1000; /* idle time before each calibration */
char *argv0 = NULL;
Display *dpy;
int scr;
Visual *vis;
Window win;
GC gc;
XColor red, green, black;
/* Functions */
static void die(const char *msg) {
fputs(msg, stderr);
if (msg[0] && msg[strlen(msg)-1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
}
exit(EXIT_FAILURE);
}
static void usage() {
fprintf(stderr, "usage: %s: -f <path to device event file>\n", argv0);
exit(EXIT_FAILURE);
}
static long unsigned int now() {
struct timespec t = { 0 };
if (-1 == clock_gettime(CLOCK_MONOTONIC, &t)) {
die("clock_gettime:");
}
return t.tv_sec * 1000 + (t.tv_nsec / 1000000);
}
static int calibrate_point(int rx, int ry, int *x, int *y) {
struct timespec t = { .tv_sec = sleep_between / 1000, .tv_nsec = sleep_between / 1000000 };
nanosleep(&t, NULL);
/* Draw target rectangles */
XSetForeground(dpy, gc, green.pixel);
XFillRectangle(dpy, win, gc, rx - 50, ry - 50, 100, 100);
XSetForeground(dpy, gc, red.pixel);
XFillRectangle(dpy, win, gc, rx - 10, ry - 10, 20, 20);
XSync(dpy, False);
int fd = open(event_filename, O_RDONLY);
if (fd == -1) {
die("open:");
}
struct pollfd fds[2];
fds[0].fd = fd;
fds[0].events = POLLIN | POLLHUP;
*x = 0;
*y = 0;
int xcount = 0;
int ycount = 0;
struct input_event event = { 0 };
long unsigned int start = now();
while (xcount == 0 || ycount == 0) {
int timeout = (start + input_timeout) - now();
if (timeout <= 0) {
break;
}
int rc = poll(fds, 1, timeout);
if (rc <= 0) {
die(rc == 0 ? "No input provided" : "poll:");
}
if ((fds[0].revents & POLLHUP) == POLLHUP) {
die("pollhup");
}
if (read(fd, (void *) &event, sizeof(event)) != sizeof(event)) {
die("expected to read more, sorry");
}
if (event.type == 3 && (event.code == 0 || event.code == 1)) {
if (event.code == 0) {
*x = event.value;
xcount = 1;
} else {
*y = event.value;
ycount = 1;
}
}
}
close(fd);
/* Clear drawn rectangles */
XSetForeground(dpy, gc, black.pixel);
XFillRectangle(dpy, win, gc, rx - 50, ry - 50, 100, 100);
XSync(dpy, False);
return 0;
}
int main(int argc, char *argv[]) {
argv0 = *(argv++); argc--;
while (argc--) {
char *arg = *(argv++);
if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0') {
usage();
}
switch (arg[1]) {
case 'f':
if (!argc) {
usage();
}
event_filename = *(argv++); argc--;
break;
default:
usage();
}
}
if (event_filename == NULL) {
usage();
}
dpy = XOpenDisplay(NULL);
if (!dpy) {
die("Can't open dpy");
}
scr = XDefaultScreen(dpy);
vis = XDefaultVisual(dpy, scr);
/* Allocate colors */
Colormap sc = DefaultColormap(dpy, scr);
XAllocNamedColor(dpy, sc, "red", &red, &red);
XAllocNamedColor(dpy, sc, "black", &black, &black);
XAllocNamedColor(dpy, sc, "green", &green, &green);
XSetWindowAttributes attrs = { 0 };
attrs.bit_gravity = NorthWestGravity;
int w = XWidthOfScreen(XDefaultScreenOfDisplay(dpy));
int h = XHeightOfScreen(XDefaultScreenOfDisplay(dpy));
win = XCreateWindow(
dpy, XRootWindow(dpy, scr),
0, 0, w, h, 0,
XDefaultDepth(dpy, scr), InputOutput,
vis, CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask | CWColormap,
&attrs
);
/* Fullscreen (if running with WM) */
Atom atoms[2] = { XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False), None };
XChangeProperty(dpy, win, XInternAtom(dpy, "_NET_WM_STATE", False), XA_ATOM, 32, PropModeReplace, (unsigned char *) atoms, 1);
/* Hide cursor */
char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
Pixmap pmap = XCreateBitmapFromData(dpy, win, curs, 8, 8);
Cursor invisible = XCreatePixmapCursor(dpy, pmap, pmap, &black, &black, 0, 0);
XDefineCursor(dpy, win, invisible);
XMapWindow(dpy, win);
XSync(dpy, False);
XGCValues gcvalues = { 0 };
gcvalues.graphics_exposures = False;
gc = XCreateGC(dpy, XRootWindow(dpy, scr), GCGraphicsExposures, &gcvalues);
int x1, x2, x3, x4;
int y1, y2, y3, y4;
calibrate_point(w * 0.1, h * 0.1, &x1, &y1);
calibrate_point(w * 0.9, h * 0.1, &x2, &y2);
calibrate_point(w * 0.1, h * 0.9, &x3, &y3);
calibrate_point(w * 0.9, h * 0.9, &x4, &y4);
int swapAxes = 0;
if (abs(x2 - x1) < abs(y1 - y2)) {
/* Swap X and Y */
swapAxes = 1;
int t;
t = x1; x1 = y1; y1 = t;
t = x2; x2 = y2; y2 = t;
t = x3; x3 = y3; y3 = t;
t = x4; x4 = y4; y4 = t;
}
int minx = x1 - ((x2 - x1) / 80) * 10;
int miny = y1 - ((y3 - y1) / 80) * 10;
int maxx = x2 + ((x2 - x1) / 80) * 10;
int maxy = y3 + ((y3 - y1) / 80) * 10;
puts("#!/bin/sh");
printf("xinput set-prop \"$DEVICE\" 'Evdev Axis Inversion' %d %d\n", 0, 0);
printf("xinput set-prop \"$DEVICE\" 'Evdev Axes Swap' %d\n", swapAxes);
printf("xinput set-prop \"$DEVICE\" 'Evdev Axis Calibration' %d %d %d %d\n", minx, maxx, miny, maxy);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment