labwc/src/desktop.c

397 lines
10 KiB
C
Raw Normal View History

2021-09-24 21:45:48 +01:00
// SPDX-License-Identifier: GPL-2.0-only
2020-12-30 10:29:21 +00:00
#include "config.h"
2020-09-28 20:53:59 +01:00
#include <assert.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_xdg_shell.h>
#include "common/scene-helpers.h"
cursor: process layer subsurfaces in `cursor_button_press()` ...to give keyboard focus to layer-shell clients if exclusive or on-demand interactivity is set, so that menu popups can be navigated with the keyboard. This still only works if the client is in top (or overlay) layers. Support for bottom and background to be done as a separate patch set. Revert 06b19f0 to process layer-shell subsurfaces in `cursor_button_press()`, but only when their parent layer-shell surface has keyboard interactivity. Fix bug in `get_cursor_context()` which resulted in layer-surfaces not being detected correctly. Background: Commit 06b19f0 (issue #1131) disabled processing of layer-shell subsurfaces in cursor_button_press() because when pressing a task in Waybar (Gtk panel using layer-shell subsurfaces) the foreign-toplevel minimize-raise action did not work correctly as the action logic relied on the recipient window being activated and by clicking on the panel, the panel itself was both surface-focusd and activated (and thus the window de-activated). The un-intended consequence was that by not responding to layer-subsurface cursor buttons presses, layer-shell clients (such as panels) were not given keyboard focus if they indeed wanted it by setting exclusive or on-demand keyboard interactivity. The good news is that that following @jlindgren90's refactoring (various) the only place where we call `view_set_actived()` is in `focus_change_notify()` in `seat.c` and we now only do it for views (bb8f0bc). Another side-effect (positive) of 06b19f0 was that a Waybar dnd bug was fixed (pointer-serial-number validation failure). Have tested with sfwbar, waybar and tint (test-panel) the following results: - Minimize-raise works even when on-demand keyboard interactivity is set - Keyboard interactivity is given popup-menus (sfwbar and tint) when the panels are in the top layer (support for bottom will be as a separate patch set) - Waybar dnd still works (even when hard-coding keyboard-interactivity) References: - https://github.com/labwc/labwc/commit/bb8f0bc960dca192b8579d67297c0586ec20bfe0 - https://github.com/labwc/labwc/blob/40ce95a68cf19796dd67b0527fddfdbe46181805/src/seat.c#L481-L483 - https://github.com/labwc/labwc/blob/40ce95a68cf19796dd67b0527fddfdbe46181805/src/dnd.c#L24 - https://github.com/johanmalm/tint Fixes: #1572
2024-03-07 19:15:02 +00:00
#include "common/surface-helpers.h"
#include "dnd.h"
#include "labwc.h"
#include "layers.h"
#include "node.h"
2025-07-26 15:34:45 -04:00
#include "output.h"
2021-03-21 20:54:55 +00:00
#include "ssd.h"
#include "view.h"
2022-06-15 02:02:50 +02:00
#include "workspaces.h"
#if HAVE_XWAYLAND
#include <wlr/xwayland.h>
#endif
void
desktop_arrange_all_views(struct server *server)
{
/*
* Adjust window positions/sizes. Skip views with no size since
* we can't do anything useful with them; they will presumably
* be initialized with valid positions/sizes later.
*
* We do not simply check view->mapped/been_mapped here because
* views can have maximized/fullscreen geometry applied while
* still unmapped. We do want to adjust the geometry of those
* views.
*/
struct view *view;
wl_list_for_each(view, &server->views, link) {
if (!wlr_box_empty(&view->pending)) {
view_adjust_for_layout_change(view);
}
}
}
static void
set_or_offer_focus(struct view *view)
{
struct seat *seat = &view->server->seat;
switch (view_wants_focus(view)) {
case VIEW_WANTS_FOCUS_ALWAYS:
if (view->surface != seat->seat->keyboard_state.focused_surface) {
seat_focus_surface(seat, view->surface);
}
break;
case VIEW_WANTS_FOCUS_LIKELY:
case VIEW_WANTS_FOCUS_UNLIKELY:
view_offer_focus(view);
break;
case VIEW_WANTS_FOCUS_NEVER:
break;
}
}
void
desktop_focus_view(struct view *view, bool raise)
2020-09-14 17:35:44 +01:00
{
2023-09-26 01:35:36 -04:00
assert(view);
/*
* Guard against views with no mapped surfaces when handling
* 'request_activate' and 'request_minimize'.
*/
if (!view->surface) {
return;
}
if (view->server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) {
wlr_log(WLR_DEBUG, "not focusing window while window switching");
return;
}
if (view->minimized) {
/*
* Unminimizing will map the view which triggers a call to this
* function again (with raise=true).
*/
2021-08-05 13:00:34 +01:00
view_minimize(view, false);
2020-10-08 20:08:41 +01:00
return;
}
if (!view->mapped) {
return;
}
/*
* Switch workspace if necessary to make the view visible
* (unnecessary for "always on {top,bottom}" views).
*/
if (!view_is_always_on_top(view) && !view_is_always_on_bottom(view)) {
workspaces_switch_to(view->workspace, /*update_focus*/ false);
}
if (raise) {
view_move_to_front(view);
}
/*
* If any child/sibling of the view is a modal dialog, focus
* the dialog instead. It does not need to be raised separately
* since view_move_to_front() raises all sibling views together.
*/
struct view *dialog = view_get_modal_dialog(view);
set_or_offer_focus(dialog ? dialog : view);
2020-09-14 17:35:44 +01:00
}
/* TODO: focus layer-shell surfaces also? */
void
desktop_focus_view_or_surface(struct seat *seat, struct view *view,
struct wlr_surface *surface, bool raise)
{
assert(view || surface);
if (view) {
desktop_focus_view(view, raise);
#if HAVE_XWAYLAND
} else {
struct wlr_xwayland_surface *xsurface =
wlr_xwayland_surface_try_from_wlr_surface(surface);
if (xsurface && wlr_xwayland_surface_override_redirect_wants_focus(xsurface)) {
seat_focus_surface(seat, surface);
}
#endif
}
}
struct view *
desktop_topmost_focusable_view(struct server *server)
{
2022-06-15 02:02:50 +02:00
struct view *view;
struct wl_list *node_list;
struct wlr_scene_node *node;
2024-07-25 21:56:05 +02:00
node_list = &server->workspaces.current->tree->children;
2022-06-15 02:02:50 +02:00
wl_list_for_each_reverse(node, node_list, link) {
if (!node->data) {
/* We found some non-view, most likely the region overlay */
continue;
}
2022-06-15 02:02:50 +02:00
view = node_view_from_node(node);
if (view->mapped && view_is_focusable(view)) {
2022-06-15 02:02:50 +02:00
return view;
}
}
2022-06-15 02:02:50 +02:00
return NULL;
}
void
desktop_focus_topmost_view(struct server *server)
{
struct view *view = desktop_topmost_focusable_view(server);
if (view) {
desktop_focus_view(view, /*raise*/ true);
2023-09-26 01:35:36 -04:00
} else {
/*
* Defocus previous focused surface/view if no longer
* focusable (e.g. unmapped or on a different workspace).
*/
seat_focus_surface(&server->seat, NULL);
}
}
void
desktop_focus_output(struct output *output)
{
if (!output_is_usable(output) || output->server->input_mode
!= LAB_INPUT_STATE_PASSTHROUGH) {
return;
}
struct view *view;
struct wlr_scene_node *node;
struct wlr_output_layout *layout = output->server->output_layout;
struct wl_list *list_head =
2024-07-25 21:56:05 +02:00
&output->server->workspaces.current->tree->children;
wl_list_for_each_reverse(node, list_head, link) {
if (!node->data) {
continue;
}
view = node_view_from_node(node);
if (!view_is_focusable(view)) {
continue;
}
if (wlr_output_layout_intersects(layout,
output->wlr_output, &view->current)) {
desktop_focus_view(view, /*raise*/ false);
wlr_cursor_warp(view->server->seat.cursor, NULL,
view->current.x + view->current.width / 2,
view->current.y + view->current.height / 2);
cursor_update_focus(view->server);
return;
}
}
/* No view found on desired output */
struct wlr_box layout_box;
wlr_output_layout_get_box(output->server->output_layout,
output->wlr_output, &layout_box);
wlr_cursor_warp(output->server->seat.cursor, NULL,
layout_box.x + output->usable_area.x + output->usable_area.width / 2,
layout_box.y + output->usable_area.y + output->usable_area.height / 2);
cursor_update_focus(output->server);
}
void
desktop_update_top_layer_visibility(struct server *server)
{
struct view *view;
struct output *output;
uint32_t top = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
/* Enable all top layers */
wl_list_for_each(output, &server->outputs, link) {
if (!output_is_usable(output)) {
continue;
}
wlr_scene_node_set_enabled(&output->layer_tree[top]->node, true);
}
/*
* And disable them again when there is a fullscreen view without
* any views above it
*/
uint64_t outputs_covered = 0;
for_each_view(view, &server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
if (view->minimized) {
continue;
}
if (!output_is_usable(view->output)) {
continue;
}
if (view->fullscreen && !(view->outputs & outputs_covered)) {
wlr_scene_node_set_enabled(
&view->output->layer_tree[top]->node, false);
}
outputs_covered |= view->outputs;
}
}
static struct wlr_surface *
get_surface_from_layer_node(struct wlr_scene_node *node)
{
assert(node->data);
struct node_descriptor *desc = (struct node_descriptor *)node->data;
if (desc->type == LAB_NODE_LAYER_SURFACE) {
struct lab_layer_surface *surface;
surface = node_layer_surface_from_node(node);
return surface->scene_layer_surface->layer_surface->surface;
} else if (desc->type == LAB_NODE_LAYER_POPUP) {
struct lab_layer_popup *popup;
popup = node_layer_popup_from_node(node);
return popup->wlr_popup->base->surface;
}
return NULL;
}
/* TODO: make this less big and scary */
struct cursor_context
get_cursor_context(struct server *server)
{
struct cursor_context ret = {.type = LAB_NODE_NONE};
struct wlr_cursor *cursor = server->seat.cursor;
/* Prevent drag icons to be on top of the hitbox detection */
if (server->seat.drag.active) {
dnd_icons_show(&server->seat, false);
}
struct wlr_scene_node *node =
wlr_scene_node_at(&server->scene->tree.node,
cursor->x, cursor->y, &ret.sx, &ret.sy);
if (server->seat.drag.active) {
dnd_icons_show(&server->seat, true);
}
ret.node = node;
if (!node) {
ret.type = LAB_NODE_ROOT;
return ret;
2021-07-19 07:06:36 +01:00
}
#if HAVE_XWAYLAND
if (node->type == WLR_SCENE_NODE_BUFFER) {
struct wlr_surface *surface = lab_wlr_surface_from_node(node);
if (node->parent == server->unmanaged_tree) {
ret.type = LAB_NODE_UNMANAGED;
ret.surface = surface;
return ret;
}
}
#endif
while (node) {
struct node_descriptor *desc = node->data;
if (desc) {
switch (desc->type) {
case LAB_NODE_VIEW:
case LAB_NODE_XDG_POPUP:
ret.view = desc->view;
ssd: clean up scene management Our codebase for ssd scenes has grown with a lot of technical debts: - We needed to call `ssd_get_part()` everywhere to get the scene node of a ssd part. We then needed to cast it to `wlr_scene_rect` and `wlr_scene_buffer`. This bloated our codebase and even blocked duplicated button types in `<titlebar><layout>`. - `ssd_get_part_type()` was a dirty hack. It compared parent, grandparent and grandgrandparent of a node with each subtree in the ssd to get the part type of the node. To resolve this issues, this commit changes how ssd scenes are managed: - Access scene rects and scene buffers just as a member of `struct ssd`. - `ssd_part` is now a attachment to a scene node that can be accessed via node_descriptor->data, with a new node-descriptor type `LAB_NODE_DESC_SSD_PART`. `LAB_NODE_DESC_SSD_BUTTON` is unified into it. Now the scene graph under ssd->tree looks like below. The parentheses indicate the type of ssd_part attached to the node: ssd->tree (LAB_SSD_NONE) +--titlebar (LAB_SSD_PART_TITLEBAR) | +--inactive | | +--background bar | | +--left corner | | +--right corner | | +--title (LAB_SSD_PART_TITLE) | | +--iconify button (LAB_SSD_BUTTON_ICONIFY) | | | +--normal close icon image | | | +--hovered close icon image | | | +--... | | +--window icon (LAB_SSD_BUTTON_WINDOW_ICON) | | | +--window icon image | | +--... | +--active | +--... +--border | +--inactive | | +--top | | +--... | +--active | +--top | +--... +--shadow | +--inactive | | +--top | | +--... | +--active | +--top | +--... +--extents +--top +--... When hovering on SSD, `get_cursor_context()` traverses this scene node from the leaf. If it finds a `ssd_part` attached to the node, it returns `ssd_part_type` that represents the resizing direction, button types or `Title`/`Titlebar`.
2025-08-13 21:00:11 +09:00
if (ret.node->type == WLR_SCENE_NODE_BUFFER
&& lab_wlr_surface_from_node(ret.node)) {
ret.type = LAB_NODE_CLIENT;
ret.surface = lab_wlr_surface_from_node(ret.node);
ssd: clean up scene management Our codebase for ssd scenes has grown with a lot of technical debts: - We needed to call `ssd_get_part()` everywhere to get the scene node of a ssd part. We then needed to cast it to `wlr_scene_rect` and `wlr_scene_buffer`. This bloated our codebase and even blocked duplicated button types in `<titlebar><layout>`. - `ssd_get_part_type()` was a dirty hack. It compared parent, grandparent and grandgrandparent of a node with each subtree in the ssd to get the part type of the node. To resolve this issues, this commit changes how ssd scenes are managed: - Access scene rects and scene buffers just as a member of `struct ssd`. - `ssd_part` is now a attachment to a scene node that can be accessed via node_descriptor->data, with a new node-descriptor type `LAB_NODE_DESC_SSD_PART`. `LAB_NODE_DESC_SSD_BUTTON` is unified into it. Now the scene graph under ssd->tree looks like below. The parentheses indicate the type of ssd_part attached to the node: ssd->tree (LAB_SSD_NONE) +--titlebar (LAB_SSD_PART_TITLEBAR) | +--inactive | | +--background bar | | +--left corner | | +--right corner | | +--title (LAB_SSD_PART_TITLE) | | +--iconify button (LAB_SSD_BUTTON_ICONIFY) | | | +--normal close icon image | | | +--hovered close icon image | | | +--... | | +--window icon (LAB_SSD_BUTTON_WINDOW_ICON) | | | +--window icon image | | +--... | +--active | +--... +--border | +--inactive | | +--top | | +--... | +--active | +--top | +--... +--shadow | +--inactive | | +--top | | +--... | +--active | +--top | +--... +--extents +--top +--... When hovering on SSD, `get_cursor_context()` traverses this scene node from the leaf. If it finds a `ssd_part` attached to the node, it returns `ssd_part_type` that represents the resizing direction, button types or `Title`/`Titlebar`.
2025-08-13 21:00:11 +09:00
} else {
/* should never be reached */
wlr_log(WLR_ERROR, "cursor not on client or ssd");
}
return ret;
case LAB_NODE_LAYER_SURFACE:
ret.node = node;
ret.type = LAB_NODE_LAYER_SURFACE;
ret.surface = get_surface_from_layer_node(node);
return ret;
case LAB_NODE_LAYER_POPUP:
ret.node = node;
ret.type = LAB_NODE_CLIENT;
ret.surface = get_surface_from_layer_node(node);
return ret;
case LAB_NODE_SESSION_LOCK_SURFACE: /* fallthrough */
case LAB_NODE_IME_POPUP:
ret.type = LAB_NODE_CLIENT;
2024-02-23 03:50:55 +09:00
ret.surface = lab_wlr_surface_from_node(ret.node);
return ret;
case LAB_NODE_MENUITEM:
2022-03-03 04:33:33 +01:00
/* Always return the top scene node for menu items */
ret.node = node;
ret.type = LAB_NODE_MENUITEM;
return ret;
case LAB_NODE_TREE:
case LAB_NODE_SCALED_BUFFER:
/* Continue to parent node */
break;
default:
/*
* All other node descriptors (buttons, title,
* etc.) should have an associated view.
*/
if (!desc->view) {
wlr_log(WLR_ERROR, "cursor not on any view "
"(node type %d)", desc->type);
return ret;
}
ret.node = node;
ret.view = desc->view;
/* Detect mouse contexts like Top, Left and TRCorner */
ret.type = ssd_get_resizing_type(ret.view->ssd, cursor);
if (ret.type == LAB_NODE_NONE) {
/*
* Otherwise, detect mouse contexts like
* Title, Titlebar and Iconify
*/
ret.type = desc->type;
}
return ret;
2022-03-03 04:33:33 +01:00
}
}
/* Edge-case nodes without node-descriptors */
if (node->type == WLR_SCENE_NODE_BUFFER) {
struct wlr_surface *surface = lab_wlr_surface_from_node(node);
cursor: process layer subsurfaces in `cursor_button_press()` ...to give keyboard focus to layer-shell clients if exclusive or on-demand interactivity is set, so that menu popups can be navigated with the keyboard. This still only works if the client is in top (or overlay) layers. Support for bottom and background to be done as a separate patch set. Revert 06b19f0 to process layer-shell subsurfaces in `cursor_button_press()`, but only when their parent layer-shell surface has keyboard interactivity. Fix bug in `get_cursor_context()` which resulted in layer-surfaces not being detected correctly. Background: Commit 06b19f0 (issue #1131) disabled processing of layer-shell subsurfaces in cursor_button_press() because when pressing a task in Waybar (Gtk panel using layer-shell subsurfaces) the foreign-toplevel minimize-raise action did not work correctly as the action logic relied on the recipient window being activated and by clicking on the panel, the panel itself was both surface-focusd and activated (and thus the window de-activated). The un-intended consequence was that by not responding to layer-subsurface cursor buttons presses, layer-shell clients (such as panels) were not given keyboard focus if they indeed wanted it by setting exclusive or on-demand keyboard interactivity. The good news is that that following @jlindgren90's refactoring (various) the only place where we call `view_set_actived()` is in `focus_change_notify()` in `seat.c` and we now only do it for views (bb8f0bc). Another side-effect (positive) of 06b19f0 was that a Waybar dnd bug was fixed (pointer-serial-number validation failure). Have tested with sfwbar, waybar and tint (test-panel) the following results: - Minimize-raise works even when on-demand keyboard interactivity is set - Keyboard interactivity is given popup-menus (sfwbar and tint) when the panels are in the top layer (support for bottom will be as a separate patch set) - Waybar dnd still works (even when hard-coding keyboard-interactivity) References: - https://github.com/labwc/labwc/commit/bb8f0bc960dca192b8579d67297c0586ec20bfe0 - https://github.com/labwc/labwc/blob/40ce95a68cf19796dd67b0527fddfdbe46181805/src/seat.c#L481-L483 - https://github.com/labwc/labwc/blob/40ce95a68cf19796dd67b0527fddfdbe46181805/src/dnd.c#L24 - https://github.com/johanmalm/tint Fixes: #1572
2024-03-07 19:15:02 +00:00
/*
* Handle layer-shell subsurfaces
*
* These don't have node-descriptors, but need to be
* able to receive pointer actions so we have to process
* them here.
*
* Test by running `gtk-layer-demo -k exclusive`, then
* open the 'set margin' dialog and try setting the
* margin with the pointer.
*/
if (surface && wlr_subsurface_try_from_wlr_surface(surface)
&& subsurface_parent_layer(surface)) {
ret.surface = surface;
ret.type = LAB_NODE_LAYER_SUBSURFACE;
cursor: process layer subsurfaces in `cursor_button_press()` ...to give keyboard focus to layer-shell clients if exclusive or on-demand interactivity is set, so that menu popups can be navigated with the keyboard. This still only works if the client is in top (or overlay) layers. Support for bottom and background to be done as a separate patch set. Revert 06b19f0 to process layer-shell subsurfaces in `cursor_button_press()`, but only when their parent layer-shell surface has keyboard interactivity. Fix bug in `get_cursor_context()` which resulted in layer-surfaces not being detected correctly. Background: Commit 06b19f0 (issue #1131) disabled processing of layer-shell subsurfaces in cursor_button_press() because when pressing a task in Waybar (Gtk panel using layer-shell subsurfaces) the foreign-toplevel minimize-raise action did not work correctly as the action logic relied on the recipient window being activated and by clicking on the panel, the panel itself was both surface-focusd and activated (and thus the window de-activated). The un-intended consequence was that by not responding to layer-subsurface cursor buttons presses, layer-shell clients (such as panels) were not given keyboard focus if they indeed wanted it by setting exclusive or on-demand keyboard interactivity. The good news is that that following @jlindgren90's refactoring (various) the only place where we call `view_set_actived()` is in `focus_change_notify()` in `seat.c` and we now only do it for views (bb8f0bc). Another side-effect (positive) of 06b19f0 was that a Waybar dnd bug was fixed (pointer-serial-number validation failure). Have tested with sfwbar, waybar and tint (test-panel) the following results: - Minimize-raise works even when on-demand keyboard interactivity is set - Keyboard interactivity is given popup-menus (sfwbar and tint) when the panels are in the top layer (support for bottom will be as a separate patch set) - Waybar dnd still works (even when hard-coding keyboard-interactivity) References: - https://github.com/labwc/labwc/commit/bb8f0bc960dca192b8579d67297c0586ec20bfe0 - https://github.com/labwc/labwc/blob/40ce95a68cf19796dd67b0527fddfdbe46181805/src/seat.c#L481-L483 - https://github.com/labwc/labwc/blob/40ce95a68cf19796dd67b0527fddfdbe46181805/src/dnd.c#L24 - https://github.com/johanmalm/tint Fixes: #1572
2024-03-07 19:15:02 +00:00
return ret;
}
}
/* node->parent is always a *wlr_scene_tree */
node = node->parent ? &node->parent->node : NULL;
}
/*
* TODO: add node descriptors for the OSDs and reinstate
* wlr_log(WLR_DEBUG, "Unknown node detected");
*/
return ret;
}