Implement single-instance for panel kitten

This commit is contained in:
Kovid Goyal
2025-04-21 16:55:55 +05:30
parent 0ad989f71d
commit 9b5d5bf678
4 changed files with 70 additions and 15 deletions

View File

@@ -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 </kittens/panel>` (:iss:`8549`)
- Allow configuring the mouse unhide behavior when using :opt:`mouse_hide_wait` (:pull:`8508`)

View File

@@ -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

View File

@@ -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:

View File

@@ -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] == '/') {