This commit is contained in:
anon 2023-12-25 17:53:21 +01:00
commit 84504c1ba4
4 changed files with 290 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
drawpad
.gdb_history

2
Makefile Normal file
View File

@ -0,0 +1,2 @@
main:
g++ drawpad.cpp -o drawpad -lX11 -lXtst

23
README.md Normal file
View File

@ -0,0 +1,23 @@
# Drawpad
> Hack your **laptop touchpad** into a *drawing pad*
### Behaviour
After launching drawpad on the desired device (find it using `evtest(1)`),
it creates a transparent window.
Said window be comes the "playing field",
all absolute input coordinates retrieved are mapped to screen coordinates where clicks will be simulated.
For example,
if you drag and resize the window over GIMP's canvas area then switch tabs,
you can start drawing away.
### Requirements
#### Compile time
+ Xlib
#### Runtime
+ Linux
+ X11
+ Compositing window manager (optional, but highly recommended)

263
drawpad.cpp Normal file
View File

@ -0,0 +1,263 @@
// @COMPILECMD g++ $@ -o test.out -lX11 -lXtst -ggdb
#include <unistd.h>
#include <fcntl.h>
#include <linux/input.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <thread>
#define PROGRAM_NAME "drawpad"
int pad_dev_fd;
Display * display;
Window root, window;
int screen_width, screen_height;
bool is_running = false;
int r = 0; // program exit value
typedef struct {
char * device_path;
int min_x;
int max_x;
int min_y;
int max_y;
int x_span() {
return max_x - min_x;
}
int y_span() {
return max_y - min_y;
}
int x_normal(int x) {
return x - min_x;
}
int y_normal(int y) {
return y - min_y;
}
} touchpad_t;
touchpad_t touchpad;
// #######################
[[ noreturn ]]
void quit(int ignore = 0) {
if (pad_dev_fd) {
close(pad_dev_fd);
}
exit(r);
}
bool init_dev() {
if (not touchpad.device_path) {
return false;
}
pad_dev_fd = open(touchpad.device_path, O_RDONLY /* | O_NONBLOCK */);
if (pad_dev_fd == -1) {
return false;
}
ioctl(pad_dev_fd, EVIOCGRAB, (void*)1);
int abs[6];
ioctl(pad_dev_fd, EVIOCGABS(ABS_X), abs);
touchpad.min_x = abs[1];
touchpad.max_x = abs[2];
ioctl(pad_dev_fd, EVIOCGABS(ABS_Y), abs);
touchpad.min_y = abs[1];
touchpad.max_y = abs[2];
return true;
}
bool init_x() {
// --- Display ---
display = XOpenDisplay(NULL);
if (!display) {
return false;
}
// --- Transparent window ---
static XVisualInfo vinfo;
XMatchVisualInfo(display, DefaultScreen(display), 32, TrueColor, &vinfo);
static XSetWindowAttributes attr;
attr.colormap = XCreateColormap(display, DefaultRootWindow(display), vinfo.visual, AllocNone);
attr.border_pixel = 0;
attr.background_pixel = 0;
window = XCreateWindow(display,
DefaultRootWindow(display),
0, 0,
300, 200,
0, vinfo.depth,
InputOutput, vinfo.visual,
CWColormap | CWBorderPixel | CWBackPixel, &attr
);
XStoreName(display, window, PROGRAM_NAME " Frame");
XMapWindow(display, window);
static Atom wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", 0);
XSetWMProtocols(display, window, &wm_delete_window, 1);
// --- Screen ---
int screen_num = DefaultScreen(display);
Screen *screen = XScreenOfDisplay(display, screen_num);
screen_width = WidthOfScreen(screen);
screen_height = HeightOfScreen(screen);
return true;
}
bool init_signal() {
signal(SIGINT, quit);
signal(SIGTERM, quit);
signal(SIGQUIT, quit);
return true;
}
// #######################
void handle_dev_event() {
static struct input_event events[64];
static int c;
static int x = 0;
static int y = 0;
static bool is_held = false;
static bool did_update;
did_update = false;
c = read(pad_dev_fd, events, sizeof(events)) / sizeof(struct input_event);
for (int i = 0; i < c; i++) {
const struct input_event &e = events[i];
switch (e.code) {
case ABS_MT_POSITION_X: {
did_update = true;
is_held = true;
x = e.value;
} break;
case ABS_MT_POSITION_Y: {
did_update = true;
is_held = true;
y = e.value;
} break;
case ABS_PRESSURE: {
if (e.value == 0) {
did_update = true;
is_held = false;
}
} break;
}
}
if (did_update) {
XWindowAttributes attr;
XGetWindowAttributes(display, window, &attr);
int x_offset, y_offset;
Window child_return;
XTranslateCoordinates(display,
window, DefaultRootWindow(display),
0, 0,
&x_offset, &y_offset,
&child_return
);
x_offset = x_offset + attr.x;
y_offset = y_offset + attr.y;
int x_tick = (int)(
x_offset
+ (
(double)attr.width
/ (
(double)touchpad.x_span() / (double)touchpad.x_normal(x)
)
)
);
int y_tick = (int)(
y_offset
+ (
(double)attr.height
/ (
(double)touchpad.y_span() / (double)touchpad.y_normal(y)
)
)
);
printf("Touched: %dx%d -> %dx%d\n", x, y, x_tick, y_tick);
XTestFakeMotionEvent(display, -1, x_tick, y_tick, CurrentTime);
XTestFakeButtonEvent(display, Button1, True, CurrentTime);
if (not is_held) {
XTestFakeButtonEvent(display, Button1, False, CurrentTime);
}
XFlush(display);
}
}
void handle_window_event() {
//while (XPending(display)) {
XEvent event;
XNextEvent(display, &event);
switch(event.type) {
case ClientMessage: {
if (event.xclient.message_type == XInternAtom(display, "WM_PROTOCOLS", 1)
&& (Atom)event.xclient.data.l[0] == XInternAtom(display, "WM_DELETE_WINDOW", 1)) {
quit();
}
} break;
}
//}
}
// #######################
const char * const help_message =
PROGRAM_NAME " <device-path>\n"
"\tNOTE: candidate devices are usually found under /dev/input/;\n"
"\t evtest(1) can be used to find the correct one\n"
"\tNOTE: opening an input device and thereby this program require root access\n"
;
signed main(int argc, char * * argv) {
if (argc != 2) {
fputs(help_message, stdout);
return 1;
}
touchpad.device_path = argv[1];
if (not (init_signal() && init_dev() && init_x())) {
r = 1;
quit();
}
std::thread dev_thread([](){ for(;;){ handle_dev_event(); } });
for(;;){ handle_window_event(); }
return 0;
}