1 /* See LICENSE for licence details. */ 
   2 #define _XOPEN_SOURCE 600 
  13 #include <sys/ioctl.h> 
  14 #include <sys/select.h> 
  16 #include <sys/types.h> 
  20 #include <X11/Xatom.h> 
  21 #include <X11/keysym.h> 
  22 #include <X11/Xutil.h> 
  26 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 
  28 #elif defined(__FreeBSD__) || defined(__DragonFly__) 
  33         "st-" VERSION ", (c) 2010 st engineers\n" \ 
  34         "usage: st [-t title] [-c class] [-e cmd] [-v]\n" 
  37 #define ESC_TITLE_SIZ 256 
  38 #define ESC_BUF_SIZ   256 
  39 #define ESC_ARG_SIZ   16 
  40 #define DRAW_BUF_SIZ  1024 
  43 #define SERRNO strerror(errno) 
  44 #define MIN(a, b)  ((a) < (b) ? (a) : (b)) 
  45 #define MAX(a, b)  ((a) < (b) ? (b) : (a)) 
  46 #define LEN(a)     (sizeof(a) / sizeof(a[0])) 
  47 #define DEFAULT(a, b)     (a) = (a) ? (a) : (b)     
  48 #define BETWEEN(x, a, b)  ((a) <= (x) && (x) <= (b)) 
  49 #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 
  50 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg) 
  51 #define IS_SET(flag) (term.mode & (flag)) 
  53 /* Attribute, Cursor, Character state, Terminal mode, Screen draw mode */ 
  54 enum { ATTR_NULL
=0 , ATTR_REVERSE
=1 , ATTR_UNDERLINE
=2, ATTR_BOLD
=4, ATTR_GFX
=8 }; 
  55 enum { CURSOR_UP
, CURSOR_DOWN
, CURSOR_LEFT
, CURSOR_RIGHT
, 
  56        CURSOR_SAVE
, CURSOR_LOAD 
}; 
  57 enum { CURSOR_DEFAULT 
= 0, CURSOR_HIDE 
= 1, CURSOR_WRAPNEXT 
= 2 }; 
  58 enum { GLYPH_SET
=1, GLYPH_DIRTY
=2 }; 
  59 enum { MODE_WRAP
=1, MODE_INSERT
=2, MODE_APPKEYPAD
=4, MODE_ALTSCREEN
=8,  
  61 enum { ESC_START
=1, ESC_CSI
=2, ESC_OSC
=4, ESC_TITLE
=8, ESC_ALTCHARSET
=16 }; 
  62 enum { SCREEN_UPDATE
, SCREEN_REDRAW 
}; 
  63 enum { WIN_VISIBLE
=1, WIN_REDRAW
=2, WIN_FOCUSED
=4 }; 
  66 enum { B0
=1, B1
=2, B2
=4, B3
=8, B4
=16, B5
=32, B6
=64, B7
=128 }; 
  69         char c
[UTF_SIZ
];     /* character code */ 
  70         char mode
;  /* attribute flags */ 
  71         int fg
;     /* foreground      */ 
  72         int bg
;     /* background      */ 
  73         char state
; /* state flags     */ 
  79         Glyph attr
;      /* current char attributes */ 
  85 /* CSI Escape sequence structs */ 
  86 /* ESC '[' [[ [<priv>] <arg> [;]] <mode>] */ 
  88         char buf
[ESC_BUF_SIZ
]; /* raw string */ 
  89         int len
;                           /* raw string length */ 
  92         int narg
;                          /* nb of args */ 
  96 /* Internal representation of the screen */ 
 100         Line
* line
;     /* screen */ 
 101         Line
* alt
;      /* alternate screen */ 
 102         TCursor c
;      /* cursor */ 
 103         int top
;        /* top    scroll limit */ 
 104         int bot
;        /* bottom scroll limit */ 
 105         int mode
;       /* terminal mode flags */ 
 106         int esc
;        /* escape state flags */ 
 107         char title
[ESC_TITLE_SIZ
]; 
 111 /* Purely graphic info */ 
 120         int w
;  /* window width  */ 
 121         int h
;  /* window height */ 
 122         int bufw
; /* pixmap width  */ 
 123         int bufh
; /* pixmap height */ 
 124         int ch
; /* char height */ 
 125         int cw
; /* char width  */ 
 126         char state
; /* focus, redraw, visible */ 
 134 /* Drawing Context */ 
 136         unsigned long col
[256]; 
 147 /* TODO: use better name for vars... */ 
 152         struct {int x
, y
;}  b
, e
; 
 158 static void die(const char *errstr
, ...); 
 159 static void draw(int); 
 160 static void execsh(void); 
 161 static void sigchld(int); 
 162 static void run(void); 
 164 static void csidump(void); 
 165 static void csihandle(void); 
 166 static void csiparse(void); 
 167 static void csireset(void); 
 169 static void tclearregion(int, int, int, int); 
 170 static void tcursor(int); 
 171 static void tdeletechar(int); 
 172 static void tdeleteline(int); 
 173 static void tinsertblank(int); 
 174 static void tinsertblankline(int); 
 175 static void tmoveto(int, int); 
 176 static void tnew(int, int); 
 177 static void tnewline(int); 
 178 static void tputtab(void); 
 179 static void tputc(char*); 
 180 static void treset(void); 
 181 static int tresize(int, int); 
 182 static void tscrollup(int, int); 
 183 static void tscrolldown(int, int); 
 184 static void tsetattr(int*, int); 
 185 static void tsetchar(char*); 
 186 static void tsetscroll(int, int); 
 187 static void tswapscreen(void); 
 189 static void ttynew(void); 
 190 static void ttyread(void); 
 191 static void ttyresize(int, int); 
 192 static void ttywrite(const char *, size_t); 
 194 static void xdraws(char *, Glyph
, int, int, int, int); 
 195 static void xhints(void); 
 196 static void xclear(int, int, int, int); 
 197 static void xdrawcursor(void); 
 198 static void xinit(void); 
 199 static void xloadcols(void); 
 200 static void xseturgency(int); 
 201 static void xsetsel(char*); 
 202 static void xresize(int, int); 
 204 static void expose(XEvent 
*); 
 205 static void visibility(XEvent 
*); 
 206 static void unmap(XEvent 
*); 
 207 static char* kmap(KeySym
); 
 208 static void kpress(XEvent 
*); 
 209 static void resize(XEvent 
*); 
 210 static void focus(XEvent 
*); 
 211 static void brelease(XEvent 
*); 
 212 static void bpress(XEvent 
*); 
 213 static void bmotion(XEvent 
*); 
 214 static void selnotify(XEvent 
*); 
 215 static void selrequest(XEvent 
*); 
 217 static void selinit(void); 
 218 static inline int selected(int, int); 
 219 static void selcopy(void); 
 220 static void selpaste(void); 
 222 static int utf8decode(char *, long *); 
 223 static int utf8encode(long *, char *); 
 224 static int utf8size(char *); 
 225 static int isfullutf8(char *, int); 
 227 static void (*handler
[LASTEvent
])(XEvent 
*) = { 
 229         [ConfigureNotify
] = resize
, 
 230         [VisibilityNotify
] = visibility
, 
 231         [UnmapNotify
] = unmap
, 
 235         [MotionNotify
] = bmotion
, 
 236         [ButtonPress
] = bpress
, 
 237         [ButtonRelease
] = brelease
, 
 238         [SelectionNotify
] = selnotify
, 
 239         [SelectionRequest
] = selrequest
, 
 246 static CSIEscape escseq
; 
 249 static Selection sel
; 
 250 static char **opt_cmd  
= NULL
; 
 251 static char *opt_title 
= NULL
; 
 252 static char *opt_class 
= NULL
; 
 255 utf8decode(char *s
, long *u
) { 
 261         if(~c
&B7
) { /* 0xxxxxxx */ 
 264         } else if((c
&(B7
|B6
|B5
)) == (B7
|B6
)) { /* 110xxxxx */ 
 265                 *u 
= c
&(B4
|B3
|B2
|B1
|B0
); 
 267         } else if((c
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) { /* 1110xxxx */ 
 268                 *u 
= c
&(B3
|B2
|B1
|B0
); 
 270         } else if((c
&(B7
|B6
|B5
|B4
|B3
)) == (B7
|B6
|B5
|B4
)) { /* 11110xxx */ 
 275         for(i
=n
,++s
; i
>0; --i
,++rtn
,++s
) { 
 277                 if((c
&(B7
|B6
)) != B7
) /* 10xxxxxx */ 
 280                 *u 
|= c
&(B5
|B4
|B3
|B2
|B1
|B0
); 
 282         if((n 
== 1 && *u 
< 0x80) || 
 283            (n 
== 2 && *u 
< 0x800) || 
 284            (n 
== 3 && *u 
< 0x10000) || 
 285            (*u 
>= 0xD800 && *u 
<= 0xDFFF)) 
 294 utf8encode(long *u
, char *s
) { 
 299         sp 
= (unsigned char*) s
; 
 302                 *sp 
= uc
; /* 0xxxxxxx */ 
 304         } else if(*u 
< 0x800) { 
 305                 *sp 
= (uc 
>> 6) | (B7
|B6
); /* 110xxxxx */ 
 307         } else if(uc 
< 0x10000) { 
 308                 *sp 
= (uc 
>> 12) | (B7
|B6
|B5
); /* 1110xxxx */ 
 310         } else if(uc 
<= 0x10FFFF) { 
 311                 *sp 
= (uc 
>> 18) | (B7
|B6
|B5
|B4
); /* 11110xxx */ 
 316         for(i
=n
,++sp
; i
>0; --i
,++sp
) 
 317                 *sp 
= ((uc 
>> 6*(i
-1)) & (B5
|B4
|B3
|B2
|B1
|B0
)) | B7
; /* 10xxxxxx */ 
 327 /* use this if your buffer is less than UTF_SIZ, it returns 1 if you can decode 
 328    UTF-8 otherwise return 0 */ 
 330 isfullutf8(char *s
, int b
) { 
 331         unsigned char *c1
, *c2
, *c3
; 
 333         c1 
= (unsigned char *) s
; 
 334         c2 
= (unsigned char *) ++s
; 
 335         c3 
= (unsigned char *) ++s
; 
 338         else if((*c1
&(B7
|B6
|B5
)) == (B7
|B6
) && b 
== 1) 
 340         else if((*c1
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
) && 
 342             ((b 
== 2) && (*c2
&(B7
|B6
)) == B7
))) 
 344         else if((*c1
&(B7
|B6
|B5
|B4
|B3
)) == (B7
|B6
|B5
|B4
) && 
 346             ((b 
== 2) && (*c2
&(B7
|B6
)) == B7
) || 
 347             ((b 
== 3) && (*c2
&(B7
|B6
)) == B7 
&& (*c3
&(B7
|B6
)) == B7
))) 
 355         unsigned char c 
= *s
; 
 359         else if ((c
&(B7
|B6
|B5
)) == (B7
|B6
)) 
 361         else if ((c
&(B7
|B6
|B5
|B4
)) == (B7
|B6
|B5
)) 
 375 selected(int x
, int y
) { 
 376         if(sel
.ey 
== y 
&& sel
.by 
== y
) { 
 377                 int bx 
= MIN(sel
.bx
, sel
.ex
); 
 378                 int ex 
= MAX(sel
.bx
, sel
.ex
); 
 379                 return BETWEEN(x
, bx
, ex
); 
 381         return ((sel
.b
.y 
< y
&&y 
< sel
.e
.y
) || (y
==sel
.e
.y 
&& x
<=sel
.e
.x
))  
 382                 || (y
==sel
.b
.y 
&& x
>=sel
.b
.x 
&& (x
<=sel
.e
.x 
|| sel
.b
.y
!=sel
.e
.y
)); 
 386 getbuttoninfo(XEvent 
*e
, int *b
, int *x
, int *y
) { 
 388                 *b 
= e
->xbutton
.button
; 
 390         *x 
= e
->xbutton
.x
/xw
.cw
; 
 391         *y 
= e
->xbutton
.y
/xw
.ch
; 
 392         sel
.b
.x 
= sel
.by 
< sel
.ey 
? sel
.bx 
: sel
.ex
; 
 393         sel
.b
.y 
= MIN(sel
.by
, sel
.ey
); 
 394         sel
.e
.x 
= sel
.by 
< sel
.ey 
? sel
.ex 
: sel
.bx
; 
 395         sel
.e
.y 
= MAX(sel
.by
, sel
.ey
); 
 401         sel
.ex 
= sel
.bx 
= e
->xbutton
.x
/xw
.cw
; 
 402         sel
.ey 
= sel
.by 
= e
->xbutton
.y
/xw
.ch
; 
 408         int x
, y
, sz
, sl
, ls 
= 0; 
 413                 sz 
= (term
.col
+1) * (sel
.e
.y
-sel
.b
.y
+1) * UTF_SIZ
; 
 414                 ptr 
= str 
= malloc(sz
); 
 415                 for(y 
= 0; y 
< term
.row
; y
++) { 
 416                         for(x 
= 0; x 
< term
.col
; x
++) 
 417                                 if(term
.line
[y
][x
].state 
& GLYPH_SET 
&& (ls 
= selected(x
, y
))) { 
 418                                         sl 
= utf8size(term
.line
[y
][x
].c
); 
 419                                         memcpy(ptr
, term
.line
[y
][x
].c
, sl
); 
 431 selnotify(XEvent 
*e
) { 
 432         unsigned long nitems
; 
 433         unsigned long ofs
, rem
; 
 440                 if(XGetWindowProperty(xw
.dpy
, xw
.win
, XA_PRIMARY
, ofs
, BUFSIZ
/4, 
 441                                         False
, AnyPropertyType
, &type
, &format
, 
 442                                         &nitems
, &rem
, &data
)) { 
 443                         fprintf(stderr
, "Clipboard allocation failed\n"); 
 446                 ttywrite((const char *) data
, nitems 
* format 
/ 8); 
 448                 /* number of 32-bit chunks returned */ 
 449                 ofs 
+= nitems 
* format 
/ 32; 
 455         XConvertSelection(xw
.dpy
, XA_PRIMARY
, XA_STRING
, XA_PRIMARY
, xw
.win
, CurrentTime
); 
 459 selrequest(XEvent 
*e
) { 
 460         XSelectionRequestEvent 
*xsre
; 
 464         xsre 
= (XSelectionRequestEvent 
*) e
; 
 465         xev
.type 
= SelectionNotify
; 
 466         xev
.requestor 
= xsre
->requestor
; 
 467         xev
.selection 
= xsre
->selection
; 
 468         xev
.target 
= xsre
->target
; 
 469         xev
.time 
= xsre
->time
; 
 473         xa_targets 
= XInternAtom(xw
.dpy
, "TARGETS", 0); 
 474         if(xsre
->target 
== xa_targets
) { 
 475                 /* respond with the supported type */ 
 476                 Atom string 
= XA_STRING
; 
 477                 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, 
 478                                 XA_ATOM
, 32, PropModeReplace
, 
 479                                 (unsigned char *) &string
, 1); 
 480                 xev
.property 
= xsre
->property
; 
 481         } else if(xsre
->target 
== XA_STRING
) { 
 482                 XChangeProperty(xsre
->display
, xsre
->requestor
, xsre
->property
, 
 483                                 xsre
->target
, 8, PropModeReplace
, 
 484                                 (unsigned char *) sel
.clip
, strlen(sel
.clip
)); 
 485                 xev
.property 
= xsre
->property
; 
 488         /* all done, send a notification to the listener */ 
 489         if(!XSendEvent(xsre
->display
, xsre
->requestor
, True
, 0, (XEvent 
*) &xev
)) 
 490                 fprintf(stderr
, "Error sending SelectionNotify event\n"); 
 495         /* register the selection for both the clipboard and the primary */ 
 501         XSetSelectionOwner(xw
.dpy
, XA_PRIMARY
, xw
.win
, CurrentTime
); 
 503         clipboard 
= XInternAtom(xw
.dpy
, "CLIPBOARD", 0); 
 504         XSetSelectionOwner(xw
.dpy
, clipboard
, xw
.win
, CurrentTime
); 
 509 /* TODO: doubleclick to select word */ 
 511 brelease(XEvent 
*e
) { 
 514         getbuttoninfo(e
, &b
, &sel
.ex
, &sel
.ey
); 
 515         if(sel
.bx
==sel
.ex 
&& sel
.by
==sel
.ey
) { 
 529                 getbuttoninfo(e
, NULL
, &sel
.ex
, &sel
.ey
); 
 530                 /* XXX: draw() can't keep up, disabled for now. 
 531                    selection is visible on button release. 
 537 die(const char *errstr
, ...) { 
 540         va_start(ap
, errstr
); 
 541         vfprintf(stderr
, errstr
, ap
); 
 549         char *envshell 
= getenv("SHELL"); 
 551         DEFAULT(envshell
, "sh"); 
 552         putenv("TERM="TNAME
); 
 553         args 
= opt_cmd 
? opt_cmd 
: (char*[]){envshell
, "-i", NULL
}; 
 554         execvp(args
[0], args
); 
 561         if(waitpid(pid
, &stat
, 0) < 0) 
 562                 die("Waiting for pid %hd failed: %s\n", pid
, SERRNO
); 
 564                 exit(WEXITSTATUS(stat
)); 
 573         /* seems to work fine on linux, openbsd and freebsd */ 
 574         struct winsize w 
= {term
.row
, term
.col
, 0, 0}; 
 575         if(openpty(&m
, &s
, NULL
, NULL
, &w
) < 0) 
 576                 die("openpty failed: %s\n", SERRNO
); 
 578         switch(pid 
= fork()) { 
 580                 die("fork failed\n"); 
 583                 setsid(); /* create a new process group */ 
 584                 dup2(s
, STDIN_FILENO
); 
 585                 dup2(s
, STDOUT_FILENO
); 
 586                 dup2(s
, STDERR_FILENO
); 
 587                 if(ioctl(s
, TIOCSCTTY
, NULL
) < 0) 
 588                         die("ioctl TIOCSCTTY failed: %s\n", SERRNO
); 
 596                 signal(SIGCHLD
, sigchld
); 
 603         fprintf(stderr
, " %02x '%c' ", c
, isprint(c
)?c
:'.'); 
 605                 fprintf(stderr
, "\n"); 
 610         static char buf
[BUFSIZ
]; 
 611         static int buflen 
= 0;  
 614         int charsize
; /* size of utf8 char in bytes */ 
 618         /* append read bytes to unprocessed bytes */ 
 619         if((ret 
= read(cmdfd
, buf
+buflen
, LEN(buf
)-buflen
)) < 0) 
 620                 die("Couldn't read from shell: %s\n", SERRNO
); 
 622         /* process every complete utf8 char */ 
 625         while(buflen 
>= UTF_SIZ 
|| isfullutf8(ptr
,buflen
)) { 
 626                 charsize 
= utf8decode(ptr
, &utf8c
); 
 627                 utf8encode(&utf8c
, s
); 
 633         /* keep any uncomplete utf8 char for the next call */ 
 634         memmove(buf
, ptr
, buflen
); 
 638 ttywrite(const char *s
, size_t n
) { 
 639         if(write(cmdfd
, s
, n
) == -1) 
 640                 die("write error on tty: %s\n", SERRNO
); 
 644 ttyresize(int x
, int y
) { 
 649         w
.ws_xpixel 
= w
.ws_ypixel 
= 0; 
 650         if(ioctl(cmdfd
, TIOCSWINSZ
, &w
) < 0) 
 651                 fprintf(stderr
, "Couldn't set window size: %s\n", SERRNO
); 
 658         if(mode 
== CURSOR_SAVE
) 
 660         else if(mode 
== CURSOR_LOAD
) 
 661                 term
.c 
= c
, tmoveto(c
.x
, c
.y
); 
 670         }, .x 
= 0, .y 
= 0, .state 
= CURSOR_DEFAULT
}; 
 672         term
.top 
= 0, term
.bot 
= term
.row 
- 1; 
 673         term
.mode 
= MODE_WRAP
; 
 674         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
 678 tnew(int col
, int row
) { 
 679         /* set screen size */ 
 680         term
.row 
= row
, term
.col 
= col
; 
 681         term
.line 
= malloc(term
.row 
* sizeof(Line
)); 
 682         term
.alt  
= malloc(term
.row 
* sizeof(Line
)); 
 683         for(row 
= 0 ; row 
< term
.row
; row
++) { 
 684                 term
.line
[row
] = malloc(term
.col 
* sizeof(Glyph
)); 
 685                 term
.alt 
[row
] = malloc(term
.col 
* sizeof(Glyph
)); 
 693         Line
* tmp 
= term
.line
; 
 694         term
.line 
= term
.alt
; 
 696         term
.mode 
^= MODE_ALTSCREEN
; 
 700 tscrolldown(int orig
, int n
) { 
 704         LIMIT(n
, 0, term
.bot
-orig
+1); 
 706         tclearregion(0, term
.bot
-n
+1, term
.col
-1, term
.bot
); 
 708         for(i 
= term
.bot
; i 
>= orig
+n
; i
--) { 
 710                 term
.line
[i
] = term
.line
[i
-n
]; 
 711                 term
.line
[i
-n
] = temp
; 
 716 tscrollup(int orig
, int n
) { 
 719         LIMIT(n
, 0, term
.bot
-orig
+1); 
 721         tclearregion(0, orig
, term
.col
-1, orig
+n
-1); 
 723         for(i 
= orig
; i 
<= term
.bot
-n
; i
++) {  
 725                  term
.line
[i
] = term
.line
[i
+n
];  
 726                  term
.line
[i
+n
] = temp
; 
 731 tnewline(int first_col
) { 
 734                 tscrollup(term
.top
, 1); 
 737         tmoveto(first_col 
? 0 : term
.c
.x
, y
); 
 743         char *p 
= escseq
.buf
; 
 747                 escseq
.priv 
= 1, p
++; 
 749         while(p 
< escseq
.buf
+escseq
.len
) { 
 751                         escseq
.arg
[escseq
.narg
] *= 10; 
 752                         escseq
.arg
[escseq
.narg
] += *p
++ - '0'/*, noarg = 0 */; 
 754                 if(*p 
== ';' && escseq
.narg
+1 < ESC_ARG_SIZ
) 
 765 tmoveto(int x
, int y
) { 
 766         LIMIT(x
, 0, term
.col
-1); 
 767         LIMIT(y
, 0, term
.row
-1); 
 768         term
.c
.state 
&= ~CURSOR_WRAPNEXT
; 
 775         term
.line
[term
.c
.y
][term
.c
.x
] = term
.c
.attr
; 
 776         memcpy(term
.line
[term
.c
.y
][term
.c
.x
].c
, c
, UTF_SIZ
); 
 777         term
.line
[term
.c
.y
][term
.c
.x
].state 
|= GLYPH_SET
; 
 781 tclearregion(int x1
, int y1
, int x2
, int y2
) { 
 785                 temp 
= x1
, x1 
= x2
, x2 
= temp
; 
 787                 temp 
= y1
, y1 
= y2
, y2 
= temp
; 
 789         LIMIT(x1
, 0, term
.col
-1); 
 790         LIMIT(x2
, 0, term
.col
-1); 
 791         LIMIT(y1
, 0, term
.row
-1); 
 792         LIMIT(y2
, 0, term
.row
-1); 
 794         for(y 
= y1
; y 
<= y2
; y
++) 
 795                 for(x 
= x1
; x 
<= x2
; x
++) 
 796                         term
.line
[y
][x
].state 
= 0; 
 801         int src 
= term
.c
.x 
+ n
; 
 803         int size 
= term
.col 
- src
; 
 805         if(src 
>= term
.col
) { 
 806                 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
 809         memmove(&term
.line
[term
.c
.y
][dst
], &term
.line
[term
.c
.y
][src
], size 
* sizeof(Glyph
)); 
 810         tclearregion(term
.col
-n
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
 814 tinsertblank(int n
) { 
 817         int size 
= term
.col 
- dst
; 
 819         if(dst 
>= term
.col
) { 
 820                 tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
 823         memmove(&term
.line
[term
.c
.y
][dst
], &term
.line
[term
.c
.y
][src
], size 
* sizeof(Glyph
)); 
 824         tclearregion(src
, term
.c
.y
, dst 
- 1, term
.c
.y
); 
 828 tinsertblankline(int n
) { 
 829         if(term
.c
.y 
< term
.top 
|| term
.c
.y 
> term
.bot
) 
 832         tscrolldown(term
.c
.y
, n
); 
 837         if(term
.c
.y 
< term
.top 
|| term
.c
.y 
> term
.bot
) 
 840         tscrollup(term
.c
.y
, n
); 
 844 tsetattr(int *attr
, int l
) { 
 847         for(i 
= 0; i 
< l
; i
++) { 
 850                         term
.c
.attr
.mode 
&= ~(ATTR_REVERSE 
| ATTR_UNDERLINE 
| ATTR_BOLD
); 
 851                         term
.c
.attr
.fg 
= DefaultFG
; 
 852                         term
.c
.attr
.bg 
= DefaultBG
; 
 855                         term
.c
.attr
.mode 
|= ATTR_BOLD
;    
 858                         term
.c
.attr
.mode 
|= ATTR_UNDERLINE
; 
 861                         term
.c
.attr
.mode 
|= ATTR_REVERSE
;        
 864                         term
.c
.attr
.mode 
&= ~ATTR_BOLD
;   
 867                         term
.c
.attr
.mode 
&= ~ATTR_UNDERLINE
; 
 870                         term
.c
.attr
.mode 
&= ~ATTR_REVERSE
;        
 873                         if (i 
+ 2 < l 
&& attr
[i 
+ 1] == 5) { 
 875                                 if (BETWEEN(attr
[i
], 0, 255)) 
 876                                         term
.c
.attr
.fg 
= attr
[i
]; 
 878                                         fprintf(stderr
, "erresc: bad fgcolor %d\n", attr
[i
]); 
 881                                 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]);  
 884                         term
.c
.attr
.fg 
= DefaultFG
; 
 887                         if (i 
+ 2 < l 
&& attr
[i 
+ 1] == 5) { 
 889                                 if (BETWEEN(attr
[i
], 0, 255)) 
 890                                         term
.c
.attr
.bg 
= attr
[i
]; 
 892                                         fprintf(stderr
, "erresc: bad bgcolor %d\n", attr
[i
]); 
 895                                 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]);  
 898                         term
.c
.attr
.bg 
= DefaultBG
; 
 901                         if(BETWEEN(attr
[i
], 30, 37)) 
 902                                 term
.c
.attr
.fg 
= attr
[i
] - 30; 
 903                         else if(BETWEEN(attr
[i
], 40, 47)) 
 904                                 term
.c
.attr
.bg 
= attr
[i
] - 40; 
 905                         else if(BETWEEN(attr
[i
], 90, 97)) 
 906                                 term
.c
.attr
.fg 
= attr
[i
] - 90 + 8; 
 907                         else if(BETWEEN(attr
[i
], 100, 107)) 
 908                                 term
.c
.attr
.fg 
= attr
[i
] - 100 + 8; 
 910                                 fprintf(stderr
, "erresc: gfx attr %d unknown\n", attr
[i
]), csidump(); 
 918 tsetscroll(int t
, int b
) { 
 921         LIMIT(t
, 0, term
.row
-1); 
 922         LIMIT(b
, 0, term
.row
-1); 
 934         switch(escseq
.mode
) { 
 937                 printf("erresc: unknown csi "); 
 941         case '@': /* ICH -- Insert <n> blank char */ 
 942                 DEFAULT(escseq
.arg
[0], 1); 
 943                 tinsertblank(escseq
.arg
[0]); 
 945         case 'A': /* CUU -- Cursor <n> Up */ 
 947                 DEFAULT(escseq
.arg
[0], 1); 
 948                 tmoveto(term
.c
.x
, term
.c
.y
-escseq
.arg
[0]); 
 950         case 'B': /* CUD -- Cursor <n> Down */ 
 951                 DEFAULT(escseq
.arg
[0], 1); 
 952                 tmoveto(term
.c
.x
, term
.c
.y
+escseq
.arg
[0]); 
 954         case 'C': /* CUF -- Cursor <n> Forward */ 
 956                 DEFAULT(escseq
.arg
[0], 1); 
 957                 tmoveto(term
.c
.x
+escseq
.arg
[0], term
.c
.y
); 
 959         case 'D': /* CUB -- Cursor <n> Backward */ 
 960                 DEFAULT(escseq
.arg
[0], 1); 
 961                 tmoveto(term
.c
.x
-escseq
.arg
[0], term
.c
.y
); 
 963         case 'E': /* CNL -- Cursor <n> Down and first col */ 
 964                 DEFAULT(escseq
.arg
[0], 1); 
 965                 tmoveto(0, term
.c
.y
+escseq
.arg
[0]); 
 967         case 'F': /* CPL -- Cursor <n> Up and first col */ 
 968                 DEFAULT(escseq
.arg
[0], 1); 
 969                 tmoveto(0, term
.c
.y
-escseq
.arg
[0]); 
 971         case 'G': /* CHA -- Move to <col> */ 
 972         case '`': /* XXX: HPA -- same? */ 
 973                 DEFAULT(escseq
.arg
[0], 1); 
 974                 tmoveto(escseq
.arg
[0]-1, term
.c
.y
); 
 976         case 'H': /* CUP -- Move to <row> <col> */ 
 977         case 'f': /* XXX: HVP -- same? */ 
 978                 DEFAULT(escseq
.arg
[0], 1); 
 979                 DEFAULT(escseq
.arg
[1], 1); 
 980                 tmoveto(escseq
.arg
[1]-1, escseq
.arg
[0]-1); 
 982         /* XXX: (CSI n I) CHT -- Cursor Forward Tabulation <n> tab stops */ 
 983         case 'J': /* ED -- Clear screen */ 
 984                 switch(escseq
.arg
[0]) { 
 986                         tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
 987                         if(term
.c
.y 
< term
.row
-1) 
 988                                 tclearregion(0, term
.c
.y
+1, term
.col
-1, term
.row
-1); 
 992                                 tclearregion(0, 0, term
.col
-1, term
.c
.y
-1); 
 993                         tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
); 
 996                         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1002         case 'K': /* EL -- Clear line */ 
1003                 switch(escseq
.arg
[0]) { 
1005                         tclearregion(term
.c
.x
, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1008                         tclearregion(0, term
.c
.y
, term
.c
.x
, term
.c
.y
); 
1011                         tclearregion(0, term
.c
.y
, term
.col
-1, term
.c
.y
); 
1015         case 'S': /* SU -- Scroll <n> line up */ 
1016                 DEFAULT(escseq
.arg
[0], 1); 
1017                 tscrollup(term
.top
, escseq
.arg
[0]); 
1019         case 'T': /* SD -- Scroll <n> line down */ 
1020                 DEFAULT(escseq
.arg
[0], 1); 
1021                 tscrolldown(term
.top
, escseq
.arg
[0]); 
1023         case 'L': /* IL -- Insert <n> blank lines */ 
1024                 DEFAULT(escseq
.arg
[0], 1); 
1025                 tinsertblankline(escseq
.arg
[0]); 
1027         case 'l': /* RM -- Reset Mode */ 
1029                         switch(escseq
.arg
[0]) { 
1031                                 term
.mode 
&= ~MODE_APPKEYPAD
; 
1033                         case 5: /* TODO: DECSCNM -- Remove reverse video */ 
1036                                 term
.mode 
&= ~MODE_WRAP
; 
1038                         case 12: /* att610 -- Stop blinking cursor (IGNORED) */ 
1041                                 term
.mode 
&= ~MODE_CRLF
; 
1044                                 term
.c
.state 
|= CURSOR_HIDE
; 
1046                         case 1049: /* = 1047 and 1048 */ 
1048                                 if(IS_SET(MODE_ALTSCREEN
)) { 
1049                                         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1052                                 if(escseq
.arg
[0] == 1047) 
1055                                 tcursor(CURSOR_LOAD
); 
1061                         switch(escseq
.arg
[0]) { 
1063                                 term
.mode 
&= ~MODE_INSERT
; 
1070         case 'M': /* DL -- Delete <n> lines */ 
1071                 DEFAULT(escseq
.arg
[0], 1); 
1072                 tdeleteline(escseq
.arg
[0]); 
1074         case 'X': /* ECH -- Erase <n> char */ 
1075                 DEFAULT(escseq
.arg
[0], 1); 
1076                 tclearregion(term
.c
.x
, term
.c
.y
, term
.c
.x 
+ escseq
.arg
[0], term
.c
.y
); 
1078         case 'P': /* DCH -- Delete <n> char */ 
1079                 DEFAULT(escseq
.arg
[0], 1); 
1080                 tdeletechar(escseq
.arg
[0]); 
1082         /* XXX: (CSI n Z) CBT -- Cursor Backward Tabulation <n> tab stops */ 
1083         case 'd': /* VPA -- Move to <row> */ 
1084                 DEFAULT(escseq
.arg
[0], 1); 
1085                 tmoveto(term
.c
.x
, escseq
.arg
[0]-1); 
1087         case 'h': /* SM -- Set terminal mode */ 
1089                         switch(escseq
.arg
[0]) { 
1091                                 term
.mode 
|= MODE_APPKEYPAD
; 
1093                         case 5: /* DECSCNM -- Reverve video */ 
1094                                 /* TODO: set REVERSE on the whole screen (f) */ 
1097                                 term
.mode 
|= MODE_WRAP
; 
1100                                 term
.mode 
|= MODE_CRLF
; 
1102                         case 12: /* att610 -- Start blinking cursor (IGNORED) */ 
1103                                  /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */ 
1104                                 if(escseq
.narg 
> 1 && escseq
.arg
[1] != 25) 
1107                                 term
.c
.state 
&= ~CURSOR_HIDE
; 
1109                         case 1049: /* = 1047 and 1048 */ 
1111                                 if(IS_SET(MODE_ALTSCREEN
)) 
1112                                         tclearregion(0, 0, term
.col
-1, term
.row
-1); 
1115                                 if(escseq
.arg
[0] == 1047) 
1118                                 tcursor(CURSOR_SAVE
); 
1120                         default: goto unknown
; 
1123                         switch(escseq
.arg
[0]) { 
1125                                 term
.mode 
|= MODE_INSERT
; 
1127                         default: goto unknown
; 
1131         case 'm': /* SGR -- Terminal attribute (color) */ 
1132                 tsetattr(escseq
.arg
, escseq
.narg
); 
1134         case 'r': /* DECSTBM -- Set Scrolling Region */ 
1138                         DEFAULT(escseq
.arg
[0], 1); 
1139                         DEFAULT(escseq
.arg
[1], term
.row
); 
1140                         tsetscroll(escseq
.arg
[0]-1, escseq
.arg
[1]-1); 
1144         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 
1145                 tcursor(CURSOR_SAVE
); 
1147         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 
1148                 tcursor(CURSOR_LOAD
); 
1156         printf("ESC [ %s", escseq
.priv 
? "? " : ""); 
1158                 for(i 
= 0; i 
< escseq
.narg
; i
++) 
1159                         printf("%d ", escseq
.arg
[i
]); 
1161                 putchar(escseq
.mode
); 
1167         memset(&escseq
, 0, sizeof(escseq
)); 
1172         int space 
= TAB 
- term
.c
.x 
% TAB
; 
1173         tmoveto(term
.c
.x 
+ space
, term
.c
.y
); 
1179         if(term
.esc 
& ESC_START
) { 
1180                 if(term
.esc 
& ESC_CSI
) { 
1181                         escseq
.buf
[escseq
.len
++] = ascii
; 
1182                         if(BETWEEN(ascii
, 0x40, 0x7E) || escseq
.len 
>= ESC_BUF_SIZ
) { 
1184                                 csiparse(), csihandle(); 
1186                         /* TODO: handle other OSC */ 
1187                 } else if(term
.esc 
& ESC_OSC
) {  
1190                                 term
.esc 
= ESC_START 
| ESC_TITLE
; 
1192                 } else if(term
.esc 
& ESC_TITLE
) { 
1193                         if(ascii 
== '\a' || term
.titlelen
+1 >= ESC_TITLE_SIZ
) { 
1195                                 term
.title
[term
.titlelen
] = '\0'; 
1196                                 XStoreName(xw
.dpy
, xw
.win
, term
.title
); 
1198                                 term
.title
[term
.titlelen
++] = ascii
; 
1200                 } else if(term
.esc 
& ESC_ALTCHARSET
) { 
1202                         case '0': /* Line drawing crap */ 
1203                                 term
.c
.attr
.mode 
|= ATTR_GFX
; 
1205                         case 'B': /* Back to regular text */ 
1206                                 term
.c
.attr
.mode 
&= ~ATTR_GFX
; 
1209                                 printf("esc unhandled charset: ESC ( %c\n", ascii
); 
1215                                 term
.esc 
|= ESC_CSI
; 
1218                                 term
.esc 
|= ESC_OSC
; 
1221                                 term
.esc 
|= ESC_ALTCHARSET
; 
1223                         case 'D': /* IND -- Linefeed */ 
1224                                 if(term
.c
.y 
== term
.bot
) 
1225                                         tscrollup(term
.top
, 1); 
1227                                         tmoveto(term
.c
.x
, term
.c
.y
+1); 
1230                         case 'E': /* NEL -- Next line */ 
1231                                 tnewline(1); /* always go to first col */ 
1234                         case 'M': /* RI -- Reverse index */ 
1235                                 if(term
.c
.y 
== term
.top
) 
1236                                         tscrolldown(term
.top
, 1); 
1238                                         tmoveto(term
.c
.x
, term
.c
.y
-1); 
1241                         case 'c': /* RIS -- Reset to inital state */ 
1245                         case '=': /* DECPAM -- Application keypad */ 
1246                                 term
.mode 
|= MODE_APPKEYPAD
; 
1249                         case '>': /* DECPNM -- Normal keypad */ 
1250                                 term
.mode 
&= ~MODE_APPKEYPAD
; 
1253                         case '7': /* DECSC -- Save Cursor */ 
1254                                 tcursor(CURSOR_SAVE
); 
1257                         case '8': /* DECRC -- Restore Cursor */ 
1258                                 tcursor(CURSOR_LOAD
); 
1262                                 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X '%c'\n", 
1263                                     (unsigned char) ascii
, isprint(ascii
)?ascii
:'.'); 
1273                         tmoveto(term
.c
.x
-1, term
.c
.y
); 
1276                         tmoveto(0, term
.c
.y
); 
1281                         /* go to first col if the mode is set */ 
1282                         tnewline(IS_SET(MODE_CRLF
)); 
1285                         if(!(xw
.state 
& WIN_FOCUSED
)) 
1290                         term
.esc 
= ESC_START
; 
1293                         if(IS_SET(MODE_WRAP
) && term
.c
.state 
& CURSOR_WRAPNEXT
) 
1294                                 tnewline(1); /* always go to first col */ 
1296                         if(term
.c
.x
+1 < term
.col
) 
1297                                 tmoveto(term
.c
.x
+1, term
.c
.y
); 
1299                                 term
.c
.state 
|= CURSOR_WRAPNEXT
; 
1306 tresize(int col
, int row
) { 
1308         int minrow 
= MIN(row
, term
.row
); 
1309         int mincol 
= MIN(col
, term
.col
); 
1310         int slide 
= term
.c
.y 
- row 
+ 1; 
1312         if(col 
< 1 || row 
< 1) 
1315         /* free unneeded rows */ 
1318                 /* slide screen to keep cursor where we expect it - 
1319                  * tscrollup would work here, but we can optimize to 
1320                  * memmove because we're freeing the earlier lines */ 
1321                 for(/* i = 0 */; i 
< slide
; i
++) { 
1325                 memmove(term
.line
, term
.line 
+ slide
, row 
* sizeof(Line
)); 
1326                 memmove(term
.alt
, term
.alt 
+ slide
, row 
* sizeof(Line
)); 
1328         for(i 
+= row
; i 
< term
.row
; i
++) { 
1333         /* resize to new height */ 
1334         term
.line 
= realloc(term
.line
, row 
* sizeof(Line
)); 
1335         term
.alt  
= realloc(term
.alt
,  row 
* sizeof(Line
)); 
1337         /* resize each row to new width, zero-pad if needed */ 
1338         for(i 
= 0; i 
< minrow
; i
++) { 
1339                 term
.line
[i
] = realloc(term
.line
[i
], col 
* sizeof(Glyph
)); 
1340                 term
.alt
[i
]  = realloc(term
.alt
[i
],  col 
* sizeof(Glyph
)); 
1341                 for(x 
= mincol
; x 
< col
; x
++) { 
1342                         term
.line
[i
][x
].state 
= 0; 
1343                         term
.alt
[i
][x
].state 
= 0; 
1347         /* allocate any new rows */ 
1348         for(/* i == minrow */; i 
< row
; i
++) { 
1349                 term
.line
[i
] = calloc(col
, sizeof(Glyph
)); 
1350                 term
.alt 
[i
] = calloc(col
, sizeof(Glyph
)); 
1353         /* update terminal size */ 
1354         term
.col 
= col
, term
.row 
= row
; 
1355         /* make use of the LIMIT in tmoveto */ 
1356         tmoveto(term
.c
.x
, term
.c
.y
); 
1357         /* reset scrolling region */ 
1358         tsetscroll(0, row
-1); 
1363 xresize(int col
, int row
) { 
1369         xw
.bufw 
= MAX(1, col 
* xw
.cw
); 
1370         xw
.bufh 
= MAX(1, row 
* xw
.ch
); 
1371         newbuf 
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.bufw
, xw
.bufh
, XDefaultDepth(xw
.dpy
, xw
.scr
)); 
1372         XCopyArea(xw
.dpy
, xw
.buf
, newbuf
, dc
.gc
, 0, 0, xw
.bufw
, xw
.bufh
, 0, 0); 
1373         XFreePixmap(xw
.dpy
, xw
.buf
); 
1374         XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[DefaultBG
]); 
1376                 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, oldw
, 0, 
1377                                 xw
.bufw
-oldw
, MIN(xw
.bufh
, oldh
)); 
1378         else if(xw
.bufw 
< oldw 
&& (BORDER 
> 0 || xw
.w 
> xw
.bufw
)) 
1379                 XClearArea(xw
.dpy
, xw
.win
, BORDER
+xw
.bufw
, BORDER
, 
1380                                 xw
.w
-xw
.bufh
-BORDER
, BORDER
+MIN(xw
.bufh
, oldh
), 
1383                 XFillRectangle(xw
.dpy
, newbuf
, dc
.gc
, 0, oldh
, 
1384                                 xw
.bufw
, xw
.bufh
-oldh
); 
1385         else if(xw
.bufh 
< oldh 
&& (BORDER 
> 0 || xw
.h 
> xw
.bufh
)) 
1386                 XClearArea(xw
.dpy
, xw
.win
, BORDER
, BORDER
+xw
.bufh
, 
1387                                 xw
.w
-2*BORDER
, xw
.h
-xw
.bufh
-BORDER
, 
1396         unsigned long white 
= WhitePixel(xw
.dpy
, xw
.scr
); 
1398         for(i 
= 0; i 
< 16; i
++) { 
1399                 if (!XAllocNamedColor(xw
.dpy
, xw
.cmap
, colorname
[i
], &color
, &color
)) { 
1401                         fprintf(stderr
, "Could not allocate color '%s'\n", colorname
[i
]); 
1403                         dc
.col
[i
] = color
.pixel
; 
1406         /* same colors as xterm */ 
1407         for(r 
= 0; r 
< 6; r
++) 
1408                 for(g 
= 0; g 
< 6; g
++) 
1409                         for(b 
= 0; b 
< 6; b
++) { 
1410                                 color
.red 
= r 
== 0 ? 0 : 0x3737 + 0x2828 * r
; 
1411                                 color
.green 
= g 
== 0 ? 0 : 0x3737 + 0x2828 * g
; 
1412                                 color
.blue 
= b 
== 0 ? 0 : 0x3737 + 0x2828 * b
; 
1413                                 if (!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) { 
1415                                         fprintf(stderr
, "Could not allocate color %d\n", i
); 
1417                                         dc
.col
[i
] = color
.pixel
; 
1421         for(r 
= 0; r 
< 24; r
++, i
++) { 
1422                 color
.red 
= color
.green 
= color
.blue 
= 0x0808 + 0x0a0a * r
; 
1423                 if (!XAllocColor(xw
.dpy
, xw
.cmap
, &color
)) { 
1425                         fprintf(stderr
, "Could not allocate color %d\n", i
); 
1427                         dc
.col
[i
] = color
.pixel
; 
1432 xclear(int x1
, int y1
, int x2
, int y2
) { 
1433         XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[DefaultBG
]); 
1434         XFillRectangle(xw
.dpy
, xw
.buf
, dc
.gc
, 
1435                        x1 
* xw
.cw
, y1 
* xw
.ch
, 
1436                        (x2
-x1
+1) * xw
.cw
, (y2
-y1
+1) * xw
.ch
); 
1442         XClassHint 
class = {opt_class 
? opt_class 
: TNAME
, TNAME
}; 
1443         XWMHints wm 
= {.flags 
= InputHint
, .input 
= 1}; 
1445                 .flags 
= PSize 
| PResizeInc 
| PBaseSize
, 
1448                 .height_inc 
= xw
.ch
, 
1450                 .base_height 
= 2*BORDER
, 
1451                 .base_width 
= 2*BORDER
, 
1453         XSetWMProperties(xw
.dpy
, xw
.win
, NULL
, NULL
, NULL
, 0, &size
, &wm
, &class); 
1457 xinitfont(char *fontstr
) 
1460         char *def
, **missing
; 
1464         set 
= XCreateFontSet(xw
.dpy
, fontstr
, &missing
, &n
, &def
); 
1467                         fprintf(stderr
, "st: missing fontset: %s\n", missing
[n
]); 
1468                 XFreeStringList(missing
); 
1474 xgetfontinfo(XFontSet set
, int *ascent
, int *descent
, short *lbearing
, short *rbearing
) 
1476         XFontStruct 
**xfonts
; 
1480         *ascent 
= *descent 
= *lbearing 
= *rbearing 
= 0; 
1481         n 
= XFontsOfFontSet(set
, &xfonts
, &font_names
); 
1482         for(i 
= 0; i 
< n
; i
++) { 
1483                 *ascent 
= MAX(*ascent
, (*xfonts
)->ascent
); 
1484                 *descent 
= MAX(*descent
, (*xfonts
)->descent
); 
1485                 *lbearing 
= MAX(*lbearing
, (*xfonts
)->min_bounds
.lbearing
); 
1486                 *rbearing 
= MAX(*rbearing
, (*xfonts
)->max_bounds
.rbearing
); 
1492 initfonts(char *fontstr
, char *bfontstr
) 
1494         if((dc
.font
.set 
= xinitfont(fontstr
)) == NULL 
|| 
1495            (dc
.bfont
.set 
= xinitfont(bfontstr
)) == NULL
) 
1496                 die("Can't load font %s\n", dc
.font
.set 
? BOLDFONT 
: FONT
); 
1497         xgetfontinfo(dc
.font
.set
, &dc
.font
.ascent
, &dc
.font
.descent
, 
1498             &dc
.font
.lbearing
, &dc
.font
.rbearing
); 
1499         xgetfontinfo(dc
.bfont
.set
, &dc
.bfont
.ascent
, &dc
.bfont
.descent
, 
1500             &dc
.bfont
.lbearing
, &dc
.bfont
.rbearing
); 
1505         XSetWindowAttributes attrs
; 
1507         if(!(xw
.dpy 
= XOpenDisplay(NULL
))) 
1508                 die("Can't open display\n"); 
1509         xw
.scr 
= XDefaultScreen(xw
.dpy
); 
1512         initfonts(FONT
, BOLDFONT
); 
1514         /* XXX: Assuming same size for bold font */ 
1515         xw
.cw 
= dc
.font
.rbearing 
- dc
.font
.lbearing
; 
1516         xw
.ch 
= dc
.font
.ascent 
+ dc
.font
.descent
; 
1519         xw
.cmap 
= XDefaultColormap(xw
.dpy
, xw
.scr
); 
1522         /* window - default size */ 
1523         xw
.bufh 
= 24 * xw
.ch
; 
1524         xw
.bufw 
= 80 * xw
.cw
; 
1525         xw
.h 
= xw
.bufh 
+ 2*BORDER
; 
1526         xw
.w 
= xw
.bufw 
+ 2*BORDER
; 
1528         attrs
.background_pixel 
= dc
.col
[DefaultBG
]; 
1529         attrs
.border_pixel 
= dc
.col
[DefaultBG
]; 
1530         attrs
.bit_gravity 
= NorthWestGravity
; 
1531         attrs
.event_mask 
= FocusChangeMask 
| KeyPressMask
 
1532                 | ExposureMask 
| VisibilityChangeMask 
| StructureNotifyMask
 
1533                 | PointerMotionMask 
| ButtonPressMask 
| ButtonReleaseMask
; 
1534         attrs
.colormap 
= xw
.cmap
; 
1536         xw
.win 
= XCreateWindow(xw
.dpy
, XRootWindow(xw
.dpy
, xw
.scr
), 0, 0, 
1537                         xw
.w
, xw
.h
, 0, XDefaultDepth(xw
.dpy
, xw
.scr
), InputOutput
, 
1538                         XDefaultVisual(xw
.dpy
, xw
.scr
), 
1539                         CWBackPixel 
| CWBorderPixel 
| CWBitGravity 
| CWEventMask
 
1542         xw
.buf 
= XCreatePixmap(xw
.dpy
, xw
.win
, xw
.bufw
, xw
.bufh
, XDefaultDepth(xw
.dpy
, xw
.scr
)); 
1546         xw
.xim 
= XOpenIM(xw
.dpy
, NULL
, NULL
, NULL
); 
1547         xw
.xic 
= XCreateIC(xw
.xim
, XNInputStyle
, XIMPreeditNothing 
 
1548                                            | XIMStatusNothing
, XNClientWindow
, xw
.win
,  
1549                                            XNFocusWindow
, xw
.win
, NULL
); 
1551         dc
.gc 
= XCreateGC(xw
.dpy
, xw
.win
, 0, NULL
); 
1553         XMapWindow(xw
.dpy
, xw
.win
); 
1555         XStoreName(xw
.dpy
, xw
.win
, opt_title 
? opt_title 
: "st"); 
1560 xdraws(char *s
, Glyph base
, int x
, int y
, int charlen
, int bytelen
) { 
1561         unsigned long xfg
, xbg
; 
1562         int winx 
= x
*xw
.cw
, winy 
= y
*xw
.ch 
+ dc
.font
.ascent
, width 
= charlen
*xw
.cw
; 
1565         if(base
.mode 
& ATTR_REVERSE
) 
1566                 xfg 
= dc
.col
[base
.bg
], xbg 
= dc
.col
[base
.fg
]; 
1568                 xfg 
= dc
.col
[base
.fg
], xbg 
= dc
.col
[base
.bg
]; 
1570         XSetBackground(xw
.dpy
, dc
.gc
, xbg
); 
1571         XSetForeground(xw
.dpy
, dc
.gc
, xfg
); 
1573         if(base
.mode 
& ATTR_GFX
) { 
1574                 for(i 
= 0; i 
< bytelen
; i
++) { 
1575                         char c 
= gfx
[(unsigned int)s
[i
] % 256]; 
1578                         else if(s
[i
] > 0x5f) 
1583         XmbDrawImageString(xw
.dpy
, xw
.buf
, base
.mode 
& ATTR_BOLD 
? dc
.bfont
.set 
: dc
.font
.set
, 
1584             dc
.gc
, winx
, winy
, s
, bytelen
); 
1586         if(base
.mode 
& ATTR_UNDERLINE
) 
1587                 XDrawLine(xw
.dpy
, xw
.buf
, dc
.gc
, winx
, winy
+1, winx
+width
-1, winy
+1); 
1592         static int oldx 
= 0; 
1593         static int oldy 
= 0; 
1595         Glyph g 
= {{' '}, ATTR_NULL
, DefaultBG
, DefaultCS
, 0}; 
1597         LIMIT(oldx
, 0, term
.col
-1); 
1598         LIMIT(oldy
, 0, term
.row
-1); 
1600         if(term
.line
[term
.c
.y
][term
.c
.x
].state 
& GLYPH_SET
) 
1601                 memcpy(g
.c
, term
.line
[term
.c
.y
][term
.c
.x
].c
, UTF_SIZ
); 
1603         /* remove the old cursor */ 
1604         if(term
.line
[oldy
][oldx
].state 
& GLYPH_SET
) { 
1605                 sl 
= utf8size(term
.line
[oldy
][oldx
].c
); 
1606                 xdraws(term
.line
[oldy
][oldx
].c
, term
.line
[oldy
][oldx
], oldx
, oldy
, 1, sl
); 
1608                 xclear(oldx
, oldy
, oldx
, oldy
); 
1610         /* draw the new one */ 
1611         if(!(term
.c
.state 
& CURSOR_HIDE
) && (xw
.state 
& WIN_FOCUSED
)) { 
1613                 xdraws(g
.c
, g
, term
.c
.x
, term
.c
.y
, 1, sl
); 
1614                 oldx 
= term
.c
.x
, oldy 
= term
.c
.y
; 
1619 /* basic drawing routines */ 
1621 xdrawc(int x
, int y
, Glyph g
) { 
1622         int sl 
= utf8size(g
.c
); 
1623         XRectangle r 
= { x 
* xw
.cw
, y 
* xw
.ch
, xw
.cw
, xw
.ch 
}; 
1624         XSetBackground(xw
.dpy
, dc
.gc
, dc
.col
[g
.bg
]); 
1625         XSetForeground(xw
.dpy
, dc
.gc
, dc
.col
[g
.fg
]); 
1626         XmbDrawImageString(xw
.dpy
, xw
.buf
, g
.mode
&ATTR_BOLD
?dc
.bfont
.fs
:dc
.font
.fs
, 
1627             dc
.gc
, r
.x
, r
.y
+dc
.font
.ascent
, g
.c
, sl
); 
1634         xclear(0, 0, term
.col
-1, term
.row
-1); 
1635         for(y 
= 0; y 
< term
.row
; y
++) 
1636                 for(x 
= 0; x 
< term
.col
; x
++) 
1637                         if(term
.line
[y
][x
].state 
& GLYPH_SET
) 
1638                                 xdrawc(x
, y
, term
.line
[y
][x
]); 
1641         XCopyArea(xw
.dpy
, xw
.buf
, xw
.win
, dc
.gc
, 0, 0, xw
.bufw
, xw
.bufh
, BORDER
, BORDER
); 
1646 /* optimized drawing routine */ 
1648 draw(int redraw_all
) { 
1649         int ic
, ib
, x
, y
, ox
, sl
; 
1651         char buf
[DRAW_BUF_SIZ
]; 
1653         if(!(xw
.state 
& WIN_VISIBLE
)) 
1656         xclear(0, 0, term
.col
-1, term
.row
-1); 
1657         for(y 
= 0; y 
< term
.row
; y
++) { 
1658                 base 
= term
.line
[y
][0]; 
1660                 for(x 
= 0; x 
< term
.col
; x
++) { 
1661                         new = term
.line
[y
][x
]; 
1662                         if(sel
.bx
!=-1 && *(new.c
) && selected(x
, y
)) 
1663                                 new.mode 
^= ATTR_REVERSE
; 
1664                         if(ib 
> 0 && (!(new.state 
& GLYPH_SET
) || ATTRCMP(base
, new) || 
1665                                         ib 
>= DRAW_BUF_SIZ
-UTF_SIZ
)) { 
1666                                 xdraws(buf
, base
, ox
, y
, ic
, ib
); 
1669                         if(new.state 
& GLYPH_SET
) { 
1674                                 sl 
= utf8size(new.c
); 
1675                                 memcpy(buf
+ib
, new.c
, sl
); 
1681                         xdraws(buf
, base
, ox
, y
, ic
, ib
); 
1684         XCopyArea(xw
.dpy
, xw
.buf
, xw
.win
, dc
.gc
, 0, 0, xw
.bufw
, xw
.bufh
, BORDER
, BORDER
); 
1690 expose(XEvent 
*ev
) { 
1691         XExposeEvent 
*e 
= &ev
->xexpose
; 
1692         if(xw
.state 
& WIN_REDRAW
) { 
1694                         xw
.state 
&= ~WIN_REDRAW
; 
1695                         draw(SCREEN_REDRAW
); 
1698                 XCopyArea(xw
.dpy
, xw
.buf
, xw
.win
, dc
.gc
, e
->x
-BORDER
, e
->y
-BORDER
, 
1699                                 e
->width
, e
->height
, e
->x
, e
->y
); 
1703 visibility(XEvent 
*ev
) { 
1704         XVisibilityEvent 
*e 
= &ev
->xvisibility
; 
1705         if(e
->state 
== VisibilityFullyObscured
) 
1706                 xw
.state 
&= ~WIN_VISIBLE
; 
1707         else if(!(xw
.state 
& WIN_VISIBLE
)) 
1708                 /* need a full redraw for next Expose, not just a buf copy */ 
1709                 xw
.state 
|= WIN_VISIBLE 
| WIN_REDRAW
; 
1714         xw
.state 
&= ~WIN_VISIBLE
; 
1718 xseturgency(int add
) { 
1719         XWMHints 
*h 
= XGetWMHints(xw
.dpy
, xw
.win
); 
1720         h
->flags 
= add 
? (h
->flags 
| XUrgencyHint
) : (h
->flags 
& ~XUrgencyHint
); 
1721         XSetWMHints(xw
.dpy
, xw
.win
, h
); 
1727         if(ev
->type 
== FocusIn
) { 
1728                 xw
.state 
|= WIN_FOCUSED
; 
1731                 xw
.state 
&= ~WIN_FOCUSED
; 
1732         draw(SCREEN_UPDATE
); 
1738         for(i 
= 0; i 
< LEN(key
); i
++) 
1740                         return (char*)key
[i
].s
; 
1745 kpress(XEvent 
*ev
) { 
1746         XKeyEvent 
*e 
= &ev
->xkey
; 
1755         meta 
= e
->state 
& Mod1Mask
; 
1756         shift 
= e
->state 
& ShiftMask
; 
1757         len 
= XmbLookupString(xw
.xic
, e
, buf
, sizeof(buf
), &ksym
, &status
); 
1759         /* 1. custom keys from config.h */ 
1760         if((customkey 
= kmap(ksym
))) 
1761                 ttywrite(customkey
, strlen(customkey
)); 
1762         /* 2. hardcoded (overrides X lookup) */ 
1769                         sprintf(buf
, "\033%c%c", IS_SET(MODE_APPKEYPAD
) ? 'O' : '[', "DACB"[ksym 
- XK_Left
]); 
1777                         if(IS_SET(MODE_CRLF
)) 
1778                                 ttywrite("\r\n", 2); 
1785                                 if(meta 
&& len 
== 1) 
1786                                         ttywrite("\033", 1); 
1788                         } else /* 4. nothing to send */ 
1789                                 fprintf(stderr
, "errkey: %d\n", (int)ksym
); 
1798         if(e
->xconfigure
.width 
== xw
.w 
&& e
->xconfigure
.height 
== xw
.h
) 
1801         xw
.w 
= e
->xconfigure
.width
; 
1802         xw
.h 
= e
->xconfigure
.height
; 
1803         col 
= (xw
.w 
- 2*BORDER
) / xw
.cw
; 
1804         row 
= (xw
.h 
- 2*BORDER
) / xw
.ch
; 
1805         if(col 
== term
.col 
&& row 
== term
.row
) 
1807         if(tresize(col
, row
)) 
1808                 draw(SCREEN_REDRAW
); 
1809         ttyresize(col
, row
); 
1817         int xfd 
= XConnectionNumber(xw
.dpy
); 
1821                 FD_SET(cmdfd
, &rfd
); 
1823                 if(select(MAX(xfd
, cmdfd
)+1, &rfd
, NULL
, NULL
, NULL
) < 0) { 
1826                         die("select failed: %s\n", SERRNO
); 
1828                 if(FD_ISSET(cmdfd
, &rfd
)) { 
1830                         draw(SCREEN_UPDATE
);  
1832                 while(XPending(xw
.dpy
)) { 
1833                         XNextEvent(xw
.dpy
, &ev
); 
1834                         if (XFilterEvent(&ev
, xw
.win
)) 
1836                         if(handler
[ev
.type
]) 
1837                                 (handler
[ev
.type
])(&ev
); 
1843 main(int argc
, char *argv
[]) { 
1846         for(i 
= 1; i 
< argc
; i
++) { 
1847                 switch(argv
[i
][0] != '-' || argv
[i
][2] ? -1 : argv
[i
][1]) { 
1849                         if(++i 
< argc
) opt_title 
= argv
[i
]; 
1852                         if(++i 
< argc
) opt_class 
= argv
[i
]; 
1855                         if(++i 
< argc
) opt_cmd 
= &argv
[i
]; 
1861                 /* -e eats every remaining arguments */ 
1865         setlocale(LC_CTYPE
, "");