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) { if (window->hit_test) {
SDL_HitTestResult rc = window->hit_test(window, &seat->pointer.last_motion, window->hit_test_data); 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) { if (rc != window_data->hit_test_result) {
window_data->hit_test_result = rc; window_data->hit_test_result = rc;
Wayland_SeatUpdateCursor(seat); 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)); 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); Wayland_DisplayCreateSeat(d, seat, id);
} else if (SDL_strcmp(interface, "xdg_wm_base") == 0) { } 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); xdg_wm_base_add_listener(d->shell.xdg, &shell_listener_xdg, NULL);
} else if (SDL_strcmp(interface, "wl_shm") == 0) { } else if (SDL_strcmp(interface, "wl_shm") == 0) {
d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); 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 active = false;
bool resizing = false; bool resizing = false;
bool suspended = false; bool suspended = false;
wind->toplevel_constraints = 0;
wl_array_for_each (state, states) { wl_array_for_each (state, states) {
switch (*state) { switch (*state) {
case XDG_TOPLEVEL_STATE_FULLSCREEN: case XDG_TOPLEVEL_STATE_FULLSCREEN:
@ -800,6 +801,18 @@ static void handle_configure_xdg_toplevel(void *data,
case XDG_TOPLEVEL_STATE_SUSPENDED: case XDG_TOPLEVEL_STATE_SUSPENDED:
suspended = true; suspended = true;
break; 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: default:
break; break;
} }
@ -1205,6 +1218,7 @@ static void decoration_frame_configure(struct libdecor_frame *frame,
#if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0) #if SDL_LIBDECOR_CHECK_VERSION(0, 3, 0)
resizing = (window_state & LIBDECOR_WINDOW_STATE_RESIZING) != 0; resizing = (window_state & LIBDECOR_WINDOW_STATE_RESIZING) != 0;
#endif #endif
// TODO: Toplevel constraint passthrough is waiting on upstream libdecor changes.
} }
const bool floating = !(fullscreen || maximized || tiled); const bool floating = !(fullscreen || maximized || tiled);

View file

@ -95,6 +95,13 @@ struct SDL_WindowData
WAYLAND_WM_CAPS_FULLSCREEN | WAYLAND_WM_CAPS_FULLSCREEN |
WAYLAND_WM_CAPS_MINIMIZE WAYLAND_WM_CAPS_MINIMIZE
} wm_caps; } 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; struct wl_egl_window *egl_window;
#ifdef SDL_VIDEO_OPENGL_EGL #ifdef SDL_VIDEO_OPENGL_EGL

View file

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