3 Commits
Author SHA1 Message Date
Guido CellaandZuki Air 721f70ea8b implement ext-foreign-toplevel-list
This is used by lswt which lists toplevel ids and titles.
keyd-application-mapper will also support it hopefully.

This will also be needed to support screenshare of single windows
(https://codeberg.org/dwl/dwl/pulls/1157).

Co-authored-by: Zuki Air <zukirust@gmail.com>
2026-09-12 08:02:27 +02:00
0a6dc6d548 add support for layer-shell on-demand keyboard interactivity
Supersedes https://codeberg.org/dwl/dwl/pulls/674

Co-authored-by: Leonardo Hernández Hernández <leohdz172@proton.me>
Co-authored-by: MayOrMayNotBeACat <maybeacat804@gmail.com>
2026-09-12 08:00:20 +02:00
Guido Cella 4c54bd8113 implement xytonode() with toplevel_from_wlr_surface()
xytonode() does not return the layer surface under the cursor because
pnode->data is always NULL. It was not noticed because it is not passed
the LayerSurface argument anywhere. But it is noticeable after
implementing layer shell on-demand keyboard interactivity because layer
shell surfaces are never focused by sloppyfocus.

Rather than figuring out how to fix it, just replace its implementation
with toplevel_from_wlr_surface() which works and also reduces the SLOC.
2026-09-11 23:55:17 +02:00
+67 -30
View File
@@ -27,6 +27,7 @@
#include <wlr/types/wlr_ext_data_control_v1.h> #include <wlr/types/wlr_ext_data_control_v1.h>
#include <wlr/types/wlr_ext_image_capture_source_v1.h> #include <wlr/types/wlr_ext_image_capture_source_v1.h>
#include <wlr/types/wlr_ext_image_copy_capture_v1.h> #include <wlr/types/wlr_ext_image_copy_capture_v1.h>
#include <wlr/types/wlr_ext_foreign_toplevel_list_v1.h>
#include <wlr/types/wlr_fractional_scale_v1.h> #include <wlr/types/wlr_fractional_scale_v1.h>
#include <wlr/types/wlr_gamma_control_v1.h> #include <wlr/types/wlr_gamma_control_v1.h>
#include <wlr/types/wlr_idle_inhibit_v1.h> #include <wlr/types/wlr_idle_inhibit_v1.h>
@@ -119,6 +120,7 @@ typedef struct {
struct wlr_xwayland_surface *xwayland; struct wlr_xwayland_surface *xwayland;
} surface; } surface;
struct wlr_xdg_toplevel_decoration_v1 *decoration; struct wlr_xdg_toplevel_decoration_v1 *decoration;
struct wlr_ext_foreign_toplevel_handle_v1 *foreign_toplevel_handle;
struct wl_listener commit; struct wl_listener commit;
struct wl_listener map; struct wl_listener map;
struct wl_listener maximize; struct wl_listener maximize;
@@ -309,7 +311,7 @@ static void moveresize(const Arg *arg);
static void outputmgrapply(struct wl_listener *listener, void *data); static void outputmgrapply(struct wl_listener *listener, void *data);
static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test); static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test);
static void outputmgrtest(struct wl_listener *listener, void *data); static void outputmgrtest(struct wl_listener *listener, void *data);
static void pointerfocus(Client *c, struct wlr_surface *surface, static void pointerfocus(Client *c, LayerSurface* l, struct wlr_surface *surface,
double sx, double sy, uint32_t time); double sx, double sy, uint32_t time);
static void printstatus(void); static void printstatus(void);
static void powermgrsetmode(struct wl_listener *listener, void *data); static void powermgrsetmode(struct wl_listener *listener, void *data);
@@ -383,6 +385,7 @@ static struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard_mgr;
static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr; static struct wlr_virtual_pointer_manager_v1 *virtual_pointer_mgr;
static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr; static struct wlr_cursor_shape_manager_v1 *cursor_shape_mgr;
static struct wlr_output_power_manager_v1 *power_mgr; static struct wlr_output_power_manager_v1 *power_mgr;
static struct wlr_ext_foreign_toplevel_list_v1 *foreign_toplevel_list;
static struct wlr_pointer_constraints_v1 *pointer_constraints; static struct wlr_pointer_constraints_v1 *pointer_constraints;
static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr; static struct wlr_relative_pointer_manager_v1 *relative_pointer_mgr;
@@ -593,10 +596,13 @@ arrangelayers(Monitor *m)
for (i = 3; i >= 0; i--) for (i = 3; i >= 0; i--)
arrangelayer(m, &m->layers[i], &usable_area, 0); arrangelayer(m, &m->layers[i], &usable_area, 0);
/* Find topmost keyboard interactive layer, if such a layer exists */ /* Find topmost keyboard interactive layer that has indicated it wants
* exclusive access to the keyboard, if such a layer exists */
for (i = 0; i < (int)LENGTH(layers_above_shell); i++) { for (i = 0; i < (int)LENGTH(layers_above_shell); i++) {
wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) { wl_list_for_each_reverse(l, &m->layers[layers_above_shell[i]], link) {
if (locked || !l->layer_surface->current.keyboard_interactive || !l->mapped) if (locked ||
l->layer_surface->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE ||
!l->mapped)
continue; continue;
/* Deactivate the focused client. */ /* Deactivate the focused client. */
focusclient(NULL, 0); focusclient(NULL, 0);
@@ -629,6 +635,7 @@ buttonpress(struct wl_listener *listener, void *data)
struct wlr_keyboard *keyboard; struct wlr_keyboard *keyboard;
uint32_t mods; uint32_t mods;
Client *c; Client *c;
LayerSurface *l;
const Button *b; const Button *b;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
@@ -640,10 +647,15 @@ buttonpress(struct wl_listener *listener, void *data)
if (locked) if (locked)
break; break;
/* Change focus if the button was _pressed_ over a client */ /* Change focus if the button was _pressed_ over a client
xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); or a layer surface with on-demand keyboard interactivity */
if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) xytonode(cursor->x, cursor->y, NULL, &c, &l, NULL, NULL);
if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) {
focusclient(c, 1); focusclient(c, 1);
} else if (l && l->layer_surface->current.keyboard_interactive) {
focusclient(NULL, 0);
client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat));
}
keyboard = wlr_seat_get_keyboard(seat); keyboard = wlr_seat_get_keyboard(seat);
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
@@ -843,6 +855,11 @@ commitlayersurfacenotify(struct wl_listener *listener, void *data)
return; return;
} }
if (layer_surface == exclusive_focus
&& layer_surface->current.keyboard_interactive !=
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE)
exclusive_focus = NULL;
if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped) if (layer_surface->current.committed == 0 && l->mapped == layer_surface->surface->mapped)
return; return;
l->mapped = layer_surface->surface->mapped; l->mapped = layer_surface->surface->mapped;
@@ -1454,7 +1471,8 @@ focusclient(Client *c, int lift)
* and focus it after the overlay is closed. */ * and focus it after the overlay is closed. */
if (old_client_type == LayerShell && wlr_scene_node_coords( if (old_client_type == LayerShell && wlr_scene_node_coords(
&old_l->scene->node, &unused_lx, &unused_ly) &old_l->scene->node, &unused_lx, &unused_ly)
&& old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { && old_l->layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP
&& old_l->layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) {
return; return;
} else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) { } else if (old_c && old_c == exclusive_focus && client_wants_focus(old_c)) {
return; return;
@@ -1773,6 +1791,11 @@ mapnotify(struct wl_listener *listener, void *data)
Monitor *m; Monitor *m;
int i; int i;
struct wlr_ext_foreign_toplevel_handle_v1_state foreign_toplevel_state = {
.app_id = client_get_appid(c),
.title = client_get_title(c),
};
/* Create scene tree for this client and its border */ /* Create scene tree for this client and its border */
c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]); c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]);
/* Enabled later by a call to arrange() */ /* Enabled later by a call to arrange() */
@@ -1812,6 +1835,10 @@ mapnotify(struct wl_listener *listener, void *data)
wl_list_insert(&clients, &c->link); wl_list_insert(&clients, &c->link);
wl_list_insert(&fstack, &c->flink); wl_list_insert(&fstack, &c->flink);
c->foreign_toplevel_handle = wlr_ext_foreign_toplevel_handle_v1_create(
foreign_toplevel_list, &foreign_toplevel_state);
c->foreign_toplevel_handle->data = c;
/* Set initial monitor, tags, floating status, and focus: /* Set initial monitor, tags, floating status, and focus:
* we always consider floating, clients that have parent and thus * we always consider floating, clients that have parent and thus
* we set the same tags and monitor as its parent. * we set the same tags and monitor as its parent.
@@ -1894,20 +1921,20 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
{ {
double sx = 0, sy = 0, sx_confined, sy_confined; double sx = 0, sy = 0, sx_confined, sy_confined;
Client *c = NULL, *w = NULL; Client *c = NULL, *w = NULL;
LayerSurface *l = NULL; LayerSurface *l = NULL, *focusedl = NULL;
struct wlr_surface *surface = NULL; struct wlr_surface *surface = NULL;
struct wlr_pointer_constraint_v1 *constraint; struct wlr_pointer_constraint_v1 *constraint;
/* Find the client under the pointer and send the event along. */ /* Find the client under the pointer and send the event along. */
xytonode(cursor->x, cursor->y, &surface, &c, NULL, &sx, &sy); xytonode(cursor->x, cursor->y, &surface, &c, &l, &sx, &sy);
if (cursor_mode == CurPressed && !seat->drag if (cursor_mode == CurPressed && !seat->drag
&& surface != seat->pointer_state.focused_surface && surface != seat->pointer_state.focused_surface
&& toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, &l) >= 0) { && toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, &focusedl) >= 0) {
c = w; c = w;
surface = seat->pointer_state.focused_surface; surface = seat->pointer_state.focused_surface;
sx = cursor->x - (l ? l->scene->node.x : w->geom.x); sx = cursor->x - (focusedl ? focusedl->scene->node.x : w->geom.x);
sy = cursor->y - (l ? l->scene->node.y : w->geom.y); sy = cursor->y - (focusedl ? focusedl->scene->node.y : w->geom.y);
} }
/* time is 0 in internal calls meant to restore pointer focus. */ /* time is 0 in internal calls meant to restore pointer focus. */
@@ -1964,7 +1991,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
if (!surface && !seat->drag) if (!surface && !seat->drag)
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
pointerfocus(c, surface, sx, sy, time); pointerfocus(c, l, surface, sx, sy, time);
} }
void void
@@ -2088,14 +2115,19 @@ outputmgrtest(struct wl_listener *listener, void *data)
} }
void void
pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, pointerfocus(Client *c, LayerSurface *l, struct wlr_surface *surface, double sx, double sy,
uint32_t time) uint32_t time)
{ {
struct timespec now; struct timespec now;
if (surface != seat->pointer_state.focused_surface && if (surface != seat->pointer_state.focused_surface && sloppyfocus && time) {
sloppyfocus && time && c && !client_is_unmanaged(c)) if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) {
focusclient(c, 0); focusclient(c, 0);
} else if (l && l->layer_surface->current.keyboard_interactive) {
focusclient(NULL, 0);
client_notify_enter(l->layer_surface->surface, wlr_seat_get_keyboard(seat));
}
}
/* If surface is NULL, clear pointer focus */ /* If surface is NULL, clear pointer focus */
if (!surface) { if (!surface) {
@@ -2583,7 +2615,7 @@ setup(void)
wl_signal_add(&xdg_shell->events.new_toplevel, &new_xdg_toplevel); wl_signal_add(&xdg_shell->events.new_toplevel, &new_xdg_toplevel);
wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup); wl_signal_add(&xdg_shell->events.new_popup, &new_xdg_popup);
layer_shell = wlr_layer_shell_v1_create(dpy, 3); layer_shell = wlr_layer_shell_v1_create(dpy, 4);
wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface); wl_signal_add(&layer_shell->events.new_surface, &new_layer_surface);
idle_notifier = wlr_idle_notifier_v1_create(dpy); idle_notifier = wlr_idle_notifier_v1_create(dpy);
@@ -2597,6 +2629,8 @@ setup(void)
(float [4]){0.1f, 0.1f, 0.1f, 1.0f}); (float [4]){0.1f, 0.1f, 0.1f, 1.0f});
wlr_scene_node_set_enabled(&locked_bg->node, 0); wlr_scene_node_set_enabled(&locked_bg->node, 0);
foreign_toplevel_list = wlr_ext_foreign_toplevel_list_v1_create(dpy,1);
/* Use decoration protocols to negotiate server-side decorations */ /* Use decoration protocols to negotiate server-side decorations */
wlr_server_decoration_manager_set_default_mode( wlr_server_decoration_manager_set_default_mode(
wlr_server_decoration_manager_create(dpy), wlr_server_decoration_manager_create(dpy),
@@ -2860,6 +2894,10 @@ unmapnotify(struct wl_listener *listener, void *data)
wl_list_remove(&c->flink); wl_list_remove(&c->flink);
} }
if (c->foreign_toplevel_handle) {
wlr_ext_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel_handle);
c->foreign_toplevel_handle = NULL;
}
wlr_scene_node_destroy(&c->scene->node); wlr_scene_node_destroy(&c->scene->node);
client_surface(c)->data = NULL; client_surface(c)->data = NULL;
printstatus(); printstatus();
@@ -2981,6 +3019,15 @@ updatetitle(struct wl_listener *listener, void *data)
Client *c = wl_container_of(listener, c, set_title); Client *c = wl_container_of(listener, c, set_title);
if (c == focustop(c->mon)) if (c == focustop(c->mon))
printstatus(); printstatus();
if (c->foreign_toplevel_handle) {
struct wlr_ext_foreign_toplevel_handle_v1_state foreign_toplevel_state = {
.app_id = client_get_appid(c),
.title = client_get_title(c),
};
wlr_ext_foreign_toplevel_handle_v1_update_state(c->foreign_toplevel_handle,
&foreign_toplevel_state);
}
} }
void void
@@ -3049,10 +3096,8 @@ void
xytonode(double x, double y, struct wlr_surface **psurface, xytonode(double x, double y, struct wlr_surface **psurface,
Client **pc, LayerSurface **pl, double *nx, double *ny) Client **pc, LayerSurface **pl, double *nx, double *ny)
{ {
struct wlr_scene_node *node, *pnode; struct wlr_scene_node *node;
struct wlr_surface *surface = NULL; struct wlr_surface *surface = NULL;
Client *c = NULL;
LayerSurface *l = NULL;
int layer; int layer;
for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) { for (layer = NUM_LAYERS - 1; !surface && layer >= 0; layer--) {
@@ -3063,18 +3108,10 @@ xytonode(double x, double y, struct wlr_surface **psurface,
if (node->type == WLR_SCENE_NODE_BUFFER) if (node->type == WLR_SCENE_NODE_BUFFER)
surface = wlr_scene_surface_try_from_buffer( surface = wlr_scene_surface_try_from_buffer(
wlr_scene_buffer_from_node(node))->surface; wlr_scene_buffer_from_node(node))->surface;
/* Walk the tree to find a node that knows the client */
for (pnode = node; pnode && !c; pnode = &pnode->parent->node)
c = pnode->data;
if (c && c->type == LayerShell) {
c = NULL;
l = pnode->data;
}
} }
if (psurface) *psurface = surface; if (psurface) *psurface = surface;
if (pc) *pc = c; toplevel_from_wlr_surface(surface, pc, pl);
if (pl) *pl = l;
} }
void void