st

st fork
git clone git://git.rr3.xyz/st
Log | Files | Refs | README | LICENSE

x.c (46118B)


      1 /* See LICENSE for license details. */
      2 #include <errno.h>
      3 #include <math.h>
      4 #include <limits.h>
      5 #include <locale.h>
      6 #include <signal.h>
      7 #include <stdint.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <sys/select.h>
     12 #include <time.h>
     13 #include <unistd.h>
     14 #include <libgen.h>
     15 #include <X11/Xatom.h>
     16 #include <X11/Xlib.h>
     17 #include <X11/keysym.h>
     18 #include <X11/Xft/Xft.h>
     19 #include <X11/XKBlib.h>
     20 
     21 char *argv0;
     22 #include "arg.h"
     23 #include "util.h"
     24 #include "config.h"
     25 #include "st.h"
     26 #include "win.h"
     27 
     28 /* XEMBED messages */
     29 #define XEMBED_FOCUS_IN  4
     30 #define XEMBED_FOCUS_OUT 5
     31 
     32 #define IS_SET(flag) ((win.mode & (flag)) != 0)
     33 #define TRUERED(x)   (((x) & 0xff0000) >> 8)
     34 #define TRUEGREEN(x) (((x) & 0xff00))
     35 #define TRUEBLUE(x)  (((x) & 0xff) << 8)
     36 
     37 typedef XftDraw *Draw;
     38 typedef XftColor Color;
     39 typedef XftGlyphFontSpec GlyphFontSpec;
     40 
     41 /* Purely graphic info */
     42 typedef struct {
     43 	int tw, th; /* tty width and height */
     44 	int w, h; /* window width and height */
     45 	int ch; /* char height */
     46 	int cw; /* char width  */
     47 	uint mode; /* window state/mode flags */
     48 	int cursor; /* cursor style */
     49 } TermWindow;
     50 
     51 typedef struct {
     52 	Display *dpy;
     53 	Colormap cmap;
     54 	Window win;
     55 	Drawable buf;
     56 	GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
     57 	Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
     58 	struct {
     59 		XIM xim;
     60 		XIC xic;
     61 		XPoint spot;
     62 		XVaNestedList spotlist;
     63 	} ime;
     64 	Draw draw;
     65 	Visual *vis;
     66 	XSetWindowAttributes attrs;
     67 	int scr;
     68 	int isfixed; /* is fixed geometry? */
     69 	int l, t; /* left and top offset */
     70 	int gm; /* geometry mask */
     71 } XWindow;
     72 
     73 typedef struct {
     74 	Atom xtarget;
     75 	char *primary, *clipboard;
     76 	struct timespec tclick1;
     77 	struct timespec tclick2;
     78 } XSelection;
     79 
     80 /* Font structure */
     81 #define Font Font_
     82 typedef struct {
     83 	int height;
     84 	int width;
     85 	int ascent;
     86 	int descent;
     87 	int badslant;
     88 	int badweight;
     89 	short lbearing;
     90 	short rbearing;
     91 	XftFont *match;
     92 	FcFontSet *set;
     93 	FcPattern *pattern;
     94 } Font;
     95 
     96 /* Drawing Context */
     97 typedef struct {
     98 	Color *col;
     99 	size_t collen;
    100 	Font font, bfont, ifont, ibfont;
    101 	GC gc;
    102 } DC;
    103 
    104 static inline ushort sixd_to_16bit(int);
    105 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
    106 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
    107 static void xdrawglyph(Glyph, int, int);
    108 static void xclear(int, int, int, int);
    109 static int xgeommasktogravity(int);
    110 static int ximopen(Display *);
    111 static void ximinstantiate(Display *, XPointer, XPointer);
    112 static void ximdestroy(XIM, XPointer, XPointer);
    113 static int xicdestroy(XIC, XPointer, XPointer);
    114 static void xinit(int, int);
    115 static void cresize(int, int);
    116 static void xresize(int, int);
    117 static void xhints(void);
    118 static int xloadcolor(int, const char *, Color *);
    119 static int xloadfont(Font *, FcPattern *);
    120 static void xloadfonts(const char *, double);
    121 static void xunloadfont(Font *);
    122 static void xunloadfonts(void);
    123 static void xsetenv(void);
    124 static void xseturgency(int);
    125 static int evcol(XEvent *);
    126 static int evrow(XEvent *);
    127 
    128 static void expose(XEvent *);
    129 static void visibility(XEvent *);
    130 static void unmap(XEvent *);
    131 static int handlesym(KeySym, uint);
    132 static void kaction(XKeyEvent *, int);
    133 static void kpress(XEvent *);
    134 static void krelease(XEvent *);
    135 static void cmessage(XEvent *);
    136 static void resize(XEvent *);
    137 static void focus(XEvent *);
    138 static int baction(XButtonEvent *, int);
    139 static void brelease(XEvent *);
    140 static void bpress(XEvent *);
    141 static void bmotion(XEvent *);
    142 static void propnotify(XEvent *);
    143 static void selnotify(XEvent *);
    144 static void selclear_(XEvent *);
    145 static void selrequest(XEvent *);
    146 static void setsel(char *, Time);
    147 static void mousesel(XEvent *, int);
    148 static void mousereport(XEvent *);
    149 
    150 static void run(void);
    151 static void usage(void);
    152 
    153 static void (*handler[LASTEvent])(XEvent *) = {
    154 	[KeyPress] = kpress,
    155 	[KeyRelease] = krelease,
    156 	[ClientMessage] = cmessage,
    157 	[ConfigureNotify] = resize,
    158 	[VisibilityNotify] = visibility,
    159 	[UnmapNotify] = unmap,
    160 	[Expose] = expose,
    161 	[FocusIn] = focus,
    162 	[FocusOut] = focus,
    163 	[MotionNotify] = bmotion,
    164 	[ButtonPress] = bpress,
    165 	[ButtonRelease] = brelease,
    166 /* Uncomment if you want the selection to disappear when you select something
    167  * different in another window. */
    168 /*	[SelectionClear] = selclear_, */
    169 	[SelectionNotify] = selnotify,
    170 	/* PropertyNotify is only turned on when there is some INCR transfer
    171 	 * happening for the selection retrieval. */
    172 	[PropertyNotify] = propnotify,
    173 	[SelectionRequest] = selrequest,
    174 };
    175 
    176 /* Globals */
    177 static DC dc;
    178 static XWindow xw;
    179 static XSelection xsel;
    180 static TermWindow win;
    181 
    182 /* Font Ring Cache */
    183 enum {
    184 	FRC_NORMAL,
    185 	FRC_ITALIC,
    186 	FRC_BOLD,
    187 	FRC_ITALICBOLD
    188 };
    189 
    190 typedef struct {
    191 	XftFont *font;
    192 	int flags;
    193 	Rune unicodep;
    194 } Fontcache;
    195 
    196 /* Fontcache is an array now. A new font will be appended to the array. */
    197 static Fontcache *frc = NULL;
    198 static int frclen = 0;
    199 static int frccap = 0;
    200 static char *usedfont = NULL;
    201 static double usedfontsize = 0;
    202 static double defaultfontsize = 0;
    203 
    204 static char *opt_class = NULL;
    205 static char **opt_cmd  = NULL;
    206 static char *opt_embed = NULL;
    207 static char *opt_font  = NULL;
    208 static char *opt_io    = NULL;
    209 static char *opt_line  = NULL;
    210 static char *opt_name  = NULL;
    211 static char *opt_title = NULL;
    212 
    213 static int oldbutton = 3; /* button event on startup: 3 = release */
    214 
    215 /* Printable characters in ASCII. Used to estimate the advance width
    216  * of single wide characters. */
    217 static char ascii_printable[] =
    218 	" !\"#$%&'()*+,-./0123456789:;<=>?"
    219 	"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
    220 	"`abcdefghijklmnopqrstuvwxyz{|}~";
    221 
    222 void
    223 xclipcopy(void)
    224 {
    225 	Atom clipboard;
    226 
    227 	free(xsel.clipboard);
    228 	xsel.clipboard = NULL;
    229 
    230 	if (xsel.primary != NULL) {
    231 		xsel.clipboard = xstrdup(xsel.primary);
    232 		clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
    233 		XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
    234 	}
    235 }
    236 
    237 void
    238 xclippaste(void)
    239 {
    240 	Atom clipboard;
    241 
    242 	clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
    243 	XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard,
    244 			xw.win, CurrentTime);
    245 }
    246 
    247 void
    248 xselpaste(void)
    249 {
    250 	XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY,
    251 			xw.win, CurrentTime);
    252 }
    253 
    254 void
    255 xzoomabs(double fontsize)
    256 {
    257 	xunloadfonts();
    258 	xloadfonts(usedfont, fontsize);
    259 	cresize(0, 0);
    260 	redraw();
    261 	xhints();
    262 }
    263 
    264 void
    265 xzoomrel(double delta)
    266 {
    267 	xzoomabs(usedfontsize+delta);
    268 }
    269 
    270 void
    271 xzoomrst(void)
    272 {
    273 	if (defaultfontsize > 0)
    274 		xzoomabs(defaultfontsize);
    275 }
    276 
    277 int
    278 evcol(XEvent *e)
    279 {
    280 	int x = e->xbutton.x - borderpx;
    281 	LIMIT(x, 0, win.tw - 1);
    282 	return x / win.cw;
    283 }
    284 
    285 int
    286 evrow(XEvent *e)
    287 {
    288 	int y = e->xbutton.y - borderpx;
    289 	LIMIT(y, 0, win.th - 1);
    290 	return y / win.ch;
    291 }
    292 
    293 void
    294 mousesel(XEvent *e, int done)
    295 {
    296 	int type;
    297 	uint state;
    298 	SelType *st;
    299 
    300 	type = SEL_REGULAR;
    301 	state = confstate(e->xbutton.state, 0);
    302 	for (st = seltypes; st->type; st++) {
    303 		if (MATCH(state, st->set, st->clr)) {
    304 			type = st->type;
    305 			break;
    306 		}
    307 	}
    308 
    309 	selextend(evcol(e), evrow(e), type, done);
    310 	if (done)
    311 		setsel(getsel(), e->xbutton.time);
    312 }
    313 
    314 void
    315 mousereport(XEvent *e)
    316 {
    317 	int len, x = evcol(e), y = evrow(e),
    318 	    button = e->xbutton.button, state = e->xbutton.state;
    319 	char buf[40];
    320 	static int ox, oy;
    321 
    322 	/* from urxvt */
    323 	if (e->xbutton.type == MotionNotify) {
    324 		if (x == ox && y == oy)
    325 			return;
    326 		if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))
    327 			return;
    328 		/* MOUSE_MOTION: no reporting if no button is pressed */
    329 		if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3)
    330 			return;
    331 
    332 		button = oldbutton + 32;
    333 		ox = x;
    334 		oy = y;
    335 	} else {
    336 		if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) {
    337 			button = 3;
    338 		} else {
    339 			button -= Button1;
    340 			if (button >= 7)
    341 				button += 128 - 7;
    342 			else if (button >= 3)
    343 				button += 64 - 3;
    344 		}
    345 		if (e->xbutton.type == ButtonPress) {
    346 			oldbutton = button;
    347 			ox = x;
    348 			oy = y;
    349 		} else if (e->xbutton.type == ButtonRelease) {
    350 			oldbutton = 3;
    351 			/* MODE_MOUSEX10: no button release reporting */
    352 			if (IS_SET(MODE_MOUSEX10))
    353 				return;
    354 			if (button == 64 || button == 65)
    355 				return;
    356 		}
    357 	}
    358 
    359 	if (!IS_SET(MODE_MOUSEX10)) {
    360 		button += ((state & ShiftMask  ) ? 4  : 0)
    361 			+ ((state & Mod4Mask   ) ? 8  : 0)
    362 			+ ((state & ControlMask) ? 16 : 0);
    363 	}
    364 
    365 	if (IS_SET(MODE_MOUSESGR)) {
    366 		len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
    367 				button, x+1, y+1,
    368 				e->xbutton.type == ButtonRelease ? 'm' : 'M');
    369 	} else if (x < 223 && y < 223) {
    370 		len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
    371 				32+button, 32+x+1, 32+y+1);
    372 	} else {
    373 		return;
    374 	}
    375 
    376 	ttywrite(buf, len, 0);
    377 }
    378 
    379 int
    380 baction(XButtonEvent *e, int release)
    381 {
    382 	uint state;
    383 	Btn *b;
    384 
    385 	state = confstate(e->state, release);
    386 	for (b = btns; b->btn; b++) {
    387 		if (b->btn == e->button && MATCH(state, b->set, b->clr)) {
    388 			b->fn(state, b->arg);
    389 			return 1;
    390 		}
    391 	}
    392 
    393 	return 0;
    394 }
    395 
    396 void
    397 bpress(XEvent *e)
    398 {
    399 	struct timespec now;
    400 	int snap;
    401 
    402 	if (IS_SET(MODE_MOUSE)) {
    403 		mousereport(e);
    404 		return;
    405 	}
    406 
    407 	if (baction(&e->xbutton, 0))
    408 		return;
    409 
    410 	if (e->xbutton.button == Button1) {
    411 		/* If the user clicks below predefined timeouts specific
    412 		 * snapping behaviour is exposed. */
    413 		clock_gettime(CLOCK_MONOTONIC, &now);
    414 		if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) {
    415 			snap = SNAP_LINE;
    416 		} else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) {
    417 			snap = SNAP_WORD;
    418 		} else {
    419 			snap = 0;
    420 		}
    421 		xsel.tclick2 = xsel.tclick1;
    422 		xsel.tclick1 = now;
    423 
    424 		selstart(evcol(e), evrow(e), snap);
    425 	}
    426 }
    427 
    428 void
    429 brelease(XEvent *e)
    430 {
    431 	if (IS_SET(MODE_MOUSE)) {
    432 		mousereport(e);
    433 		return;
    434 	}
    435 
    436 	if (baction(&e->xbutton, 1))
    437 		return;
    438 	if (e->xbutton.button == Button1)
    439 		mousesel(e, 1);
    440 }
    441 
    442 void
    443 propnotify(XEvent *e)
    444 {
    445 	XPropertyEvent *xpev;
    446 	Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
    447 
    448 	xpev = &e->xproperty;
    449 	if (xpev->state == PropertyNewValue &&
    450 			(xpev->atom == XA_PRIMARY ||
    451 			 xpev->atom == clipboard)) {
    452 		selnotify(e);
    453 	}
    454 }
    455 
    456 void
    457 selnotify(XEvent *e)
    458 {
    459 	ulong nitems, ofs, rem;
    460 	int format;
    461 	uchar *data, *last, *repl;
    462 	Atom type, incratom, property = None;
    463 
    464 	incratom = XInternAtom(xw.dpy, "INCR", 0);
    465 
    466 	ofs = 0;
    467 	if (e->type == SelectionNotify)
    468 		property = e->xselection.property;
    469 	else if (e->type == PropertyNotify)
    470 		property = e->xproperty.atom;
    471 
    472 	if (property == None)
    473 		return;
    474 
    475 	do {
    476 		if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
    477 					BUFSIZ/4, False, AnyPropertyType,
    478 					&type, &format, &nitems, &rem,
    479 					&data)) {
    480 			fprintf(stderr, "clipboard allocation failed\n");
    481 			return;
    482 		}
    483 
    484 		if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
    485 			/* If there is some PropertyNotify with no data, then
    486 			 * this is the signal of the selection owner that all
    487 			 * data has been transferred. We won't need to receive
    488 			 * PropertyNotify events anymore. */
    489 			MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);
    490 			XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
    491 					&xw.attrs);
    492 		}
    493 
    494 		if (type == incratom) {
    495 			/* Activate the PropertyNotify events so we receive
    496 			 * when the selection owner does send us the next
    497 			 * chunk of data. */
    498 			MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
    499 			XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
    500 					&xw.attrs);
    501 
    502 			/* Deleting the property is the transfer start signal. */
    503 			XDeleteProperty(xw.dpy, xw.win, (int)property);
    504 			continue;
    505 		}
    506 
    507 		/* As seen in getsel:
    508 		 * Line endings are inconsistent in the terminal and GUI world
    509 		 * copy and pasting. When receiving some selection data,
    510 		 * replace all '\n' with '\r'.
    511 		 * FIXME: Fix the computer world. */
    512 		repl = data;
    513 		last = data + nitems * format / 8;
    514 		while ((repl = memchr(repl, '\n', last - repl))) {
    515 			*repl++ = '\r';
    516 		}
    517 
    518 		if (IS_SET(MODE_BRCKTPASTE) && ofs == 0)
    519 			ttywrite("\033[200~", 6, 0);
    520 		ttywrite((char *)data, nitems * format / 8, 1);
    521 		if (IS_SET(MODE_BRCKTPASTE) && rem == 0)
    522 			ttywrite("\033[201~", 6, 0);
    523 		XFree(data);
    524 		/* number of 32-bit chunks returned */
    525 		ofs += nitems * format / 32;
    526 	} while (rem > 0);
    527 
    528 	/* Deleting the property again tells the selection owner to send the
    529 	 * next data chunk in the property. */
    530 	XDeleteProperty(xw.dpy, xw.win, (int)property);
    531 }
    532 
    533 void
    534 selclear_(XEvent *e)
    535 {
    536 	selclear();
    537 }
    538 
    539 void
    540 selrequest(XEvent *e)
    541 {
    542 	XSelectionRequestEvent *xsre;
    543 	XSelectionEvent xev;
    544 	Atom xa_targets, string, clipboard;
    545 	char *seltext;
    546 
    547 	xsre = (XSelectionRequestEvent *) e;
    548 	xev.type = SelectionNotify;
    549 	xev.requestor = xsre->requestor;
    550 	xev.selection = xsre->selection;
    551 	xev.target = xsre->target;
    552 	xev.time = xsre->time;
    553 	if (xsre->property == None)
    554 		xsre->property = xsre->target;
    555 
    556 	/* reject */
    557 	xev.property = None;
    558 
    559 	xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
    560 	if (xsre->target == xa_targets) {
    561 		/* respond with the supported type */
    562 		string = xsel.xtarget;
    563 		XChangeProperty(xsre->display, xsre->requestor, xsre->property,
    564 				XA_ATOM, 32, PropModeReplace,
    565 				(uchar *) &string, 1);
    566 		xev.property = xsre->property;
    567 	} else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) {
    568 		/* xith XA_STRING non ascii characters may be incorrect in the
    569 		 * requestor. It is not our problem, use utf8. */
    570 		clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
    571 		if (xsre->selection == XA_PRIMARY) {
    572 			seltext = xsel.primary;
    573 		} else if (xsre->selection == clipboard) {
    574 			seltext = xsel.clipboard;
    575 		} else {
    576 			fprintf(stderr,
    577 					"unhandled clipboard selection 0x%lx\n",
    578 					xsre->selection);
    579 			return;
    580 		}
    581 		if (seltext != NULL) {
    582 			XChangeProperty(xsre->display, xsre->requestor,
    583 					xsre->property, xsre->target,
    584 					8, PropModeReplace,
    585 					(uchar *)seltext, strlen(seltext));
    586 			xev.property = xsre->property;
    587 		}
    588 	}
    589 
    590 	/* all done, send a notification to the listener */
    591 	if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev))
    592 		fprintf(stderr, "error sending SelectionNotify event\n");
    593 }
    594 
    595 void
    596 setsel(char *str, Time t)
    597 {
    598 	if (!str)
    599 		return;
    600 
    601 	free(xsel.primary);
    602 	xsel.primary = str;
    603 
    604 	XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
    605 	if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
    606 		selclear();
    607 }
    608 
    609 void
    610 xsetsel(char *str)
    611 {
    612 	setsel(str, CurrentTime);
    613 }
    614 
    615 void
    616 bmotion(XEvent *e)
    617 {
    618 	if (IS_SET(MODE_MOUSE)) {
    619 		mousereport(e);
    620 		return;
    621 	}
    622 
    623 	mousesel(e, 0);
    624 }
    625 
    626 void
    627 cresize(int width, int height)
    628 {
    629 	int col, row;
    630 
    631 	if (width != 0)
    632 		win.w = width;
    633 	if (height != 0)
    634 		win.h = height;
    635 
    636 	col = (win.w - 2 * borderpx) / win.cw;
    637 	row = (win.h - 2 * borderpx) / win.ch;
    638 	col = MAX(1, col);
    639 	row = MAX(1, row);
    640 
    641 	tresize(col, row);
    642 	xresize(col, row);
    643 	ttyresize(win.tw, win.th);
    644 }
    645 
    646 void
    647 xresize(int col, int row)
    648 {
    649 	win.tw = col * win.cw;
    650 	win.th = row * win.ch;
    651 
    652 	XFreePixmap(xw.dpy, xw.buf);
    653 	xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
    654 			DefaultDepth(xw.dpy, xw.scr));
    655 	XftDrawChange(xw.draw, xw.buf);
    656 	xclear(0, 0, win.w, win.h);
    657 
    658 	/* resize to new width */
    659 	xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
    660 }
    661 
    662 ushort
    663 sixd_to_16bit(int x)
    664 {
    665 	return x == 0 ? 0 : 0x3737 + 0x2828 * x;
    666 }
    667 
    668 int
    669 xloadcolor(int i, const char *name, Color *ncolor)
    670 {
    671 	XRenderColor color = { .alpha = 0xffff };
    672 
    673 	if (!name) {
    674 		if (BETWEEN(i, 16, 255)) { /* 256 color */
    675 			if (i < 6*6*6+16) { /* same colors as xterm */
    676 				color.red   = sixd_to_16bit( ((i-16)/36)%6 );
    677 				color.green = sixd_to_16bit( ((i-16)/6) %6 );
    678 				color.blue  = sixd_to_16bit( ((i-16)/1) %6 );
    679 			} else { /* greyscale */
    680 				color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16));
    681 				color.green = color.blue = color.red;
    682 			}
    683 			return XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, ncolor);
    684 		} else {
    685 			name = colorname[i];
    686 		}
    687 	}
    688 
    689 	return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
    690 }
    691 
    692 void
    693 xloadcolors(void)
    694 {
    695 	int i;
    696 	static int loaded;
    697 	Color *cp;
    698 	const char **c;
    699 
    700 	if (loaded) {
    701 		for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
    702 			XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
    703 	} else {
    704 		dc.collen = 256;
    705 		for (c = &colorname[256]; *c; c++)
    706 			dc.collen++;
    707 		dc.col = xmalloc(dc.collen * sizeof(Color));
    708 	}
    709 
    710 	for (i = 0; i < dc.collen; i++) {
    711 		if (!xloadcolor(i, NULL, &dc.col[i])) {
    712 			if (colorname[i])
    713 				die("allocate color '%s' failed\n", colorname[i]);
    714 			else
    715 				die("allocate color %d failed\n", i);
    716 		}
    717 	}
    718 	loaded = 1;
    719 }
    720 
    721 int
    722 xsetcolorname(int x, const char *name)
    723 {
    724 	Color ncolor;
    725 
    726 	if (!BETWEEN(x, 0, dc.collen))
    727 		return 1;
    728 
    729 	if (!xloadcolor(x, name, &ncolor))
    730 		return 1;
    731 
    732 	XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
    733 	dc.col[x] = ncolor;
    734 
    735 	return 0;
    736 }
    737 
    738 /* Absolute coordinates. */
    739 void
    740 xclear(int x1, int y1, int x2, int y2)
    741 {
    742 	XftDrawRect(xw.draw,
    743 			&dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],
    744 			x1, y1, x2-x1, y2-y1);
    745 }
    746 
    747 void
    748 xhints(void)
    749 {
    750 	XClassHint class = {opt_name ? opt_name : termname,
    751 	                    opt_class ? opt_class : termname};
    752 	XWMHints wm = {.flags = InputHint, .input = 1};
    753 	XSizeHints *sizeh;
    754 
    755 	sizeh = XAllocSizeHints();
    756 
    757 	sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
    758 	sizeh->height = win.h;
    759 	sizeh->width = win.w;
    760 	sizeh->height_inc = win.ch;
    761 	sizeh->width_inc = win.cw;
    762 	sizeh->base_height = 2 * borderpx;
    763 	sizeh->base_width = 2 * borderpx;
    764 	sizeh->min_height = win.ch + 2 * borderpx;
    765 	sizeh->min_width = win.cw + 2 * borderpx;
    766 	if (xw.isfixed) {
    767 		sizeh->flags |= PMaxSize;
    768 		sizeh->min_width = sizeh->max_width = win.w;
    769 		sizeh->min_height = sizeh->max_height = win.h;
    770 	}
    771 	if (xw.gm & (XValue|YValue)) {
    772 		sizeh->flags |= USPosition | PWinGravity;
    773 		sizeh->x = xw.l;
    774 		sizeh->y = xw.t;
    775 		sizeh->win_gravity = xgeommasktogravity(xw.gm);
    776 	}
    777 
    778 	XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, &class);
    779 	XFree(sizeh);
    780 }
    781 
    782 int
    783 xgeommasktogravity(int mask)
    784 {
    785 	switch (mask & (XNegative|YNegative)) {
    786 	case 0:
    787 		return NorthWestGravity;
    788 	case XNegative:
    789 		return NorthEastGravity;
    790 	case YNegative:
    791 		return SouthWestGravity;
    792 	}
    793 
    794 	return SouthEastGravity;
    795 }
    796 
    797 int
    798 xloadfont(Font *f, FcPattern *pattern)
    799 {
    800 	FcPattern *configured;
    801 	FcPattern *match;
    802 	FcResult result;
    803 	XGlyphInfo extents;
    804 	int wantattr, haveattr;
    805 
    806 	/* Manually configure instead of calling XftMatchFont so that we can use
    807 	 * the configured pattern for "missing glyph" lookups. */
    808 	configured = FcPatternDuplicate(pattern);
    809 	if (!configured)
    810 		return 1;
    811 
    812 	FcConfigSubstitute(NULL, configured, FcMatchPattern);
    813 	XftDefaultSubstitute(xw.dpy, xw.scr, configured);
    814 
    815 	match = FcFontMatch(NULL, configured, &result);
    816 	if (!match) {
    817 		FcPatternDestroy(configured);
    818 		return 1;
    819 	}
    820 
    821 	if (!(f->match = XftFontOpenPattern(xw.dpy, match))) {
    822 		FcPatternDestroy(configured);
    823 		FcPatternDestroy(match);
    824 		return 1;
    825 	}
    826 
    827 	if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) ==
    828 	    XftResultMatch)) {
    829 		/* Check if xft was unable to find a font with the appropriate
    830 		 * slant but gave us one anyway. Try to mitigate. */
    831 		if ((XftPatternGetInteger(f->match->pattern, "slant", 0,
    832 		    &haveattr) != XftResultMatch) || haveattr < wantattr) {
    833 			f->badslant = 1;
    834 			fputs("font slant does not match\n", stderr);
    835 		}
    836 	}
    837 
    838 	if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) ==
    839 	    XftResultMatch)) {
    840 		if ((XftPatternGetInteger(f->match->pattern, "weight", 0,
    841 		    &haveattr) != XftResultMatch) || haveattr != wantattr) {
    842 			f->badweight = 1;
    843 			fputs("font weight does not match\n", stderr);
    844 		}
    845 	}
    846 
    847 	XftTextExtentsUtf8(xw.dpy, f->match,
    848 		(const FcChar8 *) ascii_printable,
    849 		strlen(ascii_printable), &extents);
    850 
    851 	f->set = NULL;
    852 	f->pattern = configured;
    853 
    854 	f->ascent = f->match->ascent;
    855 	f->descent = f->match->descent;
    856 	f->lbearing = 0;
    857 	f->rbearing = f->match->max_advance_width;
    858 
    859 	f->height = f->ascent + f->descent;
    860 	f->width = DIVCEIL(extents.xOff, strlen(ascii_printable));
    861 
    862 	return 0;
    863 }
    864 
    865 void
    866 xloadfonts(const char *fontstr, double fontsize)
    867 {
    868 	FcPattern *pattern;
    869 	double fontval;
    870 
    871 	if (fontstr[0] == '-')
    872 		pattern = XftXlfdParse(fontstr, False, False);
    873 	else
    874 		pattern = FcNameParse((const FcChar8 *)fontstr);
    875 
    876 	if (!pattern)
    877 		die("open font '%s' failed\n", fontstr);
    878 
    879 	if (fontsize > 1) {
    880 		FcPatternDel(pattern, FC_PIXEL_SIZE);
    881 		FcPatternDel(pattern, FC_SIZE);
    882 		FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontsize);
    883 		usedfontsize = fontsize;
    884 	} else {
    885 		if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
    886 				FcResultMatch) {
    887 			usedfontsize = fontval;
    888 		} else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
    889 				FcResultMatch) {
    890 			usedfontsize = -1;
    891 		} else {
    892 			/* Default font size is 12, if none given. This is to
    893 			 * have a known usedfontsize value. */
    894 			FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
    895 			usedfontsize = 12;
    896 		}
    897 		defaultfontsize = usedfontsize;
    898 	}
    899 
    900 	if (xloadfont(&dc.font, pattern))
    901 		die("open font '%s' failed\n", fontstr);
    902 
    903 	if (usedfontsize < 0) {
    904 		FcPatternGetDouble(dc.font.match->pattern, FC_PIXEL_SIZE, 0, &fontval);
    905 		usedfontsize = fontval;
    906 		if (fontsize == 0)
    907 			defaultfontsize = fontval;
    908 	}
    909 
    910 	/* Setting character width and height. */
    911 	win.cw = ceilf(dc.font.width * cwscale);
    912 	win.ch = ceilf(dc.font.height * chscale);
    913 
    914 	FcPatternDel(pattern, FC_SLANT);
    915 	FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
    916 	if (xloadfont(&dc.ifont, pattern))
    917 		die("open font '%s' failed\n", fontstr);
    918 
    919 	FcPatternDel(pattern, FC_WEIGHT);
    920 	FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
    921 	if (xloadfont(&dc.ibfont, pattern))
    922 		die("open font '%s' failed\n", fontstr);
    923 
    924 	FcPatternDel(pattern, FC_SLANT);
    925 	FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
    926 	if (xloadfont(&dc.bfont, pattern))
    927 		die("open font '%s' failed\n", fontstr);
    928 
    929 	FcPatternDestroy(pattern);
    930 }
    931 
    932 void
    933 xunloadfont(Font *f)
    934 {
    935 	XftFontClose(xw.dpy, f->match);
    936 	FcPatternDestroy(f->pattern);
    937 	if (f->set)
    938 		FcFontSetDestroy(f->set);
    939 }
    940 
    941 void
    942 xunloadfonts(void)
    943 {
    944 	/* Free the loaded fonts in the font cache.  */
    945 	while (frclen > 0)
    946 		XftFontClose(xw.dpy, frc[--frclen].font);
    947 
    948 	xunloadfont(&dc.font);
    949 	xunloadfont(&dc.bfont);
    950 	xunloadfont(&dc.ifont);
    951 	xunloadfont(&dc.ibfont);
    952 }
    953 
    954 int
    955 ximopen(Display *dpy)
    956 {
    957 	XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };
    958 	XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };
    959 
    960 	xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
    961 	if (xw.ime.xim == NULL)
    962 		return 0;
    963 
    964 	if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL))
    965 		fprintf(stderr, "XSetIMValues: failed to set XNDestroyCallback\n");
    966 
    967 	xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation,
    968 			&xw.ime.spot, NULL);
    969 
    970 	if (xw.ime.xic == NULL) {
    971 		xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle,
    972 				XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win,
    973 				XNDestroyCallback, &icdestroy, NULL);
    974 		if (xw.ime.xic == NULL)
    975 			fprintf(stderr, "XCreateIC: failed to create input context\n");
    976 	}
    977 
    978 	return 1;
    979 }
    980 
    981 void
    982 ximinstantiate(Display *dpy, XPointer client, XPointer call)
    983 {
    984 	if (ximopen(dpy))
    985 		XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
    986 				ximinstantiate, NULL);
    987 }
    988 
    989 void
    990 ximdestroy(XIM xim, XPointer client, XPointer call)
    991 {
    992 	xw.ime.xim = NULL;
    993 	XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
    994 			ximinstantiate, NULL);
    995 	XFree(xw.ime.spotlist);
    996 }
    997 
    998 int
    999 xicdestroy(XIC xim, XPointer client, XPointer call)
   1000 {
   1001 	xw.ime.xic = NULL;
   1002 	return 1;
   1003 }
   1004 
   1005 void
   1006 xinit(int cols, int rows)
   1007 {
   1008 	XGCValues gcvalues;
   1009 	Cursor cursor;
   1010 	Window parent;
   1011 	pid_t thispid = getpid();
   1012 	XColor xmousefg, xmousebg;
   1013 
   1014 	if (!(xw.dpy = XOpenDisplay(NULL)))
   1015 		die("open display failed\n");
   1016 	xw.scr = XDefaultScreen(xw.dpy);
   1017 	xw.vis = XDefaultVisual(xw.dpy, xw.scr);
   1018 
   1019 	/* font */
   1020 	if (!FcInit())
   1021 		die("fontconfig init failed\n");
   1022 
   1023 	usedfont = (opt_font == NULL) ? font : opt_font;
   1024 	xloadfonts(usedfont, 0);
   1025 
   1026 	/* colors */
   1027 	xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
   1028 	xloadcolors();
   1029 
   1030 	/* adjust fixed window geometry */
   1031 	win.w = 2 * borderpx + cols * win.cw;
   1032 	win.h = 2 * borderpx + rows * win.ch;
   1033 	if (xw.gm & XNegative)
   1034 		xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
   1035 	if (xw.gm & YNegative)
   1036 		xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2;
   1037 
   1038 	/* Events */
   1039 	xw.attrs.background_pixel = dc.col[defaultbg].pixel;
   1040 	xw.attrs.border_pixel = dc.col[defaultbg].pixel;
   1041 	xw.attrs.bit_gravity = NorthWestGravity;
   1042 	xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask
   1043 		| ExposureMask | VisibilityChangeMask | StructureNotifyMask
   1044 		| ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
   1045 	xw.attrs.colormap = xw.cmap;
   1046 
   1047 	if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))
   1048 		parent = XRootWindow(xw.dpy, xw.scr);
   1049 	xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
   1050 			win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
   1051 			xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
   1052 			| CWEventMask | CWColormap, &xw.attrs);
   1053 
   1054 	memset(&gcvalues, 0, sizeof(gcvalues));
   1055 	gcvalues.graphics_exposures = False;
   1056 	dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
   1057 			&gcvalues);
   1058 	xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
   1059 			DefaultDepth(xw.dpy, xw.scr));
   1060 	XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
   1061 	XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
   1062 
   1063 	/* font spec buffer */
   1064 	xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
   1065 
   1066 	/* Xft rendering context */
   1067 	xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
   1068 
   1069 	/* input methods */
   1070 	if (!ximopen(xw.dpy))
   1071 		XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
   1072 				ximinstantiate, NULL);
   1073 
   1074 	/* white cursor, black outline */
   1075 	cursor = XCreateFontCursor(xw.dpy, mouseshape);
   1076 	XDefineCursor(xw.dpy, xw.win, cursor);
   1077 
   1078 	if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) {
   1079 		xmousefg.red   = 0xffff;
   1080 		xmousefg.green = 0xffff;
   1081 		xmousefg.blue  = 0xffff;
   1082 	}
   1083 
   1084 	if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) {
   1085 		xmousebg.red   = 0x0000;
   1086 		xmousebg.green = 0x0000;
   1087 		xmousebg.blue  = 0x0000;
   1088 	}
   1089 
   1090 	XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg);
   1091 
   1092 	xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
   1093 	xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
   1094 	xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
   1095 	xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False);
   1096 	XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
   1097 
   1098 	xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
   1099 	XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
   1100 			PropModeReplace, (uchar *)&thispid, 1);
   1101 
   1102 	win.mode = MODE_NUMLOCK;
   1103 	xsettitle(NULL);
   1104 	xhints();
   1105 	XMapWindow(xw.dpy, xw.win);
   1106 	XSync(xw.dpy, False);
   1107 
   1108 	clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1);
   1109 	clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2);
   1110 	xsel.primary = NULL;
   1111 	xsel.clipboard = NULL;
   1112 	xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
   1113 	if (xsel.xtarget == None)
   1114 		xsel.xtarget = XA_STRING;
   1115 }
   1116 
   1117 int
   1118 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
   1119 {
   1120 	float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
   1121 	ushort mode, prevmode = USHRT_MAX;
   1122 	Font *font = &dc.font;
   1123 	int frcflags = FRC_NORMAL;
   1124 	float runewidth = win.cw;
   1125 	Rune rune;
   1126 	FT_UInt glyphidx;
   1127 	FcResult fcres;
   1128 	FcPattern *fcpattern, *fontpattern;
   1129 	FcFontSet *fcsets[] = { NULL };
   1130 	FcCharSet *fccharset;
   1131 	int i, f, numspecs = 0;
   1132 
   1133 	for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
   1134 		/* Fetch rune and mode for current glyph. */
   1135 		rune = glyphs[i].u;
   1136 		mode = glyphs[i].mode;
   1137 
   1138 		/* Skip dummy wide-character spacing. */
   1139 		if (mode == ATTR_WDUMMY)
   1140 			continue;
   1141 
   1142 		/* Determine font for glyph if different from previous glyph. */
   1143 		if (prevmode != mode) {
   1144 			prevmode = mode;
   1145 			font = &dc.font;
   1146 			frcflags = FRC_NORMAL;
   1147 			runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
   1148 			if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
   1149 				font = &dc.ibfont;
   1150 				frcflags = FRC_ITALICBOLD;
   1151 			} else if (mode & ATTR_ITALIC) {
   1152 				font = &dc.ifont;
   1153 				frcflags = FRC_ITALIC;
   1154 			} else if (mode & ATTR_BOLD) {
   1155 				font = &dc.bfont;
   1156 				frcflags = FRC_BOLD;
   1157 			}
   1158 			yp = winy + font->ascent;
   1159 		}
   1160 
   1161 		/* Lookup character index with default font. */
   1162 		glyphidx = XftCharIndex(xw.dpy, font->match, rune);
   1163 		if (glyphidx) {
   1164 			specs[numspecs].font = font->match;
   1165 			specs[numspecs].glyph = glyphidx;
   1166 			specs[numspecs].x = (short)xp;
   1167 			specs[numspecs].y = (short)yp;
   1168 			xp += runewidth;
   1169 			numspecs++;
   1170 			continue;
   1171 		}
   1172 
   1173 		/* Fallback on font cache, search the font cache for match. */
   1174 		for (f = 0; f < frclen; f++) {
   1175 			glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
   1176 			/* Everything correct. */
   1177 			if (glyphidx && frc[f].flags == frcflags)
   1178 				break;
   1179 			/* We got a default font for a not found glyph. */
   1180 			if (!glyphidx && frc[f].flags == frcflags
   1181 					&& frc[f].unicodep == rune) {
   1182 				break;
   1183 			}
   1184 		}
   1185 
   1186 		/* Nothing was found. Use fontconfig to find matching font. */
   1187 		if (f >= frclen) {
   1188 			if (!font->set)
   1189 				font->set = FcFontSort(0, font->pattern, 1, 0, &fcres);
   1190 			fcsets[0] = font->set;
   1191 
   1192 			/* Nothing was found in the cache. Now use
   1193 			 * some dozen of Fontconfig calls to get the
   1194 			 * font for one single character.
   1195 			 *
   1196 			 * Xft and fontconfig are design failures. */
   1197 			fcpattern = FcPatternDuplicate(font->pattern);
   1198 			fccharset = FcCharSetCreate();
   1199 
   1200 			FcCharSetAddChar(fccharset, rune);
   1201 			FcPatternAddCharSet(fcpattern, FC_CHARSET,
   1202 					fccharset);
   1203 			FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
   1204 
   1205 			FcConfigSubstitute(0, fcpattern,
   1206 					FcMatchPattern);
   1207 			FcDefaultSubstitute(fcpattern);
   1208 
   1209 			fontpattern = FcFontSetMatch(0, fcsets, 1,
   1210 					fcpattern, &fcres);
   1211 
   1212 			/* Allocate memory for the new cache entry. */
   1213 			if (frclen >= frccap) {
   1214 				frccap += 16;
   1215 				frc = xrealloc(frc, frccap * sizeof(Fontcache));
   1216 			}
   1217 
   1218 			frc[frclen].font = XftFontOpenPattern(xw.dpy,
   1219 					fontpattern);
   1220 			if (!frc[frclen].font)
   1221 				die("XftFontOpenPattern failed seeking fallback font: %s\n",
   1222 						strerror(errno));
   1223 			frc[frclen].flags = frcflags;
   1224 			frc[frclen].unicodep = rune;
   1225 
   1226 			glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
   1227 
   1228 			f = frclen;
   1229 			frclen++;
   1230 
   1231 			FcPatternDestroy(fcpattern);
   1232 			FcCharSetDestroy(fccharset);
   1233 		}
   1234 
   1235 		specs[numspecs].font = frc[f].font;
   1236 		specs[numspecs].glyph = glyphidx;
   1237 		specs[numspecs].x = (short)xp;
   1238 		specs[numspecs].y = (short)yp;
   1239 		xp += runewidth;
   1240 		numspecs++;
   1241 	}
   1242 
   1243 	return numspecs;
   1244 }
   1245 
   1246 void
   1247 xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
   1248 {
   1249 	int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
   1250 	int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
   1251 	    width = charlen * win.cw;
   1252 	Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
   1253 	XRenderColor colfg, colbg;
   1254 	XRectangle r;
   1255 
   1256 	/* Fallback on color display for attributes not supported by the font */
   1257 	if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) {
   1258 		if (dc.ibfont.badslant || dc.ibfont.badweight)
   1259 			base.fg = defaultattr;
   1260 	} else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) ||
   1261 	    (base.mode & ATTR_BOLD && dc.bfont.badweight)) {
   1262 		base.fg = defaultattr;
   1263 	}
   1264 
   1265 	if (IS_TRUECOL(base.fg)) {
   1266 		colfg.alpha = 0xffff;
   1267 		colfg.red = TRUERED(base.fg);
   1268 		colfg.green = TRUEGREEN(base.fg);
   1269 		colfg.blue = TRUEBLUE(base.fg);
   1270 		XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg);
   1271 		fg = &truefg;
   1272 	} else {
   1273 		fg = &dc.col[base.fg];
   1274 	}
   1275 
   1276 	if (IS_TRUECOL(base.bg)) {
   1277 		colbg.alpha = 0xffff;
   1278 		colbg.green = TRUEGREEN(base.bg);
   1279 		colbg.red = TRUERED(base.bg);
   1280 		colbg.blue = TRUEBLUE(base.bg);
   1281 		XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg);
   1282 		bg = &truebg;
   1283 	} else {
   1284 		bg = &dc.col[base.bg];
   1285 	}
   1286 
   1287 	/* Change basic system colors [0-7] to bright system colors [8-15] */
   1288 	if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
   1289 		fg = &dc.col[base.fg + 8];
   1290 
   1291 	if (IS_SET(MODE_REVERSE)) {
   1292 		if (fg == &dc.col[defaultfg]) {
   1293 			fg = &dc.col[defaultbg];
   1294 		} else {
   1295 			colfg.red = ~fg->color.red;
   1296 			colfg.green = ~fg->color.green;
   1297 			colfg.blue = ~fg->color.blue;
   1298 			colfg.alpha = fg->color.alpha;
   1299 			XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
   1300 			fg = &revfg;
   1301 		}
   1302 
   1303 		if (bg == &dc.col[defaultbg]) {
   1304 			bg = &dc.col[defaultfg];
   1305 		} else {
   1306 			colbg.red = ~bg->color.red;
   1307 			colbg.green = ~bg->color.green;
   1308 			colbg.blue = ~bg->color.blue;
   1309 			colbg.alpha = bg->color.alpha;
   1310 			XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &revbg);
   1311 			bg = &revbg;
   1312 		}
   1313 	}
   1314 
   1315 	if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {
   1316 		colfg.red = fg->color.red / 2;
   1317 		colfg.green = fg->color.green / 2;
   1318 		colfg.blue = fg->color.blue / 2;
   1319 		colfg.alpha = fg->color.alpha;
   1320 		XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
   1321 		fg = &revfg;
   1322 	}
   1323 
   1324 	if (base.mode & ATTR_REVERSE) {
   1325 		temp = fg;
   1326 		fg = bg;
   1327 		bg = temp;
   1328 	}
   1329 
   1330 	if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
   1331 		fg = bg;
   1332 
   1333 	if (base.mode & ATTR_INVISIBLE)
   1334 		fg = bg;
   1335 
   1336 	/* Intelligent cleaning up of the borders. */
   1337 	if (x == 0) {
   1338 		xclear(0, (y == 0)? 0 : winy, borderpx, winy + win.ch +
   1339 				((winy + win.ch >= borderpx + win.th) ? win.h : 0));
   1340 	}
   1341 	if (winx + width >= borderpx + win.tw) {
   1342 		xclear(winx + width, (y == 0)? 0 : winy, win.w, ((winy + win.ch >=
   1343 				borderpx + win.th) ? win.h : (winy + win.ch)));
   1344 	}
   1345 	if (y == 0)
   1346 		xclear(winx, 0, winx + width, borderpx);
   1347 	if (winy + win.ch >= borderpx + win.th)
   1348 		xclear(winx, winy + win.ch, winx + width, win.h);
   1349 
   1350 	/* Clean up the region we want to draw to. */
   1351 	XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
   1352 
   1353 	/* Set the clip region because Xft is sometimes dirty. */
   1354 	r.x = 0;
   1355 	r.y = 0;
   1356 	r.height = win.ch;
   1357 	r.width = width;
   1358 	XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
   1359 
   1360 	/* Render the glyphs. */
   1361 	XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
   1362 
   1363 	/* Render underline and strikethrough. */
   1364 	if (base.mode & ATTR_UNDERLINE) {
   1365 		XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1);
   1366 	}
   1367 
   1368 	if (base.mode & ATTR_STRUCK) {
   1369 		XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,
   1370 				width, 1);
   1371 	}
   1372 
   1373 	/* Reset clip to none. */
   1374 	XftDrawSetClip(xw.draw, 0);
   1375 }
   1376 
   1377 void
   1378 xdrawglyph(Glyph g, int x, int y)
   1379 {
   1380 	int numspecs;
   1381 	XftGlyphFontSpec spec;
   1382 
   1383 	numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
   1384 	xdrawglyphfontspecs(&spec, g, numspecs, x, y);
   1385 }
   1386 
   1387 void
   1388 xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
   1389 {
   1390 	Color drawcol;
   1391 
   1392 	/* remove the old cursor */
   1393 	if (selected(ox, oy))
   1394 		og.mode ^= ATTR_REVERSE;
   1395 	xdrawglyph(og, ox, oy);
   1396 
   1397 	if (IS_SET(MODE_HIDE))
   1398 		return;
   1399 
   1400 	/* Select the right color for the right mode. */
   1401 	g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE;
   1402 
   1403 	if (IS_SET(MODE_REVERSE)) {
   1404 		g.mode |= ATTR_REVERSE;
   1405 		g.bg = defaultfg;
   1406 		if (selected(cx, cy)) {
   1407 			drawcol = dc.col[defaultcs];
   1408 			g.fg = defaultrcs;
   1409 		} else {
   1410 			drawcol = dc.col[defaultrcs];
   1411 			g.fg = defaultcs;
   1412 		}
   1413 	} else {
   1414 		if (selected(cx, cy)) {
   1415 			g.fg = defaultfg;
   1416 			g.bg = defaultrcs;
   1417 		} else {
   1418 			g.fg = defaultbg;
   1419 			g.bg = defaultcs;
   1420 		}
   1421 		drawcol = dc.col[g.bg];
   1422 	}
   1423 
   1424 	/* draw the new one */
   1425 	if (IS_SET(MODE_FOCUSED)) {
   1426 		switch (win.cursor) {
   1427 		case 7: /* st extension */
   1428 			g.u = 0x2603; /* snowman (U+2603) */
   1429 			/* FALLTHROUGH */
   1430 		case 0: /* Blinking Block */
   1431 		case 1: /* Blinking Block (Default) */
   1432 		case 2: /* Steady Block */
   1433 			xdrawglyph(g, cx, cy);
   1434 			break;
   1435 		case 3: /* Blinking Underline */
   1436 		case 4: /* Steady Underline */
   1437 			XftDrawRect(xw.draw, &drawcol,
   1438 					borderpx + cx * win.cw,
   1439 					borderpx + (cy + 1) * win.ch - \
   1440 						cursorthickness,
   1441 					win.cw, cursorthickness);
   1442 			break;
   1443 		case 5: /* Blinking bar */
   1444 		case 6: /* Steady bar */
   1445 			XftDrawRect(xw.draw, &drawcol,
   1446 					borderpx + cx * win.cw,
   1447 					borderpx + cy * win.ch,
   1448 					cursorthickness, win.ch);
   1449 			break;
   1450 		}
   1451 	} else {
   1452 		XftDrawRect(xw.draw, &drawcol,
   1453 				borderpx + cx * win.cw,
   1454 				borderpx + cy * win.ch,
   1455 				win.cw - 1, 1);
   1456 		XftDrawRect(xw.draw, &drawcol,
   1457 				borderpx + cx * win.cw,
   1458 				borderpx + cy * win.ch,
   1459 				1, win.ch - 1);
   1460 		XftDrawRect(xw.draw, &drawcol,
   1461 				borderpx + (cx + 1) * win.cw - 1,
   1462 				borderpx + cy * win.ch,
   1463 				1, win.ch - 1);
   1464 		XftDrawRect(xw.draw, &drawcol,
   1465 				borderpx + cx * win.cw,
   1466 				borderpx + (cy + 1) * win.ch - 1,
   1467 				win.cw, 1);
   1468 	}
   1469 }
   1470 
   1471 void
   1472 xsetenv(void)
   1473 {
   1474 	char buf[sizeof(long) * 8 + 1];
   1475 
   1476 	snprintf(buf, sizeof(buf), "%lu", xw.win);
   1477 	setenv("WINDOWID", buf, 1);
   1478 }
   1479 
   1480 void
   1481 xseticontitle(char *p)
   1482 {
   1483 	XTextProperty prop;
   1484 	DEFAULT(p, opt_title);
   1485 
   1486 	Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, &prop);
   1487 	XSetWMIconName(xw.dpy, xw.win, &prop);
   1488 	XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname);
   1489 	XFree(prop.value);
   1490 }
   1491 
   1492 void
   1493 xsettitle(char *p)
   1494 {
   1495 	XTextProperty prop;
   1496 	DEFAULT(p, opt_title);
   1497 
   1498 	Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, &prop);
   1499 	XSetWMName(xw.dpy, xw.win, &prop);
   1500 	XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
   1501 	XFree(prop.value);
   1502 }
   1503 
   1504 int
   1505 xstartdraw(void)
   1506 {
   1507 	return IS_SET(MODE_VISIBLE);
   1508 }
   1509 
   1510 void
   1511 xdrawline(Line line, int x1, int y1, int x2)
   1512 {
   1513 	int i, x, ox, numspecs;
   1514 	Glyph base, new;
   1515 	XftGlyphFontSpec *specs = xw.specbuf;
   1516 
   1517 	numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
   1518 	i = ox = 0;
   1519 	for (x = x1; x < x2 && i < numspecs; x++) {
   1520 		new = line[x];
   1521 		if (new.mode == ATTR_WDUMMY)
   1522 			continue;
   1523 		if (selected(x, y1))
   1524 			new.mode ^= ATTR_REVERSE;
   1525 		if (i > 0 && ATTRCMP(base, new)) {
   1526 			xdrawglyphfontspecs(specs, base, i, ox, y1);
   1527 			specs += i;
   1528 			numspecs -= i;
   1529 			i = 0;
   1530 		}
   1531 		if (i == 0) {
   1532 			ox = x;
   1533 			base = new;
   1534 		}
   1535 		i++;
   1536 	}
   1537 	if (i > 0)
   1538 		xdrawglyphfontspecs(specs, base, i, ox, y1);
   1539 }
   1540 
   1541 void
   1542 xfinishdraw(void)
   1543 {
   1544 	XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, win.h, 0, 0);
   1545 	XSetForeground(xw.dpy, dc.gc,
   1546 			dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg].pixel);
   1547 }
   1548 
   1549 void
   1550 xximspot(int x, int y)
   1551 {
   1552 	if (xw.ime.xic == NULL)
   1553 		return;
   1554 
   1555 	xw.ime.spot.x = borderpx + x * win.cw;
   1556 	xw.ime.spot.y = borderpx + (y + 1) * win.ch;
   1557 
   1558 	XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL);
   1559 }
   1560 
   1561 void
   1562 expose(XEvent *ev)
   1563 {
   1564 	redraw();
   1565 }
   1566 
   1567 void
   1568 visibility(XEvent *ev)
   1569 {
   1570 	XVisibilityEvent *e = &ev->xvisibility;
   1571 
   1572 	MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE);
   1573 }
   1574 
   1575 void
   1576 unmap(XEvent *ev)
   1577 {
   1578 	win.mode &= ~MODE_VISIBLE;
   1579 }
   1580 
   1581 void
   1582 xsetpointermotion(int set)
   1583 {
   1584 	MODBIT(xw.attrs.event_mask, set, PointerMotionMask);
   1585 	XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
   1586 }
   1587 
   1588 /* Modify the window mode and return the new mode.
   1589  * set clr
   1590  *  0   0   unchanged
   1591  *  0   1   clear
   1592  *  1   0   set
   1593  *  1   1   toggle */
   1594 uint
   1595 xmode(uint set, uint clr)
   1596 {
   1597 	int orig = win.mode;
   1598 	win.mode = (~win.mode & set) | (win.mode & ~clr);
   1599 	if ((win.mode & MODE_REVERSE) != (orig & MODE_REVERSE))
   1600 		redraw();
   1601 	return win.mode;
   1602 }
   1603 
   1604 int
   1605 xsetcursor(int cursor)
   1606 {
   1607 	if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
   1608 		return 1;
   1609 	win.cursor = cursor;
   1610 	return 0;
   1611 }
   1612 
   1613 void
   1614 xseturgency(int add)
   1615 {
   1616 	XWMHints *h = XGetWMHints(xw.dpy, xw.win);
   1617 
   1618 	MODBIT(h->flags, add, XUrgencyHint);
   1619 	XSetWMHints(xw.dpy, xw.win, h);
   1620 	XFree(h);
   1621 }
   1622 
   1623 void
   1624 xbell(void)
   1625 {
   1626 	if (!(IS_SET(MODE_FOCUSED)))
   1627 		xseturgency(1);
   1628 	if (bellvolume)
   1629 		XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL);
   1630 }
   1631 
   1632 void
   1633 focus(XEvent *ev)
   1634 {
   1635 	XFocusChangeEvent *e = &ev->xfocus;
   1636 
   1637 	if (e->mode == NotifyGrab)
   1638 		return;
   1639 
   1640 	if (ev->type == FocusIn) {
   1641 		if (xw.ime.xic)
   1642 			XSetICFocus(xw.ime.xic);
   1643 		win.mode |= MODE_FOCUSED;
   1644 		xseturgency(0);
   1645 		if (IS_SET(MODE_FOCUS))
   1646 			ttywrite("\033[I", 3, 0);
   1647 	} else {
   1648 		if (xw.ime.xic)
   1649 			XUnsetICFocus(xw.ime.xic);
   1650 		win.mode &= ~MODE_FOCUSED;
   1651 		if (IS_SET(MODE_FOCUS))
   1652 			ttywrite("\033[O", 3, 0);
   1653 	}
   1654 }
   1655 
   1656 int
   1657 handlesym(KeySym sym, uint state)
   1658 {
   1659 	Key *k;
   1660 	char buf[64]; /* big enough for CSI sequence */
   1661 	size_t len;
   1662 
   1663 	/* 1. Custom handling from config */
   1664 	for (k = keys; k->sym; k++) {
   1665 		if (k->sym == sym && MATCH(state, k->set, k->clr)) {
   1666 			k->fn(state, k->arg);
   1667 			return 1;
   1668 		}
   1669 	}
   1670 
   1671 	/* 2. Printable ASCII (some special cases are handled in the keys table) */
   1672 	if (0x20 <= sym && sym < 0x7f) {
   1673 		/* CTRL + [ALT +] non-lowercase-letter must be encoded as CSI
   1674 		 * XXX some of these should probably be mapped to C0 controls */
   1675 		if ((state&CTRL) > 0 && !('a' <= sym && sym <= 'z')) {
   1676 			len = csienc(buf, sizeof buf, state, sym, SHFT, 'u');
   1677 		} else {
   1678 			buf[0] = sym;
   1679 			len = 1;
   1680 			/* CTRL + lowercase letter can usually be handled by translating to
   1681 			 * the corresponding C0 control */
   1682 			if ((state&CTRL) > 0)
   1683 				buf[0] -= 0x60;
   1684 			/* ALT can usually be handled by appending ESC */
   1685 			if ((state&ALT) > 0) {
   1686 				buf[1] = buf[0];
   1687 				buf[0] = '\033';
   1688 				len = 2;
   1689 			}
   1690 		}
   1691 		ttywrite(buf, len, 1);
   1692 		return 1;
   1693 	}
   1694 
   1695 	return 0;
   1696 }
   1697 
   1698 void
   1699 kaction(XKeyEvent *e, int release)
   1700 {
   1701 	uint state;
   1702 	char buf[64]; /* big enough for CSI sequence */
   1703 	size_t len;
   1704 	KeySym sym;
   1705 	Status status;
   1706 	Rune c;
   1707 
   1708 	state = confstate(e->state, release);
   1709 
   1710 	sym = NoSymbol;
   1711 	if (xw.ime.xic)
   1712 		len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &sym, &status);
   1713 	else
   1714 		len = XLookupString(e, buf, sizeof buf, &sym, NULL);
   1715 
   1716 	/* 1. Sym  */
   1717 	if (sym != NoSymbol && handlesym(sym, state))
   1718 		return;
   1719 
   1720 	if (len == 0)
   1721 		return;
   1722 
   1723 	/* 2. Modified UTF8-encoded unicode */
   1724 	if ((state&KEXCL(SHFT)) > 0
   1725 			&& utf8dec(buf, &c, len) == len
   1726 			&& c != UTF_INVALID)
   1727 		len = csienc(buf, sizeof buf, state, c, SHFT, 'u');
   1728 
   1729 	/* 3. Default to directly sending composed string from the input method
   1730 	 * (might not be UTF8; encoding is dependent on locale of input method) */
   1731 
   1732 	ttywrite(buf, len, 1);
   1733 }
   1734 
   1735 void
   1736 kpress(XEvent *e)
   1737 {
   1738 	if (IS_SET(MODE_KBDLOCK))
   1739 		return;
   1740 	kaction(&e->xkey, 0);
   1741 }
   1742 
   1743 void
   1744 krelease(XEvent *e)
   1745 {
   1746 	if (IS_SET(MODE_KBDLOCK))
   1747 		return;
   1748 	kaction(&e->xkey, 1);
   1749 }
   1750 
   1751 void
   1752 cmessage(XEvent *e)
   1753 {
   1754 	/* See xembed specs:
   1755 	 * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
   1756 	if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
   1757 		if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
   1758 			win.mode |= MODE_FOCUSED;
   1759 			xseturgency(0);
   1760 		} else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
   1761 			win.mode &= ~MODE_FOCUSED;
   1762 		}
   1763 	} else if (e->xclient.data.l[0] == xw.wmdeletewin) {
   1764 		ttyhangup();
   1765 		exit(0);
   1766 	}
   1767 }
   1768 
   1769 void
   1770 resize(XEvent *e)
   1771 {
   1772 	if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)
   1773 		return;
   1774 
   1775 	cresize(e->xconfigure.width, e->xconfigure.height);
   1776 }
   1777 
   1778 void
   1779 run(void)
   1780 {
   1781 	XEvent ev;
   1782 	int w = win.w, h = win.h;
   1783 	fd_set rfd;
   1784 	int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
   1785 	struct timespec seltv, *tv, now, lastblink, trigger;
   1786 	double timeout;
   1787 
   1788 	/* Waiting for window mapping */
   1789 	do {
   1790 		XNextEvent(xw.dpy, &ev);
   1791 		/* This XFilterEvent call is required because of XOpenIM. It
   1792 		 * does filter out the key event and some client message for
   1793 		 * the input method too. */
   1794 		if (XFilterEvent(&ev, None))
   1795 			continue;
   1796 		if (ev.type == ConfigureNotify) {
   1797 			w = ev.xconfigure.width;
   1798 			h = ev.xconfigure.height;
   1799 		}
   1800 	} while (ev.type != MapNotify);
   1801 
   1802 	ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
   1803 	cresize(w, h);
   1804 
   1805 	for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
   1806 		FD_ZERO(&rfd);
   1807 		FD_SET(ttyfd, &rfd);
   1808 		FD_SET(xfd, &rfd);
   1809 
   1810 		if (XPending(xw.dpy))
   1811 			timeout = 0;  /* existing events might not set xfd */
   1812 
   1813 		seltv.tv_sec = timeout / 1E3;
   1814 		seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
   1815 		tv = timeout >= 0 ? &seltv : NULL;
   1816 
   1817 		if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
   1818 			if (errno == EINTR)
   1819 				continue;
   1820 			die("select failed: %s\n", strerror(errno));
   1821 		}
   1822 		clock_gettime(CLOCK_MONOTONIC, &now);
   1823 
   1824 		if (FD_ISSET(ttyfd, &rfd))
   1825 			ttyread();
   1826 
   1827 		xev = 0;
   1828 		while (XPending(xw.dpy)) {
   1829 			xev = 1;
   1830 			XNextEvent(xw.dpy, &ev);
   1831 			if (XFilterEvent(&ev, None))
   1832 				continue;
   1833 			if (handler[ev.type])
   1834 				(handler[ev.type])(&ev);
   1835 		}
   1836 
   1837 		/* To reduce flicker and tearing, when new content or event
   1838 		 * triggers drawing, we first wait a bit to ensure we got
   1839 		 * everything, and if nothing new arrives - we draw.
   1840 		 * We start with trying to wait minlatency ms. If more content
   1841 		 * arrives sooner, we retry with shorter and shorter periods,
   1842 		 * and eventually draw even without idle after maxlatency ms.
   1843 		 * Typically this results in low latency while interacting,
   1844 		 * maximum latency intervals during `cat huge.txt`, and perfect
   1845 		 * sync with periodic updates from animations/key-repeats/etc. */
   1846 		if (FD_ISSET(ttyfd, &rfd) || xev) {
   1847 			if (!drawing) {
   1848 				trigger = now;
   1849 				drawing = 1;
   1850 			}
   1851 			timeout = (maxlatency - TIMEDIFF(now, trigger)) /
   1852 				maxlatency * minlatency;
   1853 			if (timeout > 0)
   1854 				continue;  /* we have time, try to find idle */
   1855 		}
   1856 
   1857 		/* idle detected or maxlatency exhausted -> draw */
   1858 		timeout = -1;
   1859 		if (blinktimeout && tattrset(ATTR_BLINK)) {
   1860 			timeout = blinktimeout - TIMEDIFF(now, lastblink);
   1861 			if (timeout <= 0) {
   1862 				if (-timeout > blinktimeout) /* start visible */
   1863 					win.mode |= MODE_BLINK;
   1864 				win.mode ^= MODE_BLINK;
   1865 				tsetdirtattr(ATTR_BLINK);
   1866 				lastblink = now;
   1867 				timeout = blinktimeout;
   1868 			}
   1869 		}
   1870 
   1871 		draw();
   1872 		XFlush(xw.dpy);
   1873 		drawing = 0;
   1874 	}
   1875 }
   1876 
   1877 void
   1878 usage(void)
   1879 {
   1880 	die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
   1881 	    " [-n name] [-o file]\n"
   1882 	    "          [-T title] [-t title] [-w windowid]"
   1883 	    " [[-e] command [args ...]]\n"
   1884 	    "       %s [-aiv] [-c class] [-f font] [-g geometry]"
   1885 	    " [-n name] [-o file]\n"
   1886 	    "          [-T title] [-t title] [-w windowid] -l line"
   1887 	    " [stty_args ...]\n", argv0, argv0);
   1888 }
   1889 
   1890 /* TODO it is weird that main is in x.c and not st.c. Probably, the tty stuff
   1891  * should be in tty.c, the X stuff should be in win.c, and the "neutral"
   1892  * stuff (e.g., main) should be in st.c. */
   1893 int
   1894 main(int argc, char *argv[])
   1895 {
   1896 	xw.l = xw.t = 0;
   1897 	xw.isfixed = False;
   1898 	/* TODO why is this here? Probably this belongs in xinit. */
   1899 	xsetcursor(cursorshape);
   1900 
   1901 	ARGBEGIN {
   1902 	case 'a':
   1903 		allowaltscreen = 0;
   1904 		break;
   1905 	case 'c':
   1906 		opt_class = EARGF(usage());
   1907 		break;
   1908 	case 'e':
   1909 		if (argc > 0)
   1910 			--argc, ++argv;
   1911 		goto run;
   1912 	case 'f':
   1913 		opt_font = EARGF(usage());
   1914 		break;
   1915 	case 'g':
   1916 		xw.gm = XParseGeometry(EARGF(usage()), &xw.l, &xw.t, &cols, &rows);
   1917 		break;
   1918 	case 'i':
   1919 		xw.isfixed = 1;
   1920 		break;
   1921 	case 'o':
   1922 		opt_io = EARGF(usage());
   1923 		break;
   1924 	case 'l':
   1925 		opt_line = EARGF(usage());
   1926 		break;
   1927 	case 'n':
   1928 		opt_name = EARGF(usage());
   1929 		break;
   1930 	case 't':
   1931 	case 'T':
   1932 		opt_title = EARGF(usage());
   1933 		break;
   1934 	case 'w':
   1935 		opt_embed = EARGF(usage());
   1936 		break;
   1937 	case 'v':
   1938 		die("%s " VERSION "\n", argv0);
   1939 		break;
   1940 	default:
   1941 		usage();
   1942 	} ARGEND;
   1943 
   1944 run:
   1945 	if (argc > 0) /* eat all remaining arguments */
   1946 		opt_cmd = argv;
   1947 
   1948 	if (!opt_title)
   1949 		opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0];
   1950 
   1951 	setlocale(LC_CTYPE, "");
   1952 	XSetLocaleModifiers("");
   1953 	cols = MAX(cols, 1);
   1954 	rows = MAX(rows, 1);
   1955 	tnew(cols, rows);
   1956 	xinit(cols, rows);
   1957 	xsetenv();
   1958 	selinit();
   1959 	run();
   1960 
   1961 	return 0;
   1962 }