#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" root_path="$PWD" mkdir -p build clang -o build/demo -g $root_path/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 "digesting_libdecor.h" //@global vtables const struct wl_registry_listener wl_registry_listener; const struct xdg_surface_listener xdg_surface_listener; const struct xdg_toplevel_listener xdg_toplevel_listener; const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener; const struct xdg_wm_base_listener xdg_wm_base_listener; const struct wl_callback_listener init_wl_display_callback_listener; const struct wl_buffer_listener buffer_listener; const struct wl_callback_listener shm_callback_listener; const struct wl_registry_listener registry_listener; const struct wl_callback_listener globals_callback_listener; struct libdecor_plugin_interface gtk_plugin_iface; struct libdecor_plugin_interface fallback_plugin_iface; // 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 static Ctx ctx = {0}; 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); } else if (!strcmp(interface, xdg_wm_base_interface.name)){ init_xdg_wm_base(name, 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))){ ctx.decoration_manager = wl_registry_bind(ctx.wl_registry, name, &zxdg_decoration_manager_v1_interface, MIN(version,2)); } } } /* (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, }; static void registry_handle_global(void *user_data, struct wl_registry *wl_registry, uint32_t id, const char *interface, uint32_t version) { if (strcmp(interface, "wl_compositor") == 0){ init_wl_compositor(id, version); } else if (strcmp(interface, "wl_subcompositor") == 0){ init_wl_subcompositor(id, version); } else if (strcmp(interface, "wl_shm") == 0){ init_wl_shm(id, version); } else if (strcmp(interface, "wl_seat") == 0){ init_wl_seat(id, version); } else if (strcmp(interface, "wl_output") == 0){ init_wl_output(id, version); } } 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(output); break; } } } const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; /* (libdecor.h) libdecor_interface::error " An error event " */ static void libdecorevent__error(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(){ /* get desktop settings */ ctx.color_scheme = libdecor_get_color_scheme(); if (libdecor_get_cursor_settings(&ctx.cursor_theme_name, &ctx.cursor_size)){ ctx.cursor_theme_name = 0; ctx.cursor_size = 24; } /* setup GTK context */ int gtk_init_success = 0; { gdk_set_allowed_backends("wayland"); gtk_disable_setlocale(); if (gtk_init_check(0, 0)){ gtk_init_success = 1; } g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", (ctx.color_scheme == LIBDECOR_COLOR_SCHEME_PREFER_DARK), NULL); if (!gtk_init_success){ printf("failed to initialize gtk\n"); } } /*~ NOTE: **~ initialize Wayland, Libdecor, & EGL */ { ctx.plugin_gtk = calloc(1, sizeof *ctx.plugin_gtk); libdecor_plugin_init(&ctx.plugin_gtk->plugin, >k_plugin_iface); wl_list_init(&ctx.frames); wl_list_init(&ctx.plugin_gtk->visible_frame_list); wl_list_init(&ctx.plugin_gtk->seat_list); wl_list_init(&ctx.plugin_gtk->output_list); } if (gtk_init_success){ ctx.wl_display = wl_display_connect(0); if (ctx.wl_display == 0){ printf("wl_display_connect failed\n"); } } 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){ wl_registry_add_listener(ctx.wl_registry, &wl_registry_listener, 0); wl_display_dispatch(ctx.wl_display); wl_display_roundtrip(ctx.wl_display); if (ctx.wl_compositor == 0){ printf("failed to get wl_compositor\n"); } } if (ctx.wl_compositor != 0){ ctx.wl_callback = wl_display_sync(ctx.wl_display); wl_callback_add_listener(ctx.wl_callback, &init_wl_display_callback_listener, 0); ctx.plugin_gtk->wl_registry = wl_display_get_registry(ctx.wl_display); wl_registry_add_listener(ctx.plugin_gtk->wl_registry, ®istry_listener, ctx.plugin_gtk); ctx.plugin_gtk->globals_callback = wl_display_sync(ctx.wl_display); wl_callback_add_listener(ctx.plugin_gtk->globals_callback, &globals_callback_listener, ctx.plugin_gtk); wl_display_roundtrip(ctx.wl_display); if (ctx.plugin_gtk->wl_compositor != 0 && ctx.plugin_gtk->wl_subcompositor != 0 && ctx.plugin_gtk->wl_shm != 0){ ctx.plugin = &ctx.plugin_gtk->plugin; } if (ctx.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.plugin != 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.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(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 "libdecor.c" /* 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_plugin *plugin = ctx.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 = ctx.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 = ctx.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); } 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_plugin *plugin = ctx.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 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; } } 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 (!ctx.decoration_manager) return; frame_priv->toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(ctx.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; if (frame_priv->xdg_surface) return; frame_priv->xdg_surface = xdg_wm_base_get_xdg_surface(ctx.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 wl_surface *wl_surface, void *user_data){ struct libdecor_plugin *plugin = ctx.plugin; struct libdecor_frame *frame; struct libdecor_frame_private *frame_priv; if (ctx.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->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(&ctx.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 (ctx.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_plugin *plugin = ctx.plugin; if (ctx.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_plugin *plugin = ctx.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 (ctx.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 = ctx.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 = ctx.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_plugin *plugin = ctx.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_plugin *plugin = ctx.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_plugin *plugin = ctx.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(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_plugin *plugin = ctx.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; } 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_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); } const struct xdg_wm_base_listener xdg_wm_base_listener = { xdg_wm_base_ping, }; static void init_xdg_wm_base(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 ctx.xdg_wm_base = wl_registry_bind(ctx.wl_registry, id, &xdg_wm_base_interface, MIN(version, desired_version)); xdg_wm_base_add_listener(ctx.xdg_wm_base, &xdg_wm_base_listener, 0); } static void notify_error(enum libdecor_error error, const char *message) { ctx.has_error = true; libdecorevent__error(error, message); ctx.plugin->priv->iface->destroy(ctx.plugin); } static void finish_init(void){ struct libdecor_frame *frame; wl_list_for_each(frame, &ctx.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; ctx.init_done = true; wl_callback_destroy(callback); ctx.wl_callback = 0; if (ctx.xdg_wm_base == 0){ notify_error(LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, "Compositor is missing required interfaces"); } if (ctx.plugin_ready) { finish_init(); } } const struct wl_callback_listener init_wl_display_callback_listener = { init_wl_display_callback }; int libdecor_dispatch(int timeout) { struct libdecor_plugin *plugin = ctx.plugin; return plugin->priv->iface->dispatch(plugin, timeout); } void libdecor_notify_plugin_ready(void){ ctx.plugin_ready = true; if (ctx.init_done){ finish_init(); } } void libdecor_notify_plugin_error(enum libdecor_error error, const char *__restrict fmt, ...) { char *msg = NULL; int nbytes = 0; va_list argp; if (ctx.has_error) return; va_start(argp, fmt); nbytes = vasprintf(&msg, fmt, argp); va_end(argp); if (nbytes > 0){ notify_error(error, msg); } if (msg) free(msg); } void cleanup(void){ if (ctx.plugin != 0){ ctx.plugin->priv->iface->destroy(ctx.plugin); } if (ctx.wl_callback != 0){ wl_callback_destroy(ctx.wl_callback); } if (ctx.xdg_wm_base != 0){ xdg_wm_base_destroy(ctx.xdg_wm_base); } if (ctx.decoration_manager != 0){ zxdg_decoration_manager_v1_destroy(ctx.decoration_manager); } } //#include "libdecor-fallback.c" 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; return wl_display_get_fd(ctx.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 = ctx.wl_display; 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_LENGTH(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; } 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(void){ struct libdecor_plugin_fallback *plugin; plugin = calloc(1, sizeof *plugin); libdecor_plugin_init(&plugin->plugin, &fallback_plugin_iface); plugin->context = 0; libdecor_notify_plugin_ready(); return &plugin->plugin; } //#include "libdecor-cairo-blur.c" 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 "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 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_LENGTH(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; } 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); } 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 = ctx.wl_display; 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 = ctx.wl_display; 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_LENGTH(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; } 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; } 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(uint32_t id, uint32_t version){ ctx.plugin_gtk->wl_compositor = wl_registry_bind(ctx.plugin_gtk->wl_registry, id, &wl_compositor_interface, MIN(version, 4)); } static void init_wl_subcompositor(uint32_t id, uint32_t version){ ctx.plugin_gtk->wl_subcompositor = wl_registry_bind(ctx.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; wl_callback_destroy(callback); plugin_gtk->globals_callback_shm = NULL; if (!plugin_gtk->has_argb) { libdecor_notify_plugin_error(LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, "Compositor is missing required shm format"); return; } libdecor_notify_plugin_ready(); } const struct wl_callback_listener shm_callback_listener = { shm_callback }; static void init_wl_shm(uint32_t id, uint32_t version){ struct wl_display *wl_display = ctx.wl_display; ctx.plugin_gtk->wl_shm = wl_registry_bind(ctx.plugin_gtk->wl_registry, id, &wl_shm_interface, 1); wl_shm_add_listener(ctx.plugin_gtk->wl_shm, &shm_listener, ctx.plugin_gtk); ctx.plugin_gtk->globals_callback_shm = wl_display_sync(wl_display); wl_callback_add_listener(ctx.plugin_gtk->globals_callback_shm, &shm_callback_listener, ctx.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(ctx.cursor_theme_name, ctx.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); } } 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(uint32_t id, uint32_t version){ struct seat *seat; if (version < 3) { libdecor_notify_plugin_error(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 = ctx.plugin_gtk; wl_list_init(&seat->cursor_outputs); wl_list_insert(&ctx.plugin_gtk->seat_list, &seat->link); seat->wl_seat = wl_registry_bind(ctx.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(uint32_t id, uint32_t version){ struct output *output; if (version < 2) { libdecor_notify_plugin_error(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 = ctx.plugin_gtk; wl_list_insert(&ctx.plugin_gtk->output_list, &output->link); output->id = id; output->wl_output = wl_registry_bind(ctx.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 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 output *output) { struct libdecor_frame_gtk *frame_gtk; struct seat *seat; wl_list_for_each(frame_gtk, &ctx.plugin_gtk->visible_frame_list, link) { remove_surface_outputs(&frame_gtk->shadow, output); } wl_list_for_each(seat, &ctx.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 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; } const struct wl_callback_listener globals_callback_listener = { globals_callback }; static struct libdecor_plugin_priority priorities[] = { { NULL, LIBDECOR_PLUGIN_PRIORITY_HIGH } }; //#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