// 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 #include #include #include #include #include #include #include #include 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; }