URI:
       add experiment of macro recorder and replayer - randomcrap - random crap programs of varying quality
  HTML git clone git://git.codemadness.org/randomcrap
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 941ecd1ec38a5d0b4e7b4f3ed3e9ea6234b6ed8b
   DIR parent c25c282d817b2fde16f5dedb4c6112f9c5771164
  HTML Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Thu,  9 Mar 2023 19:53:13 +0100
       
       add experiment of macro recorder and replayer
       
       Diffstat:
         A rec.c                               |     708 +++++++++++++++++++++++++++++++
       
       1 file changed, 708 insertions(+), 0 deletions(-)
       ---
   DIR diff --git a/rec.c b/rec.c
       @@ -0,0 +1,708 @@
       +/* serialize and replay keyboard and mouse events
       +   Dependencies: Xlib, Xtst, XInput2, etc
       +   Some code adapted from the examples in xorg-xinput/src/ test.c and test_xi2.c.
       +   Compile: cc -o main main.c -lX11 -lXtst -lXi -lrt -D_GNU_SOURCE -Wall */
       +
       +#include <sys/time.h>
       +
       +#include <X11/extensions/XInput.h>
       +#include <X11/extensions/XInput2.h>
       +#include <X11/extensions/XTest.h>
       +
       +#include <X11/Xlib.h>
       +
       +#include <err.h>
       +#include <errno.h>
       +#include <poll.h>
       +#include <signal.h>
       +#include <stdio.h>
       +#include <string.h>
       +#include <stdlib.h>
       +#include <time.h>
       +#include <unistd.h>
       +
       +enum EventType { EventNone = 0, EventMotion, EventKeyPress, EventKeyRelease,
       +                 EventButtonPress, EventButtonRelease, EventSleep };
       +
       +struct event {
       +        enum EventType evtype;
       +        int button; /* button or key */
       +        int x, y; /* mouse x, y */
       +        float sleeptime;
       +};
       +
       +volatile sig_atomic_t state_sigcont = 0, state_sighup = 0, state_sigabrt = 0;
       +volatile sig_atomic_t state_sigusr1 = 0, state_sigusr2 = 0;
       +
       +Window win, root;
       +Display *dpy;
       +int screen;
       +int xi_opcode;
       +
       +int isrecording = 0;
       +int isreplaying = 0;
       +int isrunning = 0;
       +int xfd;
       +
       +struct event *events = NULL;
       +size_t nevents = 0;
       +
       +const char *replay_file = NULL;
       +
       +/* configuration */
       +int replay_usebuttons = 1; /* use button events? */
       +int replay_usemotion = 1;  /* use motion events? */
       +int replay_usekeys = 1;    /* use keyboard events */
       +int replay_usesleep = 1;   /* use timings/sleep for replaying? */
       +
       +int record_usebuttons = 1; /* use button events? */
       +int record_usemotion = 1;  /* use motion events? */
       +int record_usekeys = 1;    /* use keyboard events */
       +int record_usesleep = 1;   /* use timings/sleep for recording? */
       +
       +int replay_key = 134;      /* Super_R */
       +int replay_repeat = 0;     /* repeat replay? */
       +int record_key = 127;      /* pause/rec key on my keyboard */
       +int timefirstevent = 1;    /* start timer at first event or since program startup */
       +int exit_key = 96;         /* F12 */
       +
       +/* button states */
       +char buttons[32], keys[256];
       +
       +void replay_events(struct event *events, size_t nevents);
       +
       +int
       +process_event(struct event *ev)
       +{
       +        unsigned long delay = CurrentTime; /* no delay */
       +        unsigned long keydelay = 1; /* delay atleast some time (ms) to process the key */
       +
       +        switch (ev->evtype) {
       +        case EventMotion:
       +                if (!replay_usemotion)
       +                        return 0;
       +                XTestFakeMotionEvent(dpy, -1, ev->x, ev->y, delay);
       +                XSync(dpy, False);
       +                return 1;
       +        case EventKeyPress:
       +                if (!replay_usekeys)
       +                        return 0;
       +                XTestFakeKeyEvent(dpy, ev->button, 1, keydelay);
       +                XSync(dpy, False);
       +                return 1;
       +        case EventKeyRelease:
       +                if (!replay_usekeys)
       +                        return 0;
       +                XTestFakeKeyEvent(dpy, ev->button, 0, keydelay);
       +                XSync(dpy, False);
       +                return 1;
       +        case EventButtonPress:
       +                if (!replay_usebuttons)
       +                        return 0;
       +                XTestFakeButtonEvent(dpy, ev->button, 1, delay);
       +                XSync(dpy, False);
       +                return 1;
       +        case EventButtonRelease:
       +                if (!replay_usebuttons)
       +                        return 0;
       +                XTestFakeButtonEvent(dpy, ev->button, 0, delay);
       +                XSync(dpy, False);
       +                return 1;
       +        case EventSleep:
       +                if (!replay_usesleep)
       +                        return 0;
       +                usleep(ev->sleeptime * 1000000);
       +                return 2; /* not an X11 event */
       +        default:
       +                return 0;
       +        }
       +}
       +
       +int
       +line2event(const char *line, struct event *ev)
       +{
       +        char *p, *end;
       +
       +        if (strncmp(line, "motion ", strlen("motion ")) == 0) {
       +                if (isrecording && !record_usemotion)
       +                        return 0;
       +
       +                ev->evtype = EventMotion;
       +
       +                /* parse arguments */
       +                p = strchr(line, ' ');
       +                ev->x = strtol(p, &end, 10);
       +
       +                p = end;
       +                ev->y = strtol(p, &end, 10);
       +                return 1;
       +        } else if (strncmp(line, "keypress ", strlen("keypress ")) == 0 ||
       +                   strncmp(line, "keyrelease ", strlen("keyrelease ")) == 0) {
       +
       +                if (isrecording && !record_usekeys)
       +                        return 0;
       +
       +                ev->evtype = line[3] == 'p' ? EventKeyPress : EventKeyRelease;
       +
       +                /* parse arguments */
       +                p = strchr(line, ' ');
       +                ev->button = strtol(p, &end, 10);
       +
       +                return 1;
       +        } else if (strncmp(line, "press ", strlen("press ")) == 0 ||
       +                   strncmp(line, "release ", strlen("release ")) == 0) {
       +                if (isrecording && !record_usebuttons)
       +                        return 0;
       +
       +                ev->evtype = line[0] == 'p' ? EventButtonPress : EventButtonRelease;
       +
       +                /* parse arguments */
       +                p = strchr(line, ' ');
       +                ev->button = strtol(p, &end, 10);
       +
       +                return 1;
       +        } else if (strncmp(line, "sleep ", strlen("sleep ")) == 0) {
       +                if (isrecording && !record_usesleep)
       +                        return 0;
       +
       +                ev->evtype = EventSleep;
       +
       +                /* parse arguments */
       +                p = strchr(line, ' '); /* there is always a space */
       +                ev->sleeptime = strtof(p, &end);
       +                return 2;
       +        }
       +        return -1;
       +}
       +
       +int
       +xevent2event(XGenericEventCookie *cookie, struct event *ev)
       +{
       +        XIDeviceEvent *dev_event;
       +
       +        dev_event = (XIDeviceEvent *)(cookie->data);
       +
       +        /* NOTE: the "detail" attribute is "the button number, key code, touch ID, or 0." */
       +        switch (cookie->evtype) {
       +        /* device event */
       +        case XI_ButtonPress:
       +                ev->evtype = EventButtonPress;
       +                ev->button = dev_event->detail;
       +                return 1;
       +        case XI_ButtonRelease:
       +                ev->evtype = EventButtonRelease;
       +                ev->button = dev_event->detail;
       +                return 1;
       +        case XI_KeyPress:
       +                ev->evtype = EventKeyPress;
       +                ev->button = dev_event->detail;
       +                return 1;
       +        case XI_KeyRelease:
       +                ev->evtype = EventKeyRelease;
       +                ev->button = dev_event->detail;
       +                return 1;
       +        case XI_Motion:
       +                ev->evtype = EventMotion;
       +                ev->x = (int)dev_event->root_x;
       +                ev->y = (int)dev_event->root_y;
       +                return 1;
       +        }
       +        return 0;
       +}
       +
       +int
       +filterevent(struct event *ev)
       +{
       +        switch (ev->evtype) {
       +        case EventButtonPress:
       +                if (ev->button < 0 || ev->button >= sizeof(buttons) / sizeof(buttons[0]))
       +                        break;
       +                buttons[ev->button] = 1;
       +                break;
       +        case EventButtonRelease:
       +                if (ev->button < 0 || ev->button >= sizeof(buttons) / sizeof(buttons[0]))
       +                        break;
       +                /* ignore if button was not registered as pressed beforehand */
       +                if (buttons[ev->button] == 0)
       +                        return 1;
       +                buttons[ev->button] = 0;
       +                break;
       +        case EventKeyPress:
       +                if (ev->button < 0 || ev->button >= sizeof(keys) / sizeof(keys[0]))
       +                        break;
       +                keys[ev->button] = 1;
       +                break;
       +        case EventKeyRelease:
       +                if (ev->button < 0 || ev->button >= sizeof(keys) / sizeof(keys[0]))
       +                        break;
       +                /* ignore if button was not registered as pressed beforehand */
       +                if (keys[ev->button] == 0)
       +                        return 1;
       +                keys[ev->button] = 0;
       +                break;
       +        default:
       +                break;
       +        }
       +        return 0;
       +}
       +
       +int
       +event2line(struct event *ev)
       +{
       +        switch (ev->evtype) {
       +        case EventButtonPress:
       +                if (isreplaying && !replay_usebuttons)
       +                        break;
       +                printf("press %d\n", ev->button);
       +                return 1;
       +        case EventButtonRelease:
       +                if (isreplaying && !replay_usebuttons)
       +                        break;
       +                printf("release %d\n", ev->button);
       +                return 1;
       +        case EventKeyPress:
       +                if (isreplaying && !replay_usekeys)
       +                        break;
       +                printf("keypress %d\n", ev->button);
       +                return 1;
       +        case EventKeyRelease:
       +                if (isreplaying && !replay_usekeys)
       +                        break;
       +                printf("keyrelease %d\n", ev->button);
       +                return 1;
       +        case EventMotion:
       +                if (isreplaying && !replay_usemotion)
       +                        break;
       +                printf("motion %d %d\n", ev->x, ev->y);
       +                return 1;
       +        case EventSleep:
       +                if (isreplaying && !replay_usesleep)
       +                        break;
       +                printf("sleep %f\n", ev->sleeptime);
       +                return 1;
       +        default:
       +                break;
       +        }
       +        return 0;
       +}
       +
       +void
       +read_events_from_file(const char *path)
       +{
       +        struct event ev;
       +        FILE *fp;
       +        char *line = NULL;
       +        size_t linesiz = 0;
       +        ssize_t n;
       +        int r;
       +
       +        if ((fp = fopen(path, "rb")) == NULL)
       +                err(1, "fopen: %s", path);
       +
       +        nevents = 0;
       +        while ((n = getline(&line, &linesiz, fp)) > 0) {
       +                if (n && line[n - 1] == '\n')
       +                        line[--n] = '\0';
       +
       +                if ((r = line2event(line, &ev)) > 0) {
       +                        /* add event */
       +                        if (!(events = realloc(events, (nevents + 1) * sizeof(ev))))
       +                                err(1, "realloc");
       +                        memcpy(&events[nevents], &ev, sizeof(ev));
       +                        nevents++;
       +                }
       +        }
       +        if (ferror(fp))
       +                err(1, "getline");
       +        free(line);
       +}
       +
       +/* fake events of buttons that are still pressed. This prevents some annoying issue when the
       +   recording is stopped and later replayed the button or key states or held */
       +void
       +fake_release_button_states(void)
       +{
       +        struct event ev;
       +        int i;
       +
       +        ev.x = 0;
       +        ev.y = 0;
       +        ev.sleeptime = 0;
       +
       +        ev.evtype = EventButtonRelease;
       +        for (i = 0; i < sizeof(buttons) / sizeof(buttons[0]); i++) {
       +                if (!buttons[i])
       +                        continue;
       +                ev.button = i;
       +                event2line(&ev);
       +        }
       +
       +        ev.evtype = EventKeyRelease;
       +        for (i = 0; i < sizeof(keys) / sizeof(keys[0]); i++) {
       +                if (!keys[i])
       +                        continue;
       +                ev.button = i;
       +                event2line(&ev);
       +        }
       +}
       +
       +void
       +record_stop(void)
       +{
       +        if (!isrecording)
       +                return;
       +
       +        isrecording = 0;
       +
       +        fake_release_button_states();
       +        fputs("recording stopped\n", stderr);
       +}
       +
       +void
       +record_reset(void)
       +{
       +        /* start new buffer for recording */
       +        free(events);
       +        events = NULL;
       +        nevents = 0;
       +}
       +
       +void
       +record_start(void)
       +{
       +        isrecording = 1;
       +
       +        record_reset();
       +
       +        /* reset currently pressed button states */
       +        memset(buttons, 0, sizeof(buttons));
       +        memset(keys, 0, sizeof(keys));
       +
       +        fputs("recording started\n", stderr);
       +}
       +
       +void
       +replay_stop(void)
       +{
       +        if (!isreplaying)
       +                return;
       +
       +        isreplaying = 0;
       +        fputs("replay stopped\n", stderr);
       +}
       +
       +void
       +replay_start(void)
       +{
       +        isreplaying = 1;
       +        fputs("replay started\n", stderr);
       +
       +        replay_events(events, nevents);
       +        if (isreplaying)
       +                replay_stop();
       +}
       +
       +int
       +eventpoll(void)
       +{
       +        struct pollfd pfds[1];
       +        int r, timeout = 16; /* timeout in ms */
       +
       +        pfds[0].fd = xfd;
       +        pfds[0].events = POLLIN | POLLHUP | POLLNVAL;
       +        pfds[0].revents = 0;
       +
       +        if ((r = poll(pfds, 1, timeout)) == -1) {
       +                /* interruptions by signals are OK */
       +                if (errno != EINTR)
       +                        err(1, "poll");
       +        }
       +        return r;
       +}
       +
       +/* check for a X11 event or handle a received signal */
       +int
       +eventloop(int dopoll)
       +{
       +        if (state_sigusr1) {
       +                state_sigusr1 = 0;
       +                record_start();
       +        }
       +        if (state_sigusr2) {
       +                state_sigusr2 = 0;
       +                record_stop();
       +        }
       +        if (state_sigcont) {
       +                state_sigcont = 0;
       +                replay_start();
       +        }
       +        if (state_sigabrt) {
       +                state_sigabrt = 0;
       +                replay_stop();
       +        }
       +        if (state_sighup) {
       +                state_sighup = 0;
       +                record_reset();
       +                read_events_from_file(replay_file);
       +        }
       +        if (XPending(dpy))
       +                return 1;
       +        if (dopoll)
       +                eventpoll();
       +
       +        return 0;
       +}
       +
       +void
       +handle_event(XEvent *xev)
       +{
       +        struct event ev;
       +        XGenericEventCookie *cookie;
       +        XIDeviceEvent *dev_event;
       +
       +        cookie = (XGenericEventCookie *)&(xev->xcookie);
       +        if (cookie->type == GenericEvent &&
       +            cookie->extension == xi_opcode &&
       +            XGetEventData(dpy, cookie)) {
       +                dev_event = (XIDeviceEvent *)(cookie->data);
       +
       +                switch (cookie->evtype) {
       +                case XI_KeyRelease:
       +                        /* exit immediately */
       +                        if (dev_event->detail == exit_key) {
       +                                isrecording = 0;
       +                                isreplaying = 0;
       +                                isrunning = 0;
       +                                goto endevent;
       +                        } else if (dev_event->detail == record_key) {
       +                                if (isreplaying)
       +                                        replay_stop();
       +                                if (!isrecording)
       +                                        record_start();
       +                                else
       +                                        record_stop();
       +                                goto endevent;
       +                        } else if (dev_event->detail == replay_key) {
       +                                if (isrecording)
       +                                        record_stop();
       +                                if (!isreplaying)
       +                                        replay_start();
       +                                else
       +                                        replay_stop();
       +                                goto endevent;
       +                        }
       +                        break;
       +                case XI_KeyPress:
       +                        if (dev_event->detail == exit_key ||
       +                            dev_event->detail == record_key ||
       +                            dev_event->detail == replay_key)
       +                                goto endevent;
       +                        break;
       +                }
       +
       +                if (isrecording && xevent2event(cookie, &ev) > 0 && !filterevent(&ev)) {
       +                        if (event2line(&ev)) {
       +                                fflush(stdout); /* force flushing the stream line buffer */
       +
       +                                /* add event */
       +                                if (!(events = realloc(events, (nevents + 1) * sizeof(ev))))
       +                                        err(1, "realloc");
       +                                memcpy(&events[nevents], &ev, sizeof(ev));
       +                                nevents++;
       +                        }
       +                }
       +endevent:
       +                XFreeEventData(dpy, cookie);
       +        }
       +}
       +
       +void
       +xinput_setup(void)
       +{
       +        int xi_major = 2, xi_minor = 0;
       +        XIEventMask masks[2];
       +        XIEventMask *m;
       +        XDeviceInfo *devs;
       +        Status status;
       +        /* bits storage for event masks */
       +        unsigned char nmask[XIMaskLen(XI_LASTEVENT)];
       +        int i, event, error, ndevs;
       +
       +        if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error))
       +                errx(1, "X Input extension not available");
       +
       +        status = XIQueryVersion(dpy, &xi_major, &xi_minor);
       +        switch (status) {
       +        case Success:
       +                break;
       +        case BadRequest:
       +                errx(1, "no XI2 support. (%d.%d only)", xi_major, xi_minor);
       +        default:
       +                errx(1, "XI2: unknown error");
       +        }
       +
       +        memset(nmask, 0, sizeof(nmask));
       +
       +        /* Select for motion events */
       +        if (record_usebuttons) {
       +                XISetMask(nmask, XI_ButtonPress);
       +                XISetMask(nmask, XI_ButtonRelease);
       +        }
       +        /* always need key events for activation */
       +        XISetMask(nmask, XI_KeyPress);
       +        XISetMask(nmask, XI_KeyRelease);
       +        if (record_usemotion)
       +                XISetMask(nmask, XI_Motion);
       +        m = &masks[0];
       +        /* m->deviceid = XIAllDevices; */ /* normal events requires XIAllDevices (not XIAllMasterDevices) */
       +        m->mask_len = sizeof(nmask);
       +        m->mask = nmask;
       +
       +        /* select events individually per device, this allows to ignore certain devices */
       +        if ((devs = XListInputDevices(dpy, &ndevs))) {
       +                for (i = 0; i < ndevs; i++) {
       +                        m->deviceid = devs[i].id;
       +                        if (strstr(devs[i].name, " XTEST "))
       +                                continue; /* ignore XTEST devices */
       +                        XISelectEvents(dpy, root, masks, 1);
       +                        XSync(dpy, False);
       +                }
       +                XFreeDeviceList(devs);
       +        }
       +}
       +
       +void
       +replay_events(struct event *events, size_t nevents)
       +{
       +        XEvent xev;
       +        size_t i;
       +
       +        do {
       +                for (i = 0; i < nevents && isreplaying; i++) {
       +                        process_event(&events[i]);
       +
       +                        while (eventloop(0)) {
       +                                XNextEvent(dpy, &xev);
       +                                handle_event(&xev);
       +                        }
       +                }
       +        } while (replay_repeat);
       +}
       +
       +void
       +run(void)
       +{
       +        struct event ev;
       +        XEvent xev;
       +        struct timespec tc, tp;
       +        struct timeval tv1, tv2, tvr;
       +        int isfirst;
       +        float sleeptime;
       +
       +        /* initial clock / timing */
       +        if (!timefirstevent) {
       +                clock_gettime(CLOCK_MONOTONIC, &tc);
       +                memcpy(&tp, &tc, sizeof(tp));
       +        }
       +
       +        isfirst = 1;
       +        isrunning = 1; /* why are you running? */
       +        while (isrunning) {
       +                if (!eventloop(1))
       +                        continue;
       +                XNextEvent(dpy, &xev);
       +
       +                /* timing relative to previous event */
       +                memcpy(&tp, &tc, sizeof(tp));
       +                clock_gettime(CLOCK_MONOTONIC, &tc);
       +
       +                if (timefirstevent && isfirst) {
       +                        memcpy(&tp, &tc, sizeof(tp));
       +                        isfirst = 0;
       +                }
       +
       +                TIMESPEC_TO_TIMEVAL(&tv1, &tp);
       +                TIMESPEC_TO_TIMEVAL(&tv2, &tc);
       +                timersub(&tv2, &tv1, &tvr);
       +                if (record_usesleep) {
       +                        sleeptime = tvr.tv_sec + (tvr.tv_usec / 1000000.0);
       +                        if (sleeptime > 0.0) {
       +                                ev.evtype = EventSleep;
       +                                ev.sleeptime = sleeptime;
       +
       +                                if (isrecording && event2line(&ev)) {
       +                                        fflush(stdout); /* force flushing the stream line buffer */
       +
       +                                        /* add event */
       +                                        if (!(events = realloc(events, (nevents + 1) * sizeof(ev))))
       +                                                err(1, "realloc");
       +                                        memcpy(&events[nevents], &ev, sizeof(ev));
       +                                        nevents++;
       +                                }
       +                        }
       +                }
       +
       +                handle_event(&xev);
       +        }
       +}
       +
       +/* prevent errors from ever stopping the program */
       +int
       +xerror(Display *dpy, XErrorEvent *ee)
       +{
       +        return 0;
       +}
       +
       +void
       +sighandler(int signo)
       +{
       +        switch (signo) {
       +        case SIGABRT: state_sigabrt = 1; break;
       +        case SIGCONT: state_sigcont = 1; break;
       +        case SIGHUP:  state_sighup = 1;  break;
       +        case SIGUSR1: state_sigusr1 = 1; break;
       +        case SIGUSR2: state_sigusr2 = 1; break;
       +        }
       +}
       +
       +int
       +main(int argc, char **argv)
       +{
       +        struct sigaction sa = { 0 };
       +
       +        sa.sa_flags = SA_RESTART;
       +        sa.sa_handler = sighandler;
       +
       +        sigaction(SIGABRT, &sa, NULL);
       +        sigaction(SIGCONT, &sa, NULL);
       +        sigaction(SIGHUP, &sa, NULL);
       +        sigaction(SIGUSR1, &sa, NULL);
       +        sigaction(SIGUSR2, &sa, NULL);
       +
       +        if ((dpy = XOpenDisplay(NULL)) == NULL)
       +                errx(1, "XOpenDisplay");
       +
       +        screen = DefaultScreen(dpy);
       +        root = RootWindow(dpy, screen);
       +        win = root;
       +        if ((xfd = ConnectionNumber(dpy)) == -1)
       +                errx(1, "No file descriptor returned from ConnectionNumber()");
       +
       +        XSetErrorHandler(xerror);
       +
       +        xinput_setup();
       +
       +        if (argc > 1) {
       +                replay_file = argv[1];
       +                read_events_from_file(replay_file);
       +                replay_start();
       +                replay_stop();
       +                run(); /* keep running */
       +        } else {
       +                run(); /* keep running */
       +        }
       +
       +        XCloseDisplay(dpy);
       +
       +        return 0;
       +}