feat: kitty images patch applied

This commit is contained in:
2026-04-15 13:46:38 +02:00
parent 688f70add0
commit 67c54ec28b
15 changed files with 10000 additions and 58 deletions

View File

@@ -4,7 +4,7 @@
include config.mk include config.mk
SRC = st.c x.c SRC = st.c x.c rowcolumn_diacritics_helpers.c graphics.c
OBJ = $(SRC:.c=.o) OBJ = $(SRC:.c=.o)
all: st all: st
@@ -15,8 +15,9 @@ config.h:
.c.o: .c.o:
$(CC) $(STCFLAGS) -c $< $(CC) $(STCFLAGS) -c $<
st.o: config.h st.h win.h st.o: config.h st.h win.h graphics.h
x.o: arg.h config.h st.h win.h x.o: arg.h config.h st.h win.h graphics.h
graphics.c: graphics.h khash.h kvec.h st.h
$(OBJ): config.h config.mk $(OBJ): config.h config.mk

View File

@@ -8,6 +8,13 @@
static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true";
static int borderpx = 2; 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: * What program is execed by st depends of these precedence rules:
* 1: program passed with -e * 1: program passed with -e
@@ -23,7 +30,8 @@ char *scroll = NULL;
char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";
/* identification sequence returned in DA and DECID */ /* 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 */ /* Kerning / character bounding-box multipliers */
static float cwscale = 1.0; static float cwscale = 1.0;
@@ -163,6 +171,28 @@ static unsigned int mousebg = 0;
*/ */
static unsigned int defaultattr = 11; 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). * 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 * 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; static uint forcemousemod = ShiftMask;
/* Internal keyboard shortcuts. */
#define MODKEY Mod1Mask
#define TERMMOD (ControlMask|ShiftMask)
/* /*
* Internal mouse shortcuts. * Internal mouse shortcuts.
* Beware that overloading Button1 will disable the selection. * Beware that overloading Button1 will disable the selection.
*/ */
static MouseShortcut mshortcuts[] = { static MouseShortcut mshortcuts[] = {
/* mask button function argument release */ /* mask button function argument release */
{ TERMMOD, Button3, previewimage, {.s = "feh"} },
{ TERMMOD, Button2, showimageinfo, {}, 1 },
{ XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 },
{ ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} },
{ XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} },
@@ -183,10 +219,6 @@ static MouseShortcut mshortcuts[] = {
{ XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} },
}; };
/* Internal keyboard shortcuts. */
#define MODKEY Mod1Mask
#define TERMMOD (ControlMask|ShiftMask)
static Shortcut shortcuts[] = { static Shortcut shortcuts[] = {
/* mask keysym function argument */ /* mask keysym function argument */
{ XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} },
@@ -201,6 +233,10 @@ static Shortcut shortcuts[] = {
{ TERMMOD, XK_Y, selpaste, {.i = 0} }, { TERMMOD, XK_Y, selpaste, {.i = 0} },
{ ShiftMask, XK_Insert, selpaste, {.i = 0} }, { ShiftMask, XK_Insert, selpaste, {.i = 0} },
{ TERMMOD, XK_Num_Lock, numlock, {.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} },
}; };
/* /*

View File

@@ -14,9 +14,12 @@ PKG_CONFIG = pkg-config
# includes and libs # includes and libs
INCS = -I$(X11INC) \ INCS = -I$(X11INC) \
`$(PKG_CONFIG) --cflags imlib2` \
`$(PKG_CONFIG) --cflags fontconfig` \ `$(PKG_CONFIG) --cflags fontconfig` \
`$(PKG_CONFIG) --cflags freetype2` `$(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 fontconfig` \
`$(PKG_CONFIG) --libs freetype2` `$(PKG_CONFIG) --libs freetype2`

4393
graphics.c Normal file
View File

File diff suppressed because it is too large Load Diff

112
graphics.h Normal file
View File

@@ -0,0 +1,112 @@
#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. The callback should return 1 if it
/// changed the glyph. This function is implemented in `st.c`.
void gr_for_each_image_cell(int (*callback)(void *data, Glyph *gp),
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);
/// Returns a pointer to the glyph under the classic placement with `image_id`
/// and `placement_id` at `col` and `row` (1-based). May return NULL if the
/// underneath text is unknown.
Glyph *gr_get_glyph_underneath_image(uint32_t image_id, uint32_t placement_id,
int col, int row);
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;
Glyph *text_underneath;
} placeholder;
} GraphicsCommandResult;
/// The result of a graphics command.
extern GraphicsCommandResult graphics_command_result;

875
icat-mini.sh Executable file
View File

@@ -0,0 +1,875 @@
#!/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=""
command_tty=""
response_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")"
#####################################################################
# 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
inside_tmux=1
fi
if [ -z "$command_tty" ] && [ -n "$inside_tmux" ]; then
# Get the pty of the current tmux pane.
command_tty="$(tmux display-message -t "$TMUX_PANE" -p "#{pane_tty}")"
if [ ! -e "$command_tty" ]; then
command_tty=""
fi
fi
#####################################################################
# Adjust the terminal state
#####################################################################
if [ -z "$command_tty" ]; then
command_tty="/dev/tty"
fi
if [ -z "$response_tty" ]; then
response_tty="/dev/tty"
fi
stty_orig="$(stty -g < "$response_tty")"
stty -echo < "$response_tty"
# Disable ctrl-z. Pressing ctrl-z during image uploading may cause some
# horrible issues otherwise.
stty susp undef < "$response_tty"
stty -icanon < "$response_tty"
restore_echo() {
[ -n "$stty_orig" ] || return
stty $stty_orig < "$response_tty"
}
trap restore_echo EXIT TERM
#####################################################################
# 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" < "$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\\' >> "$command_tty"
else
printf '\e[16t' >> "$command_tty"
fi
# The expected response will look like ^[[6;<height>;<width>t
term_response=""
while true; do
char=$(dd bs=1 count=1 <"$response_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
# Send a graphics command with the correct start and end
gr_command() {
printf "${graphics_command_start}%s${graphics_command_end}" "$1" >> "$command_tty"
}
# Compute the size of a data chunk for direct transmission.
if [ "$uploading_method" = "direct" ]; then
# Get the value of PIPE_BUF.
pipe_buf="$(getconf PIPE_BUF "$command_tty" 2> /dev/null)"
if is_pos_int "$pipe_buf"; then
# Make sure it's between 512 and 4096.
if [ "$(expr "$pipe_buf" \< 512)" -eq 1 ]; then
pipe_buf=512
elif [ "$(expr "$pipe_buf" \> 4096)" -eq 1 ]; then
pipe_buf=4096
fi
else
pipe_buf=512
fi
# The size of each graphics command shouldn't be more than PIPE_BUF, so we
# set the size of an encoded chunk to be PIPE_BUF - 128 to leave some space
# for the command.
chunk_size="$(expr "$pipe_buf" - 128)"
fi
# Check if the image format is supported.
is_format_supported() {
arg_format="$1"
if [ "$arg_format" = "PNG" ]; then
return 0
elif [ "$arg_format" = "JPEG" ]; then
if [ -z "$inside_tmux" ]; then
actual_term="$TERM"
else
# Get the actual current terminal name from tmux.
actual_term="$(tmux display-message -p "#{client_termname}")"
fi
# st is known to support JPEG.
case "$actual_term" in
st | *-st | st-* | *-st-*)
return 0
;;
esac
return 1
else
return 1
fi
}
# 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)
# If the file name contains 'tty-graphics-protocol', assume it's
# temporary and use t=t.
medium="t=f"
case "$arg_file" in
*tty-graphics-protocol*)
medium="t=t"
;;
*)
medium="t=f"
;;
esac
gr_command "${arg_action},${arg_command},${medium};${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
gr_command "${arg_action},i=${image_id},m=1;$(cat "$chunk")"
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.
format_output=$($identify -format '%n %m\n' "$file" | head -n 1)
frame_count="$(printf '%s' "$format_output" | cut -d ' ' -f 1)"
image_format="$(printf '%s' "$format_output" | cut -d ' ' -f 2)"
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
# Abort the previous transmission, just in case.
gr_command "q=2,a=t,i=${image_id},m=0"
# 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 &
elif is_format_supported "$image_format"; then
# The file is not an animation and has a supported format, 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
else
# The format is not supported, try to convert it to png.
temp_file="$(mktemp --tmpdir "icat-mini-tty-graphics-protocol-XXXXX.png")"
if ! $convert "$file" "$temp_file"; then
echo "Failed to convert the image to PNG" >&2
exit 1
fi
# Upload the converted image.
gr_upload "q=2,a=T" "U=1,i=${image_id},f=100,c=${cols},r=${rows}" "$temp_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}"
line="$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
line="${line}${placeholder}${row_diacritic}"
else
line="${line}${placeholder}${row_diacritic}${col_diacritic}${id_diacritic}"
fi
done
line="${line}${line_end}"
printf "%s\n" "$line"
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

627
khash.h Normal file
View File

@@ -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 */

90
kvec.h Normal file
View File

@@ -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

View File

@@ -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;
}

303
st.c
View File

@@ -19,6 +19,7 @@
#include "st.h" #include "st.h"
#include "win.h" #include "win.h"
#include "graphics.h"
#if defined(__linux) #if defined(__linux)
#include <pty.h> #include <pty.h>
@@ -36,6 +37,10 @@
#define STR_BUF_SIZ ESC_BUF_SIZ #define STR_BUF_SIZ ESC_BUF_SIZ
#define STR_ARG_SIZ ESC_ARG_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 */ /* macros */
#define IS_SET(flag) ((term.mode & (flag)) != 0) #define IS_SET(flag) ((term.mode & (flag)) != 0)
#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
@@ -113,6 +118,8 @@ typedef struct {
typedef struct { typedef struct {
int row; /* nb row */ int row; /* nb row */
int col; /* nb col */ 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 *line; /* screen */
Line *alt; /* alternate screen */ Line *alt; /* alternate screen */
int *dirty; /* dirtyness of lines */ int *dirty; /* dirtyness of lines */
@@ -213,7 +220,6 @@ static Rune utf8decodebyte(char, size_t *);
static char utf8encodebyte(Rune, size_t); static char utf8encodebyte(Rune, size_t);
static size_t utf8validate(Rune *, size_t); static size_t utf8validate(Rune *, size_t);
static char *base64dec(const char *);
static char base64dec_getc(const char **); static char base64dec_getc(const char **);
static ssize_t xwrite(int, const char *, size_t); 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 utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 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 ssize_t
xwrite(int fd, const char *s, size_t len) xwrite(int fd, const char *s, size_t len)
{ {
@@ -616,6 +626,12 @@ getsel(void)
if (gp->mode & ATTR_WDUMMY) if (gp->mode & ATTR_WDUMMY)
continue; continue;
if (gp->mode & ATTR_IMAGE) {
// TODO: Copy diacritics as well
ptr += utf8encode(IMAGE_PLACEHOLDER_CHAR, ptr);
continue;
}
ptr += utf8encode(gp->u, ptr); ptr += utf8encode(gp->u, ptr);
} }
@@ -715,11 +731,14 @@ sigchld(int a)
int stat; int stat;
pid_t p; pid_t p;
if ((p = waitpid(pid, &stat, WNOHANG)) < 0) if ((p = waitpid(-1, &stat, WNOHANG)) < 0)
die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
if (pid != p) if (pid != p) {
/* reinstall sigchld handler */
signal(SIGCHLD, sigchld);
return; return;
}
if (WIFEXITED(stat) && WEXITSTATUS(stat)) if (WIFEXITED(stat) && WEXITSTATUS(stat))
die("child exited with status %d\n", WEXITSTATUS(stat)); die("child exited with status %d\n", WEXITSTATUS(stat));
@@ -819,7 +838,11 @@ ttyread(void)
{ {
static char buf[BUFSIZ]; static char buf[BUFSIZ];
static int buflen = 0; 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 */ /* append read bytes to unprocessed bytes */
ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
@@ -831,7 +854,24 @@ ttyread(void)
die("couldn't read from shell: %s\n", strerror(errno)); die("couldn't read from shell: %s\n", strerror(errno));
default: default:
buflen += ret; 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; buflen -= written;
/* keep any incomplete UTF-8 byte sequence for the next call */ /* keep any incomplete UTF-8 byte sequence for the next call */
if (buflen > 0) if (buflen > 0)
@@ -874,6 +914,7 @@ ttywriteraw(const char *s, size_t n)
fd_set wfd, rfd; fd_set wfd, rfd;
ssize_t r; ssize_t r;
size_t lim = 256; size_t lim = 256;
int retries_left = 100;
/* /*
* Remember that we are using a pty, which might be a modem line. * Remember that we are using a pty, which might be a modem line.
@@ -882,6 +923,9 @@ ttywriteraw(const char *s, size_t n)
* FIXME: Migrate the world to Plan 9. * FIXME: Migrate the world to Plan 9.
*/ */
while (n > 0) { while (n > 0) {
if (retries_left-- <= 0)
goto too_many_retries;
FD_ZERO(&wfd); FD_ZERO(&wfd);
FD_ZERO(&rfd); FD_ZERO(&rfd);
FD_SET(cmdfd, &wfd); FD_SET(cmdfd, &wfd);
@@ -923,11 +967,16 @@ ttywriteraw(const char *s, size_t n)
write_error: write_error:
die("write error on tty: %s\n", strerror(errno)); die("write error on tty: %s\n", strerror(errno));
too_many_retries:
fprintf(stderr, "Could not write %zu bytes to tty\n", n);
} }
void void
ttyresize(int tw, int th) ttyresize(int tw, int th)
{ {
term.pixw = tw;
term.pixh = th;
struct winsize w; struct winsize w;
w.ws_row = term.row; w.ws_row = term.row;
@@ -1018,7 +1067,8 @@ treset(void)
term.c = (TCursor){{ term.c = (TCursor){{
.mode = ATTR_NULL, .mode = ATTR_NULL,
.fg = defaultfg, .fg = defaultfg,
.bg = defaultbg .bg = defaultbg,
.decor = DECOR_DEFAULT_COLOR
}, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
memset(term.tabs, 0, term.col * sizeof(*term.tabs)); memset(term.tabs, 0, term.col * sizeof(*term.tabs));
@@ -1041,7 +1091,9 @@ treset(void)
void void
tnew(int col, int row) 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); tresize(col, row);
treset(); treset();
} }
@@ -1218,9 +1270,24 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
term.line[y][x-1].mode &= ~ATTR_WIDE; 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.dirty[y] = 1;
term.line[y][x] = *attr; term.line[y][x] = *attr;
term.line[y][x].u = u; 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 void
@@ -1247,12 +1314,110 @@ tclearregion(int x1, int y1, int x2, int y2)
selclear(); selclear();
gp->fg = term.c.attr.fg; gp->fg = term.c.attr.fg;
gp->bg = term.c.attr.bg; gp->bg = term.c.attr.bg;
gp->decor = term.c.attr.decor;
gp->mode = 0; gp->mode = 0;
gp->u = ' '; 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,
Glyph *text_underneath) {
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();
if (text_underneath) {
Glyph *to_save = gp;
// If there is already a classic placeholder,
// use the text underneath it. This will leave
// holes in images, but at least we are
// guaranteed to restore the original text.
if (gp->mode & ATTR_IMAGE &&
tgetisclassicplaceholder(gp)) {
Glyph *under =
gr_get_glyph_underneath_image(
tgetimgid(gp),
tgetimgplacementid(gp),
tgetimgcol(gp),
tgetimgrow(gp));
if (under)
to_save = under;
}
text_underneath[cols * row + col] = *to_save;
}
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, Glyph *gp),
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) {
if (callback(data, gp))
term.dirty[row] = 1;
}
}
}
}
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 void
tdeletechar(int n) tdeletechar(int n)
{ {
@@ -1371,6 +1536,7 @@ tsetattr(const int *attr, int l)
ATTR_STRUCK ); ATTR_STRUCK );
term.c.attr.fg = defaultfg; term.c.attr.fg = defaultfg;
term.c.attr.bg = defaultbg; term.c.attr.bg = defaultbg;
term.c.attr.decor = DECOR_DEFAULT_COLOR;
break; break;
case 1: case 1:
term.c.attr.mode |= ATTR_BOLD; term.c.attr.mode |= ATTR_BOLD;
@@ -1383,6 +1549,20 @@ tsetattr(const int *attr, int l)
break; break;
case 4: case 4:
term.c.attr.mode |= ATTR_UNDERLINE; 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; break;
case 5: /* slow blink */ case 5: /* slow blink */
/* FALLTHROUGH */ /* FALLTHROUGH */
@@ -1406,6 +1586,7 @@ tsetattr(const int *attr, int l)
break; break;
case 24: case 24:
term.c.attr.mode &= ~ATTR_UNDERLINE; term.c.attr.mode &= ~ATTR_UNDERLINE;
tsetdecorstyle(&term.c.attr, 0);
break; break;
case 25: case 25:
term.c.attr.mode &= ~ATTR_BLINK; term.c.attr.mode &= ~ATTR_BLINK;
@@ -1434,10 +1615,11 @@ tsetattr(const int *attr, int l)
term.c.attr.bg = defaultbg; term.c.attr.bg = defaultbg;
break; break;
case 58: case 58:
/* This starts a sequence to change the color of if ((idx = tdefcolor(attr, &i, l)) >= 0)
* "underline" pixels. We don't support that and tsetdecorcolor(&term.c.attr, idx);
* instead eat up a following "5;n" or "2;r;g;b". */ break;
tdefcolor(attr, &i, l); case 59:
tsetdecorcolor(&term.c.attr, DECOR_DEFAULT_COLOR);
break; break;
default: default:
if (BETWEEN(attr[i], 30, 37)) { if (BETWEEN(attr[i], 30, 37)) {
@@ -1826,6 +2008,39 @@ csihandle(void)
goto unknown; goto unknown;
} }
break; 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;
} }
} }
@@ -1988,8 +2203,27 @@ strhandle(void)
case 'k': /* old title set compatibility */ case 'k': /* old title set compatibility */
xsettitle(strescseq.args[0]); xsettitle(strescseq.args[0]);
return; return;
case 'P': /* DCS -- Device Control String */
case '_': /* APC -- Application Program Command */ 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,
res->placeholder.text_underneath);
}
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 */ case '^': /* PM -- Privacy Message */
return; return;
} }
@@ -2496,6 +2730,41 @@ check_control_code:
if (selected(term.c.x, term.c.y)) if (selected(term.c.x, term.c.y))
selclear(); selclear();
// wcwidth is broken on some systems, set the width to 0 if it's a known
// diacritic used for images.
uint16_t num = diacritic_to_num(u);
if (num != 0)
width = 0;
// Set the width to 1 if it's an image placeholder character.
if (u == IMAGE_PLACEHOLDER_CHAR || u == IMAGE_PLACEHOLDER_CHAR_OLD)
width = 1;
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];
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]; gp = &term.line[term.c.y][term.c.x];
if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
gp->mode |= ATTR_WRAP; gp->mode |= ATTR_WRAP;
@@ -2662,6 +2931,8 @@ drawregion(int x1, int y1, int x2, int y2)
{ {
int y; int y;
xstartimagedraw(term.dirty, term.row);
for (y = y1; y < y2; y++) { for (y = y1; y < y2; y++) {
if (!term.dirty[y]) if (!term.dirty[y])
continue; continue;
@@ -2669,6 +2940,8 @@ drawregion(int x1, int y1, int x2, int y2)
term.dirty[y] = 0; term.dirty[y] = 0;
xdrawline(term.line[y], x1, y, x2); xdrawline(term.line[y], x1, y, x2);
} }
xfinishimagedraw();
} }
void void
@@ -2703,3 +2976,9 @@ redraw(void)
tfulldirt(); tfulldirt();
draw(); draw();
} }
Glyph
getglyphat(int col, int row)
{
return term.line[row][col];
}

2705
st.c.orig Normal file
View File

File diff suppressed because it is too large Load Diff

84
st.h
View File

@@ -12,7 +12,7 @@
#define DEFAULT(a, b) (a) = (a) ? (a) : (b) #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) #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 || \ #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 + \ #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
(t1.tv_nsec-t2.tv_nsec)/1E6) (t1.tv_nsec-t2.tv_nsec)/1E6)
#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) #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 TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
#define IS_TRUECOL(x) (1 << 24 & (x)) #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 { enum glyph_attribute {
ATTR_NULL = 0, ATTR_NULL = 0,
ATTR_BOLD = 1 << 0, ATTR_BOLD = 1 << 0,
@@ -34,6 +38,7 @@ enum glyph_attribute {
ATTR_WIDE = 1 << 9, ATTR_WIDE = 1 << 9,
ATTR_WDUMMY = 1 << 10, ATTR_WDUMMY = 1 << 10,
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
ATTR_IMAGE = 1 << 14,
}; };
enum selection_mode { enum selection_mode {
@@ -52,6 +57,14 @@ enum selection_snap {
SNAP_LINE = 2 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 char uchar;
typedef unsigned int uint; typedef unsigned int uint;
typedef unsigned long ulong; typedef unsigned long ulong;
@@ -65,6 +78,7 @@ typedef struct {
ushort mode; /* attribute flags */ ushort mode; /* attribute flags */
uint32_t fg; /* foreground */ uint32_t fg; /* foreground */
uint32_t bg; /* background */ uint32_t bg; /* background */
uint32_t decor; /* decoration (like underline) */
} Glyph; } Glyph;
typedef Glyph *Line; typedef Glyph *Line;
@@ -105,6 +119,8 @@ void selextend(int, int, int, int);
int selected(int, int); int selected(int, int);
char *getsel(void); char *getsel(void);
Glyph getglyphat(int, int);
size_t utf8encode(Rune, char *); size_t utf8encode(Rune, char *);
void *xmalloc(size_t); void *xmalloc(size_t);
@@ -124,3 +140,69 @@ extern unsigned int tabspaces;
extern unsigned int defaultfg; extern unsigned int defaultfg;
extern unsigned int defaultbg; extern unsigned int defaultbg;
extern unsigned int defaultcs; 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);
}

View File

@@ -195,6 +195,7 @@ st-mono| simpleterm monocolor,
Ms=\E]52;%p1%s;%p2%s\007, Ms=\E]52;%p1%s;%p2%s\007,
Se=\E[2 q, Se=\E[2 q,
Ss=\E[%p1%d q, Ss=\E[%p1%d q,
Smulx=\E[4:%p1%dm,
st| simpleterm, st| simpleterm,
use=st-mono, 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\\, 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, 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, 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, st-meta| simpleterm with meta key,
use=st, use=st,

3
win.h
View File

@@ -39,3 +39,6 @@ void xsetpointermotion(int);
void xsetsel(char *); void xsetsel(char *);
int xstartdraw(void); int xstartdraw(void);
void xximspot(int, int); void xximspot(int, int);
void xstartimagedraw(int *dirty, int rows);
void xfinishimagedraw();

411
x.c
View File

@@ -4,6 +4,8 @@
#include <limits.h> #include <limits.h>
#include <locale.h> #include <locale.h>
#include <signal.h> #include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h> #include <sys/select.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
@@ -19,6 +21,7 @@ char *argv0;
#include "arg.h" #include "arg.h"
#include "st.h" #include "st.h"
#include "win.h" #include "win.h"
#include "graphics.h"
/* types used in config.h */ /* types used in config.h */
typedef struct { typedef struct {
@@ -59,6 +62,12 @@ static void zoom(const Arg *);
static void zoomabs(const Arg *); static void zoomabs(const Arg *);
static void zoomreset(const Arg *); static void zoomreset(const Arg *);
static void ttysend(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. */ /* config.h for applying patches and the configuration. */
#include "config.h" #include "config.h"
@@ -81,6 +90,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
typedef struct { typedef struct {
int tw, th; /* tty width and height */ int tw, th; /* tty width and height */
int w, h; /* window width and height */ int w, h; /* window width and height */
int hborderpx, vborderpx;
int ch; /* char height */ int ch; /* char height */
int cw; /* char width */ int cw; /* char width */
int mode; /* window state/mode flags */ 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 int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
static void xdrawglyph(Glyph, 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 void xclear(int, int, int, int);
static int xgeommasktogravity(int); static int xgeommasktogravity(int);
static int ximopen(Display *); static int ximopen(Display *);
@@ -220,6 +232,7 @@ static DC dc;
static XWindow xw; static XWindow xw;
static XSelection xsel; static XSelection xsel;
static TermWindow win; static TermWindow win;
static unsigned int mouse_col = 0, mouse_row = 0;
/* Font Ring Cache */ /* Font Ring Cache */
enum { enum {
@@ -328,10 +341,72 @@ ttysend(const Arg *arg)
ttywrite(arg->s, strlen(arg->s), 1); 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 int
evcol(XEvent *e) evcol(XEvent *e)
{ {
int x = e->xbutton.x - borderpx; int x = e->xbutton.x - win.hborderpx;
LIMIT(x, 0, win.tw - 1); LIMIT(x, 0, win.tw - 1);
return x / win.cw; return x / win.cw;
} }
@@ -339,7 +414,7 @@ evcol(XEvent *e)
int int
evrow(XEvent *e) evrow(XEvent *e)
{ {
int y = e->xbutton.y - borderpx; int y = e->xbutton.y - win.vborderpx;
LIMIT(y, 0, win.th - 1); LIMIT(y, 0, win.th - 1);
return y / win.ch; 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 */ /* ignore Button<N>mask for Button<N> - it's set on release */
uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); 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++) { for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
if (ms->release == release && if (ms->release == release &&
ms->button == e->xbutton.button && ms->button == e->xbutton.button &&
@@ -739,6 +817,9 @@ cresize(int width, int height)
col = MAX(1, col); col = MAX(1, col);
row = MAX(1, row); 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); tresize(col, row);
xresize(col, row); xresize(col, row);
ttyresize(win.tw, win.th); ttyresize(win.tw, win.th);
@@ -869,8 +950,8 @@ xhints(void)
sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
sizeh->height = win.h; sizeh->height = win.h;
sizeh->width = win.w; sizeh->width = win.w;
sizeh->height_inc = win.ch; sizeh->height_inc = 1;
sizeh->width_inc = win.cw; sizeh->width_inc = 1;
sizeh->base_height = 2 * borderpx; sizeh->base_height = 2 * borderpx;
sizeh->base_width = 2 * borderpx; sizeh->base_width = 2 * borderpx;
sizeh->min_height = win.ch + 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); FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
usedfontsize = 12; usedfontsize = 12;
} }
defaultfontsize = usedfontsize; if (defaultfontsize <= 0)
defaultfontsize = usedfontsize;
} }
if (xloadfont(&dc.font, pattern)) if (xloadfont(&dc.font, pattern))
@@ -1024,7 +1106,7 @@ xloadfonts(const char *fontstr, double fontsize)
FcPatternGetDouble(dc.font.match->pattern, FcPatternGetDouble(dc.font.match->pattern,
FC_PIXEL_SIZE, 0, &fontval); FC_PIXEL_SIZE, 0, &fontval);
usedfontsize = fontval; usedfontsize = fontval;
if (fontsize == 0) if (defaultfontsize <= 0 && fontsize == 0)
defaultfontsize = fontval; defaultfontsize = fontval;
} }
@@ -1152,8 +1234,8 @@ xinit(int cols, int rows)
xloadcols(); xloadcols();
/* adjust fixed window geometry */ /* adjust fixed window geometry */
win.w = 2 * borderpx + cols * win.cw; win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw;
win.h = 2 * borderpx + rows * win.ch; win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch;
if (xw.gm & XNegative) if (xw.gm & XNegative)
xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
if (xw.gm & YNegative) if (xw.gm & YNegative)
@@ -1240,12 +1322,15 @@ xinit(int cols, int rows)
xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
if (xsel.xtarget == None) if (xsel.xtarget == None)
xsel.xtarget = XA_STRING; xsel.xtarget = XA_STRING;
// Initialize the graphics (image display) module.
gr_init(xw.dpy, xw.vis, xw.cmap);
} }
int int
xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) 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; ushort mode, prevmode = USHRT_MAX;
Font *font = &dc.font; Font *font = &dc.font;
int frcflags = FRC_NORMAL; int frcflags = FRC_NORMAL;
@@ -1267,6 +1352,11 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
if (mode == ATTR_WDUMMY) if (mode == ATTR_WDUMMY)
continue; 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. */ /* Determine font for glyph if different from previous glyph. */
if (prevmode != mode) { if (prevmode != mode) {
prevmode = mode; prevmode = mode;
@@ -1374,11 +1464,61 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
return 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 void
xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
{ {
int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); 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; width = charlen * win.cw;
Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
XRenderColor colfg, colbg; XRenderColor colfg, colbg;
@@ -1468,17 +1608,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
/* Intelligent cleaning up of the borders. */ /* Intelligent cleaning up of the borders. */
if (x == 0) { if (x == 0) {
xclear(0, (y == 0)? 0 : winy, borderpx, xclear(0, (y == 0)? 0 : winy, win.hborderpx,
winy + win.ch + 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, 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) if (y == 0)
xclear(winx, 0, winx + width, borderpx); xclear(winx, 0, winx + width, win.vborderpx);
if (winy + win.ch >= borderpx + win.th) if (winy + win.ch >= win.vborderpx + win.th)
xclear(winx, winy + win.ch, winx + width, win.h); xclear(winx, winy + win.ch, winx + width, win.h);
/* Clean up the region we want to draw to. */ /* 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; r.width = width;
XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
/* 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. */ /* Render the glyphs. */
XftDrawGlyphFontSpec(xw.draw, fg, specs, len); XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
/* Render underline and strikethrough. */ /* Render strikethrough. Alway use the fg color. */
if (base.mode & ATTR_UNDERLINE) {
XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
width, 1);
}
if (base.mode & ATTR_STRUCK) { if (base.mode & ATTR_STRUCK) {
XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,
width, 1); width, thick);
} }
/* Reset clip to none. */ /* Reset clip to none. */
@@ -1517,6 +1707,11 @@ xdrawglyph(Glyph g, int x, int y)
numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
xdrawglyphfontspecs(&spec, g, numspecs, 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 void
@@ -1532,6 +1727,10 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
if (IS_SET(MODE_HIDE)) if (IS_SET(MODE_HIDE))
return; 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. * 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 3: /* Blinking Underline */
case 4: /* Steady Underline */ case 4: /* Steady Underline */
XftDrawRect(xw.draw, &drawcol, XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw, win.hborderpx + cx * win.cw,
borderpx + (cy + 1) * win.ch - \ win.vborderpx + (cy + 1) * win.ch - \
cursorthickness, cursorthickness,
win.cw, cursorthickness); win.cw, cursorthickness);
break; break;
case 5: /* Blinking bar */ case 5: /* Blinking bar */
case 6: /* Steady bar */ case 6: /* Steady bar */
XftDrawRect(xw.draw, &drawcol, XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw, win.hborderpx + cx * win.cw,
borderpx + cy * win.ch, win.vborderpx + cy * win.ch,
cursorthickness, win.ch); cursorthickness, win.ch);
break; break;
} }
} else { } else {
XftDrawRect(xw.draw, &drawcol, XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw, win.hborderpx + cx * win.cw,
borderpx + cy * win.ch, win.vborderpx + cy * win.ch,
win.cw - 1, 1); win.cw - 1, 1);
XftDrawRect(xw.draw, &drawcol, XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw, win.hborderpx + cx * win.cw,
borderpx + cy * win.ch, win.vborderpx + cy * win.ch,
1, win.ch - 1); 1, win.ch - 1);
XftDrawRect(xw.draw, &drawcol, XftDrawRect(xw.draw, &drawcol,
borderpx + (cx + 1) * win.cw - 1, win.hborderpx + (cx + 1) * win.cw - 1,
borderpx + cy * win.ch, win.vborderpx + cy * win.ch,
1, win.ch - 1); 1, win.ch - 1);
XftDrawRect(xw.draw, &drawcol, XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw, win.hborderpx + cx * win.cw,
borderpx + (cy + 1) * win.ch - 1, win.vborderpx + (cy + 1) * win.ch - 1,
win.cw, 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 void
xsetenv(void) xsetenv(void)
{ {
@@ -1671,6 +1998,8 @@ xdrawline(Line line, int x1, int y1, int x2)
new.mode ^= ATTR_REVERSE; new.mode ^= ATTR_REVERSE;
if (i > 0 && ATTRCMP(base, new)) { if (i > 0 && ATTRCMP(base, new)) {
xdrawglyphfontspecs(specs, base, i, ox, y1); xdrawglyphfontspecs(specs, base, i, ox, y1);
if (base.mode & ATTR_IMAGE)
xdrawimages(base, line, ox, y1, x);
specs += i; specs += i;
numspecs -= i; numspecs -= i;
i = 0; i = 0;
@@ -1683,6 +2012,8 @@ xdrawline(Line line, int x1, int y1, int x2)
} }
if (i > 0) if (i > 0)
xdrawglyphfontspecs(specs, base, i, ox, y1); xdrawglyphfontspecs(specs, base, i, ox, y1);
if (i > 0 && base.mode & ATTR_IMAGE)
xdrawimages(base, line, ox, y1, x);
} }
void void
@@ -1907,6 +2238,7 @@ cmessage(XEvent *e)
} }
} else if (e->xclient.data.l[0] == xw.wmdeletewin) { } else if (e->xclient.data.l[0] == xw.wmdeletewin) {
ttyhangup(); ttyhangup();
gr_deinit();
exit(0); exit(0);
} }
} }
@@ -1957,6 +2289,13 @@ run(void)
if (XPending(xw.dpy)) if (XPending(xw.dpy))
timeout = 0; /* existing events might not set xfd */ 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_sec = timeout / 1E3;
seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
tv = timeout >= 0 ? &seltv : NULL; tv = timeout >= 0 ? &seltv : NULL;