| drw.c - dmenu - dynamic menu | |
| git clone git://git.suckless.org/dmenu | |
| Log | |
| Files | |
| Refs | |
| README | |
| LICENSE | |
| --- | |
| drw.c (11585B) | |
| --- | |
| 1 /* See LICENSE file for copyright and license details. */ | |
| 2 #include <stdio.h> | |
| 3 #include <stdlib.h> | |
| 4 #include <string.h> | |
| 5 #include <X11/Xlib.h> | |
| 6 #include <X11/Xft/Xft.h> | |
| 7 | |
| 8 #include "drw.h" | |
| 9 #include "util.h" | |
| 10 | |
| 11 #define UTF_INVALID 0xFFFD | |
| 12 | |
| 13 static int | |
| 14 utf8decode(const char *s_in, long *u, int *err) | |
| 15 { | |
| 16 static const unsigned char lens[] = { | |
| 17 /* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,… | |
| 18 /* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */ | |
| 19 /* 110XX */ 2, 2, 2, 2, | |
| 20 /* 1110X */ 3, 3, | |
| 21 /* 11110 */ 4, | |
| 22 /* 11111 */ 0, /* invalid */ | |
| 23 }; | |
| 24 static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, … | |
| 25 static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10… | |
| 26 | |
| 27 const unsigned char *s = (const unsigned char *)s_in; | |
| 28 int len = lens[*s >> 3]; | |
| 29 *u = UTF_INVALID; | |
| 30 *err = 1; | |
| 31 if (len == 0) | |
| 32 return 1; | |
| 33 | |
| 34 long cp = s[0] & leading_mask[len - 1]; | |
| 35 for (int i = 1; i < len; ++i) { | |
| 36 if (s[i] == '\0' || (s[i] & 0xC0) != 0x80) | |
| 37 return i; | |
| 38 cp = (cp << 6) | (s[i] & 0x3F); | |
| 39 } | |
| 40 /* out of range, surrogate, overlong encoding */ | |
| 41 if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1… | |
| 42 return len; | |
| 43 | |
| 44 *err = 0; | |
| 45 *u = cp; | |
| 46 return len; | |
| 47 } | |
| 48 | |
| 49 Drw * | |
| 50 drw_create(Display *dpy, int screen, Window root, unsigned int w, unsign… | |
| 51 { | |
| 52 Drw *drw = ecalloc(1, sizeof(Drw)); | |
| 53 | |
| 54 drw->dpy = dpy; | |
| 55 drw->screen = screen; | |
| 56 drw->root = root; | |
| 57 drw->w = w; | |
| 58 drw->h = h; | |
| 59 drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy,… | |
| 60 drw->gc = XCreateGC(dpy, root, 0, NULL); | |
| 61 XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMite… | |
| 62 | |
| 63 return drw; | |
| 64 } | |
| 65 | |
| 66 void | |
| 67 drw_resize(Drw *drw, unsigned int w, unsigned int h) | |
| 68 { | |
| 69 if (!drw) | |
| 70 return; | |
| 71 | |
| 72 drw->w = w; | |
| 73 drw->h = h; | |
| 74 if (drw->drawable) | |
| 75 XFreePixmap(drw->dpy, drw->drawable); | |
| 76 drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, Default… | |
| 77 } | |
| 78 | |
| 79 void | |
| 80 drw_free(Drw *drw) | |
| 81 { | |
| 82 XFreePixmap(drw->dpy, drw->drawable); | |
| 83 XFreeGC(drw->dpy, drw->gc); | |
| 84 drw_fontset_free(drw->fonts); | |
| 85 free(drw); | |
| 86 } | |
| 87 | |
| 88 /* This function is an implementation detail. Library users should use | |
| 89 * drw_fontset_create instead. | |
| 90 */ | |
| 91 static Fnt * | |
| 92 xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) | |
| 93 { | |
| 94 Fnt *font; | |
| 95 XftFont *xfont = NULL; | |
| 96 FcPattern *pattern = NULL; | |
| 97 | |
| 98 if (fontname) { | |
| 99 /* Using the pattern found at font->xfont->pattern does … | |
| 100 * same substitution results as using the pattern return… | |
| 101 * FcNameParse; using the latter results in the desired … | |
| 102 * behaviour whereas the former just results in missing-… | |
| 103 * rectangles being drawn, at least with some fonts. */ | |
| 104 if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fon… | |
| 105 fprintf(stderr, "error, cannot load font from na… | |
| 106 return NULL; | |
| 107 } | |
| 108 if (!(pattern = FcNameParse((FcChar8 *) fontname))) { | |
| 109 fprintf(stderr, "error, cannot parse font name t… | |
| 110 XftFontClose(drw->dpy, xfont); | |
| 111 return NULL; | |
| 112 } | |
| 113 } else if (fontpattern) { | |
| 114 if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))… | |
| 115 fprintf(stderr, "error, cannot load font from pa… | |
| 116 return NULL; | |
| 117 } | |
| 118 } else { | |
| 119 die("no font specified."); | |
| 120 } | |
| 121 | |
| 122 font = ecalloc(1, sizeof(Fnt)); | |
| 123 font->xfont = xfont; | |
| 124 font->pattern = pattern; | |
| 125 font->h = xfont->ascent + xfont->descent; | |
| 126 font->dpy = drw->dpy; | |
| 127 | |
| 128 return font; | |
| 129 } | |
| 130 | |
| 131 static void | |
| 132 xfont_free(Fnt *font) | |
| 133 { | |
| 134 if (!font) | |
| 135 return; | |
| 136 if (font->pattern) | |
| 137 FcPatternDestroy(font->pattern); | |
| 138 XftFontClose(font->dpy, font->xfont); | |
| 139 free(font); | |
| 140 } | |
| 141 | |
| 142 Fnt* | |
| 143 drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) | |
| 144 { | |
| 145 Fnt *cur, *ret = NULL; | |
| 146 size_t i; | |
| 147 | |
| 148 if (!drw || !fonts) | |
| 149 return NULL; | |
| 150 | |
| 151 for (i = 1; i <= fontcount; i++) { | |
| 152 if ((cur = xfont_create(drw, fonts[fontcount - i], NULL)… | |
| 153 cur->next = ret; | |
| 154 ret = cur; | |
| 155 } | |
| 156 } | |
| 157 return (drw->fonts = ret); | |
| 158 } | |
| 159 | |
| 160 void | |
| 161 drw_fontset_free(Fnt *font) | |
| 162 { | |
| 163 if (font) { | |
| 164 drw_fontset_free(font->next); | |
| 165 xfont_free(font); | |
| 166 } | |
| 167 } | |
| 168 | |
| 169 void | |
| 170 drw_clr_create(Drw *drw, Clr *dest, const char *clrname) | |
| 171 { | |
| 172 if (!drw || !dest || !clrname) | |
| 173 return; | |
| 174 | |
| 175 if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->sc… | |
| 176 DefaultColormap(drw->dpy, drw->screen), | |
| 177 clrname, dest)) | |
| 178 die("error, cannot allocate color '%s'", clrname); | |
| 179 } | |
| 180 | |
| 181 /* Create color schemes. */ | |
| 182 Clr * | |
| 183 drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) | |
| 184 { | |
| 185 size_t i; | |
| 186 Clr *ret; | |
| 187 | |
| 188 /* need at least two colors for a scheme */ | |
| 189 if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcoun… | |
| 190 return NULL; | |
| 191 | |
| 192 for (i = 0; i < clrcount; i++) | |
| 193 drw_clr_create(drw, &ret[i], clrnames[i]); | |
| 194 return ret; | |
| 195 } | |
| 196 | |
| 197 void | |
| 198 drw_clr_free(Drw *drw, Clr *c) | |
| 199 { | |
| 200 if (!drw || !c) | |
| 201 return; | |
| 202 | |
| 203 /* c is typedef XftColor Clr */ | |
| 204 XftColorFree(drw->dpy, DefaultVisual(drw->dpy, drw->screen), | |
| 205 DefaultColormap(drw->dpy, drw->screen), c); | |
| 206 } | |
| 207 | |
| 208 void | |
| 209 drw_scm_free(Drw *drw, Clr *scm, size_t clrcount) | |
| 210 { | |
| 211 size_t i; | |
| 212 | |
| 213 if (!drw || !scm) | |
| 214 return; | |
| 215 | |
| 216 for (i = 0; i < clrcount; i++) | |
| 217 drw_clr_free(drw, &scm[i]); | |
| 218 free(scm); | |
| 219 } | |
| 220 | |
| 221 void | |
| 222 drw_setfontset(Drw *drw, Fnt *set) | |
| 223 { | |
| 224 if (drw) | |
| 225 drw->fonts = set; | |
| 226 } | |
| 227 | |
| 228 void | |
| 229 drw_setscheme(Drw *drw, Clr *scm) | |
| 230 { | |
| 231 if (drw) | |
| 232 drw->scheme = scm; | |
| 233 } | |
| 234 | |
| 235 void | |
| 236 drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int fil… | |
| 237 { | |
| 238 if (!drw || !drw->scheme) | |
| 239 return; | |
| 240 XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pi… | |
| 241 if (filled) | |
| 242 XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w… | |
| 243 else | |
| 244 XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w… | |
| 245 } | |
| 246 | |
| 247 int | |
| 248 drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigne… | |
| 249 { | |
| 250 int ty, ellipsis_x = 0; | |
| 251 unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h… | |
| 252 XftDraw *d = NULL; | |
| 253 Fnt *usedfont, *curfont, *nextfont; | |
| 254 int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; | |
| 255 long utf8codepoint = 0; | |
| 256 const char *utf8str; | |
| 257 FcCharSet *fccharset; | |
| 258 FcPattern *fcpattern; | |
| 259 FcPattern *match; | |
| 260 XftResult result; | |
| 261 int charexists = 0, overflow = 0; | |
| 262 /* keep track of a couple codepoints for which we have no match.… | |
| 263 static unsigned int nomatches[128], ellipsis_width, invalid_widt… | |
| 264 static const char invalid[] = "�"; | |
| 265 | |
| 266 if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->f… | |
| 267 return 0; | |
| 268 | |
| 269 if (!render) { | |
| 270 w = invert ? invert : ~invert; | |
| 271 } else { | |
| 272 XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? C… | |
| 273 XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w… | |
| 274 if (w < lpad) | |
| 275 return x + w; | |
| 276 d = XftDrawCreate(drw->dpy, drw->drawable, | |
| 277 DefaultVisual(drw->dpy, drw->screen), | |
| 278 DefaultColormap(drw->dpy, drw->screen)… | |
| 279 x += lpad; | |
| 280 w -= lpad; | |
| 281 } | |
| 282 | |
| 283 usedfont = drw->fonts; | |
| 284 if (!ellipsis_width && render) | |
| 285 ellipsis_width = drw_fontset_getwidth(drw, "..."); | |
| 286 if (!invalid_width && render) | |
| 287 invalid_width = drw_fontset_getwidth(drw, invalid); | |
| 288 while (1) { | |
| 289 ew = ellipsis_len = utf8err = utf8charlen = utf8strlen =… | |
| 290 utf8str = text; | |
| 291 nextfont = NULL; | |
| 292 while (*text) { | |
| 293 utf8charlen = utf8decode(text, &utf8codepoint, &… | |
| 294 for (curfont = drw->fonts; curfont; curfont = cu… | |
| 295 charexists = charexists || XftCharExists… | |
| 296 if (charexists) { | |
| 297 drw_font_getexts(curfont, text, … | |
| 298 if (ew + ellipsis_width <= w) { | |
| 299 /* keep track where the … | |
| 300 ellipsis_x = x + ew; | |
| 301 ellipsis_w = w - ew; | |
| 302 ellipsis_len = utf8strle… | |
| 303 } | |
| 304 | |
| 305 if (ew + tmpw > w) { | |
| 306 overflow = 1; | |
| 307 /* called from drw_fonts… | |
| 308 * it wants the width AF… | |
| 309 */ | |
| 310 if (!render) | |
| 311 x += tmpw; | |
| 312 else | |
| 313 utf8strlen = ell… | |
| 314 } else if (curfont == usedfont) { | |
| 315 text += utf8charlen; | |
| 316 utf8strlen += utf8err ? … | |
| 317 ew += utf8err ? 0 : tmpw; | |
| 318 } else { | |
| 319 nextfont = curfont; | |
| 320 } | |
| 321 break; | |
| 322 } | |
| 323 } | |
| 324 | |
| 325 if (overflow || !charexists || nextfont || utf8e… | |
| 326 break; | |
| 327 else | |
| 328 charexists = 0; | |
| 329 } | |
| 330 | |
| 331 if (utf8strlen) { | |
| 332 if (render) { | |
| 333 ty = y + (h - usedfont->h) / 2 + usedfon… | |
| 334 XftDrawStringUtf8(d, &drw->scheme[invert… | |
| 335 usedfont->xfont, x, ty… | |
| 336 } | |
| 337 x += ew; | |
| 338 w -= ew; | |
| 339 } | |
| 340 if (utf8err && (!render || invalid_width < w)) { | |
| 341 if (render) | |
| 342 drw_text(drw, x, y, w, h, 0, invalid, in… | |
| 343 x += invalid_width; | |
| 344 w -= invalid_width; | |
| 345 } | |
| 346 if (render && overflow) | |
| 347 drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "… | |
| 348 | |
| 349 if (!*text || overflow) { | |
| 350 break; | |
| 351 } else if (nextfont) { | |
| 352 charexists = 0; | |
| 353 usedfont = nextfont; | |
| 354 } else { | |
| 355 /* Regardless of whether or not a fallback font … | |
| 356 * character must be drawn. */ | |
| 357 charexists = 1; | |
| 358 | |
| 359 hash = (unsigned int)utf8codepoint; | |
| 360 hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; | |
| 361 hash = ((hash >> 15) ^ hash) * 0xD35A2D97; | |
| 362 h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches); | |
| 363 h1 = (hash >> 17) % LENGTH(nomatches); | |
| 364 /* avoid expensive XftFontMatch call when we kno… | |
| 365 if (nomatches[h0] == utf8codepoint || nomatches[… | |
| 366 goto no_match; | |
| 367 | |
| 368 fccharset = FcCharSetCreate(); | |
| 369 FcCharSetAddChar(fccharset, utf8codepoint); | |
| 370 | |
| 371 if (!drw->fonts->pattern) { | |
| 372 /* Refer to the comment in xfont_create … | |
| 373 die("the first font in the cache must be… | |
| 374 } | |
| 375 | |
| 376 fcpattern = FcPatternDuplicate(drw->fonts->patte… | |
| 377 FcPatternAddCharSet(fcpattern, FC_CHARSET, fccha… | |
| 378 FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); | |
| 379 | |
| 380 FcConfigSubstitute(NULL, fcpattern, FcMatchPatte… | |
| 381 FcDefaultSubstitute(fcpattern); | |
| 382 match = XftFontMatch(drw->dpy, drw->screen, fcpa… | |
| 383 | |
| 384 FcCharSetDestroy(fccharset); | |
| 385 FcPatternDestroy(fcpattern); | |
| 386 | |
| 387 if (match) { | |
| 388 usedfont = xfont_create(drw, NULL, match… | |
| 389 if (usedfont && XftCharExists(drw->dpy, … | |
| 390 for (curfont = drw->fonts; curfo… | |
| 391 ; /* NOP */ | |
| 392 curfont->next = usedfont; | |
| 393 } else { | |
| 394 xfont_free(usedfont); | |
| 395 nomatches[nomatches[h0] ? h1 : h… | |
| 396 no_match: | |
| 397 usedfont = drw->fonts; | |
| 398 } | |
| 399 } | |
| 400 } | |
| 401 } | |
| 402 if (d) | |
| 403 XftDrawDestroy(d); | |
| 404 | |
| 405 return x + (render ? w : 0); | |
| 406 } | |
| 407 | |
| 408 void | |
| 409 drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int… | |
| 410 { | |
| 411 if (!drw) | |
| 412 return; | |
| 413 | |
| 414 XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, … | |
| 415 XSync(drw->dpy, False); | |
| 416 } | |
| 417 | |
| 418 unsigned int | |
| 419 drw_fontset_getwidth(Drw *drw, const char *text) | |
| 420 { | |
| 421 if (!drw || !drw->fonts || !text) | |
| 422 return 0; | |
| 423 return drw_text(drw, 0, 0, 0, 0, 0, text, 0); | |
| 424 } | |
| 425 | |
| 426 unsigned int | |
| 427 drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) | |
| 428 { | |
| 429 unsigned int tmp = 0; | |
| 430 if (drw && drw->fonts && text && n) | |
| 431 tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); | |
| 432 return MIN(n, tmp); | |
| 433 } | |
| 434 | |
| 435 void | |
| 436 drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned… | |
| 437 { | |
| 438 XGlyphInfo ext; | |
| 439 | |
| 440 if (!font || !text) | |
| 441 return; | |
| 442 | |
| 443 XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len… | |
| 444 if (w) | |
| 445 *w = ext.xOff; | |
| 446 if (h) | |
| 447 *h = font->h; | |
| 448 } | |
| 449 | |
| 450 Cur * | |
| 451 drw_cur_create(Drw *drw, int shape) | |
| 452 { | |
| 453 Cur *cur; | |
| 454 | |
| 455 if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) | |
| 456 return NULL; | |
| 457 | |
| 458 cur->cursor = XCreateFontCursor(drw->dpy, shape); | |
| 459 | |
| 460 return cur; | |
| 461 } | |
| 462 | |
| 463 void | |
| 464 drw_cur_free(Drw *drw, Cur *cursor) | |
| 465 { | |
| 466 if (!cursor) | |
| 467 return; | |
| 468 | |
| 469 XFreeCursor(drw->dpy, cursor->cursor); | |
| 470 free(cursor); | |
| 471 } |