Xinqi Bao's Git

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