From c1885512331df155662dcd0695f80e24de14e0ea Mon Sep 17 00:00:00 2001 From: hashirama Date: Mon, 7 Apr 2025 15:15:03 +0000 Subject: [PATCH 1/4] Add screenshot.c --- screenshot.c | 405 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 405 insertions(+) create mode 100644 screenshot.c diff --git a/screenshot.c b/screenshot.c new file mode 100644 index 0000000..b3cc5cd --- /dev/null +++ b/screenshot.c @@ -0,0 +1,405 @@ +#define _POSIX_C_SOURCE 200112L // NOLINT + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +typedef uint8_t u8; +typedef uint32_t u32; +typedef uint64_t u64; +typedef ptrdiff_t Size; +typedef struct { u8 *s; Size len; } Str; +typedef struct { u8 *buf, *off, *end; int fd, err; } Out; + +#define NOP() ((void)0) +#define SIZEOF(...) ((Size)sizeof(__VA_ARGS__)) +#define ARRLEN(...) (SIZEOF(__VA_ARGS__) / SIZEOF(0[__VA_ARGS__])) +#define S(X) ((Str){ .s = (u8 *)(X), .len = ARRLEN(X) - 1 }) +#define SC(X) { .s = (u8 *)(X), .len = ARRLEN(X) - 1 } + +#ifdef __GNUC__ + // When debugging, use gcc/clang and compile with UBSan enabled: + // `-fsanitize=undefined -fsanitize-trap` + #define ASSERT(X) ((X) ? (void)0 : __builtin_unreachable()) +#else + #define ASSERT(X) ((void)0) +#endif + +static Str +str_from_cstr(char *s) +{ + Str ret = { .s = (u8 *)s, 0 }; + if (s) for (NOP(); s[ret.len] != '\0'; ++ret.len) {} + return ret; +} + +static bool +str_eq(Str a, Str b) +{ + Size n = -1; + ASSERT(a.len >= 0 && b.len >= 0); + if (a.len == b.len) { + for (n = 0; n < a.len && a.s[n] == b.s[n]; ++n) {} + } + return n == a.len; +} + +static Str +str_tok(Str *s, u8 ch) +{ + Str ret = { .s = s->s }; + ASSERT(!(s->len < 0)); + for (NOP(); ret.len < s->len && ret.s[ret.len] != ch; ++ret.len) {} + s->s += ret.len + (ret.len < s->len); + s->len -= ret.len + (ret.len < s->len); + return ret; +} + +static bool +str_to_int(Str s, int *out) +{ + u64 n = 0; + ASSERT(!(s.len < 0)); + for (Size i = 0; i < s.len; ++i) { + u8 ch = s.s[i]; + n = (n * 10) + (ch - '0'); + if (n > INT_MAX || !(ch >= '0' && ch <= '9')) { + return false; + } + } + *out = n; + return s.len > 0; +} + +static int +full_write(int fd, Str s) +{ + while (s.len > 0) { + Size res = write(fd, s.s, s.len); + if (res < 0) { + return -1; + } + s.s += res; s.len -= res; + } + return 0; +} + +static Out +out_create(int fd, u8 *buf, u8 *end) +{ + ASSERT(buf != NULL && end != NULL && buf < end); + return (Out){ .fd = fd, .buf = buf, .off = buf, .end = end }; +} + +static void +out_flush(Out *out) +{ + Size n = out->off - out->buf; + out->off = out->buf; + out->err = out->err || full_write(out->fd, (Str){out->buf, n}) < 0; +} + +static void +out_str(Out *out, Str str) +{ + u8 *s = str.s; + u8 *end = (str.len > 0) ? s + str.len : s; + if (!out->err && str.len >= (out->end - out->buf)) { // Fast path for large writes + out_flush(out); + if (!out->err) { + out->err = full_write(out->fd, str) < 0; + } + } else while (!out->err && s < end) { + if (out->off == out->end) { + out_flush(out); + } else while (out->off < out->end && s < end) { + *out->off++ = *s++; + } + } +} + +static void +out_uint(Out *out, u64 n) +{ + u8 buf[32], *p = buf + ARRLEN(buf), *end = p; + do { *--p = '0' + (n % 10); } while (n /= 10); + out_str(out, (Str){ p, end - p }); +} + +_Noreturn static void +fatal(Out *eout, Str *sv, Size n) +{ + for (Size i = 0; i < n; ++i) out_str(eout, sv[i]); + out_str(eout, S("\n")); + out_flush(eout); + exit(1); +} +#define fatal(O, ...) fatal((O), (Str []){ __VA_ARGS__ }, ARRLEN((Str []){ __VA_ARGS__ })) + +static void +write_ppm(u8 *data, int width, int height, bool msb_first, Out *out) +{ + out_str(out, S("P6\n")); + out_uint(out, width); out_str(out, S(" ")); + out_uint(out, height); out_str(out, S("\n")); + out_str(out, S("255\n")); + + u8 *w = data; + u8 *end = data + ((Size)width * height * 4); + for (u8 *p = data; p < end; p += 4) { + int R, G, B; + if (msb_first) { + R = p[1]; G = p[2]; B = p[3]; + } else { + R = p[2]; G = p[1]; B = p[0]; + } + *w++ = R; *w++ = G; *w++ = B; + } + ASSERT(w < end); + out_str(out, (Str){ data, w - data }); +} + +static void +blend_cursor(XImage *im, XFixesCursorImage *cur) +{ + int yend = (cur->y + cur->height) < im->height ? + (cur->y + cur->height) : im->height; + int xend = (cur->x + cur->width) < im->width ? + (cur->x + cur->width) : im->width; + int R, G, B; + if (im->byte_order == MSBFirst) { + R = 1; G = 2; B = 3; + } else { + R = 2; G = 1; B = 0; + } + for (int y = cur->y, yc = 0; y < yend; ++y, ++yc) { + for (int x = cur->x, xc = 0; x < xend; ++x, ++xc) { + Size off = ((Size)y * im->width) + x; + u8 *p = (u8 *)im->data + (off * 4); + u32 c = cur->pixels[(yc * cur->width) + xc]; + u32 a = (c >> 24) & 0xFF; + if (a == 0) { + continue; + } + p[R] = ((((c >> 16) & 0xFF) * a) + (p[R] * (0xFF - a))) / 0xFF; + p[G] = ((((c >> 8) & 0xFF) * a) + (p[G] * (0xFF - a))) / 0xFF; + p[B] = ((((c >> 0) & 0xFF) * a) + (p[B] * (0xFF - a))) / 0xFF; + } + } +} + +int main(int argc, char *argv[]) +{ + static const Str usage = SC( + "Usage: sxot [OPTIONS..] [FILE]\n" + "Capture a binary ppm screenshot and writes it to FILE.\n" + "Writes to stdout by default if no FILE is given.\n" + "\n" + "OPTIONS:\n" + " -c, --cursor Capture the cursor also\n" + " -f, --force Overwrite FILE if it already exists." + ); + static const Str version = SC( + "sxot v0.2.0\n" + "Copyright (C) 2023-2024 NRK.\n" + "License: GPLv3+ \n" + "Upstream: " + ); + + static u8 out_buffer[1 << 16]; + static u8 eout_buffer[1 << 8]; + struct { + union { + int v[4]; + struct { int x, y, w, h; }; + }; + Out out[1]; + Out eout[1]; + const char *output_filename; + bool capture_cursor; + bool overwrite_file; + } opt = { .x = -1 }; + struct { Display *dpy; Window root; } x11; + + opt.eout[0] = out_create(2, eout_buffer, eout_buffer + ARRLEN(eout_buffer)); + + if ((x11.dpy = XOpenDisplay(NULL)) == NULL) { + fatal(opt.eout, S("failed to open X Display")); + } + x11.root = DefaultRootWindow(x11.dpy); + + // Parse command-line arguments (removed -g option) + for (int i = 1; i < argc; ++i) { + Str arg = str_from_cstr(argv[i]); + if (str_eq(arg, S("--cursor")) || str_eq(arg, S("-c"))) { + opt.capture_cursor = true; + } else if (str_eq(arg, S("--force")) || str_eq(arg, S("-f"))) { + opt.overwrite_file = true; + } else if (str_eq(arg, S("--help")) || str_eq(arg, S("-h"))) { + fatal(opt.eout, usage); + } else if (str_eq(arg, S("--version")) || str_eq(arg, S("-v"))) { + fatal(opt.eout, version); + } else if (arg.len > 0 && arg.s[0] == '-') { + fatal(opt.eout, S("unknown flag: `"), arg, S("`")); + } else if (opt.output_filename == NULL) { + opt.output_filename = (const char *)arg.s; + } else { + fatal(opt.eout, S("excess argument: `"), arg, S("`")); + } + } + + // Cursor-based selection code + Cursor cursor = XCreateFontCursor(x11.dpy, XC_crosshair); + if (cursor == None) { + fatal(opt.eout, S("couldn't create cursor")); + } + + GC gc = XCreateGC(x11.dpy, x11.root, 0, NULL); + if (gc == None) { + fatal(opt.eout, S("couldn't create GC")); + } + + XSetFunction(x11.dpy, gc, GXxor); + XSetForeground(x11.dpy, gc, WhitePixel(x11.dpy, DefaultScreen(x11.dpy))); + + int grab_result = XGrabPointer(x11.dpy, x11.root, False, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | KeyPressMask, + GrabModeAsync, GrabModeAsync, None, cursor, CurrentTime); + + if (grab_result != GrabSuccess) { + fatal(opt.eout, S("couldn't grab pointer")); + } + + int start_x, start_y, end_x, end_y; + bool selecting = false; + + XEvent ev; + while (1) { + XNextEvent(x11.dpy, &ev); + switch (ev.type) { + case ButtonPress: + if (ev.xbutton.button == Button1) { + start_x = ev.xbutton.x_root; + start_y = ev.xbutton.y_root; + selecting = true; + end_x = start_x; // Initialize to start position + end_y = start_y; + } + break; + case MotionNotify: + if (selecting) { + int current_x = ev.xmotion.x_root; + int current_y = ev.xmotion.y_root; + // Erase previous rectangle + XDrawRectangle(x11.dpy, x11.root, gc, start_x, start_y, end_x - start_x, end_y - start_y); + end_x = current_x; + end_y = current_y; + // Draw new rectangle + XDrawRectangle(x11.dpy, x11.root, gc, start_x, start_y, end_x - start_x, end_y - start_y); + XFlush(x11.dpy); + } + break; + case ButtonRelease: + if (ev.xbutton.button == Button1) { + end_x = ev.xbutton.x_root; + end_y = ev.xbutton.y_root; + // Erase the rectangle + XDrawRectangle(x11.dpy, x11.root, gc, start_x, start_y, end_x - start_x, end_y - start_y); + XFlush(x11.dpy); + XUngrabPointer(x11.dpy, CurrentTime); + goto selection_done; + } + break; + case KeyPress: + if (XLookupKeysym(&ev.xkey, 0) == XK_Escape) { + XUngrabPointer(x11.dpy, CurrentTime); + XCloseDisplay(x11.dpy); + exit(1); + } + break; + } + } +selection_done: + XFreeGC(x11.dpy, gc); + XFreeCursor(x11.dpy, cursor); + + opt.x = (start_x < end_x) ? start_x : end_x; + opt.y = (start_y < end_y) ? start_y : end_y; + opt.w = abs(end_x - start_x); + opt.h = abs(end_y - start_y); + + if (opt.w == 0 || opt.h == 0) { + fatal(opt.eout, S("selection has zero size")); + } + + // Output handling + if (opt.output_filename == NULL) { + opt.out[0] = out_create(1, out_buffer, out_buffer + ARRLEN(out_buffer)); + } else { + int flags = O_WRONLY | O_CREAT | O_EXCL; + if (opt.overwrite_file) { + flags &= ~O_EXCL; + flags |= O_TRUNC; + } + int fd = open(opt.output_filename, flags, 0600); + if (fd < 0) { + fatal(opt.eout, S("couldn't open output file: "), str_from_cstr(strerror(errno))); + } + opt.out[0] = out_create(fd, out_buffer, out_buffer + ARRLEN(out_buffer)); + } + + if (isatty(opt.out->fd)) { + fatal(opt.eout, S("aborting: output is connected to a terminal")); + } + + // Capture the image + XImage *im = XGetImage( + x11.dpy, x11.root, opt.x, opt.y, opt.w, opt.h, + AllPlanes, ZPixmap + ); + if (im == NULL) { + fatal(opt.eout, S("XGetImage failed")); + } + if (im->bits_per_pixel != 32 || im->bytes_per_line != (im->width * 4) || + !(im->depth == 24 || im->depth == 32)) { + fatal(opt.eout, S("unsupported XImage format")); + } + + // Blend cursor if requested + if (opt.capture_cursor) { + if (XFixesQueryVersion(x11.dpy, (int[]){0}, (int[]){0}) != True) { + fatal(opt.eout, S("XFixesQueryVersion failed")); + } + XFixesCursorImage *cur = XFixesGetCursorImage(x11.dpy); + if (cur != NULL) { + blend_cursor(im, cur); + XFree(cur); + } + } + + // Write PPM + write_ppm((u8 *)im->data, im->width, im->height, im->byte_order == MSBFirst, opt.out); + + out_flush(opt.out); + if (opt.out->err) { + // not permanent, since we will turn this whole thing into a function + // which will return the PPM as a bytestring (raw bytes) + fatal(opt.eout, S("error writing to output")); + } + + XDestroyImage(im); + XCloseDisplay(x11.dpy); + + return 0; +} \ No newline at end of file -- 2.39.5 From 4e693b70cc57ac3c867c529c7d187fb0c9ba382c Mon Sep 17 00:00:00 2001 From: hashirama Date: Mon, 7 Apr 2025 15:16:09 +0000 Subject: [PATCH 2/4] Update screenshot.c --- screenshot.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/screenshot.c b/screenshot.c index b3cc5cd..eff0585 100644 --- a/screenshot.c +++ b/screenshot.c @@ -395,6 +395,9 @@ selection_done: if (opt.out->err) { // not permanent, since we will turn this whole thing into a function // which will return the PPM as a bytestring (raw bytes) + + // stb_image.h can load PPM and we can load from memory by using: + // stbi_load_from_memory , which will receive the output of our screenshot function directly. fatal(opt.eout, S("error writing to output")); } -- 2.39.5 From eb0e9809058d9444a7bf7f69985b6465792bd5c0 Mon Sep 17 00:00:00 2001 From: hashirama Date: Mon, 7 Apr 2025 22:12:06 +0000 Subject: [PATCH 3/4] fix grab cursor function --- screenshot.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/screenshot.c b/screenshot.c index eff0585..b641c11 100644 --- a/screenshot.c +++ b/screenshot.c @@ -27,6 +27,8 @@ typedef struct { u8 *buf, *off, *end; int fd, err; } Out; #define ARRLEN(...) (SIZEOF(__VA_ARGS__) / SIZEOF(0[__VA_ARGS__])) #define S(X) ((Str){ .s = (u8 *)(X), .len = ARRLEN(X) - 1 }) #define SC(X) { .s = (u8 *)(X), .len = ARRLEN(X) - 1 } +// +#define XC_crosshair 34 #ifdef __GNUC__ // When debugging, use gcc/clang and compile with UBSan enabled: @@ -273,9 +275,10 @@ int main(int argc, char *argv[]) XSetFunction(x11.dpy, gc, GXxor); XSetForeground(x11.dpy, gc, WhitePixel(x11.dpy, DefaultScreen(x11.dpy))); - int grab_result = XGrabPointer(x11.dpy, x11.root, False, - ButtonPressMask | ButtonReleaseMask | PointerMotionMask | KeyPressMask, - GrabModeAsync, GrabModeAsync, None, cursor, CurrentTime); + int grab_result = XGrabPointer(x11.dpy, x11.root, False, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, // Remove KeyPressMask + GrabModeAsync, GrabModeAsync, None, cursor, CurrentTime); + if (grab_result != GrabSuccess) { fatal(opt.eout, S("couldn't grab pointer")); @@ -405,4 +408,4 @@ selection_done: XCloseDisplay(x11.dpy); return 0; -} \ No newline at end of file +} -- 2.39.5 From 928dcebe7f037768d430af7455a75c6637b14e19 Mon Sep 17 00:00:00 2001 From: hashirama Date: Tue, 8 Apr 2025 01:04:01 +0000 Subject: [PATCH 4/4] add final version --- screenshot.c | 485 ++++++++++----------------------------------------- 1 file changed, 93 insertions(+), 392 deletions(-) diff --git a/screenshot.c b/screenshot.c index b641c11..9b69e53 100644 --- a/screenshot.c +++ b/screenshot.c @@ -1,411 +1,112 @@ -#define _POSIX_C_SOURCE 200112L // NOLINT - -#include -#include -#include -#include - -#include -#include -#include -#include -#include +// Copyright © 2025 Hashirama Senju #include #include -#include +#include +#include +#include +#include +#include -typedef uint8_t u8; -typedef uint32_t u32; -typedef uint64_t u64; -typedef ptrdiff_t Size; -typedef struct { u8 *s; Size len; } Str; -typedef struct { u8 *buf, *off, *end; int fd, err; } Out; +typedef struct { + uint8_t* pixels; // Raw RGB bytes (PPM P6 format) + int width; + int height; + size_t size; // Total buffer size (header + pixels) +} PPMBuffer; -#define NOP() ((void)0) -#define SIZEOF(...) ((Size)sizeof(__VA_ARGS__)) -#define ARRLEN(...) (SIZEOF(__VA_ARGS__) / SIZEOF(0[__VA_ARGS__])) -#define S(X) ((Str){ .s = (u8 *)(X), .len = ARRLEN(X) - 1 }) -#define SC(X) { .s = (u8 *)(X), .len = ARRLEN(X) - 1 } -// -#define XC_crosshair 34 +// Interactive region selection and capture +PPMBuffer capture_ppm_region() { + PPMBuffer buf = {0}; + Display* disp = XOpenDisplay(NULL); + if (!disp) return buf; -#ifdef __GNUC__ - // When debugging, use gcc/clang and compile with UBSan enabled: - // `-fsanitize=undefined -fsanitize-trap` - #define ASSERT(X) ((X) ? (void)0 : __builtin_unreachable()) -#else - #define ASSERT(X) ((void)0) -#endif - -static Str -str_from_cstr(char *s) -{ - Str ret = { .s = (u8 *)s, 0 }; - if (s) for (NOP(); s[ret.len] != '\0'; ++ret.len) {} - return ret; -} - -static bool -str_eq(Str a, Str b) -{ - Size n = -1; - ASSERT(a.len >= 0 && b.len >= 0); - if (a.len == b.len) { - for (n = 0; n < a.len && a.s[n] == b.s[n]; ++n) {} - } - return n == a.len; -} - -static Str -str_tok(Str *s, u8 ch) -{ - Str ret = { .s = s->s }; - ASSERT(!(s->len < 0)); - for (NOP(); ret.len < s->len && ret.s[ret.len] != ch; ++ret.len) {} - s->s += ret.len + (ret.len < s->len); - s->len -= ret.len + (ret.len < s->len); - return ret; -} - -static bool -str_to_int(Str s, int *out) -{ - u64 n = 0; - ASSERT(!(s.len < 0)); - for (Size i = 0; i < s.len; ++i) { - u8 ch = s.s[i]; - n = (n * 10) + (ch - '0'); - if (n > INT_MAX || !(ch >= '0' && ch <= '9')) { - return false; - } - } - *out = n; - return s.len > 0; -} - -static int -full_write(int fd, Str s) -{ - while (s.len > 0) { - Size res = write(fd, s.s, s.len); - if (res < 0) { - return -1; - } - s.s += res; s.len -= res; - } - return 0; -} - -static Out -out_create(int fd, u8 *buf, u8 *end) -{ - ASSERT(buf != NULL && end != NULL && buf < end); - return (Out){ .fd = fd, .buf = buf, .off = buf, .end = end }; -} - -static void -out_flush(Out *out) -{ - Size n = out->off - out->buf; - out->off = out->buf; - out->err = out->err || full_write(out->fd, (Str){out->buf, n}) < 0; -} - -static void -out_str(Out *out, Str str) -{ - u8 *s = str.s; - u8 *end = (str.len > 0) ? s + str.len : s; - if (!out->err && str.len >= (out->end - out->buf)) { // Fast path for large writes - out_flush(out); - if (!out->err) { - out->err = full_write(out->fd, str) < 0; - } - } else while (!out->err && s < end) { - if (out->off == out->end) { - out_flush(out); - } else while (out->off < out->end && s < end) { - *out->off++ = *s++; - } - } -} - -static void -out_uint(Out *out, u64 n) -{ - u8 buf[32], *p = buf + ARRLEN(buf), *end = p; - do { *--p = '0' + (n % 10); } while (n /= 10); - out_str(out, (Str){ p, end - p }); -} - -_Noreturn static void -fatal(Out *eout, Str *sv, Size n) -{ - for (Size i = 0; i < n; ++i) out_str(eout, sv[i]); - out_str(eout, S("\n")); - out_flush(eout); - exit(1); -} -#define fatal(O, ...) fatal((O), (Str []){ __VA_ARGS__ }, ARRLEN((Str []){ __VA_ARGS__ })) - -static void -write_ppm(u8 *data, int width, int height, bool msb_first, Out *out) -{ - out_str(out, S("P6\n")); - out_uint(out, width); out_str(out, S(" ")); - out_uint(out, height); out_str(out, S("\n")); - out_str(out, S("255\n")); - - u8 *w = data; - u8 *end = data + ((Size)width * height * 4); - for (u8 *p = data; p < end; p += 4) { - int R, G, B; - if (msb_first) { - R = p[1]; G = p[2]; B = p[3]; - } else { - R = p[2]; G = p[1]; B = p[0]; - } - *w++ = R; *w++ = G; *w++ = B; - } - ASSERT(w < end); - out_str(out, (Str){ data, w - data }); -} - -static void -blend_cursor(XImage *im, XFixesCursorImage *cur) -{ - int yend = (cur->y + cur->height) < im->height ? - (cur->y + cur->height) : im->height; - int xend = (cur->x + cur->width) < im->width ? - (cur->x + cur->width) : im->width; - int R, G, B; - if (im->byte_order == MSBFirst) { - R = 1; G = 2; B = 3; - } else { - R = 2; G = 1; B = 0; - } - for (int y = cur->y, yc = 0; y < yend; ++y, ++yc) { - for (int x = cur->x, xc = 0; x < xend; ++x, ++xc) { - Size off = ((Size)y * im->width) + x; - u8 *p = (u8 *)im->data + (off * 4); - u32 c = cur->pixels[(yc * cur->width) + xc]; - u32 a = (c >> 24) & 0xFF; - if (a == 0) { - continue; - } - p[R] = ((((c >> 16) & 0xFF) * a) + (p[R] * (0xFF - a))) / 0xFF; - p[G] = ((((c >> 8) & 0xFF) * a) + (p[G] * (0xFF - a))) / 0xFF; - p[B] = ((((c >> 0) & 0xFF) * a) + (p[B] * (0xFF - a))) / 0xFF; - } - } -} - -int main(int argc, char *argv[]) -{ - static const Str usage = SC( - "Usage: sxot [OPTIONS..] [FILE]\n" - "Capture a binary ppm screenshot and writes it to FILE.\n" - "Writes to stdout by default if no FILE is given.\n" - "\n" - "OPTIONS:\n" - " -c, --cursor Capture the cursor also\n" - " -f, --force Overwrite FILE if it already exists." - ); - static const Str version = SC( - "sxot v0.2.0\n" - "Copyright (C) 2023-2024 NRK.\n" - "License: GPLv3+ \n" - "Upstream: " - ); - - static u8 out_buffer[1 << 16]; - static u8 eout_buffer[1 << 8]; - struct { - union { - int v[4]; - struct { int x, y, w, h; }; - }; - Out out[1]; - Out eout[1]; - const char *output_filename; - bool capture_cursor; - bool overwrite_file; - } opt = { .x = -1 }; - struct { Display *dpy; Window root; } x11; - - opt.eout[0] = out_create(2, eout_buffer, eout_buffer + ARRLEN(eout_buffer)); - - if ((x11.dpy = XOpenDisplay(NULL)) == NULL) { - fatal(opt.eout, S("failed to open X Display")); - } - x11.root = DefaultRootWindow(x11.dpy); - - // Parse command-line arguments (removed -g option) - for (int i = 1; i < argc; ++i) { - Str arg = str_from_cstr(argv[i]); - if (str_eq(arg, S("--cursor")) || str_eq(arg, S("-c"))) { - opt.capture_cursor = true; - } else if (str_eq(arg, S("--force")) || str_eq(arg, S("-f"))) { - opt.overwrite_file = true; - } else if (str_eq(arg, S("--help")) || str_eq(arg, S("-h"))) { - fatal(opt.eout, usage); - } else if (str_eq(arg, S("--version")) || str_eq(arg, S("-v"))) { - fatal(opt.eout, version); - } else if (arg.len > 0 && arg.s[0] == '-') { - fatal(opt.eout, S("unknown flag: `"), arg, S("`")); - } else if (opt.output_filename == NULL) { - opt.output_filename = (const char *)arg.s; - } else { - fatal(opt.eout, S("excess argument: `"), arg, S("`")); - } - } - - // Cursor-based selection code - Cursor cursor = XCreateFontCursor(x11.dpy, XC_crosshair); - if (cursor == None) { - fatal(opt.eout, S("couldn't create cursor")); - } - - GC gc = XCreateGC(x11.dpy, x11.root, 0, NULL); - if (gc == None) { - fatal(opt.eout, S("couldn't create GC")); - } - - XSetFunction(x11.dpy, gc, GXxor); - XSetForeground(x11.dpy, gc, WhitePixel(x11.dpy, DefaultScreen(x11.dpy))); - - int grab_result = XGrabPointer(x11.dpy, x11.root, False, - ButtonPressMask | ButtonReleaseMask | PointerMotionMask, // Remove KeyPressMask - GrabModeAsync, GrabModeAsync, None, cursor, CurrentTime); - - - if (grab_result != GrabSuccess) { - fatal(opt.eout, S("couldn't grab pointer")); - } - - int start_x, start_y, end_x, end_y; - bool selecting = false; + // Region selection logic + Window root = DefaultRootWindow(disp); + Cursor cursor = XCreateFontCursor(disp, XC_crosshair); + XGrabPointer(disp, root, False, ButtonPressMask|ButtonReleaseMask|PointerMotionMask, + GrabModeAsync, GrabModeAsync, root, cursor, CurrentTime); XEvent ev; + int start_x = 0, start_y = 0, end_x = 0, end_y = 0; + int selecting = 0; + while (1) { - XNextEvent(x11.dpy, &ev); - switch (ev.type) { - case ButtonPress: - if (ev.xbutton.button == Button1) { - start_x = ev.xbutton.x_root; - start_y = ev.xbutton.y_root; - selecting = true; - end_x = start_x; // Initialize to start position - end_y = start_y; - } - break; - case MotionNotify: - if (selecting) { - int current_x = ev.xmotion.x_root; - int current_y = ev.xmotion.y_root; - // Erase previous rectangle - XDrawRectangle(x11.dpy, x11.root, gc, start_x, start_y, end_x - start_x, end_y - start_y); - end_x = current_x; - end_y = current_y; - // Draw new rectangle - XDrawRectangle(x11.dpy, x11.root, gc, start_x, start_y, end_x - start_x, end_y - start_y); - XFlush(x11.dpy); - } - break; - case ButtonRelease: - if (ev.xbutton.button == Button1) { - end_x = ev.xbutton.x_root; - end_y = ev.xbutton.y_root; - // Erase the rectangle - XDrawRectangle(x11.dpy, x11.root, gc, start_x, start_y, end_x - start_x, end_y - start_y); - XFlush(x11.dpy); - XUngrabPointer(x11.dpy, CurrentTime); - goto selection_done; - } - break; - case KeyPress: - if (XLookupKeysym(&ev.xkey, 0) == XK_Escape) { - XUngrabPointer(x11.dpy, CurrentTime); - XCloseDisplay(x11.dpy); - exit(1); - } - break; - } - } -selection_done: - XFreeGC(x11.dpy, gc); - XFreeCursor(x11.dpy, cursor); - - opt.x = (start_x < end_x) ? start_x : end_x; - opt.y = (start_y < end_y) ? start_y : end_y; - opt.w = abs(end_x - start_x); - opt.h = abs(end_y - start_y); - - if (opt.w == 0 || opt.h == 0) { - fatal(opt.eout, S("selection has zero size")); - } - - // Output handling - if (opt.output_filename == NULL) { - opt.out[0] = out_create(1, out_buffer, out_buffer + ARRLEN(out_buffer)); - } else { - int flags = O_WRONLY | O_CREAT | O_EXCL; - if (opt.overwrite_file) { - flags &= ~O_EXCL; - flags |= O_TRUNC; - } - int fd = open(opt.output_filename, flags, 0600); - if (fd < 0) { - fatal(opt.eout, S("couldn't open output file: "), str_from_cstr(strerror(errno))); - } - opt.out[0] = out_create(fd, out_buffer, out_buffer + ARRLEN(out_buffer)); - } - - if (isatty(opt.out->fd)) { - fatal(opt.eout, S("aborting: output is connected to a terminal")); - } - - // Capture the image - XImage *im = XGetImage( - x11.dpy, x11.root, opt.x, opt.y, opt.w, opt.h, - AllPlanes, ZPixmap - ); - if (im == NULL) { - fatal(opt.eout, S("XGetImage failed")); - } - if (im->bits_per_pixel != 32 || im->bytes_per_line != (im->width * 4) || - !(im->depth == 24 || im->depth == 32)) { - fatal(opt.eout, S("unsupported XImage format")); - } - - // Blend cursor if requested - if (opt.capture_cursor) { - if (XFixesQueryVersion(x11.dpy, (int[]){0}, (int[]){0}) != True) { - fatal(opt.eout, S("XFixesQueryVersion failed")); - } - XFixesCursorImage *cur = XFixesGetCursorImage(x11.dpy); - if (cur != NULL) { - blend_cursor(im, cur); - XFree(cur); + XNextEvent(disp, &ev); + if (ev.type == ButtonPress) { + start_x = ev.xbutton.x; + start_y = ev.xbutton.y; + selecting = 1; + } else if (ev.type == MotionNotify && selecting) { + end_x = ev.xmotion.x; + end_y = ev.xmotion.y; + } else if (ev.type == ButtonRelease) { + break; } } - // Write PPM - write_ppm((u8 *)im->data, im->width, im->height, im->byte_order == MSBFirst, opt.out); + XUngrabPointer(disp, CurrentTime); + XFreeCursor(disp, cursor); - out_flush(opt.out); - if (opt.out->err) { - // not permanent, since we will turn this whole thing into a function - // which will return the PPM as a bytestring (raw bytes) + // Calculate region bounds + int x = start_x < end_x ? start_x : end_x; + int y = start_y < end_y ? start_y : end_y; + int width = abs(end_x - start_x); + int height = abs(end_y - start_y); - // stb_image.h can load PPM and we can load from memory by using: - // stbi_load_from_memory , which will receive the output of our screenshot function directly. - fatal(opt.eout, S("error writing to output")); + // Capture pixels + XImage* ximg = XGetImage(disp, root, x, y, width, height, AllPlanes, ZPixmap); + if (!ximg) { + XCloseDisplay(disp); + return buf; } - XDestroyImage(im); - XCloseDisplay(x11.dpy); + // Create PPM buffer + const char header[64]; + int header_len = snprintf((char*)header, sizeof(header), + "P6\n%d %d\n255\n", width, height); + + buf.width = width; + buf.height = height; + buf.size = header_len + (width * height * 3); + buf.pixels = malloc(buf.size); + + // Copy header + memcpy(buf.pixels, header, header_len); + + // Convert XImage to RGB24 + uint8_t* pixel_ptr = buf.pixels + header_len; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + unsigned long pixel = XGetPixel(ximg, x, y); + *pixel_ptr++ = (pixel >> 16) & 0xFF; // R + *pixel_ptr++ = (pixel >> 8) & 0xFF; // G + *pixel_ptr++ = pixel & 0xFF; // B + } + } + // Cleanup + XDestroyImage(ximg); + XCloseDisplay(disp); + return buf; +} + +int main() { + PPMBuffer ppm = capture_ppm_region(); + + if (ppm.pixels) { + // Example: Write to file (could pass to another function) + FILE* fp = fopen("screenshot.ppm", "wb"); + fwrite(ppm.pixels, 1, ppm.size, fp); + fclose(fp); + + // To simulate stbi_load_from_memory usage: + // uint8_t* rgb = ppm.pixels + snprintf(...); + // int w = ppm.width, h = ppm.height; + // Your processing here... + + free(ppm.pixels); + } + return 0; } -- 2.39.5