From f0392474f2726e91202116c38d1d333da722348c Mon Sep 17 00:00:00 2001 From: Colin Kinloch Date: Fri, 24 Dec 2021 13:20:45 +0000 Subject: [PATCH] (Wayland) Add libdecor for client side decoration (#13397) --- Makefile.common | 6 ++ gfx/drivers_context/wayland_ctx.c | 128 +++++++++++++++++++++++++++ gfx/drivers_context/wayland_vk_ctx.c | 119 +++++++++++++++++++++++++ input/common/wayland_common.c | 8 ++ input/common/wayland_common.h | 4 + qb/config.libs.sh | 2 + qb/config.params.sh | 1 + 7 files changed, 268 insertions(+) diff --git a/Makefile.common b/Makefile.common index f18ecc8097..0a98c80811 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1264,6 +1264,12 @@ ifeq ($(HAVE_WAYLAND), 1) DEF_FLAGS += $(WAYLAND_CFLAGS) $(WAYLAND_CURSOR_CFLAGS) LIBS += $(WAYLAND_LIBS) $(WAYLAND_CURSOR_LIBS) + ifeq ($(HAVE_LIBDECOR), 1) + DEFINES += -DHAVE_LIBDECOR + LIBS += $(LIBDECOR_LIBS) + DEF_FLAGS += $(LIBDECOR_CFLAGS) + endif + endif # XML diff --git a/gfx/drivers_context/wayland_ctx.c b/gfx/drivers_context/wayland_ctx.c index 336c4437f9..9dcaae46cb 100644 --- a/gfx/drivers_context/wayland_ctx.c +++ b/gfx/drivers_context/wayland_ctx.c @@ -30,6 +30,10 @@ #include "../common/egl_common.h" #endif +#ifdef HAVE_LIBDECOR +#include +#endif + #include "../../frontend/frontend_driver.h" #include "../../input/common/wayland_common.h" #include "../../input/input_driver.h" @@ -246,6 +250,11 @@ static void gfx_ctx_wl_update_title(void *data) video_driver_get_window_title(title, sizeof(title)); +#ifdef HAVE_LIBDECOR + if (wl && title[0]) { + libdecor_frame_set_title(wl->libdecor_frame, title); + } +#else if (wl && title[0]) { if (wl->deco) @@ -255,6 +264,7 @@ static void gfx_ctx_wl_update_title(void *data) } xdg_toplevel_set_title(wl->xdg_toplevel, title); } +#endif } static bool gfx_ctx_wl_get_metrics(void *data, @@ -287,6 +297,95 @@ static bool gfx_ctx_wl_get_metrics(void *data, return true; } +#ifdef HAVE_LIBDECOR +static void +handle_libdecor_error(struct libdecor *context, + enum libdecor_error error, const char *message) +{ + RARCH_ERR("[Wayland]: libdecor Caught error (%d): %s\n", error, message); +} + +static struct libdecor_interface libdecor_interface = { + .error = handle_libdecor_error, +}; + +static void +handle_libdecor_frame_configure(struct libdecor_frame *frame, + struct libdecor_configuration *configuration, void *data) +{ + gfx_ctx_wayland_data_t *wl = (gfx_ctx_wayland_data_t*)data; + struct libdecor_state *state; + int width, height; + + static const enum libdecor_window_state tiled_states = ( + LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT | + LIBDECOR_WINDOW_STATE_TILED_TOP | LIBDECOR_WINDOW_STATE_TILED_BOTTOM + ); + + enum libdecor_window_state window_state; + bool focused = false; + wl->fullscreen = false; + wl->maximized = false; + bool tiled = false; + if (libdecor_configuration_get_window_state(configuration, &window_state)) { + wl->fullscreen = (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0; + wl->maximized = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) != 0; + focused = (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) != 0; + tiled = (window_state & tiled_states) != 0; + } + + if (!libdecor_configuration_get_content_size(configuration, frame, + &width, &height)) { + width = wl->prev_width; + height = wl->prev_height; + } + + if (width > 0 && height > 0) + { + wl->prev_width = width; + wl->prev_height = height; + wl->width = width; + wl->height = height; + } + +#ifdef HAVE_EGL + if (wl->win) + wl_egl_window_resize(wl->win, wl->width, wl->height, 0, 0); + else + wl->win = wl_egl_window_create(wl->surface, + wl->width * wl->buffer_scale, + wl->height * wl->buffer_scale); +#endif + + state = libdecor_state_new(wl->width, wl->height); + libdecor_frame_commit(frame, state, configuration); + libdecor_state_free(state); + + wl->configured = false; +} + +static void +handle_libdecor_frame_close(struct libdecor_frame *frame, + void *data) +{ + gfx_ctx_wayland_data_t *wl = (gfx_ctx_wayland_data_t*)data; + command_event(CMD_EVENT_QUIT, NULL); +} + +static void +handle_libdecor_frame_commit(struct libdecor_frame *frame, + void *data) +{ + gfx_ctx_wayland_data_t *wl = (gfx_ctx_wayland_data_t*)data; +} + +static struct libdecor_frame_interface libdecor_frame_interface = { + handle_libdecor_frame_configure, + handle_libdecor_frame_close, + handle_libdecor_frame_commit, +}; +#endif + #define DEFAULT_WINDOWED_WIDTH 640 #define DEFAULT_WINDOWED_HEIGHT 480 @@ -594,6 +693,30 @@ static bool gfx_ctx_wl_set_video_mode(void *data, wl->win = wl_egl_window_create(wl->surface, wl->width * wl->buffer_scale, wl->height * wl->buffer_scale); #endif +#ifdef HAVE_LIBDECOR + wl->libdecor_context = libdecor_new(wl->input.dpy, &libdecor_interface); + if (wl->libdecor_context) { + wl->libdecor_frame = libdecor_decorate(wl->libdecor_context, wl->surface, &libdecor_frame_interface, wl); + if (wl->libdecor_frame == NULL) { + RARCH_ERR("[Wayland]: Failed to crate libdecor frame\n"); + goto error; + } else { + libdecor_frame_set_app_id(wl->libdecor_frame, "retroarch"); + libdecor_frame_set_title(wl->libdecor_frame, "RetroArch"); + libdecor_frame_map(wl->libdecor_frame); + } + } + + /* Waiting for libdecor to be configured before starting to draw */ + wl_surface_commit(wl->surface); + wl->configured = true; + + while (wl->configured) + if (libdecor_dispatch(wl->libdecor_context, 0) < 0) { + RARCH_ERR("[Wayland]: libdecor failed to dispatch\n"); + goto error; + }; +#else wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->xdg_shell, wl->surface); xdg_surface_add_listener(wl->xdg_surface, &xdg_surface_listener, wl); @@ -615,6 +738,7 @@ static bool gfx_ctx_wl_set_video_mode(void *data, while (wl->configured) wl_display_dispatch(wl->input.dpy); +#endif wl_display_roundtrip(wl->input.dpy); xdg_wm_base_add_listener(wl->xdg_shell, &xdg_shell_listener, NULL); @@ -634,7 +758,11 @@ static bool gfx_ctx_wl_set_video_mode(void *data, if (fullscreen) { +#ifdef HAVE_LIBDECOR + libdecor_frame_set_fullscreen(wl->libdecor_frame, NULL); +#else xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL); +#endif } flush_wayland_fd(&wl->input); diff --git a/gfx/drivers_context/wayland_vk_ctx.c b/gfx/drivers_context/wayland_vk_ctx.c index 59198af732..800cc9fe7c 100644 --- a/gfx/drivers_context/wayland_vk_ctx.c +++ b/gfx/drivers_context/wayland_vk_ctx.c @@ -25,6 +25,10 @@ #include "../../config.h" #endif +#ifdef HAVE_LIBDECOR +#include +#endif + #include "../common/vulkan_common.h" #include "../../frontend/frontend_driver.h" @@ -238,6 +242,11 @@ static void gfx_ctx_wl_update_title(void *data) video_driver_get_window_title(title, sizeof(title)); +#ifdef HAVE_LIBDECOR + if (wl && title[0]) { + libdecor_frame_set_title(wl->libdecor_frame, title); + } +#else if (wl && title[0]) { if (wl->deco) @@ -248,6 +257,7 @@ static void gfx_ctx_wl_update_title(void *data) xdg_toplevel_set_title(wl->xdg_toplevel, title); } +#endif } static bool gfx_ctx_wl_get_metrics(void *data, @@ -280,6 +290,86 @@ static bool gfx_ctx_wl_get_metrics(void *data, return true; } +#ifdef HAVE_LIBDECOR +static void +handle_libdecor_error(struct libdecor *context, + enum libdecor_error error, const char *message) +{ + RARCH_ERR("[Wayland/Vulkan]: libdecor Caught error (%d): %s\n", error, message); +} + +static struct libdecor_interface libdecor_interface = { + .error = handle_libdecor_error, +}; + +static void +handle_libdecor_frame_configure(struct libdecor_frame *frame, + struct libdecor_configuration *configuration, void *data) +{ + gfx_ctx_wayland_data_t *wl = (gfx_ctx_wayland_data_t*)data; + struct libdecor_state *state; + int width, height; + + static const enum libdecor_window_state tiled_states = ( + LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT | + LIBDECOR_WINDOW_STATE_TILED_TOP | LIBDECOR_WINDOW_STATE_TILED_BOTTOM + ); + + enum libdecor_window_state window_state; + bool focused = false; + wl->fullscreen = false; + wl->maximized = false; + bool tiled = false; + if (libdecor_configuration_get_window_state(configuration, &window_state)) { + wl->fullscreen = (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) != 0; + wl->maximized = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) != 0; + focused = (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) != 0; + tiled = (window_state & tiled_states) != 0; + } + + if (!libdecor_configuration_get_content_size(configuration, frame, + &width, &height)) { + width = wl->prev_width; + height = wl->prev_height; + } + + if (width > 0 && height > 0) + { + wl->prev_width = width; + wl->prev_height = height; + wl->width = width; + wl->height = height; + } + + state = libdecor_state_new(wl->width, wl->height); + libdecor_frame_commit(frame, state, configuration); + libdecor_state_free(state); + + wl->configured = false; +} + +static void +handle_libdecor_frame_close(struct libdecor_frame *frame, + void *data) +{ + gfx_ctx_wayland_data_t *wl = (gfx_ctx_wayland_data_t*)data; + command_event(CMD_EVENT_QUIT, NULL); +} + +static void +handle_libdecor_frame_commit(struct libdecor_frame *frame, + void *data) +{ + gfx_ctx_wayland_data_t *wl = (gfx_ctx_wayland_data_t*)data; +} + +static struct libdecor_frame_interface libdecor_frame_interface = { + handle_libdecor_frame_configure, + handle_libdecor_frame_close, + handle_libdecor_frame_commit, +}; +#endif + #define DEFAULT_WINDOWED_WIDTH 640 #define DEFAULT_WINDOWED_HEIGHT 480 @@ -420,6 +510,30 @@ static bool gfx_ctx_wl_set_video_mode(void *data, wl_surface_set_buffer_scale(wl->surface, wl->buffer_scale); wl_surface_add_listener(wl->surface, &wl_surface_listener, wl); +#ifdef HAVE_LIBDECOR + wl->libdecor_context = libdecor_new(wl->input.dpy, &libdecor_interface); + if (wl->libdecor_context) { + wl->libdecor_frame = libdecor_decorate(wl->libdecor_context, wl->surface, &libdecor_frame_interface, wl); + if (wl->libdecor_frame == NULL) { + RARCH_ERR("[Wayland/Vulkan]: Failed to crate libdecor frame\n"); + goto error; + } else { + libdecor_frame_set_app_id(wl->libdecor_frame, "retroarch"); + libdecor_frame_set_title(wl->libdecor_frame, "RetroArch"); + libdecor_frame_map(wl->libdecor_frame); + } + } + + /* Waiting for libdecor to be configured before starting to draw */ + wl_surface_commit(wl->surface); + wl->configured = true; + + while (wl->configured) + if (libdecor_dispatch(wl->libdecor_context, 0) < 0) { + RARCH_ERR("[Wayland/Vulkan]: libdecor failed to dispatch\n"); + goto error; + }; +#else wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->xdg_shell, wl->surface); xdg_surface_add_listener(wl->xdg_surface, &xdg_surface_listener, wl); @@ -441,13 +555,18 @@ static bool gfx_ctx_wl_set_video_mode(void *data, while (wl->configured) wl_display_dispatch(wl->input.dpy); +#endif wl_display_roundtrip(wl->input.dpy); xdg_wm_base_add_listener(wl->xdg_shell, &xdg_shell_listener, NULL); if (fullscreen) { +#ifdef HAVE_LIBDECOR + libdecor_frame_set_fullscreen(wl->libdecor_frame, NULL); +#else xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL); +#endif } flush_wayland_fd(&wl->input); diff --git a/input/common/wayland_common.c b/input/common/wayland_common.c index 9fc2924017..f6e687fa83 100644 --- a/input/common/wayland_common.c +++ b/input/common/wayland_common.c @@ -21,6 +21,10 @@ #include +#ifdef HAVE_LIBDECOR +#include +#endif + #include "wayland_common.h" #include "../input_keymaps.h" @@ -208,7 +212,11 @@ static void pointer_handle_button(void *data, if (BIT_GET(wl->input.key_state, KEY_LEFTALT)) { +#ifdef HAVE_LIBDECOR + libdecor_frame_move(wl->libdecor_frame, wl->seat, serial); +#else xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial); +#endif } break; case BTN_RIGHT: diff --git a/input/common/wayland_common.h b/input/common/wayland_common.h index 2c709be573..fc44bf497d 100644 --- a/input/common/wayland_common.h +++ b/input/common/wayland_common.h @@ -126,6 +126,10 @@ typedef struct gfx_ctx_wayland_data struct wl_touch *wl_touch; struct wl_seat *seat; struct wl_shm *shm; +#ifdef HAVE_LIBDECOR + struct libdecor *libdecor_context; + struct libdecor_frame *libdecor_frame; +#endif struct zxdg_decoration_manager_v1 *deco_manager; struct zxdg_toplevel_decoration_v1 *deco; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager; diff --git a/qb/config.libs.sh b/qb/config.libs.sh index d2f35bc158..e64c4977f1 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -534,6 +534,8 @@ if [ "$HAVE_WAYLAND_SCANNER" = yes ] && -p "$HAVE_WAYLAND_PROTOS" \ -s "$SHARE_DIR" || die 1 'Error: Failed generating wayland protocols.' + + check_pkgconf LIBDECOR libdecor-0 else die : 'Notice: wayland libraries not found, disabling wayland support.' HAVE_WAYLAND='no' diff --git a/qb/config.params.sh b/qb/config.params.sh index e73c43c5df..3896c09188 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -97,6 +97,7 @@ HAVE_EXYNOS=no # Exynos video support HAVE_DISPMANX=no # Dispmanx video support HAVE_SUNXI=no # Sunxi video support HAVE_WAYLAND=auto # Wayland support +HAVE_LIBDECOR=auto # libdecor support C89_WAYLAND=no CXX_WAYLAND=no HAVE_DYNAMIC_EGL=no # Dynamic library EGL support