- wm: hot corner polling via select()-based wm_run loop; TL=Exposé, TR=show desktop, BL=launcher, BR=lock - wm: XF86 media key grabs (volume, brightness, playback) + Super+V for clipboard - wm: autostart reads ~/.config/orionos/autostart on startup - events: media key dispatch (amixer/brightnessctl/playerctl); Super+V → orionclip --show; Print → orionshot - orionshot: new X11 screenshot tool, full-screen and rubber-band region modes, saves PNG to ~/Pictures/Screenshots/ - orionclip: new clipboard manager daemon; polls clipboard, stores 20 entries, popup via Super+V IPC - orionnotify: append-only notification history log at ~/.local/share/orionos/notif-history.log - themes: GTK accent updated from #3A6898 to #1E58A8 to match WM/deskbar - themes: orionwallpaper gains --slideshow DIR [SECS] mode using feh + sleep loop - iso: Docker-based archiso builder, live-session xinitrc, profiledef.sh - Makefile: add orionshot and orionclip to all/clean/install; Docker-based iso target
230 lines
7.9 KiB
C
230 lines
7.9 KiB
C
/*
|
|
* orionshot — X11 screenshot tool for OrionOS
|
|
* Usage: orionshot full full-screen PNG capture
|
|
* orionshot region interactive rubber-band region select
|
|
*
|
|
* Saves to ~/Pictures/Screenshots/orionos_YYYYMMDD_HHMMSS.png
|
|
* Sends a desktop notification on success.
|
|
*/
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/cursorfont.h>
|
|
#include <png.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
|
|
#ifndef MIN
|
|
#define MIN(a,b) ((a)<(b)?(a):(b))
|
|
#endif
|
|
|
|
/* ── PNG writer ──────────────────────────────────────────────────────── */
|
|
|
|
static int get_shift(unsigned long mask) {
|
|
if (!mask) return 0;
|
|
int s = 0;
|
|
while (!(mask & 1)) { mask >>= 1; s++; }
|
|
return s;
|
|
}
|
|
|
|
static int write_png(const char *path, XImage *img, int ox, int oy, int w, int h) {
|
|
FILE *fp = fopen(path, "wb");
|
|
if (!fp) { perror("orionshot: fopen"); return -1; }
|
|
|
|
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
|
if (!png) { fclose(fp); return -1; }
|
|
png_infop info = png_create_info_struct(png);
|
|
if (!info) { png_destroy_write_struct(&png, NULL); fclose(fp); return -1; }
|
|
if (setjmp(png_jmpbuf(png))) {
|
|
png_destroy_write_struct(&png, &info);
|
|
fclose(fp);
|
|
return -1;
|
|
}
|
|
|
|
png_init_io(png, fp);
|
|
png_set_IHDR(png, info, (unsigned)w, (unsigned)h, 8,
|
|
PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
|
|
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
|
png_write_info(png, info);
|
|
|
|
int r_shift = get_shift(img->red_mask);
|
|
int g_shift = get_shift(img->green_mask);
|
|
int b_shift = get_shift(img->blue_mask);
|
|
int r_scale = (img->red_mask >> r_shift) < 0xFF ? 2 : 1;
|
|
int g_scale = (img->green_mask >> g_shift) < 0xFF ? 2 : 1;
|
|
int b_scale = (img->blue_mask >> b_shift) < 0xFF ? 2 : 1;
|
|
(void)r_scale; (void)g_scale; (void)b_scale;
|
|
|
|
unsigned char *row = malloc((size_t)w * 3);
|
|
if (!row) { png_destroy_write_struct(&png, &info); fclose(fp); return -1; }
|
|
|
|
for (int j = 0; j < h; j++) {
|
|
for (int i = 0; i < w; i++) {
|
|
unsigned long px = XGetPixel(img, ox + i, oy + j);
|
|
row[i*3+0] = (unsigned char)((px & img->red_mask) >> r_shift);
|
|
row[i*3+1] = (unsigned char)((px & img->green_mask) >> g_shift);
|
|
row[i*3+2] = (unsigned char)((px & img->blue_mask) >> b_shift);
|
|
}
|
|
png_write_row(png, row);
|
|
}
|
|
free(row);
|
|
png_write_end(png, NULL);
|
|
png_destroy_write_struct(&png, &info);
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
/* ── output path ─────────────────────────────────────────────────────── */
|
|
|
|
static void make_save_path(char *buf, size_t bufsz) {
|
|
const char *home = getenv("HOME");
|
|
if (!home) home = "/tmp";
|
|
char dir[512];
|
|
snprintf(dir, sizeof(dir), "%s/Pictures/Screenshots", home);
|
|
mkdir(dir, 0755);
|
|
|
|
time_t t = time(NULL);
|
|
struct tm *tm = localtime(&t);
|
|
char ts[32];
|
|
strftime(ts, sizeof(ts), "%Y%m%d_%H%M%S", tm);
|
|
snprintf(buf, bufsz, "%s/orionos_%s.png", dir, ts);
|
|
}
|
|
|
|
/* ── region selection ────────────────────────────────────────────────── */
|
|
|
|
static int select_region(Display *dpy, Window root,
|
|
int *rx, int *ry, int *rw, int *rh) {
|
|
Cursor cross = XCreateFontCursor(dpy, XC_crosshair);
|
|
if (XGrabPointer(dpy, root, False,
|
|
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
|
|
GrabModeAsync, GrabModeAsync,
|
|
root, cross, CurrentTime) != GrabSuccess) {
|
|
XFreeCursor(dpy, cross);
|
|
return -1;
|
|
}
|
|
XGrabKeyboard(dpy, root, False, GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
|
|
GC gc;
|
|
XGCValues gcv;
|
|
gcv.function = GXxor;
|
|
gcv.foreground = 0xFFFFFF;
|
|
gcv.subwindow_mode = IncludeInferiors;
|
|
gcv.line_width = 1;
|
|
gc = XCreateGC(dpy, root,
|
|
GCFunction | GCForeground | GCSubwindowMode | GCLineWidth, &gcv);
|
|
|
|
int sx = 0, sy = 0, ex = 0, ey = 0;
|
|
int started = 0, drawn = 0, done = 0, cancelled = 0;
|
|
|
|
while (!done) {
|
|
XEvent e;
|
|
XNextEvent(dpy, &e);
|
|
|
|
if (e.type == ButtonPress && e.xbutton.button == Button1) {
|
|
sx = e.xbutton.x_root; sy = e.xbutton.y_root;
|
|
ex = sx; ey = sy; started = 1;
|
|
|
|
} else if (e.type == MotionNotify && started) {
|
|
/* erase previous rubber band */
|
|
if (drawn) {
|
|
int dx = MIN(sx,ex), dy = MIN(sy,ey);
|
|
int dw = abs(ex-sx), dh = abs(ey-sy);
|
|
if (dw > 0 && dh > 0)
|
|
XDrawRectangle(dpy, root, gc, dx, dy, (unsigned)dw, (unsigned)dh);
|
|
XFlush(dpy); drawn = 0;
|
|
}
|
|
ex = e.xmotion.x_root; ey = e.xmotion.y_root;
|
|
{
|
|
int dx = MIN(sx,ex), dy = MIN(sy,ey);
|
|
int dw = abs(ex-sx), dh = abs(ey-sy);
|
|
if (dw > 0 && dh > 0) {
|
|
XDrawRectangle(dpy, root, gc, dx, dy, (unsigned)dw, (unsigned)dh);
|
|
XFlush(dpy); drawn = 1;
|
|
}
|
|
}
|
|
|
|
} else if (e.type == ButtonRelease && e.xbutton.button == Button1) {
|
|
if (drawn) {
|
|
int dx = MIN(sx,ex), dy = MIN(sy,ey);
|
|
int dw = abs(ex-sx), dh = abs(ey-sy);
|
|
if (dw > 0 && dh > 0)
|
|
XDrawRectangle(dpy, root, gc, dx, dy, (unsigned)dw, (unsigned)dh);
|
|
XFlush(dpy);
|
|
}
|
|
ex = e.xbutton.x_root; ey = e.xbutton.y_root;
|
|
done = 1;
|
|
|
|
} else if (e.type == KeyPress) {
|
|
cancelled = 1; done = 1;
|
|
}
|
|
}
|
|
|
|
XUngrabPointer(dpy, CurrentTime);
|
|
XUngrabKeyboard(dpy, CurrentTime);
|
|
XFreeGC(dpy, gc);
|
|
XFreeCursor(dpy, cross);
|
|
XSync(dpy, False);
|
|
|
|
if (cancelled) return -1;
|
|
|
|
*rx = MIN(sx,ex); *ry = MIN(sy,ey);
|
|
*rw = abs(ex-sx); *rh = abs(ey-sy);
|
|
if (*rw < 2 || *rh < 2) return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* ── main ────────────────────────────────────────────────────────────── */
|
|
|
|
int main(int argc, char *argv[]) {
|
|
int do_region = (argc > 1 && strcmp(argv[1], "region") == 0);
|
|
|
|
Display *dpy = XOpenDisplay(NULL);
|
|
if (!dpy) { fprintf(stderr, "orionshot: cannot open display\n"); return 1; }
|
|
int screen = DefaultScreen(dpy);
|
|
Window root = RootWindow(dpy, screen);
|
|
int sw = DisplayWidth(dpy, screen);
|
|
int sh = DisplayHeight(dpy, screen);
|
|
|
|
int cx = 0, cy = 0, cw = sw, ch = sh;
|
|
|
|
if (do_region) {
|
|
if (select_region(dpy, root, &cx, &cy, &cw, &ch) < 0) {
|
|
XCloseDisplay(dpy);
|
|
return 0; /* cancelled — silently exit */
|
|
}
|
|
/* tiny pause so rubber-band XOR erasure is fully flushed */
|
|
usleep(60000);
|
|
}
|
|
|
|
XImage *img = XGetImage(dpy, root, cx, cy, (unsigned)cw, (unsigned)ch,
|
|
AllPlanes, ZPixmap);
|
|
XCloseDisplay(dpy);
|
|
|
|
if (!img) { fprintf(stderr, "orionshot: XGetImage failed\n"); return 1; }
|
|
|
|
char path[1024];
|
|
make_save_path(path, sizeof(path));
|
|
|
|
if (write_png(path, img, 0, 0, cw, ch) < 0) {
|
|
XDestroyImage(img);
|
|
fprintf(stderr, "orionshot: failed to write %s\n", path);
|
|
return 1;
|
|
}
|
|
XDestroyImage(img);
|
|
|
|
/* extract just the filename for the notification body */
|
|
const char *fname = strrchr(path, '/');
|
|
fname = fname ? fname + 1 : path;
|
|
|
|
char notif[512];
|
|
snprintf(notif, sizeof(notif),
|
|
"notify-send -t 3000 'Screenshot' '%s' 2>/dev/null &", fname);
|
|
system(notif);
|
|
|
|
fprintf(stderr, "orionshot: saved %s\n", path);
|
|
return 0;
|
|
}
|