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.cmain
parent
4417250d0d
commit
f7dd0f9491
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue