Implement starting kitty hidden

Fixes #3466
This commit is contained in:
Kovid Goyal
2025-04-23 08:50:02 +05:30
parent ca30b4196b
commit c1b6b4494a
11 changed files with 64 additions and 30 deletions

View File

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

View File

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

2
glfw/wl_platform.h vendored
View File

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

22
glfw/wl_window.c vendored
View File

@@ -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) {
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);
}
}

View File

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

View File

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

View File

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

View File

@@ -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");

View File

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

View File

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

View File

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