st-noxz

[fork] suckless st - personal fork
git clone https://noxz.tech/git/st-noxz.git
st-noxz

x.c
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 <sys/select.h>
8#include <time.h>
9#include <unistd.h>
10#include <libgen.h>
11#include <X11/Xatom.h>
12#include <X11/Xlib.h>
13#include <X11/cursorfont.h>
14#include <X11/keysym.h>
15#include <X11/Xft/Xft.h>
16#include <X11/XKBlib.h>
17#include <X11/Xcursor/Xcursor.h>
18#include <X11/Xresource.h>
19#include <Imlib2.h>
20
21char *argv0;
22#include "arg.h"
23#include "st.h"
24#include "sixel.h"
25#include "win.h"
26
27/* types used in config.h */
28typedef struct {
29	uint mod;
30	KeySym keysym;
31	void (*func)(const Arg *);
32	const Arg arg;
33} Shortcut;
34
35typedef struct {
36	uint mod;
37	uint button;
38	void (*func)(const Arg *);
39	const Arg arg;
40	uint  release;
41} MouseShortcut;
42
43typedef struct {
44	KeySym k;
45	uint mask;
46	char *s;
47	/* three-valued logic variables: 0 indifferent, 1 on, -1 off */
48	signed char appkey;    /* application keypad */
49	signed char appcursor; /* application cursor */
50} Key;
51
52/* Xresources preferences */
53enum resource_type {
54	STRING = 0,
55	INTEGER = 1,
56	FLOAT = 2
57};
58
59typedef struct {
60	char *name;
61	enum resource_type type;
62	void *dst;
63} ResourcePref;
64
65/* Undercurl slope types */
66enum undercurl_slope_type {
67	UNDERCURL_SLOPE_ASCENDING = 0,
68	UNDERCURL_SLOPE_TOP_CAP = 1,
69	UNDERCURL_SLOPE_DESCENDING = 2,
70	UNDERCURL_SLOPE_BOTTOM_CAP = 3
71};
72
73/* X modifiers */
74#define XK_ANY_MOD    UINT_MAX
75#define XK_NO_MOD     0
76#define XK_SWITCH_MOD (1<<13|1<<14)
77
78/* function definitions used in config.h */
79static void clipcopy(const Arg *);
80static void clippaste(const Arg *);
81static void numlock(const Arg *);
82static void selpaste(const Arg *);
83static void zoom(const Arg *);
84static void zoomabs(const Arg *);
85static void zoomreset(const Arg *);
86static void ttysend(const Arg *);
87
88/* config.h for applying patches and the configuration. */
89#include "config.h"
90
91/* XEMBED messages */
92#define XEMBED_FOCUS_IN  4
93#define XEMBED_FOCUS_OUT 5
94
95/* macros */
96#define IS_SET(flag)		((win.mode & (flag)) != 0)
97#define TRUERED(x)		(((x) & 0xff0000) >> 8)
98#define TRUEGREEN(x)		(((x) & 0xff00))
99#define TRUEBLUE(x)		(((x) & 0xff) << 8)
100
101static inline ushort sixd_to_16bit(int);
102static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
103static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
104static void xdrawglyph(Glyph, int, int);
105static void xclear(int, int, int, int);
106static int xgeommasktogravity(int);
107static int ximopen(Display *);
108static void ximinstantiate(Display *, XPointer, XPointer);
109static void ximdestroy(XIM, XPointer, XPointer);
110static int xicdestroy(XIC, XPointer, XPointer);
111static void xinit(int, int);
112static void cresize(int, int);
113static void xresize(int, int);
114static void xhints(void);
115static int xloadcolor(int, const char *, Color *);
116static int xloadfont(Font *, FcPattern *);
117static void xloadfonts(const char *, double);
118static void xunloadfont(Font *);
119static void xunloadfonts(void);
120static void xsetenv(void);
121static void xseturgency(int);
122static int evcol(XEvent *);
123static int evrow(XEvent *);
124
125static void expose(XEvent *);
126static void visibility(XEvent *);
127static void unmap(XEvent *);
128static void kpress(XEvent *);
129static void cmessage(XEvent *);
130static void resize(XEvent *);
131static void enter(XEvent *);
132static void focus(XEvent *);
133static uint buttonmask(uint);
134static int mouseaction(XEvent *, uint);
135static void brelease(XEvent *);
136static void bpress(XEvent *);
137static void bmotion(XEvent *);
138static void propnotify(XEvent *);
139static void selnotify(XEvent *);
140static void selclear_(XEvent *);
141static void selrequest(XEvent *);
142static void setsel(char *, Time);
143static void mousesel(XEvent *, int);
144static void mousereport(XEvent *);
145static char *kmap(KeySym, uint);
146static int match(uint, uint);
147
148static void run(void);
149static void usage(void);
150
151static void (*handler[LASTEvent])(XEvent *) = {
152	[KeyPress] = kpress,
153	[ClientMessage] = cmessage,
154	[ConfigureNotify] = resize,
155	[VisibilityNotify] = visibility,
156	[UnmapNotify] = unmap,
157	[Expose] = expose,
158	[FocusIn] = focus,
159	[FocusOut] = focus,
160	[MotionNotify] = bmotion,
161	[ButtonPress] = bpress,
162	[ButtonRelease] = brelease,
163/*
164 * Uncomment if you want the selection to disappear when you select something
165 * different in another window.
166 */
167/*	[SelectionClear] = selclear_, */
168	[SelectionNotify] = selnotify,
169/*
170 * PropertyNotify is only turned on when there is some INCR transfer happening
171 * for the selection retrieval.
172 */
173	[PropertyNotify] = propnotify,
174	[SelectionRequest] = selrequest,
175
176	[EnterNotify] = enter,
177	[LeaveNotify] = enter,
178};
179
180/* Globals */
181Term term;
182DC dc;
183XWindow xw;
184XSelection xsel;
185TermWindow win;
186
187/* Font Ring Cache */
188enum {
189	FRC_NORMAL,
190	FRC_ITALIC,
191	FRC_BOLD,
192	FRC_ITALICBOLD
193};
194
195typedef struct {
196	XftFont *font;
197	int flags;
198	Rune unicodep;
199} Fontcache;
200
201/* Fontcache is an array now. A new font will be appended to the array. */
202static Fontcache *frc = NULL;
203static int frclen = 0;
204static int frccap = 0;
205static char *usedfont = NULL;
206static double usedfontsize = 0;
207static double defaultfontsize = 0;
208
209static char *opt_class = NULL;
210static char **opt_cmd  = NULL;
211static char *opt_embed = NULL;
212static char *opt_font  = NULL;
213static char *opt_io    = NULL;
214static char *opt_line  = NULL;
215static char *opt_name  = NULL;
216static char *opt_title = NULL;
217
218static uint buttons; /* bit field of pressed buttons */
219
220static int focused = 0;
221static int entered = 0;
222static int oldbutton = 3; /* button event on startup: 3 = release */
223
224void
225clipcopy(const Arg *dummy)
226{
227	Atom clipboard;
228
229	free(xsel.clipboard);
230	xsel.clipboard = NULL;
231
232	if (xsel.primary != NULL) {
233		xsel.clipboard = xstrdup(xsel.primary);
234		clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
235		XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
236	}
237}
238
239void
240clippaste(const Arg *dummy)
241{
242	Atom clipboard;
243
244	clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
245	XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard,
246			xw.win, CurrentTime);
247}
248
249void
250selpaste(const Arg *dummy)
251{
252	XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY,
253			xw.win, CurrentTime);
254}
255
256void
257numlock(const Arg *dummy)
258{
259	win.mode ^= MODE_NUMLOCK;
260}
261
262void
263zoom(const Arg *arg)
264{
265	Arg larg;
266
267	larg.f = usedfontsize + arg->f;
268	if (larg.f >= 1.0)
269		zoomabs(&larg);
270}
271
272void
273zoomabs(const Arg *arg)
274{
275	ImageList *im;
276
277	xunloadfonts();
278	xloadfonts(usedfont, arg->f);
279
280	for (im = term.images; im; im = im->next) {
281		if (im->pixmap)
282			XFreePixmap(xw.dpy, (Drawable)im->pixmap);
283		if (im->clipmask)
284			XFreePixmap(xw.dpy, (Drawable)im->clipmask);
285		im->pixmap = NULL;
286		im->clipmask = NULL;
287	}
288
289	cresize(0, 0);
290	redraw();
291	xhints();
292}
293
294void
295zoomreset(const Arg *arg)
296{
297	Arg larg;
298
299	if (defaultfontsize > 0) {
300		larg.f = defaultfontsize;
301		zoomabs(&larg);
302	}
303}
304
305const char* getcolorname(int i)
306{
307	if (i == defaultbg)
308		return focused ? colorname[i]
309		      : entered ? colorname[defaultbgi]
310		      : colorname[defaultbgu];
311	return colorname[i];
312}
313
314void
315ttysend(const Arg *arg)
316{
317	ttywrite(arg->s, strlen(arg->s), 1);
318}
319
320int
321evcol(XEvent *e)
322{
323	int x = e->xbutton.x - borderpx;
324	LIMIT(x, 0, win.tw - 1);
325	return x / win.cw;
326}
327
328int
329evrow(XEvent *e)
330{
331	int y = e->xbutton.y - borderpx;
332	LIMIT(y, 0, win.th - 1);
333	return y / win.ch;
334}
335
336void
337mousesel(XEvent *e, int done)
338{
339	int type, seltype = SEL_REGULAR;
340	uint state = e->xbutton.state & ~(Button1Mask | forcemousemod);
341
342	for (type = 1; type < LEN(selmasks); ++type) {
343		if (match(selmasks[type], state)) {
344			seltype = type;
345			break;
346		}
347	}
348	selextend(evcol(e), evrow(e), seltype, done);
349	if (done)
350		setsel(getsel(), e->xbutton.time);
351}
352
353void
354mousereport(XEvent *e)
355{
356	int len, btn, code;
357	int x = evcol(e), y = evrow(e);
358	int state = e->xbutton.state;
359	char buf[40];
360	static int ox, oy;
361
362	if (e->type == MotionNotify) {
363		if (x == ox && y == oy)
364			return;
365		if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))
366			return;
367		/* MODE_MOUSEMOTION: no reporting if no button is pressed */
368		if (IS_SET(MODE_MOUSEMOTION) && buttons == 0)
369			return;
370		/* Set btn to lowest-numbered pressed button, or 12 if no
371		 * buttons are pressed. */
372		for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++)
373			;
374		code = 32;
375	} else {
376		btn = e->xbutton.button;
377		/* Only buttons 1 through 11 can be encoded */
378		if (btn < 1 || btn > 11)
379			return;
380		if (e->type == ButtonRelease) {
381			/* MODE_MOUSEX10: no button release reporting */
382			if (IS_SET(MODE_MOUSEX10))
383				return;
384			/* Don't send release events for the scroll wheel */
385			if (btn == 4 || btn == 5)
386				return;
387		}
388		code = 0;
389	}
390
391	ox = x;
392	oy = y;
393
394	/* Encode btn into code. If no button is pressed for a motion event in
395	 * MODE_MOUSEMANY, then encode it as a release. */
396	if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12)
397		code += 3;
398	else if (btn >= 8)
399		code += 128 + btn - 8;
400	else if (btn >= 4)
401		code += 64 + btn - 4;
402	else
403		code += btn - 1;
404
405	if (!IS_SET(MODE_MOUSEX10)) {
406		code += ((state & ShiftMask  ) ?  4 : 0)
407		      + ((state & Mod1Mask   ) ?  8 : 0) /* meta key: alt */
408		      + ((state & ControlMask) ? 16 : 0);
409	}
410
411	if (IS_SET(MODE_MOUSESGR)) {
412		len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
413				code, x+1, y+1,
414				e->type == ButtonRelease ? 'm' : 'M');
415	} else if (x < 223 && y < 223) {
416		len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
417				32+code, 32+x+1, 32+y+1);
418	} else {
419		return;
420	}
421
422	ttywrite(buf, len, 0);
423}
424
425uint
426buttonmask(uint button)
427{
428	return button == Button1 ? Button1Mask
429	     : button == Button2 ? Button2Mask
430	     : button == Button3 ? Button3Mask
431	     : button == Button4 ? Button4Mask
432	     : button == Button5 ? Button5Mask
433	     : 0;
434}
435
436int
437mouseaction(XEvent *e, uint release)
438{
439	MouseShortcut *ms;
440
441	/* ignore Button<N>mask for Button<N> - it's set on release */
442	uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
443
444	for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
445		if (ms->release == release &&
446		    ms->button == e->xbutton.button &&
447		    (match(ms->mod, state) ||  /* exact or forced */
448		     match(ms->mod, state & ~forcemousemod))) {
449			ms->func(&(ms->arg));
450			return 1;
451		}
452	}
453
454	return 0;
455}
456
457void
458bpress(XEvent *e)
459{
460	int btn = e->xbutton.button;
461	struct timespec now;
462	int snap;
463
464	if (1 <= btn && btn <= 11)
465		buttons |= 1 << (btn-1);
466
467	if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
468		mousereport(e);
469		return;
470	}
471
472	if (mouseaction(e, 0))
473		return;
474
475	if (btn == Button1) {
476		/*
477		 * If the user clicks below predefined timeouts specific
478		 * snapping behaviour is exposed.
479		 */
480		clock_gettime(CLOCK_MONOTONIC, &now);
481		if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) {
482			snap = SNAP_LINE;
483		} else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) {
484			snap = SNAP_WORD;
485		} else {
486			snap = 0;
487		}
488		xsel.tclick2 = xsel.tclick1;
489		xsel.tclick1 = now;
490
491		selstart(evcol(e), evrow(e), snap);
492	}
493}
494
495void
496propnotify(XEvent *e)
497{
498	XPropertyEvent *xpev;
499	Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
500
501	xpev = &e->xproperty;
502	if (xpev->state == PropertyNewValue &&
503			(xpev->atom == XA_PRIMARY ||
504			 xpev->atom == clipboard)) {
505		selnotify(e);
506	}
507}
508
509void
510selnotify(XEvent *e)
511{
512	ulong nitems, ofs, rem;
513	int format;
514	uchar *data, *last, *repl;
515	Atom type, incratom, property = None;
516
517	incratom = XInternAtom(xw.dpy, "INCR", 0);
518
519	ofs = 0;
520	if (e->type == SelectionNotify)
521		property = e->xselection.property;
522	else if (e->type == PropertyNotify)
523		property = e->xproperty.atom;
524
525	if (property == None)
526		return;
527
528	do {
529		if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
530					BUFSIZ/4, False, AnyPropertyType,
531					&type, &format, &nitems, &rem,
532					&data)) {
533			fprintf(stderr, "Clipboard allocation failed\n");
534			return;
535		}
536
537		if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
538			/*
539			 * If there is some PropertyNotify with no data, then
540			 * this is the signal of the selection owner that all
541			 * data has been transferred. We won't need to receive
542			 * PropertyNotify events anymore.
543			 */
544			MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);
545			XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
546					&xw.attrs);
547		}
548
549		if (type == incratom) {
550			/*
551			 * Activate the PropertyNotify events so we receive
552			 * when the selection owner does send us the next
553			 * chunk of data.
554			 */
555			MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
556			XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
557					&xw.attrs);
558
559			/*
560			 * Deleting the property is the transfer start signal.
561			 */
562			XDeleteProperty(xw.dpy, xw.win, (int)property);
563			continue;
564		}
565
566		/*
567		 * As seen in getsel:
568		 * Line endings are inconsistent in the terminal and GUI world
569		 * copy and pasting. When receiving some selection data,
570		 * replace all '\n' with '\r'.
571		 * FIXME: Fix the computer world.
572		 */
573		repl = data;
574		last = data + nitems * format / 8;
575		while ((repl = memchr(repl, '\n', last - repl))) {
576			*repl++ = '\r';
577		}
578
579		if (IS_SET(MODE_BRCKTPASTE) && ofs == 0)
580			ttywrite("\033[200~", 6, 0);
581		ttywrite((char *)data, nitems * format / 8, 1);
582		if (IS_SET(MODE_BRCKTPASTE) && rem == 0)
583			ttywrite("\033[201~", 6, 0);
584		XFree(data);
585		/* number of 32-bit chunks returned */
586		ofs += nitems * format / 32;
587	} while (rem > 0);
588
589	/*
590	 * Deleting the property again tells the selection owner to send the
591	 * next data chunk in the property.
592	 */
593	XDeleteProperty(xw.dpy, xw.win, (int)property);
594}
595
596void
597xclipcopy(void)
598{
599	clipcopy(NULL);
600}
601
602void
603selclear_(XEvent *e)
604{
605	selclear();
606}
607
608void
609selrequest(XEvent *e)
610{
611	XSelectionRequestEvent *xsre;
612	XSelectionEvent xev;
613	Atom xa_targets, string, clipboard;
614	char *seltext;
615
616	xsre = (XSelectionRequestEvent *) e;
617	xev.type = SelectionNotify;
618	xev.requestor = xsre->requestor;
619	xev.selection = xsre->selection;
620	xev.target = xsre->target;
621	xev.time = xsre->time;
622	if (xsre->property == None)
623		xsre->property = xsre->target;
624
625	/* reject */
626	xev.property = None;
627
628	xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
629	if (xsre->target == xa_targets) {
630		/* respond with the supported type */
631		string = xsel.xtarget;
632		XChangeProperty(xsre->display, xsre->requestor, xsre->property,
633				XA_ATOM, 32, PropModeReplace,
634				(uchar *) &string, 1);
635		xev.property = xsre->property;
636	} else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) {
637		/*
638		 * xith XA_STRING non ascii characters may be incorrect in the
639		 * requestor. It is not our problem, use utf8.
640		 */
641		clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
642		if (xsre->selection == XA_PRIMARY) {
643			seltext = xsel.primary;
644		} else if (xsre->selection == clipboard) {
645			seltext = xsel.clipboard;
646		} else {
647			fprintf(stderr,
648				"Unhandled clipboard selection 0x%lx\n",
649				xsre->selection);
650			return;
651		}
652		if (seltext != NULL) {
653			XChangeProperty(xsre->display, xsre->requestor,
654					xsre->property, xsre->target,
655					8, PropModeReplace,
656					(uchar *)seltext, strlen(seltext));
657			xev.property = xsre->property;
658		}
659	}
660
661	/* all done, send a notification to the listener */
662	if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev))
663		fprintf(stderr, "Error sending SelectionNotify event\n");
664}
665
666void
667setsel(char *str, Time t)
668{
669	if (!str)
670		return;
671
672	free(xsel.primary);
673	xsel.primary = str;
674
675	XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
676	if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
677		selclear();
678}
679
680void
681xsetsel(char *str)
682{
683	setsel(str, CurrentTime);
684}
685
686void
687brelease(XEvent *e)
688{
689	int btn = e->xbutton.button;
690
691	if (1 <= btn && btn <= 11)
692		buttons &= ~(1 << (btn-1));
693
694	if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
695		mousereport(e);
696		return;
697	}
698
699	if (mouseaction(e, 1))
700		return;
701	if (btn == Button1)
702		mousesel(e, 1);
703}
704
705void
706bmotion(XEvent *e)
707{
708	if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
709		mousereport(e);
710		return;
711	}
712
713	mousesel(e, 0);
714}
715
716void
717cresize(int width, int height)
718{
719	int col, row;
720
721	if (width != 0)
722		win.w = width;
723	if (height != 0)
724		win.h = height;
725
726	col = (win.w - 2 * borderpx) / win.cw;
727	row = (win.h - 2 * borderpx) / win.ch;
728	col = MAX(1, col);
729	row = MAX(1, row);
730
731	tresize(col, row);
732	xresize(col, row);
733	ttyresize(win.tw, win.th);
734}
735
736void
737xresize(int col, int row)
738{
739	win.tw = col * win.cw;
740	win.th = row * win.ch;
741
742	XFreePixmap(xw.dpy, xw.buf);
743	xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
744			DefaultDepth(xw.dpy, xw.scr));
745	XftDrawChange(xw.draw, xw.buf);
746	xclear(0, 0, win.w, win.h);
747
748	/* resize to new width */
749	xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
750}
751
752ushort
753sixd_to_16bit(int x)
754{
755	return x == 0 ? 0 : 0x3737 + 0x2828 * x;
756}
757
758int
759xloadcolor(int i, const char *name, Color *ncolor)
760{
761	XRenderColor color = { .alpha = 0xffff };
762
763	if (!name) {
764		if (BETWEEN(i, 16, 255)) { /* 256 color */
765			if (i < 6*6*6+16) { /* same colors as xterm */
766				color.red   = sixd_to_16bit( ((i-16)/36)%6 );
767				color.green = sixd_to_16bit( ((i-16)/6) %6 );
768				color.blue  = sixd_to_16bit( ((i-16)/1) %6 );
769			} else { /* greyscale */
770				color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16));
771				color.green = color.blue = color.red;
772			}
773			return XftColorAllocValue(xw.dpy, xw.vis,
774			                          xw.cmap, &color, ncolor);
775		} else
776			name = getcolorname(i);
777	}
778
779	return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
780}
781
782void
783redraw_signalhandler(int signum)
784{
785	if (signum == SIGREDRW) {
786		xloadcols();
787		redraw();
788		xhints();
789	}
790}
791
792void
793xloadcols(void)
794{
795	int i;
796	static int loaded;
797	Color *cp;
798
799	signal(SIGREDRW, &redraw_signalhandler);
800
801	if (loaded) {
802		for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
803			XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
804	} else {
805		dc.collen = MAX(LEN(colorname), 256);
806		dc.col = xmalloc(dc.collen * sizeof(Color));
807	}
808
809	for (i = 0; i < dc.collen; i++)
810		if (!xloadcolor(i, NULL, &dc.col[i])) {
811			if (getcolorname(i))
812				die("could not allocate color '%s'\n", getcolorname(i));
813			else
814				die("could not allocate color %d\n", i);
815		}
816	loaded = 1;
817}
818
819int
820xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b)
821{
822	if (!BETWEEN(x, 0, dc.collen - 1))
823		return 1;
824
825	*r = dc.col[x].color.red >> 8;
826	*g = dc.col[x].color.green >> 8;
827	*b = dc.col[x].color.blue >> 8;
828
829	return 0;
830}
831
832int
833xsetcolorname(int x, const char *name)
834{
835	Color ncolor;
836
837	if (!BETWEEN(x, 0, dc.collen - 1))
838		return 1;
839
840	if (!xloadcolor(x, name, &ncolor))
841		return 1;
842
843	XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
844	dc.col[x] = ncolor;
845
846	return 0;
847}
848
849/*
850 * Absolute coordinates.
851 */
852void
853xclear(int x1, int y1, int x2, int y2)
854{
855	XftDrawRect(xw.draw,
856			&dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],
857			x1, y1, x2-x1, y2-y1);
858}
859
860void
861xhints(void)
862{
863	XClassHint class = {opt_name ? opt_name : "st",
864	                    opt_class ? opt_class : "St"};
865	XWMHints wm = {.flags = InputHint, .input = 1};
866	XSizeHints *sizeh;
867
868	sizeh = XAllocSizeHints();
869
870	sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
871	sizeh->height = win.h;
872	sizeh->width = win.w;
873	sizeh->height_inc = win.ch;
874	sizeh->width_inc = win.cw;
875	sizeh->base_height = 2 * borderpx;
876	sizeh->base_width = 2 * borderpx;
877	sizeh->min_height = win.ch + 2 * borderpx;
878	sizeh->min_width = win.cw + 2 * borderpx;
879	if (xw.isfixed) {
880		sizeh->flags |= PMaxSize;
881		sizeh->min_width = sizeh->max_width = win.w;
882		sizeh->min_height = sizeh->max_height = win.h;
883	}
884	if (xw.gm & (XValue|YValue)) {
885		sizeh->flags |= USPosition | PWinGravity;
886		sizeh->x = xw.l;
887		sizeh->y = xw.t;
888		sizeh->win_gravity = xgeommasktogravity(xw.gm);
889	}
890
891	XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm,
892			&class);
893	XFree(sizeh);
894}
895
896int
897xgeommasktogravity(int mask)
898{
899	switch (mask & (XNegative|YNegative)) {
900	case 0:
901		return NorthWestGravity;
902	case XNegative:
903		return NorthEastGravity;
904	case YNegative:
905		return SouthWestGravity;
906	}
907
908	return SouthEastGravity;
909}
910
911int
912xloadfont(Font *f, FcPattern *pattern)
913{
914	FcPattern *configured;
915	FcPattern *match;
916	FcResult result;
917	XGlyphInfo extents;
918	int wantattr, haveattr;
919
920	/*
921	 * Manually configure instead of calling XftMatchFont
922	 * so that we can use the configured pattern for
923	 * "missing glyph" lookups.
924	 */
925	configured = FcPatternDuplicate(pattern);
926	if (!configured)
927		return 1;
928
929	FcConfigSubstitute(NULL, configured, FcMatchPattern);
930	XftDefaultSubstitute(xw.dpy, xw.scr, configured);
931
932	match = FcFontMatch(NULL, configured, &result);
933	if (!match) {
934		FcPatternDestroy(configured);
935		return 1;
936	}
937
938	if (!(f->match = XftFontOpenPattern(xw.dpy, match))) {
939		FcPatternDestroy(configured);
940		FcPatternDestroy(match);
941		return 1;
942	}
943
944	if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) ==
945	    XftResultMatch)) {
946		/*
947		 * Check if xft was unable to find a font with the appropriate
948		 * slant but gave us one anyway. Try to mitigate.
949		 */
950		if ((XftPatternGetInteger(f->match->pattern, "slant", 0,
951		    &haveattr) != XftResultMatch) || haveattr < wantattr) {
952			f->badslant = 1;
953			fputs("font slant does not match\n", stderr);
954		}
955	}
956
957	if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) ==
958	    XftResultMatch)) {
959		if ((XftPatternGetInteger(f->match->pattern, "weight", 0,
960		    &haveattr) != XftResultMatch) || haveattr != wantattr) {
961			f->badweight = 1;
962			fputs("font weight does not match\n", stderr);
963		}
964	}
965
966	XftTextExtentsUtf8(xw.dpy, f->match,
967		(const FcChar8 *) ascii_printable,
968		strlen(ascii_printable), &extents);
969
970	f->set = NULL;
971	f->pattern = configured;
972
973	f->ascent = f->match->ascent;
974	f->descent = f->match->descent;
975	f->lbearing = 0;
976	f->rbearing = f->match->max_advance_width;
977
978	f->height = f->ascent + f->descent;
979	f->width = DIVCEIL(extents.xOff, strlen(ascii_printable));
980
981	return 0;
982}
983
984void
985xloadfonts(const char *fontstr, double fontsize)
986{
987	FcPattern *pattern;
988	double fontval;
989
990	if (fontstr[0] == '-')
991		pattern = XftXlfdParse(fontstr, False, False);
992	else
993		pattern = FcNameParse((const FcChar8 *)fontstr);
994
995	if (!pattern)
996		die("can't open font %s\n", fontstr);
997
998	if (fontsize > 1) {
999		FcPatternDel(pattern, FC_PIXEL_SIZE);
1000		FcPatternDel(pattern, FC_SIZE);
1001		FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize);
1002		usedfontsize = fontsize;
1003	} else {
1004		if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
1005				FcResultMatch) {
1006			usedfontsize = fontval;
1007		} else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
1008				FcResultMatch) {
1009			usedfontsize = -1;
1010		} else {
1011			/*
1012			 * Default font size is 12, if none given. This is to
1013			 * have a known usedfontsize value.
1014			 */
1015			FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
1016			usedfontsize = 12;
1017		}
1018		defaultfontsize = usedfontsize;
1019	}
1020
1021	if (xloadfont(&dc.font, pattern))
1022		die("can't open font %s\n", fontstr);
1023
1024	if (usedfontsize < 0) {
1025		FcPatternGetDouble(dc.font.match->pattern,
1026		                   FC_PIXEL_SIZE, 0, &fontval);
1027		usedfontsize = fontval;
1028		if (fontsize == 0)
1029			defaultfontsize = fontval;
1030	}
1031
1032	/* Setting character width and height. */
1033	win.cw = ceilf(dc.font.width * cwscale);
1034	win.ch = ceilf(dc.font.height * chscale);
1035	win.cyo = ceilf(dc.font.height * (chscale - 1) / 2);
1036
1037	FcPatternDel(pattern, FC_SLANT);
1038	FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
1039	if (xloadfont(&dc.ifont, pattern))
1040		die("can't open font %s\n", fontstr);
1041
1042	FcPatternDel(pattern, FC_WEIGHT);
1043	FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
1044	if (xloadfont(&dc.ibfont, pattern))
1045		die("can't open font %s\n", fontstr);
1046
1047	FcPatternDel(pattern, FC_SLANT);
1048	FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
1049	if (xloadfont(&dc.bfont, pattern))
1050		die("can't open font %s\n", fontstr);
1051
1052	FcPatternDestroy(pattern);
1053}
1054
1055void
1056xunloadfont(Font *f)
1057{
1058	XftFontClose(xw.dpy, f->match);
1059	FcPatternDestroy(f->pattern);
1060	if (f->set)
1061		FcFontSetDestroy(f->set);
1062}
1063
1064void
1065xunloadfonts(void)
1066{
1067	/* Free the loaded fonts in the font cache.  */
1068	while (frclen > 0)
1069		XftFontClose(xw.dpy, frc[--frclen].font);
1070
1071	xunloadfont(&dc.font);
1072	xunloadfont(&dc.bfont);
1073	xunloadfont(&dc.ifont);
1074	xunloadfont(&dc.ibfont);
1075}
1076
1077int
1078ximopen(Display *dpy)
1079{
1080	XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };
1081	XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };
1082
1083	xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
1084	if (xw.ime.xim == NULL)
1085		return 0;
1086
1087	if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL))
1088		fprintf(stderr, "XSetIMValues: "
1089		                "Could not set XNDestroyCallback.\n");
1090
1091	xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot,
1092	                                      NULL);
1093
1094	if (xw.ime.xic == NULL) {
1095		xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle,
1096		                       XIMPreeditNothing | XIMStatusNothing,
1097		                       XNClientWindow, xw.win,
1098		                       XNDestroyCallback, &icdestroy,
1099		                       NULL);
1100	}
1101	if (xw.ime.xic == NULL)
1102		fprintf(stderr, "XCreateIC: Could not create input context.\n");
1103
1104	return 1;
1105}
1106
1107void
1108ximinstantiate(Display *dpy, XPointer client, XPointer call)
1109{
1110	if (ximopen(dpy))
1111		XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
1112		                                 ximinstantiate, NULL);
1113}
1114
1115void
1116ximdestroy(XIM xim, XPointer client, XPointer call)
1117{
1118	xw.ime.xim = NULL;
1119	XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
1120	                               ximinstantiate, NULL);
1121	XFree(xw.ime.spotlist);
1122}
1123
1124int
1125xicdestroy(XIC xim, XPointer client, XPointer call)
1126{
1127	xw.ime.xic = NULL;
1128	return 1;
1129}
1130
1131void
1132xinit(int cols, int rows)
1133{
1134	XGCValues gcvalues;
1135	Cursor cursor;
1136	Window parent;
1137	pid_t thispid = getpid();
1138	XColor xmousefg, xmousebg;
1139
1140	xw.scr = XDefaultScreen(xw.dpy);
1141	xw.vis = XDefaultVisual(xw.dpy, xw.scr);
1142
1143	/* font */
1144	if (!FcInit())
1145		die("could not init fontconfig.\n");
1146
1147	usedfont = (opt_font == NULL)? font : opt_font;
1148	xloadfonts(usedfont, 0);
1149
1150	/* colors */
1151	xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
1152	xloadcols();
1153
1154	/* adjust fixed window geometry */
1155	win.w = 2 * borderpx + cols * win.cw;
1156	win.h = 2 * borderpx + rows * win.ch;
1157	if (xw.gm & XNegative)
1158		xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
1159	if (xw.gm & YNegative)
1160		xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2;
1161
1162	/* Events */
1163	xw.attrs.background_pixel = dc.col[defaultbg].pixel;
1164	xw.attrs.border_pixel = dc.col[defaultbg].pixel;
1165	xw.attrs.bit_gravity = NorthWestGravity;
1166	xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask
1167		| ExposureMask | VisibilityChangeMask | StructureNotifyMask
1168		| ButtonMotionMask | ButtonPressMask | ButtonReleaseMask
1169		| EnterWindowMask | LeaveWindowMask;
1170	xw.attrs.colormap = xw.cmap;
1171
1172	if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))
1173		parent = XRootWindow(xw.dpy, xw.scr);
1174	xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
1175			win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
1176			xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
1177			| CWEventMask | CWColormap, &xw.attrs);
1178
1179	memset(&gcvalues, 0, sizeof(gcvalues));
1180	gcvalues.graphics_exposures = False;
1181	dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
1182			&gcvalues);
1183	xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
1184			DefaultDepth(xw.dpy, xw.scr));
1185	XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
1186	XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
1187
1188	/* font spec buffer */
1189	xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
1190
1191	/* Xft rendering context */
1192	xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
1193
1194	/* input methods */
1195	if (!ximopen(xw.dpy)) {
1196		XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
1197	                                       ximinstantiate, NULL);
1198	}
1199
1200	/* white cursor, black outline */
1201	cursor = XcursorLibraryLoadCursor(xw.dpy, mouseshape);
1202	XDefineCursor(xw.dpy, xw.win, cursor);
1203
1204
1205	xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
1206	xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
1207	xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
1208	xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False);
1209	XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
1210
1211	xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
1212	XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
1213			PropModeReplace, (uchar *)&thispid, 1);
1214
1215	win.mode = MODE_NUMLOCK;
1216	resettitle();
1217	xhints();
1218	XMapWindow(xw.dpy, xw.win);
1219	XSync(xw.dpy, False);
1220
1221	clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1);
1222	clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2);
1223	xsel.primary = NULL;
1224	xsel.clipboard = NULL;
1225	xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
1226	if (xsel.xtarget == None)
1227		xsel.xtarget = XA_STRING;
1228
1229	boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis);
1230}
1231
1232int
1233xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
1234{
1235	float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
1236	ushort mode, prevmode = USHRT_MAX;
1237	Font *font = &dc.font;
1238	int frcflags = FRC_NORMAL;
1239	float runewidth = win.cw;
1240	Rune rune;
1241	FT_UInt glyphidx;
1242	FcResult fcres;
1243	FcPattern *fcpattern, *fontpattern;
1244	FcFontSet *fcsets[] = { NULL };
1245	FcCharSet *fccharset;
1246	int i, f, numspecs = 0;
1247
1248	for (i = 0, xp = winx, yp = winy + font->ascent + win.cyo; i < len; ++i) {
1249		/* Fetch rune and mode for current glyph. */
1250		rune = glyphs[i].u;
1251		mode = glyphs[i].mode;
1252
1253		/* Skip dummy wide-character spacing. */
1254		if (mode == ATTR_WDUMMY)
1255			continue;
1256
1257		/* Determine font for glyph if different from previous glyph. */
1258		if (prevmode != mode) {
1259			prevmode = mode;
1260			font = &dc.font;
1261			frcflags = FRC_NORMAL;
1262			runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
1263			if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
1264				font = &dc.ibfont;
1265				frcflags = FRC_ITALICBOLD;
1266			} else if (mode & ATTR_ITALIC) {
1267				font = &dc.ifont;
1268				frcflags = FRC_ITALIC;
1269			} else if (mode & ATTR_BOLD) {
1270				font = &dc.bfont;
1271				frcflags = FRC_BOLD;
1272			}
1273			yp = winy + font->ascent + win.cyo;
1274		}
1275
1276		if (mode & ATTR_BOXDRAW) {
1277			/* minor shoehorning: boxdraw uses only this ushort */
1278			glyphidx = boxdrawindex(&glyphs[i]);
1279		} else {
1280			/* Lookup character index with default font. */
1281			glyphidx = XftCharIndex(xw.dpy, font->match, rune);
1282		}
1283		if (glyphidx) {
1284			specs[numspecs].font = font->match;
1285			specs[numspecs].glyph = glyphidx;
1286			specs[numspecs].x = (short)xp;
1287			specs[numspecs].y = (short)yp;
1288			xp += runewidth;
1289			numspecs++;
1290			continue;
1291		}
1292
1293		/* Fallback on font cache, search the font cache for match. */
1294		for (f = 0; f < frclen; f++) {
1295			glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
1296			/* Everything correct. */
1297			if (glyphidx && frc[f].flags == frcflags)
1298				break;
1299			/* We got a default font for a not found glyph. */
1300			if (!glyphidx && frc[f].flags == frcflags
1301					&& frc[f].unicodep == rune) {
1302				break;
1303			}
1304		}
1305
1306		/* Nothing was found. Use fontconfig to find matching font. */
1307		if (f >= frclen) {
1308			if (!font->set)
1309				font->set = FcFontSort(0, font->pattern,
1310				                       1, 0, &fcres);
1311			fcsets[0] = font->set;
1312
1313			/*
1314			 * Nothing was found in the cache. Now use
1315			 * some dozen of Fontconfig calls to get the
1316			 * font for one single character.
1317			 *
1318			 * Xft and fontconfig are design failures.
1319			 */
1320			fcpattern = FcPatternDuplicate(font->pattern);
1321			fccharset = FcCharSetCreate();
1322
1323			FcCharSetAddChar(fccharset, rune);
1324			FcPatternAddCharSet(fcpattern, FC_CHARSET,
1325					fccharset);
1326			FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
1327
1328			FcConfigSubstitute(0, fcpattern,
1329					FcMatchPattern);
1330			FcDefaultSubstitute(fcpattern);
1331
1332			fontpattern = FcFontSetMatch(0, fcsets, 1,
1333					fcpattern, &fcres);
1334
1335			/* Allocate memory for the new cache entry. */
1336			if (frclen >= frccap) {
1337				frccap += 16;
1338				frc = xrealloc(frc, frccap * sizeof(Fontcache));
1339			}
1340
1341			frc[frclen].font = XftFontOpenPattern(xw.dpy,
1342					fontpattern);
1343			if (!frc[frclen].font)
1344				die("XftFontOpenPattern failed seeking fallback font: %s\n",
1345					strerror(errno));
1346			frc[frclen].flags = frcflags;
1347			frc[frclen].unicodep = rune;
1348
1349			glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
1350
1351			f = frclen;
1352			frclen++;
1353
1354			FcPatternDestroy(fcpattern);
1355			FcCharSetDestroy(fccharset);
1356		}
1357
1358		specs[numspecs].font = frc[f].font;
1359		specs[numspecs].glyph = glyphidx;
1360		specs[numspecs].x = (short)xp;
1361		specs[numspecs].y = (short)yp;
1362		xp += runewidth;
1363		numspecs++;
1364	}
1365
1366	return numspecs;
1367}
1368
1369static int isSlopeRising (int x, int iPoint, int waveWidth)
1370{
1371	//    .     .     .     .
1372	//   / \   / \   / \   / \
1373	//  /   \ /   \ /   \ /   \
1374	// .     .     .     .     .
1375
1376	// Find absolute `x` of point
1377	x += iPoint * (waveWidth/2);
1378
1379	// Find index of absolute wave
1380	int absSlope = x / ((float)waveWidth/2);
1381
1382	return (absSlope % 2);
1383}
1384
1385static int getSlope (int x, int iPoint, int waveWidth)
1386{
1387	// Sizes: Caps are half width of slopes
1388	//    1_2       1_2       1_2      1_2
1389	//   /   \     /   \     /   \    /   \
1390	//  /     \   /     \   /     \  /     \
1391	// 0       3_0       3_0      3_0       3_
1392	// <2->    <1>         <---6---->
1393
1394	// Find type of first point
1395	int firstType;
1396	x -= (x / waveWidth) * waveWidth;
1397	if (x < (waveWidth * (2.f/6.f)))
1398		firstType = UNDERCURL_SLOPE_ASCENDING;
1399	else if (x < (waveWidth * (3.f/6.f)))
1400		firstType = UNDERCURL_SLOPE_TOP_CAP;
1401	else if (x < (waveWidth * (5.f/6.f)))
1402		firstType = UNDERCURL_SLOPE_DESCENDING;
1403	else
1404		firstType = UNDERCURL_SLOPE_BOTTOM_CAP;
1405
1406	// Find type of given point
1407	int pointType = (iPoint % 4);
1408	pointType += firstType;
1409	pointType %= 4;
1410
1411	return pointType;
1412}
1413
1414void
1415xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int dmode)
1416{
1417	int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
1418	int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
1419	    width = charlen * win.cw;
1420	Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
1421	XRenderColor colfg, colbg;
1422	XRectangle r;
1423
1424	/* Fallback on color display for attributes not supported by the font */
1425	if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) {
1426		if (dc.ibfont.badslant || dc.ibfont.badweight)
1427			base.fg = defaultattr;
1428	} else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) ||
1429	    (base.mode & ATTR_BOLD && dc.bfont.badweight)) {
1430		base.fg = defaultattr;
1431	}
1432
1433	if (IS_TRUECOL(base.fg)) {
1434		colfg.alpha = 0xffff;
1435		colfg.red = TRUERED(base.fg);
1436		colfg.green = TRUEGREEN(base.fg);
1437		colfg.blue = TRUEBLUE(base.fg);
1438		XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg);
1439		fg = &truefg;
1440	} else {
1441		fg = &dc.col[base.fg];
1442	}
1443
1444	if (IS_TRUECOL(base.bg)) {
1445		colbg.alpha = 0xffff;
1446		colbg.green = TRUEGREEN(base.bg);
1447		colbg.red = TRUERED(base.bg);
1448		colbg.blue = TRUEBLUE(base.bg);
1449		XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg);
1450		bg = &truebg;
1451	} else {
1452		bg = &dc.col[base.bg];
1453	}
1454
1455	/* Change basic system colors [0-7] to bright system colors [8-15] */
1456	if (boldisbright && (base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
1457		fg = &dc.col[base.fg + 8];
1458
1459	if (IS_SET(MODE_REVERSE)) {
1460		if (fg == &dc.col[defaultfg]) {
1461			fg = &dc.col[defaultbg];
1462		} else {
1463			colfg.red = ~fg->color.red;
1464			colfg.green = ~fg->color.green;
1465			colfg.blue = ~fg->color.blue;
1466			colfg.alpha = fg->color.alpha;
1467			XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg,
1468					&revfg);
1469			fg = &revfg;
1470		}
1471
1472		if (bg == &dc.col[defaultbg]) {
1473			bg = &dc.col[defaultfg];
1474		} else {
1475			colbg.red = ~bg->color.red;
1476			colbg.green = ~bg->color.green;
1477			colbg.blue = ~bg->color.blue;
1478			colbg.alpha = bg->color.alpha;
1479			XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg,
1480					&revbg);
1481			bg = &revbg;
1482		}
1483	}
1484
1485	if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {
1486		colfg.red = fg->color.red / 2;
1487		colfg.green = fg->color.green / 2;
1488		colfg.blue = fg->color.blue / 2;
1489		colfg.alpha = fg->color.alpha;
1490		XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
1491		fg = &revfg;
1492	}
1493
1494	if (base.mode & ATTR_REVERSE) {
1495		temp = fg;
1496		fg = bg;
1497		bg = temp;
1498	}
1499
1500	if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
1501		fg = bg;
1502
1503	if (base.mode & ATTR_INVISIBLE)
1504		fg = bg;
1505
1506	if (dmode & DRAW_BG) {
1507		/* Intelligent cleaning up of the borders. */
1508		if (x == 0) {
1509			xclear(0, (y == 0)? 0 : winy, borderpx,
1510				winy + win.ch +
1511				((winy + win.ch >= borderpx + win.th)? win.h : 0));
1512		}
1513		if (winx + width >= borderpx + win.tw) {
1514			xclear(winx + width, (y == 0)? 0 : winy, win.w,
1515				((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));
1516		}
1517		if (y == 0)
1518			xclear(winx, 0, winx + width, borderpx);
1519		if (winy + win.ch >= borderpx + win.th)
1520			xclear(winx, winy + win.ch, winx + width, win.h);
1521		/* Fill the background */
1522		XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
1523	}
1524
1525	if (dmode & DRAW_FG) {
1526		if (base.mode & ATTR_BOXDRAW) {
1527			drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len);
1528		} else {
1529			/* Render the glyphs. */
1530			XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
1531		}
1532
1533		/* Render underline and strikethrough. */
1534		if (base.mode & ATTR_UNDERLINE) {
1535			// Underline Color
1536			const int widthThreshold  = 28; // +1 width every widthThreshold px of font
1537			int wlw = (win.ch / widthThreshold) + 1; // Wave Line Width
1538			int linecolor;
1539			if ((base.ucolor[0] >= 0) &&
1540				!(base.mode & ATTR_BLINK && win.mode & MODE_BLINK) &&
1541				!(base.mode & ATTR_INVISIBLE)
1542			) {
1543				// Special color for underline
1544				// Index
1545				if (base.ucolor[1] < 0) {
1546					linecolor = dc.col[base.ucolor[0]].pixel;
1547				}
1548				// RGB
1549				else {
1550					XColor lcolor;
1551					lcolor.red = base.ucolor[0] * 257;
1552					lcolor.green = base.ucolor[1] * 257;
1553					lcolor.blue = base.ucolor[2] * 257;
1554					lcolor.flags = DoRed | DoGreen | DoBlue;
1555					XAllocColor(xw.dpy, xw.cmap, &lcolor);
1556					linecolor = lcolor.pixel;
1557				}
1558			} else {
1559				// Foreground color for underline
1560				linecolor = fg->pixel;
1561			}
1562
1563			XGCValues ugcv = {
1564				.foreground = linecolor,
1565				.line_width = wlw,
1566				.line_style = LineSolid,
1567				.cap_style = CapNotLast
1568			};
1569
1570			GC ugc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw),
1571				GCForeground | GCLineWidth | GCLineStyle | GCCapStyle,
1572				&ugcv);
1573
1574			// Underline Style
1575			if (base.ustyle != 3) {
1576				XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx,
1577					winy + dc.font.ascent + 1, width, wlw);
1578			} else if (base.ustyle == 3) {
1579				int ww = win.cw;//width;
1580				int wh = dc.font.descent - wlw/2 - 1;//r.height/7;
1581				int wx = winx;
1582				int wy = winy + win.ch - dc.font.descent;
1583
1584#if UNDERCURL_STYLE == UNDERCURL_CURLY
1585				// Draw waves
1586				int narcs = charlen * 2 + 1;
1587				XArc *arcs = xmalloc(sizeof(XArc) * narcs);
1588
1589				int i = 0;
1590				for (i = 0; i < charlen-1; i++) {
1591					arcs[i*2] = (XArc) {
1592						.x = wx + win.cw * i + ww / 4,
1593						.y = wy,
1594						.width = win.cw / 2,
1595						.height = wh,
1596						.angle1 = 0,
1597						.angle2 = 180 * 64
1598					};
1599					arcs[i*2+1] = (XArc) {
1600						.x = wx + win.cw * i + ww * 0.75,
1601						.y = wy,
1602						.width = win.cw/2,
1603						.height = wh,
1604						.angle1 = 180 * 64,
1605						.angle2 = 180 * 64
1606					};
1607				}
1608				// Last wave
1609				arcs[i*2] = (XArc) {wx + ww * i + ww / 4, wy, ww / 2, wh,
1610				0, 180 * 64 };
1611				// Last wave tail
1612				arcs[i*2+1] = (XArc) {wx + ww * i + ww * 0.75, wy, ceil(ww / 2.),
1613				wh, 180 * 64, 90 * 64};
1614				// First wave tail
1615				i++;
1616				arcs[i*2] = (XArc) {wx - ww/4 - 1, wy, ceil(ww / 2.), wh, 270 * 64,
1617				90 * 64 };
1618
1619				XDrawArcs(xw.dpy, XftDrawDrawable(xw.draw), ugc, arcs, narcs);
1620
1621				free(arcs);
1622#elif UNDERCURL_STYLE == UNDERCURL_SPIKY
1623				// Make the underline corridor larger
1624				/*
1625				wy -= wh;
1626				*/
1627				wh *= 2;
1628
1629				// Set the angle of the slope to 45°
1630				ww = wh;
1631
1632				// Position of wave is independent of word, it's absolute
1633				wx = (wx / (ww/2)) * (ww/2);
1634
1635				int marginStart = winx - wx;
1636
1637				// Calculate number of points with floating precision
1638				float n = width;					// Width of word in pixels
1639				n = (n / ww) * 2;					// Number of slopes (/ or \)
1640				n += 2;								// Add two last points
1641				int npoints = n;					// Convert to int
1642
1643				// Total length of underline
1644				float waveLength = 0;
1645
1646				if (npoints >= 3) {
1647					// We add an aditional slot in case we use a bonus point
1648					XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
1649
1650					// First point (Starts with the word bounds)
1651					points[0] = (XPoint) {
1652						.x = wx + marginStart,
1653						.y = (isSlopeRising(wx, 0, ww))
1654							? (wy - marginStart + ww/2.f)
1655							: (wy + marginStart)
1656					};
1657
1658					// Second point (Goes back to the absolute point coordinates)
1659					points[1] = (XPoint) {
1660						.x = (ww/2.f) - marginStart,
1661						.y = (isSlopeRising(wx, 1, ww))
1662							? (ww/2.f - marginStart)
1663							: (-ww/2.f + marginStart)
1664					};
1665					waveLength += (ww/2.f) - marginStart;
1666
1667					// The rest of the points
1668					for (int i = 2; i < npoints-1; i++) {
1669						points[i] = (XPoint) {
1670							.x = ww/2,
1671							.y = (isSlopeRising(wx, i, ww))
1672								? wh/2
1673								: -wh/2
1674						};
1675						waveLength += ww/2;
1676					}
1677
1678					// Last point
1679					points[npoints-1] = (XPoint) {
1680						.x = ww/2,
1681						.y = (isSlopeRising(wx, npoints-1, ww))
1682							? wh/2
1683							: -wh/2
1684					};
1685					waveLength += ww/2;
1686
1687					// End
1688					if (waveLength < width) { // Add a bonus point?
1689						int marginEnd = width - waveLength;
1690						points[npoints] = (XPoint) {
1691							.x = marginEnd,
1692							.y = (isSlopeRising(wx, npoints, ww))
1693								? (marginEnd)
1694								: (-marginEnd)
1695						};
1696
1697						npoints++;
1698					} else if (waveLength > width) { // Is last point too far?
1699						int marginEnd = waveLength - width;
1700						points[npoints-1].x -= marginEnd;
1701						if (isSlopeRising(wx, npoints-1, ww))
1702							points[npoints-1].y -= (marginEnd);
1703						else
1704							points[npoints-1].y += (marginEnd);
1705					}
1706
1707					// Draw the lines
1708					XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
1709							CoordModePrevious);
1710
1711					// Draw a second underline with an offset of 1 pixel
1712					if ( ((win.ch / (widthThreshold/2)) % 2)) {
1713						points[0].x++;
1714
1715						XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
1716								npoints, CoordModePrevious);
1717					}
1718
1719					// Free resources
1720					free(points);
1721				}
1722#else // UNDERCURL_CAPPED
1723				// Cap is half of wave width
1724				float capRatio = 0.5f;
1725
1726				// Make the underline corridor larger
1727				wh *= 2;
1728
1729				// Set the angle of the slope to 45°
1730				ww = wh;
1731				ww *= 1 + capRatio; // Add a bit of width for the cap
1732
1733				// Position of wave is independent of word, it's absolute
1734				wx = (wx / ww) * ww;
1735
1736				float marginStart;
1737				switch(getSlope(winx, 0, ww)) {
1738					case UNDERCURL_SLOPE_ASCENDING:
1739						marginStart = winx - wx;
1740						break;
1741					case UNDERCURL_SLOPE_TOP_CAP:
1742						marginStart = winx - (wx + (ww * (2.f/6.f)));
1743						break;
1744					case UNDERCURL_SLOPE_DESCENDING:
1745						marginStart = winx - (wx + (ww * (3.f/6.f)));
1746						break;
1747					case UNDERCURL_SLOPE_BOTTOM_CAP:
1748						marginStart = winx - (wx + (ww * (5.f/6.f)));
1749						break;
1750				}
1751
1752				// Calculate number of points with floating precision
1753				float n = width;					// Width of word in pixels
1754													//					   ._.
1755				n = (n / ww) * 4;					// Number of points (./   \.)
1756				n += 2;								// Add two last points
1757				int npoints = n;					// Convert to int
1758
1759				// Position of the pen to draw the lines
1760				float penX = 0;
1761				float penY = 0;
1762
1763				if (npoints >= 3) {
1764					XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
1765
1766					// First point (Starts with the word bounds)
1767					penX = winx;
1768					switch (getSlope(winx, 0, ww)) {
1769						case UNDERCURL_SLOPE_ASCENDING:
1770							penY = wy + wh/2.f - marginStart;
1771							break;
1772						case UNDERCURL_SLOPE_TOP_CAP:
1773							penY = wy;
1774							break;
1775						case UNDERCURL_SLOPE_DESCENDING:
1776							penY = wy + marginStart;
1777							break;
1778						case UNDERCURL_SLOPE_BOTTOM_CAP:
1779							penY = wy + wh/2.f;
1780							break;
1781					}
1782					points[0].x = penX;
1783					points[0].y = penY;
1784
1785					// Second point (Goes back to the absolute point coordinates)
1786					switch (getSlope(winx, 1, ww)) {
1787						case UNDERCURL_SLOPE_ASCENDING:
1788							penX += ww * (1.f/6.f) - marginStart;
1789							penY += 0;
1790							break;
1791						case UNDERCURL_SLOPE_TOP_CAP:
1792							penX += ww * (2.f/6.f) - marginStart;
1793							penY += -wh/2.f + marginStart;
1794							break;
1795						case UNDERCURL_SLOPE_DESCENDING:
1796							penX += ww * (1.f/6.f) - marginStart;
1797							penY += 0;
1798							break;
1799						case UNDERCURL_SLOPE_BOTTOM_CAP:
1800							penX += ww * (2.f/6.f) - marginStart;
1801							penY += -marginStart + wh/2.f;
1802							break;
1803					}
1804					points[1].x = penX;
1805					points[1].y = penY;
1806
1807					// The rest of the points
1808					for (int i = 2; i < npoints; i++) {
1809						switch (getSlope(winx, i, ww)) {
1810							case UNDERCURL_SLOPE_ASCENDING:
1811							case UNDERCURL_SLOPE_DESCENDING:
1812								penX += ww * (1.f/6.f);
1813								penY += 0;
1814								break;
1815							case UNDERCURL_SLOPE_TOP_CAP:
1816								penX += ww * (2.f/6.f);
1817								penY += -wh / 2.f;
1818								break;
1819							case UNDERCURL_SLOPE_BOTTOM_CAP:
1820								penX += ww * (2.f/6.f);
1821								penY += wh / 2.f;
1822								break;
1823						}
1824						points[i].x = penX;
1825						points[i].y = penY;
1826					}
1827
1828					// End
1829					float waveLength = penX - winx;
1830					if (waveLength < width) { // Add a bonus point?
1831						int marginEnd = width - waveLength;
1832						penX += marginEnd;
1833						switch(getSlope(winx, npoints, ww)) {
1834							case UNDERCURL_SLOPE_ASCENDING:
1835							case UNDERCURL_SLOPE_DESCENDING:
1836								//penY += 0;
1837								break;
1838							case UNDERCURL_SLOPE_TOP_CAP:
1839								penY += -marginEnd;
1840								break;
1841							case UNDERCURL_SLOPE_BOTTOM_CAP:
1842								penY += marginEnd;
1843								break;
1844						}
1845
1846						points[npoints].x = penX;
1847						points[npoints].y = penY;
1848
1849						npoints++;
1850					} else if (waveLength > width) { // Is last point too far?
1851						int marginEnd = waveLength - width;
1852						points[npoints-1].x -= marginEnd;
1853						switch(getSlope(winx, npoints-1, ww)) {
1854							case UNDERCURL_SLOPE_TOP_CAP:
1855								points[npoints-1].y += marginEnd;
1856								break;
1857							case UNDERCURL_SLOPE_BOTTOM_CAP:
1858								points[npoints-1].y -= marginEnd;
1859								break;
1860							default:
1861								break;
1862						}
1863					}
1864
1865					// Draw the lines
1866					XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
1867							CoordModeOrigin);
1868
1869					// Draw a second underline with an offset of 1 pixel
1870					if ( ((win.ch / (widthThreshold/2)) % 2)) {
1871						for (int i = 0; i < npoints; i++)
1872							points[i].x++;
1873
1874						XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
1875								npoints, CoordModeOrigin);
1876					}
1877
1878					// Free resources
1879					free(points);
1880				}
1881#endif
1882			}
1883
1884			XFreeGC(xw.dpy, ugc);
1885		}
1886
1887		if (base.mode & ATTR_STRUCK) {
1888			XftDrawRect(xw.draw, fg, winx, winy + win.cyo + 2 * dc.font.ascent * chscale / 3,
1889					width, 1);
1890		}
1891	}
1892}
1893
1894void
1895xdrawglyph(Glyph g, int x, int y)
1896{
1897	int numspecs;
1898	XftGlyphFontSpec spec;
1899
1900	numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
1901	xdrawglyphfontspecs(&spec, g, numspecs, x, y, DRAW_BG | DRAW_FG);
1902}
1903
1904void
1905xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
1906{
1907	Color drawcol;
1908
1909	/* remove the old cursor */
1910	if (selected(ox, oy))
1911		og.mode ^= ATTR_REVERSE;
1912	xdrawglyph(og, ox, oy);
1913
1914	if (IS_SET(MODE_HIDE))
1915		return;
1916
1917	/*
1918	 * Select the right color for the right mode.
1919	 */
1920	g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW;
1921
1922	if (IS_SET(MODE_REVERSE)) {
1923		g.mode |= ATTR_REVERSE;
1924		g.bg = defaultfg;
1925		if (selected(cx, cy)) {
1926			drawcol = dc.col[defaultcs];
1927			g.fg = defaultrcs;
1928		} else {
1929			drawcol = dc.col[defaultrcs];
1930			g.fg = defaultcs;
1931		}
1932	} else {
1933		if (selected(cx, cy)) {
1934			g.fg = defaultfg;
1935			g.bg = defaultrcs;
1936		} else {
1937			g.fg = defaultbg;
1938			g.bg = defaultcs;
1939		}
1940		drawcol = dc.col[g.bg];
1941	}
1942
1943	/* draw the new one */
1944	if (IS_SET(MODE_FOCUSED)) {
1945		switch (win.cursor) {
1946		case 7: /* st extension */
1947			g.u = 0x2603; /* snowman (U+2603) */
1948			/* FALLTHROUGH */
1949		case 0: /* Blinking Block */
1950		case 1: /* Blinking Block (Default) */
1951		case 2: /* Steady Block */
1952			xdrawglyph(g, cx, cy);
1953			break;
1954		case 3: /* Blinking Underline */
1955		case 4: /* Steady Underline */
1956			XftDrawRect(xw.draw, &drawcol,
1957					borderpx + cx * win.cw,
1958					borderpx + (cy + 1) * win.ch - \
1959						cursorthickness,
1960					win.cw, cursorthickness);
1961			break;
1962		case 5: /* Blinking bar */
1963		case 6: /* Steady bar */
1964			XftDrawRect(xw.draw, &drawcol,
1965					borderpx + cx * win.cw,
1966					borderpx + cy * win.ch,
1967					cursorthickness, win.ch);
1968			break;
1969		}
1970	} else {
1971		XftDrawRect(xw.draw, &drawcol,
1972				borderpx + cx * win.cw,
1973				borderpx + cy * win.ch,
1974				win.cw - 1, 1);
1975		XftDrawRect(xw.draw, &drawcol,
1976				borderpx + cx * win.cw,
1977				borderpx + cy * win.ch,
1978				1, win.ch - 1);
1979		XftDrawRect(xw.draw, &drawcol,
1980				borderpx + (cx + 1) * win.cw - 1,
1981				borderpx + cy * win.ch,
1982				1, win.ch - 1);
1983		XftDrawRect(xw.draw, &drawcol,
1984				borderpx + cx * win.cw,
1985				borderpx + (cy + 1) * win.ch - 1,
1986				win.cw, 1);
1987	}
1988}
1989
1990void
1991xsetenv(void)
1992{
1993	char buf[sizeof(long) * 8 + 1];
1994
1995	snprintf(buf, sizeof(buf), "%lu", xw.win);
1996	setenv("WINDOWID", buf, 1);
1997}
1998
1999void
2000xseticontitle(char *p)
2001{
2002	XTextProperty prop;
2003	DEFAULT(p, opt_title);
2004
2005	if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
2006	                                &prop) != Success)
2007		return;
2008	XSetWMIconName(xw.dpy, xw.win, &prop);
2009	XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname);
2010	XFree(prop.value);
2011}
2012
2013void
2014xsettitle(char *p)
2015{
2016	XTextProperty prop;
2017	DEFAULT(p, opt_title);
2018
2019	if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
2020	                                &prop) != Success)
2021		return;
2022	XSetWMName(xw.dpy, xw.win, &prop);
2023	XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
2024	XFree(prop.value);
2025}
2026
2027int
2028xstartdraw(void)
2029{
2030	return IS_SET(MODE_VISIBLE);
2031}
2032
2033void
2034xdrawline(Line line, int x1, int y1, int x2)
2035{
2036	int i, x, ox, numspecs, numspecs_cached;
2037	Glyph base, new;
2038	XftGlyphFontSpec *specs;
2039
2040	numspecs_cached = xmakeglyphfontspecs(xw.specbuf, &line[x1], x2 - x1, x1, y1);
2041
2042	/* Draw line in 2 passes: background and foreground. This way wide glyphs
2043       won't get truncated (#223) */
2044	for (int dmode = DRAW_BG; dmode <= DRAW_FG; dmode <<= 1) {
2045		specs = xw.specbuf;
2046		numspecs = numspecs_cached;
2047		i = ox = 0;
2048		for (x = x1; x < x2 && i < numspecs; x++) {
2049			new = line[x];
2050			if (new.mode == ATTR_WDUMMY)
2051				continue;
2052			if (selected(x, y1))
2053				new.mode ^= ATTR_REVERSE;
2054			if (i > 0 && ATTRCMP(base, new)) {
2055				xdrawglyphfontspecs(specs, base, i, ox, y1, dmode);
2056				specs += i;
2057				numspecs -= i;
2058				i = 0;
2059			}
2060			if (i == 0) {
2061				ox = x;
2062				base = new;
2063			}
2064			i++;
2065		}
2066		if (i > 0)
2067			xdrawglyphfontspecs(specs, base, i, ox, y1, dmode);
2068	}
2069}
2070
2071void
2072xfinishdraw(void)
2073{
2074	ImageList *im, *next;
2075	Imlib_Image origin, scaled;
2076	XGCValues gcvalues;
2077	GC gc;
2078	int width, height;
2079	int x, x2, del, destx, desty;
2080	Line line;
2081
2082	for (im = term.images; im; im = next) {
2083		next = im->next;
2084
2085		/* do not draw or process the image, if it is not visible */
2086		if (im->x >= term.col || im->y >= term.row || im->y < 0)
2087			continue;
2088
2089		/* scale the image */
2090		width = MAX(im->width * win.cw / im->cw, 1);
2091		height = MAX(im->height * win.ch / im->ch, 1);
2092		if (!im->pixmap) {
2093			im->pixmap = (void *)XCreatePixmap(xw.dpy, xw.win, width, height,
2094				DefaultDepth(xw.dpy, xw.scr)
2095			);
2096			if (!im->pixmap)
2097				continue;
2098			if (win.cw == im->cw && win.ch == im->ch) {
2099				XImage ximage = {
2100					.format = ZPixmap,
2101					.data = (char *)im->pixels,
2102					.width = im->width,
2103					.height = im->height,
2104					.xoffset = 0,
2105					.byte_order = sixelbyteorder,
2106					.bitmap_bit_order = MSBFirst,
2107					.bits_per_pixel = 32,
2108					.bytes_per_line = im->width * 4,
2109					.bitmap_unit = 32,
2110					.bitmap_pad = 32,
2111					.depth = 24
2112				};
2113				XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, width, height);
2114				if (im->transparent)
2115					im->clipmask = (void *)sixel_create_clipmask((char *)im->pixels, width, height);
2116			} else {
2117				origin = imlib_create_image_using_data(im->width, im->height, (DATA32 *)im->pixels);
2118				if (!origin)
2119					continue;
2120				imlib_context_set_image(origin);
2121				imlib_image_set_has_alpha(1);
2122				imlib_context_set_anti_alias(im->transparent ? 0 : 1); /* anti-aliasing messes up the clip mask */
2123				scaled = imlib_create_cropped_scaled_image(0, 0, im->width, im->height, width, height);
2124				imlib_free_image_and_decache();
2125				if (!scaled)
2126					continue;
2127				imlib_context_set_image(scaled);
2128				imlib_image_set_has_alpha(1);
2129				XImage ximage = {
2130					.format = ZPixmap,
2131					.data = (char *)imlib_image_get_data_for_reading_only(),
2132					.width = width,
2133					.height = height,
2134					.xoffset = 0,
2135					.byte_order = sixelbyteorder,
2136					.bitmap_bit_order = MSBFirst,
2137					.bits_per_pixel = 32,
2138					.bytes_per_line = width * 4,
2139					.bitmap_unit = 32,
2140					.bitmap_pad = 32,
2141					.depth = 24
2142				};
2143				XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, width, height);
2144				if (im->transparent)
2145					im->clipmask = (void *)sixel_create_clipmask((char *)imlib_image_get_data_for_reading_only(), width, height);
2146				imlib_free_image_and_decache();
2147			}
2148		}
2149
2150		/* clip the image so it does not go over to borders */
2151		x2 = MIN(im->x + im->cols, term.col);
2152		width = MIN(width, (x2 - im->x) * win.cw);
2153
2154		/* delete the image if the text cells behind it have been changed */
2155		line = term.line[im->y];
2156		for (del = 0, x = im->x; x < x2; x++) {
2157			if ((del = !(line[x].mode & ATTR_SIXEL)))
2158				break;
2159		}
2160		if (del) {
2161			delete_image(im);
2162			continue;
2163		}
2164
2165		/* draw the image */
2166		memset(&gcvalues, 0, sizeof(gcvalues));
2167		gcvalues.graphics_exposures = False;
2168		gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues);
2169		destx = borderpx + im->x * win.cw;
2170		desty = borderpx + im->y * win.ch;
2171		if (im->clipmask) {
2172			XSetClipMask(xw.dpy, gc, (Drawable)im->clipmask);
2173			XSetClipOrigin(xw.dpy, gc, destx, desty);
2174		}
2175		XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, width, height, destx, desty);
2176		XFreeGC(xw.dpy, gc);
2177	}
2178
2179	XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, win.h, 0, 0);
2180	XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg].pixel);
2181}
2182
2183void
2184xximspot(int x, int y)
2185{
2186	if (xw.ime.xic == NULL)
2187		return;
2188
2189	xw.ime.spot.x = borderpx + x * win.cw;
2190	xw.ime.spot.y = borderpx + (y + 1) * win.ch;
2191
2192	XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL);
2193}
2194
2195void
2196expose(XEvent *ev)
2197{
2198	redraw();
2199}
2200
2201void
2202visibility(XEvent *ev)
2203{
2204	XVisibilityEvent *e = &ev->xvisibility;
2205
2206	MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE);
2207}
2208
2209void
2210unmap(XEvent *ev)
2211{
2212	win.mode &= ~MODE_VISIBLE;
2213}
2214
2215void
2216xsetpointermotion(int set)
2217{
2218	MODBIT(xw.attrs.event_mask, set, PointerMotionMask);
2219	XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
2220}
2221
2222void
2223xsetmode(int set, unsigned int flags)
2224{
2225	int mode = win.mode;
2226	MODBIT(win.mode, set, flags);
2227	if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE))
2228		redraw();
2229}
2230
2231int
2232xsetcursor(int cursor)
2233{
2234	if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
2235		return 1;
2236	win.cursor = cursor;
2237	return 0;
2238}
2239
2240void
2241xseturgency(int add)
2242{
2243	XWMHints *h = XGetWMHints(xw.dpy, xw.win);
2244
2245	MODBIT(h->flags, add, XUrgencyHint);
2246	XSetWMHints(xw.dpy, xw.win, h);
2247	XFree(h);
2248}
2249
2250void
2251xbell(void)
2252{
2253	if (!(IS_SET(MODE_FOCUSED)))
2254		xseturgency(1);
2255	if (bellvolume)
2256		XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL);
2257}
2258
2259void
2260enter(XEvent *ev)
2261{
2262	if ((ev->type == EnterNotify) == entered)
2263		return; /* would probably never happen */
2264
2265	entered = (ev->type == EnterNotify);
2266
2267	if (!focused) { /* do not update if focused */
2268		xloadcols();
2269		tfulldirt();
2270	}
2271}
2272
2273void
2274focus(XEvent *ev)
2275{
2276	XFocusChangeEvent *e = &ev->xfocus;
2277
2278	if (e->mode == NotifyGrab)
2279		return;
2280
2281	if (ev->type == FocusIn) {
2282		if (xw.ime.xic)
2283			XSetICFocus(xw.ime.xic);
2284		win.mode |= MODE_FOCUSED;
2285		xseturgency(0);
2286		if (IS_SET(MODE_FOCUS))
2287			ttywrite("\033[I", 3, 0);
2288		if (!focused) {
2289			focused = 1;
2290			xloadcols();
2291			tfulldirt();
2292		}
2293	} else {
2294		if (xw.ime.xic)
2295			XUnsetICFocus(xw.ime.xic);
2296		win.mode &= ~MODE_FOCUSED;
2297		if (IS_SET(MODE_FOCUS))
2298			ttywrite("\033[O", 3, 0);
2299		if (focused) {
2300			focused = 0;
2301			xloadcols();
2302			tfulldirt();
2303		}
2304	}
2305}
2306
2307int
2308match(uint mask, uint state)
2309{
2310	return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
2311}
2312
2313char*
2314kmap(KeySym k, uint state)
2315{
2316	Key *kp;
2317	int i;
2318
2319	/* Check for mapped keys out of X11 function keys. */
2320	for (i = 0; i < LEN(mappedkeys); i++) {
2321		if (mappedkeys[i] == k)
2322			break;
2323	}
2324	if (i == LEN(mappedkeys)) {
2325		if ((k & 0xFFFF) < 0xFD00)
2326			return NULL;
2327	}
2328
2329	for (kp = key; kp < key + LEN(key); kp++) {
2330		if (kp->k != k)
2331			continue;
2332
2333		if (!match(kp->mask, state))
2334			continue;
2335
2336		if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0)
2337			continue;
2338		if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2)
2339			continue;
2340
2341		if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0)
2342			continue;
2343
2344		return kp->s;
2345	}
2346
2347	return NULL;
2348}
2349
2350void
2351kpress(XEvent *ev)
2352{
2353	XKeyEvent *e = &ev->xkey;
2354	KeySym ksym = NoSymbol;
2355	char buf[64], *customkey;
2356	int len;
2357	Rune c;
2358	Status status;
2359	Shortcut *bp;
2360
2361	if (IS_SET(MODE_KBDLOCK))
2362		return;
2363
2364	if (xw.ime.xic) {
2365		len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status);
2366		if (status == XBufferOverflow)
2367			return;
2368	} else {
2369		len = XLookupString(e, buf, sizeof buf, &ksym, NULL);
2370	}
2371	/* 1. shortcuts */
2372	for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
2373		if (ksym == bp->keysym && match(bp->mod, e->state)) {
2374			bp->func(&(bp->arg));
2375			return;
2376		}
2377	}
2378
2379	/* 2. custom keys from config.h */
2380	if ((customkey = kmap(ksym, e->state))) {
2381		ttywrite(customkey, strlen(customkey), 1);
2382		return;
2383	}
2384
2385	/* 3. composed string from input method */
2386	if (len == 0)
2387		return;
2388	if (len == 1 && e->state & Mod1Mask) {
2389		if (IS_SET(MODE_8BIT)) {
2390			if (*buf < 0177) {
2391				c = *buf | 0x80;
2392				len = utf8encode(c, buf);
2393			}
2394		} else {
2395			buf[1] = buf[0];
2396			buf[0] = '\033';
2397			len = 2;
2398		}
2399	}
2400	ttywrite(buf, len, 1);
2401}
2402
2403void
2404cmessage(XEvent *e)
2405{
2406	/*
2407	 * See xembed specs
2408	 *  http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
2409	 */
2410	if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
2411		if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
2412			win.mode |= MODE_FOCUSED;
2413			xseturgency(0);
2414		} else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
2415			win.mode &= ~MODE_FOCUSED;
2416		}
2417	} else if (e->xclient.data.l[0] == xw.wmdeletewin) {
2418		ttyhangup();
2419		exit(0);
2420	}
2421}
2422
2423void
2424resize(XEvent *e)
2425{
2426	if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)
2427		return;
2428
2429	cresize(e->xconfigure.width, e->xconfigure.height);
2430}
2431
2432void
2433run(void)
2434{
2435	XEvent ev;
2436	int w = win.w, h = win.h;
2437	fd_set rfd;
2438	int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
2439	struct timespec seltv, *tv, now, lastblink, trigger;
2440	double timeout;
2441
2442	/* Waiting for window mapping */
2443	do {
2444		XNextEvent(xw.dpy, &ev);
2445		/*
2446		 * This XFilterEvent call is required because of XOpenIM. It
2447		 * does filter out the key event and some client message for
2448		 * the input method too.
2449		 */
2450		if (XFilterEvent(&ev, None))
2451			continue;
2452		if (ev.type == ConfigureNotify) {
2453			w = ev.xconfigure.width;
2454			h = ev.xconfigure.height;
2455		}
2456	} while (ev.type != MapNotify);
2457
2458	ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
2459	cresize(w, h);
2460
2461	for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
2462		FD_ZERO(&rfd);
2463		FD_SET(ttyfd, &rfd);
2464		FD_SET(xfd, &rfd);
2465
2466		if (XPending(xw.dpy))
2467			timeout = 0;  /* existing events might not set xfd */
2468
2469		seltv.tv_sec = timeout / 1E3;
2470		seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
2471		tv = timeout >= 0 ? &seltv : NULL;
2472
2473		if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
2474			if (errno == EINTR)
2475				continue;
2476			die("select failed: %s\n", strerror(errno));
2477		}
2478		clock_gettime(CLOCK_MONOTONIC, &now);
2479
2480		if (FD_ISSET(ttyfd, &rfd))
2481			ttyread();
2482
2483		xev = 0;
2484		while (XPending(xw.dpy)) {
2485			xev = 1;
2486			XNextEvent(xw.dpy, &ev);
2487			if (XFilterEvent(&ev, None))
2488				continue;
2489			if (handler[ev.type])
2490				(handler[ev.type])(&ev);
2491		}
2492
2493		/*
2494		 * To reduce flicker and tearing, when new content or event
2495		 * triggers drawing, we first wait a bit to ensure we got
2496		 * everything, and if nothing new arrives - we draw.
2497		 * We start with trying to wait minlatency ms. If more content
2498		 * arrives sooner, we retry with shorter and shorter periods,
2499		 * and eventually draw even without idle after maxlatency ms.
2500		 * Typically this results in low latency while interacting,
2501		 * maximum latency intervals during `cat huge.txt`, and perfect
2502		 * sync with periodic updates from animations/key-repeats/etc.
2503		 */
2504		if (FD_ISSET(ttyfd, &rfd) || xev) {
2505			if (!drawing) {
2506				trigger = now;
2507				drawing = 1;
2508			}
2509			timeout = (maxlatency - TIMEDIFF(now, trigger)) \
2510			          / maxlatency * minlatency;
2511			if (timeout > 0)
2512				continue;  /* we have time, try to find idle */
2513		}
2514
2515		/* idle detected or maxlatency exhausted -> draw */
2516		timeout = -1;
2517		if (blinktimeout && tattrset(ATTR_BLINK)) {
2518			timeout = blinktimeout - TIMEDIFF(now, lastblink);
2519			if (timeout <= 0) {
2520				if (-timeout > blinktimeout) /* start visible */
2521					win.mode |= MODE_BLINK;
2522				win.mode ^= MODE_BLINK;
2523				tsetdirtattr(ATTR_BLINK);
2524				lastblink = now;
2525				timeout = blinktimeout;
2526			}
2527		}
2528
2529		draw();
2530		XFlush(xw.dpy);
2531		drawing = 0;
2532	}
2533}
2534
2535int
2536resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
2537{
2538	char **sdst = dst;
2539	int *idst = dst;
2540	float *fdst = dst;
2541
2542	char fullname[256];
2543	char fullclass[256];
2544	char *type;
2545	XrmValue ret;
2546
2547	snprintf(fullname, sizeof(fullname), "%s.%s",
2548			opt_name ? opt_name : "st", name);
2549	snprintf(fullclass, sizeof(fullclass), "%s.%s",
2550			opt_class ? opt_class : "St", name);
2551	fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0';
2552
2553	XrmGetResource(db, fullname, fullclass, &type, &ret);
2554	if (ret.addr == NULL || strncmp("String", type, 64))
2555		return 1;
2556
2557	switch (rtype) {
2558	case STRING:
2559		*sdst = ret.addr;
2560		break;
2561	case INTEGER:
2562		*idst = strtoul(ret.addr, NULL, 10);
2563		break;
2564	case FLOAT:
2565		*fdst = strtof(ret.addr, NULL);
2566		break;
2567	}
2568	return 0;
2569}
2570
2571void
2572config_init(void)
2573{
2574	char *resm;
2575	XrmDatabase db;
2576	ResourcePref *p;
2577
2578	XrmInitialize();
2579	resm = XResourceManagerString(xw.dpy);
2580	if (!resm)
2581		return;
2582
2583	db = XrmGetStringDatabase(resm);
2584	for (p = resources; p < resources + LEN(resources); p++)
2585		resource_load(db, p->name, p->type, p->dst);
2586}
2587
2588void
2589usage(void)
2590{
2591	die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
2592	    " [-n name] [-o file]\n"
2593	    "          [-T title] [-t title] [-w windowid]"
2594	    " [[-e] command [args ...]]\n"
2595	    "       %s [-aiv] [-c class] [-f font] [-g geometry]"
2596	    " [-n name] [-o file]\n"
2597	    "          [-T title] [-t title] [-w windowid] -l line"
2598	    " [stty_args ...]\n", argv0, argv0);
2599}
2600
2601int
2602main(int argc, char *argv[])
2603{
2604	xw.l = xw.t = 0;
2605	xw.isfixed = False;
2606	xsetcursor(cursorshape);
2607
2608	ARGBEGIN {
2609	case 'a':
2610		allowaltscreen = 0;
2611		break;
2612	case 'c':
2613		opt_class = EARGF(usage());
2614		break;
2615	case 'e':
2616		if (argc > 0)
2617			--argc, ++argv;
2618		goto run;
2619	case 'f':
2620		opt_font = EARGF(usage());
2621		break;
2622	case 'g':
2623		xw.gm = XParseGeometry(EARGF(usage()),
2624				&xw.l, &xw.t, &cols, &rows);
2625		break;
2626	case 'i':
2627		xw.isfixed = 1;
2628		break;
2629	case 'o':
2630		opt_io = EARGF(usage());
2631		break;
2632	case 'l':
2633		opt_line = EARGF(usage());
2634		break;
2635	case 'n':
2636		opt_name = EARGF(usage());
2637		break;
2638	case 't':
2639	case 'T':
2640		opt_title = EARGF(usage());
2641		break;
2642	case 'w':
2643		opt_embed = EARGF(usage());
2644		break;
2645	case 'v':
2646		die("%s " VERSION "\n", argv0);
2647		break;
2648	default:
2649		usage();
2650	} ARGEND;
2651
2652run:
2653	if (argc > 0) /* eat all remaining arguments */
2654		opt_cmd = argv;
2655
2656	if (!opt_title)
2657		opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0];
2658
2659	setlocale(LC_CTYPE, "");
2660	XSetLocaleModifiers("");
2661
2662	if(!(xw.dpy = XOpenDisplay(NULL)))
2663		die("Can't open display\n");
2664
2665	config_init();
2666	cols = MAX(cols, 1);
2667	rows = MAX(rows, 1);
2668	tnew(cols, rows);
2669	xinit(cols, rows);
2670	xsetenv();
2671	selinit();
2672	run();
2673
2674	return 0;
2675}