wayland: client-side decoration

This commit is contained in:
Christian Rauch 2020-06-11 22:10:28 +01:00 committed by Sam Lantinga
parent f4f9c6bc55
commit 9e6fcbe72c
13 changed files with 499 additions and 7 deletions

View file

@ -39,6 +39,20 @@
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "xdg-activation-v1-client-protocol.h"
#ifdef HAVE_LIBDECOR_H
#include <libdecor.h>
#endif
static const char *SDL_WAYLAND_surface_tag = "sdl-window";
SDL_bool SDL_WAYLAND_own_surface(struct wl_surface *surface)
{
if (SDL_WAYLAND_HAVE_WAYLAND_CLIENT_1_18) {
return wl_proxy_get_tag((struct wl_proxy *) surface) == &SDL_WAYLAND_surface_tag;
}
return SDL_TRUE; /* For older clients we have to assume this is us... */
}
static float get_window_scale_factor(SDL_Window *window) {
return ((SDL_WindowData*)window->driverdata)->scale_factor;
}
@ -67,6 +81,19 @@ CommitMinMaxDimensions(SDL_Window *window)
max_height = window->windowed.h;
}
#ifdef HAVE_LIBDECOR_H
if (data->shell.libdecor) {
if (wind->shell_surface.libdecor.frame == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
}
libdecor_frame_set_min_content_size(wind->shell_surface.libdecor.frame,
min_width,
min_height);
libdecor_frame_set_max_content_size(wind->shell_surface.libdecor.frame,
max_width,
max_height);
} else
#endif
if (data->shell.xdg) {
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
@ -77,6 +104,7 @@ CommitMinMaxDimensions(SDL_Window *window)
xdg_toplevel_set_max_size(wind->shell_surface.xdg.roleobj.toplevel,
max_width,
max_height);
wl_surface_commit(wind->surface);
} else if (data->shell.zxdg) {
if (wind->shell_surface.zxdg.roleobj.toplevel == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
@ -87,9 +115,8 @@ CommitMinMaxDimensions(SDL_Window *window)
zxdg_toplevel_v6_set_max_size(wind->shell_surface.zxdg.roleobj.toplevel,
max_width,
max_height);
wl_surface_commit(wind->surface);
}
wl_surface_commit(wind->surface);
}
static void
@ -103,6 +130,18 @@ SetFullscreen(SDL_Window *window, struct wl_output *output)
*/
CommitMinMaxDimensions(window);
#ifdef HAVE_LIBDECOR_H
if (viddata->shell.libdecor) {
if (wind->shell_surface.libdecor.frame == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
}
if (output) {
libdecor_frame_set_fullscreen(wind->shell_surface.libdecor.frame, output);
} else {
libdecor_frame_unset_fullscreen(wind->shell_surface.libdecor.frame);
}
} else
#endif
if (viddata->shell.xdg) {
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
@ -481,8 +520,97 @@ static const struct xdg_toplevel_listener toplevel_listener_xdg = {
handle_close_xdg_toplevel
};
#ifdef HAVE_LIBDECOR_H
static void
decoration_frame_configure(struct libdecor_frame *frame,
struct libdecor_configuration *configuration,
void *user_data)
{
SDL_WindowData *wind = user_data;
SDL_Window *window = wind->sdlwindow;
int width, height;
enum libdecor_window_state window_state;
struct libdecor_state *state;
/* window size */
if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
width = window->w;
height = window->h;
}
wind->resize.width = width;
wind->resize.height = height;
wind->resize.pending = SDL_TRUE;
wind->resize.configure = SDL_TRUE;
Wayland_HandlePendingResize(window);
wind->shell_surface.libdecor.initial_configure_seen = SDL_TRUE;
window->w = wind->resize.width;
window->h = wind->resize.height;
/* window state */
if (!libdecor_configuration_get_window_state(configuration, &window_state)) {
window_state = LIBDECOR_WINDOW_STATE_NONE;
}
if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {
window->flags |= SDL_WINDOW_MAXIMIZED;
}
else {
window->flags &= ~SDL_WINDOW_MAXIMIZED;
}
if (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) {
window->flags |= SDL_WINDOW_INPUT_FOCUS;
}
else {
window->flags &= ~SDL_WINDOW_INPUT_FOCUS;
}
/* The fullscreen flag is already set my some other entity when 'SDL_SetWindowFullscreen'
* is called, but we will set it here again in case the compositor requests fullscreen.
*/
if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) {
window->flags |= SDL_WINDOW_FULLSCREEN;
}
else {
window->flags &= ~SDL_WINDOW_FULLSCREEN;
}
/* commit frame state */
state = libdecor_state_new(width, height);
libdecor_frame_commit(frame, state, configuration);
libdecor_state_free(state);
/* Update the resize capability. Since this will change the capabilities and
* commit a new frame state with the last known content dimension, this has
* to be called after the new state has been commited and the new content
* dimensions were updated. */
Wayland_SetWindowResizable(SDL_GetVideoDevice(), window,
window->flags & SDL_WINDOW_RESIZABLE);
}
static void
decoration_frame_close(struct libdecor_frame *frame, void *user_data)
{
SDL_SendWindowEvent(((SDL_WindowData *)user_data)->sdlwindow, SDL_WINDOWEVENT_CLOSE, 0, 0);
}
static void
decoration_frame_commit(struct libdecor_frame *frame, void *user_data)
{
SDL_WindowData *wind = user_data;
SDL_SendWindowEvent(wind->sdlwindow, SDL_WINDOWEVENT_EXPOSED, 0, 0);
}
static struct libdecor_frame_interface libdecor_frame_interface = {
decoration_frame_configure,
decoration_frame_close,
decoration_frame_commit,
};
#endif
#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
static void
@ -706,6 +834,20 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
wl_surface_commit(data->surface);
/* Create the shell surface and map the toplevel */
#ifdef HAVE_LIBDECOR_H
if (c->shell.libdecor) {
data->shell_surface.libdecor.frame = libdecor_decorate(c->shell.libdecor,
data->surface,
&libdecor_frame_interface,
data);
if (data->shell_surface.libdecor.frame == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Failed to create libdecor frame!");
} else {
libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, c->classname);
libdecor_frame_map(data->shell_surface.libdecor.frame);
}
} else
#endif
if (c->shell.xdg) {
data->shell_surface.xdg.surface = xdg_wm_base_get_xdg_surface(c->shell.xdg, data->surface);
xdg_surface_set_user_data(data->shell_surface.xdg.surface, data);
@ -744,6 +886,16 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
/* We have to wait until the surface gets a "configure" event, or use of
* this surface will fail. This is a new rule for xdg_shell.
*/
#ifdef HAVE_LIBDECOR_H
if (c->shell.libdecor) {
if (data->shell_surface.libdecor.frame) {
while (!data->shell_surface.libdecor.initial_configure_seen) {
WAYLAND_wl_display_flush(c->display);
WAYLAND_wl_display_dispatch(c->display);
}
}
} else
#endif
if (c->shell.xdg) {
if (data->shell_surface.xdg.surface) {
while (!data->shell_surface.xdg.initial_configure_seen) {
@ -801,6 +953,14 @@ void Wayland_HideWindow(_THIS, SDL_Window *window)
wind->server_decoration = NULL;
}
#ifdef HAVE_LIBDECOR_H
if (data->shell.libdecor) {
if (wind->shell_surface.libdecor.frame) {
libdecor_frame_unref(wind->shell_surface.libdecor.frame);
wind->shell_surface.libdecor.frame = NULL;
}
} else
#endif
if (data->shell.xdg) {
if (wind->shell_surface.xdg.roleobj.toplevel) {
xdg_toplevel_destroy(wind->shell_surface.xdg.roleobj.toplevel);
@ -1027,6 +1187,14 @@ Wayland_RestoreWindow(_THIS, SDL_Window * window)
*/
window->flags &= ~SDL_WINDOW_MAXIMIZED;
#ifdef HAVE_LIBDECOR_H
if (viddata->shell.libdecor) {
if (wind->shell_surface.libdecor.frame == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
}
libdecor_frame_unset_maximized(wind->shell_surface.libdecor.frame);
} else
#endif
/* Note that xdg-shell does NOT provide a way to unset minimize! */
if (viddata->shell.xdg) {
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
@ -1053,6 +1221,11 @@ Wayland_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered)
{
SDL_WindowData *wind = window->driverdata;
const SDL_VideoData *viddata = (const SDL_VideoData *) _this->driverdata;
#ifdef HAVE_LIBDECOR_H
if (viddata->shell.libdecor) {
SDL_SetError("FIXME libdecor: Implement toggling decorations");
} else
#endif
if ((viddata->decoration_manager) && (wind->server_decoration)) {
const enum zxdg_toplevel_decoration_v1_mode mode = bordered ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
zxdg_toplevel_decoration_v1_set_mode(wind->server_decoration, mode);
@ -1062,7 +1235,24 @@ Wayland_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered)
void
Wayland_SetWindowResizable(_THIS, SDL_Window * window, SDL_bool resizable)
{
CommitMinMaxDimensions(window);
#ifdef HAVE_LIBDECOR_H
SDL_VideoData *data = _this->driverdata;
const SDL_WindowData *wind = window->driverdata;
if (data->shell.libdecor) {
if (wind->shell_surface.libdecor.frame == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
}
if (resizable) {
libdecor_frame_set_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE);
} else {
libdecor_frame_unset_capabilities(wind->shell_surface.libdecor.frame, LIBDECOR_ACTION_RESIZE);
}
} else
#endif
{
CommitMinMaxDimensions(window);
}
}
void
@ -1080,6 +1270,14 @@ Wayland_MaximizeWindow(_THIS, SDL_Window * window)
*/
window->flags |= SDL_WINDOW_MAXIMIZED;
#ifdef HAVE_LIBDECOR_H
if (viddata->shell.libdecor) {
if (wind->shell_surface.libdecor.frame == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
}
libdecor_frame_set_maximized(wind->shell_surface.libdecor.frame);
} else
#endif
if (viddata->shell.xdg) {
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
@ -1106,6 +1304,14 @@ Wayland_MinimizeWindow(_THIS, SDL_Window * window)
SDL_WindowData *wind = window->driverdata;
SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata;
#ifdef HAVE_LIBDECOR_H
if (viddata->shell.libdecor) {
if (wind->shell_surface.libdecor.frame == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
}
libdecor_frame_set_minimized(wind->shell_surface.libdecor.frame);
} else
#endif
if (viddata->shell.xdg) {
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
@ -1199,6 +1405,10 @@ int Wayland_CreateWindow(_THIS, SDL_Window *window)
wl_compositor_create_surface(c->compositor);
wl_surface_add_listener(data->surface, &surface_listener, data);
if (SDL_WAYLAND_HAVE_WAYLAND_CLIENT_1_18) {
wl_proxy_set_tag((struct wl_proxy *)data->surface, &SDL_WAYLAND_surface_tag);
}
/* Fire a callback when the compositor wants a new frame rendered.
* Right now this only matters for OpenGL; we use this callback to add a
* wait timeout that avoids getting deadlocked by the compositor when the
@ -1278,6 +1488,11 @@ Wayland_HandlePendingResize(SDL_Window *window)
}
if (data->resize.configure) {
#ifdef HAVE_LIBDECOR_H
if (data->waylandData->shell.libdecor) {
/* this has already been acknowledged in the the frames's 'configure' callback */
} else
#endif
if (data->waylandData->shell.xdg) {
xdg_surface_ack_configure(data->shell_surface.xdg.surface, data->resize.serial);
} else if (data->waylandData->shell.zxdg) {
@ -1312,6 +1527,9 @@ void Wayland_SetWindowSize(_THIS, SDL_Window * window)
SDL_VideoData *data = _this->driverdata;
SDL_WindowData *wind = window->driverdata;
struct wl_region *region;
#ifdef HAVE_LIBDECOR_H
struct libdecor_state *state;
#endif
wl_surface_set_buffer_scale(wind->surface, get_window_scale_factor(window));
@ -1319,6 +1537,14 @@ void Wayland_SetWindowSize(_THIS, SDL_Window * window)
WAYLAND_wl_egl_window_resize(wind->egl_window, window->w * get_window_scale_factor(window), window->h * get_window_scale_factor(window), 0, 0);
}
#ifdef HAVE_LIBDECOR_H
if (data->shell.libdecor && wind->shell_surface.libdecor.frame) {
state = libdecor_state_new(window->w, window->h);
libdecor_frame_commit(wind->shell_surface.libdecor.frame, state, NULL);
libdecor_state_free(state);
}
#endif
region = wl_compositor_create_region(data->compositor);
wl_region_add(region, 0, 0, window->w, window->h);
wl_surface_set_opaque_region(wind->surface, region);
@ -1331,6 +1557,14 @@ void Wayland_SetWindowTitle(_THIS, SDL_Window * window)
SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata;
if (window->title != NULL) {
#ifdef HAVE_LIBDECOR_H
if (viddata->shell.libdecor) {
if (wind->shell_surface.libdecor.frame == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */
}
libdecor_frame_set_title(wind->shell_surface.libdecor.frame, window->title);
} else
#endif
if (viddata->shell.xdg) {
if (wind->shell_surface.xdg.roleobj.toplevel == NULL) {
return; /* Can't do anything yet, wait for ShowWindow */