summaryrefslogtreecommitdiff
path: root/libdecor/src
diff options
context:
space:
mode:
authorManoloFLTK <41016272+ManoloFLTK@users.noreply.github.com>2022-03-04 15:40:29 +0100
committerManoloFLTK <41016272+ManoloFLTK@users.noreply.github.com>2022-03-04 15:41:00 +0100
commit3718effc431f5622a23c55b254153efdfe4e72c4 (patch)
treed8a805870c6a3785022e2f52f0c3715410e29a37 /libdecor/src
parenta773fdc44bfb818f1830e9e48ba765881e68c942 (diff)
Add the Wayland platform to FLTK 1.4
Diffstat (limited to 'libdecor/src')
-rw-r--r--libdecor/src/cursor-settings.c136
-rw-r--r--libdecor/src/cursor-settings.h6
-rw-r--r--libdecor/src/libdecor-fallback.c209
-rw-r--r--libdecor/src/libdecor-fallback.h35
-rw-r--r--libdecor/src/libdecor-plugin.h215
-rw-r--r--libdecor/src/libdecor.c1664
-rw-r--r--libdecor/src/libdecor.h521
-rw-r--r--libdecor/src/os-compatibility.c181
-rw-r--r--libdecor/src/os-compatibility.h34
-rw-r--r--libdecor/src/plugins/cairo/libdecor-cairo-blur.c255
-rw-r--r--libdecor/src/plugins/cairo/libdecor-cairo-blur.h10
-rw-r--r--libdecor/src/plugins/cairo/libdecor-cairo.c2824
-rw-r--r--libdecor/src/plugins/dummy/libdecor-dummy.c176
-rw-r--r--libdecor/src/plugins/gtk/libdecor-gtk.c2762
-rw-r--r--libdecor/src/utils.h48
15 files changed, 9076 insertions, 0 deletions
diff --git a/libdecor/src/cursor-settings.c b/libdecor/src/cursor-settings.c
new file mode 100644
index 000000000..b94623fd6
--- /dev/null
+++ b/libdecor/src/cursor-settings.c
@@ -0,0 +1,136 @@
+#include "cursor-settings.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include "config.h"
+
+#ifdef HAS_DBUS
+#include <dbus/dbus.h>
+
+static DBusMessage *
+get_setting_sync(DBusConnection *const connection,
+ const char *key,
+ const char *value)
+{
+ DBusError error;
+ dbus_bool_t success;
+ DBusMessage *message;
+ DBusMessage *reply;
+
+ dbus_error_init(&error);
+
+ 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;
+
+ 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))
+ return NULL;
+
+ 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))
+ return false;
+
+ reply = get_setting_sync(connection, name, key_theme);
+ if (!reply)
+ return false;
+
+ if (!parse_type(reply, DBUS_TYPE_STRING, &value_theme)) {
+ dbus_message_unref(reply);
+ return false;
+ }
+
+ *theme = strdup(value_theme);
+
+ dbus_message_unref(reply);
+
+ reply = get_setting_sync(connection, name, key_size);
+ if (!reply)
+ return false;
+
+ if (!parse_type(reply, DBUS_TYPE_INT32, size)) {
+ dbus_message_unref(reply);
+ return false;
+ }
+
+ dbus_message_unref(reply);
+
+ return true;
+}
+#else
+bool
+libdecor_get_cursor_settings(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;
+}
+#endif
diff --git a/libdecor/src/cursor-settings.h b/libdecor/src/cursor-settings.h
new file mode 100644
index 000000000..700bb4f30
--- /dev/null
+++ b/libdecor/src/cursor-settings.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <stdbool.h>
+
+bool
+libdecor_get_cursor_settings(char **theme, int *size);
diff --git a/libdecor/src/libdecor-fallback.c b/libdecor/src/libdecor-fallback.c
new file mode 100644
index 000000000..c281197c2
--- /dev/null
+++ b/libdecor/src/libdecor-fallback.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright © 2019 Jonas Ådahl
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "config.h"
+
+#include "libdecor-fallback.h"
+
+#include <poll.h>
+#include <errno.h>
+
+#include "utils.h"
+
+struct libdecor_plugin_fallback {
+ struct libdecor_plugin plugin;
+ struct libdecor *context;
+};
+
+static void
+libdecor_plugin_fallback_destroy(struct libdecor_plugin *plugin)
+{
+ libdecor_plugin_release(plugin);
+ free(plugin);
+}
+
+static int
+libdecor_plugin_fallback_get_fd(struct libdecor_plugin *plugin)
+{
+ struct libdecor_plugin_fallback *plugin_fallback =
+ (struct libdecor_plugin_fallback *) plugin;
+ struct wl_display *wl_display =
+ libdecor_get_wl_display(plugin_fallback->context);
+
+ return wl_display_get_fd(wl_display);
+}
+
+static int
+libdecor_plugin_fallback_dispatch(struct libdecor_plugin *plugin,
+ int timeout)
+{
+ struct libdecor_plugin_fallback *plugin_fallback =
+ (struct libdecor_plugin_fallback *) plugin;
+ struct wl_display *wl_display =
+ libdecor_get_wl_display(plugin_fallback->context);
+ struct pollfd fds[1];
+ int ret;
+ int dispatch_count = 0;
+
+ while (wl_display_prepare_read(wl_display) != 0)
+ dispatch_count += wl_display_dispatch_pending(wl_display);
+
+ if (wl_display_flush(wl_display) < 0 &&
+ errno != EAGAIN) {
+ wl_display_cancel_read(wl_display);
+ return -errno;
+ }
+
+ fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN };
+
+ ret = poll(fds, ARRAY_SIZE (fds), timeout);
+ if (ret > 0) {
+ if (fds[0].revents & POLLIN) {
+ wl_display_read_events(wl_display);
+ dispatch_count += wl_display_dispatch_pending(wl_display);
+ return dispatch_count;
+ } else {
+ wl_display_cancel_read(wl_display);
+ return dispatch_count;
+ }
+ } else if (ret == 0) {
+ wl_display_cancel_read(wl_display);
+ return dispatch_count;
+ } else {
+ wl_display_cancel_read(wl_display);
+ return -errno;
+ }
+}
+
+static struct libdecor_frame *
+libdecor_plugin_fallback_frame_new(struct libdecor_plugin *plugin)
+{
+ struct libdecor_frame *frame;
+
+ frame = zalloc(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_translate_coordinate(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ int content_x,
+ int content_y,
+ int *frame_x,
+ int *frame_y)
+{
+ *frame_x = content_x;
+ *frame_y = content_y;
+}
+
+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_configuration_get_content_size(struct libdecor_plugin *plugin,
+ struct libdecor_configuration *configuration,
+ struct libdecor_frame *frame,
+ int *content_width,
+ int *content_height)
+{
+ return libdecor_configuration_get_window_size(configuration,
+ content_width,
+ content_height);
+}
+
+static bool
+libdecor_plugin_fallback_frame_get_window_size_for(
+ struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ int *window_width,
+ int *window_height)
+{
+ *window_width = libdecor_state_get_content_width (state);
+ *window_height = libdecor_state_get_content_height (state);
+ return true;
+}
+
+static struct libdecor_plugin_interface fallback_plugin_iface = {
+ .destroy = libdecor_plugin_fallback_destroy,
+ .get_fd = libdecor_plugin_fallback_get_fd,
+ .dispatch = libdecor_plugin_fallback_dispatch,
+ .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_translate_coordinate =
+ libdecor_plugin_fallback_frame_translate_coordinate,
+ .frame_popup_grab = libdecor_plugin_fallback_frame_popup_grab,
+ .frame_popup_ungrab = libdecor_plugin_fallback_frame_popup_ungrab,
+ .configuration_get_content_size = libdecor_plugin_fallback_configuration_get_content_size,
+ .frame_get_window_size_for = libdecor_plugin_fallback_frame_get_window_size_for,
+};
+
+struct libdecor_plugin *
+libdecor_fallback_plugin_new(struct libdecor *context)
+{
+ struct libdecor_plugin_fallback *plugin;
+
+ plugin = zalloc(sizeof *plugin);
+ libdecor_plugin_init(&plugin->plugin, context, &fallback_plugin_iface);
+ plugin->context = context;
+
+ libdecor_notify_plugin_ready(context);
+
+ return &plugin->plugin;
+}
diff --git a/libdecor/src/libdecor-fallback.h b/libdecor/src/libdecor-fallback.h
new file mode 100644
index 000000000..40a5c51d2
--- /dev/null
+++ b/libdecor/src/libdecor-fallback.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2019 Jonas Ådahl
+ *
+ * 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.
+ */
+
+#ifndef LIBDECOR_FALLBACK_H
+#define LIBDECOR_FALLBACK_H
+
+#include "libdecor.h"
+#include "libdecor-plugin.h"
+
+struct libdecor_plugin *
+libdecor_fallback_plugin_new(struct libdecor *context);
+
+#endif /* LIBDECOR_FALLBACK_H */
diff --git a/libdecor/src/libdecor-plugin.h b/libdecor/src/libdecor-plugin.h
new file mode 100644
index 000000000..9e5d9510b
--- /dev/null
+++ b/libdecor/src/libdecor-plugin.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright © 2017-2018 Red Hat Inc.
+ * Copyright © 2018 Jonas Ådahl
+ *
+ * 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.
+ */
+
+#ifndef LIBDECOR_PLUGIN_H
+#define LIBDECOR_PLUGIN_H
+
+#include "libdecor.h"
+
+struct libdecor_frame_private;
+
+struct libdecor_frame {
+ struct libdecor_frame_private *priv;
+ struct wl_list link;
+};
+
+struct libdecor_plugin_private;
+
+struct libdecor_plugin {
+ struct libdecor_plugin_private *priv;
+};
+
+typedef struct libdecor_plugin * (* libdecor_plugin_constructor)(struct libdecor *context);
+
+#define LIBDECOR_PLUGIN_PRIORITY_HIGH 1000
+#define LIBDECOR_PLUGIN_PRIORITY_MEDIUM 100
+#define LIBDECOR_PLUGIN_PRIORITY_LOW 0
+
+struct libdecor_plugin_priority {
+ const char *desktop;
+ int priority;
+};
+
+enum libdecor_plugin_capabilities {
+ LIBDECOR_PLUGIN_CAPABILITY_BASE = 1 << 0,
+};
+
+struct libdecor_plugin_description {
+ /* API version the plugin is compatible with. */
+ int api_version;
+
+ /* Human readable string describing the plugin. */
+ char *description;
+
+ /* A plugin has a bitmask of capabilities. The plugin loader can use this
+ * to load a plugin with the right capabilities. */
+ enum libdecor_plugin_capabilities capabilities;
+
+ /*
+ * The priorities field points to a list of per desktop priorities.
+ * properties[i].desktop is matched against XDG_CURRENT_DESKTOP when
+ * determining what plugin to use. The last entry in the list MUST have
+ * the priorities[i].desktop pointer set to NULL as a default
+ * priority.
+ */
+ const struct libdecor_plugin_priority *priorities;
+
+ /* Vfunc used for constructing a plugin instance. */
+ libdecor_plugin_constructor constructor;
+};
+
+struct libdecor_plugin_interface {
+ void (* destroy)(struct libdecor_plugin *plugin);
+
+ int (* get_fd)(struct libdecor_plugin *plugin);
+ int (* dispatch)(struct libdecor_plugin *plugin,
+ int timeout);
+
+ struct libdecor_frame * (* frame_new)(struct libdecor_plugin *plugin);
+ void (* frame_free)(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame);
+ void (* frame_commit)(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ struct libdecor_configuration *configuration);
+ void (*frame_property_changed)(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame);
+ void (* frame_translate_coordinate)(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ int content_x,
+ int content_y,
+ int *window_x,
+ int *window_y);
+ void (* frame_popup_grab)(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ const char *seat_name);
+ void (* frame_popup_ungrab)(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ const char *seat_name);
+
+ bool (* frame_get_window_size_for)(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ int *window_width,
+ int *window_height);
+
+ bool (* configuration_get_content_size)(struct libdecor_plugin *plugin,
+ struct libdecor_configuration *configuration,
+ struct libdecor_frame *frame,
+ int *content_width,
+ int *content_height);
+
+ /* Reserved */
+ void (* reserved0)(void);
+ void (* reserved1)(void);
+ void (* reserved2)(void);
+ void (* reserved3)(void);
+ void (* reserved4)(void);
+ void (* reserved5)(void);
+ void (* reserved6)(void);
+ void (* reserved7)(void);
+ void (* reserved8)(void);
+ void (* reserved9)(void);
+};
+
+struct wl_surface *
+libdecor_frame_get_wl_surface(struct libdecor_frame *frame);
+
+int
+libdecor_frame_get_content_width(struct libdecor_frame *frame);
+
+int
+libdecor_frame_get_content_height(struct libdecor_frame *frame);
+
+enum libdecor_window_state
+libdecor_frame_get_window_state(struct libdecor_frame *frame);
+
+void
+libdecor_frame_set_window_geometry(struct libdecor_frame *frame,
+ int32_t x, int32_t y,
+ int32_t width, int32_t height);
+
+enum libdecor_capabilities
+libdecor_frame_get_capabilities(const struct libdecor_frame *frame);
+
+void
+libdecor_frame_dismiss_popup(struct libdecor_frame *frame,
+ const char *seat_name);
+
+void
+libdecor_frame_toplevel_commit(struct libdecor_frame *frame);
+
+struct wl_display *
+libdecor_get_wl_display(struct libdecor *context);
+
+void
+libdecor_notify_plugin_ready(struct libdecor *context);
+
+void
+libdecor_notify_plugin_error(struct libdecor *context,
+ enum libdecor_error error,
+ const char *__restrict fmt,
+ ...);
+
+int
+libdecor_state_get_content_width (struct libdecor_state *state);
+
+int
+libdecor_state_get_content_height (struct libdecor_state *state);
+
+enum libdecor_window_state
+libdecor_state_get_window_state(struct libdecor_state *state);
+
+bool
+libdecor_configuration_get_window_size(struct libdecor_configuration *configuration,
+ int *width,
+ int *height);
+
+int
+libdecor_plugin_init(struct libdecor_plugin *plugin,
+ struct libdecor *context,
+ struct libdecor_plugin_interface *iface);
+
+void
+libdecor_plugin_release(struct libdecor_plugin *plugin);
+
+/*
+ * Get the min content size as set before with libdecor_frame_set_min_content_size().
+ */
+void
+libdecor_frame_get_min_content_size(struct libdecor_frame *frame,
+ int *pcontent_width,
+ int *pcontent_height);
+
+/*
+ * Get the max content size as set before with libdecor_frame_set_max_content_size().
+ */
+void
+libdecor_frame_get_max_content_size(struct libdecor_frame *frame,
+ int *pcontent_width,
+ int *pcontent_height);
+
+#endif /* LIBDECOR_PLUGIN_H */
diff --git a/libdecor/src/libdecor.c b/libdecor/src/libdecor.c
new file mode 100644
index 000000000..2de2da3be
--- /dev/null
+++ b/libdecor/src/libdecor.c
@@ -0,0 +1,1664 @@
+/*
+ * Copyright © 2017-2018 Red Hat Inc.
+ * Copyright © 2018 Jonas Ådahl
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "libdecor.h"
+#include "libdecor-fallback.h"
+#include "libdecor-plugin.h"
+#include "utils.h"
+
+#include "xdg-shell-client-protocol.h"
+#include "xdg-decoration-client-protocol.h"
+
+struct libdecor {
+ int ref_count;
+
+ struct libdecor_interface *iface;
+
+ struct libdecor_plugin *plugin;
+ bool plugin_ready;
+
+ struct wl_display *wl_display;
+ struct wl_registry *wl_registry;
+ struct xdg_wm_base *xdg_wm_base;
+ struct zxdg_decoration_manager_v1 *decoration_manager;
+
+ struct wl_callback *init_callback;
+ bool init_done;
+ bool has_error;
+
+ struct wl_list frames;
+};
+
+struct libdecor_state {
+ enum libdecor_window_state window_state;
+
+ int content_width;
+ int content_height;
+};
+
+struct libdecor_limits {
+ int min_width;
+ int min_height;
+ int max_width;
+ int max_height;
+};
+
+struct libdecor_configuration {
+ uint32_t serial;
+
+ bool has_window_state;
+ enum libdecor_window_state window_state;
+
+ bool has_size;
+ int window_width;
+ int window_height;
+};
+
+struct libdecor_frame_private {
+ int ref_count;
+
+ struct libdecor *context;
+
+ struct wl_surface *wl_surface;
+
+ struct libdecor_frame_interface *iface;
+ void *user_data;
+
+ struct xdg_surface *xdg_surface;
+ struct xdg_toplevel *xdg_toplevel;
+ struct zxdg_toplevel_decoration_v1 *toplevel_decoration;
+
+ bool pending_map;
+
+ struct {
+ char *app_id;
+ char *title;
+ struct libdecor_limits content_limits;
+ struct xdg_toplevel *parent;
+ } state;
+
+ struct libdecor_configuration *pending_configuration;
+
+ int content_width;
+ int content_height;
+
+ enum libdecor_window_state window_state;
+
+ enum zxdg_toplevel_decoration_v1_mode decoration_mode;
+
+ enum libdecor_capabilities capabilities;
+
+ /* original limits for interactive resize */
+ struct libdecor_limits interactive_limits;
+
+ bool visible;
+};
+
+struct libdecor_plugin_private {
+ struct libdecor_plugin_interface *iface;
+};
+
+/* gather all states at which a window is non-floating */
+static const enum libdecor_window_state states_non_floating =
+ LIBDECOR_WINDOW_STATE_MAXIMIZED | LIBDECOR_WINDOW_STATE_FULLSCREEN |
+ LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT |
+ LIBDECOR_WINDOW_STATE_TILED_TOP | LIBDECOR_WINDOW_STATE_TILED_BOTTOM;
+
+static bool
+streql(const char *str1, const char *str2)
+{
+ return (str1 && str2) && (strcmp(str1, str2) == 0);
+}
+
+
+static void
+do_map(struct libdecor_frame *frame);
+
+static bool
+state_is_floating(enum libdecor_window_state window_state)
+{
+ return !(window_state & states_non_floating);
+}
+
+static void
+constrain_content_size(const struct libdecor_frame *frame,
+ int *width,
+ int *height)
+{
+ const struct libdecor_limits lim = frame->priv->state.content_limits;
+
+ if (lim.min_width > 0)
+ *width = MAX(lim.min_width, *width);
+ if (lim.max_width > 0)
+ *width = MIN(*width, lim.max_width);
+
+ if (lim.min_height > 0)
+ *height = MAX(lim.min_height, *height);
+ if (lim.max_height > 0)
+ *height = MIN(*height, lim.max_height);
+}
+
+static bool
+frame_has_visible_client_side_decoration(struct libdecor_frame *frame)
+{
+ /* visibility by client configuration */
+ const bool vis_client = frame->priv->visible;
+ /* visibility by compositor configuration */
+ const bool vis_server = (frame->priv->decoration_mode ==
+ ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
+
+ return vis_client && vis_server;
+}
+
+LIBDECOR_EXPORT int
+libdecor_state_get_content_width(struct libdecor_state *state)
+{
+ return state->content_width;
+}
+
+LIBDECOR_EXPORT int
+libdecor_state_get_content_height(struct libdecor_state *state)
+{
+ return state->content_height;
+}
+
+LIBDECOR_EXPORT enum libdecor_window_state
+libdecor_state_get_window_state(struct libdecor_state *state)
+{
+ return state->window_state;
+}
+
+LIBDECOR_EXPORT struct libdecor_state *
+libdecor_state_new(int width,
+ int height)
+{
+ struct libdecor_state *state;
+
+ state = zalloc(sizeof *state);
+ state->content_width = width;
+ state->content_height = height;
+
+ return state;
+}
+
+LIBDECOR_EXPORT void
+libdecor_state_free(struct libdecor_state *state)
+{
+ free(state);
+}
+
+static struct libdecor_configuration *
+libdecor_configuration_new(void)
+{
+ struct libdecor_configuration *configuration;
+
+ configuration = zalloc(sizeof *configuration);
+
+ return configuration;
+}
+
+static void
+libdecor_configuration_free(struct libdecor_configuration *configuration)
+{
+ free(configuration);
+}
+
+static bool
+frame_get_window_size_for(struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ int *window_width,
+ int *window_height)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+ struct libdecor *context = frame_priv->context;
+ struct libdecor_plugin *plugin = context->plugin;
+
+ if (frame_has_visible_client_side_decoration(frame)) {
+ return plugin->priv->iface->frame_get_window_size_for(
+ plugin, frame, state,
+ window_width, window_height);
+ } else {
+ *window_width = state->content_width;
+ *window_height = state->content_height;
+ return true;
+ }
+}
+
+static bool
+window_size_to_content_size(struct libdecor_configuration *configuration,
+ struct libdecor_frame *frame,
+ int *content_width,
+ int *content_height)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+ struct libdecor *context = frame_priv->context;
+ struct libdecor_plugin *plugin = context->plugin;
+
+ if (frame_has_visible_client_side_decoration(frame)) {
+ return plugin->priv->iface->configuration_get_content_size(
+ plugin, configuration, frame,
+ content_width, content_height);
+ } else {
+ *content_width = configuration->window_width;
+ *content_height = configuration->window_height;
+ return true;
+ }
+}
+
+LIBDECOR_EXPORT bool
+libdecor_configuration_get_content_size(struct libdecor_configuration *configuration,
+ struct libdecor_frame *frame,
+ int *width,
+ int *height)
+{
+ int content_width;
+ int content_height;
+
+ if (!configuration->has_size)
+ return false;
+
+ if (configuration->window_width == 0 || configuration->window_height == 0)
+ return false;
+
+ if (!window_size_to_content_size(configuration,
+ frame,
+ &content_width,
+ &content_height))
+ return false;
+
+ *width = content_width;
+ *height = content_height;
+
+ if (state_is_floating(configuration->window_state)) {
+ constrain_content_size(frame, width, height);
+ }
+
+ return true;
+}
+
+LIBDECOR_EXPORT bool
+libdecor_configuration_get_window_size(struct libdecor_configuration *configuration,
+ int *width,
+ int *height)
+{
+ 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;
+ return true;
+}
+
+LIBDECOR_EXPORT 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;
+
+ frame_priv->iface->configure(frame,
+ configuration,
+ frame_priv->user_data);
+
+ libdecor_configuration_free(configuration);
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+ xdg_surface_configure,
+};
+
+static enum libdecor_window_state
+parse_states(struct wl_array *states)
+{
+ enum libdecor_window_state pending_state = LIBDECOR_WINDOW_STATE_NONE;
+ uint32_t *p;
+
+ wl_array_for_each(p, states) {
+ enum xdg_toplevel_state state = *p;
+
+ switch (state) {
+ case XDG_TOPLEVEL_STATE_FULLSCREEN:
+ pending_state |= LIBDECOR_WINDOW_STATE_FULLSCREEN;
+ break;
+ case XDG_TOPLEVEL_STATE_MAXIMIZED:
+ pending_state |= LIBDECOR_WINDOW_STATE_MAXIMIZED;
+ break;
+ case XDG_TOPLEVEL_STATE_ACTIVATED:
+ pending_state |= LIBDECOR_WINDOW_STATE_ACTIVE;
+ break;
+ case XDG_TOPLEVEL_STATE_TILED_LEFT:
+ pending_state |= LIBDECOR_WINDOW_STATE_TILED_LEFT;
+ break;
+ case XDG_TOPLEVEL_STATE_TILED_RIGHT:
+ pending_state |= LIBDECOR_WINDOW_STATE_TILED_RIGHT;
+ break;
+ case XDG_TOPLEVEL_STATE_TILED_TOP:
+ pending_state |= LIBDECOR_WINDOW_STATE_TILED_TOP;
+ break;
+ case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
+ pending_state |= LIBDECOR_WINDOW_STATE_TILED_BOTTOM;
+ break;
+ 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;
+
+ frame_priv->iface->close(frame, frame_priv->user_data);
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ xdg_toplevel_configure,
+ xdg_toplevel_close,
+};
+
+static void
+toplevel_decoration_configure(
+ void *data,
+ struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
+ uint32_t mode)
+{
+ ((struct libdecor_frame_private *)(data))->decoration_mode = mode;
+}
+
+static const struct zxdg_toplevel_decoration_v1_listener
+ xdg_toplevel_decoration_listener = {
+ toplevel_decoration_configure,
+};
+
+void
+libdecor_frame_create_xdg_decoration(struct libdecor_frame_private *frame_priv)
+{
+ if (!frame_priv->context->decoration_manager)
+ return;
+
+ frame_priv->toplevel_decoration =
+ zxdg_decoration_manager_v1_get_toplevel_decoration(
+ frame_priv->context->decoration_manager,
+ frame_priv->xdg_toplevel);
+
+ zxdg_toplevel_decoration_v1_add_listener(
+ frame_priv->toplevel_decoration,
+ &xdg_toplevel_decoration_listener,
+ frame_priv);
+}
+
+static void
+init_shell_surface(struct libdecor_frame *frame)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+ struct libdecor *context = frame_priv->context;
+
+ if (frame_priv->xdg_surface)
+ return;
+
+ frame_priv->xdg_surface =
+ xdg_wm_base_get_xdg_surface(context->xdg_wm_base,
+ frame_priv->wl_surface);
+ xdg_surface_add_listener(frame_priv->xdg_surface,
+ &xdg_surface_listener,
+ frame);
+
+ frame_priv->xdg_toplevel =
+ xdg_surface_get_toplevel(frame_priv->xdg_surface);
+ xdg_toplevel_add_listener(frame_priv->xdg_toplevel,
+ &xdg_toplevel_listener,
+ frame);
+
+ frame_priv->decoration_mode =
+ ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
+ frame_priv->toplevel_decoration = NULL;
+ libdecor_frame_create_xdg_decoration(frame_priv);
+
+ if (frame_priv->state.parent) {
+ xdg_toplevel_set_parent(frame_priv->xdg_toplevel,
+ frame_priv->state.parent);
+ }
+ if (frame_priv->state.title) {
+ xdg_toplevel_set_title(frame_priv->xdg_toplevel,
+ frame_priv->state.title);
+ }
+ if (frame_priv->state.app_id) {
+ xdg_toplevel_set_app_id(frame_priv->xdg_toplevel,
+ frame_priv->state.app_id);
+ }
+
+ if (frame_priv->pending_map)
+ do_map(frame);
+}
+
+LIBDECOR_EXPORT struct libdecor_frame *
+libdecor_decorate(struct libdecor *context,
+ struct wl_surface *wl_surface,
+ struct libdecor_frame_interface *iface,
+ void *user_data)
+{
+ struct libdecor_plugin *plugin = context->plugin;
+ struct libdecor_frame *frame;
+ struct libdecor_frame_private *frame_priv;
+
+ if (context->has_error)
+ return NULL;
+
+ frame = plugin->priv->iface->frame_new(plugin);
+ if (!frame)
+ return NULL;
+
+ frame_priv = zalloc(sizeof *frame_priv);
+ frame->priv = frame_priv;
+
+ frame_priv->ref_count = 1;
+ frame_priv->context = context;
+
+ frame_priv->wl_surface = wl_surface;
+ frame_priv->iface = iface;
+ frame_priv->user_data = user_data;
+
+ wl_list_insert(&context->frames, &frame->link);
+
+ libdecor_frame_set_capabilities(frame,
+ LIBDECOR_ACTION_MOVE |
+ LIBDECOR_ACTION_RESIZE |
+ LIBDECOR_ACTION_MINIMIZE |
+ LIBDECOR_ACTION_FULLSCREEN |
+ LIBDECOR_ACTION_CLOSE);
+
+ frame_priv->visible = true;
+
+ if (context->init_done)
+ init_shell_surface(frame);
+
+ return frame;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_ref(struct libdecor_frame *frame)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ frame_priv->ref_count++;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_unref(struct libdecor_frame *frame)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ frame_priv->ref_count--;
+ if (frame_priv->ref_count == 0) {
+ struct libdecor *context = frame_priv->context;
+ struct libdecor_plugin *plugin = context->plugin;
+
+ 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);
+ }
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_visibility(struct libdecor_frame *frame,
+ bool visible)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+ struct libdecor *context = frame_priv->context;
+ struct libdecor_plugin *plugin = context->plugin;
+
+ frame_priv->visible = visible;
+
+ /* enable/disable decorations that are managed by the compositor,
+ * only xdg-decoration version 2 and above allows to toggle decoration */
+ if (context->decoration_manager &&
+ zxdg_decoration_manager_v1_get_version(context->decoration_manager) > 1) {
+ if (frame_priv->visible &&
+ frame_priv->toplevel_decoration == NULL) {
+ /* - request to SHOW decorations
+ * - decorations are NOT HANDLED
+ * => create new decorations for already mapped surface */
+ libdecor_frame_create_xdg_decoration(frame_priv);
+ } else if (!frame_priv->visible &&
+ frame_priv->toplevel_decoration != NULL) {
+ /* - request to HIDE decorations
+ * - decorations are HANDLED
+ * => destroy decorations */
+ zxdg_toplevel_decoration_v1_destroy(frame_priv->toplevel_decoration);
+ frame_priv->toplevel_decoration = NULL;
+ }
+ }
+
+ /* 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);
+
+ libdecor_frame_set_window_geometry(frame, 0, 0,
+ frame_priv->content_width,
+ frame_priv->content_height);
+ }
+
+ libdecor_frame_toplevel_commit(frame);
+}
+
+LIBDECOR_EXPORT bool
+libdecor_frame_is_visible(struct libdecor_frame *frame)
+{
+ return frame->priv->visible;
+}
+
+LIBDECOR_EXPORT 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->priv->xdg_toplevel;
+
+ xdg_toplevel_set_parent(frame_priv->xdg_toplevel,
+ parent->priv->xdg_toplevel);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_title(struct libdecor_frame *frame,
+ const char *title)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+ struct libdecor_plugin *plugin = frame_priv->context->plugin;
+
+ if (!streql(frame_priv->state.title, title)) {
+ free(frame_priv->state.title);
+ frame_priv->state.title = strdup(title);
+
+ if (!frame_priv->xdg_toplevel)
+ return;
+
+ xdg_toplevel_set_title(frame_priv->xdg_toplevel, title);
+
+ plugin->priv->iface->frame_property_changed(plugin, frame);
+ }
+}
+
+LIBDECOR_EXPORT const char *
+libdecor_frame_get_title(struct libdecor_frame *frame)
+{
+ return frame->priv->state.title;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_app_id(struct libdecor_frame *frame,
+ const char *app_id)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ free(frame_priv->state.app_id);
+ frame_priv->state.app_id = strdup(app_id);
+
+ if (!frame_priv->xdg_toplevel)
+ return;
+
+ xdg_toplevel_set_app_id(frame_priv->xdg_toplevel, app_id);
+}
+
+static void
+notify_on_capability_change(struct libdecor_frame *frame,
+ const enum libdecor_capabilities old_capabilities)
+{
+ struct libdecor_plugin *plugin = frame->priv->context->plugin;
+ struct libdecor_state *state;
+
+ if (frame->priv->capabilities == old_capabilities)
+ return;
+
+ if (frame->priv->content_width == 0 ||
+ frame->priv->content_height == 0)
+ return;
+
+ plugin->priv->iface->frame_property_changed(plugin, frame);
+
+ if (!libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE)) {
+ frame->priv->interactive_limits = frame->priv->state.content_limits;
+ /* set fixed window size */
+ libdecor_frame_set_min_content_size(frame,
+ frame->priv->content_width,
+ frame->priv->content_height);
+ libdecor_frame_set_max_content_size(frame,
+ frame->priv->content_width,
+ frame->priv->content_height);
+ } else {
+ /* restore old limits */
+ frame->priv->state.content_limits = frame->priv->interactive_limits;
+ }
+
+ state = libdecor_state_new(frame->priv->content_width,
+ frame->priv->content_height);
+ libdecor_frame_commit(frame, state, NULL);
+ libdecor_state_free(state);
+
+ libdecor_frame_toplevel_commit(frame);
+}
+
+LIBDECOR_EXPORT 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);
+}
+
+LIBDECOR_EXPORT 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);
+}
+
+LIBDECOR_EXPORT bool
+libdecor_frame_has_capability(struct libdecor_frame *frame,
+ enum libdecor_capabilities capability)
+{
+ return frame->priv->capabilities & capability;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_popup_grab(struct libdecor_frame *frame,
+ const char *seat_name)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+ struct libdecor *context = frame_priv->context;
+ struct libdecor_plugin *plugin = context->plugin;
+
+ plugin->priv->iface->frame_popup_grab(plugin, frame, seat_name);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_popup_ungrab(struct libdecor_frame *frame,
+ const char *seat_name)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+ struct libdecor *context = frame_priv->context;
+ struct libdecor_plugin *plugin = context->plugin;
+
+ plugin->priv->iface->frame_popup_ungrab(plugin, frame, seat_name);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_dismiss_popup(struct libdecor_frame *frame,
+ const char *seat_name)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ frame_priv->iface->dismiss_popup(frame, seat_name, frame_priv->user_data);
+}
+
+LIBDECOR_EXPORT 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);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_translate_coordinate(struct libdecor_frame *frame,
+ int content_x,
+ int content_y,
+ int *frame_x,
+ int *frame_y)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+ struct libdecor *context = frame_priv->context;
+ struct libdecor_plugin *plugin = context->plugin;
+
+ plugin->priv->iface->frame_translate_coordinate(plugin, frame,
+ content_x, content_y,
+ frame_x, frame_y);
+}
+
+LIBDECOR_EXPORT 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;
+}
+
+LIBDECOR_EXPORT 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;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_get_min_content_size(struct libdecor_frame *frame,
+ int *pcontent_width,
+ int *pcontent_height)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ *pcontent_width = frame_priv->state.content_limits.min_width;
+ *pcontent_height = frame_priv->state.content_limits.min_height;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_get_max_content_size(struct libdecor_frame *frame,
+ int *pcontent_width,
+ int *pcontent_height)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ *pcontent_width = frame_priv->state.content_limits.max_width;
+ *pcontent_height = frame_priv->state.content_limits.max_height;
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_window_geometry(struct libdecor_frame *frame,
+ int32_t x, int32_t y,
+ int32_t width, int32_t height)
+{
+ xdg_surface_set_window_geometry(frame->priv->xdg_surface, x, y, width, height);
+}
+
+LIBDECOR_EXPORT 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();
+}
+
+LIBDECOR_EXPORT 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);
+}
+
+LIBDECOR_EXPORT 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);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_minimized(struct libdecor_frame *frame)
+{
+ xdg_toplevel_set_minimized(frame->priv->xdg_toplevel);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_maximized(struct libdecor_frame *frame)
+{
+ xdg_toplevel_set_maximized(frame->priv->xdg_toplevel);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_unset_maximized(struct libdecor_frame *frame)
+{
+ xdg_toplevel_unset_maximized(frame->priv->xdg_toplevel);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_set_fullscreen(struct libdecor_frame *frame,
+ struct wl_output *output)
+{
+ xdg_toplevel_set_fullscreen(frame->priv->xdg_toplevel, output);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_unset_fullscreen(struct libdecor_frame *frame)
+{
+ xdg_toplevel_unset_fullscreen(frame->priv->xdg_toplevel);
+}
+
+LIBDECOR_EXPORT bool
+libdecor_frame_is_floating(struct libdecor_frame *frame)
+{
+ return state_is_floating(frame->priv->window_state);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_close(struct libdecor_frame *frame)
+{
+ xdg_toplevel_close(frame, frame->priv->xdg_toplevel);
+}
+
+bool
+valid_limits(struct libdecor_frame_private *frame_priv)
+{
+ if (frame_priv->state.content_limits.min_width > 0 &&
+ frame_priv->state.content_limits.max_width > 0 &&
+ frame_priv->state.content_limits.min_width >
+ frame_priv->state.content_limits.max_width)
+ return false;
+
+ if (frame_priv->state.content_limits.min_height > 0 &&
+ frame_priv->state.content_limits.max_height > 0 &&
+ frame_priv->state.content_limits.min_height >
+ frame_priv->state.content_limits.max_height)
+ return false;
+
+ return true;
+}
+
+static void
+libdecor_frame_apply_limits(struct libdecor_frame *frame,
+ enum libdecor_window_state window_state)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ if (!valid_limits(frame_priv)) {
+ libdecor_notify_plugin_error(
+ frame_priv->context,
+ LIBDECOR_ERROR_INVALID_FRAME_CONFIGURATION,
+ "minimum size (%i,%i) must be smaller than maximum size (%i,%i)",
+ frame_priv->state.content_limits.min_width,
+ frame_priv->state.content_limits.min_height,
+ frame_priv->state.content_limits.max_width,
+ frame_priv->state.content_limits.max_height);
+ }
+
+ /* If the frame is configured as non-resizable before the first
+ * configure event is received, we have to manually set the min/max
+ * limits with the configured content size afterwards. */
+ if (!libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE)) {
+ frame_priv->state.content_limits.min_width =
+ frame_priv->content_width;
+ frame_priv->state.content_limits.max_width =
+ frame_priv->content_width;
+
+ frame_priv->state.content_limits.min_height =
+ frame_priv->content_height;
+ frame_priv->state.content_limits.max_height =
+ frame_priv->content_height;
+ }
+
+ if (frame_priv->state.content_limits.min_width > 0 &&
+ frame_priv->state.content_limits.min_height > 0) {
+ struct libdecor_state state_min;
+ int win_min_width, win_min_height;
+
+ state_min.content_width = frame_priv->state.content_limits.min_width;
+ state_min.content_height = frame_priv->state.content_limits.min_height;
+ state_min.window_state = window_state;
+
+ frame_get_window_size_for(frame, &state_min,
+ &win_min_width, &win_min_height);
+ xdg_toplevel_set_min_size(frame_priv->xdg_toplevel,
+ win_min_width, win_min_height);
+ } else {
+ xdg_toplevel_set_min_size(frame_priv->xdg_toplevel, 0, 0);
+ }
+
+ if (frame_priv->state.content_limits.max_width > 0 &&
+ frame_priv->state.content_limits.max_height > 0) {
+ struct libdecor_state state_max;
+ int win_max_width, win_max_height;
+
+ state_max.content_width = frame_priv->state.content_limits.max_width;
+ state_max.content_height = frame_priv->state.content_limits.max_height;
+ state_max.window_state = window_state;
+
+ frame_get_window_size_for(frame, &state_max,
+ &win_max_width, &win_max_height);
+ xdg_toplevel_set_max_size(frame_priv->xdg_toplevel,
+ win_max_width, win_max_height);
+ } else {
+ xdg_toplevel_set_max_size(frame_priv->xdg_toplevel, 0, 0);
+ }
+}
+
+static void
+libdecor_frame_apply_state(struct libdecor_frame *frame,
+ struct libdecor_state *state)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ frame_priv->content_width = state->content_width;
+ frame_priv->content_height = state->content_height;
+
+ /* do not set limits in non-floating states */
+ if (state_is_floating(state->window_state)) {
+ libdecor_frame_apply_limits(frame, state->window_state);
+ }
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_toplevel_commit(struct libdecor_frame *frame)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ frame_priv->iface->commit(frame, frame_priv->user_data);
+}
+
+LIBDECOR_EXPORT void
+libdecor_frame_commit(struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ struct libdecor_configuration *configuration)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+ struct libdecor *context = frame_priv->context;
+ struct libdecor_plugin *plugin = context->plugin;
+
+ if (configuration && configuration->has_window_state) {
+ frame_priv->window_state = configuration->window_state;
+ state->window_state = configuration->window_state;
+ } else {
+ state->window_state = frame_priv->window_state;
+ }
+
+ libdecor_frame_apply_state(frame, state);
+
+ /* switch between decoration modes */
+ if (frame_has_visible_client_side_decoration(frame)) {
+ plugin->priv->iface->frame_commit(plugin, frame, state,
+ configuration);
+ } else {
+ plugin->priv->iface->frame_free(plugin, frame);
+
+ libdecor_frame_set_window_geometry(frame, 0, 0,
+ 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);
+}
+
+LIBDECOR_EXPORT 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);
+}
+
+LIBDECOR_EXPORT 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;
+}
+
+LIBDECOR_EXPORT 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;
+}
+
+LIBDECOR_EXPORT struct xdg_toplevel *
+libdecor_frame_get_xdg_toplevel(struct libdecor_frame *frame)
+{
+ return frame->priv->xdg_toplevel;
+}
+
+LIBDECOR_EXPORT int
+libdecor_frame_get_content_width(struct libdecor_frame *frame)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ return frame_priv->content_width;
+}
+
+LIBDECOR_EXPORT int
+libdecor_frame_get_content_height(struct libdecor_frame *frame)
+{
+ struct libdecor_frame_private *frame_priv = frame->priv;
+
+ return frame_priv->content_height;
+}
+
+LIBDECOR_EXPORT 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;
+}
+
+LIBDECOR_EXPORT int
+libdecor_plugin_init(struct libdecor_plugin *plugin,
+ struct libdecor *context,
+ struct libdecor_plugin_interface *iface)
+{
+ plugin->priv = zalloc(sizeof (struct libdecor_plugin_private));
+ if (!plugin->priv)
+ return -1;
+
+ plugin->priv->iface = iface;
+
+ return 0;
+}
+
+LIBDECOR_EXPORT void
+libdecor_plugin_release(struct libdecor_plugin *plugin)
+{
+ free(plugin->priv);
+}
+
+static void
+xdg_wm_base_ping(void *user_data,
+ struct xdg_wm_base *xdg_wm_base,
+ uint32_t serial)
+{
+ xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+ xdg_wm_base_ping,
+};
+
+static void
+init_xdg_wm_base(struct libdecor *context,
+ uint32_t id,
+ uint32_t version)
+{
+ context->xdg_wm_base = wl_registry_bind(context->wl_registry,
+ id,
+ &xdg_wm_base_interface,
+ MIN(version,2));
+ xdg_wm_base_add_listener(context->xdg_wm_base,
+ &xdg_wm_base_listener,
+ context);
+}
+
+static void
+registry_handle_global(void *user_data,
+ struct wl_registry *wl_registry,
+ uint32_t id,
+ const char *interface,
+ uint32_t version)
+{
+ struct libdecor *context = user_data;
+
+ if (!strcmp(interface, xdg_wm_base_interface.name)) {
+ init_xdg_wm_base(context, id, version);
+ } else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)) {
+ context->decoration_manager = wl_registry_bind(
+ context->wl_registry, id,
+ &zxdg_decoration_manager_v1_interface,
+ MIN(version,2));
+ }
+}
+
+static void
+registry_handle_global_remove(void *user_data,
+ struct wl_registry *wl_registry,
+ uint32_t name)
+{
+}
+
+static const struct wl_registry_listener registry_listener = {
+ registry_handle_global,
+ registry_handle_global_remove
+};
+
+static bool
+is_compositor_compatible(struct libdecor *context)
+{
+ if (!context->xdg_wm_base)
+ return false;
+
+ return true;
+}
+
+static void
+notify_error(struct libdecor *context,
+ enum libdecor_error error,
+ const char *message)
+{
+ context->has_error = true;
+ context->iface->error(context, error, message);
+ context->plugin->priv->iface->destroy(context->plugin);
+}
+
+static void
+finish_init(struct libdecor *context)
+{
+ struct libdecor_frame *frame;
+
+ wl_list_for_each(frame, &context->frames, link)
+ init_shell_surface(frame);
+}
+
+static void
+init_wl_display_callback(void *user_data,
+ struct wl_callback *callback,
+ uint32_t time)
+{
+ struct libdecor *context = user_data;
+
+ context->init_done = true;
+
+ wl_callback_destroy(callback);
+ context->init_callback = NULL;
+
+ if (!is_compositor_compatible(context)) {
+ notify_error(context,
+ LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+ "Compositor is missing required interfaces");
+ }
+
+ if (context->plugin_ready) {
+ finish_init(context);
+ }
+}
+
+static const struct wl_callback_listener init_wl_display_callback_listener = {
+ init_wl_display_callback
+};
+
+struct plugin_loader {
+ struct wl_list link;
+ void *lib;
+ const struct libdecor_plugin_description *description;
+ int priority;
+ char *name;
+};
+
+static int
+calculate_priority(const struct libdecor_plugin_description *plugin_description)
+{
+ const char *current_desktop;
+ int i;
+
+ if (!plugin_description->priorities)
+ return -1;
+
+ current_desktop = getenv("XDG_CURRENT_DESKTOP");
+
+ i = 0;
+ while (true) {
+ struct libdecor_plugin_priority priority =
+ plugin_description->priorities[i];
+
+ i++;
+
+ if (priority.desktop) {
+ char *tokens;
+ char *saveptr;
+ char *token;
+
+ if (!current_desktop)
+ continue;
+
+ tokens = strdup(current_desktop);
+ token = strtok_r(tokens, ":", &saveptr);
+ while (token) {
+ if (strcmp(priority.desktop, token) == 0) {
+ free(tokens);
+ return priority.priority;
+ }
+ token = strtok_r(NULL, ":", &saveptr);
+ }
+ free(tokens);
+ } else {
+ return priority.priority;
+ }
+ }
+
+ return -1;
+}
+
+static struct plugin_loader *
+load_plugin_loader(struct libdecor *context,
+ const char *path,
+ const char *name)
+{
+ char *ext;
+ char *filename;
+ void *lib;
+ const struct libdecor_plugin_description *plugin_description;
+ int priority;
+ struct plugin_loader *plugin_loader;
+
+ ext = strrchr(name, '.');
+ if (ext == NULL || strcmp(ext, ".so") != 0)
+ return NULL;
+
+ if (asprintf(&filename, "%s/%s", path, name) == -1)
+ return NULL;
+
+ lib = dlopen(filename, RTLD_NOW | RTLD_LAZY);
+ free(filename);
+ if (!lib) {
+ fprintf(stderr, "Failed to load plugin: '%s'\n", dlerror());
+ return NULL;
+ }
+
+ plugin_description = dlsym(lib, "libdecor_plugin_description");
+ if (!plugin_description) {
+ fprintf(stderr,
+ "Failed to load plugin '%s': no plugin description symbol\n",
+ name);
+ dlclose(lib);
+ return NULL;
+ }
+
+ if (plugin_description->api_version != LIBDECOR_PLUGIN_API_VERSION) {
+ fprintf(stderr,
+ "Plugin '%s' found, but it's incompatible "
+ "(expected API version %d, but got %d)\n",
+ name,
+ LIBDECOR_PLUGIN_API_VERSION,
+ plugin_description->api_version);
+ dlclose(lib);
+ return NULL;
+ }
+
+ if (!(plugin_description->capabilities & LIBDECOR_PLUGIN_CAPABILITY_BASE)) {
+ dlclose(lib);
+ return NULL;
+ }
+
+ priority = calculate_priority(plugin_description);
+ if (priority == -1) {
+ fprintf(stderr,
+ "Plugin '%s' found, but has an invalid description\n",
+ name);
+ dlclose(lib);
+ return NULL;
+ }
+
+ plugin_loader = zalloc(sizeof *plugin_loader);
+ plugin_loader->description = plugin_description;
+ plugin_loader->lib = lib;
+ plugin_loader->priority = priority;
+ plugin_loader->name = strdup(name);
+
+ return plugin_loader;
+}
+
+static bool
+plugin_loader_higher_priority(struct plugin_loader *plugin_loader,
+ struct plugin_loader *best_plugin_loader)
+{
+ return plugin_loader->priority > best_plugin_loader->priority;
+}
+
+static int
+init_plugins(struct libdecor *context)
+{
+ const char *plugin_dir_env;
+ char *all_plugin_dirs;
+ char *plugin_dir;
+ char *saveptr;
+ DIR *dir;
+ struct wl_list plugin_loaders;
+ struct plugin_loader *plugin_loader, *tmp;
+ struct plugin_loader *best_plugin_loader;
+ struct libdecor_plugin *plugin;
+
+ plugin_dir_env = getenv("LIBDECOR_PLUGIN_DIR");
+ if (!plugin_dir_env) {
+ plugin_dir_env = LIBDECOR_PLUGIN_DIR;
+ }
+ all_plugin_dirs = strdup(plugin_dir_env);
+
+ wl_list_init(&plugin_loaders);
+ plugin_dir = strtok_r(all_plugin_dirs, ":", &saveptr);
+ while (plugin_dir) {
+ dir = opendir(plugin_dir);
+ if (!dir) {
+ fprintf(stderr, "Couldn't open plugin directory: %s\n",
+ strerror(errno));
+ continue;
+ }
+
+ while (true) {
+ struct dirent *de;
+
+ de = readdir(dir);
+ if (!de)
+ break;
+
+ plugin_loader = load_plugin_loader(context, plugin_dir, de->d_name);
+ if (!plugin_loader)
+ continue;
+
+ wl_list_insert(plugin_loaders.prev, &plugin_loader->link);
+ }
+
+ closedir(dir);
+
+ plugin_dir = strtok_r(NULL, ":", &saveptr);
+ }
+ free(all_plugin_dirs);
+
+retry_next:
+ best_plugin_loader = NULL;
+ wl_list_for_each(plugin_loader, &plugin_loaders, link) {
+ if (!best_plugin_loader) {
+ best_plugin_loader = plugin_loader;
+ continue;
+ }
+
+ if (plugin_loader_higher_priority(plugin_loader,
+ best_plugin_loader))
+ best_plugin_loader = plugin_loader;
+ }
+
+ if (!best_plugin_loader)
+ return -1;
+
+ plugin_loader = best_plugin_loader;
+ plugin = plugin_loader->description->constructor(context);
+ if (!plugin) {
+ fprintf(stderr,
+ "Failed to load plugin '%s': failed to init\n",
+ plugin_loader->name);
+ dlclose(plugin_loader->lib);
+ wl_list_remove(&plugin_loader->link);
+ free(plugin_loader->name);
+ free(plugin_loader);
+ goto retry_next;
+ }
+
+ context->plugin = plugin;
+
+ wl_list_remove(&plugin_loader->link);
+ free(plugin_loader->name);
+ free(plugin_loader);
+
+ wl_list_for_each_safe(plugin_loader, tmp, &plugin_loaders, link) {
+ dlclose(plugin_loader->lib);
+ free(plugin_loader->name);
+ free(plugin_loader);
+ }
+
+ return 0;
+}
+
+LIBDECOR_EXPORT int
+libdecor_get_fd(struct libdecor *context)
+{
+ struct libdecor_plugin *plugin = context->plugin;
+
+ return plugin->priv->iface->get_fd(plugin);
+}
+
+LIBDECOR_EXPORT int
+libdecor_dispatch(struct libdecor *context,
+ int timeout)
+{
+ struct libdecor_plugin *plugin = context->plugin;
+
+ return plugin->priv->iface->dispatch(plugin, timeout);
+}
+
+LIBDECOR_EXPORT struct wl_display *
+libdecor_get_wl_display(struct libdecor *context)
+{
+ return context->wl_display;
+}
+
+LIBDECOR_EXPORT void
+libdecor_notify_plugin_ready(struct libdecor *context)
+{
+ context->plugin_ready = true;
+
+ if (context->init_done)
+ finish_init(context);
+}
+
+LIBDECOR_EXPORT void
+libdecor_notify_plugin_error(struct libdecor *context,
+ enum libdecor_error error,
+ const char *__restrict fmt,
+ ...)
+{
+ char *msg = NULL;
+ int nbytes = 0;
+ va_list argp;
+
+ if (context->has_error)
+ return;
+
+ va_start(argp, fmt);
+ nbytes = vasprintf(&msg, fmt, argp);
+ va_end(argp);
+
+ if (nbytes>0)
+ notify_error(context, error, msg);
+
+ if (msg)
+ free(msg);
+}
+
+LIBDECOR_EXPORT void
+libdecor_unref(struct libdecor *context)
+{
+ context->ref_count--;
+ if (context->ref_count == 0) {
+ if (context->plugin)
+ context->plugin->priv->iface->destroy(context->plugin);
+ if (context->init_callback)
+ wl_callback_destroy(context->init_callback);
+ wl_registry_destroy(context->wl_registry);
+ if (context->xdg_wm_base)
+ xdg_wm_base_destroy(context->xdg_wm_base);
+ if (context->decoration_manager)
+ zxdg_decoration_manager_v1_destroy(
+ context->decoration_manager);
+ free(context);
+ }
+}
+
+LIBDECOR_EXPORT struct libdecor *
+libdecor_new(struct wl_display *wl_display,
+ struct libdecor_interface *iface)
+{
+ struct libdecor *context;
+
+ context = zalloc(sizeof *context);
+
+ context->ref_count = 1;
+ context->iface = iface;
+ context->wl_display = wl_display;
+ context->wl_registry = wl_display_get_registry(wl_display);
+ wl_registry_add_listener(context->wl_registry,
+ &registry_listener,
+ context);
+ context->init_callback = wl_display_sync(context->wl_display);
+ wl_callback_add_listener(context->init_callback,
+ &init_wl_display_callback_listener,
+ context);
+
+ wl_list_init(&context->frames);
+
+ if (init_plugins(context) != 0) {
+ fprintf(stderr,
+ "No plugins found, falling back on no decorations\n");
+ context->plugin = libdecor_fallback_plugin_new(context);
+ }
+
+ wl_display_flush(wl_display);
+
+ return context;
+}
diff --git a/libdecor/src/libdecor.h b/libdecor/src/libdecor.h
new file mode 100644
index 000000000..214a10c4e
--- /dev/null
+++ b/libdecor/src/libdecor.h
@@ -0,0 +1,521 @@
+/*
+ * Copyright © 2017-2018 Red Hat Inc.
+ * Copyright © 2018 Jonas Ådahl
+ *
+ * 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.
+ */
+
+#ifndef LIBDECOR_H
+#define LIBDECOR_H
+
+#include <stdbool.h>
+#include <wayland-client.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__GNUC__) && __GNUC__ >= 4
+#define LIBDECOR_EXPORT __attribute__ ((visibility("default")))
+#else
+#define LIBDECOR_EXPORT
+#endif
+
+struct xdg_toplevel;
+
+/** \class libdecor
+ *
+ * \brief A libdecor context instance.
+ */
+struct libdecor;
+
+/** \class libdecor_frame
+ *
+ * \brief A frame used for decorating a Wayland surface.
+ */
+struct libdecor_frame;
+
+/** \class libdecor_configuration
+ *
+ * \brief An object representing a toplevel window configuration.
+ */
+struct libdecor_configuration;
+
+/** \class libdecor_state
+ *
+ * \brief An object corresponding to a configured content state.
+ */
+struct libdecor_state;
+
+enum libdecor_error {
+ LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+ LIBDECOR_ERROR_INVALID_FRAME_CONFIGURATION,
+};
+
+enum libdecor_window_state {
+ LIBDECOR_WINDOW_STATE_NONE = 0,
+ LIBDECOR_WINDOW_STATE_ACTIVE = 1 << 0,
+ LIBDECOR_WINDOW_STATE_MAXIMIZED = 1 << 1,
+ LIBDECOR_WINDOW_STATE_FULLSCREEN = 1 << 2,
+ LIBDECOR_WINDOW_STATE_TILED_LEFT = 1 << 3,
+ LIBDECOR_WINDOW_STATE_TILED_RIGHT = 1 << 4,
+ LIBDECOR_WINDOW_STATE_TILED_TOP = 1 << 5,
+ LIBDECOR_WINDOW_STATE_TILED_BOTTOM = 1 << 6,
+};
+
+enum libdecor_resize_edge {
+ LIBDECOR_RESIZE_EDGE_NONE,
+ LIBDECOR_RESIZE_EDGE_TOP,
+ LIBDECOR_RESIZE_EDGE_BOTTOM,
+ LIBDECOR_RESIZE_EDGE_LEFT,
+ LIBDECOR_RESIZE_EDGE_TOP_LEFT,
+ LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT,
+ LIBDECOR_RESIZE_EDGE_RIGHT,
+ LIBDECOR_RESIZE_EDGE_TOP_RIGHT,
+ LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT,
+};
+
+enum libdecor_capabilities {
+ LIBDECOR_ACTION_MOVE = 1 << 0,
+ LIBDECOR_ACTION_RESIZE = 1 << 1,
+ LIBDECOR_ACTION_MINIMIZE = 1 << 2,
+ LIBDECOR_ACTION_FULLSCREEN = 1 << 3,
+ LIBDECOR_ACTION_CLOSE = 1 << 4,
+};
+
+struct libdecor_interface {
+ /**
+ * An error event
+ */
+ void (* error)(struct libdecor *context,
+ enum libdecor_error error,
+ const char *message);
+
+ /* Reserved */
+ void (* reserved0)(void);
+ void (* reserved1)(void);
+ void (* reserved2)(void);
+ void (* reserved3)(void);
+ void (* reserved4)(void);
+ void (* reserved5)(void);
+ void (* reserved6)(void);
+ void (* reserved7)(void);
+ void (* reserved8)(void);
+ void (* reserved9)(void);
+};
+
+/**
+ * Interface for integrating a Wayland surface with libdecor.
+ */
+struct libdecor_frame_interface {
+ /**
+ * A new configuration was received. An application should respond to
+ * this by creating a suitable libdecor_state, and apply it using
+ * libdecor_frame_commit.
+ */
+ void (* configure)(struct libdecor_frame *frame,
+ struct libdecor_configuration *configuration,
+ void *user_data);
+
+ /**
+ * The window was requested to be closed by the compositor.
+ */
+ void (* close)(struct libdecor_frame *frame,
+ void *user_data);
+
+ /**
+ * The window decoration asked to have the main surface to be
+ * committed. This is required when the decoration is implemented using
+ * synchronous subsurfaces.
+ */
+ void (* commit)(struct libdecor_frame *frame,
+ void *user_data);
+
+ /**
+ * Any mapped popup that has a grab on the given seat should be
+ * dismissed.
+ */
+ void (* dismiss_popup)(struct libdecor_frame *frame,
+ const char *seat_name,
+ void *user_data);
+
+ /* Reserved */
+ void (* reserved0)(void);
+ void (* reserved1)(void);
+ void (* reserved2)(void);
+ void (* reserved3)(void);
+ void (* reserved4)(void);
+ void (* reserved5)(void);
+ void (* reserved6)(void);
+ void (* reserved7)(void);
+ void (* reserved8)(void);
+ void (* reserved9)(void);
+};
+
+/**
+ * Remove a reference to the libdecor instance. When the reference count
+ * reaches zero, it is freed.
+ */
+void
+libdecor_unref(struct libdecor *context);
+
+/**
+ * Create a new libdecor context for the given wl_display.
+ */
+struct libdecor *
+libdecor_new(struct wl_display *display,
+ struct libdecor_interface *iface);
+
+/**
+ * Get the file descriptor used by libdecor. This is similar to
+ * wl_display_get_fd(), thus should be polled, and when data is available,
+ * libdecor_dispatch() should be called.
+ */
+int
+libdecor_get_fd(struct libdecor *context);
+
+/**
+ * Dispatch events. This function should be called when data is available on
+ * the file descriptor returned by libdecor_get_fd(). If timeout is zero, this
+ * function will never block.
+ */
+int
+libdecor_dispatch(struct libdecor *context,
+ int timeout);
+
+/**
+ * Decorate the given content wl_surface.
+ *
+ * This will create an xdg_surface and an xdg_toplevel, and integrate it
+ * properly with the windowing system, including creating appropriate
+ * decorations when needed, as well as handle windowing integration events such
+ * as resizing, moving, maximizing, etc.
+ *
+ * The passed wl_surface should only contain actual application content,
+ * without any window decoration.
+ */
+struct libdecor_frame *
+libdecor_decorate(struct libdecor *context,
+ struct wl_surface *surface,
+ struct libdecor_frame_interface *iface,
+ void *user_data);
+
+/**
+ * Add a reference to the frame object.
+ */
+void
+libdecor_frame_ref(struct libdecor_frame *frame);
+
+/**
+ * Remove a reference to the frame object. When the reference count reaches
+ * zero, the frame object is destroyed.
+ */
+void
+libdecor_frame_unref(struct libdecor_frame *frame);
+
+/**
+ * Set the visibility of the frame.
+ *
+ * If an application wants to be borderless, it can set the frame visibility to
+ * false.
+ */
+void
+libdecor_frame_set_visibility(struct libdecor_frame *frame,
+ bool visible);
+
+/**
+ * Get the visibility of the frame.
+ */
+bool
+libdecor_frame_is_visible(struct libdecor_frame *frame);
+
+
+/**
+ * Set the parent of the window.
+ *
+ * This can be used to stack multiple toplevel windows above or under each
+ * other.
+ */
+void
+libdecor_frame_set_parent(struct libdecor_frame *frame,
+ struct libdecor_frame *parent);
+
+/**
+ * Set the title of the window.
+ */
+void
+libdecor_frame_set_title(struct libdecor_frame *frame,
+ const char *title);
+
+/**
+ * Get the title of the window.
+ */
+const char *
+libdecor_frame_get_title(struct libdecor_frame *frame);
+
+/**
+ * Set the application ID of the window.
+ */
+void
+libdecor_frame_set_app_id(struct libdecor_frame *frame,
+ const char *app_id);
+
+/**
+ * Set new capabilities of the window.
+ *
+ * This determines whether e.g. a window decoration should show a maximize
+ * button, etc.
+ *
+ * Setting a capability does not implicitly unset any other.
+ */
+void
+libdecor_frame_set_capabilities(struct libdecor_frame *frame,
+ enum libdecor_capabilities capabilities);
+
+/**
+ * Unset capabilities of the window.
+ *
+ * The opposite of libdecor_frame_set_capabilities.
+ */
+void
+libdecor_frame_unset_capabilities(struct libdecor_frame *frame,
+ enum libdecor_capabilities capabilities);
+
+/**
+ * Check whether the window has any of the given capabilities.
+ */
+bool
+libdecor_frame_has_capability(struct libdecor_frame *frame,
+ enum libdecor_capabilities capability);
+
+/**
+ * Show the window menu.
+ */
+void
+libdecor_frame_show_window_menu(struct libdecor_frame *frame,
+ struct wl_seat *wl_seat,
+ uint32_t serial,
+ int x,
+ int y);
+
+/**
+ * Issue a popup grab on the window. Call this when a xdg_popup is mapped, so
+ * that it can be properly dismissed by the decorations.
+ */
+void
+libdecor_frame_popup_grab(struct libdecor_frame *frame,
+ const char *seat_name);
+
+/**
+ * Release the popup grab. Call this when you unmap a popup.
+ */
+void
+libdecor_frame_popup_ungrab(struct libdecor_frame *frame,
+ const char *seat_name);
+
+/**
+ * Translate content surface local coordinates to toplevel window local
+ * coordinates.
+ *
+ * This can be used to translate surface coordinates to coordinates useful for
+ * e.g. showing the window menu, or positioning a popup.
+ */
+void
+libdecor_frame_translate_coordinate(struct libdecor_frame *frame,
+ int surface_x,
+ int surface_y,
+ int *frame_x,
+ int *frame_y);
+
+/**
+ * Set the max content size.
+ *
+ * This translates roughly to xdg_toplevel_set_max_size().
+ */
+void
+libdecor_frame_set_max_content_size(struct libdecor_frame *frame,
+ int content_width,
+ int content_height);
+
+/**
+ * Set the min content size.
+ *
+ * This translates roughly to xdg_toplevel_set_min_size().
+ */
+void
+libdecor_frame_set_min_content_size(struct libdecor_frame *frame,
+ int content_width,
+ int content_height);
+
+/**
+ * Initiate an interactive resize.
+ *
+ * This roughly translates to xdg_toplevel_resize().
+ */
+void
+libdecor_frame_resize(struct libdecor_frame *frame,
+ struct wl_seat *wl_seat,
+ uint32_t serial,
+ enum libdecor_resize_edge edge);
+
+/**
+ * Initiate an interactive move.
+ *
+ * This roughly translates to xdg_toplevel_move().
+ */
+void
+libdecor_frame_move(struct libdecor_frame *frame,
+ struct wl_seat *wl_seat,
+ uint32_t serial);
+
+/**
+ * Commit a new window state. This can be called on application driven resizes
+ * when the window is floating, or in response to received configurations, i.e.
+ * from e.g. interactive resizes or state changes.
+ */
+void
+libdecor_frame_commit(struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ struct libdecor_configuration *configuration);
+
+/**
+ * Minimize the window.
+ *
+ * Roughly translates to xdg_toplevel_set_minimized().
+ */
+void
+libdecor_frame_set_minimized(struct libdecor_frame *frame);
+
+/**
+ * Maximize the window.
+ *
+ * Roughly translates to xdg_toplevel_set_maximized().
+ */
+void
+libdecor_frame_set_maximized(struct libdecor_frame *frame);
+
+/**
+ * Unmaximize the window.
+ *
+ * Roughly translates to xdg_toplevel_unset_maximized().
+ */
+void
+libdecor_frame_unset_maximized(struct libdecor_frame *frame);
+
+/**
+ * Fullscreen the window.
+ *
+ * Roughly translates to xdg_toplevel_set_fullscreen().
+ */
+void
+libdecor_frame_set_fullscreen(struct libdecor_frame *frame,
+ struct wl_output *output);
+
+/**
+ * Unfullscreen the window.
+ *
+ * Roughly translates to xdg_toplevel_unset_unfullscreen().
+ */
+void
+libdecor_frame_unset_fullscreen(struct libdecor_frame *frame);
+
+/**
+ * Return true if the window is floating.
+ *
+ * A window is floating when it's not maximized, tiled, fullscreen, or in any
+ * similar way with a fixed size and state.
+ * Note that this function uses the "applied" configuration. If this function
+ * is used in the 'configure' callback, the provided configuration has to be
+ * applied via 'libdecor_frame_commit' first, before it will reflect the current
+ * window state from the provided configuration.
+ */
+bool
+libdecor_frame_is_floating(struct libdecor_frame *frame);
+
+/**
+ * Close the window.
+ *
+ * Roughly translates to xdg_toplevel_close().
+ */
+void
+libdecor_frame_close(struct libdecor_frame *frame);
+
+/**
+ * Map the window.
+ *
+ * This will eventually result in the initial configure event.
+ */
+void
+libdecor_frame_map(struct libdecor_frame *frame);
+
+/**
+ * Get the associated xdg_surface for content wl_surface.
+ */
+struct xdg_surface *
+libdecor_frame_get_xdg_surface(struct libdecor_frame *frame);
+
+/**
+ * Get the associated xdg_toplevel for the content wl_surface.
+ */
+struct xdg_toplevel *
+libdecor_frame_get_xdg_toplevel(struct libdecor_frame *frame);
+
+/**
+ * Create a new content surface state.
+ */
+struct libdecor_state *
+libdecor_state_new(int width,
+ int height);
+
+/**
+ * Free a content surface state.
+ */
+void
+libdecor_state_free(struct libdecor_state *state);
+
+/**
+ * Get the expected size of the content for this configuration.
+ *
+ * If the configuration doesn't contain a size, false is returned.
+ */
+bool
+libdecor_configuration_get_content_size(struct libdecor_configuration *configuration,
+ struct libdecor_frame *frame,
+ int *width,
+ int *height);
+
+/**
+ * Get the window state for this configuration.
+ *
+ * If the configuration doesn't contain any associated window state, false is
+ * returned, and the application should assume the window state remains
+ * unchanged.
+ */
+bool
+libdecor_configuration_get_window_state(struct libdecor_configuration *configuration,
+ enum libdecor_window_state *window_state);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBDECOR_H */
diff --git a/libdecor/src/os-compatibility.c b/libdecor/src/os-compatibility.c
new file mode 100644
index 000000000..50beb79f8
--- /dev/null
+++ b/libdecor/src/os-compatibility.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright © 2012 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_MEMFD_CREATE
+#include <sys/mman.h>
+#endif
+
+#include "os-compatibility.h"
+
+#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
+ /*
+ * Filesystems that do support fallocate will return EINVAL or
+ * EOPNOTSUPP. In this case we need to fall back to ftruncate
+ */
+ errno = posix_fallocate(fd, 0, size);
+ 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
+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;
+}
diff --git a/libdecor/src/os-compatibility.h b/libdecor/src/os-compatibility.h
new file mode 100644
index 000000000..d0e69acd9
--- /dev/null
+++ b/libdecor/src/os-compatibility.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2012 Collabora, Ltd.
+ *
+ * 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.
+ */
+
+#ifndef OS_COMPATIBILITY_H
+#define OS_COMPATIBILITY_H
+
+#include <sys/types.h>
+
+int
+os_create_anonymous_file(off_t size);
+
+#endif /* OS_COMPATIBILITY_H */
diff --git a/libdecor/src/plugins/cairo/libdecor-cairo-blur.c b/libdecor/src/plugins/cairo/libdecor-cairo-blur.c
new file mode 100644
index 000000000..2cddc7a23
--- /dev/null
+++ b/libdecor/src/plugins/cairo/libdecor-cairo-blur.c
@@ -0,0 +1,255 @@
+/*
+ * Copyright © 2008 Kristian Høgsberg
+ * Copyright © 2012 Intel Corporation
+ *
+ * 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.
+ */
+
+/*
+ * functions 'blur_surface' and 'render_shadow' from weston project:
+ * https://gitlab.freedesktop.org/wayland/weston/raw/master/shared/cairo-util.c
+ */
+
+#include "libdecor-cairo-blur.h"
+#include <stdint.h>
+#include <stdlib.h>
+#include <math.h>
+
+/**
+ * Compile-time computation of number of items in a hardcoded array.
+ *
+ * @param a the array being measured.
+ * @return the number of items hardcoded into the array.
+ */
+#ifndef ARRAY_LENGTH
+#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
+#endif
+
+int
+blur_surface(cairo_surface_t *surface, int margin)
+{
+ int32_t width, height, stride, x, y, z, w;
+ uint8_t *src, *dst;
+ uint32_t *s, *d, a, p;
+ int i, j, k, size, half;
+ uint32_t kernel[71];
+ double f;
+
+ size = ARRAY_LENGTH(kernel);
+ width = cairo_image_surface_get_width(surface);
+ height = cairo_image_surface_get_height(surface);
+ stride = cairo_image_surface_get_stride(surface);
+ src = cairo_image_surface_get_data(surface);
+
+ dst = malloc(height * stride);
+ if (dst == NULL)
+ return -1;
+
+ half = size / 2;
+ a = 0;
+ for (i = 0; i < size; i++) {
+ f = (i - half);
+ kernel[i] = exp(- f * f / ARRAY_LENGTH(kernel)) * 10000;
+ a += kernel[i];
+ }
+
+ for (i = 0; i < height; i++) {
+ s = (uint32_t *) (src + i * stride);
+ d = (uint32_t *) (dst + i * stride);
+ for (j = 0; j < width; j++) {
+ if (margin < j && j < width - margin) {
+ d[j] = s[j];
+ continue;
+ }
+
+ x = 0;
+ y = 0;
+ z = 0;
+ w = 0;
+ for (k = 0; k < size; k++) {
+ if (j - half + k < 0 || j - half + k >= width)
+ continue;
+ p = s[j - half + k];
+
+ x += (p >> 24) * kernel[k];
+ y += ((p >> 16) & 0xff) * kernel[k];
+ z += ((p >> 8) & 0xff) * kernel[k];
+ w += (p & 0xff) * kernel[k];
+ }
+ d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a;
+ }
+ }
+
+ for (i = 0; i < height; i++) {
+ s = (uint32_t *) (dst + i * stride);
+ d = (uint32_t *) (src + i * stride);
+ for (j = 0; j < width; j++) {
+ if (margin <= i && i < height - margin) {
+ d[j] = s[j];
+ continue;
+ }
+
+ x = 0;
+ y = 0;
+ z = 0;
+ w = 0;
+ for (k = 0; k < size; k++) {
+ if (i - half + k < 0 || i - half + k >= height)
+ continue;
+ s = (uint32_t *) (dst + (i - half + k) * stride);
+ p = s[j];
+
+ x += (p >> 24) * kernel[k];
+ y += ((p >> 16) & 0xff) * kernel[k];
+ z += ((p >> 8) & 0xff) * kernel[k];
+ w += (p & 0xff) * kernel[k];
+ }
+ d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a;
+ }
+ }
+
+ free(dst);
+ cairo_surface_mark_dirty(surface);
+
+ return 0;
+}
+
+void
+render_shadow(cairo_t *cr, cairo_surface_t *surface,
+ int x, int y, int width, int height, int margin, int top_margin)
+{
+ cairo_pattern_t *pattern;
+ cairo_matrix_t matrix;
+ int i, fx, fy, shadow_height, shadow_width;
+
+ cairo_set_source_rgba(cr, 0, 0, 0, 0.45);
+ cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
+ pattern = cairo_pattern_create_for_surface (surface);
+ cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
+
+ for (i = 0; i < 4; i++) {
+ /* when fy is set, then we are working with lower corners,
+ * when fx is set, then we are working with right corners
+ *
+ * 00 ------- 01
+ * | |
+ * | |
+ * 10 ------- 11
+ */
+ fx = i & 1;
+ fy = i >> 1;
+
+ cairo_matrix_init_translate(&matrix,
+ -x + fx * (128 - width),
+ -y + fy * (128 - height));
+ cairo_pattern_set_matrix(pattern, &matrix);
+
+ shadow_width = margin;
+ shadow_height = fy ? margin : top_margin;
+
+ /* if the shadows together are greater than the surface, we need
+ * to fix it - set the shadow size to the half of
+ * the size of surface. Also handle the case when the size is
+ * not divisible by 2. In that case we need one part of the
+ * shadow to be one pixel greater. !fy or !fx, respectively,
+ * will do the work.
+ */
+ if (height < 2 * shadow_height)
+ shadow_height = (height + !fy) / 2;
+
+ if (width < 2 * shadow_width)
+ shadow_width = (width + !fx) / 2;
+
+ cairo_reset_clip(cr);
+ cairo_rectangle(cr,
+ x + fx * (width - shadow_width),
+ y + fy * (height - shadow_height),
+ shadow_width, shadow_height);
+ cairo_clip (cr);
+ cairo_mask(cr, pattern);
+ }
+
+
+ shadow_width = width - 2 * margin;
+ shadow_height = top_margin;
+ if (height < 2 * shadow_height)
+ shadow_height = height / 2;
+
+ if (shadow_width > 0 && shadow_height) {
+ /* Top stretch */
+ cairo_matrix_init_translate(&matrix, 60, 0);
+ cairo_matrix_scale(&matrix, 8.0 / width, 1);
+ cairo_matrix_translate(&matrix, -x - width / 2, -y);
+ cairo_pattern_set_matrix(pattern, &matrix);
+ cairo_rectangle(cr, x + margin, y, shadow_width, shadow_height);
+
+ cairo_reset_clip(cr);
+ cairo_rectangle(cr,
+ x + margin, y,
+ shadow_width, shadow_height);
+ cairo_clip (cr);
+ cairo_mask(cr, pattern);
+
+ /* Bottom stretch */
+ cairo_matrix_translate(&matrix, 0, -height + 128);
+ cairo_pattern_set_matrix(pattern, &matrix);
+
+ cairo_reset_clip(cr);
+ cairo_rectangle(cr, x + margin, y + height - margin,
+ shadow_width, margin);
+ cairo_clip (cr);
+ cairo_mask(cr, pattern);
+ }
+
+ shadow_width = margin;
+ if (width < 2 * shadow_width)
+ shadow_width = width / 2;
+
+ shadow_height = height - margin - top_margin;
+
+ /* if height is smaller than sum of margins,
+ * then the shadow is already done by the corners */
+ if (shadow_height > 0 && shadow_width) {
+ /* Left stretch */
+ cairo_matrix_init_translate(&matrix, 0, 60);
+ cairo_matrix_scale(&matrix, 1, 8.0 / height);
+ cairo_matrix_translate(&matrix, -x, -y - height / 2);
+ cairo_pattern_set_matrix(pattern, &matrix);
+ cairo_reset_clip(cr);
+ cairo_rectangle(cr, x, y + top_margin,
+ shadow_width, shadow_height);
+ cairo_clip (cr);
+ cairo_mask(cr, pattern);
+
+ /* Right stretch */
+ cairo_matrix_translate(&matrix, -width + 128, 0);
+ cairo_pattern_set_matrix(pattern, &matrix);
+ cairo_rectangle(cr, x + width - shadow_width, y + top_margin,
+ shadow_width, shadow_height);
+ cairo_reset_clip(cr);
+ cairo_clip (cr);
+ cairo_mask(cr, pattern);
+ }
+
+ cairo_pattern_destroy(pattern);
+ cairo_reset_clip(cr);
+}
diff --git a/libdecor/src/plugins/cairo/libdecor-cairo-blur.h b/libdecor/src/plugins/cairo/libdecor-cairo-blur.h
new file mode 100644
index 000000000..d48e9dd3d
--- /dev/null
+++ b/libdecor/src/plugins/cairo/libdecor-cairo-blur.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <cairo/cairo.h>
+
+int
+blur_surface(cairo_surface_t *surface, int margin);
+
+void
+render_shadow(cairo_t *cr, cairo_surface_t *surface,
+ int x, int y, int width, int height, int margin, int top_margin);
diff --git a/libdecor/src/plugins/cairo/libdecor-cairo.c b/libdecor/src/plugins/cairo/libdecor-cairo.c
new file mode 100644
index 000000000..6e4222863
--- /dev/null
+++ b/libdecor/src/plugins/cairo/libdecor-cairo.c
@@ -0,0 +1,2824 @@
+/*
+ * Copyright © 2018 Jonas Ådahl
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <linux/input.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+#include <wayland-cursor.h>
+
+#include "libdecor-plugin.h"
+#include "utils.h"
+#include "cursor-settings.h"
+#include "os-compatibility.h"
+
+#include <cairo/cairo.h>
+#include <pango/pangocairo.h>
+
+#ifndef APPLY_FLTK_CHANGES
+#define APPLY_FLTK_CHANGES 1
+#endif
+
+/* Note for FLTK: This header file changes location, while its content stays unchanged,
+ between the master and gtk_cairo_single branches */
+#include "libdecor-cairo-blur.h"
+
+static const size_t SHADOW_MARGIN = 24; /* graspable part of the border */
+static const size_t TITLE_HEIGHT = 24;
+static const size_t BUTTON_WIDTH = 32;
+static const size_t SYM_DIM = 14;
+
+static const uint32_t COL_TITLE = 0xFF080706;
+static const uint32_t COL_TITLE_INACT = 0xFF303030;
+static const uint32_t COL_BUTTON_MIN = 0xFFFFBB00;
+static const uint32_t COL_BUTTON_MAX = 0xFF238823;
+static const uint32_t COL_BUTTON_CLOSE = 0xFFFB6542;
+static const uint32_t COL_BUTTON_INACT = 0xFF404040;
+static const uint32_t COL_SYM = 0xFFF4F4EF;
+static const uint32_t COL_SYM_ACT = 0xFF20322A;
+static const uint32_t COL_SYM_INACT = 0xFF909090;
+
+static const uint32_t DOUBLE_CLICK_TIME_MS = 400;
+
+static const char *cursor_names[] = {
+ "top_side",
+ "bottom_side",
+ "left_side",
+ "top_left_corner",
+ "bottom_left_corner",
+ "right_side",
+ "top_right_corner",
+ "bottom_right_corner"
+};
+
+
+/* color conversion function from 32bit integer to double components */
+
+double
+red(const uint32_t *const col) {
+ return ((const uint8_t*)(col))[2] / (double)(255);
+}
+
+double
+green(const uint32_t *const col) {
+ return ((const uint8_t*)(col))[1] / (double)(255);
+}
+
+double
+blue(const uint32_t *const col) {
+ return ((const uint8_t*)(col))[0] / (double)(255);
+}
+
+double
+alpha(const uint32_t *const col) {
+ return ((const uint8_t*)(col))[3] / (double)(255);
+}
+
+void
+cairo_set_rgba32(cairo_t *cr, const uint32_t *const c) {
+ cairo_set_source_rgba(cr, red(c), green(c), blue(c), alpha(c));
+}
+
+static bool
+streql(const char *str1, const char *str2)
+{
+ return (str1 && str2) && (strcmp(str1, str2) == 0);
+}
+
+enum decoration_type {
+ DECORATION_TYPE_NONE,
+ DECORATION_TYPE_ALL,
+ DECORATION_TYPE_MAXIMIZED,
+ DECORATION_TYPE_TILED
+};
+
+enum component {
+ NONE = 0,
+ SHADOW,
+ TITLE,
+ BUTTON_MIN,
+ BUTTON_MAX,
+ BUTTON_CLOSE,
+};
+
+enum composite_mode {
+ COMPOSITE_SERVER,
+ COMPOSITE_CLIENT,
+};
+
+struct seat {
+ struct libdecor_plugin_cairo *plugin_cairo;
+
+ char *name;
+
+ struct wl_seat *wl_seat;
+ struct wl_pointer *wl_pointer;
+
+ struct wl_surface *cursor_surface;
+ struct wl_cursor *current_cursor;
+ int cursor_scale;
+ struct wl_list cursor_outputs;
+
+ struct wl_cursor_theme *cursor_theme;
+ /* cursors for resize edges and corners */
+ struct wl_cursor *cursors[ARRAY_LENGTH(cursor_names)];
+ struct wl_cursor *cursor_left_ptr;
+
+ struct wl_surface *pointer_focus;
+
+ int pointer_x, pointer_y;
+
+ uint32_t pointer_button_time_stamp;
+
+ uint32_t serial;
+
+ bool grabbed;
+
+ struct wl_list link;
+};
+
+struct output {
+ struct libdecor_plugin_cairo *plugin_cairo;
+
+ struct wl_output *wl_output;
+ uint32_t id;
+ int scale;
+
+ struct wl_list link;
+};
+
+struct buffer {
+ struct wl_buffer *wl_buffer;
+ bool in_use;
+ bool is_detached;
+
+ void *data;
+ size_t data_size;
+ int width;
+ int height;
+ int scale;
+ int buffer_width;
+ int buffer_height;
+};
+
+struct border_component {
+ enum component type;
+
+ bool is_hidden;
+ bool opaque;
+
+ enum composite_mode composite_mode;
+ struct {
+ struct wl_surface *wl_surface;
+ struct wl_subsurface *wl_subsurface;
+ struct buffer *buffer;
+ struct wl_list output_list;
+ int scale;
+ } server;
+ struct {
+ cairo_surface_t *image;
+ struct border_component *parent_component;
+ } client;
+
+ struct wl_list child_components; /* border_component::link */
+ struct wl_list link; /* border_component::child_components */
+};
+
+struct surface_output {
+ struct output *output;
+ struct wl_list link;
+};
+
+struct cursor_output {
+ struct output *output;
+ struct wl_list link;
+};
+
+struct libdecor_frame_cairo {
+ struct libdecor_frame frame;
+
+ struct libdecor_plugin_cairo *plugin_cairo;
+
+ int content_width;
+ int content_height;
+
+ enum decoration_type decoration_type;
+
+ enum libdecor_window_state window_state;
+
+ char *title;
+
+ enum libdecor_capabilities capabilities;
+
+ struct border_component *focus;
+ struct border_component *active;
+ struct border_component *grab;
+
+ bool shadow_showing;
+ struct border_component shadow;
+
+ struct {
+ bool is_showing;
+ struct border_component title;
+ struct border_component min;
+ struct border_component max;
+ struct border_component close;
+ } title_bar;
+
+ /* store pre-processed shadow tile */
+ cairo_surface_t *shadow_blur;
+
+ struct wl_list link;
+};
+
+struct libdecor_plugin_cairo {
+ struct libdecor_plugin plugin;
+
+ struct wl_callback *globals_callback;
+ struct wl_callback *globals_callback_shm;
+
+ struct libdecor *context;
+
+ struct wl_registry *wl_registry;
+ struct wl_subcompositor *wl_subcompositor;
+ struct wl_compositor *wl_compositor;
+
+ struct wl_shm *wl_shm;
+ struct wl_callback *shm_callback;
+ bool has_argb;
+
+ struct wl_list visible_frame_list;
+ struct wl_list seat_list;
+ struct wl_list output_list;
+
+ char *cursor_theme_name;
+ int cursor_size;
+
+ PangoFontDescription *font;
+};
+
+static const char *libdecor_cairo_proxy_tag = "libdecor-cairo";
+
+static void
+sync_active_component(struct libdecor_frame_cairo *frame_cairo,
+ struct seat *seat);
+
+static void
+synthesize_pointer_enter(struct seat *seat);
+
+static void
+synthesize_pointer_leave(struct seat *seat);
+
+static bool
+own_proxy(struct wl_proxy *proxy)
+{
+ return (wl_proxy_get_tag(proxy) == &libdecor_cairo_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_cairo *frame_cairo) {
+ return libdecor_frame_has_capability(&frame_cairo->frame,
+ LIBDECOR_ACTION_MOVE);
+}
+
+static bool
+resizable(struct libdecor_frame_cairo *frame_cairo) {
+ return libdecor_frame_has_capability(&frame_cairo->frame,
+ LIBDECOR_ACTION_RESIZE);
+}
+
+static bool
+minimizable(struct libdecor_frame_cairo *frame_cairo) {
+ return libdecor_frame_has_capability(&frame_cairo->frame,
+ LIBDECOR_ACTION_MINIMIZE);
+}
+
+static bool
+closeable(struct libdecor_frame_cairo *frame_cairo) {
+ return libdecor_frame_has_capability(&frame_cairo->frame,
+ LIBDECOR_ACTION_CLOSE);
+}
+
+static void
+buffer_free(struct buffer *buffer);
+
+static void
+draw_border_component(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *border_component);
+
+static void
+send_cursor(struct seat *seat);
+
+static bool
+update_local_cursor(struct seat *seat);
+
+static void
+libdecor_plugin_cairo_destroy(struct libdecor_plugin *plugin)
+{
+ struct libdecor_plugin_cairo *plugin_cairo =
+ (struct libdecor_plugin_cairo *) plugin;
+ struct seat *seat, *seat_tmp;
+ struct output *output, *output_tmp;
+ struct libdecor_frame_cairo *frame, *frame_tmp;
+
+ if (plugin_cairo->globals_callback)
+ wl_callback_destroy(plugin_cairo->globals_callback);
+ if (plugin_cairo->globals_callback_shm)
+ wl_callback_destroy(plugin_cairo->globals_callback_shm);
+ if (plugin_cairo->shm_callback)
+ wl_callback_destroy(plugin_cairo->shm_callback);
+ wl_registry_destroy(plugin_cairo->wl_registry);
+
+ wl_list_for_each_safe(seat, seat_tmp, &plugin_cairo->seat_list, link) {
+ struct cursor_output *cursor_output, *tmp;
+
+ if (seat->wl_pointer)
+ wl_pointer_destroy(seat->wl_pointer);
+ 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_cairo->output_list, link) {
+ wl_output_destroy(output->wl_output);
+ free(output);
+ }
+
+ wl_list_for_each_safe(frame, frame_tmp,
+ &plugin_cairo->visible_frame_list, link) {
+ wl_list_remove(&frame->link);
+ }
+
+ free(plugin_cairo->cursor_theme_name);
+
+ wl_shm_destroy(plugin_cairo->wl_shm);
+
+ pango_font_description_free(plugin_cairo->font);
+
+ wl_compositor_destroy(plugin_cairo->wl_compositor);
+ wl_subcompositor_destroy(plugin_cairo->wl_subcompositor);
+
+ libdecor_plugin_release(&plugin_cairo->plugin);
+ free(plugin_cairo);
+}
+
+static void
+init_server_component(struct border_component *border_component,
+ enum component type)
+{
+ border_component->composite_mode = COMPOSITE_SERVER;
+ wl_list_init(&border_component->child_components);
+ border_component->type = type;
+}
+
+static void
+init_client_component(struct border_component *border_component,
+ struct border_component *parent,
+ enum component type)
+{
+ border_component->composite_mode = COMPOSITE_CLIENT;
+ wl_list_init(&border_component->child_components);
+ wl_list_insert(parent->child_components.prev, &border_component->link);
+ border_component->client.parent_component = parent;
+ border_component->type = type;
+}
+
+static void
+init_components(struct libdecor_frame_cairo *frame_cairo)
+{
+ init_server_component(&frame_cairo->title_bar.title,
+ TITLE);
+ init_client_component(&frame_cairo->title_bar.min,
+ &frame_cairo->title_bar.title,
+ BUTTON_MIN);
+ init_client_component(&frame_cairo->title_bar.max,
+ &frame_cairo->title_bar.title,
+ BUTTON_MAX);
+ init_client_component(&frame_cairo->title_bar.close,
+ &frame_cairo->title_bar.title,
+ BUTTON_CLOSE);
+ init_server_component(&frame_cairo->shadow,
+ SHADOW);
+}
+
+static struct libdecor_frame_cairo *
+libdecor_frame_cairo_new(struct libdecor_plugin_cairo *plugin_cairo)
+{
+ struct libdecor_frame_cairo *frame_cairo = zalloc(sizeof *frame_cairo);
+ cairo_t *cr;
+
+ static const int size = 128;
+ static const int boundary = 32;
+
+ frame_cairo->plugin_cairo = plugin_cairo;
+ frame_cairo->shadow_blur = cairo_image_surface_create(
+ CAIRO_FORMAT_ARGB32, size, size);
+ wl_list_insert(&plugin_cairo->visible_frame_list, &frame_cairo->link);
+
+ init_components(frame_cairo);
+
+ cr = cairo_create(frame_cairo->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_cairo->shadow_blur, 64);
+
+ return frame_cairo;
+}
+
+static int
+libdecor_plugin_cairo_get_fd(struct libdecor_plugin *plugin)
+{
+ struct libdecor_plugin_cairo *plugin_cairo =
+ (struct libdecor_plugin_cairo *) plugin;
+ struct wl_display *wl_display =
+ libdecor_get_wl_display(plugin_cairo->context);
+
+ return wl_display_get_fd(wl_display);
+}
+
+static int
+libdecor_plugin_cairo_dispatch(struct libdecor_plugin *plugin,
+ int timeout)
+{
+ struct libdecor_plugin_cairo *plugin_cairo =
+ (struct libdecor_plugin_cairo *) plugin;
+ struct wl_display *wl_display =
+ libdecor_get_wl_display(plugin_cairo->context);
+ struct pollfd fds[1];
+ int ret;
+ int dispatch_count = 0;
+
+ while (wl_display_prepare_read(wl_display) != 0)
+ dispatch_count += wl_display_dispatch_pending(wl_display);
+
+ if (wl_display_flush(wl_display) < 0 &&
+ errno != EAGAIN) {
+ wl_display_cancel_read(wl_display);
+ return -errno;
+ }
+
+ fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN };
+
+ ret = poll(fds, ARRAY_SIZE (fds), timeout);
+ if (ret > 0) {
+ if (fds[0].revents & POLLIN) {
+ wl_display_read_events(wl_display);
+ dispatch_count += wl_display_dispatch_pending(wl_display);
+ return dispatch_count;
+ } else {
+ wl_display_cancel_read(wl_display);
+ return dispatch_count;
+ }
+ } else if (ret == 0) {
+ wl_display_cancel_read(wl_display);
+ return dispatch_count;
+ } else {
+ wl_display_cancel_read(wl_display);
+ return -errno;
+ }
+}
+
+static struct libdecor_frame *
+libdecor_plugin_cairo_frame_new(struct libdecor_plugin *plugin)
+{
+ struct libdecor_plugin_cairo *plugin_cairo =
+ (struct libdecor_plugin_cairo *) plugin;
+ struct libdecor_frame_cairo *frame_cairo;
+
+ frame_cairo = libdecor_frame_cairo_new(plugin_cairo);
+
+ return &frame_cairo->frame;
+}
+
+static void
+toggle_maximized(struct libdecor_frame *const frame)
+{
+ if (!resizable((struct libdecor_frame_cairo *)frame))
+ return;
+
+ if (!(libdecor_frame_get_window_state(frame) &
+ LIBDECOR_WINDOW_STATE_MAXIMIZED))
+ libdecor_frame_set_maximized(frame);
+ else
+ libdecor_frame_unset_maximized(frame);
+}
+
+static void
+buffer_release(void *user_data,
+ struct wl_buffer *wl_buffer)
+{
+ struct buffer *buffer = user_data;
+
+ if (buffer->is_detached)
+ buffer_free(buffer);
+ else
+ buffer->in_use = false;
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+ buffer_release
+};
+
+static struct buffer *
+create_shm_buffer(struct libdecor_plugin_cairo *plugin_cairo,
+ 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 = 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_cairo->wl_shm, fd, size);
+ buffer = zalloc(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)
+{
+ struct surface_output *surface_output, *surface_output_tmp;
+
+ if (border_component->server.wl_surface) {
+ wl_subsurface_destroy(border_component->server.wl_subsurface);
+ border_component->server.wl_subsurface = NULL;
+ wl_surface_destroy(border_component->server.wl_surface);
+ border_component->server.wl_surface = NULL;
+ }
+ if (border_component->server.buffer) {
+ buffer_free(border_component->server.buffer);
+ border_component->server.buffer = NULL;
+ }
+ if (border_component->client.image) {
+ cairo_surface_destroy(border_component->client.image);
+ border_component->client.image = NULL;
+ }
+ if (border_component->server.output_list.next != NULL) {
+ wl_list_for_each_safe(surface_output, surface_output_tmp,
+ &border_component->server.output_list, link) {
+ wl_list_remove(&surface_output->link);
+ free(surface_output);
+ }
+ }
+}
+
+static void
+libdecor_plugin_cairo_frame_free(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame)
+{
+ struct libdecor_plugin_cairo *plugin_cairo =
+ (struct libdecor_plugin_cairo *) plugin;
+ struct libdecor_frame_cairo *frame_cairo =
+ (struct libdecor_frame_cairo *) frame;
+ struct seat *seat;
+
+ wl_list_for_each(seat, &plugin_cairo->seat_list, link) {
+ if (seat->pointer_focus != NULL &&
+ wl_surface_get_user_data(seat->pointer_focus) == frame_cairo)
+ seat->pointer_focus = NULL;
+ }
+
+ free_border_component(&frame_cairo->title_bar.title);
+ free_border_component(&frame_cairo->title_bar.min);
+ free_border_component(&frame_cairo->title_bar.max);
+ free_border_component(&frame_cairo->title_bar.close);
+ frame_cairo->title_bar.is_showing = false;
+ free_border_component(&frame_cairo->shadow);
+ frame_cairo->shadow_showing = false;
+ if (frame_cairo->shadow_blur != NULL) {
+ cairo_surface_destroy(frame_cairo->shadow_blur);
+ frame_cairo->shadow_blur = NULL;
+ }
+
+ free(frame_cairo->title);
+ frame_cairo->title = NULL;
+
+ frame_cairo->decoration_type = DECORATION_TYPE_NONE;
+
+ if (frame_cairo->link.next != NULL)
+ wl_list_remove(&frame_cairo->link);
+}
+
+static bool
+is_border_surfaces_showing(struct libdecor_frame_cairo *frame_cairo)
+{
+ return frame_cairo->shadow_showing;
+}
+
+static bool
+is_title_bar_surfaces_showing(struct libdecor_frame_cairo *frame_cairo)
+{
+ return frame_cairo->title_bar.is_showing;
+}
+
+static struct border_component *
+get_server_component(struct border_component *border_component)
+{
+ switch (border_component->composite_mode) {
+ case COMPOSITE_SERVER:
+ return border_component;
+ case COMPOSITE_CLIENT:
+ return get_server_component(border_component->client.parent_component);
+ }
+ return NULL;
+}
+
+static void
+redraw_border_component(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *border_component)
+{
+ struct border_component *server_component;
+
+ server_component = get_server_component(border_component);
+ draw_border_component(frame_cairo, server_component);
+}
+
+static void
+hide_border_component(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *border_component)
+{
+ border_component->is_hidden = true;
+
+ switch (border_component->composite_mode) {
+ case COMPOSITE_SERVER:
+ if (!border_component->server.wl_surface)
+ return;
+
+ wl_surface_attach(border_component->server.wl_surface,
+ NULL, 0, 0);
+ wl_surface_commit(border_component->server.wl_surface);
+ break;
+ case COMPOSITE_CLIENT:
+ redraw_border_component(frame_cairo, border_component);
+ break;
+ }
+}
+
+static void
+hide_border_surfaces(struct libdecor_frame_cairo *frame_cairo)
+{
+ hide_border_component(frame_cairo, &frame_cairo->shadow);
+ frame_cairo->shadow_showing = false;
+}
+
+static void
+hide_title_bar_surfaces(struct libdecor_frame_cairo *frame_cairo)
+{
+ hide_border_component(frame_cairo, &frame_cairo->title_bar.title);
+ hide_border_component(frame_cairo, &frame_cairo->title_bar.min);
+ hide_border_component(frame_cairo, &frame_cairo->title_bar.max);
+ hide_border_component(frame_cairo, &frame_cairo->title_bar.close);
+ frame_cairo->title_bar.is_showing = false;
+}
+
+static struct border_component *
+get_component_for_surface(struct libdecor_frame_cairo *frame_cairo,
+ struct wl_surface *surface)
+{
+ if (frame_cairo->shadow.server.wl_surface == surface)
+ return &frame_cairo->shadow;
+ if (frame_cairo->title_bar.title.server.wl_surface == surface)
+ return &frame_cairo->title_bar.title;
+ return NULL;
+}
+
+static void
+calculate_component_size(struct libdecor_frame_cairo *frame_cairo,
+ enum component component,
+ int *component_x,
+ int *component_y,
+ int *component_width,
+ int *component_height);
+
+static void
+update_component_focus(struct libdecor_frame_cairo *frame_cairo,
+ 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_cairo, 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_cairo, 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_cairo->grab)
+ frame_cairo->active = frame_cairo->grab;
+ else
+ frame_cairo->active = focus_component;
+ frame_cairo->focus = focus_component;
+
+}
+
+static void
+ensure_component(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *cmpnt);
+
+static bool
+redraw_scale(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *cmpnt)
+{
+ struct surface_output *surface_output;
+ int scale = 1;
+
+ if (cmpnt->is_hidden)
+ return false;
+
+ ensure_component(frame_cairo, cmpnt);
+
+ wl_list_for_each(surface_output, &cmpnt->server.output_list, link) {
+ scale = MAX(scale, surface_output->output->scale);
+ }
+ if (scale != cmpnt->server.scale) {
+ cmpnt->server.scale = scale;
+ if ((cmpnt->type != SHADOW) || is_border_surfaces_showing(frame_cairo)) {
+ draw_border_component(frame_cairo, cmpnt);
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool
+add_surface_output(struct libdecor_plugin_cairo *plugin_cairo,
+ 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 = zalloc(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_cairo *frame_cairo = data;
+ struct border_component *cmpnt;
+
+ if (!(own_surface(wl_surface) && own_output(wl_output)))
+ return;
+
+ cmpnt = get_component_for_surface(frame_cairo, wl_surface);
+ if (cmpnt == NULL)
+ return;
+
+ if (!add_surface_output(frame_cairo->plugin_cairo, wl_output,
+ &cmpnt->server.output_list))
+ return;
+
+ if (redraw_scale(frame_cairo, cmpnt))
+ libdecor_frame_toplevel_commit(&frame_cairo->frame);
+}
+
+static bool
+remove_surface_output(struct wl_list *list, 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_cairo *frame_cairo = data;
+ struct border_component *cmpnt;
+
+ if (!(own_surface(wl_surface) && own_output(wl_output)))
+ return;
+
+ cmpnt = get_component_for_surface(frame_cairo, wl_surface);
+ if (cmpnt == NULL)
+ return;
+
+ if (!remove_surface_output(&cmpnt->server.output_list, wl_output))
+ return;
+
+ if (redraw_scale(frame_cairo, cmpnt))
+ libdecor_frame_toplevel_commit(&frame_cairo->frame);
+}
+
+static struct wl_surface_listener surface_listener = {
+ surface_enter,
+ surface_leave,
+};
+
+static void
+create_surface_subsurface_pair(struct libdecor_frame_cairo *frame_cairo,
+ struct wl_surface **out_wl_surface,
+ struct wl_subsurface **out_wl_subsurface)
+{
+ struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo;
+ struct libdecor_frame *frame = &frame_cairo->frame;
+ struct wl_compositor *wl_compositor = plugin_cairo->wl_compositor;
+ struct wl_subcompositor *wl_subcompositor = plugin_cairo->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_cairo_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_cairo *frame_cairo,
+ struct border_component *cmpnt)
+{
+ switch (cmpnt->composite_mode) {
+ case COMPOSITE_SERVER:
+ if (!cmpnt->server.wl_surface) {
+ wl_list_init(&cmpnt->server.output_list);
+ cmpnt->server.scale = 1;
+ create_surface_subsurface_pair(frame_cairo,
+ &cmpnt->server.wl_surface,
+ &cmpnt->server.wl_subsurface);
+ wl_surface_add_listener(cmpnt->server.wl_surface,
+ &surface_listener,
+ frame_cairo);
+ }
+ break;
+ case COMPOSITE_CLIENT:
+ wl_list_init(&cmpnt->server.output_list);
+ break;
+ }
+
+ cmpnt->is_hidden = false;
+}
+
+static void
+ensure_border_surfaces(struct libdecor_frame_cairo *frame_cairo)
+{
+ int min_width, min_height;
+
+ frame_cairo->shadow.opaque = false;
+ ensure_component(frame_cairo, &frame_cairo->shadow);
+
+#if ! APPLY_FLTK_CHANGES
+ libdecor_frame_get_min_content_size(&frame_cairo->frame,
+ &min_width, &min_height);
+ libdecor_frame_set_min_content_size(&frame_cairo->frame,
+ MAX(min_width, (int)MAX(56, 4 * BUTTON_WIDTH)),
+ MAX(min_height, (int)MAX(56, TITLE_HEIGHT + 1)));
+#endif
+}
+
+static void
+ensure_title_bar_surfaces(struct libdecor_frame_cairo *frame_cairo)
+{
+ frame_cairo->title_bar.title.opaque = true;
+ ensure_component(frame_cairo, &frame_cairo->title_bar.title);
+
+ frame_cairo->title_bar.min.opaque = true;
+ ensure_component(frame_cairo, &frame_cairo->title_bar.min);
+
+ frame_cairo->title_bar.max.opaque = true;
+ ensure_component(frame_cairo, &frame_cairo->title_bar.max);
+
+ frame_cairo->title_bar.close.opaque = true;
+ ensure_component(frame_cairo, &frame_cairo->title_bar.close);
+}
+
+static void
+calculate_component_size(struct libdecor_frame_cairo *frame_cairo,
+ enum component component,
+ int *component_x,
+ int *component_y,
+ int *component_width,
+ int *component_height)
+{
+ struct libdecor_frame *frame = &frame_cairo->frame;
+ int content_width, content_height;
+
+ content_width = libdecor_frame_get_content_width(frame);
+ content_height = libdecor_frame_get_content_height(frame);
+
+ 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 TITLE:
+ *component_x = 0;
+ *component_y = -(int)TITLE_HEIGHT;
+ *component_width = content_width;
+ *component_height = TITLE_HEIGHT;
+ return;
+ case BUTTON_MIN:
+ *component_x = content_width - 3 * BUTTON_WIDTH;
+ *component_y = 0;
+ *component_width = BUTTON_WIDTH;
+ *component_height = TITLE_HEIGHT;
+ return;
+ case BUTTON_MAX:
+ *component_x = content_width - 2 * BUTTON_WIDTH;
+ *component_y = 0;
+ *component_width = BUTTON_WIDTH;
+ *component_height = TITLE_HEIGHT;
+ return;
+ case BUTTON_CLOSE:
+ *component_x = content_width - BUTTON_WIDTH;
+ *component_y = 0;
+ *component_width = BUTTON_WIDTH;
+ *component_height = TITLE_HEIGHT;
+ return;
+ }
+
+ abort();
+}
+
+static int
+border_component_get_scale(struct border_component *border_component)
+{
+ switch (border_component->composite_mode) {
+ case COMPOSITE_SERVER:
+ return border_component->server.scale;
+ case COMPOSITE_CLIENT:
+ return border_component_get_scale(
+ border_component->client.parent_component);
+ }
+ return 0;
+}
+
+static void
+draw_title_text(struct libdecor_frame_cairo *frame_cairo,
+ cairo_t *cr,
+ const int *title_width,
+ bool active)
+{
+ const uint32_t col_title = active ? COL_TITLE : COL_TITLE_INACT;
+ const uint32_t col_title_text = active ? COL_SYM : COL_SYM_INACT;
+
+ PangoLayout *layout;
+
+ /* title fade out at buttons */
+ const int fade_width = 5 * BUTTON_WIDTH;
+ int fade_start;
+ cairo_pattern_t *fade;
+
+ /* text position and dimensions */
+ int text_extents_width, text_extents_height;
+ double text_x, text_y;
+ double text_width, text_height;
+
+ const char *title;
+
+ title = libdecor_frame_get_title((struct libdecor_frame*) frame_cairo);
+ if (!title)
+ return;
+
+ layout = pango_cairo_create_layout(cr);
+
+ pango_layout_set_text(layout,
+ title,
+ -1);
+ pango_layout_set_font_description(layout, frame_cairo->plugin_cairo->font);
+ pango_layout_get_size(layout, &text_extents_width, &text_extents_height);
+
+ /* set text position and dimensions */
+ text_width = text_extents_width / PANGO_SCALE;
+ text_height = text_extents_height / PANGO_SCALE;
+ text_x = *title_width / 2.0 - text_width / 2.0;
+ text_x += MIN(0.0, ((*title_width - fade_width) - (text_x + text_width)));
+ text_x = MAX(text_x, BUTTON_WIDTH);
+ text_y = TITLE_HEIGHT / 2.0 - text_height / 2.0;
+
+ /* draw title text */
+ cairo_move_to(cr, text_x, text_y);
+ cairo_set_rgba32(cr, &col_title_text);
+ pango_cairo_show_layout(cr, layout);
+
+ /* draw fade-out from title text to buttons */
+ fade_start = *title_width - fade_width;
+ fade = cairo_pattern_create_linear(fade_start, 0,
+ fade_start + 2 * BUTTON_WIDTH, 0);
+ cairo_pattern_add_color_stop_rgba(fade, 0,
+ red(&col_title),
+ green(&col_title),
+ blue(&col_title),
+ 0);
+ cairo_pattern_add_color_stop_rgb(fade, 1,
+ red(&col_title),
+ green(&col_title),
+ blue(&col_title));
+ cairo_rectangle(cr, fade_start, 0, fade_width, TITLE_HEIGHT);
+ cairo_set_source(cr, fade);
+ cairo_fill(cr);
+
+ cairo_pattern_destroy(fade);
+ g_object_unref(layout);
+}
+
+static void
+draw_component_content(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *border_component,
+ int component_width,
+ int component_height,
+ enum component component)
+{
+ struct buffer *buffer;
+ cairo_surface_t *surface = NULL;
+ int width = 0, height = 0;
+ int scale;
+ cairo_t *cr;
+
+ /* button symbol origin */
+ const double x = BUTTON_WIDTH / 2 - SYM_DIM / 2 + 0.5;
+ const double y = TITLE_HEIGHT / 2 - SYM_DIM / 2 + 0.5;
+
+ enum libdecor_window_state state;
+
+ bool active;
+
+ uint32_t col_title;
+
+ bool cap_min, cap_max, cap_close;
+
+ /* capabilities of decorations */
+ cap_min = minimizable(frame_cairo);
+ cap_max = resizable(frame_cairo);
+ cap_close = closeable(frame_cairo);
+
+ scale = border_component_get_scale(border_component);
+
+ state = libdecor_frame_get_window_state((struct libdecor_frame *) frame_cairo);
+
+ active = state & LIBDECOR_WINDOW_STATE_ACTIVE;
+
+ col_title = active ? COL_TITLE : COL_TITLE_INACT;
+
+ /* clear buffer */
+ switch (border_component->composite_mode) {
+ case COMPOSITE_SERVER:
+ buffer = border_component->server.buffer;
+
+ 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)
+ );
+ cairo_surface_set_device_scale(surface, scale, scale);
+ width = buffer->width;
+ height = buffer->height;
+ break;
+ case COMPOSITE_CLIENT:
+ surface = cairo_surface_reference(border_component->client.image);
+ width = cairo_image_surface_get_width(surface);
+ height = cairo_image_surface_get_height(surface);
+ break;
+ }
+
+ cr = cairo_create(surface);
+ cairo_save(cr);
+ cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(cr);
+ cairo_restore(cr);
+
+ /* background */
+ switch (component) {
+ case NONE:
+ break;
+ case SHADOW:
+ if (frame_cairo->decoration_type != DECORATION_TYPE_TILED)
+ render_shadow(cr,
+ frame_cairo->shadow_blur,
+ -(int)SHADOW_MARGIN/2,
+ -(int)SHADOW_MARGIN/2,
+ width + SHADOW_MARGIN,
+ height + SHADOW_MARGIN,
+ 64,
+ 64);
+ break;
+ case TITLE:
+ cairo_set_rgba32(cr, &col_title);
+ cairo_paint(cr);
+ break;
+ case BUTTON_MIN:
+ if (cap_min && frame_cairo->active == &frame_cairo->title_bar.min)
+ cairo_set_rgba32(cr, active ? &COL_BUTTON_MIN : &COL_BUTTON_INACT);
+ else
+ cairo_set_rgba32(cr, &col_title);
+ cairo_paint(cr);
+ break;
+ case BUTTON_MAX:
+ if (cap_max && frame_cairo->active == &frame_cairo->title_bar.max)
+ cairo_set_rgba32(cr, active ? &COL_BUTTON_MAX : &COL_BUTTON_INACT);
+ else
+ cairo_set_rgba32(cr, &col_title);
+ cairo_paint(cr);
+ break;
+ case BUTTON_CLOSE:
+ if (cap_close && frame_cairo->active == &frame_cairo->title_bar.close)
+ cairo_set_rgba32(cr, active ? &COL_BUTTON_CLOSE : &COL_BUTTON_INACT);
+ else
+ cairo_set_rgba32(cr, &col_title);
+ cairo_paint(cr);
+ break;
+ }
+
+ /* button symbols */
+ /* https://www.cairographics.org/FAQ/#sharp_lines */
+ cairo_set_line_width(cr, 1);
+
+ switch (component) {
+ case TITLE:
+ draw_title_text(frame_cairo,cr, &component_width, active);
+ break;
+ case BUTTON_MIN:
+ if (!active) {
+ /* inactive: use single desaturated color */
+ cairo_set_rgba32(cr, &COL_SYM_INACT);
+ } else {
+ if (!cap_min ||
+ frame_cairo->active == &frame_cairo->title_bar.min) {
+ /* active (a.k.a. prelight) */
+ cairo_set_rgba32(cr, &COL_SYM_ACT);
+ } else {
+ /* normal */
+ cairo_set_rgba32(cr, &COL_SYM);
+ }
+ }
+ cairo_move_to(cr, x, y + SYM_DIM - 1);
+ cairo_rel_line_to(cr, SYM_DIM - 1, 0);
+ cairo_stroke(cr);
+ break;
+ case BUTTON_MAX:
+ if (!active) {
+ /* inactive: use single desaturated color */
+ cairo_set_rgba32(cr, &COL_SYM_INACT);
+ } else {
+ if (!cap_max ||
+ frame_cairo->active == &frame_cairo->title_bar.max) {
+ /* active (a.k.a. prelight) */
+ cairo_set_rgba32(cr, &COL_SYM_ACT);
+ } else {
+ /* normal */
+ cairo_set_rgba32(cr, &COL_SYM);
+ }
+ }
+
+ if (state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {
+ const size_t small = 12;
+ cairo_rectangle(cr,
+ x,
+ y + SYM_DIM - small,
+ small - 1,
+ small - 1);
+ cairo_move_to(cr,
+ x + SYM_DIM - small,
+ y + SYM_DIM - small);
+ cairo_line_to(cr, x + SYM_DIM - small, y);
+ cairo_rel_line_to(cr, small - 1, 0);
+ cairo_rel_line_to(cr, 0, small - 1);
+ cairo_line_to(cr, x + small - 1, y + small - 1);
+ }
+ else {
+ cairo_rectangle(cr, x, y, SYM_DIM - 1, SYM_DIM - 1);
+ }
+ cairo_stroke(cr);
+ break;
+ case BUTTON_CLOSE:
+ if (!active) {
+ /* inactive: use single desaturated color */
+ cairo_set_rgba32(cr, &COL_SYM_INACT);
+ } else {
+ if (!cap_close ||
+ frame_cairo->active == &frame_cairo->title_bar.close) {
+ /* active (a.k.a. prelight) */
+ cairo_set_rgba32(cr, &COL_SYM_ACT);
+ } else {
+ /* normal */
+ cairo_set_rgba32(cr, &COL_SYM);
+ }
+ }
+ cairo_move_to(cr, x, y);
+ cairo_rel_line_to(cr, SYM_DIM - 1, SYM_DIM - 1);
+ cairo_move_to(cr, x + SYM_DIM - 1, y);
+ cairo_line_to(cr, x, y + SYM_DIM - 1);
+ cairo_stroke(cr);
+ break;
+ default:
+ break;
+ }
+
+ /* mask the toplevel surface */
+ if (component == SHADOW) {
+ int component_x, component_y, component_width, component_height;
+ calculate_component_size(frame_cairo, 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_cairo->frame),
+ libdecor_frame_get_content_height(
+ &frame_cairo->frame));
+ cairo_fill(cr);
+ }
+
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+}
+
+static void
+set_component_input_region(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *border_component)
+{
+ if (border_component->type == SHADOW && frame_cairo->shadow_showing) {
+ struct wl_region *input_region;
+ int component_x;
+ int component_y;
+ int component_width;
+ int component_height;
+
+ calculate_component_size(frame_cairo, 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_cairo->plugin_cairo->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_cairo->frame),
+ libdecor_frame_get_content_height(&frame_cairo->frame));
+ wl_surface_set_input_region(border_component->server.wl_surface,
+ input_region);
+ wl_region_destroy(input_region);
+ }
+}
+
+static void
+ensure_component_realized_server(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *border_component,
+ int component_width,
+ int component_height,
+ int scale)
+{
+ struct buffer *old_buffer;
+ struct buffer *buffer = NULL;
+
+ old_buffer = border_component->server.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->server.buffer = NULL;
+ }
+ }
+
+ if (!buffer)
+ buffer = create_shm_buffer(frame_cairo->plugin_cairo,
+ component_width,
+ component_height,
+ border_component->opaque,
+ border_component->server.scale);
+
+ border_component->server.buffer = buffer;
+}
+
+static void
+ensure_component_realized_client(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *border_component,
+ int component_width,
+ int component_height,
+ int scale)
+{
+ cairo_surface_t *old_image;
+
+ old_image = border_component->client.image;
+ if (old_image) {
+ int cairo_buffer_width;
+ int cairo_buffer_height;
+ double x_scale;
+ double y_scale;
+
+ cairo_surface_get_device_scale(old_image, &x_scale, &y_scale);
+ cairo_buffer_width =
+ (int) round(cairo_image_surface_get_width(old_image) *
+ x_scale);
+ cairo_buffer_height =
+ (int) round(cairo_image_surface_get_height(old_image) *
+ y_scale);
+
+ if (cairo_buffer_width != component_width * scale ||
+ cairo_buffer_height != component_height * scale) {
+ cairo_surface_destroy(old_image);
+ border_component->client.image = NULL;
+ }
+ }
+
+ if (!border_component->client.image) {
+ cairo_surface_t *new_image;
+
+ new_image =
+ cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ component_width * scale,
+ component_height * scale);
+ cairo_surface_set_device_scale(new_image, scale, scale);
+ border_component->client.image = new_image;
+ }
+
+}
+
+static void
+ensure_component_realized(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *border_component,
+ int component_width,
+ int component_height,
+ int scale)
+{
+ switch (border_component->composite_mode) {
+ case COMPOSITE_SERVER:
+ ensure_component_realized_server(frame_cairo, border_component,
+ component_width,
+ component_height,
+ scale);
+ break;
+ case COMPOSITE_CLIENT:
+ ensure_component_realized_client(frame_cairo, border_component,
+ component_width,
+ component_height,
+ scale);
+ break;
+ }
+}
+
+static cairo_t *
+create_cairo_for_parent(struct border_component *border_component)
+{
+ struct border_component *parent =
+ border_component->client.parent_component;
+ struct buffer *buffer;
+ struct border_component *server_component;
+ cairo_surface_t *parent_surface;
+ cairo_t *cr;
+
+ switch (parent->composite_mode) {
+ case COMPOSITE_SERVER:
+ buffer = parent->server.buffer;
+ parent_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(parent_surface);
+ cairo_surface_destroy(parent_surface);
+ cairo_scale(cr, buffer->scale, buffer->scale);
+ return cr;
+ case COMPOSITE_CLIENT:
+ cr = cairo_create(parent->client.image);
+ server_component = get_server_component(border_component);
+ cairo_scale(cr,
+ server_component->server.scale,
+ server_component->server.scale);
+ return cr;
+ }
+ return NULL;
+}
+
+static void
+draw_border_component(struct libdecor_frame_cairo *frame_cairo,
+ struct border_component *border_component)
+{
+ enum component component = border_component->type;
+ struct buffer *buffer;
+ cairo_t *cr;
+ int component_x;
+ int component_y;
+ int component_width;
+ int component_height;
+ int scale;
+ struct border_component *child_component;
+
+ if (border_component->is_hidden)
+ return;
+
+ calculate_component_size(frame_cairo, component,
+ &component_x, &component_y,
+ &component_width, &component_height);
+
+ set_component_input_region(frame_cairo, border_component);
+
+ scale = border_component_get_scale(border_component);
+ ensure_component_realized(frame_cairo, border_component,
+ component_width,
+ component_height,
+ scale);
+
+ draw_component_content(frame_cairo,
+ border_component,
+ component_width, component_height,
+ component);
+
+ switch(border_component->composite_mode) {
+ case COMPOSITE_SERVER:
+ buffer = border_component->server.buffer;
+ wl_surface_attach(border_component->server.wl_surface,
+ buffer->wl_buffer,
+ 0, 0);
+ wl_surface_set_buffer_scale(border_component->server.wl_surface,
+ buffer->scale);
+ buffer->in_use = true;
+ wl_surface_commit(border_component->server.wl_surface);
+ wl_surface_damage_buffer(border_component->server.wl_surface, 0, 0,
+ component_width * scale,
+ component_height * scale);
+ wl_subsurface_set_position(border_component->server.wl_subsurface,
+ component_x, component_y);
+ break;
+ case COMPOSITE_CLIENT:
+ cr = create_cairo_for_parent(border_component);
+ cairo_set_source_surface(cr,
+ border_component->client.image,
+ component_x, component_y);
+ cairo_paint(cr);
+ cairo_destroy(cr);
+ break;
+ }
+
+ wl_list_for_each(child_component, &border_component->child_components, link)
+ draw_border_component(frame_cairo, child_component);
+}
+
+static void
+draw_border(struct libdecor_frame_cairo *frame_cairo)
+{
+ draw_border_component(frame_cairo, &frame_cairo->shadow);
+ frame_cairo->shadow_showing = true;
+}
+
+static void
+draw_title_bar(struct libdecor_frame_cairo *frame_cairo)
+{
+ draw_border_component(frame_cairo, &frame_cairo->title_bar.title);
+ frame_cairo->title_bar.is_showing = true;
+}
+
+static void
+draw_decoration(struct libdecor_frame_cairo *frame_cairo)
+{
+ switch (frame_cairo->decoration_type) {
+ case DECORATION_TYPE_NONE:
+ if (frame_cairo->link.next != NULL)
+ wl_list_remove(&frame_cairo->link);
+ if (is_border_surfaces_showing(frame_cairo))
+ hide_border_surfaces(frame_cairo);
+ if (is_title_bar_surfaces_showing(frame_cairo))
+ hide_title_bar_surfaces(frame_cairo);
+ break;
+ case DECORATION_TYPE_TILED:
+ case DECORATION_TYPE_ALL:
+ /* show borders */
+ ensure_border_surfaces(frame_cairo);
+ draw_border(frame_cairo);
+ /* show title bar */
+ ensure_title_bar_surfaces(frame_cairo);
+ draw_title_bar(frame_cairo);
+ /* link frame */
+ if (frame_cairo->link.next == NULL)
+ wl_list_insert(
+ &frame_cairo->plugin_cairo->visible_frame_list,
+ &frame_cairo->link);
+ break;
+ case DECORATION_TYPE_MAXIMIZED:
+ /* hide borders */
+ if (is_border_surfaces_showing(frame_cairo))
+ hide_border_surfaces(frame_cairo);
+ /* show title bar */
+ ensure_title_bar_surfaces(frame_cairo);
+ draw_title_bar(frame_cairo);
+ /* link frame */
+ if (frame_cairo->link.next == NULL)
+ wl_list_insert(
+ &frame_cairo->plugin_cairo->visible_frame_list,
+ &frame_cairo->link);
+ break;
+ }
+}
+
+static void
+set_window_geometry(struct libdecor_frame_cairo *frame_cairo)
+{
+ struct libdecor_frame *frame = &frame_cairo->frame;
+ int x = 0, y = 0, width = 0, height = 0;
+
+ switch (frame_cairo->decoration_type) {
+ case DECORATION_TYPE_NONE:
+ x = 0;
+ y = 0;
+ width = libdecor_frame_get_content_width(frame);
+ height = libdecor_frame_get_content_height(frame);
+ break;
+ case DECORATION_TYPE_ALL:
+ case DECORATION_TYPE_TILED:
+ case DECORATION_TYPE_MAXIMIZED:
+ x = 0;
+ y = -(int)TITLE_HEIGHT;
+ width = libdecor_frame_get_content_width(frame);
+ height = libdecor_frame_get_content_height(frame) + TITLE_HEIGHT;
+ break;
+ }
+
+ libdecor_frame_set_window_geometry(frame, x, y, width, height);
+}
+
+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)
+ /* title bar, no shadows */
+ return DECORATION_TYPE_MAXIMIZED;
+ else if (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, invisible shadows */
+ return DECORATION_TYPE_TILED;
+ else
+ /* title bar, shadows */
+ return DECORATION_TYPE_ALL;
+}
+
+static void
+libdecor_plugin_cairo_frame_commit(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ struct libdecor_configuration *configuration)
+{
+ struct libdecor_frame_cairo *frame_cairo =
+ (struct libdecor_frame_cairo *) 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_cairo->window_state;
+ new_window_state = libdecor_frame_get_window_state(frame);
+
+ old_content_width = frame_cairo->content_width;
+ old_content_height = frame_cairo->content_height;
+ new_content_width = libdecor_frame_get_content_width(frame);
+ new_content_height = libdecor_frame_get_content_height(frame);
+
+ old_decoration_type = frame_cairo->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_cairo->content_width = new_content_width;
+ frame_cairo->content_height = new_content_height;
+ frame_cairo->decoration_type = new_decoration_type;
+ frame_cairo->window_state = new_window_state;
+
+ draw_decoration(frame_cairo);
+ set_window_geometry(frame_cairo);
+}
+
+static void
+libdecor_plugin_cairo_frame_property_changed(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame)
+{
+ struct libdecor_frame_cairo *frame_cairo =
+ (struct libdecor_frame_cairo *) frame;
+ bool redraw_needed = false;
+ const char *new_title;
+
+ new_title = libdecor_frame_get_title(frame);
+ if (frame_cairo->title_bar.is_showing) {
+ if (!streql(frame_cairo->title, new_title))
+ redraw_needed = true;
+ }
+
+ if (frame_cairo->title) {
+ free(frame_cairo->title);
+ frame_cairo->title = NULL;
+ }
+
+ if (new_title) {
+ frame_cairo->title = strdup(new_title);
+ }
+
+ if (frame_cairo->capabilities != libdecor_frame_get_capabilities(frame)) {
+ frame_cairo->capabilities = libdecor_frame_get_capabilities(frame);
+ redraw_needed = true;
+ }
+
+ if (redraw_needed) {
+ draw_decoration(frame_cairo);
+ libdecor_frame_toplevel_commit(frame);
+ }
+}
+
+static void
+libdecor_plugin_cairo_frame_translate_coordinate(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ int content_x,
+ int content_y,
+ int *frame_x,
+ int *frame_y)
+{
+ struct libdecor_frame_cairo *frame_cairo =
+ (struct libdecor_frame_cairo *) frame;
+
+ *frame_x = content_x;
+ *frame_y = content_y;
+
+ if (frame_cairo->title_bar.is_showing)
+ *frame_y += TITLE_HEIGHT;
+}
+
+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 void
+libdecor_plugin_cairo_frame_popup_grab(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ const char *seat_name)
+{
+ struct libdecor_frame_cairo *frame_cairo =
+ (struct libdecor_frame_cairo *) frame;
+ struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo;
+ struct seat *seat;
+
+ wl_list_for_each(seat, &plugin_cairo->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_cairo_frame_popup_ungrab(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ const char *seat_name)
+{
+ struct libdecor_frame_cairo *frame_cairo =
+ (struct libdecor_frame_cairo *) frame;
+ struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo;
+ struct seat *seat;
+
+ wl_list_for_each(seat, &plugin_cairo->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_cairo, seat);
+ return;
+ }
+ }
+
+ fprintf(stderr,
+ "libdecor-WARNING: Application tried to ungrab unknown seat\n");
+}
+
+static bool
+libdecor_plugin_cairo_configuration_get_content_size(
+ struct libdecor_plugin *plugin,
+ struct libdecor_configuration *configuration,
+ struct libdecor_frame *frame,
+ int *content_width,
+ int *content_height)
+{
+ int win_width, win_height;
+ if (!libdecor_configuration_get_window_size(configuration,
+ &win_width,
+ &win_height))
+ return false;
+
+ enum libdecor_window_state state;
+ if (!libdecor_configuration_get_window_state(configuration, &state)) {
+ return false;
+ }
+
+ switch (window_state_to_decoration_type(state)) {
+ case DECORATION_TYPE_NONE:
+ *content_width = win_width;
+ *content_height = win_height;
+ break;
+ case DECORATION_TYPE_ALL:
+ case DECORATION_TYPE_TILED:
+ case DECORATION_TYPE_MAXIMIZED:
+ *content_width = win_width;
+ *content_height = win_height - TITLE_HEIGHT;
+ break;
+ }
+
+ return true;
+}
+
+static bool
+libdecor_plugin_cairo_frame_get_window_size_for(
+ struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ int *window_width,
+ int *window_height)
+{
+ enum libdecor_window_state window_state =
+ libdecor_state_get_window_state(state);
+
+ switch (window_state_to_decoration_type(window_state)) {
+ case DECORATION_TYPE_NONE:
+ *window_width = libdecor_state_get_content_width(state);
+ *window_height = libdecor_state_get_content_height(state);
+ break;
+ case DECORATION_TYPE_ALL:
+ case DECORATION_TYPE_TILED:
+ case DECORATION_TYPE_MAXIMIZED:
+ *window_width = libdecor_state_get_content_width(state);
+ *window_height =
+ libdecor_state_get_content_height(state) + TITLE_HEIGHT;
+ break;
+ }
+
+ return true;
+}
+
+static struct libdecor_plugin_interface cairo_plugin_iface = {
+ .destroy = libdecor_plugin_cairo_destroy,
+ .get_fd = libdecor_plugin_cairo_get_fd,
+ .dispatch = libdecor_plugin_cairo_dispatch,
+
+ .frame_new = libdecor_plugin_cairo_frame_new,
+ .frame_free = libdecor_plugin_cairo_frame_free,
+ .frame_commit = libdecor_plugin_cairo_frame_commit,
+ .frame_property_changed = libdecor_plugin_cairo_frame_property_changed,
+ .frame_translate_coordinate =
+ libdecor_plugin_cairo_frame_translate_coordinate,
+ .frame_popup_grab = libdecor_plugin_cairo_frame_popup_grab,
+ .frame_popup_ungrab = libdecor_plugin_cairo_frame_popup_ungrab,
+
+ .configuration_get_content_size =
+ libdecor_plugin_cairo_configuration_get_content_size,
+ .frame_get_window_size_for =
+ libdecor_plugin_cairo_frame_get_window_size_for,
+};
+
+static void
+init_wl_compositor(struct libdecor_plugin_cairo *plugin_cairo,
+ uint32_t id,
+ uint32_t version)
+{
+ plugin_cairo->wl_compositor =
+ wl_registry_bind(plugin_cairo->wl_registry,
+ id, &wl_compositor_interface,
+ MIN(version, 4));
+}
+
+static void
+init_wl_subcompositor(struct libdecor_plugin_cairo *plugin_cairo,
+ uint32_t id,
+ uint32_t version)
+{
+ plugin_cairo->wl_subcompositor =
+ wl_registry_bind(plugin_cairo->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_cairo *plugin_cairo = user_data;
+
+ if (format == WL_SHM_FORMAT_ARGB8888)
+ plugin_cairo->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_cairo *plugin_cairo = user_data;
+ struct libdecor *context = plugin_cairo->context;
+
+ wl_callback_destroy(callback);
+ plugin_cairo->globals_callback_shm = NULL;
+
+ if (!plugin_cairo->has_argb) {
+ libdecor_notify_plugin_error(
+ context,
+ LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+ "Compositor is missing required shm format");
+ return;
+ }
+
+ libdecor_notify_plugin_ready(context);
+}
+
+static const struct wl_callback_listener shm_callback_listener = {
+ shm_callback
+};
+
+static void
+init_wl_shm(struct libdecor_plugin_cairo *plugin_cairo,
+ uint32_t id,
+ uint32_t version)
+{
+ struct libdecor *context = plugin_cairo->context;
+ struct wl_display *wl_display = libdecor_get_wl_display(context);
+
+ plugin_cairo->wl_shm =
+ wl_registry_bind(plugin_cairo->wl_registry,
+ id, &wl_shm_interface, 1);
+ wl_shm_add_listener(plugin_cairo->wl_shm, &shm_listener, plugin_cairo);
+
+ plugin_cairo->globals_callback_shm = wl_display_sync(wl_display);
+ wl_callback_add_listener(plugin_cairo->globals_callback_shm,
+ &shm_callback_listener,
+ plugin_cairo);
+}
+
+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 = zalloc(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_cairo->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_cairo *plugin_cairo = seat->plugin_cairo;
+ int scale = 1;
+ struct wl_cursor_theme *theme;
+ struct cursor_output *cursor_output;
+
+ wl_list_for_each(cursor_output, &seat->cursor_outputs, link) {
+ scale = MAX(scale, cursor_output->output->scale);
+ }
+
+ if (seat->cursor_theme && seat->cursor_scale == scale)
+ return false;
+
+ seat->cursor_scale = scale;
+ theme = wl_cursor_theme_load(plugin_cairo->cursor_theme_name,
+ plugin_cairo->cursor_size * scale,
+ plugin_cairo->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;
+ const bool bottom = pointer_y > (cmpnt->server.buffer->height - margin);
+ const bool left = pointer_x < margin;
+ const bool right = pointer_x > (cmpnt->server.buffer->width - margin);
+
+ 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_cairo *frame_cairo =
+ wl_surface_get_user_data(seat->pointer_focus);
+ struct wl_cursor *wl_cursor = NULL;
+
+ if (!frame_cairo || !frame_cairo->active) {
+ seat->current_cursor = seat->cursor_left_ptr;
+ return false;
+ }
+
+ bool theme_updated = ensure_cursor_theme(seat);
+
+ if (frame_cairo->active->type == SHADOW &&
+ is_border_surfaces_showing(frame_cairo) &&
+ resizable(frame_cairo)) {
+ enum libdecor_resize_edge edge;
+ edge = component_edge(frame_cairo->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
+sync_active_component(struct libdecor_frame_cairo *frame_cairo,
+ struct seat *seat)
+{
+ struct border_component *old_active;
+
+ if (!seat->pointer_focus)
+ return;
+
+ old_active = frame_cairo->active;
+ update_component_focus(frame_cairo, seat->pointer_focus, seat);
+ if (old_active != frame_cairo->active) {
+ draw_decoration(frame_cairo);
+ libdecor_frame_toplevel_commit(&frame_cairo->frame);
+ }
+
+ if (update_local_cursor(seat))
+ send_cursor(seat);
+}
+
+static void
+synthesize_pointer_enter(struct seat *seat)
+{
+ struct wl_surface *surface;
+ struct libdecor_frame_cairo *frame_cairo;
+
+ surface = seat->pointer_focus;
+ if (!surface)
+ return;
+
+ frame_cairo = wl_surface_get_user_data(surface);
+ if (!frame_cairo)
+ return;
+
+ update_component_focus(frame_cairo, seat->pointer_focus, seat);
+ frame_cairo->grab = NULL;
+
+ /* update decorations */
+ if (frame_cairo->active) {
+ draw_decoration(frame_cairo);
+ libdecor_frame_toplevel_commit(&frame_cairo->frame);
+ }
+
+ update_local_cursor(seat);
+ send_cursor(seat);
+}
+
+static void
+synthesize_pointer_leave(struct seat *seat)
+{
+ struct wl_surface *surface;
+ struct libdecor_frame_cairo *frame_cairo;
+
+ surface = seat->pointer_focus;
+ if (!surface)
+ return;
+
+ frame_cairo = wl_surface_get_user_data(surface);
+ if (!frame_cairo)
+ return;
+
+ if (!frame_cairo->active)
+ return;
+
+ frame_cairo->active = NULL;
+ draw_decoration(frame_cairo);
+ libdecor_frame_toplevel_commit(&frame_cairo->frame);
+ update_local_cursor(seat);
+}
+
+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)
+{
+ struct seat *seat = data;
+
+ if (!surface)
+ return;
+
+ if (!own_surface(surface))
+ return;
+
+ 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 (seat->grabbed)
+ return;
+
+ synthesize_pointer_enter(seat);
+}
+
+static void
+pointer_leave(void *data,
+ struct wl_pointer *wl_pointer,
+ uint32_t serial,
+ struct wl_surface *surface)
+{
+ struct seat *seat = data;
+
+ if (!surface)
+ return;
+
+ if (!own_surface(surface))
+ return;
+
+ synthesize_pointer_leave(seat);
+ seat->pointer_focus = NULL;
+}
+
+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_cairo *frame_cairo;
+
+ seat->pointer_x = wl_fixed_to_int(surface_x);
+ seat->pointer_y = wl_fixed_to_int(surface_y);
+
+ if (seat->grabbed)
+ return;
+
+ if (!seat->pointer_focus)
+ return;
+
+ frame_cairo = wl_surface_get_user_data(seat->pointer_focus);
+
+ sync_active_component(frame_cairo, seat);
+}
+
+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_cairo *frame_cairo;
+
+ if (!seat->pointer_focus || !own_surface(seat->pointer_focus))
+ return;
+
+ frame_cairo = wl_surface_get_user_data(seat->pointer_focus);
+ if (!frame_cairo)
+ return;
+
+ if (seat->grabbed) {
+ libdecor_frame_dismiss_popup(&frame_cairo->frame, seat->name);
+ return;
+ }
+
+ if (!frame_cairo->active)
+ return;
+
+ if (button == BTN_LEFT) {
+ if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+ enum libdecor_resize_edge edge =
+ LIBDECOR_RESIZE_EDGE_NONE;
+
+ frame_cairo->grab = NULL;
+
+ switch (frame_cairo->active->type) {
+ case SHADOW:
+ edge = component_edge(frame_cairo->active,
+ seat->pointer_x,
+ seat->pointer_y,
+ SHADOW_MARGIN);
+ break;
+ case TITLE:
+ if (time-seat->pointer_button_time_stamp <
+ DOUBLE_CLICK_TIME_MS) {
+ toggle_maximized(&frame_cairo->frame);
+ }
+ else if (moveable(frame_cairo)) {
+ seat->pointer_button_time_stamp = time;
+ libdecor_frame_move(&frame_cairo->frame,
+ seat->wl_seat,
+ serial);
+ }
+ break;
+ case BUTTON_MIN:
+ case BUTTON_MAX:
+ case BUTTON_CLOSE:
+ frame_cairo->grab = frame_cairo->active;
+ break;
+ default:
+ break;
+ }
+
+ if (edge != LIBDECOR_RESIZE_EDGE_NONE &&
+ resizable(frame_cairo)) {
+ libdecor_frame_resize(
+ &frame_cairo->frame,
+ seat->wl_seat,
+ serial,
+ edge);
+ }
+ }
+ else if (state == WL_POINTER_BUTTON_STATE_RELEASED &&
+ frame_cairo->grab) {
+ if (frame_cairo->grab == frame_cairo->focus) {
+ switch (frame_cairo->active->type) {
+ case BUTTON_MIN:
+ if (minimizable(frame_cairo))
+ libdecor_frame_set_minimized(
+ &frame_cairo->frame);
+ break;
+ case BUTTON_MAX:
+ toggle_maximized(&frame_cairo->frame);
+ break;
+ case BUTTON_CLOSE:
+ if (closeable(frame_cairo))
+ libdecor_frame_close(&frame_cairo->frame);
+ break;
+ default:
+ break;
+ }
+ }
+ frame_cairo->grab = NULL;
+ sync_active_component(frame_cairo, seat);
+ }
+ }
+ else if (button == BTN_RIGHT &&
+ state == WL_POINTER_BUTTON_STATE_PRESSED &&
+ seat->pointer_focus == frame_cairo->title_bar.title.server.wl_surface) {
+ libdecor_frame_show_window_menu(&frame_cairo->frame,
+ seat->wl_seat,
+ serial,
+ seat->pointer_x,
+ seat->pointer_y - TITLE_HEIGHT);
+ }
+}
+
+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
+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;
+ }
+}
+
+static void
+seat_name(void *data,
+ struct wl_seat *wl_seat,
+ const char *name)
+{
+ struct seat *seat = data;
+
+ seat->name = strdup(name);
+}
+
+static struct wl_seat_listener seat_listener = {
+ seat_capabilities,
+ seat_name
+};
+
+static void
+init_wl_seat(struct libdecor_plugin_cairo *plugin_cairo,
+ uint32_t id,
+ uint32_t version)
+{
+ struct seat *seat;
+
+ if (version < 3) {
+ libdecor_notify_plugin_error(
+ plugin_cairo->context,
+ LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+ "%s version 3 required but only version %i is available\n",
+ wl_seat_interface.name, version);
+ }
+
+ seat = zalloc(sizeof *seat);
+ seat->cursor_scale = 1;
+ seat->plugin_cairo = plugin_cairo;
+ wl_list_init(&seat->cursor_outputs);
+ wl_list_insert(&plugin_cairo->seat_list, &seat->link);
+ seat->wl_seat =
+ wl_registry_bind(plugin_cairo->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_cairo *frame_cairo;
+ struct seat *seat;
+
+ wl_list_for_each(frame_cairo,
+ &output->plugin_cairo->visible_frame_list, link) {
+ bool updated = false;
+ updated |= redraw_scale(frame_cairo, &frame_cairo->shadow);
+ updated |= redraw_scale(frame_cairo, &frame_cairo->title_bar.title);
+ if (updated)
+ libdecor_frame_toplevel_commit(&frame_cairo->frame);
+ }
+ wl_list_for_each(seat, &output->plugin_cairo->seat_list, link) {
+ if (update_local_cursor(seat))
+ send_cursor(seat);
+ }
+}
+
+static void
+output_scale(void *data,
+ struct wl_output *wl_output,
+ int32_t factor)
+{
+ struct output *output = data;
+
+ output->scale = factor;
+}
+
+static struct wl_output_listener output_listener = {
+ output_geometry,
+ output_mode,
+ output_done,
+ output_scale
+};
+
+static void
+init_wl_output(struct libdecor_plugin_cairo *plugin_cairo,
+ uint32_t id,
+ uint32_t version)
+{
+ struct output *output;
+
+ if (version < 2) {
+ libdecor_notify_plugin_error(
+ plugin_cairo->context,
+ LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+ "%s version 2 required but only version %i is available\n",
+ wl_output_interface.name, version);
+ }
+
+ output = zalloc(sizeof *output);
+ output->plugin_cairo = plugin_cairo;
+ wl_list_insert(&plugin_cairo->output_list, &output->link);
+ output->id = id;
+ output->wl_output =
+ wl_registry_bind(plugin_cairo->wl_registry,
+ id, &wl_output_interface, 2);
+ wl_proxy_set_tag((struct wl_proxy *) output->wl_output,
+ &libdecor_cairo_proxy_tag);
+ wl_output_add_listener(output->wl_output, &output_listener, output);
+}
+
+static void
+registry_handle_global(void *user_data,
+ struct wl_registry *wl_registry,
+ uint32_t id,
+ const char *interface,
+ uint32_t version)
+{
+ struct libdecor_plugin_cairo *plugin_cairo = user_data;
+
+ if (strcmp(interface, "wl_compositor") == 0)
+ init_wl_compositor(plugin_cairo, id, version);
+ else if (strcmp(interface, "wl_subcompositor") == 0)
+ init_wl_subcompositor(plugin_cairo, id, version);
+ else if (strcmp(interface, "wl_shm") == 0)
+ init_wl_shm(plugin_cairo, id, version);
+ else if (strcmp(interface, "wl_seat") == 0)
+ init_wl_seat(plugin_cairo, id, version);
+ else if (strcmp(interface, "wl_output") == 0)
+ init_wl_output(plugin_cairo, id, version);
+}
+
+static void
+remove_surface_outputs(struct border_component *cmpnt, struct output *output)
+{
+ struct surface_output *surface_output;
+ wl_list_for_each(surface_output, &cmpnt->server.output_list, link) {
+ if (surface_output->output == output) {
+ wl_list_remove(&surface_output->link);
+ free(surface_output);
+ break;
+ }
+ }
+}
+
+static void
+output_removed(struct libdecor_plugin_cairo *plugin_cairo,
+ struct output *output)
+{
+ struct libdecor_frame_cairo *frame_cairo;
+ struct seat *seat;
+
+ wl_list_for_each(frame_cairo, &plugin_cairo->visible_frame_list, link) {
+ remove_surface_outputs(&frame_cairo->shadow, output);
+ remove_surface_outputs(&frame_cairo->title_bar.title, output);
+ remove_surface_outputs(&frame_cairo->title_bar.min, output);
+ remove_surface_outputs(&frame_cairo->title_bar.max, output);
+ remove_surface_outputs(&frame_cairo->title_bar.close, output);
+ }
+ wl_list_for_each(seat, &plugin_cairo->seat_list, link) {
+ struct cursor_output *cursor_output, *tmp;
+ wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) {
+ if (cursor_output->output == output) {
+ wl_list_remove(&cursor_output->link);
+ free(cursor_output);
+ }
+ }
+ }
+
+ wl_list_remove(&output->link);
+ wl_output_destroy(output->wl_output);
+ free(output);
+}
+
+static void
+registry_handle_global_remove(void *user_data,
+ struct wl_registry *wl_registry,
+ uint32_t name)
+{
+ struct libdecor_plugin_cairo *plugin_cairo = user_data;
+ struct output *output;
+
+ wl_list_for_each(output, &plugin_cairo->output_list, link) {
+ if (output->id == name) {
+ output_removed(plugin_cairo, output);
+ break;
+ }
+ }
+}
+
+static const struct wl_registry_listener registry_listener = {
+ registry_handle_global,
+ registry_handle_global_remove
+};
+
+static bool
+has_required_globals(struct libdecor_plugin_cairo *plugin_cairo)
+{
+ if (!plugin_cairo->wl_compositor)
+ return false;
+ if (!plugin_cairo->wl_subcompositor)
+ return false;
+ if (!plugin_cairo->wl_shm)
+ return false;
+
+ return true;
+}
+
+static void
+globals_callback(void *user_data,
+ struct wl_callback *callback,
+ uint32_t time)
+{
+ struct libdecor_plugin_cairo *plugin_cairo = user_data;
+
+ wl_callback_destroy(callback);
+ plugin_cairo->globals_callback = NULL;
+
+ if (!has_required_globals(plugin_cairo)) {
+ libdecor_notify_plugin_error(
+ plugin_cairo->context,
+ LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+ "Compositor is missing required globals");
+ }
+}
+
+static const struct wl_callback_listener globals_callback_listener = {
+ globals_callback
+};
+
+static struct libdecor_plugin *
+libdecor_plugin_new(struct libdecor *context)
+{
+ struct libdecor_plugin_cairo *plugin_cairo;
+ struct wl_display *wl_display;
+
+ plugin_cairo = zalloc(sizeof *plugin_cairo);
+ libdecor_plugin_init(&plugin_cairo->plugin,
+ context,
+ &cairo_plugin_iface);
+ plugin_cairo->context = context;
+
+ wl_list_init(&plugin_cairo->visible_frame_list);
+ wl_list_init(&plugin_cairo->seat_list);
+ wl_list_init(&plugin_cairo->output_list);
+
+ /* fetch cursor theme and size*/
+ if (!libdecor_get_cursor_settings(&plugin_cairo->cursor_theme_name,
+ &plugin_cairo->cursor_size)) {
+ plugin_cairo->cursor_theme_name = NULL;
+ plugin_cairo->cursor_size = 24;
+ }
+
+ /* define a sens-serif bold font at symbol size */
+ plugin_cairo->font = pango_font_description_new();
+ pango_font_description_set_family(plugin_cairo->font, "sans");
+ pango_font_description_set_weight(plugin_cairo->font, PANGO_WEIGHT_BOLD);
+ pango_font_description_set_size(plugin_cairo->font, SYM_DIM * PANGO_SCALE);
+
+ wl_display = libdecor_get_wl_display(context);
+ plugin_cairo->wl_registry = wl_display_get_registry(wl_display);
+ wl_registry_add_listener(plugin_cairo->wl_registry,
+ &registry_listener,
+ plugin_cairo);
+
+ plugin_cairo->globals_callback = wl_display_sync(wl_display);
+ wl_callback_add_listener(plugin_cairo->globals_callback,
+ &globals_callback_listener,
+ plugin_cairo);
+
+ return &plugin_cairo->plugin;
+}
+
+static struct libdecor_plugin_priority priorities[] = {
+ { NULL, LIBDECOR_PLUGIN_PRIORITY_MEDIUM }
+};
+
+LIBDECOR_EXPORT const struct libdecor_plugin_description
+libdecor_plugin_description = {
+ .api_version = LIBDECOR_PLUGIN_API_VERSION,
+ .capabilities = LIBDECOR_PLUGIN_CAPABILITY_BASE,
+ .description = "libdecor plugin using Cairo",
+ .priorities = priorities,
+ .constructor = libdecor_plugin_new,
+};
diff --git a/libdecor/src/plugins/dummy/libdecor-dummy.c b/libdecor/src/plugins/dummy/libdecor-dummy.c
new file mode 100644
index 000000000..1891b7e51
--- /dev/null
+++ b/libdecor/src/plugins/dummy/libdecor-dummy.c
@@ -0,0 +1,176 @@
+/*
+ * Copyright © 2021 Jonas Ådahl
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "config.h"
+
+#include "libdecor-plugin.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <wayland-cursor.h>
+
+#include "utils.h"
+
+struct libdecor_plugin_dummy {
+ struct libdecor_plugin plugin;
+ struct libdecor *context;
+};
+
+static void
+libdecor_plugin_dummy_destroy(struct libdecor_plugin *plugin)
+{
+ struct libdecor_plugin_dummy *plugin_dummy =
+ (struct libdecor_plugin_dummy *) plugin;
+
+ free(plugin_dummy);
+}
+
+static struct libdecor_frame *
+libdecor_plugin_dummy_frame_new(struct libdecor_plugin *plugin)
+{
+ struct libdecor_frame *frame;
+
+ frame = zalloc(sizeof *frame);
+
+ return frame;
+}
+
+static void
+libdecor_plugin_dummy_frame_free(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame)
+{
+}
+
+static void
+libdecor_plugin_dummy_frame_commit(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ struct libdecor_configuration *configuration)
+{
+}
+
+static void
+libdecor_plugin_dummy_frame_property_changed(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame)
+{
+}
+
+static void
+libdecor_plugin_dummy_frame_translate_coordinate(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ int content_x,
+ int content_y,
+ int *frame_x,
+ int *frame_y)
+{
+ *frame_x = content_x;
+ *frame_y = content_y;
+}
+
+static void
+libdecor_plugin_dummy_frame_popup_grab(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ const char *seat_name)
+{
+}
+
+static void
+libdecor_plugin_dummy_frame_popup_ungrab(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ const char *seat_name)
+{
+}
+
+static bool
+libdecor_plugin_dummy_configuration_get_content_size(
+ struct libdecor_plugin *plugin,
+ struct libdecor_configuration *configuration,
+ struct libdecor_frame *frame,
+ int *content_width,
+ int *content_height)
+{
+ return libdecor_configuration_get_window_size(configuration,
+ content_width,
+ content_height);
+}
+
+static bool
+libdecor_plugin_dummy_frame_get_window_size_for(
+ struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ int *window_width,
+ int *window_height)
+{
+ *window_width = libdecor_state_get_content_width (state);
+ *window_height = libdecor_state_get_content_height (state);
+ return true;
+}
+
+static struct libdecor_plugin_interface dummy_plugin_iface = {
+ .destroy = libdecor_plugin_dummy_destroy,
+
+ .frame_new = libdecor_plugin_dummy_frame_new,
+ .frame_free = libdecor_plugin_dummy_frame_free,
+ .frame_commit = libdecor_plugin_dummy_frame_commit,
+ .frame_property_changed = libdecor_plugin_dummy_frame_property_changed,
+ .frame_translate_coordinate =
+ libdecor_plugin_dummy_frame_translate_coordinate,
+ .frame_popup_grab = libdecor_plugin_dummy_frame_popup_grab,
+ .frame_popup_ungrab = libdecor_plugin_dummy_frame_popup_ungrab,
+
+ .configuration_get_content_size =
+ libdecor_plugin_dummy_configuration_get_content_size,
+ .frame_get_window_size_for =
+ libdecor_plugin_dummy_frame_get_window_size_for,
+};
+
+static struct libdecor_plugin *
+libdecor_plugin_new(struct libdecor *context)
+{
+ struct libdecor_plugin_dummy *plugin_dummy;
+
+ plugin_dummy = zalloc(sizeof *plugin_dummy);
+ plugin_dummy->plugin.iface = &dummy_plugin_iface;
+ plugin_dummy->context = context;
+
+ libdecor_notify_plugin_ready(context);
+
+ return &plugin_dummy->plugin;
+}
+
+static struct libdecor_plugin_priority priorities[] = {
+ { NULL, LIBDECOR_PLUGIN_PRIORITY_LOW }
+};
+
+LIBDECOR_EXPORT const struct libdecor_plugin_description
+libdecor_plugin_description = {
+ .api_version = LIBDECOR_PLUGIN_API_VERSION,
+ .description = "dummy libdecor plugin",
+ .priorities = priorities,
+ .constructor = libdecor_plugin_new,
+};
diff --git a/libdecor/src/plugins/gtk/libdecor-gtk.c b/libdecor/src/plugins/gtk/libdecor-gtk.c
new file mode 100644
index 000000000..d5261c153
--- /dev/null
+++ b/libdecor/src/plugins/gtk/libdecor-gtk.c
@@ -0,0 +1,2762 @@
+#include "config.h"
+
+#include <linux/input.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <wayland-cursor.h>
+
+#include "libdecor-plugin.h"
+#include "utils.h"
+#include "cursor-settings.h"
+
+#include <cairo/cairo.h>
+
+#ifndef APPLY_FLTK_CHANGES
+#define APPLY_FLTK_CHANGES 1
+#endif
+
+/* Note for FLTK: This header file changes location, while its content stays unchanged,
+ between the master and gtk_cairo_single branches */
+#include "../cairo/libdecor-cairo-blur.h"
+#include <poll.h>
+
+#include <gtk/gtk.h>
+
+static const size_t SHADOW_MARGIN = 24; /* graspable part of the border */
+
+static const char *cursor_names[] = {
+ "top_side",
+ "bottom_side",
+ "left_side",
+ "top_left_corner",
+ "bottom_left_corner",
+ "right_side",
+ "top_right_corner",
+ "bottom_right_corner"
+};
+
+enum header_element {
+ HDR_NONE,
+ HDR_HDR, /* entire header bar */
+ HDR_TITLE, /* label */
+ HDR_MIN,
+ HDR_MAX,
+ HDR_CLOSE,
+};
+
+struct header_element_data {
+ const char* name;
+ enum header_element type;
+ /* pointer to button or NULL if not found*/
+ GtkWidget *widget;
+ GtkStateFlags state;
+};
+
+static void
+find_widget_by_name(GtkWidget *widget, void *data)
+{
+ if (GTK_IS_WIDGET(widget)) {
+ char *style_ctx = gtk_style_context_to_string(
+ gtk_widget_get_style_context(widget),
+ GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE);
+ if (strstr(style_ctx, ((struct header_element_data *)data)->name)) {
+ ((struct header_element_data *)data)->widget = widget;
+ free(style_ctx);
+ return;
+ }
+ free(style_ctx);
+ }
+
+ if (GTK_IS_CONTAINER(widget)) {
+ /* recursively traverse container */
+ gtk_container_forall(GTK_CONTAINER(widget), &find_widget_by_name, data);
+ }
+}
+
+static struct header_element_data
+find_widget_by_type(GtkWidget *widget, enum header_element type)
+{
+ char* name = NULL;
+ switch (type) {
+ case HDR_HDR:
+ name = "headerbar.titlebar:";
+ break;
+ case HDR_TITLE:
+ name = "label.title:";
+ break;
+ case HDR_MIN:
+ name = ".minimize";
+ break;
+ case HDR_MAX:
+ name = ".maximize";
+ break;
+ case HDR_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[] =
+ {HDR_TITLE, HDR_MIN, HDR_MAX, HDR_CLOSE};
+
+ for (size_t i=0; i<4; i++) {
+ struct header_element_data elem =
+ find_widget_by_type(GTK_WIDGET(header_bar), elems[i]);
+ GtkAllocation allocation;
+#if APPLY_FLTK_CHANGES
+ if (elem.widget)
+#endif
+ gtk_widget_get_allocation(GTK_WIDGET(elem.widget), &allocation);
+ if (elem.widget && in_region(&allocation, &x, &y)) {
+ return elem;
+ }
+ }
+
+ struct header_element_data elem_none = { .widget=NULL};
+ return elem_none;
+}
+
+static bool
+streq(const char *str1,
+ const char *str2)
+{
+ if (!str1 && !str2)
+ return true;
+
+ if (str1 && str2)
+ return strcmp(str1, str2) == 0;
+
+ return false;
+}
+
+enum decoration_type {
+ DECORATION_TYPE_NONE,
+ DECORATION_TYPE_ALL,
+ DECORATION_TYPE_TITLE_ONLY
+};
+
+enum component {
+ NONE = 0,
+ SHADOW,
+ HEADER,
+};
+
+struct seat {
+ struct libdecor_plugin_gtk *plugin_gtk;
+
+ char *name;
+
+ struct wl_seat *wl_seat;
+ struct wl_pointer *wl_pointer;
+
+ struct wl_surface *cursor_surface;
+ struct wl_cursor *current_cursor;
+ int cursor_scale;
+ struct wl_list cursor_outputs;
+
+ struct wl_cursor_theme *cursor_theme;
+ /* cursors for resize edges and corners */
+ struct wl_cursor *cursors[ARRAY_LENGTH(cursor_names)];
+ struct wl_cursor *cursor_left_ptr;
+
+ struct wl_surface *pointer_focus;
+
+ int pointer_x, pointer_y;
+
+ uint32_t pointer_button_time_stamp;
+
+ uint32_t serial;
+
+ bool grabbed;
+
+ struct wl_list link;
+};
+
+struct output {
+ struct libdecor_plugin_gtk *plugin_gtk;
+
+ struct wl_output *wl_output;
+ uint32_t id;
+ int scale;
+
+ struct wl_list link;
+};
+
+struct buffer {
+ struct wl_buffer *wl_buffer;
+ bool in_use;
+ bool is_detached;
+
+ void *data;
+ size_t data_size;
+ int width;
+ int height;
+ int scale;
+ int buffer_width;
+ int buffer_height;
+};
+
+struct border_component {
+ enum component type;
+ struct wl_surface *wl_surface;
+ struct wl_subsurface *wl_subsurface;
+ struct buffer *buffer;
+ bool opaque;
+ struct wl_list output_list;
+ int scale;
+
+ struct wl_list child_components; /* border_component::link */
+ struct wl_list link; /* border_component::child_components */
+};
+
+struct surface_output {
+ struct output *output;
+ struct wl_list link;
+};
+
+struct cursor_output {
+ struct output *output;
+ struct wl_list link;
+};
+
+struct libdecor_frame_gtk {
+ struct libdecor_frame frame;
+
+ struct libdecor_plugin_gtk *plugin_gtk;
+
+ int content_width;
+ int content_height;
+
+ enum libdecor_window_state window_state;
+
+ enum decoration_type decoration_type;
+
+ char *title;
+
+ enum libdecor_capabilities capabilities;
+
+ struct border_component *active;
+
+ struct border_component *focus;
+ struct border_component *grab;
+
+ bool shadow_showing;
+ struct border_component shadow;
+
+ GtkWidget *window; /* offscreen window for rendering */
+ GtkWidget *header; /* header bar with widgets */
+ struct border_component headerbar;
+ struct header_element_data hdr_focus;
+
+ /* store pre-processed shadow tile */
+ cairo_surface_t *shadow_blur;
+
+ struct wl_list link;
+};
+
+struct libdecor_plugin_gtk {
+ struct libdecor_plugin plugin;
+
+ struct wl_callback *globals_callback;
+ struct wl_callback *globals_callback_shm;
+
+ struct libdecor *context;
+
+ struct wl_registry *wl_registry;
+ struct wl_subcompositor *wl_subcompositor;
+ struct wl_compositor *wl_compositor;
+
+ struct wl_shm *wl_shm;
+ struct wl_callback *shm_callback;
+ bool has_argb;
+
+ struct wl_list visible_frame_list;
+ struct wl_list seat_list;
+ struct wl_list output_list;
+
+ char *cursor_theme_name;
+ int cursor_size;
+
+ GtkApplication *app;
+ int double_click_time_ms;
+};
+
+static const char *libdecor_gtk_proxy_tag = "libdecor-gtk";
+
+static bool
+own_proxy(struct wl_proxy *proxy)
+{
+ 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);
+}
+
+#if APPLY_FLTK_CHANGES
+/*FLTK : remove this useless forward declaration */
+#else
+struct libdecor_plugin *
+libdecor_plugin_new(struct libdecor *context);
+#endif
+
+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->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);
+ }
+
+ wl_list_for_each_safe(output, output_tmp,
+ &plugin_gtk->output_list, link) {
+ wl_output_destroy(output->wl_output);
+ free(output);
+ }
+
+ wl_list_for_each_safe(frame, frame_tmp,
+ &plugin_gtk->visible_frame_list, link) {
+ wl_list_remove(&frame->link);
+ }
+
+ free(plugin_gtk->cursor_theme_name);
+
+ wl_shm_destroy(plugin_gtk->wl_shm);
+
+ wl_compositor_destroy(plugin_gtk->wl_compositor);
+ wl_subcompositor_destroy(plugin_gtk->wl_subcompositor);
+
+ g_object_unref(plugin_gtk->app);
+// free(plugin_gtk->gtk_theme_name);
+
+ free(plugin_gtk);
+}
+
+static struct libdecor_frame_gtk *
+libdecor_frame_gtk_new(struct libdecor_plugin_gtk *plugin_gtk)
+{
+ struct libdecor_frame_gtk *frame_gtk = zalloc(sizeof *frame_gtk);
+ cairo_t *cr;
+
+ static const int size = 128;
+ static const int boundary = 32;
+
+ frame_gtk->plugin_gtk = plugin_gtk;
+ frame_gtk->shadow_blur = cairo_image_surface_create(
+ CAIRO_FORMAT_ARGB32, size, size);
+ wl_list_insert(&plugin_gtk->visible_frame_list, &frame_gtk->link);
+
+ cr = cairo_create(frame_gtk->shadow_blur);
+ cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
+ cairo_set_source_rgba(cr, 0, 0, 0, 1);
+ cairo_rectangle(cr, boundary, boundary, size-2*boundary, size-2*boundary);
+ cairo_fill(cr);
+ cairo_destroy(cr);
+ blur_surface(frame_gtk->shadow_blur, 64);
+
+ return frame_gtk;
+}
+
+static int
+libdecor_plugin_gtk_get_fd(struct libdecor_plugin *plugin)
+{
+ struct libdecor_plugin_gtk *plugin_gtk =
+ (struct libdecor_plugin_gtk *) plugin;
+ struct wl_display *wl_display =
+ libdecor_get_wl_display(plugin_gtk->context);
+
+ return wl_display_get_fd(wl_display);
+}
+
+static int
+libdecor_plugin_gtk_dispatch(struct libdecor_plugin *plugin,
+ int timeout)
+{
+ struct libdecor_plugin_gtk *plugin_gtk =
+ (struct libdecor_plugin_gtk *) plugin;
+ struct wl_display *wl_display =
+ libdecor_get_wl_display(plugin_gtk->context);
+ struct pollfd fds[1];
+ int ret;
+ int dispatch_count = 0;
+
+ while (g_main_context_pending(NULL)) {
+ g_main_context_iteration(NULL, timeout > 0);
+ }
+
+ while (wl_display_prepare_read(wl_display) != 0)
+ dispatch_count += wl_display_dispatch_pending(wl_display);
+
+ if (wl_display_flush(wl_display) < 0 &&
+ errno != EAGAIN) {
+ wl_display_cancel_read(wl_display);
+ return -errno;
+ }
+
+ fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN };
+
+ ret = poll(fds, ARRAY_SIZE (fds), timeout);
+ if (ret > 0) {
+ if (fds[0].revents & POLLIN) {
+ wl_display_read_events(wl_display);
+ dispatch_count += wl_display_dispatch_pending(wl_display);
+ return dispatch_count;
+ } else {
+ wl_display_cancel_read(wl_display);
+ return dispatch_count;
+ }
+ } else if (ret == 0) {
+ wl_display_cancel_read(wl_display);
+ return dispatch_count;
+ } else {
+ wl_display_cancel_read(wl_display);
+ return -errno;
+ }
+}
+
+static 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 int
+create_anonymous_file(off_t size)
+{
+ int ret;
+
+ int fd;
+
+ fd = memfd_create("libdecor-gtk", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+
+ if (fd < 0)
+ return -1;
+
+ fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
+
+ do {
+ ret = posix_fallocate(fd, 0, size);
+ } while (ret == EINTR);
+ if (ret != 0) {
+ close(fd);
+ errno = ret;
+ return -1;
+ }
+
+ return fd;
+}
+
+static void
+toggle_maximized(struct libdecor_frame *const frame)
+{
+ if (!resizable((struct libdecor_frame_gtk *)frame))
+ return;
+
+ if (!(libdecor_frame_get_window_state(frame) &
+ LIBDECOR_WINDOW_STATE_MAXIMIZED))
+ libdecor_frame_set_maximized(frame);
+ else
+ libdecor_frame_unset_maximized(frame);
+}
+
+static void
+buffer_release(void *user_data,
+ struct wl_buffer *wl_buffer)
+{
+ struct buffer *buffer = user_data;
+
+ if (buffer->is_detached)
+ buffer_free(buffer);
+ else
+ buffer->in_use = false;
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+ buffer_release
+};
+
+static struct buffer *
+create_shm_buffer(struct libdecor_plugin_gtk *plugin_gtk,
+ int width,
+ int height,
+ bool opaque,
+ int scale)
+{
+ struct wl_shm_pool *pool;
+ int fd, size, buffer_width, buffer_height, stride;
+ void *data;
+ struct buffer *buffer;
+ enum wl_shm_format buf_fmt;
+
+ buffer_width = width * scale;
+ buffer_height = height * scale;
+ stride = buffer_width * 4;
+ size = stride * buffer_height;
+
+ fd = 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 = zalloc(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)
+{
+ struct surface_output *surface_output, *surface_output_tmp;
+
+ 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) {
+ 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;
+
+#if APPLY_FLTK_CHANGES
+ if (GTK_IS_WIDGET(frame_gtk->header))
+#endif
+ gtk_widget_destroy(frame_gtk->header);
+#if APPLY_FLTK_CHANGES
+ if (GTK_IS_WIDGET(frame_gtk->window))
+#endif
+ gtk_widget_destroy(frame_gtk->window);
+
+ free_border_component(&frame_gtk->headerbar);
+ free_border_component(&frame_gtk->shadow);
+ frame_gtk->shadow_showing = false;
+ if (frame_gtk->shadow_blur != NULL) {
+ cairo_surface_destroy(frame_gtk->shadow_blur);
+ frame_gtk->shadow_blur = NULL;
+ }
+
+ free(frame_gtk->title);
+
+ 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,
+ 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 ((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 = zalloc(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, 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);
+
+ /* 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.
+ */
+#if APPLY_FLTK_CHANGES
+ if (GTK_IS_WIDGET(frame_gtk->header))
+#else
+ if (frame_gtk->header)
+#endif
+ gtk_widget_destroy(frame_gtk->header);
+#if APPLY_FLTK_CHANGES
+ if (GTK_IS_WIDGET(frame_gtk->window))
+#else
+ if (frame_gtk->window)
+#endif
+ gtk_widget_destroy(frame_gtk->window);
+ 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, 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);
+
+#if APPLY_FLTK_CHANGES
+ const int title_height = GTK_IS_WIDGET(frame_gtk->header) ? gtk_widget_get_allocated_height(frame_gtk->header) : 0;
+#else
+ const int title_height = frame_gtk->header ? gtk_widget_get_allocated_height(frame_gtk->header) : 0;
+#endif
+
+ 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;
+#if APPLY_FLTK_CHANGES
+ *component_y = -title_height;
+#else
+ *component_y = -gtk_widget_get_allocated_height(frame_gtk->header);
+#endif
+ *component_width = gtk_widget_get_allocated_width(frame_gtk->header);
+#if APPLY_FLTK_CHANGES
+ *component_height = title_height;
+#else
+ *component_height = gtk_widget_get_allocated_height(frame_gtk->header);
+#endif
+ 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(struct libdecor_frame_gtk *frame_gtk,
+ cairo_t *cr,
+ cairo_surface_t *surface)
+{
+ {
+ /* background */
+ GtkAllocation allocation;
+ GtkStyleContext* style;
+#if APPLY_FLTK_CHANGES
+ if (!frame_gtk->header) return; // was deleted
+#endif
+ 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);
+ }
+
+ {
+ /* title */
+ GtkWidget *label;
+ GtkAllocation allocation;
+ cairo_surface_t *label_surface = NULL;
+ cairo_t *cr;
+
+ label = find_widget_by_type(frame_gtk->header, HDR_TITLE).widget;
+ gtk_widget_get_allocation(find_widget_by_type(frame_gtk->header, HDR_TITLE).widget, &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_draw(label, cr);
+ cairo_destroy(cr);
+ cairo_surface_destroy(label_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, HDR_MIN);
+ if (resizable(frame_gtk))
+ array_append(&buttons, &nbuttons, HDR_MAX);
+ if (closeable(frame_gtk))
+ array_append(&buttons, &nbuttons, HDR_CLOSE);
+
+ for (size_t i=0; i<nbuttons; i++) {
+ struct header_element_data elem;
+ GtkWidget *button;
+ GtkStyleContext* button_style;
+ GtkStateFlags style_state;
+
+ elem = find_widget_by_type(frame_gtk->header, buttons[i]);
+ button = elem.widget;
+ if (!button)
+ continue;
+ 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;
+ }
+ }
+
+ GtkAllocation allocation;
+ {
+ /* background */
+ gtk_widget_get_clip(button, &allocation);
+
+#if 0
+ // DBG
+ switch (i) {
+ case 0: cairo_set_source_rgb(cr, 1, 0, 0); break;
+ case 1: cairo_set_source_rgb(cr, 0, 1, 0); break;
+ case 2: cairo_set_source_rgb(cr, 0, 0, 1); break;
+ }
+ cairo_set_line_width(cr, 1);
+ cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height);
+ cairo_stroke_preserve(cr);
+#endif
+
+ 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 */
+ gchar *icon_name;
+ int scale;
+ GtkWidget *icon_widget;
+ GtkAllocation allocation_icon;
+ GtkIconInfo* icon_info;
+
+ switch (buttons[i]) {
+ case HDR_MIN:
+ icon_name = "window-minimize-symbolic";
+ break;
+ case HDR_MAX:
+ icon_name = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) ?
+ "window-restore-symbolic" :
+ "window-maximize-symbolic";
+ break;
+ case HDR_CLOSE:
+ icon_name = "window-close-symbolic";
+ break;
+ default:
+ icon_name = NULL;
+ break;
+ }
+
+ {
+ /* get scale */
+ double sx, sy;
+ 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 */
+ gint icon_width, icon_height;
+ gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &icon_width, &icon_height);
+ 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);
+ GdkPixbuf* icon_pixbuf = gtk_icon_info_load_symbolic_for_context(
+ icon_info, button_style, NULL, NULL);
+ cairo_surface_t* icon_surface =
+ gdk_cairo_surface_create_from_pixbuf(icon_pixbuf, scale, NULL);
+ gtk_style_context_restore(button_style);
+
+ gint width = 0, height = 0;
+ gtk_style_context_get(button_style, gtk_style_context_get_state(button_style),
+ "min-width", &width, "min-height", &height, NULL);
+
+ gint iconWidth, iconHeight;
+ if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &iconWidth, &iconHeight)) {
+ iconWidth = 16;
+ iconHeight = 16;
+ }
+ if (width < iconWidth) width = iconWidth;
+ if (height < iconHeight) height = iconHeight;
+
+ gint left = 0, top = 0, right = 0, bottom = 0;
+ GtkBorder border;
+ 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;
+
+ GtkBorder padding;
+ 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;
+ int iconXPosition = (width - iconWidth) / 2;
+ int iconYPosition = (height - iconHeight) / 2;
+
+ gtk_render_icon_surface(gtk_widget_get_style_context(icon_widget),
+ cr, icon_surface,
+ allocation.x+iconXPosition,
+ allocation.y+iconYPosition);
+ cairo_paint(cr);
+ cairo_surface_destroy(icon_surface);
+ g_object_unref(icon_pixbuf);
+ }
+ } /* symbol */
+ } /* loop buttons */
+ free(buttons);
+ }
+}
+
+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;
+
+ 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);
+
+ gtk_header_bar_set_title(GTK_HEADER_BAR(frame_gtk->header), libdecor_frame_get_title(&frame_gtk->frame));
+
+ /* set default height */
+ gtk_widget_get_preferred_height(frame_gtk->header, NULL, &allocation.height);
+
+#if ! APPLY_FLTK_CHANGES
+ int pref_width;
+ gtk_widget_get_preferred_width(frame_gtk->header, NULL, &pref_width);
+ libdecor_frame_set_min_content_size(&frame_gtk->frame, pref_width, 1);
+ gtk_widget_size_allocate(frame_gtk->header, &allocation);
+#else // new code for FLTK
+ int min_GTK_width = 15;
+ /* // Comment out to remove need for hiding (see below) warnings sent to stderr
+ // just enough space not to create: Gtk-WARNING **: Negative content width ……
+ min_GTK_width = 134;
+ if (!libdecor_frame_has_capability(&frame_gtk->frame, LIBDECOR_ACTION_FULLSCREEN))
+ min_GTK_width = 100; // no maximize button, window can be narrower*/
+ int current_min_w, current_min_h;
+ libdecor_frame_get_min_content_size(&frame_gtk->frame, &current_min_w, &current_min_h);
+ bool need_change = false;
+ if (current_min_w < min_GTK_width) {
+ current_min_w = min_GTK_width;
+ need_change = true;
+ }
+ if (current_min_h < 1) {
+ current_min_h = 1;
+ need_change = true;
+ }
+ if (need_change) {
+ libdecor_frame_set_min_content_size(&frame_gtk->frame, current_min_w, current_min_h);
+ if (!libdecor_frame_has_capability(&frame_gtk->frame, LIBDECOR_ACTION_FULLSCREEN)) {
+ libdecor_frame_set_max_content_size(&frame_gtk->frame, current_min_w, current_min_h);
+ }
+ }
+ int W = libdecor_frame_get_content_width(&frame_gtk->frame);
+ int H = libdecor_frame_get_content_height(&frame_gtk->frame);
+ need_change = false;
+ if (W < current_min_w) {W = current_min_w; need_change=true;}
+ if (H < current_min_h) {H = current_min_h; need_change=true;}
+ if (need_change) {
+ 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;
+ }
+ // hack to hide "Gtk-WARNING **: Negative content width" sent to stderr
+ static FILE *null_stderr = NULL;
+ if (!null_stderr) null_stderr = fopen("/dev/null", "w+");
+ FILE *old_stderr = stderr;
+ stderr = null_stderr;
+ gtk_widget_size_allocate(frame_gtk->header, &allocation); // warnings are sent here
+ stderr = old_stderr;
+#endif // end of new code for FLTK
+ 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 void
+set_window_geometry(struct libdecor_frame_gtk *frame_gtk)
+{
+ struct libdecor_frame *frame = &frame_gtk->frame;
+ int x = 0, y = 0, width = 0, height = 0;
+#if APPLY_FLTK_CHANGES
+ const int title_height = GTK_IS_WIDGET(frame_gtk->header) ? gtk_widget_get_allocated_height(frame_gtk->header) : 0;
+#else
+ const int title_height = gtk_widget_get_allocated_height(frame_gtk->header);
+#endif
+
+ switch (frame_gtk->decoration_type) {
+ case DECORATION_TYPE_NONE:
+ x = 0;
+ y = 0;
+ width = libdecor_frame_get_content_width(frame);
+ height = libdecor_frame_get_content_height(frame);
+ break;
+ case DECORATION_TYPE_ALL:
+ case DECORATION_TYPE_TITLE_ONLY:
+ x = 0;
+ y = -title_height;
+ width = libdecor_frame_get_content_width(frame);
+ height = libdecor_frame_get_content_height(frame) + title_height;
+ break;
+ }
+
+ libdecor_frame_set_window_geometry(frame, x, y, width, height);
+}
+
+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_window_geometry(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;
+
+ new_title = libdecor_frame_get_title(frame);
+#if APPLY_FLTK_CHANGES
+ if (frame_gtk->title && !streq(frame_gtk->title, new_title)) {
+#else
+ if (!streq(frame_gtk->title, new_title))
+#endif
+ redraw_needed = true;
+ free(frame_gtk->title);
+ if (new_title)
+ frame_gtk->title = strdup(new_title);
+ else
+ frame_gtk->title = NULL;
+#if APPLY_FLTK_CHANGES
+ }
+#endif
+
+ 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
+libdecor_plugin_gtk_frame_translate_coordinate(struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ int content_x,
+ int content_y,
+ int *frame_x,
+ int *frame_y)
+{
+ struct libdecor_frame_gtk *frame_gtk =
+ (struct libdecor_frame_gtk *) frame;
+
+ *frame_x = content_x;
+ *frame_y = content_y;
+
+#if APPLY_FLTK_CHANGES
+ *frame_y += (frame_gtk->header ? gtk_widget_get_allocated_height(frame_gtk->header) : 0);
+#else
+ *frame_y += gtk_widget_get_allocated_height(frame_gtk->header);
+#endif
+}
+
+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)
+ 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)
+ 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_configuration_get_content_size(
+ struct libdecor_plugin *plugin,
+ struct libdecor_configuration *configuration,
+ struct libdecor_frame *frame,
+ int *content_width,
+ int *content_height)
+{
+ struct libdecor_frame_gtk *frame_gtk =
+ (struct libdecor_frame_gtk *) frame;
+ int win_width, win_height;
+ enum libdecor_window_state state;
+
+ if (!libdecor_configuration_get_window_size(configuration,
+ &win_width,
+ &win_height))
+ return false;
+
+ if (!libdecor_configuration_get_window_state(configuration, &state))
+ return false;
+#if APPLY_FLTK_CHANGES
+ const int title_bar_height = GTK_IS_WIDGET(frame_gtk->header)? gtk_widget_get_allocated_height(frame_gtk->header) : 0;
+#else
+ const int title_bar_height = gtk_widget_get_allocated_height(frame_gtk->header);
+#endif
+
+ switch (window_state_to_decoration_type(state)) {
+ case DECORATION_TYPE_NONE:
+ *content_width = win_width;
+ *content_height = win_height;
+ break;
+ case DECORATION_TYPE_ALL:
+ case DECORATION_TYPE_TITLE_ONLY:
+ *content_width = win_width;
+ *content_height = win_height - title_bar_height;
+ break;
+ }
+
+ return true;
+}
+
+static bool
+libdecor_plugin_gtk_frame_get_window_size_for(
+ struct libdecor_plugin *plugin,
+ struct libdecor_frame *frame,
+ struct libdecor_state *state,
+ int *window_width,
+ int *window_height)
+{
+ enum libdecor_window_state window_state =
+ libdecor_state_get_window_state (state);
+
+#if APPLY_FLTK_CHANGES
+ struct libdecor_frame_gtk *frame_gtk = (struct libdecor_frame_gtk *)frame;
+ const int title_bar_height = (GTK_IS_WIDGET(frame_gtk->header) ? gtk_widget_get_allocated_height(
+ frame_gtk->header) : 0);
+#else
+ const int title_bar_height = gtk_widget_get_allocated_height(
+ ((struct libdecor_frame_gtk *)frame)->header);
+#endif
+
+ switch (window_state_to_decoration_type(window_state)) {
+ case DECORATION_TYPE_NONE:
+ *window_width = libdecor_state_get_content_width(state);
+ *window_height = libdecor_state_get_content_height(state);
+ break;
+ case DECORATION_TYPE_ALL:
+ case DECORATION_TYPE_TITLE_ONLY:
+ *window_width = libdecor_state_get_content_width(state);
+ *window_height =
+ libdecor_state_get_content_height(state) + title_bar_height;
+ break;
+ }
+
+ return true;
+}
+
+static struct libdecor_plugin_interface gtk_plugin_iface = {
+ .destroy = libdecor_plugin_gtk_destroy,
+ .get_fd = libdecor_plugin_gtk_get_fd,
+ .dispatch = libdecor_plugin_gtk_dispatch,
+
+ .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_translate_coordinate =
+ libdecor_plugin_gtk_frame_translate_coordinate,
+ .frame_popup_grab = libdecor_plugin_gtk_frame_popup_grab,
+ .frame_popup_ungrab = libdecor_plugin_gtk_frame_popup_ungrab,
+
+ .configuration_get_content_size =
+ libdecor_plugin_gtk_configuration_get_content_size,
+ .frame_get_window_size_for =
+ libdecor_plugin_gtk_frame_get_window_size_for,
+};
+
+static void
+init_wl_compositor(struct libdecor_plugin_gtk *plugin_gtk,
+ uint32_t id,
+ uint32_t version)
+{
+ plugin_gtk->wl_compositor =
+ wl_registry_bind(plugin_gtk->wl_registry,
+ id, &wl_compositor_interface,
+ MIN(version, 4));
+}
+
+static void
+init_wl_subcompositor(struct libdecor_plugin_gtk *plugin_gtk,
+ uint32_t id,
+ uint32_t version)
+{
+ plugin_gtk->wl_subcompositor =
+ wl_registry_bind(plugin_gtk->wl_registry,
+ id, &wl_subcompositor_interface, 1);
+}
+
+static void
+shm_format(void *user_data,
+ struct wl_shm *wl_shm,
+ uint32_t format)
+{
+ struct libdecor_plugin_gtk *plugin_gtk = user_data;
+
+ if (format == WL_SHM_FORMAT_ARGB8888)
+ plugin_gtk->has_argb = true;
+}
+
+struct wl_shm_listener shm_listener = {
+ shm_format
+};
+
+static void
+shm_callback(void *user_data,
+ struct wl_callback *callback,
+ uint32_t time)
+{
+ struct libdecor_plugin_gtk *plugin_gtk = user_data;
+ struct libdecor *context = plugin_gtk->context;
+
+ wl_callback_destroy(callback);
+ plugin_gtk->globals_callback_shm = NULL;
+
+ if (!plugin_gtk->has_argb) {
+ libdecor_notify_plugin_error(
+ context,
+ LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+ "Compositor is missing required shm format");
+ return;
+ }
+
+ libdecor_notify_plugin_ready(context);
+}
+
+static const struct wl_callback_listener shm_callback_listener = {
+ shm_callback
+};
+
+static void
+init_wl_shm(struct libdecor_plugin_gtk *plugin_gtk,
+ uint32_t id,
+ uint32_t version)
+{
+ struct libdecor *context = plugin_gtk->context;
+ struct wl_display *wl_display = libdecor_get_wl_display(context);
+
+ plugin_gtk->wl_shm =
+ wl_registry_bind(plugin_gtk->wl_registry,
+ id, &wl_shm_interface, 1);
+ wl_shm_add_listener(plugin_gtk->wl_shm, &shm_listener, plugin_gtk);
+
+ plugin_gtk->globals_callback_shm = wl_display_sync(wl_display);
+ wl_callback_add_listener(plugin_gtk->globals_callback_shm,
+ &shm_callback_listener,
+ plugin_gtk);
+}
+
+static void
+cursor_surface_enter(void *data,
+ struct wl_surface *wl_surface,
+ struct wl_output *wl_output)
+{
+ struct seat *seat = data;
+
+ if(own_output(wl_output)) {
+ struct cursor_output *cursor_output;
+ cursor_output = zalloc(sizeof *cursor_output);
+ cursor_output->output = wl_output_get_user_data(wl_output);
+ wl_list_insert(&seat->cursor_outputs, &cursor_output->link);
+ if (update_local_cursor(seat))
+ send_cursor(seat);
+ }
+}
+
+static void
+cursor_surface_leave(void *data,
+ struct wl_surface *wl_surface,
+ struct wl_output *wl_output)
+{
+ struct seat *seat = data;
+
+ if(own_output(wl_output)) {
+ struct cursor_output *cursor_output, *tmp;
+ wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) {
+ if (cursor_output->output->wl_output == wl_output) {
+ wl_list_remove(&cursor_output->link);
+ free(cursor_output);
+ }
+ }
+
+ if (update_local_cursor(seat))
+ send_cursor(seat);
+ }
+}
+
+static struct wl_surface_listener cursor_surface_listener = {
+ cursor_surface_enter,
+ cursor_surface_leave,
+};
+
+static void
+ensure_cursor_surface(struct seat *seat)
+{
+ struct wl_compositor *wl_compositor = seat->plugin_gtk->wl_compositor;
+
+ if (seat->cursor_surface)
+ return;
+
+ seat->cursor_surface = wl_compositor_create_surface(wl_compositor);
+ wl_surface_add_listener(seat->cursor_surface,
+ &cursor_surface_listener, seat);
+}
+
+static bool
+ensure_cursor_theme(struct seat *seat)
+{
+ struct libdecor_plugin_gtk *plugin_gtk = seat->plugin_gtk;
+ int scale = 1;
+ struct wl_cursor_theme *theme;
+ struct cursor_output *cursor_output;
+
+ wl_list_for_each(cursor_output, &seat->cursor_outputs, link) {
+ scale = MAX(scale, cursor_output->output->scale);
+ }
+
+ if (seat->cursor_theme && seat->cursor_scale == scale)
+ return false;
+
+ seat->cursor_scale = scale;
+ theme = wl_cursor_theme_load(plugin_gtk->cursor_theme_name,
+ plugin_gtk->cursor_size * scale,
+ plugin_gtk->wl_shm);
+ if (theme == NULL)
+ return false;
+
+ if (seat->cursor_theme)
+ wl_cursor_theme_destroy(seat->cursor_theme);
+
+ seat->cursor_theme = theme;
+
+ for (unsigned int i = 0; i < ARRAY_LENGTH(cursor_names); i++) {
+ seat->cursors[i] = wl_cursor_theme_get_cursor(
+ seat->cursor_theme,
+ cursor_names[i]);
+ }
+
+ seat->cursor_left_ptr = wl_cursor_theme_get_cursor(seat->cursor_theme,
+ "left_ptr");
+ seat->current_cursor = seat->cursor_left_ptr;
+
+ return true;
+}
+
+enum libdecor_resize_edge
+component_edge(const struct border_component *cmpnt,
+ const int pointer_x,
+ const int pointer_y,
+ const int margin)
+{
+ const bool top = pointer_y < margin;
+ const bool bottom = pointer_y > (cmpnt->buffer->height - margin);
+ const bool left = pointer_x < margin;
+ const bool right = pointer_x > (cmpnt->buffer->width - margin);
+
+ 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;
+
+ if (!own_surface(surface))
+ return;
+
+ 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)
+{
+ if (!surface)
+ return;
+
+ struct seat *seat = data;
+ struct libdecor_frame_gtk *frame_gtk;
+
+ if (!own_surface(surface))
+ return;
+
+ frame_gtk = wl_surface_get_user_data(surface);
+
+ seat->pointer_focus = NULL;
+ if (frame_gtk) {
+ frame_gtk->active = NULL;
+ frame_gtk->hdr_focus.widget = NULL;
+ 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;
+
+ 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);
+#if APPLY_FLTK_CHANGES
+ if (!frame_gtk->header) return;
+#endif
+ if (frame_gtk->active->type == HEADER) {
+ struct header_element_data 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);
+ }
+ else {
+ frame_gtk->hdr_focus.type = HDR_NONE;
+ }
+}
+
+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;
+
+ if (button == BTN_LEFT) {
+ if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+ enum libdecor_resize_edge edge =
+ LIBDECOR_RESIZE_EDGE_NONE;
+ switch (frame_gtk->active->type) {
+ case SHADOW:
+ edge = component_edge(frame_gtk->active,
+ seat->pointer_x,
+ seat->pointer_y,
+ SHADOW_MARGIN);
+ break;
+ case HEADER:
+ switch (frame_gtk->hdr_focus.type) {
+ case HDR_MIN:
+ case HDR_MAX:
+ case HDR_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->pointer_button_time_stamp <
+ (uint32_t)frame_gtk->plugin_gtk->double_click_time_ms) {
+ toggle_maximized(&frame_gtk->frame);
+ }
+ else if (moveable(frame_gtk)) {
+ seat->pointer_button_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);
+ }
+ }
+ else if (state == WL_POINTER_BUTTON_STATE_RELEASED) {
+ switch (frame_gtk->active->type) {
+ case HEADER:
+ switch (frame_gtk->hdr_focus.type) {
+ case HDR_MIN:
+ if (minimizable(frame_gtk))
+ libdecor_frame_set_minimized(
+ &frame_gtk->frame);
+ break;
+ case HDR_MAX:
+ toggle_maximized(&frame_gtk->frame);
+ break;
+ case HDR_CLOSE:
+ if (closeable(frame_gtk))
+#if APPLY_FLTK_CHANGES
+ {libdecor_frame_close(&frame_gtk->frame);
+ frame_gtk->header = NULL; // marks deletion
+ return;
+ }
+#else
+ libdecor_frame_close(&frame_gtk->frame);
+#endif
+ break;
+ default:
+ break;
+ }
+ /* unset active/clicked state once released */
+ frame_gtk->hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE;
+ draw_title_bar(frame_gtk);
+ libdecor_frame_toplevel_commit(&frame_gtk->frame);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ else if (button == BTN_RIGHT &&
+ state == WL_POINTER_BUTTON_STATE_PRESSED &&
+ seat->pointer_focus == frame_gtk->headerbar.wl_surface) {
+#if APPLY_FLTK_CHANGES
+ const int title_height = frame_gtk->header ? gtk_widget_get_allocated_height(frame_gtk->header) : 0;
+#else
+ const int title_height = gtk_widget_get_allocated_height(frame_gtk->header);
+#endif
+ libdecor_frame_show_window_menu(&frame_gtk->frame,
+ seat->wl_seat,
+ serial,
+ seat->pointer_x,
+ seat->pointer_y
+ -title_height);
+ }
+}
+
+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
+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;
+ }
+}
+
+static void
+seat_name(void *data,
+ struct wl_seat *wl_seat,
+ const char *name)
+{
+#if APPLY_FLTK_CHANGES
+ struct seat *seat = (struct seat*)data;
+ seat->name = strdup(name);
+#endif
+}
+
+static struct wl_seat_listener seat_listener = {
+ seat_capabilities,
+ seat_name
+};
+
+static void
+init_wl_seat(struct libdecor_plugin_gtk *plugin_gtk,
+ uint32_t id,
+ uint32_t version)
+{
+ struct seat *seat;
+
+ if (version < 3) {
+ char *err_msg;
+ asprintf(&err_msg,
+ "%s version 3 required but only version %i is available\n",
+ wl_seat_interface.name, version);
+ libdecor_notify_plugin_error(
+ plugin_gtk->context,
+ LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+ err_msg);
+ free(err_msg);
+ }
+
+ seat = zalloc(sizeof *seat);
+ seat->cursor_scale = 1;
+ seat->plugin_gtk = plugin_gtk;
+ wl_list_init(&seat->cursor_outputs);
+ wl_list_insert(&plugin_gtk->seat_list, &seat->link);
+ seat->wl_seat =
+ wl_registry_bind(plugin_gtk->wl_registry,
+ id, &wl_seat_interface, 3);
+ wl_seat_add_listener(seat->wl_seat, &seat_listener, seat);
+}
+
+static void
+output_geometry(void *data,
+ struct wl_output *wl_output,
+ int32_t x,
+ int32_t y,
+ int32_t physical_width,
+ int32_t physical_height,
+ int32_t subpixel,
+ const char *make,
+ const char *model,
+ int32_t transform)
+{
+}
+
+static void
+output_mode(void *data,
+ struct wl_output *wl_output,
+ uint32_t flags,
+ int32_t width,
+ int32_t height,
+ int32_t refresh)
+{
+}
+
+static void
+output_done(void *data,
+ struct wl_output *wl_output)
+{
+ struct output *output = data;
+ struct libdecor_frame_gtk *frame_gtk;
+ struct seat *seat;
+
+ wl_list_for_each(frame_gtk,
+ &output->plugin_gtk->visible_frame_list, link) {
+ bool updated = false;
+ updated |= redraw_scale(frame_gtk, &frame_gtk->shadow);
+ if (updated)
+ libdecor_frame_toplevel_commit(&frame_gtk->frame);
+ }
+ wl_list_for_each(seat, &output->plugin_gtk->seat_list, link) {
+ if (update_local_cursor(seat))
+ send_cursor(seat);
+ }
+}
+
+static void
+output_scale(void *data,
+ struct wl_output *wl_output,
+ int32_t factor)
+{
+ struct output *output = data;
+
+ output->scale = factor;
+}
+
+static struct wl_output_listener output_listener = {
+ output_geometry,
+ output_mode,
+ output_done,
+ output_scale
+};
+
+static void
+init_wl_output(struct libdecor_plugin_gtk *plugin_gtk,
+ uint32_t id,
+ uint32_t version)
+{
+ struct output *output;
+
+ if (version < 2) {
+ char *err_msg;
+ asprintf(&err_msg,
+ "%s version 2 required but only version %i is available\n",
+ wl_output_interface.name, version);
+ libdecor_notify_plugin_error(
+ plugin_gtk->context,
+ LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+ err_msg);
+ free(err_msg);
+ }
+
+ output = zalloc(sizeof *output);
+ output->plugin_gtk = plugin_gtk;
+ wl_list_insert(&plugin_gtk->output_list, &output->link);
+ output->id = id;
+ output->wl_output =
+ wl_registry_bind(plugin_gtk->wl_registry,
+ id, &wl_output_interface, 2);
+ wl_proxy_set_tag((struct wl_proxy *) output->wl_output,
+ &libdecor_gtk_proxy_tag);
+ wl_output_add_listener(output->wl_output, &output_listener, output);
+}
+
+static void
+registry_handle_global(void *user_data,
+ struct wl_registry *wl_registry,
+ uint32_t id,
+ const char *interface,
+ uint32_t version)
+{
+ struct libdecor_plugin_gtk *plugin_gtk = user_data;
+
+ if (strcmp(interface, "wl_compositor") == 0)
+ init_wl_compositor(plugin_gtk, id, version);
+ else if (strcmp(interface, "wl_subcompositor") == 0)
+ init_wl_subcompositor(plugin_gtk, id, version);
+ else if (strcmp(interface, "wl_shm") == 0)
+ init_wl_shm(plugin_gtk, id, version);
+ else if (strcmp(interface, "wl_seat") == 0)
+ init_wl_seat(plugin_gtk, id, version);
+ else if (strcmp(interface, "wl_output") == 0)
+ init_wl_output(plugin_gtk, id, version);
+}
+
+static void
+remove_surface_outputs(struct border_component *cmpnt, struct output *output)
+{
+ struct surface_output *surface_output;
+ wl_list_for_each(surface_output, &cmpnt->output_list, link) {
+ if (surface_output->output == output) {
+ wl_list_remove(&surface_output->link);
+ free(surface_output);
+ break;
+ }
+ }
+}
+
+static void
+output_removed(struct libdecor_plugin_gtk *plugin_gtk,
+ struct output *output)
+{
+ struct libdecor_frame_gtk *frame_gtk;
+ struct seat *seat;
+
+ wl_list_for_each(frame_gtk, &plugin_gtk->visible_frame_list, link) {
+ remove_surface_outputs(&frame_gtk->shadow, output);
+ }
+ wl_list_for_each(seat, &plugin_gtk->seat_list, link) {
+ struct cursor_output *cursor_output;
+ wl_list_for_each(cursor_output, &seat->cursor_outputs, link) {
+ if (cursor_output->output == output) {
+ wl_list_remove(&cursor_output->link);
+ free(cursor_output);
+ }
+ }
+ }
+
+ wl_list_remove(&output->link);
+ wl_output_destroy(output->wl_output);
+ free(output);
+}
+
+static void
+registry_handle_global_remove(void *user_data,
+ struct wl_registry *wl_registry,
+ uint32_t name)
+{
+ struct libdecor_plugin_gtk *plugin_gtk = user_data;
+ struct output *output;
+
+ wl_list_for_each(output, &plugin_gtk->output_list, link) {
+ if (output->id == name) {
+ output_removed(plugin_gtk, output);
+ break;
+ }
+ }
+}
+
+static const struct wl_registry_listener registry_listener = {
+ registry_handle_global,
+ registry_handle_global_remove
+};
+
+static bool
+has_required_globals(struct libdecor_plugin_gtk *plugin_gtk)
+{
+ if (!plugin_gtk->wl_compositor)
+ return false;
+ if (!plugin_gtk->wl_subcompositor)
+ return false;
+ if (!plugin_gtk->wl_shm)
+ return false;
+
+ return true;
+}
+
+static void
+globals_callback(void *user_data,
+ struct wl_callback *callback,
+ uint32_t time)
+{
+ struct libdecor_plugin_gtk *plugin_gtk = user_data;
+
+ wl_callback_destroy(callback);
+ plugin_gtk->globals_callback = NULL;
+
+ if (!has_required_globals(plugin_gtk)) {
+ struct libdecor *context = plugin_gtk->context;
+
+ libdecor_notify_plugin_error(
+ context,
+ LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
+ "Compositor is missing required globals");
+ }
+}
+
+static const struct wl_callback_listener globals_callback_listener = {
+ globals_callback
+};
+
+static void
+on_activate(GtkApplication* app, gpointer user_data) { }
+
+#if APPLY_FLTK_CHANGES
+/* FLTK: replace export by static which makes GTK plugin do as Cairo plugin does */
+static
+#else
+LIBDECOR_EXPORT
+#endif
+struct libdecor_plugin *
+libdecor_plugin_new(struct libdecor *context)
+{
+ struct libdecor_plugin_gtk *plugin_gtk;
+ struct wl_display *wl_display;
+
+ plugin_gtk = zalloc(sizeof *plugin_gtk);
+ libdecor_plugin_init(&plugin_gtk->plugin,
+ context,
+ &gtk_plugin_iface);
+ plugin_gtk->context = context;
+
+ wl_list_init(&plugin_gtk->visible_frame_list);
+ wl_list_init(&plugin_gtk->seat_list);
+ wl_list_init(&plugin_gtk->output_list);
+
+ /* fetch cursor theme and size*/
+ if (!libdecor_get_cursor_settings(&plugin_gtk->cursor_theme_name,
+ &plugin_gtk->cursor_size)) {
+ plugin_gtk->cursor_theme_name = NULL;
+ plugin_gtk->cursor_size = 24;
+ }
+
+ wl_display = libdecor_get_wl_display(context);
+ plugin_gtk->wl_registry = wl_display_get_registry(wl_display);
+ wl_registry_add_listener(plugin_gtk->wl_registry,
+ &registry_listener,
+ plugin_gtk);
+
+ plugin_gtk->globals_callback = wl_display_sync(wl_display);
+ wl_callback_add_listener(plugin_gtk->globals_callback,
+ &globals_callback_listener,
+ plugin_gtk);
+
+ /* setup GTK context */
+ plugin_gtk->app = gtk_application_new("org.libdecor.gtk", G_APPLICATION_NON_UNIQUE);
+ g_signal_connect (plugin_gtk->app, "activate", G_CALLBACK(on_activate), NULL);
+ g_application_run(G_APPLICATION(plugin_gtk->app), 0, NULL);
+
+ return &plugin_gtk->plugin;
+}
+
+static struct libdecor_plugin_priority priorities[] = {
+ { NULL, LIBDECOR_PLUGIN_PRIORITY_MEDIUM }
+};
+
+LIBDECOR_EXPORT const struct libdecor_plugin_description
+libdecor_plugin_description = {
+ .api_version = LIBDECOR_PLUGIN_API_VERSION,
+ .capabilities = LIBDECOR_PLUGIN_CAPABILITY_BASE,
+ .description = "GTK plugin",
+ .priorities = priorities,
+ .constructor = libdecor_plugin_new,
+};
diff --git a/libdecor/src/utils.h b/libdecor/src/utils.h
new file mode 100644
index 000000000..7892793b6
--- /dev/null
+++ b/libdecor/src/utils.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2017 Red Hat Inc.
+ *
+ * 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.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <stdlib.h>
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+
+#ifndef ARRAY_LENGTH
+#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
+#endif
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+static inline void *
+zalloc(size_t size)
+{
+ return calloc(1, size);
+}
+
+#endif /* UTILS_H */