From 65ae8b4b402e2dcbc83d61be72ac9e09b36ce69a Mon Sep 17 00:00:00 2001 From: Allen Webster Date: Tue, 24 Feb 2026 18:35:07 -0800 Subject: [PATCH] [digesting_libdecor.c] --- build.sh | 2 + digesting_libdecor.c | 6453 ++++++++++++++++++++++++++++++++++++++++ wayland_libdecor_egl.c | 1 - 3 files changed, 6455 insertions(+), 1 deletion(-) create mode 100755 build.sh create mode 100755 digesting_libdecor.c diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..5391f3b --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +#!/bin/bash +./digesting_libdecor.c \ No newline at end of file diff --git a/digesting_libdecor.c b/digesting_libdecor.c new file mode 100755 index 0000000..66c95bd --- /dev/null +++ b/digesting_libdecor.c @@ -0,0 +1,6453 @@ +#if 0 +libdecor_path="/home/mr4th/mr4th/libdecor" +gtk_flags="$(pkg-config --cflags --libs gtk+-3.0)" +dbus_flags="$(pkg-config --cflags --libs dbus-1)" +#echo "gtk_flags: $gtk_flags" +#echo "dbus_flags: $dbus_flags" +mkdir -p build +clang -o build/demo -g digesting_libdecor.c $gtk_flags $dbus_flags -Iwayland -I$libdecor_path/src -I$libdecor_path/src/plugins -I$libdecor_path/build -lwayland-client -lwayland-cursor -lwayland-egl -lEGL -lm +exit 0 +#endif + +/* +** Reading From: +** (1) Wayland Docs https://wayland.freedesktop.org/docs/html/ +** (egl) EGL spec https://registry.khronos.org/EGL/sdk/docs/man/ +** (libdecor.h) /usr/include/libdecor-0/libdecor.h +** +** (nodocs-wl_egl) I cannot find any documentation for wl_egl_ except for +** headers and example code. +*/ + +/* [1] IMPORTANT NOTE @see([1] in wayland_xdg_egl.c) */ + +#define _GNU_SOURCE + +#include +#include +#include + +#include +/*~ NOTE: wayland-egl.h *before* EGL/ */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include "xdg-shell-client-protocol.h" +#include "xdg-decoration-client-protocol.h" + +#include "xdg-shell-client-protocol.c" +#include "xdg-decoration-client-protocol.c" + +//#include +#ifndef LIBDECOR_H +#define LIBDECOR_H + +struct xdg_toplevel; +struct libdecor_frame; +struct libdecor_configuration; +struct libdecor_state; + +struct libdecor { + int ref_count; + + void *user_data; + + struct libdecor_plugin *plugin; + bool plugin_ready; + + struct wl_display *wl_display; + struct wl_registry *wl_registry; + struct xdg_wm_base *xdg_wm_base; + struct zxdg_decoration_manager_v1 *decoration_manager; + + struct wl_callback *init_callback; + bool init_done; + bool has_error; + + struct wl_list frames; +}; + +enum libdecor_error { + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + LIBDECOR_ERROR_INVALID_FRAME_CONFIGURATION, +}; + +enum libdecor_window_state { + LIBDECOR_WINDOW_STATE_NONE = 0, + LIBDECOR_WINDOW_STATE_ACTIVE = 1 << 0, + LIBDECOR_WINDOW_STATE_MAXIMIZED = 1 << 1, + LIBDECOR_WINDOW_STATE_FULLSCREEN = 1 << 2, + LIBDECOR_WINDOW_STATE_TILED_LEFT = 1 << 3, + LIBDECOR_WINDOW_STATE_TILED_RIGHT = 1 << 4, + LIBDECOR_WINDOW_STATE_TILED_TOP = 1 << 5, + LIBDECOR_WINDOW_STATE_TILED_BOTTOM = 1 << 6, + LIBDECOR_WINDOW_STATE_SUSPENDED = 1 << 7, + LIBDECOR_WINDOW_STATE_RESIZING = 1 << 8, + LIBDECOR_WINDOW_STATE_CONSTRAINED_LEFT = 1 << 9, + LIBDECOR_WINDOW_STATE_CONSTRAINED_RIGHT = 1 << 10, + LIBDECOR_WINDOW_STATE_CONSTRAINED_TOP = 1 << 11, + LIBDECOR_WINDOW_STATE_CONSTRAINED_BOTTOM = 1 << 12, +}; + +enum libdecor_resize_edge { + LIBDECOR_RESIZE_EDGE_NONE, + LIBDECOR_RESIZE_EDGE_TOP, + LIBDECOR_RESIZE_EDGE_BOTTOM, + LIBDECOR_RESIZE_EDGE_LEFT, + LIBDECOR_RESIZE_EDGE_TOP_LEFT, + LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT, + LIBDECOR_RESIZE_EDGE_RIGHT, + LIBDECOR_RESIZE_EDGE_TOP_RIGHT, + LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT, +}; + +enum libdecor_capabilities { + LIBDECOR_ACTION_MOVE = 1 << 0, + LIBDECOR_ACTION_RESIZE = 1 << 1, + LIBDECOR_ACTION_MINIMIZE = 1 << 2, + LIBDECOR_ACTION_FULLSCREEN = 1 << 3, + LIBDECOR_ACTION_CLOSE = 1 << 4, +}; + +enum libdecor_wm_capabilities { + LIBDECOR_WM_CAPABILITIES_WINDOW_MENU = 1 << 0, + LIBDECOR_WM_CAPABILITIES_MAXIMIZE = 1 << 1, + LIBDECOR_WM_CAPABILITIES_FULLSCREEN = 1 << 2, + LIBDECOR_WM_CAPABILITIES_MINIMIZE = 1 << 3 +}; + +/** + * Remove a reference to the libdecor instance. When the reference count + * reaches zero, it is freed. + */ +void +libdecor_unref(struct libdecor *context); + +/** + * Dispatch events. This function should be called when data is available on + * the file descriptor returned by libdecor_get_fd(). If timeout is zero, this + * function will never block. + */ +int +libdecor_dispatch(struct libdecor *context, + int timeout); + +/** + * Decorate the given content wl_surface. + * + * This will create an xdg_surface and an xdg_toplevel, and integrate it + * properly with the windowing system, including creating appropriate + * decorations when needed, as well as handle windowing integration events such + * as resizing, moving, maximizing, etc. + * + * The passed wl_surface should only contain actual application content, + * without any window decoration. + */ +struct libdecor_frame * +libdecor_decorate(struct libdecor *context, + struct wl_surface *surface, + void *user_data); + +/** + * Add a reference to the frame object. + */ +void +libdecor_frame_ref(struct libdecor_frame *frame); + +/** + * Remove a reference to the frame object. When the reference count reaches + * zero, the frame object is destroyed. + */ +void +libdecor_frame_unref(struct libdecor_frame *frame); + +/** + * Get the user data associated with this libdecor frame. + */ +void * +libdecor_frame_get_user_data(struct libdecor_frame *frame); + +/** + * Set the user data associated with this libdecor frame. + */ +void +libdecor_frame_set_user_data(struct libdecor_frame *frame, void *user_data); + +/** + * Set the visibility of the frame. + * + * If an application wants to be borderless, it can set the frame visibility to + * false. + */ +void +libdecor_frame_set_visibility(struct libdecor_frame *frame, + bool visible); + +/** + * Get the visibility of the frame. + */ +bool +libdecor_frame_is_visible(struct libdecor_frame *frame); + + +/** + * Set the parent of the window. + * + * This can be used to stack multiple toplevel windows above or under each + * other. + */ +void +libdecor_frame_set_parent(struct libdecor_frame *frame, + struct libdecor_frame *parent); + +/** + * Set the title of the window. + */ +void +libdecor_frame_set_title(struct libdecor_frame *frame, + const char *title); + +/** + * Get the title of the window. + */ +const char * +libdecor_frame_get_title(struct libdecor_frame *frame); + +/** + * Set the application ID of the window. + */ +void +libdecor_frame_set_app_id(struct libdecor_frame *frame, + const char *app_id); + +/** + * Set new capabilities of the window. + * + * This determines whether e.g. a window decoration should show a maximize + * button, etc. + * + * Setting a capability does not implicitly unset any other. + */ +void +libdecor_frame_set_capabilities(struct libdecor_frame *frame, + enum libdecor_capabilities capabilities); + +/** + * Unset capabilities of the window. + * + * The opposite of libdecor_frame_set_capabilities. + */ +void +libdecor_frame_unset_capabilities(struct libdecor_frame *frame, + enum libdecor_capabilities capabilities); + +/** + * Check whether the window has any of the given capabilities. + */ +bool +libdecor_frame_has_capability(struct libdecor_frame *frame, + enum libdecor_capabilities capability); + +/** + * Show the window menu. + */ +void +libdecor_frame_show_window_menu(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial, + int x, + int y); + +/** + * Issue a popup grab on the window. Call this when a xdg_popup is mapped, so + * that it can be properly dismissed by the decorations. + */ +void +libdecor_frame_popup_grab(struct libdecor_frame *frame, + const char *seat_name); + +/** + * Release the popup grab. Call this when you unmap a popup. + */ +void +libdecor_frame_popup_ungrab(struct libdecor_frame *frame, + const char *seat_name); + +/** + * Translate content surface local coordinates to toplevel window local + * coordinates. + * + * This can be used to translate surface coordinates to coordinates useful for + * e.g. showing the window menu, or positioning a popup. + */ +void +libdecor_frame_translate_coordinate(struct libdecor_frame *frame, + int surface_x, + int surface_y, + int *frame_x, + int *frame_y); + +/** + * Set the min content size. + * + * This translates roughly to xdg_toplevel_set_min_size(). + */ +void +libdecor_frame_set_min_content_size(struct libdecor_frame *frame, + int content_width, + int content_height); + +/** + * Set the max content size. + * + * This translates roughly to xdg_toplevel_set_max_size(). + */ +void +libdecor_frame_set_max_content_size(struct libdecor_frame *frame, + int content_width, + int content_height); + +/** + * Get the min content size. + */ +void +libdecor_frame_get_min_content_size(const struct libdecor_frame *frame, + int *content_width, + int *content_height); + +/** + * Get the max content size. + */ +void +libdecor_frame_get_max_content_size(const struct libdecor_frame *frame, + int *content_width, + int *content_height); + +/** + * Initiate an interactive resize. + * + * This roughly translates to xdg_toplevel_resize(). + */ +void +libdecor_frame_resize(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial, + enum libdecor_resize_edge edge); + +/** + * Initiate an interactive move. + * + * This roughly translates to xdg_toplevel_move(). + */ +void +libdecor_frame_move(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial); + +/** + * Commit a new window state. This can be called on application driven resizes + * when the window is floating, or in response to received configurations, i.e. + * from e.g. interactive resizes or state changes. + */ +void +libdecor_frame_commit(struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration); + +/** + * Minimize the window. + * + * Roughly translates to xdg_toplevel_set_minimized(). + */ +void +libdecor_frame_set_minimized(struct libdecor_frame *frame); + +/** + * Maximize the window. + * + * Roughly translates to xdg_toplevel_set_maximized(). + */ +void +libdecor_frame_set_maximized(struct libdecor_frame *frame); + +/** + * Unmaximize the window. + * + * Roughly translates to xdg_toplevel_unset_maximized(). + */ +void +libdecor_frame_unset_maximized(struct libdecor_frame *frame); + +/** + * Fullscreen the window. + * + * Roughly translates to xdg_toplevel_set_fullscreen(). + */ +void +libdecor_frame_set_fullscreen(struct libdecor_frame *frame, + struct wl_output *output); + +/** + * Unfullscreen the window. + * + * Roughly translates to xdg_toplevel_unset_unfullscreen(). + */ +void +libdecor_frame_unset_fullscreen(struct libdecor_frame *frame); + +/** + * Return true if the window is floating. + * + * A window is floating when it's not maximized, tiled, fullscreen, or in any + * similar way with a fixed size and state. + * Note that this function uses the "applied" configuration. If this function + * is used in the 'configure' callback, the provided configuration has to be + * applied via 'libdecor_frame_commit' first, before it will reflect the current + * window state from the provided configuration. + */ +bool +libdecor_frame_is_floating(struct libdecor_frame *frame); + +/** + * Close the window. + * + * Roughly translates to xdg_toplevel_close(). + */ +void +libdecor_frame_close(struct libdecor_frame *frame); + +/** + * Map the window. + * + * This will eventually result in the initial configure event. + */ +void +libdecor_frame_map(struct libdecor_frame *frame); + +/** + * Get the associated xdg_surface for content wl_surface. + */ +struct xdg_surface * +libdecor_frame_get_xdg_surface(struct libdecor_frame *frame); + +/** + * Get the associated xdg_toplevel for the content wl_surface. + */ +struct xdg_toplevel * +libdecor_frame_get_xdg_toplevel(struct libdecor_frame *frame); + +/** + * Get the supported window manager capabilities for the window. + */ +enum libdecor_wm_capabilities +libdecor_frame_get_wm_capabilities(struct libdecor_frame *frame); + +/** + * Tell libdecor to set the default pointer cursor when the pointer is over an + * application surface. The default false. + */ +void +libdecor_set_handle_application_cursor(struct libdecor *context, + bool handle_cursor); + +/** + * Create a new content surface state. + */ +struct libdecor_state * +libdecor_state_new(int width, + int height); + +/** + * Free a content surface state. + */ +void +libdecor_state_free(struct libdecor_state *state); + +/** + * Get the expected size of the content for this configuration. + * + * If the configuration doesn't contain a size, false is returned. + */ +bool +libdecor_configuration_get_content_size(struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *width, + int *height); + +/** + * Get the window state for this configuration. + * + * If the configuration doesn't contain any associated window state, false is + * returned, and the application should assume the window state remains + * unchanged. + */ +bool +libdecor_configuration_get_window_state(struct libdecor_configuration *configuration, + enum libdecor_window_state *window_state); + +#endif /* LIBDECOR_H */ + + +// X(N:name,R:return,P:params) +#define GL_FUNCS_XLIST(X)\ +X(glDrawBuffer, void, (GLenum buf)) \ +X(glViewport, void, (GLint x, GLint y, GLsizei w, GLsizei h)) \ +X(glClear, void, (GLbitfield mask)) \ +X(glClearColor, void, (GLfloat r, GLfloat g, GLfloat b, GLfloat a)) + +#define X(N,R,P) R (*N)P = 0; +GL_FUNCS_XLIST(X) +#undef X + +typedef struct Ctx{ + /* globals */ + struct wl_display *wl_display; + struct wl_registry *wl_registry; + struct wl_compositor *wl_compositor; + struct xdg_wm_base *xdg_wm_base; + + /* window */ + struct wl_surface *wl_surface; + struct libdecor_frame *libdecor_frame; + struct wl_egl_window *wl_egl_window; + int configured; + int w; + int h; + int close_signal; + //struct xdg_surface *xdg_surface; + //struct xdg_toplevel *xdg_toplevel; + //struct wl_region *wl_region; + EGLDisplay egl_display; + EGLContext egl_context; + EGLSurface egl_surface; + + /* libdecor digestion */ + struct libdecor *libdecor; +} Ctx; + +static Ctx ctx = {0}; + + +typedef struct libdecor_plugin * (* libdecor_plugin_constructor)(struct libdecor *context); + +enum libdecor_plugin_capabilities { + LIBDECOR_PLUGIN_CAPABILITY_BASE = 1 << 0, +}; + +struct libdecor_plugin_description { + /* API version the plugin is compatible with. */ + int api_version; + + /* Human readable string describing the plugin. */ + char *description; + + /* A plugin has a bitmask of capabilities. The plugin loader can use this + * to load a plugin with the right capabilities. */ + enum libdecor_plugin_capabilities capabilities; + + /* + * The priorities field points to a list of per desktop priorities. + * properties[i].desktop is matched against XDG_CURRENT_DESKTOP when + * determining what plugin to use. The last entry in the list MUST have + * the priorities[i].desktop pointer set to NULL as a default + * priority. + */ + const struct libdecor_plugin_priority *priorities; + + /* Vfunc used for constructing a plugin instance. */ + libdecor_plugin_constructor constructor; + + /* NULL terminated list of incompatible symbols. */ + char *conflicting_symbols[1024]; +}; + +const struct wl_registry_listener scrap__registry_listener; +const struct wl_callback_listener init_wl_display_callback_listener; +const struct libdecor_plugin_description libdecor_plugin_description; + + +/* (1) Appendix A: wl_registry::global +** " The event notifies the client that a global object with the given +** name is now available " +*/ +static void +init_xdg_wm_base(struct libdecor *context, + uint32_t id, + uint32_t version); + +static void +wlevent__wl_registry_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, + uint32_t version){ + /* (1) Appendix A: wl_registry::bind + ** " Binds a new, client-created object to the server " + */ + + if (strcmp(interface, "wl_compositor") == 0){ + ctx.wl_compositor = (struct wl_compositor*) + wl_registry_bind(registry, name, &wl_compositor_interface, 1); + } +} + +/* (1) Appendix A: wl_registry::global_remove */ +static void +wlevent__wl_registry_global_remove(void *data, struct wl_registry *registry, + uint32_t name){} + +const struct wl_registry_listener wl_registry_listener = { + wlevent__wl_registry_global, + wlevent__wl_registry_global_remove, +}; + + +/* (libdecor.h) libdecor_interface::error " An error event " */ +static void +libdecorevent__error(struct libdecor *libdecor, enum libdecor_error error, + const char *msg){} + + +/* (libdecor.h) libdecor_frame_interface::configure +** " A new configuration was received. An application should respond to +** this by creating a suitable libdecor_state, and apply it using +** libdecor_frame_commit. " +*/ +static void +libdecorevent__frame_configure(struct libdecor_frame *frame, + struct libdecor_configuration *config, + void *udata){ + int w = ctx.w; + int h = ctx.h; + /* (libdecor.h) + ** " Get the expected size of the content for this configuration. " + */ + if (libdecor_configuration_get_content_size(config, frame, &w, &h)){ + + /* (libdecor.h) " Create a new content surface state. " */ + struct libdecor_state *state = libdecor_state_new(w, h); + + /* (libdecor.h) libdecor_frame_commit + ** " Commit a new window state. This can be called on application + ** driven resizes when the window is floating, or in response to + ** received configurations, i.e. from e.g. interactive resizes + ** or state changes. " + */ + libdecor_frame_commit(frame, state, config); + + /* (libdecor.h) " Free a content surface state. " */ + libdecor_state_free(state); + } + ctx.configured = 1; + ctx.w = w; + ctx.h = h; +} + +/* (libdecor.h) libdecor_frame_interface::close +** " The window was requested to be closed by the compositor. " +*/ +static void +libdecorevent__frame_close(struct libdecor_frame *frame, void *udata){ + ctx.close_signal = 1; +} + +/* (libdecor.h) libdecor_frame_interface::commit +** " The window decoration asked to have the main surface to be +** committed. This is required when the decoration is implemented +** using synchronous subsurfaces. " +*/ +static void +libdecorevent__frame_commit(struct libdecor_frame *frame, void *udata){ + wl_surface_commit(ctx.wl_surface); +} + +/* (libdecor.h) libdecor_frame_interface::dismiss_popup +** " Any mapped popup that has a grab on the given seat should be dismissed. " +*/ +static void +libdecorevent__frame_dismiss_popup(struct libdecor_frame *frame, + const char *seat_name, + void *user_data){ + +} + +static void +libdecorevent__frame_bounds(struct libdecor_frame *frame, + int width, + int height, + void *user_data){ +} + +int main(){ + /*~ NOTE: + **~ initialize Wayland, Libdecor, & EGL + */ + + /* (1) Appendix B: wl_display_connect + ** " Connect to a Wayland display. " + */ + ctx.wl_display = wl_display_connect(0); + if (ctx.wl_display == 0){ + printf("wl_display_connect failed\n"); + } + + /* (1) Appendix A: wl_display::get_registry + ** " creates a registry object that allows the client to list + ** and bind the global objects available from the compositor " + */ + if (ctx.wl_display != 0){ + ctx.wl_registry = wl_display_get_registry(ctx.wl_display); + if (ctx.wl_registry == 0){ + printf("wl_display_get_registry failed\n"); + } + } + + if (ctx.wl_registry != 0){ + /* [1] */ + wl_registry_add_listener(ctx.wl_registry, &wl_registry_listener, 0); + + /* (1) Appendix B: wl_display::dispatch + ** " Dispatch events on the default event queue. If the default + ** event queue is empty, this function blocks until there are + ** events to be read from the display fd. " + */ + wl_display_dispatch(ctx.wl_display); + + /* (1) Appendix B: wl_display_roundtrip + ** " Block until all pending request are processed by the server " + */ + wl_display_roundtrip(ctx.wl_display); + + if (ctx.wl_compositor == 0){ + printf("failed to get wl_compositor\n"); + } + } + + /* (libdecor.h) " Create a new libdecor context for the given wl_display " */ + if (ctx.wl_display != 0 && ctx.wl_compositor != 0){ + ctx.libdecor = calloc(1, sizeof *ctx.libdecor); + + ctx.libdecor->ref_count = 1; + ctx.libdecor->wl_display = ctx.wl_display; + ctx.libdecor->wl_registry = wl_display_get_registry(ctx.wl_display); + wl_registry_add_listener(ctx.libdecor->wl_registry, + &scrap__registry_listener, + ctx.libdecor); + ctx.libdecor->init_callback = wl_display_sync(ctx.wl_display); + wl_callback_add_listener(ctx.libdecor->init_callback, + &init_wl_display_callback_listener, + ctx.libdecor); + + wl_list_init(&ctx.libdecor->frames); + + ctx.libdecor->plugin = libdecor_plugin_description.constructor(ctx.libdecor); + if (ctx.libdecor->plugin == 0){ + fprintf(stderr, "Failed to load static plugin: failed to init\n"); + exit(1); + } + + wl_display_flush(ctx.wl_display); + } + + int opengl_load_success = 0; + if (ctx.libdecor != 0){ + /* (egl) eglGetDisplay + ** " obtains the EGL display connection for the native display " + */ + ctx.egl_display = eglGetDisplay(ctx.wl_display); + if (ctx.egl_display == 0){ + printf("eglGetDisplay failed\n"); + } + + /* (egl) eglInitialize + ** " initializes* the EGL display connection obtained with eglGetDisplay " + */ + int egl_init_success = 0; + EGLint major = 0, minor = 0; + if (ctx.egl_display != 0){ + egl_init_success = eglInitialize(ctx.egl_display, &major, &minor); + if (!egl_init_success){ + printf("eglInitialize failed\n"); + } + } + + // print version + if (egl_init_success){ + printf("EGL version %d.%d\n", major, minor); + if (major < 1 || (major == 1 && minor < 5)){ + printf("version 1.5 or higher\n"); + egl_init_success = 0; + } + } + + /* (egl) eglBindAPI + ** " defines the current rendering API for EGL in the thread it is + ** called from " + */ + EGLBoolean bind_api_success = 0; + if (egl_init_success){ + bind_api_success = eglBindAPI(EGL_OPENGL_API); + } + + /* (egl) eglCreateContext + ** " creates an EGL rendering context for the current rendering API + ** (as set with eglBindAPI) and returns a handle to the context " + */ + if (bind_api_success){ + EGLint attr[] = { + EGL_CONTEXT_MAJOR_VERSION, 3, + EGL_CONTEXT_MINOR_VERSION, 3, + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_NONE, + }; + ctx.egl_context = eglCreateContext(ctx.egl_display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attr); + if (ctx.egl_context == EGL_NO_CONTEXT){ + printf("eglCreateContext failed\n"); + } + } + + /* (egl) eglMakeCurrent + ** " binds context to the current rendering thread " + */ + EGLBoolean make_current_success = 0; + if (ctx.egl_context != 0){ + make_current_success = eglMakeCurrent(ctx.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx.egl_context); + if (!make_current_success){ + printf("eglMakeCurrent failed\n"); + } + } + + /* (egl) eglGetProcAddress + ** " returns the address of the client API or EGL function " + */ + if (make_current_success){ + opengl_load_success = 1; +#define X(N,R,P) N = (R(*)P)(eglGetProcAddress(#N)); if (N == 0) opengl_load_success = 0; + GL_FUNCS_XLIST(X) +#undef X + if (!opengl_load_success){ + printf("GL procedure loading failed\n"); + } + } + } + + /*~ NOTE: + **~ Create a window + */ + + if (opengl_load_success){ + ctx.w = 640; + ctx.h = 480; + + /* (1) Appendix A: wl_compositor::create_surface + ** " create new surface " + */ + ctx.wl_surface = wl_compositor_create_surface(ctx.wl_compositor); + if (ctx.wl_surface == 0){ + printf("wl_compositor_create_surface failed\n"); + } + } + + if (ctx.wl_surface != 0){ + /* (libdecor.h) " Decorate the given content wl_surface. " */ + ctx.libdecor_frame = libdecor_decorate(ctx.libdecor, ctx.wl_surface, 0); + } + + if (ctx.libdecor_frame != 0){ + /* (libdecor.h) " Set the title of the window. " */ + libdecor_frame_set_title(ctx.libdecor_frame, "Example Window"); + + /* (libdecor.h) " This translates roughly to xdg_toplevel_set_min_size() " + **~ NOTE: I recommend setting this to something greater than 0 on each + ** axis, to prevent some artifacts when resize goes 0 or negative. + */ + libdecor_frame_set_min_content_size(ctx.libdecor_frame, 80, 60); + + /* (libdecor.h) " Map the window. " */ + libdecor_frame_map(ctx.libdecor_frame); + + /* (nodocs-wl_egl) */ + ctx.wl_egl_window = wl_egl_window_create(ctx.wl_surface, ctx.w, ctx.h); + if (ctx.wl_egl_window == EGL_NO_SURFACE){ + printf("wl_egl_window_create failed\n"); + } + } + + if (ctx.wl_egl_window != EGL_NO_SURFACE){ + /* (nodocs-wl_egl) eglChooseConfig + ** " returns in configs a list of all EGL frame buffer configurations + ** that match the attributes specified in attrib_list " + */ + EGLConfig configs[64]; + EGLint config_cap = sizeof(configs)/sizeof(*configs); + EGLint config_count = 0; + { + EGLint attributes[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_CONFORMANT, EGL_OPENGL_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_NONE, + }; + if (!eglChooseConfig(ctx.egl_display, attributes, configs, config_cap, &config_count)){ + printf("eglChooseConfig failed\n"); + config_count = 0; + } + } + + /* (egl) eglCreateWindowSurface + ** " creates an on-screen EGL window surface and returns a handle to it " + */ + { + EGLint attributes[] = { + EGL_RENDER_BUFFER, EGL_BACK_BUFFER, + EGL_NONE, + }; + for (EGLint i = 0; i < config_count; i += 1){ + ctx.egl_surface = eglCreateWindowSurface(ctx.egl_display, configs[i], ctx.wl_egl_window, attributes); + if (ctx.egl_surface != EGL_NO_SURFACE){ + break; + } + } + if (ctx.egl_surface == EGL_NO_SURFACE){ + printf("eglCreateWindowSurface failed\n"); + } + } + } + + /* (egl) eglMakeCurrent */ + EGLBoolean make_current_success2 = 0; + if (ctx.egl_surface != EGL_NO_SURFACE){ + make_current_success2 = eglMakeCurrent(ctx.egl_display, ctx.egl_surface, ctx.egl_surface, ctx.egl_context); + } + + /* (egl) eglSwapInterval + ** " specifies the minimum number of video frame periods per buffer swap + ** for the window associated with the current context " + */ + EGLBoolean swap_interval_success = 0; + if (make_current_success2){ + swap_interval_success = eglSwapInterval(ctx.egl_display, 1); + if (!swap_interval_success){ + printf("eglSwapInterval failed\n"); + } + } + + /*~ NOTE: Main loop */ + int exit_loop = 0; + if (!swap_interval_success){ + exit_loop = 1; + } + for (;!exit_loop;){ + /* (libdecor.h) + ** " Dispatch events. This function should be called when data is available on + ** the file descriptor returned by libdecor_get_fd(). If timeout is zero, this + ** function will never block. " + */ + libdecor_dispatch(ctx.libdecor, 0); + + if (ctx.close_signal){ + exit_loop = 1; + } + + /* (nodocs-wl_egl) */ + wl_egl_window_resize(ctx.wl_egl_window, ctx.w, ctx.h, 0, 0); + + /*~ NOTE: render */ + { + glDrawBuffer(GL_BACK); + glViewport(0, 0, 640, 480); + glClearColor(0.40f, 0.90f, 0.15f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + } + + /* (egl) eglSwapBuffers + ** " back-buffered window surface, then the color buffer is copied + ** (posted) to the native window associated with that surface " + */ + EGLBoolean swap_success = eglSwapBuffers(ctx.egl_display, ctx.egl_surface); + if (!swap_success){ + printf("eglSwapBuffers failed\n"); + } + } + + /* (1) #Client-classwl__display_1a9150a7e3213a58b469a6966e60a9f108 + ** " Close the connection to display " + */ + if (ctx.wl_display != 0){ + wl_display_disconnect(ctx.wl_display); + } + + return(0); +} + + +/*libdecor.so, libdecor-gtk.so*/ +/* + * Copyright © 2012 Collabora, Ltd. + * Copyright © 2012 Intel Corporation + * Copyright © 2017-2018 Red Hat Inc. + * Copyright © 2018 Jonas Ådahl + * Copyright © 2021 Christian Rauch + * Copyright © 2024 Colin Kinloch + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +//#include "config.h" +/* Version number of package */ +#define VERSION "0.2.2" + +/* Plugin directiory path */ +#define LIBDECOR_PLUGIN_DIR "/usr/local/lib/x86_64-linux-gnu/libdecor/plugins-1" + +/* Plugin API version */ +#define LIBDECOR_PLUGIN_API_VERSION 1 + +#define HAS_DBUS + +/* #undef HAVE_MKOSTEMP */ +#define HAVE_POSIX_FALLOCATE +#define HAVE_MEMFD_CREATE +#define HAVE_GETTID + + +//#include "libdecor-fallback.h" +#ifndef LIBDECOR_FALLBACK_H +#define LIBDECOR_FALLBACK_H + +struct libdecor_plugin * +libdecor_fallback_plugin_new(struct libdecor *context); + +#endif /* LIBDECOR_FALLBACK_H */ + + +//#include "libdecor-plugin.h" +#ifndef LIBDECOR_PLUGIN_H +#define LIBDECOR_PLUGIN_H + +struct libdecor_frame_private; + +struct libdecor_frame { + struct libdecor_frame_private *priv; + struct wl_list link; +}; + +struct libdecor_plugin_private; + +struct libdecor_plugin { + struct libdecor_plugin_private *priv; +}; + +#define LIBDECOR_PLUGIN_PRIORITY_HIGH 1000 +#define LIBDECOR_PLUGIN_PRIORITY_MEDIUM 100 +#define LIBDECOR_PLUGIN_PRIORITY_LOW 0 + +struct libdecor_plugin_priority { + const char *desktop; + int priority; +}; + +struct libdecor_plugin_interface { + void (* destroy)(struct libdecor_plugin *plugin); + + int (* get_fd)(struct libdecor_plugin *plugin); + int (* dispatch)(struct libdecor_plugin *plugin, + int timeout); + + void (* set_handle_application_cursor)(struct libdecor_plugin *plugin, + bool handle_cursor); + + struct libdecor_frame * (* frame_new)(struct libdecor_plugin *plugin); + void (* frame_free)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame); + void (* frame_commit)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration); + void (*frame_property_changed)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame); + void (* frame_popup_grab)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name); + void (* frame_popup_ungrab)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name); + + bool (* frame_get_border_size)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_configuration *configuration, + int *left, + int *right, + int *top, + int *bottom); + + /* Reserved */ + void (* reserved0)(void); + void (* reserved1)(void); + void (* reserved2)(void); + void (* reserved3)(void); + void (* reserved4)(void); + void (* reserved5)(void); + void (* reserved6)(void); + void (* reserved7)(void); + void (* reserved8)(void); + void (* reserved9)(void); +}; + +struct wl_surface * +libdecor_frame_get_wl_surface(struct libdecor_frame *frame); + +int +libdecor_frame_get_content_width(struct libdecor_frame *frame); + +int +libdecor_frame_get_content_height(struct libdecor_frame *frame); + +enum libdecor_window_state +libdecor_frame_get_window_state(struct libdecor_frame *frame); + +enum libdecor_capabilities +libdecor_frame_get_capabilities(const struct libdecor_frame *frame); + +void +libdecor_frame_dismiss_popup(struct libdecor_frame *frame, + const char *seat_name); + +void +libdecor_frame_toplevel_commit(struct libdecor_frame *frame); + +struct wl_display * +libdecor_get_wl_display(struct libdecor *context); + +void +libdecor_notify_plugin_ready(struct libdecor *context); + +void +libdecor_notify_plugin_error(struct libdecor *context, + enum libdecor_error error, + const char *__restrict fmt, + ...); + +int +libdecor_state_get_content_width(struct libdecor_state *state); + +int +libdecor_state_get_content_height(struct libdecor_state *state); + +enum libdecor_window_state +libdecor_state_get_window_state(struct libdecor_state *state); + +int +libdecor_plugin_init(struct libdecor_plugin *plugin, + struct libdecor *context, + struct libdecor_plugin_interface *iface); + +void +libdecor_plugin_release(struct libdecor_plugin *plugin); + +#endif /* LIBDECOR_PLUGIN_H */ + +//#include "utils.h" +#ifndef UTILS_H +#define UTILS_H + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#ifndef ARRAY_LENGTH +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#endif /* UTILS_H */ + + +//#include "libdecor.c" +struct libdecor_state { + enum libdecor_window_state window_state; + + int content_width; + int content_height; +}; + +struct libdecor_limits { + int min_width; + int min_height; + int max_width; + int max_height; +}; + +struct libdecor_configuration { + uint32_t serial; + + bool has_window_state; + enum libdecor_window_state window_state; + + bool has_size; + int window_width; + int window_height; +}; + +struct libdecor_frame_private { + int ref_count; + + struct libdecor *context; + + struct wl_surface *wl_surface; + + void *user_data; + + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct zxdg_toplevel_decoration_v1 *toplevel_decoration; + + bool pending_map; + + struct { + char *app_id; + char *title; + struct libdecor_limits content_limits; + struct xdg_toplevel *parent; + } state; + + struct libdecor_configuration *pending_configuration; + + int content_width; + int content_height; + + enum libdecor_window_state window_state; + + bool has_decoration_mode; + enum zxdg_toplevel_decoration_v1_mode decoration_mode; + + enum libdecor_capabilities capabilities; + + enum libdecor_wm_capabilities wm_capabilities; + + /* original limits for interactive resize */ + struct libdecor_limits interactive_limits; + + bool visible; +}; + +struct libdecor_plugin_private { + struct libdecor_plugin_interface *iface; +}; + +/* gather all states at which a window is non-floating */ +static const enum libdecor_window_state states_non_floating = +LIBDECOR_WINDOW_STATE_MAXIMIZED | LIBDECOR_WINDOW_STATE_FULLSCREEN | +LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT | +LIBDECOR_WINDOW_STATE_TILED_TOP | LIBDECOR_WINDOW_STATE_TILED_BOTTOM; + +static bool +streql(const char *str1, const char *str2) +{ + return (str1 && str2) && (strcmp(str1, str2) == 0); +} + + +static void +do_map(struct libdecor_frame *frame); + +static bool +state_is_floating(enum libdecor_window_state window_state) +{ + return !(window_state & states_non_floating); +} + +static void +constrain_content_size(const struct libdecor_frame *frame, + int *width, + int *height) +{ + const struct libdecor_limits lim = frame->priv->state.content_limits; + + if (lim.min_width > 0) + *width = MAX(lim.min_width, *width); + if (lim.max_width > 0) + *width = MIN(*width, lim.max_width); + + if (lim.min_height > 0) + *height = MAX(lim.min_height, *height); + if (lim.max_height > 0) + *height = MIN(*height, lim.max_height); +} + +static bool +frame_has_visible_client_side_decoration(struct libdecor_frame *frame) +{ + /* visibility by client configuration */ + const bool vis_client = frame->priv->visible; + /* visibility by compositor configuration */ + const bool vis_server = (frame->priv->decoration_mode == + ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); + + return vis_client && vis_server; +} + +int +libdecor_state_get_content_width(struct libdecor_state *state) +{ + return state->content_width; +} + +int +libdecor_state_get_content_height(struct libdecor_state *state) +{ + return state->content_height; +} + +enum libdecor_window_state +libdecor_state_get_window_state(struct libdecor_state *state) +{ + return state->window_state; +} + +struct libdecor_state * +libdecor_state_new(int width, + int height) +{ + struct libdecor_state *state; + + state = calloc(1, sizeof *state); + state->content_width = width; + state->content_height = height; + + return state; +} + +void +libdecor_state_free(struct libdecor_state *state) +{ + free(state); +} + +static struct libdecor_configuration * +libdecor_configuration_new(void) +{ + struct libdecor_configuration *configuration; + + configuration = calloc(1, sizeof *configuration); + + return configuration; +} + +static void +libdecor_configuration_free(struct libdecor_configuration *configuration) +{ + free(configuration); +} + +static bool +frame_get_window_size_for(struct libdecor_frame *frame, + struct libdecor_state *state, + int *window_width, + int *window_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + *window_width = state->content_width; + *window_height = state->content_height; + + if (frame_has_visible_client_side_decoration(frame) && + plugin->priv->iface->frame_get_border_size) { + int left, right, top, bottom; + if (!plugin->priv->iface->frame_get_border_size( + plugin, frame, NULL, &left, &right, &top, &bottom)) + return false; + *window_width += left + right; + *window_height += top + bottom; + } + + return true; +} + +static void +frame_set_window_geometry(struct libdecor_frame *frame, + int32_t content_width, int32_t content_height) +{ + struct libdecor_plugin *plugin = frame->priv->context->plugin; + int x, y, width, height; + int left, right, top, bottom; + + if (frame_has_visible_client_side_decoration(frame) && + plugin->priv->iface->frame_get_border_size(plugin, frame, + NULL, + &left, &right, + &top, &bottom)) { + x = -left; + y = -top; + width = content_width + left + right; + height = content_height + top + bottom; + } else { + x = 0; + y = 0; + width = content_width; + height = content_height; + } + + xdg_surface_set_window_geometry(frame->priv->xdg_surface, x, y, width, height); +} + +bool +libdecor_configuration_get_content_size(struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *width, + int *height) +{ + struct libdecor_plugin *plugin = frame->priv->context->plugin; + + /* get configured toplevel dimensions */ + if (!configuration->has_size) + return false; + + if (configuration->window_width == 0 || configuration->window_height == 0) + return false; + + *width = configuration->window_width; + *height = configuration->window_height; + + /* remove plugin-specific border size */ + if (frame_has_visible_client_side_decoration(frame) && + plugin->priv->iface->frame_get_border_size) { + int left, right, top, bottom; + + /* Update window state for correct border size calculation */ + frame->priv->window_state = configuration->window_state; + if (!plugin->priv->iface->frame_get_border_size( + plugin, frame, configuration, &left, &right, &top, &bottom)) + return false; + + *width -= (left + right); + *height -= (top + bottom); + } + + /* constrain content dimensions manually */ + if (state_is_floating(configuration->window_state)) { + constrain_content_size(frame, width, height); + } + + return true; +} + +bool +libdecor_configuration_get_window_state(struct libdecor_configuration *configuration, + enum libdecor_window_state *window_state) +{ + if (!configuration->has_window_state) + return false; + + *window_state = configuration->window_state; + return true; +} + +static void +xdg_surface_configure(void *user_data, + struct xdg_surface *xdg_surface, + uint32_t serial) +{ + struct libdecor_frame *frame = user_data; + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor_configuration *configuration; + + configuration = frame_priv->pending_configuration; + frame_priv->pending_configuration = NULL; + + if (!configuration) + configuration = libdecor_configuration_new(); + + configuration->serial = serial; + + libdecorevent__frame_configure(frame, configuration, frame_priv->user_data); + + libdecor_configuration_free(configuration); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + xdg_surface_configure, +}; + +static enum libdecor_window_state +parse_states(struct wl_array *states) +{ + enum libdecor_window_state pending_state = LIBDECOR_WINDOW_STATE_NONE; + uint32_t *p; + + wl_array_for_each(p, states) { + enum xdg_toplevel_state state = *p; + + switch (state) { + case XDG_TOPLEVEL_STATE_FULLSCREEN: + pending_state |= LIBDECOR_WINDOW_STATE_FULLSCREEN; + break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: + pending_state |= LIBDECOR_WINDOW_STATE_MAXIMIZED; + break; + case XDG_TOPLEVEL_STATE_ACTIVATED: + pending_state |= LIBDECOR_WINDOW_STATE_ACTIVE; + break; + case XDG_TOPLEVEL_STATE_TILED_LEFT: + pending_state |= LIBDECOR_WINDOW_STATE_TILED_LEFT; + break; + case XDG_TOPLEVEL_STATE_TILED_RIGHT: + pending_state |= LIBDECOR_WINDOW_STATE_TILED_RIGHT; + break; + case XDG_TOPLEVEL_STATE_TILED_TOP: + pending_state |= LIBDECOR_WINDOW_STATE_TILED_TOP; + break; + case XDG_TOPLEVEL_STATE_TILED_BOTTOM: + pending_state |= LIBDECOR_WINDOW_STATE_TILED_BOTTOM; + break; + case XDG_TOPLEVEL_STATE_RESIZING: + pending_state |= LIBDECOR_WINDOW_STATE_RESIZING; + break; +#ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION + case XDG_TOPLEVEL_STATE_SUSPENDED: + pending_state |= LIBDECOR_WINDOW_STATE_SUSPENDED; + break; +#endif +#ifdef XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION + case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT: + pending_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_LEFT; + break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT: + pending_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_RIGHT; + break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP: + pending_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_TOP; + break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: + pending_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_BOTTOM; + break; +#endif + default: + break; + } + } + + return pending_state; +} + +static void +xdg_toplevel_configure(void *user_data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array *states) +{ + struct libdecor_frame *frame = user_data; + struct libdecor_frame_private *frame_priv = frame->priv; + enum libdecor_window_state window_state; + + window_state = parse_states(states); + + frame_priv->pending_configuration = libdecor_configuration_new(); + + frame_priv->pending_configuration->has_size = true; + frame_priv->pending_configuration->window_width = width; + frame_priv->pending_configuration->window_height = height; + + frame_priv->pending_configuration->has_window_state = true; + frame_priv->pending_configuration->window_state = window_state; +} + +static void +xdg_toplevel_close(void *user_data, + struct xdg_toplevel *xdg_toplevel) +{ + struct libdecor_frame *frame = user_data; + struct libdecor_frame_private *frame_priv = frame->priv; + + libdecorevent__frame_close(frame, frame_priv->user_data); +} + +#ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION +static void +xdg_toplevel_configure_bounds(void *user_data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height) +{ + struct libdecor_frame *frame = user_data; + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + int left = 0, top = 0, right = 0, bottom = 0; + + if (frame_has_visible_client_side_decoration(frame) && + plugin->priv->iface->frame_get_border_size) { + plugin->priv->iface->frame_get_border_size(plugin, frame, NULL, + &left, &right, &top, &bottom); + } + + width -= left + right; + height -= top + bottom; + libdecorevent__frame_bounds(frame, width, height, frame_priv->user_data); +} +#endif + +#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION +static void +xdg_toplevel_wm_capabilities(void *user_data, + struct xdg_toplevel *xdg_toplevel, + struct wl_array *capabilities) +{ + struct libdecor_frame *frame = user_data; + struct libdecor_frame_private *frame_priv = frame->priv; + enum xdg_toplevel_wm_capabilities *wm_cap; + + frame_priv->wm_capabilities = 0; + + wl_array_for_each(wm_cap, capabilities) { + switch (*wm_cap) { + case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU: + frame_priv->wm_capabilities |= LIBDECOR_WM_CAPABILITIES_WINDOW_MENU; + break; + case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: + frame_priv->wm_capabilities |= LIBDECOR_WM_CAPABILITIES_MAXIMIZE; + break; + case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: + frame_priv->wm_capabilities |= LIBDECOR_WM_CAPABILITIES_FULLSCREEN; + break; + case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: + frame_priv->wm_capabilities |= LIBDECOR_WM_CAPABILITIES_MINIMIZE; + break; + default: + break; + } + } +} +#endif + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + xdg_toplevel_configure, + xdg_toplevel_close, +#ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION + xdg_toplevel_configure_bounds, +#endif +#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION + xdg_toplevel_wm_capabilities, +#endif +}; + +static void +toplevel_decoration_configure( + void *data, + struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, + uint32_t mode) +{ + struct libdecor_frame_private *frame_priv = (struct libdecor_frame_private *)data; + /* Ignore any _configure calls after the first, they will be + * from our set_mode call. */ + if (!frame_priv->has_decoration_mode) { + frame_priv->has_decoration_mode = true; + frame_priv->decoration_mode = mode; + } +} + +static const struct zxdg_toplevel_decoration_v1_listener +xdg_toplevel_decoration_listener = { + toplevel_decoration_configure, +}; + +void +libdecor_frame_create_xdg_decoration(struct libdecor_frame_private *frame_priv) +{ + if (!frame_priv->context->decoration_manager) + return; + + frame_priv->toplevel_decoration = + zxdg_decoration_manager_v1_get_toplevel_decoration( + frame_priv->context->decoration_manager, + frame_priv->xdg_toplevel); + + zxdg_toplevel_decoration_v1_add_listener( + frame_priv->toplevel_decoration, + &xdg_toplevel_decoration_listener, + frame_priv); +} + +static void +init_shell_surface(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + + if (frame_priv->xdg_surface) + return; + + frame_priv->xdg_surface = + xdg_wm_base_get_xdg_surface(context->xdg_wm_base, + frame_priv->wl_surface); + xdg_surface_add_listener(frame_priv->xdg_surface, + &xdg_surface_listener, + frame); + + frame_priv->xdg_toplevel = + xdg_surface_get_toplevel(frame_priv->xdg_surface); + xdg_toplevel_add_listener(frame_priv->xdg_toplevel, + &xdg_toplevel_listener, + frame); + + frame_priv->decoration_mode = + ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + frame_priv->toplevel_decoration = NULL; + libdecor_frame_create_xdg_decoration(frame_priv); + + if (frame_priv->state.parent) { + xdg_toplevel_set_parent(frame_priv->xdg_toplevel, + frame_priv->state.parent); + } + if (frame_priv->state.title) { + xdg_toplevel_set_title(frame_priv->xdg_toplevel, + frame_priv->state.title); + } + if (frame_priv->state.app_id) { + xdg_toplevel_set_app_id(frame_priv->xdg_toplevel, + frame_priv->state.app_id); + } + + if (frame_priv->pending_map) + do_map(frame); +} + +struct libdecor_frame * +libdecor_decorate(struct libdecor *context, + struct wl_surface *wl_surface, + void *user_data) +{ + struct libdecor_plugin *plugin = context->plugin; + struct libdecor_frame *frame; + struct libdecor_frame_private *frame_priv; + + if (context->has_error) + return NULL; + + frame = plugin->priv->iface->frame_new(plugin); + if (!frame) + return NULL; + + frame_priv = calloc(1, sizeof *frame_priv); + frame->priv = frame_priv; + + frame_priv->ref_count = 1; + frame_priv->context = context; + + frame_priv->wl_surface = wl_surface; + frame_priv->user_data = user_data; + frame_priv->wm_capabilities = LIBDECOR_WM_CAPABILITIES_WINDOW_MENU | + LIBDECOR_WM_CAPABILITIES_MAXIMIZE | + LIBDECOR_WM_CAPABILITIES_FULLSCREEN | + LIBDECOR_WM_CAPABILITIES_MINIMIZE; + + wl_list_insert(&context->frames, &frame->link); + + libdecor_frame_set_capabilities(frame, + LIBDECOR_ACTION_MOVE | + LIBDECOR_ACTION_RESIZE | + LIBDECOR_ACTION_MINIMIZE | + LIBDECOR_ACTION_FULLSCREEN | + LIBDECOR_ACTION_CLOSE); + + frame_priv->visible = true; + + if (context->init_done) + init_shell_surface(frame); + + return frame; +} + +void +libdecor_frame_ref(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->ref_count++; +} + +void +libdecor_frame_unref(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->ref_count--; + if (frame_priv->ref_count == 0) { + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + if (context->decoration_manager && frame_priv->toplevel_decoration) { + zxdg_toplevel_decoration_v1_destroy(frame_priv->toplevel_decoration); + frame_priv->toplevel_decoration = NULL; + } + + wl_list_remove(&frame->link); + + if (frame_priv->xdg_toplevel) + xdg_toplevel_destroy(frame_priv->xdg_toplevel); + if (frame_priv->xdg_surface) + xdg_surface_destroy(frame_priv->xdg_surface); + + plugin->priv->iface->frame_free(plugin, frame); + + free(frame_priv->state.title); + free(frame_priv->state.app_id); + + free(frame_priv); + + free(frame); + } +} + +void * +libdecor_frame_get_user_data(struct libdecor_frame *frame) +{ + return frame->priv->user_data; +} + +void +libdecor_frame_set_user_data(struct libdecor_frame *frame, void *user_data) +{ + frame->priv->user_data = user_data; +} + +void +libdecor_frame_set_visibility(struct libdecor_frame *frame, + bool visible) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + frame_priv->visible = visible; + + /* enable/disable decorations that are managed by the compositor. + * Note that, as of xdg_decoration v1, this is just a hint and there is + * no reliable way of disabling all decorations. In practice this should + * work but per spec this is not guaranteed. + * + * See also: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/17 + */ + if (context->decoration_manager && + frame_priv->toplevel_decoration && + frame_priv->has_decoration_mode && + frame_priv->decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) { + zxdg_toplevel_decoration_v1_set_mode(frame_priv->toplevel_decoration, + frame->priv->visible + ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE + : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); + } + + if (frame_priv->content_width <= 0 || + frame_priv->content_height <= 0) + return; + + /* enable/disable decorations that are managed by a plugin */ + if (frame_has_visible_client_side_decoration(frame)) { + /* show client-side decorations */ + plugin->priv->iface->frame_commit(plugin, frame, NULL, NULL); + } else { + /* destroy client-side decorations */ + plugin->priv->iface->frame_free(plugin, frame); + } + + frame_set_window_geometry(frame, + frame_priv->content_width, + frame_priv->content_height); + + libdecor_frame_toplevel_commit(frame); +} + +bool +libdecor_frame_is_visible(struct libdecor_frame *frame) +{ + return frame->priv->visible; +} + +void +libdecor_frame_set_parent(struct libdecor_frame *frame, + struct libdecor_frame *parent) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + if (!frame_priv->xdg_toplevel) + return; + + frame_priv->state.parent = parent ? parent->priv->xdg_toplevel : NULL; + + xdg_toplevel_set_parent(frame_priv->xdg_toplevel, + frame_priv->state.parent); +} + +void +libdecor_frame_set_title(struct libdecor_frame *frame, + const char *title) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor_plugin *plugin = frame_priv->context->plugin; + + if (!streql(frame_priv->state.title, title)) { + free(frame_priv->state.title); + frame_priv->state.title = strdup(title); + + if (!frame_priv->xdg_toplevel) + return; + + xdg_toplevel_set_title(frame_priv->xdg_toplevel, title); + + plugin->priv->iface->frame_property_changed(plugin, frame); + } +} + +const char * +libdecor_frame_get_title(struct libdecor_frame *frame) +{ + return frame->priv->state.title; +} + +void +libdecor_frame_set_app_id(struct libdecor_frame *frame, + const char *app_id) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + free(frame_priv->state.app_id); + frame_priv->state.app_id = strdup(app_id); + + if (!frame_priv->xdg_toplevel) + return; + + xdg_toplevel_set_app_id(frame_priv->xdg_toplevel, app_id); +} + +static void +notify_on_capability_change(struct libdecor_frame *frame, + const enum libdecor_capabilities old_capabilities) +{ + struct libdecor_plugin *plugin = frame->priv->context->plugin; + struct libdecor_state *state; + + if (frame->priv->capabilities == old_capabilities) + return; + + if (frame->priv->content_width == 0 || + frame->priv->content_height == 0) + return; + + plugin->priv->iface->frame_property_changed(plugin, frame); + + if (!libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE)) { + frame->priv->interactive_limits = frame->priv->state.content_limits; + /* set fixed window size */ + libdecor_frame_set_min_content_size(frame, + frame->priv->content_width, + frame->priv->content_height); + libdecor_frame_set_max_content_size(frame, + frame->priv->content_width, + frame->priv->content_height); + } else { + /* restore old limits */ + frame->priv->state.content_limits = frame->priv->interactive_limits; + } + + state = libdecor_state_new(frame->priv->content_width, + frame->priv->content_height); + libdecor_frame_commit(frame, state, NULL); + libdecor_state_free(state); + + libdecor_frame_toplevel_commit(frame); +} + +void +libdecor_frame_set_capabilities(struct libdecor_frame *frame, + enum libdecor_capabilities capabilities) +{ + const enum libdecor_capabilities old_capabilities = + frame->priv->capabilities; + + frame->priv->capabilities |= capabilities; + + notify_on_capability_change(frame, old_capabilities); +} + +void +libdecor_frame_unset_capabilities(struct libdecor_frame *frame, + enum libdecor_capabilities capabilities) +{ + const enum libdecor_capabilities old_capabilities = + frame->priv->capabilities; + + frame->priv->capabilities &= ~capabilities; + + notify_on_capability_change(frame, old_capabilities); +} + +bool +libdecor_frame_has_capability(struct libdecor_frame *frame, + enum libdecor_capabilities capability) +{ + return frame->priv->capabilities & capability; +} + +void +libdecor_frame_popup_grab(struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + plugin->priv->iface->frame_popup_grab(plugin, frame, seat_name); +} + +void +libdecor_frame_popup_ungrab(struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + plugin->priv->iface->frame_popup_ungrab(plugin, frame, seat_name); +} + +void +libdecor_frame_dismiss_popup(struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + libdecorevent__frame_dismiss_popup(frame, seat_name, frame_priv->user_data); +} + +void +libdecor_frame_show_window_menu(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial, + int x, + int y) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + if (!frame_priv->xdg_toplevel) { + fprintf(stderr, "Can't show window menu before being mapped\n"); + return; + } + + xdg_toplevel_show_window_menu(frame_priv->xdg_toplevel, + wl_seat, serial, + x, y); +} + +void +libdecor_frame_translate_coordinate(struct libdecor_frame *frame, + int content_x, + int content_y, + int *frame_x, + int *frame_y) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + *frame_x = content_x; + *frame_y = content_y; + + if (frame_has_visible_client_side_decoration(frame) && + plugin->priv->iface->frame_get_border_size) { + int left, top; + plugin->priv->iface->frame_get_border_size(plugin, frame, NULL, + &left, NULL, &top, NULL); + *frame_x += left; + *frame_y += top; + } +} + +void +libdecor_frame_set_min_content_size(struct libdecor_frame *frame, + int content_width, + int content_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->state.content_limits.min_width = content_width; + frame_priv->state.content_limits.min_height = content_height; +} + +void +libdecor_frame_set_max_content_size(struct libdecor_frame *frame, + int content_width, + int content_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->state.content_limits.max_width = content_width; + frame_priv->state.content_limits.max_height = content_height; +} + +void +libdecor_frame_get_min_content_size(const struct libdecor_frame *frame, + int *content_width, + int *content_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + *content_width = frame_priv->state.content_limits.min_width; + *content_height = frame_priv->state.content_limits.min_height; +} + +void +libdecor_frame_get_max_content_size(const struct libdecor_frame *frame, + int *content_width, + int *content_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + *content_width = frame_priv->state.content_limits.max_width; + *content_height = frame_priv->state.content_limits.max_height; +} + +enum libdecor_capabilities +libdecor_frame_get_capabilities(const struct libdecor_frame *frame) +{ + return frame->priv->capabilities; +} + +enum xdg_toplevel_resize_edge +edge_to_xdg_edge(enum libdecor_resize_edge edge) +{ + switch (edge) { + case LIBDECOR_RESIZE_EDGE_NONE: + return XDG_TOPLEVEL_RESIZE_EDGE_NONE; + case LIBDECOR_RESIZE_EDGE_TOP: + return XDG_TOPLEVEL_RESIZE_EDGE_TOP; + case LIBDECOR_RESIZE_EDGE_BOTTOM: + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + case LIBDECOR_RESIZE_EDGE_LEFT: + return XDG_TOPLEVEL_RESIZE_EDGE_LEFT; + case LIBDECOR_RESIZE_EDGE_TOP_LEFT: + return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; + case LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT: + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; + case LIBDECOR_RESIZE_EDGE_RIGHT: + return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; + case LIBDECOR_RESIZE_EDGE_TOP_RIGHT: + return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; + case LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT: + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; + } + + abort(); +} + +void +libdecor_frame_resize(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial, + enum libdecor_resize_edge edge) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + enum xdg_toplevel_resize_edge xdg_edge; + + xdg_edge = edge_to_xdg_edge(edge); + xdg_toplevel_resize(frame_priv->xdg_toplevel, + wl_seat, serial, xdg_edge); +} + +void +libdecor_frame_move(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + xdg_toplevel_move(frame_priv->xdg_toplevel, wl_seat, serial); +} + +void +libdecor_frame_set_minimized(struct libdecor_frame *frame) +{ + xdg_toplevel_set_minimized(frame->priv->xdg_toplevel); +} + +void +libdecor_frame_set_maximized(struct libdecor_frame *frame) +{ + xdg_toplevel_set_maximized(frame->priv->xdg_toplevel); +} + +void +libdecor_frame_unset_maximized(struct libdecor_frame *frame) +{ + xdg_toplevel_unset_maximized(frame->priv->xdg_toplevel); +} + +void +libdecor_frame_set_fullscreen(struct libdecor_frame *frame, + struct wl_output *output) +{ + xdg_toplevel_set_fullscreen(frame->priv->xdg_toplevel, output); +} + +void +libdecor_frame_unset_fullscreen(struct libdecor_frame *frame) +{ + xdg_toplevel_unset_fullscreen(frame->priv->xdg_toplevel); +} + +bool +libdecor_frame_is_floating(struct libdecor_frame *frame) +{ + return state_is_floating(frame->priv->window_state); +} + +void +libdecor_frame_close(struct libdecor_frame *frame) +{ + xdg_toplevel_close(frame, frame->priv->xdg_toplevel); +} + +bool +valid_limits(struct libdecor_frame_private *frame_priv) +{ + if (frame_priv->state.content_limits.min_width > 0 && + frame_priv->state.content_limits.max_width > 0 && + frame_priv->state.content_limits.min_width > + frame_priv->state.content_limits.max_width) + return false; + + if (frame_priv->state.content_limits.min_height > 0 && + frame_priv->state.content_limits.max_height > 0 && + frame_priv->state.content_limits.min_height > + frame_priv->state.content_limits.max_height) + return false; + + return true; +} + +static void +libdecor_frame_apply_limits(struct libdecor_frame *frame, + enum libdecor_window_state window_state) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + if (!valid_limits(frame_priv)) { + libdecor_notify_plugin_error( + frame_priv->context, + LIBDECOR_ERROR_INVALID_FRAME_CONFIGURATION, + "minimum size (%i,%i) must be smaller than maximum size (%i,%i)", + frame_priv->state.content_limits.min_width, + frame_priv->state.content_limits.min_height, + frame_priv->state.content_limits.max_width, + frame_priv->state.content_limits.max_height); + } + + /* If the frame is configured as non-resizable before the first + * configure event is received, we have to manually set the min/max + * limits with the configured content size afterwards. */ + if (!libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE)) { + frame_priv->state.content_limits.min_width = + frame_priv->content_width; + frame_priv->state.content_limits.max_width = + frame_priv->content_width; + + frame_priv->state.content_limits.min_height = + frame_priv->content_height; + frame_priv->state.content_limits.max_height = + frame_priv->content_height; + } + + if (frame_priv->state.content_limits.min_width > 0 && + frame_priv->state.content_limits.min_height > 0) { + struct libdecor_state state_min; + int win_min_width, win_min_height; + + state_min.content_width = frame_priv->state.content_limits.min_width; + state_min.content_height = frame_priv->state.content_limits.min_height; + state_min.window_state = window_state; + + frame_get_window_size_for(frame, &state_min, + &win_min_width, &win_min_height); + xdg_toplevel_set_min_size(frame_priv->xdg_toplevel, + win_min_width, win_min_height); + } else { + xdg_toplevel_set_min_size(frame_priv->xdg_toplevel, 0, 0); + } + + if (frame_priv->state.content_limits.max_width > 0 && + frame_priv->state.content_limits.max_height > 0) { + struct libdecor_state state_max; + int win_max_width, win_max_height; + + state_max.content_width = frame_priv->state.content_limits.max_width; + state_max.content_height = frame_priv->state.content_limits.max_height; + state_max.window_state = window_state; + + frame_get_window_size_for(frame, &state_max, + &win_max_width, &win_max_height); + xdg_toplevel_set_max_size(frame_priv->xdg_toplevel, + win_max_width, win_max_height); + } else { + xdg_toplevel_set_max_size(frame_priv->xdg_toplevel, 0, 0); + } +} + +static void +libdecor_frame_apply_state(struct libdecor_frame *frame, + struct libdecor_state *state) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->content_width = state->content_width; + frame_priv->content_height = state->content_height; + + libdecor_frame_apply_limits(frame, state->window_state); +} + +void +libdecor_frame_toplevel_commit(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + libdecorevent__frame_commit(frame, frame_priv->user_data); +} + +void +libdecor_frame_commit(struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + if (configuration && configuration->has_window_state) { + frame_priv->window_state = configuration->window_state; + state->window_state = configuration->window_state; + } else { + state->window_state = frame_priv->window_state; + } + + libdecor_frame_apply_state(frame, state); + + /* switch between decoration modes */ + if (frame_has_visible_client_side_decoration(frame)) { + plugin->priv->iface->frame_commit(plugin, frame, state, + configuration); + } else { + plugin->priv->iface->frame_free(plugin, frame); + } + + frame_set_window_geometry(frame, + frame_priv->content_width, + frame_priv->content_height); + + if (configuration) { + xdg_surface_ack_configure(frame_priv->xdg_surface, + configuration->serial); + } +} + +static void +do_map(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->pending_map = false; + wl_surface_commit(frame_priv->wl_surface); +} + +void +libdecor_frame_map(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + if (!frame_priv->xdg_surface) { + frame_priv->pending_map = true; + return; + } + + do_map(frame); +} + +struct wl_surface * +libdecor_frame_get_wl_surface(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->wl_surface; +} + +struct xdg_surface * +libdecor_frame_get_xdg_surface(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->xdg_surface; +} + +struct xdg_toplevel * +libdecor_frame_get_xdg_toplevel(struct libdecor_frame *frame) +{ + return frame->priv->xdg_toplevel; +} + +void +libdecor_set_handle_application_cursor(struct libdecor *context, + bool handle_cursor) +{ + struct libdecor_plugin *plugin = context->plugin; + + plugin->priv->iface->set_handle_application_cursor(plugin, + handle_cursor); +} + +int +libdecor_frame_get_content_width(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->content_width; +} + +int +libdecor_frame_get_content_height(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->content_height; +} + +enum libdecor_window_state +libdecor_frame_get_window_state(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->window_state; +} + +enum libdecor_wm_capabilities +libdecor_frame_get_wm_capabilities(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->wm_capabilities; +} + +int +libdecor_plugin_init(struct libdecor_plugin *plugin, + struct libdecor *context, + struct libdecor_plugin_interface *iface) +{ + plugin->priv = calloc(1, sizeof (struct libdecor_plugin_private)); + if (!plugin->priv) + return -1; + + plugin->priv->iface = iface; + + return 0; +} + +void +libdecor_plugin_release(struct libdecor_plugin *plugin) +{ + free(plugin->priv); +} + +static void +xdg_wm_base_ping(void *user_data, + struct xdg_wm_base *xdg_wm_base, + uint32_t serial) +{ + xdg_wm_base_pong(xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_wm_base_ping, +}; + +static void +init_xdg_wm_base(struct libdecor *context, + uint32_t id, + uint32_t version) +{ + uint32_t desired_version = 2; + + /* Find the required version for the available features. */ +#ifdef XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION + desired_version = MAX(desired_version, XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION); +#endif +#ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION + desired_version = MAX(desired_version, XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION); +#endif +#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION + desired_version = MAX(desired_version, XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION); +#endif +#ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION + desired_version = MAX(desired_version, XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION); +#endif + + context->xdg_wm_base = wl_registry_bind(context->wl_registry, + id, + &xdg_wm_base_interface, + MIN(version, desired_version)); + xdg_wm_base_add_listener(context->xdg_wm_base, + &xdg_wm_base_listener, + context); +} + +static void +scrap__registry_handle_global(void *user_data, + struct wl_registry *wl_registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + struct libdecor *context = user_data; + + if (!strcmp(interface, xdg_wm_base_interface.name)) { + init_xdg_wm_base(context, id, version); + } else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)) { + const char *force_csd = getenv("LIBDECOR_FORCE_CSD"); + + if (force_csd && atoi(force_csd)) { + return; + } + + context->decoration_manager = wl_registry_bind( + context->wl_registry, id, + &zxdg_decoration_manager_v1_interface, + MIN(version,2)); + } +} + +static void +scrap__registry_handle_global_remove(void *user_data, + struct wl_registry *wl_registry, + uint32_t name) +{ +} + +const struct wl_registry_listener scrap__registry_listener = { + scrap__registry_handle_global, + scrap__registry_handle_global_remove +}; + +static bool +is_compositor_compatible(struct libdecor *context) +{ + if (!context->xdg_wm_base) + return false; + + return true; +} + +static void +notify_error(struct libdecor *context, + enum libdecor_error error, + const char *message) +{ + context->has_error = true; + libdecorevent__error(context, error, message); + context->plugin->priv->iface->destroy(context->plugin); +} + +static void +finish_init(struct libdecor *context) +{ + struct libdecor_frame *frame; + + wl_list_for_each(frame, &context->frames, link) + init_shell_surface(frame); +} + +static void +init_wl_display_callback(void *user_data, + struct wl_callback *callback, + uint32_t time) +{ + struct libdecor *context = user_data; + + context->init_done = true; + + wl_callback_destroy(callback); + context->init_callback = NULL; + + if (!is_compositor_compatible(context)) { + notify_error(context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "Compositor is missing required interfaces"); + } + + if (context->plugin_ready) { + finish_init(context); + } +} + +const struct wl_callback_listener init_wl_display_callback_listener = { + init_wl_display_callback +}; + +int +libdecor_dispatch(struct libdecor *context, + int timeout) +{ + struct libdecor_plugin *plugin = context->plugin; + + return plugin->priv->iface->dispatch(plugin, timeout); +} + +struct wl_display * +libdecor_get_wl_display(struct libdecor *context) +{ + return context->wl_display; +} + +void +libdecor_notify_plugin_ready(struct libdecor *context) +{ + context->plugin_ready = true; + + if (context->init_done) + finish_init(context); +} + +void +libdecor_notify_plugin_error(struct libdecor *context, + enum libdecor_error error, + const char *__restrict fmt, + ...) +{ + char *msg = NULL; + int nbytes = 0; + va_list argp; + + if (context->has_error) + return; + + va_start(argp, fmt); + nbytes = vasprintf(&msg, fmt, argp); + va_end(argp); + + if (nbytes>0) + notify_error(context, error, msg); + + if (msg) + free(msg); +} + +void +libdecor_unref(struct libdecor *context) +{ + context->ref_count--; + if (context->ref_count == 0) { + if (context->plugin) + context->plugin->priv->iface->destroy(context->plugin); + if (context->init_callback) + wl_callback_destroy(context->init_callback); + wl_registry_destroy(context->wl_registry); + if (context->xdg_wm_base) + xdg_wm_base_destroy(context->xdg_wm_base); + if (context->decoration_manager) + zxdg_decoration_manager_v1_destroy( + context->decoration_manager); + free(context); + } +} + +//#include "libdecor-fallback.c" +struct libdecor_plugin_fallback { + struct libdecor_plugin plugin; + struct libdecor *context; +}; + +static void +libdecor_plugin_fallback_destroy(struct libdecor_plugin *plugin) +{ + libdecor_plugin_release(plugin); + free(plugin); +} + +static int +libdecor_plugin_fallback_get_fd(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_fallback *plugin_fallback = + (struct libdecor_plugin_fallback *) plugin; + struct wl_display *wl_display = + libdecor_get_wl_display(plugin_fallback->context); + + return wl_display_get_fd(wl_display); +} + +static int +libdecor_plugin_fallback_dispatch(struct libdecor_plugin *plugin, + int timeout) +{ + struct libdecor_plugin_fallback *plugin_fallback = + (struct libdecor_plugin_fallback *) plugin; + struct wl_display *wl_display = + libdecor_get_wl_display(plugin_fallback->context); + struct pollfd fds[1]; + int ret; + int dispatch_count = 0; + + while (wl_display_prepare_read(wl_display) != 0) + dispatch_count += wl_display_dispatch_pending(wl_display); + + if (wl_display_flush(wl_display) < 0 && + errno != EAGAIN) { + wl_display_cancel_read(wl_display); + return -errno; + } + + fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN }; + + ret = poll(fds, ARRAY_SIZE (fds), timeout); + if (ret > 0) { + if (fds[0].revents & POLLIN) { + wl_display_read_events(wl_display); + dispatch_count += wl_display_dispatch_pending(wl_display); + return dispatch_count; + } else { + wl_display_cancel_read(wl_display); + return dispatch_count; + } + } else if (ret == 0) { + wl_display_cancel_read(wl_display); + return dispatch_count; + } else { + wl_display_cancel_read(wl_display); + return -errno; + } +} + +static void +libdecor_plugin_fallback_set_handle_application_cursor(struct libdecor_plugin *plugin, + bool handle_cursor) +{ +} + +static struct libdecor_frame * +libdecor_plugin_fallback_frame_new(struct libdecor_plugin *plugin) +{ + struct libdecor_frame *frame; + + frame = calloc(1, sizeof *frame); + + return frame; +} + +static void +libdecor_plugin_fallback_frame_free(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ +} + +static void +libdecor_plugin_fallback_frame_commit(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration) +{ +} + +static void +libdecor_plugin_fallback_frame_property_changed(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ +} + +static void +libdecor_plugin_fallback_frame_popup_grab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ +} + +static void +libdecor_plugin_fallback_frame_popup_ungrab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ +} + +static bool +libdecor_plugin_fallback_frame_get_border_size(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_configuration *configuration, + int *left, + int *right, + int *top, + int *bottom) +{ + if (left) + *left = 0; + if (right) + *right = 0; + if (top) + *top = 0; + if (bottom) + *bottom = 0; + + return true; +} + +static struct libdecor_plugin_interface fallback_plugin_iface = { + .destroy = libdecor_plugin_fallback_destroy, + .get_fd = libdecor_plugin_fallback_get_fd, + .dispatch = libdecor_plugin_fallback_dispatch, + + .set_handle_application_cursor = libdecor_plugin_fallback_set_handle_application_cursor, + + .frame_new = libdecor_plugin_fallback_frame_new, + .frame_free = libdecor_plugin_fallback_frame_free, + .frame_commit = libdecor_plugin_fallback_frame_commit, + .frame_property_changed = libdecor_plugin_fallback_frame_property_changed, + .frame_popup_grab = libdecor_plugin_fallback_frame_popup_grab, + .frame_popup_ungrab = libdecor_plugin_fallback_frame_popup_ungrab, + .frame_get_border_size = libdecor_plugin_fallback_frame_get_border_size, +}; + +struct libdecor_plugin * +libdecor_fallback_plugin_new(struct libdecor *context) +{ + struct libdecor_plugin_fallback *plugin; + + plugin = calloc(1, sizeof *plugin); + libdecor_plugin_init(&plugin->plugin, context, &fallback_plugin_iface); + plugin->context = context; + + libdecor_notify_plugin_ready(context); + + return &plugin->plugin; +} + +//#include "libdecor-cairo-blur.h" + +int +blur_surface(cairo_surface_t *surface, int margin); + +void +render_shadow(cairo_t *cr, cairo_surface_t *surface, + int x, int y, int width, int height, int margin, int top_margin); + + +//#include "libdecor-cairo-blur.c" +/* + * functions 'blur_surface' and 'render_shadow' from weston project: + * https://gitlab.freedesktop.org/wayland/weston/raw/master/shared/cairo-util.c + */ + +/** + * Compile-time computation of number of items in a hardcoded array. + * + * @param a the array being measured. + * @return the number of items hardcoded into the array. + */ +#ifndef ARRAY_LENGTH +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) +#endif + +int +blur_surface(cairo_surface_t *surface, int margin) +{ + int32_t width, height, stride, x, y, z, w; + uint8_t *src, *dst; + uint32_t *s, *d, a, p; + int i, j, k, size, half; + uint32_t kernel[71]; + double f; + + size = ARRAY_LENGTH(kernel); + width = cairo_image_surface_get_width(surface); + height = cairo_image_surface_get_height(surface); + stride = cairo_image_surface_get_stride(surface); + src = cairo_image_surface_get_data(surface); + + dst = malloc(height * stride); + if (dst == NULL) + return -1; + + half = size / 2; + a = 0; + for (i = 0; i < size; i++) { + f = (i - half); + kernel[i] = exp(- f * f / ARRAY_LENGTH(kernel)) * 10000; + a += kernel[i]; + } + + for (i = 0; i < height; i++) { + s = (uint32_t *) (src + i * stride); + d = (uint32_t *) (dst + i * stride); + for (j = 0; j < width; j++) { + if (margin < j && j < width - margin) { + d[j] = s[j]; + continue; + } + + x = 0; + y = 0; + z = 0; + w = 0; + for (k = 0; k < size; k++) { + if (j - half + k < 0 || j - half + k >= width) + continue; + p = s[j - half + k]; + + x += (p >> 24) * kernel[k]; + y += ((p >> 16) & 0xff) * kernel[k]; + z += ((p >> 8) & 0xff) * kernel[k]; + w += (p & 0xff) * kernel[k]; + } + d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; + } + } + + for (i = 0; i < height; i++) { + s = (uint32_t *) (dst + i * stride); + d = (uint32_t *) (src + i * stride); + for (j = 0; j < width; j++) { + if (margin <= i && i < height - margin) { + d[j] = s[j]; + continue; + } + + x = 0; + y = 0; + z = 0; + w = 0; + for (k = 0; k < size; k++) { + if (i - half + k < 0 || i - half + k >= height) + continue; + s = (uint32_t *) (dst + (i - half + k) * stride); + p = s[j]; + + x += (p >> 24) * kernel[k]; + y += ((p >> 16) & 0xff) * kernel[k]; + z += ((p >> 8) & 0xff) * kernel[k]; + w += (p & 0xff) * kernel[k]; + } + d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; + } + } + + free(dst); + cairo_surface_mark_dirty(surface); + + return 0; +} + +void +render_shadow(cairo_t *cr, cairo_surface_t *surface, + int x, int y, int width, int height, int margin, int top_margin) +{ + cairo_pattern_t *pattern; + cairo_matrix_t matrix; + int i, fx, fy, shadow_height, shadow_width; + + cairo_set_source_rgba(cr, 0, 0, 0, 0.45); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + pattern = cairo_pattern_create_for_surface (surface); + cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); + + for (i = 0; i < 4; i++) { + /* when fy is set, then we are working with lower corners, + * when fx is set, then we are working with right corners + * + * 00 ------- 01 + * | | + * | | + * 10 ------- 11 + */ + fx = i & 1; + fy = i >> 1; + + cairo_matrix_init_translate(&matrix, + -x + fx * (128 - width), + -y + fy * (128 - height)); + cairo_pattern_set_matrix(pattern, &matrix); + + shadow_width = margin; + shadow_height = fy ? margin : top_margin; + + /* if the shadows together are greater than the surface, we need + * to fix it - set the shadow size to the half of + * the size of surface. Also handle the case when the size is + * not divisible by 2. In that case we need one part of the + * shadow to be one pixel greater. !fy or !fx, respectively, + * will do the work. + */ + if (height < 2 * shadow_height) + shadow_height = (height + !fy) / 2; + + if (width < 2 * shadow_width) + shadow_width = (width + !fx) / 2; + + cairo_reset_clip(cr); + cairo_rectangle(cr, + x + fx * (width - shadow_width), + y + fy * (height - shadow_height), + shadow_width, shadow_height); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + + shadow_width = width - 2 * margin; + shadow_height = top_margin; + if (height < 2 * shadow_height) + shadow_height = height / 2; + + if (shadow_width > 0 && shadow_height) { + /* Top stretch */ + cairo_matrix_init_translate(&matrix, 60, 0); + cairo_matrix_scale(&matrix, 8.0 / width, 1); + cairo_matrix_translate(&matrix, -x - width / 2, -y); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + margin, y, shadow_width, shadow_height); + + cairo_reset_clip(cr); + cairo_rectangle(cr, + x + margin, y, + shadow_width, shadow_height); + cairo_clip (cr); + cairo_mask(cr, pattern); + + /* Bottom stretch */ + cairo_matrix_translate(&matrix, 0, -height + 128); + cairo_pattern_set_matrix(pattern, &matrix); + + cairo_reset_clip(cr); + cairo_rectangle(cr, x + margin, y + height - margin, + shadow_width, margin); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + shadow_width = margin; + if (width < 2 * shadow_width) + shadow_width = width / 2; + + shadow_height = height - margin - top_margin; + + /* if height is smaller than sum of margins, + * then the shadow is already done by the corners */ + if (shadow_height > 0 && shadow_width) { + /* Left stretch */ + cairo_matrix_init_translate(&matrix, 0, 60); + cairo_matrix_scale(&matrix, 1, 8.0 / height); + cairo_matrix_translate(&matrix, -x, -y - height / 2); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_reset_clip(cr); + cairo_rectangle(cr, x, y + top_margin, + shadow_width, shadow_height); + cairo_clip (cr); + cairo_mask(cr, pattern); + + /* Right stretch */ + cairo_matrix_translate(&matrix, -width + 128, 0); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + width - shadow_width, y + top_margin, + shadow_width, shadow_height); + cairo_reset_clip(cr); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + cairo_pattern_destroy(pattern); + cairo_reset_clip(cr); +} + +//#include "desktop-settings.h" + +enum libdecor_color_scheme { + LIBDECOR_COLOR_SCHEME_DEFAULT, + LIBDECOR_COLOR_SCHEME_PREFER_DARK, + LIBDECOR_COLOR_SCHEME_PREFER_LIGHT, +}; + +bool +libdecor_get_cursor_settings(char **theme, int *size); + +enum libdecor_color_scheme +libdecor_get_color_scheme(); + +//#include "os-compatibility.h" +#ifndef OS_COMPATIBILITY_H +#define OS_COMPATIBILITY_H + +int +libdecor_os_create_anonymous_file(off_t size); + +#endif /* OS_COMPATIBILITY_H */ + +//#include "os-compatibility.c" +#ifndef HAVE_MKOSTEMP +static int +set_cloexec_or_close(int fd) +{ + long flags; + + if (fd == -1) + return -1; + + flags = fcntl(fd, F_GETFD); + if (flags == -1) + goto err; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + goto err; + + return fd; + + err: + close(fd); + return -1; +} +#endif + +static int +create_tmpfile_cloexec(char *tmpname) +{ + int fd; + +#ifdef HAVE_MKOSTEMP + fd = mkostemp(tmpname, O_CLOEXEC); + if (fd >= 0) + unlink(tmpname); +#else + fd = mkstemp(tmpname); + if (fd >= 0) { + fd = set_cloexec_or_close(fd); + unlink(tmpname); + } +#endif + + return fd; +} + +static int +os_resize_anonymous_file(int fd, off_t size) +{ +#ifdef HAVE_POSIX_FALLOCATE + sigset_t mask; + sigset_t old_mask; + + /* posix_fallocate() might be interrupted, so we need to check + * for EINTR and retry in that case. + * However, in the presence of an alarm, the interrupt may trigger + * repeatedly and prevent a large posix_fallocate() to ever complete + * successfully, so we need to first block SIGALRM to prevent + * this. + */ + sigemptyset(&mask); + sigaddset(&mask, SIGALRM); + sigprocmask(SIG_BLOCK, &mask, &old_mask); + /* + * Filesystems that do not support fallocate will return EINVAL or + * EOPNOTSUPP. In this case we need to fall back to ftruncate + */ + do { + errno = posix_fallocate(fd, 0, size); + } while (errno == EINTR); + sigprocmask(SIG_SETMASK, &old_mask, NULL); + if (errno == 0) + return 0; + else if (errno != EINVAL && errno != EOPNOTSUPP) + return -1; +#endif + if (ftruncate(fd, size) < 0) + return -1; + + return 0; +} + +/* + * Create a new, unique, anonymous file of the given size, and + * return the file descriptor for it. The file descriptor is set + * CLOEXEC. The file is immediately suitable for mmap()'ing + * the given size at offset zero. + * + * The file should not have a permanent backing store like a disk, + * but may have if XDG_RUNTIME_DIR is not properly implemented in OS. + * + * The file name is deleted from the file system. + * + * The file is suitable for buffer sharing between processes by + * transmitting the file descriptor over Unix sockets using the + * SCM_RIGHTS methods. + * + * If the C library implements posix_fallocate(), it is used to + * guarantee that disk space is available for the file at the + * given size. If disk space is insufficient, errno is set to ENOSPC. + * If posix_fallocate() is not supported, program may receive + * SIGBUS on accessing mmap()'ed file contents instead. + * + * If the C library implements memfd_create(), it is used to create the + * file purely in memory, without any backing file name on the file + * system, and then sealing off the possibility of shrinking it. This + * can then be checked before accessing mmap()'ed file contents, to + * make sure SIGBUS can't happen. It also avoids requiring + * XDG_RUNTIME_DIR. + */ +int +libdecor_os_create_anonymous_file(off_t size) +{ + static const char template[] = "/libdecor-shared-XXXXXX"; + const char *path; + char *name; + int fd; + +#ifdef HAVE_MEMFD_CREATE + fd = memfd_create("libdecor", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0) { + /* We can add this seal before calling posix_fallocate(), as + * the file is currently zero-sized anyway. + * + * There is also no need to check for the return value, we + * couldn't do anything with it anyway. + */ + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); + } else +#endif + { + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } + + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; + + strcpy(name, path); + strcat(name, template); + + fd = create_tmpfile_cloexec(name); + + free(name); + + if (fd < 0) + return -1; + } + + if (os_resize_anonymous_file(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + + +//#include "plugins/gtk/libdecor-gtk.c" +static const size_t SHADOW_MARGIN = 24; /* grabbable part of the border */ + +static const char *cursor_names[] = { + "top_side", + "bottom_side", + "left_side", + "top_left_corner", + "bottom_left_corner", + "right_side", + "top_right_corner", + "bottom_right_corner" +}; + +enum header_element { + HEADER_NONE, + HEADER_FULL, /* entire header bar */ + HEADER_TITLE, /* label */ + HEADER_MIN, + HEADER_MAX, + HEADER_CLOSE, +}; + +enum titlebar_gesture_state { + TITLEBAR_GESTURE_STATE_INIT, + TITLEBAR_GESTURE_STATE_BUTTON_PRESSED, + TITLEBAR_GESTURE_STATE_CONSUMED, + TITLEBAR_GESTURE_STATE_DISCARDED, +}; + +struct header_element_data { + const char *name; + enum header_element type; + /* pointer to button or NULL if not found*/ + GtkWidget *widget; + GtkStateFlags state; +}; + +static void +find_widget_by_name(GtkWidget *widget, void *data) +{ + if (GTK_IS_WIDGET(widget)) { + char *style_ctx = gtk_style_context_to_string( + gtk_widget_get_style_context(widget), + GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE); + if (strstr(style_ctx, ((struct header_element_data *)data)->name)) { + ((struct header_element_data *)data)->widget = widget; + free(style_ctx); + return; + } + free(style_ctx); + } + + if (GTK_IS_CONTAINER(widget)) { + /* recursively traverse container */ + gtk_container_forall(GTK_CONTAINER(widget), &find_widget_by_name, data); + } +} + +static struct header_element_data +find_widget_by_type(GtkWidget *widget, enum header_element type) +{ + char* name = NULL; + switch (type) { + case HEADER_FULL: + name = "headerbar.titlebar:"; + break; + case HEADER_TITLE: + name = "label.title:"; + break; + case HEADER_MIN: + name = ".minimize"; + break; + case HEADER_MAX: + name = ".maximize"; + break; + case HEADER_CLOSE: + name = ".close"; + break; + default: + break; + } + + struct header_element_data data = {.name = name, .type = type, .widget = NULL}; + find_widget_by_name(widget, &data); + return data; +} + +static bool +in_region(const cairo_rectangle_int_t *rect, const int *x, const int *y) +{ + return (*x>=rect->x) & (*y>=rect->y) & + (*x<(rect->x+rect->width)) & (*y<(rect->y+rect->height)); +} + +static struct header_element_data +get_header_focus(const GtkHeaderBar *header_bar, const int x, const int y) +{ + /* we have to check child widgets (buttons, title) before the 'HDR_HDR' root widget */ + static const enum header_element elems[] = + {HEADER_TITLE, HEADER_MIN, HEADER_MAX, HEADER_CLOSE}; + + for (size_t i = 0; i < ARRAY_SIZE(elems); i++) { + struct header_element_data elem = + find_widget_by_type(GTK_WIDGET(header_bar), elems[i]); + if (elem.widget) { + GtkAllocation allocation; + gtk_widget_get_allocation(GTK_WIDGET(elem.widget), &allocation); + if (in_region(&allocation, &x, &y)) + return elem; + } + } + + struct header_element_data elem_none = { .widget=NULL}; + return elem_none; +} + +static bool +streq(const char *str1, + const char *str2) +{ + if (!str1 && !str2) + return true; + + if (str1 && str2) + return strcmp(str1, str2) == 0; + + return false; +} + +enum decoration_type { + DECORATION_TYPE_NONE, + DECORATION_TYPE_ALL, + DECORATION_TYPE_TITLE_ONLY +}; + +enum component { + NONE = 0, + SHADOW, + HEADER, +}; + +struct seat { + struct libdecor_plugin_gtk *plugin_gtk; + + char *name; + + struct wl_seat *wl_seat; + struct wl_pointer *wl_pointer; + struct wl_touch *wl_touch; + + struct wl_surface *cursor_surface; + struct wl_cursor *current_cursor; + int cursor_scale; + struct wl_list cursor_outputs; + + struct wl_cursor_theme *cursor_theme; + /* cursors for resize edges and corners */ + struct wl_cursor *cursors[ARRAY_LENGTH(cursor_names)]; + struct wl_cursor *cursor_left_ptr; + + struct wl_surface *pointer_focus; + struct wl_surface *touch_focus; + + int pointer_x, pointer_y; + + uint32_t touch_down_time_stamp; + + uint32_t serial; + + bool grabbed; + + struct wl_list link; +}; + +struct output { + struct libdecor_plugin_gtk *plugin_gtk; + + struct wl_output *wl_output; + uint32_t id; + int scale; + + struct wl_list link; +}; + +struct buffer { + struct wl_buffer *wl_buffer; + bool in_use; + bool is_detached; + + void *data; + size_t data_size; + int width; + int height; + int scale; + int buffer_width; + int buffer_height; +}; + +struct border_component { + enum component type; + struct wl_surface *wl_surface; + struct wl_subsurface *wl_subsurface; + struct buffer *buffer; + bool opaque; + struct wl_list output_list; + int scale; + + struct wl_list child_components; /* border_component::link */ + struct wl_list link; /* border_component::child_components */ +}; + +struct surface_output { + struct output *output; + struct wl_list link; +}; + +struct cursor_output { + struct output *output; + struct wl_list link; +}; + +struct libdecor_frame_gtk { + struct libdecor_frame frame; + + struct libdecor_plugin_gtk *plugin_gtk; + + int content_width; + int content_height; + + enum libdecor_window_state window_state; + + enum decoration_type decoration_type; + + char *title; + + enum libdecor_capabilities capabilities; + + struct border_component *active; + struct border_component *touch_active; + + struct border_component *focus; + struct border_component *grab; + + bool shadow_showing; + struct border_component shadow; + + GtkWidget *window; /* offscreen window for rendering */ + GtkWidget *header; /* header bar with widgets */ + struct border_component headerbar; + struct header_element_data hdr_focus; + + /* store pre-processed shadow tile */ + cairo_surface_t *shadow_blur; + + struct wl_list link; + + struct { + enum titlebar_gesture_state state; + int button_pressed_count; + uint32_t first_pressed_button; + uint32_t first_pressed_time; + double pressed_x; + double pressed_y; + uint32_t pressed_serial; + } titlebar_gesture; +}; + +struct libdecor_plugin_gtk { + struct libdecor_plugin plugin; + + struct wl_callback *globals_callback; + struct wl_callback *globals_callback_shm; + + struct libdecor *context; + + struct wl_registry *wl_registry; + struct wl_subcompositor *wl_subcompositor; + struct wl_compositor *wl_compositor; + + struct wl_shm *wl_shm; + struct wl_callback *shm_callback; + bool has_argb; + + struct wl_list visible_frame_list; + struct wl_list seat_list; + struct wl_list output_list; + + char *cursor_theme_name; + int cursor_size; + + uint32_t color_scheme_setting; + + int double_click_time_ms; + int drag_threshold; + + bool handle_cursor; +}; + +static const char *libdecor_gtk_proxy_tag = "libdecor-gtk"; + +static bool +own_proxy(struct wl_proxy *proxy) +{ + if (!proxy) + return false; + + return (wl_proxy_get_tag(proxy) == &libdecor_gtk_proxy_tag); +} + +static bool +own_surface(struct wl_surface *surface) +{ + return own_proxy((struct wl_proxy *) surface); +} + +static bool +own_output(struct wl_output *output) +{ + return own_proxy((struct wl_proxy *) output); +} + +static bool +moveable(struct libdecor_frame_gtk *frame_gtk) { + return libdecor_frame_has_capability(&frame_gtk->frame, + LIBDECOR_ACTION_MOVE); +} + +static bool +resizable(struct libdecor_frame_gtk *frame_gtk) { + return libdecor_frame_has_capability(&frame_gtk->frame, + LIBDECOR_ACTION_RESIZE); +} + +static bool +minimizable(struct libdecor_frame_gtk *frame_gtk) { + return libdecor_frame_has_capability(&frame_gtk->frame, + LIBDECOR_ACTION_MINIMIZE); +} + +static bool +closeable(struct libdecor_frame_gtk *frame_gtk) { + return libdecor_frame_has_capability(&frame_gtk->frame, + LIBDECOR_ACTION_CLOSE); +} + +static void +buffer_free(struct buffer *buffer); + +static void +draw_border_component(struct libdecor_frame_gtk *frame_gtk, + struct border_component *border_component, + enum component component); + +static void +send_cursor(struct seat *seat); + +static bool +update_local_cursor(struct seat *seat); + +static void +libdecor_plugin_gtk_destroy(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_gtk *plugin_gtk = + (struct libdecor_plugin_gtk *) plugin; + struct seat *seat, *seat_tmp; + struct output *output, *output_tmp; + struct libdecor_frame_gtk *frame, *frame_tmp; + + if (plugin_gtk->globals_callback) + wl_callback_destroy(plugin_gtk->globals_callback); + if (plugin_gtk->globals_callback_shm) + wl_callback_destroy(plugin_gtk->globals_callback_shm); + if (plugin_gtk->shm_callback) + wl_callback_destroy(plugin_gtk->shm_callback); + wl_registry_destroy(plugin_gtk->wl_registry); + + wl_list_for_each_safe(seat, seat_tmp, &plugin_gtk->seat_list, link) { + struct cursor_output *cursor_output, *tmp; + + if (seat->wl_pointer) + wl_pointer_destroy(seat->wl_pointer); + if (seat->wl_touch) + wl_touch_destroy(seat->wl_touch); + if (seat->cursor_surface) + wl_surface_destroy(seat->cursor_surface); + wl_seat_destroy(seat->wl_seat); + if (seat->cursor_theme) + wl_cursor_theme_destroy(seat->cursor_theme); + + wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) { + wl_list_remove(&cursor_output->link); + free(cursor_output); + } + + free(seat->name); + free(seat); + } + + wl_list_for_each_safe(output, output_tmp, + &plugin_gtk->output_list, link) { + if (wl_output_get_version (output->wl_output) >= + WL_OUTPUT_RELEASE_SINCE_VERSION) + wl_output_release(output->wl_output); + else + wl_output_destroy(output->wl_output); + free(output); + } + + wl_list_for_each_safe(frame, frame_tmp, + &plugin_gtk->visible_frame_list, link) { + wl_list_remove(&frame->link); + } + + free(plugin_gtk->cursor_theme_name); + + if (plugin_gtk->wl_shm) + wl_shm_destroy(plugin_gtk->wl_shm); + + if (plugin_gtk->wl_compositor) + wl_compositor_destroy(plugin_gtk->wl_compositor); + if (plugin_gtk->wl_subcompositor) + wl_subcompositor_destroy(plugin_gtk->wl_subcompositor); + + libdecor_plugin_release(&plugin_gtk->plugin); + free(plugin_gtk); +} + +static struct libdecor_frame_gtk * +libdecor_frame_gtk_new(struct libdecor_plugin_gtk *plugin_gtk) +{ + struct libdecor_frame_gtk *frame_gtk = calloc(1, sizeof *frame_gtk); + cairo_t *cr; + + static const int size = 128; + static const int boundary = 32; + + frame_gtk->plugin_gtk = plugin_gtk; + frame_gtk->shadow_blur = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, size, size); + wl_list_insert(&plugin_gtk->visible_frame_list, &frame_gtk->link); + + cr = cairo_create(frame_gtk->shadow_blur); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_rectangle(cr, boundary, boundary, + size - 2 * boundary, + size - 2 * boundary); + cairo_fill(cr); + cairo_destroy(cr); + blur_surface(frame_gtk->shadow_blur, 64); + + return frame_gtk; +} + +static int +libdecor_plugin_gtk_get_fd(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_gtk *plugin_gtk = + (struct libdecor_plugin_gtk *) plugin; + struct wl_display *wl_display = + libdecor_get_wl_display(plugin_gtk->context); + + return wl_display_get_fd(wl_display); +} + +static int +libdecor_plugin_gtk_dispatch(struct libdecor_plugin *plugin, + int timeout) +{ + struct libdecor_plugin_gtk *plugin_gtk = + (struct libdecor_plugin_gtk *) plugin; + struct wl_display *wl_display = + libdecor_get_wl_display(plugin_gtk->context); + struct pollfd fds[1]; + int ret; + int dispatch_count = 0; + + while (g_main_context_iteration(NULL, FALSE)); + + while (wl_display_prepare_read(wl_display) != 0) + dispatch_count += wl_display_dispatch_pending(wl_display); + + if (wl_display_flush(wl_display) < 0 && + errno != EAGAIN) { + wl_display_cancel_read(wl_display); + return -errno; + } + + fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN }; + + ret = poll(fds, ARRAY_SIZE (fds), timeout); + if (ret > 0) { + if (fds[0].revents & POLLIN) { + wl_display_read_events(wl_display); + dispatch_count += wl_display_dispatch_pending(wl_display); + return dispatch_count; + } else { + wl_display_cancel_read(wl_display); + return dispatch_count; + } + } else if (ret == 0) { + wl_display_cancel_read(wl_display); + return dispatch_count; + } else { + wl_display_cancel_read(wl_display); + return -errno; + } +} + +static void +libdecor_plugin_gtk_set_handle_application_cursor(struct libdecor_plugin *plugin, + bool handle_cursor) +{ + struct libdecor_plugin_gtk *plugin_gtk = + (struct libdecor_plugin_gtk *) plugin; + + plugin_gtk->handle_cursor = handle_cursor; +} + +static struct libdecor_frame * +libdecor_plugin_gtk_frame_new(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_gtk *plugin_gtk = + (struct libdecor_plugin_gtk *) plugin; + struct libdecor_frame_gtk *frame_gtk; + + frame_gtk = libdecor_frame_gtk_new(plugin_gtk); + + return &frame_gtk->frame; +} + +static void +toggle_maximized(struct libdecor_frame *const frame) +{ + if (!resizable((struct libdecor_frame_gtk *)frame)) + return; + + if (!(libdecor_frame_get_window_state(frame) & + LIBDECOR_WINDOW_STATE_MAXIMIZED)) + libdecor_frame_set_maximized(frame); + else + libdecor_frame_unset_maximized(frame); +} + +static void +buffer_release(void *user_data, + struct wl_buffer *wl_buffer) +{ + struct buffer *buffer = user_data; + + if (buffer->is_detached) + buffer_free(buffer); + else + buffer->in_use = false; +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static struct buffer * +create_shm_buffer(struct libdecor_plugin_gtk *plugin_gtk, + int width, + int height, + bool opaque, + int scale) +{ + struct wl_shm_pool *pool; + int fd, size, buffer_width, buffer_height, stride; + void *data; + struct buffer *buffer; + enum wl_shm_format buf_fmt; + + buffer_width = width * scale; + buffer_height = height * scale; + stride = buffer_width * 4; + size = stride * buffer_height; + + fd = libdecor_os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); + return NULL; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return NULL; + } + + buf_fmt = opaque ? WL_SHM_FORMAT_XRGB8888 : WL_SHM_FORMAT_ARGB8888; + + pool = wl_shm_create_pool(plugin_gtk->wl_shm, fd, size); + buffer = calloc(1, sizeof *buffer); + buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0, + buffer_width, buffer_height, + stride, + buf_fmt); + wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + buffer->data = data; + buffer->data_size = size; + buffer->width = width; + buffer->height = height; + buffer->scale = scale; + buffer->buffer_width = buffer_width; + buffer->buffer_height = buffer_height; + + return buffer; +} + +static void +buffer_free(struct buffer *buffer) +{ + if (buffer->wl_buffer) { + wl_buffer_destroy(buffer->wl_buffer); + munmap(buffer->data, buffer->data_size); + buffer->wl_buffer = NULL; + buffer->in_use = false; + } + free(buffer); +} + +static void +free_border_component(struct border_component *border_component) +{ + if (border_component->wl_surface) { + wl_subsurface_destroy(border_component->wl_subsurface); + border_component->wl_subsurface = NULL; + wl_surface_destroy(border_component->wl_surface); + border_component->wl_surface = NULL; + } + if (border_component->buffer) { + buffer_free(border_component->buffer); + border_component->buffer = NULL; + } + if (border_component->output_list.next != NULL) { + struct surface_output *surface_output, *surface_output_tmp; + wl_list_for_each_safe(surface_output, surface_output_tmp, + &border_component->output_list, link) { + wl_list_remove(&surface_output->link); + free(surface_output); + } + } +} + +static void +libdecor_plugin_gtk_frame_free(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + + g_clear_pointer (&frame_gtk->header, gtk_widget_destroy); + g_clear_pointer (&frame_gtk->window, gtk_widget_destroy); + + free_border_component(&frame_gtk->headerbar); + free_border_component(&frame_gtk->shadow); + frame_gtk->shadow_showing = false; + + g_clear_pointer (&frame_gtk->shadow_blur, cairo_surface_destroy); + + g_clear_pointer (&frame_gtk->title, free); + + frame_gtk->decoration_type = DECORATION_TYPE_NONE; + + if (frame_gtk->link.next != NULL) + wl_list_remove(&frame_gtk->link); +} + +static bool +is_border_surfaces_showing(struct libdecor_frame_gtk *frame_gtk) +{ + return frame_gtk->shadow_showing; +} + +static void +hide_border_component(struct border_component *border_component) +{ + if (!border_component->wl_surface) + return; + + wl_surface_attach(border_component->wl_surface, NULL, 0, 0); + wl_surface_commit(border_component->wl_surface); +} + +static void +hide_border_surfaces(struct libdecor_frame_gtk *frame_gtk) +{ + hide_border_component(&frame_gtk->shadow); + frame_gtk->shadow_showing = false; +} + +static struct border_component * +get_component_for_surface(struct libdecor_frame_gtk *frame_gtk, + const struct wl_surface *surface) +{ + if (frame_gtk->shadow.wl_surface == surface) + return &frame_gtk->shadow; + if (frame_gtk->headerbar.wl_surface == surface) + return &frame_gtk->headerbar; + return NULL; +} + +static bool +redraw_scale(struct libdecor_frame_gtk *frame_gtk, + struct border_component *cmpnt) +{ + struct surface_output *surface_output; + int scale = 1; + + if (cmpnt->wl_surface == NULL) + return false; + + wl_list_for_each(surface_output, &cmpnt->output_list, link) { + scale = MAX(scale, surface_output->output->scale); + } + if (scale != cmpnt->scale) { + cmpnt->scale = scale; + if ((frame_gtk->decoration_type != DECORATION_TYPE_NONE) && + ((cmpnt->type != SHADOW) || is_border_surfaces_showing(frame_gtk))) { + draw_border_component(frame_gtk, cmpnt, cmpnt->type); + return true; + } + } + return false; +} + +static bool +add_surface_output(struct libdecor_plugin_gtk *plugin_gtk, + struct wl_output *wl_output, + struct wl_list *list) +{ + struct output *output; + struct surface_output *surface_output; + + if (!own_output(wl_output)) + return false; + + output = wl_output_get_user_data(wl_output); + + if (output == NULL) + return false; + + surface_output = calloc(1, sizeof *surface_output); + surface_output->output = output; + wl_list_insert(list, &surface_output->link); + return true; +} + +static void +surface_enter(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct libdecor_frame_gtk *frame_gtk = data; + struct border_component *cmpnt; + + if (!(own_surface(wl_surface) && own_output(wl_output))) + return; + + cmpnt = get_component_for_surface(frame_gtk, wl_surface); + if (cmpnt == NULL) + return; + + if (!add_surface_output(frame_gtk->plugin_gtk, wl_output, + &cmpnt->output_list)) + return; + + if (redraw_scale(frame_gtk, cmpnt)) + libdecor_frame_toplevel_commit(&frame_gtk->frame); +} + +static bool +remove_surface_output(struct wl_list *list, const struct wl_output *wl_output) +{ + struct surface_output *surface_output; + wl_list_for_each(surface_output, list, link) { + if (surface_output->output->wl_output == wl_output) { + wl_list_remove(&surface_output->link); + free(surface_output); + return true; + } + } + return false; +} + +static void +surface_leave(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct libdecor_frame_gtk *frame_gtk = data; + struct border_component *cmpnt; + + if (!(own_surface(wl_surface) && own_output(wl_output))) + return; + + cmpnt = get_component_for_surface(frame_gtk, wl_surface); + if (cmpnt == NULL) + return; + + if (!remove_surface_output(&cmpnt->output_list, wl_output)) + return; + + if (redraw_scale(frame_gtk, cmpnt)) + libdecor_frame_toplevel_commit(&frame_gtk->frame); +} + +static struct wl_surface_listener surface_listener = { + surface_enter, + surface_leave, +}; + +static void +create_surface_subsurface_pair(struct libdecor_frame_gtk *frame_gtk, + struct wl_surface **out_wl_surface, + struct wl_subsurface **out_wl_subsurface) +{ + struct libdecor_plugin_gtk *plugin_gtk = frame_gtk->plugin_gtk; + struct libdecor_frame *frame = &frame_gtk->frame; + struct wl_compositor *wl_compositor = plugin_gtk->wl_compositor; + struct wl_subcompositor *wl_subcompositor = plugin_gtk->wl_subcompositor; + struct wl_surface *wl_surface; + struct wl_surface *parent; + struct wl_subsurface *wl_subsurface; + + wl_surface = wl_compositor_create_surface(wl_compositor); + wl_proxy_set_tag((struct wl_proxy *) wl_surface, + &libdecor_gtk_proxy_tag); + + parent = libdecor_frame_get_wl_surface(frame); + wl_subsurface = wl_subcompositor_get_subsurface(wl_subcompositor, + wl_surface, + parent); + + *out_wl_surface = wl_surface; + *out_wl_subsurface = wl_subsurface; +} + +static void +ensure_component(struct libdecor_frame_gtk *frame_gtk, + struct border_component *cmpnt) +{ + if (!cmpnt->wl_surface) { + wl_list_init(&cmpnt->output_list); + cmpnt->scale = 1; + create_surface_subsurface_pair(frame_gtk, + &cmpnt->wl_surface, + &cmpnt->wl_subsurface); + wl_surface_add_listener(cmpnt->wl_surface, &surface_listener, + frame_gtk); + } +} + +static void +ensure_border_surfaces(struct libdecor_frame_gtk *frame_gtk) +{ + frame_gtk->shadow.type = SHADOW; + frame_gtk->shadow.opaque = false; + ensure_component(frame_gtk, &frame_gtk->shadow); +} + +static void +ensure_title_bar_surfaces(struct libdecor_frame_gtk *frame_gtk) +{ + GtkStyleContext *context_hdr; + + frame_gtk->headerbar.type = HEADER; + frame_gtk->headerbar.opaque = false; + ensure_component(frame_gtk, &frame_gtk->headerbar); + + if (frame_gtk->shadow.wl_surface) { + wl_subsurface_place_above(frame_gtk->headerbar.wl_subsurface, + frame_gtk->shadow.wl_surface); + } + + /* create an offscreen window with a header bar */ + /* TODO: This should only be done once at frame consutrction, but then + * the window and headerbar would not change style (e.g. backdrop) + * after construction. So we just destroy and re-create them. + */ + /* avoid warning when restoring previously turned off decoration */ + if (GTK_IS_WIDGET(frame_gtk->header)) { + gtk_widget_destroy(frame_gtk->header); + frame_gtk->header = NULL; + } + /* avoid warning when restoring previously turned off decoration */ + if (GTK_IS_WIDGET(frame_gtk->window)) { + gtk_widget_destroy(frame_gtk->window); + frame_gtk->window = NULL; + } + frame_gtk->window = gtk_offscreen_window_new(); + frame_gtk->header = gtk_header_bar_new(); + + g_object_get(gtk_widget_get_settings(frame_gtk->window), + "gtk-double-click-time", + &frame_gtk->plugin_gtk->double_click_time_ms, + "gtk-dnd-drag-threshold", + &frame_gtk->plugin_gtk->drag_threshold, + NULL); + /* set as "default" decoration */ + g_object_set(frame_gtk->header, + "title", libdecor_frame_get_title(&frame_gtk->frame), + "has-subtitle", FALSE, + "show-close-button", TRUE, + NULL); + + context_hdr = gtk_widget_get_style_context(frame_gtk->header); + gtk_style_context_add_class(context_hdr, GTK_STYLE_CLASS_TITLEBAR); + gtk_style_context_add_class(context_hdr, "default-decoration"); + + gtk_window_set_titlebar(GTK_WINDOW(frame_gtk->window), frame_gtk->header); + gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(frame_gtk->header), TRUE); + + gtk_window_set_resizable(GTK_WINDOW(frame_gtk->window), resizable(frame_gtk)); +} + +static void +calculate_component_size(struct libdecor_frame_gtk *frame_gtk, + enum component component, + int *component_x, + int *component_y, + int *component_width, + int *component_height) +{ + struct libdecor_frame *frame = &frame_gtk->frame; + int content_width, content_height; + + content_width = libdecor_frame_get_content_width(frame); + content_height = libdecor_frame_get_content_height(frame); + + /* avoid warning when restoring previously turned off decoration */ + const int title_height = + GTK_IS_WIDGET(frame_gtk->header) + ? gtk_widget_get_allocated_height(frame_gtk->header) : 0; + + switch (component) { + case NONE: + *component_width = 0; + *component_height = 0; + return; + case SHADOW: + *component_x = -(int)SHADOW_MARGIN; + *component_y = -(int)(SHADOW_MARGIN+title_height); + *component_width = content_width + 2 * SHADOW_MARGIN; + *component_height = content_height + + 2 * SHADOW_MARGIN + + title_height; + return; + case HEADER: + *component_x = 0; + /* reuse product of function call above */ + *component_y = - title_height; + *component_width = gtk_widget_get_allocated_width(frame_gtk->header); + /* reuse product of function call above */ + *component_height = title_height; + return; + } + + abort(); +} + +static void +array_append(enum header_element **array, size_t *n, enum header_element item) +{ + (*n)++; + *array = realloc(*array, (*n) * sizeof (enum header_element)); + (*array)[(*n)-1] = item; +} + +static void +draw_header_background(struct libdecor_frame_gtk *frame_gtk, + cairo_t *cr) +{ + /* background */ + GtkAllocation allocation; + GtkStyleContext* style; + + gtk_widget_get_allocation(GTK_WIDGET(frame_gtk->header), &allocation); + style = gtk_widget_get_style_context(frame_gtk->header); + gtk_render_background(style, cr, allocation.x, allocation.y, allocation.width, allocation.height); +} + +static void +draw_header_title(struct libdecor_frame_gtk *frame_gtk, + cairo_surface_t *surface) +{ + /* title */ + GtkWidget *label; + GtkAllocation allocation; + cairo_surface_t *label_surface = NULL; + cairo_t *cr; + + label = find_widget_by_type(frame_gtk->header, HEADER_TITLE).widget; + gtk_widget_get_allocation(label, &allocation); + + /* create subsection in which to draw label */ + label_surface = cairo_surface_create_for_rectangle( + surface, + allocation.x, allocation.y, + allocation.width, allocation.height); + cr = cairo_create(label_surface); + gtk_widget_size_allocate(label, &allocation); + gtk_widget_draw(label, cr); + cairo_destroy(cr); + cairo_surface_destroy(label_surface); +} + +static void +draw_header_button(struct libdecor_frame_gtk *frame_gtk, + cairo_t *cr, + cairo_surface_t *surface, + enum header_element button_type, + enum libdecor_window_state window_state) +{ + struct header_element_data elem; + GtkWidget *button; + GtkStyleContext* button_style; + GtkStateFlags style_state; + + GtkAllocation allocation; + + gchar *icon_name; + int scale; + GtkWidget *icon_widget; + GtkAllocation allocation_icon; + GtkIconInfo* icon_info; + + double sx, sy; + + gint icon_width, icon_height; + + GdkPixbuf* icon_pixbuf; + cairo_surface_t* icon_surface; + + gint width = 0, height = 0; + + gint left = 0, top = 0, right = 0, bottom = 0; + GtkBorder border; + + GtkBorder padding; + + elem = find_widget_by_type(frame_gtk->header, button_type); + button = elem.widget; + if (!button) + return; + button_style = gtk_widget_get_style_context(button); + style_state = elem.state; + + /* change style based on window state and focus */ + if (!(window_state & LIBDECOR_WINDOW_STATE_ACTIVE)) { + style_state |= GTK_STATE_FLAG_BACKDROP; + } + if (frame_gtk->hdr_focus.widget == button) { + style_state |= GTK_STATE_FLAG_PRELIGHT; + if (frame_gtk->hdr_focus.state & GTK_STATE_FLAG_ACTIVE) { + style_state |= GTK_STATE_FLAG_ACTIVE; + } + } + + /* background */ + gtk_widget_get_clip(button, &allocation); + + gtk_style_context_save(button_style); + gtk_style_context_set_state(button_style, style_state); + gtk_render_background(button_style, cr, + allocation.x, allocation.y, + allocation.width, allocation.height); + gtk_render_frame(button_style, cr, + allocation.x, allocation.y, + allocation.width, allocation.height); + gtk_style_context_restore(button_style); + + /* symbol */ + switch (button_type) { + case HEADER_MIN: + icon_name = "window-minimize-symbolic"; + break; + case HEADER_MAX: + icon_name = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) ? + "window-restore-symbolic" : + "window-maximize-symbolic"; + break; + case HEADER_CLOSE: + icon_name = "window-close-symbolic"; + break; + default: + icon_name = NULL; + break; + } + + /* get scale */ + cairo_surface_get_device_scale(surface, &sx, &sy); + scale = (sx+sy) / 2.0; + + /* get original icon dimensions */ + icon_widget = gtk_bin_get_child(GTK_BIN(button)); + gtk_widget_get_allocation(icon_widget, &allocation_icon); + + /* icon info */ + if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &icon_width, &icon_height)) { + icon_width = 16; + icon_height = 16; + } + icon_info = gtk_icon_theme_lookup_icon_for_scale( + gtk_icon_theme_get_default(), icon_name, + icon_width, scale, (GtkIconLookupFlags)0); + + /* icon pixel buffer*/ + gtk_style_context_save(button_style); + gtk_style_context_set_state(button_style, style_state); + icon_pixbuf = gtk_icon_info_load_symbolic_for_context( + icon_info, button_style, NULL, NULL); + icon_surface = gdk_cairo_surface_create_from_pixbuf(icon_pixbuf, scale, NULL); + gtk_style_context_restore(button_style); + + /* dimensions and position */ + gtk_style_context_get(button_style, gtk_style_context_get_state(button_style), + "min-width", &width, "min-height", &height, NULL); + + if (width < icon_width) + width = icon_width; + if (height < icon_height) + height = icon_height; + + gtk_style_context_get_border(button_style, gtk_style_context_get_state(button_style), &border); + left += border.left; + right += border.right; + top += border.top; + bottom += border.bottom; + + gtk_style_context_get_padding(button_style, gtk_style_context_get_state(button_style), &padding); + left += padding.left; + right += padding.right; + top += padding.top; + bottom += padding.bottom; + + width += left + right; + height += top + bottom; + + gtk_render_icon_surface(gtk_widget_get_style_context(icon_widget), + cr, icon_surface, + allocation.x + ((width - icon_width) / 2), + allocation.y + ((height - icon_height) / 2)); + cairo_paint(cr); + cairo_surface_destroy(icon_surface); + g_object_unref(icon_pixbuf); +} + +static void +draw_header_buttons(struct libdecor_frame_gtk *frame_gtk, + cairo_t *cr, + cairo_surface_t *surface) +{ + /* buttons */ + enum libdecor_window_state window_state; + enum header_element *buttons = NULL; + size_t nbuttons = 0; + + window_state = libdecor_frame_get_window_state( + (struct libdecor_frame*)frame_gtk); + + /* set buttons by capability */ + if (minimizable(frame_gtk)) + array_append(&buttons, &nbuttons, HEADER_MIN); + if (resizable(frame_gtk)) + array_append(&buttons, &nbuttons, HEADER_MAX); + if (closeable(frame_gtk)) + array_append(&buttons, &nbuttons, HEADER_CLOSE); + + for (size_t i = 0; i < nbuttons; i++) { + draw_header_button(frame_gtk, cr, surface, buttons[i], window_state); + } /* loop buttons */ + free(buttons); +} + +static void +draw_header(struct libdecor_frame_gtk *frame_gtk, + cairo_t *cr, + cairo_surface_t *surface) +{ + draw_header_background(frame_gtk, cr); + draw_header_title(frame_gtk, surface); + draw_header_buttons(frame_gtk, cr, surface); +} + +static void +draw_component_content(struct libdecor_frame_gtk *frame_gtk, + struct buffer *buffer, + int component_width, + int component_height, + enum component component) +{ + cairo_surface_t *surface; + cairo_t *cr; + + /* clear buffer */ + memset(buffer->data, 0, buffer->data_size); + + surface = cairo_image_surface_create_for_data( + buffer->data, CAIRO_FORMAT_ARGB32, + buffer->buffer_width, buffer->buffer_height, + cairo_format_stride_for_width( + CAIRO_FORMAT_ARGB32, + buffer->buffer_width) + ); + + cr = cairo_create(surface); + + cairo_surface_set_device_scale(surface, buffer->scale, buffer->scale); + + /* background */ + switch (component) { + case NONE: + break; + case SHADOW: + render_shadow(cr, + frame_gtk->shadow_blur, + -(int)SHADOW_MARGIN/2, + -(int)SHADOW_MARGIN/2, + buffer->width + SHADOW_MARGIN, + buffer->height + SHADOW_MARGIN, + 64, + 64); + break; + case HEADER: + draw_header(frame_gtk, cr, surface); + break; + } + + /* mask the toplevel surface */ + if (component == SHADOW) { + int component_x, component_y, component_width, component_height; + calculate_component_size(frame_gtk, component, + &component_x, &component_y, + &component_width, &component_height); + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + cairo_rectangle(cr, -component_x, -component_y, + libdecor_frame_get_content_width( + &frame_gtk->frame), + libdecor_frame_get_content_height( + &frame_gtk->frame)); + cairo_fill(cr); + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static void +set_component_input_region(struct libdecor_frame_gtk *frame_gtk, + struct border_component *border_component) +{ + if (border_component->type == SHADOW && frame_gtk->shadow_showing) { + struct wl_region *input_region; + int component_x; + int component_y; + int component_width; + int component_height; + + calculate_component_size(frame_gtk, border_component->type, + &component_x, &component_y, + &component_width, &component_height); + + /* + * the input region is the outer surface size minus the inner + * content size + */ + input_region = wl_compositor_create_region( + frame_gtk->plugin_gtk->wl_compositor); + wl_region_add(input_region, 0, 0, + component_width, component_height); + wl_region_subtract(input_region, -component_x, -component_y, + libdecor_frame_get_content_width(&frame_gtk->frame), + libdecor_frame_get_content_height(&frame_gtk->frame)); + wl_surface_set_input_region(border_component->wl_surface, + input_region); + wl_region_destroy(input_region); + } +} + +static void +draw_border_component(struct libdecor_frame_gtk *frame_gtk, + struct border_component *border_component, + enum component component) +{ + struct libdecor_plugin_gtk *plugin_gtk = frame_gtk->plugin_gtk; + struct buffer *old_buffer; + struct buffer *buffer = NULL; + int component_x; + int component_y; + int component_width; + int component_height; + int scale = border_component->scale; + + if (border_component->wl_surface == NULL) + return; + + calculate_component_size(frame_gtk, component, + &component_x, &component_y, + &component_width, &component_height); + + set_component_input_region(frame_gtk, border_component); + + old_buffer = border_component->buffer; + if (old_buffer) { + if (!old_buffer->in_use && + old_buffer->buffer_width == component_width * scale && + old_buffer->buffer_height == component_height * scale) { + buffer = old_buffer; + } else { + buffer_free(old_buffer); + border_component->buffer = NULL; + } + } + + if (!buffer) + buffer = create_shm_buffer(plugin_gtk, + component_width, + component_height, + border_component->opaque, + border_component->scale); + + draw_component_content(frame_gtk, buffer, + component_width, component_height, + component); + + wl_surface_attach(border_component->wl_surface, buffer->wl_buffer, 0, 0); + wl_surface_set_buffer_scale(border_component->wl_surface, buffer->scale); + buffer->in_use = true; + wl_surface_commit(border_component->wl_surface); + wl_surface_damage_buffer(border_component->wl_surface, 0, 0, + component_width * scale, + component_height * scale); + wl_subsurface_set_position(border_component->wl_subsurface, + component_x, component_y); + + border_component->buffer = buffer; +} + +static void +draw_border(struct libdecor_frame_gtk *frame_gtk) +{ + draw_border_component(frame_gtk, &frame_gtk->shadow, SHADOW); + frame_gtk->shadow_showing = true; +} + +static void +draw_title_bar(struct libdecor_frame_gtk *frame_gtk) +{ + GtkAllocation allocation = {0, 0, frame_gtk->content_width, 0}; + enum libdecor_window_state state; + GtkStyleContext *style; + int pref_width; + int current_min_w, current_min_h, current_max_w, current_max_h, W, H; + + state = libdecor_frame_get_window_state((struct libdecor_frame*)frame_gtk); + style = gtk_widget_get_style_context(frame_gtk->window); + + if (!(state & LIBDECOR_WINDOW_STATE_ACTIVE)) { + gtk_widget_set_state_flags(frame_gtk->window, GTK_STATE_FLAG_BACKDROP, true); + } else { + gtk_widget_unset_state_flags(frame_gtk->window, GTK_STATE_FLAG_BACKDROP); + } + + if (libdecor_frame_is_floating(&frame_gtk->frame)) { + gtk_style_context_remove_class(style, "maximized"); + } else { + gtk_style_context_add_class(style, "maximized"); + } + + gtk_widget_show_all(frame_gtk->window); + + /* set default width, using an empty title to estimate its smallest admissible value */ + gtk_header_bar_set_title(GTK_HEADER_BAR(frame_gtk->header), ""); + gtk_widget_get_preferred_width(frame_gtk->header, NULL, &pref_width); + gtk_header_bar_set_title(GTK_HEADER_BAR(frame_gtk->header), + libdecor_frame_get_title(&frame_gtk->frame)); + libdecor_frame_get_min_content_size(&frame_gtk->frame, ¤t_min_w, ¤t_min_h); + if (current_min_w < pref_width) { + current_min_w = pref_width; + libdecor_frame_set_min_content_size(&frame_gtk->frame, current_min_w, current_min_h); + } + libdecor_frame_get_max_content_size(&frame_gtk->frame, ¤t_max_w, ¤t_max_h); + if (current_max_w && current_max_w < current_min_w) { + libdecor_frame_set_max_content_size(&frame_gtk->frame, current_min_w, current_max_h); + } + W = libdecor_frame_get_content_width(&frame_gtk->frame); + H = libdecor_frame_get_content_height(&frame_gtk->frame); + if (W < current_min_w) { + W = current_min_w; + struct libdecor_state *libdecor_state = libdecor_state_new(W, H); + libdecor_frame_commit(&frame_gtk->frame, libdecor_state, NULL); + libdecor_state_free(libdecor_state); + return; + } + /* set default height */ + gtk_widget_get_preferred_height(frame_gtk->header, NULL, &allocation.height); + + gtk_widget_size_allocate(frame_gtk->header, &allocation); + + draw_border_component(frame_gtk, &frame_gtk->headerbar, HEADER); +} + +static void +draw_decoration(struct libdecor_frame_gtk *frame_gtk) +{ + switch (frame_gtk->decoration_type) { + case DECORATION_TYPE_NONE: + if (frame_gtk->link.next != NULL) + wl_list_remove(&frame_gtk->link); + if (is_border_surfaces_showing(frame_gtk)) + hide_border_surfaces(frame_gtk); + hide_border_component(&frame_gtk->headerbar); + break; + case DECORATION_TYPE_ALL: + /* show borders */ + ensure_border_surfaces(frame_gtk); + draw_border(frame_gtk); + /* show title bar */ + ensure_title_bar_surfaces(frame_gtk); + draw_title_bar(frame_gtk); + /* link frame */ + if (frame_gtk->link.next == NULL) + wl_list_insert( + &frame_gtk->plugin_gtk->visible_frame_list, + &frame_gtk->link); + break; + case DECORATION_TYPE_TITLE_ONLY: + /* hide borders */ + if (is_border_surfaces_showing(frame_gtk)) + hide_border_surfaces(frame_gtk); + /* show title bar */ + ensure_title_bar_surfaces(frame_gtk); + draw_title_bar(frame_gtk); + /* link frame */ + if (frame_gtk->link.next == NULL) + wl_list_insert( + &frame_gtk->plugin_gtk->visible_frame_list, + &frame_gtk->link); + break; + } +} + +static enum decoration_type +window_state_to_decoration_type(enum libdecor_window_state window_state) +{ + if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) + return DECORATION_TYPE_NONE; + else if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED || + window_state & LIBDECOR_WINDOW_STATE_TILED_LEFT || + window_state & LIBDECOR_WINDOW_STATE_TILED_RIGHT || + window_state & LIBDECOR_WINDOW_STATE_TILED_TOP || + window_state & LIBDECOR_WINDOW_STATE_TILED_BOTTOM) + /* title bar, no shadows */ + return DECORATION_TYPE_TITLE_ONLY; + else + /* title bar, shadows */ + return DECORATION_TYPE_ALL; +} + +static void +libdecor_plugin_gtk_frame_commit(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + enum libdecor_window_state old_window_state; + enum libdecor_window_state new_window_state; + int old_content_width, old_content_height; + int new_content_width, new_content_height; + enum decoration_type old_decoration_type; + enum decoration_type new_decoration_type; + + old_window_state = frame_gtk->window_state; + new_window_state = libdecor_frame_get_window_state(frame); + + old_content_width = frame_gtk->content_width; + old_content_height = frame_gtk->content_height; + new_content_width = libdecor_frame_get_content_width(frame); + new_content_height = libdecor_frame_get_content_height(frame); + + old_decoration_type = frame_gtk->decoration_type; + new_decoration_type = window_state_to_decoration_type(new_window_state); + + if (old_decoration_type == new_decoration_type && + old_content_width == new_content_width && + old_content_height == new_content_height && + old_window_state == new_window_state) + return; + + frame_gtk->content_width = new_content_width; + frame_gtk->content_height = new_content_height; + frame_gtk->window_state = new_window_state; + frame_gtk->decoration_type = new_decoration_type; + + draw_decoration(frame_gtk); + + /* set fixed window size */ + if (!resizable(frame_gtk)) { + libdecor_frame_set_min_content_size(frame, + frame_gtk->content_width, + frame_gtk->content_height); + libdecor_frame_set_max_content_size(frame, + frame_gtk->content_width, + frame_gtk->content_height); + } +} + +static void +libdecor_plugin_gtk_frame_property_changed(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + bool redraw_needed = false; + const char *new_title; + + /* + * when in SSD mode, the window title is not to be managed by GTK; + * this is detected by frame_gtk->header not being a proper GTK widget + */ + if (!GTK_IS_WIDGET(frame_gtk->header)) return; + + new_title = libdecor_frame_get_title(frame); + if (!streq(frame_gtk->title, new_title)) + redraw_needed = true; + free(frame_gtk->title); + frame_gtk->title = NULL; + if (new_title) + frame_gtk->title = strdup(new_title); + + if (frame_gtk->capabilities != libdecor_frame_get_capabilities(frame)) { + frame_gtk->capabilities = libdecor_frame_get_capabilities(frame); + redraw_needed = true; + } + + if (redraw_needed) { + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(frame); + } +} + +static void +update_component_focus(struct libdecor_frame_gtk *frame_gtk, + struct wl_surface *surface, + struct seat *seat) +{ + static struct border_component *border_component; + static struct border_component *child_component; + static struct border_component *focus_component; + + border_component = get_component_for_surface(frame_gtk, surface); + + focus_component = border_component; + wl_list_for_each(child_component, &border_component->child_components, link) { + int component_x = 0, component_y = 0; + int component_width = 0, component_height = 0; + + calculate_component_size(frame_gtk, child_component->type, + &component_x, &component_y, + &component_width, &component_height); + if (seat->pointer_x >= component_x && + seat->pointer_x < component_x + component_width && + seat->pointer_y >= component_y && + seat->pointer_y < component_y + component_height) { + focus_component = child_component; + break; + } + } + + if (frame_gtk->grab) + frame_gtk->active = frame_gtk->grab; + else + frame_gtk->active = focus_component; + frame_gtk->focus = focus_component; + +} + +static void +sync_active_component(struct libdecor_frame_gtk *frame_gtk, + struct seat *seat) +{ + struct border_component *old_active; + + if (!seat->pointer_focus) + return; + + old_active = frame_gtk->active; + update_component_focus(frame_gtk, seat->pointer_focus, seat); + if (old_active != frame_gtk->active) { + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + + if (update_local_cursor(seat)) + send_cursor(seat); +} + +static void +synthesize_pointer_enter(struct seat *seat) +{ + struct wl_surface *surface; + struct libdecor_frame_gtk *frame_gtk; + + surface = seat->pointer_focus; + if (!surface || !own_surface(surface)) + return; + + frame_gtk = wl_surface_get_user_data(surface); + if (!frame_gtk) + return; + + update_component_focus(frame_gtk, seat->pointer_focus, seat); + frame_gtk->grab = NULL; + + /* update decorations */ + if (frame_gtk->active) { + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + + update_local_cursor(seat); + send_cursor(seat); +} + +static void +synthesize_pointer_leave(struct seat *seat) +{ + struct wl_surface *surface; + struct libdecor_frame_gtk *frame_gtk; + + surface = seat->pointer_focus; + if (!surface || !own_surface(surface)) + return; + + frame_gtk = wl_surface_get_user_data(surface); + if (!frame_gtk) + return; + + if (!frame_gtk->active) + return; + + frame_gtk->active = NULL; + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + update_local_cursor(seat); +} + +static void +libdecor_plugin_gtk_frame_popup_grab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + struct libdecor_plugin_gtk *plugin_gtk = frame_gtk->plugin_gtk; + struct seat *seat; + + wl_list_for_each(seat, &plugin_gtk->seat_list, link) { + if (streq(seat->name, seat_name)) { + if (seat->grabbed) { + fprintf(stderr, "libdecor-WARNING: Application " + "tried to grab seat twice\n"); + } + synthesize_pointer_leave(seat); + seat->grabbed = true; + return; + } + } + + fprintf(stderr, + "libdecor-WARNING: Application tried to grab unknown seat\n"); +} + +static void +libdecor_plugin_gtk_frame_popup_ungrab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + struct libdecor_plugin_gtk *plugin_gtk = frame_gtk->plugin_gtk; + struct seat *seat; + + wl_list_for_each(seat, &plugin_gtk->seat_list, link) { + if (streq(seat->name, seat_name)) { + if (!seat->grabbed) { + fprintf(stderr, "libdecor-WARNING: Application " + "tried to ungrab seat twice\n"); + } + seat->grabbed = false; + synthesize_pointer_enter(seat); + sync_active_component(frame_gtk, seat); + return; + } + } + + fprintf(stderr, + "libdecor-WARNING: Application tried to ungrab unknown seat\n"); +} + +static bool +libdecor_plugin_gtk_frame_get_border_size(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_configuration *configuration, + int *left, + int *right, + int *top, + int *bottom) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + enum libdecor_window_state window_state; + + if (configuration) { + if (!libdecor_configuration_get_window_state( + configuration, &window_state)) + return false; + } else { + window_state = libdecor_frame_get_window_state(frame); + } + + if (left) + *left = 0; + if (right) + *right = 0; + if (bottom) + *bottom = 0; + if (top) { + enum decoration_type type = window_state_to_decoration_type(window_state); + + switch (type) { + case DECORATION_TYPE_NONE: + *top = 0; + break; + case DECORATION_TYPE_ALL: + ensure_border_surfaces(frame_gtk); + G_GNUC_FALLTHROUGH; + case DECORATION_TYPE_TITLE_ONLY: + if (!frame_gtk->header) + ensure_title_bar_surfaces(frame_gtk); + gtk_widget_show_all(frame_gtk->window); + gtk_widget_get_preferred_height(frame_gtk->header, NULL, top); + break; + } + } + + return true; +} + +static struct libdecor_plugin_interface gtk_plugin_iface = { + .destroy = libdecor_plugin_gtk_destroy, + .get_fd = libdecor_plugin_gtk_get_fd, + .dispatch = libdecor_plugin_gtk_dispatch, + + .set_handle_application_cursor = libdecor_plugin_gtk_set_handle_application_cursor, + + .frame_new = libdecor_plugin_gtk_frame_new, + .frame_free = libdecor_plugin_gtk_frame_free, + .frame_commit = libdecor_plugin_gtk_frame_commit, + .frame_property_changed = libdecor_plugin_gtk_frame_property_changed, + + .frame_popup_grab = libdecor_plugin_gtk_frame_popup_grab, + .frame_popup_ungrab = libdecor_plugin_gtk_frame_popup_ungrab, + + .frame_get_border_size = libdecor_plugin_gtk_frame_get_border_size, +}; + +static void +init_wl_compositor(struct libdecor_plugin_gtk *plugin_gtk, + uint32_t id, + uint32_t version) +{ + plugin_gtk->wl_compositor = + wl_registry_bind(plugin_gtk->wl_registry, + id, &wl_compositor_interface, + MIN(version, 4)); +} + +static void +init_wl_subcompositor(struct libdecor_plugin_gtk *plugin_gtk, + uint32_t id, + uint32_t version) +{ + plugin_gtk->wl_subcompositor = + wl_registry_bind(plugin_gtk->wl_registry, + id, &wl_subcompositor_interface, 1); +} + +static void +shm_format(void *user_data, + struct wl_shm *wl_shm, + uint32_t format) +{ + struct libdecor_plugin_gtk *plugin_gtk = user_data; + + if (format == WL_SHM_FORMAT_ARGB8888) + plugin_gtk->has_argb = true; +} + +struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +shm_callback(void *user_data, + struct wl_callback *callback, + uint32_t time) +{ + struct libdecor_plugin_gtk *plugin_gtk = user_data; + struct libdecor *context = plugin_gtk->context; + + wl_callback_destroy(callback); + plugin_gtk->globals_callback_shm = NULL; + + if (!plugin_gtk->has_argb) { + libdecor_notify_plugin_error( + context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "Compositor is missing required shm format"); + return; + } + + libdecor_notify_plugin_ready(context); +} + +static const struct wl_callback_listener shm_callback_listener = { + shm_callback +}; + +static void +init_wl_shm(struct libdecor_plugin_gtk *plugin_gtk, + uint32_t id, + uint32_t version) +{ + struct libdecor *context = plugin_gtk->context; + struct wl_display *wl_display = libdecor_get_wl_display(context); + + plugin_gtk->wl_shm = + wl_registry_bind(plugin_gtk->wl_registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(plugin_gtk->wl_shm, &shm_listener, plugin_gtk); + + plugin_gtk->globals_callback_shm = wl_display_sync(wl_display); + wl_callback_add_listener(plugin_gtk->globals_callback_shm, + &shm_callback_listener, + plugin_gtk); +} + +static void +cursor_surface_enter(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct seat *seat = data; + + if (own_output(wl_output)) { + struct cursor_output *cursor_output; + cursor_output = calloc(1, sizeof *cursor_output); + cursor_output->output = wl_output_get_user_data(wl_output); + wl_list_insert(&seat->cursor_outputs, &cursor_output->link); + if (update_local_cursor(seat)) + send_cursor(seat); + } +} + +static void +cursor_surface_leave(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct seat *seat = data; + + if (own_output(wl_output)) { + struct cursor_output *cursor_output, *tmp; + wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) { + if (cursor_output->output->wl_output == wl_output) { + wl_list_remove(&cursor_output->link); + free(cursor_output); + } + } + + if (update_local_cursor(seat)) + send_cursor(seat); + } +} + +static struct wl_surface_listener cursor_surface_listener = { + cursor_surface_enter, + cursor_surface_leave, +}; + +static void +ensure_cursor_surface(struct seat *seat) +{ + struct wl_compositor *wl_compositor = seat->plugin_gtk->wl_compositor; + + if (seat->cursor_surface) + return; + + seat->cursor_surface = wl_compositor_create_surface(wl_compositor); + wl_surface_add_listener(seat->cursor_surface, + &cursor_surface_listener, seat); +} + +static bool +ensure_cursor_theme(struct seat *seat) +{ + struct libdecor_plugin_gtk *plugin_gtk = seat->plugin_gtk; + int scale = 1; + struct wl_cursor_theme *theme; + struct cursor_output *cursor_output; + + wl_list_for_each(cursor_output, &seat->cursor_outputs, link) { + scale = MAX(scale, cursor_output->output->scale); + } + + if (seat->cursor_theme && seat->cursor_scale == scale) + return false; + + seat->cursor_scale = scale; + theme = wl_cursor_theme_load(plugin_gtk->cursor_theme_name, + plugin_gtk->cursor_size * scale, + plugin_gtk->wl_shm); + if (theme == NULL) + return false; + + if (seat->cursor_theme) + wl_cursor_theme_destroy(seat->cursor_theme); + + seat->cursor_theme = theme; + + for (unsigned int i = 0; i < ARRAY_LENGTH(cursor_names); i++) { + seat->cursors[i] = wl_cursor_theme_get_cursor( + seat->cursor_theme, + cursor_names[i]); + } + + seat->cursor_left_ptr = wl_cursor_theme_get_cursor(seat->cursor_theme, + "left_ptr"); + seat->current_cursor = seat->cursor_left_ptr; + + return true; +} + +enum libdecor_resize_edge +component_edge(const struct border_component *cmpnt, + const int pointer_x, + const int pointer_y, + const int margin) +{ + const bool top = pointer_y < margin * 2; + const bool bottom = pointer_y > (cmpnt->buffer->height - margin * 2); + const bool left = pointer_x < margin * 2; + const bool right = pointer_x > (cmpnt->buffer->width - margin * 2); + + if (top) { + if (left) + return LIBDECOR_RESIZE_EDGE_TOP_LEFT; + else if (right) + return LIBDECOR_RESIZE_EDGE_TOP_RIGHT; + else + return LIBDECOR_RESIZE_EDGE_TOP; + } else if (bottom) { + if (left) + return LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT; + else if (right) + return LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT; + else + return LIBDECOR_RESIZE_EDGE_BOTTOM; + } else if (left) { + return LIBDECOR_RESIZE_EDGE_LEFT; + } else if (right) { + return LIBDECOR_RESIZE_EDGE_RIGHT; + } else { + return LIBDECOR_RESIZE_EDGE_NONE; + } +} + +static bool +update_local_cursor(struct seat *seat) +{ + if (!seat->pointer_focus) { + seat->current_cursor = seat->cursor_left_ptr; + return false; + } + + if (!own_surface(seat->pointer_focus)) + return false; + + struct libdecor_frame_gtk *frame_gtk = + wl_surface_get_user_data(seat->pointer_focus); + struct wl_cursor *wl_cursor = NULL; + + if (!frame_gtk || !frame_gtk->active) { + seat->current_cursor = seat->cursor_left_ptr; + return false; + } + + bool theme_updated = ensure_cursor_theme(seat); + + if (frame_gtk->active->type == SHADOW && + is_border_surfaces_showing(frame_gtk) && + resizable(frame_gtk)) { + enum libdecor_resize_edge edge; + edge = component_edge(frame_gtk->active, + seat->pointer_x, + seat->pointer_y, SHADOW_MARGIN); + + if (edge != LIBDECOR_RESIZE_EDGE_NONE) + wl_cursor = seat->cursors[edge - 1]; + } else { + wl_cursor = seat->cursor_left_ptr; + } + + if (seat->current_cursor != wl_cursor) { + seat->current_cursor = wl_cursor; + return true; + } + + return theme_updated; +} + +static void +send_cursor(struct seat *seat) +{ + struct wl_cursor_image *image; + struct wl_buffer *buffer; + + if (seat->pointer_focus == NULL || seat->current_cursor == NULL) + return; + + image = seat->current_cursor->images[0]; + buffer = wl_cursor_image_get_buffer(image); + wl_surface_attach(seat->cursor_surface, buffer, 0, 0); + wl_surface_set_buffer_scale(seat->cursor_surface, seat->cursor_scale); + wl_surface_damage_buffer(seat->cursor_surface, 0, 0, + image->width * seat->cursor_scale, + image->height * seat->cursor_scale); + wl_surface_commit(seat->cursor_surface); + wl_pointer_set_cursor(seat->wl_pointer, seat->serial, + seat->cursor_surface, + image->hotspot_x / seat->cursor_scale, + image->hotspot_y / seat->cursor_scale); +} + +static void +pointer_enter(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + struct wl_surface *surface, + wl_fixed_t surface_x, + wl_fixed_t surface_y) +{ + if (!surface) + return; + + struct seat *seat = data; + struct libdecor_frame_gtk *frame_gtk = NULL; + + if (!own_surface(surface)) { + struct seat *seat = wl_pointer_get_user_data(wl_pointer); + struct libdecor_plugin_gtk *plugin_gtk = seat->plugin_gtk; + + if (!plugin_gtk->handle_cursor) + return; + } else { + frame_gtk = wl_surface_get_user_data(surface); + } + + ensure_cursor_surface(seat); + + seat->pointer_x = wl_fixed_to_int(surface_x); + seat->pointer_y = wl_fixed_to_int(surface_y); + seat->serial = serial; + seat->pointer_focus = surface; + + if (!frame_gtk) + return; + + frame_gtk->active = get_component_for_surface(frame_gtk, surface); + + /* update decorations */ + if (frame_gtk->active) { + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + + update_local_cursor(seat); + send_cursor(seat); +} + +static void +pointer_leave(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + struct wl_surface *surface) +{ + struct seat *seat = data; + struct libdecor_frame_gtk *frame_gtk; + + seat->pointer_focus = NULL; + + if (!surface) + return; + + if (!own_surface(surface)) + return; + + frame_gtk = wl_surface_get_user_data(surface); + + if (frame_gtk) { + frame_gtk->titlebar_gesture.state = + TITLEBAR_GESTURE_STATE_INIT; + frame_gtk->titlebar_gesture.first_pressed_button = 0; + + frame_gtk->active = NULL; + frame_gtk->hdr_focus.widget = NULL; + frame_gtk->hdr_focus.type = HEADER_NONE; + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + update_local_cursor(seat); + } +} + +static void +pointer_motion(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + wl_fixed_t surface_x, + wl_fixed_t surface_y) +{ + struct seat *seat = data; + struct libdecor_frame_gtk *frame_gtk; + struct header_element_data new_focus; + + if (!seat->pointer_focus || !own_surface(seat->pointer_focus)) + return; + + seat->pointer_x = wl_fixed_to_int(surface_x); + seat->pointer_y = wl_fixed_to_int(surface_y); + if (update_local_cursor(seat)) + send_cursor(seat); + + frame_gtk = wl_surface_get_user_data(seat->pointer_focus); + /* avoid warnings after decoration has been turned off */ + if (!GTK_IS_WIDGET(frame_gtk->header) || frame_gtk->active->type != HEADER) { + frame_gtk->hdr_focus.type = HEADER_NONE; + } + + new_focus = get_header_focus(GTK_HEADER_BAR(frame_gtk->header), + seat->pointer_x, seat->pointer_y); + + /* only update if widget change so that we keep the state */ + if (frame_gtk->hdr_focus.widget != new_focus.widget) { + frame_gtk->hdr_focus = new_focus; + } + frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_PRELIGHT; + /* redraw with updated button visuals */ + draw_title_bar(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + + switch (frame_gtk->titlebar_gesture.state) { + case TITLEBAR_GESTURE_STATE_BUTTON_PRESSED: + if (frame_gtk->titlebar_gesture.first_pressed_button == BTN_LEFT) { + if (ABS ((double) seat->pointer_x - + (double) frame_gtk->titlebar_gesture.pressed_x) > + frame_gtk->plugin_gtk->drag_threshold || + ABS ((double) seat->pointer_y - + (double) frame_gtk->titlebar_gesture.pressed_y) > + frame_gtk->plugin_gtk->drag_threshold) { + libdecor_frame_move(&frame_gtk->frame, + seat->wl_seat, + frame_gtk->titlebar_gesture.pressed_serial); + } + } + case TITLEBAR_GESTURE_STATE_INIT: + case TITLEBAR_GESTURE_STATE_CONSUMED: + case TITLEBAR_GESTURE_STATE_DISCARDED: + break; + } +} + +static void +handle_button_on_shadow(struct libdecor_frame_gtk *frame_gtk, + struct seat *seat, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state) +{ + enum libdecor_resize_edge edge = LIBDECOR_RESIZE_EDGE_NONE; + + edge = component_edge(frame_gtk->active, + seat->pointer_x, + seat->pointer_y, + SHADOW_MARGIN); + + if (edge != LIBDECOR_RESIZE_EDGE_NONE && resizable(frame_gtk)) { + libdecor_frame_resize(&frame_gtk->frame, + seat->wl_seat, + serial, + edge); + } +} + +enum titlebar_gesture { + TITLEBAR_GESTURE_DOUBLE_CLICK, + TITLEBAR_GESTURE_MIDDLE_CLICK, + TITLEBAR_GESTURE_RIGHT_CLICK, +}; + +static void +handle_titlebar_gesture(struct libdecor_frame_gtk *frame_gtk, + struct seat *seat, + uint32_t serial, + enum titlebar_gesture gesture) +{ + switch (gesture) { + case TITLEBAR_GESTURE_DOUBLE_CLICK: + toggle_maximized(&frame_gtk->frame); + break; + case TITLEBAR_GESTURE_MIDDLE_CLICK: + break; + case TITLEBAR_GESTURE_RIGHT_CLICK: + { + const int title_height = gtk_widget_get_allocated_height(frame_gtk->header); + libdecor_frame_show_window_menu(&frame_gtk->frame, + seat->wl_seat, + serial, + seat->pointer_x, + seat->pointer_y + -title_height); + } + break; + } +} + +static void +handle_button_on_header(struct libdecor_frame_gtk *frame_gtk, + struct seat *seat, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state) +{ + switch (frame_gtk->titlebar_gesture.state) { + case TITLEBAR_GESTURE_STATE_INIT: + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + return; + + if (button == BTN_RIGHT) { + handle_titlebar_gesture(frame_gtk, + seat, + serial, + TITLEBAR_GESTURE_RIGHT_CLICK); + frame_gtk->titlebar_gesture.state = + TITLEBAR_GESTURE_STATE_CONSUMED; + } else { + if (button == BTN_LEFT && + frame_gtk->titlebar_gesture.first_pressed_button == BTN_LEFT && + time - frame_gtk->titlebar_gesture.first_pressed_time < + (uint32_t) frame_gtk->plugin_gtk->double_click_time_ms) { + handle_titlebar_gesture(frame_gtk, + seat, + serial, + TITLEBAR_GESTURE_DOUBLE_CLICK); + frame_gtk->titlebar_gesture.state = + TITLEBAR_GESTURE_STATE_CONSUMED; + } else { + frame_gtk->titlebar_gesture.first_pressed_button = button; + frame_gtk->titlebar_gesture.first_pressed_time = time; + frame_gtk->titlebar_gesture.pressed_x = seat->pointer_x; + frame_gtk->titlebar_gesture.pressed_y = seat->pointer_y; + frame_gtk->titlebar_gesture.pressed_serial = serial; + frame_gtk->titlebar_gesture.state = + TITLEBAR_GESTURE_STATE_BUTTON_PRESSED; + } + } + + frame_gtk->titlebar_gesture.button_pressed_count = 1; + + switch (frame_gtk->hdr_focus.type) { + case HEADER_MIN: + case HEADER_MAX: + case HEADER_CLOSE: + frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_ACTIVE; + draw_title_bar(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + break; + default: + break; + } + + break; + case TITLEBAR_GESTURE_STATE_BUTTON_PRESSED: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + frame_gtk->titlebar_gesture.state = + TITLEBAR_GESTURE_STATE_DISCARDED; + frame_gtk->titlebar_gesture.button_pressed_count++; + } else { + frame_gtk->titlebar_gesture.button_pressed_count--; + + if (frame_gtk->titlebar_gesture.button_pressed_count == 0) { + frame_gtk->titlebar_gesture.state = + TITLEBAR_GESTURE_STATE_INIT; + if (frame_gtk->titlebar_gesture.first_pressed_button == button && + button == BTN_LEFT) { + libdecor_frame_ref(&frame_gtk->frame); + switch (frame_gtk->hdr_focus.type) { + case HEADER_MIN: + if (minimizable(frame_gtk)) + libdecor_frame_set_minimized( + &frame_gtk->frame); + break; + case HEADER_MAX: + toggle_maximized(&frame_gtk->frame); + break; + case HEADER_CLOSE: + if (closeable(frame_gtk)) { + libdecor_frame_close( + &frame_gtk->frame); + seat->pointer_focus = NULL; + } + break; + default: + break; + } + + frame_gtk->hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE; + if (GTK_IS_WIDGET(frame_gtk->header)) { + draw_title_bar(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + libdecor_frame_unref(&frame_gtk->frame); + } + } else { + frame_gtk->hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE; + if (GTK_IS_WIDGET(frame_gtk->header)) { + draw_title_bar(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + } + + } + break; + case TITLEBAR_GESTURE_STATE_CONSUMED: + case TITLEBAR_GESTURE_STATE_DISCARDED: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + frame_gtk->titlebar_gesture.button_pressed_count++; + } else { + frame_gtk->titlebar_gesture.button_pressed_count--; + if (frame_gtk->titlebar_gesture.button_pressed_count == 0) { + frame_gtk->titlebar_gesture.state = + TITLEBAR_GESTURE_STATE_INIT; + frame_gtk->titlebar_gesture.first_pressed_button = 0; + } + } + break; + } +} + +static void +pointer_button(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state) +{ + struct seat *seat = data; + struct libdecor_frame_gtk *frame_gtk; + + if (!seat->pointer_focus || !own_surface(seat->pointer_focus)) + return; + + frame_gtk = wl_surface_get_user_data(seat->pointer_focus); + if (!frame_gtk) + return; + + switch (frame_gtk->active->type) { + case SHADOW: + handle_button_on_shadow (frame_gtk, seat, serial, time, button, state); + break; + case HEADER: + handle_button_on_header (frame_gtk, seat, serial, time, button, state); + break; + default: + break; + } +} + +static void +pointer_axis(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value) +{ +} + +static struct wl_pointer_listener pointer_listener = { + pointer_enter, + pointer_leave, + pointer_motion, + pointer_button, + pointer_axis +}; + +static void +update_touch_focus(struct seat *seat, + struct libdecor_frame_gtk *frame_gtk, + wl_fixed_t x, + wl_fixed_t y) +{ + /* avoid warnings after decoration has been turned off */ + if (GTK_IS_WIDGET(frame_gtk->header) && frame_gtk->touch_active->type == HEADER) { + struct header_element_data new_focus = get_header_focus( + GTK_HEADER_BAR(frame_gtk->header), + wl_fixed_to_int(x), wl_fixed_to_int(y)); + /* only update if widget change so that we keep the state */ + if (frame_gtk->hdr_focus.widget != new_focus.widget) { + frame_gtk->hdr_focus = new_focus; + } + frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_PRELIGHT; + /* redraw with updated button visuals */ + draw_title_bar(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } else { + frame_gtk->hdr_focus.type = HEADER_NONE; + } +} + +static void +touch_down(void *data, + struct wl_touch *wl_touch, + uint32_t serial, + uint32_t time, + struct wl_surface *surface, + int32_t id, + wl_fixed_t x, + wl_fixed_t y) +{ + struct seat *seat = data; + struct libdecor_frame_gtk *frame_gtk; + + if (!surface || !own_surface(surface)) + return; + + frame_gtk = wl_surface_get_user_data(surface); + if (!frame_gtk) + return; + + seat->touch_focus = surface; + frame_gtk->touch_active = get_component_for_surface(frame_gtk, surface); + + if (!frame_gtk->touch_active) + return; + + update_touch_focus(seat, frame_gtk, x, y); + + /* update decorations */ + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + + enum libdecor_resize_edge edge = + LIBDECOR_RESIZE_EDGE_NONE; + switch (frame_gtk->touch_active->type) { + case SHADOW: + edge = component_edge(frame_gtk->touch_active, + wl_fixed_to_int(x), + wl_fixed_to_int(y), + SHADOW_MARGIN); + break; + case HEADER: + switch (frame_gtk->hdr_focus.type) { + case HEADER_MIN: + case HEADER_MAX: + case HEADER_CLOSE: + frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_ACTIVE; + draw_title_bar(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + break; + default: + if (time - seat->touch_down_time_stamp < + (uint32_t)frame_gtk->plugin_gtk->double_click_time_ms) { + toggle_maximized(&frame_gtk->frame); + } + else if (moveable(frame_gtk)) { + seat->touch_down_time_stamp = time; + libdecor_frame_move(&frame_gtk->frame, + seat->wl_seat, + serial); + } + break; + } + break; + default: + break; + } + if (edge != LIBDECOR_RESIZE_EDGE_NONE && + resizable(frame_gtk)) { + libdecor_frame_resize( + &frame_gtk->frame, + seat->wl_seat, + serial, + edge); + } +} + +static void +touch_up(void *data, + struct wl_touch *wl_touch, + uint32_t serial, + uint32_t time, + int32_t id) +{ + struct seat *seat = data; + struct libdecor_frame_gtk *frame_gtk; + + if (!seat->touch_focus || !own_surface(seat->touch_focus)) + return; + + frame_gtk = wl_surface_get_user_data(seat->touch_focus); + if (!frame_gtk) + return; + + if (!frame_gtk->touch_active) + return; + + switch (frame_gtk->touch_active->type) { + case HEADER: + libdecor_frame_ref(&frame_gtk->frame); + switch (frame_gtk->hdr_focus.type) { + case HEADER_MIN: + if (minimizable(frame_gtk)) { + libdecor_frame_set_minimized( + &frame_gtk->frame); + } + break; + case HEADER_MAX: + toggle_maximized(&frame_gtk->frame); + break; + case HEADER_CLOSE: + if (closeable(frame_gtk)) { + libdecor_frame_close( + &frame_gtk->frame); + seat->touch_focus = NULL; + } + break; + default: + break; + } + /* unset active/clicked state once released */ + frame_gtk->hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE; + if (GTK_IS_WIDGET(frame_gtk->header)) { + draw_title_bar(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + libdecor_frame_unref(&frame_gtk->frame); + break; + default: + break; + } + + seat->touch_focus = NULL; + frame_gtk->touch_active = NULL; + frame_gtk->hdr_focus.widget = NULL; + frame_gtk->hdr_focus.type = HEADER_NONE; + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); +} + +static void +touch_motion(void *data, + struct wl_touch *wl_touch, + uint32_t time, + int32_t id, + wl_fixed_t x, + wl_fixed_t y) +{ + struct seat *seat = data; + struct libdecor_frame_gtk *frame_gtk; + + if (!seat->touch_focus || !own_surface(seat->touch_focus)) + return; + + frame_gtk = wl_surface_get_user_data(seat->touch_focus); + if (!frame_gtk) + return; + + update_touch_focus(seat, frame_gtk, x, y); +} + +static void +touch_frame(void *data, + struct wl_touch *wl_touch) +{ +} + +static void +touch_cancel(void *data, + struct wl_touch *wl_touch) +{ +} + +static struct wl_touch_listener touch_listener = { + touch_down, + touch_up, + touch_motion, + touch_frame, + touch_cancel +}; + +static void +seat_capabilities(void *data, + struct wl_seat *wl_seat, + uint32_t capabilities) +{ + struct seat *seat = data; + + if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && + !seat->wl_pointer) { + seat->wl_pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(seat->wl_pointer, + &pointer_listener, seat); + } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && + seat->wl_pointer) { + wl_pointer_release(seat->wl_pointer); + seat->wl_pointer = NULL; + } + + if ((capabilities & WL_SEAT_CAPABILITY_TOUCH) && + !seat->wl_touch) { + seat->wl_touch = wl_seat_get_touch(wl_seat); + wl_touch_add_listener(seat->wl_touch, + &touch_listener, seat); + } else if (!(capabilities & WL_SEAT_CAPABILITY_TOUCH) && + seat->wl_touch) { + wl_touch_release(seat->wl_touch); + seat->wl_touch = NULL; + } +} + +static void +seat_name(void *data, + struct wl_seat *wl_seat, + const char *name) +{ + /* avoid warning messages when opening/closing popup window */ + struct seat *seat = (struct seat*)data; + seat->name = strdup(name); +} + +static struct wl_seat_listener seat_listener = { + seat_capabilities, + seat_name +}; + +static void +init_wl_seat(struct libdecor_plugin_gtk *plugin_gtk, + uint32_t id, + uint32_t version) +{ + struct seat *seat; + + if (version < 3) { + libdecor_notify_plugin_error( + plugin_gtk->context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "%s version 3 required but only version %i is available\n", + wl_seat_interface.name, + version); + } + + seat = calloc(1, sizeof *seat); + seat->cursor_scale = 1; + seat->plugin_gtk = plugin_gtk; + wl_list_init(&seat->cursor_outputs); + wl_list_insert(&plugin_gtk->seat_list, &seat->link); + seat->wl_seat = + wl_registry_bind(plugin_gtk->wl_registry, + id, &wl_seat_interface, 3); + wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); +} + +static void +output_geometry(void *data, + struct wl_output *wl_output, + int32_t x, + int32_t y, + int32_t physical_width, + int32_t physical_height, + int32_t subpixel, + const char *make, + const char *model, + int32_t transform) +{ +} + +static void +output_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh) +{ +} + +static void +output_done(void *data, + struct wl_output *wl_output) +{ + struct output *output = data; + struct libdecor_frame_gtk *frame_gtk; + struct seat *seat; + + wl_list_for_each(frame_gtk, + &output->plugin_gtk->visible_frame_list, link) { + bool updated = false; + updated |= redraw_scale(frame_gtk, &frame_gtk->shadow); + if (updated) + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + wl_list_for_each(seat, &output->plugin_gtk->seat_list, link) { + if (update_local_cursor(seat)) + send_cursor(seat); + } +} + +static void +output_scale(void *data, + struct wl_output *wl_output, + int32_t factor) +{ + struct output *output = data; + + output->scale = factor; +} + +static struct wl_output_listener output_listener = { + output_geometry, + output_mode, + output_done, + output_scale +}; + +static void +init_wl_output(struct libdecor_plugin_gtk *plugin_gtk, + uint32_t id, + uint32_t version) +{ + struct output *output; + + if (version < 2) { + libdecor_notify_plugin_error( + plugin_gtk->context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "%s version 2 required but only version %i is available\n", + wl_output_interface.name, + version); + } + + output = calloc(1, sizeof *output); + output->plugin_gtk = plugin_gtk; + wl_list_insert(&plugin_gtk->output_list, &output->link); + output->id = id; + output->wl_output = + wl_registry_bind(plugin_gtk->wl_registry, + id, &wl_output_interface, + MIN (version, 3)); + wl_proxy_set_tag((struct wl_proxy *) output->wl_output, + &libdecor_gtk_proxy_tag); + wl_output_add_listener(output->wl_output, &output_listener, output); +} + +static void +registry_handle_global(void *user_data, + struct wl_registry *wl_registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + struct libdecor_plugin_gtk *plugin_gtk = user_data; + + if (strcmp(interface, "wl_compositor") == 0) + init_wl_compositor(plugin_gtk, id, version); + else if (strcmp(interface, "wl_subcompositor") == 0) + init_wl_subcompositor(plugin_gtk, id, version); + else if (strcmp(interface, "wl_shm") == 0) + init_wl_shm(plugin_gtk, id, version); + else if (strcmp(interface, "wl_seat") == 0) + init_wl_seat(plugin_gtk, id, version); + else if (strcmp(interface, "wl_output") == 0) + init_wl_output(plugin_gtk, id, version); +} + +static void +remove_surface_outputs(struct border_component *cmpnt, const struct output *output) +{ + struct surface_output *surface_output; + wl_list_for_each(surface_output, &cmpnt->output_list, link) { + if (surface_output->output == output) { + wl_list_remove(&surface_output->link); + free(surface_output); + break; + } + } +} + +static void +output_removed(struct libdecor_plugin_gtk *plugin_gtk, + struct output *output) +{ + struct libdecor_frame_gtk *frame_gtk; + struct seat *seat; + + wl_list_for_each(frame_gtk, &plugin_gtk->visible_frame_list, link) { + remove_surface_outputs(&frame_gtk->shadow, output); + } + wl_list_for_each(seat, &plugin_gtk->seat_list, link) { + struct cursor_output *cursor_output; + wl_list_for_each(cursor_output, &seat->cursor_outputs, link) { + if (cursor_output->output == output) { + wl_list_remove(&cursor_output->link); + free(cursor_output); + } + } + } + + wl_list_remove(&output->link); + wl_output_destroy(output->wl_output); + free(output); +} + +static void +registry_handle_global_remove(void *user_data, + struct wl_registry *wl_registry, + uint32_t name) +{ + struct libdecor_plugin_gtk *plugin_gtk = user_data; + struct output *output; + + wl_list_for_each(output, &plugin_gtk->output_list, link) { + if (output->id == name) { + output_removed(plugin_gtk, output); + break; + } + } +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static bool +has_required_globals(struct libdecor_plugin_gtk *plugin_gtk) +{ + if (!plugin_gtk->wl_compositor) + return false; + if (!plugin_gtk->wl_subcompositor) + return false; + if (!plugin_gtk->wl_shm) + return false; + + return true; +} + +static void +globals_callback(void *user_data, + struct wl_callback *callback, + uint32_t time) +{ + struct libdecor_plugin_gtk *plugin_gtk = user_data; + + wl_callback_destroy(callback); + plugin_gtk->globals_callback = NULL; +} + +static const struct wl_callback_listener globals_callback_listener = { + globals_callback +}; + +static struct libdecor_plugin * +libdecor_plugin_new(struct libdecor *context) +{ + struct libdecor_plugin_gtk *plugin_gtk; + struct wl_display *wl_display; + +#ifdef HAVE_GETTID + /* Only support running on the main thread. */ + if (getpid () != gettid ()) + return NULL; +#endif + + plugin_gtk = calloc(1, sizeof *plugin_gtk); + libdecor_plugin_init(&plugin_gtk->plugin, + context, + >k_plugin_iface); + plugin_gtk->context = context; + + wl_list_init(&plugin_gtk->visible_frame_list); + wl_list_init(&plugin_gtk->seat_list); + wl_list_init(&plugin_gtk->output_list); + + /* fetch cursor theme and size*/ + if (!libdecor_get_cursor_settings(&plugin_gtk->cursor_theme_name, + &plugin_gtk->cursor_size)) { + plugin_gtk->cursor_theme_name = NULL; + plugin_gtk->cursor_size = 24; + } + + plugin_gtk->color_scheme_setting = libdecor_get_color_scheme(); + + wl_display = libdecor_get_wl_display(context); + plugin_gtk->wl_registry = wl_display_get_registry(wl_display); + wl_registry_add_listener(plugin_gtk->wl_registry, + ®istry_listener, + plugin_gtk); + + plugin_gtk->globals_callback = wl_display_sync(wl_display); + wl_callback_add_listener(plugin_gtk->globals_callback, + &globals_callback_listener, + plugin_gtk); + wl_display_roundtrip(wl_display); + + if (!has_required_globals(plugin_gtk)) { + fprintf(stderr, "libdecor-gtk-WARNING: Could not get required globals\n"); + libdecor_plugin_gtk_destroy(&plugin_gtk->plugin); + return NULL; + } + + /* setup GTK context */ + gdk_set_allowed_backends("wayland"); + gtk_disable_setlocale(); + + if (!gtk_init_check(NULL, NULL)) { + fprintf(stderr, "libdecor-gtk-WARNING: Failed to initialize GTK\n"); + libdecor_plugin_gtk_destroy(&plugin_gtk->plugin); + return NULL; + } + + g_object_set(gtk_settings_get_default(), + "gtk-application-prefer-dark-theme", + plugin_gtk->color_scheme_setting == LIBDECOR_COLOR_SCHEME_PREFER_DARK, + NULL); + + return &plugin_gtk->plugin; +} + +static struct libdecor_plugin_priority priorities[] = { + { NULL, LIBDECOR_PLUGIN_PRIORITY_HIGH } +}; + +const struct libdecor_plugin_description +libdecor_plugin_description = { + .api_version = LIBDECOR_PLUGIN_API_VERSION, + .capabilities = LIBDECOR_PLUGIN_CAPABILITY_BASE, + .description = "GTK3 plugin", + .priorities = priorities, + .constructor = libdecor_plugin_new, + .conflicting_symbols = { + "png_free", + "gdk_get_use_xshm", + NULL, + }, +}; + +//#include "desktop-settings.c" + +static bool +get_cursor_settings_from_env(char **theme, int *size) +{ + char *env_xtheme; + char *env_xsize; + + env_xtheme = getenv("XCURSOR_THEME"); + if (env_xtheme != NULL) + *theme = strdup(env_xtheme); + + env_xsize = getenv("XCURSOR_SIZE"); + if (env_xsize != NULL) + *size = atoi(env_xsize); + + return env_xtheme != NULL && env_xsize != NULL; +} + +#ifdef HAS_DBUS +#include + +static DBusMessage * +get_setting_sync(DBusConnection *const connection, + const char *key, + const char *value) +{ + DBusError error; + dbus_bool_t success; + DBusMessage *message; + DBusMessage *reply; + + message = dbus_message_new_method_call( + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Settings", + "Read"); + + success = dbus_message_append_args(message, + DBUS_TYPE_STRING, &key, + DBUS_TYPE_STRING, &value, + DBUS_TYPE_INVALID); + + if (!success) + return NULL; + + dbus_error_init(&error); + + reply = dbus_connection_send_with_reply_and_block( + connection, + message, + DBUS_TIMEOUT_USE_DEFAULT, + &error); + + dbus_message_unref(message); + + if (dbus_error_is_set(&error)) { + dbus_error_free(&error); + return NULL; + } + + dbus_error_free(&error); + return reply; +} + +static bool +parse_type(DBusMessage *const reply, + const int type, + void *value) +{ + DBusMessageIter iter[3]; + + dbus_message_iter_init(reply, &iter[0]); + if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) + return false; + + dbus_message_iter_recurse(&iter[0], &iter[1]); + if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) + return false; + + dbus_message_iter_recurse(&iter[1], &iter[2]); + if (dbus_message_iter_get_arg_type(&iter[2]) != type) + return false; + + dbus_message_iter_get_basic(&iter[2], value); + + return true; +} + +bool +libdecor_get_cursor_settings(char **theme, int *size){ + static const char name[] = "org.gnome.desktop.interface"; + static const char key_theme[] = "cursor-theme"; + static const char key_size[] = "cursor-size"; + + DBusError error; + DBusConnection *connection; + DBusMessage *reply; + const char *value_theme = NULL; + + dbus_error_init(&error); + + connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + + if (dbus_error_is_set(&error)) + goto fallback; + + reply = get_setting_sync(connection, name, key_theme); + if (!reply) + goto fallback; + + if (!parse_type(reply, DBUS_TYPE_STRING, &value_theme)) { + dbus_message_unref(reply); + goto fallback; + } + + *theme = strdup(value_theme); + + dbus_message_unref(reply); + + reply = get_setting_sync(connection, name, key_size); + if (!reply) + goto fallback; + + if (!parse_type(reply, DBUS_TYPE_INT32, size)) { + dbus_message_unref(reply); + goto fallback; + } + + dbus_message_unref(reply); + + return true; + + fallback: + return get_cursor_settings_from_env(theme, size); +} + +enum libdecor_color_scheme +libdecor_get_color_scheme() +{ + static const char name[] = "org.freedesktop.appearance"; + static const char key_color_scheme[] = "color-scheme"; + uint32_t color = 0; + + DBusError error; + DBusConnection *connection; + DBusMessage *reply; + + dbus_error_init(&error); + + connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + + if (dbus_error_is_set(&error)) + return 0; + + reply = get_setting_sync(connection, name, key_color_scheme); + if (!reply) + return 0; + + if (!parse_type(reply, DBUS_TYPE_UINT32, &color)) { + dbus_message_unref(reply); + return 0; + } + + dbus_message_unref(reply); + + return color; +} +#else +bool +libdecor_get_cursor_settings(char **theme, int *size) +{ + return get_cursor_settings_from_env(theme, size); +} + +uint32_t +libdecor_get_color_scheme() +{ + return LIBDECOR_COLOR_SCHEME_DEFAULT; +} +#endif + diff --git a/wayland_libdecor_egl.c b/wayland_libdecor_egl.c index a3b51d4..1ff5f9f 100755 --- a/wayland_libdecor_egl.c +++ b/wayland_libdecor_egl.c @@ -464,4 +464,3 @@ int main(){ return(0); } -