beh

X11 image viewer
git clone https://git.porkepik.fr/beh
Log | Files | Refs | README | LICENSE

beh.c (9094B)


      1 /*
      2    TODO
      3    - handle archives
      4    - set window title
      5    - display filename, res, file size
      6 */
      7 
      8 #include <dirent.h>
      9 #include <err.h>
     10 #include <stdio.h>
     11 #include <stdlib.h>
     12 #include <string.h>
     13 #include <sys/stat.h>
     14 
     15 #include <X11/Xlib.h>
     16 #include <X11/Xutil.h>
     17 #include <X11/XKBlib.h>
     18 
     19 #define STB_IMAGE_IMPLEMENTATION
     20 #include "stb_image.h"
     21 
     22 #define STB_IMAGE_RESIZE_IMPLEMENTATION
     23 #include "stb_image_resize.h"
     24 
     25 #define RGBA 4
     26 
     27 
     28 enum zoom {
     29 	NO_ZOOM,
     30 	FILL,
     31 	MAX
     32 };
     33 
     34 struct app {
     35 	Window win;
     36 	int width;
     37 	int height;
     38 	char *bg_color;
     39 };
     40 
     41 struct image {
     42 	int filepath;
     43 	int width;
     44 	int height;
     45 	int dpy_width;
     46 	int dpy_height;
     47 	unsigned char *data;
     48 	unsigned char *dpy_data;
     49 	int src_x;
     50 	int src_y;
     51 	int dest_x;
     52 	int dest_y;
     53 	enum zoom zoom_mode;
     54 };
     55 
     56 
     57 static Display *dpy;
     58 static struct app app;
     59 static char **filepaths;
     60 static int nimgs;
     61 static struct image img;
     62 
     63 
     64 static void
     65 usage(void)
     66 {
     67 	printf("Usage: beh [--bg-color \"#ffffff\"] [FILES]\n");
     68 	exit(0);
     69 }
     70 
     71 static void
     72 check_file(char *path)
     73 {
     74 	struct stat path_stat;
     75 	stat(path, &path_stat);
     76 
     77 	if (S_ISREG(path_stat.st_mode)) {
     78 		filepaths = realloc(filepaths, sizeof(char *) * nimgs+1);
     79 		filepaths[nimgs++] = path;
     80 	} else if (S_ISDIR(path_stat.st_mode)) {
     81 		struct dirent *dir;
     82 		DIR *d;
     83 		if (!(d = opendir(path))) {
     84 			printf("Could not open directory: %s\n", path);
     85 			return;
     86 		}
     87 		while ((dir = readdir(d))) {
     88 			char *filepath = malloc(strlen(path) +
     89 			    strlen(dir->d_name) + 2);
     90 			strcpy(filepath, path);
     91 			strcat(filepath, "/");
     92 			strcat(filepath, dir->d_name);
     93 			stat(filepath, &path_stat);
     94 			if (!S_ISREG(path_stat.st_mode))
     95 				continue;
     96 			filepaths = realloc(filepaths, sizeof(char *)*nimgs+1);
     97 			filepaths[nimgs++] = filepath;
     98 		}
     99 		closedir(d);
    100 	}
    101 }
    102 
    103 
    104 static void
    105 check_args(int argc, char **argv)
    106 {
    107 	app.bg_color = NULL;
    108 
    109 	for (int i = 1; i <= argc - 1; i++) {
    110 		if (strcmp(argv[i], "--bg-color") == 0 && i+1 != argc)
    111 			app.bg_color = argv[++i];
    112 		else if (strcmp(argv[i], "-h") == 0)
    113 			usage();
    114 		else
    115 			check_file(argv[i]);
    116 	}
    117 }
    118 
    119 static void
    120 rgb_to_bgr(void)
    121 {
    122 	unsigned char red, blue;
    123 
    124 	for (long i = 0; i < img.width * img.height * RGBA; i += RGBA) {
    125 		red = img.data[i+2];
    126 		blue = img.data[i];
    127 		img.data[i] = red;
    128 		img.data[i+2] = blue;
    129 	}
    130 }
    131 
    132 static int
    133 init_img(char *filepath)
    134 {
    135 	img.data = stbi_load(filepath, &img.width, &img.height, NULL, RGBA);
    136 	if (img.data == NULL) {
    137 		printf("Could not open %s\n", filepath);
    138 		return 1;
    139 	}
    140 	printf("Opened: %s\n", filepath);
    141 	rgb_to_bgr();
    142 
    143 	img.dpy_data = img.data;
    144 	img.dpy_width = img.width;
    145 	img.dpy_height = img.height;
    146 
    147 	return 0;
    148 }
    149 
    150 static void
    151 free_img(void)
    152 {
    153 	if (img.dpy_data != img.data)
    154 		free(img.dpy_data);
    155 	stbi_image_free(img.data);
    156 }
    157 
    158 static unsigned char *
    159 resize(int new_w, int new_h)
    160 {
    161 	unsigned char *new_data = malloc(new_h * new_w * RGBA);
    162 	stbir_resize_uint8(img.data, img.width, img.height, 0,
    163 	    new_data, new_w, new_h, 0, RGBA);
    164 
    165 	return new_data;
    166 }
    167 
    168 static void
    169 set_zoom(enum zoom zoom_mode)
    170 {
    171 	int scaled_w, scaled_h, new_w, new_h, tmp;
    172 
    173 	scaled_w = (float)app.height * ((float)img.width / (float)img.height);
    174 	scaled_h = (float)app.width / ((float)img.width / (float)img.height);
    175 
    176 	if ((zoom_mode == FILL && scaled_w < app.width) ||
    177 	    (zoom_mode == MAX && scaled_h <= app.height)) {
    178 		new_w = app.width;
    179 		new_h = scaled_h;
    180 	} else {
    181 		new_w = scaled_w;
    182 		new_h = app.height;
    183 	}
    184 
    185 	if (img.dpy_data != img.data)
    186 		free(img.dpy_data);
    187 	if (zoom_mode == FILL || zoom_mode == MAX) {
    188 		img.dpy_data = resize(new_w, new_h);
    189 		img.dpy_width = new_w;
    190 		img.dpy_height = new_h;
    191 	} else if (zoom_mode == NO_ZOOM) {
    192 		img.dpy_data = img.data;
    193 		img.dpy_width = img.width;
    194 		img.dpy_height = img.height;
    195 	}
    196 
    197 	if (zoom_mode == FILL) {
    198 		img.src_x = (img.dpy_width - app.width) / 2;
    199 		img.src_y = (img.dpy_height - app.height) / 2;
    200 		img.dest_x = 0;
    201 		img.dest_y = 0;
    202 	} else if (zoom_mode == MAX) {
    203 		img.dest_x = (app.width - img.dpy_width) / 2;
    204 		img.dest_y = (app.height - img.dpy_height) / 2;
    205 		img.src_x = 0;
    206 		img.src_y = 0;
    207 	} else if (zoom_mode == NO_ZOOM) {
    208 		tmp = (img.dpy_width - app.width) / 2;
    209 		img.src_x = tmp < 0 ? 0 : tmp;
    210 		tmp = (img.dpy_height - app.height) / 2;
    211 		img.src_y = tmp < 0 ? 0 : tmp;
    212 		tmp = (app.width - img.dpy_width) / 2;
    213 		img.dest_x = tmp < 0 ? 0 : tmp;
    214 		tmp = (app.height - img.dpy_height) / 2;
    215 		img.dest_y = tmp < 0 ? 0 : tmp;
    216 	}
    217 }
    218 
    219 static Pixmap
    220 create_pixmap(void)
    221 {
    222 	XColor color;
    223 	GC gc;
    224 	XGCValues gcval;
    225 
    226 	Pixmap pixmap = XCreatePixmap(dpy, app.win, app.width, app.height,
    227 	    DefaultDepth(dpy, 0));
    228 	Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy));
    229 
    230 	if (app.bg_color == NULL)
    231 		XAllocNamedColor(dpy, cmap, "black", &color, &color);
    232 	else
    233 		XAllocNamedColor(dpy, cmap, app.bg_color, &color, &color);
    234 	gcval.foreground = color.pixel;
    235 	gc = XCreateGC(dpy, app.win, GCForeground, &gcval);
    236 	XFillRectangle(dpy, pixmap, gc, 0, 0, app.width, app.height);
    237 
    238 	XFreeGC(dpy, gc);
    239 	
    240 	return pixmap;
    241 }
    242 
    243 static void
    244 apply_image(Pixmap pixmap)
    245 {
    246 	XImage *ximg = XCreateImage(dpy, DefaultVisual(dpy, 0), 24, ZPixmap, 0,
    247 	    (char *)img.dpy_data, img.dpy_width, img.dpy_height, 8, 0);
    248 
    249 	XSetWindowBackgroundPixmap(dpy, app.win, pixmap);
    250 	XPutImage(dpy, pixmap, DefaultGC(dpy, 0), ximg, img.src_x, img.src_y,
    251 	    img.dest_x, img.dest_y, img.dpy_width, img.dpy_height);
    252 	XClearWindow(dpy, app.win);
    253 
    254 	XFree(ximg);
    255 }
    256 
    257 static void
    258 event_loop()
    259 {
    260 	Pixmap pixmap;
    261 	XEvent ev;
    262 	KeySym keysym;
    263 	XConfigureEvent xce;
    264 	int tmp_x, tmp_y, left_mouse_hold, hold_x, hold_y;
    265 	int imgid, status;
    266 	char gotobuf[10];
    267 
    268 	memset(gotobuf, 0, 10);
    269 	left_mouse_hold = imgid = 0;
    270 
    271 	set_zoom(NO_ZOOM);
    272 	pixmap = create_pixmap();
    273 	apply_image(pixmap);
    274 
    275 	while (1) {
    276 		XNextEvent(dpy, &ev);
    277 		switch (ev.type) {
    278 		case ConfigureNotify:
    279 			xce = ev.xconfigure;
    280 			if (xce.width != app.width ||
    281 			    xce.height != app.height) {
    282 				app.width = xce.width;
    283 				app.height = xce.height;
    284 				set_zoom(NO_ZOOM);
    285 				pixmap = create_pixmap();
    286 				apply_image(pixmap);
    287 			}
    288 			break;
    289 		case KeyPress:
    290 			keysym = XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0,
    291 			    ev.xkey.state & ShiftMask ? 1 : 0);
    292 			if (keysym == XK_q || keysym == XK_Escape) {
    293 				goto quit;
    294 			} else if (keysym >= XK_0 && keysym <= XK_9) {
    295 				if (gotobuf[9] == '\0') {
    296 					char tmp[2];
    297 					sprintf(tmp, "%lu", keysym - 48);
    298 					strcat(gotobuf, tmp);
    299 				}
    300 			} else if (keysym == XK_g) {
    301 				int newid = atoi(gotobuf) - 1;
    302 				memset(gotobuf, 0, 10);
    303 				if (newid < 0 || newid > nimgs - 1)
    304 					break;
    305 				free_img();
    306 				if (init_img(filepaths[newid]) != 0)
    307 					init_img(filepaths[imgid]);
    308 				imgid = newid;
    309 				set_zoom(NO_ZOOM);
    310 			} else if (keysym == XK_m) {
    311 				set_zoom(MAX);
    312 			} else if (keysym == XK_l) {
    313 				set_zoom(FILL);
    314 			} else if (keysym == XK_o) {
    315 				set_zoom(NO_ZOOM);
    316 			} else if (keysym == XK_p) {
    317 				if (imgid == 0)
    318 					break;
    319 				free_img();
    320 				do {
    321 					imgid--;
    322 					status = init_img(filepaths[imgid]);
    323 					set_zoom(NO_ZOOM);
    324 				} while (status != 0 && imgid > 0);
    325 				if (status != 0)
    326 					goto quit;
    327 			} else if (keysym == XK_n) {
    328 				if (imgid == nimgs - 1)
    329 					break;
    330 				free_img();
    331 				do {
    332 					imgid++;
    333 					status = init_img(filepaths[imgid]);
    334 					set_zoom(NO_ZOOM);
    335 				} while (status != 0 && imgid < nimgs - 1);
    336 				if (status != 0)
    337 					goto quit;
    338 			} else {
    339 				break;
    340 			}
    341 			pixmap = create_pixmap();
    342 			apply_image(pixmap);
    343 			break;
    344 		case ButtonPress:
    345 			if (ev.xbutton.button == 1) {
    346 				left_mouse_hold = 1;
    347 				hold_x = ev.xmotion.x;
    348 				hold_y = ev.xmotion.y;
    349 			}
    350 			break;
    351 		case ButtonRelease:
    352 			if (ev.xbutton.button == 1)
    353 				left_mouse_hold = 0;
    354 		case MotionNotify:
    355 			if (!left_mouse_hold)
    356 				break;
    357 			tmp_x = img.src_x - ev.xmotion.x + hold_x;
    358 			tmp_y = img.src_y - ev.xmotion.y + hold_y;
    359 
    360 			if (img.dpy_width > app.width) {
    361 				if (tmp_x < 0)
    362 					tmp_x = 0;
    363 				if (app.width + tmp_x > img.dpy_width)
    364 					tmp_x = img.dpy_width - app.width;
    365 				img.src_x = tmp_x;
    366 			}
    367 			if (img.dpy_height > app.height) {
    368 				if (tmp_y < 0)
    369 					tmp_y = 0;
    370 				if (app.height + tmp_y > img.dpy_height)
    371 					tmp_y = img.dpy_height - app.height;
    372 
    373 				img.src_y = tmp_y;
    374 			}
    375 			if (img.src_x == tmp_x || img.src_y == tmp_y)
    376 				apply_image(pixmap);
    377 
    378 			hold_x = ev.xmotion.x;
    379 			hold_y = ev.xmotion.y;
    380 			break;
    381 		}
    382 	}
    383 quit:
    384 	XFreePixmap(dpy, pixmap);
    385 }
    386 
    387 int
    388 main(int argc, char **argv)
    389 {
    390 	int screen, status, i;
    391 
    392 	dpy = XOpenDisplay(NULL);
    393 	if (dpy == NULL)
    394 		errx(1, "Failed opening DISPLAY.");
    395 
    396 	if (argc < 2)
    397 		usage();
    398 	check_args(argc, argv);
    399 
    400 	for (i = 0; i < nimgs - 1; i++) {
    401 		status = init_img(filepaths[i]);
    402 		if (status == 0)
    403 			break;
    404 	}
    405 	if (status != 0)
    406 		return 0;
    407 	app.width = img.width;
    408 	app.height = img.height;
    409 
    410 	screen = DefaultScreen(dpy);
    411 	app.win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen), 0, 0,
    412 	    app.width, app.height, 0, 0, WhitePixel(dpy, screen));
    413 	XSelectInput(dpy, app.win, ExposureMask | KeyPressMask |
    414 	    ButtonPressMask | StructureNotifyMask | PointerMotionMask |
    415 	    ButtonReleaseMask);
    416 	XMapRaised(dpy, app.win);
    417 
    418 	event_loop();
    419 
    420 	XDestroyWindow(dpy, app.win);
    421 	XCloseDisplay(dpy);
    422 	free_img();
    423 	free(filepaths);
    424 
    425 	return 0;
    426 }