commit 84504c1ba4aa575631d77d2e768461b918b0bb8f Author: anon Date: Mon Dec 25 17:53:21 2023 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..328f7b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +drawpad +.gdb_history diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3293a05 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +main: + g++ drawpad.cpp -o drawpad -lX11 -lXtst diff --git a/README.md b/README.md new file mode 100644 index 0000000..e86f1d6 --- /dev/null +++ b/README.md @@ -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) diff --git a/drawpad.cpp b/drawpad.cpp new file mode 100644 index 0000000..50151b3 --- /dev/null +++ b/drawpad.cpp @@ -0,0 +1,263 @@ +// @COMPILECMD g++ $@ -o test.out -lX11 -lXtst -ggdb +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#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 " \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; +}