se

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

x.c (55982B)


      1 /* See LICENSE for license details. */
      2 
      3 /*
      4 ** This file mainly contains X11 stuff (drawing to the screen, window hints, etc)
      5 ** Most of that part is unchanged from ST (https://st.suckless.org/)
      6 ** the main() function and the main loop are found at the very bottom of this file
      7 ** there are a very few functions here that are interresting for configuratinos.
      8 */
      9 
     10 
     11 #include <errno.h>
     12 #include <math.h>
     13 #include <locale.h>
     14 #include <stdio.h>
     15 #include <time.h>
     16 #include <stdarg.h>
     17 #include <unistd.h>
     18 #include <dirent.h>
     19 #include <assert.h>
     20 
     21 #include "se.h"
     22 #include "x.h"
     23 #include "config.h"
     24 #include "extension.h"
     25 
     26 //////////////////////////////////
     27 // macros
     28 //
     29 
     30 // XEMBED messages
     31 #define XEMBED_FOCUS_IN  4
     32 #define XEMBED_FOCUS_OUT 5
     33 
     34 #define IS_SET(flag)        ((win.mode & (flag)) != 0)
     35 #define TRUERED(x)        (((x) & 0xff0000) >> 8)
     36 #define TRUEGREEN(x)        (((x) & 0xff00))
     37 #define TRUEBLUE(x)        (((x) & 0xff) << 8)
     38 #define DEFAULT(a, b)        (a) = (a) ? (a) : (b)
     39 #define MODBIT(x, set, bit)    ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
     40 #define IS_TRUECOL(x)        (1 << 24 & (x))
     41 #define ATTRCMP(a, b)        ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
     42 #define DIVCEIL(n, d)        (((n) + ((d) - 1)) / (d))
     43 
     44 #include <X11/Xatom.h>
     45 #include <X11/cursorfont.h>
     46 #include <X11/Xft/Xft.h>
     47 #include <X11/XKBlib.h>
     48 
     49 // Purely graphic info
     50 typedef struct {
     51         int tw, th;    // tty width and height
     52         int w, h;    // window width and height
     53         int ch;        // char height
     54         int cw;        // char width
     55         int mode;    // window state/mode flags
     56 } TermWindow;
     57 extern TermWindow win;
     58 
     59 typedef XftDraw *Draw;
     60 typedef XftColor Color;
     61 typedef XftGlyphFontSpec GlyphFontSpec;
     62 
     63 typedef struct {
     64         Display *dpy;
     65         Colormap cmap;
     66         Window win;
     67         Drawable buf;
     68         GlyphFontSpec *specbuf; // font spec buffer used for rendering
     69         Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
     70         struct {
     71                 XIM xim;
     72                 XIC xic;
     73                 XPoint spot;
     74                 XVaNestedList spotlist;
     75         } ime;
     76         Draw draw;
     77         Visual *vis;
     78         XSetWindowAttributes attrs;
     79         int scr;
     80         int isfixed;    // is fixed geometry?
     81         int l, t;        // left and top offset
     82         int gm;            // geometry mask
     83 } XWindow;
     84 extern XWindow xw;
     85 
     86 // Font structure
     87 #define Font Font_
     88 typedef struct {
     89         int height;
     90         int width;
     91         int ascent;
     92         int descent;
     93         int badslant;
     94         int badweight;
     95         short lbearing;
     96         short rbearing;
     97         XftFont *match;
     98         FcFontSet *set;
     99         FcPattern *pattern;
    100 } Font;
    101 
    102 // Font Ring Cache
    103 enum {
    104         FRC_NORMAL,
    105         FRC_ITALIC,
    106         FRC_BOLD,
    107         FRC_ITALICBOLD
    108 };
    109 
    110 typedef struct {
    111         XftFont *font;
    112         int flags;
    113         rune_t unicodep;
    114 } Fontcache;
    115 
    116 extern Fontcache *frc;
    117 extern int frclen;
    118 
    119 // Drawing Context
    120 typedef struct {
    121         Color *col;
    122         size_t collen;
    123         Font font, bfont, ifont, ibfont;
    124         GC gc;
    125 } DC;
    126 extern DC dc;
    127 
    128 ////////////////////////////////////////
    129 // Internal Functions
    130 //
    131 
    132 static void xunloadfont(Font *);
    133 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const struct glyph *, int, int, int);
    134 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, struct glyph, int, int, int);
    135 static void xdrawglyph(struct glyph, int, int);
    136 static void xclear(int, int, int, int);
    137 static int xgeommasktogravity(int);
    138 static int ximopen(Display *);
    139 static void ximinstantiate(Display *, XPointer, XPointer);
    140 static void ximdestroy(XIM, XPointer, XPointer);
    141 static int xicdestroy(XIC, XPointer, XPointer);
    142 static void xinit(int, int);
    143 static void xresize(int, int);
    144 static int xloadcolor(int, const char *, Color *);
    145 static int xloadfont(Font *, FcPattern *);
    146 static void xsetenv(void);
    147 static void xseturgency(int);
    148 static void xsettitle(char *);
    149 static void run(void);
    150 
    151 ///////////////////////////////////////////////////
    152 // X11 events
    153 //
    154 
    155 static void expose(XEvent *);
    156 static void visibility(XEvent *);
    157 static void unmap(XEvent *);
    158 static void selnotify(XEvent *);
    159 static void propnotify(XEvent *e);
    160 static void selrequest(XEvent *);
    161 static void kpress(XEvent *);
    162 static void cmessage(XEvent *);
    163 static void resize(XEvent *);
    164 static void focus(XEvent *);
    165 
    166 static void (*handler[LASTEvent])(XEvent *) = {
    167         [KeyPress] = kpress,
    168         [ClientMessage] = cmessage,
    169         [ConfigureNotify] = resize,
    170         [VisibilityNotify] = visibility,
    171         [UnmapNotify] = unmap,
    172         [Expose] = expose,
    173         [FocusIn] = focus,
    174         [FocusOut] = focus,
    175         [PropertyNotify] = propnotify,
    176         [SelectionNotify] = selnotify,
    177         [SelectionRequest] = selrequest,
    178 };
    179 
    180 ////////////////////////////////////////////////
    181 // Globals
    182 //
    183 
    184 struct screen screen;
    185 struct glyph global_attr;
    186 
    187 static Atom xtarget;
    188 static char* copy_buffer;
    189 static int copy_len;
    190 
    191 TermWindow win;
    192 XWindow xw;
    193 DC dc;
    194 
    195 // Fontcache is an array. A new font will be appended to the array.
    196 Fontcache *frc = NULL;
    197 int frccap = 0;
    198 int frclen = 0;
    199 double defaultfontsize = 0;
    200 double usedfontsize = 0;
    201 
    202 /////////////////////////////////////////////////
    203 // function implementations
    204 //
    205 
    206 void
    207 screen_init(int col, int row)
    208 {
    209         global_attr = default_attributes;
    210 
    211         screen.col = 0;
    212         screen.row = 0;
    213         screen.lines = NULL;
    214         screen_resize(col, row);
    215 }
    216 
    217 void
    218 draw_horisontal_line(int y, int x1, int x2)
    219 {
    220         if (y < 0 || y > screen.row ||
    221             x2 < x1 || x2 > screen.col ||
    222             x1 < 0 || x1 > x2-1)
    223                 return;
    224 
    225         Color drawcol = dc.col[default_attributes.fg];
    226         XftDrawRect(xw.draw, &drawcol,
    227                     border_px + x1 * win.cw, border_px + (y + 1) * win.ch - cursor_thickness,
    228                     win.cw * (x2-x1+1), 1);
    229 }
    230 
    231 void
    232 set_clipboard_copy(char* buffer, int len)
    233 {
    234         if (!buffer)
    235                 return;
    236         if (copy_buffer)
    237                 free(copy_buffer);
    238         copy_buffer = buffer;
    239         copy_len = len;
    240 
    241         Atom clipboard;
    242         clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
    243         XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
    244 }
    245 
    246 void
    247 execute_clipbaord_event()
    248 {
    249         Atom clipboard;
    250 
    251         clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
    252         XConvertSelection(xw.dpy, clipboard, xtarget, clipboard,
    253                           xw.win, CurrentTime);
    254 }
    255 
    256 int
    257 screen_set_char(rune_t u, int x, int y)
    258 {
    259         struct glyph attr = global_attr;
    260         if (y >= screen.row || x >= screen.col ||
    261             y < 0         || x < 0)
    262                 return 1;
    263 
    264         if (u == 0)
    265                 u = screen.lines[y][x].u;
    266         int width = wcwidth(u);
    267         if (width == -1)
    268                 width = 1;
    269         else if (width > 1)
    270                 attr.mode |= ATTR_WIDE;
    271 
    272         if (screen.lines[y][x].mode & ATTR_WIDE || attr.mode & ATTR_WIDE) {
    273                 if (x+1 < screen.col) {
    274                         screen.lines[y][x+1].u = ' ';
    275                         screen.lines[y][x+1].mode |= ATTR_WDUMMY;
    276                 }
    277         } else if (screen.lines[y][x].mode & ATTR_WDUMMY && x-1 >= 0) {
    278                 screen.lines[y][x-1].u = ' ';
    279                 screen.lines[y][x-1].mode &= ~ATTR_WIDE;
    280         }
    281 
    282         screen.lines[y][x] = attr;
    283         screen.lines[y][x].u = u;
    284 
    285         return width;
    286 }
    287 
    288 void*
    289 xmalloc(size_t len)
    290 {
    291         void *p;
    292         if (!(p = malloc(len)))
    293                 die("malloc: error, reutrned NULL | errno: %s\n", strerror(errno));
    294         return p;
    295 }
    296 
    297 void*
    298 xrealloc(void *p, size_t len)
    299 {
    300         if ((p = realloc(p, len)) == NULL)
    301                 die("realloc: error, returned NULL | errno: %s\n", strerror(errno));
    302         return p;
    303 }
    304 
    305 void
    306 die(const char *errstr, ...)
    307 {
    308         va_list ap;
    309 
    310         va_start(ap, errstr);
    311         vfprintf(stderr, errstr, ap);
    312         va_end(ap);
    313         assert(0);
    314 }
    315 
    316 ////////////////////////////////////////////////
    317 // X11 and drawing
    318 //
    319 
    320 struct glyph*
    321 screen_set_attr(int x, int y)
    322 {
    323         static struct glyph dummy;
    324         if (y >= screen.row || x >= screen.col ||
    325             y < 0         || x < 0)
    326                 return &dummy;
    327 
    328         return &screen.lines[y][x];
    329 }
    330 
    331 void
    332 screen_set_region(int x1, int y1, int x2, int y2, rune_t u)
    333 {
    334         for (int y = y1; y <= y2; y++)
    335                 for (int x = x1; x <= x2; x++)
    336                         screen_set_char(u, x, y);
    337 }
    338 
    339 void
    340 screen_resize(int col, int row)
    341 {
    342         if (col < 1 || row < 1) {
    343                 fprintf(stderr,
    344                         "tresize: error resizing to %dx%d\n", col, row);
    345                 return;
    346         }
    347 
    348         // resize to new height
    349         for (int i = row; i < screen.row; i++)
    350                 free(screen.lines[i]);
    351 
    352         screen.lines = xrealloc(screen.lines, row * sizeof(*screen.lines));
    353 
    354         for (int i = screen.row; i < row; i++)
    355                 screen.lines[i] = NULL;
    356 
    357         // resize each row to new width, zero-pad if needed
    358         for (int i = 0; i < row; i++) {
    359                 screen.lines[i] = xrealloc(screen.lines[i], col * sizeof(struct glyph));
    360                 memset(screen.lines[i], 0, col * sizeof(struct glyph));
    361         }
    362 
    363         // update terminal size
    364         screen.col = col;
    365         screen.row = row;
    366 }
    367 
    368 
    369 
    370 
    371 void
    372 propnotify(XEvent *e)
    373 {
    374         XPropertyEvent *xpev;
    375         Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
    376 
    377         xpev = &e->xproperty;
    378         if (xpev->state == PropertyNewValue &&
    379             (xpev->atom == XA_PRIMARY ||
    380              xpev->atom == clipboard)) {
    381                 selnotify(e);
    382         }
    383 }
    384 
    385 void
    386 selnotify(XEvent *e)
    387 {
    388         unsigned long nitems, ofs, rem;
    389         int format;
    390         uint8_t *data, *last, *repl;
    391         Atom type, incratom, property = None;
    392 
    393         incratom = XInternAtom(xw.dpy, "INCR", 0);
    394 
    395         ofs = 0;
    396         if (e->type == SelectionNotify)
    397                 property = e->xselection.property;
    398         else if (e->type == PropertyNotify)
    399                 property = e->xproperty.atom;
    400 
    401         if (property == None)
    402                 return;
    403 
    404         do {
    405                 if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
    406                                        BUFSIZ/4, False, AnyPropertyType,
    407                                        &type, &format, &nitems, &rem,
    408                                        &data)) {
    409                         fprintf(stderr, "Clipboard allocation failed\n");
    410                         return;
    411                 }
    412 
    413                 if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
    414                         /*
    415                          * If there is some PropertyNotify with no data, then
    416                          * this is the signal of the selection owner that all
    417                          * data has been transferred. We won't need to receive
    418                          * PropertyNotify events anymore.
    419                          */
    420                         MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);
    421                         XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
    422                                                 &xw.attrs);
    423                 }
    424 
    425                 if (type == incratom) {
    426                         /*
    427                          * Activate the PropertyNotify events so we receive
    428                          * when the selection owner does send us the next
    429                          * chunk of data.
    430                          */
    431                         MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
    432                         XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
    433                                                 &xw.attrs);
    434 
    435                         // Deleting the property is the transfer start signal.
    436                         XDeleteProperty(xw.dpy, xw.win, (int)property);
    437                         continue;
    438                 }
    439 
    440                 // replace all '\r' with '\n'.
    441                 repl = data;
    442                 last = data + nitems * format / 8;
    443                 while ((repl = memchr(repl, '\r', last - repl))) {
    444                         *repl++ = '\n';
    445                 }
    446 
    447                 struct file_buffer* fb = get_fb(focused_window);
    448                 if (fb->contents) {
    449                         if (fb->mode & FB_SELECTION_ON) {
    450                                 fb_remove_selection(fb);
    451                                 wb_move_cursor_to_selection_start(focused_window);
    452                                 fb->mode &= ~(FB_SELECTION_ON);
    453                         }
    454                         call_extension(fb_paste, fb, (char*)data, nitems * format / 8);
    455                         call_extension(fb_contents_updated, fb, focused_window->cursor_offset, FB_CONTENT_BIG_CHANGE);
    456                 }
    457                 XFree(data);
    458                 /* number of 32-bit chunks returned */
    459                 ofs += nitems * format / 32;
    460         } while (rem > 0);
    461 
    462         /*
    463          * Deleting the property again tells the selection owner to send the
    464          * next data chunk in the property.
    465          */
    466         XDeleteProperty(xw.dpy, xw.win, (int)property);
    467 }
    468 
    469 void
    470 selrequest(XEvent *e)
    471 {
    472         XSelectionRequestEvent *xsre = (XSelectionRequestEvent *) e;
    473         XSelectionEvent xev = {0};
    474         Atom xa_targets, string, clipboard;
    475         char *seltext = NULL;
    476 
    477         xev.type = SelectionNotify;
    478         xev.requestor = xsre->requestor;
    479         xev.selection = xsre->selection;
    480         xev.target = xsre->target;
    481         xev.time = xsre->time;
    482         if (xsre->property == None)
    483                 xsre->property = xsre->target;
    484 
    485         // reject
    486         xev.property = None;
    487 
    488         xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
    489         if (xsre->target == xa_targets) {
    490                 // respond with the supported type
    491                 string = xtarget;
    492                 XChangeProperty(xsre->display, xsre->requestor, xsre->property,
    493                                 XA_ATOM, 32, PropModeReplace,
    494                                 (uint8_t *) &string, 1);
    495                 xev.property = xsre->property;
    496         } else if (xsre->target == xtarget || xsre->target == XA_STRING) {
    497                 /*
    498                  * xith XA_STRING non ascii characters may be incorrect in the
    499                  * requestor. It is not our problem, use utf8.
    500                  */
    501                 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
    502                 int sel_len;
    503                 if (xsre->selection == XA_PRIMARY) {
    504                         seltext = fb_get_selection(get_fb(focused_window), &sel_len);
    505                 } else if (xsre->selection == clipboard) {
    506                         seltext = copy_buffer;
    507                         sel_len = copy_len;
    508                 } else {
    509                         fprintf(stderr,
    510                                 "Unhandled clipboard selection 0x%lx\n",
    511                                 xsre->selection);
    512                         return;
    513                 }
    514                 if (seltext) {
    515                         XChangeProperty(xsre->display, xsre->requestor,
    516                                         xsre->property, xsre->target,
    517                                         8, PropModeReplace,
    518                                         (uint8_t *)seltext, sel_len);
    519                         xev.property = xsre->property;
    520                         if (seltext != copy_buffer)
    521                                 free(seltext);
    522                 }
    523         }
    524 
    525         // all done, send a notification to the listener
    526         if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev))
    527                 fprintf(stderr, "Error sending SelectionNotify event\n");
    528 }
    529 
    530 void
    531 cresize(int width, int height)
    532 {
    533         int col, row;
    534 
    535         if (width > 0)
    536                 win.w = width;
    537         if (height > 0)
    538                 win.h = height;
    539 
    540         col = (win.w - 2 * border_px) / win.cw;
    541         row = (win.h - 2 * border_px) / win.ch;
    542         col = MAX(1, col);
    543         row = MAX(1, row);
    544 
    545         screen_resize(col, row);
    546         xresize(col, row);
    547 }
    548 
    549 void
    550 xresize(int col, int row)
    551 {
    552         win.tw = col * win.cw;
    553         win.th = row * win.ch;
    554 
    555         XFreePixmap(xw.dpy, xw.buf);
    556         xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
    557                                DefaultDepth(xw.dpy, xw.scr));
    558         XftDrawChange(xw.draw, xw.buf);
    559         xclear(0, 0, win.w, win.h);
    560 
    561         // resize to new width
    562         xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
    563 }
    564 
    565 int
    566 xloadcolor(int i, const char *name, Color *ncolor)
    567 {
    568         if (!name) {
    569                 if (!(name = colors[i]))
    570                         return 0;
    571         }
    572 
    573         return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
    574 }
    575 
    576 void
    577 xloadcols(void)
    578 {
    579         int i;
    580         static int loaded;
    581 
    582         if (loaded) {
    583                 Color *cp;
    584                 for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
    585                         XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
    586         } else {
    587                 i = 0;
    588                 while (colors[i++])
    589                         ;
    590                 dc.collen  = i;
    591                 dc.col = xmalloc(dc.collen * sizeof(Color));
    592                 loaded = 1;
    593         }
    594 
    595         for (i = 0; i < dc.collen; i++) {
    596                 if (!xloadcolor(i, NULL, &dc.col[i])) {
    597                         if (colors[i])
    598                                 die("could not allocate color '%s'\n", colors[i]);
    599                 }
    600         }
    601 }
    602 
    603 int
    604 xsetcolorname(int x, const char *name)
    605 {
    606         Color ncolor;
    607 
    608         if (!BETWEEN(x, 0, dc.collen))
    609                 return 1;
    610 
    611         if (!xloadcolor(x, name, &ncolor))
    612                 return 1;
    613 
    614         XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
    615         dc.col[x] = ncolor;
    616 
    617         return 0;
    618 }
    619 
    620 
    621 // Absolute coordinates.
    622 void
    623 xclear(int x1, int y1, int x2, int y2)
    624 {
    625         XftDrawRect(xw.draw, &dc.col[default_attributes.bg],
    626                     x1, y1, x2-x1, y2-y1);
    627 }
    628 
    629 void
    630 xhints(void)
    631 {
    632         XClassHint class = {"se", "se"};
    633         XWMHints wm = {.flags = InputHint, .input = 1};
    634         XSizeHints *sizeh;
    635 
    636         sizeh = XAllocSizeHints();
    637 
    638         sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
    639         sizeh->height = win.h;
    640         sizeh->width = win.w;
    641         sizeh->height_inc = win.ch;
    642         sizeh->width_inc = win.cw;
    643         sizeh->base_height = 2 * border_px;
    644         sizeh->base_width = 2 * border_px;
    645         sizeh->min_height = win.ch + 2 * border_px;
    646         sizeh->min_width = win.cw + 2 * border_px;
    647         if (xw.isfixed) {
    648                 sizeh->flags |= PMaxSize;
    649                 sizeh->min_width = sizeh->max_width = win.w;
    650                 sizeh->min_height = sizeh->max_height = win.h;
    651         }
    652         if (xw.gm & (XValue|YValue)) {
    653                 sizeh->flags |= USPosition | PWinGravity;
    654                 sizeh->x = xw.l;
    655                 sizeh->y = xw.t;
    656                 sizeh->win_gravity = xgeommasktogravity(xw.gm);
    657         }
    658 
    659         XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm,
    660                          &class);
    661         XFree(sizeh);
    662 }
    663 
    664 int
    665 xgeommasktogravity(int mask)
    666 {
    667         switch (mask & (XNegative|YNegative)) {
    668         case 0:
    669                 return NorthWestGravity;
    670         case XNegative:
    671                 return NorthEastGravity;
    672         case YNegative:
    673                 return SouthWestGravity;
    674         }
    675 
    676         return SouthEastGravity;
    677 }
    678 
    679 int
    680 xloadfont(Font *f, FcPattern *pattern)
    681 {
    682         FcPattern *configured;
    683         FcPattern *match;
    684         FcResult result;
    685         XGlyphInfo extents;
    686         int wantattr, haveattr;
    687 
    688         /*
    689          * Manually configure instead of calling XftMatchFont
    690          * so that we can use the configured pattern for
    691          * "missing struct glyph" lookups.
    692          */
    693         configured = FcPatternDuplicate(pattern);
    694         if (!configured)
    695                 return 1;
    696 
    697         FcConfigSubstitute(NULL, configured, FcMatchPattern);
    698         XftDefaultSubstitute(xw.dpy, xw.scr, configured);
    699 
    700         match = FcFontMatch(NULL, configured, &result);
    701         if (!match) {
    702                 FcPatternDestroy(configured);
    703                 return 1;
    704         }
    705 
    706         if (!(f->match = XftFontOpenPattern(xw.dpy, match))) {
    707                 FcPatternDestroy(configured);
    708                 FcPatternDestroy(match);
    709                 return 1;
    710         }
    711 
    712         if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) ==
    713              XftResultMatch)) {
    714                 /*
    715                  * Check if xft was unable to find a font with the appropriate
    716                  * slant but gave us one anyway. Try to mitigate.
    717                  */
    718                 if ((XftPatternGetInteger(f->match->pattern, "slant", 0,
    719                                           &haveattr) != XftResultMatch) || haveattr < wantattr) {
    720                         f->badslant = 1;
    721                         fputs("font slant does not match\n", stderr);
    722                 }
    723         }
    724 
    725         if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) ==
    726              XftResultMatch)) {
    727                 if ((XftPatternGetInteger(f->match->pattern, "weight", 0,
    728                                           &haveattr) != XftResultMatch) || haveattr != wantattr) {
    729                         f->badweight = 1;
    730                         fputs("font weight does not match\n", stderr);
    731                 }
    732         }
    733 
    734         // Printable characters in ASCII, used to estimate the advance width of single wide characters.
    735         const char ascii_printable[] =
    736                 " !\"#$%&'()*+,-./0123456789:;<=>?"
    737                 "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
    738                 "`abcdefghijklmnopqrstuvwxyz{|}~";
    739 
    740         XftTextExtentsUtf8(xw.dpy, f->match,
    741                            (const FcChar8 *) ascii_printable,
    742                            strlen(ascii_printable), &extents);
    743 
    744         f->set = NULL;
    745         f->pattern = configured;
    746 
    747         f->ascent = f->match->ascent;
    748         f->descent = f->match->descent;
    749         f->lbearing = 0;
    750         f->rbearing = f->match->max_advance_width;
    751 
    752         f->height = f->ascent + f->descent;
    753         f->width = DIVCEIL(extents.xOff, strlen(ascii_printable));
    754 
    755         return 0;
    756 }
    757 
    758 void
    759 xloadfonts(const char *fontstr, double fontsize)
    760 {
    761         FcPattern *pattern;
    762         double fontval;
    763 
    764         if (fontstr[0] == '-')
    765                 pattern = XftXlfdParse(fontstr, False, False);
    766         else
    767                 pattern = FcNameParse((const FcChar8 *)fontstr);
    768 
    769         if (!pattern)
    770                 die("can't open font %s\n", fontstr);
    771 
    772         if (fontsize > 1) {
    773                 FcPatternDel(pattern, FC_PIXEL_SIZE);
    774                 FcPatternDel(pattern, FC_SIZE);
    775                 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize);
    776                 usedfontsize = fontsize;
    777         } else {
    778                 if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
    779                     FcResultMatch) {
    780                         usedfontsize = fontval;
    781                 } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
    782                            FcResultMatch) {
    783                         usedfontsize = -1;
    784                 } else {
    785                         /*
    786                          * Default font size is 12, if none given. This is to
    787                          * have a known usedfontsize value.
    788                          */
    789                         FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
    790                         usedfontsize = 12;
    791                 }
    792                 defaultfontsize = usedfontsize;
    793         }
    794 
    795         if (xloadfont(&dc.font, pattern))
    796                 die("can't open font %s\n", fontstr);
    797 
    798         if (usedfontsize < 0) {
    799                 FcPatternGetDouble(dc.font.match->pattern,
    800                                    FC_PIXEL_SIZE, 0, &fontval);
    801                 usedfontsize = fontval;
    802                 if (fontsize == 0)
    803                         defaultfontsize = fontval;
    804         }
    805 
    806         /* Setting character width and height. */
    807         win.cw = ceilf(dc.font.width * cw_scale);
    808         win.ch = ceilf(dc.font.height * ch_scale);
    809 
    810         FcPatternDel(pattern, FC_SLANT);
    811         FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
    812         if (xloadfont(&dc.ifont, pattern))
    813                 die("can't open font %s\n", fontstr);
    814 
    815         FcPatternDel(pattern, FC_WEIGHT);
    816         FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
    817         if (xloadfont(&dc.ibfont, pattern))
    818                 die("can't open font %s\n", fontstr);
    819 
    820         FcPatternDel(pattern, FC_SLANT);
    821         FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
    822         if (xloadfont(&dc.bfont, pattern))
    823                 die("can't open font %s\n", fontstr);
    824 
    825         FcPatternDestroy(pattern);
    826 }
    827 
    828 int
    829 ximopen(Display *dpy)
    830 {
    831         XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };
    832         XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };
    833 
    834         xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
    835         if (xw.ime.xim == NULL)
    836                 return 0;
    837 
    838         if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL))
    839                 fprintf(stderr, "XSetIMValues: "
    840                         "Could not set XNDestroyCallback.\n");
    841 
    842         xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot,
    843                                               NULL);
    844 
    845         if (xw.ime.xic == NULL) {
    846                 xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle,
    847                                        XIMPreeditNothing | XIMStatusNothing,
    848                                        XNClientWindow, xw.win,
    849                                        XNDestroyCallback, &icdestroy,
    850                                        NULL);
    851         }
    852         if (xw.ime.xic == NULL)
    853                 fprintf(stderr, "XCreateIC: Could not create input context.\n");
    854 
    855         return 1;
    856 }
    857 
    858 void
    859 ximinstantiate(Display *dpy, XPointer client, XPointer call)
    860 {
    861         if (ximopen(dpy))
    862                 XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
    863                                                  ximinstantiate, NULL);
    864 }
    865 
    866 void
    867 ximdestroy(XIM xim, XPointer client, XPointer call)
    868 {
    869         xw.ime.xim = NULL;
    870         XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
    871                                        ximinstantiate, NULL);
    872         XFree(xw.ime.spotlist);
    873 }
    874 
    875 int
    876 xicdestroy(XIC xim, XPointer client, XPointer call)
    877 {
    878         xw.ime.xic = NULL;
    879         return 1;
    880 }
    881 
    882 void
    883 xinit(int cols, int rows)
    884 {
    885         XGCValues gcvalues;
    886         Cursor cursor;
    887         Window parent;
    888         pid_t thispid = getpid();
    889         XColor xmousefg, xmousebg;
    890 
    891         if (!(xw.dpy = XOpenDisplay(NULL)))
    892                 die("can't open display\n");
    893         xw.scr = XDefaultScreen(xw.dpy);
    894         xw.vis = XDefaultVisual(xw.dpy, xw.scr);
    895 
    896         /* font */
    897         if (!FcInit())
    898                 die("could not init fontconfig.\n");
    899 
    900         xloadfonts(fontconfig, 0);
    901 
    902         /* colors */
    903         xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
    904         xloadcols();
    905 
    906         /* adjust fixed window geometry */
    907         win.w = 2 * border_px + cols * win.cw;
    908         win.h = 2 * border_px + rows * win.ch;
    909         if (xw.gm & XNegative)
    910                 xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
    911         if (xw.gm & YNegative)
    912                 xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2;
    913 
    914         /* Events */
    915         xw.attrs.background_pixel = dc.col[default_attributes.bg].pixel;
    916         xw.attrs.border_pixel = dc.col[default_attributes.bg].pixel;
    917         xw.attrs.bit_gravity = NorthWestGravity;
    918         xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask
    919                 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
    920                 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
    921         xw.attrs.colormap = xw.cmap;
    922 
    923         parent = XRootWindow(xw.dpy, xw.scr);
    924         xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
    925                                win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
    926                                xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
    927                                | CWEventMask | CWColormap, &xw.attrs);
    928 
    929         memset(&gcvalues, 0, sizeof(gcvalues));
    930         gcvalues.graphics_exposures = False;
    931         dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
    932                           &gcvalues);
    933         xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
    934                                DefaultDepth(xw.dpy, xw.scr));
    935         XSetForeground(xw.dpy, dc.gc, dc.col[default_attributes.bg].pixel);
    936         XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
    937 
    938         /* font spec buffer */
    939         xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
    940 
    941         /* Xft rendering context */
    942         xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
    943 
    944         /* input methods */
    945         if (!ximopen(xw.dpy)) {
    946                 XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
    947                                                ximinstantiate, NULL);
    948         }
    949 
    950         /* white cursor, black outline */
    951         cursor = XCreateFontCursor(xw.dpy, XC_xterm);
    952         XDefineCursor(xw.dpy, xw.win, cursor);
    953 
    954         if (XParseColor(xw.dpy, xw.cmap, colors[cursor_fg], &xmousefg) == 0) {
    955                 xmousefg.red   = 0xffff;
    956                 xmousefg.green = 0xffff;
    957                 xmousefg.blue  = 0xffff;
    958         }
    959 
    960         if (XParseColor(xw.dpy, xw.cmap, colors[cursor_bg], &xmousebg) == 0) {
    961                 xmousebg.red   = 0x0000;
    962                 xmousebg.green = 0x0000;
    963                 xmousebg.blue  = 0x0000;
    964         }
    965 
    966         XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg);
    967 
    968         xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
    969         xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
    970         xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
    971         xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False);
    972         XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
    973 
    974         xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
    975         XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
    976                         PropModeReplace, (uint8_t *)&thispid, 1);
    977 
    978         win.mode = MODE_NUMLOCK;
    979         xsettitle(NULL);
    980         xhints();
    981         XMapWindow(xw.dpy, xw.win);
    982         XSync(xw.dpy, False);
    983 
    984         xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
    985         if (xtarget == None)
    986                 xtarget = XA_STRING;
    987 }
    988 
    989 int
    990 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const struct glyph *glyphs, int len, int x, int y)
    991 {
    992         float winx = border_px + x * win.cw, winy = border_px + y * win.ch, xp, yp;
    993         unsigned short mode, prevmode = USHRT_MAX;
    994         Font *font = &dc.font;
    995         int frcflags = FRC_NORMAL;
    996         float runewidth = win.cw;
    997         rune_t rune;
    998         FT_UInt glyphidx;
    999         FcResult fcres;
   1000         FcPattern *fcpattern, *fontpattern;
   1001         FcFontSet *fcsets[] = { NULL };
   1002         FcCharSet *fccharset;
   1003         int i, f, numspecs = 0;
   1004 
   1005         for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
   1006                 /* Fetch rune and mode for current struct glyph. */
   1007                 rune = glyphs[i].u;
   1008                 mode = glyphs[i].mode;
   1009 
   1010                 /* Skip dummy wide-character spacing. */
   1011                 if (mode & ATTR_WDUMMY)
   1012                         continue;
   1013 
   1014                 /* Determine font for struct glyph if different from previous struct glyph. */
   1015                 if (prevmode != mode) {
   1016                         prevmode = mode;
   1017                         font = &dc.font;
   1018                         frcflags = FRC_NORMAL;
   1019                         runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
   1020                         if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
   1021                                 font = &dc.ibfont;
   1022                                 frcflags = FRC_ITALICBOLD;
   1023                         } else if (mode & ATTR_ITALIC) {
   1024                                 font = &dc.ifont;
   1025                                 frcflags = FRC_ITALIC;
   1026                         } else if (mode & ATTR_BOLD) {
   1027                                 font = &dc.bfont;
   1028                                 frcflags = FRC_BOLD;
   1029                         }
   1030                         yp = winy + font->ascent;
   1031                 }
   1032 
   1033                 /* Lookup character index with default font. */
   1034                 glyphidx = XftCharIndex(xw.dpy, font->match, rune);
   1035                 if (glyphidx) {
   1036                         specs[numspecs].font = font->match;
   1037                         specs[numspecs].glyph = glyphidx;
   1038                         specs[numspecs].x = (short)xp;
   1039                         specs[numspecs].y = (short)yp;
   1040                         xp += runewidth;
   1041                         numspecs++;
   1042                         continue;
   1043                 }
   1044 
   1045                 /* Fallback on font cache, search the font cache for match. */
   1046                 for (f = 0; f < frclen; f++) {
   1047                         glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
   1048                         /* Everything correct. */
   1049                         if (glyphidx && frc[f].flags == frcflags)
   1050                                 break;
   1051                         /* We got a default font for a not found struct glyph. */
   1052                         if (!glyphidx && frc[f].flags == frcflags
   1053                             && frc[f].unicodep == rune) {
   1054                                 break;
   1055                         }
   1056                 }
   1057 
   1058                 /* Nothing was found. Use fontconfig to find matching font. */
   1059                 if (f >= frclen) {
   1060                         if (!font->set)
   1061                                 font->set = FcFontSort(0, font->pattern,
   1062                                                        1, 0, &fcres);
   1063                         fcsets[0] = font->set;
   1064 
   1065                         /*
   1066                          * Nothing was found in the cache. Now use
   1067                          * some dozen of Fontconfig calls to get the
   1068                          * font for one single character.
   1069                          *
   1070                          * Xft and fontconfig are design failures.
   1071                          */
   1072                         fcpattern = FcPatternDuplicate(font->pattern);
   1073                         fccharset = FcCharSetCreate();
   1074 
   1075                         FcCharSetAddChar(fccharset, rune);
   1076                         FcPatternAddCharSet(fcpattern, FC_CHARSET,
   1077                                             fccharset);
   1078                         FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
   1079 
   1080                         FcConfigSubstitute(0, fcpattern,
   1081                                            FcMatchPattern);
   1082                         FcDefaultSubstitute(fcpattern);
   1083 
   1084                         fontpattern = FcFontSetMatch(0, fcsets, 1,
   1085                                                      fcpattern, &fcres);
   1086 
   1087                         /* Allocate memory for the new cache entry. */
   1088                         if (frclen >= frccap) {
   1089                                 frccap += 16;
   1090                                 frc = xrealloc(frc, frccap * sizeof(Fontcache));
   1091                         }
   1092 
   1093                         frc[frclen].font = XftFontOpenPattern(xw.dpy,
   1094                                                               fontpattern);
   1095                         if (!frc[frclen].font)
   1096                                 die("XftFontOpenPattern failed seeking fallback font: %s\n",
   1097                                     strerror(errno));
   1098                         frc[frclen].flags = frcflags;
   1099                         frc[frclen].unicodep = rune;
   1100 
   1101                         glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
   1102 
   1103                         f = frclen;
   1104                         frclen++;
   1105 
   1106                         FcPatternDestroy(fcpattern);
   1107                         FcCharSetDestroy(fccharset);
   1108                 }
   1109 
   1110                 specs[numspecs].font = frc[f].font;
   1111                 specs[numspecs].glyph = glyphidx;
   1112                 specs[numspecs].x = (short)xp;
   1113                 specs[numspecs].y = (short)yp;
   1114                 xp += runewidth;
   1115                 numspecs++;
   1116         }
   1117 
   1118         return numspecs;
   1119 }
   1120 
   1121 void
   1122 xdrawglyphfontspecs(const XftGlyphFontSpec *specs, struct glyph base, int len, int x, int y)
   1123 {
   1124         int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
   1125         int winx = border_px + x * win.cw, winy = border_px + y * win.ch,
   1126                 width = charlen * win.cw;
   1127         Color *fg, *bg, *temp, revfg, truefg, truebg;
   1128         XRenderColor colfg, colbg;
   1129         XRectangle r;
   1130 
   1131         /* Fallback on color display for attributes not supported by the font */
   1132         if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) {
   1133                 if (dc.ibfont.badslant || dc.ibfont.badweight)
   1134                         base.fg = default_attributes.fg;
   1135         } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) ||
   1136                    (base.mode & ATTR_BOLD && dc.bfont.badweight)) {
   1137                 base.fg = default_attributes.fg;
   1138         }
   1139 
   1140         if (IS_TRUECOL(base.fg)) {
   1141                 colfg.alpha = 0xffff;
   1142                 colfg.red = TRUERED(base.fg);
   1143                 colfg.green = TRUEGREEN(base.fg);
   1144                 colfg.blue = TRUEBLUE(base.fg);
   1145                 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg);
   1146                 fg = &truefg;
   1147         } else {
   1148                 fg = &dc.col[base.fg];
   1149         }
   1150 
   1151         if (IS_TRUECOL(base.bg)) {
   1152                 colbg.alpha = 0xffff;
   1153                 colbg.green = TRUEGREEN(base.bg);
   1154                 colbg.red = TRUERED(base.bg);
   1155                 colbg.blue = TRUEBLUE(base.bg);
   1156                 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg);
   1157                 bg = &truebg;
   1158         } else {
   1159                 bg = &dc.col[base.bg];
   1160         }
   1161 
   1162         /* Change basic system colors [0-7] to bright system colors [8-15] */
   1163         if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
   1164                 fg = &dc.col[base.fg + 8];
   1165 
   1166         if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {
   1167                 colfg.red = fg->color.red / 2;
   1168                 colfg.green = fg->color.green / 2;
   1169                 colfg.blue = fg->color.blue / 2;
   1170                 colfg.alpha = fg->color.alpha;
   1171                 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
   1172                 fg = &revfg;
   1173         }
   1174 
   1175         if (base.mode & ATTR_REVERSE) {
   1176                 temp = fg;
   1177                 fg = bg;
   1178                 bg = temp;
   1179         }
   1180 
   1181         if (base.mode & ATTR_INVISIBLE)
   1182                 fg = bg;
   1183 
   1184         /* Intelligent cleaning up of the borders. */
   1185         if (x == 0) {
   1186                 xclear(0, (y == 0)? 0 : winy, border_px,
   1187                        winy + win.ch +
   1188                        ((winy + win.ch >= border_px + win.th)? win.h : 0));
   1189         }
   1190         if (winx + width >= border_px + win.tw) {
   1191                 xclear(winx + width, (y == 0)? 0 : winy, win.w,
   1192                        ((winy + win.ch >= border_px + win.th)? win.h : (winy + win.ch)));
   1193         }
   1194         if (y == 0)
   1195                 xclear(winx, 0, winx + width, border_px);
   1196         if (winy + win.ch >= border_px + win.th)
   1197                 xclear(winx, winy + win.ch, winx + width, win.h);
   1198 
   1199         /* Clean up the region we want to draw to. */
   1200         XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
   1201 
   1202         /* Set the clip region because Xft is sometimes dirty. */
   1203         r.x = 0;
   1204         r.y = 0;
   1205         r.height = win.ch;
   1206         r.width = width;
   1207         XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
   1208 
   1209         /* Render the glyphs. */
   1210         XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
   1211 
   1212         /* Render underline and strikethrough. */
   1213         if (base.mode & ATTR_UNDERLINE) {
   1214                 XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
   1215                             width, 1);
   1216         }
   1217 
   1218         if (base.mode & ATTR_STRUCK) {
   1219                 XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,
   1220                             width, 1);
   1221         }
   1222 
   1223         /* Reset clip to none. */
   1224         XftDrawSetClip(xw.draw, 0);
   1225 }
   1226 
   1227 void
   1228 xdrawglyph(struct glyph g, int x, int y)
   1229 {
   1230         int numspecs;
   1231         XftGlyphFontSpec spec;
   1232 
   1233         numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
   1234         xdrawglyphfontspecs(&spec, g, numspecs, x, y);
   1235 }
   1236 
   1237 void
   1238 xdrawcursor(int cx, int cy, int focused)
   1239 {
   1240         LIMIT(cx, 0, screen.col-1);
   1241         LIMIT(cy, 0, screen.row-1);
   1242         struct glyph g = screen.lines[cy][cx];
   1243         if (IS_SET(MODE_HIDE)) return;
   1244 
   1245         g.mode &= ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE;
   1246         g.fg = cursor_bg;
   1247         g.bg = cursor_fg;
   1248         Color drawcol = dc.col[g.bg];
   1249 
   1250         /* draw the new one */
   1251         if (IS_SET(MODE_FOCUSED) && !(get_fb(focused_window)->mode & FB_SELECTION_ON) && focused) {
   1252                 switch (cursor_shape) {
   1253                 case 0: // Blinking Block
   1254                 case 1: // Blinking Block (Default)
   1255                 case 2: // Steady Block
   1256                         xdrawglyph(g, cx, cy);
   1257                         break;
   1258                 case 3: // Blinking Underline
   1259                 case 4: // Steady Underline
   1260                         XftDrawRect(xw.draw, &drawcol,
   1261                                     border_px + cx * win.cw,
   1262                                     border_px + (cy + 1) * win.ch - \
   1263                                     cursor_thickness,
   1264                                     win.cw, cursor_thickness);
   1265                         break;
   1266                 case 5: // Blinking bar
   1267                 case 6: // Steady bar
   1268                         XftDrawRect(xw.draw, &drawcol,
   1269                                     border_px + cx * win.cw,
   1270                                     border_px + cy * win.ch,
   1271                                     cursor_thickness, win.ch);
   1272                         break;
   1273                 }
   1274         } else {
   1275                 XftDrawRect(xw.draw, &drawcol,
   1276                             border_px + cx * win.cw,
   1277                             border_px + cy * win.ch,
   1278                             win.cw - 1, 1);
   1279                 XftDrawRect(xw.draw, &drawcol,
   1280                             border_px + cx * win.cw,
   1281                             border_px + cy * win.ch,
   1282                             1, win.ch - 1);
   1283                 XftDrawRect(xw.draw, &drawcol,
   1284                             border_px + (cx + 1) * win.cw - 1,
   1285                             border_px + cy * win.ch,
   1286                             1, win.ch - 1);
   1287                 XftDrawRect(xw.draw, &drawcol,
   1288                             border_px + cx * win.cw,
   1289                             border_px + (cy + 1) * win.ch - 1,
   1290                             win.cw, 1);
   1291         }
   1292 }
   1293 
   1294 void
   1295 xseticontitle(char *p)
   1296 {
   1297         XTextProperty prop;
   1298         DEFAULT(p, "se");
   1299 
   1300         if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
   1301                                         &prop) != Success)
   1302                 return;
   1303         XSetWMIconName(xw.dpy, xw.win, &prop);
   1304         XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname);
   1305         XFree(prop.value);
   1306 }
   1307 
   1308 void
   1309 xsettitle(char *p)
   1310 {
   1311         XTextProperty prop;
   1312         DEFAULT(p, "se");
   1313 
   1314         if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
   1315                                         &prop) != Success)
   1316                 return;
   1317         XSetWMName(xw.dpy, xw.win, &prop);
   1318         XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
   1319         XFree(prop.value);
   1320 }
   1321 
   1322 void
   1323 xdrawline(int x1, int y1, int x2)
   1324 {
   1325         LIMIT(y1, 0, screen.row);
   1326         LIMIT(x2, 0, screen.col);
   1327         LIMIT(x1, 0, x2);
   1328         struct glyph* line = screen.lines[y1];
   1329         int i, x, ox, numspecs;
   1330         struct glyph base, new;
   1331         XftGlyphFontSpec *specs = xw.specbuf;
   1332 
   1333         numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
   1334         i = ox = 0;
   1335         for (x = x1; x < x2 && i < numspecs; x++) {
   1336                 new = line[x];
   1337                 if (new.mode & ATTR_WDUMMY)
   1338                         continue;
   1339                 if (i > 0 && ATTRCMP(base, new)) {
   1340                         xdrawglyphfontspecs(specs, base, i, ox, y1);
   1341                         specs += i;
   1342                         numspecs -= i;
   1343                         i = 0;
   1344                 }
   1345                 if (i == 0) {
   1346                         ox = x;
   1347                         base = new;
   1348                 }
   1349                 i++;
   1350         }
   1351         if (i > 0)
   1352                 xdrawglyphfontspecs(specs, base, i, ox, y1);
   1353 }
   1354 
   1355 void xsetenv(void) {
   1356         char buf[sizeof(long) * 8 + 1];
   1357         snprintf(buf, sizeof(buf), "%lu", xw.win);
   1358         setenv("WINDOWID", buf, 1);
   1359 }
   1360 
   1361 int xstartdraw(void) {return IS_SET(MODE_VISIBLE);}
   1362 
   1363 void xfinishdraw(void) {
   1364         XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, win.h, 0, 0);
   1365         XSetForeground(xw.dpy, dc.gc, dc.col[default_attributes.bg].pixel);
   1366 }
   1367 
   1368 void expose(XEvent *ev) {} // do nothing
   1369 
   1370 void visibility(XEvent *ev) {
   1371         XVisibilityEvent *e = &ev->xvisibility;
   1372         MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE);
   1373 }
   1374 
   1375 void unmap(XEvent *ev) {win.mode &= ~MODE_VISIBLE;}
   1376 
   1377 void
   1378 xsetpointermotion(int set)
   1379 {
   1380         MODBIT(xw.attrs.event_mask, set, PointerMotionMask);
   1381         XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
   1382 }
   1383 
   1384 void xseturgency(int add) {
   1385         XWMHints *h = XGetWMHints(xw.dpy, xw.win);
   1386         MODBIT(h->flags, add, XUrgencyHint);
   1387         XSetWMHints(xw.dpy, xw.win, h);
   1388         XFree(h);
   1389 }
   1390 
   1391 void
   1392 xunloadfonts(void)
   1393 {
   1394         /* Free the loaded fonts in the font cache.  */
   1395         while (frclen > 0)
   1396                 XftFontClose(xw.dpy, frc[--frclen].font);
   1397 
   1398         xunloadfont(&dc.font);
   1399         xunloadfont(&dc.bfont);
   1400         xunloadfont(&dc.ifont);
   1401         xunloadfont(&dc.ibfont);
   1402 }
   1403 
   1404 void
   1405 xunloadfont(Font *f)
   1406 {
   1407         soft_assert(f, return;);
   1408         soft_assert(f->match, return;);
   1409         soft_assert(f->pattern, return;);
   1410         XftFontClose(xw.dpy, f->match);
   1411         FcPatternDestroy(f->pattern);
   1412         if (f->set)
   1413                 FcFontSetDestroy(f->set);
   1414 }
   1415 
   1416 void
   1417 focus(XEvent *ev)
   1418 {
   1419         XFocusChangeEvent *e = &ev->xfocus;
   1420 
   1421         if (e->mode == NotifyGrab)
   1422                 return;
   1423 
   1424         if (ev->type == FocusIn) {
   1425                 if (xw.ime.xic)
   1426                         XSetICFocus(xw.ime.xic);
   1427                 win.mode |= MODE_FOCUSED;
   1428                 xseturgency(0);
   1429         } else {
   1430                 if (xw.ime.xic)
   1431                         XUnsetICFocus(xw.ime.xic);
   1432                 win.mode &= ~MODE_FOCUSED;
   1433         }
   1434 }
   1435 
   1436 int
   1437 match(unsigned int mask, unsigned int state) {
   1438         const unsigned int ignoremod = Mod2Mask|XK_SWITCH_MOD;
   1439         return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
   1440 }
   1441 
   1442 static void
   1443 search_term_string_insert(const char* buf, int buflen)
   1444 {
   1445         static int first = 0;
   1446 
   1447         struct file_buffer* fb = get_fb(focused_window);
   1448         if (!buf) {
   1449                 first = 1;
   1450                 return;
   1451         }
   1452         if (first) {
   1453                 *fb->search_term = 0;
   1454                 first = 0;
   1455         }
   1456         if (buf[0] >= 32 || buflen > 1) {
   1457                 int len = strlen(fb->search_term);
   1458                 if (len + buflen + 1 > SEARCH_TERM_MAX_LEN)
   1459                         return;
   1460                 memcpy(fb->search_term + len, buf, buflen);
   1461                 fb->search_term[len + buflen] = 0;
   1462                 if (fb->mode & FB_SEARCH_BLOCKING_BACKWARDS)
   1463                         focused_window->cursor_offset = fb_seek_string_backwards(fb, focused_window->cursor_offset, fb->search_term);
   1464                 else
   1465                         focused_window->cursor_offset = fb_seek_string(fb, focused_window->cursor_offset, fb->search_term);
   1466                 writef_to_status_bar("search: %s", fb->search_term);
   1467         }
   1468 }
   1469 
   1470 
   1471 static int
   1472 search_term_actions(KeySym keysym, int modkey)
   1473 {
   1474         static int first = 0;
   1475         struct file_buffer* fb = get_fb(focused_window);
   1476         if (keysym == XK_Return || keysym == XK_Escape) {
   1477                 int count = fb_count_string_instances(fb, fb->search_term, 0, NULL);
   1478                 if (!count) {
   1479                         fb->mode &= ~FB_SEARCH_BLOCKING_MASK;
   1480                         writef_to_status_bar("no resulrs for \"%s\"", fb->search_term);
   1481                 } else {
   1482                         fb->mode &= ~FB_SEARCH_BLOCKING;
   1483                         fb->mode &= ~FB_SEARCH_BLOCKING_BACKWARDS;
   1484                         fb->mode |= FB_SEARCH_BLOCKING_IDLE;
   1485 
   1486                         writef_to_status_bar("%d results for \"%s\"", count, fb->search_term);
   1487                         if (fb->mode & FB_SEARCH_BLOCKING_BACKWARDS)
   1488                                 focused_window->cursor_offset = fb_seek_string_backwards(fb, focused_window->cursor_offset, fb->search_term);
   1489                         else
   1490                                 focused_window->cursor_offset = fb_seek_string(fb, focused_window->cursor_offset, fb->search_term);
   1491                 }
   1492                 first = 1;
   1493                 search_term_string_insert(NULL, 0);
   1494                 return 1;
   1495         }
   1496         if (keysym == XK_BackSpace) {
   1497                 if (first) {
   1498                         first = 0;
   1499                         *fb->search_term = 0;
   1500                 } else {
   1501                         utf8_remove_string_end(fb->search_term);
   1502                         focused_window->cursor_offset = wb_seek_string_wrap(focused_window, focused_window->cursor_offset, fb->search_term);
   1503                 }
   1504                 writef_to_status_bar("search: %s", fb->search_term);
   1505                 return 1;
   1506         }
   1507         first = 0;
   1508         return 0;
   1509 }
   1510 
   1511 
   1512 void
   1513 kpress(XEvent *ev)
   1514 {
   1515         XKeyEvent *e = &ev->xkey;
   1516         KeySym ksym;
   1517         char buf[64];
   1518         int len;
   1519         rune_t c;
   1520         Status status;
   1521 
   1522         if (IS_SET(MODE_KBDLOCK))
   1523                 return;
   1524 
   1525         if (xw.ime.xic)
   1526                 len = XmbLookupString(xw.ime.xic, e, buf, sizeof(buf), &ksym, &status);
   1527         else
   1528                 len = XLookupString(e, buf, sizeof(buf), &ksym, NULL);
   1529         if (len == 1 && e->state & Mod1Mask) {
   1530                 if (*buf < 0177) {
   1531                         c = *buf | 0x80;
   1532                         len = utf8_encode(c, buf);
   1533                 }
   1534         }
   1535 
   1536         const struct file_buffer* fb = get_fb(focused_window);
   1537         // keysym callback
   1538         if (fb->mode & FB_SEARCH_BLOCKING) {
   1539                 if (search_term_actions(ksym, e->state))
   1540                         return;
   1541         }
   1542         if (fb->mode & FB_SEARCH_BLOCKING) {
   1543                 search_term_string_insert(buf, len);
   1544                 return;
   1545         }
   1546 
   1547         if (focused_window->mode != WB_NORMAL) {
   1548                 int override = 0;
   1549                 call_extension(wn_custom_window_keypress_override, &override, focused_node, ksym, e->state, buf, len);
   1550                 if (override)
   1551                         return;
   1552 
   1553                 int wn_custom_window_keypress_override_callback_exists = 0;
   1554                 extension_callback_exists(wn_custom_window_keypress_override, wn_custom_window_keypress_override_callback_exists = 1;);
   1555                 soft_assert(wn_custom_window_keypress_override_callback_exists, );
   1556         }
   1557 
   1558         call_extension(keypress, ksym, e->state, buf, len);
   1559 }
   1560 
   1561 void
   1562 cmessage(XEvent *e)
   1563 {
   1564         // See xembed specs
   1565         //  http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
   1566         if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
   1567                 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
   1568                         win.mode |= MODE_FOCUSED;
   1569                         xseturgency(0);
   1570                 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
   1571                         win.mode &= ~MODE_FOCUSED;
   1572                 }
   1573         } else if (e->xclient.data.l[0] == xw.wmdeletewin) {
   1574                 exit(0);
   1575         }
   1576 }
   1577 
   1578 void
   1579 resize(XEvent *e)
   1580 {
   1581         if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)
   1582                 return;
   1583 
   1584         cresize(e->xconfigure.width, e->xconfigure.height);
   1585         writef_to_status_bar("window resize: %d:%d", screen.col, screen.row);
   1586 }
   1587 
   1588 void
   1589 run(void)
   1590 {
   1591         XEvent ev;
   1592         int w = win.w, h = win.h;
   1593 
   1594         // Waiting for window mapping
   1595         do {
   1596                 XNextEvent(xw.dpy, &ev);
   1597                 /*
   1598                  * This XFilterEvent call is required because of XOpenIM. It
   1599                  * does filter out the key event and some client message for
   1600                  * the input method too.
   1601                  */
   1602                 if (XFilterEvent(&ev, None))
   1603                         continue;
   1604                 if (ev.type == ConfigureNotify) {
   1605                         w = ev.xconfigure.width;
   1606                         h = ev.xconfigure.height;
   1607                 }
   1608         } while (ev.type != MapNotify);
   1609 
   1610         cresize(w, h);
   1611 
   1612         for (;;) {
   1613                 int xev = 0;
   1614                 while (XPending(xw.dpy)) {
   1615                         XNextEvent(xw.dpy, &ev);
   1616                         if (XFilterEvent(&ev, None))
   1617                                 continue;
   1618                         if (handler[ev.type]) {
   1619                                 (handler[ev.type])(&ev);
   1620                                 xev = 1;
   1621                         }
   1622                 }
   1623 
   1624                 if (!xev) {
   1625                         nanosleep(&(struct timespec){.tv_nsec = 1e6}, NULL);
   1626                         continue;
   1627                 }
   1628 
   1629                 screen_set_region(0, 0, screen.col-1, screen.row-1, ' ');
   1630                 if (screen.row-2 >= 0)
   1631                         window_node_draw_tree_to_screen(&root_node, 0, 0, screen.col-1, screen.row-1);
   1632                 draw_status_bar();
   1633 
   1634                 xfinishdraw();
   1635                 XFlush(xw.dpy);
   1636         }
   1637 }
   1638 
   1639 int
   1640 main(int argc, char *argv[])
   1641 {
   1642         xw.l = xw.t = 0;
   1643         xw.isfixed = False;
   1644 
   1645         setlocale(LC_CTYPE, "");
   1646         XSetLocaleModifiers("");
   1647         int cols = MAX(default_cols, 1);
   1648         int rows = MAX(default_rows, 1);
   1649         screen_init(cols, rows);
   1650         xinit(cols, rows);
   1651         xsetenv();
   1652 
   1653         if (argc <= 1) {
   1654                 *focused_window = wb_new(fb_new_entry(NULL));
   1655         } else  {
   1656                 int master_stack = 1;
   1657                 for (int i = 1; i < argc; i++) {
   1658                         if (*argv[i] == '-') {
   1659                                 i++;
   1660                         } else {
   1661                                 if (master_stack < 0) {
   1662                                         window_node_split(focused_node, 0.5, WINDOW_HORISONTAL);
   1663                                         master_stack = 0;
   1664                                 } else if (master_stack > 0) {
   1665                                         *focused_window = wb_new(fb_new_entry(argv[i]));
   1666                                         master_stack = -1;
   1667                                         continue;
   1668                                 } else {
   1669                                         window_node_split(focused_node, 0.5, WINDOW_VERTICAL);
   1670                                 }
   1671 
   1672                                 if (focused_node->node2) {
   1673                                         focused_node = focused_node->node2;
   1674                                         focused_window = &focused_node->wb;
   1675                                         if (!master_stack)
   1676                                                 *focused_window = wb_new(fb_new_entry(argv[i]));
   1677                                 }
   1678                                 master_stack = 0;
   1679                         }
   1680                 }
   1681         }
   1682 
   1683         srand(time(NULL));
   1684 
   1685         // TODO: start screen extension
   1686 
   1687         if (extensions) {
   1688                 for (int i = 0; !extensions[i].end; i++) {
   1689                         if (extensions[i].e.init)
   1690                                 extensions[i].e.init(&extensions[i].e);
   1691                         if (extensions[i].e.enable)
   1692                                 extensions[i].e.enable();
   1693                 }
   1694         }
   1695 
   1696         run();
   1697 
   1698         return 0;
   1699 }