1 /* See LICENSE for licence details. */ 
   2 #define _XOPEN_SOURCE 600 
  14 #include <sys/ioctl.h> 
  15 #include <sys/select.h> 
  18 #include <sys/types.h> 
  22 #include <X11/Xatom.h> 
  24 #include <X11/Xutil.h> 
  25 #include <X11/cursorfont.h> 
  26 #include <X11/keysym.h> 
  30 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 
  32 #elif defined(__FreeBSD__) || defined(__DragonFly__) 
  37         "st " VERSION " (c) 2010-2012 st engineers\n" \ 
  38         "usage: st [-t title] [-c class] [-w windowid] [-v] [-e command...]\n" 
  41 #define XEMBED_FOCUS_IN  4 
  42 #define XEMBED_FOCUS_OUT 5 
  45 #define ESC_TITLE_SIZ 256 
  46 #define ESC_BUF_SIZ   256 
  47 #define ESC_ARG_SIZ   16 
  48 #define DRAW_BUF_SIZ  1024 
  50 #define XK_NO_MOD     UINT_MAX 
  53 #define SELECT_TIMEOUT (20*1000) /* 20 ms */ 
  54 #define DRAW_TIMEOUT  (20*1000) /* 20 ms */ 
  56 #define SERRNO strerror(errno) 
  57 #define MIN(a, b)  ((a) < (b) ? (a) : (b)) 
  58 #define MAX(a, b)  ((a) < (b) ? (b) : (a)) 
  59 #define LEN(a)     (sizeof(a) / sizeof(a[0])) 
  60 #define DEFAULT(a, b)     (a) = (a) ? (a) : (b) 
  61 #define BETWEEN(x, a, b)  ((a) <= (x) && (x) <= (b)) 
  62 #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 
  63 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg) 
  64 #define IS_SET(flag) (term.mode & (flag)) 
  65 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + (t1.tv_usec-t2.tv_usec)/1000) 
  66 #define X2COL(x) (((x) - BORDER)/xw.cw) 
  67 #define Y2ROW(y) (((y) - BORDER)/xw.ch) 
  69 /* Attribute, Cursor, Character state, Terminal mode, Screen draw mode */ 
  70 enum { ATTR_NULL
=0 , ATTR_REVERSE
=1 , ATTR_UNDERLINE
=2, ATTR_BOLD
=4, ATTR_GFX
=8 }; 
  71 enum { CURSOR_UP
, CURSOR_DOWN
, CURSOR_LEFT
, CURSOR_RIGHT
, 
  72        CURSOR_SAVE
, CURSOR_LOAD 
}; 
  73 enum { CURSOR_DEFAULT 
= 0, CURSOR_HIDE 
= 1, CURSOR_WRAPNEXT 
= 2 }; 
  74 enum { GLYPH_SET
=1, GLYPH_DIRTY
=2 }; 
  75 enum { MODE_WRAP
=1, MODE_INSERT
=2, MODE_APPKEYPAD
=4, MODE_ALTSCREEN
=8, 
  76        MODE_CRLF
=16, MODE_MOUSEBTN
=32, MODE_MOUSEMOTION
=64, MODE_MOUSE
=32|64, MODE_REVERSE
=128 }; 
  77 enum { ESC_START
=1, ESC_CSI
=2, ESC_OSC
=4, ESC_TITLE
=8, ESC_ALTCHARSET
=16 }; 
  78 enum { WIN_VISIBLE
=1, WIN_REDRAW
=2, WIN_FOCUSED
=4 }; 
  81 enum { B0
=1, B1
=2, B2
=4, B3
=8, B4
=16, B5
=32, B6
=64, B7
=128 }; 
  83 typedef unsigned char uchar
; 
  84 typedef unsigned int uint
; 
  85 typedef unsigned long ulong
; 
  86 typedef unsigned short ushort
; 
  89         char c
[UTF_SIZ
];     /* character code */ 
  90         uchar mode
;  /* attribute flags */ 
  91         ushort fg
;   /* foreground  */ 
  92         ushort bg
;   /* background  */ 
  93         uchar state
; /* state flags    */ 
  99         Glyph attr
;      /* current char attributes */ 
 105 /* CSI Escape sequence structs */ 
 106 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */ 
 108         char buf
[ESC_BUF_SIZ
]; /* raw string */ 
 109         int len
;               /* raw string length */ 
 111         int arg
[ESC_ARG_SIZ
]; 
 112         int narg
;              /* nb of args */ 
 116 /* Internal representation of the screen */ 
 118         int row
;        /* nb row */ 
 119         int col
;        /* nb col */ 
 120         Line
* line
;     /* screen */ 
 121         Line
* alt
;      /* alternate screen */ 
 122         bool* dirty
; /* dirtyness of lines */ 
 123         TCursor c
;      /* cursor */ 
 124         int top
;        /* top    scroll limit */ 
 125         int bot
;        /* bottom scroll limit */ 
 126         int mode
;       /* terminal mode flags */ 
 127         int esc
;        /* escape state flags */ 
 128         char title
[ESC_TITLE_SIZ
]; 
 132 /* Purely graphic info */ 
 142         int w
;  /* window width */ 
 143         int h
;  /* window height */ 
 144         int bufw
; /* pixmap width  */ 
 145         int bufh
; /* pixmap height */ 
 146         int ch
; /* char height */ 
 147         int cw
; /* char width  */ 
 148         char state
; /* focus, redraw, visible */ 
 149         struct timeval lastdraw
; 
 159 /* TODO: use better name for vars... */ 
 164         struct {int x
, y
;} b
, e
; 
 167         struct timeval tclick1
; 
 168         struct timeval tclick2
; 
 173 /* Drawing Context */ 
 175         ulong col
[LEN(colorname
) < 256 ? 256 : LEN(colorname
)]; 
 186 static void die(const char*, ...); 
 187 static void draw(void); 
 188 static void drawregion(int, int, int, int); 
 189 static void execsh(void); 
 190 static void sigchld(int); 
 191 static void run(void); 
 192 static bool last_draw_too_old(void); 
 194 static void csidump(void); 
 195 static void csihandle(void); 
 196 static void csiparse(void); 
 197 static void csireset(void); 
 199 static void tclearregion(int, int, int, int); 
 200 static void tcursor(int); 
 201 static void tdeletechar(int); 
 202 static void tdeleteline(int); 
 203 static void tinsertblank(int); 
 204 static void tinsertblankline(int); 
 205 static void tmoveto(int, int); 
 206 static void tnew(int, int); 
 207 static void tnewline(int); 
 208 static void tputtab(void); 
 209 static void tputc(char*); 
 210 static void treset(void); 
 211 static int tresize(int, int); 
 212 static void tscrollup(int, int); 
 213 static void tscrolldown(int, int); 
 214 static void tsetattr(int*, int); 
 215 static void tsetchar(char*); 
 216 static void tsetscroll(int, int); 
 217 static void tswapscreen(void); 
 218 static void tfulldirt(void); 
 220 static void ttynew(void); 
 221 static void ttyread(void); 
 222 static void ttyresize(int, int); 
 223 static void ttywrite(const char *, size_t); 
 225 static void xdraws(char *, Glyph
, int, int, int, int); 
 226 static void xhints(void); 
 227 static void xclear(int, int, int, int); 
 228 static void xcopy(int, int, int, int); 
 229 static void xdrawcursor(void); 
 230 static void xinit(void); 
 231 static void xloadcols(void); 
 232 static void xseturgency(int); 
 233 static void xsetsel(char*); 
 234 static void xresize(int, int); 
 236 static void expose(XEvent 
*); 
 237 static void visibility(XEvent 
*); 
 238 static void unmap(XEvent 
*); 
 239 static char* kmap(KeySym
, uint
); 
 240 static void kpress(XEvent 
*); 
 241 static void cmessage(XEvent 
*); 
 242 static void resize(XEvent 
*); 
 243 static void focus(XEvent 
*); 
 244 static void brelease(XEvent 
*); 
 245 static void bpress(XEvent 
*); 
 246 static void bmotion(XEvent 
*); 
 247 static void selnotify(XEvent 
*); 
 248 static void selrequest(XEvent 
*); 
 250 static void selinit(void); 
 251 static inline bool selected(int, int); 
 252 static void selcopy(void); 
 253 static void selpaste(); 
 254 static void selscroll(int, int); 
 256 static int utf8decode(char *, long *); 
 257 static int utf8encode(long *, char *); 
 258 static int utf8size(char *); 
 259 static int isfullutf8(char *, int); 
 261 static void (*handler
[LASTEvent
])(XEvent 
*) = { 
 263         [ClientMessage
] = cmessage
, 
 264         [ConfigureNotify
] = resize
, 
 265         [VisibilityNotify
] = visibility
, 
 266         [UnmapNotify
] = unmap
, 
 270         [MotionNotify
] = bmotion
, 
 271         [ButtonPress
] = bpress
, 
 272         [ButtonRelease
] = brelease
, 
 273         [SelectionNotify
] = selnotify
, 
 274         [SelectionRequest
] = selrequest
, 
 281 static CSIEscape escseq
; 
 284 static Selection sel
; 
 285 static char **opt_cmd  
= NULL
; 
 286 static char *opt_title 
= NULL
; 
 287 static char *opt_embed 
= NULL
; 
 288 static char *opt_class 
= NULL
; 
 291 utf8decode(char *s
, long *u
) { 
 297         if(~c 
& B7
) { /* 0xxxxxxx */ 
 300         } else if((c 
& (B7
|B6
|B5
)) == (B7
|B6
)) { /* 110xxxxx */ 
 301                 *u 
= c
&(B4
|B3
|B2
|B1
|B0
); 
 303         } else if((c 
& (B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) { /* 1110xxxx */ 
 304                 *u 
= c
&(B3
|B2
|B1
|B0
); 
 306         } else if((c 
& (B7
|B6
|B5
|B4
|B3
)) == (B7
|B6
|B5
|B4
)) { /* 11110xxx */ 
 311         for(i 
= n
, ++s
; i 
> 0; --i
, ++rtn
, ++s
) { 
 313                 if((c 
& (B7
|B6
)) != B7
) /* 10xxxxxx */ 
 316                 *u 
|= c 
& (B5
|B4
|B3
|B2
|B1
|B0
); 
 318         if((n 
== 1 && *u 
< 0x80) || 
 319            (n 
== 2 && *u 
< 0x800) || 
 320            (n 
== 3 && *u 
< 0x10000) || 
 321            (*u 
>= 0xD800 && *u 
<= 0xDFFF)) 
 330 utf8encode(long *u
, char *s
) { 
 338                 *sp 
= uc
; /* 0xxxxxxx */ 
 340         } else if(*u 
< 0x800) { 
 341                 *sp 
= (uc 
>> 6) | (B7
|B6
); /* 110xxxxx */ 
 343         } else if(uc 
< 0x10000) { 
 344                 *sp 
= (uc 
>> 12) | (B7
|B6
|B5
); /* 1110xxxx */ 
 346         } else if(uc 
<= 0x10FFFF) { 
 347                 *sp 
= (uc 
>> 18) | (B7
|B6
|B5
|B4
); /* 11110xxx */ 
 352         for(i
=n
,++sp
; i
>0; --i
,++sp
) 
 353                 *sp 
= ((uc 
>> 6*(i
-1)) & (B5
|B4
|B3
|B2
|B1
|B0
)) | B7
; /* 10xxxxxx */ 
 363 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode 
 364    UTF-8 otherwise return 0 */ 
 366 isfullutf8(char *s
, int b
) { 
 374         else if((*c1
&(B7
|B6
|B5
)) == (B7
|B6
) && b 
== 1) 
 376         else if((*c1
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
) && 
 378             ((b 
== 2) && (*c2
&(B7
|B6
)) == B7
))) 
 380         else if((*c1
&(B7
|B6
|B5
|B4
|B3
)) == (B7
|B6
|B5
|B4
) && 
 382             ((b 
== 2) && (*c2
&(B7
|B6
)) == B7
) || 
 383             ((b 
== 3) && (*c2
&(B7
|B6
)) == B7 
&& (*c3
&(B7
|B6
)) == B7
))) 
 395         else if((c
&(B7
|B6
|B5
)) == (B7
|B6
)) 
 397         else if((c
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) 
 405         sel
.tclick1
.tv_sec 
= 0; 
 406         sel
.tclick1
.tv_usec 
= 0; 
 410         sel
.xtarget 
= XInternAtom(xw
.dpy
, "UTF8_STRING", 0); 
 411         if(sel
.xtarget 
== None
) 
 412                 sel
.xtarget 
= XA_STRING
; 
 416 selected(int x
, int y
) { 
 417         if(sel
.ey 
== y 
&& sel
.by 
== y
) { 
 418                 int bx 
= MIN(sel
.bx
, sel
.ex
); 
 419                 int ex 
= MAX(sel
.bx
, sel
.ex
); 
 420                 return BETWEEN(x
, bx
, ex
); 
 422         return ((sel
.b
.y 
< y
&&y 
< sel
.e
.y
) || (y
==sel
.e
.y 
&& x
<=sel
.e
.x
)) 
 423                 || (y
==sel
.b
.y 
&& x
>=sel
.b
.x 
&& (x
<=sel
.e
.x 
|| sel
.b
.y
!=sel
.e
.y
)); 
 427 getbuttoninfo(XEvent 
*e
, int *b
, int *x
, int *y
) { 
 429                 *b 
= e
->xbutton
.button
; 
 431         *x 
= X2COL(e
->xbutton
.x
); 
 432         *y 
= Y2ROW(e
->xbutton
.y
); 
 433         sel
.b
.x 
= sel
.by 
< sel
.ey 
? sel
.bx 
: sel
.ex
; 
 434         sel
.b
.y 
= MIN(sel
.by
, sel
.ey
); 
 435         sel
.e
.x 
= sel
.by 
< sel
.ey 
? sel
.ex 
: sel
.bx
; 
 436         sel
.e
.y 
= MAX(sel
.by
, sel
.ey
); 
 440 mousereport(XEvent 
*e
) { 
 441         int x 
= X2COL(e
->xbutton
.x
); 
 442         int y 
= Y2ROW(e
->xbutton
.y
); 
 443         int button 
= e
->xbutton
.button
; 
 444         int state 
= e
->xbutton
.state
; 
 445         char buf
[] = { '\033', '[', 'M', 0, 32+x
+1, 32+y
+1 }; 
 446         static int ob
, ox
, oy
; 
 449         if(e
->xbutton
.type 
== MotionNotify
) { 
 450                 if(!IS_SET(MODE_MOUSEMOTION
) || (x 
== ox 
&& y 
== oy
)) 
 454         } else if(e
->xbutton
.type 
== ButtonRelease 
|| button 
== AnyButton
) { 
 460                 if(e
->xbutton
.type 
== ButtonPress
) { 
 466         buf
[3] = 32 + button 
+ (state 
& ShiftMask 
? 4 : 0) 
 467                 + (state 
& Mod4Mask    
? 8  : 0) 
 468                 + (state 
& ControlMask 
? 16 : 0); 
 470         ttywrite(buf
, sizeof(buf
)); 
 475         if(IS_SET(MODE_MOUSE
)) 
 477         else if(e
->xbutton
.button 
== Button1
) { 
 479                         for(int i
=sel
.b
.y
; i
<=sel
.e
.y
; i
++) 
 482                 sel
.ex 
= sel
.bx 
= X2COL(e
->xbutton
.x
); 
 483                 sel
.ey 
= sel
.by 
= Y2ROW(e
->xbutton
.y
); 
 490         int x
, y
, sz
, sl
, ls 
= 0; 
 495                 sz 
= (term
.col
+1) * (sel
.e
.y
-sel
.b
.y
+1) * UTF_SIZ
; 
 496                 ptr 
= str 
= malloc(sz
); 
 497                 for(y 
= 0; y 
< term
.row
; y
++) { 
 498                         for(x 
= 0; x 
< term
.col
; x
++) 
 499                                 if(term
.line
[y
][x
].state 
& GLYPH_SET 
&& (ls 
= selected(x
, y
))) { 
 500                                         sl 
= utf8size(term
.line
[y
][x
].c
); 
 501                                         memcpy(ptr
, term
.line
[y
][x
].c
, sl
); 
 504                         if(ls 
&& y 
< sel
.e
.y
) 
 513 selnotify(XEvent 
*e
) { 
 514         ulong nitems
, ofs
, rem
; 
 521                 if(XGetWindowProperty(xw
.dpy
, xw
.win
, XA_PRIMARY
, ofs
, BUFSIZ
/4, 
 522                                         False
, AnyPropertyType
, &type
, &format
, 
 523                                         &nitems
, &rem
, &data
)) { 
 524                         fprintf(stderr
, "Clipboard allocation failed\n"); 
 527                 ttywrite((const char *) data
, nitems 
* format 
/ 8); 
 529                 /* number of 32-bit chunks returned */ 
 530                 ofs 
+= nitems 
* format 
/ 32; 
 536         XConvertSelection(xw
.dpy
, XA_PRIMARY
, sel
.xtarget
, XA_PRIMARY
, xw
.win
, CurrentTime
); 
 540 selrequest(XEvent 
*e
) { 
 541         XSelectionRequestEvent 
*xsre
; 
 545         xsre 
= (XSelectionRequestEvent 
*) e
; 
 546         xev
.type 
= SelectionNotify
; 
 547         xev
.requestor 
= xsre
->requestor
; 
 548         xev
.selection 
= xsre
->selection
; 
 549         xev
.target 
= xsre
->target
; 
 550         xev
.time 
= xsre
->time
; 
 554         xa_targets 
= XInternAtom(xw
.dpy
, "TARGETS", 0); 
 555         if(xsre
->target 
== xa_targets
) { 
 556                 /* respond with the supported type */ 
 557                 Atom string 
= sel
.xtarget
; 
 558                 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, 
 559                                 XA_ATOM
, 32, PropModeReplace
, 
 560                                 (uchar 
*) &string
, 1); 
 561                 xev
.property 
= xsre
->property
; 
 562         } else if(xsre
->target 
== sel
.xtarget 
&& sel
.clip 
!= NULL
) { 
 563                 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, 
 564                                 xsre
->target
, 8, PropModeReplace
, 
 565                                 (uchar 
*) sel
.clip
, strlen(sel
.clip
)); 
 566                 xev
.property 
= xsre
->property
; 
 569         /* all done, send a notification to the listener */ 
 570         if(!XSendEvent(xsre
->display
, xsre
->requestor
, True
, 0, (XEvent 
*) &xev
)) 
 571                 fprintf(stderr
, "Error sending SelectionNotify event\n"); 
 576         /* register the selection for both the clipboard and the primary */ 
 582         XSetSelectionOwner(xw
.dpy
, XA_PRIMARY
, xw
.win
, CurrentTime
); 
 584         clipboard 
= XInternAtom(xw
.dpy
, "CLIPBOARD", 0); 
 585         XSetSelectionOwner(xw
.dpy
, clipboard
, xw
.win
, CurrentTime
); 
 591 brelease(XEvent 
*e
) { 
 592         if(IS_SET(MODE_MOUSE
)) { 
 596         if(e
->xbutton
.button 
== Button2
) 
 598         else if(e
->xbutton
.button 
== Button1
) { 
 600                 getbuttoninfo(e
, NULL
, &sel
.ex
, &sel
.ey
); 
 601                 term
.dirty
[sel
.ey
] = 1; 
 602                 if(sel
.bx 
== sel
.ex 
&& sel
.by 
== sel
.ey
) { 
 605                         gettimeofday(&now
, NULL
); 
 607                         if(TIMEDIFF(now
, sel
.tclick2
) <= TRIPLECLICK_TIMEOUT
) { 
 608                                 /* triple click on the line */ 
 609                                 sel
.b
.x 
= sel
.bx 
= 0; 
 610                                 sel
.e
.x 
= sel
.ex 
= term
.col
; 
 611                                 sel
.b
.y 
= sel
.e
.y 
= sel
.ey
; 
 613                         } else if(TIMEDIFF(now
, sel
.tclick1
) <= DOUBLECLICK_TIMEOUT
) { 
 614                                 /* double click to select word */ 
 616                                 while(sel
.bx 
> 0 && term
.line
[sel
.ey
][sel
.bx
-1].state 
& GLYPH_SET 
&& 
 617                                           term
.line
[sel
.ey
][sel
.bx
-1].c
[0] != ' ') sel
.bx
--; 
 619                                 while(sel
.ex 
< term
.col
-1 && term
.line
[sel
.ey
][sel
.ex
+1].state 
& GLYPH_SET 
&& 
 620                                           term
.line
[sel
.ey
][sel
.ex
+1].c
[0] != ' ') sel
.ex
++; 
 622                                 sel
.b
.y 
= sel
.e
.y 
= sel
.ey
; 
 628         memcpy(&sel
.tclick2
, &sel
.tclick1
, sizeof(struct timeval
)); 
 629         gettimeofday(&sel
.tclick1
, NULL
); 
 635         if(IS_SET(MODE_MOUSE
)) { 
 640                 int oldey 
= sel
.ey
, oldex 
= sel
.ex
; 
 641                 getbuttoninfo(e
, NULL
, &sel
.ex
, &sel
.ey
); 
 643                 if(oldey 
!= sel
.ey 
|| oldex 
!= sel
.ex
) { 
 644                         int starty 
= MIN(oldey
, sel
.ey
); 
 645                         int endy 
= MAX(oldey
, sel
.ey
); 
 646                         for(int i 
= starty
; i 
<= endy
; i
++) 
 654 die(const char *errstr
, ...) { 
 657         va_start(ap
, errstr
); 
 658         vfprintf(stderr
, errstr
, ap
); 
 666         char *envshell 
= getenv("SHELL"); 
 668         DEFAULT(envshell
, "sh"); 
 669         putenv("TERM="TNAME
); 
 670         args 
= opt_cmd 
? opt_cmd 
: (char*[]){envshell
, "-i", NULL
}; 
 671         execvp(args
[0], args
); 
 678         if(waitpid(pid
, &stat
, 0) < 0) 
 679                 die("Waiting for pid %hd failed: %s\n", pid
, SERRNO
); 
 681                 exit(WEXITSTATUS(stat
)); 
 690         /* seems to work fine on linux, openbsd and freebsd */ 
 691         struct winsize w 
= {term
.row
, term
.col
, 0, 0}; 
 692         if(openpty(&m
, &s
, NULL
, NULL
, &w
) < 0) 
 693                 die("openpty failed: %s\n", SERRNO
); 
 695         switch(pid 
= fork()) { 
 697                 die("fork failed\n"); 
 700                 setsid(); /* create a new process group */ 
 701                 dup2(s
, STDIN_FILENO
); 
 702                 dup2(s
, STDOUT_FILENO
); 
 703                 dup2(s
, STDERR_FILENO
); 
 704                 if(ioctl(s
, TIOCSCTTY
, NULL
) < 0) 
 705                         die("ioctl TIOCSCTTY failed: %s\n", SERRNO
); 
 713                 signal(SIGCHLD
, sigchld
); 
 720         fprintf(stderr
, " %02x '%c' ", c
, isprint(c
)?c
:'.'); 
 722                 fprintf(stderr
, "\n"); 
 727         static char buf
[BUFSIZ
]; 
 728         static int buflen 
= 0; 
 731         int charsize
; /* size of utf8 char in bytes */ 
 735         /* append read bytes to unprocessed bytes */ 
 736         if((ret 
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0) 
 737                 die("Couldn't read from shell: %s\n", SERRNO
); 
 739         /* process every complete utf8 char */ 
 742         while(buflen 
>= UTF_SIZ 
|| isfullutf8(ptr
,buflen
)) { 
 743                 charsize 
= utf8decode(ptr
, &utf8c
); 
 744                 utf8encode(&utf8c
, s
); 
 750         /* keep any uncomplete utf8 char for the next call */ 
 751         memmove(buf
, ptr
, buflen
); 
 755 ttywrite(const char *s
, size_t n
) { 
 756         if(write(cmdfd
, s
, n
) == -1) 
 757                 die("write error on tty: %s\n", SERRNO
); 
 761 ttyresize(int x
, int y
) { 
 766         w
.ws_xpixel 
= w
.ws_ypixel 
= 0; 
 767         if(ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0) 
 768                 fprintf(stderr
, "Couldn't set window size: %s\n", SERRNO
); 
 775         for(i 
= 0; i 
< term
.row
; i
++) 
 783         if(mode 
== CURSOR_SAVE
) 
 785         else if(mode 
== CURSOR_LOAD
) 
 786                 term
.c 
= c
, tmoveto(c
.x
, c
.y
); 
 795         }, .x 
= 0, .y 
= 0, .state 
= CURSOR_DEFAULT
}; 
 797         term
.top 
= 0, term
.bot 
= term
.row 
- 1; 
 798         term
.mode 
= MODE_WRAP
; 
 799         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
 803 tnew(int col
, int row
) { 
 804         /* set screen size */ 
 805         term
.row 
= row
, term
.col 
= col
; 
 806         term
.line 
= malloc(term
.row 
* sizeof(Line
)); 
 807         term
.alt  
= malloc(term
.row 
* sizeof(Line
)); 
 808         term
.dirty 
= malloc(term
.row 
* sizeof(*term
.dirty
)); 
 810         for(row 
= 0; row 
< term
.row
; row
++) { 
 811                 term
.line
[row
] = malloc(term
.col 
* sizeof(Glyph
)); 
 812                 term
.alt 
[row
] = malloc(term
.col 
* sizeof(Glyph
)); 
 821         Line
* tmp 
= term
.line
; 
 822         term
.line 
= term
.alt
; 
 824         term
.mode 
^= MODE_ALTSCREEN
; 
 829 tscrolldown(int orig
, int n
) { 
 833         LIMIT(n
, 0, term
.bot
-orig
+1); 
 835         tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
); 
 837         for(i 
= term
.bot
; i 
>= orig
+n
; i
--) { 
 839                 term
.line
[i
] = term
.line
[i
-n
]; 
 840                 term
.line
[i
-n
] = temp
; 
 850 tscrollup(int orig
, int n
) { 
 853         LIMIT(n
, 0, term
.bot
-orig
+1); 
 855         tclearregion(0, orig
, term
.col
-1, orig
+n
-1); 
 857         for(i 
= orig
; i 
<= term
.bot
-n
; i
++) { 
 859                  term
.line
[i
] = term
.line
[i
+n
]; 
 860                  term
.line
[i
+n
] = temp
; 
 870 selscroll(int orig
, int n
) { 
 874         if(BETWEEN(sel
.by
, orig
, term
.bot
) || BETWEEN(sel
.ey
, orig
, term
.bot
)) { 
 875                 if((sel
.by 
+= n
) > term
.bot 
|| (sel
.ey 
+= n
) < term
.top
) { 
 879                 if(sel
.by 
< term
.top
) { 
 883                 if(sel
.ey 
> term
.bot
) { 
 887                 sel
.b
.y 
= sel
.by
, sel
.b
.x 
= sel
.bx
; 
 888                 sel
.e
.y 
= sel
.ey
, sel
.e
.x 
= sel
.ex
; 
 893 tnewline(int first_col
) { 
 896                 tscrollup(term
.top
, 1); 
 899         tmoveto(first_col 
? 0 : term
.c
.x
, y
); 
 905         char *p 
= escseq
.buf
; 
 909                 escseq
.priv 
= 1, p
++; 
 911         while(p 
< escseq
.buf
+escseq
.len
) { 
 913                         escseq
.arg
[escseq
.narg
] *= 10; 
 914                         escseq
.arg
[escseq
.narg
] += *p
++ - '0'/*, noarg = 0 */; 
 916                 if(*p 
== ';' && escseq
.narg
+1 < ESC_ARG_SIZ
) 
 927 tmoveto(int x
, int y
) { 
 928         LIMIT(x
, 0, term
.col
-1); 
 929         LIMIT(y
, 0, term
.row
-1); 
 930         term
.c
.state 
&= ~CURSOR_WRAPNEXT
; 
 937         term
.dirty
[term
.c
.y
] = 1; 
 938         term
.line
[term
.c
.y
][term
.c
.x
] = term
.c
.attr
; 
 939         memcpy(term
.line
[term
.c
.y
][term
.c
.x
].c
, c
, UTF_SIZ
); 
 940         term
.line
[term
.c
.y
][term
.c
.x
].state 
|= GLYPH_SET
; 
 944 tclearregion(int x1
, int y1
, int x2
, int y2
) { 
 948                 temp 
= x1
, x1 
= x2
, x2 
= temp
; 
 950                 temp 
= y1
, y1 
= y2
, y2 
= temp
; 
 952         LIMIT(x1
, 0, term
.col
-1); 
 953         LIMIT(x2
, 0, term
.col
-1); 
 954         LIMIT(y1
, 0, term
.row
-1); 
 955         LIMIT(y2
, 0, term
.row
-1); 
 957         for(y 
= y1
; y 
<= y2
; y
++) { 
 959                 for(x 
= x1
; x 
<= x2
; x
++) 
 960                         term
.line
[y
][x
].state 
= 0; 
 966         int src 
= term
.c
.x 
+ n
; 
 968         int size 
= term
.col 
- src
; 
 970         term
.dirty
[term
.c
.y
] = 1; 
 972         if(src 
>= term
.col
) { 
 973                 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
 976         memmove(&term
.line
[term
.c
.y
][dst
], &term
.line
[term
.c
.y
][src
], size 
* sizeof(Glyph
)); 
 977         tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
 981 tinsertblank(int n
) { 
 984         int size 
= term
.col 
- dst
; 
 986         term
.dirty
[term
.c
.y
] = 1; 
 988         if(dst 
>= term
.col
) { 
 989                 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
 992         memmove(&term
.line
[term
.c
.y
][dst
], &term
.line
[term
.c
.y
][src
], size 
* sizeof(Glyph
)); 
 993         tclearregion(src
, term
.c
.y
, dst 
- 1, term
.c
.y
); 
 997 tinsertblankline(int n
) { 
 998         if(term
.c
.y 
< term
.top 
|| term
.c
.y 
> term
.bot
) 
1001         tscrolldown(term
.c
.y
, n
); 
1005 tdeleteline(int n
) { 
1006         if(term
.c
.y 
< term
.top 
|| term
.c
.y 
> term
.bot
) 
1009         tscrollup(term
.c
.y
, n
); 
1013 tsetattr(int *attr
, int l
) { 
1016         for(i 
= 0; i 
< l
; i
++) { 
1019                         term
.c
.attr
.mode 
&= ~(ATTR_REVERSE 
| ATTR_UNDERLINE 
| ATTR_BOLD
); 
1020                         term
.c
.attr
.fg 
= DefaultFG
; 
1021                         term
.c
.attr
.bg 
= DefaultBG
; 
1024                         term
.c
.attr
.mode 
|= ATTR_BOLD
; 
1027                         term
.c
.attr
.mode 
|= ATTR_UNDERLINE
; 
1030                         term
.c
.attr
.mode 
|= ATTR_REVERSE
; 
1033                         term
.c
.attr
.mode 
&= ~ATTR_BOLD
; 
1036                         term
.c
.attr
.mode 
&= ~ATTR_UNDERLINE
; 
1039                         term
.c
.attr
.mode 
&= ~ATTR_REVERSE
; 
1042                         if(i 
+ 2 < l 
&& attr
[i 
+ 1] == 5) { 
1044                                 if(BETWEEN(attr
[i
], 0, 255)) 
1045                                         term
.c
.attr
.fg 
= attr
[i
]; 
1047                                         fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[i
]); 
1050                                 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]); 
1053                         term
.c
.attr
.fg 
= DefaultFG
; 
1056                         if(i 
+ 2 < l 
&& attr
[i 
+ 1] == 5) { 
1058                                 if(BETWEEN(attr
[i
], 0, 255)) 
1059                                         term
.c
.attr
.bg 
= attr
[i
]; 
1061                                         fprintf(stderr
, "erresc: bad bgcolor %d\n", attr
[i
]); 
1064                                 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]); 
1067                         term
.c
.attr
.bg 
= DefaultBG
; 
1070                         if(BETWEEN(attr
[i
], 30, 37)) 
1071                                 term
.c
.attr
.fg 
= attr
[i
] - 30; 
1072                         else if(BETWEEN(attr
[i
], 40, 47)) 
1073                                 term
.c
.attr
.bg 
= attr
[i
] - 40; 
1074                         else if(BETWEEN(attr
[i
], 90, 97)) 
1075                                 term
.c
.attr
.fg 
= attr
[i
] - 90 + 8; 
1076                         else if(BETWEEN(attr
[i
], 100, 107)) 
1077                                 term
.c
.attr
.fg 
= attr
[i
] - 100 + 8; 
1079                                 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]), csidump(); 
1087 tsetscroll(int t
, int b
) { 
1090         LIMIT(t
, 0, term
.row
-1); 
1091         LIMIT(b
, 0, term
.row
-1); 
1103         switch(escseq
.mode
) { 
1106                 fprintf(stderr
, "erresc: unknown csi "); 
1110         case '@': /* ICH -- Insert <n> blank char */ 
1111                 DEFAULT(escseq
.arg
[0], 1); 
1112                 tinsertblank(escseq
.arg
[0]); 
1114         case 'A': /* CUU -- Cursor <n> Up */ 
1116                 DEFAULT(escseq
.arg
[0], 1); 
1117                 tmoveto(term
.c
.x
, term
.c
.y
-escseq
.arg
[0]); 
1119         case 'B': /* CUD -- Cursor <n> Down */ 
1120                 DEFAULT(escseq
.arg
[0], 1); 
1121                 tmoveto(term
.c
.x
, term
.c
.y
+escseq
.arg
[0]); 
1123         case 'C': /* CUF -- Cursor <n> Forward */ 
1125                 DEFAULT(escseq
.arg
[0], 1); 
1126                 tmoveto(term
.c
.x
+escseq
.arg
[0], term
.c
.y
); 
1128         case 'D': /* CUB -- Cursor <n> Backward */ 
1129                 DEFAULT(escseq
.arg
[0], 1); 
1130                 tmoveto(term
.c
.x
-escseq
.arg
[0], term
.c
.y
); 
1132         case 'E': /* CNL -- Cursor <n> Down and first col */ 
1133                 DEFAULT(escseq
.arg
[0], 1); 
1134                 tmoveto(0, term
.c
.y
+escseq
.arg
[0]); 
1136         case 'F': /* CPL -- Cursor <n> Up and first col */ 
1137                 DEFAULT(escseq
.arg
[0], 1); 
1138                 tmoveto(0, term
.c
.y
-escseq
.arg
[0]); 
1140         case 'G': /* CHA -- Move to <col> */ 
1141         case '`': /* XXX: HPA -- same? */ 
1142                 DEFAULT(escseq
.arg
[0], 1); 
1143                 tmoveto(escseq
.arg
[0]-1, term
.c
.y
); 
1145         case 'H': /* CUP -- Move to <row> <col> */ 
1146         case 'f': /* XXX: HVP -- same? */ 
1147                 DEFAULT(escseq
.arg
[0], 1); 
1148                 DEFAULT(escseq
.arg
[1], 1); 
1149                 tmoveto(escseq
.arg
[1]-1, escseq
.arg
[0]-1); 
1151         /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */ 
1152         case 'J': /* ED -- Clear screen */ 
1154                 switch(escseq
.arg
[0]) { 
1156                         tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1157                         if(term
.c
.y 
< term
.row
-1) 
1158                                 tclearregion(0, term
.c
.y
+1, term
.col
-1, term
.row
-1); 
1162                                 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1); 
1163                         tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
); 
1166                         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1172         case 'K': /* EL -- Clear line */ 
1173                 switch(escseq
.arg
[0]) { 
1175                         tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1178                         tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
); 
1181                         tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1185         case 'S': /* SU -- Scroll <n> line up */ 
1186                 DEFAULT(escseq
.arg
[0], 1); 
1187                 tscrollup(term
.top
, escseq
.arg
[0]); 
1189         case 'T': /* SD -- Scroll <n> line down */ 
1190                 DEFAULT(escseq
.arg
[0], 1); 
1191                 tscrolldown(term
.top
, escseq
.arg
[0]); 
1193         case 'L': /* IL -- Insert <n> blank lines */ 
1194                 DEFAULT(escseq
.arg
[0], 1); 
1195                 tinsertblankline(escseq
.arg
[0]); 
1197         case 'l': /* RM -- Reset Mode */ 
1199                         switch(escseq
.arg
[0]) { 
1201                                 term
.mode 
&= ~MODE_APPKEYPAD
; 
1203                         case 5: /* DECSCNM -- Remove reverse video */ 
1204                                 if(IS_SET(MODE_REVERSE
)) { 
1205                                         term
.mode 
&= ~MODE_REVERSE
; 
1210                                 term
.mode 
&= ~MODE_WRAP
; 
1212                         case 12: /* att610 -- Stop blinking cursor (IGNORED) */ 
1215                                 term
.mode 
&= ~MODE_CRLF
; 
1218                                 term
.c
.state 
|= CURSOR_HIDE
; 
1220                         case 1000: /* disable X11 xterm mouse reporting */ 
1221                                 term
.mode 
&= ~MODE_MOUSEBTN
; 
1224                                 term
.mode 
&= ~MODE_MOUSEMOTION
; 
1226                         case 1049: /* = 1047 and 1048 */ 
1229                                 if(IS_SET(MODE_ALTSCREEN
)) { 
1230                                         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1233                                 if(escseq
.arg
[0] != 1049) 
1236                                 tcursor(CURSOR_LOAD
); 
1242                         switch(escseq
.arg
[0]) { 
1244                                 term
.mode 
&= ~MODE_INSERT
; 
1251         case 'M': /* DL -- Delete <n> lines */ 
1252                 DEFAULT(escseq
.arg
[0], 1); 
1253                 tdeleteline(escseq
.arg
[0]); 
1255         case 'X': /* ECH -- Erase <n> char */ 
1256                 DEFAULT(escseq
.arg
[0], 1); 
1257                 tclearregion(term
.c
.x
, term
.c
.y
, term
.c
.x 
+ escseq
.arg
[0], term
.c
.y
); 
1259         case 'P': /* DCH -- Delete <n> char */ 
1260                 DEFAULT(escseq
.arg
[0], 1); 
1261                 tdeletechar(escseq
.arg
[0]); 
1263         /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */ 
1264         case 'd': /* VPA -- Move to <row> */ 
1265                 DEFAULT(escseq
.arg
[0], 1); 
1266                 tmoveto(term
.c
.x
, escseq
.arg
[0]-1); 
1268         case 'h': /* SM -- Set terminal mode */ 
1270                         switch(escseq
.arg
[0]) { 
1272                                 term
.mode 
|= MODE_APPKEYPAD
; 
1274                         case 5: /* DECSCNM -- Reverve video */ 
1275                                 if(!IS_SET(MODE_REVERSE
)) { 
1276                                         term
.mode 
|= MODE_REVERSE
; 
1281                                 term
.mode 
|= MODE_WRAP
; 
1284                                 term
.mode 
|= MODE_CRLF
; 
1286                         case 12: /* att610 -- Start blinking cursor (IGNORED) */ 
1287                                  /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */ 
1288                                 if(escseq
.narg 
> 1 && escseq
.arg
[1] != 25) 
1291                                 term
.c
.state 
&= ~CURSOR_HIDE
; 
1293                         case 1000: /* 1000,1002: enable xterm mouse report */ 
1294                                 term
.mode 
|= MODE_MOUSEBTN
; 
1297                                 term
.mode 
|= MODE_MOUSEMOTION
; 
1299                         case 1049: /* = 1047 and 1048 */ 
1302                                 if(IS_SET(MODE_ALTSCREEN
)) 
1303                                         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1306                                 if(escseq
.arg
[0] != 1049) 
1309                                 tcursor(CURSOR_SAVE
); 
1311                         default: goto unknown
; 
1314                         switch(escseq
.arg
[0]) { 
1316                                 term
.mode 
|= MODE_INSERT
; 
1318                         default: goto unknown
; 
1322         case 'm': /* SGR -- Terminal attribute (color) */ 
1323                 tsetattr(escseq
.arg
, escseq
.narg
); 
1325         case 'r': /* DECSTBM -- Set Scrolling Region */ 
1329                         DEFAULT(escseq
.arg
[0], 1); 
1330                         DEFAULT(escseq
.arg
[1], term
.row
); 
1331                         tsetscroll(escseq
.arg
[0]-1, escseq
.arg
[1]-1); 
1335         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 
1336                 tcursor(CURSOR_SAVE
); 
1338         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 
1339                 tcursor(CURSOR_LOAD
); 
1346         fwrite("\033[", 1, 2, stdout
); 
1347         fwrite(escseq
.buf
, 1, escseq
.len
, stdout
); 
1352         memset(&escseq
, 0, sizeof(escseq
)); 
1357         int space 
= TAB 
- term
.c
.x 
% TAB
; 
1358         tmoveto(term
.c
.x 
+ space
, term
.c
.y
); 
1364         if(term
.esc 
& ESC_START
) { 
1365                 if(term
.esc 
& ESC_CSI
) { 
1366                         escseq
.buf
[escseq
.len
++] = ascii
; 
1367                         if(BETWEEN(ascii
, 0x40, 0x7E) || escseq
.len 
>= ESC_BUF_SIZ
) { 
1369                                 csiparse(), csihandle(); 
1371                         /* TODO: handle other OSC */ 
1372                 } else if(term
.esc 
& ESC_OSC
) { 
1375                                 term
.esc 
= ESC_START 
| ESC_TITLE
; 
1377                 } else if(term
.esc 
& ESC_TITLE
) { 
1378                         if(ascii 
== '\a' || term
.titlelen
+1 >= ESC_TITLE_SIZ
) { 
1380                                 term
.title
[term
.titlelen
] = '\0'; 
1381                                 XStoreName(xw
.dpy
, xw
.win
, term
.title
); 
1383                                 term
.title
[term
.titlelen
++] = ascii
; 
1385                 } else if(term
.esc 
& ESC_ALTCHARSET
) { 
1387                         case '0': /* Line drawing crap */ 
1388                                 term
.c
.attr
.mode 
|= ATTR_GFX
; 
1390                         case 'B': /* Back to regular text */ 
1391                                 term
.c
.attr
.mode 
&= ~ATTR_GFX
; 
1394                                 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
); 
1400                                 term
.esc 
|= ESC_CSI
; 
1403                                 term
.esc 
|= ESC_OSC
; 
1406                                 term
.esc 
|= ESC_ALTCHARSET
; 
1408                         case 'D': /* IND -- Linefeed */ 
1409                                 if(term
.c
.y 
== term
.bot
) 
1410                                         tscrollup(term
.top
, 1); 
1412                                         tmoveto(term
.c
.x
, term
.c
.y
+1); 
1415                         case 'E': /* NEL -- Next line */ 
1416                                 tnewline(1); /* always go to first col */ 
1419                         case 'M': /* RI -- Reverse index */ 
1420                                 if(term
.c
.y 
== term
.top
) 
1421                                         tscrolldown(term
.top
, 1); 
1423                                         tmoveto(term
.c
.x
, term
.c
.y
-1); 
1426                         case 'c': /* RIS -- Reset to inital state */ 
1430                         case '=': /* DECPAM -- Application keypad */ 
1431                                 term
.mode 
|= MODE_APPKEYPAD
; 
1434                         case '>': /* DECPNM -- Normal keypad */ 
1435                                 term
.mode 
&= ~MODE_APPKEYPAD
; 
1438                         case '7': /* DECSC -- Save Cursor */ 
1439                                 tcursor(CURSOR_SAVE
); 
1442                         case '8': /* DECRC -- Restore Cursor */ 
1443                                 tcursor(CURSOR_LOAD
); 
1447                                 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n", 
1448                                     (uchar
) ascii
, isprint(ascii
)?ascii
:'.'); 
1453                 if(sel
.bx 
!= -1 && BETWEEN(term
.c
.y
, sel
.by
, sel
.ey
)) 
1460                         tmoveto(term
.c
.x
-1, term
.c
.y
); 
1463                         tmoveto(0, term
.c
.y
); 
1468                         /* go to first col if the mode is set */ 
1469                         tnewline(IS_SET(MODE_CRLF
)); 
1472                         if(!(xw
.state 
& WIN_FOCUSED
)) 
1477                         term
.esc 
= ESC_START
; 
1480                         if(IS_SET(MODE_WRAP
) && term
.c
.state 
& CURSOR_WRAPNEXT
) 
1481                                 tnewline(1); /* always go to first col */ 
1483                         if(term
.c
.x
+1 < term
.col
) 
1484                                 tmoveto(term
.c
.x
+1, term
.c
.y
); 
1486                                 term
.c
.state 
|= CURSOR_WRAPNEXT
; 
1492 tresize(int col
, int row
) { 
1494         int minrow 
= MIN(row
, term
.row
); 
1495         int mincol 
= MIN(col
, term
.col
); 
1496         int slide 
= term
.c
.y 
- row 
+ 1; 
1498         if(col 
< 1 || row 
< 1) 
1501         /* free unneeded rows */ 
1504                 /* slide screen to keep cursor where we expect it - 
1505                  * tscrollup would work here, but we can optimize to 
1506                  * memmove because we're freeing the earlier lines */ 
1507                 for(/* i = 0 */; i 
< slide
; i
++) { 
1511                 memmove(term
.line
, term
.line 
+ slide
, row 
* sizeof(Line
)); 
1512                 memmove(term
.alt
, term
.alt 
+ slide
, row 
* sizeof(Line
)); 
1514         for(i 
+= row
; i 
< term
.row
; i
++) { 
1519         /* resize to new height */ 
1520         term
.line 
= realloc(term
.line
, row 
* sizeof(Line
)); 
1521         term
.alt  
= realloc(term
.alt
,  row 
* sizeof(Line
)); 
1522         term
.dirty 
= realloc(term
.dirty
, row 
* sizeof(*term
.dirty
)); 
1524         /* resize each row to new width, zero-pad if needed */ 
1525         for(i 
= 0; i 
< minrow
; i
++) { 
1527                 term
.line
[i
] = realloc(term
.line
[i
], col 
* sizeof(Glyph
)); 
1528                 term
.alt
[i
]  = realloc(term
.alt
[i
],  col 
* sizeof(Glyph
)); 
1529                 for(x 
= mincol
; x 
< col
; x
++) { 
1530                         term
.line
[i
][x
].state 
= 0; 
1531                         term
.alt
[i
][x
].state 
= 0; 
1535         /* allocate any new rows */ 
1536         for(/* i == minrow */; i 
< row
; i
++) { 
1538                 term
.line
[i
] = calloc(col
, sizeof(Glyph
)); 
1539                 term
.alt 
[i
] = calloc(col
, sizeof(Glyph
)); 
1542         /* update terminal size */ 
1543         term
.col 
= col
, term
.row 
= row
; 
1544         /* make use of the LIMIT in tmoveto */ 
1545         tmoveto(term
.c
.x
, term
.c
.y
); 
1546         /* reset scrolling region */ 
1547         tsetscroll(0, row
-1); 
1553 xresize(int col
, int row
) { 
1559         xw
.bufw 
= MAX(1, col 
* xw
.cw
); 
1560         xw
.bufh 
= MAX(1, row 
* xw
.ch
); 
1561         newbuf 
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.bufw
, xw
.bufh
, XDefaultDepth(xw
.dpy
, xw
.scr
)); 
1562         XCopyArea(xw
.dpy
, xw
.buf
, newbuf
, dc
.gc
, 0, 0, xw
.bufw
, xw
.bufh
, 0, 0); 
1563         XFreePixmap(xw
.dpy
, xw
.buf
); 
1564         XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[DefaultBG
]); 
1566                 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, oldw
, 0, 
1567                                 xw
.bufw
-oldw
, MIN(xw
.bufh
, oldh
)); 
1568         else if(xw
.bufw 
< oldw 
&& (BORDER 
> 0 || xw
.w 
> xw
.bufw
)) 
1569                 XClearArea(xw
.dpy
, xw
.win
, BORDER
+xw
.bufw
, BORDER
, 
1570                                 xw
.w
-xw
.bufh
-BORDER
, BORDER
+MIN(xw
.bufh
, oldh
), 
1573                 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, 0, oldh
, 
1574                                 xw
.bufw
, xw
.bufh
-oldh
); 
1575         else if(xw
.bufh 
< oldh 
&& (BORDER 
> 0 || xw
.h 
> xw
.bufh
)) 
1576                 XClearArea(xw
.dpy
, xw
.win
, BORDER
, BORDER
+xw
.bufh
, 
1577                                 xw
.w
-2*BORDER
, xw
.h
-xw
.bufh
-BORDER
, 
1586         ulong white 
= WhitePixel(xw
.dpy
, xw
.scr
); 
1588         /* load colors [0-15] colors and [256-LEN(colorname)[ (config.h) */ 
1589         for(i 
= 0; i 
< LEN(colorname
); i
++) { 
1592                 if(!XAllocNamedColor(xw
.dpy
, xw
.cmap
, colorname
[i
], &color
, &color
)) { 
1594                         fprintf(stderr
, "Could not allocate color '%s'\n", colorname
[i
]); 
1596                         dc
.col
[i
] = color
.pixel
; 
1599         /* load colors [16-255] ; same colors as xterm */ 
1600         for(i 
= 16, r 
= 0; r 
< 6; r
++) 
1601                 for(g 
= 0; g 
< 6; g
++) 
1602                         for(b 
= 0; b 
< 6; b
++) { 
1603                                 color
.red 
= r 
== 0 ? 0 : 0x3737 + 0x2828 * r
; 
1604                                 color
.green 
= g 
== 0 ? 0 : 0x3737 + 0x2828 * g
; 
1605                                 color
.blue 
= b 
== 0 ? 0 : 0x3737 + 0x2828 * b
; 
1606                                 if(!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) { 
1608                                         fprintf(stderr
, "Could not allocate color %d\n", i
); 
1610                                         dc
.col
[i
] = color
.pixel
; 
1614         for(r 
= 0; r 
< 24; r
++, i
++) { 
1615                 color
.red 
= color
.green 
= color
.blue 
= 0x0808 + 0x0a0a * r
; 
1616                 if (!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) { 
1618                         fprintf(stderr
, "Could not allocate color %d\n", i
); 
1620                         dc
.col
[i
] = color
.pixel
; 
1625 xclear(int x1
, int y1
, int x2
, int y2
) { 
1626         XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[IS_SET(MODE_REVERSE
) ? DefaultFG 
: DefaultBG
]); 
1627         XFillRectangle(xw
.dpy
, xw
.buf
, dc
.gc
, 
1628                        x1 
* xw
.cw
, y1 
* xw
.ch
, 
1629                        (x2
-x1
+1) * xw
.cw
, (y2
-y1
+1) * xw
.ch
); 
1634         XClassHint 
class = {opt_class 
? opt_class 
: TNAME
, TNAME
}; 
1635         XWMHints wm 
= {.flags 
= InputHint
, .input 
= 1}; 
1637                 .flags 
= PSize 
| PResizeInc 
| PBaseSize
, 
1640                 .height_inc 
= xw
.ch
, 
1642                 .base_height 
= 2*BORDER
, 
1643                 .base_width 
= 2*BORDER
, 
1645         XSetWMProperties(xw
.dpy
, xw
.win
, NULL
, NULL
, NULL
, 0, &size
, &wm
, &class); 
1649 xinitfont(char *fontstr
) { 
1651         char *def
, **missing
; 
1655         set 
= XCreateFontSet(xw
.dpy
, fontstr
, &missing
, &n
, &def
); 
1658                         fprintf(stderr
, "st: missing fontset: %s\n", missing
[n
]); 
1659                 XFreeStringList(missing
); 
1665 xgetfontinfo(XFontSet set
, int *ascent
, int *descent
, short *lbearing
, short *rbearing
) { 
1666         XFontStruct 
**xfonts
; 
1670         *ascent 
= *descent 
= *lbearing 
= *rbearing 
= 0; 
1671         n 
= XFontsOfFontSet(set
, &xfonts
, &font_names
); 
1672         for(i 
= 0; i 
< n
; i
++) { 
1673                 *ascent 
= MAX(*ascent
, (*xfonts
)->ascent
); 
1674                 *descent 
= MAX(*descent
, (*xfonts
)->descent
); 
1675                 *lbearing 
= MAX(*lbearing
, (*xfonts
)->min_bounds
.lbearing
); 
1676                 *rbearing 
= MAX(*rbearing
, (*xfonts
)->max_bounds
.rbearing
); 
1682 initfonts(char *fontstr
, char *bfontstr
) { 
1683         if((dc
.font
.set 
= xinitfont(fontstr
)) == NULL 
|| 
1684            (dc
.bfont
.set 
= xinitfont(bfontstr
)) == NULL
) 
1685                 die("Can't load font %s\n", dc
.font
.set 
? BOLDFONT 
: FONT
); 
1686         xgetfontinfo(dc
.font
.set
, &dc
.font
.ascent
, &dc
.font
.descent
, 
1687             &dc
.font
.lbearing
, &dc
.font
.rbearing
); 
1688         xgetfontinfo(dc
.bfont
.set
, &dc
.bfont
.ascent
, &dc
.bfont
.descent
, 
1689             &dc
.bfont
.lbearing
, &dc
.bfont
.rbearing
); 
1694         XSetWindowAttributes attrs
; 
1698         if(!(xw
.dpy 
= XOpenDisplay(NULL
))) 
1699                 die("Can't open display\n"); 
1700         xw
.scr 
= XDefaultScreen(xw
.dpy
); 
1703         initfonts(FONT
, BOLDFONT
); 
1705         /* XXX: Assuming same size for bold font */ 
1706         xw
.cw 
= dc
.font
.rbearing 
- dc
.font
.lbearing
; 
1707         xw
.ch 
= dc
.font
.ascent 
+ dc
.font
.descent
; 
1710         xw
.cmap 
= XDefaultColormap(xw
.dpy
, xw
.scr
); 
1713         /* window - default size */ 
1714         xw
.bufh 
= term
.row 
* xw
.ch
; 
1715         xw
.bufw 
= term
.col 
* xw
.cw
; 
1716         xw
.h 
= xw
.bufh 
+ 2*BORDER
; 
1717         xw
.w 
= xw
.bufw 
+ 2*BORDER
; 
1719         attrs
.background_pixel 
= dc
.col
[DefaultBG
]; 
1720         attrs
.border_pixel 
= dc
.col
[DefaultBG
]; 
1721         attrs
.bit_gravity 
= NorthWestGravity
; 
1722         attrs
.event_mask 
= FocusChangeMask 
| KeyPressMask
 
1723                 | ExposureMask 
| VisibilityChangeMask 
| StructureNotifyMask
 
1724                 | ButtonMotionMask 
| ButtonPressMask 
| ButtonReleaseMask
 
1725                 | EnterWindowMask 
| LeaveWindowMask
; 
1726         attrs
.colormap 
= xw
.cmap
; 
1728         parent 
= opt_embed 
? strtol(opt_embed
, NULL
, 0) : XRootWindow(xw
.dpy
, xw
.scr
); 
1729         xw
.win 
= XCreateWindow(xw
.dpy
, parent
, 0, 0, 
1730                         xw
.w
, xw
.h
, 0, XDefaultDepth(xw
.dpy
, xw
.scr
), InputOutput
, 
1731                         XDefaultVisual(xw
.dpy
, xw
.scr
), 
1732                         CWBackPixel 
| CWBorderPixel 
| CWBitGravity 
| CWEventMask
 
1735         xw
.buf 
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.bufw
, xw
.bufh
, XDefaultDepth(xw
.dpy
, xw
.scr
)); 
1739         xw
.xim 
= XOpenIM(xw
.dpy
, NULL
, NULL
, NULL
); 
1740         xw
.xic 
= XCreateIC(xw
.xim
, XNInputStyle
, XIMPreeditNothing
 
1741                                            | XIMStatusNothing
, XNClientWindow
, xw
.win
, 
1742                                            XNFocusWindow
, xw
.win
, NULL
); 
1744         dc
.gc 
= XCreateGC(xw
.dpy
, xw
.win
, 0, NULL
); 
1746         /* white cursor, black outline */ 
1747         cursor 
= XCreateFontCursor(xw
.dpy
, XC_xterm
); 
1748         XDefineCursor(xw
.dpy
, xw
.win
, cursor
); 
1749         XRecolorCursor(xw
.dpy
, cursor
, 
1750                 &(XColor
){.red 
= 0xffff, .green 
= 0xffff, .blue 
= 0xffff}, 
1751                 &(XColor
){.red 
= 0x0000, .green 
= 0x0000, .blue 
= 0x0000}); 
1753         xw
.xembed 
= XInternAtom(xw
.dpy
, "_XEMBED", False
); 
1755         XStoreName(xw
.dpy
, xw
.win
, opt_title 
? opt_title 
: "st"); 
1756         XMapWindow(xw
.dpy
, xw
.win
); 
1762 xdraws(char *s
, Glyph base
, int x
, int y
, int charlen
, int bytelen
) { 
1763         int fg 
= base
.fg
, bg 
= base
.bg
, temp
; 
1764         int winx 
= x
*xw
.cw
, winy 
= y
*xw
.ch 
+ dc
.font
.ascent
, width 
= charlen
*xw
.cw
; 
1765         XFontSet fontset 
= dc
.font
.set
; 
1768         /* only switch default fg/bg if term is in RV mode */ 
1769         if(IS_SET(MODE_REVERSE
)) { 
1776         if(base
.mode 
& ATTR_REVERSE
) 
1777                 temp 
= fg
, fg 
= bg
, bg 
= temp
; 
1779         if(base
.mode 
& ATTR_BOLD
) { 
1781                 fontset 
= dc
.bfont
.set
; 
1784         XSetBackground(xw
.dpy
, dc
.gc
, dc
.col
[bg
]); 
1785         XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[fg
]); 
1787         if(base
.mode 
& ATTR_GFX
) { 
1788                 for(i 
= 0; i 
< bytelen
; i
++) { 
1789                         char c 
= gfx
[(uint
)s
[i
] % 256]; 
1792                         else if(s
[i
] > 0x5f) 
1797         XmbDrawImageString(xw
.dpy
, xw
.buf
, fontset
, dc
.gc
, winx
, winy
, s
, bytelen
); 
1799         if(base
.mode 
& ATTR_UNDERLINE
) 
1800                 XDrawLine(xw
.dpy
, xw
.buf
, dc
.gc
, winx
, winy
+1, winx
+width
-1, winy
+1); 
1803 /* copy buffer pixmap to screen pixmap */ 
1805 xcopy(int x
, int y
, int cols
, int rows
) { 
1806         int src_x 
= x
*xw
.cw
, src_y 
= y
*xw
.ch
, src_w 
= cols
*xw
.cw
, src_h 
= rows
*xw
.ch
; 
1807         int dst_x 
= BORDER
+src_x
, dst_y 
= BORDER
+src_y
; 
1808         XCopyArea(xw
.dpy
, xw
.buf
, xw
.win
, dc
.gc
, src_x
, src_y
, src_w
, src_h
, dst_x
, dst_y
); 
1813         static int oldx 
= 0; 
1814         static int oldy 
= 0; 
1816         Glyph g 
= {{' '}, ATTR_NULL
, DefaultBG
, DefaultCS
, 0}; 
1818         LIMIT(oldx
, 0, term
.col
-1); 
1819         LIMIT(oldy
, 0, term
.row
-1); 
1821         if(term
.line
[term
.c
.y
][term
.c
.x
].state 
& GLYPH_SET
) 
1822                 memcpy(g
.c
, term
.line
[term
.c
.y
][term
.c
.x
].c
, UTF_SIZ
); 
1824         /* remove the old cursor */ 
1825         if(term
.line
[oldy
][oldx
].state 
& GLYPH_SET
) { 
1826                 sl 
= utf8size(term
.line
[oldy
][oldx
].c
); 
1827                 xdraws(term
.line
[oldy
][oldx
].c
, term
.line
[oldy
][oldx
], oldx
, oldy
, 1, sl
); 
1829                 xclear(oldx
, oldy
, oldx
, oldy
); 
1831         xcopy(oldx
, oldy
, 1, 1); 
1833         /* draw the new one */ 
1834         if(!(term
.c
.state 
& CURSOR_HIDE
)) { 
1835                 if(!(xw
.state 
& WIN_FOCUSED
)) 
1838                 if(IS_SET(MODE_REVERSE
)) 
1839                         g
.mode 
|= ATTR_REVERSE
, g
.fg 
= DefaultCS
, g
.bg 
= DefaultFG
; 
1842                 xdraws(g
.c
, g
, term
.c
.x
, term
.c
.y
, 1, sl
); 
1843                 oldx 
= term
.c
.x
, oldy 
= term
.c
.y
; 
1846         xcopy(term
.c
.x
, term
.c
.y
, 1, 1); 
1851         drawregion(0, 0, term
.col
, term
.row
); 
1852         gettimeofday(&xw
.lastdraw
, NULL
); 
1856 drawregion(int x1
, int y1
, int x2
, int y2
) { 
1857         int ic
, ib
, x
, y
, ox
, sl
; 
1859         char buf
[DRAW_BUF_SIZ
]; 
1861         if(!(xw
.state 
& WIN_VISIBLE
)) 
1864         for(y 
= y1
; y 
< y2
; y
++) { 
1867                 xclear(0, y
, term
.col
, y
); 
1869                 base 
= term
.line
[y
][0]; 
1871                 for(x 
= x1
; x 
< x2
; x
++) { 
1872                         new = term
.line
[y
][x
]; 
1873                         if(sel
.bx 
!= -1 && *(new.c
) && selected(x
, y
)) 
1874                                 new.mode 
^= ATTR_REVERSE
; 
1875                         if(ib 
> 0 && (!(new.state 
& GLYPH_SET
) || ATTRCMP(base
, new) || 
1876                                                   ib 
>= DRAW_BUF_SIZ
-UTF_SIZ
)) { 
1877                                 xdraws(buf
, base
, ox
, y
, ic
, ib
); 
1880                         if(new.state 
& GLYPH_SET
) { 
1885                                 sl 
= utf8size(new.c
); 
1886                                 memcpy(buf
+ib
, new.c
, sl
); 
1892                         xdraws(buf
, base
, ox
, y
, ic
, ib
); 
1893                 xcopy(0, y
, term
.col
, 1); 
1899 expose(XEvent 
*ev
) { 
1900         XExposeEvent 
*e 
= &ev
->xexpose
; 
1901         if(xw
.state 
& WIN_REDRAW
) { 
1903                         xw
.state 
&= ~WIN_REDRAW
; 
1904                         xcopy(0, 0, term
.col
, term
.row
); 
1907                 XCopyArea(xw
.dpy
, xw
.buf
, xw
.win
, dc
.gc
, e
->x
-BORDER
, e
->y
-BORDER
, 
1908                                 e
->width
, e
->height
, e
->x
, e
->y
); 
1912 visibility(XEvent 
*ev
) { 
1913         XVisibilityEvent 
*e 
= &ev
->xvisibility
; 
1914         if(e
->state 
== VisibilityFullyObscured
) 
1915                 xw
.state 
&= ~WIN_VISIBLE
; 
1916         else if(!(xw
.state 
& WIN_VISIBLE
)) 
1917                 /* need a full redraw for next Expose, not just a buf copy */ 
1918                 xw
.state 
|= WIN_VISIBLE 
| WIN_REDRAW
; 
1923         xw
.state 
&= ~WIN_VISIBLE
; 
1927 xseturgency(int add
) { 
1928         XWMHints 
*h 
= XGetWMHints(xw
.dpy
, xw
.win
); 
1929         h
->flags 
= add 
? (h
->flags 
| XUrgencyHint
) : (h
->flags 
& ~XUrgencyHint
); 
1930         XSetWMHints(xw
.dpy
, xw
.win
, h
); 
1936         if(ev
->type 
== FocusIn
) { 
1937                 xw
.state 
|= WIN_FOCUSED
; 
1940                 xw
.state 
&= ~WIN_FOCUSED
; 
1945 kmap(KeySym k
, uint state
) { 
1948         for(i 
= 0; i 
< LEN(key
); i
++) { 
1949                 uint mask 
= key
[i
].mask
; 
1950                 if(key
[i
].k 
== k 
&& ((state 
& mask
) == mask 
|| (mask 
== XK_NO_MOD 
&& !state
))) 
1951                         return (char*)key
[i
].s
; 
1957 kpress(XEvent 
*ev
) { 
1958         XKeyEvent 
*e 
= &ev
->xkey
; 
1967         meta 
= e
->state 
& Mod1Mask
; 
1968         shift 
= e
->state 
& ShiftMask
; 
1969         len 
= XmbLookupString(xw
.xic
, e
, buf
, sizeof(buf
), &ksym
, &status
); 
1971         /* 1. custom keys from config.h */ 
1972         if((customkey 
= kmap(ksym
, e
->state
))) 
1973                 ttywrite(customkey
, strlen(customkey
)); 
1974         /* 2. hardcoded (overrides X lookup) */ 
1981                         /* XXX: shift up/down doesn't work */ 
1982                         sprintf(buf
, "\033%c%c", IS_SET(MODE_APPKEYPAD
) ? 'O' : '[', (shift 
? "dacb":"DACB")[ksym 
- XK_Left
]); 
1990                         if(IS_SET(MODE_CRLF
)) 
1991                                 ttywrite("\r\n", 2); 
1998                                 if(meta 
&& len 
== 1) 
1999                                         ttywrite("\033", 1); 
2007 cmessage(XEvent 
*e
) { 
2009            http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */ 
2010         if (e
->xclient
.message_type 
== xw
.xembed 
&& e
->xclient
.format 
== 32) { 
2011                 if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_IN
) { 
2012                         xw
.state 
|= WIN_FOCUSED
; 
2014                 } else if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_OUT
) { 
2015                         xw
.state 
&= ~WIN_FOCUSED
; 
2025         if(e
->xconfigure
.width 
== xw
.w 
&& e
->xconfigure
.height 
== xw
.h
) 
2028         xw
.w 
= e
->xconfigure
.width
; 
2029         xw
.h 
= e
->xconfigure
.height
; 
2030         col 
= (xw
.w 
- 2*BORDER
) / xw
.cw
; 
2031         row 
= (xw
.h 
- 2*BORDER
) / xw
.ch
; 
2032         if(col 
== term
.col 
&& row 
== term
.row
) 
2034         if(tresize(col
, row
)) 
2036         ttyresize(col
, row
); 
2041 last_draw_too_old(void) { 
2043         gettimeofday(&now
, NULL
); 
2044         return TIMEDIFF(now
, xw
.lastdraw
) >= DRAW_TIMEOUT
/1000; 
2051         int xfd 
= XConnectionNumber(xw
.dpy
); 
2052         struct timeval timeout 
= {0}; 
2053         bool stuff_to_print 
= 0; 
2057                 FD_SET(cmdfd
, &rfd
); 
2060                 timeout
.tv_usec 
= SELECT_TIMEOUT
; 
2061                 if(select(MAX(xfd
, cmdfd
)+1, &rfd
, NULL
, NULL
, &timeout
) < 0) { 
2064                         die("select failed: %s\n", SERRNO
); 
2066                 if(FD_ISSET(cmdfd
, &rfd
)) { 
2071                 if(stuff_to_print 
&& last_draw_too_old()) { 
2076                 while(XPending(xw
.dpy
)) { 
2077                         XNextEvent(xw
.dpy
, &ev
); 
2078                         if(XFilterEvent(&ev
, xw
.win
)) 
2080                         if(handler
[ev
.type
]) 
2081                                 (handler
[ev
.type
])(&ev
); 
2087 main(int argc
, char *argv
[]) { 
2090         for(i 
= 1; i 
< argc
; i
++) { 
2091                 switch(argv
[i
][0] != '-' || argv
[i
][2] ? -1 : argv
[i
][1]) { 
2093                         if(++i 
< argc
) opt_title 
= argv
[i
]; 
2096                         if(++i 
< argc
) opt_class 
= argv
[i
]; 
2099                         if(++i 
< argc
) opt_embed 
= argv
[i
]; 
2102                         /* eat every remaining arguments */ 
2103                         if(++i 
< argc
) opt_cmd 
= &argv
[i
]; 
2112         setlocale(LC_CTYPE
, "");