usrlib.de / CVS

Revision 1.23 of bgset/bgset.c

/*
 * bgset
 * Copyright (c) 2016, 2017 Lukas Hofmann <lhofmann@fsfe.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xinerama.h>
#include <Imlib2.h>


enum bgmode {
	BGM_CENTER,  /* centered, original size */
	BGM_TILE,    /* tiled, original size */
	BGM_SCALE,   /* scaled to fit screen with borders */
	BGM_FILL     /* scaled and cropped to fill screen */
};


struct scrbg {
	int mode;
	int xinerama;
	char *filename;
	struct scrbg *next;
};


int draw_image(Display *, int, Pixmap, char *, enum bgmode, int, int, int, int);
Pixmap init_root_pixmap(Display *, int);
void update_root_bg(Display *, Pixmap);
void x_init(Display **, int *, XineramaScreenInfo **, int *);
void x_teardown(Display *, XineramaScreenInfo *);
void scrbg_free(struct scrbg *);


int
draw_image(Display *dpy, int scr, Pixmap pxm, char *filename,
    enum bgmode mode, int scr_x, int scr_y, int scr_w, int scr_h) {
	int orig_w, orig_h, prep_x, prep_y, scr_scl_w, scr_scl_h;
	int x, y;
	double orig_ratio, scr_ratio;
	Imlib_Image orig, prep;

	if (!(orig = imlib_load_image(filename))) {
		return 0;
	}

	/* init imlib */
	imlib_context_set_display(dpy);
	imlib_context_set_drawable(pxm);
	imlib_context_set_visual(DefaultVisual(dpy, scr));
	imlib_context_set_colormap(DefaultColormap(dpy, scr));

	imlib_context_set_image(orig);
	orig_w = imlib_image_get_width();
	orig_h = imlib_image_get_height();

	/* prepare image */
	switch (mode) {
	case BGM_CENTER:
		prep_x = (orig_w - scr_w) / 2;
		prep_y = (orig_h - scr_h) / 2;
		prep = imlib_create_cropped_image(prep_x, prep_y, scr_w, scr_h);
		break;
	case BGM_TILE:
		prep = imlib_create_image(scr_w, scr_h);
		imlib_context_set_image(prep);
		for (x = 0; x < scr_w; x += orig_w) {
			for (y = 0; y < scr_h; y += orig_h) {
				imlib_blend_image_onto_image(orig, 1, 0, 0,
				    orig_w, orig_h, x, y, orig_w, orig_h);
			}
		}
		imlib_context_set_image(orig);
		break;
	case BGM_SCALE:
	case BGM_FILL:
		orig_ratio = (double) orig_w / orig_h;
		scr_ratio = (double) scr_w / scr_h;
		if (mode == BGM_SCALE && orig_ratio >= scr_ratio ||
		    mode == BGM_FILL && orig_ratio < scr_ratio) {
			scr_scl_w = orig_w;
			scr_scl_h = orig_w / scr_ratio;
		} else {
			scr_scl_h = orig_h;
			scr_scl_w = orig_h * scr_ratio;
		}
		prep_x = (orig_w - scr_scl_w) / 2;
		prep_y = (orig_h - scr_scl_h) / 2;
		prep = imlib_create_cropped_scaled_image(prep_x, prep_y,
		    scr_scl_w, scr_scl_h, scr_w, scr_h);
		break;
	}

	imlib_free_image();
	imlib_context_set_image(prep);

	imlib_render_image_on_drawable(scr_x, scr_y);
	imlib_free_image();

	return 1;
}

Pixmap
init_root_pixmap(Display *dpy, int scr) {
	int format;
	unsigned int dpy_w, dpy_h;
	unsigned int px_x, px_y, px_w, px_h, px_border, px_depth;
	unsigned char *data;
	unsigned long nitems, bytes_after;
	Atom pxm_prop, type;
	Window root;
	Pixmap pxm;
	GC gc;
	XGCValues gc_val = { .background = 0 };

	/* get current background pixmap */
	root = DefaultRootWindow(dpy);
	dpy_w = DisplayWidth(dpy, scr);
	dpy_h = DisplayHeight(dpy, scr);
	pxm_prop = XInternAtom(dpy, "_XROOTPMAP_ID", False);
	XGetWindowProperty(dpy, root, pxm_prop, 0L, 1L, False,
	    AnyPropertyType, &type, &format, &nitems, &bytes_after, &data);
	if (data) {
		if (type != XA_PIXMAP) {
			warnx("_XROOTPMAP_ID is not a pixmap.");
			XFree(data);
			return 0;
		}
		pxm = *((Pixmap *) data);
		XFree(data);
		/* reset if screen geometry has changed */
		XGetGeometry(dpy, pxm, &root, &px_x, &px_y, &px_w, &px_h,
		    &px_border, &px_depth);
		if (px_w != dpy_w || px_h != dpy_h) {
			warnx("Screen geometry has changed. Clearing"
			    " background.");
			XFreePixmap(dpy, pxm);
			data = NULL;
		}
	}
	/* initialize pixmap if none has been found or the previous one has been
	   cleared */
	if (!data) {
		pxm = XCreatePixmap(dpy, root, dpy_w, dpy_h,
		    DefaultDepth(dpy, scr));
		gc = XCreateGC(dpy, pxm, (1L<<3), &gc_val);
		XDrawRectangle(dpy, pxm, gc, 0, 0, dpy_w, dpy_h);
		XFreeGC(dpy, gc);
	}

	XChangeProperty(dpy, root, pxm_prop, XA_PIXMAP, 32, PropModeReplace,
	    (unsigned char *) &pxm, 1);
	return pxm;
}

void
update_root_bg(Display *dpy, Pixmap pxm) {
	Window root;

	root = DefaultRootWindow(dpy);
	XSetWindowBackgroundPixmap(dpy, root, pxm);
	XClearWindow(dpy, root);
	XFlush(dpy);
}

void
x_init(Display **dpy, int *scr, XineramaScreenInfo **info, int *scrnum) {
	if (!(*dpy = XOpenDisplay(NULL))) {
		errx(1, "error opening display");
	}
	*scr = DefaultScreen(*dpy);
	*info = XineramaQueryScreens(*dpy, scrnum);
	XSetCloseDownMode(*dpy, RetainPermanent);
}

void
x_teardown(Display *dpy, XineramaScreenInfo *info) {
	if (info) {
		XFree(info);
	}
	XCloseDisplay(dpy);
}

void scrbg_free(struct scrbg *scr) {
	if (scr) {
		scrbg_free(scr->next);
		free(scr);
	}
}

int
main(int argc, char *argv[]) {
	int scr, scrnum, i = 1;
	int x, y, w, h;
	int ret = 0;
	char opt = 'f';
	enum bgmode mode;
	Display *dpy;
	Pixmap pxm;
	XineramaScreenInfo *info;
	struct scrbg bg_default = {
		.mode = BGM_FILL,
		.xinerama = 1,
		.next = NULL
	};
	struct scrbg *bg = &bg_default;

	if (argc < 2) {
		goto usage;
	}

	/* parse command line */
	while (i < argc) {
		if (!(bg->next = malloc(sizeof(struct scrbg)))) {
			ret = 2;
			goto finish;
		}
		bg->next->mode = bg->mode;
		bg->next->xinerama = bg->xinerama;
		bg = bg->next;
		if (argv[i][0] == '-') {
			if (argv[i][1] != '\0' && argv[i][2] == '\0') {
				switch (argv[i][1]) {
				case 'c':
				case 'C':
					bg->mode = BGM_CENTER;
					break;
				case 't':
				case 'T':
					bg->mode = BGM_TILE;
					break;
				case 's':
				case 'S':
					bg->mode = BGM_SCALE;
					break;
				case 'f':
				case 'F':
					bg->mode = BGM_FILL;
					break;
				case 'n':
					bg->filename = NULL;
					i++;
					continue;
				default:
					goto usage;
				}
				bg->xinerama = argv[i][1] >= 'a';
			} else {
				goto usage;
			}
			i++;
			if (i >= argc) {
				goto usage;
			}
		}
		bg->filename = argv[i];
		i++;
	}

	/* initialize */
	x_init(&dpy, &scr, &info, &scrnum);
	if (!(pxm = init_root_pixmap(dpy, scr))) {
		ret = 2;
		goto finish;
	}

	/* draw images */
	i = 0;
	for (bg = bg_default.next; bg; bg = bg->next) {
		if (info && bg->xinerama) {
			/* Xinerama screen */
			if (i >= scrnum) {
				break;
			}
			x = info[i].x_org;
			y = info[i].y_org;
			w = info[i].width;
			h = info[i].height;
			i++;
		} else {
			/* default screen dimensions: entire display area */
			x = y = 0;
			w = DisplayWidth(dpy, scr);
			h = DisplayHeight(dpy, scr);
		}

		if (bg->filename) {
			if (!(draw_image(dpy, scr, pxm, bg->filename, bg->mode,
			    x, y, w, h))) {
				warn("%s", bg->filename);
			}
		}
	}

	update_root_bg(dpy, pxm);
	x_teardown(dpy, info);
	goto finish;
usage:
	ret = 1;
	fprintf(stderr, "usage: %s -n | -cCfFsStT file [...]\n", argv[0]);
finish:
	scrbg_free(bg_default.next);
	return ret;
}