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