xscreen

X11 screenshot utility
git clone https://git.porkepik.fr/xscreen
Log | Files | Refs | README | LICENSE

xscreen.c (7945B)


      1 #include <err.h>
      2 #include <stdbool.h>
      3 #include <stdio.h>
      4 #include <stdlib.h>
      5 #include <string.h>
      6 #include <time.h>
      7 #include <unistd.h>
      8 
      9 #include <X11/Xlib.h>
     10 #include <X11/Xutil.h>
     11 
     12 #define STB_IMAGE_WRITE_IMPLEMENTATION
     13 #include "stb_image_write.h"
     14 
     15 
     16 enum {
     17 	ROOT,
     18 	FOCUSED,
     19 	RECTANGLE
     20 };
     21 
     22 enum {
     23 	PNG,
     24 	JPEG
     25 };
     26 
     27 struct screenshot {
     28 	char *filename;
     29 	int width;
     30 	int height;
     31 	int format;
     32 	int jpg_quality;
     33 	int window_type;
     34 	bool freeze;
     35 	int delay;
     36 	char *data;
     37 };
     38 
     39 
     40 static Display *disp;
     41 
     42 
     43 static int
     44 strend(const char *s, const char *t)
     45 {
     46 	size_t ns = strlen(s), nt = strlen(t);
     47 	return nt <= ns && strcmp(s + ns - nt, t) == 0;
     48 }
     49 
     50 static void
     51 display_help(void)
     52 {
     53 	printf("Usage: By default, takes a screenshot of the root window\n "
     54 	    "      and saves it in a PNG file in the current directory.\n\n"
     55 	    "    -u           Focused window.\n"
     56 	    "    -s           Rectangle selection.\n"
     57 	    "    -z           Freeze display during rectangle selection.\n"
     58 	    "    -j [quality] Save in a JPG, quality from 1 to 100.\n"
     59 	    "    -d [delay]   Delay in seconds.\n"
     60 	    "    -f [path]    Path + optional filename location.\n");
     61 	exit(EXIT_SUCCESS);
     62 }
     63 
     64 static void
     65 check_args(int argc, char **argv, struct screenshot *shot)
     66 {
     67 	int opt;
     68 	while ((opt = getopt(argc, argv, "f:d:j:husz")) != -1) {
     69 		switch (opt) {
     70 		case 'f':
     71 			shot->filename = malloc(strlen(optarg) + 1);
     72 			if (shot->filename == NULL)
     73 				errx(1, "malloc failure");
     74 			strcpy(shot->filename, optarg);
     75 			break;
     76 		case 'd':
     77 			shot->delay = strtol(optarg, NULL, 0);
     78 			if (shot->delay < 0)
     79 				shot->delay = 0;
     80 			break;
     81 		case 'h':
     82 			display_help();
     83 			break;
     84 		case 'u':
     85 			shot->window_type = FOCUSED;
     86 			break;
     87 		case 's':
     88 			shot->window_type = RECTANGLE;
     89 			break;
     90 		case 'z':
     91 			shot->freeze = true;
     92 			break;
     93 		case 'j':
     94 			shot->format = JPEG;
     95 			shot->jpg_quality = strtol(optarg, NULL, 0);
     96 			if (shot->jpg_quality < 1)
     97 				shot->jpg_quality = 1;
     98 			else if (shot->jpg_quality > 100)
     99 				shot->jpg_quality = 100;
    100 			break;
    101 		}
    102 	}
    103 }
    104 
    105 static char *
    106 make_default_filename(int format)
    107 {
    108 	size_t len;
    109 	time_t cur_time = time(NULL);
    110 	struct tm *date = localtime(&cur_time);
    111 
    112 	/* get length of formatted string before malloc */
    113 	len = snprintf(NULL, 0, "screenshot_%d-%02d-%02d-%02d-%02d-%02d",
    114 	    date->tm_year + 1900, date->tm_mon + 1, date->tm_mday,
    115 	    date->tm_hour, date->tm_min, date->tm_sec);
    116 
    117 	char *filename = malloc(len + 5);
    118 	if (filename == NULL)
    119 		errx(1, "malloc failure");
    120 
    121 	sprintf(filename, "screenshot_%d-%02d-%02d-%02d-%02d-%02d",
    122 	    date->tm_year + 1900, date->tm_mon + 1, date->tm_mday,
    123 	    date->tm_hour, date->tm_min, date->tm_sec);
    124 
    125 	if (format == PNG)
    126 		strcat(filename, ".png");
    127 	else if (format == JPEG)
    128 		strcat(filename, ".jpg");
    129 
    130 	return filename;
    131 }
    132 
    133 static Window
    134 get_root_window(void)
    135 {
    136 	return DefaultRootWindow(disp);
    137 }
    138 
    139 static Window
    140 get_toplevel_parent(Window win)
    141 {
    142 	Window parent, root, *children;
    143 	unsigned int num_children;
    144 	int status;
    145 
    146 	while (1) {
    147 		status = XQueryTree(disp, win, &root, &parent,
    148 			&children, &num_children);
    149 		if (status == 0)
    150 			errx(1, "XQueryTree error");
    151 		if (children)
    152 			XFree(children);
    153 		if (win == root || parent == root)
    154 			return win;
    155 		else
    156 			win = parent;
    157 	}
    158 }
    159 
    160 static Window
    161 get_focused_window(void)
    162 {
    163 	Window focused_win;
    164 	int revert_to;
    165 
    166 	XGetInputFocus(disp, &focused_win, &revert_to);
    167 	if (focused_win == None)
    168 		errx(1, "No window is being focused.");
    169 	focused_win = get_toplevel_parent(focused_win);
    170 
    171 	return focused_win;
    172 }
    173 
    174 static Window
    175 get_selected_rectangle(bool freeze)
    176 {
    177 	int x = 0, y = 0, rx = 0, ry = 0;
    178 	int width = 0, height = 0, rw = 0, rh = 0;
    179 	XEvent e;
    180 	XGCValues gcval;
    181 	Window root = DefaultRootWindow(disp);
    182 
    183 	XGrabPointer(disp, root, False,
    184 	    ButtonMotionMask | ButtonPressMask | ButtonReleaseMask,
    185 	    GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
    186 
    187 	gcval.foreground = XWhitePixel(disp, 0);
    188 	gcval.function = GXxor;
    189 	gcval.background = XBlackPixel(disp, 0);
    190 	gcval.plane_mask = gcval.background ^ gcval.foreground;
    191 	gcval.subwindow_mode = IncludeInferiors;
    192 
    193 	GC gc = XCreateGC(disp, root,
    194 	    GCFunction | GCForeground | GCBackground | GCSubwindowMode, &gcval);
    195 
    196 	while (1) {
    197 		XNextEvent(disp, &e);
    198 
    199 		switch (e.type) {
    200 		case ButtonPress:
    201 			x = e.xbutton.x;
    202 			y = e.xbutton.y;
    203 			if (freeze)
    204 				XGrabServer(disp);
    205 			break;
    206 		case ButtonRelease:
    207 			width = e.xbutton.x - x;
    208 			height = e.xbutton.y - y;
    209 
    210 			if (width < 0) {
    211 				width *= -1;
    212 				x -= width;
    213 			}
    214 			if (height < 0) {
    215 				height *= -1;
    216 				y -= height;
    217 			}
    218 			break;
    219 		case MotionNotify:
    220 			XDrawRectangle(disp, root, gc, rx, ry, rw, rh);
    221 
    222 			rx = x - 1;
    223 			ry = y - 1;
    224 			rw = e.xmotion.x - rx + 1;
    225 			rh = e.xmotion.y - ry + 1;
    226 
    227 			if (rw < 0) {
    228 				rx += rw;
    229 				rw = 0 - rw;
    230 			}
    231 			if (rh < 0) {
    232 				ry += rh;
    233 				rh = 0 - rh;
    234 			}
    235 
    236 			XDrawRectangle(disp, root, gc, rx, ry, rw, rh);
    237 			XFlush(disp);
    238 			break;
    239 		}
    240 
    241 		if (e.type == ButtonRelease)
    242 			break;
    243 	}
    244 
    245 	XDrawRectangle(disp, root, gc, rx, ry, rw, rh);
    246 	if (freeze)
    247 		XUngrabServer(disp);
    248 	XUngrabPointer(disp, CurrentTime);
    249 	XFreeGC(disp, gc);
    250 	XFlush(disp);
    251 
    252 	return XCreateSimpleWindow(disp, root, x, y, width, height, 0, 0, 0);
    253 }
    254 
    255 static void
    256 delay(int delay_sec)
    257 {
    258 	int i;
    259 	for (i = delay_sec; i > 0; i--) {
    260 		printf("%d... ", i);
    261 		fflush(stdout);
    262 		sleep(1);
    263 	}
    264 	if (delay_sec)
    265 		printf("\n");
    266 }
    267 
    268 static void
    269 capture(Window win, struct screenshot *shot)
    270 {
    271 	int sr, sg;
    272 	XWindowAttributes attr;
    273 	XImage *ximg;
    274 
    275 	XGetWindowAttributes(disp, win, &attr);
    276 
    277 	shot->width = attr.width;
    278 	shot->height = attr.height;
    279 
    280 	if (shot->window_type == RECTANGLE) {
    281 		ximg = XGetImage(disp, DefaultRootWindow(disp), attr.x, attr.y,
    282 		    shot->width, shot->height, AllPlanes, ZPixmap);
    283 	} else {
    284 		ximg = XGetImage(disp, win, 0, 0, shot->width, shot->height,
    285 		    AllPlanes, ZPixmap);
    286 	}
    287 
    288 	shot->data = malloc(shot->width * shot->height * 3);
    289 	if (shot->data == NULL)
    290 		errx(1, "malloc failure");
    291 
    292 	switch (ximg->bits_per_pixel) {
    293 	case 16:
    294 		sr = 11;
    295 		sg = 5;
    296 		break;
    297 	case 24:
    298 	case 32:
    299 		sr = 16;
    300 		sg = 8;
    301 		break;
    302 	default:
    303 		errx(1, "unsupported bpp: %d", ximg->bits_per_pixel);
    304 	}
    305 
    306 	for (int x = 0; x < shot->width; x++) {
    307 		for (int y = 0; y < shot->height ; y++) {
    308 			unsigned long xpix = XGetPixel(ximg, x, y);
    309 			size_t pix_pos = (x + shot->width * y) * 3;
    310 
    311 			shot->data[pix_pos] = (xpix & ximg->red_mask) >> sr;
    312 			shot->data[pix_pos+1] = (xpix & ximg->green_mask) >> sg;
    313 			shot->data[pix_pos+2] = xpix & ximg->blue_mask;
    314 		}
    315 	}
    316 
    317 	XDestroyImage(ximg);
    318 }
    319 
    320 static void
    321 save_image(struct screenshot shot)
    322 {
    323 	if (access(shot.filename, F_OK) == -1) {
    324 		if (shot.format == PNG) {
    325 			stbi_write_png(shot.filename, shot.width,
    326 			    shot.height, 3, shot.data, shot.width * 3);
    327 		} else if (shot.format == JPEG) {
    328 			stbi_write_jpg(shot.filename, shot.width,
    329 			    shot.height, 3, shot.data, shot.jpg_quality);
    330 		}
    331 	} else {
    332 		printf("\"%s\" already exists\n", shot.filename);
    333 	}
    334 }
    335 
    336 int
    337 main(int argc, char **argv)
    338 {
    339 	disp = XOpenDisplay(NULL);
    340 	if (disp == NULL)
    341 		errx(1, "Failed opening DISPLAY.");
    342 	Window win;
    343 
    344 	struct screenshot shot;
    345 	shot.format = PNG;
    346 	shot.jpg_quality = 100;
    347 	shot.window_type = ROOT;
    348 	shot.delay = 0;
    349 	shot.freeze = false;
    350 	shot.filename = NULL;
    351 
    352 	check_args(argc, argv, &shot);
    353 	if (shot.filename == NULL) {
    354 		shot.filename = make_default_filename(shot.format);
    355 	} else if (strend(shot.filename, "/")) {
    356 		char *temp = make_default_filename(shot.format);
    357 		shot.filename = realloc(shot.filename,
    358 			strlen(shot.filename) + strlen(temp) + 1);
    359 		strcat(shot.filename, temp);
    360 		free(temp);
    361 	}
    362 
    363 	if (shot.window_type == RECTANGLE) {
    364 		win = get_selected_rectangle(shot.freeze);
    365 		delay(shot.delay);
    366 	} else if (shot.window_type == FOCUSED) {
    367 		delay(shot.delay);
    368 		win = get_focused_window();
    369 	} else {
    370 		delay(shot.delay);
    371 		win = get_root_window();
    372 	}
    373 
    374 	capture(win, &shot);
    375 	save_image(shot);
    376 
    377 	free(shot.filename);
    378 	free(shot.data);
    379 	XCloseDisplay(disp);
    380 
    381 	return EXIT_SUCCESS;
    382 }