Skip to content

Commit 0b1f487

Browse files
committed
wayland: Add experimental zone positioning support
1 parent 41039b4 commit 0b1f487

File tree

8 files changed

+995
-20
lines changed

8 files changed

+995
-20
lines changed

docs/README-wayland.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ encounter limitations or behavior that is different from other windowing systems
66

77
## Common issues:
88

9+
### ```SDL_SetWindowPosition()``` doesn't work on non-popup windows
10+
11+
- Wayland requires the `ext-zones-v1` extension to position windows programmatically. Otherwise, toplevel windows may
12+
not be positioned programmatically.\
13+
\
14+
Enabling this protocol requires setting the `SDL_VIDEO_WAYLAND_ENABLE_ZONES` hint to `1`. After initializing the video
15+
subsystem, compositor support for the required protocol may be queried via the
16+
`SDL_PROP_GLOBAL_VIDEO_WAYLAND_HAS_ZONES_BOOLEAN` global video property.\
17+
\
18+
This protocol allows for positioning windows within the boundaries of desktop zones, the coordinates of which may not
19+
correspond 1:1 to output display coordinates. This is primarily intended for clients with multi-window interfaces that
20+
need to position windows relative to one another, development environments/workflows, and embedded scenarios where
21+
positioning is desired and the underlying environment and its capabilities are known. Single window clients should
22+
_not_ enable this by default, as it can override compositor window positioning, and tiling window managers in
23+
particular may demonstrate undesirable behavior with it.
24+
925
### Legacy, DPI-unaware applications are blurry
1026

1127
- Wayland handles high-DPI displays by scaling the desktop, which causes applications that are not designed to be
@@ -34,10 +50,6 @@ encounter limitations or behavior that is different from other windowing systems
3450
system settings, and falling back to a selection algorithm if this fails. If it is incorrect, it can be manually
3551
overridden by setting the ```SDL_VIDEO_DISPLAY_PRIORITY``` hint.
3652

37-
### ```SDL_SetWindowPosition()``` doesn't work on non-popup windows
38-
39-
- Wayland does not allow toplevel windows to position themselves programmatically.
40-
4153
### Retrieving the global mouse cursor position when the cursor is outside a window doesn't work
4254

4355
- Wayland only provides applications with the cursor position within the borders of the application windows. Querying

include/SDL3/SDL_hints.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3784,6 +3784,34 @@ extern "C" {
37843784
*/
37853785
#define SDL_HINT_VIDEO_WAYLAND_ALLOW_LIBDECOR "SDL_VIDEO_WAYLAND_ALLOW_LIBDECOR"
37863786

3787+
/**
3788+
* A variable controlling whether to allow positioning of windows via the
3789+
* `ext-zones-v1` protocol.
3790+
*
3791+
* This hint requires that the compositor supports the `ext-zones-v1` protocol.
3792+
* Support for this protocol can be checked via the global
3793+
* `SDL_PROP_GLOBAL_VIDEO_WAYLAND_HAS_ZONES_BOOLEAN` property after initializing
3794+
* the video subsystem with this hint set.
3795+
*
3796+
* If the compositor lacks support for the required protocol, this hint does
3797+
* nothing.
3798+
*
3799+
* Zones are arbitrary regions that allow for limited window placement within a
3800+
* logical space, and should not be presumed to correlate 1:1 to display output
3801+
* coordinates, so care must be taken when enabling this. See
3802+
* docs/README-wayland.md and wayland-protocols/ext-zones-v1.xml for more details.
3803+
*
3804+
* The variable can be set to the following values:
3805+
*
3806+
* - "0": positioning with ext-zones is disabled. (default)
3807+
* - "1": positioning with ext-zones is enabled.
3808+
*
3809+
* This hint should be set before SDL is initialized.
3810+
*
3811+
* \since This hint is available since SDL 3.1.6.
3812+
*/
3813+
#define SDL_HINT_VIDEO_WAYLAND_ENABLE_ZONES "SDL_VIDEO_WAYLAND_ENABLE_ZONES"
3814+
37873815
/**
37883816
* A variable controlling whether video mode emulation is enabled under
37893817
* Wayland.

include/SDL3/SDL_video.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ typedef Uint32 SDL_WindowID;
100100
*/
101101
#define SDL_PROP_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER "SDL.video.wayland.wl_display"
102102

103+
/**
104+
* A boolean set to true if the windowing system supports the optional
105+
* ext-zones protocol for positioning windows in Wayland. Requires that the
106+
* `SDL_VIDEO_WAYLAND_ENABLE_ZONES` hint be set to enable. See
107+
* docs/README-wayland.md for more information.
108+
*
109+
* Can be queried after video subsystem initialization.
110+
*/
111+
#define SDL_PROP_GLOBAL_VIDEO_WAYLAND_HAS_ZONES_BOOLEAN "SDL.video.wayland.has_zones"
112+
103113
/**
104114
* System theme.
105115
*

src/video/wayland/SDL_waylandvideo.c

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747

4848
#include "alpha-modifier-v1-client-protocol.h"
4949
#include "cursor-shape-v1-client-protocol.h"
50+
#include "ext-zones-v1-client-protocol.h"
5051
#include "fractional-scale-v1-client-protocol.h"
5152
#include "frog-color-management-v1-client-protocol.h"
5253
#include "idle-inhibit-unstable-v1-client-protocol.h"
@@ -400,6 +401,7 @@ static void handle_wl_output_done(void *data, struct wl_output *output);
400401
// Initialization/Query functions
401402
static bool Wayland_VideoInit(SDL_VideoDevice *_this);
402403
static bool Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect);
404+
static bool Wayland_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect);
403405
static void Wayland_VideoQuit(SDL_VideoDevice *_this);
404406

405407
static const char *SDL_WAYLAND_surface_tag = "sdl-window";
@@ -617,6 +619,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols)
617619
device->VideoInit = Wayland_VideoInit;
618620
device->VideoQuit = Wayland_VideoQuit;
619621
device->GetDisplayBounds = Wayland_GetDisplayBounds;
622+
device->GetDisplayUsableBounds = Wayland_GetDisplayUsableBounds;
620623
device->SuspendScreenSaver = Wayland_SuspendScreenSaver;
621624

622625
device->PumpEvents = Wayland_PumpEvents;
@@ -661,6 +664,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols)
661664
device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels;
662665
device->GetWindowContentScale = Wayland_GetWindowContentScale;
663666
device->GetWindowICCProfile = Wayland_GetWindowICCProfile;
667+
device->GetWindowBordersSize = Wayland_GetWindowBorderSize;
664668
device->GetDisplayForWindow = Wayland_GetDisplayForWindow;
665669
device->DestroyWindow = Wayland_DestroyWindow;
666670
device->SetWindowHitTest = Wayland_SetWindowHitTest;
@@ -673,6 +677,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(bool require_preferred_protocols)
673677
device->SyncWindow = Wayland_SyncWindow;
674678
device->SetWindowFocusable = Wayland_SetWindowFocusable;
675679
device->ReconfigureWindow = Wayland_ReconfigureWindow;
680+
device->SetWindowAlwaysOnTop = Wayland_SetWindowAlwaysOnTop;
676681

677682
#ifdef SDL_USE_LIBDBUS
678683
if (SDL_SystemTheme_Init())
@@ -731,7 +736,86 @@ VideoBootStrap Wayland_bootstrap = {
731736
false
732737
};
733738

734-
static void handle_xdg_output_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y)
739+
static void handle_zone_size(void *data, struct ext_zone_v1 *ext_zone_v1, int32_t width, int32_t height)
740+
{
741+
SDL_DisplayData *disp = (SDL_DisplayData *)data;
742+
743+
// Negative size values mean that zone creation on this output was denied.
744+
if (width < 0 || height < 0) {
745+
ext_zone_v1_destroy(disp->ext_zone_v1);
746+
disp->ext_zone_v1 = NULL;
747+
return;
748+
}
749+
750+
disp->zone_width = width ? width : SDL_MAX_SINT32;
751+
disp->zone_height = height ? height : SDL_MAX_SINT32;
752+
753+
if (disp->display) {
754+
SDL_VideoDisplay *display = SDL_GetVideoDisplay(disp->display);
755+
if (display) {
756+
SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, width, height);
757+
}
758+
}
759+
}
760+
761+
static void handle_zone_handle(void *data, struct ext_zone_v1 *ext_zone_v1, const char *handle)
762+
{
763+
// NOP
764+
}
765+
766+
static void handle_zone_done(void *data, struct ext_zone_v1 *ext_zone_v1)
767+
{
768+
// NOP
769+
}
770+
771+
static void handle_zone_item_blocked(void *data, struct ext_zone_v1 *ext_zone_v1, struct ext_zone_item_v1 *item)
772+
{
773+
SDL_WindowData *wind = (SDL_WindowData *)ext_zone_item_v1_get_user_data(item);
774+
wind->entering_new_zone = false;
775+
}
776+
777+
static void handle_zone_item_entered(void *data, struct ext_zone_v1 *ext_zone_v1, struct ext_zone_item_v1 *item)
778+
{
779+
if (item) {
780+
SDL_WindowData *wind = (SDL_WindowData *)ext_zone_item_v1_get_user_data(item);
781+
wind->current_ext_zone_v1 = ext_zone_v1;
782+
783+
if (wind->entering_new_zone) {
784+
Wayland_ApplyZoneItemPosition(wind);
785+
}
786+
787+
wind->entering_new_zone = false;
788+
}
789+
}
790+
791+
static void handle_zone_item_left(void *data, struct ext_zone_v1 *ext_zone_v1, struct ext_zone_item_v1 *item)
792+
{
793+
if (item) {
794+
SDL_WindowData *wind = (SDL_WindowData *)ext_zone_item_v1_get_user_data(item);
795+
wind->current_ext_zone_v1 = NULL;
796+
797+
if (!wind->entering_new_zone) {
798+
// If leaving a zone without a new join pending, find a new zone to join.
799+
const SDL_DisplayData *display = Wayland_GetDisplayForWindowZone(wind);
800+
801+
if (display) {
802+
ext_zone_v1_add_item(display->ext_zone_v1, item);
803+
}
804+
}
805+
}
806+
}
807+
808+
static const struct ext_zone_v1_listener zone_listener = {
809+
handle_zone_size,
810+
handle_zone_handle,
811+
handle_zone_done,
812+
handle_zone_item_blocked,
813+
handle_zone_item_entered,
814+
handle_zone_item_left
815+
};
816+
817+
static void handle_xdg_output_logical_position(void *data, struct zxdg_output_v1 *xdg_output,
818+
int32_t x, int32_t y)
735819
{
736820
SDL_DisplayData *internal = (SDL_DisplayData *)data;
737821

@@ -1071,6 +1155,13 @@ static void handle_wl_output_done(void *data, struct wl_output *output)
10711155

10721156
SDL_SetDisplayHDRProperties(dpy, &internal->HDR);
10731157

1158+
// Get the zone for this output, if the extension is available.
1159+
if (video->ext_zone_manager_v1 && !internal->ext_zone_v1) {
1160+
internal->ext_zone_v1 = ext_zone_manager_v1_get_zone(video->ext_zone_manager_v1, internal->output);
1161+
ext_zone_v1_set_user_data(internal->ext_zone_v1, internal);
1162+
ext_zone_v1_add_listener(internal->ext_zone_v1, &zone_listener, internal);
1163+
}
1164+
10741165
if (internal->display == 0) {
10751166
// First time getting display info, initialize the VideoDisplay
10761167
if (internal->physical_width_mm >= internal->physical_height_mm) {
@@ -1197,6 +1288,10 @@ static void Wayland_free_display(SDL_VideoDisplay *display, bool send_event)
11971288
wp_color_management_output_v1_destroy(display_data->wp_color_management_output);
11981289
}
11991290

1291+
if (display_data->ext_zone_v1) {
1292+
ext_zone_v1_destroy(display_data->ext_zone_v1);
1293+
}
1294+
12001295
if (display_data->xdg_output) {
12011296
zxdg_output_v1_destroy(display_data->xdg_output);
12021297
}
@@ -1327,6 +1422,10 @@ static void handle_registry_global(void *data, struct wl_registry *registry, uin
13271422
d->wp_alpha_modifier_v1 = wl_registry_bind(d->registry, id, &wp_alpha_modifier_v1_interface, 1);
13281423
} else if (SDL_strcmp(interface, "xdg_toplevel_icon_manager_v1") == 0) {
13291424
d->xdg_toplevel_icon_manager_v1 = wl_registry_bind(d->registry, id, &xdg_toplevel_icon_manager_v1_interface, 1);
1425+
} else if (SDL_strcmp(interface, "ext_zone_manager_v1") == 0) {
1426+
if (SDL_GetHintBoolean(SDL_HINT_VIDEO_WAYLAND_ENABLE_ZONES, false)) {
1427+
d->ext_zone_manager_v1 = wl_registry_bind(d->registry, id, &ext_zone_manager_v1_interface, 1);
1428+
}
13301429
} else if (SDL_strcmp(interface, "frog_color_management_factory_v1") == 0) {
13311430
d->frog_color_management_factory_v1 = wl_registry_bind(d->registry, id, &frog_color_management_factory_v1_interface, 1);
13321431
} else if (SDL_strcmp(interface, "wp_color_manager_v1") == 0) {
@@ -1462,11 +1561,18 @@ bool Wayland_VideoInit(SDL_VideoDevice *_this)
14621561
// Second roundtrip to receive all output events.
14631562
WAYLAND_wl_display_roundtrip(data->display);
14641563

1564+
// Third roundtrip, if necessary, to retrieve zone info.
1565+
if (data->ext_zone_manager_v1) {
1566+
WAYLAND_wl_display_roundtrip(data->display);
1567+
}
1568+
14651569
Wayland_FinalizeDisplays(data);
14661570

14671571
Wayland_InitMouse(data);
14681572
Wayland_InitKeyboard(_this);
14691573

1574+
SDL_SetBooleanProperty(SDL_GetGlobalProperties(), SDL_PROP_GLOBAL_VIDEO_WAYLAND_HAS_ZONES_BOOLEAN, !!data->ext_zone_manager_v1);
1575+
14701576
if (data->primary_selection_device_manager) {
14711577
_this->SetPrimarySelectionText = Wayland_SetPrimarySelectionText;
14721578
_this->GetPrimarySelectionText = Wayland_GetPrimarySelectionText;
@@ -1508,6 +1614,22 @@ static bool Wayland_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *d
15081614
return true;
15091615
}
15101616

1617+
static bool Wayland_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
1618+
{
1619+
SDL_DisplayData *internal = display->internal;
1620+
1621+
if (internal->ext_zone_v1) {
1622+
rect->x = internal->x;
1623+
rect->y = internal->y;
1624+
rect->w = internal->zone_width;
1625+
rect->h = internal->zone_height;
1626+
1627+
return true;
1628+
}
1629+
1630+
return Wayland_GetDisplayBounds(_this, display, rect);
1631+
}
1632+
15111633
static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
15121634
{
15131635
SDL_VideoData *data = _this->internal;
@@ -1642,6 +1764,11 @@ static void Wayland_VideoCleanup(SDL_VideoDevice *_this)
16421764
data->xdg_toplevel_icon_manager_v1 = NULL;
16431765
}
16441766

1767+
if (data->ext_zone_manager_v1) {
1768+
ext_zone_manager_v1_destroy(data->ext_zone_manager_v1);
1769+
data->ext_zone_manager_v1 = NULL;
1770+
}
1771+
16451772
if (data->frog_color_management_factory_v1) {
16461773
frog_color_management_factory_v1_destroy(data->frog_color_management_factory_v1);
16471774
data->frog_color_management_factory_v1 = NULL;

src/video/wayland/SDL_waylandvideo.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ struct SDL_VideoData
8787
struct zwp_tablet_manager_v2 *tablet_manager;
8888
struct wl_fixes *wl_fixes;
8989
struct zwp_pointer_gestures_v1 *zwp_pointer_gestures;
90+
struct ext_zone_manager_v1 *ext_zone_manager_v1;
9091

9192
struct xkb_context *xkb_context;
9293

@@ -109,12 +110,14 @@ struct SDL_DisplayData
109110
struct wl_output *output;
110111
struct zxdg_output_v1 *xdg_output;
111112
struct wp_color_management_output_v1 *wp_color_management_output;
113+
struct ext_zone_v1 *ext_zone_v1;
112114
char *wl_output_name;
113115
double scale_factor;
114116
uint32_t registry_id;
115117
int logical_width, logical_height;
116118
int pixel_width, pixel_height;
117119
int x, y, refresh, transform;
120+
int zone_width, zone_height;
118121
SDL_DisplayOrientation orientation;
119122
int physical_width_mm, physical_height_mm;
120123
bool has_logical_position, has_logical_size;

0 commit comments

Comments
 (0)