wayland: Allow the creation of roleless window surfaces for custom application use

Allow for the creation of SDL windows with a roleless surface that applications can use for their own purposes, such as with a windowing protocol other than XDG toplevel.

The property `wayland.surface_role_custom` will create a window with a surface that SDL can render to and handles input for, but is not associated with a toplevel window, so applications can use it for their own, custom purposes (e.g. wlr_layer_shell).

A test/minimal example is included in tests/testwaylandcustom.c
This commit is contained in:
Frank Praznik 2024-01-06 15:37:38 -05:00
parent 4417250d0d
commit f7dd0f9491
6 changed files with 466 additions and 59 deletions

View File

@ -43,3 +43,31 @@ encounter limitations or behavior that is different from other windowing systems
on the format of this file. Note that if your application manually sets the application ID via the `SDL_APP_ID` hint
string, the desktop entry file name should match the application ID. For example, if your application ID is set
to `org.my_org.sdl_app`, the desktop entry file should be named `org.my_org.sdl_app.desktop`.
## Using custom Wayland windowing protocols with SDL windows
Under normal operation, an `SDL_Window` corresponds to an XDG toplevel window, which provides a standard desktop window.
If an application wishes to use a different windowing protocol with an SDL window (e.g. wlr_layer_shell) while still
having SDL handle input and rendering, it needs to create a custom, roleless surface and attach that surface to its own
toplevel window.
This is done by using `SDL_CreateWindowWithProperties()` and setting the
`SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN` property to `SDL_TRUE`. Once the window has been
successfully created, the `wl_display` and `wl_surface` objects can then be retrieved from the
`SDL_PROPERTY_WINDOW_WAYLAND_DISPLAY_POINTER` and `SDL_PROPERTY_WINDOW_WAYLAND_SURFACE_POINTER` properties respectively.
Surfaces don't receive any size change notifications, so if an application changes the window size, it must inform SDL
that the surface size has changed by calling SDL_SetWindowSize() with the new dimensions.
Custom surfaces will automatically handle scaling internally if the window was created with the `high-pixel-density`
property set to `SDL_TRUE`. In this case, applications should not manually attach viewports or change the surface scale
value, as SDL will handle this internally. Calls to `SDL_SetWindowSize()` should use the logical size of the window, and
`SDL_GetWindowSizeInPixels()` should be used to query the size of the backbuffer surface in pixels. If this property is
not set or is `SDL_FALSE`, applications can attach their own viewports or change the surface scale manually, and the SDL
backend will not interfere or change any values internally. In this case, calls to `SDL_SetWindowSize()` should pass the
requested surface size in pixels, not the logical window size, as no scaling calculations will be done internally.
All window functions that control window state aside from `SDL_SetWindowSize()` are no-ops with custom surfaces.
Please see the minimal example in tests/testwaylandcustom.c for an example of how to use a custom, roleless surface and
attach it to an application-managed toplevel window.

View File

@ -866,6 +866,13 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_CreatePopupWindow(SDL_Window *parent, in
* `(__unsafe_unretained)` NSView associated with the window, defaults to
* `[window contentView]`
*
* These are additional supported properties on Wayland:
*
* - `SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN` - true
* if the application wants to use the Wayland surface for a custom role and
* does not want it attached to an XDG toplevel window. See
* docs/README-wayland.md for more information on using custom surfaces.
*
* These are additional supported properties on Windows:
*
* - `SDL_PROPERTY_WINDOW_CREATE_WIN32_HWND_POINTER`: the HWND associated with
@ -894,35 +901,35 @@ extern DECLSPEC SDL_Window *SDLCALL SDL_CreatePopupWindow(SDL_Window *parent, in
*/
extern DECLSPEC SDL_Window *SDLCALL SDL_CreateWindowWithProperties(SDL_PropertiesID props);
#define SDL_PROPERTY_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN "always-on-top"
#define SDL_PROPERTY_WINDOW_CREATE_BORDERLESS_BOOLEAN "borderless"
#define SDL_PROPERTY_WINDOW_CREATE_FOCUSABLE_BOOLEAN "focusable"
#define SDL_PROPERTY_WINDOW_CREATE_FULLSCREEN_BOOLEAN "fullscreen"
#define SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER "height"
#define SDL_PROPERTY_WINDOW_CREATE_HIDDEN_BOOLEAN "hidden"
#define SDL_PROPERTY_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN "high-pixel-density"
#define SDL_PROPERTY_WINDOW_CREATE_MAXIMIZED_BOOLEAN "maximized"
#define SDL_PROPERTY_WINDOW_CREATE_MENU_BOOLEAN "menu"
#define SDL_PROPERTY_WINDOW_CREATE_METAL_BOOLEAN "metal"
#define SDL_PROPERTY_WINDOW_CREATE_MINIMIZED_BOOLEAN "minimized"
#define SDL_PROPERTY_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN "mouse-grabbed"
#define SDL_PROPERTY_WINDOW_CREATE_OPENGL_BOOLEAN "opengl"
#define SDL_PROPERTY_WINDOW_CREATE_PARENT_POINTER "parent"
#define SDL_PROPERTY_WINDOW_CREATE_RESIZABLE_BOOLEAN "resizable"
#define SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING "title"
#define SDL_PROPERTY_WINDOW_CREATE_TRANSPARENT_BOOLEAN "transparent"
#define SDL_PROPERTY_WINDOW_CREATE_TOOLTIP_BOOLEAN "tooltip"
#define SDL_PROPERTY_WINDOW_CREATE_UTILITY_BOOLEAN "utility"
#define SDL_PROPERTY_WINDOW_CREATE_VULKAN_BOOLEAN "vulkan"
#define SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER "width"
#define SDL_PROPERTY_WINDOW_CREATE_X_NUMBER "x"
#define SDL_PROPERTY_WINDOW_CREATE_Y_NUMBER "y"
#define SDL_PROPERTY_WINDOW_CREATE_COCOA_WINDOW_POINTER "cocoa.window"
#define SDL_PROPERTY_WINDOW_CREATE_COCOA_VIEW_POINTER "cocoa.view"
#define SDL_PROPERTY_WINDOW_CREATE_WIN32_HWND_POINTER "win32.hwnd"
#define SDL_PROPERTY_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER "win32.pixel_format_hwnd"
#define SDL_PROPERTY_WINDOW_CREATE_X11_WINDOW_NUMBER "x11.window"
#define SDL_PROPERTY_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN "always-on-top"
#define SDL_PROPERTY_WINDOW_CREATE_BORDERLESS_BOOLEAN "borderless"
#define SDL_PROPERTY_WINDOW_CREATE_FOCUSABLE_BOOLEAN "focusable"
#define SDL_PROPERTY_WINDOW_CREATE_FULLSCREEN_BOOLEAN "fullscreen"
#define SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER "height"
#define SDL_PROPERTY_WINDOW_CREATE_HIDDEN_BOOLEAN "hidden"
#define SDL_PROPERTY_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN "high-pixel-density"
#define SDL_PROPERTY_WINDOW_CREATE_MAXIMIZED_BOOLEAN "maximized"
#define SDL_PROPERTY_WINDOW_CREATE_MENU_BOOLEAN "menu"
#define SDL_PROPERTY_WINDOW_CREATE_METAL_BOOLEAN "metal"
#define SDL_PROPERTY_WINDOW_CREATE_MINIMIZED_BOOLEAN "minimized"
#define SDL_PROPERTY_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN "mouse-grabbed"
#define SDL_PROPERTY_WINDOW_CREATE_OPENGL_BOOLEAN "opengl"
#define SDL_PROPERTY_WINDOW_CREATE_PARENT_POINTER "parent"
#define SDL_PROPERTY_WINDOW_CREATE_RESIZABLE_BOOLEAN "resizable"
#define SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING "title"
#define SDL_PROPERTY_WINDOW_CREATE_TRANSPARENT_BOOLEAN "transparent"
#define SDL_PROPERTY_WINDOW_CREATE_TOOLTIP_BOOLEAN "tooltip"
#define SDL_PROPERTY_WINDOW_CREATE_UTILITY_BOOLEAN "utility"
#define SDL_PROPERTY_WINDOW_CREATE_VULKAN_BOOLEAN "vulkan"
#define SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER "width"
#define SDL_PROPERTY_WINDOW_CREATE_X_NUMBER "x"
#define SDL_PROPERTY_WINDOW_CREATE_Y_NUMBER "y"
#define SDL_PROPERTY_WINDOW_CREATE_COCOA_WINDOW_POINTER "cocoa.window"
#define SDL_PROPERTY_WINDOW_CREATE_COCOA_VIEW_POINTER "cocoa.view"
#define SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN "wayland.surface_role_custom"
#define SDL_PROPERTY_WINDOW_CREATE_WIN32_HWND_POINTER "win32.hwnd"
#define SDL_PROPERTY_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER "win32.pixel_format_hwnd"
#define SDL_PROPERTY_WINDOW_CREATE_X11_WINDOW_NUMBER "x11.window"
/**
* Get the numeric ID of a window.

View File

@ -395,8 +395,10 @@ static void ConfigureWindowGeometry(SDL_Window *window)
wl_surface_set_buffer_scale(data->surface, 1);
SetDrawSurfaceViewport(window, data->drawable_width, data->drawable_height,
window_width, window_height);
} else {
} else if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
UnsetDrawSurfaceViewport(window);
/* Don't change this if DPI awareness flag is unset, as an application may have set this manually. */
wl_surface_set_buffer_scale(data->surface, (int32_t)data->windowed_scale_factor);
}
@ -1415,6 +1417,11 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
SDL_WindowData *data = window->driverdata;
SDL_PropertiesID props = SDL_GetWindowProperties(window);
/* Custom surfaces don't get toplevels and are always considered 'shown'; nothing to do here. */
if (data->shell_surface_type == WAYLAND_SURFACE_CUSTOM) {
return;
}
/* If this is a child window, the parent *must* be in the final shown state,
* meaning that it has received a configure event, followed by a frame callback.
* If not, a race condition can result, with effects ranging from the child
@ -1437,8 +1444,6 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
WAYLAND_wl_display_roundtrip(c->display);
}
data->surface_status = WAYLAND_SURFACE_STATUS_WAITING_FOR_CONFIGURE;
/* Detach any previous buffers before resetting everything, otherwise when
* calling this a second time you'll get an annoying protocol error!
*
@ -1683,6 +1688,11 @@ void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
SDL_WindowData *wind = window->driverdata;
SDL_PropertiesID props = SDL_GetWindowProperties(window);
/* Custom surfaces have nothing to destroy and are always considered to be 'shown'; nothing to do here. */
if (wind->shell_surface_type == WAYLAND_SURFACE_CUSTOM) {
return;
}
/* The window was shown, but the sync point hasn't yet been reached.
* Pump events to avoid a possible protocol violation.
*/
@ -1715,17 +1725,16 @@ void Wayland_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
} else
#endif
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_POPUP) {
Wayland_ReleasePopup(_this, window);
} else if (wind->shell_surface.xdg.roleobj.toplevel) {
xdg_toplevel_destroy(wind->shell_surface.xdg.roleobj.toplevel);
wind->shell_surface.xdg.roleobj.toplevel = NULL;
SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL);
}
if (wind->shell_surface.xdg.surface) {
xdg_surface_destroy(wind->shell_surface.xdg.surface);
wind->shell_surface.xdg.surface = NULL;
SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL);
}
Wayland_ReleasePopup(_this, window);
} else if (wind->shell_surface.xdg.roleobj.toplevel) {
xdg_toplevel_destroy(wind->shell_surface.xdg.roleobj.toplevel);
wind->shell_surface.xdg.roleobj.toplevel = NULL;
SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_TOPLEVEL_POINTER, NULL);
}
if (wind->shell_surface.xdg.surface) {
xdg_surface_destroy(wind->shell_surface.xdg.surface);
wind->shell_surface.xdg.surface = NULL;
SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_XDG_SURFACE_POINTER, NULL);
}
wind->show_hide_sync_required = SDL_TRUE;
@ -1844,6 +1853,11 @@ int Wayland_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window,
SDL_WindowData *wind = window->driverdata;
struct wl_output *output = display->driverdata->output;
/* Custom surfaces have no toplevel to make fullscreen. */
if (wind->shell_surface_type == WAYLAND_SURFACE_CUSTOM) {
return -1;
}
if (wind->show_hide_sync_required) {
WAYLAND_wl_display_roundtrip(_this->driverdata->display);
}
@ -2041,6 +2055,7 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
{
SDL_WindowData *data;
SDL_VideoData *c;
const SDL_bool custom_surface_role = SDL_GetBooleanProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN, SDL_FALSE);
data = SDL_calloc(1, sizeof(*data));
if (!data) {
@ -2140,18 +2155,24 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
/* We may need to create an idle inhibitor for this new window */
Wayland_SuspendScreenSaver(_this);
if (!custom_surface_role) {
#ifdef HAVE_LIBDECOR_H
if (c->shell.libdecor && !SDL_WINDOW_IS_POPUP(window)) {
data->shell_surface_type = WAYLAND_SURFACE_LIBDECOR;
} else
if (c->shell.libdecor && !SDL_WINDOW_IS_POPUP(window)) {
data->shell_surface_type = WAYLAND_SURFACE_LIBDECOR;
} else
#endif
if (c->shell.xdg) {
if (SDL_WINDOW_IS_POPUP(window)) {
data->shell_surface_type = WAYLAND_SURFACE_XDG_POPUP;
} else {
data->shell_surface_type = WAYLAND_SURFACE_XDG_TOPLEVEL;
}
} /* All other cases will be WAYLAND_SURFACE_UNKNOWN */
if (c->shell.xdg) {
if (SDL_WINDOW_IS_POPUP(window)) {
data->shell_surface_type = WAYLAND_SURFACE_XDG_POPUP;
} else {
data->shell_surface_type = WAYLAND_SURFACE_XDG_TOPLEVEL;
}
} /* All other cases will be WAYLAND_SURFACE_UNKNOWN */
} else {
/* Roleless surfaces are always considered to be in the shown state by the backend. */
data->shell_surface_type = WAYLAND_SURFACE_CUSTOM;
data->surface_status = WAYLAND_SURFACE_STATUS_SHOWN;
}
SDL_PropertiesID props = SDL_GetWindowProperties(window);
SDL_SetProperty(props, SDL_PROPERTY_WINDOW_WAYLAND_DISPLAY_POINTER, data->waylandData->display);
@ -2259,12 +2280,20 @@ void Wayland_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_WindowData *wind = window->driverdata;
/* Queue an event to send the window size. */
struct wl_callback *cb = wl_display_sync(_this->driverdata->display);
if (wind->shell_surface_type != WAYLAND_SURFACE_CUSTOM) {
/* Queue an event to send the window size. */
struct wl_callback *cb = wl_display_sync(_this->driverdata->display);
wind->pending_size_event.width = window->floating.w;
wind->pending_size_event.height = window->floating.h;
wl_callback_add_listener(cb, &size_event_listener, (void*)((uintptr_t)window->id));
wind->pending_size_event.width = window->floating.w;
wind->pending_size_event.height = window->floating.h;
wl_callback_add_listener(cb, &size_event_listener, (void *)((uintptr_t)window->id));
} else {
/* We are being informed of a size change on a custom surface, just configure. */
wind->requested_window_width = window->floating.w;
wind->requested_window_height = window->floating.h;
ConfigureWindowGeometry(window);
}
}
void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h)

View File

@ -70,7 +70,8 @@ struct SDL_WindowData
WAYLAND_SURFACE_UNKNOWN = 0,
WAYLAND_SURFACE_XDG_TOPLEVEL,
WAYLAND_SURFACE_XDG_POPUP,
WAYLAND_SURFACE_LIBDECOR
WAYLAND_SURFACE_LIBDECOR,
WAYLAND_SURFACE_CUSTOM
} shell_surface_type;
enum
{

View File

@ -398,6 +398,15 @@ add_sdl_test_executable(testvulkan NO_C90 SOURCES testvulkan.c)
add_sdl_test_executable(testoffscreen SOURCES testoffscreen.c)
add_sdl_test_executable(testpopup SOURCES testpopup.c)
if (HAVE_WAYLAND)
# Set the GENERATED property on the protocol file, since it is first created at build time
set_property(SOURCE ${SDL3_BINARY_DIR}/wayland-generated-protocols/xdg-shell-protocol.c PROPERTY GENERATED 1)
add_sdl_test_executable(testwaylandcustom NO_C90 NEEDS_RESOURCES SOURCES testwaylandcustom.c ${SDL3_BINARY_DIR}/wayland-generated-protocols/xdg-shell-protocol.c)
# Needed to silence the documentation warning in the generated header file
target_compile_options(testwaylandcustom PRIVATE -Wno-documentation-unknown-command)
target_link_libraries(testwaylandcustom PRIVATE wayland-client)
endif()
check_c_compiler_flag(-Wformat-overflow HAVE_WFORMAT_OVERFLOW)
if(HAVE_WFORMAT_OVERFLOW)
target_compile_definitions(testautomation PRIVATE HAVE_WFORMAT_OVERFLOW)

333
test/testwaylandcustom.c Normal file
View File

@ -0,0 +1,333 @@
/*
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely.
*/
#include <stdlib.h>
#include <time.h>
#include <SDL3/SDL.h>
#include <wayland-client.h>
#include <xdg-shell-client-protocol.h>
#include "icon.h"
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#define NUM_SPRITES 100
#define MAX_SPEED 1
static SDL_Window *window;
static SDL_Renderer *renderer;
static SDL_Texture *sprite;
static SDL_FRect positions[NUM_SPRITES];
static SDL_FRect velocities[NUM_SPRITES];
static int sprite_w, sprite_h;
static int done;
static SDL_Texture *CreateTexture(SDL_Renderer *r, unsigned char *data, unsigned int len, int *w, int *h)
{
SDL_Texture *texture = NULL;
SDL_Surface *surface;
SDL_RWops *src = SDL_RWFromConstMem(data, len);
if (src) {
surface = SDL_LoadBMP_RW(src, SDL_TRUE);
if (surface) {
/* Treat white as transparent */
SDL_SetSurfaceColorKey(surface, SDL_TRUE, SDL_MapRGB(surface->format, 255, 255, 255));
texture = SDL_CreateTextureFromSurface(r, surface);
*w = surface->w;
*h = surface->h;
SDL_DestroySurface(surface);
}
}
return texture;
}
static void MoveSprites(void)
{
int i;
int window_w;
int window_h;
SDL_FRect *position, *velocity;
/* Get the window size */
SDL_GetWindowSizeInPixels(window, &window_w, &window_h);
/* Draw a gray background */
SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF);
SDL_RenderClear(renderer);
/* Move the sprite, bounce at the wall, and draw */
for (i = 0; i < NUM_SPRITES; ++i) {
position = &positions[i];
velocity = &velocities[i];
position->x += velocity->x;
if ((position->x < 0) || (position->x >= (window_w - sprite_w))) {
velocity->x = -velocity->x;
position->x += velocity->x;
}
position->y += velocity->y;
if ((position->y < 0) || (position->y >= (window_h - sprite_h))) {
velocity->y = -velocity->y;
position->y += velocity->y;
}
/* Blit the sprite onto the screen */
SDL_RenderTexture(renderer, sprite, NULL, position);
}
/* Update the screen! */
SDL_RenderPresent(renderer);
}
static int InitSprites(void)
{
/* Create the sprite texture and initialize the sprite positions */
sprite = CreateTexture(renderer, icon_bmp, icon_bmp_len, &sprite_w, &sprite_h);
if (!sprite) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create sprite texture");
return -1;
}
srand((unsigned int)time(NULL));
for (int i = 0; i < NUM_SPRITES; ++i) {
positions[i].x = (float)(rand() % (WINDOW_WIDTH - sprite_w));
positions[i].y = (float)(rand() % (WINDOW_HEIGHT - sprite_h));
positions[i].w = (float)sprite_w;
positions[i].h = (float)sprite_h;
velocities[i].x = 0.0f;
velocities[i].y = 0.0f;
while (!velocities[i].x && !velocities[i].y) {
velocities[i].x = (float)((rand() % (MAX_SPEED * 2 + 1)) - MAX_SPEED);
velocities[i].y = (float)((rand() % (MAX_SPEED * 2 + 1)) - MAX_SPEED);
}
}
return 0;
}
/* Encapsulated in a struct to silence shadow variable warnings */
static struct _state
{
/* These are owned by SDL and must not be destroyed! */
struct wl_display *wl_display;
struct wl_surface *wl_surface;
/* These are owned by the application and need to be cleaned up on exit. */
struct wl_registry *wl_registry;
struct xdg_wm_base *xdg_wm_base;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *xdg_toplevel;
} state;
static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
{
xdg_surface_ack_configure(state.xdg_surface, serial);
}
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = xdg_surface_configure,
};
static void xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states)
{
/* NOP */
}
static void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
{
done = 1;
}
static void xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height)
{
/* NOP */
}
static void xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities)
{
/* NOP */
}
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = xdg_toplevel_configure,
.close = xdg_toplevel_close,
.configure_bounds = xdg_toplevel_configure_bounds,
.wm_capabilities = xdg_toplevel_wm_capabilities
};
static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
{
xdg_wm_base_pong(state.xdg_wm_base, serial);
}
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
.ping = xdg_wm_base_ping,
};
static void registry_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version)
{
if (SDL_strcmp(interface, xdg_wm_base_interface.name) == 0) {
state.xdg_wm_base = wl_registry_bind(state.wl_registry, name, &xdg_wm_base_interface, 1);
xdg_wm_base_add_listener(state.xdg_wm_base, &xdg_wm_base_listener, NULL);
}
}
static void registry_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name)
{
/* NOP */
}
static const struct wl_registry_listener wl_registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove,
};
int main(int argc, char **argv)
{
int ret = -1;
SDL_PropertiesID props;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
return -1;
}
if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Video driver must be 'wayland', not '%s'", SDL_GetCurrentVideoDriver());
goto exit;
}
/* Create a window with the custom surface role property set. */
props = SDL_CreateProperties();
SDL_SetBooleanProperty(props, SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN, SDL_TRUE); /* Roleless surface */
SDL_SetBooleanProperty(props, SDL_PROPERTY_WINDOW_CREATE_OPENGL_BOOLEAN, SDL_TRUE); /* OpenGL enabled */
SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_WIDTH_NUMBER, WINDOW_WIDTH); /* Default width */
SDL_SetNumberProperty(props, SDL_PROPERTY_WINDOW_CREATE_HEIGHT_NUMBER, WINDOW_HEIGHT); /* Default height */
SDL_SetBooleanProperty(props, SDL_PROPERTY_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, SDL_TRUE); /* Handle DPI scaling internally */
SDL_SetStringProperty(props, SDL_PROPERTY_WINDOW_CREATE_TITLE_STRING, "Wayland custom surface role test"); /* Default title */
window = SDL_CreateWindowWithProperties(props);
if (!window) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Window creation failed");
goto exit;
}
/* Create the renderer */
renderer = SDL_CreateRenderer(window, NULL, 0);
if (!renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Renderer creation failed");
goto exit;
}
/* Get the display object and use it to create a registry object, which will enumerate the xdg_wm_base protocol. */
state.wl_display = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_WAYLAND_DISPLAY_POINTER, NULL);
state.wl_registry = wl_display_get_registry(state.wl_display);
wl_registry_add_listener(state.wl_registry, &wl_registry_listener, NULL);
/* Roundtrip to enumerate registry objects. */
wl_display_roundtrip(state.wl_display);
if (!state.xdg_wm_base) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "'xdg_wm_base' protocol not found!");
goto exit;
}
/* Get the wl_surface object from the SDL_Window, and create a toplevel window with it. */
state.wl_surface = SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROPERTY_WINDOW_WAYLAND_SURFACE_POINTER, NULL);
/* Create the xdg_surface from the wl_surface. */
state.xdg_surface = xdg_wm_base_get_xdg_surface(state.xdg_wm_base, state.wl_surface);
xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, NULL);
/* Create the xdg_toplevel from the xdg_surface. */
state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);
xdg_toplevel_add_listener(state.xdg_toplevel, &xdg_toplevel_listener, NULL);
xdg_toplevel_set_title(state.xdg_toplevel, SDL_GetWindowTitle(window));
/* Initialize the sprites. */
if (InitSprites() < 0) {
goto exit;
}
while (!done) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_KEY_DOWN) {
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
done = 1;
break;
case SDLK_EQUALS:
/* Ctrl+ enlarges the window */
if (event.key.keysym.mod & SDL_KMOD_CTRL) {
int w, h;
SDL_GetWindowSize(window, &w, &h);
SDL_SetWindowSize(window, w * 2, h * 2);
}
break;
case SDLK_MINUS:
/* Ctrl- shrinks the window */
if (event.key.keysym.mod & SDL_KMOD_CTRL) {
int w, h;
SDL_GetWindowSize(window, &w, &h);
SDL_SetWindowSize(window, w / 2, h / 2);
}
break;
default:
break;
}
}
}
/* Draw the sprites */
MoveSprites();
}
ret = 0;
exit:
/* The display and surface handles obtained from SDL are owned by SDL and must *NOT* be destroyed here! */
if (state.xdg_toplevel) {
xdg_toplevel_destroy(state.xdg_toplevel);
state.xdg_toplevel = NULL;
}
if (state.xdg_surface) {
xdg_surface_destroy(state.xdg_surface);
state.xdg_surface = NULL;
}
if (state.xdg_wm_base) {
xdg_wm_base_destroy(state.xdg_wm_base);
state.xdg_wm_base = NULL;
}
if (state.wl_registry) {
wl_registry_destroy(state.wl_registry);
state.wl_registry = NULL;
}
/* Destroy the SDL resources */
if (sprite) {
SDL_DestroyTexture(sprite);
sprite = NULL;
}
if (renderer) {
SDL_DestroyRenderer(renderer);
renderer = NULL;
}
if (window) {
SDL_DestroyWindow(window);
window = NULL;
}
SDL_Quit();
return ret;
}