labwc/src/xdg.c

1150 lines
34 KiB
C
Raw Normal View History

2021-09-24 21:45:48 +01:00
// SPDX-License-Identifier: GPL-2.0-only
2020-09-28 20:53:59 +01:00
#include <assert.h>
#include <wlr/types/wlr_fractional_scale_v1.h>
#include <wlr/types/wlr_xdg_activation_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/types/wlr_xdg_toplevel_icon_v1.h>
#include "buffer.h"
#include "common/array.h"
#include "common/macros.h"
#include "common/mem.h"
2025-08-17 16:01:50 -04:00
#include "config/rcxml.h"
#include "decorations.h"
#include "foreign-toplevel/foreign.h"
2019-12-26 21:37:31 +00:00
#include "labwc.h"
2024-09-17 00:32:02 +09:00
#include "menu/menu.h"
#include "node.h"
2025-07-26 15:34:45 -04:00
#include "output.h"
#include "snap-constraints.h"
#include "view.h"
#include "view-impl-common.h"
#include "window-rules.h"
2022-06-15 02:02:50 +02:00
#include "workspaces.h"
2019-12-26 21:37:31 +00:00
#define LAB_XDG_SHELL_VERSION 6
#define CONFIGURE_TIMEOUT_MS 100
static struct xdg_toplevel_view *
xdg_toplevel_view_from_view(struct view *view)
{
assert(view->type == LAB_XDG_SHELL_VIEW);
return (struct xdg_toplevel_view *)view;
}
struct wlr_xdg_surface *
xdg_surface_from_view(struct view *view)
{
assert(view->type == LAB_XDG_SHELL_VIEW);
struct xdg_toplevel_view *xdg_toplevel_view =
(struct xdg_toplevel_view *)view;
assert(xdg_toplevel_view->xdg_surface);
return xdg_toplevel_view->xdg_surface;
}
static struct wlr_xdg_toplevel *
xdg_toplevel_from_view(struct view *view)
{
struct wlr_xdg_surface *xdg_surface = xdg_surface_from_view(view);
assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
assert(xdg_surface->toplevel);
return xdg_surface->toplevel;
}
static struct view_size_hints
xdg_toplevel_view_get_size_hints(struct view *view)
{
assert(view);
struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view);
struct wlr_xdg_toplevel_state *state = &toplevel->current;
return (struct view_size_hints){
.min_width = state->min_width,
.min_height = state->min_height,
};
}
static bool
xdg_toplevel_view_contains_window_type(struct view *view,
2025-08-17 16:01:50 -04:00
enum lab_window_type window_type)
{
assert(view);
struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view);
struct wlr_xdg_toplevel_state *state = &toplevel->current;
bool is_dialog = (state->min_width != 0 && state->min_height != 0
&& (state->min_width == state->max_width
|| state->min_height == state->max_height))
|| toplevel->parent;
switch (window_type) {
2025-08-17 16:01:50 -04:00
case LAB_WINDOW_TYPE_NORMAL:
return !is_dialog;
2025-08-17 16:01:50 -04:00
case LAB_WINDOW_TYPE_DIALOG:
return is_dialog;
default:
return false;
}
}
2021-01-09 22:51:20 +00:00
static void
handle_new_popup(struct wl_listener *listener, void *data)
2021-01-09 22:51:20 +00:00
{
struct xdg_toplevel_view *xdg_toplevel_view =
wl_container_of(listener, xdg_toplevel_view, new_popup);
struct view *view = &xdg_toplevel_view->base;
2021-01-09 22:51:20 +00:00
struct wlr_xdg_popup *wlr_popup = data;
xdg_popup_create(view, wlr_popup);
}
static void
set_fullscreen_from_request(struct view *view,
struct wlr_xdg_toplevel_requested *requested)
{
if (!view->fullscreen && requested->fullscreen
&& requested->fullscreen_output) {
view_set_output(view, output_from_wlr_output(view->server,
requested->fullscreen_output));
}
view_set_fullscreen(view, requested->fullscreen);
}
xdg: handle initially maximized xdg-shell views better Currently, initially maximized (or fullscreen) xdg-shell views exhibit one of two issues: - some (e.g. GTK and Qt apps) paint an initial frame un-maximized (before the "map" event) and only maximize in a later commit - others (e.g. foot) maximize immediately without flicker, but never store a valid natural size, so we end up using a fallback (640x480) Under KWin, neither of these issues occur, so I looked into what labwc is doing wrong. It seems that: - wlroots internally sends an initial configure event with a size of 0x0 to all xdg-shell views. This requests the client to set its own preferred (a.k.a. natural) size. - For an initially maximized/fullscreen view, the initial configure event should contain the maximized/fullscreen size rather than 0x0. In labwc, this means we have to call wlr_xdg_toplevel_set_size() earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG shows that the initial configure event now has the correct geometry, matching KWin behavior. With this change, GTK and Qt apps no longer paint an incorrect un-maximized frame. - However, this means that all xdg-shell views now suffer from the same issue as foot, where we never receive a commit with the un-maximized (natural) geometry. The correct way to get the natural geometry seems to be to wait until we want to un-maximize, and send a configure event of 0x0 at that point. Sending a configure event of 0x0 when un-maximizing is a bit annoying as it breaks some assumptions in labwc code. In particular: - view->natural_geometry may now be unknown (0x0), requiring various wlr_box_empty() checks sprinkled around. I added these in all the obvious places, but there could be some code paths that I missed. - Positioning the newly un-maximized view within view_maximize() no longer works since we don't know the natural size. Instead we have to run the positioning logic from the surface commit handler. This results in some extra complexity, especially for interactive move. See the new do_late_positioning() function in xdg.c. Some TODOs/FIXMEs (non-blocking in my opinion): - The view_wants_decorations() check is now duplicated in both the new_surface and map event handlers. I'm not sure if this is necessary but it seemed like the safest approach for now. More testing would be nice, particularly with various combinations of config and client SSD preferences. - Aside from the interactive move case, the "late positioning" logic always centers the view when un-maximizing, and does not invoke any of the smart placement logic. If we want to invoke smart placement here, I'd appreciate someone with more knowledge of that code to take a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
static void
do_late_positioning(struct view *view)
{
struct server *server = view->server;
if (server->input_mode == LAB_INPUT_STATE_MOVE
&& view == server->grabbed_view) {
/* Reposition the view while anchoring it to cursor */
interactive_anchor_to_cursor(server, &view->pending);
xdg: handle initially maximized xdg-shell views better Currently, initially maximized (or fullscreen) xdg-shell views exhibit one of two issues: - some (e.g. GTK and Qt apps) paint an initial frame un-maximized (before the "map" event) and only maximize in a later commit - others (e.g. foot) maximize immediately without flicker, but never store a valid natural size, so we end up using a fallback (640x480) Under KWin, neither of these issues occur, so I looked into what labwc is doing wrong. It seems that: - wlroots internally sends an initial configure event with a size of 0x0 to all xdg-shell views. This requests the client to set its own preferred (a.k.a. natural) size. - For an initially maximized/fullscreen view, the initial configure event should contain the maximized/fullscreen size rather than 0x0. In labwc, this means we have to call wlr_xdg_toplevel_set_size() earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG shows that the initial configure event now has the correct geometry, matching KWin behavior. With this change, GTK and Qt apps no longer paint an incorrect un-maximized frame. - However, this means that all xdg-shell views now suffer from the same issue as foot, where we never receive a commit with the un-maximized (natural) geometry. The correct way to get the natural geometry seems to be to wait until we want to un-maximize, and send a configure event of 0x0 at that point. Sending a configure event of 0x0 when un-maximizing is a bit annoying as it breaks some assumptions in labwc code. In particular: - view->natural_geometry may now be unknown (0x0), requiring various wlr_box_empty() checks sprinkled around. I added these in all the obvious places, but there could be some code paths that I missed. - Positioning the newly un-maximized view within view_maximize() no longer works since we don't know the natural size. Instead we have to run the positioning logic from the surface commit handler. This results in some extra complexity, especially for interactive move. See the new do_late_positioning() function in xdg.c. Some TODOs/FIXMEs (non-blocking in my opinion): - The view_wants_decorations() check is now duplicated in both the new_surface and map event handlers. I'm not sure if this is necessary but it seemed like the safest approach for now. More testing would be nice, particularly with various combinations of config and client SSD preferences. - Aside from the interactive move case, the "late positioning" logic always centers the view when un-maximizing, and does not invoke any of the smart placement logic. If we want to invoke smart placement here, I'd appreciate someone with more knowledge of that code to take a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
} else {
/* TODO: smart placement? */
view_compute_centered_position(view, NULL,
view->pending.width, view->pending.height,
&view->pending.x, &view->pending.y);
}
}
/* TODO: reorder so this forward declaration isn't needed */
static void set_pending_configure_serial(struct view *view, uint32_t serial);
static void
handle_commit(struct wl_listener *listener, void *data)
{
struct view *view = wl_container_of(listener, view, commit);
struct wlr_xdg_surface *xdg_surface = xdg_surface_from_view(view);
struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view);
2020-09-28 20:53:59 +01:00
assert(view->surface);
if (xdg_surface->initial_commit) {
uint32_t serial =
wlr_xdg_surface_schedule_configure(xdg_surface);
if (serial > 0) {
set_pending_configure_serial(view, serial);
}
uint32_t wm_caps = WLR_XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU
| WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE
| WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN
| WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE;
wlr_xdg_toplevel_set_wm_capabilities(toplevel, wm_caps);
if (view->output) {
wlr_xdg_toplevel_set_bounds(toplevel,
view->output->usable_area.width,
view->output->usable_area.height);
}
/*
* Handle initial fullscreen/maximize requests immediately after
* scheduling the initial configure event (before it is sent) in
* order to send the correct size and avoid flicker.
*
* In normal (non-fullscreen/maximized) cases, the initial
* configure event is sent with a zero size, which requests the
* application to choose its own size.
*/
if (toplevel->requested.fullscreen) {
set_fullscreen_from_request(view, &toplevel->requested);
}
if (toplevel->requested.maximized) {
view_maximize(view, VIEW_AXIS_BOTH,
/*store_natural_geometry*/ true);
}
return;
}
struct wlr_box size = xdg_surface->geometry;
xdg: handle initially maximized xdg-shell views better Currently, initially maximized (or fullscreen) xdg-shell views exhibit one of two issues: - some (e.g. GTK and Qt apps) paint an initial frame un-maximized (before the "map" event) and only maximize in a later commit - others (e.g. foot) maximize immediately without flicker, but never store a valid natural size, so we end up using a fallback (640x480) Under KWin, neither of these issues occur, so I looked into what labwc is doing wrong. It seems that: - wlroots internally sends an initial configure event with a size of 0x0 to all xdg-shell views. This requests the client to set its own preferred (a.k.a. natural) size. - For an initially maximized/fullscreen view, the initial configure event should contain the maximized/fullscreen size rather than 0x0. In labwc, this means we have to call wlr_xdg_toplevel_set_size() earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG shows that the initial configure event now has the correct geometry, matching KWin behavior. With this change, GTK and Qt apps no longer paint an incorrect un-maximized frame. - However, this means that all xdg-shell views now suffer from the same issue as foot, where we never receive a commit with the un-maximized (natural) geometry. The correct way to get the natural geometry seems to be to wait until we want to un-maximize, and send a configure event of 0x0 at that point. Sending a configure event of 0x0 when un-maximizing is a bit annoying as it breaks some assumptions in labwc code. In particular: - view->natural_geometry may now be unknown (0x0), requiring various wlr_box_empty() checks sprinkled around. I added these in all the obvious places, but there could be some code paths that I missed. - Positioning the newly un-maximized view within view_maximize() no longer works since we don't know the natural size. Instead we have to run the positioning logic from the surface commit handler. This results in some extra complexity, especially for interactive move. See the new do_late_positioning() function in xdg.c. Some TODOs/FIXMEs (non-blocking in my opinion): - The view_wants_decorations() check is now duplicated in both the new_surface and map event handlers. I'm not sure if this is necessary but it seemed like the safest approach for now. More testing would be nice, particularly with various combinations of config and client SSD preferences. - Aside from the interactive move case, the "late positioning" logic always centers the view when un-maximizing, and does not invoke any of the smart placement logic. If we want to invoke smart placement here, I'd appreciate someone with more knowledge of that code to take a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
bool update_required = false;
/*
* If we didn't know the natural size when leaving fullscreen or
* unmaximizing, then the pending size will be 0x0. In this case,
* the pending x/y is also unset and we still need to position
* the window.
*/
if (wlr_box_empty(&view->pending) && !wlr_box_empty(&size)) {
xdg: handle initially maximized xdg-shell views better Currently, initially maximized (or fullscreen) xdg-shell views exhibit one of two issues: - some (e.g. GTK and Qt apps) paint an initial frame un-maximized (before the "map" event) and only maximize in a later commit - others (e.g. foot) maximize immediately without flicker, but never store a valid natural size, so we end up using a fallback (640x480) Under KWin, neither of these issues occur, so I looked into what labwc is doing wrong. It seems that: - wlroots internally sends an initial configure event with a size of 0x0 to all xdg-shell views. This requests the client to set its own preferred (a.k.a. natural) size. - For an initially maximized/fullscreen view, the initial configure event should contain the maximized/fullscreen size rather than 0x0. In labwc, this means we have to call wlr_xdg_toplevel_set_size() earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG shows that the initial configure event now has the correct geometry, matching KWin behavior. With this change, GTK and Qt apps no longer paint an incorrect un-maximized frame. - However, this means that all xdg-shell views now suffer from the same issue as foot, where we never receive a commit with the un-maximized (natural) geometry. The correct way to get the natural geometry seems to be to wait until we want to un-maximize, and send a configure event of 0x0 at that point. Sending a configure event of 0x0 when un-maximizing is a bit annoying as it breaks some assumptions in labwc code. In particular: - view->natural_geometry may now be unknown (0x0), requiring various wlr_box_empty() checks sprinkled around. I added these in all the obvious places, but there could be some code paths that I missed. - Positioning the newly un-maximized view within view_maximize() no longer works since we don't know the natural size. Instead we have to run the positioning logic from the surface commit handler. This results in some extra complexity, especially for interactive move. See the new do_late_positioning() function in xdg.c. Some TODOs/FIXMEs (non-blocking in my opinion): - The view_wants_decorations() check is now duplicated in both the new_surface and map event handlers. I'm not sure if this is necessary but it seemed like the safest approach for now. More testing would be nice, particularly with various combinations of config and client SSD preferences. - Aside from the interactive move case, the "late positioning" logic always centers the view when un-maximizing, and does not invoke any of the smart placement logic. If we want to invoke smart placement here, I'd appreciate someone with more knowledge of that code to take a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
view->pending.width = size.width;
view->pending.height = size.height;
do_late_positioning(view);
update_required = true;
}
/*
* Qt applications occasionally fail to call set_window_geometry
* after a configure request, but do correctly update the actual
* surface extent. This results in a mismatch between the window
* decorations (which follow the logical geometry) and the visual
* size of the client area. As a workaround, we try to detect
* this case and ignore the out-of-date window geometry.
*/
if (size.width != view->pending.width
|| size.height != view->pending.height) {
/*
* Not using wlr_surface_get_extend() since Thunderbird
* sometimes resizes the window geometry and the toplevel
* surface size, but not the subsurface size (see #2183).
*/
struct wlr_box extent = {
.width = view->surface->current.width,
.height = view->surface->current.height,
};
if (extent.width == view->pending.width
&& extent.height == view->pending.height) {
wlr_log(WLR_DEBUG, "window geometry for client (%s) "
"appears to be incorrect - ignoring",
view_get_string_prop(view, "app_id"));
size = extent; /* Use surface extent instead */
}
}
struct wlr_box *current = &view->current;
xdg: handle initially maximized xdg-shell views better Currently, initially maximized (or fullscreen) xdg-shell views exhibit one of two issues: - some (e.g. GTK and Qt apps) paint an initial frame un-maximized (before the "map" event) and only maximize in a later commit - others (e.g. foot) maximize immediately without flicker, but never store a valid natural size, so we end up using a fallback (640x480) Under KWin, neither of these issues occur, so I looked into what labwc is doing wrong. It seems that: - wlroots internally sends an initial configure event with a size of 0x0 to all xdg-shell views. This requests the client to set its own preferred (a.k.a. natural) size. - For an initially maximized/fullscreen view, the initial configure event should contain the maximized/fullscreen size rather than 0x0. In labwc, this means we have to call wlr_xdg_toplevel_set_size() earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG shows that the initial configure event now has the correct geometry, matching KWin behavior. With this change, GTK and Qt apps no longer paint an incorrect un-maximized frame. - However, this means that all xdg-shell views now suffer from the same issue as foot, where we never receive a commit with the un-maximized (natural) geometry. The correct way to get the natural geometry seems to be to wait until we want to un-maximize, and send a configure event of 0x0 at that point. Sending a configure event of 0x0 when un-maximizing is a bit annoying as it breaks some assumptions in labwc code. In particular: - view->natural_geometry may now be unknown (0x0), requiring various wlr_box_empty() checks sprinkled around. I added these in all the obvious places, but there could be some code paths that I missed. - Positioning the newly un-maximized view within view_maximize() no longer works since we don't know the natural size. Instead we have to run the positioning logic from the surface commit handler. This results in some extra complexity, especially for interactive move. See the new do_late_positioning() function in xdg.c. Some TODOs/FIXMEs (non-blocking in my opinion): - The view_wants_decorations() check is now duplicated in both the new_surface and map event handlers. I'm not sure if this is necessary but it seemed like the safest approach for now. More testing would be nice, particularly with various combinations of config and client SSD preferences. - Aside from the interactive move case, the "late positioning" logic always centers the view when un-maximizing, and does not invoke any of the smart placement logic. If we want to invoke smart placement here, I'd appreciate someone with more knowledge of that code to take a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
if (current->width != size.width || current->height != size.height) {
update_required = true;
}
uint32_t serial = view->pending_configure_serial;
if (serial > 0 && serial == xdg_surface->current.configure_serial) {
assert(view->pending_configure_timeout);
wl_event_source_remove(view->pending_configure_timeout);
view->pending_configure_serial = 0;
view->pending_configure_timeout = NULL;
update_required = true;
}
if (update_required) {
view_impl_apply_geometry(view, size.width, size.height);
/*
* Some views (e.g., terminals that scale as multiples of rows
* and columns, or windows that impose a fixed aspect ratio),
* may respond to a resize but alter the width or height. When
* this happens, view->pending will be out of sync with the
* actual geometry (size *and* position, depending on the edge
* from which the resize was attempted). When no other
* configure is pending, re-sync the pending geometry with the
* actual view.
*/
if (!view->pending_configure_serial) {
snap_constraints_update(view);
view->pending = view->current;
/*
* wlroots retains the size set by any call to
* wlr_xdg_toplevel_set_size and will send the retained
* values with every subsequent configure request. If a
* client has resized itself in the meantime, a
2024-03-08 21:59:20 +09:00
* configure request that sends the now-outdated size
* may prompt the client to resize itself unexpectedly.
*
* Calling wlr_xdg_toplevel_set_size to update the
* value held by wlroots is undesirable here, because
* that will trigger another configure event and we
* don't want to get stuck in a request-response loop.
* Instead, just manipulate the dimensions that *would*
* be adjusted by the call, so the right values will
* apply next time.
*
* This is not ideal, but it is the cleanest option.
*/
toplevel->scheduled.width = view->current.width;
toplevel->scheduled.height = view->current.height;
}
}
}
static int
handle_configure_timeout(void *data)
{
struct view *view = data;
assert(view->pending_configure_serial > 0);
assert(view->pending_configure_timeout);
const char *app_id = view_get_string_prop(view, "app_id");
wlr_log(WLR_INFO, "client (%s) did not respond to configure request "
"in %d ms", app_id, CONFIGURE_TIMEOUT_MS);
wl_event_source_remove(view->pending_configure_timeout);
view->pending_configure_serial = 0;
view->pending_configure_timeout = NULL;
/*
* No need to do anything else if the view is just being slow to
* map - the map handler will take care of the positioning.
*/
if (!view->mapped) {
return 0; /* ignored per wl_event_loop docs */
}
bool empty_pending = wlr_box_empty(&view->pending);
if (empty_pending || view->pending.x != view->current.x
|| view->pending.y != view->current.y) {
/*
* This is a pending move + resize and the client is
* taking too long to respond to the resize. Apply the
* move now (while keeping the current size) so that the
* desktop doesn't appear unresponsive.
*
* We do not use view_impl_apply_geometry() here since
* in this case we prefer to always put the top-left
* corner of the view at the desired position rather
* than anchoring some other edge or corner.
*
* Corner case: we may get here with an empty pending
* geometry in the case of an initially-maximized view
* which is taking a long time to un-maximize (seen for
* example with Thunderbird on slow machines). In that
* case we have no great options (we can't center the
* view since we don't know the un-maximized size yet),
* so set a fallback position.
*/
if (empty_pending) {
wlr_log(WLR_INFO, "using fallback position");
view->pending.x = VIEW_FALLBACK_X;
view->pending.y = VIEW_FALLBACK_Y;
/* At least try to keep it on the same output */
if (output_is_usable(view->output)) {
struct wlr_box box =
output_usable_area_in_layout_coords(view->output);
view->pending.x += box.x;
view->pending.y += box.y;
}
}
view->current.x = view->pending.x;
view->current.y = view->pending.y;
Don't detect edge-resizes in configure timeout If a client times out responding to a configure request then the handle_configure_timeout() callback is run. This cleans up the pending state and moves the view to the pending location, but keeps the current size. The idea is to stop slow applications causing too much lag when the user manipulates the window. This callback used view_impl_apply_geometry() to actually apply the changes. view_impl_apply_geometry() contains some heuristics to detect if we're resizing a window from the top, left, or top-left, and if so to do the expected behaviour of keeping the window's bottom/right corner in the same place. However, that code was erroneously triggering in the case when the user requests to change a window from maximized to fullscreen but the client times out on the configure request. handle_configure_timeout() decides to enact the movement of the window but keep its size at the old size and tells view_impl_apply_geometry() to do that. The current view position and size is 0,64 1920x1016, the pending position/size is 0,0 1920x1080, and the last committed size is 1920x1016. Looking at the current and pending position and size, the height changes while the bottom edge stays in the same place so this looks like a top-edge-resize and view_impl_apply_geometry() decides to keep the window's bottom edge in the same place while setting the position according to the last-committed size. This results in the window staying at position 0,64 size 1920x1016 despite being marked as fullscreen. My solution to this is just to change handle_configure_timeout() to directly change the view's position and call view_moved() if necessary. The idea of handle_configure_timeout() is to action the window movement now while discarding the size change, and let the size change take place later on when the client catches up. The logic of view_impl_apply_geometry() doesn't make sense in this case so just avoid it entirely. Fixes #1922
2024-09-23 16:17:22 +01:00
view_moved(view);
}
/* Re-sync pending view with current state */
snap_constraints_update(view);
view->pending = view->current;
return 0; /* ignored per wl_event_loop docs */
}
static void
set_pending_configure_serial(struct view *view, uint32_t serial)
{
view->pending_configure_serial = serial;
if (!view->pending_configure_timeout) {
view->pending_configure_timeout =
wl_event_loop_add_timer(view->server->wl_event_loop,
handle_configure_timeout, view);
}
wl_event_source_timer_update(view->pending_configure_timeout,
CONFIGURE_TIMEOUT_MS);
}
static void
handle_destroy(struct wl_listener *listener, void *data)
2019-12-26 21:37:31 +00:00
{
2019-12-27 20:48:58 +00:00
struct view *view = wl_container_of(listener, view, destroy);
assert(view->type == LAB_XDG_SHELL_VIEW);
struct xdg_toplevel_view *xdg_toplevel_view =
xdg_toplevel_view_from_view(view);
struct wlr_xdg_popup *popup, *tmp;
wl_list_for_each_safe(popup, tmp, &xdg_toplevel_view->xdg_surface->popups, link) {
wlr_xdg_popup_destroy(popup);
}
xdg_toplevel_view->xdg_surface->data = NULL;
xdg_toplevel_view->xdg_surface = NULL;
/* Remove xdg-shell view specific listeners */
wl_list_remove(&xdg_toplevel_view->set_app_id.link);
wl_list_remove(&xdg_toplevel_view->request_show_window_menu.link);
wl_list_remove(&xdg_toplevel_view->new_popup.link);
wl_list_remove(&view->commit.link);
if (view->pending_configure_timeout) {
wl_event_source_remove(view->pending_configure_timeout);
view->pending_configure_timeout = NULL;
}
view_destroy(view);
2019-12-26 21:37:31 +00:00
}
static void
handle_request_move(struct wl_listener *listener, void *data)
2019-12-26 21:37:31 +00:00
{
2021-09-21 22:05:56 +01:00
/*
* This event is raised when a client would like to begin an interactive
2019-12-26 21:37:31 +00:00
* move, typically because the user clicked on their client-side
2019-12-27 21:22:45 +00:00
* decorations. Note that a more sophisticated compositor should check
2024-03-08 21:59:20 +09:00
* the provided serial against a list of button press serials sent to
* this client, to prevent the client from requesting this whenever they
2021-09-21 22:05:56 +01:00
* want.
*/
2019-12-27 20:48:58 +00:00
struct view *view = wl_container_of(listener, view, request_move);
if (view == view->server->seat.pressed.view) {
interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE);
}
2019-12-26 21:37:31 +00:00
}
static void
handle_request_resize(struct wl_listener *listener, void *data)
2019-12-26 21:37:31 +00:00
{
2021-09-21 22:05:56 +01:00
/*
* This event is raised when a client would like to begin an interactive
2019-12-26 21:37:31 +00:00
* resize, typically because the user clicked on their client-side
2019-12-27 21:22:45 +00:00
* decorations. Note that a more sophisticated compositor should check
2024-03-08 21:59:20 +09:00
* the provided serial against a list of button press serials sent to
* this client, to prevent the client from requesting this whenever they
2021-09-21 22:05:56 +01:00
* want.
*/
2019-12-26 21:37:31 +00:00
struct wlr_xdg_toplevel_resize_event *event = data;
2019-12-27 20:48:58 +00:00
struct view *view = wl_container_of(listener, view, request_resize);
if (view == view->server->seat.pressed.view) {
interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges);
}
2019-12-26 21:37:31 +00:00
}
static void
handle_request_minimize(struct wl_listener *listener, void *data)
{
struct view *view = wl_container_of(listener, view, request_minimize);
view_minimize(view, xdg_toplevel_from_view(view)->requested.minimized);
}
static void
handle_request_maximize(struct wl_listener *listener, void *data)
{
struct view *view = wl_container_of(listener, view, request_maximize);
struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view);
if (!toplevel->base->initialized) {
/*
* Do nothing if we have not received the initial commit yet.
* We will maximize the view in the commit handler.
*/
return;
}
if (!view->mapped && !view->output) {
2023-02-28 11:46:48 -05:00
view_set_output(view, output_nearest_to_cursor(view->server));
}
bool maximized = toplevel->requested.maximized;
view_maximize(view, maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE,
/*store_natural_geometry*/ true);
}
2021-08-23 22:05:30 +01:00
static void
handle_request_fullscreen(struct wl_listener *listener, void *data)
{
struct view *view = wl_container_of(listener, view, request_fullscreen);
struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view);
if (!toplevel->base->initialized) {
/*
* Do nothing if we have not received the initial commit yet.
* We will fullscreen the view in the commit handler.
*/
return;
}
if (!view->mapped && !view->output) {
2023-02-28 11:46:48 -05:00
view_set_output(view, output_nearest_to_cursor(view->server));
}
set_fullscreen_from_request(view,
&xdg_toplevel_from_view(view)->requested);
2021-08-23 22:05:30 +01:00
}
2024-09-17 00:32:02 +09:00
static void
handle_request_show_window_menu(struct wl_listener *listener, void *data)
{
struct xdg_toplevel_view *xdg_toplevel_view = wl_container_of(
listener, xdg_toplevel_view, request_show_window_menu);
struct server *server = xdg_toplevel_view->base.server;
2024-09-17 00:32:02 +09:00
struct menu *menu = menu_get_by_id(server, "client-menu");
assert(menu);
menu->triggered_by_view = &xdg_toplevel_view->base;
2024-09-17 00:32:02 +09:00
struct wlr_cursor *cursor = server->seat.cursor;
menu_open_root(menu, cursor->x, cursor->y);
}
static void
handle_set_title(struct wl_listener *listener, void *data)
{
struct view *view = wl_container_of(listener, view, set_title);
view_update_title(view);
}
static void
handle_set_app_id(struct wl_listener *listener, void *data)
{
struct xdg_toplevel_view *xdg_toplevel_view =
wl_container_of(listener, xdg_toplevel_view, set_app_id);
struct view *view = &xdg_toplevel_view->base;
view_update_app_id(view);
}
static void
xdg_toplevel_view_configure(struct view *view, struct wlr_box geo)
{
uint32_t serial = 0;
xdg: handle initially maximized xdg-shell views better Currently, initially maximized (or fullscreen) xdg-shell views exhibit one of two issues: - some (e.g. GTK and Qt apps) paint an initial frame un-maximized (before the "map" event) and only maximize in a later commit - others (e.g. foot) maximize immediately without flicker, but never store a valid natural size, so we end up using a fallback (640x480) Under KWin, neither of these issues occur, so I looked into what labwc is doing wrong. It seems that: - wlroots internally sends an initial configure event with a size of 0x0 to all xdg-shell views. This requests the client to set its own preferred (a.k.a. natural) size. - For an initially maximized/fullscreen view, the initial configure event should contain the maximized/fullscreen size rather than 0x0. In labwc, this means we have to call wlr_xdg_toplevel_set_size() earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG shows that the initial configure event now has the correct geometry, matching KWin behavior. With this change, GTK and Qt apps no longer paint an incorrect un-maximized frame. - However, this means that all xdg-shell views now suffer from the same issue as foot, where we never receive a commit with the un-maximized (natural) geometry. The correct way to get the natural geometry seems to be to wait until we want to un-maximize, and send a configure event of 0x0 at that point. Sending a configure event of 0x0 when un-maximizing is a bit annoying as it breaks some assumptions in labwc code. In particular: - view->natural_geometry may now be unknown (0x0), requiring various wlr_box_empty() checks sprinkled around. I added these in all the obvious places, but there could be some code paths that I missed. - Positioning the newly un-maximized view within view_maximize() no longer works since we don't know the natural size. Instead we have to run the positioning logic from the surface commit handler. This results in some extra complexity, especially for interactive move. See the new do_late_positioning() function in xdg.c. Some TODOs/FIXMEs (non-blocking in my opinion): - The view_wants_decorations() check is now duplicated in both the new_surface and map event handlers. I'm not sure if this is necessary but it seemed like the safest approach for now. More testing would be nice, particularly with various combinations of config and client SSD preferences. - Aside from the interactive move case, the "late positioning" logic always centers the view when un-maximizing, and does not invoke any of the smart placement logic. If we want to invoke smart placement here, I'd appreciate someone with more knowledge of that code to take a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view);
/*
* We do not need to send a configure request unless the size
* changed (wayland has no notion of a global position). If the
* size is the same (and there is no pending configure request)
* then we can just move the view directly.
*/
if (geo.width != view->pending.width
|| geo.height != view->pending.height) {
if (toplevel->base->initialized) {
serial = wlr_xdg_toplevel_set_size(toplevel, geo.width, geo.height);
} else {
/*
* This may happen, for example, when a panel resizes because a
* foreign-toplevel has been destroyed. This would then trigger
* a call to desktop_arrange_all_views() which in turn explicitly
* also tries to configure unmapped surfaces. This is fine when
* trying to resize surfaces before they are mapped but it will
* also try to resize surfaces which have been unmapped but their
* associated struct view has not been destroyed yet.
*/
wlr_log(WLR_DEBUG, "Preventing configure of uninitialized surface");
}
}
view->pending = geo;
if (serial > 0) {
set_pending_configure_serial(view, serial);
} else if (view->pending_configure_serial == 0) {
view->current.x = geo.x;
view->current.y = geo.y;
view_moved(view);
}
}
static void
xdg_toplevel_view_close(struct view *view)
2020-09-02 21:00:28 +01:00
{
wlr_xdg_toplevel_send_close(xdg_toplevel_from_view(view));
2020-09-02 21:00:28 +01:00
}
static void
xdg_toplevel_view_maximize(struct view *view, enum view_axis maximized)
{
if (!xdg_toplevel_from_view(view)->base->initialized) {
wlr_log(WLR_DEBUG, "Prevented maximize notification for a non-intialized view");
return;
}
uint32_t serial = wlr_xdg_toplevel_set_maximized(
xdg_toplevel_from_view(view), maximized == VIEW_AXIS_BOTH);
if (serial > 0) {
set_pending_configure_serial(view, serial);
}
}
static void
xdg_toplevel_view_minimize(struct view *view, bool minimized)
{
/* noop */
}
static struct wlr_xdg_toplevel *
top_parent_of(struct view *view)
{
struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view);
while (toplevel->parent) {
toplevel = toplevel->parent;
}
return toplevel;
}
/* Return the most senior parent (=root) view */
static struct view *
xdg_toplevel_view_get_root(struct view *view)
{
struct wlr_xdg_toplevel *root = top_parent_of(view);
struct wlr_xdg_surface *surface = (struct wlr_xdg_surface *)root->base;
return (struct view *)surface->data;
}
static void
xdg_toplevel_view_append_children(struct view *self, struct wl_array *children)
{
struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(self);
struct view *view;
wl_list_for_each_reverse(view, &self->server->views, link) {
if (view == self) {
continue;
}
if (view->type != LAB_XDG_SHELL_VIEW) {
continue;
}
if (!view->mapped && !view->minimized) {
continue;
}
if (top_parent_of(view) != toplevel) {
continue;
}
struct view **child = wl_array_add(children, sizeof(*child));
*child = view;
}
}
static void
xdg_toplevel_view_set_activated(struct view *view, bool activated)
{
if (!xdg_toplevel_from_view(view)->base->initialized) {
wlr_log(WLR_DEBUG, "Prevented activating a non-intialized view");
return;
}
uint32_t serial = wlr_xdg_toplevel_set_activated(
xdg_toplevel_from_view(view), activated);
if (serial > 0) {
set_pending_configure_serial(view, serial);
}
}
2021-08-23 22:05:30 +01:00
static void
xdg_toplevel_view_set_fullscreen(struct view *view, bool fullscreen)
{
if (!xdg_toplevel_from_view(view)->base->initialized) {
wlr_log(WLR_DEBUG, "Prevented fullscreening a non-intialized view");
return;
}
uint32_t serial = wlr_xdg_toplevel_set_fullscreen(
xdg_toplevel_from_view(view), fullscreen);
if (serial > 0) {
set_pending_configure_serial(view, serial);
}
2020-09-25 20:22:18 +01:00
}
static void
xdg_toplevel_view_notify_tiled(struct view *view)
{
/* Take no action if xdg-shell tiling is disabled */
if (rc.snap_tiling_events_mode == LAB_TILING_EVENTS_NEVER) {
return;
}
if (!xdg_toplevel_from_view(view)->base->initialized) {
wlr_log(WLR_DEBUG, "Prevented tiling notification for a non-intialized view");
return;
}
enum lab_edge edge = LAB_EDGE_NONE;
bool want_edge = rc.snap_tiling_events_mode & LAB_TILING_EVENTS_EDGE;
bool want_region = rc.snap_tiling_events_mode & LAB_TILING_EVENTS_REGION;
/*
* Edge-snapped view are considered tiled on the snapped edge and those
* perpendicular to it.
*/
if (want_edge) {
switch (view->tiled) {
2025-08-17 16:01:50 -04:00
case LAB_EDGE_LEFT:
edge = LAB_EDGES_EXCEPT_RIGHT;
break;
2025-08-17 16:01:50 -04:00
case LAB_EDGE_RIGHT:
edge = LAB_EDGES_EXCEPT_LEFT;
break;
case LAB_EDGE_TOP:
edge = LAB_EDGES_EXCEPT_BOTTOM;
break;
case LAB_EDGE_BOTTOM:
edge = LAB_EDGES_EXCEPT_TOP;
break;
case LAB_EDGES_TOP_LEFT:
case LAB_EDGES_TOP_RIGHT:
case LAB_EDGES_BOTTOM_LEFT:
case LAB_EDGES_BOTTOM_RIGHT:
edge = view->tiled;
break;
2025-08-17 16:01:50 -04:00
/* TODO: LAB_EDGE_CENTER? */
default:
edge = LAB_EDGE_NONE;
}
}
if (want_region && view->tiled_region) {
/* Region-snapped views are considered tiled on all edges */
edge = LAB_EDGES_ALL;
}
uint32_t serial =
wlr_xdg_toplevel_set_tiled(xdg_toplevel_from_view(view), edge);
if (serial > 0) {
set_pending_configure_serial(view, serial);
}
}
static struct view *
lookup_view_by_xdg_toplevel(struct server *server,
struct wlr_xdg_toplevel *xdg_toplevel)
{
struct view *view;
wl_list_for_each(view, &server->views, link) {
if (view->type != LAB_XDG_SHELL_VIEW) {
continue;
}
if (xdg_toplevel_from_view(view) == xdg_toplevel) {
return view;
}
}
return NULL;
}
static void
set_initial_position(struct view *view)
{
struct wlr_xdg_toplevel *parent_xdg_toplevel =
xdg_toplevel_from_view(view)->parent;
view_constrain_size_to_that_of_usable_area(view);
if (parent_xdg_toplevel) {
/* Child views are center-aligned relative to their parents */
struct view *parent = lookup_view_by_xdg_toplevel(
view->server, parent_xdg_toplevel);
assert(parent);
2023-02-28 11:46:48 -05:00
view_set_output(view, parent->output);
view_center(view, &parent->pending);
return;
}
/* All other views are placed according to a configured strategy */
view_place_by_policy(view, /* allow_cursor */ true, rc.placement_policy);
}
static const char *
xdg_toplevel_view_get_string_prop(struct view *view, const char *prop)
{
struct xdg_toplevel_view *xdg_view = xdg_toplevel_view_from_view(view);
struct wlr_xdg_toplevel *xdg_toplevel = xdg_view->xdg_surface
? xdg_view->xdg_surface->toplevel
: NULL;
if (!xdg_toplevel) {
/*
* This may happen due to a matchOnce rule when
* a view is destroyed while A-Tab is open. See
* https://github.com/labwc/labwc/issues/1082#issuecomment-1716137180
*/
return "";
}
if (!strcmp(prop, "title")) {
return xdg_toplevel->title ? xdg_toplevel->title : "";
}
if (!strcmp(prop, "app_id")) {
return xdg_toplevel->app_id ? xdg_toplevel->app_id : "";
}
return "";
}
static void
init_foreign_toplevel(struct view *view)
{
assert(!view->foreign_toplevel);
view->foreign_toplevel = foreign_toplevel_create(view);
struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view);
if (!toplevel->parent) {
return;
}
struct wlr_xdg_surface *surface = toplevel->parent->base;
struct view *parent = surface->data;
if (!parent->foreign_toplevel) {
return;
}
foreign_toplevel_set_parent(view->foreign_toplevel, parent->foreign_toplevel);
}
static void
xdg_toplevel_view_map(struct view *view)
{
if (view->mapped) {
return;
}
view->mapped = true;
/*
* An output should have been chosen when the surface was first
* created, but take one more opportunity to assign an output if not.
*/
if (!view->output) {
2023-02-28 11:46:48 -05:00
view_set_output(view, output_nearest_to_cursor(view->server));
}
struct wlr_xdg_surface *xdg_surface = xdg_surface_from_view(view);
wlr_scene_node_set_enabled(&view->scene_tree->node, true);
if (!view->foreign_toplevel) {
init_foreign_toplevel(view);
/*
* Initial outputs will be synced via
* view->events.new_outputs on view_moved()
*/
}
if (!view->been_mapped) {
2024-04-19 13:35:16 +02:00
if (view_wants_decorations(view)) {
view_set_ssd_mode(view, LAB_SSD_MODE_FULL);
} else {
view_set_ssd_mode(view, LAB_SSD_MODE_NONE);
}
/*
xdg: handle initially maximized xdg-shell views better Currently, initially maximized (or fullscreen) xdg-shell views exhibit one of two issues: - some (e.g. GTK and Qt apps) paint an initial frame un-maximized (before the "map" event) and only maximize in a later commit - others (e.g. foot) maximize immediately without flicker, but never store a valid natural size, so we end up using a fallback (640x480) Under KWin, neither of these issues occur, so I looked into what labwc is doing wrong. It seems that: - wlroots internally sends an initial configure event with a size of 0x0 to all xdg-shell views. This requests the client to set its own preferred (a.k.a. natural) size. - For an initially maximized/fullscreen view, the initial configure event should contain the maximized/fullscreen size rather than 0x0. In labwc, this means we have to call wlr_xdg_toplevel_set_size() earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG shows that the initial configure event now has the correct geometry, matching KWin behavior. With this change, GTK and Qt apps no longer paint an incorrect un-maximized frame. - However, this means that all xdg-shell views now suffer from the same issue as foot, where we never receive a commit with the un-maximized (natural) geometry. The correct way to get the natural geometry seems to be to wait until we want to un-maximize, and send a configure event of 0x0 at that point. Sending a configure event of 0x0 when un-maximizing is a bit annoying as it breaks some assumptions in labwc code. In particular: - view->natural_geometry may now be unknown (0x0), requiring various wlr_box_empty() checks sprinkled around. I added these in all the obvious places, but there could be some code paths that I missed. - Positioning the newly un-maximized view within view_maximize() no longer works since we don't know the natural size. Instead we have to run the positioning logic from the surface commit handler. This results in some extra complexity, especially for interactive move. See the new do_late_positioning() function in xdg.c. Some TODOs/FIXMEs (non-blocking in my opinion): - The view_wants_decorations() check is now duplicated in both the new_surface and map event handlers. I'm not sure if this is necessary but it seemed like the safest approach for now. More testing would be nice, particularly with various combinations of config and client SSD preferences. - Aside from the interactive move case, the "late positioning" logic always centers the view when un-maximizing, and does not invoke any of the smart placement logic. If we want to invoke smart placement here, I'd appreciate someone with more knowledge of that code to take a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
* Set initial "pending" dimensions. "Current"
* dimensions remain zero until handle_commit().
*/
if (wlr_box_empty(&view->pending)) {
view->pending.width = xdg_surface->geometry.width;
view->pending.height = xdg_surface->geometry.height;
}
/*
* Set initial "pending" position for floating views.
*/
if (view_is_floating(view)) {
set_initial_position(view);
}
/*
* Set initial "current" position directly before
* calling view_moved() to reduce flicker
*/
view->current.x = view->pending.x;
view->current.y = view->pending.y;
view_moved(view);
}
view_impl_map(view);
view->been_mapped = true;
}
static void
xdg_toplevel_view_unmap(struct view *view, bool client_request)
{
if (view->mapped) {
view->mapped = false;
wlr_scene_node_set_enabled(&view->scene_tree->node, false);
view_impl_unmap(view);
}
/*
* If the view was explicitly unmapped by the client (rather
* than just minimized), destroy the foreign toplevel handle so
* the unmapped view doesn't show up in panels and the like.
*/
if (client_request && view->foreign_toplevel) {
foreign_toplevel_destroy(view->foreign_toplevel);
view->foreign_toplevel = NULL;
}
}
static pid_t
xdg_view_get_pid(struct view *view)
{
assert(view);
pid_t pid = -1;
if (view->surface && view->surface->resource
&& view->surface->resource->client) {
struct wl_client *client = view->surface->resource->client;
wl_client_get_credentials(client, &pid, NULL, NULL);
}
return pid;
}
static const struct view_impl xdg_toplevel_view_impl = {
.configure = xdg_toplevel_view_configure,
.close = xdg_toplevel_view_close,
.get_string_prop = xdg_toplevel_view_get_string_prop,
.map = xdg_toplevel_view_map,
.set_activated = xdg_toplevel_view_set_activated,
2021-08-23 22:05:30 +01:00
.set_fullscreen = xdg_toplevel_view_set_fullscreen,
.notify_tiled = xdg_toplevel_view_notify_tiled,
.unmap = xdg_toplevel_view_unmap,
.maximize = xdg_toplevel_view_maximize,
.minimize = xdg_toplevel_view_minimize,
.get_root = xdg_toplevel_view_get_root,
.append_children = xdg_toplevel_view_append_children,
.get_size_hints = xdg_toplevel_view_get_size_hints,
.contains_window_type = xdg_toplevel_view_contains_window_type,
.get_pid = xdg_view_get_pid,
};
struct token_data {
bool had_valid_surface;
bool had_valid_seat;
struct wl_listener destroy;
};
static void
handle_xdg_activation_token_destroy(struct wl_listener *listener, void *data)
{
struct token_data *token_data = wl_container_of(listener, token_data, destroy);
wl_list_remove(&token_data->destroy.link);
free(token_data);
}
static void
handle_xdg_activation_new_token(struct wl_listener *listener, void *data)
{
struct wlr_xdg_activation_token_v1 *token = data;
struct token_data *token_data = znew(*token_data);
token_data->had_valid_surface = !!token->surface;
token_data->had_valid_seat = !!token->seat;
token->data = token_data;
token_data->destroy.notify = handle_xdg_activation_token_destroy;
wl_signal_add(&token->events.destroy, &token_data->destroy);
}
static void
handle_xdg_activation_request(struct wl_listener *listener, void *data)
Add xdg-activation protocol This PR allows applications to activate themselves *if they provide a valid xdg_activation token* (e.g. raise to the top and get keyboard focus). These tokens are given out by the xdg_activation protocol implemented by wlroots and can be configured by the client requesting the token in three ways: - an "empty" token (apparently used to tell the compositor about "urgency") - seat / input serial attached - surface attached Wlroots makes sure that - If the client attached the seat / input serial: those two are valid. - If the client attached a surface: that it has keyboard focus at the point where the request is finalized. There is a patch [1] pending for backport to wlroots 0.16 that also allows valid tokens when the supplied surface had cursor focus. - a token is only valid for 30 seconds after being given out The token can then be used by the client or given to other clients by unspecified means (e.g. via environment variable or dbus) which then may use the token on their own surface and request activation. We only handle the actual request activation part: - If the seat is set on the token we know wlroots validated seat and input serial - Thus, if no seat is set we deny the activation request so we don't have windows suddenly popping up and taking over the keyboard focus (focus stealing prevention) - We should also check for the surface being set but we can't do that with wlroots 0.16 as it will reset the surface to `NULL` when it is destroyed (which is something that usually happens for notifications). Once we move to wlroots 0.17.x we can add the missing surface check because it provides a `new_token` signal. We can use it to attach further details to the token which are then verified later when we decide if we allow the activate request or not. With this PR in place the following setup should activate windows: - launching an URL in foot should activate the target application if it is already running, foot requests a proper token and then sets it as `XDG_ACTIVATION_TOKEN` environment var before spawning `xdg-open` - clicking on a `mako` notification with a `default` action defined should request a proper token which is then given to the application starting the notification and can thus be used to activate itself This protocol is still very much in the process of being implemented / finalized all over the place (e.g. GTK / QT / Firefox / notification daemons, ..) but we should do our part and remove labwc from the puzzle of potential issues causing this not to work. [1] https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/f6008ffff41f67e3c20bd8b3be8f216da6a4bb30)
2023-02-04 10:07:46 +01:00
{
const struct wlr_xdg_activation_v1_request_activate_event *event = data;
struct token_data *token_data = event->token->data;
assert(token_data);
Add xdg-activation protocol This PR allows applications to activate themselves *if they provide a valid xdg_activation token* (e.g. raise to the top and get keyboard focus). These tokens are given out by the xdg_activation protocol implemented by wlroots and can be configured by the client requesting the token in three ways: - an "empty" token (apparently used to tell the compositor about "urgency") - seat / input serial attached - surface attached Wlroots makes sure that - If the client attached the seat / input serial: those two are valid. - If the client attached a surface: that it has keyboard focus at the point where the request is finalized. There is a patch [1] pending for backport to wlroots 0.16 that also allows valid tokens when the supplied surface had cursor focus. - a token is only valid for 30 seconds after being given out The token can then be used by the client or given to other clients by unspecified means (e.g. via environment variable or dbus) which then may use the token on their own surface and request activation. We only handle the actual request activation part: - If the seat is set on the token we know wlroots validated seat and input serial - Thus, if no seat is set we deny the activation request so we don't have windows suddenly popping up and taking over the keyboard focus (focus stealing prevention) - We should also check for the surface being set but we can't do that with wlroots 0.16 as it will reset the surface to `NULL` when it is destroyed (which is something that usually happens for notifications). Once we move to wlroots 0.17.x we can add the missing surface check because it provides a `new_token` signal. We can use it to attach further details to the token which are then verified later when we decide if we allow the activate request or not. With this PR in place the following setup should activate windows: - launching an URL in foot should activate the target application if it is already running, foot requests a proper token and then sets it as `XDG_ACTIVATION_TOKEN` environment var before spawning `xdg-open` - clicking on a `mako` notification with a `default` action defined should request a proper token which is then given to the application starting the notification and can thus be used to activate itself This protocol is still very much in the process of being implemented / finalized all over the place (e.g. GTK / QT / Firefox / notification daemons, ..) but we should do our part and remove labwc from the puzzle of potential issues causing this not to work. [1] https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/f6008ffff41f67e3c20bd8b3be8f216da6a4bb30)
2023-02-04 10:07:46 +01:00
struct wlr_xdg_surface *xdg_surface =
wlr_xdg_surface_try_from_wlr_surface(event->surface);
if (!xdg_surface) {
Add xdg-activation protocol This PR allows applications to activate themselves *if they provide a valid xdg_activation token* (e.g. raise to the top and get keyboard focus). These tokens are given out by the xdg_activation protocol implemented by wlroots and can be configured by the client requesting the token in three ways: - an "empty" token (apparently used to tell the compositor about "urgency") - seat / input serial attached - surface attached Wlroots makes sure that - If the client attached the seat / input serial: those two are valid. - If the client attached a surface: that it has keyboard focus at the point where the request is finalized. There is a patch [1] pending for backport to wlroots 0.16 that also allows valid tokens when the supplied surface had cursor focus. - a token is only valid for 30 seconds after being given out The token can then be used by the client or given to other clients by unspecified means (e.g. via environment variable or dbus) which then may use the token on their own surface and request activation. We only handle the actual request activation part: - If the seat is set on the token we know wlroots validated seat and input serial - Thus, if no seat is set we deny the activation request so we don't have windows suddenly popping up and taking over the keyboard focus (focus stealing prevention) - We should also check for the surface being set but we can't do that with wlroots 0.16 as it will reset the surface to `NULL` when it is destroyed (which is something that usually happens for notifications). Once we move to wlroots 0.17.x we can add the missing surface check because it provides a `new_token` signal. We can use it to attach further details to the token which are then verified later when we decide if we allow the activate request or not. With this PR in place the following setup should activate windows: - launching an URL in foot should activate the target application if it is already running, foot requests a proper token and then sets it as `XDG_ACTIVATION_TOKEN` environment var before spawning `xdg-open` - clicking on a `mako` notification with a `default` action defined should request a proper token which is then given to the application starting the notification and can thus be used to activate itself This protocol is still very much in the process of being implemented / finalized all over the place (e.g. GTK / QT / Firefox / notification daemons, ..) but we should do our part and remove labwc from the puzzle of potential issues causing this not to work. [1] https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/f6008ffff41f67e3c20bd8b3be8f216da6a4bb30)
2023-02-04 10:07:46 +01:00
return;
}
struct view *view = xdg_surface->data;
Add xdg-activation protocol This PR allows applications to activate themselves *if they provide a valid xdg_activation token* (e.g. raise to the top and get keyboard focus). These tokens are given out by the xdg_activation protocol implemented by wlroots and can be configured by the client requesting the token in three ways: - an "empty" token (apparently used to tell the compositor about "urgency") - seat / input serial attached - surface attached Wlroots makes sure that - If the client attached the seat / input serial: those two are valid. - If the client attached a surface: that it has keyboard focus at the point where the request is finalized. There is a patch [1] pending for backport to wlroots 0.16 that also allows valid tokens when the supplied surface had cursor focus. - a token is only valid for 30 seconds after being given out The token can then be used by the client or given to other clients by unspecified means (e.g. via environment variable or dbus) which then may use the token on their own surface and request activation. We only handle the actual request activation part: - If the seat is set on the token we know wlroots validated seat and input serial - Thus, if no seat is set we deny the activation request so we don't have windows suddenly popping up and taking over the keyboard focus (focus stealing prevention) - We should also check for the surface being set but we can't do that with wlroots 0.16 as it will reset the surface to `NULL` when it is destroyed (which is something that usually happens for notifications). Once we move to wlroots 0.17.x we can add the missing surface check because it provides a `new_token` signal. We can use it to attach further details to the token which are then verified later when we decide if we allow the activate request or not. With this PR in place the following setup should activate windows: - launching an URL in foot should activate the target application if it is already running, foot requests a proper token and then sets it as `XDG_ACTIVATION_TOKEN` environment var before spawning `xdg-open` - clicking on a `mako` notification with a `default` action defined should request a proper token which is then given to the application starting the notification and can thus be used to activate itself This protocol is still very much in the process of being implemented / finalized all over the place (e.g. GTK / QT / Firefox / notification daemons, ..) but we should do our part and remove labwc from the puzzle of potential issues causing this not to work. [1] https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/f6008ffff41f67e3c20bd8b3be8f216da6a4bb30)
2023-02-04 10:07:46 +01:00
if (!view) {
wlr_log(WLR_INFO, "Not activating surface - no view attached to surface");
return;
}
if (!token_data->had_valid_seat) {
Add xdg-activation protocol This PR allows applications to activate themselves *if they provide a valid xdg_activation token* (e.g. raise to the top and get keyboard focus). These tokens are given out by the xdg_activation protocol implemented by wlroots and can be configured by the client requesting the token in three ways: - an "empty" token (apparently used to tell the compositor about "urgency") - seat / input serial attached - surface attached Wlroots makes sure that - If the client attached the seat / input serial: those two are valid. - If the client attached a surface: that it has keyboard focus at the point where the request is finalized. There is a patch [1] pending for backport to wlroots 0.16 that also allows valid tokens when the supplied surface had cursor focus. - a token is only valid for 30 seconds after being given out The token can then be used by the client or given to other clients by unspecified means (e.g. via environment variable or dbus) which then may use the token on their own surface and request activation. We only handle the actual request activation part: - If the seat is set on the token we know wlroots validated seat and input serial - Thus, if no seat is set we deny the activation request so we don't have windows suddenly popping up and taking over the keyboard focus (focus stealing prevention) - We should also check for the surface being set but we can't do that with wlroots 0.16 as it will reset the surface to `NULL` when it is destroyed (which is something that usually happens for notifications). Once we move to wlroots 0.17.x we can add the missing surface check because it provides a `new_token` signal. We can use it to attach further details to the token which are then verified later when we decide if we allow the activate request or not. With this PR in place the following setup should activate windows: - launching an URL in foot should activate the target application if it is already running, foot requests a proper token and then sets it as `XDG_ACTIVATION_TOKEN` environment var before spawning `xdg-open` - clicking on a `mako` notification with a `default` action defined should request a proper token which is then given to the application starting the notification and can thus be used to activate itself This protocol is still very much in the process of being implemented / finalized all over the place (e.g. GTK / QT / Firefox / notification daemons, ..) but we should do our part and remove labwc from the puzzle of potential issues causing this not to work. [1] https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/f6008ffff41f67e3c20bd8b3be8f216da6a4bb30)
2023-02-04 10:07:46 +01:00
wlr_log(WLR_INFO, "Denying focus request, seat wasn't supplied");
return;
}
/*
* TODO: The verification of source surface is temporarily disabled to
* allow activation of some clients (e.g. thunderbird). Reland this
* check when we implement the configuration for activation policy or
* urgency hints.
*
* if (!token_data->had_valid_surface) {
* wlr_log(WLR_INFO, "Denying focus request, source surface not set");
* return;
* }
*/
Add xdg-activation protocol This PR allows applications to activate themselves *if they provide a valid xdg_activation token* (e.g. raise to the top and get keyboard focus). These tokens are given out by the xdg_activation protocol implemented by wlroots and can be configured by the client requesting the token in three ways: - an "empty" token (apparently used to tell the compositor about "urgency") - seat / input serial attached - surface attached Wlroots makes sure that - If the client attached the seat / input serial: those two are valid. - If the client attached a surface: that it has keyboard focus at the point where the request is finalized. There is a patch [1] pending for backport to wlroots 0.16 that also allows valid tokens when the supplied surface had cursor focus. - a token is only valid for 30 seconds after being given out The token can then be used by the client or given to other clients by unspecified means (e.g. via environment variable or dbus) which then may use the token on their own surface and request activation. We only handle the actual request activation part: - If the seat is set on the token we know wlroots validated seat and input serial - Thus, if no seat is set we deny the activation request so we don't have windows suddenly popping up and taking over the keyboard focus (focus stealing prevention) - We should also check for the surface being set but we can't do that with wlroots 0.16 as it will reset the surface to `NULL` when it is destroyed (which is something that usually happens for notifications). Once we move to wlroots 0.17.x we can add the missing surface check because it provides a `new_token` signal. We can use it to attach further details to the token which are then verified later when we decide if we allow the activate request or not. With this PR in place the following setup should activate windows: - launching an URL in foot should activate the target application if it is already running, foot requests a proper token and then sets it as `XDG_ACTIVATION_TOKEN` environment var before spawning `xdg-open` - clicking on a `mako` notification with a `default` action defined should request a proper token which is then given to the application starting the notification and can thus be used to activate itself This protocol is still very much in the process of being implemented / finalized all over the place (e.g. GTK / QT / Firefox / notification daemons, ..) but we should do our part and remove labwc from the puzzle of potential issues causing this not to work. [1] https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/f6008ffff41f67e3c20bd8b3be8f216da6a4bb30)
2023-02-04 10:07:46 +01:00
if (window_rules_get_property(view, "ignoreFocusRequest") == LAB_PROP_TRUE) {
wlr_log(WLR_INFO, "Ignoring focus request due to window rule configuration");
return;
}
Add xdg-activation protocol This PR allows applications to activate themselves *if they provide a valid xdg_activation token* (e.g. raise to the top and get keyboard focus). These tokens are given out by the xdg_activation protocol implemented by wlroots and can be configured by the client requesting the token in three ways: - an "empty" token (apparently used to tell the compositor about "urgency") - seat / input serial attached - surface attached Wlroots makes sure that - If the client attached the seat / input serial: those two are valid. - If the client attached a surface: that it has keyboard focus at the point where the request is finalized. There is a patch [1] pending for backport to wlroots 0.16 that also allows valid tokens when the supplied surface had cursor focus. - a token is only valid for 30 seconds after being given out The token can then be used by the client or given to other clients by unspecified means (e.g. via environment variable or dbus) which then may use the token on their own surface and request activation. We only handle the actual request activation part: - If the seat is set on the token we know wlroots validated seat and input serial - Thus, if no seat is set we deny the activation request so we don't have windows suddenly popping up and taking over the keyboard focus (focus stealing prevention) - We should also check for the surface being set but we can't do that with wlroots 0.16 as it will reset the surface to `NULL` when it is destroyed (which is something that usually happens for notifications). Once we move to wlroots 0.17.x we can add the missing surface check because it provides a `new_token` signal. We can use it to attach further details to the token which are then verified later when we decide if we allow the activate request or not. With this PR in place the following setup should activate windows: - launching an URL in foot should activate the target application if it is already running, foot requests a proper token and then sets it as `XDG_ACTIVATION_TOKEN` environment var before spawning `xdg-open` - clicking on a `mako` notification with a `default` action defined should request a proper token which is then given to the application starting the notification and can thus be used to activate itself This protocol is still very much in the process of being implemented / finalized all over the place (e.g. GTK / QT / Firefox / notification daemons, ..) but we should do our part and remove labwc from the puzzle of potential issues causing this not to work. [1] https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/f6008ffff41f67e3c20bd8b3be8f216da6a4bb30)
2023-02-04 10:07:46 +01:00
wlr_log(WLR_DEBUG, "Activating surface");
desktop_focus_view(view, /*raise*/ true);
Add xdg-activation protocol This PR allows applications to activate themselves *if they provide a valid xdg_activation token* (e.g. raise to the top and get keyboard focus). These tokens are given out by the xdg_activation protocol implemented by wlroots and can be configured by the client requesting the token in three ways: - an "empty" token (apparently used to tell the compositor about "urgency") - seat / input serial attached - surface attached Wlroots makes sure that - If the client attached the seat / input serial: those two are valid. - If the client attached a surface: that it has keyboard focus at the point where the request is finalized. There is a patch [1] pending for backport to wlroots 0.16 that also allows valid tokens when the supplied surface had cursor focus. - a token is only valid for 30 seconds after being given out The token can then be used by the client or given to other clients by unspecified means (e.g. via environment variable or dbus) which then may use the token on their own surface and request activation. We only handle the actual request activation part: - If the seat is set on the token we know wlroots validated seat and input serial - Thus, if no seat is set we deny the activation request so we don't have windows suddenly popping up and taking over the keyboard focus (focus stealing prevention) - We should also check for the surface being set but we can't do that with wlroots 0.16 as it will reset the surface to `NULL` when it is destroyed (which is something that usually happens for notifications). Once we move to wlroots 0.17.x we can add the missing surface check because it provides a `new_token` signal. We can use it to attach further details to the token which are then verified later when we decide if we allow the activate request or not. With this PR in place the following setup should activate windows: - launching an URL in foot should activate the target application if it is already running, foot requests a proper token and then sets it as `XDG_ACTIVATION_TOKEN` environment var before spawning `xdg-open` - clicking on a `mako` notification with a `default` action defined should request a proper token which is then given to the application starting the notification and can thus be used to activate itself This protocol is still very much in the process of being implemented / finalized all over the place (e.g. GTK / QT / Firefox / notification daemons, ..) but we should do our part and remove labwc from the puzzle of potential issues causing this not to work. [1] https://gitlab.freedesktop.org/wlroots/wlroots/-/commit/f6008ffff41f67e3c20bd8b3be8f216da6a4bb30)
2023-02-04 10:07:46 +01:00
}
/*
* We use the following struct user_data pointers:
* - wlr_xdg_surface->data = view
* for the wlr_xdg_toplevel_decoration_v1 implementation
* - wlr_surface->data = scene_tree
* to help the popups find their parent nodes
*/
static void
handle_new_xdg_toplevel(struct wl_listener *listener, void *data)
2019-12-26 21:37:31 +00:00
{
2019-12-27 20:48:58 +00:00
struct server *server =
wl_container_of(listener, server, new_xdg_toplevel);
struct wlr_xdg_toplevel *xdg_toplevel = data;
struct wlr_xdg_surface *xdg_surface = xdg_toplevel->base;
assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
2020-10-08 19:58:47 +01:00
wlr_xdg_surface_ping(xdg_surface);
2019-12-26 21:37:31 +00:00
struct xdg_toplevel_view *xdg_toplevel_view = znew(*xdg_toplevel_view);
struct view *view = &xdg_toplevel_view->base;
2019-12-26 21:37:31 +00:00
view->server = server;
view->type = LAB_XDG_SHELL_VIEW;
view->impl = &xdg_toplevel_view_impl;
view_init(view);
xdg_toplevel_view->xdg_surface = xdg_surface;
2019-12-26 21:37:31 +00:00
/*
* Pick an output for the surface as soon as its created, so that the
* client can be notified about any fractional scale before it is given
* the chance to configure itself (and possibly pick its dimensions).
*/
view_set_output(view, output_nearest_to_cursor(server));
if (view->output) {
wlr_fractional_scale_v1_notify_scale(xdg_surface->surface,
view->output->wlr_output->scale);
}
2024-07-25 21:56:05 +02:00
view->workspace = server->workspaces.current;
2022-06-15 02:02:50 +02:00
view->scene_tree = wlr_scene_tree_create(view->workspace->tree);
wlr_scene_node_set_enabled(&view->scene_tree->node, false);
2022-02-21 03:18:38 +01:00
struct wlr_scene_tree *tree = wlr_scene_xdg_surface_create(
view->scene_tree, xdg_surface);
if (!tree) {
/* TODO: might need further clean up */
wl_resource_post_no_memory(xdg_surface->resource);
free(xdg_toplevel_view);
return;
}
view->content_tree = tree;
node_descriptor_create(&view->scene_tree->node,
LAB_NODE_DESC_VIEW, view);
/*
* The xdg_toplevel_decoration and kde_server_decoration protocols
* expects clients to use client side decorations unless server side
* decorations are negotiated. So we default to client side ones here.
*
* TODO: We may want to assign the default based on a new rc.xml
* config option like "enforce-server" in the future.
*/
view->ssd_preference = LAB_SSD_PREF_CLIENT;
/*
* xdg_toplevel_decoration and kde_server_decoration use this
* pointer to connect the view to a decoration object that may
* be created in the future.
*/
xdg_surface->data = view;
/*
* GTK4 initializes the decorations on the wl_surface before
* converting it into a xdg surface. This call takes care of
* connecting the view to an existing decoration. If there
* is no existing decoration object available for the
* wl_surface, this call is a no-op.
*/
kde_server_decoration_set_view(view, xdg_surface->surface);
2024-02-23 03:50:55 +09:00
/* In support of xdg popups and IME popup */
view->surface = xdg_surface->surface;
view->surface->data = tree;
view_connect_map(view, xdg_surface->surface);
2019-12-26 21:37:31 +00:00
struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel;
CONNECT_SIGNAL(toplevel, view, destroy);
CONNECT_SIGNAL(toplevel, view, request_move);
CONNECT_SIGNAL(toplevel, view, request_resize);
CONNECT_SIGNAL(toplevel, view, request_minimize);
CONNECT_SIGNAL(toplevel, view, request_maximize);
CONNECT_SIGNAL(toplevel, view, request_fullscreen);
CONNECT_SIGNAL(toplevel, view, set_title);
CONNECT_SIGNAL(view->surface, view, commit);
/* Events specific to XDG toplevel views */
CONNECT_SIGNAL(toplevel, xdg_toplevel_view, set_app_id);
2024-09-17 00:32:02 +09:00
CONNECT_SIGNAL(toplevel, xdg_toplevel_view, request_show_window_menu);
CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup);
2019-12-26 21:37:31 +00:00
wl_list_insert(&server->views, &view->link);
}
static void
handle_xdg_toplevel_icon_set_icon(struct wl_listener *listener, void *data)
{
struct wlr_xdg_toplevel_icon_manager_v1_set_icon_event *event = data;
struct wlr_xdg_surface *xdg_surface = event->toplevel->base;
struct view *view = xdg_surface->data;
assert(view);
char *icon_name = NULL;
struct wl_array buffers;
wl_array_init(&buffers);
if (event->icon) {
icon_name = event->icon->name;
struct wlr_xdg_toplevel_icon_v1_buffer *icon_buffer;
wl_list_for_each(icon_buffer, &event->icon->buffers, link) {
struct lab_data_buffer *buffer =
buffer_create_from_wlr_buffer(icon_buffer->buffer);
if (buffer) {
array_add(&buffers, buffer);
}
}
}
2025-05-29 19:17:24 +02:00
/* view takes ownership of the buffers */
view_set_icon(view, icon_name, &buffers);
wl_array_release(&buffers);
}
void
xdg_shell_init(struct server *server)
{
server->xdg_shell = wlr_xdg_shell_create(server->wl_display,
LAB_XDG_SHELL_VERSION);
if (!server->xdg_shell) {
wlr_log(WLR_ERROR, "unable to create the XDG shell interface");
exit(EXIT_FAILURE);
}
server->new_xdg_toplevel.notify = handle_new_xdg_toplevel;
wl_signal_add(&server->xdg_shell->events.new_toplevel, &server->new_xdg_toplevel);
server->xdg_activation = wlr_xdg_activation_v1_create(server->wl_display);
if (!server->xdg_activation) {
wlr_log(WLR_ERROR, "unable to create xdg_activation interface");
exit(EXIT_FAILURE);
}
server->xdg_activation_request.notify = handle_xdg_activation_request;
wl_signal_add(&server->xdg_activation->events.request_activate,
&server->xdg_activation_request);
server->xdg_activation_new_token.notify = handle_xdg_activation_new_token;
wl_signal_add(&server->xdg_activation->events.new_token,
&server->xdg_activation_new_token);
server->xdg_toplevel_icon_manager = wlr_xdg_toplevel_icon_manager_v1_create(
server->wl_display, 1);
server->xdg_toplevel_icon_set_icon.notify = handle_xdg_toplevel_icon_set_icon;
wl_signal_add(&server->xdg_toplevel_icon_manager->events.set_icon,
&server->xdg_toplevel_icon_set_icon);
}
void
xdg_shell_finish(struct server *server)
{
wl_list_remove(&server->new_xdg_toplevel.link);
wl_list_remove(&server->xdg_activation_request.link);
wl_list_remove(&server->xdg_activation_new_token.link);
wl_list_remove(&server->xdg_toplevel_icon_set_icon.link);
}