diff options
author | Christian Rauch <christian.rauch> | 2022-06-24 09:10:20 +0300 |
---|---|---|
committer | Campbell Barton <campbell@blender.org> | 2022-06-24 17:10:39 +0300 |
commit | 29755e1df82e34061a0b0586234a5aaac5177d35 (patch) | |
tree | 0f1b754f25e9492349d1f33b96a71ac9da73c985 /intern/ghost | |
parent | f1d191120f4f1d762369b19bdbf8d9be29000d77 (diff) |
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
Diffstat (limited to 'intern/ghost')
-rw-r--r-- | intern/ghost/CMakeLists.txt | 25 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.cpp | 63 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_SystemWayland.h | 13 | ||||
-rw-r--r-- | intern/ghost/intern/GHOST_WindowWayland.cpp | 163 |
4 files changed, 250 insertions, 14 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt index 945b3261562..150bcb9273e 100644 --- a/intern/ghost/CMakeLists.txt +++ b/intern/ghost/CMakeLists.txt @@ -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" diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index 8e3ca59cc74..66d0902c0ca 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -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; diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index 4b953dccac6..972d16257eb 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -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; diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index afaa2e11aa7..bd11476b2e2 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -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; } |