From c1b6b4494afef5131250ca3a4afd6b6f466b0322 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 23 Apr 2025 08:50:02 +0530 Subject: [PATCH] Implement starting kitty hidden Fixes #3466 --- docs/changelog.rst | 2 ++ docs/kittens/panel.rst | 5 +++++ glfw/wl_platform.h | 2 +- glfw/wl_window.c | 30 ++++++++++++++++-------------- kittens/panel/main.py | 7 +++++++ kitty/cli.py | 5 +++-- kitty/fast_data_types.pyi | 1 + kitty/glfw.c | 20 +++++++++++++------- kitty/state.c | 1 + kitty/state.h | 2 +- kitty/utils.py | 19 ++++++++++++++----- 11 files changed, 64 insertions(+), 30 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index ee62c2633..4a3d9af64 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -109,6 +109,8 @@ Detailed list of changes - Remote control: Allow modifying desktop panels and showing/hiding OS Windows using the `kitten @ resize-os-window` command (:iss:`8550`) +- Allow starting kitty with the OS window hidden via :option:`kitty --start-as`=hidden useful for single instance mode (:iss:`3466`) + - Allow configuring the mouse unhide behavior when using :opt:`mouse_hide_wait` (:pull:`8508`) - diff kitten: Add half page and full page scroll vim-like bindings (:pull:`8514`) diff --git a/docs/kittens/panel.rst b/docs/kittens/panel.rst index 83d65df50..232b0306b 100644 --- a/docs/kittens/panel.rst +++ b/docs/kittens/panel.rst @@ -88,6 +88,11 @@ position of the quick access panel. In particular, the :option:`kitty +kitten pa :option:`kitty +kitten panel --override` options can be used to theme the terminal appropriately, making it look different from regular kitty terminal instances. +.. note:: + If you want to start the quake terminal hidden, use + :option:`kitty +kitten panel --start-as-hidden`, useful if you are starting it in the background + during computer startup. + Controlling panels via remote control ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index ffdcf3c51..7990641aa 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -165,7 +165,7 @@ enum _GLFWWaylandAxisEvent { typedef struct _GLFWwindowWayland { int width, height; - bool visible; + bool visible, created; bool hovered; bool transparent; struct wl_surface* surface; diff --git a/glfw/wl_window.c b/glfw/wl_window.c index a2bbd2ca1..e88634ccc 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -1143,6 +1143,7 @@ create_layer_shell_surface(_GLFWwindow *window) { layer_set_properties(window); commit_window_surface(window); wl_display_roundtrip(_glfw.wl.display); + window->wl.created = true; #undef ls return true; } @@ -1214,6 +1215,7 @@ create_window_desktop_surface(_GLFWwindow* window) commit_window_surface(window); wl_display_roundtrip(_glfw.wl.display); + window->wl.created = true; return true; } @@ -1413,22 +1415,17 @@ int _glfwPlatformCreateWindow( if (!createSurface(window, wndconfig)) return false; if (wndconfig->title) window->wl.title = _glfw_strdup(wndconfig->title); if (wndconfig->maximized) window->wl.maximize_on_first_show = true; - - if (wndconfig->visible) - { - if (!create_window_desktop_surface(window)) - return false; - + if (wndconfig->visible) { + if (!create_window_desktop_surface(window)) return false; window->wl.visible = true; - } - else - { + } else { + window->wl.visible = false; window->wl.xdg.surface = NULL; window->wl.xdg.toplevel = NULL; window->wl.layer_shell.zwlr_layer_surface_v1 = NULL; - window->wl.visible = false; } + window->wl.currentCursor = NULL; // Don't set window->wl.cursorTheme to NULL here. @@ -1722,10 +1719,15 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window) void _glfwPlatformShowWindow(_GLFWwindow* window) { if (!window->wl.visible) { - // workaround for kwin layer shell bug: https://bugs.kde.org/show_bug.cgi?id=503121 - if (is_layer_shell(window)) layer_set_properties(window); - window->wl.visible = true; - commit_window_surface(window); + if (!window->wl.created) { + create_window_desktop_surface(window); + window->wl.visible = true; + } else { + // workaround for kwin layer shell bug: https://bugs.kde.org/show_bug.cgi?id=503121 + if (is_layer_shell(window)) layer_set_properties(window); + window->wl.visible = true; + commit_window_surface(window); + } debug("Window %llu mapped waiting for configure event from compositor\n", window->id); } } diff --git a/kittens/panel/main.py b/kittens/panel/main.py index e3ce2a98b..811fc66dd 100644 --- a/kittens/panel/main.py +++ b/kittens/panel/main.py @@ -172,6 +172,11 @@ When set and using :option:`--single-instance` will toggle the visibility of the existing panel rather than creating a new one. +--start-as-hidden +type=bool-set +Start in hidden mode, useful with :option:`--toggle-visibility`. + + --debug-rendering type=bool-set For internal debugging use. @@ -338,6 +343,8 @@ def main(sys_args: list[str]) -> None: sys.argv.extend(('--class', args.cls)) if args.name: sys.argv.extend(('--name', args.name)) + if args.start_as_hidden: + sys.argv.append('--start-as=hidden') for override in args.override: sys.argv.extend(('--override', override)) sys.argv.append('--override=linux_display_server=auto') diff --git a/kitty/cli.py b/kitty/cli.py index 07a799b9a..12e9955c3 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -959,13 +959,14 @@ previous instance is found, then :italic:`{appname}` will wait anyway, regardless of this option. -{listen_on_defn} +{listen_on_defn} To start in headless mode, +without an actual window, use :option:`{appname} --start-as`=hidden. --start-as type=choices default=normal -choices=normal,fullscreen,maximized,minimized +choices=normal,fullscreen,maximized,minimized,hidden Control how the initial kitty window is created. diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index aea54eb43..518c1f910 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -311,6 +311,7 @@ WINDOW_NORMAL: int = 0 WINDOW_FULLSCREEN: int WINDOW_MAXIMIZED: int WINDOW_MINIMIZED: int +WINDOW_HIDDEN: int TEXT_SIZE_CODE: int TOP_EDGE: int BOTTOM_EDGE: int diff --git a/kitty/glfw.c b/kitty/glfw.c index f30933299..6b179b2ad 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -989,6 +989,8 @@ change_state_for_os_window(OSWindow *w, int state) { if (is_os_window_fullscreen(w)) toggle_fullscreen_for_os_window(w); else glfwRestoreWindow(w->handle); break; + case WINDOW_HIDDEN: + glfwHideWindow(w->handle); break; } } @@ -1229,8 +1231,12 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { if (!PyArg_ParseTupleAndKeywords(args, kw, "OOsss|OOOOpO", (char**)kwlist, &get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &optional_window_state, &load_programs, &optional_x, &optional_y, &disallow_override_title, &layer_shell_config)) return NULL; GLFWLayerShellConfig *lsc = NULL, lsc_stack = {0}; - + if (optional_window_state && optional_window_state != Py_None) { + if (!PyLong_Check(optional_window_state)) { PyErr_SetString(PyExc_TypeError, "window_state must be an int"); return NULL; } + window_state = (int) PyLong_AsLong(optional_window_state); + } if (layer_shell_config && layer_shell_config != Py_None ) { + if (window_state != WINDOW_HIDDEN) window_state = WINDOW_NORMAL; #ifdef __APPLE__ lsc = &lsc_stack; #else @@ -1243,10 +1249,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { } #endif } else { - if (optional_window_state && optional_window_state != Py_None) { if (!PyLong_Check(optional_window_state)) { PyErr_SetString(PyExc_TypeError, "window_state must be an int"); return NULL; } window_state = (int) PyLong_AsLong(optional_window_state); } if (optional_x && optional_x != Py_None) { if (!PyLong_Check(optional_x)) { PyErr_SetString(PyExc_TypeError, "x must be an int"); return NULL;} x = (int)PyLong_AsLong(optional_x); } if (optional_y && optional_y != Py_None) { if (!PyLong_Check(optional_y)) { PyErr_SetString(PyExc_TypeError, "y must be an int"); return NULL;} y = (int)PyLong_AsLong(optional_y); } - if (window_state < WINDOW_NORMAL || window_state > WINDOW_MINIMIZED) window_state = WINDOW_NORMAL; + if (window_state < WINDOW_NORMAL || window_state > WINDOW_HIDDEN) window_state = WINDOW_NORMAL; } if (PyErr_Occurred()) return NULL; @@ -1301,8 +1306,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { glfwWindowHint(GLFW_WAYLAND_BGCOLOR, ((bgalpha & 0xff) << 24) | bgcolor); // We use a temp window to avoid the need to set the window size after // creation, which causes a resize event and all the associated processing. - // The temp window is used to get the DPI. - if (!global_state.is_wayland) glfwWindowHint(GLFW_VISIBLE, false); + // The temp window is used to get the DPI. On Wayland no temp window can be + // used, so start with window visible unless hidden window requested. + glfwWindowHint(GLFW_VISIBLE, window_state != WINDOW_HIDDEN && global_state.is_wayland); GLFWwindow *common_context = global_state.num_os_windows ? global_state.os_windows[0].handle : NULL; GLFWwindow *temp_window = NULL; #ifdef __APPLE__ @@ -1356,7 +1362,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { if (pret == NULL) return NULL; Py_DECREF(pret); if (x != INT_MIN && y != INT_MIN) glfwSetWindowPos(glfw_window, x, y); - if (!global_state.is_apple && !global_state.is_wayland) glfwShowWindow(glfw_window); + if (!global_state.is_apple && !global_state.is_wayland && window_state != WINDOW_HIDDEN) glfwShowWindow(glfw_window); if (global_state.is_wayland || global_state.is_apple) { float n_xscale, n_yscale; double n_xdpi, n_ydpi; @@ -1447,7 +1453,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { if (window_state != WINDOW_NORMAL) change_state_for_os_window(w, window_state); #ifdef __APPLE__ // macOS: Show the window after it is ready - glfwShowWindow(glfw_window); + if (window_state != WINDOW_HIDDEN) glfwShowWindow(glfw_window); #endif w->redraw_count = 1; debug("OS Window created\n"); diff --git a/kitty/state.c b/kitty/state.c index a75f5f608..db1ffe07f 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -1556,6 +1556,7 @@ init_state(PyObject *module) { PyModule_AddIntMacro(module, WINDOW_NORMAL); PyModule_AddIntMacro(module, WINDOW_FULLSCREEN); PyModule_AddIntMacro(module, WINDOW_MAXIMIZED); + PyModule_AddIntMacro(module, WINDOW_HIDDEN); PyModule_AddIntMacro(module, WINDOW_MINIMIZED); PyModule_AddIntMacro(module, TOP_EDGE); PyModule_AddIntMacro(module, BOTTOM_EDGE); diff --git a/kitty/state.h b/kitty/state.h index 6093840e1..1c42f7b4b 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -22,7 +22,7 @@ typedef enum { LEFT_EDGE = 1, TOP_EDGE = 2, RIGHT_EDGE = 4, BOTTOM_EDGE = 8 } Edge; typedef enum { REPEAT_MIRROR, REPEAT_CLAMP, REPEAT_DEFAULT } RepeatStrategy; -typedef enum { WINDOW_NORMAL, WINDOW_FULLSCREEN, WINDOW_MAXIMIZED, WINDOW_MINIMIZED } WindowState; +typedef enum { WINDOW_NORMAL, WINDOW_FULLSCREEN, WINDOW_MAXIMIZED, WINDOW_MINIMIZED, WINDOW_HIDDEN } WindowState; typedef struct { char_type string[16]; diff --git a/kitty/utils.py b/kitty/utils.py index 7d5aa495e..634d999e4 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -30,7 +30,7 @@ from .constants import ( shell_path, ssh_control_master_template, ) -from .fast_data_types import WINDOW_FULLSCREEN, WINDOW_MAXIMIZED, WINDOW_MINIMIZED, WINDOW_NORMAL, Color, Shlex, get_options, monotonic, open_tty +from .fast_data_types import WINDOW_FULLSCREEN, WINDOW_HIDDEN, WINDOW_MAXIMIZED, WINDOW_MINIMIZED, WINDOW_NORMAL, Color, Shlex, get_options, monotonic, open_tty from .fast_data_types import timed_debug_print as _timed_debug_print from .types import run_once from .typing import AddressFamily, PopenType, StartupCtx @@ -401,10 +401,19 @@ def parse_address_spec(spec: str) -> tuple[AddressFamily, tuple[str, int] | str, def parse_os_window_state(state: str) -> int: - return { - 'normal': WINDOW_NORMAL, 'maximized': WINDOW_MAXIMIZED, 'minimized': WINDOW_MINIMIZED, - 'fullscreen': WINDOW_FULLSCREEN, 'fullscreened':WINDOW_FULLSCREEN - }[state] + match state: + case 'normal': + return WINDOW_NORMAL + case 'maximized': + return WINDOW_MAXIMIZED + case 'minimized': + return WINDOW_MINIMIZED + case 'fullscreen' | 'fullscreened': + return WINDOW_FULLSCREEN + case 'hidden': + return WINDOW_HIDDEN + case _: + return WINDOW_NORMAL def write_all(fd: int, data: str | bytes, block_until_written: bool = True) -> None: