diff options
Diffstat (limited to 'tools/guest-diag')
| -rw-r--r-- | tools/guest-diag/guest_pin_stream.py | 3 | ||||
| -rw-r--r-- | tools/guest-diag/pin_gr_stream.py | 3 | ||||
| -rwxr-xr-x | tools/guest-diag/pin_supervisor.sh | 9 | ||||
| -rw-r--r-- | tools/guest-diag/qga_snip.py | 41 | ||||
| -rw-r--r-- | tools/guest-diag/sdl_cap.c | 229 | ||||
| -rw-r--r-- | tools/guest-diag/xfocus.c | 69 | ||||
| -rw-r--r-- | tools/guest-diag/xgrab.c | 115 | ||||
| -rw-r--r-- | tools/guest-diag/xresize.c | 66 | ||||
| -rw-r--r-- | tools/guest-diag/xtest_move.c | 126 |
9 files changed, 658 insertions, 3 deletions
diff --git a/tools/guest-diag/guest_pin_stream.py b/tools/guest-diag/guest_pin_stream.py index 8768f2f..3db61ba 100644 --- a/tools/guest-diag/guest_pin_stream.py +++ b/tools/guest-diag/guest_pin_stream.py @@ -1428,7 +1428,8 @@ def pid_line(tag, pid, start): def cdt_pid(): prefixes = tuple(p for p in os.environ.get("CLIENT_PREFIXES", - "cdt,va,victory").split(",") if p) + "cdt,va,victory,Xorg,X,glxgears," + "gzdoom").split(",") if p) # Processes that hold a /dev/nvidia* fd but are not CUDA clients; the # scanner must never commit to them (a transient nvidia-smi at boot was # picked once and its exited pagemap read back EOF forever). diff --git a/tools/guest-diag/pin_gr_stream.py b/tools/guest-diag/pin_gr_stream.py index ff7e299..4289d37 100644 --- a/tools/guest-diag/pin_gr_stream.py +++ b/tools/guest-diag/pin_gr_stream.py @@ -136,7 +136,8 @@ def _start_cmd(): b64 = subprocess.run(["base64", "-w0"], stdin=f, capture_output=True, text=True, timeout=10).stdout - prefixes = os.environ.get("CLIENT_PREFIXES", "cdt,va,victory") + prefixes = os.environ.get("CLIENT_PREFIXES", + "cdt,va,victory,Xorg,X,glxgears,gzdoom") vma_every = os.environ.get("STREAM_VMA_EVERY", "1") # Kill EVERY existing scanner, not just the pid-file one: a retry after # a partially-failed first attempt (pid file not written yet) used to diff --git a/tools/guest-diag/pin_supervisor.sh b/tools/guest-diag/pin_supervisor.sh index b87d5c1..72f588f 100755 --- a/tools/guest-diag/pin_supervisor.sh +++ b/tools/guest-diag/pin_supervisor.sh @@ -70,7 +70,14 @@ kill_tool() { start_pin() { if tool_alive "$PIN"; then return 0; fi touch "$PIN_HB" 2>/dev/null || true - ( setsid nohup env SEMFIX_COUNTER_RETRY=10 \ + # CLIENT_PREFIXES threads through pin_gr_stream.py into the guest + # scanner launch (its _start_cmd). GL clients (glxgears, gzdoom) and + # the X server must be covered or the GL client's ring/mappings are + # never pinned (SESSION 149: the GR ring scan then finds no candidate + # for h=0xbfef0002, va=0x1203cfff0). + ( setsid nohup env \ + CLIENT_PREFIXES="${CLIENT_PREFIXES:-cdt,va,victory,Xorg,X,glxgears,gzdoom}" \ + SEMFIX_COUNTER_RETRY=10 \ python3 "$PIN" >> "$PIN_LOG" 2>&1 & ) pin_started=$(date +%s) log "START pin_gr_stream" diff --git a/tools/guest-diag/qga_snip.py b/tools/guest-diag/qga_snip.py new file mode 100644 index 0000000..cd3b992 --- /dev/null +++ b/tools/guest-diag/qga_snip.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# Fetch a byte range from a guest file via the QEMU guest agent. +# Usage: qga_snip.py <guest_path> <offset> <length> <host_out> +import base64 +import json +import socket +import sys + +GUEST, OFF, LEN, HOST = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4] +import os +SOCK = os.environ.get("QGA_SOCK", "/tmp/qemu-guest-agent.sock") + + +def recv_line(s): + buf = b"" + while b"\n" not in buf: + c = s.recv(65536) + if not c: + break + buf += c + return buf.split(b"\n", 1)[0] + + +def cmd(s, obj): + s.sendall((json.dumps(obj) + "\r\n").encode()) + return json.loads(recv_line(s)) + + +s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +s.settimeout(30.0) +s.connect(SOCK) +h = cmd(s, {"execute": "guest-file-open", + "arguments": {"path": GUEST, "mode": "r"}})["return"] +cmd(s, {"execute": "guest-file-seek", + "arguments": {"handle": h, "offset": OFF, "whence": 0}}) +r = cmd(s, {"execute": "guest-file-read", + "arguments": {"handle": h, "count": LEN}}) +data = base64.b64decode(r["return"].get("buf-b64", "")) +open(HOST, "wb").write(data) +cmd(s, {"execute": "guest-file-close", "arguments": {"handle": h}}) +print(f"GOT {len(data)} bytes @ {OFF} -> {HOST}") diff --git a/tools/guest-diag/sdl_cap.c b/tools/guest-diag/sdl_cap.c new file mode 100644 index 0000000..23c83ab --- /dev/null +++ b/tools/guest-diag/sdl_cap.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: BSD-3-Clause +// sdl_cap.c - LD_PRELOAD capture shim: records every frame GZDoom presents +// through SDL into an append-only file, so the host can harvest the guest's +// real rendered frames without any X/driver readback path. +// +// Hooks: +// SDL_RenderPresent(SDL_Renderer*) -> SDL_RenderReadPixels(ARGB8888) +// SDL_UpdateWindowSurface(SDL_Window*) -> SDL_GetWindowSurface pixels +// Both forward to the real SDL after capturing. +// +// File layout (little endian): +// [0] 8B magic "GZCAPV01" (+ 4088 B reserved zero) +// [4096] frame records: +// 4B "GZF2" | u32 w | u32 h | u32 stride | u32 bpp +// u64 ts_ns (CLOCK_MONOTONIC) | u64 seq | stride*h bytes (top-down) +// A trailing record with seq = 0xFFFFFFFFFFFFFFFF marks clean stop. +// +// Env: GZCAP (path, default /dev/shm/gzframes.bin), GZCAP_MAXF (0=unlimited) +#define _GNU_SOURCE +#include <dlfcn.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#define ARGB8888 0x16362004u + +typedef struct SDL_Renderer SDL_Renderer; +typedef struct SDL_Window SDL_Window; + +typedef struct SDL_Surface { + uint32_t flags; + void *format; + int w, h; + int pitch; + void *pixels; + void *userdata; + int locked; + void *list_blitmap; + struct { int x, y, w, h; } clip_rect; + void *map; + int refcount; +} SDL_Surface; + +static int (*real_RenderReadPixels)(SDL_Renderer *, const void *, uint32_t, + void *, int); +static int (*real_RenderPresent)(SDL_Renderer *); +static SDL_Surface *(*real_GetWindowSurface)(SDL_Window *); +static int (*real_UpdateWindowSurface)(SDL_Window *); +static int (*real_GetRendererOutputSize)(SDL_Renderer *, int *, int *); +static void (*real_GL_SwapWindow)(SDL_Window *); +static int (*real_GL_GetDrawableSize)(SDL_Window *, int *, int *); +static void (*real_glReadPixels)(int, int, int, int, unsigned, unsigned, void *); + +static FILE *cap_fp; +static unsigned long long cap_count, cap_max = 0; +static int cap_stop; + +__attribute__((constructor)) static void cap_init(void) +{ + const char *p = getenv("GZCAP"); + const char *m = getenv("GZCAP_MAXF"); + char hdr[4096]; + + if (m) + cap_max = strtoull(m, NULL, 0); + if (!p) + p = "/dev/shm/gzframes.bin"; + real_RenderReadPixels = dlsym(RTLD_NEXT, "SDL_RenderReadPixels"); + real_RenderPresent = dlsym(RTLD_NEXT, "SDL_RenderPresent"); + real_GetWindowSurface = dlsym(RTLD_NEXT, "SDL_GetWindowSurface"); + real_UpdateWindowSurface = dlsym(RTLD_NEXT, "SDL_UpdateWindowSurface"); + real_GetRendererOutputSize = + dlsym(RTLD_NEXT, "SDL_GetRendererOutputSize"); + real_GL_SwapWindow = dlsym(RTLD_NEXT, "SDL_GL_SwapWindow"); + real_GL_GetDrawableSize = dlsym(RTLD_NEXT, "SDL_GL_GetDrawableSize"); + real_glReadPixels = dlsym(RTLD_NEXT, "glReadPixels"); + if (!real_glReadPixels) + real_glReadPixels = dlsym(RTLD_DEFAULT, "glReadPixels"); + if (!real_glReadPixels) { + void *h = dlopen("libGL.so.1", RTLD_NOW | RTLD_GLOBAL); + + if (!h) + h = dlopen("libGLESv2.so.2", RTLD_NOW | RTLD_GLOBAL); + if (h) + real_glReadPixels = dlsym(h, "glReadPixels"); + } + fprintf(stderr, "[gzcap] init path=%s present=%p updwin=%p swap=%p read=%p gros=%p\n", + p, (void *)real_RenderPresent, (void *)real_UpdateWindowSurface, + (void *)real_GL_SwapWindow, (void *)real_RenderReadPixels, + (void *)real_GetRendererOutputSize, (void *)real_glReadPixels); + cap_fp = fopen(p, "wb"); + if (!cap_fp) + return; + memset(hdr, 0, sizeof(hdr)); + memcpy(hdr, "GZCAPV01", 8); + fwrite(hdr, 1, sizeof(hdr), cap_fp); + fflush(cap_fp); +} + +static void cap_record(const void *px, int w, int h, int stride, int bpp) +{ + struct timespec ts; + uint64_t tns, seq; + uint32_t rh[4]; + char m[4] = { 'G', 'Z', 'F', '2' }; + + if (!cap_fp || cap_stop) + return; + if (cap_max && cap_count >= cap_max) { + uint64_t endseq = 0xFFFFFFFFFFFFFFFFull; + + fwrite(m, 1, 4, cap_fp); + fwrite(&endseq, 8, 1, cap_fp); + fflush(cap_fp); + cap_stop = 1; + return; + } + clock_gettime(CLOCK_MONOTONIC, &ts); + tns = (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; + seq = cap_count; + rh[0] = (uint32_t)w; + rh[1] = (uint32_t)h; + rh[2] = (uint32_t)stride; + rh[3] = (uint32_t)bpp; + fwrite(m, 1, 4, cap_fp); + fwrite(rh, 4, 4, cap_fp); + fwrite(&tns, 8, 1, cap_fp); + fwrite(&seq, 8, 1, cap_fp); + fwrite(px, 1, (size_t)stride * h, cap_fp); + fflush(cap_fp); + cap_count++; + if ((cap_count & 31) == 0) + fprintf(stderr, "[gzcap] frames=%llu\n", cap_count); +} + +int SDL_RenderPresent(SDL_Renderer *r) +{ + static int hit; + + if (!hit++) + fprintf(stderr, "[gzcap] hook RenderPresent\n"); + if (cap_fp && !cap_stop && real_RenderReadPixels && + real_GetRendererOutputSize) { + int w = 0, h = 0; + + if (real_GetRendererOutputSize(r, &w, &h) == 0 && w > 0 && h > 0 && + w <= 4096 && h <= 4096) { + static unsigned char *buf; + static size_t bufsz; + size_t need = (size_t)w * 4 * h; + + if (bufsz < need) { + free(buf); + buf = malloc(need); + bufsz = buf ? need : 0; + } + if (buf && real_RenderReadPixels(r, NULL, ARGB8888, buf, + w * 4) == 0) + cap_record(buf, w, h, w * 4, 32); + } + } + if (real_RenderPresent) + return real_RenderPresent(r); + return 0; +} + +int SDL_UpdateWindowSurface(SDL_Window *win) +{ + static int hit; + + if (!hit++) + fprintf(stderr, "[gzcap] hook UpdateWindowSurface\n"); + if (cap_fp && !cap_stop && real_GetWindowSurface) { + SDL_Surface *s = real_GetWindowSurface(win); + + if (s && s->pixels && s->w > 0 && s->h > 0 && s->pitch > 0 && + s->w <= 4096 && s->h <= 4096) + cap_record(s->pixels, s->w, s->h, s->pitch, 32); + } + if (real_UpdateWindowSurface) + return real_UpdateWindowSurface(win); + return 0; +} + +void SDL_GL_SwapWindow(SDL_Window *win) +{ + static int hit; + static unsigned char *gbuf; + static size_t gbufsz; + + if (!hit++) + fprintf(stderr, "[gzcap] hook GLSwapWindow\n"); + if (cap_fp && !cap_stop && real_glReadPixels && real_GL_GetDrawableSize) { + int w = 0, h = 0; + + if (real_GL_GetDrawableSize(win, &w, &h) == 0 && w > 0 && h > 0 && + w <= 4096 && h <= 4096) { + size_t need = (size_t)w * 4 * h; + + if (gbufsz < need) { + free(gbuf); + gbuf = malloc(need); + gbufsz = gbuf ? need : 0; + } + if (gbuf) { + real_glReadPixels(0, 0, w, h, 0x1908, 0x1401, gbuf); + /* GL rows are bottom-up: flip into a scratch record */ + { + unsigned char *tmp = malloc(need); + int y; + + if (tmp) { + for (y = 0; y < h; y++) + memcpy(tmp + (size_t)y * w * 4, + gbuf + (size_t)(h - 1 - y) * w * 4, + (size_t)w * 4); + cap_record(tmp, w, h, w * 4, 32); + free(tmp); + } + } + } + } + } + if (real_GL_SwapWindow) + real_GL_SwapWindow(win); +} diff --git a/tools/guest-diag/xfocus.c b/tools/guest-diag/xfocus.c new file mode 100644 index 0000000..e0ec05d --- /dev/null +++ b/tools/guest-diag/xfocus.c @@ -0,0 +1,69 @@ +// xfocus.c - print the current X input focus and the gzdoom window, and try +// to focus the gzdoom window; then report again. Diagnostic for WM-less X. +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <stdio.h> +#include <string.h> + +static Window find_named(Display *d, Window root, const char *sub, + char *title, size_t tl) +{ + Window r, parent, *kids = NULL; + unsigned int nk = 0; + + title[0] = 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)) { + snprintf(title, tl, "%s", name); + Window w = kids[i]; + + XFree(name); + if (kids) + XFree(kids); + return w; + } + XFree(name); + } + } + if (kids) + XFree(kids); + return 0; +} + +int main(void) +{ + Display *d = XOpenDisplay(NULL); + Window root, focus, w, croot; + int rx, ry, wx, wy; + unsigned int mask; + char title[256]; + + if (!d) { + printf("no display\n"); + return 1; + } + root = DefaultRootWindow(d); + w = find_named(d, root, "Freedoom", title, sizeof(title)); + XGetInputFocus(d, &focus, &rx); + printf("gzdoom window: 0x%lx '%s'\n", w, title); + printf("input focus: 0x%lx\n", focus); + printf("pointer window: "); + if (XQueryPointer(d, root, &croot, &rx /*reuse*/, &wx, &wy, &rx, &ry, + &mask)) + printf("0x%lx at %d,%d mask=0x%x\n", rx, wx, wy, mask); + else + printf("query failed\n"); + if (w) { + XSetInputFocus(d, w, RevertToParent, CurrentTime); + XFlush(d); + XGetInputFocus(d, &focus, &rx); + printf("after set: 0x%lx\n", focus); + } + XCloseDisplay(d); + return 0; +} diff --git a/tools/guest-diag/xgrab.c b/tools/guest-diag/xgrab.c new file mode 100644 index 0000000..eb605d6 --- /dev/null +++ b/tools/guest-diag/xgrab.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: BSD-3-Clause +// xgrab.c - capture the X screen (root window) into an append-only frame file. +// Pure client-side XGetImage on a software X server (Xvfb): no GL, no driver, +// no present-path hooks. Same record format as sdl_cap.c: +// [0] 8B "GZCAPV01" + zero pad to 4096 +// [4096..] records: 4B "GZF2" | u32 w | u32 h | u32 stride | u32 bpp | +// u64 ts_ns | u64 seq | stride*h bytes (top-down BGRA) +// Trailing record seq=0xFFFFFFFFFFFFFFFF on clean stop. +// Usage: xgrab <display> <out> <maxframes|0> <pace_us|0> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +int main(int argc, char **argv) +{ + const char *dpy = argc > 1 ? argv[1] : ":1"; + const char *out = argc > 2 ? argv[2] : "/dev/shm/gzframes.bin"; + long maxf = argc > 3 ? atol(argv[3]) : 0; + long pace = argc > 4 ? atol(argv[4]) : 0; + int r_x = argc > 5 ? atoi(argv[5]) : -1; + int r_y = argc > 6 ? atoi(argv[6]) : 0; + int r_w = argc > 7 ? atoi(argv[7]) : 0; + int r_h = argc > 8 ? atoi(argv[8]) : 0; + Display *d; + Window root; + XWindowAttributes wa; + FILE *f; + char hdr[4096]; + uint64_t seq = 0; + + d = XOpenDisplay(dpy); + if (!d) { + fprintf(stderr, "xgrab: cannot open display %s\n", dpy); + return 1; + } + root = DefaultRootWindow(d); + if (!XGetWindowAttributes(d, root, &wa)) { + fprintf(stderr, "xgrab: no root attrs\n"); + return 1; + } + if (r_x < 0 || r_w <= 0 || r_h <= 0 || r_x + r_w > wa.width || + r_y + r_h > wa.height) { + r_x = 0; r_y = 0; r_w = wa.width; r_h = wa.height; + } + fprintf(stderr, "xgrab: root %dx%d depth=%d region %d,%d %dx%d\n", + wa.width, wa.height, wa.depth, r_x, r_y, r_w, r_h); + f = fopen(out, "wb"); + if (!f) { + perror("xgrab: open out"); + return 1; + } + memset(hdr, 0, sizeof(hdr)); + memcpy(hdr, "GZCAPV01", 8); + fwrite(hdr, 1, sizeof(hdr), f); + fflush(f); + + while (!maxf || (long)seq < maxf) { + XImage *im = XGetImage(d, root, r_x, r_y, r_w, r_h, AllPlanes, + ZPixmap); + struct timespec ts; + uint64_t tns; + uint32_t rh[4]; + char m[4] = { 'G', 'Z', 'F', '2' }; + int y; + + if (!im) { + usleep(10000); + continue; + } + if (im->bits_per_pixel != 32) { + fprintf(stderr, "xgrab: unexpected bpp %d\n", + im->bits_per_pixel); + XDestroyImage(im); + break; + } + clock_gettime(CLOCK_MONOTONIC, &ts); + tns = (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; + rh[0] = (uint32_t)r_w; + rh[1] = (uint32_t)r_h; + rh[2] = (uint32_t)r_w * 4; + rh[3] = 32; + fwrite(m, 1, 4, f); + fwrite(rh, 4, 4, f); + fwrite(&tns, 8, 1, f); + fwrite(&seq, 8, 1, f); + for (y = 0; y < r_h; y++) + fwrite(im->data + (size_t)y * im->bytes_per_line, 1, + (size_t)r_w * 4, f); + fflush(f); + seq++; + XDestroyImage(im); + if (pace) + usleep(pace); + if ((seq & 63) == 0) + fprintf(stderr, "xgrab: frames=%llu\n", + (unsigned long long)seq); + } + { + char m[4] = { 'G', 'Z', 'F', '2' }; + uint64_t end = 0xFFFFFFFFFFFFFFFFull; + + fwrite(m, 1, 4, f); + fwrite(&end, 8, 1, f); + fflush(f); + } + fclose(f); + XCloseDisplay(d); + fprintf(stderr, "xgrab: done frames=%llu\n", (unsigned long long)seq); + return 0; +} diff --git a/tools/guest-diag/xresize.c b/tools/guest-diag/xresize.c new file mode 100644 index 0000000..877f0e9 --- /dev/null +++ b/tools/guest-diag/xresize.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: BSD-3-Clause +// xresize.c - find a window by name substring and resize/move it (Xvfb has +// no WM, so a client-side XMoveResizeWindow is the only way to force the +// game window onto the screen). +// Usage: xresize <display> <name-substr> <w> <h> <x> <y> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +static Window find_named(Display *d, Window root, const char *sub) +{ + Window r, parent, *kids = NULL; + unsigned int nkids = 0; + + if (!XQueryTree(d, root, &r, &parent, &kids, &nkids)) + return 0; + for (unsigned int i = 0; i < nkids; i++) { + char *name = NULL; + + if (XFetchName(d, kids[i], &name) && name) { + int hit = strstr(name, sub) != NULL; + + XFree(name); + if (hit) { + Window w = kids[i]; + + if (kids) + XFree(kids); + return w; + } + } + } + if (kids) + XFree(kids); + return 0; +} + +int main(int argc, char **argv) +{ + Display *d; + Window w; + + if (argc < 6) { + fprintf(stderr, "usage: xresize <display> <name> <w> <h> <x> <y>\n"); + return 2; + } + d = XOpenDisplay(argv[1]); + if (!d) { + fprintf(stderr, "no display\n"); + return 1; + } + w = find_named(d, DefaultRootWindow(d), argv[2]); + if (!w) { + fprintf(stderr, "window '%s' not found\n", argv[2]); + return 1; + } + XMoveResizeWindow(d, w, atoi(argv[5]), atoi(argv[6]), atoi(argv[3]), + atoi(argv[4])); + XFlush(d); + printf("resized 0x%lx to %sx%s at %s,%s\n", w, argv[3], argv[4], + argv[5], argv[6]); + XCloseDisplay(d); + return 0; +} diff --git a/tools/guest-diag/xtest_move.c b/tools/guest-diag/xtest_move.c new file mode 100644 index 0000000..8331487 --- /dev/null +++ b/tools/guest-diag/xtest_move.c @@ -0,0 +1,126 @@ +// 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 <display> <seconds> +#include <X11/Xlib.h> +#include <X11/keysym.h> +#include <X11/Xutil.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +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; +} |
