Created
October 12, 2018 19:17
-
-
Save sineemore/583c95430288338a19d3d0dea1885266 to your computer and use it in GitHub Desktop.
Simple X calibrator based on `xinput`
This file contains 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
#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