Allow preloading multiple background images to GPU for fast switching

Fixes #9836
This commit is contained in:
Kovid Goyal 2026-04-15 22:34:20 +05:30
parent 92262ca095
commit 2c9541cf21
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
15 changed files with 255 additions and 85 deletions

View file

@ -181,6 +181,8 @@ Detailed list of changes
- Draw a progress bar at the top of the window when a program reports progress using the OSC 9;4 escape sequence, controlled by :opt:`progress_bar` (:iss:`9777`)
- Allow specifying multiple background images for :opt:`background_image` that are stored on GPU to allow fast image switching (:pull:`9836`)
- :doc:`Remote control <remote-control>`: Expose :code:`session_name` and :code:`last_focused_at` in the output of ``kitten @ ls`` for each window (:iss:`9732`, :iss:`9799`)
- Allow optionally dragging URLs with the mouse, see :sc:`start_simple_selection` (:pull:`9804`)

View file

@ -3141,11 +3141,6 @@ class Boss:
if is_macos:
from .fast_data_types import cocoa_recreate_global_menu
cocoa_recreate_global_menu()
# Update misc options
try:
set_background_image(opts.background_image, tuple(self.os_window_map), True, opts.background_image_layout)
except Exception as e:
log_error(f'Failed to set background image with error: {e}')
for tm in self.all_tab_managers:
tm.apply_options()
# Update colors
@ -3544,9 +3539,12 @@ class Boss:
def set_background_image(
self, path: str | None, os_windows: tuple[int, ...], configured: bool, layout: str | None, png_data: bytes = b'',
linear_interpolation: bool | None = None, tint: float | None = None, tint_gaps: float | None = None
linear_interpolation: bool | None = None, tint: float | None = None, tint_gaps: float | None = None,
global_index: int = -1, is_increment: bool = False,
) -> None:
set_background_image(path, os_windows, configured, layout, png_data, linear_interpolation, tint, tint_gaps)
set_background_image(
path, os_windows, configured, layout, png_data, linear_interpolation, tint, tint_gaps,
global_index, is_increment)
# Can be called with kitty -o "map f1 send_test_notification"
def send_test_notification(self) -> None:

View file

@ -761,7 +761,7 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *
bool was_previously_rendered_with_layers = os_window->needs_layers;
os_window->needs_layers = (
!global_state.supports_framebuffer_srgb || effective_os_window_alpha(os_window) < 1.f ||
os_window->live_resize.in_progress || (os_window->bgimage && os_window->bgimage->texture_id > 0)
os_window->live_resize.in_progress || (background_image_for_os_window(os_window) != NULL)
);
if (TD.screen && os_window->num_tabs && !os_window->has_too_few_tabs) {
if (!os_window->tab_bar_data_updated) {
@ -983,7 +983,7 @@ render_os_window(OSWindow *w, monotonic_t now, bool scan_for_animated_images) {
if (!w->fonts_data) { log_error("No fonts data found for window id: %llu", w->id); return false; }
if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows, &all_windows_have_same_bg, scan_for_animated_images)) needs_render = true;
if (w->last_active_window_id != active_window_id || w->last_active_tab != w->active_tab || w->focused_at_last_render != w->is_focused) needs_render = true;
if (w->render_calls < 3 && w->bgimage && w->bgimage->texture_id) needs_render = true;
if (w->render_calls < 3 && background_image_for_os_window(w) != NULL) needs_render = true;
if (needs_render) render_prepared_os_window(w, active_window_id, active_window_bg, num_visible_windows, all_windows_have_same_bg);
if (w->is_focused) change_menubar_title(w->window_title);
return needs_render;

View file

@ -728,6 +728,8 @@ def set_background_image(
linear: bool | None = None,
tint: float | None = None,
tint_gaps: float | None = None,
global_index: int = -1,
is_increment: bool = False,
) -> None:
pass

View file

@ -1956,8 +1956,13 @@ this option by reloading the config is not supported.
opt('background_image', 'none',
option_type='config_or_absolute_path', ctype='!background_image',
long_text='Path to a background image. Must be in PNG/JPEG/WEBP/TIFF/GIF/BMP format.'
option_type='background_images', ctype='!background_images',
long_text='Glob pattern matching one or more background images. Must be in PNG/JPEG/WEBP/TIFF/GIF/BMP format.'
' The first one is used as the default image, you can change the current image using :ac:`change_background_image`.'
' Multiple images are stored in GPU VRAM (on demand) so that transitioning between them is instant.'
' In order to move between images in the list of background images, use the remote control command'
' :code:`set-backround-image`, see `kitten set-backround-image --help` for details. You can map the command'
' to a key like this: :code:`map f1 remote_control set-backround-image +1`.'
' Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect.'
)

35
kitty/options/parse.py generated
View file

@ -8,22 +8,23 @@ from kitty.conf.utils import (
to_color, to_color_or_none, unit_float
)
from kitty.options.utils import (
action_alias, active_tab_title_template, allow_hyperlinks, bell_on_tab, box_drawing_scale,
clear_all_mouse_actions, clear_all_shortcuts, clipboard_control, clone_source_strategies,
config_or_absolute_path, confirm_close, copy_on_select, cursor_blink_interval, cursor_text_color,
cursor_trail_decay, deprecated_adjust_line_height, deprecated_hide_window_decorations_aliases,
deprecated_macos_show_window_title_in_menubar_alias, deprecated_scrollback_indicator_opacity,
deprecated_send_text, disable_ligatures, edge_width, env, filter_notification, font_features,
hide_window_decorations, macos_option_as_alt, macos_titlebar_color, menu_map, modify_font,
mouse_hide_wait, narrow_symbols, notify_on_cmd_finish, optional_edge_width, parse_font_spec,
parse_map, parse_mouse_map, paste_actions, pointer_shape_when_dragging, remote_control_password,
resize_debounce_time, scrollback_lines, scrollback_pager_history_size, scrollbar_color,
shell_integration, show_hyperlink_targets, store_multiple, symbol_map, tab_activity_symbol,
tab_bar_edge, tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator,
tab_title_template, text_fg_override_threshold, titlebar_color, to_cursor_shape,
to_cursor_unfocused_shape, to_font_size, to_layout_names, to_modifiers,
transparent_background_colors, underline_exclusion, url_prefixes, url_style, visual_bell_duration,
visual_window_select_characters, window_border_width, window_logo_scale, window_size
action_alias, active_tab_title_template, allow_hyperlinks, background_images, bell_on_tab,
box_drawing_scale, clear_all_mouse_actions, clear_all_shortcuts, clipboard_control,
clone_source_strategies, config_or_absolute_path, confirm_close, copy_on_select,
cursor_blink_interval, cursor_text_color, cursor_trail_decay, deprecated_adjust_line_height,
deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias,
deprecated_scrollback_indicator_opacity, deprecated_send_text, disable_ligatures, edge_width, env,
filter_notification, font_features, hide_window_decorations, macos_option_as_alt,
macos_titlebar_color, menu_map, modify_font, mouse_hide_wait, narrow_symbols, notify_on_cmd_finish,
optional_edge_width, parse_font_spec, parse_map, parse_mouse_map, paste_actions,
pointer_shape_when_dragging, remote_control_password, resize_debounce_time, scrollback_lines,
scrollback_pager_history_size, scrollbar_color, shell_integration, show_hyperlink_targets,
store_multiple, symbol_map, tab_activity_symbol, tab_bar_edge, tab_bar_margin_height,
tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, tab_title_template,
text_fg_override_threshold, titlebar_color, to_cursor_shape, to_cursor_unfocused_shape,
to_font_size, to_layout_names, to_modifiers, transparent_background_colors, underline_exclusion,
url_prefixes, url_style, visual_bell_duration, visual_window_select_characters, window_border_width,
window_logo_scale, window_size
)
@ -77,7 +78,7 @@ class Parser:
ans['background_blur'] = int(val)
def background_image(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['background_image'] = config_or_absolute_path(val)
ans['background_image'] = background_images(val)
def background_image_layout(self, val: str, ans: dict[str, typing.Any]) -> None:
val = val.lower()

View file

@ -1203,7 +1203,7 @@ convert_from_opts_dynamic_background_opacity(PyObject *py_opts, Options *opts) {
static void
convert_from_python_background_image(PyObject *val, Options *opts) {
background_image(val, opts);
background_images(val, opts);
}
static void

View file

@ -167,7 +167,28 @@ bganchor(PyObject *anchor_name) {
}
static inline void
background_image(PyObject *src, Options *opts) { STR_SETTER(background_image); }
free_background_images(Options *opts) {
if (opts->background_images.paths) {
for (size_t i = 0; i < opts->background_images.count; i++) free(opts->background_images.paths[i]);
free(opts->background_images.paths);
}
opts->background_images.paths = NULL;
opts->background_images.count = 0;
}
static inline void
background_images(PyObject *src, Options *opts) {
free_background_images(opts);
static unsigned generation = 0;
opts->background_images.generation = ++generation;
if (!PyTuple_Check(src) || PyTuple_GET_SIZE(src) == 0) return;
opts->background_images.paths = calloc(PyTuple_GET_SIZE(src), sizeof(opts->background_images.paths[0]));
if (opts->background_images.paths) {
opts->background_images.count = PyTuple_GET_SIZE(src);
for (size_t i = 0; i < opts->background_images.count; i++) opts->background_images.paths[i] = strdup(
PyUnicode_AsUTF8(PyTuple_GET_ITEM(src, i)));
}
}
static inline void
bell_path(PyObject *src, Options *opts) { STR_SETTER(bell_path); }
@ -554,8 +575,9 @@ free_allocs_in_options(Options *opts) {
free_menu_map(opts);
free_url_prefixes(opts);
free_font_features(opts);
free_background_images(opts);
#define F(x) free(opts->x); opts->x = NULL;
F(select_by_word_characters); F(url_excluded_characters); F(select_by_word_characters_forward);
F(background_image); F(bell_path); F(bell_theme); F(default_window_logo);
F(bell_path); F(bell_theme); F(default_window_logo);
#undef F
}

View file

@ -529,7 +529,7 @@ class Options:
allow_remote_control: choices_for_allow_remote_control = 'no'
background: Color = Color(0, 0, 0)
background_blur: int = 0
background_image: str | None = None
background_image: tuple[str, ...] = ()
background_image_layout: choices_for_background_image_layout = 'tiled'
background_image_linear: bool = False
background_opacity: float = 1.0

View file

@ -855,6 +855,15 @@ def config_or_absolute_path(x: str, env: dict[str, str] | None = None) -> str |
return resolve_abs_or_config_path(x, env)
def background_images(x: str) -> tuple[str, ...]:
if x.lower() in ('none', ''):
return ()
from glob import glob
x = resolve_abs_or_config_path(x, None)
return tuple(x for x in sorted(glob(x)) if x.rpartition('.')[-1].lower() in (
'jpeg', 'jpg', 'png', 'webp', 'tiff', 'bmp', 'gif'))
def filter_notification(val: str, current_val: dict[str, str]) -> Iterable[tuple[str, str]]:
yield val, ''

View file

@ -35,7 +35,7 @@ class SetBackgroundImage(RemoteCommand):
protocol_spec = __doc__ = '''
data+/str: Chunk of at most 512 bytes of PNG data, base64 encoded. Must send an empty chunk to indicate end of image. \
Or the special value - to indicate image must be removed.
Or the special value - to indicate image must be removed. Or the value index:idx to change image.
match/str: Window to change opacity in
layout/choices.{layout_choices.replace(",", ".")}: The image layout
all/bool: Boolean indicating operate on all windows
@ -46,8 +46,14 @@ class SetBackgroundImage(RemoteCommand):
desc = (
'Set the background image for the specified OS windows. You must specify the path to an image that'
' will be used as the background. If you specify the special value :code:`none` then any existing image will'
' be removed. Supported image formats are: '
) + ', '.join(SUPPORTED_IMAGE_FORMATS)
' be removed.'
f' Supported image formats are: {", ".join(SUPPORTED_IMAGE_FORMATS)}'
'\n\n'
'You can also specify an index to change the background image to one of the pre-configured set of background images.'
' The index is either a number, or a number preceeded by the plus or minus sign which acts as an increment.'
' If the index falls outside the range of configured images, the first configured image is used.'
' For example: set-background -- -1 or set-background 3'
)
options_spec = f'''\
--all -a
type=bool-set
@ -57,7 +63,8 @@ cause the image to be changed in all windows.
--configured -c
type=bool-set
Change the configured background image which is used for new OS windows.
Change the configured background image which is used for new OS windows. Note that an index with this option
is not supported. Specifying :code:`none` will erase *all* configured background images.
--layout
@ -73,7 +80,7 @@ default=false
Don't wait for a response from kitty. This means that even if setting the background image
failed, the command will exit with a success code.
''' + '\n\n' + MATCH_WINDOW_OPTION
args = RemoteCommand.Args(spec='PATH_TO_PNG_IMAGE', count=1, json_field='data', special_parse='!read_window_logo(io_data, args[0])',
args = RemoteCommand.Args(spec='PATH_TO_IMAGE_OR_INDEX', count=1, json_field='data', special_parse='!read_background_image(io_data, args[0])',
completion=ImageCompletion)
reads_streaming_data = True
@ -92,6 +99,10 @@ failed, the command will exit with a success code.
if path.lower() == 'none':
ret['data'] = '-'
return ret
import re
if re.match(r'[+-]?\d+', path) is not None:
ret['data'] = f'index:{path}'
return ret
if not is_png(path):
self.fatal(f'{path} is not a PNG image')
@ -109,12 +120,20 @@ failed, the command will exit with a success code.
def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType:
data = payload_get('data')
windows = self.windows_for_payload(boss, window, payload_get, window_match_name='match')
os_windows = tuple({w.os_window_id for w in windows if w})
layout = payload_get('layout')
path = None
global_index = -1
is_increment = False
if data == '-':
path = None
tfile = BytesIO()
elif data and data.startswith('index:'):
if payload_get('configured'):
ex = ValueError('Cannot use --configured with an image index')
ex.hide_traceback = True # type: ignore
raise ex
tfile = BytesIO()
data = data[len('index:'):]
global_index = int(data)
is_increment = data[0] in '+-'
else:
q = self.handle_streamed_data(standard_b64decode(data) if data else b'', payload_get)
if isinstance(q, AsyncResponse):
@ -122,8 +141,13 @@ failed, the command will exit with a success code.
path = '/image/from/remote/control'
tfile = q
windows = self.windows_for_payload(boss, window, payload_get, window_match_name='match')
os_windows = tuple({w.os_window_id for w in windows if w})
layout = payload_get('layout')
try:
boss.set_background_image(path, os_windows, payload_get('configured'), layout, tfile.getvalue())
boss.set_background_image(
path, os_windows, payload_get('configured'), layout, tfile.getvalue(),
global_index=global_index, is_increment=is_increment)
except ValueError as err:
err.hide_traceback = True # type: ignore
raise

View file

@ -450,7 +450,7 @@ pick_cursor_color(color_type cell_fg, color_type cell_bg, color_type *cursor_fg,
static bool
has_bgimage(OSWindow *w) {
return w->bgimage && w->bgimage->texture_id > 0;
return background_image_for_os_window(w) != NULL;
}
static color_type
@ -1526,13 +1526,14 @@ draw_cursor_trail(CursorTrail *trail, Window *active_window) {
// OSWindow {{{
static void
draw_bg_image(OSWindow *os_window, Tab *tab) {
if (!has_bgimage(os_window)) return;
BackgroundImage *bg = background_image_for_os_window(os_window);
if (!bg) return;
BackgroundImageRenderSettings s = {
.os_window.width = os_window->viewport_width, .os_window.height = os_window->viewport_height,
.instance_id = os_window->bgimage->id, .layout=OPT(background_image_layout),
.instance_id = bg->id, .layout=OPT(background_image_layout),
.linear=OPT(background_image_linear), .bgcolor=OPT(background), .opacity=effective_os_window_alpha(os_window),
};
GLfloat iwidth = os_window->bgimage->width, iheight = os_window->bgimage->height;
GLfloat iwidth = bg->width, iheight = bg->height;
GLfloat vwidth = s.os_window.width, vheight = s.os_window.height;
if (CENTER_SCALED == OPT(background_image_layout)) {
GLfloat ifrac = iwidth / iheight;
@ -1570,7 +1571,7 @@ draw_bg_image(OSWindow *os_window, Tab *tab) {
glUniform1i(bgimage_program_layout.uniforms.image, GRAPHICS_UNIT);
color_vec4(bgimage_program_layout.uniforms.background, s.bgcolor, s.opacity);
glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT);
glBindTexture(GL_TEXTURE_2D, os_window->bgimage->texture_id);
glBindTexture(GL_TEXTURE_2D, bg->texture_id);
draw_quad(false, 0);
unbind_program();
}

View file

@ -209,6 +209,70 @@ free_bgimage(BackgroundImage **bgimage, bool release_texture) {
bgimage = NULL;
}
static void
free_global_background_images(bool release_texture) {
if (global_state.background_images.images) {
for (size_t i = 0; i < global_state.background_images.count; i++)
free_bgimage(&global_state.background_images.images[i], release_texture);
free(global_state.background_images.images);
global_state.background_images.images = NULL;
}
zero_at_ptr(&global_state.background_images);
}
static void
ensure_background_images_generation(bool release_texture) {
if (global_state.background_images.generation == OPT(background_images).generation) return;
free_global_background_images(release_texture);
global_state.background_images.generation = OPT(background_images).generation;
global_state.background_images.images = calloc(
OPT(background_images).count, sizeof(global_state.background_images.images[0]));
if (!global_state.background_images.images) fatal("Out of memory");
}
static unsigned bg_image_id_counter = 0;
static BackgroundImage*
global_background_image(size_t idx) {
ensure_background_images_generation(true);
while (global_state.background_images.count <= idx && OPT(background_images).count) {
RAII_ALLOC(char, path, OPT(background_images).paths[0]); // transfer ownership
remove_i_from_array(OPT(background_images.paths), 0, OPT(background_images).count);
BackgroundImage *img = calloc(1, sizeof(BackgroundImage));
if (!img) fatal("Out of memory");
if (image_path_to_bitmap(path, &img->bitmap, &img->width, &img->height, &img->mmap_size)) {
img->refcnt++;
img->id = ++bg_image_id_counter;
global_state.background_images.images[global_state.background_images.count++] = img;
send_bgimage_to_gpu(OPT(background_image_layout), img);
} else free(img);
}
return global_state.background_images.count > idx ? global_state.background_images.images[idx] : NULL;
}
BackgroundImage*
background_image_for_os_window(OSWindow *w) {
if (w->background_image.no_image) return NULL;
if (w->background_image.override) return w->background_image.override;
BackgroundImage *ans = NULL;
if (w->background_image.global_bg_images_idx && (
ans = global_background_image(w->background_image.global_bg_images_idx))) return ans;
return global_background_image(0);
}
static size_t
increment_bg_image_idx(size_t idx, int delta) {
if (delta == 0) return idx;
if (delta > 0) {
size_t new_idx = idx + delta;
return global_background_image(new_idx) ? new_idx : 0;
}
if (-delta <= (ssize_t)idx) return idx + delta;
// wrap to last image, which means we need to load all
global_background_image(global_state.background_images.count + OPT(background_images).count + 1);
return global_state.background_images.count ? global_state.background_images.count - 1 : 0;
}
OSWindow*
add_os_window(void) {
WITH_OS_WINDOW_REFS
@ -219,23 +283,6 @@ add_os_window(void) {
ans->tab_bar_render_data.vao_idx = create_cell_vao();
ans->background_opacity.alpha = OPT(background_opacity);
ans->created_at = monotonic();
bool wants_bg = OPT(background_image) && OPT(background_image)[0] != 0;
if (wants_bg) {
if (!global_state.bgimage) {
global_state.bgimage = calloc(1, sizeof(BackgroundImage));
if (!global_state.bgimage) fatal("Out of memory allocating the global bg image object");
global_state.bgimage->refcnt++;
if (image_path_to_bitmap(OPT(background_image), &global_state.bgimage->bitmap, &global_state.bgimage->width, &global_state.bgimage->height, &global_state.bgimage->mmap_size)) {
send_bgimage_to_gpu(OPT(background_image_layout), global_state.bgimage);
}
}
if (global_state.bgimage->texture_id) {
ans->bgimage = global_state.bgimage;
ans->bgimage->refcnt++;
}
}
END_WITH_OS_WINDOW_REFS
return ans;
}
@ -496,8 +543,8 @@ destroy_os_window_item(OSWindow *w) {
Py_CLEAR(w->window_title); Py_CLEAR(w->tab_bar_render_data.screen);
remove_vao(w->tab_bar_render_data.vao_idx);
free(w->tabs); w->tabs = NULL;
free_bgimage(&w->bgimage, true);
zero_at_ptr(&w->bgimage);
free_bgimage(&w->background_image.override, true);
zero_at_ptr(&w->background_image);
if (w->indirect_output.framebuffer_id) free_framebuffer(&w->indirect_output.framebuffer_id);
}
@ -951,7 +998,7 @@ PYWRAP1(os_window_has_background_image) {
id_type os_window_id;
PA("K", &os_window_id);
WITH_OS_WINDOW(os_window_id)
if (os_window->bgimage && os_window->bgimage->texture_id > 0) { Py_RETURN_TRUE; }
if (background_image_for_os_window(os_window) != NULL) Py_RETURN_TRUE;
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
}
@ -1322,8 +1369,12 @@ pyset_background_image(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
PyObject *os_window_ids;
int configured = 0;
char *png_data = NULL; Py_ssize_t png_data_size = 0;
static char *kwds[] = {"path", "os_window_ids", "configured", "layout_name", "png_data", "linear", "tint", "tint_gaps", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kw, "zO!|pOy#OOO", kwds, &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name, &png_data, &png_data_size, &pylinear, &pytint, &pytint_gaps)) return NULL;
int global_index = -1; int is_increment = 0;
static char *kwds[] = {
"path", "os_window_ids", "configured", "layout_name", "png_data", "linear", "tint", "tint_gaps", "global_index",
"is_increment",
NULL};
if (!PyArg_ParseTupleAndKeywords(args, kw, "zO!|pOy#OOOip", kwds, &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name, &png_data, &png_data_size, &pylinear, &pytint, &pytint_gaps, &global_index, &is_increment)) return NULL;
size_t size;
BackgroundImageLayout layout = PyUnicode_Check(layout_name) ? bglayout(layout_name) : OPT(background_image_layout);
BackgroundImage *bgimage = NULL;
@ -1341,15 +1392,22 @@ pyset_background_image(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
free(bgimage);
return NULL;
}
static uint32_t bgimage_id_counter = 0;
bgimage->id = ++bgimage_id_counter;
bgimage->id = ++bg_image_id_counter;
send_bgimage_to_gpu(layout, bgimage);
bgimage->refcnt++;
}
if (configured) {
free_bgimage(&global_state.bgimage, true);
global_state.bgimage = bgimage;
if (bgimage) bgimage->refcnt++;
if (bgimage) {
if (global_background_image(0)) {
free_bgimage(&global_state.background_images.images[0], true);
} else {
free_global_background_images(true);
global_state.background_images.images = calloc(1, sizeof(global_state.background_images.images[0]));
if (!global_state.background_images.images) fatal("Out of memory");
}
global_state.background_images.images[0] = bgimage;
bgimage->refcnt++;
} else free_global_background_images(true);
OPT(background_image_layout) = layout;
if (pylinear && pylinear != Py_None) convert_from_python_background_image_linear(pylinear, &global_state.opts);
if (pytint && pytint != Py_None) convert_from_python_background_tint(pytint, &global_state.opts);
@ -1358,11 +1416,20 @@ pyset_background_image(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(os_window_ids); i++) {
id_type os_window_id = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(os_window_ids, i));
WITH_OS_WINDOW(os_window_id)
unsigned idx = os_window->background_image.global_bg_images_idx;
make_os_window_context_current(os_window);
free_bgimage(&os_window->bgimage, true);
os_window->bgimage = bgimage;
free_bgimage(&os_window->background_image.override, true);
zero_at_ptr(&os_window->background_image);
os_window->render_calls = 0;
if (bgimage) bgimage->refcnt++;
if (bgimage) {
if (!configured) { // configured means we use the zero index global image
os_window->background_image.override = bgimage;
bgimage->refcnt++;
}
} else if (is_increment) {
os_window->background_image.global_bg_images_idx = increment_bg_image_idx(idx, global_index);
} else if (global_index < 0) os_window->background_image.no_image = true;
else os_window->background_image.global_bg_images_idx = global_index;
END_WITH_OS_WINDOW
}
if (bgimage) free_bgimage(&bgimage, true);
@ -1700,9 +1767,6 @@ finalize(void) {
}
if (detached_windows.windows) free(detached_windows.windows);
detached_windows.capacity = 0;
#define F(x) free(OPT(x)); OPT(x) = NULL;
F(background_image); F(bell_path); F(bell_theme); F(default_window_logo);
#undef F
Py_CLEAR(global_state.options_object);
free_animation(OPT(animation.cursor));
free_animation(OPT(animation.visual_bell));
@ -1710,9 +1774,8 @@ finalize(void) {
// that freeing the texture will work during shutdown and
// the GPU driver should take care of it when the OpenGL context is
// destroyed.
free_bgimage(&global_state.bgimage, false);
free_global_background_images(false);
free_window_logo_table(&global_state.all_window_logos);
global_state.bgimage = NULL;
free_drag_source();
Py_CLEAR(global_state.drop_dest.data);
zero_at_ptr(&global_state.drop_dest);

View file

@ -93,7 +93,11 @@ typedef struct Options {
float text_contrast, text_gamma_adjustment;
bool text_old_gamma;
char *background_image, *default_window_logo;
char *default_window_logo;
struct {
char **paths; size_t count;
unsigned generation;
} background_images;
BackgroundImageLayout background_image_layout;
ImageAnchorPosition window_logo_position;
bool background_image_linear;
@ -409,7 +413,11 @@ typedef struct OSWindow {
int viewport_width, viewport_height, window_width, window_height;
double viewport_x_ratio, viewport_y_ratio;
Tab *tabs;
BackgroundImage *bgimage;
struct {
size_t global_bg_images_idx;
BackgroundImage *override;
bool no_image;
} background_image;
struct {
uint32_t framebuffer_id, attached_texture_generation;
} indirect_output;
@ -460,7 +468,11 @@ typedef struct GlobalState {
id_type os_window_id_counter, tab_id_counter, window_id_counter;
PyObject *boss;
BackgroundImage *bgimage;
struct {
BackgroundImage **images;
size_t count;
unsigned generation;
} background_images;
OSWindow *os_windows;
size_t num_os_windows, capacity;
OSWindow *callback_os_window;
@ -625,3 +637,4 @@ void cancel_current_drag_source(void);
bool change_drag_image(int idx);
int start_window_drag(Window *w);
int notify_drag_data_ready(id_type os_window_id, const char *mime_type);
BackgroundImage* background_image_for_os_window(OSWindow *w);

View file

@ -76,3 +76,33 @@ func read_window_logo(io_data *rc_io_data, path string) (func(io_data *rc_io_dat
return false, nil
}, nil
}
func is_index_arg(s string) bool {
if len(s) == 0 {
return false
}
start := 0
if s[0] == '+' || s[0] == '-' {
start = 1
}
if start >= len(s) {
return false
}
for _, c := range s[start:] {
if c < '0' || c > '9' {
return false
}
}
return true
}
func read_background_image(io_data *rc_io_data, path string) (func(io_data *rc_io_data) (bool, error), error) {
if is_index_arg(path) {
io_data.rc.Stream = false
return func(io_data *rc_io_data) (bool, error) {
set_payload_data(io_data, "index:"+path)
return true, nil
}, nil
}
return read_window_logo(io_data, path)
}