swm

sigma window manager
git clone git://wolog.xyz/swm
Log | Files | Refs | README | LICENSE

drw.c (10980B)


      1 /*
      2  * Copyright (C) Raymond Cole <rc@wolog.xyz>
      3  *
      4  * Licensed under the GNU General Public License; either version 3 of
      5  * the License, or (at your option) any later version.  See the LICENSE
      6  * file for details.
      7  *
      8  * Portions of the code originally belong to dwm as developed by Anselm
      9  * R. Garbe et al.  See LICENSE-dwm for the copyright and license details
     10  * of those portions.
     11  */
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 #include <X11/Xlib.h>
     16 #include <X11/Xft/Xft.h>
     17 
     18 #include "drw.h"
     19 #include "util.h"
     20 
     21 #define UTF_INVALID 0xFFFD
     22 #define UTF_SIZ     4
     23 
     24 static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
     25 static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
     26 static const long utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
     27 static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
     28 
     29 static long
     30 utf8decodebyte(const char c, size_t *i)
     31 {
     32 	for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
     33 		if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
     34 			return (unsigned char)c & ~utfmask[*i];
     35 	return 0;
     36 }
     37 
     38 static size_t
     39 utf8validate(long *u, size_t i)
     40 {
     41 	if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
     42 		*u = UTF_INVALID;
     43 	for (i = 1; *u > utfmax[i]; ++i)
     44 		;
     45 	return i;
     46 }
     47 
     48 static size_t
     49 utf8decode(const char *c, long *u, size_t clen)
     50 {
     51 	size_t i, j, len, type;
     52 	long udecoded;
     53 
     54 	*u = UTF_INVALID;
     55 	if (!clen)
     56 		return 0;
     57 	udecoded = utf8decodebyte(c[0], &len);
     58 	if (!BETWEEN(len, 1, UTF_SIZ))
     59 		return 1;
     60 	for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
     61 		udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
     62 		if (type)
     63 			return j;
     64 	}
     65 	if (j < len)
     66 		return 0;
     67 	*u = udecoded;
     68 	utf8validate(u, len);
     69 
     70 	return len;
     71 }
     72 
     73 Drw *
     74 drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
     75 {
     76 	Drw *drw;
     77 
     78 	if (!(drw = malloc(sizeof(*drw))))
     79 		return NULL;
     80 	drw->dpy = dpy;
     81 	drw->screen = screen;
     82 	drw->root = root;
     83 	drw->w = w;
     84 	drw->h = h;
     85 	drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
     86 	drw->gc = XCreateGC(dpy, root, 0, NULL);
     87 	XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
     88 
     89 	return drw;
     90 }
     91 
     92 void
     93 drw_resize(Drw *drw, unsigned int w, unsigned int h)
     94 {
     95 	if (!drw)
     96 		return;
     97 
     98 	drw->w = w;
     99 	drw->h = h;
    100 	if (drw->drawable)
    101 		XFreePixmap(drw->dpy, drw->drawable);
    102 	drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
    103 }
    104 
    105 void
    106 drw_free(Drw *drw)
    107 {
    108 	XFreePixmap(drw->dpy, drw->drawable);
    109 	XFreeGC(drw->dpy, drw->gc);
    110 	drw_fontset_free(drw->fonts);
    111 	free(drw);
    112 }
    113 
    114 /* This function is an implementation detail. Library users should use
    115  * drw_fontset_create instead.
    116  */
    117 static Fnt *
    118 xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
    119 {
    120 	Fnt *font;
    121 	XftFont *xfont = NULL;
    122 	FcPattern *pattern = NULL;
    123 
    124 	if (fontname) {
    125 		if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
    126 			fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
    127 			return NULL;
    128 		}
    129 		if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
    130 			fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
    131 			XftFontClose(drw->dpy, xfont);
    132 			return NULL;
    133 		}
    134 	} else if (fontpattern) {
    135 		if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
    136 			fprintf(stderr, "error, cannot load font from pattern.\n");
    137 			return NULL;
    138 		}
    139 	} else {
    140 		warn("no font specified");
    141 		return NULL;
    142 	}
    143 
    144 	if (!(font = malloc(sizeof(*font)))) {
    145 		warn("malloc:");
    146 		return NULL;
    147 	}
    148 	font->dpy = drw->dpy;
    149 	font->h = xfont->ascent + xfont->descent;
    150 	font->xfont = xfont;
    151 	font->pattern = pattern;
    152 	font->next = NULL;
    153 
    154 	return font;
    155 }
    156 
    157 static void
    158 xfont_free(Fnt *font)
    159 {
    160 	if (!font)
    161 		return;
    162 	if (font->pattern)
    163 		FcPatternDestroy(font->pattern);
    164 	XftFontClose(font->dpy, font->xfont);
    165 	free(font);
    166 }
    167 
    168 Fnt *
    169 drw_fontset_create(Drw* drw, const char *const fonts[], size_t fontcount)
    170 {
    171 	Fnt *cur, *ret = NULL;
    172 	size_t i;
    173 
    174 	if (!drw || !fonts)
    175 		return NULL;
    176 
    177 	for (i = 1; i <= fontcount; i++) {
    178 		if (!(cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
    179 			drw_fontset_free(ret);
    180 			return NULL;
    181 		}
    182 		cur->next = ret;
    183 		ret = cur;
    184 	}
    185 	return (drw->fonts = ret);
    186 }
    187 
    188 void
    189 drw_fontset_free(Fnt *font)
    190 {
    191 	if (font) {
    192 		drw_fontset_free(font->next);
    193 		xfont_free(font);
    194 	}
    195 }
    196 
    197 Clr *
    198 drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
    199 {
    200 	if (!drw || !dest || !clrname)
    201 		return NULL;
    202 
    203 	if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
    204 	                       DefaultColormap(drw->dpy, drw->screen),
    205 	                       clrname, dest)) {
    206 		warn("cannot allocate color `%s'", clrname);
    207 		return NULL;
    208 	}
    209 	return dest;
    210 }
    211 
    212 /* Wrapper to create color schemes. The caller has to call free(3) on the
    213  * returned color scheme when done using it. */
    214 Scm *
    215 drw_scm_create(Drw *drw, const char *fg, const char *bg)
    216 {
    217 	Scm *ret;
    218 
    219 	if (!drw || !fg || !bg)
    220 		return NULL;
    221 
    222 	if (!(ret = malloc(sizeof(*ret)))) {
    223 		warn("malloc:");
    224 		return NULL;
    225 	}
    226 
    227 	if (!drw_clr_create(drw, &ret->fg, fg)
    228 	 || !drw_clr_create(drw, &ret->bg, bg)) {
    229 		free(ret);
    230 		return NULL;
    231 	}
    232 	return ret;
    233 }
    234 
    235 void
    236 drw_setfontset(Drw *drw, Fnt *set)
    237 {
    238 	if (drw)
    239 		drw->fonts = set;
    240 }
    241 
    242 void
    243 drw_setscheme(Drw *drw, Scm *scm)
    244 {
    245 	if (drw)
    246 		drw->scheme = scm;
    247 }
    248 
    249 void
    250 drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
    251 {
    252 	if (!drw || !drw->scheme)
    253 		return;
    254 	XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->bg.pixel : drw->scheme->fg.pixel);
    255 	if (filled)
    256 		XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
    257 	else
    258 		XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
    259 }
    260 
    261 int
    262 drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
    263 {
    264 	int i, ty, ellipsis_x = 0;
    265 	unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len;
    266 	XftDraw *d = NULL;
    267 	Fnt *usedfont, *curfont, *nextfont;
    268 	int utf8strlen, utf8charlen, render = x || y || w || h;
    269 	long utf8codepoint = 0;
    270 	const char *utf8str;
    271 	FcCharSet *fccharset;
    272 	FcPattern *fcpattern;
    273 	FcPattern *match;
    274 	XftResult result;
    275 	int charexists = 0, overflow = 0;
    276 	/* keep track of a couple codepoints for which we have no match. */
    277 	enum { nomatches_len = 64 };
    278 	static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches;
    279 	static unsigned int ellipsis_width = 0;
    280 
    281 	if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
    282 		return 0;
    283 
    284 	if (!render) {
    285 		w = invert ? invert : ~invert;
    286 	} else {
    287 		XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->fg.pixel : drw->scheme->bg.pixel);
    288 		XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
    289 		if (w < lpad) /* avoid underflowing w */
    290 			return x + w;
    291 		d = XftDrawCreate(drw->dpy, drw->drawable,
    292 		                  DefaultVisual(drw->dpy, drw->screen),
    293 		                  DefaultColormap(drw->dpy, drw->screen));
    294 		x += lpad;
    295 		w -= lpad;
    296 	}
    297 
    298 	usedfont = drw->fonts;
    299 	if (!ellipsis_width && render)
    300 		ellipsis_width = drw_fontset_getwidth(drw, "...");
    301 	while (1) {
    302 		ew = ellipsis_len = utf8strlen = 0;
    303 		utf8str = text;
    304 		nextfont = NULL;
    305 		while (*text) {
    306 			utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
    307 			for (curfont = drw->fonts; curfont; curfont = curfont->next) {
    308 				charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
    309 				if (charexists) {
    310 					drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL);
    311 					if (ew + ellipsis_width <= w) {
    312 						/* keep track where the ellipsis still fits */
    313 						ellipsis_x = x + ew;
    314 						ellipsis_w = w - ew;
    315 						ellipsis_len = utf8strlen;
    316 					}
    317 
    318 					if (ew + tmpw > w) {
    319 						overflow = 1;
    320 						/* called from drw_fontset_getwidth_clamp():
    321 						 * it wants the width AFTER the overflow
    322 						 */
    323 						if (!render)
    324 							x += tmpw;
    325 						else
    326 							utf8strlen = ellipsis_len;
    327 					} else if (curfont == usedfont) {
    328 						utf8strlen += utf8charlen;
    329 						text += utf8charlen;
    330 						ew += tmpw;
    331 					} else {
    332 						nextfont = curfont;
    333 					}
    334 					break;
    335 				}
    336 			}
    337 
    338 			if (overflow || !charexists || nextfont)
    339 				break;
    340 			else
    341 				charexists = 0;
    342 		}
    343 
    344 		if (utf8strlen) {
    345 			if (render) {
    346 				ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
    347 				XftDrawStringUtf8(d, invert ? &drw->scheme->bg : &drw->scheme->fg,
    348 				                  usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen);
    349 			}
    350 			x += ew;
    351 			w -= ew;
    352 		}
    353 		if (render && overflow)
    354 			drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert);
    355 
    356 		if (!*text || overflow) {
    357 			break;
    358 		} else if (nextfont) {
    359 			charexists = 0;
    360 			usedfont = nextfont;
    361 		} else {
    362 			/* Regardless of whether or not a fallback font is found, the
    363 			 * character must be drawn.
    364 			 */
    365 			charexists = 1;
    366 
    367 			for (i = 0; i < nomatches_len; ++i) {
    368 				/* avoid calling XftFontMatch if we know we won't find a match */
    369 				if (utf8codepoint == nomatches.codepoint[i])
    370 					goto no_match;
    371 			}
    372 
    373 			fccharset = FcCharSetCreate();
    374 			FcCharSetAddChar(fccharset, utf8codepoint);
    375 
    376 			if (!drw->fonts->pattern) {
    377 				/* Refer to the comment in xfont_create for more information. */
    378 				die("the first font in the cache must be loaded from a font string.");
    379 			}
    380 
    381 			fcpattern = FcPatternDuplicate(drw->fonts->pattern);
    382 			FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
    383 			FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
    384 
    385 			FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
    386 			FcDefaultSubstitute(fcpattern);
    387 			match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
    388 
    389 			FcCharSetDestroy(fccharset);
    390 			FcPatternDestroy(fcpattern);
    391 
    392 			if (match) {
    393 				usedfont = xfont_create(drw, NULL, match);
    394 				if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
    395 					for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
    396 						; /* NOP */
    397 					curfont->next = usedfont;
    398 				} else {
    399 					xfont_free(usedfont);
    400 					nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint;
    401 no_match:
    402 					usedfont = drw->fonts;
    403 				}
    404 			}
    405 		}
    406 	}
    407 	if (d)
    408 		XftDrawDestroy(d);
    409 
    410 	return x + (render ? w : 0);
    411 }
    412 
    413 void
    414 drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
    415 {
    416 	if (!drw)
    417 		return;
    418 
    419 	XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
    420 	XSync(drw->dpy, False);
    421 }
    422 
    423 unsigned int
    424 drw_fontset_getwidth(Drw *drw, const char *text)
    425 {
    426 	if (!drw || !drw->fonts || !text)
    427 		return 0;
    428 	return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
    429 }
    430 
    431 unsigned int
    432 drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n)
    433 {
    434 	unsigned int tmp = 0;
    435 	if (drw && drw->fonts && text && n)
    436 		tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n);
    437 	return MIN(n, tmp);
    438 }
    439 
    440 void
    441 drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
    442 {
    443 	XGlyphInfo ext;
    444 
    445 	if (!font || !text)
    446 		return;
    447 
    448 	XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
    449 	if (w)
    450 		*w = ext.xOff;
    451 	if (h)
    452 		*h = font->h;
    453 }