mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-08-04 14:46:03 +00:00
Allow preloading multiple background images to GPU for fast switching
Fixes #9836
This commit is contained in:
parent
92262ca095
commit
2c9541cf21
15 changed files with 255 additions and 85 deletions
|
|
@ -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`)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
35
kitty/options/parse.py
generated
|
|
@ -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()
|
||||
|
|
|
|||
2
kitty/options/to-c-generated.h
generated
2
kitty/options/to-c-generated.h
generated
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
2
kitty/options/types.py
generated
2
kitty/options/types.py
generated
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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, ''
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
133
kitty/state.c
133
kitty/state.c
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue