pax_global_header00006660000000000000000000000064145513163720014521gustar00rootroot0000000000000052 comment=7807ae3480f5c6a37c5e8505d94af1e764aaf704 libdecor-0.2.2/000077500000000000000000000000001455131637200133055ustar00rootroot00000000000000libdecor-0.2.2/.gitignore000066400000000000000000000000071455131637200152720ustar00rootroot00000000000000build/ libdecor-0.2.2/.gitlab-ci.yml000066400000000000000000000042571455131637200157510ustar00rootroot00000000000000include: - remote: 'https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/3d03cccd770c04e63b40325b42223495274d6a1d/templates/fedora.yml' stages: - prepare - build .libdecor.fedora:34@common: variables: FDO_DISTRIBUTION_VERSION: 34 BASE_TAG: '2023-07-04.1' FDO_UPSTREAM_REPO: jadahl/libdecor FDO_DISTRIBUTION_PACKAGES: | pkgconfig(wayland-client) pkgconfig(dbus-1) pkgconfig(wayland-protocols) pkgconfig(cairo) pkgconfig(pangocairo) pkgconfig(wayland-cursor) pkgconfig(xkbcommon) pkgconfig(egl) pkgconfig(gl) pkgconfig(wayland-egl) pkgconfig(gtk+-3.0) meson FDO_DISTRIBUTION_EXEC: | dnf group install -y 'Development Tools' \ 'C Development Tools and Libraries' && dnf install -y https://kojipkgs.fedoraproject.org//packages/wayland-protocols/1.32/1.fc39/noarch/wayland-protocols-devel-1.32-1.fc39.noarch.rpm && dnf clean all default: # Cancel jobs if newer commits are pushed to the branch interruptible: true # Auto-retry jobs in case of infra failures retry: max: 1 when: - 'runner_system_failure' - 'stuck_or_timeout_failure' - 'scheduler_failure' - 'api_failure' workflow: rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' - if: $CI_PIPELINE_SOURCE == 'push' .pipeline-guard: &pipeline-guard rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_COMMIT_TAG' - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' - when: 'manual' .libdecor.fedora:34@x86_64: extends: .libdecor.fedora:34@common variables: FDO_DISTRIBUTION_TAG: "x86_64-${BASE_TAG}" build-fedora-container@x86_64: extends: - .fdo.container-build@fedora@x86_64 - .libdecor.fedora:34@x86_64 stage: prepare variables: GIT_STRATEGY: none <<: *pipeline-guard .build-libdecor: extends: - .fdo.distribution-image@fedora stage: build script: - meson . build -Dbuildtype=debugoptimized -Dwarning_level=2 --werror --prefix /usr - ninja -C build - ninja -C build install build-libdecor@x86_64: extends: - .build-libdecor - .libdecor.fedora:34@x86_64 needs: - build-fedora-container@x86_64 libdecor-0.2.2/LICENSE000066400000000000000000000020141455131637200143070ustar00rootroot00000000000000MIT License 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 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. libdecor-0.2.2/README.md000066400000000000000000000062101455131637200145630ustar00rootroot00000000000000# libdecor - A client-side decorations library for Wayland client libdecor is a library that can help Wayland clients draw window decorations for them. It aims to provide multiple backends that implements the decoration drawing. ## Dependencies Required: - `meson` >= 0.47 - `ninja` - `wayland-client` >= 1.18 - `wayland-protocols` >= 1.15 - `wayland-cursor` - `cairo` - `pangocairo` Recommended: - `dbus-1` (to query current cursor theme) Optional - `egl` (to build EGL example) - `opengl` - `xkbcommon` (to build cairo demo) - `gtk+-3.0` (for GTK plugin) Install via apt: `sudo apt install meson libwayland-dev wayland-protocols libpango1.0-dev libdbus-1-dev libegl-dev libopengl-dev libxkbcommon-dev libgtk-3-dev` Install via dnf: `sudo dnf install meson wayland-devel wayland-protocols-devel pango-devel dbus-devel mesa-libEGL-devel libglvnd-devel libxkbcommon-devel gtk3-devel` Newer meson versions can be installed via pip: `pip3 install -U meson`. ## Build & Install ### Quick Start To build and run the example program: 1. `meson build -Dinstall_demo=true && meson compile -C build` 2. `meson devenv -C build libdecor-demo` ### Release Builds The library and default plugins can be built and installed via: 1. `meson build --buildtype release` 2. `meson install -C build` where `build` is the build directory that will be created during this process. This will install by default to `/usr/local/`. To change this set the `prefix` during built, e.g. `meson build --buildtype release -Dprefix=$HOME/.local/`. Plugins will be installed into the same directory and from thereon will be selected automatically depending on their precedence. This behaviour can be overridden at runtime by setting the environment variable `LIBDECOR_PLUGIN_DIR` and pointing it to a directory with a valid plugin. ### Debug and Development Builds During development and when debugging, it is recommended to enable the AddressSanitizer and increase the warning level: 1. `meson build -Dinstall_demo=true -Db_sanitize=address -Dwarning_level=3` 2. `meson compile -C build` You may have to install `libasan6` (apt) or `libasan` (dnf). Otherwise linking will fail. By default `libdecor` will look for plugins in the target directory of the installation. Therefore, when running the demos directly from the `build` directory, no plugins will be found and the fallback plugin without any decorations will be used. On Meson 0.58.0 and above, this can be corrected using `devenv`, i.e., to run the demo: `meson devenv -C build libdecor-demo` On older Meson versions, the search path for plugins can be overridden by the environment variable `LIBDECOR_PLUGIN_DIR`. To use the `cairo` plugin, point to the plugin directory: `export LIBDECOR_PLUGIN_DIR=build/src/plugins/cairo/` and run the demo: `./build/demo/libdecor-demo`. ### Code of Conduct libdecor follows the Contributor Covenant, found at: https://www.freedesktop.org/wiki/CodeOfConduct Please conduct yourself in a respectful and civilised manner when interacting with community members on mailing lists, IRC, or bug trackers. The community represents the project as a whole, and abusive or bullying behaviour is not tolerated by the project. libdecor-0.2.2/config.h.meson000066400000000000000000000005131455131637200160420ustar00rootroot00000000000000/* Version number of package */ #mesondefine VERSION /* Plugin directiory path */ #mesondefine LIBDECOR_PLUGIN_DIR /* Plugin API version */ #mesondefine LIBDECOR_PLUGIN_API_VERSION #mesondefine HAS_DBUS #mesondefine HAVE_MKOSTEMP #mesondefine HAVE_POSIX_FALLOCATE #mesondefine HAVE_MEMFD_CREATE #mesondefine HAVE_XDG_SHELL_V6 libdecor-0.2.2/demo/000077500000000000000000000000001455131637200142315ustar00rootroot00000000000000libdecor-0.2.2/demo/c++-demo.cc000066400000000000000000000222171455131637200160360ustar00rootroot00000000000000/* * Copyright © 2011 Benjamin Franzke * Copyright © 2010 Intel Corporation * Copyright © 2018-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 #include #include #include #include #include #include #include #include #include #include #include "libdecor.h" #include "utils.h" #include "cursor-settings.h" extern "C" { #include "os-compatibility.h" } #include "xdg-shell-client-protocol.h" static const int DEFAULT_WIDTH = 400; static const int DEFAULT_HEIGHT = 400; static struct wl_compositor *wl_compositor; static struct wl_shm *wl_shm; static bool has_xrgb = false; using std::cerr; using std::endl; class Buffer { public: Buffer(struct wl_buffer *wl_buffer, int width, int height, void *data, size_t data_size) : wl_buffer(wl_buffer), width(width), height(height), data(data), data_size(data_size) { wl_buffer_add_listener(this->wl_buffer, &this->buffer_listener, this); this->buffer_listener.release = buffer_release; } virtual ~Buffer() { wl_buffer_destroy(this->wl_buffer); munmap(this->data, this->data_size); } static Buffer * create_shm_buffer(int width, int height, uint32_t format) { struct wl_shm_pool *pool; int fd, size, stride; void *data; struct wl_buffer *wl_buffer; stride = width * 4; size = stride * height; fd = os_create_anonymous_file(size); if (fd < 0) { cerr << "Creating a buffer file for " << size << " B failed: " << strerror(errno) << endl; return NULL; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { cerr << "mmap failed: " << strerror(errno) << endl; close(fd); return NULL; } pool = wl_shm_create_pool(wl_shm, fd, size); wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); wl_shm_pool_destroy(pool); close(fd); return new Buffer(wl_buffer, width, height, data, size); } void paint_buffer(enum libdecor_window_state window_state) { uint32_t *pixels = reinterpret_cast(this->data); uint32_t color; int y, x; size_t off; if (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) { color = 0xffbcbcbc; } else { color = 0xff8e8e8e; } for (y = 0; y < this->height; y++) { for (x = 0; x < this->width; x++) { off = x + y * this->width; pixels[off] = color; } } } struct wl_buffer * get_buffer() { return this->wl_buffer; } private: static void buffer_release(void *user_data, struct wl_buffer *wl_buffer) { Buffer *buffer = reinterpret_cast(user_data); delete buffer; } struct wl_buffer *wl_buffer; struct wl_buffer_listener buffer_listener; int width; int height; void *data; size_t data_size; }; class Window { public: Window(struct libdecor *context, struct wl_compositor *wl_compositor) : floating_width(DEFAULT_WIDTH), floating_height(DEFAULT_HEIGHT) { this->wl_surface = wl_compositor_create_surface(wl_compositor); this->libdecor_frame_iface.configure = handle_configure; this->libdecor_frame_iface.close = handle_close; this->libdecor_frame_iface.commit = handle_commit; this->libdecor_frame_iface.dismiss_popup = handle_dismiss_popup; this->frame = libdecor_decorate(context, this->wl_surface, &libdecor_frame_iface, this); libdecor_frame_set_app_id(this->frame, "libdecor-c++-demo"); libdecor_frame_set_title(this->frame, "libdecor C++ demo"); libdecor_frame_map(this->frame); } void redraw() { Buffer *buffer; buffer = Buffer::create_shm_buffer(this->configured_width, this->configured_height, WL_SHM_FORMAT_XRGB8888); buffer->paint_buffer(this->window_state); wl_surface_attach(this->wl_surface, buffer->get_buffer(), 0, 0); wl_surface_damage_buffer(this->wl_surface, 0, 0, this->configured_width, this->configured_height); wl_surface_commit(this->wl_surface); } private: void configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration) { int width = 0, height = 0; enum libdecor_window_state window_state; struct libdecor_state *state; /* Update window state first for the correct calculations */ if (!libdecor_configuration_get_window_state(configuration, &window_state)) window_state = LIBDECOR_WINDOW_STATE_NONE; this->window_state = window_state; libdecor_configuration_get_content_size(configuration, frame, &width, &height); width = (width == 0) ? this->floating_width : width; height = (height == 0) ? this->floating_height : height; this->configured_width = width; this->configured_height = height; state = libdecor_state_new(width, height); libdecor_frame_commit(frame, state, configuration); libdecor_state_free(state); /* store floating dimensions */ if (libdecor_frame_is_floating(this->frame)) { this->floating_width = width; this->floating_height = height; } this->redraw(); } static void handle_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data) { Window *window = reinterpret_cast(user_data); window->configure(frame, configuration); } static void handle_close(struct libdecor_frame *frame, void *user_data) { exit(EXIT_SUCCESS); } void commit() { wl_surface_commit(this->wl_surface); } static void handle_commit(struct libdecor_frame *frame, void *user_data) { Window *window = reinterpret_cast(user_data); window->commit(); } static void handle_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) { } struct wl_surface *wl_surface; struct libdecor_frame *frame; struct libdecor_frame_interface libdecor_frame_iface; int configured_width; int configured_height; enum libdecor_window_state window_state; int floating_width; int floating_height; }; static Window *window; static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { if (format == WL_SHM_FORMAT_XRGB8888) has_xrgb = true; } static struct wl_shm_listener shm_listener = { shm_format }; static void registry_handle_global(void *user_data, struct wl_registry *wl_registry, uint32_t id, const char *interface, uint32_t version) { if (strcmp(interface, "wl_compositor") == 0) { if (version < 4) { cerr << "wl_compositor version >= 4 required" << endl; exit(EXIT_FAILURE); } wl_compositor = reinterpret_cast( wl_registry_bind(wl_registry, id, &wl_compositor_interface, 4)); } else if (strcmp(interface, "wl_shm") == 0) { wl_shm = reinterpret_cast( wl_registry_bind(wl_registry, id, &wl_shm_interface, 1)); wl_shm_add_listener(wl_shm, &shm_listener, NULL); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static void handle_error(struct libdecor *context, enum libdecor_error error, const char *message) { cerr << "Caught error (" << error << "): " << message << endl; exit(EXIT_FAILURE); } static struct libdecor_interface libdecor_iface = { handle_error, }; int main(int argc, char **argv) { struct wl_display *wl_display; struct wl_registry *wl_registry; struct libdecor *context; wl_display = wl_display_connect(NULL); if (!wl_display) { cerr << "No Wayland connection" << endl; return EXIT_FAILURE; } wl_registry = wl_display_get_registry(wl_display); wl_registry_add_listener(wl_registry, ®istry_listener, NULL); wl_display_roundtrip(wl_display); wl_display_roundtrip(wl_display); if (!has_xrgb) { cerr << "No XRGB shm format" << endl; return EXIT_FAILURE; } context = libdecor_new(wl_display, &libdecor_iface); window = new Window(context, wl_compositor); while (libdecor_dispatch(context, -1) >= 0); delete window; return EXIT_SUCCESS; } libdecor-0.2.2/demo/demo.c000066400000000000000000000756521455131637200153400ustar00rootroot00000000000000/* * Copyright © 2011 Benjamin Franzke * Copyright © 2010 Intel Corporation * 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 #include #include #include #include "libdecor.h" #include "utils.h" #include "cursor-settings.h" #include "os-compatibility.h" #include "xdg-shell-client-protocol.h" struct window; static const size_t chk = 16; static const int DEFAULT_WIDTH = 30*chk; static const int DEFAULT_HEIGHT = 20*chk; static const int POPUP_WIDTH = 100; static const int POPUP_HEIGHT = 300; static const char *proxy_tag = "libdecor-demo"; static const char *titles[] = { "Hello!", "Hallå!", "Привет!", "Γειά σου!", "שלום!", "你好!", "สวัสดี!", "こんにちは!", "👻❤️🤖➕🍰", }; static const size_t N_TITLES = ARRAY_SIZE(titles); static bool own_proxy(struct wl_proxy *proxy) { return (wl_proxy_get_tag(proxy) == &proxy_tag); } static bool own_output(struct wl_output *output) { if (!output) return false; return own_proxy((struct wl_proxy *) output); } struct buffer { struct wl_buffer *wl_buffer; void *data; size_t data_size; }; struct popup { struct wl_surface *wl_surface; struct xdg_surface *xdg_surface; struct xdg_popup *xdg_popup; struct xdg_surface *parent; struct seat *seat; struct window *window; }; struct window { struct wl_surface *wl_surface; struct buffer *buffer; struct libdecor_frame *frame; int content_width; int content_height; int configured_width; int configured_height; int floating_width; int floating_height; enum libdecor_window_state window_state; struct wl_list outputs; int scale; struct popup *popup; size_t title_index; }; struct seat { struct wl_seat *wl_seat; struct wl_keyboard *wl_keyboard; struct wl_pointer *wl_pointer; struct wl_list link; struct wl_list pointer_outputs; struct wl_cursor_theme *cursor_theme; struct wl_cursor *left_ptr_cursor; struct wl_surface *cursor_surface; struct wl_surface *pointer_focus; int pointer_scale; uint32_t serial; wl_fixed_t pointer_sx; wl_fixed_t pointer_sy; char *name; struct xkb_context *xkb_context; struct xkb_state *xkb_state; }; struct output { uint32_t id; struct wl_output *wl_output; int scale; struct wl_list link; }; struct window_output { struct output* output; struct wl_list link; }; struct pointer_output { struct output* output; struct wl_list link; }; static struct wl_compositor *wl_compositor; static struct wl_shm *wl_shm; static struct xdg_wm_base *xdg_wm_base; static struct wl_list seats; static struct wl_list outputs; static bool has_xrgb = false; static struct window *window; static void redraw(struct window *window); static void constrain_content_size(const struct libdecor_frame *frame, int *width, int *height) { int min_width, min_height, max_width, max_height; libdecor_frame_get_min_content_size(frame, &min_width, &min_height); libdecor_frame_get_max_content_size(frame, &max_width, &max_height); if (min_width > 0) *width = MAX(min_width, *width); if (max_width > 0) *width = MIN(*width, max_width); if (min_height > 0) *height = MAX(min_height, *height); if (max_height > 0) *height = MIN(*height, max_height); } static void resize(struct window *window, int width, int height) { struct libdecor_state *state; if (!libdecor_frame_is_floating(window->frame)) { printf("... ignoring in non-floating mode\n"); return; } constrain_content_size(window->frame, &width, &height); /* commit changes to decorations */ state = libdecor_state_new(width, height); libdecor_frame_commit(window->frame, state, NULL); libdecor_state_free(state); /* force redraw of content and commit */ window->configured_width = width; window->configured_height = height; /* store floating dimensions */ window->floating_width = width; window->floating_height = height; redraw(window); } static struct buffer * create_shm_buffer(int width, int height, uint32_t format); static void update_scale(struct window *window) { int scale = 1; struct window_output *window_output; wl_list_for_each(window_output, &window->outputs, link) { scale = MAX(scale, window_output->output->scale); } if (scale != window->scale) { window->scale = scale; redraw(window); } } static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { if (format == WL_SHM_FORMAT_XRGB8888) has_xrgb = true; } static struct wl_shm_listener shm_listener = { shm_format }; static void try_update_cursor(struct seat *seat); static void cursor_surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct seat *seat = data; struct pointer_output *pointer_output; if (!own_output(wl_output)) return; pointer_output = zalloc(sizeof *pointer_output); pointer_output->output = wl_output_get_user_data(wl_output); wl_list_insert(&seat->pointer_outputs, &pointer_output->link); try_update_cursor(seat); } static void cursor_surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct seat *seat = data; struct pointer_output *pointer_output, *tmp; wl_list_for_each_safe(pointer_output, tmp, &seat->pointer_outputs, link) { if (pointer_output->output->wl_output == wl_output) { wl_list_remove(&pointer_output->link); free(pointer_output); } } } static struct wl_surface_listener cursor_surface_listener = { cursor_surface_enter, cursor_surface_leave, }; static void init_cursors(struct seat *seat) { char *name; int size; struct wl_cursor_theme *theme; if (!libdecor_get_cursor_settings(&name, &size)) { name = NULL; size = 24; } size *= seat->pointer_scale; theme = wl_cursor_theme_load(name, size, wl_shm); free(name); if (theme != NULL) { if (seat->cursor_theme) wl_cursor_theme_destroy(seat->cursor_theme); seat->cursor_theme = theme; } if (seat->cursor_theme) seat->left_ptr_cursor = wl_cursor_theme_get_cursor(seat->cursor_theme, "left_ptr"); if (!seat->cursor_surface) { seat->cursor_surface = wl_compositor_create_surface( wl_compositor); wl_surface_add_listener(seat->cursor_surface, &cursor_surface_listener, seat); } } static void set_cursor(struct seat *seat) { struct wl_cursor *wl_cursor; struct wl_cursor_image *image; struct wl_buffer *buffer; const int scale = seat->pointer_scale; if (!seat->cursor_theme) return; wl_cursor = seat->left_ptr_cursor; image = wl_cursor->images[0]; buffer = wl_cursor_image_get_buffer(image); wl_pointer_set_cursor(seat->wl_pointer, seat->serial, seat->cursor_surface, image->hotspot_x / scale, image->hotspot_y / scale); wl_surface_attach(seat->cursor_surface, buffer, 0, 0); wl_surface_set_buffer_scale(seat->cursor_surface, scale); wl_surface_damage_buffer(seat->cursor_surface, 0, 0, image->width, image->height); wl_surface_commit(seat->cursor_surface); } static void try_update_cursor(struct seat *seat) { struct pointer_output *pointer_output; int scale = 1; wl_list_for_each(pointer_output, &seat->pointer_outputs, link) { scale = MAX(scale, pointer_output->output->scale); } if (scale != seat->pointer_scale) { seat->pointer_scale = scale; init_cursors(seat); set_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; seat->pointer_focus = surface; seat->serial = serial; if (surface != window->wl_surface) return; set_cursor(seat); seat->pointer_sx = surface_x; seat->pointer_sy = surface_y; } static void pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { struct seat *seat = data; if (seat->pointer_focus == surface) 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; seat->pointer_sx = surface_x; seat->pointer_sy = surface_y; } static struct xdg_positioner * create_positioner(struct seat *seat) { struct xdg_positioner *positioner; enum xdg_positioner_constraint_adjustment constraint_adjustment; int x, y; positioner = xdg_wm_base_create_positioner(xdg_wm_base); xdg_positioner_set_size(positioner, POPUP_WIDTH, POPUP_HEIGHT); libdecor_frame_translate_coordinate(window->frame, wl_fixed_to_int(seat->pointer_sx), wl_fixed_to_int(seat->pointer_sy), &x, &y); xdg_positioner_set_anchor_rect(positioner, x, y, 1, 1); constraint_adjustment = (XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X); xdg_positioner_set_constraint_adjustment (positioner, constraint_adjustment); xdg_positioner_set_anchor (positioner, XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT); xdg_positioner_set_gravity (positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); return positioner; } static void xdg_popup_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) { } static void popup_destroy(struct popup *popup) { libdecor_frame_popup_ungrab(popup->window->frame, popup->seat->name); xdg_popup_destroy(popup->xdg_popup); xdg_surface_destroy(popup->xdg_surface); wl_surface_destroy(popup->wl_surface); popup->window->popup = NULL; free(popup); } static void xdg_popup_done(void *data, struct xdg_popup *xdg_popup) { struct popup *popup = data; popup_destroy(popup); } static const struct xdg_popup_listener xdg_popup_listener = { xdg_popup_configure, xdg_popup_done, }; static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { struct popup *popup = data; uint32_t *pixels; struct buffer *buffer; int y; buffer = create_shm_buffer(POPUP_WIDTH, POPUP_HEIGHT, WL_SHM_FORMAT_XRGB8888); pixels = buffer->data; for (y = 0; y < POPUP_HEIGHT; y++) { int x; for (x = 0; x < POPUP_WIDTH; x++) pixels[y * POPUP_WIDTH + x] = 0xff4455ff; } wl_surface_attach(popup->wl_surface, buffer->wl_buffer, 0, 0); wl_surface_set_buffer_scale(window->wl_surface, window->scale); wl_surface_damage(window->wl_surface, 0, 0, POPUP_WIDTH, POPUP_HEIGHT); xdg_surface_ack_configure(popup->xdg_surface, serial); wl_surface_commit(popup->wl_surface); } static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_configure, }; static void open_popup(struct seat *seat) { struct popup *popup; struct xdg_positioner *positioner; popup = zalloc(sizeof *popup); popup->wl_surface = wl_compositor_create_surface(wl_compositor); popup->xdg_surface = xdg_wm_base_get_xdg_surface (xdg_wm_base, popup->wl_surface); popup->parent = libdecor_frame_get_xdg_surface(window->frame); popup->window = window; popup->seat = seat; positioner = create_positioner(seat); popup->xdg_popup = xdg_surface_get_popup(popup->xdg_surface, popup->parent, positioner); xdg_positioner_destroy(positioner); xdg_surface_add_listener (popup->xdg_surface, &xdg_surface_listener, popup); xdg_popup_add_listener (popup->xdg_popup, &xdg_popup_listener, popup); window->popup = popup; xdg_popup_grab(popup->xdg_popup, seat->wl_seat, seat->serial); wl_surface_commit(popup->wl_surface); libdecor_frame_popup_grab(window->frame, seat->name); } static void close_popup(struct window *window) { struct popup *popup = window->popup; popup_destroy(popup); } 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; if (seat->pointer_focus != window->wl_surface) return; seat->serial = serial; if (window->popup && state == WL_POINTER_BUTTON_STATE_PRESSED) close_popup(window); if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) { libdecor_frame_move(window->frame, seat->wl_seat, serial); } else if (button == BTN_MIDDLE && state == WL_POINTER_BUTTON_STATE_PRESSED) { libdecor_frame_show_window_menu(window->frame, seat->wl_seat, serial, wl_fixed_to_int(seat->pointer_sx), wl_fixed_to_int(seat->pointer_sy)); } else if (button == BTN_RIGHT && state == WL_POINTER_BUTTON_STATE_PRESSED) { if (!window->popup) open_popup(seat); } } 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 keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { struct seat *seat = data; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { close(fd); return; } char *map_str = (char *)(mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)); if (map_str == MAP_FAILED) { close(fd); fprintf(stderr, "keymap mmap failed: %s", strerror(errno)); return; } struct xkb_keymap *keymap = xkb_keymap_new_from_string( seat->xkb_context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap(map_str, size); close(fd); if (!keymap) return; seat->xkb_state = xkb_state_new(keymap); xkb_keymap_unref(keymap); } static void keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { } static void keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) { } static void keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { struct seat *seat = data; if (state & WL_KEYBOARD_KEY_STATE_PRESSED) { const xkb_keysym_t *syms; if (xkb_state_key_get_syms(seat->xkb_state, key + 8, &syms) != 1) return; switch (syms[0]) { case XKB_KEY_Escape: printf("close\n"); libdecor_frame_close(window->frame); break; case XKB_KEY_1: /* toggle resizability */ if (libdecor_frame_has_capability( window->frame, LIBDECOR_ACTION_RESIZE)) { printf("set fixed-size\n"); libdecor_frame_unset_capabilities(window->frame, LIBDECOR_ACTION_RESIZE); } else { printf("set resizeable\n"); libdecor_frame_set_capabilities(window->frame, LIBDECOR_ACTION_RESIZE); } break; case XKB_KEY_2: /* maximize */ printf("maximize\n"); libdecor_frame_set_maximized(window->frame); break; case XKB_KEY_3: /* un-maximize / restore */ printf("un-maximize\n"); libdecor_frame_unset_maximized(window->frame); break; case XKB_KEY_4: /* fullscreen */ printf("fullscreen\n"); libdecor_frame_set_fullscreen(window->frame, NULL); break; case XKB_KEY_5: /* un-fullscreen / restore */ printf("un-fullscreen\n"); libdecor_frame_unset_fullscreen(window->frame); break; case XKB_KEY_minus: case XKB_KEY_plus: { const int dd = (syms[0] == XKB_KEY_minus ? -1 : +1) * chk/2; printf("resize to: %i x %i\n", window->configured_width + dd, window->configured_height + dd); resize(window, window->configured_width + dd, window->configured_height + dd); } break; case XKB_KEY_v: /* VGA: 640x480 */ printf("set VGA resolution: 640x480\n"); resize(window, 640, 480); break; case XKB_KEY_s: /* SVGA: 800x600 */ printf("set SVGA resolution: 800x600\n"); resize(window, 800, 600); break; case XKB_KEY_x: /* XVGA: 1024x768 */ printf("set XVGA resolution: 1024x768\n"); resize(window, 1024, 768); break; case XKB_KEY_t: libdecor_frame_set_title(window->frame, titles[window->title_index]); window->title_index = (window->title_index + 1) % N_TITLES; break; case XKB_KEY_h: /* toggle decorations */ libdecor_frame_set_visibility( window->frame, !libdecor_frame_is_visible(window->frame)); printf("decorations %s\n", libdecor_frame_is_visible(window->frame) ? "visible" : "hidden"); break; } } } static void keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { struct seat *seat = data; xkb_state_update_mask(seat->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); } static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { } static struct wl_keyboard_listener keyboard_listener = { keyboard_keymap, keyboard_enter, keyboard_leave, keyboard_key, keyboard_modifiers, keyboard_repeat_info, }; 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); seat->pointer_scale = 1; init_cursors(seat); } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer) { wl_pointer_release(seat->wl_pointer); seat->wl_pointer = NULL; } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && !seat->wl_keyboard) { seat->wl_keyboard = wl_seat_get_keyboard(wl_seat); seat->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, seat); } else if (!(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && seat->wl_keyboard) { xkb_context_unref(seat->xkb_context); wl_keyboard_release(seat->wl_keyboard); seat->wl_keyboard = 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 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 seat *seat; if (window) { if (output->scale != window->scale) update_scale(window); } wl_list_for_each(seat, &seats, link) { try_update_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 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 registry_handle_global(void *user_data, struct wl_registry *wl_registry, uint32_t id, const char *interface, uint32_t version) { struct seat *seat; struct output *output; if (strcmp(interface, "wl_compositor") == 0) { if (version < 4) { fprintf(stderr, "wl_compositor version >= 4 required"); exit(EXIT_FAILURE); } wl_compositor = wl_registry_bind(wl_registry, id, &wl_compositor_interface, 4); } else if (strcmp(interface, "wl_shm") == 0) { wl_shm = wl_registry_bind(wl_registry, id, &wl_shm_interface, 1); wl_shm_add_listener(wl_shm, &shm_listener, NULL); } else if (strcmp(interface, "wl_seat") == 0) { if (version < 3) { fprintf(stderr, "%s version 3 required but only version " "%i is available\n", interface, version); exit(EXIT_FAILURE); } seat = zalloc(sizeof *seat); wl_list_init(&seat->pointer_outputs); seat->wl_seat = wl_registry_bind(wl_registry, id, &wl_seat_interface, 3); wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); } else if (strcmp(interface, "wl_output") == 0) { if (version < 2) { fprintf(stderr, "%s version 3 required but only version " "%i is available\n", interface, version); exit(EXIT_FAILURE); } output = zalloc(sizeof *output); output->id = id; output->scale = 1; output->wl_output = wl_registry_bind(wl_registry, id, &wl_output_interface, 2); wl_proxy_set_tag((struct wl_proxy *) output->wl_output, &proxy_tag); wl_output_add_listener(output->wl_output, &output_listener, output); wl_list_insert(&outputs, &output->link); } else if (strcmp(interface, "xdg_wm_base") == 0) { xdg_wm_base = wl_registry_bind(wl_registry, id, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct output *output; struct window_output *window_output; wl_list_for_each(output, &outputs, link) { if (output->id == name) { wl_list_for_each(window_output, &window->outputs, link) { if (window_output->output == output) { wl_list_remove(&window_output->link); free(window_output); } } wl_list_remove(&output->link); wl_output_destroy(output->wl_output); free(output); break; } } } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static void handle_error(struct libdecor *context, enum libdecor_error error, const char *message) { fprintf(stderr, "Caught error (%d): %s\n", error, message); exit(EXIT_FAILURE); } static struct libdecor_interface libdecor_iface = { .error = handle_error, }; static void buffer_release(void *user_data, struct wl_buffer *wl_buffer) { struct buffer *buffer = user_data; wl_buffer_destroy(buffer->wl_buffer); munmap(buffer->data, buffer->data_size); free(buffer); } static const struct wl_buffer_listener buffer_listener = { buffer_release }; static struct buffer * create_shm_buffer(int width, int height, uint32_t format) { struct wl_shm_pool *pool; int fd, size, stride; void *data; struct buffer *buffer; stride = width * 4; size = stride * 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; } buffer = zalloc(sizeof *buffer); pool = wl_shm_create_pool(wl_shm, fd, size); buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer); wl_shm_pool_destroy(pool); close(fd); buffer->data = data; buffer->data_size = size; return buffer; } static void paint_buffer(struct buffer *buffer, int width, int height, int scale, enum libdecor_window_state window_state) { uint32_t *pixels = buffer->data; uint32_t bg, fg, color; int y, x, sx, sy; size_t off; int stride = width * scale; if (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) { fg = 0xffbcbcbc; bg = 0xff8e8e8e; } else { fg = 0xff8e8e8e; bg = 0xff484848; } for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { color = (x & chk) ^ (y & chk) ? fg : bg; for (sx = 0; sx < scale; sx++) { for (sy = 0; sy < scale; sy++) { off = x * scale + sx + (y * scale + sy) * stride; pixels[off] = color; } } } } } static void redraw(struct window *window) { struct buffer *buffer; buffer = create_shm_buffer(window->configured_width * window->scale, window->configured_height * window->scale, WL_SHM_FORMAT_XRGB8888); paint_buffer(buffer, window->configured_width, window->configured_height, window->scale, window->window_state); wl_surface_attach(window->wl_surface, buffer->wl_buffer, 0, 0); wl_surface_set_buffer_scale(window->wl_surface, window->scale); wl_surface_damage_buffer(window->wl_surface, 0, 0, window->configured_width * window->scale, window->configured_height * window->scale); wl_surface_commit(window->wl_surface); } static void handle_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data) { struct window *window = user_data; int width, height; enum libdecor_window_state window_state; struct libdecor_state *state; /* Update window state first for the correct calculations */ if (!libdecor_configuration_get_window_state(configuration, &window_state)) window_state = LIBDECOR_WINDOW_STATE_NONE; window->window_state = window_state; if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { width = window->content_width; height = window->content_height; } width = (width == 0) ? window->floating_width : width; height = (height == 0) ? window->floating_height : height; window->configured_width = width; window->configured_height = height; state = libdecor_state_new(width, height); libdecor_frame_commit(frame, state, configuration); libdecor_state_free(state); /* store floating dimensions */ if (libdecor_frame_is_floating(window->frame)) { window->floating_width = width; window->floating_height = height; } redraw(window); } static void handle_close(struct libdecor_frame *frame, void *user_data) { exit(EXIT_SUCCESS); } static void handle_commit(struct libdecor_frame *frame, void *user_data) { wl_surface_commit(window->wl_surface); } static void handle_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) { popup_destroy(window->popup); } static struct libdecor_frame_interface libdecor_frame_iface = { handle_configure, handle_close, handle_commit, handle_dismiss_popup, }; static void surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct window *window = data; struct output *output; struct window_output *window_output; if (!own_output(wl_output)) return; output = wl_output_get_user_data(wl_output); if (output == NULL) return; window_output = zalloc(sizeof *window_output); window_output->output = output; wl_list_insert(&window->outputs, &window_output->link); update_scale(window); } static void surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct window *window = data; struct window_output *window_output; if (!own_output(wl_output)) return; wl_list_for_each(window_output, &window->outputs, link) { if (window_output->output->wl_output == wl_output) { wl_list_remove(&window_output->link); free(window_output); update_scale(window); break; } } } static struct wl_surface_listener surface_listener = { surface_enter, surface_leave, }; static void free_outputs() { struct output *output; struct window_output *window_output, *window_output_tmp; wl_list_for_each(output, &outputs, link) { wl_list_for_each_safe(window_output, window_output_tmp, &window->outputs, link) { if (window_output->output == output) { wl_list_remove(&window_output->link); free(window_output); } } wl_list_remove(&output->link); wl_output_destroy(output->wl_output); free(output); break; } } int main(int argc, char **argv) { struct timeval start; struct timeval now; struct wl_display *wl_display; struct wl_registry *wl_registry; struct libdecor *context; struct output *output; int timeout = -1; bool timed_quit = false; if (argc > 1 && strcmp(argv[1], "--timed-quit") == 0) { timed_quit = true; timeout = 500; /* ms */ } /* write all output to stdout immediately */ setbuf(stdout, NULL); wl_display = wl_display_connect(NULL); if (!wl_display) { fprintf(stderr, "No Wayland connection\n"); return EXIT_FAILURE; } wl_list_init(&seats); wl_list_init(&outputs); wl_registry = wl_display_get_registry(wl_display); wl_registry_add_listener(wl_registry, ®istry_listener, NULL); wl_display_roundtrip(wl_display); wl_display_roundtrip(wl_display); if (!has_xrgb) { fprintf(stderr, "No XRGB shm format\n"); return EXIT_FAILURE; } window = zalloc(sizeof *window); window->scale = 1; window->title_index = 0; wl_list_for_each(output, &outputs, link) { window->scale = MAX(window->scale, output->scale); } wl_list_init(&window->outputs); window->wl_surface = wl_compositor_create_surface(wl_compositor); wl_surface_add_listener(window->wl_surface, &surface_listener, window); /* initialise content dimensions */ window->floating_width = DEFAULT_WIDTH; window->floating_height = DEFAULT_HEIGHT; context = libdecor_new(wl_display, &libdecor_iface); window->frame = libdecor_decorate(context, window->wl_surface, &libdecor_frame_iface, window); libdecor_frame_set_app_id(window->frame, "libdecor-demo"); libdecor_frame_set_title(window->frame, "libdecor demo"); libdecor_frame_map(window->frame); gettimeofday(&start, NULL); libdecor_frame_set_min_content_size(window->frame, 15 * chk, 10 * chk); while (libdecor_dispatch(context, timeout) >= 0) { if (timed_quit) { gettimeofday(&now, NULL); if (now.tv_sec >= start.tv_sec + 2) { fprintf(stderr, "Exiting due to --timed-quit\n"); libdecor_frame_close(window->frame); } } } free_outputs(); free(window); return EXIT_SUCCESS; } libdecor-0.2.2/demo/egl.c000066400000000000000000000212351455131637200151470ustar00rootroot00000000000000/* * Copyright © 2011 Benjamin Franzke * Copyright © 2010 Intel Corporation * 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 #include #include #include #include #include #include #include #include #include #include static const size_t default_size = 200; struct client { struct wl_display *display; struct wl_compositor *compositor; EGLDisplay egl_display; EGLContext egl_context; }; struct window { struct client *client; struct wl_surface *surface; struct libdecor_frame *frame; struct wl_egl_window *egl_window; EGLSurface egl_surface; int content_width; int content_height; int floating_width; int floating_height; bool open; bool configured; }; static void frame_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data) { struct window *window = user_data; struct libdecor_state *state; int width, height; if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { width = window->floating_width; height = window->floating_height; } window->content_width = width; window->content_height = height; wl_egl_window_resize(window->egl_window, window->content_width, window->content_height, 0, 0); state = libdecor_state_new(width, height); libdecor_frame_commit(frame, state, configuration); libdecor_state_free(state); /* store floating dimensions */ if (libdecor_frame_is_floating(window->frame)) { window->floating_width = width; window->floating_height = height; } window->configured = true; } static void frame_close(struct libdecor_frame *frame, void *user_data) { struct window *window = user_data; window->open = false; } static void frame_commit(struct libdecor_frame *frame, void *user_data) { struct window *window = user_data; eglSwapBuffers(window->client->display, window->egl_surface); } static struct libdecor_frame_interface frame_interface = { frame_configure, frame_close, frame_commit, }; static void libdecor_error(struct libdecor *context, enum libdecor_error error, const char *message) { fprintf(stderr, "Caught error (%d): %s\n", error, message); exit(EXIT_FAILURE); } static struct libdecor_interface libdecor_interface = { libdecor_error, }; static void registry_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) { struct client *client = data; if (strcmp(interface, wl_compositor_interface.name) == 0) { client->compositor = wl_registry_bind(wl_registry, name, &wl_compositor_interface, 1); } } static void registry_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_global, registry_global_remove }; static bool setup(struct window *window) { static const EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE }; EGLint major, minor; EGLint n; EGLConfig config; window->client->egl_display = eglGetDisplay((EGLNativeDisplayType)window->client->display); if (eglInitialize(window->client->egl_display, &major, &minor) == EGL_FALSE) { fprintf(stderr, "Cannot initialise EGL!\n"); return false; } if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { fprintf(stderr, "Cannot bind EGL API!\n"); return false; } if (eglChooseConfig(window->client->egl_display, config_attribs, &config, 1, &n) == EGL_FALSE) { fprintf(stderr, "No matching EGL configurations!\n"); return false; } window->client->egl_context = eglCreateContext(window->client->egl_display, config, EGL_NO_CONTEXT, NULL); if (window->client->egl_context == EGL_NO_CONTEXT) { fprintf(stderr, "No EGL context!\n"); return false; } window->surface = wl_compositor_create_surface(window->client->compositor); window->egl_window = wl_egl_window_create(window->surface, default_size, default_size); window->egl_surface = eglCreateWindowSurface( window->client->egl_display, config, (EGLNativeWindowType)window->egl_window, NULL); eglMakeCurrent(window->client->egl_display, window->egl_surface, window->egl_surface, window->client->egl_context); return true; } static void cleanup(struct window *window) { if (window->client->egl_display) { eglMakeCurrent(window->client->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } if (window->egl_surface) { eglDestroySurface(window->client->egl_display, window->egl_surface); } if (window->egl_window) { wl_egl_window_destroy(window->egl_window); } if (window->surface) { wl_surface_destroy(window->surface); } if (window->client->egl_context) { eglDestroyContext(window->client->egl_display, window->client->egl_context); } if (window->client->egl_display) { eglTerminate(window->client->egl_display); } } static float hue_to_channel(const float *const hue, const int n) { /* convert hue to rgb channels with saturation and value equal to 1 * https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB_alternative */ const float k = fmod(n + ((*hue) * 3 / M_PI), 6); return 1 - MAX(0, MIN(MIN(k, 4 - k), 1)); } static void hue_to_rgb(const float *const hue, float (*rgb)[3]) { (*rgb)[0] = hue_to_channel(hue, 5); (*rgb)[1] = hue_to_channel(hue, 3); (*rgb)[2] = hue_to_channel(hue, 1); } static void draw(struct window *window) { struct timespec tv; double time; /* change of colour hue (HSV space) in rad/sec */ static const float hue_change = (2 * M_PI) / 10; float hue; float rgb[3] = {0,0,0}; clock_gettime(CLOCK_REALTIME, &tv); time = tv.tv_sec + tv.tv_nsec * 1e-9; hue = fmod(time * hue_change, 2 * M_PI); hue_to_rgb(&hue, &rgb); glClearColor(rgb[0], rgb[1], rgb[2], 1); glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(window->client->egl_display, window->egl_surface); } int main(int argc, char *argv[]) { struct wl_registry *wl_registry; struct libdecor *context = NULL; struct window *window; struct client *client; int ret = EXIT_SUCCESS; client = calloc(1, sizeof(struct client)); client->display = wl_display_connect(NULL); if (!client->display) { fprintf(stderr, "No Wayland connection\n"); free(client); return EXIT_FAILURE; } wl_registry = wl_display_get_registry(client->display); wl_registry_add_listener(wl_registry, ®istry_listener, client); wl_display_roundtrip(client->display); window = calloc(1, sizeof(struct window)); window->client = client; window->open = true; window->configured = false; window->floating_width = window->floating_height = default_size; if (!setup(window)) { goto out; } context = libdecor_new(client->display, &libdecor_interface); window->frame = libdecor_decorate(context, window->surface, &frame_interface, window); libdecor_frame_set_app_id(window->frame, "egl-demo"); libdecor_frame_set_title(window->frame, "EGL demo"); libdecor_frame_map(window->frame); wl_display_roundtrip(client->display); wl_display_roundtrip(client->display); /* wait for the first configure event */ while (!window->configured) { if (libdecor_dispatch(context, 0) < 0) { ret = EXIT_FAILURE; goto out; } } while (window->open) { if (libdecor_dispatch(context, 0) < 0) { ret = EXIT_FAILURE; goto out; } draw(window); } out: if (context) { libdecor_unref(context); } cleanup(window); free(window); free(client); return ret; } libdecor-0.2.2/demo/meson.build000066400000000000000000000031641455131637200163770ustar00rootroot00000000000000add_languages('cpp') wayland_cursor_dep = dependency('wayland-cursor') math_dep = cc.find_library('m') egl_dep = dependency('egl') gl_dep = dependency('opengl') wayland_egl_dep = dependency('wayland-egl') xkb_dep = dependency('xkbcommon') xdg_shell_path = join_paths(protocols_dir, 'stable', 'xdg-shell', 'xdg-shell.xml') xdg_shell_client_header = custom_target('xdg-shell client header (demo)', input: xdg_shell_path, output: 'xdg-shell-client-protocol.h', command: [ wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@', ] ) xdg_shell_source = custom_target('xdg-shell source (demo)', input: xdg_shell_path, output: 'xdg-shell-protocol.c', command: [ wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@', ] ) executable('libdecor-demo', sources: [ 'demo.c', xdg_shell_client_header, xdg_shell_source, ], include_directories: [ top_includepath, ], dependencies: [ libdecor_dep, wayland_cursor_dep, cursor_settings_dep, os_compatibility_dep, xkb_dep], install: get_option('install_demo'), ) executable('libdecor-egl', sources: 'egl.c', dependencies: [ libdecor_dep, wayland_egl_dep, egl_dep, gl_dep, math_dep], install: get_option('install_demo'), ) executable('libdecor-c++-demo', sources: [ 'c++-demo.cc', ], cpp_args: [ '-Wno-unused-parameter', '-Wno-missing-field-initializers', ], include_directories: [ top_includepath, ], dependencies: [ libdecor_dep, wayland_cursor_dep, cursor_settings_dep, os_compatibility_dep, xkb_dep], install: get_option('install_demo'), ) libdecor-0.2.2/meson.build000066400000000000000000000061271455131637200154550ustar00rootroot00000000000000project('libdecor', 'c', version: '0.2.2', meson_version: '>= 0.49.0', license: 'MIT', default_options: [ 'c_std=c99', ], ) version_array = meson.project_version().split('.') libdecor_major_version = version_array[0].to_int() libdecor_minor_version = version_array[1].to_int() libdecor_micro_version = version_array[2].to_int() libdecor_api_version_string = '@0@'.format(libdecor_major_version) libdecor_interface_age = libdecor_micro_version libdecor_binary_age = 100 * libdecor_minor_version + libdecor_micro_version # Maintain compatibility with libtool versioning libdecor_soversion = 0 current = libdecor_binary_age - libdecor_interface_age revision = libdecor_interface_age libdecor_libversion = '@0@.@1@.@2@'.format(libdecor_soversion, current, revision) plugin_api_version = 1 plugin_api_version_string = '@0@'.format(plugin_api_version) libdecor_name = 'decor-' + libdecor_api_version_string libdecor_full_name = 'libdecor-' + libdecor_api_version_string cc = meson.get_compiler('c') add_project_arguments('-D_GNU_SOURCE', language: 'c') add_project_arguments(cc.get_supported_arguments([ '-Wno-unused-parameter', '-Wno-missing-field-initializers', ]), language: 'c') wayland_client_req = '>= 1.18' wayland_protocols_req = '>= 1.15' dbus_req = '>= 1.0' wayland_client_dep = dependency('wayland-client', version: wayland_client_req) wayland_protocols_dep = dependency('wayland-protocols', version: wayland_protocols_req) dl_dep = cc.find_library('dl', required: true) dbus_dep = dependency('dbus-1', version: dbus_req, required: get_option('dbus')) # list of (function, prefix) required_functions = [ ['asprintf', '#define _GNU_SOURCE\n#include '], ] foreach function : required_functions if not cc.has_function(function[0], prefix: function[1]) error('Required function ' + function[0] + ' missing') endif endforeach prefix = get_option('prefix') libdir = join_paths(prefix, get_option('libdir')) devenv = environment() pkgname = meson.project_name() pkglibdir = join_paths(libdir, pkgname) plugindir = join_paths(pkglibdir, 'plugins-@0@'.format(plugin_api_version_string)) cdata = configuration_data() cdata.set_quoted('VERSION', meson.project_version()) cdata.set_quoted('LIBDECOR_PLUGIN_DIR', plugindir) cdata.set('LIBDECOR_PLUGIN_API_VERSION', plugin_api_version) if dbus_dep.found() cdata.set('HAS_DBUS', true) endif if cc.has_function('mkostemp', prefix: '#include ') cdata.set('HAVE_MKOSTEMP', true) endif if cc.has_function('posix_fallocate', prefix: '#define _GNU_SOURCE\n#include ') cdata.set('HAVE_POSIX_FALLOCATE', true) endif if cc.has_function('memfd_create', prefix: '#define _GNU_SOURCE\n#include ') cdata.set('HAVE_MEMFD_CREATE', true) endif if wayland_protocols_dep.version().version_compare('>=1.32') cdata.set('HAVE_XDG_SHELL_V6', true) endif config_h = configure_file( input: 'config.h.meson', output: 'config.h', configuration: cdata ) top_includepath = include_directories('.') subdir('src') if get_option('demo') subdir('demo') endif if meson.version().version_compare('>=0.58.0') meson.add_devenv(devenv) endif libdecor-0.2.2/meson_options.txt000066400000000000000000000005671455131637200167520ustar00rootroot00000000000000option('demo', type: 'boolean', value: true, description: 'build cairo and EGL client examples') option('dbus', type: 'feature', value: 'enabled', description: 'use D-Bus to fetch cursor settings') option('install_demo', type: 'boolean', value: false, description: 'install demo programs') option('gtk', type: 'feature', value: 'enabled', description: 'build GTK plugin') libdecor-0.2.2/src/000077500000000000000000000000001455131637200140745ustar00rootroot00000000000000libdecor-0.2.2/src/cursor-settings.c000066400000000000000000000101271455131637200174140ustar00rootroot00000000000000/* * 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 "cursor-settings.h" #include #include #include #include "config.h" static bool get_cursor_settings_from_env(char **theme, int *size) { char *env_xtheme; char *env_xsize; env_xtheme = getenv("XCURSOR_THEME"); if (env_xtheme != NULL) *theme = strdup(env_xtheme); env_xsize = getenv("XCURSOR_SIZE"); if (env_xsize != NULL) *size = atoi(env_xsize); return env_xtheme != NULL && env_xsize != NULL; } #ifdef HAS_DBUS #include static DBusMessage * get_setting_sync(DBusConnection *const connection, const char *key, const char *value) { DBusError error; dbus_bool_t success; DBusMessage *message; DBusMessage *reply; message = dbus_message_new_method_call( "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Settings", "Read"); success = dbus_message_append_args(message, DBUS_TYPE_STRING, &key, DBUS_TYPE_STRING, &value, DBUS_TYPE_INVALID); if (!success) return NULL; dbus_error_init(&error); reply = dbus_connection_send_with_reply_and_block( connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error); dbus_message_unref(message); if (dbus_error_is_set(&error)) { dbus_error_free(&error); return NULL; } dbus_error_free(&error); return reply; } static bool parse_type(DBusMessage *const reply, const int type, void *value) { DBusMessageIter iter[3]; dbus_message_iter_init(reply, &iter[0]); if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) return false; dbus_message_iter_recurse(&iter[0], &iter[1]); if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) return false; dbus_message_iter_recurse(&iter[1], &iter[2]); if (dbus_message_iter_get_arg_type(&iter[2]) != type) return false; dbus_message_iter_get_basic(&iter[2], value); return true; } bool libdecor_get_cursor_settings(char **theme, int *size) { static const char name[] = "org.gnome.desktop.interface"; static const char key_theme[] = "cursor-theme"; static const char key_size[] = "cursor-size"; DBusError error; DBusConnection *connection; DBusMessage *reply; const char *value_theme = NULL; dbus_error_init(&error); connection = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) goto fallback; reply = get_setting_sync(connection, name, key_theme); if (!reply) goto fallback; if (!parse_type(reply, DBUS_TYPE_STRING, &value_theme)) { dbus_message_unref(reply); goto fallback; } *theme = strdup(value_theme); dbus_message_unref(reply); reply = get_setting_sync(connection, name, key_size); if (!reply) goto fallback; if (!parse_type(reply, DBUS_TYPE_INT32, size)) { dbus_message_unref(reply); goto fallback; } dbus_message_unref(reply); return true; fallback: return get_cursor_settings_from_env(theme, size); } #else bool libdecor_get_cursor_settings(char **theme, int *size) { return get_cursor_settings_from_env(theme, size); } #endif libdecor-0.2.2/src/cursor-settings.h000066400000000000000000000023511455131637200174210ustar00rootroot00000000000000/* * 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. */ #pragma once #include bool libdecor_get_cursor_settings(char **theme, int *size); libdecor-0.2.2/src/libdecor-fallback.c000066400000000000000000000122311455131637200175570ustar00rootroot00000000000000/* * 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 #include #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_popup_grab(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name) { } static void libdecor_plugin_fallback_frame_popup_ungrab(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name) { } static bool libdecor_plugin_fallback_frame_get_border_size(struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_configuration *configuration, int *left, int *right, int *top, int *bottom) { if (left) *left = 0; if (right) *right = 0; if (top) *top = 0; if (bottom) *bottom = 0; return true; } static struct libdecor_plugin_interface fallback_plugin_iface = { .destroy = libdecor_plugin_fallback_destroy, .get_fd = libdecor_plugin_fallback_get_fd, .dispatch = libdecor_plugin_fallback_dispatch, .frame_new = libdecor_plugin_fallback_frame_new, .frame_free = libdecor_plugin_fallback_frame_free, .frame_commit = libdecor_plugin_fallback_frame_commit, .frame_property_changed = libdecor_plugin_fallback_frame_property_changed, .frame_popup_grab = libdecor_plugin_fallback_frame_popup_grab, .frame_popup_ungrab = libdecor_plugin_fallback_frame_popup_ungrab, .frame_get_border_size = libdecor_plugin_fallback_frame_get_border_size, }; struct libdecor_plugin * libdecor_fallback_plugin_new(struct libdecor *context) { struct libdecor_plugin_fallback *plugin; plugin = zalloc(sizeof *plugin); libdecor_plugin_init(&plugin->plugin, context, &fallback_plugin_iface); plugin->context = context; libdecor_notify_plugin_ready(context); return &plugin->plugin; } libdecor-0.2.2/src/libdecor-fallback.h000066400000000000000000000025461455131637200175740ustar00rootroot00000000000000/* * 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 */ libdecor-0.2.2/src/libdecor-plugin.h000066400000000000000000000127231455131637200173310ustar00rootroot00000000000000/* * 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. */ #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; /* NULL terminated list of incompatible symbols. */ char *conflicting_symbols[1024]; }; 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_popup_grab)(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name); void (* frame_popup_ungrab)(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name); bool (* frame_get_border_size)(struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_configuration *configuration, int *left, int *right, int *top, int *bottom); /* Reserved */ void (* reserved0)(void); void (* reserved1)(void); void (* reserved2)(void); void (* reserved3)(void); void (* reserved4)(void); void (* reserved5)(void); void (* reserved6)(void); void (* reserved7)(void); void (* reserved8)(void); void (* reserved9)(void); }; struct wl_surface * libdecor_frame_get_wl_surface(struct libdecor_frame *frame); int libdecor_frame_get_content_width(struct libdecor_frame *frame); int libdecor_frame_get_content_height(struct libdecor_frame *frame); enum libdecor_window_state libdecor_frame_get_window_state(struct libdecor_frame *frame); enum libdecor_capabilities libdecor_frame_get_capabilities(const struct libdecor_frame *frame); void libdecor_frame_dismiss_popup(struct libdecor_frame *frame, const char *seat_name); void libdecor_frame_toplevel_commit(struct libdecor_frame *frame); struct wl_display * libdecor_get_wl_display(struct libdecor *context); void libdecor_notify_plugin_ready(struct libdecor *context); void libdecor_notify_plugin_error(struct libdecor *context, enum libdecor_error error, const char *__restrict fmt, ...); int libdecor_state_get_content_width(struct libdecor_state *state); int libdecor_state_get_content_height(struct libdecor_state *state); enum libdecor_window_state libdecor_state_get_window_state(struct libdecor_state *state); int libdecor_plugin_init(struct libdecor_plugin *plugin, struct libdecor *context, struct libdecor_plugin_interface *iface); void libdecor_plugin_release(struct libdecor_plugin *plugin); #endif /* LIBDECOR_PLUGIN_H */ libdecor-0.2.2/src/libdecor.c000066400000000000000000001260241455131637200160300ustar00rootroot00000000000000/* * 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" #ifdef HAVE_XDG_SHELL_V6 #define XDG_WM_BASE_VER 6 #else #define XDG_WM_BASE_VER 2 #endif 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; #ifdef HAVE_XDG_SHELL_V6 case XDG_TOPLEVEL_STATE_SUSPENDED: pending_state |= LIBDECOR_WINDOW_STATE_SUSPENDED; break; #endif default: break; } } return pending_state; } static void xdg_toplevel_configure(void *user_data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { struct libdecor_frame *frame = user_data; struct libdecor_frame_private *frame_priv = frame->priv; enum libdecor_window_state window_state; window_state = parse_states(states); frame_priv->pending_configuration = libdecor_configuration_new(); frame_priv->pending_configuration->has_size = true; frame_priv->pending_configuration->window_width = width; frame_priv->pending_configuration->window_height = height; frame_priv->pending_configuration->has_window_state = true; frame_priv->pending_configuration->window_state = window_state; } static void xdg_toplevel_close(void *user_data, struct xdg_toplevel *xdg_toplevel) { struct libdecor_frame *frame = user_data; struct libdecor_frame_private *frame_priv = frame->priv; frame_priv->iface->close(frame, frame_priv->user_data); } #ifdef HAVE_XDG_SHELL_V6 static void xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) { } static void xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) { } #endif static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_configure, xdg_toplevel_close, #ifdef HAVE_XDG_SHELL_V6 xdg_toplevel_configure_bounds, xdg_toplevel_wm_capabilities, #endif }; static void toplevel_decoration_configure( void *data, struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode) { ((struct libdecor_frame_private *)(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; if (context->decoration_manager && frame_priv->toplevel_decoration) { zxdg_toplevel_decoration_v1_destroy(frame_priv->toplevel_decoration); frame_priv->toplevel_decoration = NULL; } wl_list_remove(&frame->link); if (frame_priv->xdg_toplevel) xdg_toplevel_destroy(frame_priv->xdg_toplevel); if (frame_priv->xdg_surface) xdg_surface_destroy(frame_priv->xdg_surface); plugin->priv->iface->frame_free(plugin, frame); free(frame_priv->state.title); free(frame_priv->state.app_id); free(frame_priv); free(frame); } } 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,XDG_WM_BASE_VER)); 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, void *lib) { char * const *symbol; symbol = plugin_description->conflicting_symbols; while (*symbol) { dlerror(); void *sym = dlsym(RTLD_DEFAULT, *symbol); if (!dlerror()) { void *libsym = dlsym(lib, *symbol); if (!dlerror() && libsym != sym) { fprintf(stderr, "Plugin \"%s\" uses conflicting symbol \"%s\".\n", plugin_description->description, *symbol); 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, lib)) { 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; } libdecor-0.2.2/src/libdecor.h000066400000000000000000000324771455131637200160450ustar00rootroot00000000000000/* * 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. */ #ifndef LIBDECOR_H #define LIBDECOR_H #include #include #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, LIBDECOR_WINDOW_STATE_SUSPENDED = 1 << 7, }; 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 min content size. * * This translates roughly to xdg_toplevel_set_min_size(). */ void libdecor_frame_set_min_content_size(struct libdecor_frame *frame, int content_width, int content_height); /** * Set the max content size. * * This translates roughly to xdg_toplevel_set_max_size(). */ void libdecor_frame_set_max_content_size(struct libdecor_frame *frame, int content_width, int content_height); /** * Get the min content size. */ void libdecor_frame_get_min_content_size(const struct libdecor_frame *frame, int *content_width, int *content_height); /** * Get the max content size. */ void libdecor_frame_get_max_content_size(const struct libdecor_frame *frame, int *content_width, int *content_height); /** * Initiate an interactive resize. * * This roughly translates to xdg_toplevel_resize(). */ void libdecor_frame_resize(struct libdecor_frame *frame, struct wl_seat *wl_seat, uint32_t serial, enum libdecor_resize_edge edge); /** * Initiate an interactive move. * * This roughly translates to xdg_toplevel_move(). */ void libdecor_frame_move(struct libdecor_frame *frame, struct wl_seat *wl_seat, uint32_t serial); /** * Commit a new window state. This can be called on application driven resizes * when the window is floating, or in response to received configurations, i.e. * from e.g. interactive resizes or state changes. */ void libdecor_frame_commit(struct libdecor_frame *frame, struct libdecor_state *state, struct libdecor_configuration *configuration); /** * Minimize the window. * * Roughly translates to xdg_toplevel_set_minimized(). */ void libdecor_frame_set_minimized(struct libdecor_frame *frame); /** * Maximize the window. * * Roughly translates to xdg_toplevel_set_maximized(). */ void libdecor_frame_set_maximized(struct libdecor_frame *frame); /** * Unmaximize the window. * * Roughly translates to xdg_toplevel_unset_maximized(). */ void libdecor_frame_unset_maximized(struct libdecor_frame *frame); /** * Fullscreen the window. * * Roughly translates to xdg_toplevel_set_fullscreen(). */ void libdecor_frame_set_fullscreen(struct libdecor_frame *frame, struct wl_output *output); /** * Unfullscreen the window. * * Roughly translates to xdg_toplevel_unset_unfullscreen(). */ void libdecor_frame_unset_fullscreen(struct libdecor_frame *frame); /** * Return true if the window is floating. * * A window is floating when it's not maximized, tiled, fullscreen, or in any * similar way with a fixed size and state. * Note that this function uses the "applied" configuration. If this function * is used in the 'configure' callback, the provided configuration has to be * applied via 'libdecor_frame_commit' first, before it will reflect the current * window state from the provided configuration. */ bool libdecor_frame_is_floating(struct libdecor_frame *frame); /** * Close the window. * * Roughly translates to xdg_toplevel_close(). */ void libdecor_frame_close(struct libdecor_frame *frame); /** * Map the window. * * This will eventually result in the initial configure event. */ void libdecor_frame_map(struct libdecor_frame *frame); /** * Get the associated xdg_surface for content wl_surface. */ struct xdg_surface * libdecor_frame_get_xdg_surface(struct libdecor_frame *frame); /** * Get the associated xdg_toplevel for the content wl_surface. */ struct xdg_toplevel * libdecor_frame_get_xdg_toplevel(struct libdecor_frame *frame); /** * 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 */ libdecor-0.2.2/src/meson.build000066400000000000000000000057611455131637200162470ustar00rootroot00000000000000pkg = import('pkgconfig') libdecor_includepath = include_directories('.') libdecor_includes = [ libdecor_includepath, top_includepath, ] libdecor_sources = [ 'libdecor.c', 'libdecor-fallback.c', ] libdecor_headers = [ 'libdecor.h', ] install_headers(libdecor_headers, subdir: '@0@'.format(libdecor_full_name), ) libdecor_built_sources = [] wayland_scanner = find_program('wayland-scanner') # Format: # - protocol stability # - protocol name # - optional: protocol version, if unstable wayland_protocols = [ ['stable', 'xdg-shell'], ['unstable', 'xdg-decoration', '1'], ] protocols_dir = wayland_protocols_dep.get_pkgconfig_variable('pkgdatadir') assert(protocols_dir != '', 'Could not get pkgdatadir from wayland-protocols.pc') foreach p: wayland_protocols stability = p.get(0) name = p.get(1) assert(stability in ['stable', 'unstable'], 'protocol \'@0@\' must be \'stable\' or \'unstable\''.format(name)) suffix = stability=='unstable' ? '-unstable-v@0@'.format(p.get(2)) : '' output_base = name input = join_paths(protocols_dir, stability, name, name+suffix+'.xml') libdecor_built_sources += custom_target('@0@ client header'.format(output_base), input: input, output: '@0@-client-protocol.h'.format(output_base), command: [ wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@', ] ) libdecor_built_sources += custom_target('@0@ source'.format(output_base), input: input, output: '@0@-protocol.c'.format(output_base), command: [ wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@', ] ) endforeach ## cursor settings cursor_settings = static_library('cursor_settings', sources: ['cursor-settings.c'], include_directories: [top_includepath], dependencies: [dbus_dep], gnu_symbol_visibility: 'hidden', ) cursor_settings_dep = declare_dependency( link_with: cursor_settings, dependencies: [dbus_dep], ) ## os compatibility os_compatibility = static_library('os_compatibility', sources: ['os-compatibility.c'], include_directories: [top_includepath], gnu_symbol_visibility: 'hidden', ) os_compatibility_dep = declare_dependency( link_with: os_compatibility, ) ## core decor library libdecor = shared_library(libdecor_name, sources: [ libdecor_sources, libdecor_built_sources, ], soversion: libdecor_soversion, version: libdecor_libversion, include_directories: libdecor_includes, gnu_symbol_visibility: 'hidden', dependencies: [ wayland_client_dep, dl_dep, cursor_settings_dep, ], install: true ) libdecor_dep = declare_dependency( link_with: libdecor, include_directories: [ libdecor_includepath, ], dependencies: [ wayland_client_dep, ], ) ## decor plugins subdir('plugins') ## pkg-config file pkg.generate( name: libdecor_full_name, description: 'library for Wayland client-side window decors', libraries: libdecor, requires_private: [ 'wayland-client', ], subdirs: libdecor_full_name, version: meson.project_version() ) libdecor-0.2.2/src/os-compatibility.c000066400000000000000000000117651455131637200175420ustar00rootroot00000000000000/* * 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 #include #include #include #include #include #include #ifdef HAVE_MEMFD_CREATE #include #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 sigset_t mask; sigset_t old_mask; /* posix_fallocate() might be interrupted, so we need to check * for EINTR and retry in that case. * However, in the presence of an alarm, the interrupt may trigger * repeatedly and prevent a large posix_fallocate() to ever complete * successfully, so we need to first block SIGALRM to prevent * this. */ sigemptyset(&mask); sigaddset(&mask, SIGALRM); sigprocmask(SIG_BLOCK, &mask, &old_mask); /* * Filesystems that do not support fallocate will return EINVAL or * EOPNOTSUPP. In this case we need to fall back to ftruncate */ do { errno = posix_fallocate(fd, 0, size); } while (errno == EINTR); sigprocmask(SIG_SETMASK, &old_mask, NULL); if (errno == 0) return 0; else if (errno != EINVAL && errno != EOPNOTSUPP) return -1; #endif if (ftruncate(fd, size) < 0) return -1; return 0; } /* * Create a new, unique, anonymous file of the given size, and * return the file descriptor for it. The file descriptor is set * CLOEXEC. The file is immediately suitable for mmap()'ing * the given size at offset zero. * * The file should not have a permanent backing store like a disk, * but may have if XDG_RUNTIME_DIR is not properly implemented in OS. * * The file name is deleted from the file system. * * The file is suitable for buffer sharing between processes by * transmitting the file descriptor over Unix sockets using the * SCM_RIGHTS methods. * * If the C library implements posix_fallocate(), it is used to * guarantee that disk space is available for the file at the * given size. If disk space is insufficient, errno is set to ENOSPC. * If posix_fallocate() is not supported, program may receive * SIGBUS on accessing mmap()'ed file contents instead. * * If the C library implements memfd_create(), it is used to create the * file purely in memory, without any backing file name on the file * system, and then sealing off the possibility of shrinking it. This * can then be checked before accessing mmap()'ed file contents, to * make sure SIGBUS can't happen. It also avoids requiring * XDG_RUNTIME_DIR. */ int 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; } libdecor-0.2.2/src/os-compatibility.h000066400000000000000000000024431455131637200175400ustar00rootroot00000000000000/* * 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 int os_create_anonymous_file(off_t size); #endif /* OS_COMPATIBILITY_H */ libdecor-0.2.2/src/plugins/000077500000000000000000000000001455131637200155555ustar00rootroot00000000000000libdecor-0.2.2/src/plugins/cairo/000077500000000000000000000000001455131637200166525ustar00rootroot00000000000000libdecor-0.2.2/src/plugins/cairo/libdecor-cairo.c000066400000000000000000002153161455131637200217040ustar00rootroot00000000000000/* * 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 #include #include #include #include "libdecor-plugin.h" #include "utils.h" #include "cursor-settings.h" #include "os-compatibility.h" #include #include #include "common/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) { if (!proxy) return false; 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) { if (wl_output_get_version (output->wl_output) >= WL_OUTPUT_RELEASE_SINCE_VERSION) wl_output_release(output->wl_output); else wl_output_destroy(output->wl_output); free(output); } wl_list_for_each_safe(frame, frame_tmp, &plugin_cairo->visible_frame_list, link) { wl_list_remove(&frame->link); } free(plugin_cairo->cursor_theme_name); if (plugin_cairo->wl_shm) wl_shm_destroy(plugin_cairo->wl_shm); pango_font_description_free(plugin_cairo->font); if (plugin_cairo->wl_compositor) wl_compositor_destroy(plugin_cairo->wl_compositor); if (plugin_cairo->wl_subcompositor) 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, current_max_w, current_max_h; frame_cairo->shadow.opaque = false; ensure_component(frame_cairo, &frame_cairo->shadow); libdecor_frame_get_min_content_size(&frame_cairo->frame, &min_width, &min_height); min_width = MAX(min_width, (int)MAX(56, 4 * BUTTON_WIDTH)); min_height = MAX(min_height, (int)MAX(56, TITLE_HEIGHT + 1)); libdecor_frame_set_min_content_size(&frame_cairo->frame, min_width, min_height); libdecor_frame_get_max_content_size(&frame_cairo->frame, ¤t_max_w, ¤t_max_h); if (current_max_w && current_max_w < min_width) current_max_w = min_width; if (current_max_h && current_max_h < min_height) current_max_h = min_height; libdecor_frame_set_max_content_size(&frame_cairo->frame, current_max_w, current_max_h); } 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 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); } 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 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_frame_get_border_size(struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_configuration *configuration, int *left, int *right, int *top, int *bottom) { enum libdecor_window_state window_state; if (configuration) { if (!libdecor_configuration_get_window_state( configuration, &window_state)) return false; } else { window_state = libdecor_frame_get_window_state(frame); } if (left) *left = 0; if (right) *right = 0; if (bottom) *bottom = 0; if (top) { enum decoration_type type = window_state_to_decoration_type(window_state); if (((struct libdecor_frame_cairo *)frame)->title_bar.is_showing && (type != DECORATION_TYPE_NONE)) *top = TITLE_HEIGHT; else *top = 0; } 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_popup_grab = libdecor_plugin_cairo_frame_popup_grab, .frame_popup_ungrab = libdecor_plugin_cairo_frame_popup_ungrab, .frame_get_border_size = libdecor_plugin_cairo_frame_get_border_size, }; 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 * 2; const bool bottom = pointer_y > (cmpnt->server.buffer->height - margin * 2); const bool left = pointer_x < margin * 2; const bool right = pointer_x > (cmpnt->server.buffer->width - margin * 2); if (top) if (left) return LIBDECOR_RESIZE_EDGE_TOP_LEFT; else if (right) return LIBDECOR_RESIZE_EDGE_TOP_RIGHT; else return LIBDECOR_RESIZE_EDGE_TOP; else if (bottom) if (left) return LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT; else if (right) return LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT; else return LIBDECOR_RESIZE_EDGE_BOTTOM; else if (left) return LIBDECOR_RESIZE_EDGE_LEFT; else if (right) return LIBDECOR_RESIZE_EDGE_RIGHT; else return LIBDECOR_RESIZE_EDGE_NONE; } static bool update_local_cursor(struct seat *seat) { if (!seat->pointer_focus) { seat->current_cursor = seat->cursor_left_ptr; return false; } if (!own_surface(seat->pointer_focus)) return false; struct libdecor_frame_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) { libdecor_frame_ref(&frame_cairo->frame); 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); libdecor_frame_unref(&frame_cairo->frame); } } 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, MIN (version, 3)); 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; } 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_absolute_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, ®istry_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); wl_display_roundtrip(wl_display); if (!has_required_globals(plugin_cairo)) { fprintf(stderr, "libdecor-cairo-WARNING: Could not get required globals\n"); libdecor_plugin_cairo_destroy(&plugin_cairo->plugin); return NULL; } 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, }; libdecor-0.2.2/src/plugins/cairo/meson.build000066400000000000000000000012021455131637200210070ustar00rootroot00000000000000pangocairo_dep = dependency('pangocairo') wayland_cursor_dep = dependency('wayland-cursor') shared_module('decor-cairo', sources: [ 'libdecor-cairo.c', ], include_directories: [ top_includepath, libdecor_includepath, plugin_include_path, ], gnu_symbol_visibility: 'hidden', dependencies: [ libdecor_dep, pangocairo_dep, wayland_cursor_dep, cursor_settings_dep, os_compatibility_dep, ], link_with: plugin_common, install_dir: join_paths(plugindir), install: true, ) if meson.version().version_compare('>=0.58.0') devenv.append('LIBDECOR_PLUGIN_DIR', meson.current_build_dir()) endif libdecor-0.2.2/src/plugins/common/000077500000000000000000000000001455131637200170455ustar00rootroot00000000000000libdecor-0.2.2/src/plugins/common/libdecor-cairo-blur.c000066400000000000000000000160011455131637200230270ustar00rootroot00000000000000/* * 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 #include #include /** * 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); } libdecor-0.2.2/src/plugins/common/libdecor-cairo-blur.h000066400000000000000000000003441455131637200230370ustar00rootroot00000000000000#pragma once #include 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); libdecor-0.2.2/src/plugins/common/meson.build000066400000000000000000000004301455131637200212040ustar00rootroot00000000000000cairo_dep = dependency('cairo') math_dep = cc.find_library('m', required: true) plugin_common = static_library('cairo-blur', sources: [ 'libdecor-cairo-blur.c', ], dependencies: [ cairo_dep, math_dep, ], gnu_symbol_visibility: 'hidden', install: false, ) libdecor-0.2.2/src/plugins/dummy/000077500000000000000000000000001455131637200167105ustar00rootroot00000000000000libdecor-0.2.2/src/plugins/dummy/libdecor-dummy.c000066400000000000000000000073551455131637200220020ustar00rootroot00000000000000/* * 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 #include #include #include #include #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; libdecor_plugin_release(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_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 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_popup_grab = libdecor_plugin_dummy_frame_popup_grab, .frame_popup_ungrab = libdecor_plugin_dummy_frame_popup_ungrab, }; static struct libdecor_plugin * libdecor_plugin_new(struct libdecor *context) { struct libdecor_plugin_dummy *plugin_dummy; plugin_dummy = zalloc(sizeof *plugin_dummy); libdecor_plugin_init(&plugin_dummy->plugin, context, &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, .capabilities = LIBDECOR_PLUGIN_CAPABILITY_BASE, .description = "dummy libdecor plugin", .priorities = priorities, .constructor = libdecor_plugin_new, }; libdecor-0.2.2/src/plugins/dummy/meson.build000066400000000000000000000003421455131637200210510ustar00rootroot00000000000000shared_module('decor-dummy', sources: [ 'libdecor-dummy.c', ], include_directories: [ top_includepath, libdecor_includepath, ], gnu_symbol_visibility: 'hidden', dependencies: [ libdecor_dep, ], ) libdecor-0.2.2/src/plugins/gtk/000077500000000000000000000000001455131637200163425ustar00rootroot00000000000000libdecor-0.2.2/src/plugins/gtk/libdecor-gtk.c000066400000000000000000002177671455131637200211000ustar00rootroot00000000000000/* * Copyright © 2018 Jonas Ådahl * Copyright © 2021 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 #include #include #include "libdecor-plugin.h" #include "utils.h" #include "cursor-settings.h" #include "os-compatibility.h" #include #include "common/libdecor-cairo-blur.h" #include #include static const size_t SHADOW_MARGIN = 24; /* grabbable part of the border */ static const char *cursor_names[] = { "top_side", "bottom_side", "left_side", "top_left_corner", "bottom_left_corner", "right_side", "top_right_corner", "bottom_right_corner" }; enum header_element { HEADER_NONE, HEADER_FULL, /* entire header bar */ HEADER_TITLE, /* label */ HEADER_MIN, HEADER_MAX, HEADER_CLOSE, }; struct header_element_data { const char *name; enum header_element type; /* pointer to button or NULL if not found*/ GtkWidget *widget; GtkStateFlags state; }; static void find_widget_by_name(GtkWidget *widget, void *data) { if (GTK_IS_WIDGET(widget)) { char *style_ctx = gtk_style_context_to_string( gtk_widget_get_style_context(widget), GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE); if (strstr(style_ctx, ((struct header_element_data *)data)->name)) { ((struct header_element_data *)data)->widget = widget; free(style_ctx); return; } free(style_ctx); } if (GTK_IS_CONTAINER(widget)) { /* recursively traverse container */ gtk_container_forall(GTK_CONTAINER(widget), &find_widget_by_name, data); } } static struct header_element_data find_widget_by_type(GtkWidget *widget, enum header_element type) { char* name = NULL; switch (type) { case HEADER_FULL: name = "headerbar.titlebar:"; break; case HEADER_TITLE: name = "label.title:"; break; case HEADER_MIN: name = ".minimize"; break; case HEADER_MAX: name = ".maximize"; break; case HEADER_CLOSE: name = ".close"; break; default: break; } struct header_element_data data = {.name = name, .type = type, .widget = NULL}; find_widget_by_name(widget, &data); return data; } static bool in_region(const cairo_rectangle_int_t *rect, const int *x, const int *y) { return (*x>=rect->x) & (*y>=rect->y) & (*x<(rect->x+rect->width)) & (*y<(rect->y+rect->height)); } static struct header_element_data get_header_focus(const GtkHeaderBar *header_bar, const int x, const int y) { /* we have to check child widgets (buttons, title) before the 'HDR_HDR' root widget */ static const enum header_element elems[] = {HEADER_TITLE, HEADER_MIN, HEADER_MAX, HEADER_CLOSE}; for (size_t i = 0; i < ARRAY_SIZE(elems); i++) { struct header_element_data elem = find_widget_by_type(GTK_WIDGET(header_bar), elems[i]); if (elem.widget) { GtkAllocation allocation; gtk_widget_get_allocation(GTK_WIDGET(elem.widget), &allocation); if (in_region(&allocation, &x, &y)) return elem; } } struct header_element_data elem_none = { .widget=NULL}; return elem_none; } static bool streq(const char *str1, const char *str2) { if (!str1 && !str2) return true; if (str1 && str2) return strcmp(str1, str2) == 0; return false; } enum decoration_type { DECORATION_TYPE_NONE, DECORATION_TYPE_ALL, DECORATION_TYPE_TITLE_ONLY }; enum component { NONE = 0, SHADOW, HEADER, }; struct seat { struct libdecor_plugin_gtk *plugin_gtk; char *name; struct wl_seat *wl_seat; struct wl_pointer *wl_pointer; struct wl_touch *wl_touch; struct wl_surface *cursor_surface; struct wl_cursor *current_cursor; int cursor_scale; struct wl_list cursor_outputs; struct wl_cursor_theme *cursor_theme; /* cursors for resize edges and corners */ struct wl_cursor *cursors[ARRAY_LENGTH(cursor_names)]; struct wl_cursor *cursor_left_ptr; struct wl_surface *pointer_focus; struct wl_surface *touch_focus; int pointer_x, pointer_y; uint32_t pointer_button_time_stamp; uint32_t touch_down_time_stamp; uint32_t serial; bool grabbed; struct wl_list link; }; struct output { struct libdecor_plugin_gtk *plugin_gtk; struct wl_output *wl_output; uint32_t id; int scale; struct wl_list link; }; struct buffer { struct wl_buffer *wl_buffer; bool in_use; bool is_detached; void *data; size_t data_size; int width; int height; int scale; int buffer_width; int buffer_height; }; struct border_component { enum component type; struct wl_surface *wl_surface; struct wl_subsurface *wl_subsurface; struct buffer *buffer; bool opaque; struct wl_list output_list; int scale; struct wl_list child_components; /* border_component::link */ struct wl_list link; /* border_component::child_components */ }; struct surface_output { struct output *output; struct wl_list link; }; struct cursor_output { struct output *output; struct wl_list link; }; struct libdecor_frame_gtk { struct libdecor_frame frame; struct libdecor_plugin_gtk *plugin_gtk; int content_width; int content_height; enum libdecor_window_state window_state; enum decoration_type decoration_type; char *title; enum libdecor_capabilities capabilities; struct border_component *active; struct border_component *touch_active; struct border_component *focus; struct border_component *grab; bool shadow_showing; struct border_component shadow; GtkWidget *window; /* offscreen window for rendering */ GtkWidget *header; /* header bar with widgets */ struct border_component headerbar; struct header_element_data hdr_focus; /* store pre-processed shadow tile */ cairo_surface_t *shadow_blur; struct wl_list link; }; struct 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; int double_click_time_ms; }; static const char *libdecor_gtk_proxy_tag = "libdecor-gtk"; static bool own_proxy(struct wl_proxy *proxy) { if (!proxy) return false; return (wl_proxy_get_tag(proxy) == &libdecor_gtk_proxy_tag); } static bool own_surface(struct wl_surface *surface) { return own_proxy((struct wl_proxy *) surface); } static bool own_output(struct wl_output *output) { return own_proxy((struct wl_proxy *) output); } static bool moveable(struct libdecor_frame_gtk *frame_gtk) { return libdecor_frame_has_capability(&frame_gtk->frame, LIBDECOR_ACTION_MOVE); } static bool resizable(struct libdecor_frame_gtk *frame_gtk) { return libdecor_frame_has_capability(&frame_gtk->frame, LIBDECOR_ACTION_RESIZE); } static bool minimizable(struct libdecor_frame_gtk *frame_gtk) { return libdecor_frame_has_capability(&frame_gtk->frame, LIBDECOR_ACTION_MINIMIZE); } static bool closeable(struct libdecor_frame_gtk *frame_gtk) { return libdecor_frame_has_capability(&frame_gtk->frame, LIBDECOR_ACTION_CLOSE); } static void buffer_free(struct buffer *buffer); static void draw_border_component(struct libdecor_frame_gtk *frame_gtk, struct border_component *border_component, enum component component); static void send_cursor(struct seat *seat); static bool update_local_cursor(struct seat *seat); static void libdecor_plugin_gtk_destroy(struct libdecor_plugin *plugin) { struct libdecor_plugin_gtk *plugin_gtk = (struct libdecor_plugin_gtk *) plugin; struct seat *seat, *seat_tmp; struct output *output, *output_tmp; struct libdecor_frame_gtk *frame, *frame_tmp; if (plugin_gtk->globals_callback) wl_callback_destroy(plugin_gtk->globals_callback); if (plugin_gtk->globals_callback_shm) wl_callback_destroy(plugin_gtk->globals_callback_shm); if (plugin_gtk->shm_callback) wl_callback_destroy(plugin_gtk->shm_callback); wl_registry_destroy(plugin_gtk->wl_registry); wl_list_for_each_safe(seat, seat_tmp, &plugin_gtk->seat_list, link) { struct cursor_output *cursor_output, *tmp; if (seat->wl_pointer) wl_pointer_destroy(seat->wl_pointer); if (seat->wl_touch) wl_touch_destroy(seat->wl_touch); if (seat->cursor_surface) wl_surface_destroy(seat->cursor_surface); wl_seat_destroy(seat->wl_seat); if (seat->cursor_theme) wl_cursor_theme_destroy(seat->cursor_theme); wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) { wl_list_remove(&cursor_output->link); free(cursor_output); } free(seat->name); free(seat); } wl_list_for_each_safe(output, output_tmp, &plugin_gtk->output_list, link) { if (wl_output_get_version (output->wl_output) >= WL_OUTPUT_RELEASE_SINCE_VERSION) wl_output_release(output->wl_output); else wl_output_destroy(output->wl_output); free(output); } wl_list_for_each_safe(frame, frame_tmp, &plugin_gtk->visible_frame_list, link) { wl_list_remove(&frame->link); } free(plugin_gtk->cursor_theme_name); if (plugin_gtk->wl_shm) wl_shm_destroy(plugin_gtk->wl_shm); if (plugin_gtk->wl_compositor) wl_compositor_destroy(plugin_gtk->wl_compositor); if (plugin_gtk->wl_subcompositor) wl_subcompositor_destroy(plugin_gtk->wl_subcompositor); libdecor_plugin_release(&plugin_gtk->plugin); free(plugin_gtk); } static struct libdecor_frame_gtk * libdecor_frame_gtk_new(struct libdecor_plugin_gtk *plugin_gtk) { struct libdecor_frame_gtk *frame_gtk = 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_iteration(NULL, FALSE)); while (wl_display_prepare_read(wl_display) != 0) dispatch_count += wl_display_dispatch_pending(wl_display); if (wl_display_flush(wl_display) < 0 && errno != EAGAIN) { wl_display_cancel_read(wl_display); return -errno; } fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN }; ret = poll(fds, ARRAY_SIZE (fds), timeout); if (ret > 0) { if (fds[0].revents & POLLIN) { wl_display_read_events(wl_display); dispatch_count += wl_display_dispatch_pending(wl_display); return dispatch_count; } else { wl_display_cancel_read(wl_display); return dispatch_count; } } else if (ret == 0) { wl_display_cancel_read(wl_display); return dispatch_count; } else { wl_display_cancel_read(wl_display); return -errno; } } static struct libdecor_frame * libdecor_plugin_gtk_frame_new(struct libdecor_plugin *plugin) { struct libdecor_plugin_gtk *plugin_gtk = (struct libdecor_plugin_gtk *) plugin; struct libdecor_frame_gtk *frame_gtk; frame_gtk = libdecor_frame_gtk_new(plugin_gtk); return &frame_gtk->frame; } static void toggle_maximized(struct libdecor_frame *const frame) { if (!resizable((struct libdecor_frame_gtk *)frame)) return; if (!(libdecor_frame_get_window_state(frame) & LIBDECOR_WINDOW_STATE_MAXIMIZED)) libdecor_frame_set_maximized(frame); else libdecor_frame_unset_maximized(frame); } static void buffer_release(void *user_data, struct wl_buffer *wl_buffer) { struct buffer *buffer = user_data; if (buffer->is_detached) buffer_free(buffer); else buffer->in_use = false; } static const struct wl_buffer_listener buffer_listener = { buffer_release }; static struct buffer * create_shm_buffer(struct libdecor_plugin_gtk *plugin_gtk, int width, int height, bool opaque, int scale) { struct wl_shm_pool *pool; int fd, size, buffer_width, buffer_height, stride; void *data; struct buffer *buffer; enum wl_shm_format buf_fmt; buffer_width = width * scale; buffer_height = height * scale; stride = buffer_width * 4; size = stride * buffer_height; fd = os_create_anonymous_file(size); if (fd < 0) { fprintf(stderr, "creating a buffer file for %d B failed: %s\n", size, strerror(errno)); return NULL; } data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { fprintf(stderr, "mmap failed: %s\n", strerror(errno)); close(fd); return NULL; } buf_fmt = opaque ? WL_SHM_FORMAT_XRGB8888 : WL_SHM_FORMAT_ARGB8888; pool = wl_shm_create_pool(plugin_gtk->wl_shm, fd, size); buffer = 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) { if (border_component->wl_surface) { wl_subsurface_destroy(border_component->wl_subsurface); border_component->wl_subsurface = NULL; wl_surface_destroy(border_component->wl_surface); border_component->wl_surface = NULL; } if (border_component->buffer) { buffer_free(border_component->buffer); border_component->buffer = NULL; } if (border_component->output_list.next != NULL) { struct surface_output *surface_output, *surface_output_tmp; wl_list_for_each_safe(surface_output, surface_output_tmp, &border_component->output_list, link) { wl_list_remove(&surface_output->link); free(surface_output); } } } static void libdecor_plugin_gtk_frame_free(struct libdecor_plugin *plugin, struct libdecor_frame *frame) { struct libdecor_frame_gtk *frame_gtk = (struct libdecor_frame_gtk *) frame; g_clear_pointer (&frame_gtk->header, gtk_widget_destroy); g_clear_pointer (&frame_gtk->window, gtk_widget_destroy); free_border_component(&frame_gtk->headerbar); free_border_component(&frame_gtk->shadow); frame_gtk->shadow_showing = false; g_clear_pointer (&frame_gtk->shadow_blur, cairo_surface_destroy); g_clear_pointer (&frame_gtk->title, free); frame_gtk->decoration_type = DECORATION_TYPE_NONE; if (frame_gtk->link.next != NULL) wl_list_remove(&frame_gtk->link); } static bool is_border_surfaces_showing(struct libdecor_frame_gtk *frame_gtk) { return frame_gtk->shadow_showing; } static void hide_border_component(struct border_component *border_component) { if (!border_component->wl_surface) return; wl_surface_attach(border_component->wl_surface, NULL, 0, 0); wl_surface_commit(border_component->wl_surface); } static void hide_border_surfaces(struct libdecor_frame_gtk *frame_gtk) { hide_border_component(&frame_gtk->shadow); frame_gtk->shadow_showing = false; } static struct border_component * get_component_for_surface(struct libdecor_frame_gtk *frame_gtk, const struct wl_surface *surface) { if (frame_gtk->shadow.wl_surface == surface) return &frame_gtk->shadow; if (frame_gtk->headerbar.wl_surface == surface) return &frame_gtk->headerbar; return NULL; } static bool redraw_scale(struct libdecor_frame_gtk *frame_gtk, struct border_component *cmpnt) { struct surface_output *surface_output; int scale = 1; if (cmpnt->wl_surface == NULL) return false; wl_list_for_each(surface_output, &cmpnt->output_list, link) { scale = MAX(scale, surface_output->output->scale); } if (scale != cmpnt->scale) { cmpnt->scale = scale; if ((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, const struct wl_output *wl_output) { struct surface_output *surface_output; wl_list_for_each(surface_output, list, link) { if (surface_output->output->wl_output == wl_output) { wl_list_remove(&surface_output->link); free(surface_output); return true; } } return false; } static void surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct libdecor_frame_gtk *frame_gtk = data; struct border_component *cmpnt; if (!(own_surface(wl_surface) && own_output(wl_output))) return; cmpnt = get_component_for_surface(frame_gtk, wl_surface); if (cmpnt == NULL) return; if (!remove_surface_output(&cmpnt->output_list, wl_output)) return; if (redraw_scale(frame_gtk, cmpnt)) libdecor_frame_toplevel_commit(&frame_gtk->frame); } static struct wl_surface_listener surface_listener = { surface_enter, surface_leave, }; static void create_surface_subsurface_pair(struct libdecor_frame_gtk *frame_gtk, struct wl_surface **out_wl_surface, struct wl_subsurface **out_wl_subsurface) { struct libdecor_plugin_gtk *plugin_gtk = frame_gtk->plugin_gtk; struct libdecor_frame *frame = &frame_gtk->frame; struct wl_compositor *wl_compositor = plugin_gtk->wl_compositor; struct wl_subcompositor *wl_subcompositor = plugin_gtk->wl_subcompositor; struct wl_surface *wl_surface; struct wl_surface *parent; struct wl_subsurface *wl_subsurface; wl_surface = wl_compositor_create_surface(wl_compositor); wl_proxy_set_tag((struct wl_proxy *) wl_surface, &libdecor_gtk_proxy_tag); parent = libdecor_frame_get_wl_surface(frame); wl_subsurface = wl_subcompositor_get_subsurface(wl_subcompositor, wl_surface, parent); *out_wl_surface = wl_surface; *out_wl_subsurface = wl_subsurface; } static void ensure_component(struct libdecor_frame_gtk *frame_gtk, struct border_component *cmpnt) { if (!cmpnt->wl_surface) { wl_list_init(&cmpnt->output_list); cmpnt->scale = 1; create_surface_subsurface_pair(frame_gtk, &cmpnt->wl_surface, &cmpnt->wl_subsurface); wl_surface_add_listener(cmpnt->wl_surface, &surface_listener, frame_gtk); } } static void ensure_border_surfaces(struct libdecor_frame_gtk *frame_gtk) { frame_gtk->shadow.type = SHADOW; frame_gtk->shadow.opaque = false; ensure_component(frame_gtk, &frame_gtk->shadow); } static void ensure_title_bar_surfaces(struct libdecor_frame_gtk *frame_gtk) { GtkStyleContext *context_hdr; frame_gtk->headerbar.type = HEADER; frame_gtk->headerbar.opaque = false; ensure_component(frame_gtk, &frame_gtk->headerbar); /* create an offscreen window with a header bar */ /* TODO: This should only be done once at frame consutrction, but then * the window and headerbar would not change style (e.g. backdrop) * after construction. So we just destroy and re-create them. */ /* avoid warning when restoring previously turned off decoration */ if (GTK_IS_WIDGET(frame_gtk->header)) { gtk_widget_destroy(frame_gtk->header); frame_gtk->header = NULL; } /* avoid warning when restoring previously turned off decoration */ if (GTK_IS_WIDGET(frame_gtk->window)) { gtk_widget_destroy(frame_gtk->window); frame_gtk->window = NULL; } frame_gtk->window = gtk_offscreen_window_new(); frame_gtk->header = gtk_header_bar_new(); g_object_get(gtk_widget_get_settings(frame_gtk->window), "gtk-double-click-time", &frame_gtk->plugin_gtk->double_click_time_ms, NULL); /* set as "default" decoration */ g_object_set(frame_gtk->header, "title", libdecor_frame_get_title(&frame_gtk->frame), "has-subtitle", FALSE, "show-close-button", TRUE, NULL); context_hdr = gtk_widget_get_style_context(frame_gtk->header); gtk_style_context_add_class(context_hdr, GTK_STYLE_CLASS_TITLEBAR); gtk_style_context_add_class(context_hdr, "default-decoration"); gtk_window_set_titlebar(GTK_WINDOW(frame_gtk->window), frame_gtk->header); gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(frame_gtk->header), TRUE); gtk_window_set_resizable(GTK_WINDOW(frame_gtk->window), resizable(frame_gtk)); } static void calculate_component_size(struct libdecor_frame_gtk *frame_gtk, enum component component, int *component_x, int *component_y, int *component_width, int *component_height) { struct libdecor_frame *frame = &frame_gtk->frame; int content_width, content_height; content_width = libdecor_frame_get_content_width(frame); content_height = libdecor_frame_get_content_height(frame); /* avoid warning when restoring previously turned off decoration */ const int title_height = GTK_IS_WIDGET(frame_gtk->header) ? gtk_widget_get_allocated_height(frame_gtk->header) : 0; switch (component) { case NONE: *component_width = 0; *component_height = 0; return; case SHADOW: *component_x = -(int)SHADOW_MARGIN; *component_y = -(int)(SHADOW_MARGIN+title_height); *component_width = content_width + 2 * SHADOW_MARGIN; *component_height = content_height + 2 * SHADOW_MARGIN + title_height; return; case HEADER: *component_x = 0; /* reuse product of function call above */ *component_y = - title_height; *component_width = gtk_widget_get_allocated_width(frame_gtk->header); /* reuse product of function call above */ *component_height = title_height; return; } abort(); } static void array_append(enum header_element **array, size_t *n, enum header_element item) { (*n)++; *array = realloc(*array, (*n) * sizeof (enum header_element)); (*array)[(*n)-1] = item; } static void draw_header_background(struct libdecor_frame_gtk *frame_gtk, cairo_t *cr) { /* background */ GtkAllocation allocation; GtkStyleContext* style; gtk_widget_get_allocation(GTK_WIDGET(frame_gtk->header), &allocation); style = gtk_widget_get_style_context(frame_gtk->header); gtk_render_background(style, cr, allocation.x, allocation.y, allocation.width, allocation.height); } static void draw_header_title(struct libdecor_frame_gtk *frame_gtk, cairo_surface_t *surface) { /* title */ GtkWidget *label; GtkAllocation allocation; cairo_surface_t *label_surface = NULL; cairo_t *cr; label = find_widget_by_type(frame_gtk->header, HEADER_TITLE).widget; gtk_widget_get_allocation(label, &allocation); /* create subsection in which to draw label */ label_surface = cairo_surface_create_for_rectangle( surface, allocation.x, allocation.y, allocation.width, allocation.height); cr = cairo_create(label_surface); gtk_widget_size_allocate(label, &allocation); gtk_widget_draw(label, cr); cairo_destroy(cr); cairo_surface_destroy(label_surface); } static void draw_header_button(struct libdecor_frame_gtk *frame_gtk, cairo_t *cr, cairo_surface_t *surface, enum header_element button_type, enum libdecor_window_state window_state) { struct header_element_data elem; GtkWidget *button; GtkStyleContext* button_style; GtkStateFlags style_state; GtkAllocation allocation; gchar *icon_name; int scale; GtkWidget *icon_widget; GtkAllocation allocation_icon; GtkIconInfo* icon_info; double sx, sy; gint icon_width, icon_height; GdkPixbuf* icon_pixbuf; cairo_surface_t* icon_surface; gint width = 0, height = 0; gint left = 0, top = 0, right = 0, bottom = 0; GtkBorder border; GtkBorder padding; elem = find_widget_by_type(frame_gtk->header, button_type); button = elem.widget; if (!button) return; button_style = gtk_widget_get_style_context(button); style_state = elem.state; /* change style based on window state and focus */ if (!(window_state & LIBDECOR_WINDOW_STATE_ACTIVE)) { style_state |= GTK_STATE_FLAG_BACKDROP; } if (frame_gtk->hdr_focus.widget == button) { style_state |= GTK_STATE_FLAG_PRELIGHT; if (frame_gtk->hdr_focus.state & GTK_STATE_FLAG_ACTIVE) { style_state |= GTK_STATE_FLAG_ACTIVE; } } /* background */ gtk_widget_get_clip(button, &allocation); gtk_style_context_save(button_style); gtk_style_context_set_state(button_style, style_state); gtk_render_background(button_style, cr, allocation.x, allocation.y, allocation.width, allocation.height); gtk_render_frame(button_style, cr, allocation.x, allocation.y, allocation.width, allocation.height); gtk_style_context_restore(button_style); /* symbol */ switch (button_type) { case HEADER_MIN: icon_name = "window-minimize-symbolic"; break; case HEADER_MAX: icon_name = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) ? "window-restore-symbolic" : "window-maximize-symbolic"; break; case HEADER_CLOSE: icon_name = "window-close-symbolic"; break; default: icon_name = NULL; break; } /* get scale */ cairo_surface_get_device_scale(surface, &sx, &sy); scale = (sx+sy) / 2.0; /* get original icon dimensions */ icon_widget = gtk_bin_get_child(GTK_BIN(button)); gtk_widget_get_allocation(icon_widget, &allocation_icon); /* icon info */ if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &icon_width, &icon_height)) { icon_width = 16; icon_height = 16; } icon_info = gtk_icon_theme_lookup_icon_for_scale( gtk_icon_theme_get_default(), icon_name, icon_width, scale, (GtkIconLookupFlags)0); /* icon pixel buffer*/ gtk_style_context_save(button_style); gtk_style_context_set_state(button_style, style_state); icon_pixbuf = gtk_icon_info_load_symbolic_for_context( icon_info, button_style, NULL, NULL); icon_surface = gdk_cairo_surface_create_from_pixbuf(icon_pixbuf, scale, NULL); gtk_style_context_restore(button_style); /* dimensions and position */ gtk_style_context_get(button_style, gtk_style_context_get_state(button_style), "min-width", &width, "min-height", &height, NULL); if (width < icon_width) width = icon_width; if (height < icon_height) height = icon_height; gtk_style_context_get_border(button_style, gtk_style_context_get_state(button_style), &border); left += border.left; right += border.right; top += border.top; bottom += border.bottom; gtk_style_context_get_padding(button_style, gtk_style_context_get_state(button_style), &padding); left += padding.left; right += padding.right; top += padding.top; bottom += padding.bottom; width += left + right; height += top + bottom; gtk_render_icon_surface(gtk_widget_get_style_context(icon_widget), cr, icon_surface, allocation.x + ((width - icon_width) / 2), allocation.y + ((height - icon_height) / 2)); cairo_paint(cr); cairo_surface_destroy(icon_surface); g_object_unref(icon_pixbuf); } static void draw_header_buttons(struct libdecor_frame_gtk *frame_gtk, cairo_t *cr, cairo_surface_t *surface) { /* buttons */ enum libdecor_window_state window_state; enum header_element *buttons = NULL; size_t nbuttons = 0; window_state = libdecor_frame_get_window_state( (struct libdecor_frame*)frame_gtk); /* set buttons by capability */ if (minimizable(frame_gtk)) array_append(&buttons, &nbuttons, HEADER_MIN); if (resizable(frame_gtk)) array_append(&buttons, &nbuttons, HEADER_MAX); if (closeable(frame_gtk)) array_append(&buttons, &nbuttons, HEADER_CLOSE); for (size_t i = 0; i < nbuttons; i++) { draw_header_button(frame_gtk, cr, surface, buttons[i], window_state); } /* loop buttons */ free(buttons); } static void draw_header(struct libdecor_frame_gtk *frame_gtk, cairo_t *cr, cairo_surface_t *surface) { draw_header_background(frame_gtk, cr); draw_header_title(frame_gtk, surface); draw_header_buttons(frame_gtk, cr, surface); } static void draw_component_content(struct libdecor_frame_gtk *frame_gtk, struct buffer *buffer, int component_width, int component_height, enum component component) { cairo_surface_t *surface; cairo_t *cr; /* clear buffer */ memset(buffer->data, 0, buffer->data_size); surface = cairo_image_surface_create_for_data( buffer->data, CAIRO_FORMAT_ARGB32, buffer->buffer_width, buffer->buffer_height, cairo_format_stride_for_width( CAIRO_FORMAT_ARGB32, buffer->buffer_width) ); cr = cairo_create(surface); cairo_surface_set_device_scale(surface, buffer->scale, buffer->scale); /* background */ switch (component) { case NONE: break; case SHADOW: render_shadow(cr, frame_gtk->shadow_blur, -(int)SHADOW_MARGIN/2, -(int)SHADOW_MARGIN/2, buffer->width + SHADOW_MARGIN, buffer->height + SHADOW_MARGIN, 64, 64); break; case HEADER: draw_header(frame_gtk, cr, surface); break; } /* mask the toplevel surface */ if (component == SHADOW) { int component_x, component_y, component_width, component_height; calculate_component_size(frame_gtk, component, &component_x, &component_y, &component_width, &component_height); cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_rectangle(cr, -component_x, -component_y, libdecor_frame_get_content_width( &frame_gtk->frame), libdecor_frame_get_content_height( &frame_gtk->frame)); cairo_fill(cr); } cairo_destroy(cr); cairo_surface_destroy(surface); } static void set_component_input_region(struct libdecor_frame_gtk *frame_gtk, struct border_component *border_component) { if (border_component->type == SHADOW && frame_gtk->shadow_showing) { struct wl_region *input_region; int component_x; int component_y; int component_width; int component_height; calculate_component_size(frame_gtk, border_component->type, &component_x, &component_y, &component_width, &component_height); /* * the input region is the outer surface size minus the inner * content size */ input_region = wl_compositor_create_region( frame_gtk->plugin_gtk->wl_compositor); wl_region_add(input_region, 0, 0, component_width, component_height); wl_region_subtract(input_region, -component_x, -component_y, libdecor_frame_get_content_width(&frame_gtk->frame), libdecor_frame_get_content_height(&frame_gtk->frame)); wl_surface_set_input_region(border_component->wl_surface, input_region); wl_region_destroy(input_region); } } static void draw_border_component(struct libdecor_frame_gtk *frame_gtk, struct border_component *border_component, enum component component) { struct libdecor_plugin_gtk *plugin_gtk = frame_gtk->plugin_gtk; struct buffer *old_buffer; struct buffer *buffer = NULL; int component_x; int component_y; int component_width; int component_height; int scale = border_component->scale; if (border_component->wl_surface == NULL) return; calculate_component_size(frame_gtk, component, &component_x, &component_y, &component_width, &component_height); set_component_input_region(frame_gtk, border_component); old_buffer = border_component->buffer; if (old_buffer) { if (!old_buffer->in_use && old_buffer->buffer_width == component_width * scale && old_buffer->buffer_height == component_height * scale) { buffer = old_buffer; } else { buffer_free(old_buffer); border_component->buffer = NULL; } } if (!buffer) buffer = create_shm_buffer(plugin_gtk, component_width, component_height, border_component->opaque, border_component->scale); draw_component_content(frame_gtk, buffer, component_width, component_height, component); wl_surface_attach(border_component->wl_surface, buffer->wl_buffer, 0, 0); wl_surface_set_buffer_scale(border_component->wl_surface, buffer->scale); buffer->in_use = true; wl_surface_commit(border_component->wl_surface); wl_surface_damage_buffer(border_component->wl_surface, 0, 0, component_width * scale, component_height * scale); wl_subsurface_set_position(border_component->wl_subsurface, component_x, component_y); border_component->buffer = buffer; } static void draw_border(struct libdecor_frame_gtk *frame_gtk) { draw_border_component(frame_gtk, &frame_gtk->shadow, SHADOW); frame_gtk->shadow_showing = true; } static void draw_title_bar(struct libdecor_frame_gtk *frame_gtk) { GtkAllocation allocation = {0, 0, frame_gtk->content_width, 0}; enum libdecor_window_state state; GtkStyleContext *style; int pref_width; int current_min_w, current_min_h, current_max_w, current_max_h, W, H; state = libdecor_frame_get_window_state((struct libdecor_frame*)frame_gtk); style = gtk_widget_get_style_context(frame_gtk->window); if (!(state & LIBDECOR_WINDOW_STATE_ACTIVE)) { gtk_widget_set_state_flags(frame_gtk->window, GTK_STATE_FLAG_BACKDROP, true); } else { gtk_widget_unset_state_flags(frame_gtk->window, GTK_STATE_FLAG_BACKDROP); } if (libdecor_frame_is_floating(&frame_gtk->frame)) { gtk_style_context_remove_class(style, "maximized"); } else { gtk_style_context_add_class(style, "maximized"); } gtk_widget_show_all(frame_gtk->window); /* set default width, using an empty title to estimate its smallest admissible value */ gtk_header_bar_set_title(GTK_HEADER_BAR(frame_gtk->header), ""); gtk_widget_get_preferred_width(frame_gtk->header, NULL, &pref_width); gtk_header_bar_set_title(GTK_HEADER_BAR(frame_gtk->header), libdecor_frame_get_title(&frame_gtk->frame)); libdecor_frame_get_min_content_size(&frame_gtk->frame, ¤t_min_w, ¤t_min_h); if (current_min_w < pref_width) { current_min_w = pref_width; libdecor_frame_set_min_content_size(&frame_gtk->frame, current_min_w, current_min_h); } libdecor_frame_get_max_content_size(&frame_gtk->frame, ¤t_max_w, ¤t_max_h); if (current_max_w && current_max_w < current_min_w) { libdecor_frame_set_max_content_size(&frame_gtk->frame, current_min_w, current_max_h); } W = libdecor_frame_get_content_width(&frame_gtk->frame); H = libdecor_frame_get_content_height(&frame_gtk->frame); if (W < current_min_w) { W = current_min_w; struct libdecor_state *libdecor_state = libdecor_state_new(W, H); libdecor_frame_commit(&frame_gtk->frame, libdecor_state, NULL); libdecor_state_free(libdecor_state); return; } /* set default height */ gtk_widget_get_preferred_height(frame_gtk->header, NULL, &allocation.height); gtk_widget_size_allocate(frame_gtk->header, &allocation); draw_border_component(frame_gtk, &frame_gtk->headerbar, HEADER); } static void draw_decoration(struct libdecor_frame_gtk *frame_gtk) { switch (frame_gtk->decoration_type) { case DECORATION_TYPE_NONE: if (frame_gtk->link.next != NULL) wl_list_remove(&frame_gtk->link); if (is_border_surfaces_showing(frame_gtk)) hide_border_surfaces(frame_gtk); hide_border_component(&frame_gtk->headerbar); break; case DECORATION_TYPE_ALL: /* show borders */ ensure_border_surfaces(frame_gtk); draw_border(frame_gtk); /* show title bar */ ensure_title_bar_surfaces(frame_gtk); draw_title_bar(frame_gtk); /* link frame */ if (frame_gtk->link.next == NULL) wl_list_insert( &frame_gtk->plugin_gtk->visible_frame_list, &frame_gtk->link); break; case DECORATION_TYPE_TITLE_ONLY: /* hide borders */ if (is_border_surfaces_showing(frame_gtk)) hide_border_surfaces(frame_gtk); /* show title bar */ ensure_title_bar_surfaces(frame_gtk); draw_title_bar(frame_gtk); /* link frame */ if (frame_gtk->link.next == NULL) wl_list_insert( &frame_gtk->plugin_gtk->visible_frame_list, &frame_gtk->link); break; } } static enum decoration_type window_state_to_decoration_type(enum libdecor_window_state window_state) { if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) return DECORATION_TYPE_NONE; else if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED || window_state & LIBDECOR_WINDOW_STATE_TILED_LEFT || window_state & LIBDECOR_WINDOW_STATE_TILED_RIGHT || window_state & LIBDECOR_WINDOW_STATE_TILED_TOP || window_state & LIBDECOR_WINDOW_STATE_TILED_BOTTOM) /* title bar, no shadows */ return DECORATION_TYPE_TITLE_ONLY; else /* title bar, shadows */ return DECORATION_TYPE_ALL; } static void libdecor_plugin_gtk_frame_commit(struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_state *state, struct libdecor_configuration *configuration) { struct libdecor_frame_gtk *frame_gtk = (struct libdecor_frame_gtk *) frame; enum libdecor_window_state old_window_state; enum libdecor_window_state new_window_state; int old_content_width, old_content_height; int new_content_width, new_content_height; enum decoration_type old_decoration_type; enum decoration_type new_decoration_type; old_window_state = frame_gtk->window_state; new_window_state = libdecor_frame_get_window_state(frame); old_content_width = frame_gtk->content_width; old_content_height = frame_gtk->content_height; new_content_width = libdecor_frame_get_content_width(frame); new_content_height = libdecor_frame_get_content_height(frame); old_decoration_type = frame_gtk->decoration_type; new_decoration_type = window_state_to_decoration_type(new_window_state); if (old_decoration_type == new_decoration_type && old_content_width == new_content_width && old_content_height == new_content_height && old_window_state == new_window_state) return; frame_gtk->content_width = new_content_width; frame_gtk->content_height = new_content_height; frame_gtk->window_state = new_window_state; frame_gtk->decoration_type = new_decoration_type; draw_decoration(frame_gtk); /* set fixed window size */ if (!resizable(frame_gtk)) { libdecor_frame_set_min_content_size(frame, frame_gtk->content_width, frame_gtk->content_height); libdecor_frame_set_max_content_size(frame, frame_gtk->content_width, frame_gtk->content_height); } } static void libdecor_plugin_gtk_frame_property_changed(struct libdecor_plugin *plugin, struct libdecor_frame *frame) { struct libdecor_frame_gtk *frame_gtk = (struct libdecor_frame_gtk *) frame; bool redraw_needed = false; const char *new_title; /* * when in SSD mode, the window title is not to be managed by GTK; * this is detected by frame_gtk->header not being a proper GTK widget */ if (!GTK_IS_WIDGET(frame_gtk->header)) return; new_title = libdecor_frame_get_title(frame); if (!streq(frame_gtk->title, new_title)) redraw_needed = true; free(frame_gtk->title); frame_gtk->title = NULL; if (new_title) frame_gtk->title = strdup(new_title); if (frame_gtk->capabilities != libdecor_frame_get_capabilities(frame)) { frame_gtk->capabilities = libdecor_frame_get_capabilities(frame); redraw_needed = true; } if (redraw_needed) { draw_decoration(frame_gtk); libdecor_frame_toplevel_commit(frame); } } static void update_component_focus(struct libdecor_frame_gtk *frame_gtk, struct wl_surface *surface, struct seat *seat) { static struct border_component *border_component; static struct border_component *child_component; static struct border_component *focus_component; border_component = get_component_for_surface(frame_gtk, surface); focus_component = border_component; wl_list_for_each(child_component, &border_component->child_components, link) { int component_x = 0, component_y = 0; int component_width = 0, component_height = 0; calculate_component_size(frame_gtk, child_component->type, &component_x, &component_y, &component_width, &component_height); if (seat->pointer_x >= component_x && seat->pointer_x < component_x + component_width && seat->pointer_y >= component_y && seat->pointer_y < component_y + component_height) { focus_component = child_component; break; } } if (frame_gtk->grab) frame_gtk->active = frame_gtk->grab; else frame_gtk->active = focus_component; frame_gtk->focus = focus_component; } static void sync_active_component(struct libdecor_frame_gtk *frame_gtk, struct seat *seat) { struct border_component *old_active; if (!seat->pointer_focus) return; old_active = frame_gtk->active; update_component_focus(frame_gtk, seat->pointer_focus, seat); if (old_active != frame_gtk->active) { draw_decoration(frame_gtk); libdecor_frame_toplevel_commit(&frame_gtk->frame); } if (update_local_cursor(seat)) send_cursor(seat); } static void synthesize_pointer_enter(struct seat *seat) { struct wl_surface *surface; struct libdecor_frame_gtk *frame_gtk; surface = seat->pointer_focus; if (!surface) 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_frame_get_border_size(struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_configuration *configuration, int *left, int *right, int *top, int *bottom) { enum libdecor_window_state window_state; if (configuration) { if (!libdecor_configuration_get_window_state( configuration, &window_state)) return false; } else { window_state = libdecor_frame_get_window_state(frame); } if (left) *left = 0; if (right) *right = 0; if (bottom) *bottom = 0; if (top) { GtkWidget *header = ((struct libdecor_frame_gtk *)frame)->header; enum decoration_type type = window_state_to_decoration_type(window_state); /* avoid warnings after decoration has been turned off */ if (GTK_IS_WIDGET(header) && (type != DECORATION_TYPE_NONE)) { /* Redraw title bar to ensure size will be up-to-date */ if (configuration && type == DECORATION_TYPE_TITLE_ONLY) draw_title_bar((struct libdecor_frame_gtk *) frame); *top = gtk_widget_get_allocated_height(header); } else { *top = 0; } } 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_popup_grab = libdecor_plugin_gtk_frame_popup_grab, .frame_popup_ungrab = libdecor_plugin_gtk_frame_popup_ungrab, .frame_get_border_size = libdecor_plugin_gtk_frame_get_border_size, }; static void init_wl_compositor(struct libdecor_plugin_gtk *plugin_gtk, uint32_t id, uint32_t version) { plugin_gtk->wl_compositor = wl_registry_bind(plugin_gtk->wl_registry, id, &wl_compositor_interface, MIN(version, 4)); } static void init_wl_subcompositor(struct libdecor_plugin_gtk *plugin_gtk, uint32_t id, uint32_t version) { plugin_gtk->wl_subcompositor = wl_registry_bind(plugin_gtk->wl_registry, id, &wl_subcompositor_interface, 1); } static void shm_format(void *user_data, struct wl_shm *wl_shm, uint32_t format) { struct libdecor_plugin_gtk *plugin_gtk = user_data; if (format == WL_SHM_FORMAT_ARGB8888) plugin_gtk->has_argb = true; } struct wl_shm_listener shm_listener = { shm_format }; static void shm_callback(void *user_data, struct wl_callback *callback, uint32_t time) { struct libdecor_plugin_gtk *plugin_gtk = user_data; struct libdecor *context = plugin_gtk->context; wl_callback_destroy(callback); plugin_gtk->globals_callback_shm = NULL; if (!plugin_gtk->has_argb) { libdecor_notify_plugin_error( context, LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, "Compositor is missing required shm format"); return; } libdecor_notify_plugin_ready(context); } static const struct wl_callback_listener shm_callback_listener = { shm_callback }; static void init_wl_shm(struct libdecor_plugin_gtk *plugin_gtk, uint32_t id, uint32_t version) { struct libdecor *context = plugin_gtk->context; struct wl_display *wl_display = libdecor_get_wl_display(context); plugin_gtk->wl_shm = wl_registry_bind(plugin_gtk->wl_registry, id, &wl_shm_interface, 1); wl_shm_add_listener(plugin_gtk->wl_shm, &shm_listener, plugin_gtk); plugin_gtk->globals_callback_shm = wl_display_sync(wl_display); wl_callback_add_listener(plugin_gtk->globals_callback_shm, &shm_callback_listener, plugin_gtk); } static void cursor_surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct seat *seat = data; if (own_output(wl_output)) { struct cursor_output *cursor_output; cursor_output = 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 * 2; const bool bottom = pointer_y > (cmpnt->buffer->height - margin * 2); const bool left = pointer_x < margin * 2; const bool right = pointer_x > (cmpnt->buffer->width - margin * 2); if (top) { if (left) return LIBDECOR_RESIZE_EDGE_TOP_LEFT; else if (right) return LIBDECOR_RESIZE_EDGE_TOP_RIGHT; else return LIBDECOR_RESIZE_EDGE_TOP; } else if (bottom) { if (left) return LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT; else if (right) return LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT; else return LIBDECOR_RESIZE_EDGE_BOTTOM; } else if (left) { return LIBDECOR_RESIZE_EDGE_LEFT; } else if (right) { return LIBDECOR_RESIZE_EDGE_RIGHT; } else { return LIBDECOR_RESIZE_EDGE_NONE; } } static bool update_local_cursor(struct seat *seat) { if (!seat->pointer_focus) { seat->current_cursor = seat->cursor_left_ptr; return false; } if (!own_surface(seat->pointer_focus)) return false; struct libdecor_frame_gtk *frame_gtk = wl_surface_get_user_data(seat->pointer_focus); struct wl_cursor *wl_cursor = NULL; if (!frame_gtk || !frame_gtk->active) { seat->current_cursor = seat->cursor_left_ptr; return false; } bool theme_updated = ensure_cursor_theme(seat); if (frame_gtk->active->type == SHADOW && is_border_surfaces_showing(frame_gtk) && resizable(frame_gtk)) { enum libdecor_resize_edge edge; edge = component_edge(frame_gtk->active, seat->pointer_x, seat->pointer_y, SHADOW_MARGIN); if (edge != LIBDECOR_RESIZE_EDGE_NONE) wl_cursor = seat->cursors[edge - 1]; } else { wl_cursor = seat->cursor_left_ptr; } if (seat->current_cursor != wl_cursor) { seat->current_cursor = wl_cursor; return true; } return theme_updated; } static void send_cursor(struct seat *seat) { struct wl_cursor_image *image; struct wl_buffer *buffer; if (seat->pointer_focus == NULL || seat->current_cursor == NULL) return; image = seat->current_cursor->images[0]; buffer = wl_cursor_image_get_buffer(image); wl_surface_attach(seat->cursor_surface, buffer, 0, 0); wl_surface_set_buffer_scale(seat->cursor_surface, seat->cursor_scale); wl_surface_damage_buffer(seat->cursor_surface, 0, 0, image->width * seat->cursor_scale, image->height * seat->cursor_scale); wl_surface_commit(seat->cursor_surface); wl_pointer_set_cursor(seat->wl_pointer, seat->serial, seat->cursor_surface, image->hotspot_x / seat->cursor_scale, image->hotspot_y / seat->cursor_scale); } static void pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { if (!surface) return; struct seat *seat = data; struct libdecor_frame_gtk *frame_gtk; 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; frame_gtk->hdr_focus.type = HEADER_NONE; draw_decoration(frame_gtk); libdecor_frame_toplevel_commit(&frame_gtk->frame); update_local_cursor(seat); } } static void pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct seat *seat = data; struct libdecor_frame_gtk *frame_gtk; if (!seat->pointer_focus || !own_surface(seat->pointer_focus)) return; seat->pointer_x = wl_fixed_to_int(surface_x); seat->pointer_y = wl_fixed_to_int(surface_y); if (update_local_cursor(seat)) send_cursor(seat); frame_gtk = wl_surface_get_user_data(seat->pointer_focus); /* avoid warnings after decoration has been turned off */ if (GTK_IS_WIDGET(frame_gtk->header) && frame_gtk->active->type == HEADER) { 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 = HEADER_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 HEADER_MIN: case HEADER_MAX: case HEADER_CLOSE: frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_ACTIVE; draw_title_bar(frame_gtk); libdecor_frame_toplevel_commit(&frame_gtk->frame); break; default: if (time-seat->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: libdecor_frame_ref(&frame_gtk->frame); switch (frame_gtk->hdr_focus.type) { case HEADER_MIN: if (minimizable(frame_gtk)) libdecor_frame_set_minimized( &frame_gtk->frame); break; case HEADER_MAX: toggle_maximized(&frame_gtk->frame); break; case HEADER_CLOSE: if (closeable(frame_gtk)) { libdecor_frame_close( &frame_gtk->frame); seat->pointer_focus = NULL; } break; default: break; } /* unset active/clicked state once released */ frame_gtk->hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE; if (GTK_IS_WIDGET(frame_gtk->header)) { draw_title_bar(frame_gtk); libdecor_frame_toplevel_commit(&frame_gtk->frame); } libdecor_frame_unref(&frame_gtk->frame); break; default: break; } } } else if (button == BTN_RIGHT && state == WL_POINTER_BUTTON_STATE_PRESSED && seat->pointer_focus == frame_gtk->headerbar.wl_surface) { const int title_height = gtk_widget_get_allocated_height(frame_gtk->header); libdecor_frame_show_window_menu(&frame_gtk->frame, seat->wl_seat, serial, seat->pointer_x, seat->pointer_y -title_height); } } static void pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { } static struct wl_pointer_listener pointer_listener = { pointer_enter, pointer_leave, pointer_motion, pointer_button, pointer_axis }; static void update_touch_focus(struct seat *seat, struct libdecor_frame_gtk *frame_gtk, wl_fixed_t x, wl_fixed_t y) { /* avoid warnings after decoration has been turned off */ if (GTK_IS_WIDGET(frame_gtk->header) && frame_gtk->touch_active->type == HEADER) { struct header_element_data new_focus = get_header_focus( GTK_HEADER_BAR(frame_gtk->header), wl_fixed_to_int(x), wl_fixed_to_int(y)); /* only update if widget change so that we keep the state */ if (frame_gtk->hdr_focus.widget != new_focus.widget) { frame_gtk->hdr_focus = new_focus; } frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_PRELIGHT; /* redraw with updated button visuals */ draw_title_bar(frame_gtk); libdecor_frame_toplevel_commit(&frame_gtk->frame); } else { frame_gtk->hdr_focus.type = HEADER_NONE; } } static void touch_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { struct seat *seat = data; struct libdecor_frame_gtk *frame_gtk; if (!surface || !own_surface(surface)) return; frame_gtk = wl_surface_get_user_data(surface); if (!frame_gtk) return; seat->touch_focus = surface; frame_gtk->touch_active = get_component_for_surface(frame_gtk, surface); if (!frame_gtk->touch_active) return; update_touch_focus(seat, frame_gtk, x, y); /* update decorations */ draw_decoration(frame_gtk); libdecor_frame_toplevel_commit(&frame_gtk->frame); enum libdecor_resize_edge edge = LIBDECOR_RESIZE_EDGE_NONE; switch (frame_gtk->touch_active->type) { case SHADOW: edge = component_edge(frame_gtk->touch_active, wl_fixed_to_int(x), wl_fixed_to_int(y), SHADOW_MARGIN); break; case HEADER: switch (frame_gtk->hdr_focus.type) { case HEADER_MIN: case HEADER_MAX: case HEADER_CLOSE: frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_ACTIVE; draw_title_bar(frame_gtk); libdecor_frame_toplevel_commit(&frame_gtk->frame); break; default: if (time - seat->touch_down_time_stamp < (uint32_t)frame_gtk->plugin_gtk->double_click_time_ms) { toggle_maximized(&frame_gtk->frame); } else if (moveable(frame_gtk)) { seat->touch_down_time_stamp = time; libdecor_frame_move(&frame_gtk->frame, seat->wl_seat, serial); } break; } break; default: break; } if (edge != LIBDECOR_RESIZE_EDGE_NONE && resizable(frame_gtk)) { libdecor_frame_resize( &frame_gtk->frame, seat->wl_seat, serial, edge); } } static void touch_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { struct seat *seat = data; struct libdecor_frame_gtk *frame_gtk; if (!seat->touch_focus || !own_surface(seat->touch_focus)) return; frame_gtk = wl_surface_get_user_data(seat->touch_focus); if (!frame_gtk) return; if (!frame_gtk->touch_active) return; switch (frame_gtk->touch_active->type) { case HEADER: libdecor_frame_ref(&frame_gtk->frame); switch (frame_gtk->hdr_focus.type) { case HEADER_MIN: if (minimizable(frame_gtk)) { libdecor_frame_set_minimized( &frame_gtk->frame); } break; case HEADER_MAX: toggle_maximized(&frame_gtk->frame); break; case HEADER_CLOSE: if (closeable(frame_gtk)) { libdecor_frame_close( &frame_gtk->frame); seat->touch_focus = NULL; } break; default: break; } /* unset active/clicked state once released */ frame_gtk->hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE; if (GTK_IS_WIDGET(frame_gtk->header)) { draw_title_bar(frame_gtk); libdecor_frame_toplevel_commit(&frame_gtk->frame); } libdecor_frame_unref(&frame_gtk->frame); break; default: break; } seat->touch_focus = NULL; frame_gtk->touch_active = NULL; frame_gtk->hdr_focus.widget = NULL; frame_gtk->hdr_focus.type = HEADER_NONE; draw_decoration(frame_gtk); libdecor_frame_toplevel_commit(&frame_gtk->frame); } static void touch_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { struct seat *seat = data; struct libdecor_frame_gtk *frame_gtk; if (!seat->touch_focus || !own_surface(seat->touch_focus)) return; frame_gtk = wl_surface_get_user_data(seat->touch_focus); if (!frame_gtk) return; update_touch_focus(seat, frame_gtk, x, y); } static void touch_frame(void *data, struct wl_touch *wl_touch) { } static void touch_cancel(void *data, struct wl_touch *wl_touch) { } static struct wl_touch_listener touch_listener = { touch_down, touch_up, touch_motion, touch_frame, touch_cancel }; static void seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) { struct seat *seat = data; if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !seat->wl_pointer) { seat->wl_pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer) { wl_pointer_release(seat->wl_pointer); seat->wl_pointer = NULL; } if ((capabilities & WL_SEAT_CAPABILITY_TOUCH) && !seat->wl_touch) { seat->wl_touch = wl_seat_get_touch(wl_seat); wl_touch_add_listener(seat->wl_touch, &touch_listener, seat); } else if (!(capabilities & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch) { wl_touch_release(seat->wl_touch); seat->wl_touch = NULL; } } static void seat_name(void *data, struct wl_seat *wl_seat, const char *name) { /* avoid warning messages when opening/closing popup window */ struct seat *seat = (struct seat*)data; seat->name = strdup(name); } static struct wl_seat_listener seat_listener = { seat_capabilities, seat_name }; static void init_wl_seat(struct libdecor_plugin_gtk *plugin_gtk, uint32_t id, uint32_t version) { struct seat *seat; if (version < 3) { libdecor_notify_plugin_error( plugin_gtk->context, LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, "%s version 3 required but only version %i is available\n", wl_seat_interface.name, version); } seat = 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) { libdecor_notify_plugin_error( plugin_gtk->context, LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, "%s version 2 required but only version %i is available\n", wl_output_interface.name, version); } output = 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, MIN (version, 3)); wl_proxy_set_tag((struct wl_proxy *) output->wl_output, &libdecor_gtk_proxy_tag); wl_output_add_listener(output->wl_output, &output_listener, output); } static void registry_handle_global(void *user_data, struct wl_registry *wl_registry, uint32_t id, const char *interface, uint32_t version) { struct libdecor_plugin_gtk *plugin_gtk = user_data; if (strcmp(interface, "wl_compositor") == 0) init_wl_compositor(plugin_gtk, id, version); else if (strcmp(interface, "wl_subcompositor") == 0) init_wl_subcompositor(plugin_gtk, id, version); else if (strcmp(interface, "wl_shm") == 0) init_wl_shm(plugin_gtk, id, version); else if (strcmp(interface, "wl_seat") == 0) init_wl_seat(plugin_gtk, id, version); else if (strcmp(interface, "wl_output") == 0) init_wl_output(plugin_gtk, id, version); } static void remove_surface_outputs(struct border_component *cmpnt, const struct output *output) { struct surface_output *surface_output; wl_list_for_each(surface_output, &cmpnt->output_list, link) { if (surface_output->output == output) { wl_list_remove(&surface_output->link); free(surface_output); break; } } } static void output_removed(struct libdecor_plugin_gtk *plugin_gtk, struct output *output) { struct libdecor_frame_gtk *frame_gtk; struct seat *seat; wl_list_for_each(frame_gtk, &plugin_gtk->visible_frame_list, link) { remove_surface_outputs(&frame_gtk->shadow, output); } wl_list_for_each(seat, &plugin_gtk->seat_list, link) { struct cursor_output *cursor_output; wl_list_for_each(cursor_output, &seat->cursor_outputs, link) { if (cursor_output->output == output) { wl_list_remove(&cursor_output->link); free(cursor_output); } } } wl_list_remove(&output->link); wl_output_destroy(output->wl_output); free(output); } static void registry_handle_global_remove(void *user_data, struct wl_registry *wl_registry, uint32_t name) { struct libdecor_plugin_gtk *plugin_gtk = user_data; struct output *output; wl_list_for_each(output, &plugin_gtk->output_list, link) { if (output->id == name) { output_removed(plugin_gtk, output); break; } } } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static bool has_required_globals(struct libdecor_plugin_gtk *plugin_gtk) { if (!plugin_gtk->wl_compositor) return false; if (!plugin_gtk->wl_subcompositor) return false; if (!plugin_gtk->wl_shm) return false; return true; } static void globals_callback(void *user_data, struct wl_callback *callback, uint32_t time) { struct libdecor_plugin_gtk *plugin_gtk = user_data; wl_callback_destroy(callback); plugin_gtk->globals_callback = NULL; } static const struct wl_callback_listener globals_callback_listener = { globals_callback }; static struct libdecor_plugin * libdecor_plugin_new(struct libdecor *context) { struct libdecor_plugin_gtk *plugin_gtk; struct wl_display *wl_display; plugin_gtk = zalloc(sizeof *plugin_gtk); libdecor_plugin_init(&plugin_gtk->plugin, context, >k_plugin_iface); plugin_gtk->context = context; wl_list_init(&plugin_gtk->visible_frame_list); wl_list_init(&plugin_gtk->seat_list); wl_list_init(&plugin_gtk->output_list); /* fetch cursor theme and size*/ if (!libdecor_get_cursor_settings(&plugin_gtk->cursor_theme_name, &plugin_gtk->cursor_size)) { plugin_gtk->cursor_theme_name = NULL; plugin_gtk->cursor_size = 24; } wl_display = libdecor_get_wl_display(context); plugin_gtk->wl_registry = wl_display_get_registry(wl_display); wl_registry_add_listener(plugin_gtk->wl_registry, ®istry_listener, plugin_gtk); plugin_gtk->globals_callback = wl_display_sync(wl_display); wl_callback_add_listener(plugin_gtk->globals_callback, &globals_callback_listener, plugin_gtk); wl_display_roundtrip(wl_display); if (!has_required_globals(plugin_gtk)) { fprintf(stderr, "libdecor-gtk-WARNING: Could not get required globals\n"); libdecor_plugin_gtk_destroy(&plugin_gtk->plugin); return NULL; } /* setup GTK context */ gdk_set_allowed_backends("wayland"); gtk_disable_setlocale(); if (!gtk_init_check(NULL, NULL)) { fprintf(stderr, "libdecor-gtk-WARNING: Failed to initialize GTK\n"); libdecor_plugin_gtk_destroy(&plugin_gtk->plugin); return NULL; } return &plugin_gtk->plugin; } static struct libdecor_plugin_priority priorities[] = { { NULL, LIBDECOR_PLUGIN_PRIORITY_HIGH } }; LIBDECOR_EXPORT const struct libdecor_plugin_description libdecor_plugin_description = { .api_version = LIBDECOR_PLUGIN_API_VERSION, .capabilities = LIBDECOR_PLUGIN_CAPABILITY_BASE, .description = "GTK3 plugin", .priorities = priorities, .constructor = libdecor_plugin_new, .conflicting_symbols = { "png_free", NULL, }, }; libdecor-0.2.2/src/plugins/gtk/meson.build000066400000000000000000000011521455131637200205030ustar00rootroot00000000000000wayland_cursor_dep = dependency('wayland-cursor') shared_module('decor-gtk', sources: [ 'libdecor-gtk.c', ], include_directories: [ top_includepath, libdecor_includepath, plugin_include_path, ], gnu_symbol_visibility: 'hidden', dependencies: [ libdecor_dep, cairo_dep, math_dep, wayland_cursor_dep, cursor_settings_dep, os_compatibility_dep, gtk_dep, ], link_with: plugin_common, install_dir: join_paths(plugindir), install: true, ) if meson.version().version_compare('>=0.58.0') devenv.append('LIBDECOR_PLUGIN_DIR', meson.current_build_dir()) endif libdecor-0.2.2/src/plugins/meson.build000066400000000000000000000003121455131637200177130ustar00rootroot00000000000000gtk_dep = dependency('gtk+-3.0', required: get_option('gtk')) plugin_include_path = include_directories('.') subdir('common') subdir('cairo') subdir('dummy') if gtk_dep.found() subdir('gtk') endif libdecor-0.2.2/src/utils.h000066400000000000000000000030211455131637200154010ustar00rootroot00000000000000/* * 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 #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 */