dmenu

my fork of dmenu
Index Commits Files Refs README LICENSE
drw.c (14925B)
   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 #define UTF_SIZ     4
  13 
  14 static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
  15 static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
  16 static const long utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
  17 static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
  18 
  19 static long
  20 utf8decodebyte(const char c, size_t *i)
  21 {
  22     for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
  23         if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
  24             return (unsigned char)c & ~utfmask[*i];
  25     return 0;
  26 }
  27 
  28 static size_t
  29 utf8validate(long *u, size_t i)
  30 {
  31     if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
  32         *u = UTF_INVALID;
  33     for (i = 1; *u > utfmax[i]; ++i)
  34         ;
  35     return i;
  36 }
  37 
  38 static size_t
  39 utf8decode(const char *c, long *u, size_t clen)
  40 {
  41     size_t i, j, len, type;
  42     long udecoded;
  43 
  44     *u = UTF_INVALID;
  45     if (!clen)
  46         return 0;
  47     udecoded = utf8decodebyte(c[0], &len);
  48     if (!BETWEEN(len, 1, UTF_SIZ))
  49         return 1;
  50     for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
  51         udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
  52         if (type)
  53             return j;
  54     }
  55     if (j < len)
  56         return 0;
  57     *u = udecoded;
  58     utf8validate(u, len);
  59 
  60     return len;
  61 }
  62 
  63 Drw *
  64 drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
  65 {
  66     Drw *drw = ecalloc(1, sizeof(Drw));
  67 
  68     drw->dpy = dpy;
  69     drw->screen = screen;
  70     drw->root = root;
  71     drw->w = w;
  72     drw->h = h;
  73     drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
  74     drw->gc = XCreateGC(dpy, root, 0, NULL);
  75     XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
  76 
  77     return drw;
  78 }
  79 
  80 void
  81 drw_resize(Drw *drw, unsigned int w, unsigned int h)
  82 {
  83     if (!drw)
  84         return;
  85 
  86     drw->w = w;
  87     drw->h = h;
  88     if (drw->drawable)
  89         XFreePixmap(drw->dpy, drw->drawable);
  90     drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
  91 }
  92 
  93 void
  94 drw_free(Drw *drw)
  95 {
  96     XFreePixmap(drw->dpy, drw->drawable);
  97     XFreeGC(drw->dpy, drw->gc);
  98     drw_fontset_free(drw->fonts);
  99     free(drw);
 100 }
 101 
 102 /* This function is an implementation detail. Library users should use
 103  * drw_fontset_create instead.
 104  */
 105 static Fnt *
 106 xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
 107 {
 108     Fnt *font;
 109     XftFont *xfont = NULL;
 110     FcPattern *pattern = NULL;
 111 
 112     if (fontname) {
 113         /* Using the pattern found at font->xfont->pattern does not yield the
 114          * same substitution results as using the pattern returned by
 115          * FcNameParse; using the latter results in the desired fallback
 116          * behaviour whereas the former just results in missing-character
 117          * rectangles being drawn, at least with some fonts. */
 118         if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
 119             fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
 120             return NULL;
 121         }
 122         if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
 123             fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
 124             XftFontClose(drw->dpy, xfont);
 125             return NULL;
 126         }
 127     } else if (fontpattern) {
 128         if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
 129             fprintf(stderr, "error, cannot load font from pattern.\n");
 130             return NULL;
 131         }
 132     } else {
 133         die("no font specified.");
 134     }
 135 
 136     /* Do not allow using color fonts. This is a workaround for a BadLength
 137      * error from Xft with color glyphs. Modelled on the Xterm workaround. See
 138      * https://bugzilla.redhat.com/show_bug.cgi?id=1498269
 139      * https://lists.suckless.org/dev/1701/30932.html
 140      * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349
 141      * and lots more all over the internet.
 142      */
 143     FcBool iscol;
 144     if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) {
 145         XftFontClose(drw->dpy, xfont);
 146         return NULL;
 147     }
 148 
 149     font = ecalloc(1, sizeof(Fnt));
 150     font->xfont = xfont;
 151     font->pattern = pattern;
 152     font->h = xfont->ascent + xfont->descent;
 153     font->dpy = drw->dpy;
 154 
 155     return font;
 156 }
 157 
 158 static void
 159 xfont_free(Fnt *font)
 160 {
 161     if (!font)
 162         return;
 163     if (font->pattern)
 164         FcPatternDestroy(font->pattern);
 165     XftFontClose(font->dpy, font->xfont);
 166     free(font);
 167 }
 168 
 169 Fnt*
 170 drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount)
 171 {
 172     Fnt *cur, *ret = NULL;
 173     size_t i;
 174 
 175     if (!drw || !fonts)
 176         return NULL;
 177 
 178     for (i = 1; i <= fontcount; i++) {
 179         if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
 180             cur->next = ret;
 181             ret = cur;
 182         }
 183     }
 184     return (drw->fonts = ret);
 185 }
 186 
 187 void
 188 drw_fontset_free(Fnt *font)
 189 {
 190     if (font) {
 191         drw_fontset_free(font->next);
 192         xfont_free(font);
 193     }
 194 }
 195 
 196 void
 197 drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
 198 {
 199     if (!drw || !dest || !clrname)
 200         return;
 201 
 202     if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
 203                            DefaultColormap(drw->dpy, drw->screen),
 204                            clrname, dest))
 205         die("error, cannot allocate color '%s'", clrname);
 206 }
 207 
 208 /* Wrapper to create color schemes. The caller has to call free(3) on the
 209  * returned color scheme when done using it. */
 210 Clr *
 211 drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
 212 {
 213     size_t i;
 214     Clr *ret;
 215 
 216     /* need at least two colors for a scheme */
 217     if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
 218         return NULL;
 219 
 220     for (i = 0; i < clrcount; i++)
 221         drw_clr_create(drw, &ret[i], clrnames[i]);
 222     return ret;
 223 }
 224 
 225 void
 226 drw_setfontset(Drw *drw, Fnt *set)
 227 {
 228     if (drw)
 229         drw->fonts = set;
 230 }
 231 
 232 void
 233 drw_setscheme(Drw *drw, Clr *scm)
 234 {
 235     if (drw)
 236         drw->scheme = scm;
 237 }
 238 
 239 void
 240 drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
 241 {
 242     if (!drw || !drw->scheme)
 243         return;
 244     XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
 245     if (filled)
 246         XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
 247     else
 248         XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
 249 }
 250 
 251 int
 252 drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
 253 {
 254     char buf[1024];
 255     int ty;
 256     unsigned int ew;
 257     XftDraw *d = NULL;
 258     Fnt *usedfont, *curfont, *nextfont;
 259     size_t i, len;
 260     int utf8strlen, utf8charlen, render = x || y || w || h;
 261     long utf8codepoint = 0;
 262     const char *utf8str;
 263     FcCharSet *fccharset;
 264     FcPattern *fcpattern;
 265     FcPattern *match;
 266     XftResult result;
 267     int charexists = 0;
 268 
 269     if (!drw || (render && !drw->scheme) || !text || !drw->fonts)
 270         return 0;
 271 
 272     if (!render) {
 273         w = ~w;
 274     } else {
 275         XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
 276         XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
 277         d = XftDrawCreate(drw->dpy, drw->drawable,
 278                           DefaultVisual(drw->dpy, drw->screen),
 279                           DefaultColormap(drw->dpy, drw->screen));
 280         x += lpad;
 281         w -= lpad;
 282     }
 283 
 284     usedfont = drw->fonts;
 285     while (1) {
 286         utf8strlen = 0;
 287         utf8str = text;
 288         nextfont = NULL;
 289         while (*text) {
 290             utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
 291             for (curfont = drw->fonts; curfont; curfont = curfont->next) {
 292                 charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
 293                 if (charexists) {
 294                     if (curfont == usedfont) {
 295                         utf8strlen += utf8charlen;
 296                         text += utf8charlen;
 297                     } else {
 298                         nextfont = curfont;
 299                     }
 300                     break;
 301                 }
 302             }
 303 
 304             if (!charexists || nextfont)
 305                 break;
 306             else
 307                 charexists = 0;
 308         }
 309 
 310         if (utf8strlen) {
 311             drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
 312             /* shorten text if necessary */
 313             for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--)
 314                 drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
 315 
 316             if (len) {
 317                 memcpy(buf, utf8str, len);
 318                 buf[len] = '\0';
 319                 if (len < utf8strlen)
 320                     for (i = len; i && i > len - 3; buf[--i] = '.')
 321                         ; /* NOP */
 322 
 323                 if (render) {
 324                     ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
 325                     XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
 326                                       usedfont->xfont, x, ty, (XftChar8 *)buf, len);
 327                 }
 328                 x += ew;
 329                 w -= ew;
 330             }
 331         }
 332 
 333         if (!*text) {
 334             break;
 335         } else if (nextfont) {
 336             charexists = 0;
 337             usedfont = nextfont;
 338         } else {
 339             /* Regardless of whether or not a fallback font is found, the
 340              * character must be drawn. */
 341             charexists = 1;
 342 
 343             fccharset = FcCharSetCreate();
 344             FcCharSetAddChar(fccharset, utf8codepoint);
 345 
 346             if (!drw->fonts->pattern) {
 347                 /* Refer to the comment in xfont_create for more information. */
 348                 die("the first font in the cache must be loaded from a font string.");
 349             }
 350 
 351             fcpattern = FcPatternDuplicate(drw->fonts->pattern);
 352             FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
 353             FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
 354             FcPatternAddBool(fcpattern, FC_COLOR, FcFalse);
 355 
 356             FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
 357             FcDefaultSubstitute(fcpattern);
 358             match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
 359 
 360             FcCharSetDestroy(fccharset);
 361             FcPatternDestroy(fcpattern);
 362 
 363             if (match) {
 364                 usedfont = xfont_create(drw, NULL, match);
 365                 if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
 366                     for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
 367                         ; /* NOP */
 368                     curfont->next = usedfont;
 369                 } else {
 370                     xfont_free(usedfont);
 371                     usedfont = drw->fonts;
 372                 }
 373             }
 374         }
 375     }
 376     if (d)
 377         XftDrawDestroy(d);
 378 
 379     return x + (render ? w : 0);
 380 }
 381 
 382 int
 383 utf8nextchar(const char *str, int len, int i, int inc)
 384 {
 385     int n;
 386 
 387     for (n = i + inc; n + inc >= 0 && n + inc <= len
 388       && (str[n] & 0xc0) == 0x80; n += inc)
 389         ;
 390     return n;
 391 }
 392 
 393 int
 394 drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align)
 395 {
 396     int ty;
 397     unsigned int ew;
 398     XftDraw *d = NULL;
 399     Fnt *usedfont, *curfont, *nextfont;
 400     size_t len;
 401     int utf8strlen, utf8charlen, render = x || y || w || h;
 402     long utf8codepoint = 0;
 403     const char *utf8str;
 404     FcCharSet *fccharset;
 405     FcPattern *fcpattern;
 406     FcPattern *match;
 407     XftResult result;
 408     int charexists = 0;
 409     int i, n;
 410 
 411     if (!drw || (render && !drw->scheme) || !text || !drw->fonts || textlen <= 0
 412       || (align != AlignL && align != AlignR))
 413         return 0;
 414 
 415     if (!render) {
 416         w = ~w;
 417     } else {
 418         XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel);
 419         XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
 420         d = XftDrawCreate(drw->dpy, drw->drawable,
 421                           DefaultVisual(drw->dpy, drw->screen),
 422                           DefaultColormap(drw->dpy, drw->screen));
 423     }
 424 
 425     usedfont = drw->fonts;
 426     i = align == AlignL ? 0 : textlen;
 427     x = align == AlignL ? x : x + w;
 428     while (1) {
 429         utf8strlen = 0;
 430         nextfont = NULL;
 431         /* if (align == AlignL) */
 432         utf8str = text + i;
 433 
 434         while ((align == AlignL && i < textlen) || (align == AlignR && i > 0)) {
 435             if (align == AlignL) {
 436                 utf8charlen = utf8decode(text + i, &utf8codepoint, MIN(textlen - i, UTF_SIZ));
 437                 if (!utf8charlen) {
 438                     textlen = i;
 439                     break;
 440                 }
 441             } else {
 442                 n = utf8nextchar(text, textlen, i, -1);
 443                 utf8charlen = utf8decode(text + n, &utf8codepoint, MIN(textlen - n, UTF_SIZ));
 444                 if (!utf8charlen) {
 445                     textlen -= i;
 446                     text += i;
 447                     i = 0;
 448                     break;
 449                 }
 450             }
 451             for (curfont = drw->fonts; curfont; curfont = curfont->next) {
 452                 charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
 453                 if (charexists) {
 454                     if (curfont == usedfont) {
 455                         utf8strlen += utf8charlen;
 456                         i += align == AlignL ? utf8charlen : -utf8charlen;
 457                     } else {
 458                         nextfont = curfont;
 459                     }
 460                     break;
 461                 }
 462             }
 463 
 464             if (!charexists || nextfont)
 465                 break;
 466             else
 467                 charexists = 0;
 468         }
 469 
 470         if (align == AlignR)
 471             utf8str = text + i;
 472 
 473         if (utf8strlen) {
 474             drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
 475             /* shorten text if necessary */
 476             if (align == AlignL) {
 477                 for (len = utf8strlen; len && ew > w; ) {
 478                     len = utf8nextchar(utf8str, len, len, -1);
 479                     drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
 480                 }
 481             } else {
 482                 for (len = utf8strlen; len && ew > w; ) {
 483                     n = utf8nextchar(utf8str, len, 0, +1);
 484                     utf8str += n;
 485                     len -= n;
 486                     drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
 487                 }
 488             }
 489 
 490             if (len) {
 491                 if (render) {
 492                     ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
 493                     XftDrawStringUtf8(d, &drw->scheme[ColFg],
 494                                       usedfont->xfont, align == AlignL ? x : x - ew, ty, (XftChar8 *)utf8str, len);
 495                 }
 496                 x += align == AlignL ? ew : -ew;
 497                 w -= ew;
 498             }
 499             if (len < utf8strlen)
 500                 break;
 501         }
 502 
 503         if ((align == AlignR && i <= 0) || (align == AlignL && i >= textlen)) {
 504             break;
 505         } else if (nextfont) {
 506             charexists = 0;
 507             usedfont = nextfont;
 508         } else {
 509             /* Regardless of whether or not a fallback font is found, the
 510              * character must be drawn. */
 511             charexists = 1;
 512 
 513             fccharset = FcCharSetCreate();
 514             FcCharSetAddChar(fccharset, utf8codepoint);
 515 
 516             if (!drw->fonts->pattern) {
 517                 /* Refer to the comment in xfont_create for more information. */
 518                 die("the first font in the cache must be loaded from a font string.");
 519             }
 520 
 521             fcpattern = FcPatternDuplicate(drw->fonts->pattern);
 522             FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
 523             FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
 524 
 525             FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
 526             FcDefaultSubstitute(fcpattern);
 527             match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
 528 
 529             FcCharSetDestroy(fccharset);
 530             FcPatternDestroy(fcpattern);
 531 
 532             if (match) {
 533                 usedfont = xfont_create(drw, NULL, match);
 534                 if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
 535                     for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
 536                         ; /* NOP */
 537                     curfont->next = usedfont;
 538                 } else {
 539                     xfont_free(usedfont);
 540                     usedfont = drw->fonts;
 541                 }
 542             }
 543         }
 544     }
 545     if (d)
 546         XftDrawDestroy(d);
 547 
 548     return x;
 549 }
 550 
 551 void
 552 drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
 553 {
 554     if (!drw)
 555         return;
 556 
 557     XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
 558     XSync(drw->dpy, False);
 559 }
 560 
 561 unsigned int
 562 drw_fontset_getwidth(Drw *drw, const char *text)
 563 {
 564     if (!drw || !drw->fonts || !text)
 565         return 0;
 566     return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
 567 }
 568 
 569 void
 570 drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
 571 {
 572     XGlyphInfo ext;
 573 
 574     if (!font || !text)
 575         return;
 576 
 577     XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
 578     if (w)
 579         *w = ext.xOff;
 580     if (h)
 581         *h = font->h;
 582 }
 583 
 584 Cur *
 585 drw_cur_create(Drw *drw, int shape)
 586 {
 587     Cur *cur;
 588 
 589     if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
 590         return NULL;
 591 
 592     cur->cursor = XCreateFontCursor(drw->dpy, shape);
 593 
 594     return cur;
 595 }
 596 
 597 void
 598 drw_cur_free(Drw *drw, Cur *cursor)
 599 {
 600     if (!cursor)
 601         return;
 602 
 603     XFreeCursor(drw->dpy, cursor->cursor);
 604     free(cursor);
 605 }