// SPDX-License-Identifier: BSD-3-Clause // xtest_move.c - drive the game with XSendEvent-synthetic keys (SDL2 on this // Xorg ignores XTEST-faked keys; it accepts send_event KeyPress/KeyRelease). // Finds the game window by name, then holds forward+fire and alternates // turning left/right so the captured footage has constant motion. // Usage: xtest_move #include #include #include #include #include #include #include static Display *d; static Window game; static Window find_named(const char *sub) { Window root = DefaultRootWindow(d), r, parent, *kids = NULL; unsigned int nk = 0; if (!XQueryTree(d, root, &r, &parent, &kids, &nk)) return 0; for (unsigned int i = 0; i < nk; i++) { char *name = NULL; if (XFetchName(d, kids[i], &name) && name) { if (strstr(name, sub)) { Window w = kids[i]; XFree(name); if (kids) XFree(kids); return w; } XFree(name); } } if (kids) XFree(kids); return 0; } static void send_key(KeySym ks, int down) { XEvent e; Window root = DefaultRootWindow(d); int rx, ry, wx, wy; unsigned int mask; Window cr, pr; if (!game) return; memset(&e, 0, sizeof(e)); e.xkey.type = down ? KeyPress : KeyRelease; e.xkey.display = d; e.xkey.window = game; e.xkey.root = root; e.xkey.subwindow = None; e.xkey.time = CurrentTime; XQueryPointer(d, root, &cr, &pr, &rx, &ry, &wx, &wy, &mask); e.xkey.x = wx; e.xkey.y = wy; e.xkey.x_root = rx; e.xkey.y_root = ry; e.xkey.same_screen = True; e.xkey.keycode = XKeysymToKeycode(d, ks); e.xkey.state = 0; XSendEvent(d, game, True, down ? KeyPressMask : KeyReleaseMask, &e); XFlush(d); } int main(int argc, char **argv) { int secs = argc > 2 ? atoi(argv[2]) : 12; int t; d = XOpenDisplay(argc > 1 ? argv[1] : ":1"); if (!d) { fprintf(stderr, "xtest_move: no display\n"); return 1; } game = find_named("Freedoom"); if (!game) game = find_named("Outer Prison"); if (!game) game = find_named("Doom"); fprintf(stderr, "xtest_move: game window 0x%lx\n", game); if (!game) return 1; XSetInputFocus(d, game, RevertToParent, CurrentTime); XFlush(d); /* Hold forward only (no fire: fire pulls enemies and the player can * die mid-capture; with +noclip from the command line this is a smooth * fly-through). */ send_key(XK_Up, 1); for (t = 0; t < secs * 10; t++) { static int dir; /* 0 = left held, 1 = right held */ XSetInputFocus(d, game, RevertToParent, CurrentTime); /* keep focus */ /* Re-assert the held forward key: SDL's held state drops between * synthetic events, so forward motion stalls unless re-sent. */ if ((t % 4) == 0) send_key(XK_Up, 1); if ((t % 9) == 8) { KeySym off = dir ? XK_Right : XK_Left; KeySym on = dir ? XK_Left : XK_Right; send_key(off, 0); usleep(30000); send_key(on, 1); dir = !dir; } XFlush(d); usleep(100000); } send_key(XK_Left, 0); send_key(XK_Right, 0); send_key(XK_Up, 0); XCloseDisplay(d); fprintf(stderr, "xtest_move: done\n"); return 0; }