diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 395fd599f9e..6be57330787 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -7,9 +7,19 @@ from kitty.fonts import FontFeature from kitty.fonts.render import FontObject from kitty.marks import MarkerFunc from kitty.options.types import Options -from kitty.types import SignalInfo +from kitty.types import LayerShellConfig, SignalInfo # Constants {{{ +GLFW_LAYER_SHELL_NONE: int +GLFW_LAYER_SHELL_PANEL: int +GLFW_LAYER_SHELL_BACKGROUND: int +GLFW_EDGE_TOP: int +GLFW_EDGE_BOTTOM: int +GLFW_EDGE_LEFT: int +GLFW_EDGE_RIGHT: int +GLFW_FOCUS_NOT_ALLOWED: int +GLFW_FOCUS_EXCLUSIVE: int +GLFW_FOCUS_ON_DEMAND: int IMAGE_PLACEHOLDER_CHAR: int GLFW_PRIMARY_SELECTION: int GLFW_CLIPBOARD: int @@ -508,6 +518,7 @@ def create_os_window( x: Optional[int] = None, y: Optional[int] = None, disallow_override_title: bool = False, + layer_shell_config: Optional[LayerShellConfig] = None, ) -> int: pass diff --git a/kitty/glfw.c b/kitty/glfw.c index 6927fbbafbc..c577a5b237b 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1047,18 +1047,35 @@ native_window_handle(GLFWwindow *w) { return Py_None; } +static GLFWLayerShellConfig +translate_layer_shell_config(PyObject *p) { + GLFWLayerShellConfig ans = {0}; +#define A(attr, type_check, convert) RAII_PyObject(attr, PyObject_GetAttrString(p, #attr)); if (attr == NULL) return ans; if (!type_check(attr)) { PyErr_SetString(PyExc_TypeError, #attr " not of the correct type"); return ans; }; ans.attr = convert(attr); + A(output_name, PyUnicode_Check, PyUnicode_AsUTF8); + A(type, PyLong_Check, PyLong_AsLong); + A(edge, PyLong_Check, PyLong_AsLong); + A(focus_policy, PyLong_Check, PyLong_AsLong); +#undef A + return ans; +} + static PyObject* create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { int x = INT_MIN, y = INT_MIN, window_state = WINDOW_NORMAL, disallow_override_title = 0; char *title, *wm_class_class, *wm_class_name; - PyObject *optional_window_state = NULL, *load_programs = NULL, *get_window_size, *pre_show_callback, *optional_x = NULL, *optional_y = NULL; - static const char* kwlist[] = {"get_window_size", "pre_show_callback", "title", "wm_class_name", "wm_class_class", "window_state", "load_programs", "x", "y", "disallow_override_title", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kw, "OOsss|OOOOp", (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)) return NULL; - 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; + PyObject *optional_window_state = NULL, *load_programs = NULL, *get_window_size, *pre_show_callback, *optional_x = NULL, *optional_y = NULL, *layer_shell_config = NULL; + static const char* kwlist[] = {"get_window_size", "pre_show_callback", "title", "wm_class_name", "wm_class_class", "window_state", "load_programs", "x", "y", "disallow_override_title", "layer_shell_config", NULL}; + 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; + if (layer_shell_config && global_state.is_wayland) { + glfwWaylandSetupLayerShellForNextWindow(translate_layer_shell_config(layer_shell_config)); + } 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 (PyErr_Occurred()) return NULL; static bool is_first_window = true; if (is_first_window) { @@ -2183,6 +2200,9 @@ init_glfw(PyObject *m) { ADDC(GLFW_REPEAT); ADDC(true); ADDC(false); ADDC(GLFW_PRIMARY_SELECTION); ADDC(GLFW_CLIPBOARD); + ADDC(GLFW_LAYER_SHELL_NONE); ADDC(GLFW_LAYER_SHELL_PANEL); ADDC(GLFW_LAYER_SHELL_BACKGROUND); + ADDC(GLFW_FOCUS_NOT_ALLOWED); ADDC(GLFW_FOCUS_EXCLUSIVE); ADDC(GLFW_FOCUS_ON_DEMAND); + ADDC(GLFW_EDGE_TOP); ADDC(GLFW_EDGE_BOTTOM); ADDC(GLFW_EDGE_LEFT); ADDC(GLFW_EDGE_RIGHT); /* start glfw functional keys (auto generated by gen-key-constants.py do not edit) */ ADDC(GLFW_FKEY_ESCAPE); diff --git a/kitty/main.py b/kitty/main.py index aa65064bfd7..7a1c4fa9586 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -50,7 +50,7 @@ from .os_window_size import initial_window_size_func from .session import create_sessions, get_os_window_sizing_data from .shaders import CompileError, load_shader_programs -from .types import SingleInstanceData +from .types import LayerShellConfig, SingleInstanceData from .utils import ( cleanup_ssh_control_masters, detach, @@ -269,7 +269,7 @@ def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ()) run_app.initial_window_size_func(get_os_window_sizing_data(opts, startup_sessions[0] if startup_sessions else None), cached_values), pre_show_callback, args.title or appname, args.name or args.cls or appname, - wincls, wstate, load_all_shaders, disallow_override_title=bool(args.title)) + wincls, wstate, load_all_shaders, disallow_override_title=bool(args.title), layer_shell_config=run_app.layer_shell_config) boss = Boss(opts, args, cached_values, global_shortcuts) boss.start(window_id, startup_sessions) if bad_lines or boss.misc_config_errors: @@ -286,6 +286,7 @@ class AppRunner: def __init__(self) -> None: self.cached_values_name = 'main' self.first_window_callback = lambda window_handle: None + self.layer_shell_config: Optional[LayerShellConfig] = None self.initial_window_size_func = initial_window_size_func def __call__(self, opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ()) -> None: diff --git a/kitty/types.py b/kitty/types.py index 237b640857c..7ba1e00f77c 100644 --- a/kitty/types.py +++ b/kitty/types.py @@ -66,6 +66,13 @@ class SignalInfo(NamedTuple): sival_ptr: int +class LayerShellConfig(NamedTuple): + type: int = 0 + edge: int = 0 + focus_policy: int = 0 + output_name: Optional[str] = None + + def mod_to_names(mods: int, has_kitty_mod: bool = False, kitty_mod: int = 0) -> Iterator[str]: if has_kitty_mod: mods &= ~kitty_mod