mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-09-11 14:45:31 +03:00
Infrastructure to go from panel CLI opts all the way to wayland layer shell implementation
This commit is contained in:
parent
56978189e0
commit
333ea519ed
@ -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
3
glfw/glfw3.h
vendored
@ -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
11
glfw/wl_window.c
vendored
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
3
kitty/glfw-wrapper.h
generated
@ -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.
|
||||
|
59
kitty/glfw.c
59
kitty/glfw.c
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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]:
|
||||
|
Loading…
Reference in New Issue
Block a user