Do not buffer PNG data to disk when setting window background or logo images

This commit is contained in:
Kovid Goyal 2023-03-01 17:34:38 +05:30
parent f0aacbd437
commit 22f6728fed
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
9 changed files with 52 additions and 46 deletions

View File

@ -2639,8 +2639,8 @@ class Boss:
self.choose_entry('Choose an OS window to move the tab to', items, chosen) self.choose_entry('Choose an OS window to move the tab to', items, chosen)
def set_background_image(self, path: Optional[str], os_windows: Tuple[int, ...], configured: bool, layout: Optional[str]) -> None: def set_background_image(self, path: Optional[str], os_windows: Tuple[int, ...], configured: bool, layout: Optional[str], png_data: bytes = b'') -> None:
set_background_image(path, os_windows, configured, layout) set_background_image(path, os_windows, configured, layout, png_data)
for os_window_id in os_windows: for os_window_id in os_windows:
self.default_bg_changed_for(os_window_id) self.default_bg_changed_for(os_window_id)

View File

@ -1351,7 +1351,7 @@ def send_mouse_event(screen: Screen, x: int, y: int, button: int, action: int, m
pass pass
def set_window_logo(os_window_id: int, tab_id: int, window_id: int, path: str, position: str, alpha: float) -> None: def set_window_logo(os_window_id: int, tab_id: int, window_id: int, path: str, position: str, alpha: float, png_data: bytes = b'') -> None:
pass pass

View File

@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import tempfile
from contextlib import suppress from contextlib import suppress
from dataclasses import dataclass, field from dataclasses import dataclass, field
from io import BytesIO
from typing import TYPE_CHECKING, Any, Callable, Dict, FrozenSet, Iterable, Iterator, List, NoReturn, Optional, Set, Tuple, Type, Union, cast from typing import TYPE_CHECKING, Any, Callable, Dict, FrozenSet, Iterable, Iterator, List, NoReturn, Optional, Set, Tuple, Type, Union, cast
from kitty.cli import CompletionSpec, get_defaults_from_seq, parse_args, parse_option_spec from kitty.cli import CompletionSpec, get_defaults_from_seq, parse_args, parse_option_spec
@ -27,16 +27,6 @@ class NoResponse:
pass pass
class NamedTemporaryFile:
name: str = ''
def __enter__(self) -> None: ...
def __exit__(self, exc: Any, value: Any, tb: Any) -> None: ...
def close(self) -> None: ...
def write(self, data: bytes) -> None: ...
def flush(self) -> None: ...
class RemoteControlError(Exception): class RemoteControlError(Exception):
pass pass
@ -279,22 +269,27 @@ class StreamInFlight:
def __init__(self) -> None: def __init__(self) -> None:
self.stream_id = '' self.stream_id = ''
self.tempfile: Optional[NamedTemporaryFile] = None self.tempfile: Optional[BytesIO] = None
def handle_data(self, stream_id: str, data: bytes) -> Union[AsyncResponse, NamedTemporaryFile]: def handle_data(self, stream_id: str, data: bytes) -> Union[AsyncResponse, BytesIO]:
from ..remote_control import close_active_stream from ..remote_control import close_active_stream
if stream_id != self.stream_id: def abort_stream() -> None:
close_active_stream(self.stream_id) close_active_stream(self.stream_id)
self.stream_id = ''
if self.tempfile is not None: if self.tempfile is not None:
self.tempfile.close() self.tempfile.close()
self.tempfile = None self.tempfile = None
if stream_id != self.stream_id:
abort_stream()
self.stream_id = stream_id self.stream_id = stream_id
if self.tempfile is None: if self.tempfile is None:
t: NamedTemporaryFile = cast(NamedTemporaryFile, tempfile.NamedTemporaryFile(suffix='.png')) self.tempfile = BytesIO()
self.tempfile = t
else:
t = self.tempfile t = self.tempfile
if data: if data:
if (t.tell() + len(data)) > 128 * 1024 * 1024:
abort_stream()
raise StreamError('Too much data being sent')
t.write(data) t.write(data)
return AsyncResponse() return AsyncResponse()
close_active_stream(self.stream_id) close_active_stream(self.stream_id)
@ -404,7 +399,7 @@ class RemoteCommand:
def cancel_async_request(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> None: def cancel_async_request(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> None:
pass pass
def handle_streamed_data(self, data: bytes, payload_get: PayloadGetType) -> Union[NamedTemporaryFile, AsyncResponse]: def handle_streamed_data(self, data: bytes, payload_get: PayloadGetType) -> Union[BytesIO, AsyncResponse]:
stream_id = payload_get('stream_id') stream_id = payload_get('stream_id')
if not stream_id or not isinstance(stream_id, str): if not stream_id or not isinstance(stream_id, str):
raise StreamError('No stream_id in rc payload') raise StreamError('No stream_id in rc payload')

View File

@ -3,6 +3,7 @@
import os import os
from base64 import standard_b64decode, standard_b64encode from base64 import standard_b64decode, standard_b64encode
from io import BytesIO
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from kitty.types import AsyncResponse from kitty.types import AsyncResponse
@ -14,7 +15,6 @@ from .base import (
Boss, Boss,
CmdGenerator, CmdGenerator,
ImageCompletion, ImageCompletion,
NamedTemporaryFile,
PayloadGetType, PayloadGetType,
PayloadType, PayloadType,
RCOptions, RCOptions,
@ -111,17 +111,16 @@ failed, the command will exit with a success code.
layout = payload_get('layout') layout = payload_get('layout')
if data == '-': if data == '-':
path = None path = None
tfile = NamedTemporaryFile() tfile = BytesIO()
else: else:
q = self.handle_streamed_data(standard_b64decode(data) if data else b'', payload_get) q = self.handle_streamed_data(standard_b64decode(data) if data else b'', payload_get)
if isinstance(q, AsyncResponse): if isinstance(q, AsyncResponse):
return q return q
path = q.name path = '/image/from/remote/control'
tfile = q tfile = q
try: try:
with tfile: boss.set_background_image(path, os_windows, payload_get('configured'), layout, tfile.getvalue())
boss.set_background_image(path, os_windows, payload_get('configured'), layout)
except ValueError as err: except ValueError as err:
err.hide_traceback = True # type: ignore err.hide_traceback = True # type: ignore
raise raise

View File

@ -3,6 +3,7 @@
import os import os
from base64 import standard_b64decode, standard_b64encode from base64 import standard_b64decode, standard_b64encode
from io import BytesIO
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from kitty.types import AsyncResponse from kitty.types import AsyncResponse
@ -14,7 +15,6 @@ from .base import (
Boss, Boss,
CmdGenerator, CmdGenerator,
ImageCompletion, ImageCompletion,
NamedTemporaryFile,
PayloadGetType, PayloadGetType,
PayloadType, PayloadType,
RCOptions, RCOptions,
@ -105,18 +105,18 @@ failed, the command will exit with a success code.
position = payload_get('position') or '' position = payload_get('position') or ''
if data == '-': if data == '-':
path = '' path = ''
tfile = NamedTemporaryFile() tfile = BytesIO()
else: else:
q = self.handle_streamed_data(standard_b64decode(data) if data else b'', payload_get) q = self.handle_streamed_data(standard_b64decode(data) if data else b'', payload_get)
if isinstance(q, AsyncResponse): if isinstance(q, AsyncResponse):
return q return q
path = q.name import hashlib
path = '/from/remote/control/' + hashlib.sha1(q.getvalue()).hexdigest()
tfile = q tfile = q
with tfile:
for window in self.windows_for_match_payload(boss, window, payload_get): for window in self.windows_for_match_payload(boss, window, payload_get):
if window: if window:
window.set_logo(path, position, alpha) window.set_logo(path, position, alpha, tfile.getvalue())
return None return None

View File

@ -256,10 +256,10 @@ release_gpu_resources_for_window(Window *w) {
} }
static bool static bool
set_window_logo(Window *w, const char *path, const ImageAnchorPosition pos, float alpha, bool is_default) { set_window_logo(Window *w, const char *path, const ImageAnchorPosition pos, float alpha, bool is_default, char *png_data, size_t png_data_size) {
bool ok = false; bool ok = false;
if (path && path[0]) { if (path && path[0]) {
window_logo_id_t wl = find_or_create_window_logo(global_state.all_window_logos, path); window_logo_id_t wl = find_or_create_window_logo(global_state.all_window_logos, path, png_data, png_data_size);
if (wl) { if (wl) {
if (w->window_logo.id) decref_window_logo(global_state.all_window_logos, w->window_logo.id); if (w->window_logo.id) decref_window_logo(global_state.all_window_logos, w->window_logo.id);
w->window_logo.id = wl; w->window_logo.id = wl;
@ -285,7 +285,7 @@ initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
w->visible = true; w->visible = true;
w->title = title; w->title = title;
Py_XINCREF(title); Py_XINCREF(title);
if (!set_window_logo(w, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true)) { if (!set_window_logo(w, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true, NULL, 0)) {
log_error("Failed to load default window logo: %s", OPT(default_window_logo)); log_error("Failed to load default window logo: %s", OPT(default_window_logo));
if (PyErr_Occurred()) PyErr_Print(); if (PyErr_Occurred()) PyErr_Print();
} }
@ -1106,7 +1106,7 @@ PYWRAP0(apply_options_update) {
for (size_t w = 0; w < tab->num_windows; w++) { for (size_t w = 0; w < tab->num_windows; w++) {
Window *window = tab->windows + w; Window *window = tab->windows + w;
if (window->window_logo.using_default) { if (window->window_logo.using_default) {
set_window_logo(window, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true); set_window_logo(window, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true, NULL, 0);
} }
} }
} }
@ -1153,7 +1153,7 @@ pyset_background_image(PyObject *self UNUSED, PyObject *args) {
PyObject *os_window_ids; PyObject *os_window_ids;
int configured = 0; int configured = 0;
char *png_data = NULL; Py_ssize_t png_data_size = 0; char *png_data = NULL; Py_ssize_t png_data_size = 0;
PA("zO!|pOy#", &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name, &png_data, png_data_size); PA("zO!|pOy#", &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name, &png_data, &png_data_size);
size_t size; size_t size;
BackgroundImageLayout layout = PyUnicode_Check(layout_name) ? bglayout(layout_name) : OPT(background_image_layout); BackgroundImageLayout layout = PyUnicode_Check(layout_name) ? bglayout(layout_name) : OPT(background_image_layout);
BackgroundImage *bgimage = NULL; BackgroundImage *bgimage = NULL;
@ -1169,6 +1169,7 @@ pyset_background_image(PyObject *self UNUSED, PyObject *args) {
return NULL; return NULL;
} }
ok = png_from_file_pointer(fp, path, &bgimage->bitmap, &bgimage->width, &bgimage->height, &size); ok = png_from_file_pointer(fp, path, &bgimage->bitmap, &bgimage->width, &bgimage->height, &size);
fclose(fp);
} else { } else {
ok = png_path_to_bitmap(path, &bgimage->bitmap, &bgimage->width, &bgimage->height, &size); ok = png_path_to_bitmap(path, &bgimage->bitmap, &bgimage->width, &bgimage->height, &size);
} }
@ -1280,10 +1281,11 @@ PYWRAP1(set_window_logo) {
id_type os_window_id, tab_id, window_id; id_type os_window_id, tab_id, window_id;
const char *path; PyObject *position; const char *path; PyObject *position;
float alpha = 0.5; float alpha = 0.5;
PA("KKKsUf", &os_window_id, &tab_id, &window_id, &path, &position, &alpha); char *png_data = NULL; Py_ssize_t png_data_size = 0;
PA("KKKsUf|y#", &os_window_id, &tab_id, &window_id, &path, &position, &alpha, &png_data, &png_data_size);
bool ok = false; bool ok = false;
WITH_WINDOW(os_window_id, tab_id, window_id); WITH_WINDOW(os_window_id, tab_id, window_id);
ok = set_window_logo(window, path, PyObject_IsTrue(position) ? bganchor(position) : OPT(window_logo_position), (0 <= alpha && alpha <= 1) ? alpha : OPT(window_logo_alpha), false); ok = set_window_logo(window, path, PyObject_IsTrue(position) ? bganchor(position) : OPT(window_logo_position), (0 <= alpha && alpha <= 1) ? alpha : OPT(window_logo_alpha), false, png_data, png_data_size);
END_WITH_WINDOW; END_WITH_WINDOW;
if (ok) Py_RETURN_TRUE; if (ok) Py_RETURN_TRUE;
Py_RETURN_FALSE; Py_RETURN_FALSE;

View File

@ -1449,9 +1449,9 @@ class Window:
'text': text 'text': text
} }
def set_logo(self, path: str, position: str = '', alpha: float = -1) -> None: def set_logo(self, path: str, position: str = '', alpha: float = -1, png_data: bytes = b'') -> None:
path = resolve_custom_file(path) if path else '' path = resolve_custom_file(path) if path else ''
set_window_logo(self.os_window_id, self.tab_id, self.id, path, position or '', alpha) set_window_logo(self.os_window_id, self.tab_id, self.id, path, position or '', alpha, png_data)
def paste_with_actions(self, text: str) -> None: def paste_with_actions(self, text: str) -> None:
if self.destroyed or not text: if self.destroyed or not text:

View File

@ -49,7 +49,7 @@ set_on_gpu_state(WindowLogo *s, bool on_gpu) {
} }
window_logo_id_t window_logo_id_t
find_or_create_window_logo(WindowLogoTable *head, const char *path) { find_or_create_window_logo(WindowLogoTable *head, const char *path, void *png_data, size_t png_data_size) {
WindowLogoItem *s = NULL; WindowLogoItem *s = NULL;
unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(path); unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(path);
HASH_FIND(hh_path, head->by_path, path, _uthash_hfstr_keylen, s); HASH_FIND(hh_path, head->by_path, path, _uthash_hfstr_keylen, s);
@ -62,7 +62,17 @@ find_or_create_window_logo(WindowLogoTable *head, const char *path) {
if (!s) { PyErr_NoMemory(); return 0; } if (!s) { PyErr_NoMemory(); return 0; }
s->path = strdup(path); s->path = strdup(path);
if (!s->path) { free(s); PyErr_NoMemory(); return 0; } if (!s->path) { free(s); PyErr_NoMemory(); return 0; }
if (png_path_to_bitmap(path, &s->wl.bitmap, &s->wl.width, &s->wl.height, &size)) s->wl.load_from_disk_ok = true; bool ok = false;
if (png_data == NULL) {
ok = png_path_to_bitmap(path, &s->wl.bitmap, &s->wl.width, &s->wl.height, &size);
} else {
FILE *fp = fmemopen(png_data, png_data_size, "r");
if (fp != NULL) {
ok = png_from_file_pointer(fp, path, &s->wl.bitmap, &s->wl.width, &s->wl.height, &size);
fclose(fp);
}
}
if (ok) s->wl.load_from_disk_ok = true;
s->refcnt++; s->refcnt++;
static window_logo_id_t idc = 0; static window_logo_id_t idc = 0;
s->id = ++idc; s->id = ++idc;

View File

@ -20,7 +20,7 @@ typedef struct WindowLogo {
typedef struct WindowLogoTable WindowLogoTable; typedef struct WindowLogoTable WindowLogoTable;
window_logo_id_t window_logo_id_t
find_or_create_window_logo(WindowLogoTable *table, const char *path); find_or_create_window_logo(WindowLogoTable *table, const char *path, void *png_data, size_t png_data_size);
WindowLogo* WindowLogo*
find_window_logo(WindowLogoTable *table, window_logo_id_t id); find_window_logo(WindowLogoTable *table, window_logo_id_t id);