*/ 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 */ 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 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 line up */ 2408 DEFAULT(csiescseq.arg[0], 1); 2409 tscrollup(term.top, csiescseq.arg[0]); 2410 break; 2411 case 'T': /* SD -- Scroll line down */ 2412 DEFAULT(csiescseq.arg[0], 1); 2413 tscrolldown(term.top, csiescseq.arg[0]); 2414 break; 2415 case 'L': /* IL -- Insert 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 lines */ 2423 DEFAULT(csiescseq.arg[0], 1); 2424 tdeleteline(csiescseq.arg[0]); 2425 break; 2426 case 'X': /* ECH -- Erase 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 char */ 2432 DEFAULT(csiescseq.arg[0], 1); 2433 tdeletechar(csiescseq.arg[0]); 2434 break; 2435 case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ 2436 DEFAULT(csiescseq.arg[0], 1); 2437 tputtab(-csiescseq.arg[0]); 2438 break; 2439 case 'd': /* VPA -- Move to */ 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
st.c - st - Simple Terminal
git clone git://r-36.net/st
Log
Files
Refs
README
LICENSE
---
st.c (100946B)
---
     1 /* See LICENSE for license details. */
     2 #include 
     3 #include 
     4 #include 
     5 #include 
     6 #include 
     7 #include 
     8 #include 
     9 #include 
    10 #include 
    11 #include 
    12 #include 
    13 #include 
    14 #include 
    15 #include 
    16 #include 
    17 #include 
    18 #include 
    19 #include 
    20 #include 
    21 #include 
    22 #include 
    23 #include 
    24 #include 
    25 #include 
    26 #include 
    27 #include 
    28 #include 
    29 #include 
    30 #include 
    31 #include 
    32 #include 
    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 
    43 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
    44  #include 
    45 #elif defined(__FreeBSD__) || defined(__DragonFly__)
    46  #include 
    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: ]  [;]]  []] */
   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 [[ []  [;]] ] 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  blank char */
  2292                 DEFAULT(csiescseq.arg[0], 1);
  2293                 tinsertblank(csiescseq.arg[0]);
  2294                 break;
  2295         case 'A': /* CUU -- Cursor  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  Down */
  2300         case 'e': /* VPR --Cursor  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  Forward */
  2328         case 'a': /* HPR -- Cursor  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  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  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  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