wayland: Add the ability to import and wrap external surfaces

Add the ability to import and wrap external surfaces from external toolkits such as Qt and GTK.

Wayland surfaces and windows are more intrinsically tied to the client library than other windowing systems, so it is necessary to provide a way to initialize SDL with an existing wl_display object, which needs to be set prior to video system initialization, or export the internal SDL wl_display object for use by external applications or toolkits. For this, the global property SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER is used.

A Wayland example was added to testnative, and a basic example of Qt 6 interoperation is provided in the Wayland readme to demonstrate the use of external windows with both SDL owning the wl_display, and an external toolkit owning it.
This commit is contained in:
Frank Praznik
2024-01-09 11:38:38 -05:00
parent 99f6bcf504
commit 4f3d4bd110
11 changed files with 548 additions and 64 deletions

View File

@@ -539,18 +539,12 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
return;
}
/* check that this surface belongs to one of the SDL windows */
if (!SDL_WAYLAND_own_surface(surface)) {
return;
}
/* This handler will be called twice in Wayland 1.4
* Once for the window surface which has valid user data
* and again for the mouse cursor surface which does not have valid user data
* We ignore the later
*/
window = (SDL_WindowData *)wl_surface_get_user_data(surface);
window = Wayland_GetWindowDataForOwnedSurface(surface);
if (window) {
input->pointer_focus = window;
@@ -576,12 +570,12 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
{
struct SDL_WaylandInput *input = data;
if (!surface || !SDL_WAYLAND_own_surface(surface)) {
if (!surface) {
return;
}
if (input->pointer_focus) {
SDL_WindowData *wind = (SDL_WindowData *)wl_surface_get_user_data(surface);
SDL_WindowData *wind = Wayland_GetWindowDataForOwnedSurface(surface);
if (wind) {
/* Clear the capture flag and raise all buttons */
@@ -980,14 +974,14 @@ static void touch_handler_down(void *data, struct wl_touch *touch, uint32_t seri
struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)data;
SDL_WindowData *window_data;
/* Check that this surface belongs to one of the SDL windows */
if (!SDL_WAYLAND_own_surface(surface)) {
/* Check that this surface is valid. */
if (!surface) {
return;
}
touch_add(id, fx, fy, surface);
Wayland_UpdateImplicitGrabSerial(input, serial);
window_data = (SDL_WindowData *)wl_surface_get_user_data(surface);
window_data = Wayland_GetWindowDataForOwnedSurface(surface);
if (window_data) {
float x, y;
@@ -1430,19 +1424,18 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
return;
}
if (!SDL_WAYLAND_own_surface(surface)) {
window = Wayland_GetWindowDataForOwnedSurface(surface);
if (!window) {
return;
}
window = wl_surface_get_user_data(surface);
input->keyboard_focus = window;
window->keyboard_device = input;
if (window) {
input->keyboard_focus = window;
window->keyboard_device = input;
/* Restore the keyboard focus to the child popup that was holding it */
SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window->sdlwindow);
/* Restore the keyboard focus to the child popup that was holding it */
SDL_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window->sdlwindow);
}
#ifdef SDL_USE_IME
if (!input->text_input) {
SDL_IME_SetFocus(SDL_TRUE);
@@ -1479,17 +1472,19 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard,
SDL_WindowData *wind;
SDL_Window *window = NULL;
if (!surface || !SDL_WAYLAND_own_surface(surface)) {
if (!surface) {
return;
}
wind = wl_surface_get_user_data(surface);
if (wind) {
wind->keyboard_device = NULL;
window = wind->sdlwindow;
window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
wind = Wayland_GetWindowDataForOwnedSurface(surface);
if (!wind) {
return;
}
wind->keyboard_device = NULL;
window = wind->sdlwindow;
window->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
/* Stop key repeat before clearing keyboard focus */
keyboard_repeat_clear(&input->keyboard_repeat);
@@ -1935,11 +1930,9 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_data_
/* find the current window */
if (surface) {
if (SDL_WAYLAND_own_surface(surface)) {
SDL_WindowData *window = (SDL_WindowData *)wl_surface_get_user_data(surface);
if (window) {
data_device->dnd_window = window->sdlwindow;
}
SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
if (window) {
data_device->dnd_window = window->sdlwindow;
} else {
data_device->dnd_window = NULL;
}
@@ -2609,11 +2602,7 @@ static void tablet_tool_handle_proximity_in(void *data, struct zwp_tablet_tool_v
return;
}
if (!SDL_WAYLAND_own_surface(surface)) {
return;
}
window = (SDL_WindowData *)wl_surface_get_user_data(surface);
window = Wayland_GetWindowDataForOwnedSurface(surface);
if (window) {
input->tool_focus = window;

View File

@@ -106,12 +106,45 @@ SDL_bool SDL_WAYLAND_own_output(struct wl_output *output)
return wl_proxy_get_tag((struct wl_proxy *)output) == &SDL_WAYLAND_output_tag;
}
/* External surfaces may have their own user data attached, the modification of which
* can cause problems with external toolkits. Instead, external windows are kept in
* their own list, and a search is conducted to find a matching surface.
*/
static struct wl_list external_window_list;
void Wayland_AddWindowDataToExternalList(SDL_WindowData *data)
{
WAYLAND_wl_list_insert(&external_window_list, &data->external_window_list_link);
}
void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data)
{
WAYLAND_wl_list_remove(&data->external_window_list_link);
}
SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface)
{
if (SDL_WAYLAND_own_surface(surface)) {
return (SDL_WindowData *)wl_surface_get_user_data(surface);
} else if (!WAYLAND_wl_list_empty(&external_window_list)) {
SDL_WindowData *p;
wl_list_for_each (p, &external_window_list, external_window_list_link) {
if (p->surface == surface) {
return p;
}
}
}
return NULL;
}
static void Wayland_DeleteDevice(SDL_VideoDevice *device)
{
SDL_VideoData *data = device->driverdata;
if (data->display) {
if (data->display && !data->display_externally_owned) {
WAYLAND_wl_display_flush(data->display);
WAYLAND_wl_display_disconnect(data->display);
SDL_ClearProperty(SDL_GetGlobalProperties(), SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER);
}
if (device->wakeup_lock) {
SDL_DestroyMutex(device->wakeup_lock);
@@ -125,7 +158,9 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
{
SDL_VideoDevice *device;
SDL_VideoData *data;
struct wl_display *display;
struct wl_display *display = SDL_GetProperty(SDL_GetGlobalProperties(),
SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, NULL);
SDL_bool display_is_external = !!display;
/* Are we trying to connect to or are currently in a Wayland session? */
if (!SDL_getenv("WAYLAND_DISPLAY")) {
@@ -139,10 +174,12 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
return NULL;
}
display = WAYLAND_wl_display_connect(NULL);
if (!display) {
SDL_WAYLAND_UnloadSymbols();
return NULL;
display = WAYLAND_wl_display_connect(NULL);
if (!display) {
SDL_WAYLAND_UnloadSymbols();
return NULL;
}
}
data = SDL_calloc(1, sizeof(*data));
@@ -154,7 +191,9 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
data->initializing = SDL_TRUE;
data->display = display;
data->display_externally_owned = display_is_external;
WAYLAND_wl_list_init(&data->output_list);
WAYLAND_wl_list_init(&external_window_list);
/* Initialize all variables that we clean on shutdown */
device = SDL_calloc(1, sizeof(SDL_VideoDevice));
@@ -165,6 +204,11 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
return NULL;
}
if (!display_is_external) {
SDL_SetProperty(SDL_GetGlobalProperties(),
SDL_PROPERTY_GLOBAL_VIDEO_WAYLAND_WL_DISPLAY_POINTER, display);
}
device->driverdata = data;
device->wakeup_lock = SDL_CreateMutex();

View File

@@ -79,6 +79,7 @@ struct SDL_VideoData
struct wl_list output_list;
int relative_mouse_mode;
SDL_bool display_externally_owned;
};
struct SDL_DisplayData
@@ -107,6 +108,10 @@ extern void SDL_WAYLAND_register_output(struct wl_output *output);
extern SDL_bool SDL_WAYLAND_own_surface(struct wl_surface *surface);
extern SDL_bool SDL_WAYLAND_own_output(struct wl_output *output);
extern SDL_WindowData *Wayland_GetWindowDataForOwnedSurface(struct wl_surface *surface);
void Wayland_AddWindowDataToExternalList(SDL_WindowData *data);
void Wayland_RemoveWindowDataFromExternalList(SDL_WindowData *data);
extern SDL_bool Wayland_LoadLibdecor(SDL_VideoData *data, SDL_bool ignore_xdg);
extern SDL_bool Wayland_VideoReconnect(SDL_VideoDevice *_this);

View File

@@ -2057,10 +2057,13 @@ 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);
struct wl_surface *external_surface = (struct wl_surface *)SDL_GetProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_WAYLAND_WL_SURFACE_POINTER,
(struct wl_surface *)SDL_GetProperty(create_props, "sdl2-compat.external_window", NULL));
const SDL_bool custom_surface_role = (external_surface != NULL) || SDL_GetBooleanProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_WAYLAND_SURFACE_ROLE_CUSTOM_BOOLEAN, SDL_FALSE);
const SDL_bool create_egl_window = !!(window->flags & SDL_WINDOW_OPENGL) ||
SDL_GetBooleanProperty(create_props, SDL_PROPERTY_WINDOW_CREATE_WAYLAND_CREATE_EGL_WINDOW_BOOLEAN, SDL_FALSE);
data = SDL_calloc(1, sizeof(*data));
if (!data) {
return -1;
@@ -2102,10 +2105,20 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
data->requested_window_width = window->w;
data->requested_window_height = window->h;
data->surface = wl_compositor_create_surface(c->compositor);
wl_surface_add_listener(data->surface, &surface_listener, data);
if (!external_surface) {
data->surface = wl_compositor_create_surface(c->compositor);
wl_surface_add_listener(data->surface, &surface_listener, data);
wl_surface_set_user_data(data->surface, data);
SDL_WAYLAND_register_surface(data->surface);
} else {
window->flags |= SDL_WINDOW_EXTERNAL;
data->surface = external_surface;
SDL_WAYLAND_register_surface(data->surface);
/* External surfaces are registered by being put in a list, as changing tags or userdata
* can cause problems with external toolkits.
*/
Wayland_AddWindowDataToExternalList(data);
}
/* Must be called before EGL configuration to set the drawable backbuffer size. */
ConfigureWindowGeometry(window);
@@ -2123,9 +2136,12 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
wl_callback_add_listener(data->gles_swap_frame_callback, &gles_swap_frame_listener, data);
}
/* Fire a callback when the compositor wants a new frame to set the surface damage region. */
data->surface_frame_callback = wl_surface_frame(data->surface);
wl_callback_add_listener(data->surface_frame_callback, &surface_frame_listener, data);
/* No frame callback on external surfaces as it may already have one attached. */
if (!external_surface) {
/* Fire a callback when the compositor wants a new frame to set the surface damage region. */
data->surface_frame_callback = wl_surface_frame(data->surface);
wl_callback_add_listener(data->surface_frame_callback, &surface_frame_listener, data);
}
if (window->flags & SDL_WINDOW_TRANSPARENT) {
if (_this->gl_config.alpha_size == 0) {
@@ -2152,7 +2168,13 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
Wayland_input_lock_pointer(c->input);
}
if (c->fractional_scale_manager) {
/* Don't attach a fractional scale manager to surfaces unless they are
* flagged as DPI-aware. Under non-scaled operation, the scale will
* always be 1.0, and external/custom surfaces may already have, or
* will try to attach, their own fractional scale manager, which will
* result in a protocol violation.
*/
if (c->fractional_scale_manager && (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)) {
data->fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(c->fractional_scale_manager, data->surface);
wp_fractional_scale_v1_add_listener(data->fractional_scale,
&fractional_scale_listener, data);
@@ -2175,7 +2197,7 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert
}
} /* All other cases will be WAYLAND_SURFACE_UNKNOWN */
} else {
/* Roleless surfaces are always considered to be in the shown state by the backend. */
/* Roleless and external 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;
}
@@ -2433,7 +2455,11 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
wl_callback_destroy(wind->surface_frame_callback);
}
wl_surface_destroy(wind->surface);
if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
wl_surface_destroy(wind->surface);
} else {
Wayland_RemoveWindowDataFromExternalList(wind);
}
SDL_free(wind);
WAYLAND_wl_display_flush(data->display);

View File

@@ -130,6 +130,8 @@ struct SDL_WindowData
SDL_bool show_hide_sync_required;
SDL_HitTestResult hit_test_result;
struct wl_list external_window_list_link;
};
extern void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window);