diff options
Diffstat (limited to 'libdecor/src/libdecor.c')
| -rw-r--r-- | libdecor/src/libdecor.c | 1664 |
1 files changed, 1664 insertions, 0 deletions
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, + ®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; +} |
