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 enum glyph_attribute 
{ 
  77 enum cursor_movement 
{ 
 104         MODE_MOUSEMOTION 
= 64, 
 125 enum { B0
=1, B1
=2, B2
=4, B3
=8, B4
=16, B5
=32, B6
=64, B7
=128 }; 
 127 typedef unsigned char uchar
; 
 128 typedef unsigned int uint
; 
 129 typedef unsigned long ulong
; 
 130 typedef unsigned short ushort
; 
 133         char c
[UTF_SIZ
];     /* character code */ 
 134         uchar mode
;  /* attribute flags */ 
 135         ushort fg
;   /* foreground  */ 
 136         ushort bg
;   /* background  */ 
 137         uchar state
; /* state flags    */ 
 143         Glyph attr
;      /* current char attributes */ 
 149 /* CSI Escape sequence structs */ 
 150 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */ 
 152         char buf
[ESC_BUF_SIZ
]; /* raw string */ 
 153         int len
;               /* raw string length */ 
 155         int arg
[ESC_ARG_SIZ
]; 
 156         int narg
;              /* nb of args */ 
 160 /* Internal representation of the screen */ 
 162         int row
;        /* nb row */ 
 163         int col
;        /* nb col */ 
 164         Line
* line
;     /* screen */ 
 165         Line
* alt
;      /* alternate screen */ 
 166         bool* dirty
; /* dirtyness of lines */ 
 167         TCursor c
;      /* cursor */ 
 168         int top
;        /* top    scroll limit */ 
 169         int bot
;        /* bottom scroll limit */ 
 170         int mode
;       /* terminal mode flags */ 
 171         int esc
;        /* escape state flags */ 
 172         char title
[ESC_TITLE_SIZ
]; 
 176 /* Purely graphic info */ 
 186         int w
;  /* window width */ 
 187         int h
;  /* window height */ 
 188         int bufw
; /* pixmap width  */ 
 189         int bufh
; /* pixmap height */ 
 190         int ch
; /* char height */ 
 191         int cw
; /* char width  */ 
 192         char state
; /* focus, redraw, visible */ 
 193         struct timeval lastdraw
; 
 203 /* TODO: use better name for vars... */ 
 208         struct {int x
, y
;} b
, e
; 
 211         struct timeval tclick1
; 
 212         struct timeval tclick2
; 
 217 /* Drawing Context */ 
 219         ulong col
[LEN(colorname
) < 256 ? 256 : LEN(colorname
)]; 
 230 static void die(const char*, ...); 
 231 static void draw(void); 
 232 static void drawregion(int, int, int, int); 
 233 static void execsh(void); 
 234 static void sigchld(int); 
 235 static void run(void); 
 236 static bool last_draw_too_old(void); 
 238 static void csidump(void); 
 239 static void csihandle(void); 
 240 static void csiparse(void); 
 241 static void csireset(void); 
 243 static void tclearregion(int, int, int, int); 
 244 static void tcursor(int); 
 245 static void tdeletechar(int); 
 246 static void tdeleteline(int); 
 247 static void tinsertblank(int); 
 248 static void tinsertblankline(int); 
 249 static void tmoveto(int, int); 
 250 static void tnew(int, int); 
 251 static void tnewline(int); 
 252 static void tputtab(void); 
 253 static void tputc(char*); 
 254 static void treset(void); 
 255 static int tresize(int, int); 
 256 static void tscrollup(int, int); 
 257 static void tscrolldown(int, int); 
 258 static void tsetattr(int*, int); 
 259 static void tsetchar(char*); 
 260 static void tsetscroll(int, int); 
 261 static void tswapscreen(void); 
 262 static void tsetdirt(int, int); 
 263 static void tfulldirt(void); 
 265 static void ttynew(void); 
 266 static void ttyread(void); 
 267 static void ttyresize(int, int); 
 268 static void ttywrite(const char *, size_t); 
 270 static void xdraws(char *, Glyph
, int, int, int, int); 
 271 static void xhints(void); 
 272 static void xclear(int, int, int, int); 
 273 static void xcopy(int, int, int, int); 
 274 static void xdrawcursor(void); 
 275 static void xinit(void); 
 276 static void xloadcols(void); 
 277 static void xseturgency(int); 
 278 static void xsetsel(char*); 
 279 static void xresize(int, int); 
 281 static void expose(XEvent 
*); 
 282 static void visibility(XEvent 
*); 
 283 static void unmap(XEvent 
*); 
 284 static char* kmap(KeySym
, uint
); 
 285 static void kpress(XEvent 
*); 
 286 static void cmessage(XEvent 
*); 
 287 static void resize(XEvent 
*); 
 288 static void focus(XEvent 
*); 
 289 static void brelease(XEvent 
*); 
 290 static void bpress(XEvent 
*); 
 291 static void bmotion(XEvent 
*); 
 292 static void selnotify(XEvent 
*); 
 293 static void selrequest(XEvent 
*); 
 295 static void selinit(void); 
 296 static inline bool selected(int, int); 
 297 static void selcopy(void); 
 298 static void selpaste(); 
 299 static void selscroll(int, int); 
 301 static int utf8decode(char *, long *); 
 302 static int utf8encode(long *, char *); 
 303 static int utf8size(char *); 
 304 static int isfullutf8(char *, int); 
 306 static void (*handler
[LASTEvent
])(XEvent 
*) = { 
 308         [ClientMessage
] = cmessage
, 
 309         [ConfigureNotify
] = resize
, 
 310         [VisibilityNotify
] = visibility
, 
 311         [UnmapNotify
] = unmap
, 
 315         [MotionNotify
] = bmotion
, 
 316         [ButtonPress
] = bpress
, 
 317         [ButtonRelease
] = brelease
, 
 318         [SelectionNotify
] = selnotify
, 
 319         [SelectionRequest
] = selrequest
, 
 326 static CSIEscape escseq
; 
 329 static Selection sel
; 
 330 static char **opt_cmd  
= NULL
; 
 331 static char *opt_title 
= NULL
; 
 332 static char *opt_embed 
= NULL
; 
 333 static char *opt_class 
= NULL
; 
 336 utf8decode(char *s
, long *u
) { 
 342         if(~c 
& B7
) { /* 0xxxxxxx */ 
 345         } else if((c 
& (B7
|B6
|B5
)) == (B7
|B6
)) { /* 110xxxxx */ 
 346                 *u 
= c
&(B4
|B3
|B2
|B1
|B0
); 
 348         } else if((c 
& (B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) { /* 1110xxxx */ 
 349                 *u 
= c
&(B3
|B2
|B1
|B0
); 
 351         } else if((c 
& (B7
|B6
|B5
|B4
|B3
)) == (B7
|B6
|B5
|B4
)) { /* 11110xxx */ 
 356         for(i 
= n
, ++s
; i 
> 0; --i
, ++rtn
, ++s
) { 
 358                 if((c 
& (B7
|B6
)) != B7
) /* 10xxxxxx */ 
 361                 *u 
|= c 
& (B5
|B4
|B3
|B2
|B1
|B0
); 
 363         if((n 
== 1 && *u 
< 0x80) || 
 364            (n 
== 2 && *u 
< 0x800) || 
 365            (n 
== 3 && *u 
< 0x10000) || 
 366            (*u 
>= 0xD800 && *u 
<= 0xDFFF)) 
 375 utf8encode(long *u
, char *s
) { 
 383                 *sp 
= uc
; /* 0xxxxxxx */ 
 385         } else if(*u 
< 0x800) { 
 386                 *sp 
= (uc 
>> 6) | (B7
|B6
); /* 110xxxxx */ 
 388         } else if(uc 
< 0x10000) { 
 389                 *sp 
= (uc 
>> 12) | (B7
|B6
|B5
); /* 1110xxxx */ 
 391         } else if(uc 
<= 0x10FFFF) { 
 392                 *sp 
= (uc 
>> 18) | (B7
|B6
|B5
|B4
); /* 11110xxx */ 
 397         for(i
=n
,++sp
; i
>0; --i
,++sp
) 
 398                 *sp 
= ((uc 
>> 6*(i
-1)) & (B5
|B4
|B3
|B2
|B1
|B0
)) | B7
; /* 10xxxxxx */ 
 408 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode 
 409    UTF-8 otherwise return 0 */ 
 411 isfullutf8(char *s
, int b
) { 
 419         else if((*c1
&(B7
|B6
|B5
)) == (B7
|B6
) && b 
== 1) 
 421         else if((*c1
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
) && 
 423             ((b 
== 2) && (*c2
&(B7
|B6
)) == B7
))) 
 425         else if((*c1
&(B7
|B6
|B5
|B4
|B3
)) == (B7
|B6
|B5
|B4
) && 
 427             ((b 
== 2) && (*c2
&(B7
|B6
)) == B7
) || 
 428             ((b 
== 3) && (*c2
&(B7
|B6
)) == B7 
&& (*c3
&(B7
|B6
)) == B7
))) 
 440         else if((c
&(B7
|B6
|B5
)) == (B7
|B6
)) 
 442         else if((c
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) 
 450         memset(&sel
.tclick1
, 0, sizeof(sel
.tclick1
)); 
 451         memset(&sel
.tclick2
, 0, sizeof(sel
.tclick2
)); 
 455         sel
.xtarget 
= XInternAtom(xw
.dpy
, "UTF8_STRING", 0); 
 456         if(sel
.xtarget 
== None
) 
 457                 sel
.xtarget 
= XA_STRING
; 
 461 selected(int x
, int y
) { 
 462         if(sel
.ey 
== y 
&& sel
.by 
== y
) { 
 463                 int bx 
= MIN(sel
.bx
, sel
.ex
); 
 464                 int ex 
= MAX(sel
.bx
, sel
.ex
); 
 465                 return BETWEEN(x
, bx
, ex
); 
 467         return ((sel
.b
.y 
< y
&&y 
< sel
.e
.y
) || (y
==sel
.e
.y 
&& x
<=sel
.e
.x
)) 
 468                 || (y
==sel
.b
.y 
&& x
>=sel
.b
.x 
&& (x
<=sel
.e
.x 
|| sel
.b
.y
!=sel
.e
.y
)); 
 472 getbuttoninfo(XEvent 
*e
, int *b
, int *x
, int *y
) { 
 474                 *b 
= e
->xbutton
.button
; 
 476         *x 
= X2COL(e
->xbutton
.x
); 
 477         *y 
= Y2ROW(e
->xbutton
.y
); 
 478         sel
.b
.x 
= sel
.by 
< sel
.ey 
? sel
.bx 
: sel
.ex
; 
 479         sel
.b
.y 
= MIN(sel
.by
, sel
.ey
); 
 480         sel
.e
.x 
= sel
.by 
< sel
.ey 
? sel
.ex 
: sel
.bx
; 
 481         sel
.e
.y 
= MAX(sel
.by
, sel
.ey
); 
 485 mousereport(XEvent 
*e
) { 
 486         int x 
= X2COL(e
->xbutton
.x
); 
 487         int y 
= Y2ROW(e
->xbutton
.y
); 
 488         int button 
= e
->xbutton
.button
; 
 489         int state 
= e
->xbutton
.state
; 
 490         char buf
[] = { '\033', '[', 'M', 0, 32+x
+1, 32+y
+1 }; 
 491         static int ob
, ox
, oy
; 
 494         if(e
->xbutton
.type 
== MotionNotify
) { 
 495                 if(!IS_SET(MODE_MOUSEMOTION
) || (x 
== ox 
&& y 
== oy
)) 
 499         } else if(e
->xbutton
.type 
== ButtonRelease 
|| button 
== AnyButton
) { 
 505                 if(e
->xbutton
.type 
== ButtonPress
) { 
 511         buf
[3] = 32 + button 
+ (state 
& ShiftMask 
? 4 : 0) 
 512                 + (state 
& Mod4Mask    
? 8  : 0) 
 513                 + (state 
& ControlMask 
? 16 : 0); 
 515         ttywrite(buf
, sizeof(buf
)); 
 520         if(IS_SET(MODE_MOUSE
)) 
 522         else if(e
->xbutton
.button 
== Button1
) { 
 524                         tsetdirt(sel
.b
.y
, sel
.e
.y
); 
 526                 sel
.ex 
= sel
.bx 
= X2COL(e
->xbutton
.x
); 
 527                 sel
.ey 
= sel
.by 
= Y2ROW(e
->xbutton
.y
); 
 534         int x
, y
, bufsize
, is_selected 
= 0; 
 540                 bufsize 
= (term
.col
+1) * (sel
.e
.y
-sel
.b
.y
+1) * UTF_SIZ
; 
 541                 ptr 
= str 
= malloc(bufsize
); 
 543                 /* append every set & selected glyph to the selection */ 
 544                 for(y 
= 0; y 
< term
.row
; y
++) { 
 545                         for(x 
= 0; x 
< term
.col
; x
++) { 
 546                                 is_selected 
= selected(x
, y
); 
 547                                 if((term
.line
[y
][x
].state 
& GLYPH_SET
) && is_selected
) { 
 548                                         int size 
= utf8size(term
.line
[y
][x
].c
); 
 549                                         memcpy(ptr
, term
.line
[y
][x
].c
, size
); 
 554                         /* \n at the end of every selected line except for the last one */ 
 555                         if(is_selected 
&& y 
< sel
.e
.y
) 
 564 selnotify(XEvent 
*e
) { 
 565         ulong nitems
, ofs
, rem
; 
 572                 if(XGetWindowProperty(xw
.dpy
, xw
.win
, XA_PRIMARY
, ofs
, BUFSIZ
/4, 
 573                                         False
, AnyPropertyType
, &type
, &format
, 
 574                                         &nitems
, &rem
, &data
)) { 
 575                         fprintf(stderr
, "Clipboard allocation failed\n"); 
 578                 ttywrite((const char *) data
, nitems 
* format 
/ 8); 
 580                 /* number of 32-bit chunks returned */ 
 581                 ofs 
+= nitems 
* format 
/ 32; 
 587         XConvertSelection(xw
.dpy
, XA_PRIMARY
, sel
.xtarget
, XA_PRIMARY
, xw
.win
, CurrentTime
); 
 591 selrequest(XEvent 
*e
) { 
 592         XSelectionRequestEvent 
*xsre
; 
 596         xsre 
= (XSelectionRequestEvent 
*) e
; 
 597         xev
.type 
= SelectionNotify
; 
 598         xev
.requestor 
= xsre
->requestor
; 
 599         xev
.selection 
= xsre
->selection
; 
 600         xev
.target 
= xsre
->target
; 
 601         xev
.time 
= xsre
->time
; 
 605         xa_targets 
= XInternAtom(xw
.dpy
, "TARGETS", 0); 
 606         if(xsre
->target 
== xa_targets
) { 
 607                 /* respond with the supported type */ 
 608                 Atom string 
= sel
.xtarget
; 
 609                 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, 
 610                                 XA_ATOM
, 32, PropModeReplace
, 
 611                                 (uchar 
*) &string
, 1); 
 612                 xev
.property 
= xsre
->property
; 
 613         } else if(xsre
->target 
== sel
.xtarget 
&& sel
.clip 
!= NULL
) { 
 614                 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, 
 615                                 xsre
->target
, 8, PropModeReplace
, 
 616                                 (uchar 
*) sel
.clip
, strlen(sel
.clip
)); 
 617                 xev
.property 
= xsre
->property
; 
 620         /* all done, send a notification to the listener */ 
 621         if(!XSendEvent(xsre
->display
, xsre
->requestor
, True
, 0, (XEvent 
*) &xev
)) 
 622                 fprintf(stderr
, "Error sending SelectionNotify event\n"); 
 627         /* register the selection for both the clipboard and the primary */ 
 633         XSetSelectionOwner(xw
.dpy
, XA_PRIMARY
, xw
.win
, CurrentTime
); 
 635         clipboard 
= XInternAtom(xw
.dpy
, "CLIPBOARD", 0); 
 636         XSetSelectionOwner(xw
.dpy
, clipboard
, xw
.win
, CurrentTime
); 
 642 brelease(XEvent 
*e
) { 
 643         if(IS_SET(MODE_MOUSE
)) { 
 647         if(e
->xbutton
.button 
== Button2
) 
 649         else if(e
->xbutton
.button 
== Button1
) { 
 651                 getbuttoninfo(e
, NULL
, &sel
.ex
, &sel
.ey
); 
 652                 term
.dirty
[sel
.ey
] = 1; 
 653                 if(sel
.bx 
== sel
.ex 
&& sel
.by 
== sel
.ey
) { 
 656                         gettimeofday(&now
, NULL
); 
 658                         if(TIMEDIFF(now
, sel
.tclick2
) <= TRIPLECLICK_TIMEOUT
) { 
 659                                 /* triple click on the line */ 
 660                                 sel
.b
.x 
= sel
.bx 
= 0; 
 661                                 sel
.e
.x 
= sel
.ex 
= term
.col
; 
 662                                 sel
.b
.y 
= sel
.e
.y 
= sel
.ey
; 
 664                         } else if(TIMEDIFF(now
, sel
.tclick1
) <= DOUBLECLICK_TIMEOUT
) { 
 665                                 /* double click to select word */ 
 667                                 while(sel
.bx 
> 0 && term
.line
[sel
.ey
][sel
.bx
-1].state 
& GLYPH_SET 
&& 
 668                                           term
.line
[sel
.ey
][sel
.bx
-1].c
[0] != ' ') sel
.bx
--; 
 670                                 while(sel
.ex 
< term
.col
-1 && term
.line
[sel
.ey
][sel
.ex
+1].state 
& GLYPH_SET 
&& 
 671                                           term
.line
[sel
.ey
][sel
.ex
+1].c
[0] != ' ') sel
.ex
++; 
 673                                 sel
.b
.y 
= sel
.e
.y 
= sel
.ey
; 
 679         memcpy(&sel
.tclick2
, &sel
.tclick1
, sizeof(struct timeval
)); 
 680         gettimeofday(&sel
.tclick1
, NULL
); 
 686         if(IS_SET(MODE_MOUSE
)) { 
 691                 int oldey 
= sel
.ey
, oldex 
= sel
.ex
; 
 692                 getbuttoninfo(e
, NULL
, &sel
.ex
, &sel
.ey
); 
 694                 if(oldey 
!= sel
.ey 
|| oldex 
!= sel
.ex
) { 
 695                         int starty 
= MIN(oldey
, sel
.ey
); 
 696                         int endy 
= MAX(oldey
, sel
.ey
); 
 697                         tsetdirt(starty
, endy
); 
 704 die(const char *errstr
, ...) { 
 707         va_start(ap
, errstr
); 
 708         vfprintf(stderr
, errstr
, ap
); 
 716         char *envshell 
= getenv("SHELL"); 
 718         DEFAULT(envshell
, SHELL
); 
 719         putenv("TERM="TNAME
); 
 720         args 
= opt_cmd 
? opt_cmd 
: (char*[]){envshell
, "-i", NULL
}; 
 721         execvp(args
[0], args
); 
 728         if(waitpid(pid
, &stat
, 0) < 0) 
 729                 die("Waiting for pid %hd failed: %s\n", pid
, SERRNO
); 
 731                 exit(WEXITSTATUS(stat
)); 
 740         /* seems to work fine on linux, openbsd and freebsd */ 
 741         struct winsize w 
= {term
.row
, term
.col
, 0, 0}; 
 742         if(openpty(&m
, &s
, NULL
, NULL
, &w
) < 0) 
 743                 die("openpty failed: %s\n", SERRNO
); 
 745         switch(pid 
= fork()) { 
 747                 die("fork failed\n"); 
 750                 setsid(); /* create a new process group */ 
 751                 dup2(s
, STDIN_FILENO
); 
 752                 dup2(s
, STDOUT_FILENO
); 
 753                 dup2(s
, STDERR_FILENO
); 
 754                 if(ioctl(s
, TIOCSCTTY
, NULL
) < 0) 
 755                         die("ioctl TIOCSCTTY failed: %s\n", SERRNO
); 
 763                 signal(SIGCHLD
, sigchld
); 
 770         fprintf(stderr
, " %02x '%c' ", c
, isprint(c
)?c
:'.'); 
 772                 fprintf(stderr
, "\n"); 
 777         static char buf
[BUFSIZ
]; 
 778         static int buflen 
= 0; 
 781         int charsize
; /* size of utf8 char in bytes */ 
 785         /* append read bytes to unprocessed bytes */ 
 786         if((ret 
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0) 
 787                 die("Couldn't read from shell: %s\n", SERRNO
); 
 789         /* process every complete utf8 char */ 
 792         while(buflen 
>= UTF_SIZ 
|| isfullutf8(ptr
,buflen
)) { 
 793                 charsize 
= utf8decode(ptr
, &utf8c
); 
 794                 utf8encode(&utf8c
, s
); 
 800         /* keep any uncomplete utf8 char for the next call */ 
 801         memmove(buf
, ptr
, buflen
); 
 805 ttywrite(const char *s
, size_t n
) { 
 806         if(write(cmdfd
, s
, n
) == -1) 
 807                 die("write error on tty: %s\n", SERRNO
); 
 811 ttyresize(int x
, int y
) { 
 816         w
.ws_xpixel 
= w
.ws_ypixel 
= 0; 
 817         if(ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0) 
 818                 fprintf(stderr
, "Couldn't set window size: %s\n", SERRNO
); 
 822 tsetdirt(int top
, int bot
) 
 826         LIMIT(top
, 0, term
.row
-1); 
 827         LIMIT(bot
, 0, term
.row
-1); 
 829         for(i 
= top
; i 
<= bot
; i
++) 
 836         tsetdirt(0, term
.row
-1); 
 843         if(mode 
== CURSOR_SAVE
) 
 845         else if(mode 
== CURSOR_LOAD
) 
 846                 term
.c 
= c
, tmoveto(c
.x
, c
.y
); 
 855         }, .x 
= 0, .y 
= 0, .state 
= CURSOR_DEFAULT
}; 
 857         term
.top 
= 0, term
.bot 
= term
.row 
- 1; 
 858         term
.mode 
= MODE_WRAP
; 
 859         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
 863 tnew(int col
, int row
) { 
 864         /* set screen size */ 
 865         term
.row 
= row
, term
.col 
= col
; 
 866         term
.line 
= malloc(term
.row 
* sizeof(Line
)); 
 867         term
.alt  
= malloc(term
.row 
* sizeof(Line
)); 
 868         term
.dirty 
= malloc(term
.row 
* sizeof(*term
.dirty
)); 
 870         for(row 
= 0; row 
< term
.row
; row
++) { 
 871                 term
.line
[row
] = malloc(term
.col 
* sizeof(Glyph
)); 
 872                 term
.alt 
[row
] = malloc(term
.col 
* sizeof(Glyph
)); 
 881         Line
* tmp 
= term
.line
; 
 882         term
.line 
= term
.alt
; 
 884         term
.mode 
^= MODE_ALTSCREEN
; 
 889 tscrolldown(int orig
, int n
) { 
 893         LIMIT(n
, 0, term
.bot
-orig
+1); 
 895         tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
); 
 897         for(i 
= term
.bot
; i 
>= orig
+n
; i
--) { 
 899                 term
.line
[i
] = term
.line
[i
-n
]; 
 900                 term
.line
[i
-n
] = temp
; 
 910 tscrollup(int orig
, int n
) { 
 913         LIMIT(n
, 0, term
.bot
-orig
+1); 
 915         tclearregion(0, orig
, term
.col
-1, orig
+n
-1); 
 917         for(i 
= orig
; i 
<= term
.bot
-n
; i
++) { 
 919                  term
.line
[i
] = term
.line
[i
+n
]; 
 920                  term
.line
[i
+n
] = temp
; 
 930 selscroll(int orig
, int n
) { 
 934         if(BETWEEN(sel
.by
, orig
, term
.bot
) || BETWEEN(sel
.ey
, orig
, term
.bot
)) { 
 935                 if((sel
.by 
+= n
) > term
.bot 
|| (sel
.ey 
+= n
) < term
.top
) { 
 939                 if(sel
.by 
< term
.top
) { 
 943                 if(sel
.ey 
> term
.bot
) { 
 947                 sel
.b
.y 
= sel
.by
, sel
.b
.x 
= sel
.bx
; 
 948                 sel
.e
.y 
= sel
.ey
, sel
.e
.x 
= sel
.ex
; 
 953 tnewline(int first_col
) { 
 956                 tscrollup(term
.top
, 1); 
 959         tmoveto(first_col 
? 0 : term
.c
.x
, y
); 
 965         char *p 
= escseq
.buf
; 
 969                 escseq
.priv 
= 1, p
++; 
 971         while(p 
< escseq
.buf
+escseq
.len
) { 
 973                         escseq
.arg
[escseq
.narg
] *= 10; 
 974                         escseq
.arg
[escseq
.narg
] += *p
++ - '0'/*, noarg = 0 */; 
 976                 if(*p 
== ';' && escseq
.narg
+1 < ESC_ARG_SIZ
) 
 987 tmoveto(int x
, int y
) { 
 988         LIMIT(x
, 0, term
.col
-1); 
 989         LIMIT(y
, 0, term
.row
-1); 
 990         term
.c
.state 
&= ~CURSOR_WRAPNEXT
; 
 997         term
.dirty
[term
.c
.y
] = 1; 
 998         term
.line
[term
.c
.y
][term
.c
.x
] = term
.c
.attr
; 
 999         memcpy(term
.line
[term
.c
.y
][term
.c
.x
].c
, c
, UTF_SIZ
); 
1000         term
.line
[term
.c
.y
][term
.c
.x
].state 
|= GLYPH_SET
; 
1004 tclearregion(int x1
, int y1
, int x2
, int y2
) { 
1008                 temp 
= x1
, x1 
= x2
, x2 
= temp
; 
1010                 temp 
= y1
, y1 
= y2
, y2 
= temp
; 
1012         LIMIT(x1
, 0, term
.col
-1); 
1013         LIMIT(x2
, 0, term
.col
-1); 
1014         LIMIT(y1
, 0, term
.row
-1); 
1015         LIMIT(y2
, 0, term
.row
-1); 
1017         for(y 
= y1
; y 
<= y2
; y
++) { 
1019                 for(x 
= x1
; x 
<= x2
; x
++) 
1020                         term
.line
[y
][x
].state 
= 0; 
1025 tdeletechar(int n
) { 
1026         int src 
= term
.c
.x 
+ n
; 
1028         int size 
= term
.col 
- src
; 
1030         term
.dirty
[term
.c
.y
] = 1; 
1032         if(src 
>= term
.col
) { 
1033                 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1036         memmove(&term
.line
[term
.c
.y
][dst
], &term
.line
[term
.c
.y
][src
], size 
* sizeof(Glyph
)); 
1037         tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1041 tinsertblank(int n
) { 
1044         int size 
= term
.col 
- dst
; 
1046         term
.dirty
[term
.c
.y
] = 1; 
1048         if(dst 
>= term
.col
) { 
1049                 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1052         memmove(&term
.line
[term
.c
.y
][dst
], &term
.line
[term
.c
.y
][src
], size 
* sizeof(Glyph
)); 
1053         tclearregion(src
, term
.c
.y
, dst 
- 1, term
.c
.y
); 
1057 tinsertblankline(int n
) { 
1058         if(term
.c
.y 
< term
.top 
|| term
.c
.y 
> term
.bot
) 
1061         tscrolldown(term
.c
.y
, n
); 
1065 tdeleteline(int n
) { 
1066         if(term
.c
.y 
< term
.top 
|| term
.c
.y 
> term
.bot
) 
1069         tscrollup(term
.c
.y
, n
); 
1073 tsetattr(int *attr
, int l
) { 
1076         for(i 
= 0; i 
< l
; i
++) { 
1079                         term
.c
.attr
.mode 
&= ~(ATTR_REVERSE 
| ATTR_UNDERLINE 
| ATTR_BOLD
); 
1080                         term
.c
.attr
.fg 
= DefaultFG
; 
1081                         term
.c
.attr
.bg 
= DefaultBG
; 
1084                         term
.c
.attr
.mode 
|= ATTR_BOLD
; 
1087                         term
.c
.attr
.mode 
|= ATTR_UNDERLINE
; 
1090                         term
.c
.attr
.mode 
|= ATTR_REVERSE
; 
1093                         term
.c
.attr
.mode 
&= ~ATTR_BOLD
; 
1096                         term
.c
.attr
.mode 
&= ~ATTR_UNDERLINE
; 
1099                         term
.c
.attr
.mode 
&= ~ATTR_REVERSE
; 
1102                         if(i 
+ 2 < l 
&& attr
[i 
+ 1] == 5) { 
1104                                 if(BETWEEN(attr
[i
], 0, 255)) 
1105                                         term
.c
.attr
.fg 
= attr
[i
]; 
1107                                         fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[i
]); 
1110                                 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]); 
1113                         term
.c
.attr
.fg 
= DefaultFG
; 
1116                         if(i 
+ 2 < l 
&& attr
[i 
+ 1] == 5) { 
1118                                 if(BETWEEN(attr
[i
], 0, 255)) 
1119                                         term
.c
.attr
.bg 
= attr
[i
]; 
1121                                         fprintf(stderr
, "erresc: bad bgcolor %d\n", attr
[i
]); 
1124                                 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]); 
1127                         term
.c
.attr
.bg 
= DefaultBG
; 
1130                         if(BETWEEN(attr
[i
], 30, 37)) 
1131                                 term
.c
.attr
.fg 
= attr
[i
] - 30; 
1132                         else if(BETWEEN(attr
[i
], 40, 47)) 
1133                                 term
.c
.attr
.bg 
= attr
[i
] - 40; 
1134                         else if(BETWEEN(attr
[i
], 90, 97)) 
1135                                 term
.c
.attr
.fg 
= attr
[i
] - 90 + 8; 
1136                         else if(BETWEEN(attr
[i
], 100, 107)) 
1137                                 term
.c
.attr
.fg 
= attr
[i
] - 100 + 8; 
1139                                 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]), csidump(); 
1147 tsetscroll(int t
, int b
) { 
1150         LIMIT(t
, 0, term
.row
-1); 
1151         LIMIT(b
, 0, term
.row
-1); 
1163         switch(escseq
.mode
) { 
1166                 fprintf(stderr
, "erresc: unknown csi "); 
1170         case '@': /* ICH -- Insert <n> blank char */ 
1171                 DEFAULT(escseq
.arg
[0], 1); 
1172                 tinsertblank(escseq
.arg
[0]); 
1174         case 'A': /* CUU -- Cursor <n> Up */ 
1176                 DEFAULT(escseq
.arg
[0], 1); 
1177                 tmoveto(term
.c
.x
, term
.c
.y
-escseq
.arg
[0]); 
1179         case 'B': /* CUD -- Cursor <n> Down */ 
1180                 DEFAULT(escseq
.arg
[0], 1); 
1181                 tmoveto(term
.c
.x
, term
.c
.y
+escseq
.arg
[0]); 
1183         case 'C': /* CUF -- Cursor <n> Forward */ 
1185                 DEFAULT(escseq
.arg
[0], 1); 
1186                 tmoveto(term
.c
.x
+escseq
.arg
[0], term
.c
.y
); 
1188         case 'D': /* CUB -- Cursor <n> Backward */ 
1189                 DEFAULT(escseq
.arg
[0], 1); 
1190                 tmoveto(term
.c
.x
-escseq
.arg
[0], term
.c
.y
); 
1192         case 'E': /* CNL -- Cursor <n> Down and first col */ 
1193                 DEFAULT(escseq
.arg
[0], 1); 
1194                 tmoveto(0, term
.c
.y
+escseq
.arg
[0]); 
1196         case 'F': /* CPL -- Cursor <n> Up and first col */ 
1197                 DEFAULT(escseq
.arg
[0], 1); 
1198                 tmoveto(0, term
.c
.y
-escseq
.arg
[0]); 
1200         case 'G': /* CHA -- Move to <col> */ 
1201         case '`': /* XXX: HPA -- same? */ 
1202                 DEFAULT(escseq
.arg
[0], 1); 
1203                 tmoveto(escseq
.arg
[0]-1, term
.c
.y
); 
1205         case 'H': /* CUP -- Move to <row> <col> */ 
1206         case 'f': /* XXX: HVP -- same? */ 
1207                 DEFAULT(escseq
.arg
[0], 1); 
1208                 DEFAULT(escseq
.arg
[1], 1); 
1209                 tmoveto(escseq
.arg
[1]-1, escseq
.arg
[0]-1); 
1211         /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */ 
1212         case 'J': /* ED -- Clear screen */ 
1214                 switch(escseq
.arg
[0]) { 
1216                         tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1217                         if(term
.c
.y 
< term
.row
-1) 
1218                                 tclearregion(0, term
.c
.y
+1, term
.col
-1, term
.row
-1); 
1222                                 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1); 
1223                         tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
); 
1226                         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1232         case 'K': /* EL -- Clear line */ 
1233                 switch(escseq
.arg
[0]) { 
1235                         tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1238                         tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
); 
1241                         tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1245         case 'S': /* SU -- Scroll <n> line up */ 
1246                 DEFAULT(escseq
.arg
[0], 1); 
1247                 tscrollup(term
.top
, escseq
.arg
[0]); 
1249         case 'T': /* SD -- Scroll <n> line down */ 
1250                 DEFAULT(escseq
.arg
[0], 1); 
1251                 tscrolldown(term
.top
, escseq
.arg
[0]); 
1253         case 'L': /* IL -- Insert <n> blank lines */ 
1254                 DEFAULT(escseq
.arg
[0], 1); 
1255                 tinsertblankline(escseq
.arg
[0]); 
1257         case 'l': /* RM -- Reset Mode */ 
1259                         switch(escseq
.arg
[0]) { 
1261                                 term
.mode 
&= ~MODE_APPKEYPAD
; 
1263                         case 5: /* DECSCNM -- Remove reverse video */ 
1264                                 if(IS_SET(MODE_REVERSE
)) { 
1265                                         term
.mode 
&= ~MODE_REVERSE
; 
1270                                 term
.mode 
&= ~MODE_WRAP
; 
1272                         case 12: /* att610 -- Stop blinking cursor (IGNORED) */ 
1275                                 term
.mode 
&= ~MODE_CRLF
; 
1278                                 term
.c
.state 
|= CURSOR_HIDE
; 
1280                         case 1000: /* disable X11 xterm mouse reporting */ 
1281                                 term
.mode 
&= ~MODE_MOUSEBTN
; 
1284                                 term
.mode 
&= ~MODE_MOUSEMOTION
; 
1286                         case 1049: /* = 1047 and 1048 */ 
1289                                 if(IS_SET(MODE_ALTSCREEN
)) { 
1290                                         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1293                                 if(escseq
.arg
[0] != 1049) 
1296                                 tcursor(CURSOR_LOAD
); 
1302                         switch(escseq
.arg
[0]) { 
1304                                 term
.mode 
&= ~MODE_INSERT
; 
1311         case 'M': /* DL -- Delete <n> lines */ 
1312                 DEFAULT(escseq
.arg
[0], 1); 
1313                 tdeleteline(escseq
.arg
[0]); 
1315         case 'X': /* ECH -- Erase <n> char */ 
1316                 DEFAULT(escseq
.arg
[0], 1); 
1317                 tclearregion(term
.c
.x
, term
.c
.y
, term
.c
.x 
+ escseq
.arg
[0], term
.c
.y
); 
1319         case 'P': /* DCH -- Delete <n> char */ 
1320                 DEFAULT(escseq
.arg
[0], 1); 
1321                 tdeletechar(escseq
.arg
[0]); 
1323         /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */ 
1324         case 'd': /* VPA -- Move to <row> */ 
1325                 DEFAULT(escseq
.arg
[0], 1); 
1326                 tmoveto(term
.c
.x
, escseq
.arg
[0]-1); 
1328         case 'h': /* SM -- Set terminal mode */ 
1330                         switch(escseq
.arg
[0]) { 
1332                                 term
.mode 
|= MODE_APPKEYPAD
; 
1334                         case 5: /* DECSCNM -- Reverve video */ 
1335                                 if(!IS_SET(MODE_REVERSE
)) { 
1336                                         term
.mode 
|= MODE_REVERSE
; 
1341                                 term
.mode 
|= MODE_WRAP
; 
1344                                 term
.mode 
|= MODE_CRLF
; 
1346                         case 12: /* att610 -- Start blinking cursor (IGNORED) */ 
1347                                  /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */ 
1348                                 if(escseq
.narg 
> 1 && escseq
.arg
[1] != 25) 
1351                                 term
.c
.state 
&= ~CURSOR_HIDE
; 
1353                         case 1000: /* 1000,1002: enable xterm mouse report */ 
1354                                 term
.mode 
|= MODE_MOUSEBTN
; 
1357                                 term
.mode 
|= MODE_MOUSEMOTION
; 
1359                         case 1049: /* = 1047 and 1048 */ 
1362                                 if(IS_SET(MODE_ALTSCREEN
)) 
1363                                         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1366                                 if(escseq
.arg
[0] != 1049) 
1369                                 tcursor(CURSOR_SAVE
); 
1371                         default: goto unknown
; 
1374                         switch(escseq
.arg
[0]) { 
1376                                 term
.mode 
|= MODE_INSERT
; 
1378                         default: goto unknown
; 
1382         case 'm': /* SGR -- Terminal attribute (color) */ 
1383                 tsetattr(escseq
.arg
, escseq
.narg
); 
1385         case 'r': /* DECSTBM -- Set Scrolling Region */ 
1389                         DEFAULT(escseq
.arg
[0], 1); 
1390                         DEFAULT(escseq
.arg
[1], term
.row
); 
1391                         tsetscroll(escseq
.arg
[0]-1, escseq
.arg
[1]-1); 
1395         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 
1396                 tcursor(CURSOR_SAVE
); 
1398         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 
1399                 tcursor(CURSOR_LOAD
); 
1408         for(i 
= 0; i 
< escseq
.len
; i
++) { 
1409                 uint c 
= escseq
.buf
[i
] & 0xff; 
1410                 if(isprint(c
)) putchar(c
); 
1411                 else if(c 
== '\n') printf("(\\n)"); 
1412                 else if(c 
== '\r') printf("(\\r)"); 
1413                 else if(c 
== 0x1b) printf("(\\e)"); 
1414                 else printf("(%02x)", c
); 
1421         memset(&escseq
, 0, sizeof(escseq
)); 
1426         int space 
= TAB 
- term
.c
.x 
% TAB
; 
1427         tmoveto(term
.c
.x 
+ space
, term
.c
.y
); 
1433         if(term
.esc 
& ESC_START
) { 
1434                 if(term
.esc 
& ESC_CSI
) { 
1435                         escseq
.buf
[escseq
.len
++] = ascii
; 
1436                         if(BETWEEN(ascii
, 0x40, 0x7E) || escseq
.len 
>= ESC_BUF_SIZ
) { 
1438                                 csiparse(), csihandle(); 
1440                         /* TODO: handle other OSC */ 
1441                 } else if(term
.esc 
& ESC_OSC
) { 
1444                                 term
.esc 
= ESC_START 
| ESC_TITLE
; 
1446                 } else if(term
.esc 
& ESC_TITLE
) { 
1447                         if(ascii 
== '\a' || term
.titlelen
+1 >= ESC_TITLE_SIZ
) { 
1449                                 term
.title
[term
.titlelen
] = '\0'; 
1450                                 XStoreName(xw
.dpy
, xw
.win
, term
.title
); 
1452                                 term
.title
[term
.titlelen
++] = ascii
; 
1454                 } else if(term
.esc 
& ESC_ALTCHARSET
) { 
1456                         case '0': /* Line drawing crap */ 
1457                                 term
.c
.attr
.mode 
|= ATTR_GFX
; 
1459                         case 'B': /* Back to regular text */ 
1460                                 term
.c
.attr
.mode 
&= ~ATTR_GFX
; 
1463                                 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n", ascii
); 
1469                                 term
.esc 
|= ESC_CSI
; 
1472                                 term
.esc 
|= ESC_OSC
; 
1475                                 term
.esc 
|= ESC_ALTCHARSET
; 
1477                         case 'D': /* IND -- Linefeed */ 
1478                                 if(term
.c
.y 
== term
.bot
) 
1479                                         tscrollup(term
.top
, 1); 
1481                                         tmoveto(term
.c
.x
, term
.c
.y
+1); 
1484                         case 'E': /* NEL -- Next line */ 
1485                                 tnewline(1); /* always go to first col */ 
1488                         case 'M': /* RI -- Reverse index */ 
1489                                 if(term
.c
.y 
== term
.top
) 
1490                                         tscrolldown(term
.top
, 1); 
1492                                         tmoveto(term
.c
.x
, term
.c
.y
-1); 
1495                         case 'c': /* RIS -- Reset to inital state */ 
1499                         case '=': /* DECPAM -- Application keypad */ 
1500                                 term
.mode 
|= MODE_APPKEYPAD
; 
1503                         case '>': /* DECPNM -- Normal keypad */ 
1504                                 term
.mode 
&= ~MODE_APPKEYPAD
; 
1507                         case '7': /* DECSC -- Save Cursor */ 
1508                                 tcursor(CURSOR_SAVE
); 
1511                         case '8': /* DECRC -- Restore Cursor */ 
1512                                 tcursor(CURSOR_LOAD
); 
1516                                 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n", 
1517                                     (uchar
) ascii
, isprint(ascii
)?ascii
:'.'); 
1522                 if(sel
.bx 
!= -1 && BETWEEN(term
.c
.y
, sel
.by
, sel
.ey
)) 
1529                         tmoveto(term
.c
.x
-1, term
.c
.y
); 
1532                         tmoveto(0, term
.c
.y
); 
1537                         /* go to first col if the mode is set */ 
1538                         tnewline(IS_SET(MODE_CRLF
)); 
1541                         if(!(xw
.state 
& WIN_FOCUSED
)) 
1546                         term
.esc 
= ESC_START
; 
1549                         if(IS_SET(MODE_WRAP
) && term
.c
.state 
& CURSOR_WRAPNEXT
) 
1550                                 tnewline(1); /* always go to first col */ 
1552                         if(term
.c
.x
+1 < term
.col
) 
1553                                 tmoveto(term
.c
.x
+1, term
.c
.y
); 
1555                                 term
.c
.state 
|= CURSOR_WRAPNEXT
; 
1561 tresize(int col
, int row
) { 
1563         int minrow 
= MIN(row
, term
.row
); 
1564         int mincol 
= MIN(col
, term
.col
); 
1565         int slide 
= term
.c
.y 
- row 
+ 1; 
1567         if(col 
< 1 || row 
< 1) 
1570         /* free unneeded rows */ 
1573                 /* slide screen to keep cursor where we expect it - 
1574                  * tscrollup would work here, but we can optimize to 
1575                  * memmove because we're freeing the earlier lines */ 
1576                 for(/* i = 0 */; i 
< slide
; i
++) { 
1580                 memmove(term
.line
, term
.line 
+ slide
, row 
* sizeof(Line
)); 
1581                 memmove(term
.alt
, term
.alt 
+ slide
, row 
* sizeof(Line
)); 
1583         for(i 
+= row
; i 
< term
.row
; i
++) { 
1588         /* resize to new height */ 
1589         term
.line 
= realloc(term
.line
, row 
* sizeof(Line
)); 
1590         term
.alt  
= realloc(term
.alt
,  row 
* sizeof(Line
)); 
1591         term
.dirty 
= realloc(term
.dirty
, row 
* sizeof(*term
.dirty
)); 
1593         /* resize each row to new width, zero-pad if needed */ 
1594         for(i 
= 0; i 
< minrow
; i
++) { 
1596                 term
.line
[i
] = realloc(term
.line
[i
], col 
* sizeof(Glyph
)); 
1597                 term
.alt
[i
]  = realloc(term
.alt
[i
],  col 
* sizeof(Glyph
)); 
1598                 for(x 
= mincol
; x 
< col
; x
++) { 
1599                         term
.line
[i
][x
].state 
= 0; 
1600                         term
.alt
[i
][x
].state 
= 0; 
1604         /* allocate any new rows */ 
1605         for(/* i == minrow */; i 
< row
; i
++) { 
1607                 term
.line
[i
] = calloc(col
, sizeof(Glyph
)); 
1608                 term
.alt 
[i
] = calloc(col
, sizeof(Glyph
)); 
1611         /* update terminal size */ 
1612         term
.col 
= col
, term
.row 
= row
; 
1613         /* make use of the LIMIT in tmoveto */ 
1614         tmoveto(term
.c
.x
, term
.c
.y
); 
1615         /* reset scrolling region */ 
1616         tsetscroll(0, row
-1); 
1622 xresize(int col
, int row
) { 
1628         xw
.bufw 
= MAX(1, col 
* xw
.cw
); 
1629         xw
.bufh 
= MAX(1, row 
* xw
.ch
); 
1630         newbuf 
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.bufw
, xw
.bufh
, XDefaultDepth(xw
.dpy
, xw
.scr
)); 
1631         XCopyArea(xw
.dpy
, xw
.buf
, newbuf
, dc
.gc
, 0, 0, xw
.bufw
, xw
.bufh
, 0, 0); 
1632         XFreePixmap(xw
.dpy
, xw
.buf
); 
1633         XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[DefaultBG
]); 
1635                 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, oldw
, 0, 
1636                                 xw
.bufw
-oldw
, MIN(xw
.bufh
, oldh
)); 
1637         else if(xw
.bufw 
< oldw 
&& (BORDER 
> 0 || xw
.w 
> xw
.bufw
)) 
1638                 XClearArea(xw
.dpy
, xw
.win
, BORDER
+xw
.bufw
, BORDER
, 
1639                                 xw
.w
-xw
.bufh
-BORDER
, BORDER
+MIN(xw
.bufh
, oldh
), 
1642                 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, 0, oldh
, 
1643                                 xw
.bufw
, xw
.bufh
-oldh
); 
1644         else if(xw
.bufh 
< oldh 
&& (BORDER 
> 0 || xw
.h 
> xw
.bufh
)) 
1645                 XClearArea(xw
.dpy
, xw
.win
, BORDER
, BORDER
+xw
.bufh
, 
1646                                 xw
.w
-2*BORDER
, xw
.h
-xw
.bufh
-BORDER
, 
1655         ulong white 
= WhitePixel(xw
.dpy
, xw
.scr
); 
1657         /* load colors [0-15] colors and [256-LEN(colorname)[ (config.h) */ 
1658         for(i 
= 0; i 
< LEN(colorname
); i
++) { 
1661                 if(!XAllocNamedColor(xw
.dpy
, xw
.cmap
, colorname
[i
], &color
, &color
)) { 
1663                         fprintf(stderr
, "Could not allocate color '%s'\n", colorname
[i
]); 
1665                         dc
.col
[i
] = color
.pixel
; 
1668         /* load colors [16-255] ; same colors as xterm */ 
1669         for(i 
= 16, r 
= 0; r 
< 6; r
++) 
1670                 for(g 
= 0; g 
< 6; g
++) 
1671                         for(b 
= 0; b 
< 6; b
++) { 
1672                                 color
.red 
= r 
== 0 ? 0 : 0x3737 + 0x2828 * r
; 
1673                                 color
.green 
= g 
== 0 ? 0 : 0x3737 + 0x2828 * g
; 
1674                                 color
.blue 
= b 
== 0 ? 0 : 0x3737 + 0x2828 * b
; 
1675                                 if(!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) { 
1677                                         fprintf(stderr
, "Could not allocate color %d\n", i
); 
1679                                         dc
.col
[i
] = color
.pixel
; 
1683         for(r 
= 0; r 
< 24; r
++, i
++) { 
1684                 color
.red 
= color
.green 
= color
.blue 
= 0x0808 + 0x0a0a * r
; 
1685                 if (!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) { 
1687                         fprintf(stderr
, "Could not allocate color %d\n", i
); 
1689                         dc
.col
[i
] = color
.pixel
; 
1694 xclear(int x1
, int y1
, int x2
, int y2
) { 
1695         XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[IS_SET(MODE_REVERSE
) ? DefaultFG 
: DefaultBG
]); 
1696         XFillRectangle(xw
.dpy
, xw
.buf
, dc
.gc
, 
1697                        x1 
* xw
.cw
, y1 
* xw
.ch
, 
1698                        (x2
-x1
+1) * xw
.cw
, (y2
-y1
+1) * xw
.ch
); 
1703         XClassHint 
class = {opt_class 
? opt_class 
: TNAME
, TNAME
}; 
1704         XWMHints wm 
= {.flags 
= InputHint
, .input 
= 1}; 
1706                 .flags 
= PSize 
| PResizeInc 
| PBaseSize
, 
1709                 .height_inc 
= xw
.ch
, 
1711                 .base_height 
= 2*BORDER
, 
1712                 .base_width 
= 2*BORDER
, 
1714         XSetWMProperties(xw
.dpy
, xw
.win
, NULL
, NULL
, NULL
, 0, &size
, &wm
, &class); 
1718 xinitfont(char *fontstr
) { 
1720         char *def
, **missing
; 
1724         set 
= XCreateFontSet(xw
.dpy
, fontstr
, &missing
, &n
, &def
); 
1727                         fprintf(stderr
, "st: missing fontset: %s\n", missing
[n
]); 
1728                 XFreeStringList(missing
); 
1734 xgetfontinfo(XFontSet set
, int *ascent
, int *descent
, short *lbearing
, short *rbearing
) { 
1735         XFontStruct 
**xfonts
; 
1739         *ascent 
= *descent 
= *lbearing 
= *rbearing 
= 0; 
1740         n 
= XFontsOfFontSet(set
, &xfonts
, &font_names
); 
1741         for(i 
= 0; i 
< n
; i
++) { 
1742                 *ascent 
= MAX(*ascent
, (*xfonts
)->ascent
); 
1743                 *descent 
= MAX(*descent
, (*xfonts
)->descent
); 
1744                 *lbearing 
= MAX(*lbearing
, (*xfonts
)->min_bounds
.lbearing
); 
1745                 *rbearing 
= MAX(*rbearing
, (*xfonts
)->max_bounds
.rbearing
); 
1751 initfonts(char *fontstr
, char *bfontstr
) { 
1752         if((dc
.font
.set 
= xinitfont(fontstr
)) == NULL 
|| 
1753            (dc
.bfont
.set 
= xinitfont(bfontstr
)) == NULL
) 
1754                 die("Can't load font %s\n", dc
.font
.set 
? BOLDFONT 
: FONT
); 
1755         xgetfontinfo(dc
.font
.set
, &dc
.font
.ascent
, &dc
.font
.descent
, 
1756             &dc
.font
.lbearing
, &dc
.font
.rbearing
); 
1757         xgetfontinfo(dc
.bfont
.set
, &dc
.bfont
.ascent
, &dc
.bfont
.descent
, 
1758             &dc
.bfont
.lbearing
, &dc
.bfont
.rbearing
); 
1763         XSetWindowAttributes attrs
; 
1767         if(!(xw
.dpy 
= XOpenDisplay(NULL
))) 
1768                 die("Can't open display\n"); 
1769         xw
.scr 
= XDefaultScreen(xw
.dpy
); 
1772         initfonts(FONT
, BOLDFONT
); 
1774         /* XXX: Assuming same size for bold font */ 
1775         xw
.cw 
= dc
.font
.rbearing 
- dc
.font
.lbearing
; 
1776         xw
.ch 
= dc
.font
.ascent 
+ dc
.font
.descent
; 
1779         xw
.cmap 
= XDefaultColormap(xw
.dpy
, xw
.scr
); 
1782         /* window - default size */ 
1783         xw
.bufh 
= term
.row 
* xw
.ch
; 
1784         xw
.bufw 
= term
.col 
* xw
.cw
; 
1785         xw
.h 
= xw
.bufh 
+ 2*BORDER
; 
1786         xw
.w 
= xw
.bufw 
+ 2*BORDER
; 
1788         attrs
.background_pixel 
= dc
.col
[DefaultBG
]; 
1789         attrs
.border_pixel 
= dc
.col
[DefaultBG
]; 
1790         attrs
.bit_gravity 
= NorthWestGravity
; 
1791         attrs
.event_mask 
= FocusChangeMask 
| KeyPressMask
 
1792                 | ExposureMask 
| VisibilityChangeMask 
| StructureNotifyMask
 
1793                 | ButtonMotionMask 
| ButtonPressMask 
| ButtonReleaseMask
 
1794                 | EnterWindowMask 
| LeaveWindowMask
; 
1795         attrs
.colormap 
= xw
.cmap
; 
1797         parent 
= opt_embed 
? strtol(opt_embed
, NULL
, 0) : XRootWindow(xw
.dpy
, xw
.scr
); 
1798         xw
.win 
= XCreateWindow(xw
.dpy
, parent
, 0, 0, 
1799                         xw
.w
, xw
.h
, 0, XDefaultDepth(xw
.dpy
, xw
.scr
), InputOutput
, 
1800                         XDefaultVisual(xw
.dpy
, xw
.scr
), 
1801                         CWBackPixel 
| CWBorderPixel 
| CWBitGravity 
| CWEventMask
 
1804         xw
.buf 
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.bufw
, xw
.bufh
, XDefaultDepth(xw
.dpy
, xw
.scr
)); 
1808         xw
.xim 
= XOpenIM(xw
.dpy
, NULL
, NULL
, NULL
); 
1809         xw
.xic 
= XCreateIC(xw
.xim
, XNInputStyle
, XIMPreeditNothing
 
1810                                            | XIMStatusNothing
, XNClientWindow
, xw
.win
, 
1811                                            XNFocusWindow
, xw
.win
, NULL
); 
1813         dc
.gc 
= XCreateGC(xw
.dpy
, xw
.win
, 0, NULL
); 
1815         /* white cursor, black outline */ 
1816         cursor 
= XCreateFontCursor(xw
.dpy
, XC_xterm
); 
1817         XDefineCursor(xw
.dpy
, xw
.win
, cursor
); 
1818         XRecolorCursor(xw
.dpy
, cursor
, 
1819                 &(XColor
){.red 
= 0xffff, .green 
= 0xffff, .blue 
= 0xffff}, 
1820                 &(XColor
){.red 
= 0x0000, .green 
= 0x0000, .blue 
= 0x0000}); 
1822         xw
.xembed 
= XInternAtom(xw
.dpy
, "_XEMBED", False
); 
1824         XStoreName(xw
.dpy
, xw
.win
, opt_title 
? opt_title 
: "st"); 
1825         XMapWindow(xw
.dpy
, xw
.win
); 
1831 xdraws(char *s
, Glyph base
, int x
, int y
, int charlen
, int bytelen
) { 
1832         int fg 
= base
.fg
, bg 
= base
.bg
, temp
; 
1833         int winx 
= x
*xw
.cw
, winy 
= y
*xw
.ch 
+ dc
.font
.ascent
, width 
= charlen
*xw
.cw
; 
1834         XFontSet fontset 
= dc
.font
.set
; 
1837         /* only switch default fg/bg if term is in RV mode */ 
1838         if(IS_SET(MODE_REVERSE
)) { 
1845         if(base
.mode 
& ATTR_REVERSE
) 
1846                 temp 
= fg
, fg 
= bg
, bg 
= temp
; 
1848         if(base
.mode 
& ATTR_BOLD
) { 
1850                 fontset 
= dc
.bfont
.set
; 
1853         XSetBackground(xw
.dpy
, dc
.gc
, dc
.col
[bg
]); 
1854         XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[fg
]); 
1856         if(base
.mode 
& ATTR_GFX
) { 
1857                 for(i 
= 0; i 
< bytelen
; i
++) { 
1858                         char c 
= gfx
[(uint
)s
[i
] % 256]; 
1861                         else if(s
[i
] > 0x5f) 
1866         XmbDrawImageString(xw
.dpy
, xw
.buf
, fontset
, dc
.gc
, winx
, winy
, s
, bytelen
); 
1868         if(base
.mode 
& ATTR_UNDERLINE
) 
1869                 XDrawLine(xw
.dpy
, xw
.buf
, dc
.gc
, winx
, winy
+1, winx
+width
-1, winy
+1); 
1872 /* copy buffer pixmap to screen pixmap */ 
1874 xcopy(int x
, int y
, int cols
, int rows
) { 
1875         int src_x 
= x
*xw
.cw
, src_y 
= y
*xw
.ch
, src_w 
= cols
*xw
.cw
, src_h 
= rows
*xw
.ch
; 
1876         int dst_x 
= BORDER
+src_x
, dst_y 
= BORDER
+src_y
; 
1877         XCopyArea(xw
.dpy
, xw
.buf
, xw
.win
, dc
.gc
, src_x
, src_y
, src_w
, src_h
, dst_x
, dst_y
); 
1882         static int oldx 
= 0; 
1883         static int oldy 
= 0; 
1885         Glyph g 
= {{' '}, ATTR_NULL
, DefaultBG
, DefaultCS
, 0}; 
1887         LIMIT(oldx
, 0, term
.col
-1); 
1888         LIMIT(oldy
, 0, term
.row
-1); 
1890         if(term
.line
[term
.c
.y
][term
.c
.x
].state 
& GLYPH_SET
) 
1891                 memcpy(g
.c
, term
.line
[term
.c
.y
][term
.c
.x
].c
, UTF_SIZ
); 
1893         /* remove the old cursor */ 
1894         if(term
.line
[oldy
][oldx
].state 
& GLYPH_SET
) { 
1895                 sl 
= utf8size(term
.line
[oldy
][oldx
].c
); 
1896                 xdraws(term
.line
[oldy
][oldx
].c
, term
.line
[oldy
][oldx
], oldx
, oldy
, 1, sl
); 
1898                 xclear(oldx
, oldy
, oldx
, oldy
); 
1900         xcopy(oldx
, oldy
, 1, 1); 
1902         /* draw the new one */ 
1903         if(!(term
.c
.state 
& CURSOR_HIDE
)) { 
1904                 if(!(xw
.state 
& WIN_FOCUSED
)) 
1907                 if(IS_SET(MODE_REVERSE
)) 
1908                         g
.mode 
|= ATTR_REVERSE
, g
.fg 
= DefaultCS
, g
.bg 
= DefaultFG
; 
1911                 xdraws(g
.c
, g
, term
.c
.x
, term
.c
.y
, 1, sl
); 
1912                 oldx 
= term
.c
.x
, oldy 
= term
.c
.y
; 
1915         xcopy(term
.c
.x
, term
.c
.y
, 1, 1); 
1920         drawregion(0, 0, term
.col
, term
.row
); 
1921         gettimeofday(&xw
.lastdraw
, NULL
); 
1925 drawregion(int x1
, int y1
, int x2
, int y2
) { 
1926         int ic
, ib
, x
, y
, ox
, sl
; 
1928         char buf
[DRAW_BUF_SIZ
]; 
1930         if(!(xw
.state 
& WIN_VISIBLE
)) 
1933         for(y 
= y1
; y 
< y2
; y
++) { 
1936                 xclear(0, y
, term
.col
, y
); 
1938                 base 
= term
.line
[y
][0]; 
1940                 for(x 
= x1
; x 
< x2
; x
++) { 
1941                         new = term
.line
[y
][x
]; 
1942                         if(sel
.bx 
!= -1 && *(new.c
) && selected(x
, y
)) 
1943                                 new.mode 
^= ATTR_REVERSE
; 
1944                         if(ib 
> 0 && (!(new.state 
& GLYPH_SET
) || ATTRCMP(base
, new) || 
1945                                                   ib 
>= DRAW_BUF_SIZ
-UTF_SIZ
)) { 
1946                                 xdraws(buf
, base
, ox
, y
, ic
, ib
); 
1949                         if(new.state 
& GLYPH_SET
) { 
1954                                 sl 
= utf8size(new.c
); 
1955                                 memcpy(buf
+ib
, new.c
, sl
); 
1961                         xdraws(buf
, base
, ox
, y
, ic
, ib
); 
1962                 xcopy(0, y
, term
.col
, 1); 
1968 expose(XEvent 
*ev
) { 
1969         XExposeEvent 
*e 
= &ev
->xexpose
; 
1970         if(xw
.state 
& WIN_REDRAW
) { 
1972                         xw
.state 
&= ~WIN_REDRAW
; 
1973                         xcopy(0, 0, term
.col
, term
.row
); 
1976                 XCopyArea(xw
.dpy
, xw
.buf
, xw
.win
, dc
.gc
, e
->x
-BORDER
, e
->y
-BORDER
, 
1977                                 e
->width
, e
->height
, e
->x
, e
->y
); 
1981 visibility(XEvent 
*ev
) { 
1982         XVisibilityEvent 
*e 
= &ev
->xvisibility
; 
1983         if(e
->state 
== VisibilityFullyObscured
) 
1984                 xw
.state 
&= ~WIN_VISIBLE
; 
1985         else if(!(xw
.state 
& WIN_VISIBLE
)) 
1986                 /* need a full redraw for next Expose, not just a buf copy */ 
1987                 xw
.state 
|= WIN_VISIBLE 
| WIN_REDRAW
; 
1992         xw
.state 
&= ~WIN_VISIBLE
; 
1996 xseturgency(int add
) { 
1997         XWMHints 
*h 
= XGetWMHints(xw
.dpy
, xw
.win
); 
1998         h
->flags 
= add 
? (h
->flags 
| XUrgencyHint
) : (h
->flags 
& ~XUrgencyHint
); 
1999         XSetWMHints(xw
.dpy
, xw
.win
, h
); 
2005         if(ev
->type 
== FocusIn
) { 
2006                 xw
.state 
|= WIN_FOCUSED
; 
2009                 xw
.state 
&= ~WIN_FOCUSED
; 
2014 kmap(KeySym k
, uint state
) { 
2017         for(i 
= 0; i 
< LEN(key
); i
++) { 
2018                 uint mask 
= key
[i
].mask
; 
2019                 if(key
[i
].k 
== k 
&& ((state 
& mask
) == mask 
|| (mask 
== XK_NO_MOD 
&& !state
))) 
2020                         return (char*)key
[i
].s
; 
2026 kpress(XEvent 
*ev
) { 
2027         XKeyEvent 
*e 
= &ev
->xkey
; 
2036         meta 
= e
->state 
& Mod1Mask
; 
2037         shift 
= e
->state 
& ShiftMask
; 
2038         len 
= XmbLookupString(xw
.xic
, e
, buf
, sizeof(buf
), &ksym
, &status
); 
2040         /* 1. custom keys from config.h */ 
2041         if((customkey 
= kmap(ksym
, e
->state
))) 
2042                 ttywrite(customkey
, strlen(customkey
)); 
2043         /* 2. hardcoded (overrides X lookup) */ 
2050                         /* XXX: shift up/down doesn't work */ 
2051                         sprintf(buf
, "\033%c%c", IS_SET(MODE_APPKEYPAD
) ? 'O' : '[', (shift 
? "dacb":"DACB")[ksym 
- XK_Left
]); 
2059                         if(IS_SET(MODE_CRLF
)) 
2060                                 ttywrite("\r\n", 2); 
2067                                 if(meta 
&& len 
== 1) 
2068                                         ttywrite("\033", 1); 
2076 cmessage(XEvent 
*e
) { 
2078            http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */ 
2079         if (e
->xclient
.message_type 
== xw
.xembed 
&& e
->xclient
.format 
== 32) { 
2080                 if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_IN
) { 
2081                         xw
.state 
|= WIN_FOCUSED
; 
2083                 } else if (e
->xclient
.data
.l
[1] == XEMBED_FOCUS_OUT
) { 
2084                         xw
.state 
&= ~WIN_FOCUSED
; 
2094         if(e
->xconfigure
.width 
== xw
.w 
&& e
->xconfigure
.height 
== xw
.h
) 
2097         xw
.w 
= e
->xconfigure
.width
; 
2098         xw
.h 
= e
->xconfigure
.height
; 
2099         col 
= (xw
.w 
- 2*BORDER
) / xw
.cw
; 
2100         row 
= (xw
.h 
- 2*BORDER
) / xw
.ch
; 
2101         if(col 
== term
.col 
&& row 
== term
.row
) 
2103         if(tresize(col
, row
)) 
2105         ttyresize(col
, row
); 
2110 last_draw_too_old(void) { 
2112         gettimeofday(&now
, NULL
); 
2113         return TIMEDIFF(now
, xw
.lastdraw
) >= DRAW_TIMEOUT
/1000; 
2120         int xfd 
= XConnectionNumber(xw
.dpy
); 
2121         struct timeval timeout 
= {0}; 
2122         bool stuff_to_print 
= 0; 
2126                 FD_SET(cmdfd
, &rfd
); 
2129                 timeout
.tv_usec 
= SELECT_TIMEOUT
; 
2130                 if(select(MAX(xfd
, cmdfd
)+1, &rfd
, NULL
, NULL
, &timeout
) < 0) { 
2133                         die("select failed: %s\n", SERRNO
); 
2135                 if(FD_ISSET(cmdfd
, &rfd
)) { 
2140                 if(stuff_to_print 
&& last_draw_too_old()) { 
2145                 while(XPending(xw
.dpy
)) { 
2146                         XNextEvent(xw
.dpy
, &ev
); 
2147                         if(XFilterEvent(&ev
, xw
.win
)) 
2149                         if(handler
[ev
.type
]) 
2150                                 (handler
[ev
.type
])(&ev
); 
2156 main(int argc
, char *argv
[]) { 
2159         for(i 
= 1; i 
< argc
; i
++) { 
2160                 switch(argv
[i
][0] != '-' || argv
[i
][2] ? -1 : argv
[i
][1]) { 
2162                         if(++i 
< argc
) opt_title 
= argv
[i
]; 
2165                         if(++i 
< argc
) opt_class 
= argv
[i
]; 
2168                         if(++i 
< argc
) opt_embed 
= argv
[i
]; 
2171                         /* eat every remaining arguments */ 
2172                         if(++i 
< argc
) opt_cmd 
= &argv
[i
]; 
2181         setlocale(LC_CTYPE
, "");