GHOST/Wayland: support client-side window decorations

This implements client-side window decorations for moving and resizing
windows and HiDPI support.

This functionality depends on the external project 'libdecor' that is
currently a build option: WITH_GHOST_WAYLAND_LIBDECOR.

Reviewed by: brecht, campbellbarton

Ref D7989
This commit is contained in:
Christian Rauch 2022-06-24 16:10:20 +10:00 committed by Campbell Barton
parent f1d191120f
commit 29755e1df8
Notes: blender-bot 2023-02-13 22:37:44 +01:00
Referenced by issue #76428, GHOST/Wayland Support
6 changed files with 266 additions and 14 deletions

View File

@ -222,6 +222,11 @@ if(UNIX AND NOT (APPLE OR HAIKU))
option(WITH_GHOST_WAYLAND "Enable building Blender against Wayland for windowing (under development)" OFF)
mark_as_advanced(WITH_GHOST_WAYLAND)
if (WITH_GHOST_WAYLAND)
option(WITH_GHOST_WAYLAND_LIBDECOR "Optionally build with LibDecor window decorations" OFF)
mark_as_advanced(WITH_GHOST_WAYLAND_LIBDECOR)
endif()
endif()
if(WITH_GHOST_X11)

View File

@ -615,6 +615,10 @@ if(WITH_GHOST_WAYLAND)
pkg_check_modules(wayland-cursor REQUIRED wayland-cursor)
pkg_check_modules(dbus REQUIRED dbus-1)
if(WITH_GHOST_WAYLAND_LIBDECOR)
pkg_check_modules(libdecor REQUIRED libdecor-0>=0.1)
endif()
set(WITH_GL_EGL ON)
list(APPEND PLATFORM_LINKLIBS
@ -624,6 +628,13 @@ if(WITH_GHOST_WAYLAND)
${wayland-cursor_LINK_LIBRARIES}
${dbus_LINK_LIBRARIES}
)
if(WITH_GHOST_WAYLAND_LIBDECOR)
list(APPEND PLATFORM_LINKLIBS
${libdecor_LIBRARIES}
)
add_definitions(-DWITH_GHOST_WAYLAND_LIBDECOR)
endif()
endif()
if(WITH_GHOST_X11)

View File

@ -271,6 +271,12 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
${dbus_INCLUDE_DIRS}
)
if(WITH_GHOST_WAYLAND_LIBDECOR)
list(APPEND INC_SYS
${libdecor_INCLUDE_DIRS}
)
endif()
include(CheckSymbolExists)
set(CMAKE_REQUIRED_DEFINITIONS "-D_GNU_SOURCE")
check_symbol_exists(memfd_create "sys/mman.h" HAVE_MEMFD_CREATE)
@ -332,14 +338,17 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
${INC_DST}
)
# `xdg-shell`.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml"
)
# `xdg-decoration`.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
)
if(NOT WITH_GHOST_WAYLAND_LIBDECOR)
# `xdg-shell`.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml"
)
# `xdg-decoration`.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
)
endif()
# `xdg-output`.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/xdg-output/xdg-output-unstable-v1.xml"

View File

@ -254,8 +254,14 @@ struct display_t {
struct wl_display *display = nullptr;
struct wl_compositor *compositor = nullptr;
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
struct libdecor *decor_context = nullptr;
#else
struct xdg_wm_base *xdg_shell = nullptr;
struct zxdg_decoration_manager_v1 *xdg_decoration_manager = nullptr;
#endif
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
struct wl_shm *shm = nullptr;
std::vector<output_t *> outputs;
@ -402,6 +408,11 @@ static void display_destroy(display_t *d)
wl_compositor_destroy(d->compositor);
}
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (d->decor_context) {
libdecor_unref(d->decor_context);
}
#else
if (d->xdg_decoration_manager) {
zxdg_decoration_manager_v1_destroy(d->xdg_decoration_manager);
}
@ -409,6 +420,7 @@ static void display_destroy(display_t *d)
if (d->xdg_shell) {
xdg_wm_base_destroy(d->xdg_shell);
}
#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
if (eglGetDisplay) {
::eglTerminate(eglGetDisplay(EGLNativeDisplayType(d->display)));
@ -2190,6 +2202,8 @@ static const struct wl_output_listener output_listener = {
/** \name Listener (XDG WM Base), #xdg_wm_base_listener
* \{ */
#ifndef WITH_GHOST_WAYLAND_LIBDECOR
static void shell_handle_ping(void * /*data*/,
struct xdg_wm_base *xdg_wm_base,
const uint32_t serial)
@ -2201,6 +2215,32 @@ static const struct xdg_wm_base_listener shell_listener = {
shell_handle_ping,
};
#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (LibDecor), #libdecor_interface
* \{ */
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
static void decor_handle_error(struct libdecor * /*context*/,
enum libdecor_error error,
const char *message)
{
(void)(error);
(void)(message);
GHOST_PRINT("decoration error (" << error << "): " << message << std::endl);
exit(EXIT_FAILURE);
}
static struct libdecor_interface libdecor_interface = {
decor_handle_error,
};
#endif /* WITH_GHOST_WAYLAND_LIBDECOR. */
/** \} */
/* -------------------------------------------------------------------- */
@ -2218,6 +2258,9 @@ static void global_handle_add(void *data,
display->compositor = static_cast<wl_compositor *>(
wl_registry_bind(wl_registry, name, &wl_compositor_interface, 3));
}
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
/* Pass. */
#else
else if (!strcmp(interface, xdg_wm_base_interface.name)) {
display->xdg_shell = static_cast<xdg_wm_base *>(
wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, 1));
@ -2227,6 +2270,7 @@ static void global_handle_add(void *data,
display->xdg_decoration_manager = static_cast<zxdg_decoration_manager_v1 *>(
wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1));
}
#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */
else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) {
display->xdg_output_manager = static_cast<zxdg_output_manager_v1 *>(
wl_registry_bind(wl_registry, name, &zxdg_output_manager_v1_interface, 3));
@ -2330,10 +2374,18 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new display_t)
wl_display_roundtrip(d->display);
wl_registry_destroy(registry);
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
d->decor_context = libdecor_new(d->display, &libdecor_interface);
if (!d->decor_context) {
display_destroy(d);
throw std::runtime_error("Wayland: unable to create window decorations!");
}
#else
if (!d->xdg_shell) {
display_destroy(d);
throw std::runtime_error("Wayland: unable to access xdg_shell!");
}
#endif
/* Register data device per seat for IPC between Wayland clients. */
if (d->data_device_manager) {
@ -2667,6 +2719,15 @@ wl_compositor *GHOST_SystemWayland::compositor()
return d->compositor;
}
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor *GHOST_SystemWayland::decor_context()
{
return d->decor_context;
}
#else /* WITH_GHOST_WAYLAND_LIBDECOR */
xdg_wm_base *GHOST_SystemWayland::xdg_shell()
{
return d->xdg_shell;
@ -2677,6 +2738,8 @@ zxdg_decoration_manager_v1 *GHOST_SystemWayland::xdg_decoration_manager()
return d->xdg_decoration_manager;
}
#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
const std::vector<output_t *> &GHOST_SystemWayland::outputs() const
{
return d->outputs;

View File

@ -13,9 +13,13 @@
#include <wayland-client.h>
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
# include <libdecor.h>
#else
/* Generated by `wayland-scanner`. */
#include <xdg-decoration-unstable-v1-client-protocol.h>
#include <xdg-shell-client-protocol.h>
# include <xdg-decoration-unstable-v1-client-protocol.h>
# include <xdg-shell-client-protocol.h>
#endif
#include <string>
@ -103,9 +107,12 @@ class GHOST_SystemWayland : public GHOST_System {
wl_compositor *compositor();
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor *decor_context();
#else
xdg_wm_base *xdg_shell();
zxdg_decoration_manager_v1 *xdg_decoration_manager();
#endif
const std::vector<output_t *> &outputs() const;

View File

@ -18,6 +18,10 @@
#include <algorithm> /* For `std::find`. */
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
# include <libdecor.h>
#endif
static constexpr size_t base_dpi = 96;
struct window_t {
@ -41,10 +45,17 @@ struct window_t {
*/
uint32_t dpi = 0;
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
struct libdecor_frame *decor_frame = nullptr;
bool decor_configured = false;
#else
struct xdg_surface *xdg_surface = nullptr;
struct xdg_toplevel *xdg_toplevel = nullptr;
struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration = nullptr;
struct xdg_toplevel *xdg_toplevel = nullptr;
enum zxdg_toplevel_decoration_v1_mode decoration_mode = (enum zxdg_toplevel_decoration_v1_mode)0;
#endif
wl_egl_window *egl_window = nullptr;
bool is_maximised = false;
bool is_fullscreen = false;
@ -121,6 +132,8 @@ static int outputs_max_scale_or_default(const std::vector<output_t *> &outputs,
/** \name Listener (XDG Top Level), #xdg_toplevel_listener
* \{ */
#ifndef WITH_GHOST_WAYLAND_LIBDECOR
static void xdg_toplevel_handle_configure(void *data,
xdg_toplevel * /*xdg_toplevel*/,
const int32_t width,
@ -163,12 +176,83 @@ static const xdg_toplevel_listener toplevel_listener = {
xdg_toplevel_handle_close,
};
#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (LibDecor Frame), #libdecor_frame_interface
* \{ */
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
static void frame_handle_configure(struct libdecor_frame *frame,
struct libdecor_configuration *configuration,
void *data)
{
window_t *win = static_cast<window_t *>(data);
int size_next[2];
enum libdecor_window_state window_state;
struct libdecor_state *state;
if (!libdecor_configuration_get_content_size(
configuration, frame, &size_next[0], &size_next[1])) {
size_next[0] = win->size[0] / win->scale;
size_next[1] = win->size[1] / win->scale;
}
win->size[0] = win->scale * size_next[0];
win->size[1] = win->scale * size_next[1];
wl_egl_window_resize(win->egl_window, win->size[0], win->size[1], 0, 0);
win->w->notify_size();
if (!libdecor_configuration_get_window_state(configuration, &window_state)) {
window_state = LIBDECOR_WINDOW_STATE_NONE;
}
win->is_maximised = window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED;
win->is_fullscreen = window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN;
win->is_active = window_state & LIBDECOR_WINDOW_STATE_ACTIVE;
win->is_active ? win->w->activate() : win->w->deactivate();
state = libdecor_state_new(size_next[0], size_next[1]);
libdecor_frame_commit(frame, state, configuration);
libdecor_state_free(state);
win->decor_configured = true;
}
static void frame_handle_close(struct libdecor_frame * /*frame*/, void *data)
{
static_cast<window_t *>(data)->w->close();
}
static void frame_handle_commit(struct libdecor_frame * /*frame*/, void *data)
{
/* we have to swap twice to keep any pop-up menues alive */
static_cast<window_t *>(data)->w->swapBuffers();
static_cast<window_t *>(data)->w->swapBuffers();
}
static struct libdecor_frame_interface libdecor_frame_iface = {
frame_handle_configure,
frame_handle_close,
frame_handle_commit,
};
#endif /* WITH_GHOST_WAYLAND_LIBDECOR. */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (XDG Decoration Listener), #zxdg_toplevel_decoration_v1_listener
* \{ */
#ifndef WITH_GHOST_WAYLAND_LIBDECOR
static void xdg_toplevel_decoration_handle_configure(
void *data,
struct zxdg_toplevel_decoration_v1 * /*zxdg_toplevel_decoration_v1*/,
@ -181,12 +265,16 @@ static const zxdg_toplevel_decoration_v1_listener toplevel_decoration_v1_listene
xdg_toplevel_decoration_handle_configure,
};
#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (XDG Surface Handle Configure), #xdg_surface_listener
* \{ */
#ifndef WITH_GHOST_WAYLAND_LIBDECOR
static void xdg_surface_handle_configure(void *data,
xdg_surface *xdg_surface,
const uint32_t serial)
@ -220,6 +308,8 @@ static const xdg_surface_listener xdg_surface_listener = {
xdg_surface_handle_configure,
};
#endif /* !WITH_GHOST_WAYLAND_LIBDECOR. */
/** \} */
/* -------------------------------------------------------------------- */
@ -316,6 +406,19 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
w->egl_window = wl_egl_window_create(w->wl_surface, int(w->size[0]), int(w->size[1]));
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
/* create window decorations */
w->decor_frame = libdecor_decorate(
m_system->decor_context(), w->wl_surface, &libdecor_frame_iface, w);
libdecor_frame_map(w->decor_frame);
if (parentWindow) {
libdecor_frame_set_parent(
w->decor_frame, dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->w->decor_frame);
}
#else
w->xdg_surface = xdg_wm_base_get_xdg_surface(m_system->xdg_shell(), w->wl_surface);
w->xdg_toplevel = xdg_surface_get_toplevel(w->xdg_surface);
@ -334,8 +437,6 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
wl_surface_set_user_data(w->wl_surface, this);
xdg_surface_add_listener(w->xdg_surface, &xdg_surface_listener, w);
xdg_toplevel_add_listener(w->xdg_toplevel, &toplevel_listener, w);
@ -344,15 +445,31 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
w->xdg_toplevel, dynamic_cast<const GHOST_WindowWayland *>(parentWindow)->w->xdg_toplevel);
}
#endif /* !WITH_GHOST_WAYLAND_LIBDECOR */
wl_surface_set_user_data(w->wl_surface, this);
/* Call top-level callbacks. */
wl_surface_commit(w->wl_surface);
wl_display_roundtrip(m_system->display());
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
/* It's important not to return until the window is configured or
* calls to `setState` from Blender will crash `libdecor`. */
while (!w->decor_configured) {
if (libdecor_dispatch(m_system->decor_context(), 0) < 0) {
break;
}
}
#endif
#ifdef GHOST_OPENGL_ALPHA
setOpaque();
#endif
#ifndef WITH_GHOST_WAYLAND_LIBDECOR /* Causes a glicth with libdecor for some reason. */
setState(state);
#endif
setTitle(title);
@ -512,8 +629,14 @@ GHOST_TSuccess GHOST_WindowWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitma
void GHOST_WindowWayland::setTitle(const char *title)
{
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor_frame_set_app_id(w->decor_frame, title);
libdecor_frame_set_title(w->decor_frame, title);
#else
xdg_toplevel_set_title(w->xdg_toplevel, title);
xdg_toplevel_set_app_id(w->xdg_toplevel, title);
#endif
this->title = title;
}
@ -581,11 +704,17 @@ GHOST_WindowWayland::~GHOST_WindowWayland()
releaseNativeHandles();
wl_egl_window_destroy(w->egl_window);
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor_frame_unref(w->decor_frame);
#else
if (w->xdg_toplevel_decoration) {
zxdg_toplevel_decoration_v1_destroy(w->xdg_toplevel_decoration);
}
xdg_toplevel_destroy(w->xdg_toplevel);
xdg_surface_destroy(w->xdg_surface);
#endif
wl_surface_destroy(w->wl_surface);
/* NOTE(@campbellbarton): This is needed so the appropriate handlers event
@ -622,23 +751,43 @@ GHOST_TSuccess GHOST_WindowWayland::setState(GHOST_TWindowState state)
/* Unset states. */
switch (getState()) {
case GHOST_kWindowStateMaximized:
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor_frame_unset_maximized(w->decor_frame);
#else
xdg_toplevel_unset_maximized(w->xdg_toplevel);
#endif
break;
case GHOST_kWindowStateFullScreen:
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor_frame_unset_fullscreen(w->decor_frame);
#else
xdg_toplevel_unset_fullscreen(w->xdg_toplevel);
#endif
break;
default:
break;
}
break;
case GHOST_kWindowStateMaximized:
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor_frame_set_maximized(w->decor_frame);
#else
xdg_toplevel_set_maximized(w->xdg_toplevel);
#endif
break;
case GHOST_kWindowStateMinimized:
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor_frame_set_minimized(w->decor_frame);
#else
xdg_toplevel_set_minimized(w->xdg_toplevel);
#endif
break;
case GHOST_kWindowStateFullScreen:
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor_frame_set_fullscreen(w->decor_frame, nullptr);
#else
xdg_toplevel_set_fullscreen(w->xdg_toplevel, nullptr);
#endif
break;
case GHOST_kWindowStateEmbedded:
return GHOST_kFailure;
@ -669,13 +818,21 @@ GHOST_TSuccess GHOST_WindowWayland::setOrder(GHOST_TWindowOrder /*order*/)
GHOST_TSuccess GHOST_WindowWayland::beginFullScreen() const
{
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor_frame_set_fullscreen(w->decor_frame, nullptr);
#else
xdg_toplevel_set_fullscreen(w->xdg_toplevel, nullptr);
#endif
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_WindowWayland::endFullScreen() const
{
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor_frame_unset_fullscreen(w->decor_frame);
#else
xdg_toplevel_unset_fullscreen(w->xdg_toplevel);
#endif
return GHOST_kSuccess;
}