nixos-dotfiles

nixos-dotfiles

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