Wayland: Add an alternate surface role using xdg-shell

This protocol matches desktops much better than the deprecated
wl_shell, fixing a bunch of race conditions, removing undefined
behaviour, adding missing features, and generally providing a much more
user-friendly experience.

Since most compositors don’t support it yet, the wl_shell_surface role
is kept as fallback for now.
This commit is contained in:
Emmanuel Gil Peyrot 2017-12-03 04:08:54 +01:00 committed by linkmauve
parent 14856e8b60
commit ae44a28125

View File

@ -284,6 +284,141 @@ static GLFWbool createShellSurface(_GLFWwindow* window)
return GLFW_TRUE; return GLFW_TRUE;
} }
static void xdgToplevelHandleConfigure(void* data,
struct xdg_toplevel* toplevel,
int32_t width,
int32_t height,
struct wl_array* states)
{
_GLFWwindow* window = data;
float aspectRatio;
float targetRatio;
uint32_t* state;
GLFWbool maximized = GLFW_FALSE;
GLFWbool fullscreen = GLFW_FALSE;
GLFWbool resizing = GLFW_FALSE;
GLFWbool activated = GLFW_FALSE;
wl_array_for_each(state, states)
{
switch (*state)
{
case XDG_TOPLEVEL_STATE_MAXIMIZED:
maximized = GLFW_TRUE;
break;
case XDG_TOPLEVEL_STATE_FULLSCREEN:
fullscreen = GLFW_TRUE;
break;
case XDG_TOPLEVEL_STATE_RESIZING:
resizing = GLFW_TRUE;
break;
case XDG_TOPLEVEL_STATE_ACTIVATED:
activated = GLFW_TRUE;
break;
}
}
if (!maximized && !fullscreen)
{
if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE)
{
aspectRatio = (float)width / (float)height;
targetRatio = (float)window->numer / (float)window->denom;
if (aspectRatio < targetRatio)
height = width / targetRatio;
else if (aspectRatio > targetRatio)
width = height * targetRatio;
}
}
_glfwInputWindowSize(window, width, height);
_glfwPlatformSetWindowSize(window, width, height);
_glfwInputWindowDamage(window);
if (!activated && window->autoIconify)
_glfwPlatformIconifyWindow(window);
_glfwInputWindowFocus(window, activated);
}
static void xdgToplevelHandleClose(void* data,
struct xdg_toplevel* toplevel)
{
_GLFWwindow* window = data;
_glfwInputWindowCloseRequest(window);
}
static const struct xdg_toplevel_listener xdgToplevelListener = {
xdgToplevelHandleConfigure,
xdgToplevelHandleClose
};
static void xdgSurfaceHandleConfigure(void* data,
struct xdg_surface* surface,
uint32_t serial)
{
xdg_surface_ack_configure(surface, serial);
}
static const struct xdg_surface_listener xdgSurfaceListener = {
xdgSurfaceHandleConfigure
};
static GLFWbool createXdgSurface(_GLFWwindow* window)
{
window->wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
window->wl.surface);
if (!window->wl.xdg.surface)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: xdg-surface creation failed");
return GLFW_FALSE;
}
xdg_surface_add_listener(window->wl.xdg.surface,
&xdgSurfaceListener,
window);
window->wl.xdg.toplevel = xdg_surface_get_toplevel(window->wl.xdg.surface);
if (!window->wl.xdg.toplevel)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: xdg-toplevel creation failed");
return GLFW_FALSE;
}
xdg_toplevel_add_listener(window->wl.xdg.toplevel,
&xdgToplevelListener,
window);
if (window->wl.title)
xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title);
xdg_toplevel_set_min_size(window->wl.xdg.toplevel,
window->minwidth, window->minheight);
xdg_toplevel_set_max_size(window->wl.xdg.toplevel,
window->maxwidth, window->maxheight);
if (window->monitor)
{
xdg_toplevel_set_fullscreen(window->wl.xdg.toplevel,
window->monitor->wl.output);
setIdleInhibitor(window, GLFW_TRUE);
}
else if (window->wl.maximized)
{
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
setIdleInhibitor(window, GLFW_FALSE);
}
else
{
setIdleInhibitor(window, GLFW_FALSE);
}
wl_surface_commit(window->wl.surface);
return GLFW_TRUE;
}
static int static int
createTmpfileCloexec(char* tmpname) createTmpfileCloexec(char* tmpname)
{ {
@ -449,13 +584,23 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
if (wndconfig->visible) if (wndconfig->visible)
{ {
if (!createShellSurface(window)) if (_glfw.wl.wmBase)
return GLFW_FALSE; {
if (!createXdgSurface(window))
return GLFW_FALSE;
}
else
{
if (!createShellSurface(window))
return GLFW_FALSE;
}
window->wl.visible = GLFW_TRUE; window->wl.visible = GLFW_TRUE;
} }
else else
{ {
window->wl.xdg.surface = NULL;
window->wl.xdg.toplevel = NULL;
window->wl.shellSurface = NULL; window->wl.shellSurface = NULL;
window->wl.visible = GLFW_FALSE; window->wl.visible = GLFW_FALSE;
} }
@ -494,6 +639,12 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
if (window->wl.shellSurface) if (window->wl.shellSurface)
wl_shell_surface_destroy(window->wl.shellSurface); wl_shell_surface_destroy(window->wl.shellSurface);
if (window->wl.xdg.toplevel)
xdg_toplevel_destroy(window->wl.xdg.toplevel);
if (window->wl.xdg.surface)
xdg_surface_destroy(window->wl.xdg.surface);
if (window->wl.surface) if (window->wl.surface)
wl_surface_destroy(window->wl.surface); wl_surface_destroy(window->wl.surface);
@ -506,7 +657,9 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
if (window->wl.title) if (window->wl.title)
free(window->wl.title); free(window->wl.title);
window->wl.title = _glfw_strdup(title); window->wl.title = _glfw_strdup(title);
if (window->wl.shellSurface) if (window->wl.xdg.toplevel)
xdg_toplevel_set_title(window->wl.xdg.toplevel, title);
else if (window->wl.shellSurface)
wl_shell_surface_set_title(window->wl.shellSurface, title); wl_shell_surface_set_title(window->wl.shellSurface, title);
} }
@ -558,8 +711,24 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
int minwidth, int minheight, int minwidth, int minheight,
int maxwidth, int maxheight) int maxwidth, int maxheight)
{ {
// TODO: find out how to trigger a resize. if (_glfw.wl.wmBase)
// The actual limits are checked in the wl_shell_surface::configure handler. {
if (window->wl.xdg.toplevel)
{
if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE)
minwidth = minheight = 0;
if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE)
maxwidth = maxheight = 0;
xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight);
xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight);
wl_surface_commit(window->wl.surface);
}
}
else
{
// TODO: find out how to trigger a resize.
// The actual limits are checked in the wl_shell_surface::configure handler.
}
} }
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
@ -594,54 +763,76 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
void _glfwPlatformIconifyWindow(_GLFWwindow* window) void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{ {
// TODO: move to xdg_shell instead of wl_shell. if (_glfw.wl.wmBase)
_glfwInputError(GLFW_PLATFORM_ERROR, {
"Wayland: Iconify window not supported"); if (window->wl.xdg.toplevel)
xdg_toplevel_set_minimized(window->wl.xdg.toplevel);
}
else
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Iconify window not supported on wl_shell");
}
} }
void _glfwPlatformRestoreWindow(_GLFWwindow* window) void _glfwPlatformRestoreWindow(_GLFWwindow* window)
{ {
// TODO: also do the same for iconified. if (window->wl.xdg.toplevel)
if (window->monitor || window->wl.maximized)
{ {
if (window->wl.shellSurface) if (window->monitor)
wl_shell_surface_set_toplevel(window->wl.shellSurface); xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
if (window->wl.maximized)
window->wl.maximized = GLFW_FALSE; xdg_toplevel_unset_maximized(window->wl.xdg.toplevel);
// There is no way to unset minimized, or even to know if we are
// minimized, so there is nothing to do here.
} }
else if (window->wl.shellSurface)
{
if (window->monitor || window->wl.maximized)
wl_shell_surface_set_toplevel(window->wl.shellSurface);
}
_glfwInputWindowMonitor(window, NULL);
window->wl.maximized = GLFW_FALSE;
} }
void _glfwPlatformMaximizeWindow(_GLFWwindow* window) void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
{ {
if (!window->monitor && !window->wl.maximized) if (window->wl.xdg.toplevel)
{ {
if (window->wl.shellSurface) xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
{
// Let the compositor select the best output.
wl_shell_surface_set_maximized(window->wl.shellSurface, NULL);
}
window->wl.maximized = GLFW_TRUE;
} }
else if (window->wl.shellSurface)
{
// Let the compositor select the best output.
wl_shell_surface_set_maximized(window->wl.shellSurface, NULL);
}
window->wl.maximized = GLFW_TRUE;
} }
void _glfwPlatformShowWindow(_GLFWwindow* window) void _glfwPlatformShowWindow(_GLFWwindow* window)
{ {
if (!window->monitor) if (_glfw.wl.wmBase && !window->wl.xdg.toplevel)
{ createXdgSurface(window);
if (!window->wl.shellSurface) else if (!window->wl.shellSurface)
createShellSurface(window); createShellSurface(window);
window->wl.visible = GLFW_TRUE; window->wl.visible = GLFW_TRUE;
}
} }
void _glfwPlatformHideWindow(_GLFWwindow* window) void _glfwPlatformHideWindow(_GLFWwindow* window)
{ {
if (!window->monitor) if (window->wl.xdg.toplevel)
{ {
if (window->wl.shellSurface) xdg_toplevel_destroy(window->wl.xdg.toplevel);
wl_shell_surface_destroy(window->wl.shellSurface); xdg_surface_destroy(window->wl.xdg.surface);
window->wl.visible = GLFW_FALSE; window->wl.xdg.toplevel = NULL;
window->wl.xdg.surface = NULL;
} }
else if (window->wl.shellSurface)
{
wl_shell_surface_destroy(window->wl.shellSurface);
window->wl.shellSurface = NULL;
}
window->wl.visible = GLFW_FALSE;
} }
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
@ -663,20 +854,35 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
int width, int height, int width, int height,
int refreshRate) int refreshRate)
{ {
if (monitor) if (window->wl.xdg.toplevel)
{ {
wl_shell_surface_set_fullscreen( if (monitor)
window->wl.shellSurface, {
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, xdg_toplevel_set_fullscreen(
refreshRate * 1000, // Convert Hz to mHz. window->wl.xdg.toplevel,
monitor->wl.output); monitor->wl.output);
setIdleInhibitor(window, GLFW_TRUE); }
else
{
xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
}
} }
else else if (window->wl.shellSurface)
{ {
wl_shell_surface_set_toplevel(window->wl.shellSurface); if (monitor)
setIdleInhibitor(window, GLFW_FALSE); {
wl_shell_surface_set_fullscreen(
window->wl.shellSurface,
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
refreshRate * 1000, // Convert Hz to mHz.
monitor->wl.output);
}
else
{
wl_shell_surface_set_toplevel(window->wl.shellSurface);
}
} }
setIdleInhibitor(window, monitor ? GLFW_TRUE : GLFW_FALSE);
_glfwInputWindowMonitor(window, monitor); _glfwInputWindowMonitor(window, monitor);
} }
@ -687,7 +893,8 @@ int _glfwPlatformWindowFocused(_GLFWwindow* window)
int _glfwPlatformWindowIconified(_GLFWwindow* window) int _glfwPlatformWindowIconified(_GLFWwindow* window)
{ {
// TODO: move to xdg_shell, wl_shell doesn't have any iconified concept. // wl_shell doesn't have any iconified concept, and xdg-shell doesnt give
// any way to request whether a surface is iconified.
return GLFW_FALSE; return GLFW_FALSE;
} }