├── .gitignore ├── LICENSE ├── Makefile.in ├── README.md ├── TODO.md ├── boxdraw.c ├── boxdraw.h ├── completion ├── bash │ └── nsst ├── fish │ └── nsst.fish ├── yash │ ├── nsst │ └── nsstc └── zsh │ └── _nsst ├── config.c ├── config.h ├── configure ├── daemon.c ├── docs ├── nsst.1 ├── nsst.conf ├── nsst.desktop └── nsstc.desktop ├── feature.h.in ├── font.c ├── font.h ├── hashtable.h ├── image.c ├── image.h ├── input.c ├── input.h ├── integration ├── nsst_fish_integration.fish ├── nsst_yash_integration.sh └── nsst_zsh_integration.sh ├── iswide.h ├── line.c ├── line.h ├── list.h ├── mouse.c ├── mouse.h ├── multipool.c ├── multipool.h ├── nrcs.c ├── nrcs.h ├── nsst-open ├── nsst.c ├── nsst.info ├── nsstc.c ├── poller.c ├── poller.h ├── precompose-table.h ├── render-shm-wayland.c ├── render-shm-x11.c ├── render-shm.c ├── render-xrender-x11.c ├── screen.c ├── screen.h ├── term.c ├── term.h ├── tools └── gen_tables.py ├── tty.c ├── tty.h ├── uri.c ├── uri.h ├── util.c ├── util.h ├── width-table.h ├── window-impl.h ├── window-wayland.c ├── window-wayland.h ├── window-x11.c ├── window-x11.h ├── window.c └── window.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Hidden files (for autocomplion) 2 | .* 3 | 4 | # Objects 5 | *.o 6 | 7 | # All kinds of analyzers files 8 | *.gcda 9 | perf.data* 10 | 11 | # These files are generated 12 | Makefile 13 | feature.h 14 | *-protocol.[ch] 15 | 16 | # Table generator resources 17 | tools/EastAsianWidth.txt 18 | tools/UnicodeData.txt 19 | 20 | # Binaries (with default names) 21 | ./nsst 22 | ./nsstc 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019-2023, Evgeniy Baskov 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | NAME ?= @NAME@ 2 | PERFIX ?= @PREFIX@ 3 | BINDIR ?= @BINDIR@ 4 | MANDIR ?= @MANDIR@ 5 | SHAREDIR ?= @SHAREDIR@ 6 | PKGCONFIG ?= pkg-config 7 | 8 | @VARS@ 9 | 10 | CFLAGS += -std=c11 -Wall -Wextra -Wpedantic @EXTRAWARNINGS@ 11 | 12 | OBJ := @OBJECTS@ 13 | 14 | LIBS != $(PKGCONFIG) @DEPS@ --libs @STATIC@ 15 | INCLUES != $(PKGCONFIG) @DEPS@ --cflags 16 | 17 | LDLIBS += @LIBRT@ -lm -lutil $(LIBS) 18 | LDFLAGS += @STATIC@ 19 | CFLAGS += $(INCLUES) 20 | 21 | all: $(NAME) $(NAME)c 22 | 23 | clean: 24 | rm -rf *.o *.gcda $(NAME) $(NAME)c 25 | 26 | force: clean 27 | $(MAKE) all 28 | 29 | install-strip: install 30 | strip $(BINDIR)/$(NAME) 31 | strip $(BINDIR)/$(NAME)c 32 | 33 | install: all 34 | install -D $(NAME) $(DESTDIR)$(BINDIR)/$(NAME) 35 | install -D $(NAME)c $(DESTDIR)$(BINDIR)/$(NAME)c 36 | install -D nsst-open $(DESTDIR)$(BINDIR)/$(NAME)-open 37 | install -Dm 644 docs/nsst.conf $(DESTDIR)$(SHAREDIR)/$(NAME)/nsst.conf 38 | install -Dm 644 docs/nsst.1 $(DESTDIR)$(MANDIR)/man1/$(NAME).1 39 | install -Dm 644 docs/nsst.desktop $(DESTDIR)/usr/share/applications/$(NAME).desktop 40 | install -Dm 644 docs/nsstc.desktop $(DESTDIR)/usr/share/applications/$(NAME)c.desktop 41 | @ # FIXME Custom $(NAME) currently do not work, need to modify names 42 | install -Dm 644 completion/zsh/_nsst $(DESTDIR)$(SHAREDIR)/zsh/site-functions/_nsst 43 | install -Dm 644 completion/fish/nsst.fish $(DESTDIR)$(SHAREDIR)/fish/vendor_completions.d/nsst.fish 44 | install -Dm 644 completion/yash/nsst $(DESTDIR)/usr/share/yash/completion/nsst 45 | install -Dm 644 completion/yash/nsstc $(DESTDIR)/usr/share/yash/completion/nsstc 46 | install -Dm 644 completion/bash/nsst $(DESTDIR)/usr/share/bash-completion/completions/nsst 47 | ln -sf nsst $(DESTDIR)/usr/share/bash-completion/completions/nsstc 48 | install -Dm 644 integration/nsst_yash_integration.sh $(DESTDIR)$(SHAREDIR)/$(NAME)/integration/nsst_yash_integration.sh 49 | install -Dm 644 integration/nsst_fish_integration.fish $(DESTDIR)$(SHAREDIR)/$(NAME)/integration/nsst_fish_integration.fish 50 | install -Dm 644 integration/nsst_zsh_integration.sh $(DESTDIR)$(SHAREDIR)/$(NAME)/integration/nsst_zsh_integration.sh 51 | tic -x -o $(DESTDIR)/usr/share/terminfo/ -e 'nsst,nsst-direct' nsst.info 52 | 53 | uninstall: 54 | rm -rf $(DESTDIR)$(SHAREDIR)/$(NAME) 55 | rm -f $(DESTDIR)$(MANDIR)/man1/$(NAME).1 56 | rm -f $(DESTDIR)$(BINDIR)/$(NAME) 57 | rm -f $(DESTDIR)$(BINDIR)/$(NAME)c 58 | rm -f $(DESTDIR)$(BINDIR)/$(NAME)-open 59 | rm -f $(DESTDIR)$(SHAREDIR)/zsh/site-functions/_nsst 60 | rm -f $(DESTDIR)$(SHAREDIR)/fish/vendor_completions.d/nsst.fish 61 | rm -f $(DESTDIR)/usr/share/yash/completion/nsst 62 | rm -f $(DESTDIR)/usr/share/yash/completion/nsstc 63 | rm -f $(DESTDIR)/usr/share/bash-completion/completions/nsst 64 | rm -f $(DESTDIR)/usr/share/bash-completion/completions/nsstc 65 | rm -f $(DESTDIR)/usr/share/terminfo/nsst 66 | rm -f $(DESTDIR)/usr/share/terminfo/nsst-direct 67 | rm -f $(DESTDIR)/usr/share/applications/$(NAME).desktop 68 | rm -f $(DESTDIR)/usr/share/applications/$(NAME)c.desktop 69 | 70 | $(NAME): $(OBJ) 71 | $(CC) $(CFLAGS) $(LDFLAGS) $(OBJ) $(LDLIBS) -o $@ 72 | 73 | $(NAME)c: nsstc.o 74 | $(CC) $(CFLAGS) $(LDFLAGS) nsstc.o -o $@ 75 | 76 | window-wayland.o: xdg-decoration-protocol.h 77 | xdg-decoration-protocol.h: 78 | $(WAYLANDSCANNER) client-header $(XDGDECORATIONPROTOCOL) $@ 79 | xdg-decoration-protocol.c: 80 | $(WAYLANDSCANNER) private-code $(XDGDECORATIONPROTOCOL) $@ 81 | 82 | window-wayland.o: primary-selection-protocol.h 83 | primary-selection-protocol.h: 84 | $(WAYLANDSCANNER) client-header $(PRIMARYSELECTIONPROTOCOL) $@ 85 | primary-selection-protocol.c: 86 | $(WAYLANDSCANNER) private-code $(PRIMARYSELECTIONPROTOCOL) $@ 87 | 88 | window-wayland.o: xdg-shell-protocol.h 89 | xdg-shell-protocol.h: 90 | $(WAYLANDSCANNER) client-header $(XDGSHELLPROTOCOL) $@ 91 | xdg-shell-protocol.c: 92 | $(WAYLANDSCANNER) private-code $(XDGSHELLPROTOCOL) $@ 93 | 94 | window-wayland.o: xdg-output-protocol.h 95 | xdg-output-protocol.h: 96 | $(WAYLANDSCANNER) client-header $(XDGOUTPUTPROTOCOL) $@ 97 | xdg-output-protocol.c: 98 | $(WAYLANDSCANNER) private-code $(XDGOUTPUTPROTOCOL) $@ 99 | 100 | nsstc.o: feature.h 101 | nsst.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h input.h tty.h window.h hashtable.h multipool.h 102 | util.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h precompose-table.h hashtable.h width-table.h multipool.h 103 | config.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h input.h window.h hashtable.h multipool.h 104 | poller.o: feature.h config.h util.h iswide.h poller.h 105 | daemon.o: feature.h config.h util.h iswide.h window.h font.h poller.h 106 | input.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h input.h term.h window.h hashtable.h multipool.h 107 | mouse.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h input.h mouse.h term.h uri.h window.h hashtable.h multipool.h poller.h 108 | nrcs.o: feature.h nrcs.h util.h iswide.h 109 | multipool.o: feature.h multipool.h 110 | line.o: feature.h line.h uri.h util.h iswide.h multipool.h 111 | screen.o : feature.h config.h line.h util.h iswide.h nrcs.h mouse.h term.h window.h uri.h hashtable.h screen.h multipool.h 112 | uri.o: feature.h config.h uri.h font.h line.h util.h iswide.h nrcs.h hashtable.h multipool.h 113 | tty.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h tty.h hashtable.h multipool.h 114 | term.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h input.h mouse.h term.h window.h tty.h uri.h hashtable.h screen.h multipool.h 115 | boxdraw.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h boxdraw.h term.h window.h hashtable.h multipool.h 116 | font.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h window.h hashtable.h multipool.h 117 | image.o: feature.h util.h iswide.h image.h font.h hashtable.h 118 | window.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h input.h mouse.h term.h window.h window-impl.h uri.h multipool.h poller.h 119 | window-x11.o: feature.h config.h line.h util.h iswide.h nrcs.h mouse.h term.h window.h window-impl.h window-x11.h uri.h multipool.h poller.h 120 | window-wayland.o: feature.h config.h line.h util.h iswide.h nrcs.h mouse.h term.h window.h window-impl.h window-wayland.h uri.h multipool.h poller.h 121 | render-shm.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h mouse.h term.h window.h window-impl.h hashtable.h multipool.h 122 | render-shm-wayland.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h mouse.h term.h window.h window-impl.h window-wayland.h hashtable.h multipool.h 123 | render-shm-x11.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h mouse.h term.h window.h window-impl.h window-x11.h hashtable.h multipool.h 124 | render-xrender-x11.o: feature.h config.h font.h line.h util.h iswide.h nrcs.h mouse.h term.h window.h window-impl.h window-x11.h hashtable.h multipool.h 125 | 126 | .PHONY: all clean install install-strip uninstall force 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Not So Simple Terminal 2 | ====================== 3 | This is an implementation of VT220-like X11/Wayland terminal emulator. 4 | 5 | ## Features 6 | * Quite fast rendering 7 | * Almost same latency as `XTerm`, which is a lot lower than for other modern terminals 8 | * Scrolling performance is on par with fastest terminals on my system (`alacritty` and `urxvt`) 9 | * Small size and almost no dependencies 10 | * Uses xcb for X11 and libwayland for Wayland directly, 11 | * A little bit faster because of its asynchrony 12 | * `size` including all loaded shared libs is only 80% of `st` on my system for X11 13 | * Most escape sequences from XTerm are implemented 14 | * Full keyboard mode from XTerm 15 | * Synchronous updates DCS 16 | * `OSC 13001 ; Po ST` "Set background opacity to `Po` (`Po` is floating point)" 17 | * Daemon mode (`urxvt`-like) 18 | * Multiple terminal windows 19 | * `Shift-Ctrl-N` is default keybinding to create new window in the same process 20 | * `nsstc` client that can create new terminal daemon 21 | * Daemon can be auto-launched by `nsstc` on demand (`nsstc -d ...`) 22 | * Configuration with symmetrical config file options and command line arguments 23 | * X11 MIT-SHM, X11 XRender and Wayland `wl_shm` backends 24 | * Compiles with `-flto` by default 25 | * No warnings (see list of all enabled warnings in Makefile) 26 | * Re-wraps text on resize 27 | * URL support (including autodetection) 28 | * Command line integration (correct wrapping after command output without newline and jumping between commands) 29 | * Can copy tab characters 30 | 31 | See TODO file for things that are to be implemented. 32 | 33 | ## Source structure 34 | 35 | * `boxdraw.c` -- Boxdrawing characters rendering 36 | * `config.c` -- Configuration handling and storage 37 | * `daemon.c` -- Daemon code 38 | * `font.c` -- Font loading and glyph caching 39 | * `image.c` -- Image manipulation utilities 40 | * `input.c` -- Input handling code (more or less compatible with Xterm) 41 | * `line.c` -- Terminal lines manipulation functions 42 | * `mouse.c` -- Mouse events and selection handling 43 | * `multipool.c` -- Allocator implementation optimized for lines allocation 44 | * `nrcs.c` -- National replacement character sets logic 45 | * `nsstc.c` -- Thin client that is able to connect to nsst daemon 46 | * `nsst.c` -- `main` function and arguments parsing 47 | * `poller.c` -- Event handling (`poll()` wrapper) 48 | * `render-xrender-x11.c` -- X11 XRender backend 49 | * `render-shm.c` -- common software rendening code 50 | * `render-shm-x11.c` -- X11 MIT-SHM backend 51 | * `render-shm-wayland.c` -- Wayland `wl_shm` backend 52 | * `screen.c` -- Screen manipulation routines 53 | * `term.c` -- Terminal logic 54 | * `tty.c` -- Low level TTY/PTY code 55 | * `uri.c` -- URL storage and validation 56 | * `util.c` -- General utilities (encoding/decoding and logging) 57 | * `window.c` -- Common window code 58 | * `window-x11.c` -- X11 specific window code 59 | * `window-wayland.c` -- Wayland specific window code 60 | 61 | ## Notes 62 | 63 | Nsst uses `TERM=xterm-256color` by default for higher compatibility. This is fine since almost every escape sequence from [ctlseqs.ms](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html) is supported. 64 | See Emulation section of TODO for not yet implemented escape sequences. This repository contains native terminfo (`nsst.info`), which contains several extensions, like curly 65 | underlines and synchronous updates. It is installed by default and can be selected if desired by setting `term-name=nsst` or `term-name=nsst-direct` in the config file. The latter uses 24-bit colors. 66 | 67 | Works well with [Iosevka](https://github.com/be5invis/Iosevka) font. (Set font spacing to -1 it it feels to wide.) 68 | Multiple fonts could be loaded by enumerating them in parameter like: 69 | 70 | font=Iosevka-13,MaterialDesignIcons-13 71 | 72 | Set `override-boxdrawing` to `true` if box drawing characters of font you use does not align. 73 | 74 | If font looks to blurry try setting `font-gamma` to value greater than `1`. 75 | 76 | Set pixelMode to your monitor's pixel alignment to enable sub-pixel rendering. 77 | 78 | By default only DEC Special Graphics charset is allowed with UTF-8 mode enabled. 79 | Spec is even stricter, disallowing any charset translations in UTF-8 mode, but DEC Special Graphics is used by applications frequently so it is allowed anyways. 80 | To force full NRCS translation in UTF-8 mode set `force-nrsc`. 81 | 82 | Sequential graphical characters are decoded all at once, so can be printed faster. 83 | 84 | Debugging output can be enabled with `trace-*` options. 85 | 86 | For now nsst supports combining characters only via precomposition, but almost everything is ready to implement proper rendering of combining character (and variant glyphs support). 87 | The only tricky part is to extract positioning tables and implement basic text shaping. It would be implemented using glyphs with codes `0x110000` - `0x1FFFFF`, 88 | giving sufficient number of possible custom glyphs. DECDLD is also easy to implement this way. 89 | 90 | ## Options 91 | 92 | For command line arguments see `nsst --help`. 93 | Config file uses same option names, just without leading `--`. 94 | Default config file location is `$XDG_CONFIG_HOME/nsst.conf` or `$XDG_CONFIG_HOME/nsst/nsst.conf`. 95 | Config file can be reloaded for already running terminal by sending `SIGUSR1` to it: `pkill -USR1 nsst`. 96 | Config file path can be set via `--config`/`-C` argument. 97 | For boolean arguments `--no-X`, `--without-X`, `--disable-X` are interpreted as `--X=0` and 98 | `--X`, `--with-X`, `--enable-X` are interpreted as `--X=1`. 99 | 100 | Also take a look at the [manual page](docs/nsst.1) (or `man nsst` if you have installed the terminal.) 101 | 102 | ## Key bindings 103 | 104 | Hotkeys are configurable. Key binds have syntax `[-]`, where `` is XKB key name and mods is a set of one or more of following: 105 | 106 | * `S` -- Shift 107 | * `C` -- Control 108 | * `L` -- Lock 109 | * `T` -- Shift+Control (configurable with `term-mod`) 110 | * `1`/`A`/`M` -- Mod1/Alt/Meta 111 | * `2` -- Mod2/Numlock 112 | * `3` -- Mod3 113 | * `4` -- Mod4/Super 114 | * `5` -- Mod5 115 | 116 | Default keybindings: 117 | 118 | key-break=Break 119 | key-numlock=T-Num_Lock 120 | key-scroll-up=T-Up 121 | key-scroll-down=T-Down 122 | key-inc-font=T-Page_Up 123 | key-dec-font=T-Page_Down 124 | key-reset-font=T-Home 125 | key-new-window=T-N 126 | key-reset=T-R 127 | key-reload-config=T-X 128 | key-reverse-video=T-I 129 | key-copy=T-C 130 | key-copy-uri=T-U 131 | key-paste=T-V 132 | key-jump-next-cmd=T-F 133 | key-jump-prev-cmd=T-B 134 | 135 | Copy URI key copies highlighted URI address. Highlighted URI is underlined by default. 136 | For `key-jump-next-cmd`/`key-jump-prev-cmd` see shell integration section. 137 | 138 | ## Mouse support 139 | 140 | If you are not using an application that enables mouse reporting, you can use built-in mouse interactions. 141 | If application enables mouse reporting, built-in interactions can be forces with mouse pressing Ctrl+Shift (Can be configured with `force-mouse-mod` option). 142 | Built-in mouse interactions: 143 | 144 | * Scroll with mouse wheel. 145 | * Jump between commands with Alt+mouse wheel (if your shell supports it more on shell integration below). 146 | * Select with left click, select snapping on words on double left click, select snapping on lines on triple left click. 147 | * Select rectangular area with Alt+left click (double click will snap to words). 148 | * Select whole command output with Alt+triple click (requires shell integration). 149 | 150 | ### Wayland CSD 151 | 152 | This implementation does not have first class support of CSD decorations, trying to rely on SSD instead. 153 | Despite that terminal provides some mouse controls when SSD are not available: 154 | 155 | * Use **Right** mouse button on the border to resize 156 | * Use **Left** mouse button on the border to move 157 | * Use **Middle** mouse button on the **top** border to close the window 158 | * Scroll **up** on the top border to set maximized or fullscreen (in order) 159 | * Scroll **down** on the top border to set maximized, normal or minimized (in order) 160 | 161 | ## Shell integration 162 | 163 | The most basic way to enable it is to put `\033]133;A\a` at the beginning of your shell prompt and `\e]133;B\a` at the end to enable jumping between commands with `T-F`/`T-B`. 164 | To make T-N open window in current directory make sure to wrap `cd` to output `\033]7;$PWD\a` after directory change to notify the terminal. 165 | To enable selecting whole command output (with Alt+triple click) additionally put `\033]133;D\a` at the end of the right prompt or before the beginning of the command output. 166 | 167 | This repository includes shell integration scripts for *yash*, *zsh* and *fish* in `integration` directory. Source them at the bottom of your `.yashrc`/`.zshrc` file to enable it. 168 | Other shells (bash) will be added in the future. Contributions are welcome. 169 | 170 | **NOTE**: Some zsh themes, like [powerlevel10k](https://github.com/romkatv/powerlevel10k) override hooks, which causes native shell integration scripts 171 | to stop working. You need to fix that depending on the theme. E.g. for powerlevel10k you need to set `POWERLEVEL9K_TERM_SHELL_INTEGRATION=true` in your 172 | `.p10k.zsh` file, instead of using provided integration script. 173 | 174 | This repository also includes *zsh*/*yash*/*fish*/*bash* completion scripts in `completion` directory. They are installed by default into appropriate paths. 175 | 176 | ## Dependencies 177 | ### Build 178 | 179 | * pkg-config 180 | * GNU make or BSD make 181 | * C11 compatible compiler 182 | 183 | ### Runtime 184 | 185 | * Common: 186 | * `fontconfig` 187 | * `freetype2` 188 | * `xkbcommon` 189 | * X11: 190 | * `libxcb` 191 | * `xcb-util-cursor` 192 | * `xkbcommon-x11` 193 | * Wayland: 194 | * `libwayland-client` 195 | * `libwayland-cursor` 196 | * `wayland-protocols` 197 | 198 | #### Void Linux 199 | 200 | xbps-install libxkbcommon-devel fontconfig-devel freetype-devel 201 | # For X11 202 | xbps-install libxcb-devel xcb-util-cursor-devel 203 | # For Wayland 204 | xbps-install wayland-devel wayland-protocols 205 | 206 | #### Arch Linux and derivatives 207 | 208 | pacman -S libxkbcommon fontconfig freetype2 209 | # For X11 210 | pacman -S libxcb libxkbcommon-x11 xcb-util-cursor 211 | # For Wayland 212 | pacman -S wayland wayland-protocols 213 | 214 | #### Debian and derivatives 215 | 216 | apt update 217 | apt install libxkbcommon-dev libfontconfig1-dev libfreetype-dev 218 | # For X11 219 | apt install libx11-xcb-dev libxcb-shm0-dev libxcb-render0-dev \ 220 | xkbcommon-x11-dev libxcb-cursor0-dev 221 | # For Wayland 222 | apt install libwayland-dev wayland-protocols 223 | 224 | ## Building and installing 225 | 226 | You need all dependencies installed before getting started. 227 | 228 | First clone this repo and cd into its folder like this: 229 | 230 | git clone https://github.com/summaryInfo/nsst 231 | cd nsst 232 | 233 | Then configure and build: 234 | 235 | ./configure 236 | make -j 237 | 238 | Default config is generally sane. 239 | Alternatively do 240 | 241 | ./configure CFLAGS='-flto=auto -O2 -march=native' 242 | make -j 243 | 244 | if you want to use it the machine you compile it on. 245 | These more aggressive optimization options works fine. 246 | It's more relevant for MIT-SHM backend. 247 | 248 | XRender backend is slightly faster in general, 249 | but a lot slower in some corner cases (e.g. small font and a lot of true color cells). 250 | XRender backend is default. 251 | In order to force MIT-SHM backend use `--backend=x11shm`. 252 | In order to switch to Wayland use `--backend=waylandshm`. 253 | By default wayland is used if available. 254 | 255 | See `./configure --help` for more. 256 | 257 | Finally install: 258 | 259 | make install 260 | 261 | Default binary location is `/usr/local/bin/$name` 262 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | Roadmap 2 | ======= 3 | 4 | ## Future versions 5 | 6 | ### Until 2.6 7 | 8 | _NOTE: These plans can change any time_ 9 | 10 | * Wayland 11 | * Explicitly specify required versions of protocols 12 | 13 | * Packaging 14 | * Expand documentation 15 | 16 | ## Planned/possible features 17 | 18 | ### Wayland 19 | 20 | * Support optional rendering of title bar 21 | * Title text 22 | * Close, Maximize, Minimize buttons 23 | * Double click to change state 24 | * Recolor on focus 25 | 26 | * Dynamic output properties tracking: 27 | * Allow specifying sizes in pt/mm/etc 28 | * Dynamically change window DPI/sub-pixel mode depending on the output 29 | * Support HiDPI. Scale surface content 30 | * Fractional scale 31 | 32 | * Support server side cursors 33 | * Support xdg-activation-v1 34 | 35 | ### General 36 | 37 | * Add bash shell integration 38 | 39 | ### Internal 40 | 41 | * Some alternative to `XParseColor()` 42 | 43 | This is really hard since `XParseColor()` has some weird built-in color-space conversions 44 | and implementing this in the way matching *XTerm* behavior means implementing whole 45 | colorspace and colors handling from *xlib*. 46 | 47 | * Re-implement mouse selection via line_handle's to (mostly) remove special case. 48 | * "Mostly", because we still would need to notify mouse code upon relocation. 49 | 50 | * Refactor screen arrays 51 | * Lazy resizing? 52 | 53 | ### Configuration 54 | 55 | * Add `always` value to some options to prevent overriding 56 | 57 | User might want to keep some settings in the certain way, not allowing an application 58 | to override it. 59 | 60 | * Make config format more sophisticated. Support arrays and quoted strings. 61 | 62 | * Add command patterns like "xdg-open {url}" for command spawning 63 | 64 | * Support named windows and allow changing options for a specific window using nsstc 65 | 66 | ### Rendering 67 | 68 | * (Maybe) show helper when hovering over URL. 69 | 70 | * Font rendering 71 | 72 | * Add options to select width of ambiguous characters 73 | 74 | * Combining characters support 75 | 76 | Basically, we need to create a global hash table of private runes 77 | mapping (with values higher than 2^21) to unicode code point sequences, 78 | parse fonts to determine relative glyphs positions and render glyphs 79 | one atop another to create new glyph and associate it with private rune in glyph cache. 80 | 81 | * Software rendering 82 | 83 | * Threaded rendering? 84 | 85 | ### Input 86 | 87 | * IME support 88 | 89 | * Implement terminal-wg protocol proposed in [!5](https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/5) (or not to?..) 90 | 91 | * Add *mintty* extensions to `CSI-u` and `xterm` protocols 92 | 93 | * Option to encode escape as `\e[27u` or `\e[27;1u` 94 | * Option to encode `Alt` as modifier, not prefix 95 | * Option to report key releases as `\e[;;1u` 96 | * Option to report modifiers presses as `\e[0;u` or `\e[0;;1u` 97 | * Report `Super` (`mod4`) and `Hyper` (`mod5`) modifiers (as 16/32) 98 | 99 | ### VTxxx/XTerm compatibility and emulation 100 | 101 | * Encode NRCSs for printer autoprint mode 102 | * Proper VT level restriction 103 | * Is xterm private mode 1044 semantics correct/should it be implemented at all? 104 | 105 | * VT220 106 | 107 | * `DCS Pf ; Pc ; Pe ; Pw ; Ps ; Pt; Ph ; Px f Pt ST` -- **DECDLD** 108 | 109 | * Fonts 110 | 111 | * OSC 50 ; Pt ST -- Set font 112 | * `CSI ? 35 l` / `CSI ? 35 h` -- font shifting functions 113 | 114 | * SIXEL 115 | 116 | * `CSI ? 80 l` / `CSI ? 80 h` -- **DECSDM** 117 | * `CSI ? 1070 l` / `CSI ? 1070 h` -- Use private registers 118 | * `CSI ? 8452 l` / `CSI ? 8452 h` -- SIXEL scrolling leaves cursor to the right of graphics 119 | * `CSI ? Pi ; Pa; Pv S` -- **XTSMGRAPHICS** (`Pi` = 1, 2; `Pa` = 1, 2, 3, 4) 120 | * `DSC Pa ; Pb ; Ph q Ps..Ps ST` -- Send SIXEL image 121 | 122 | * Termcap 123 | 124 | * `CSI ? 1040 l` / `CSI ? 1040 h` -- terminfo/termcap function key mode 125 | * `DCS + p Pt ST` -- **XTSETTCAP** 126 | * `DCS + q Pt ST` -- **XTGETTCAP** 127 | 128 | * Extended editing (interacts well with shell integration) 129 | 130 | * `CSI ? 2001 l` / `CSI ? 2001 h` -- `srm_BUTTON1_MOVE_POINT` 131 | * `CSI ? 2002 l` / `CSI ? 2002 h` -- `srm_BUTTON2_MOVE_POINT` 132 | * `CSI ? 2003 l` / `CSI ? 2003 h` -- `srm_DBUTTON3_DELETE` 133 | * Also, need to look into terminal-wg discussion about mouse shell interaction 134 | 135 | * Misc 136 | 137 | * `CSI 4:4 m` -- dotted underline 138 | * `CSI 4:5 m` -- dashed underline 139 | * `CSI ? 1039 l` / `CSI ? 1039 h` -- Alt sends escape 140 | * (There are no plans to differentiate `Alt` and `Meta`) 141 | * `CSI ? 14 l` / `CSI ? 14 h` -- Cursor blinking XORing 142 | * `CSI Pm # p, CSI Pm # {` -- **XTPUSHSGR** 143 | * `CSI # q, CSI # }` -- **XTPOPSGR** 144 | * `CSI Pm # Q` -- **XTPOPCOLORS** 145 | * `CSI Pm # P` -- **XTPUSHCOLORS** 146 | * `CSI # R` -- **XTREPORTCOLORS** 147 | * `OSC I Pt ST` -- Set icon XPM file (Would not work in Wayland) 148 | 149 | * Flawed and bad sequences (won't implement) 150 | 151 | * `CSI ? 38 l` / `CSI ? 38 h` -- **DECTEK** (this is complex, obscure and unused) 152 | * `OSC 3 ; Pt ST` -- Set X property (insecure) 153 | * `DCS + Q Pt ST` -> `DCS Ps + R Pt ST` -- **XTGETXRES** (too X11/xterm specific) 154 | * `CSI ? 1001 l` / `CSI ? 1001 h` -- Highlight mouse tracking (can hang the terminal) 155 | * `CSI Ps ; Ps ; Ps ; Ps ; Ps T` -- **XTHIMOUSE** (can hang the terminal) 156 | * `ESC # 3` / `ESC # 4` -- **DECDHL** (poorly interacts with mouse) 157 | * `ESC # 5` -- **DECSWL** (poorly interacts with mouse) 158 | * `ESC # 6` -- **DECDWL** (poorly interacts with mouse) 159 | * `OSC 13 Pt ST` -- Set mouse background color (not supported for modern cursors) 160 | * `OSC 14 Pt ST` -- Set mouse foreground color (not supported for modern cursors) 161 | * `OSC 113 Pt ST` -- Reset mouse foreground color (not supported for modern cursors) 162 | * `OSC 114 Pt ST` -- Reset mouse background color (not supported for modern cursors) 163 | -------------------------------------------------------------------------------- /boxdraw.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #include "feature.h" 4 | 5 | #include "config.h" 6 | #include "boxdraw.h" 7 | #include "font.h" 8 | #include "term.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | static void draw_rect(struct glyph * glyph, bool lcd, int16_t xs, int16_t ys, int16_t xe, int16_t ye, uint8_t val) { 17 | if (xs < xe && ys < ye) { 18 | xs = MAX(0, xs); 19 | xe = MIN(xe, glyph->width); 20 | ys = MAX(0, ys); 21 | ye = MIN(ye, glyph->height); 22 | 23 | if (lcd) xs *= 4, xe *= 4; 24 | for (int16_t j = ys; j < ye; j++) 25 | for (int16_t i = xs; i < xe; i++) 26 | glyph->data[j * glyph->stride + i] = val; 27 | } 28 | } 29 | 30 | static void put(struct glyph *glyph, bool lcd, int16_t x, int16_t y, uint8_t val) { 31 | if (!lcd) glyph->data[glyph->stride * y + x] = val; 32 | else for (size_t i = 0; i < 4; i++) 33 | glyph->data[glyph->stride*y + 4*x + i] = val; 34 | } 35 | 36 | struct glyph *make_boxdraw(uint32_t c, int16_t width, int16_t height, int16_t depth, enum pixel_mode pixmode, int16_t hspacing, int16_t vspacing, bool force_aligned) { 37 | if (!is_boxdraw(c)) return NULL; 38 | 39 | bool lcd = pixmode != pixmode_mono; 40 | size_t stride; 41 | 42 | if (force_aligned) { 43 | /* X11 MIT-SHM requires 16 byte alignment */ 44 | stride = ROUNDUP(width, 4); 45 | if (lcd) stride *= 4; 46 | } else { 47 | /* X11 XRender backend requires all glyphs 48 | * to be in the same format (i.e. subpixel or not) */ 49 | stride = lcd ? width * 4U : ROUNDUP(width, 4); 50 | } 51 | 52 | struct glyph *glyph = aligned_alloc(_Alignof(struct glyph), sizeof(struct glyph) + 53 | ROUNDUP(stride * (height + depth) * sizeof(uint8_t), _Alignof(struct glyph))); 54 | if (!glyph) return NULL; 55 | 56 | glyph->y_off = 0; 57 | glyph->x = hspacing/2; 58 | glyph->y = height + vspacing/2; 59 | glyph->height = height + depth; 60 | glyph->x_off = glyph->width = width; 61 | glyph->stride = stride; 62 | glyph->pixmode = pixmode; 63 | memset(glyph->data, 0, stride * (height + depth)); 64 | 65 | enum { 66 | NOC = 1 << 1, /* No center */ 67 | CUR = 1 << 2, /* Curved */ 68 | TL = 1 << 4, BL = 1 << 5, LL = 1 << 6, RL = 1 << 7, /* Light lines */ 69 | TD = 1 << 8, BD = 1 << 9, LD = 1 << 10, RD = 1 << 11, /* Double lines */ 70 | /* These are considered blocks */ 71 | BLK = 1 << 3, /* Block */ 72 | TLQ = BLK | 1 << 4, TRQ = BLK | 1 << 5, BLQ = BLK | 1 << 6, BRQ = BLK | 1 << 7, /* Quoters */ 73 | V = BLK | 1 << 8, VR = BLK | 1 << 9, /* Vertical blocks */ 74 | H = BLK | 1 << 10, HR = BLK | 1 << 11, /* Horizontal blocks */ 75 | LX = BLK | 1 << 12, RX = BLK | 1 << 13, /* Diagonal lines */ 76 | DT1 = 1 << 14, DT2 = 1 << 15, /* Dotted (both for blocks and lines) */ 77 | }; 78 | uint16_t desc = (uint16_t[]){ 79 | LL|RL, LL|LD|RL|RD, TL|BL, TL|TD|BL|BD, 80 | LL|RL|DT1, LL|LD|RL|RD|DT1, TL|BL|DT1, TL|TD|BL|BD|DT1, 81 | LL|RL|DT2, LL|LD|RL|RD|DT2, TL|BL|DT2, TL|TD|BL|BD|DT2, 82 | BL|RL, BL|RL|RD, BL|BD|RL, BL|BD|RL|RD, 83 | BL|LL, BL|LL|LD, BL|BD|LL, BL|BD|LL|LD, 84 | TL|RL, TL|RL|RD, TL|TD|RL, TL|TD|RL|RD, 85 | TL|LL, TL|LL|LD, TL|TD|LL, TL|TD|LL|LD, 86 | TL|BL|RL, TL|BL|RL|RD, TL|TD|BL|RL, TL|BL|BD|RL, 87 | TL|TD|BL|BD|RL, TL|TD|BL|RL|RD, TL|BL|BD|RL|RD, TL|TD|BL|BD|RL|RD, 88 | TL|BL|LL, TL|BL|LL|LD, TL|TD|BL|LL, TL|BL|BD|LL, 89 | TL|TD|BL|BD|LL, TL|TD|BL|LL|LD, TL|BL|BD|LL|LD, TL|TD|BL|BD|LL|LD, 90 | LL|BL|RL, LL|LD|BL|RL, LL|BL|RL|RD, LL|LD|BL|RL|RD, 91 | LL|BL|BD|RL, LL|LD|BL|BD|RL, LL|BL|BD|RL|RD, LL|LD|BL|BD|RL|RD, 92 | LL|TL|RL, LL|LD|TL|RL, LL|TL|RL|RD, LL|LD|TL|RL|RD, 93 | LL|TL|TD|RL, LL|LD|TL|TD|RL, LL|TL|TD|RL|RD, LL|LD|TL|TD|RL|RD, 94 | LL|RL|TL|BL, LL|LD|RL|TL|BL, LL|RL|RD|TL|BL, LL|LD|RL|RD|TL|BL, 95 | LL|RL|TL|TD|BL, LL|RL|TL|BL|BD, LL|RL|TL|TD|BL|BD, LL|LD|RL|TL|TD|BL, 96 | LL|RL|RD|TL|TD|BL, LL|LD|RL|TL|BL|BD, LL|RL|RD|TL|BL|BD, LL|LD|RL|RD|TL|TD|BL, 97 | LL|LD|RL|RD|TL|BL|BD,LL|LD|RL|TL|TD|BL|BD,LL|RL|RD|TL|TD|BL|BD,LL|LD|RL|RD|TL|TD|BL|BD, 98 | LL|RL|NOC, LL|LD|RL|RD|NOC, TL|BL|NOC, TL|TD|BL|BD|NOC, 99 | LD|RD, TD|BD, BL|RD, BD|RL, 100 | BD|RD, BL|LD, BD|LL, BD|LD, 101 | TL|RD, TD|RL, TD|RD, TL|LD, 102 | TD|LL, TD|LD, TL|BL|RD, TD|BD|RL, 103 | TD|BD|RD, TL|BL|LD, TD|BD|LL, TD|BD|LD, 104 | LD|RD|BL, LL|RL|BD, LD|RD|BD, LD|RD|TL, 105 | LL|RL|TD, LD|RD|TD, TL|BL|RD|LD, TD|BD|RL|LL, 106 | TD|BD|RD|LD, RL|BL|CUR|NOC, LL|BL|CUR|NOC, TL|LL|CUR|NOC, 107 | TL|RL|CUR|NOC, RX, LX, RX|LX, 108 | LL, TL, RL, BL, 109 | LD, RL, TD, BD, 110 | LL|RD, TL|BD, LD|RL, TD|BL, 111 | TLQ|TRQ, H, H|1, H|2, 112 | H|3, H|4, H|5, H|6, 113 | H|7, V|6, V|5, V|4, 114 | V|3, V|2, V|1, V, 115 | TRQ|BRQ, BLK|DT1, BLK|DT2, DT1|H|7, 116 | HR|1, VR|1, BLQ, BRQ, 117 | TLQ, TLQ|BRQ|BLQ, TLQ|BRQ, TLQ|TRQ|BLQ, 118 | TLQ|TRQ|BRQ, TRQ, TRQ|BLQ, TRQ|BLQ|BRQ, 119 | }[c - 0x2500]; 120 | 121 | int16_t h = height + depth, w = width; 122 | int16_t ch = h/2, cw = w/2; 123 | int16_t lw = MAX(w/8, 1), lw2 = MAX(lw/2, 1); 124 | int16_t mod, x0 = cw-lw+lw2, y0 = ch-lw+lw2; 125 | bool dt1 = desc & DT1, dt2 = desc & DT2, noc = desc & NOC, cur = desc & CUR; 126 | bool td = desc & TD, bd = desc & BD, ld = desc & LD, rd = desc & RD; 127 | bool tl = desc & TL, bl = desc & BL, ll = desc & LL, rl = desc & RL; 128 | 129 | if (desc & BLK) { 130 | desc &= ~BLK; 131 | if (desc & TLQ) draw_rect(glyph, lcd, 0, 0, cw, ch, 0xFF); 132 | if (desc & TRQ) draw_rect(glyph, lcd, cw, 0, w, ch, 0xFF); 133 | if (desc & BLQ) draw_rect(glyph, lcd, 0, ch, cw, h, 0xFF); 134 | if (desc & BRQ) draw_rect(glyph, lcd, cw, ch, w, h, 0xFF); 135 | if (desc & H) draw_rect(glyph, lcd, 0, h*(7 - (desc & 7))/8, w, h, 0xFF); 136 | if (desc & V) draw_rect(glyph, lcd, 0, 0, w*(desc & 7)/8, h, 0xFF); 137 | if (desc & HR) draw_rect(glyph, lcd, 0, 0, w, h*(desc & 7)/8, 0xFF); 138 | if (desc & VR) draw_rect(glyph, lcd, w*(7 - (desc & 7))/8, 0, w, h, 0xFF); 139 | if (desc & LX) 140 | for (int16_t i = 0; i < h; i++) 141 | put(glyph, lcd, (w*i/h), i, 0xFF); 142 | if (desc & RX) 143 | for (int16_t i = 0; i < h; i++) 144 | put(glyph, lcd, w - 1 - (w*i/h), i, 0xFF); 145 | if (dt1 | dt2) 146 | for (int16_t i = 0; i < h; i += (1 + dt1)) 147 | for (int16_t j = i & (1 + dt1); j < w; j+= 2*(1 + dt1)) 148 | put(glyph, lcd, j, i, 0xFF * !(desc & H)); 149 | } else { 150 | 151 | if (cur) draw_rect(glyph, lcd, x0+lw*(2*rl-1), y0+lw*(2*bl-1), x0+lw*2*rl, y0+lw*2*bl, 0xFF); 152 | 153 | mod = !noc * !(td && bd && !tl) - noc; 154 | if (ll) draw_rect(glyph, lcd, 0, y0, x0+lw*mod, y0+lw, 0xFF); 155 | if (rl) draw_rect(glyph, lcd, x0+lw*(1-mod), y0, w, y0+lw, 0xFF); 156 | 157 | mod = !noc * !(ld && rd && !ll) - noc; 158 | if (tl) draw_rect(glyph, lcd, x0, 0, x0+lw, y0+lw*mod, 0xFF); 159 | if (bl) draw_rect(glyph, lcd, x0, y0+lw*(1-mod), x0+lw, h, 0xFF); 160 | 161 | mod = !noc * (!(td|tl) * MAX(bl,2*bd) + !(tl|td|bl|bd)) - noc; 162 | if (ld) draw_rect(glyph, lcd, 0, y0-lw, x0+lw*mod, y0, 0xFF); 163 | if (rd) draw_rect(glyph, lcd, x0+lw*(1-mod), y0-lw, w, y0, 0xFF); 164 | 165 | mod = !noc * (!(bd|bl) * MAX(tl,2*td) + !(tl|td|bl|bd)) - noc; 166 | if (ld) draw_rect(glyph, lcd, 0, y0+lw, x0+lw*mod, y0+2*lw, 0xFF); 167 | if (rd) draw_rect(glyph, lcd, x0+lw*(1-mod), y0+lw, w, y0+2*lw, 0xFF); 168 | 169 | mod = !noc * (!(ld|ll) * MAX(rl,2*rd) + !(ll|ld|rl|rd)) - noc; 170 | if (td) draw_rect(glyph, lcd, x0-lw, 0, x0, y0+lw*mod, 0xFF); 171 | if (bd) draw_rect(glyph, lcd, x0-lw, y0+lw*(1-mod), x0, h, 0xFF); 172 | 173 | mod = !noc * (!(rd|rl) * MAX(ll,2*ld) + !(ll|ld|rl|rd)) - noc; 174 | if (td) draw_rect(glyph, lcd, x0+lw, 0, x0+2*lw, y0+lw*mod, 0xFF); 175 | if (bd) draw_rect(glyph, lcd, x0+lw, y0+lw*(1-mod), x0+2*lw, h, 0xFF); 176 | 177 | if (dt1 | dt2) { 178 | for (int16_t step = MAX(1, ((tl?h:w)+1+dt2)/(3 + dt2)), i = 1; i < 3 + dt2; i++) 179 | if (tl) 180 | draw_rect(glyph, lcd, x0 - lw, i*step - (lw+1)/2, x0 + 2*lw, i*step + (lw+1)/2, 0); 181 | else 182 | draw_rect(glyph, lcd, i*step - (lw+1)/2, y0 - lw, i*step + (lw+1)/2, y0 + 2*lw, 0); 183 | } 184 | } 185 | return glyph; 186 | } 187 | -------------------------------------------------------------------------------- /boxdraw.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef BOXDRAW_H_ 4 | #define BOXDRAW_H_ 1 5 | 6 | #include "font.h" 7 | 8 | #include 9 | #include 10 | 11 | struct glyph *make_boxdraw(uint32_t c, int16_t width, int16_t height, int16_t depth, enum pixel_mode pixmode, int16_t hspacing, int16_t vspacing, bool force_same); 12 | 13 | static inline bool is_boxdraw(uint32_t ch) { 14 | return ch >= 0x2500 && ch < 0x25A0; 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /completion/bash/nsst: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Evgeniy Baskov. All rights reserved 2 | 3 | # Bash completion script for the nsst terminal emulator. 4 | 5 | _nsst() { 6 | local IFS=$' \t\n' 7 | COMP_WORDBREAKS="${COMP_WORDBREAKS//=}" 8 | COMPREPLY=() 9 | 10 | # First we need to parse the command to find out whether we 11 | # are need to complete our option or an option of some other program 12 | 13 | local i opt cmdopt fullopt optvalue optname prefix 14 | for (( i=1; i <= COMP_CWORD; i++ )); do 15 | if [[ -n "$opt" ]]; then 16 | if [[ "${COMP_WORDS[i-1]}" = '--' ]]; then 17 | cmdopt="$i" 18 | break 19 | fi 20 | prefix= 21 | optvalue="${COMP_WORDS[i]}" 22 | opt= 23 | continue 24 | fi 25 | case "${COMP_WORDS[i]}" in 26 | (-e) 27 | # Last option, command starts next, we should complete it instead 28 | cmdopt=$((i+1)) 29 | break 30 | ;; 31 | # Boolean options without arguments (need to specify them explicitly) 32 | (--allow-alternate|--allow-blinking|--allow-modify-edit-keypad|--allow-modify-function|\ 33 | --allow-modify-keypad|--allow-modify-misc|--alternate-scroll|--appcursor|--appkey|\ 34 | --autorepeat|--autowrap|--backspace-is-del|--blend-all-background|--blend-foreground|\ 35 | --daemon|--delete-is-del|--erase-scrollback|--extended-cir|--fixed|--force-nrcs|\ 36 | --force-scalable|--force-wayland-csd|--fork|--has-meta|--keep-clipboard|--keep-selection|\ 37 | --lock-keyboard|--luit|--meta-sends-escape|--nrcs|--numlock|--override-boxdrawing|\ 38 | --print-attributes|--raise-on-bell|--reverse-video|--scroll-on-input|--scroll-on-output|\ 39 | --select-to-clipboard|--smooth-resize|--smooth-scroll|--special-blink|--special-bold|\ 40 | --special-italic|--special-reverse|--special-underlined|--substitute-fonts|--trace-characters|\ 41 | --trace-controls|--trace-events|--trace-fonts|--trace-input|--trace-misc|--unique-uris|\ 42 | --urgent-on-bell|--use-utf8|--visual-bell|--window-ops|--version|--help|\ 43 | --no-allow-alternate|--no-allow-blinking|--no-allow-modify-edit-keypad|--no-allow-modify-function|\ 44 | --no-allow-modify-keypad|--no-allow-modify-misc|--no-alternate-scroll|--no-appcursor|--no-appkey|\ 45 | --no-autorepeat|--no-autowrap|--no-backspace-is-del|--no-blend-all-background|--no-blend-foreground|\ 46 | --no-daemon|--no-delete-is-del|--no-erase-scrollback|--no-extended-cir|--no-fixed|--no-force-nrcs|\ 47 | --no-force-scalable|--no-force-wayland-csd|--no-fork|--no-has-meta|--no-keep-clipboard|--no-keep-selection|\ 48 | --no-lock-keyboard|--no-luit|--no-meta-sends-escape|--no-nrcs|--no-numlock|--no-override-boxdrawing|\ 49 | --no-print-attributes|--no-raise-on-bell|--no-reverse-video|--no-scroll-on-input|--no-scroll-on-output|\ 50 | --no-select-to-clipboard|--no-smooth-resize|--no-smooth-scroll|--no-special-blink|--no-special-bold|\ 51 | --no-special-italic|--no-special-reverse|--no-special-underlined|--no-substitute-fonts|--no-trace-characters|\ 52 | --no-trace-controls|--no-trace-events|--no-trace-fonts|--no-trace-input|--no-trace-misc|--no-unique-uris|\ 53 | --no-urgent-on-bell|--no-use-utf8|--no-visual-bell|--no-window-ops|--clone-config|--no-clone-config) 54 | prefix= 55 | optname="${COMP_WORDS[i]}" 56 | optvalue= 57 | ;; 58 | (--*) 59 | # Options with explicit arguments 60 | if [[ "${COMP_WORDS[i]}" =~ ^(--[a-z-]+=)$ ]]; then 61 | # Long option with parameter on the next word with = 62 | prefix="${BASH_REMATCH[1]}" 63 | optname="${BASH_REMATCH[1]}" 64 | optvalue= 65 | opt=1 66 | elif [[ "${COMP_WORDS[i]}" =~ ^(--[a-z-]+=)(.+)$ ]]; then 67 | # Long option with parameter on the same word with = 68 | prefix="${BASH_REMATCH[1]}" 69 | optname="${BASH_REMATCH[1]}" 70 | optvalue="${BASH_REMATCH[2]}" 71 | else 72 | # Long option with parameter on the next word 73 | prefix= 74 | optname="${COMP_WORDS[i]}" 75 | optvalue= 76 | opt=1 77 | [[ "$i" = "$COMP_CWORD" ]] || optname+="=" 78 | fi 79 | ;; 80 | (-*) 81 | if [[ "${COMP_WORDS[i]}" =~ ^(-[vdh]*([CDefGgHLosTtV]))$ ]]; then 82 | # Short option with parameter on the next word 83 | prefix="${BASH_REMATCH[1]}" 84 | optname="-${BASH_REMATCH[2]}" 85 | optvalue= 86 | opt=1 87 | [[ "$i" = "$COMP_CWORD" ]] || optname+="=" 88 | elif [[ "${COMP_WORDS[i]}" =~ ^(-[vdh]*([CDefGgHLosTtV]))(.*)$ ]]; then 89 | # Short option with parameter on the same word 90 | prefix="${BASH_REMATCH[1]}" 91 | optname="-${BASH_REMATCH[2]}=" 92 | optvalue="${BASH_REMATCH[3]}" 93 | elif [[ "${COMP_WORDS[i]}" =~ ^-[vdh]*[vdh]$ ]]; then 94 | # Short option without parameter 95 | prefix="${COMP_WORDS[i]}" 96 | optname="${COMP_WORDS[i]}" 97 | optvalue= 98 | else 99 | prefix= 100 | optname="${COMP_WORDS[i]}" 101 | optvalue= 102 | fi 103 | ;; 104 | (*) 105 | cmdopt=$i 106 | break 107 | ;; 108 | esac 109 | done 110 | 111 | local fullopt="${COMP_WORDS[COMP_CWORD]}" 112 | local shortopts=( "-C" "-d" "-D" "-e" "-f" "-g" "-G" "-h" "-H" "-L" "-o" "-s" "-t" "-T" "-v" "-V" ) 113 | local longopts=( 114 | "--allow-alternate" "--allow-blinking" "--allow-modify-edit-keypad" "--allow-modify-function" 115 | "--allow-modify-keypad" "--allow-modify-misc" "--alpha" "--alternate-scroll" "--answerback-string" 116 | "--appcursor" "--appkey" "--autorepeat" "--autowrap" "--backend" "--background" "--backspace-is-del" 117 | "--bell" "--bell-high-volume" "--bell-low-volume" "--blend-all-background" 118 | "--blend-foreground" "--blink-color" "--blink-time" "--bold-color" "--border" "--bottom-border" 119 | "--char-geometry" "--config" "--cursor-background" "--cursor-foreground" "--cursor-shape" 120 | "--cursor-width" "--cwd" "--daemon" "--delete-is-del" "--double-click-time" "--dpi" 121 | "--erase-scrollback" "--extended-cir" "--fixed" "--fkey-increment" "--font" "--font-gamma" 122 | "--font-size" "--font-size-step" "--font-spacing" "--font-cache-size" "--force-mouse-mod" "--force-nrcs" 123 | "--force-scalable" "--force-wayland-csd" "--foreground" "--fork" "--fps" "--frame-wait-delay" 124 | "--geometry" "--has-meta" "--help" "--horizontal-border" "--italic-color" "--keep-clipboard" 125 | "--keep-selection" "--keyboard-dialect" "--keyboard-mapping" "--key-break" "--key-copy" 126 | "--key-copy-uri" "--key-dec-font" "--key-inc-font" "--key-jump-next-cmd" "--key-jump-prev-cmd" 127 | "--key-new-window" "--key-numlock" "--key-paste" "--key-reload-config" "--key-reset" 128 | "--key-reset-font" "--key-reverse-video" "--key-scroll-down" "--key-scroll-up" "--left-border" 129 | "--line-spacing" "--lock-keyboard" "--log-level" "--luit" "--luit" "--luit-path" "---margin-bell" 130 | "--margin-bell-column" "--margin-bell-high-volume" "--margin-bell-low-volume" "--max-frame-time" 131 | "--meta-sends-escape" "--modify-cursor" "--modify-function" "--modify-keypad" "--modify-other" 132 | "--modify-other-fmt" "--nrcs" "--numlock" "--open-cmd" "--override-boxdrawing" "--pixel-mode" 133 | "--pointer-shape" "--print-attributes" "--print-command" "--printer-file" "--raise-on-bell" 134 | "--reversed-color" "--reverse-video" "--right-border" "--scroll-amount" "--scrollback-size" 135 | "--scroll-on-input" "--scroll-on-output" "--selected-background" "--selected-foreground" 136 | "--select-scroll-time" "--select-to-clipboard" "--shell" "--smooth-resize" "--smooth-scroll" 137 | "--smooth-scroll-delay" "--smooth-scroll-step" "--socket" "--special-blink" "--special-bold" 138 | "--special-italic" "--special-reverse" "--special-underlined" "--substitute-fonts" "--sync-timeout" 139 | "--tab-width" "--term-mod" "--term-name" "--title" "--top-border" "--trace-characters" 140 | "--trace-controls" "--trace-events" "--trace-fonts" "--trace-input" "--trace-misc" 141 | "--triple-click-time" "--underlined-color" "--underline-width" "--unique-uris" "--urgent-on-bell" 142 | "--uri-click-mod" "--uri-color" "--uri-mode" "--uri-underline-color" "--use-utf8" 143 | "--version" "--vertical-border" "--visual-bell" "--visual-bell-time" "--vt-version" 144 | "--wait-for-configure-delay" "--window-class" "--window-ops" "--word-break" 145 | ) 146 | 147 | [[ "${COMP_WORDS[0]}" == nsstc ]] && longopts+=("--quit") 148 | 149 | if [[ -n "$cmdopt" && "$cmdopt" < "${#COMP_WORDS[@]}" ]]; then 150 | declare -F _command_offset >/dev/null || return 1 151 | _command_offset $cmdopt 152 | return 0 153 | fi 154 | 155 | case "$optname" in 156 | (-D=|--term-name=) 157 | COMPREPLY=( $(compgen -P "${prefix}" -W "$(toe | cut -f1 | grep -v + | sort -u)" -- "${optvalue}") ) 158 | ;; 159 | (--bell=|--margin-bell=) 160 | COMPREPLY=( $(compgen -P "${prefix}" -W "off low high default" -- "${optvalue}") ) 161 | ;; 162 | (--cursor-shape=) 163 | COMPREPLY=( $(compgen -P "${prefix}" -W "blinking-block block blinking-underline underline blinking-bar bar default" -- "${optvalue}") ) 164 | ;; 165 | (--keyboard-mapping=) 166 | COMPREPLY=( $(compgen -P "${prefix}" -W "legacy vt220 hp sun sco default" -- "${optvalue}") ) 167 | ;; 168 | (-L=|--log-level=) 169 | COMPREPLY=( $(compgen -P "${prefix}" -W "quiet fatal warn info default" -- "${optvalue}") ) 170 | ;; 171 | (--modify-other-fmt) 172 | COMPREPLY=( $(compgen -P "${prefix}" -W "xterm csi-u default" -- "${optvalue}") ) 173 | ;; 174 | (--pixel-mode=) 175 | COMPREPLY=( $(compgen -P "${prefix}" -W "mono bgr rgb bgrv rgbv default" -- "${optvalue}") ) 176 | ;; 177 | (--uri-mode=) 178 | COMPREPLY=( $(compgen -P "${prefix}" -W "off manual auto default" -- "${optvalue}") ) 179 | ;; 180 | (--backend=) 181 | COMPREPLY=( $(compgen -P "${prefix}" -W "auto x11 wayland x11xrender x11shm waylandshm default" -- "${optvalue}") ) 182 | ;; 183 | (-f=|--font=) 184 | COMPREPLY=( $(compgen -P "${prefix}" -W "$(fc-list :spacing=mono family | sed 's/,/\n/g' | sort -u | tr -d ' ')" -- "${optvalue}") ) 185 | ;; 186 | (--cwd=) 187 | COMPREPLY=( $(compgen -P "${prefix}" -A directory -- "${optvalue}") ) 188 | ;; 189 | (-[Cos]=|--config=|--printer-file=|--socket=) 190 | COMPREPLY=( $(compgen -P "${prefix}" -A file -- "${optvalue}") ) 191 | ;; 192 | (--luit-path=|--open-cmd=|--print-command=|--shell=) 193 | COMPREPLY=( $(compgen -P "${prefix}" -A command -- "${optvalue}") ) 194 | ;; 195 | (--autorepeat=|--allow-alternate=|--allow-blinking=|--allow-modify-edit-keypad=|\ 196 | --allow-modify-function=|--allow-modify-keypad=|--allow-modify-misc=|--alternate-scroll=|\ 197 | --appcursor=|--appkey=|--autowrap=|--backspace-is-del=|--blend-all-background=|\ 198 | --blend-foreground=|d\--daemon=|--delete-is-del=|--erase-scrollback=|--extended-cir=|--fixed=|\ 199 | --force-wayland-csd=|--force-nrcs=|--force-scalable=|--fork=|--has-meta=|--keep-clipboard=|\ 200 | --keep-selection=|--lock-keyboard=|--luit=|--meta-sends-escape=|--nrcs=|--numlock=|\ 201 | --override-boxdrawing=|--unique-uris=|--print-attributes=|--raise-on-bell=|--reverse-video=|\ 202 | --scroll-on-input=|--scroll-on-output=|--select-to-clipboard=|--smooth-scroll=|--special-blink=|\ 203 | --special-bold=|--special-italic=|--special-reverse=|--special-underlined=|--substitute-fonts=|\ 204 | --trace-characters=|--trace-controls=|--trace-events=|--trace-fonts=|--trace-input=|\ 205 | --trace-misc=|--urgent-on-bell=|--use-utf8=|--visual-bell=|--window-ops=|--smooth-resize=|--clone-config=) 206 | COMPREPLY=( $(compgen -P "${prefix}" -W "true false default" -- "${optvalue}") ) 207 | ;; 208 | (--*) 209 | COMPREPLY=( $(compgen -W "${longopts[*]}" -- "${optname}") ) 210 | ;; 211 | (-*) 212 | COMPREPLY=( $(compgen -W "${shortopts[*]} ${longopts[*]}" -- "${optname}") ) 213 | ;; 214 | (*) 215 | local commands=$(compgen -c | grep -vFx "$(compgen -k)" | grep -vE '^([.:[]|nsst)$') 216 | COMPREPLY=( $(compgen -W "${commands[*]}" -- "${optname}") ) 217 | ;; 218 | esac 219 | 220 | return 0 221 | } 222 | 223 | complete -F _nsst nsst 224 | complete -F _nsst nsstc 225 | 226 | # vim: set ft=sh ts=8 sts=8 sw=8 noet: 227 | -------------------------------------------------------------------------------- /completion/yash/nsst: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Evgeniy Baskov. All rights reserved 2 | 3 | # Completion script for nsst terminal emulator. 4 | 5 | function completion/nsst { 6 | 7 | typeset OPTIONS ARGOPT PREFIX _FONTS 8 | OPTIONS=( #># 9 | "--allow-alternate::;Enable alternate screen" 10 | "--allow-blinking::;Allow blinking text and cursor" 11 | "--allow-modify-edit-keypad::;Allow modifying edit keypad keys" 12 | "--allow-modify-function::;Allow modifying function keys" 13 | "--allow-modify-keypad::;Allow modifying keypad keys" 14 | "--allow-modify-misc::;Allow modifying miscellaneous keys" 15 | "--alpha:;Background opacity, requires compositor to be running" 16 | "--alternate-scroll::;Scrolling sends arrow keys escapes in alternate screen" 17 | "--answerback-string:;ENQ report" 18 | "--appcursor::;Initial application cursor mode value" 19 | "--appkey::;Initial application keypad mode value" 20 | "--autorepeat::;Enable key autorepeat" 21 | "--autowrap::;Initial autowrap setting" 22 | "--backend:;Select rendering backend" 23 | "--background:;Default background color" 24 | "--backspace-is-del::;Backspace sends DEL instead of BS" 25 | "--bell:;Bell setting" 26 | "--bell-high-volume:;High volume value for DECSWBV" 27 | "--bell-low-volume:;Low volume value for DECSWBV" 28 | "--bell:;Margin bell setting" 29 | "--blend-all-background::;Apply opacity to all background colors, not just default one" 30 | "--blend-foreground::;Apply opacity to foreground colors" 31 | "--blink-color:;Special color of blinking text" 32 | "--blink-time:;Text blink interval in microseconds" 33 | "--bold-color:;Special color of bold text" 34 | "--border:;Border size" 35 | "--bottom-border:;Bottom border size" 36 | "C: --config:;Configuration file path" 37 | "--clone-config:;Copy config from previous window instead of loading it from file" 38 | "--cursor-background:;Default cursor background color" 39 | "--cursor-foreground:;Default cursor foreground color" 40 | "--cursor-shape:;Shape of cursor" 41 | "--cursor-width:;Width of lines that forms cursor" 42 | "--cwd:;Current working directory for an application" 43 | "d --daemon::;Start terminal as daemon" 44 | "--delete-is-del::;Delete sends DEL symbol instead of escape sequence" 45 | "--double-click-time:;Time gap in microseconds in witch two mouse presses will be considered double" 46 | "--dpi:;DPI value for fonts" 47 | "D: --term-name:;TERM value" 48 | "e:;Execute command" 49 | "--erase-scrollback::;Allow ED 3 to clear scrollback buffer" 50 | "--extended-cir::;Report all SGR attributes in DECCIR" 51 | "f: --font:;Comma-separated list of fontconfig font patterns" 52 | "--fixed::;Don't allow to change window size, if supported" 53 | "--fkey-increment:;Step in numbering function keys" 54 | "--font-gamma:;Factor of font sharpening" 55 | "--font-size:;Font size in points" 56 | "--font-size-step:;Font size step in points" 57 | "--font-spacing:;Additional spacing for individual symbols" 58 | "--font-cache-size:;Number of cached glyphs per font" 59 | "--force-mouse-mod:;Modifier to force mouse action" 60 | "--force-nrcs::;Enable NRCS translation when UTF-8 mode is enabled" 61 | "--force-scalable::;Do not search for pixmap fonts" 62 | "--force-wayland-csd::;Don't request SSD" 63 | "--foreground:;Default foreground color" 64 | "--fork::;Fork in daemon mode" 65 | "--fps:;Window refresh rate" 66 | "--frame-wait-delay:;Maximal time since last application output before redraw" 67 | "G: --char-geometry:;Window geometry in characters, format is [=][{xX}][{+-}{+-}]" 68 | "g: --geometry:;Window geometry, format is [=][{xX}][{+-}{+-}]" 69 | "--has-meta::;Handle meta/alt" 70 | "h --help;Print this message and exit" 71 | "--horizontal-border:;Horizontal border size (deprecated)" 72 | "H: --scrollback-size:;Number of saved lines" 73 | "--italic-color:;Special color of italic text" 74 | "--keep-clipboard::;Reuse copied clipboard content instead of current selection data" 75 | "--keep-selection::;Don't clear X11 selection when not highlighted" 76 | "--keyboard-dialect:;National replacement character set to be used in non-UTF-8 mode" 77 | "--keyboard-mapping:;Initial keyboard mapping" 78 | "--key-break:;Send break hotkey" 79 | "--key-copy:;Copy to clipboard hotkey" 80 | "--key-copy-uri:;Copy selected URI to clipboard hotkey" 81 | "--key-dec-font:;Decrement font size hotkey" 82 | "--key-inc-font:;Increment font size hotkey" 83 | "--key-jump-next-cmd:;Jump to next command beginning hotkey" 84 | "--key-jump-prev-cmd:;Jump to previous command beginning hotkey" 85 | "--key-new-window:;Create new window hotkey" 86 | "--key-numlock:;'appkey' mode allow toggle hotkey" 87 | "--key-paste:;Paste from clipboard hotkey" 88 | "--key-reload-config:;Reload configuration hotkey" 89 | "--key-reset-font:;Reset font size hotkey" 90 | "--key-reset:;Terminal reset hotkey" 91 | "--key-reverse-video:;Toggle reverse video mode hotkey" 92 | "--key-scroll-down:;Scroll down hotkey" 93 | "--key-scroll-up:;Scroll up hotkey" 94 | "--left-border:;Left border size" 95 | "--line-spacing:;Additional lines vertical spacing" 96 | "L: --log-level:;Filtering level of logged information" 97 | "--lock-keyboard::;Disable keyboard input" 98 | "--luit" 99 | "--luit-path:;Path to luit executable" 100 | "--luit::;Run luit if terminal doesn't support encoding by itself" 101 | "--margin-bell-column:;Column at which margin bell rings when armed" 102 | "--margin-bell-high-volume:;High volume value for DECSMBV" 103 | "--margin-bell-low-volume:;Low volume value for DECSMBV" 104 | "--max-frame-time:;Maximal time between frames in microseconds" 105 | "--meta-sends-escape::;Alt/Meta sends escape prefix instead of setting 8-th bit" 106 | "--modify-cursor:;Enable encoding modifiers for cursor keys" 107 | "--modify-function:;Enable encoding modifiers for function keys" 108 | "--modify-keypad:;Enable encoding modifiers keypad keys" 109 | "--modify-other:;Enable encoding modifiers for other keys" 110 | "--modify-other-fmt:;Format of encoding modifiers" 111 | "--nrcs::;Enable NRCSs support" 112 | "--numlock::;Initial numlock state" 113 | "--open-cmd:;A command used to open URIs when clicked" 114 | "o: --printer-file:;File where CSI MC output to" 115 | "--override-boxdrawing::;Use built-in box drawing characters" 116 | "--pixel-mode:;Sub-pixel rendering mode" 117 | "--pointer-shape:;Default mouse pointer shape for the window" 118 | "--print-attributes::;Print cell attributes when printing is enabled" 119 | "--print-command:;Program to pipe CSI MC output into" 120 | "--raise-on-bell::;Raise terminal window on bell" 121 | "--resize-pointer-shape:;Mouse pointer shape for window resizing" 122 | "--reversed-color:;Special color of reversed text" 123 | "--reverse-video::;Initial reverse video setting" 124 | "--right-border:;Right border size" 125 | "--scroll-amount:;Number of lines scrolled in a time" 126 | "--scroll-on-input::;Scroll view to bottom on key press" 127 | "--scroll-on-output::;Scroll view to bottom when character in printed" 128 | "--selected-background:;Color of selected background" 129 | "--selected-foreground:;Color of selected text" 130 | "--select-scroll-time:;Delay between scrolls of window while selecting with mouse in microseconds" 131 | "--select-to-clipboard::;Use CLIPBOARD selection to store highlighted data" 132 | "--shell:;Shell to start in new instance" 133 | "--smooth-resize::;Don't force window size to be aligned on character size" 134 | "--smooth-scroll-delay:;Delay between scrolls when DECSCLM is enabled" 135 | "--smooth-scroll::;Initial value of DECSCLM mode" 136 | "--smooth-scroll-step:;Amount of lines per scroll when DECSCLM is enabled" 137 | "--special-blink::;If special color should be used for blinking text" 138 | "--special-bold::;If special color should be used for bold text" 139 | "--special-italic::;If special color should be used for italic text" 140 | "--special-reverse::;If special color should be used for reverse text" 141 | "--special-underlined::;If special color should be used for underlined text" 142 | "s: --socket:;Daemon socket path" 143 | "--substitute-fonts::;Enable substitute font support" 144 | "--sync-timeout:;Synchronous update timeout" 145 | "--tab-width:;Initial width of tab character" 146 | "--term-mod:;Meaning of 'T' modifier" 147 | "--top-border:;Top border size" 148 | "--trace-characters::;Trace interpreted characters" 149 | "--trace-controls::;Trace interpreted control characters and sequences" 150 | "--trace-events::;Trace received events" 151 | "--trace-fonts::;Log font related information" 152 | "--trace-input::;Trace user input" 153 | "--trace-misc::;Trace miscellaneous information" 154 | "--triple-click-time:;Time gap in microseconds in witch tree mouse presses will be considered triple" 155 | "T: --title:;Initial window title" 156 | "t: --vt-version:;Emulated VT version" 157 | "--underlined-color:;Special color of underlined text" 158 | "--underline-width:;Text underline width" 159 | "--unique-uris::;Make distinction between URIs with the same location" 160 | "--urgent-on-bell::;Set window urgency on bell" 161 | "--uri-click-mod:;keyboard modifier used to click-open URIs" 162 | "--uri-color:;Special color of URI text" 163 | "--uri-mode:;Allow URI parsing/clicking" 164 | "--uri-pointer-shape:;Mouse pointer shape for hovering over URI" 165 | "--uri-underline-color:;Special color of URI underline" 166 | "--use-utf8::;Enable UTF-8 I/O" 167 | "--vertical-border:;Vertical border size (deprecated)" 168 | "--visual-bell-time:;Length of visual bell" 169 | "--visual-bell::;Whether bell should be visual or normal" 170 | "v --version;Print version and exit" 171 | "V: --window-class:;X11 Window class" 172 | "--wait-for-configure-delay:;Time gap in microseconds waiting for configure after resize request" 173 | "--window-ops::;Allow window manipulation with escape sequences" 174 | "--word-break:;Symbols treated as word separators when snapping mouse selection" 175 | ) #<# 176 | 177 | command -f completion//parseoptions 178 | case $ARGOPT in 179 | (-) 180 | command -f completion//completeoptions -e 181 | ;; 182 | (D|--term-name) 183 | complete -P "$PREFIX" -- $(toe | cut -f1 | grep -v + | sort -u) 184 | ;; 185 | (--pointer-shape|--resize-pointer-shape|--uri-pointer-shape) 186 | complete -P "$PREFIX" -- $(ls /usr/share/icons/*/cursors | grep -v cursors: | sort -u | xargs echo) 187 | ;; 188 | (--bell|--margin-bell) 189 | complete -P "$PREFIX" -- off low high default 190 | ;; 191 | (--cursor-shape) 192 | complete -P "$PREFIX" -- blinking-block block blinking-underline underline blinking-bar bar default 193 | ;; 194 | (--keyboard-mapping) 195 | complete -P "$PREFIX" -- legacy vt220 hp sun sco default 196 | ;; 197 | (L|--log-level) 198 | complete -P "$PREFIX" -- quiet fatal warn info default 199 | ;; 200 | (--modify-other-fmt) 201 | complete -P "$PREFIX" -- xterm csi-u default 202 | ;; 203 | (--pixel-mode) 204 | complete -P "$PREFIX" -- mono bgr rgb bgrv rgbv default 205 | ;; 206 | (--uri-mode) 207 | complete -P "$PREFIX" -- off manual auto default 208 | ;; 209 | (--backend) 210 | complete -P "$PREFIX" -- auto x11 wayland x11xrender x11shm waylandshm default 211 | ;; 212 | (f|--font) 213 | eval array _FONTS $(fc-list -f '\\"%{family}\\"\n' :spacing=mono | sed -e 's/,/\\"\n\\"/' -e 's/ /\\ /g' | sort -u | sed 's/\n/ /' | xargs echo -n) 214 | complete -P "$PREFIX" -- "$_FONTS" default 215 | ;; 216 | (--cwd) 217 | complete -P "$PREFIX" --directory -- default 218 | ;; 219 | (C|--config|o|--printer-file|s|--socket) 220 | complete -P "$PREFIX" --file -- default 221 | ;; 222 | (--luit-path|--open-cmd|--print-command|--shell) 223 | complete -P "$PREFIX" --executable-file -- default 224 | ;; 225 | (--autorepeat|--allow-alternate|--allow-blinking|--allow-modify-edit-keypad|\ 226 | --allow-modify-function|--allow-modify-keypad|--allow-modify-misc|--alternate-scroll|\ 227 | --appcursor|--appkey|--autowrap|--backspace-is-del|--blend-all-background|\ 228 | --blend-foreground|--daemon|--delete-is-del|--erase-scrollback|--extended-cir|--fixed|\ 229 | --force-wayland-csd|--force-nrcs|--force-scalable|--fork|--has-meta|--keep-clipboard|\ 230 | --keep-selection|--lock-keyboard|--luit|--meta-sends-escape|--nrcs|--numlock|\ 231 | --override-boxdrawing|--unique-uris|--print-attributes|--raise-on-bell|--reverse-video|\ 232 | --scroll-on-input|--scroll-on-output|--select-to-clipboard|--smooth-scroll|--special-blink|\ 233 | --special-bold|--special-italic|--special-reverse|--special-underlined|--substitute-fonts|\ 234 | --trace-characters|--trace-controls|--trace-events|--trace-fonts|--trace-input|\ 235 | --trace-misc|--urgent-on-bell|--use-utf8|--visual-bell|--window-ops|--smooth-resize|--clone-config) 236 | complete -P "$PREFIX" -- true false default 237 | ;; 238 | (''|e) 239 | typeset i=2 240 | while [ $i -le ${WORDS[#]} ]; do 241 | case ${WORDS[i]} in 242 | (--|-e) break;; 243 | esac 244 | i=$((i+1)) 245 | done 246 | command -f completion//getoperands 247 | command -f completion//reexecute 248 | ;; 249 | esac 250 | 251 | } 252 | 253 | # vim: set ft=sh ts=8 sts=8 sw=8 noet: 254 | -------------------------------------------------------------------------------- /completion/yash/nsstc: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Evgeniy Baskov. All rights reserved 2 | 3 | # Completion script for nsst terminal emulator client. 4 | 5 | function completion/nsstc { 6 | command -f completion//reexecute nsst 7 | } 8 | 9 | # vim: set ft=sh ts=8 sts=8 sw=8 noet: 10 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022,2025, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef CONFIG_H_ 4 | #define CONFIG_H_ 1 5 | 6 | #include 7 | #include 8 | 9 | #include "feature.h" 10 | #include "font.h" 11 | #include "line.h" 12 | #include "nrcs.h" 13 | 14 | enum cursor_type { 15 | cusor_type_block_blink = 1, 16 | cusor_type_block = 2, 17 | cusor_type_underline_blink = 3, 18 | cusor_type_underline = 4, 19 | cusor_type_bar_blink = 5, 20 | cusor_type_bar = 6, 21 | }; 22 | 23 | enum shortcut_action { 24 | shortcut_none, 25 | shortcut_break, 26 | shortcut_numlock, 27 | shortcut_scroll_up, 28 | shortcut_scroll_down, 29 | shortcut_font_up, 30 | shortcut_font_down, 31 | shortcut_font_default, 32 | shortcut_new_window, 33 | shortcut_reset, 34 | shortcut_reload_config, 35 | shortcut_copy, 36 | shortcut_copy_uri, 37 | shortcut_paste, 38 | shortcut_reverse_video, 39 | shortcut_view_next_cmd, 40 | shortcut_view_prev_cmd, 41 | shortcut_MAX 42 | }; 43 | 44 | enum renderer_backend { 45 | renderer_auto, 46 | renderer_x11, 47 | renderer_wayland, 48 | renderer_x11_xrender, 49 | renderer_x11_shm, 50 | renderer_wayland_shm, 51 | }; 52 | 53 | enum keyboad_mapping { 54 | keymap_default, 55 | keymap_legacy, 56 | keymap_vt220, 57 | keymap_hp, 58 | keymap_sun, 59 | keymap_sco, 60 | keymap_MAX 61 | }; 62 | 63 | enum uri_mode { 64 | uri_mode_off, 65 | uri_mode_manual, 66 | uri_mode_auto, 67 | }; 68 | 69 | #define MAX_DOMAIN_NAME 254 70 | 71 | struct global_config { 72 | uint64_t font_cache_size; 73 | 74 | char *sockpath; 75 | char hostname[MAX_DOMAIN_NAME]; 76 | 77 | enum renderer_backend backend; 78 | 79 | int log_level; 80 | 81 | bool unique_uris; 82 | bool daemon_mode; 83 | bool clone_config; 84 | bool trace_characters; 85 | bool trace_controls; 86 | bool trace_events; 87 | bool trace_fonts; 88 | bool trace_input; 89 | bool trace_misc; 90 | bool want_luit; 91 | bool fork; 92 | bool log_color; 93 | }; 94 | 95 | struct geometry { 96 | struct rect r; 97 | bool stick_to_bottom : 1; 98 | bool stick_to_right : 1; 99 | bool char_geometry : 1; 100 | bool has_position : 1; 101 | bool has_extent : 1; 102 | }; 103 | 104 | struct instance_config { 105 | color_t palette[PALETTE_SIZE]; 106 | 107 | double gamma; 108 | double dpi; 109 | double alpha; 110 | 111 | int64_t fps; 112 | int64_t smooth_scroll_delay; 113 | int64_t blink_time; 114 | int64_t visual_bell_time; 115 | int64_t max_frame_time; 116 | int64_t frame_finished_delay; 117 | int64_t sync_time; 118 | int64_t double_click_time; 119 | int64_t triple_click_time; 120 | int64_t select_scroll_time; 121 | int64_t scrollback_size; 122 | int64_t wait_for_configure_delay; 123 | 124 | struct shortcut { 125 | uint32_t ksym; 126 | uint32_t mask; 127 | } cshorts[shortcut_MAX]; 128 | 129 | char *key[shortcut_MAX]; 130 | char *word_separators; 131 | char *cwd; 132 | char *printer_cmd; 133 | char *printer_file; 134 | char *luit; 135 | char *terminfo; 136 | char **argv; 137 | char *answerback_string; 138 | char *title; 139 | char *window_class; 140 | char *font_name; 141 | char *config_path; 142 | char *term_mod; 143 | char *force_mouse_mod; 144 | char *shell; 145 | char *uri_click_mod; 146 | char *normal_pointer; 147 | char *resize_pointer; 148 | char *uri_pointer; 149 | char *open_command; 150 | char *notify_command; 151 | 152 | enum keyboad_mapping mapping; 153 | enum cursor_type cursor_shape; 154 | enum pixel_mode pixel_mode; 155 | enum charset keyboard_nrcs; 156 | enum uri_mode uri_mode; 157 | int modify_other_fmt; 158 | int margin_bell_volume; 159 | int bell_volume; 160 | 161 | uint32_t force_mouse_mask; 162 | uint32_t uri_click_mask; 163 | 164 | struct geometry geometry; 165 | 166 | int16_t tab_width; 167 | int16_t vt_version; 168 | int16_t margin_bell_column; 169 | int16_t smooth_scroll_step; 170 | int16_t underline_width; 171 | int16_t cursor_width; 172 | struct border { 173 | int16_t left; 174 | int16_t right; 175 | int16_t top; 176 | int16_t bottom; 177 | } border; 178 | int16_t font_size; 179 | int16_t font_size_step; 180 | int16_t font_spacing; 181 | int16_t line_spacing; 182 | int16_t scroll_amount; 183 | 184 | uint8_t margin_bell_high_volume; 185 | uint8_t margin_bell_low_volume; 186 | uint8_t bell_high_volume; 187 | uint8_t bell_low_volume; 188 | uint8_t modify_cursor; 189 | uint8_t modify_function; 190 | uint8_t modify_keypad; 191 | uint8_t fkey_increment; 192 | uint8_t modify_other; 193 | 194 | bool allow_altscreen; 195 | bool allow_blinking; 196 | bool allow_erase_scrollback; 197 | bool allow_legacy_edit; 198 | bool allow_legacy_function; 199 | bool allow_legacy_keypad; 200 | bool allow_legacy_misc; 201 | bool allow_luit; 202 | bool allow_nrcs; 203 | bool allow_subst_font; 204 | bool allow_window_ops; 205 | bool alternate_scroll; 206 | bool appcursor; 207 | bool appkey; 208 | bool autorepeat; 209 | bool backspace_is_delete; 210 | bool blend_all_bg; 211 | bool blend_fg; 212 | bool delete_is_delete; 213 | bool extended_cir; 214 | bool fixed; 215 | bool force_scalable; 216 | bool force_utf8_nrcs; 217 | bool force_utf8_title; 218 | bool force_wayland_csd; 219 | bool has_meta; 220 | bool keep_clipboard; 221 | bool keep_selection; 222 | bool lock; 223 | bool meta_is_esc; 224 | bool numlock; 225 | bool override_boxdraw; 226 | bool print_attr; 227 | bool raise_on_bell; 228 | bool reverse_video; 229 | bool scroll_on_input; 230 | bool scroll_on_output; 231 | bool select_to_clipboard; 232 | bool smooth_resize; 233 | bool smooth_scroll; 234 | bool special_blink; 235 | bool special_bold; 236 | bool special_italic; 237 | bool special_reverse; 238 | bool special_underline; 239 | bool urgency_on_bell; 240 | bool utf8; 241 | bool visual_bell; 242 | bool wrap; 243 | }; 244 | 245 | extern struct global_config gconfig; 246 | extern char *default_config_path; 247 | 248 | struct option; 249 | 250 | void init_options(const char *config_path); 251 | void free_options(void); 252 | 253 | struct option *find_option_entry(const char *name, bool need_warn); 254 | struct option *find_short_option_entry(char name); 255 | bool is_boolean_option(struct option *opt); 256 | 257 | bool set_option_entry(struct instance_config *c, struct option *, const char *value, int allow_global); 258 | void set_default_dpi(double dpi, struct instance_config *cfg); 259 | void copy_config(struct instance_config *dst, struct instance_config *src); 260 | void free_config(struct instance_config *src); 261 | void init_instance_config(struct instance_config *cfg, const char *config_path, int allow_global); 262 | 263 | #endif 264 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | use_boxdrawing=1 4 | use_precompose=1 5 | debug_lines=0 6 | use_clang=0 7 | use_static= 8 | use_uri=1 9 | prefix=/usr/local 10 | name=nsst 11 | use_x11=0 12 | use_wayland=0 13 | use_x11shm=unset 14 | use_x11xrender=1 15 | use_waylandshm=unset 16 | cflags='-O2 -flto=auto' 17 | vars= 18 | version=20504 19 | 20 | # Test some features by trying compiling snippets 21 | linkrt=' -lrt ' 22 | testcc='cc' 23 | 24 | emptytest="int main(){}\n" 25 | shmtest="#define _POSIX_C_SOURCE 200809L\n#include \nint main(){shm_open(0,0,0);}\n" 26 | ppolltest="#define _GNU_SOURCE\n#include \nint main(){ppoll(0,0,0,0);}\n" 27 | memfdtest="#define _GNU_SOURCE\n#include \nint main(){memfd_create(0,0);}\n" 28 | nanosleeptest="#define _POSIX_C_SOURCE 200809L\n#include \nint main(){clock_nanosleep(0,0,0,0);}\n" 29 | 30 | warns="-Walloca -Wno-aggressive-loop-optimizations" 31 | warns="$warns -Wdisabled-optimization -Wduplicated-branches -Wduplicated-cond" 32 | warns="$warns -Wignored-attributes -Wincompatible-pointer-types" 33 | warns="$warns -Winit-self -Wwrite-strings -Wvla -Wundef" 34 | warns="$warns -Wmissing-attributes -Wmissing-format-attribute -Wmissing-noreturn" 35 | warns="$warns -Wswitch-bool -Wpacked -Wshadow -Wformat-security" 36 | warns="$warns -Wswitch-unreachable -Wlogical-op -Wstringop-truncation" 37 | warns="$warns -Wbad-function-cast -Wnested-externs -Wstrict-prototypes" 38 | outwarns='' 39 | 40 | dotest() { 41 | echo -n "Testing for '$2'... " >&2 42 | if printf "$1" | $testcc $linkrt -x c - >/dev/null 2>&1; then 43 | rm -f a.out 44 | printf "\033[1;32mYES\033[m.\n" >&2 45 | return 0 46 | else 47 | printf "\033[1;31mNO\033[m.\n" >&2 48 | return 1 49 | fi 50 | } 51 | 52 | dotestwarn() { 53 | echo -n "Testing for '$1' warning... " >&2 54 | if printf "$emptytest" | $testcc -Werror $1 -x c - >/dev/null 2>&1; then 55 | rm -f a.out 56 | printf "\033[1;32mYES\033[m.\n" >&2 57 | return 0 58 | else 59 | printf "\033[1;31mNO\033[m.\n" >&2 60 | return 1 61 | fi 62 | } 63 | 64 | help() { 65 | cat < "./feature.h" \ 300 | -e "s:@USE_PPOLL@:$use_ppoll:g" \ 301 | -e "s:@USE_CLOCK_NANOSLEEP@:$use_nanosleep:g" \ 302 | -e "s:@USE_BOXDRAWING@:$use_boxdrawing:g" \ 303 | -e "s:@USE_X11SHM@:$use_x11shm:g" \ 304 | -e "s:@USE_X11@:$use_x11:g" \ 305 | -e "s:@USE_XRENDER@:$use_x11xrender:g" \ 306 | -e "s:@USE_PRECOMPOSE@:$use_precompose:g" \ 307 | -e "s:@USE_WAYLAND@:$use_wayland:g" \ 308 | -e "s:@USE_WAYLANDSHM@:$use_waylandshm:g" \ 309 | -e "s:@USE_POSIX_SHM@:$use_posix_shm:g" \ 310 | -e "s:@USE_MEMFD@:$use_memfd:g" \ 311 | -e "s:@USE_URI@:$use_uri:g" \ 312 | -e "s:@DEBUG_LINES@:$debug_lines:g" \ 313 | -e "s:@VERSION@:$version:g" 314 | 315 | sed < "./Makefile.in" > "./Makefile" \ 316 | -e "s:@NAME@:$name:" \ 317 | -e "s:@PREFIX@:$prefix:" \ 318 | -e "s:@BINDIR@:$bindir:" \ 319 | -e "s:@MANDIR@:$mandir:" \ 320 | -e "s:@SHAREDIR@:$sharedir:" \ 321 | -e "s:@DEPS@:$deps:" \ 322 | -e "s:@STATIC@:$use_static:" \ 323 | -e "s:@EXTRAWARNINGS@:$outwarns:" \ 324 | -e "s:@LIBRT@:$linkrt:" \ 325 | -e "s:@OBJECTS@:$objs:" \ 326 | -e "s:@VARS@:$vars:" 327 | 328 | cat < 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #define MAX_ARG_LEN 512 24 | #define INIT_ARGN 4 25 | #define ARGN_STEP(x) (MAX(INIT_ARGN, 3*(x)/2)) 26 | #define NUM_PENDING 8 27 | 28 | static void handle_launch(void *data_, uint32_t mask); 29 | static void handle_daemon(void *data_, uint32_t mask); 30 | 31 | struct pending_launch { 32 | struct list_head link; 33 | struct event *evt; 34 | ssize_t argn; 35 | ssize_t argcap; 36 | int fd; 37 | char **args; 38 | struct instance_config cfg; 39 | }; 40 | 41 | struct daemon_context { 42 | struct list_head pending_launches; 43 | struct event *socket_event; 44 | int socket_fd; 45 | }; 46 | 47 | static struct daemon_context ctx; 48 | 49 | static void daemonize(void) { 50 | pid_t pid = fork(); 51 | if (pid > 0) 52 | _exit(0); 53 | else if (pid < 0) 54 | die("Can't fork() daemon: %s", strerror(errno)); 55 | 56 | if (setsid() < 0) 57 | die("Can't setsid(): %s", strerror(errno)); 58 | 59 | pid = fork(); 60 | if (pid > 0) 61 | _exit(0); 62 | else if (pid < 0) 63 | die("Can't fork() daemon: %s", strerror(errno)); 64 | 65 | int devnull = open("/dev/null", O_RDONLY); 66 | dup2(STDERR_FILENO, STDOUT_FILENO); 67 | dup2(devnull, STDIN_FILENO); 68 | close(devnull); 69 | 70 | if (chdir("/") < 0) 71 | warn("chdir(): %s", strerror(errno)); 72 | } 73 | 74 | 75 | bool init_daemon(void) { 76 | list_init(&ctx.pending_launches); 77 | ctx.socket_fd = -1; 78 | 79 | struct sockaddr_un addr; 80 | memset(&addr, 0, sizeof addr); 81 | addr.sun_family = AF_UNIX; 82 | strncpy(addr.sun_path, gconfig.sockpath, sizeof addr.sun_path - 1); 83 | 84 | int fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); 85 | if (fd < 0) { 86 | warn("Can't create daemon socket: %s", strerror(errno)); 87 | return false; 88 | } 89 | 90 | set_cloexec(fd); 91 | 92 | if (bind(fd, (struct sockaddr*)&addr, 93 | offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path)) < 0) { 94 | warn("Can't bind daemon socket: %s", strerror(errno)); 95 | close(fd); 96 | return false; 97 | } 98 | 99 | if (listen(fd, NUM_PENDING) < 0) { 100 | warn("Can't listen to daemon socket: %s", strerror(errno)); 101 | close(fd); 102 | unlink(gconfig.sockpath); 103 | return false; 104 | } 105 | 106 | ctx.socket_fd = fd; 107 | ctx.socket_event = poller_add_fd(handle_daemon, NULL, fd, POLLIN); 108 | poller_set_autoreset(ctx.socket_event, &ctx.socket_event); 109 | 110 | if (gconfig.fork) daemonize(); 111 | return true; 112 | } 113 | 114 | static void free_pending_launch(struct pending_launch *lnch) { 115 | list_remove(&lnch->link); 116 | 117 | close(lnch->fd); 118 | poller_remove(lnch->evt); 119 | 120 | for (ssize_t i = 0; i < lnch->argn; i++) 121 | free(lnch->args[i]); 122 | free(lnch->args); 123 | free_config(&lnch->cfg); 124 | free(lnch); 125 | } 126 | 127 | void free_daemon(void) { 128 | if (!gconfig.daemon_mode) 129 | return; 130 | 131 | LIST_FOREACH_SAFE(it, &ctx.pending_launches) 132 | free_pending_launch(CONTAINEROF(it, struct pending_launch, link)); 133 | 134 | poller_unset(&ctx.socket_event); 135 | 136 | if (ctx.socket_fd > 0) { 137 | close(ctx.socket_fd); 138 | ctx.socket_fd = -1; 139 | } 140 | unlink(gconfig.sockpath); 141 | gconfig.daemon_mode = false; 142 | } 143 | 144 | static bool send_pending_launch_resp(struct pending_launch *lnch, const char *str) { 145 | if (send(lnch->fd, str, strlen(str), 0) < 0) { 146 | warn("Can't send responce to client, dropping"); 147 | free_pending_launch(lnch); 148 | return 0; 149 | } 150 | return 1; 151 | } 152 | 153 | static void append_pending_launch(struct pending_launch *lnch) { 154 | char buffer[MAX(MAX_ARG_LEN, MAX_OPTION_DESC) + 1]; 155 | 156 | ssize_t len = recv(lnch->fd, buffer, MAX_ARG_LEN, 0); 157 | if (len < 0) { 158 | warn("Can't recv argument: %s", strerror(errno)); 159 | free_pending_launch(lnch); 160 | return; 161 | } 162 | 163 | buffer[len] = '\0'; 164 | 165 | if (buffer[0] == '\001' /* SOH */) /* Header */ { 166 | char *cpath = len > 1 ? buffer + 1 : NULL; 167 | if (!cpath && gconfig.clone_config) 168 | copy_config(&lnch->cfg, &global_instance_config); 169 | else 170 | init_instance_config(&lnch->cfg, cpath, 0); 171 | } else if (buffer[0] == '\003' /* ETX */ && len == 1) /* End of configuration */ { 172 | if (lnch->args) lnch->args[lnch->argn] = NULL; 173 | lnch->cfg.argv = lnch->args; 174 | create_window(&lnch->cfg); 175 | free_pending_launch(lnch); 176 | } else if (buffer[0] == '\034' /* FS */ && len > 1) /* Short option */ { 177 | char *name = buffer + 1, *value = memchr(buffer + 1, '=', len); 178 | if (!value || name + 1 != value) { 179 | warn("Wrong option format: '%s'", name); 180 | return; 181 | } 182 | 183 | struct option *opt = find_short_option_entry(*name); 184 | if (opt) set_option_entry(&lnch->cfg, opt, value, 0); 185 | } else if (buffer[0] == '\035' /* GS */ && len > 1) /* Option */ { 186 | char *name = buffer + 1, *value = memchr(buffer + 1, '=', len); 187 | if (!value) { 188 | warn("Wrong option format: '%s'", name); 189 | return; 190 | } 191 | 192 | *value++ = '\0'; 193 | struct option *opt = find_option_entry(name, true); 194 | if (opt) set_option_entry(&lnch->cfg, opt, value, 0); 195 | } else if (buffer[0] == '\036' /* RS */ && len > 1) /* Argument */ { 196 | if (lnch->argn + 2 > lnch->argcap) { 197 | ssize_t newsz = ARGN_STEP(lnch->argcap); 198 | lnch->args = xrealloc(lnch->args, lnch->argcap*sizeof(*lnch->args), newsz*sizeof(*lnch->args)); 199 | lnch->argcap = newsz; 200 | } 201 | 202 | lnch->args[lnch->argn++] = strdup(buffer + 1); 203 | } else if (buffer[0] == '\005' /* ENQ */ && len == 1) /* Version text request */ { 204 | const char *resps[] = {version_string(), "Features: ", features_string()}; 205 | 206 | for (size_t i = 0; i < LEN(resps); i++) 207 | if (!send_pending_launch_resp(lnch, resps[i])) return; /* Don't free pending_launch twice */ 208 | 209 | free_pending_launch(lnch); 210 | } else if (buffer[0] == '\025' /* NAK */ && len == 1) /* Usage text request */ { 211 | ssize_t i = 0; 212 | for (const char *part; (part = usage_string(buffer, i++));) 213 | if (!send_pending_launch_resp(lnch, part)) return; /* Don't free pending_launch twice */ 214 | 215 | free_pending_launch(lnch); 216 | } else if (buffer[0] == '\031' /* EM */ && len == 1) /* Exit daemon*/ { 217 | poller_stop(); 218 | free_pending_launch(lnch); 219 | } 220 | } 221 | 222 | static void accept_pending_launch(void) { 223 | int fd = accept(ctx.socket_fd, NULL, NULL); 224 | 225 | set_cloexec(fd); 226 | 227 | struct pending_launch *lnch = xzalloc(sizeof(struct pending_launch)); 228 | if (fd < 0 || !(lnch->evt = poller_add_fd(handle_launch, lnch, fd, POLLIN))) { 229 | close(fd); 230 | free(lnch); 231 | warn("Can't create pending launch: %s", strerror(errno)); 232 | } else { 233 | lnch->fd = fd; 234 | list_insert_after(&ctx.pending_launches, &lnch->link); 235 | } 236 | } 237 | 238 | static void handle_launch(void *data_, uint32_t mask) { 239 | struct pending_launch *holder = data_; 240 | 241 | if (mask & POLLIN) 242 | append_pending_launch(holder); 243 | else if (mask & (POLLERR | POLLHUP | POLLNVAL)) 244 | free_pending_launch(holder); 245 | } 246 | 247 | static void handle_daemon(void *data_, uint32_t mask) { 248 | (void)data_; 249 | 250 | if (ctx.socket_fd < 0) return; 251 | 252 | if (mask & POLLIN) 253 | accept_pending_launch(); 254 | else if (mask & (POLLERR | POLLNVAL | POLLHUP)) 255 | free_daemon(); 256 | } 257 | -------------------------------------------------------------------------------- /docs/nsst.conf: -------------------------------------------------------------------------------- 1 | # Not So Simple Terminal 2 | # Default config path is $XDG_CONFIG_HOME/nsst.conf, $XDG_CONFIG_HOME/nsst/nsst.conf, 3 | # ~/.config/nsst/nsst.conf or ~/.config/nsst.conf 4 | # Config path can be selected with --config= or -C 5 | 6 | # Example of configuration file 7 | # WARN: Not all options are present here 8 | 9 | # Comments starts with # and are allowed only on empty lines 10 | # Options are in format key=value, one per line, with optional spaces 11 | 12 | # Background opacity 13 | alpha=0.8 14 | 15 | # Configure fonts 16 | 17 | # Comma-separated list of fonts 18 | # All following fonts are resized to the size of first font 19 | 20 | font=mono-13 21 | 22 | # Font sharpening coef. 23 | 24 | font-gamma=1.2 25 | 26 | # Set pixel mode to enable subpixel rendering 27 | # Need to be the same as your monitors subpixels order 28 | 29 | pixel-mode=rgb 30 | 31 | # Use builtin pseudographic symbols 32 | # since a lot of fonts lacks them or 33 | # they are unalligned 34 | 35 | override-boxdrawing=true 36 | 37 | # Don't allow applications to clear scrollback buffer with \e[3J 38 | 39 | erase-scrollback=false 40 | 41 | # Disable all kinds of bell 42 | # It can be annoying 43 | 44 | visual-bell=false 45 | bell=off 46 | 47 | # Color theme 48 | # Colors can be in format #RGB or rgb:R/G/B 49 | # where R, G and B are one to four hex digits 50 | 51 | # This is default colorscheme 52 | 53 | background=#222222 54 | foreground=#FFFFCC 55 | cursor-background=#000000 56 | cursor-foreground=#FFFFFF 57 | 58 | color0=#222222 59 | color1=#FF4433 60 | color2=#BBBB22 61 | color3=#FFBB22 62 | color4=#88AA99 63 | color5=#DD8899 64 | color6=#88CC77 65 | color7=#DDCCAA 66 | color8=#665555 67 | color9=#FF4433 68 | color10=#BBBB22 69 | color11=#FFBB22 70 | color12=#88AA99 71 | color13=#DD8899 72 | color14=#88CC77 73 | color15=#FFFFCC 74 | -------------------------------------------------------------------------------- /docs/nsst.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=Nsst 4 | Comment=Not So Simple Terminal Emulator 5 | Encoding=UTF-8 6 | Categories=System;TerminalEmulator; 7 | 8 | TryExec=nsst 9 | Exec=nsst 10 | Terminal=false 11 | StartupWMClass=Nsst 12 | -------------------------------------------------------------------------------- /docs/nsstc.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=Nsstc 4 | Comment=Not So Simple Terminal Emulator Client 5 | Encoding=UTF-8 6 | Categories=System;TerminalEmulator; 7 | 8 | TryExec=nsstc 9 | Exec=nsstc -d 10 | Terminal=false 11 | StartupWMClass=Nsst 12 | -------------------------------------------------------------------------------- /feature.h.in: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2021, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef FEATURES_H_ 4 | #define FEATURES_H_ 1 5 | 6 | #define _XOPEN_SOURCE 700 7 | #define _POSIX_C_SOURCE 200809L 8 | 9 | /* This allows better frames timing */ 10 | #define USE_PPOLL @USE_PPOLL@ 11 | 12 | #define USE_MEMFD @USE_MEMFD@ 13 | 14 | #define USE_CLOCK_NANOSLEEP @USE_CLOCK_NANOSLEEP@ 15 | 16 | /* Ability to override box drawing characters */ 17 | #define USE_BOXDRAWING @USE_BOXDRAWING@ 18 | 19 | /* X11 Support */ 20 | #define USE_X11 @USE_X11@ 21 | 22 | /* X11 Backend (MIT-SHM) */ 23 | #define USE_X11SHM @USE_X11SHM@ 24 | 25 | /* X11 Backend (XRender) */ 26 | #define USE_XRENDER @USE_XRENDER@ 27 | 28 | /* Wayland Support */ 29 | #define USE_WAYLAND @USE_WAYLAND@ 30 | 31 | /* Wayland Shared memory Backend Support */ 32 | #define USE_WAYLANDSHM @USE_WAYLANDSHM@ 33 | 34 | /* Unicode combining characters precomposition */ 35 | #define USE_PRECOMPOSE @USE_PRECOMPOSE@ 36 | 37 | /* Use posix shm interface */ 38 | #define USE_POSIX_SHM @USE_POSIX_SHM@ 39 | 40 | /* URI opening/parsing/highlighting */ 41 | #define USE_URI @USE_URI@ 42 | 43 | /* NSST Version number */ 44 | #define NSST_VERSION @VERSION@ 45 | 46 | /* Enable internal data structures validation. 47 | * NOTE: It is really slows down terminal */ 48 | #define DEBUG_LINES @DEBUG_LINES@ 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /font.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022,2025, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef FONT_H_ 4 | #define FONT_H_ 1 5 | 6 | #include "feature.h" 7 | 8 | #include "hashtable.h" 9 | #include "list.h" 10 | #include "util.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | /* IMPORTANT: Order should be the same as 17 | * in enum cell_attr */ 18 | enum face_name { 19 | face_normal = 0, 20 | face_italic = 1 << 0, 21 | face_bold = 1 << 1, 22 | face_bold_italic = face_bold | face_italic, 23 | face_MAX = 4, 24 | }; 25 | 26 | enum pixel_mode { 27 | pixmode_mono, 28 | pixmode_bgr, 29 | pixmode_rgb, 30 | pixmode_bgrv, 31 | pixmode_rgbv, 32 | pixmode_bgra, 33 | }; 34 | 35 | #define GLYPH_ALIGNMENT MAX(16, MALLOC_ALIGNMENT) 36 | 37 | struct glyph { 38 | ht_head_t head; 39 | struct list_head lru; 40 | 41 | #ifdef USE_XRENDER 42 | uint32_t id; 43 | #endif 44 | 45 | uint32_t g; 46 | uint16_t width, height; 47 | int16_t x, y; 48 | int16_t x_off, y_off; 49 | int16_t stride; 50 | 51 | int16_t pixmode; 52 | _Alignas(GLYPH_ALIGNMENT) uint8_t data[]; 53 | }; 54 | 55 | #define GLYPH_UNDERCURL UINT32_MAX 56 | 57 | struct font *create_font(const char* descr, double size, double dpi, double gamma, bool force_scalable, bool allow_subst); 58 | void free_font(struct font *font); 59 | struct font *font_ref(struct font *font); 60 | int16_t font_get_size(struct font *font); 61 | 62 | struct glyph_cache *create_glyph_cache(struct font *font, enum pixel_mode pixmode, int16_t vspacing, int16_t hspacing, int16_t underline_width, bool boxdraw, bool force_aligned); 63 | struct glyph_cache *glyph_cache_ref(struct glyph_cache *ref); 64 | void free_glyph_cache(struct glyph_cache *cache); 65 | void glyph_cache_get_dim(struct glyph_cache *cache, int16_t *w, int16_t *h, int16_t *d); 66 | struct glyph *glyph_cache_fetch(struct glyph_cache *cache, uint32_t ch, enum face_name face, bool *is_new); 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /hashtable.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef HASHTABLE_H_ 4 | #define HASHTABLE_H_ 1 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define HT_INIT_CAPS 8 13 | 14 | #ifndef HT_CALLOC 15 | #define HT_CALLOC xzalloc 16 | extern void *xzalloc(size_t size); 17 | #endif 18 | 19 | typedef struct ht_head ht_head_t; 20 | typedef struct ht_iter ht_iter_t; 21 | typedef struct hashtable hashtable_t; 22 | typedef bool ht_cmpfn_t(const ht_head_t *a, const ht_head_t *b); 23 | 24 | struct ht_head { 25 | ht_head_t *next; 26 | uintptr_t hash; 27 | }; 28 | 29 | struct hashtable { 30 | ht_cmpfn_t *cmpfn; 31 | intptr_t caps; 32 | intptr_t size; 33 | ht_head_t **data; 34 | }; 35 | 36 | struct ht_iter { 37 | hashtable_t *ht; 38 | ht_head_t **bucket; 39 | ht_head_t **elem; 40 | }; 41 | 42 | extern void ht_shrink(struct hashtable *ht); 43 | extern void ht_adjust(struct hashtable *ht, intptr_t inc); 44 | 45 | static inline size_t ceil_power_of_2(size_t n) { 46 | if (n < 2) return n; 47 | return 1UL << (sizeof(n)*8 - (__builtin_clzl(n - 1))); 48 | } 49 | 50 | static inline ht_iter_t ht_begin(hashtable_t *ht) { 51 | ht_iter_t it = { .ht = ht, ht->data, ht->data }; 52 | ht_head_t **end = ht->data + ht->caps; 53 | 54 | while (!*it.bucket && ++it.bucket < end); 55 | it.elem = it.bucket; 56 | return it; 57 | } 58 | 59 | static inline ht_head_t *ht_next(ht_iter_t *it) { 60 | ht_head_t **end = it->ht->data + it->ht->caps; 61 | if (it->bucket >= end) return NULL; 62 | 63 | ht_head_t *cur = *it->elem; 64 | 65 | if ((*it->elem)->next) { 66 | /* Next in chain */ 67 | it->elem = &(*it->elem)->next; 68 | } else { 69 | /* Next bucket */ 70 | while (++it->bucket < end && !*it->bucket); 71 | it->elem = it->bucket; 72 | } 73 | 74 | return cur; 75 | } 76 | 77 | static inline ht_head_t *ht_current(ht_iter_t *it) { 78 | ht_head_t **end = it->ht->data + it->ht->caps; 79 | if (it->bucket >= end) return NULL; 80 | return *it->elem; 81 | } 82 | 83 | static inline ht_head_t *ht_erase_current(ht_iter_t *it) { 84 | ht_head_t **end = it->ht->data + it->ht->caps; 85 | if (it->bucket >= end) return NULL; 86 | 87 | it->ht->size--; 88 | ht_head_t *cur = *it->elem; 89 | *it->elem = cur->next; 90 | cur->next = NULL; 91 | 92 | if (!*it->elem) { 93 | /* Next bucket */ 94 | while (++it->bucket < end && !*it->bucket); 95 | it->elem = it->bucket; 96 | } 97 | 98 | return cur; 99 | } 100 | 101 | static inline void ht_free(hashtable_t *ht) { 102 | /* This function assumes, that all elements was freed before */ 103 | assert(!ht->size); 104 | 105 | free(ht->data); 106 | *ht = (hashtable_t){ 0 }; 107 | } 108 | 109 | static inline void ht_init(hashtable_t *ht, size_t caps, ht_cmpfn_t *cmpfn) { 110 | caps = ceil_power_of_2(caps); 111 | *ht = (hashtable_t) { 112 | .data = HT_CALLOC(caps * sizeof(ht->data[0])), 113 | .caps = caps, 114 | .cmpfn = cmpfn, 115 | }; 116 | } 117 | 118 | static inline ht_head_t **ht_lookup_ptr(hashtable_t *ht, ht_head_t *elem) { 119 | ht_head_t **cand = &ht->data[elem->hash & (ht->caps - 1)]; 120 | while (*cand) { 121 | if (elem->hash == (*cand)->hash && 122 | ht->cmpfn(*cand, elem)) break; 123 | cand = &(*cand)->next; 124 | } 125 | 126 | return cand; 127 | } 128 | 129 | static inline ht_head_t *ht_insert_hint(hashtable_t *ht, ht_head_t **cand, ht_head_t *elem) { 130 | ht_head_t *old = *cand; 131 | if (!*cand) { 132 | *cand = elem; 133 | ht_adjust(ht, 1); 134 | } 135 | 136 | return old; 137 | } 138 | 139 | static inline ht_head_t *ht_replace_hint(hashtable_t *ht, ht_head_t **cand, ht_head_t *elem) { 140 | ht_head_t *old = *cand; 141 | 142 | *cand = elem; 143 | if (old) { 144 | elem->next = old->next; 145 | old->next = NULL; 146 | } else { 147 | ht_adjust(ht, 1); 148 | } 149 | 150 | return old; 151 | } 152 | 153 | static inline ht_head_t *ht_erase_hint(hashtable_t *ht, ht_head_t **cand) { 154 | ht_head_t *old = *cand; 155 | if (old) { 156 | *cand = old->next; 157 | old->next = NULL; 158 | ht_adjust(ht, -1); 159 | } 160 | return old; 161 | } 162 | 163 | static inline ht_head_t *ht_find(hashtable_t *ht, ht_head_t *elem) { 164 | return *ht_lookup_ptr(ht, elem); 165 | } 166 | 167 | static inline ht_head_t *ht_replace(hashtable_t *ht, ht_head_t *elem) { 168 | ht_head_t **cand = ht_lookup_ptr(ht, elem); 169 | return ht_replace_hint(ht, cand, elem); 170 | } 171 | 172 | static inline ht_head_t *ht_insert(hashtable_t *ht, ht_head_t *elem) { 173 | ht_head_t **cand = ht_lookup_ptr(ht, elem); 174 | return ht_insert_hint(ht, cand, elem); 175 | } 176 | 177 | static inline ht_head_t *ht_erase(hashtable_t *ht, ht_head_t *elem) { 178 | ht_head_t **cand = ht_lookup_ptr(ht, elem); 179 | return ht_erase_hint(ht, cand); 180 | } 181 | 182 | /* Murmur64A */ 183 | static inline uint64_t hash64(const void *vdata, size_t len) { 184 | const uint64_t m = 0xC6A4A7935BD1E995LLU; 185 | 186 | 187 | const uint64_t *data = (const uint64_t *)vdata; 188 | const uint64_t *end = data + (len >> 3); 189 | 190 | uint64_t k = 0, h = 123 ^ (len * m); 191 | while (data < end) { 192 | memcpy(&k, data++, sizeof(k)); 193 | k *= m, k ^= k >> 47, k *= m; 194 | h ^= k, h *= m; 195 | } 196 | 197 | const uint8_t *tail = (const uint8_t *)data; 198 | switch (len & 7) { 199 | case 7: h ^= (uint64_t)tail[6] << 48; /* fallthrough */ 200 | case 6: h ^= (uint64_t)tail[5] << 40; /* fallthrough */ 201 | case 5: h ^= (uint64_t)tail[4] << 32; /* fallthrough */ 202 | case 4: h ^= (uint64_t)tail[3] << 24; /* fallthrough */ 203 | case 3: h ^= (uint64_t)tail[2] << 16; /* fallthrough */ 204 | case 2: h ^= (uint64_t)tail[1] << 8; /* fallthrough */ 205 | case 1: h ^= (uint64_t)tail[0]; 206 | h *= m; 207 | }; 208 | 209 | h ^= h >> 47; 210 | h *= m; 211 | h ^= h >> 47; 212 | return h; 213 | } 214 | 215 | static inline uint64_t uint_hash64(uint64_t h) { 216 | h ^= h >> 33; 217 | h *= 0xFF51AFD7ED558CCDUL; 218 | h ^= h >> 33; 219 | h *= 0xC4CEB9FE1A85EC53UL; 220 | h ^= h >> 33; 221 | return h; 222 | } 223 | 224 | static inline uint32_t uint_hash32(uint32_t v) { 225 | v = ((v >> 16) ^ v) * 0x45D9F3B; 226 | v = ((v >> 16) ^ v) * 0x45D9F3B; 227 | v = (v >> 16) ^ v; 228 | return v; 229 | } 230 | 231 | #endif 232 | -------------------------------------------------------------------------------- /image.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef IMAGE_H_ 4 | #define IMAGE_H_ 1 5 | 6 | #include "feature.h" 7 | 8 | #include "font.h" 9 | #include "util.h" 10 | 11 | #include 12 | 13 | struct image { 14 | int16_t width; 15 | int16_t height; 16 | int shmid; 17 | color_t *data; 18 | }; 19 | 20 | void image_draw_rect(struct image im, struct rect rect, color_t fg); 21 | void image_compose_glyph(struct image im, int16_t dx, int16_t dy, struct glyph *glyph, color_t fg, struct rect clip); 22 | void image_copy(struct image im, struct rect rect, struct image src, int16_t sx, int16_t sy); 23 | void free_image(struct image *im); 24 | struct image create_shm_image(int16_t width, int16_t height); 25 | struct image create_image(int16_t width, int16_t height); 26 | 27 | #define STRIDE_ALIGNMENT 4UL 28 | #define STRIDE(x) (((x) + STRIDE_ALIGNMENT - 1) & ~(STRIDE_ALIGNMENT - 1)) 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /input.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef INPUT_H_ 4 | #define INPUT_H_ 1 5 | 6 | #include "config.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define UDK_MAX 37 13 | 14 | struct keyboard_state { 15 | bool keyboad_vt52 : 1; 16 | 17 | bool modkey_legacy_allow_keypad : 1; 18 | bool modkey_legacy_allow_edit_keypad : 1; 19 | bool modkey_legacy_allow_function : 1; 20 | bool modkey_legacy_allow_misc : 1; 21 | 22 | bool appkey : 1; 23 | bool appcursor : 1; 24 | bool allow_numlock : 1; 25 | bool keylock : 1; 26 | 27 | bool has_meta : 1; 28 | bool meta_escape : 1; 29 | bool backspace_is_del : 1; 30 | bool delete_is_del : 1; 31 | 32 | bool udk_locked : 1; 33 | 34 | bool modkey_other_fmt : 1; 35 | /* 0 -> CSI 27 ; M ; K ~ 36 | * 1 -> CSI K ; M u 37 | */ 38 | 39 | uint16_t modkey_fn : 3; 40 | uint16_t modkey_cursor : 3; 41 | uint16_t modkey_keypad : 3; 42 | /* 0 -> 43 | * 1 -> SS3 ... 44 | * 2 -> CSI ... 45 | * 3 -> CSI 1 ; ... 46 | * 4 -> CSI > 1 ; ... 47 | * 5,6,7 reserved 48 | */ 49 | uint16_t modkey_other : 2; 50 | /* 0 -> nothing 51 | * 1 -> all, but common 52 | * 2 -> all 53 | * 3 reserved 54 | */ 55 | 56 | uint16_t fkey_inc_step : 5; 57 | 58 | enum keyboad_mapping keyboard_mapping; 59 | 60 | struct udk { 61 | uint8_t *val; 62 | size_t len; 63 | } udk[UDK_MAX]; 64 | 65 | }; 66 | 67 | 68 | struct key { 69 | uint32_t utf32; 70 | uint32_t sym; 71 | uint32_t mask; 72 | uint8_t utf8data[6]; /* Zero terminated */ 73 | uint8_t utf8len; 74 | uint8_t ascii : 7; 75 | uint8_t is_fkey : 1; 76 | }; 77 | 78 | struct term; 79 | 80 | void keyboard_handle_input(struct key k, struct term *term); 81 | struct key keyboard_describe_key(struct xkb_state *state, xkb_keycode_t keycode); 82 | void keyboard_reset_udk(struct term *term); 83 | bool keyboard_set_udk(struct term *term, const uint8_t *str, const uint8_t *end, bool reset, bool lock); 84 | enum shortcut_action keyboard_find_shortcut(struct instance_config *cfg, struct key k); 85 | void keyboard_parse_config(struct instance_config *cfg); 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /integration/nsst_fish_integration.fish: -------------------------------------------------------------------------------- 1 | if status --is-interactive; and not functions -q _nsst_prompt_start 2 | function _nsst_prompt_start; printf "\033]133;A\a"; end 3 | function _nsst_prompt_end; printf "\033]133;B\a"; end 4 | function _nsst_command_start; printf "\033]133;C\a"; end 5 | function _nsst_command_end; printf "\033]133;D\a"; end 6 | function _nsst_report_cwd; printf "\033]7;file://$HOSTNAME$PWD\a" > /dev/tty; end 7 | 8 | # Configure shell for avoid redrawing prompt on resize 9 | set -g fish_handle_reflow 0 10 | 11 | function _nsst_preexec_hook --on-event fish_preexec 12 | _nsst_command_start 13 | end 14 | function _nsst_postexec_hook --on-event fish_postexec 15 | _nsst_command_end 16 | end 17 | function _nsst_prompt_hook --on-event fish_prompt 18 | _nsst_prompt_start 19 | end 20 | 21 | # There's no prompt end hook, so we have to decorate the function 22 | functions -c fish_prompt _nsst_old_fish_prompt 23 | function fish_prompt 24 | _nsst_old_fish_prompt 25 | _nsst_prompt_end 26 | end 27 | 28 | function _nsst_cwd_hook --on-variable PWD 29 | _nsst_report_cwd 30 | end 31 | 32 | _nsst_report_cwd 33 | end 34 | -------------------------------------------------------------------------------- /integration/nsst_yash_integration.sh: -------------------------------------------------------------------------------- 1 | if [[ -o interactive ]] && [ -z "$_NSST_INTEGRATION_INITALIZED" ]; then 2 | _NSST_INTEGRATION_INITALIZED=1 3 | 4 | # Stop printing end of the line after command, 5 | # nsst will do it better for us 6 | set ++le-promptsp 7 | 8 | _nsst_prompt_start() { printf "\033]133;A\a"; } 9 | _nsst_prompt_end() { printf "\033]133;B\a"; } 10 | _nsst_command_start() { printf "\033]133;C\a"; } 11 | _nsst_command_end() { printf "\033]133;D\a"; } 12 | _nsst_report_cwd() { printf "\033]7;file://$HOSTNAME$PWD\a" > /dev/tty; } 13 | 14 | # FIXME It's not entirely correct to compare versions like this 15 | if [ "$YASH_VERSION" '>' "2.56.1" ]; then 16 | _nsst_prompt_hook() { 17 | if [ -n "$_NSST_COMMAND_EXECUTED" ]; then 18 | _NSST_COMMAND_EXECUTED= 19 | _nsst_command_end 20 | fi 21 | if [ "${_NSST_MODIFIED_PS1-}" != "$YASH_PS1" ]; then 22 | _NSST_SAVED_PS1="$YASH_PS1" 23 | [[ "$YASH_PS1" != *"$(_nsst_prompt_start)"* ]] && \ 24 | YASH_PS1='\['"$(_nsst_prompt_start)"'\]'"$YASH_PS1" 25 | [[ "$YASH_PS1" != *"$(_nsst_prompt_end)"* ]] && \ 26 | YASH_PS1="$YASH_PS1"'\['"$(_nsst_prompt_end)"'\]' 27 | _NSST_MODIFIED_PS1="$YASH_PS1" 28 | fi 29 | } 30 | 31 | _nsst_precmd_hook() { 32 | YASH_PS1="$_NSST_SAVED_PS1" 33 | _nsst_command_start 34 | _NSST_COMMAND_EXECUTED=1 35 | } 36 | 37 | POST_PROMPT_COMMAND=("$POST_PROMPT_COMMAND" '_nsst_precmd_hook') 38 | else 39 | _nsst_prompt_hook() { 40 | if [ "${_NSST_MODIFIED_PS1-}" != "$YASH_PS1" ]; then 41 | [[ "$YASH_PS1" != *"$(_nsst_prompt_start)"* ]] && \ 42 | YASH_PS1='\['"$(_nsst_prompt_start)"'\]'"$YASH_PS1" 43 | [[ "$YASH_PS1" != *"$(_nsst_prompt_end)"* ]] && \ 44 | YASH_PS1="$YASH_PS1"'\['"$(_nsst_prompt_end)"'\]' 45 | _NSST_MODIFIED_PS1="$YASH_PS1" 46 | fi 47 | if [ "${_NSST_MODIFIED_PS1R-}" != "$YASH_PS1R" ]; then 48 | [[ "$YASH_PS1R" != *"$(_nsst_command_start)"* ]] && \ 49 | YASH_PS1R="$YASH_PS1R"'\['"$(_nsst_command_start)"'\]' 50 | _NSST_MODIFIED_PS1R="$YASH_PS1R" 51 | fi 52 | } 53 | fi 54 | 55 | PROMPT_COMMAND=("$PROMPT_COMMAND" '_nsst_prompt_hook') 56 | YASH_AFTER_CD=("$YASH_AFTER_CD" '_nsst_report_cwd') 57 | 58 | _nsst_report_cwd 59 | fi 60 | -------------------------------------------------------------------------------- /integration/nsst_zsh_integration.sh: -------------------------------------------------------------------------------- 1 | if [[ -o interactive ]] && [ -z "$_NSST_INTEGRATION_INITALIZED" ]; then 2 | _NSST_INTEGRATION_INITALIZED=1 3 | 4 | _nsst_prompt_start() { printf "\033]133;A\a" } 5 | _nsst_prompt_end() { printf "\033]133;B\a" } 6 | _nsst_command_start() { printf "\033]133;C\a" } 7 | _nsst_command_end() { printf "\033]133;D\a" } 8 | 9 | _nsst_precmd_hook() { 10 | if [ -n "${_NSST_COMMAND_EXECUTED-}" ]; then 11 | _NSST_COMMAND_EXECUTED= 12 | _nsst_command_end 13 | fi 14 | if [ "$PS1" != "${_NSST_MODIFIED_PS1-}" ]; then 15 | _NSST_SAVED_PS1="$PS1" 16 | [[ "$PS1" != *"$(_nsst_prompt_start)"* ]] && \ 17 | PS1='%{'"$(_nsst_prompt_start)"'%}'"$PS1" 18 | [[ "$PS1" != *"$(_nsst_prompt_end)"* ]] && \ 19 | PS1="$PS1"'%{'"$(_nsst_prompt_end)"'%}' 20 | _NSST_MODIFIED_PS1="$PS1" 21 | fi 22 | } 23 | 24 | _nsst_preexec_hook() { 25 | PS1="$_NSST_SAVED_PS1" 26 | _NSST_COMMAND_EXECUTED=1 27 | _nsst_command_start 28 | } 29 | 30 | [[ -z ${precmd_functions-} ]] && precmd_functions=() 31 | precmd_functions=($precmd_functions _nsst_precmd_hook) 32 | 33 | [[ -z ${preexec_functions-} ]] && preexec_functions=() 34 | preexec_functions=($preexec_functions _nsst_preexec_hook) 35 | 36 | _NSST_COMMAND_EXECUTED= 37 | 38 | # This is not set by default since oh-my-zsh and others can do it 39 | # by themselves, if you need that one et _NSST_NEED_CD_HOOK to 1. 40 | if [[ -n "$_NSST_NEED_CD_HOOK" ]]; then 41 | _nsst_report_cwd() { 42 | printf "\033]7;file://$HOSTNAME$PWD\a" > /dev/tty; 43 | } 44 | 45 | [[ -z ${chpwd_functions-} ]] && chpwd_functions=() 46 | chpwd_functions=($preexec_functions _nsst_report_cwd) 47 | _nsst_report_cwd 48 | fi 49 | fi 50 | -------------------------------------------------------------------------------- /iswide.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated with tools/gen_tables.py. 3 | * DO NOT EDIT IT DIRECTLY. 4 | * Edit generator instead. 5 | */ 6 | 7 | #ifndef _ISWIDE_H 8 | #define _ISWIDE_H 1 9 | 10 | #include 11 | #include 12 | 13 | /* 14 | * Since Unicode does not allocate code points 15 | * in planes 4-13 (and plane 14 contains only control characters), 16 | * we can save a few bits for attributes by compressing unicode like: 17 | * 18 | * [0x00000, 0x3FFFF] -> [0x00000, 0x3FFFF] (planes 0-3) 19 | * [0x40000, 0xDFFFF] -> nothing 20 | * [0xE0000,0x10FFFF] -> [0x40000, 0x7FFFF] (planes 14-16 -- Special Purpose Plane, PUA) 21 | * 22 | * And with this encoding scheme 23 | * we can encode all defined characters only with 19 bits. 24 | * 25 | * And so we have as much as 13 bits left for flags and attributes. 26 | */ 27 | 28 | #define CELL_ENC_COMPACT_BASE 0x40000 29 | #define CELL_ENC_UTF8_BASE 0xE0000 30 | 31 | static inline uint32_t uncompact(uint32_t u) { 32 | return u < CELL_ENC_COMPACT_BASE ? u : u + (CELL_ENC_UTF8_BASE - CELL_ENC_COMPACT_BASE); 33 | } 34 | 35 | static inline uint32_t compact(uint32_t u) { 36 | return u < CELL_ENC_UTF8_BASE ? u : u - (CELL_ENC_UTF8_BASE - CELL_ENC_COMPACT_BASE); 37 | } 38 | 39 | extern const uint8_t wide_table1_[804]; 40 | extern const uint8_t combining_table1_[1026]; 41 | extern const uint32_t width_data_[118][8]; 42 | 43 | static inline bool iswide_compact(uint32_t x) { 44 | return x - 0x1100U < 0x100*sizeof(wide_table1_)/sizeof(wide_table1_[0]) - 0x1100U && 45 | width_data_[wide_table1_[x >> 8]][(x >> 5) & 7] & (1U << (x & 0x1F)); 46 | } 47 | 48 | static inline bool iswide(uint32_t x) { 49 | return iswide_compact(compact(x)); 50 | } 51 | 52 | 53 | static inline bool iscombining_compact(uint32_t x) { 54 | return x - 0x300U < 0x100*sizeof(combining_table1_)/sizeof(combining_table1_[0]) - 0x300U && 55 | width_data_[combining_table1_[x >> 8]][(x >> 5) & 7] & (1U << (x & 0x1F)); 56 | } 57 | 58 | static inline bool iscombining(uint32_t x) { 59 | return iscombining_compact(compact(x)); 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /list.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2022-2024, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef LIST_H_ 4 | #define LIST_H_ 1 5 | 6 | #include 7 | #include 8 | 9 | struct list_head { 10 | struct list_head *next; 11 | struct list_head *prev; 12 | }; 13 | 14 | #define LIST_FOREACH(it__, head__) \ 15 | for (struct list_head *it__ = (head__)->next; \ 16 | (it__) != (head__); \ 17 | (it__) = (it__)->next) 18 | 19 | #define LIST_FOREACH_SAFE(it__, head__) \ 20 | for (struct list_head *it__ = (head__)->next, *next__ = (head__)->next->next; \ 21 | (it__) != (head__); \ 22 | (it__) = next__, next__ = (it__)->next) 23 | 24 | #define LIST_FOREACH_CONTINUTE_SAFE(it__, continue__, head__) \ 25 | for (struct list_head *it__ = (continue__), *next__ = (continue__)->next; \ 26 | (it__) != (head__); \ 27 | (it__) = next__, next__ = (it__)->next) 28 | 29 | static inline struct list_head *list_remove(struct list_head *head) { 30 | head->next->prev = head->prev; 31 | head->prev->next = head->next; 32 | head->next = head->prev = NULL; 33 | return head; 34 | } 35 | 36 | static inline void list_init(struct list_head *head) { 37 | head->next = head->prev = head; 38 | } 39 | 40 | static inline bool list_empty(struct list_head *head) { 41 | return !head || head->next == head; 42 | } 43 | 44 | static inline struct list_head *list_add(struct list_head *head, struct list_head *prev, struct list_head *next) { 45 | prev->next = head; 46 | next->prev = head; 47 | 48 | head->next = next; 49 | head->prev = prev; 50 | return head; 51 | } 52 | 53 | static inline struct list_head *list_insert_after(struct list_head *head, struct list_head *elem) { 54 | return list_add(elem, head, head->next); 55 | } 56 | 57 | static inline struct list_head *list_insert_before(struct list_head *head, struct list_head *elem) { 58 | return list_add(elem, head->prev, head); 59 | } 60 | 61 | static inline struct list_head *list_add_range(struct list_head *first, struct list_head *last, struct list_head *prev, struct list_head *next) { 62 | first->prev = prev; 63 | last->next = next; 64 | 65 | prev->next = first; 66 | next->prev = last; 67 | return first; 68 | } 69 | 70 | static inline struct list_head *list_insert_range_after(struct list_head *head, struct list_head *first, struct list_head *last) { 71 | return list_add_range(first, last, head, head->next); 72 | } 73 | 74 | static inline struct list_head *list_insert_range_before(struct list_head *head, struct list_head *first, struct list_head *last) { 75 | return list_add_range(first, last, head->prev, head); 76 | } 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /mouse.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef MOUSE_H_ 4 | #define MOUSE_H_ 1 5 | 6 | #include "term.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | struct screen; 13 | 14 | struct segments { 15 | struct line *line; 16 | bool new_line_flag; 17 | uint16_t size; 18 | uint16_t caps; 19 | struct segment { 20 | int32_t offset; 21 | int32_t length; 22 | } segs[]; 23 | }; 24 | 25 | #define SELECTION_EMPTY 0 26 | 27 | struct mouse_state { 28 | int16_t reported_x; 29 | int16_t reported_y; 30 | uint8_t reported_button; 31 | 32 | bool locator_enabled : 1; 33 | bool locator_oneshot : 1; 34 | bool locator_filter : 1; 35 | bool locator_pixels : 1; 36 | bool locator_report_press : 1; 37 | bool locator_report_release : 1; 38 | 39 | struct rect filter; 40 | 41 | enum mouse_mode { 42 | mouse_mode_none, 43 | mouse_mode_x10, 44 | mouse_mode_button, 45 | mouse_mode_drag, 46 | mouse_mode_motion, 47 | } mouse_mode; 48 | 49 | enum mouse_format { 50 | mouse_format_default, 51 | mouse_format_sgr, 52 | mouse_format_utf8, 53 | mouse_format_uxvt, 54 | mouse_format_pixel 55 | } mouse_format; 56 | }; 57 | 58 | struct mouse_selection_iterator { 59 | struct segment *seg; 60 | ssize_t idx; 61 | }; 62 | 63 | void mouse_handle_input(struct term *term, struct mouse_event ev); 64 | void mouse_report_locator(struct term *term, uint8_t evt, int16_t x, int16_t y, uint32_t mask); 65 | void mouse_set_filter(struct term *term, iparam_t xs, iparam_t xe, iparam_t ys, iparam_t ye); 66 | 67 | struct selection_state { 68 | struct window *win; 69 | struct screen *screen; 70 | 71 | size_t seg_caps; 72 | size_t seg_size; 73 | struct segments **seg; 74 | 75 | struct line_handle start; 76 | struct line_handle end; 77 | bool rectangular; 78 | 79 | enum { 80 | snap_none, 81 | snap_word, 82 | snap_line, 83 | snap_command, 84 | } snap; 85 | 86 | enum { 87 | state_sel_none, 88 | state_sel_pressed = mouse_event_press + 1, 89 | state_sel_released = mouse_event_release + 1, 90 | state_sel_progress = mouse_event_motion + 1, 91 | } state; 92 | 93 | struct timespec click0; 94 | struct timespec click1; 95 | struct event *scroll_timer; 96 | 97 | int32_t pending_scroll; 98 | int16_t pointer_x; 99 | int16_t pointer_y; 100 | int16_t pointer_x_raw; 101 | int16_t pointer_y_raw; 102 | 103 | enum clip_target targ; 104 | 105 | bool keep_selection; 106 | bool select_to_clipboard; 107 | }; 108 | 109 | 110 | void free_selection(struct selection_state *sel); 111 | bool init_selection(struct selection_state *sel, struct window *win, struct screen *scr); 112 | 113 | void selection_view_scrolled(struct selection_state *sel, struct screen *scr); 114 | 115 | /* Starts from the last character */ 116 | struct mouse_selection_iterator selection_begin_iteration(struct selection_state *sel, struct line_span *view); 117 | bool is_selected_prev(struct mouse_selection_iterator *it, struct line_span *view, int16_t x); 118 | 119 | void selection_clear(struct selection_state *sel); 120 | void selection_damage(struct selection_state *sel, struct line *line); 121 | void selection_concat(struct selection_state *sel, struct line *dst, struct line *src); 122 | void selection_split(struct selection_state *sel, struct line *line, struct line *tail); 123 | void selection_relocated(struct selection_state *sel, struct line *line); 124 | void selection_load_config(struct selection_state *sel); 125 | void selection_scrolled(struct selection_state *sel, struct screen *scr, ssize_t top, ssize_t bottom, bool save); 126 | bool selection_intersects(struct selection_state *sel, struct line *line, int16_t x0, int16_t x1); 127 | 128 | static inline bool selection_active(struct selection_state *sel) { 129 | return sel->state != state_sel_none && 130 | sel->state != state_sel_pressed; 131 | } 132 | 133 | static inline bool view_selection_intersects(struct selection_state *sel, struct line_span *line, int16_t x0, int16_t x1) { 134 | ssize_t offset = line->offset; 135 | return selection_intersects(sel, line->line, x0 + offset, x1 + offset); 136 | } 137 | 138 | #endif 139 | -------------------------------------------------------------------------------- /multipool.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2023,2025, Evgeniy Baskov. All rights reserved */ 2 | 3 | #include "feature.h" 4 | 5 | #define _GNU_SOURCE 6 | 7 | #include "list.h" 8 | #include "util.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "multipool.h" 19 | 20 | #if 1 21 | #define DO_ALLOC(size) xalloc(size) 22 | #define DO_FREE(x, size) free(x) 23 | #define ALLOC_ERROR NULL 24 | #else 25 | #define DO_ALLOC(size) mmap(NULL, (size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0) 26 | #define DO_FREE(x, size) munmap(x, size) 27 | #define ALLOC_ERROR MAP_FAILED 28 | #endif 29 | 30 | #define GET_PTR_HEADER(ptr) ((struct header *)(ptr)-1) 31 | #define GET_HEADER_POOL(header) ((struct pool *)((uint8_t *)(header)-(header)->offset-sizeof(struct pool))) 32 | 33 | /* allocation metadata */ 34 | struct header { 35 | /* allocation size */ 36 | int32_t size; 37 | /* offset from pool beginning */ 38 | int32_t offset; 39 | /* allocation data */ 40 | uint8_t data[]; 41 | }; 42 | 43 | /* pool metadata */ 44 | struct pool { 45 | struct list_head link; 46 | 47 | int32_t n_alloc; 48 | int32_t offset; 49 | int32_t size; 50 | bool sealed; 51 | uint8_t data[] ALIGNED(MPA_ALIGNMENT/2); 52 | }; 53 | 54 | #define INIT_OFFSET ((int32_t)(ROUNDUP(sizeof(struct pool) + sizeof(struct header), MPA_ALIGNMENT)\ 55 | - (sizeof(struct pool) + sizeof(struct header)))) 56 | 57 | static inline void pool_seal(struct multipool *mp, struct pool *pool) { 58 | list_remove(&pool->link); 59 | mp->unsealed_count--; 60 | pool->sealed = true; 61 | } 62 | 63 | static inline void pool_unseal(struct multipool *mp, struct pool *pool) { 64 | list_insert_after(&mp->unsealed, &pool->link); 65 | mp->unsealed_count++; 66 | pool->sealed = false; 67 | } 68 | 69 | static void pool_free(struct multipool *mp, struct pool *pool) { 70 | if (!pool->sealed) 71 | pool_seal(mp, pool); 72 | 73 | DO_FREE(pool, pool->size + sizeof *pool); 74 | mp->pool_count--; 75 | } 76 | 77 | static struct pool *get_fitting_pool(struct multipool *mp, ssize_t size) { 78 | LIST_FOREACH(it, &mp->unsealed) { 79 | struct pool *p = CONTAINEROF(it, struct pool, link); 80 | if (p->size - p->offset >= size) 81 | return p; 82 | } 83 | 84 | ssize_t pool_size = MAX(mp->pool_size, size + INIT_OFFSET); 85 | struct pool *pool = DO_ALLOC(sizeof *pool + pool_size); 86 | if (pool == ALLOC_ERROR) return NULL; 87 | 88 | memset(pool, 0, sizeof *pool); 89 | 90 | mp->pool_count++; 91 | pool->size = pool_size; 92 | pool->offset = INIT_OFFSET; 93 | pool_unseal(mp, pool); 94 | 95 | return pool; 96 | } 97 | 98 | void mpa_release(struct multipool *mp) { 99 | LIST_FOREACH_SAFE(it, &mp->unsealed) { 100 | struct pool *p = CONTAINEROF(it, struct pool, link); 101 | list_remove(it); 102 | DO_FREE(p, sizeof *p + p->size); 103 | } 104 | 105 | assert(mp->pool_count == mp->unsealed_count); 106 | 107 | memset(mp, 0, sizeof *mp); 108 | } 109 | 110 | void mpa_init(struct multipool *mp, ssize_t pool_size) { 111 | list_init(&mp->unsealed); 112 | mp->max_pad = 0; 113 | mp->pool_count = mp->unsealed_count = 0; 114 | mp->pool_size = pool_size - sizeof(struct pool); 115 | } 116 | 117 | void mpa_free(struct multipool *mp, void *ptr) { 118 | struct header *header = GET_PTR_HEADER(ptr); 119 | struct pool *pool = GET_HEADER_POOL(header); 120 | 121 | assert(pool->n_alloc); 122 | 123 | if (header->offset + header->size == pool->offset) { 124 | pool->offset -= header->size; 125 | if (pool->sealed) 126 | pool_unseal(mp, pool); 127 | } 128 | 129 | if (!--pool->n_alloc) { 130 | pool->offset = INIT_OFFSET; 131 | if (mp->unsealed_count + 1 > mp->max_unsealed) { 132 | pool_free(mp, pool); 133 | } else if (pool->sealed) { 134 | pool_unseal(mp, pool); 135 | } 136 | } 137 | } 138 | 139 | static inline int32_t round_size(int32_t size) { 140 | static_assert(sizeof(struct header)*2 == MPA_ALIGNMENT, "Alignment and header size are not synchronized"); 141 | static_assert(0 == (MPA_ALIGNMENT & (MPA_ALIGNMENT - 1)), "Alignment is not a power of two"); 142 | return ROUNDUP(size + sizeof(struct header), MPA_ALIGNMENT); 143 | } 144 | 145 | void mpa_set_seal_max_pad(struct multipool *mp, ssize_t max_pad, ssize_t max_unsealed) { 146 | mp->max_pad = max_pad = round_size(max_pad); 147 | mp->max_unsealed = max_unsealed; 148 | 149 | LIST_FOREACH_SAFE(it, &mp->unsealed) { 150 | struct pool *pool = CONTAINEROF(it, struct pool, link); 151 | if (pool->size < max_pad + pool->offset) { 152 | pool_seal(mp, pool); 153 | if (!pool->n_alloc) { 154 | DO_FREE(pool, pool->size + sizeof *pool); 155 | mp->pool_count--; 156 | } 157 | } 158 | } 159 | } 160 | 161 | void *mpa_alloc(struct multipool *mp, ssize_t size) { 162 | size = round_size(size); 163 | 164 | struct pool *pool = get_fitting_pool(mp, MAX(size, mp->max_pad)); 165 | if (!pool) return NULL; 166 | 167 | struct header *dst = (void *)(pool->data + pool->offset); 168 | dst->offset = pool->offset; 169 | dst->size = size; 170 | 171 | pool->offset += size; 172 | pool->n_alloc++; 173 | 174 | pool_seal(mp, pool); 175 | 176 | return (void *)(dst + 1); 177 | } 178 | 179 | ssize_t mpa_allocated_size(void *ptr) { 180 | struct header *header = GET_PTR_HEADER(ptr); 181 | return header->size - sizeof *header; 182 | } 183 | 184 | static void check_unseal(struct multipool *mp, struct pool *pool) { 185 | if (pool->sealed && pool->size - pool->offset >= mp->max_pad) 186 | pool_unseal(mp, pool); 187 | } 188 | 189 | void *mpa_realloc(struct multipool *mp, void *ptr, ssize_t size, bool pin) { 190 | struct header *header = GET_PTR_HEADER(ptr); 191 | struct pool *pool = GET_HEADER_POOL(header); 192 | 193 | size = round_size(size); 194 | 195 | bool is_last = header->offset + header->size == pool->offset; 196 | 197 | /* Can resize inside pool */ 198 | if (is_last && size - header->size <= pool->size - pool->offset) { 199 | pool->offset += size - header->size; 200 | header->size = size; 201 | } else if (header->size < size) { 202 | void *new = mpa_alloc(mp, size - sizeof *header); 203 | if (!new) return NULL; 204 | 205 | memcpy(new, ptr, MIN(header->size, size) - sizeof *header); 206 | mpa_free(mp, ptr); 207 | return new; 208 | } 209 | 210 | if (pin) 211 | check_unseal(mp, pool); 212 | 213 | return ptr; 214 | } 215 | 216 | void mpa_pin(struct multipool *mp, void *ptr) { 217 | struct header *header = GET_PTR_HEADER(ptr); 218 | struct pool *pool = GET_HEADER_POOL(header); 219 | check_unseal(mp, pool); 220 | } 221 | -------------------------------------------------------------------------------- /multipool.h: -------------------------------------------------------------------------------- 1 | #ifndef MULTIPOOL_H_ 2 | #define MULTIPOOL_H_ 1 3 | 4 | #include "feature.h" 5 | 6 | #include "list.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define MPA_ALIGNMENT MAX(16, _Alignof(max_align_t)) 13 | #define MPA_POOL_SIZE 65536ULL 14 | 15 | struct pool; 16 | 17 | struct multipool { 18 | struct list_head unsealed; 19 | ssize_t max_pad; 20 | ssize_t pool_size; 21 | ssize_t unsealed_count; 22 | ssize_t pool_count; 23 | ssize_t max_unsealed; 24 | bool force_fast_resize; 25 | }; 26 | 27 | void mpa_init(struct multipool *mp, ssize_t pool_size); 28 | 29 | /* 30 | * Set the maximum amount of wasted bytes per pool. 31 | * This also sets the guaranteed maximal size, 32 | * which an unsealed object can be resized without 33 | * hitting slow path. 34 | */ 35 | void mpa_set_seal_max_pad(struct multipool *mp, ssize_t max_pad, ssize_t max_unsealed); 36 | /* Release all allocated memory */ 37 | void mpa_release(struct multipool *mp); 38 | /* Free an object */ 39 | void mpa_free(struct multipool *mp, void *ptr); 40 | /* Allocate an object */ 41 | void *mpa_alloc(struct multipool *mp, ssize_t size); 42 | /* Resize object, might move */ 43 | void *mpa_realloc(struct multipool *mp, void *ptr, ssize_t size, bool pin); 44 | 45 | /* Mark an object to be not (easily) resizable */ 46 | void mpa_pin(struct multipool *mp, void *ptr); 47 | 48 | /* Return allocated size */ 49 | ssize_t mpa_allocated_size(void *ptr); 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /nrcs.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #include "feature.h" 4 | 5 | #include "nrcs.h" 6 | #include "util.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | static const unsigned short *nrcs_trs[] = { 13 | /* 14 | * Order of characters in this string, like in trans_idx[]: 15 | * [0x23] [0x40] [0x5B 0x5C 0x5D 0x5E 0x5F 0x60] [0x7B 0x7C 0x7D 0x7E] 16 | * NOTE: This assumes UTF-8 encoding for source files. 17 | */ 18 | [nrcs_french_canadian] = u"#àâçêî_ôéùèû", 19 | [nrcs_french_canadian2] = u"#àâçêî_ôéùèû", 20 | [nrcs_finnish] = u"#@ÄÖÅÜ_éäöåü", 21 | [nrcs_finnish2] = u"#@ÄÖÅÜ_éäöåü", 22 | [nrcs_german] = u"#§ÄÖÜ^_`äöüß", 23 | [nrcs_dutch] = u"£¾ij½|^_`¨f¼´", 24 | [nrcs_itallian] = u"£§°çé^_ùàòèì", 25 | [nrcs_swiss] = u"ùàéçêîèôäöüû", 26 | [nrcs_swedish] = u"#ÉÆØÅÜ_éæøåü", 27 | [nrcs_swedish2] = u"#ÉÆØÅÜ_éæøåü", 28 | [nrcs_norwegian_dannish] = u"#ÄÆØÅÜ_äæøåü", 29 | [nrcs_norwegian_dannish2] = u"#ÄÆØÅÜ_äæøåü", 30 | [nrcs_norwegian_dannish3] = u"#ÄÆØÅÜ_äæøåü", 31 | [nrcs_french] = u"£à°ç§^_`éùè¨", 32 | [nrcs_french2] = u"£à°ç§^_`éùè¨", 33 | [nrcs_spannish] = u"£§¡Ñ¿^_`°ñç~", 34 | [nrcs_portuguese] = u"#@ÃÇÕ^_`ãçõ~", 35 | [nrcs_turkish] = u"#İŞÖÇÜ_Ğşöçü", 36 | }; 37 | 38 | static const uint8_t trans_idx[] = { 39 | 0x23, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x7B, 0x7C, 0x7D, 0x7E 40 | }; 41 | 42 | /* DEC Graph character set */ 43 | static const unsigned short graph_tr[] = u" ◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·"; 44 | 45 | /* DEC Technical character set */ 46 | static const unsigned short tech_tr[] = { 47 | 0x23B7, 0x250C, 0x2500, 0x2320, 0x2321, 0x2502, 0x23A1, 48 | 0x23A3, 0x23A4, 0x23A6, 0x239B, 0x239D, 0x239E, 0x23A0, 0x23A8, 49 | 0x23AC, 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFE, 50 | 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFE, 0x2264, 0x2260, 0x2265, 0x222B, 51 | 0x2234, 0x221D, 0x221E, 0x00F7, 0x0394, 0x2207, 0x03A6, 0x0393, 52 | 0x223C, 0x2243, 0x0398, 0x00D7, 0x039B, 0x21D4, 0x21D2, 0x2261, 53 | 0x03A0, 0x03A8, 0xFFFE, 0x03A3, 0xFFFE, 0xFFFE, 0x221A, 0x03A9, 54 | 0x039E, 0x03A5, 0x2282, 0x2283, 0x2229, 0x222A, 0x2227, 0x2228, 55 | 0x00AC, 0x03B1, 0x03B2, 0x03C7, 0x03B4, 0x03B5, 0x03C6, 0x03B3, 56 | 0x03B7, 0x03B9, 0x03B8, 0x03BA, 0x03BB, 0xFFFE, 0x03BD, 0x2202, 57 | 0x03C0, 0x03C8, 0x03C1, 0x03C3, 0x03C4, 0xFFFE, 0x0192, 0x03C9, 58 | 0x03BE, 0x03C5, 0x03B6, 0x2190, 0x2191, 0x2192, 0x2193, 59 | }; 60 | 61 | bool nrcs_encode(enum charset set, uint32_t *ch, bool nrcs) { 62 | bool done = 0; 63 | switch (set) { 64 | case cs94_ascii: 65 | case cs94_dec_altchars: 66 | case cs94_dec_altgraph: 67 | done = *ch < 0x80; 68 | break; 69 | case cs94_british: 70 | case cs96_latin_1: 71 | if (!nrcs || set == cs96_latin_1) { 72 | if (0x80 <= *ch && *ch < 0x100) 73 | *ch -= 0x80, done = 1; 74 | } else { 75 | if (*ch == U'£') *ch = '$', done = 1; 76 | else done = *ch != '$'; 77 | } 78 | break; 79 | case cs94_dec_sup: 80 | case cs94_dec_sup_graph: 81 | switch (*ch) { 82 | case U'¤': *ch = 0xA8 - 0x80; done = 1; break; 83 | case U'Œ': *ch = 0xD7 - 0x80; done = 1; break; 84 | case U'Ÿ': *ch = 0xDD - 0x80; done = 1; break; 85 | case U'œ': *ch = 0xF7 - 0x80; done = 1; break; 86 | case U'ÿ': *ch = 0xFD - 0x80; done = 1; break; 87 | } 88 | if (*ch >= 0x80 && *ch < 0x100) { 89 | done = (*ch != 0xA8 && 90 | (*ch & ~0x20) != 0xD7 && 91 | (*ch & ~0x20) != 0xDD); 92 | if (done) *ch -= 0x80; 93 | } 94 | break; 95 | case cs96_latin_5: 96 | switch (*ch) { 97 | case U'Ğ': *ch = 0xD0 - 0x80; done = 1; break; 98 | case U'İ': *ch = 0xDD - 0x80; done = 1; break; 99 | case U'Ş': *ch = 0xDE - 0x80; done = 1; break; 100 | case U'ğ': *ch = 0xF0 - 0x80; done = 1; break; 101 | case U'ı': *ch = 0xFD - 0x80; done = 1; break; 102 | case U'ş': *ch = 0xFE - 0x80; done = 1; break; 103 | } 104 | if (*ch >= 0x80 && *ch < 0x100) { 105 | done = ((*ch & ~0x20) != 0xD0 && 106 | (*ch & ~0x20) != 0xDD && 107 | (*ch & ~0x20) != 0xDE); 108 | if (done) *ch -= 0x80; 109 | } 110 | break; 111 | 112 | case cs94_dec_graph: 113 | for (size_t i = 0; i < LEN(graph_tr); i++) { 114 | if (graph_tr[i] == *ch) { 115 | *ch = i + 0x5F; 116 | done = 1; 117 | break; 118 | } 119 | } 120 | done |= *ch < 0x5F || *ch == 0x7F; 121 | break; 122 | case cs94_dec_tech: 123 | for (size_t i = 0; i < LEN(tech_tr); i++) { 124 | if (tech_tr[i] == *ch) { 125 | *ch = i + 0x21; 126 | done = 1; 127 | break; 128 | } 129 | } 130 | done |= *ch < 0x21 || *ch == 0x7F; 131 | break; 132 | case nrcs_turkish: 133 | if (*ch == U'ğ') *ch = 0x26, done = 1; 134 | break; 135 | default:; 136 | } 137 | 138 | if (set <= nrcs__impl_high) { 139 | for (size_t i = 0; i < LEN(trans_idx); i++) { 140 | if (nrcs_trs[set][i] == *ch) { 141 | *ch = trans_idx[i]; 142 | done = 1; 143 | break; 144 | } 145 | } 146 | done |= (*ch < 0x7B && *ch != 0x23 && *ch != 0x40 147 | && !(0x5B <= *ch && *ch <= 0x60)) || *ch == 0x7F; 148 | } 149 | 150 | return done; 151 | } 152 | 153 | uint32_t nrcs_decode_fast(enum charset gl, uint32_t ch) { 154 | if (UNLIKELY(gl == cs94_dec_graph)) { 155 | if (ch - 0x5FU < 0x7EU - 0x5FU) 156 | ch = graph_tr[ch - 0x5F]; 157 | } 158 | return ch; 159 | } 160 | 161 | uint32_t nrcs_decode(enum charset gl, enum charset gr, enum charset ups, uint32_t ch, bool nrcs) { 162 | if (ch > 0xFF) return ch; 163 | if (ch == 0x7F) return U' '; 164 | 165 | enum charset set = ch > 0x7F ? gr : gl; 166 | 167 | /* User preferred supplemental */ 168 | if (set == cs94_dec_sup) set = ups; 169 | 170 | switch (set) { 171 | case cs94_ascii: 172 | case cs94_dec_altchars: 173 | case cs94_dec_altgraph: 174 | return ch; 175 | case cs94_dec_sup: 176 | case cs94_dec_sup_graph: 177 | switch (ch |= 0x80) { 178 | case 0xA8: return U'¤'; 179 | case 0xD7: return U'Œ'; 180 | case 0xDD: return U'Ÿ'; 181 | case 0xF7: return U'œ'; 182 | case 0xFD: return U'ÿ'; 183 | } 184 | return ch; 185 | case cs94_dec_graph: 186 | ch &= 0x7F; 187 | if (0x5F <= ch && ch <= 0x7E) 188 | ch = graph_tr[ch - 0x5F]; 189 | return ch; 190 | case cs96_latin_1: 191 | case cs94_british: 192 | if (nrcs) { 193 | ch &= 0x7F; 194 | if (ch == '#') ch = U'£'; 195 | return ch; 196 | } 197 | return ch | 0x80; 198 | case cs96_latin_5: 199 | switch (ch |= 0x80) { 200 | case 0xD0: ch = U'Ğ'; break; 201 | case 0xDD: ch = U'İ'; break; 202 | case 0xDE: ch = U'Ş'; break; 203 | case 0xF0: ch = U'ğ'; break; 204 | case 0xFD: ch = U'ı'; break; 205 | case 0xFE: ch = U'ş'; break; 206 | } 207 | return ch; 208 | case cs94_dec_tech: 209 | ch &= 0x7F; 210 | if (0x20 < ch && ch < 0x7F) 211 | return tech_tr[ch - 0x21]; 212 | return ch; 213 | case nrcs_turkish: 214 | if ((ch & 0x7F) == 0x26) return U'ğ'; 215 | default:; 216 | } 217 | 218 | if (/* nrcs && */ set <= nrcs__impl_high) { 219 | ch &= 0x7F; 220 | if (ch == 0x23) return nrcs_trs[set][0]; 221 | if (ch == 0x40) return nrcs_trs[set][1]; 222 | if (0x5B <= ch && ch <= 0x60) 223 | return nrcs_trs[set][2 + ch - 0x5B]; 224 | if (0x7B <= ch && ch <= 0x7E) 225 | return nrcs_trs[set][8 + ch - 0x7B]; 226 | } 227 | 228 | return ch; 229 | } 230 | 231 | struct nrcs_desc { 232 | uint16_t min_vt_level; 233 | uint16_t max_vt_level; 234 | uint32_t selector; 235 | } descs[] = { 236 | [nrcs_finnish] = { 2, 9, E('C') }, 237 | [nrcs_finnish2] = { 2, 9, E('5') }, 238 | [nrcs_swedish] = { 2, 9, E('H') }, 239 | [nrcs_swedish2] = { 2, 9, E('7') }, 240 | [nrcs_german] = { 2, 9, E('K') }, 241 | [nrcs_french_canadian] = { 2, 9, E('Q') }, 242 | [nrcs_french] = { 2, 9, E('R') }, 243 | [nrcs_french2] = { 2, 9, E('f') }, 244 | [nrcs_itallian] = { 2, 9, E('Y') }, 245 | [nrcs_spannish] = { 2, 9, E('Z') }, 246 | [nrcs_dutch] = { 2, 9, E('4') }, 247 | [nrcs_swiss] = { 2, 9, E('=') }, 248 | [nrcs_norwegian_dannish] = { 2, 9, E('E') }, 249 | [nrcs_norwegian_dannish2] = { 2, 9, E('6') }, 250 | [nrcs_norwegian_dannish3] = { 3, 9, E('`') }, 251 | [nrcs_french_canadian2] = { 3, 9, E('9') }, 252 | [nrcs_portuguese] = { 3, 9, E('6') | I1('%') }, 253 | [nrcs_hebrew] = { 5, 9, E('=') | I1('%') }, 254 | [nrcs_greek] = { 5, 9, E('>') | I1('"') }, 255 | [nrcs_turkish] = { 5, 9, E('2') | I1('%') }, 256 | [nrcs_cyrillic] = { 5, 9, E('4') | I1('&') }, 257 | [cs94_ascii] = { 1, 9, E('B') }, 258 | [cs94_british] = { 1, 9, E('A') }, 259 | [cs94_dec_graph] = { 1, 9, E('0') }, 260 | [cs94_dec_altchars] = { 1, 1, E('1') }, 261 | [cs94_dec_altgraph] = { 1, 1, E('2') }, 262 | [cs94_dec_sup] = { 2, 9, E('<') }, 263 | [cs94_dec_sup_graph] = { 3, 9, E('5') | I1('%') }, 264 | [cs94_dec_tech] = { 3, 9, E('>') }, 265 | [cs94_dec_hebrew] = { 5, 9, E('4') | I1('"') }, 266 | [cs94_dec_greek] = { 5, 9, E('?') | I1('"') }, 267 | [cs94_dec_turkish] = { 5, 9, E('0') | I1('%') }, 268 | [cs96_latin_1] = { 3, 9, E('A') }, 269 | [cs96_greek] = { 5, 9, E('F') }, 270 | [cs96_hebrew] = { 5, 9, E('H') }, 271 | [cs96_latin_cyrillic] = { 5, 9, E('L') }, 272 | [cs96_latin_5] = { 5, 9, E('M') }, 273 | }; 274 | 275 | enum charset nrcs_parse(uint32_t selector, bool is96, uint16_t vt_level, bool nrcs) { 276 | size_t start = is96 ? (nrcs ? cs96_END + 1 : cs96_START) : 277 | (nrcs ? nrcs_START : cs94_START); 278 | size_t end = is96 ? cs96_END + 1 : cs94_END + 1; 279 | selector &= I1_MASK | E_MASK; 280 | 281 | for (size_t i = start; i < end; i++) 282 | if (descs[i].selector == selector && 283 | descs[i].min_vt_level <= vt_level && 284 | vt_level <= descs[i].max_vt_level) 285 | return i; 286 | 287 | return nrcs_invalid; 288 | } 289 | 290 | char *nrcs_unparse(char selstring[static 3], enum charset cs) { 291 | selstring[1] = '\0'; 292 | selstring[0] = I1_CHAR(descs[cs].selector); 293 | selstring[2] = '\0'; 294 | selstring[!!selstring[0]] = E_CHAR(descs[cs].selector); 295 | return selstring; 296 | } 297 | -------------------------------------------------------------------------------- /nrcs.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef NRCS_H_ 4 | #define NRCS_H_ 1 5 | 6 | #include "feature.h" 7 | 8 | #include 9 | #include 10 | 11 | /* NOTE: Order of groups is important */ 12 | enum charset { 13 | nrcs_french_canadian, 14 | nrcs_START = nrcs_french_canadian, 15 | nrcs_finnish, 16 | nrcs_german, 17 | nrcs_dutch, 18 | nrcs_itallian, 19 | nrcs_swiss, 20 | nrcs_swedish, 21 | nrcs_norwegian_dannish, 22 | nrcs_french, 23 | nrcs_spannish, 24 | nrcs_portuguese, 25 | nrcs_turkish, 26 | nrcs_french_canadian2, 27 | nrcs_finnish2, 28 | nrcs_swedish2, 29 | nrcs_norwegian_dannish2, 30 | nrcs_norwegian_dannish3, 31 | nrcs_french2, 32 | nrcs__impl_high = nrcs_french2, 33 | nrcs_hebrew, /* Not implemented */ 34 | nrcs_greek, /* Not implemented */ 35 | nrcs_cyrillic, /* Not implemented */ 36 | 37 | cs94_ascii, 38 | cs94_START = cs94_ascii, 39 | cs94_dec_altchars, 40 | cs94_dec_altgraph, 41 | cs94_british, /* Same as latin-1 */ 42 | cs94_dec_sup, /* User prefered */ 43 | cs94_dec_sup_graph, 44 | cs94_dec_graph, 45 | cs94_dec_tech, 46 | cs94_dec_greek, /* Not implemented */ 47 | cs94_dec_hebrew, /* Not implemented */ 48 | cs94_dec_turkish, /* Not implemented */ 49 | cs94_END = cs94_dec_turkish, 50 | 51 | cs96_latin_1, 52 | cs96_START = cs96_latin_1, 53 | cs96_greek, /* Not implemented */ 54 | cs96_hebrew, /* Not implemented */ 55 | cs96_latin_cyrillic, /* Not implemented */ 56 | cs96_latin_5, 57 | cs96_END = cs96_latin_5, 58 | 59 | nrcs_invalid = -1, 60 | }; 61 | 62 | /* 63 | * Macros for encoding dispatch selectors 64 | * OSC commands just stores osc number as selector 65 | * (and OSC L/OSC l/OSC I are translated to 0/1/2). 66 | * 67 | * Generic escape sequences uses E(c) for final byte 68 | * and I0(c), I1(c) for first and second intermediate 69 | * bytes. 70 | * 71 | * CSI and DCS sequences use C(c) for final byte 72 | * P(c) for private indicator byte and 73 | * I0(c), I1(c) for intermediate bytes. 74 | * 75 | * *_MASK macros can be used to extract 76 | * corresponding parts of selectors. 77 | * 78 | * *_CHAR macros are used for getting 79 | * source character. 80 | */ 81 | 82 | #define I1_SHIFT 14 83 | #define I0_SHIFT 9 84 | #define P_SHIFT 6 85 | 86 | #define C_MASK (0x3F) 87 | #define E_MASK (0x7F) 88 | #define I0_MASK (0x1F << I0_SHIFT) 89 | #define I1_MASK (0x1F << I1_SHIFT) 90 | #define P_MASK (0x7 << P_SHIFT) 91 | 92 | #define C(c) ((c) & C_MASK) 93 | #define E(c) ((c) & E_MASK) 94 | #define I0(i) ((i) ? (((i) & 0xF) + 1) << I0_SHIFT : 0) 95 | #define I1(i) ((i) ? (((i) & 0xF) + 1) << I1_SHIFT : 0) 96 | #define P(p) ((p) ? ((((p) & 3) + 1) << P_SHIFT) : 0) 97 | 98 | #define E_CHAR(s) ((s) & 0x7F) 99 | #define I0_CHAR(s) ((s) >> I0_SHIFT ? (((s) >> I0_SHIFT) - 1) | ' ' : 0) 100 | #define I1_CHAR(s) ((s) >> I1_SHIFT ? (((s) >> I1_SHIFT) - 1) | ' ' : 0) 101 | 102 | static inline bool nrcs_is_96(enum charset cs) { 103 | return cs >= cs96_latin_1; 104 | } 105 | 106 | bool nrcs_encode(enum charset set, uint32_t *ch, bool nrcs); 107 | uint32_t nrcs_decode(enum charset gl, enum charset gr, enum charset ups, uint32_t ch, bool nrcs); 108 | uint32_t nrcs_decode_fast(enum charset gl, uint32_t ch); 109 | enum charset nrcs_parse(uint32_t selector, bool is96, uint16_t vt_level, bool nrcs); 110 | char *nrcs_unparse(char selstring[static 3], enum charset cs); 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /nsst-open: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | HOSTNAME="$(uname -n)" 4 | file="${1#file://$HOSTNAME}" 5 | 6 | if [ x"${file}" != x"${1}" ]; then 7 | # Decode filename if it has percent-encoded characters 8 | # since they can not be parsed by xdg-open. 9 | file="$(echo "${file}" | sed 's@+@ @g;s@%@\\x@g' | xargs -0 printf "%b")" 10 | xdg-open "$file" 11 | else 12 | xdg-open "$1" 13 | fi 14 | -------------------------------------------------------------------------------- /nsst.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #include "feature.h" 4 | 5 | #include "config.h" 6 | #include "feature.h" 7 | #include "input.h" 8 | #include "poller.h" 9 | #include "tty.h" 10 | #include "util.h" 11 | #include "window.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | static _Noreturn void usage(const char *argv0, int code) { 24 | char buffer[MAX_OPTION_DESC+1]; 25 | if (gconfig.log_level > 0 || code == EXIT_SUCCESS) { 26 | ssize_t i = 0; 27 | do fputs(argv0, stdout); 28 | while ((argv0 = usage_string(buffer, i++))); 29 | } 30 | exit(code); 31 | } 32 | 33 | static _Noreturn void version(void) { 34 | printf("%sFeatures: %s", version_string(), features_string()); 35 | exit(EXIT_SUCCESS); 36 | } 37 | 38 | static void parse_options(struct instance_config *cfg, char **argv) { 39 | struct option *opt = NULL, *config_path_entry = find_short_option_entry('C'); 40 | size_t arg_i = 1; 41 | char *name_end; 42 | 43 | for (const char *arg, *name; argv[arg_i] && argv[arg_i][0] == '-'; arg_i += !!argv[arg_i]) { 44 | switch (argv[arg_i][1]) { 45 | case '\0': /* Invalid syntax */; 46 | usage(argv[0], EXIT_FAILURE); 47 | case '-': /* Long options */; 48 | /* End of flags mark */ 49 | if (!*(name = argv[arg_i] + 2)) { 50 | arg_i++; 51 | goto finish; 52 | } 53 | 54 | /* Options without arguments */ 55 | if (!strcmp(name, "help")) 56 | usage(argv[0], EXIT_SUCCESS); 57 | if (!strcmp(name, "version")) 58 | version(); 59 | 60 | /* Options with arguments */ 61 | if ((arg = name_end = strchr(name, '='))) 62 | *name_end = '\0', arg++; 63 | 64 | if (!strncmp(name, "no-", 3) && is_boolean_option(opt = find_option_entry(name + 3, false))) 65 | arg = "false"; 66 | else if (!(opt = find_option_entry(name, true))) 67 | usage(argv[0], EXIT_FAILURE); 68 | 69 | if (is_boolean_option(opt)) { 70 | if (!arg) arg = "true"; 71 | } else { 72 | if (!arg || !*arg) 73 | arg = argv[++arg_i]; 74 | } 75 | 76 | if (!arg || (opt != config_path_entry && !set_option_entry(cfg, opt, arg, 2))) 77 | usage(argv[0], EXIT_FAILURE); 78 | continue; 79 | } 80 | 81 | /* Short options, may be clustered */ 82 | for (size_t char_i = 1; argv[arg_i] && argv[arg_i][char_i]; char_i++) { 83 | char letter = argv[arg_i][char_i]; 84 | /* Handle options without arguments */ 85 | switch (letter) { 86 | case 'e': 87 | /* Works the same way as -- */ 88 | if (argv[arg_i++][char_i + 1]) 89 | usage(argv[0], EXIT_FAILURE); 90 | goto finish; 91 | case 'h': 92 | usage(argv[0], EXIT_SUCCESS); 93 | case 'v': 94 | version(); 95 | } 96 | 97 | /* Handle options with arguments (including implicit arguments) */ 98 | if ((opt = find_short_option_entry(letter))) { 99 | if (!is_boolean_option(opt)) { 100 | if (!argv[arg_i][++char_i]) arg_i++, char_i = 0; 101 | if (!argv[arg_i]) usage(argv[0], EXIT_FAILURE); 102 | arg = argv[arg_i] + char_i; 103 | } else { 104 | arg = "true"; 105 | } 106 | 107 | /* Config path option should be ignored, since it is set before */ 108 | if (opt != config_path_entry) 109 | if (!set_option_entry(cfg, opt, arg, 2)) 110 | usage(argv[0], EXIT_FAILURE); 111 | 112 | if (is_boolean_option(opt)) continue; 113 | else break; 114 | } 115 | } 116 | } 117 | 118 | if (argv[arg_i]) { 119 | finish: 120 | if (!argv[arg_i]) 121 | usage(argv[0], EXIT_FAILURE); 122 | cfg->argv = &argv[arg_i]; 123 | } 124 | 125 | /* Parse all shortcuts */ 126 | keyboard_parse_config(cfg); 127 | } 128 | 129 | static inline char *parse_config_path(int argc, char **argv) { 130 | char *config_path = NULL; 131 | 132 | for (int opt_i = 1; opt_i < argc; opt_i++) { 133 | char *arg; 134 | if (!strcmp(argv[opt_i], "--config")) { 135 | arg = argv[++opt_i]; 136 | } else if (!strncmp(argv[opt_i], "--config=", sizeof "--config=" - 1)) { 137 | arg = argv[opt_i] + (sizeof "--config=" - 1); 138 | if (!*arg) arg = argv[++opt_i]; 139 | } else if (!strncmp(argv[opt_i], "-C", sizeof "-C" - 1)) { 140 | arg = argv[opt_i] + (sizeof "-C" - 1); 141 | if (!*arg) arg = argv[++opt_i]; 142 | } else continue; 143 | 144 | if (!arg) usage(argv[0], EXIT_FAILURE); 145 | config_path = arg; 146 | } 147 | 148 | return config_path; 149 | } 150 | 151 | struct instance_config global_instance_config; 152 | 153 | static void free_instance_config_at_exit(void) { 154 | free_config(&global_instance_config); 155 | } 156 | 157 | int main(int argc, char **argv) { 158 | int result = EXIT_SUCCESS; 159 | 160 | /* Load locale from environment variable */ 161 | setlocale(LC_CTYPE, ""); 162 | 163 | /* Parse config path argument before parsing config file 164 | * to use correct one. This path is used as a default one later. */ 165 | char *cpath = parse_config_path(argc, argv); 166 | 167 | init_options(cpath); 168 | atexit(free_options); 169 | 170 | init_instance_config(&global_instance_config, cpath, 2); 171 | atexit(free_instance_config_at_exit); 172 | 173 | parse_options(&global_instance_config, argv); 174 | 175 | #if USE_URI 176 | init_proto_tree(); 177 | atexit(uri_release_memory); 178 | #endif 179 | init_poller(); 180 | atexit(free_poller); 181 | 182 | init_context(&global_instance_config); 183 | atexit(free_context); 184 | 185 | init_default_termios(); 186 | 187 | if (gconfig.daemon_mode) { 188 | atexit(free_daemon); 189 | result = !init_daemon(); 190 | } else { 191 | result = !create_window(&global_instance_config); 192 | } 193 | 194 | if (!result) poller_run(); 195 | return result; 196 | } 197 | -------------------------------------------------------------------------------- /nsst.info: -------------------------------------------------------------------------------- 1 | nsst|Not So Simple Terminal with 256 colors, 2 | use=nsst+base, 3 | ccc, 4 | colors#0x100, 5 | initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, 6 | oc=\E]104\007, 7 | setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, 8 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, 9 | 10 | nsst-direct|Not So Simple Terminal with 24-bit color, 11 | use=nsst+base, 12 | RGB, 13 | colors#0x1000000, 14 | setab=\E[%?%p1%{8}%<%t4%p1%d%e48\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m, 15 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e38\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m, 16 | 17 | nsst+base|Not So Simple Terminal base fragment, 18 | acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 19 | am, 20 | AX, 21 | bce, 22 | BD=\E[?2004l, 23 | BE=\E[?2004h, 24 | bel=^G, 25 | blink=\E[5m, 26 | bold=\E[1m, 27 | bs, 28 | cbt=\E[Z, 29 | civis=\E[?25l, 30 | clear=\E[H\E[2J, 31 | Clmg=\E[s, 32 | Cmg=\E[%i%p1%d;%p2%ds, 33 | cnorm=\E[?12l\E[?25h, 34 | cols#80, 35 | Cr=\E]112\007, 36 | cr=\r, 37 | Cs=\E]12;%p1%s\007, 38 | csr=\E[%i%p1%d;%p2%dr, 39 | cub1=^H, 40 | cub=\E[%p1%dD, 41 | cud1=\n, 42 | cud=\E[%p1%dB, 43 | cuf1=\E[C, 44 | cuf=\E[%p1%dC, 45 | cup=\E[%i%p1%d;%p2%dH, 46 | cuu1=\E[A, 47 | cuu=\E[%p1%dA, 48 | cvvis=\E[?12;25h, 49 | dch1=\E[P, 50 | dch=\E[%p1%dP, 51 | dim=\E[2m, 52 | dl1=\E[M, 53 | dl=\E[%p1%dM, 54 | Dsbp=\E[?2004l, 55 | Dsfcs=\E[?1004l, 56 | dsl=\E]2;\007, 57 | Dsmg=\E[?69l, 58 | E3=\E[3J, 59 | ech=\E[%p1%dX, 60 | ed=\E[J, 61 | el1=\E[1K, 62 | el=\E[K, 63 | Enbp=\E[?2004h, 64 | Enfcs=\E[?1004h, 65 | Enmg=\E[?69h, 66 | fd=\E[?1004l, 67 | fe=\E[?1004h, 68 | flash=\E[?5h$<100/>\E[?5l, 69 | fsl=\007, 70 | Hls=\E]8;%?%p1%l%tid=%p1%s%;;%p2%s\E\\, 71 | home=\E[H, 72 | hpa=\E[%i%p1%dG, 73 | hs, 74 | ht=^I, 75 | hts=\EH, 76 | ich=\E[%p1%d@, 77 | il1=\E[L, 78 | il=\E[%p1%dL, 79 | ind=\n, 80 | indn=\E[%p1%dS, 81 | invis=\E[8m, 82 | is2=\E[!p\E>, 83 | it#8, 84 | ka1=\EOw, 85 | ka2=\EOx, 86 | ka3=\EOy, 87 | kb1=\EOt, 88 | kb2=\EOu, 89 | kb3=\EOv, 90 | kbeg=\EOE, 91 | kbs=^?, 92 | kc1=\EOq, 93 | kc2=\EOr, 94 | kc3=\EOs, 95 | kcbt=\E[Z, 96 | kcub1=\EOD, 97 | kcud1=\EOB, 98 | kcuf1=\EOC, 99 | kcuu1=\EOA, 100 | kDC3=\E[3;3~, 101 | kDC4=\E[3;4~, 102 | kDC5=\E[3;5~, 103 | kDC6=\E[3;6~, 104 | kDC7=\E[3;7~, 105 | kDC=\E[3;2~, 106 | kdch1=\E[3~, 107 | kDN3=\E[1;3B, 108 | kDN4=\E[1;4B, 109 | kDN5=\E[1;5B, 110 | kDN6=\E[1;6B, 111 | kDN7=\E[1;7B, 112 | kDN=\E[1;2B, 113 | kEND3=\E[1;3F, 114 | kEND4=\E[1;4F, 115 | kEND5=\E[1;5F, 116 | kEND6=\E[1;6F, 117 | kEND7=\E[1;7F, 118 | kEND=\E[1;2F, 119 | kend=\EOF, 120 | kent=\EOM, 121 | kf10=\E[21~, 122 | kf11=\E[23~, 123 | kf12=\E[24~, 124 | kf13=\E[1;2P, 125 | kf14=\E[1;2Q, 126 | kf15=\E[1;2R, 127 | kf16=\E[1;2S, 128 | kf17=\E[15;2~, 129 | kf18=\E[17;2~, 130 | kf19=\E[18;2~, 131 | kf1=\EOP, 132 | kf20=\E[19;2~, 133 | kf21=\E[20;2~, 134 | kf22=\E[21;2~, 135 | kf23=\E[23;2~, 136 | kf24=\E[24;2~, 137 | kf25=\E[1;5P, 138 | kf26=\E[1;5Q, 139 | kf27=\E[1;5R, 140 | kf28=\E[1;5S, 141 | kf29=\E[15;5~, 142 | kf2=\EOQ, 143 | kf30=\E[17;5~, 144 | kf31=\E[18;5~, 145 | kf32=\E[19;5~, 146 | kf33=\E[20;5~, 147 | kf34=\E[21;5~, 148 | kf35=\E[23;5~, 149 | kf36=\E[24;5~, 150 | kf37=\E[1;6P, 151 | kf38=\E[1;6Q, 152 | kf39=\E[1;6R, 153 | kf3=\EOR, 154 | kf40=\E[1;6S, 155 | kf41=\E[15;6~, 156 | kf42=\E[17;6~, 157 | kf43=\E[18;6~, 158 | kf44=\E[19;6~, 159 | kf45=\E[20;6~, 160 | kf46=\E[21;6~, 161 | kf47=\E[23;6~, 162 | kf48=\E[24;6~, 163 | kf49=\E[1;3P, 164 | kf4=\EOS, 165 | kf50=\E[1;3Q, 166 | kf51=\E[1;3R, 167 | kf52=\E[1;3S, 168 | kf53=\E[15;3~, 169 | kf54=\E[17;3~, 170 | kf55=\E[18;3~, 171 | kf56=\E[19;3~, 172 | kf57=\E[20;3~, 173 | kf58=\E[21;3~, 174 | kf59=\E[23;3~, 175 | kf5=\E[15~, 176 | kf60=\E[24;3~, 177 | kf61=\E[1;4P, 178 | kf62=\E[1;4Q, 179 | kf63=\E[1;4R, 180 | kf6=\E[17~, 181 | kf7=\E[18~, 182 | kf8=\E[19~, 183 | kf9=\E[20~, 184 | kHOM3=\E[1;3H, 185 | kHOM4=\E[1;4H, 186 | kHOM5=\E[1;5H, 187 | kHOM6=\E[1;6H, 188 | kHOM7=\E[1;7H, 189 | kHOM=\E[1;2H, 190 | khome=\EOH, 191 | kIC3=\E[2;3~, 192 | kIC4=\E[2;4~, 193 | kIC5=\E[2;5~, 194 | kIC6=\E[2;6~, 195 | kIC7=\E[2;7~, 196 | kIC=\E[2;2~, 197 | kich1=\E[2~, 198 | kind=\E[1;2B, 199 | kLFT3=\E[1;3D, 200 | kLFT4=\E[1;4D, 201 | kLFT5=\E[1;5D, 202 | kLFT6=\E[1;6D, 203 | kLFT7=\E[1;7D, 204 | kLFT=\E[1;2D, 205 | km, 206 | kmous=\E[<, 207 | knp=\E[6~, 208 | kNXT3=\E[6;3~, 209 | kNXT4=\E[6;4~, 210 | kNXT5=\E[6;5~, 211 | kNXT6=\E[6;6~, 212 | kNXT7=\E[6;7~, 213 | kNXT=\E[6;2~, 214 | kp5=\EOE, 215 | kpADD=\EOk, 216 | kpCMA=\EOl, 217 | kpDIV=\EOo, 218 | kpDOT=\EOn, 219 | kpMUL=\EOj, 220 | kpp=\E[5~, 221 | kPRV3=\E[5;3~, 222 | kPRV4=\E[5;4~, 223 | kPRV5=\E[5;5~, 224 | kPRV6=\E[5;6~, 225 | kPRV7=\E[5;7~, 226 | kPRV=\E[5;2~, 227 | kpSUB=\EOm, 228 | kpZRO=\EOp, 229 | kri=\E[1;2A, 230 | kRIT3=\E[1;3C, 231 | kRIT4=\E[1;4C, 232 | kRIT5=\E[1;5C, 233 | kRIT6=\E[1;6C, 234 | kRIT7=\E[1;7C, 235 | kRIT=\E[1;2C, 236 | kUP3=\E[1;3A, 237 | kUP4=\E[1;4A, 238 | kUP5=\E[1;5A, 239 | kUP6=\E[1;6A, 240 | kUP7=\E[1;7A, 241 | kUP=\E[1;2A, 242 | kxIN=\E[I, 243 | kxOUT=\E[O, 244 | lines#24, 245 | mc0=\E[i, 246 | mc4=\E[4i, 247 | mc5=\E[5i, 248 | mc5i, 249 | meml=\El, 250 | memu=\Em, 251 | mgc=\E[?69l, 252 | mir, 253 | Ms=\E]52;%p1%s;%p2%s\E\\, 254 | msgr, 255 | neks=\E[>4;1m, 256 | nel=\EE, 257 | npc, 258 | oc=\E]104\007, 259 | ol=\E[59m, 260 | op=\E[39;49m, 261 | OTbs, 262 | pairs#0x10000, 263 | PE=\E[201~, 264 | PS=\E[200~, 265 | rc=\E8, 266 | Rect=\E[%p1%d;%p2%d;%p3%d;%p4%d;%p5%d$x, 267 | rep=%p1%c\E[%p2%{1}%-%db, 268 | rev=\E[7m, 269 | ri=\EM, 270 | rin=\E[%p1%dT, 271 | ritm=\E[23m, 272 | rmacs=\E(B, 273 | rmam=\E[?7l, 274 | rmcup=\E[?1049l\E[23;0;0t, 275 | rmir=\E[4l, 276 | rmkx=\E[?1l\E>, 277 | rmm=\E[?1034l, 278 | rmso=\E[27m, 279 | rmul=\E[24m, 280 | rmxx=\E[29m, 281 | rs1=\Ec, 282 | rs2=\E[!p\E>, 283 | rv=\E\\[>1;[0-9][0-9][0-9][0-9][0-9][0-9];0c, 284 | RV=\E[>c, 285 | sc=\E7, 286 | Se=\E[\sq, 287 | seks=\E[>4m, 288 | setrgbb=\E[48\:2\:\:%p1%d\:%p2%d\:%p3%dm, 289 | setrgbf=\E[38\:2\:\:%p1%d\:%p2%d\:%p3%dm, 290 | Setulc1=\E[58\:5\:%p1%dm, 291 | Setulc=\E[58\:2\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m, 292 | sgr0=\E(B\E[m, 293 | sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p5%t;2%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, 294 | sitm=\E[3m, 295 | smacs=\E(0, 296 | smam=\E[?7h, 297 | smcup=\E[?1049h\E[22;0;0t, 298 | smglp=\E[?69h\E[%i%p1%ds, 299 | smglr=\E[?69h\E[%i%p1%d;%p2%ds, 300 | smgrp=\E[?69h\E[%i;%p1%ds, 301 | smir=\E[4h, 302 | smkx=\E[?1h\E=, 303 | smm=\E[?1034h, 304 | smm=\E[?1036l\E[?1034h, 305 | smso=\E[7m, 306 | smul=\E[4m, 307 | Smulx=\E[4\:%p1%dm, 308 | smxx=\E[9m, 309 | Ss=\E[%p1%d\sq, 310 | Su, 311 | Swd=\E]7;, 312 | Sync=\E[?2026%?%p1%{1}%-%tl%eh%;, 313 | tbc=\E[3g, 314 | Tc, 315 | TS=\E]2;, 316 | tsl=\E]2;, 317 | u6=\E[%i%d;%dR, 318 | u7=\E[6n, 319 | u8=\E[?%[;0123456789]c, 320 | u9=\E[c, 321 | vpa=\E[%i%p1%dd, 322 | xenl, 323 | XF, 324 | XM=\E[?1006;1000%?%p1%{1}%=%th%el%;, 325 | xm=\E[<%i%p3%d;%p1%d;%p2%d;%?%p4%tM%em%;, 326 | XR=\E[>0q, 327 | xr=\EP>\\|nsst v[0-9]+\\.[0-9]+\\.[0-9]+\E\\\\, 328 | XT, 329 | -------------------------------------------------------------------------------- /nsstc.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #include "feature.h" 4 | 5 | #define _GNU_SOURCE 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define MAX_OPTION_DESC 1024 25 | #define MAX_WAIT_LOOP 32 26 | #define STARTUP_DELAY 10000000LL 27 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 28 | #define WARN_PREFIX "[\033[33;1mWARN\033[m] " 29 | 30 | static char buffer[MAX(MAX_OPTION_DESC, PATH_MAX) + 1]; 31 | static const char *config_path; 32 | static const char *socket_path = "/tmp/nsst-sock0"; 33 | static const char *cwd; 34 | static bool need_daemon; 35 | static bool need_exit; 36 | 37 | static inline void send_char(int fd, char c) { 38 | while (send(fd, (char[1]){c}, 1, 0) < 0 && errno == EAGAIN); 39 | } 40 | 41 | static inline void recv_response(int fd) { 42 | ssize_t res = 0; 43 | while ((res = recv(fd, buffer, sizeof buffer - 1, 0)) > 0) { 44 | buffer[res] = '\0'; 45 | fputs(buffer, stdout); 46 | } 47 | } 48 | 49 | static _Noreturn void usage(int fd, const char *argv0, int code) { 50 | send_char(fd, '\025' /* NAK */); 51 | 52 | fputs(argv0, stdout); 53 | recv_response(fd); 54 | 55 | exit(code); 56 | } 57 | 58 | static _Noreturn void version(int fd) { 59 | send_char(fd, '\005' /* ENQ */); 60 | 61 | recv_response(fd); 62 | 63 | exit(0); 64 | } 65 | 66 | static void send_opt(int fd, const char *opt, const char *value) { 67 | /* This API lacks const's but doesn't change values... */ 68 | struct iovec dvec[4] = { 69 | { .iov_base = (char *)"\035" /* GS */, .iov_len = 1 }, 70 | { .iov_base = (char *)opt, .iov_len = strlen(opt) }, 71 | { .iov_base = (char *)"=", .iov_len = 1 }, 72 | { .iov_base = (char *)value, .iov_len = strlen(value) }, 73 | }; 74 | 75 | struct msghdr hdr = { 76 | .msg_iov = dvec, 77 | .msg_iovlen = sizeof dvec / sizeof *dvec 78 | }; 79 | 80 | while (sendmsg(fd, &hdr, 0) < 0 && errno == EAGAIN); 81 | } 82 | 83 | static void send_short_opt(int fd, char opt, const char *value) { 84 | struct iovec dvec[4] = { 85 | { .iov_base = (char *)"\034" /* FS */, .iov_len = 1 }, 86 | { .iov_base = &opt, .iov_len = 1 }, 87 | { .iov_base = (char *)"=", .iov_len = 1 }, 88 | { .iov_base = (char *)value, .iov_len = strlen(value) }, 89 | }; 90 | 91 | struct msghdr hdr = { 92 | .msg_iov = dvec, 93 | .msg_iovlen = sizeof dvec / sizeof *dvec 94 | }; 95 | 96 | while (sendmsg(fd, &hdr, 0) < 0 && errno == EAGAIN); 97 | } 98 | 99 | static void send_arg(int fd, char *arg) { 100 | struct iovec dvec[4] = { 101 | { .iov_base = (char *)"\036" /* RS */, .iov_len = 1 }, 102 | { .iov_base = arg, .iov_len = strlen(arg) }, 103 | }; 104 | 105 | struct msghdr hdr = { 106 | .msg_iov = dvec, 107 | .msg_iovlen = sizeof dvec / sizeof *dvec 108 | }; 109 | 110 | while (sendmsg(fd, &hdr, 0) < 0 && errno == EAGAIN); 111 | } 112 | 113 | static void send_header(int fd, const char *cpath) { 114 | struct iovec dvec[4] = { 115 | { .iov_base = (char *)"\001" /* SOH */, .iov_len = 1 }, 116 | { .iov_base = (char *)cpath, .iov_len = cpath ? strlen(cpath) : 0 }, 117 | }; 118 | 119 | struct msghdr hdr = { 120 | .msg_iov = dvec, 121 | .msg_iovlen = 1 + !!cpath, 122 | }; 123 | 124 | while (sendmsg(fd, &hdr, 0) < 0 && errno == EAGAIN); 125 | } 126 | 127 | static bool is_boolean_option(const char *opt) { 128 | // TODO Use hash table? 129 | const char *bool_opts[] = { 130 | "allow-alternate", "allow-blinking", "allow-modify-edit-keypad", "allow-modify-function", 131 | "allow-modify-keypad", "allow-modify-misc", "allow-uris", "alternate-scroll", "appcursor", 132 | "appkey", "autorepeat", "autowrap", "backspace-is-del", "blend-all-background", 133 | "blend-foreground", "clone-config", "daemon", "delete-is-del", "erase-scrollback", "extended-cir", 134 | "fixed", "force-nrcs", "force-scalable", "force-wayland-csd", "fork", "has-meta", 135 | "keep-clipboard", "keep-selection", "lock-keyboard", "luit", "meta-sends-escape", "nrcs", 136 | "numlock", "override-boxdrawing", "print-attributes", "raise-on-bell", "reverse-video", 137 | "scroll-on-input", "scroll-on-output", "select-to-clipboard", "smooth-resize", "smooth-scroll", 138 | "special-blink", "special-bold", "special-italic", "special-reverse", "special-underlined", 139 | "substitute-fonts", "trace-characters", "trace-controls", "trace-events", "trace-fonts", 140 | "trace-input", "trace-misc", "unique-uris", "urgent-on-bell", "use-utf8", "visual-bell", 141 | "window-ops", 142 | }; 143 | for (size_t i = 0; i < sizeof bool_opts/sizeof *bool_opts; i++) 144 | if (!strcmp(bool_opts[i], opt)) return true; 145 | return false; 146 | } 147 | 148 | static bool is_boolean_short_option(char opt) { 149 | return opt == 'q' || opt == 'd' || opt == 'h' || opt == 'v'; 150 | } 151 | 152 | static inline bool is_client_only_option(const char *opt) { 153 | if (!strcmp(opt, "config")) return true; 154 | if (!strcmp(opt, "socket")) return true; 155 | if (!strcmp(opt, "cwd")) return true; 156 | if (!strcmp(opt, "exit")) return true; 157 | return false; 158 | } 159 | 160 | static inline bool is_client_only_short_option(char opt) { 161 | return opt == 'q' || opt == 's' || opt == 'C' || opt == 'd'; 162 | } 163 | 164 | bool is_true(const char *value) { 165 | if (!strcasecmp(value, "true")) return 1; 166 | if (!strcasecmp(value, "yes")) return 1; 167 | if (!strcmp(value, "1")) return 1; 168 | return 0; 169 | } 170 | 171 | static void parse_args(char **argv, int fd, bool client) { 172 | size_t arg_i = 1; 173 | char *name_end; 174 | 175 | for (const char *arg, *name; argv[arg_i] && argv[arg_i][0] == '-'; arg_i += !!argv[arg_i]) { 176 | switch (argv[arg_i][1]) { 177 | case '\0': /* Invalid syntax */; 178 | if (client) continue; 179 | usage(fd, argv[0], EXIT_FAILURE); 180 | case '-': /* Long options */; 181 | /* End of flags mark */ 182 | if (!*(name = argv[arg_i] + 2)) { 183 | arg_i++; 184 | goto finish; 185 | } 186 | 187 | /* Options without arguments */ 188 | if (!strcmp(name, "help")) { 189 | if (client) continue; 190 | usage(fd, argv[0], EXIT_SUCCESS); 191 | } 192 | if (!strcmp(name, "version")) { 193 | if (client) continue; 194 | version(fd); 195 | } 196 | 197 | /* Options with arguments */ 198 | if ((arg = name_end = strchr(name, '='))) 199 | *name_end = '\0', arg++; 200 | 201 | if (!strncmp(name, "no-", 3) && is_boolean_option(name + 3)) 202 | arg = "false", name += 3; 203 | 204 | if (is_boolean_option(name)) { 205 | if (!arg) arg = "true"; 206 | } else { 207 | if (!arg || !*arg) 208 | arg = argv[++arg_i]; 209 | } 210 | 211 | if (!client) { 212 | if (!arg) 213 | usage(fd, argv[0], EXIT_FAILURE); 214 | if (!is_client_only_option(name)) 215 | send_opt(fd, name, arg); 216 | } else { 217 | if (!arg) continue; 218 | if (!strcmp(name, "config")) 219 | config_path = arg; 220 | else if (!strcmp(name, "socket")) 221 | socket_path = arg; 222 | else if (!strcmp(name, "cwd")) 223 | cwd = arg; 224 | else if (!strcmp(name, "daemon")) 225 | need_daemon = is_true(arg); 226 | else if (!strcmp(name, "quit")) 227 | need_exit = is_true(arg); 228 | } 229 | 230 | /* We need to restore the original 231 | * argv value since we parse options twice. */ 232 | if (name_end) 233 | *name_end = '='; 234 | continue; 235 | } 236 | 237 | /* Short options, may be clustered */ 238 | for (size_t char_i = 1; argv[arg_i] && argv[arg_i][char_i]; char_i++) { 239 | char letter = argv[arg_i][char_i]; 240 | /* Handle options without arguments */ 241 | switch (letter) { 242 | case 'e': 243 | /* Works the same way as -- */ 244 | if (!client && argv[arg_i++][char_i + 1]) 245 | usage(fd, argv[0], EXIT_FAILURE); 246 | goto finish; 247 | case 'h': 248 | if (client) break; 249 | usage(fd, argv[0], EXIT_SUCCESS); 250 | case 'v': 251 | if (client) break; 252 | version(fd); 253 | } 254 | 255 | /* Handle options with arguments (including implicit arguments) */ 256 | if (!is_boolean_short_option(letter)) { 257 | if (!argv[arg_i][++char_i]) arg_i++, char_i = 0; 258 | if (!argv[arg_i]) { 259 | if (client) break; 260 | usage(fd, argv[0], EXIT_FAILURE); 261 | } 262 | arg = argv[arg_i] + char_i; 263 | } else { 264 | arg = "true"; 265 | } 266 | 267 | if (!client && !is_client_only_short_option(letter)) 268 | send_short_opt(fd, letter, arg); 269 | else if (client) { 270 | switch (letter) { 271 | case 'q': 272 | need_exit = 1; 273 | break; 274 | case 'd': 275 | need_daemon = 1; 276 | break; 277 | case 'C': 278 | config_path = arg; 279 | break; 280 | case 's': 281 | socket_path = arg; 282 | break; 283 | } 284 | } 285 | 286 | if (!is_boolean_short_option(letter)) break; 287 | else continue; 288 | } 289 | } 290 | 291 | if (argv[arg_i]) { 292 | finish: 293 | if (!client) { 294 | if (!argv[arg_i]) 295 | usage(fd, argv[0], EXIT_FAILURE); 296 | while (argv[arg_i]) 297 | send_arg(fd, argv[arg_i++]); 298 | } 299 | } 300 | 301 | if (client && cwd && !(cwd = realpath(cwd, buffer))) 302 | fprintf(stderr, WARN_PREFIX"realpath(): %s\n", strerror(errno)); 303 | } 304 | 305 | static void do_fork(const char *spath) { 306 | int res; 307 | struct stat stt; 308 | switch (fork()) { 309 | case -1: 310 | exit(1); 311 | case 0: 312 | switch ((res = fork())) { 313 | case 0: 314 | setsid(); 315 | execlp("nsst", "nsst", "-d", NULL); 316 | /* fallthrough */ 317 | default: 318 | _exit(res <= 0); 319 | } 320 | default: 321 | while (wait(NULL) < 0 && errno == EINTR); 322 | /* Wait for socket */ 323 | struct timespec ts = {.tv_nsec = STARTUP_DELAY}; 324 | for (int i = 0; stat(spath, &stt) < 0 && i < MAX_WAIT_LOOP; i++) 325 | #if USE_CLOCK_NANOSLEEP 326 | clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); 327 | #else 328 | nanosleep(&ts, NULL); 329 | #endif 330 | } 331 | } 332 | 333 | static int try_connect(const char *spath) { 334 | struct sockaddr_un addr; 335 | memset(&addr, 0, sizeof addr); 336 | addr.sun_family = AF_UNIX; 337 | strncpy(addr.sun_path, spath, sizeof addr.sun_path - 1); 338 | 339 | int fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); 340 | if (fd < 0) return -1; 341 | 342 | if (connect(fd, (struct sockaddr *)&addr, 343 | offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path)) < 0) { 344 | close(fd); 345 | return -2; 346 | } 347 | return fd; 348 | } 349 | 350 | int main(int argc, char **argv) { 351 | 352 | (void)argc; 353 | 354 | cwd = getcwd(buffer, sizeof(buffer)); 355 | parse_args(argv, -1, true); 356 | 357 | int fd = try_connect(socket_path); 358 | if (fd < 0 && need_daemon) { 359 | do_fork(socket_path); 360 | fd = try_connect(socket_path); 361 | } 362 | 363 | if (fd < 0) { 364 | perror(fd == -2 ? "connect()" : "socket()"); 365 | return fd; 366 | } 367 | 368 | if (need_exit) { 369 | send_char(fd, '\031'); 370 | return 0; 371 | } 372 | 373 | send_header(fd, config_path); 374 | if (cwd) send_opt(fd, "cwd", cwd); 375 | 376 | parse_args(argv, fd, false); 377 | 378 | send_char(fd, '\003' /* ETX */); 379 | 380 | return 0; 381 | } 382 | -------------------------------------------------------------------------------- /poller.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2024, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef POLLER_H_ 4 | #define POLLER_H_ 1 5 | 6 | #include "feature.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | struct event; 14 | 15 | typedef bool (*poller_timer_cb_t)(void *arg); 16 | typedef void (*poller_fd_cb_t)(void *arg, uint32_t mask); 17 | typedef void (*poller_tick_cb_t)(void *arg); 18 | 19 | struct event *poller_add_fd(poller_fd_cb_t cb, void *arg, int fd, uint32_t mask); 20 | struct event *poller_add_timer(poller_timer_cb_t cb, void *arg, int64_t periodns); 21 | void poller_add_tick(poller_tick_cb_t tick, void *arg); 22 | void poller_remove(struct event *evt); 23 | void poller_toggle(struct event *evt, bool enable); 24 | bool poller_is_enabled(struct event *evt); 25 | void poller_stop(void); 26 | void poller_run(void); 27 | void poller_set_autoreset(struct event *evt, struct event **pevt); 28 | void poller_skip_wait(void); 29 | void poller_fd_set_mask(struct event *evt, uint32_t mask); 30 | uint32_t poller_fd_get_mask(struct event *evt); 31 | 32 | static inline bool poller_unset(struct event **evt) { 33 | if (!*evt) return false; 34 | poller_remove(*evt); 35 | return true; 36 | } 37 | 38 | static inline bool poller_set_timer(struct event **evt, poller_timer_cb_t cb, void *arg, int64_t periodns) { 39 | bool result = poller_unset(evt); 40 | *evt = poller_add_timer(cb, arg, periodns); 41 | poller_set_autoreset(*evt, evt); 42 | return result; 43 | } 44 | 45 | void init_poller(void); 46 | void free_poller(void); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /render-shm-wayland.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | 4 | #include "feature.h" 5 | 6 | /* Make linting always work for this 7 | * file (force choosing the right renderer 8 | * structure variant in window-impl.h)*/ 9 | #undef USE_WAYLANDSHM 10 | #define USE_WAYLANDSHM 1 11 | 12 | #include "config.h" 13 | #include "font.h" 14 | #include "mouse.h" 15 | #include "window-impl.h" 16 | #include "window-wayland.h" 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | extern bool has_fast_damage; 23 | 24 | static bool resize_buffer(struct window *win, struct wl_shm_pool *pool) { 25 | /* We create buffer width width smaller than the image by (ab-)using stride to avoid extra copies. 26 | * Who needs wp-viewporter when we have dirty hacks */ 27 | ssize_t stride = STRIDE(get_plat(win)->shm.im.width)*sizeof(color_t); 28 | struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, win->w.width, win->w.height, stride, WL_SHM_FORMAT_ARGB8888); 29 | if (!buffer) 30 | return false; 31 | 32 | if (get_plat(win)->buffer) 33 | wl_buffer_destroy(get_plat(win)->buffer); 34 | get_plat(win)->buffer = buffer; 35 | 36 | return true; 37 | } 38 | 39 | struct image wayland_shm_create_image(struct window *win, int16_t width, int16_t height) { 40 | struct image old = get_plat(win)->shm.im; 41 | 42 | get_plat(win)->shm.im = create_shm_image(width, height); 43 | 44 | static_assert(USE_POSIX_SHM, "System V shared memory is not supported with wayland backend"); 45 | 46 | struct wl_shm_pool *pool = wl_shm_create_pool(wl_shm, get_plat(win)->shm.im.shmid, STRIDE(width)*height*sizeof(color_t)); 47 | if (!pool) 48 | goto error; 49 | 50 | if (!resize_buffer(win, pool)) 51 | goto error; 52 | 53 | if (get_plat(win)->pool) 54 | wl_shm_pool_destroy(get_plat(win)->pool); 55 | get_plat(win)->pool = pool; 56 | 57 | return old; 58 | 59 | error: 60 | free_image(&get_plat(win)->shm.im); 61 | get_plat(win)->shm.im = old; 62 | warn("Can't create shm image"); 63 | return (struct image) {0}; 64 | } 65 | 66 | void wayland_shm_update(struct window *win, struct rect rect) { 67 | wl_surface_damage_buffer(get_plat(win)->surface, rect.x, rect.y, rect.width, rect.height); 68 | } 69 | 70 | void wayland_shm_resize_exact(struct window *win, int16_t new_w, int16_t new_h, int16_t old_w, int16_t old_h) { 71 | if (!resize_buffer(win, get_plat(win)->pool)) return; 72 | 73 | int16_t min_h = MIN(new_h, old_h); 74 | int16_t min_w = MIN(new_w, old_w); 75 | 76 | if (new_w > old_w) 77 | wayland_shm_update(win, (struct rect) { min_w, 0, new_w - old_w, MAX(new_h, old_h) }); 78 | if (new_h > old_h) 79 | wayland_shm_update(win, (struct rect) { 0, min_h, min_w, new_h - old_h }); 80 | } 81 | 82 | void wayland_shm_free(struct window *win) { 83 | if (get_plat(win)->buffer) 84 | wl_buffer_destroy(get_plat(win)->buffer); 85 | if (get_plat(win)->pool) 86 | wl_shm_pool_destroy(get_plat(win)->pool); 87 | if (get_plat(win)->shm.im.data) 88 | free_image(&get_plat(win)->shm.im); 89 | free(get_plat(win)->shm.bounds); 90 | } 91 | 92 | void wayland_shm_free_context(void) { 93 | /* nothing */ 94 | } 95 | 96 | void wayland_shm_init_context(void) { 97 | has_fast_damage = true; 98 | } 99 | -------------------------------------------------------------------------------- /render-shm-x11.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | 4 | #include "feature.h" 5 | 6 | /* Make linting always work for this 7 | * file (force choosing the right renderer 8 | * structure variant in window-impl.h)*/ 9 | #undef USE_X11SHM 10 | #define USE_X11SHM 1 11 | 12 | #include "config.h" 13 | #include "font.h" 14 | #include "mouse.h" 15 | #include "window-impl.h" 16 | #include "window-x11.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | static bool has_shm_pixmaps; 24 | extern bool has_fast_damage; 25 | 26 | /* Returns old image */ 27 | struct image x11_shm_create_image(struct window *win, int16_t width, int16_t height) { 28 | struct image old = get_plat(win)->shm.im; 29 | xcb_void_cookie_t c; 30 | 31 | if (has_fast_damage) { 32 | get_plat(win)->shm.im = create_shm_image(width, height); 33 | 34 | if (!get_plat(win)->shm_seg) { 35 | get_plat(win)->shm_seg = xcb_generate_id(con); 36 | } else { 37 | if (has_shm_pixmaps && get_plat(win)->shm_pixmap) 38 | xcb_free_pixmap(con, get_plat(win)->shm_pixmap); 39 | xcb_shm_detach_checked(con, get_plat(win)->shm_seg); 40 | } 41 | 42 | #if USE_POSIX_SHM 43 | c = xcb_shm_attach_fd_checked(con, get_plat(win)->shm_seg, dup(get_plat(win)->shm.im.shmid), 0); 44 | #else 45 | c = xcb_shm_attach_checked(con, get_plat(win)->shm_seg, get_plat(win)->shm.im.shmid, 0); 46 | #endif 47 | if (check_void_cookie(c)) goto error; 48 | 49 | if (has_shm_pixmaps) { 50 | if (!get_plat(win)->shm_pixmap) 51 | get_plat(win)->shm_pixmap = xcb_generate_id(con); 52 | c = xcb_shm_create_pixmap(con, get_plat(win)->shm_pixmap, 53 | get_plat(win)->wid, STRIDE(width), height, TRUE_COLOR_ALPHA_DEPTH, get_plat(win)->shm_seg, 0); 54 | if (check_void_cookie(c)) goto error; 55 | } 56 | } else { 57 | get_plat(win)->shm.im = create_image(width, height); 58 | } 59 | 60 | return old; 61 | 62 | error: 63 | free_image(&get_plat(win)->shm.im); 64 | get_plat(win)->shm.im = old; 65 | warn("Can't attach MITSHM image"); 66 | return (struct image) {0}; 67 | } 68 | 69 | void x11_shm_update(struct window *win, struct rect rect) { 70 | if (has_shm_pixmaps) { 71 | xcb_copy_area(con, get_plat(win)->shm_pixmap, get_plat(win)->wid, get_plat(win)->gc, rect.x, rect.y, rect.x, rect.y, rect.width, rect.height); 72 | } else if (has_fast_damage) { 73 | xcb_shm_put_image(con, get_plat(win)->wid, get_plat(win)->gc, STRIDE(get_plat(win)->shm.im.width), get_plat(win)->shm.im.height, rect.x, rect.y, 74 | rect.width, rect.height, rect.x, rect.y, TRUE_COLOR_ALPHA_DEPTH, XCB_IMAGE_FORMAT_Z_PIXMAP, 0, get_plat(win)->shm_seg, 0); 75 | } else { 76 | xcb_put_image(con, XCB_IMAGE_FORMAT_Z_PIXMAP, get_plat(win)->wid, get_plat(win)->gc, 77 | STRIDE(get_plat(win)->shm.im.width), rect.height, 0, rect.y, 0, TRUE_COLOR_ALPHA_DEPTH, 78 | rect.height * STRIDE(get_plat(win)->shm.im.width) * sizeof(color_t), 79 | (const uint8_t *)(get_plat(win)->shm.im.data + rect.y*STRIDE(get_plat(win)->shm.im.width))); 80 | } 81 | } 82 | 83 | void x11_shm_free(struct window *win) { 84 | if (has_fast_damage) 85 | xcb_shm_detach(con, get_plat(win)->shm_seg); 86 | if (has_shm_pixmaps) 87 | xcb_free_pixmap(con, get_plat(win)->shm_pixmap); 88 | if (get_plat(win)->shm.im.data) 89 | free_image(&get_plat(win)->shm.im); 90 | free(get_plat(win)->shm.bounds); 91 | } 92 | 93 | void x11_shm_free_context(void) { 94 | /* nothing */ 95 | } 96 | 97 | void x11_shm_init_context(void) { 98 | /* That's kind of hack 99 | * Try guessing if DISPLAY refers to localhost */ 100 | 101 | char *display = getenv("DISPLAY"); 102 | const char *local[] = { "localhost:", "127.0.0.1:", "unix:", }; 103 | bool localhost = display[0] == ':'; 104 | for (size_t i = 0; !localhost && i < LEN(local); i++) 105 | localhost = display == strstr(display, local[i]); 106 | 107 | if (localhost) { 108 | xcb_shm_query_version_cookie_t q = xcb_shm_query_version(con); 109 | xcb_generic_error_t *er = NULL; 110 | xcb_shm_query_version_reply_t *qr = xcb_shm_query_version_reply(con, q, &er); 111 | if (er) free(er); 112 | 113 | if (qr) { 114 | has_shm_pixmaps = qr->shared_pixmaps && 115 | qr->pixmap_format == XCB_IMAGE_FORMAT_Z_PIXMAP; 116 | free(qr); 117 | } 118 | if (!(has_fast_damage = qr && !er)) { 119 | warn("MIT-SHM is not available"); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /term.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef TERM_H_ 4 | #define TERM_H_ 1 5 | 6 | #include "feature.h" 7 | 8 | #include "util.h" 9 | #include "window.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | struct term; 18 | struct screen; 19 | 20 | typedef uint32_t uparam_t; 21 | typedef int32_t iparam_t; 22 | 23 | #define SCNparam SCNu32 24 | 25 | static inline struct cell *view_cell(struct line_span *view, ssize_t x) { 26 | return view->line->cell + view->offset + x; 27 | } 28 | 29 | static inline const struct attr *view_attr_at(struct line_span *view, ssize_t x) { 30 | return attr_at(view->line, view->offset + x); 31 | } 32 | 33 | static inline const struct attr *view_attr(struct line_span *view, uint32_t attrid) { 34 | return attrid ? &view->line->attrs->data[attrid - 1] : &ATTR_DEFAULT; 35 | } 36 | 37 | static inline void view_adjust_wide_right(struct line_span *view, ssize_t x) { 38 | adjust_wide_right(view->line, view->offset + x); 39 | } 40 | 41 | static inline void view_adjust_wide_left(struct line_span *view, ssize_t x) { 42 | adjust_wide_left(view->line, view->offset + x); 43 | } 44 | 45 | struct term *create_term(struct window *win, int16_t width, int16_t height); 46 | void free_term(struct term *term); 47 | void term_resize(struct term *term, int16_t width, int16_t height); 48 | bool term_is_requested_resize(struct term *term); 49 | void term_notify_resize(struct term *term, int16_t width, int16_t height, int16_t cw, int16_t ch); 50 | void term_handle_focus(struct term *term, bool focused); 51 | bool term_read(struct term *term); 52 | void term_scroll_view(struct term *term, int16_t amount); 53 | void term_scroll_view_to_cmd(struct term *term, int16_t amount); 54 | void term_reload_config(struct term *term); 55 | void term_toggle_numlock(struct term *term); 56 | struct keyboard_state *term_get_kstate(struct term *term); 57 | struct mouse_state *term_get_mstate(struct term *term); 58 | struct selection_state *term_get_sstate(struct term *term); 59 | struct window *term_window(struct term *term); 60 | color_t *term_palette(struct term *term); 61 | int term_fd(struct term *term); 62 | void term_paste(struct term *term, uint8_t *data, ssize_t size, bool utf8, bool is_first, bool is_last); 63 | void term_sendkey(struct term *term, const uint8_t *data, size_t size); 64 | void term_answerback(struct term *term, const char *str, ...) __attribute__ ((format (printf, 2, 3))); 65 | void term_reset(struct term *term); 66 | void term_set_reverse(struct term *term, bool set); 67 | void term_break(struct term *term); 68 | void term_hang(struct term *term); 69 | void term_toggle_read(struct term *term, bool enable); 70 | 71 | struct screen *term_screen(struct term *term); 72 | 73 | void screen_span_width(struct screen *scr, struct line_span *pos); 74 | bool screen_redraw(struct screen *scr, bool blink_commited); 75 | void screen_damage_lines(struct screen *scr, ssize_t ys, ssize_t yd); 76 | void screen_scroll_view(struct screen *scr, int16_t amount); 77 | struct line_span screen_view(struct screen *scr); 78 | struct line_span screen_span(struct screen *scr, ssize_t y); 79 | ssize_t screen_span_shift_n(struct screen *scr, struct line_span *pos, ssize_t amount); 80 | ssize_t screen_span_shift(struct screen *scr, struct line_span *pos); 81 | void screen_damage_selection(struct screen *scr); 82 | #if USE_URI 83 | void screen_damage_uri(struct screen *scr, uint32_t uri); 84 | #endif 85 | 86 | /* Needs to be multiple of 4 */ 87 | #define PASTE_BLOCK_SIZE 1024 88 | 89 | bool term_is_keep_clipboard_enabled(struct term *term); 90 | bool term_is_bell_urgent_enabled(struct term *term); 91 | bool term_is_bell_raise_enabled(struct term *term); 92 | bool term_is_utf8_enabled(struct term *term); 93 | bool term_is_nrcs_enabled(struct term *term); 94 | bool term_is_reverse(struct term *term); 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /tools/gen_tables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (c) 2022, Evgeniy Baskov. All rights reserved 3 | 4 | # This program requires UnicodeData.txt and EastAsianWidth.txt 5 | # in the working directory and generates iswide.h, wide.h and 6 | # precompose.h. Need to run it on every Unicode update. 7 | # References: 8 | # https://www.unicode.org/reports/tr44/ 9 | # https://www.unicode.org/Public/UCD/latest/ucd/ 10 | 11 | import math 12 | 13 | UINT8_MAX=0xFF 14 | UINT16_MAX=0xFFFF 15 | UNICODE_MAX=0x110000 16 | 17 | COMPACT_TO=0x40000 18 | COMPACT_FROM=0xE0000 19 | 20 | LEVEL2_TABLE_BITS=8 21 | LEVEL2_TABLE_ELEMENT_BITS_LOG=5 22 | LEVEL1_ELEMENTS_PER_LINE=16 23 | 24 | LEVEL2_TABLE_ELEMENT_BITS=(1<= COMPACT_TO: 36 | assert(value >= COMPACT_FROM) 37 | value += COMPACT_TO - COMPACT_FROM 38 | return value 39 | def UC(value): 40 | if value >= COMPACT_TO: 41 | value -= COMPACT_TO - COMPACT_FROM 42 | return value 43 | 44 | class CodePoint: 45 | def __init__(self, value, name, category, decomposition): 46 | self.value = value 47 | self.name = name 48 | self.category = category 49 | self.decomposition = decomposition 50 | if value < UNICODE_MAX: 51 | codepoints[value] = self 52 | 53 | def parse(): 54 | with open('UnicodeData.txt') as ud: 55 | ranged=None 56 | for line in ud.readlines(): 57 | fields = line.split(';') 58 | name, value = fields[1], C(int(fields[0], 16)) 59 | if ranged: 60 | assert(name.endswith('Last>')) 61 | for i in range(ranged,value+1): 62 | CodePoint(i, f'CODE POINT {UC(i):04X}', fields[2], '') 63 | ranged=None 64 | continue 65 | elif name.endswith('First>'): 66 | ranged=value 67 | continue 68 | CodePoint(value, name, fields[2], fields[5].split(' ')) 69 | with open('EastAsianWidth.txt') as eaw: 70 | for line in eaw.readlines(): 71 | comment = line.find('#') 72 | if comment >= 0: 73 | line = line[0:comment] 74 | line = line.strip() 75 | if not line: 76 | continue 77 | line = line.split(';') 78 | if line[0].find('..') > 0: 79 | r = line[0].split('..') 80 | start = int(r[0], 16) 81 | end = int(r[1], 16) 82 | else: 83 | start = int(line[0], 16) 84 | end = start 85 | for i in range(start, end + 1): 86 | if not codepoints[C(i)]: 87 | CodePoint(value=C(i), name=f'UNDEFINED CODE POINT {i:04X}', category='Cn', decomposition='') 88 | codepoints[C(i)].widthtype = line[1] 89 | 90 | def filter_compose(): 91 | precomp=[] 92 | for cp in codepoints: 93 | if not cp: 94 | continue 95 | dec = [int(i, 16) for i in cp.decomposition if i and not i.startswith('<')] 96 | if len(dec) != 2: 97 | continue 98 | cp2 = codepoints[dec[1]] 99 | if not cp2 or cp2.category not in ['Mn', 'Mc']: 100 | continue 101 | precomp.append((dec[0],dec[1],cp.value)) 102 | precomp.sort() 103 | return precomp 104 | 105 | def compress_table(table): 106 | global level1_total_elems 107 | max_nonempty, min_nonempty, level1_table = None, None, [] 108 | for i in range((UNICODE_MAX+LEVEL2_TABLE_WIDTH-1)//LEVEL2_TABLE_WIDTH): 109 | row = [0]*LEVEL2_TABLE_ROW_LEN 110 | for k in range(LEVEL2_TABLE_WIDTH): 111 | if table[i*LEVEL2_TABLE_WIDTH+k]: 112 | row[k//LEVEL2_TABLE_ELEMENT_BITS] |= 1 << (k & (LEVEL2_TABLE_ELEMENT_BITS - 1)) 113 | if min_nonempty is None: 114 | min_nonempty = k+i*LEVEL2_TABLE_WIDTH 115 | row = tuple(row) 116 | if row not in level2_table: 117 | level2_table[row] = len(level2_table) 118 | level1_table.append(level2_table[row]) 119 | if empty_lvl2_row != row: 120 | max_nonempty = len(level1_table) 121 | level1_table=level1_table[:max_nonempty] 122 | level1_total_elems += len(level1_table) 123 | return level1_table, min_nonempty 124 | 125 | def write_level1_table(f, g, name, table): 126 | type = '8' if len(level2_table) <= UINT8_MAX else '16' 127 | f.write(f'const uint{type}_t {name}_table1_[{len(table)}] = {{\n') 128 | g.write(f'extern const uint{type}_t {name}_table1_[{len(table)}];\n') 129 | for i in range((len(table)+LEVEL1_ELEMENTS_PER_LINE-1)//LEVEL1_ELEMENTS_PER_LINE): 130 | f.write(' ') 131 | for j in range(LEVEL1_ELEMENTS_PER_LINE): 132 | index=i*LEVEL1_ELEMENTS_PER_LINE+j 133 | if index >= len(table): 134 | break 135 | f.write(f'{table[index]:3},') 136 | f.write(f' /* 0x{i*LEVEL1_ELEMENTS_PER_LINE*LEVEL2_TABLE_WIDTH:X}-0x{(i+1)*LEVEL1_ELEMENTS_PER_LINE*LEVEL2_TABLE_WIDTH-1:X} */\n') 137 | f.write('};\n\n') 138 | 139 | def write_level2_table(f, g): 140 | f.write(f"const uint32_t width_data_[{len(level2_table)}][{LEVEL2_TABLE_ROW_LEN}] = {{\n") 141 | g.write(f"extern const uint32_t width_data_[{len(level2_table)}][{LEVEL2_TABLE_ROW_LEN}];\n") 142 | for row in level2_table: 143 | f.write(' { ') 144 | for elem in row: 145 | f.write(f'0x{elem:08X}, ') 146 | f.write(f'}}, /* {level2_table[row]} */\n') 147 | f.write('};\n\n') 148 | 149 | def write_precompose(f, precomp): 150 | # Generate two tables a smaller one with 16 bit elements 151 | # and larger one with 32 bit elements. 152 | global precomp_elems 153 | precomp_elems = 0 154 | f.write('#ifndef _PRECOMPOSE_H\n#define _PRECOMPOSE_H 1\n\n' 155 | '#include \n\n' 156 | 'static struct pre1_item {\n uint16_t src, mod, dst;\n} pre1_tab[] = {\n') 157 | for i in precomp: 158 | if i[0] <= UINT16_MAX and i[1] <= UINT16_MAX and i[2] <= UINT16_MAX: 159 | f.write(f' {{0x{i[0]:04X}, 0x{i[1]:04X}, 0x{i[2]:04X}}},\n') 160 | precomp_elems += 1 161 | f.write('};\n\nstatic struct pre2_item {\n uint32_t src, mod, dst;\n} pre2_tab[] = {\n') 162 | for i in precomp: 163 | if i[0] > UINT16_MAX or i[1] > UINT16_MAX or i[2] > UINT16_MAX: 164 | f.write(f' {{0x{i[0]:X}, 0x{i[1]:X}, 0x{i[2]:X}}},\n') 165 | precomp_elems += 2 166 | f.write('};\n\n\n#endif\n') 167 | 168 | def write_predicate(f, min_elem, x): 169 | f.write(f'\nstatic inline bool is{x}_compact(uint32_t x) {{\n' 170 | f' return x - 0x{min_elem:X}U < 0x{LEVEL2_TABLE_WIDTH:X}*sizeof({x}_table1_)/sizeof({x}_table1_[0]) - 0x{min_elem:X}U &&\n' 171 | f' width_data_[{x}_table1_[x >> {LEVEL2_TABLE_BITS}]][(x >> 5) &' 172 | f' {(LEVEL2_TABLE_WIDTH//LEVEL2_TABLE_ELEMENT_BITS)-1}] & (1U << (x & 0x1F));\n' 173 | f'}}\n' 174 | f'\nstatic inline bool is{x}(uint32_t x) {{\n' 175 | f' return is{x}_compact(compact(x));\n' 176 | f'}}\n\n') 177 | 178 | def set_predicate(table, fun): 179 | for cp in codepoints: 180 | if not cp: 181 | continue 182 | if fun(cp): 183 | table[cp.value] = True 184 | 185 | def set_ranges(table, value, *ranges): 186 | for r in ranges: 187 | if type(r) is int: 188 | table[r] = value 189 | else: 190 | for k in range(r[0],r[1]+1): 191 | table[k] = value 192 | 193 | def mk_wide(): 194 | table=[False]*UNICODE_MAX 195 | set_predicate(table, lambda cp: cp.widthtype == 'W' 196 | and cp.category not in ['Cn', 'Mn']) 197 | return table 198 | 199 | def mk_combining(): 200 | table=[False]*UNICODE_MAX 201 | set_ranges(table, True, [0x1160, 0x11FF], [0xD7B0, 0xD7C6], [0xD7CB,0xD7FB]) # Hangul 202 | # Consider "SOFT HYPHEN" to be a printable character for compatibility 203 | set_predicate(table, lambda cp: cp.category in ['Me', 'Mn', 'Cf'] and cp.value != 0xAD) 204 | return table 205 | 206 | def mk_ambiguous(): 207 | # FIXME: This suggests to mark every character, that is not wide as ambiguous, 208 | # not sure if it should be like this... 209 | # uniset +0000..DFFF -4e00..9fd5 +F900..10FFFD unknown +2028..2029 c 210 | #set_ranges(table, [0, 0x4DFF], [0x9FD6, 0xDFFF], [0xF900, 0x10FFFD], [0x2028, 0x2029]) 211 | #for k in range(UNICODE_MAX): 212 | # if not codepoints[k]: 213 | # table[k] = True 214 | 215 | # uniset +WIDTH-A c 216 | table=[False]*UNICODE_MAX 217 | set_predicate(table, lambda cp: cp.widthtype == 'A') 218 | return table 219 | 220 | generated_msg="""/* 221 | * This file was generated with tools/gen_tables.py. 222 | * DO NOT EDIT IT DIRECTLY. 223 | * Edit generator instead. 224 | */ 225 | 226 | """ 227 | iswide_preambula="""#ifndef _ISWIDE_H 228 | #define _ISWIDE_H 1 229 | 230 | #include 231 | #include 232 | 233 | /* 234 | * Since Unicode does not allocate code points 235 | * in planes 4-13 (and plane 14 contains only control characters), 236 | * we can save a few bits for attributes by compressing unicode like: 237 | * 238 | * [0x00000, 0x3FFFF] -> [0x00000, 0x3FFFF] (planes 0-3) 239 | * [0x40000, 0xDFFFF] -> nothing 240 | * [0xE0000,0x10FFFF] -> [0x40000, 0x7FFFF] (planes 14-16 -- Special Purpose Plane, PUA) 241 | * 242 | * And with this encoding scheme 243 | * we can encode all defined characters only with 19 bits. 244 | * 245 | * And so we have as much as 13 bits left for flags and attributes. 246 | */ 247 | 248 | #define CELL_ENC_COMPACT_BASE 0x40000 249 | #define CELL_ENC_UTF8_BASE 0xE0000 250 | 251 | static inline uint32_t uncompact(uint32_t u) { 252 | return u < CELL_ENC_COMPACT_BASE ? u : u + (CELL_ENC_UTF8_BASE - CELL_ENC_COMPACT_BASE); 253 | } 254 | 255 | static inline uint32_t compact(uint32_t u) { 256 | return u < CELL_ENC_UTF8_BASE ? u : u - (CELL_ENC_UTF8_BASE - CELL_ENC_COMPACT_BASE); 257 | } 258 | 259 | """ 260 | 261 | def gen_precompose(precompose_file): 262 | with open(precompose_file, 'w') as f: 263 | f.write(generated_msg) 264 | write_precompose(f, filter_compose()) 265 | 266 | def gen_wide(wide_file, iswide_file): 267 | with open(wide_file, 'w') as f, open(iswide_file, 'w') as g: 268 | # FIXME Support ambiguos characters in nsst 269 | f.write(generated_msg) 270 | f.write('#ifndef _WIDE_H\n#define _WIDE_H 1\n\n#include \n\n') 271 | g.write(generated_msg) 272 | g.write(iswide_preambula) 273 | wtab, wtab_min = compress_table(mk_wide()) 274 | ctab, ctab_min = compress_table(mk_combining()) 275 | #atab, atab_min = compress_table(mk_ambiguous()) 276 | write_level1_table(f, g, "wide", wtab) 277 | write_level1_table(f, g, "combining", ctab) 278 | #write_level1_table(f, g, "ambiguos", atab) 279 | write_level2_table(f, g) 280 | write_predicate(g, wtab_min, 'wide') 281 | write_predicate(g, ctab_min, 'combining') 282 | #write_predicate(g, atab_min, 'ambiguos') 283 | g.write('#endif\n') 284 | f.write('#endif\n') 285 | 286 | parse() 287 | gen_precompose('precompose-table.h') 288 | gen_wide('width-table.h', 'iswide.h') 289 | 290 | size2 = len(level2_table)*(LEVEL2_TABLE_WIDTH//8) 291 | size1 = (1 if len(level2_table) <= UINT8_MAX else 2)*level1_total_elems 292 | size3 = precomp_elems*3*2 293 | print("Generated", size1+size2+size3, "bytes of tables") 294 | -------------------------------------------------------------------------------- /tty.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef TTY_H_ 4 | #define TTY_H_ 1 5 | 6 | #define FD_BUF_SIZE 16384 7 | 8 | #include "config.h" 9 | #include "list.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | struct watcher { 16 | struct list_head link; 17 | pid_t child; 18 | int fd; 19 | }; 20 | 21 | /* Printer controller mode 22 | * parser state */ 23 | enum pr_state { 24 | pr_ground, 25 | pr_csi, 26 | pr_esc, 27 | pr_bracket, 28 | pr_5, 29 | pr_4, 30 | pr_ignore, 31 | }; 32 | 33 | struct printer { 34 | struct watcher w; 35 | enum pr_state state; 36 | bool print_controller; 37 | }; 38 | 39 | struct deferred_write { 40 | struct list_head link; 41 | ssize_t offset; 42 | ssize_t size; 43 | char data[]; 44 | }; 45 | 46 | struct tty { 47 | struct watcher w; 48 | struct list_head deferred; 49 | struct event *evt; 50 | ssize_t deferred_count; 51 | 52 | uint8_t fd_buf[FD_BUF_SIZE]; 53 | uint8_t *start; 54 | uint8_t *end; 55 | }; 56 | 57 | static inline bool tty_has_data(struct tty *tty) { 58 | return tty->start < tty->end; 59 | } 60 | 61 | void init_default_termios(void); 62 | void hang_watched_children(void); 63 | 64 | struct window; 65 | 66 | int tty_open(struct tty *tty, struct instance_config *cfg, struct window *win); 67 | void tty_break(struct tty *tty); 68 | void tty_set_winsz(struct tty *tty, int16_t width, int16_t height, int16_t wwidth, int16_t wheight); 69 | ssize_t tty_refill(struct tty *tty); 70 | void tty_write(struct tty *tty, const uint8_t *buf, size_t len, bool crlf); 71 | void tty_hang(struct tty *tty); 72 | void tty_toggle_read(struct tty *tty, bool enable); 73 | 74 | void printer_intercept(struct printer *pr, const uint8_t **start, const uint8_t *end); 75 | void printer_print_string(struct printer *pr, const uint8_t *str, ssize_t size); 76 | bool printer_is_available(struct printer *pr); 77 | void free_printer(struct printer *pr); 78 | void init_printer(struct printer *pr, struct instance_config *cfg); 79 | void handle_term_read(void *win_, uint32_t mask); 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /uri.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef URI_H_ 4 | #define URI_H_ 1 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | enum uri_match_result { 12 | urim_ground, 13 | urim_need_more, 14 | urim_may_finish, 15 | urim_finished, 16 | }; 17 | 18 | enum uri_match_state1 { 19 | uris1_ground, 20 | uris1_colon, 21 | uris1_slash1, 22 | uris1_slash2, 23 | uris1_user, 24 | uris1_host, 25 | uris1_port, 26 | uris1_path, 27 | uris1_query, 28 | uris1_fragment, 29 | uris1_p_hex1, 30 | uris1_p_hex2, 31 | uris1_filename 32 | }; 33 | 34 | struct uri_match_state { 35 | enum uri_match_state1 state; 36 | enum uri_match_state1 saved; 37 | enum uri_match_result res; 38 | bool matched_file_proto; 39 | size_t size; 40 | size_t caps; 41 | char *data; 42 | bool no_copy; 43 | }; 44 | 45 | #define EMPTY_URI 0 46 | #define MAX_PROTOCOL_LEN 16 47 | 48 | uint32_t uri_add(const char *uri, const char *id); 49 | void uri_ref(uint32_t uri); 50 | void uri_unref(uint32_t uri); 51 | void uri_open(const char *opencmd, uint32_t uri); 52 | const char *uri_get(uint32_t uri); 53 | 54 | const uint8_t *match_reverse_proto_tree(struct uri_match_state *stt, const uint8_t *str, ssize_t len); 55 | enum uri_match_result uri_match_next_from_colon(struct uri_match_state *state, uint8_t ch); 56 | void uri_match_reset(struct uri_match_state *state, bool soft); 57 | char *uri_match_get(struct uri_match_state *state); 58 | void uri_release_memory(void); 59 | 60 | void init_proto_tree(void); 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022,2025, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef UTIL_H_ 4 | #define UTIL_H_ 1 5 | 6 | #include "feature.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define ROUNDUP(x, align) (((x) + (align) - 1) & ~((uintptr_t)(align) - 1)) 15 | #define ROUNDDOWN(x, align) ((x) & ~((uintptr_t)(align) - 1)) 16 | 17 | #define MIN(x, y) ((x) < (y) ? (x) : (y)) 18 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 19 | 20 | #define SWAP(a, b) do {__typeof__(a) t__ = (a); (a) = (b); (b) = t__;}while (0) 21 | 22 | #define SEC 1000000000LL 23 | #define CACHE_LINE 64 24 | 25 | #define IS_SAME_TYPE_(a, b) __builtin_types_compatible_p(__typeof__(a), __typeof__(b)) 26 | #define IS_ARRAY_(arr) (!IS_SAME_TYPE_((arr), &(arr)[0])) 27 | #define MUST_BE_(e) (0*(size_t)sizeof(struct {_Static_assert(e, "Argument has wrong type");int dummy__;})) 28 | 29 | #define CONTAINEROF(ptr, T, member) ((T*)__builtin_assume_aligned((char *)ptr - offsetof(T, member), _Alignof(T)) \ 30 | + MUST_BE_(IS_SAME_TYPE_(&((T*)0)->member, ptr))) 31 | 32 | #define LEN(x) (sizeof(x)/sizeof((x)[0]) + MUST_BE_(IS_ARRAY_(x))) 33 | 34 | #define LIKELY(x) (__builtin_expect(!!(x), 1)) 35 | #define UNLIKELY(x) (__builtin_expect((x), 0)) 36 | #define ASSUME(x) do { if(!(x)) __builtin_unreachable(); }while(0) 37 | #define PACKED __attribute__((packed)) 38 | #define HOT __attribute__((hot)) 39 | #define FORCEINLINE __attribute__((always_inline)) 40 | #define ALIGNED(n) __attribute__((aligned(n))) 41 | 42 | #ifdef CLOCK_MONOTONIC_RAW 43 | # define CLOCK_TYPE CLOCK_MONOTONIC_RAW 44 | #else 45 | # define CLOCK_TYPE CLOCK_MONOTONIC 46 | #endif 47 | 48 | #define PROFILE_BEGIN do {\ 49 | struct timespec start__, end__; \ 50 | clock_gettime(CLOCK_TYPE, &start__); 51 | #define PROFILE_END(label) \ 52 | clock_gettime(CLOCK_TYPE, &end__); \ 53 | warn(label " took %lfms", ts_diff(&start__, &end__)/1000000.); \ 54 | } while (0) 55 | 56 | #define PROFILE_FUNC(f) \ 57 | PROFILE_BEGIN\ 58 | f; \ 59 | PROFILE_END(#f) 60 | 61 | typedef uint32_t color_t; 62 | struct rect { 63 | int16_t x; 64 | int16_t y; 65 | uint16_t width; 66 | uint16_t height; 67 | }; 68 | 69 | void info(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); 70 | void warn(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); 71 | void fatal(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); 72 | _Noreturn void die(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); 73 | 74 | #define MALLOC_ALIGNMENT 16 75 | 76 | /* malloc() wrappers which calls die() on failure and 77 | * ensure 16 bytes alignment */ 78 | void *xalloc(size_t size); 79 | void *xzalloc(size_t size); 80 | void *xrealloc(void *src, size_t old_size, size_t size); 81 | void *xrezalloc(void *src, size_t old_size, size_t size); 82 | void xtrim_heap(void); 83 | 84 | static inline struct rect rect_scale_up(struct rect rect, int16_t x_factor, int16_t y_factor) { 85 | rect.x *= x_factor; 86 | rect.y *= y_factor; 87 | rect.width *= x_factor; 88 | rect.height *= y_factor; 89 | return rect; 90 | } 91 | static inline struct rect rect_scale_down(struct rect rect, int16_t x_factor, int16_t y_factor) { 92 | rect.x /= x_factor; 93 | rect.y /= y_factor; 94 | rect.width /= x_factor; 95 | rect.height /= y_factor; 96 | return rect; 97 | } 98 | static inline struct rect rect_shift(struct rect rect, int16_t x_off, int16_t y_off) { 99 | rect.x += x_off; 100 | rect.y += y_off; 101 | return rect; 102 | } 103 | static inline struct rect rect_resize(struct rect rect, int16_t x_off, int16_t y_off) { 104 | rect.width += x_off; 105 | rect.height += y_off; 106 | return rect; 107 | } 108 | static inline struct rect rect_union(struct rect rect, struct rect other) { 109 | rect.width = MAX(rect.width + rect.x, other.width + other.x); 110 | rect.height = MAX(rect.height + rect.y, other.height + other.y); 111 | rect.width -= rect.x = MIN(rect.x, other.x); 112 | rect.height -= rect.y = MIN(rect.y, other.y); 113 | return rect; 114 | } 115 | 116 | static inline bool intersect_with(struct rect *src, struct rect *dst) { 117 | struct rect inters = { .x = MAX(src->x, dst->x), .y = MAX(src->y, dst->y) }; 118 | 119 | int32_t x1 = MIN(src->x + (int32_t)src->width, dst->x + (int32_t)dst->width); 120 | int32_t y1 = MIN(src->y + (int32_t)src->height, dst->y + (int32_t)dst->height); 121 | 122 | if (x1 <= inters.x || y1 <= inters.y) { 123 | *src = (struct rect) {0, 0, 0, 0}; 124 | return 0; 125 | } else { 126 | inters.width = x1 - inters.x; 127 | inters.height = y1 - inters.y; 128 | *src = inters; 129 | return 1; 130 | } 131 | } 132 | 133 | static inline bool ts_leq(struct timespec *a, struct timespec *b) { 134 | return a->tv_sec < b->tv_sec || (a->tv_sec == b->tv_sec && a->tv_nsec <= b->tv_nsec); 135 | } 136 | 137 | static inline struct timespec ts_sub_sat(struct timespec *a, struct timespec *b) { 138 | if (ts_leq(a, b)) 139 | return (struct timespec) {0}; 140 | struct timespec result = { 141 | .tv_sec = a->tv_sec - b->tv_sec, 142 | .tv_nsec = a->tv_nsec - b->tv_nsec, 143 | }; 144 | if (result.tv_nsec < 0) { 145 | result.tv_nsec += SEC; 146 | result.tv_sec--; 147 | } 148 | return result; 149 | } 150 | 151 | static inline struct timespec ts_add(struct timespec *a, int64_t inc) { 152 | long ns = a->tv_nsec + inc; 153 | long sadj = (ns - (SEC + 1)*(inc < 0)) / SEC; 154 | return (struct timespec) { 155 | .tv_sec = a->tv_sec + sadj, 156 | .tv_nsec = ns - sadj * SEC 157 | }; 158 | } 159 | 160 | static inline int64_t ts_diff(struct timespec *b, struct timespec *a) { 161 | return (a->tv_sec - b->tv_sec)*SEC + (a->tv_nsec - b->tv_nsec); 162 | } 163 | 164 | /* Adjust buffer capacity if no space left (size > *caps) */ 165 | void adjust_buffer(void **buf, size_t *caps, size_t size, size_t elem); 166 | 167 | /* Version information helper functions */ 168 | #define MAX_OPTION_DESC 512 169 | const char *version_string(void); 170 | const char *features_string(void); 171 | const char *usage_string(char buffer[static MAX_OPTION_DESC + 1], ssize_t idx); 172 | 173 | #define UTF8_MAX_LEN 4 174 | #define UTF_INVAL 0xFFFD 175 | 176 | #include "iswide.h" 177 | 178 | static inline int uwidth(uint32_t x) { 179 | /* This variant wcwidth treats 180 | * C0 and C1 characters as of width 1 */ 181 | if (LIKELY(x < 0x300)) return 1; 182 | if (UNLIKELY(iscombining(x))) return 0; 183 | return 1 + iswide(x); 184 | } 185 | 186 | 187 | size_t utf8_encode(uint32_t u, uint8_t *buf, uint8_t *end); 188 | bool utf8_decode(uint32_t *res, const uint8_t **buf, const uint8_t *end); 189 | 190 | /* *_decode returns source buffer end */ 191 | /* *_encode returns destination buffer end */ 192 | uint8_t *hex_encode(uint8_t *dst, const uint8_t *str, const uint8_t *end); 193 | const uint8_t *hex_decode(uint8_t *dst, const uint8_t *hex, const uint8_t *end); 194 | uint8_t *base64_encode(uint8_t *dst, const uint8_t *buf, const uint8_t *end); 195 | const uint8_t *base64_decode(uint8_t *dst, const uint8_t *buf, const uint8_t *end); 196 | 197 | color_t parse_color(const uint8_t *str, const uint8_t *end); 198 | 199 | /* Unicode precomposition */ 200 | uint32_t try_precompose(uint32_t ch, uint32_t comb); 201 | 202 | int set_cloexec(int fd); 203 | int set_nonblocking(int fd); 204 | 205 | #endif 206 | -------------------------------------------------------------------------------- /window-wayland.h: -------------------------------------------------------------------------------- 1 | #ifndef WINDOW_PLATFORM_WAYLAND_H_ 2 | #define WINDOW_PLATFORM_WAYLAND_H_ 3 | 4 | #include "feature.h" 5 | 6 | #include "util.h" 7 | #include "list.h" 8 | #if USE_WAYLANDSHM 9 | # include "image.h" 10 | #endif 11 | #include "window-impl.h" 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | enum win_ptr_kind { 18 | win_ptr_other, 19 | win_ptr_keyboard, 20 | win_ptr_paste, 21 | }; 22 | 23 | struct window_ptr { 24 | struct list_head link; 25 | struct window *win; 26 | enum win_ptr_kind kind; 27 | }; 28 | 29 | struct wayland_window { 30 | union { 31 | #if USE_WAYLANDSHM 32 | struct { 33 | struct platform_shm shm; 34 | struct wl_buffer *buffer; 35 | struct wl_shm_pool *pool; 36 | }; 37 | #endif 38 | }; 39 | 40 | struct wl_surface *surface; 41 | struct xdg_surface *xdg_surface; 42 | struct xdg_toplevel *xdg_toplevel; 43 | struct wl_data_source *data_source; 44 | struct zwp_primary_selection_source_v1 *primary_selection_source; 45 | struct zxdg_toplevel_decoration_v1 *decoration; 46 | struct wl_callback *frame_callback; 47 | 48 | struct cursor *cursor; 49 | struct cursor *cursor_uri; 50 | struct cursor *cursor_resize; 51 | struct cursor *cursor_default; 52 | struct cursor *cursor_user; 53 | enum hide_pointer_mode cursor_mode; 54 | bool cursor_is_hidden; 55 | 56 | /* We cannot query the title so we need to store them */ 57 | char *title; 58 | char *icon_title; 59 | 60 | /* These list is used to implement zeroing of week pointers */ 61 | struct list_head pointers; 62 | 63 | /* We cannot query the pointer state, so store it here */ 64 | struct { 65 | int32_t x; 66 | int32_t y; 67 | uint32_t mask; 68 | } mouse; 69 | 70 | struct { 71 | int32_t width; 72 | int32_t height; 73 | bool resize; 74 | } pending_configure; 75 | 76 | struct extent output_size; 77 | 78 | bool can_maximize : 1; 79 | bool can_minimize : 1; 80 | bool can_fullscreen : 1; 81 | bool is_maximized : 1; 82 | bool is_fullscreen : 1; 83 | bool is_resizing : 1; 84 | bool is_tiled : 1; 85 | bool use_ssd : 1; 86 | } ALIGNED(MALLOC_ALIGNMENT); 87 | 88 | extern struct wl_shm *wl_shm; 89 | 90 | static inline struct wayland_window *get_plat(struct window *win) { 91 | return (struct wayland_window *)win->platform_window_opaque; 92 | } 93 | 94 | static inline void win_ptr_clear(struct window_ptr *ptr) { 95 | if (ptr->win) { 96 | list_remove(&ptr->link); 97 | ptr->win = NULL; 98 | } 99 | } 100 | 101 | static inline void win_ptr_set(struct window_ptr *ptr, struct window *win, enum win_ptr_kind kind) { 102 | win_ptr_clear(ptr); 103 | list_insert_after(&get_plat(win)->pointers, &ptr->link); 104 | ptr->win = win; 105 | ptr->kind = kind; 106 | } 107 | 108 | /* Move pointer to the start of the list */ 109 | static inline void win_ptr_ping(struct window_ptr *ptr) { 110 | if (ptr->win) { 111 | list_remove(&ptr->link); 112 | list_insert_after(&get_plat(ptr->win)->pointers, &ptr->link); 113 | } 114 | } 115 | 116 | #if USE_WAYLANDSHM 117 | void wayland_shm_init_context(void); 118 | void wayland_shm_free_context(void); 119 | void wayland_shm_free(struct window *win); 120 | void wayland_shm_update(struct window *win, struct rect rect); 121 | void wayland_shm_resize_exact(struct window *win, int16_t new_w, int16_t new_h, int16_t old_w, int16_t old_h); 122 | struct extent wayland_shm_size(struct window *win, bool artificial); 123 | struct image wayland_shm_create_image(struct window *win, int16_t width, int16_t height); 124 | 125 | void shm_recolor_border(struct window *win); 126 | bool shm_reload_font(struct window *win, bool need_free); 127 | bool shm_submit_screen(struct window *win, int16_t cur_x, ssize_t cur_y, bool cursor, bool marg); 128 | void shm_copy(struct window *win, struct rect dst, int16_t sx, int16_t sy); 129 | void shm_resize(struct window *win, int16_t new_w, int16_t new_h, int16_t new_cw, int16_t new_ch, bool artificial); 130 | #endif 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /window-x11.h: -------------------------------------------------------------------------------- 1 | #ifndef WINDOW_PLATFORM_X11_H_ 2 | #define WINDOW_PLATFORM_X11_H_ 3 | 4 | #include "feature.h" 5 | 6 | #include "util.h" 7 | #if USE_X11SHM 8 | # include "image.h" 9 | #endif 10 | #include "window-impl.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #if USE_X11SHM 16 | # include 17 | #endif 18 | #if USE_XRENDER 19 | # include 20 | #endif 21 | 22 | #define TRUE_COLOR_ALPHA_DEPTH 32 23 | 24 | struct platform_window { 25 | union { 26 | #if USE_X11SHM 27 | struct { 28 | /* This must be the first member so that 29 | * get_shm() in render-shm.c functions correctly*/ 30 | struct platform_shm shm; 31 | xcb_shm_seg_t shm_seg; 32 | xcb_pixmap_t shm_pixmap; 33 | }; 34 | #endif 35 | #if USE_XRENDER 36 | struct { 37 | /* Active IDs, actual X11 objects */ 38 | xcb_pixmap_t pid1; 39 | xcb_render_picture_t pic1; 40 | /* Cached IDs, used for copying */ 41 | xcb_pixmap_t pid2; 42 | xcb_render_picture_t pic2; 43 | 44 | xcb_pixmap_t glyph_pid; 45 | 46 | xcb_render_picture_t pen; 47 | xcb_render_glyphset_t gsid; 48 | xcb_render_pictformat_t pfglyph; 49 | }; 50 | #endif 51 | }; 52 | 53 | xcb_window_t wid; 54 | xcb_gcontext_t gc; 55 | xcb_event_mask_t ev_mask; 56 | struct cursor *cursor; 57 | struct cursor *cursor_default; 58 | struct cursor *cursor_user; 59 | struct cursor *cursor_uri; 60 | enum hide_pointer_mode cursor_mode; 61 | bool cursor_is_hidden; 62 | 63 | /* Used to restore maximized window */ 64 | struct rect saved; 65 | } ALIGNED(MALLOC_ALIGNMENT); 66 | 67 | extern xcb_connection_t *con; 68 | 69 | static inline struct platform_window *get_plat(struct window *win) { 70 | return (struct platform_window *)win->platform_window_opaque; 71 | } 72 | 73 | static inline bool check_void_cookie(xcb_void_cookie_t ck) { 74 | xcb_generic_error_t *err = xcb_request_check(con, ck); 75 | if (err) { 76 | warn("[X11 Error Checked] major=%"PRIu8", minor=%"PRIu16", error=%"PRIu8, 77 | err->major_code, err->minor_code, err->error_code); 78 | return true; 79 | } 80 | free(err); 81 | return false; 82 | } 83 | 84 | #if USE_X11SHM 85 | void x11_shm_init_context(void); 86 | void x11_shm_free_context(void); 87 | void x11_shm_free(struct window *win); 88 | void x11_shm_update(struct window *win, struct rect rect); 89 | struct image x11_shm_create_image(struct window *win, int16_t width, int16_t height); 90 | struct extent x11_shm_size(struct window *win, bool artificial); 91 | 92 | void shm_recolor_border(struct window *win); 93 | bool shm_reload_font(struct window *win, bool need_free); 94 | bool shm_submit_screen(struct window *win, int16_t cur_x, ssize_t cur_y, bool cursor, bool marg); 95 | void shm_copy(struct window *win, struct rect dst, int16_t sx, int16_t sy); 96 | void shm_resize(struct window *win, int16_t new_w, int16_t new_h, int16_t new_cw, int16_t new_ch, bool artificial); 97 | #endif 98 | 99 | #if USE_XRENDER 100 | void x11_xrender_init_context(void); 101 | void x11_xrender_free_context(void); 102 | void x11_xrender_free(struct window *win); 103 | void x11_xrender_update(struct window *win, struct rect rect); 104 | bool x11_xrender_reload_font(struct window *win, bool need_free); 105 | void x11_xrender_resize(struct window *win, int16_t new_w, int16_t new_h, int16_t new_cw, int16_t new_ch, bool artificial); 106 | void x11_xrender_copy(struct window *win, struct rect dst, int16_t sx, int16_t sy); 107 | void x11_xrender_recolor_border(struct window *win); 108 | bool x11_xrender_submit_screen(struct window *win, int16_t cur_x, ssize_t cur_y, bool cursor, bool marg); 109 | #endif 110 | 111 | void x11_update_window_props(struct window *win); 112 | struct extent x11_get_screen_size(struct window *win); 113 | void x11_apply_geometry(struct window *win, struct geometry *geometry); 114 | 115 | #endif 116 | -------------------------------------------------------------------------------- /window.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2022, Evgeniy Baskov. All rights reserved */ 2 | 3 | #ifndef WINDOW_H_ 4 | #define WINDOW_H_ 1 5 | 6 | #include "feature.h" 7 | 8 | #include "font.h" 9 | #include "config.h" 10 | #include "util.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | /* WARNING: Order is important */ 18 | enum mouse_event_type { 19 | mouse_event_press, 20 | mouse_event_release, 21 | mouse_event_motion, 22 | }; 23 | 24 | enum modifier_mask { 25 | mask_shift = 1 << 0, 26 | mask_lock = 1 << 1, 27 | mask_control = 1 << 2, 28 | mask_mod_1 = 1 << 3, /* Alt */ 29 | mask_mod_2 = 1 << 4, /* Numlock */ 30 | mask_mod_3 = 1 << 5, 31 | mask_mod_4 = 1 << 6, /* Super */ 32 | mask_mod_5 = 1 << 7, 33 | mask_button_1 = 1 << 8, 34 | mask_button_2 = 1 << 9, 35 | mask_button_3 = 1 << 10, 36 | mask_button_4 = 1 << 11, 37 | mask_button_5 = 1 << 12, 38 | mask_state_mask = 0x1FFF, 39 | mask_mod_mask = 0xFF, 40 | }; 41 | 42 | enum clip_target { 43 | clip_primary, 44 | clip_clipboard, 45 | clip_secondary, 46 | clip_MAX, 47 | clip_invalid = -1, 48 | }; 49 | 50 | enum title_target { 51 | target_title = 1, 52 | target_icon_label = 2, 53 | target_title_icon_label = target_title | target_icon_label, 54 | }; 55 | 56 | enum window_action { 57 | action_none, 58 | action_minimize, 59 | action_restore_minimized, 60 | action_maximize, 61 | action_maximize_width, 62 | action_maximize_height, 63 | action_fullscreen, 64 | action_toggle_fullscreen, 65 | action_restore, 66 | action_lower, 67 | action_raise, 68 | }; 69 | 70 | 71 | struct mouse_event { 72 | enum mouse_event_type event; 73 | enum modifier_mask mask; 74 | int16_t x; 75 | int16_t y; 76 | uint8_t button; 77 | }; 78 | 79 | struct extent { 80 | int16_t width, height; 81 | }; 82 | 83 | enum hide_pointer_mode { 84 | hide_never, 85 | hide_no_tracking, 86 | hide_always, 87 | hide_invalid = -1, 88 | }; 89 | 90 | typedef uint32_t color_t; 91 | 92 | void init_context(struct instance_config *cfg); 93 | void free_context(void); 94 | 95 | struct window *create_window(struct instance_config *cfg); 96 | void free_window(struct window *win); 97 | 98 | bool window_submit_screen(struct window *win, int16_t cur_x, ssize_t cur_y, bool cursor, bool marg, bool cmoved); 99 | void window_shift(struct window *win, int16_t ys, int16_t yd, int16_t height); 100 | void window_paste_clip(struct window *win, enum clip_target target); 101 | void window_delay_redraw(struct window *win); 102 | void window_request_scroll_flush(struct window *win); 103 | bool window_resize(struct window *win, int16_t width, int16_t height); 104 | void window_move(struct window *win, int16_t x, int16_t y); 105 | bool window_action(struct window *win, enum window_action act); 106 | bool window_is_mapped(struct window *win); 107 | void window_bell(struct window *win, uint8_t vol); 108 | void window_reset_delayed_redraw(struct window *win); 109 | 110 | struct extent window_get_position(struct window *win); 111 | struct extent window_get_grid_position(struct window *win); 112 | struct extent window_get_grid_size(struct window *win); 113 | struct extent window_get_screen_size(struct window *win); 114 | struct extent window_get_cell_size(struct window *win); 115 | struct border window_get_border(struct window *win); 116 | struct extent window_get_size(struct window *win); 117 | 118 | void window_set_title(struct window *win, enum title_target which, const char *name, bool utf8); 119 | void window_push_title(struct window *win, enum title_target which); 120 | void window_pop_title(struct window *win, enum title_target which); 121 | /* Both at the same time are not supported */ 122 | void window_get_title(struct window *win, enum title_target which, char **name, bool *utf8); 123 | 124 | void window_set_active_uri(struct window *win, uint32_t uri, bool pressed); 125 | void window_set_mouse(struct window *win, bool enabled); 126 | void window_set_colors(struct window *win, color_t bg, color_t cursor_fg); 127 | void window_get_pointer(struct window *win, int16_t *px, int16_t *py, uint32_t *pmask); 128 | enum cursor_type window_get_cursor(struct window *win); 129 | void window_set_clip(struct window *win, uint8_t *data, enum clip_target target); 130 | void window_set_sync(struct window *win, bool state); 131 | bool window_get_sync(struct window *win); 132 | void window_set_autorepeat(struct window *win, bool state); 133 | bool window_get_autorepeat(struct window *win); 134 | void window_set_alpha(struct window *win, double alpha); 135 | struct instance_config *window_cfg(struct window *win); 136 | void window_set_pointer_mode(struct window *win, enum hide_pointer_mode mode); 137 | void window_set_pointer_shape(struct window *win, const char *name); 138 | 139 | bool init_daemon(void); 140 | void free_daemon(void); 141 | bool daemon_process_clients(void); 142 | 143 | extern struct instance_config global_instance_config; 144 | 145 | void x11_xrender_release_glyph(struct glyph *); 146 | 147 | #endif 148 | --------------------------------------------------------------------------------