diff options
| author | Shipwreckt <me@shipwreckt.co.uk> | 2025-10-22 22:23:00 +0100 |
|---|---|---|
| committer | Shipwreckt <me@shipwreckt.co.uk> | 2025-10-22 22:23:00 +0100 |
| commit | 1ae24861a06d773836fb674814aa03df90bbb095 (patch) | |
| tree | 23c9ae7a23593be5aa1d78b1cb0d9b0cc7a255a1 | |
| parent | e63a16b509b05993fc7900b6296ba8601e343976 (diff) | |
Updated install script, some configs, added vimwiki, added doas def
38 files changed, 1867 insertions, 18775 deletions
@@ -51,11 +51,12 @@ This repository includes configurations for the following programs: - [Fish shell](https://fishshell.com/) - Terminal shell - [Starship](https://starship.rs/) - Terminal bling - [Dunst](https://github.com/dunst-project/dunst) - Notifications -- [ncmpcpp](https://github.com/ncmpcpp/ncmpcpp) - Kool TUI music player +- [doas](https://codeberg.org/thejessesmith/doas/) - Alternative to sudo ## Programs No Longer Used These are programs and tools that I no longer use: +- [ncmpcpp](https://github.com/ncmpcpp/ncmpcpp) - Kool TUI music player - [Alacritty](https://github.com/alacritty/alacritty) - Terminal - [Polybar](https://github.com/polybar/polybar) - Simple Status Bar - [Synth Shell](https://github.com/andresgongora/synth-shell) - Terminal Customization and Git Helper diff --git a/autoinstall.sh b/autoinstall.sh index 22d0fd9..e270f71 100755 --- a/autoinstall.sh +++ b/autoinstall.sh @@ -2,8 +2,10 @@ set -e +DotfilesDir=$(pwd) INSTALL='sudo pacman -S --noconfirm' UPDATE='sudo pacman -Syu --noconfirm' +SucklessDir="$DotfilesDir/files/config/suckless" install_packages() { $UPDATE @@ -54,30 +56,27 @@ setup_home_directory() { copy_config_files() { sudo mkdir -p /usr/share/xsessions - sudo cp ~/Dotfiles/files/dwm.desktop /usr/share/xsessions/ + sudo cp "$DotfilesDir/files/dwm.desktop" /usr/share/xsessions/ - sudo cp -r ~/Dotfiles/files/pacman.conf /etc/pacman.conf - - sudo cp -r ~/Dotfiles/files/config/* ~/.config/ - sudo cp ~/Dotfiles/files/Ly/config.ini /etc/ly/config.ini - - cd ~/Dotfiles/files/config/suckless/dwm/ - sudo make clean install - cd ../slstatus - sudo make clean install - cd ../dmenu - sudo make clean install - cd ../surf - sudo make clean install - cd ../st - sudo make clean install - cd ../scroll - sudo make clean install + sudo cp -r "$DotfilesDir/files/pacman.conf" /etc/pacman.conf + sudo cp -r "$DotfilesDir/files/config/*" ~/.config/ + sudo cp "$DotfilesDir/files/Ly/config.ini" /etc/ly/config.ini + + # Suckless software + for dir in dwm slstatus dmenu surf st scroll; do + if cd "$SucklessDir/$dir"; then + echo "Building $dir..." + sudo make clean install || echo "Build failed in $dir" + else + echo " Directory not found: $SucklessDir/$dir" + fi + done + # Ranger config ranger --copy-config=all rm -rf ~/.config/ranger/* - sudo cp -r ~/Dotfiles/files/ranger/* ~/.config/ranger/ + sudo cp -r "$DotfilesDir/files/ranger/*" ~/.config/ranger/ # Install files for plug manager for NVIM sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \ @@ -89,7 +88,7 @@ copy_config_files() { } fonts(){ - cp -rf ~/Dotfiles/files/fonts ~/.fonts + cp -rf "$DotfilesDir/files/fonts" ~/.fonts } bashrc_additions(){ @@ -98,7 +97,6 @@ bashrc_additions(){ echo 'alias P="cd ~/Programming"' >> ~/.bashrc echo 'alias C="cd ~/.config"' >> ~/.bashrc echo 'alias vim='nvim'' >> ~/.bashrc - echo "printf '\033[?1h\033= >/dev/tty'" >> ~/.bashrc } fish(){ @@ -118,6 +116,22 @@ Security() { sudo sed -i '6 i auth optional pam_faildelay.so delay=4000000' /etc/pam.d/system-login } +Doas() { + $INSTALL opendoas + echo "permit setenv {PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin} :wheel" > /etc/doas.conf + doas pacman -Rdd sudo + doas ln -s $(which doas) /usr/bin/sudo + doas chown -c root:root /etc/doas.conf + doas chmod -c 0400 /etc/doas.conf + + # VIDOAS + # Credit to https://www.cjjackson.dev/posts/replacing-sudo-with-doas-on-arch-linux/ + doas cp "$DotfilesDir/files/doas/vidoas" /root/script/vidoas + doas cp "$DotfilesDir/files/doas/vidoas1" /usr/local/bin/vidoas + + doas chmod 700 /root/script/vidoas + doas chmod 755 /usr/local/bin/vidoas +} main() { install_packages setup_ufw diff --git a/files/config/nvim/init.vim b/files/config/nvim/init.vim index bd66c00..567e5bd 100644 --- a/files/config/nvim/init.vim +++ b/files/config/nvim/init.vim @@ -48,16 +48,25 @@ call plug#begin('~/.config/nvim/plug') " List your plugins here Plug 'junegunn/goyo.vim', { 'for': 'markdown' } -Plug 'jceb/vim-orgmode' +Plug 'vimwiki/vimwiki' + call plug#end() +" Vimwiki settings +let g:vimwiki_list = [{ + \ 'path': '~/Documents/Notes/', + \ 'syntax': 'markdown', + \ 'ext': '.md' + \ }] + + +" Goyo settings function! s:goyo_enter() set noshowmode set noshowcmd set scrolloff=999 set linebreak - " ... endfunction function! s:goyo_leave() diff --git a/files/config/nvim/plug/vimwiki b/files/config/nvim/plug/vimwiki new file mode 160000 +Subproject 72792615e739d0eb54a9c8f7e0a46a6e2407c9e diff --git a/files/config/suckless/dwm/config.h b/files/config/suckless/dwm/config.h index f2f179e..eee2c25 100644 --- a/files/config/suckless/dwm/config.h +++ b/files/config/suckless/dwm/config.h @@ -57,25 +57,29 @@ static const Layout layouts[] = { /* commands */ static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; -static const char *browsercmd[] = { "librewolf", NULL }; -static const char *audiocmd[] = { "pavucontrol", NULL }; static const char *printcmd[] = { "scrot", "-s", NULL }; -static const char *passwdcmd[] = { "keepassxc", NULL }; -static const char *termcmd[] = { "st", NULL }; + +static const char *audiocmd[] = { "pavucontrol", NULL }; static const char *audioup[] = { "pactl", "set-sink-volume", "@DEFAULT_SINK@", "+5%", NULL }; static const char *audiodown[] = { "pactl", "set-sink-volume", "@DEFAULT_SINK@", "-5%", NULL }; -static const char *ncmpcppcmd[] = { "st", "-e", "ncmpcpp", NULL }; + +static const char *youtubecmd[] = { "gtk-pipe-viewer", NULL }; +static const char *browsercmd[] = { "librewolf", NULL }; +static const char *gimpcmd[] = { "gimp", NULL }; +static const char *passwdcmd[] = { "keepassxc", NULL }; +static const char *termcmd[] = { "st", NULL }; static const char *emailcmd[] = { "thunderbird", NULL }; static const Key keys[] = { /* modifier key function argument */ { MODKEY|ShiftMask, XK_Insert, spawn, SHCMD("sh ~/.Scripts/bookmark.sh") }, { MODKEY, XK_Insert, spawn, SHCMD("xdotool type $(grep -v '^#' ~/.bookmarks | dmenu -i -l 50 | cut -d' ' -f1)") }, + { MODKEY, XK_y, spawn, {.v = youtubecmd } }, + { MODKEY, XK_g, spawn, {.v = gimpcmd } }, { MODKEY, XK_t, spawn, {.v = emailcmd } }, { MODKEY, XK_F3, spawn, {.v = audioup } }, { MODKEY, XK_F2, spawn, {.v = audiodown } }, { MODKEY, XK_p, spawn, {.v = audiocmd } }, - { MODKEY, XK_m, spawn, {.v = ncmpcppcmd } }, { 0, XK_Print, spawn, {.v = printcmd } }, { MODKEY|ShiftMask, XK_k, spawn, {.v = passwdcmd } }, { MODKEY, XK_r, spawn, {.v = dmenucmd } }, diff --git a/files/config/suckless/dwm/dwm b/files/config/suckless/dwm/dwm Binary files differindex 9ef9b4f..8c61d6d 100755 --- a/files/config/suckless/dwm/dwm +++ b/files/config/suckless/dwm/dwm diff --git a/files/config/suckless/dwm/dwm.o b/files/config/suckless/dwm/dwm.o Binary files differindex 2d17a7a..a53d670 100644 --- a/files/config/suckless/dwm/dwm.o +++ b/files/config/suckless/dwm/dwm.o diff --git a/files/config/suckless/st/Makefile.orig b/files/config/suckless/st/Makefile.orig index a64b4c2..15db421 100644 --- a/files/config/suckless/st/Makefile.orig +++ b/files/config/suckless/st/Makefile.orig @@ -4,7 +4,7 @@ include config.mk -SRC = st.c x.c boxdraw.c +SRC = st.c x.c OBJ = $(SRC:.c=.o) all: st @@ -17,7 +17,6 @@ config.h: st.o: config.h st.h win.h x.o: arg.h config.h st.h win.h -boxdraw.o: config.h st.h boxdraw_data.h $(OBJ): config.h config.mk diff --git a/files/config/suckless/st/Makefile.rej b/files/config/suckless/st/Makefile.rej deleted file mode 100644 index bb559e5..0000000 --- a/files/config/suckless/st/Makefile.rej +++ /dev/null @@ -1,20 +0,0 @@ ---- Makefile -+++ Makefile -@@ -4,7 +4,7 @@ - - include config.mk - --SRC = st.c x.c -+SRC = st.c x.c rowcolumn_diacritics_helpers.c graphics.c - OBJ = $(SRC:.c=.o) - - all: st -@@ -16,7 +16,7 @@ config.h: - $(CC) $(STCFLAGS) -c $< - - st.o: config.h st.h win.h --x.o: arg.h config.h st.h win.h -+x.o: arg.h config.h st.h win.h graphics.h - - $(OBJ): config.h config.mk - diff --git a/files/config/suckless/st/boxdraw.o b/files/config/suckless/st/boxdraw.o Binary files differnew file mode 100644 index 0000000..4128dba --- /dev/null +++ b/files/config/suckless/st/boxdraw.o diff --git a/files/config/suckless/st/config.def.h b/files/config/suckless/st/config.def.h index e47e401..73f0706 100644 --- a/files/config/suckless/st/config.def.h +++ b/files/config/suckless/st/config.def.h @@ -8,13 +8,6 @@ static char *font = "Hack Nerd Font:pixelsize=10:antialias=true:autohint=true"; static int borderpx = 2; -/* How to align the content in the window when the size of the terminal - * doesn't perfectly match the size of the window. The values are percentages. - * 50 means center, 0 means flush left/top, 100 means flush right/bottom. - */ -static int anysize_halign = 50; -static int anysize_valign = 50; - /* * What program is execed by st depends of these precedence rules: * 1: program passed with -e @@ -30,8 +23,7 @@ char *scroll = NULL; char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; /* identification sequence returned in DA and DECID */ -/* By default, use the same one as kitty. */ -char *vtiden = "\033[?62c"; +char *vtiden = "\033[?6c"; /* Kerning / character bounding-box multipliers */ static float cwscale = 1.0; @@ -184,46 +176,18 @@ static unsigned int mousebg = 0; static unsigned int defaultattr = 11; /* - * Graphics configuration - */ - -/// The template for the cache directory. -const char graphics_cache_dir_template[] = "/tmp/st-images-XXXXXX"; -/// The max size of a single image file, in bytes. -unsigned graphics_max_single_image_file_size = 20 * 1024 * 1024; -/// The max size of the cache, in bytes. -unsigned graphics_total_file_cache_size = 300 * 1024 * 1024; -/// The max ram size of an image or placement, in bytes. -unsigned graphics_max_single_image_ram_size = 100 * 1024 * 1024; -/// The max total size of all images loaded into RAM. -unsigned graphics_max_total_ram_size = 300 * 1024 * 1024; -/// The max total number of image placements and images. -unsigned graphics_max_total_placements = 4096; -/// The ratio by which limits can be exceeded. This is to reduce the frequency -/// of image removal. -double graphics_excess_tolerance_ratio = 0.05; -/// The minimum delay between redraws caused by animations, in milliseconds. -unsigned graphics_animation_min_delay = 20; - -/* * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). * Note that if you want to use ShiftMask with selmasks, set this to an other * modifier, set to 0 to not use it. */ static uint forcemousemod = ShiftMask; -/* Internal keyboard shortcuts. */ -#define MODKEY Mod1Mask -#define TERMMOD (ControlMask|ShiftMask) - /* * Internal mouse shortcuts. * Beware that overloading Button1 will disable the selection. */ static MouseShortcut mshortcuts[] = { /* mask button function argument release */ - { TERMMOD, Button3, previewimage, {.s = "feh"} }, - { TERMMOD, Button2, showimageinfo, {}, 1 }, { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, @@ -231,6 +195,10 @@ static MouseShortcut mshortcuts[] = { { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, }; +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + static Shortcut shortcuts[] = { /* mask keysym function argument */ { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, diff --git a/files/config/suckless/st/config.def.h.orig b/files/config/suckless/st/config.def.h.orig deleted file mode 100644 index 73f0706..0000000 --- a/files/config/suckless/st/config.def.h.orig +++ /dev/null @@ -1,488 +0,0 @@ -/* See LICENSE file for copyright and license details. */ - -/* - * appearance - * - * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html - */ -static char *font = "Hack Nerd Font:pixelsize=10:antialias=true:autohint=true"; -static int borderpx = 2; - -/* - * What program is execed by st depends of these precedence rules: - * 1: program passed with -e - * 2: scroll and/or utmp - * 3: SHELL environment variable - * 4: value of shell in /etc/passwd - * 5: value of shell in config.h - */ -static char *shell = "/bin/sh"; -char *utmp = NULL; -/* scroll program: to enable use a string like "scroll" */ -char *scroll = NULL; -char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; - -/* identification sequence returned in DA and DECID */ -char *vtiden = "\033[?6c"; - -/* Kerning / character bounding-box multipliers */ -static float cwscale = 1.0; -static float chscale = 1.0; - -/* - * word delimiter string - * - * More advanced example: L" `'\"()[]{}" - */ -wchar_t *worddelimiters = L" "; - -/* selection timeouts (in milliseconds) */ -static unsigned int doubleclicktimeout = 300; -static unsigned int tripleclicktimeout = 600; - -/* alt screens */ -int allowaltscreen = 1; - -/* allow certain non-interactive (insecure) window operations such as: - setting the clipboard text */ -int allowwindowops = 0; - -/* - * draw latency range in ms - from new content/keypress/etc until drawing. - * within this range, st draws when content stops arriving (idle). mostly it's - * near minlatency, but it waits longer for slow updates to avoid partial draw. - * low minlatency will tear/flicker more, as it can "detect" idle too early. - */ -static double minlatency = 2; -static double maxlatency = 33; - -/* - * blinking timeout (set to 0 to disable blinking) for the terminal blinking - * attribute. - */ -static unsigned int blinktimeout = 800; - -/* - * thickness of underline and bar cursors - */ -static unsigned int cursorthickness = 2; - -/* - * 1: render most of the lines/blocks characters without using the font for - * perfect alignment between cells (U2500 - U259F except dashes/diagonals). - * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored. - * 0: disable (render all U25XX glyphs normally from the font). - */ -const int boxdraw = 0; -const int boxdraw_bold = 0; - -/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */ -const int boxdraw_braille = 0; - -/* - * bell volume. It must be a value between -100 and 100. Use 0 for disabling - * it - */ -static int bellvolume = 0; - -/* default TERM value */ -char *termname = "st-256color"; - -/* - * spaces per tab - * - * When you are changing this value, don't forget to adapt the »it« value in - * the st.info and appropriately install the st.info in the environment where - * you use this st version. - * - * it#$tabspaces, - * - * Secondly make sure your kernel is not expanding tabs. When running `stty - * -a` »tab0« should appear. You can tell the terminal to not expand tabs by - * running following command: - * - * stty tabs - */ -unsigned int tabspaces = 8; - -/* Terminal colors (16 first used in escape sequence) */ -static const char *colorname[] = { - /* 8 normal colors */ - "black", - "red3", - "green3", - "yellow3", - "blue2", - "magenta3", - "cyan3", - "gray90", - - /* 8 bright colors */ - "gray50", - "red", - "green", - "yellow", - "#5c5cff", - "magenta", - "cyan", - "white", - - [255] = 0, - - /* more colors can be added after 255 to use with DefaultXX */ - "#cccccc", - "#555555", - "gray90", /* default foreground colour */ - "black", /* default background colour */ -}; - - -/* - * Default colors (colorname index) - * foreground, background, cursor, reverse cursor - */ -unsigned int defaultfg = 258; -unsigned int defaultbg = 259; -unsigned int defaultcs = 256; -static unsigned int defaultrcs = 257; - -/* - * Default shape of cursor - * 2: Block ("█") - * 4: Underline ("_") - * 6: Bar ("|") - * 7: Snowman ("☃") - */ -static unsigned int cursorshape = 2; - -/* - * Default columns and rows numbers - */ - -static unsigned int cols = 80; -static unsigned int rows = 24; - -/* - * Default colour and shape of the mouse cursor - */ -static unsigned int mouseshape = XC_xterm; -static unsigned int mousefg = 7; -static unsigned int mousebg = 0; - -/* - * Color used to display font attributes when fontconfig selected a font which - * doesn't match the ones requested. - */ -static unsigned int defaultattr = 11; - -/* - * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). - * Note that if you want to use ShiftMask with selmasks, set this to an other - * modifier, set to 0 to not use it. - */ -static uint forcemousemod = ShiftMask; - -/* - * Internal mouse shortcuts. - * Beware that overloading Button1 will disable the selection. - */ -static MouseShortcut mshortcuts[] = { - /* mask button function argument release */ - { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, - { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, - { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, - { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, - { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, -}; - -/* Internal keyboard shortcuts. */ -#define MODKEY Mod1Mask -#define TERMMOD (ControlMask|ShiftMask) - -static Shortcut shortcuts[] = { - /* mask keysym function argument */ - { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, - { ControlMask, XK_Print, toggleprinter, {.i = 0} }, - { ShiftMask, XK_Print, printscreen, {.i = 0} }, - { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, - { TERMMOD, XK_Prior, zoom, {.f = +1} }, - { TERMMOD, XK_Next, zoom, {.f = -1} }, - { TERMMOD, XK_Home, zoomreset, {.f = 0} }, - { TERMMOD, XK_C, clipcopy, {.i = 0} }, - { TERMMOD, XK_V, clippaste, {.i = 0} }, - { TERMMOD, XK_Y, selpaste, {.i = 0} }, - { ShiftMask, XK_Insert, selpaste, {.i = 0} }, - { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, - { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, - { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, -}; - -/* - * Special keys (change & recompile st.info accordingly) - * - * Mask value: - * * Use XK_ANY_MOD to match the key no matter modifiers state - * * Use XK_NO_MOD to match the key alone (no modifiers) - * appkey value: - * * 0: no value - * * > 0: keypad application mode enabled - * * = 2: term.numlock = 1 - * * < 0: keypad application mode disabled - * appcursor value: - * * 0: no value - * * > 0: cursor application mode enabled - * * < 0: cursor application mode disabled - * - * Be careful with the order of the definitions because st searches in - * this table sequentially, so any XK_ANY_MOD must be in the last - * position for a key. - */ - -/* - * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) - * to be mapped below, add them to this array. - */ -static KeySym mappedkeys[] = { -1 }; - -/* - * State bits to ignore when matching key or button events. By default, - * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. - */ -static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; - -/* - * This is the huge key array which defines all compatibility to the Linux - * world. Please decide about changes wisely. - */ -static Key key[] = { - /* keysym mask string appkey appcursor */ - { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, - { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, - { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, - { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, - { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, - { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, - { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, - { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, - { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, - { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, - { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, - { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, - { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, - { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, - { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, - { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, - { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, - { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, - { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, - { XK_KP_End, ControlMask, "\033[J", -1, 0}, - { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, - { XK_KP_End, ShiftMask, "\033[K", -1, 0}, - { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, - { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, - { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, - { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, - { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, - { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, - { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, - { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, - { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, - { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, - { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, - { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, - { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, - { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, - { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, - { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, - { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, - { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, - { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, - { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, - { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, - { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, - { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, - { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, - { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, - { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, - { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, - { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, - { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, - { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, - { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, - { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, - { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, - { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, - { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, - { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, - { XK_Up, ControlMask, "\033[1;5A", 0, 0}, - { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, - { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, - { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, - { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, - { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, - { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, - { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, - { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, - { XK_Down, ControlMask, "\033[1;5B", 0, 0}, - { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, - { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, - { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, - { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, - { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, - { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, - { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, - { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, - { XK_Left, ControlMask, "\033[1;5D", 0, 0}, - { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, - { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, - { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, - { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, - { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, - { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, - { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, - { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, - { XK_Right, ControlMask, "\033[1;5C", 0, 0}, - { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, - { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, - { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, - { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, - { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, - { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, - { XK_Return, Mod1Mask, "\033\r", 0, 0}, - { XK_Return, XK_ANY_MOD, "\r", 0, 0}, - { XK_Insert, ShiftMask, "\033[4l", -1, 0}, - { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, - { XK_Insert, ControlMask, "\033[L", -1, 0}, - { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, - { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, - { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, - { XK_Delete, ControlMask, "\033[M", -1, 0}, - { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, - { XK_Delete, ShiftMask, "\033[2K", -1, 0}, - { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, - { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, - { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, - { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, - { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, - { XK_Home, ShiftMask, "\033[2J", 0, -1}, - { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, - { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, - { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, - { XK_End, ControlMask, "\033[J", -1, 0}, - { XK_End, ControlMask, "\033[1;5F", +1, 0}, - { XK_End, ShiftMask, "\033[K", -1, 0}, - { XK_End, ShiftMask, "\033[1;2F", +1, 0}, - { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, - { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, - { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, - { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, - { XK_Next, ControlMask, "\033[6;5~", 0, 0}, - { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, - { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, - { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, - { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, - { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, - { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, - { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, - { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, - { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, - { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, - { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, - { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, - { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, - { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, - { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, - { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, - { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, - { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, - { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, - { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, - { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, - { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, - { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, - { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, - { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, - { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, - { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, - { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, - { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, - { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, - { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, - { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, - { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, - { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, - { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, - { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, - { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, - { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, - { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, - { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, - { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, - { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, - { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, - { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, - { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, - { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, - { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, - { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, - { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, - { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, - { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, - { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, - { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, - { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, - { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, - { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, - { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, - { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, - { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, - { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, - { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, - { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, - { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, - { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, - { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, - { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, - { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, - { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, - { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, - { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, - { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, - { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, - { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, - { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, - { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, - { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, - { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, - { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, - { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, - { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, - { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, - { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, - { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, - { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, - { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, - { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, - { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, - { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, -}; - -/* - * Selection types' masks. - * Use the same masks as usual. - * Button1Mask is always unset, to make masks match between ButtonPress. - * ButtonRelease and MotionNotify. - * If no match is found, regular selection is used. - */ -static uint selmasks[] = { - [SEL_RECTANGULAR] = Mod1Mask, -}; - -/* - * Printable characters in ASCII, used to estimate the advance width - * of single wide characters. - */ -static char ascii_printable[] = - " !\"#$%&'()*+,-./0123456789:;<=>?" - "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" - "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/files/config/suckless/st/config.def.h.rej b/files/config/suckless/st/config.def.h.rej deleted file mode 100644 index faaaae1..0000000 --- a/files/config/suckless/st/config.def.h.rej +++ /dev/null @@ -1,13 +0,0 @@ ---- config.def.h -+++ config.def.h -@@ -233,6 +265,10 @@ static Shortcut shortcuts[] = { - { TERMMOD, XK_Y, selpaste, {.i = 0} }, - { ShiftMask, XK_Insert, selpaste, {.i = 0} }, - { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, -+ { TERMMOD, XK_F1, togglegrdebug, {.i = 0} }, -+ { TERMMOD, XK_F6, dumpgrstate, {.i = 0} }, -+ { TERMMOD, XK_F7, unloadimages, {.i = 0} }, -+ { TERMMOD, XK_F8, toggleimages, {.i = 0} }, - }; - - /* diff --git a/files/config/suckless/st/config.mk b/files/config/suckless/st/config.mk index cb2875c..fdc29a7 100644 --- a/files/config/suckless/st/config.mk +++ b/files/config/suckless/st/config.mk @@ -14,12 +14,9 @@ PKG_CONFIG = pkg-config # includes and libs INCS = -I$(X11INC) \ - `$(PKG_CONFIG) --cflags imlib2` \ `$(PKG_CONFIG) --cflags fontconfig` \ `$(PKG_CONFIG) --cflags freetype2` -LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ - `$(PKG_CONFIG) --libs imlib2` \ - `$(PKG_CONFIG) --libs zlib` \ +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ `$(PKG_CONFIG) --libs fontconfig` \ `$(PKG_CONFIG) --libs freetype2` diff --git a/files/config/suckless/st/graphics.c b/files/config/suckless/st/graphics.c deleted file mode 100644 index 64e6fe0..0000000 --- a/files/config/suckless/st/graphics.c +++ /dev/null @@ -1,3812 +0,0 @@ -/* The MIT License - - Copyright (c) 2021-2024 Sergei Grechanik <sergei.grechanik@gmail.com> - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -//////////////////////////////////////////////////////////////////////////////// -// -// This file implements a subset of the kitty graphics protocol. -// -//////////////////////////////////////////////////////////////////////////////// - -#define _POSIX_C_SOURCE 200809L - -#include <zlib.h> -#include <Imlib2.h> -#include <X11/Xlib.h> -#include <X11/extensions/Xrender.h> -#include <assert.h> -#include <ctype.h> -#include <spawn.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <time.h> -#include <unistd.h> -#include <errno.h> - -#include "graphics.h" -#include "khash.h" -#include "kvec.h" - -extern char **environ; - -#define MAX_FILENAME_SIZE 256 -#define MAX_INFO_LEN 256 -#define MAX_IMAGE_RECTS 20 - -/// The type used in this file to represent time. Used both for time differences -/// and absolute times (as milliseconds since an arbitrary point in time, see -/// `initialization_time`). -typedef int64_t Milliseconds; - -enum ScaleMode { - SCALE_MODE_UNSET = 0, - /// Stretch or shrink the image to fill the box, ignoring aspect ratio. - SCALE_MODE_FILL = 1, - /// Preserve aspect ratio and fit to width or to height so that the - /// whole image is visible. - SCALE_MODE_CONTAIN = 2, - /// Do not scale. The image may be cropped if the box is too small. - SCALE_MODE_NONE = 3, - /// Do not scale, unless the box is too small, in which case the image - /// will be shrunk like with `SCALE_MODE_CONTAIN`. - SCALE_MODE_NONE_OR_CONTAIN = 4, -}; - -enum AnimationState { - ANIMATION_STATE_UNSET = 0, - /// The animation is stopped. Display the current frame, but don't - /// advance to the next one. - ANIMATION_STATE_STOPPED = 1, - /// Run the animation to then end, then wait for the next frame. - ANIMATION_STATE_LOADING = 2, - /// Run the animation in a loop. - ANIMATION_STATE_LOOPING = 3, -}; - -/// The status of an image. Each image uploaded to the terminal is cached on -/// disk, then it is loaded to ram when needed. -enum ImageStatus { - STATUS_UNINITIALIZED = 0, - STATUS_UPLOADING = 1, - STATUS_UPLOADING_ERROR = 2, - STATUS_UPLOADING_SUCCESS = 3, - STATUS_RAM_LOADING_ERROR = 4, - STATUS_RAM_LOADING_IN_PROGRESS = 5, - STATUS_RAM_LOADING_SUCCESS = 6, -}; - -const char *image_status_strings[6] = { - "STATUS_UNINITIALIZED", - "STATUS_UPLOADING", - "STATUS_UPLOADING_ERROR", - "STATUS_UPLOADING_SUCCESS", - "STATUS_RAM_LOADING_ERROR", - "STATUS_RAM_LOADING_SUCCESS", -}; - -enum ImageUploadingFailure { - ERROR_OVER_SIZE_LIMIT = 1, - ERROR_CANNOT_OPEN_CACHED_FILE = 2, - ERROR_UNEXPECTED_SIZE = 3, - ERROR_CANNOT_COPY_FILE = 4, -}; - -const char *image_uploading_failure_strings[5] = { - "NO_ERROR", - "ERROR_OVER_SIZE_LIMIT", - "ERROR_CANNOT_OPEN_CACHED_FILE", - "ERROR_UNEXPECTED_SIZE", - "ERROR_CANNOT_COPY_FILE", -}; - -//////////////////////////////////////////////////////////////////////////////// -// -// We use the following structures to represent images and placements: -// -// - Image: this is the main structure representing an image, usually created -// by actions 'a=t', 'a=T`. Each image has an id (image id aka client id, -// specified by 'i='). An image may have multiple frames (ImageFrame) and -// placements (ImagePlacement). -// -// - ImageFrame: represents a single frame of an image, usually created by -// the action 'a=f' (and the first frame is created with the image itself). -// Each frame has an index and also: -// - a file containing the frame data (considered to be "on disk", although -// it's probably in tmpfs), -// - an imlib object containing the fully composed frame (i.e. the frame -// data from the file composed onto the background frame or color). It is -// not ready for display yet, because it needs to be scaled and uploaded -// to the X server. -// -// - ImagePlacement: represents a placement of an image, created by 'a=p' and -// 'a=T'. Each placement has an id (placement id, specified by 'p='). Also -// each placement has an array of pixmaps: one for each frame of the image. -// Each pixmap is a scaled and uploaded image ready to be displayed. -// -// Images are store in the `images` hash table, mapping image ids to Image -// objects (allocated on the heap). -// -// Placements are stored in the `placements` hash table of each Image object, -// mapping placement ids to ImagePlacement objects (also allocated on the heap). -// -// ImageFrames are stored in the `first_frame` field and in the -// `frames_beyond_the_first` array of each Image object. They are stored by -// value, so ImageFrame pointer may be invalidated when frames are -// added/deleted, be careful. -// -//////////////////////////////////////////////////////////////////////////////// - -struct Image; -struct ImageFrame; -struct ImagePlacement; - -KHASH_MAP_INIT_INT(id2image, struct Image *) -KHASH_MAP_INIT_INT(id2placement, struct ImagePlacement *) - -typedef struct ImageFrame { - /// The image this frame belongs to. - struct Image *image; - /// The 1-based index of the frame. Zero if the frame isn't initialized. - int index; - /// The last time when the frame was displayed or otherwise touched. - Milliseconds atime; - /// The background color of the frame in the 0xRRGGBBAA format. - uint32_t background_color; - /// The index of the background frame. Zero to use the color instead. - int background_frame_index; - /// The duration of the frame in milliseconds. - int gap; - /// The expected size of the frame image file (specified with 'S='), - /// used to check if uploading succeeded. - unsigned expected_size; - /// Format specification (see the `f=` key). - int format; - /// Pixel width and height of the non-composed (on-disk) frame data. May - /// differ from the image (i.e. first frame) dimensions. - int data_pix_width, data_pix_height; - /// The offset of the frame relative to the first frame. - int x, y; - /// Compression mode (see the `o=` key). - char compression; - /// The status (see `ImageStatus`). - char status; - /// The reason of uploading failure (see `ImageUploadingFailure`). - char uploading_failure; - /// Whether failures and successes should be reported ('q='). - char quiet; - /// Whether to blend the frame with the background or replace it. - char blend; - /// The file corresponding to the on-disk cache, used when uploading. - FILE *open_file; - /// The size of the corresponding file cached on disk. - unsigned disk_size; - /// The imlib object containing the fully composed frame. It's not - /// scaled for screen display yet. - Imlib_Image imlib_object; -} ImageFrame; - -typedef struct Image { - /// The client id (the one specified with 'i='). Must be nonzero. - uint32_t image_id; - /// The client id specified in the query command (`a=q`). This one must - /// be used to create the response if it's non-zero. - uint32_t query_id; - /// The number specified in the transmission command (`I=`). If - /// non-zero, it may be used to identify the image instead of the - /// image_id, and it also should be mentioned in responses. - uint32_t image_number; - /// The last time when the image was displayed or otherwise touched. - Milliseconds atime; - /// The total duration of the animation in milliseconds. - int total_duration; - /// The total size of cached image files for all frames. - int total_disk_size; - /// The global index of the creation command. Used to decide which image - /// is newer if they have the same image number. - uint64_t global_command_index; - /// The 1-based index of the currently displayed frame. - int current_frame; - /// The state of the animation, see `AnimationState`. - char animation_state; - /// The absolute time that is assumed to be the start of the current - /// frame (in ms since initialization). - Milliseconds current_frame_time; - /// The absolute time of the last redraw (in ms since initialization). - /// Used to check whether it's the first time we draw the image in the - /// current redraw cycle. - Milliseconds last_redraw; - /// The absolute time of the next redraw (in ms since initialization). - /// 0 means no redraw is scheduled. - Milliseconds next_redraw; - /// The unscaled pixel width and height of the image. Usually inherited - /// from the first frame. - int pix_width, pix_height; - /// The first frame. - ImageFrame first_frame; - /// The array of frames beyond the first one. - kvec_t(ImageFrame) frames_beyond_the_first; - /// Image placements. - khash_t(id2placement) *placements; - /// The default placement. - uint32_t default_placement; - /// The initial placement id, specified with the transmission command, - /// used to report success or failure. - uint32_t initial_placement_id; -} Image; - -typedef struct ImagePlacement { - /// The image this placement belongs to. - Image *image; - /// The id of the placement. Must be nonzero. - uint32_t placement_id; - /// The last time when the placement was displayed or otherwise touched. - Milliseconds atime; - /// The 1-based index of the protected pixmap. We protect a pixmap in - /// gr_load_pixmap to avoid unloading it right after it was loaded. - int protected_frame; - /// Whether the placement is used only for Unicode placeholders. - char virtual; - /// The scaling mode (see `ScaleMode`). - char scale_mode; - /// Height and width in cells. - uint16_t rows, cols; - /// Top-left corner of the source rectangle ('x=' and 'y='). - int src_pix_x, src_pix_y; - /// Height and width of the source rectangle (zero if full image). - int src_pix_width, src_pix_height; - /// The image appropriately scaled and uploaded to the X server. This - /// pixmap is premultiplied by alpha. - Pixmap first_pixmap; - /// The array of pixmaps beyond the first one. - kvec_t(Pixmap) pixmaps_beyond_the_first; - /// The dimensions of the cell used to scale the image. If cell - /// dimensions are changed (font change), the image will be rescaled. - uint16_t scaled_cw, scaled_ch; - /// If true, do not move the cursor when displaying this placement - /// (non-virtual placements only). - char do_not_move_cursor; -} ImagePlacement; - -/// A rectangular piece of an image to be drawn. -typedef struct { - uint32_t image_id; - uint32_t placement_id; - /// The position of the rectangle in pixels. - int screen_x_pix, screen_y_pix; - /// The starting row on the screen. - int screen_y_row; - /// The part of the whole image to be drawn, in cells. Starts are - /// zero-based, ends are exclusive. - int img_start_col, img_end_col, img_start_row, img_end_row; - /// The current cell width and height in pixels. - int cw, ch; - /// Whether colors should be inverted. - int reverse; -} ImageRect; - -/// Executes `code` for each frame of an image. Example: -/// -/// foreach_frame(image, frame, { -/// printf("Frame %d\n", frame->index); -/// }); -/// -#define foreach_frame(image, framevar, code) { size_t __i; \ - for (__i = 0; __i <= kv_size((image).frames_beyond_the_first); ++__i) { \ - ImageFrame *framevar = \ - __i == 0 ? &(image).first_frame \ - : &kv_A((image).frames_beyond_the_first, __i - 1); \ - code; \ - } } - -/// Executes `code` for each pixmap of a placement. Example: -/// -/// foreach_pixmap(placement, pixmap, { -/// ... -/// }); -/// -#define foreach_pixmap(placement, pixmapvar, code) { size_t __i; \ - for (__i = 0; __i <= kv_size((placement).pixmaps_beyond_the_first); ++__i) { \ - Pixmap pixmapvar = \ - __i == 0 ? (placement).first_pixmap \ - : kv_A((placement).pixmaps_beyond_the_first, __i - 1); \ - code; \ - } } - - -static Image *gr_find_image(uint32_t image_id); -static void gr_get_frame_filename(ImageFrame *frame, char *out, size_t max_len); -static void gr_delete_image(Image *img); -static void gr_check_limits(); -static char *gr_base64dec(const char *src, size_t *size); -static void sanitize_str(char *str, size_t max_len); -static const char *sanitized_filename(const char *str); - -/// The array of image rectangles to draw. It is reset each frame. -static ImageRect image_rects[MAX_IMAGE_RECTS] = {{0}}; -/// The known images (including the ones being uploaded). -static khash_t(id2image) *images = NULL; -/// The total number of placements in all images. -static unsigned total_placement_count = 0; -/// The total size of all image files stored in the on-disk cache. -static int64_t images_disk_size = 0; -/// The total size of all images and placements loaded into ram. -static int64_t images_ram_size = 0; -/// The id of the last loaded image. -static uint32_t last_image_id = 0; -/// Current cell width and heigh in pixels. -static int current_cw = 0, current_ch = 0; -/// The id of the currently uploaded image (when using direct uploading). -static uint32_t current_upload_image_id = 0; -/// The index of the frame currently being uploaded. -static int current_upload_frame_index = 0; -/// The time when the graphics module was initialized. -static struct timespec initialization_time = {0}; -/// The time when the current frame drawing started, used for debugging fps and -/// to calculate the current frame for animations. -static Milliseconds drawing_start_time; -/// The global index of the current command. -static uint64_t global_command_counter = 0; -/// The next redraw times for each row of the terminal. Used for animations. -/// 0 means no redraw is scheduled. -static kvec_t(Milliseconds) next_redraw_times = {0, 0, NULL}; -/// The number of files loaded in the current redraw cycle. -static int this_redraw_cycle_loaded_files = 0; -/// The number of pixmaps loaded in the current redraw cycle. -static int this_redraw_cycle_loaded_pixmaps = 0; - -/// The directory where the cache files are stored. -static char cache_dir[MAX_FILENAME_SIZE - 16]; - -/// The table used for color inversion. -static unsigned char reverse_table[256]; - -// Declared in the header. -GraphicsDebugMode graphics_debug_mode = GRAPHICS_DEBUG_NONE; -char graphics_display_images = 1; -GraphicsCommandResult graphics_command_result = {0}; -int graphics_next_redraw_delay = INT_MAX; - -// Defined in config.h -extern const char graphics_cache_dir_template[]; -extern unsigned graphics_max_single_image_file_size; -extern unsigned graphics_total_file_cache_size; -extern unsigned graphics_max_single_image_ram_size; -extern unsigned graphics_max_total_ram_size; -extern unsigned graphics_max_total_placements; -extern double graphics_excess_tolerance_ratio; -extern unsigned graphics_animation_min_delay; - - -//////////////////////////////////////////////////////////////////////////////// -// Basic helpers. -//////////////////////////////////////////////////////////////////////////////// - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) < (b) ? (b) : (a)) - -/// Returns the difference between `end` and `start` in milliseconds. -static int64_t gr_timediff_ms(const struct timespec *end, - const struct timespec *start) { - return (end->tv_sec - start->tv_sec) * 1000 + - (end->tv_nsec - start->tv_nsec) / 1000000; -} - -/// Returns the current time in milliseconds since the initialization. -static Milliseconds gr_now_ms() { - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - return gr_timediff_ms(&now, &initialization_time); -} - -//////////////////////////////////////////////////////////////////////////////// -// Logging. -//////////////////////////////////////////////////////////////////////////////// - -#define GR_LOG(...) \ - do { if(graphics_debug_mode) fprintf(stderr, __VA_ARGS__); } while(0) - -//////////////////////////////////////////////////////////////////////////////// -// Basic image management functions (create, delete, find, etc). -//////////////////////////////////////////////////////////////////////////////// - -/// Returns the 1-based index of the last frame. Note that you may want to use -/// `gr_last_uploaded_frame_index` instead since the last frame may be not -/// fully uploaded yet. -static inline int gr_last_frame_index(Image *img) { - return kv_size(img->frames_beyond_the_first) + 1; -} - -/// Returns the frame with the given index. Returns NULL if the index is out of -/// bounds. The index is 1-based. -static ImageFrame *gr_get_frame(Image *img, int index) { - if (!img) - return NULL; - if (index == 1) - return &img->first_frame; - if (2 <= index && index <= gr_last_frame_index(img)) - return &kv_A(img->frames_beyond_the_first, index - 2); - return NULL; -} - -/// Returns the last frame of the image. Returns NULL if `img` is NULL. -static ImageFrame *gr_get_last_frame(Image *img) { - if (!img) - return NULL; - return gr_get_frame(img, gr_last_frame_index(img)); -} - -/// Returns the 1-based index of the last frame or the second-to-last frame if -/// the last frame is not fully uploaded yet. -static inline int gr_last_uploaded_frame_index(Image *img) { - int last_index = gr_last_frame_index(img); - if (last_index > 1 && - gr_get_frame(img, last_index)->status < STATUS_UPLOADING_SUCCESS) - return last_index - 1; - return last_index; -} - -/// Returns the pixmap for the frame with the given index. Returns 0 if the -/// index is out of bounds. The index is 1-based. -static Pixmap gr_get_frame_pixmap(ImagePlacement *placement, int index) { - if (index == 1) - return placement->first_pixmap; - if (2 <= index && - index <= kv_size(placement->pixmaps_beyond_the_first) + 1) - return kv_A(placement->pixmaps_beyond_the_first, index - 2); - return 0; -} - -/// Sets the pixmap for the frame with the given index. The index is 1-based. -/// The array of pixmaps is resized if needed. -static void gr_set_frame_pixmap(ImagePlacement *placement, int index, - Pixmap pixmap) { - if (index == 1) { - placement->first_pixmap = pixmap; - return; - } - // Resize the array if needed. - size_t old_size = kv_size(placement->pixmaps_beyond_the_first); - if (old_size < index - 1) { - kv_a(Pixmap, placement->pixmaps_beyond_the_first, index - 2); - for (size_t i = old_size; i < index - 1; i++) - kv_A(placement->pixmaps_beyond_the_first, i) = 0; - } - kv_A(placement->pixmaps_beyond_the_first, index - 2) = pixmap; -} - -/// Finds the image corresponding to the client id. Returns NULL if cannot find. -static Image *gr_find_image(uint32_t image_id) { - khiter_t k = kh_get(id2image, images, image_id); - if (k == kh_end(images)) - return NULL; - Image *res = kh_value(images, k); - return res; -} - -/// Finds the newest image corresponding to the image number. Returns NULL if -/// cannot find. -static Image *gr_find_image_by_number(uint32_t image_number) { - if (image_number == 0) - return NULL; - Image *newest_img = NULL; - Image *img = NULL; - kh_foreach_value(images, img, { - if (img->image_number == image_number && - (!newest_img || newest_img->global_command_index < - img->global_command_index)) - newest_img = img; - }); - if (!newest_img) - GR_LOG("Image number %u not found\n", image_number); - else - GR_LOG("Found image number %u, its id is %u\n", image_number, - img->image_id); - return newest_img; -} - -/// Finds the placement corresponding to the id. If the placement id is 0, -/// returns some default placement. -static ImagePlacement *gr_find_placement(Image *img, uint32_t placement_id) { - if (!img) - return NULL; - if (placement_id == 0) { - // Try to get the default placement. - ImagePlacement *dflt = NULL; - if (img->default_placement != 0) - dflt = gr_find_placement(img, img->default_placement); - if (dflt) - return dflt; - // If there is no default placement, return the first one and - // set it as the default. - kh_foreach_value(img->placements, dflt, { - img->default_placement = dflt->placement_id; - return dflt; - }); - // If there are no placements, return NULL. - return NULL; - } - khiter_t k = kh_get(id2placement, img->placements, placement_id); - if (k == kh_end(img->placements)) - return NULL; - ImagePlacement *res = kh_value(img->placements, k); - return res; -} - -/// Finds the placement by image id and placement id. -static ImagePlacement *gr_find_image_and_placement(uint32_t image_id, - uint32_t placement_id) { - return gr_find_placement(gr_find_image(image_id), placement_id); -} - -/// Writes the name of the on-disk cache file to `out`. `max_len` should be the -/// size of `out`. The name will be something like -/// "/tmp/st-images-xxx/img-ID-FRAME". -static void gr_get_frame_filename(ImageFrame *frame, char *out, - size_t max_len) { - snprintf(out, max_len, "%s/img-%.3u-%.3u", cache_dir, - frame->image->image_id, frame->index); -} - -/// Returns the (estimation) of the RAM size used by the frame right now. -static unsigned gr_frame_current_ram_size(ImageFrame *frame) { - if (!frame->imlib_object) - return 0; - return (unsigned)frame->image->pix_width * frame->image->pix_height * 4; -} - -/// Returns the (estimation) of the RAM size used by a single frame pixmap. -static unsigned gr_placement_single_frame_ram_size(ImagePlacement *placement) { - return (unsigned)placement->rows * placement->cols * - placement->scaled_ch * placement->scaled_cw * 4; -} - -/// Returns the (estimation) of the RAM size used by the placemenet right now. -static unsigned gr_placement_current_ram_size(ImagePlacement *placement) { - unsigned single_frame_size = - gr_placement_single_frame_ram_size(placement); - unsigned result = 0; - foreach_pixmap(*placement, pixmap, { - if (pixmap) - result += single_frame_size; - }); - return result; -} - -/// Unload the frame from RAM (i.e. delete the corresponding imlib object). -/// If the on-disk file of the frame is preserved, it can be reloaded later. -static void gr_unload_frame(ImageFrame *frame) { - if (!frame->imlib_object) - return; - - unsigned frame_ram_size = gr_frame_current_ram_size(frame); - images_ram_size -= frame_ram_size; - - imlib_context_set_image(frame->imlib_object); - imlib_free_image_and_decache(); - frame->imlib_object = NULL; - - GR_LOG("After unloading image %u frame %u (atime %ld ms ago) " - "ram: %ld KiB (- %u KiB)\n", - frame->image->image_id, frame->index, - drawing_start_time - frame->atime, images_ram_size / 1024, - frame_ram_size / 1024); -} - -/// Unload all frames of the image. -static void gr_unload_all_frames(Image *img) { - foreach_frame(*img, frame, { - gr_unload_frame(frame); - }); -} - -/// Unload the placement from RAM (i.e. free all of the corresponding pixmaps). -/// If the on-disk files or imlib objects of the corresponding image are -/// preserved, the placement can be reloaded later. -static void gr_unload_placement(ImagePlacement *placement) { - unsigned placement_ram_size = gr_placement_current_ram_size(placement); - images_ram_size -= placement_ram_size; - - Display *disp = imlib_context_get_display(); - foreach_pixmap(*placement, pixmap, { - if (pixmap) - XFreePixmap(disp, pixmap); - }); - - placement->first_pixmap = 0; - placement->pixmaps_beyond_the_first.n = 0; - placement->scaled_ch = placement->scaled_cw = 0; - - GR_LOG("After unloading placement %u/%u (atime %ld ms ago) " - "ram: %ld KiB (- %u KiB)\n", - placement->image->image_id, placement->placement_id, - drawing_start_time - placement->atime, images_ram_size / 1024, - placement_ram_size / 1024); -} - -/// Unload a single pixmap of the placement from RAM. -static void gr_unload_pixmap(ImagePlacement *placement, int frameidx) { - Pixmap pixmap = gr_get_frame_pixmap(placement, frameidx); - if (!pixmap) - return; - - Display *disp = imlib_context_get_display(); - XFreePixmap(disp, pixmap); - gr_set_frame_pixmap(placement, frameidx, 0); - images_ram_size -= gr_placement_single_frame_ram_size(placement); - - GR_LOG("After unloading pixmap %ld of " - "placement %u/%u (atime %ld ms ago) " - "frame %u (atime %ld ms ago) " - "ram: %ld KiB (- %u KiB)\n", - pixmap, placement->image->image_id, placement->placement_id, - drawing_start_time - placement->atime, frameidx, - drawing_start_time - - gr_get_frame(placement->image, frameidx)->atime, - images_ram_size / 1024, - gr_placement_single_frame_ram_size(placement) / 1024); -} - -/// Deletes the on-disk cache file corresponding to the frame. The in-ram image -/// object (if it exists) is not deleted, placements are not unloaded either. -static void gr_delete_imagefile(ImageFrame *frame) { - // It may still be being loaded. Close the file in this case. - if (frame->open_file) { - fclose(frame->open_file); - frame->open_file = NULL; - } - - if (frame->disk_size == 0) - return; - - char filename[MAX_FILENAME_SIZE]; - gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); - remove(filename); - - unsigned disk_size = frame->disk_size; - images_disk_size -= disk_size; - frame->image->total_disk_size -= disk_size; - frame->disk_size = 0; - - GR_LOG("After deleting image file %u frame %u (atime %ld ms ago) " - "disk: %ld KiB (- %u KiB)\n", - frame->image->image_id, frame->index, - drawing_start_time - frame->atime, images_disk_size / 1024, - disk_size / 1024); -} - -/// Deletes all on-disk cache files of the image (for each frame). -static void gr_delete_imagefiles(Image *img) { - foreach_frame(*img, frame, { - gr_delete_imagefile(frame); - }); -} - -/// Deletes the given placement: unloads, frees the object, but doesn't change -/// the `placements` hash table. -static void gr_delete_placement_keep_id(ImagePlacement *placement) { - if (!placement) - return; - GR_LOG("Deleting placement %u/%u\n", placement->image->image_id, - placement->placement_id); - gr_unload_placement(placement); - kv_destroy(placement->pixmaps_beyond_the_first); - free(placement); - total_placement_count--; -} - -/// Deletes all placements of `img`. -static void gr_delete_all_placements(Image *img) { - ImagePlacement *placement = NULL; - kh_foreach_value(img->placements, placement, { - gr_delete_placement_keep_id(placement); - }); - kh_clear(id2placement, img->placements); -} - -/// Deletes the given image: unloads, deletes the file, frees the Image object, -/// but doesn't change the `images` hash table. -static void gr_delete_image_keep_id(Image *img) { - if (!img) - return; - GR_LOG("Deleting image %u\n", img->image_id); - foreach_frame(*img, frame, { - gr_delete_imagefile(frame); - gr_unload_frame(frame); - }); - kv_destroy(img->frames_beyond_the_first); - gr_delete_all_placements(img); - kh_destroy(id2placement, img->placements); - free(img); -} - -/// Deletes the given image: unloads, deletes the file, frees the Image object, -/// and also removes it from `images`. -static void gr_delete_image(Image *img) { - if (!img) - return; - uint32_t id = img->image_id; - gr_delete_image_keep_id(img); - khiter_t k = kh_get(id2image, images, id); - kh_del(id2image, images, k); -} - -/// Deletes the given placement: unloads, frees the object, and also removes it -/// from `placements`. -static void gr_delete_placement(ImagePlacement *placement) { - if (!placement) - return; - uint32_t id = placement->placement_id; - Image *img = placement->image; - gr_delete_placement_keep_id(placement); - khiter_t k = kh_get(id2placement, img->placements, id); - kh_del(id2placement, img->placements, k); -} - -/// Deletes all images and clears `images`. -static void gr_delete_all_images() { - Image *img = NULL; - kh_foreach_value(images, img, { - gr_delete_image_keep_id(img); - }); - kh_clear(id2image, images); -} - -/// Update the atime of the image. -static void gr_touch_image(Image *img) { - img->atime = gr_now_ms(); -} - -/// Update the atime of the frame. -static void gr_touch_frame(ImageFrame *frame) { - frame->image->atime = frame->atime = gr_now_ms(); -} - -/// Update the atime of the placement. Touches the images too. -static void gr_touch_placement(ImagePlacement *placement) { - placement->image->atime = placement->atime = gr_now_ms(); -} - -/// Creates a new image with the given id. If an image with that id already -/// exists, it is deleted first. If the provided id is 0, generates a -/// random id. -static Image *gr_new_image(uint32_t id) { - if (id == 0) { - do { - id = rand(); - // Avoid IDs that don't need full 32 bits. - } while ((id & 0xFF000000) == 0 || (id & 0x00FFFF00) == 0 || - gr_find_image(id)); - GR_LOG("Generated random image id %u\n", id); - } - Image *img = gr_find_image(id); - gr_delete_image_keep_id(img); - GR_LOG("Creating image %u\n", id); - img = malloc(sizeof(Image)); - memset(img, 0, sizeof(Image)); - img->placements = kh_init(id2placement); - int ret; - khiter_t k = kh_put(id2image, images, id, &ret); - kh_value(images, k) = img; - img->image_id = id; - gr_touch_image(img); - img->global_command_index = global_command_counter; - return img; -} - -/// Creates a new frame at the end of the frame array. It may be the first frame -/// if there are no frames yet. -static ImageFrame *gr_append_new_frame(Image *img) { - ImageFrame *frame = NULL; - if (img->first_frame.index == 0 && - kv_size(img->frames_beyond_the_first) == 0) { - frame = &img->first_frame; - frame->index = 1; - } else { - frame = kv_pushp(ImageFrame, img->frames_beyond_the_first); - memset(frame, 0, sizeof(ImageFrame)); - frame->index = kv_size(img->frames_beyond_the_first) + 1; - } - frame->image = img; - gr_touch_frame(frame); - GR_LOG("Appending frame %d to image %u\n", frame->index, img->image_id); - return frame; -} - -/// Creates a new placement with the given id. If a placement with that id -/// already exists, it is deleted first. If the provided id is 0, generates a -/// random id. -static ImagePlacement *gr_new_placement(Image *img, uint32_t id) { - if (id == 0) { - do { - // Currently we support only 24-bit IDs. - id = rand() & 0xFFFFFF; - // Avoid IDs that need only one byte. - } while ((id & 0x00FFFF00) == 0 || gr_find_placement(img, id)); - } - ImagePlacement *placement = gr_find_placement(img, id); - gr_delete_placement_keep_id(placement); - GR_LOG("Creating placement %u/%u\n", img->image_id, id); - placement = malloc(sizeof(ImagePlacement)); - memset(placement, 0, sizeof(ImagePlacement)); - total_placement_count++; - int ret; - khiter_t k = kh_put(id2placement, img->placements, id, &ret); - kh_value(img->placements, k) = placement; - placement->image = img; - placement->placement_id = id; - gr_touch_placement(placement); - if (img->default_placement == 0) - img->default_placement = id; - return placement; -} - -static int64_t ceil_div(int64_t a, int64_t b) { - return (a + b - 1) / b; -} - -/// Computes the best number of rows and columns for a placement if it's not -/// specified, and also adjusts the source rectangle size. -static void gr_infer_placement_size_maybe(ImagePlacement *placement) { - // The size of the image. - int image_pix_width = placement->image->pix_width; - int image_pix_height = placement->image->pix_height; - // Negative values are not allowed. Quietly set them to 0. - if (placement->src_pix_x < 0) - placement->src_pix_x = 0; - if (placement->src_pix_y < 0) - placement->src_pix_y = 0; - if (placement->src_pix_width < 0) - placement->src_pix_width = 0; - if (placement->src_pix_height < 0) - placement->src_pix_height = 0; - // If the source rectangle is outside the image, truncate it. - if (placement->src_pix_x > image_pix_width) - placement->src_pix_x = image_pix_width; - if (placement->src_pix_y > image_pix_height) - placement->src_pix_y = image_pix_height; - // If the source rectangle is not specified, use the whole image. If - // it's partially outside the image, truncate it. - if (placement->src_pix_width == 0 || - placement->src_pix_x + placement->src_pix_width > image_pix_width) - placement->src_pix_width = - image_pix_width - placement->src_pix_x; - if (placement->src_pix_height == 0 || - placement->src_pix_y + placement->src_pix_height > image_pix_height) - placement->src_pix_height = - image_pix_height - placement->src_pix_y; - - if (placement->cols != 0 && placement->rows != 0) - return; - if (placement->src_pix_width == 0 || placement->src_pix_height == 0) - return; - if (current_cw == 0 || current_ch == 0) - return; - - // If no size is specified, use the image size. - if (placement->cols == 0 && placement->rows == 0) { - placement->cols = - ceil_div(placement->src_pix_width, current_cw); - placement->rows = - ceil_div(placement->src_pix_height, current_ch); - return; - } - - // Some applications specify only one of the dimensions. - if (placement->scale_mode == SCALE_MODE_CONTAIN) { - // If we preserve aspect ratio and fit to width/height, the most - // logical thing is to find the minimum size of the - // non-specified dimension that allows the image to fit the - // specified dimension. - if (placement->cols == 0) { - placement->cols = ceil_div( - placement->src_pix_width * placement->rows * - current_ch, - placement->src_pix_height * current_cw); - return; - } - if (placement->rows == 0) { - placement->rows = - ceil_div(placement->src_pix_height * - placement->cols * current_cw, - placement->src_pix_width * current_ch); - return; - } - } else { - // Otherwise we stretch the image or preserve the original size. - // In both cases we compute the best number of columns from the - // pixel size and cell size. - // TODO: In the case of stretching it's not the most logical - // thing to do, may need to revisit in the future. - // Currently we switch to SCALE_MODE_CONTAIN when only one - // of the dimensions is specified, so this case shouldn't - // happen in practice. - if (!placement->cols) - placement->cols = - ceil_div(placement->src_pix_width, current_cw); - if (!placement->rows) - placement->rows = - ceil_div(placement->src_pix_height, current_ch); - } -} - -/// Adjusts the current frame index if enough time has passed since the display -/// of the current frame. Also computes the time of the next redraw of this -/// image (`img->next_redraw`). The current time is passed as an argument so -/// that all animations are in sync. -static void gr_update_frame_index(Image *img, Milliseconds now) { - if (img->current_frame == 0) { - img->current_frame_time = now; - img->current_frame = 1; - img->next_redraw = now + MAX(1, img->first_frame.gap); - return; - } - // If the animation is stopped, show the current frame. - if (!img->animation_state || - img->animation_state == ANIMATION_STATE_STOPPED || - img->animation_state == ANIMATION_STATE_UNSET) { - // The next redraw is never (unless the state is changed). - img->next_redraw = 0; - return; - } - int last_uploaded_frame_index = gr_last_uploaded_frame_index(img); - // If we are loading and we reached the last frame, show the last frame. - if (img->animation_state == ANIMATION_STATE_LOADING && - img->current_frame == last_uploaded_frame_index) { - // The next redraw is never (unless the state is changed or - // frames are added). - img->next_redraw = 0; - return; - } - - // Check how many milliseconds passed since the current frame was shown. - int passed_ms = now - img->current_frame_time; - // If the animation is looping and too much time has passes, we can - // make a shortcut. - if (img->animation_state == ANIMATION_STATE_LOOPING && - img->total_duration > 0 && passed_ms >= img->total_duration) { - passed_ms %= img->total_duration; - img->current_frame_time = now - passed_ms; - } - // Find the next frame. - int original_frame_index = img->current_frame; - while (1) { - ImageFrame *frame = gr_get_frame(img, img->current_frame); - if (!frame) { - // The frame doesn't exist, go to the first frame. - img->current_frame = 1; - img->current_frame_time = now; - img->next_redraw = now + MAX(1, img->first_frame.gap); - return; - } - if (frame->gap >= 0 && passed_ms < frame->gap) { - // Not enough time has passed, we are still in the same - // frame, and it's not a gapless frame. - img->next_redraw = - img->current_frame_time + MAX(1, frame->gap); - return; - } - // Otherwise go to the next frame. - passed_ms -= MAX(0, frame->gap); - if (img->current_frame >= last_uploaded_frame_index) { - // It's the last frame, if the animation is loading, - // remain on it. - if (img->animation_state == ANIMATION_STATE_LOADING) { - img->next_redraw = 0; - return; - } - // Otherwise the animation is looping. - img->current_frame = 1; - // TODO: Support finite number of loops. - } else { - img->current_frame++; - } - // Make sure we don't get stuck in an infinite loop. - if (img->current_frame == original_frame_index) { - // We looped through all frames, but haven't reached the - // next frame yet. This may happen if too much time has - // passed since the last redraw or all the frames are - // gapless. Just move on to the next frame. - img->current_frame++; - if (img->current_frame > - last_uploaded_frame_index) - img->current_frame = 1; - img->current_frame_time = now; - img->next_redraw = now + MAX( - 1, gr_get_frame(img, img->current_frame)->gap); - return; - } - // Adjust the start time of the frame. The next redraw time will - // be set in the next iteration. - img->current_frame_time += MAX(0, frame->gap); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Unloading and deleting images to save resources. -//////////////////////////////////////////////////////////////////////////////// - -/// A helper to compare frames by atime for qsort. -static int gr_cmp_frames_by_atime(const void *a, const void *b) { - ImageFrame *frame_a = *(ImageFrame *const *)a; - ImageFrame *frame_b = *(ImageFrame *const *)b; - if (frame_a->atime == frame_b->atime) - return frame_a->image->global_command_index - - frame_b->image->global_command_index; - return frame_a->atime - frame_b->atime; -} - -/// A helper to compare images by atime for qsort. -static int gr_cmp_images_by_atime(const void *a, const void *b) { - Image *img_a = *(Image *const *)a; - Image *img_b = *(Image *const *)b; - if (img_a->atime == img_b->atime) - return img_a->global_command_index - - img_b->global_command_index; - return img_a->atime - img_b->atime; -} - -/// A helper to compare placements by atime for qsort. -static int gr_cmp_placements_by_atime(const void *a, const void *b) { - ImagePlacement *p_a = *(ImagePlacement **)a; - ImagePlacement *p_b = *(ImagePlacement **)b; - if (p_a->atime == p_b->atime) - return p_a->image->global_command_index - - p_b->image->global_command_index; - return p_a->atime - p_b->atime; -} - -typedef kvec_t(Image *) ImageVec; -typedef kvec_t(ImagePlacement *) ImagePlacementVec; -typedef kvec_t(ImageFrame *) ImageFrameVec; - -/// Returns an array of pointers to all images sorted by atime. -static ImageVec gr_get_images_sorted_by_atime() { - ImageVec vec; - kv_init(vec); - if (kh_size(images) == 0) - return vec; - kv_resize(Image *, vec, kh_size(images)); - Image *img = NULL; - kh_foreach_value(images, img, { kv_push(Image *, vec, img); }); - qsort(vec.a, kv_size(vec), sizeof(Image *), gr_cmp_images_by_atime); - return vec; -} - -/// Returns an array of pointers to all placements sorted by atime. -static ImagePlacementVec gr_get_placements_sorted_by_atime() { - ImagePlacementVec vec; - kv_init(vec); - if (total_placement_count == 0) - return vec; - kv_resize(ImagePlacement *, vec, total_placement_count); - Image *img = NULL; - ImagePlacement *placement = NULL; - kh_foreach_value(images, img, { - kh_foreach_value(img->placements, placement, { - kv_push(ImagePlacement *, vec, placement); - }); - }); - qsort(vec.a, kv_size(vec), sizeof(ImagePlacement *), - gr_cmp_placements_by_atime); - return vec; -} - -/// Returns an array of pointers to all frames sorted by atime. -static ImageFrameVec gr_get_frames_sorted_by_atime() { - ImageFrameVec frames; - kv_init(frames); - Image *img = NULL; - kh_foreach_value(images, img, { - foreach_frame(*img, frame, { - kv_push(ImageFrame *, frames, frame); - }); - }); - qsort(frames.a, kv_size(frames), sizeof(ImageFrame *), - gr_cmp_frames_by_atime); - return frames; -} - -/// An object that can be unloaded from RAM. -typedef struct { - /// Some score, probably based on access time. The lower the score, the - /// more likely that the object should be unloaded. - int64_t score; - union { - ImagePlacement *placement; - ImageFrame *frame; - }; - /// If zero, the object is the imlib object of `frame`, if non-zero, - /// the object is a pixmap of `frameidx`-th frame of `placement`. - int frameidx; -} UnloadableObject; - -typedef kvec_t(UnloadableObject) UnloadableObjectVec; - -/// A helper to compare unloadable objects by score for qsort. -static int gr_cmp_unloadable_objects(const void *a, const void *b) { - UnloadableObject *obj_a = (UnloadableObject *)a; - UnloadableObject *obj_b = (UnloadableObject *)b; - return obj_a->score - obj_b->score; -} - -/// Unloads an unloadable object from RAM. -static void gr_unload_object(UnloadableObject *obj) { - if (obj->frameidx) { - if (obj->placement->protected_frame == obj->frameidx) - return; - gr_unload_pixmap(obj->placement, obj->frameidx); - } else { - gr_unload_frame(obj->frame); - } -} - -/// Returns the recency threshold for an image. Frames that were accessed within -/// this threshold from now are considered recent and may be handled -/// differently because we may need them again very soon. -static Milliseconds gr_recency_threshold(Image *img) { - return img->total_duration * 2 + 1000; -} - -/// Creates an unloadable object for the imlib object of a frame. -static UnloadableObject gr_unloadable_object_for_frame(Milliseconds now, - ImageFrame *frame) { - UnloadableObject obj = {0}; - obj.frameidx = 0; - obj.frame = frame; - Milliseconds atime = frame->atime; - obj.score = atime; - if (atime >= now - gr_recency_threshold(frame->image)) { - // This is a recent frame, probably from an active animation. - // Score it above `now` to prefer unloading non-active frames. - // Randomize the score because it's not very clear in which - // order we want to unload them: reloading a frame may require - // reloading other frames. - obj.score = now + 1000 + rand() % 1000; - } - return obj; -} - -/// Creates an unloadable object for a pixmap. -static UnloadableObject -gr_unloadable_object_for_pixmap(Milliseconds now, ImageFrame *frame, - ImagePlacement *placement) { - UnloadableObject obj = {0}; - obj.frameidx = frame->index; - obj.placement = placement; - obj.score = placement->atime; - // Since we don't store pixmap atimes, use the - // oldest atime of the frame and the placement. - Milliseconds atime = MIN(placement->atime, frame->atime); - obj.score = atime; - if (atime >= now - gr_recency_threshold(frame->image)) { - // This is a recent pixmap, probably from an active animation. - // Score it above `now` to prefer unloading non-active frames. - // Also assign higher scores to frames that are closer to the - // current frame (more likely to be used soon). - int num_frames = gr_last_frame_index(frame->image); - int dist = frame->index - frame->image->current_frame; - if (dist < 0) - dist += num_frames; - obj.score = - now + 1000 + (num_frames - dist) * 1000 / num_frames; - // If the pixmap is much larger than the imlib image, prefer to - // unload the pixmap by adding up to -1000 to the score. If the - // imlib image is larger, add up to +1000. - float imlib_size = gr_frame_current_ram_size(frame); - float pixmap_size = - gr_placement_single_frame_ram_size(placement); - obj.score += - 2000 * (imlib_size / (imlib_size + pixmap_size) - 0.5); - } - return obj; -} - -/// Returns an array of unloadable objects sorted by score. -static UnloadableObjectVec -gr_get_unloadable_objects_sorted_by_score(Milliseconds now) { - UnloadableObjectVec objects; - kv_init(objects); - Image *img = NULL; - ImagePlacement *placement = NULL; - kh_foreach_value(images, img, { - foreach_frame(*img, frame, { - if (!frame->imlib_object) - continue; - kv_push(UnloadableObject, objects, - gr_unloadable_object_for_frame(now, frame)); - int frameidx = frame->index; - kh_foreach_value(img->placements, placement, { - if (!gr_get_frame_pixmap(placement, frameidx)) - continue; - kv_push(UnloadableObject, objects, - gr_unloadable_object_for_pixmap( - now, frame, placement)); - }); - }); - }); - qsort(objects.a, kv_size(objects), sizeof(UnloadableObject), - gr_cmp_unloadable_objects); - return objects; -} - -/// Returns the limit adjusted by the excess tolerance ratio. -static inline unsigned apply_tolerance(unsigned limit) { - return limit + (unsigned)(limit * graphics_excess_tolerance_ratio); -} - -/// Checks RAM and disk cache limits and deletes/unloads some images. -static void gr_check_limits() { - Milliseconds now = gr_now_ms(); - ImageVec images_sorted = {0}; - ImagePlacementVec placements_sorted = {0}; - ImageFrameVec frames_sorted = {0}; - UnloadableObjectVec objects_sorted = {0}; - int images_begin = 0; - int placements_begin = 0; - char changed = 0; - // First reduce the number of images if there are too many. - if (kh_size(images) > apply_tolerance(graphics_max_total_placements)) { - GR_LOG("Too many images: %d\n", kh_size(images)); - changed = 1; - images_sorted = gr_get_images_sorted_by_atime(); - int to_delete = kv_size(images_sorted) - - graphics_max_total_placements; - for (; images_begin < to_delete; images_begin++) - gr_delete_image(images_sorted.a[images_begin]); - } - // Then reduce the number of placements if there are too many. - if (total_placement_count > - apply_tolerance(graphics_max_total_placements)) { - GR_LOG("Too many placements: %d\n", total_placement_count); - changed = 1; - placements_sorted = gr_get_placements_sorted_by_atime(); - int to_delete = kv_size(placements_sorted) - - graphics_max_total_placements; - for (; placements_begin < to_delete; placements_begin++) { - ImagePlacement *placement = - placements_sorted.a[placements_begin]; - if (placement->protected_frame) - break; - gr_delete_placement(placement); - } - } - // Then reduce the size of the image file cache. The files correspond to - // image frames. - if (images_disk_size > - apply_tolerance(graphics_total_file_cache_size)) { - GR_LOG("Too big disk cache: %ld KiB\n", - images_disk_size / 1024); - changed = 1; - frames_sorted = gr_get_frames_sorted_by_atime(); - for (int i = 0; i < kv_size(frames_sorted); i++) { - if (images_disk_size <= graphics_total_file_cache_size) - break; - gr_delete_imagefile(kv_A(frames_sorted, i)); - } - } - // Then unload images from RAM. - if (images_ram_size > apply_tolerance(graphics_max_total_ram_size)) { - changed = 1; - int frames_begin = 0; - GR_LOG("Too much ram: %ld KiB\n", images_ram_size / 1024); - objects_sorted = gr_get_unloadable_objects_sorted_by_score(now); - for (int i = 0; i < kv_size(objects_sorted); i++) { - if (images_ram_size <= graphics_max_total_ram_size) - break; - gr_unload_object(&kv_A(objects_sorted, i)); - } - } - if (changed) { - GR_LOG("After cleaning: ram: %ld KiB disk: %ld KiB " - "img count: %d placement count: %d\n", - images_ram_size / 1024, images_disk_size / 1024, - kh_size(images), total_placement_count); - } - kv_destroy(images_sorted); - kv_destroy(placements_sorted); - kv_destroy(frames_sorted); - kv_destroy(objects_sorted); -} - -/// Unloads all images by user request. -void gr_unload_images_to_reduce_ram() { - Image *img = NULL; - ImagePlacement *placement = NULL; - kh_foreach_value(images, img, { - kh_foreach_value(img->placements, placement, { - if (placement->protected_frame) - continue; - gr_unload_placement(placement); - }); - gr_unload_all_frames(img); - }); -} - -//////////////////////////////////////////////////////////////////////////////// -// Image loading. -//////////////////////////////////////////////////////////////////////////////// - -/// Copies `num_pixels` pixels (not bytes!) from a buffer `from` to an imlib2 -/// image data `to`. The format may be 24 (RGB) or 32 (RGBA), and it's converted -/// to imlib2's representation, which is 0xAARRGGBB (having BGRA memory layout -/// on little-endian architectures). -static inline void gr_copy_pixels(DATA32 *to, unsigned char *from, int format, - size_t num_pixels) { - size_t pixel_size = format == 24 ? 3 : 4; - if (format == 32) { - for (unsigned i = 0; i < num_pixels; ++i) { - unsigned byte_i = i * pixel_size; - to[i] = ((DATA32)from[byte_i + 2]) | - ((DATA32)from[byte_i + 1]) << 8 | - ((DATA32)from[byte_i]) << 16 | - ((DATA32)from[byte_i + 3]) << 24; - } - } else { - for (unsigned i = 0; i < num_pixels; ++i) { - unsigned byte_i = i * pixel_size; - to[i] = ((DATA32)from[byte_i + 2]) | - ((DATA32)from[byte_i + 1]) << 8 | - ((DATA32)from[byte_i]) << 16 | 0xFF000000; - } - } -} - -/// Loads uncompressed RGB or RGBA image data from a file. -static void gr_load_raw_pixel_data_uncompressed(DATA32 *data, FILE *file, - int format, - size_t total_pixels) { - unsigned char chunk[BUFSIZ]; - size_t pixel_size = format == 24 ? 3 : 4; - size_t chunk_size_pix = BUFSIZ / 4; - size_t chunk_size_bytes = chunk_size_pix * pixel_size; - size_t bytes = total_pixels * pixel_size; - for (size_t chunk_start_pix = 0; chunk_start_pix < total_pixels; - chunk_start_pix += chunk_size_pix) { - size_t read_size = fread(chunk, 1, chunk_size_bytes, file); - size_t read_pixels = read_size / pixel_size; - if (chunk_start_pix + read_pixels > total_pixels) - read_pixels = total_pixels - chunk_start_pix; - gr_copy_pixels(data + chunk_start_pix, chunk, format, - read_pixels); - } -} - -#define COMPRESSED_CHUNK_SIZE BUFSIZ -#define DECOMPRESSED_CHUNK_SIZE (BUFSIZ * 4) - -/// Loads compressed RGB or RGBA image data from a file. -static int gr_load_raw_pixel_data_compressed(DATA32 *data, FILE *file, - int format, size_t total_pixels) { - size_t pixel_size = format == 24 ? 3 : 4; - unsigned char compressed_chunk[COMPRESSED_CHUNK_SIZE]; - unsigned char decompressed_chunk[DECOMPRESSED_CHUNK_SIZE]; - - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.next_out = decompressed_chunk; - strm.avail_out = DECOMPRESSED_CHUNK_SIZE; - strm.avail_in = 0; - strm.next_in = Z_NULL; - int ret = inflateInit(&strm); - if (ret != Z_OK) - return 1; - - int error = 0; - int progress = 0; - size_t total_copied_pixels = 0; - while (1) { - // If we don't have enough data in the input buffer, try to read - // from the file. - if (strm.avail_in <= COMPRESSED_CHUNK_SIZE / 4) { - // Move the existing data to the beginning. - memmove(compressed_chunk, strm.next_in, strm.avail_in); - strm.next_in = compressed_chunk; - // Read more data. - size_t bytes_read = fread( - compressed_chunk + strm.avail_in, 1, - COMPRESSED_CHUNK_SIZE - strm.avail_in, file); - strm.avail_in += bytes_read; - if (bytes_read != 0) - progress = 1; - } - - // Try to inflate the data. - int ret = inflate(&strm, Z_SYNC_FLUSH); - if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR) { - error = 1; - fprintf(stderr, - "error: could not decompress the image, error " - "%s\n", - ret == Z_MEM_ERROR ? "Z_MEM_ERROR" - : "Z_DATA_ERROR"); - break; - } - - // Copy the data from the output buffer to the image. - size_t full_pixels = - (DECOMPRESSED_CHUNK_SIZE - strm.avail_out) / pixel_size; - // Make sure we don't overflow the image. - if (full_pixels > total_pixels - total_copied_pixels) - full_pixels = total_pixels - total_copied_pixels; - if (full_pixels > 0) { - // Copy pixels. - gr_copy_pixels(data, decompressed_chunk, format, - full_pixels); - data += full_pixels; - total_copied_pixels += full_pixels; - if (total_copied_pixels >= total_pixels) { - // We filled the whole image, there may be some - // data left, but we just truncate it. - break; - } - // Move the remaining data to the beginning. - size_t copied_bytes = full_pixels * pixel_size; - size_t leftover = - (DECOMPRESSED_CHUNK_SIZE - strm.avail_out) - - copied_bytes; - memmove(decompressed_chunk, - decompressed_chunk + copied_bytes, leftover); - strm.next_out -= copied_bytes; - strm.avail_out += copied_bytes; - progress = 1; - } - - // If we haven't made any progress, then we have reached the end - // of both the file and the inflated data. - if (!progress) - break; - progress = 0; - } - - inflateEnd(&strm); - return error; -} - -#undef COMPRESSED_CHUNK_SIZE -#undef DECOMPRESSED_CHUNK_SIZE - -/// Load the image from a file containing raw pixel data (RGB or RGBA), the data -/// may be compressed. -static Imlib_Image gr_load_raw_pixel_data(ImageFrame *frame, - const char *filename) { - size_t total_pixels = frame->data_pix_width * frame->data_pix_height; - if (total_pixels * 4 > graphics_max_single_image_ram_size) { - fprintf(stderr, - "error: image %u frame %u is too big too load: %zu > %u\n", - frame->image->image_id, frame->index, total_pixels * 4, - graphics_max_single_image_ram_size); - return NULL; - } - - FILE* file = fopen(filename, "rb"); - if (!file) { - fprintf(stderr, - "error: could not open image file: %s\n", - sanitized_filename(filename)); - return NULL; - } - - Imlib_Image image = imlib_create_image(frame->data_pix_width, - frame->data_pix_height); - if (!image) { - fprintf(stderr, - "error: could not create an image of size %d x %d\n", - frame->data_pix_width, frame->data_pix_height); - fclose(file); - return NULL; - } - - imlib_context_set_image(image); - imlib_image_set_has_alpha(1); - DATA32* data = imlib_image_get_data(); - - // The default format is 32. - int format = frame->format ? frame->format : 32; - - if (frame->compression == 0) { - gr_load_raw_pixel_data_uncompressed(data, file, format, - total_pixels); - } else { - int ret = gr_load_raw_pixel_data_compressed(data, file, format, - total_pixels); - if (ret != 0) { - imlib_image_put_back_data(data); - imlib_free_image(); - fclose(file); - return NULL; - } - } - - fclose(file); - imlib_image_put_back_data(data); - return image; -} - -/// Loads the unscaled frame into RAM as an imlib object. The frame imlib object -/// is fully composed on top of the background frame. If the frame is already -/// loaded, does nothing. Loading may fail, in which case the status of the -/// frame will be set to STATUS_RAM_LOADING_ERROR. -static void gr_load_imlib_object(ImageFrame *frame) { - if (frame->imlib_object) - return; - - // If the image is uninitialized or uploading has failed, or the file - // has been deleted, we cannot load the image. - if (frame->status < STATUS_UPLOADING_SUCCESS) - return; - if (frame->disk_size == 0) { - if (frame->status != STATUS_RAM_LOADING_ERROR) { - fprintf(stderr, - "error: cached image was deleted: %u frame %u\n", - frame->image->image_id, frame->index); - } - frame->status = STATUS_RAM_LOADING_ERROR; - return; - } - - // Prevent recursive dependences between frames. - if (frame->status == STATUS_RAM_LOADING_IN_PROGRESS) { - fprintf(stderr, - "error: recursive loading of image %u frame %u\n", - frame->image->image_id, frame->index); - frame->status = STATUS_RAM_LOADING_ERROR; - return; - } - frame->status = STATUS_RAM_LOADING_IN_PROGRESS; - - // Load the background frame if needed. Hopefully it's not recursive. - ImageFrame *bg_frame = NULL; - if (frame->background_frame_index) { - bg_frame = gr_get_frame(frame->image, - frame->background_frame_index); - if (!bg_frame) { - fprintf(stderr, - "error: could not find background " - "frame %d for image %u frame %d\n", - frame->background_frame_index, - frame->image->image_id, frame->index); - frame->status = STATUS_RAM_LOADING_ERROR; - return; - } - gr_load_imlib_object(bg_frame); - if (!bg_frame->imlib_object) { - fprintf(stderr, - "error: could not load background frame %d for " - "image %u frame %d\n", - frame->background_frame_index, - frame->image->image_id, frame->index); - frame->status = STATUS_RAM_LOADING_ERROR; - return; - } - } - - // Load the frame data image. - Imlib_Image frame_data_image = NULL; - char filename[MAX_FILENAME_SIZE]; - gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); - GR_LOG("Loading image: %s\n", sanitized_filename(filename)); - if (frame->format == 100 || frame->format == 0) - frame_data_image = imlib_load_image(filename); - if (frame->format == 32 || frame->format == 24 || - (!frame_data_image && frame->format == 0)) - frame_data_image = gr_load_raw_pixel_data(frame, filename); - this_redraw_cycle_loaded_files++; - - if (!frame_data_image) { - if (frame->status != STATUS_RAM_LOADING_ERROR) { - fprintf(stderr, "error: could not load image: %s\n", - sanitized_filename(filename)); - } - frame->status = STATUS_RAM_LOADING_ERROR; - return; - } - - imlib_context_set_image(frame_data_image); - int frame_data_width = imlib_image_get_width(); - int frame_data_height = imlib_image_get_height(); - GR_LOG("Successfully loaded, size %d x %d\n", frame_data_width, - frame_data_height); - // If imlib loading succeeded, and it is the first frame, set the - // information about the original image size, unless it's already set. - if (frame->index == 1 && frame->image->pix_width == 0 && - frame->image->pix_height == 0) { - frame->image->pix_width = frame_data_width; - frame->image->pix_height = frame_data_height; - } - - int image_width = frame->image->pix_width; - int image_height = frame->image->pix_height; - - // Compose the image with the background color or frame. - if (frame->background_color != 0 || bg_frame || - image_width != frame_data_width || - image_height != frame_data_height) { - GR_LOG("Composing the frame bg = 0x%08X, bgframe = %d\n", - frame->background_color, frame->background_frame_index); - Imlib_Image composed_image = imlib_create_image( - image_width, image_height); - imlib_context_set_image(composed_image); - imlib_image_set_has_alpha(1); - imlib_context_set_anti_alias(0); - - // Start with the background frame or color. - imlib_context_set_blend(0); - if (bg_frame && bg_frame->imlib_object) { - imlib_blend_image_onto_image( - bg_frame->imlib_object, 1, 0, 0, - image_width, image_height, 0, 0, - image_width, image_height); - } else { - int r = (frame->background_color >> 24) & 0xFF; - int g = (frame->background_color >> 16) & 0xFF; - int b = (frame->background_color >> 8) & 0xFF; - int a = frame->background_color & 0xFF; - imlib_context_set_color(r, g, b, a); - imlib_image_fill_rectangle(0, 0, image_width, - image_height); - } - - // Blend the frame data image onto the background. - imlib_context_set_blend(1); - imlib_blend_image_onto_image( - frame_data_image, 1, 0, 0, frame->data_pix_width, - frame->data_pix_height, frame->x, frame->y, - frame->data_pix_width, frame->data_pix_height); - - // Free the frame data image. - imlib_context_set_image(frame_data_image); - imlib_free_image(); - - frame_data_image = composed_image; - } - - frame->imlib_object = frame_data_image; - - images_ram_size += gr_frame_current_ram_size(frame); - frame->status = STATUS_RAM_LOADING_SUCCESS; - - GR_LOG("After loading image %u frame %d ram: %ld KiB (+ %u KiB)\n", - frame->image->image_id, frame->index, - images_ram_size / 1024, gr_frame_current_ram_size(frame) / 1024); -} - -/// Premultiplies the alpha channel of the image data. The data is an array of -/// pixels such that each pixel is a 32-bit integer in the format 0xAARRGGBB. -static void gr_premultiply_alpha(DATA32 *data, size_t num_pixels) { - for (size_t i = 0; i < num_pixels; ++i) { - DATA32 pixel = data[i]; - unsigned char a = pixel >> 24; - if (a == 0) { - data[i] = 0; - } else if (a != 255) { - unsigned char b = (pixel & 0xFF) * a / 255; - unsigned char g = ((pixel >> 8) & 0xFF) * a / 255; - unsigned char r = ((pixel >> 16) & 0xFF) * a / 255; - data[i] = (a << 24) | (r << 16) | (g << 8) | b; - } - } -} - -/// Creates a pixmap for the frame of an image placement. The pixmap contain the -/// image data correctly scaled and fit to the box defined by the number of -/// rows/columns of the image placement and the provided cell dimensions in -/// pixels. If the placement is already loaded, it will be reloaded only if the -/// cell dimensions have changed. -Pixmap gr_load_pixmap(ImagePlacement *placement, int frameidx, int cw, int ch) { - Image *img = placement->image; - ImageFrame *frame = gr_get_frame(img, frameidx); - - // Update the atime uncoditionally. - gr_touch_placement(placement); - if (frame) - gr_touch_frame(frame); - - // If cw or ch are different, unload all the pixmaps. - if (placement->scaled_cw != cw || placement->scaled_ch != ch) { - gr_unload_placement(placement); - placement->scaled_cw = cw; - placement->scaled_ch = ch; - } - - // If it's already loaded, do nothing. - Pixmap pixmap = gr_get_frame_pixmap(placement, frameidx); - if (pixmap) - return pixmap; - - GR_LOG("Loading placement: %u/%u frame %u\n", img->image_id, - placement->placement_id, frameidx); - - // Load the imlib object for the frame. - if (!frame) { - fprintf(stderr, - "error: could not find frame %u for image %u\n", - frameidx, img->image_id); - return 0; - } - gr_load_imlib_object(frame); - if (!frame->imlib_object) - return 0; - - // Infer the placement size if needed. - gr_infer_placement_size_maybe(placement); - - // Create the scaled image. This is temporary, we will scale it - // appropriately, upload to the X server, and then delete immediately. - int scaled_w = (int)placement->cols * cw; - int scaled_h = (int)placement->rows * ch; - if (scaled_w * scaled_h * 4 > graphics_max_single_image_ram_size) { - fprintf(stderr, - "error: placement %u/%u would be too big to load: %d x " - "%d x 4 > %u\n", - img->image_id, placement->placement_id, scaled_w, - scaled_h, graphics_max_single_image_ram_size); - return 0; - } - Imlib_Image scaled_image = imlib_create_image(scaled_w, scaled_h); - if (!scaled_image) { - fprintf(stderr, - "error: imlib_create_image(%d, %d) returned " - "null\n", - scaled_w, scaled_h); - return 0; - } - imlib_context_set_image(scaled_image); - imlib_image_set_has_alpha(1); - - // First fill the scaled image with the transparent color. - imlib_context_set_blend(0); - imlib_context_set_color(0, 0, 0, 0); - imlib_image_fill_rectangle(0, 0, scaled_w, scaled_h); - imlib_context_set_anti_alias(1); - imlib_context_set_blend(1); - - // The source rectangle. - int src_x = placement->src_pix_x; - int src_y = placement->src_pix_y; - int src_w = placement->src_pix_width; - int src_h = placement->src_pix_height; - // Whether the box is too small to use the true size of the image. - char box_too_small = scaled_w < src_w || scaled_h < src_h; - char mode = placement->scale_mode; - - // Then blend the original image onto the transparent background. - if (src_w <= 0 || src_h <= 0) { - fprintf(stderr, "warning: image of zero size\n"); - } else if (mode == SCALE_MODE_FILL) { - imlib_blend_image_onto_image(frame->imlib_object, 1, src_x, - src_y, src_w, src_h, 0, 0, - scaled_w, scaled_h); - } else if (mode == SCALE_MODE_NONE || - (mode == SCALE_MODE_NONE_OR_CONTAIN && !box_too_small)) { - imlib_blend_image_onto_image(frame->imlib_object, 1, src_x, - src_y, src_w, src_h, 0, 0, src_w, - src_h); - } else { - if (mode != SCALE_MODE_CONTAIN && - mode != SCALE_MODE_NONE_OR_CONTAIN) { - fprintf(stderr, - "warning: unknown scale mode %u, using " - "'contain' instead\n", - mode); - } - int dest_x, dest_y; - int dest_w, dest_h; - if (scaled_w * src_h > src_w * scaled_h) { - // If the box is wider than the original image, fit to - // height. - dest_h = scaled_h; - dest_y = 0; - dest_w = src_w * scaled_h / src_h; - dest_x = (scaled_w - dest_w) / 2; - } else { - // Otherwise, fit to width. - dest_w = scaled_w; - dest_x = 0; - dest_h = src_h * scaled_w / src_w; - dest_y = (scaled_h - dest_h) / 2; - } - imlib_blend_image_onto_image(frame->imlib_object, 1, src_x, - src_y, src_w, src_h, dest_x, - dest_y, dest_w, dest_h); - } - - // XRender needs the alpha channel premultiplied. - DATA32 *data = imlib_image_get_data(); - gr_premultiply_alpha(data, scaled_w * scaled_h); - - // Upload the image to the X server. - Display *disp = imlib_context_get_display(); - Visual *vis = imlib_context_get_visual(); - Colormap cmap = imlib_context_get_colormap(); - Drawable drawable = imlib_context_get_drawable(); - if (!drawable) - drawable = DefaultRootWindow(disp); - pixmap = XCreatePixmap(disp, drawable, scaled_w, scaled_h, 32); - XVisualInfo visinfo; - XMatchVisualInfo(disp, DefaultScreen(disp), 32, TrueColor, &visinfo); - XImage *ximage = XCreateImage(disp, visinfo.visual, 32, ZPixmap, 0, - (char *)data, scaled_w, scaled_h, 32, 0); - GC gc = XCreateGC(disp, pixmap, 0, NULL); - XPutImage(disp, pixmap, gc, ximage, 0, 0, 0, 0, scaled_w, - scaled_h); - XFreeGC(disp, gc); - // XDestroyImage will free the data as well, but it is managed by imlib, - // so set it to NULL. - ximage->data = NULL; - XDestroyImage(ximage); - imlib_image_put_back_data(data); - imlib_free_image(); - - // Assign the pixmap to the frame and increase the ram size. - gr_set_frame_pixmap(placement, frameidx, pixmap); - images_ram_size += gr_placement_single_frame_ram_size(placement); - this_redraw_cycle_loaded_pixmaps++; - - GR_LOG("After loading placement %u/%u frame %d ram: %ld KiB (+ %u " - "KiB)\n", - frame->image->image_id, placement->placement_id, frame->index, - images_ram_size / 1024, - gr_placement_single_frame_ram_size(placement) / 1024); - - // Free up ram if needed, but keep the pixmap we've loaded no matter - // what. - placement->protected_frame = frameidx; - gr_check_limits(); - placement->protected_frame = 0; - - return pixmap; -} - -//////////////////////////////////////////////////////////////////////////////// -// Initialization and deinitialization. -//////////////////////////////////////////////////////////////////////////////// - -/// Creates a temporary directory. -static int gr_create_cache_dir() { - strncpy(cache_dir, graphics_cache_dir_template, sizeof(cache_dir)); - if (!mkdtemp(cache_dir)) { - fprintf(stderr, - "error: could not create temporary dir from template " - "%s\n", - sanitized_filename(cache_dir)); - return 0; - } - fprintf(stderr, "Graphics cache directory: %s\n", cache_dir); - return 1; -} - -/// Checks whether `tmp_dir` exists and recreates it if it doesn't. -static void gr_make_sure_tmpdir_exists() { - struct stat st; - if (stat(cache_dir, &st) == 0 && S_ISDIR(st.st_mode)) - return; - fprintf(stderr, - "error: %s is not a directory, will need to create a new " - "graphics cache directory\n", - sanitized_filename(cache_dir)); - gr_create_cache_dir(); -} - -/// Initialize the graphics module. -void gr_init(Display *disp, Visual *vis, Colormap cm) { - // Set the initialization time. - clock_gettime(CLOCK_MONOTONIC, &initialization_time); - - // Create the temporary dir. - if (!gr_create_cache_dir()) - abort(); - - // Initialize imlib. - imlib_context_set_display(disp); - imlib_context_set_visual(vis); - imlib_context_set_colormap(cm); - imlib_context_set_anti_alias(1); - imlib_context_set_blend(1); - // Imlib2 checks only the file name when caching, which is not enough - // for us since we reuse file names. Disable caching. - imlib_set_cache_size(0); - - // Prepare for color inversion. - for (size_t i = 0; i < 256; ++i) - reverse_table[i] = 255 - i; - - // Create data structures. - images = kh_init(id2image); - kv_init(next_redraw_times); - - atexit(gr_deinit); -} - -/// Deinitialize the graphics module. -void gr_deinit() { - // Remove the cache dir. - remove(cache_dir); - kv_destroy(next_redraw_times); - if (images) { - // Delete all images. - gr_delete_all_images(); - // Destroy the data structures. - kh_destroy(id2image, images); - images = NULL; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Dumping, debugging, and image preview. -//////////////////////////////////////////////////////////////////////////////// - -/// Returns a string containing a time difference in a human-readable format. -/// Uses a static buffer, so be careful. -static const char *gr_ago(Milliseconds diff) { - static char result[32]; - double seconds = (double)diff / 1000.0; - if (seconds < 1) - snprintf(result, sizeof(result), "%.2f sec ago", seconds); - else if (seconds < 60) - snprintf(result, sizeof(result), "%d sec ago", (int)seconds); - else if (seconds < 3600) - snprintf(result, sizeof(result), "%d min %d sec ago", - (int)(seconds / 60), (int)(seconds) % 60); - else { - snprintf(result, sizeof(result), "%d hr %d min %d sec ago", - (int)(seconds / 3600), (int)(seconds) % 3600 / 60, - (int)(seconds) % 60); - } - return result; -} - -/// Prints to `file` with an indentation of `ind` spaces. -static void fprintf_ind(FILE *file, int ind, const char *format, ...) { - fprintf(file, "%*s", ind, ""); - va_list args; - va_start(args, format); - vfprintf(file, format, args); - va_end(args); -} - -/// Dumps the image info to `file` with an indentation of `ind` spaces. -static void gr_dump_image_info(FILE *file, Image *img, int ind) { - if (!img) { - fprintf_ind(file, ind, "Image is NULL\n"); - return; - } - Milliseconds now = gr_now_ms(); - fprintf_ind(file, ind, "Image %u\n", img->image_id); - ind += 4; - fprintf_ind(file, ind, "number: %u\n", img->image_number); - fprintf_ind(file, ind, "global command index: %lu\n", - img->global_command_index); - fprintf_ind(file, ind, "accessed: %ld %s\n", img->atime, - gr_ago(now - img->atime)); - fprintf_ind(file, ind, "pix size: %ux%u\n", img->pix_width, - img->pix_height); - fprintf_ind(file, ind, "cur frame start time: %ld %s\n", - img->current_frame_time, - gr_ago(now - img->current_frame_time)); - if (img->next_redraw) - fprintf_ind(file, ind, "next redraw: %ld in %ld ms\n", - img->next_redraw, img->next_redraw - now); - fprintf_ind(file, ind, "total disk size: %u KiB\n", - img->total_disk_size / 1024); - fprintf_ind(file, ind, "total duration: %d\n", img->total_duration); - fprintf_ind(file, ind, "frames: %d\n", gr_last_frame_index(img)); - fprintf_ind(file, ind, "cur frame: %d\n", img->current_frame); - fprintf_ind(file, ind, "animation state: %d\n", img->animation_state); - fprintf_ind(file, ind, "default_placement: %u\n", - img->default_placement); -} - -/// Dumps the frame info to `file` with an indentation of `ind` spaces. -static void gr_dump_frame_info(FILE *file, ImageFrame *frame, int ind) { - if (!frame) { - fprintf_ind(file, ind, "Frame is NULL\n"); - return; - } - Milliseconds now = gr_now_ms(); - fprintf_ind(file, ind, "Frame %d\n", frame->index); - ind += 4; - if (frame->index == 0) { - fprintf_ind(file, ind, "NOT INITIALIZED\n"); - return; - } - if (frame->uploading_failure) - fprintf_ind(file, ind, "uploading failure: %s\n", - image_uploading_failure_strings - [frame->uploading_failure]); - fprintf_ind(file, ind, "gap: %d\n", frame->gap); - fprintf_ind(file, ind, "accessed: %ld %s\n", frame->atime, - gr_ago(now - frame->atime)); - fprintf_ind(file, ind, "data pix size: %ux%u\n", frame->data_pix_width, - frame->data_pix_height); - char filename[MAX_FILENAME_SIZE]; - gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); - if (access(filename, F_OK) != -1) - fprintf_ind(file, ind, "file: %s\n", - sanitized_filename(filename)); - else - fprintf_ind(file, ind, "not on disk\n"); - fprintf_ind(file, ind, "disk size: %u KiB\n", frame->disk_size / 1024); - if (frame->imlib_object) { - unsigned ram_size = gr_frame_current_ram_size(frame); - fprintf_ind(file, ind, - "loaded into ram, size: %d " - "KiB\n", - ram_size / 1024); - } else { - fprintf_ind(file, ind, "not loaded into ram\n"); - } -} - -/// Dumps the placement info to `file` with an indentation of `ind` spaces. -static void gr_dump_placement_info(FILE *file, ImagePlacement *placement, - int ind) { - if (!placement) { - fprintf_ind(file, ind, "Placement is NULL\n"); - return; - } - Milliseconds now = gr_now_ms(); - fprintf_ind(file, ind, "Placement %u\n", placement->placement_id); - ind += 4; - fprintf_ind(file, ind, "accessed: %ld %s\n", placement->atime, - gr_ago(now - placement->atime)); - fprintf_ind(file, ind, "scale_mode: %u\n", placement->scale_mode); - fprintf_ind(file, ind, "size: %u cols x %u rows\n", placement->cols, - placement->rows); - fprintf_ind(file, ind, "cell size: %ux%u\n", placement->scaled_cw, - placement->scaled_ch); - fprintf_ind(file, ind, "ram per frame: %u KiB\n", - gr_placement_single_frame_ram_size(placement) / 1024); - unsigned ram_size = gr_placement_current_ram_size(placement); - fprintf_ind(file, ind, "ram size: %d KiB\n", ram_size / 1024); -} - -/// Dumps placement pixmaps to `file` with an indentation of `ind` spaces. -static void gr_dump_placement_pixmaps(FILE *file, ImagePlacement *placement, - int ind) { - if (!placement) - return; - int frameidx = 1; - foreach_pixmap(*placement, pixmap, { - fprintf_ind(file, ind, "Frame %d pixmap %lu\n", frameidx, - pixmap); - ++frameidx; - }); -} - -/// Dumps the internal state (images and placements) to stderr. -void gr_dump_state() { - FILE *file = stderr; - int ind = 0; - fprintf_ind(file, ind, "======= Graphics module state dump =======\n"); - fprintf_ind(file, ind, - "sizeof(Image) = %lu sizeof(ImageFrame) = %lu " - "sizeof(ImagePlacement) = %lu\n", - sizeof(Image), sizeof(ImageFrame), sizeof(ImagePlacement)); - fprintf_ind(file, ind, "Image count: %u\n", kh_size(images)); - fprintf_ind(file, ind, "Placement count: %u\n", total_placement_count); - fprintf_ind(file, ind, "Estimated RAM usage: %ld KiB\n", - images_ram_size / 1024); - fprintf_ind(file, ind, "Estimated Disk usage: %ld KiB\n", - images_disk_size / 1024); - - Milliseconds now = gr_now_ms(); - - int64_t images_ram_size_computed = 0; - int64_t images_disk_size_computed = 0; - - Image *img = NULL; - ImagePlacement *placement = NULL; - kh_foreach_value(images, img, { - fprintf_ind(file, ind, "----------------\n"); - gr_dump_image_info(file, img, 0); - int64_t total_disk_size_computed = 0; - int total_duration_computed = 0; - foreach_frame(*img, frame, { - gr_dump_frame_info(file, frame, 4); - if (frame->image != img) - fprintf_ind(file, 8, - "ERROR: WRONG IMAGE POINTER\n"); - total_duration_computed += frame->gap; - images_disk_size_computed += frame->disk_size; - total_disk_size_computed += frame->disk_size; - if (frame->imlib_object) - images_ram_size_computed += - gr_frame_current_ram_size(frame); - }); - if (img->total_disk_size != total_disk_size_computed) { - fprintf_ind(file, ind, - " ERROR: total_disk_size is %u, but " - "computed value is %ld\n", - img->total_disk_size, total_disk_size_computed); - } - if (img->total_duration != total_duration_computed) { - fprintf_ind(file, ind, - " ERROR: total_duration is %d, but computed " - "value is %d\n", - img->total_duration, total_duration_computed); - } - kh_foreach_value(img->placements, placement, { - gr_dump_placement_info(file, placement, 4); - if (placement->image != img) - fprintf_ind(file, 8, - "ERROR: WRONG IMAGE POINTER\n"); - fprintf_ind(file, 8, - "Pixmaps:\n"); - gr_dump_placement_pixmaps(file, placement, 12); - unsigned ram_size = - gr_placement_current_ram_size(placement); - images_ram_size_computed += ram_size; - }); - }); - if (images_ram_size != images_ram_size_computed) { - fprintf_ind(file, ind, - "ERROR: images_ram_size is %ld, but computed value " - "is %ld\n", - images_ram_size, images_ram_size_computed); - } - if (images_disk_size != images_disk_size_computed) { - fprintf_ind(file, ind, - "ERROR: images_disk_size is %ld, but computed value " - "is %ld\n", - images_disk_size, images_disk_size_computed); - } - fprintf_ind(file, ind, "===========================================\n"); -} - -/// Executes `command` with the name of the file corresponding to `image_id` as -/// the argument. Executes xmessage with an error message on failure. -// TODO: Currently we do this for the first frame only. Not sure what to do with -// animations. -void gr_preview_image(uint32_t image_id, const char *exec) { - char command[256]; - size_t len; - Image *img = gr_find_image(image_id); - if (img) { - ImageFrame *frame = &img->first_frame; - char filename[MAX_FILENAME_SIZE]; - gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); - if (frame->disk_size == 0) { - len = snprintf(command, 255, - "xmessage 'Image with id=%u is not " - "fully copied to %s'", - image_id, sanitized_filename(filename)); - } else { - len = snprintf(command, 255, "%s %s &", exec, - sanitized_filename(filename)); - } - } else { - len = snprintf(command, 255, - "xmessage 'Cannot find image with id=%u'", - image_id); - } - if (len > 255) { - fprintf(stderr, "error: command too long: %s\n", command); - snprintf(command, 255, "xmessage 'error: command too long'"); - } - if (system(command) != 0) { - fprintf(stderr, "error: could not execute command %s\n", - command); - } -} - -/// Executes `<st> -e less <file>` where <file> is the name of a temporary file -/// containing the information about an image and placement, and <st> is -/// specified with `st_executable`. -void gr_show_image_info(uint32_t image_id, uint32_t placement_id, - uint32_t imgcol, uint32_t imgrow, - char is_classic_placeholder, int32_t diacritic_count, - char *st_executable) { - char filename[MAX_FILENAME_SIZE]; - snprintf(filename, sizeof(filename), "%s/info-%u", cache_dir, image_id); - FILE *file = fopen(filename, "w"); - if (!file) { - perror("fopen"); - return; - } - // Basic information about the cell. - fprintf(file, "image_id = %u = 0x%08X\n", image_id, image_id); - fprintf(file, "placement_id = %u = 0x%08X\n", placement_id, placement_id); - fprintf(file, "column = %d, row = %d\n", imgcol, imgrow); - fprintf(file, "classic/unicode placeholder = %s\n", - is_classic_placeholder ? "classic" : "unicode"); - fprintf(file, "original diacritic count = %d\n", diacritic_count); - // Information about the image and the placement. - Image *img = gr_find_image(image_id); - ImagePlacement *placement = gr_find_placement(img, placement_id); - gr_dump_image_info(file, img, 0); - gr_dump_placement_info(file, placement, 0); - if (img) { - fprintf(file, "Frames:\n"); - foreach_frame(*img, frame, { - gr_dump_frame_info(file, frame, 4); - }); - } - if (placement) { - fprintf(file, "Placement pixmaps:\n"); - gr_dump_placement_pixmaps(file, placement, 4); - } - fclose(file); - char *argv[] = {st_executable, "-e", "less", filename, NULL}; - if (posix_spawnp(NULL, st_executable, NULL, NULL, argv, environ) != 0) { - perror("posix_spawnp"); - return; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Appending and displaying image rectangles. -//////////////////////////////////////////////////////////////////////////////// - -/// Displays debug information in the rectangle using colors col1 and col2. -static void gr_displayinfo(Drawable buf, ImageRect *rect, int col1, int col2, - const char *message) { - int w_pix = (rect->img_end_col - rect->img_start_col) * rect->cw; - int h_pix = (rect->img_end_row - rect->img_start_row) * rect->ch; - Display *disp = imlib_context_get_display(); - GC gc = XCreateGC(disp, buf, 0, NULL); - char info[MAX_INFO_LEN]; - if (rect->placement_id) - snprintf(info, MAX_INFO_LEN, "%s%u/%u [%d:%d)x[%d:%d)", message, - rect->image_id, rect->placement_id, - rect->img_start_col, rect->img_end_col, - rect->img_start_row, rect->img_end_row); - else - snprintf(info, MAX_INFO_LEN, "%s%u [%d:%d)x[%d:%d)", message, - rect->image_id, rect->img_start_col, rect->img_end_col, - rect->img_start_row, rect->img_end_row); - XSetForeground(disp, gc, col1); - XDrawString(disp, buf, gc, rect->screen_x_pix + 4, - rect->screen_y_pix + h_pix - 3, info, strlen(info)); - XSetForeground(disp, gc, col2); - XDrawString(disp, buf, gc, rect->screen_x_pix + 2, - rect->screen_y_pix + h_pix - 5, info, strlen(info)); - XFreeGC(disp, gc); -} - -/// Draws a rectangle (bounding box) for debugging. -static void gr_showrect(Drawable buf, ImageRect *rect) { - int w_pix = (rect->img_end_col - rect->img_start_col) * rect->cw; - int h_pix = (rect->img_end_row - rect->img_start_row) * rect->ch; - Display *disp = imlib_context_get_display(); - GC gc = XCreateGC(disp, buf, 0, NULL); - XSetForeground(disp, gc, 0xFF00FF00); - XDrawRectangle(disp, buf, gc, rect->screen_x_pix, rect->screen_y_pix, - w_pix - 1, h_pix - 1); - XSetForeground(disp, gc, 0xFFFF0000); - XDrawRectangle(disp, buf, gc, rect->screen_x_pix + 1, - rect->screen_y_pix + 1, w_pix - 3, h_pix - 3); - XFreeGC(disp, gc); -} - -/// Updates the next redraw time for the given row. Resizes the -/// next_redraw_times array if needed. -static void gr_update_next_redraw_time(int row, Milliseconds next_redraw) { - if (next_redraw == 0) - return; - if (row >= kv_size(next_redraw_times)) { - size_t old_size = kv_size(next_redraw_times); - kv_a(Milliseconds, next_redraw_times, row); - for (size_t i = old_size; i <= row; ++i) - kv_A(next_redraw_times, i) = 0; - } - Milliseconds old_value = kv_A(next_redraw_times, row); - if (old_value == 0 || old_value > next_redraw) - kv_A(next_redraw_times, row) = next_redraw; -} - -/// Draws the given part of an image. -static void gr_drawimagerect(Drawable buf, ImageRect *rect) { - ImagePlacement *placement = - gr_find_image_and_placement(rect->image_id, rect->placement_id); - // If the image does not exist or image display is switched off, draw - // the bounding box. - if (!placement || !graphics_display_images) { - gr_showrect(buf, rect); - if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) - gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); - return; - } - - Image *img = placement->image; - - if (img->last_redraw < drawing_start_time) { - // This is the first time we draw this image in this redraw - // cycle. Update the frame index we are going to display. Note - // that currently all image placements are synchronized. - int old_frame = img->current_frame; - gr_update_frame_index(img, drawing_start_time); - img->last_redraw = drawing_start_time; - } - - // Adjust next redraw times for the rows of this image rect. - if (img->next_redraw) { - for (int row = rect->screen_y_row; - row <= rect->screen_y_row + rect->img_end_row - - rect->img_start_row - 1; ++row) { - gr_update_next_redraw_time( - row, img->next_redraw); - } - } - - // Load the frame. - Pixmap pixmap = gr_load_pixmap(placement, img->current_frame, rect->cw, - rect->ch); - - // If the image couldn't be loaded, display the bounding box. - if (!pixmap) { - gr_showrect(buf, rect); - if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) - gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); - return; - } - - int src_x = rect->img_start_col * rect->cw; - int src_y = rect->img_start_row * rect->ch; - int width = (rect->img_end_col - rect->img_start_col) * rect->cw; - int height = (rect->img_end_row - rect->img_start_row) * rect->ch; - int dst_x = rect->screen_x_pix; - int dst_y = rect->screen_y_pix; - - // Display the image. - Display *disp = imlib_context_get_display(); - Visual *vis = imlib_context_get_visual(); - - // Create an xrender picture for the window. - XRenderPictFormat *win_format = - XRenderFindVisualFormat(disp, vis); - Picture window_pic = - XRenderCreatePicture(disp, buf, win_format, 0, NULL); - - // If needed, invert the image pixmap. Note that this naive approach of - // inverting the pixmap is not entirely correct, because the pixmap is - // premultiplied. But the result is good enough to visually indicate - // selection. - if (rect->reverse) { - unsigned pixmap_w = - (unsigned)placement->cols * placement->scaled_cw; - unsigned pixmap_h = - (unsigned)placement->rows * placement->scaled_ch; - Pixmap invpixmap = - XCreatePixmap(disp, buf, pixmap_w, pixmap_h, 32); - XGCValues gcv = {.function = GXcopyInverted}; - GC gc = XCreateGC(disp, invpixmap, GCFunction, &gcv); - XCopyArea(disp, pixmap, invpixmap, gc, 0, 0, pixmap_w, - pixmap_h, 0, 0); - XFreeGC(disp, gc); - pixmap = invpixmap; - } - - // Create a picture for the image pixmap. - XRenderPictFormat *pic_format = - XRenderFindStandardFormat(disp, PictStandardARGB32); - Picture pixmap_pic = - XRenderCreatePicture(disp, pixmap, pic_format, 0, NULL); - - // Composite the image onto the window. In the reverse mode we ignore - // the alpha channel of the image because the naive inversion above - // seems to invert the alpha channel as well. - int pictop = rect->reverse ? PictOpSrc : PictOpOver; - XRenderComposite(disp, pictop, pixmap_pic, 0, window_pic, - src_x, src_y, src_x, src_y, dst_x, dst_y, width, - height); - - // Free resources - XRenderFreePicture(disp, pixmap_pic); - XRenderFreePicture(disp, window_pic); - if (rect->reverse) - XFreePixmap(disp, pixmap); - - // In debug mode always draw bounding boxes and print info. - if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) { - gr_showrect(buf, rect); - gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); - } -} - -/// Removes the given image rectangle. -static void gr_freerect(ImageRect *rect) { memset(rect, 0, sizeof(ImageRect)); } - -/// Returns the bottom coordinate of the rect. -static int gr_getrectbottom(ImageRect *rect) { - return rect->screen_y_pix + - (rect->img_end_row - rect->img_start_row) * rect->ch; -} - -/// Prepare for image drawing. `cw` and `ch` are dimensions of the cell. -void gr_start_drawing(Drawable buf, int cw, int ch) { - current_cw = cw; - current_ch = ch; - this_redraw_cycle_loaded_files = 0; - this_redraw_cycle_loaded_pixmaps = 0; - drawing_start_time = gr_now_ms(); - imlib_context_set_drawable(buf); -} - -/// Finish image drawing. This functions will draw all the rectangles left to -/// draw. -void gr_finish_drawing(Drawable buf) { - // Draw and then delete all known image rectangles. - for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { - ImageRect *rect = &image_rects[i]; - if (!rect->image_id) - continue; - gr_drawimagerect(buf, rect); - gr_freerect(rect); - } - - // Compute the delay until the next redraw as the minimum of the next - // redraw delays for all rows. - Milliseconds drawing_end_time = gr_now_ms(); - graphics_next_redraw_delay = INT_MAX; - for (int row = 0; row < kv_size(next_redraw_times); ++row) { - Milliseconds row_next_redraw = kv_A(next_redraw_times, row); - if (row_next_redraw > 0) { - int delay = MAX(graphics_animation_min_delay, - row_next_redraw - drawing_end_time); - graphics_next_redraw_delay = - MIN(graphics_next_redraw_delay, delay); - } - } - - // In debug mode display additional info. - if (graphics_debug_mode) { - int milliseconds = drawing_end_time - drawing_start_time; - - Display *disp = imlib_context_get_display(); - GC gc = XCreateGC(disp, buf, 0, NULL); - const char *debug_mode_str = - graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES - ? "(boxes shown) " - : ""; - int redraw_delay = graphics_next_redraw_delay == INT_MAX - ? -1 - : graphics_next_redraw_delay; - char info[MAX_INFO_LEN]; - snprintf(info, MAX_INFO_LEN, - "%sRender time: %d ms ram %ld K disk %ld K count " - "%d cell %dx%d delay %d", - debug_mode_str, milliseconds, images_ram_size / 1024, - images_disk_size / 1024, kh_size(images), current_cw, - current_ch, redraw_delay); - XSetForeground(disp, gc, 0xFF000000); - XFillRectangle(disp, buf, gc, 0, 0, 600, 16); - XSetForeground(disp, gc, 0xFFFFFFFF); - XDrawString(disp, buf, gc, 0, 14, info, strlen(info)); - XFreeGC(disp, gc); - - if (milliseconds > 0) { - fprintf(stderr, "%s (loaded %d files, %d pixmaps)\n", - info, this_redraw_cycle_loaded_files, - this_redraw_cycle_loaded_pixmaps); - } - } - - // Check the limits in case we have used too much ram for placements. - gr_check_limits(); -} - -// Add an image rectangle to the list of rectangles to draw. -void gr_append_imagerect(Drawable buf, uint32_t image_id, uint32_t placement_id, - int img_start_col, int img_end_col, int img_start_row, - int img_end_row, int x_col, int y_row, int x_pix, - int y_pix, int cw, int ch, int reverse) { - current_cw = cw; - current_ch = ch; - - ImageRect new_rect; - new_rect.image_id = image_id; - new_rect.placement_id = placement_id; - new_rect.img_start_col = img_start_col; - new_rect.img_end_col = img_end_col; - new_rect.img_start_row = img_start_row; - new_rect.img_end_row = img_end_row; - new_rect.screen_y_row = y_row; - new_rect.screen_x_pix = x_pix; - new_rect.screen_y_pix = y_pix; - new_rect.ch = ch; - new_rect.cw = cw; - new_rect.reverse = reverse; - - // Display some red text in debug mode. - if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) - gr_displayinfo(buf, &new_rect, 0xFF000000, 0xFFFF0000, "? "); - - // If it's the empty image (image_id=0) or an empty rectangle, do - // nothing. - if (image_id == 0 || img_end_col - img_start_col <= 0 || - img_end_row - img_start_row <= 0) - return; - // Try to find a rect to merge with. - ImageRect *free_rect = NULL; - for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { - ImageRect *rect = &image_rects[i]; - if (rect->image_id == 0) { - if (!free_rect) - free_rect = rect; - continue; - } - if (rect->image_id != image_id || - rect->placement_id != placement_id || rect->cw != cw || - rect->ch != ch || rect->reverse != reverse) - continue; - // We only support the case when the new stripe is added to the - // bottom of an existing rectangle and they are perfectly - // aligned. - if (rect->img_end_row == img_start_row && - gr_getrectbottom(rect) == y_pix) { - if (rect->img_start_col == img_start_col && - rect->img_end_col == img_end_col && - rect->screen_x_pix == x_pix) { - rect->img_end_row = img_end_row; - return; - } - } - } - // If we haven't merged the new rect with any existing rect, and there - // is no free rect, we have to render one of the existing rects. - if (!free_rect) { - for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { - ImageRect *rect = &image_rects[i]; - if (!free_rect || gr_getrectbottom(free_rect) > - gr_getrectbottom(rect)) - free_rect = rect; - } - gr_drawimagerect(buf, free_rect); - gr_freerect(free_rect); - } - // Start a new rectangle in `free_rect`. - *free_rect = new_rect; -} - -/// Mark rows containing animations as dirty if it's time to redraw them. Must -/// be called right after `gr_start_drawing`. -void gr_mark_dirty_animations(int *dirty, int rows) { - if (rows < kv_size(next_redraw_times)) - kv_size(next_redraw_times) = rows; - if (rows * 2 < kv_max(next_redraw_times)) - kv_resize(Milliseconds, next_redraw_times, rows); - for (int i = 0; i < MIN(rows, kv_size(next_redraw_times)); ++i) { - if (dirty[i]) { - kv_A(next_redraw_times, i) = 0; - continue; - } - Milliseconds next_update = kv_A(next_redraw_times, i); - if (next_update > 0 && next_update <= drawing_start_time) { - dirty[i] = 1; - kv_A(next_redraw_times, i) = 0; - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Command parsing and handling. -//////////////////////////////////////////////////////////////////////////////// - -/// A parsed kitty graphics protocol command. -typedef struct { - /// The command itself, without the 'G'. - char *command; - /// The payload (after ';'). - char *payload; - /// 'a=', may be 't', 'q', 'f', 'T', 'p', 'd', 'a'. - char action; - /// 'q=', 1 to suppress OK response, 2 to suppress errors too. - int quiet; - /// 'f=', use 24 or 32 for raw pixel data, 100 to autodetect with - /// imlib2. If 'f=0', will try to load with imlib2, then fallback to - /// 32-bit pixel data. - int format; - /// 'o=', may be 'z' for RFC 1950 ZLIB. - int compression; - /// 't=', may be 'f', 't' or 'd'. - char transmission_medium; - /// 'd=' - char delete_specifier; - /// 's=', 'v=', if 'a=t' or 'a=T', used only when 'f=24' or 'f=32'. - /// When 'a=f', this is the size of the frame rectangle when composed on - /// top of another frame. - int frame_pix_width, frame_pix_height; - /// 'x=', 'y=' - top-left corner of the source rectangle. - int src_pix_x, src_pix_y; - /// 'w=', 'h=' - width and height of the source rectangle. - int src_pix_width, src_pix_height; - /// 'r=', 'c=' - int rows, columns; - /// 'i=' - uint32_t image_id; - /// 'I=' - uint32_t image_number; - /// 'p=' - uint32_t placement_id; - /// 'm=', may be 0 or 1. - int more; - /// True if either 'm=0' or 'm=1' is specified. - char is_data_transmission; - /// True if turns out that this command is a continuation of a data - /// transmission and not the first one for this image. Populated by - /// `gr_handle_transmit_command`. - char is_direct_transmission_continuation; - /// 'S=', used to check the size of uploaded data. - int size; - /// 'U=', whether it's a virtual placement for Unicode placeholders. - int virtual; - /// 'C=', if true, do not move the cursor when displaying this placement - /// (non-virtual placements only). - char do_not_move_cursor; - // --------------------------------------------------------------------- - // Animation-related fields. Their keys often overlap with keys of other - // commands, so these make sense only if the action is 'a=f' (frame - // transmission) or 'a=a' (animation control). - // - // 'x=' and 'y=', the relative position of the frame image when it's - // composed on top of another frame. - int frame_dst_pix_x, frame_dst_pix_y; - /// 'X=', 'X=1' to replace colors instead of alpha blending on top of - /// the background color or frame. - char replace_instead_of_blending; - /// 'Y=', the background color in the 0xRRGGBBAA format (still - /// transmitted as a decimal number). - uint32_t background_color; - /// (Only for 'a=f'). 'c=', the 1-based index of the background frame. - int background_frame; - /// (Only for 'a=a'). 'c=', sets the index of the current frame. - int current_frame; - /// 'r=', the 1-based index of the frame to edit. - int edit_frame; - /// 'z=', the duration of the frame. Zero if not specified, negative if - /// the frame is gapless (i.e. skipped). - int gap; - /// (Only for 'a=a'). 's=', if non-zero, sets the state of the - /// animation, 1 to stop, 2 to run in loading mode, 3 to loop. - int animation_state; - /// (Only for 'a=a'). 'v=', if non-zero, sets the number of times the - /// animation will loop. 1 to loop infinitely, N to loop N-1 times. - int loops; -} GraphicsCommand; - -/// Replaces all non-printed characters in `str` with '?' and truncates the -/// string to `max_size`, maybe inserting ellipsis at the end. -static void sanitize_str(char *str, size_t max_size) { - assert(max_size >= 4); - for (size_t i = 0; i < max_size; ++i) { - unsigned c = str[i]; - if (c == '\0') - return; - if (c >= 128 || !isprint(c)) - str[i] = '?'; - } - str[max_size - 1] = '\0'; - str[max_size - 2] = '.'; - str[max_size - 3] = '.'; - str[max_size - 4] = '.'; -} - -/// A non-destructive version of `sanitize_str`. Uses a static buffer, so be -/// careful. -static const char *sanitized_filename(const char *str) { - static char buf[MAX_FILENAME_SIZE]; - strncpy(buf, str, sizeof(buf)); - sanitize_str(buf, sizeof(buf)); - return buf; -} - -/// Creates a response to the current command in `graphics_command_result`. -static void gr_createresponse(uint32_t image_id, uint32_t image_number, - uint32_t placement_id, const char *msg) { - if (!image_id && !image_number && !placement_id) { - // Nobody expects the response in this case, so just print it to - // stderr. - fprintf(stderr, - "error: No image id or image number or placement_id, " - "but still there is a response: %s\n", - msg); - return; - } - char *buf = graphics_command_result.response; - size_t maxlen = MAX_GRAPHICS_RESPONSE_LEN; - size_t written; - written = snprintf(buf, maxlen, "\033_G"); - buf += written; - maxlen -= written; - if (image_id) { - written = snprintf(buf, maxlen, "i=%u,", image_id); - buf += written; - maxlen -= written; - } - if (image_number) { - written = snprintf(buf, maxlen, "I=%u,", image_number); - buf += written; - maxlen -= written; - } - if (placement_id) { - written = snprintf(buf, maxlen, "p=%u,", placement_id); - buf += written; - maxlen -= written; - } - buf[-1] = ';'; - written = snprintf(buf, maxlen, "%s\033\\", msg); - buf += written; - maxlen -= written; - buf[-2] = '\033'; - buf[-1] = '\\'; -} - -/// Creates the 'OK' response to the current command, unless suppressed or a -/// non-final data transmission. -static void gr_reportsuccess_cmd(GraphicsCommand *cmd) { - if (cmd->quiet < 1 && !cmd->more) - gr_createresponse(cmd->image_id, cmd->image_number, - cmd->placement_id, "OK"); -} - -/// Creates the 'OK' response to the current command (unless suppressed). -static void gr_reportsuccess_frame(ImageFrame *frame) { - uint32_t id = frame->image->query_id ? frame->image->query_id - : frame->image->image_id; - if (frame->quiet < 1) - gr_createresponse(id, frame->image->image_number, - frame->image->initial_placement_id, "OK"); -} - -/// Creates an error response to the current command (unless suppressed). -static void gr_reporterror_cmd(GraphicsCommand *cmd, const char *format, ...) { - char errmsg[MAX_GRAPHICS_RESPONSE_LEN]; - graphics_command_result.error = 1; - va_list args; - va_start(args, format); - vsnprintf(errmsg, MAX_GRAPHICS_RESPONSE_LEN, format, args); - va_end(args); - - fprintf(stderr, "%s in command: %s\n", errmsg, cmd->command); - if (cmd->quiet < 2) - gr_createresponse(cmd->image_id, cmd->image_number, - cmd->placement_id, errmsg); -} - -/// Creates an error response to the current command (unless suppressed). -static void gr_reporterror_frame(ImageFrame *frame, const char *format, ...) { - char errmsg[MAX_GRAPHICS_RESPONSE_LEN]; - graphics_command_result.error = 1; - va_list args; - va_start(args, format); - vsnprintf(errmsg, MAX_GRAPHICS_RESPONSE_LEN, format, args); - va_end(args); - - if (!frame) { - fprintf(stderr, "%s\n", errmsg); - gr_createresponse(0, 0, 0, errmsg); - } else { - uint32_t id = frame->image->query_id ? frame->image->query_id - : frame->image->image_id; - fprintf(stderr, "%s id=%u\n", errmsg, id); - if (frame->quiet < 2) - gr_createresponse(id, frame->image->image_number, - frame->image->initial_placement_id, - errmsg); - } -} - -/// Loads an image and creates a success/failure response. Returns `frame`, or -/// NULL if it's a query action and the image was deleted. -static ImageFrame *gr_loadimage_and_report(ImageFrame *frame) { - gr_load_imlib_object(frame); - if (!frame->imlib_object) { - gr_reporterror_frame(frame, "EBADF: could not load image"); - } else { - gr_reportsuccess_frame(frame); - } - // If it was a query action, discard the image. - if (frame->image->query_id) { - gr_delete_image(frame->image); - return NULL; - } - return frame; -} - -/// Creates an appropriate uploading failure response to the current command. -static void gr_reportuploaderror(ImageFrame *frame) { - switch (frame->uploading_failure) { - case 0: - return; - case ERROR_CANNOT_OPEN_CACHED_FILE: - gr_reporterror_frame(frame, - "EIO: could not create a file for image"); - break; - case ERROR_OVER_SIZE_LIMIT: - gr_reporterror_frame( - frame, - "EFBIG: the size of the uploaded image exceeded " - "the image size limit %u", - graphics_max_single_image_file_size); - break; - case ERROR_UNEXPECTED_SIZE: - gr_reporterror_frame(frame, - "EINVAL: the size of the uploaded image %u " - "doesn't match the expected size %u", - frame->disk_size, frame->expected_size); - break; - }; -} - -/// Displays a non-virtual placement. This functions records the information in -/// `graphics_command_result`, the placeholder itself is created by the terminal -/// after handling the current command in the graphics module. -static void gr_display_nonvirtual_placement(ImagePlacement *placement) { - if (placement->virtual) - return; - if (placement->image->first_frame.status < STATUS_RAM_LOADING_SUCCESS) - return; - // Infer the placement size if needed. - gr_infer_placement_size_maybe(placement); - // Populate the information about the placeholder which will be created - // by the terminal. - graphics_command_result.create_placeholder = 1; - graphics_command_result.placeholder.image_id = placement->image->image_id; - graphics_command_result.placeholder.placement_id = placement->placement_id; - graphics_command_result.placeholder.columns = placement->cols; - graphics_command_result.placeholder.rows = placement->rows; - graphics_command_result.placeholder.do_not_move_cursor = - placement->do_not_move_cursor; - GR_LOG("Creating a placeholder for %u/%u %d x %d\n", - placement->image->image_id, placement->placement_id, - placement->cols, placement->rows); -} - -/// Marks the rows that are occupied by the image as dirty. -static void gr_schedule_image_redraw(Image *img) { - if (!img) - return; - gr_schedule_image_redraw_by_id(img->image_id); -} - -/// Appends data from `payload` to the frame `frame` when using direct -/// transmission. Note that we report errors only for the final command -/// (`!more`) to avoid spamming the client. If the frame is not specified, use -/// the image id and frame index we are currently uploading. -static void gr_append_data(ImageFrame *frame, const char *payload, int more) { - if (!frame) { - Image *img = gr_find_image(current_upload_image_id); - frame = gr_get_frame(img, current_upload_frame_index); - GR_LOG("Appending data to image %u frame %d\n", - current_upload_image_id, current_upload_frame_index); - if (!img) - GR_LOG("ERROR: this image doesn't exist\n"); - if (!frame) - GR_LOG("ERROR: this frame doesn't exist\n"); - } - if (!more) { - current_upload_image_id = 0; - current_upload_frame_index = 0; - } - if (!frame) { - if (!more) - gr_reporterror_frame(NULL, "ENOENT: could not find the " - "image to append data to"); - return; - } - if (frame->status != STATUS_UPLOADING) { - if (!more) - gr_reportuploaderror(frame); - return; - } - - // Decode the data. - size_t data_size = 0; - char *data = gr_base64dec(payload, &data_size); - - GR_LOG("appending %u + %zu = %zu bytes\n", frame->disk_size, data_size, - frame->disk_size + data_size); - - // Do not append this data if the image exceeds the size limit. - if (frame->disk_size + data_size > - graphics_max_single_image_file_size || - frame->expected_size > graphics_max_single_image_file_size) { - free(data); - gr_delete_imagefile(frame); - frame->uploading_failure = ERROR_OVER_SIZE_LIMIT; - if (!more) - gr_reportuploaderror(frame); - return; - } - - // If there is no open file corresponding to the image, create it. - if (!frame->open_file) { - gr_make_sure_tmpdir_exists(); - char filename[MAX_FILENAME_SIZE]; - gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); - FILE *file = fopen(filename, frame->disk_size ? "a" : "w"); - if (!file) { - frame->status = STATUS_UPLOADING_ERROR; - frame->uploading_failure = ERROR_CANNOT_OPEN_CACHED_FILE; - if (!more) - gr_reportuploaderror(frame); - return; - } - frame->open_file = file; - } - - // Write data to the file and update disk size variables. - fwrite(data, 1, data_size, frame->open_file); - free(data); - frame->disk_size += data_size; - frame->image->total_disk_size += data_size; - images_disk_size += data_size; - gr_touch_frame(frame); - - if (more) { - current_upload_image_id = frame->image->image_id; - current_upload_frame_index = frame->index; - } else { - current_upload_image_id = 0; - current_upload_frame_index = 0; - // Close the file. - if (frame->open_file) { - fclose(frame->open_file); - frame->open_file = NULL; - } - frame->status = STATUS_UPLOADING_SUCCESS; - uint32_t placement_id = frame->image->default_placement; - if (frame->expected_size && - frame->expected_size != frame->disk_size) { - // Report failure if the uploaded image size doesn't - // match the expected size. - frame->status = STATUS_UPLOADING_ERROR; - frame->uploading_failure = ERROR_UNEXPECTED_SIZE; - gr_reportuploaderror(frame); - } else { - // Make sure to redraw all existing image instances. - gr_schedule_image_redraw(frame->image); - // Try to load the image into ram and report the result. - frame = gr_loadimage_and_report(frame); - // If there is a non-virtual image placement, we may - // need to display it. - if (frame && frame->index == 1) { - Image *img = frame->image; - ImagePlacement *placement = NULL; - kh_foreach_value(img->placements, placement, { - gr_display_nonvirtual_placement(placement); - }); - } - } - } - - // Check whether we need to delete old images. - gr_check_limits(); -} - -/// Finds the image either by id or by number specified in the command and sets -/// the image_id of `cmd` if the image was found. -static Image *gr_find_image_for_command(GraphicsCommand *cmd) { - if (cmd->image_id) - return gr_find_image(cmd->image_id); - Image *img = NULL; - // If the image number is not specified, we can't find the image, unless - // it's a put command, in which case we will try the last image. - if (cmd->image_number == 0 && cmd->action == 'p') - img = gr_find_image(last_image_id); - else - img = gr_find_image_by_number(cmd->image_number); - if (img) - cmd->image_id = img->image_id; - return img; -} - -/// Creates a new image or a new frame in an existing image (depending on the -/// command's action) and initializes its parameters from the command. -static ImageFrame *gr_new_image_or_frame_from_command(GraphicsCommand *cmd) { - if (cmd->format != 0 && cmd->format != 32 && cmd->format != 24 && - cmd->compression != 0) { - gr_reporterror_cmd(cmd, "EINVAL: compression is supported only " - "for raw pixel data (f=32 or f=24)"); - // Even though we report an error, we still create an image. - } - - Image *img = NULL; - if (cmd->action == 'f') { - // If it's a frame transmission action, there must be an - // existing image. - img = gr_find_image_for_command(cmd); - if (!img) { - gr_reporterror_cmd(cmd, "ENOENT: image not found"); - return NULL; - } - } else { - // Otherwise create a new image object. If the action is `q`, - // we'll use random id instead of the one specified in the - // command. - uint32_t image_id = cmd->action == 'q' ? 0 : cmd->image_id; - img = gr_new_image(image_id); - if (!img) - return NULL; - if (cmd->action == 'q') - img->query_id = cmd->image_id; - else if (!cmd->image_id) - cmd->image_id = img->image_id; - // Set the image number. - img->image_number = cmd->image_number; - } - - ImageFrame *frame = gr_append_new_frame(img); - // Initialize the frame. - frame->expected_size = cmd->size; - frame->format = cmd->format; - frame->compression = cmd->compression; - frame->background_color = cmd->background_color; - frame->background_frame_index = cmd->background_frame; - frame->gap = cmd->gap; - img->total_duration += frame->gap; - frame->blend = !cmd->replace_instead_of_blending; - frame->data_pix_width = cmd->frame_pix_width; - frame->data_pix_height = cmd->frame_pix_height; - if (cmd->action == 'f') { - frame->x = cmd->frame_dst_pix_x; - frame->y = cmd->frame_dst_pix_y; - } - // We save the quietness information in the frame because for direct - // transmission subsequent transmission command won't contain this info. - frame->quiet = cmd->quiet; - return frame; -} - -/// Removes a file if it actually looks like a temporary file. -static void gr_delete_tmp_file(const char *filename) { - if (strstr(filename, "tty-graphics-protocol") == NULL) - return; - if (strstr(filename, "/tmp/") != filename) { - const char *tmpdir = getenv("TMPDIR"); - if (!tmpdir || !tmpdir[0] || - strstr(filename, tmpdir) != filename) - return; - } - unlink(filename); -} - -/// Handles a data transmission command. -static ImageFrame *gr_handle_transmit_command(GraphicsCommand *cmd) { - // The default is direct transmission. - if (!cmd->transmission_medium) - cmd->transmission_medium = 'd'; - - // If neither id, nor image number is specified, and the transmission - // medium is 'd' (or unspecified), and there is an active direct upload, - // this is a continuation of the upload. - if (current_upload_image_id != 0 && cmd->image_id == 0 && - cmd->image_number == 0 && cmd->transmission_medium == 'd') { - cmd->image_id = current_upload_image_id; - GR_LOG("No images id is specified, continuing uploading %u\n", - cmd->image_id); - } - - ImageFrame *frame = NULL; - if (cmd->transmission_medium == 'f' || - cmd->transmission_medium == 't') { - // File transmission. - // Create a new image or a new frame of an existing image. - frame = gr_new_image_or_frame_from_command(cmd); - if (!frame) - return NULL; - last_image_id = frame->image->image_id; - // Decode the filename. - char *original_filename = gr_base64dec(cmd->payload, NULL); - GR_LOG("Copying image %s\n", - sanitized_filename(original_filename)); - // Stat the file and check that it's a regular file and not too - // big. - struct stat st; - int stat_res = stat(original_filename, &st); - const char *stat_error = NULL; - if (stat_res) - stat_error = strerror(errno); - else if (!S_ISREG(st.st_mode)) - stat_error = "Not a regular file"; - else if (st.st_size == 0) - stat_error = "The size of the file is zero"; - else if (st.st_size > graphics_max_single_image_file_size) - stat_error = "The file is too large"; - if (stat_error) { - gr_reporterror_cmd(cmd, - "EBADF: %s", stat_error); - fprintf(stderr, "Could not load the file %s\n", - sanitized_filename(original_filename)); - frame->status = STATUS_UPLOADING_ERROR; - frame->uploading_failure = ERROR_CANNOT_COPY_FILE; - } else { - gr_make_sure_tmpdir_exists(); - // Build the filename for the cached copy of the file. - char cache_filename[MAX_FILENAME_SIZE]; - gr_get_frame_filename(frame, cache_filename, - MAX_FILENAME_SIZE); - // We will create a symlink to the original file, and - // then copy the file to the temporary cache dir. We do - // this symlink trick mostly to be able to use cp for - // copying, and avoid escaping file name characters when - // calling system at the same time. - char tmp_filename_symlink[MAX_FILENAME_SIZE + 4] = {0}; - strcat(tmp_filename_symlink, cache_filename); - strcat(tmp_filename_symlink, ".sym"); - char command[MAX_FILENAME_SIZE + 256]; - size_t len = - snprintf(command, MAX_FILENAME_SIZE + 255, - "cp '%s' '%s'", tmp_filename_symlink, - cache_filename); - if (len > MAX_FILENAME_SIZE + 255 || - symlink(original_filename, tmp_filename_symlink) || - system(command) != 0) { - gr_reporterror_cmd(cmd, - "EBADF: could not copy the " - "image to the cache dir"); - fprintf(stderr, - "Could not copy the image " - "%s (symlink %s) to %s", - sanitized_filename(original_filename), - tmp_filename_symlink, cache_filename); - frame->status = STATUS_UPLOADING_ERROR; - frame->uploading_failure = ERROR_CANNOT_COPY_FILE; - } else { - // Get the file size of the copied file. - frame->status = STATUS_UPLOADING_SUCCESS; - frame->disk_size = st.st_size; - frame->image->total_disk_size += st.st_size; - images_disk_size += frame->disk_size; - if (frame->expected_size && - frame->expected_size != frame->disk_size) { - // The file has unexpected size. - frame->status = STATUS_UPLOADING_ERROR; - frame->uploading_failure = - ERROR_UNEXPECTED_SIZE; - gr_reportuploaderror(frame); - } else { - // Everything seems fine, try to load - // and redraw existing instances. - gr_schedule_image_redraw(frame->image); - frame = gr_loadimage_and_report(frame); - } - } - // Delete the symlink. - unlink(tmp_filename_symlink); - // Delete the original file if it's temporary. - if (cmd->transmission_medium == 't') - gr_delete_tmp_file(original_filename); - } - free(original_filename); - gr_check_limits(); - } else if (cmd->transmission_medium == 'd') { - // Direct transmission (default if 't' is not specified). - frame = gr_get_last_frame(gr_find_image_for_command(cmd)); - if (frame && frame->status == STATUS_UPLOADING) { - // This is a continuation of the previous transmission. - cmd->is_direct_transmission_continuation = 1; - gr_append_data(frame, cmd->payload, cmd->more); - return frame; - } - // If no action is specified, it's not the first transmission - // command. If we couldn't find the image, something went wrong - // and we should just drop this command. - if (cmd->action == 0) - return NULL; - // Otherwise create a new image or frame structure. - frame = gr_new_image_or_frame_from_command(cmd); - if (!frame) - return NULL; - last_image_id = frame->image->image_id; - frame->status = STATUS_UPLOADING; - // Start appending data. - gr_append_data(frame, cmd->payload, cmd->more); - } else { - gr_reporterror_cmd( - cmd, - "EINVAL: transmission medium '%c' is not supported", - cmd->transmission_medium); - return NULL; - } - - return frame; -} - -/// Handles the 'put' command by creating a placement. -static void gr_handle_put_command(GraphicsCommand *cmd) { - if (cmd->image_id == 0 && cmd->image_number == 0) { - gr_reporterror_cmd(cmd, - "EINVAL: neither image id nor image number " - "are specified or both are zero"); - return; - } - - // Find the image with the id or number. - Image *img = gr_find_image_for_command(cmd); - if (!img) { - gr_reporterror_cmd(cmd, "ENOENT: image not found"); - return; - } - - // Create a placement. If a placement with the same id already exists, - // it will be deleted. If the id is zero, a random id will be generated. - ImagePlacement *placement = gr_new_placement(img, cmd->placement_id); - placement->virtual = cmd->virtual; - placement->src_pix_x = cmd->src_pix_x; - placement->src_pix_y = cmd->src_pix_y; - placement->src_pix_width = cmd->src_pix_width; - placement->src_pix_height = cmd->src_pix_height; - placement->cols = cmd->columns; - placement->rows = cmd->rows; - placement->do_not_move_cursor = cmd->do_not_move_cursor; - - if (placement->virtual) { - placement->scale_mode = SCALE_MODE_CONTAIN; - } else if (placement->cols && placement->rows) { - // For classic placements the default is to stretch the image if - // both cols and rows are specified. - placement->scale_mode = SCALE_MODE_FILL; - } else if (placement->cols || placement->rows) { - // But if only one of them is specified, the default is to - // contain. - placement->scale_mode = SCALE_MODE_CONTAIN; - } else { - // If none of them are specified, the default is to use the - // original size. - placement->scale_mode = SCALE_MODE_NONE; - } - - // Display the placement unless it's virtual. - gr_display_nonvirtual_placement(placement); - - // Report success. - gr_reportsuccess_cmd(cmd); -} - -/// Information about what to delete. -typedef struct DeletionData { - uint32_t image_id; - uint32_t placement_id; - /// If true, delete the image object if there are no more placements. - char delete_image_if_no_ref; -} DeletionData; - -/// The callback called for each cell to perform deletion. -static int gr_deletion_callback(void *data, uint32_t image_id, - uint32_t placement_id, int col, - int row, char is_classic) { - DeletionData *del_data = data; - // Leave unicode placeholders alone. - if (!is_classic) - return 0; - if (del_data->image_id && del_data->image_id != image_id) - return 0; - if (del_data->placement_id && del_data->placement_id != placement_id) - return 0; - Image *img = gr_find_image(image_id); - // If the image is already deleted, just erase the placeholder. - if (!img) - return 1; - // Delete the placement. - if (placement_id) - gr_delete_placement(gr_find_placement(img, placement_id)); - // Delete the image if image deletion is requested (uppercase delete - // specifier) and there are no more placements. - if (del_data->delete_image_if_no_ref && kh_size(img->placements) == 0) - gr_delete_image(img); - return 1; -} - -/// Handles the delete command. -static void gr_handle_delete_command(GraphicsCommand *cmd) { - DeletionData del_data = {0}; - del_data.delete_image_if_no_ref = isupper(cmd->delete_specifier) != 0; - char d = tolower(cmd->delete_specifier); - - if (d == 'n') { - d = 'i'; - Image *img = gr_find_image_by_number(cmd->image_number); - if (!img) - return; - del_data.image_id = img->image_id; - } - - if (!d || d == 'a') { - // Delete all visible placements. - gr_for_each_image_cell(gr_deletion_callback, &del_data); - } else if (d == 'i') { - // Delete the specified image by image id and maybe placement - // id. - if (!del_data.image_id) - del_data.image_id = cmd->image_id; - if (!del_data.image_id) { - fprintf(stderr, - "ERROR: image id is not specified in the " - "delete command\n"); - return; - } - del_data.placement_id = cmd->placement_id; - // NOTE: It's not very clear whether we should delete the image - // even if there are no _visible_ placements to delete. We do - // this because otherwise there is no way to delete an image - // with virtual placements in one command. - if (!del_data.placement_id && del_data.delete_image_if_no_ref) - gr_delete_image(gr_find_image(cmd->image_id)); - gr_for_each_image_cell(gr_deletion_callback, &del_data); - } else { - fprintf(stderr, - "WARNING: unsupported value of the d key: '%c'. The " - "command is ignored.\n", - cmd->delete_specifier); - } -} - -static void gr_handle_animation_control_command(GraphicsCommand *cmd) { - if (cmd->image_id == 0 && cmd->image_number == 0) { - gr_reporterror_cmd(cmd, - "EINVAL: neither image id nor image number " - "are specified or both are zero"); - return; - } - - // Find the image with the id or number. - Image *img = gr_find_image_for_command(cmd); - if (!img) { - gr_reporterror_cmd(cmd, "ENOENT: image not found"); - return; - } - - // Find the frame to edit, if requested. - ImageFrame *frame = NULL; - if (cmd->edit_frame) - frame = gr_get_frame(img, cmd->edit_frame); - if (cmd->edit_frame || cmd->gap) { - if (!frame) { - gr_reporterror_cmd(cmd, "ENOENT: frame %d not found", - cmd->edit_frame); - return; - } - if (cmd->gap) { - img->total_duration -= frame->gap; - frame->gap = cmd->gap; - img->total_duration += frame->gap; - } - } - - // Set animation-related parameters of the image. - if (cmd->current_frame) - img->current_frame = cmd->current_frame; - if (cmd->animation_state) { - if (cmd->animation_state == 1) { - img->animation_state = ANIMATION_STATE_STOPPED; - } else if (cmd->animation_state == 2) { - img->animation_state = ANIMATION_STATE_LOADING; - } else if (cmd->animation_state == 3) { - img->animation_state = ANIMATION_STATE_LOOPING; - } else { - gr_reporterror_cmd( - cmd, "EINVAL: invalid animation state: %d", - cmd->animation_state); - } - } - // TODO: Set the number of loops to cmd->loops - - // Make sure we redraw all instances of the image. - gr_schedule_image_redraw(img); -} - -/// Handles a command. -static void gr_handle_command(GraphicsCommand *cmd) { - if (!cmd->image_id && !cmd->image_number) { - // If there is no image id or image number, nobody expects a - // response, so set quiet to 2. - cmd->quiet = 2; - } - ImageFrame *frame = NULL; - switch (cmd->action) { - case 0: - // If no action is specified, it may be a data transmission - // command if 'm=' is specified. - if (cmd->is_data_transmission) { - gr_handle_transmit_command(cmd); - break; - } - gr_reporterror_cmd(cmd, "EINVAL: no action specified"); - break; - case 't': - case 'q': - case 'f': - // Transmit data. 'q' means query, which is basically the same - // as transmit, but the image is discarded, and the id is fake. - // 'f' appends a frame to an existing image. - gr_handle_transmit_command(cmd); - break; - case 'p': - // Display (put) the image. - gr_handle_put_command(cmd); - break; - case 'T': - // Transmit and display. - frame = gr_handle_transmit_command(cmd); - if (frame && !cmd->is_direct_transmission_continuation) { - gr_handle_put_command(cmd); - if (cmd->placement_id) - frame->image->initial_placement_id = - cmd->placement_id; - } - break; - case 'd': - gr_handle_delete_command(cmd); - break; - case 'a': - gr_handle_animation_control_command(cmd); - break; - default: - gr_reporterror_cmd(cmd, "EINVAL: unsupported action: %c", - cmd->action); - return; - } -} - -/// A partially parsed key-value pair. -typedef struct KeyAndValue { - char *key_start; - char *val_start; - unsigned key_len, val_len; -} KeyAndValue; - -/// Parses the value of a key and assigns it to the appropriate field of `cmd`. -static void gr_set_keyvalue(GraphicsCommand *cmd, KeyAndValue *kv) { - char *key_start = kv->key_start; - char *key_end = key_start + kv->key_len; - char *value_start = kv->val_start; - char *value_end = value_start + kv->val_len; - // Currently all keys are one-character. - if (key_end - key_start != 1) { - gr_reporterror_cmd(cmd, "EINVAL: unknown key of length %ld: %s", - key_end - key_start, key_start); - return; - } - long num = 0; - if (*key_start == 'a' || *key_start == 't' || *key_start == 'd' || - *key_start == 'o') { - // Some keys have one-character values. - if (value_end - value_start != 1) { - gr_reporterror_cmd( - cmd, - "EINVAL: value of 'a', 't' or 'd' must be a " - "single char: %s", - key_start); - return; - } - } else { - // All the other keys have integer values. - char *num_end = NULL; - num = strtol(value_start, &num_end, 10); - if (num_end != value_end) { - gr_reporterror_cmd( - cmd, "EINVAL: could not parse number value: %s", - key_start); - return; - } - } - switch (*key_start) { - case 'a': - cmd->action = *value_start; - break; - case 't': - cmd->transmission_medium = *value_start; - break; - case 'd': - cmd->delete_specifier = *value_start; - break; - case 'q': - cmd->quiet = num; - break; - case 'f': - cmd->format = num; - if (num != 0 && num != 24 && num != 32 && num != 100) { - gr_reporterror_cmd( - cmd, - "EINVAL: unsupported format specification: %s", - key_start); - } - break; - case 'o': - cmd->compression = *value_start; - if (cmd->compression != 'z') { - gr_reporterror_cmd(cmd, - "EINVAL: unsupported compression " - "specification: %s", - key_start); - } - break; - case 's': - if (cmd->action == 'a') - cmd->animation_state = num; - else - cmd->frame_pix_width = num; - break; - case 'v': - if (cmd->action == 'a') - cmd->loops = num; - else - cmd->frame_pix_height = num; - break; - case 'i': - cmd->image_id = num; - break; - case 'I': - cmd->image_number = num; - break; - case 'p': - cmd->placement_id = num; - break; - case 'x': - cmd->src_pix_x = num; - cmd->frame_dst_pix_x = num; - break; - case 'y': - if (cmd->action == 'f') - cmd->frame_dst_pix_y = num; - else - cmd->src_pix_y = num; - break; - case 'w': - cmd->src_pix_width = num; - break; - case 'h': - cmd->src_pix_height = num; - break; - case 'c': - if (cmd->action == 'f') - cmd->background_frame = num; - else if (cmd->action == 'a') - cmd->current_frame = num; - else - cmd->columns = num; - break; - case 'r': - if (cmd->action == 'f' || cmd->action == 'a') - cmd->edit_frame = num; - else - cmd->rows = num; - break; - case 'm': - cmd->is_data_transmission = 1; - cmd->more = num; - break; - case 'S': - cmd->size = num; - break; - case 'U': - cmd->virtual = num; - break; - case 'X': - if (cmd->action == 'f') - cmd->replace_instead_of_blending = num; - else - break; /*ignore*/ - break; - case 'Y': - if (cmd->action == 'f') - cmd->background_color = num; - else - break; /*ignore*/ - break; - case 'z': - if (cmd->action == 'f' || cmd->action == 'a') - cmd->gap = num; - else - break; /*ignore*/ - break; - case 'C': - cmd->do_not_move_cursor = num; - break; - default: - gr_reporterror_cmd(cmd, "EINVAL: unsupported key: %s", - key_start); - return; - } -} - -/// Parse and execute a graphics command. `buf` must start with 'G' and contain -/// at least `len + 1` characters. Returns 1 on success. -int gr_parse_command(char *buf, size_t len) { - if (buf[0] != 'G') - return 0; - - memset(&graphics_command_result, 0, sizeof(GraphicsCommandResult)); - - global_command_counter++; - GR_LOG("### Command %lu: %.80s\n", global_command_counter, buf); - - // Eat the 'G'. - ++buf; - --len; - - GraphicsCommand cmd = {.command = buf}; - // The state of parsing. 'k' to parse key, 'v' to parse value, 'p' to - // parse the payload. - char state = 'k'; - // An array of partially parsed key-value pairs. - KeyAndValue key_vals[32]; - unsigned key_vals_count = 0; - char *key_start = buf; - char *key_end = NULL; - char *val_start = NULL; - char *val_end = NULL; - char *c = buf; - while (c - buf < len + 1) { - if (state == 'k') { - switch (*c) { - case ',': - case ';': - case '\0': - state = *c == ',' ? 'k' : 'p'; - key_end = c; - gr_reporterror_cmd( - &cmd, "EINVAL: key without value: %s ", - key_start); - break; - case '=': - key_end = c; - state = 'v'; - val_start = c + 1; - break; - default: - break; - } - } else if (state == 'v') { - switch (*c) { - case ',': - case ';': - case '\0': - state = *c == ',' ? 'k' : 'p'; - val_end = c; - if (key_vals_count >= - sizeof(key_vals) / sizeof(*key_vals)) { - gr_reporterror_cmd(&cmd, - "EINVAL: too many " - "key-value pairs"); - break; - } - key_vals[key_vals_count].key_start = key_start; - key_vals[key_vals_count].val_start = val_start; - key_vals[key_vals_count].key_len = - key_end - key_start; - key_vals[key_vals_count].val_len = - val_end - val_start; - ++key_vals_count; - key_start = c + 1; - break; - default: - break; - } - } else if (state == 'p') { - cmd.payload = c; - // break out of the loop, we don't check the payload - break; - } - ++c; - } - - // Set the action key ('a=') first because we need it to disambiguate - // some keys. Also set 'i=' and 'I=' for better error reporting. - for (unsigned i = 0; i < key_vals_count; ++i) { - if (key_vals[i].key_len == 1) { - char *start = key_vals[i].key_start; - if (*start == 'a' || *start == 'i' || *start == 'I') { - gr_set_keyvalue(&cmd, &key_vals[i]); - break; - } - } - } - // Set the rest of the keys. - for (unsigned i = 0; i < key_vals_count; ++i) - gr_set_keyvalue(&cmd, &key_vals[i]); - - if (!cmd.payload) - cmd.payload = buf + len; - - if (cmd.payload && cmd.payload[0]) - GR_LOG(" payload size: %ld\n", strlen(cmd.payload)); - - if (!graphics_command_result.error) - gr_handle_command(&cmd); - - if (graphics_debug_mode) { - fprintf(stderr, "Response: "); - for (const char *resp = graphics_command_result.response; - *resp != '\0'; ++resp) { - if (isprint(*resp)) - fprintf(stderr, "%c", *resp); - else - fprintf(stderr, "(0x%x)", *resp); - } - fprintf(stderr, "\n"); - } - - // Make sure that we suppress response if needed. Usually cmd.quiet is - // taken into account when creating the response, but it's not very - // reliable in the current implementation. - if (cmd.quiet) { - if (!graphics_command_result.error || cmd.quiet >= 2) - graphics_command_result.response[0] = '\0'; - } - - return 1; -} - -//////////////////////////////////////////////////////////////////////////////// -// base64 decoding part is basically copied from st.c -//////////////////////////////////////////////////////////////////////////////// - -static const char gr_base64_digits[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, - 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, - 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -static char gr_base64_getc(const char **src) { - while (**src && !isprint(**src)) - (*src)++; - return **src ? *((*src)++) : '='; /* emulate padding if string ends */ -} - -char *gr_base64dec(const char *src, size_t *size) { - size_t in_len = strlen(src); - char *result, *dst; - - result = dst = malloc((in_len + 3) / 4 * 3 + 1); - while (*src) { - int a = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; - int b = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; - int c = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; - int d = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; - - if (a == -1 || b == -1) - break; - - *dst++ = (a << 2) | ((b & 0x30) >> 4); - if (c == -1) - break; - *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); - if (d == -1) - break; - *dst++ = ((c & 0x03) << 6) | d; - } - *dst = '\0'; - if (size) { - *size = dst - result; - } - return result; -} diff --git a/files/config/suckless/st/graphics.h b/files/config/suckless/st/graphics.h deleted file mode 100644 index 2e75dea..0000000 --- a/files/config/suckless/st/graphics.h +++ /dev/null @@ -1,107 +0,0 @@ - -#include <stdint.h> -#include <sys/types.h> -#include <X11/Xlib.h> - -/// Initialize the graphics module. -void gr_init(Display *disp, Visual *vis, Colormap cm); -/// Deinitialize the graphics module. -void gr_deinit(); - -/// Add an image rectangle to a list if rectangles to draw. This function may -/// actually draw some rectangles, or it may wait till more rectangles are -/// appended. Must be called between `gr_start_drawing` and `gr_finish_drawing`. -/// - `img_start_col..img_end_col` and `img_start_row..img_end_row` define the -/// part of the image to draw (row/col indices are zero-based, ends are -/// excluded). -/// - `x_col` and `y_row` are the coordinates of the top-left corner of the -/// image in the terminal grid. -/// - `x_pix` and `y_pix` are the same but in pixels. -/// - `reverse` indicates whether colors should be inverted. -void gr_append_imagerect(Drawable buf, uint32_t image_id, uint32_t placement_id, - int img_start_col, int img_end_col, int img_start_row, - int img_end_row, int x_col, int y_row, int x_pix, - int y_pix, int cw, int ch, int reverse); -/// Prepare for image drawing. `cw` and `ch` are dimensions of the cell. -void gr_start_drawing(Drawable buf, int cw, int ch); -/// Finish image drawing. This functions will draw all the rectangles left to -/// draw. -void gr_finish_drawing(Drawable buf); -/// Mark rows containing animations as dirty if it's time to redraw them. Must -/// be called right after `gr_start_drawing`. -void gr_mark_dirty_animations(int *dirty, int rows); - -/// Parse and execute a graphics command. `buf` must start with 'G' and contain -/// at least `len + 1` characters (including '\0'). Returns 1 on success. -/// Additional informations is returned through `graphics_command_result`. -int gr_parse_command(char *buf, size_t len); - -/// Executes `command` with the name of the file corresponding to `image_id` as -/// the argument. Executes xmessage with an error message on failure. -void gr_preview_image(uint32_t image_id, const char *command); - -/// Executes `<st> -e less <file>` where <file> is the name of a temporary file -/// containing the information about an image and placement, and <st> is -/// specified with `st_executable`. -void gr_show_image_info(uint32_t image_id, uint32_t placement_id, - uint32_t imgcol, uint32_t imgrow, - char is_classic_placeholder, int32_t diacritic_count, - char *st_executable); - -/// Dumps the internal state (images and placements) to stderr. -void gr_dump_state(); - -/// Unloads images to reduce RAM usage. -void gr_unload_images_to_reduce_ram(); - -/// Executes `callback` for each image cell. `callback` may return 1 to erase -/// the cell or 0 to keep it. This function is implemented in `st.c`. -void gr_for_each_image_cell(int (*callback)(void *data, uint32_t image_id, - uint32_t placement_id, int col, - int row, char is_classic), - void *data); - -/// Marks all the rows containing the image with `image_id` as dirty. -void gr_schedule_image_redraw_by_id(uint32_t image_id); - -typedef enum { - GRAPHICS_DEBUG_NONE = 0, - GRAPHICS_DEBUG_LOG = 1, - GRAPHICS_DEBUG_LOG_AND_BOXES = 2, -} GraphicsDebugMode; - -/// Print additional information, draw bounding bounding boxes, etc. -extern GraphicsDebugMode graphics_debug_mode; - -/// Whether to display images or just draw bounding boxes. -extern char graphics_display_images; - -/// The time in milliseconds until the next redraw to update animations. -/// INT_MAX means no redraw is needed. Populated by `gr_finish_drawing`. -extern int graphics_next_redraw_delay; - -#define MAX_GRAPHICS_RESPONSE_LEN 256 - -/// A structure representing the result of a graphics command. -typedef struct { - /// Indicates if the terminal needs to be redrawn. - char redraw; - /// The response of the command that should be sent back to the client - /// (may be empty if the quiet flag is set). - char response[MAX_GRAPHICS_RESPONSE_LEN]; - /// Whether there was an error executing this command (not very useful, - /// the response must be sent back anyway). - char error; - /// Whether the terminal has to create a placeholder for a non-virtual - /// placement. - char create_placeholder; - /// The placeholder that needs to be created. - struct { - uint32_t rows, columns; - uint32_t image_id, placement_id; - char do_not_move_cursor; - } placeholder; -} GraphicsCommandResult; - -/// The result of a graphics command. -extern GraphicsCommandResult graphics_command_result; diff --git a/files/config/suckless/st/icat-mini.sh b/files/config/suckless/st/icat-mini.sh deleted file mode 100755 index 0a8ebab..0000000 --- a/files/config/suckless/st/icat-mini.sh +++ /dev/null @@ -1,801 +0,0 @@ -#!/bin/sh - -# vim: shiftwidth=4 - -script_name="$(basename "$0")" - -short_help="Usage: $script_name [OPTIONS] <image_file> - -This is a script to display images in the terminal using the kitty graphics -protocol with Unicode placeholders. It is very basic, please use something else -if you have alternatives. - -Options: - -h Show this help. - -s SCALE The scale of the image, may be floating point. - -c N, --cols N The number of columns. - -r N, --rows N The number of rows. - --max-cols N The maximum number of columns. - --max-rows N The maximum number of rows. - --cell-size WxH The cell size in pixels. - -m METHOD The uploading method, may be 'file', 'direct' or 'auto'. - --speed SPEED The multiplier for the animation speed (float). -" - -# Exit the script on keyboard interrupt -trap "echo 'icat-mini was interrupted' >&2; exit 1" INT - -cols="" -rows="" -file="" -tty="/dev/tty" -uploading_method="auto" -cell_size="" -scale=1 -max_cols="" -max_rows="" -speed="" - -# Parse the command line. -while [ $# -gt 0 ]; do - case "$1" in - -c|--columns|--cols) - cols="$2" - shift 2 - ;; - -r|--rows|-l|--lines) - rows="$2" - shift 2 - ;; - -s|--scale) - scale="$2" - shift 2 - ;; - -h|--help) - echo "$short_help" - exit 0 - ;; - -m|--upload-method|--uploading-method) - uploading_method="$2" - shift 2 - ;; - --cell-size) - cell_size="$2" - shift 2 - ;; - --max-cols) - max_cols="$2" - shift 2 - ;; - --max-rows) - max_rows="$2" - shift 2 - ;; - --speed) - speed="$2" - shift 2 - ;; - --) - file="$2" - shift 2 - ;; - -*) - echo "Unknown option: $1" >&2 - exit 1 - ;; - *) - if [ -n "$file" ]; then - echo "Multiple image files are not supported: $file and $1" >&2 - exit 1 - fi - file="$1" - shift - ;; - esac -done - -file="$(realpath "$file")" - -##################################################################### -# Adjust the terminal state -##################################################################### - -stty_orig="$(stty -g < "$tty")" -stty -echo < "$tty" -# Disable ctrl-z. Pressing ctrl-z during image uploading may cause some -# horrible issues otherwise. -stty susp undef < "$tty" -stty -icanon < "$tty" - -restore_echo() { - [ -n "$stty_orig" ] || return - stty $stty_orig < "$tty" -} - -trap restore_echo EXIT TERM - -##################################################################### -# Detect imagemagick -##################################################################### - -# If there is the 'magick' command, use it instead of separate 'convert' and -# 'identify' commands. -if command -v magick > /dev/null; then - identify="magick identify" - convert="magick" -else - identify="identify" - convert="convert" -fi - -##################################################################### -# Detect tmux -##################################################################### - -# Check if we are inside tmux. -inside_tmux="" -if [ -n "$TMUX" ]; then - case "$TERM" in - *tmux*|*screen*) - inside_tmux=1 - ;; - esac -fi - -##################################################################### -# Compute the number of rows and columns -##################################################################### - -is_pos_int() { - if [ -z "$1" ]; then - return 1 # false - fi - if [ -z "$(printf '%s' "$1" | tr -d '[:digit:]')" ]; then - if [ "$1" -gt 0 ]; then - return 0 # true - fi - fi - return 1 # false -} - -if [ -n "$cols" ] || [ -n "$rows" ]; then - if [ -n "$max_cols" ] || [ -n "$max_rows" ]; then - echo "You can't specify both max-cols/rows and cols/rows" >&2 - exit 1 - fi -fi - -# Get the max number of cols and rows. -[ -n "$max_cols" ] || max_cols="$(tput cols)" -[ -n "$max_rows" ] || max_rows="$(tput lines)" -if [ "$max_rows" -gt 255 ]; then - max_rows=255 -fi - -python_ioctl_command="import array, fcntl, termios -buf = array.array('H', [0, 0, 0, 0]) -fcntl.ioctl(0, termios.TIOCGWINSZ, buf) -print(int(buf[2]/buf[1]), int(buf[3]/buf[0]))" - -# Get the cell size in pixels if either cols or rows are not specified. -if [ -z "$cols" ] || [ -z "$rows" ]; then - cell_width="" - cell_height="" - # If the cell size is specified, use it. - if [ -n "$cell_size" ]; then - cell_width="${cell_size%x*}" - cell_height="${cell_size#*x}" - if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then - echo "Invalid cell size: $cell_size" >&2 - exit 1 - fi - fi - # Otherwise try to use TIOCGWINSZ ioctl via python. - if [ -z "$cell_width" ] || [ -z "$cell_height" ]; then - cell_size_ioctl="$(python3 -c "$python_ioctl_command" < "$tty" 2> /dev/null)" - cell_width="${cell_size_ioctl% *}" - cell_height="${cell_size_ioctl#* }" - if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then - cell_width="" - cell_height="" - fi - fi - # If it didn't work, try to use csi XTWINOPS. - if [ -z "$cell_width" ] || [ -z "$cell_height" ]; then - if [ -n "$inside_tmux" ]; then - printf '\ePtmux;\e\e[16t\e\\' >> "$tty" - else - printf '\e[16t' >> "$tty" - fi - # The expected response will look like ^[[6;<height>;<width>t - term_response="" - while true; do - char=$(dd bs=1 count=1 <"$tty" 2>/dev/null) - if [ "$char" = "t" ]; then - break - fi - term_response="$term_response$char" - done - cell_height="$(printf '%s' "$term_response" | cut -d ';' -f 2)" - cell_width="$(printf '%s' "$term_response" | cut -d ';' -f 3)" - if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then - cell_width=8 - cell_height=16 - fi - fi -fi - -# Compute a formula with bc and round to the nearest integer. -bc_round() { - LC_NUMERIC=C printf '%.0f' "$(printf '%s\n' "scale=2;($1) + 0.5" | bc)" -} - -# Compute the number of rows and columns of the image. -if [ -z "$cols" ] || [ -z "$rows" ]; then - # Get the size of the image and its resolution. If it's an animation, use - # the first frame. - format_output="$($identify -format '%w %h\n' "$file" | head -1)" - img_width="$(printf '%s' "$format_output" | cut -d ' ' -f 1)" - img_height="$(printf '%s' "$format_output" | cut -d ' ' -f 2)" - if ! is_pos_int "$img_width" || ! is_pos_int "$img_height"; then - echo "Couldn't get image size from identify: $format_output" >&2 - echo >&2 - exit 1 - fi - opt_cols_expr="(${scale}*${img_width}/${cell_width})" - opt_rows_expr="(${scale}*${img_height}/${cell_height})" - if [ -z "$cols" ] && [ -z "$rows" ]; then - # If columns and rows are not specified, compute the optimal values - # using the information about rows and columns per inch. - cols="$(bc_round "$opt_cols_expr")" - rows="$(bc_round "$opt_rows_expr")" - # Make sure that automatically computed rows and columns are within some - # sane limits - if [ "$cols" -gt "$max_cols" ]; then - rows="$(bc_round "$rows * $max_cols / $cols")" - cols="$max_cols" - fi - if [ "$rows" -gt "$max_rows" ]; then - cols="$(bc_round "$cols * $max_rows / $rows")" - rows="$max_rows" - fi - elif [ -z "$cols" ]; then - # If only one dimension is specified, compute the other one to match the - # aspect ratio as close as possible. - cols="$(bc_round "${opt_cols_expr}*${rows}/${opt_rows_expr}")" - elif [ -z "$rows" ]; then - rows="$(bc_round "${opt_rows_expr}*${cols}/${opt_cols_expr}")" - fi - - if [ "$cols" -lt 1 ]; then - cols=1 - fi - if [ "$rows" -lt 1 ]; then - rows=1 - fi -fi - -##################################################################### -# Generate an image id -##################################################################### - -image_id="" -while [ -z "$image_id" ]; do - image_id="$(shuf -i 16777217-4294967295 -n 1)" - # Check that the id requires 24-bit fg colors. - if [ "$(expr \( "$image_id" / 256 \) % 65536)" -eq 0 ]; then - image_id="" - fi -done - -##################################################################### -# Uploading the image -##################################################################### - -# Choose the uploading method -if [ "$uploading_method" = "auto" ]; then - if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ] || [ -n "$SSH_CONNECTION" ]; then - uploading_method="direct" - else - uploading_method="file" - fi -fi - -# Functions to emit the start and the end of a graphics command. -if [ -n "$inside_tmux" ]; then - # If we are in tmux we have to wrap the command in Ptmux. - graphics_command_start='\ePtmux;\e\e_G' - graphics_command_end='\e\e\\\e\\' -else - graphics_command_start='\e_G' - graphics_command_end='\e\\' -fi - -start_gr_command() { - printf "$graphics_command_start" >> "$tty" -} -end_gr_command() { - printf "$graphics_command_end" >> "$tty" -} - -# Send a graphics command with the correct start and end -gr_command() { - start_gr_command - printf '%s' "$1" >> "$tty" - end_gr_command -} - -# Send an uploading command. Usage: gr_upload <action> <command> <file> -# Where <action> is a part of command that specifies the action, it will be -# repeated for every chunk (if the method is direct), and <command> is the rest -# of the command that specifies the image parameters. <action> and <command> -# must not include the transmission method or ';'. -# Example: -# gr_upload "a=T,q=2" "U=1,i=${image_id},f=100,c=${cols},r=${rows}" "$file" -gr_upload() { - arg_action="$1" - arg_command="$2" - arg_file="$3" - if [ "$uploading_method" = "file" ]; then - # base64-encode the filename - encoded_filename=$(printf '%s' "$arg_file" | base64 -w0) - gr_command "${arg_action},${arg_command},t=f;${encoded_filename}" - fi - if [ "$uploading_method" = "direct" ]; then - # Create a temporary directory to store the chunked image. - chunkdir="$(mktemp -d)" - if [ ! "$chunkdir" ] || [ ! -d "$chunkdir" ]; then - echo "Can't create a temp dir" >&2 - exit 1 - fi - # base64-encode the file and split it into chunks. The size of each - # graphics command shouldn't be more than 4096, so we set the size of an - # encoded chunk to be 3968, slightly less than that. - chunk_size=3968 - cat "$arg_file" | base64 -w0 | split -b "$chunk_size" - "$chunkdir/chunk_" - - # Issue a command indicating that we want to start data transmission for - # a new image. - gr_command "${arg_action},${arg_command},t=d,m=1" - - # Transmit chunks. - for chunk in "$chunkdir/chunk_"*; do - start_gr_command - printf '%s' "${arg_action},i=${image_id},m=1;" >> "$tty" - cat "$chunk" >> "$tty" - end_gr_command - rm "$chunk" - done - - # Tell the terminal that we are done. - gr_command "${arg_action},i=$image_id,m=0" - - # Remove the temporary directory. - rmdir "$chunkdir" - fi -} - -delayed_frame_dir_cleanup() { - arg_frame_dir="$1" - sleep 2 - if [ -n "$arg_frame_dir" ]; then - for frame in "$arg_frame_dir"/frame_*.png; do - rm "$frame" - done - rmdir "$arg_frame_dir" - fi -} - -upload_image_and_print_placeholder() { - # Check if the file is an animation. - frame_count=$($identify -format '%n\n' "$file" | head -n 1) - if [ "$frame_count" -gt 1 ]; then - # The file is an animation, decompose into frames and upload each frame. - frame_dir="$(mktemp -d)" - frame_dir="$HOME/temp/frames${frame_dir}" - mkdir -p "$frame_dir" - if [ ! "$frame_dir" ] || [ ! -d "$frame_dir" ]; then - echo "Can't create a temp dir for frames" >&2 - exit 1 - fi - - # Decompose the animation into separate frames. - $convert "$file" -coalesce "$frame_dir/frame_%06d.png" - - # Get all frame delays at once, in centiseconds, as a space-separated - # string. - delays=$($identify -format "%T " "$file") - - frame_number=1 - for frame in "$frame_dir"/frame_*.png; do - # Read the delay for the current frame and convert it from - # centiseconds to milliseconds. - delay=$(printf '%s' "$delays" | cut -d ' ' -f "$frame_number") - delay=$((delay * 10)) - # If the delay is 0, set it to 100ms. - if [ "$delay" -eq 0 ]; then - delay=100 - fi - - if [ -n "$speed" ]; then - delay=$(bc_round "$delay / $speed") - fi - - if [ "$frame_number" -eq 1 ]; then - # Upload the first frame with a=T - gr_upload "q=2,a=T" "f=100,U=1,i=${image_id},c=${cols},r=${rows}" "$frame" - # Set the delay for the first frame and also play the animation - # in loading mode (s=2). - gr_command "a=a,v=1,s=2,r=${frame_number},z=${delay},i=${image_id}" - # Print the placeholder after the first frame to reduce the wait - # time. - print_placeholder - else - # Upload subsequent frames with a=f - gr_upload "q=2,a=f" "f=100,i=${image_id},z=${delay}" "$frame" - fi - - frame_number=$((frame_number + 1)) - done - - # Play the animation in loop mode (s=3). - gr_command "a=a,v=1,s=3,i=${image_id}" - - # Remove the temporary directory, but do it in the background with a - # delay to avoid removing files before they are loaded by the terminal. - delayed_frame_dir_cleanup "$frame_dir" 2> /dev/null & - else - # The file is not an animation, upload it directly - gr_upload "q=2,a=T" "U=1,i=${image_id},f=100,c=${cols},r=${rows}" "$file" - # Print the placeholder - print_placeholder - fi -} - -##################################################################### -# Printing the image placeholder -##################################################################### - -print_placeholder() { - # Each line starts with the escape sequence to set the foreground color to - # the image id. - blue="$(expr "$image_id" % 256 )" - green="$(expr \( "$image_id" / 256 \) % 256 )" - red="$(expr \( "$image_id" / 65536 \) % 256 )" - line_start="$(printf "\e[38;2;%d;%d;%dm" "$red" "$green" "$blue")" - line_end="$(printf "\e[39;m")" - - id4th="$(expr \( "$image_id" / 16777216 \) % 256 )" - eval "id_diacritic=\$d${id4th}" - - # Reset the brush state, mostly to reset the underline color. - printf "\e[0m" - - # Fill the output with characters representing the image - for y in $(seq 0 "$(expr "$rows" - 1)"); do - eval "row_diacritic=\$d${y}" - printf '%s' "$line_start" - for x in $(seq 0 "$(expr "$cols" - 1)"); do - eval "col_diacritic=\$d${x}" - # Note that when $x is out of bounds, the column diacritic will - # be empty, meaning that the column should be guessed by the - # terminal. - if [ "$x" -ge "$num_diacritics" ]; then - printf '%s' "${placeholder}${row_diacritic}" - else - printf '%s' "${placeholder}${row_diacritic}${col_diacritic}${id_diacritic}" - fi - done - printf '%s\n' "$line_end" - done - - printf "\e[0m" -} - -d0="̅" -d1="̍" -d2="̎" -d3="̐" -d4="̒" -d5="̽" -d6="̾" -d7="̿" -d8="͆" -d9="͊" -d10="͋" -d11="͌" -d12="͐" -d13="͑" -d14="͒" -d15="͗" -d16="͛" -d17="ͣ" -d18="ͤ" -d19="ͥ" -d20="ͦ" -d21="ͧ" -d22="ͨ" -d23="ͩ" -d24="ͪ" -d25="ͫ" -d26="ͬ" -d27="ͭ" -d28="ͮ" -d29="ͯ" -d30="҃" -d31="҄" -d32="҅" -d33="҆" -d34="҇" -d35="֒" -d36="֓" -d37="֔" -d38="֕" -d39="֗" -d40="֘" -d41="֙" -d42="֜" -d43="֝" -d44="֞" -d45="֟" -d46="֠" -d47="֡" -d48="֨" -d49="֩" -d50="֫" -d51="֬" -d52="֯" -d53="ׄ" -d54="ؐ" -d55="ؑ" -d56="ؒ" -d57="ؓ" -d58="ؔ" -d59="ؕ" -d60="ؖ" -d61="ؗ" -d62="ٗ" -d63="٘" -d64="ٙ" -d65="ٚ" -d66="ٛ" -d67="ٝ" -d68="ٞ" -d69="ۖ" -d70="ۗ" -d71="ۘ" -d72="ۙ" -d73="ۚ" -d74="ۛ" -d75="ۜ" -d76="۟" -d77="۠" -d78="ۡ" -d79="ۢ" -d80="ۤ" -d81="ۧ" -d82="ۨ" -d83="۫" -d84="۬" -d85="ܰ" -d86="ܲ" -d87="ܳ" -d88="ܵ" -d89="ܶ" -d90="ܺ" -d91="ܽ" -d92="ܿ" -d93="݀" -d94="݁" -d95="݃" -d96="݅" -d97="݇" -d98="݉" -d99="݊" -d100="߫" -d101="߬" -d102="߭" -d103="߮" -d104="߯" -d105="߰" -d106="߱" -d107="߳" -d108="ࠖ" -d109="ࠗ" -d110="࠘" -d111="࠙" -d112="ࠛ" -d113="ࠜ" -d114="ࠝ" -d115="ࠞ" -d116="ࠟ" -d117="ࠠ" -d118="ࠡ" -d119="ࠢ" -d120="ࠣ" -d121="ࠥ" -d122="ࠦ" -d123="ࠧ" -d124="ࠩ" -d125="ࠪ" -d126="ࠫ" -d127="ࠬ" -d128="࠭" -d129="॑" -d130="॓" -d131="॔" -d132="ྂ" -d133="ྃ" -d134="྆" -d135="྇" -d136="፝" -d137="፞" -d138="፟" -d139="៝" -d140="᤺" -d141="ᨗ" -d142="᩵" -d143="᩶" -d144="᩷" -d145="᩸" -d146="᩹" -d147="᩺" -d148="᩻" -d149="᩼" -d150="᭫" -d151="᭭" -d152="᭮" -d153="᭯" -d154="᭰" -d155="᭱" -d156="᭲" -d157="᭳" -d158="᳐" -d159="᳑" -d160="᳒" -d161="᳚" -d162="᳛" -d163="᳠" -d164="᷀" -d165="᷁" -d166="᷃" -d167="᷄" -d168="᷅" -d169="᷆" -d170="᷇" -d171="᷈" -d172="᷉" -d173="᷋" -d174="᷌" -d175="᷑" -d176="᷒" -d177="ᷓ" -d178="ᷔ" -d179="ᷕ" -d180="ᷖ" -d181="ᷗ" -d182="ᷘ" -d183="ᷙ" -d184="ᷚ" -d185="ᷛ" -d186="ᷜ" -d187="ᷝ" -d188="ᷞ" -d189="ᷟ" -d190="ᷠ" -d191="ᷡ" -d192="ᷢ" -d193="ᷣ" -d194="ᷤ" -d195="ᷥ" -d196="ᷦ" -d197="᷾" -d198="⃐" -d199="⃑" -d200="⃔" -d201="⃕" -d202="⃖" -d203="⃗" -d204="⃛" -d205="⃜" -d206="⃡" -d207="⃧" -d208="⃩" -d209="⃰" -d210="⳯" -d211="⳰" -d212="⳱" -d213="ⷠ" -d214="ⷡ" -d215="ⷢ" -d216="ⷣ" -d217="ⷤ" -d218="ⷥ" -d219="ⷦ" -d220="ⷧ" -d221="ⷨ" -d222="ⷩ" -d223="ⷪ" -d224="ⷫ" -d225="ⷬ" -d226="ⷭ" -d227="ⷮ" -d228="ⷯ" -d229="ⷰ" -d230="ⷱ" -d231="ⷲ" -d232="ⷳ" -d233="ⷴ" -d234="ⷵ" -d235="ⷶ" -d236="ⷷ" -d237="ⷸ" -d238="ⷹ" -d239="ⷺ" -d240="ⷻ" -d241="ⷼ" -d242="ⷽ" -d243="ⷾ" -d244="ⷿ" -d245="꙯" -d246="꙼" -d247="꙽" -d248="꛰" -d249="꛱" -d250="꣠" -d251="꣡" -d252="꣢" -d253="꣣" -d254="꣤" -d255="꣥" -d256="꣦" -d257="꣧" -d258="꣨" -d259="꣩" -d260="꣪" -d261="꣫" -d262="꣬" -d263="꣭" -d264="꣮" -d265="꣯" -d266="꣰" -d267="꣱" -d268="ꪰ" -d269="ꪲ" -d270="ꪳ" -d271="ꪷ" -d272="ꪸ" -d273="ꪾ" -d274="꪿" -d275="꫁" -d276="︠" -d277="︡" -d278="︢" -d279="︣" -d280="︤" -d281="︥" -d282="︦" -d283="𐨏" -d284="𐨸" -d285="𝆅" -d286="𝆆" -d287="𝆇" -d288="𝆈" -d289="𝆉" -d290="𝆪" -d291="𝆫" -d292="𝆬" -d293="𝆭" -d294="𝉂" -d295="𝉃" -d296="𝉄" - -num_diacritics="297" - -placeholder="" - -##################################################################### -# Upload the image and print the placeholder -##################################################################### - -upload_image_and_print_placeholder diff --git a/files/config/suckless/st/khash.h b/files/config/suckless/st/khash.h deleted file mode 100644 index f75f347..0000000 --- a/files/config/suckless/st/khash.h +++ /dev/null @@ -1,627 +0,0 @@ -/* The MIT License - - Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk> - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -/* - An example: - -#include "khash.h" -KHASH_MAP_INIT_INT(32, char) -int main() { - int ret, is_missing; - khiter_t k; - khash_t(32) *h = kh_init(32); - k = kh_put(32, h, 5, &ret); - kh_value(h, k) = 10; - k = kh_get(32, h, 10); - is_missing = (k == kh_end(h)); - k = kh_get(32, h, 5); - kh_del(32, h, k); - for (k = kh_begin(h); k != kh_end(h); ++k) - if (kh_exist(h, k)) kh_value(h, k) = 1; - kh_destroy(32, h); - return 0; -} -*/ - -/* - 2013-05-02 (0.2.8): - - * Use quadratic probing. When the capacity is power of 2, stepping function - i*(i+1)/2 guarantees to traverse each bucket. It is better than double - hashing on cache performance and is more robust than linear probing. - - In theory, double hashing should be more robust than quadratic probing. - However, my implementation is probably not for large hash tables, because - the second hash function is closely tied to the first hash function, - which reduce the effectiveness of double hashing. - - Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php - - 2011-12-29 (0.2.7): - - * Minor code clean up; no actual effect. - - 2011-09-16 (0.2.6): - - * The capacity is a power of 2. This seems to dramatically improve the - speed for simple keys. Thank Zilong Tan for the suggestion. Reference: - - - http://code.google.com/p/ulib/ - - http://nothings.org/computer/judy/ - - * Allow to optionally use linear probing which usually has better - performance for random input. Double hashing is still the default as it - is more robust to certain non-random input. - - * Added Wang's integer hash function (not used by default). This hash - function is more robust to certain non-random input. - - 2011-02-14 (0.2.5): - - * Allow to declare global functions. - - 2009-09-26 (0.2.4): - - * Improve portability - - 2008-09-19 (0.2.3): - - * Corrected the example - * Improved interfaces - - 2008-09-11 (0.2.2): - - * Improved speed a little in kh_put() - - 2008-09-10 (0.2.1): - - * Added kh_clear() - * Fixed a compiling error - - 2008-09-02 (0.2.0): - - * Changed to token concatenation which increases flexibility. - - 2008-08-31 (0.1.2): - - * Fixed a bug in kh_get(), which has not been tested previously. - - 2008-08-31 (0.1.1): - - * Added destructor -*/ - - -#ifndef __AC_KHASH_H -#define __AC_KHASH_H - -/*! - @header - - Generic hash table library. - */ - -#define AC_VERSION_KHASH_H "0.2.8" - -#include <stdlib.h> -#include <string.h> -#include <limits.h> - -/* compiler specific configuration */ - -#if UINT_MAX == 0xffffffffu -typedef unsigned int khint32_t; -#elif ULONG_MAX == 0xffffffffu -typedef unsigned long khint32_t; -#endif - -#if ULONG_MAX == ULLONG_MAX -typedef unsigned long khint64_t; -#else -typedef unsigned long long khint64_t; -#endif - -#ifndef kh_inline -#ifdef _MSC_VER -#define kh_inline __inline -#else -#define kh_inline inline -#endif -#endif /* kh_inline */ - -#ifndef klib_unused -#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) -#define klib_unused __attribute__ ((__unused__)) -#else -#define klib_unused -#endif -#endif /* klib_unused */ - -typedef khint32_t khint_t; -typedef khint_t khiter_t; - -#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) -#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) -#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) -#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) -#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) -#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) -#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) - -#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) - -#ifndef kroundup32 -#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) -#endif - -#ifndef kcalloc -#define kcalloc(N,Z) calloc(N,Z) -#endif -#ifndef kmalloc -#define kmalloc(Z) malloc(Z) -#endif -#ifndef krealloc -#define krealloc(P,Z) realloc(P,Z) -#endif -#ifndef kfree -#define kfree(P) free(P) -#endif - -static const double __ac_HASH_UPPER = 0.77; - -#define __KHASH_TYPE(name, khkey_t, khval_t) \ - typedef struct kh_##name##_s { \ - khint_t n_buckets, size, n_occupied, upper_bound; \ - khint32_t *flags; \ - khkey_t *keys; \ - khval_t *vals; \ - } kh_##name##_t; - -#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ - extern kh_##name##_t *kh_init_##name(void); \ - extern void kh_destroy_##name(kh_##name##_t *h); \ - extern void kh_clear_##name(kh_##name##_t *h); \ - extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ - extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ - extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ - extern void kh_del_##name(kh_##name##_t *h, khint_t x); - -#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - SCOPE kh_##name##_t *kh_init_##name(void) { \ - return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ - } \ - SCOPE void kh_destroy_##name(kh_##name##_t *h) \ - { \ - if (h) { \ - kfree((void *)h->keys); kfree(h->flags); \ - kfree((void *)h->vals); \ - kfree(h); \ - } \ - } \ - SCOPE void kh_clear_##name(kh_##name##_t *h) \ - { \ - if (h && h->flags) { \ - memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ - h->size = h->n_occupied = 0; \ - } \ - } \ - SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ - { \ - if (h->n_buckets) { \ - khint_t k, i, last, mask, step = 0; \ - mask = h->n_buckets - 1; \ - k = __hash_func(key); i = k & mask; \ - last = i; \ - while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ - i = (i + (++step)) & mask; \ - if (i == last) return h->n_buckets; \ - } \ - return __ac_iseither(h->flags, i)? h->n_buckets : i; \ - } else return 0; \ - } \ - SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ - { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ - khint32_t *new_flags = 0; \ - khint_t j = 1; \ - { \ - kroundup32(new_n_buckets); \ - if (new_n_buckets < 4) new_n_buckets = 4; \ - if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ - else { /* hash table size to be changed (shrink or expand); rehash */ \ - new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ - if (!new_flags) return -1; \ - memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ - if (h->n_buckets < new_n_buckets) { /* expand */ \ - khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ - if (!new_keys) { kfree(new_flags); return -1; } \ - h->keys = new_keys; \ - if (kh_is_map) { \ - khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ - if (!new_vals) { kfree(new_flags); return -1; } \ - h->vals = new_vals; \ - } \ - } /* otherwise shrink */ \ - } \ - } \ - if (j) { /* rehashing is needed */ \ - for (j = 0; j != h->n_buckets; ++j) { \ - if (__ac_iseither(h->flags, j) == 0) { \ - khkey_t key = h->keys[j]; \ - khval_t val; \ - khint_t new_mask; \ - new_mask = new_n_buckets - 1; \ - if (kh_is_map) val = h->vals[j]; \ - __ac_set_isdel_true(h->flags, j); \ - while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ - khint_t k, i, step = 0; \ - k = __hash_func(key); \ - i = k & new_mask; \ - while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ - __ac_set_isempty_false(new_flags, i); \ - if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ - { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ - if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ - __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ - } else { /* write the element and jump out of the loop */ \ - h->keys[i] = key; \ - if (kh_is_map) h->vals[i] = val; \ - break; \ - } \ - } \ - } \ - } \ - if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ - h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ - if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ - } \ - kfree(h->flags); /* free the working space */ \ - h->flags = new_flags; \ - h->n_buckets = new_n_buckets; \ - h->n_occupied = h->size; \ - h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ - } \ - return 0; \ - } \ - SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ - { \ - khint_t x; \ - if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ - if (h->n_buckets > (h->size<<1)) { \ - if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ - *ret = -1; return h->n_buckets; \ - } \ - } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ - *ret = -1; return h->n_buckets; \ - } \ - } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ - { \ - khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ - x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ - if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ - else { \ - last = i; \ - while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ - if (__ac_isdel(h->flags, i)) site = i; \ - i = (i + (++step)) & mask; \ - if (i == last) { x = site; break; } \ - } \ - if (x == h->n_buckets) { \ - if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ - else x = i; \ - } \ - } \ - } \ - if (__ac_isempty(h->flags, x)) { /* not present at all */ \ - h->keys[x] = key; \ - __ac_set_isboth_false(h->flags, x); \ - ++h->size; ++h->n_occupied; \ - *ret = 1; \ - } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ - h->keys[x] = key; \ - __ac_set_isboth_false(h->flags, x); \ - ++h->size; \ - *ret = 2; \ - } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ - return x; \ - } \ - SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ - { \ - if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ - __ac_set_isdel_true(h->flags, x); \ - --h->size; \ - } \ - } - -#define KHASH_DECLARE(name, khkey_t, khval_t) \ - __KHASH_TYPE(name, khkey_t, khval_t) \ - __KHASH_PROTOTYPES(name, khkey_t, khval_t) - -#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - __KHASH_TYPE(name, khkey_t, khval_t) \ - __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) - -#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) - -/* --- BEGIN OF HASH FUNCTIONS --- */ - -/*! @function - @abstract Integer hash function - @param key The integer [khint32_t] - @return The hash value [khint_t] - */ -#define kh_int_hash_func(key) (khint32_t)(key) -/*! @function - @abstract Integer comparison function - */ -#define kh_int_hash_equal(a, b) ((a) == (b)) -/*! @function - @abstract 64-bit integer hash function - @param key The integer [khint64_t] - @return The hash value [khint_t] - */ -#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) -/*! @function - @abstract 64-bit integer comparison function - */ -#define kh_int64_hash_equal(a, b) ((a) == (b)) -/*! @function - @abstract const char* hash function - @param s Pointer to a null terminated string - @return The hash value - */ -static kh_inline khint_t __ac_X31_hash_string(const char *s) -{ - khint_t h = (khint_t)*s; - if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; - return h; -} -/*! @function - @abstract Another interface to const char* hash function - @param key Pointer to a null terminated string [const char*] - @return The hash value [khint_t] - */ -#define kh_str_hash_func(key) __ac_X31_hash_string(key) -/*! @function - @abstract Const char* comparison function - */ -#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) - -static kh_inline khint_t __ac_Wang_hash(khint_t key) -{ - key += ~(key << 15); - key ^= (key >> 10); - key += (key << 3); - key ^= (key >> 6); - key += ~(key << 11); - key ^= (key >> 16); - return key; -} -#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key) - -/* --- END OF HASH FUNCTIONS --- */ - -/* Other convenient macros... */ - -/*! - @abstract Type of the hash table. - @param name Name of the hash table [symbol] - */ -#define khash_t(name) kh_##name##_t - -/*! @function - @abstract Initiate a hash table. - @param name Name of the hash table [symbol] - @return Pointer to the hash table [khash_t(name)*] - */ -#define kh_init(name) kh_init_##name() - -/*! @function - @abstract Destroy a hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - */ -#define kh_destroy(name, h) kh_destroy_##name(h) - -/*! @function - @abstract Reset a hash table without deallocating memory. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - */ -#define kh_clear(name, h) kh_clear_##name(h) - -/*! @function - @abstract Resize a hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param s New size [khint_t] - */ -#define kh_resize(name, h, s) kh_resize_##name(h, s) - -/*! @function - @abstract Insert a key to the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Key [type of keys] - @param r Extra return code: -1 if the operation failed; - 0 if the key is present in the hash table; - 1 if the bucket is empty (never used); 2 if the element in - the bucket has been deleted [int*] - @return Iterator to the inserted element [khint_t] - */ -#define kh_put(name, h, k, r) kh_put_##name(h, k, r) - -/*! @function - @abstract Retrieve a key from the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Key [type of keys] - @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] - */ -#define kh_get(name, h, k) kh_get_##name(h, k) - -/*! @function - @abstract Remove a key from the hash table. - @param name Name of the hash table [symbol] - @param h Pointer to the hash table [khash_t(name)*] - @param k Iterator to the element to be deleted [khint_t] - */ -#define kh_del(name, h, k) kh_del_##name(h, k) - -/*! @function - @abstract Test whether a bucket contains data. - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return 1 if containing data; 0 otherwise [int] - */ -#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) - -/*! @function - @abstract Get key given an iterator - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return Key [type of keys] - */ -#define kh_key(h, x) ((h)->keys[x]) - -/*! @function - @abstract Get value given an iterator - @param h Pointer to the hash table [khash_t(name)*] - @param x Iterator to the bucket [khint_t] - @return Value [type of values] - @discussion For hash sets, calling this results in segfault. - */ -#define kh_val(h, x) ((h)->vals[x]) - -/*! @function - @abstract Alias of kh_val() - */ -#define kh_value(h, x) ((h)->vals[x]) - -/*! @function - @abstract Get the start iterator - @param h Pointer to the hash table [khash_t(name)*] - @return The start iterator [khint_t] - */ -#define kh_begin(h) (khint_t)(0) - -/*! @function - @abstract Get the end iterator - @param h Pointer to the hash table [khash_t(name)*] - @return The end iterator [khint_t] - */ -#define kh_end(h) ((h)->n_buckets) - -/*! @function - @abstract Get the number of elements in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @return Number of elements in the hash table [khint_t] - */ -#define kh_size(h) ((h)->size) - -/*! @function - @abstract Get the number of buckets in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @return Number of buckets in the hash table [khint_t] - */ -#define kh_n_buckets(h) ((h)->n_buckets) - -/*! @function - @abstract Iterate over the entries in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @param kvar Variable to which key will be assigned - @param vvar Variable to which value will be assigned - @param code Block of code to execute - */ -#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ - for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ - if (!kh_exist(h,__i)) continue; \ - (kvar) = kh_key(h,__i); \ - (vvar) = kh_val(h,__i); \ - code; \ - } } - -/*! @function - @abstract Iterate over the values in the hash table - @param h Pointer to the hash table [khash_t(name)*] - @param vvar Variable to which value will be assigned - @param code Block of code to execute - */ -#define kh_foreach_value(h, vvar, code) { khint_t __i; \ - for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ - if (!kh_exist(h,__i)) continue; \ - (vvar) = kh_val(h,__i); \ - code; \ - } } - -/* More convenient interfaces */ - -/*! @function - @abstract Instantiate a hash set containing integer keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_INT(name) \ - KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing integer keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_INT(name, khval_t) \ - KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) - -/*! @function - @abstract Instantiate a hash set containing 64-bit integer keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_INT64(name) \ - KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing 64-bit integer keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_INT64(name, khval_t) \ - KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) - -typedef const char *kh_cstr_t; -/*! @function - @abstract Instantiate a hash map containing const char* keys - @param name Name of the hash table [symbol] - */ -#define KHASH_SET_INIT_STR(name) \ - KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) - -/*! @function - @abstract Instantiate a hash map containing const char* keys - @param name Name of the hash table [symbol] - @param khval_t Type of values [type] - */ -#define KHASH_MAP_INIT_STR(name, khval_t) \ - KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) - -#endif /* __AC_KHASH_H */ diff --git a/files/config/suckless/st/kvec.h b/files/config/suckless/st/kvec.h deleted file mode 100644 index 10f1c5b..0000000 --- a/files/config/suckless/st/kvec.h +++ /dev/null @@ -1,90 +0,0 @@ -/* The MIT License - - Copyright (c) 2008, by Attractive Chaos <attractor@live.co.uk> - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -/* - An example: - -#include "kvec.h" -int main() { - kvec_t(int) array; - kv_init(array); - kv_push(int, array, 10); // append - kv_a(int, array, 20) = 5; // dynamic - kv_A(array, 20) = 4; // static - kv_destroy(array); - return 0; -} -*/ - -/* - 2008-09-22 (0.1.0): - - * The initial version. - -*/ - -#ifndef AC_KVEC_H -#define AC_KVEC_H - -#include <stdlib.h> - -#define kv_roundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) - -#define kvec_t(type) struct { size_t n, m; type *a; } -#define kv_init(v) ((v).n = (v).m = 0, (v).a = 0) -#define kv_destroy(v) free((v).a) -#define kv_A(v, i) ((v).a[(i)]) -#define kv_pop(v) ((v).a[--(v).n]) -#define kv_size(v) ((v).n) -#define kv_max(v) ((v).m) - -#define kv_resize(type, v, s) ((v).m = (s), (v).a = (type*)realloc((v).a, sizeof(type) * (v).m)) - -#define kv_copy(type, v1, v0) do { \ - if ((v1).m < (v0).n) kv_resize(type, v1, (v0).n); \ - (v1).n = (v0).n; \ - memcpy((v1).a, (v0).a, sizeof(type) * (v0).n); \ - } while (0) \ - -#define kv_push(type, v, x) do { \ - if ((v).n == (v).m) { \ - (v).m = (v).m? (v).m<<1 : 2; \ - (v).a = (type*)realloc((v).a, sizeof(type) * (v).m); \ - } \ - (v).a[(v).n++] = (x); \ - } while (0) - -#define kv_pushp(type, v) ((((v).n == (v).m)? \ - ((v).m = ((v).m? (v).m<<1 : 2), \ - (v).a = (type*)realloc((v).a, sizeof(type) * (v).m), 0) \ - : 0), ((v).a + ((v).n++))) - -#define kv_a(type, v, i) (((v).m <= (size_t)(i)? \ - ((v).m = (v).n = (i) + 1, kv_roundup32((v).m), \ - (v).a = (type*)realloc((v).a, sizeof(type) * (v).m), 0) \ - : (v).n <= (size_t)(i)? (v).n = (i) + 1 \ - : 0), (v).a[(i)]) - -#endif diff --git a/files/config/suckless/st/patches/st-kitty-graphics-20240922-a0274bc.diff b/files/config/suckless/st/patches/st-kitty-graphics-20240922-a0274bc.diff deleted file mode 100644 index 6049fe2..0000000 --- a/files/config/suckless/st/patches/st-kitty-graphics-20240922-a0274bc.diff +++ /dev/null @@ -1,7324 +0,0 @@ -From 25d9cca81ce48141de7f6a823b006dddaafd9de8 Mon Sep 17 00:00:00 2001 -From: Sergei Grechanik <sergei.grechanik@gmail.com> -Date: Sun, 22 Sep 2024 08:36:05 -0700 -Subject: [PATCH] Kitty graphics protocol support 7b717e3 2024-09-22 - -This patch implements the kitty graphics protocol in st. -See https://github.com/sergei-grechanik/st-graphics -Created by squashing the graphics branch, the most recent -commit is 7b717e38b1739e11356b34df9fdfdfa339960864 (2024-09-22). - -Squashed on top of a0274bc20e11d8672bb2953fdd1d3010c0e708c5 - -Note that the following files were excluded from the squash: - .clang-format - README.md - generate-rowcolumn-helpers.py - rowcolumn-diacritics.txt - rowcolumn_diacritics.sh ---- - Makefile | 4 +- - config.def.h | 46 +- - config.mk | 5 +- - graphics.c | 3812 ++++++++++++++++++++++++++++++++ - graphics.h | 107 + - icat-mini.sh | 801 +++++++ - khash.h | 627 ++++++ - kvec.h | 90 + - rowcolumn_diacritics_helpers.c | 391 ++++ - st.c | 279 ++- - st.h | 84 +- - st.info | 6 + - win.h | 3 + - x.c | 411 +++- - 14 files changed, 6615 insertions(+), 51 deletions(-) - create mode 100644 graphics.c - create mode 100644 graphics.h - create mode 100755 icat-mini.sh - create mode 100644 khash.h - create mode 100644 kvec.h - create mode 100644 rowcolumn_diacritics_helpers.c - -diff --git a/Makefile b/Makefile -index 15db421..e79b89e 100644 ---- a/Makefile -+++ b/Makefile -@@ -4,7 +4,7 @@ - - include config.mk - --SRC = st.c x.c -+SRC = st.c x.c rowcolumn_diacritics_helpers.c graphics.c - OBJ = $(SRC:.c=.o) - - all: st -@@ -16,7 +16,7 @@ config.h: - $(CC) $(STCFLAGS) -c $< - - st.o: config.h st.h win.h --x.o: arg.h config.h st.h win.h -+x.o: arg.h config.h st.h win.h graphics.h - - $(OBJ): config.h config.mk - -diff --git a/config.def.h b/config.def.h -index 2cd740a..4aadbbc 100644 ---- a/config.def.h -+++ b/config.def.h -@@ -8,6 +8,13 @@ - static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; - static int borderpx = 2; - -+/* How to align the content in the window when the size of the terminal -+ * doesn't perfectly match the size of the window. The values are percentages. -+ * 50 means center, 0 means flush left/top, 100 means flush right/bottom. -+ */ -+static int anysize_halign = 50; -+static int anysize_valign = 50; -+ - /* - * What program is execed by st depends of these precedence rules: - * 1: program passed with -e -@@ -23,7 +30,8 @@ char *scroll = NULL; - char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; - - /* identification sequence returned in DA and DECID */ --char *vtiden = "\033[?6c"; -+/* By default, use the same one as kitty. */ -+char *vtiden = "\033[?62c"; - - /* Kerning / character bounding-box multipliers */ - static float cwscale = 1.0; -@@ -163,6 +171,28 @@ static unsigned int mousebg = 0; - */ - static unsigned int defaultattr = 11; - -+/* -+ * Graphics configuration -+ */ -+ -+/// The template for the cache directory. -+const char graphics_cache_dir_template[] = "/tmp/st-images-XXXXXX"; -+/// The max size of a single image file, in bytes. -+unsigned graphics_max_single_image_file_size = 20 * 1024 * 1024; -+/// The max size of the cache, in bytes. -+unsigned graphics_total_file_cache_size = 300 * 1024 * 1024; -+/// The max ram size of an image or placement, in bytes. -+unsigned graphics_max_single_image_ram_size = 100 * 1024 * 1024; -+/// The max total size of all images loaded into RAM. -+unsigned graphics_max_total_ram_size = 300 * 1024 * 1024; -+/// The max total number of image placements and images. -+unsigned graphics_max_total_placements = 4096; -+/// The ratio by which limits can be exceeded. This is to reduce the frequency -+/// of image removal. -+double graphics_excess_tolerance_ratio = 0.05; -+/// The minimum delay between redraws caused by animations, in milliseconds. -+unsigned graphics_animation_min_delay = 20; -+ - /* - * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). - * Note that if you want to use ShiftMask with selmasks, set this to an other -@@ -170,12 +200,18 @@ static unsigned int defaultattr = 11; - */ - static uint forcemousemod = ShiftMask; - -+/* Internal keyboard shortcuts. */ -+#define MODKEY Mod1Mask -+#define TERMMOD (ControlMask|ShiftMask) -+ - /* - * Internal mouse shortcuts. - * Beware that overloading Button1 will disable the selection. - */ - static MouseShortcut mshortcuts[] = { - /* mask button function argument release */ -+ { TERMMOD, Button3, previewimage, {.s = "feh"} }, -+ { TERMMOD, Button2, showimageinfo, {}, 1 }, - { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, - { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, - { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, -@@ -183,10 +219,6 @@ static MouseShortcut mshortcuts[] = { - { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, - }; - --/* Internal keyboard shortcuts. */ --#define MODKEY Mod1Mask --#define TERMMOD (ControlMask|ShiftMask) -- - static Shortcut shortcuts[] = { - /* mask keysym function argument */ - { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, -@@ -201,6 +233,10 @@ static Shortcut shortcuts[] = { - { TERMMOD, XK_Y, selpaste, {.i = 0} }, - { ShiftMask, XK_Insert, selpaste, {.i = 0} }, - { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, -+ { TERMMOD, XK_F1, togglegrdebug, {.i = 0} }, -+ { TERMMOD, XK_F6, dumpgrstate, {.i = 0} }, -+ { TERMMOD, XK_F7, unloadimages, {.i = 0} }, -+ { TERMMOD, XK_F8, toggleimages, {.i = 0} }, - }; - - /* -diff --git a/config.mk b/config.mk -index fdc29a7..cb2875c 100644 ---- a/config.mk -+++ b/config.mk -@@ -14,9 +14,12 @@ PKG_CONFIG = pkg-config - - # includes and libs - INCS = -I$(X11INC) \ -+ `$(PKG_CONFIG) --cflags imlib2` \ - `$(PKG_CONFIG) --cflags fontconfig` \ - `$(PKG_CONFIG) --cflags freetype2` --LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ -+LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ -+ `$(PKG_CONFIG) --libs imlib2` \ -+ `$(PKG_CONFIG) --libs zlib` \ - `$(PKG_CONFIG) --libs fontconfig` \ - `$(PKG_CONFIG) --libs freetype2` - -diff --git a/graphics.c b/graphics.c -new file mode 100644 -index 0000000..64e6fe0 ---- /dev/null -+++ b/graphics.c -@@ -0,0 +1,3812 @@ -+/* The MIT License -+ -+ Copyright (c) 2021-2024 Sergei Grechanik <sergei.grechanik@gmail.com> -+ -+ Permission is hereby granted, free of charge, to any person obtaining -+ a copy of this software and associated documentation files (the -+ "Software"), to deal in the Software without restriction, including -+ without limitation the rights to use, copy, modify, merge, publish, -+ distribute, sublicense, and/or sell copies of the Software, and to -+ permit persons to whom the Software is furnished to do so, subject to -+ the following conditions: -+ -+ The above copyright notice and this permission notice shall be -+ included in all copies or substantial portions of the Software. -+ -+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -+ SOFTWARE. -+*/ -+ -+//////////////////////////////////////////////////////////////////////////////// -+// -+// This file implements a subset of the kitty graphics protocol. -+// -+//////////////////////////////////////////////////////////////////////////////// -+ -+#define _POSIX_C_SOURCE 200809L -+ -+#include <zlib.h> -+#include <Imlib2.h> -+#include <X11/Xlib.h> -+#include <X11/extensions/Xrender.h> -+#include <assert.h> -+#include <ctype.h> -+#include <spawn.h> -+#include <stdarg.h> -+#include <stdio.h> -+#include <stdlib.h> -+#include <string.h> -+#include <sys/stat.h> -+#include <time.h> -+#include <unistd.h> -+#include <errno.h> -+ -+#include "graphics.h" -+#include "khash.h" -+#include "kvec.h" -+ -+extern char **environ; -+ -+#define MAX_FILENAME_SIZE 256 -+#define MAX_INFO_LEN 256 -+#define MAX_IMAGE_RECTS 20 -+ -+/// The type used in this file to represent time. Used both for time differences -+/// and absolute times (as milliseconds since an arbitrary point in time, see -+/// `initialization_time`). -+typedef int64_t Milliseconds; -+ -+enum ScaleMode { -+ SCALE_MODE_UNSET = 0, -+ /// Stretch or shrink the image to fill the box, ignoring aspect ratio. -+ SCALE_MODE_FILL = 1, -+ /// Preserve aspect ratio and fit to width or to height so that the -+ /// whole image is visible. -+ SCALE_MODE_CONTAIN = 2, -+ /// Do not scale. The image may be cropped if the box is too small. -+ SCALE_MODE_NONE = 3, -+ /// Do not scale, unless the box is too small, in which case the image -+ /// will be shrunk like with `SCALE_MODE_CONTAIN`. -+ SCALE_MODE_NONE_OR_CONTAIN = 4, -+}; -+ -+enum AnimationState { -+ ANIMATION_STATE_UNSET = 0, -+ /// The animation is stopped. Display the current frame, but don't -+ /// advance to the next one. -+ ANIMATION_STATE_STOPPED = 1, -+ /// Run the animation to then end, then wait for the next frame. -+ ANIMATION_STATE_LOADING = 2, -+ /// Run the animation in a loop. -+ ANIMATION_STATE_LOOPING = 3, -+}; -+ -+/// The status of an image. Each image uploaded to the terminal is cached on -+/// disk, then it is loaded to ram when needed. -+enum ImageStatus { -+ STATUS_UNINITIALIZED = 0, -+ STATUS_UPLOADING = 1, -+ STATUS_UPLOADING_ERROR = 2, -+ STATUS_UPLOADING_SUCCESS = 3, -+ STATUS_RAM_LOADING_ERROR = 4, -+ STATUS_RAM_LOADING_IN_PROGRESS = 5, -+ STATUS_RAM_LOADING_SUCCESS = 6, -+}; -+ -+const char *image_status_strings[6] = { -+ "STATUS_UNINITIALIZED", -+ "STATUS_UPLOADING", -+ "STATUS_UPLOADING_ERROR", -+ "STATUS_UPLOADING_SUCCESS", -+ "STATUS_RAM_LOADING_ERROR", -+ "STATUS_RAM_LOADING_SUCCESS", -+}; -+ -+enum ImageUploadingFailure { -+ ERROR_OVER_SIZE_LIMIT = 1, -+ ERROR_CANNOT_OPEN_CACHED_FILE = 2, -+ ERROR_UNEXPECTED_SIZE = 3, -+ ERROR_CANNOT_COPY_FILE = 4, -+}; -+ -+const char *image_uploading_failure_strings[5] = { -+ "NO_ERROR", -+ "ERROR_OVER_SIZE_LIMIT", -+ "ERROR_CANNOT_OPEN_CACHED_FILE", -+ "ERROR_UNEXPECTED_SIZE", -+ "ERROR_CANNOT_COPY_FILE", -+}; -+ -+//////////////////////////////////////////////////////////////////////////////// -+// -+// We use the following structures to represent images and placements: -+// -+// - Image: this is the main structure representing an image, usually created -+// by actions 'a=t', 'a=T`. Each image has an id (image id aka client id, -+// specified by 'i='). An image may have multiple frames (ImageFrame) and -+// placements (ImagePlacement). -+// -+// - ImageFrame: represents a single frame of an image, usually created by -+// the action 'a=f' (and the first frame is created with the image itself). -+// Each frame has an index and also: -+// - a file containing the frame data (considered to be "on disk", although -+// it's probably in tmpfs), -+// - an imlib object containing the fully composed frame (i.e. the frame -+// data from the file composed onto the background frame or color). It is -+// not ready for display yet, because it needs to be scaled and uploaded -+// to the X server. -+// -+// - ImagePlacement: represents a placement of an image, created by 'a=p' and -+// 'a=T'. Each placement has an id (placement id, specified by 'p='). Also -+// each placement has an array of pixmaps: one for each frame of the image. -+// Each pixmap is a scaled and uploaded image ready to be displayed. -+// -+// Images are store in the `images` hash table, mapping image ids to Image -+// objects (allocated on the heap). -+// -+// Placements are stored in the `placements` hash table of each Image object, -+// mapping placement ids to ImagePlacement objects (also allocated on the heap). -+// -+// ImageFrames are stored in the `first_frame` field and in the -+// `frames_beyond_the_first` array of each Image object. They are stored by -+// value, so ImageFrame pointer may be invalidated when frames are -+// added/deleted, be careful. -+// -+//////////////////////////////////////////////////////////////////////////////// -+ -+struct Image; -+struct ImageFrame; -+struct ImagePlacement; -+ -+KHASH_MAP_INIT_INT(id2image, struct Image *) -+KHASH_MAP_INIT_INT(id2placement, struct ImagePlacement *) -+ -+typedef struct ImageFrame { -+ /// The image this frame belongs to. -+ struct Image *image; -+ /// The 1-based index of the frame. Zero if the frame isn't initialized. -+ int index; -+ /// The last time when the frame was displayed or otherwise touched. -+ Milliseconds atime; -+ /// The background color of the frame in the 0xRRGGBBAA format. -+ uint32_t background_color; -+ /// The index of the background frame. Zero to use the color instead. -+ int background_frame_index; -+ /// The duration of the frame in milliseconds. -+ int gap; -+ /// The expected size of the frame image file (specified with 'S='), -+ /// used to check if uploading succeeded. -+ unsigned expected_size; -+ /// Format specification (see the `f=` key). -+ int format; -+ /// Pixel width and height of the non-composed (on-disk) frame data. May -+ /// differ from the image (i.e. first frame) dimensions. -+ int data_pix_width, data_pix_height; -+ /// The offset of the frame relative to the first frame. -+ int x, y; -+ /// Compression mode (see the `o=` key). -+ char compression; -+ /// The status (see `ImageStatus`). -+ char status; -+ /// The reason of uploading failure (see `ImageUploadingFailure`). -+ char uploading_failure; -+ /// Whether failures and successes should be reported ('q='). -+ char quiet; -+ /// Whether to blend the frame with the background or replace it. -+ char blend; -+ /// The file corresponding to the on-disk cache, used when uploading. -+ FILE *open_file; -+ /// The size of the corresponding file cached on disk. -+ unsigned disk_size; -+ /// The imlib object containing the fully composed frame. It's not -+ /// scaled for screen display yet. -+ Imlib_Image imlib_object; -+} ImageFrame; -+ -+typedef struct Image { -+ /// The client id (the one specified with 'i='). Must be nonzero. -+ uint32_t image_id; -+ /// The client id specified in the query command (`a=q`). This one must -+ /// be used to create the response if it's non-zero. -+ uint32_t query_id; -+ /// The number specified in the transmission command (`I=`). If -+ /// non-zero, it may be used to identify the image instead of the -+ /// image_id, and it also should be mentioned in responses. -+ uint32_t image_number; -+ /// The last time when the image was displayed or otherwise touched. -+ Milliseconds atime; -+ /// The total duration of the animation in milliseconds. -+ int total_duration; -+ /// The total size of cached image files for all frames. -+ int total_disk_size; -+ /// The global index of the creation command. Used to decide which image -+ /// is newer if they have the same image number. -+ uint64_t global_command_index; -+ /// The 1-based index of the currently displayed frame. -+ int current_frame; -+ /// The state of the animation, see `AnimationState`. -+ char animation_state; -+ /// The absolute time that is assumed to be the start of the current -+ /// frame (in ms since initialization). -+ Milliseconds current_frame_time; -+ /// The absolute time of the last redraw (in ms since initialization). -+ /// Used to check whether it's the first time we draw the image in the -+ /// current redraw cycle. -+ Milliseconds last_redraw; -+ /// The absolute time of the next redraw (in ms since initialization). -+ /// 0 means no redraw is scheduled. -+ Milliseconds next_redraw; -+ /// The unscaled pixel width and height of the image. Usually inherited -+ /// from the first frame. -+ int pix_width, pix_height; -+ /// The first frame. -+ ImageFrame first_frame; -+ /// The array of frames beyond the first one. -+ kvec_t(ImageFrame) frames_beyond_the_first; -+ /// Image placements. -+ khash_t(id2placement) *placements; -+ /// The default placement. -+ uint32_t default_placement; -+ /// The initial placement id, specified with the transmission command, -+ /// used to report success or failure. -+ uint32_t initial_placement_id; -+} Image; -+ -+typedef struct ImagePlacement { -+ /// The image this placement belongs to. -+ Image *image; -+ /// The id of the placement. Must be nonzero. -+ uint32_t placement_id; -+ /// The last time when the placement was displayed or otherwise touched. -+ Milliseconds atime; -+ /// The 1-based index of the protected pixmap. We protect a pixmap in -+ /// gr_load_pixmap to avoid unloading it right after it was loaded. -+ int protected_frame; -+ /// Whether the placement is used only for Unicode placeholders. -+ char virtual; -+ /// The scaling mode (see `ScaleMode`). -+ char scale_mode; -+ /// Height and width in cells. -+ uint16_t rows, cols; -+ /// Top-left corner of the source rectangle ('x=' and 'y='). -+ int src_pix_x, src_pix_y; -+ /// Height and width of the source rectangle (zero if full image). -+ int src_pix_width, src_pix_height; -+ /// The image appropriately scaled and uploaded to the X server. This -+ /// pixmap is premultiplied by alpha. -+ Pixmap first_pixmap; -+ /// The array of pixmaps beyond the first one. -+ kvec_t(Pixmap) pixmaps_beyond_the_first; -+ /// The dimensions of the cell used to scale the image. If cell -+ /// dimensions are changed (font change), the image will be rescaled. -+ uint16_t scaled_cw, scaled_ch; -+ /// If true, do not move the cursor when displaying this placement -+ /// (non-virtual placements only). -+ char do_not_move_cursor; -+} ImagePlacement; -+ -+/// A rectangular piece of an image to be drawn. -+typedef struct { -+ uint32_t image_id; -+ uint32_t placement_id; -+ /// The position of the rectangle in pixels. -+ int screen_x_pix, screen_y_pix; -+ /// The starting row on the screen. -+ int screen_y_row; -+ /// The part of the whole image to be drawn, in cells. Starts are -+ /// zero-based, ends are exclusive. -+ int img_start_col, img_end_col, img_start_row, img_end_row; -+ /// The current cell width and height in pixels. -+ int cw, ch; -+ /// Whether colors should be inverted. -+ int reverse; -+} ImageRect; -+ -+/// Executes `code` for each frame of an image. Example: -+/// -+/// foreach_frame(image, frame, { -+/// printf("Frame %d\n", frame->index); -+/// }); -+/// -+#define foreach_frame(image, framevar, code) { size_t __i; \ -+ for (__i = 0; __i <= kv_size((image).frames_beyond_the_first); ++__i) { \ -+ ImageFrame *framevar = \ -+ __i == 0 ? &(image).first_frame \ -+ : &kv_A((image).frames_beyond_the_first, __i - 1); \ -+ code; \ -+ } } -+ -+/// Executes `code` for each pixmap of a placement. Example: -+/// -+/// foreach_pixmap(placement, pixmap, { -+/// ... -+/// }); -+/// -+#define foreach_pixmap(placement, pixmapvar, code) { size_t __i; \ -+ for (__i = 0; __i <= kv_size((placement).pixmaps_beyond_the_first); ++__i) { \ -+ Pixmap pixmapvar = \ -+ __i == 0 ? (placement).first_pixmap \ -+ : kv_A((placement).pixmaps_beyond_the_first, __i - 1); \ -+ code; \ -+ } } -+ -+ -+static Image *gr_find_image(uint32_t image_id); -+static void gr_get_frame_filename(ImageFrame *frame, char *out, size_t max_len); -+static void gr_delete_image(Image *img); -+static void gr_check_limits(); -+static char *gr_base64dec(const char *src, size_t *size); -+static void sanitize_str(char *str, size_t max_len); -+static const char *sanitized_filename(const char *str); -+ -+/// The array of image rectangles to draw. It is reset each frame. -+static ImageRect image_rects[MAX_IMAGE_RECTS] = {{0}}; -+/// The known images (including the ones being uploaded). -+static khash_t(id2image) *images = NULL; -+/// The total number of placements in all images. -+static unsigned total_placement_count = 0; -+/// The total size of all image files stored in the on-disk cache. -+static int64_t images_disk_size = 0; -+/// The total size of all images and placements loaded into ram. -+static int64_t images_ram_size = 0; -+/// The id of the last loaded image. -+static uint32_t last_image_id = 0; -+/// Current cell width and heigh in pixels. -+static int current_cw = 0, current_ch = 0; -+/// The id of the currently uploaded image (when using direct uploading). -+static uint32_t current_upload_image_id = 0; -+/// The index of the frame currently being uploaded. -+static int current_upload_frame_index = 0; -+/// The time when the graphics module was initialized. -+static struct timespec initialization_time = {0}; -+/// The time when the current frame drawing started, used for debugging fps and -+/// to calculate the current frame for animations. -+static Milliseconds drawing_start_time; -+/// The global index of the current command. -+static uint64_t global_command_counter = 0; -+/// The next redraw times for each row of the terminal. Used for animations. -+/// 0 means no redraw is scheduled. -+static kvec_t(Milliseconds) next_redraw_times = {0, 0, NULL}; -+/// The number of files loaded in the current redraw cycle. -+static int this_redraw_cycle_loaded_files = 0; -+/// The number of pixmaps loaded in the current redraw cycle. -+static int this_redraw_cycle_loaded_pixmaps = 0; -+ -+/// The directory where the cache files are stored. -+static char cache_dir[MAX_FILENAME_SIZE - 16]; -+ -+/// The table used for color inversion. -+static unsigned char reverse_table[256]; -+ -+// Declared in the header. -+GraphicsDebugMode graphics_debug_mode = GRAPHICS_DEBUG_NONE; -+char graphics_display_images = 1; -+GraphicsCommandResult graphics_command_result = {0}; -+int graphics_next_redraw_delay = INT_MAX; -+ -+// Defined in config.h -+extern const char graphics_cache_dir_template[]; -+extern unsigned graphics_max_single_image_file_size; -+extern unsigned graphics_total_file_cache_size; -+extern unsigned graphics_max_single_image_ram_size; -+extern unsigned graphics_max_total_ram_size; -+extern unsigned graphics_max_total_placements; -+extern double graphics_excess_tolerance_ratio; -+extern unsigned graphics_animation_min_delay; -+ -+ -+//////////////////////////////////////////////////////////////////////////////// -+// Basic helpers. -+//////////////////////////////////////////////////////////////////////////////// -+ -+#define MIN(a, b) ((a) < (b) ? (a) : (b)) -+#define MAX(a, b) ((a) < (b) ? (b) : (a)) -+ -+/// Returns the difference between `end` and `start` in milliseconds. -+static int64_t gr_timediff_ms(const struct timespec *end, -+ const struct timespec *start) { -+ return (end->tv_sec - start->tv_sec) * 1000 + -+ (end->tv_nsec - start->tv_nsec) / 1000000; -+} -+ -+/// Returns the current time in milliseconds since the initialization. -+static Milliseconds gr_now_ms() { -+ struct timespec now; -+ clock_gettime(CLOCK_MONOTONIC, &now); -+ return gr_timediff_ms(&now, &initialization_time); -+} -+ -+//////////////////////////////////////////////////////////////////////////////// -+// Logging. -+//////////////////////////////////////////////////////////////////////////////// -+ -+#define GR_LOG(...) \ -+ do { if(graphics_debug_mode) fprintf(stderr, __VA_ARGS__); } while(0) -+ -+//////////////////////////////////////////////////////////////////////////////// -+// Basic image management functions (create, delete, find, etc). -+//////////////////////////////////////////////////////////////////////////////// -+ -+/// Returns the 1-based index of the last frame. Note that you may want to use -+/// `gr_last_uploaded_frame_index` instead since the last frame may be not -+/// fully uploaded yet. -+static inline int gr_last_frame_index(Image *img) { -+ return kv_size(img->frames_beyond_the_first) + 1; -+} -+ -+/// Returns the frame with the given index. Returns NULL if the index is out of -+/// bounds. The index is 1-based. -+static ImageFrame *gr_get_frame(Image *img, int index) { -+ if (!img) -+ return NULL; -+ if (index == 1) -+ return &img->first_frame; -+ if (2 <= index && index <= gr_last_frame_index(img)) -+ return &kv_A(img->frames_beyond_the_first, index - 2); -+ return NULL; -+} -+ -+/// Returns the last frame of the image. Returns NULL if `img` is NULL. -+static ImageFrame *gr_get_last_frame(Image *img) { -+ if (!img) -+ return NULL; -+ return gr_get_frame(img, gr_last_frame_index(img)); -+} -+ -+/// Returns the 1-based index of the last frame or the second-to-last frame if -+/// the last frame is not fully uploaded yet. -+static inline int gr_last_uploaded_frame_index(Image *img) { -+ int last_index = gr_last_frame_index(img); -+ if (last_index > 1 && -+ gr_get_frame(img, last_index)->status < STATUS_UPLOADING_SUCCESS) -+ return last_index - 1; -+ return last_index; -+} -+ -+/// Returns the pixmap for the frame with the given index. Returns 0 if the -+/// index is out of bounds. The index is 1-based. -+static Pixmap gr_get_frame_pixmap(ImagePlacement *placement, int index) { -+ if (index == 1) -+ return placement->first_pixmap; -+ if (2 <= index && -+ index <= kv_size(placement->pixmaps_beyond_the_first) + 1) -+ return kv_A(placement->pixmaps_beyond_the_first, index - 2); -+ return 0; -+} -+ -+/// Sets the pixmap for the frame with the given index. The index is 1-based. -+/// The array of pixmaps is resized if needed. -+static void gr_set_frame_pixmap(ImagePlacement *placement, int index, -+ Pixmap pixmap) { -+ if (index == 1) { -+ placement->first_pixmap = pixmap; -+ return; -+ } -+ // Resize the array if needed. -+ size_t old_size = kv_size(placement->pixmaps_beyond_the_first); -+ if (old_size < index - 1) { -+ kv_a(Pixmap, placement->pixmaps_beyond_the_first, index - 2); -+ for (size_t i = old_size; i < index - 1; i++) -+ kv_A(placement->pixmaps_beyond_the_first, i) = 0; -+ } -+ kv_A(placement->pixmaps_beyond_the_first, index - 2) = pixmap; -+} -+ -+/// Finds the image corresponding to the client id. Returns NULL if cannot find. -+static Image *gr_find_image(uint32_t image_id) { -+ khiter_t k = kh_get(id2image, images, image_id); -+ if (k == kh_end(images)) -+ return NULL; -+ Image *res = kh_value(images, k); -+ return res; -+} -+ -+/// Finds the newest image corresponding to the image number. Returns NULL if -+/// cannot find. -+static Image *gr_find_image_by_number(uint32_t image_number) { -+ if (image_number == 0) -+ return NULL; -+ Image *newest_img = NULL; -+ Image *img = NULL; -+ kh_foreach_value(images, img, { -+ if (img->image_number == image_number && -+ (!newest_img || newest_img->global_command_index < -+ img->global_command_index)) -+ newest_img = img; -+ }); -+ if (!newest_img) -+ GR_LOG("Image number %u not found\n", image_number); -+ else -+ GR_LOG("Found image number %u, its id is %u\n", image_number, -+ img->image_id); -+ return newest_img; -+} -+ -+/// Finds the placement corresponding to the id. If the placement id is 0, -+/// returns some default placement. -+static ImagePlacement *gr_find_placement(Image *img, uint32_t placement_id) { -+ if (!img) -+ return NULL; -+ if (placement_id == 0) { -+ // Try to get the default placement. -+ ImagePlacement *dflt = NULL; -+ if (img->default_placement != 0) -+ dflt = gr_find_placement(img, img->default_placement); -+ if (dflt) -+ return dflt; -+ // If there is no default placement, return the first one and -+ // set it as the default. -+ kh_foreach_value(img->placements, dflt, { -+ img->default_placement = dflt->placement_id; -+ return dflt; -+ }); -+ // If there are no placements, return NULL. -+ return NULL; -+ } -+ khiter_t k = kh_get(id2placement, img->placements, placement_id); -+ if (k == kh_end(img->placements)) -+ return NULL; -+ ImagePlacement *res = kh_value(img->placements, k); -+ return res; -+} -+ -+/// Finds the placement by image id and placement id. -+static ImagePlacement *gr_find_image_and_placement(uint32_t image_id, -+ uint32_t placement_id) { -+ return gr_find_placement(gr_find_image(image_id), placement_id); -+} -+ -+/// Writes the name of the on-disk cache file to `out`. `max_len` should be the -+/// size of `out`. The name will be something like -+/// "/tmp/st-images-xxx/img-ID-FRAME". -+static void gr_get_frame_filename(ImageFrame *frame, char *out, -+ size_t max_len) { -+ snprintf(out, max_len, "%s/img-%.3u-%.3u", cache_dir, -+ frame->image->image_id, frame->index); -+} -+ -+/// Returns the (estimation) of the RAM size used by the frame right now. -+static unsigned gr_frame_current_ram_size(ImageFrame *frame) { -+ if (!frame->imlib_object) -+ return 0; -+ return (unsigned)frame->image->pix_width * frame->image->pix_height * 4; -+} -+ -+/// Returns the (estimation) of the RAM size used by a single frame pixmap. -+static unsigned gr_placement_single_frame_ram_size(ImagePlacement *placement) { -+ return (unsigned)placement->rows * placement->cols * -+ placement->scaled_ch * placement->scaled_cw * 4; -+} -+ -+/// Returns the (estimation) of the RAM size used by the placemenet right now. -+static unsigned gr_placement_current_ram_size(ImagePlacement *placement) { -+ unsigned single_frame_size = -+ gr_placement_single_frame_ram_size(placement); -+ unsigned result = 0; -+ foreach_pixmap(*placement, pixmap, { -+ if (pixmap) -+ result += single_frame_size; -+ }); -+ return result; -+} -+ -+/// Unload the frame from RAM (i.e. delete the corresponding imlib object). -+/// If the on-disk file of the frame is preserved, it can be reloaded later. -+static void gr_unload_frame(ImageFrame *frame) { -+ if (!frame->imlib_object) -+ return; -+ -+ unsigned frame_ram_size = gr_frame_current_ram_size(frame); -+ images_ram_size -= frame_ram_size; -+ -+ imlib_context_set_image(frame->imlib_object); -+ imlib_free_image_and_decache(); -+ frame->imlib_object = NULL; -+ -+ GR_LOG("After unloading image %u frame %u (atime %ld ms ago) " -+ "ram: %ld KiB (- %u KiB)\n", -+ frame->image->image_id, frame->index, -+ drawing_start_time - frame->atime, images_ram_size / 1024, -+ frame_ram_size / 1024); -+} -+ -+/// Unload all frames of the image. -+static void gr_unload_all_frames(Image *img) { -+ foreach_frame(*img, frame, { -+ gr_unload_frame(frame); -+ }); -+} -+ -+/// Unload the placement from RAM (i.e. free all of the corresponding pixmaps). -+/// If the on-disk files or imlib objects of the corresponding image are -+/// preserved, the placement can be reloaded later. -+static void gr_unload_placement(ImagePlacement *placement) { -+ unsigned placement_ram_size = gr_placement_current_ram_size(placement); -+ images_ram_size -= placement_ram_size; -+ -+ Display *disp = imlib_context_get_display(); -+ foreach_pixmap(*placement, pixmap, { -+ if (pixmap) -+ XFreePixmap(disp, pixmap); -+ }); -+ -+ placement->first_pixmap = 0; -+ placement->pixmaps_beyond_the_first.n = 0; -+ placement->scaled_ch = placement->scaled_cw = 0; -+ -+ GR_LOG("After unloading placement %u/%u (atime %ld ms ago) " -+ "ram: %ld KiB (- %u KiB)\n", -+ placement->image->image_id, placement->placement_id, -+ drawing_start_time - placement->atime, images_ram_size / 1024, -+ placement_ram_size / 1024); -+} -+ -+/// Unload a single pixmap of the placement from RAM. -+static void gr_unload_pixmap(ImagePlacement *placement, int frameidx) { -+ Pixmap pixmap = gr_get_frame_pixmap(placement, frameidx); -+ if (!pixmap) -+ return; -+ -+ Display *disp = imlib_context_get_display(); -+ XFreePixmap(disp, pixmap); -+ gr_set_frame_pixmap(placement, frameidx, 0); -+ images_ram_size -= gr_placement_single_frame_ram_size(placement); -+ -+ GR_LOG("After unloading pixmap %ld of " -+ "placement %u/%u (atime %ld ms ago) " -+ "frame %u (atime %ld ms ago) " -+ "ram: %ld KiB (- %u KiB)\n", -+ pixmap, placement->image->image_id, placement->placement_id, -+ drawing_start_time - placement->atime, frameidx, -+ drawing_start_time - -+ gr_get_frame(placement->image, frameidx)->atime, -+ images_ram_size / 1024, -+ gr_placement_single_frame_ram_size(placement) / 1024); -+} -+ -+/// Deletes the on-disk cache file corresponding to the frame. The in-ram image -+/// object (if it exists) is not deleted, placements are not unloaded either. -+static void gr_delete_imagefile(ImageFrame *frame) { -+ // It may still be being loaded. Close the file in this case. -+ if (frame->open_file) { -+ fclose(frame->open_file); -+ frame->open_file = NULL; -+ } -+ -+ if (frame->disk_size == 0) -+ return; -+ -+ char filename[MAX_FILENAME_SIZE]; -+ gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); -+ remove(filename); -+ -+ unsigned disk_size = frame->disk_size; -+ images_disk_size -= disk_size; -+ frame->image->total_disk_size -= disk_size; -+ frame->disk_size = 0; -+ -+ GR_LOG("After deleting image file %u frame %u (atime %ld ms ago) " -+ "disk: %ld KiB (- %u KiB)\n", -+ frame->image->image_id, frame->index, -+ drawing_start_time - frame->atime, images_disk_size / 1024, -+ disk_size / 1024); -+} -+ -+/// Deletes all on-disk cache files of the image (for each frame). -+static void gr_delete_imagefiles(Image *img) { -+ foreach_frame(*img, frame, { -+ gr_delete_imagefile(frame); -+ }); -+} -+ -+/// Deletes the given placement: unloads, frees the object, but doesn't change -+/// the `placements` hash table. -+static void gr_delete_placement_keep_id(ImagePlacement *placement) { -+ if (!placement) -+ return; -+ GR_LOG("Deleting placement %u/%u\n", placement->image->image_id, -+ placement->placement_id); -+ gr_unload_placement(placement); -+ kv_destroy(placement->pixmaps_beyond_the_first); -+ free(placement); -+ total_placement_count--; -+} -+ -+/// Deletes all placements of `img`. -+static void gr_delete_all_placements(Image *img) { -+ ImagePlacement *placement = NULL; -+ kh_foreach_value(img->placements, placement, { -+ gr_delete_placement_keep_id(placement); -+ }); -+ kh_clear(id2placement, img->placements); -+} -+ -+/// Deletes the given image: unloads, deletes the file, frees the Image object, -+/// but doesn't change the `images` hash table. -+static void gr_delete_image_keep_id(Image *img) { -+ if (!img) -+ return; -+ GR_LOG("Deleting image %u\n", img->image_id); -+ foreach_frame(*img, frame, { -+ gr_delete_imagefile(frame); -+ gr_unload_frame(frame); -+ }); -+ kv_destroy(img->frames_beyond_the_first); -+ gr_delete_all_placements(img); -+ kh_destroy(id2placement, img->placements); -+ free(img); -+} -+ -+/// Deletes the given image: unloads, deletes the file, frees the Image object, -+/// and also removes it from `images`. -+static void gr_delete_image(Image *img) { -+ if (!img) -+ return; -+ uint32_t id = img->image_id; -+ gr_delete_image_keep_id(img); -+ khiter_t k = kh_get(id2image, images, id); -+ kh_del(id2image, images, k); -+} -+ -+/// Deletes the given placement: unloads, frees the object, and also removes it -+/// from `placements`. -+static void gr_delete_placement(ImagePlacement *placement) { -+ if (!placement) -+ return; -+ uint32_t id = placement->placement_id; -+ Image *img = placement->image; -+ gr_delete_placement_keep_id(placement); -+ khiter_t k = kh_get(id2placement, img->placements, id); -+ kh_del(id2placement, img->placements, k); -+} -+ -+/// Deletes all images and clears `images`. -+static void gr_delete_all_images() { -+ Image *img = NULL; -+ kh_foreach_value(images, img, { -+ gr_delete_image_keep_id(img); -+ }); -+ kh_clear(id2image, images); -+} -+ -+/// Update the atime of the image. -+static void gr_touch_image(Image *img) { -+ img->atime = gr_now_ms(); -+} -+ -+/// Update the atime of the frame. -+static void gr_touch_frame(ImageFrame *frame) { -+ frame->image->atime = frame->atime = gr_now_ms(); -+} -+ -+/// Update the atime of the placement. Touches the images too. -+static void gr_touch_placement(ImagePlacement *placement) { -+ placement->image->atime = placement->atime = gr_now_ms(); -+} -+ -+/// Creates a new image with the given id. If an image with that id already -+/// exists, it is deleted first. If the provided id is 0, generates a -+/// random id. -+static Image *gr_new_image(uint32_t id) { -+ if (id == 0) { -+ do { -+ id = rand(); -+ // Avoid IDs that don't need full 32 bits. -+ } while ((id & 0xFF000000) == 0 || (id & 0x00FFFF00) == 0 || -+ gr_find_image(id)); -+ GR_LOG("Generated random image id %u\n", id); -+ } -+ Image *img = gr_find_image(id); -+ gr_delete_image_keep_id(img); -+ GR_LOG("Creating image %u\n", id); -+ img = malloc(sizeof(Image)); -+ memset(img, 0, sizeof(Image)); -+ img->placements = kh_init(id2placement); -+ int ret; -+ khiter_t k = kh_put(id2image, images, id, &ret); -+ kh_value(images, k) = img; -+ img->image_id = id; -+ gr_touch_image(img); -+ img->global_command_index = global_command_counter; -+ return img; -+} -+ -+/// Creates a new frame at the end of the frame array. It may be the first frame -+/// if there are no frames yet. -+static ImageFrame *gr_append_new_frame(Image *img) { -+ ImageFrame *frame = NULL; -+ if (img->first_frame.index == 0 && -+ kv_size(img->frames_beyond_the_first) == 0) { -+ frame = &img->first_frame; -+ frame->index = 1; -+ } else { -+ frame = kv_pushp(ImageFrame, img->frames_beyond_the_first); -+ memset(frame, 0, sizeof(ImageFrame)); -+ frame->index = kv_size(img->frames_beyond_the_first) + 1; -+ } -+ frame->image = img; -+ gr_touch_frame(frame); -+ GR_LOG("Appending frame %d to image %u\n", frame->index, img->image_id); -+ return frame; -+} -+ -+/// Creates a new placement with the given id. If a placement with that id -+/// already exists, it is deleted first. If the provided id is 0, generates a -+/// random id. -+static ImagePlacement *gr_new_placement(Image *img, uint32_t id) { -+ if (id == 0) { -+ do { -+ // Currently we support only 24-bit IDs. -+ id = rand() & 0xFFFFFF; -+ // Avoid IDs that need only one byte. -+ } while ((id & 0x00FFFF00) == 0 || gr_find_placement(img, id)); -+ } -+ ImagePlacement *placement = gr_find_placement(img, id); -+ gr_delete_placement_keep_id(placement); -+ GR_LOG("Creating placement %u/%u\n", img->image_id, id); -+ placement = malloc(sizeof(ImagePlacement)); -+ memset(placement, 0, sizeof(ImagePlacement)); -+ total_placement_count++; -+ int ret; -+ khiter_t k = kh_put(id2placement, img->placements, id, &ret); -+ kh_value(img->placements, k) = placement; -+ placement->image = img; -+ placement->placement_id = id; -+ gr_touch_placement(placement); -+ if (img->default_placement == 0) -+ img->default_placement = id; -+ return placement; -+} -+ -+static int64_t ceil_div(int64_t a, int64_t b) { -+ return (a + b - 1) / b; -+} -+ -+/// Computes the best number of rows and columns for a placement if it's not -+/// specified, and also adjusts the source rectangle size. -+static void gr_infer_placement_size_maybe(ImagePlacement *placement) { -+ // The size of the image. -+ int image_pix_width = placement->image->pix_width; -+ int image_pix_height = placement->image->pix_height; -+ // Negative values are not allowed. Quietly set them to 0. -+ if (placement->src_pix_x < 0) -+ placement->src_pix_x = 0; -+ if (placement->src_pix_y < 0) -+ placement->src_pix_y = 0; -+ if (placement->src_pix_width < 0) -+ placement->src_pix_width = 0; -+ if (placement->src_pix_height < 0) -+ placement->src_pix_height = 0; -+ // If the source rectangle is outside the image, truncate it. -+ if (placement->src_pix_x > image_pix_width) -+ placement->src_pix_x = image_pix_width; -+ if (placement->src_pix_y > image_pix_height) -+ placement->src_pix_y = image_pix_height; -+ // If the source rectangle is not specified, use the whole image. If -+ // it's partially outside the image, truncate it. -+ if (placement->src_pix_width == 0 || -+ placement->src_pix_x + placement->src_pix_width > image_pix_width) -+ placement->src_pix_width = -+ image_pix_width - placement->src_pix_x; -+ if (placement->src_pix_height == 0 || -+ placement->src_pix_y + placement->src_pix_height > image_pix_height) -+ placement->src_pix_height = -+ image_pix_height - placement->src_pix_y; -+ -+ if (placement->cols != 0 && placement->rows != 0) -+ return; -+ if (placement->src_pix_width == 0 || placement->src_pix_height == 0) -+ return; -+ if (current_cw == 0 || current_ch == 0) -+ return; -+ -+ // If no size is specified, use the image size. -+ if (placement->cols == 0 && placement->rows == 0) { -+ placement->cols = -+ ceil_div(placement->src_pix_width, current_cw); -+ placement->rows = -+ ceil_div(placement->src_pix_height, current_ch); -+ return; -+ } -+ -+ // Some applications specify only one of the dimensions. -+ if (placement->scale_mode == SCALE_MODE_CONTAIN) { -+ // If we preserve aspect ratio and fit to width/height, the most -+ // logical thing is to find the minimum size of the -+ // non-specified dimension that allows the image to fit the -+ // specified dimension. -+ if (placement->cols == 0) { -+ placement->cols = ceil_div( -+ placement->src_pix_width * placement->rows * -+ current_ch, -+ placement->src_pix_height * current_cw); -+ return; -+ } -+ if (placement->rows == 0) { -+ placement->rows = -+ ceil_div(placement->src_pix_height * -+ placement->cols * current_cw, -+ placement->src_pix_width * current_ch); -+ return; -+ } -+ } else { -+ // Otherwise we stretch the image or preserve the original size. -+ // In both cases we compute the best number of columns from the -+ // pixel size and cell size. -+ // TODO: In the case of stretching it's not the most logical -+ // thing to do, may need to revisit in the future. -+ // Currently we switch to SCALE_MODE_CONTAIN when only one -+ // of the dimensions is specified, so this case shouldn't -+ // happen in practice. -+ if (!placement->cols) -+ placement->cols = -+ ceil_div(placement->src_pix_width, current_cw); -+ if (!placement->rows) -+ placement->rows = -+ ceil_div(placement->src_pix_height, current_ch); -+ } -+} -+ -+/// Adjusts the current frame index if enough time has passed since the display -+/// of the current frame. Also computes the time of the next redraw of this -+/// image (`img->next_redraw`). The current time is passed as an argument so -+/// that all animations are in sync. -+static void gr_update_frame_index(Image *img, Milliseconds now) { -+ if (img->current_frame == 0) { -+ img->current_frame_time = now; -+ img->current_frame = 1; -+ img->next_redraw = now + MAX(1, img->first_frame.gap); -+ return; -+ } -+ // If the animation is stopped, show the current frame. -+ if (!img->animation_state || -+ img->animation_state == ANIMATION_STATE_STOPPED || -+ img->animation_state == ANIMATION_STATE_UNSET) { -+ // The next redraw is never (unless the state is changed). -+ img->next_redraw = 0; -+ return; -+ } -+ int last_uploaded_frame_index = gr_last_uploaded_frame_index(img); -+ // If we are loading and we reached the last frame, show the last frame. -+ if (img->animation_state == ANIMATION_STATE_LOADING && -+ img->current_frame == last_uploaded_frame_index) { -+ // The next redraw is never (unless the state is changed or -+ // frames are added). -+ img->next_redraw = 0; -+ return; -+ } -+ -+ // Check how many milliseconds passed since the current frame was shown. -+ int passed_ms = now - img->current_frame_time; -+ // If the animation is looping and too much time has passes, we can -+ // make a shortcut. -+ if (img->animation_state == ANIMATION_STATE_LOOPING && -+ img->total_duration > 0 && passed_ms >= img->total_duration) { -+ passed_ms %= img->total_duration; -+ img->current_frame_time = now - passed_ms; -+ } -+ // Find the next frame. -+ int original_frame_index = img->current_frame; -+ while (1) { -+ ImageFrame *frame = gr_get_frame(img, img->current_frame); -+ if (!frame) { -+ // The frame doesn't exist, go to the first frame. -+ img->current_frame = 1; -+ img->current_frame_time = now; -+ img->next_redraw = now + MAX(1, img->first_frame.gap); -+ return; -+ } -+ if (frame->gap >= 0 && passed_ms < frame->gap) { -+ // Not enough time has passed, we are still in the same -+ // frame, and it's not a gapless frame. -+ img->next_redraw = -+ img->current_frame_time + MAX(1, frame->gap); -+ return; -+ } -+ // Otherwise go to the next frame. -+ passed_ms -= MAX(0, frame->gap); -+ if (img->current_frame >= last_uploaded_frame_index) { -+ // It's the last frame, if the animation is loading, -+ // remain on it. -+ if (img->animation_state == ANIMATION_STATE_LOADING) { -+ img->next_redraw = 0; -+ return; -+ } -+ // Otherwise the animation is looping. -+ img->current_frame = 1; -+ // TODO: Support finite number of loops. -+ } else { -+ img->current_frame++; -+ } -+ // Make sure we don't get stuck in an infinite loop. -+ if (img->current_frame == original_frame_index) { -+ // We looped through all frames, but haven't reached the -+ // next frame yet. This may happen if too much time has -+ // passed since the last redraw or all the frames are -+ // gapless. Just move on to the next frame. -+ img->current_frame++; -+ if (img->current_frame > -+ last_uploaded_frame_index) -+ img->current_frame = 1; -+ img->current_frame_time = now; -+ img->next_redraw = now + MAX( -+ 1, gr_get_frame(img, img->current_frame)->gap); -+ return; -+ } -+ // Adjust the start time of the frame. The next redraw time will -+ // be set in the next iteration. -+ img->current_frame_time += MAX(0, frame->gap); -+ } -+} -+ -+//////////////////////////////////////////////////////////////////////////////// -+// Unloading and deleting images to save resources. -+//////////////////////////////////////////////////////////////////////////////// -+ -+/// A helper to compare frames by atime for qsort. -+static int gr_cmp_frames_by_atime(const void *a, const void *b) { -+ ImageFrame *frame_a = *(ImageFrame *const *)a; -+ ImageFrame *frame_b = *(ImageFrame *const *)b; -+ if (frame_a->atime == frame_b->atime) -+ return frame_a->image->global_command_index - -+ frame_b->image->global_command_index; -+ return frame_a->atime - frame_b->atime; -+} -+ -+/// A helper to compare images by atime for qsort. -+static int gr_cmp_images_by_atime(const void *a, const void *b) { -+ Image *img_a = *(Image *const *)a; -+ Image *img_b = *(Image *const *)b; -+ if (img_a->atime == img_b->atime) -+ return img_a->global_command_index - -+ img_b->global_command_index; -+ return img_a->atime - img_b->atime; -+} -+ -+/// A helper to compare placements by atime for qsort. -+static int gr_cmp_placements_by_atime(const void *a, const void *b) { -+ ImagePlacement *p_a = *(ImagePlacement **)a; -+ ImagePlacement *p_b = *(ImagePlacement **)b; -+ if (p_a->atime == p_b->atime) -+ return p_a->image->global_command_index - -+ p_b->image->global_command_index; -+ return p_a->atime - p_b->atime; -+} -+ -+typedef kvec_t(Image *) ImageVec; -+typedef kvec_t(ImagePlacement *) ImagePlacementVec; -+typedef kvec_t(ImageFrame *) ImageFrameVec; -+ -+/// Returns an array of pointers to all images sorted by atime. -+static ImageVec gr_get_images_sorted_by_atime() { -+ ImageVec vec; -+ kv_init(vec); -+ if (kh_size(images) == 0) -+ return vec; -+ kv_resize(Image *, vec, kh_size(images)); -+ Image *img = NULL; -+ kh_foreach_value(images, img, { kv_push(Image *, vec, img); }); -+ qsort(vec.a, kv_size(vec), sizeof(Image *), gr_cmp_images_by_atime); -+ return vec; -+} -+ -+/// Returns an array of pointers to all placements sorted by atime. -+static ImagePlacementVec gr_get_placements_sorted_by_atime() { -+ ImagePlacementVec vec; -+ kv_init(vec); -+ if (total_placement_count == 0) -+ return vec; -+ kv_resize(ImagePlacement *, vec, total_placement_count); -+ Image *img = NULL; -+ ImagePlacement *placement = NULL; -+ kh_foreach_value(images, img, { -+ kh_foreach_value(img->placements, placement, { -+ kv_push(ImagePlacement *, vec, placement); -+ }); -+ }); -+ qsort(vec.a, kv_size(vec), sizeof(ImagePlacement *), -+ gr_cmp_placements_by_atime); -+ return vec; -+} -+ -+/// Returns an array of pointers to all frames sorted by atime. -+static ImageFrameVec gr_get_frames_sorted_by_atime() { -+ ImageFrameVec frames; -+ kv_init(frames); -+ Image *img = NULL; -+ kh_foreach_value(images, img, { -+ foreach_frame(*img, frame, { -+ kv_push(ImageFrame *, frames, frame); -+ }); -+ }); -+ qsort(frames.a, kv_size(frames), sizeof(ImageFrame *), -+ gr_cmp_frames_by_atime); -+ return frames; -+} -+ -+/// An object that can be unloaded from RAM. -+typedef struct { -+ /// Some score, probably based on access time. The lower the score, the -+ /// more likely that the object should be unloaded. -+ int64_t score; -+ union { -+ ImagePlacement *placement; -+ ImageFrame *frame; -+ }; -+ /// If zero, the object is the imlib object of `frame`, if non-zero, -+ /// the object is a pixmap of `frameidx`-th frame of `placement`. -+ int frameidx; -+} UnloadableObject; -+ -+typedef kvec_t(UnloadableObject) UnloadableObjectVec; -+ -+/// A helper to compare unloadable objects by score for qsort. -+static int gr_cmp_unloadable_objects(const void *a, const void *b) { -+ UnloadableObject *obj_a = (UnloadableObject *)a; -+ UnloadableObject *obj_b = (UnloadableObject *)b; -+ return obj_a->score - obj_b->score; -+} -+ -+/// Unloads an unloadable object from RAM. -+static void gr_unload_object(UnloadableObject *obj) { -+ if (obj->frameidx) { -+ if (obj->placement->protected_frame == obj->frameidx) -+ return; -+ gr_unload_pixmap(obj->placement, obj->frameidx); -+ } else { -+ gr_unload_frame(obj->frame); -+ } -+} -+ -+/// Returns the recency threshold for an image. Frames that were accessed within -+/// this threshold from now are considered recent and may be handled -+/// differently because we may need them again very soon. -+static Milliseconds gr_recency_threshold(Image *img) { -+ return img->total_duration * 2 + 1000; -+} -+ -+/// Creates an unloadable object for the imlib object of a frame. -+static UnloadableObject gr_unloadable_object_for_frame(Milliseconds now, -+ ImageFrame *frame) { -+ UnloadableObject obj = {0}; -+ obj.frameidx = 0; -+ obj.frame = frame; -+ Milliseconds atime = frame->atime; -+ obj.score = atime; -+ if (atime >= now - gr_recency_threshold(frame->image)) { -+ // This is a recent frame, probably from an active animation. -+ // Score it above `now` to prefer unloading non-active frames. -+ // Randomize the score because it's not very clear in which -+ // order we want to unload them: reloading a frame may require -+ // reloading other frames. -+ obj.score = now + 1000 + rand() % 1000; -+ } -+ return obj; -+} -+ -+/// Creates an unloadable object for a pixmap. -+static UnloadableObject -+gr_unloadable_object_for_pixmap(Milliseconds now, ImageFrame *frame, -+ ImagePlacement *placement) { -+ UnloadableObject obj = {0}; -+ obj.frameidx = frame->index; -+ obj.placement = placement; -+ obj.score = placement->atime; -+ // Since we don't store pixmap atimes, use the -+ // oldest atime of the frame and the placement. -+ Milliseconds atime = MIN(placement->atime, frame->atime); -+ obj.score = atime; -+ if (atime >= now - gr_recency_threshold(frame->image)) { -+ // This is a recent pixmap, probably from an active animation. -+ // Score it above `now` to prefer unloading non-active frames. -+ // Also assign higher scores to frames that are closer to the -+ // current frame (more likely to be used soon). -+ int num_frames = gr_last_frame_index(frame->image); -+ int dist = frame->index - frame->image->current_frame; -+ if (dist < 0) -+ dist += num_frames; -+ obj.score = -+ now + 1000 + (num_frames - dist) * 1000 / num_frames; -+ // If the pixmap is much larger than the imlib image, prefer to -+ // unload the pixmap by adding up to -1000 to the score. If the -+ // imlib image is larger, add up to +1000. -+ float imlib_size = gr_frame_current_ram_size(frame); -+ float pixmap_size = -+ gr_placement_single_frame_ram_size(placement); -+ obj.score += -+ 2000 * (imlib_size / (imlib_size + pixmap_size) - 0.5); -+ } -+ return obj; -+} -+ -+/// Returns an array of unloadable objects sorted by score. -+static UnloadableObjectVec -+gr_get_unloadable_objects_sorted_by_score(Milliseconds now) { -+ UnloadableObjectVec objects; -+ kv_init(objects); -+ Image *img = NULL; -+ ImagePlacement *placement = NULL; -+ kh_foreach_value(images, img, { -+ foreach_frame(*img, frame, { -+ if (!frame->imlib_object) -+ continue; -+ kv_push(UnloadableObject, objects, -+ gr_unloadable_object_for_frame(now, frame)); -+ int frameidx = frame->index; -+ kh_foreach_value(img->placements, placement, { -+ if (!gr_get_frame_pixmap(placement, frameidx)) -+ continue; -+ kv_push(UnloadableObject, objects, -+ gr_unloadable_object_for_pixmap( -+ now, frame, placement)); -+ }); -+ }); -+ }); -+ qsort(objects.a, kv_size(objects), sizeof(UnloadableObject), -+ gr_cmp_unloadable_objects); -+ return objects; -+} -+ -+/// Returns the limit adjusted by the excess tolerance ratio. -+static inline unsigned apply_tolerance(unsigned limit) { -+ return limit + (unsigned)(limit * graphics_excess_tolerance_ratio); -+} -+ -+/// Checks RAM and disk cache limits and deletes/unloads some images. -+static void gr_check_limits() { -+ Milliseconds now = gr_now_ms(); -+ ImageVec images_sorted = {0}; -+ ImagePlacementVec placements_sorted = {0}; -+ ImageFrameVec frames_sorted = {0}; -+ UnloadableObjectVec objects_sorted = {0}; -+ int images_begin = 0; -+ int placements_begin = 0; -+ char changed = 0; -+ // First reduce the number of images if there are too many. -+ if (kh_size(images) > apply_tolerance(graphics_max_total_placements)) { -+ GR_LOG("Too many images: %d\n", kh_size(images)); -+ changed = 1; -+ images_sorted = gr_get_images_sorted_by_atime(); -+ int to_delete = kv_size(images_sorted) - -+ graphics_max_total_placements; -+ for (; images_begin < to_delete; images_begin++) -+ gr_delete_image(images_sorted.a[images_begin]); -+ } -+ // Then reduce the number of placements if there are too many. -+ if (total_placement_count > -+ apply_tolerance(graphics_max_total_placements)) { -+ GR_LOG("Too many placements: %d\n", total_placement_count); -+ changed = 1; -+ placements_sorted = gr_get_placements_sorted_by_atime(); -+ int to_delete = kv_size(placements_sorted) - -+ graphics_max_total_placements; -+ for (; placements_begin < to_delete; placements_begin++) { -+ ImagePlacement *placement = -+ placements_sorted.a[placements_begin]; -+ if (placement->protected_frame) -+ break; -+ gr_delete_placement(placement); -+ } -+ } -+ // Then reduce the size of the image file cache. The files correspond to -+ // image frames. -+ if (images_disk_size > -+ apply_tolerance(graphics_total_file_cache_size)) { -+ GR_LOG("Too big disk cache: %ld KiB\n", -+ images_disk_size / 1024); -+ changed = 1; -+ frames_sorted = gr_get_frames_sorted_by_atime(); -+ for (int i = 0; i < kv_size(frames_sorted); i++) { -+ if (images_disk_size <= graphics_total_file_cache_size) -+ break; -+ gr_delete_imagefile(kv_A(frames_sorted, i)); -+ } -+ } -+ // Then unload images from RAM. -+ if (images_ram_size > apply_tolerance(graphics_max_total_ram_size)) { -+ changed = 1; -+ int frames_begin = 0; -+ GR_LOG("Too much ram: %ld KiB\n", images_ram_size / 1024); -+ objects_sorted = gr_get_unloadable_objects_sorted_by_score(now); -+ for (int i = 0; i < kv_size(objects_sorted); i++) { -+ if (images_ram_size <= graphics_max_total_ram_size) -+ break; -+ gr_unload_object(&kv_A(objects_sorted, i)); -+ } -+ } -+ if (changed) { -+ GR_LOG("After cleaning: ram: %ld KiB disk: %ld KiB " -+ "img count: %d placement count: %d\n", -+ images_ram_size / 1024, images_disk_size / 1024, -+ kh_size(images), total_placement_count); -+ } -+ kv_destroy(images_sorted); -+ kv_destroy(placements_sorted); -+ kv_destroy(frames_sorted); -+ kv_destroy(objects_sorted); -+} -+ -+/// Unloads all images by user request. -+void gr_unload_images_to_reduce_ram() { -+ Image *img = NULL; -+ ImagePlacement *placement = NULL; -+ kh_foreach_value(images, img, { -+ kh_foreach_value(img->placements, placement, { -+ if (placement->protected_frame) -+ continue; -+ gr_unload_placement(placement); -+ }); -+ gr_unload_all_frames(img); -+ }); -+} -+ -+//////////////////////////////////////////////////////////////////////////////// -+// Image loading. -+//////////////////////////////////////////////////////////////////////////////// -+ -+/// Copies `num_pixels` pixels (not bytes!) from a buffer `from` to an imlib2 -+/// image data `to`. The format may be 24 (RGB) or 32 (RGBA), and it's converted -+/// to imlib2's representation, which is 0xAARRGGBB (having BGRA memory layout -+/// on little-endian architectures). -+static inline void gr_copy_pixels(DATA32 *to, unsigned char *from, int format, -+ size_t num_pixels) { -+ size_t pixel_size = format == 24 ? 3 : 4; -+ if (format == 32) { -+ for (unsigned i = 0; i < num_pixels; ++i) { -+ unsigned byte_i = i * pixel_size; -+ to[i] = ((DATA32)from[byte_i + 2]) | -+ ((DATA32)from[byte_i + 1]) << 8 | -+ ((DATA32)from[byte_i]) << 16 | -+ ((DATA32)from[byte_i + 3]) << 24; -+ } -+ } else { -+ for (unsigned i = 0; i < num_pixels; ++i) { -+ unsigned byte_i = i * pixel_size; -+ to[i] = ((DATA32)from[byte_i + 2]) | -+ ((DATA32)from[byte_i + 1]) << 8 | -+ ((DATA32)from[byte_i]) << 16 | 0xFF000000; -+ } -+ } -+} -+ -+/// Loads uncompressed RGB or RGBA image data from a file. -+static void gr_load_raw_pixel_data_uncompressed(DATA32 *data, FILE *file, -+ int format, -+ size_t total_pixels) { -+ unsigned char chunk[BUFSIZ]; -+ size_t pixel_size = format == 24 ? 3 : 4; -+ size_t chunk_size_pix = BUFSIZ / 4; -+ size_t chunk_size_bytes = chunk_size_pix * pixel_size; -+ size_t bytes = total_pixels * pixel_size; -+ for (size_t chunk_start_pix = 0; chunk_start_pix < total_pixels; -+ chunk_start_pix += chunk_size_pix) { -+ size_t read_size = fread(chunk, 1, chunk_size_bytes, file); -+ size_t read_pixels = read_size / pixel_size; -+ if (chunk_start_pix + read_pixels > total_pixels) -+ read_pixels = total_pixels - chunk_start_pix; -+ gr_copy_pixels(data + chunk_start_pix, chunk, format, -+ read_pixels); -+ } -+} -+ -+#define COMPRESSED_CHUNK_SIZE BUFSIZ -+#define DECOMPRESSED_CHUNK_SIZE (BUFSIZ * 4) -+ -+/// Loads compressed RGB or RGBA image data from a file. -+static int gr_load_raw_pixel_data_compressed(DATA32 *data, FILE *file, -+ int format, size_t total_pixels) { -+ size_t pixel_size = format == 24 ? 3 : 4; -+ unsigned char compressed_chunk[COMPRESSED_CHUNK_SIZE]; -+ unsigned char decompressed_chunk[DECOMPRESSED_CHUNK_SIZE]; -+ -+ z_stream strm; -+ strm.zalloc = Z_NULL; -+ strm.zfree = Z_NULL; -+ strm.opaque = Z_NULL; -+ strm.next_out = decompressed_chunk; -+ strm.avail_out = DECOMPRESSED_CHUNK_SIZE; -+ strm.avail_in = 0; -+ strm.next_in = Z_NULL; -+ int ret = inflateInit(&strm); -+ if (ret != Z_OK) -+ return 1; -+ -+ int error = 0; -+ int progress = 0; -+ size_t total_copied_pixels = 0; -+ while (1) { -+ // If we don't have enough data in the input buffer, try to read -+ // from the file. -+ if (strm.avail_in <= COMPRESSED_CHUNK_SIZE / 4) { -+ // Move the existing data to the beginning. -+ memmove(compressed_chunk, strm.next_in, strm.avail_in); -+ strm.next_in = compressed_chunk; -+ // Read more data. -+ size_t bytes_read = fread( -+ compressed_chunk + strm.avail_in, 1, -+ COMPRESSED_CHUNK_SIZE - strm.avail_in, file); -+ strm.avail_in += bytes_read; -+ if (bytes_read != 0) -+ progress = 1; -+ } -+ -+ // Try to inflate the data. -+ int ret = inflate(&strm, Z_SYNC_FLUSH); -+ if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR) { -+ error = 1; -+ fprintf(stderr, -+ "error: could not decompress the image, error " -+ "%s\n", -+ ret == Z_MEM_ERROR ? "Z_MEM_ERROR" -+ : "Z_DATA_ERROR"); -+ break; -+ } -+ -+ // Copy the data from the output buffer to the image. -+ size_t full_pixels = -+ (DECOMPRESSED_CHUNK_SIZE - strm.avail_out) / pixel_size; -+ // Make sure we don't overflow the image. -+ if (full_pixels > total_pixels - total_copied_pixels) -+ full_pixels = total_pixels - total_copied_pixels; -+ if (full_pixels > 0) { -+ // Copy pixels. -+ gr_copy_pixels(data, decompressed_chunk, format, -+ full_pixels); -+ data += full_pixels; -+ total_copied_pixels += full_pixels; -+ if (total_copied_pixels >= total_pixels) { -+ // We filled the whole image, there may be some -+ // data left, but we just truncate it. -+ break; -+ } -+ // Move the remaining data to the beginning. -+ size_t copied_bytes = full_pixels * pixel_size; -+ size_t leftover = -+ (DECOMPRESSED_CHUNK_SIZE - strm.avail_out) - -+ copied_bytes; -+ memmove(decompressed_chunk, -+ decompressed_chunk + copied_bytes, leftover); -+ strm.next_out -= copied_bytes; -+ strm.avail_out += copied_bytes; -+ progress = 1; -+ } -+ -+ // If we haven't made any progress, then we have reached the end -+ // of both the file and the inflated data. -+ if (!progress) -+ break; -+ progress = 0; -+ } -+ -+ inflateEnd(&strm); -+ return error; -+} -+ -+#undef COMPRESSED_CHUNK_SIZE -+#undef DECOMPRESSED_CHUNK_SIZE -+ -+/// Load the image from a file containing raw pixel data (RGB or RGBA), the data -+/// may be compressed. -+static Imlib_Image gr_load_raw_pixel_data(ImageFrame *frame, -+ const char *filename) { -+ size_t total_pixels = frame->data_pix_width * frame->data_pix_height; -+ if (total_pixels * 4 > graphics_max_single_image_ram_size) { -+ fprintf(stderr, -+ "error: image %u frame %u is too big too load: %zu > %u\n", -+ frame->image->image_id, frame->index, total_pixels * 4, -+ graphics_max_single_image_ram_size); -+ return NULL; -+ } -+ -+ FILE* file = fopen(filename, "rb"); -+ if (!file) { -+ fprintf(stderr, -+ "error: could not open image file: %s\n", -+ sanitized_filename(filename)); -+ return NULL; -+ } -+ -+ Imlib_Image image = imlib_create_image(frame->data_pix_width, -+ frame->data_pix_height); -+ if (!image) { -+ fprintf(stderr, -+ "error: could not create an image of size %d x %d\n", -+ frame->data_pix_width, frame->data_pix_height); -+ fclose(file); -+ return NULL; -+ } -+ -+ imlib_context_set_image(image); -+ imlib_image_set_has_alpha(1); -+ DATA32* data = imlib_image_get_data(); -+ -+ // The default format is 32. -+ int format = frame->format ? frame->format : 32; -+ -+ if (frame->compression == 0) { -+ gr_load_raw_pixel_data_uncompressed(data, file, format, -+ total_pixels); -+ } else { -+ int ret = gr_load_raw_pixel_data_compressed(data, file, format, -+ total_pixels); -+ if (ret != 0) { -+ imlib_image_put_back_data(data); -+ imlib_free_image(); -+ fclose(file); -+ return NULL; -+ } -+ } -+ -+ fclose(file); -+ imlib_image_put_back_data(data); -+ return image; -+} -+ -+/// Loads the unscaled frame into RAM as an imlib object. The frame imlib object -+/// is fully composed on top of the background frame. If the frame is already -+/// loaded, does nothing. Loading may fail, in which case the status of the -+/// frame will be set to STATUS_RAM_LOADING_ERROR. -+static void gr_load_imlib_object(ImageFrame *frame) { -+ if (frame->imlib_object) -+ return; -+ -+ // If the image is uninitialized or uploading has failed, or the file -+ // has been deleted, we cannot load the image. -+ if (frame->status < STATUS_UPLOADING_SUCCESS) -+ return; -+ if (frame->disk_size == 0) { -+ if (frame->status != STATUS_RAM_LOADING_ERROR) { -+ fprintf(stderr, -+ "error: cached image was deleted: %u frame %u\n", -+ frame->image->image_id, frame->index); -+ } -+ frame->status = STATUS_RAM_LOADING_ERROR; -+ return; -+ } -+ -+ // Prevent recursive dependences between frames. -+ if (frame->status == STATUS_RAM_LOADING_IN_PROGRESS) { -+ fprintf(stderr, -+ "error: recursive loading of image %u frame %u\n", -+ frame->image->image_id, frame->index); -+ frame->status = STATUS_RAM_LOADING_ERROR; -+ return; -+ } -+ frame->status = STATUS_RAM_LOADING_IN_PROGRESS; -+ -+ // Load the background frame if needed. Hopefully it's not recursive. -+ ImageFrame *bg_frame = NULL; -+ if (frame->background_frame_index) { -+ bg_frame = gr_get_frame(frame->image, -+ frame->background_frame_index); -+ if (!bg_frame) { -+ fprintf(stderr, -+ "error: could not find background " -+ "frame %d for image %u frame %d\n", -+ frame->background_frame_index, -+ frame->image->image_id, frame->index); -+ frame->status = STATUS_RAM_LOADING_ERROR; -+ return; -+ } -+ gr_load_imlib_object(bg_frame); -+ if (!bg_frame->imlib_object) { -+ fprintf(stderr, -+ "error: could not load background frame %d for " -+ "image %u frame %d\n", -+ frame->background_frame_index, -+ frame->image->image_id, frame->index); -+ frame->status = STATUS_RAM_LOADING_ERROR; -+ return; -+ } -+ } -+ -+ // Load the frame data image. -+ Imlib_Image frame_data_image = NULL; -+ char filename[MAX_FILENAME_SIZE]; -+ gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); -+ GR_LOG("Loading image: %s\n", sanitized_filename(filename)); -+ if (frame->format == 100 || frame->format == 0) -+ frame_data_image = imlib_load_image(filename); -+ if (frame->format == 32 || frame->format == 24 || -+ (!frame_data_image && frame->format == 0)) -+ frame_data_image = gr_load_raw_pixel_data(frame, filename); -+ this_redraw_cycle_loaded_files++; -+ -+ if (!frame_data_image) { -+ if (frame->status != STATUS_RAM_LOADING_ERROR) { -+ fprintf(stderr, "error: could not load image: %s\n", -+ sanitized_filename(filename)); -+ } -+ frame->status = STATUS_RAM_LOADING_ERROR; -+ return; -+ } -+ -+ imlib_context_set_image(frame_data_image); -+ int frame_data_width = imlib_image_get_width(); -+ int frame_data_height = imlib_image_get_height(); -+ GR_LOG("Successfully loaded, size %d x %d\n", frame_data_width, -+ frame_data_height); -+ // If imlib loading succeeded, and it is the first frame, set the -+ // information about the original image size, unless it's already set. -+ if (frame->index == 1 && frame->image->pix_width == 0 && -+ frame->image->pix_height == 0) { -+ frame->image->pix_width = frame_data_width; -+ frame->image->pix_height = frame_data_height; -+ } -+ -+ int image_width = frame->image->pix_width; -+ int image_height = frame->image->pix_height; -+ -+ // Compose the image with the background color or frame. -+ if (frame->background_color != 0 || bg_frame || -+ image_width != frame_data_width || -+ image_height != frame_data_height) { -+ GR_LOG("Composing the frame bg = 0x%08X, bgframe = %d\n", -+ frame->background_color, frame->background_frame_index); -+ Imlib_Image composed_image = imlib_create_image( -+ image_width, image_height); -+ imlib_context_set_image(composed_image); -+ imlib_image_set_has_alpha(1); -+ imlib_context_set_anti_alias(0); -+ -+ // Start with the background frame or color. -+ imlib_context_set_blend(0); -+ if (bg_frame && bg_frame->imlib_object) { -+ imlib_blend_image_onto_image( -+ bg_frame->imlib_object, 1, 0, 0, -+ image_width, image_height, 0, 0, -+ image_width, image_height); -+ } else { -+ int r = (frame->background_color >> 24) & 0xFF; -+ int g = (frame->background_color >> 16) & 0xFF; -+ int b = (frame->background_color >> 8) & 0xFF; -+ int a = frame->background_color & 0xFF; -+ imlib_context_set_color(r, g, b, a); -+ imlib_image_fill_rectangle(0, 0, image_width, -+ image_height); -+ } -+ -+ // Blend the frame data image onto the background. -+ imlib_context_set_blend(1); -+ imlib_blend_image_onto_image( -+ frame_data_image, 1, 0, 0, frame->data_pix_width, -+ frame->data_pix_height, frame->x, frame->y, -+ frame->data_pix_width, frame->data_pix_height); -+ -+ // Free the frame data image. -+ imlib_context_set_image(frame_data_image); -+ imlib_free_image(); -+ -+ frame_data_image = composed_image; -+ } -+ -+ frame->imlib_object = frame_data_image; -+ -+ images_ram_size += gr_frame_current_ram_size(frame); -+ frame->status = STATUS_RAM_LOADING_SUCCESS; -+ -+ GR_LOG("After loading image %u frame %d ram: %ld KiB (+ %u KiB)\n", -+ frame->image->image_id, frame->index, -+ images_ram_size / 1024, gr_frame_current_ram_size(frame) / 1024); -+} -+ -+/// Premultiplies the alpha channel of the image data. The data is an array of -+/// pixels such that each pixel is a 32-bit integer in the format 0xAARRGGBB. -+static void gr_premultiply_alpha(DATA32 *data, size_t num_pixels) { -+ for (size_t i = 0; i < num_pixels; ++i) { -+ DATA32 pixel = data[i]; -+ unsigned char a = pixel >> 24; -+ if (a == 0) { -+ data[i] = 0; -+ } else if (a != 255) { -+ unsigned char b = (pixel & 0xFF) * a / 255; -+ unsigned char g = ((pixel >> 8) & 0xFF) * a / 255; -+ unsigned char r = ((pixel >> 16) & 0xFF) * a / 255; -+ data[i] = (a << 24) | (r << 16) | (g << 8) | b; -+ } -+ } -+} -+ -+/// Creates a pixmap for the frame of an image placement. The pixmap contain the -+/// image data correctly scaled and fit to the box defined by the number of -+/// rows/columns of the image placement and the provided cell dimensions in -+/// pixels. If the placement is already loaded, it will be reloaded only if the -+/// cell dimensions have changed. -+Pixmap gr_load_pixmap(ImagePlacement *placement, int frameidx, int cw, int ch) { -+ Image *img = placement->image; -+ ImageFrame *frame = gr_get_frame(img, frameidx); -+ -+ // Update the atime uncoditionally. -+ gr_touch_placement(placement); -+ if (frame) -+ gr_touch_frame(frame); -+ -+ // If cw or ch are different, unload all the pixmaps. -+ if (placement->scaled_cw != cw || placement->scaled_ch != ch) { -+ gr_unload_placement(placement); -+ placement->scaled_cw = cw; -+ placement->scaled_ch = ch; -+ } -+ -+ // If it's already loaded, do nothing. -+ Pixmap pixmap = gr_get_frame_pixmap(placement, frameidx); -+ if (pixmap) -+ return pixmap; -+ -+ GR_LOG("Loading placement: %u/%u frame %u\n", img->image_id, -+ placement->placement_id, frameidx); -+ -+ // Load the imlib object for the frame. -+ if (!frame) { -+ fprintf(stderr, -+ "error: could not find frame %u for image %u\n", -+ frameidx, img->image_id); -+ return 0; -+ } -+ gr_load_imlib_object(frame); -+ if (!frame->imlib_object) -+ return 0; -+ -+ // Infer the placement size if needed. -+ gr_infer_placement_size_maybe(placement); -+ -+ // Create the scaled image. This is temporary, we will scale it -+ // appropriately, upload to the X server, and then delete immediately. -+ int scaled_w = (int)placement->cols * cw; -+ int scaled_h = (int)placement->rows * ch; -+ if (scaled_w * scaled_h * 4 > graphics_max_single_image_ram_size) { -+ fprintf(stderr, -+ "error: placement %u/%u would be too big to load: %d x " -+ "%d x 4 > %u\n", -+ img->image_id, placement->placement_id, scaled_w, -+ scaled_h, graphics_max_single_image_ram_size); -+ return 0; -+ } -+ Imlib_Image scaled_image = imlib_create_image(scaled_w, scaled_h); -+ if (!scaled_image) { -+ fprintf(stderr, -+ "error: imlib_create_image(%d, %d) returned " -+ "null\n", -+ scaled_w, scaled_h); -+ return 0; -+ } -+ imlib_context_set_image(scaled_image); -+ imlib_image_set_has_alpha(1); -+ -+ // First fill the scaled image with the transparent color. -+ imlib_context_set_blend(0); -+ imlib_context_set_color(0, 0, 0, 0); -+ imlib_image_fill_rectangle(0, 0, scaled_w, scaled_h); -+ imlib_context_set_anti_alias(1); -+ imlib_context_set_blend(1); -+ -+ // The source rectangle. -+ int src_x = placement->src_pix_x; -+ int src_y = placement->src_pix_y; -+ int src_w = placement->src_pix_width; -+ int src_h = placement->src_pix_height; -+ // Whether the box is too small to use the true size of the image. -+ char box_too_small = scaled_w < src_w || scaled_h < src_h; -+ char mode = placement->scale_mode; -+ -+ // Then blend the original image onto the transparent background. -+ if (src_w <= 0 || src_h <= 0) { -+ fprintf(stderr, "warning: image of zero size\n"); -+ } else if (mode == SCALE_MODE_FILL) { -+ imlib_blend_image_onto_image(frame->imlib_object, 1, src_x, -+ src_y, src_w, src_h, 0, 0, -+ scaled_w, scaled_h); -+ } else if (mode == SCALE_MODE_NONE || -+ (mode == SCALE_MODE_NONE_OR_CONTAIN && !box_too_small)) { -+ imlib_blend_image_onto_image(frame->imlib_object, 1, src_x, -+ src_y, src_w, src_h, 0, 0, src_w, -+ src_h); -+ } else { -+ if (mode != SCALE_MODE_CONTAIN && -+ mode != SCALE_MODE_NONE_OR_CONTAIN) { -+ fprintf(stderr, -+ "warning: unknown scale mode %u, using " -+ "'contain' instead\n", -+ mode); -+ } -+ int dest_x, dest_y; -+ int dest_w, dest_h; -+ if (scaled_w * src_h > src_w * scaled_h) { -+ // If the box is wider than the original image, fit to -+ // height. -+ dest_h = scaled_h; -+ dest_y = 0; -+ dest_w = src_w * scaled_h / src_h; -+ dest_x = (scaled_w - dest_w) / 2; -+ } else { -+ // Otherwise, fit to width. -+ dest_w = scaled_w; -+ dest_x = 0; -+ dest_h = src_h * scaled_w / src_w; -+ dest_y = (scaled_h - dest_h) / 2; -+ } -+ imlib_blend_image_onto_image(frame->imlib_object, 1, src_x, -+ src_y, src_w, src_h, dest_x, -+ dest_y, dest_w, dest_h); -+ } -+ -+ // XRender needs the alpha channel premultiplied. -+ DATA32 *data = imlib_image_get_data(); -+ gr_premultiply_alpha(data, scaled_w * scaled_h); -+ -+ // Upload the image to the X server. -+ Display *disp = imlib_context_get_display(); -+ Visual *vis = imlib_context_get_visual(); -+ Colormap cmap = imlib_context_get_colormap(); -+ Drawable drawable = imlib_context_get_drawable(); -+ if (!drawable) -+ drawable = DefaultRootWindow(disp); -+ pixmap = XCreatePixmap(disp, drawable, scaled_w, scaled_h, 32); -+ XVisualInfo visinfo; -+ XMatchVisualInfo(disp, DefaultScreen(disp), 32, TrueColor, &visinfo); -+ XImage *ximage = XCreateImage(disp, visinfo.visual, 32, ZPixmap, 0, -+ (char *)data, scaled_w, scaled_h, 32, 0); -+ GC gc = XCreateGC(disp, pixmap, 0, NULL); -+ XPutImage(disp, pixmap, gc, ximage, 0, 0, 0, 0, scaled_w, -+ scaled_h); -+ XFreeGC(disp, gc); -+ // XDestroyImage will free the data as well, but it is managed by imlib, -+ // so set it to NULL. -+ ximage->data = NULL; -+ XDestroyImage(ximage); -+ imlib_image_put_back_data(data); -+ imlib_free_image(); -+ -+ // Assign the pixmap to the frame and increase the ram size. -+ gr_set_frame_pixmap(placement, frameidx, pixmap); -+ images_ram_size += gr_placement_single_frame_ram_size(placement); -+ this_redraw_cycle_loaded_pixmaps++; -+ -+ GR_LOG("After loading placement %u/%u frame %d ram: %ld KiB (+ %u " -+ "KiB)\n", -+ frame->image->image_id, placement->placement_id, frame->index, -+ images_ram_size / 1024, -+ gr_placement_single_frame_ram_size(placement) / 1024); -+ -+ // Free up ram if needed, but keep the pixmap we've loaded no matter -+ // what. -+ placement->protected_frame = frameidx; -+ gr_check_limits(); -+ placement->protected_frame = 0; -+ -+ return pixmap; -+} -+ -+//////////////////////////////////////////////////////////////////////////////// -+// Initialization and deinitialization. -+//////////////////////////////////////////////////////////////////////////////// -+ -+/// Creates a temporary directory. -+static int gr_create_cache_dir() { -+ strncpy(cache_dir, graphics_cache_dir_template, sizeof(cache_dir)); -+ if (!mkdtemp(cache_dir)) { -+ fprintf(stderr, -+ "error: could not create temporary dir from template " -+ "%s\n", -+ sanitized_filename(cache_dir)); -+ return 0; -+ } -+ fprintf(stderr, "Graphics cache directory: %s\n", cache_dir); -+ return 1; -+} -+ -+/// Checks whether `tmp_dir` exists and recreates it if it doesn't. -+static void gr_make_sure_tmpdir_exists() { -+ struct stat st; -+ if (stat(cache_dir, &st) == 0 && S_ISDIR(st.st_mode)) -+ return; -+ fprintf(stderr, -+ "error: %s is not a directory, will need to create a new " -+ "graphics cache directory\n", -+ sanitized_filename(cache_dir)); -+ gr_create_cache_dir(); -+} -+ -+/// Initialize the graphics module. -+void gr_init(Display *disp, Visual *vis, Colormap cm) { -+ // Set the initialization time. -+ clock_gettime(CLOCK_MONOTONIC, &initialization_time); -+ -+ // Create the temporary dir. -+ if (!gr_create_cache_dir()) -+ abort(); -+ -+ // Initialize imlib. -+ imlib_context_set_display(disp); -+ imlib_context_set_visual(vis); -+ imlib_context_set_colormap(cm); -+ imlib_context_set_anti_alias(1); -+ imlib_context_set_blend(1); -+ // Imlib2 checks only the file name when caching, which is not enough -+ // for us since we reuse file names. Disable caching. -+ imlib_set_cache_size(0); -+ -+ // Prepare for color inversion. -+ for (size_t i = 0; i < 256; ++i) -+ reverse_table[i] = 255 - i; -+ -+ // Create data structures. -+ images = kh_init(id2image); -+ kv_init(next_redraw_times); -+ -+ atexit(gr_deinit); -+} -+ -+/// Deinitialize the graphics module. -+void gr_deinit() { -+ // Remove the cache dir. -+ remove(cache_dir); -+ kv_destroy(next_redraw_times); -+ if (images) { -+ // Delete all images. -+ gr_delete_all_images(); -+ // Destroy the data structures. -+ kh_destroy(id2image, images); -+ images = NULL; -+ } -+} -+ -+//////////////////////////////////////////////////////////////////////////////// -+// Dumping, debugging, and image preview. -+//////////////////////////////////////////////////////////////////////////////// -+ -+/// Returns a string containing a time difference in a human-readable format. -+/// Uses a static buffer, so be careful. -+static const char *gr_ago(Milliseconds diff) { -+ static char result[32]; -+ double seconds = (double)diff / 1000.0; -+ if (seconds < 1) -+ snprintf(result, sizeof(result), "%.2f sec ago", seconds); -+ else if (seconds < 60) -+ snprintf(result, sizeof(result), "%d sec ago", (int)seconds); -+ else if (seconds < 3600) -+ snprintf(result, sizeof(result), "%d min %d sec ago", -+ (int)(seconds / 60), (int)(seconds) % 60); -+ else { -+ snprintf(result, sizeof(result), "%d hr %d min %d sec ago", -+ (int)(seconds / 3600), (int)(seconds) % 3600 / 60, -+ (int)(seconds) % 60); -+ } -+ return result; -+} -+ -+/// Prints to `file` with an indentation of `ind` spaces. -+static void fprintf_ind(FILE *file, int ind, const char *format, ...) { -+ fprintf(file, "%*s", ind, ""); -+ va_list args; -+ va_start(args, format); -+ vfprintf(file, format, args); -+ va_end(args); -+} -+ -+/// Dumps the image info to `file` with an indentation of `ind` spaces. -+static void gr_dump_image_info(FILE *file, Image *img, int ind) { -+ if (!img) { -+ fprintf_ind(file, ind, "Image is NULL\n"); -+ return; -+ } -+ Milliseconds now = gr_now_ms(); -+ fprintf_ind(file, ind, "Image %u\n", img->image_id); -+ ind += 4; -+ fprintf_ind(file, ind, "number: %u\n", img->image_number); -+ fprintf_ind(file, ind, "global command index: %lu\n", -+ img->global_command_index); -+ fprintf_ind(file, ind, "accessed: %ld %s\n", img->atime, -+ gr_ago(now - img->atime)); -+ fprintf_ind(file, ind, "pix size: %ux%u\n", img->pix_width, -+ img->pix_height); -+ fprintf_ind(file, ind, "cur frame start time: %ld %s\n", -+ img->current_frame_time, -+ gr_ago(now - img->current_frame_time)); -+ if (img->next_redraw) -+ fprintf_ind(file, ind, "next redraw: %ld in %ld ms\n", -+ img->next_redraw, img->next_redraw - now); -+ fprintf_ind(file, ind, "total disk size: %u KiB\n", -+ img->total_disk_size / 1024); -+ fprintf_ind(file, ind, "total duration: %d\n", img->total_duration); -+ fprintf_ind(file, ind, "frames: %d\n", gr_last_frame_index(img)); -+ fprintf_ind(file, ind, "cur frame: %d\n", img->current_frame); -+ fprintf_ind(file, ind, "animation state: %d\n", img->animation_state); -+ fprintf_ind(file, ind, "default_placement: %u\n", -+ img->default_placement); -+} -+ -+/// Dumps the frame info to `file` with an indentation of `ind` spaces. -+static void gr_dump_frame_info(FILE *file, ImageFrame *frame, int ind) { -+ if (!frame) { -+ fprintf_ind(file, ind, "Frame is NULL\n"); -+ return; -+ } -+ Milliseconds now = gr_now_ms(); -+ fprintf_ind(file, ind, "Frame %d\n", frame->index); -+ ind += 4; -+ if (frame->index == 0) { -+ fprintf_ind(file, ind, "NOT INITIALIZED\n"); -+ return; -+ } -+ if (frame->uploading_failure) -+ fprintf_ind(file, ind, "uploading failure: %s\n", -+ image_uploading_failure_strings -+ [frame->uploading_failure]); -+ fprintf_ind(file, ind, "gap: %d\n", frame->gap); -+ fprintf_ind(file, ind, "accessed: %ld %s\n", frame->atime, -+ gr_ago(now - frame->atime)); -+ fprintf_ind(file, ind, "data pix size: %ux%u\n", frame->data_pix_width, -+ frame->data_pix_height); -+ char filename[MAX_FILENAME_SIZE]; -+ gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); -+ if (access(filename, F_OK) != -1) -+ fprintf_ind(file, ind, "file: %s\n", -+ sanitized_filename(filename)); -+ else -+ fprintf_ind(file, ind, "not on disk\n"); -+ fprintf_ind(file, ind, "disk size: %u KiB\n", frame->disk_size / 1024); -+ if (frame->imlib_object) { -+ unsigned ram_size = gr_frame_current_ram_size(frame); -+ fprintf_ind(file, ind, -+ "loaded into ram, size: %d " -+ "KiB\n", -+ ram_size / 1024); -+ } else { -+ fprintf_ind(file, ind, "not loaded into ram\n"); -+ } -+} -+ -+/// Dumps the placement info to `file` with an indentation of `ind` spaces. -+static void gr_dump_placement_info(FILE *file, ImagePlacement *placement, -+ int ind) { -+ if (!placement) { -+ fprintf_ind(file, ind, "Placement is NULL\n"); -+ return; -+ } -+ Milliseconds now = gr_now_ms(); -+ fprintf_ind(file, ind, "Placement %u\n", placement->placement_id); -+ ind += 4; -+ fprintf_ind(file, ind, "accessed: %ld %s\n", placement->atime, -+ gr_ago(now - placement->atime)); -+ fprintf_ind(file, ind, "scale_mode: %u\n", placement->scale_mode); -+ fprintf_ind(file, ind, "size: %u cols x %u rows\n", placement->cols, -+ placement->rows); -+ fprintf_ind(file, ind, "cell size: %ux%u\n", placement->scaled_cw, -+ placement->scaled_ch); -+ fprintf_ind(file, ind, "ram per frame: %u KiB\n", -+ gr_placement_single_frame_ram_size(placement) / 1024); -+ unsigned ram_size = gr_placement_current_ram_size(placement); -+ fprintf_ind(file, ind, "ram size: %d KiB\n", ram_size / 1024); -+} -+ -+/// Dumps placement pixmaps to `file` with an indentation of `ind` spaces. -+static void gr_dump_placement_pixmaps(FILE *file, ImagePlacement *placement, -+ int ind) { -+ if (!placement) -+ return; -+ int frameidx = 1; -+ foreach_pixmap(*placement, pixmap, { -+ fprintf_ind(file, ind, "Frame %d pixmap %lu\n", frameidx, -+ pixmap); -+ ++frameidx; -+ }); -+} -+ -+/// Dumps the internal state (images and placements) to stderr. -+void gr_dump_state() { -+ FILE *file = stderr; -+ int ind = 0; -+ fprintf_ind(file, ind, "======= Graphics module state dump =======\n"); -+ fprintf_ind(file, ind, -+ "sizeof(Image) = %lu sizeof(ImageFrame) = %lu " -+ "sizeof(ImagePlacement) = %lu\n", -+ sizeof(Image), sizeof(ImageFrame), sizeof(ImagePlacement)); -+ fprintf_ind(file, ind, "Image count: %u\n", kh_size(images)); -+ fprintf_ind(file, ind, "Placement count: %u\n", total_placement_count); -+ fprintf_ind(file, ind, "Estimated RAM usage: %ld KiB\n", -+ images_ram_size / 1024); -+ fprintf_ind(file, ind, "Estimated Disk usage: %ld KiB\n", -+ images_disk_size / 1024); -+ -+ Milliseconds now = gr_now_ms(); -+ -+ int64_t images_ram_size_computed = 0; -+ int64_t images_disk_size_computed = 0; -+ -+ Image *img = NULL; -+ ImagePlacement *placement = NULL; -+ kh_foreach_value(images, img, { -+ fprintf_ind(file, ind, "----------------\n"); -+ gr_dump_image_info(file, img, 0); -+ int64_t total_disk_size_computed = 0; -+ int total_duration_computed = 0; -+ foreach_frame(*img, frame, { -+ gr_dump_frame_info(file, frame, 4); -+ if (frame->image != img) -+ fprintf_ind(file, 8, -+ "ERROR: WRONG IMAGE POINTER\n"); -+ total_duration_computed += frame->gap; -+ images_disk_size_computed += frame->disk_size; -+ total_disk_size_computed += frame->disk_size; -+ if (frame->imlib_object) -+ images_ram_size_computed += -+ gr_frame_current_ram_size(frame); -+ }); -+ if (img->total_disk_size != total_disk_size_computed) { -+ fprintf_ind(file, ind, -+ " ERROR: total_disk_size is %u, but " -+ "computed value is %ld\n", -+ img->total_disk_size, total_disk_size_computed); -+ } -+ if (img->total_duration != total_duration_computed) { -+ fprintf_ind(file, ind, -+ " ERROR: total_duration is %d, but computed " -+ "value is %d\n", -+ img->total_duration, total_duration_computed); -+ } -+ kh_foreach_value(img->placements, placement, { -+ gr_dump_placement_info(file, placement, 4); -+ if (placement->image != img) -+ fprintf_ind(file, 8, -+ "ERROR: WRONG IMAGE POINTER\n"); -+ fprintf_ind(file, 8, -+ "Pixmaps:\n"); -+ gr_dump_placement_pixmaps(file, placement, 12); -+ unsigned ram_size = -+ gr_placement_current_ram_size(placement); -+ images_ram_size_computed += ram_size; -+ }); -+ }); -+ if (images_ram_size != images_ram_size_computed) { -+ fprintf_ind(file, ind, -+ "ERROR: images_ram_size is %ld, but computed value " -+ "is %ld\n", -+ images_ram_size, images_ram_size_computed); -+ } -+ if (images_disk_size != images_disk_size_computed) { -+ fprintf_ind(file, ind, -+ "ERROR: images_disk_size is %ld, but computed value " -+ "is %ld\n", -+ images_disk_size, images_disk_size_computed); -+ } -+ fprintf_ind(file, ind, "===========================================\n"); -+} -+ -+/// Executes `command` with the name of the file corresponding to `image_id` as -+/// the argument. Executes xmessage with an error message on failure. -+// TODO: Currently we do this for the first frame only. Not sure what to do with -+// animations. -+void gr_preview_image(uint32_t image_id, const char *exec) { -+ char command[256]; -+ size_t len; -+ Image *img = gr_find_image(image_id); -+ if (img) { -+ ImageFrame *frame = &img->first_frame; -+ char filename[MAX_FILENAME_SIZE]; -+ gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); -+ if (frame->disk_size == 0) { -+ len = snprintf(command, 255, -+ "xmessage 'Image with id=%u is not " -+ "fully copied to %s'", -+ image_id, sanitized_filename(filename)); -+ } else { -+ len = snprintf(command, 255, "%s %s &", exec, -+ sanitized_filename(filename)); -+ } -+ } else { -+ len = snprintf(command, 255, -+ "xmessage 'Cannot find image with id=%u'", -+ image_id); -+ } -+ if (len > 255) { -+ fprintf(stderr, "error: command too long: %s\n", command); -+ snprintf(command, 255, "xmessage 'error: command too long'"); -+ } -+ if (system(command) != 0) { -+ fprintf(stderr, "error: could not execute command %s\n", -+ command); -+ } -+} -+ -+/// Executes `<st> -e less <file>` where <file> is the name of a temporary file -+/// containing the information about an image and placement, and <st> is -+/// specified with `st_executable`. -+void gr_show_image_info(uint32_t image_id, uint32_t placement_id, -+ uint32_t imgcol, uint32_t imgrow, -+ char is_classic_placeholder, int32_t diacritic_count, -+ char *st_executable) { -+ char filename[MAX_FILENAME_SIZE]; -+ snprintf(filename, sizeof(filename), "%s/info-%u", cache_dir, image_id); -+ FILE *file = fopen(filename, "w"); -+ if (!file) { -+ perror("fopen"); -+ return; -+ } -+ // Basic information about the cell. -+ fprintf(file, "image_id = %u = 0x%08X\n", image_id, image_id); -+ fprintf(file, "placement_id = %u = 0x%08X\n", placement_id, placement_id); -+ fprintf(file, "column = %d, row = %d\n", imgcol, imgrow); -+ fprintf(file, "classic/unicode placeholder = %s\n", -+ is_classic_placeholder ? "classic" : "unicode"); -+ fprintf(file, "original diacritic count = %d\n", diacritic_count); -+ // Information about the image and the placement. -+ Image *img = gr_find_image(image_id); -+ ImagePlacement *placement = gr_find_placement(img, placement_id); -+ gr_dump_image_info(file, img, 0); -+ gr_dump_placement_info(file, placement, 0); -+ if (img) { -+ fprintf(file, "Frames:\n"); -+ foreach_frame(*img, frame, { -+ gr_dump_frame_info(file, frame, 4); -+ }); -+ } -+ if (placement) { -+ fprintf(file, "Placement pixmaps:\n"); -+ gr_dump_placement_pixmaps(file, placement, 4); -+ } -+ fclose(file); -+ char *argv[] = {st_executable, "-e", "less", filename, NULL}; -+ if (posix_spawnp(NULL, st_executable, NULL, NULL, argv, environ) != 0) { -+ perror("posix_spawnp"); -+ return; -+ } -+} -+ -+//////////////////////////////////////////////////////////////////////////////// -+// Appending and displaying image rectangles. -+//////////////////////////////////////////////////////////////////////////////// -+ -+/// Displays debug information in the rectangle using colors col1 and col2. -+static void gr_displayinfo(Drawable buf, ImageRect *rect, int col1, int col2, -+ const char *message) { -+ int w_pix = (rect->img_end_col - rect->img_start_col) * rect->cw; -+ int h_pix = (rect->img_end_row - rect->img_start_row) * rect->ch; -+ Display *disp = imlib_context_get_display(); -+ GC gc = XCreateGC(disp, buf, 0, NULL); -+ char info[MAX_INFO_LEN]; -+ if (rect->placement_id) -+ snprintf(info, MAX_INFO_LEN, "%s%u/%u [%d:%d)x[%d:%d)", message, -+ rect->image_id, rect->placement_id, -+ rect->img_start_col, rect->img_end_col, -+ rect->img_start_row, rect->img_end_row); -+ else -+ snprintf(info, MAX_INFO_LEN, "%s%u [%d:%d)x[%d:%d)", message, -+ rect->image_id, rect->img_start_col, rect->img_end_col, -+ rect->img_start_row, rect->img_end_row); -+ XSetForeground(disp, gc, col1); -+ XDrawString(disp, buf, gc, rect->screen_x_pix + 4, -+ rect->screen_y_pix + h_pix - 3, info, strlen(info)); -+ XSetForeground(disp, gc, col2); -+ XDrawString(disp, buf, gc, rect->screen_x_pix + 2, -+ rect->screen_y_pix + h_pix - 5, info, strlen(info)); -+ XFreeGC(disp, gc); -+} -+ -+/// Draws a rectangle (bounding box) for debugging. -+static void gr_showrect(Drawable buf, ImageRect *rect) { -+ int w_pix = (rect->img_end_col - rect->img_start_col) * rect->cw; -+ int h_pix = (rect->img_end_row - rect->img_start_row) * rect->ch; -+ Display *disp = imlib_context_get_display(); -+ GC gc = XCreateGC(disp, buf, 0, NULL); -+ XSetForeground(disp, gc, 0xFF00FF00); -+ XDrawRectangle(disp, buf, gc, rect->screen_x_pix, rect->screen_y_pix, -+ w_pix - 1, h_pix - 1); -+ XSetForeground(disp, gc, 0xFFFF0000); -+ XDrawRectangle(disp, buf, gc, rect->screen_x_pix + 1, -+ rect->screen_y_pix + 1, w_pix - 3, h_pix - 3); -+ XFreeGC(disp, gc); -+} -+ -+/// Updates the next redraw time for the given row. Resizes the -+/// next_redraw_times array if needed. -+static void gr_update_next_redraw_time(int row, Milliseconds next_redraw) { -+ if (next_redraw == 0) -+ return; -+ if (row >= kv_size(next_redraw_times)) { -+ size_t old_size = kv_size(next_redraw_times); -+ kv_a(Milliseconds, next_redraw_times, row); -+ for (size_t i = old_size; i <= row; ++i) -+ kv_A(next_redraw_times, i) = 0; -+ } -+ Milliseconds old_value = kv_A(next_redraw_times, row); -+ if (old_value == 0 || old_value > next_redraw) -+ kv_A(next_redraw_times, row) = next_redraw; -+} -+ -+/// Draws the given part of an image. -+static void gr_drawimagerect(Drawable buf, ImageRect *rect) { -+ ImagePlacement *placement = -+ gr_find_image_and_placement(rect->image_id, rect->placement_id); -+ // If the image does not exist or image display is switched off, draw -+ // the bounding box. -+ if (!placement || !graphics_display_images) { -+ gr_showrect(buf, rect); -+ if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) -+ gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); -+ return; -+ } -+ -+ Image *img = placement->image; -+ -+ if (img->last_redraw < drawing_start_time) { -+ // This is the first time we draw this image in this redraw -+ // cycle. Update the frame index we are going to display. Note -+ // that currently all image placements are synchronized. -+ int old_frame = img->current_frame; -+ gr_update_frame_index(img, drawing_start_time); -+ img->last_redraw = drawing_start_time; -+ } -+ -+ // Adjust next redraw times for the rows of this image rect. -+ if (img->next_redraw) { -+ for (int row = rect->screen_y_row; -+ row <= rect->screen_y_row + rect->img_end_row - -+ rect->img_start_row - 1; ++row) { -+ gr_update_next_redraw_time( -+ row, img->next_redraw); -+ } -+ } -+ -+ // Load the frame. -+ Pixmap pixmap = gr_load_pixmap(placement, img->current_frame, rect->cw, -+ rect->ch); -+ -+ // If the image couldn't be loaded, display the bounding box. -+ if (!pixmap) { -+ gr_showrect(buf, rect); -+ if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) -+ gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); -+ return; -+ } -+ -+ int src_x = rect->img_start_col * rect->cw; -+ int src_y = rect->img_start_row * rect->ch; -+ int width = (rect->img_end_col - rect->img_start_col) * rect->cw; -+ int height = (rect->img_end_row - rect->img_start_row) * rect->ch; -+ int dst_x = rect->screen_x_pix; -+ int dst_y = rect->screen_y_pix; -+ -+ // Display the image. -+ Display *disp = imlib_context_get_display(); -+ Visual *vis = imlib_context_get_visual(); -+ -+ // Create an xrender picture for the window. -+ XRenderPictFormat *win_format = -+ XRenderFindVisualFormat(disp, vis); -+ Picture window_pic = -+ XRenderCreatePicture(disp, buf, win_format, 0, NULL); -+ -+ // If needed, invert the image pixmap. Note that this naive approach of -+ // inverting the pixmap is not entirely correct, because the pixmap is -+ // premultiplied. But the result is good enough to visually indicate -+ // selection. -+ if (rect->reverse) { -+ unsigned pixmap_w = -+ (unsigned)placement->cols * placement->scaled_cw; -+ unsigned pixmap_h = -+ (unsigned)placement->rows * placement->scaled_ch; -+ Pixmap invpixmap = -+ XCreatePixmap(disp, buf, pixmap_w, pixmap_h, 32); -+ XGCValues gcv = {.function = GXcopyInverted}; -+ GC gc = XCreateGC(disp, invpixmap, GCFunction, &gcv); -+ XCopyArea(disp, pixmap, invpixmap, gc, 0, 0, pixmap_w, -+ pixmap_h, 0, 0); -+ XFreeGC(disp, gc); -+ pixmap = invpixmap; -+ } -+ -+ // Create a picture for the image pixmap. -+ XRenderPictFormat *pic_format = -+ XRenderFindStandardFormat(disp, PictStandardARGB32); -+ Picture pixmap_pic = -+ XRenderCreatePicture(disp, pixmap, pic_format, 0, NULL); -+ -+ // Composite the image onto the window. In the reverse mode we ignore -+ // the alpha channel of the image because the naive inversion above -+ // seems to invert the alpha channel as well. -+ int pictop = rect->reverse ? PictOpSrc : PictOpOver; -+ XRenderComposite(disp, pictop, pixmap_pic, 0, window_pic, -+ src_x, src_y, src_x, src_y, dst_x, dst_y, width, -+ height); -+ -+ // Free resources -+ XRenderFreePicture(disp, pixmap_pic); -+ XRenderFreePicture(disp, window_pic); -+ if (rect->reverse) -+ XFreePixmap(disp, pixmap); -+ -+ // In debug mode always draw bounding boxes and print info. -+ if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) { -+ gr_showrect(buf, rect); -+ gr_displayinfo(buf, rect, 0xFF000000, 0xFFFFFFFF, ""); -+ } -+} -+ -+/// Removes the given image rectangle. -+static void gr_freerect(ImageRect *rect) { memset(rect, 0, sizeof(ImageRect)); } -+ -+/// Returns the bottom coordinate of the rect. -+static int gr_getrectbottom(ImageRect *rect) { -+ return rect->screen_y_pix + -+ (rect->img_end_row - rect->img_start_row) * rect->ch; -+} -+ -+/// Prepare for image drawing. `cw` and `ch` are dimensions of the cell. -+void gr_start_drawing(Drawable buf, int cw, int ch) { -+ current_cw = cw; -+ current_ch = ch; -+ this_redraw_cycle_loaded_files = 0; -+ this_redraw_cycle_loaded_pixmaps = 0; -+ drawing_start_time = gr_now_ms(); -+ imlib_context_set_drawable(buf); -+} -+ -+/// Finish image drawing. This functions will draw all the rectangles left to -+/// draw. -+void gr_finish_drawing(Drawable buf) { -+ // Draw and then delete all known image rectangles. -+ for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { -+ ImageRect *rect = &image_rects[i]; -+ if (!rect->image_id) -+ continue; -+ gr_drawimagerect(buf, rect); -+ gr_freerect(rect); -+ } -+ -+ // Compute the delay until the next redraw as the minimum of the next -+ // redraw delays for all rows. -+ Milliseconds drawing_end_time = gr_now_ms(); -+ graphics_next_redraw_delay = INT_MAX; -+ for (int row = 0; row < kv_size(next_redraw_times); ++row) { -+ Milliseconds row_next_redraw = kv_A(next_redraw_times, row); -+ if (row_next_redraw > 0) { -+ int delay = MAX(graphics_animation_min_delay, -+ row_next_redraw - drawing_end_time); -+ graphics_next_redraw_delay = -+ MIN(graphics_next_redraw_delay, delay); -+ } -+ } -+ -+ // In debug mode display additional info. -+ if (graphics_debug_mode) { -+ int milliseconds = drawing_end_time - drawing_start_time; -+ -+ Display *disp = imlib_context_get_display(); -+ GC gc = XCreateGC(disp, buf, 0, NULL); -+ const char *debug_mode_str = -+ graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES -+ ? "(boxes shown) " -+ : ""; -+ int redraw_delay = graphics_next_redraw_delay == INT_MAX -+ ? -1 -+ : graphics_next_redraw_delay; -+ char info[MAX_INFO_LEN]; -+ snprintf(info, MAX_INFO_LEN, -+ "%sRender time: %d ms ram %ld K disk %ld K count " -+ "%d cell %dx%d delay %d", -+ debug_mode_str, milliseconds, images_ram_size / 1024, -+ images_disk_size / 1024, kh_size(images), current_cw, -+ current_ch, redraw_delay); -+ XSetForeground(disp, gc, 0xFF000000); -+ XFillRectangle(disp, buf, gc, 0, 0, 600, 16); -+ XSetForeground(disp, gc, 0xFFFFFFFF); -+ XDrawString(disp, buf, gc, 0, 14, info, strlen(info)); -+ XFreeGC(disp, gc); -+ -+ if (milliseconds > 0) { -+ fprintf(stderr, "%s (loaded %d files, %d pixmaps)\n", -+ info, this_redraw_cycle_loaded_files, -+ this_redraw_cycle_loaded_pixmaps); -+ } -+ } -+ -+ // Check the limits in case we have used too much ram for placements. -+ gr_check_limits(); -+} -+ -+// Add an image rectangle to the list of rectangles to draw. -+void gr_append_imagerect(Drawable buf, uint32_t image_id, uint32_t placement_id, -+ int img_start_col, int img_end_col, int img_start_row, -+ int img_end_row, int x_col, int y_row, int x_pix, -+ int y_pix, int cw, int ch, int reverse) { -+ current_cw = cw; -+ current_ch = ch; -+ -+ ImageRect new_rect; -+ new_rect.image_id = image_id; -+ new_rect.placement_id = placement_id; -+ new_rect.img_start_col = img_start_col; -+ new_rect.img_end_col = img_end_col; -+ new_rect.img_start_row = img_start_row; -+ new_rect.img_end_row = img_end_row; -+ new_rect.screen_y_row = y_row; -+ new_rect.screen_x_pix = x_pix; -+ new_rect.screen_y_pix = y_pix; -+ new_rect.ch = ch; -+ new_rect.cw = cw; -+ new_rect.reverse = reverse; -+ -+ // Display some red text in debug mode. -+ if (graphics_debug_mode == GRAPHICS_DEBUG_LOG_AND_BOXES) -+ gr_displayinfo(buf, &new_rect, 0xFF000000, 0xFFFF0000, "? "); -+ -+ // If it's the empty image (image_id=0) or an empty rectangle, do -+ // nothing. -+ if (image_id == 0 || img_end_col - img_start_col <= 0 || -+ img_end_row - img_start_row <= 0) -+ return; -+ // Try to find a rect to merge with. -+ ImageRect *free_rect = NULL; -+ for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { -+ ImageRect *rect = &image_rects[i]; -+ if (rect->image_id == 0) { -+ if (!free_rect) -+ free_rect = rect; -+ continue; -+ } -+ if (rect->image_id != image_id || -+ rect->placement_id != placement_id || rect->cw != cw || -+ rect->ch != ch || rect->reverse != reverse) -+ continue; -+ // We only support the case when the new stripe is added to the -+ // bottom of an existing rectangle and they are perfectly -+ // aligned. -+ if (rect->img_end_row == img_start_row && -+ gr_getrectbottom(rect) == y_pix) { -+ if (rect->img_start_col == img_start_col && -+ rect->img_end_col == img_end_col && -+ rect->screen_x_pix == x_pix) { -+ rect->img_end_row = img_end_row; -+ return; -+ } -+ } -+ } -+ // If we haven't merged the new rect with any existing rect, and there -+ // is no free rect, we have to render one of the existing rects. -+ if (!free_rect) { -+ for (size_t i = 0; i < MAX_IMAGE_RECTS; ++i) { -+ ImageRect *rect = &image_rects[i]; -+ if (!free_rect || gr_getrectbottom(free_rect) > -+ gr_getrectbottom(rect)) -+ free_rect = rect; -+ } -+ gr_drawimagerect(buf, free_rect); -+ gr_freerect(free_rect); -+ } -+ // Start a new rectangle in `free_rect`. -+ *free_rect = new_rect; -+} -+ -+/// Mark rows containing animations as dirty if it's time to redraw them. Must -+/// be called right after `gr_start_drawing`. -+void gr_mark_dirty_animations(int *dirty, int rows) { -+ if (rows < kv_size(next_redraw_times)) -+ kv_size(next_redraw_times) = rows; -+ if (rows * 2 < kv_max(next_redraw_times)) -+ kv_resize(Milliseconds, next_redraw_times, rows); -+ for (int i = 0; i < MIN(rows, kv_size(next_redraw_times)); ++i) { -+ if (dirty[i]) { -+ kv_A(next_redraw_times, i) = 0; -+ continue; -+ } -+ Milliseconds next_update = kv_A(next_redraw_times, i); -+ if (next_update > 0 && next_update <= drawing_start_time) { -+ dirty[i] = 1; -+ kv_A(next_redraw_times, i) = 0; -+ } -+ } -+} -+ -+//////////////////////////////////////////////////////////////////////////////// -+// Command parsing and handling. -+//////////////////////////////////////////////////////////////////////////////// -+ -+/// A parsed kitty graphics protocol command. -+typedef struct { -+ /// The command itself, without the 'G'. -+ char *command; -+ /// The payload (after ';'). -+ char *payload; -+ /// 'a=', may be 't', 'q', 'f', 'T', 'p', 'd', 'a'. -+ char action; -+ /// 'q=', 1 to suppress OK response, 2 to suppress errors too. -+ int quiet; -+ /// 'f=', use 24 or 32 for raw pixel data, 100 to autodetect with -+ /// imlib2. If 'f=0', will try to load with imlib2, then fallback to -+ /// 32-bit pixel data. -+ int format; -+ /// 'o=', may be 'z' for RFC 1950 ZLIB. -+ int compression; -+ /// 't=', may be 'f', 't' or 'd'. -+ char transmission_medium; -+ /// 'd=' -+ char delete_specifier; -+ /// 's=', 'v=', if 'a=t' or 'a=T', used only when 'f=24' or 'f=32'. -+ /// When 'a=f', this is the size of the frame rectangle when composed on -+ /// top of another frame. -+ int frame_pix_width, frame_pix_height; -+ /// 'x=', 'y=' - top-left corner of the source rectangle. -+ int src_pix_x, src_pix_y; -+ /// 'w=', 'h=' - width and height of the source rectangle. -+ int src_pix_width, src_pix_height; -+ /// 'r=', 'c=' -+ int rows, columns; -+ /// 'i=' -+ uint32_t image_id; -+ /// 'I=' -+ uint32_t image_number; -+ /// 'p=' -+ uint32_t placement_id; -+ /// 'm=', may be 0 or 1. -+ int more; -+ /// True if either 'm=0' or 'm=1' is specified. -+ char is_data_transmission; -+ /// True if turns out that this command is a continuation of a data -+ /// transmission and not the first one for this image. Populated by -+ /// `gr_handle_transmit_command`. -+ char is_direct_transmission_continuation; -+ /// 'S=', used to check the size of uploaded data. -+ int size; -+ /// 'U=', whether it's a virtual placement for Unicode placeholders. -+ int virtual; -+ /// 'C=', if true, do not move the cursor when displaying this placement -+ /// (non-virtual placements only). -+ char do_not_move_cursor; -+ // --------------------------------------------------------------------- -+ // Animation-related fields. Their keys often overlap with keys of other -+ // commands, so these make sense only if the action is 'a=f' (frame -+ // transmission) or 'a=a' (animation control). -+ // -+ // 'x=' and 'y=', the relative position of the frame image when it's -+ // composed on top of another frame. -+ int frame_dst_pix_x, frame_dst_pix_y; -+ /// 'X=', 'X=1' to replace colors instead of alpha blending on top of -+ /// the background color or frame. -+ char replace_instead_of_blending; -+ /// 'Y=', the background color in the 0xRRGGBBAA format (still -+ /// transmitted as a decimal number). -+ uint32_t background_color; -+ /// (Only for 'a=f'). 'c=', the 1-based index of the background frame. -+ int background_frame; -+ /// (Only for 'a=a'). 'c=', sets the index of the current frame. -+ int current_frame; -+ /// 'r=', the 1-based index of the frame to edit. -+ int edit_frame; -+ /// 'z=', the duration of the frame. Zero if not specified, negative if -+ /// the frame is gapless (i.e. skipped). -+ int gap; -+ /// (Only for 'a=a'). 's=', if non-zero, sets the state of the -+ /// animation, 1 to stop, 2 to run in loading mode, 3 to loop. -+ int animation_state; -+ /// (Only for 'a=a'). 'v=', if non-zero, sets the number of times the -+ /// animation will loop. 1 to loop infinitely, N to loop N-1 times. -+ int loops; -+} GraphicsCommand; -+ -+/// Replaces all non-printed characters in `str` with '?' and truncates the -+/// string to `max_size`, maybe inserting ellipsis at the end. -+static void sanitize_str(char *str, size_t max_size) { -+ assert(max_size >= 4); -+ for (size_t i = 0; i < max_size; ++i) { -+ unsigned c = str[i]; -+ if (c == '\0') -+ return; -+ if (c >= 128 || !isprint(c)) -+ str[i] = '?'; -+ } -+ str[max_size - 1] = '\0'; -+ str[max_size - 2] = '.'; -+ str[max_size - 3] = '.'; -+ str[max_size - 4] = '.'; -+} -+ -+/// A non-destructive version of `sanitize_str`. Uses a static buffer, so be -+/// careful. -+static const char *sanitized_filename(const char *str) { -+ static char buf[MAX_FILENAME_SIZE]; -+ strncpy(buf, str, sizeof(buf)); -+ sanitize_str(buf, sizeof(buf)); -+ return buf; -+} -+ -+/// Creates a response to the current command in `graphics_command_result`. -+static void gr_createresponse(uint32_t image_id, uint32_t image_number, -+ uint32_t placement_id, const char *msg) { -+ if (!image_id && !image_number && !placement_id) { -+ // Nobody expects the response in this case, so just print it to -+ // stderr. -+ fprintf(stderr, -+ "error: No image id or image number or placement_id, " -+ "but still there is a response: %s\n", -+ msg); -+ return; -+ } -+ char *buf = graphics_command_result.response; -+ size_t maxlen = MAX_GRAPHICS_RESPONSE_LEN; -+ size_t written; -+ written = snprintf(buf, maxlen, "\033_G"); -+ buf += written; -+ maxlen -= written; -+ if (image_id) { -+ written = snprintf(buf, maxlen, "i=%u,", image_id); -+ buf += written; -+ maxlen -= written; -+ } -+ if (image_number) { -+ written = snprintf(buf, maxlen, "I=%u,", image_number); -+ buf += written; -+ maxlen -= written; -+ } -+ if (placement_id) { -+ written = snprintf(buf, maxlen, "p=%u,", placement_id); -+ buf += written; -+ maxlen -= written; -+ } -+ buf[-1] = ';'; -+ written = snprintf(buf, maxlen, "%s\033\\", msg); -+ buf += written; -+ maxlen -= written; -+ buf[-2] = '\033'; -+ buf[-1] = '\\'; -+} -+ -+/// Creates the 'OK' response to the current command, unless suppressed or a -+/// non-final data transmission. -+static void gr_reportsuccess_cmd(GraphicsCommand *cmd) { -+ if (cmd->quiet < 1 && !cmd->more) -+ gr_createresponse(cmd->image_id, cmd->image_number, -+ cmd->placement_id, "OK"); -+} -+ -+/// Creates the 'OK' response to the current command (unless suppressed). -+static void gr_reportsuccess_frame(ImageFrame *frame) { -+ uint32_t id = frame->image->query_id ? frame->image->query_id -+ : frame->image->image_id; -+ if (frame->quiet < 1) -+ gr_createresponse(id, frame->image->image_number, -+ frame->image->initial_placement_id, "OK"); -+} -+ -+/// Creates an error response to the current command (unless suppressed). -+static void gr_reporterror_cmd(GraphicsCommand *cmd, const char *format, ...) { -+ char errmsg[MAX_GRAPHICS_RESPONSE_LEN]; -+ graphics_command_result.error = 1; -+ va_list args; -+ va_start(args, format); -+ vsnprintf(errmsg, MAX_GRAPHICS_RESPONSE_LEN, format, args); -+ va_end(args); -+ -+ fprintf(stderr, "%s in command: %s\n", errmsg, cmd->command); -+ if (cmd->quiet < 2) -+ gr_createresponse(cmd->image_id, cmd->image_number, -+ cmd->placement_id, errmsg); -+} -+ -+/// Creates an error response to the current command (unless suppressed). -+static void gr_reporterror_frame(ImageFrame *frame, const char *format, ...) { -+ char errmsg[MAX_GRAPHICS_RESPONSE_LEN]; -+ graphics_command_result.error = 1; -+ va_list args; -+ va_start(args, format); -+ vsnprintf(errmsg, MAX_GRAPHICS_RESPONSE_LEN, format, args); -+ va_end(args); -+ -+ if (!frame) { -+ fprintf(stderr, "%s\n", errmsg); -+ gr_createresponse(0, 0, 0, errmsg); -+ } else { -+ uint32_t id = frame->image->query_id ? frame->image->query_id -+ : frame->image->image_id; -+ fprintf(stderr, "%s id=%u\n", errmsg, id); -+ if (frame->quiet < 2) -+ gr_createresponse(id, frame->image->image_number, -+ frame->image->initial_placement_id, -+ errmsg); -+ } -+} -+ -+/// Loads an image and creates a success/failure response. Returns `frame`, or -+/// NULL if it's a query action and the image was deleted. -+static ImageFrame *gr_loadimage_and_report(ImageFrame *frame) { -+ gr_load_imlib_object(frame); -+ if (!frame->imlib_object) { -+ gr_reporterror_frame(frame, "EBADF: could not load image"); -+ } else { -+ gr_reportsuccess_frame(frame); -+ } -+ // If it was a query action, discard the image. -+ if (frame->image->query_id) { -+ gr_delete_image(frame->image); -+ return NULL; -+ } -+ return frame; -+} -+ -+/// Creates an appropriate uploading failure response to the current command. -+static void gr_reportuploaderror(ImageFrame *frame) { -+ switch (frame->uploading_failure) { -+ case 0: -+ return; -+ case ERROR_CANNOT_OPEN_CACHED_FILE: -+ gr_reporterror_frame(frame, -+ "EIO: could not create a file for image"); -+ break; -+ case ERROR_OVER_SIZE_LIMIT: -+ gr_reporterror_frame( -+ frame, -+ "EFBIG: the size of the uploaded image exceeded " -+ "the image size limit %u", -+ graphics_max_single_image_file_size); -+ break; -+ case ERROR_UNEXPECTED_SIZE: -+ gr_reporterror_frame(frame, -+ "EINVAL: the size of the uploaded image %u " -+ "doesn't match the expected size %u", -+ frame->disk_size, frame->expected_size); -+ break; -+ }; -+} -+ -+/// Displays a non-virtual placement. This functions records the information in -+/// `graphics_command_result`, the placeholder itself is created by the terminal -+/// after handling the current command in the graphics module. -+static void gr_display_nonvirtual_placement(ImagePlacement *placement) { -+ if (placement->virtual) -+ return; -+ if (placement->image->first_frame.status < STATUS_RAM_LOADING_SUCCESS) -+ return; -+ // Infer the placement size if needed. -+ gr_infer_placement_size_maybe(placement); -+ // Populate the information about the placeholder which will be created -+ // by the terminal. -+ graphics_command_result.create_placeholder = 1; -+ graphics_command_result.placeholder.image_id = placement->image->image_id; -+ graphics_command_result.placeholder.placement_id = placement->placement_id; -+ graphics_command_result.placeholder.columns = placement->cols; -+ graphics_command_result.placeholder.rows = placement->rows; -+ graphics_command_result.placeholder.do_not_move_cursor = -+ placement->do_not_move_cursor; -+ GR_LOG("Creating a placeholder for %u/%u %d x %d\n", -+ placement->image->image_id, placement->placement_id, -+ placement->cols, placement->rows); -+} -+ -+/// Marks the rows that are occupied by the image as dirty. -+static void gr_schedule_image_redraw(Image *img) { -+ if (!img) -+ return; -+ gr_schedule_image_redraw_by_id(img->image_id); -+} -+ -+/// Appends data from `payload` to the frame `frame` when using direct -+/// transmission. Note that we report errors only for the final command -+/// (`!more`) to avoid spamming the client. If the frame is not specified, use -+/// the image id and frame index we are currently uploading. -+static void gr_append_data(ImageFrame *frame, const char *payload, int more) { -+ if (!frame) { -+ Image *img = gr_find_image(current_upload_image_id); -+ frame = gr_get_frame(img, current_upload_frame_index); -+ GR_LOG("Appending data to image %u frame %d\n", -+ current_upload_image_id, current_upload_frame_index); -+ if (!img) -+ GR_LOG("ERROR: this image doesn't exist\n"); -+ if (!frame) -+ GR_LOG("ERROR: this frame doesn't exist\n"); -+ } -+ if (!more) { -+ current_upload_image_id = 0; -+ current_upload_frame_index = 0; -+ } -+ if (!frame) { -+ if (!more) -+ gr_reporterror_frame(NULL, "ENOENT: could not find the " -+ "image to append data to"); -+ return; -+ } -+ if (frame->status != STATUS_UPLOADING) { -+ if (!more) -+ gr_reportuploaderror(frame); -+ return; -+ } -+ -+ // Decode the data. -+ size_t data_size = 0; -+ char *data = gr_base64dec(payload, &data_size); -+ -+ GR_LOG("appending %u + %zu = %zu bytes\n", frame->disk_size, data_size, -+ frame->disk_size + data_size); -+ -+ // Do not append this data if the image exceeds the size limit. -+ if (frame->disk_size + data_size > -+ graphics_max_single_image_file_size || -+ frame->expected_size > graphics_max_single_image_file_size) { -+ free(data); -+ gr_delete_imagefile(frame); -+ frame->uploading_failure = ERROR_OVER_SIZE_LIMIT; -+ if (!more) -+ gr_reportuploaderror(frame); -+ return; -+ } -+ -+ // If there is no open file corresponding to the image, create it. -+ if (!frame->open_file) { -+ gr_make_sure_tmpdir_exists(); -+ char filename[MAX_FILENAME_SIZE]; -+ gr_get_frame_filename(frame, filename, MAX_FILENAME_SIZE); -+ FILE *file = fopen(filename, frame->disk_size ? "a" : "w"); -+ if (!file) { -+ frame->status = STATUS_UPLOADING_ERROR; -+ frame->uploading_failure = ERROR_CANNOT_OPEN_CACHED_FILE; -+ if (!more) -+ gr_reportuploaderror(frame); -+ return; -+ } -+ frame->open_file = file; -+ } -+ -+ // Write data to the file and update disk size variables. -+ fwrite(data, 1, data_size, frame->open_file); -+ free(data); -+ frame->disk_size += data_size; -+ frame->image->total_disk_size += data_size; -+ images_disk_size += data_size; -+ gr_touch_frame(frame); -+ -+ if (more) { -+ current_upload_image_id = frame->image->image_id; -+ current_upload_frame_index = frame->index; -+ } else { -+ current_upload_image_id = 0; -+ current_upload_frame_index = 0; -+ // Close the file. -+ if (frame->open_file) { -+ fclose(frame->open_file); -+ frame->open_file = NULL; -+ } -+ frame->status = STATUS_UPLOADING_SUCCESS; -+ uint32_t placement_id = frame->image->default_placement; -+ if (frame->expected_size && -+ frame->expected_size != frame->disk_size) { -+ // Report failure if the uploaded image size doesn't -+ // match the expected size. -+ frame->status = STATUS_UPLOADING_ERROR; -+ frame->uploading_failure = ERROR_UNEXPECTED_SIZE; -+ gr_reportuploaderror(frame); -+ } else { -+ // Make sure to redraw all existing image instances. -+ gr_schedule_image_redraw(frame->image); -+ // Try to load the image into ram and report the result. -+ frame = gr_loadimage_and_report(frame); -+ // If there is a non-virtual image placement, we may -+ // need to display it. -+ if (frame && frame->index == 1) { -+ Image *img = frame->image; -+ ImagePlacement *placement = NULL; -+ kh_foreach_value(img->placements, placement, { -+ gr_display_nonvirtual_placement(placement); -+ }); -+ } -+ } -+ } -+ -+ // Check whether we need to delete old images. -+ gr_check_limits(); -+} -+ -+/// Finds the image either by id or by number specified in the command and sets -+/// the image_id of `cmd` if the image was found. -+static Image *gr_find_image_for_command(GraphicsCommand *cmd) { -+ if (cmd->image_id) -+ return gr_find_image(cmd->image_id); -+ Image *img = NULL; -+ // If the image number is not specified, we can't find the image, unless -+ // it's a put command, in which case we will try the last image. -+ if (cmd->image_number == 0 && cmd->action == 'p') -+ img = gr_find_image(last_image_id); -+ else -+ img = gr_find_image_by_number(cmd->image_number); -+ if (img) -+ cmd->image_id = img->image_id; -+ return img; -+} -+ -+/// Creates a new image or a new frame in an existing image (depending on the -+/// command's action) and initializes its parameters from the command. -+static ImageFrame *gr_new_image_or_frame_from_command(GraphicsCommand *cmd) { -+ if (cmd->format != 0 && cmd->format != 32 && cmd->format != 24 && -+ cmd->compression != 0) { -+ gr_reporterror_cmd(cmd, "EINVAL: compression is supported only " -+ "for raw pixel data (f=32 or f=24)"); -+ // Even though we report an error, we still create an image. -+ } -+ -+ Image *img = NULL; -+ if (cmd->action == 'f') { -+ // If it's a frame transmission action, there must be an -+ // existing image. -+ img = gr_find_image_for_command(cmd); -+ if (!img) { -+ gr_reporterror_cmd(cmd, "ENOENT: image not found"); -+ return NULL; -+ } -+ } else { -+ // Otherwise create a new image object. If the action is `q`, -+ // we'll use random id instead of the one specified in the -+ // command. -+ uint32_t image_id = cmd->action == 'q' ? 0 : cmd->image_id; -+ img = gr_new_image(image_id); -+ if (!img) -+ return NULL; -+ if (cmd->action == 'q') -+ img->query_id = cmd->image_id; -+ else if (!cmd->image_id) -+ cmd->image_id = img->image_id; -+ // Set the image number. -+ img->image_number = cmd->image_number; -+ } -+ -+ ImageFrame *frame = gr_append_new_frame(img); -+ // Initialize the frame. -+ frame->expected_size = cmd->size; -+ frame->format = cmd->format; -+ frame->compression = cmd->compression; -+ frame->background_color = cmd->background_color; -+ frame->background_frame_index = cmd->background_frame; -+ frame->gap = cmd->gap; -+ img->total_duration += frame->gap; -+ frame->blend = !cmd->replace_instead_of_blending; -+ frame->data_pix_width = cmd->frame_pix_width; -+ frame->data_pix_height = cmd->frame_pix_height; -+ if (cmd->action == 'f') { -+ frame->x = cmd->frame_dst_pix_x; -+ frame->y = cmd->frame_dst_pix_y; -+ } -+ // We save the quietness information in the frame because for direct -+ // transmission subsequent transmission command won't contain this info. -+ frame->quiet = cmd->quiet; -+ return frame; -+} -+ -+/// Removes a file if it actually looks like a temporary file. -+static void gr_delete_tmp_file(const char *filename) { -+ if (strstr(filename, "tty-graphics-protocol") == NULL) -+ return; -+ if (strstr(filename, "/tmp/") != filename) { -+ const char *tmpdir = getenv("TMPDIR"); -+ if (!tmpdir || !tmpdir[0] || -+ strstr(filename, tmpdir) != filename) -+ return; -+ } -+ unlink(filename); -+} -+ -+/// Handles a data transmission command. -+static ImageFrame *gr_handle_transmit_command(GraphicsCommand *cmd) { -+ // The default is direct transmission. -+ if (!cmd->transmission_medium) -+ cmd->transmission_medium = 'd'; -+ -+ // If neither id, nor image number is specified, and the transmission -+ // medium is 'd' (or unspecified), and there is an active direct upload, -+ // this is a continuation of the upload. -+ if (current_upload_image_id != 0 && cmd->image_id == 0 && -+ cmd->image_number == 0 && cmd->transmission_medium == 'd') { -+ cmd->image_id = current_upload_image_id; -+ GR_LOG("No images id is specified, continuing uploading %u\n", -+ cmd->image_id); -+ } -+ -+ ImageFrame *frame = NULL; -+ if (cmd->transmission_medium == 'f' || -+ cmd->transmission_medium == 't') { -+ // File transmission. -+ // Create a new image or a new frame of an existing image. -+ frame = gr_new_image_or_frame_from_command(cmd); -+ if (!frame) -+ return NULL; -+ last_image_id = frame->image->image_id; -+ // Decode the filename. -+ char *original_filename = gr_base64dec(cmd->payload, NULL); -+ GR_LOG("Copying image %s\n", -+ sanitized_filename(original_filename)); -+ // Stat the file and check that it's a regular file and not too -+ // big. -+ struct stat st; -+ int stat_res = stat(original_filename, &st); -+ const char *stat_error = NULL; -+ if (stat_res) -+ stat_error = strerror(errno); -+ else if (!S_ISREG(st.st_mode)) -+ stat_error = "Not a regular file"; -+ else if (st.st_size == 0) -+ stat_error = "The size of the file is zero"; -+ else if (st.st_size > graphics_max_single_image_file_size) -+ stat_error = "The file is too large"; -+ if (stat_error) { -+ gr_reporterror_cmd(cmd, -+ "EBADF: %s", stat_error); -+ fprintf(stderr, "Could not load the file %s\n", -+ sanitized_filename(original_filename)); -+ frame->status = STATUS_UPLOADING_ERROR; -+ frame->uploading_failure = ERROR_CANNOT_COPY_FILE; -+ } else { -+ gr_make_sure_tmpdir_exists(); -+ // Build the filename for the cached copy of the file. -+ char cache_filename[MAX_FILENAME_SIZE]; -+ gr_get_frame_filename(frame, cache_filename, -+ MAX_FILENAME_SIZE); -+ // We will create a symlink to the original file, and -+ // then copy the file to the temporary cache dir. We do -+ // this symlink trick mostly to be able to use cp for -+ // copying, and avoid escaping file name characters when -+ // calling system at the same time. -+ char tmp_filename_symlink[MAX_FILENAME_SIZE + 4] = {0}; -+ strcat(tmp_filename_symlink, cache_filename); -+ strcat(tmp_filename_symlink, ".sym"); -+ char command[MAX_FILENAME_SIZE + 256]; -+ size_t len = -+ snprintf(command, MAX_FILENAME_SIZE + 255, -+ "cp '%s' '%s'", tmp_filename_symlink, -+ cache_filename); -+ if (len > MAX_FILENAME_SIZE + 255 || -+ symlink(original_filename, tmp_filename_symlink) || -+ system(command) != 0) { -+ gr_reporterror_cmd(cmd, -+ "EBADF: could not copy the " -+ "image to the cache dir"); -+ fprintf(stderr, -+ "Could not copy the image " -+ "%s (symlink %s) to %s", -+ sanitized_filename(original_filename), -+ tmp_filename_symlink, cache_filename); -+ frame->status = STATUS_UPLOADING_ERROR; -+ frame->uploading_failure = ERROR_CANNOT_COPY_FILE; -+ } else { -+ // Get the file size of the copied file. -+ frame->status = STATUS_UPLOADING_SUCCESS; -+ frame->disk_size = st.st_size; -+ frame->image->total_disk_size += st.st_size; -+ images_disk_size += frame->disk_size; -+ if (frame->expected_size && -+ frame->expected_size != frame->disk_size) { -+ // The file has unexpected size. -+ frame->status = STATUS_UPLOADING_ERROR; -+ frame->uploading_failure = -+ ERROR_UNEXPECTED_SIZE; -+ gr_reportuploaderror(frame); -+ } else { -+ // Everything seems fine, try to load -+ // and redraw existing instances. -+ gr_schedule_image_redraw(frame->image); -+ frame = gr_loadimage_and_report(frame); -+ } -+ } -+ // Delete the symlink. -+ unlink(tmp_filename_symlink); -+ // Delete the original file if it's temporary. -+ if (cmd->transmission_medium == 't') -+ gr_delete_tmp_file(original_filename); -+ } -+ free(original_filename); -+ gr_check_limits(); -+ } else if (cmd->transmission_medium == 'd') { -+ // Direct transmission (default if 't' is not specified). -+ frame = gr_get_last_frame(gr_find_image_for_command(cmd)); -+ if (frame && frame->status == STATUS_UPLOADING) { -+ // This is a continuation of the previous transmission. -+ cmd->is_direct_transmission_continuation = 1; -+ gr_append_data(frame, cmd->payload, cmd->more); -+ return frame; -+ } -+ // If no action is specified, it's not the first transmission -+ // command. If we couldn't find the image, something went wrong -+ // and we should just drop this command. -+ if (cmd->action == 0) -+ return NULL; -+ // Otherwise create a new image or frame structure. -+ frame = gr_new_image_or_frame_from_command(cmd); -+ if (!frame) -+ return NULL; -+ last_image_id = frame->image->image_id; -+ frame->status = STATUS_UPLOADING; -+ // Start appending data. -+ gr_append_data(frame, cmd->payload, cmd->more); -+ } else { -+ gr_reporterror_cmd( -+ cmd, -+ "EINVAL: transmission medium '%c' is not supported", -+ cmd->transmission_medium); -+ return NULL; -+ } -+ -+ return frame; -+} -+ -+/// Handles the 'put' command by creating a placement. -+static void gr_handle_put_command(GraphicsCommand *cmd) { -+ if (cmd->image_id == 0 && cmd->image_number == 0) { -+ gr_reporterror_cmd(cmd, -+ "EINVAL: neither image id nor image number " -+ "are specified or both are zero"); -+ return; -+ } -+ -+ // Find the image with the id or number. -+ Image *img = gr_find_image_for_command(cmd); -+ if (!img) { -+ gr_reporterror_cmd(cmd, "ENOENT: image not found"); -+ return; -+ } -+ -+ // Create a placement. If a placement with the same id already exists, -+ // it will be deleted. If the id is zero, a random id will be generated. -+ ImagePlacement *placement = gr_new_placement(img, cmd->placement_id); -+ placement->virtual = cmd->virtual; -+ placement->src_pix_x = cmd->src_pix_x; -+ placement->src_pix_y = cmd->src_pix_y; -+ placement->src_pix_width = cmd->src_pix_width; -+ placement->src_pix_height = cmd->src_pix_height; -+ placement->cols = cmd->columns; -+ placement->rows = cmd->rows; -+ placement->do_not_move_cursor = cmd->do_not_move_cursor; -+ -+ if (placement->virtual) { -+ placement->scale_mode = SCALE_MODE_CONTAIN; -+ } else if (placement->cols && placement->rows) { -+ // For classic placements the default is to stretch the image if -+ // both cols and rows are specified. -+ placement->scale_mode = SCALE_MODE_FILL; -+ } else if (placement->cols || placement->rows) { -+ // But if only one of them is specified, the default is to -+ // contain. -+ placement->scale_mode = SCALE_MODE_CONTAIN; -+ } else { -+ // If none of them are specified, the default is to use the -+ // original size. -+ placement->scale_mode = SCALE_MODE_NONE; -+ } -+ -+ // Display the placement unless it's virtual. -+ gr_display_nonvirtual_placement(placement); -+ -+ // Report success. -+ gr_reportsuccess_cmd(cmd); -+} -+ -+/// Information about what to delete. -+typedef struct DeletionData { -+ uint32_t image_id; -+ uint32_t placement_id; -+ /// If true, delete the image object if there are no more placements. -+ char delete_image_if_no_ref; -+} DeletionData; -+ -+/// The callback called for each cell to perform deletion. -+static int gr_deletion_callback(void *data, uint32_t image_id, -+ uint32_t placement_id, int col, -+ int row, char is_classic) { -+ DeletionData *del_data = data; -+ // Leave unicode placeholders alone. -+ if (!is_classic) -+ return 0; -+ if (del_data->image_id && del_data->image_id != image_id) -+ return 0; -+ if (del_data->placement_id && del_data->placement_id != placement_id) -+ return 0; -+ Image *img = gr_find_image(image_id); -+ // If the image is already deleted, just erase the placeholder. -+ if (!img) -+ return 1; -+ // Delete the placement. -+ if (placement_id) -+ gr_delete_placement(gr_find_placement(img, placement_id)); -+ // Delete the image if image deletion is requested (uppercase delete -+ // specifier) and there are no more placements. -+ if (del_data->delete_image_if_no_ref && kh_size(img->placements) == 0) -+ gr_delete_image(img); -+ return 1; -+} -+ -+/// Handles the delete command. -+static void gr_handle_delete_command(GraphicsCommand *cmd) { -+ DeletionData del_data = {0}; -+ del_data.delete_image_if_no_ref = isupper(cmd->delete_specifier) != 0; -+ char d = tolower(cmd->delete_specifier); -+ -+ if (d == 'n') { -+ d = 'i'; -+ Image *img = gr_find_image_by_number(cmd->image_number); -+ if (!img) -+ return; -+ del_data.image_id = img->image_id; -+ } -+ -+ if (!d || d == 'a') { -+ // Delete all visible placements. -+ gr_for_each_image_cell(gr_deletion_callback, &del_data); -+ } else if (d == 'i') { -+ // Delete the specified image by image id and maybe placement -+ // id. -+ if (!del_data.image_id) -+ del_data.image_id = cmd->image_id; -+ if (!del_data.image_id) { -+ fprintf(stderr, -+ "ERROR: image id is not specified in the " -+ "delete command\n"); -+ return; -+ } -+ del_data.placement_id = cmd->placement_id; -+ // NOTE: It's not very clear whether we should delete the image -+ // even if there are no _visible_ placements to delete. We do -+ // this because otherwise there is no way to delete an image -+ // with virtual placements in one command. -+ if (!del_data.placement_id && del_data.delete_image_if_no_ref) -+ gr_delete_image(gr_find_image(cmd->image_id)); -+ gr_for_each_image_cell(gr_deletion_callback, &del_data); -+ } else { -+ fprintf(stderr, -+ "WARNING: unsupported value of the d key: '%c'. The " -+ "command is ignored.\n", -+ cmd->delete_specifier); -+ } -+} -+ -+static void gr_handle_animation_control_command(GraphicsCommand *cmd) { -+ if (cmd->image_id == 0 && cmd->image_number == 0) { -+ gr_reporterror_cmd(cmd, -+ "EINVAL: neither image id nor image number " -+ "are specified or both are zero"); -+ return; -+ } -+ -+ // Find the image with the id or number. -+ Image *img = gr_find_image_for_command(cmd); -+ if (!img) { -+ gr_reporterror_cmd(cmd, "ENOENT: image not found"); -+ return; -+ } -+ -+ // Find the frame to edit, if requested. -+ ImageFrame *frame = NULL; -+ if (cmd->edit_frame) -+ frame = gr_get_frame(img, cmd->edit_frame); -+ if (cmd->edit_frame || cmd->gap) { -+ if (!frame) { -+ gr_reporterror_cmd(cmd, "ENOENT: frame %d not found", -+ cmd->edit_frame); -+ return; -+ } -+ if (cmd->gap) { -+ img->total_duration -= frame->gap; -+ frame->gap = cmd->gap; -+ img->total_duration += frame->gap; -+ } -+ } -+ -+ // Set animation-related parameters of the image. -+ if (cmd->current_frame) -+ img->current_frame = cmd->current_frame; -+ if (cmd->animation_state) { -+ if (cmd->animation_state == 1) { -+ img->animation_state = ANIMATION_STATE_STOPPED; -+ } else if (cmd->animation_state == 2) { -+ img->animation_state = ANIMATION_STATE_LOADING; -+ } else if (cmd->animation_state == 3) { -+ img->animation_state = ANIMATION_STATE_LOOPING; -+ } else { -+ gr_reporterror_cmd( -+ cmd, "EINVAL: invalid animation state: %d", -+ cmd->animation_state); -+ } -+ } -+ // TODO: Set the number of loops to cmd->loops -+ -+ // Make sure we redraw all instances of the image. -+ gr_schedule_image_redraw(img); -+} -+ -+/// Handles a command. -+static void gr_handle_command(GraphicsCommand *cmd) { -+ if (!cmd->image_id && !cmd->image_number) { -+ // If there is no image id or image number, nobody expects a -+ // response, so set quiet to 2. -+ cmd->quiet = 2; -+ } -+ ImageFrame *frame = NULL; -+ switch (cmd->action) { -+ case 0: -+ // If no action is specified, it may be a data transmission -+ // command if 'm=' is specified. -+ if (cmd->is_data_transmission) { -+ gr_handle_transmit_command(cmd); -+ break; -+ } -+ gr_reporterror_cmd(cmd, "EINVAL: no action specified"); -+ break; -+ case 't': -+ case 'q': -+ case 'f': -+ // Transmit data. 'q' means query, which is basically the same -+ // as transmit, but the image is discarded, and the id is fake. -+ // 'f' appends a frame to an existing image. -+ gr_handle_transmit_command(cmd); -+ break; -+ case 'p': -+ // Display (put) the image. -+ gr_handle_put_command(cmd); -+ break; -+ case 'T': -+ // Transmit and display. -+ frame = gr_handle_transmit_command(cmd); -+ if (frame && !cmd->is_direct_transmission_continuation) { -+ gr_handle_put_command(cmd); -+ if (cmd->placement_id) -+ frame->image->initial_placement_id = -+ cmd->placement_id; -+ } -+ break; -+ case 'd': -+ gr_handle_delete_command(cmd); -+ break; -+ case 'a': -+ gr_handle_animation_control_command(cmd); -+ break; -+ default: -+ gr_reporterror_cmd(cmd, "EINVAL: unsupported action: %c", -+ cmd->action); -+ return; -+ } -+} -+ -+/// A partially parsed key-value pair. -+typedef struct KeyAndValue { -+ char *key_start; -+ char *val_start; -+ unsigned key_len, val_len; -+} KeyAndValue; -+ -+/// Parses the value of a key and assigns it to the appropriate field of `cmd`. -+static void gr_set_keyvalue(GraphicsCommand *cmd, KeyAndValue *kv) { -+ char *key_start = kv->key_start; -+ char *key_end = key_start + kv->key_len; -+ char *value_start = kv->val_start; -+ char *value_end = value_start + kv->val_len; -+ // Currently all keys are one-character. -+ if (key_end - key_start != 1) { -+ gr_reporterror_cmd(cmd, "EINVAL: unknown key of length %ld: %s", -+ key_end - key_start, key_start); -+ return; -+ } -+ long num = 0; -+ if (*key_start == 'a' || *key_start == 't' || *key_start == 'd' || -+ *key_start == 'o') { -+ // Some keys have one-character values. -+ if (value_end - value_start != 1) { -+ gr_reporterror_cmd( -+ cmd, -+ "EINVAL: value of 'a', 't' or 'd' must be a " -+ "single char: %s", -+ key_start); -+ return; -+ } -+ } else { -+ // All the other keys have integer values. -+ char *num_end = NULL; -+ num = strtol(value_start, &num_end, 10); -+ if (num_end != value_end) { -+ gr_reporterror_cmd( -+ cmd, "EINVAL: could not parse number value: %s", -+ key_start); -+ return; -+ } -+ } -+ switch (*key_start) { -+ case 'a': -+ cmd->action = *value_start; -+ break; -+ case 't': -+ cmd->transmission_medium = *value_start; -+ break; -+ case 'd': -+ cmd->delete_specifier = *value_start; -+ break; -+ case 'q': -+ cmd->quiet = num; -+ break; -+ case 'f': -+ cmd->format = num; -+ if (num != 0 && num != 24 && num != 32 && num != 100) { -+ gr_reporterror_cmd( -+ cmd, -+ "EINVAL: unsupported format specification: %s", -+ key_start); -+ } -+ break; -+ case 'o': -+ cmd->compression = *value_start; -+ if (cmd->compression != 'z') { -+ gr_reporterror_cmd(cmd, -+ "EINVAL: unsupported compression " -+ "specification: %s", -+ key_start); -+ } -+ break; -+ case 's': -+ if (cmd->action == 'a') -+ cmd->animation_state = num; -+ else -+ cmd->frame_pix_width = num; -+ break; -+ case 'v': -+ if (cmd->action == 'a') -+ cmd->loops = num; -+ else -+ cmd->frame_pix_height = num; -+ break; -+ case 'i': -+ cmd->image_id = num; -+ break; -+ case 'I': -+ cmd->image_number = num; -+ break; -+ case 'p': -+ cmd->placement_id = num; -+ break; -+ case 'x': -+ cmd->src_pix_x = num; -+ cmd->frame_dst_pix_x = num; -+ break; -+ case 'y': -+ if (cmd->action == 'f') -+ cmd->frame_dst_pix_y = num; -+ else -+ cmd->src_pix_y = num; -+ break; -+ case 'w': -+ cmd->src_pix_width = num; -+ break; -+ case 'h': -+ cmd->src_pix_height = num; -+ break; -+ case 'c': -+ if (cmd->action == 'f') -+ cmd->background_frame = num; -+ else if (cmd->action == 'a') -+ cmd->current_frame = num; -+ else -+ cmd->columns = num; -+ break; -+ case 'r': -+ if (cmd->action == 'f' || cmd->action == 'a') -+ cmd->edit_frame = num; -+ else -+ cmd->rows = num; -+ break; -+ case 'm': -+ cmd->is_data_transmission = 1; -+ cmd->more = num; -+ break; -+ case 'S': -+ cmd->size = num; -+ break; -+ case 'U': -+ cmd->virtual = num; -+ break; -+ case 'X': -+ if (cmd->action == 'f') -+ cmd->replace_instead_of_blending = num; -+ else -+ break; /*ignore*/ -+ break; -+ case 'Y': -+ if (cmd->action == 'f') -+ cmd->background_color = num; -+ else -+ break; /*ignore*/ -+ break; -+ case 'z': -+ if (cmd->action == 'f' || cmd->action == 'a') -+ cmd->gap = num; -+ else -+ break; /*ignore*/ -+ break; -+ case 'C': -+ cmd->do_not_move_cursor = num; -+ break; -+ default: -+ gr_reporterror_cmd(cmd, "EINVAL: unsupported key: %s", -+ key_start); -+ return; -+ } -+} -+ -+/// Parse and execute a graphics command. `buf` must start with 'G' and contain -+/// at least `len + 1` characters. Returns 1 on success. -+int gr_parse_command(char *buf, size_t len) { -+ if (buf[0] != 'G') -+ return 0; -+ -+ memset(&graphics_command_result, 0, sizeof(GraphicsCommandResult)); -+ -+ global_command_counter++; -+ GR_LOG("### Command %lu: %.80s\n", global_command_counter, buf); -+ -+ // Eat the 'G'. -+ ++buf; -+ --len; -+ -+ GraphicsCommand cmd = {.command = buf}; -+ // The state of parsing. 'k' to parse key, 'v' to parse value, 'p' to -+ // parse the payload. -+ char state = 'k'; -+ // An array of partially parsed key-value pairs. -+ KeyAndValue key_vals[32]; -+ unsigned key_vals_count = 0; -+ char *key_start = buf; -+ char *key_end = NULL; -+ char *val_start = NULL; -+ char *val_end = NULL; -+ char *c = buf; -+ while (c - buf < len + 1) { -+ if (state == 'k') { -+ switch (*c) { -+ case ',': -+ case ';': -+ case '\0': -+ state = *c == ',' ? 'k' : 'p'; -+ key_end = c; -+ gr_reporterror_cmd( -+ &cmd, "EINVAL: key without value: %s ", -+ key_start); -+ break; -+ case '=': -+ key_end = c; -+ state = 'v'; -+ val_start = c + 1; -+ break; -+ default: -+ break; -+ } -+ } else if (state == 'v') { -+ switch (*c) { -+ case ',': -+ case ';': -+ case '\0': -+ state = *c == ',' ? 'k' : 'p'; -+ val_end = c; -+ if (key_vals_count >= -+ sizeof(key_vals) / sizeof(*key_vals)) { -+ gr_reporterror_cmd(&cmd, -+ "EINVAL: too many " -+ "key-value pairs"); -+ break; -+ } -+ key_vals[key_vals_count].key_start = key_start; -+ key_vals[key_vals_count].val_start = val_start; -+ key_vals[key_vals_count].key_len = -+ key_end - key_start; -+ key_vals[key_vals_count].val_len = -+ val_end - val_start; -+ ++key_vals_count; -+ key_start = c + 1; -+ break; -+ default: -+ break; -+ } -+ } else if (state == 'p') { -+ cmd.payload = c; -+ // break out of the loop, we don't check the payload -+ break; -+ } -+ ++c; -+ } -+ -+ // Set the action key ('a=') first because we need it to disambiguate -+ // some keys. Also set 'i=' and 'I=' for better error reporting. -+ for (unsigned i = 0; i < key_vals_count; ++i) { -+ if (key_vals[i].key_len == 1) { -+ char *start = key_vals[i].key_start; -+ if (*start == 'a' || *start == 'i' || *start == 'I') { -+ gr_set_keyvalue(&cmd, &key_vals[i]); -+ break; -+ } -+ } -+ } -+ // Set the rest of the keys. -+ for (unsigned i = 0; i < key_vals_count; ++i) -+ gr_set_keyvalue(&cmd, &key_vals[i]); -+ -+ if (!cmd.payload) -+ cmd.payload = buf + len; -+ -+ if (cmd.payload && cmd.payload[0]) -+ GR_LOG(" payload size: %ld\n", strlen(cmd.payload)); -+ -+ if (!graphics_command_result.error) -+ gr_handle_command(&cmd); -+ -+ if (graphics_debug_mode) { -+ fprintf(stderr, "Response: "); -+ for (const char *resp = graphics_command_result.response; -+ *resp != '\0'; ++resp) { -+ if (isprint(*resp)) -+ fprintf(stderr, "%c", *resp); -+ else -+ fprintf(stderr, "(0x%x)", *resp); -+ } -+ fprintf(stderr, "\n"); -+ } -+ -+ // Make sure that we suppress response if needed. Usually cmd.quiet is -+ // taken into account when creating the response, but it's not very -+ // reliable in the current implementation. -+ if (cmd.quiet) { -+ if (!graphics_command_result.error || cmd.quiet >= 2) -+ graphics_command_result.response[0] = '\0'; -+ } -+ -+ return 1; -+} -+ -+//////////////////////////////////////////////////////////////////////////////// -+// base64 decoding part is basically copied from st.c -+//////////////////////////////////////////////////////////////////////////////// -+ -+static const char gr_base64_digits[] = { -+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, -+ 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, -+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, -+ 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, -+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, -+ 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; -+ -+static char gr_base64_getc(const char **src) { -+ while (**src && !isprint(**src)) -+ (*src)++; -+ return **src ? *((*src)++) : '='; /* emulate padding if string ends */ -+} -+ -+char *gr_base64dec(const char *src, size_t *size) { -+ size_t in_len = strlen(src); -+ char *result, *dst; -+ -+ result = dst = malloc((in_len + 3) / 4 * 3 + 1); -+ while (*src) { -+ int a = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; -+ int b = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; -+ int c = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; -+ int d = gr_base64_digits[(unsigned char)gr_base64_getc(&src)]; -+ -+ if (a == -1 || b == -1) -+ break; -+ -+ *dst++ = (a << 2) | ((b & 0x30) >> 4); -+ if (c == -1) -+ break; -+ *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); -+ if (d == -1) -+ break; -+ *dst++ = ((c & 0x03) << 6) | d; -+ } -+ *dst = '\0'; -+ if (size) { -+ *size = dst - result; -+ } -+ return result; -+} -diff --git a/graphics.h b/graphics.h -new file mode 100644 -index 0000000..2e75dea ---- /dev/null -+++ b/graphics.h -@@ -0,0 +1,107 @@ -+ -+#include <stdint.h> -+#include <sys/types.h> -+#include <X11/Xlib.h> -+ -+/// Initialize the graphics module. -+void gr_init(Display *disp, Visual *vis, Colormap cm); -+/// Deinitialize the graphics module. -+void gr_deinit(); -+ -+/// Add an image rectangle to a list if rectangles to draw. This function may -+/// actually draw some rectangles, or it may wait till more rectangles are -+/// appended. Must be called between `gr_start_drawing` and `gr_finish_drawing`. -+/// - `img_start_col..img_end_col` and `img_start_row..img_end_row` define the -+/// part of the image to draw (row/col indices are zero-based, ends are -+/// excluded). -+/// - `x_col` and `y_row` are the coordinates of the top-left corner of the -+/// image in the terminal grid. -+/// - `x_pix` and `y_pix` are the same but in pixels. -+/// - `reverse` indicates whether colors should be inverted. -+void gr_append_imagerect(Drawable buf, uint32_t image_id, uint32_t placement_id, -+ int img_start_col, int img_end_col, int img_start_row, -+ int img_end_row, int x_col, int y_row, int x_pix, -+ int y_pix, int cw, int ch, int reverse); -+/// Prepare for image drawing. `cw` and `ch` are dimensions of the cell. -+void gr_start_drawing(Drawable buf, int cw, int ch); -+/// Finish image drawing. This functions will draw all the rectangles left to -+/// draw. -+void gr_finish_drawing(Drawable buf); -+/// Mark rows containing animations as dirty if it's time to redraw them. Must -+/// be called right after `gr_start_drawing`. -+void gr_mark_dirty_animations(int *dirty, int rows); -+ -+/// Parse and execute a graphics command. `buf` must start with 'G' and contain -+/// at least `len + 1` characters (including '\0'). Returns 1 on success. -+/// Additional informations is returned through `graphics_command_result`. -+int gr_parse_command(char *buf, size_t len); -+ -+/// Executes `command` with the name of the file corresponding to `image_id` as -+/// the argument. Executes xmessage with an error message on failure. -+void gr_preview_image(uint32_t image_id, const char *command); -+ -+/// Executes `<st> -e less <file>` where <file> is the name of a temporary file -+/// containing the information about an image and placement, and <st> is -+/// specified with `st_executable`. -+void gr_show_image_info(uint32_t image_id, uint32_t placement_id, -+ uint32_t imgcol, uint32_t imgrow, -+ char is_classic_placeholder, int32_t diacritic_count, -+ char *st_executable); -+ -+/// Dumps the internal state (images and placements) to stderr. -+void gr_dump_state(); -+ -+/// Unloads images to reduce RAM usage. -+void gr_unload_images_to_reduce_ram(); -+ -+/// Executes `callback` for each image cell. `callback` may return 1 to erase -+/// the cell or 0 to keep it. This function is implemented in `st.c`. -+void gr_for_each_image_cell(int (*callback)(void *data, uint32_t image_id, -+ uint32_t placement_id, int col, -+ int row, char is_classic), -+ void *data); -+ -+/// Marks all the rows containing the image with `image_id` as dirty. -+void gr_schedule_image_redraw_by_id(uint32_t image_id); -+ -+typedef enum { -+ GRAPHICS_DEBUG_NONE = 0, -+ GRAPHICS_DEBUG_LOG = 1, -+ GRAPHICS_DEBUG_LOG_AND_BOXES = 2, -+} GraphicsDebugMode; -+ -+/// Print additional information, draw bounding bounding boxes, etc. -+extern GraphicsDebugMode graphics_debug_mode; -+ -+/// Whether to display images or just draw bounding boxes. -+extern char graphics_display_images; -+ -+/// The time in milliseconds until the next redraw to update animations. -+/// INT_MAX means no redraw is needed. Populated by `gr_finish_drawing`. -+extern int graphics_next_redraw_delay; -+ -+#define MAX_GRAPHICS_RESPONSE_LEN 256 -+ -+/// A structure representing the result of a graphics command. -+typedef struct { -+ /// Indicates if the terminal needs to be redrawn. -+ char redraw; -+ /// The response of the command that should be sent back to the client -+ /// (may be empty if the quiet flag is set). -+ char response[MAX_GRAPHICS_RESPONSE_LEN]; -+ /// Whether there was an error executing this command (not very useful, -+ /// the response must be sent back anyway). -+ char error; -+ /// Whether the terminal has to create a placeholder for a non-virtual -+ /// placement. -+ char create_placeholder; -+ /// The placeholder that needs to be created. -+ struct { -+ uint32_t rows, columns; -+ uint32_t image_id, placement_id; -+ char do_not_move_cursor; -+ } placeholder; -+} GraphicsCommandResult; -+ -+/// The result of a graphics command. -+extern GraphicsCommandResult graphics_command_result; -diff --git a/icat-mini.sh b/icat-mini.sh -new file mode 100755 -index 0000000..0a8ebab ---- /dev/null -+++ b/icat-mini.sh -@@ -0,0 +1,801 @@ -+#!/bin/sh -+ -+# vim: shiftwidth=4 -+ -+script_name="$(basename "$0")" -+ -+short_help="Usage: $script_name [OPTIONS] <image_file> -+ -+This is a script to display images in the terminal using the kitty graphics -+protocol with Unicode placeholders. It is very basic, please use something else -+if you have alternatives. -+ -+Options: -+ -h Show this help. -+ -s SCALE The scale of the image, may be floating point. -+ -c N, --cols N The number of columns. -+ -r N, --rows N The number of rows. -+ --max-cols N The maximum number of columns. -+ --max-rows N The maximum number of rows. -+ --cell-size WxH The cell size in pixels. -+ -m METHOD The uploading method, may be 'file', 'direct' or 'auto'. -+ --speed SPEED The multiplier for the animation speed (float). -+" -+ -+# Exit the script on keyboard interrupt -+trap "echo 'icat-mini was interrupted' >&2; exit 1" INT -+ -+cols="" -+rows="" -+file="" -+tty="/dev/tty" -+uploading_method="auto" -+cell_size="" -+scale=1 -+max_cols="" -+max_rows="" -+speed="" -+ -+# Parse the command line. -+while [ $# -gt 0 ]; do -+ case "$1" in -+ -c|--columns|--cols) -+ cols="$2" -+ shift 2 -+ ;; -+ -r|--rows|-l|--lines) -+ rows="$2" -+ shift 2 -+ ;; -+ -s|--scale) -+ scale="$2" -+ shift 2 -+ ;; -+ -h|--help) -+ echo "$short_help" -+ exit 0 -+ ;; -+ -m|--upload-method|--uploading-method) -+ uploading_method="$2" -+ shift 2 -+ ;; -+ --cell-size) -+ cell_size="$2" -+ shift 2 -+ ;; -+ --max-cols) -+ max_cols="$2" -+ shift 2 -+ ;; -+ --max-rows) -+ max_rows="$2" -+ shift 2 -+ ;; -+ --speed) -+ speed="$2" -+ shift 2 -+ ;; -+ --) -+ file="$2" -+ shift 2 -+ ;; -+ -*) -+ echo "Unknown option: $1" >&2 -+ exit 1 -+ ;; -+ *) -+ if [ -n "$file" ]; then -+ echo "Multiple image files are not supported: $file and $1" >&2 -+ exit 1 -+ fi -+ file="$1" -+ shift -+ ;; -+ esac -+done -+ -+file="$(realpath "$file")" -+ -+##################################################################### -+# Adjust the terminal state -+##################################################################### -+ -+stty_orig="$(stty -g < "$tty")" -+stty -echo < "$tty" -+# Disable ctrl-z. Pressing ctrl-z during image uploading may cause some -+# horrible issues otherwise. -+stty susp undef < "$tty" -+stty -icanon < "$tty" -+ -+restore_echo() { -+ [ -n "$stty_orig" ] || return -+ stty $stty_orig < "$tty" -+} -+ -+trap restore_echo EXIT TERM -+ -+##################################################################### -+# Detect imagemagick -+##################################################################### -+ -+# If there is the 'magick' command, use it instead of separate 'convert' and -+# 'identify' commands. -+if command -v magick > /dev/null; then -+ identify="magick identify" -+ convert="magick" -+else -+ identify="identify" -+ convert="convert" -+fi -+ -+##################################################################### -+# Detect tmux -+##################################################################### -+ -+# Check if we are inside tmux. -+inside_tmux="" -+if [ -n "$TMUX" ]; then -+ case "$TERM" in -+ *tmux*|*screen*) -+ inside_tmux=1 -+ ;; -+ esac -+fi -+ -+##################################################################### -+# Compute the number of rows and columns -+##################################################################### -+ -+is_pos_int() { -+ if [ -z "$1" ]; then -+ return 1 # false -+ fi -+ if [ -z "$(printf '%s' "$1" | tr -d '[:digit:]')" ]; then -+ if [ "$1" -gt 0 ]; then -+ return 0 # true -+ fi -+ fi -+ return 1 # false -+} -+ -+if [ -n "$cols" ] || [ -n "$rows" ]; then -+ if [ -n "$max_cols" ] || [ -n "$max_rows" ]; then -+ echo "You can't specify both max-cols/rows and cols/rows" >&2 -+ exit 1 -+ fi -+fi -+ -+# Get the max number of cols and rows. -+[ -n "$max_cols" ] || max_cols="$(tput cols)" -+[ -n "$max_rows" ] || max_rows="$(tput lines)" -+if [ "$max_rows" -gt 255 ]; then -+ max_rows=255 -+fi -+ -+python_ioctl_command="import array, fcntl, termios -+buf = array.array('H', [0, 0, 0, 0]) -+fcntl.ioctl(0, termios.TIOCGWINSZ, buf) -+print(int(buf[2]/buf[1]), int(buf[3]/buf[0]))" -+ -+# Get the cell size in pixels if either cols or rows are not specified. -+if [ -z "$cols" ] || [ -z "$rows" ]; then -+ cell_width="" -+ cell_height="" -+ # If the cell size is specified, use it. -+ if [ -n "$cell_size" ]; then -+ cell_width="${cell_size%x*}" -+ cell_height="${cell_size#*x}" -+ if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then -+ echo "Invalid cell size: $cell_size" >&2 -+ exit 1 -+ fi -+ fi -+ # Otherwise try to use TIOCGWINSZ ioctl via python. -+ if [ -z "$cell_width" ] || [ -z "$cell_height" ]; then -+ cell_size_ioctl="$(python3 -c "$python_ioctl_command" < "$tty" 2> /dev/null)" -+ cell_width="${cell_size_ioctl% *}" -+ cell_height="${cell_size_ioctl#* }" -+ if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then -+ cell_width="" -+ cell_height="" -+ fi -+ fi -+ # If it didn't work, try to use csi XTWINOPS. -+ if [ -z "$cell_width" ] || [ -z "$cell_height" ]; then -+ if [ -n "$inside_tmux" ]; then -+ printf '\ePtmux;\e\e[16t\e\\' >> "$tty" -+ else -+ printf '\e[16t' >> "$tty" -+ fi -+ # The expected response will look like ^[[6;<height>;<width>t -+ term_response="" -+ while true; do -+ char=$(dd bs=1 count=1 <"$tty" 2>/dev/null) -+ if [ "$char" = "t" ]; then -+ break -+ fi -+ term_response="$term_response$char" -+ done -+ cell_height="$(printf '%s' "$term_response" | cut -d ';' -f 2)" -+ cell_width="$(printf '%s' "$term_response" | cut -d ';' -f 3)" -+ if ! is_pos_int "$cell_height" || ! is_pos_int "$cell_width"; then -+ cell_width=8 -+ cell_height=16 -+ fi -+ fi -+fi -+ -+# Compute a formula with bc and round to the nearest integer. -+bc_round() { -+ LC_NUMERIC=C printf '%.0f' "$(printf '%s\n' "scale=2;($1) + 0.5" | bc)" -+} -+ -+# Compute the number of rows and columns of the image. -+if [ -z "$cols" ] || [ -z "$rows" ]; then -+ # Get the size of the image and its resolution. If it's an animation, use -+ # the first frame. -+ format_output="$($identify -format '%w %h\n' "$file" | head -1)" -+ img_width="$(printf '%s' "$format_output" | cut -d ' ' -f 1)" -+ img_height="$(printf '%s' "$format_output" | cut -d ' ' -f 2)" -+ if ! is_pos_int "$img_width" || ! is_pos_int "$img_height"; then -+ echo "Couldn't get image size from identify: $format_output" >&2 -+ echo >&2 -+ exit 1 -+ fi -+ opt_cols_expr="(${scale}*${img_width}/${cell_width})" -+ opt_rows_expr="(${scale}*${img_height}/${cell_height})" -+ if [ -z "$cols" ] && [ -z "$rows" ]; then -+ # If columns and rows are not specified, compute the optimal values -+ # using the information about rows and columns per inch. -+ cols="$(bc_round "$opt_cols_expr")" -+ rows="$(bc_round "$opt_rows_expr")" -+ # Make sure that automatically computed rows and columns are within some -+ # sane limits -+ if [ "$cols" -gt "$max_cols" ]; then -+ rows="$(bc_round "$rows * $max_cols / $cols")" -+ cols="$max_cols" -+ fi -+ if [ "$rows" -gt "$max_rows" ]; then -+ cols="$(bc_round "$cols * $max_rows / $rows")" -+ rows="$max_rows" -+ fi -+ elif [ -z "$cols" ]; then -+ # If only one dimension is specified, compute the other one to match the -+ # aspect ratio as close as possible. -+ cols="$(bc_round "${opt_cols_expr}*${rows}/${opt_rows_expr}")" -+ elif [ -z "$rows" ]; then -+ rows="$(bc_round "${opt_rows_expr}*${cols}/${opt_cols_expr}")" -+ fi -+ -+ if [ "$cols" -lt 1 ]; then -+ cols=1 -+ fi -+ if [ "$rows" -lt 1 ]; then -+ rows=1 -+ fi -+fi -+ -+##################################################################### -+# Generate an image id -+##################################################################### -+ -+image_id="" -+while [ -z "$image_id" ]; do -+ image_id="$(shuf -i 16777217-4294967295 -n 1)" -+ # Check that the id requires 24-bit fg colors. -+ if [ "$(expr \( "$image_id" / 256 \) % 65536)" -eq 0 ]; then -+ image_id="" -+ fi -+done -+ -+##################################################################### -+# Uploading the image -+##################################################################### -+ -+# Choose the uploading method -+if [ "$uploading_method" = "auto" ]; then -+ if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ] || [ -n "$SSH_CONNECTION" ]; then -+ uploading_method="direct" -+ else -+ uploading_method="file" -+ fi -+fi -+ -+# Functions to emit the start and the end of a graphics command. -+if [ -n "$inside_tmux" ]; then -+ # If we are in tmux we have to wrap the command in Ptmux. -+ graphics_command_start='\ePtmux;\e\e_G' -+ graphics_command_end='\e\e\\\e\\' -+else -+ graphics_command_start='\e_G' -+ graphics_command_end='\e\\' -+fi -+ -+start_gr_command() { -+ printf "$graphics_command_start" >> "$tty" -+} -+end_gr_command() { -+ printf "$graphics_command_end" >> "$tty" -+} -+ -+# Send a graphics command with the correct start and end -+gr_command() { -+ start_gr_command -+ printf '%s' "$1" >> "$tty" -+ end_gr_command -+} -+ -+# Send an uploading command. Usage: gr_upload <action> <command> <file> -+# Where <action> is a part of command that specifies the action, it will be -+# repeated for every chunk (if the method is direct), and <command> is the rest -+# of the command that specifies the image parameters. <action> and <command> -+# must not include the transmission method or ';'. -+# Example: -+# gr_upload "a=T,q=2" "U=1,i=${image_id},f=100,c=${cols},r=${rows}" "$file" -+gr_upload() { -+ arg_action="$1" -+ arg_command="$2" -+ arg_file="$3" -+ if [ "$uploading_method" = "file" ]; then -+ # base64-encode the filename -+ encoded_filename=$(printf '%s' "$arg_file" | base64 -w0) -+ gr_command "${arg_action},${arg_command},t=f;${encoded_filename}" -+ fi -+ if [ "$uploading_method" = "direct" ]; then -+ # Create a temporary directory to store the chunked image. -+ chunkdir="$(mktemp -d)" -+ if [ ! "$chunkdir" ] || [ ! -d "$chunkdir" ]; then -+ echo "Can't create a temp dir" >&2 -+ exit 1 -+ fi -+ # base64-encode the file and split it into chunks. The size of each -+ # graphics command shouldn't be more than 4096, so we set the size of an -+ # encoded chunk to be 3968, slightly less than that. -+ chunk_size=3968 -+ cat "$arg_file" | base64 -w0 | split -b "$chunk_size" - "$chunkdir/chunk_" -+ -+ # Issue a command indicating that we want to start data transmission for -+ # a new image. -+ gr_command "${arg_action},${arg_command},t=d,m=1" -+ -+ # Transmit chunks. -+ for chunk in "$chunkdir/chunk_"*; do -+ start_gr_command -+ printf '%s' "${arg_action},i=${image_id},m=1;" >> "$tty" -+ cat "$chunk" >> "$tty" -+ end_gr_command -+ rm "$chunk" -+ done -+ -+ # Tell the terminal that we are done. -+ gr_command "${arg_action},i=$image_id,m=0" -+ -+ # Remove the temporary directory. -+ rmdir "$chunkdir" -+ fi -+} -+ -+delayed_frame_dir_cleanup() { -+ arg_frame_dir="$1" -+ sleep 2 -+ if [ -n "$arg_frame_dir" ]; then -+ for frame in "$arg_frame_dir"/frame_*.png; do -+ rm "$frame" -+ done -+ rmdir "$arg_frame_dir" -+ fi -+} -+ -+upload_image_and_print_placeholder() { -+ # Check if the file is an animation. -+ frame_count=$($identify -format '%n\n' "$file" | head -n 1) -+ if [ "$frame_count" -gt 1 ]; then -+ # The file is an animation, decompose into frames and upload each frame. -+ frame_dir="$(mktemp -d)" -+ frame_dir="$HOME/temp/frames${frame_dir}" -+ mkdir -p "$frame_dir" -+ if [ ! "$frame_dir" ] || [ ! -d "$frame_dir" ]; then -+ echo "Can't create a temp dir for frames" >&2 -+ exit 1 -+ fi -+ -+ # Decompose the animation into separate frames. -+ $convert "$file" -coalesce "$frame_dir/frame_%06d.png" -+ -+ # Get all frame delays at once, in centiseconds, as a space-separated -+ # string. -+ delays=$($identify -format "%T " "$file") -+ -+ frame_number=1 -+ for frame in "$frame_dir"/frame_*.png; do -+ # Read the delay for the current frame and convert it from -+ # centiseconds to milliseconds. -+ delay=$(printf '%s' "$delays" | cut -d ' ' -f "$frame_number") -+ delay=$((delay * 10)) -+ # If the delay is 0, set it to 100ms. -+ if [ "$delay" -eq 0 ]; then -+ delay=100 -+ fi -+ -+ if [ -n "$speed" ]; then -+ delay=$(bc_round "$delay / $speed") -+ fi -+ -+ if [ "$frame_number" -eq 1 ]; then -+ # Upload the first frame with a=T -+ gr_upload "q=2,a=T" "f=100,U=1,i=${image_id},c=${cols},r=${rows}" "$frame" -+ # Set the delay for the first frame and also play the animation -+ # in loading mode (s=2). -+ gr_command "a=a,v=1,s=2,r=${frame_number},z=${delay},i=${image_id}" -+ # Print the placeholder after the first frame to reduce the wait -+ # time. -+ print_placeholder -+ else -+ # Upload subsequent frames with a=f -+ gr_upload "q=2,a=f" "f=100,i=${image_id},z=${delay}" "$frame" -+ fi -+ -+ frame_number=$((frame_number + 1)) -+ done -+ -+ # Play the animation in loop mode (s=3). -+ gr_command "a=a,v=1,s=3,i=${image_id}" -+ -+ # Remove the temporary directory, but do it in the background with a -+ # delay to avoid removing files before they are loaded by the terminal. -+ delayed_frame_dir_cleanup "$frame_dir" 2> /dev/null & -+ else -+ # The file is not an animation, upload it directly -+ gr_upload "q=2,a=T" "U=1,i=${image_id},f=100,c=${cols},r=${rows}" "$file" -+ # Print the placeholder -+ print_placeholder -+ fi -+} -+ -+##################################################################### -+# Printing the image placeholder -+##################################################################### -+ -+print_placeholder() { -+ # Each line starts with the escape sequence to set the foreground color to -+ # the image id. -+ blue="$(expr "$image_id" % 256 )" -+ green="$(expr \( "$image_id" / 256 \) % 256 )" -+ red="$(expr \( "$image_id" / 65536 \) % 256 )" -+ line_start="$(printf "\e[38;2;%d;%d;%dm" "$red" "$green" "$blue")" -+ line_end="$(printf "\e[39;m")" -+ -+ id4th="$(expr \( "$image_id" / 16777216 \) % 256 )" -+ eval "id_diacritic=\$d${id4th}" -+ -+ # Reset the brush state, mostly to reset the underline color. -+ printf "\e[0m" -+ -+ # Fill the output with characters representing the image -+ for y in $(seq 0 "$(expr "$rows" - 1)"); do -+ eval "row_diacritic=\$d${y}" -+ printf '%s' "$line_start" -+ for x in $(seq 0 "$(expr "$cols" - 1)"); do -+ eval "col_diacritic=\$d${x}" -+ # Note that when $x is out of bounds, the column diacritic will -+ # be empty, meaning that the column should be guessed by the -+ # terminal. -+ if [ "$x" -ge "$num_diacritics" ]; then -+ printf '%s' "${placeholder}${row_diacritic}" -+ else -+ printf '%s' "${placeholder}${row_diacritic}${col_diacritic}${id_diacritic}" -+ fi -+ done -+ printf '%s\n' "$line_end" -+ done -+ -+ printf "\e[0m" -+} -+ -+d0="̅" -+d1="̍" -+d2="̎" -+d3="̐" -+d4="̒" -+d5="̽" -+d6="̾" -+d7="̿" -+d8="͆" -+d9="͊" -+d10="͋" -+d11="͌" -+d12="͐" -+d13="͑" -+d14="͒" -+d15="͗" -+d16="͛" -+d17="ͣ" -+d18="ͤ" -+d19="ͥ" -+d20="ͦ" -+d21="ͧ" -+d22="ͨ" -+d23="ͩ" -+d24="ͪ" -+d25="ͫ" -+d26="ͬ" -+d27="ͭ" -+d28="ͮ" -+d29="ͯ" -+d30="҃" -+d31="҄" -+d32="҅" -+d33="҆" -+d34="҇" -+d35="֒" -+d36="֓" -+d37="֔" -+d38="֕" -+d39="֗" -+d40="֘" -+d41="֙" -+d42="֜" -+d43="֝" -+d44="֞" -+d45="֟" -+d46="֠" -+d47="֡" -+d48="֨" -+d49="֩" -+d50="֫" -+d51="֬" -+d52="֯" -+d53="ׄ" -+d54="ؐ" -+d55="ؑ" -+d56="ؒ" -+d57="ؓ" -+d58="ؔ" -+d59="ؕ" -+d60="ؖ" -+d61="ؗ" -+d62="ٗ" -+d63="٘" -+d64="ٙ" -+d65="ٚ" -+d66="ٛ" -+d67="ٝ" -+d68="ٞ" -+d69="ۖ" -+d70="ۗ" -+d71="ۘ" -+d72="ۙ" -+d73="ۚ" -+d74="ۛ" -+d75="ۜ" -+d76="۟" -+d77="۠" -+d78="ۡ" -+d79="ۢ" -+d80="ۤ" -+d81="ۧ" -+d82="ۨ" -+d83="۫" -+d84="۬" -+d85="ܰ" -+d86="ܲ" -+d87="ܳ" -+d88="ܵ" -+d89="ܶ" -+d90="ܺ" -+d91="ܽ" -+d92="ܿ" -+d93="݀" -+d94="݁" -+d95="݃" -+d96="݅" -+d97="݇" -+d98="݉" -+d99="݊" -+d100="߫" -+d101="߬" -+d102="߭" -+d103="߮" -+d104="߯" -+d105="߰" -+d106="߱" -+d107="߳" -+d108="ࠖ" -+d109="ࠗ" -+d110="࠘" -+d111="࠙" -+d112="ࠛ" -+d113="ࠜ" -+d114="ࠝ" -+d115="ࠞ" -+d116="ࠟ" -+d117="ࠠ" -+d118="ࠡ" -+d119="ࠢ" -+d120="ࠣ" -+d121="ࠥ" -+d122="ࠦ" -+d123="ࠧ" -+d124="ࠩ" -+d125="ࠪ" -+d126="ࠫ" -+d127="ࠬ" -+d128="࠭" -+d129="॑" -+d130="॓" -+d131="॔" -+d132="ྂ" -+d133="ྃ" -+d134="྆" -+d135="྇" -+d136="፝" -+d137="፞" -+d138="፟" -+d139="៝" -+d140="᤺" -+d141="ᨗ" -+d142="᩵" -+d143="᩶" -+d144="᩷" -+d145="᩸" -+d146="᩹" -+d147="᩺" -+d148="᩻" -+d149="᩼" -+d150="᭫" -+d151="᭭" -+d152="᭮" -+d153="᭯" -+d154="᭰" -+d155="᭱" -+d156="᭲" -+d157="᭳" -+d158="᳐" -+d159="᳑" -+d160="᳒" -+d161="᳚" -+d162="᳛" -+d163="᳠" -+d164="᷀" -+d165="᷁" -+d166="᷃" -+d167="᷄" -+d168="᷅" -+d169="᷆" -+d170="᷇" -+d171="᷈" -+d172="᷉" -+d173="᷋" -+d174="᷌" -+d175="᷑" -+d176="᷒" -+d177="ᷓ" -+d178="ᷔ" -+d179="ᷕ" -+d180="ᷖ" -+d181="ᷗ" -+d182="ᷘ" -+d183="ᷙ" -+d184="ᷚ" -+d185="ᷛ" -+d186="ᷜ" -+d187="ᷝ" -+d188="ᷞ" -+d189="ᷟ" -+d190="ᷠ" -+d191="ᷡ" -+d192="ᷢ" -+d193="ᷣ" -+d194="ᷤ" -+d195="ᷥ" -+d196="ᷦ" -+d197="᷾" -+d198="⃐" -+d199="⃑" -+d200="⃔" -+d201="⃕" -+d202="⃖" -+d203="⃗" -+d204="⃛" -+d205="⃜" -+d206="⃡" -+d207="⃧" -+d208="⃩" -+d209="⃰" -+d210="⳯" -+d211="⳰" -+d212="⳱" -+d213="ⷠ" -+d214="ⷡ" -+d215="ⷢ" -+d216="ⷣ" -+d217="ⷤ" -+d218="ⷥ" -+d219="ⷦ" -+d220="ⷧ" -+d221="ⷨ" -+d222="ⷩ" -+d223="ⷪ" -+d224="ⷫ" -+d225="ⷬ" -+d226="ⷭ" -+d227="ⷮ" -+d228="ⷯ" -+d229="ⷰ" -+d230="ⷱ" -+d231="ⷲ" -+d232="ⷳ" -+d233="ⷴ" -+d234="ⷵ" -+d235="ⷶ" -+d236="ⷷ" -+d237="ⷸ" -+d238="ⷹ" -+d239="ⷺ" -+d240="ⷻ" -+d241="ⷼ" -+d242="ⷽ" -+d243="ⷾ" -+d244="ⷿ" -+d245="꙯" -+d246="꙼" -+d247="꙽" -+d248="꛰" -+d249="꛱" -+d250="꣠" -+d251="꣡" -+d252="꣢" -+d253="꣣" -+d254="꣤" -+d255="꣥" -+d256="꣦" -+d257="꣧" -+d258="꣨" -+d259="꣩" -+d260="꣪" -+d261="꣫" -+d262="꣬" -+d263="꣭" -+d264="꣮" -+d265="꣯" -+d266="꣰" -+d267="꣱" -+d268="ꪰ" -+d269="ꪲ" -+d270="ꪳ" -+d271="ꪷ" -+d272="ꪸ" -+d273="ꪾ" -+d274="꪿" -+d275="꫁" -+d276="︠" -+d277="︡" -+d278="︢" -+d279="︣" -+d280="︤" -+d281="︥" -+d282="︦" -+d283="𐨏" -+d284="𐨸" -+d285="𝆅" -+d286="𝆆" -+d287="𝆇" -+d288="𝆈" -+d289="𝆉" -+d290="𝆪" -+d291="𝆫" -+d292="𝆬" -+d293="𝆭" -+d294="𝉂" -+d295="𝉃" -+d296="𝉄" -+ -+num_diacritics="297" -+ -+placeholder="" -+ -+##################################################################### -+# Upload the image and print the placeholder -+##################################################################### -+ -+upload_image_and_print_placeholder -diff --git a/khash.h b/khash.h -new file mode 100644 -index 0000000..f75f347 ---- /dev/null -+++ b/khash.h -@@ -0,0 +1,627 @@ -+/* The MIT License -+ -+ Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk> -+ -+ Permission is hereby granted, free of charge, to any person obtaining -+ a copy of this software and associated documentation files (the -+ "Software"), to deal in the Software without restriction, including -+ without limitation the rights to use, copy, modify, merge, publish, -+ distribute, sublicense, and/or sell copies of the Software, and to -+ permit persons to whom the Software is furnished to do so, subject to -+ the following conditions: -+ -+ The above copyright notice and this permission notice shall be -+ included in all copies or substantial portions of the Software. -+ -+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -+ SOFTWARE. -+*/ -+ -+/* -+ An example: -+ -+#include "khash.h" -+KHASH_MAP_INIT_INT(32, char) -+int main() { -+ int ret, is_missing; -+ khiter_t k; -+ khash_t(32) *h = kh_init(32); -+ k = kh_put(32, h, 5, &ret); -+ kh_value(h, k) = 10; -+ k = kh_get(32, h, 10); -+ is_missing = (k == kh_end(h)); -+ k = kh_get(32, h, 5); -+ kh_del(32, h, k); -+ for (k = kh_begin(h); k != kh_end(h); ++k) -+ if (kh_exist(h, k)) kh_value(h, k) = 1; -+ kh_destroy(32, h); -+ return 0; -+} -+*/ -+ -+/* -+ 2013-05-02 (0.2.8): -+ -+ * Use quadratic probing. When the capacity is power of 2, stepping function -+ i*(i+1)/2 guarantees to traverse each bucket. It is better than double -+ hashing on cache performance and is more robust than linear probing. -+ -+ In theory, double hashing should be more robust than quadratic probing. -+ However, my implementation is probably not for large hash tables, because -+ the second hash function is closely tied to the first hash function, -+ which reduce the effectiveness of double hashing. -+ -+ Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php -+ -+ 2011-12-29 (0.2.7): -+ -+ * Minor code clean up; no actual effect. -+ -+ 2011-09-16 (0.2.6): -+ -+ * The capacity is a power of 2. This seems to dramatically improve the -+ speed for simple keys. Thank Zilong Tan for the suggestion. Reference: -+ -+ - http://code.google.com/p/ulib/ -+ - http://nothings.org/computer/judy/ -+ -+ * Allow to optionally use linear probing which usually has better -+ performance for random input. Double hashing is still the default as it -+ is more robust to certain non-random input. -+ -+ * Added Wang's integer hash function (not used by default). This hash -+ function is more robust to certain non-random input. -+ -+ 2011-02-14 (0.2.5): -+ -+ * Allow to declare global functions. -+ -+ 2009-09-26 (0.2.4): -+ -+ * Improve portability -+ -+ 2008-09-19 (0.2.3): -+ -+ * Corrected the example -+ * Improved interfaces -+ -+ 2008-09-11 (0.2.2): -+ -+ * Improved speed a little in kh_put() -+ -+ 2008-09-10 (0.2.1): -+ -+ * Added kh_clear() -+ * Fixed a compiling error -+ -+ 2008-09-02 (0.2.0): -+ -+ * Changed to token concatenation which increases flexibility. -+ -+ 2008-08-31 (0.1.2): -+ -+ * Fixed a bug in kh_get(), which has not been tested previously. -+ -+ 2008-08-31 (0.1.1): -+ -+ * Added destructor -+*/ -+ -+ -+#ifndef __AC_KHASH_H -+#define __AC_KHASH_H -+ -+/*! -+ @header -+ -+ Generic hash table library. -+ */ -+ -+#define AC_VERSION_KHASH_H "0.2.8" -+ -+#include <stdlib.h> -+#include <string.h> -+#include <limits.h> -+ -+/* compiler specific configuration */ -+ -+#if UINT_MAX == 0xffffffffu -+typedef unsigned int khint32_t; -+#elif ULONG_MAX == 0xffffffffu -+typedef unsigned long khint32_t; -+#endif -+ -+#if ULONG_MAX == ULLONG_MAX -+typedef unsigned long khint64_t; -+#else -+typedef unsigned long long khint64_t; -+#endif -+ -+#ifndef kh_inline -+#ifdef _MSC_VER -+#define kh_inline __inline -+#else -+#define kh_inline inline -+#endif -+#endif /* kh_inline */ -+ -+#ifndef klib_unused -+#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) -+#define klib_unused __attribute__ ((__unused__)) -+#else -+#define klib_unused -+#endif -+#endif /* klib_unused */ -+ -+typedef khint32_t khint_t; -+typedef khint_t khiter_t; -+ -+#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) -+#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) -+#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) -+#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) -+#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) -+#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) -+#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) -+ -+#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) -+ -+#ifndef kroundup32 -+#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) -+#endif -+ -+#ifndef kcalloc -+#define kcalloc(N,Z) calloc(N,Z) -+#endif -+#ifndef kmalloc -+#define kmalloc(Z) malloc(Z) -+#endif -+#ifndef krealloc -+#define krealloc(P,Z) realloc(P,Z) -+#endif -+#ifndef kfree -+#define kfree(P) free(P) -+#endif -+ -+static const double __ac_HASH_UPPER = 0.77; -+ -+#define __KHASH_TYPE(name, khkey_t, khval_t) \ -+ typedef struct kh_##name##_s { \ -+ khint_t n_buckets, size, n_occupied, upper_bound; \ -+ khint32_t *flags; \ -+ khkey_t *keys; \ -+ khval_t *vals; \ -+ } kh_##name##_t; -+ -+#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ -+ extern kh_##name##_t *kh_init_##name(void); \ -+ extern void kh_destroy_##name(kh_##name##_t *h); \ -+ extern void kh_clear_##name(kh_##name##_t *h); \ -+ extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ -+ extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ -+ extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ -+ extern void kh_del_##name(kh_##name##_t *h, khint_t x); -+ -+#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ -+ SCOPE kh_##name##_t *kh_init_##name(void) { \ -+ return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ -+ } \ -+ SCOPE void kh_destroy_##name(kh_##name##_t *h) \ -+ { \ -+ if (h) { \ -+ kfree((void *)h->keys); kfree(h->flags); \ -+ kfree((void *)h->vals); \ -+ kfree(h); \ -+ } \ -+ } \ -+ SCOPE void kh_clear_##name(kh_##name##_t *h) \ -+ { \ -+ if (h && h->flags) { \ -+ memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ -+ h->size = h->n_occupied = 0; \ -+ } \ -+ } \ -+ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ -+ { \ -+ if (h->n_buckets) { \ -+ khint_t k, i, last, mask, step = 0; \ -+ mask = h->n_buckets - 1; \ -+ k = __hash_func(key); i = k & mask; \ -+ last = i; \ -+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ -+ i = (i + (++step)) & mask; \ -+ if (i == last) return h->n_buckets; \ -+ } \ -+ return __ac_iseither(h->flags, i)? h->n_buckets : i; \ -+ } else return 0; \ -+ } \ -+ SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ -+ { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ -+ khint32_t *new_flags = 0; \ -+ khint_t j = 1; \ -+ { \ -+ kroundup32(new_n_buckets); \ -+ if (new_n_buckets < 4) new_n_buckets = 4; \ -+ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ -+ else { /* hash table size to be changed (shrink or expand); rehash */ \ -+ new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ -+ if (!new_flags) return -1; \ -+ memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ -+ if (h->n_buckets < new_n_buckets) { /* expand */ \ -+ khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ -+ if (!new_keys) { kfree(new_flags); return -1; } \ -+ h->keys = new_keys; \ -+ if (kh_is_map) { \ -+ khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ -+ if (!new_vals) { kfree(new_flags); return -1; } \ -+ h->vals = new_vals; \ -+ } \ -+ } /* otherwise shrink */ \ -+ } \ -+ } \ -+ if (j) { /* rehashing is needed */ \ -+ for (j = 0; j != h->n_buckets; ++j) { \ -+ if (__ac_iseither(h->flags, j) == 0) { \ -+ khkey_t key = h->keys[j]; \ -+ khval_t val; \ -+ khint_t new_mask; \ -+ new_mask = new_n_buckets - 1; \ -+ if (kh_is_map) val = h->vals[j]; \ -+ __ac_set_isdel_true(h->flags, j); \ -+ while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ -+ khint_t k, i, step = 0; \ -+ k = __hash_func(key); \ -+ i = k & new_mask; \ -+ while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ -+ __ac_set_isempty_false(new_flags, i); \ -+ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ -+ { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ -+ if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ -+ __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ -+ } else { /* write the element and jump out of the loop */ \ -+ h->keys[i] = key; \ -+ if (kh_is_map) h->vals[i] = val; \ -+ break; \ -+ } \ -+ } \ -+ } \ -+ } \ -+ if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ -+ h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ -+ if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ -+ } \ -+ kfree(h->flags); /* free the working space */ \ -+ h->flags = new_flags; \ -+ h->n_buckets = new_n_buckets; \ -+ h->n_occupied = h->size; \ -+ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ -+ } \ -+ return 0; \ -+ } \ -+ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ -+ { \ -+ khint_t x; \ -+ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ -+ if (h->n_buckets > (h->size<<1)) { \ -+ if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ -+ *ret = -1; return h->n_buckets; \ -+ } \ -+ } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ -+ *ret = -1; return h->n_buckets; \ -+ } \ -+ } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ -+ { \ -+ khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ -+ x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ -+ if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ -+ else { \ -+ last = i; \ -+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ -+ if (__ac_isdel(h->flags, i)) site = i; \ -+ i = (i + (++step)) & mask; \ -+ if (i == last) { x = site; break; } \ -+ } \ -+ if (x == h->n_buckets) { \ -+ if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ -+ else x = i; \ -+ } \ -+ } \ -+ } \ -+ if (__ac_isempty(h->flags, x)) { /* not present at all */ \ -+ h->keys[x] = key; \ -+ __ac_set_isboth_false(h->flags, x); \ -+ ++h->size; ++h->n_occupied; \ -+ *ret = 1; \ -+ } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ -+ h->keys[x] = key; \ -+ __ac_set_isboth_false(h->flags, x); \ -+ ++h->size; \ -+ *ret = 2; \ -+ } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ -+ return x; \ -+ } \ -+ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ -+ { \ -+ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ -+ __ac_set_isdel_true(h->flags, x); \ -+ --h->size; \ -+ } \ -+ } -+ -+#define KHASH_DECLARE(name, khkey_t, khval_t) \ -+ __KHASH_TYPE(name, khkey_t, khval_t) \ -+ __KHASH_PROTOTYPES(name, khkey_t, khval_t) -+ -+#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ -+ __KHASH_TYPE(name, khkey_t, khval_t) \ -+ __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) -+ -+#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ -+ KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) -+ -+/* --- BEGIN OF HASH FUNCTIONS --- */ -+ -+/*! @function -+ @abstract Integer hash function -+ @param key The integer [khint32_t] -+ @return The hash value [khint_t] -+ */ -+#define kh_int_hash_func(key) (khint32_t)(key) -+/*! @function -+ @abstract Integer comparison function -+ */ -+#define kh_int_hash_equal(a, b) ((a) == (b)) -+/*! @function -+ @abstract 64-bit integer hash function -+ @param key The integer [khint64_t] -+ @return The hash value [khint_t] -+ */ -+#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) -+/*! @function -+ @abstract 64-bit integer comparison function -+ */ -+#define kh_int64_hash_equal(a, b) ((a) == (b)) -+/*! @function -+ @abstract const char* hash function -+ @param s Pointer to a null terminated string -+ @return The hash value -+ */ -+static kh_inline khint_t __ac_X31_hash_string(const char *s) -+{ -+ khint_t h = (khint_t)*s; -+ if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; -+ return h; -+} -+/*! @function -+ @abstract Another interface to const char* hash function -+ @param key Pointer to a null terminated string [const char*] -+ @return The hash value [khint_t] -+ */ -+#define kh_str_hash_func(key) __ac_X31_hash_string(key) -+/*! @function -+ @abstract Const char* comparison function -+ */ -+#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) -+ -+static kh_inline khint_t __ac_Wang_hash(khint_t key) -+{ -+ key += ~(key << 15); -+ key ^= (key >> 10); -+ key += (key << 3); -+ key ^= (key >> 6); -+ key += ~(key << 11); -+ key ^= (key >> 16); -+ return key; -+} -+#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key) -+ -+/* --- END OF HASH FUNCTIONS --- */ -+ -+/* Other convenient macros... */ -+ -+/*! -+ @abstract Type of the hash table. -+ @param name Name of the hash table [symbol] -+ */ -+#define khash_t(name) kh_##name##_t -+ -+/*! @function -+ @abstract Initiate a hash table. -+ @param name Name of the hash table [symbol] -+ @return Pointer to the hash table [khash_t(name)*] -+ */ -+#define kh_init(name) kh_init_##name() -+ -+/*! @function -+ @abstract Destroy a hash table. -+ @param name Name of the hash table [symbol] -+ @param h Pointer to the hash table [khash_t(name)*] -+ */ -+#define kh_destroy(name, h) kh_destroy_##name(h) -+ -+/*! @function -+ @abstract Reset a hash table without deallocating memory. -+ @param name Name of the hash table [symbol] -+ @param h Pointer to the hash table [khash_t(name)*] -+ */ -+#define kh_clear(name, h) kh_clear_##name(h) -+ -+/*! @function -+ @abstract Resize a hash table. -+ @param name Name of the hash table [symbol] -+ @param h Pointer to the hash table [khash_t(name)*] -+ @param s New size [khint_t] -+ */ -+#define kh_resize(name, h, s) kh_resize_##name(h, s) -+ -+/*! @function -+ @abstract Insert a key to the hash table. -+ @param name Name of the hash table [symbol] -+ @param h Pointer to the hash table [khash_t(name)*] -+ @param k Key [type of keys] -+ @param r Extra return code: -1 if the operation failed; -+ 0 if the key is present in the hash table; -+ 1 if the bucket is empty (never used); 2 if the element in -+ the bucket has been deleted [int*] -+ @return Iterator to the inserted element [khint_t] -+ */ -+#define kh_put(name, h, k, r) kh_put_##name(h, k, r) -+ -+/*! @function -+ @abstract Retrieve a key from the hash table. -+ @param name Name of the hash table [symbol] -+ @param h Pointer to the hash table [khash_t(name)*] -+ @param k Key [type of keys] -+ @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] -+ */ -+#define kh_get(name, h, k) kh_get_##name(h, k) -+ -+/*! @function -+ @abstract Remove a key from the hash table. -+ @param name Name of the hash table [symbol] -+ @param h Pointer to the hash table [khash_t(name)*] -+ @param k Iterator to the element to be deleted [khint_t] -+ */ -+#define kh_del(name, h, k) kh_del_##name(h, k) -+ -+/*! @function -+ @abstract Test whether a bucket contains data. -+ @param h Pointer to the hash table [khash_t(name)*] -+ @param x Iterator to the bucket [khint_t] -+ @return 1 if containing data; 0 otherwise [int] -+ */ -+#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) -+ -+/*! @function -+ @abstract Get key given an iterator -+ @param h Pointer to the hash table [khash_t(name)*] -+ @param x Iterator to the bucket [khint_t] -+ @return Key [type of keys] -+ */ -+#define kh_key(h, x) ((h)->keys[x]) -+ -+/*! @function -+ @abstract Get value given an iterator -+ @param h Pointer to the hash table [khash_t(name)*] -+ @param x Iterator to the bucket [khint_t] -+ @return Value [type of values] -+ @discussion For hash sets, calling this results in segfault. -+ */ -+#define kh_val(h, x) ((h)->vals[x]) -+ -+/*! @function -+ @abstract Alias of kh_val() -+ */ -+#define kh_value(h, x) ((h)->vals[x]) -+ -+/*! @function -+ @abstract Get the start iterator -+ @param h Pointer to the hash table [khash_t(name)*] -+ @return The start iterator [khint_t] -+ */ -+#define kh_begin(h) (khint_t)(0) -+ -+/*! @function -+ @abstract Get the end iterator -+ @param h Pointer to the hash table [khash_t(name)*] -+ @return The end iterator [khint_t] -+ */ -+#define kh_end(h) ((h)->n_buckets) -+ -+/*! @function -+ @abstract Get the number of elements in the hash table -+ @param h Pointer to the hash table [khash_t(name)*] -+ @return Number of elements in the hash table [khint_t] -+ */ -+#define kh_size(h) ((h)->size) -+ -+/*! @function -+ @abstract Get the number of buckets in the hash table -+ @param h Pointer to the hash table [khash_t(name)*] -+ @return Number of buckets in the hash table [khint_t] -+ */ -+#define kh_n_buckets(h) ((h)->n_buckets) -+ -+/*! @function -+ @abstract Iterate over the entries in the hash table -+ @param h Pointer to the hash table [khash_t(name)*] -+ @param kvar Variable to which key will be assigned -+ @param vvar Variable to which value will be assigned -+ @param code Block of code to execute -+ */ -+#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ -+ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ -+ if (!kh_exist(h,__i)) continue; \ -+ (kvar) = kh_key(h,__i); \ -+ (vvar) = kh_val(h,__i); \ -+ code; \ -+ } } -+ -+/*! @function -+ @abstract Iterate over the values in the hash table -+ @param h Pointer to the hash table [khash_t(name)*] -+ @param vvar Variable to which value will be assigned -+ @param code Block of code to execute -+ */ -+#define kh_foreach_value(h, vvar, code) { khint_t __i; \ -+ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ -+ if (!kh_exist(h,__i)) continue; \ -+ (vvar) = kh_val(h,__i); \ -+ code; \ -+ } } -+ -+/* More convenient interfaces */ -+ -+/*! @function -+ @abstract Instantiate a hash set containing integer keys -+ @param name Name of the hash table [symbol] -+ */ -+#define KHASH_SET_INIT_INT(name) \ -+ KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) -+ -+/*! @function -+ @abstract Instantiate a hash map containing integer keys -+ @param name Name of the hash table [symbol] -+ @param khval_t Type of values [type] -+ */ -+#define KHASH_MAP_INIT_INT(name, khval_t) \ -+ KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) -+ -+/*! @function -+ @abstract Instantiate a hash set containing 64-bit integer keys -+ @param name Name of the hash table [symbol] -+ */ -+#define KHASH_SET_INIT_INT64(name) \ -+ KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) -+ -+/*! @function -+ @abstract Instantiate a hash map containing 64-bit integer keys -+ @param name Name of the hash table [symbol] -+ @param khval_t Type of values [type] -+ */ -+#define KHASH_MAP_INIT_INT64(name, khval_t) \ -+ KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) -+ -+typedef const char *kh_cstr_t; -+/*! @function -+ @abstract Instantiate a hash map containing const char* keys -+ @param name Name of the hash table [symbol] -+ */ -+#define KHASH_SET_INIT_STR(name) \ -+ KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) -+ -+/*! @function -+ @abstract Instantiate a hash map containing const char* keys -+ @param name Name of the hash table [symbol] -+ @param khval_t Type of values [type] -+ */ -+#define KHASH_MAP_INIT_STR(name, khval_t) \ -+ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) -+ -+#endif /* __AC_KHASH_H */ -diff --git a/kvec.h b/kvec.h -new file mode 100644 -index 0000000..10f1c5b ---- /dev/null -+++ b/kvec.h -@@ -0,0 +1,90 @@ -+/* The MIT License -+ -+ Copyright (c) 2008, by Attractive Chaos <attractor@live.co.uk> -+ -+ Permission is hereby granted, free of charge, to any person obtaining -+ a copy of this software and associated documentation files (the -+ "Software"), to deal in the Software without restriction, including -+ without limitation the rights to use, copy, modify, merge, publish, -+ distribute, sublicense, and/or sell copies of the Software, and to -+ permit persons to whom the Software is furnished to do so, subject to -+ the following conditions: -+ -+ The above copyright notice and this permission notice shall be -+ included in all copies or substantial portions of the Software. -+ -+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -+ SOFTWARE. -+*/ -+ -+/* -+ An example: -+ -+#include "kvec.h" -+int main() { -+ kvec_t(int) array; -+ kv_init(array); -+ kv_push(int, array, 10); // append -+ kv_a(int, array, 20) = 5; // dynamic -+ kv_A(array, 20) = 4; // static -+ kv_destroy(array); -+ return 0; -+} -+*/ -+ -+/* -+ 2008-09-22 (0.1.0): -+ -+ * The initial version. -+ -+*/ -+ -+#ifndef AC_KVEC_H -+#define AC_KVEC_H -+ -+#include <stdlib.h> -+ -+#define kv_roundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) -+ -+#define kvec_t(type) struct { size_t n, m; type *a; } -+#define kv_init(v) ((v).n = (v).m = 0, (v).a = 0) -+#define kv_destroy(v) free((v).a) -+#define kv_A(v, i) ((v).a[(i)]) -+#define kv_pop(v) ((v).a[--(v).n]) -+#define kv_size(v) ((v).n) -+#define kv_max(v) ((v).m) -+ -+#define kv_resize(type, v, s) ((v).m = (s), (v).a = (type*)realloc((v).a, sizeof(type) * (v).m)) -+ -+#define kv_copy(type, v1, v0) do { \ -+ if ((v1).m < (v0).n) kv_resize(type, v1, (v0).n); \ -+ (v1).n = (v0).n; \ -+ memcpy((v1).a, (v0).a, sizeof(type) * (v0).n); \ -+ } while (0) \ -+ -+#define kv_push(type, v, x) do { \ -+ if ((v).n == (v).m) { \ -+ (v).m = (v).m? (v).m<<1 : 2; \ -+ (v).a = (type*)realloc((v).a, sizeof(type) * (v).m); \ -+ } \ -+ (v).a[(v).n++] = (x); \ -+ } while (0) -+ -+#define kv_pushp(type, v) ((((v).n == (v).m)? \ -+ ((v).m = ((v).m? (v).m<<1 : 2), \ -+ (v).a = (type*)realloc((v).a, sizeof(type) * (v).m), 0) \ -+ : 0), ((v).a + ((v).n++))) -+ -+#define kv_a(type, v, i) (((v).m <= (size_t)(i)? \ -+ ((v).m = (v).n = (i) + 1, kv_roundup32((v).m), \ -+ (v).a = (type*)realloc((v).a, sizeof(type) * (v).m), 0) \ -+ : (v).n <= (size_t)(i)? (v).n = (i) + 1 \ -+ : 0), (v).a[(i)]) -+ -+#endif -diff --git a/rowcolumn_diacritics_helpers.c b/rowcolumn_diacritics_helpers.c -new file mode 100644 -index 0000000..829c0fc ---- /dev/null -+++ b/rowcolumn_diacritics_helpers.c -@@ -0,0 +1,391 @@ -+#include <stdint.h> -+ -+uint16_t diacritic_to_num(uint32_t code) -+{ -+ switch (code) { -+ case 0x305: -+ return code - 0x305 + 1; -+ case 0x30d: -+ case 0x30e: -+ return code - 0x30d + 2; -+ case 0x310: -+ return code - 0x310 + 4; -+ case 0x312: -+ return code - 0x312 + 5; -+ case 0x33d: -+ case 0x33e: -+ case 0x33f: -+ return code - 0x33d + 6; -+ case 0x346: -+ return code - 0x346 + 9; -+ case 0x34a: -+ case 0x34b: -+ case 0x34c: -+ return code - 0x34a + 10; -+ case 0x350: -+ case 0x351: -+ case 0x352: -+ return code - 0x350 + 13; -+ case 0x357: -+ return code - 0x357 + 16; -+ case 0x35b: -+ return code - 0x35b + 17; -+ case 0x363: -+ case 0x364: -+ case 0x365: -+ case 0x366: -+ case 0x367: -+ case 0x368: -+ case 0x369: -+ case 0x36a: -+ case 0x36b: -+ case 0x36c: -+ case 0x36d: -+ case 0x36e: -+ case 0x36f: -+ return code - 0x363 + 18; -+ case 0x483: -+ case 0x484: -+ case 0x485: -+ case 0x486: -+ case 0x487: -+ return code - 0x483 + 31; -+ case 0x592: -+ case 0x593: -+ case 0x594: -+ case 0x595: -+ return code - 0x592 + 36; -+ case 0x597: -+ case 0x598: -+ case 0x599: -+ return code - 0x597 + 40; -+ case 0x59c: -+ case 0x59d: -+ case 0x59e: -+ case 0x59f: -+ case 0x5a0: -+ case 0x5a1: -+ return code - 0x59c + 43; -+ case 0x5a8: -+ case 0x5a9: -+ return code - 0x5a8 + 49; -+ case 0x5ab: -+ case 0x5ac: -+ return code - 0x5ab + 51; -+ case 0x5af: -+ return code - 0x5af + 53; -+ case 0x5c4: -+ return code - 0x5c4 + 54; -+ case 0x610: -+ case 0x611: -+ case 0x612: -+ case 0x613: -+ case 0x614: -+ case 0x615: -+ case 0x616: -+ case 0x617: -+ return code - 0x610 + 55; -+ case 0x657: -+ case 0x658: -+ case 0x659: -+ case 0x65a: -+ case 0x65b: -+ return code - 0x657 + 63; -+ case 0x65d: -+ case 0x65e: -+ return code - 0x65d + 68; -+ case 0x6d6: -+ case 0x6d7: -+ case 0x6d8: -+ case 0x6d9: -+ case 0x6da: -+ case 0x6db: -+ case 0x6dc: -+ return code - 0x6d6 + 70; -+ case 0x6df: -+ case 0x6e0: -+ case 0x6e1: -+ case 0x6e2: -+ return code - 0x6df + 77; -+ case 0x6e4: -+ return code - 0x6e4 + 81; -+ case 0x6e7: -+ case 0x6e8: -+ return code - 0x6e7 + 82; -+ case 0x6eb: -+ case 0x6ec: -+ return code - 0x6eb + 84; -+ case 0x730: -+ return code - 0x730 + 86; -+ case 0x732: -+ case 0x733: -+ return code - 0x732 + 87; -+ case 0x735: -+ case 0x736: -+ return code - 0x735 + 89; -+ case 0x73a: -+ return code - 0x73a + 91; -+ case 0x73d: -+ return code - 0x73d + 92; -+ case 0x73f: -+ case 0x740: -+ case 0x741: -+ return code - 0x73f + 93; -+ case 0x743: -+ return code - 0x743 + 96; -+ case 0x745: -+ return code - 0x745 + 97; -+ case 0x747: -+ return code - 0x747 + 98; -+ case 0x749: -+ case 0x74a: -+ return code - 0x749 + 99; -+ case 0x7eb: -+ case 0x7ec: -+ case 0x7ed: -+ case 0x7ee: -+ case 0x7ef: -+ case 0x7f0: -+ case 0x7f1: -+ return code - 0x7eb + 101; -+ case 0x7f3: -+ return code - 0x7f3 + 108; -+ case 0x816: -+ case 0x817: -+ case 0x818: -+ case 0x819: -+ return code - 0x816 + 109; -+ case 0x81b: -+ case 0x81c: -+ case 0x81d: -+ case 0x81e: -+ case 0x81f: -+ case 0x820: -+ case 0x821: -+ case 0x822: -+ case 0x823: -+ return code - 0x81b + 113; -+ case 0x825: -+ case 0x826: -+ case 0x827: -+ return code - 0x825 + 122; -+ case 0x829: -+ case 0x82a: -+ case 0x82b: -+ case 0x82c: -+ case 0x82d: -+ return code - 0x829 + 125; -+ case 0x951: -+ return code - 0x951 + 130; -+ case 0x953: -+ case 0x954: -+ return code - 0x953 + 131; -+ case 0xf82: -+ case 0xf83: -+ return code - 0xf82 + 133; -+ case 0xf86: -+ case 0xf87: -+ return code - 0xf86 + 135; -+ case 0x135d: -+ case 0x135e: -+ case 0x135f: -+ return code - 0x135d + 137; -+ case 0x17dd: -+ return code - 0x17dd + 140; -+ case 0x193a: -+ return code - 0x193a + 141; -+ case 0x1a17: -+ return code - 0x1a17 + 142; -+ case 0x1a75: -+ case 0x1a76: -+ case 0x1a77: -+ case 0x1a78: -+ case 0x1a79: -+ case 0x1a7a: -+ case 0x1a7b: -+ case 0x1a7c: -+ return code - 0x1a75 + 143; -+ case 0x1b6b: -+ return code - 0x1b6b + 151; -+ case 0x1b6d: -+ case 0x1b6e: -+ case 0x1b6f: -+ case 0x1b70: -+ case 0x1b71: -+ case 0x1b72: -+ case 0x1b73: -+ return code - 0x1b6d + 152; -+ case 0x1cd0: -+ case 0x1cd1: -+ case 0x1cd2: -+ return code - 0x1cd0 + 159; -+ case 0x1cda: -+ case 0x1cdb: -+ return code - 0x1cda + 162; -+ case 0x1ce0: -+ return code - 0x1ce0 + 164; -+ case 0x1dc0: -+ case 0x1dc1: -+ return code - 0x1dc0 + 165; -+ case 0x1dc3: -+ case 0x1dc4: -+ case 0x1dc5: -+ case 0x1dc6: -+ case 0x1dc7: -+ case 0x1dc8: -+ case 0x1dc9: -+ return code - 0x1dc3 + 167; -+ case 0x1dcb: -+ case 0x1dcc: -+ return code - 0x1dcb + 174; -+ case 0x1dd1: -+ case 0x1dd2: -+ case 0x1dd3: -+ case 0x1dd4: -+ case 0x1dd5: -+ case 0x1dd6: -+ case 0x1dd7: -+ case 0x1dd8: -+ case 0x1dd9: -+ case 0x1dda: -+ case 0x1ddb: -+ case 0x1ddc: -+ case 0x1ddd: -+ case 0x1dde: -+ case 0x1ddf: -+ case 0x1de0: -+ case 0x1de1: -+ case 0x1de2: -+ case 0x1de3: -+ case 0x1de4: -+ case 0x1de5: -+ case 0x1de6: -+ return code - 0x1dd1 + 176; -+ case 0x1dfe: -+ return code - 0x1dfe + 198; -+ case 0x20d0: -+ case 0x20d1: -+ return code - 0x20d0 + 199; -+ case 0x20d4: -+ case 0x20d5: -+ case 0x20d6: -+ case 0x20d7: -+ return code - 0x20d4 + 201; -+ case 0x20db: -+ case 0x20dc: -+ return code - 0x20db + 205; -+ case 0x20e1: -+ return code - 0x20e1 + 207; -+ case 0x20e7: -+ return code - 0x20e7 + 208; -+ case 0x20e9: -+ return code - 0x20e9 + 209; -+ case 0x20f0: -+ return code - 0x20f0 + 210; -+ case 0x2cef: -+ case 0x2cf0: -+ case 0x2cf1: -+ return code - 0x2cef + 211; -+ case 0x2de0: -+ case 0x2de1: -+ case 0x2de2: -+ case 0x2de3: -+ case 0x2de4: -+ case 0x2de5: -+ case 0x2de6: -+ case 0x2de7: -+ case 0x2de8: -+ case 0x2de9: -+ case 0x2dea: -+ case 0x2deb: -+ case 0x2dec: -+ case 0x2ded: -+ case 0x2dee: -+ case 0x2def: -+ case 0x2df0: -+ case 0x2df1: -+ case 0x2df2: -+ case 0x2df3: -+ case 0x2df4: -+ case 0x2df5: -+ case 0x2df6: -+ case 0x2df7: -+ case 0x2df8: -+ case 0x2df9: -+ case 0x2dfa: -+ case 0x2dfb: -+ case 0x2dfc: -+ case 0x2dfd: -+ case 0x2dfe: -+ case 0x2dff: -+ return code - 0x2de0 + 214; -+ case 0xa66f: -+ return code - 0xa66f + 246; -+ case 0xa67c: -+ case 0xa67d: -+ return code - 0xa67c + 247; -+ case 0xa6f0: -+ case 0xa6f1: -+ return code - 0xa6f0 + 249; -+ case 0xa8e0: -+ case 0xa8e1: -+ case 0xa8e2: -+ case 0xa8e3: -+ case 0xa8e4: -+ case 0xa8e5: -+ case 0xa8e6: -+ case 0xa8e7: -+ case 0xa8e8: -+ case 0xa8e9: -+ case 0xa8ea: -+ case 0xa8eb: -+ case 0xa8ec: -+ case 0xa8ed: -+ case 0xa8ee: -+ case 0xa8ef: -+ case 0xa8f0: -+ case 0xa8f1: -+ return code - 0xa8e0 + 251; -+ case 0xaab0: -+ return code - 0xaab0 + 269; -+ case 0xaab2: -+ case 0xaab3: -+ return code - 0xaab2 + 270; -+ case 0xaab7: -+ case 0xaab8: -+ return code - 0xaab7 + 272; -+ case 0xaabe: -+ case 0xaabf: -+ return code - 0xaabe + 274; -+ case 0xaac1: -+ return code - 0xaac1 + 276; -+ case 0xfe20: -+ case 0xfe21: -+ case 0xfe22: -+ case 0xfe23: -+ case 0xfe24: -+ case 0xfe25: -+ case 0xfe26: -+ return code - 0xfe20 + 277; -+ case 0x10a0f: -+ return code - 0x10a0f + 284; -+ case 0x10a38: -+ return code - 0x10a38 + 285; -+ case 0x1d185: -+ case 0x1d186: -+ case 0x1d187: -+ case 0x1d188: -+ case 0x1d189: -+ return code - 0x1d185 + 286; -+ case 0x1d1aa: -+ case 0x1d1ab: -+ case 0x1d1ac: -+ case 0x1d1ad: -+ return code - 0x1d1aa + 291; -+ case 0x1d242: -+ case 0x1d243: -+ case 0x1d244: -+ return code - 0x1d242 + 295; -+ } -+ return 0; -+} -diff --git a/st.c b/st.c -index 57c6e96..f1c5299 100644 ---- a/st.c -+++ b/st.c -@@ -19,6 +19,7 @@ - - #include "st.h" - #include "win.h" -+#include "graphics.h" - - #if defined(__linux) - #include <pty.h> -@@ -36,6 +37,10 @@ - #define STR_BUF_SIZ ESC_BUF_SIZ - #define STR_ARG_SIZ ESC_ARG_SIZ - -+/* PUA character used as an image placeholder */ -+#define IMAGE_PLACEHOLDER_CHAR 0x10EEEE -+#define IMAGE_PLACEHOLDER_CHAR_OLD 0xEEEE -+ - /* macros */ - #define IS_SET(flag) ((term.mode & (flag)) != 0) - #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) -@@ -113,6 +118,8 @@ typedef struct { - typedef struct { - int row; /* nb row */ - int col; /* nb col */ -+ int pixw; /* width of the text area in pixels */ -+ int pixh; /* height of the text area in pixels */ - Line *line; /* screen */ - Line *alt; /* alternate screen */ - int *dirty; /* dirtyness of lines */ -@@ -213,7 +220,6 @@ static Rune utf8decodebyte(char, size_t *); - static char utf8encodebyte(Rune, size_t); - static size_t utf8validate(Rune *, size_t); - --static char *base64dec(const char *); - static char base64dec_getc(const char **); - - static ssize_t xwrite(int, const char *, size_t); -@@ -232,6 +238,10 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; - static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; - static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; - -+/* Converts a diacritic to a row/column/etc number. The result is 1-base, 0 -+ * means "couldn't convert". Defined in rowcolumn_diacritics_helpers.c */ -+uint16_t diacritic_to_num(uint32_t code); -+ - ssize_t - xwrite(int fd, const char *s, size_t len) - { -@@ -616,6 +626,12 @@ getsel(void) - if (gp->mode & ATTR_WDUMMY) - continue; - -+ if (gp->mode & ATTR_IMAGE) { -+ // TODO: Copy diacritics as well -+ ptr += utf8encode(IMAGE_PLACEHOLDER_CHAR, ptr); -+ continue; -+ } -+ - ptr += utf8encode(gp->u, ptr); - } - -@@ -819,7 +835,11 @@ ttyread(void) - { - static char buf[BUFSIZ]; - static int buflen = 0; -- int ret, written; -+ static int already_processing = 0; -+ int ret, written = 0; -+ -+ if (buflen >= LEN(buf)) -+ return 0; - - /* append read bytes to unprocessed bytes */ - ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); -@@ -831,7 +851,24 @@ ttyread(void) - die("couldn't read from shell: %s\n", strerror(errno)); - default: - buflen += ret; -- written = twrite(buf, buflen, 0); -+ if (already_processing) { -+ /* Avoid recursive call to twrite() */ -+ return ret; -+ } -+ already_processing = 1; -+ while (1) { -+ int buflen_before_processing = buflen; -+ written += twrite(buf + written, buflen - written, 0); -+ // If buflen changed during the call to twrite, there is -+ // new data, and we need to keep processing, otherwise -+ // we can exit. This will not loop forever because the -+ // buffer is limited, and we don't clean it in this -+ // loop, so at some point ttywrite will have to drop -+ // some data. -+ if (buflen_before_processing == buflen) -+ break; -+ } -+ already_processing = 0; - buflen -= written; - /* keep any incomplete UTF-8 byte sequence for the next call */ - if (buflen > 0) -@@ -874,6 +911,7 @@ ttywriteraw(const char *s, size_t n) - fd_set wfd, rfd; - ssize_t r; - size_t lim = 256; -+ int retries_left = 100; - - /* - * Remember that we are using a pty, which might be a modem line. -@@ -882,6 +920,9 @@ ttywriteraw(const char *s, size_t n) - * FIXME: Migrate the world to Plan 9. - */ - while (n > 0) { -+ if (retries_left-- <= 0) -+ goto too_many_retries; -+ - FD_ZERO(&wfd); - FD_ZERO(&rfd); - FD_SET(cmdfd, &wfd); -@@ -923,11 +964,16 @@ ttywriteraw(const char *s, size_t n) - - write_error: - die("write error on tty: %s\n", strerror(errno)); -+too_many_retries: -+ fprintf(stderr, "Could not write %zu bytes to tty\n", n); - } - - void - ttyresize(int tw, int th) - { -+ term.pixw = tw; -+ term.pixh = th; -+ - struct winsize w; - - w.ws_row = term.row; -@@ -1015,7 +1061,8 @@ treset(void) - term.c = (TCursor){{ - .mode = ATTR_NULL, - .fg = defaultfg, -- .bg = defaultbg -+ .bg = defaultbg, -+ .decor = DECOR_DEFAULT_COLOR - }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; - - memset(term.tabs, 0, term.col * sizeof(*term.tabs)); -@@ -1038,7 +1085,9 @@ treset(void) - void - tnew(int col, int row) - { -- term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; -+ term = (Term){.c = {.attr = {.fg = defaultfg, -+ .bg = defaultbg, -+ .decor = DECOR_DEFAULT_COLOR}}}; - tresize(col, row); - treset(); - } -@@ -1215,9 +1264,24 @@ tsetchar(Rune u, const Glyph *attr, int x, int y) - term.line[y][x-1].mode &= ~ATTR_WIDE; - } - -+ if (u == ' ' && term.line[y][x].mode & ATTR_IMAGE && -+ tgetisclassicplaceholder(&term.line[y][x])) { -+ // This is a workaround: don't overwrite classic placement -+ // placeholders with space symbols (unlike Unicode placeholders -+ // which must be overwritten by anything). -+ term.line[y][x].bg = attr->bg; -+ term.dirty[y] = 1; -+ return; -+ } -+ - term.dirty[y] = 1; - term.line[y][x] = *attr; - term.line[y][x].u = u; -+ -+ if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD) { -+ term.line[y][x].u = 0; -+ term.line[y][x].mode |= ATTR_IMAGE; -+ } - } - - void -@@ -1244,12 +1308,104 @@ tclearregion(int x1, int y1, int x2, int y2) - selclear(); - gp->fg = term.c.attr.fg; - gp->bg = term.c.attr.bg; -+ gp->decor = term.c.attr.decor; - gp->mode = 0; - gp->u = ' '; - } - } - } - -+/// Fills a rectangle area with an image placeholder. The starting point is the -+/// cursor. Adds empty lines if needed. The placeholder will be marked as -+/// classic. -+void -+tcreateimgplaceholder(uint32_t image_id, uint32_t placement_id, -+ int cols, int rows, char do_not_move_cursor) -+{ -+ for (int row = 0; row < rows; ++row) { -+ int y = term.c.y; -+ term.dirty[y] = 1; -+ for (int col = 0; col < cols; ++col) { -+ int x = term.c.x + col; -+ if (x >= term.col) -+ break; -+ Glyph *gp = &term.line[y][x]; -+ if (selected(x, y)) -+ selclear(); -+ gp->mode = ATTR_IMAGE; -+ gp->u = 0; -+ tsetimgrow(gp, row + 1); -+ tsetimgcol(gp, col + 1); -+ tsetimgid(gp, image_id); -+ tsetimgplacementid(gp, placement_id); -+ tsetimgdiacriticcount(gp, 3); -+ tsetisclassicplaceholder(gp, 1); -+ } -+ // If moving the cursor is not allowed and this is the last line -+ // of the terminal, we are done. -+ if (do_not_move_cursor && y == term.row - 1) -+ break; -+ // Move the cursor down, maybe creating a new line. The x is -+ // preserved (we never change term.c.x in the loop above). -+ if (row != rows - 1) -+ tnewline(/*first_col=*/0); -+ } -+ if (do_not_move_cursor) { -+ // Return the cursor to the original position. -+ tmoveto(term.c.x, term.c.y - rows + 1); -+ } else { -+ // Move the cursor beyond the last column, as required by the -+ // protocol. If the cursor goes beyond the screen edge, insert a -+ // newline to match the behavior of kitty. -+ if (term.c.x + cols >= term.col) -+ tnewline(/*first_col=*/1); -+ else -+ tmoveto(term.c.x + cols, term.c.y); -+ } -+} -+ -+void gr_for_each_image_cell(int (*callback)(void *data, uint32_t image_id, -+ uint32_t placement_id, int col, -+ int row, char is_classic), -+ void *data) { -+ for (int row = 0; row < term.row; ++row) { -+ for (int col = 0; col < term.col; ++col) { -+ Glyph *gp = &term.line[row][col]; -+ if (gp->mode & ATTR_IMAGE) { -+ uint32_t image_id = tgetimgid(gp); -+ uint32_t placement_id = tgetimgplacementid(gp); -+ int ret = -+ callback(data, tgetimgid(gp), -+ tgetimgplacementid(gp), -+ tgetimgcol(gp), tgetimgrow(gp), -+ tgetisclassicplaceholder(gp)); -+ if (ret == 1) { -+ term.dirty[row] = 1; -+ gp->mode = 0; -+ gp->u = ' '; -+ } -+ } -+ } -+ } -+} -+ -+void gr_schedule_image_redraw_by_id(uint32_t image_id) { -+ for (int row = 0; row < term.row; ++row) { -+ if (term.dirty[row]) -+ continue; -+ for (int col = 0; col < term.col; ++col) { -+ Glyph *gp = &term.line[row][col]; -+ if (gp->mode & ATTR_IMAGE) { -+ uint32_t cell_image_id = tgetimgid(gp); -+ if (cell_image_id == image_id) { -+ term.dirty[row] = 1; -+ break; -+ } -+ } -+ } -+ } -+} -+ - void - tdeletechar(int n) - { -@@ -1368,6 +1524,7 @@ tsetattr(const int *attr, int l) - ATTR_STRUCK ); - term.c.attr.fg = defaultfg; - term.c.attr.bg = defaultbg; -+ term.c.attr.decor = DECOR_DEFAULT_COLOR; - break; - case 1: - term.c.attr.mode |= ATTR_BOLD; -@@ -1380,6 +1537,20 @@ tsetattr(const int *attr, int l) - break; - case 4: - term.c.attr.mode |= ATTR_UNDERLINE; -+ if (i + 1 < l) { -+ idx = attr[++i]; -+ if (BETWEEN(idx, 1, 5)) { -+ tsetdecorstyle(&term.c.attr, idx); -+ } else if (idx == 0) { -+ term.c.attr.mode &= ~ATTR_UNDERLINE; -+ tsetdecorstyle(&term.c.attr, 0); -+ } else { -+ fprintf(stderr, -+ "erresc: unknown underline " -+ "style %d\n", -+ idx); -+ } -+ } - break; - case 5: /* slow blink */ - /* FALLTHROUGH */ -@@ -1403,6 +1574,7 @@ tsetattr(const int *attr, int l) - break; - case 24: - term.c.attr.mode &= ~ATTR_UNDERLINE; -+ tsetdecorstyle(&term.c.attr, 0); - break; - case 25: - term.c.attr.mode &= ~ATTR_BLINK; -@@ -1430,6 +1602,13 @@ tsetattr(const int *attr, int l) - case 49: - term.c.attr.bg = defaultbg; - break; -+ case 58: -+ if ((idx = tdefcolor(attr, &i, l)) >= 0) -+ tsetdecorcolor(&term.c.attr, idx); -+ break; -+ case 59: -+ tsetdecorcolor(&term.c.attr, DECOR_DEFAULT_COLOR); -+ break; - default: - if (BETWEEN(attr[i], 30, 37)) { - term.c.attr.fg = attr[i] - 30; -@@ -1813,6 +1992,39 @@ csihandle(void) - goto unknown; - } - break; -+ case '>': -+ switch (csiescseq.mode[1]) { -+ case 'q': /* XTVERSION -- Print terminal name and version */ -+ len = snprintf(buf, sizeof(buf), -+ "\033P>|st-graphics(%s)\033\\", VERSION); -+ ttywrite(buf, len, 0); -+ break; -+ default: -+ goto unknown; -+ } -+ break; -+ case 't': /* XTWINOPS -- Window manipulation */ -+ switch (csiescseq.arg[0]) { -+ case 14: /* Report text area size in pixels. */ -+ len = snprintf(buf, sizeof(buf), "\033[4;%i;%it", -+ term.pixh, term.pixw); -+ ttywrite(buf, len, 0); -+ break; -+ case 16: /* Report character cell size in pixels. */ -+ len = snprintf(buf, sizeof(buf), "\033[6;%i;%it", -+ term.pixh / term.row, -+ term.pixw / term.col); -+ ttywrite(buf, len, 0); -+ break; -+ case 18: /* Report the size of the text area in characters. */ -+ len = snprintf(buf, sizeof(buf), "\033[8;%i;%it", -+ term.row, term.col); -+ ttywrite(buf, len, 0); -+ break; -+ default: -+ goto unknown; -+ } -+ break; - } - } - -@@ -1962,8 +2174,26 @@ strhandle(void) - case 'k': /* old title set compatibility */ - xsettitle(strescseq.args[0]); - return; -- case 'P': /* DCS -- Device Control String */ - case '_': /* APC -- Application Program Command */ -+ if (gr_parse_command(strescseq.buf, strescseq.len)) { -+ GraphicsCommandResult *res = &graphics_command_result; -+ if (res->create_placeholder) { -+ tcreateimgplaceholder( -+ res->placeholder.image_id, -+ res->placeholder.placement_id, -+ res->placeholder.columns, -+ res->placeholder.rows, -+ res->placeholder.do_not_move_cursor); -+ } -+ if (res->response[0]) -+ ttywrite(res->response, strlen(res->response), -+ 0); -+ if (res->redraw) -+ tfulldirt(); -+ return; -+ } -+ return; -+ case 'P': /* DCS -- Device Control String */ - case '^': /* PM -- Privacy Message */ - return; - } -@@ -2469,6 +2699,33 @@ check_control_code: - if (selected(term.c.x, term.c.y)) - selclear(); - -+ if (width == 0) { -+ // It's probably a combining char. Combining characters are not -+ // supported, so we just ignore them, unless it denotes the row and -+ // column of an image character. -+ if (term.c.y <= 0 && term.c.x <= 0) -+ return; -+ else if (term.c.x == 0) -+ gp = &term.line[term.c.y-1][term.col-1]; -+ else if (term.c.state & CURSOR_WRAPNEXT) -+ gp = &term.line[term.c.y][term.c.x]; -+ else -+ gp = &term.line[term.c.y][term.c.x-1]; -+ uint16_t num = diacritic_to_num(u); -+ if (num && (gp->mode & ATTR_IMAGE)) { -+ unsigned diaccount = tgetimgdiacriticcount(gp); -+ if (diaccount == 0) -+ tsetimgrow(gp, num); -+ else if (diaccount == 1) -+ tsetimgcol(gp, num); -+ else if (diaccount == 2) -+ tsetimg4thbyteplus1(gp, num); -+ tsetimgdiacriticcount(gp, diaccount + 1); -+ } -+ term.lastc = u; -+ return; -+ } -+ - gp = &term.line[term.c.y][term.c.x]; - if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { - gp->mode |= ATTR_WRAP; -@@ -2635,6 +2892,8 @@ drawregion(int x1, int y1, int x2, int y2) - { - int y; - -+ xstartimagedraw(term.dirty, term.row); -+ - for (y = y1; y < y2; y++) { - if (!term.dirty[y]) - continue; -@@ -2642,6 +2901,8 @@ drawregion(int x1, int y1, int x2, int y2) - term.dirty[y] = 0; - xdrawline(term.line[y], x1, y, x2); - } -+ -+ xfinishimagedraw(); - } - - void -@@ -2676,3 +2937,9 @@ redraw(void) - tfulldirt(); - draw(); - } -+ -+Glyph -+getglyphat(int col, int row) -+{ -+ return term.line[row][col]; -+} -diff --git a/st.h b/st.h -index fd3b0d8..c5dd731 100644 ---- a/st.h -+++ b/st.h -@@ -12,7 +12,7 @@ - #define DEFAULT(a, b) (a) = (a) ? (a) : (b) - #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) - #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ -- (a).bg != (b).bg) -+ (a).bg != (b).bg || (a).decor != (b).decor) - #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ - (t1.tv_nsec-t2.tv_nsec)/1E6) - #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) -@@ -20,6 +20,10 @@ - #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) - #define IS_TRUECOL(x) (1 << 24 & (x)) - -+// This decor color indicates that the fg color should be used. Note that it's -+// not a 24-bit color because the 25-th bit is not set. -+#define DECOR_DEFAULT_COLOR 0x0ffffff -+ - enum glyph_attribute { - ATTR_NULL = 0, - ATTR_BOLD = 1 << 0, -@@ -34,6 +38,7 @@ enum glyph_attribute { - ATTR_WIDE = 1 << 9, - ATTR_WDUMMY = 1 << 10, - ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, -+ ATTR_IMAGE = 1 << 14, - }; - - enum selection_mode { -@@ -52,6 +57,14 @@ enum selection_snap { - SNAP_LINE = 2 - }; - -+enum underline_style { -+ UNDERLINE_STRAIGHT = 1, -+ UNDERLINE_DOUBLE = 2, -+ UNDERLINE_CURLY = 3, -+ UNDERLINE_DOTTED = 4, -+ UNDERLINE_DASHED = 5, -+}; -+ - typedef unsigned char uchar; - typedef unsigned int uint; - typedef unsigned long ulong; -@@ -65,6 +78,7 @@ typedef struct { - ushort mode; /* attribute flags */ - uint32_t fg; /* foreground */ - uint32_t bg; /* background */ -+ uint32_t decor; /* decoration (like underline) */ - } Glyph; - - typedef Glyph *Line; -@@ -105,6 +119,8 @@ void selextend(int, int, int, int); - int selected(int, int); - char *getsel(void); - -+Glyph getglyphat(int, int); -+ - size_t utf8encode(Rune, char *); - - void *xmalloc(size_t); -@@ -124,3 +140,69 @@ extern unsigned int tabspaces; - extern unsigned int defaultfg; - extern unsigned int defaultbg; - extern unsigned int defaultcs; -+ -+// Accessors to decoration properties stored in `decor`. -+// The 25-th bit is used to indicate if it's a 24-bit color. -+static inline uint32_t tgetdecorcolor(Glyph *g) { return g->decor & 0x1ffffff; } -+static inline uint32_t tgetdecorstyle(Glyph *g) { return (g->decor >> 25) & 0x7; } -+static inline void tsetdecorcolor(Glyph *g, uint32_t color) { -+ g->decor = (g->decor & ~0x1ffffff) | (color & 0x1ffffff); -+} -+static inline void tsetdecorstyle(Glyph *g, uint32_t style) { -+ g->decor = (g->decor & ~(0x7 << 25)) | ((style & 0x7) << 25); -+} -+ -+ -+// Some accessors to image placeholder properties stored in `u`: -+// - row (1-base) - 9 bits -+// - column (1-base) - 9 bits -+// - most significant byte of the image id plus 1 - 9 bits (0 means unspecified, -+// don't forget to subtract 1). -+// - the original number of diacritics (0, 1, 2, or 3) - 2 bits -+// - whether this is a classic (1) or Unicode (0) placeholder - 1 bit -+static inline uint32_t tgetimgrow(Glyph *g) { return g->u & 0x1ff; } -+static inline uint32_t tgetimgcol(Glyph *g) { return (g->u >> 9) & 0x1ff; } -+static inline uint32_t tgetimgid4thbyteplus1(Glyph *g) { return (g->u >> 18) & 0x1ff; } -+static inline uint32_t tgetimgdiacriticcount(Glyph *g) { return (g->u >> 27) & 0x3; } -+static inline uint32_t tgetisclassicplaceholder(Glyph *g) { return (g->u >> 29) & 0x1; } -+static inline void tsetimgrow(Glyph *g, uint32_t row) { -+ g->u = (g->u & ~0x1ff) | (row & 0x1ff); -+} -+static inline void tsetimgcol(Glyph *g, uint32_t col) { -+ g->u = (g->u & ~(0x1ff << 9)) | ((col & 0x1ff) << 9); -+} -+static inline void tsetimg4thbyteplus1(Glyph *g, uint32_t byteplus1) { -+ g->u = (g->u & ~(0x1ff << 18)) | ((byteplus1 & 0x1ff) << 18); -+} -+static inline void tsetimgdiacriticcount(Glyph *g, uint32_t count) { -+ g->u = (g->u & ~(0x3 << 27)) | ((count & 0x3) << 27); -+} -+static inline void tsetisclassicplaceholder(Glyph *g, uint32_t isclassic) { -+ g->u = (g->u & ~(0x1 << 29)) | ((isclassic & 0x1) << 29); -+} -+ -+/// Returns the full image id. This is a naive implementation, if the most -+/// significant byte is not specified, it's assumed to be 0 instead of inferring -+/// it from the cells to the left. -+static inline uint32_t tgetimgid(Glyph *g) { -+ uint32_t msb = tgetimgid4thbyteplus1(g); -+ if (msb != 0) -+ --msb; -+ return (msb << 24) | (g->fg & 0xFFFFFF); -+} -+ -+/// Sets the full image id. -+static inline void tsetimgid(Glyph *g, uint32_t id) { -+ g->fg = (id & 0xFFFFFF) | (1 << 24); -+ tsetimg4thbyteplus1(g, ((id >> 24) & 0xFF) + 1); -+} -+ -+static inline uint32_t tgetimgplacementid(Glyph *g) { -+ if (tgetdecorcolor(g) == DECOR_DEFAULT_COLOR) -+ return 0; -+ return g->decor & 0xFFFFFF; -+} -+ -+static inline void tsetimgplacementid(Glyph *g, uint32_t id) { -+ g->decor = (id & 0xFFFFFF) | (1 << 24); -+} -diff --git a/st.info b/st.info -index efab2cf..ded76c1 100644 ---- a/st.info -+++ b/st.info -@@ -195,6 +195,7 @@ st-mono| simpleterm monocolor, - Ms=\E]52;%p1%s;%p2%s\007, - Se=\E[2 q, - Ss=\E[%p1%d q, -+ Smulx=\E[4:%p1%dm, - - st| simpleterm, - use=st-mono, -@@ -215,6 +216,11 @@ st-256color| simpleterm with 256 colors, - initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, - setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, - setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, -+# Underline colors -+ Su, -+ Setulc=\E[58:2:%p1%{65536}%/%d:%p1%{256}%/%{255}%&%d:%p1%{255}%&%d%;m, -+ Setulc1=\E[58:5:%p1%dm, -+ ol=\E[59m, - - st-meta| simpleterm with meta key, - use=st, -diff --git a/win.h b/win.h -index 6de960d..31b3fff 100644 ---- a/win.h -+++ b/win.h -@@ -39,3 +39,6 @@ void xsetpointermotion(int); - void xsetsel(char *); - int xstartdraw(void); - void xximspot(int, int); -+ -+void xstartimagedraw(int *dirty, int rows); -+void xfinishimagedraw(); -diff --git a/x.c b/x.c -index d73152b..6f1bf8c 100644 ---- a/x.c -+++ b/x.c -@@ -4,6 +4,8 @@ - #include <limits.h> - #include <locale.h> - #include <signal.h> -+#include <stdio.h> -+#include <stdlib.h> - #include <sys/select.h> - #include <time.h> - #include <unistd.h> -@@ -19,6 +21,7 @@ char *argv0; - #include "arg.h" - #include "st.h" - #include "win.h" -+#include "graphics.h" - - /* types used in config.h */ - typedef struct { -@@ -59,6 +62,12 @@ static void zoom(const Arg *); - static void zoomabs(const Arg *); - static void zoomreset(const Arg *); - static void ttysend(const Arg *); -+static void previewimage(const Arg *); -+static void showimageinfo(const Arg *); -+static void togglegrdebug(const Arg *); -+static void dumpgrstate(const Arg *); -+static void unloadimages(const Arg *); -+static void toggleimages(const Arg *); - - /* config.h for applying patches and the configuration. */ - #include "config.h" -@@ -81,6 +90,7 @@ typedef XftGlyphFontSpec GlyphFontSpec; - typedef struct { - int tw, th; /* tty width and height */ - int w, h; /* window width and height */ -+ int hborderpx, vborderpx; - int ch; /* char height */ - int cw; /* char width */ - int mode; /* window state/mode flags */ -@@ -144,6 +154,8 @@ static inline ushort sixd_to_16bit(int); - static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); - static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); - static void xdrawglyph(Glyph, int, int); -+static void xdrawimages(Glyph, Line, int x1, int y1, int x2); -+static void xdrawoneimagecell(Glyph, int x, int y); - static void xclear(int, int, int, int); - static int xgeommasktogravity(int); - static int ximopen(Display *); -@@ -220,6 +232,7 @@ static DC dc; - static XWindow xw; - static XSelection xsel; - static TermWindow win; -+static unsigned int mouse_col = 0, mouse_row = 0; - - /* Font Ring Cache */ - enum { -@@ -328,10 +341,72 @@ ttysend(const Arg *arg) - ttywrite(arg->s, strlen(arg->s), 1); - } - -+void -+previewimage(const Arg *arg) -+{ -+ Glyph g = getglyphat(mouse_col, mouse_row); -+ if (g.mode & ATTR_IMAGE) { -+ uint32_t image_id = tgetimgid(&g); -+ fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n", -+ image_id, tgetimgplacementid(&g), tgetimgcol(&g), -+ tgetimgrow(&g)); -+ gr_preview_image(image_id, arg->s); -+ } -+} -+ -+void -+showimageinfo(const Arg *arg) -+{ -+ Glyph g = getglyphat(mouse_col, mouse_row); -+ if (g.mode & ATTR_IMAGE) { -+ uint32_t image_id = tgetimgid(&g); -+ fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n", -+ image_id, tgetimgplacementid(&g), tgetimgcol(&g), -+ tgetimgrow(&g)); -+ char stcommand[256] = {0}; -+ size_t len = snprintf(stcommand, sizeof(stcommand), "%s -e less", argv0); -+ if (len > sizeof(stcommand) - 1) { -+ fprintf(stderr, "Executable name too long: %s\n", -+ argv0); -+ return; -+ } -+ gr_show_image_info(image_id, tgetimgplacementid(&g), -+ tgetimgcol(&g), tgetimgrow(&g), -+ tgetisclassicplaceholder(&g), -+ tgetimgdiacriticcount(&g), argv0); -+ } -+} -+ -+void -+togglegrdebug(const Arg *arg) -+{ -+ graphics_debug_mode = (graphics_debug_mode + 1) % 3; -+ redraw(); -+} -+ -+void -+dumpgrstate(const Arg *arg) -+{ -+ gr_dump_state(); -+} -+ -+void -+unloadimages(const Arg *arg) -+{ -+ gr_unload_images_to_reduce_ram(); -+} -+ -+void -+toggleimages(const Arg *arg) -+{ -+ graphics_display_images = !graphics_display_images; -+ redraw(); -+} -+ - int - evcol(XEvent *e) - { -- int x = e->xbutton.x - borderpx; -+ int x = e->xbutton.x - win.hborderpx; - LIMIT(x, 0, win.tw - 1); - return x / win.cw; - } -@@ -339,7 +414,7 @@ evcol(XEvent *e) - int - evrow(XEvent *e) - { -- int y = e->xbutton.y - borderpx; -+ int y = e->xbutton.y - win.vborderpx; - LIMIT(y, 0, win.th - 1); - return y / win.ch; - } -@@ -452,6 +527,9 @@ mouseaction(XEvent *e, uint release) - /* ignore Button<N>mask for Button<N> - it's set on release */ - uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); - -+ mouse_col = evcol(e); -+ mouse_row = evrow(e); -+ - for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { - if (ms->release == release && - ms->button == e->xbutton.button && -@@ -739,6 +817,9 @@ cresize(int width, int height) - col = MAX(1, col); - row = MAX(1, row); - -+ win.hborderpx = (win.w - col * win.cw) * anysize_halign / 100; -+ win.vborderpx = (win.h - row * win.ch) * anysize_valign / 100; -+ - tresize(col, row); - xresize(col, row); - ttyresize(win.tw, win.th); -@@ -869,8 +950,8 @@ xhints(void) - sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; - sizeh->height = win.h; - sizeh->width = win.w; -- sizeh->height_inc = win.ch; -- sizeh->width_inc = win.cw; -+ sizeh->height_inc = 1; -+ sizeh->width_inc = 1; - sizeh->base_height = 2 * borderpx; - sizeh->base_width = 2 * borderpx; - sizeh->min_height = win.ch + 2 * borderpx; -@@ -1014,7 +1095,8 @@ xloadfonts(const char *fontstr, double fontsize) - FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); - usedfontsize = 12; - } -- defaultfontsize = usedfontsize; -+ if (defaultfontsize <= 0) -+ defaultfontsize = usedfontsize; - } - - if (xloadfont(&dc.font, pattern)) -@@ -1024,7 +1106,7 @@ xloadfonts(const char *fontstr, double fontsize) - FcPatternGetDouble(dc.font.match->pattern, - FC_PIXEL_SIZE, 0, &fontval); - usedfontsize = fontval; -- if (fontsize == 0) -+ if (defaultfontsize <= 0 && fontsize == 0) - defaultfontsize = fontval; - } - -@@ -1152,8 +1234,8 @@ xinit(int cols, int rows) - xloadcols(); - - /* adjust fixed window geometry */ -- win.w = 2 * borderpx + cols * win.cw; -- win.h = 2 * borderpx + rows * win.ch; -+ win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw; -+ win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch; - if (xw.gm & XNegative) - xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; - if (xw.gm & YNegative) -@@ -1240,12 +1322,15 @@ xinit(int cols, int rows) - xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); - if (xsel.xtarget == None) - xsel.xtarget = XA_STRING; -+ -+ // Initialize the graphics (image display) module. -+ gr_init(xw.dpy, xw.vis, xw.cmap); - } - - int - xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) - { -- float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; -+ float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp; - ushort mode, prevmode = USHRT_MAX; - Font *font = &dc.font; - int frcflags = FRC_NORMAL; -@@ -1267,6 +1352,11 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x - if (mode == ATTR_WDUMMY) - continue; - -+ /* Draw spaces for image placeholders (images will be drawn -+ * separately). */ -+ if (mode & ATTR_IMAGE) -+ rune = ' '; -+ - /* Determine font for glyph if different from previous glyph. */ - if (prevmode != mode) { - prevmode = mode; -@@ -1374,11 +1464,61 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x - return numspecs; - } - -+/* Draws a horizontal dashed line of length `w` starting at `(x, y)`. `wavelen` -+ * is the length of the dash plus the length of the gap. `fraction` is the -+ * fraction of the dash length compared to `wavelen`. */ -+static void -+xdrawunderdashed(Draw draw, Color *color, int x, int y, int w, -+ int wavelen, float fraction, int thick) -+{ -+ int dashw = MAX(1, fraction * wavelen); -+ for (int i = x - x % wavelen; i < x + w; i += wavelen) { -+ int startx = MAX(i, x); -+ int endx = MIN(i + dashw, x + w); -+ if (startx < endx) -+ XftDrawRect(xw.draw, color, startx, y, endx - startx, -+ thick); -+ } -+} -+ -+/* Draws an undercurl. `h` is the total height, including line thickness. */ -+static void -+xdrawundercurl(Draw draw, Color *color, int x, int y, int w, int h, int thick) -+{ -+ XGCValues gcvals = {.foreground = color->pixel, -+ .line_width = thick, -+ .line_style = LineSolid, -+ .cap_style = CapRound}; -+ GC gc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw), -+ GCForeground | GCLineWidth | GCLineStyle | GCCapStyle, -+ &gcvals); -+ -+ XRectangle clip = {.x = x, .y = y, .width = w, .height = h}; -+ XSetClipRectangles(xw.dpy, gc, 0, 0, &clip, 1, Unsorted); -+ -+ int yoffset = thick / 2; -+ int segh = MAX(1, h - thick); -+ /* Make sure every segment is at a 45 degree angle, otherwise it doesn't -+ * look good without antialiasing. */ -+ int segw = segh; -+ int wavelen = MAX(1, segw * 2); -+ -+ for (int i = x - (x % wavelen); i < x + w; i += wavelen) { -+ XPoint points[3] = {{.x = i, .y = y + yoffset}, -+ {.x = i + segw, .y = y + yoffset + segh}, -+ {.x = i + wavelen, .y = y + yoffset}}; -+ XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), gc, points, 3, -+ CoordModeOrigin); -+ } -+ -+ XFreeGC(xw.dpy, gc); -+} -+ - void - xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) - { - int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); -- int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, -+ int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, - width = charlen * win.cw; - Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; - XRenderColor colfg, colbg; -@@ -1468,17 +1608,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i - - /* Intelligent cleaning up of the borders. */ - if (x == 0) { -- xclear(0, (y == 0)? 0 : winy, borderpx, -+ xclear(0, (y == 0)? 0 : winy, win.hborderpx, - winy + win.ch + -- ((winy + win.ch >= borderpx + win.th)? win.h : 0)); -+ ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); - } -- if (winx + width >= borderpx + win.tw) { -+ if (winx + width >= win.hborderpx + win.tw) { - xclear(winx + width, (y == 0)? 0 : winy, win.w, -- ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); -+ ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); - } - if (y == 0) -- xclear(winx, 0, winx + width, borderpx); -- if (winy + win.ch >= borderpx + win.th) -+ xclear(winx, 0, winx + width, win.vborderpx); -+ if (winy + win.ch >= win.vborderpx + win.th) - xclear(winx, winy + win.ch, winx + width, win.h); - - /* Clean up the region we want to draw to. */ -@@ -1491,18 +1631,68 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i - r.width = width; - XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); - -- /* Render the glyphs. */ -- XftDrawGlyphFontSpec(xw.draw, fg, specs, len); -- -- /* Render underline and strikethrough. */ -+ /* Decoration color. */ -+ Color decor; -+ uint32_t decorcolor = tgetdecorcolor(&base); -+ if (decorcolor == DECOR_DEFAULT_COLOR) { -+ decor = *fg; -+ } else if (IS_TRUECOL(decorcolor)) { -+ colfg.alpha = 0xffff; -+ colfg.red = TRUERED(decorcolor); -+ colfg.green = TRUEGREEN(decorcolor); -+ colfg.blue = TRUEBLUE(decorcolor); -+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &decor); -+ } else { -+ decor = dc.col[decorcolor]; -+ } -+ decor.color.alpha = 0xffff; -+ decor.pixel |= 0xff << 24; -+ -+ /* Float thickness, used as a base to compute other values. */ -+ float fthick = dc.font.height / 18.0; -+ /* Integer thickness in pixels. Must not be 0. */ -+ int thick = MAX(1, roundf(fthick)); -+ /* The default gap between the baseline and a single underline. */ -+ int gap = roundf(fthick * 2); -+ /* The total thickness of a double underline. */ -+ int doubleh = thick * 2 + ceilf(fthick * 0.5); -+ /* The total thickness of an undercurl. */ -+ int curlh = thick * 2 + roundf(fthick * 0.75); -+ -+ /* Render the underline before the glyphs. */ - if (base.mode & ATTR_UNDERLINE) { -- XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, -- width, 1); -+ uint32_t style = tgetdecorstyle(&base); -+ int liney = winy + dc.font.ascent + gap; -+ /* Adjust liney to guarantee that a single underline fits. */ -+ liney -= MAX(0, liney + thick - (winy + win.ch)); -+ if (style == UNDERLINE_DOUBLE) { -+ liney -= MAX(0, liney + doubleh - (winy + win.ch)); -+ XftDrawRect(xw.draw, &decor, winx, liney, width, thick); -+ XftDrawRect(xw.draw, &decor, winx, -+ liney + doubleh - thick, width, thick); -+ } else if (style == UNDERLINE_DOTTED) { -+ xdrawunderdashed(xw.draw, &decor, winx, liney, width, -+ thick * 2, 0.5, thick); -+ } else if (style == UNDERLINE_DASHED) { -+ int wavelen = MAX(2, win.cw * 0.9); -+ xdrawunderdashed(xw.draw, &decor, winx, liney, width, -+ wavelen, 0.65, thick); -+ } else if (style == UNDERLINE_CURLY) { -+ liney -= MAX(0, liney + curlh - (winy + win.ch)); -+ xdrawundercurl(xw.draw, &decor, winx, liney, width, -+ curlh, thick); -+ } else { -+ XftDrawRect(xw.draw, &decor, winx, liney, width, thick); -+ } - } - -+ /* Render the glyphs. */ -+ XftDrawGlyphFontSpec(xw.draw, fg, specs, len); -+ -+ /* Render strikethrough. Alway use the fg color. */ - if (base.mode & ATTR_STRUCK) { -- XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, -- width, 1); -+ XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, -+ width, thick); - } - - /* Reset clip to none. */ -@@ -1517,6 +1707,11 @@ xdrawglyph(Glyph g, int x, int y) - - numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); - xdrawglyphfontspecs(&spec, g, numspecs, x, y); -+ if (g.mode & ATTR_IMAGE) { -+ gr_start_drawing(xw.buf, win.cw, win.ch); -+ xdrawoneimagecell(g, x, y); -+ gr_finish_drawing(xw.buf); -+ } - } - - void -@@ -1532,6 +1727,10 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) - if (IS_SET(MODE_HIDE)) - return; - -+ // If it's an image, just draw a ballot box for simplicity. -+ if (g.mode & ATTR_IMAGE) -+ g.u = 0x2610; -+ - /* - * Select the right color for the right mode. - */ -@@ -1572,39 +1771,167 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) - case 3: /* Blinking Underline */ - case 4: /* Steady Underline */ - XftDrawRect(xw.draw, &drawcol, -- borderpx + cx * win.cw, -- borderpx + (cy + 1) * win.ch - \ -+ win.hborderpx + cx * win.cw, -+ win.vborderpx + (cy + 1) * win.ch - \ - cursorthickness, - win.cw, cursorthickness); - break; - case 5: /* Blinking bar */ - case 6: /* Steady bar */ - XftDrawRect(xw.draw, &drawcol, -- borderpx + cx * win.cw, -- borderpx + cy * win.ch, -+ win.hborderpx + cx * win.cw, -+ win.vborderpx + cy * win.ch, - cursorthickness, win.ch); - break; - } - } else { - XftDrawRect(xw.draw, &drawcol, -- borderpx + cx * win.cw, -- borderpx + cy * win.ch, -+ win.hborderpx + cx * win.cw, -+ win.vborderpx + cy * win.ch, - win.cw - 1, 1); - XftDrawRect(xw.draw, &drawcol, -- borderpx + cx * win.cw, -- borderpx + cy * win.ch, -+ win.hborderpx + cx * win.cw, -+ win.vborderpx + cy * win.ch, - 1, win.ch - 1); - XftDrawRect(xw.draw, &drawcol, -- borderpx + (cx + 1) * win.cw - 1, -- borderpx + cy * win.ch, -+ win.hborderpx + (cx + 1) * win.cw - 1, -+ win.vborderpx + cy * win.ch, - 1, win.ch - 1); - XftDrawRect(xw.draw, &drawcol, -- borderpx + cx * win.cw, -- borderpx + (cy + 1) * win.ch - 1, -+ win.hborderpx + cx * win.cw, -+ win.vborderpx + (cy + 1) * win.ch - 1, - win.cw, 1); - } - } - -+/* Draw (or queue for drawing) image cells between columns x1 and x2 assuming -+ * that they have the same attributes (and thus the same lower 24 bits of the -+ * image ID and the same placement ID). */ -+void -+xdrawimages(Glyph base, Line line, int x1, int y1, int x2) { -+ int y_pix = win.vborderpx + y1 * win.ch; -+ uint32_t image_id_24bits = base.fg & 0xFFFFFF; -+ uint32_t placement_id = tgetimgplacementid(&base); -+ // Columns and rows are 1-based, 0 means unspecified. -+ int last_col = 0; -+ int last_row = 0; -+ int last_start_col = 0; -+ int last_start_x = x1; -+ // The most significant byte is also 1-base, subtract 1 before use. -+ uint32_t last_id_4thbyteplus1 = 0; -+ // We may need to inherit row/column/4th byte from the previous cell. -+ Glyph *prev = &line[x1 - 1]; -+ if (x1 > 0 && (prev->mode & ATTR_IMAGE) && -+ (prev->fg & 0xFFFFFF) == image_id_24bits && -+ prev->decor == base.decor) { -+ last_row = tgetimgrow(prev); -+ last_col = tgetimgcol(prev); -+ last_id_4thbyteplus1 = tgetimgid4thbyteplus1(prev); -+ last_start_col = last_col + 1; -+ } -+ for (int x = x1; x < x2; ++x) { -+ Glyph *g = &line[x]; -+ uint32_t cur_row = tgetimgrow(g); -+ uint32_t cur_col = tgetimgcol(g); -+ uint32_t cur_id_4thbyteplus1 = tgetimgid4thbyteplus1(g); -+ uint32_t num_diacritics = tgetimgdiacriticcount(g); -+ // If the row is not specified, assume it's the same as the row -+ // of the previous cell. Note that `cur_row` may contain a -+ // value imputed earlier, which will be preserved if `last_row` -+ // is zero (i.e. we don't know the row of the previous cell). -+ if (last_row && (num_diacritics == 0 || !cur_row)) -+ cur_row = last_row; -+ // If the column is not specified and the row is the same as the -+ // row of the previous cell, then assume that the column is the -+ // next one. -+ if (last_col && (num_diacritics <= 1 || !cur_col) && -+ cur_row == last_row) -+ cur_col = last_col + 1; -+ // If the additional id byte is not specified and the -+ // coordinates are consecutive, assume the byte is also the -+ // same. -+ if (last_id_4thbyteplus1 && -+ (num_diacritics <= 2 || !cur_id_4thbyteplus1) && -+ cur_row == last_row && cur_col == last_col + 1) -+ cur_id_4thbyteplus1 = last_id_4thbyteplus1; -+ // If we couldn't infer row and column, start from the top left -+ // corner. -+ if (cur_row == 0) -+ cur_row = 1; -+ if (cur_col == 0) -+ cur_col = 1; -+ // If this cell breaks a contiguous stripe of image cells, draw -+ // that line and start a new one. -+ if (cur_col != last_col + 1 || cur_row != last_row || -+ cur_id_4thbyteplus1 != last_id_4thbyteplus1) { -+ uint32_t image_id = image_id_24bits; -+ if (last_id_4thbyteplus1) -+ image_id |= (last_id_4thbyteplus1 - 1) << 24; -+ if (last_row != 0) { -+ int x_pix = -+ win.hborderpx + last_start_x * win.cw; -+ gr_append_imagerect( -+ xw.buf, image_id, placement_id, -+ last_start_col - 1, last_col, -+ last_row - 1, last_row, last_start_x, -+ y1, x_pix, y_pix, win.cw, win.ch, -+ base.mode & ATTR_REVERSE); -+ } -+ last_start_col = cur_col; -+ last_start_x = x; -+ } -+ last_row = cur_row; -+ last_col = cur_col; -+ last_id_4thbyteplus1 = cur_id_4thbyteplus1; -+ // Populate the missing glyph data to enable inheritance between -+ // runs and support the naive implementation of tgetimgid. -+ if (!tgetimgrow(g)) -+ tsetimgrow(g, cur_row); -+ // We cannot save this information if there are > 511 cols. -+ if (!tgetimgcol(g) && (cur_col & ~0x1ff) == 0) -+ tsetimgcol(g, cur_col); -+ if (!tgetimgid4thbyteplus1(g)) -+ tsetimg4thbyteplus1(g, cur_id_4thbyteplus1); -+ } -+ uint32_t image_id = image_id_24bits; -+ if (last_id_4thbyteplus1) -+ image_id |= (last_id_4thbyteplus1 - 1) << 24; -+ // Draw the last contiguous stripe. -+ if (last_row != 0) { -+ int x_pix = win.hborderpx + last_start_x * win.cw; -+ gr_append_imagerect(xw.buf, image_id, placement_id, -+ last_start_col - 1, last_col, last_row - 1, -+ last_row, last_start_x, y1, x_pix, y_pix, -+ win.cw, win.ch, base.mode & ATTR_REVERSE); -+ } -+} -+ -+/* Draw just one image cell without inheriting attributes from the left. */ -+void xdrawoneimagecell(Glyph g, int x, int y) { -+ if (!(g.mode & ATTR_IMAGE)) -+ return; -+ int x_pix = win.hborderpx + x * win.cw; -+ int y_pix = win.vborderpx + y * win.ch; -+ uint32_t row = tgetimgrow(&g) - 1; -+ uint32_t col = tgetimgcol(&g) - 1; -+ uint32_t placement_id = tgetimgplacementid(&g); -+ uint32_t image_id = tgetimgid(&g); -+ gr_append_imagerect(xw.buf, image_id, placement_id, col, col + 1, row, -+ row + 1, x, y, x_pix, y_pix, win.cw, win.ch, -+ g.mode & ATTR_REVERSE); -+} -+ -+/* Prepare for image drawing. */ -+void xstartimagedraw(int *dirty, int rows) { -+ gr_start_drawing(xw.buf, win.cw, win.ch); -+ gr_mark_dirty_animations(dirty, rows); -+} -+ -+/* Draw all queued image cells. */ -+void xfinishimagedraw() { -+ gr_finish_drawing(xw.buf); -+} -+ - void - xsetenv(void) - { -@@ -1671,6 +1998,8 @@ xdrawline(Line line, int x1, int y1, int x2) - new.mode ^= ATTR_REVERSE; - if (i > 0 && ATTRCMP(base, new)) { - xdrawglyphfontspecs(specs, base, i, ox, y1); -+ if (base.mode & ATTR_IMAGE) -+ xdrawimages(base, line, ox, y1, x); - specs += i; - numspecs -= i; - i = 0; -@@ -1683,6 +2012,8 @@ xdrawline(Line line, int x1, int y1, int x2) - } - if (i > 0) - xdrawglyphfontspecs(specs, base, i, ox, y1); -+ if (i > 0 && base.mode & ATTR_IMAGE) -+ xdrawimages(base, line, ox, y1, x); - } - - void -@@ -1907,6 +2238,7 @@ cmessage(XEvent *e) - } - } else if (e->xclient.data.l[0] == xw.wmdeletewin) { - ttyhangup(); -+ gr_deinit(); - exit(0); - } - } -@@ -1957,6 +2289,13 @@ run(void) - if (XPending(xw.dpy)) - timeout = 0; /* existing events might not set xfd */ - -+ /* Decrease the timeout if there are active animations. */ -+ if (graphics_next_redraw_delay != INT_MAX && -+ IS_SET(MODE_VISIBLE)) -+ timeout = timeout < 0 ? graphics_next_redraw_delay -+ : MIN(timeout, -+ graphics_next_redraw_delay); -+ - seltv.tv_sec = timeout / 1E3; - seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); - tv = timeout >= 0 ? &seltv : NULL; --- -2.43.0 - diff --git a/files/config/suckless/st/rowcolumn_diacritics_helpers.c b/files/config/suckless/st/rowcolumn_diacritics_helpers.c deleted file mode 100644 index 829c0fc..0000000 --- a/files/config/suckless/st/rowcolumn_diacritics_helpers.c +++ /dev/null @@ -1,391 +0,0 @@ -#include <stdint.h> - -uint16_t diacritic_to_num(uint32_t code) -{ - switch (code) { - case 0x305: - return code - 0x305 + 1; - case 0x30d: - case 0x30e: - return code - 0x30d + 2; - case 0x310: - return code - 0x310 + 4; - case 0x312: - return code - 0x312 + 5; - case 0x33d: - case 0x33e: - case 0x33f: - return code - 0x33d + 6; - case 0x346: - return code - 0x346 + 9; - case 0x34a: - case 0x34b: - case 0x34c: - return code - 0x34a + 10; - case 0x350: - case 0x351: - case 0x352: - return code - 0x350 + 13; - case 0x357: - return code - 0x357 + 16; - case 0x35b: - return code - 0x35b + 17; - case 0x363: - case 0x364: - case 0x365: - case 0x366: - case 0x367: - case 0x368: - case 0x369: - case 0x36a: - case 0x36b: - case 0x36c: - case 0x36d: - case 0x36e: - case 0x36f: - return code - 0x363 + 18; - case 0x483: - case 0x484: - case 0x485: - case 0x486: - case 0x487: - return code - 0x483 + 31; - case 0x592: - case 0x593: - case 0x594: - case 0x595: - return code - 0x592 + 36; - case 0x597: - case 0x598: - case 0x599: - return code - 0x597 + 40; - case 0x59c: - case 0x59d: - case 0x59e: - case 0x59f: - case 0x5a0: - case 0x5a1: - return code - 0x59c + 43; - case 0x5a8: - case 0x5a9: - return code - 0x5a8 + 49; - case 0x5ab: - case 0x5ac: - return code - 0x5ab + 51; - case 0x5af: - return code - 0x5af + 53; - case 0x5c4: - return code - 0x5c4 + 54; - case 0x610: - case 0x611: - case 0x612: - case 0x613: - case 0x614: - case 0x615: - case 0x616: - case 0x617: - return code - 0x610 + 55; - case 0x657: - case 0x658: - case 0x659: - case 0x65a: - case 0x65b: - return code - 0x657 + 63; - case 0x65d: - case 0x65e: - return code - 0x65d + 68; - case 0x6d6: - case 0x6d7: - case 0x6d8: - case 0x6d9: - case 0x6da: - case 0x6db: - case 0x6dc: - return code - 0x6d6 + 70; - case 0x6df: - case 0x6e0: - case 0x6e1: - case 0x6e2: - return code - 0x6df + 77; - case 0x6e4: - return code - 0x6e4 + 81; - case 0x6e7: - case 0x6e8: - return code - 0x6e7 + 82; - case 0x6eb: - case 0x6ec: - return code - 0x6eb + 84; - case 0x730: - return code - 0x730 + 86; - case 0x732: - case 0x733: - return code - 0x732 + 87; - case 0x735: - case 0x736: - return code - 0x735 + 89; - case 0x73a: - return code - 0x73a + 91; - case 0x73d: - return code - 0x73d + 92; - case 0x73f: - case 0x740: - case 0x741: - return code - 0x73f + 93; - case 0x743: - return code - 0x743 + 96; - case 0x745: - return code - 0x745 + 97; - case 0x747: - return code - 0x747 + 98; - case 0x749: - case 0x74a: - return code - 0x749 + 99; - case 0x7eb: - case 0x7ec: - case 0x7ed: - case 0x7ee: - case 0x7ef: - case 0x7f0: - case 0x7f1: - return code - 0x7eb + 101; - case 0x7f3: - return code - 0x7f3 + 108; - case 0x816: - case 0x817: - case 0x818: - case 0x819: - return code - 0x816 + 109; - case 0x81b: - case 0x81c: - case 0x81d: - case 0x81e: - case 0x81f: - case 0x820: - case 0x821: - case 0x822: - case 0x823: - return code - 0x81b + 113; - case 0x825: - case 0x826: - case 0x827: - return code - 0x825 + 122; - case 0x829: - case 0x82a: - case 0x82b: - case 0x82c: - case 0x82d: - return code - 0x829 + 125; - case 0x951: - return code - 0x951 + 130; - case 0x953: - case 0x954: - return code - 0x953 + 131; - case 0xf82: - case 0xf83: - return code - 0xf82 + 133; - case 0xf86: - case 0xf87: - return code - 0xf86 + 135; - case 0x135d: - case 0x135e: - case 0x135f: - return code - 0x135d + 137; - case 0x17dd: - return code - 0x17dd + 140; - case 0x193a: - return code - 0x193a + 141; - case 0x1a17: - return code - 0x1a17 + 142; - case 0x1a75: - case 0x1a76: - case 0x1a77: - case 0x1a78: - case 0x1a79: - case 0x1a7a: - case 0x1a7b: - case 0x1a7c: - return code - 0x1a75 + 143; - case 0x1b6b: - return code - 0x1b6b + 151; - case 0x1b6d: - case 0x1b6e: - case 0x1b6f: - case 0x1b70: - case 0x1b71: - case 0x1b72: - case 0x1b73: - return code - 0x1b6d + 152; - case 0x1cd0: - case 0x1cd1: - case 0x1cd2: - return code - 0x1cd0 + 159; - case 0x1cda: - case 0x1cdb: - return code - 0x1cda + 162; - case 0x1ce0: - return code - 0x1ce0 + 164; - case 0x1dc0: - case 0x1dc1: - return code - 0x1dc0 + 165; - case 0x1dc3: - case 0x1dc4: - case 0x1dc5: - case 0x1dc6: - case 0x1dc7: - case 0x1dc8: - case 0x1dc9: - return code - 0x1dc3 + 167; - case 0x1dcb: - case 0x1dcc: - return code - 0x1dcb + 174; - case 0x1dd1: - case 0x1dd2: - case 0x1dd3: - case 0x1dd4: - case 0x1dd5: - case 0x1dd6: - case 0x1dd7: - case 0x1dd8: - case 0x1dd9: - case 0x1dda: - case 0x1ddb: - case 0x1ddc: - case 0x1ddd: - case 0x1dde: - case 0x1ddf: - case 0x1de0: - case 0x1de1: - case 0x1de2: - case 0x1de3: - case 0x1de4: - case 0x1de5: - case 0x1de6: - return code - 0x1dd1 + 176; - case 0x1dfe: - return code - 0x1dfe + 198; - case 0x20d0: - case 0x20d1: - return code - 0x20d0 + 199; - case 0x20d4: - case 0x20d5: - case 0x20d6: - case 0x20d7: - return code - 0x20d4 + 201; - case 0x20db: - case 0x20dc: - return code - 0x20db + 205; - case 0x20e1: - return code - 0x20e1 + 207; - case 0x20e7: - return code - 0x20e7 + 208; - case 0x20e9: - return code - 0x20e9 + 209; - case 0x20f0: - return code - 0x20f0 + 210; - case 0x2cef: - case 0x2cf0: - case 0x2cf1: - return code - 0x2cef + 211; - case 0x2de0: - case 0x2de1: - case 0x2de2: - case 0x2de3: - case 0x2de4: - case 0x2de5: - case 0x2de6: - case 0x2de7: - case 0x2de8: - case 0x2de9: - case 0x2dea: - case 0x2deb: - case 0x2dec: - case 0x2ded: - case 0x2dee: - case 0x2def: - case 0x2df0: - case 0x2df1: - case 0x2df2: - case 0x2df3: - case 0x2df4: - case 0x2df5: - case 0x2df6: - case 0x2df7: - case 0x2df8: - case 0x2df9: - case 0x2dfa: - case 0x2dfb: - case 0x2dfc: - case 0x2dfd: - case 0x2dfe: - case 0x2dff: - return code - 0x2de0 + 214; - case 0xa66f: - return code - 0xa66f + 246; - case 0xa67c: - case 0xa67d: - return code - 0xa67c + 247; - case 0xa6f0: - case 0xa6f1: - return code - 0xa6f0 + 249; - case 0xa8e0: - case 0xa8e1: - case 0xa8e2: - case 0xa8e3: - case 0xa8e4: - case 0xa8e5: - case 0xa8e6: - case 0xa8e7: - case 0xa8e8: - case 0xa8e9: - case 0xa8ea: - case 0xa8eb: - case 0xa8ec: - case 0xa8ed: - case 0xa8ee: - case 0xa8ef: - case 0xa8f0: - case 0xa8f1: - return code - 0xa8e0 + 251; - case 0xaab0: - return code - 0xaab0 + 269; - case 0xaab2: - case 0xaab3: - return code - 0xaab2 + 270; - case 0xaab7: - case 0xaab8: - return code - 0xaab7 + 272; - case 0xaabe: - case 0xaabf: - return code - 0xaabe + 274; - case 0xaac1: - return code - 0xaac1 + 276; - case 0xfe20: - case 0xfe21: - case 0xfe22: - case 0xfe23: - case 0xfe24: - case 0xfe25: - case 0xfe26: - return code - 0xfe20 + 277; - case 0x10a0f: - return code - 0x10a0f + 284; - case 0x10a38: - return code - 0x10a38 + 285; - case 0x1d185: - case 0x1d186: - case 0x1d187: - case 0x1d188: - case 0x1d189: - return code - 0x1d185 + 286; - case 0x1d1aa: - case 0x1d1ab: - case 0x1d1ac: - case 0x1d1ad: - return code - 0x1d1aa + 291; - case 0x1d242: - case 0x1d243: - case 0x1d244: - return code - 0x1d242 + 295; - } - return 0; -} diff --git a/files/config/suckless/st/st b/files/config/suckless/st/st Binary files differnew file mode 100755 index 0000000..6851bee --- /dev/null +++ b/files/config/suckless/st/st diff --git a/files/config/suckless/st/st.c b/files/config/suckless/st/st.c index f634c9a..03e10d1 100644 --- a/files/config/suckless/st/st.c +++ b/files/config/suckless/st/st.c @@ -19,7 +19,6 @@ #include "st.h" #include "win.h" -#include "graphics.h" #if defined(__linux) #include <pty.h> @@ -38,14 +37,6 @@ #define STR_ARG_SIZ ESC_ARG_SIZ #define HISTSIZE 2000 -static inline void tsetimgrow(Glyph *g, uint32_t row) { - g->u.img.row = row; -} - -/* PUA character used as an image placeholder */ -#define IMAGE_PLACEHOLDER_CHAR 0x10EEEE -#define IMAGE_PLACEHOLDER_CHAR_OLD 0xEEEE - /* macros */ #define IS_SET(flag) ((term.mode & (flag)) != 0) #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) @@ -126,8 +117,6 @@ typedef struct { typedef struct { int row; /* nb row */ int col; /* nb col */ - int pixw; /* width of the text area in pixels */ - int pixh; /* height of the text area in pixels */ Line *line; /* screen */ Line *alt; /* alternate screen */ Line hist[HISTSIZE]; /* history buffer */ @@ -231,6 +220,7 @@ static Rune utf8decodebyte(char, size_t *); static char utf8encodebyte(Rune, size_t); static size_t utf8validate(Rune *, size_t); +static char *base64dec(const char *); static char base64dec_getc(const char **); static ssize_t xwrite(int, const char *, size_t); @@ -249,10 +239,6 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; -/* Converts a diacritic to a row/column/etc number. The result is 1-base, 0 - * means "couldn't convert". Defined in rowcolumn_diacritics_helpers.c */ -uint16_t diacritic_to_num(uint32_t code); - ssize_t xwrite(int fd, const char *s, size_t len) { @@ -637,12 +623,6 @@ getsel(void) if (gp->mode & ATTR_WDUMMY) continue; - if (gp->mode & ATTR_IMAGE) { - // TODO: Copy diacritics as well - ptr += utf8encode(IMAGE_PLACEHOLDER_CHAR, ptr); - continue; - } - ptr += utf8encode(gp->u, ptr); } @@ -846,11 +826,7 @@ ttyread(void) { static char buf[BUFSIZ]; static int buflen = 0; - static int already_processing = 0; - int ret, written = 0; - - if (buflen >= LEN(buf)) - return 0; + int ret, written; /* append read bytes to unprocessed bytes */ ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); @@ -862,24 +838,7 @@ ttyread(void) die("couldn't read from shell: %s\n", strerror(errno)); default: buflen += ret; - if (already_processing) { - /* Avoid recursive call to twrite() */ - return ret; - } - already_processing = 1; - while (1) { - int buflen_before_processing = buflen; - written += twrite(buf + written, buflen - written, 0); - // If buflen changed during the call to twrite, there is - // new data, and we need to keep processing, otherwise - // we can exit. This will not loop forever because the - // buffer is limited, and we don't clean it in this - // loop, so at some point ttywrite will have to drop - // some data. - if (buflen_before_processing == buflen) - break; - } - already_processing = 0; + written = twrite(buf, buflen, 0); buflen -= written; /* keep any incomplete UTF-8 byte sequence for the next call */ if (buflen > 0) @@ -925,7 +884,6 @@ ttywriteraw(const char *s, size_t n) fd_set wfd, rfd; ssize_t r; size_t lim = 256; - int retries_left = 100; /* * Remember that we are using a pty, which might be a modem line. @@ -934,9 +892,6 @@ ttywriteraw(const char *s, size_t n) * FIXME: Migrate the world to Plan 9. */ while (n > 0) { - if (retries_left-- <= 0) - goto too_many_retries; - FD_ZERO(&wfd); FD_ZERO(&rfd); FD_SET(cmdfd, &wfd); @@ -978,16 +933,11 @@ ttywriteraw(const char *s, size_t n) write_error: die("write error on tty: %s\n", strerror(errno)); -too_many_retries: - fprintf(stderr, "Could not write %zu bytes to tty\n", n); } void ttyresize(int tw, int th) { - term.pixw = tw; - term.pixh = th; - struct winsize w; w.ws_row = term.row; @@ -1075,8 +1025,7 @@ treset(void) term.c = (TCursor){{ .mode = ATTR_NULL, .fg = defaultfg, - .bg = defaultbg, - .decor = DECOR_DEFAULT_COLOR + .bg = defaultbg }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; memset(term.tabs, 0, term.col * sizeof(*term.tabs)); @@ -1099,9 +1048,7 @@ treset(void) void tnew(int col, int row) { - term = (Term){.c = {.attr = {.fg = defaultfg, - .bg = defaultbg, - .decor = DECOR_DEFAULT_COLOR}}}; + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; tresize(col, row); treset(); } @@ -1362,104 +1309,12 @@ tclearregion(int x1, int y1, int x2, int y2) selclear(); gp->fg = term.c.attr.fg; gp->bg = term.c.attr.bg; - gp->decor = term.c.attr.decor; gp->mode = 0; gp->u = ' '; } } } -/// Fills a rectangle area with an image placeholder. The starting point is the -/// cursor. Adds empty lines if needed. The placeholder will be marked as -/// classic. -void -tcreateimgplaceholder(uint32_t image_id, uint32_t placement_id, - int cols, int rows, char do_not_move_cursor) -{ - for (int row = 0; row < rows; ++row) { - int y = term.c.y; - term.dirty[y] = 1; - for (int col = 0; col < cols; ++col) { - int x = term.c.x + col; - if (x >= term.col) - break; - Glyph *gp = &term.line[y][x]; - if (selected(x, y)) - selclear(); - gp->mode = ATTR_IMAGE; - gp->u = 0; - tsetimgrow(gp, row + 1); - tsetimgcol(gp, col + 1); - tsetimgid(gp, image_id); - tsetimgplacementid(gp, placement_id); - tsetimgdiacriticcount(gp, 3); - tsetisclassicplaceholder(gp, 1); - } - // If moving the cursor is not allowed and this is the last line - // of the terminal, we are done. - if (do_not_move_cursor && y == term.row - 1) - break; - // Move the cursor down, maybe creating a new line. The x is - // preserved (we never change term.c.x in the loop above). - if (row != rows - 1) - tnewline(/*first_col=*/0); - } - if (do_not_move_cursor) { - // Return the cursor to the original position. - tmoveto(term.c.x, term.c.y - rows + 1); - } else { - // Move the cursor beyond the last column, as required by the - // protocol. If the cursor goes beyond the screen edge, insert a - // newline to match the behavior of kitty. - if (term.c.x + cols >= term.col) - tnewline(/*first_col=*/1); - else - tmoveto(term.c.x + cols, term.c.y); - } -} - -void gr_for_each_image_cell(int (*callback)(void *data, uint32_t image_id, - uint32_t placement_id, int col, - int row, char is_classic), - void *data) { - for (int row = 0; row < term.row; ++row) { - for (int col = 0; col < term.col; ++col) { - Glyph *gp = &term.line[row][col]; - if (gp->mode & ATTR_IMAGE) { - uint32_t image_id = tgetimgid(gp); - uint32_t placement_id = tgetimgplacementid(gp); - int ret = - callback(data, tgetimgid(gp), - tgetimgplacementid(gp), - tgetimgcol(gp), tgetimgrow(gp), - tgetisclassicplaceholder(gp)); - if (ret == 1) { - term.dirty[row] = 1; - gp->mode = 0; - gp->u = ' '; - } - } - } - } -} - -void gr_schedule_image_redraw_by_id(uint32_t image_id) { - for (int row = 0; row < term.row; ++row) { - if (term.dirty[row]) - continue; - for (int col = 0; col < term.col; ++col) { - Glyph *gp = &term.line[row][col]; - if (gp->mode & ATTR_IMAGE) { - uint32_t cell_image_id = tgetimgid(gp); - if (cell_image_id == image_id) { - term.dirty[row] = 1; - break; - } - } - } - } -} - void tdeletechar(int n) { @@ -1578,7 +1433,6 @@ tsetattr(const int *attr, int l) ATTR_STRUCK ); term.c.attr.fg = defaultfg; term.c.attr.bg = defaultbg; - term.c.attr.decor = DECOR_DEFAULT_COLOR; break; case 1: term.c.attr.mode |= ATTR_BOLD; @@ -1591,20 +1445,6 @@ tsetattr(const int *attr, int l) break; case 4: term.c.attr.mode |= ATTR_UNDERLINE; - if (i + 1 < l) { - idx = attr[++i]; - if (BETWEEN(idx, 1, 5)) { - tsetdecorstyle(&term.c.attr, idx); - } else if (idx == 0) { - term.c.attr.mode &= ~ATTR_UNDERLINE; - tsetdecorstyle(&term.c.attr, 0); - } else { - fprintf(stderr, - "erresc: unknown underline " - "style %d\n", - idx); - } - } break; case 5: /* slow blink */ /* FALLTHROUGH */ @@ -1628,7 +1468,6 @@ tsetattr(const int *attr, int l) break; case 24: term.c.attr.mode &= ~ATTR_UNDERLINE; - tsetdecorstyle(&term.c.attr, 0); break; case 25: term.c.attr.mode &= ~ATTR_BLINK; @@ -1656,13 +1495,6 @@ tsetattr(const int *attr, int l) case 49: term.c.attr.bg = defaultbg; break; - case 58: - if ((idx = tdefcolor(attr, &i, l)) >= 0) - tsetdecorcolor(&term.c.attr, idx); - break; - case 59: - tsetdecorcolor(&term.c.attr, DECOR_DEFAULT_COLOR); - break; default: if (BETWEEN(attr[i], 30, 37)) { term.c.attr.fg = attr[i] - 30; @@ -2046,39 +1878,6 @@ csihandle(void) goto unknown; } break; - case '>': - switch (csiescseq.mode[1]) { - case 'q': /* XTVERSION -- Print terminal name and version */ - len = snprintf(buf, sizeof(buf), - "\033P>|st-graphics(%s)\033\\", VERSION); - ttywrite(buf, len, 0); - break; - default: - goto unknown; - } - break; - case 't': /* XTWINOPS -- Window manipulation */ - switch (csiescseq.arg[0]) { - case 14: /* Report text area size in pixels. */ - len = snprintf(buf, sizeof(buf), "\033[4;%i;%it", - term.pixh, term.pixw); - ttywrite(buf, len, 0); - break; - case 16: /* Report character cell size in pixels. */ - len = snprintf(buf, sizeof(buf), "\033[6;%i;%it", - term.pixh / term.row, - term.pixw / term.col); - ttywrite(buf, len, 0); - break; - case 18: /* Report the size of the text area in characters. */ - len = snprintf(buf, sizeof(buf), "\033[8;%i;%it", - term.row, term.col); - ttywrite(buf, len, 0); - break; - default: - goto unknown; - } - break; } } @@ -2228,26 +2027,8 @@ strhandle(void) case 'k': /* old title set compatibility */ xsettitle(strescseq.args[0]); return; - case '_': /* APC -- Application Program Command */ - if (gr_parse_command(strescseq.buf, strescseq.len)) { - GraphicsCommandResult *res = &graphics_command_result; - if (res->create_placeholder) { - tcreateimgplaceholder( - res->placeholder.image_id, - res->placeholder.placement_id, - res->placeholder.columns, - res->placeholder.rows, - res->placeholder.do_not_move_cursor); - } - if (res->response[0]) - ttywrite(res->response, strlen(res->response), - 0); - if (res->redraw) - tfulldirt(); - return; - } - return; case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ case '^': /* PM -- Privacy Message */ return; } @@ -2753,33 +2534,6 @@ check_control_code: if (selected(term.c.x, term.c.y)) selclear(); - if (width == 0) { - // It's probably a combining char. Combining characters are not - // supported, so we just ignore them, unless it denotes the row and - // column of an image character. - if (term.c.y <= 0 && term.c.x <= 0) - return; - else if (term.c.x == 0) - gp = &term.line[term.c.y-1][term.col-1]; - else if (term.c.state & CURSOR_WRAPNEXT) - gp = &term.line[term.c.y][term.c.x]; - else - gp = &term.line[term.c.y][term.c.x-1]; - uint16_t num = diacritic_to_num(u); - if (num && (gp->mode & ATTR_IMAGE)) { - unsigned diaccount = tgetimgdiacriticcount(gp); - if (diaccount == 0) - tsetimgrow(gp, num); - else if (diaccount == 1) - tsetimgcol(gp, num); - else if (diaccount == 2) - tsetimg4thbyteplus1(gp, num); - tsetimgdiacriticcount(gp, diaccount + 1); - } - term.lastc = u; - return; - } - gp = &term.line[term.c.y][term.c.x]; if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { gp->mode |= ATTR_WRAP; @@ -2954,8 +2708,6 @@ drawregion(int x1, int y1, int x2, int y2) { int y; - xstartimagedraw(term.dirty, term.row); - for (y = y1; y < y2; y++) { if (!term.dirty[y]) continue; @@ -2963,8 +2715,6 @@ drawregion(int x1, int y1, int x2, int y2) term.dirty[y] = 0; xdrawline(TLINE(y), x1, y, x2); } - - xfinishimagedraw(); } void @@ -3000,9 +2750,3 @@ redraw(void) tfulldirt(); draw(); } - -Glyph -getglyphat(int col, int row) -{ - return term.line[row][col]; -} diff --git a/files/config/suckless/st/st.c.orig b/files/config/suckless/st/st.c.orig index 03e10d1..3370e7e 100644 --- a/files/config/suckless/st/st.c.orig +++ b/files/config/suckless/st/st.c.orig @@ -1280,9 +1280,6 @@ tsetchar(Rune u, const Glyph *attr, int x, int y) term.dirty[y] = 1; term.line[y][x] = *attr; term.line[y][x].u = u; - - if (isboxdraw(u)) - term.line[y][x].mode |= ATTR_BOXDRAW; } void diff --git a/files/config/suckless/st/st.c.rej b/files/config/suckless/st/st.c.rej deleted file mode 100644 index 81bdbd9..0000000 --- a/files/config/suckless/st/st.c.rej +++ /dev/null @@ -1,27 +0,0 @@ ---- st.c -+++ st.c -@@ -1264,9 +1313,24 @@ tsetchar(Rune u, const Glyph *attr, int x, int y) - term.line[y][x-1].mode &= ~ATTR_WIDE; - } - -+ if (u == ' ' && term.line[y][x].mode & ATTR_IMAGE && -+ tgetisclassicplaceholder(&term.line[y][x])) { -+ // This is a workaround: don't overwrite classic placement -+ // placeholders with space symbols (unlike Unicode placeholders -+ // which must be overwritten by anything). -+ term.line[y][x].bg = attr->bg; -+ term.dirty[y] = 1; -+ return; -+ } -+ - term.dirty[y] = 1; - term.line[y][x] = *attr; - term.line[y][x].u = u; -+ -+ if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD) { -+ term.line[y][x].u = 0; -+ term.line[y][x].mode |= ATTR_IMAGE; -+ } - } - - void diff --git a/files/config/suckless/st/st.h b/files/config/suckless/st/st.h index a611939..99b4e2b 100644 --- a/files/config/suckless/st/st.h +++ b/files/config/suckless/st/st.h @@ -12,7 +12,7 @@ #define DEFAULT(a, b) (a) = (a) ? (a) : (b) #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ - (a).bg != (b).bg || (a).decor != (b).decor) + (a).bg != (b).bg) #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ (t1.tv_nsec-t2.tv_nsec)/1E6) #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) @@ -20,10 +20,6 @@ #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) #define IS_TRUECOL(x) (1 << 24 & (x)) -// This decor color indicates that the fg color should be used. Note that it's -// not a 24-bit color because the 25-th bit is not set. -#define DECOR_DEFAULT_COLOR 0x0ffffff - enum glyph_attribute { ATTR_NULL = 0, ATTR_BOLD = 1 << 0, @@ -39,7 +35,6 @@ enum glyph_attribute { ATTR_WDUMMY = 1 << 10, ATTR_BOXDRAW = 1 << 11, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, - ATTR_IMAGE = 1 << 14, }; enum selection_mode { @@ -48,11 +43,6 @@ enum selection_mode { SEL_READY = 2 }; -static inline void tsetimgrow(Glyph *g, uint32_t row) { - g->u.img.row = row; -} - - enum selection_type { SEL_REGULAR = 1, SEL_RECTANGULAR = 2 @@ -63,14 +53,6 @@ enum selection_snap { SNAP_LINE = 2 }; -enum underline_style { - UNDERLINE_STRAIGHT = 1, - UNDERLINE_DOUBLE = 2, - UNDERLINE_CURLY = 3, - UNDERLINE_DOTTED = 4, - UNDERLINE_DASHED = 5, -}; - typedef unsigned char uchar; typedef unsigned int uint; typedef unsigned long ulong; @@ -84,7 +66,6 @@ typedef struct { ushort mode; /* attribute flags */ uint32_t fg; /* foreground */ uint32_t bg; /* background */ - uint32_t decor; /* decoration (like underline) */ } Glyph; typedef Glyph *Line; @@ -127,8 +108,6 @@ void selextend(int, int, int, int); int selected(int, int); char *getsel(void); -Glyph getglyphat(int, int); - size_t utf8encode(Rune, char *); void *xmalloc(size_t); diff --git a/files/config/suckless/st/st.h.orig b/files/config/suckless/st/st.h.orig index 99b4e2b..818a6f8 100644 --- a/files/config/suckless/st/st.h.orig +++ b/files/config/suckless/st/st.h.orig @@ -33,7 +33,6 @@ enum glyph_attribute { ATTR_WRAP = 1 << 8, ATTR_WIDE = 1 << 9, ATTR_WDUMMY = 1 << 10, - ATTR_BOXDRAW = 1 << 11, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, }; @@ -114,14 +113,6 @@ void *xmalloc(size_t); void *xrealloc(void *, size_t); char *xstrdup(const char *); -int isboxdraw(Rune); -ushort boxdrawindex(const Glyph *); -#ifdef XFT_VERSION -/* only exposed to x.c, otherwise we'll need Xft.h for the types */ -void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *); -void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int); -#endif - /* config.h globals */ extern char *utmp; extern char *scroll; @@ -129,12 +120,9 @@ extern char *stty_args; extern char *vtiden; extern wchar_t *worddelimiters; extern int allowaltscreen; -extern int boxdraw, boxdraw_braille, boxdraw_bold; extern int allowwindowops; extern char *termname; extern unsigned int tabspaces; extern unsigned int defaultfg; extern unsigned int defaultbg; extern unsigned int defaultcs; -extern int boxdraw, boxdraw_bold, boxdraw_braille; - diff --git a/files/config/suckless/st/st.h.rej b/files/config/suckless/st/st.h.rej deleted file mode 100644 index 0434f72..0000000 --- a/files/config/suckless/st/st.h.rej +++ /dev/null @@ -1,72 +0,0 @@ ---- st.h -+++ st.h -@@ -140,3 +156,69 @@ extern unsigned int tabspaces; - extern unsigned int defaultfg; - extern unsigned int defaultbg; - extern unsigned int defaultcs; -+ -+// Accessors to decoration properties stored in `decor`. -+// The 25-th bit is used to indicate if it's a 24-bit color. -+static inline uint32_t tgetdecorcolor(Glyph *g) { return g->decor & 0x1ffffff; } -+static inline uint32_t tgetdecorstyle(Glyph *g) { return (g->decor >> 25) & 0x7; } -+static inline void tsetdecorcolor(Glyph *g, uint32_t color) { -+ g->decor = (g->decor & ~0x1ffffff) | (color & 0x1ffffff); -+} -+static inline void tsetdecorstyle(Glyph *g, uint32_t style) { -+ g->decor = (g->decor & ~(0x7 << 25)) | ((style & 0x7) << 25); -+} -+ -+ -+// Some accessors to image placeholder properties stored in `u`: -+// - row (1-base) - 9 bits -+// - column (1-base) - 9 bits -+// - most significant byte of the image id plus 1 - 9 bits (0 means unspecified, -+// don't forget to subtract 1). -+// - the original number of diacritics (0, 1, 2, or 3) - 2 bits -+// - whether this is a classic (1) or Unicode (0) placeholder - 1 bit -+static inline uint32_t tgetimgrow(Glyph *g) { return g->u & 0x1ff; } -+static inline uint32_t tgetimgcol(Glyph *g) { return (g->u >> 9) & 0x1ff; } -+static inline uint32_t tgetimgid4thbyteplus1(Glyph *g) { return (g->u >> 18) & 0x1ff; } -+static inline uint32_t tgetimgdiacriticcount(Glyph *g) { return (g->u >> 27) & 0x3; } -+static inline uint32_t tgetisclassicplaceholder(Glyph *g) { return (g->u >> 29) & 0x1; } -+static inline void tsetimgrow(Glyph *g, uint32_t row) { -+ g->u = (g->u & ~0x1ff) | (row & 0x1ff); -+} -+static inline void tsetimgcol(Glyph *g, uint32_t col) { -+ g->u = (g->u & ~(0x1ff << 9)) | ((col & 0x1ff) << 9); -+} -+static inline void tsetimg4thbyteplus1(Glyph *g, uint32_t byteplus1) { -+ g->u = (g->u & ~(0x1ff << 18)) | ((byteplus1 & 0x1ff) << 18); -+} -+static inline void tsetimgdiacriticcount(Glyph *g, uint32_t count) { -+ g->u = (g->u & ~(0x3 << 27)) | ((count & 0x3) << 27); -+} -+static inline void tsetisclassicplaceholder(Glyph *g, uint32_t isclassic) { -+ g->u = (g->u & ~(0x1 << 29)) | ((isclassic & 0x1) << 29); -+} -+ -+/// Returns the full image id. This is a naive implementation, if the most -+/// significant byte is not specified, it's assumed to be 0 instead of inferring -+/// it from the cells to the left. -+static inline uint32_t tgetimgid(Glyph *g) { -+ uint32_t msb = tgetimgid4thbyteplus1(g); -+ if (msb != 0) -+ --msb; -+ return (msb << 24) | (g->fg & 0xFFFFFF); -+} -+ -+/// Sets the full image id. -+static inline void tsetimgid(Glyph *g, uint32_t id) { -+ g->fg = (id & 0xFFFFFF) | (1 << 24); -+ tsetimg4thbyteplus1(g, ((id >> 24) & 0xFF) + 1); -+} -+ -+static inline uint32_t tgetimgplacementid(Glyph *g) { -+ if (tgetdecorcolor(g) == DECOR_DEFAULT_COLOR) -+ return 0; -+ return g->decor & 0xFFFFFF; -+} -+ -+static inline void tsetimgplacementid(Glyph *g, uint32_t id) { -+ g->decor = (id & 0xFFFFFF) | (1 << 24); -+} diff --git a/files/config/suckless/st/st.info b/files/config/suckless/st/st.info index ded76c1..efab2cf 100644 --- a/files/config/suckless/st/st.info +++ b/files/config/suckless/st/st.info @@ -195,7 +195,6 @@ st-mono| simpleterm monocolor, Ms=\E]52;%p1%s;%p2%s\007, Se=\E[2 q, Ss=\E[%p1%d q, - Smulx=\E[4:%p1%dm, st| simpleterm, use=st-mono, @@ -216,11 +215,6 @@ st-256color| simpleterm with 256 colors, initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, -# Underline colors - Su, - Setulc=\E[58:2:%p1%{65536}%/%d:%p1%{256}%/%{255}%&%d:%p1%{255}%&%d%;m, - Setulc1=\E[58:5:%p1%dm, - ol=\E[59m, st-meta| simpleterm with meta key, use=st, diff --git a/files/config/suckless/st/st.o b/files/config/suckless/st/st.o Binary files differnew file mode 100644 index 0000000..0f75b8d --- /dev/null +++ b/files/config/suckless/st/st.o diff --git a/files/config/suckless/st/win.h b/files/config/suckless/st/win.h index 31b3fff..6de960d 100644 --- a/files/config/suckless/st/win.h +++ b/files/config/suckless/st/win.h @@ -39,6 +39,3 @@ void xsetpointermotion(int); void xsetsel(char *); int xstartdraw(void); void xximspot(int, int); - -void xstartimagedraw(int *dirty, int rows); -void xfinishimagedraw(); diff --git a/files/config/suckless/st/x.c b/files/config/suckless/st/x.c index c2c890d..7157e56 100644 --- a/files/config/suckless/st/x.c +++ b/files/config/suckless/st/x.c @@ -4,8 +4,6 @@ #include <limits.h> #include <locale.h> #include <signal.h> -#include <stdio.h> -#include <stdlib.h> #include <sys/select.h> #include <time.h> #include <unistd.h> @@ -21,31 +19,30 @@ char *argv0; #include "arg.h" #include "st.h" #include "win.h" -#include "graphics.h" /* types used in config.h */ typedef struct { - uint mod; - KeySym keysym; - void (*func)(const Arg *); - const Arg arg; + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; } Shortcut; typedef struct { - uint mod; - uint button; - void (*func)(const Arg *); - const Arg arg; - uint release; + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; } MouseShortcut; typedef struct { - KeySym k; - uint mask; - char *s; - /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ - signed char appkey; /* application keypad */ - signed char appcursor; /* application cursor */ + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ } Key; /* X modifiers */ @@ -62,12 +59,6 @@ static void zoom(const Arg *); static void zoomabs(const Arg *); static void zoomreset(const Arg *); static void ttysend(const Arg *); -static void previewimage(const Arg *); -static void showimageinfo(const Arg *); -static void togglegrdebug(const Arg *); -static void dumpgrstate(const Arg *); -static void unloadimages(const Arg *); -static void toggleimages(const Arg *); /* config.h for applying patches and the configuration. */ #include "config.h" @@ -88,75 +79,71 @@ typedef XftGlyphFontSpec GlyphFontSpec; /* Purely graphic info */ typedef struct { - int tw, th; /* tty width and height */ - int w, h; /* window width and height */ - int hborderpx, vborderpx; - int ch; /* char height */ - int cw; /* char width */ - int mode; /* window state/mode flags */ - int cursor; /* cursor style */ + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ } TermWindow; typedef struct { - Display *dpy; - Colormap cmap; - Window win; - Drawable buf; - GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ - Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; - struct { - XIM xim; - XIC xic; - XPoint spot; - XVaNestedList spotlist; - } ime; - Draw draw; - Visual *vis; - XSetWindowAttributes attrs; - int scr; - int isfixed; /* is fixed geometry? */ - int l, t; /* left and top offset */ - int gm; /* geometry mask */ + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ } XWindow; typedef struct { - Atom xtarget; - char *primary, *clipboard; - struct timespec tclick1; - struct timespec tclick2; + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; } XSelection; /* Font structure */ #define Font Font_ typedef struct { - int height; - int width; - int ascent; - int descent; - int badslant; - int badweight; - short lbearing; - short rbearing; - XftFont *match; - FcFontSet *set; - FcPattern *pattern; + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; } Font; /* Drawing Context */ typedef struct { - Color *col; - size_t collen; - Font font, bfont, ifont, ibfont; - GC gc; + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; } DC; static inline ushort sixd_to_16bit(int); -static inline void tsetimgrow(Glyph *g, uint32_t row) { static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); static void xdrawglyph(Glyph, int, int); -static void xdrawimages(Glyph, Line, int x1, int y1, int x2); -static void xdrawoneimagecell(Glyph, int x, int y); static void xclear(int, int, int, int); static int xgeommasktogravity(int); static int ximopen(Display *); @@ -203,29 +190,29 @@ static void run(void); static void usage(void); static void (*handler[LASTEvent])(XEvent *) = { - [KeyPress] = kpress, - [ClientMessage] = cmessage, - [ConfigureNotify] = resize, - [VisibilityNotify] = visibility, - [UnmapNotify] = unmap, - [Expose] = expose, - [FocusIn] = focus, - [FocusOut] = focus, - [MotionNotify] = bmotion, - [ButtonPress] = bpress, - [ButtonRelease] = brelease, - /* - * Uncomment if you want the selection to disappear when you select something - * different in another window. - */ - /* [SelectionClear] = selclear_, */ - [SelectionNotify] = selnotify, - /* - * PropertyNotify is only turned on when there is some INCR transfer happening - * for the selection retrieval. - */ - [PropertyNotify] = propnotify, - [SelectionRequest] = selrequest, + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, }; /* Globals */ @@ -233,20 +220,19 @@ static DC dc; static XWindow xw; static XSelection xsel; static TermWindow win; -static unsigned int mouse_col = 0, mouse_row = 0; /* Font Ring Cache */ enum { - FRC_NORMAL, - FRC_ITALIC, - FRC_BOLD, - FRC_ITALICBOLD + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD }; typedef struct { - XftFont *font; - int flags; - Rune unicodep; + XftFont *font; + int flags; + Rune unicodep; } Fontcache; /* Fontcache is an array now. A new font will be appended to the array. */ @@ -268,2190 +254,1866 @@ static char *opt_title = NULL; static uint buttons; /* bit field of pressed buttons */ - void +void clipcopy(const Arg *dummy) { - Atom clipboard; + Atom clipboard; - free(xsel.clipboard); - xsel.clipboard = NULL; + free(xsel.clipboard); + xsel.clipboard = NULL; - if (xsel.primary != NULL) { - xsel.clipboard = xstrdup(xsel.primary); - clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); - } + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } } - void +void clippaste(const Arg *dummy) { - Atom clipboard; + Atom clipboard; - clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, - xw.win, CurrentTime); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); } - void +void selpaste(const Arg *dummy) { - XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, - xw.win, CurrentTime); + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); } - void +void numlock(const Arg *dummy) { - win.mode ^= MODE_NUMLOCK; + win.mode ^= MODE_NUMLOCK; } - void +void zoom(const Arg *arg) { - Arg larg; + Arg larg; - larg.f = usedfontsize + arg->f; - zoomabs(&larg); + larg.f = usedfontsize + arg->f; + zoomabs(&larg); } - void +void zoomabs(const Arg *arg) { - xunloadfonts(); - xloadfonts(usedfont, arg->f); - cresize(0, 0); - redraw(); - xhints(); + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); } - void +void zoomreset(const Arg *arg) { - Arg larg; + Arg larg; - if (defaultfontsize > 0) { - larg.f = defaultfontsize; - zoomabs(&larg); - } + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } } - void +void ttysend(const Arg *arg) { - ttywrite(arg->s, strlen(arg->s), 1); -} - - void -previewimage(const Arg *arg) -{ - Glyph g = getglyphat(mouse_col, mouse_row); - if (g.mode & ATTR_IMAGE) { - uint32_t image_id = tgetimgid(&g); - fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n", - image_id, tgetimgplacementid(&g), tgetimgcol(&g), - tgetimgrow(&g)); - gr_preview_image(image_id, arg->s); - } -} - - void -showimageinfo(const Arg *arg) -{ - Glyph g = getglyphat(mouse_col, mouse_row); - if (g.mode & ATTR_IMAGE) { - uint32_t image_id = tgetimgid(&g); - fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n", - image_id, tgetimgplacementid(&g), tgetimgcol(&g), - tgetimgrow(&g)); - char stcommand[256] = {0}; - size_t len = snprintf(stcommand, sizeof(stcommand), "%s -e less", argv0); - if (len > sizeof(stcommand) - 1) { - fprintf(stderr, "Executable name too long: %s\n", - argv0); - return; - } - gr_show_image_info(image_id, tgetimgplacementid(&g), - tgetimgcol(&g), tgetimgrow(&g), - tgetisclassicplaceholder(&g), - tgetimgdiacriticcount(&g), argv0); - } -} - - void -togglegrdebug(const Arg *arg) -{ - graphics_debug_mode = (graphics_debug_mode + 1) % 3; - redraw(); -} - - void -dumpgrstate(const Arg *arg) -{ - gr_dump_state(); -} - - void -unloadimages(const Arg *arg) -{ - gr_unload_images_to_reduce_ram(); + ttywrite(arg->s, strlen(arg->s), 1); } - void -toggleimages(const Arg *arg) -{ - graphics_display_images = !graphics_display_images; - redraw(); -} - - int +int evcol(XEvent *e) { - int x = e->xbutton.x - win.hborderpx; - LIMIT(x, 0, win.tw - 1); - return x / win.cw; + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; } - int +int evrow(XEvent *e) { - int y = e->xbutton.y - win.vborderpx; - LIMIT(y, 0, win.th - 1); - return y / win.ch; + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; } - void +void mousesel(XEvent *e, int done) { - int type, seltype = SEL_REGULAR; - uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); - - for (type = 1; type < LEN(selmasks); ++type) { - if (match(selmasks[type], state)) { - seltype = type; - break; - } - } - selextend(evcol(e), evrow(e), seltype, done); - if (done) - setsel(getsel(), e->xbutton.time); + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); } - void +void mousereport(XEvent *e) { - int len, btn, code; - int x = evcol(e), y = evrow(e); - int state = e->xbutton.state; - char buf[40]; - static int ox, oy; - - if (e->type == MotionNotify) { - if (x == ox && y == oy) - return; - if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) - return; - /* MODE_MOUSEMOTION: no reporting if no button is pressed */ - if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) - return; - /* Set btn to lowest-numbered pressed button, or 12 if no - * buttons are pressed. */ - for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) - ; - code = 32; - } else { - btn = e->xbutton.button; - /* Only buttons 1 through 11 can be encoded */ - if (btn < 1 || btn > 11) - return; - if (e->type == ButtonRelease) { - /* MODE_MOUSEX10: no button release reporting */ - if (IS_SET(MODE_MOUSEX10)) - return; - /* Don't send release events for the scroll wheel */ - if (btn == 4 || btn == 5) - return; - } - code = 0; - } - - ox = x; - oy = y; - - /* Encode btn into code. If no button is pressed for a motion event in - * MODE_MOUSEMANY, then encode it as a release. */ - if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) - code += 3; - else if (btn >= 8) - code += 128 + btn - 8; - else if (btn >= 4) - code += 64 + btn - 4; - else - code += btn - 1; - - if (!IS_SET(MODE_MOUSEX10)) { - code += ((state & ShiftMask ) ? 4 : 0) - + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ - + ((state & ControlMask) ? 16 : 0); - } - - if (IS_SET(MODE_MOUSESGR)) { - len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", - code, x+1, y+1, - e->type == ButtonRelease ? 'm' : 'M'); - } else if (x < 223 && y < 223) { - len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", - 32+code, 32+x+1, 32+y+1); - } else { - return; - } - - ttywrite(buf, len, 0); -} - - uint + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; + } else { + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) + return; + } + code = 0; + } + + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + + if (!IS_SET(MODE_MOUSEX10)) { + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + code, x+1, y+1, + e->type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint buttonmask(uint button) { - return button == Button1 ? Button1Mask - : button == Button2 ? Button2Mask - : button == Button3 ? Button3Mask - : button == Button4 ? Button4Mask - : button == Button5 ? Button5Mask - : 0; + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; } - int +int mouseaction(XEvent *e, uint release) { - MouseShortcut *ms; - - /* ignore Button<N>mask for Button<N> - it's set on release */ - uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); - - mouse_col = evcol(e); - mouse_row = evrow(e); - - for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { - if (ms->release == release && - ms->button == e->xbutton.button && - (match(ms->mod, state) || /* exact or forced */ - match(ms->mod, state & ~forcemousemod))) { - ms->func(&(ms->arg)); - return 1; - } - } - - return 0; + MouseShortcut *ms; + + /* ignore Button<N>mask for Button<N> - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; } - void +void bpress(XEvent *e) { - int btn = e->xbutton.button; - struct timespec now; - int snap; - - if (1 <= btn && btn <= 11) - buttons |= 1 << (btn-1); - - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { - mousereport(e); - return; - } - - if (mouseaction(e, 0)) - return; - - if (btn == Button1) { - /* - * If the user clicks below predefined timeouts specific - * snapping behaviour is exposed. - */ - clock_gettime(CLOCK_MONOTONIC, &now); - if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { - snap = SNAP_LINE; - } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { - snap = SNAP_WORD; - } else { - snap = 0; - } - xsel.tclick2 = xsel.tclick1; - xsel.tclick1 = now; - - selstart(evcol(e), evrow(e), snap); - } -} - - void + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void propnotify(XEvent *e) { - XPropertyEvent *xpev; - Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - - xpev = &e->xproperty; - if (xpev->state == PropertyNewValue && - (xpev->atom == XA_PRIMARY || - xpev->atom == clipboard)) { - selnotify(e); - } + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } } - void +void selnotify(XEvent *e) { - ulong nitems, ofs, rem; - int format; - uchar *data, *last, *repl; - Atom type, incratom, property = None; - - incratom = XInternAtom(xw.dpy, "INCR", 0); - - ofs = 0; - if (e->type == SelectionNotify) - property = e->xselection.property; - else if (e->type == PropertyNotify) - property = e->xproperty.atom; - - if (property == None) - return; - - do { - if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, - BUFSIZ/4, False, AnyPropertyType, - &type, &format, &nitems, &rem, - &data)) { - fprintf(stderr, "Clipboard allocation failed\n"); - return; - } - - if (e->type == PropertyNotify && nitems == 0 && rem == 0) { - /* - * If there is some PropertyNotify with no data, then - * this is the signal of the selection owner that all - * data has been transferred. We won't need to receive - * PropertyNotify events anymore. - */ - MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); - XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, - &xw.attrs); - } - - if (type == incratom) { - /* - * Activate the PropertyNotify events so we receive - * when the selection owner does send us the next - * chunk of data. - */ - MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); - XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, - &xw.attrs); - - /* - * Deleting the property is the transfer start signal. - */ - XDeleteProperty(xw.dpy, xw.win, (int)property); - continue; - } - - /* - * As seen in getsel: - * Line endings are inconsistent in the terminal and GUI world - * copy and pasting. When receiving some selection data, - * replace all '\n' with '\r'. - * FIXME: Fix the computer world. - */ - repl = data; - last = data + nitems * format / 8; - while ((repl = memchr(repl, '\n', last - repl))) { - *repl++ = '\r'; - } - - if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) - ttywrite("\033[200~", 6, 0); - ttywrite((char *)data, nitems * format / 8, 1); - if (IS_SET(MODE_BRCKTPASTE) && rem == 0) - ttywrite("\033[201~", 6, 0); - XFree(data); - /* number of 32-bit chunks returned */ - ofs += nitems * format / 32; - } while (rem > 0); - - /* - * Deleting the property again tells the selection owner to send the - * next data chunk in the property. - */ - XDeleteProperty(xw.dpy, xw.win, (int)property); -} - - void + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void xclipcopy(void) { - clipcopy(NULL); + clipcopy(NULL); } - void +void selclear_(XEvent *e) { - selclear(); + selclear(); } - void +void selrequest(XEvent *e) { - XSelectionRequestEvent *xsre; - XSelectionEvent xev; - Atom xa_targets, string, clipboard; - char *seltext; - - xsre = (XSelectionRequestEvent *) e; - xev.type = SelectionNotify; - xev.requestor = xsre->requestor; - xev.selection = xsre->selection; - xev.target = xsre->target; - xev.time = xsre->time; - if (xsre->property == None) - xsre->property = xsre->target; - - /* reject */ - xev.property = None; - - xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); - if (xsre->target == xa_targets) { - /* respond with the supported type */ - string = xsel.xtarget; - XChangeProperty(xsre->display, xsre->requestor, xsre->property, - XA_ATOM, 32, PropModeReplace, - (uchar *) &string, 1); - xev.property = xsre->property; - } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { - /* - * xith XA_STRING non ascii characters may be incorrect in the - * requestor. It is not our problem, use utf8. - */ - clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - if (xsre->selection == XA_PRIMARY) { - seltext = xsel.primary; - } else if (xsre->selection == clipboard) { - seltext = xsel.clipboard; - } else { - fprintf(stderr, - "Unhandled clipboard selection 0x%lx\n", - xsre->selection); - return; - } - if (seltext != NULL) { - XChangeProperty(xsre->display, xsre->requestor, - xsre->property, xsre->target, - 8, PropModeReplace, - (uchar *)seltext, strlen(seltext)); - xev.property = xsre->property; - } - } - - /* all done, send a notification to the listener */ - if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) - fprintf(stderr, "Error sending SelectionNotify event\n"); -} - - void + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void setsel(char *str, Time t) { - if (!str) - return; + if (!str) + return; - free(xsel.primary); - xsel.primary = str; + free(xsel.primary); + xsel.primary = str; - XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); - if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) - selclear(); + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); } - void +void xsetsel(char *str) { - setsel(str, CurrentTime); + setsel(str, CurrentTime); } - void +void brelease(XEvent *e) { - int btn = e->xbutton.button; + int btn = e->xbutton.button; - if (1 <= btn && btn <= 11) - buttons &= ~(1 << (btn-1)); + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { - mousereport(e); - return; - } + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } - if (mouseaction(e, 1)) - return; - if (btn == Button1) - mousesel(e, 1); + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); } - void +void bmotion(XEvent *e) { - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { - mousereport(e); - return; - } + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } - mousesel(e, 0); + mousesel(e, 0); } - void +void cresize(int width, int height) { - int col, row; + int col, row; - if (width != 0) - win.w = width; - if (height != 0) - win.h = height; + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; - col = (win.w - 2 * borderpx) / win.cw; - row = (win.h - 2 * borderpx) / win.ch; - col = MAX(1, col); - row = MAX(1, row); + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); - win.hborderpx = (win.w - col * win.cw) * anysize_halign / 100; - win.vborderpx = (win.h - row * win.ch) * anysize_valign / 100; - - tresize(col, row); - xresize(col, row); - ttyresize(win.tw, win.th); + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); } - void +void xresize(int col, int row) { - win.tw = col * win.cw; - win.th = row * win.ch; + win.tw = col * win.cw; + win.th = row * win.ch; - XFreePixmap(xw.dpy, xw.buf); - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - DefaultDepth(xw.dpy, xw.scr)); - XftDrawChange(xw.draw, xw.buf); - xclear(0, 0, win.w, win.h); + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); - /* resize to new width */ - xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); } - ushort +ushort sixd_to_16bit(int x) { - return x == 0 ? 0 : 0x3737 + 0x2828 * x; + return x == 0 ? 0 : 0x3737 + 0x2828 * x; } - int +int xloadcolor(int i, const char *name, Color *ncolor) { - XRenderColor color = { .alpha = 0xffff }; - - if (!name) { - if (BETWEEN(i, 16, 255)) { /* 256 color */ - if (i < 6*6*6+16) { /* same colors as xterm */ - color.red = sixd_to_16bit( ((i-16)/36)%6 ); - color.green = sixd_to_16bit( ((i-16)/6) %6 ); - color.blue = sixd_to_16bit( ((i-16)/1) %6 ); - } else { /* greyscale */ - color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); - color.green = color.blue = color.red; - } - return XftColorAllocValue(xw.dpy, xw.vis, - xw.cmap, &color, ncolor); - } else - name = colorname[i]; - } - - return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); } - void +void xloadcols(void) { - int i; - static int loaded; - Color *cp; - - if (loaded) { - for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) - XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); - } else { - dc.collen = MAX(LEN(colorname), 256); - dc.col = xmalloc(dc.collen * sizeof(Color)); - } - - for (i = 0; i < dc.collen; i++) - if (!xloadcolor(i, NULL, &dc.col[i])) { - if (colorname[i]) - die("could not allocate color '%s'\n", colorname[i]); - else - die("could not allocate color %d\n", i); - } - loaded = 1; -} - - int + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + loaded = 1; +} + +int xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) { - if (!BETWEEN(x, 0, dc.collen - 1)) - return 1; + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; - *r = dc.col[x].color.red >> 8; - *g = dc.col[x].color.green >> 8; - *b = dc.col[x].color.blue >> 8; + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; - return 0; + return 0; } - int +int xsetcolorname(int x, const char *name) { - Color ncolor; + Color ncolor; - if (!BETWEEN(x, 0, dc.collen - 1)) - return 1; + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; - if (!xloadcolor(x, name, &ncolor)) - return 1; + if (!xloadcolor(x, name, &ncolor)) + return 1; - XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); - dc.col[x] = ncolor; + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; - return 0; + return 0; } /* * Absolute coordinates. */ - void +void xclear(int x1, int y1, int x2, int y2) { - XftDrawRect(xw.draw, - &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], - x1, y1, x2-x1, y2-y1); + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); } - void +void xhints(void) { - XClassHint class = {opt_name ? opt_name : termname, - opt_class ? opt_class : termname}; - XWMHints wm = {.flags = InputHint, .input = 1}; - XSizeHints *sizeh; - - sizeh = XAllocSizeHints(); - - sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; - sizeh->height = win.h; - sizeh->width = win.w; - sizeh->height_inc = 1; - sizeh->width_inc = 1; - sizeh->base_height = 2 * borderpx; - sizeh->base_width = 2 * borderpx; - sizeh->min_height = win.ch + 2 * borderpx; - sizeh->min_width = win.cw + 2 * borderpx; - if (xw.isfixed) { - sizeh->flags |= PMaxSize; - sizeh->min_width = sizeh->max_width = win.w; - sizeh->min_height = sizeh->max_height = win.h; - } - if (xw.gm & (XValue|YValue)) { - sizeh->flags |= USPosition | PWinGravity; - sizeh->x = xw.l; - sizeh->y = xw.t; - sizeh->win_gravity = xgeommasktogravity(xw.gm); - } - - XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, - &class); - XFree(sizeh); -} - - int + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int xgeommasktogravity(int mask) { - switch (mask & (XNegative|YNegative)) { - case 0: - return NorthWestGravity; - case XNegative: - return NorthEastGravity; - case YNegative: - return SouthWestGravity; - } - - return SouthEastGravity; + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; } - int +int xloadfont(Font *f, FcPattern *pattern) { - FcPattern *configured; - FcPattern *match; - FcResult result; - XGlyphInfo extents; - int wantattr, haveattr; - - /* - * Manually configure instead of calling XftMatchFont - * so that we can use the configured pattern for - * "missing glyph" lookups. - */ - configured = FcPatternDuplicate(pattern); - if (!configured) - return 1; - - FcConfigSubstitute(NULL, configured, FcMatchPattern); - XftDefaultSubstitute(xw.dpy, xw.scr, configured); - - match = FcFontMatch(NULL, configured, &result); - if (!match) { - FcPatternDestroy(configured); - return 1; - } - - if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { - FcPatternDestroy(configured); - FcPatternDestroy(match); - return 1; - } - - if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == - XftResultMatch)) { - /* - * Check if xft was unable to find a font with the appropriate - * slant but gave us one anyway. Try to mitigate. - */ - if ((XftPatternGetInteger(f->match->pattern, "slant", 0, - &haveattr) != XftResultMatch) || haveattr < wantattr) { - f->badslant = 1; - fputs("font slant does not match\n", stderr); - } - } - - if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == - XftResultMatch)) { - if ((XftPatternGetInteger(f->match->pattern, "weight", 0, - &haveattr) != XftResultMatch) || haveattr != wantattr) { - f->badweight = 1; - fputs("font weight does not match\n", stderr); - } - } - - XftTextExtentsUtf8(xw.dpy, f->match, - (const FcChar8 *) ascii_printable, - strlen(ascii_printable), &extents); - - f->set = NULL; - f->pattern = configured; - - f->ascent = f->match->ascent; - f->descent = f->match->descent; - f->lbearing = 0; - f->rbearing = f->match->max_advance_width; - - f->height = f->ascent + f->descent; - f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); - - return 0; -} - - void + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void xloadfonts(const char *fontstr, double fontsize) { - FcPattern *pattern; - double fontval; - - if (fontstr[0] == '-') - pattern = XftXlfdParse(fontstr, False, False); - else - pattern = FcNameParse((const FcChar8 *)fontstr); - - if (!pattern) - die("can't open font %s\n", fontstr); - - if (fontsize > 1) { - FcPatternDel(pattern, FC_PIXEL_SIZE); - FcPatternDel(pattern, FC_SIZE); - FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); - usedfontsize = fontsize; - } else { - if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == - FcResultMatch) { - usedfontsize = fontval; - } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == - FcResultMatch) { - usedfontsize = -1; - } else { - /* - * Default font size is 12, if none given. This is to - * have a known usedfontsize value. - */ - FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); - usedfontsize = 12; - } - if (defaultfontsize <= 0) - defaultfontsize = usedfontsize; - } - - if (xloadfont(&dc.font, pattern)) - die("can't open font %s\n", fontstr); - - if (usedfontsize < 0) { - FcPatternGetDouble(dc.font.match->pattern, - FC_PIXEL_SIZE, 0, &fontval); - usedfontsize = fontval; - if (defaultfontsize <= 0 && fontsize == 0) - defaultfontsize = fontval; - } - - /* Setting character width and height. */ - win.cw = ceilf(dc.font.width * cwscale); - win.ch = ceilf(dc.font.height * chscale); - - FcPatternDel(pattern, FC_SLANT); - FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); - if (xloadfont(&dc.ifont, pattern)) - die("can't open font %s\n", fontstr); - - FcPatternDel(pattern, FC_WEIGHT); - FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); - if (xloadfont(&dc.ibfont, pattern)) - die("can't open font %s\n", fontstr); - - FcPatternDel(pattern, FC_SLANT); - FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); - if (xloadfont(&dc.bfont, pattern)) - die("can't open font %s\n", fontstr); - - FcPatternDestroy(pattern); -} - - void + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void xunloadfont(Font *f) { - XftFontClose(xw.dpy, f->match); - FcPatternDestroy(f->pattern); - if (f->set) - FcFontSetDestroy(f->set); + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); } - void +void xunloadfonts(void) { - /* Free the loaded fonts in the font cache. */ - while (frclen > 0) - XftFontClose(xw.dpy, frc[--frclen].font); - - xunloadfont(&dc.font); - xunloadfont(&dc.bfont); - xunloadfont(&dc.ifont); - xunloadfont(&dc.ibfont); + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); } - int +int ximopen(Display *dpy) { - XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; - XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; - - xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); - if (xw.ime.xim == NULL) - return 0; - - if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) - fprintf(stderr, "XSetIMValues: " - "Could not set XNDestroyCallback.\n"); - - xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, - NULL); - - if (xw.ime.xic == NULL) { - xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, - XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, xw.win, - XNDestroyCallback, &icdestroy, - NULL); - } - if (xw.ime.xic == NULL) - fprintf(stderr, "XCreateIC: Could not create input context.\n"); - - return 1; + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; } - void +void ximinstantiate(Display *dpy, XPointer client, XPointer call) { - if (ximopen(dpy)) - XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, - ximinstantiate, NULL); + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); } - void +void ximdestroy(XIM xim, XPointer client, XPointer call) { - xw.ime.xim = NULL; - XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, - ximinstantiate, NULL); - XFree(xw.ime.spotlist); + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); } - int +int xicdestroy(XIC xim, XPointer client, XPointer call) { - xw.ime.xic = NULL; - return 1; + xw.ime.xic = NULL; + return 1; } - void +void xinit(int cols, int rows) { - XGCValues gcvalues; - Cursor cursor; - Window parent, root; - pid_t thispid = getpid(); - XColor xmousefg, xmousebg; - - if (!(xw.dpy = XOpenDisplay(NULL))) - die("can't open display\n"); - xw.scr = XDefaultScreen(xw.dpy); - xw.vis = XDefaultVisual(xw.dpy, xw.scr); - - /* font */ - if (!FcInit()) - die("could not init fontconfig.\n"); - - usedfont = (opt_font == NULL)? font : opt_font; - xloadfonts(usedfont, 0); - - /* colors */ - xw.cmap = XDefaultColormap(xw.dpy, xw.scr); - xloadcols(); - - /* adjust fixed window geometry */ - win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw; - win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch; - if (xw.gm & XNegative) - xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; - if (xw.gm & YNegative) - xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; - - /* Events */ - xw.attrs.background_pixel = dc.col[defaultbg].pixel; - xw.attrs.border_pixel = dc.col[defaultbg].pixel; - xw.attrs.bit_gravity = NorthWestGravity; - xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask - | ExposureMask | VisibilityChangeMask | StructureNotifyMask - | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; - xw.attrs.colormap = xw.cmap; - - root = XRootWindow(xw.dpy, xw.scr); - if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) - parent = root; - xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, - win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, - xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity - | CWEventMask | CWColormap, &xw.attrs); - if (parent != root) - XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); - - memset(&gcvalues, 0, sizeof(gcvalues)); - gcvalues.graphics_exposures = False; - dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, - &gcvalues); - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - DefaultDepth(xw.dpy, xw.scr)); - XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); - XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); - - /* font spec buffer */ - xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); - - /* Xft rendering context */ - xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); - - /* input methods */ - if (!ximopen(xw.dpy)) { - XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, - ximinstantiate, NULL); - } - - /* white cursor, black outline */ - cursor = XCreateFontCursor(xw.dpy, mouseshape); - XDefineCursor(xw.dpy, xw.win, cursor); - - if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { - xmousefg.red = 0xffff; - xmousefg.green = 0xffff; - xmousefg.blue = 0xffff; - } - - if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { - xmousebg.red = 0x0000; - xmousebg.green = 0x0000; - xmousebg.blue = 0x0000; - } - - XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); - - xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); - xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); - xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); - xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); - XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); - - xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); - XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, - PropModeReplace, (uchar *)&thispid, 1); - - win.mode = MODE_NUMLOCK; - resettitle(); - xhints(); - XMapWindow(xw.dpy, xw.win); - XSync(xw.dpy, False); - - clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); - clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); - xsel.primary = NULL; - xsel.clipboard = NULL; - xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); - if (xsel.xtarget == None) - xsel.xtarget = XA_STRING; - - boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); -} - - int + XGCValues gcvalues; + Cursor cursor; + Window parent, root; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + if (parent != root) + XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; + + boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); +} + +int xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) { - float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; - float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp; - ushort mode, prevmode = USHRT_MAX; - Font *font = &dc.font; - int frcflags = FRC_NORMAL; - float runewidth = win.cw; - Rune rune; - FT_UInt glyphidx; - FcResult fcres; - FcPattern *fcpattern, *fontpattern; - FcFontSet *fcsets[] = { NULL }; - FcCharSet *fccharset; - int i, f, numspecs = 0; - - for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { - /* Fetch rune and mode for current glyph. */ - rune = glyphs[i].u; - mode = glyphs[i].mode; - - /* Skip dummy wide-character spacing. */ - if (mode == ATTR_WDUMMY) - continue; - - /* Draw spaces for image placeholders (images will be drawn - * separately). */ - if (mode & ATTR_IMAGE) - rune = ' '; - - /* Determine font for glyph if different from previous glyph. */ - if (prevmode != mode) { - prevmode = mode; - font = &dc.font; - frcflags = FRC_NORMAL; - runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); - if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { - font = &dc.ibfont; - frcflags = FRC_ITALICBOLD; - } else if (mode & ATTR_ITALIC) { - font = &dc.ifont; - frcflags = FRC_ITALIC; - } else if (mode & ATTR_BOLD) { - font = &dc.bfont; - frcflags = FRC_BOLD; - } - yp = winy + font->ascent; - } - - if (mode & ATTR_BOXDRAW) { - /* minor shoehorning: boxdraw uses only this ushort */ - glyphidx = boxdrawindex(&glyphs[i]); - } else { - /* Lookup character index with default font. */ - glyphidx = XftCharIndex(xw.dpy, font->match, rune); - } - if (glyphidx) { - specs[numspecs].font = font->match; - specs[numspecs].glyph = glyphidx; - specs[numspecs].x = (short)xp; - specs[numspecs].y = (short)yp; - xp += runewidth; - numspecs++; - continue; - } - - /* Fallback on font cache, search the font cache for match. */ - for (f = 0; f < frclen; f++) { - glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); - /* Everything correct. */ - if (glyphidx && frc[f].flags == frcflags) - break; - /* We got a default font for a not found glyph. */ - if (!glyphidx && frc[f].flags == frcflags - && frc[f].unicodep == rune) { - break; - } - } - - /* Nothing was found. Use fontconfig to find matching font. */ - if (f >= frclen) { - if (!font->set) - font->set = FcFontSort(0, font->pattern, - 1, 0, &fcres); - fcsets[0] = font->set; - - /* - * Nothing was found in the cache. Now use - * some dozen of Fontconfig calls to get the - * font for one single character. - * - * Xft and fontconfig are design failures. - */ - fcpattern = FcPatternDuplicate(font->pattern); - fccharset = FcCharSetCreate(); - - FcCharSetAddChar(fccharset, rune); - FcPatternAddCharSet(fcpattern, FC_CHARSET, - fccharset); - FcPatternAddBool(fcpattern, FC_SCALABLE, 1); - - FcConfigSubstitute(0, fcpattern, - FcMatchPattern); - FcDefaultSubstitute(fcpattern); - - fontpattern = FcFontSetMatch(0, fcsets, 1, - fcpattern, &fcres); - - /* Allocate memory for the new cache entry. */ - if (frclen >= frccap) { - frccap += 16; - frc = xrealloc(frc, frccap * sizeof(Fontcache)); - } - - frc[frclen].font = XftFontOpenPattern(xw.dpy, - fontpattern); - if (!frc[frclen].font) - die("XftFontOpenPattern failed seeking fallback font: %s\n", - strerror(errno)); - frc[frclen].flags = frcflags; - frc[frclen].unicodep = rune; - - glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); - - f = frclen; - frclen++; - - FcPatternDestroy(fcpattern); - FcCharSetDestroy(fccharset); - } - - specs[numspecs].font = frc[f].font; - specs[numspecs].glyph = glyphidx; - specs[numspecs].x = (short)xp; - specs[numspecs].y = (short)yp; - xp += runewidth; - numspecs++; - } - - return numspecs; -} - -/* Draws a horizontal dashed line of length `w` starting at `(x, y)`. `wavelen` - * is the length of the dash plus the length of the gap. `fraction` is the - * fraction of the dash length compared to `wavelen`. */ - static void -xdrawunderdashed(Draw draw, Color *color, int x, int y, int w, - int wavelen, float fraction, int thick) -{ - int dashw = MAX(1, fraction * wavelen); - for (int i = x - x % wavelen; i < x + w; i += wavelen) { - int startx = MAX(i, x); - int endx = MIN(i + dashw, x + w); - if (startx < endx) - XftDrawRect(xw.draw, color, startx, y, endx - startx, - thick); - } -} - -/* Draws an undercurl. `h` is the total height, including line thickness. */ - static void -xdrawundercurl(Draw draw, Color *color, int x, int y, int w, int h, int thick) -{ - XGCValues gcvals = {.foreground = color->pixel, - .line_width = thick, - .line_style = LineSolid, - .cap_style = CapRound}; - GC gc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw), - GCForeground | GCLineWidth | GCLineStyle | GCCapStyle, - &gcvals); - - XRectangle clip = {.x = x, .y = y, .width = w, .height = h}; - XSetClipRectangles(xw.dpy, gc, 0, 0, &clip, 1, Unsorted); - - int yoffset = thick / 2; - int segh = MAX(1, h - thick); - /* Make sure every segment is at a 45 degree angle, otherwise it doesn't - * look good without antialiasing. */ - int segw = segh; - int wavelen = MAX(1, segw * 2); - - for (int i = x - (x % wavelen); i < x + w; i += wavelen) { - XPoint points[3] = {{.x = i, .y = y + yoffset}, - {.x = i + segw, .y = y + yoffset + segh}, - {.x = i + wavelen, .y = y + yoffset}}; - XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), gc, points, 3, - CoordModeOrigin); - } - - XFreeGC(xw.dpy, gc); -} - - void + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + if (mode & ATTR_BOXDRAW) { + /* minor shoehorning: boxdraw uses only this ushort */ + glyphidx = boxdrawindex(&glyphs[i]); + } else { + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + } + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) { - int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); - int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, - width = charlen * win.cw; - Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; - XRenderColor colfg, colbg; - XRectangle r; - - /* Fallback on color display for attributes not supported by the font */ - if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { - if (dc.ibfont.badslant || dc.ibfont.badweight) - base.fg = defaultattr; - } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || - (base.mode & ATTR_BOLD && dc.bfont.badweight)) { - base.fg = defaultattr; - } - - if (IS_TRUECOL(base.fg)) { - colfg.alpha = 0xffff; - colfg.red = TRUERED(base.fg); - colfg.green = TRUEGREEN(base.fg); - colfg.blue = TRUEBLUE(base.fg); - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); - fg = &truefg; - } else { - fg = &dc.col[base.fg]; - } - - if (IS_TRUECOL(base.bg)) { - colbg.alpha = 0xffff; - colbg.green = TRUEGREEN(base.bg); - colbg.red = TRUERED(base.bg); - colbg.blue = TRUEBLUE(base.bg); - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); - bg = &truebg; - } else { - bg = &dc.col[base.bg]; - } - - /* Change basic system colors [0-7] to bright system colors [8-15] */ - if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) - fg = &dc.col[base.fg + 8]; - - if (IS_SET(MODE_REVERSE)) { - if (fg == &dc.col[defaultfg]) { - fg = &dc.col[defaultbg]; - } else { - colfg.red = ~fg->color.red; - colfg.green = ~fg->color.green; - colfg.blue = ~fg->color.blue; - colfg.alpha = fg->color.alpha; - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, - &revfg); - fg = &revfg; - } - - if (bg == &dc.col[defaultbg]) { - bg = &dc.col[defaultfg]; - } else { - colbg.red = ~bg->color.red; - colbg.green = ~bg->color.green; - colbg.blue = ~bg->color.blue; - colbg.alpha = bg->color.alpha; - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, - &revbg); - bg = &revbg; - } - } - - if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { - colfg.red = fg->color.red / 2; - colfg.green = fg->color.green / 2; - colfg.blue = fg->color.blue / 2; - colfg.alpha = fg->color.alpha; - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); - fg = &revfg; - } - - if (base.mode & ATTR_REVERSE) { - temp = fg; - fg = bg; - bg = temp; - } - - if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) - fg = bg; - - if (base.mode & ATTR_INVISIBLE) - fg = bg; - - /* Intelligent cleaning up of the borders. */ - if (x == 0) { - xclear(0, (y == 0)? 0 : winy, win.hborderpx, - winy + win.ch + - ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); - } - if (winx + width >= win.hborderpx + win.tw) { - xclear(winx + width, (y == 0)? 0 : winy, win.w, - ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); - } - if (y == 0) - xclear(winx, 0, winx + width, win.vborderpx); - if (winy + win.ch >= win.vborderpx + win.th) - xclear(winx, winy + win.ch, winx + width, win.h); - - /* Clean up the region we want to draw to. */ - XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); - - /* Set the clip region because Xft is sometimes dirty. */ - r.x = 0; - r.y = 0; - r.height = win.ch; - r.width = width; - XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); - - if (base.mode & ATTR_BOXDRAW) { - drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); - } else { - - /* Decoration color. */ - Color decor; - uint32_t decorcolor = tgetdecorcolor(&base); - if (decorcolor == DECOR_DEFAULT_COLOR) { - decor = *fg; - } else if (IS_TRUECOL(decorcolor)) { - colfg.alpha = 0xffff; - colfg.red = TRUERED(decorcolor); - colfg.green = TRUEGREEN(decorcolor); - colfg.blue = TRUEBLUE(decorcolor); - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &decor); - } else { - decor = dc.col[decorcolor]; - } - decor.color.alpha = 0xffff; - decor.pixel |= 0xff << 24; - - /* Float thickness, used as a base to compute other values. */ - float fthick = dc.font.height / 18.0; - /* Integer thickness in pixels. Must not be 0. */ - int thick = MAX(1, roundf(fthick)); - /* The default gap between the baseline and a single underline. */ - int gap = roundf(fthick * 2); - /* The total thickness of a double underline. */ - int doubleh = thick * 2 + ceilf(fthick * 0.5); - /* The total thickness of an undercurl. */ - int curlh = thick * 2 + roundf(fthick * 0.75); - - /* Render the underline before the glyphs. */ - if (base.mode & ATTR_UNDERLINE) { - uint32_t style = tgetdecorstyle(&base); - int liney = winy + dc.font.ascent + gap; - /* Adjust liney to guarantee that a single underline fits. */ - liney -= MAX(0, liney + thick - (winy + win.ch)); - if (style == UNDERLINE_DOUBLE) { - liney -= MAX(0, liney + doubleh - (winy + win.ch)); - XftDrawRect(xw.draw, &decor, winx, liney, width, thick); - XftDrawRect(xw.draw, &decor, winx, - liney + doubleh - thick, width, thick); - } else if (style == UNDERLINE_DOTTED) { - xdrawunderdashed(xw.draw, &decor, winx, liney, width, - thick * 2, 0.5, thick); - } else if (style == UNDERLINE_DASHED) { - int wavelen = MAX(2, win.cw * 0.9); - xdrawunderdashed(xw.draw, &decor, winx, liney, width, - wavelen, 0.65, thick); - } else if (style == UNDERLINE_CURLY) { - liney -= MAX(0, liney + curlh - (winy + win.ch)); - xdrawundercurl(xw.draw, &decor, winx, liney, width, - curlh, thick); - } else { - XftDrawRect(xw.draw, &decor, winx, liney, width, thick); - } - } - - /* Render the glyphs. */ - XftDrawGlyphFontSpec(xw.draw, fg, specs, len); - - /* Render strikethrough. Alway use the fg color. */ - if (base.mode & ATTR_STRUCK) { - XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, - width, thick); - } - - /* Reset clip to none. */ - XftDrawSetClip(xw.draw, 0); - } - - void - xdrawglyph(Glyph g, int x, int y) - { - int numspecs; - XftGlyphFontSpec spec; - - numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); - xdrawglyphfontspecs(&spec, g, numspecs, x, y); - if (g.mode & ATTR_IMAGE) { - gr_start_drawing(xw.buf, win.cw, win.ch); - xdrawoneimagecell(g, x, y); - gr_finish_drawing(xw.buf); - } - } - - void - xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) - { - Color drawcol; - - /* remove the old cursor */ - if (selected(ox, oy)) - og.mode ^= ATTR_REVERSE; - xdrawglyph(og, ox, oy); - - if (IS_SET(MODE_HIDE)) - return; - - // If it's an image, just draw a ballot box for simplicity. - if (g.mode & ATTR_IMAGE) - g.u = 0x2610; - - /* - * Select the right color for the right mode. - */ - g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; - - if (IS_SET(MODE_REVERSE)) { - g.mode |= ATTR_REVERSE; - g.bg = defaultfg; - if (selected(cx, cy)) { - drawcol = dc.col[defaultcs]; - g.fg = defaultrcs; - } else { - drawcol = dc.col[defaultrcs]; - g.fg = defaultcs; - } - } else { - if (selected(cx, cy)) { - g.fg = defaultfg; - g.bg = defaultrcs; - } else { - g.fg = defaultbg; - g.bg = defaultcs; - } - drawcol = dc.col[g.bg]; - } - - /* draw the new one */ - if (IS_SET(MODE_FOCUSED)) { - switch (win.cursor) { - case 7: /* st extension */ - g.u = 0x2603; /* snowman (U+2603) */ - /* FALLTHROUGH */ - case 0: /* Blinking Block */ - case 1: /* Blinking Block (Default) */ - case 2: /* Steady Block */ - xdrawglyph(g, cx, cy); - break; - case 3: /* Blinking Underline */ - case 4: /* Steady Underline */ - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + (cy + 1) * win.ch - \ - cursorthickness, - win.cw, cursorthickness); - break; - case 5: /* Blinking bar */ - case 6: /* Steady bar */ - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + cy * win.ch, - cursorthickness, win.ch); - break; - } - } else { - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + cy * win.ch, - win.cw - 1, 1); - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + cy * win.ch, - 1, win.ch - 1); - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + (cx + 1) * win.cw - 1, - win.vborderpx + cy * win.ch, - 1, win.ch - 1); - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + (cy + 1) * win.ch - 1, - win.cw, 1); - } - } - - /* Draw (or queue for drawing) image cells between columns x1 and x2 assuming - * that they have the same attributes (and thus the same lower 24 bits of the - * image ID and the same placement ID). */ - void - xdrawimages(Glyph base, Line line, int x1, int y1, int x2) { - int y_pix = win.vborderpx + y1 * win.ch; - uint32_t image_id_24bits = base.fg & 0xFFFFFF; - uint32_t placement_id = tgetimgplacementid(&base); - // Columns and rows are 1-based, 0 means unspecified. - int last_col = 0; - int last_row = 0; - int last_start_col = 0; - int last_start_x = x1; - // The most significant byte is also 1-base, subtract 1 before use. - uint32_t last_id_4thbyteplus1 = 0; - // We may need to inherit row/column/4th byte from the previous cell. - Glyph *prev = &line[x1 - 1]; - if (x1 > 0 && (prev->mode & ATTR_IMAGE) && - (prev->fg & 0xFFFFFF) == image_id_24bits && - prev->decor == base.decor) { - last_row = tgetimgrow(prev); - last_col = tgetimgcol(prev); - last_id_4thbyteplus1 = tgetimgid4thbyteplus1(prev); - last_start_col = last_col + 1; - } - for (int x = x1; x < x2; ++x) { - Glyph *g = &line[x]; - uint32_t cur_row = tgetimgrow(g); - uint32_t cur_col = tgetimgcol(g); - uint32_t cur_id_4thbyteplus1 = tgetimgid4thbyteplus1(g); - uint32_t num_diacritics = tgetimgdiacriticcount(g); - // If the row is not specified, assume it's the same as the row - // of the previous cell. Note that `cur_row` may contain a - // value imputed earlier, which will be preserved if `last_row` - // is zero (i.e. we don't know the row of the previous cell). - if (last_row && (num_diacritics == 0 || !cur_row)) - cur_row = last_row; - // If the column is not specified and the row is the same as the - // row of the previous cell, then assume that the column is the - // next one. - if (last_col && (num_diacritics <= 1 || !cur_col) && - cur_row == last_row) - cur_col = last_col + 1; - // If the additional id byte is not specified and the - // coordinates are consecutive, assume the byte is also the - // same. - if (last_id_4thbyteplus1 && - (num_diacritics <= 2 || !cur_id_4thbyteplus1) && - cur_row == last_row && cur_col == last_col + 1) - cur_id_4thbyteplus1 = last_id_4thbyteplus1; - // If we couldn't infer row and column, start from the top left - // corner. - if (cur_row == 0) - cur_row = 1; - if (cur_col == 0) - cur_col = 1; - // If this cell breaks a contiguous stripe of image cells, draw - // that line and start a new one. - if (cur_col != last_col + 1 || cur_row != last_row || - cur_id_4thbyteplus1 != last_id_4thbyteplus1) { - uint32_t image_id = image_id_24bits; - if (last_id_4thbyteplus1) - image_id |= (last_id_4thbyteplus1 - 1) << 24; - if (last_row != 0) { - int x_pix = - win.hborderpx + last_start_x * win.cw; - gr_append_imagerect( - xw.buf, image_id, placement_id, - last_start_col - 1, last_col, - last_row - 1, last_row, last_start_x, - y1, x_pix, y_pix, win.cw, win.ch, - base.mode & ATTR_REVERSE); - } - last_start_col = cur_col; - last_start_x = x; - } - last_row = cur_row; - last_col = cur_col; - last_id_4thbyteplus1 = cur_id_4thbyteplus1; - // Populate the missing glyph data to enable inheritance between - // runs and support the naive implementation of tgetimgid. - if (!tgetimgrow(g)) - tsetimgrow(g, cur_row); - // We cannot save this information if there are > 511 cols. - if (!tgetimgcol(g) && (cur_col & ~0x1ff) == 0) - tsetimgcol(g, cur_col); - if (!tgetimgid4thbyteplus1(g)) - tsetimg4thbyteplus1(g, cur_id_4thbyteplus1); - } - uint32_t image_id = image_id_24bits; - if (last_id_4thbyteplus1) - image_id |= (last_id_4thbyteplus1 - 1) << 24; - // Draw the last contiguous stripe. - if (last_row != 0) { - int x_pix = win.hborderpx + last_start_x * win.cw; - gr_append_imagerect(xw.buf, image_id, placement_id, - last_start_col - 1, last_col, last_row - 1, - last_row, last_start_x, y1, x_pix, y_pix, - win.cw, win.ch, base.mode & ATTR_REVERSE); - } - } - - /* Draw just one image cell without inheriting attributes from the left. */ - void xdrawoneimagecell(Glyph g, int x, int y) { - if (!(g.mode & ATTR_IMAGE)) - return; - int x_pix = win.hborderpx + x * win.cw; - int y_pix = win.vborderpx + y * win.ch; - uint32_t row = tgetimgrow(&g) - 1; - uint32_t col = tgetimgcol(&g) - 1; - uint32_t placement_id = tgetimgplacementid(&g); - uint32_t image_id = tgetimgid(&g); - gr_append_imagerect(xw.buf, image_id, placement_id, col, col + 1, row, - row + 1, x, y, x_pix, y_pix, win.cw, win.ch, - g.mode & ATTR_REVERSE); - } - - /* Prepare for image drawing. */ - void xstartimagedraw(int *dirty, int rows) { - gr_start_drawing(xw.buf, win.cw, win.ch); - gr_mark_dirty_animations(dirty, rows); - } - - /* Draw all queued image cells. */ - void xfinishimagedraw() { - gr_finish_drawing(xw.buf); - } - - void - xsetenv(void) - { - char buf[sizeof(long) * 8 + 1]; - - snprintf(buf, sizeof(buf), "%lu", xw.win); - setenv("WINDOWID", buf, 1); - } - - void - xseticontitle(char *p) - { - XTextProperty prop; - DEFAULT(p, opt_title); - - if (p[0] == '\0') - p = opt_title; - - if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, - &prop) != Success) - return; - XSetWMIconName(xw.dpy, xw.win, &prop); - XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); - XFree(prop.value); - } - - void - xsettitle(char *p) - { - XTextProperty prop; - DEFAULT(p, opt_title); - - if (p[0] == '\0') - p = opt_title; - - if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, - &prop) != Success) - return; - XSetWMName(xw.dpy, xw.win, &prop); - XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); - XFree(prop.value); - } - - int - xstartdraw(void) - { - return IS_SET(MODE_VISIBLE); - } - - void - xdrawline(Line line, int x1, int y1, int x2) - { - int i, x, ox, numspecs; - Glyph base, new; - XftGlyphFontSpec *specs = xw.specbuf; - - numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); - i = ox = 0; - for (x = x1; x < x2 && i < numspecs; x++) { - new = line[x]; - if (new.mode == ATTR_WDUMMY) - continue; - if (selected(x, y1)) - new.mode ^= ATTR_REVERSE; - if (i > 0 && ATTRCMP(base, new)) { - xdrawglyphfontspecs(specs, base, i, ox, y1); - if (base.mode & ATTR_IMAGE) - xdrawimages(base, line, ox, y1, x); - specs += i; - numspecs -= i; - i = 0; - } - if (i == 0) { - ox = x; - base = new; - } - i++; - } - if (i > 0) - xdrawglyphfontspecs(specs, base, i, ox, y1); - if (i > 0 && base.mode & ATTR_IMAGE) - xdrawimages(base, line, ox, y1, x); - } - - void - xfinishdraw(void) - { - XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, - win.h, 0, 0); - XSetForeground(xw.dpy, dc.gc, - dc.col[IS_SET(MODE_REVERSE)? - defaultfg : defaultbg].pixel); - } - - void - xximspot(int x, int y) - { - if (xw.ime.xic == NULL) - return; - - xw.ime.spot.x = borderpx + x * win.cw; - xw.ime.spot.y = borderpx + (y + 1) * win.ch; - - XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); - } - - void - expose(XEvent *ev) - { - redraw(); - } - - void - visibility(XEvent *ev) - { - XVisibilityEvent *e = &ev->xvisibility; - - MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); - } - - void - unmap(XEvent *ev) - { - win.mode &= ~MODE_VISIBLE; - } - - void - xsetpointermotion(int set) - { - MODBIT(xw.attrs.event_mask, set, PointerMotionMask); - XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); - } - - void - xsetmode(int set, unsigned int flags) - { - int mode = win.mode; - MODBIT(win.mode, set, flags); - if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) - redraw(); - } - - int - xsetcursor(int cursor) - { - if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ - return 1; - win.cursor = cursor; - return 0; - } - - void - xseturgency(int add) - { - XWMHints *h = XGetWMHints(xw.dpy, xw.win); - - MODBIT(h->flags, add, XUrgencyHint); - XSetWMHints(xw.dpy, xw.win, h); - XFree(h); - } - - void - xbell(void) - { - if (!(IS_SET(MODE_FOCUSED))) - xseturgency(1); - if (bellvolume) - XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); - } - - void - focus(XEvent *ev) - { - XFocusChangeEvent *e = &ev->xfocus; - - if (e->mode == NotifyGrab) - return; - - if (ev->type == FocusIn) { - if (xw.ime.xic) - XSetICFocus(xw.ime.xic); - win.mode |= MODE_FOCUSED; - xseturgency(0); - if (IS_SET(MODE_FOCUS)) - ttywrite("\033[I", 3, 0); - } else { - if (xw.ime.xic) - XUnsetICFocus(xw.ime.xic); - win.mode &= ~MODE_FOCUSED; - if (IS_SET(MODE_FOCUS)) - ttywrite("\033[O", 3, 0); - } - } - - int - match(uint mask, uint state) - { - return mask == XK_ANY_MOD || mask == (state & ~ignoremod); - } - - char* - kmap(KeySym k, uint state) - { - Key *kp; - int i; - - /* Check for mapped keys out of X11 function keys. */ - for (i = 0; i < LEN(mappedkeys); i++) { - if (mappedkeys[i] == k) - break; - } - if (i == LEN(mappedkeys)) { - if ((k & 0xFFFF) < 0xFD00) - return NULL; - } - - for (kp = key; kp < key + LEN(key); kp++) { - if (kp->k != k) - continue; - - if (!match(kp->mask, state)) - continue; - - if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) - continue; - if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) - continue; - - if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) - continue; - - return kp->s; - } - - return NULL; - } - - void - kpress(XEvent *ev) - { - XKeyEvent *e = &ev->xkey; - KeySym ksym = NoSymbol; - char buf[64], *customkey; - int len; - Rune c; - Status status; - Shortcut *bp; - - if (IS_SET(MODE_KBDLOCK)) - return; - - if (xw.ime.xic) { - len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); - if (status == XBufferOverflow) - return; - } else { - len = XLookupString(e, buf, sizeof buf, &ksym, NULL); - } - /* 1. shortcuts */ - for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { - if (ksym == bp->keysym && match(bp->mod, e->state)) { - bp->func(&(bp->arg)); - return; - } - } - - /* 2. custom keys from config.h */ - if ((customkey = kmap(ksym, e->state))) { - ttywrite(customkey, strlen(customkey), 1); - return; - } - - /* 3. composed string from input method */ - if (len == 0) - return; - if (len == 1 && e->state & Mod1Mask) { - if (IS_SET(MODE_8BIT)) { - if (*buf < 0177) { - c = *buf | 0x80; - len = utf8encode(c, buf); - } - } else { - buf[1] = buf[0]; - buf[0] = '\033'; - len = 2; - } - } - ttywrite(buf, len, 1); - } - - void - cmessage(XEvent *e) - { - /* - * See xembed specs - * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html - */ - if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { - if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { - win.mode |= MODE_FOCUSED; - xseturgency(0); - } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { - win.mode &= ~MODE_FOCUSED; - } - } else if (e->xclient.data.l[0] == xw.wmdeletewin) { - ttyhangup(); - gr_deinit(); - exit(0); - } - } - - void - resize(XEvent *e) - { - if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) - return; - - cresize(e->xconfigure.width, e->xconfigure.height); - } - - void - run(void) - { - XEvent ev; - int w = win.w, h = win.h; - fd_set rfd; - int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; - struct timespec seltv, *tv, now, lastblink, trigger; - double timeout; - - /* Waiting for window mapping */ - do { - XNextEvent(xw.dpy, &ev); - /* - * This XFilterEvent call is required because of XOpenIM. It - * does filter out the key event and some client message for - * the input method too. - */ - if (XFilterEvent(&ev, None)) - continue; - if (ev.type == ConfigureNotify) { - w = ev.xconfigure.width; - h = ev.xconfigure.height; - } - } while (ev.type != MapNotify); - - ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); - cresize(w, h); - - for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { - FD_ZERO(&rfd); - FD_SET(ttyfd, &rfd); - FD_SET(xfd, &rfd); - - if (XPending(xw.dpy)) - timeout = 0; /* existing events might not set xfd */ - - /* Decrease the timeout if there are active animations. */ - if (graphics_next_redraw_delay != INT_MAX && - IS_SET(MODE_VISIBLE)) - timeout = timeout < 0 ? graphics_next_redraw_delay - : MIN(timeout, - graphics_next_redraw_delay); - - seltv.tv_sec = timeout / 1E3; - seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); - tv = timeout >= 0 ? &seltv : NULL; - - if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { - if (errno == EINTR) - continue; - die("select failed: %s\n", strerror(errno)); - } - clock_gettime(CLOCK_MONOTONIC, &now); - - if (FD_ISSET(ttyfd, &rfd)) - ttyread(); - - xev = 0; - while (XPending(xw.dpy)) { - xev = 1; - XNextEvent(xw.dpy, &ev); - if (XFilterEvent(&ev, None)) - continue; - if (handler[ev.type]) - (handler[ev.type])(&ev); - } - - /* - * To reduce flicker and tearing, when new content or event - * triggers drawing, we first wait a bit to ensure we got - * everything, and if nothing new arrives - we draw. - * We start with trying to wait minlatency ms. If more content - * arrives sooner, we retry with shorter and shorter periods, - * and eventually draw even without idle after maxlatency ms. - * Typically this results in low latency while interacting, - * maximum latency intervals during `cat huge.txt`, and perfect - * sync with periodic updates from animations/key-repeats/etc. - */ - if (FD_ISSET(ttyfd, &rfd) || xev) { - if (!drawing) { - trigger = now; - drawing = 1; - } - timeout = (maxlatency - TIMEDIFF(now, trigger)) \ - / maxlatency * minlatency; - if (timeout > 0) - continue; /* we have time, try to find idle */ - } - - /* idle detected or maxlatency exhausted -> draw */ - timeout = -1; - if (blinktimeout && tattrset(ATTR_BLINK)) { - timeout = blinktimeout - TIMEDIFF(now, lastblink); - if (timeout <= 0) { - if (-timeout > blinktimeout) /* start visible */ - win.mode |= MODE_BLINK; - win.mode ^= MODE_BLINK; - tsetdirtattr(ATTR_BLINK); - lastblink = now; - timeout = blinktimeout; - } - } - - draw(); - XFlush(xw.dpy); - drawing = 0; - } - } - - void - usage(void) - { - die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" - " [-n name] [-o file]\n" - " [-T title] [-t title] [-w windowid]" - " [[-e] command [args ...]]\n" - " %s [-aiv] [-c class] [-f font] [-g geometry]" - " [-n name] [-o file]\n" - " [-T title] [-t title] [-w windowid] -l line" - " [stty_args ...]\n", argv0, argv0); - } - - int - main(int argc, char *argv[]) - { - xw.l = xw.t = 0; - xw.isfixed = False; - xsetcursor(cursorshape); - - ARGBEGIN { - case 'a': - allowaltscreen = 0; - break; - case 'c': - opt_class = EARGF(usage()); - break; - case 'e': - if (argc > 0) - --argc, ++argv; - goto run; - case 'f': - opt_font = EARGF(usage()); - break; - case 'g': - xw.gm = XParseGeometry(EARGF(usage()), - &xw.l, &xw.t, &cols, &rows); - break; - case 'i': - xw.isfixed = 1; - break; - case 'o': - opt_io = EARGF(usage()); - break; - case 'l': - opt_line = EARGF(usage()); - break; - case 'n': - opt_name = EARGF(usage()); - break; - case 't': - case 'T': - opt_title = EARGF(usage()); - break; - case 'w': - opt_embed = EARGF(usage()); - break; - case 'v': - die("%s " VERSION "\n", argv0); - break; - default: - usage(); - } ARGEND; + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + if (base.mode & ATTR_BOXDRAW) { + drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); + } else { + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + } + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (p[0] == '\0') + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (p[0] == '\0') + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym = NoSymbol; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) { + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + if (status == XBufferOverflow) + return; + } else { + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + } + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; run: - if (argc > 0) /* eat all remaining arguments */ - opt_cmd = argv; - - if (!opt_title) - opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; - - setlocale(LC_CTYPE, ""); - XSetLocaleModifiers(""); - cols = MAX(cols, 1); - rows = MAX(rows, 1); - tnew(cols, rows); - xinit(cols, rows); - xsetenv(); - selinit(); - run(); - - return 0; - } + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/files/config/suckless/st/x.c.bk b/files/config/suckless/st/x.c.bk deleted file mode 100644 index 38a4bc3..0000000 --- a/files/config/suckless/st/x.c.bk +++ /dev/null @@ -1,2405 +0,0 @@ -/* See LICENSE for license details. */ -#include <errno.h> -#include <math.h> -#include <limits.h> -#include <locale.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <sys/select.h> -#include <time.h> -#include <unistd.h> -#include <libgen.h> -#include <X11/Xatom.h> -#include <X11/Xlib.h> -#include <X11/cursorfont.h> -#include <X11/keysym.h> -#include <X11/Xft/Xft.h> -#include <X11/XKBlib.h> - -char *argv0; -#include "arg.h" -#include "st.h" -#include "win.h" -#include "graphics.h" - -/* types used in config.h */ -typedef struct { - uint mod; - KeySym keysym; - void (*func)(const Arg *); - const Arg arg; -} Shortcut; - -typedef struct { - uint mod; - uint button; - void (*func)(const Arg *); - const Arg arg; - uint release; -} MouseShortcut; - -typedef struct { - KeySym k; - uint mask; - char *s; - /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ - signed char appkey; /* application keypad */ - signed char appcursor; /* application cursor */ -} Key; - -/* X modifiers */ -#define XK_ANY_MOD UINT_MAX -#define XK_NO_MOD 0 -#define XK_SWITCH_MOD (1<<13|1<<14) - -/* function definitions used in config.h */ -static void clipcopy(const Arg *); -static void clippaste(const Arg *); -static void numlock(const Arg *); -static void selpaste(const Arg *); -static void zoom(const Arg *); -static void zoomabs(const Arg *); -static void zoomreset(const Arg *); -static void ttysend(const Arg *); -static void previewimage(const Arg *); -static void showimageinfo(const Arg *); -static void togglegrdebug(const Arg *); -static void dumpgrstate(const Arg *); -static void unloadimages(const Arg *); -static void toggleimages(const Arg *); - -/* config.h for applying patches and the configuration. */ -#include "config.h" - -/* XEMBED messages */ -#define XEMBED_FOCUS_IN 4 -#define XEMBED_FOCUS_OUT 5 - -/* macros */ -#define IS_SET(flag) ((win.mode & (flag)) != 0) -#define TRUERED(x) (((x) & 0xff0000) >> 8) -#define TRUEGREEN(x) (((x) & 0xff00)) -#define TRUEBLUE(x) (((x) & 0xff) << 8) - -typedef XftDraw *Draw; -typedef XftColor Color; -typedef XftGlyphFontSpec GlyphFontSpec; - -/* Purely graphic info */ -typedef struct { - int tw, th; /* tty width and height */ - int w, h; /* window width and height */ - int hborderpx, vborderpx; - int ch; /* char height */ - int cw; /* char width */ - int mode; /* window state/mode flags */ - int cursor; /* cursor style */ -} TermWindow; - -typedef struct { - Display *dpy; - Colormap cmap; - Window win; - Drawable buf; - GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ - Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; - struct { - XIM xim; - XIC xic; - XPoint spot; - XVaNestedList spotlist; - } ime; - Draw draw; - Visual *vis; - XSetWindowAttributes attrs; - int scr; - int isfixed; /* is fixed geometry? */ - int l, t; /* left and top offset */ - int gm; /* geometry mask */ -} XWindow; - -typedef struct { - Atom xtarget; - char *primary, *clipboard; - struct timespec tclick1; - struct timespec tclick2; -} XSelection; - -/* Font structure */ -#define Font Font_ -typedef struct { - int height; - int width; - int ascent; - int descent; - int badslant; - int badweight; - short lbearing; - short rbearing; - XftFont *match; - FcFontSet *set; - FcPattern *pattern; -} Font; - -/* Drawing Context */ -typedef struct { - Color *col; - size_t collen; - Font font, bfont, ifont, ibfont; - GC gc; -} DC; - -static inline ushort sixd_to_16bit(int); -static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); -static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); -static void xdrawglyph(Glyph, int, int); -static void xdrawimages(Glyph, Line, int x1, int y1, int x2); -static void xdrawoneimagecell(Glyph, int x, int y); -static void xclear(int, int, int, int); -static int xgeommasktogravity(int); -static int ximopen(Display *); -static void ximinstantiate(Display *, XPointer, XPointer); -static void ximdestroy(XIM, XPointer, XPointer); -static int xicdestroy(XIC, XPointer, XPointer); -static void xinit(int, int); -static void cresize(int, int); -static void xresize(int, int); -static void xhints(void); -static int xloadcolor(int, const char *, Color *); -static int xloadfont(Font *, FcPattern *); -static void xloadfonts(const char *, double); -static void xunloadfont(Font *); -static void xunloadfonts(void); -static void xsetenv(void); -static void xseturgency(int); -static int evcol(XEvent *); -static int evrow(XEvent *); - -static void expose(XEvent *); -static void visibility(XEvent *); -static void unmap(XEvent *); -static void kpress(XEvent *); -static void cmessage(XEvent *); -static void resize(XEvent *); -static void focus(XEvent *); -static uint buttonmask(uint); -static int mouseaction(XEvent *, uint); -static void brelease(XEvent *); -static void bpress(XEvent *); -static void bmotion(XEvent *); -static void propnotify(XEvent *); -static void selnotify(XEvent *); -static void selclear_(XEvent *); -static void selrequest(XEvent *); -static void setsel(char *, Time); -static void mousesel(XEvent *, int); -static void mousereport(XEvent *); -static char *kmap(KeySym, uint); -static int match(uint, uint); - -static void run(void); -static void usage(void); - -static void (*handler[LASTEvent])(XEvent *) = { - [KeyPress] = kpress, - [ClientMessage] = cmessage, - [ConfigureNotify] = resize, - [VisibilityNotify] = visibility, - [UnmapNotify] = unmap, - [Expose] = expose, - [FocusIn] = focus, - [FocusOut] = focus, - [MotionNotify] = bmotion, - [ButtonPress] = bpress, - [ButtonRelease] = brelease, -/* - * Uncomment if you want the selection to disappear when you select something - * different in another window. - */ -/* [SelectionClear] = selclear_, */ - [SelectionNotify] = selnotify, -/* - * PropertyNotify is only turned on when there is some INCR transfer happening - * for the selection retrieval. - */ - [PropertyNotify] = propnotify, - [SelectionRequest] = selrequest, -}; - -/* Globals */ -static DC dc; -static XWindow xw; -static XSelection xsel; -static TermWindow win; -static unsigned int mouse_col = 0, mouse_row = 0; - -/* Font Ring Cache */ -enum { - FRC_NORMAL, - FRC_ITALIC, - FRC_BOLD, - FRC_ITALICBOLD -}; - -typedef struct { - XftFont *font; - int flags; - Rune unicodep; -} Fontcache; - -/* Fontcache is an array now. A new font will be appended to the array. */ -static Fontcache *frc = NULL; -static int frclen = 0; -static int frccap = 0; -static char *usedfont = NULL; -static double usedfontsize = 0; -static double defaultfontsize = 0; - -static char *opt_class = NULL; -static char **opt_cmd = NULL; -static char *opt_embed = NULL; -static char *opt_font = NULL; -static char *opt_io = NULL; -static char *opt_line = NULL; -static char *opt_name = NULL; -static char *opt_title = NULL; - -static uint buttons; /* bit field of pressed buttons */ - -void -clipcopy(const Arg *dummy) -{ - Atom clipboard; - - free(xsel.clipboard); - xsel.clipboard = NULL; - - if (xsel.primary != NULL) { - xsel.clipboard = xstrdup(xsel.primary); - clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); - } -} - -void -clippaste(const Arg *dummy) -{ - Atom clipboard; - - clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, - xw.win, CurrentTime); -} - -void -selpaste(const Arg *dummy) -{ - XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, - xw.win, CurrentTime); -} - -void -numlock(const Arg *dummy) -{ - win.mode ^= MODE_NUMLOCK; -} - -void -zoom(const Arg *arg) -{ - Arg larg; - - larg.f = usedfontsize + arg->f; - zoomabs(&larg); -} - -void -zoomabs(const Arg *arg) -{ - xunloadfonts(); - xloadfonts(usedfont, arg->f); - cresize(0, 0); - redraw(); - xhints(); -} - -void -zoomreset(const Arg *arg) -{ - Arg larg; - - if (defaultfontsize > 0) { - larg.f = defaultfontsize; - zoomabs(&larg); - } -} - -void -ttysend(const Arg *arg) -{ - ttywrite(arg->s, strlen(arg->s), 1); -} - -void -previewimage(const Arg *arg) -{ - Glyph g = getglyphat(mouse_col, mouse_row); - if (g.mode & ATTR_IMAGE) { - uint32_t image_id = tgetimgid(&g); - fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n", - image_id, tgetimgplacementid(&g), tgetimgcol(&g), - tgetimgrow(&g)); - gr_preview_image(image_id, arg->s); - } -} - -void -showimageinfo(const Arg *arg) -{ - Glyph g = getglyphat(mouse_col, mouse_row); - if (g.mode & ATTR_IMAGE) { - uint32_t image_id = tgetimgid(&g); - fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n", - image_id, tgetimgplacementid(&g), tgetimgcol(&g), - tgetimgrow(&g)); - char stcommand[256] = {0}; - size_t len = snprintf(stcommand, sizeof(stcommand), "%s -e less", argv0); - if (len > sizeof(stcommand) - 1) { - fprintf(stderr, "Executable name too long: %s\n", - argv0); - return; - } - gr_show_image_info(image_id, tgetimgplacementid(&g), - tgetimgcol(&g), tgetimgrow(&g), - tgetisclassicplaceholder(&g), - tgetimgdiacriticcount(&g), argv0); - } -} - -void -togglegrdebug(const Arg *arg) -{ - graphics_debug_mode = (graphics_debug_mode + 1) % 3; - redraw(); -} - -void -dumpgrstate(const Arg *arg) -{ - gr_dump_state(); -} - -void -unloadimages(const Arg *arg) -{ - gr_unload_images_to_reduce_ram(); -} - -void -toggleimages(const Arg *arg) -{ - graphics_display_images = !graphics_display_images; - redraw(); -} - -int -evcol(XEvent *e) -{ - int x = e->xbutton.x - win.hborderpx; - LIMIT(x, 0, win.tw - 1); - return x / win.cw; -} - -int -evrow(XEvent *e) -{ - int y = e->xbutton.y - win.vborderpx; - LIMIT(y, 0, win.th - 1); - return y / win.ch; -} - -void -mousesel(XEvent *e, int done) -{ - int type, seltype = SEL_REGULAR; - uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); - - for (type = 1; type < LEN(selmasks); ++type) { - if (match(selmasks[type], state)) { - seltype = type; - break; - } - } - selextend(evcol(e), evrow(e), seltype, done); - if (done) - setsel(getsel(), e->xbutton.time); -} - -void -mousereport(XEvent *e) -{ - int len, btn, code; - int x = evcol(e), y = evrow(e); - int state = e->xbutton.state; - char buf[40]; - static int ox, oy; - - if (e->type == MotionNotify) { - if (x == ox && y == oy) - return; - if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) - return; - /* MODE_MOUSEMOTION: no reporting if no button is pressed */ - if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) - return; - /* Set btn to lowest-numbered pressed button, or 12 if no - * buttons are pressed. */ - for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) - ; - code = 32; - } else { - btn = e->xbutton.button; - /* Only buttons 1 through 11 can be encoded */ - if (btn < 1 || btn > 11) - return; - if (e->type == ButtonRelease) { - /* MODE_MOUSEX10: no button release reporting */ - if (IS_SET(MODE_MOUSEX10)) - return; - /* Don't send release events for the scroll wheel */ - if (btn == 4 || btn == 5) - return; - } - code = 0; - } - - ox = x; - oy = y; - - /* Encode btn into code. If no button is pressed for a motion event in - * MODE_MOUSEMANY, then encode it as a release. */ - if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) - code += 3; - else if (btn >= 8) - code += 128 + btn - 8; - else if (btn >= 4) - code += 64 + btn - 4; - else - code += btn - 1; - - if (!IS_SET(MODE_MOUSEX10)) { - code += ((state & ShiftMask ) ? 4 : 0) - + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ - + ((state & ControlMask) ? 16 : 0); - } - - if (IS_SET(MODE_MOUSESGR)) { - len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", - code, x+1, y+1, - e->type == ButtonRelease ? 'm' : 'M'); - } else if (x < 223 && y < 223) { - len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", - 32+code, 32+x+1, 32+y+1); - } else { - return; - } - - ttywrite(buf, len, 0); -} - -uint -buttonmask(uint button) -{ - return button == Button1 ? Button1Mask - : button == Button2 ? Button2Mask - : button == Button3 ? Button3Mask - : button == Button4 ? Button4Mask - : button == Button5 ? Button5Mask - : 0; -} - -int -mouseaction(XEvent *e, uint release) -{ - MouseShortcut *ms; - - /* ignore Button<N>mask for Button<N> - it's set on release */ - uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); - - mouse_col = evcol(e); - mouse_row = evrow(e); - - for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { - if (ms->release == release && - ms->button == e->xbutton.button && - (match(ms->mod, state) || /* exact or forced */ - match(ms->mod, state & ~forcemousemod))) { - ms->func(&(ms->arg)); - return 1; - } - } - - return 0; -} - -void -bpress(XEvent *e) -{ - int btn = e->xbutton.button; - struct timespec now; - int snap; - - if (1 <= btn && btn <= 11) - buttons |= 1 << (btn-1); - - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { - mousereport(e); - return; - } - - if (mouseaction(e, 0)) - return; - - if (btn == Button1) { - /* - * If the user clicks below predefined timeouts specific - * snapping behaviour is exposed. - */ - clock_gettime(CLOCK_MONOTONIC, &now); - if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { - snap = SNAP_LINE; - } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { - snap = SNAP_WORD; - } else { - snap = 0; - } - xsel.tclick2 = xsel.tclick1; - xsel.tclick1 = now; - - selstart(evcol(e), evrow(e), snap); - } -} - -void -propnotify(XEvent *e) -{ - XPropertyEvent *xpev; - Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - - xpev = &e->xproperty; - if (xpev->state == PropertyNewValue && - (xpev->atom == XA_PRIMARY || - xpev->atom == clipboard)) { - selnotify(e); - } -} - -void -selnotify(XEvent *e) -{ - ulong nitems, ofs, rem; - int format; - uchar *data, *last, *repl; - Atom type, incratom, property = None; - - incratom = XInternAtom(xw.dpy, "INCR", 0); - - ofs = 0; - if (e->type == SelectionNotify) - property = e->xselection.property; - else if (e->type == PropertyNotify) - property = e->xproperty.atom; - - if (property == None) - return; - - do { - if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, - BUFSIZ/4, False, AnyPropertyType, - &type, &format, &nitems, &rem, - &data)) { - fprintf(stderr, "Clipboard allocation failed\n"); - return; - } - - if (e->type == PropertyNotify && nitems == 0 && rem == 0) { - /* - * If there is some PropertyNotify with no data, then - * this is the signal of the selection owner that all - * data has been transferred. We won't need to receive - * PropertyNotify events anymore. - */ - MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); - XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, - &xw.attrs); - } - - if (type == incratom) { - /* - * Activate the PropertyNotify events so we receive - * when the selection owner does send us the next - * chunk of data. - */ - MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); - XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, - &xw.attrs); - - /* - * Deleting the property is the transfer start signal. - */ - XDeleteProperty(xw.dpy, xw.win, (int)property); - continue; - } - - /* - * As seen in getsel: - * Line endings are inconsistent in the terminal and GUI world - * copy and pasting. When receiving some selection data, - * replace all '\n' with '\r'. - * FIXME: Fix the computer world. - */ - repl = data; - last = data + nitems * format / 8; - while ((repl = memchr(repl, '\n', last - repl))) { - *repl++ = '\r'; - } - - if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) - ttywrite("\033[200~", 6, 0); - ttywrite((char *)data, nitems * format / 8, 1); - if (IS_SET(MODE_BRCKTPASTE) && rem == 0) - ttywrite("\033[201~", 6, 0); - XFree(data); - /* number of 32-bit chunks returned */ - ofs += nitems * format / 32; - } while (rem > 0); - - /* - * Deleting the property again tells the selection owner to send the - * next data chunk in the property. - */ - XDeleteProperty(xw.dpy, xw.win, (int)property); -} - -void -xclipcopy(void) -{ - clipcopy(NULL); -} - -void -selclear_(XEvent *e) -{ - selclear(); -} - -void -selrequest(XEvent *e) -{ - XSelectionRequestEvent *xsre; - XSelectionEvent xev; - Atom xa_targets, string, clipboard; - char *seltext; - - xsre = (XSelectionRequestEvent *) e; - xev.type = SelectionNotify; - xev.requestor = xsre->requestor; - xev.selection = xsre->selection; - xev.target = xsre->target; - xev.time = xsre->time; - if (xsre->property == None) - xsre->property = xsre->target; - - /* reject */ - xev.property = None; - - xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); - if (xsre->target == xa_targets) { - /* respond with the supported type */ - string = xsel.xtarget; - XChangeProperty(xsre->display, xsre->requestor, xsre->property, - XA_ATOM, 32, PropModeReplace, - (uchar *) &string, 1); - xev.property = xsre->property; - } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { - /* - * xith XA_STRING non ascii characters may be incorrect in the - * requestor. It is not our problem, use utf8. - */ - clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - if (xsre->selection == XA_PRIMARY) { - seltext = xsel.primary; - } else if (xsre->selection == clipboard) { - seltext = xsel.clipboard; - } else { - fprintf(stderr, - "Unhandled clipboard selection 0x%lx\n", - xsre->selection); - return; - } - if (seltext != NULL) { - XChangeProperty(xsre->display, xsre->requestor, - xsre->property, xsre->target, - 8, PropModeReplace, - (uchar *)seltext, strlen(seltext)); - xev.property = xsre->property; - } - } - - /* all done, send a notification to the listener */ - if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) - fprintf(stderr, "Error sending SelectionNotify event\n"); -} - -void -setsel(char *str, Time t) -{ - if (!str) - return; - - free(xsel.primary); - xsel.primary = str; - - XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); - if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) - selclear(); -} - -void -xsetsel(char *str) -{ - setsel(str, CurrentTime); -} - -void -brelease(XEvent *e) -{ - int btn = e->xbutton.button; - - if (1 <= btn && btn <= 11) - buttons &= ~(1 << (btn-1)); - - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { - mousereport(e); - return; - } - - if (mouseaction(e, 1)) - return; - if (btn == Button1) - mousesel(e, 1); -} - -void -bmotion(XEvent *e) -{ - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { - mousereport(e); - return; - } - - mousesel(e, 0); -} - -void -cresize(int width, int height) -{ - int col, row; - - if (width != 0) - win.w = width; - if (height != 0) - win.h = height; - - col = (win.w - 2 * borderpx) / win.cw; - row = (win.h - 2 * borderpx) / win.ch; - col = MAX(1, col); - row = MAX(1, row); - - win.hborderpx = (win.w - col * win.cw) * anysize_halign / 100; - win.vborderpx = (win.h - row * win.ch) * anysize_valign / 100; - - tresize(col, row); - xresize(col, row); - ttyresize(win.tw, win.th); -} - -void -xresize(int col, int row) -{ - win.tw = col * win.cw; - win.th = row * win.ch; - - XFreePixmap(xw.dpy, xw.buf); - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - DefaultDepth(xw.dpy, xw.scr)); - XftDrawChange(xw.draw, xw.buf); - xclear(0, 0, win.w, win.h); - - /* resize to new width */ - xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); -} - -ushort -sixd_to_16bit(int x) -{ - return x == 0 ? 0 : 0x3737 + 0x2828 * x; -} - -int -xloadcolor(int i, const char *name, Color *ncolor) -{ - XRenderColor color = { .alpha = 0xffff }; - - if (!name) { - if (BETWEEN(i, 16, 255)) { /* 256 color */ - if (i < 6*6*6+16) { /* same colors as xterm */ - color.red = sixd_to_16bit( ((i-16)/36)%6 ); - color.green = sixd_to_16bit( ((i-16)/6) %6 ); - color.blue = sixd_to_16bit( ((i-16)/1) %6 ); - } else { /* greyscale */ - color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); - color.green = color.blue = color.red; - } - return XftColorAllocValue(xw.dpy, xw.vis, - xw.cmap, &color, ncolor); - } else - name = colorname[i]; - } - - return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); -} - -void -xloadcols(void) -{ - int i; - static int loaded; - Color *cp; - - if (loaded) { - for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) - XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); - } else { - dc.collen = MAX(LEN(colorname), 256); - dc.col = xmalloc(dc.collen * sizeof(Color)); - } - - for (i = 0; i < dc.collen; i++) - if (!xloadcolor(i, NULL, &dc.col[i])) { - if (colorname[i]) - die("could not allocate color '%s'\n", colorname[i]); - else - die("could not allocate color %d\n", i); - } - loaded = 1; -} - -int -xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) -{ - if (!BETWEEN(x, 0, dc.collen - 1)) - return 1; - - *r = dc.col[x].color.red >> 8; - *g = dc.col[x].color.green >> 8; - *b = dc.col[x].color.blue >> 8; - - return 0; -} - -int -xsetcolorname(int x, const char *name) -{ - Color ncolor; - - if (!BETWEEN(x, 0, dc.collen - 1)) - return 1; - - if (!xloadcolor(x, name, &ncolor)) - return 1; - - XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); - dc.col[x] = ncolor; - - return 0; -} - -/* - * Absolute coordinates. - */ -void -xclear(int x1, int y1, int x2, int y2) -{ - XftDrawRect(xw.draw, - &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], - x1, y1, x2-x1, y2-y1); -} - -void -xhints(void) -{ - XClassHint class = {opt_name ? opt_name : termname, - opt_class ? opt_class : termname}; - XWMHints wm = {.flags = InputHint, .input = 1}; - XSizeHints *sizeh; - - sizeh = XAllocSizeHints(); - - sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; - sizeh->height = win.h; - sizeh->width = win.w; - sizeh->height_inc = 1; - sizeh->width_inc = 1; - sizeh->base_height = 2 * borderpx; - sizeh->base_width = 2 * borderpx; - sizeh->min_height = win.ch + 2 * borderpx; - sizeh->min_width = win.cw + 2 * borderpx; - if (xw.isfixed) { - sizeh->flags |= PMaxSize; - sizeh->min_width = sizeh->max_width = win.w; - sizeh->min_height = sizeh->max_height = win.h; - } - if (xw.gm & (XValue|YValue)) { - sizeh->flags |= USPosition | PWinGravity; - sizeh->x = xw.l; - sizeh->y = xw.t; - sizeh->win_gravity = xgeommasktogravity(xw.gm); - } - - XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, - &class); - XFree(sizeh); -} - -int -xgeommasktogravity(int mask) -{ - switch (mask & (XNegative|YNegative)) { - case 0: - return NorthWestGravity; - case XNegative: - return NorthEastGravity; - case YNegative: - return SouthWestGravity; - } - - return SouthEastGravity; -} - -int -xloadfont(Font *f, FcPattern *pattern) -{ - FcPattern *configured; - FcPattern *match; - FcResult result; - XGlyphInfo extents; - int wantattr, haveattr; - - /* - * Manually configure instead of calling XftMatchFont - * so that we can use the configured pattern for - * "missing glyph" lookups. - */ - configured = FcPatternDuplicate(pattern); - if (!configured) - return 1; - - FcConfigSubstitute(NULL, configured, FcMatchPattern); - XftDefaultSubstitute(xw.dpy, xw.scr, configured); - - match = FcFontMatch(NULL, configured, &result); - if (!match) { - FcPatternDestroy(configured); - return 1; - } - - if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { - FcPatternDestroy(configured); - FcPatternDestroy(match); - return 1; - } - - if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == - XftResultMatch)) { - /* - * Check if xft was unable to find a font with the appropriate - * slant but gave us one anyway. Try to mitigate. - */ - if ((XftPatternGetInteger(f->match->pattern, "slant", 0, - &haveattr) != XftResultMatch) || haveattr < wantattr) { - f->badslant = 1; - fputs("font slant does not match\n", stderr); - } - } - - if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == - XftResultMatch)) { - if ((XftPatternGetInteger(f->match->pattern, "weight", 0, - &haveattr) != XftResultMatch) || haveattr != wantattr) { - f->badweight = 1; - fputs("font weight does not match\n", stderr); - } - } - - XftTextExtentsUtf8(xw.dpy, f->match, - (const FcChar8 *) ascii_printable, - strlen(ascii_printable), &extents); - - f->set = NULL; - f->pattern = configured; - - f->ascent = f->match->ascent; - f->descent = f->match->descent; - f->lbearing = 0; - f->rbearing = f->match->max_advance_width; - - f->height = f->ascent + f->descent; - f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); - - return 0; -} - -void -xloadfonts(const char *fontstr, double fontsize) -{ - FcPattern *pattern; - double fontval; - - if (fontstr[0] == '-') - pattern = XftXlfdParse(fontstr, False, False); - else - pattern = FcNameParse((const FcChar8 *)fontstr); - - if (!pattern) - die("can't open font %s\n", fontstr); - - if (fontsize > 1) { - FcPatternDel(pattern, FC_PIXEL_SIZE); - FcPatternDel(pattern, FC_SIZE); - FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); - usedfontsize = fontsize; - } else { - if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == - FcResultMatch) { - usedfontsize = fontval; - } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == - FcResultMatch) { - usedfontsize = -1; - } else { - /* - * Default font size is 12, if none given. This is to - * have a known usedfontsize value. - */ - FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); - usedfontsize = 12; - } - if (defaultfontsize <= 0) - defaultfontsize = usedfontsize; - } - - if (xloadfont(&dc.font, pattern)) - die("can't open font %s\n", fontstr); - - if (usedfontsize < 0) { - FcPatternGetDouble(dc.font.match->pattern, - FC_PIXEL_SIZE, 0, &fontval); - usedfontsize = fontval; - if (defaultfontsize <= 0 && fontsize == 0) - defaultfontsize = fontval; - } - - /* Setting character width and height. */ - win.cw = ceilf(dc.font.width * cwscale); - win.ch = ceilf(dc.font.height * chscale); - - FcPatternDel(pattern, FC_SLANT); - FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); - if (xloadfont(&dc.ifont, pattern)) - die("can't open font %s\n", fontstr); - - FcPatternDel(pattern, FC_WEIGHT); - FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); - if (xloadfont(&dc.ibfont, pattern)) - die("can't open font %s\n", fontstr); - - FcPatternDel(pattern, FC_SLANT); - FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); - if (xloadfont(&dc.bfont, pattern)) - die("can't open font %s\n", fontstr); - - FcPatternDestroy(pattern); -} - -void -xunloadfont(Font *f) -{ - XftFontClose(xw.dpy, f->match); - FcPatternDestroy(f->pattern); - if (f->set) - FcFontSetDestroy(f->set); -} - -void -xunloadfonts(void) -{ - /* Free the loaded fonts in the font cache. */ - while (frclen > 0) - XftFontClose(xw.dpy, frc[--frclen].font); - - xunloadfont(&dc.font); - xunloadfont(&dc.bfont); - xunloadfont(&dc.ifont); - xunloadfont(&dc.ibfont); -} - -int -ximopen(Display *dpy) -{ - XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; - XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; - - xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); - if (xw.ime.xim == NULL) - return 0; - - if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) - fprintf(stderr, "XSetIMValues: " - "Could not set XNDestroyCallback.\n"); - - xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, - NULL); - - if (xw.ime.xic == NULL) { - xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, - XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, xw.win, - XNDestroyCallback, &icdestroy, - NULL); - } - if (xw.ime.xic == NULL) - fprintf(stderr, "XCreateIC: Could not create input context.\n"); - - return 1; -} - -void -ximinstantiate(Display *dpy, XPointer client, XPointer call) -{ - if (ximopen(dpy)) - XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, - ximinstantiate, NULL); -} - -void -ximdestroy(XIM xim, XPointer client, XPointer call) -{ - xw.ime.xim = NULL; - XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, - ximinstantiate, NULL); - XFree(xw.ime.spotlist); -} - -int -xicdestroy(XIC xim, XPointer client, XPointer call) -{ - xw.ime.xic = NULL; - return 1; -} - -void -xinit(int cols, int rows) -{ - XGCValues gcvalues; - Cursor cursor; - Window parent, root; - pid_t thispid = getpid(); - XColor xmousefg, xmousebg; - - if (!(xw.dpy = XOpenDisplay(NULL))) - die("can't open display\n"); - xw.scr = XDefaultScreen(xw.dpy); - xw.vis = XDefaultVisual(xw.dpy, xw.scr); - - /* font */ - if (!FcInit()) - die("could not init fontconfig.\n"); - - usedfont = (opt_font == NULL)? font : opt_font; - xloadfonts(usedfont, 0); - - /* colors */ - xw.cmap = XDefaultColormap(xw.dpy, xw.scr); - xloadcols(); - - /* adjust fixed window geometry */ - win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw; - win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch; - if (xw.gm & XNegative) - xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; - if (xw.gm & YNegative) - xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; - - /* Events */ - xw.attrs.background_pixel = dc.col[defaultbg].pixel; - xw.attrs.border_pixel = dc.col[defaultbg].pixel; - xw.attrs.bit_gravity = NorthWestGravity; - xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask - | ExposureMask | VisibilityChangeMask | StructureNotifyMask - | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; - xw.attrs.colormap = xw.cmap; - - root = XRootWindow(xw.dpy, xw.scr); - if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) - parent = root; - xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, - win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, - xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity - | CWEventMask | CWColormap, &xw.attrs); - if (parent != root) - XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); - - memset(&gcvalues, 0, sizeof(gcvalues)); - gcvalues.graphics_exposures = False; - dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, - &gcvalues); - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - DefaultDepth(xw.dpy, xw.scr)); - XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); - XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); - - /* font spec buffer */ - xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); - - /* Xft rendering context */ - xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); - - /* input methods */ - if (!ximopen(xw.dpy)) { - XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, - ximinstantiate, NULL); - } - - /* white cursor, black outline */ - cursor = XCreateFontCursor(xw.dpy, mouseshape); - XDefineCursor(xw.dpy, xw.win, cursor); - - if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { - xmousefg.red = 0xffff; - xmousefg.green = 0xffff; - xmousefg.blue = 0xffff; - } - - if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { - xmousebg.red = 0x0000; - xmousebg.green = 0x0000; - xmousebg.blue = 0x0000; - } - - XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); - - xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); - xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); - xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); - xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); - XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); - - xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); - XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, - PropModeReplace, (uchar *)&thispid, 1); - - win.mode = MODE_NUMLOCK; - resettitle(); - xhints(); - XMapWindow(xw.dpy, xw.win); - XSync(xw.dpy, False); - - clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); - clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); - xsel.primary = NULL; - xsel.clipboard = NULL; - xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); - if (xsel.xtarget == None) - xsel.xtarget = XA_STRING; - - boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); -} - -int -xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) -{ - float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; - ushort mode, prevmode = USHRT_MAX; - Font *font = &dc.font; - int frcflags = FRC_NORMAL; - float runewidth = win.cw; - Rune rune; - FT_UInt glyphidx; - FcResult fcres; - FcPattern *fcpattern, *fontpattern; - FcFontSet *fcsets[] = { NULL }; - FcCharSet *fccharset; - int i, f, numspecs = 0; - - for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { - /* Fetch rune and mode for current glyph. */ - rune = glyphs[i].u; - mode = glyphs[i].mode; - - /* Skip dummy wide-character spacing. */ - if (mode == ATTR_WDUMMY) - continue; - - /* Draw spaces for image placeholders (images will be drawn - * separately). */ - if (mode & ATTR_IMAGE) - rune = ' '; - - /* Determine font for glyph if different from previous glyph. */ - if (prevmode != mode) { - prevmode = mode; - font = &dc.font; - frcflags = FRC_NORMAL; - runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); - if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { - font = &dc.ibfont; - frcflags = FRC_ITALICBOLD; - } else if (mode & ATTR_ITALIC) { - font = &dc.ifont; - frcflags = FRC_ITALIC; - } else if (mode & ATTR_BOLD) { - font = &dc.bfont; - frcflags = FRC_BOLD; - } - yp = winy + font->ascent; - } - - if (mode & ATTR_BOXDRAW) { - /* minor shoehorning: boxdraw uses only this ushort */ - glyphidx = boxdrawindex(&glyphs[i]); - } else { - /* Lookup character index with default font. */ - glyphidx = XftCharIndex(xw.dpy, font->match, rune); - } - if (glyphidx) { - specs[numspecs].font = font->match; - specs[numspecs].glyph = glyphidx; - specs[numspecs].x = (short)xp; - specs[numspecs].y = (short)yp; - xp += runewidth; - numspecs++; - continue; - } - - /* Fallback on font cache, search the font cache for match. */ - for (f = 0; f < frclen; f++) { - glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); - /* Everything correct. */ - if (glyphidx && frc[f].flags == frcflags) - break; - /* We got a default font for a not found glyph. */ - if (!glyphidx && frc[f].flags == frcflags - && frc[f].unicodep == rune) { - break; - } - } - - /* Nothing was found. Use fontconfig to find matching font. */ - if (f >= frclen) { - if (!font->set) - font->set = FcFontSort(0, font->pattern, - 1, 0, &fcres); - fcsets[0] = font->set; - - /* - * Nothing was found in the cache. Now use - * some dozen of Fontconfig calls to get the - * font for one single character. - * - * Xft and fontconfig are design failures. - */ - fcpattern = FcPatternDuplicate(font->pattern); - fccharset = FcCharSetCreate(); - - FcCharSetAddChar(fccharset, rune); - FcPatternAddCharSet(fcpattern, FC_CHARSET, - fccharset); - FcPatternAddBool(fcpattern, FC_SCALABLE, 1); - - FcConfigSubstitute(0, fcpattern, - FcMatchPattern); - FcDefaultSubstitute(fcpattern); - - fontpattern = FcFontSetMatch(0, fcsets, 1, - fcpattern, &fcres); - - /* Allocate memory for the new cache entry. */ - if (frclen >= frccap) { - frccap += 16; - frc = xrealloc(frc, frccap * sizeof(Fontcache)); - } - - frc[frclen].font = XftFontOpenPattern(xw.dpy, - fontpattern); - if (!frc[frclen].font) - die("XftFontOpenPattern failed seeking fallback font: %s\n", - strerror(errno)); - frc[frclen].flags = frcflags; - frc[frclen].unicodep = rune; - - glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); - - f = frclen; - frclen++; - - FcPatternDestroy(fcpattern); - FcCharSetDestroy(fccharset); - } - - specs[numspecs].font = frc[f].font; - specs[numspecs].glyph = glyphidx; - specs[numspecs].x = (short)xp; - specs[numspecs].y = (short)yp; - xp += runewidth; - numspecs++; - } - - return numspecs; -} - -/* Draws a horizontal dashed line of length `w` starting at `(x, y)`. `wavelen` - * is the length of the dash plus the length of the gap. `fraction` is the - * fraction of the dash length compared to `wavelen`. */ -static void -xdrawunderdashed(Draw draw, Color *color, int x, int y, int w, - int wavelen, float fraction, int thick) -{ - int dashw = MAX(1, fraction * wavelen); - for (int i = x - x % wavelen; i < x + w; i += wavelen) { - int startx = MAX(i, x); - int endx = MIN(i + dashw, x + w); - if (startx < endx) - XftDrawRect(xw.draw, color, startx, y, endx - startx, - thick); - } -} - -/* Draws an undercurl. `h` is the total height, including line thickness. */ -static void -xdrawundercurl(Draw draw, Color *color, int x, int y, int w, int h, int thick) -{ - XGCValues gcvals = {.foreground = color->pixel, - .line_width = thick, - .line_style = LineSolid, - .cap_style = CapRound}; - GC gc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw), - GCForeground | GCLineWidth | GCLineStyle | GCCapStyle, - &gcvals); - - XRectangle clip = {.x = x, .y = y, .width = w, .height = h}; - XSetClipRectangles(xw.dpy, gc, 0, 0, &clip, 1, Unsorted); - - int yoffset = thick / 2; - int segh = MAX(1, h - thick); - /* Make sure every segment is at a 45 degree angle, otherwise it doesn't - * look good without antialiasing. */ - int segw = segh; - int wavelen = MAX(1, segw * 2); - - for (int i = x - (x % wavelen); i < x + w; i += wavelen) { - XPoint points[3] = {{.x = i, .y = y + yoffset}, - {.x = i + segw, .y = y + yoffset + segh}, - {.x = i + wavelen, .y = y + yoffset}}; - XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), gc, points, 3, - CoordModeOrigin); - } - - XFreeGC(xw.dpy, gc); -} - -void -xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) -{ - int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); - int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, - width = charlen * win.cw; - Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; - XRenderColor colfg, colbg; - XRectangle r; - - /* Fallback on color display for attributes not supported by the font */ - if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { - if (dc.ibfont.badslant || dc.ibfont.badweight) - base.fg = defaultattr; - } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || - (base.mode & ATTR_BOLD && dc.bfont.badweight)) { - base.fg = defaultattr; - } - - if (IS_TRUECOL(base.fg)) { - colfg.alpha = 0xffff; - colfg.red = TRUERED(base.fg); - colfg.green = TRUEGREEN(base.fg); - colfg.blue = TRUEBLUE(base.fg); - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); - fg = &truefg; - } else { - fg = &dc.col[base.fg]; - } - - if (IS_TRUECOL(base.bg)) { - colbg.alpha = 0xffff; - colbg.green = TRUEGREEN(base.bg); - colbg.red = TRUERED(base.bg); - colbg.blue = TRUEBLUE(base.bg); - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); - bg = &truebg; - } else { - bg = &dc.col[base.bg]; - } - - /* Change basic system colors [0-7] to bright system colors [8-15] */ - if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) - fg = &dc.col[base.fg + 8]; - - if (IS_SET(MODE_REVERSE)) { - if (fg == &dc.col[defaultfg]) { - fg = &dc.col[defaultbg]; - } else { - colfg.red = ~fg->color.red; - colfg.green = ~fg->color.green; - colfg.blue = ~fg->color.blue; - colfg.alpha = fg->color.alpha; - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, - &revfg); - fg = &revfg; - } - - if (bg == &dc.col[defaultbg]) { - bg = &dc.col[defaultfg]; - } else { - colbg.red = ~bg->color.red; - colbg.green = ~bg->color.green; - colbg.blue = ~bg->color.blue; - colbg.alpha = bg->color.alpha; - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, - &revbg); - bg = &revbg; - } - } - - if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { - colfg.red = fg->color.red / 2; - colfg.green = fg->color.green / 2; - colfg.blue = fg->color.blue / 2; - colfg.alpha = fg->color.alpha; - XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); - fg = &revfg; - } - - if (base.mode & ATTR_REVERSE) { - temp = fg; - fg = bg; - bg = temp; - } - - if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) - fg = bg; - - if (base.mode & ATTR_INVISIBLE) - fg = bg; - - /* Intelligent cleaning up of the borders. */ - if (x == 0) { - xclear(0, (y == 0)? 0 : winy, win.hborderpx, - winy + win.ch + - ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); - } - if (winx + width >= win.hborderpx + win.tw) { - xclear(winx + width, (y == 0)? 0 : winy, win.w, - ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); - } - if (y == 0) - xclear(winx, 0, winx + width, win.vborderpx); - if (winy + win.ch >= win.vborderpx + win.th) - xclear(winx, winy + win.ch, winx + width, win.h); - - /* Clean up the region we want to draw to. */ - XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); - - /* Set the clip region because Xft is sometimes dirty. */ - r.x = 0; - r.y = 0; - r.height = win.ch; - r.width = width; - XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); - - if (base.mode & ATTR_BOXDRAW) { - drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); - } else { - /* Render the glyphs. */ - XftDrawGlyphFontSpec(xw.draw, fg, specs, len); - } - - /* Render underline and strikethrough. */ - if (base.mode & ATTR_UNDERLINE) { - XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, - width, 1); - } - - if (base.mode & ATTR_STRUCK) { - XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, - width, 1); - } - - /* Reset clip to none. */ - XftDrawSetClip(xw.draw, 0); -} - -void -xdrawglyph(Glyph g, int x, int y) -{ - int numspecs; - XftGlyphFontSpec spec; - - numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); - xdrawglyphfontspecs(&spec, g, numspecs, x, y); - if (g.mode & ATTR_IMAGE) { - gr_start_drawing(xw.buf, win.cw, win.ch); - xdrawoneimagecell(g, x, y); - gr_finish_drawing(xw.buf); - } -} - -void -xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) -{ - Color drawcol; - - /* remove the old cursor */ - if (selected(ox, oy)) - og.mode ^= ATTR_REVERSE; - xdrawglyph(og, ox, oy); - - if (IS_SET(MODE_HIDE)) - return; - - // If it's an image, just draw a ballot box for simplicity. - if (g.mode & ATTR_IMAGE) - g.u = 0x2610; - - /* - * Select the right color for the right mode. - */ - g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; - - if (IS_SET(MODE_REVERSE)) { - g.mode |= ATTR_REVERSE; - g.bg = defaultfg; - if (selected(cx, cy)) { - drawcol = dc.col[defaultcs]; - g.fg = defaultrcs; - } else { - drawcol = dc.col[defaultrcs]; - g.fg = defaultcs; - } - } else { - if (selected(cx, cy)) { - g.fg = defaultfg; - g.bg = defaultrcs; - } else { - g.fg = defaultbg; - g.bg = defaultcs; - } - drawcol = dc.col[g.bg]; - } - - /* draw the new one */ - if (IS_SET(MODE_FOCUSED)) { - switch (win.cursor) { - case 7: /* st extension */ - g.u = 0x2603; /* snowman (U+2603) */ - /* FALLTHROUGH */ - case 0: /* Blinking Block */ - case 1: /* Blinking Block (Default) */ - case 2: /* Steady Block */ - xdrawglyph(g, cx, cy); - break; - case 3: /* Blinking Underline */ - case 4: /* Steady Underline */ - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + (cy + 1) * win.ch - \ - cursorthickness, - win.cw, cursorthickness); - break; - case 5: /* Blinking bar */ - case 6: /* Steady bar */ - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + cy * win.ch, - cursorthickness, win.ch); - break; - } - } else { - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + cy * win.ch, - win.cw - 1, 1); - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + cy * win.ch, - 1, win.ch - 1); - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + (cx + 1) * win.cw - 1, - win.vborderpx + cy * win.ch, - 1, win.ch - 1); - XftDrawRect(xw.draw, &drawcol, - win.hborderpx + cx * win.cw, - win.vborderpx + (cy + 1) * win.ch - 1, - win.cw, 1); - } -} - -/* Draw (or queue for drawing) image cells between columns x1 and x2 assuming - * that they have the same attributes (and thus the same lower 24 bits of the - * image ID and the same placement ID). */ -void -xdrawimages(Glyph base, Line line, int x1, int y1, int x2) { - int y_pix = win.vborderpx + y1 * win.ch; - uint32_t image_id_24bits = base.fg & 0xFFFFFF; - uint32_t placement_id = tgetimgplacementid(&base); - // Columns and rows are 1-based, 0 means unspecified. - int last_col = 0; - int last_row = 0; - int last_start_col = 0; - int last_start_x = x1; - // The most significant byte is also 1-base, subtract 1 before use. - uint32_t last_id_4thbyteplus1 = 0; - // We may need to inherit row/column/4th byte from the previous cell. - Glyph *prev = &line[x1 - 1]; - if (x1 > 0 && (prev->mode & ATTR_IMAGE) && - (prev->fg & 0xFFFFFF) == image_id_24bits && - prev->decor == base.decor) { - last_row = tgetimgrow(prev); - last_col = tgetimgcol(prev); - last_id_4thbyteplus1 = tgetimgid4thbyteplus1(prev); - last_start_col = last_col + 1; - } - for (int x = x1; x < x2; ++x) { - Glyph *g = &line[x]; - uint32_t cur_row = tgetimgrow(g); - uint32_t cur_col = tgetimgcol(g); - uint32_t cur_id_4thbyteplus1 = tgetimgid4thbyteplus1(g); - uint32_t num_diacritics = tgetimgdiacriticcount(g); - // If the row is not specified, assume it's the same as the row - // of the previous cell. Note that `cur_row` may contain a - // value imputed earlier, which will be preserved if `last_row` - // is zero (i.e. we don't know the row of the previous cell). - if (last_row && (num_diacritics == 0 || !cur_row)) - cur_row = last_row; - // If the column is not specified and the row is the same as the - // row of the previous cell, then assume that the column is the - // next one. - if (last_col && (num_diacritics <= 1 || !cur_col) && - cur_row == last_row) - cur_col = last_col + 1; - // If the additional id byte is not specified and the - // coordinates are consecutive, assume the byte is also the - // same. - if (last_id_4thbyteplus1 && - (num_diacritics <= 2 || !cur_id_4thbyteplus1) && - cur_row == last_row && cur_col == last_col + 1) - cur_id_4thbyteplus1 = last_id_4thbyteplus1; - // If we couldn't infer row and column, start from the top left - // corner. - if (cur_row == 0) - cur_row = 1; - if (cur_col == 0) - cur_col = 1; - // If this cell breaks a contiguous stripe of image cells, draw - // that line and start a new one. - if (cur_col != last_col + 1 || cur_row != last_row || - cur_id_4thbyteplus1 != last_id_4thbyteplus1) { - uint32_t image_id = image_id_24bits; - if (last_id_4thbyteplus1) - image_id |= (last_id_4thbyteplus1 - 1) << 24; - if (last_row != 0) { - int x_pix = - win.hborderpx + last_start_x * win.cw; - gr_append_imagerect( - xw.buf, image_id, placement_id, - last_start_col - 1, last_col, - last_row - 1, last_row, last_start_x, - y1, x_pix, y_pix, win.cw, win.ch, - base.mode & ATTR_REVERSE); - } - last_start_col = cur_col; - last_start_x = x; - } - last_row = cur_row; - last_col = cur_col; - last_id_4thbyteplus1 = cur_id_4thbyteplus1; - // Populate the missing glyph data to enable inheritance between - // runs and support the naive implementation of tgetimgid. - if (!tgetimgrow(g)) - tsetimgrow(g, cur_row); - // We cannot save this information if there are > 511 cols. - if (!tgetimgcol(g) && (cur_col & ~0x1ff) == 0) - tsetimgcol(g, cur_col); - if (!tgetimgid4thbyteplus1(g)) - tsetimg4thbyteplus1(g, cur_id_4thbyteplus1); - } - uint32_t image_id = image_id_24bits; - if (last_id_4thbyteplus1) - image_id |= (last_id_4thbyteplus1 - 1) << 24; - // Draw the last contiguous stripe. - if (last_row != 0) { - int x_pix = win.hborderpx + last_start_x * win.cw; - gr_append_imagerect(xw.buf, image_id, placement_id, - last_start_col - 1, last_col, last_row - 1, - last_row, last_start_x, y1, x_pix, y_pix, - win.cw, win.ch, base.mode & ATTR_REVERSE); - } -} - -/* Draw just one image cell without inheriting attributes from the left. */ -void xdrawoneimagecell(Glyph g, int x, int y) { - if (!(g.mode & ATTR_IMAGE)) - return; - int x_pix = win.hborderpx + x * win.cw; - int y_pix = win.vborderpx + y * win.ch; - uint32_t row = tgetimgrow(&g) - 1; - uint32_t col = tgetimgcol(&g) - 1; - uint32_t placement_id = tgetimgplacementid(&g); - uint32_t image_id = tgetimgid(&g); - gr_append_imagerect(xw.buf, image_id, placement_id, col, col + 1, row, - row + 1, x, y, x_pix, y_pix, win.cw, win.ch, - g.mode & ATTR_REVERSE); -} - -/* Prepare for image drawing. */ -void xstartimagedraw(int *dirty, int rows) { - gr_start_drawing(xw.buf, win.cw, win.ch); - gr_mark_dirty_animations(dirty, rows); -} - -/* Draw all queued image cells. */ -void xfinishimagedraw() { - gr_finish_drawing(xw.buf); -} - -void -xsetenv(void) -{ - char buf[sizeof(long) * 8 + 1]; - - snprintf(buf, sizeof(buf), "%lu", xw.win); - setenv("WINDOWID", buf, 1); -} - -void -xseticontitle(char *p) -{ - XTextProperty prop; - DEFAULT(p, opt_title); - - if (p[0] == '\0') - p = opt_title; - - if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, - &prop) != Success) - return; - XSetWMIconName(xw.dpy, xw.win, &prop); - XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); - XFree(prop.value); -} - -void -xsettitle(char *p) -{ - XTextProperty prop; - DEFAULT(p, opt_title); - - if (p[0] == '\0') - p = opt_title; - - if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, - &prop) != Success) - return; - XSetWMName(xw.dpy, xw.win, &prop); - XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); - XFree(prop.value); -} - -int -xstartdraw(void) -{ - return IS_SET(MODE_VISIBLE); -} - -void -xdrawline(Line line, int x1, int y1, int x2) -{ - int i, x, ox, numspecs; - Glyph base, new; - XftGlyphFontSpec *specs = xw.specbuf; - - numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); - i = ox = 0; - for (x = x1; x < x2 && i < numspecs; x++) { - new = line[x]; - if (new.mode == ATTR_WDUMMY) - continue; - if (selected(x, y1)) - new.mode ^= ATTR_REVERSE; - if (i > 0 && ATTRCMP(base, new)) { - xdrawglyphfontspecs(specs, base, i, ox, y1); - if (base.mode & ATTR_IMAGE) - xdrawimages(base, line, ox, y1, x); - specs += i; - numspecs -= i; - i = 0; - } - if (i == 0) { - ox = x; - base = new; - } - i++; - } - if (i > 0) - xdrawglyphfontspecs(specs, base, i, ox, y1); - if (i > 0 && base.mode & ATTR_IMAGE) - xdrawimages(base, line, ox, y1, x); -} - -void -xfinishdraw(void) -{ - XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, - win.h, 0, 0); - XSetForeground(xw.dpy, dc.gc, - dc.col[IS_SET(MODE_REVERSE)? - defaultfg : defaultbg].pixel); -} - -void -xximspot(int x, int y) -{ - if (xw.ime.xic == NULL) - return; - - xw.ime.spot.x = borderpx + x * win.cw; - xw.ime.spot.y = borderpx + (y + 1) * win.ch; - - XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); -} - -void -expose(XEvent *ev) -{ - redraw(); -} - -void -visibility(XEvent *ev) -{ - XVisibilityEvent *e = &ev->xvisibility; - - MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); -} - -void -unmap(XEvent *ev) -{ - win.mode &= ~MODE_VISIBLE; -} - -void -xsetpointermotion(int set) -{ - MODBIT(xw.attrs.event_mask, set, PointerMotionMask); - XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); -} - -void -xsetmode(int set, unsigned int flags) -{ - int mode = win.mode; - MODBIT(win.mode, set, flags); - if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) - redraw(); -} - -int -xsetcursor(int cursor) -{ - if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ - return 1; - win.cursor = cursor; - return 0; -} - -void -xseturgency(int add) -{ - XWMHints *h = XGetWMHints(xw.dpy, xw.win); - - MODBIT(h->flags, add, XUrgencyHint); - XSetWMHints(xw.dpy, xw.win, h); - XFree(h); -} - -void -xbell(void) -{ - if (!(IS_SET(MODE_FOCUSED))) - xseturgency(1); - if (bellvolume) - XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); -} - -void -focus(XEvent *ev) -{ - XFocusChangeEvent *e = &ev->xfocus; - - if (e->mode == NotifyGrab) - return; - - if (ev->type == FocusIn) { - if (xw.ime.xic) - XSetICFocus(xw.ime.xic); - win.mode |= MODE_FOCUSED; - xseturgency(0); - if (IS_SET(MODE_FOCUS)) - ttywrite("\033[I", 3, 0); - } else { - if (xw.ime.xic) - XUnsetICFocus(xw.ime.xic); - win.mode &= ~MODE_FOCUSED; - if (IS_SET(MODE_FOCUS)) - ttywrite("\033[O", 3, 0); - } -} - -int -match(uint mask, uint state) -{ - return mask == XK_ANY_MOD || mask == (state & ~ignoremod); -} - -char* -kmap(KeySym k, uint state) -{ - Key *kp; - int i; - - /* Check for mapped keys out of X11 function keys. */ - for (i = 0; i < LEN(mappedkeys); i++) { - if (mappedkeys[i] == k) - break; - } - if (i == LEN(mappedkeys)) { - if ((k & 0xFFFF) < 0xFD00) - return NULL; - } - - for (kp = key; kp < key + LEN(key); kp++) { - if (kp->k != k) - continue; - - if (!match(kp->mask, state)) - continue; - - if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) - continue; - if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) - continue; - - if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) - continue; - - return kp->s; - } - - return NULL; -} - -void -kpress(XEvent *ev) -{ - XKeyEvent *e = &ev->xkey; - KeySym ksym = NoSymbol; - char buf[64], *customkey; - int len; - Rune c; - Status status; - Shortcut *bp; - - if (IS_SET(MODE_KBDLOCK)) - return; - - if (xw.ime.xic) { - len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); - if (status == XBufferOverflow) - return; - } else { - len = XLookupString(e, buf, sizeof buf, &ksym, NULL); - } - /* 1. shortcuts */ - for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { - if (ksym == bp->keysym && match(bp->mod, e->state)) { - bp->func(&(bp->arg)); - return; - } - } - - /* 2. custom keys from config.h */ - if ((customkey = kmap(ksym, e->state))) { - ttywrite(customkey, strlen(customkey), 1); - return; - } - - /* 3. composed string from input method */ - if (len == 0) - return; - if (len == 1 && e->state & Mod1Mask) { - if (IS_SET(MODE_8BIT)) { - if (*buf < 0177) { - c = *buf | 0x80; - len = utf8encode(c, buf); - } - } else { - buf[1] = buf[0]; - buf[0] = '\033'; - len = 2; - } - } - ttywrite(buf, len, 1); -} - -void -cmessage(XEvent *e) -{ - /* - * See xembed specs - * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html - */ - if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { - if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { - win.mode |= MODE_FOCUSED; - xseturgency(0); - } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { - win.mode &= ~MODE_FOCUSED; - } - } else if (e->xclient.data.l[0] == xw.wmdeletewin) { - ttyhangup(); - gr_deinit(); - exit(0); - } -} - -void -resize(XEvent *e) -{ - if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) - return; - - cresize(e->xconfigure.width, e->xconfigure.height); -} - -void -run(void) -{ - XEvent ev; - int w = win.w, h = win.h; - fd_set rfd; - int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; - struct timespec seltv, *tv, now, lastblink, trigger; - double timeout; - - /* Waiting for window mapping */ - do { - XNextEvent(xw.dpy, &ev); - /* - * This XFilterEvent call is required because of XOpenIM. It - * does filter out the key event and some client message for - * the input method too. - */ - if (XFilterEvent(&ev, None)) - continue; - if (ev.type == ConfigureNotify) { - w = ev.xconfigure.width; - h = ev.xconfigure.height; - } - } while (ev.type != MapNotify); - - ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); - cresize(w, h); - - for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { - FD_ZERO(&rfd); - FD_SET(ttyfd, &rfd); - FD_SET(xfd, &rfd); - - if (XPending(xw.dpy)) - timeout = 0; /* existing events might not set xfd */ - - /* Decrease the timeout if there are active animations. */ - if (graphics_next_redraw_delay != INT_MAX && - IS_SET(MODE_VISIBLE)) - timeout = timeout < 0 ? graphics_next_redraw_delay - : MIN(timeout, - graphics_next_redraw_delay); - - seltv.tv_sec = timeout / 1E3; - seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); - tv = timeout >= 0 ? &seltv : NULL; - - if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { - if (errno == EINTR) - continue; - die("select failed: %s\n", strerror(errno)); - } - clock_gettime(CLOCK_MONOTONIC, &now); - - if (FD_ISSET(ttyfd, &rfd)) - ttyread(); - - xev = 0; - while (XPending(xw.dpy)) { - xev = 1; - XNextEvent(xw.dpy, &ev); - if (XFilterEvent(&ev, None)) - continue; - if (handler[ev.type]) - (handler[ev.type])(&ev); - } - - /* - * To reduce flicker and tearing, when new content or event - * triggers drawing, we first wait a bit to ensure we got - * everything, and if nothing new arrives - we draw. - * We start with trying to wait minlatency ms. If more content - * arrives sooner, we retry with shorter and shorter periods, - * and eventually draw even without idle after maxlatency ms. - * Typically this results in low latency while interacting, - * maximum latency intervals during `cat huge.txt`, and perfect - * sync with periodic updates from animations/key-repeats/etc. - */ - if (FD_ISSET(ttyfd, &rfd) || xev) { - if (!drawing) { - trigger = now; - drawing = 1; - } - timeout = (maxlatency - TIMEDIFF(now, trigger)) \ - / maxlatency * minlatency; - if (timeout > 0) - continue; /* we have time, try to find idle */ - } - - /* idle detected or maxlatency exhausted -> draw */ - timeout = -1; - if (blinktimeout && tattrset(ATTR_BLINK)) { - timeout = blinktimeout - TIMEDIFF(now, lastblink); - if (timeout <= 0) { - if (-timeout > blinktimeout) /* start visible */ - win.mode |= MODE_BLINK; - win.mode ^= MODE_BLINK; - tsetdirtattr(ATTR_BLINK); - lastblink = now; - timeout = blinktimeout; - } - } - - draw(); - XFlush(xw.dpy); - drawing = 0; - } -} - -void -usage(void) -{ - die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" - " [-n name] [-o file]\n" - " [-T title] [-t title] [-w windowid]" - " [[-e] command [args ...]]\n" - " %s [-aiv] [-c class] [-f font] [-g geometry]" - " [-n name] [-o file]\n" - " [-T title] [-t title] [-w windowid] -l line" - " [stty_args ...]\n", argv0, argv0); -} - -int -main(int argc, char *argv[]) -{ - xw.l = xw.t = 0; - xw.isfixed = False; - xsetcursor(cursorshape); - - ARGBEGIN { - case 'a': - allowaltscreen = 0; - break; - case 'c': - opt_class = EARGF(usage()); - break; - case 'e': - if (argc > 0) - --argc, ++argv; - goto run; - case 'f': - opt_font = EARGF(usage()); - break; - case 'g': - xw.gm = XParseGeometry(EARGF(usage()), - &xw.l, &xw.t, &cols, &rows); - break; - case 'i': - xw.isfixed = 1; - break; - case 'o': - opt_io = EARGF(usage()); - break; - case 'l': - opt_line = EARGF(usage()); - break; - case 'n': - opt_name = EARGF(usage()); - break; - case 't': - case 'T': - opt_title = EARGF(usage()); - break; - case 'w': - opt_embed = EARGF(usage()); - break; - case 'v': - die("%s " VERSION "\n", argv0); - break; - default: - usage(); - } ARGEND; - -run: - if (argc > 0) /* eat all remaining arguments */ - opt_cmd = argv; - - if (!opt_title) - opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; - - setlocale(LC_CTYPE, ""); - XSetLocaleModifiers(""); - cols = MAX(cols, 1); - rows = MAX(rows, 1); - tnew(cols, rows); - xinit(cols, rows); - xsetenv(); - selinit(); - run(); - - return 0; -} diff --git a/files/config/suckless/st/x.c.orig b/files/config/suckless/st/x.c.orig index 7157e56..d73152b 100644 --- a/files/config/suckless/st/x.c.orig +++ b/files/config/suckless/st/x.c.orig @@ -1240,8 +1240,6 @@ xinit(int cols, int rows) xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); if (xsel.xtarget == None) xsel.xtarget = XA_STRING; - - boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); } int @@ -1288,13 +1286,8 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x yp = winy + font->ascent; } - if (mode & ATTR_BOXDRAW) { - /* minor shoehorning: boxdraw uses only this ushort */ - glyphidx = boxdrawindex(&glyphs[i]); - } else { - /* Lookup character index with default font. */ - glyphidx = XftCharIndex(xw.dpy, font->match, rune); - } + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); if (glyphidx) { specs[numspecs].font = font->match; specs[numspecs].glyph = glyphidx; @@ -1498,12 +1491,8 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i r.width = width; XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); - if (base.mode & ATTR_BOXDRAW) { - drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); - } else { - /* Render the glyphs. */ - XftDrawGlyphFontSpec(xw.draw, fg, specs, len); - } + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); /* Render underline and strikethrough. */ if (base.mode & ATTR_UNDERLINE) { @@ -1546,7 +1535,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) /* * Select the right color for the right mode. */ - g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; if (IS_SET(MODE_REVERSE)) { g.mode |= ATTR_REVERSE; diff --git a/files/config/suckless/st/x.c.rej b/files/config/suckless/st/x.c.rej deleted file mode 100644 index fe46ef1..0000000 --- a/files/config/suckless/st/x.c.rej +++ /dev/null @@ -1,96 +0,0 @@ ---- x.c -+++ x.c -@@ -1322,12 +1404,15 @@ xinit(int cols, int rows) - xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); - if (xsel.xtarget == None) - xsel.xtarget = XA_STRING; -+ -+ // Initialize the graphics (image display) module. -+ gr_init(xw.dpy, xw.vis, xw.cmap); - } - - int - xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) - { -- float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; -+ float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp; - ushort mode, prevmode = USHRT_MAX; - Font *font = &dc.font; - int frcflags = FRC_NORMAL; -@@ -1628,18 +1768,68 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i - r.width = width; - XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); - -- /* Render the glyphs. */ -- XftDrawGlyphFontSpec(xw.draw, fg, specs, len); -- -- /* Render underline and strikethrough. */ -+ /* Decoration color. */ -+ Color decor; -+ uint32_t decorcolor = tgetdecorcolor(&base); -+ if (decorcolor == DECOR_DEFAULT_COLOR) { -+ decor = *fg; -+ } else if (IS_TRUECOL(decorcolor)) { -+ colfg.alpha = 0xffff; -+ colfg.red = TRUERED(decorcolor); -+ colfg.green = TRUEGREEN(decorcolor); -+ colfg.blue = TRUEBLUE(decorcolor); -+ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &decor); -+ } else { -+ decor = dc.col[decorcolor]; -+ } -+ decor.color.alpha = 0xffff; -+ decor.pixel |= 0xff << 24; -+ -+ /* Float thickness, used as a base to compute other values. */ -+ float fthick = dc.font.height / 18.0; -+ /* Integer thickness in pixels. Must not be 0. */ -+ int thick = MAX(1, roundf(fthick)); -+ /* The default gap between the baseline and a single underline. */ -+ int gap = roundf(fthick * 2); -+ /* The total thickness of a double underline. */ -+ int doubleh = thick * 2 + ceilf(fthick * 0.5); -+ /* The total thickness of an undercurl. */ -+ int curlh = thick * 2 + roundf(fthick * 0.75); -+ -+ /* Render the underline before the glyphs. */ - if (base.mode & ATTR_UNDERLINE) { -- XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, -- width, 1); -+ uint32_t style = tgetdecorstyle(&base); -+ int liney = winy + dc.font.ascent + gap; -+ /* Adjust liney to guarantee that a single underline fits. */ -+ liney -= MAX(0, liney + thick - (winy + win.ch)); -+ if (style == UNDERLINE_DOUBLE) { -+ liney -= MAX(0, liney + doubleh - (winy + win.ch)); -+ XftDrawRect(xw.draw, &decor, winx, liney, width, thick); -+ XftDrawRect(xw.draw, &decor, winx, -+ liney + doubleh - thick, width, thick); -+ } else if (style == UNDERLINE_DOTTED) { -+ xdrawunderdashed(xw.draw, &decor, winx, liney, width, -+ thick * 2, 0.5, thick); -+ } else if (style == UNDERLINE_DASHED) { -+ int wavelen = MAX(2, win.cw * 0.9); -+ xdrawunderdashed(xw.draw, &decor, winx, liney, width, -+ wavelen, 0.65, thick); -+ } else if (style == UNDERLINE_CURLY) { -+ liney -= MAX(0, liney + curlh - (winy + win.ch)); -+ xdrawundercurl(xw.draw, &decor, winx, liney, width, -+ curlh, thick); -+ } else { -+ XftDrawRect(xw.draw, &decor, winx, liney, width, thick); -+ } - } - -+ /* Render the glyphs. */ -+ XftDrawGlyphFontSpec(xw.draw, fg, specs, len); -+ -+ /* Render strikethrough. Alway use the fg color. */ - if (base.mode & ATTR_STRUCK) { -- XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, -- width, 1); -+ XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, -+ width, thick); - } - - /* Reset clip to none. */ diff --git a/files/config/suckless/st/x.o b/files/config/suckless/st/x.o Binary files differnew file mode 100644 index 0000000..d8e3556 --- /dev/null +++ b/files/config/suckless/st/x.o diff --git a/files/doas/vidoas b/files/doas/vidoas new file mode 100644 index 0000000..11ff80c --- /dev/null +++ b/files/doas/vidoas @@ -0,0 +1,17 @@ +#!/bin/dash + +DOASDIR="/tmp/doas-$(date +%s)" +mkdir $DOASDIR +chmod 700 $DOASDIR +DOASFILE="$DOASDIR/doas.conf" + +cp /etc/doas.conf $DOASFILE +chmod 600 $DOASFILE + +nvim $DOASFILE +sync + +doas -C $DOASFILE && echo "valid config" && cp $DOASFILE /etc/doas.conf && chmod 400 /etc/doas.conf || echo "invalid config" +sync + +rm -rf $DOASDIR diff --git a/files/doas/vidoas1 b/files/doas/vidoas1 new file mode 100644 index 0000000..9006eb6 --- /dev/null +++ b/files/doas/vidoas1 @@ -0,0 +1,6 @@ +#!/bin/dash +if [ "$(id -u)" != 0 ]; then + doas /root/script/vidoas +else + /root/script/vidoas +fi |
