kitty can finally natively implement a quake like terminal dropdown

Currently Wayland (except for GNOME as usual) only.
This commit is contained in:
Kovid Goyal 2025-04-21 20:25:10 +05:30
parent 9b5d5bf678
commit 85d58de035
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
7 changed files with 100 additions and 33 deletions

View file

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

View file

@ -62,4 +62,31 @@ panels and desktop components:
* `pawbar <https://github.com/codelif/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

59
glfw/wl_window.c vendored
View file

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

View file

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

View file

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

View file

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

View file

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