From 85d58de035517c5c4b69546691167cfcd69e803e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 21 Apr 2025 20:25:10 +0530 Subject: [PATCH] kitty can finally natively implement a quake like terminal dropdown Currently Wayland (except for GNOME as usual) only. --- docs/changelog.rst | 2 ++ docs/kittens/panel.rst | 27 ++++++++++++++++++ glfw/wl_window.c | 59 ++++++++++++++++++++++----------------- kittens/panel/main.py | 21 +++++++++++--- kitty/fast_data_types.pyi | 1 + kitty/glfw.c | 11 ++++++++ setup.py | 12 +++++--- 7 files changed, 100 insertions(+), 33 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e0227b724..7ad59f544 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -97,6 +97,8 @@ Detailed list of changes 0.42.0 [future] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- The panel kitten can now be used to :ref:`quake` + - **Behavior change**: Now kitty does full grapheme segmentation following the Unicode 16 spec when splitting text into cells (:iss:`8533`) diff --git a/docs/kittens/panel.rst b/docs/kittens/panel.rst index 9956ae181..0ce6df62d 100644 --- a/docs/kittens/panel.rst +++ b/docs/kittens/panel.rst @@ -62,4 +62,31 @@ panels and desktop components: * `pawbar `__ +.. _quake: + +Make a Quake like quick access terminal +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 0.42.0 + Support for quake mode, works only on Wayland, except for GNOME. + +This kitten can be used to make a quick access terminal, that appears and +disappears at a key press. To do so use the following command:: + + kitty +kitten panel --edge=top --layer=overlay --lines=15 \ + --focus-policy=exclusive --exclusive-zone=0 --override-exclusive-zone \ + -o background_opacity=0.8 --toggle-visibility --single-instance \ + --instance-group=quake kitten run-shell + +Run this command in a terminal, and a quick access kitty panel will show up at +the top of your screen. Run it again, and the panel will be hidden. + +Simply bind this command to some key press in your window manager or desktop +environment settings and then you have a quick access terminal at a single key press. +You can use the various panel options to configure the size, appearance and +position of the quick access panel. In particular, the :option:`kitty +kitten panel --config` and +:option:`kitty +kitten panel --override` options can be used to theme the terminal appropriately, +making it look different from regular kitty terminal instances. + + .. include:: ../generated/cli-kitten-panel.rst diff --git a/glfw/wl_window.c b/glfw/wl_window.c index f78ff2f54..7030ca7fc 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -1341,6 +1341,26 @@ struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape, struct wl_cursor_theme* ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// +static bool +attach_opengl_context_to_window(_GLFWwindow *window, const _GLFWctxconfig *ctxconfig, const _GLFWfbconfig *fbconfig) { + if (ctxconfig->source == GLFW_EGL_CONTEXT_API || + ctxconfig->source == GLFW_NATIVE_CONTEXT_API) + { + if (!_glfwInitEGL()) + return false; + if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) + return false; + } + else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) + { + if (!_glfwInitOSMesa()) + return false; + if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) + return false; + } + return true; +} + int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, @@ -1383,24 +1403,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, // and only then create the OpenGL context. if (window->wl.visible) loop_till_window_fully_created(window); debug("Creating OpenGL context and attaching it to window\n"); - if (ctxconfig->client != GLFW_NO_API) - { - if (ctxconfig->source == GLFW_EGL_CONTEXT_API || - ctxconfig->source == GLFW_NATIVE_CONTEXT_API) - { - if (!_glfwInitEGL()) - return false; - if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) - return false; - } - else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) - { - if (!_glfwInitOSMesa()) - return false; - if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) - return false; - } - } + if (ctxconfig->client != GLFW_NO_API) attach_opengl_context_to_window(window, ctxconfig, fbconfig); return true; } @@ -1682,23 +1685,29 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window) void _glfwPlatformShowWindow(_GLFWwindow* window) { if (!window->wl.visible) { - create_window_desktop_surface(window); + if (!is_layer_shell(window)) create_window_desktop_surface(window); window->wl.visible = true; + wl_surface_commit(window->wl.surface); } } void _glfwPlatformHideWindow(_GLFWwindow* window) { - if (window->wl.xdg.toplevel) - { - xdg_toplevel_destroy(window->wl.xdg.toplevel); - xdg_surface_destroy(window->wl.xdg.surface); + if (!window->wl.visible) return; + if (is_layer_shell(window)) { + wl_surface_attach(window->wl.surface, NULL, 0, 0); + } else { + if (window->wl.xdg.toplevel) { + xdg_toplevel_destroy(window->wl.xdg.toplevel); + xdg_surface_destroy(window->wl.xdg.surface); + } window->wl.xdg.toplevel = NULL; window->wl.xdg.surface = NULL; - window->wl.once.surface_configured = false; - window->swaps_disallowed = true; } + window->wl.once.surface_configured = false; + window->swaps_disallowed = true; window->wl.visible = false; + wl_surface_commit(window->wl.surface); } static void diff --git a/kittens/panel/main.py b/kittens/panel/main.py index 9f052e74e..37734453e 100644 --- a/kittens/panel/main.py +++ b/kittens/panel/main.py @@ -26,6 +26,7 @@ from kitty.fast_data_types import ( GLFW_LAYER_SHELL_TOP, glfw_primary_monitor_size, make_x11_window_a_dock_window, + toggle_os_window_visibility, ) from kitty.os_window_size import WindowSizeData, edge_spacing from kitty.types import LayerShellConfig @@ -161,6 +162,12 @@ panel invocations with the same :option:`--instance-group` will result in new panels being created in the first panel instance within that group. +--toggle-visibility +type=bool-set +When set and using :option:`--single-instance` will toggle the visibility of the +existing panel rather than creating a new one. + + --debug-rendering type=bool-set For internal debugging use. @@ -265,10 +272,12 @@ def dual_distance(spec: str, min_cell_value_if_no_pixels: int = 0) -> tuple[int, def layer_shell_config(opts: PanelCLIOptions) -> LayerShellConfig: - ltype = {'background': GLFW_LAYER_SHELL_BACKGROUND, - 'bottom': GLFW_LAYER_SHELL_PANEL, - 'top': GLFW_LAYER_SHELL_TOP, - 'overlay': GLFW_LAYER_SHELL_OVERLAY}.get(opts.layer, GLFW_LAYER_SHELL_PANEL) + ltype = { + 'background': GLFW_LAYER_SHELL_BACKGROUND, + 'bottom': GLFW_LAYER_SHELL_PANEL, + 'top': GLFW_LAYER_SHELL_TOP, + 'overlay': GLFW_LAYER_SHELL_OVERLAY + }.get(opts.layer, GLFW_LAYER_SHELL_PANEL) ltype = GLFW_LAYER_SHELL_BACKGROUND if opts.edge == 'background' else ltype edge = { 'top': GLFW_EDGE_TOP, 'bottom': GLFW_EDGE_BOTTOM, 'left': GLFW_EDGE_LEFT, 'right': GLFW_EDGE_RIGHT, 'center': GLFW_EDGE_CENTER, 'none': GLFW_EDGE_NONE @@ -294,6 +303,10 @@ def layer_shell_config(opts: PanelCLIOptions) -> LayerShellConfig: 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:])) + if args.toggle_visibility and boss.os_window_map: + for os_window_id in boss.os_window_map: + toggle_os_window_visibility(os_window_id) + return items = items or [kitten_exe(), 'run-shell'] lsc = layer_shell_config(args) os_window_id = boss.add_os_panel(lsc, args.cls, args.name) diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 552b82144..cea9bf7b0 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1703,6 +1703,7 @@ def set_clipboard_data_types(ct: int, mime_types: Tuple[str, ...]) -> None: ... def get_clipboard_mime(ct: int, mime: Optional[str], callback: Callable[[bytes], None]) -> None: ... def run_with_activation_token(func: Callable[[str], None]) -> bool: ... def make_x11_window_a_dock_window(x11_window_id: int, strut: Tuple[int, int, int, int, int, int, int, int, int, int, int, int]) -> None: ... +def toggle_os_window_visibility(os_window_id: int) -> bool: ... def wrapped_kitten_names() -> List[str]: ... def expand_ansi_c_escapes(test: str) -> str: ... def update_tab_bar_edge_colors(os_window_id: int) -> bool: ... diff --git a/kitty/glfw.c b/kitty/glfw.c index c0486f2ef..f93d28528 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -2421,12 +2421,23 @@ is_layer_shell_supported(PyObject *self UNUSED, PyObject *args UNUSED) { #endif } +static PyObject* +toggle_os_window_visibility(PyObject *self UNUSED, PyObject *wid) { + if (!PyLong_Check(wid)) { PyErr_SetString(PyExc_TypeError, "os_window_id must be a int"); return NULL; } + id_type id = PyLong_AsUnsignedLongLong(wid); + OSWindow *w = os_window_for_id(id); + if (!w || !w->handle) Py_RETURN_FALSE; + if (glfwGetWindowAttrib(w->handle, GLFW_VISIBLE)) glfwHideWindow(w->handle); + else glfwShowWindow(w->handle); + Py_RETURN_TRUE; +} // Boilerplate {{{ static PyMethodDef module_methods[] = { METHODB(set_custom_cursor, METH_VARARGS), METHODB(is_css_pointer_name_valid, METH_O), + METHODB(toggle_os_window_visibility, METH_O), METHODB(pointer_name_to_css_name, METH_O), {"create_os_window", (PyCFunction)(void (*) (void))(create_os_window), METH_VARARGS | METH_KEYWORDS, NULL}, METHODB(set_default_window_icon, METH_VARARGS), diff --git a/setup.py b/setup.py index 8b29f7029..bd7a55584 100755 --- a/setup.py +++ b/setup.py @@ -1255,11 +1255,10 @@ def build_static_binaries(args: Options, launcher_dir: str) -> None: build_static_kittens(args, launcher_dir, args.dir_for_static_binaries, for_platform=(os_, arch)) -@lru_cache(2) -def kitty_cli_boolean_options() -> Tuple[str, ...]: - with open(os.path.join(src_base, 'kitty/cli.py')) as f: +def read_bool_options(path: str = 'kitty/cli.py') -> Tuple[str, ...]: + with open(os.path.join(src_base, path)) as f: raw = f.read() - m = re.search(r"^\s*OPTIONS = '''(.+?)'''", raw, flags=re.MULTILINE | re.DOTALL) + m = re.search(r"^\s*OPTIONS = r?'''(.+?)'''", raw, flags=re.MULTILINE | re.DOTALL) assert m is not None ans: List[str] = [] in_option: List[str] = [] @@ -1279,6 +1278,11 @@ def kitty_cli_boolean_options() -> Tuple[str, ...]: return tuple(ans) +@lru_cache(2) +def kitty_cli_boolean_options() -> Tuple[str, ...]: + return tuple(set(read_bool_options()) | set(read_bool_options('kittens/panel/main.py'))) + + def build_launcher(args: Options, launcher_dir: str = '.', bundle_type: str = 'source') -> None: werror = '' if args.ignore_compiler_warnings else '-pedantic-errors -Werror' cflags = f'-Wall {werror} -fpie'.split()