1 /* See LICENSE for licence details. */
5 #define _XOPEN_SOURCE 600
20 #include <sys/ioctl.h>
21 #include <sys/select.h>
24 #include <sys/types.h>
28 #include <X11/Xatom.h>
30 #include <X11/Xutil.h>
31 #include <X11/cursorfont.h>
32 #include <X11/keysym.h>
37 #define MOUSE_REPORTING_ALWAYS
38 #define DUMP_PROG_OUTPUT
39 #define DUMP_PROG_INPUT
42 ////////////////////////////////////////////////////////////////////////////////
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 ////////////////////////////////////////////////////////////////////////////////
50 #define XEMBED_FOCUS_IN (4)
51 #define XEMBED_FOCUS_OUT (5)
54 #define ESC_TITLE_SIZ (256)
55 #define ESC_BUF_SIZ (256)
56 #define ESC_ARG_SIZ (16)
57 #define DRAW_BUF_SIZ (2048)
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
{
83 ATTR_UNDERLINE
= 0x02,
88 enum cursor_movement
{
104 GLYPH_SET
= 0x01, /* for selection only */
112 MODE_APPKEYPAD
= 0x04,
113 MODE_ALTSCREEN
= 0x08,
115 MODE_MOUSEBTN
= 0x20,
116 MODE_MOUSEMOTION
= 0x40,
117 MODE_MOUSE
= 0x20|0x40,
126 ESC_ALTCHARSET
= 0x10,
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
;
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 */
161 Glyph attr
; /* current char attributes */
168 /* CSI Escape sequence structs */
169 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */
171 char buf
[ESC_BUF_SIZ
]; /* raw string */
172 int len
; /* raw string length */
174 int arg
[ESC_ARG_SIZ
];
175 int narg
; /* nb of args */
180 /* Internal representation of the screen */
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
];
197 /* Purely graphic info */
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
;
225 /* TODO: use better name for vars... */
230 struct { int x
, y
; } b
, e
;
233 struct timeval tclick1
;
234 struct timeval tclick2
;
241 /* Drawing Context */
243 ulong col
[LEN(colorname
) < 256 ? 256 : LEN(colorname
)];
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
*) = {
336 [ClientMessage
] = cmessage
,
337 [ConfigureNotify
] = resize
,
338 [VisibilityNotify
] = visibility
,
339 [UnmapNotify
] = unmap
,
343 [MotionNotify
] = bmotion
,
344 [ButtonPress
] = bpress
,
345 [ButtonRelease
] = brelease
,
346 [SelectionNotify
] = selnotify
,
347 [SelectionRequest
] = selrequest
,
351 ////////////////////////////////////////////////////////////////////////////////
356 static CSIEscape escseq
;
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 ////////////////////////////////////////////////////////////////////////////////
372 static int utf8decode (const char *s
, long *u
) {
378 if (~c
& B7
) { /* 0xxxxxxx */
381 } else if ((c
& (B7
|B6
|B5
)) == (B7
|B6
)) { /* 110xxxxx */
382 *u
= c
&(B4
|B3
|B2
|B1
|B0
);
384 } else if ((c
& (B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) { /* 1110xxxx */
385 *u
= c
&(B3
|B2
|B1
|B0
);
387 } else if ((c
& (B7
|B6
|B5
|B4
|B3
)) == (B7
|B6
|B5
|B4
)) { /* 11110xxx */
393 for (i
= n
, ++s
; i
> 0; --i
, ++rtn
, ++s
) {
395 if ((c
& (B7
|B6
)) != B7
) goto invalid
; /* 10xxxxxx */
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)) {
412 static int utf8encode (const long *u
, char *s
) {
420 *sp
= uc
; /* 0xxxxxxx */
422 } else if (*u
< 0x800) {
423 *sp
= (uc
>> 6) | (B7
|B6
); /* 110xxxxx */
425 } else if (uc
< 0x10000) {
426 *sp
= (uc
>> 12) | (B7
|B6
|B5
); /* 1110xxxx */
428 } else if (uc
<= 0x10FFFF) {
429 *sp
= (uc
>> 18) | (B7
|B6
|B5
|B4
); /* 11110xxx */
434 for (i
= n
, ++sp
; i
> 0; --i
, ++sp
) *sp
= ((uc
>> 6*(i
-1)) & (B5
|B4
|B3
|B2
|B1
|B0
)) | B7
; /* 10xxxxxx */
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
) {
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;
461 static int utf8size (const char *s
) {
465 if ((c
&(B7
|B6
|B5
)) == (B7
|B6
)) return 2;
466 if ((c
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) return 3;
471 ////////////////////////////////////////////////////////////////////////////////
473 static char *SPrintfVA (const char *fmt
, va_list vaorig
) {
478 if (buf
== NULL
) { fprintf(stderr
, "\nFATAL: out of memory!\n"); abort(); }
484 olen
= vsnprintf(buf
, len
, fmt
, 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(); }
496 static __attribute__((format(printf
,1,2))) char *SPrintf (const char *fmt
, ...) {
501 buf
= SPrintfVA(fmt
, va
);
507 static __attribute__((noreturn
)) __attribute__((format(printf
,1,2))) void die (const char *errstr
, ...) {
510 va_start(ap
, errstr
);
511 vfprintf(stderr
, errstr
, ap
);
517 static __attribute__((noreturn
)) void execsh (void) {
519 char *envshell
= getenv("SHELL");
520 char *term
= SPrintf("TERM=%s", opt_term
? opt_term
: TNAME
);
522 DEFAULT(envshell
, SHELL
);
525 args
= opt_cmd
? opt_cmd
: (char*[]){envshell
, "-i", NULL
};
526 execvp(args
[0], args
);
531 static __attribute__((noreturn
)) void sigchld (int a
) {
533 if (waitpid(pid
, &stat
, 0) < 0) die("Waiting for pid %hd failed: %s\n", pid
, SERRNO
);
534 if (WIFEXITED(stat
)) exit(WEXITSTATUS(stat
));
539 ////////////////////////////////////////////////////////////////////////////////
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) {
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 ////////////////////////////////////////////////////////////////////////////////
560 static void selinit (void) {
561 memset(&sel
.tclick1
, 0, sizeof(sel
.tclick1
));
562 memset(&sel
.tclick2
, 0, sizeof(sel
.tclick2
));
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
);
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
;
603 if (e
->xbutton
.type
== MotionNotify
) {
604 if (!IS_SET(MODE_MOUSEMOTION
) || (x
== ox
&& y
== oy
)) return;
607 } else if (e
->xbutton
.type
== ButtonRelease
|| button
== AnyButton
) {
611 if (button
>= 3) button
+= 64-3;
612 if (e
->xbutton
.type
== ButtonPress
) {
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
);
627 sel
.ex
= sel
.bx
= X2COL(e
->xbutton
.x
);
628 sel
.ey
= sel
.by
= Y2ROW(e
->xbutton
.y
);
633 static void selcopy (void) {
635 int x
, y
, bufsize
, is_selected
= 0;
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
);
652 /* \n at the end of every selected line except for the last one */
653 if (is_selected
&& y
< sel
.e
.y
) *ptr
++ = '\n';
661 static void selnotify (XEvent
*e
) {
662 ulong nitems
, ofs
, rem
;
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");
673 ttywrite((const char *)data
, nitems
*format
/8);
675 /* number of 32-bit chunks returned */
676 ofs
+= nitems
*format
/32;
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
;
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
;
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 */
720 XSetSelectionOwner(xw
.dpy
, XA_PRIMARY
, xw
.win
, CurrentTime
);
721 clipboard
= XInternAtom(xw
.dpy
, "CLIPBOARD", 0);
722 XSetSelectionOwner(xw
.dpy
, clipboard
, xw
.win
, CurrentTime
);
727 static void brelease (XEvent
*e
) {
728 if (IS_SET(MODE_MOUSE
)) {
732 if (e
->xbutton
.button
== Button2
) {
734 } else if (e
->xbutton
.button
== Button1
) {
736 getbuttoninfo(e
, NULL
, &sel
.ex
, &sel
.ey
);
737 term
.dirty
[sel
.ey
] = 1;
738 if (sel
.bx
== sel
.ex
&& sel
.by
== sel
.ey
) {
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
;
748 } else if (TIMEDIFF(now
, sel
.tclick1
) <= DOUBLECLICK_TIMEOUT
) {
749 /* double click to select word */
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
;
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
;
755 sel
.b
.y
= sel
.e
.y
= sel
.ey
;
762 memcpy(&sel
.tclick2
, &sel
.tclick1
, sizeof(struct timeval
));
763 gettimeofday(&sel
.tclick1
, NULL
);
768 static void bmotion (XEvent
*e
) {
769 if (IS_SET(MODE_MOUSE
)) {
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
);
788 ////////////////////////////////////////////////////////////////////////////////
791 static void dump (char c) {
794 fprintf(stderr, " %02x '%c' ", c, isprint(c)?c:'.');
795 if (++col % 10 == 0) fprintf(stderr, "\n");
800 static void ttynew (void) {
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()) {
807 die("fork failed\n");
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
);
822 signal(SIGCHLD
, sigchld
);
827 ////////////////////////////////////////////////////////////////////////////////
829 static int ttycanread (void) {
832 struct timeval timeout
= {0};
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;
847 static int ttycanwrite (void) {
850 struct timeval timeout
= {0};
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;
865 static void ttyread (void) {
866 static char buf
[BUFSIZ
];
867 static int buflen
= 0, obuflen
= 0;
872 /* append read bytes to unprocessed bytes */
874 left
= LEN(buf
)-buflen
;
875 //dlogf("0: ttyread before: free=%d, used=%d", left, buflen);
876 while (left
> 0 && ttycanread()) {
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
);
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");
890 fwrite(buf
+obuflen
, buflen
-obuflen
, 1, fo
);
896 while (buflen
>= UTF_SIZ
|| isfullutf8(ptr
, buflen
)) {
899 int charsize
= utf8decode(ptr
, &utf8c
); /* returns size of utf8 char in bytes */
901 utf8encode(&utf8c
, s
);
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;
922 //dlogf("0: ttyflushwrbuf before: towrite=%d", tty_wrbufused-tty_wrbufpos);
923 while (tty_wrbufpos
< tty_wrbufused
&& ttycanwrite()) {
926 if ((ret
= write(cmdfd
, tty_wrbuf
+tty_wrbufpos
, tty_wrbufused
-tty_wrbufpos
)) == -1) die("write error on tty: %s\n", SERRNO
);
929 if (tty_wrbufpos
> 0) {
930 int left
= tty_wrbufused
-tty_wrbufpos
;
933 // write buffer is empty
934 tty_wrbufpos
= tty_wrbufused
= 0;
936 memmove(tty_wrbuf
, tty_wrbuf
+tty_wrbufpos
, left
);
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");
956 if (s
!= NULL
&& 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
);
964 ttyflushwrbuf(); // make room for char
966 tty_wrbuf
[tty_wrbufused
++] = *s
++;
973 ////////////////////////////////////////////////////////////////////////////////
975 static void ttyresize (int x
, int y
) {
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 ////////////////////////////////////////////////////////////////////////////////
987 static void tsetdirt (int top
, int bot
) {
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
) {
1004 if (mode
== CURSOR_SAVE
) {
1006 } else if (mode
== CURSOR_LOAD
) {
1013 static void treset (void) {
1014 term
.c
= (TCursor
){{
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
;
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;
1047 static void tswapscreen (void) {
1048 Line
*tmp
= term
.line
;
1050 term
.line
= term
.alt
;
1052 term
.mode
^= MODE_ALTSCREEN
;
1057 static void tscrolldown (int orig
, int n
) {
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
;
1068 term
.dirty
[i
-n
] = 1;
1074 static void tscrollup (int orig
, int n
) {
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
;
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
) {
1098 if (sel
.by
< term
.top
) {
1102 if (sel
.ey
> term
.bot
) {
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
) {
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
;
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
) {
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
;
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
) {
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
) {
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
) {
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
) {
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
;
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
);
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
) {
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
);
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
) {
1255 for (i
= 0; i
< l
; ++i
) {
1258 term
.c
.attr
.mode
&= ~(ATTR_REVERSE
| ATTR_UNDERLINE
| ATTR_BOLD
);
1259 term
.c
.attr
.fg
= DefaultFG
;
1260 term
.c
.attr
.bg
= DefaultBG
;
1263 term
.c
.attr
.mode
|= ATTR_BOLD
;
1266 term
.c
.attr
.mode
|= ATTR_UNDERLINE
;
1269 term
.c
.attr
.mode
|= ATTR_REVERSE
;
1272 term
.c
.attr
.mode
&= ~ATTR_BOLD
;
1275 term
.c
.attr
.mode
&= ~ATTR_UNDERLINE
;
1278 term
.c
.attr
.mode
&= ~ATTR_REVERSE
;
1281 if (i
+ 2 < l
&& attr
[i
+ 1] == 5) {
1283 if (BETWEEN(attr
[i
], 0, 255)) {
1284 term
.c
.attr
.fg
= attr
[i
];
1286 fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[i
]);
1289 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]);
1293 term
.c
.attr
.fg
= DefaultFG
;
1296 if (i
+ 2 < l
&& attr
[i
+ 1] == 5) {
1298 if (BETWEEN(attr
[i
], 0, 255)) {
1299 term
.c
.attr
.bg
= attr
[i
];
1301 fprintf(stderr
, "erresc: bad bgcolor %d\n", attr
[i
]);
1304 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]);
1308 term
.c
.attr
.bg
= DefaultBG
;
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(); }
1322 static void tsetscroll (int t
, int b
) {
1325 LIMIT(t
, 0, term
.row
-1);
1326 LIMIT(b
, 0, term
.row
-1);
1337 ////////////////////////////////////////////////////////////////////////////////
1339 #define UPDATE_LASTWRITE do { tty_last_write_ms = mclock_ticks(); } while (0)
1342 static void csidump (void) {
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
);
1360 static void csidumpdbg (void) {
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);
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]);
1384 case 'A': /* CUU -- Cursor <n> Up */
1386 DEFAULT(escseq
.arg
[0], 1);
1387 tmoveto(term
.c
.x
, term
.c
.y
-escseq
.arg
[0]);
1389 case 'B': /* CUD -- Cursor <n> Down */
1390 DEFAULT(escseq
.arg
[0], 1);
1391 tmoveto(term
.c
.x
, term
.c
.y
+escseq
.arg
[0]);
1393 case 'C': /* CUF -- Cursor <n> Forward */
1395 DEFAULT(escseq
.arg
[0], 1);
1396 tmoveto(term
.c
.x
+escseq
.arg
[0], term
.c
.y
);
1398 case 'D': /* CUB -- Cursor <n> Backward */
1399 DEFAULT(escseq
.arg
[0], 1);
1400 tmoveto(term
.c
.x
-escseq
.arg
[0], term
.c
.y
);
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]);
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]);
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
);
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);
1421 /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */
1422 case 'J': /* ED -- Clear screen */
1424 switch (escseq
.arg
[0]) {
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);
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
);
1434 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1440 case 'K': /* EL -- Clear line */
1441 switch (escseq
.arg
[0]) {
1443 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
);
1446 tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
);
1449 tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
);
1453 case 'S': /* SU -- Scroll <n> line up */
1454 DEFAULT(escseq
.arg
[0], 1);
1455 tscrollup(term
.top
, escseq
.arg
[0]);
1457 case 'T': /* SD -- Scroll <n> line down */
1458 DEFAULT(escseq
.arg
[0], 1);
1459 tscrolldown(term
.top
, escseq
.arg
[0]);
1461 case 'L': /* IL -- Insert <n> blank lines */
1462 DEFAULT(escseq
.arg
[0], 1);
1463 tinsertblankline(escseq
.arg
[0]);
1465 case 'l': /* RM -- Reset Mode */
1467 switch (escseq
.arg
[0]) {
1469 term
.mode
&= ~MODE_APPKEYPAD
;
1471 case 5: /* DECSCNM -- Remove reverse video */
1472 if (IS_SET(MODE_REVERSE
)) {
1473 term
.mode
&= ~MODE_REVERSE
;
1477 case 7: /* autowrap off */
1478 term
.mode
&= ~MODE_WRAP
;
1480 case 12: /* att610 -- Stop blinking cursor (IGNORED) */
1482 case 20: /* non-standard code? */
1483 term
.mode
&= ~MODE_CRLF
;
1485 case 25: /* hide cursor */
1486 term
.c
.state
|= CURSOR_HIDE
;
1488 case 1000: /* disable X11 xterm mouse reporting */
1489 #ifndef MOUSE_REPORTING_ALWAYS
1490 term
.mode
&= ~MODE_MOUSEBTN
;
1494 #ifndef MOUSE_REPORTING_ALWAYS
1495 term
.mode
&= ~MODE_MOUSEMOTION
;
1498 case 1049: /* = 1047 and 1048 */
1501 if (IS_SET(MODE_ALTSCREEN
)) {
1502 tclearregion(0, 0, term
.col
-1, term
.row
-1);
1505 if (escseq
.arg
[0] != 1049) break;
1507 tcursor(CURSOR_LOAD
);
1513 switch (escseq
.arg
[0]) {
1515 term
.mode
&= ~MODE_INSERT
;
1522 case 'M': /* DL -- Delete <n> lines */
1523 DEFAULT(escseq
.arg
[0], 1);
1524 tdeleteline(escseq
.arg
[0]);
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
);
1530 case 'P': /* DCH -- Delete <n> char */
1531 DEFAULT(escseq
.arg
[0], 1);
1532 tdeletechar(escseq
.arg
[0]);
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);
1539 case 'h': /* SM -- Set terminal mode */
1541 switch (escseq
.arg
[0]) {
1543 term
.mode
|= MODE_APPKEYPAD
;
1545 case 5: /* DECSCNM -- Reverve video */
1546 if (!IS_SET(MODE_REVERSE
)) {
1547 term
.mode
|= MODE_REVERSE
;
1552 term
.mode
|= MODE_WRAP
;
1555 term
.mode
|= MODE_CRLF
;
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;
1561 term
.c
.state
&= ~CURSOR_HIDE
;
1563 case 1000: /* 1000,1002: enable xterm mouse report */
1564 #ifndef MOUSE_REPORTING_ALWAYS
1565 term
.mode
|= MODE_MOUSEBTN
;
1569 #ifndef MOUSE_REPORTING_ALWAYS
1570 term
.mode
|= MODE_MOUSEMOTION
;
1573 case 1049: /* = 1047 and 1048 */
1576 if (IS_SET(MODE_ALTSCREEN
)) tclearregion(0, 0, term
.col
-1, term
.row
-1); else tswapscreen();
1577 if (escseq
.arg
[0] != 1049) break;
1579 tcursor(CURSOR_SAVE
);
1581 default: goto unknown
;
1584 switch (escseq
.arg
[0]) {
1586 term
.mode
|= MODE_INSERT
;
1588 default: goto unknown
;
1592 case 'm': /* SGR -- Terminal attribute (color) */
1593 tsetattr(escseq
.arg
, escseq
.narg
);
1595 case 'r': /* DECSTBM -- Set Scrolling Region */
1599 DEFAULT(escseq
.arg
[0], 1);
1600 DEFAULT(escseq
.arg
[1], term
.row
);
1601 tsetscroll(escseq
.arg
[0]-1, escseq
.arg
[1]-1);
1605 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1606 tcursor(CURSOR_SAVE
);
1608 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1609 tcursor(CURSOR_LOAD
);
1613 fprintf(stderr
, "erresc: unknown csi ");
1616 dlogf("erresc: unknown csi ");
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
) {
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
) {
1651 /* TODO: handle other OSC */
1652 } else if (term
.esc
& ESC_OSC
) {
1655 term
.esc
= ESC_START
| ESC_TITLE
;
1657 } else if (term
.esc
& ESC_TITLE
) {
1658 if (ascii
== '\a' || term
.titlelen
+1 >= ESC_TITLE_SIZ
) {
1660 term
.title
[term
.titlelen
] = '\0';
1661 XStoreName(xw
.dpy
, xw
.win
, term
.title
);
1663 term
.title
[term
.titlelen
++] = ascii
;
1665 } else if (term
.esc
& ESC_ALTCHARSET
) {
1667 case '0': /* Line drawing crap */
1668 term
.c
.attr
.mode
|= ATTR_GFX
;
1670 case 'B': /* Back to regular text */
1671 term
.c
.attr
.mode
&= ~ATTR_GFX
;
1674 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
);
1675 term
.c
.attr
.mode
&= ~ATTR_GFX
;
1678 } else if (term
.esc
& ESC_HASH
) {
1680 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
1681 tfillregionwithE(0, 0, term
.col
-1, term
.row
-1);
1686 } else if (term
.esc
& ESC_PERCENT
) {
1691 term
.esc
|= ESC_CSI
;
1694 term
.esc
|= ESC_OSC
;
1697 term
.esc
|= ESC_ALTCHARSET
;
1700 term
.esc
|= ESC_HASH
;
1703 term
.esc
|= ESC_PERCENT
;
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);
1710 case 'E': /* NEL -- Next line */
1711 tnewline(1); /* always go to first col */
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);
1720 case 'c': /* RIS -- Reset to inital state */
1725 case '=': /* DECPAM -- Application keypad */
1726 term
.mode
|= MODE_APPKEYPAD
;
1729 case '>': /* DECPNM -- Normal keypad */
1730 term
.mode
&= ~MODE_APPKEYPAD
;
1733 case '7': /* DECSC -- Save Cursor */
1734 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
1736 tcursor(CURSOR_SAVE
);
1739 case '8': /* DECRC -- Restore Cursor */
1741 tcursor(CURSOR_LOAD
);
1745 case 'Z': /* DEC private identification */
1746 ttywrite("\x1b[?1;2c", 7);
1755 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n", (uchar
)ascii
, isprint(ascii
)?ascii
:'.');
1760 if (sel
.bx
!= -1 && BETWEEN(term
.c
.y
, sel
.by
, sel
.ey
)) sel
.bx
= -1;
1767 tmoveto(term
.c
.x
-1, term
.c
.y
);
1771 tmoveto(0, term
.c
.y
);
1777 /* go to first col if the mode is set */
1778 tnewline(IS_SET(MODE_CRLF
));
1782 //if (!(xw.state & WIN_FOCUSED)) xseturgency(1);
1786 term
.esc
= ESC_START
;
1789 if (term
.c
.attr
.mode
&ATTR_GFX
) {
1793 if (cc
< 32 || cc
>= 127) break; //FIXME: nothing at all
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 */
1799 if (term
.c
.x
+1 < term
.col
) tmoveto(term
.c
.x
+1, term
.c
.y
); else term
.c
.state
|= CURSOR_WRAPNEXT
;
1807 ////////////////////////////////////////////////////////////////////////////////
1809 static int tresize (int col
, int row
) {
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 */
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
) {
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
) {
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
) {
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
) {
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);
1864 static void xresize (int col
, int row
) {
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
);
1890 ////////////////////////////////////////////////////////////////////////////////
1891 // x11 drawing and utils
1892 static void xloadcols (void) {
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
)) {
1901 fprintf(stderr
, "Could not allocate color '%s'\n", colorname
[i
]);
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
)) {
1915 fprintf(stderr
, "Could not allocate color %d\n", i
);
1917 dc
.col
[i
] = color
.pixel
;
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
)) {
1927 fprintf(stderr
, "Could not allocate color %d\n", i
);
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};
1945 .flags
= PSize
| PResizeInc
| PBaseSize
,
1948 .height_inc
= xw
.ch
,
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
) {
1959 char *def
, **missing
;
1963 set
= XCreateFontSet(xw
.dpy
, fontstr
, &missing
, &n
, &def
);
1965 while (n
--) fprintf(stderr
, "st: missing fontset: %s\n", missing
[n
]);
1966 XFreeStringList(missing
);
1972 static void xgetfontinfo (XFontSet set
, int *ascent
, int *descent
, short *lbearing
, short *rbearing
) {
1973 XFontStruct
**xfonts
;
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
);
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
;
2001 if (!(xw
.dpy
= XOpenDisplay(NULL
))) die("Can't open display\n");
2002 xw
.scr
= XDefaultScreen(xw
.dpy
);
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
;
2009 xw
.cmap
= XDefaultColormap(xw
.dpy
, xw
.scr
);
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
2031 xw
.buf
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.bufw
, xw
.bufh
, XDefaultDepth(xw
.dpy
, xw
.scr
));
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
);
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
);
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
;
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
;
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];
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;
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
);
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
;
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
) {
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
);
2149 base
= term
.line
[y
][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
);
2158 if (/*new.state & GLYPH_SET*/ 1) {
2163 if (new.mode
& ATTR_GFX
) {
2166 sl
= utf8size(new.c
);
2168 memcpy(buf
+ib
, new.c
, sl
);
2173 if (ib
> 0) xdraws(buf
, &base
, ox
, y
, ic
, ib
);
2174 xcopy(0, y
, term
.col
, 1);
2180 static void expose (XEvent
*ev
) {
2181 XExposeEvent
*e
= &ev
->xexpose
;
2182 if (xw
.state
& WIN_REDRAW
) {
2184 xw
.state
&= ~WIN_REDRAW
;
2185 xcopy(0, 0, term
.col
, term
.row
);
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
);
2215 static void focus (XEvent
*ev
) {
2216 if (ev
->type
== FocusIn
) {
2217 xw
.state
|= WIN_FOCUSED
;
2220 xw
.state
&= ~WIN_FOCUSED
;
2226 ////////////////////////////////////////////////////////////////////////////////
2228 static const char *kmap (KeySym k
, uint state
) {
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
;
2238 static void kpress (XEvent
*ev
) {
2239 XKeyEvent
*e
= &ev
->xkey
;
2242 const char *customkey
;
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) */
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
]);
2266 if (shift
) selpaste();
2269 if (IS_SET(MODE_CRLF
)) ttywrite("\r\n", 2); else ttywrite("\r", 1);
2274 if (meta
&& len
== 1) ttywrite("\033", 1);
2283 ////////////////////////////////////////////////////////////////////////////////
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
;
2291 } else if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_OUT
) {
2292 xw
.state
&= ~WIN_FOCUSED
;
2299 ////////////////////////////////////////////////////////////////////////////////
2300 static void resize (XEvent
*e
) {
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
);
2315 static bool last_draw_too_old (void) {
2319 gettimeofday(&now, NULL);
2320 return TIMEDIFF(now, xw.lastdraw) >= DRAW_TIMEOUT/1000;
2322 return (mclock_ticks()-tty_last_write_ms
>= DRAW_TIMEOUT
);
2326 ////////////////////////////////////////////////////////////////////////////////
2328 static void run (void) {
2329 bool stuff_to_print
= 0;
2330 int xfd
= XConnectionNumber(xw
.dpy
);
2335 struct timeval timeout
;
2339 FD_SET(cmdfd
, &rfd
);
2341 // have something to write?
2342 if (tty_wrbufpos
< tty_wrbufused
) FD_SET(cmdfd
, &wfd
);
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");
2355 //dlogf("flushing write buffer done");
2358 if (FD_ISSET(cmdfd
, &rfd
)) {
2359 //dlogf("reading from tty");
2362 //dlogf("reading from tty done");
2365 if (stuff_to_print
) {
2366 if (last_draw_too_old()) {
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
[]) {
2392 for (int i
= 1; i
< argc
; i
++) {
2393 switch (argv
[i
][0] != '-' || argv
[i
][2] ? -1 : argv
[i
][1]) {
2395 if (++i
< argc
) opt_title
= argv
[i
];
2398 if (++i
< argc
) opt_class
= argv
[i
];
2401 if (++i
< argc
) opt_embed
= argv
[i
];
2404 /* eat every remaining arguments */
2405 if (++i
< argc
) opt_cmd
= &argv
[i
];
2408 if (++i
< argc
) opt_term
= argv
[i
];
2418 setlocale(LC_CTYPE
, "");