Infrastructure to go from panel CLI opts all the way to wayland layer shell implementation

This commit is contained in:
Kovid Goyal 2024-03-24 13:58:26 +05:30
parent 56978189e0
commit 333ea519ed
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
10 changed files with 135 additions and 49 deletions

View File

@ -30,7 +30,9 @@ activity, CPU load, date/time, etc.
.. note::
This kitten currently only works on X11 desktops
This kitten currently only works on X11 desktops and Wayland compositors
that support the `wlr layer shell protocol
<https://wayland.app/protocols/wlr-layer-shell-unstable-v1#compositor-support>`__
Using this kitten is simple, for example::

3
glfw/glfw3.h vendored
View File

@ -1303,7 +1303,8 @@ typedef struct GLFWLayerShellConfig {
GLFWEdge edge;
const char *output_name;
GLFWFocusPolicy focus_policy;
void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, float scale, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height);
unsigned size_in_cells;
void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height);
} GLFWLayerShellConfig;
/*! @brief The function pointer type for error callbacks.

11
glfw/wl_window.c vendored
View File

@ -795,20 +795,17 @@ static void
layer_surface_handle_configure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial, uint32_t width, uint32_t height) {
debug("Layer shell configure event: width: %u height: %u\n", width, height);
_GLFWwindow* window = data;
unsigned monitor_width = 0, monitor_height = 0;
if (window->wl.monitorsCount) {
_GLFWmonitor *m = window->wl.monitors[0];
monitor_width = m->currentMode.width; monitor_height = m->currentMode.height;
}
GLFWvidmode m = {0};
if (window->wl.monitorsCount) _glfwPlatformGetVideoMode(window->wl.monitors[0], &m);
window->wl.layer_shell.config.size_callback(
(GLFWwindow*)window, &window->wl.layer_shell.config, _glfwWaylandWindowScale(window), monitor_width, monitor_height,
&width, &height);
(GLFWwindow*)window, &window->wl.layer_shell.config, m.width, m.height, &width, &height);
zwlr_layer_surface_v1_ack_configure(surface, serial);
if ((int)width != window->wl.width || (int)height != window->wl.height) {
debug("Layer shell size changed to %ux%u in layer_surface_handle_configure\n", width, height);
_glfwInputWindowSize(window, width, height);
window->wl.width = width; window->wl.height = height;
resizeFramebuffer(window);
_glfwInputWindowDamage(window);
}
commit_window_surface_if_safe(window);
}

View File

@ -8,27 +8,34 @@
from kitty.cli import parse_args
from kitty.cli_stub import PanelCLIOptions
from kitty.constants import appname, is_macos, is_wayland
from kitty.fast_data_types import make_x11_window_a_dock_window
from kitty.os_window_size import WindowSizeData
from kitty.fast_data_types import (
GLFW_EDGE_BOTTOM,
GLFW_EDGE_LEFT,
GLFW_EDGE_RIGHT,
GLFW_EDGE_TOP,
GLFW_LAYER_SHELL_BACKGROUND,
GLFW_LAYER_SHELL_PANEL,
glfw_primary_monitor_size,
make_x11_window_a_dock_window,
)
from kitty.os_window_size import WindowSizeData, edge_spacing
from kitty.types import LayerShellConfig
from kitty.typing import EdgeLiteral
OPTIONS = r'''
--lines
--lines --columns
type=int
default=1
The number of lines shown in the panel (the height of the panel). Applies to horizontal panels.
--columns
type=int
default=20
The number of columns shown in the panel (the width of the panel). Applies to vertical panels.
The number of lines shown in the panel if horizontal otherwise the number of columns shown in the panel. Ignored for background panels.
--edge
choices=top,bottom,left,right
choices=top,bottom,left,right,background
default=top
Which edge of the screen to place the panel on. Note that some window managers
(such as i3) do not support placing docked windows on the left and right edges.
The value :code:`background` means make the panel the "desktop wallpaper". This
is only supported on Wayland, not X11.
--config -c
@ -42,6 +49,12 @@
Syntax: :italic:`name=value`. For example: :option:`kitty +kitten panel -o` font_size=20
--output-name
On Wayland, the panel can only be displayed on a single monitor (output) at a time. This allows
you to specify which output is used, by name. If not specified the compositor will choose an
output automatically, typically the last output the user interacted with or the primary monitor.
--class
dest=cls
default={appname}-panel
@ -96,26 +109,17 @@ def create_right_strut(win_id: int, width: int, height: int) -> Strut:
def setup_x11_window(win_id: int) -> None:
if is_wayland():
return
func = globals()[f'create_{args.edge}_strut']
strut = func(win_id, window_width, window_height)
make_x11_window_a_dock_window(win_id, strut)
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
from kitty.fast_data_types import glfw_primary_monitor_size
from kitty.typing import EdgeLiteral
def effective_margin(which: EdgeLiteral) -> float:
ans: float = getattr(opts.single_window_margin_width, which)
if ans < 0:
ans = getattr(opts.window_margin_width, which)
return ans
def effective_padding(which: EdgeLiteral) -> float:
ans: float = getattr(opts.single_window_padding_width, which)
if ans < 0:
ans = getattr(opts.window_padding_width, which)
return ans
def es(which: EdgeLiteral) -> float:
return edge_spacing(which, opts)
def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]:
if not is_macos and not is_wayland():
@ -125,20 +129,26 @@ def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y:
monitor_width, monitor_height = glfw_primary_monitor_size()
if args.edge in {'top', 'bottom'}:
spacing = effective_margin('top') + effective_margin('bottom')
spacing += effective_padding('top') + effective_padding('bottom')
spacing = es('top') + es('bottom')
window_height = int(cell_height * args.lines / yscale + (dpi_y / 72) * spacing + 1)
window_width = monitor_width
elif args.edge == 'background':
window_width, window_height = monitor_width, monitor_height
else:
spacing = effective_margin('left') + effective_margin('right')
spacing += effective_padding('left') + effective_padding('right')
window_width = int(cell_width * args.columns / xscale + (dpi_x / 72) * spacing + 1)
spacing = es('left') + es('right')
window_width = int(cell_width * args.lines / xscale + (dpi_x / 72) * spacing + 1)
window_height = monitor_height
return window_width, window_height
return initial_window_size
def layer_shell_config(opts: PanelCLIOptions) -> LayerShellConfig:
ltype = GLFW_LAYER_SHELL_BACKGROUND if opts.edge == 'background' else GLFW_LAYER_SHELL_PANEL
edge = {'top': GLFW_EDGE_TOP, 'bottom': GLFW_EDGE_BOTTOM, 'left': GLFW_EDGE_LEFT, 'right': GLFW_EDGE_RIGHT}.get(opts.edge, GLFW_EDGE_TOP)
return LayerShellConfig(type=ltype, edge=edge, size_in_cells=max(1, opts.lines), output_name=opts.output_name or None)
def main(sys_args: List[str]) -> None:
global args
if is_macos or not os.environ.get('DISPLAY'):
@ -154,10 +164,12 @@ def main(sys_args: List[str]) -> None:
sys.argv.extend(('--name', args.name))
for override in args.override:
sys.argv.extend(('--override', override))
sys.argv.append('--override=linux_display_server=auto')
sys.argv.extend(items)
from kitty.main import main as real_main
from kitty.main import run_app
run_app.cached_values_name = 'panel'
run_app.layer_shell_config = layer_shell_config(args)
run_app.first_window_callback = setup_x11_window
run_app.initial_window_size_func = initial_window_size_func
real_main()

View File

@ -8,6 +8,7 @@ from kitty.fonts.render import FontObject
from kitty.marks import MarkerFunc
from kitty.options.types import Options
from kitty.types import LayerShellConfig, SignalInfo
from kitty.typing import EdgeLiteral
# Constants {{{
GLFW_LAYER_SHELL_NONE: int
@ -586,7 +587,7 @@ def glfw_terminate() -> None:
pass
def glfw_init(path: str, debug_keyboard: bool = False, debug_rendering: bool = False) -> bool:
def glfw_init(path: str, edge_spacing_func: Callable[[EdgeLiteral], float], debug_keyboard: bool = False, debug_rendering: bool = False) -> bool:
pass

3
kitty/glfw-wrapper.h generated
View File

@ -1041,7 +1041,8 @@ typedef struct GLFWLayerShellConfig {
GLFWEdge edge;
const char *output_name;
GLFWFocusPolicy focus_policy;
void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, float scale, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height);
unsigned size_in_cells;
void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height);
} GLFWLayerShellConfig;
/*! @brief The function pointer type for error callbacks.

View File

@ -1047,14 +1047,63 @@ native_window_handle(GLFWwindow *w) {
return Py_None;
}
static PyObject* edge_spacing_func = NULL;
static double
edge_spacing(GLFWEdge which) {
const char* edge = "top";
switch(which) {
case GLFW_EDGE_TOP: edge = "top"; break;
case GLFW_EDGE_BOTTOM: edge = "bottom"; break;
case GLFW_EDGE_LEFT: edge = "left"; break;
case GLFW_EDGE_RIGHT: edge = "right"; break;
}
if (!edge_spacing_func) {
log_error("Attempt to call edge_spacing() without first setting edge_spacing_func");
return 100;
}
RAII_PyObject(ret, PyObject_CallFunction(edge_spacing_func, "s", edge));
if (!ret) { PyErr_Print(); return 100; }
if (!PyFloat_Check(ret)) { log_error("edge_spacing_func() return something other than a float"); return 100; }
return PyFloat_AsDouble(ret);
}
static void
calculate_layer_shell_window_size(
GLFWwindow *window UNUSED, const GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height) {
if (config->type == GLFW_LAYER_SHELL_BACKGROUND) {
if (!*width) *width = monitor_width;
if (!*height) *height = monitor_height;
return;
}
OSWindow *os_window = os_window_for_glfw_window(window);
if (!os_window) return;
float xscale, yscale;
glfwGetWindowContentScale(window, &xscale, &yscale);
if (config->edge == GLFW_EDGE_LEFT || config->edge == GLFW_EDGE_RIGHT) {
if (!*height) *height = monitor_height;
double spacing = edge_spacing(GLFW_EDGE_LEFT) + edge_spacing(GLFW_EDGE_RIGHT);
spacing *= os_window->logical_dpi_x / 72.;
spacing += (os_window->fonts_data->cell_width * config->size_in_cells) / yscale;
*width = (uint32_t)(1. + spacing);
} else {
if (!*width) *width = monitor_width;
double spacing = edge_spacing(GLFW_EDGE_TOP) + edge_spacing(GLFW_EDGE_BOTTOM);
spacing *= os_window->logical_dpi_y / 72.;
spacing += 1. + (os_window->fonts_data->cell_height * config->size_in_cells) / xscale;
*height = (uint32_t)(1. + spacing);
}
}
static GLFWLayerShellConfig
translate_layer_shell_config(PyObject *p) {
GLFWLayerShellConfig ans = {0};
GLFWLayerShellConfig ans = {.size_callback=calculate_layer_shell_window_size};
#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);
A(size_in_cells, PyLong_Check, PyLong_AsLong);
#undef A
return ans;
}
@ -1370,7 +1419,10 @@ static PyObject*
glfw_init(PyObject UNUSED *self, PyObject *args) {
const char* path;
int debug_keyboard = 0, debug_rendering = 0;
if (!PyArg_ParseTuple(args, "s|pp", &path, &debug_keyboard, &debug_rendering)) return NULL;
PyObject *edge_sf;
if (!PyArg_ParseTuple(args, "sO|pp", &path, &edge_sf, &debug_keyboard, &debug_rendering)) return NULL;
if (!PyCallable_Check(edge_sf)) { PyErr_SetString(PyExc_TypeError, "edge_spacing_func must be a callable"); return NULL; }
Py_CLEAR(edge_spacing_func);
#ifdef __APPLE__
cocoa_set_uncaught_exception_handler();
#endif
@ -1400,6 +1452,7 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
global_state.default_dpi.x = w.logical_dpi_x;
global_state.default_dpi.y = w.logical_dpi_y;
}
edge_spacing_func = edge_sf; Py_INCREF(edge_spacing_func);
Py_INCREF(ans);
return ans;
}
@ -1413,6 +1466,7 @@ glfw_terminate(PYNOARG) {
}
}
glfwTerminate();
Py_CLEAR(edge_spacing_func);
Py_RETURN_NONE;
}
@ -2183,6 +2237,7 @@ static PyMethodDef module_methods[] = {
void cleanup_glfw(void) {
if (logo.pixels) free(logo.pixels);
logo.pixels = NULL;
Py_CLEAR(edge_spacing_func);
#ifndef __APPLE__
release_freetype_render_context(csd_title_render_ctx);
#endif

View File

@ -47,7 +47,7 @@
from .fonts.render import set_font_family
from .options.types import Options
from .options.utils import DELETE_ENV_VAR
from .os_window_size import initial_window_size_func
from .os_window_size import edge_spacing, initial_window_size_func
from .session import create_sessions, get_os_window_sizing_data
from .shaders import CompileError, load_shader_programs
from .types import LayerShellConfig, SingleInstanceData
@ -135,7 +135,7 @@ def load_all_shaders(semi_transparent: bool = False) -> None:
def init_glfw_module(glfw_module: str, debug_keyboard: bool = False, debug_rendering: bool = False) -> None:
if not glfw_init(glfw_path(glfw_module), debug_keyboard, debug_rendering):
if not glfw_init(glfw_path(glfw_module), edge_spacing, debug_keyboard, debug_rendering):
raise SystemExit('GLFW initialization failed')
@ -539,15 +539,15 @@ def _main() -> None:
with suppress(AttributeError): # python compiled without threading
sys.setswitchinterval(1000.0) # we have only a single python thread
if cli_opts.watcher:
from .window import global_watchers
global_watchers.set_extra(cli_opts.watcher)
log_error('The --watcher command line option has been deprecated in favor of using the watcher option in kitty.conf')
# mask the signals now as on some platforms the display backend starts
# threads. These threads must not handle the masked signals, to ensure
# kitty can handle them. See https://github.com/kovidgoyal/kitty/issues/4636
mask_kitty_signals_process_wide()
init_glfw(opts, cli_opts.debug_keyboard, cli_opts.debug_rendering)
if cli_opts.watcher:
from .window import global_watchers
global_watchers.set_extra(cli_opts.watcher)
log_error('The --watcher command line option has been deprecated in favor of using the watcher option in kitty.conf')
try:
with setup_profiling():
# Avoid needing to launch threads to reap zombies

View File

@ -1,9 +1,11 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Any, Callable, Dict, NamedTuple, Tuple
from typing import Any, Callable, Dict, NamedTuple, Optional, Tuple, Union
from .constants import is_macos, is_wayland
from .fast_data_types import get_options
from .options.types import Options
from .types import FloatEdges
from .typing import EdgeLiteral
from .utils import log_error
@ -35,6 +37,20 @@ def sanitize_window_size(x: Any) -> int:
return max(20, min(ans, 50000))
def edge_spacing(which: EdgeLiteral, opts:Optional[Union[WindowSizeData, Options]] = None) -> float:
if opts is None:
opts = get_options()
margin: float = getattr(opts.single_window_margin_width, which)
if margin < 0:
margin = getattr(opts.window_margin_width, which)
padding: float = getattr(opts.single_window_padding_width, which)
if padding < 0:
padding = getattr(opts.window_padding_width, which)
return padding + margin
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
if 'window-size' in cached_values and opts.remember_window_size:

View File

@ -71,6 +71,7 @@ class LayerShellConfig(NamedTuple):
edge: int = 0
focus_policy: int = 0
output_name: Optional[str] = None
size_in_cells: int = 0
def mod_to_names(mods: int, has_kitty_mod: bool = False, kitty_mod: int = 0) -> Iterator[str]: