diff --git a/docs/README-wayland.md b/docs/README-wayland.md index a376b48ad..ffbadced1 100644 --- a/docs/README-wayland.md +++ b/docs/README-wayland.md @@ -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. \ No newline at end of file diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 0eefdec6d..7c223fe40 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -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. diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 92ceb3ec1..ddc585d2e 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -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) diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index f7f503717..96c7a0eab 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -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 { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 98fc28c95..7604e0e65 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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) diff --git a/test/testwaylandcustom.c b/test/testwaylandcustom.c new file mode 100644 index 000000000..3e1042bbb --- /dev/null +++ b/test/testwaylandcustom.c @@ -0,0 +1,333 @@ +/* + Copyright (C) 1997-2024 Sam Lantinga + + 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 +#include + +#include +#include +#include + +#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; +}