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