mc now colored correctly
[k8sterm.git] / src / sterm.c
blob9f1c9dfe71c30431f859dd7e74db2b4d07502ae5
1 /* See LICENSE for licence details. */
2 #ifdef _XOPEN_SOURCE
3 # undef _XOPEN_SOURCE
4 #endif
5 #define _XOPEN_SOURCE 600
7 #include <alloca.h>
8 #include <ctype.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <limits.h>
12 #include <locale.h>
13 #include <pty.h>
14 #include <stdarg.h>
15 #include <stdbool.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <signal.h>
20 #include <sys/ioctl.h>
21 #include <sys/select.h>
22 #include <sys/stat.h>
23 #include <sys/time.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <time.h>
27 #include <unistd.h>
28 #include <X11/Xatom.h>
29 #include <X11/Xlib.h>
30 #include <X11/Xutil.h>
31 #include <X11/cursorfont.h>
32 #include <X11/keysym.h>
34 #include "dbglog.h"
37 #define MOUSE_REPORTING_ALWAYS
38 #define DUMP_PROG_OUTPUT
39 #define DUMP_PROG_INPUT
42 ////////////////////////////////////////////////////////////////////////////////
43 #define USAGE \
44 "sterm " VERSION " (c) 2010-2012 st engineers\n" \
45 "usage: sterm [-t title] [-c class] [-w windowid] [-T termname] [-v] [-e command...]\n"
48 ////////////////////////////////////////////////////////////////////////////////
49 /* XEMBED messages */
50 #define XEMBED_FOCUS_IN (4)
51 #define XEMBED_FOCUS_OUT (5)
53 /* Arbitrary sizes */
54 #define ESC_TITLE_SIZ (256)
55 #define ESC_BUF_SIZ (256)
56 #define ESC_ARG_SIZ (16)
57 #define DRAW_BUF_SIZ (2048)
58 #define UTF_SIZ (4)
59 #define XK_NO_MOD UINT_MAX
60 #define XK_ANY_MOD (0)
62 #define SELECT_TIMEOUT (20*1000) /* 20 ms */
63 #define DRAW_TIMEOUT 30 /* 30 ms */
65 #define SERRNO strerror(errno)
66 #define MIN(a, b) ((a) < (b) ? (a) : (b))
67 #define MAX(a, b) ((a) < (b) ? (b) : (a))
68 #define LEN(a) (sizeof(a)/sizeof(a[0]))
69 #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
70 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
71 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
72 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
73 #define IS_SET(flag) (term.mode & (flag))
74 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000+(t1.tv_usec-t2.tv_usec)/1000)
75 #define X2COL(x) (((x)-BORDER)/xw.cw)
76 #define Y2ROW(y) (((y)-BORDER)/xw.ch)
79 ////////////////////////////////////////////////////////////////////////////////
80 enum glyph_attribute {
81 ATTR_NULL = 0x00,
82 ATTR_REVERSE = 0x01,
83 ATTR_UNDERLINE = 0x02,
84 ATTR_BOLD = 0x04,
85 ATTR_GFX = 0x08,
88 enum cursor_movement {
89 CURSOR_UP,
90 CURSOR_DOWN,
91 CURSOR_LEFT,
92 CURSOR_RIGHT,
93 CURSOR_SAVE,
94 CURSOR_LOAD
97 enum cursor_state {
98 CURSOR_DEFAULT = 0,
99 CURSOR_HIDE = 1,
100 CURSOR_WRAPNEXT = 2
103 enum glyph_state {
104 GLYPH_SET = 0x01, /* for selection only */
105 GLYPH_DIRTY = 0x02,
109 enum term_mode {
110 MODE_WRAP = 0x01,
111 MODE_INSERT = 0x02,
112 MODE_APPKEYPAD = 0x04,
113 MODE_ALTSCREEN = 0x08,
114 MODE_CRLF = 0x10,
115 MODE_MOUSEBTN = 0x20,
116 MODE_MOUSEMOTION = 0x40,
117 MODE_MOUSE = 0x20|0x40,
118 MODE_REVERSE = 0x80,
121 enum escape_state {
122 ESC_START = 0x01,
123 ESC_CSI = 0x02,
124 ESC_OSC = 0x04,
125 ESC_TITLE = 0x08,
126 ESC_ALTCHARSET = 0x10,
127 ESC_HASH = 0x20,
128 ESC_PERCENT = 0x40,
131 enum window_state {
132 WIN_VISIBLE = 0x01,
133 WIN_REDRAW = 0x02,
134 WIN_FOCUSED = 0x04,
137 /* bit macro */
138 #undef B0
139 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 };
142 ////////////////////////////////////////////////////////////////////////////////
143 typedef unsigned char uchar;
144 typedef unsigned int uint;
145 typedef unsigned long ulong;
146 typedef unsigned short ushort;
149 typedef struct {
150 char c[UTF_SIZ]; /* character code */
151 uchar mode; /* attribute flags */
152 ushort fg; /* foreground */
153 ushort bg; /* background */
154 uchar state; /* state flags */
155 } Glyph;
158 typedef Glyph *Line;
160 typedef struct {
161 Glyph attr; /* current char attributes */
162 int x;
163 int y;
164 char state;
165 } TCursor;
168 /* CSI Escape sequence structs */
169 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
170 typedef struct {
171 char buf[ESC_BUF_SIZ]; /* raw string */
172 int len; /* raw string length */
173 char priv;
174 int arg[ESC_ARG_SIZ];
175 int narg; /* nb of args */
176 char mode;
177 } CSIEscape;
180 /* Internal representation of the screen */
181 typedef struct {
182 int row; /* nb row */
183 int col; /* nb col */
184 Line *line; /* screen */
185 Line *alt; /* alternate screen */
186 bool *dirty; /* dirtyness of lines */
187 TCursor c; /* cursor */
188 int top; /* top scroll limit */
189 int bot; /* bottom scroll limit */
190 int mode; /* terminal mode flags */
191 int esc; /* escape state flags */
192 char title[ESC_TITLE_SIZ];
193 int titlelen;
194 } Term;
197 /* Purely graphic info */
198 typedef struct {
199 Display *dpy;
200 Colormap cmap;
201 Window win;
202 Pixmap buf;
203 Atom xembed;
204 XIM xim;
205 XIC xic;
206 int scr;
207 int w; /* window width */
208 int h; /* window height */
209 int bufw; /* pixmap width */
210 int bufh; /* pixmap height */
211 int ch; /* char height */
212 int cw; /* char width */
213 char state; /* focus, redraw, visible */
214 struct timeval lastdraw;
215 } XWindow;
218 typedef struct {
219 KeySym k;
220 uint mask;
221 char s[ESC_BUF_SIZ];
222 } Key;
225 /* TODO: use better name for vars... */
226 typedef struct {
227 int mode;
228 int bx, by;
229 int ex, ey;
230 struct { int x, y; } b, e;
231 char *clip;
232 Atom xtarget;
233 struct timeval tclick1;
234 struct timeval tclick2;
235 } Selection;
238 #include "config.h"
241 /* Drawing Context */
242 typedef struct {
243 ulong col[LEN(colorname) < 256 ? 256 : LEN(colorname)];
244 GC gc;
245 struct {
246 int ascent;
247 int descent;
248 short lbearing;
249 short rbearing;
250 XFontSet set;
251 } font, bfont;
252 } DC;
255 ////////////////////////////////////////////////////////////////////////////////
256 static void die (const char*, ...);
257 static void draw (void);
258 static void drawregion (int, int, int, int);
259 static void execsh (void);
260 static void sigchld (int);
261 static void run (void);
262 static bool last_draw_too_old (void);
264 static void csidump (void);
265 static void csihandle (void);
266 static void csiparse (void);
267 static void csireset (void);
269 static void tclearregion (int, int, int, int);
270 static void tcursor (int);
271 static void tdeletechar (int);
272 static void tdeleteline (int);
273 static void tinsertblank (int);
274 static void tinsertblankline (int);
275 static void tmoveto (int, int);
276 static void tnew (int, int);
277 static void tnewline (int);
278 static void tputtab (void);
279 static void tputc (const char *);
280 static void treset (void);
281 static int tresize (int, int);
282 static void tscrollup (int, int);
283 static void tscrolldown (int, int);
284 static void tsetattr (int*, int);
285 static void tsetchar (const char *);
286 static void tsetscroll (int, int);
287 static void tswapscreen (void);
288 static void tsetdirt (int, int);
289 static void tfulldirt (void);
291 static void ttynew (void);
292 static void ttyread (void);
293 static void ttyresize (int, int);
294 static void ttywrite (const char *, size_t);
296 static void xdraws (char *, const Glyph *, int, int, int, int);
297 static void xhints (void);
298 static void xclear (int, int, int, int);
299 static void xcopy (int, int, int, int);
300 static void xdrawcursor (void);
301 static void xinit (void);
302 static void xloadcols (void);
303 static void xseturgency (int);
304 static void xsetsel (char *);
305 static void xresize (int, int);
307 static void expose (XEvent *);
308 static void visibility (XEvent *);
309 static void unmap (XEvent *);
310 static const char *kmap (KeySym, uint);
311 static void kpress (XEvent *);
312 static void cmessage (XEvent *);
313 static void resize (XEvent *);
314 static void focus (XEvent *);
315 static void brelease (XEvent *);
316 static void bpress (XEvent *);
317 static void bmotion (XEvent *);
318 static void selnotify (XEvent *);
319 static void selrequest (XEvent *);
321 static void selinit (void);
322 static inline bool selected (int, int);
323 static void selcopy (void);
324 static void selpaste ();
325 static void selscroll (int, int);
327 static int utf8decode (const char *, long *);
328 static int utf8encode (const long *, char *);
329 static int utf8size (const char *);
330 static int isfullutf8 (const char *, int);
333 ////////////////////////////////////////////////////////////////////////////////
334 static void (*handler[LASTEvent])(XEvent *) = {
335 [KeyPress] = kpress,
336 [ClientMessage] = cmessage,
337 [ConfigureNotify] = resize,
338 [VisibilityNotify] = visibility,
339 [UnmapNotify] = unmap,
340 [Expose] = expose,
341 [FocusIn] = focus,
342 [FocusOut] = focus,
343 [MotionNotify] = bmotion,
344 [ButtonPress] = bpress,
345 [ButtonRelease] = brelease,
346 [SelectionNotify] = selnotify,
347 [SelectionRequest] = selrequest,
351 ////////////////////////////////////////////////////////////////////////////////
352 /* Globals */
353 static DC dc;
354 static XWindow xw;
355 static Term term;
356 static CSIEscape escseq;
357 static int cmdfd;
358 static pid_t pid;
359 static Selection sel;
360 static char **opt_cmd = NULL;
361 static char *opt_title = NULL;
362 static char *opt_embed = NULL;
363 static char *opt_class = NULL;
364 static char *opt_term = NULL;
367 static int tty_last_write_ms = 0;
370 ////////////////////////////////////////////////////////////////////////////////
371 // UTF-8
372 static int utf8decode (const char *s, long *u) {
373 uchar c;
374 int i, n, rtn;
376 rtn = 1;
377 c = *s;
378 if (~c & B7) { /* 0xxxxxxx */
379 *u = c;
380 return rtn;
381 } else if ((c & (B7|B6|B5)) == (B7|B6)) { /* 110xxxxx */
382 *u = c&(B4|B3|B2|B1|B0);
383 n = 1;
384 } else if ((c & (B7|B6|B5|B4)) == (B7|B6|B5)) { /* 1110xxxx */
385 *u = c&(B3|B2|B1|B0);
386 n = 2;
387 } else if ((c & (B7|B6|B5|B4|B3)) == (B7|B6|B5|B4)) { /* 11110xxx */
388 *u = c & (B2|B1|B0);
389 n = 3;
390 } else {
391 goto invalid;
393 for (i = n, ++s; i > 0; --i, ++rtn, ++s) {
394 c = *s;
395 if ((c & (B7|B6)) != B7) goto invalid; /* 10xxxxxx */
396 *u <<= 6;
397 *u |= c & (B5|B4|B3|B2|B1|B0);
399 if ((n == 1 && *u < 0x80) ||
400 (n == 2 && *u < 0x800) ||
401 (n == 3 && *u < 0x10000) ||
402 (*u >= 0xD800 && *u <= 0xDFFF)) {
403 goto invalid;
405 return rtn;
406 invalid:
407 *u = 0xFFFD;
408 return rtn;
412 static int utf8encode (const long *u, char *s) {
413 uchar *sp;
414 ulong uc;
415 int i, n;
417 sp = (uchar *)s;
418 uc = *u;
419 if (uc < 0x80) {
420 *sp = uc; /* 0xxxxxxx */
421 return 1;
422 } else if (*u < 0x800) {
423 *sp = (uc >> 6) | (B7|B6); /* 110xxxxx */
424 n = 1;
425 } else if (uc < 0x10000) {
426 *sp = (uc >> 12) | (B7|B6|B5); /* 1110xxxx */
427 n = 2;
428 } else if (uc <= 0x10FFFF) {
429 *sp = (uc >> 18) | (B7|B6|B5|B4); /* 11110xxx */
430 n = 3;
431 } else {
432 goto invalid;
434 for (i = n, ++sp; i > 0; --i, ++sp) *sp = ((uc >> 6*(i-1)) & (B5|B4|B3|B2|B1|B0)) | B7; /* 10xxxxxx */
435 return n+1;
436 invalid:
437 /* U+FFFD */
438 *s++ = '\xEF';
439 *s++ = '\xBF';
440 *s = '\xBD';
441 return 3;
445 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode
446 UTF-8 otherwise return 0 */
447 static int isfullutf8 (const char *s, int b) {
448 uchar *c1, *c2, *c3;
450 c1 = (uchar *) s;
451 c2 = (uchar *) ++s;
452 c3 = (uchar *) ++s;
453 if (b < 1) return 0;
454 if ((*c1&(B7|B6|B5)) == (B7|B6) && b == 1) return 0;
455 if ((*c1&(B7|B6|B5|B4)) == (B7|B6|B5) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7))) return 0;
456 if ((*c1&(B7|B6|B5|B4|B3)) == (B7|B6|B5|B4) && (b == 1 || (b == 2 && (*c2&(B7|B6)) == B7) || (b == 3 && (*c2&(B7|B6)) == B7 && (*c3&(B7|B6)) == B7))) return 0;
457 return 1;
461 static int utf8size (const char *s) {
462 uchar c = *s;
464 if (~c&B7) return 1;
465 if ((c&(B7|B6|B5)) == (B7|B6)) return 2;
466 if ((c&(B7|B6|B5|B4)) == (B7|B6|B5)) return 3;
467 return 4;
471 ////////////////////////////////////////////////////////////////////////////////
472 // utilities
473 static char *SPrintfVA (const char *fmt, va_list vaorig) {
474 char *buf = NULL;
475 int olen, len = 128;
477 buf = malloc(len);
478 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
479 for (;;) {
480 char *nb;
481 va_list va;
483 va_copy(va, vaorig);
484 olen = vsnprintf(buf, len, fmt, va);
485 va_end(va);
486 if (olen >= 0 && olen < len) return buf;
487 if (olen < 0) olen = len*2-1;
488 nb = realloc(buf, olen+1);
489 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
490 buf = nb;
491 len = olen+1;
496 static __attribute__((format(printf,1,2))) char *SPrintf (const char *fmt, ...) {
497 char *buf = NULL;
498 va_list va;
500 va_start(va, fmt);
501 buf = SPrintfVA(fmt, va);
502 va_end(va);
503 return buf;
507 static __attribute__((noreturn)) __attribute__((format(printf,1,2))) void die (const char *errstr, ...) {
508 va_list ap;
510 va_start(ap, errstr);
511 vfprintf(stderr, errstr, ap);
512 va_end(ap);
513 exit(EXIT_FAILURE);
517 static __attribute__((noreturn)) void execsh (void) {
518 char **args;
519 char *envshell = getenv("SHELL");
520 char *term = SPrintf("TERM=%s", opt_term ? opt_term : TNAME);
522 DEFAULT(envshell, SHELL);
523 putenv(term);
524 free(term);
525 args = opt_cmd ? opt_cmd : (char*[]){envshell, "-i", NULL};
526 execvp(args[0], args);
527 exit(EXIT_FAILURE);
531 static __attribute__((noreturn)) void sigchld (int a) {
532 int stat = 0;
533 if (waitpid(pid, &stat, 0) < 0) die("Waiting for pid %hd failed: %s\n", pid, SERRNO);
534 if (WIFEXITED(stat)) exit(WEXITSTATUS(stat));
535 exit(EXIT_FAILURE);
539 ////////////////////////////////////////////////////////////////////////////////
540 // getticks
541 static struct timespec mclk_sttime; // starting time of monotonic clock
544 static void mclock_init (void) {
545 clock_gettime(CLOCK_MONOTONIC_RAW, &mclk_sttime);
549 static int mclock_ticks (void) {
550 struct timespec tp;
552 clock_gettime(CLOCK_MONOTONIC_RAW, &tp);
553 tp.tv_sec -= mclk_sttime.tv_sec;
554 return tp.tv_sec*1000+(tp.tv_nsec/1000000);
558 ////////////////////////////////////////////////////////////////////////////////
559 // selection
560 static void selinit (void) {
561 memset(&sel.tclick1, 0, sizeof(sel.tclick1));
562 memset(&sel.tclick2, 0, sizeof(sel.tclick2));
563 sel.mode = 0;
564 sel.bx = -1;
565 sel.clip = NULL;
566 sel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
567 if (sel.xtarget == None) sel.xtarget = XA_STRING;
571 static inline bool selected (int x, int y) {
572 if (sel.ey == y && sel.by == y) {
573 int bx = MIN(sel.bx, sel.ex);
574 int ex = MAX(sel.bx, sel.ex);
575 return BETWEEN(x, bx, ex);
577 return
578 ((sel.b.y < y && y < sel.e.y) || (y == sel.e.y && x <= sel.e.x)) ||
579 (y == sel.b.y && x >= sel.b.x && (x <= sel.e.x || sel.b.y != sel.e.y));
583 static void getbuttoninfo (XEvent *e, int *b, int *x, int *y) {
584 if (b) *b = e->xbutton.button;
585 *x = X2COL(e->xbutton.x);
586 *y = Y2ROW(e->xbutton.y);
587 sel.b.x = sel.by < sel.ey ? sel.bx : sel.ex;
588 sel.b.y = MIN(sel.by, sel.ey);
589 sel.e.x = sel.by < sel.ey ? sel.ex : sel.bx;
590 sel.e.y = MAX(sel.by, sel.ey);
594 static void mousereport (XEvent *e) {
595 int x = X2COL(e->xbutton.x);
596 int y = Y2ROW(e->xbutton.y);
597 int button = e->xbutton.button;
598 int state = e->xbutton.state;
599 char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 };
600 static int ob, ox, oy;
602 /* from urxvt */
603 if (e->xbutton.type == MotionNotify) {
604 if (!IS_SET(MODE_MOUSEMOTION) || (x == ox && y == oy)) return;
605 button = ob + 32;
606 ox = x, oy = y;
607 } else if (e->xbutton.type == ButtonRelease || button == AnyButton) {
608 button = 3;
609 } else {
610 button -= Button1;
611 if (button >= 3) button += 64-3;
612 if (e->xbutton.type == ButtonPress) {
613 ob = button;
614 ox = x, oy = y;
617 buf[3] = 32+button+(state & ShiftMask ? 4 : 0)+(state & Mod4Mask ? 8 : 0)+(state & ControlMask ? 16 : 0);
618 ttywrite(buf, sizeof(buf));
622 static void bpress (XEvent *e) {
623 if (IS_SET(MODE_MOUSE)) mousereport(e);
624 else if (e->xbutton.button == Button1) {
625 if (sel.bx != -1) tsetdirt(sel.b.y, sel.e.y);
626 sel.mode = 1;
627 sel.ex = sel.bx = X2COL(e->xbutton.x);
628 sel.ey = sel.by = Y2ROW(e->xbutton.y);
633 static void selcopy (void) {
634 char *str, *ptr;
635 int x, y, bufsize, is_selected = 0;
637 if (sel.bx == -1) {
638 str = NULL;
639 } else {
640 bufsize = (term.col+1) * (sel.e.y-sel.b.y+1) * UTF_SIZ;
641 ptr = str = malloc(bufsize);
642 /* append every set & selected glyph to the selection */
643 for (y = 0; y < term.row; ++y) {
644 for (x = 0; x < term.col; ++x) {
645 is_selected = selected(x, y);
646 if ((term.line[y][x].state & GLYPH_SET) && is_selected) {
647 int size = utf8size(term.line[y][x].c);
648 memcpy(ptr, term.line[y][x].c, size);
649 ptr += size;
652 /* \n at the end of every selected line except for the last one */
653 if (is_selected && y < sel.e.y) *ptr++ = '\n';
655 *ptr = 0;
657 xsetsel(str);
661 static void selnotify (XEvent *e) {
662 ulong nitems, ofs, rem;
663 int format;
664 uchar *data;
665 Atom type;
667 ofs = 0;
668 do {
669 if (XGetWindowProperty(xw.dpy, xw.win, XA_PRIMARY, ofs, BUFSIZ/4, False, AnyPropertyType, &type, &format, &nitems, &rem, &data)) {
670 fprintf(stderr, "Clipboard allocation failed\n");
671 return;
673 ttywrite((const char *)data, nitems*format/8);
674 XFree(data);
675 /* number of 32-bit chunks returned */
676 ofs += nitems*format/32;
677 } while (rem > 0);
681 static void selpaste (void) {
682 XConvertSelection(xw.dpy, XA_PRIMARY, sel.xtarget, XA_PRIMARY, xw.win, CurrentTime);
686 void selrequest (XEvent *e) {
687 XSelectionRequestEvent *xsre;
688 XSelectionEvent xev;
689 Atom xa_targets;
691 xsre = (XSelectionRequestEvent *)e;
692 xev.type = SelectionNotify;
693 xev.requestor = xsre->requestor;
694 xev.selection = xsre->selection;
695 xev.target = xsre->target;
696 xev.time = xsre->time;
697 /* reject */
698 xev.property = None;
699 xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
700 if (xsre->target == xa_targets) {
701 /* respond with the supported type */
702 Atom string = sel.xtarget;
703 XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, (uchar *)&string, 1);
704 xev.property = xsre->property;
705 } else if (xsre->target == sel.xtarget && sel.clip != NULL) {
706 XChangeProperty(xsre->display, xsre->requestor, xsre->property, xsre->target, 8, PropModeReplace, (uchar *)sel.clip, strlen(sel.clip));
707 xev.property = xsre->property;
709 /* all done, send a notification to the listener */
710 if (!XSendEvent(xsre->display, xsre->requestor, True, 0, (XEvent *) &xev)) fprintf(stderr, "Error sending SelectionNotify event\n");
714 static void xsetsel (char *str) {
715 /* register the selection for both the clipboard and the primary */
716 Atom clipboard;
718 free(sel.clip);
719 sel.clip = str;
720 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, CurrentTime);
721 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
722 XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
723 XFlush(xw.dpy);
727 static void brelease (XEvent *e) {
728 if (IS_SET(MODE_MOUSE)) {
729 mousereport(e);
730 return;
732 if (e->xbutton.button == Button2) {
733 selpaste();
734 } else if (e->xbutton.button == Button1) {
735 sel.mode = 0;
736 getbuttoninfo(e, NULL, &sel.ex, &sel.ey);
737 term.dirty[sel.ey] = 1;
738 if (sel.bx == sel.ex && sel.by == sel.ey) {
739 struct timeval now;
740 sel.bx = -1;
741 gettimeofday(&now, NULL);
742 if (TIMEDIFF(now, sel.tclick2) <= TRIPLECLICK_TIMEOUT) {
743 /* triple click on the line */
744 sel.b.x = sel.bx = 0;
745 sel.e.x = sel.ex = term.col;
746 sel.b.y = sel.e.y = sel.ey;
747 selcopy();
748 } else if (TIMEDIFF(now, sel.tclick1) <= DOUBLECLICK_TIMEOUT) {
749 /* double click to select word */
750 sel.bx = sel.ex;
751 while (sel.bx > 0 && term.line[sel.ey][sel.bx-1].state & GLYPH_SET && term.line[sel.ey][sel.bx-1].c[0] != ' ') --sel.bx;
752 sel.b.x = sel.bx;
753 while (sel.ex < term.col-1 && term.line[sel.ey][sel.ex+1].state & GLYPH_SET && term.line[sel.ey][sel.ex+1].c[0] != ' ') ++sel.ex;
754 sel.e.x = sel.ex;
755 sel.b.y = sel.e.y = sel.ey;
756 selcopy();
758 } else {
759 selcopy();
762 memcpy(&sel.tclick2, &sel.tclick1, sizeof(struct timeval));
763 gettimeofday(&sel.tclick1, NULL);
764 draw();
768 static void bmotion (XEvent *e) {
769 if (IS_SET(MODE_MOUSE)) {
770 mousereport(e);
771 return;
773 if (sel.mode) {
774 int oldey = sel.ey, oldex = sel.ex;
776 getbuttoninfo(e, NULL, &sel.ex, &sel.ey);
777 if (oldey != sel.ey || oldex != sel.ex) {
778 int starty = MIN(oldey, sel.ey);
779 int endy = MAX(oldey, sel.ey);
781 tsetdirt(starty, endy);
782 draw();
788 ////////////////////////////////////////////////////////////////////////////////
789 // tty init
791 static void dump (char c) {
792 static int col;
794 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
795 if (++col % 10 == 0) fprintf(stderr, "\n");
800 static void ttynew (void) {
801 int m, s;
802 struct winsize w = {term.row, term.col, 0, 0};
804 if (openpty(&m, &s, NULL, NULL, &w) < 0) die("openpty failed: %s\n", SERRNO);
805 switch (pid = fork()) {
806 case -1:
807 die("fork failed\n");
808 break;
809 case 0:
810 setsid(); /* create a new process group */
811 dup2(s, STDIN_FILENO);
812 dup2(s, STDOUT_FILENO);
813 dup2(s, STDERR_FILENO);
814 if (ioctl(s, TIOCSCTTY, NULL) < 0) die("ioctl TIOCSCTTY failed: %s\n", SERRNO);
815 close(s);
816 close(m);
817 execsh();
818 break;
819 default:
820 close(s);
821 cmdfd = m;
822 signal(SIGCHLD, sigchld);
827 ////////////////////////////////////////////////////////////////////////////////
828 // tty r/w
829 static int ttycanread (void) {
830 for (;;) {
831 fd_set rfd;
832 struct timeval timeout = {0};
834 FD_ZERO(&rfd);
835 FD_SET(cmdfd, &rfd);
836 if (select(cmdfd+1, &rfd, NULL, NULL, &timeout) < 0) {
837 if (errno == EINTR) continue;
838 die("select failed: %s\n", SERRNO);
840 if (FD_ISSET(cmdfd, &rfd)) return 1;
841 break;
843 return 0;
847 static int ttycanwrite (void) {
848 for (;;) {
849 fd_set wfd;
850 struct timeval timeout = {0};
852 FD_ZERO(&wfd);
853 FD_SET(cmdfd, &wfd);
854 if (select(cmdfd+1, NULL, &wfd, NULL, &timeout) < 0) {
855 if (errno == EINTR) continue;
856 die("select failed: %s\n", SERRNO);
858 if (FD_ISSET(cmdfd, &wfd)) return 1;
859 break;
861 return 0;
865 static void ttyread (void) {
866 static char buf[BUFSIZ];
867 static int buflen = 0, obuflen = 0;
869 char *ptr;
870 int left;
872 /* append read bytes to unprocessed bytes */
873 obuflen = buflen;
874 left = LEN(buf)-buflen;
875 //dlogf("0: ttyread before: free=%d, used=%d", left, buflen);
876 while (left > 0 && ttycanread()) {
877 int ret;
879 //if ((ret = read(cmdfd, buf+buflen, 1)) < 0) die("Couldn't read from shell: %s\n", SERRNO);
880 if ((ret = read(cmdfd, buf+buflen, left)) < 0) die("Couldn't read from shell: %s\n", SERRNO);
881 buflen += ret;
882 left -= ret;
884 //dlogf("1: ttyread after: free=%d, used=%d", left, buflen);
885 /* process every complete utf8 char */
886 #ifdef DUMP_PROG_OUTPUT
888 FILE *fo = fopen("zlogo.log", "ab");
889 if (fo) {
890 fwrite(buf+obuflen, buflen-obuflen, 1, fo);
891 fclose(fo);
894 #endif
895 ptr = buf;
896 while (buflen >= UTF_SIZ || isfullutf8(ptr, buflen)) {
897 long utf8c;
898 char s[UTF_SIZ];
899 int charsize = utf8decode(ptr, &utf8c); /* returns size of utf8 char in bytes */
901 utf8encode(&utf8c, s);
902 tputc(s);
903 ptr += charsize;
904 buflen -= charsize;
906 //dlogf("2: ttyread afterproc: used=%d", buflen);
907 /* keep any uncomplete utf8 char for the next call */
908 if (buflen > 0) memmove(buf, ptr, buflen);
912 #define WRBUF_SIZE (1024)
913 static char tty_wrbuf[WRBUF_SIZE];
914 static int tty_wrbufsize = WRBUF_SIZE, tty_wrbufused = 0, tty_wrbufpos = 0;
917 static void ttyflushwrbuf (void) {
918 if (tty_wrbufpos >= tty_wrbufused) {
919 tty_wrbufpos = tty_wrbufused = 0;
920 return;
922 //dlogf("0: ttyflushwrbuf before: towrite=%d", tty_wrbufused-tty_wrbufpos);
923 while (tty_wrbufpos < tty_wrbufused && ttycanwrite()) {
924 int ret;
926 if ((ret = write(cmdfd, tty_wrbuf+tty_wrbufpos, tty_wrbufused-tty_wrbufpos)) == -1) die("write error on tty: %s\n", SERRNO);
927 tty_wrbufpos += ret;
929 if (tty_wrbufpos > 0) {
930 int left = tty_wrbufused-tty_wrbufpos;
932 if (left < 1) {
933 // write buffer is empty
934 tty_wrbufpos = tty_wrbufused = 0;
935 } else {
936 memmove(tty_wrbuf, tty_wrbuf+tty_wrbufpos, left);
937 tty_wrbufpos = 0;
938 tty_wrbufused = left;
941 //dlogf("1: ttyflushwrbuf after: towrite=%d", tty_wrbufused-tty_wrbufpos);
945 static void ttywrite (const char *s, size_t n) {
946 #ifdef DUMP_PROG_INPUT
947 if (s != NULL && n > 0) {
948 FILE *fo = fopen("zlogw.log", "ab");
949 if (fo) {
950 fwrite(s, n, 1, fo);
951 fclose(fo);
954 #endif
955 ttyflushwrbuf();
956 if (s != NULL && n > 0) {
957 while (n > 0) {
958 while (tty_wrbufused >= tty_wrbufsize) {
959 //FIXME: make write buffer dynamic?
960 // force write at least one char
961 //dlogf("ttywrite: forced write");
962 if (write(cmdfd, tty_wrbuf+tty_wrbufpos, 1) == -1) die("write error on tty: %s\n", SERRNO);
963 ++tty_wrbufpos;
964 ttyflushwrbuf(); // make room for char
966 tty_wrbuf[tty_wrbufused++] = *s++;
967 --n;
973 ////////////////////////////////////////////////////////////////////////////////
974 // tty resize ioctl
975 static void ttyresize (int x, int y) {
976 struct winsize w;
978 w.ws_row = term.row;
979 w.ws_col = term.col;
980 w.ws_xpixel = w.ws_ypixel = 0;
981 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) fprintf(stderr, "Couldn't set window size: %s\n", SERRNO);
985 ////////////////////////////////////////////////////////////////////////////////
986 // tty utilities
987 static void tsetdirt (int top, int bot) {
988 int i;
990 LIMIT(top, 0, term.row-1);
991 LIMIT(bot, 0, term.row-1);
992 for (i = top; i <= bot; ++i) term.dirty[i] = 1;
996 static void tfulldirt (void) {
997 tsetdirt(0, term.row-1);
1001 static void tcursor (int mode) {
1002 static TCursor c;
1004 if (mode == CURSOR_SAVE) {
1005 c = term.c;
1006 } else if (mode == CURSOR_LOAD) {
1007 term.c = c;
1008 tmoveto(c.x, c.y);
1013 static void treset (void) {
1014 term.c = (TCursor){{
1015 .mode = ATTR_NULL,
1016 .fg = DefaultFG,
1017 .bg = DefaultBG
1018 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1020 term.top = 0, term.bot = term.row-1;
1021 term.mode = MODE_WRAP;
1022 #ifdef MOUSE_REPORTING_ALWAYS
1023 term.mode = MODE_MOUSEBTN;
1024 #endif
1025 tclearregion(0, 0, term.col-1, term.row-1);
1029 static void tnew (int col, int row) {
1030 /* set screen size */
1031 term.row = row, term.col = col;
1032 term.line = malloc(term.row*sizeof(Line));
1033 term.alt = malloc(term.row*sizeof(Line));
1034 term.dirty = malloc(term.row*sizeof(*term.dirty));
1035 for (row = 0; row < term.row; ++row) {
1036 term.line[row] = malloc(term.col*sizeof(Glyph));
1037 term.alt[row] = malloc(term.col*sizeof(Glyph));
1038 term.dirty[row] = 0;
1040 /* setup screen */
1041 treset();
1042 tswapscreen();
1043 treset();
1047 static void tswapscreen (void) {
1048 Line *tmp = term.line;
1050 term.line = term.alt;
1051 term.alt = tmp;
1052 term.mode ^= MODE_ALTSCREEN;
1053 tfulldirt();
1057 static void tscrolldown (int orig, int n) {
1058 int i;
1059 Line temp;
1061 LIMIT(n, 0, term.bot-orig+1);
1062 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1063 for (i = term.bot; i >= orig+n; --i) {
1064 temp = term.line[i];
1065 term.line[i] = term.line[i-n];
1066 term.line[i-n] = temp;
1067 term.dirty[i] = 1;
1068 term.dirty[i-n] = 1;
1070 selscroll(orig, n);
1074 static void tscrollup (int orig, int n) {
1075 int i;
1076 Line temp;
1078 LIMIT(n, 0, term.bot-orig+1);
1079 tclearregion(0, orig, term.col-1, orig+n-1);
1080 for (i = orig; i <= term.bot-n; ++i) {
1081 temp = term.line[i];
1082 term.line[i] = term.line[i+n];
1083 term.line[i+n] = temp;
1084 term.dirty[i] = 1;
1085 term.dirty[i+n] = 1;
1087 selscroll(orig, -n);
1091 static void selscroll (int orig, int n) {
1092 if (sel.bx == -1) return;
1093 if (BETWEEN(sel.by, orig, term.bot) || BETWEEN(sel.ey, orig, term.bot)) {
1094 if ((sel.by += n) > term.bot || (sel.ey += n) < term.top) {
1095 sel.bx = -1;
1096 return;
1098 if (sel.by < term.top) {
1099 sel.by = term.top;
1100 sel.bx = 0;
1102 if (sel.ey > term.bot) {
1103 sel.ey = term.bot;
1104 sel.ex = term.col;
1106 sel.b.y = sel.by, sel.b.x = sel.bx;
1107 sel.e.y = sel.ey, sel.e.x = sel.ex;
1112 static void tnewline (int first_col) {
1113 int y = term.c.y;
1114 if (y == term.bot) tscrollup(term.top, 1); else ++y;
1115 tmoveto(first_col ? 0 : term.c.x, y);
1119 static void csiparse (void) {
1120 /* int noarg = 1; */
1121 char *p = escseq.buf;
1122 escseq.narg = 0;
1123 if (*p == '?') { escseq.priv = 1; ++p; }
1124 while (p < escseq.buf+escseq.len) {
1125 while (isdigit(*p)) {
1126 escseq.arg[escseq.narg] *= 10;
1127 escseq.arg[escseq.narg] += *p++ - '0'/*, noarg = 0 */;
1129 if (*p == ';' && escseq.narg+1 < ESC_ARG_SIZ) {
1130 ++escseq.narg;
1131 ++p;
1132 } else {
1133 escseq.mode = *p;
1134 ++escseq.narg;
1135 return;
1141 static void tmoveto (int x, int y) {
1142 LIMIT(x, 0, term.col-1);
1143 LIMIT(y, 0, term.row-1);
1144 term.c.state &= ~CURSOR_WRAPNEXT;
1145 term.c.x = x;
1146 term.c.y = y;
1150 static void tsetchar (const char *c) {
1151 term.dirty[term.c.y] = 1;
1152 term.line[term.c.y][term.c.x] = term.c.attr;
1153 memcpy(term.line[term.c.y][term.c.x].c, c, UTF_SIZ);
1154 term.line[term.c.y][term.c.x].state |= GLYPH_SET;
1159 static void tsetchar1b (char c) {
1160 term.dirty[term.c.y] = 1;
1161 term.line[term.c.y][term.c.x] = term.c.attr;
1162 memset(term.line[term.c.y][term.c.x].c, 0, UTF_SIZ);
1163 term.line[term.c.y][term.c.x].c[0] = c;
1164 term.line[term.c.y][term.c.x].state |= GLYPH_SET;
1169 static void tclearregion (int x1, int y1, int x2, int y2) {
1170 int temp;
1172 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
1173 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
1174 LIMIT(x1, 0, term.col-1);
1175 LIMIT(x2, 0, term.col-1);
1176 LIMIT(y1, 0, term.row-1);
1177 LIMIT(y2, 0, term.row-1);
1178 for (int y = y1; y <= y2; ++y) {
1179 term.dirty[y] = 1;
1180 for (int x = x1; x <= x2; ++x) {
1181 term.line[y][x] = term.c.attr;
1182 term.line[y][x].state = 0;
1183 term.line[y][x].mode &= ATTR_GFX;
1184 term.line[y][x].c[0] = ' ';
1190 static void tfillregionwithE (int x1, int y1, int x2, int y2) {
1191 int temp;
1193 if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
1194 if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; }
1195 LIMIT(x1, 0, term.col-1);
1196 LIMIT(x2, 0, term.col-1);
1197 LIMIT(y1, 0, term.row-1);
1198 LIMIT(y2, 0, term.row-1);
1199 for (int y = y1; y <= y2; ++y) {
1200 term.dirty[y] = 1;
1201 for (int x = x1; x <= x2; ++x) {
1202 term.line[y][x].state = /*GLYPH_SET |*/ GLYPH_DIRTY;
1203 term.line[y][x].c[0] = 'E';
1204 term.line[y][x].mode = ATTR_NULL;
1210 static void tdeletechar (int n) {
1211 int src = term.c.x+n;
1212 int dst = term.c.x;
1213 int size = term.col-src;
1215 term.dirty[term.c.y] = 1;
1216 if (src >= term.col) {
1217 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1218 return;
1220 memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size*sizeof(Glyph));
1221 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1225 static void tinsertblank (int n) {
1226 int src = term.c.x;
1227 int dst = src+n;
1228 int size = term.col-dst;
1230 term.dirty[term.c.y] = 1;
1231 if (dst >= term.col) {
1232 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1233 return;
1235 memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size*sizeof(Glyph));
1236 tclearregion(src, term.c.y, dst-1, term.c.y);
1240 static void tinsertblankline (int n) {
1241 if (term.c.y < term.top || term.c.y > term.bot) return;
1242 tscrolldown(term.c.y, n);
1246 static void tdeleteline (int n) {
1247 if (term.c.y < term.top || term.c.y > term.bot) return;
1248 tscrollup(term.c.y, n);
1252 static void tsetattr (int *attr, int l) {
1253 int i;
1255 for (i = 0; i < l; ++i) {
1256 switch (attr[i]) {
1257 case 0:
1258 term.c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD);
1259 term.c.attr.fg = DefaultFG;
1260 term.c.attr.bg = DefaultBG;
1261 break;
1262 case 1:
1263 term.c.attr.mode |= ATTR_BOLD;
1264 break;
1265 case 4:
1266 term.c.attr.mode |= ATTR_UNDERLINE;
1267 break;
1268 case 7:
1269 term.c.attr.mode |= ATTR_REVERSE;
1270 break;
1271 case 22:
1272 term.c.attr.mode &= ~ATTR_BOLD;
1273 break;
1274 case 24:
1275 term.c.attr.mode &= ~ATTR_UNDERLINE;
1276 break;
1277 case 27:
1278 term.c.attr.mode &= ~ATTR_REVERSE;
1279 break;
1280 case 38:
1281 if (i + 2 < l && attr[i + 1] == 5) {
1282 i += 2;
1283 if (BETWEEN(attr[i], 0, 255)) {
1284 term.c.attr.fg = attr[i];
1285 } else {
1286 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[i]);
1288 } else {
1289 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]);
1291 break;
1292 case 39:
1293 term.c.attr.fg = DefaultFG;
1294 break;
1295 case 48:
1296 if (i + 2 < l && attr[i + 1] == 5) {
1297 i += 2;
1298 if (BETWEEN(attr[i], 0, 255)) {
1299 term.c.attr.bg = attr[i];
1300 } else {
1301 fprintf(stderr, "erresc: bad bgcolor %d\n", attr[i]);
1303 } else {
1304 fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]);
1306 break;
1307 case 49:
1308 term.c.attr.bg = DefaultBG;
1309 break;
1310 default:
1311 if (BETWEEN(attr[i], 30, 37)) term.c.attr.fg = attr[i]-30;
1312 else if (BETWEEN(attr[i], 40, 47)) term.c.attr.bg = attr[i]-40;
1313 else if (BETWEEN(attr[i], 90, 97)) term.c.attr.fg = attr[i]-90+8;
1314 else if (BETWEEN(attr[i], 100, 107)) term.c.attr.fg = attr[i]-100+8;
1315 else { fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]); csidump(); }
1316 break;
1322 static void tsetscroll (int t, int b) {
1323 int temp;
1325 LIMIT(t, 0, term.row-1);
1326 LIMIT(b, 0, term.row-1);
1327 if (t > b) {
1328 temp = t;
1329 t = b;
1330 b = temp;
1332 term.top = t;
1333 term.bot = b;
1337 ////////////////////////////////////////////////////////////////////////////////
1338 // esc processing
1339 #define UPDATE_LASTWRITE do { tty_last_write_ms = mclock_ticks(); } while (0)
1342 static void csidump (void) {
1343 int i;
1345 printf("ESC[");
1346 for (i = 0; i < escseq.len; ++i) {
1347 uint c = escseq.buf[i] & 0xff;
1349 if (isprint(c)) putchar(c);
1350 else if (c == '\n') printf("(\\n)");
1351 else if (c == '\r') printf("(\\r)");
1352 else if (c == 0x1b) printf("(\\e)");
1353 else printf("(%02x)", c);
1355 putchar('\n');
1360 static void csidumpdbg (void) {
1361 int i;
1363 dlogf("ESC[");
1364 for (i = 0; i < escseq.len; ++i) {
1365 uint c = escseq.buf[i] & 0xff;
1367 if (isprint(c)) dlogf(" %c", c);
1368 else if (c == '\n') dlogf(" (\\n)");
1369 else if (c == '\r') dlogf(" (\\r)");
1370 else if (c == 0x1b) dlogf(" (\\e)");
1371 else dlogf(" (%02x)", c);
1373 //putchar('\n');
1378 static void csihandle (void) {
1379 switch (escseq.mode) {
1380 case '@': /* ICH -- Insert <n> blank char */
1381 DEFAULT(escseq.arg[0], 1);
1382 tinsertblank(escseq.arg[0]);
1383 break;
1384 case 'A': /* CUU -- Cursor <n> Up */
1385 case 'e':
1386 DEFAULT(escseq.arg[0], 1);
1387 tmoveto(term.c.x, term.c.y-escseq.arg[0]);
1388 break;
1389 case 'B': /* CUD -- Cursor <n> Down */
1390 DEFAULT(escseq.arg[0], 1);
1391 tmoveto(term.c.x, term.c.y+escseq.arg[0]);
1392 break;
1393 case 'C': /* CUF -- Cursor <n> Forward */
1394 case 'a':
1395 DEFAULT(escseq.arg[0], 1);
1396 tmoveto(term.c.x+escseq.arg[0], term.c.y);
1397 break;
1398 case 'D': /* CUB -- Cursor <n> Backward */
1399 DEFAULT(escseq.arg[0], 1);
1400 tmoveto(term.c.x-escseq.arg[0], term.c.y);
1401 break;
1402 case 'E': /* CNL -- Cursor <n> Down and first col */
1403 DEFAULT(escseq.arg[0], 1);
1404 tmoveto(0, term.c.y+escseq.arg[0]);
1405 break;
1406 case 'F': /* CPL -- Cursor <n> Up and first col */
1407 DEFAULT(escseq.arg[0], 1);
1408 tmoveto(0, term.c.y-escseq.arg[0]);
1409 break;
1410 case 'G': /* CHA -- Move to <col> */
1411 case '`': /* XXX: HPA -- same? */
1412 DEFAULT(escseq.arg[0], 1);
1413 tmoveto(escseq.arg[0]-1, term.c.y);
1414 break;
1415 case 'H': /* CUP -- Move to <row> <col> */
1416 case 'f': /* XXX: HVP -- same? */
1417 DEFAULT(escseq.arg[0], 1);
1418 DEFAULT(escseq.arg[1], 1);
1419 tmoveto(escseq.arg[1]-1, escseq.arg[0]-1);
1420 break;
1421 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
1422 case 'J': /* ED -- Clear screen */
1423 sel.bx = -1;
1424 switch (escseq.arg[0]) {
1425 case 0: /* below */
1426 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1427 if (term.c.y < term.row-1) tclearregion(0, term.c.y+1, term.col-1, term.row-1);
1428 break;
1429 case 1: /* above */
1430 if (term.c.y > 1) tclearregion(0, 0, term.col-1, term.c.y-1);
1431 tclearregion(0, term.c.y, term.c.x, term.c.y);
1432 break;
1433 case 2: /* all */
1434 tclearregion(0, 0, term.col-1, term.row-1);
1435 break;
1436 default:
1437 goto unknown;
1439 break;
1440 case 'K': /* EL -- Clear line */
1441 switch (escseq.arg[0]) {
1442 case 0: /* right */
1443 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1444 break;
1445 case 1: /* left */
1446 tclearregion(0, term.c.y, term.c.x, term.c.y);
1447 break;
1448 case 2: /* all */
1449 tclearregion(0, term.c.y, term.col-1, term.c.y);
1450 break;
1452 break;
1453 case 'S': /* SU -- Scroll <n> line up */
1454 DEFAULT(escseq.arg[0], 1);
1455 tscrollup(term.top, escseq.arg[0]);
1456 break;
1457 case 'T': /* SD -- Scroll <n> line down */
1458 DEFAULT(escseq.arg[0], 1);
1459 tscrolldown(term.top, escseq.arg[0]);
1460 break;
1461 case 'L': /* IL -- Insert <n> blank lines */
1462 DEFAULT(escseq.arg[0], 1);
1463 tinsertblankline(escseq.arg[0]);
1464 break;
1465 case 'l': /* RM -- Reset Mode */
1466 if (escseq.priv) {
1467 switch (escseq.arg[0]) {
1468 case 1:
1469 term.mode &= ~MODE_APPKEYPAD;
1470 break;
1471 case 5: /* DECSCNM -- Remove reverse video */
1472 if (IS_SET(MODE_REVERSE)) {
1473 term.mode &= ~MODE_REVERSE;
1474 draw();
1476 break;
1477 case 7: /* autowrap off */
1478 term.mode &= ~MODE_WRAP;
1479 break;
1480 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
1481 break;
1482 case 20: /* non-standard code? */
1483 term.mode &= ~MODE_CRLF;
1484 break;
1485 case 25: /* hide cursor */
1486 term.c.state |= CURSOR_HIDE;
1487 break;
1488 case 1000: /* disable X11 xterm mouse reporting */
1489 #ifndef MOUSE_REPORTING_ALWAYS
1490 term.mode &= ~MODE_MOUSEBTN;
1491 #endif
1492 break;
1493 case 1002:
1494 #ifndef MOUSE_REPORTING_ALWAYS
1495 term.mode &= ~MODE_MOUSEMOTION;
1496 #endif
1497 break;
1498 case 1049: /* = 1047 and 1048 */
1499 case 47:
1500 case 1047:
1501 if (IS_SET(MODE_ALTSCREEN)) {
1502 tclearregion(0, 0, term.col-1, term.row-1);
1503 tswapscreen();
1505 if (escseq.arg[0] != 1049) break;
1506 case 1048:
1507 tcursor(CURSOR_LOAD);
1508 break;
1509 default:
1510 goto unknown;
1512 } else {
1513 switch (escseq.arg[0]) {
1514 case 4:
1515 term.mode &= ~MODE_INSERT;
1516 break;
1517 default:
1518 goto unknown;
1521 break;
1522 case 'M': /* DL -- Delete <n> lines */
1523 DEFAULT(escseq.arg[0], 1);
1524 tdeleteline(escseq.arg[0]);
1525 break;
1526 case 'X': /* ECH -- Erase <n> char */
1527 DEFAULT(escseq.arg[0], 1);
1528 tclearregion(term.c.x, term.c.y, term.c.x + escseq.arg[0], term.c.y);
1529 break;
1530 case 'P': /* DCH -- Delete <n> char */
1531 DEFAULT(escseq.arg[0], 1);
1532 tdeletechar(escseq.arg[0]);
1533 break;
1534 /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */
1535 case 'd': /* VPA -- Move to <row> */
1536 DEFAULT(escseq.arg[0], 1);
1537 tmoveto(term.c.x, escseq.arg[0]-1);
1538 break;
1539 case 'h': /* SM -- Set terminal mode */
1540 if (escseq.priv) {
1541 switch (escseq.arg[0]) {
1542 case 1:
1543 term.mode |= MODE_APPKEYPAD;
1544 break;
1545 case 5: /* DECSCNM -- Reverve video */
1546 if (!IS_SET(MODE_REVERSE)) {
1547 term.mode |= MODE_REVERSE;
1548 draw();
1550 break;
1551 case 7:
1552 term.mode |= MODE_WRAP;
1553 break;
1554 case 20:
1555 term.mode |= MODE_CRLF;
1556 break;
1557 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1558 /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */
1559 if (escseq.narg > 1 && escseq.arg[1] != 25) break;
1560 case 25:
1561 term.c.state &= ~CURSOR_HIDE;
1562 break;
1563 case 1000: /* 1000,1002: enable xterm mouse report */
1564 #ifndef MOUSE_REPORTING_ALWAYS
1565 term.mode |= MODE_MOUSEBTN;
1566 #endif
1567 break;
1568 case 1002:
1569 #ifndef MOUSE_REPORTING_ALWAYS
1570 term.mode |= MODE_MOUSEMOTION;
1571 #endif
1572 break;
1573 case 1049: /* = 1047 and 1048 */
1574 case 47:
1575 case 1047:
1576 if (IS_SET(MODE_ALTSCREEN)) tclearregion(0, 0, term.col-1, term.row-1); else tswapscreen();
1577 if (escseq.arg[0] != 1049) break;
1578 case 1048:
1579 tcursor(CURSOR_SAVE);
1580 break;
1581 default: goto unknown;
1583 } else {
1584 switch (escseq.arg[0]) {
1585 case 4:
1586 term.mode |= MODE_INSERT;
1587 break;
1588 default: goto unknown;
1591 break;
1592 case 'm': /* SGR -- Terminal attribute (color) */
1593 tsetattr(escseq.arg, escseq.narg);
1594 break;
1595 case 'r': /* DECSTBM -- Set Scrolling Region */
1596 if (escseq.priv) {
1597 goto unknown;
1598 } else {
1599 DEFAULT(escseq.arg[0], 1);
1600 DEFAULT(escseq.arg[1], term.row);
1601 tsetscroll(escseq.arg[0]-1, escseq.arg[1]-1);
1602 tmoveto(0, 0);
1604 break;
1605 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1606 tcursor(CURSOR_SAVE);
1607 break;
1608 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1609 tcursor(CURSOR_LOAD);
1610 break;
1611 default:
1612 unknown:
1613 fprintf(stderr, "erresc: unknown csi ");
1614 csidump();
1616 dlogf("erresc: unknown csi ");
1617 csidumpdbg();
1619 /* die(""); */
1620 break;
1622 UPDATE_LASTWRITE;
1626 static void csireset (void) {
1627 memset(&escseq, 0, sizeof(escseq));
1631 static void tputtab (void) {
1632 int space = TAB-term.c.x%TAB;
1634 tmoveto(term.c.x+space, term.c.y);
1638 ////////////////////////////////////////////////////////////////////////////////
1639 // put char to output buffer or process command
1640 static void tputc (const char *c) {
1641 char ascii = *c;
1643 if (term.esc & ESC_START) {
1644 if (term.esc & ESC_CSI) {
1645 escseq.buf[escseq.len++] = ascii;
1646 if (BETWEEN(ascii, 0x40, 0x7E) || escseq.len >= ESC_BUF_SIZ) {
1647 term.esc = 0;
1648 csiparse();
1649 csihandle();
1651 /* TODO: handle other OSC */
1652 } else if (term.esc & ESC_OSC) {
1653 if (ascii == ';') {
1654 term.titlelen = 0;
1655 term.esc = ESC_START | ESC_TITLE;
1657 } else if (term.esc & ESC_TITLE) {
1658 if (ascii == '\a' || term.titlelen+1 >= ESC_TITLE_SIZ) {
1659 term.esc = 0;
1660 term.title[term.titlelen] = '\0';
1661 XStoreName(xw.dpy, xw.win, term.title);
1662 } else {
1663 term.title[term.titlelen++] = ascii;
1665 } else if (term.esc & ESC_ALTCHARSET) {
1666 switch (ascii) {
1667 case '0': /* Line drawing crap */
1668 term.c.attr.mode |= ATTR_GFX;
1669 break;
1670 case 'B': /* Back to regular text */
1671 term.c.attr.mode &= ~ATTR_GFX;
1672 break;
1673 default:
1674 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
1675 term.c.attr.mode &= ~ATTR_GFX;
1677 term.esc = 0;
1678 } else if (term.esc & ESC_HASH) {
1679 switch (ascii) {
1680 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
1681 tfillregionwithE(0, 0, term.col-1, term.row-1);
1682 UPDATE_LASTWRITE;
1683 break;
1685 term.esc = 0;
1686 } else if (term.esc & ESC_PERCENT) {
1687 term.esc = 0;
1688 } else {
1689 switch (ascii) {
1690 case '[':
1691 term.esc |= ESC_CSI;
1692 break;
1693 case ']':
1694 term.esc |= ESC_OSC;
1695 break;
1696 case '(':
1697 term.esc |= ESC_ALTCHARSET;
1698 break;
1699 case '#':
1700 term.esc |= ESC_HASH;
1701 break;
1702 case '%':
1703 term.esc |= ESC_PERCENT;
1704 break;
1705 case 'D': /* IND -- Linefeed */
1706 if (term.c.y == term.bot) tscrollup(term.top, 1); else tmoveto(term.c.x, term.c.y+1);
1707 term.esc = 0;
1708 UPDATE_LASTWRITE;
1709 break;
1710 case 'E': /* NEL -- Next line */
1711 tnewline(1); /* always go to first col */
1712 term.esc = 0;
1713 UPDATE_LASTWRITE;
1714 break;
1715 case 'M': /* RI -- Reverse linefeed */
1716 if (term.c.y == term.top) tscrolldown(term.top, 1); else tmoveto(term.c.x, term.c.y-1);
1717 term.esc = 0;
1718 UPDATE_LASTWRITE;
1719 break;
1720 case 'c': /* RIS -- Reset to inital state */
1721 treset();
1722 term.esc = 0;
1723 UPDATE_LASTWRITE;
1724 break;
1725 case '=': /* DECPAM -- Application keypad */
1726 term.mode |= MODE_APPKEYPAD;
1727 term.esc = 0;
1728 break;
1729 case '>': /* DECPNM -- Normal keypad */
1730 term.mode &= ~MODE_APPKEYPAD;
1731 term.esc = 0;
1732 break;
1733 case '7': /* DECSC -- Save Cursor */
1734 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
1735 //TODO?
1736 tcursor(CURSOR_SAVE);
1737 term.esc = 0;
1738 break;
1739 case '8': /* DECRC -- Restore Cursor */
1740 //TODO?
1741 tcursor(CURSOR_LOAD);
1742 term.esc = 0;
1743 UPDATE_LASTWRITE;
1744 break;
1745 case 'Z': /* DEC private identification */
1746 ttywrite("\x1b[?1;2c", 7);
1747 term.esc = 0;
1748 break;
1750 case '\x1b':
1751 term.esc = 0;
1752 break;
1754 default:
1755 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", (uchar)ascii, isprint(ascii)?ascii:'.');
1756 term.esc = 0;
1759 } else {
1760 if (sel.bx != -1 && BETWEEN(term.c.y, sel.by, sel.ey)) sel.bx = -1;
1761 switch (ascii) {
1762 case '\t':
1763 tputtab();
1764 UPDATE_LASTWRITE;
1765 break;
1766 case '\b':
1767 tmoveto(term.c.x-1, term.c.y);
1768 UPDATE_LASTWRITE;
1769 break;
1770 case '\r':
1771 tmoveto(0, term.c.y);
1772 UPDATE_LASTWRITE;
1773 break;
1774 case '\f':
1775 case '\v':
1776 case '\n':
1777 /* go to first col if the mode is set */
1778 tnewline(IS_SET(MODE_CRLF));
1779 UPDATE_LASTWRITE;
1780 break;
1781 case '\a':
1782 //if (!(xw.state & WIN_FOCUSED)) xseturgency(1);
1783 break;
1784 case '\033':
1785 csireset();
1786 term.esc = ESC_START;
1787 break;
1788 default:
1789 if (term.c.attr.mode&ATTR_GFX) {
1790 long cc;
1792 utf8decode(c, &cc);
1793 if (cc < 32 || cc >= 127) break; //FIXME: nothing at all
1794 } else {
1795 if ((unsigned char)ascii < 32 || ascii == 127) break; // seems that this chars are empty too
1797 if (IS_SET(MODE_WRAP) && term.c.state & CURSOR_WRAPNEXT) tnewline(1); /* always go to first col */
1798 tsetchar(c);
1799 if (term.c.x+1 < term.col) tmoveto(term.c.x+1, term.c.y); else term.c.state |= CURSOR_WRAPNEXT;
1800 UPDATE_LASTWRITE;
1801 break;
1807 ////////////////////////////////////////////////////////////////////////////////
1808 // tty resising
1809 static int tresize (int col, int row) {
1810 int i, x;
1811 int minrow = MIN(row, term.row);
1812 int mincol = MIN(col, term.col);
1813 int slide = term.c.y-row+1;
1815 if (col < 1 || row < 1) return 0;
1816 /* free unneeded rows */
1817 i = 0;
1818 if (slide > 0) {
1819 /* slide screen to keep cursor where we expect it -
1820 * tscrollup would work here, but we can optimize to
1821 * memmove because we're freeing the earlier lines */
1822 for (/* i = 0 */; i < slide; ++i) {
1823 free(term.line[i]);
1824 free(term.alt[i]);
1826 memmove(term.line, term.line+slide, row*sizeof(Line));
1827 memmove(term.alt, term.alt+slide, row*sizeof(Line));
1829 for (i += row; i < term.row; ++i) {
1830 free(term.line[i]);
1831 free(term.alt[i]);
1833 /* resize to new height */
1834 term.line = realloc(term.line, row*sizeof(Line));
1835 term.alt = realloc(term.alt, row*sizeof(Line));
1836 term.dirty = realloc(term.dirty, row*sizeof(*term.dirty));
1837 /* resize each row to new width, zero-pad if needed */
1838 for (i = 0; i < minrow; ++i) {
1839 term.dirty[i] = 1;
1840 term.line[i] = realloc(term.line[i], col*sizeof(Glyph));
1841 term.alt[i] = realloc(term.alt[i], col*sizeof(Glyph));
1842 for (x = mincol; x < col; ++x) {
1843 term.line[i][x].state = 0;
1844 term.alt[i][x].state = 0;
1847 /* allocate any new rows */
1848 for (/* i == minrow */; i < row; ++i) {
1849 term.dirty[i] = 1;
1850 term.line[i] = calloc(col, sizeof(Glyph));
1851 term.alt[i] = calloc(col, sizeof(Glyph));
1853 /* update terminal size */
1854 term.col = col, term.row = row;
1855 /* make use of the LIMIT in tmoveto */
1856 tmoveto(term.c.x, term.c.y);
1857 /* reset scrolling region */
1858 tsetscroll(0, row-1);
1860 return (slide > 0);
1864 static void xresize (int col, int row) {
1865 Pixmap newbuf;
1866 int oldw, oldh;
1868 oldw = xw.bufw;
1869 oldh = xw.bufh;
1870 xw.bufw = MAX(1, col*xw.cw);
1871 xw.bufh = MAX(1, row*xw.ch);
1872 newbuf = XCreatePixmap(xw.dpy, xw.win, xw.bufw, xw.bufh, XDefaultDepth(xw.dpy, xw.scr));
1873 XCopyArea(xw.dpy, xw.buf, newbuf, dc.gc, 0, 0, xw.bufw, xw.bufh, 0, 0);
1874 XFreePixmap(xw.dpy, xw.buf);
1875 XSetForeground(xw.dpy, dc.gc, dc.col[DefaultBG]);
1876 if (xw.bufw > oldw) {
1877 XFillRectangle(xw.dpy, newbuf, dc.gc, oldw, 0, xw.bufw-oldw, MIN(xw.bufh, oldh));
1878 } else if (xw.bufw < oldw && (BORDER > 0 || xw.w > xw.bufw)) {
1879 XClearArea(xw.dpy, xw.win, BORDER+xw.bufw, BORDER, xw.w-xw.bufh-BORDER, BORDER+MIN(xw.bufh, oldh), False);
1881 if (xw.bufh > oldh) {
1882 XFillRectangle(xw.dpy, newbuf, dc.gc, 0, oldh, xw.bufw, xw.bufh-oldh);
1883 } else if (xw.bufh < oldh && (BORDER > 0 || xw.h > xw.bufh)) {
1884 XClearArea(xw.dpy, xw.win, BORDER, BORDER+xw.bufh, xw.w-2*BORDER, xw.h-xw.bufh-BORDER, False);
1886 xw.buf = newbuf;
1890 ////////////////////////////////////////////////////////////////////////////////
1891 // x11 drawing and utils
1892 static void xloadcols (void) {
1893 int i, r, g, b;
1894 XColor color;
1895 ulong white = WhitePixel(xw.dpy, xw.scr);
1896 /* load colors [0-15] colors and [256-LEN(colorname)[ (config.h) */
1897 for (i = 0; i < LEN(colorname); ++i) {
1898 if (!colorname[i]) continue;
1899 if (!XAllocNamedColor(xw.dpy, xw.cmap, colorname[i], &color, &color)) {
1900 dc.col[i] = white;
1901 fprintf(stderr, "Could not allocate color '%s'\n", colorname[i]);
1902 } else {
1903 dc.col[i] = color.pixel;
1906 /* load colors [16-255] ; same colors as xterm */
1907 for (i = 16, r = 0; r < 6; ++r) {
1908 for (g = 0; g < 6; ++g) {
1909 for (b = 0; b < 6; ++b) {
1910 color.red = r == 0 ? 0 : 0x3737 + 0x2828 * r;
1911 color.green = g == 0 ? 0 : 0x3737 + 0x2828 * g;
1912 color.blue = b == 0 ? 0 : 0x3737 + 0x2828 * b;
1913 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
1914 dc.col[i] = white;
1915 fprintf(stderr, "Could not allocate color %d\n", i);
1916 } else {
1917 dc.col[i] = color.pixel;
1919 ++i;
1923 for (r = 0; r < 24; ++r, ++i) {
1924 color.red = color.green = color.blue = 0x0808+0x0a0a*r;
1925 if (!XAllocColor(xw.dpy, xw.cmap, &color)) {
1926 dc.col[i] = white;
1927 fprintf(stderr, "Could not allocate color %d\n", i);
1928 } else {
1929 dc.col[i] = color.pixel;
1935 static void xclear (int x1, int y1, int x2, int y2) {
1936 XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? DefaultFG : DefaultBG]);
1937 XFillRectangle(xw.dpy, xw.buf, dc.gc, x1*xw.cw, y1*xw.ch, (x2-x1+1)*xw.cw, (y2-y1+1)*xw.ch);
1941 static void xhints (void) {
1942 XClassHint class = {opt_class ? opt_class : TNAME, TNAME};
1943 XWMHints wm = {.flags = InputHint, .input = 1};
1944 XSizeHints size = {
1945 .flags = PSize | PResizeInc | PBaseSize,
1946 .height = xw.h,
1947 .width = xw.w,
1948 .height_inc = xw.ch,
1949 .width_inc = xw.cw,
1950 .base_height = 2*BORDER,
1951 .base_width = 2*BORDER,
1953 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, &size, &wm, &class);
1957 static XFontSet xinitfont (const char *fontstr) {
1958 XFontSet set;
1959 char *def, **missing;
1960 int n;
1962 missing = NULL;
1963 set = XCreateFontSet(xw.dpy, fontstr, &missing, &n, &def);
1964 if (missing) {
1965 while (n--) fprintf(stderr, "st: missing fontset: %s\n", missing[n]);
1966 XFreeStringList(missing);
1968 return set;
1972 static void xgetfontinfo (XFontSet set, int *ascent, int *descent, short *lbearing, short *rbearing) {
1973 XFontStruct **xfonts;
1974 char **font_names;
1975 int i, n;
1977 *ascent = *descent = *lbearing = *rbearing = 0;
1978 n = XFontsOfFontSet(set, &xfonts, &font_names);
1979 for (i = 0; i < n; ++i) {
1980 *ascent = MAX(*ascent, (*xfonts)->ascent);
1981 *descent = MAX(*descent, (*xfonts)->descent);
1982 *lbearing = MAX(*lbearing, (*xfonts)->min_bounds.lbearing);
1983 *rbearing = MAX(*rbearing, (*xfonts)->max_bounds.rbearing);
1984 xfonts++;
1989 static void initfonts (char *fontstr, char *bfontstr) {
1990 if ((dc.font.set = xinitfont(fontstr)) == NULL || (dc.bfont.set = xinitfont(bfontstr)) == NULL) die("Can't load font %s\n", dc.font.set ? BOLDFONT : FONT);
1991 xgetfontinfo(dc.font.set, &dc.font.ascent, &dc.font.descent, &dc.font.lbearing, &dc.font.rbearing);
1992 xgetfontinfo(dc.bfont.set, &dc.bfont.ascent, &dc.bfont.descent, &dc.bfont.lbearing, &dc.bfont.rbearing);
1996 static void xinit (void) {
1997 XSetWindowAttributes attrs;
1998 Cursor cursor;
1999 Window parent;
2001 if (!(xw.dpy = XOpenDisplay(NULL))) die("Can't open display\n");
2002 xw.scr = XDefaultScreen(xw.dpy);
2003 /* font */
2004 initfonts(FONT, BOLDFONT);
2005 /* XXX: Assuming same size for bold font */
2006 xw.cw = dc.font.rbearing-dc.font.lbearing;
2007 xw.ch = dc.font.ascent+dc.font.descent;
2008 /* colors */
2009 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
2010 xloadcols();
2011 /* window - default size */
2012 xw.bufh = term.row*xw.ch;
2013 xw.bufw = term.col*xw.cw;
2014 xw.h = xw.bufh+2*BORDER;
2015 xw.w = xw.bufw+2*BORDER;
2016 attrs.background_pixel = dc.col[DefaultBG];
2017 attrs.border_pixel = dc.col[DefaultBG];
2018 attrs.bit_gravity = NorthWestGravity;
2019 attrs.event_mask = FocusChangeMask | KeyPressMask
2020 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
2021 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask
2022 | EnterWindowMask | LeaveWindowMask;
2023 attrs.colormap = xw.cmap;
2024 parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr);
2025 xw.win = XCreateWindow(xw.dpy, parent, 0, 0,
2026 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
2027 XDefaultVisual(xw.dpy, xw.scr),
2028 CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask
2029 | CWColormap,
2030 &attrs);
2031 xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.bufw, xw.bufh, XDefaultDepth(xw.dpy, xw.scr));
2032 /* input methods */
2033 xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
2034 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
2035 /* gc */
2036 dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL);
2037 /* white cursor, black outline */
2038 cursor = XCreateFontCursor(xw.dpy, XC_xterm);
2039 XDefineCursor(xw.dpy, xw.win, cursor);
2040 XRecolorCursor(xw.dpy, cursor,
2041 &(XColor){.red = 0xffff, .green = 0xffff, .blue = 0xffff},
2042 &(XColor){.red = 0x0000, .green = 0x0000, .blue = 0x0000});
2043 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
2044 XStoreName(xw.dpy, xw.win, opt_title ? opt_title : "sterm");
2045 XMapWindow(xw.dpy, xw.win);
2046 xhints();
2047 XSync(xw.dpy, 0);
2051 static void xdraws (char *s, const Glyph *base, int x, int y, int charlen, int bytelen) {
2052 int fg = base->fg, bg = base->bg, temp;
2053 int winx = x*xw.cw, winy = y*xw.ch+dc.font.ascent, width = charlen*xw.cw;
2054 XFontSet fontset = dc.font.set;
2055 int i;
2056 int defF = (fg == DefaultFG), defB = (bg == DefaultBG);
2058 /* only switch default fg/bg if term is in RV mode */
2059 if (IS_SET(MODE_REVERSE)) {
2060 if (defF) fg = DefaultBG;
2061 if (defB) bg = DefaultFG;
2063 if (base->mode & ATTR_BOLD) {
2064 if (defF && defB) fg = DefaultBoldFG; else fg += 8;
2065 fontset = dc.bfont.set;
2067 #ifdef UNDERLINE_COLORED
2068 if (base->mode & ATTR_UNDERLINE) {
2069 if (defF && defB) fg = DefaultULFG;
2071 #endif
2073 if (base->mode & ATTR_REVERSE) { temp = fg; fg = bg; bg = temp; }
2075 XSetBackground(xw.dpy, dc.gc, dc.col[bg]);
2076 XSetForeground(xw.dpy, dc.gc, dc.col[fg]);
2078 if (base->mode & ATTR_GFX) {
2079 for (i = 0; i < bytelen; ++i) {
2080 /*char c = gfx[(uint)s[i]%256];
2081 if (c) s[i] = c;
2082 else*/
2083 if ((unsigned char)s[i] > 95) s[i] -= 95;
2087 XmbDrawImageString(xw.dpy, xw.buf, fontset, dc.gc, winx, winy, s, bytelen);
2089 if (base->mode & ATTR_UNDERLINE) {
2090 XDrawLine(xw.dpy, xw.buf, dc.gc, winx, winy+1, winx+width-1, winy+1);
2095 /* copy buffer pixmap to screen pixmap */
2096 static void xcopy (int x, int y, int cols, int rows) {
2097 int src_x = x*xw.cw, src_y = y*xw.ch, src_w = cols*xw.cw, src_h = rows*xw.ch;
2098 int dst_x = BORDER+src_x, dst_y = BORDER+src_y;
2100 XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, src_x, src_y, src_w, src_h, dst_x, dst_y);
2104 static void xdrawcursor (void) {
2105 static int oldx = 0;
2106 static int oldy = 0;
2107 int sl;
2108 Glyph g = {{' '}, ATTR_NULL, DefaultBG, DefaultCS, 0};
2109 LIMIT(oldx, 0, term.col-1);
2110 LIMIT(oldy, 0, term.row-1);
2111 /*if (term.line[term.c.y][term.c.x].state & GLYPH_SET)*/ memcpy(g.c, term.line[term.c.y][term.c.x].c, UTF_SIZ);
2112 /* remove the old cursor */
2113 if (/*term.line[oldy][oldx].state & GLYPH_SET*/ 1) {
2114 sl = utf8size(term.line[oldy][oldx].c);
2115 xdraws(term.line[oldy][oldx].c, &term.line[oldy][oldx], oldx, oldy, 1, sl);
2116 } else {
2117 xclear(oldx, oldy, oldx, oldy);
2119 xcopy(oldx, oldy, 1, 1);
2120 /* draw the new one */
2121 if (!(term.c.state & CURSOR_HIDE)) {
2122 if (!(xw.state & WIN_FOCUSED)) g.bg = DefaultUCS;
2123 if (IS_SET(MODE_REVERSE)) g.mode |= ATTR_REVERSE, g.fg = DefaultCS, g.bg = DefaultFG;
2124 sl = utf8size(g.c);
2125 xdraws(g.c, &g, term.c.x, term.c.y, 1, sl);
2126 oldx = term.c.x, oldy = term.c.y;
2128 xcopy(term.c.x, term.c.y, 1, 1);
2132 static void draw (void) {
2133 drawregion(0, 0, term.col, term.row);
2134 gettimeofday(&xw.lastdraw, NULL);
2138 static void drawregion (int x1, int y1, int x2, int y2) {
2139 int ic, ib, ox, sl;
2140 Glyph base, new;
2141 static char buf[DRAW_BUF_SIZ]; //FIXME! CAUTION
2143 if (!(xw.state & WIN_VISIBLE)) return;
2145 for (int y = y1; y < y2; ++y) {
2146 if (!term.dirty[y]) continue;
2147 xclear(0, y, term.col, y);
2148 term.dirty[y] = 0;
2149 base = term.line[y][0];
2150 ic = ib = ox = 0;
2151 for (int x = x1; x < x2; ++x) {
2152 new = term.line[y][x];
2153 if (sel.bx != -1 && *(new.c) && selected(x, y)) new.mode ^= ATTR_REVERSE;
2154 if (ib > 0 && (/*!(new.state & GLYPH_SET) ||*/ ATTRCMP(base, new) || ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
2155 xdraws(buf, &base, ox, y, ic, ib);
2156 ic = ib = 0;
2158 if (/*new.state & GLYPH_SET*/ 1) {
2159 if (ib == 0) {
2160 ox = x;
2161 base = new;
2163 if (new.mode & ATTR_GFX) {
2164 sl = 1;
2165 } else {
2166 sl = utf8size(new.c);
2168 memcpy(buf+ib, new.c, sl);
2169 ib += sl;
2170 ++ic;
2173 if (ib > 0) xdraws(buf, &base, ox, y, ic, ib);
2174 xcopy(0, y, term.col, 1);
2176 xdrawcursor();
2180 static void expose (XEvent *ev) {
2181 XExposeEvent *e = &ev->xexpose;
2182 if (xw.state & WIN_REDRAW) {
2183 if (!e->count) {
2184 xw.state &= ~WIN_REDRAW;
2185 xcopy(0, 0, term.col, term.row);
2187 } else {
2188 XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, e->x-BORDER, e->y-BORDER, e->width, e->height, e->x, e->y);
2193 static void visibility (XEvent *ev) {
2194 XVisibilityEvent *e = &ev->xvisibility;
2195 if (e->state == VisibilityFullyObscured) xw.state &= ~WIN_VISIBLE;
2196 else if (!(xw.state & WIN_VISIBLE)) xw.state |= WIN_VISIBLE | WIN_REDRAW;
2197 /* need a full redraw for next Expose, not just a buf copy */
2201 static void unmap (XEvent *ev) {
2202 xw.state &= ~WIN_VISIBLE;
2206 static void xseturgency (int add) {
2207 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
2209 h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
2210 XSetWMHints(xw.dpy, xw.win, h);
2211 XFree(h);
2215 static void focus (XEvent *ev) {
2216 if (ev->type == FocusIn) {
2217 xw.state |= WIN_FOCUSED;
2218 xseturgency(0);
2219 } else {
2220 xw.state &= ~WIN_FOCUSED;
2222 draw();
2226 ////////////////////////////////////////////////////////////////////////////////
2227 // keyboard mapping
2228 static const char *kmap (KeySym k, uint state) {
2229 state &= ~Mod2Mask;
2230 for (int i = 0; i < LEN(key); ++i) {
2231 uint mask = key[i].mask;
2232 if (key[i].k == k && ((state & mask) == mask || (mask == XK_NO_MOD && !state))) return (char*)key[i].s;
2234 return NULL;
2238 static void kpress (XEvent *ev) {
2239 XKeyEvent *e = &ev->xkey;
2240 KeySym ksym;
2241 char buf[32];
2242 const char *customkey;
2243 int len;
2244 int meta;
2245 int shift;
2246 Status status;
2248 meta = e->state & Mod1Mask;
2249 shift = e->state & ShiftMask;
2250 len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
2251 /* 1. custom keys from config.h */
2252 if ((customkey = kmap(ksym, e->state))) {
2253 ttywrite(customkey, strlen(customkey));
2254 /* 2. hardcoded (overrides X lookup) */
2255 } else {
2256 switch (ksym) {
2257 case XK_Up:
2258 case XK_Down:
2259 case XK_Left:
2260 case XK_Right:
2261 /* XXX: shift up/down doesn't work */
2262 sprintf(buf, "\033%c%c", IS_SET(MODE_APPKEYPAD) ? 'O' : '[', (shift ? "dacb":"DACB")[ksym - XK_Left]);
2263 ttywrite(buf, 3);
2264 break;
2265 case XK_Insert:
2266 if (shift) selpaste();
2267 break;
2268 case XK_Return:
2269 if (IS_SET(MODE_CRLF)) ttywrite("\r\n", 2); else ttywrite("\r", 1);
2270 break;
2271 /* 3. X lookup */
2272 default:
2273 if (len > 0) {
2274 if (meta && len == 1) ttywrite("\033", 1);
2275 ttywrite(buf, len);
2277 break;
2283 ////////////////////////////////////////////////////////////////////////////////
2284 // xembed?
2285 static void cmessage (XEvent *e) {
2286 /* See xembed specs http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */
2287 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
2288 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
2289 xw.state |= WIN_FOCUSED;
2290 xseturgency(0);
2291 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
2292 xw.state &= ~WIN_FOCUSED;
2294 draw();
2299 ////////////////////////////////////////////////////////////////////////////////
2300 static void resize (XEvent *e) {
2301 int col, row;
2303 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) return;
2304 xw.w = e->xconfigure.width;
2305 xw.h = e->xconfigure.height;
2306 col = (xw.w - 2*BORDER) / xw.cw;
2307 row = (xw.h - 2*BORDER) / xw.ch;
2308 if (col == term.col && row == term.row) return;
2309 if (tresize(col, row)) draw();
2310 ttyresize(col, row);
2311 xresize(col, row);
2315 static bool last_draw_too_old (void) {
2317 struct timeval now;
2319 gettimeofday(&now, NULL);
2320 return TIMEDIFF(now, xw.lastdraw) >= DRAW_TIMEOUT/1000;
2322 return (mclock_ticks()-tty_last_write_ms >= DRAW_TIMEOUT);
2326 ////////////////////////////////////////////////////////////////////////////////
2327 // main loop
2328 static void run (void) {
2329 bool stuff_to_print = 0;
2330 int xfd = XConnectionNumber(xw.dpy);
2332 for (;;) {
2333 XEvent ev;
2334 fd_set rfd, wfd;
2335 struct timeval timeout;
2337 FD_ZERO(&rfd);
2338 FD_ZERO(&wfd);
2339 FD_SET(cmdfd, &rfd);
2340 FD_SET(xfd, &rfd);
2341 // have something to write?
2342 if (tty_wrbufpos < tty_wrbufused) FD_SET(cmdfd, &wfd);
2344 timeout.tv_sec = 0;
2345 timeout.tv_usec = SELECT_TIMEOUT;
2346 if (select(MAX(xfd, cmdfd)+1, &rfd, &wfd, NULL, &timeout) < 0) {
2347 if (errno == EINTR) continue;
2348 die("select failed: %s\n", SERRNO);
2350 //dlogf("after select()");
2352 if (tty_wrbufpos < tty_wrbufused && FD_ISSET(cmdfd, &wfd)) {
2353 //dlogf("flushing write buffer");
2354 ttyflushwrbuf();
2355 //dlogf("flushing write buffer done");
2358 if (FD_ISSET(cmdfd, &rfd)) {
2359 //dlogf("reading from tty");
2360 ttyread();
2361 stuff_to_print = 1;
2362 //dlogf("reading from tty done");
2365 if (stuff_to_print) {
2366 if (last_draw_too_old()) {
2367 //dlogf("drawing");
2368 stuff_to_print = 0;
2369 draw();
2370 } else {
2371 //dlogf("excess output, not drawing");
2375 if (XPending(xw.dpy)) {
2376 //dlogf("X11 events");
2377 while (XPending(xw.dpy)) {
2378 XNextEvent(xw.dpy, &ev);
2379 if (XFilterEvent(&ev, xw.win)) continue;
2380 if (handler[ev.type]) (handler[ev.type])(&ev);
2382 //dlogf("X11 events done");
2388 ////////////////////////////////////////////////////////////////////////////////
2389 int main (int argc, char *argv[]) {
2390 //dbgLogInit();
2392 for (int i = 1; i < argc; i++) {
2393 switch (argv[i][0] != '-' || argv[i][2] ? -1 : argv[i][1]) {
2394 case 't':
2395 if (++i < argc) opt_title = argv[i];
2396 break;
2397 case 'c':
2398 if (++i < argc) opt_class = argv[i];
2399 break;
2400 case 'w':
2401 if (++i < argc) opt_embed = argv[i];
2402 break;
2403 case 'e':
2404 /* eat every remaining arguments */
2405 if (++i < argc) opt_cmd = &argv[i];
2406 goto run;
2407 case 'T':
2408 if (++i < argc) opt_term = argv[i];
2409 break;
2410 case 'v':
2411 case 'h':
2412 default:
2413 die(USAGE);
2416 run:
2417 mclock_init();
2418 setlocale(LC_CTYPE, "");
2419 tnew(80, 24);
2420 ttynew();
2421 xinit();
2422 selinit();
2423 run();
2424 return 0;