/* * Copyright © 2017-2018 Red Hat Inc. * Copyright © 2018 Jonas Ådahl * Copyright © 2019 Christian Rauch * * 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 #include #include #include #include #include #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; *window_width = state->content_width; *window_height = state->content_height; if (frame_has_visible_client_side_decoration(frame) && plugin->priv->iface->frame_get_border_size) { int left, right, top, bottom; if (!plugin->priv->iface->frame_get_border_size( plugin, frame, NULL, &left, &right, &top, &bottom)) return false; *window_width += left + right; *window_height += top + bottom; } return true; } static void frame_set_window_geometry(struct libdecor_frame *frame, int32_t content_width, int32_t content_height) { struct libdecor_plugin *plugin = frame->priv->context->plugin; int x, y, width, height; int left, right, top, bottom; plugin->priv->iface->frame_get_border_size(plugin, frame, NULL, &left, &right, &top, &bottom); x = -left; y = -top; width = content_width + left + right; height = content_height + top + bottom; xdg_surface_set_window_geometry(frame->priv->xdg_surface, x, y, width, height); } LIBDECOR_EXPORT bool libdecor_configuration_get_content_size(struct libdecor_configuration *configuration, struct libdecor_frame *frame, int *width, int *height) { struct libdecor_plugin *plugin = frame->priv->context->plugin; /* get configured toplevel dimensions */ if (!configuration->has_size) return false; if (configuration->window_width == 0 || configuration->window_height == 0) return false; *width = configuration->window_width; *height = configuration->window_height; /* remove plugin-specific border size */ if (frame_has_visible_client_side_decoration(frame) && plugin->priv->iface->frame_get_border_size) { int left, right, top, bottom; /* Update window state for correct border size calculation */ frame->priv->window_state = configuration->window_state; if (!plugin->priv->iface->frame_get_border_size( plugin, frame, configuration, &left, &right, &top, &bottom)) return false; *width -= (left + right); *height -= (top + bottom); } /* constrain content dimensions manually */ if (state_is_floating(configuration->window_state)) { constrain_content_size(frame, width, height); } return true; } 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); } frame_set_window_geometry(frame, 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; *frame_x = content_x; *frame_y = content_y; if (frame_has_visible_client_side_decoration(frame) && plugin->priv->iface->frame_get_border_size) { int left, top; plugin->priv->iface->frame_get_border_size(plugin, frame, NULL, &left, NULL, &top, NULL); *frame_x += left; *frame_y += top; } } 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_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_get_min_content_size(const struct libdecor_frame *frame, int *content_width, int *content_height) { struct libdecor_frame_private *frame_priv = frame->priv; *content_width = frame_priv->state.content_limits.min_width; *content_height = frame_priv->state.content_limits.min_height; } LIBDECOR_EXPORT void libdecor_frame_get_max_content_size(const struct libdecor_frame *frame, int *content_width, int *content_height) { struct libdecor_frame_private *frame_priv = frame->priv; *content_width = frame_priv->state.content_limits.max_width; *content_height = frame_priv->state.content_limits.max_height; } 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); } frame_set_window_geometry(frame, frame_priv->content_width, frame_priv->content_height); if (configuration) { xdg_surface_ack_configure(frame_priv->xdg_surface, configuration->serial); } } static void do_map(struct libdecor_frame *frame) { struct libdecor_frame_private *frame_priv = frame->priv; frame_priv->pending_map = false; wl_surface_commit(frame_priv->wl_surface); } 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)) { const char *force_csd = getenv("LIBDECOR_FORCE_CSD"); if (force_csd && atoi(force_csd)) { return; } context->decoration_manager = wl_registry_bind( context->wl_registry, id, &zxdg_decoration_manager_v1_interface, MIN(version,2)); } } static void 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 bool check_symbol_conflicts(const struct libdecor_plugin_description *plugin_description) { char * const *symbol; symbol = plugin_description->conflicting_symbols; while (*symbol) { dlerror(); dlsym (RTLD_DEFAULT, *symbol); if (!dlerror()) return false; symbol++; } return true; } 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; } if (!check_symbol_conflicts(plugin_description)) { 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)); } else { 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, ®istry_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; }