wayland: Add xdg_toplevel v7 edge constraint support

If a window isn't resizable from specific directions, the compositor can inform clients of the current edge constraints, so they don't display resize cursors for non-resizable edges.
This commit is contained in:
Frank Praznik 2025-04-13 10:49:20 -04:00
parent 113475acbd
commit edd08771a9
No known key found for this signature in database
5 changed files with 150 additions and 17 deletions

View file

@ -599,6 +599,73 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
if (window->hit_test) {
SDL_HitTestResult rc = window->hit_test(window, &seat->pointer.last_motion, window->hit_test_data);
// Apply the toplevel constraints if the window isn't resizable from those directions.
switch (rc) {
case SDL_HITTEST_RESIZE_TOPLEFT:
if ((window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_TOP) &&
(window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_LEFT)) {
rc = SDL_HITTEST_NORMAL;
} else if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_TOP) {
rc = SDL_HITTEST_RESIZE_LEFT;
} else if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_LEFT) {
rc = SDL_HITTEST_RESIZE_TOP;
}
break;
case SDL_HITTEST_RESIZE_TOP:
if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_TOP) {
rc = SDL_HITTEST_NORMAL;
}
break;
case SDL_HITTEST_RESIZE_TOPRIGHT:
if ((window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_TOP) &&
(window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_RIGHT)) {
rc = SDL_HITTEST_NORMAL;
} else if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_TOP) {
rc = SDL_HITTEST_RESIZE_RIGHT;
} else if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_RIGHT) {
rc = SDL_HITTEST_RESIZE_TOP;
}
break;
case SDL_HITTEST_RESIZE_RIGHT:
if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_RIGHT) {
rc = SDL_HITTEST_NORMAL;
}
break;
case SDL_HITTEST_RESIZE_BOTTOMRIGHT:
if ((window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_BOTTOM) &&
(window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_RIGHT)) {
rc = SDL_HITTEST_NORMAL;
} else if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_BOTTOM) {
rc = SDL_HITTEST_RESIZE_RIGHT;
} else if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_RIGHT) {
rc = SDL_HITTEST_RESIZE_BOTTOM;
}
break;
case SDL_HITTEST_RESIZE_BOTTOM:
if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_BOTTOM) {
rc = SDL_HITTEST_NORMAL;
}
break;
case SDL_HITTEST_RESIZE_BOTTOMLEFT:
if ((window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_BOTTOM) &&
(window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_LEFT)) {
rc = SDL_HITTEST_NORMAL;
} else if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_BOTTOM) {
rc = SDL_HITTEST_RESIZE_LEFT;
} else if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_LEFT) {
rc = SDL_HITTEST_RESIZE_BOTTOM;
}
break;
case SDL_HITTEST_RESIZE_LEFT:
if (window_data->toplevel_constraints & WAYLAND_TOPLEVEL_CONSTRAINED_LEFT) {
rc = SDL_HITTEST_NORMAL;
}
break;
default:
break;
}
if (rc != window_data->hit_test_result) {
window_data->hit_test_result = rc;
Wayland_SeatUpdateCursor(seat);

View file

@ -1253,7 +1253,7 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
struct wl_seat *seat = wl_registry_bind(d->registry, id, &wl_seat_interface, SDL_min(SDL_WL_SEAT_VERSION, version));
Wayland_DisplayCreateSeat(d, seat, id);
} else if (SDL_strcmp(interface, "xdg_wm_base") == 0) {
d->shell.xdg = wl_registry_bind(d->registry, id, &xdg_wm_base_interface, SDL_min(version, 6));
d->shell.xdg = wl_registry_bind(d->registry, id, &xdg_wm_base_interface, SDL_min(version, 7));
xdg_wm_base_add_listener(d->shell.xdg, &shell_listener_xdg, NULL);
} else if (SDL_strcmp(interface, "wl_shm") == 0) {
d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);

View file

@ -774,6 +774,7 @@ static void handle_configure_xdg_toplevel(void *data,
bool active = false;
bool resizing = false;
bool suspended = false;
wind->toplevel_constraints = 0;
wl_array_for_each (state, states) {
switch (*state) {
case XDG_TOPLEVEL_STATE_FULLSCREEN:
@ -800,6 +801,18 @@ static void handle_configure_xdg_toplevel(void *data,
case XDG_TOPLEVEL_STATE_SUSPENDED:
suspended = true;
break;
case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT:
wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_LEFT;
break;
case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT:
wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_RIGHT;
break;
case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP:
wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_TOP;
break;
case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM:
wind->toplevel_constraints |= WAYLAND_TOPLEVEL_CONSTRAINED_BOTTOM;
break;
default:
break;
}
@ -1205,6 +1218,7 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
#if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0)
resizing = (window_state & LIBDECOR_WINDOW_STATE_RESIZING) != 0;
#endif
// TODO: Toplevel constraint passthrough is waiting on upstream libdecor changes.
}
const bool floating = !(fullscreen || maximized || tiled);

View file

@ -95,6 +95,13 @@ struct SDL_WindowData
WAYLAND_WM_CAPS_FULLSCREEN |
WAYLAND_WM_CAPS_MINIMIZE
} wm_caps;
enum
{
WAYLAND_TOPLEVEL_CONSTRAINED_LEFT = 0x01,
WAYLAND_TOPLEVEL_CONSTRAINED_RIGHT = 0x02,
WAYLAND_TOPLEVEL_CONSTRAINED_TOP = 0x04,
WAYLAND_TOPLEVEL_CONSTRAINED_BOTTOM = 0x08
} toplevel_constraints;
struct wl_egl_window *egl_window;
#ifdef SDL_VIDEO_OPENGL_EGL

View file

@ -29,7 +29,7 @@
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="xdg_wm_base" version="6">
<interface name="xdg_wm_base" version="7">
<description summary="create desktop-style surfaces">
The xdg_wm_base interface is exposed as a global object enabling clients
to turn their wl_surfaces into windows in a desktop environment. It
@ -122,7 +122,7 @@
</event>
</interface>
<interface name="xdg_positioner" version="6">
<interface name="xdg_positioner" version="7">
<description summary="child surface positioner">
The xdg_positioner provides a collection of rules for the placement of a
child surface relative to a parent surface. Rules can be defined to ensure
@ -344,7 +344,7 @@
The default adjustment is none.
</description>
<arg name="constraint_adjustment" type="uint"
<arg name="constraint_adjustment" type="uint" enum="constraint_adjustment"
summary="bit mask of constraint adjustments"/>
</request>
@ -407,7 +407,7 @@
</request>
</interface>
<interface name="xdg_surface" version="6">
<interface name="xdg_surface" version="7">
<description summary="desktop user interface surface base interface">
An interface that may be implemented by a wl_surface, for
implementations that provide a desktop-style user interface.
@ -434,7 +434,8 @@
manipulate a buffer prior to the first xdg_surface.configure call must
also be treated as errors.
After creating a role-specific object and setting it up, the client must
After creating a role-specific object and setting it up (e.g. by sending
the title, app ID, size constraints, parent, etc), the client must
perform an initial commit without any buffer attached. The compositor
will reply with initial wl_surface state such as
wl_surface.preferred_buffer_scale followed by an xdg_surface.configure
@ -515,8 +516,7 @@
portions like drop-shadows which should be ignored for the
purposes of aligning, placing and constraining windows.
The window geometry is double buffered, and will be applied at the
time wl_surface.commit of the corresponding wl_surface is called.
The window geometry is double-buffered state, see wl_surface.commit.
When maintaining a position, the compositor should treat the (x, y)
coordinate of the window geometry as the top left corner of the window.
@ -617,7 +617,7 @@
</interface>
<interface name="xdg_toplevel" version="6">
<interface name="xdg_toplevel" version="7">
<description summary="toplevel surface">
This interface defines an xdg_surface role which allows a surface to,
among other things, set window-like properties such as maximize,
@ -625,13 +625,17 @@
id, and well as trigger user interactive operations such as interactive
resize and move.
A xdg_toplevel by default is responsible for providing the full intended
visual representation of the toplevel, which depending on the window
state, may mean things like a title bar, window controls and drop shadow.
Unmapping an xdg_toplevel means that the surface cannot be shown
by the compositor until it is explicitly mapped again.
All active operations (e.g., move, resize) are canceled and all
attributes (e.g. title, state, stacking, ...) are discarded for
an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to
the state it had right after xdg_surface.get_toplevel. The client
can re-map the toplevel by perfoming a commit without any buffer
can re-map the toplevel by performing a commit without any buffer
attached, waiting for a configure event and handling it as usual (see
xdg_surface description).
@ -828,8 +832,7 @@
configure event to ensure that both the client and the compositor
setting the state can be synchronized.
States set in this way are double-buffered. They will get applied on
the next commit.
States set in this way are double-buffered, see wl_surface.commit.
</description>
<entry name="maximized" value="1" summary="the surface is maximized">
<description summary="the surface is maximized">
@ -869,24 +872,36 @@
<description summary="the surfaces left edge is tiled">
The window is currently in a tiled layout and the left edge is
considered to be adjacent to another part of the tiling grid.
The client should draw without shadow or other decoration outside of
the window geometry on the left edge.
</description>
</entry>
<entry name="tiled_right" value="6" since="2">
<description summary="the surfaces right edge is tiled">
The window is currently in a tiled layout and the right edge is
considered to be adjacent to another part of the tiling grid.
The client should draw without shadow or other decoration outside of
the window geometry on the right edge.
</description>
</entry>
<entry name="tiled_top" value="7" since="2">
<description summary="the surfaces top edge is tiled">
The window is currently in a tiled layout and the top edge is
considered to be adjacent to another part of the tiling grid.
The client should draw without shadow or other decoration outside of
the window geometry on the top edge.
</description>
</entry>
<entry name="tiled_bottom" value="8" since="2">
<description summary="the surfaces bottom edge is tiled">
The window is currently in a tiled layout and the bottom edge is
considered to be adjacent to another part of the tiling grid.
The client should draw without shadow or other decoration outside of
the window geometry on the bottom edge.
</description>
</entry>
<entry name="suspended" value="9" since="6">
@ -896,6 +911,38 @@
outputs are switched off due to screen locking.
</description>
</entry>
<entry name="constrained_left" value="10" since="7">
<description summary="the surfaces left edge is constrained">
The left edge of the window is currently constrained, meaning it
shouldn't attempt to resize from that edge. It can for example mean
it's tiled next to a monitor edge on the constrained side of the
window.
</description>
</entry>
<entry name="constrained_right" value="11" since="7">
<description summary="the surfaces right edge is constrained">
The right edge of the window is currently constrained, meaning it
shouldn't attempt to resize from that edge. It can for example mean
it's tiled next to a monitor edge on the constrained side of the
window.
</description>
</entry>
<entry name="constrained_top" value="12" since="7">
<description summary="the surfaces top edge is constrained">
The top edge of the window is currently constrained, meaning it
shouldn't attempt to resize from that edge. It can for example mean
it's tiled next to a monitor edge on the constrained side of the
window.
</description>
</entry>
<entry name="constrained_bottom" value="13" since="7">
<description summary="the surfaces bottom edge is tiled">
The bottom edge of the window is currently constrained, meaning it
shouldn't attempt to resize from that edge. It can for example mean
it's tiled next to a monitor edge on the constrained side of the
window.
</description>
</entry>
</enum>
<request name="set_max_size">
@ -908,8 +955,7 @@
The width and height arguments are in window geometry coordinates.
See xdg_surface.set_window_geometry.
Values set in this way are double-buffered. They will get applied
on the next commit.
Values set in this way are double-buffered, see wl_surface.commit.
The compositor can use this information to allow or disallow
different states like maximize or fullscreen and draw accurate
@ -949,8 +995,7 @@
The width and height arguments are in window geometry coordinates.
See xdg_surface.set_window_geometry.
Values set in this way are double-buffered. They will get applied
on the next commit.
Values set in this way are double-buffered, see wl_surface.commit.
The compositor can use this information to allow or disallow
different states like maximize or fullscreen and draw accurate
@ -1194,7 +1239,7 @@
</event>
</interface>
<interface name="xdg_popup" version="6">
<interface name="xdg_popup" version="7">
<description summary="short-lived, popup surfaces for menus">
A popup surface is a short-lived, temporary surface. It can be used to
implement for example menus, popovers, tooltips and other similar user