├── .gitignore ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── arg.h ├── config.h ├── mt.cc ├── mt.h ├── x.cc ├── x.h └── xterm-emulation-terminfo.info /.gitignore: -------------------------------------------------------------------------------- 1 | mt 2 | CMakeCache.txt 3 | CMakeFiles/* 4 | cmake_install.cmake 5 | .ninja_* 6 | *.ninja 7 | 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.5) 2 | project (mt CXX) 3 | 4 | # FIXME: clean up signed/unsigned comparisons and enable the warning. 5 | add_compile_options(-std=c++11 -pedantic -Wall -Werror -Wno-sign-compare) 6 | add_definitions(-DVERSION=\"0.1\" -D_XOPEN_SOURCE=600) 7 | 8 | find_package(X11 REQUIRED) 9 | 10 | find_package(PkgConfig REQUIRED) 11 | pkg_check_modules(FC REQUIRED fontconfig) 12 | pkg_check_modules(FT REQUIRED freetype2) 13 | 14 | include_directories(${FC_INCLUDE_DIRS} ${FT_INCLUDE_DIRS}) 15 | link_directories(${FC_LBIRARY_DIRS} ${FT_LIBRARY_DIRS}) 16 | add_compile_options(${FC_CFLAGS} ${FT_CFLAGS}) 17 | 18 | add_executable(mt mt.cc arg.h config.h mt.h x.h x.cc) 19 | target_link_libraries(mt -lm -lrt -lutil 20 | ${X11_LIBRARIES} ${X11_Xft_LIB} 21 | ${FC_LIBRARIES} ${FT_LIBRARIES}) 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Submitting Patches 19 | 20 | Submissions should generally use GitHub pull requests. Consult [GitHub 21 | Help](https://help.github.com/articles/about-pull-requests/) for more 22 | information. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2009-2012 Aurélien APTEL 4 | © 2009 Anselm R Garbe 5 | © 2012-2016 Roberto E. Vargas Caballero 6 | © 2012-2016 Christoph Lohmann <20h at r-36 dot net> 7 | © 2013 Eon S. Jeon 8 | © 2013 Alexander Sedov 9 | © 2013 Mark Edgar 10 | © 2013 Eric Pruitt 11 | © 2013 Michael Forney 12 | © 2013-2014 Markus Teich 13 | © 2014-2015 Laslo Hunhold 14 | © 2017 Google Inc. 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a 17 | copy of this software and associated documentation files (the "Software"), 18 | to deal in the Software without restriction, including without limitation 19 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 20 | and/or sell copies of the Software, and to permit persons to whom the 21 | Software is furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in 24 | all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 29 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 32 | DEALINGS IN THE SOFTWARE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `mt` - Modern, minimalist, minty-fresh terminal. 2 | 3 | This is intended to be a modern, minimalist terminal emulator for X with the 4 | following design priorities: 5 | 6 | - Work well with other platform's modern terminal emulators (iTerm2, hterm). 7 | This means largely being xterm-compatible rather than using a custom terminfo 8 | database entry. 9 | - Zero non-terminal UI components (no menus, no scroll bars, etc.) and leverage 10 | in-terminal tools like tmux for these kinds of features. 11 | - High quality font rendering and color support. The terminal itself should 12 | look as good as it is possible to look in X. 13 | 14 | While satisfying these criteria, the terminal should be as small and 15 | low-overhead as possible. Also, it should use any modern software development 16 | tools that make it easier to work with. 17 | 18 | # Requirements to build 19 | 20 | You will need the Xlib header files, CMake, and a modern C++ toolchain. Using 21 | the Ninja generator of CMake is encouraged. 22 | 23 | # TODO list 24 | 25 | ## Cleanup 26 | 27 | - Use C++ to simplify any constructs from the old C code. 28 | - Identify any existing libraries that can be used effectively. 29 | - Factor code into libraries and write unittests. 30 | 31 | ## Terminal emulation 32 | 33 | - Compare all terminal codes with xterm, iTerm2, and hterm; harmonize where 34 | possible. 35 | - From the original `st` documentation 36 | - double-height support 37 | 38 | ## Display and rendering 39 | 40 | - From the original `st` documentation 41 | - add diacritics support to xdraws() 42 | - make the font cache simpler 43 | - add better support for brightening of the upper colors 44 | 45 | ## Application 46 | 47 | - Add configuration file reading rather than compiling all settings into the 48 | binary. 49 | 50 | ## Bugs and/or known isuses 51 | 52 | - From the original `st` documentation 53 | - fix shift up/down (shift selection in emacs) 54 | - remove DEC test sequence when appropriate 55 | 56 | # FAQ 57 | 58 | ## How do I get scrolling functionality? 59 | 60 | Using a terminal multiplexer like `tmux` or `screen`. 61 | 62 | # Disclaimer 63 | 64 | This is not an official Google product. 65 | 66 | # Credits 67 | 68 | This is based on the `st` terminal, but doesn't share the same goals or 69 | development philosophy and so is very likely to diverge. From that project's 70 | documentation: 71 | 72 | Based on Aurélien APTEL bt source code. 73 | -------------------------------------------------------------------------------- /arg.h: -------------------------------------------------------------------------------- 1 | #ifndef MT_ARG_H 2 | #define MT_ARG_H 3 | 4 | extern char *argv0; 5 | 6 | /* use main(int argc, char *argv[]) */ 7 | #define ARGBEGIN \ 8 | for (argv0 = *argv, argv++, argc--; \ 9 | argv[0] && argv[0][0] == '-' && argv[0][1]; argc--, argv++) { \ 10 | char argc_; \ 11 | char **argv_; \ 12 | int brk_; \ 13 | if (argv[0][1] == '-' && argv[0][2] == '\0') { \ 14 | argv++; \ 15 | argc--; \ 16 | break; \ 17 | } \ 18 | for (brk_ = 0, argv[0]++, argv_ = argv; argv[0][0] && !brk_; argv[0]++) { \ 19 | if (argv_ != argv) \ 20 | break; \ 21 | argc_ = argv[0][0]; \ 22 | switch (argc_) 23 | #define ARGEND \ 24 | } \ 25 | } 26 | 27 | #define ARGC() argc_ 28 | 29 | #define EARGF(x) \ 30 | ((argv[0][1] == '\0' && argv[1] == NULL) \ 31 | ? ((x), abort(), (char *)0) \ 32 | : (brk_ = 1, \ 33 | (argv[0][1] != '\0') ? (&argv[0][1]) : (argc--, argv++, argv[0]))) 34 | 35 | #define ARGF() \ 36 | ((argv[0][1] == '\0' && argv[1] == NULL) \ 37 | ? (char *)0 \ 38 | : (brk_ = 1, \ 39 | (argv[0][1] != '\0') ? (&argv[0][1]) : (argc--, argv++, argv[0]))) 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | // Font specification in fontconfig format. 2 | // See http://freedesktop.org/software/fontconfig/fontconfig-user.html 3 | char font[] = "RobotoMono Nerd Font:weight=Light:size=11:antialias=true:autohint=true"; 4 | 5 | // Margin between window edges and console area. 6 | int borderpx = 2; 7 | 8 | // Shell program to run when mt starts. 9 | // The following sources are consulted in order: 10 | // 1. program passed with -e 11 | // 5. the `shell` option 12 | // 3. POSIX $SHELL environment variable 13 | // 4. user's shell in /etc/passwd 14 | // 5. /bin/sh 15 | static char *shell = NULL; 16 | 17 | // Kerning / character bounding-box multipliers. 18 | float cwscale = 1.0; 19 | float chscale = 1.0; 20 | 21 | // Characters that separate words. 22 | // This affects text selection. 23 | static char worddelimiters[] = " "; 24 | 25 | // Timeouts for selection gestures, in milliseconds. 26 | unsigned int doubleclicktimeout = 300; 27 | unsigned int tripleclicktimeout = 600; 28 | 29 | // Enable the "alternate screen" feature? 30 | // This allows fullscreen editors etc to restore the screen contents on exit. 31 | int allowaltscreen = 1; 32 | 33 | // Maximum redraw rate for events triggered by the UI (keystrokes, mouse). 34 | unsigned int xfps = 120; 35 | // Maximum redraw rate for events triggered by the terminal (program output). 36 | unsigned int actionfps = 30; 37 | 38 | // Blink period in ms, for text with the blinking attribute. 39 | // 0 disables blinking. 40 | unsigned int blinktimeout = 800; 41 | 42 | // Width of the underline and vertical bar cursors, in pixels. 43 | unsigned int cursorthickness = 2; 44 | 45 | // Ring the bell for the ^G character. 46 | // XkbBell() is used, the volume can be controlled with xset. 47 | static int bell = 0; 48 | 49 | // Value of the $TERM variable. 50 | char termname[] = "xterm-256color"; 51 | 52 | // Available terminal colors (X color names, or #123abc hex codes). 53 | // Basic colors (required): 54 | // 0-7: basic colors - black, red, green, yellow, blue, magenta, cyan, grey 55 | // 8-15: bright versions of the above colors 56 | // RGB colors (default to conventional values): 57 | // 16-231: RGB colors (6 levels of each) 58 | // 232-255: additional shades of grey 59 | // Additional colors (optional): 60 | // 256+: colors not accessible to applications (cursor colors etc) 61 | const char *colorname[] = { 62 | "black", "red3", "green3", "yellow3", 63 | "blue2", "magenta3", "cyan3", "gray90", 64 | 65 | "gray50", "red", "green", "yellow", 66 | "#5c5cff", "magenta", "cyan", "white", 67 | 68 | // FIXME: this is silly. 69 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 70 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 71 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 72 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 73 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 74 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 75 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 76 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 77 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 78 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 79 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 80 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 81 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 82 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 83 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 84 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 85 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 86 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 87 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 88 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 89 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 90 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 91 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 92 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 93 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 94 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 95 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 96 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 97 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 98 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 99 | 100 | "#cccccc", "#555555", 101 | }; 102 | 103 | // Default foreground and background colors. 104 | unsigned int defaultfg = 7; 105 | unsigned int defaultbg = 0; 106 | 107 | // Cursor color. 108 | unsigned int defaultcs = 256; 109 | // Cursor color in reverse mode and when selecting. 110 | unsigned int defaultrcs = 257; 111 | 112 | // Default shape of cursor. 113 | // 0-2: Block ("█") 114 | // 3-4: Underline ("_") 115 | // 5-6: Bar ("|") 116 | // 7: Snowman ("☃") 117 | // 0, 1, 3, 5 are desinated blinking variants, but blinking is not implemented. 118 | unsigned int cursorshape = 2; 119 | 120 | // Default terminal window size. 121 | unsigned int cols = 80; 122 | unsigned int rows = 24; 123 | 124 | // Default colour and shape of the mouse cursor. 125 | unsigned int mouseshape = XC_xterm; 126 | unsigned int mousefg = 7; 127 | unsigned int mousebg = 0; 128 | 129 | // Color indicating the active style (bold, italic) isn't supported by the font. 130 | unsigned int defaultattr = 11; 131 | 132 | // Mouse shortcuts. 133 | // The default values map the scroll wheel (Button4/5) to ^Y/^E (vim commands). 134 | // Note that mapping Button1 will interfere with selection. 135 | MouseShortcut mshortcuts[] = { 136 | /* button mask string */ 137 | { Button4, XK_ANY_MOD, "\031" }, 138 | { Button5, XK_ANY_MOD, "\005" }, 139 | }; 140 | 141 | // Keyboard shortcuts that trigger internal functions. 142 | Shortcut shortcuts[] = { 143 | // mask keysym function argument 144 | { XK_ANY_MOD, XK_Break, sendbreak, 0 }, 145 | { ControlMask, XK_Print, toggleprinter, 0 }, 146 | { ShiftMask, XK_Print, printscreen, 0 }, 147 | { XK_ANY_MOD, XK_Print, printsel, 0 }, 148 | { (ControlMask | ShiftMask), XK_Prior, zoom, +1.f }, 149 | { (ControlMask | ShiftMask), XK_Next, zoom, -1.f }, 150 | { (ControlMask | ShiftMask), XK_Home, zoomreset, 0.f }, 151 | { (ControlMask | ShiftMask), XK_C, clipcopy, 0 }, 152 | { (ControlMask | ShiftMask), XK_V, clippaste, 0 }, 153 | { (ControlMask | ShiftMask), XK_Y, selpaste, 0 }, 154 | { (ControlMask | ShiftMask), XK_Num_Lock, numlock, 0 }, 155 | { (ControlMask | ShiftMask), XK_I, iso14755, 0 }, 156 | }; 157 | 158 | // Keys outside the X11 function key range (0xFD00 - 0xFFFF) with shortcuts. 159 | // By default, shortcuts in `key` are only scanned for KeySyms in this range. 160 | static KeySym mappedkeys[] = {(KeySym)-1}; 161 | 162 | // Modifiers to ignore when matching keyboard and mouse events. 163 | // By default, numlock and keyboard layout are ignored. 164 | static uint ignoremod = Mod2Mask | XK_SWITCH_MOD; 165 | 166 | // Keyboard modifier to force mouse selection when in mouse mode. 167 | // This lets you select text even if the application has captured the mouse. 168 | uint forceselmod = ShiftMask; 169 | 170 | // Keyboard shortcuts that send bytes to the terminal. 171 | // The first entry whose critera match the key event is chosen. 172 | // 173 | // Some criteria match against the current mode: 174 | // `appkey` is linked to whether the keypad is in "application mode" 175 | // `appcursor` is linked to whether cursor keys are in "application mode" 176 | // `crlf` is linked to whether the terminal is in "automatic newline mode" 177 | // These have three values 178 | // -1: only match if mode is NOT active 179 | // 0 : match independent of mode 180 | // 1 : only match if mode is active 181 | // appkey has one extra value: 182 | // 2 : only match if mode is active AND numlock was toggled off. 183 | static Key key[] = { 184 | /* keysym mask string appkey appcursor crlf */ 185 | { XK_KP_Home, ShiftMask, "\033[1;2H", 0, 0, 0}, 186 | { XK_KP_Home, XK_ANY_MOD, "\033OH", 0, 0, 0}, 187 | { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0, 0}, 188 | { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1, 0}, 189 | { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1, 0}, 190 | { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0, 0}, 191 | { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1, 0}, 192 | { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1, 0}, 193 | { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0, 0}, 194 | { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1, 0}, 195 | { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1, 0}, 196 | { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0, 0}, 197 | { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1, 0}, 198 | { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1, 0}, 199 | { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0, 0}, 200 | { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0, 0}, 201 | { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0, 0}, 202 | { XK_KP_End, ControlMask, "\033[1;5F", 0, 0, 0}, 203 | { XK_KP_End, ShiftMask, "\033[1;2F", 0, 0, 0}, 204 | { XK_KP_End, XK_ANY_MOD, "\033OF", 0, 0, 0}, 205 | { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0, 0}, 206 | { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0, 0}, 207 | { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0, 0}, 208 | { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0, 0}, 209 | { XK_KP_Insert, ControlMask, "\033[L", -1, 0, 0}, 210 | { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0, 0}, 211 | { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0, 0}, 212 | { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0, 0}, 213 | { XK_KP_Delete, ControlMask, "\033[3;5~", 0, 0, 0}, 214 | { XK_KP_Delete, ShiftMask, "\033[3;2~", 0, 0, 0}, 215 | { XK_KP_Delete, XK_ANY_MOD, "\033[3~", 0, 0, 0}, 216 | { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0, 0}, 217 | { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0, 0}, 218 | { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0, 0}, 219 | { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0, -1}, 220 | { XK_KP_Enter, XK_ANY_MOD, "\r\n", -1, 0, +1}, 221 | { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0, 0}, 222 | { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0, 0}, 223 | { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0, 0}, 224 | { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0, 0}, 225 | { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0, 0}, 226 | { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0, 0}, 227 | { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0, 0}, 228 | { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0, 0}, 229 | { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0, 0}, 230 | { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0, 0}, 231 | { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0, 0}, 232 | { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0, 0}, 233 | { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0, 0}, 234 | { XK_Up, ShiftMask, "\033[1;2A", 0, 0, 0}, 235 | { XK_Up, Mod1Mask, "\033[1;3A", 0, 0, 0}, 236 | { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0, 0}, 237 | { XK_Up, ControlMask, "\033[1;5A", 0, 0, 0}, 238 | { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0, 0}, 239 | { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0, 0}, 240 | { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0, 0}, 241 | { XK_Up, XK_ANY_MOD, "\033[A", 0, -1, 0}, 242 | { XK_Up, XK_ANY_MOD, "\033OA", 0, +1, 0}, 243 | { XK_Down, ShiftMask, "\033[1;2B", 0, 0, 0}, 244 | { XK_Down, Mod1Mask, "\033[1;3B", 0, 0, 0}, 245 | { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0, 0}, 246 | { XK_Down, ControlMask, "\033[1;5B", 0, 0, 0}, 247 | { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0, 0}, 248 | { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0, 0}, 249 | { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0, 0}, 250 | { XK_Down, XK_ANY_MOD, "\033[B", 0, -1, 0}, 251 | { XK_Down, XK_ANY_MOD, "\033OB", 0, +1, 0}, 252 | { XK_Left, ShiftMask, "\033[1;2D", 0, 0, 0}, 253 | { XK_Left, Mod1Mask, "\033[1;3D", 0, 0, 0}, 254 | { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0, 0}, 255 | { XK_Left, ControlMask, "\033[1;5D", 0, 0, 0}, 256 | { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0, 0}, 257 | { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0, 0}, 258 | { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0, 0}, 259 | { XK_Left, XK_ANY_MOD, "\033[D", 0, -1, 0}, 260 | { XK_Left, XK_ANY_MOD, "\033OD", 0, +1, 0}, 261 | { XK_Right, ShiftMask, "\033[1;2C", 0, 0, 0}, 262 | { XK_Right, Mod1Mask, "\033[1;3C", 0, 0, 0}, 263 | { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0, 0}, 264 | { XK_Right, ControlMask, "\033[1;5C", 0, 0, 0}, 265 | { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0, 0}, 266 | { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0, 0}, 267 | { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0, 0}, 268 | { XK_Right, XK_ANY_MOD, "\033[C", 0, -1, 0}, 269 | { XK_Right, XK_ANY_MOD, "\033OC", 0, +1, 0}, 270 | { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0, 0}, 271 | { XK_Return, Mod1Mask, "\033\r", 0, 0, -1}, 272 | { XK_Return, Mod1Mask, "\033\r\n", 0, 0, +1}, 273 | { XK_Return, XK_ANY_MOD, "\r", 0, 0, -1}, 274 | { XK_Return, XK_ANY_MOD, "\r\n", 0, 0, +1}, 275 | { XK_Insert, ShiftMask, "\033[4l", -1, 0, 0}, 276 | { XK_Insert, ShiftMask, "\033[2;2~", +1, 0, 0}, 277 | { XK_Insert, ControlMask, "\033[L", -1, 0, 0}, 278 | { XK_Insert, ControlMask, "\033[2;5~", +1, 0, 0}, 279 | { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0, 0}, 280 | { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0, 0}, 281 | { XK_Delete, ControlMask, "\033[3;5~", 0, 0, 0}, 282 | { XK_Delete, ShiftMask, "\033[3;2~", 0, 0, 0}, 283 | { XK_Delete, XK_ANY_MOD, "\033[3~", 0, 0, 0}, 284 | { XK_BackSpace, XK_ANY_MOD, "\177", 0, 0, 0}, 285 | { XK_Home, ShiftMask, "\033[1;2H", 0, 0, 0}, 286 | { XK_Home, XK_ANY_MOD, "\033OH", 0, 0, 0}, 287 | { XK_End, ControlMask, "\033[1;5F", 0, 0, 0}, 288 | { XK_End, ShiftMask, "\033[1;2F", 0, 0, 0}, 289 | { XK_End, XK_ANY_MOD, "\033OF", 0, 0, 0}, 290 | { XK_Prior, ControlMask, "\033[5;5~", 0, 0, 0}, 291 | { XK_Prior, ShiftMask, "\033[5;2~", 0, 0, 0}, 292 | { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0, 0}, 293 | { XK_Next, ControlMask, "\033[6;5~", 0, 0, 0}, 294 | { XK_Next, ShiftMask, "\033[6;2~", 0, 0, 0}, 295 | { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0, 0}, 296 | { XK_F1, XK_NO_MOD, "\033OP" , 0, 0, 0}, 297 | { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0, 0}, 298 | { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0, 0}, 299 | { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0, 0}, 300 | { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0, 0}, 301 | { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0, 0}, 302 | { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0, 0}, 303 | { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0, 0}, 304 | { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0, 0}, 305 | { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0, 0}, 306 | { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0, 0}, 307 | { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0, 0}, 308 | { XK_F3, XK_NO_MOD, "\033OR" , 0, 0, 0}, 309 | { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0, 0}, 310 | { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0, 0}, 311 | { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0, 0}, 312 | { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0, 0}, 313 | { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0, 0}, 314 | { XK_F4, XK_NO_MOD, "\033OS" , 0, 0, 0}, 315 | { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0, 0}, 316 | { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0, 0}, 317 | { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0, 0}, 318 | { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0, 0}, 319 | { XK_F5, XK_NO_MOD, "\033[15~", 0, 0, 0}, 320 | { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0, 0}, 321 | { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0, 0}, 322 | { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0, 0}, 323 | { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0, 0}, 324 | { XK_F6, XK_NO_MOD, "\033[17~", 0, 0, 0}, 325 | { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0, 0}, 326 | { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0, 0}, 327 | { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0, 0}, 328 | { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0, 0}, 329 | { XK_F7, XK_NO_MOD, "\033[18~", 0, 0, 0}, 330 | { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0, 0}, 331 | { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0, 0}, 332 | { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0, 0}, 333 | { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0, 0}, 334 | { XK_F8, XK_NO_MOD, "\033[19~", 0, 0, 0}, 335 | { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0, 0}, 336 | { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0, 0}, 337 | { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0, 0}, 338 | { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0, 0}, 339 | { XK_F9, XK_NO_MOD, "\033[20~", 0, 0, 0}, 340 | { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0, 0}, 341 | { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0, 0}, 342 | { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0, 0}, 343 | { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0, 0}, 344 | { XK_F10, XK_NO_MOD, "\033[21~", 0, 0, 0}, 345 | { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0, 0}, 346 | { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0, 0}, 347 | { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0, 0}, 348 | { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0, 0}, 349 | { XK_F11, XK_NO_MOD, "\033[23~", 0, 0, 0}, 350 | { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0, 0}, 351 | { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0, 0}, 352 | { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0, 0}, 353 | { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0, 0}, 354 | { XK_F12, XK_NO_MOD, "\033[24~", 0, 0, 0}, 355 | { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0, 0}, 356 | { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0, 0}, 357 | { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0, 0}, 358 | { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0, 0}, 359 | { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0, 0}, 360 | { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0, 0}, 361 | { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0, 0}, 362 | { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0, 0}, 363 | { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0, 0}, 364 | { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0, 0}, 365 | { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0, 0}, 366 | { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0, 0}, 367 | { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0, 0}, 368 | { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0, 0}, 369 | { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0, 0}, 370 | { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0, 0}, 371 | { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0, 0}, 372 | { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0, 0}, 373 | { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0, 0}, 374 | { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0, 0}, 375 | { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0, 0}, 376 | { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0, 0}, 377 | { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0, 0}, 378 | { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0, 0}, 379 | { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0, 0}, 380 | { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0, 0}, 381 | { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0, 0}, 382 | }; 383 | 384 | // Keyboard modifiers that alter the behavior of mouse selection. 385 | // If no match is found, regular selection is used. 386 | uint selmasks[] = { 387 | /* none= */ 0, 388 | /* SEL_REGULAR= */ 0, 389 | /* SEL_RECTANGULAR= */ Mod1Mask, 390 | }; 391 | -------------------------------------------------------------------------------- /mt.cc: -------------------------------------------------------------------------------- 1 | #include "mt.h" 2 | 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 | 16 | extern "C" { 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 | 32 | #if defined(__linux) 33 | #include 34 | #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) 35 | #include 36 | #elif defined(__FreeBSD__) || defined(__DragonFly__) 37 | #include 38 | #endif 39 | } 40 | 41 | #include "x.h" 42 | 43 | char *argv0; 44 | 45 | /* Arbitrary sizes */ 46 | #define UTF_INVALID 0xFFFD 47 | #define ESC_BUF_SIZ (128 * UTF_SIZ) 48 | #define ESC_ARG_SIZ 16 49 | #define STR_BUF_SIZ ESC_BUF_SIZ 50 | #define STR_ARG_SIZ ESC_ARG_SIZ 51 | 52 | /* macros */ 53 | #define NUMMAXLEN(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1) 54 | #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 55 | #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') 56 | #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 57 | #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 58 | #define ISDELIM(u) (utf8strchr(worddelimiters, u) != NULL) 59 | 60 | /* constants */ 61 | #define ISO14755CMD "dmenu -w %lu -p codepoint: ] [;]] []] */ 94 | typedef struct { 95 | char buf[ESC_BUF_SIZ]; /* raw string */ 96 | int len; /* raw string length */ 97 | char priv; 98 | int arg[ESC_ARG_SIZ]; 99 | int narg; /* nb of args */ 100 | char mode[2]; 101 | } CSIEscape; 102 | 103 | /* STR Escape sequence structs */ 104 | /* ESC type [[ [] [;]] ] ESC '\' */ 105 | typedef struct { 106 | char type; /* ESC type ... */ 107 | char buf[STR_BUF_SIZ]; /* raw string */ 108 | int len; /* raw string length */ 109 | char *args[STR_ARG_SIZ]; 110 | int narg; /* nb of args */ 111 | } STREscape; 112 | 113 | typedef struct { 114 | KeySym k; 115 | uint mask; 116 | const char *s; 117 | /* three valued logic variables: 0 indifferent, 1 on, -1 off */ 118 | signed char appkey; /* application keypad */ 119 | signed char appcursor; /* application cursor */ 120 | signed char crlf; /* crlf mode */ 121 | } Key; 122 | 123 | /* function definitions used in config.h */ 124 | static void clipcopy(const Arg *); 125 | static void clippaste(const Arg *); 126 | static void numlock(const Arg *); 127 | static void selpaste(const Arg *); 128 | static void zoom(const Arg *); 129 | static void zoomabs(const Arg *); 130 | static void zoomreset(const Arg *); 131 | static void printsel(const Arg *); 132 | static void printscreen(const Arg *); 133 | static void iso14755(const Arg *); 134 | static void toggleprinter(const Arg *); 135 | static void sendbreak(const Arg *); 136 | 137 | /* config.h for applying patches and the configuration. */ 138 | #include "config.h" 139 | 140 | static void execsh(void); 141 | static void sigchld(int); 142 | 143 | static void csidump(void); 144 | static void csihandle(void); 145 | static void csiparse(void); 146 | static void csireset(void); 147 | static int eschandle(uchar); 148 | static void strdump(void); 149 | static void strhandle(void); 150 | static void strparse(void); 151 | static void strreset(void); 152 | 153 | static void tprinter(const char *, size_t); 154 | static void tdumpsel(void); 155 | static void tdumpline(int); 156 | static void tdump(void); 157 | static void tclearregion(int, int, int, int); 158 | static void tcursor(int); 159 | static void tdeletechar(int); 160 | static void tdeleteline(int); 161 | static void tinsertblank(int); 162 | static void tinsertblankline(int); 163 | static int tlinelen(int); 164 | static void tmoveto(int, int); 165 | static void tmoveato(int, int); 166 | static void tnewline(int); 167 | static void tputtab(int); 168 | static void tputc(Rune); 169 | static void treset(void); 170 | static void tresize(int, int); 171 | static void tscrollup(int, int); 172 | static void tscrolldown(int, int); 173 | static void tsetattr(int *, int); 174 | static void tsetchar(Rune, MTGlyph *, int, int); 175 | static void tsetscroll(int, int); 176 | static void tswapscreen(void); 177 | static void tsetmode(int, int, int *, int); 178 | static void tfulldirt(void); 179 | static void techo(Rune); 180 | static void tcontrolcode(uchar); 181 | static void tdectest(char); 182 | static void tdefutf8(char); 183 | static int32_t tdefcolor(int *, int *, int); 184 | static void tdeftran(char); 185 | static void tstrsequence(uchar); 186 | 187 | static void selscroll(int, int); 188 | static void selsnap(int *, int *, int); 189 | 190 | static Rune utf8decodebyte(char, size_t *); 191 | static char utf8encodebyte(Rune, size_t); 192 | static char *utf8strchr(char *s, Rune u); 193 | static size_t utf8validate(Rune *, size_t); 194 | 195 | static char *base64dec(const char *); 196 | 197 | static ssize_t xwrite(int, const char *, size_t); 198 | 199 | /* Globals */ 200 | TermWindow win; 201 | Term term; 202 | Selection sel; 203 | int cmdfd; 204 | pid_t pid; 205 | char **opt_cmd = NULL; 206 | char *opt_class = NULL; 207 | char *opt_embed = NULL; 208 | char *opt_font = NULL; 209 | char *opt_io = NULL; 210 | char *opt_name = NULL; 211 | char *opt_title = NULL; 212 | int oldbutton = 3; /* button event on startup: 3 = release */ 213 | 214 | static CSIEscape csiescseq; 215 | static STREscape strescseq; 216 | static int iofd = 1; 217 | 218 | char *usedfont = NULL; 219 | double usedfontsize = 0; 220 | double defaultfontsize = 0; 221 | 222 | static uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 223 | static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 224 | static Rune utfmin[UTF_SIZ + 1] = {0, 0, 0x80, 0x800, 0x10000}; 225 | static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 226 | 227 | /* config.h array lengths */ 228 | size_t colornamelen = LEN(colorname); 229 | size_t mshortcutslen = LEN(mshortcuts); 230 | size_t shortcutslen = LEN(shortcuts); 231 | size_t selmaskslen = LEN(selmasks); 232 | 233 | // Identification sequence returned in DA and DECID. 234 | // We claim to be a VT102, feature detection is via terminfo in practice. 235 | static char vt102_identify[] = "\033[?6c"; 236 | 237 | ssize_t xwrite(int fd, const char *s, size_t len) { 238 | size_t aux = len; 239 | ssize_t r; 240 | 241 | while (len > 0) { 242 | r = write(fd, s, len); 243 | if (r < 0) 244 | return r; 245 | len -= r; 246 | s += r; 247 | } 248 | 249 | return aux; 250 | } 251 | 252 | template T *xmalloc(size_t len) { 253 | void *p = malloc(len * sizeof(T)); 254 | 255 | if (!p) 256 | die("Out of memory\n"); 257 | 258 | return static_cast(p); 259 | } 260 | 261 | template T *xrealloc(void *p, size_t len) { 262 | if ((p = realloc(p, len * sizeof(T))) == NULL) 263 | die("Out of memory\n"); 264 | 265 | return static_cast(p); 266 | } 267 | 268 | char *xstrdup(char *s) { 269 | if ((s = strdup(s)) == NULL) 270 | die("Out of memory\n"); 271 | 272 | return s; 273 | } 274 | 275 | size_t utf8decode(const char *c, Rune *u, size_t clen) { 276 | size_t i, j, len, type; 277 | Rune udecoded; 278 | 279 | *u = UTF_INVALID; 280 | if (!clen) 281 | return 0; 282 | udecoded = utf8decodebyte(c[0], &len); 283 | if (!BETWEEN(len, 1, UTF_SIZ)) 284 | return 1; 285 | for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 286 | udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 287 | if (type != 0) 288 | return j; 289 | } 290 | if (j < len) 291 | return 0; 292 | *u = udecoded; 293 | utf8validate(u, len); 294 | 295 | return len; 296 | } 297 | 298 | Rune utf8decodebyte(char c, size_t *i) { 299 | for (*i = 0; *i < LEN(utfmask); ++(*i)) 300 | if (((uchar)c & utfmask[*i]) == utfbyte[*i]) 301 | return (uchar)c & ~utfmask[*i]; 302 | 303 | return 0; 304 | } 305 | 306 | size_t utf8encode(Rune u, char *c) { 307 | size_t len, i; 308 | 309 | len = utf8validate(&u, 0); 310 | if (len > UTF_SIZ) 311 | return 0; 312 | 313 | for (i = len - 1; i != 0; --i) { 314 | c[i] = utf8encodebyte(u, 0); 315 | u >>= 6; 316 | } 317 | c[0] = utf8encodebyte(u, len); 318 | 319 | return len; 320 | } 321 | 322 | char utf8encodebyte(Rune u, size_t i) { return utfbyte[i] | (u & ~utfmask[i]); } 323 | 324 | char *utf8strchr(char *s, Rune u) { 325 | Rune r; 326 | size_t i, j, len; 327 | 328 | len = strlen(s); 329 | for (i = 0, j = 0; i < len; i += j) { 330 | if (!(j = utf8decode(&s[i], &r, len - i))) 331 | break; 332 | if (r == u) 333 | return &(s[i]); 334 | } 335 | 336 | return NULL; 337 | } 338 | 339 | size_t utf8validate(Rune *u, size_t i) { 340 | if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 341 | *u = UTF_INVALID; 342 | for (i = 1; *u > utfmax[i]; ++i) 343 | ; 344 | 345 | return i; 346 | } 347 | 348 | static const char base64_digits[] = { 349 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 350 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 351 | 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 352 | 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 353 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 354 | 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 355 | 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 356 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 357 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 358 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 359 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 360 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 361 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 362 | 0, 0, 0, 0, 0, 0, 0, 0}; 363 | 364 | char *base64dec(const char *src) { 365 | size_t in_len = strlen(src); 366 | char *result, *dst; 367 | 368 | if (in_len % 4) 369 | return NULL; 370 | result = dst = xmalloc(in_len / 4 * 3 + 1); 371 | while (*src) { 372 | int a = base64_digits[(unsigned char)*src++]; 373 | int b = base64_digits[(unsigned char)*src++]; 374 | int c = base64_digits[(unsigned char)*src++]; 375 | int d = base64_digits[(unsigned char)*src++]; 376 | 377 | *dst++ = (a << 2) | ((b & 0x30) >> 4); 378 | if (c == -1) 379 | break; 380 | *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); 381 | if (d == -1) 382 | break; 383 | *dst++ = ((c & 0x03) << 6) | d; 384 | } 385 | *dst = '\0'; 386 | return result; 387 | } 388 | 389 | void selinit(void) { 390 | clock_gettime(CLOCK_MONOTONIC, &sel.tclick1); 391 | clock_gettime(CLOCK_MONOTONIC, &sel.tclick2); 392 | sel.mode = SEL_IDLE; 393 | sel.snap = 0; 394 | sel.ob.x = -1; 395 | sel.primary = NULL; 396 | sel.clipboard = NULL; 397 | } 398 | 399 | int x2col(int x) { 400 | x -= borderpx; 401 | x /= win.cw; 402 | 403 | return LIMIT(x, 0, term.col - 1); 404 | } 405 | 406 | int y2row(int y) { 407 | y -= borderpx; 408 | y /= win.ch; 409 | 410 | return LIMIT(y, 0, term.row - 1); 411 | } 412 | 413 | int tlinelen(int y) { 414 | int i = term.col; 415 | 416 | if (term.line[y][i - 1].mode & ATTR_WRAP) 417 | return i; 418 | 419 | while (i > 0 && term.line[y][i - 1].u == ' ') 420 | --i; 421 | 422 | return i; 423 | } 424 | 425 | void selnormalize(void) { 426 | int i; 427 | 428 | if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 429 | sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 430 | sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 431 | } else { 432 | sel.nb.x = MIN(sel.ob.x, sel.oe.x); 433 | sel.ne.x = MAX(sel.ob.x, sel.oe.x); 434 | } 435 | sel.nb.y = MIN(sel.ob.y, sel.oe.y); 436 | sel.ne.y = MAX(sel.ob.y, sel.oe.y); 437 | 438 | selsnap(&sel.nb.x, &sel.nb.y, -1); 439 | selsnap(&sel.ne.x, &sel.ne.y, +1); 440 | 441 | /* expand selection over line breaks */ 442 | if (sel.type == SEL_RECTANGULAR) 443 | return; 444 | i = tlinelen(sel.nb.y); 445 | if (i < sel.nb.x) 446 | sel.nb.x = i; 447 | if (tlinelen(sel.ne.y) <= sel.ne.x) 448 | sel.ne.x = term.col - 1; 449 | } 450 | 451 | int selected(int x, int y) { 452 | if (sel.mode == SEL_EMPTY) 453 | return 0; 454 | 455 | if (sel.type == SEL_RECTANGULAR) 456 | return BETWEEN(y, sel.nb.y, sel.ne.y) && BETWEEN(x, sel.nb.x, sel.ne.x); 457 | 458 | return BETWEEN(y, sel.nb.y, sel.ne.y) && (y != sel.nb.y || x >= sel.nb.x) && 459 | (y != sel.ne.y || x <= sel.ne.x); 460 | } 461 | 462 | void selsnap(int *x, int *y, int direction) { 463 | int newx, newy, xt, yt; 464 | int delim, prevdelim; 465 | MTGlyph *gp, *prevgp; 466 | 467 | switch (sel.snap) { 468 | case SNAP_WORD: 469 | /* 470 | * Snap around if the word wraps around at the end or 471 | * beginning of a line. 472 | */ 473 | prevgp = &term.line[*y][*x]; 474 | prevdelim = ISDELIM(prevgp->u); 475 | for (;;) { 476 | newx = *x + direction; 477 | newy = *y; 478 | if (!BETWEEN(newx, 0, term.col - 1)) { 479 | newy += direction; 480 | newx = (newx + term.col) % term.col; 481 | if (!BETWEEN(newy, 0, term.row - 1)) 482 | break; 483 | 484 | if (direction > 0) 485 | yt = *y, xt = *x; 486 | else 487 | yt = newy, xt = newx; 488 | if (!(term.line[yt][xt].mode & ATTR_WRAP)) 489 | break; 490 | } 491 | 492 | if (newx >= tlinelen(newy)) 493 | break; 494 | 495 | gp = &term.line[newy][newx]; 496 | delim = ISDELIM(gp->u); 497 | if (!(gp->mode & ATTR_WDUMMY) && 498 | (delim != prevdelim || (delim && gp->u != prevgp->u))) 499 | break; 500 | 501 | *x = newx; 502 | *y = newy; 503 | prevgp = gp; 504 | prevdelim = delim; 505 | } 506 | break; 507 | case SNAP_LINE: 508 | /* 509 | * Snap around if the the previous line or the current one 510 | * has set ATTR_WRAP at its end. Then the whole next or 511 | * previous line will be selected. 512 | */ 513 | *x = (direction < 0) ? 0 : term.col - 1; 514 | if (direction < 0) { 515 | for (; *y > 0; *y += direction) { 516 | if (!(term.line[*y - 1][term.col - 1].mode & ATTR_WRAP)) { 517 | break; 518 | } 519 | } 520 | } else if (direction > 0) { 521 | for (; *y < term.row - 1; *y += direction) { 522 | if (!(term.line[*y][term.col - 1].mode & ATTR_WRAP)) { 523 | break; 524 | } 525 | } 526 | } 527 | break; 528 | } 529 | } 530 | 531 | char *getsel(void) { 532 | char *str, *ptr; 533 | int y, bufsize, lastx, linelen; 534 | MTGlyph *gp, *last; 535 | 536 | if (sel.ob.x == -1) 537 | return NULL; 538 | 539 | bufsize = (term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ; 540 | ptr = str = xmalloc(bufsize); 541 | 542 | /* append every set & selected glyph to the selection */ 543 | for (y = sel.nb.y; y <= sel.ne.y; y++) { 544 | if ((linelen = tlinelen(y)) == 0) { 545 | *ptr++ = '\n'; 546 | continue; 547 | } 548 | 549 | if (sel.type == SEL_RECTANGULAR) { 550 | gp = &term.line[y][sel.nb.x]; 551 | lastx = sel.ne.x; 552 | } else { 553 | gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; 554 | lastx = (sel.ne.y == y) ? sel.ne.x : term.col - 1; 555 | } 556 | last = &term.line[y][MIN(lastx, linelen - 1)]; 557 | while (last >= gp && last->u == ' ') 558 | --last; 559 | 560 | for (; gp <= last; ++gp) { 561 | if (gp->mode & ATTR_WDUMMY) 562 | continue; 563 | 564 | ptr += utf8encode(gp->u, ptr); 565 | } 566 | 567 | /* 568 | * Copy and pasting of line endings is inconsistent 569 | * in the inconsistent terminal and GUI world. 570 | * The best solution seems like to produce '\n' when 571 | * something is copied from st and convert '\n' to 572 | * '\r', when something to be pasted is received by 573 | * st. 574 | * FIXME: Fix the computer world. 575 | */ 576 | if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP)) 577 | *ptr++ = '\n'; 578 | } 579 | *ptr = 0; 580 | return str; 581 | } 582 | 583 | void selpaste(const Arg *dummy) { xselpaste(); } 584 | 585 | void clipcopy(const Arg *dummy) { xclipcopy(); } 586 | 587 | void clippaste(const Arg *dummy) { xclippaste(); } 588 | 589 | void selclear(void) { 590 | if (sel.ob.x == -1) 591 | return; 592 | sel.mode = SEL_IDLE; 593 | sel.ob.x = -1; 594 | tsetdirt(sel.nb.y, sel.ne.y); 595 | } 596 | 597 | void die(const char *errstr, ...) { 598 | va_list ap; 599 | 600 | va_start(ap, errstr); 601 | vfprintf(stderr, errstr, ap); 602 | va_end(ap); 603 | exit(1); 604 | } 605 | 606 | void execsh(void) { 607 | // Read user info from passwd file. 608 | const struct passwd *pw; 609 | errno = 0; 610 | if ((pw = getpwuid(getuid())) == NULL) { 611 | if (errno) 612 | die("getpwuid:%s\n", strerror(errno)); 613 | else 614 | die("who are you?\n"); 615 | } 616 | 617 | // Find user's preferred shell. This will become $SHELL. 618 | char *sh; 619 | if (shell != NULL) { 620 | sh = shell; 621 | } else if (char *env_shell = getenv("SHELL")) { 622 | sh = env_shell; 623 | } else if (pw->pw_shell[0]) { 624 | sh = pw->pw_shell; 625 | } else { 626 | static char bin_sh[] = "/bin/sh"; 627 | sh = bin_sh; 628 | } 629 | 630 | // The program to execute is the shell unless explicitly specified. 631 | char *prog_only_args[2] = {NULL, NULL}; 632 | char **args; 633 | if (opt_cmd) { 634 | args = opt_cmd; 635 | } else { 636 | prog_only_args[0] = sh; 637 | args = prog_only_args; 638 | } 639 | char *prog = args[0]; 640 | 641 | unsetenv("COLUMNS"); 642 | unsetenv("LINES"); 643 | unsetenv("TERMCAP"); 644 | setenv("LOGNAME", pw->pw_name, 1); 645 | setenv("USER", pw->pw_name, 1); 646 | setenv("SHELL", sh, 1); 647 | setenv("HOME", pw->pw_dir, 1); 648 | setenv("TERM", termname, 1); 649 | xsetenv(); 650 | 651 | signal(SIGCHLD, SIG_DFL); 652 | signal(SIGHUP, SIG_DFL); 653 | signal(SIGINT, SIG_DFL); 654 | signal(SIGQUIT, SIG_DFL); 655 | signal(SIGTERM, SIG_DFL); 656 | signal(SIGALRM, SIG_DFL); 657 | 658 | execvp(prog, args); 659 | _exit(1); 660 | } 661 | 662 | void sigchld(int a) { 663 | int stat; 664 | pid_t p; 665 | 666 | if ((p = waitpid(pid, &stat, WNOHANG)) < 0) 667 | die("Waiting for pid %hd failed: %s\n", pid, strerror(errno)); 668 | 669 | if (pid != p) 670 | return; 671 | 672 | if (!WIFEXITED(stat) || WEXITSTATUS(stat)) 673 | die("child finished with error '%d'\n", stat); 674 | exit(0); 675 | } 676 | 677 | void ttynew(void) { 678 | int m, s; 679 | struct winsize w = {static_cast(term.row), 680 | static_cast(term.col), 0, 0}; 681 | 682 | if (opt_io) { 683 | term.mode |= MODE_PRINT; 684 | iofd = (!strcmp(opt_io, "-")) ? 1 : open(opt_io, O_WRONLY | O_CREAT, 0666); 685 | if (iofd < 0) { 686 | fprintf(stderr, "Error opening %s:%s\n", opt_io, strerror(errno)); 687 | } 688 | } 689 | 690 | /* seems to work fine on linux, openbsd and freebsd */ 691 | if (openpty(&m, &s, NULL, NULL, &w) < 0) 692 | die("openpty failed: %s\n", strerror(errno)); 693 | 694 | switch (pid = fork()) { 695 | case -1: 696 | die("fork failed\n"); 697 | break; 698 | case 0: 699 | close(iofd); 700 | setsid(); /* create a new process group */ 701 | dup2(s, 0); 702 | dup2(s, 1); 703 | dup2(s, 2); 704 | if (ioctl(s, TIOCSCTTY, NULL) < 0) 705 | die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); 706 | close(s); 707 | close(m); 708 | execsh(); 709 | break; 710 | default: 711 | close(s); 712 | cmdfd = m; 713 | signal(SIGCHLD, sigchld); 714 | break; 715 | } 716 | } 717 | 718 | size_t ttyread(void) { 719 | static char buf[BUFSIZ]; 720 | static int buflen = 0; 721 | char *ptr; 722 | int charsize; /* size of utf8 char in bytes */ 723 | Rune unicodep; 724 | int ret; 725 | 726 | /* append read bytes to unprocessed bytes */ 727 | if ((ret = read(cmdfd, buf + buflen, LEN(buf) - buflen)) < 0) 728 | die("Couldn't read from shell: %s\n", strerror(errno)); 729 | 730 | buflen += ret; 731 | ptr = buf; 732 | 733 | for (;;) { 734 | if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { 735 | /* process a complete utf8 char */ 736 | charsize = utf8decode(ptr, &unicodep, buflen); 737 | if (charsize == 0) 738 | break; 739 | tputc(unicodep); 740 | ptr += charsize; 741 | buflen -= charsize; 742 | 743 | } else { 744 | if (buflen <= 0) 745 | break; 746 | tputc(*ptr++ & 0xFF); 747 | buflen--; 748 | } 749 | } 750 | /* keep any uncomplete utf8 char for the next call */ 751 | if (buflen > 0) 752 | memmove(buf, ptr, buflen); 753 | 754 | return ret; 755 | } 756 | 757 | void ttywrite(const char *s, size_t n) { 758 | fd_set wfd, rfd; 759 | ssize_t r; 760 | size_t lim = 256; 761 | 762 | /* 763 | * Remember that we are using a pty, which might be a modem line. 764 | * Writing too much will clog the line. That's why we are doing this 765 | * dance. 766 | * FIXME: Migrate the world to Plan 9. 767 | */ 768 | while (n > 0) { 769 | FD_ZERO(&wfd); 770 | FD_ZERO(&rfd); 771 | FD_SET(cmdfd, &wfd); 772 | FD_SET(cmdfd, &rfd); 773 | 774 | /* Check if we can write. */ 775 | if (pselect(cmdfd + 1, &rfd, &wfd, NULL, NULL, NULL) < 0) { 776 | if (errno == EINTR) 777 | continue; 778 | die("select failed: %s\n", strerror(errno)); 779 | } 780 | if (FD_ISSET(cmdfd, &wfd)) { 781 | /* 782 | * Only write the bytes written by ttywrite() or the 783 | * default of 256. This seems to be a reasonable value 784 | * for a serial line. Bigger values might clog the I/O. 785 | */ 786 | if ((r = write(cmdfd, s, (n < lim) ? n : lim)) < 0) 787 | goto write_error; 788 | if (r < n) { 789 | /* 790 | * We weren't able to write out everything. 791 | * This means the buffer is getting full 792 | * again. Empty it. 793 | */ 794 | if (n < lim) 795 | lim = ttyread(); 796 | n -= r; 797 | s += r; 798 | } else { 799 | /* All bytes have been written. */ 800 | break; 801 | } 802 | } 803 | if (FD_ISSET(cmdfd, &rfd)) 804 | lim = ttyread(); 805 | } 806 | return; 807 | 808 | write_error: 809 | die("write error on tty: %s\n", strerror(errno)); 810 | } 811 | 812 | void ttysend(const char *s, size_t n) { 813 | int len; 814 | const char *t, *lim; 815 | Rune u; 816 | 817 | ttywrite(s, n); 818 | if (!IS_SET(MODE_ECHO)) 819 | return; 820 | 821 | lim = &s[n]; 822 | for (t = s; t < lim; t += len) { 823 | if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { 824 | len = utf8decode(t, &u, n); 825 | } else { 826 | u = *t & 0xFF; 827 | len = 1; 828 | } 829 | if (len <= 0) 830 | break; 831 | techo(u); 832 | n -= len; 833 | } 834 | } 835 | 836 | void ttyresize(void) { 837 | struct winsize w; 838 | 839 | w.ws_row = term.row; 840 | w.ws_col = term.col; 841 | w.ws_xpixel = win.tw; 842 | w.ws_ypixel = win.th; 843 | if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) 844 | fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); 845 | } 846 | 847 | int tattrset(int attr) { 848 | int i, j; 849 | 850 | for (i = 0; i < term.row - 1; i++) { 851 | for (j = 0; j < term.col - 1; j++) { 852 | if (term.line[i][j].mode & attr) 853 | return 1; 854 | } 855 | } 856 | 857 | return 0; 858 | } 859 | 860 | void tsetdirt(int top, int bot) { 861 | int i; 862 | 863 | LIMIT(top, 0, term.row - 1); 864 | LIMIT(bot, 0, term.row - 1); 865 | 866 | for (i = top; i <= bot; i++) 867 | term.dirty[i] = 1; 868 | } 869 | 870 | void tsetdirtattr(int attr) { 871 | int i, j; 872 | 873 | for (i = 0; i < term.row - 1; i++) { 874 | for (j = 0; j < term.col - 1; j++) { 875 | if (term.line[i][j].mode & attr) { 876 | tsetdirt(i, i); 877 | break; 878 | } 879 | } 880 | } 881 | } 882 | 883 | void tfulldirt(void) { tsetdirt(0, term.row - 1); } 884 | 885 | void tcursor(int mode) { 886 | static TCursor c[2]; 887 | int alt = IS_SET(MODE_ALTSCREEN); 888 | 889 | if (mode == CURSOR_SAVE) { 890 | c[alt] = term.c; 891 | } else if (mode == CURSOR_LOAD) { 892 | term.c = c[alt]; 893 | tmoveto(c[alt].x, c[alt].y); 894 | } 895 | } 896 | 897 | void treset(void) { 898 | uint i; 899 | 900 | term.c = TCursor{MTGlyph{/* rune */ 0, ATTR_NULL, defaultfg, defaultbg}, 901 | /* x */ 0, 902 | /* y */ 0, CURSOR_DEFAULT}; 903 | 904 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 905 | // Inital tabstops every 8 columns, matching 'it#' in terminfo. 906 | for (i = 8; i < term.col; i += 8) 907 | term.tabs[i] = 1; 908 | term.top = 0; 909 | term.bot = term.row - 1; 910 | term.mode = MODE_WRAP | MODE_UTF8; 911 | memset(term.trantbl, CS_USA, sizeof(term.trantbl)); 912 | term.charset = 0; 913 | 914 | for (i = 0; i < 2; i++) { 915 | tmoveto(0, 0); 916 | tcursor(CURSOR_SAVE); 917 | tclearregion(0, 0, term.col - 1, term.row - 1); 918 | tswapscreen(); 919 | } 920 | } 921 | 922 | void tnew(int col, int row) { 923 | term = {}; 924 | term.c.attr = {/* rune */ 0, ATTR_NULL, defaultfg, defaultbg}; 925 | tresize(col, row); 926 | term.numlock = 1; 927 | 928 | treset(); 929 | } 930 | 931 | void tswapscreen(void) { 932 | Line *tmp = term.line; 933 | 934 | term.line = term.alt; 935 | term.alt = tmp; 936 | term.mode ^= MODE_ALTSCREEN; 937 | tfulldirt(); 938 | } 939 | 940 | void tscrolldown(int orig, int n) { 941 | int i; 942 | Line temp; 943 | 944 | LIMIT(n, 0, term.bot - orig + 1); 945 | 946 | tsetdirt(orig, term.bot - n); 947 | tclearregion(0, term.bot - n + 1, term.col - 1, term.bot); 948 | 949 | for (i = term.bot; i >= orig + n; i--) { 950 | temp = term.line[i]; 951 | term.line[i] = term.line[i - n]; 952 | term.line[i - n] = temp; 953 | } 954 | 955 | selscroll(orig, n); 956 | } 957 | 958 | void tscrollup(int orig, int n) { 959 | int i; 960 | Line temp; 961 | 962 | LIMIT(n, 0, term.bot - orig + 1); 963 | 964 | tclearregion(0, orig, term.col - 1, orig + n - 1); 965 | tsetdirt(orig + n, term.bot); 966 | 967 | for (i = orig; i <= term.bot - n; i++) { 968 | temp = term.line[i]; 969 | term.line[i] = term.line[i + n]; 970 | term.line[i + n] = temp; 971 | } 972 | 973 | selscroll(orig, -n); 974 | } 975 | 976 | void selscroll(int orig, int n) { 977 | if (sel.ob.x == -1) 978 | return; 979 | 980 | if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { 981 | if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { 982 | selclear(); 983 | return; 984 | } 985 | if (sel.type == SEL_RECTANGULAR) { 986 | if (sel.ob.y < term.top) 987 | sel.ob.y = term.top; 988 | if (sel.oe.y > term.bot) 989 | sel.oe.y = term.bot; 990 | } else { 991 | if (sel.ob.y < term.top) { 992 | sel.ob.y = term.top; 993 | sel.ob.x = 0; 994 | } 995 | if (sel.oe.y > term.bot) { 996 | sel.oe.y = term.bot; 997 | sel.oe.x = term.col; 998 | } 999 | } 1000 | selnormalize(); 1001 | } 1002 | } 1003 | 1004 | void tnewline(int first_col) { 1005 | int y = term.c.y; 1006 | 1007 | if (y == term.bot) { 1008 | tscrollup(term.top, 1); 1009 | } else { 1010 | y++; 1011 | } 1012 | tmoveto(first_col ? 0 : term.c.x, y); 1013 | } 1014 | 1015 | void csiparse(void) { 1016 | char *p = csiescseq.buf, *np; 1017 | long int v; 1018 | 1019 | csiescseq.narg = 0; 1020 | if (*p == '?') { 1021 | csiescseq.priv = 1; 1022 | p++; 1023 | } 1024 | 1025 | csiescseq.buf[csiescseq.len] = '\0'; 1026 | while (p < csiescseq.buf + csiescseq.len) { 1027 | np = NULL; 1028 | v = strtol(p, &np, 10); 1029 | if (np == p) 1030 | v = 0; 1031 | if (v == LONG_MAX || v == LONG_MIN) 1032 | v = -1; 1033 | csiescseq.arg[csiescseq.narg++] = v; 1034 | p = np; 1035 | if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) 1036 | break; 1037 | p++; 1038 | } 1039 | csiescseq.mode[0] = *p++; 1040 | csiescseq.mode[1] = (p < csiescseq.buf + csiescseq.len) ? *p : '\0'; 1041 | } 1042 | 1043 | /* for absolute user moves, when decom is set */ 1044 | void tmoveato(int x, int y) { 1045 | tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top : 0)); 1046 | } 1047 | 1048 | void tmoveto(int x, int y) { 1049 | int miny, maxy; 1050 | 1051 | if (term.c.state & CURSOR_ORIGIN) { 1052 | miny = term.top; 1053 | maxy = term.bot; 1054 | } else { 1055 | miny = 0; 1056 | maxy = term.row - 1; 1057 | } 1058 | term.c.state &= ~CURSOR_WRAPNEXT; 1059 | term.c.x = LIMIT(x, 0, term.col - 1); 1060 | term.c.y = LIMIT(y, miny, maxy); 1061 | } 1062 | 1063 | void tsetchar(Rune u, MTGlyph *attr, int x, int y) { 1064 | static const char *vt100_0[62] = { 1065 | /* 0x41 - 0x7e */ 1066 | "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 1067 | 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 1068 | 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ 1069 | 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ 1070 | "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ 1071 | "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ 1072 | "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ 1073 | "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ 1074 | }; 1075 | 1076 | /* 1077 | * The table is proudly stolen from rxvt. 1078 | */ 1079 | if (term.trantbl[term.charset] == CS_GRAPHIC0 && BETWEEN(u, 0x41, 0x7e) && 1080 | vt100_0[u - 0x41]) 1081 | utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); 1082 | 1083 | if (term.line[y][x].mode & ATTR_WIDE) { 1084 | if (x + 1 < term.col) { 1085 | term.line[y][x + 1].u = ' '; 1086 | term.line[y][x + 1].mode &= ~ATTR_WDUMMY; 1087 | } 1088 | } else if (term.line[y][x].mode & ATTR_WDUMMY) { 1089 | term.line[y][x - 1].u = ' '; 1090 | term.line[y][x - 1].mode &= ~ATTR_WIDE; 1091 | } 1092 | 1093 | term.dirty[y] = 1; 1094 | term.line[y][x] = *attr; 1095 | term.line[y][x].u = u; 1096 | } 1097 | 1098 | void tclearregion(int x1, int y1, int x2, int y2) { 1099 | int x, y, temp; 1100 | MTGlyph *gp; 1101 | 1102 | if (x1 > x2) 1103 | temp = x1, x1 = x2, x2 = temp; 1104 | if (y1 > y2) 1105 | temp = y1, y1 = y2, y2 = temp; 1106 | 1107 | LIMIT(x1, 0, term.col - 1); 1108 | LIMIT(x2, 0, term.col - 1); 1109 | LIMIT(y1, 0, term.row - 1); 1110 | LIMIT(y2, 0, term.row - 1); 1111 | 1112 | for (y = y1; y <= y2; y++) { 1113 | term.dirty[y] = 1; 1114 | for (x = x1; x <= x2; x++) { 1115 | gp = &term.line[y][x]; 1116 | if (selected(x, y)) 1117 | selclear(); 1118 | gp->fg = term.c.attr.fg; 1119 | gp->bg = term.c.attr.bg; 1120 | gp->mode = 0; 1121 | gp->u = ' '; 1122 | } 1123 | } 1124 | } 1125 | 1126 | void tdeletechar(int n) { 1127 | int dst, src, size; 1128 | MTGlyph *line; 1129 | 1130 | LIMIT(n, 0, term.col - term.c.x); 1131 | 1132 | dst = term.c.x; 1133 | src = term.c.x + n; 1134 | size = term.col - src; 1135 | line = term.line[term.c.y]; 1136 | 1137 | memmove(&line[dst], &line[src], size * sizeof(MTGlyph)); 1138 | tclearregion(term.col - n, term.c.y, term.col - 1, term.c.y); 1139 | } 1140 | 1141 | void tinsertblank(int n) { 1142 | int dst, src, size; 1143 | MTGlyph *line; 1144 | 1145 | LIMIT(n, 0, term.col - term.c.x); 1146 | 1147 | dst = term.c.x + n; 1148 | src = term.c.x; 1149 | size = term.col - dst; 1150 | line = term.line[term.c.y]; 1151 | 1152 | memmove(&line[dst], &line[src], size * sizeof(MTGlyph)); 1153 | tclearregion(src, term.c.y, dst - 1, term.c.y); 1154 | } 1155 | 1156 | void tinsertblankline(int n) { 1157 | if (BETWEEN(term.c.y, term.top, term.bot)) 1158 | tscrolldown(term.c.y, n); 1159 | } 1160 | 1161 | void tdeleteline(int n) { 1162 | if (BETWEEN(term.c.y, term.top, term.bot)) 1163 | tscrollup(term.c.y, n); 1164 | } 1165 | 1166 | int32_t tdefcolor(int *attr, int *npar, int l) { 1167 | int32_t idx = -1; 1168 | uint r, g, b; 1169 | 1170 | switch (attr[*npar + 1]) { 1171 | case 2: /* direct color in RGB space */ 1172 | if (*npar + 4 >= l) { 1173 | fprintf(stderr, "erresc(38): Incorrect number of parameters (%d)\n", 1174 | *npar); 1175 | break; 1176 | } 1177 | r = attr[*npar + 2]; 1178 | g = attr[*npar + 3]; 1179 | b = attr[*npar + 4]; 1180 | *npar += 4; 1181 | if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) 1182 | fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", r, g, b); 1183 | else 1184 | idx = TRUECOLOR(r, g, b); 1185 | break; 1186 | case 5: /* indexed color */ 1187 | if (*npar + 2 >= l) { 1188 | fprintf(stderr, "erresc(38): Incorrect number of parameters (%d)\n", 1189 | *npar); 1190 | break; 1191 | } 1192 | *npar += 2; 1193 | if (!BETWEEN(attr[*npar], 0, 255)) 1194 | fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); 1195 | else 1196 | idx = attr[*npar]; 1197 | break; 1198 | case 0: /* implemented defined (only foreground) */ 1199 | case 1: /* transparent */ 1200 | case 3: /* direct color in CMY space */ 1201 | case 4: /* direct color in CMYK space */ 1202 | default: 1203 | fprintf(stderr, "erresc(38): gfx attr %d unknown\n", attr[*npar]); 1204 | break; 1205 | } 1206 | 1207 | return idx; 1208 | } 1209 | 1210 | void tsetattr(int *attr, int l) { 1211 | int i; 1212 | int32_t idx; 1213 | 1214 | for (i = 0; i < l; i++) { 1215 | switch (attr[i]) { 1216 | case 0: 1217 | term.c.attr.mode &= 1218 | ~(ATTR_BOLD | ATTR_FAINT | ATTR_ITALIC | ATTR_UNDERLINE | ATTR_BLINK | 1219 | ATTR_REVERSE | ATTR_INVISIBLE | ATTR_STRUCK); 1220 | term.c.attr.fg = defaultfg; 1221 | term.c.attr.bg = defaultbg; 1222 | break; 1223 | case 1: 1224 | term.c.attr.mode |= ATTR_BOLD; 1225 | break; 1226 | case 2: 1227 | term.c.attr.mode |= ATTR_FAINT; 1228 | break; 1229 | case 3: 1230 | term.c.attr.mode |= ATTR_ITALIC; 1231 | break; 1232 | case 4: 1233 | term.c.attr.mode |= ATTR_UNDERLINE; 1234 | break; 1235 | case 5: /* slow blink */ 1236 | /* FALLTHROUGH */ 1237 | case 6: /* rapid blink */ 1238 | term.c.attr.mode |= ATTR_BLINK; 1239 | break; 1240 | case 7: 1241 | term.c.attr.mode |= ATTR_REVERSE; 1242 | break; 1243 | case 8: 1244 | term.c.attr.mode |= ATTR_INVISIBLE; 1245 | break; 1246 | case 9: 1247 | term.c.attr.mode |= ATTR_STRUCK; 1248 | break; 1249 | case 22: 1250 | term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); 1251 | break; 1252 | case 23: 1253 | term.c.attr.mode &= ~ATTR_ITALIC; 1254 | break; 1255 | case 24: 1256 | term.c.attr.mode &= ~ATTR_UNDERLINE; 1257 | break; 1258 | case 25: 1259 | term.c.attr.mode &= ~ATTR_BLINK; 1260 | break; 1261 | case 27: 1262 | term.c.attr.mode &= ~ATTR_REVERSE; 1263 | break; 1264 | case 28: 1265 | term.c.attr.mode &= ~ATTR_INVISIBLE; 1266 | break; 1267 | case 29: 1268 | term.c.attr.mode &= ~ATTR_STRUCK; 1269 | break; 1270 | case 38: 1271 | if ((idx = tdefcolor(attr, &i, l)) >= 0) 1272 | term.c.attr.fg = idx; 1273 | break; 1274 | case 39: 1275 | term.c.attr.fg = defaultfg; 1276 | break; 1277 | case 48: 1278 | if ((idx = tdefcolor(attr, &i, l)) >= 0) 1279 | term.c.attr.bg = idx; 1280 | break; 1281 | case 49: 1282 | term.c.attr.bg = defaultbg; 1283 | break; 1284 | default: 1285 | if (BETWEEN(attr[i], 30, 37)) { 1286 | term.c.attr.fg = attr[i] - 30; 1287 | } else if (BETWEEN(attr[i], 40, 47)) { 1288 | term.c.attr.bg = attr[i] - 40; 1289 | } else if (BETWEEN(attr[i], 90, 97)) { 1290 | term.c.attr.fg = attr[i] - 90 + 8; 1291 | } else if (BETWEEN(attr[i], 100, 107)) { 1292 | term.c.attr.bg = attr[i] - 100 + 8; 1293 | } else { 1294 | fprintf(stderr, "erresc(default): gfx attr %d unknown\n", attr[i]), 1295 | csidump(); 1296 | } 1297 | break; 1298 | } 1299 | } 1300 | } 1301 | 1302 | void tsetscroll(int t, int b) { 1303 | int temp; 1304 | 1305 | LIMIT(t, 0, term.row - 1); 1306 | LIMIT(b, 0, term.row - 1); 1307 | if (t > b) { 1308 | temp = t; 1309 | t = b; 1310 | b = temp; 1311 | } 1312 | term.top = t; 1313 | term.bot = b; 1314 | } 1315 | 1316 | void tsetmode(int priv, int set, int *args, int narg) { 1317 | int *lim, mode; 1318 | int alt; 1319 | 1320 | for (lim = args + narg; args < lim; ++args) { 1321 | if (priv) { 1322 | switch (*args) { 1323 | case 1: /* DECCKM -- Cursor key */ 1324 | MODBIT(term.mode, set, MODE_APPCURSOR); 1325 | break; 1326 | case 5: /* DECSCNM -- Reverse video */ 1327 | mode = term.mode; 1328 | MODBIT(term.mode, set, MODE_REVERSE); 1329 | if (mode != term.mode) 1330 | redraw(); 1331 | break; 1332 | case 6: /* DECOM -- Origin */ 1333 | MODBIT(term.c.state, set, CURSOR_ORIGIN); 1334 | tmoveato(0, 0); 1335 | break; 1336 | case 7: /* DECAWM -- Auto wrap */ 1337 | MODBIT(term.mode, set, MODE_WRAP); 1338 | break; 1339 | case 0: /* Error (IGNORED) */ 1340 | case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ 1341 | case 3: /* DECCOLM -- Column (IGNORED) */ 1342 | case 4: /* DECSCLM -- Scroll (IGNORED) */ 1343 | case 8: /* DECARM -- Auto repeat (IGNORED) */ 1344 | case 18: /* DECPFF -- Printer feed (IGNORED) */ 1345 | case 19: /* DECPEX -- Printer extent (IGNORED) */ 1346 | case 42: /* DECNRCM -- National characters (IGNORED) */ 1347 | case 12: /* att610 -- Start blinking cursor (IGNORED) */ 1348 | break; 1349 | case 25: /* DECTCEM -- Text Cursor Enable Mode */ 1350 | MODBIT(term.mode, !set, MODE_HIDE); 1351 | break; 1352 | case 9: /* X10 mouse compatibility mode */ 1353 | xsetpointermotion(0); 1354 | MODBIT(term.mode, 0, MODE_MOUSE); 1355 | MODBIT(term.mode, set, MODE_MOUSEX10); 1356 | break; 1357 | case 1000: /* 1000: report button press */ 1358 | xsetpointermotion(0); 1359 | MODBIT(term.mode, 0, MODE_MOUSE); 1360 | MODBIT(term.mode, set, MODE_MOUSEBTN); 1361 | break; 1362 | case 1002: /* 1002: report motion on button press */ 1363 | xsetpointermotion(0); 1364 | MODBIT(term.mode, 0, MODE_MOUSE); 1365 | MODBIT(term.mode, set, MODE_MOUSEMOTION); 1366 | break; 1367 | case 1003: /* 1003: enable all mouse motions */ 1368 | xsetpointermotion(set); 1369 | MODBIT(term.mode, 0, MODE_MOUSE); 1370 | MODBIT(term.mode, set, MODE_MOUSEMANY); 1371 | break; 1372 | case 1004: /* 1004: send focus events to tty */ 1373 | MODBIT(term.mode, set, MODE_FOCUS); 1374 | break; 1375 | case 1006: /* 1006: extended reporting mode */ 1376 | MODBIT(term.mode, set, MODE_MOUSESGR); 1377 | break; 1378 | case 1034: 1379 | MODBIT(term.mode, set, MODE_8BIT); 1380 | break; 1381 | case 1049: /* swap screen & set/restore cursor as xterm */ 1382 | if (!allowaltscreen) 1383 | break; 1384 | tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1385 | /* FALLTHROUGH */ 1386 | case 47: /* swap screen */ 1387 | case 1047: 1388 | if (!allowaltscreen) 1389 | break; 1390 | alt = IS_SET(MODE_ALTSCREEN); 1391 | if (alt) { 1392 | tclearregion(0, 0, term.col - 1, term.row - 1); 1393 | } 1394 | if (set ^ alt) /* set is always 1 or 0 */ 1395 | tswapscreen(); 1396 | if (*args != 1049) 1397 | break; 1398 | /* FALLTHROUGH */ 1399 | case 1048: 1400 | tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); 1401 | break; 1402 | case 2004: /* 2004: bracketed paste mode */ 1403 | MODBIT(term.mode, set, MODE_BRCKTPASTE); 1404 | break; 1405 | /* Not implemented mouse modes. See comments there. */ 1406 | case 1001: /* mouse highlight mode; can hang the 1407 | terminal by design when implemented. */ 1408 | case 1005: /* UTF-8 mouse mode; will confuse 1409 | applications not supporting UTF-8 1410 | and luit. */ 1411 | case 1015: /* urxvt mangled mouse mode; incompatible 1412 | and can be mistaken for other control 1413 | codes. */ 1414 | default: 1415 | fprintf(stderr, "erresc: unknown private set/reset mode %d\n", *args); 1416 | break; 1417 | } 1418 | } else { 1419 | switch (*args) { 1420 | case 0: /* Error (IGNORED) */ 1421 | break; 1422 | case 2: /* KAM -- keyboard action */ 1423 | MODBIT(term.mode, set, MODE_KBDLOCK); 1424 | break; 1425 | case 4: /* IRM -- Insertion-replacement */ 1426 | MODBIT(term.mode, set, MODE_INSERT); 1427 | break; 1428 | case 12: /* SRM -- Send/Receive */ 1429 | MODBIT(term.mode, !set, MODE_ECHO); 1430 | break; 1431 | case 20: /* LNM -- Linefeed/new line */ 1432 | MODBIT(term.mode, set, MODE_CRLF); 1433 | break; 1434 | default: 1435 | fprintf(stderr, "erresc: unknown set/reset mode %d\n", *args); 1436 | break; 1437 | } 1438 | } 1439 | } 1440 | } 1441 | 1442 | void csihandle(void) { 1443 | char buf[40]; 1444 | int len; 1445 | 1446 | switch (csiescseq.mode[0]) { 1447 | default: 1448 | unknown: 1449 | fprintf(stderr, "erresc: unknown csi "); 1450 | csidump(); 1451 | /* die(""); */ 1452 | break; 1453 | case '@': /* ICH -- Insert blank char */ 1454 | DEFAULT(csiescseq.arg[0], 1); 1455 | tinsertblank(csiescseq.arg[0]); 1456 | break; 1457 | case 'A': /* CUU -- Cursor Up */ 1458 | DEFAULT(csiescseq.arg[0], 1); 1459 | tmoveto(term.c.x, term.c.y - csiescseq.arg[0]); 1460 | break; 1461 | case 'B': /* CUD -- Cursor Down */ 1462 | case 'e': /* VPR --Cursor Down */ 1463 | DEFAULT(csiescseq.arg[0], 1); 1464 | tmoveto(term.c.x, term.c.y + csiescseq.arg[0]); 1465 | break; 1466 | case 'i': /* MC -- Media Copy */ 1467 | switch (csiescseq.arg[0]) { 1468 | case 0: 1469 | tdump(); 1470 | break; 1471 | case 1: 1472 | tdumpline(term.c.y); 1473 | break; 1474 | case 2: 1475 | tdumpsel(); 1476 | break; 1477 | case 4: 1478 | term.mode &= ~MODE_PRINT; 1479 | break; 1480 | case 5: 1481 | term.mode |= MODE_PRINT; 1482 | break; 1483 | } 1484 | break; 1485 | case 'c': /* DA -- Device Attributes */ 1486 | if (csiescseq.arg[0] == 0) 1487 | ttywrite(vt102_identify, strlen(vt102_identify)); 1488 | break; 1489 | case 'C': /* CUF -- Cursor Forward */ 1490 | case 'a': /* HPR -- Cursor Forward */ 1491 | DEFAULT(csiescseq.arg[0], 1); 1492 | tmoveto(term.c.x + csiescseq.arg[0], term.c.y); 1493 | break; 1494 | case 'D': /* CUB -- Cursor Backward */ 1495 | DEFAULT(csiescseq.arg[0], 1); 1496 | tmoveto(term.c.x - csiescseq.arg[0], term.c.y); 1497 | break; 1498 | case 'E': /* CNL -- Cursor Down and first col */ 1499 | DEFAULT(csiescseq.arg[0], 1); 1500 | tmoveto(0, term.c.y + csiescseq.arg[0]); 1501 | break; 1502 | case 'F': /* CPL -- Cursor Up and first col */ 1503 | DEFAULT(csiescseq.arg[0], 1); 1504 | tmoveto(0, term.c.y - csiescseq.arg[0]); 1505 | break; 1506 | case 'g': /* TBC -- Tabulation clear */ 1507 | switch (csiescseq.arg[0]) { 1508 | case 0: /* clear current tab stop */ 1509 | term.tabs[term.c.x] = 0; 1510 | break; 1511 | case 3: /* clear all the tabs */ 1512 | memset(term.tabs, 0, term.col * sizeof(*term.tabs)); 1513 | break; 1514 | default: 1515 | goto unknown; 1516 | } 1517 | break; 1518 | case 'G': /* CHA -- Move to */ 1519 | case '`': /* HPA */ 1520 | DEFAULT(csiescseq.arg[0], 1); 1521 | tmoveto(csiescseq.arg[0] - 1, term.c.y); 1522 | break; 1523 | case 'H': /* CUP -- Move to */ 1524 | case 'f': /* HVP */ 1525 | DEFAULT(csiescseq.arg[0], 1); 1526 | DEFAULT(csiescseq.arg[1], 1); 1527 | tmoveato(csiescseq.arg[1] - 1, csiescseq.arg[0] - 1); 1528 | break; 1529 | case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ 1530 | DEFAULT(csiescseq.arg[0], 1); 1531 | tputtab(csiescseq.arg[0]); 1532 | break; 1533 | case 'J': /* ED -- Clear screen */ 1534 | selclear(); 1535 | switch (csiescseq.arg[0]) { 1536 | case 0: /* below */ 1537 | tclearregion(term.c.x, term.c.y, term.col - 1, term.c.y); 1538 | if (term.c.y < term.row - 1) { 1539 | tclearregion(0, term.c.y + 1, term.col - 1, term.row - 1); 1540 | } 1541 | break; 1542 | case 1: /* above */ 1543 | if (term.c.y > 1) 1544 | tclearregion(0, 0, term.col - 1, term.c.y - 1); 1545 | tclearregion(0, term.c.y, term.c.x, term.c.y); 1546 | break; 1547 | case 2: /* all */ 1548 | tclearregion(0, 0, term.col - 1, term.row - 1); 1549 | break; 1550 | default: 1551 | goto unknown; 1552 | } 1553 | break; 1554 | case 'K': /* EL -- Clear line */ 1555 | switch (csiescseq.arg[0]) { 1556 | case 0: /* right */ 1557 | tclearregion(term.c.x, term.c.y, term.col - 1, term.c.y); 1558 | break; 1559 | case 1: /* left */ 1560 | tclearregion(0, term.c.y, term.c.x, term.c.y); 1561 | break; 1562 | case 2: /* all */ 1563 | tclearregion(0, term.c.y, term.col - 1, term.c.y); 1564 | break; 1565 | } 1566 | break; 1567 | case 'S': /* SU -- Scroll line up */ 1568 | DEFAULT(csiescseq.arg[0], 1); 1569 | tscrollup(term.top, csiescseq.arg[0]); 1570 | break; 1571 | case 'T': /* SD -- Scroll line down */ 1572 | DEFAULT(csiescseq.arg[0], 1); 1573 | tscrolldown(term.top, csiescseq.arg[0]); 1574 | break; 1575 | case 'L': /* IL -- Insert blank lines */ 1576 | DEFAULT(csiescseq.arg[0], 1); 1577 | tinsertblankline(csiescseq.arg[0]); 1578 | break; 1579 | case 'l': /* RM -- Reset Mode */ 1580 | tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); 1581 | break; 1582 | case 'M': /* DL -- Delete lines */ 1583 | DEFAULT(csiescseq.arg[0], 1); 1584 | tdeleteline(csiescseq.arg[0]); 1585 | break; 1586 | case 'X': /* ECH -- Erase char */ 1587 | DEFAULT(csiescseq.arg[0], 1); 1588 | tclearregion(term.c.x, term.c.y, term.c.x + csiescseq.arg[0] - 1, term.c.y); 1589 | break; 1590 | case 'P': /* DCH -- Delete char */ 1591 | DEFAULT(csiescseq.arg[0], 1); 1592 | tdeletechar(csiescseq.arg[0]); 1593 | break; 1594 | case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ 1595 | DEFAULT(csiescseq.arg[0], 1); 1596 | tputtab(-csiescseq.arg[0]); 1597 | break; 1598 | case 'd': /* VPA -- Move to */ 1599 | DEFAULT(csiescseq.arg[0], 1); 1600 | tmoveato(term.c.x, csiescseq.arg[0] - 1); 1601 | break; 1602 | case 'h': /* SM -- Set terminal mode */ 1603 | tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); 1604 | break; 1605 | case 'm': /* SGR -- Terminal attribute (color) */ 1606 | tsetattr(csiescseq.arg, csiescseq.narg); 1607 | break; 1608 | case 'n': /* DSR – Device Status Report (cursor position) */ 1609 | if (csiescseq.arg[0] == 6) { 1610 | len = 1611 | snprintf(buf, sizeof(buf), "\033[%i;%iR", term.c.y + 1, term.c.x + 1); 1612 | ttywrite(buf, len); 1613 | } 1614 | break; 1615 | case 'r': /* DECSTBM -- Set Scrolling Region */ 1616 | if (csiescseq.priv) { 1617 | goto unknown; 1618 | } else { 1619 | DEFAULT(csiescseq.arg[0], 1); 1620 | DEFAULT(csiescseq.arg[1], term.row); 1621 | tsetscroll(csiescseq.arg[0] - 1, csiescseq.arg[1] - 1); 1622 | tmoveato(0, 0); 1623 | } 1624 | break; 1625 | case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ 1626 | tcursor(CURSOR_SAVE); 1627 | break; 1628 | case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ 1629 | tcursor(CURSOR_LOAD); 1630 | break; 1631 | case ' ': 1632 | switch (csiescseq.mode[1]) { 1633 | case 'q': /* DECSCUSR -- Set Cursor Style */ 1634 | DEFAULT(csiescseq.arg[0], 1); 1635 | if (!BETWEEN(csiescseq.arg[0], 0, 6)) { 1636 | goto unknown; 1637 | } 1638 | win.cursor = csiescseq.arg[0]; 1639 | break; 1640 | default: 1641 | goto unknown; 1642 | } 1643 | break; 1644 | } 1645 | } 1646 | 1647 | void csidump(void) { 1648 | int i; 1649 | uint c; 1650 | 1651 | fprintf(stderr, "ESC["); 1652 | for (i = 0; i < csiescseq.len; i++) { 1653 | c = csiescseq.buf[i] & 0xff; 1654 | if (isprint(c)) { 1655 | putc(c, stderr); 1656 | } else if (c == '\n') { 1657 | fprintf(stderr, "(\\n)"); 1658 | } else if (c == '\r') { 1659 | fprintf(stderr, "(\\r)"); 1660 | } else if (c == 0x1b) { 1661 | fprintf(stderr, "(\\e)"); 1662 | } else { 1663 | fprintf(stderr, "(%02x)", c); 1664 | } 1665 | } 1666 | putc('\n', stderr); 1667 | } 1668 | 1669 | void csireset(void) { memset(&csiescseq, 0, sizeof(csiescseq)); } 1670 | 1671 | void strhandle(void) { 1672 | char *p = NULL; 1673 | int j, narg, par; 1674 | 1675 | term.esc &= ~(ESC_STR_END | ESC_STR); 1676 | strparse(); 1677 | par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; 1678 | 1679 | switch (strescseq.type) { 1680 | case ']': /* OSC -- Operating System Command */ 1681 | switch (par) { 1682 | case 0: 1683 | case 1: 1684 | case 2: 1685 | if (narg > 1) 1686 | xsettitle(strescseq.args[1]); 1687 | return; 1688 | case 52: 1689 | if (narg > 2) { 1690 | char *dec; 1691 | 1692 | dec = base64dec(strescseq.args[2]); 1693 | if (dec) { 1694 | xsetsel(dec, CurrentTime); 1695 | clipcopy(NULL); 1696 | } else { 1697 | fprintf(stderr, "erresc: invalid base64\n"); 1698 | } 1699 | } 1700 | return; 1701 | case 4: /* color set */ 1702 | if (narg < 3) 1703 | break; 1704 | p = strescseq.args[2]; 1705 | /* FALLTHROUGH */ 1706 | case 104: /* color reset, here p = NULL */ 1707 | j = (narg > 1) ? atoi(strescseq.args[1]) : -1; 1708 | if (xsetcolorname(j, p)) { 1709 | fprintf(stderr, "erresc: invalid color %s\n", p); 1710 | } else { 1711 | /* 1712 | * TODO if defaultbg color is changed, borders 1713 | * are dirty 1714 | */ 1715 | redraw(); 1716 | } 1717 | return; 1718 | } 1719 | break; 1720 | case 'k': /* old title set compatibility */ 1721 | xsettitle(strescseq.args[0]); 1722 | return; 1723 | case 'P': /* DCS -- Device Control String */ 1724 | term.esc |= ESC_DCS; 1725 | case '_': /* APC -- Application Program Command */ 1726 | case '^': /* PM -- Privacy Message */ 1727 | return; 1728 | } 1729 | 1730 | fprintf(stderr, "erresc: unknown str "); 1731 | strdump(); 1732 | } 1733 | 1734 | void strparse(void) { 1735 | int c; 1736 | char *p = strescseq.buf; 1737 | 1738 | strescseq.narg = 0; 1739 | strescseq.buf[strescseq.len] = '\0'; 1740 | 1741 | if (*p == '\0') 1742 | return; 1743 | 1744 | while (strescseq.narg < STR_ARG_SIZ) { 1745 | strescseq.args[strescseq.narg++] = p; 1746 | while ((c = *p) != ';' && c != '\0') 1747 | ++p; 1748 | if (c == '\0') 1749 | return; 1750 | *p++ = '\0'; 1751 | } 1752 | } 1753 | 1754 | void strdump(void) { 1755 | int i; 1756 | uint c; 1757 | 1758 | fprintf(stderr, "ESC%c", strescseq.type); 1759 | for (i = 0; i < strescseq.len; i++) { 1760 | c = strescseq.buf[i] & 0xff; 1761 | if (c == '\0') { 1762 | putc('\n', stderr); 1763 | return; 1764 | } else if (isprint(c)) { 1765 | putc(c, stderr); 1766 | } else if (c == '\n') { 1767 | fprintf(stderr, "(\\n)"); 1768 | } else if (c == '\r') { 1769 | fprintf(stderr, "(\\r)"); 1770 | } else if (c == 0x1b) { 1771 | fprintf(stderr, "(\\e)"); 1772 | } else { 1773 | fprintf(stderr, "(%02x)", c); 1774 | } 1775 | } 1776 | fprintf(stderr, "ESC\\\n"); 1777 | } 1778 | 1779 | void strreset(void) { memset(&strescseq, 0, sizeof(strescseq)); } 1780 | 1781 | void sendbreak(const Arg *arg) { 1782 | if (tcsendbreak(cmdfd, 0)) 1783 | perror("Error sending break"); 1784 | } 1785 | 1786 | void tprinter(const char *s, size_t len) { 1787 | if (iofd != -1 && xwrite(iofd, s, len) < 0) { 1788 | fprintf(stderr, "Error writing in %s:%s\n", opt_io, strerror(errno)); 1789 | close(iofd); 1790 | iofd = -1; 1791 | } 1792 | } 1793 | 1794 | void iso14755(const Arg *arg) { 1795 | unsigned long id = xwinid(); 1796 | char cmd[sizeof(ISO14755CMD) + NUMMAXLEN(id)]; 1797 | FILE *p; 1798 | char *us, *e, codepoint[9], uc[UTF_SIZ]; 1799 | unsigned long utf32; 1800 | 1801 | snprintf(cmd, sizeof(cmd), ISO14755CMD, id); 1802 | if (!(p = popen(cmd, "r"))) 1803 | return; 1804 | 1805 | us = fgets(codepoint, sizeof(codepoint), p); 1806 | pclose(p); 1807 | 1808 | if (!us || *us == '\0' || *us == '-' || strlen(us) > 7) 1809 | return; 1810 | if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX || (*e != '\n' && *e != '\0')) 1811 | return; 1812 | 1813 | ttysend(uc, utf8encode(utf32, uc)); 1814 | } 1815 | 1816 | void toggleprinter(const Arg *arg) { term.mode ^= MODE_PRINT; } 1817 | 1818 | void printscreen(const Arg *arg) { tdump(); } 1819 | 1820 | void printsel(const Arg *arg) { tdumpsel(); } 1821 | 1822 | void tdumpsel(void) { 1823 | char *ptr; 1824 | 1825 | if ((ptr = getsel())) { 1826 | tprinter(ptr, strlen(ptr)); 1827 | free(ptr); 1828 | } 1829 | } 1830 | 1831 | void tdumpline(int n) { 1832 | char buf[UTF_SIZ]; 1833 | MTGlyph *bp, *end; 1834 | 1835 | bp = &term.line[n][0]; 1836 | end = &bp[MIN(tlinelen(n), term.col) - 1]; 1837 | if (bp != end || bp->u != ' ') { 1838 | for (; bp <= end; ++bp) 1839 | tprinter(buf, utf8encode(bp->u, buf)); 1840 | } 1841 | tprinter("\n", 1); 1842 | } 1843 | 1844 | void tdump(void) { 1845 | int i; 1846 | 1847 | for (i = 0; i < term.row; ++i) 1848 | tdumpline(i); 1849 | } 1850 | 1851 | void tputtab(int n) { 1852 | uint x = term.c.x; 1853 | 1854 | if (n > 0) { 1855 | while (x < term.col && n--) 1856 | for (++x; x < term.col && !term.tabs[x]; ++x) 1857 | /* nothing */; 1858 | } else if (n < 0) { 1859 | while (x > 0 && n++) 1860 | for (--x; x > 0 && !term.tabs[x]; --x) 1861 | /* nothing */; 1862 | } 1863 | term.c.x = LIMIT(x, 0, term.col - 1); 1864 | } 1865 | 1866 | void techo(Rune u) { 1867 | if (ISCONTROL(u)) { /* control code */ 1868 | if (u & 0x80) { 1869 | u &= 0x7f; 1870 | tputc('^'); 1871 | tputc('['); 1872 | } else if (u != '\n' && u != '\r' && u != '\t') { 1873 | u ^= 0x40; 1874 | tputc('^'); 1875 | } 1876 | } 1877 | tputc(u); 1878 | } 1879 | 1880 | void tdefutf8(char ascii) { 1881 | if (ascii == 'G') 1882 | term.mode |= MODE_UTF8; 1883 | else if (ascii == '@') 1884 | term.mode &= ~MODE_UTF8; 1885 | } 1886 | 1887 | void tdeftran(char ascii) { 1888 | static char cs[] = "0B"; 1889 | static int vcs[] = {CS_GRAPHIC0, CS_USA}; 1890 | char *p; 1891 | 1892 | if ((p = strchr(cs, ascii)) == NULL) { 1893 | fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); 1894 | } else { 1895 | term.trantbl[term.icharset] = vcs[p - cs]; 1896 | } 1897 | } 1898 | 1899 | void tdectest(char c) { 1900 | int x, y; 1901 | 1902 | if (c == '8') { /* DEC screen alignment test. */ 1903 | for (x = 0; x < term.col; ++x) { 1904 | for (y = 0; y < term.row; ++y) 1905 | tsetchar('E', &term.c.attr, x, y); 1906 | } 1907 | } 1908 | } 1909 | 1910 | void tstrsequence(uchar c) { 1911 | strreset(); 1912 | 1913 | switch (c) { 1914 | case 0x90: /* DCS -- Device Control String */ 1915 | c = 'P'; 1916 | term.esc |= ESC_DCS; 1917 | break; 1918 | case 0x9f: /* APC -- Application Program Command */ 1919 | c = '_'; 1920 | break; 1921 | case 0x9e: /* PM -- Privacy Message */ 1922 | c = '^'; 1923 | break; 1924 | case 0x9d: /* OSC -- Operating System Command */ 1925 | c = ']'; 1926 | break; 1927 | } 1928 | strescseq.type = c; 1929 | term.esc |= ESC_STR; 1930 | } 1931 | 1932 | void tcontrolcode(uchar ascii) { 1933 | switch (ascii) { 1934 | case '\t': /* HT */ 1935 | tputtab(1); 1936 | return; 1937 | case '\b': /* BS */ 1938 | tmoveto(term.c.x - 1, term.c.y); 1939 | return; 1940 | case '\r': /* CR */ 1941 | tmoveto(0, term.c.y); 1942 | return; 1943 | case '\f': /* LF */ 1944 | case '\v': /* VT */ 1945 | case '\n': /* LF */ 1946 | /* go to first col if the mode is set */ 1947 | tnewline(IS_SET(MODE_CRLF)); 1948 | return; 1949 | case '\a': /* BEL */ 1950 | if (term.esc & ESC_STR_END) { 1951 | /* backwards compatibility to xterm */ 1952 | strhandle(); 1953 | } else { 1954 | if (!(win.state & WIN_FOCUSED)) 1955 | xseturgency(1); 1956 | if (bell) 1957 | xbell(); 1958 | } 1959 | break; 1960 | case '\033': /* ESC */ 1961 | csireset(); 1962 | term.esc &= ~(ESC_CSI | ESC_ALTCHARSET | ESC_TEST); 1963 | term.esc |= ESC_START; 1964 | return; 1965 | case '\016': /* SO (LS1 -- Locking shift 1) */ 1966 | case '\017': /* SI (LS0 -- Locking shift 0) */ 1967 | term.charset = 1 - (ascii - '\016'); 1968 | return; 1969 | case '\032': /* SUB */ 1970 | tsetchar('?', &term.c.attr, term.c.x, term.c.y); 1971 | case '\030': /* CAN */ 1972 | csireset(); 1973 | break; 1974 | case '\005': /* ENQ (IGNORED) */ 1975 | case '\000': /* NUL (IGNORED) */ 1976 | case '\021': /* XON (IGNORED) */ 1977 | case '\023': /* XOFF (IGNORED) */ 1978 | case 0177: /* DEL (IGNORED) */ 1979 | return; 1980 | case 0x80: /* TODO: PAD */ 1981 | case 0x81: /* TODO: HOP */ 1982 | case 0x82: /* TODO: BPH */ 1983 | case 0x83: /* TODO: NBH */ 1984 | case 0x84: /* TODO: IND */ 1985 | break; 1986 | case 0x85: /* NEL -- Next line */ 1987 | tnewline(1); /* always go to first col */ 1988 | break; 1989 | case 0x86: /* TODO: SSA */ 1990 | case 0x87: /* TODO: ESA */ 1991 | break; 1992 | case 0x88: /* HTS -- Horizontal tab stop */ 1993 | term.tabs[term.c.x] = 1; 1994 | break; 1995 | case 0x89: /* TODO: HTJ */ 1996 | case 0x8a: /* TODO: VTS */ 1997 | case 0x8b: /* TODO: PLD */ 1998 | case 0x8c: /* TODO: PLU */ 1999 | case 0x8d: /* TODO: RI */ 2000 | case 0x8e: /* TODO: SS2 */ 2001 | case 0x8f: /* TODO: SS3 */ 2002 | case 0x91: /* TODO: PU1 */ 2003 | case 0x92: /* TODO: PU2 */ 2004 | case 0x93: /* TODO: STS */ 2005 | case 0x94: /* TODO: CCH */ 2006 | case 0x95: /* TODO: MW */ 2007 | case 0x96: /* TODO: SPA */ 2008 | case 0x97: /* TODO: EPA */ 2009 | case 0x98: /* TODO: SOS */ 2010 | case 0x99: /* TODO: SGCI */ 2011 | break; 2012 | case 0x9a: /* DECID -- Identify Terminal */ 2013 | ttywrite(vt102_identify, strlen(vt102_identify)); 2014 | break; 2015 | case 0x9b: /* TODO: CSI */ 2016 | case 0x9c: /* TODO: ST */ 2017 | break; 2018 | case 0x90: /* DCS -- Device Control String */ 2019 | case 0x9d: /* OSC -- Operating System Command */ 2020 | case 0x9e: /* PM -- Privacy Message */ 2021 | case 0x9f: /* APC -- Application Program Command */ 2022 | tstrsequence(ascii); 2023 | return; 2024 | } 2025 | /* only CAN, SUB, \a and C1 chars interrupt a sequence */ 2026 | term.esc &= ~(ESC_STR_END | ESC_STR); 2027 | } 2028 | 2029 | /* 2030 | * returns 1 when the sequence is finished and it hasn't to read 2031 | * more characters for this sequence, otherwise 0 2032 | */ 2033 | int eschandle(uchar ascii) { 2034 | switch (ascii) { 2035 | case '[': 2036 | term.esc |= ESC_CSI; 2037 | return 0; 2038 | case '#': 2039 | term.esc |= ESC_TEST; 2040 | return 0; 2041 | case '%': 2042 | term.esc |= ESC_UTF8; 2043 | return 0; 2044 | case 'P': /* DCS -- Device Control String */ 2045 | case '_': /* APC -- Application Program Command */ 2046 | case '^': /* PM -- Privacy Message */ 2047 | case ']': /* OSC -- Operating System Command */ 2048 | case 'k': /* old title set compatibility */ 2049 | tstrsequence(ascii); 2050 | return 0; 2051 | case 'n': /* LS2 -- Locking shift 2 */ 2052 | case 'o': /* LS3 -- Locking shift 3 */ 2053 | term.charset = 2 + (ascii - 'n'); 2054 | break; 2055 | case '(': /* GZD4 -- set primary charset G0 */ 2056 | case ')': /* G1D4 -- set secondary charset G1 */ 2057 | case '*': /* G2D4 -- set tertiary charset G2 */ 2058 | case '+': /* G3D4 -- set quaternary charset G3 */ 2059 | term.icharset = ascii - '('; 2060 | term.esc |= ESC_ALTCHARSET; 2061 | return 0; 2062 | case 'D': /* IND -- Linefeed */ 2063 | if (term.c.y == term.bot) { 2064 | tscrollup(term.top, 1); 2065 | } else { 2066 | tmoveto(term.c.x, term.c.y + 1); 2067 | } 2068 | break; 2069 | case 'E': /* NEL -- Next line */ 2070 | tnewline(1); /* always go to first col */ 2071 | break; 2072 | case 'H': /* HTS -- Horizontal tab stop */ 2073 | term.tabs[term.c.x] = 1; 2074 | break; 2075 | case 'M': /* RI -- Reverse index */ 2076 | if (term.c.y == term.top) { 2077 | tscrolldown(term.top, 1); 2078 | } else { 2079 | tmoveto(term.c.x, term.c.y - 1); 2080 | } 2081 | break; 2082 | case 'Z': /* DECID -- Identify Terminal */ 2083 | ttywrite(vt102_identify, strlen(vt102_identify)); 2084 | break; 2085 | case 'c': /* RIS -- Reset to inital state */ 2086 | treset(); 2087 | resettitle(); 2088 | xloadcols(); 2089 | break; 2090 | case '=': /* DECPAM -- Application keypad */ 2091 | term.mode |= MODE_APPKEYPAD; 2092 | break; 2093 | case '>': /* DECPNM -- Normal keypad */ 2094 | term.mode &= ~MODE_APPKEYPAD; 2095 | break; 2096 | case '7': /* DECSC -- Save Cursor */ 2097 | tcursor(CURSOR_SAVE); 2098 | break; 2099 | case '8': /* DECRC -- Restore Cursor */ 2100 | tcursor(CURSOR_LOAD); 2101 | break; 2102 | case '\\': /* ST -- String Terminator */ 2103 | if (term.esc & ESC_STR_END) 2104 | strhandle(); 2105 | break; 2106 | default: 2107 | fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", (uchar)ascii, 2108 | isprint(ascii) ? ascii : '.'); 2109 | break; 2110 | } 2111 | return 1; 2112 | } 2113 | 2114 | void tputc(Rune u) { 2115 | char c[UTF_SIZ]; 2116 | int control; 2117 | int width, len; 2118 | MTGlyph *gp; 2119 | 2120 | control = ISCONTROL(u); 2121 | if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { 2122 | c[0] = u; 2123 | width = len = 1; 2124 | } else { 2125 | len = utf8encode(u, c); 2126 | if (!control && (width = wcwidth(u)) == -1) { 2127 | memcpy(c, "\357\277\275", 4); /* UTF_INVALID */ 2128 | width = 1; 2129 | } 2130 | } 2131 | 2132 | if (IS_SET(MODE_PRINT)) 2133 | tprinter(c, len); 2134 | 2135 | /* 2136 | * STR sequence must be checked before anything else 2137 | * because it uses all following characters until it 2138 | * receives a ESC, a SUB, a ST or any other C1 control 2139 | * character. 2140 | */ 2141 | if (term.esc & ESC_STR) { 2142 | if (u == '\a' || u == 030 || u == 032 || u == 033 || ISCONTROLC1(u)) { 2143 | term.esc &= ~(ESC_START | ESC_STR | ESC_DCS); 2144 | if (IS_SET(MODE_SIXEL)) { 2145 | /* TODO: render sixel */; 2146 | term.mode &= ~MODE_SIXEL; 2147 | return; 2148 | } 2149 | term.esc |= ESC_STR_END; 2150 | goto check_control_code; 2151 | } 2152 | 2153 | if (IS_SET(MODE_SIXEL)) { 2154 | /* TODO: implement sixel mode */ 2155 | return; 2156 | } 2157 | if (term.esc & ESC_DCS && strescseq.len == 0 && u == 'q') 2158 | term.mode |= MODE_SIXEL; 2159 | 2160 | if (strescseq.len + len >= sizeof(strescseq.buf) - 1) { 2161 | /* 2162 | * Here is a bug in terminals. If the user never sends 2163 | * some code to stop the str or esc command, then st 2164 | * will stop responding. But this is better than 2165 | * silently failing with unknown characters. At least 2166 | * then users will report back. 2167 | * 2168 | * In the case users ever get fixed, here is the code: 2169 | */ 2170 | /* 2171 | * term.esc = 0; 2172 | * strhandle(); 2173 | */ 2174 | return; 2175 | } 2176 | 2177 | memmove(&strescseq.buf[strescseq.len], c, len); 2178 | strescseq.len += len; 2179 | return; 2180 | } 2181 | 2182 | check_control_code: 2183 | /* 2184 | * Actions of control codes must be performed as soon they arrive 2185 | * because they can be embedded inside a control sequence, and 2186 | * they must not cause conflicts with sequences. 2187 | */ 2188 | if (control) { 2189 | tcontrolcode(u); 2190 | /* 2191 | * control codes are not shown ever 2192 | */ 2193 | return; 2194 | } else if (term.esc & ESC_START) { 2195 | if (term.esc & ESC_CSI) { 2196 | csiescseq.buf[csiescseq.len++] = u; 2197 | if (BETWEEN(u, 0x40, 0x7E) || 2198 | csiescseq.len >= sizeof(csiescseq.buf) - 1) { 2199 | term.esc = 0; 2200 | csiparse(); 2201 | csihandle(); 2202 | } 2203 | return; 2204 | } else if (term.esc & ESC_UTF8) { 2205 | tdefutf8(u); 2206 | } else if (term.esc & ESC_ALTCHARSET) { 2207 | tdeftran(u); 2208 | } else if (term.esc & ESC_TEST) { 2209 | tdectest(u); 2210 | } else { 2211 | if (!eschandle(u)) 2212 | return; 2213 | /* sequence already finished */ 2214 | } 2215 | term.esc = 0; 2216 | /* 2217 | * All characters which form part of a sequence are not 2218 | * printed 2219 | */ 2220 | return; 2221 | } 2222 | if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y)) 2223 | selclear(); 2224 | 2225 | gp = &term.line[term.c.y][term.c.x]; 2226 | if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { 2227 | gp->mode |= ATTR_WRAP; 2228 | tnewline(1); 2229 | gp = &term.line[term.c.y][term.c.x]; 2230 | } 2231 | 2232 | if (IS_SET(MODE_INSERT) && term.c.x + width < term.col) 2233 | memmove(gp + width, gp, (term.col - term.c.x - width) * sizeof(MTGlyph)); 2234 | 2235 | if (term.c.x + width > term.col) { 2236 | tnewline(1); 2237 | gp = &term.line[term.c.y][term.c.x]; 2238 | } 2239 | 2240 | tsetchar(u, &term.c.attr, term.c.x, term.c.y); 2241 | 2242 | if (width == 2) { 2243 | gp->mode |= ATTR_WIDE; 2244 | if (term.c.x + 1 < term.col) { 2245 | gp[1].u = '\0'; 2246 | gp[1].mode = ATTR_WDUMMY; 2247 | } 2248 | } 2249 | if (term.c.x + width < term.col) { 2250 | tmoveto(term.c.x + width, term.c.y); 2251 | } else { 2252 | term.c.state |= CURSOR_WRAPNEXT; 2253 | } 2254 | } 2255 | 2256 | void tresize(int col, int row) { 2257 | int i; 2258 | int minrow = MIN(row, term.row); 2259 | int mincol = MIN(col, term.col); 2260 | int *bp; 2261 | TCursor c; 2262 | 2263 | if (col < 1 || row < 1) { 2264 | fprintf(stderr, "tresize: error resizing to %dx%d\n", col, row); 2265 | return; 2266 | } 2267 | 2268 | /* 2269 | * slide screen to keep cursor where we expect it - 2270 | * tscrollup would work here, but we can optimize to 2271 | * memmove because we're freeing the earlier lines 2272 | */ 2273 | for (i = 0; i <= term.c.y - row; i++) { 2274 | free(term.line[i]); 2275 | free(term.alt[i]); 2276 | } 2277 | /* ensure that both src and dst are not NULL */ 2278 | if (i > 0) { 2279 | memmove(term.line, term.line + i, row * sizeof(Line)); 2280 | memmove(term.alt, term.alt + i, row * sizeof(Line)); 2281 | } 2282 | for (i += row; i < term.row; i++) { 2283 | free(term.line[i]); 2284 | free(term.alt[i]); 2285 | } 2286 | 2287 | /* resize to new width */ 2288 | term.specbuf = xrealloc(term.specbuf, col); 2289 | 2290 | /* resize to new height */ 2291 | term.line = xrealloc(term.line, row * sizeof(Line)); 2292 | term.alt = xrealloc(term.alt, row * sizeof(Line)); 2293 | term.dirty = xrealloc(term.dirty, row); 2294 | term.tabs = xrealloc(term.tabs, col); 2295 | 2296 | /* resize each row to new width, zero-pad if needed */ 2297 | for (i = 0; i < minrow; i++) { 2298 | term.line[i] = xrealloc(term.line[i], col); 2299 | term.alt[i] = xrealloc(term.alt[i], col); 2300 | } 2301 | 2302 | /* allocate any new rows */ 2303 | for (/* i = minrow */; i < row; i++) { 2304 | term.line[i] = xmalloc(col); 2305 | term.alt[i] = xmalloc(col); 2306 | } 2307 | // If the window was widened, tabstops may need to be added. 2308 | if (col > term.col) { 2309 | // Guess the width based on the first tabstop (user may have adjusted it). 2310 | int tabspaces = 8; 2311 | for (int i = 1; i < term.col; ++i) { 2312 | if (term.tabs[i]) { 2313 | tabspaces = i; 2314 | break; 2315 | } 2316 | } 2317 | 2318 | // Insert tabstops at regular intervals after the last one. 2319 | bp = term.tabs + term.col; 2320 | memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); 2321 | while (--bp > term.tabs && !*bp) 2322 | /* nothing */; 2323 | for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) 2324 | *bp = 1; 2325 | } 2326 | /* update terminal size */ 2327 | term.col = col; 2328 | term.row = row; 2329 | /* reset scrolling region */ 2330 | tsetscroll(0, row - 1); 2331 | /* make use of the LIMIT in tmoveto */ 2332 | tmoveto(term.c.x, term.c.y); 2333 | /* Clearing both screens (it makes dirty all lines) */ 2334 | c = term.c; 2335 | for (i = 0; i < 2; i++) { 2336 | if (mincol < col && 0 < minrow) { 2337 | tclearregion(mincol, 0, col - 1, minrow - 1); 2338 | } 2339 | if (0 < col && minrow < row) { 2340 | tclearregion(0, minrow, col - 1, row - 1); 2341 | } 2342 | tswapscreen(); 2343 | tcursor(CURSOR_LOAD); 2344 | } 2345 | term.c = c; 2346 | } 2347 | 2348 | void zoom(const Arg *arg) { 2349 | Arg larg = float(usedfontsize + arg->f); 2350 | zoomabs(&larg); 2351 | } 2352 | 2353 | void zoomabs(const Arg *arg) { 2354 | xunloadfonts(); 2355 | xloadfonts(usedfont, arg->f); 2356 | cresize(0, 0); 2357 | ttyresize(); 2358 | redraw(); 2359 | xhints(); 2360 | } 2361 | 2362 | void zoomreset(const Arg *arg) { 2363 | if (defaultfontsize > 0) { 2364 | Arg larg = float(defaultfontsize); 2365 | zoomabs(&larg); 2366 | } 2367 | } 2368 | 2369 | void resettitle(void) { xsettitle(opt_title ? opt_title : "mt"); } 2370 | 2371 | void redraw(void) { 2372 | tfulldirt(); 2373 | draw(); 2374 | } 2375 | 2376 | int match(uint mask, uint state) { 2377 | return mask == XK_ANY_MOD || mask == (state & ~ignoremod); 2378 | } 2379 | 2380 | void numlock(const Arg *dummy) { term.numlock ^= 1; } 2381 | 2382 | const char *kmap(KeySym k, uint state) { 2383 | Key *kp; 2384 | int i; 2385 | 2386 | /* Check for mapped keys out of X11 function keys. */ 2387 | for (i = 0; i < LEN(mappedkeys); i++) { 2388 | if (mappedkeys[i] == k) 2389 | break; 2390 | } 2391 | if (i == LEN(mappedkeys)) { 2392 | if ((k & 0xFFFF) < 0xFD00) 2393 | return NULL; 2394 | } 2395 | 2396 | for (kp = key; kp < key + LEN(key); kp++) { 2397 | if (kp->k != k) 2398 | continue; 2399 | 2400 | if (!match(kp->mask, state)) 2401 | continue; 2402 | 2403 | if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) 2404 | continue; 2405 | if (term.numlock && kp->appkey == 2) 2406 | continue; 2407 | 2408 | if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) 2409 | continue; 2410 | 2411 | if (IS_SET(MODE_CRLF) ? kp->crlf < 0 : kp->crlf > 0) 2412 | continue; 2413 | 2414 | return kp->s; 2415 | } 2416 | 2417 | return NULL; 2418 | } 2419 | 2420 | void cresize(int width, int height) { 2421 | int col, row; 2422 | 2423 | if (width != 0) 2424 | win.w = width; 2425 | if (height != 0) 2426 | win.h = height; 2427 | 2428 | col = (win.w - 2 * borderpx) / win.cw; 2429 | row = (win.h - 2 * borderpx) / win.ch; 2430 | 2431 | tresize(col, row); 2432 | xresize(col, row); 2433 | } 2434 | 2435 | void usage(void) { 2436 | die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" 2437 | " [-n name] [-o file]\n" 2438 | " [-T title] [-t title] [-w windowid]" 2439 | " [[-e] command [args ...]]\n", 2440 | argv0); 2441 | } 2442 | -------------------------------------------------------------------------------- /mt.h: -------------------------------------------------------------------------------- 1 | #ifndef MT_MT_H 2 | #define MT_MT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | extern "C" { 9 | #include 10 | } 11 | 12 | /* Arbitrary sizes */ 13 | #define UTF_SIZ 4 14 | 15 | /* macros */ 16 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 17 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 18 | #define LEN(a) (sizeof(a) / sizeof(a)[0]) 19 | #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 20 | #define DIVCEIL(n, d) (((n) + ((d)-1)) / (d)) 21 | #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 22 | #define ATTRCMP(a, b) \ 23 | ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg) 24 | #define IS_SET(flag) ((term.mode & (flag)) != 0) 25 | #define TIMEDIFF(t1, t2) \ 26 | ((t1.tv_sec - t2.tv_sec) * 1000 + (t1.tv_nsec - t2.tv_nsec) / 1E6) 27 | #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) 28 | 29 | #define TRUECOLOR(r, g, b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) 30 | #define IS_TRUECOL(x) (1 << 24 & (x)) 31 | 32 | enum glyph_attribute { 33 | ATTR_NULL = 0, 34 | ATTR_BOLD = 1 << 0, 35 | ATTR_FAINT = 1 << 1, 36 | ATTR_ITALIC = 1 << 2, 37 | ATTR_UNDERLINE = 1 << 3, 38 | ATTR_BLINK = 1 << 4, 39 | ATTR_REVERSE = 1 << 5, 40 | ATTR_INVISIBLE = 1 << 6, 41 | ATTR_STRUCK = 1 << 7, 42 | ATTR_WRAP = 1 << 8, 43 | ATTR_WIDE = 1 << 9, 44 | ATTR_WDUMMY = 1 << 10, 45 | ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 46 | }; 47 | 48 | enum term_mode { 49 | MODE_WRAP = 1 << 0, 50 | MODE_INSERT = 1 << 1, 51 | MODE_APPKEYPAD = 1 << 2, 52 | MODE_ALTSCREEN = 1 << 3, 53 | MODE_CRLF = 1 << 4, 54 | MODE_MOUSEBTN = 1 << 5, 55 | MODE_MOUSEMOTION = 1 << 6, 56 | MODE_REVERSE = 1 << 7, 57 | MODE_KBDLOCK = 1 << 8, 58 | MODE_HIDE = 1 << 9, 59 | MODE_ECHO = 1 << 10, 60 | MODE_APPCURSOR = 1 << 11, 61 | MODE_MOUSESGR = 1 << 12, 62 | MODE_8BIT = 1 << 13, 63 | MODE_BLINK = 1 << 14, 64 | MODE_FBLINK = 1 << 15, 65 | MODE_FOCUS = 1 << 16, 66 | MODE_MOUSEX10 = 1 << 17, 67 | MODE_MOUSEMANY = 1 << 18, 68 | MODE_BRCKTPASTE = 1 << 19, 69 | MODE_PRINT = 1 << 20, 70 | MODE_UTF8 = 1 << 21, 71 | MODE_SIXEL = 1 << 22, 72 | MODE_MOUSE = MODE_MOUSEBTN | MODE_MOUSEMOTION | MODE_MOUSEX10 | 73 | MODE_MOUSEMANY, 74 | }; 75 | 76 | enum selection_mode { SEL_IDLE = 0, SEL_EMPTY = 1, SEL_READY = 2 }; 77 | 78 | enum selection_type { SEL_REGULAR = 1, SEL_RECTANGULAR = 2 }; 79 | 80 | enum selection_snap { SNAP_WORD = 1, SNAP_LINE = 2 }; 81 | 82 | enum window_state { WIN_VISIBLE = 1, WIN_FOCUSED = 2 }; 83 | 84 | typedef unsigned char uchar; 85 | typedef unsigned int uint; 86 | typedef unsigned long ulong; 87 | typedef unsigned short ushort; 88 | 89 | typedef uint_least32_t Rune; 90 | 91 | typedef struct { 92 | Rune u; /* character code */ 93 | ushort mode; /* attribute flags */ 94 | uint32_t fg; /* foreground */ 95 | uint32_t bg; /* background */ 96 | } MTGlyph; 97 | 98 | typedef MTGlyph *Line; 99 | 100 | typedef struct { 101 | MTGlyph attr; /* current char attributes */ 102 | int x; 103 | int y; 104 | char state; 105 | } TCursor; 106 | 107 | /* Internal representation of the screen */ 108 | typedef struct { 109 | int row; /* nb row */ 110 | int col; /* nb col */ 111 | Line *line; /* screen */ 112 | Line *alt; /* alternate screen */ 113 | int *dirty; /* dirtyness of lines */ 114 | XftGlyphFontSpec *specbuf; /* font spec buffer used for rendering */ 115 | TCursor c; /* cursor */ 116 | int top; /* top scroll limit */ 117 | int bot; /* bottom scroll limit */ 118 | int mode; /* terminal mode flags */ 119 | int esc; /* escape state flags */ 120 | char trantbl[4]; /* charset table translation */ 121 | int charset; /* current charset */ 122 | int icharset; /* selected charset for sequence */ 123 | int numlock; /* lock numbers in keyboard */ 124 | int *tabs; 125 | } Term; 126 | 127 | /* Purely graphic info */ 128 | typedef struct { 129 | int tw, th; /* tty width and height */ 130 | int w, h; /* window width and height */ 131 | int ch; /* char height */ 132 | int cw; /* char width */ 133 | char state; /* focus, redraw, visible */ 134 | int cursor; /* cursor style */ 135 | } TermWindow; 136 | 137 | typedef struct { 138 | uint b; 139 | uint mask; 140 | const char *s; 141 | } MouseShortcut; 142 | 143 | typedef struct { 144 | int mode; 145 | int type; 146 | int snap; 147 | /* 148 | * Selection variables: 149 | * nb – normalized coordinates of the beginning of the selection 150 | * ne – normalized coordinates of the end of the selection 151 | * ob – original coordinates of the beginning of the selection 152 | * oe – original coordinates of the end of the selection 153 | */ 154 | struct { 155 | int x, y; 156 | } nb, ne, ob, oe; 157 | 158 | char *primary, *clipboard; 159 | int alt; 160 | struct timespec tclick1; 161 | struct timespec tclick2; 162 | 163 | // Atom xtarget; 164 | } Selection; 165 | 166 | union Arg { 167 | Arg(int val) { i = val; } 168 | Arg(uint val) { i = val; } 169 | Arg(float val) { f = val; } 170 | Arg(const void *val) { v = val; } 171 | 172 | int i; 173 | uint ui; 174 | float f; 175 | const void *v; 176 | }; 177 | 178 | typedef struct { 179 | uint mod; 180 | KeySym keysym; 181 | void (*func)(const Arg *); 182 | const Arg arg; 183 | } Shortcut; 184 | 185 | void die(const char *, ...); 186 | void redraw(void); 187 | 188 | int tattrset(int); 189 | void tnew(int, int); 190 | void tsetdirt(int, int); 191 | void tsetdirtattr(int); 192 | int match(uint, uint); 193 | void ttynew(void); 194 | size_t ttyread(void); 195 | void ttyresize(void); 196 | void ttysend(const char *, size_t); 197 | void ttywrite(const char *, size_t); 198 | 199 | void resettitle(void); 200 | 201 | const char *kmap(KeySym, uint); 202 | void cresize(int, int); 203 | void selclear(void); 204 | 205 | void selinit(void); 206 | void selnormalize(void); 207 | int selected(int, int); 208 | char *getsel(void); 209 | int x2col(int); 210 | int y2row(int); 211 | 212 | size_t utf8decode(const char *, Rune *, size_t); 213 | size_t utf8encode(Rune, char *); 214 | 215 | char *xstrdup(char *); 216 | 217 | void usage(void); 218 | 219 | /* Globals */ 220 | extern TermWindow win; 221 | extern Term term; 222 | extern Selection sel; 223 | extern int cmdfd; 224 | extern pid_t pid; 225 | extern char **opt_cmd; 226 | extern char *opt_class; 227 | extern char *opt_embed; 228 | extern char *opt_font; 229 | extern char *opt_io; 230 | extern char *opt_name; 231 | extern char *opt_title; 232 | extern int oldbutton; 233 | 234 | extern char *usedfont; 235 | extern double usedfontsize; 236 | extern double defaultfontsize; 237 | 238 | /* config.h globals */ 239 | extern char font[]; 240 | extern int borderpx; 241 | extern float cwscale; 242 | extern float chscale; 243 | extern unsigned int doubleclicktimeout; 244 | extern unsigned int tripleclicktimeout; 245 | extern int allowaltscreen; 246 | extern unsigned int xfps; 247 | extern unsigned int actionfps; 248 | extern unsigned int cursorthickness; 249 | extern unsigned int blinktimeout; 250 | extern char termname[]; 251 | extern const char *colorname[]; 252 | extern size_t colornamelen; 253 | extern unsigned int defaultfg; 254 | extern unsigned int defaultbg; 255 | extern unsigned int defaultcs; 256 | extern unsigned int defaultrcs; 257 | extern unsigned int cursorshape; 258 | extern unsigned int cols; 259 | extern unsigned int rows; 260 | extern unsigned int mouseshape; 261 | extern unsigned int mousefg; 262 | extern unsigned int mousebg; 263 | extern unsigned int defaultattr; 264 | extern MouseShortcut mshortcuts[]; 265 | extern size_t mshortcutslen; 266 | extern Shortcut shortcuts[]; 267 | extern size_t shortcutslen; 268 | extern uint forceselmod; 269 | extern uint selmasks[]; 270 | extern size_t selmaskslen; 271 | 272 | #endif 273 | -------------------------------------------------------------------------------- /x.cc: -------------------------------------------------------------------------------- 1 | #include "x.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | extern "C" { 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | } 24 | 25 | #include "arg.h" 26 | #include "mt.h" 27 | 28 | /* XEMBED messages */ 29 | #define XEMBED_FOCUS_IN 4 30 | #define XEMBED_FOCUS_OUT 5 31 | 32 | /* macros */ 33 | #define TRUERED(x) (((x)&0xff0000) >> 8) 34 | #define TRUEGREEN(x) (((x)&0xff00)) 35 | #define TRUEBLUE(x) (((x)&0xff) << 8) 36 | 37 | typedef XftDraw *Draw; 38 | typedef XftColor Color; 39 | 40 | /* Purely graphic info */ 41 | typedef struct { 42 | Display *dpy; 43 | Colormap cmap; 44 | Window win; 45 | Drawable buf; 46 | Atom xembed, wmdeletewin, netwmname, netwmpid; 47 | XIM xim; 48 | XIC xic; 49 | Draw draw; 50 | Visual *vis; 51 | XSetWindowAttributes attrs; 52 | int scr; 53 | int isfixed; /* is fixed geometry? */ 54 | int l, t; /* left and top offset */ 55 | int gm; /* geometry mask */ 56 | } XWindow; 57 | 58 | typedef struct { Atom xtarget; } XSelection; 59 | 60 | /* MTFont structure */ 61 | typedef struct { 62 | int height; 63 | int width; 64 | int ascent; 65 | int descent; 66 | int badslant; 67 | int badweight; 68 | short lbearing; 69 | short rbearing; 70 | XftFont *match; 71 | FcFontSet *set; 72 | FcPattern *pattern; 73 | } MTFont; 74 | 75 | /* Drawing Context */ 76 | typedef struct { 77 | Color *col; 78 | size_t collen; 79 | MTFont font, bfont, ifont, ibfont; 80 | GC gc; 81 | } DC; 82 | 83 | static inline ushort sixd_to_16bit(int); 84 | static int xmakeglyphfontspecs(XftGlyphFontSpec *, const MTGlyph *, int, int, 85 | int); 86 | static void xdrawglyphfontspecs(const XftGlyphFontSpec *, MTGlyph, int, int, 87 | int); 88 | static void xdrawglyph(MTGlyph, int, int); 89 | static void xclear(int, int, int, int); 90 | static void xdrawcursor(void); 91 | static int xgeommasktogravity(int); 92 | static int xloadfont(MTFont *, FcPattern *); 93 | static void xunloadfont(MTFont *); 94 | 95 | static void expose(XEvent *); 96 | static void visibility(XEvent *); 97 | static void unmap(XEvent *); 98 | static void kpress(XEvent *); 99 | static void cmessage(XEvent *); 100 | static void resize(XEvent *); 101 | static void focus(XEvent *); 102 | static void brelease(XEvent *); 103 | static void bpress(XEvent *); 104 | static void bmotion(XEvent *); 105 | static void propnotify(XEvent *); 106 | static void selnotify(XEvent *); 107 | static void selclear_(XEvent *); 108 | static void selrequest(XEvent *); 109 | 110 | static void selcopy(Time); 111 | static void getbuttoninfo(XEvent *); 112 | static void mousereport(XEvent *); 113 | 114 | void handle(XEvent *ev) { 115 | switch (ev->type) { 116 | case KeyPress: 117 | return kpress(ev); 118 | case ClientMessage: 119 | return cmessage(ev); 120 | case ConfigureNotify: 121 | return resize(ev); 122 | case VisibilityNotify: 123 | return visibility(ev); 124 | case UnmapNotify: 125 | return unmap(ev); 126 | case Expose: 127 | return expose(ev); 128 | case FocusIn: 129 | return focus(ev); 130 | case FocusOut: 131 | return focus(ev); 132 | case MotionNotify: 133 | return bmotion(ev); 134 | case ButtonPress: 135 | return bpress(ev); 136 | case ButtonRelease: 137 | return brelease(ev); 138 | // Uncomment if you want the selection to disappear when you select 139 | // something different in another window. 140 | // case SelectionClear: return selclear_(ev); 141 | case SelectionNotify: 142 | return selnotify(ev); 143 | // PropertyNotify is only turned on when there is some INCR transfer 144 | // happening for the selection retrieval. 145 | case PropertyNotify: 146 | return propnotify(ev); 147 | case SelectionRequest: 148 | return selrequest(ev); 149 | } 150 | } 151 | 152 | /* Globals */ 153 | static DC dc; 154 | static XWindow xw; 155 | static XSelection xsel; 156 | 157 | /* MTFont Ring Cache */ 158 | enum { FRC_NORMAL, FRC_ITALIC, FRC_BOLD, FRC_ITALICBOLD }; 159 | 160 | typedef struct { 161 | XftFont *font; 162 | int flags; 163 | Rune unicodep; 164 | } Fontcache; 165 | 166 | /* Fontcache is an array now. A new font will be appended to the array. */ 167 | static Fontcache frc[16]; 168 | static int frclen = 0; 169 | 170 | void getbuttoninfo(XEvent *e) { 171 | int type; 172 | uint state = e->xbutton.state & ~(Button1Mask | forceselmod); 173 | 174 | sel.alt = IS_SET(MODE_ALTSCREEN); 175 | 176 | sel.oe.x = x2col(e->xbutton.x); 177 | sel.oe.y = y2row(e->xbutton.y); 178 | selnormalize(); 179 | 180 | sel.type = SEL_REGULAR; 181 | for (type = 1; type < selmaskslen; ++type) { 182 | if (match(selmasks[type], state)) { 183 | sel.type = type; 184 | break; 185 | } 186 | } 187 | } 188 | 189 | void mousereport(XEvent *e) { 190 | int x = x2col(e->xbutton.x), y = y2row(e->xbutton.y), 191 | button = e->xbutton.button, state = e->xbutton.state, len; 192 | char buf[40]; 193 | static int ox, oy; 194 | 195 | /* from urxvt */ 196 | if (e->xbutton.type == MotionNotify) { 197 | if (x == ox && y == oy) 198 | return; 199 | if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) 200 | return; 201 | /* MOUSE_MOTION: no reporting if no button is pressed */ 202 | if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) 203 | return; 204 | 205 | button = oldbutton + 32; 206 | ox = x; 207 | oy = y; 208 | } else { 209 | if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { 210 | button = 3; 211 | } else { 212 | button -= Button1; 213 | if (button >= 3) 214 | button += 64 - 3; 215 | } 216 | if (e->xbutton.type == ButtonPress) { 217 | oldbutton = button; 218 | ox = x; 219 | oy = y; 220 | } else if (e->xbutton.type == ButtonRelease) { 221 | oldbutton = 3; 222 | /* MODE_MOUSEX10: no button release reporting */ 223 | if (IS_SET(MODE_MOUSEX10)) 224 | return; 225 | if (button == 64 || button == 65) 226 | return; 227 | } 228 | } 229 | 230 | if (!IS_SET(MODE_MOUSEX10)) { 231 | button += ((state & ShiftMask) ? 4 : 0) + ((state & Mod4Mask) ? 8 : 0) + 232 | ((state & ControlMask) ? 16 : 0); 233 | } 234 | 235 | if (IS_SET(MODE_MOUSESGR)) { 236 | len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", button, x + 1, y + 1, 237 | e->xbutton.type == ButtonRelease ? 'm' : 'M'); 238 | } else if (x < 223 && y < 223) { 239 | len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", 32 + button, 32 + x + 1, 240 | 32 + y + 1); 241 | } else { 242 | return; 243 | } 244 | 245 | ttywrite(buf, len); 246 | } 247 | 248 | void bpress(XEvent *e) { 249 | struct timespec now; 250 | MouseShortcut *ms; 251 | 252 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { 253 | mousereport(e); 254 | return; 255 | } 256 | 257 | for (ms = mshortcuts; ms < mshortcuts + mshortcutslen; ms++) { 258 | if (e->xbutton.button == ms->b && match(ms->mask, e->xbutton.state)) { 259 | ttysend(ms->s, strlen(ms->s)); 260 | return; 261 | } 262 | } 263 | 264 | if (e->xbutton.button == Button1) { 265 | clock_gettime(CLOCK_MONOTONIC, &now); 266 | 267 | /* Clear previous selection, logically and visually. */ 268 | selclear_(NULL); 269 | sel.mode = SEL_EMPTY; 270 | sel.type = SEL_REGULAR; 271 | sel.oe.x = sel.ob.x = x2col(e->xbutton.x); 272 | sel.oe.y = sel.ob.y = y2row(e->xbutton.y); 273 | 274 | /* 275 | * If the user clicks below predefined timeouts specific 276 | * snapping behaviour is exposed. 277 | */ 278 | if (TIMEDIFF(now, sel.tclick2) <= tripleclicktimeout) { 279 | sel.snap = SNAP_LINE; 280 | } else if (TIMEDIFF(now, sel.tclick1) <= doubleclicktimeout) { 281 | sel.snap = SNAP_WORD; 282 | } else { 283 | sel.snap = 0; 284 | } 285 | selnormalize(); 286 | 287 | if (sel.snap != 0) 288 | sel.mode = SEL_READY; 289 | tsetdirt(sel.nb.y, sel.ne.y); 290 | sel.tclick2 = sel.tclick1; 291 | sel.tclick1 = now; 292 | } 293 | } 294 | 295 | void selcopy(Time t) { xsetsel(getsel(), t); } 296 | 297 | void propnotify(XEvent *e) { 298 | XPropertyEvent *xpev; 299 | Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 300 | 301 | xpev = &e->xproperty; 302 | if (xpev->state == PropertyNewValue && 303 | (xpev->atom == XA_PRIMARY || xpev->atom == clipboard)) { 304 | selnotify(e); 305 | } 306 | } 307 | 308 | void selnotify(XEvent *e) { 309 | ulong nitems, ofs, rem; 310 | int format; 311 | uchar *data, *last; 312 | Atom type, incratom, property; 313 | 314 | incratom = XInternAtom(xw.dpy, "INCR", 0); 315 | 316 | ofs = 0; 317 | if (e->type == SelectionNotify) { 318 | property = e->xselection.property; 319 | } else if (e->type == PropertyNotify) { 320 | property = e->xproperty.atom; 321 | } else { 322 | return; 323 | } 324 | if (property == None) 325 | return; 326 | 327 | do { 328 | if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, BUFSIZ / 4, False, 329 | AnyPropertyType, &type, &format, &nitems, &rem, 330 | &data)) { 331 | fprintf(stderr, "Clipboard allocation failed\n"); 332 | return; 333 | } 334 | 335 | if (e->type == PropertyNotify && nitems == 0 && rem == 0) { 336 | /* 337 | * If there is some PropertyNotify with no data, then 338 | * this is the signal of the selection owner that all 339 | * data has been transferred. We won't need to receive 340 | * PropertyNotify events anymore. 341 | */ 342 | MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); 343 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); 344 | } 345 | 346 | if (type == incratom) { 347 | /* 348 | * Activate the PropertyNotify events so we receive 349 | * when the selection owner does send us the next 350 | * chunk of data. 351 | */ 352 | MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); 353 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); 354 | 355 | /* 356 | * Deleting the property is the transfer start signal. 357 | */ 358 | XDeleteProperty(xw.dpy, xw.win, (int)property); 359 | continue; 360 | } 361 | 362 | /* 363 | * As seen in getsel: 364 | * Line endings are inconsistent in the terminal and GUI world 365 | * copy and pasting. When receiving some selection data, 366 | * replace all '\n' with '\r'. 367 | * FIXME: Fix the computer world. 368 | */ 369 | last = data + nitems * format / 8; 370 | std::replace(data, last, '\n', '\r'); 371 | 372 | if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) 373 | ttywrite("\033[200~", 6); 374 | ttysend((char *)data, nitems * format / 8); 375 | if (IS_SET(MODE_BRCKTPASTE) && rem == 0) 376 | ttywrite("\033[201~", 6); 377 | XFree(data); 378 | /* number of 32-bit chunks returned */ 379 | ofs += nitems * format / 32; 380 | } while (rem > 0); 381 | 382 | /* 383 | * Deleting the property again tells the selection owner to send the 384 | * next data chunk in the property. 385 | */ 386 | XDeleteProperty(xw.dpy, xw.win, (int)property); 387 | } 388 | 389 | void xselpaste(void) { 390 | XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, xw.win, 391 | CurrentTime); 392 | } 393 | 394 | void xclipcopy(void) { 395 | Atom clipboard; 396 | 397 | if (sel.clipboard != NULL) 398 | free(sel.clipboard); 399 | 400 | if (sel.primary != NULL) { 401 | sel.clipboard = xstrdup(sel.primary); 402 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 403 | XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); 404 | } 405 | } 406 | 407 | void xclippaste(void) { 408 | Atom clipboard; 409 | 410 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 411 | XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, xw.win, 412 | CurrentTime); 413 | } 414 | 415 | void selclear_(XEvent *e) { selclear(); } 416 | 417 | void selrequest(XEvent *e) { 418 | XSelectionRequestEvent *xsre; 419 | XSelectionEvent xev; 420 | Atom xa_targets, string, clipboard; 421 | char *seltext; 422 | 423 | xsre = (XSelectionRequestEvent *)e; 424 | xev.type = SelectionNotify; 425 | xev.requestor = xsre->requestor; 426 | xev.selection = xsre->selection; 427 | xev.target = xsre->target; 428 | xev.time = xsre->time; 429 | if (xsre->property == None) 430 | xsre->property = xsre->target; 431 | 432 | /* reject */ 433 | xev.property = None; 434 | 435 | xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); 436 | if (xsre->target == xa_targets) { 437 | /* respond with the supported type */ 438 | string = xsel.xtarget; 439 | XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, 440 | PropModeReplace, (uchar *)&string, 1); 441 | xev.property = xsre->property; 442 | } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { 443 | /* 444 | * xith XA_STRING non ascii characters may be incorrect in the 445 | * requestor. It is not our problem, use utf8. 446 | */ 447 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 448 | if (xsre->selection == XA_PRIMARY) { 449 | seltext = sel.primary; 450 | } else if (xsre->selection == clipboard) { 451 | seltext = sel.clipboard; 452 | } else { 453 | fprintf(stderr, "Unhandled clipboard selection 0x%lx\n", xsre->selection); 454 | return; 455 | } 456 | if (seltext != NULL) { 457 | XChangeProperty(xsre->display, xsre->requestor, xsre->property, 458 | xsre->target, 8, PropModeReplace, (uchar *)seltext, 459 | strlen(seltext)); 460 | xev.property = xsre->property; 461 | } 462 | } 463 | 464 | /* all done, send a notification to the listener */ 465 | if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *)&xev)) 466 | fprintf(stderr, "Error sending SelectionNotify event\n"); 467 | } 468 | 469 | void xsetsel(char *str, Time t) { 470 | free(sel.primary); 471 | sel.primary = str; 472 | 473 | XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); 474 | if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) 475 | selclear_(NULL); 476 | } 477 | 478 | void brelease(XEvent *e) { 479 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { 480 | mousereport(e); 481 | return; 482 | } 483 | 484 | if (e->xbutton.button == Button2) { 485 | xselpaste(); 486 | } else if (e->xbutton.button == Button1) { 487 | if (sel.mode == SEL_READY) { 488 | getbuttoninfo(e); 489 | selcopy(e->xbutton.time); 490 | } else 491 | selclear_(NULL); 492 | sel.mode = SEL_IDLE; 493 | tsetdirt(sel.nb.y, sel.ne.y); 494 | } 495 | } 496 | 497 | void bmotion(XEvent *e) { 498 | int oldey, oldex, oldsby, oldsey; 499 | 500 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { 501 | mousereport(e); 502 | return; 503 | } 504 | 505 | if (!sel.mode) 506 | return; 507 | 508 | sel.mode = SEL_READY; 509 | oldey = sel.oe.y; 510 | oldex = sel.oe.x; 511 | oldsby = sel.nb.y; 512 | oldsey = sel.ne.y; 513 | getbuttoninfo(e); 514 | 515 | if (oldey != sel.oe.y || oldex != sel.oe.x) 516 | tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 517 | } 518 | 519 | void xresize(int col, int row) { 520 | win.tw = MAX(1, col * win.cw); 521 | win.th = MAX(1, row * win.ch); 522 | 523 | XFreePixmap(xw.dpy, xw.buf); 524 | xw.buf = 525 | XCreatePixmap(xw.dpy, xw.win, win.w, win.h, DefaultDepth(xw.dpy, xw.scr)); 526 | XftDrawChange(xw.draw, xw.buf); 527 | xclear(0, 0, win.w, win.h); 528 | } 529 | 530 | ushort sixd_to_16bit(int x) { return x == 0 ? 0 : 0x3737 + 0x2828 * x; } 531 | 532 | int xloadcolor(int i, const char *name, Color *ncolor) { 533 | XRenderColor color = {}; 534 | color.alpha = 0xffff; 535 | 536 | if (!name) { 537 | if (BETWEEN(i, 16, 255)) { /* 256 color */ 538 | if (i < 6 * 6 * 6 + 16) { /* same colors as xterm */ 539 | color.red = sixd_to_16bit(((i - 16) / 36) % 6); 540 | color.green = sixd_to_16bit(((i - 16) / 6) % 6); 541 | color.blue = sixd_to_16bit(((i - 16) / 1) % 6); 542 | } else { /* greyscale */ 543 | color.red = 0x0808 + 0x0a0a * (i - (6 * 6 * 6 + 16)); 544 | color.green = color.blue = color.red; 545 | } 546 | return XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, ncolor); 547 | } else 548 | name = colorname[i]; 549 | } 550 | 551 | return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); 552 | } 553 | 554 | void xloadcols(void) { 555 | int i; 556 | static int loaded; 557 | Color *cp; 558 | 559 | dc.collen = MAX(colornamelen, 256); 560 | dc.col = static_cast(malloc(dc.collen * sizeof(Color))); 561 | if (!dc.col) 562 | die("Out of memory\n"); 563 | 564 | if (loaded) { 565 | for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) 566 | XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); 567 | } 568 | 569 | for (i = 0; i < dc.collen; i++) 570 | if (!xloadcolor(i, NULL, &dc.col[i])) { 571 | if (colorname[i]) 572 | die("Could not allocate color '%s'\n", colorname[i]); 573 | else 574 | die("Could not allocate color %d\n", i); 575 | } 576 | loaded = 1; 577 | } 578 | 579 | int xsetcolorname(int x, const char *name) { 580 | Color ncolor; 581 | 582 | if (!BETWEEN(x, 0, dc.collen)) 583 | return 1; 584 | 585 | if (!xloadcolor(x, name, &ncolor)) 586 | return 1; 587 | 588 | XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); 589 | dc.col[x] = ncolor; 590 | 591 | return 0; 592 | } 593 | 594 | /* 595 | * Absolute coordinates. 596 | */ 597 | void xclear(int x1, int y1, int x2, int y2) { 598 | XftDrawRect(xw.draw, &dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg], 599 | x1, y1, x2 - x1, y2 - y1); 600 | } 601 | 602 | void xhints(void) { 603 | XClassHint xclass = {opt_name ? opt_name : termname, 604 | opt_class ? opt_class : termname}; 605 | XWMHints wm = {}; 606 | wm.flags = InputHint; 607 | wm.input = 1; 608 | XSizeHints *sizeh = NULL; 609 | 610 | sizeh = XAllocSizeHints(); 611 | 612 | sizeh->flags = PSize | PResizeInc | PBaseSize; 613 | sizeh->height = win.h; 614 | sizeh->width = win.w; 615 | sizeh->height_inc = win.ch; 616 | sizeh->width_inc = win.cw; 617 | sizeh->base_height = 2 * borderpx; 618 | sizeh->base_width = 2 * borderpx; 619 | if (xw.isfixed) { 620 | sizeh->flags |= PMaxSize | PMinSize; 621 | sizeh->min_width = sizeh->max_width = win.w; 622 | sizeh->min_height = sizeh->max_height = win.h; 623 | } 624 | if (xw.gm & (XValue | YValue)) { 625 | sizeh->flags |= USPosition | PWinGravity; 626 | sizeh->x = xw.l; 627 | sizeh->y = xw.t; 628 | sizeh->win_gravity = xgeommasktogravity(xw.gm); 629 | } 630 | 631 | XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, &xclass); 632 | XFree(sizeh); 633 | } 634 | 635 | int xgeommasktogravity(int mask) { 636 | switch (mask & (XNegative | YNegative)) { 637 | case 0: 638 | return NorthWestGravity; 639 | case XNegative: 640 | return NorthEastGravity; 641 | case YNegative: 642 | return SouthWestGravity; 643 | } 644 | 645 | return SouthEastGravity; 646 | } 647 | 648 | int xloadfont(MTFont *f, FcPattern *pattern) { 649 | FcPattern *configured; 650 | FcPattern *match; 651 | FcResult result; 652 | XGlyphInfo extents; 653 | int wantattr, haveattr; 654 | 655 | /* 656 | * Manually configure instead of calling XftMatchFont 657 | * so that we can use the configured pattern for 658 | * "missing glyph" lookups. 659 | */ 660 | configured = FcPatternDuplicate(pattern); 661 | if (!configured) 662 | return 1; 663 | 664 | FcConfigSubstitute(NULL, configured, FcMatchPattern); 665 | XftDefaultSubstitute(xw.dpy, xw.scr, configured); 666 | 667 | match = FcFontMatch(NULL, configured, &result); 668 | if (!match) { 669 | FcPatternDestroy(configured); 670 | return 1; 671 | } 672 | 673 | if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { 674 | FcPatternDestroy(configured); 675 | FcPatternDestroy(match); 676 | return 1; 677 | } 678 | 679 | if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == 680 | XftResultMatch)) { 681 | /* 682 | * Check if xft was unable to find a font with the appropriate 683 | * slant but gave us one anyway. Try to mitigate. 684 | */ 685 | if ((XftPatternGetInteger(f->match->pattern, "slant", 0, &haveattr) != 686 | XftResultMatch) || 687 | haveattr < wantattr) { 688 | f->badslant = 1; 689 | fputs("mt: font slant does not match\n", stderr); 690 | } 691 | } 692 | 693 | if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == 694 | XftResultMatch)) { 695 | if ((XftPatternGetInteger(f->match->pattern, "weight", 0, &haveattr) != 696 | XftResultMatch) || 697 | haveattr != wantattr) { 698 | f->badweight = 1; 699 | fputs("mt: font weight does not match\n", stderr); 700 | } 701 | } 702 | 703 | // We take font width as the average of these characters. 704 | static char ascii_printable[] = 705 | " !\"#$%&'()*+,-./0123456789:;<=>?" 706 | "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 707 | "`abcdefghijklmnopqrstuvwxyz{|}~"; 708 | XftTextExtentsUtf8(xw.dpy, f->match, (const FcChar8 *)ascii_printable, 709 | strlen(ascii_printable), &extents); 710 | 711 | f->set = NULL; 712 | f->pattern = configured; 713 | 714 | f->ascent = f->match->ascent; 715 | f->descent = f->match->descent; 716 | f->lbearing = 0; 717 | f->rbearing = f->match->max_advance_width; 718 | 719 | f->height = f->ascent + f->descent; 720 | f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); 721 | 722 | return 0; 723 | } 724 | 725 | void xloadfonts(const char *fontstr, double fontsize) { 726 | FcPattern *pattern; 727 | double fontval; 728 | 729 | if (fontstr[0] == '-') { 730 | pattern = XftXlfdParse(fontstr, False, False); 731 | } else { 732 | pattern = FcNameParse((FcChar8 *)fontstr); 733 | } 734 | 735 | if (!pattern) 736 | die("mt: can't open font %s\n", fontstr); 737 | 738 | if (fontsize > 1) { 739 | FcPatternDel(pattern, FC_PIXEL_SIZE); 740 | FcPatternDel(pattern, FC_SIZE); 741 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); 742 | usedfontsize = fontsize; 743 | } else { 744 | if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == 745 | FcResultMatch) { 746 | usedfontsize = fontval; 747 | } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == 748 | FcResultMatch) { 749 | usedfontsize = -1; 750 | } else { 751 | /* 752 | * Default font size is 12, if none given. This is to 753 | * have a known usedfontsize value. 754 | */ 755 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); 756 | usedfontsize = 12; 757 | } 758 | defaultfontsize = usedfontsize; 759 | } 760 | 761 | if (xloadfont(&dc.font, pattern)) 762 | die("mt: can't open font %s\n", fontstr); 763 | 764 | if (usedfontsize < 0) { 765 | FcPatternGetDouble(dc.font.match->pattern, FC_PIXEL_SIZE, 0, &fontval); 766 | usedfontsize = fontval; 767 | if (fontsize == 0) 768 | defaultfontsize = fontval; 769 | } 770 | 771 | /* Setting character width and height. */ 772 | win.cw = ceilf(dc.font.width * cwscale); 773 | win.ch = ceilf(dc.font.height * chscale); 774 | 775 | FcPatternDel(pattern, FC_SLANT); 776 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 777 | if (xloadfont(&dc.ifont, pattern)) 778 | die("mt: can't open font %s\n", fontstr); 779 | 780 | FcPatternDel(pattern, FC_WEIGHT); 781 | FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 782 | if (xloadfont(&dc.ibfont, pattern)) 783 | die("mt: can't open font %s\n", fontstr); 784 | 785 | FcPatternDel(pattern, FC_SLANT); 786 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); 787 | if (xloadfont(&dc.bfont, pattern)) 788 | die("mt: can't open font %s\n", fontstr); 789 | 790 | FcPatternDestroy(pattern); 791 | } 792 | 793 | void xunloadfont(MTFont *f) { 794 | XftFontClose(xw.dpy, f->match); 795 | FcPatternDestroy(f->pattern); 796 | if (f->set) 797 | FcFontSetDestroy(f->set); 798 | } 799 | 800 | void xunloadfonts(void) { 801 | /* Free the loaded fonts in the font cache. */ 802 | while (frclen > 0) 803 | XftFontClose(xw.dpy, frc[--frclen].font); 804 | 805 | xunloadfont(&dc.font); 806 | xunloadfont(&dc.bfont); 807 | xunloadfont(&dc.ifont); 808 | xunloadfont(&dc.ibfont); 809 | } 810 | 811 | void xinit(void) { 812 | XGCValues gcvalues; 813 | Cursor cursor; 814 | Window parent; 815 | pid_t thispid = getpid(); 816 | XColor xmousefg, xmousebg; 817 | 818 | if (!(xw.dpy = XOpenDisplay(NULL))) 819 | die("Can't open display\n"); 820 | xw.scr = XDefaultScreen(xw.dpy); 821 | xw.vis = XDefaultVisual(xw.dpy, xw.scr); 822 | 823 | /* font */ 824 | if (!FcInit()) 825 | die("Could not init fontconfig.\n"); 826 | 827 | usedfont = (opt_font == NULL) ? font : opt_font; 828 | xloadfonts(usedfont, 0); 829 | 830 | /* colors */ 831 | xw.cmap = XDefaultColormap(xw.dpy, xw.scr); 832 | xloadcols(); 833 | 834 | /* adjust fixed window geometry */ 835 | win.w = 2 * borderpx + term.col * win.cw; 836 | win.h = 2 * borderpx + term.row * win.ch; 837 | if (xw.gm & XNegative) 838 | xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; 839 | if (xw.gm & YNegative) 840 | xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; 841 | 842 | /* Events */ 843 | xw.attrs.background_pixel = dc.col[defaultbg].pixel; 844 | xw.attrs.border_pixel = dc.col[defaultbg].pixel; 845 | xw.attrs.bit_gravity = NorthWestGravity; 846 | xw.attrs.event_mask = FocusChangeMask | KeyPressMask | ExposureMask | 847 | VisibilityChangeMask | StructureNotifyMask | 848 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; 849 | xw.attrs.colormap = xw.cmap; 850 | 851 | if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) 852 | parent = XRootWindow(xw.dpy, xw.scr); 853 | xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, win.w, win.h, 0, 854 | XDefaultDepth(xw.dpy, xw.scr), InputOutput, xw.vis, 855 | CWBackPixel | CWBorderPixel | CWBitGravity | 856 | CWEventMask | CWColormap, 857 | &xw.attrs); 858 | 859 | memset(&gcvalues, 0, sizeof(gcvalues)); 860 | gcvalues.graphics_exposures = False; 861 | dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, &gcvalues); 862 | xw.buf = 863 | XCreatePixmap(xw.dpy, xw.win, win.w, win.h, DefaultDepth(xw.dpy, xw.scr)); 864 | XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 865 | XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); 866 | 867 | /* Xft rendering context */ 868 | xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); 869 | 870 | /* input methods */ 871 | if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { 872 | XSetLocaleModifiers("@im=local"); 873 | if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { 874 | XSetLocaleModifiers("@im="); 875 | if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { 876 | die("XOpenIM failed. Could not open input" 877 | " device.\n"); 878 | } 879 | } 880 | } 881 | xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 882 | XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL); 883 | if (xw.xic == NULL) 884 | die("XCreateIC failed. Could not obtain input method.\n"); 885 | 886 | /* white cursor, black outline */ 887 | cursor = XCreateFontCursor(xw.dpy, mouseshape); 888 | XDefineCursor(xw.dpy, xw.win, cursor); 889 | 890 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { 891 | xmousefg.red = 0xffff; 892 | xmousefg.green = 0xffff; 893 | xmousefg.blue = 0xffff; 894 | } 895 | 896 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { 897 | xmousebg.red = 0x0000; 898 | xmousebg.green = 0x0000; 899 | xmousebg.blue = 0x0000; 900 | } 901 | 902 | XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); 903 | 904 | xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); 905 | xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); 906 | xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); 907 | XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); 908 | 909 | xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); 910 | XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, PropModeReplace, 911 | (uchar *)&thispid, 1); 912 | 913 | resettitle(); 914 | XMapWindow(xw.dpy, xw.win); 915 | xhints(); 916 | XSync(xw.dpy, False); 917 | 918 | xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); 919 | if (xsel.xtarget == None) 920 | xsel.xtarget = XA_STRING; 921 | } 922 | 923 | int xmakeglyphfontspecs(XftGlyphFontSpec *specs, const MTGlyph *glyphs, int len, 924 | int x, int y) { 925 | float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; 926 | ushort mode, prevmode = USHRT_MAX; 927 | MTFont *font = &dc.font; 928 | int frcflags = FRC_NORMAL; 929 | float runewidth = win.cw; 930 | Rune rune; 931 | FT_UInt glyphidx; 932 | FcResult fcres; 933 | FcPattern *fcpattern, *fontpattern; 934 | FcFontSet *fcsets[] = {NULL}; 935 | FcCharSet *fccharset; 936 | int i, f, numspecs = 0; 937 | 938 | for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { 939 | /* Fetch rune and mode for current glyph. */ 940 | rune = glyphs[i].u; 941 | mode = glyphs[i].mode; 942 | 943 | /* Skip dummy wide-character spacing. */ 944 | if (mode == ATTR_WDUMMY) 945 | continue; 946 | 947 | /* Determine font for glyph if different from previous glyph. */ 948 | if (prevmode != mode) { 949 | prevmode = mode; 950 | font = &dc.font; 951 | frcflags = FRC_NORMAL; 952 | runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); 953 | if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { 954 | font = &dc.ibfont; 955 | frcflags = FRC_ITALICBOLD; 956 | } else if (mode & ATTR_ITALIC) { 957 | font = &dc.ifont; 958 | frcflags = FRC_ITALIC; 959 | } else if (mode & ATTR_BOLD) { 960 | font = &dc.bfont; 961 | frcflags = FRC_BOLD; 962 | } 963 | yp = winy + font->ascent; 964 | } 965 | 966 | /* Lookup character index with default font. */ 967 | glyphidx = XftCharIndex(xw.dpy, font->match, rune); 968 | if (glyphidx) { 969 | specs[numspecs].font = font->match; 970 | specs[numspecs].glyph = glyphidx; 971 | specs[numspecs].x = (short)xp; 972 | specs[numspecs].y = (short)yp; 973 | xp += runewidth; 974 | numspecs++; 975 | continue; 976 | } 977 | 978 | /* Fallback on font cache, search the font cache for match. */ 979 | for (f = 0; f < frclen; f++) { 980 | glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); 981 | /* Everything correct. */ 982 | if (glyphidx && frc[f].flags == frcflags) 983 | break; 984 | /* We got a default font for a not found glyph. */ 985 | if (!glyphidx && frc[f].flags == frcflags && frc[f].unicodep == rune) { 986 | break; 987 | } 988 | } 989 | 990 | /* Nothing was found. Use fontconfig to find matching font. */ 991 | if (f >= frclen) { 992 | if (!font->set) 993 | font->set = FcFontSort(0, font->pattern, 1, 0, &fcres); 994 | fcsets[0] = font->set; 995 | 996 | /* 997 | * Nothing was found in the cache. Now use 998 | * some dozen of Fontconfig calls to get the 999 | * font for one single character. 1000 | * 1001 | * Xft and fontconfig are design failures. 1002 | */ 1003 | fcpattern = FcPatternDuplicate(font->pattern); 1004 | fccharset = FcCharSetCreate(); 1005 | 1006 | FcCharSetAddChar(fccharset, rune); 1007 | FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); 1008 | FcPatternAddBool(fcpattern, FC_SCALABLE, 1); 1009 | 1010 | FcConfigSubstitute(0, fcpattern, FcMatchPattern); 1011 | FcDefaultSubstitute(fcpattern); 1012 | 1013 | fontpattern = FcFontSetMatch(0, fcsets, 1, fcpattern, &fcres); 1014 | 1015 | /* 1016 | * Overwrite or create the new cache entry. 1017 | */ 1018 | if (frclen >= LEN(frc)) { 1019 | frclen = LEN(frc) - 1; 1020 | XftFontClose(xw.dpy, frc[frclen].font); 1021 | frc[frclen].unicodep = 0; 1022 | } 1023 | 1024 | frc[frclen].font = XftFontOpenPattern(xw.dpy, fontpattern); 1025 | frc[frclen].flags = frcflags; 1026 | frc[frclen].unicodep = rune; 1027 | 1028 | glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); 1029 | 1030 | f = frclen; 1031 | frclen++; 1032 | 1033 | FcPatternDestroy(fcpattern); 1034 | FcCharSetDestroy(fccharset); 1035 | } 1036 | 1037 | specs[numspecs].font = frc[f].font; 1038 | specs[numspecs].glyph = glyphidx; 1039 | specs[numspecs].x = (short)xp; 1040 | specs[numspecs].y = (short)yp; 1041 | xp += runewidth; 1042 | numspecs++; 1043 | } 1044 | 1045 | return numspecs; 1046 | } 1047 | 1048 | void xdrawglyphfontspecs(const XftGlyphFontSpec *specs, MTGlyph base, int len, 1049 | int x, int y) { 1050 | int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); 1051 | int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, 1052 | width = charlen * win.cw; 1053 | Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; 1054 | XRenderColor colfg, colbg; 1055 | XRectangle r; 1056 | 1057 | /* Fallback on color display for attributes not supported by the font */ 1058 | if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { 1059 | if (dc.ibfont.badslant || dc.ibfont.badweight) 1060 | base.fg = defaultattr; 1061 | } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || 1062 | (base.mode & ATTR_BOLD && dc.bfont.badweight)) { 1063 | base.fg = defaultattr; 1064 | } 1065 | 1066 | if (IS_TRUECOL(base.fg)) { 1067 | colfg.alpha = 0xffff; 1068 | colfg.red = TRUERED(base.fg); 1069 | colfg.green = TRUEGREEN(base.fg); 1070 | colfg.blue = TRUEBLUE(base.fg); 1071 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); 1072 | fg = &truefg; 1073 | } else { 1074 | fg = &dc.col[base.fg]; 1075 | } 1076 | 1077 | if (IS_TRUECOL(base.bg)) { 1078 | colbg.alpha = 0xffff; 1079 | colbg.green = TRUEGREEN(base.bg); 1080 | colbg.red = TRUERED(base.bg); 1081 | colbg.blue = TRUEBLUE(base.bg); 1082 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); 1083 | bg = &truebg; 1084 | } else { 1085 | bg = &dc.col[base.bg]; 1086 | } 1087 | 1088 | /* Change basic system colors [0-7] to bright system colors [8-15] */ 1089 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) 1090 | fg = &dc.col[base.fg + 8]; 1091 | 1092 | if (IS_SET(MODE_REVERSE)) { 1093 | if (fg == &dc.col[defaultfg]) { 1094 | fg = &dc.col[defaultbg]; 1095 | } else { 1096 | colfg.red = ~fg->color.red; 1097 | colfg.green = ~fg->color.green; 1098 | colfg.blue = ~fg->color.blue; 1099 | colfg.alpha = fg->color.alpha; 1100 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); 1101 | fg = &revfg; 1102 | } 1103 | 1104 | if (bg == &dc.col[defaultbg]) { 1105 | bg = &dc.col[defaultfg]; 1106 | } else { 1107 | colbg.red = ~bg->color.red; 1108 | colbg.green = ~bg->color.green; 1109 | colbg.blue = ~bg->color.blue; 1110 | colbg.alpha = bg->color.alpha; 1111 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &revbg); 1112 | bg = &revbg; 1113 | } 1114 | } 1115 | 1116 | if (base.mode & ATTR_REVERSE) { 1117 | temp = fg; 1118 | fg = bg; 1119 | bg = temp; 1120 | } 1121 | 1122 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { 1123 | colfg.red = fg->color.red / 2; 1124 | colfg.green = fg->color.green / 2; 1125 | colfg.blue = fg->color.blue / 2; 1126 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); 1127 | fg = &revfg; 1128 | } 1129 | 1130 | if (base.mode & ATTR_BLINK && term.mode & MODE_BLINK) 1131 | fg = bg; 1132 | 1133 | if (base.mode & ATTR_INVISIBLE) 1134 | fg = bg; 1135 | 1136 | /* Intelligent cleaning up of the borders. */ 1137 | if (x == 0) { 1138 | xclear(0, (y == 0) ? 0 : winy, borderpx, 1139 | winy + win.ch + ((y >= term.row - 1) ? win.h : 0)); 1140 | } 1141 | if (x + charlen >= term.col) { 1142 | xclear(winx + width, (y == 0) ? 0 : winy, win.w, 1143 | ((y >= term.row - 1) ? win.h : (winy + win.ch))); 1144 | } 1145 | if (y == 0) 1146 | xclear(winx, 0, winx + width, borderpx); 1147 | if (y == term.row - 1) 1148 | xclear(winx, winy + win.ch, winx + width, win.h); 1149 | 1150 | /* Clean up the region we want to draw to. */ 1151 | XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); 1152 | 1153 | /* Set the clip region because Xft is sometimes dirty. */ 1154 | r.x = 0; 1155 | r.y = 0; 1156 | r.height = win.ch; 1157 | r.width = width; 1158 | XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); 1159 | 1160 | /* Render the glyphs. */ 1161 | XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 1162 | 1163 | /* Render underline and strikethrough. */ 1164 | if (base.mode & ATTR_UNDERLINE) { 1165 | XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1); 1166 | } 1167 | 1168 | if (base.mode & ATTR_STRUCK) { 1169 | XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, width, 1); 1170 | } 1171 | 1172 | /* Reset clip to none. */ 1173 | XftDrawSetClip(xw.draw, 0); 1174 | } 1175 | 1176 | void xdrawglyph(MTGlyph g, int x, int y) { 1177 | int numspecs; 1178 | XftGlyphFontSpec spec; 1179 | 1180 | numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); 1181 | xdrawglyphfontspecs(&spec, g, numspecs, x, y); 1182 | } 1183 | 1184 | void xdrawcursor(void) { 1185 | static int oldx = 0, oldy = 0; 1186 | int curx; 1187 | MTGlyph g = {' ', ATTR_NULL, defaultbg, defaultcs}, og; 1188 | int ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN); 1189 | Color drawcol; 1190 | 1191 | LIMIT(oldx, 0, term.col - 1); 1192 | LIMIT(oldy, 0, term.row - 1); 1193 | 1194 | curx = term.c.x; 1195 | 1196 | /* adjust position if in dummy */ 1197 | if (term.line[oldy][oldx].mode & ATTR_WDUMMY) 1198 | oldx--; 1199 | if (term.line[term.c.y][curx].mode & ATTR_WDUMMY) 1200 | curx--; 1201 | 1202 | /* remove the old cursor */ 1203 | og = term.line[oldy][oldx]; 1204 | if (ena_sel && selected(oldx, oldy)) 1205 | og.mode ^= ATTR_REVERSE; 1206 | xdrawglyph(og, oldx, oldy); 1207 | 1208 | g.u = term.line[term.c.y][term.c.x].u; 1209 | g.mode |= term.line[term.c.y][term.c.x].mode & 1210 | (ATTR_BOLD | ATTR_ITALIC | ATTR_UNDERLINE | ATTR_STRUCK); 1211 | 1212 | /* 1213 | * Select the right color for the right mode. 1214 | */ 1215 | if (IS_SET(MODE_REVERSE)) { 1216 | g.mode |= ATTR_REVERSE; 1217 | g.bg = defaultfg; 1218 | if (ena_sel && selected(term.c.x, term.c.y)) { 1219 | drawcol = dc.col[defaultcs]; 1220 | g.fg = defaultrcs; 1221 | } else { 1222 | drawcol = dc.col[defaultrcs]; 1223 | g.fg = defaultcs; 1224 | } 1225 | } else { 1226 | if (ena_sel && selected(term.c.x, term.c.y)) { 1227 | drawcol = dc.col[defaultrcs]; 1228 | g.fg = defaultfg; 1229 | g.bg = defaultrcs; 1230 | } else { 1231 | drawcol = dc.col[defaultcs]; 1232 | } 1233 | } 1234 | 1235 | if (IS_SET(MODE_HIDE)) 1236 | return; 1237 | 1238 | /* draw the new one */ 1239 | if (win.state & WIN_FOCUSED) { 1240 | switch (win.cursor) { 1241 | case 7: /* mt extension: snowman */ 1242 | utf8decode("☃", &g.u, UTF_SIZ); 1243 | case 0: /* Blinking Block */ 1244 | case 1: /* Blinking Block (Default) */ 1245 | case 2: /* Steady Block */ 1246 | g.mode |= term.line[term.c.y][curx].mode & ATTR_WIDE; 1247 | xdrawglyph(g, term.c.x, term.c.y); 1248 | break; 1249 | case 3: /* Blinking Underline */ 1250 | case 4: /* Steady Underline */ 1251 | XftDrawRect(xw.draw, &drawcol, borderpx + curx * win.cw, 1252 | borderpx + (term.c.y + 1) * win.ch - cursorthickness, win.cw, 1253 | cursorthickness); 1254 | break; 1255 | case 5: /* Blinking bar */ 1256 | case 6: /* Steady bar */ 1257 | XftDrawRect(xw.draw, &drawcol, borderpx + curx * win.cw, 1258 | borderpx + term.c.y * win.ch, cursorthickness, win.ch); 1259 | break; 1260 | } 1261 | } else { 1262 | XftDrawRect(xw.draw, &drawcol, borderpx + curx * win.cw, 1263 | borderpx + term.c.y * win.ch, win.cw - 1, 1); 1264 | XftDrawRect(xw.draw, &drawcol, borderpx + curx * win.cw, 1265 | borderpx + term.c.y * win.ch, 1, win.ch - 1); 1266 | XftDrawRect(xw.draw, &drawcol, borderpx + (curx + 1) * win.cw - 1, 1267 | borderpx + term.c.y * win.ch, 1, win.ch - 1); 1268 | XftDrawRect(xw.draw, &drawcol, borderpx + curx * win.cw, 1269 | borderpx + (term.c.y + 1) * win.ch - 1, win.cw, 1); 1270 | } 1271 | oldx = curx, oldy = term.c.y; 1272 | } 1273 | 1274 | void xsetenv(void) { 1275 | char buf[sizeof(long) * 8 + 1]; 1276 | 1277 | snprintf(buf, sizeof(buf), "%lu", xw.win); 1278 | setenv("WINDOWID", buf, 1); 1279 | } 1280 | 1281 | void xsettitle(const char *p) { 1282 | XTextProperty prop; 1283 | 1284 | // This function only reads p, but doesn't declare it const... 1285 | Xutf8TextListToTextProperty(xw.dpy, const_cast(&p), 1, 1286 | XUTF8StringStyle, &prop); 1287 | XSetWMName(xw.dpy, xw.win, &prop); 1288 | XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); 1289 | XFree(prop.value); 1290 | } 1291 | 1292 | void draw(void) { 1293 | drawregion(0, 0, term.col, term.row); 1294 | XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, win.h, 0, 0); 1295 | XSetForeground(xw.dpy, dc.gc, 1296 | dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg].pixel); 1297 | } 1298 | 1299 | void drawregion(int x1, int y1, int x2, int y2) { 1300 | int i, x, y, ox, numspecs; 1301 | MTGlyph base, changed; 1302 | XftGlyphFontSpec *specs; 1303 | int ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN); 1304 | 1305 | if (!(win.state & WIN_VISIBLE)) 1306 | return; 1307 | 1308 | for (y = y1; y < y2; y++) { 1309 | if (!term.dirty[y]) 1310 | continue; 1311 | 1312 | term.dirty[y] = 0; 1313 | 1314 | specs = term.specbuf; 1315 | numspecs = xmakeglyphfontspecs(specs, &term.line[y][x1], x2 - x1, x1, y); 1316 | 1317 | i = ox = 0; 1318 | for (x = x1; x < x2 && i < numspecs; x++) { 1319 | changed = term.line[y][x]; 1320 | if (changed.mode == ATTR_WDUMMY) 1321 | continue; 1322 | if (ena_sel && selected(x, y)) 1323 | changed.mode ^= ATTR_REVERSE; 1324 | if (i > 0 && ATTRCMP(base, changed)) { 1325 | xdrawglyphfontspecs(specs, base, i, ox, y); 1326 | specs += i; 1327 | numspecs -= i; 1328 | i = 0; 1329 | } 1330 | if (i == 0) { 1331 | ox = x; 1332 | base = changed; 1333 | } 1334 | i++; 1335 | } 1336 | if (i > 0) 1337 | xdrawglyphfontspecs(specs, base, i, ox, y); 1338 | } 1339 | xdrawcursor(); 1340 | } 1341 | 1342 | void expose(XEvent *ev) { redraw(); } 1343 | 1344 | void visibility(XEvent *ev) { 1345 | XVisibilityEvent *e = &ev->xvisibility; 1346 | 1347 | MODBIT(win.state, e->state != VisibilityFullyObscured, WIN_VISIBLE); 1348 | } 1349 | 1350 | void unmap(XEvent *ev) { win.state &= ~WIN_VISIBLE; } 1351 | 1352 | void xsetpointermotion(int set) { 1353 | MODBIT(xw.attrs.event_mask, set, PointerMotionMask); 1354 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); 1355 | } 1356 | 1357 | void xseturgency(int add) { 1358 | XWMHints *h = XGetWMHints(xw.dpy, xw.win); 1359 | 1360 | MODBIT(h->flags, add, XUrgencyHint); 1361 | XSetWMHints(xw.dpy, xw.win, h); 1362 | XFree(h); 1363 | } 1364 | 1365 | void xbell(void) { XkbBell(xw.dpy, xw.win, 0, (Atom)NULL); } 1366 | 1367 | unsigned long xwinid(void) { return xw.win; } 1368 | 1369 | void focus(XEvent *ev) { 1370 | XFocusChangeEvent *e = &ev->xfocus; 1371 | 1372 | if (e->mode == NotifyGrab) 1373 | return; 1374 | 1375 | if (ev->type == FocusIn) { 1376 | XSetICFocus(xw.xic); 1377 | win.state |= WIN_FOCUSED; 1378 | xseturgency(0); 1379 | if (IS_SET(MODE_FOCUS)) 1380 | ttywrite("\033[I", 3); 1381 | } else { 1382 | XUnsetICFocus(xw.xic); 1383 | win.state &= ~WIN_FOCUSED; 1384 | if (IS_SET(MODE_FOCUS)) 1385 | ttywrite("\033[O", 3); 1386 | } 1387 | } 1388 | 1389 | void kpress(XEvent *ev) { 1390 | XKeyEvent *e = &ev->xkey; 1391 | KeySym ksym; 1392 | char buf[32]; 1393 | const char *customkey; 1394 | int len; 1395 | Rune c; 1396 | Status status; 1397 | Shortcut *bp; 1398 | 1399 | if (IS_SET(MODE_KBDLOCK)) 1400 | return; 1401 | 1402 | len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status); 1403 | /* 1. shortcuts */ 1404 | for (bp = shortcuts; bp < shortcuts + shortcutslen; bp++) { 1405 | if (ksym == bp->keysym && match(bp->mod, e->state)) { 1406 | bp->func(&(bp->arg)); 1407 | return; 1408 | } 1409 | } 1410 | 1411 | /* 2. custom keys from config.h */ 1412 | if ((customkey = kmap(ksym, e->state))) { 1413 | ttysend(customkey, strlen(customkey)); 1414 | return; 1415 | } 1416 | 1417 | /* 3. composed string from input method */ 1418 | if (len == 0) 1419 | return; 1420 | if (len == 1 && e->state & Mod1Mask) { 1421 | if (IS_SET(MODE_8BIT)) { 1422 | if (*buf < 0177) { 1423 | c = *buf | 0x80; 1424 | len = utf8encode(c, buf); 1425 | } 1426 | } else { 1427 | buf[1] = buf[0]; 1428 | buf[0] = '\033'; 1429 | len = 2; 1430 | } 1431 | } 1432 | ttysend(buf, len); 1433 | } 1434 | 1435 | void cmessage(XEvent *e) { 1436 | /* 1437 | * See xembed specs 1438 | * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html 1439 | */ 1440 | if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { 1441 | if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { 1442 | win.state |= WIN_FOCUSED; 1443 | xseturgency(0); 1444 | } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { 1445 | win.state &= ~WIN_FOCUSED; 1446 | } 1447 | } else if (e->xclient.data.l[0] == xw.wmdeletewin) { 1448 | /* Send SIGHUP to shell */ 1449 | kill(pid, SIGHUP); 1450 | exit(0); 1451 | } 1452 | } 1453 | 1454 | void resize(XEvent *e) { 1455 | if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) 1456 | return; 1457 | 1458 | cresize(e->xconfigure.width, e->xconfigure.height); 1459 | ttyresize(); 1460 | } 1461 | 1462 | void run(void) { 1463 | XEvent ev; 1464 | int w = win.w, h = win.h; 1465 | fd_set rfd; 1466 | int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0; 1467 | struct timespec drawtimeout, *tv = NULL, now, last, lastblink; 1468 | long deltatime; 1469 | 1470 | /* Waiting for window mapping */ 1471 | do { 1472 | XNextEvent(xw.dpy, &ev); 1473 | /* 1474 | * This XFilterEvent call is required because of XOpenIM. It 1475 | * does filter out the key event and some client message for 1476 | * the input method too. 1477 | */ 1478 | if (XFilterEvent(&ev, None)) 1479 | continue; 1480 | if (ev.type == ConfigureNotify) { 1481 | w = ev.xconfigure.width; 1482 | h = ev.xconfigure.height; 1483 | } 1484 | } while (ev.type != MapNotify); 1485 | 1486 | cresize(w, h); 1487 | ttynew(); 1488 | ttyresize(); 1489 | 1490 | clock_gettime(CLOCK_MONOTONIC, &last); 1491 | lastblink = last; 1492 | 1493 | for (xev = actionfps;;) { 1494 | FD_ZERO(&rfd); 1495 | FD_SET(cmdfd, &rfd); 1496 | FD_SET(xfd, &rfd); 1497 | 1498 | if (pselect(MAX(xfd, cmdfd) + 1, &rfd, NULL, NULL, tv, NULL) < 0) { 1499 | if (errno == EINTR) 1500 | continue; 1501 | die("select failed: %s\n", strerror(errno)); 1502 | } 1503 | if (FD_ISSET(cmdfd, &rfd)) { 1504 | ttyread(); 1505 | if (blinktimeout) { 1506 | blinkset = tattrset(ATTR_BLINK); 1507 | if (!blinkset) 1508 | MODBIT(term.mode, 0, MODE_BLINK); 1509 | } 1510 | } 1511 | 1512 | if (FD_ISSET(xfd, &rfd)) 1513 | xev = actionfps; 1514 | 1515 | clock_gettime(CLOCK_MONOTONIC, &now); 1516 | drawtimeout.tv_sec = 0; 1517 | drawtimeout.tv_nsec = (1000 * 1E6) / xfps; 1518 | tv = &drawtimeout; 1519 | 1520 | dodraw = 0; 1521 | if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { 1522 | tsetdirtattr(ATTR_BLINK); 1523 | term.mode ^= MODE_BLINK; 1524 | lastblink = now; 1525 | dodraw = 1; 1526 | } 1527 | deltatime = TIMEDIFF(now, last); 1528 | if (deltatime > 1000 / (xev ? xfps : actionfps)) { 1529 | dodraw = 1; 1530 | last = now; 1531 | } 1532 | 1533 | if (dodraw) { 1534 | while (XPending(xw.dpy)) { 1535 | XNextEvent(xw.dpy, &ev); 1536 | if (XFilterEvent(&ev, None)) 1537 | continue; 1538 | handle(&ev); 1539 | } 1540 | 1541 | draw(); 1542 | XFlush(xw.dpy); 1543 | 1544 | if (xev && !FD_ISSET(xfd, &rfd)) 1545 | xev--; 1546 | if (!FD_ISSET(cmdfd, &rfd) && !FD_ISSET(xfd, &rfd)) { 1547 | if (blinkset) { 1548 | if (TIMEDIFF(now, lastblink) > blinktimeout) { 1549 | drawtimeout.tv_nsec = 1000; 1550 | } else { 1551 | drawtimeout.tv_nsec = 1552 | (1E6 * (blinktimeout - TIMEDIFF(now, lastblink))); 1553 | } 1554 | drawtimeout.tv_sec = drawtimeout.tv_nsec / 1E9; 1555 | drawtimeout.tv_nsec %= (long)1E9; 1556 | } else { 1557 | tv = NULL; 1558 | } 1559 | } 1560 | } 1561 | } 1562 | } 1563 | 1564 | int main(int argc, char *argv[]) { 1565 | xw.l = xw.t = 0; 1566 | xw.isfixed = False; 1567 | win.cursor = cursorshape; 1568 | 1569 | ARGBEGIN { 1570 | case 'a': 1571 | allowaltscreen = 0; 1572 | break; 1573 | case 'c': 1574 | opt_class = EARGF(usage()); 1575 | break; 1576 | case 'e': 1577 | if (argc > 0) 1578 | --argc, ++argv; 1579 | goto run; 1580 | case 'f': 1581 | opt_font = EARGF(usage()); 1582 | break; 1583 | case 'g': 1584 | xw.gm = XParseGeometry(EARGF(usage()), &xw.l, &xw.t, &cols, &rows); 1585 | break; 1586 | case 'i': 1587 | xw.isfixed = 1; 1588 | break; 1589 | case 'o': 1590 | opt_io = EARGF(usage()); 1591 | break; 1592 | case 'n': 1593 | opt_name = EARGF(usage()); 1594 | break; 1595 | case 't': 1596 | case 'T': 1597 | opt_title = EARGF(usage()); 1598 | break; 1599 | case 'w': 1600 | opt_embed = EARGF(usage()); 1601 | break; 1602 | case 'v': 1603 | die("%s " VERSION "\n", argv0); 1604 | break; 1605 | default: 1606 | usage(); 1607 | } 1608 | ARGEND; 1609 | 1610 | run: 1611 | if (argc > 0) { 1612 | /* eat all remaining arguments */ 1613 | opt_cmd = argv; 1614 | if (!opt_title) 1615 | opt_title = basename(xstrdup(argv[0])); 1616 | } 1617 | setlocale(LC_CTYPE, ""); 1618 | XSetLocaleModifiers(""); 1619 | tnew(MAX(cols, 1), MAX(rows, 1)); 1620 | xinit(); 1621 | selinit(); 1622 | run(); 1623 | 1624 | return 0; 1625 | } 1626 | -------------------------------------------------------------------------------- /x.h: -------------------------------------------------------------------------------- 1 | #ifndef MT_X_H 2 | #define MT_X_H 3 | 4 | extern "C" { 5 | #include 6 | } 7 | 8 | /* X modifiers */ 9 | #define XK_ANY_MOD UINT_MAX 10 | #define XK_NO_MOD 0 11 | #define XK_SWITCH_MOD (1 << 13) 12 | 13 | void draw(void); 14 | void drawregion(int, int, int, int); 15 | void run(void); 16 | 17 | void xbell(void); 18 | void xclipcopy(void); 19 | void xclippaste(void); 20 | void xhints(void); 21 | void xinit(void); 22 | void xloadcols(void); 23 | int xsetcolorname(int, const char *); 24 | void xloadfonts(const char *, double); 25 | void xsetenv(void); 26 | void xsettitle(const char *); 27 | void xsetpointermotion(int); 28 | void xseturgency(int); 29 | void xunloadfonts(void); 30 | void xresize(int, int); 31 | void xselpaste(void); 32 | unsigned long xwinid(void); 33 | void xsetsel(char *, Time); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /xterm-emulation-terminfo.info: -------------------------------------------------------------------------------- 1 | # We store the expected terminfo behavior of 'mt' in the terminfo format for 2 | # easy comparison with whatever terminfo is available on a particular system 3 | # for xterm (which 'mt' emulates). Each of the entries is annotated to help 4 | # clarify its origins when it isn't common between xterm and 'mt'. The entries 5 | # marked 'st' come from the original 'st' terminfo. The entries marked 'xterm' 6 | # match what is in its terminfo. There are also a few commented out duplicates 7 | # where there are different values found in the 'xterm', 'st', and 'st-meta' 8 | # terminfo. All of these need to be carefully evaluated and resolved to either 9 | # not be provided if really unnecessary when emulating xterm, be supported by 10 | # the implementation of 'mt' if they come from 'xterm' and haven't been 11 | # incorporated, or have the discrepancy between them resolved. 12 | # 13 | # These are terminfo definitions that can be used if your system doesn't have 14 | # one, or if the one on your system doesn't support the desired features. 15 | # 16 | # Note that the 'mt' terminal purports to be compatible with 'xterm' and uses 17 | # that name in its 'TERM' environment variable. As a consequence, the name here 18 | # matches that one. If you actually use 'xterm' you may not want to replace 19 | # your terminfo with this. 20 | xterm-256color| Generic terminfo settings that are largely xterm compatible. 21 | Ms=\E]52;%p1%s;%p2%s\007, # st 22 | Se, # st 23 | Ss, # st 24 | Tc, # st 25 | # xterm: acsc=``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 26 | acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 27 | am, 28 | bce, 29 | bel=^G, 30 | blink=\E[5m, 31 | bold=\E[1m, 32 | cbt=\E[Z, 33 | ccc, # xterm 34 | civis=\E[?25l, 35 | clear=\E[H\E[2J, 36 | cnorm=\E[?12l\E[?25h, 37 | colors#256, 38 | cols#80, 39 | cr=^M, 40 | csr=\E[%i%p1%d;%p2%dr, 41 | cub1=^H, 42 | cub=\E[%p1%dD, 43 | cud1=^J, 44 | cud=\E[%p1%dB, 45 | cuf1=\E[C, 46 | cuf=\E[%p1%dC, 47 | cup=\E[%i%p1%d;%p2%dH, 48 | cuu1=\E[A, 49 | cuu=\E[%p1%dA, 50 | # xterm: cvvis=\E[?12;25h, 51 | cvvis=\E[?25h, 52 | dch1=\E[P, 53 | dch=\E[%p1%dP, 54 | dl1=\E[M, 55 | dl=\E[%p1%dM, 56 | ech=\E[%p1%dX, 57 | ed=\E[J, 58 | el1=\E[1K, 59 | el=\E[K, 60 | enacs=\E)0, # st 61 | # xterm flash=\E[?5h$<100/>\E[?5l, 62 | flash=\E[?5h$<80/>\E[?5l, 63 | fsl=^G, # st 64 | home=\E[H, 65 | hpa=\E[%i%p1%dG, 66 | hs, # st 67 | ht=^I, 68 | hts=\EH, 69 | ich=\E[%p1%d@, 70 | il1=\E[L, 71 | il=\E[%p1%dL, 72 | ind=^J, 73 | indn=\E[%p1%dS, 74 | initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, # xterm 75 | invis=\E[8m, 76 | # xterm: is2=\E[!p\E[?3;4l\E[4l\E>, 77 | # st-meta: is2=\E[4l\E>\E[?1034h, 78 | is2=\E[4l\E>\E[?1034l, 79 | it#8, 80 | kDC=\E[3;2~, 81 | kEND=\E[1;2F, 82 | kHOM=\E[1;2H, 83 | kIC=\E[2;2~, 84 | kLFT=\E[1;2D, 85 | kNXT=\E[6;2~, 86 | kPRV=\E[5;2~, 87 | kRIT=\E[1;2C, 88 | ka1=\E[1~, # st 89 | ka3=\E[5~, # st 90 | # xterm: kb2=\EOE, 91 | kb2=\EOu, 92 | # xterm: kbs=^H 93 | # Most linux distributions change this to kbs=\177. 94 | # On Mac, this is ^H but terminals ignore it and send \177. 95 | kbs=\177, 96 | kc1=\E[4~, # st 97 | kc3=\E[6~, # st 98 | kcbt=\E[Z, 99 | kclr=\E[3;5~, # st 100 | kcub1=\EOD, 101 | kcud1=\EOB, 102 | kcuf1=\EOC, 103 | kcuu1=\EOA, 104 | kdch1=\E[3~, 105 | kdl1=\E[3;2~, # st 106 | ked=\E[1;5F, # st 107 | kel=\E[1;2F, # st 108 | kend=\EOF, 109 | kent=\EOM, 110 | kf10=\E[21~, 111 | kf11=\E[23~, 112 | kf12=\E[24~, 113 | kf13=\E[1;2P, 114 | kf14=\E[1;2Q, 115 | kf15=\E[1;2R, 116 | kf16=\E[1;2S, 117 | kf17=\E[15;2~, 118 | kf18=\E[17;2~, 119 | kf19=\E[18;2~, 120 | kf1=\EOP, 121 | kf20=\E[19;2~, 122 | kf21=\E[20;2~, 123 | kf22=\E[21;2~, 124 | kf23=\E[23;2~, 125 | kf24=\E[24;2~, 126 | kf25=\E[1;5P, 127 | kf26=\E[1;5Q, 128 | kf27=\E[1;5R, 129 | kf28=\E[1;5S, 130 | kf29=\E[15;5~, 131 | kf2=\EOQ, 132 | kf30=\E[17;5~, 133 | kf31=\E[18;5~, 134 | kf32=\E[19;5~, 135 | kf33=\E[20;5~, 136 | kf34=\E[21;5~, 137 | kf35=\E[23;5~, 138 | kf36=\E[24;5~, 139 | kf37=\E[1;6P, 140 | kf38=\E[1;6Q, 141 | kf39=\E[1;6R, 142 | kf3=\EOR, 143 | kf40=\E[1;6S, 144 | kf41=\E[15;6~, 145 | kf42=\E[17;6~, 146 | kf43=\E[18;6~, 147 | kf44=\E[19;6~, 148 | kf45=\E[20;6~, 149 | kf46=\E[21;6~, 150 | kf47=\E[23;6~, 151 | kf48=\E[24;6~, 152 | kf49=\E[1;3P, 153 | kf4=\EOS, 154 | kf50=\E[1;3Q, 155 | kf51=\E[1;3R, 156 | kf52=\E[1;3S, 157 | kf53=\E[15;3~, 158 | kf54=\E[17;3~, 159 | kf55=\E[18;3~, 160 | kf56=\E[19;3~, 161 | kf57=\E[20;3~, 162 | kf58=\E[21;3~, 163 | kf59=\E[23;3~, 164 | kf5=\E[15~, 165 | kf60=\E[24;3~, 166 | kf61=\E[1;4P, 167 | kf62=\E[1;4Q, 168 | kf63=\E[1;4R, 169 | kf6=\E[17~, 170 | kf7=\E[18~, 171 | kf8=\E[19~, 172 | kf9=\E[20~, 173 | khome=\EOH, 174 | kich1=\E[2~, 175 | kil1=\E[2;5~, # st 176 | kind=\E[1;2B, 177 | km, 178 | kmous=\E[M, 179 | knp=\E[6~, 180 | kpp=\E[5~, 181 | kri=\E[1;2A, 182 | krmir=\E[2;2~, # st 183 | lines#24, 184 | mc0=\E[i, 185 | mc4=\E[4i, 186 | mc5=\E[5i, 187 | mc5i, # xterm 188 | mir, 189 | msgr, 190 | npc, 191 | op=\E[39;49m, 192 | pairs#32767, 193 | rc=\E8, 194 | rev=\E[7m, 195 | ri=\EM, 196 | rin=\E[%p1%dT, # xterm 197 | ritm=\E[23m, 198 | rmacs=\E(B, 199 | rmam=\E[?7l, # xterm 200 | rmcup=\E[?1049l, 201 | rmir=\E[4l, 202 | rmkx=\E[?1l\E>, 203 | rmm=\E[?1034l, 204 | rmso=\E[27m, 205 | rmul=\E[24m, 206 | rs1=\Ec, 207 | # xterm: rs2=\E[!p\E[?3;4l\E[4l\E>, 208 | # st-meta: rs2=\E[4l\E>\E[?1034h, 209 | rs2=\E[4l\E>\E[?1034l, 210 | sc=\E7, 211 | setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, 212 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, 213 | setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, # st 214 | setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, # st 215 | # xterm: sgr0=\E(B\E[m, 216 | sgr0=\E[0m, 217 | sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, 218 | sitm=\E[3m, 219 | smacs=\E(0, 220 | smam=\E[?7h, # xterm 221 | smcup=\E[?1049h, 222 | smir=\E[4h, 223 | smkx=\E[?1h\E=, 224 | smm=\E[?1034h, 225 | smso=\E[7m, 226 | smul=\E[4m, 227 | tbc=\E[3g, 228 | tsl=\E]0;, # st 229 | u6=\E[%i%d;%dR, # xterm 230 | u7=\E[6n, # xterm 231 | u8=\E[?1;2c, # xterm 232 | u9=\E[c, # xterm 233 | vpa=\E[%i%p1%dd, 234 | xenl, 235 | --------------------------------------------------------------------------------