From 9b5d5bf678889ccbf64be55bfd14094723f856fe Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 21 Apr 2025 16:55:55 +0530 Subject: [PATCH] Implement single-instance for panel kitten --- docs/changelog.rst | 2 ++ kittens/panel/main.py | 34 +++++++++++++++++++++++++++++++--- kitty/boss.py | 6 +++++- kitty/launcher/main.c | 43 ++++++++++++++++++++++++++++++++----------- 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0c0fad01b..e0227b724 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -100,6 +100,8 @@ Detailed list of changes - **Behavior change**: Now kitty does full grapheme segmentation following the Unicode 16 spec when splitting text into cells (:iss:`8533`) +- panel kitten: Allow using :option:`kitty +kitten panel --single-instance` to create multiple panels in one process (:iss:`8549`) + - launch: Allow creating desktop panels such as those created by the :doc:`panel kitten ` (:iss:`8549`) - Allow configuring the mouse unhide behavior when using :opt:`mouse_hide_wait` (:pull:`8508`) diff --git a/kittens/panel/main.py b/kittens/panel/main.py index bd8d6f014..9f052e74e 100644 --- a/kittens/panel/main.py +++ b/kittens/panel/main.py @@ -4,11 +4,12 @@ import sys from collections.abc import Callable from contextlib import suppress -from typing import Any +from functools import partial +from typing import Any, Mapping, Sequence from kitty.cli import parse_args from kitty.cli_stub import PanelCLIOptions -from kitty.constants import appname, is_macos, is_wayland +from kitty.constants import appname, is_macos, is_wayland, kitten_exe from kitty.fast_data_types import ( GLFW_EDGE_BOTTOM, GLFW_EDGE_CENTER, @@ -28,7 +29,7 @@ from kitty.fast_data_types import ( ) from kitty.os_window_size import WindowSizeData, edge_spacing from kitty.types import LayerShellConfig -from kitty.typing import EdgeLiteral +from kitty.typing import BossType, EdgeLiteral OPTIONS = r''' --lines @@ -147,6 +148,19 @@ On a Wayland compositor that supports the wlr layer shell protocol, override the This has effect only if :option:`--edge` is set to :code:`top`, :code:`left`, :code:`bottom` or :code:`right`. +--single-instance -1 +type=bool-set +If specified only a single instance of the panel will run. New +invocations will instead create a new top-level window in the existing +panel instance. + + +--instance-group +Used in combination with the :option:`--single-instance` option. All +panel invocations with the same :option:`--instance-group` will result +in new panels being created in the first panel instance within that group. + + --debug-rendering type=bool-set For internal debugging use. @@ -277,6 +291,18 @@ def layer_shell_config(opts: PanelCLIOptions) -> LayerShellConfig: output_name=opts.output_name or '') +def handle_single_instance_command(boss: BossType, sys_args: Sequence[str], environ: Mapping[str, str], notify_on_os_window_death: str | None = '') -> None: + from kitty.tabs import SpecialWindow + args, items = parse_panel_args(list(sys_args[1:])) + items = items or [kitten_exe(), 'run-shell'] + lsc = layer_shell_config(args) + os_window_id = boss.add_os_panel(lsc, args.cls, args.name) + if notify_on_os_window_death: + boss.os_window_death_actions[os_window_id] = partial(boss.notify_on_os_window_death, notify_on_os_window_death) + tm = boss.os_window_map[os_window_id] + tm.new_tab(SpecialWindow(cmd=items, env=dict(environ))) + + def main(sys_args: list[str]) -> None: global args if is_macos: @@ -295,6 +321,8 @@ def main(sys_args: list[str]) -> None: for override in args.override: sys.argv.extend(('--override', override)) sys.argv.append('--override=linux_display_server=auto') + if args.single_instance: + sys.argv.append('--single-instance') sys.argv.extend(items) from kitty.main import main as real_main from kitty.main import run_app diff --git a/kitty/boss.py b/kitty/boss.py index 89ac62fcd..5d91eebe7 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -841,6 +841,10 @@ class Boss: log_error('Malformed command received over single instance socket, ignoring') return None if isinstance(data, dict) and data.get('cmd') == 'new_instance': + if data['args'][0] == 'panel': + from kittens.panel.main import handle_single_instance_command + handle_single_instance_command(self, data['args'], data['environ'], data.get('notify_on_os_window_death', '')) + return None from .cli_stub import CLIOptions startup_id = data['environ'].get('DESKTOP_STARTUP_ID', '') activation_token = data['environ'].get('XDG_ACTIVATION_TOKEN', '') @@ -864,7 +868,7 @@ class Boss: focused_os_window = os_window_id = 0 for session in create_sessions(opts, args, respect_cwd=True): if not session.has_non_background_processes: - # background only do not create and OS Window + # background only do not create an OS Window from .launch import LaunchSpec, launch for tab in session.tabs: for window in tab.windows: diff --git a/kitty/launcher/main.c b/kitty/launcher/main.c index b6c7d3005..0fe7ba50d 100644 --- a/kitty/launcher/main.c +++ b/kitty/launcher/main.c @@ -352,13 +352,6 @@ exec_kitten(int argc, char *argv[], char *exe_dir) { exit(1); } -static void -delegate_to_kitten_if_possible(int argc, char *argv[], char* exe_dir) { - if (argc > 1 && argv[1][0] == '@') exec_kitten(argc, argv, exe_dir); - if (argc > 2 && strcmp(argv[1], "+kitten") == 0 && is_wrapped_kitten(argv[2])) exec_kitten(argc - 1, argv + 1, exe_dir); - if (argc > 3 && strcmp(argv[1], "+") == 0 && strcmp(argv[2], "kitten") == 0 && is_wrapped_kitten(argv[3])) exec_kitten(argc - 2, argv + 2, exe_dir); -} - static bool is_boolean_flag(const char *x) { static const char *all_boolean_options = KITTY_CLI_BOOL_OPTIONS; @@ -368,7 +361,7 @@ is_boolean_flag(const char *x) { } static void -handle_fast_commandline(int argc, char *argv[]) { +handle_fast_commandline(int argc, char *argv[], const char *instance_group_prefix) { char current_option_expecting_argument[128] = {0}; CLIOptions opts = {0}; int first_arg = 1; @@ -436,7 +429,36 @@ handle_option_value: exit(0); } unsetenv("KITTY_SI_DATA"); - if (opts.single_instance) single_instance_main(argc, argv, &opts); + if (opts.single_instance) { + char igbuf[256]; + if (instance_group_prefix && instance_group_prefix[0]) { + if (opts.instance_group && opts.instance_group[0]) { + snprintf(igbuf, sizeof(igbuf), "%s-%s", instance_group_prefix, opts.instance_group ? opts.instance_group : ""); + opts.instance_group = igbuf; + } opts.instance_group = instance_group_prefix; + } + single_instance_main(argc, argv, &opts); + } +} + +static bool +delegate_to_kitten_if_possible(int argc, char *argv[], char* exe_dir) { + if (argc > 1 && argv[1][0] == '@') exec_kitten(argc, argv, exe_dir); + if (argc > 2 && strcmp(argv[1], "+kitten") == 0) { + if (is_wrapped_kitten(argv[2])) exec_kitten(argc - 1, argv + 1, exe_dir); + if (strcmp(argv[2], "panel") == 0) { + handle_fast_commandline(argc - 2, argv + 2, "panel"); + return true; + } + } + if (argc > 3 && strcmp(argv[1], "+") == 0 && strcmp(argv[2], "kitten") == 0) { + if (is_wrapped_kitten(argv[3])) exec_kitten(argc - 2, argv + 2, exe_dir); + if (strcmp(argv[3], "panel") == 0) { + handle_fast_commandline(argc - 3, argv + 3, "panel"); + return true; + } + } + return false; } int main(int argc, char *argv[], char* envp[]) { @@ -452,8 +474,7 @@ int main(int argc, char *argv[], char* envp[]) { if (!read_exe_path(exe, sizeof(exe))) return 1; strncpy(exe_dir_buf, exe, sizeof(exe_dir_buf)); char *exe_dir = dirname(exe_dir_buf); - delegate_to_kitten_if_possible(argc, argv, exe_dir); - handle_fast_commandline(argc, argv); + if (!delegate_to_kitten_if_possible(argc, argv, exe_dir)) handle_fast_commandline(argc, argv, NULL); int num, ret=0; char lib[PATH_MAX+1] = {0}; if (KITTY_LIB_PATH[0] == '/') {