layer: improve keyboard-interactivity

- Process layer-shell keyboard interactivity in the map and commit
  handlers only, rather than in layers_arrange(). This allows handling of
  the layer-surface that emitted the event rather than iterating over all
  surfaces in the output layer-tree, and therefore avoids having to guess
  which surface should have keyboard preference (and it might not be the
  last one added to the list which was the assumption previously).

- Prevent seat_focus_surface() from setting keyboard focus if a layer-shell
  surface with exclusive keyboard-interactivity has the focus.

- Set cursor_context type for layer-surfaces without node-descriptors
  in order to set keyboard focus correctly in cursor_button_press().

Tested satisfactorily with xfce4-panel and gtk-layer-demo.

Fixes #725 and #704
This commit is contained in:
Johan Malm 2023-01-28 22:34:27 +00:00 committed by Johan Malm
parent 4dc99e2f38
commit 6f3043b08d
3 changed files with 72 additions and 35 deletions

View file

@ -323,6 +323,7 @@ get_cursor_context(struct server *server)
ret.type = LAB_SSD_ROOT;
return ret;
}
#if HAVE_XWAYLAND
if (node->type == WLR_SCENE_NODE_BUFFER) {
struct wlr_surface *surface = lab_wlr_surface_from_node(node);
@ -382,6 +383,9 @@ get_cursor_context(struct server *server)
if (node->type == WLR_SCENE_NODE_BUFFER) {
struct wlr_surface *surface = lab_wlr_surface_from_node(node);
if (surface) {
if (wlr_surface_is_layer_surface(surface)) {
ret.type = LAB_SSD_LAYER_SURFACE;
}
if (is_layer_descendant(node)) {
/*
* layer-shell subsurfaces need to be

View file

@ -75,38 +75,6 @@ layers_arrange(struct output *output)
}
output->usable_area = usable_area;
/* Find topmost keyboard interactive layer, if such a layer exists */
uint32_t layers_above_views[] = {
ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
ZWLR_LAYER_SHELL_V1_LAYER_TOP,
};
size_t nlayers = sizeof(layers_above_views) / sizeof(layers_above_views[0]);
struct lab_layer_surface *topmost = NULL;
struct wlr_scene_node *node;
for (size_t i = 0; i < nlayers; ++i) {
struct wlr_scene_tree *tree = output->layer_tree[layers_above_views[i]];
/* Iterate in reverse to give most recent node preference */
wl_list_for_each_reverse(node, &tree->children, link) {
struct lab_layer_surface *surface = node_layer_surface_from_node(node);
struct wlr_scene_layer_surface_v1 *scene = surface->scene_layer_surface;
if (scene->layer_surface->current.keyboard_interactive) {
topmost = surface;
break;
}
}
if (topmost) {
break;
}
}
struct seat *seat = &output->server->seat;
if (topmost) {
seat_set_focus_layer(seat,
topmost->scene_layer_surface->layer_surface);
} else if (seat->focused_layer &&
!seat->focused_layer->current.keyboard_interactive) {
seat_set_focus_layer(seat, NULL);
}
}
static void
@ -118,6 +86,48 @@ handle_output_destroy(struct wl_listener *listener, void *data)
wlr_layer_surface_v1_destroy(layer->scene_layer_surface->layer_surface);
}
static void
process_keyboard_interactivity(struct lab_layer_surface *layer)
{
struct wlr_layer_surface_v1 *layer_surface = layer->scene_layer_surface->layer_surface;
struct seat *seat = &layer->server->seat;
if (layer_surface->current.keyboard_interactive
&& layer_surface->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) {
/*
* Give keyboard focus to surface if
* - keyboard-interactivity is 'exclusive' or 'on-demand'; and
* - surface is in top/overlay layers; and
* - currently focused layer has a lower precedence
*
* In other words, when dealing with two surfaces with
* exclusive/on-demand keyboard-interactivity (firstly the
* currently focused 'focused_layer' and secondly the
* 'layer_surface' for which we're just responding to a
* map/commit event), the following logic applies:
*
* | focused_layer | layer_surface | who gets keyboard focus |
* |---------------|---------------|-------------------------|
* | overlay | top | focused_layer |
* | overlay | overlay | layer_surface |
* | top | top | layer_surface |
* | top | overlay | layer_surface |
*/
if (!seat->focused_layer || seat->focused_layer->current.layer
<= layer_surface->current.layer) {
seat_set_focus_layer(seat, layer_surface);
}
} else if (seat->focused_layer
&& !seat->focused_layer->current.keyboard_interactive) {
/*
* Clear focus if keyboard-interactivity has been set to
* ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE
*/
seat_set_focus_layer(seat, NULL);
}
}
static void
handle_surface_commit(struct wl_listener *listener, void *data)
{
@ -140,6 +150,10 @@ handle_surface_commit(struct wl_listener *listener, void *data)
wlr_scene_node_reparent(&layer->scene_layer_surface->tree->node,
output->layer_tree[layer_surface->current.layer]);
}
/* Process keyboard-interactivity change */
if (committed & WLR_LAYER_SURFACE_V1_STATE_KEYBOARD_INTERACTIVITY) {
process_keyboard_interactivity(layer);
}
if (committed || layer->mapped != layer_surface->mapped) {
layer->mapped = layer_surface->mapped;
@ -158,6 +172,12 @@ handle_node_destroy(struct wl_listener *listener, void *data)
struct lab_layer_surface *layer =
wl_container_of(listener, layer, node_destroy);
/*
* TODO: Determine if this layer is being used by an exclusive client.
* If it is, try and find another layer owned by this client to pass
* focus to.
*/
wl_list_remove(&layer->map.link);
wl_list_remove(&layer->unmap.link);
wl_list_remove(&layer->surface_commit.link);
@ -197,6 +217,8 @@ handle_map(struct wl_listener *listener, void *data)
* automatically based on the position of the surface and outputs in
* the scene. See wlr_scene_surface_create() documentation.
*/
process_keyboard_interactivity(layer);
}
static void

View file

@ -440,8 +440,8 @@ seat_reconfigure(struct server *server)
}
}
void
seat_focus_surface(struct seat *seat, struct wlr_surface *surface)
static void
seat_focus(struct seat *seat, struct wlr_surface *surface)
{
if (!surface) {
wlr_seat_keyboard_notify_clear_focus(seat->seat);
@ -469,6 +469,17 @@ seat_focus_surface(struct seat *seat, struct wlr_surface *surface)
constrain_cursor(server, constraint);
}
void
seat_focus_surface(struct seat *seat, struct wlr_surface *surface)
{
/* Respect layer-shell exlusive keyboard-interactivity. */
if (seat->focused_layer && seat->focused_layer->current.keyboard_interactive
== ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) {
return;
}
seat_focus(seat, surface);
}
void
seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer)
{
@ -477,7 +488,7 @@ seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer)
desktop_focus_topmost_mapped_view(seat->server);
return;
}
seat_focus_surface(seat, layer->surface);
seat_focus(seat, layer->surface);
if (layer->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) {
seat->focused_layer = layer;
}