st.c (100946B)
1 /* See LICENSE for license details. */ 2 #include <ctype.h> 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <limits.h> 6 #include <locale.h> 7 #include <pwd.h> 8 #include <stdarg.h> 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 #include <signal.h> 13 #include <stdint.h> 14 #include <sys/ioctl.h> 15 #include <sys/select.h> 16 #include <sys/stat.h> 17 #include <sys/time.h> 18 #include <sys/types.h> 19 #include <sys/wait.h> 20 #include <termios.h> 21 #include <time.h> 22 #include <unistd.h> 23 #include <libgen.h> 24 #include <X11/Xatom.h> 25 #include <X11/Xlib.h> 26 #include <X11/Xutil.h> 27 #include <X11/cursorfont.h> 28 #include <X11/keysym.h> 29 #include <X11/Xft/Xft.h> 30 #include <X11/XKBlib.h> 31 #include <fontconfig/fontconfig.h> 32 #include <wchar.h> 33 34 #include "arg.h" 35 36 char *argv0; 37 38 #define Glyph Glyph_ 39 #define Font Font_ 40 41 #if defined(__linux) 42 #include <pty.h> 43 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 44 #include <util.h> 45 #elif defined(__FreeBSD__) || defined(__DragonFly__) 46 #include <libutil.h> 47 #endif 48 49 50 /* XEMBED messages */ 51 #define XEMBED_FOCUS_IN 4 52 #define XEMBED_FOCUS_OUT 5 53 54 /* Arbitrary sizes */ 55 #define UTF_INVALID 0xFFFD 56 #define UTF_SIZ 4 57 #define ESC_BUF_SIZ (128*UTF_SIZ) 58 #define ESC_ARG_SIZ 16 59 #define STR_BUF_SIZ ESC_BUF_SIZ 60 #define STR_ARG_SIZ ESC_ARG_SIZ 61 #define XK_ANY_MOD UINT_MAX 62 #define XK_NO_MOD 0 63 #define XK_SWITCH_MOD (1<<13) 64 65 /* macros */ 66 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 67 #define MAX(a, b) ((a) < (b) ? (b) : (a)) 68 #define LEN(a) (sizeof(a) / sizeof(a)[0]) 69 #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1) 70 #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 71 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 72 #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) 73 #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') 74 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 75 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 76 #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL) 77 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 78 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ 79 (a).bg != (b).bg) 80 #define IS_SET(flag) ((term.mode & (flag)) != 0) 81 #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ 82 (t1.tv_nsec-t2.tv_nsec)/1E6) 83 #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) 84 85 #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) 86 #define IS_TRUECOL(x) (1 << 24 & (x)) 87 #define TRUERED(x) (((x) & 0xff0000) >> 8) 88 #define TRUEGREEN(x) (((x) & 0xff00)) 89 #define TRUEBLUE(x) (((x) & 0xff) << 8) 90 91 /* constants */ 92 #define ISO14755CMD "dmenu -w %lu -p codepoint: </dev/null" 93 94 enum glyph_attribute { 95 ATTR_NULL = 0, 96 ATTR_BOLD = 1 << 0, 97 ATTR_FAINT = 1 << 1, 98 ATTR_ITALIC = 1 << 2, 99 ATTR_UNDERLINE = 1 << 3, 100 ATTR_BLINK = 1 << 4, 101 ATTR_REVERSE = 1 << 5, 102 ATTR_INVISIBLE = 1 << 6, 103 ATTR_STRUCK = 1 << 7, 104 ATTR_WRAP = 1 << 8, 105 ATTR_WIDE = 1 << 9, 106 ATTR_WDUMMY = 1 << 10, 107 ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 108 }; 109 110 enum cursor_movement { 111 CURSOR_SAVE, 112 CURSOR_LOAD 113 }; 114 115 enum cursor_state { 116 CURSOR_DEFAULT = 0, 117 CURSOR_WRAPNEXT = 1, 118 CURSOR_ORIGIN = 2 119 }; 120 121 enum term_mode { 122 MODE_WRAP = 1 << 0, 123 MODE_INSERT = 1 << 1, 124 MODE_APPKEYPAD = 1 << 2, 125 MODE_ALTSCREEN = 1 << 3, 126 MODE_CRLF = 1 << 4, 127 MODE_MOUSEBTN = 1 << 5, 128 MODE_MOUSEMOTION = 1 << 6, 129 MODE_REVERSE = 1 << 7, 130 MODE_KBDLOCK = 1 << 8, 131 MODE_HIDE = 1 << 9, 132 MODE_ECHO = 1 << 10, 133 MODE_APPCURSOR = 1 << 11, 134 MODE_MOUSESGR = 1 << 12, 135 MODE_8BIT = 1 << 13, 136 MODE_BLINK = 1 << 14, 137 MODE_FBLINK = 1 << 15, 138 MODE_FOCUS = 1 << 16, 139 MODE_MOUSEX10 = 1 << 17, 140 MODE_MOUSEMANY = 1 << 18, 141 MODE_BRCKTPASTE = 1 << 19, 142 MODE_PRINT = 1 << 20, 143 MODE_UTF8 = 1 << 21, 144 MODE_SIXEL = 1 << 22, 145 MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ 146 |MODE_MOUSEMANY, 147 }; 148 149 enum charset { 150 CS_GRAPHIC0, 151 CS_GRAPHIC1, 152 CS_UK, 153 CS_USA, 154 CS_MULTI, 155 CS_GER, 156 CS_FIN 157 }; 158 159 enum escape_state { 160 ESC_START = 1, 161 ESC_CSI = 2, 162 ESC_STR = 4, /* OSC, PM, APC */ 163 ESC_ALTCHARSET = 8, 164 ESC_STR_END = 16, /* a final string was encountered */ 165 ESC_TEST = 32, /* Enter in test mode */ 166 ESC_UTF8 = 64, 167 ESC_DCS =128, 168 }; 169 170 enum window_state { 171 WIN_VISIBLE = 1, 172 WIN_FOCUSED = 2 173 }; 174 175 enum selection_mode { 176 SEL_IDLE = 0, 177 SEL_EMPTY = 1, 178 SEL_READY = 2 179 }; 180 181 enum selection_type { 182 SEL_REGULAR = 1, 183 SEL_RECTANGULAR = 2 184 }; 185 186 enum selection_snap { 187 SNAP_WORD = 1, 188 SNAP_LINE = 2 189 }; 190 191 typedef unsigned char uchar; 192 typedef unsigned int uint; 193 typedef unsigned long ulong; 194 typedef unsigned short ushort; 195 196 typedef uint_least32_t Rune; 197 198 typedef XftDraw *Draw; 199 typedef XftColor Color; 200 201 typedef struct { 202 Rune u; /* character code */ 203 ushort mode; /* attribute flags */ 204 uint32_t fg; /* foreground */ 205 uint32_t bg; /* background */ 206 } Glyph; 207 208 typedef Glyph *Line; 209 210 typedef struct { 211 Glyph attr; /* current char attributes */ 212 int x; 213 int y; 214 char state; 215 } TCursor; 216 217 /* CSI Escape sequence structs */ 218 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ 219 typedef struct { 220 char buf[ESC_BUF_SIZ]; /* raw string */ 221 int len; /* raw string length */ 222 char priv; 223 int arg[ESC_ARG_SIZ]; 224 int narg; /* nb of args */ 225 char mode[2]; 226 } CSIEscape; 227 228 /* STR Escape sequence structs */ 229 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */ 230 typedef struct { 231 char type; /* ESC type ... */ 232 char buf[STR_BUF_SIZ]; /* raw string */ 233 int len; /* raw string length */ 234 char *args[STR_ARG_SIZ]; 235 int narg; /* nb of args */ 236 } STREscape; 237 238 /* Internal representation of the screen */ 239 typedef struct { 240 int row; /* nb row */ 241 int col; /* nb col */ 242 Line *line; /* screen */ 243 Line *alt; /* alternate screen */ 244 int *dirty; /* dirtyness of lines */ 245 XftGlyphFontSpec *specbuf; /* font spec buffer used for rendering */ 246 TCursor c; /* cursor */ 247 int top; /* top scroll limit */ 248 int bot; /* bottom scroll limit */ 249 int mode; /* terminal mode flags */ 250 int esc; /* escape state flags */ 251 char trantbl[4]; /* charset table translation */ 252 int charset; /* current charset */ 253 int icharset; /* selected charset for sequence */ 254 int numlock; /* lock numbers in keyboard */ 255 int *tabs; 256 } Term; 257 258 /* Purely graphic info */ 259 typedef struct { 260 Display *dpy; 261 Colormap cmap; 262 Window win; 263 Drawable buf; 264 Atom xembed, wmdeletewin, netwmname, netwmpid; 265 XIM xim; 266 XIC xic; 267 Draw draw; 268 Visual *vis; 269 XSetWindowAttributes attrs; 270 int scr; 271 int isfixed; /* is fixed geometry? */ 272 int l, t; /* left and top offset */ 273 int gm; /* geometry mask */ 274 int tw, th; /* tty width and height */ 275 int w, h; /* window width and height */ 276 int ch; /* char height */ 277 int cw; /* char width */ 278 char state; /* focus, redraw, visible */ 279 int cursor; /* cursor style */ 280 } XWindow; 281 282 typedef struct { 283 uint b; 284 uint mask; 285 char *s; 286 } MouseShortcut; 287 288 typedef struct { 289 KeySym k; 290 uint mask; 291 char *s; 292 /* three valued logic variables: 0 indifferent, 1 on, -1 off */ 293 signed char appkey; /* application keypad */ 294 signed char appcursor; /* application cursor */ 295 signed char crlf; /* crlf mode */ 296 } Key; 297 298 typedef struct { 299 int mode; 300 int type; 301 int snap; 302 /* 303 * Selection variables: 304 * nb – normalized coordinates of the beginning of the selection 305 * ne – normalized coordinates of the end of the selection 306 * ob – original coordinates of the beginning of the selection 307 * oe – original coordinates of the end of the selection 308 */ 309 struct { 310 int x, y; 311 } nb, ne, ob, oe; 312 313 char *primary, *clipboard; 314 Atom xtarget; 315 int alt; 316 struct timespec tclick1; 317 struct timespec tclick2; 318 } Selection; 319 320 typedef union { 321 int i; 322 uint ui; 323 float f; 324 const void *v; 325 } Arg; 326 327 typedef struct { 328 uint mod; 329 KeySym keysym; 330 void (*func)(const Arg *); 331 const Arg arg; 332 } Shortcut; 333 334 /* function definitions used in config.h */ 335 static void clipcopy(const Arg *); 336 static void clippaste(const Arg *); 337 static void numlock(const Arg *); 338 static void selpaste(const Arg *); 339 static void xzoom(const Arg *); 340 static void xzoomabs(const Arg *); 341 static void xzoomreset(const Arg *); 342 static void printsel(const Arg *); 343 static void printscreen(const Arg *) ; 344 static void iso14755(const Arg *); 345 static void toggleprinter(const Arg *); 346 static void sendbreak(const Arg *); 347 static void externalpipe(const Arg *); 348 349 /* Config.h for applying patches and the configuration. */ 350 #include "config.h" 351 352 /* Font structure */ 353 typedef struct { 354 int height; 355 int width; 356 int ascent; 357 int descent; 358 int badslant; 359 int badweight; 360 short lbearing; 361 short rbearing; 362 XftFont *match; 363 FcFontSet *set; 364 FcPattern *pattern; 365 } Font; 366 367 /* Drawing Context */ 368 typedef struct { 369 Color col[MAX(LEN(colorname), 256)]; 370 Font font, bfont, ifont, ibfont; 371 GC gc; 372 } DC; 373 374 static void die(const char *, ...); 375 static void draw(void); 376 static void redraw(void); 377 static void drawregion(int, int, int, int); 378 static void execsh(void); 379 static void stty(void); 380 static void sigchld(int); 381 static void run(void); 382 383 static void csidump(void); 384 static void csihandle(void); 385 static void csiparse(void); 386 static void csireset(void); 387 static int eschandle(uchar); 388 static void strdump(void); 389 static void strhandle(void); 390 static void strparse(void); 391 static void strreset(void); 392 393 static int tattrset(int); 394 static void tprinter(char *, size_t); 395 static void tdumpsel(void); 396 static void tdumpline(int); 397 static void tdump(void); 398 static void tclearregion(int, int, int, int); 399 static void tcursor(int); 400 static void tdeletechar(int); 401 static void tdeleteline(int); 402 static void tinsertblank(int); 403 static void tinsertblankline(int); 404 static int tlinelen(int); 405 static void tmoveto(int, int); 406 static void tmoveato(int, int); 407 static void tnew(int, int); 408 static void tnewline(int); 409 static void tputtab(int); 410 static void tputc(Rune); 411 static void treset(void); 412 static void tresize(int, int); 413 static void tscrollup(int, int); 414 static void tscrolldown(int, int); 415 static void tsetattr(int *, int); 416 static void tsetchar(Rune, Glyph *, int, int); 417 static void tsetscroll(int, int); 418 static void tswapscreen(void); 419 static void tsetdirt(int, int); 420 static void tsetdirtattr(int); 421 static void tsetmode(int, int, int *, int); 422 static void tfulldirt(void); 423 static void techo(Rune); 424 static void tcontrolcode(uchar ); 425 static void tdectest(char ); 426 static void tdefutf8(char); 427 static int32_t tdefcolor(int *, int *, int); 428 static void tdeftran(char); 429 static inline int match(uint, uint); 430 static void ttynew(void); 431 static size_t ttyread(void); 432 static void ttyresize(void); 433 static void ttysend(char *, size_t); 434 static void ttywrite(const char *, size_t); 435 static void tstrsequence(uchar); 436 437 static inline ushort sixd_to_16bit(int); 438 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); 439 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); 440 static void xdrawglyph(Glyph, int, int); 441 static void xhints(void); 442 static void xclear(int, int, int, int); 443 static void xdrawcursor(void); 444 static void xinit(void); 445 static void xloadcols(void); 446 static int xsetcolorname(int, const char *); 447 static int xgeommasktogravity(int); 448 static int xloadfont(Font *, FcPattern *); 449 static void xloadfonts(char *, double); 450 static void xsettitle(char *); 451 static void xresettitle(void); 452 static void xsetpointermotion(int); 453 static void xseturgency(int); 454 static void xsetsel(char *, Time); 455 static void xunloadfont(Font *); 456 static void xunloadfonts(void); 457 static void xresize(int, int); 458 459 static void expose(XEvent *); 460 static void visibility(XEvent *); 461 static void unmap(XEvent *); 462 static char *kmap(KeySym, uint); 463 static void kpress(XEvent *); 464 static void cmessage(XEvent *); 465 static void cresize(int, int); 466 static void resize(XEvent *); 467 static void focus(XEvent *); 468 static void brelease(XEvent *); 469 static void bpress(XEvent *); 470 static void bmotion(XEvent *); 471 static void propnotify(XEvent *); 472 static void selnotify(XEvent *); 473 static void selclear(XEvent *); 474 static void selrequest(XEvent *); 475 476 static void selinit(void); 477 static void selnormalize(void); 478 static inline int selected(int, int); 479 static char *getsel(void); 480 static void selcopy(Time); 481 static void selscroll(int, int); 482 static void selsnap(int *, int *, int); 483 static int x2col(int); 484 static int y2row(int); 485 static void getbuttoninfo(XEvent *); 486 static void mousereport(XEvent *); 487 488 static size_t utf8decode(char *, Rune *, size_t); 489 static Rune utf8decodebyte(char, size_t *); 490 static size_t utf8encode(Rune, char *); 491 static char utf8encodebyte(Rune, size_t); 492 static char *utf8strchr(char *s, Rune u); 493 static size_t utf8validate(Rune *, size_t); 494 495 static ssize_t xwrite(int, const char *, size_t); 496 static void *xmalloc(size_t); 497 static void *xrealloc(void *, size_t); 498 static char *xstrdup(char *); 499 500 static void usage(void); 501 502 static void (*handler[LASTEvent])(XEvent *) = { 503 [KeyPress] = kpress, 504 [ClientMessage] = cmessage, 505 [ConfigureNotify] = resize, 506 [VisibilityNotify] = visibility, 507 [UnmapNotify] = unmap, 508 [Expose] = expose, 509 [FocusIn] = focus, 510 [FocusOut] = focus, 511 [MotionNotify] = bmotion, 512 [ButtonPress] = bpress, 513 [ButtonRelease] = brelease, 514 /* 515 * Uncomment if you want the selection to disappear when you select something 516 * different in another window. 517 */ 518 /* [SelectionClear] = selclear, */ 519 [SelectionNotify] = selnotify, 520 /* 521 * PropertyNotify is only turned on when there is some INCR transfer happening 522 * for the selection retrieval. 523 */ 524 [PropertyNotify] = propnotify, 525 [SelectionRequest] = selrequest, 526 }; 527 528 /* Globals */ 529 static DC dc; 530 static XWindow xw; 531 static Term term; 532 static CSIEscape csiescseq; 533 static STREscape strescseq; 534 static int cmdfd; 535 static pid_t pid; 536 static Selection sel; 537 static int iofd = 1; 538 static char **opt_cmd = NULL; 539 static char *opt_class = NULL; 540 static char *opt_embed = NULL; 541 static char *opt_font = NULL; 542 static char *opt_io = NULL; 543 static char *opt_line = NULL; 544 static char *opt_name = NULL; 545 static char *opt_title = NULL; 546 static int oldbutton = 3; /* button event on startup: 3 = release */ 547 548 static char *usedfont = NULL; 549 static double usedfontsize = 0; 550 static double defaultfontsize = 0; 551 552 static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 553 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 554 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 555 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 556 557 /* Font Ring Cache */ 558 enum { 559 FRC_NORMAL, 560 FRC_ITALIC, 561 FRC_BOLD, 562 FRC_ITALICBOLD 563 }; 564 565 typedef struct { 566 XftFont *font; 567 int flags; 568 Rune unicodep; 569 } Fontcache; 570 571 /* Fontcache is an array now. A new font will be appended to the array. */ 572 static Fontcache frc[16]; 573 static int frclen = 0; 574 575 ssize_t 576 xwrite(int fd, const char *s, size_t len) 577 { 578 size_t aux = len; 579 ssize_t r; 580 581 while (len > 0) { 582 r = write(fd, s, len); 583 if (r < 0) 584 return r; 585 len -= r; 586 s += r; 587 } 588 589 return aux; 590 } 591 592 void * 593 xmalloc(size_t len) 594 { 595 void *p = malloc(len); 596 597 if (!p) 598 die("Out of memory\n"); 599 600 return p; 601 } 602 603 void * 604 xrealloc(void *p, size_t len) 605 { 606 if ((p = realloc(p, len)) == NULL) 607 die("Out of memory\n"); 608 609 return p; 610 } 611 612 char * 613 xstrdup(char *s) 614 { 615 if ((s = strdup(s)) == NULL) 616 die("Out of memory\n"); 617 618 return s; 619 } 620 621 size_t 622 utf8decode(char *c, Rune *u, size_t clen) 623 { 624 size_t i, j, len, type; 625 Rune udecoded; 626 627 *u = UTF_INVALID; 628 if (!clen) 629 return 0; 630 udecoded = utf8decodebyte(c[0], &len); 631 if (!BETWEEN(len, 1, UTF_SIZ)) 632 return 1; 633 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 634 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 635 if (type != 0) 636 return j; 637 } 638 if (j < len) 639 return 0; 640 *u = udecoded; 641 utf8validate(u, len); 642 643 return len; 644 } 645 646 Rune 647 utf8decodebyte(char c, size_t *i) 648 { 649 for (*i = 0; *i < LEN(utfmask); ++(*i)) 650 if (((uchar)c & utfmask[*i]) == utfbyte[*i]) 651 return (uchar)c & ~utfmask[*i]; 652 653 return 0; 654 } 655 656 size_t 657 utf8encode(Rune u, char *c) 658 { 659 size_t len, i; 660 661 len = utf8validate(&u, 0); 662 if (len > UTF_SIZ) 663 return 0; 664 665 for (i = len - 1; i != 0; --i) { 666 c[i] = utf8encodebyte(u, 0); 667 u >>= 6; 668 } 669 c[0] = utf8encodebyte(u, len); 670 671 return len; 672 } 673 674 char 675 utf8encodebyte(Rune u, size_t i) 676 { 677 return utfbyte[i] | (u & ~utfmask[i]); 678 } 679 680 char * 681 utf8strchr(char *s, Rune u) 682 { 683 Rune r; 684 size_t i, j, len; 685 686 len = strlen(s); 687 for (i = 0, j = 0; i < len; i += j) { 688 if (!(j = utf8decode(&s[i], &r, len - i))) 689 break; 690 if (r == u) 691 return &(s[i]); 692 } 693 694 return NULL; 695 } 696 697 size_t 698 utf8validate(Rune *u, size_t i) 699 { 700 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 701 *u = UTF_INVALID; 702 for (i = 1; *u > utfmax[i]; ++i) 703 ; 704 705 return i; 706 } 707 708 void 709 selinit(void) 710 { 711 clock_gettime(CLOCK_MONOTONIC, &sel.tclick1); 712 clock_gettime(CLOCK_MONOTONIC, &sel.tclick2); 713 sel.mode = SEL_IDLE; 714 sel.snap = 0; 715 sel.ob.x = -1; 716 sel.primary = NULL; 717 sel.clipboard = NULL; 718 sel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); 719 if (sel.xtarget == None) 720 sel.xtarget = XA_STRING; 721 } 722 723 int 724 x2col(int x) 725 { 726 x -= borderpx; 727 x /= xw.cw; 728 729 return LIMIT(x, 0, term.col-1); 730 } 731 732 int 733 y2row(int y) 734 { 735 y -= borderpx; 736 y /= xw.ch; 737 738 return LIMIT(y, 0, term.row-1); 739 } 740 741 int 742 tlinelen(int y) 743 { 744 int i = term.col; 745 746 if (term.line[y][i - 1].mode & ATTR_WRAP) 747 return i; 748 749 while (i > 0 && term.line[y][i - 1].u == ' ') 750 --i; 751 752 return i; 753 } 754 755 void 756 selnormalize(void) 757 { 758 int i; 759 760 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 761 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 762 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 763 } else { 764 sel.nb.x = MIN(sel.ob.x, sel.oe.x); 765 sel.ne.x = MAX(sel.ob.x, sel.oe.x); 766 } 767 sel.nb.y = MIN(sel.ob.y, sel.oe.y); 768 sel.ne.y = MAX(sel.ob.y, sel.oe.y); 769 770 selsnap(&sel.nb.x, &sel.nb.y, -1); 771 selsnap(&sel.ne.x, &sel.ne.y, +1); 772 773 /* expand selection over line breaks */ 774 if (sel.type == SEL_RECTANGULAR) 775 return; 776 i = tlinelen(sel.nb.y); 777 if (i < sel.nb.x) 778 sel.nb.x = i; 779 if (tlinelen(sel.ne.y) <= sel.ne.x) 780 sel.ne.x = term.col - 1; 781 } 782 783 int 784 selected(int x, int y) 785 { 786 if (sel.mode == SEL_EMPTY) 787 return 0; 788 789 if (sel.type == SEL_RECTANGULAR) 790 return BETWEEN(y, sel.nb.y, sel.ne.y) 791 && BETWEEN(x, sel.nb.x, sel.ne.x); 792 793 return BETWEEN(y, sel.nb.y, sel.ne.y) 794 && (y != sel.nb.y || x >= sel.nb.x) 795 && (y != sel.ne.y || x <= sel.ne.x); 796 } 797 798 void 799 selsnap(int *x, int *y, int direction) 800 { 801 int newx, newy, xt, yt; 802 int delim, prevdelim; 803 Glyph *gp, *prevgp; 804 805 switch (sel.snap) { 806 case SNAP_WORD: 807 /* 808 * Snap around if the word wraps around at the end or 809 * beginning of a line. 810 */ 811 prevgp = &term.line[*y][*x]; 812 prevdelim = ISDELIM(prevgp->u); 813 for (;;) { 814 newx = *x + direction; 815 newy = *y; 816 if (!BETWEEN(newx, 0, term.col - 1)) { 817 newy += direction; 818 newx = (newx + term.col) % term.col; 819 if (!BETWEEN(newy, 0, term.row - 1)) 820 break; 821 822 if (direction > 0) 823 yt = *y, xt = *x; 824 else 825 yt = newy, xt = newx; 826 if (!(term.line[yt][xt].mode & ATTR_WRAP)) 827 break; 828 } 829 830 if (newx >= tlinelen(newy)) 831 break; 832 833 gp = &term.line[newy][newx]; 834 delim = ISDELIM(gp->u); 835 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 836 || (delim && gp->u != prevgp->u))) 837 break; 838 839 *x = newx; 840 *y = newy; 841 prevgp = gp; 842 prevdelim = delim; 843 } 844 break; 845 case SNAP_LINE: 846 /* 847 * Snap around if the the previous line or the current one 848 * has set ATTR_WRAP at its end. Then the whole next or 849 * previous line will be selected. 850 */ 851 *x = (direction < 0) ? 0 : term.col - 1; 852 if (direction < 0) { 853 for (; *y > 0; *y += direction) { 854 if (!(term.line[*y-1][term.col-1].mode 855 & ATTR_WRAP)) { 856 break; 857 } 858 } 859 } else if (direction > 0) { 860 for (; *y < term.row-1; *y += direction) { 861 if (!(term.line[*y][term.col-1].mode 862 & ATTR_WRAP)) { 863 break; 864 } 865 } 866 } 867 break; 868 } 869 } 870 871 void 872 getbuttoninfo(XEvent *e) 873 { 874 int type; 875 uint state = e->xbutton.state & ~(Button1Mask | forceselmod); 876 877 sel.alt = IS_SET(MODE_ALTSCREEN); 878 879 sel.oe.x = x2col(e->xbutton.x); 880 sel.oe.y = y2row(e->xbutton.y); 881 selnormalize(); 882 883 sel.type = SEL_REGULAR; 884 for (type = 1; type < LEN(selmasks); ++type) { 885 if (match(selmasks[type], state)) { 886 sel.type = type; 887 break; 888 } 889 } 890 } 891 892 void 893 mousereport(XEvent *e) 894 { 895 int x = x2col(e->xbutton.x), y = y2row(e->xbutton.y), 896 button = e->xbutton.button, state = e->xbutton.state, 897 len; 898 char buf[40]; 899 static int ox, oy; 900 901 /* from urxvt */ 902 if (e->xbutton.type == MotionNotify) { 903 if (x == ox && y == oy) 904 return; 905 if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) 906 return; 907 /* MOUSE_MOTION: no reporting if no button is pressed */ 908 if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) 909 return; 910 911 button = oldbutton + 32; 912 ox = x; 913 oy = y; 914 } else { 915 if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { 916 button = 3; 917 } else { 918 button -= Button1; 919 if (button >= 3) 920 button += 64 - 3; 921 } 922 if (e->xbutton.type == ButtonPress) { 923 oldbutton = button; 924 ox = x; 925 oy = y; 926 } else if (e->xbutton.type == ButtonRelease) { 927 oldbutton = 3; 928 /* MODE_MOUSEX10: no button release reporting */ 929 if (IS_SET(MODE_MOUSEX10)) 930 return; 931 if (button == 64 || button == 65) 932 return; 933 } 934 } 935 936 if (!IS_SET(MODE_MOUSEX10)) { 937 button += ((state & ShiftMask ) ? 4 : 0) 938 + ((state & Mod4Mask ) ? 8 : 0) 939 + ((state & ControlMask) ? 16 : 0); 940 } 941 942 if (IS_SET(MODE_MOUSESGR)) { 943 len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", 944 button, x+1, y+1, 945 e->xbutton.type == ButtonRelease ? 'm' : 'M'); 946 } else if (x < 223 && y < 223) { 947 len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", 948 32+button, 32+x+1, 32+y+1); 949 } else { 950 return; 951 } 952 953 ttywrite(buf, len); 954 } 955 956 void 957 bpress(XEvent *e) 958 { 959 struct timespec now; 960 MouseShortcut *ms; 961 962 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { 963 mousereport(e); 964 return; 965 } 966 967 for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { 968 if (e->xbutton.button == ms->b 969 && match(ms->mask, e->xbutton.state)) { 970 ttysend(ms->s, strlen(ms->s)); 971 return; 972 } 973 } 974 975 if (e->xbutton.button == Button1) { 976 clock_gettime(CLOCK_MONOTONIC, &now); 977 978 /* Clear previous selection, logically and visually. */ 979 selclear(NULL); 980 sel.mode = SEL_EMPTY; 981 sel.type = SEL_REGULAR; 982 sel.oe.x = sel.ob.x = x2col(e->xbutton.x); 983 sel.oe.y = sel.ob.y = y2row(e->xbutton.y); 984 985 /* 986 * If the user clicks below predefined timeouts specific 987 * snapping behaviour is exposed. 988 */ 989 if (TIMEDIFF(now, sel.tclick2) <= tripleclicktimeout) { 990 sel.snap = SNAP_LINE; 991 } else if (TIMEDIFF(now, sel.tclick1) <= doubleclicktimeout) { 992 sel.snap = SNAP_WORD; 993 } else { 994 sel.snap = 0; 995 } 996 selnormalize(); 997 998 if (sel.snap != 0) 999 sel.mode = SEL_READY; 1000 tsetdirt(sel.nb.y, sel.ne.y); 1001 sel.tclick2 = sel.tclick1; 1002 sel.tclick1 = now; 1003 } 1004 } 1005 1006 char * 1007 getsel(void) 1008 { 1009 char *str, *ptr; 1010 int y, bufsize, lastx, linelen; 1011 Glyph *gp, *last; 1012 1013 if (sel.ob.x == -1) 1014 return NULL; 1015 1016 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 1017 ptr = str = xmalloc(bufsize); 1018 1019 /* append every set & selected glyph to the selection */ 1020 for (y = sel.nb.y; y <= sel.ne.y; y++) { 1021 if ((linelen = tlinelen(y)) == 0) { 1022 *ptr++ = '\n'; 1023 continue; 1024 } 1025 1026 if (sel.type == SEL_RECTANGULAR) { 1027 gp = &term.line[y][sel.nb.x]; 1028 lastx = sel.ne.x; 1029 } else { 1030 gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; 1031 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 1032 } 1033 last = &term.line[y][MIN(lastx, linelen-1)]; 1034 while (last >= gp && last->u == ' ') 1035 --last; 1036 1037 for ( ; gp <= last; ++gp) { 1038 if (gp->mode & ATTR_WDUMMY) 1039 continue; 1040 1041 ptr += utf8encode(gp->u, ptr); 1042 } 1043 1044 /* 1045 * Copy and pasting of line endings is inconsistent 1046 * in the inconsistent terminal and GUI world. 1047 * The best solution seems like to produce '\n' when 1048 * something is copied from st and convert '\n' to 1049 * '\r', when something to be pasted is received by 1050 * st. 1051 * FIXME: Fix the computer world. 1052 */ 1053 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP)) 1054 *ptr++ = '\n'; 1055 } 1056 *ptr = 0; 1057 return str; 1058 } 1059 1060 void 1061 selcopy(Time t) 1062 { 1063 xsetsel(getsel(), t); 1064 } 1065 1066 void 1067 propnotify(XEvent *e) 1068 { 1069 XPropertyEvent *xpev; 1070 Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 1071 1072 xpev = &e->xproperty; 1073 if (xpev->state == PropertyNewValue && 1074 (xpev->atom == XA_PRIMARY || 1075 xpev->atom == clipboard)) { 1076 selnotify(e); 1077 } 1078 } 1079 1080 void 1081 selnotify(XEvent *e) 1082 { 1083 ulong nitems, ofs, rem; 1084 int format; 1085 uchar *data, *last, *repl; 1086 Atom type, incratom, property; 1087 1088 incratom = XInternAtom(xw.dpy, "INCR", 0); 1089 1090 ofs = 0; 1091 if (e->type == SelectionNotify) { 1092 property = e->xselection.property; 1093 } else if(e->type == PropertyNotify) { 1094 property = e->xproperty.atom; 1095 } else { 1096 return; 1097 } 1098 if (property == None) 1099 return; 1100 1101 do { 1102 if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, 1103 BUFSIZ/4, False, AnyPropertyType, 1104 &type, &format, &nitems, &rem, 1105 &data)) { 1106 fprintf(stderr, "Clipboard allocation failed\n"); 1107 return; 1108 } 1109 1110 if (e->type == PropertyNotify && nitems == 0 && rem == 0) { 1111 /* 1112 * If there is some PropertyNotify with no data, then 1113 * this is the signal of the selection owner that all 1114 * data has been transferred. We won't need to receive 1115 * PropertyNotify events anymore. 1116 */ 1117 MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); 1118 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 1119 &xw.attrs); 1120 } 1121 1122 if (type == incratom) { 1123 /* 1124 * Activate the PropertyNotify events so we receive 1125 * when the selection owner does send us the next 1126 * chunk of data. 1127 */ 1128 MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); 1129 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 1130 &xw.attrs); 1131 1132 /* 1133 * Deleting the property is the transfer start signal. 1134 */ 1135 XDeleteProperty(xw.dpy, xw.win, (int)property); 1136 continue; 1137 } 1138 1139 /* 1140 * As seen in getsel: 1141 * Line endings are inconsistent in the terminal and GUI world 1142 * copy and pasting. When receiving some selection data, 1143 * replace all '\n' with '\r'. 1144 * FIXME: Fix the computer world. 1145 */ 1146 repl = data; 1147 last = data + nitems * format / 8; 1148 while ((repl = memchr(repl, '\n', last - repl))) { 1149 *repl++ = '\r'; 1150 } 1151 1152 if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) 1153 ttywrite("\033[200~", 6); 1154 ttysend((char *)data, nitems * format / 8); 1155 if (IS_SET(MODE_BRCKTPASTE) && rem == 0) 1156 ttywrite("\033[201~", 6); 1157 XFree(data); 1158 /* number of 32-bit chunks returned */ 1159 ofs += nitems * format / 32; 1160 } while (rem > 0); 1161 1162 /* 1163 * Deleting the property again tells the selection owner to send the 1164 * next data chunk in the property. 1165 */ 1166 XDeleteProperty(xw.dpy, xw.win, (int)property); 1167 } 1168 1169 void 1170 selpaste(const Arg *dummy) 1171 { 1172 XConvertSelection(xw.dpy, XA_PRIMARY, sel.xtarget, XA_PRIMARY, 1173 xw.win, CurrentTime); 1174 } 1175 1176 void 1177 clipcopy(const Arg *dummy) 1178 { 1179 Atom clipboard; 1180 1181 if (sel.clipboard != NULL) 1182 free(sel.clipboard); 1183 1184 if (sel.primary != NULL) { 1185 sel.clipboard = xstrdup(sel.primary); 1186 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 1187 XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); 1188 } 1189 } 1190 1191 void 1192 clippaste(const Arg *dummy) 1193 { 1194 Atom clipboard; 1195 1196 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 1197 XConvertSelection(xw.dpy, clipboard, sel.xtarget, clipboard, 1198 xw.win, CurrentTime); 1199 } 1200 1201 void 1202 selclear(XEvent *e) 1203 { 1204 if (sel.ob.x == -1) 1205 return; 1206 sel.mode = SEL_IDLE; 1207 sel.ob.x = -1; 1208 tsetdirt(sel.nb.y, sel.ne.y); 1209 } 1210 1211 void 1212 selrequest(XEvent *e) 1213 { 1214 XSelectionRequestEvent *xsre; 1215 XSelectionEvent xev; 1216 Atom xa_targets, string, clipboard; 1217 char *seltext; 1218 1219 xsre = (XSelectionRequestEvent *) e; 1220 xev.type = SelectionNotify; 1221 xev.requestor = xsre->requestor; 1222 xev.selection = xsre->selection; 1223 xev.target = xsre->target; 1224 xev.time = xsre->time; 1225 if (xsre->property == None) 1226 xsre->property = xsre->target; 1227 1228 /* reject */ 1229 xev.property = None; 1230 1231 xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); 1232 if (xsre->target == xa_targets) { 1233 /* respond with the supported type */ 1234 string = sel.xtarget; 1235 XChangeProperty(xsre->display, xsre->requestor, xsre->property, 1236 XA_ATOM, 32, PropModeReplace, 1237 (uchar *) &string, 1); 1238 xev.property = xsre->property; 1239 } else if (xsre->target == sel.xtarget || xsre->target == XA_STRING) { 1240 /* 1241 * xith XA_STRING non ascii characters may be incorrect in the 1242 * requestor. It is not our problem, use utf8. 1243 */ 1244 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 1245 if (xsre->selection == XA_PRIMARY) { 1246 seltext = sel.primary; 1247 } else if (xsre->selection == clipboard) { 1248 seltext = sel.clipboard; 1249 } else { 1250 fprintf(stderr, 1251 "Unhandled clipboard selection 0x%lx\n", 1252 xsre->selection); 1253 return; 1254 } 1255 if (seltext != NULL) { 1256 XChangeProperty(xsre->display, xsre->requestor, 1257 xsre->property, xsre->target, 1258 8, PropModeReplace, 1259 (uchar *)seltext, strlen(seltext)); 1260 xev.property = xsre->property; 1261 } 1262 } 1263 1264 /* all done, send a notification to the listener */ 1265 if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) 1266 fprintf(stderr, "Error sending SelectionNotify event\n"); 1267 } 1268 1269 void 1270 xsetsel(char *str, Time t) 1271 { 1272 free(sel.primary); 1273 sel.primary = str; 1274 1275 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); 1276 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) 1277 selclear(0); 1278 } 1279 1280 void 1281 brelease(XEvent *e) 1282 { 1283 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { 1284 mousereport(e); 1285 return; 1286 } 1287 1288 if (e->xbutton.button == Button2) { 1289 selpaste(NULL); 1290 } else if (e->xbutton.button == Button1) { 1291 if (sel.mode == SEL_READY) { 1292 getbuttoninfo(e); 1293 selcopy(e->xbutton.time); 1294 } else 1295 selclear(NULL); 1296 sel.mode = SEL_IDLE; 1297 tsetdirt(sel.nb.y, sel.ne.y); 1298 } 1299 } 1300 1301 void 1302 bmotion(XEvent *e) 1303 { 1304 int oldey, oldex, oldsby, oldsey; 1305 1306 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { 1307 mousereport(e); 1308 return; 1309 } 1310 1311 if (!sel.mode) 1312 return; 1313 1314 sel.mode = SEL_READY; 1315 oldey = sel.oe.y; 1316 oldex = sel.oe.x; 1317 oldsby = sel.nb.y; 1318 oldsey = sel.ne.y; 1319 getbuttoninfo(e); 1320 1321 if (oldey != sel.oe.y || oldex != sel.oe.x) 1322 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 1323 } 1324 1325 void 1326 die(const char *errstr, ...) 1327 { 1328 va_list ap; 1329 1330 va_start(ap, errstr); 1331 vfprintf(stderr, errstr, ap); 1332 va_end(ap); 1333 exit(1); 1334 } 1335 1336 void 1337 execsh(void) 1338 { 1339 char **args, *sh, *prog; 1340 const struct passwd *pw; 1341 char buf[sizeof(long) * 8 + 1]; 1342 1343 errno = 0; 1344 if ((pw = getpwuid(getuid())) == NULL) { 1345 if (errno) 1346 die("getpwuid:%s\n", strerror(errno)); 1347 else 1348 die("who are you?\n"); 1349 } 1350 1351 if ((sh = getenv("SHELL")) == NULL) 1352 sh = (pw->pw_shell[0]) ? pw->pw_shell : shell; 1353 1354 if (opt_cmd) 1355 prog = opt_cmd[0]; 1356 else if (utmp) 1357 prog = utmp; 1358 else 1359 prog = sh; 1360 args = (opt_cmd) ? opt_cmd : (char *[]) {prog, NULL}; 1361 1362 snprintf(buf, sizeof(buf), "%lu", xw.win); 1363 1364 unsetenv("COLUMNS"); 1365 unsetenv("LINES"); 1366 unsetenv("TERMCAP"); 1367 setenv("LOGNAME", pw->pw_name, 1); 1368 setenv("USER", pw->pw_name, 1); 1369 setenv("SHELL", sh, 1); 1370 setenv("HOME", pw->pw_dir, 1); 1371 setenv("TERM", termname, 1); 1372 setenv("WINDOWID", buf, 1); 1373 1374 signal(SIGCHLD, SIG_DFL); 1375 signal(SIGHUP, SIG_DFL); 1376 signal(SIGINT, SIG_DFL); 1377 signal(SIGQUIT, SIG_DFL); 1378 signal(SIGTERM, SIG_DFL); 1379 signal(SIGALRM, SIG_DFL); 1380 1381 execvp(prog, args); 1382 _exit(1); 1383 } 1384 1385 void 1386 sigchld(int a) 1387 { 1388 int stat; 1389 pid_t p; 1390 1391 if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 1392 die("Waiting for pid %hd failed: %s\n", pid, strerror(errno)); 1393 1394 if (pid != p) 1395 return; 1396 1397 if (!WIFEXITED(stat) || WEXITSTATUS(stat)) 1398 die("child finished with error '%d'\n", stat); 1399 exit(0); 1400 } 1401 1402 1403 void 1404 stty(void) 1405 { 1406 char cmd[_POSIX_ARG_MAX], **p, *q, *s; 1407 size_t n, siz; 1408 1409 if ((n = strlen(stty_args)) > sizeof(cmd)-1) 1410 die("incorrect stty parameters\n"); 1411 memcpy(cmd, stty_args, n); 1412 q = cmd + n; 1413 siz = sizeof(cmd) - n; 1414 for (p = opt_cmd; p && (s = *p); ++p) { 1415 if ((n = strlen(s)) > siz-1) 1416 die("stty parameter length too long\n"); 1417 *q++ = ' '; 1418 memcpy(q, s, n); 1419 q += n; 1420 siz -= n + 1; 1421 } 1422 *q = '\0'; 1423 if (system(cmd) != 0) 1424 perror("Couldn't call stty"); 1425 } 1426 1427 void 1428 ttynew(void) 1429 { 1430 int m, s; 1431 struct winsize w = {term.row, term.col, 0, 0}; 1432 1433 if (opt_io) { 1434 term.mode |= MODE_PRINT; 1435 iofd = (!strcmp(opt_io, "-")) ? 1436 1 : open(opt_io, O_WRONLY | O_CREAT, 0666); 1437 if (iofd < 0) { 1438 fprintf(stderr, "Error opening %s:%s\n", 1439 opt_io, strerror(errno)); 1440 } 1441 } 1442 1443 if (opt_line) { 1444 if ((cmdfd = open(opt_line, O_RDWR)) < 0) 1445 die("open line failed: %s\n", strerror(errno)); 1446 dup2(cmdfd, 0); 1447 stty(); 1448 return; 1449 } 1450 1451 /* seems to work fine on linux, openbsd and freebsd */ 1452 if (openpty(&m, &s, NULL, NULL, &w) < 0) 1453 die("openpty failed: %s\n", strerror(errno)); 1454 1455 switch (pid = fork()) { 1456 case -1: 1457 die("fork failed\n"); 1458 break; 1459 case 0: 1460 close(iofd); 1461 setsid(); /* create a new process group */ 1462 dup2(s, 0); 1463 dup2(s, 1); 1464 dup2(s, 2); 1465 if (ioctl(s, TIOCSCTTY, NULL) < 0) 1466 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 1467 close(s); 1468 close(m); 1469 execsh(); 1470 break; 1471 default: 1472 close(s); 1473 cmdfd = m; 1474 signal(SIGCHLD, sigchld); 1475 break; 1476 } 1477 } 1478 1479 size_t 1480 ttyread(void) 1481 { 1482 static char buf[BUFSIZ]; 1483 static int buflen = 0; 1484 char *ptr; 1485 int charsize; /* size of utf8 char in bytes */ 1486 Rune unicodep; 1487 int ret; 1488 1489 /* append read bytes to unprocessed bytes */ 1490 if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0) 1491 die("Couldn't read from shell: %s\n", strerror(errno)); 1492 1493 buflen += ret; 1494 ptr = buf; 1495 1496 for (;;) { 1497 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { 1498 /* process a complete utf8 char */ 1499 charsize = utf8decode(ptr, &unicodep, buflen); 1500 if (charsize == 0) 1501 break; 1502 tputc(unicodep); 1503 ptr += charsize; 1504 buflen -= charsize; 1505 1506 } else { 1507 if (buflen <= 0) 1508 break; 1509 tputc(*ptr++ & 0xFF); 1510 buflen--; 1511 } 1512 } 1513 /* keep any uncomplete utf8 char for the next call */ 1514 if (buflen > 0) 1515 memmove(buf, ptr, buflen); 1516 1517 return ret; 1518 } 1519 1520 void 1521 ttywrite(const char *s, size_t n) 1522 { 1523 fd_set wfd, rfd; 1524 ssize_t r; 1525 size_t lim = 256; 1526 1527 /* 1528 * Remember that we are using a pty, which might be a modem line. 1529 * Writing too much will clog the line. That's why we are doing this 1530 * dance. 1531 * FIXME: Migrate the world to Plan 9. 1532 */ 1533 while (n > 0) { 1534 FD_ZERO(&wfd); 1535 FD_ZERO(&rfd); 1536 FD_SET(cmdfd, &wfd); 1537 FD_SET(cmdfd, &rfd); 1538 1539 /* Check if we can write. */ 1540 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { 1541 if (errno == EINTR) 1542 continue; 1543 die("select failed: %s\n", strerror(errno)); 1544 } 1545 if (FD_ISSET(cmdfd, &wfd)) { 1546 /* 1547 * Only write the bytes written by ttywrite() or the 1548 * default of 256. This seems to be a reasonable value 1549 * for a serial line. Bigger values might clog the I/O. 1550 */ 1551 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) 1552 goto write_error; 1553 if (r < n) { 1554 /* 1555 * We weren't able to write out everything. 1556 * This means the buffer is getting full 1557 * again. Empty it. 1558 */ 1559 if (n < lim) 1560 lim = ttyread(); 1561 n -= r; 1562 s += r; 1563 } else { 1564 /* All bytes have been written. */ 1565 break; 1566 } 1567 } 1568 if (FD_ISSET(cmdfd, &rfd)) 1569 lim = ttyread(); 1570 } 1571 return; 1572 1573 write_error: 1574 die("write error on tty: %s\n", strerror(errno)); 1575 } 1576 1577 void 1578 ttysend(char *s, size_t n) 1579 { 1580 int len; 1581 char *t, *lim; 1582 Rune u; 1583 1584 ttywrite(s, n); 1585 if (!IS_SET(MODE_ECHO)) 1586 return; 1587 1588 lim = &s[n]; 1589 for (t = s; t < lim; t += len) { 1590 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { 1591 len = utf8decode(t, &u, n); 1592 } else { 1593 u = *t & 0xFF; 1594 len = 1; 1595 } 1596 if (len <= 0) 1597 break; 1598 techo(u); 1599 n -= len; 1600 } 1601 } 1602 1603 void 1604 ttyresize(void) 1605 { 1606 struct winsize w; 1607 1608 w.ws_row = term.row; 1609 w.ws_col = term.col; 1610 w.ws_xpixel = xw.tw; 1611 w.ws_ypixel = xw.th; 1612 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) 1613 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); 1614 } 1615 1616 int 1617 tattrset(int attr) 1618 { 1619 int i, j; 1620 1621 for (i = 0; i < term.row-1; i++) { 1622 for (j = 0; j < term.col-1; j++) { 1623 if (term.line[i][j].mode & attr) 1624 return 1; 1625 } 1626 } 1627 1628 return 0; 1629 } 1630 1631 void 1632 tsetdirt(int top, int bot) 1633 { 1634 int i; 1635 1636 LIMIT(top, 0, term.row-1); 1637 LIMIT(bot, 0, term.row-1); 1638 1639 for (i = top; i <= bot; i++) 1640 term.dirty[i] = 1; 1641 } 1642 1643 void 1644 tsetdirtattr(int attr) 1645 { 1646 int i, j; 1647 1648 for (i = 0; i < term.row-1; i++) { 1649 for (j = 0; j < term.col-1; j++) { 1650 if (term.line[i][j].mode & attr) { 1651 tsetdirt(i, i); 1652 break; 1653 } 1654 } 1655 } 1656 } 1657 1658 void 1659 tfulldirt(void) 1660 { 1661 tsetdirt(0, term.row-1); 1662 } 1663 1664 void 1665 tcursor(int mode) 1666 { 1667 static TCursor c[2]; 1668 int alt = IS_SET(MODE_ALTSCREEN); 1669 1670 if (mode == CURSOR_SAVE) { 1671 c[alt] = term.c; 1672 } else if (mode == CURSOR_LOAD) { 1673 term.c = c[alt]; 1674 tmoveto(c[alt].x, c[alt].y); 1675 } 1676 } 1677 1678 void 1679 treset(void) 1680 { 1681 uint i; 1682 1683 term.c = (TCursor){{ 1684 .mode = ATTR_NULL, 1685 .fg = defaultfg, 1686 .bg = defaultbg 1687 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; 1688 1689 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1690 for (i = tabspaces; i < term.col; i += tabspaces) 1691 term.tabs[i] = 1; 1692 term.top = 0; 1693 term.bot = term.row - 1; 1694 term.mode = MODE_WRAP|MODE_UTF8; 1695 memset(term.trantbl, CS_USA, sizeof(term.trantbl)); 1696 term.charset = 0; 1697 1698 for (i = 0; i < 2; i++) { 1699 tmoveto(0, 0); 1700 tcursor(CURSOR_SAVE); 1701 tclearregion(0, 0, term.col-1, term.row-1); 1702 tswapscreen(); 1703 } 1704 } 1705 1706 void 1707 tnew(int col, int row) 1708 { 1709 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; 1710 tresize(col, row); 1711 term.numlock = 1; 1712 1713 treset(); 1714 } 1715 1716 void 1717 tswapscreen(void) 1718 { 1719 Line *tmp = term.line; 1720 1721 term.line = term.alt; 1722 term.alt = tmp; 1723 term.mode ^= MODE_ALTSCREEN; 1724 tfulldirt(); 1725 } 1726 1727 void 1728 tscrolldown(int orig, int n) 1729 { 1730 int i; 1731 Line temp; 1732 1733 LIMIT(n, 0, term.bot-orig+1); 1734 1735 tsetdirt(orig, term.bot-n); 1736 tclearregion(0, term.bot-n+1, term.col-1, term.bot); 1737 1738 for (i = term.bot; i >= orig+n; i--) { 1739 temp = term.line[i]; 1740 term.line[i] = term.line[i-n]; 1741 term.line[i-n] = temp; 1742 } 1743 1744 selscroll(orig, n); 1745 } 1746 1747 void 1748 tscrollup(int orig, int n) 1749 { 1750 int i; 1751 Line temp; 1752 1753 LIMIT(n, 0, term.bot-orig+1); 1754 1755 tclearregion(0, orig, term.col-1, orig+n-1); 1756 tsetdirt(orig+n, term.bot); 1757 1758 for (i = orig; i <= term.bot-n; i++) { 1759 temp = term.line[i]; 1760 term.line[i] = term.line[i+n]; 1761 term.line[i+n] = temp; 1762 } 1763 1764 selscroll(orig, -n); 1765 } 1766 1767 void 1768 selscroll(int orig, int n) 1769 { 1770 if (sel.ob.x == -1) 1771 return; 1772 1773 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { 1774 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { 1775 selclear(NULL); 1776 return; 1777 } 1778 if (sel.type == SEL_RECTANGULAR) { 1779 if (sel.ob.y < term.top) 1780 sel.ob.y = term.top; 1781 if (sel.oe.y > term.bot) 1782 sel.oe.y = term.bot; 1783 } else { 1784 if (sel.ob.y < term.top) { 1785 sel.ob.y = term.top; 1786 sel.ob.x = 0; 1787 } 1788 if (sel.oe.y > term.bot) { 1789 sel.oe.y = term.bot; 1790 sel.oe.x = term.col; 1791 } 1792 } 1793 selnormalize(); 1794 } 1795 } 1796 1797 void 1798 tnewline(int first_col) 1799 { 1800 int y = term.c.y; 1801 1802 if (y == term.bot) { 1803 tscrollup(term.top, 1); 1804 } else { 1805 y++; 1806 } 1807 tmoveto(first_col ? 0 : term.c.x, y); 1808 } 1809 1810 void 1811 csiparse(void) 1812 { 1813 char *p = csiescseq.buf, *np; 1814 long int v; 1815 1816 csiescseq.narg = 0; 1817 if (*p == '?') { 1818 csiescseq.priv = 1; 1819 p++; 1820 } 1821 1822 csiescseq.buf[csiescseq.len] = '\0'; 1823 while (p < csiescseq.buf+csiescseq.len) { 1824 np = NULL; 1825 v = strtol(p, &np, 10); 1826 if (np == p) 1827 v = 0; 1828 if (v == LONG_MAX || v == LONG_MIN) 1829 v = -1; 1830 csiescseq.arg[csiescseq.narg++] = v; 1831 p = np; 1832 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1833 break; 1834 p++; 1835 } 1836 csiescseq.mode[0] = *p++; 1837 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; 1838 } 1839 1840 /* for absolute user moves, when decom is set */ 1841 void 1842 tmoveato(int x, int y) 1843 { 1844 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); 1845 } 1846 1847 void 1848 tmoveto(int x, int y) 1849 { 1850 int miny, maxy; 1851 1852 if (term.c.state & CURSOR_ORIGIN) { 1853 miny = term.top; 1854 maxy = term.bot; 1855 } else { 1856 miny = 0; 1857 maxy = term.row - 1; 1858 } 1859 term.c.state &= ~CURSOR_WRAPNEXT; 1860 term.c.x = LIMIT(x, 0, term.col-1); 1861 term.c.y = LIMIT(y, miny, maxy); 1862 } 1863 1864 void 1865 tsetchar(Rune u, Glyph *attr, int x, int y) 1866 { 1867 static char *vt100_0[62] = { /* 0x41 - 0x7e */ 1868 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1869 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1870 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1871 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1872 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1873 "", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1874 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1875 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1876 }; 1877 1878 /* 1879 * The table is proudly stolen from rxvt. 1880 */ 1881 if (term.trantbl[term.charset] == CS_GRAPHIC0 && 1882 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) 1883 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1884 1885 if (term.line[y][x].mode & ATTR_WIDE) { 1886 if (x+1 < term.col) { 1887 term.line[y][x+1].u = ' '; 1888 term.line[y][x+1].mode &= ~ATTR_WDUMMY; 1889 } 1890 } else if (term.line[y][x].mode & ATTR_WDUMMY) { 1891 term.line[y][x-1].u = ' '; 1892 term.line[y][x-1].mode &= ~ATTR_WIDE; 1893 } 1894 1895 term.dirty[y] = 1; 1896 term.line[y][x] = *attr; 1897 term.line[y][x].u = u; 1898 } 1899 1900 void 1901 tclearregion(int x1, int y1, int x2, int y2) 1902 { 1903 int x, y, temp; 1904 Glyph *gp; 1905 1906 if (x1 > x2) 1907 temp = x1, x1 = x2, x2 = temp; 1908 if (y1 > y2) 1909 temp = y1, y1 = y2, y2 = temp; 1910 1911 LIMIT(x1, 0, term.col-1); 1912 LIMIT(x2, 0, term.col-1); 1913 LIMIT(y1, 0, term.row-1); 1914 LIMIT(y2, 0, term.row-1); 1915 1916 for (y = y1; y <= y2; y++) { 1917 term.dirty[y] = 1; 1918 for (x = x1; x <= x2; x++) { 1919 gp = &term.line[y][x]; 1920 if (selected(x, y)) 1921 selclear(NULL); 1922 gp->fg = term.c.attr.fg; 1923 gp->bg = term.c.attr.bg; 1924 gp->mode = 0; 1925 gp->u = ' '; 1926 } 1927 } 1928 } 1929 1930 void 1931 tdeletechar(int n) 1932 { 1933 int dst, src, size; 1934 Glyph *line; 1935 1936 LIMIT(n, 0, term.col - term.c.x); 1937 1938 dst = term.c.x; 1939 src = term.c.x + n; 1940 size = term.col - src; 1941 line = term.line[term.c.y]; 1942 1943 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1944 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); 1945 } 1946 1947 void 1948 tinsertblank(int n) 1949 { 1950 int dst, src, size; 1951 Glyph *line; 1952 1953 LIMIT(n, 0, term.col - term.c.x); 1954 1955 dst = term.c.x + n; 1956 src = term.c.x; 1957 size = term.col - dst; 1958 line = term.line[term.c.y]; 1959 1960 memmove(&line[dst], &line[src], size * sizeof(Glyph)); 1961 tclearregion(src, term.c.y, dst - 1, term.c.y); 1962 } 1963 1964 void 1965 tinsertblankline(int n) 1966 { 1967 if (BETWEEN(term.c.y, term.top, term.bot)) 1968 tscrolldown(term.c.y, n); 1969 } 1970 1971 void 1972 tdeleteline(int n) 1973 { 1974 if (BETWEEN(term.c.y, term.top, term.bot)) 1975 tscrollup(term.c.y, n); 1976 } 1977 1978 int32_t 1979 tdefcolor(int *attr, int *npar, int l) 1980 { 1981 int32_t idx = -1; 1982 uint r, g, b; 1983 1984 switch (attr[*npar + 1]) { 1985 case 2: /* direct color in RGB space */ 1986 if (*npar + 4 >= l) { 1987 fprintf(stderr, 1988 "erresc(38): Incorrect number of parameters (%d)\n", 1989 *npar); 1990 break; 1991 } 1992 r = attr[*npar + 2]; 1993 g = attr[*npar + 3]; 1994 b = attr[*npar + 4]; 1995 *npar += 4; 1996 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1997 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", 1998 r, g, b); 1999 else 2000 idx = TRUECOLOR(r, g, b); 2001 break; 2002 case 5: /* indexed color */ 2003 if (*npar + 2 >= l) { 2004 fprintf(stderr, 2005 "erresc(38): Incorrect number of parameters (%d)\n", 2006 *npar); 2007 break; 2008 } 2009 *npar += 2; 2010 if (!BETWEEN(attr[*npar], 0, 255)) 2011 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 2012 else 2013 idx = attr[*npar]; 2014 break; 2015 case 0: /* implemented defined (only foreground) */ 2016 case 1: /* transparent */ 2017 case 3: /* direct color in CMY space */ 2018 case 4: /* direct color in CMYK space */ 2019 default: 2020 fprintf(stderr, 2021 "erresc(38): gfx attr %d unknown\n", attr[*npar]); 2022 break; 2023 } 2024 2025 return idx; 2026 } 2027 2028 void 2029 tsetattr(int *attr, int l) 2030 { 2031 int i; 2032 int32_t idx; 2033 2034 for (i = 0; i < l; i++) { 2035 switch (attr[i]) { 2036 case 0: 2037 term.c.attr.mode &= ~( 2038 ATTR_BOLD | 2039 ATTR_FAINT | 2040 ATTR_ITALIC | 2041 ATTR_UNDERLINE | 2042 ATTR_BLINK | 2043 ATTR_REVERSE | 2044 ATTR_INVISIBLE | 2045 ATTR_STRUCK ); 2046 term.c.attr.fg = defaultfg; 2047 term.c.attr.bg = defaultbg; 2048 break; 2049 case 1: 2050 term.c.attr.mode |= ATTR_BOLD; 2051 break; 2052 case 2: 2053 term.c.attr.mode |= ATTR_FAINT; 2054 break; 2055 case 3: 2056 term.c.attr.mode |= ATTR_ITALIC; 2057 break; 2058 case 4: 2059 term.c.attr.mode |= ATTR_UNDERLINE; 2060 break; 2061 case 5: /* slow blink */ 2062 /* FALLTHROUGH */ 2063 case 6: /* rapid blink */ 2064 term.c.attr.mode |= ATTR_BLINK; 2065 break; 2066 case 7: 2067 term.c.attr.mode |= ATTR_REVERSE; 2068 break; 2069 case 8: 2070 term.c.attr.mode |= ATTR_INVISIBLE; 2071 break; 2072 case 9: 2073 term.c.attr.mode |= ATTR_STRUCK; 2074 break; 2075 case 22: 2076 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 2077 break; 2078 case 23: 2079 term.c.attr.mode &= ~ATTR_ITALIC; 2080 break; 2081 case 24: 2082 term.c.attr.mode &= ~ATTR_UNDERLINE; 2083 break; 2084 case 25: 2085 term.c.attr.mode &= ~ATTR_BLINK; 2086 break; 2087 case 27: 2088 term.c.attr.mode &= ~ATTR_REVERSE; 2089 break; 2090 case 28: 2091 term.c.attr.mode &= ~ATTR_INVISIBLE; 2092 break; 2093 case 29: 2094 term.c.attr.mode &= ~ATTR_STRUCK; 2095 break; 2096 case 38: 2097 if ((idx = tdefcolor(attr, &i, l)) >= 0) 2098 term.c.attr.fg = idx; 2099 break; 2100 case 39: 2101 term.c.attr.fg = defaultfg; 2102 break; 2103 case 48: 2104 if ((idx = tdefcolor(attr, &i, l)) >= 0) 2105 term.c.attr.bg = idx; 2106 break; 2107 case 49: 2108 term.c.attr.bg = defaultbg; 2109 break; 2110 default: 2111 if (BETWEEN(attr[i], 30, 37)) { 2112 term.c.attr.fg = attr[i] - 30; 2113 } else if (BETWEEN(attr[i], 40, 47)) { 2114 term.c.attr.bg = attr[i] - 40; 2115 } else if (BETWEEN(attr[i], 90, 97)) { 2116 term.c.attr.fg = attr[i] - 90 + 8; 2117 } else if (BETWEEN(attr[i], 100, 107)) { 2118 term.c.attr.bg = attr[i] - 100 + 8; 2119 } else { 2120 fprintf(stderr, 2121 "erresc(default): gfx attr %d unknown\n", 2122 attr[i]), csidump(); 2123 } 2124 break; 2125 } 2126 } 2127 } 2128 2129 void 2130 tsetscroll(int t, int b) 2131 { 2132 int temp; 2133 2134 LIMIT(t, 0, term.row-1); 2135 LIMIT(b, 0, term.row-1); 2136 if (t > b) { 2137 temp = t; 2138 t = b; 2139 b = temp; 2140 } 2141 term.top = t; 2142 term.bot = b; 2143 } 2144 2145 void 2146 tsetmode(int priv, int set, int *args, int narg) 2147 { 2148 int *lim, mode; 2149 int alt; 2150 2151 for (lim = args + narg; args < lim; ++args) { 2152 if (priv) { 2153 switch (*args) { 2154 case 1: /* DECCKM -- Cursor key */ 2155 MODBIT(term.mode, set, MODE_APPCURSOR); 2156 break; 2157 case 5: /* DECSCNM -- Reverse video */ 2158 mode = term.mode; 2159 MODBIT(term.mode, set, MODE_REVERSE); 2160 if (mode != term.mode) 2161 redraw(); 2162 break; 2163 case 6: /* DECOM -- Origin */ 2164 MODBIT(term.c.state, set, CURSOR_ORIGIN); 2165 tmoveato(0, 0); 2166 break; 2167 case 7: /* DECAWM -- Auto wrap */ 2168 MODBIT(term.mode, set, MODE_WRAP); 2169 break; 2170 case 0: /* Error (IGNORED) */ 2171 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 2172 case 3: /* DECCOLM -- Column (IGNORED) */ 2173 case 4: /* DECSCLM -- Scroll (IGNORED) */ 2174 case 8: /* DECARM -- Auto repeat (IGNORED) */ 2175 case 18: /* DECPFF -- Printer feed (IGNORED) */ 2176 case 19: /* DECPEX -- Printer extent (IGNORED) */ 2177 case 42: /* DECNRCM -- National characters (IGNORED) */ 2178 case 12: /* att610 -- Start blinking cursor (IGNORED) */ 2179 break; 2180 case 25: /* DECTCEM -- Text Cursor Enable Mode */ 2181 MODBIT(term.mode, !set, MODE_HIDE); 2182 break; 2183 case 9: /* X10 mouse compatibility mode */ 2184 xsetpointermotion(0); 2185 MODBIT(term.mode, 0, MODE_MOUSE); 2186 MODBIT(term.mode, set, MODE_MOUSEX10); 2187 break; 2188 case 1000: /* 1000: report button press */ 2189 xsetpointermotion(0); 2190 MODBIT(term.mode, 0, MODE_MOUSE); 2191 MODBIT(term.mode, set, MODE_MOUSEBTN); 2192 break; 2193 case 1002: /* 1002: report motion on button press */ 2194 xsetpointermotion(0); 2195 MODBIT(term.mode, 0, MODE_MOUSE); 2196 MODBIT(term.mode, set, MODE_MOUSEMOTION); 2197 break; 2198 case 1003: /* 1003: enable all mouse motions */ 2199 xsetpointermotion(set); 2200 MODBIT(term.mode, 0, MODE_MOUSE); 2201 MODBIT(term.mode, set, MODE_MOUSEMANY); 2202 break; 2203 case 1004: /* 1004: send focus events to tty */ 2204 MODBIT(term.mode, set, MODE_FOCUS); 2205 break; 2206 case 1006: /* 1006: extended reporting mode */ 2207 MODBIT(term.mode, set, MODE_MOUSESGR); 2208 break; 2209 case 1034: 2210 MODBIT(term.mode, set, MODE_8BIT); 2211 break; 2212 case 1049: /* swap screen & set/restore cursor as xterm */ 2213 if (!allowaltscreen) 2214 break; 2215 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 2216 /* FALLTHROUGH */ 2217 case 47: /* swap screen */ 2218 case 1047: 2219 if (!allowaltscreen) 2220 break; 2221 alt = IS_SET(MODE_ALTSCREEN); 2222 if (alt) { 2223 tclearregion(0, 0, term.col-1, 2224 term.row-1); 2225 } 2226 if (set ^ alt) /* set is always 1 or 0 */ 2227 tswapscreen(); 2228 if (*args != 1049) 2229 break; 2230 /* FALLTHROUGH */ 2231 case 1048: 2232 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 2233 break; 2234 case 2004: /* 2004: bracketed paste mode */ 2235 MODBIT(term.mode, set, MODE_BRCKTPASTE); 2236 break; 2237 /* Not implemented mouse modes. See comments there. */ 2238 case 1001: /* mouse highlight mode; can hang the 2239 terminal by design when implemented. */ 2240 case 1005: /* UTF-8 mouse mode; will confuse 2241 applications not supporting UTF-8 2242 and luit. */ 2243 case 1015: /* urxvt mangled mouse mode; incompatible 2244 and can be mistaken for other control 2245 codes. */ 2246 default: 2247 fprintf(stderr, 2248 "erresc: unknown private set/reset mode %d\n", 2249 *args); 2250 break; 2251 } 2252 } else { 2253 switch (*args) { 2254 case 0: /* Error (IGNORED) */ 2255 break; 2256 case 2: /* KAM -- keyboard action */ 2257 MODBIT(term.mode, set, MODE_KBDLOCK); 2258 break; 2259 case 4: /* IRM -- Insertion-replacement */ 2260 MODBIT(term.mode, set, MODE_INSERT); 2261 break; 2262 case 12: /* SRM -- Send/Receive */ 2263 MODBIT(term.mode, !set, MODE_ECHO); 2264 break; 2265 case 20: /* LNM -- Linefeed/new line */ 2266 MODBIT(term.mode, set, MODE_CRLF); 2267 break; 2268 default: 2269 fprintf(stderr, 2270 "erresc: unknown set/reset mode %d\n", 2271 *args); 2272 break; 2273 } 2274 } 2275 } 2276 } 2277 2278 void 2279 csihandle(void) 2280 { 2281 char buf[40]; 2282 int len; 2283 2284 switch (csiescseq.mode[0]) { 2285 default: 2286 unknown: 2287 fprintf(stderr, "erresc: unknown csi "); 2288 csidump(); 2289 /* die(""); */ 2290 break; 2291 case '@': /* ICH -- Insert <n> blank char */ 2292 DEFAULT(csiescseq.arg[0], 1); 2293 tinsertblank(csiescseq.arg[0]); 2294 break; 2295 case 'A': /* CUU -- Cursor <n> Up */ 2296 DEFAULT(csiescseq.arg[0], 1); 2297 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); 2298 break; 2299 case 'B': /* CUD -- Cursor <n> Down */ 2300 case 'e': /* VPR --Cursor <n> Down */ 2301 DEFAULT(csiescseq.arg[0], 1); 2302 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); 2303 break; 2304 case 'i': /* MC -- Media Copy */ 2305 switch (csiescseq.arg[0]) { 2306 case 0: 2307 tdump(); 2308 break; 2309 case 1: 2310 tdumpline(term.c.y); 2311 break; 2312 case 2: 2313 tdumpsel(); 2314 break; 2315 case 4: 2316 term.mode &= ~MODE_PRINT; 2317 break; 2318 case 5: 2319 term.mode |= MODE_PRINT; 2320 break; 2321 } 2322 break; 2323 case 'c': /* DA -- Device Attributes */ 2324 if (csiescseq.arg[0] == 0) 2325 ttywrite(vtiden, sizeof(vtiden) - 1); 2326 break; 2327 case 'C': /* CUF -- Cursor <n> Forward */ 2328 case 'a': /* HPR -- Cursor <n> Forward */ 2329 DEFAULT(csiescseq.arg[0], 1); 2330 tmoveto(term.c.x+csiescseq.arg[0], term.c.y); 2331 break; 2332 case 'D': /* CUB -- Cursor <n> Backward */ 2333 DEFAULT(csiescseq.arg[0], 1); 2334 tmoveto(term.c.x-csiescseq.arg[0], term.c.y); 2335 break; 2336 case 'E': /* CNL -- Cursor <n> Down and first col */ 2337 DEFAULT(csiescseq.arg[0], 1); 2338 tmoveto(0, term.c.y+csiescseq.arg[0]); 2339 break; 2340 case 'F': /* CPL -- Cursor <n> Up and first col */ 2341 DEFAULT(csiescseq.arg[0], 1); 2342 tmoveto(0, term.c.y-csiescseq.arg[0]); 2343 break; 2344 case 'g': /* TBC -- Tabulation clear */ 2345 switch (csiescseq.arg[0]) { 2346 case 0: /* clear current tab stop */ 2347 term.tabs[term.c.x] = 0; 2348 break; 2349 case 3: /* clear all the tabs */ 2350 memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 2351 break; 2352 default: 2353 goto unknown; 2354 } 2355 break; 2356 case 'G': /* CHA -- Move to <col> */ 2357 case '`': /* HPA */ 2358 DEFAULT(csiescseq.arg[0], 1); 2359 tmoveto(csiescseq.arg[0]-1, term.c.y); 2360 break; 2361 case 'H': /* CUP -- Move to <row> <col> */ 2362 case 'f': /* HVP */ 2363 DEFAULT(csiescseq.arg[0], 1); 2364 DEFAULT(csiescseq.arg[1], 1); 2365 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); 2366 break; 2367 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */ 2368 DEFAULT(csiescseq.arg[0], 1); 2369 tputtab(csiescseq.arg[0]); 2370 break; 2371 case 'J': /* ED -- Clear screen */ 2372 selclear(NULL); 2373 switch (csiescseq.arg[0]) { 2374 case 0: /* below */ 2375 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); 2376 if (term.c.y < term.row-1) { 2377 tclearregion(0, term.c.y+1, term.col-1, 2378 term.row-1); 2379 } 2380 break; 2381 case 1: /* above */ 2382 if (term.c.y > 1) 2383 tclearregion(0, 0, term.col-1, term.c.y-1); 2384 tclearregion(0, term.c.y, term.c.x, term.c.y); 2385 break; 2386 case 2: /* all */ 2387 tclearregion(0, 0, term.col-1, term.row-1); 2388 break; 2389 default: 2390 goto unknown; 2391 } 2392 break; 2393 case 'K': /* EL -- Clear line */ 2394 switch (csiescseq.arg[0]) { 2395 case 0: /* right */ 2396 tclearregion(term.c.x, term.c.y, term.col-1, 2397 term.c.y); 2398 break; 2399 case 1: /* left */ 2400 tclearregion(0, term.c.y, term.c.x, term.c.y); 2401 break; 2402 case 2: /* all */ 2403 tclearregion(0, term.c.y, term.col-1, term.c.y); 2404 break; 2405 } 2406 break; 2407 case 'S': /* SU -- Scroll <n> line up */ 2408 DEFAULT(csiescseq.arg[0], 1); 2409 tscrollup(term.top, csiescseq.arg[0]); 2410 break; 2411 case 'T': /* SD -- Scroll <n> line down */ 2412 DEFAULT(csiescseq.arg[0], 1); 2413 tscrolldown(term.top, csiescseq.arg[0]); 2414 break; 2415 case 'L': /* IL -- Insert <n> blank lines */ 2416 DEFAULT(csiescseq.arg[0], 1); 2417 tinsertblankline(csiescseq.arg[0]); 2418 break; 2419 case 'l': /* RM -- Reset Mode */ 2420 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 2421 break; 2422 case 'M': /* DL -- Delete <n> lines */ 2423 DEFAULT(csiescseq.arg[0], 1); 2424 tdeleteline(csiescseq.arg[0]); 2425 break; 2426 case 'X': /* ECH -- Erase <n> char */ 2427 DEFAULT(csiescseq.arg[0], 1); 2428 tclearregion(term.c.x, term.c.y, 2429 term.c.x + csiescseq.arg[0] - 1, term.c.y); 2430 break; 2431 case 'P': /* DCH -- Delete <n> char */ 2432 DEFAULT(csiescseq.arg[0], 1); 2433 tdeletechar(csiescseq.arg[0]); 2434 break; 2435 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ 2436 DEFAULT(csiescseq.arg[0], 1); 2437 tputtab(-csiescseq.arg[0]); 2438 break; 2439 case 'd': /* VPA -- Move to <row> */ 2440 DEFAULT(csiescseq.arg[0], 1); 2441 tmoveato(term.c.x, csiescseq.arg[0]-1); 2442 break; 2443 case 'h': /* SM -- Set terminal mode */ 2444 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 2445 break; 2446 case 'm': /* SGR -- Terminal attribute (color) */ 2447 tsetattr(csiescseq.arg, csiescseq.narg); 2448 break; 2449 case 'n': /* DSR – Device Status Report (cursor position) */ 2450 if (csiescseq.arg[0] == 6) { 2451 len = snprintf(buf, sizeof(buf),"\033[%i;%iR", 2452 term.c.y+1, term.c.x+1); 2453 ttywrite(buf, len); 2454 } 2455 break; 2456 case 'r': /* DECSTBM -- Set Scrolling Region */ 2457 if (csiescseq.priv) { 2458 goto unknown; 2459 } else { 2460 DEFAULT(csiescseq.arg[0], 1); 2461 DEFAULT(csiescseq.arg[1], term.row); 2462 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); 2463 tmoveato(0, 0); 2464 } 2465 break; 2466 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 2467 tcursor(CURSOR_SAVE); 2468 break; 2469 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 2470 tcursor(CURSOR_LOAD); 2471 break; 2472 case ' ': 2473 switch (csiescseq.mode[1]) { 2474 case 'q': /* DECSCUSR -- Set Cursor Style */ 2475 DEFAULT(csiescseq.arg[0], 1); 2476 if (!BETWEEN(csiescseq.arg[0], 0, 6)) { 2477 goto unknown; 2478 } 2479 xw.cursor = csiescseq.arg[0]; 2480 break; 2481 default: 2482 goto unknown; 2483 } 2484 break; 2485 } 2486 } 2487 2488 void 2489 csidump(void) 2490 { 2491 int i; 2492 uint c; 2493 2494 fprintf(stderr, "ESC["); 2495 for (i = 0; i < csiescseq.len; i++) { 2496 c = csiescseq.buf[i] & 0xff; 2497 if (isprint(c)) { 2498 putc(c, stderr); 2499 } else if (c == '\n') { 2500 fprintf(stderr, "(\\n)"); 2501 } else if (c == '\r') { 2502 fprintf(stderr, "(\\r)"); 2503 } else if (c == 0x1b) { 2504 fprintf(stderr, "(\\e)"); 2505 } else { 2506 fprintf(stderr, "(%02x)", c); 2507 } 2508 } 2509 putc('\n', stderr); 2510 } 2511 2512 void 2513 csireset(void) 2514 { 2515 memset(&csiescseq, 0, sizeof(csiescseq)); 2516 } 2517 2518 void 2519 strhandle(void) 2520 { 2521 char *p = NULL; 2522 int j, narg, par; 2523 2524 term.esc &= ~(ESC_STR_END|ESC_STR); 2525 strparse(); 2526 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 2527 2528 switch (strescseq.type) { 2529 case ']': /* OSC -- Operating System Command */ 2530 switch (par) { 2531 case 0: 2532 case 1: 2533 case 2: 2534 if (narg > 1) 2535 xsettitle(strescseq.args[1]); 2536 return; 2537 case 4: /* color set */ 2538 if (narg < 3) 2539 break; 2540 p = strescseq.args[2]; 2541 /* FALLTHROUGH */ 2542 case 104: /* color reset, here p = NULL */ 2543 j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 2544 if (xsetcolorname(j, p)) { 2545 fprintf(stderr, "erresc: invalid color %s\n", p); 2546 } else { 2547 /* 2548 * TODO if defaultbg color is changed, borders 2549 * are dirty 2550 */ 2551 redraw(); 2552 } 2553 return; 2554 } 2555 break; 2556 case 'k': /* old title set compatibility */ 2557 xsettitle(strescseq.args[0]); 2558 return; 2559 case 'P': /* DCS -- Device Control String */ 2560 term.mode |= ESC_DCS; 2561 case '_': /* APC -- Application Program Command */ 2562 case '^': /* PM -- Privacy Message */ 2563 return; 2564 } 2565 2566 fprintf(stderr, "erresc: unknown str "); 2567 strdump(); 2568 } 2569 2570 void 2571 strparse(void) 2572 { 2573 int c; 2574 char *p = strescseq.buf; 2575 2576 strescseq.narg = 0; 2577 strescseq.buf[strescseq.len] = '\0'; 2578 2579 if (*p == '\0') 2580 return; 2581 2582 while (strescseq.narg < STR_ARG_SIZ) { 2583 strescseq.args[strescseq.narg++] = p; 2584 while ((c = *p) != ';' && c != '\0') 2585 ++p; 2586 if (c == '\0') 2587 return; 2588 *p++ = '\0'; 2589 } 2590 } 2591 2592 void 2593 strdump(void) 2594 { 2595 int i; 2596 uint c; 2597 2598 fprintf(stderr, "ESC%c", strescseq.type); 2599 for (i = 0; i < strescseq.len; i++) { 2600 c = strescseq.buf[i] & 0xff; 2601 if (c == '\0') { 2602 putc('\n', stderr); 2603 return; 2604 } else if (isprint(c)) { 2605 putc(c, stderr); 2606 } else if (c == '\n') { 2607 fprintf(stderr, "(\\n)"); 2608 } else if (c == '\r') { 2609 fprintf(stderr, "(\\r)"); 2610 } else if (c == 0x1b) { 2611 fprintf(stderr, "(\\e)"); 2612 } else { 2613 fprintf(stderr, "(%02x)", c); 2614 } 2615 } 2616 fprintf(stderr, "ESC\\\n"); 2617 } 2618 2619 void 2620 strreset(void) 2621 { 2622 memset(&strescseq, 0, sizeof(strescseq)); 2623 } 2624 2625 void 2626 sendbreak(const Arg *arg) 2627 { 2628 if (tcsendbreak(cmdfd, 0)) 2629 perror("Error sending break"); 2630 } 2631 2632 void 2633 tprinter(char *s, size_t len) 2634 { 2635 if (iofd != -1 && xwrite(iofd, s, len) < 0) { 2636 fprintf(stderr, "Error writing in %s:%s\n", 2637 opt_io, strerror(errno)); 2638 close(iofd); 2639 iofd = -1; 2640 } 2641 } 2642 2643 void 2644 iso14755(const Arg *arg) 2645 { 2646 char cmd[sizeof(ISO14755CMD) + NUMMAXLEN(xw.win)]; 2647 FILE *p; 2648 char *us, *e, codepoint[9], uc[UTF_SIZ]; 2649 unsigned long utf32; 2650 2651 snprintf(cmd, sizeof(cmd), ISO14755CMD, xw.win); 2652 if (!(p = popen(cmd, "r"))) 2653 return; 2654 2655 us = fgets(codepoint, sizeof(codepoint), p); 2656 pclose(p); 2657 2658 if (!us || *us == '\0' || *us == '-' || strlen(us) > 7) 2659 return; 2660 if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX || 2661 (*e != '\n' && *e != '\0')) 2662 return; 2663 2664 ttysend(uc, utf8encode(utf32, uc)); 2665 } 2666 2667 void 2668 toggleprinter(const Arg *arg) 2669 { 2670 term.mode ^= MODE_PRINT; 2671 } 2672 2673 void 2674 printscreen(const Arg *arg) 2675 { 2676 tdump(); 2677 } 2678 2679 void 2680 printsel(const Arg *arg) 2681 { 2682 tdumpsel(); 2683 } 2684 2685 void 2686 tdumpsel(void) 2687 { 2688 char *ptr; 2689 2690 if ((ptr = getsel())) { 2691 tprinter(ptr, strlen(ptr)); 2692 free(ptr); 2693 } 2694 } 2695 2696 void 2697 tdumpline(int n) 2698 { 2699 char buf[UTF_SIZ]; 2700 Glyph *bp, *end; 2701 2702 bp = &term.line[n][0]; 2703 end = &bp[MIN(tlinelen(n), term.col) - 1]; 2704 if (bp != end || bp->u != ' ') { 2705 for ( ;bp <= end; ++bp) 2706 tprinter(buf, utf8encode(bp->u, buf)); 2707 } 2708 tprinter("\n", 1); 2709 } 2710 2711 void 2712 tdump(void) 2713 { 2714 int i; 2715 2716 for (i = 0; i < term.row; ++i) 2717 tdumpline(i); 2718 } 2719 2720 void 2721 tputtab(int n) 2722 { 2723 uint x = term.c.x; 2724 2725 if (n > 0) { 2726 while (x < term.col && n--) 2727 for (++x; x < term.col && !term.tabs[x]; ++x) 2728 /* nothing */ ; 2729 } else if (n < 0) { 2730 while (x > 0 && n++) 2731 for (--x; x > 0 && !term.tabs[x]; --x) 2732 /* nothing */ ; 2733 } 2734 term.c.x = LIMIT(x, 0, term.col-1); 2735 } 2736 2737 void 2738 techo(Rune u) 2739 { 2740 if (ISCONTROL(u)) { /* control code */ 2741 if (u & 0x80) { 2742 u &= 0x7f; 2743 tputc('^'); 2744 tputc('['); 2745 } else if (u != '\n' && u != '\r' && u != '\t') { 2746 u ^= 0x40; 2747 tputc('^'); 2748 } 2749 } 2750 tputc(u); 2751 } 2752 2753 void 2754 tdefutf8(char ascii) 2755 { 2756 if (ascii == 'G') 2757 term.mode |= MODE_UTF8; 2758 else if (ascii == '@') 2759 term.mode &= ~MODE_UTF8; 2760 } 2761 2762 void 2763 tdeftran(char ascii) 2764 { 2765 static char cs[] = "0B"; 2766 static int vcs[] = {CS_GRAPHIC0, CS_USA}; 2767 char *p; 2768 2769 if ((p = strchr(cs, ascii)) == NULL) { 2770 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 2771 } else { 2772 term.trantbl[term.icharset] = vcs[p - cs]; 2773 } 2774 } 2775 2776 void 2777 tdectest(char c) 2778 { 2779 int x, y; 2780 2781 if (c == '8') { /* DEC screen alignment test. */ 2782 for (x = 0; x < term.col; ++x) { 2783 for (y = 0; y < term.row; ++y) 2784 tsetchar('E', &term.c.attr, x, y); 2785 } 2786 } 2787 } 2788 2789 void 2790 tstrsequence(uchar c) 2791 { 2792 strreset(); 2793 2794 switch (c) { 2795 case 0x90: /* DCS -- Device Control String */ 2796 c = 'P'; 2797 term.esc |= ESC_DCS; 2798 break; 2799 case 0x9f: /* APC -- Application Program Command */ 2800 c = '_'; 2801 break; 2802 case 0x9e: /* PM -- Privacy Message */ 2803 c = '^'; 2804 break; 2805 case 0x9d: /* OSC -- Operating System Command */ 2806 c = ']'; 2807 break; 2808 } 2809 strescseq.type = c; 2810 term.esc |= ESC_STR; 2811 } 2812 2813 void 2814 tcontrolcode(uchar ascii) 2815 { 2816 switch (ascii) { 2817 case '\t': /* HT */ 2818 tputtab(1); 2819 return; 2820 case '\b': /* BS */ 2821 tmoveto(term.c.x-1, term.c.y); 2822 return; 2823 case '\r': /* CR */ 2824 tmoveto(0, term.c.y); 2825 return; 2826 case '\f': /* LF */ 2827 case '\v': /* VT */ 2828 case '\n': /* LF */ 2829 /* go to first col if the mode is set */ 2830 tnewline(IS_SET(MODE_CRLF)); 2831 return; 2832 case '\a': /* BEL */ 2833 if (term.esc & ESC_STR_END) { 2834 /* backwards compatibility to xterm */ 2835 strhandle(); 2836 } else { 2837 if (!(xw.state & WIN_FOCUSED)) 2838 xseturgency(1); 2839 if (bellvolume) 2840 XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); 2841 } 2842 break; 2843 case '\033': /* ESC */ 2844 csireset(); 2845 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); 2846 term.esc |= ESC_START; 2847 return; 2848 case '\016': /* SO (LS1 -- Locking shift 1) */ 2849 case '\017': /* SI (LS0 -- Locking shift 0) */ 2850 term.charset = 1 - (ascii - '\016'); 2851 return; 2852 case '\032': /* SUB */ 2853 tsetchar('?', &term.c.attr, term.c.x, term.c.y); 2854 case '\030': /* CAN */ 2855 csireset(); 2856 break; 2857 case '\005': /* ENQ (IGNORED) */ 2858 case '\000': /* NUL (IGNORED) */ 2859 case '\021': /* XON (IGNORED) */ 2860 case '\023': /* XOFF (IGNORED) */ 2861 case 0177: /* DEL (IGNORED) */ 2862 return; 2863 case 0x80: /* TODO: PAD */ 2864 case 0x81: /* TODO: HOP */ 2865 case 0x82: /* TODO: BPH */ 2866 case 0x83: /* TODO: NBH */ 2867 case 0x84: /* TODO: IND */ 2868 break; 2869 case 0x85: /* NEL -- Next line */ 2870 tnewline(1); /* always go to first col */ 2871 break; 2872 case 0x86: /* TODO: SSA */ 2873 case 0x87: /* TODO: ESA */ 2874 break; 2875 case 0x88: /* HTS -- Horizontal tab stop */ 2876 term.tabs[term.c.x] = 1; 2877 break; 2878 case 0x89: /* TODO: HTJ */ 2879 case 0x8a: /* TODO: VTS */ 2880 case 0x8b: /* TODO: PLD */ 2881 case 0x8c: /* TODO: PLU */ 2882 case 0x8d: /* TODO: RI */ 2883 case 0x8e: /* TODO: SS2 */ 2884 case 0x8f: /* TODO: SS3 */ 2885 case 0x91: /* TODO: PU1 */ 2886 case 0x92: /* TODO: PU2 */ 2887 case 0x93: /* TODO: STS */ 2888 case 0x94: /* TODO: CCH */ 2889 case 0x95: /* TODO: MW */ 2890 case 0x96: /* TODO: SPA */ 2891 case 0x97: /* TODO: EPA */ 2892 case 0x98: /* TODO: SOS */ 2893 case 0x99: /* TODO: SGCI */ 2894 break; 2895 case 0x9a: /* DECID -- Identify Terminal */ 2896 ttywrite(vtiden, sizeof(vtiden) - 1); 2897 break; 2898 case 0x9b: /* TODO: CSI */ 2899 case 0x9c: /* TODO: ST */ 2900 break; 2901 case 0x90: /* DCS -- Device Control String */ 2902 case 0x9d: /* OSC -- Operating System Command */ 2903 case 0x9e: /* PM -- Privacy Message */ 2904 case 0x9f: /* APC -- Application Program Command */ 2905 tstrsequence(ascii); 2906 return; 2907 } 2908 /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2909 term.esc &= ~(ESC_STR_END|ESC_STR); 2910 } 2911 2912 /* 2913 * returns 1 when the sequence is finished and it hasn't to read 2914 * more characters for this sequence, otherwise 0 2915 */ 2916 int 2917 eschandle(uchar ascii) 2918 { 2919 switch (ascii) { 2920 case '[': 2921 term.esc |= ESC_CSI; 2922 return 0; 2923 case '#': 2924 term.esc |= ESC_TEST; 2925 return 0; 2926 case '%': 2927 term.esc |= ESC_UTF8; 2928 return 0; 2929 case 'P': /* DCS -- Device Control String */ 2930 case '_': /* APC -- Application Program Command */ 2931 case '^': /* PM -- Privacy Message */ 2932 case ']': /* OSC -- Operating System Command */ 2933 case 'k': /* old title set compatibility */ 2934 tstrsequence(ascii); 2935 return 0; 2936 case 'n': /* LS2 -- Locking shift 2 */ 2937 case 'o': /* LS3 -- Locking shift 3 */ 2938 term.charset = 2 + (ascii - 'n'); 2939 break; 2940 case '(': /* GZD4 -- set primary charset G0 */ 2941 case ')': /* G1D4 -- set secondary charset G1 */ 2942 case '*': /* G2D4 -- set tertiary charset G2 */ 2943 case '+': /* G3D4 -- set quaternary charset G3 */ 2944 term.icharset = ascii - '('; 2945 term.esc |= ESC_ALTCHARSET; 2946 return 0; 2947 case 'D': /* IND -- Linefeed */ 2948 if (term.c.y == term.bot) { 2949 tscrollup(term.top, 1); 2950 } else { 2951 tmoveto(term.c.x, term.c.y+1); 2952 } 2953 break; 2954 case 'E': /* NEL -- Next line */ 2955 tnewline(1); /* always go to first col */ 2956 break; 2957 case 'H': /* HTS -- Horizontal tab stop */ 2958 term.tabs[term.c.x] = 1; 2959 break; 2960 case 'M': /* RI -- Reverse index */ 2961 if (term.c.y == term.top) { 2962 tscrolldown(term.top, 1); 2963 } else { 2964 tmoveto(term.c.x, term.c.y-1); 2965 } 2966 break; 2967 case 'Z': /* DECID -- Identify Terminal */ 2968 ttywrite(vtiden, sizeof(vtiden) - 1); 2969 break; 2970 case 'c': /* RIS -- Reset to inital state */ 2971 treset(); 2972 xresettitle(); 2973 xloadcols(); 2974 break; 2975 case '=': /* DECPAM -- Application keypad */ 2976 term.mode |= MODE_APPKEYPAD; 2977 break; 2978 case '>': /* DECPNM -- Normal keypad */ 2979 term.mode &= ~MODE_APPKEYPAD; 2980 break; 2981 case '7': /* DECSC -- Save Cursor */ 2982 tcursor(CURSOR_SAVE); 2983 break; 2984 case '8': /* DECRC -- Restore Cursor */ 2985 tcursor(CURSOR_LOAD); 2986 break; 2987 case '\\': /* ST -- String Terminator */ 2988 if (term.esc & ESC_STR_END) 2989 strhandle(); 2990 break; 2991 default: 2992 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", 2993 (uchar) ascii, isprint(ascii)? ascii:'.'); 2994 break; 2995 } 2996 return 1; 2997 } 2998 2999 void 3000 externalpipe(const Arg *arg) 3001 { 3002 int to[2]; /* 0 = read, 1 = write */ 3003 pid_t child; 3004 int n; 3005 void (*oldsigpipe)(int); 3006 char buf[UTF_SIZ]; 3007 Glyph *bp, *end; 3008 3009 if(pipe(to) == -1) 3010 return; 3011 3012 /* sigchld() handles this */ 3013 switch(child = fork()){ 3014 case -1: 3015 close(to[0]), close(to[1]); 3016 return; 3017 case 0: 3018 /* child */ 3019 close(to[1]); 3020 dup2(to[0], STDIN_FILENO); /* 0<&to */ 3021 close(to[0]); 3022 execvp( 3023 "sh", 3024 (char *const []){ 3025 "/bin/sh", 3026 "-c", 3027 (char *)arg->v, 3028 0 3029 }); 3030 exit(127); 3031 } 3032 3033 /* parent */ 3034 close(to[0]); 3035 /* ignore sigpipe for now, in case child exits early */ 3036 oldsigpipe = signal(SIGPIPE, SIG_IGN); 3037 3038 for(n = 0; n < term.row; n++){ 3039 bp = &term.line[n][0]; 3040 end = &bp[MIN(tlinelen(n), term.col) - 1]; 3041 if(bp != end || bp->u != ' ') 3042 for(; bp <= end; ++bp) 3043 if(xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0) 3044 break; 3045 if(xwrite(to[1], "\n", 1) < 0) 3046 break; 3047 } 3048 3049 close(to[1]); 3050 3051 /* restore */ 3052 signal(SIGPIPE, oldsigpipe); 3053 } 3054 3055 void 3056 tputc(Rune u) 3057 { 3058 char c[UTF_SIZ]; 3059 int control; 3060 int width, len; 3061 Glyph *gp; 3062 3063 control = ISCONTROL(u); 3064 if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { 3065 c[0] = u; 3066 width = len = 1; 3067 } else { 3068 len = utf8encode(u, c); 3069 if (!control && (width = wcwidth(u)) == -1) { 3070 memcpy(c, "\357\277\275", 4); /* UTF_INVALID */ 3071 width = 1; 3072 } 3073 } 3074 3075 if (IS_SET(MODE_PRINT)) 3076 tprinter(c, len); 3077 3078 /* 3079 * STR sequence must be checked before anything else 3080 * because it uses all following characters until it 3081 * receives a ESC, a SUB, a ST or any other C1 control 3082 * character. 3083 */ 3084 if (term.esc & ESC_STR) { 3085 if (u == '\a' || u == 030 || u == 032 || u == 033 || 3086 ISCONTROLC1(u)) { 3087 term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); 3088 if (IS_SET(MODE_SIXEL)) { 3089 /* TODO: render sixel */; 3090 term.mode &= ~MODE_SIXEL; 3091 return; 3092 } 3093 term.esc |= ESC_STR_END; 3094 goto check_control_code; 3095 } 3096 3097 3098 if (IS_SET(MODE_SIXEL)) { 3099 /* TODO: implement sixel mode */ 3100 return; 3101 } 3102 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') 3103 term.mode |= MODE_SIXEL; 3104 3105 if (strescseq.len+len >= sizeof(strescseq.buf)-1) { 3106 /* 3107 * Here is a bug in terminals. If the user never sends 3108 * some code to stop the str or esc command, then st 3109 * will stop responding. But this is better than 3110 * silently failing with unknown characters. At least 3111 * then users will report back. 3112 * 3113 * In the case users ever get fixed, here is the code: 3114 */ 3115 /* 3116 * term.esc = 0; 3117 * strhandle(); 3118 */ 3119 return; 3120 } 3121 3122 memmove(&strescseq.buf[strescseq.len], c, len); 3123 strescseq.len += len; 3124 return; 3125 } 3126 3127 check_control_code: 3128 /* 3129 * Actions of control codes must be performed as soon they arrive 3130 * because they can be embedded inside a control sequence, and 3131 * they must not cause conflicts with sequences. 3132 */ 3133 if (control) { 3134 tcontrolcode(u); 3135 /* 3136 * control codes are not shown ever 3137 */ 3138 return; 3139 } else if (term.esc & ESC_START) { 3140 if (term.esc & ESC_CSI) { 3141 csiescseq.buf[csiescseq.len++] = u; 3142 if (BETWEEN(u, 0x40, 0x7E) 3143 || csiescseq.len >= \ 3144 sizeof(csiescseq.buf)-1) { 3145 term.esc = 0; 3146 csiparse(); 3147 csihandle(); 3148 } 3149 return; 3150 } else if (term.esc & ESC_UTF8) { 3151 tdefutf8(u); 3152 } else if (term.esc & ESC_ALTCHARSET) { 3153 tdeftran(u); 3154 } else if (term.esc & ESC_TEST) { 3155 tdectest(u); 3156 } else { 3157 if (!eschandle(u)) 3158 return; 3159 /* sequence already finished */ 3160 } 3161 term.esc = 0; 3162 /* 3163 * All characters which form part of a sequence are not 3164 * printed 3165 */ 3166 return; 3167 } 3168 if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y)) 3169 selclear(NULL); 3170 3171 gp = &term.line[term.c.y][term.c.x]; 3172 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 3173 gp->mode |= ATTR_WRAP; 3174 tnewline(1); 3175 gp = &term.line[term.c.y][term.c.x]; 3176 } 3177 3178 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) 3179 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); 3180 3181 if (term.c.x+width > term.col) { 3182 tnewline(1); 3183 gp = &term.line[term.c.y][term.c.x]; 3184 } 3185 3186 tsetchar(u, &term.c.attr, term.c.x, term.c.y); 3187 3188 if (width == 2) { 3189 gp->mode |= ATTR_WIDE; 3190 if (term.c.x+1 < term.col) { 3191 gp[1].u = '\0'; 3192 gp[1].mode = ATTR_WDUMMY; 3193 } 3194 } 3195 if (term.c.x+width < term.col) { 3196 tmoveto(term.c.x+width, term.c.y); 3197 } else { 3198 term.c.state |= CURSOR_WRAPNEXT; 3199 } 3200 } 3201 3202 void 3203 tresize(int col, int row) 3204 { 3205 int i; 3206 int minrow = MIN(row, term.row); 3207 int mincol = MIN(col, term.col); 3208 int *bp; 3209 TCursor c; 3210 3211 if (col < 1 || row < 1) { 3212 fprintf(stderr, 3213 "tresize: error resizing to %dx%d\n", col, row); 3214 return; 3215 } 3216 3217 /* 3218 * slide screen to keep cursor where we expect it - 3219 * tscrollup would work here, but we can optimize to 3220 * memmove because we're freeing the earlier lines 3221 */ 3222 for (i = 0; i <= term.c.y - row; i++) { 3223 free(term.line[i]); 3224 free(term.alt[i]); 3225 } 3226 /* ensure that both src and dst are not NULL */ 3227 if (i > 0) { 3228 memmove(term.line, term.line + i, row * sizeof(Line)); 3229 memmove(term.alt, term.alt + i, row * sizeof(Line)); 3230 } 3231 for (i += row; i < term.row; i++) { 3232 free(term.line[i]); 3233 free(term.alt[i]); 3234 } 3235 3236 /* resize to new width */ 3237 term.specbuf = xrealloc(term.specbuf, col * sizeof(XftGlyphFontSpec)); 3238 3239 /* resize to new height */ 3240 term.line = xrealloc(term.line, row * sizeof(Line)); 3241 term.alt = xrealloc(term.alt, row * sizeof(Line)); 3242 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 3243 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 3244 3245 /* resize each row to new width, zero-pad if needed */ 3246 for (i = 0; i < minrow; i++) { 3247 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 3248 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); 3249 } 3250 3251 /* allocate any new rows */ 3252 for (/* i == minrow */; i < row; i++) { 3253 term.line[i] = xmalloc(col * sizeof(Glyph)); 3254 term.alt[i] = xmalloc(col * sizeof(Glyph)); 3255 } 3256 if (col > term.col) { 3257 bp = term.tabs + term.col; 3258 3259 memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 3260 while (--bp > term.tabs && !*bp) 3261 /* nothing */ ; 3262 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 3263 *bp = 1; 3264 } 3265 /* update terminal size */ 3266 term.col = col; 3267 term.row = row; 3268 /* reset scrolling region */ 3269 tsetscroll(0, row-1); 3270 /* make use of the LIMIT in tmoveto */ 3271 tmoveto(term.c.x, term.c.y); 3272 /* Clearing both screens (it makes dirty all lines) */ 3273 c = term.c; 3274 for (i = 0; i < 2; i++) { 3275 if (mincol < col && 0 < minrow) { 3276 tclearregion(mincol, 0, col - 1, minrow - 1); 3277 } 3278 if (0 < col && minrow < row) { 3279 tclearregion(0, minrow, col - 1, row - 1); 3280 } 3281 tswapscreen(); 3282 tcursor(CURSOR_LOAD); 3283 } 3284 term.c = c; 3285 } 3286 3287 void 3288 xresize(int col, int row) 3289 { 3290 xw.tw = MAX(1, col * xw.cw); 3291 xw.th = MAX(1, row * xw.ch); 3292 3293 XFreePixmap(xw.dpy, xw.buf); 3294 xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h, 3295 DefaultDepth(xw.dpy, xw.scr)); 3296 XftDrawChange(xw.draw, xw.buf); 3297 xclear(0, 0, xw.w, xw.h); 3298 } 3299 3300 ushort 3301 sixd_to_16bit(int x) 3302 { 3303 return x == 0 ? 0 : 0x3737 + 0x2828 * x; 3304 } 3305 3306 int 3307 xloadcolor(int i, const char *name, Color *ncolor) 3308 { 3309 XRenderColor color = { .alpha = 0xffff }; 3310 3311 if (!name) { 3312 if (BETWEEN(i, 16, 255)) { /* 256 color */ 3313 if (i < 6*6*6+16) { /* same colors as xterm */ 3314 color.red = sixd_to_16bit( ((i-16)/36)%6 ); 3315 color.green = sixd_to_16bit( ((i-16)/6) %6 ); 3316 color.blue = sixd_to_16bit( ((i-16)/1) %6 ); 3317 } else { /* greyscale */ 3318 color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); 3319 color.green = color.blue = color.red; 3320 } 3321 return XftColorAllocValue(xw.dpy, xw.vis, 3322 xw.cmap, &color, ncolor); 3323 } else 3324 name = colorname[i]; 3325 } 3326 3327 return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); 3328 } 3329 3330 void 3331 xloadcols(void) 3332 { 3333 int i; 3334 static int loaded; 3335 Color *cp; 3336 3337 if (loaded) { 3338 for (cp = dc.col; cp < &dc.col[LEN(dc.col)]; ++cp) 3339 XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); 3340 } 3341 3342 for (i = 0; i < LEN(dc.col); i++) 3343 if (!xloadcolor(i, NULL, &dc.col[i])) { 3344 if (colorname[i]) 3345 die("Could not allocate color '%s'\n", colorname[i]); 3346 else 3347 die("Could not allocate color %d\n", i); 3348 } 3349 loaded = 1; 3350 } 3351 3352 int 3353 xsetcolorname(int x, const char *name) 3354 { 3355 Color ncolor; 3356 3357 if (!BETWEEN(x, 0, LEN(dc.col))) 3358 return 1; 3359 3360 3361 if (!xloadcolor(x, name, &ncolor)) 3362 return 1; 3363 3364 XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); 3365 dc.col[x] = ncolor; 3366 3367 return 0; 3368 } 3369 3370 /* 3371 * Absolute coordinates. 3372 */ 3373 void 3374 xclear(int x1, int y1, int x2, int y2) 3375 { 3376 XftDrawRect(xw.draw, 3377 &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], 3378 x1, y1, x2-x1, y2-y1); 3379 } 3380 3381 void 3382 xhints(void) 3383 { 3384 XClassHint class = {opt_name ? opt_name : termname, 3385 opt_class ? opt_class : termname}; 3386 XWMHints wm = {.flags = InputHint, .input = 1}; 3387 XSizeHints *sizeh = NULL; 3388 3389 sizeh = XAllocSizeHints(); 3390 3391 sizeh->flags = PSize | PResizeInc | PBaseSize; 3392 sizeh->height = xw.h; 3393 sizeh->width = xw.w; 3394 sizeh->height_inc = xw.ch; 3395 sizeh->width_inc = xw.cw; 3396 sizeh->base_height = 2 * borderpx; 3397 sizeh->base_width = 2 * borderpx; 3398 if (xw.isfixed) { 3399 sizeh->flags |= PMaxSize | PMinSize; 3400 sizeh->min_width = sizeh->max_width = xw.w; 3401 sizeh->min_height = sizeh->max_height = xw.h; 3402 } 3403 if (xw.gm & (XValue|YValue)) { 3404 sizeh->flags |= USPosition | PWinGravity; 3405 sizeh->x = xw.l; 3406 sizeh->y = xw.t; 3407 sizeh->win_gravity = xgeommasktogravity(xw.gm); 3408 } 3409 3410 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, 3411 &class); 3412 XFree(sizeh); 3413 } 3414 3415 int 3416 xgeommasktogravity(int mask) 3417 { 3418 switch (mask & (XNegative|YNegative)) { 3419 case 0: 3420 return NorthWestGravity; 3421 case XNegative: 3422 return NorthEastGravity; 3423 case YNegative: 3424 return SouthWestGravity; 3425 } 3426 3427 return SouthEastGravity; 3428 } 3429 3430 int 3431 xloadfont(Font *f, FcPattern *pattern) 3432 { 3433 FcPattern *configured; 3434 FcPattern *match; 3435 FcResult result; 3436 XGlyphInfo extents; 3437 int wantattr, haveattr; 3438 3439 /* 3440 * Manually configure instead of calling XftMatchFont 3441 * so that we can use the configured pattern for 3442 * "missing glyph" lookups. 3443 */ 3444 configured = FcPatternDuplicate(pattern); 3445 if (!configured) 3446 return 1; 3447 3448 FcConfigSubstitute(NULL, configured, FcMatchPattern); 3449 XftDefaultSubstitute(xw.dpy, xw.scr, configured); 3450 3451 match = FcFontMatch(NULL, configured, &result); 3452 if (!match) { 3453 FcPatternDestroy(configured); 3454 return 1; 3455 } 3456 3457 if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { 3458 FcPatternDestroy(configured); 3459 FcPatternDestroy(match); 3460 return 1; 3461 } 3462 3463 if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == 3464 XftResultMatch)) { 3465 /* 3466 * Check if xft was unable to find a font with the appropriate 3467 * slant but gave us one anyway. Try to mitigate. 3468 */ 3469 if ((XftPatternGetInteger(f->match->pattern, "slant", 0, 3470 &haveattr) != XftResultMatch) || haveattr < wantattr) { 3471 f->badslant = 1; 3472 fputs("st: font slant does not match\n", stderr); 3473 } 3474 } 3475 3476 if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == 3477 XftResultMatch)) { 3478 if ((XftPatternGetInteger(f->match->pattern, "weight", 0, 3479 &haveattr) != XftResultMatch) || haveattr != wantattr) { 3480 f->badweight = 1; 3481 fputs("st: font weight does not match\n", stderr); 3482 } 3483 } 3484 3485 XftTextExtentsUtf8(xw.dpy, f->match, 3486 (const FcChar8 *) ascii_printable, 3487 strlen(ascii_printable), &extents); 3488 3489 f->set = NULL; 3490 f->pattern = configured; 3491 3492 f->ascent = f->match->ascent; 3493 f->descent = f->match->descent; 3494 f->lbearing = 0; 3495 f->rbearing = f->match->max_advance_width; 3496 3497 f->height = f->ascent + f->descent; 3498 f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); 3499 3500 return 0; 3501 } 3502 3503 void 3504 xloadfonts(char *fontstr, double fontsize) 3505 { 3506 FcPattern *pattern; 3507 double fontval; 3508 float ceilf(float); 3509 3510 if (fontstr[0] == '-') { 3511 pattern = XftXlfdParse(fontstr, False, False); 3512 } else { 3513 pattern = FcNameParse((FcChar8 *)fontstr); 3514 } 3515 3516 if (!pattern) 3517 die("st: can't open font %s\n", fontstr); 3518 3519 if (fontsize > 1) { 3520 FcPatternDel(pattern, FC_PIXEL_SIZE); 3521 FcPatternDel(pattern, FC_SIZE); 3522 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); 3523 usedfontsize = fontsize; 3524 } else { 3525 if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == 3526 FcResultMatch) { 3527 usedfontsize = fontval; 3528 } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == 3529 FcResultMatch) { 3530 usedfontsize = -1; 3531 } else { 3532 /* 3533 * Default font size is 12, if none given. This is to 3534 * have a known usedfontsize value. 3535 */ 3536 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); 3537 usedfontsize = 12; 3538 } 3539 defaultfontsize = usedfontsize; 3540 } 3541 3542 if (xloadfont(&dc.font, pattern)) 3543 die("st: can't open font %s\n", fontstr); 3544 3545 if (usedfontsize < 0) { 3546 FcPatternGetDouble(dc.font.match->pattern, 3547 FC_PIXEL_SIZE, 0, &fontval); 3548 usedfontsize = fontval; 3549 if (fontsize == 0) 3550 defaultfontsize = fontval; 3551 } 3552 3553 /* Setting character width and height. */ 3554 xw.cw = ceilf(dc.font.width * cwscale); 3555 xw.ch = ceilf(dc.font.height * chscale); 3556 3557 FcPatternDel(pattern, FC_SLANT); 3558 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 3559 if (xloadfont(&dc.ifont, pattern)) 3560 die("st: can't open font %s\n", fontstr); 3561 3562 FcPatternDel(pattern, FC_WEIGHT); 3563 FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 3564 if (xloadfont(&dc.ibfont, pattern)) 3565 die("st: can't open font %s\n", fontstr); 3566 3567 FcPatternDel(pattern, FC_SLANT); 3568 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); 3569 if (xloadfont(&dc.bfont, pattern)) 3570 die("st: can't open font %s\n", fontstr); 3571 3572 FcPatternDestroy(pattern); 3573 } 3574 3575 void 3576 xunloadfont(Font *f) 3577 { 3578 XftFontClose(xw.dpy, f->match); 3579 FcPatternDestroy(f->pattern); 3580 if (f->set) 3581 FcFontSetDestroy(f->set); 3582 } 3583 3584 void 3585 xunloadfonts(void) 3586 { 3587 /* Free the loaded fonts in the font cache. */ 3588 while (frclen > 0) 3589 XftFontClose(xw.dpy, frc[--frclen].font); 3590 3591 xunloadfont(&dc.font); 3592 xunloadfont(&dc.bfont); 3593 xunloadfont(&dc.ifont); 3594 xunloadfont(&dc.ibfont); 3595 } 3596 3597 void 3598 xzoom(const Arg *arg) 3599 { 3600 Arg larg; 3601 3602 larg.f = usedfontsize + arg->f; 3603 xzoomabs(&larg); 3604 } 3605 3606 void 3607 xzoomabs(const Arg *arg) 3608 { 3609 xunloadfonts(); 3610 xloadfonts(usedfont, arg->f); 3611 cresize(0, 0); 3612 ttyresize(); 3613 redraw(); 3614 xhints(); 3615 } 3616 3617 void 3618 xzoomreset(const Arg *arg) 3619 { 3620 Arg larg; 3621 3622 if (defaultfontsize > 0) { 3623 larg.f = defaultfontsize; 3624 xzoomabs(&larg); 3625 } 3626 } 3627 3628 void 3629 xinit(void) 3630 { 3631 XGCValues gcvalues; 3632 Cursor cursor; 3633 Window parent; 3634 pid_t thispid = getpid(); 3635 XColor xmousefg, xmousebg; 3636 3637 if (!(xw.dpy = XOpenDisplay(NULL))) 3638 die("Can't open display\n"); 3639 xw.scr = XDefaultScreen(xw.dpy); 3640 xw.vis = XDefaultVisual(xw.dpy, xw.scr); 3641 3642 /* font */ 3643 if (!FcInit()) 3644 die("Could not init fontconfig.\n"); 3645 3646 usedfont = (opt_font == NULL)? font : opt_font; 3647 xloadfonts(usedfont, 0); 3648 3649 /* colors */ 3650 xw.cmap = XDefaultColormap(xw.dpy, xw.scr); 3651 xloadcols(); 3652 3653 /* adjust fixed window geometry */ 3654 xw.w = 2 * borderpx + term.col * xw.cw; 3655 xw.h = 2 * borderpx + term.row * xw.ch; 3656 if (xw.gm & XNegative) 3657 xw.l += DisplayWidth(xw.dpy, xw.scr) - xw.w - 2; 3658 if (xw.gm & YNegative) 3659 xw.t += DisplayHeight(xw.dpy, xw.scr) - xw.h - 2; 3660 3661 /* Events */ 3662 xw.attrs.background_pixel = dc.col[defaultbg].pixel; 3663 xw.attrs.border_pixel = dc.col[defaultbg].pixel; 3664 xw.attrs.bit_gravity = NorthWestGravity; 3665 xw.attrs.event_mask = FocusChangeMask | KeyPressMask 3666 | ExposureMask | VisibilityChangeMask | StructureNotifyMask 3667 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; 3668 xw.attrs.colormap = xw.cmap; 3669 3670 if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) 3671 parent = XRootWindow(xw.dpy, xw.scr); 3672 xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, 3673 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, 3674 xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity 3675 | CWEventMask | CWColormap, &xw.attrs); 3676 3677 memset(&gcvalues, 0, sizeof(gcvalues)); 3678 gcvalues.graphics_exposures = False; 3679 dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, 3680 &gcvalues); 3681 xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h, 3682 DefaultDepth(xw.dpy, xw.scr)); 3683 XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 3684 XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, xw.w, xw.h); 3685 3686 /* Xft rendering context */ 3687 xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); 3688 3689 /* input methods */ 3690 if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { 3691 XSetLocaleModifiers("@im=local"); 3692 if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { 3693 XSetLocaleModifiers("@im="); 3694 if ((xw.xim = XOpenIM(xw.dpy, 3695 NULL, NULL, NULL)) == NULL) { 3696 die("XOpenIM failed. Could not open input" 3697 " device.\n"); 3698 } 3699 } 3700 } 3701 xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing 3702 | XIMStatusNothing, XNClientWindow, xw.win, 3703 XNFocusWindow, xw.win, NULL); 3704 if (xw.xic == NULL) 3705 die("XCreateIC failed. Could not obtain input method.\n"); 3706 3707 /* white cursor, black outline */ 3708 cursor = XCreateFontCursor(xw.dpy, mouseshape); 3709 XDefineCursor(xw.dpy, xw.win, cursor); 3710 3711 if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { 3712 xmousefg.red = 0xffff; 3713 xmousefg.green = 0xffff; 3714 xmousefg.blue = 0xffff; 3715 } 3716 3717 if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { 3718 xmousebg.red = 0x0000; 3719 xmousebg.green = 0x0000; 3720 xmousebg.blue = 0x0000; 3721 } 3722 3723 XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); 3724 3725 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); 3726 xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); 3727 xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); 3728 XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); 3729 3730 xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); 3731 XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, 3732 PropModeReplace, (uchar *)&thispid, 1); 3733 3734 xresettitle(); 3735 XMapWindow(xw.dpy, xw.win); 3736 xhints(); 3737 XSync(xw.dpy, False); 3738 } 3739 3740 int 3741 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) 3742 { 3743 float winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, xp, yp; 3744 ushort mode, prevmode = USHRT_MAX; 3745 Font *font = &dc.font; 3746 int frcflags = FRC_NORMAL; 3747 float runewidth = xw.cw; 3748 Rune rune; 3749 FT_UInt glyphidx; 3750 FcResult fcres; 3751 FcPattern *fcpattern, *fontpattern; 3752 FcFontSet *fcsets[] = { NULL }; 3753 FcCharSet *fccharset; 3754 int i, f, numspecs = 0; 3755 3756 for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { 3757 /* Fetch rune and mode for current glyph. */ 3758 rune = glyphs[i].u; 3759 mode = glyphs[i].mode; 3760 3761 /* Skip dummy wide-character spacing. */ 3762 if (mode == ATTR_WDUMMY) 3763 continue; 3764 3765 /* Determine font for glyph if different from previous glyph. */ 3766 if (prevmode != mode) { 3767 prevmode = mode; 3768 font = &dc.font; 3769 frcflags = FRC_NORMAL; 3770 runewidth = xw.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); 3771 if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { 3772 font = &dc.ibfont; 3773 frcflags = FRC_ITALICBOLD; 3774 } else if (mode & ATTR_ITALIC) { 3775 font = &dc.ifont; 3776 frcflags = FRC_ITALIC; 3777 } else if (mode & ATTR_BOLD) { 3778 font = &dc.bfont; 3779 frcflags = FRC_BOLD; 3780 } 3781 yp = winy + font->ascent; 3782 } 3783 3784 /* Lookup character index with default font. */ 3785 glyphidx = XftCharIndex(xw.dpy, font->match, rune); 3786 if (glyphidx) { 3787 specs[numspecs].font = font->match; 3788 specs[numspecs].glyph = glyphidx; 3789 specs[numspecs].x = (short)xp; 3790 specs[numspecs].y = (short)yp; 3791 xp += runewidth; 3792 numspecs++; 3793 continue; 3794 } 3795 3796 /* Fallback on font cache, search the font cache for match. */ 3797 for (f = 0; f < frclen; f++) { 3798 glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); 3799 /* Everything correct. */ 3800 if (glyphidx && frc[f].flags == frcflags) 3801 break; 3802 /* We got a default font for a not found glyph. */ 3803 if (!glyphidx && frc[f].flags == frcflags 3804 && frc[f].unicodep == rune) { 3805 break; 3806 } 3807 } 3808 3809 /* Nothing was found. Use fontconfig to find matching font. */ 3810 if (f >= frclen) { 3811 if (!font->set) 3812 font->set = FcFontSort(0, font->pattern, 3813 1, 0, &fcres); 3814 fcsets[0] = font->set; 3815 3816 /* 3817 * Nothing was found in the cache. Now use 3818 * some dozen of Fontconfig calls to get the 3819 * font for one single character. 3820 * 3821 * Xft and fontconfig are design failures. 3822 */ 3823 fcpattern = FcPatternDuplicate(font->pattern); 3824 fccharset = FcCharSetCreate(); 3825 3826 FcCharSetAddChar(fccharset, rune); 3827 FcPatternAddCharSet(fcpattern, FC_CHARSET, 3828 fccharset); 3829 FcPatternAddBool(fcpattern, FC_SCALABLE, 1); 3830 3831 FcConfigSubstitute(0, fcpattern, 3832 FcMatchPattern); 3833 FcDefaultSubstitute(fcpattern); 3834 3835 fontpattern = FcFontSetMatch(0, fcsets, 1, 3836 fcpattern, &fcres); 3837 3838 /* 3839 * Overwrite or create the new cache entry. 3840 */ 3841 if (frclen >= LEN(frc)) { 3842 frclen = LEN(frc) - 1; 3843 XftFontClose(xw.dpy, frc[frclen].font); 3844 frc[frclen].unicodep = 0; 3845 } 3846 3847 frc[frclen].font = XftFontOpenPattern(xw.dpy, 3848 fontpattern); 3849 frc[frclen].flags = frcflags; 3850 frc[frclen].unicodep = rune; 3851 3852 glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); 3853 3854 f = frclen; 3855 frclen++; 3856 3857 FcPatternDestroy(fcpattern); 3858 FcCharSetDestroy(fccharset); 3859 } 3860 3861 specs[numspecs].font = frc[f].font; 3862 specs[numspecs].glyph = glyphidx; 3863 specs[numspecs].x = (short)xp; 3864 specs[numspecs].y = (short)yp; 3865 xp += runewidth; 3866 numspecs++; 3867 } 3868 3869 return numspecs; 3870 } 3871 3872 void 3873 xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) 3874 { 3875 int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); 3876 int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, 3877 width = charlen * xw.cw; 3878 Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; 3879 XRenderColor colfg, colbg; 3880 XRectangle r; 3881 3882 /* Fallback on color display for attributes not supported by the font */ 3883 if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { 3884 if (dc.ibfont.badslant || dc.ibfont.badweight) 3885 base.fg = defaultattr; 3886 } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || 3887 (base.mode & ATTR_BOLD && dc.bfont.badweight)) { 3888 base.fg = defaultattr; 3889 } 3890 3891 if (IS_TRUECOL(base.fg)) { 3892 colfg.alpha = 0xffff; 3893 colfg.red = TRUERED(base.fg); 3894 colfg.green = TRUEGREEN(base.fg); 3895 colfg.blue = TRUEBLUE(base.fg); 3896 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); 3897 fg = &truefg; 3898 } else { 3899 fg = &dc.col[base.fg]; 3900 } 3901 3902 if (IS_TRUECOL(base.bg)) { 3903 colbg.alpha = 0xffff; 3904 colbg.green = TRUEGREEN(base.bg); 3905 colbg.red = TRUERED(base.bg); 3906 colbg.blue = TRUEBLUE(base.bg); 3907 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); 3908 bg = &truebg; 3909 } else { 3910 bg = &dc.col[base.bg]; 3911 } 3912 3913 /* Change basic system colors [0-7] to bright system colors [8-15] */ 3914 if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) 3915 fg = &dc.col[base.fg + 8]; 3916 3917 if (IS_SET(MODE_REVERSE)) { 3918 if (fg == &dc.col[defaultfg]) { 3919 fg = &dc.col[defaultbg]; 3920 } else { 3921 colfg.red = ~fg->color.red; 3922 colfg.green = ~fg->color.green; 3923 colfg.blue = ~fg->color.blue; 3924 colfg.alpha = fg->color.alpha; 3925 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, 3926 &revfg); 3927 fg = &revfg; 3928 } 3929 3930 if (bg == &dc.col[defaultbg]) { 3931 bg = &dc.col[defaultfg]; 3932 } else { 3933 colbg.red = ~bg->color.red; 3934 colbg.green = ~bg->color.green; 3935 colbg.blue = ~bg->color.blue; 3936 colbg.alpha = bg->color.alpha; 3937 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, 3938 &revbg); 3939 bg = &revbg; 3940 } 3941 } 3942 3943 if (base.mode & ATTR_REVERSE) { 3944 temp = fg; 3945 fg = bg; 3946 bg = temp; 3947 } 3948 3949 if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { 3950 colfg.red = fg->color.red / 2; 3951 colfg.green = fg->color.green / 2; 3952 colfg.blue = fg->color.blue / 2; 3953 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); 3954 fg = &revfg; 3955 } 3956 3957 if (base.mode & ATTR_BLINK && term.mode & MODE_BLINK) 3958 fg = bg; 3959 3960 if (base.mode & ATTR_INVISIBLE) 3961 fg = bg; 3962 3963 /* Intelligent cleaning up of the borders. */ 3964 if (x == 0) { 3965 xclear(0, (y == 0)? 0 : winy, borderpx, 3966 winy + xw.ch + ((y >= term.row-1)? xw.h : 0)); 3967 } 3968 if (x + charlen >= term.col) { 3969 xclear(winx + width, (y == 0)? 0 : winy, xw.w, 3970 ((y >= term.row-1)? xw.h : (winy + xw.ch))); 3971 } 3972 if (y == 0) 3973 xclear(winx, 0, winx + width, borderpx); 3974 if (y == term.row-1) 3975 xclear(winx, winy + xw.ch, winx + width, xw.h); 3976 3977 /* Clean up the region we want to draw to. */ 3978 XftDrawRect(xw.draw, bg, winx, winy, width, xw.ch); 3979 3980 /* Set the clip region because Xft is sometimes dirty. */ 3981 r.x = 0; 3982 r.y = 0; 3983 r.height = xw.ch; 3984 r.width = width; 3985 XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); 3986 3987 /* Render the glyphs. */ 3988 XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 3989 3990 /* Render underline and strikethrough. */ 3991 if (base.mode & ATTR_UNDERLINE) { 3992 XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, 3993 width, 1); 3994 } 3995 3996 if (base.mode & ATTR_STRUCK) { 3997 XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, 3998 width, 1); 3999 } 4000 4001 /* Reset clip to none. */ 4002 XftDrawSetClip(xw.draw, 0); 4003 } 4004 4005 void 4006 xdrawglyph(Glyph g, int x, int y) 4007 { 4008 int numspecs; 4009 XftGlyphFontSpec spec; 4010 4011 numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); 4012 xdrawglyphfontspecs(&spec, g, numspecs, x, y); 4013 } 4014 4015 void 4016 xdrawcursor(void) 4017 { 4018 static int oldx = 0, oldy = 0; 4019 int curx; 4020 Glyph g = {' ', ATTR_NULL, defaultbg, defaultcs}, og; 4021 int ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN); 4022 Color drawcol; 4023 4024 LIMIT(oldx, 0, term.col-1); 4025 LIMIT(oldy, 0, term.row-1); 4026 4027 curx = term.c.x; 4028 4029 /* adjust position if in dummy */ 4030 if (term.line[oldy][oldx].mode & ATTR_WDUMMY) 4031 oldx--; 4032 if (term.line[term.c.y][curx].mode & ATTR_WDUMMY) 4033 curx--; 4034 4035 /* remove the old cursor */ 4036 og = term.line[oldy][oldx]; 4037 if (ena_sel && selected(oldx, oldy)) 4038 og.mode ^= ATTR_REVERSE; 4039 xdrawglyph(og, oldx, oldy); 4040 4041 g.u = term.line[term.c.y][term.c.x].u; 4042 4043 /* 4044 * Select the right color for the right mode. 4045 */ 4046 if (IS_SET(MODE_REVERSE)) { 4047 g.mode |= ATTR_REVERSE; 4048 g.bg = defaultfg; 4049 if (ena_sel && selected(term.c.x, term.c.y)) { 4050 drawcol = dc.col[defaultcs]; 4051 g.fg = defaultrcs; 4052 } else { 4053 drawcol = dc.col[defaultrcs]; 4054 g.fg = defaultcs; 4055 } 4056 } else { 4057 if (ena_sel && selected(term.c.x, term.c.y)) { 4058 drawcol = dc.col[defaultrcs]; 4059 g.fg = defaultfg; 4060 g.bg = defaultrcs; 4061 } else { 4062 drawcol = dc.col[defaultcs]; 4063 } 4064 } 4065 4066 if (IS_SET(MODE_HIDE)) 4067 return; 4068 4069 /* draw the new one */ 4070 if (xw.state & WIN_FOCUSED) { 4071 switch (xw.cursor) { 4072 case 7: /* st extension: snowman */ 4073 utf8decode("☃", &g.u, UTF_SIZ); 4074 case 0: /* Blinking Block */ 4075 case 1: /* Blinking Block (Default) */ 4076 case 2: /* Steady Block */ 4077 g.mode |= term.line[term.c.y][curx].mode & ATTR_WIDE; 4078 xdrawglyph(g, term.c.x, term.c.y); 4079 break; 4080 case 3: /* Blinking Underline */ 4081 case 4: /* Steady Underline */ 4082 XftDrawRect(xw.draw, &drawcol, 4083 borderpx + curx * xw.cw, 4084 borderpx + (term.c.y + 1) * xw.ch - \ 4085 cursorthickness, 4086 xw.cw, cursorthickness); 4087 break; 4088 case 5: /* Blinking bar */ 4089 case 6: /* Steady bar */ 4090 XftDrawRect(xw.draw, &drawcol, 4091 borderpx + curx * xw.cw, 4092 borderpx + term.c.y * xw.ch, 4093 cursorthickness, xw.ch); 4094 break; 4095 } 4096 } else { 4097 XftDrawRect(xw.draw, &drawcol, 4098 borderpx + curx * xw.cw, 4099 borderpx + term.c.y * xw.ch, 4100 xw.cw - 1, 1); 4101 XftDrawRect(xw.draw, &drawcol, 4102 borderpx + curx * xw.cw, 4103 borderpx + term.c.y * xw.ch, 4104 1, xw.ch - 1); 4105 XftDrawRect(xw.draw, &drawcol, 4106 borderpx + (curx + 1) * xw.cw - 1, 4107 borderpx + term.c.y * xw.ch, 4108 1, xw.ch - 1); 4109 XftDrawRect(xw.draw, &drawcol, 4110 borderpx + curx * xw.cw, 4111 borderpx + (term.c.y + 1) * xw.ch - 1, 4112 xw.cw, 1); 4113 } 4114 oldx = curx, oldy = term.c.y; 4115 } 4116 4117 4118 void 4119 xsettitle(char *p) 4120 { 4121 XTextProperty prop; 4122 4123 Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 4124 &prop); 4125 XSetWMName(xw.dpy, xw.win, &prop); 4126 XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); 4127 XFree(prop.value); 4128 } 4129 4130 void 4131 xresettitle(void) 4132 { 4133 xsettitle(opt_title ? opt_title : "st"); 4134 } 4135 4136 void 4137 redraw(void) 4138 { 4139 tfulldirt(); 4140 draw(); 4141 } 4142 4143 void 4144 draw(void) 4145 { 4146 drawregion(0, 0, term.col, term.row); 4147 XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, xw.w, 4148 xw.h, 0, 0); 4149 XSetForeground(xw.dpy, dc.gc, 4150 dc.col[IS_SET(MODE_REVERSE)? 4151 defaultfg : defaultbg].pixel); 4152 } 4153 4154 void 4155 drawregion(int x1, int y1, int x2, int y2) 4156 { 4157 int i, x, y, ox, numspecs; 4158 Glyph base, new; 4159 XftGlyphFontSpec *specs; 4160 int ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN); 4161 4162 if (!(xw.state & WIN_VISIBLE)) 4163 return; 4164 4165 for (y = y1; y < y2; y++) { 4166 if (!term.dirty[y]) 4167 continue; 4168 4169 term.dirty[y] = 0; 4170 4171 specs = term.specbuf; 4172 numspecs = xmakeglyphfontspecs(specs, &term.line[y][x1], x2 - x1, x1, y); 4173 4174 i = ox = 0; 4175 for (x = x1; x < x2 && i < numspecs; x++) { 4176 new = term.line[y][x]; 4177 if (new.mode == ATTR_WDUMMY) 4178 continue; 4179 if (ena_sel && selected(x, y)) 4180 new.mode ^= ATTR_REVERSE; 4181 if (i > 0 && ATTRCMP(base, new)) { 4182 xdrawglyphfontspecs(specs, base, i, ox, y); 4183 specs += i; 4184 numspecs -= i; 4185 i = 0; 4186 } 4187 if (i == 0) { 4188 ox = x; 4189 base = new; 4190 } 4191 i++; 4192 } 4193 if (i > 0) 4194 xdrawglyphfontspecs(specs, base, i, ox, y); 4195 } 4196 xdrawcursor(); 4197 } 4198 4199 void 4200 expose(XEvent *ev) 4201 { 4202 redraw(); 4203 } 4204 4205 void 4206 visibility(XEvent *ev) 4207 { 4208 XVisibilityEvent *e = &ev->xvisibility; 4209 4210 MODBIT(xw.state, e->state != VisibilityFullyObscured, WIN_VISIBLE); 4211 } 4212 4213 void 4214 unmap(XEvent *ev) 4215 { 4216 xw.state &= ~WIN_VISIBLE; 4217 } 4218 4219 void 4220 xsetpointermotion(int set) 4221 { 4222 MODBIT(xw.attrs.event_mask, set, PointerMotionMask); 4223 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); 4224 } 4225 4226 void 4227 xseturgency(int add) 4228 { 4229 XWMHints *h = XGetWMHints(xw.dpy, xw.win); 4230 4231 MODBIT(h->flags, add, XUrgencyHint); 4232 XSetWMHints(xw.dpy, xw.win, h); 4233 XFree(h); 4234 } 4235 4236 void 4237 focus(XEvent *ev) 4238 { 4239 XFocusChangeEvent *e = &ev->xfocus; 4240 4241 if (e->mode == NotifyGrab) 4242 return; 4243 4244 if (ev->type == FocusIn) { 4245 XSetICFocus(xw.xic); 4246 xw.state |= WIN_FOCUSED; 4247 xseturgency(0); 4248 if (IS_SET(MODE_FOCUS)) 4249 ttywrite("\033[I", 3); 4250 } else { 4251 XUnsetICFocus(xw.xic); 4252 xw.state &= ~WIN_FOCUSED; 4253 if (IS_SET(MODE_FOCUS)) 4254 ttywrite("\033[O", 3); 4255 } 4256 } 4257 4258 int 4259 match(uint mask, uint state) 4260 { 4261 return mask == XK_ANY_MOD || mask == (state & ~ignoremod); 4262 } 4263 4264 void 4265 numlock(const Arg *dummy) 4266 { 4267 term.numlock ^= 1; 4268 } 4269 4270 char* 4271 kmap(KeySym k, uint state) 4272 { 4273 Key *kp; 4274 int i; 4275 4276 /* Check for mapped keys out of X11 function keys. */ 4277 for (i = 0; i < LEN(mappedkeys); i++) { 4278 if (mappedkeys[i] == k) 4279 break; 4280 } 4281 if (i == LEN(mappedkeys)) { 4282 if ((k & 0xFFFF) < 0xFD00) 4283 return NULL; 4284 } 4285 4286 for (kp = key; kp < key + LEN(key); kp++) { 4287 if (kp->k != k) 4288 continue; 4289 4290 if (!match(kp->mask, state)) 4291 continue; 4292 4293 if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) 4294 continue; 4295 if (term.numlock && kp->appkey == 2) 4296 continue; 4297 4298 if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) 4299 continue; 4300 4301 if (IS_SET(MODE_CRLF) ? kp->crlf < 0 : kp->crlf > 0) 4302 continue; 4303 4304 return kp->s; 4305 } 4306 4307 return NULL; 4308 } 4309 4310 void 4311 kpress(XEvent *ev) 4312 { 4313 XKeyEvent *e = &ev->xkey; 4314 KeySym ksym; 4315 char buf[32], *customkey; 4316 int len; 4317 Rune c; 4318 Status status; 4319 Shortcut *bp; 4320 4321 if (IS_SET(MODE_KBDLOCK)) 4322 return; 4323 4324 len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status); 4325 /* 1. shortcuts */ 4326 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 4327 if (ksym == bp->keysym && match(bp->mod, e->state)) { 4328 bp->func(&(bp->arg)); 4329 return; 4330 } 4331 } 4332 4333 /* 2. custom keys from config.h */ 4334 if ((customkey = kmap(ksym, e->state))) { 4335 ttysend(customkey, strlen(customkey)); 4336 return; 4337 } 4338 4339 /* 3. composed string from input method */ 4340 if (len == 0) 4341 return; 4342 if (len == 1 && e->state & Mod1Mask) { 4343 if (IS_SET(MODE_8BIT)) { 4344 if (*buf < 0177) { 4345 c = *buf | 0x80; 4346 len = utf8encode(c, buf); 4347 } 4348 } else { 4349 buf[1] = buf[0]; 4350 buf[0] = '\033'; 4351 len = 2; 4352 } 4353 } 4354 ttysend(buf, len); 4355 } 4356 4357 4358 void 4359 cmessage(XEvent *e) 4360 { 4361 /* 4362 * See xembed specs 4363 * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html 4364 */ 4365 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { 4366 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { 4367 xw.state |= WIN_FOCUSED; 4368 xseturgency(0); 4369 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { 4370 xw.state &= ~WIN_FOCUSED; 4371 } 4372 } else if (e->xclient.data.l[0] == xw.wmdeletewin) { 4373 /* Send SIGHUP to shell */ 4374 kill(pid, SIGHUP); 4375 exit(0); 4376 } 4377 } 4378 4379 void 4380 cresize(int width, int height) 4381 { 4382 int col, row; 4383 4384 if (width != 0) 4385 xw.w = width; 4386 if (height != 0) 4387 xw.h = height; 4388 4389 col = (xw.w - 2 * borderpx) / xw.cw; 4390 row = (xw.h - 2 * borderpx) / xw.ch; 4391 4392 tresize(col, row); 4393 xresize(col, row); 4394 } 4395 4396 void 4397 resize(XEvent *e) 4398 { 4399 if (e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) 4400 return; 4401 4402 cresize(e->xconfigure.width, e->xconfigure.height); 4403 ttyresize(); 4404 } 4405 4406 void 4407 run(void) 4408 { 4409 XEvent ev; 4410 int w = xw.w, h = xw.h; 4411 fd_set rfd; 4412 int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0; 4413 struct timespec drawtimeout, *tv = NULL, now, last, lastblink; 4414 long deltatime; 4415 4416 /* Waiting for window mapping */ 4417 do { 4418 XNextEvent(xw.dpy, &ev); 4419 /* 4420 * This XFilterEvent call is required because of XOpenIM. It 4421 * does filter out the key event and some client message for 4422 * the input method too. 4423 */ 4424 if (XFilterEvent(&ev, None)) 4425 continue; 4426 if (ev.type == ConfigureNotify) { 4427 w = ev.xconfigure.width; 4428 h = ev.xconfigure.height; 4429 } 4430 } while (ev.type != MapNotify); 4431 4432 cresize(w, h); 4433 ttynew(); 4434 ttyresize(); 4435 4436 clock_gettime(CLOCK_MONOTONIC, &last); 4437 lastblink = last; 4438 4439 for (xev = actionfps;;) { 4440 FD_ZERO(&rfd); 4441 FD_SET(cmdfd, &rfd); 4442 FD_SET(xfd, &rfd); 4443 4444 if (pselect(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { 4445 if (errno == EINTR) 4446 continue; 4447 die("select failed: %s\n", strerror(errno)); 4448 } 4449 if (FD_ISSET(cmdfd, &rfd)) { 4450 ttyread(); 4451 if (blinktimeout) { 4452 blinkset = tattrset(ATTR_BLINK); 4453 if (!blinkset) 4454 MODBIT(term.mode, 0, MODE_BLINK); 4455 } 4456 } 4457 4458 if (FD_ISSET(xfd, &rfd)) 4459 xev = actionfps; 4460 4461 clock_gettime(CLOCK_MONOTONIC, &now); 4462 drawtimeout.tv_sec = 0; 4463 drawtimeout.tv_nsec = (1000 * 1E6)/ xfps; 4464 tv = &drawtimeout; 4465 4466 dodraw = 0; 4467 if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { 4468 tsetdirtattr(ATTR_BLINK); 4469 term.mode ^= MODE_BLINK; 4470 lastblink = now; 4471 dodraw = 1; 4472 } 4473 deltatime = TIMEDIFF(now, last); 4474 if (deltatime > 1000 / (xev ? xfps : actionfps)) { 4475 dodraw = 1; 4476 last = now; 4477 } 4478 4479 if (dodraw) { 4480 while (XPending(xw.dpy)) { 4481 XNextEvent(xw.dpy, &ev); 4482 if (XFilterEvent(&ev, None)) 4483 continue; 4484 if (handler[ev.type]) 4485 (handler[ev.type])(&ev); 4486 } 4487 4488 draw(); 4489 XFlush(xw.dpy); 4490 4491 if (xev && !FD_ISSET(xfd, &rfd)) 4492 xev--; 4493 if (!FD_ISSET(cmdfd, &rfd) && !FD_ISSET(xfd, &rfd)) { 4494 if (blinkset) { 4495 if (TIMEDIFF(now, lastblink) \ 4496 > blinktimeout) { 4497 drawtimeout.tv_nsec = 1000; 4498 } else { 4499 drawtimeout.tv_nsec = (1E6 * \ 4500 (blinktimeout - \ 4501 TIMEDIFF(now, 4502 lastblink))); 4503 } 4504 drawtimeout.tv_sec = \ 4505 drawtimeout.tv_nsec / 1E9; 4506 drawtimeout.tv_nsec %= (long)1E9; 4507 } else { 4508 tv = NULL; 4509 } 4510 } 4511 } 4512 } 4513 } 4514 4515 void 4516 usage(void) 4517 { 4518 die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" 4519 " [-n name] [-o file]\n" 4520 " [-T title] [-t title] [-w windowid]" 4521 " [[-e] command [args ...]]\n" 4522 " %s [-aiv] [-c class] [-f font] [-g geometry]" 4523 " [-n name] [-o file]\n" 4524 " [-T title] [-t title] [-w windowid] -l line" 4525 " [stty_args ...]\n", argv0, argv0); 4526 } 4527 4528 int 4529 main(int argc, char *argv[]) 4530 { 4531 xw.l = xw.t = 0; 4532 xw.isfixed = False; 4533 xw.cursor = cursorshape; 4534 4535 ARGBEGIN { 4536 case 'a': 4537 allowaltscreen = 0; 4538 break; 4539 case 'c': 4540 opt_class = EARGF(usage()); 4541 break; 4542 case 'e': 4543 if (argc > 0) 4544 --argc, ++argv; 4545 goto run; 4546 case 'f': 4547 opt_font = EARGF(usage()); 4548 break; 4549 case 'g': 4550 xw.gm = XParseGeometry(EARGF(usage()), 4551 &xw.l, &xw.t, &cols, &rows); 4552 break; 4553 case 'i': 4554 xw.isfixed = 1; 4555 break; 4556 case 'o': 4557 opt_io = EARGF(usage()); 4558 break; 4559 case 'l': 4560 opt_line = EARGF(usage()); 4561 break; 4562 case 'n': 4563 opt_name = EARGF(usage()); 4564 break; 4565 case 't': 4566 case 'T': 4567 opt_title = EARGF(usage()); 4568 break; 4569 case 'w': 4570 opt_embed = EARGF(usage()); 4571 break; 4572 case 'v': 4573 die("%s " VERSION " (c) 2010-2016 st engineers\n", argv0); 4574 break; 4575 default: 4576 usage(); 4577 } ARGEND; 4578 4579 run: 4580 if (argc > 0) { 4581 /* eat all remaining arguments */ 4582 opt_cmd = argv; 4583 if (!opt_title && !opt_line) 4584 opt_title = basename(xstrdup(argv[0])); 4585 } 4586 setlocale(LC_CTYPE, ""); 4587 XSetLocaleModifiers(""); 4588 tnew(MAX(cols, 1), MAX(rows, 1)); 4589 xinit(); 4590 selinit(); 4591 run(); 4592 4593 return 0; 4594 } 4595