summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShipwreckt <me@shipwreckt.co.uk>2025-10-22 22:23:00 +0100
committerShipwreckt <me@shipwreckt.co.uk>2025-10-22 22:23:00 +0100
commit1ae24861a06d773836fb674814aa03df90bbb095 (patch)
tree23c9ae7a23593be5aa1d78b1cb0d9b0cc7a255a1
parente63a16b509b05993fc7900b6296ba8601e343976 (diff)
Updated install script, some configs, added vimwiki, added doas def
-rw-r--r--README.md3
-rwxr-xr-xautoinstall.sh56
-rw-r--r--files/config/nvim/init.vim13
m---------files/config/nvim/plug/vimwiki0
-rw-r--r--files/config/suckless/dwm/config.h16
-rwxr-xr-xfiles/config/suckless/dwm/dwmbin67936 -> 67936 bytes
-rw-r--r--files/config/suckless/dwm/dwm.obin60176 -> 60248 bytes
-rw-r--r--files/config/suckless/st/Makefile.orig3
-rw-r--r--files/config/suckless/st/Makefile.rej20
-rw-r--r--files/config/suckless/st/boxdraw.obin0 -> 6160 bytes
-rw-r--r--files/config/suckless/st/config.def.h42
-rw-r--r--files/config/suckless/st/config.def.h.orig488
-rw-r--r--files/config/suckless/st/config.def.h.rej13
-rw-r--r--files/config/suckless/st/config.mk5
-rw-r--r--files/config/suckless/st/graphics.c3812
-rw-r--r--files/config/suckless/st/graphics.h107
-rwxr-xr-xfiles/config/suckless/st/icat-mini.sh801
-rw-r--r--files/config/suckless/st/khash.h627
-rw-r--r--files/config/suckless/st/kvec.h90
-rw-r--r--files/config/suckless/st/patches/st-kitty-graphics-20240922-a0274bc.diff7324
-rw-r--r--files/config/suckless/st/rowcolumn_diacritics_helpers.c391
-rwxr-xr-xfiles/config/suckless/st/stbin0 -> 110208 bytes
-rw-r--r--files/config/suckless/st/st.c268
-rw-r--r--files/config/suckless/st/st.c.orig3
-rw-r--r--files/config/suckless/st/st.c.rej27
-rw-r--r--files/config/suckless/st/st.h23
-rw-r--r--files/config/suckless/st/st.h.orig12
-rw-r--r--files/config/suckless/st/st.h.rej72
-rw-r--r--files/config/suckless/st/st.info6
-rw-r--r--files/config/suckless/st/st.obin0 -> 81376 bytes
-rw-r--r--files/config/suckless/st/win.h3
-rw-r--r--files/config/suckless/st/x.c3872
-rw-r--r--files/config/suckless/st/x.c.bk2405
-rw-r--r--files/config/suckless/st/x.c.orig21
-rw-r--r--files/config/suckless/st/x.c.rej96
-rw-r--r--files/config/suckless/st/x.obin0 -> 76184 bytes
-rw-r--r--files/doas/vidoas17
-rw-r--r--files/doas/vidoas16
38 files changed, 1867 insertions, 18775 deletions
diff --git a/README.md b/README.md
index dbd6638..e390783 100644
--- a/README.md
+++ b/README.md
@@ -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
index 9ef9b4f..8c61d6d 100755
--- a/files/config/suckless/dwm/dwm
+++ b/files/config/suckless/dwm/dwm
Binary files differ
diff --git a/files/config/suckless/dwm/dwm.o b/files/config/suckless/dwm/dwm.o
index 2d17a7a..a53d670 100644
--- a/files/config/suckless/dwm/dwm.o
+++ b/files/config/suckless/dwm/dwm.o
Binary files differ
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
new file mode 100644
index 0000000..4128dba
--- /dev/null
+++ b/files/config/suckless/st/boxdraw.o
Binary files differ
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
new file mode 100755
index 0000000..6851bee
--- /dev/null
+++ b/files/config/suckless/st/st
Binary files differ
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
new file mode 100644
index 0000000..0f75b8d
--- /dev/null
+++ b/files/config/suckless/st/st.o
Binary files differ
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
new file mode 100644
index 0000000..d8e3556
--- /dev/null
+++ b/files/config/suckless/st/x.o
Binary files differ
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