From 46e9223c05795070f46cf367620b73a4e7fabdb8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 11 Aug 2025 21:20:55 +0530 Subject: [PATCH] Add function to get window as launch command for serialization --- kitty/fast_data_types.pyi | 3 ++ kitty/launch.py | 9 ++++-- kitty/state.c | 20 ++++++++++++ kitty/window.py | 64 +++++++++++++++++++++++++++++++++++++-- kitty/window_logo.c | 8 +++++ kitty/window_logo.h | 3 ++ 6 files changed, 103 insertions(+), 4 deletions(-) diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index e57a00755..8c7daa390 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1527,6 +1527,9 @@ def set_window_logo(os_window_id: int, tab_id: int, window_id: int, path: str, p pass +def get_window_logo_settings_if_not_default(os_window_id: int, tab_id: int, window_id: int) -> None | tuple[ + str, float, tuple[float, float, float, float]]: ... + def apply_options_update() -> None: pass diff --git a/kitty/launch.py b/kitty/launch.py index 11e70267d..db26a5590 100644 --- a/kitty/launch.py +++ b/kitty/launch.py @@ -779,8 +779,13 @@ def _launch( env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, **kw) if child_death_callback is not None: boss.monitor_pid(new_window.child.pid or 0, child_death_callback) - if new_window.creation_spec and opts.watcher: - new_window.creation_spec = new_window.creation_spec._replace(watchers=tuple(opts.watcher)) + if new_window.creation_spec: + if opts.watcher: + new_window.creation_spec = new_window.creation_spec._replace(watchers=tuple(opts.watcher)) + if opts.spacing: + new_window.creation_spec = new_window.creation_spec._replace(spacing=tuple(opts.spacing)) + if opts.color: + new_window.creation_spec = new_window.creation_spec._replace(colors=tuple(opts.color)) if spacing: patch_window_edges(new_window, spacing) tab.relayout() diff --git a/kitty/state.c b/kitty/state.c index da9d71993..34c86a200 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -1355,6 +1355,25 @@ pymouse_selection(PyObject *self UNUSED, PyObject *args) { Py_RETURN_NONE; } +PYWRAP1(get_window_logo_settings_if_not_default) { + id_type os_window_id, tab_id, window_id; + PA("KKK", &os_window_id, &tab_id, &window_id); + WITH_WINDOW(os_window_id, tab_id, window_id); + if (window->window_logo.instance != NULL && window->window_logo.id && !window->window_logo.using_default) { + WindowLogo *wl = find_window_logo(global_state.all_window_logos, window->window_logo.id); + if (wl != NULL && wl->load_from_disk_ok) { + const char *path = window_logo_path_for_id(global_state.all_window_logos, window->window_logo.id); + if (path) { + ImageAnchorPosition *p = &window->window_logo.position; + return Py_BuildValue("sfN", path, window->window_logo.alpha, Py_BuildValue( + "ffff", p->image_x, p->image_y, p->canvas_x, p->canvas_y)); + } + } + } + END_WITH_WINDOW; + Py_RETURN_NONE; +} + PYWRAP1(set_window_logo) { id_type os_window_id, tab_id, window_id; const char *path; PyObject *position; @@ -1458,6 +1477,7 @@ static PyMethodDef module_methods[] = { MW(redirect_mouse_handling, METH_O), MW(mouse_selection, METH_VARARGS), MW(set_window_logo, METH_VARARGS), + MW(get_window_logo_settings_if_not_default, METH_VARARGS), MW(set_ignore_os_keyboard_processing, METH_O), MW(handle_for_window_id, METH_VARARGS), MW(update_ime_position_for_window, METH_VARARGS), diff --git a/kitty/window.py b/kitty/window.py index edbb3e863..c41b539df 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -19,6 +19,7 @@ TYPE_CHECKING, Any, Deque, + Iterator, Literal, NamedTuple, Optional, @@ -67,6 +68,7 @@ get_click_interval, get_mouse_data_for_window, get_options, + get_window_logo_settings_if_not_default, is_css_pointer_name_valid, is_modifier_key, last_focused_os_window_id, @@ -369,6 +371,7 @@ class WindowCreationSpec(NamedTuple): env: tuple[tuple[str, str], ...] | None = None location: str | None = None copy_colors_from: int | None = None + colors: tuple[str, ...] = () allow_remote_control: bool = False marker: str | None = None watchers: tuple[str, ...] = () @@ -377,9 +380,8 @@ class WindowCreationSpec(NamedTuple): remote_control_passwords: dict[str, Sequence[str]] | None = None hold: bool = False bias: float | None = None - pass_fds: tuple[int, ...] = () - remote_control_fd: int = -1 hold_after_ssh: bool = False + spacing: tuple[str, ...] = () def pagerhist(screen: Screen, as_ansi: bool = False, add_wrap_markers: bool = True, upto_output_start: bool = False) -> str: @@ -605,6 +607,16 @@ def serialize(self) -> dict[str, float | None]: def copy(self) -> 'EdgeWidths': return EdgeWidths(self.serialize()) + def as_launch_args(self, prefix: str = 'padding') -> Iterator[str]: + if self.left is not None: + yield f'--spacing={prefix}-left={self.left}' + if self.right is not None: + yield f'--spacing={prefix}-left={self.right}' + if self.top is not None: + yield f'--spacing={prefix}-left={self.top}' + if self.bottom is not None: + yield f'--spacing={prefix}-left={self.bottom}' + class GlobalWatchers: @@ -1931,6 +1943,54 @@ def current_mouse_position(self) -> Optional['MousePosition']: ' Return the last position at which a mouse event was received by this window ' return get_mouse_data_for_window(self.os_window_id, self.tab_id, self.id) + def as_launch_command(self) -> list[str]: + ' Return a launch command that can be used to serialize this window. Empty list indicates not serializable. ' + if self.actions_on_close or self.actions_on_focus_change or self.actions_on_removal: + # such windows are typically UI kittens. The actions are not + # serializable anyway, so skip. + return [] + ans = ['launch'] + cwd = self.get_cwd_of_child(oldest=False) or self.get_cwd_of_child(oldest=True) + if self.screen.last_reported_cwd and self.at_prompt and not self.child_is_remote: + cwd = path_from_osc7_url(self.screen.last_reported_cwd) or cwd + if cwd: + ans.append(f'--cwd={cwd}') + if self.creation_spec: + if self.creation_spec.env: + env = dict(self.creation_spec.env) + env.pop('KITTY_PIPE_DATA', None) + for k, v in env.items(): + if k not in ('KITTY_PIPE_DATA',): + ans.append(f'--env={k}={v}') + for cs in self.creation_spec.colors: + ans.append(f'--color={cs}') + for k, v in self.user_vars.items(): + ans.append(f'--var={k}={v}') + ans.extend(self.padding.as_launch_args()) + ans.extend(self.margin.as_launch_args('margin')) + wl = get_window_logo_settings_if_not_default(self.os_window_id, self.tab_id, self.id) + if wl is not None: + logo_path, logo_alpha, logo_pos = wl + ans.extend((f'--logo={logo_path}', f'--logo-alpha={logo_alpha}')) + xpos = ypos = '' + if logo_pos[0] == logo_pos[2] != 0.5: + xpos = 'right' if logo_pos[0] else 'left' + if logo_pos[1] == logo_pos[3] != 0.5: + ypos = 'bottom' if logo_pos[1] else 'top' + lpos = 'center' + if xpos or ypos: + lpos = (f'{ypos}-{xpos}' if ypos else xpos) if xpos else ypos + ans.append(f'--logo-position={lpos}') + + if self.overlay_parent is not None: + t = 'overlay-main' if self.overlay_type is OverlayType.main else 'overlay' + ans.append(f'--type={t}') + + if self.creation_spec and self.creation_spec.cmd: + ans.extend(self.creation_spec.cmd) + return ans + + # actions {{{ @ac('cp', 'Show scrollback in a pager like less') diff --git a/kitty/window_logo.c b/kitty/window_logo.c index 5bd4058ed..45acf1e5f 100644 --- a/kitty/window_logo.c +++ b/kitty/window_logo.c @@ -68,6 +68,14 @@ set_on_gpu_state(WindowLogo *s, bool on_gpu) { } } +const char* +window_logo_path_for_id(WindowLogoTable *head, window_logo_id_t id) { + vt_create_for_loop(hash_by_path_itr, itr, &head->by_path) { + if (itr.data->val->id == id) return itr.data->key; + } + return NULL; +} + window_logo_id_t find_or_create_window_logo(WindowLogoTable *head, const char *path, void *png_data, size_t png_data_size) { hash_by_path_itr n = vt_get(&head->by_path, path); diff --git a/kitty/window_logo.h b/kitty/window_logo.h index c8e11b7fc..312840e70 100644 --- a/kitty/window_logo.h +++ b/kitty/window_logo.h @@ -28,6 +28,9 @@ find_or_create_window_logo(WindowLogoTable *table, const char *path, void *png_d WindowLogo* find_window_logo(WindowLogoTable *table, window_logo_id_t id); +const char* +window_logo_path_for_id(WindowLogoTable *head, window_logo_id_t id); + void decref_window_logo(WindowLogoTable *table, window_logo_id_t id);