2021-09-24 21:45:48 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2023-12-10 21:00:05 -05:00
|
|
|
|
2020-09-28 20:53:59 +01:00
|
|
|
#include <assert.h>
|
2024-01-17 13:12:35 -05:00
|
|
|
#include <wlr/types/wlr_fractional_scale_v1.h>
|
|
|
|
|
|
2023-10-16 02:01:35 -04:00
|
|
|
#include "common/macros.h"
|
2022-09-16 18:41:02 -04:00
|
|
|
#include "common/mem.h"
|
2023-03-22 19:56:59 +00:00
|
|
|
#include "decorations.h"
|
2019-12-26 21:37:31 +00:00
|
|
|
#include "labwc.h"
|
2022-03-02 21:07:04 +00:00
|
|
|
#include "node.h"
|
2024-04-02 15:58:50 -04:00
|
|
|
#include "snap-constraints.h"
|
2022-11-21 10:10:39 -05:00
|
|
|
#include "view.h"
|
2023-02-05 19:29:24 +00:00
|
|
|
#include "view-impl-common.h"
|
2023-04-28 21:41:41 +01:00
|
|
|
#include "window-rules.h"
|
2022-06-15 02:02:50 +02:00
|
|
|
#include "workspaces.h"
|
2019-12-26 21:37:31 +00:00
|
|
|
|
2024-06-27 19:17:38 -04:00
|
|
|
#define LAB_XDG_SHELL_VERSION (3)
|
2023-02-28 11:30:42 -05:00
|
|
|
#define CONFIGURE_TIMEOUT_MS 100
|
|
|
|
|
|
2023-02-24 21:15:11 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 13:41:12 -05:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-19 20:15:49 +02:00
|
|
|
static bool
|
|
|
|
|
xdg_toplevel_view_contains_window_type(struct view *view, int32_t 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) {
|
|
|
|
|
case NET_WM_WINDOW_TYPE_NORMAL:
|
|
|
|
|
return !is_dialog;
|
|
|
|
|
case NET_WM_WINDOW_TYPE_DIALOG:
|
|
|
|
|
return is_dialog;
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-09 22:51:20 +00:00
|
|
|
static void
|
2023-10-16 02:01:35 -04:00
|
|
|
handle_new_popup(struct wl_listener *listener, void *data)
|
2021-01-09 22:51:20 +00:00
|
|
|
{
|
2022-11-22 20:13:06 +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);
|
|
|
|
|
}
|
|
|
|
|
|
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) {
|
|
|
|
|
/* Keep view underneath cursor */
|
|
|
|
|
interactive_anchor_to_cursor(view, &view->pending);
|
|
|
|
|
/* Update grab offsets */
|
|
|
|
|
server->grab_x = server->seat.cursor->x;
|
|
|
|
|
server->grab_y = server->seat.cursor->y;
|
|
|
|
|
server->grab_box = view->pending;
|
|
|
|
|
} else {
|
|
|
|
|
/* TODO: smart placement? */
|
|
|
|
|
view_compute_centered_position(view, NULL,
|
|
|
|
|
view->pending.width, view->pending.height,
|
|
|
|
|
&view->pending.x, &view->pending.y);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
|
|
|
|
handle_commit(struct wl_listener *listener, void *data)
|
2020-08-31 08:12:44 +01:00
|
|
|
{
|
|
|
|
|
struct view *view = wl_container_of(listener, view, commit);
|
2022-11-25 13:41:12 -05:00
|
|
|
struct wlr_xdg_surface *xdg_surface = xdg_surface_from_view(view);
|
2020-09-28 20:53:59 +01:00
|
|
|
assert(view->surface);
|
2022-11-25 13:41:12 -05:00
|
|
|
|
2024-04-23 23:37:08 +02:00
|
|
|
if (xdg_surface->initial_commit) {
|
|
|
|
|
wlr_log(WLR_DEBUG, "scheduling configure");
|
|
|
|
|
wlr_xdg_surface_schedule_configure(xdg_surface);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-22 21:08:17 +00:00
|
|
|
struct wlr_box size;
|
2022-11-25 13:41:12 -05:00
|
|
|
wlr_xdg_surface_get_geometry(xdg_surface, &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
|
|
|
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)) {
|
|
|
|
|
view->pending.width = size.width;
|
|
|
|
|
view->pending.height = size.height;
|
|
|
|
|
do_late_positioning(view);
|
|
|
|
|
update_required = true;
|
|
|
|
|
}
|
2020-12-22 21:08:17 +00:00
|
|
|
|
2023-10-21 19:49:38 -04:00
|
|
|
/*
|
|
|
|
|
* 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) {
|
|
|
|
|
struct wlr_box extent;
|
|
|
|
|
wlr_surface_get_extends(xdg_surface->surface, &extent);
|
|
|
|
|
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 */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-08 23:19:14 -05:00
|
|
|
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;
|
|
|
|
|
}
|
2020-12-22 21:08:17 +00:00
|
|
|
|
2023-02-08 23:19:14 -05:00
|
|
|
uint32_t serial = view->pending_configure_serial;
|
2023-02-28 11:30:42 -05:00
|
|
|
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;
|
2023-02-25 12:05:22 -05:00
|
|
|
update_required = true;
|
2020-12-22 21:08:17 +00:00
|
|
|
}
|
2023-02-28 11:30:42 -05:00
|
|
|
|
2022-09-08 00:55:57 +02:00
|
|
|
if (update_required) {
|
2023-02-25 12:05:22 -05:00
|
|
|
view_impl_apply_geometry(view, size.width, size.height);
|
2023-12-27 11:56:48 -05:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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) {
|
2024-04-02 15:58:50 -04:00
|
|
|
snap_constraints_update(view);
|
2023-12-27 11:56:48 -05:00
|
|
|
view->pending = view->current;
|
2024-01-29 15:07:27 -05:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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
|
2024-01-29 15:07:27 -05:00
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
struct wlr_xdg_toplevel *toplevel =
|
|
|
|
|
xdg_toplevel_from_view(view);
|
|
|
|
|
toplevel->scheduled.width = view->current.width;
|
|
|
|
|
toplevel->scheduled.height = view->current.height;
|
2023-12-27 11:56:48 -05:00
|
|
|
}
|
2022-09-08 00:55:57 +02:00
|
|
|
}
|
2020-08-31 08:12:44 +01:00
|
|
|
}
|
|
|
|
|
|
2023-02-28 11:30:42 -05:00
|
|
|
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");
|
2023-03-05 02:03:41 -05:00
|
|
|
wlr_log(WLR_INFO, "client (%s) did not respond to configure request "
|
2023-02-28 11:30:42 -05:00
|
|
|
"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;
|
|
|
|
|
|
2023-12-27 11:56:48 -05:00
|
|
|
view_impl_apply_geometry(view,
|
|
|
|
|
view->current.width, view->current.height);
|
|
|
|
|
|
|
|
|
|
/* Re-sync pending view with current state */
|
2024-04-02 15:58:50 -04:00
|
|
|
snap_constraints_update(view);
|
2023-12-27 11:56:48 -05:00
|
|
|
view->pending = view->current;
|
2023-02-28 11:30:42 -05:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
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);
|
2022-04-23 03:44:41 +02:00
|
|
|
assert(view->type == LAB_XDG_SHELL_VIEW);
|
2023-02-24 21:15:11 +00:00
|
|
|
struct xdg_toplevel_view *xdg_toplevel_view =
|
|
|
|
|
xdg_toplevel_view_from_view(view);
|
|
|
|
|
|
|
|
|
|
xdg_toplevel_view->xdg_surface->data = NULL;
|
|
|
|
|
xdg_toplevel_view->xdg_surface = NULL;
|
|
|
|
|
|
2023-02-24 21:45:03 +00:00
|
|
|
/* Remove xdg-shell view specific listeners */
|
2023-02-24 21:15:11 +00:00
|
|
|
wl_list_remove(&xdg_toplevel_view->set_app_id.link);
|
|
|
|
|
wl_list_remove(&xdg_toplevel_view->new_popup.link);
|
2024-04-23 23:37:08 +02:00
|
|
|
wl_list_remove(&view->commit.link);
|
2022-04-23 03:44:41 +02:00
|
|
|
|
2023-02-28 11:30:42 -05:00
|
|
|
if (view->pending_configure_timeout) {
|
|
|
|
|
wl_event_source_remove(view->pending_configure_timeout);
|
|
|
|
|
view->pending_configure_timeout = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-23 03:44:41 +02:00
|
|
|
view_destroy(view);
|
2019-12-26 21:37:31 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01: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
|
2024-03-08 00:45:04 +09:00
|
|
|
* 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);
|
2024-03-08 00:53:39 +09:00
|
|
|
if (view == view->server->seat.pressed.view) {
|
|
|
|
|
interactive_begin(view, LAB_INPUT_STATE_MOVE, 0);
|
|
|
|
|
}
|
2019-12-26 21:37:31 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01: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
|
2024-03-08 00:45:04 +09:00
|
|
|
* 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);
|
2024-03-08 00:53:39 +09:00
|
|
|
if (view == view->server->seat.pressed.view) {
|
|
|
|
|
interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges);
|
|
|
|
|
}
|
2019-12-26 21:37:31 +00:00
|
|
|
}
|
|
|
|
|
|
2021-12-31 02:44:25 +00:00
|
|
|
static void
|
|
|
|
|
handle_request_minimize(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct view *view = wl_container_of(listener, view, request_minimize);
|
2022-11-25 13:41:12 -05:00
|
|
|
view_minimize(view, xdg_toplevel_from_view(view)->requested.minimized);
|
2021-12-31 02:44:25 +00:00
|
|
|
}
|
|
|
|
|
|
2021-02-27 17:10:53 -05:00
|
|
|
static void
|
|
|
|
|
handle_request_maximize(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct view *view = wl_container_of(listener, view, request_maximize);
|
2023-02-03 14:45:04 -05:00
|
|
|
if (!view->mapped && !view->output) {
|
2023-02-28 11:46:48 -05:00
|
|
|
view_set_output(view, output_nearest_to_cursor(view->server));
|
2023-02-03 14:45:04 -05:00
|
|
|
}
|
view: implement separate horizontal/vertical maximize
This is a useful (if lesser-known) feature of at least a few popular X11
window managers, for example Openbox and XFWM4. Typically right-click on
the maximize button toggles horizontal maximize, while middle-click
toggles vertical maximize.
Support in labwc uses the same configuration syntax as Openbox, where the
Maximize/ToggleMaximize actions have an optional "direction" argument:
horizontal, vertical, or both (default). The default mouse bindings match
the XFWM4 defaults (not sure what Openbox has by default).
Most of the external protocols still assume "maximized" is a Boolean,
which is no longer true internally. For the sake of the outside world,
a view is only "maximized" if maximized in both directions.
Internally, I've taken the following approach:
- SSD code decorates the view as "maximized" (i.e. hiding borders) only
if maximized in both directions.
- Layout code (interactive move/resize, tiling, etc.) generally treats
the view as "maximized" (with the restrictions that entails) if
maximized in either direction. For example, moving a vertically-
maximized view first restores the natural geometry (this differs from
Openbox, which instead allows the view to move only horizontally.)
v2: use enum view_axis for view->maximized
v3:
- update docs
- allow resizing if partly maximized
- add TODOs & corrections noted by Consolatis
2023-10-26 00:38:29 -04:00
|
|
|
bool maximized = xdg_toplevel_from_view(view)->requested.maximized;
|
|
|
|
|
view_maximize(view, maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE,
|
2022-11-19 12:58:52 -05:00
|
|
|
/*store_natural_geometry*/ true);
|
2021-02-27 17:10:53 -05:00
|
|
|
}
|
|
|
|
|
|
2023-02-20 13:36:15 -05:00
|
|
|
static void
|
|
|
|
|
set_fullscreen_from_request(struct view *view,
|
|
|
|
|
struct wlr_xdg_toplevel_requested *requested)
|
|
|
|
|
{
|
|
|
|
|
if (!view->fullscreen && requested->fullscreen
|
|
|
|
|
&& requested->fullscreen_output) {
|
2023-02-28 11:46:48 -05:00
|
|
|
view_set_output(view, output_from_wlr_output(view->server,
|
|
|
|
|
requested->fullscreen_output));
|
2023-02-20 13:36:15 -05:00
|
|
|
}
|
|
|
|
|
view_set_fullscreen(view, requested->fullscreen);
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
2023-02-03 14:45:04 -05:00
|
|
|
if (!view->mapped && !view->output) {
|
2023-02-28 11:46:48 -05:00
|
|
|
view_set_output(view, output_nearest_to_cursor(view->server));
|
2023-02-03 14:45:04 -05:00
|
|
|
}
|
2023-02-20 13:36:15 -05:00
|
|
|
set_fullscreen_from_request(view,
|
|
|
|
|
&xdg_toplevel_from_view(view)->requested);
|
2021-08-23 22:05:30 +01:00
|
|
|
}
|
|
|
|
|
|
2021-08-05 12:18:10 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-16 21:50:56 +01:00
|
|
|
static void
|
|
|
|
|
handle_set_app_id(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
2022-11-22 20:13:06 +00:00
|
|
|
struct xdg_toplevel_view *xdg_toplevel_view =
|
|
|
|
|
wl_container_of(listener, xdg_toplevel_view, set_app_id);
|
|
|
|
|
struct view *view = &xdg_toplevel_view->base;
|
2021-10-16 21:50:56 +01:00
|
|
|
view_update_app_id(view);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
|
|
|
|
xdg_toplevel_view_configure(struct view *view, struct wlr_box geo)
|
2020-09-02 20:20:52 +01:00
|
|
|
{
|
2023-02-09 01:07:07 -05:00
|
|
|
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
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Leave a size of 0x0 unchanged; this has special meaning in
|
|
|
|
|
* an xdg-toplevel configure event and requests the application
|
|
|
|
|
* to choose its own preferred size.
|
|
|
|
|
*/
|
|
|
|
|
if (!wlr_box_empty(&geo)) {
|
|
|
|
|
view_adjust_size(view, &geo.width, &geo.height);
|
|
|
|
|
}
|
2020-12-22 21:08:17 +00:00
|
|
|
|
2023-02-09 01:07:07 -05:00
|
|
|
/*
|
|
|
|
|
* 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) {
|
|
|
|
|
serial = wlr_xdg_toplevel_set_size(xdg_toplevel_from_view(view),
|
|
|
|
|
geo.width, geo.height);
|
2023-02-12 07:38:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view->pending = geo;
|
2020-12-22 21:08:17 +00:00
|
|
|
if (serial > 0) {
|
2023-02-28 11:30:42 -05:00
|
|
|
set_pending_configure_serial(view, serial);
|
2023-02-08 23:19:14 -05:00
|
|
|
} else if (view->pending_configure_serial == 0) {
|
2023-12-28 10:16:51 +00:00
|
|
|
view->current.x = geo.x;
|
|
|
|
|
view->current.y = geo.y;
|
|
|
|
|
view_moved(view);
|
2020-12-22 21:08:17 +00:00
|
|
|
}
|
2020-09-02 20:20:52 +01:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
|
|
|
|
xdg_toplevel_view_close(struct view *view)
|
2020-09-02 21:00:28 +01:00
|
|
|
{
|
2022-11-25 13:41:12 -05:00
|
|
|
wlr_xdg_toplevel_send_close(xdg_toplevel_from_view(view));
|
2020-09-02 21:00:28 +01:00
|
|
|
}
|
|
|
|
|
|
2021-03-12 21:23:46 +00:00
|
|
|
static void
|
|
|
|
|
xdg_toplevel_view_maximize(struct view *view, bool maximized)
|
2020-09-15 20:41:01 +01:00
|
|
|
{
|
2022-11-25 13:41:12 -05:00
|
|
|
wlr_xdg_toplevel_set_maximized(xdg_toplevel_from_view(view), maximized);
|
2020-09-15 20:41:01 +01:00
|
|
|
}
|
|
|
|
|
|
2023-06-25 11:37:56 +01:00
|
|
|
static void
|
|
|
|
|
xdg_toplevel_view_minimize(struct view *view, bool minimized)
|
|
|
|
|
{
|
|
|
|
|
/* noop */
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-17 20:16:21 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-19 17:19:37 +01:00
|
|
|
/* Return the most senior parent (=root) view */
|
|
|
|
|
static struct view *
|
2023-08-02 20:57:39 +01:00
|
|
|
xdg_toplevel_view_get_root(struct view *view)
|
2023-07-19 17:19:37 +01:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-02 20:57:39 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-16 19:24:26 +01:00
|
|
|
static void
|
|
|
|
|
xdg_toplevel_view_set_activated(struct view *view, bool activated)
|
|
|
|
|
{
|
2022-11-25 13:41:12 -05:00
|
|
|
wlr_xdg_toplevel_set_activated(xdg_toplevel_from_view(view), activated);
|
2021-10-16 19:24:26 +01:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 22:05:30 +01:00
|
|
|
static void
|
|
|
|
|
xdg_toplevel_view_set_fullscreen(struct view *view, bool fullscreen)
|
|
|
|
|
{
|
2022-11-25 13:41:12 -05:00
|
|
|
wlr_xdg_toplevel_set_fullscreen(xdg_toplevel_from_view(view),
|
|
|
|
|
fullscreen);
|
2020-09-25 20:22:18 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-22 15:38:14 -05:00
|
|
|
static void
|
2024-01-22 22:11:53 -05:00
|
|
|
xdg_toplevel_view_notify_tiled(struct view *view)
|
2024-01-22 15:38:14 -05:00
|
|
|
{
|
2024-01-22 22:11:53 -05:00
|
|
|
/* Take no action if xdg-shell tiling is disabled */
|
|
|
|
|
if (rc.snap_tiling_events_mode == LAB_TILING_EVENTS_NEVER) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum wlr_edges edge = WLR_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;
|
2024-01-22 15:38:14 -05:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Edge-snapped view are considered tiled on the snapped edge and those
|
|
|
|
|
* perpendicular to it.
|
|
|
|
|
*/
|
2024-01-22 22:11:53 -05:00
|
|
|
if (want_edge) {
|
|
|
|
|
switch (view->tiled) {
|
|
|
|
|
case VIEW_EDGE_LEFT:
|
|
|
|
|
edge = WLR_EDGE_LEFT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM;
|
|
|
|
|
break;
|
|
|
|
|
case VIEW_EDGE_RIGHT:
|
|
|
|
|
edge = WLR_EDGE_RIGHT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM;
|
|
|
|
|
break;
|
|
|
|
|
case VIEW_EDGE_UP:
|
|
|
|
|
edge = WLR_EDGE_TOP | WLR_EDGE_LEFT | WLR_EDGE_RIGHT;
|
|
|
|
|
break;
|
|
|
|
|
case VIEW_EDGE_DOWN:
|
|
|
|
|
edge = WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
edge = WLR_EDGE_NONE;
|
|
|
|
|
}
|
2024-01-22 15:38:14 -05:00
|
|
|
}
|
|
|
|
|
|
2024-01-22 22:11:53 -05:00
|
|
|
if (want_region && view->tiled_region) {
|
2024-01-22 15:38:14 -05:00
|
|
|
/* Region-snapped views are considered tiled on all edges */
|
|
|
|
|
edge = WLR_EDGE_LEFT | WLR_EDGE_RIGHT |
|
|
|
|
|
WLR_EDGE_TOP | WLR_EDGE_BOTTOM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wlr_xdg_toplevel_set_tiled(xdg_toplevel_from_view(view), edge);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-20 14:30:45 +00:00
|
|
|
static struct view *
|
2022-11-25 13:41:12 -05:00
|
|
|
lookup_view_by_xdg_toplevel(struct server *server,
|
|
|
|
|
struct wlr_xdg_toplevel *xdg_toplevel)
|
2021-03-20 14:30:45 +00:00
|
|
|
{
|
2022-11-25 13:41:12 -05:00
|
|
|
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;
|
2021-03-20 14:30:45 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2024-01-06 21:39:47 +00:00
|
|
|
set_initial_position(struct view *view)
|
2021-03-20 14:30:45 +00:00
|
|
|
{
|
2022-11-25 13:41:12 -05:00
|
|
|
struct wlr_xdg_toplevel *parent_xdg_toplevel =
|
|
|
|
|
xdg_toplevel_from_view(view)->parent;
|
|
|
|
|
|
2024-01-06 21:36:12 +00:00
|
|
|
view_constrain_size_to_that_of_usable_area(view);
|
|
|
|
|
|
2023-12-10 21:00:05 -05:00
|
|
|
if (parent_xdg_toplevel) {
|
|
|
|
|
/* Child views are center-aligned relative to their parents */
|
2022-11-25 13:41:12 -05:00
|
|
|
struct view *parent = lookup_view_by_xdg_toplevel(
|
|
|
|
|
view->server, parent_xdg_toplevel);
|
2021-03-20 14:30:45 +00:00
|
|
|
assert(parent);
|
2023-02-28 11:46:48 -05:00
|
|
|
view_set_output(view, parent->output);
|
2023-02-20 16:23:53 -05:00
|
|
|
view_center(view, &parent->pending);
|
2023-12-10 21:00:05 -05:00
|
|
|
return;
|
2021-03-20 14:30:45 +00:00
|
|
|
}
|
2023-12-10 21:00:05 -05:00
|
|
|
|
|
|
|
|
/* All other views are placed according to a configured strategy */
|
2024-05-07 09:46:05 -04:00
|
|
|
view_place_by_policy(view, /* allow_cursor */ true, rc.placement_policy);
|
2021-03-20 14:30:45 +00:00
|
|
|
}
|
|
|
|
|
|
2021-08-05 12:18:10 +01:00
|
|
|
static const char *
|
|
|
|
|
xdg_toplevel_view_get_string_prop(struct view *view, const char *prop)
|
|
|
|
|
{
|
2023-09-21 18:13:44 +02:00
|
|
|
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 "";
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-05 12:18:10 +01:00
|
|
|
if (!strcmp(prop, "title")) {
|
2022-11-25 13:41:12 -05:00
|
|
|
return xdg_toplevel->title;
|
2021-08-05 12:18:10 +01:00
|
|
|
}
|
2021-08-16 07:16:56 +01:00
|
|
|
if (!strcmp(prop, "app_id")) {
|
2022-11-25 13:41:12 -05:00
|
|
|
return xdg_toplevel->app_id;
|
2021-08-16 07:16:56 +01:00
|
|
|
}
|
|
|
|
|
return "";
|
2021-08-05 12:18:10 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-03 21:32:58 +01:00
|
|
|
static void
|
|
|
|
|
init_foreign_toplevel(struct view *view)
|
|
|
|
|
{
|
|
|
|
|
foreign_toplevel_handle_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->toplevel.handle) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
wlr_foreign_toplevel_handle_v1_set_parent(view->toplevel.handle, parent->toplevel.handle);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
|
|
|
|
xdg_toplevel_view_map(struct view *view)
|
2020-09-03 20:50:35 +01:00
|
|
|
{
|
2022-02-23 01:32:07 +01:00
|
|
|
if (view->mapped) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-01-17 13:12:35 -05:00
|
|
|
|
2020-09-03 20:50:35 +01:00
|
|
|
view->mapped = true;
|
2024-01-17 13:12:35 -05:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* An output should have been chosen when the surface was first
|
|
|
|
|
* created, but take one more opportunity to assign an output if not.
|
|
|
|
|
*/
|
2023-02-03 14:45:04 -05:00
|
|
|
if (!view->output) {
|
2023-02-28 11:46:48 -05:00
|
|
|
view_set_output(view, output_nearest_to_cursor(view->server));
|
2023-02-03 14:45:04 -05:00
|
|
|
}
|
2022-11-25 13:41:12 -05:00
|
|
|
struct wlr_xdg_surface *xdg_surface = xdg_surface_from_view(view);
|
2022-02-23 01:32:07 +01:00
|
|
|
wlr_scene_node_set_enabled(&view->scene_tree->node, true);
|
2020-09-03 20:50:35 +01:00
|
|
|
if (!view->been_mapped) {
|
2023-08-03 21:32:58 +01:00
|
|
|
init_foreign_toplevel(view);
|
|
|
|
|
|
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
|
|
|
/*
|
|
|
|
|
* FIXME: is this needed or is the earlier logic in
|
|
|
|
|
* xdg_surface_new() enough?
|
|
|
|
|
*/
|
2024-04-19 13:35:16 +02:00
|
|
|
if (view_wants_decorations(view)) {
|
2024-04-18 09:46:36 +02:00
|
|
|
view_set_ssd_mode(view, LAB_SSD_MODE_FULL);
|
|
|
|
|
} else {
|
|
|
|
|
view_set_ssd_mode(view, LAB_SSD_MODE_NONE);
|
|
|
|
|
}
|
2021-07-21 22:04:54 +01:00
|
|
|
|
2023-02-09 01:21:52 -05:00
|
|
|
/*
|
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"
|
2023-02-09 01:21:52 -05:00
|
|
|
* dimensions remain zero until handle_commit().
|
|
|
|
|
*/
|
2023-02-09 16:06:07 -05:00
|
|
|
if (wlr_box_empty(&view->pending)) {
|
2023-02-20 17:30:17 -05:00
|
|
|
struct wlr_box size;
|
|
|
|
|
wlr_xdg_surface_get_geometry(xdg_surface, &size);
|
|
|
|
|
view->pending.width = size.width;
|
|
|
|
|
view->pending.height = size.height;
|
2023-02-09 16:06:07 -05:00
|
|
|
}
|
2023-02-09 01:21:52 -05:00
|
|
|
|
2023-02-18 00:55:42 -05:00
|
|
|
/*
|
|
|
|
|
* Set initial "pending" position for floating views.
|
|
|
|
|
*/
|
|
|
|
|
if (view_is_floating(view)) {
|
2024-01-06 21:39:47 +00:00
|
|
|
set_initial_position(view);
|
2023-02-18 00:55:42 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Set initial "current" position directly before
|
|
|
|
|
* calling view_moved() to reduce flicker
|
|
|
|
|
*/
|
|
|
|
|
view->current.x = view->pending.x;
|
|
|
|
|
view->current.y = view->pending.y;
|
|
|
|
|
|
2022-09-04 22:47:54 -04:00
|
|
|
view_moved(view);
|
2020-09-03 20:50:35 +01:00
|
|
|
}
|
2020-09-15 20:41:01 +01:00
|
|
|
|
2021-10-16 21:26:57 +01:00
|
|
|
view_impl_map(view);
|
2023-04-28 21:41:41 +01:00
|
|
|
view->been_mapped = true;
|
2020-09-03 20:50:35 +01:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
2023-07-01 12:37:47 -04:00
|
|
|
xdg_toplevel_view_unmap(struct view *view, bool client_request)
|
2020-09-03 20:50:35 +01:00
|
|
|
{
|
2021-12-11 23:24:44 +00:00
|
|
|
if (view->mapped) {
|
|
|
|
|
view->mapped = false;
|
2022-02-23 01:32:07 +01:00
|
|
|
wlr_scene_node_set_enabled(&view->scene_tree->node, false);
|
2023-10-14 22:29:24 -04:00
|
|
|
view_impl_unmap(view);
|
2021-12-11 23:24:44 +00:00
|
|
|
}
|
2020-09-03 20:50:35 +01:00
|
|
|
}
|
|
|
|
|
|
2024-04-22 17:43:46 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-02 20:20:52 +01:00
|
|
|
static const struct view_impl xdg_toplevel_view_impl = {
|
|
|
|
|
.configure = xdg_toplevel_view_configure,
|
2020-09-03 20:50:35 +01:00
|
|
|
.close = xdg_toplevel_view_close,
|
2021-08-05 12:18:10 +01:00
|
|
|
.get_string_prop = xdg_toplevel_view_get_string_prop,
|
2020-09-03 20:50:35 +01:00
|
|
|
.map = xdg_toplevel_view_map,
|
2021-10-16 19:24:26 +01:00
|
|
|
.set_activated = xdg_toplevel_view_set_activated,
|
2021-08-23 22:05:30 +01:00
|
|
|
.set_fullscreen = xdg_toplevel_view_set_fullscreen,
|
2024-01-22 22:11:53 -05:00
|
|
|
.notify_tiled = xdg_toplevel_view_notify_tiled,
|
2020-09-03 20:50:35 +01:00
|
|
|
.unmap = xdg_toplevel_view_unmap,
|
2021-03-12 21:23:46 +00:00
|
|
|
.maximize = xdg_toplevel_view_maximize,
|
2023-06-25 11:37:56 +01:00
|
|
|
.minimize = xdg_toplevel_view_minimize,
|
2023-10-20 21:34:29 -04:00
|
|
|
.move_to_front = view_impl_move_to_front,
|
|
|
|
|
.move_to_back = view_impl_move_to_back,
|
2023-08-02 20:57:39 +01:00
|
|
|
.get_root = xdg_toplevel_view_get_root,
|
|
|
|
|
.append_children = xdg_toplevel_view_append_children,
|
2024-04-19 20:15:49 +02:00
|
|
|
.contains_window_type = xdg_toplevel_view_contains_window_type,
|
2024-04-22 17:43:46 +02:00
|
|
|
.get_pid = xdg_view_get_pid,
|
2020-09-02 20:20:52 +01:00
|
|
|
};
|
|
|
|
|
|
2024-07-10 20:22:52 +02:00
|
|
|
struct token_data {
|
|
|
|
|
bool had_valid_surface;
|
|
|
|
|
bool had_valid_seat;
|
|
|
|
|
struct wl_listener destroy;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xdg_activation_handle_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
|
|
|
|
|
xdg_activation_handle_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 = xdg_activation_handle_token_destroy;
|
|
|
|
|
wl_signal_add(&token->events.destroy, &token_data->destroy);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-20 22:04:08 +01:00
|
|
|
static void
|
2023-02-04 10:07:46 +01:00
|
|
|
xdg_activation_handle_request(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
const struct wlr_xdg_activation_v1_request_activate_event *event = data;
|
2024-07-10 20:22:52 +02:00
|
|
|
struct token_data *token_data = event->token->data;
|
|
|
|
|
assert(token_data);
|
2023-02-04 10:07:46 +01:00
|
|
|
|
2023-02-03 14:53:26 -05:00
|
|
|
struct wlr_xdg_surface *xdg_surface =
|
|
|
|
|
wlr_xdg_surface_try_from_wlr_surface(event->surface);
|
|
|
|
|
if (!xdg_surface) {
|
2023-02-04 10:07:46 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2023-02-03 14:53:26 -05:00
|
|
|
struct view *view = xdg_surface->data;
|
2023-02-04 10:07:46 +01:00
|
|
|
|
|
|
|
|
if (!view) {
|
|
|
|
|
wlr_log(WLR_INFO, "Not activating surface - no view attached to surface");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-07-10 20:22:52 +02:00
|
|
|
|
|
|
|
|
if (!token_data->had_valid_seat) {
|
2023-02-04 10:07:46 +01:00
|
|
|
wlr_log(WLR_INFO, "Denying focus request, seat wasn't supplied");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-07-10 20:22:52 +02:00
|
|
|
|
|
|
|
|
if (!token_data->had_valid_surface) {
|
|
|
|
|
wlr_log(WLR_INFO, "Denying focus request, source surface not set");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-02-04 10:07:46 +01:00
|
|
|
|
2023-09-03 14:01:40 +02:00
|
|
|
if (window_rules_get_property(view, "ignoreFocusRequest") == LAB_PROP_TRUE) {
|
|
|
|
|
wlr_log(WLR_INFO, "Ignoring focus request due to window rule configuration");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 02:53:53 +01:00
|
|
|
if (view->server->osd_state.cycle_view) {
|
|
|
|
|
wlr_log(WLR_INFO, "Preventing focus request while in window switcher");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-04 10:07:46 +01:00
|
|
|
wlr_log(WLR_DEBUG, "Activating surface");
|
2023-09-27 18:37:28 -04:00
|
|
|
desktop_focus_view(view, /*raise*/ true);
|
2023-02-04 10:07:46 +01:00
|
|
|
}
|
|
|
|
|
|
2022-02-11 23:12:45 +00:00
|
|
|
/*
|
|
|
|
|
* We use the following struct user_data pointers:
|
|
|
|
|
* - wlr_xdg_surface->data = view
|
|
|
|
|
* for the wlr_xdg_toplevel_decoration_v1 implementation
|
2022-06-05 15:17:35 +02:00
|
|
|
* - wlr_surface->data = scene_tree
|
2022-02-11 23:12:45 +00:00
|
|
|
* to help the popups find their parent nodes
|
|
|
|
|
*/
|
2023-10-20 22:04:08 +01:00
|
|
|
static void
|
2024-03-19 02:38:09 +01:00
|
|
|
xdg_toplevel_new(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 =
|
2024-03-19 02:38:09 +01:00
|
|
|
wl_container_of(listener, server, new_xdg_toplevel);
|
|
|
|
|
struct wlr_xdg_toplevel *xdg_toplevel = data;
|
|
|
|
|
struct wlr_xdg_surface *xdg_surface = xdg_toplevel->base;
|
2022-02-11 23:12:45 +00:00
|
|
|
|
2024-03-19 02:38:09 +01:00
|
|
|
assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
|
2022-02-11 23:12:45 +00:00
|
|
|
|
2020-10-08 19:58:47 +01:00
|
|
|
wlr_xdg_surface_ping(xdg_surface);
|
2019-12-26 21:37:31 +00:00
|
|
|
|
2022-11-22 20:13:06 +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;
|
2020-09-02 20:20:52 +01:00
|
|
|
view->impl = &xdg_toplevel_view_impl;
|
2022-11-25 13:41:12 -05:00
|
|
|
xdg_toplevel_view->xdg_surface = xdg_surface;
|
2019-12-26 21:37:31 +00:00
|
|
|
|
2024-01-17 13:12:35 -05: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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-15 02:02:50 +02:00
|
|
|
view->workspace = server->workspace_current;
|
|
|
|
|
view->scene_tree = wlr_scene_tree_create(view->workspace->tree);
|
2022-02-23 01:32:07 +01:00
|
|
|
wlr_scene_node_set_enabled(&view->scene_tree->node, false);
|
2022-02-21 03:18:38 +01:00
|
|
|
|
2022-06-05 15:17:35 +02:00
|
|
|
struct wlr_scene_tree *tree = wlr_scene_xdg_surface_create(
|
2022-11-25 13:41:12 -05:00
|
|
|
view->scene_tree, xdg_surface);
|
2022-06-05 15:17:35 +02:00
|
|
|
if (!tree) {
|
2022-02-23 01:32:07 +01:00
|
|
|
/* TODO: might need further clean up */
|
2022-12-08 05:27:25 +00:00
|
|
|
wl_resource_post_no_memory(xdg_surface->resource);
|
|
|
|
|
free(xdg_toplevel_view);
|
2022-02-11 23:12:45 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2022-06-05 15:17:35 +02:00
|
|
|
view->scene_node = &tree->node;
|
2022-02-25 22:31:24 +00:00
|
|
|
node_descriptor_create(&view->scene_tree->node,
|
|
|
|
|
LAB_NODE_DESC_VIEW, view);
|
2022-02-11 23:12:45 +00:00
|
|
|
|
2023-03-24 20:21:21 +01:00
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2021-10-20 16:02:45 +00:00
|
|
|
xdg_surface->data = view;
|
|
|
|
|
|
2023-03-22 19:56:59 +00:00
|
|
|
/*
|
|
|
|
|
* 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 */
|
2024-04-23 23:37:08 +02:00
|
|
|
view->surface = xdg_surface->surface;
|
|
|
|
|
view->surface->data = tree;
|
2022-02-11 23:12:45 +00:00
|
|
|
|
2023-06-15 02:35:43 -07:00
|
|
|
view_connect_map(view, xdg_surface->surface);
|
2019-12-26 21:37:31 +00:00
|
|
|
|
|
|
|
|
struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel;
|
2024-05-27 15:55:46 +02:00
|
|
|
CONNECT_SIGNAL(toplevel, view, destroy);
|
2023-10-16 02:01:35 -04:00
|
|
|
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);
|
2024-04-23 23:37:08 +02:00
|
|
|
CONNECT_SIGNAL(view->surface, view, commit);
|
2021-02-27 17:10:53 -05:00
|
|
|
|
2022-11-22 20:13:06 +00:00
|
|
|
/* Events specific to XDG toplevel views */
|
2023-10-16 02:01:35 -04:00
|
|
|
CONNECT_SIGNAL(toplevel, xdg_toplevel_view, set_app_id);
|
|
|
|
|
CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup);
|
2021-10-16 21:50:56 +01:00
|
|
|
|
2019-12-26 21:37:31 +00:00
|
|
|
wl_list_insert(&server->views, &view->link);
|
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
|
|
|
|
|
|
|
|
/* FIXME: is view_wants_decorations() reliable this early? */
|
|
|
|
|
if (view_wants_decorations(view)) {
|
|
|
|
|
view_set_ssd_mode(view, LAB_SSD_MODE_FULL);
|
|
|
|
|
} else {
|
|
|
|
|
view_set_ssd_mode(view, LAB_SSD_MODE_NONE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Handle initial fullscreen/maximize requests. This needs to be
|
|
|
|
|
* done early (before map) in order to send the correct size to
|
|
|
|
|
* the initial configure event and avoid flicker.
|
|
|
|
|
*
|
|
|
|
|
* Note that at this point, wlroots has already scheduled (but
|
|
|
|
|
* not yet sent) the initial configure event with a size of 0x0.
|
|
|
|
|
* In normal (non-fullscreen/maximized) cases, the zero size
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
2019-12-26 21:37:31 +00:00
|
|
|
}
|
2023-10-20 22:04:08 +01:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
2024-03-19 02:38:09 +01:00
|
|
|
|
|
|
|
|
server->new_xdg_toplevel.notify = xdg_toplevel_new;
|
|
|
|
|
wl_signal_add(&server->xdg_shell->events.new_toplevel, &server->new_xdg_toplevel);
|
2023-10-20 22:04:08 +01:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
2024-07-10 20:22:52 +02:00
|
|
|
|
2023-10-20 22:04:08 +01:00
|
|
|
server->xdg_activation_request.notify = xdg_activation_handle_request;
|
|
|
|
|
wl_signal_add(&server->xdg_activation->events.request_activate,
|
|
|
|
|
&server->xdg_activation_request);
|
2024-07-10 20:22:52 +02:00
|
|
|
|
|
|
|
|
server->xdg_activation_new_token.notify = xdg_activation_handle_new_token;
|
|
|
|
|
wl_signal_add(&server->xdg_activation->events.new_token,
|
|
|
|
|
&server->xdg_activation_new_token);
|
2023-10-20 22:04:08 +01:00
|
|
|
}
|
|
|
|
|
|