pax_global_header00006660000000000000000000000064140755233350014521gustar00rootroot0000000000000052 comment=3ec3fadd59a21835079fbb3046d2bec6c649d6fa libdecor-0.1.0/000077500000000000000000000000001407552333500133025ustar00rootroot00000000000000libdecor-0.1.0/.gitignore000066400000000000000000000000071407552333500152670ustar00rootroot00000000000000build/ libdecor-0.1.0/.gitlab-ci.yml000066400000000000000000000037701407552333500157450ustar00rootroot00000000000000include: - 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: '2021-07-16.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) meson FDO_DISTRIBUTION_EXEC: | dnf group install -y 'Development Tools' \ 'C Development Tools and Libraries' && 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_MERGE_REQUEST_IID' - if: '$CI_COMMIT_TAG' - if: '$CI_COMMIT_BRANCH' .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.1.0/LICENSE000066400000000000000000000020141407552333500143040ustar00rootroot00000000000000MIT 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.1.0/README.md000066400000000000000000000050201407552333500145560ustar00rootroot00000000000000# 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) - `xkbcommon` (to build cairo demo) Install via apt: `sudo apt install meson libwayland-dev wayland-protocols libpango1.0-dev libdbus-1-dev libegl-dev libxkbcommon-dev` Install via dnf: `sudo dnf install meson wayland-devel wayland-protocols-devel pango-devel dbus-devel mesa-libEGL-devel libxkbcommon-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 && meson compile -C build` 2. `LIBDECOR_PLUGIN_DIR=build/src/plugins/cairo/ ./build/demo/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 -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. 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`. libdecor-0.1.0/config.h.meson000066400000000000000000000003161407552333500160400ustar00rootroot00000000000000/* Version number of package */ #mesondefine VERSION /* Plugin directiory path */ #mesondefine LIBDECOR_PLUGIN_DIR /* Plugin API version */ #mesondefine LIBDECOR_PLUGIN_API_VERSION #mesondefine HAS_DBUS libdecor-0.1.0/demo/000077500000000000000000000000001407552333500142265ustar00rootroot00000000000000libdecor-0.1.0/demo/c++-demo.cc000066400000000000000000000222331407552333500160310ustar00rootroot00000000000000/* * 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" #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 = 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 int create_anonymous_file(off_t size) { int fd; int ret; fd = memfd_create("libdecor-demo", MFD_CLOEXEC | MFD_ALLOW_SEALING); if (fd < 0) return -1; fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK); do { ret = posix_fallocate(fd, 0, size); } while (ret == EINTR); if (ret != 0) { close(fd); errno = ret; return -1; } return fd; } static void 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) { 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; libdecor_configuration_get_content_size(configuration, frame, &width, &height); width = (width == 0) ? DEFAULT_WIDTH : width; height = (height == 0) ? DEFAULT_HEIGHT : height; this->configured_width = width; this->configured_height = height; if (!libdecor_configuration_get_window_state(configuration, &window_state)) window_state = LIBDECOR_WINDOW_STATE_NONE; this->window_state = window_state; state = libdecor_state_new(width, height); libdecor_frame_commit(frame, state, configuration); libdecor_state_free(state); 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; Buffer *buffer; int configured_width; int configured_height; enum libdecor_window_state window_state; }; 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 = { .error = 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.1.0/demo/demo.c000066400000000000000000000730751407552333500153320ustar00rootroot00000000000000/* * Copyright © 2011 Benjamin Franzke * Copyright © 2010 Intel Corporation * Copyright © 2018 Jonas Ådahl * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "libdecor.h" #include "utils.h" #include "cursor-settings.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) { 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; 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 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; } /* 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; 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 int create_anonymous_file(off_t size) { int fd; int ret; fd = memfd_create("libdecor-demo", MFD_CLOEXEC | MFD_ALLOW_SEALING); if (fd < 0) return -1; fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK); do { ret = posix_fallocate(fd, 0, size); } while (ret == EINTR); if (ret != 0) { close(fd); errno = ret; return -1; } return fd; } static 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 = 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; if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { width = window->content_width; height = window->content_height; } width = (width == 0) ? DEFAULT_WIDTH : width; height = (height == 0) ? DEFAULT_HEIGHT : height; window->configured_width = width; window->configured_height = height; if (!libdecor_configuration_get_window_state(configuration, &window_state)) window_state = LIBDECOR_WINDOW_STATE_NONE; window->window_state = window_state; state = libdecor_state_new(width, height); libdecor_frame_commit(frame, state, configuration); libdecor_state_free(state); 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; 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 wl_display *wl_display; struct wl_registry *wl_registry; struct libdecor *context; struct output *output; /* 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); 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); while (libdecor_dispatch(context, -1) >= 0); free_outputs(); free(window); return EXIT_SUCCESS; } libdecor-0.1.0/demo/egl.c000066400000000000000000000171371407552333500151520ustar00rootroot00000000000000/* * Copyright © 2011 Benjamin Franzke * Copyright © 2010 Intel Corporation * Copyright © 2018 Jonas Ådahl * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #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; 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)) { height = width = default_size; } 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); 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 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; 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"); 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; setup(window); 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: free(window); free(client); return ret; } libdecor-0.1.0/demo/meson.build000066400000000000000000000031361407552333500163730ustar00rootroot00000000000000add_languages('cpp') wayland_cursor_dep = dependency('wayland-cursor') math_dep = cc.find_library('m') egl_dep = dependency('egl') gl_dep = dependency('gl') 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, libdecor_includepath, ], dependencies: [ libdecor_dep, wayland_cursor_dep, cursor_settings_dep, xkb_dep], install: false, ) executable('libdecor-egl', sources: 'egl.c', include_directories: libdecor_includepath, dependencies: [ libdecor_dep, wayland_egl_dep, egl_dep, gl_dep, math_dep], install: false, ) executable('libdecor-c++-demo', sources: [ 'c++-demo.cc', ], cpp_args: [ '-Wno-unused-parameter', '-Wno-missing-field-initializers', ], include_directories: [ top_includepath, libdecor_includepath, ], dependencies: [ libdecor_dep, wayland_cursor_dep, cursor_settings_dep, xkb_dep], install: false, ) libdecor-0.1.0/meson.build000066400000000000000000000052001407552333500154410ustar00rootroot00000000000000project('libdecor', 'c', version: '0.1.0', meson_version: '>= 0.47.0', license: 'MIT' ) 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 = [ ['memfd_create', '#define _GNU_SOURCE\n#include '], ['posix_fallocate', '#include '], ['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')) pkgname = meson.project_name() pkglibdir = join_paths(libdir, pkgname) plugindir = join_paths(pkglibdir, 'plugins-@0@'.format(plugin_api_version_string)) libdecor_c_args = [ '-fvisibility=hidden', ] 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 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 libdecor-0.1.0/meson_options.txt000066400000000000000000000003071407552333500167370ustar00rootroot00000000000000option('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') libdecor-0.1.0/src/000077500000000000000000000000001407552333500140715ustar00rootroot00000000000000libdecor-0.1.0/src/cursor-settings.c000066400000000000000000000053271407552333500174170ustar00rootroot00000000000000#include "cursor-settings.h" #include #include #include #include "config.h" #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; dbus_error_init(&error); message = dbus_message_new_method_call( "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Settings", "Read"); success = dbus_message_append_args(message, DBUS_TYPE_STRING, &key, DBUS_TYPE_STRING, &value, DBUS_TYPE_INVALID); if (!success) return NULL; reply = dbus_connection_send_with_reply_and_block( connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error); dbus_message_unref(message); if (dbus_error_is_set(&error)) return NULL; return reply; } static bool parse_type(DBusMessage *const reply, const int type, void *value) { DBusMessageIter iter[3]; dbus_message_iter_init(reply, &iter[0]); if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) return false; dbus_message_iter_recurse(&iter[0], &iter[1]); if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) return false; dbus_message_iter_recurse(&iter[1], &iter[2]); if (dbus_message_iter_get_arg_type(&iter[2]) != type) return false; dbus_message_iter_get_basic(&iter[2], value); return true; } bool libdecor_get_cursor_settings(char **theme, int *size) { static const char name[] = "org.gnome.desktop.interface"; static const char key_theme[] = "cursor-theme"; static const char key_size[] = "cursor-size"; DBusError error; DBusConnection *connection; DBusMessage *reply; const char *value_theme = NULL; dbus_error_init(&error); connection = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) return false; reply = get_setting_sync(connection, name, key_theme); if (!reply) return false; if (!parse_type(reply, DBUS_TYPE_STRING, &value_theme)) { dbus_message_unref(reply); return false; } *theme = strdup(value_theme); dbus_message_unref(reply); reply = get_setting_sync(connection, name, key_size); if (!reply) return false; if (!parse_type(reply, DBUS_TYPE_INT32, size)) { dbus_message_unref(reply); return false; } dbus_message_unref(reply); return true; } #else bool libdecor_get_cursor_settings(char **theme, int *size) { char *env_xtheme; char *env_xsize; env_xtheme = getenv("XCURSOR_THEME"); if (env_xtheme != NULL) *theme = strdup(env_xtheme); env_xsize = getenv("XCURSOR_SIZE"); if (env_xsize != NULL) *size = atoi(env_xsize); return env_xtheme != NULL && env_xsize != NULL; } #endif libdecor-0.1.0/src/cursor-settings.h000066400000000000000000000001401407552333500174100ustar00rootroot00000000000000#pragma once #include bool libdecor_get_cursor_settings(char **theme, int *size); libdecor-0.1.0/src/libdecor-fallback.c000066400000000000000000000136431407552333500175640ustar00rootroot00000000000000/* * 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_translate_coordinate(struct libdecor_plugin *plugin, struct libdecor_frame *frame, int content_x, int content_y, int *frame_x, int *frame_y) { *frame_x = content_x; *frame_y = content_y; } static void libdecor_plugin_fallback_frame_popup_grab(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name) { } static void libdecor_plugin_fallback_frame_popup_ungrab(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name) { } static bool libdecor_plugin_fallback_configuration_get_content_size(struct libdecor_plugin *plugin, struct libdecor_configuration *configuration, struct libdecor_frame *frame, int *content_width, int *content_height) { return libdecor_configuration_get_window_size(configuration, content_width, content_height); } static bool libdecor_plugin_fallback_frame_get_window_size_for( struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_state *state, int *window_width, int *window_height) { *window_width = libdecor_state_get_content_width (state); *window_height = libdecor_state_get_content_height (state); return true; } static struct libdecor_plugin_interface fallback_plugin_iface = { .destroy = libdecor_plugin_fallback_destroy, .get_fd = libdecor_plugin_fallback_get_fd, .dispatch = libdecor_plugin_fallback_dispatch, .frame_new = libdecor_plugin_fallback_frame_new, .frame_free = libdecor_plugin_fallback_frame_free, .frame_commit = libdecor_plugin_fallback_frame_commit, .frame_property_changed = libdecor_plugin_fallback_frame_property_changed, .frame_translate_coordinate = libdecor_plugin_fallback_frame_translate_coordinate, .frame_popup_grab = libdecor_plugin_fallback_frame_popup_grab, .frame_popup_ungrab = libdecor_plugin_fallback_frame_popup_ungrab, .configuration_get_content_size = libdecor_plugin_fallback_configuration_get_content_size, .frame_get_window_size_for = libdecor_plugin_fallback_frame_get_window_size_for, }; struct libdecor_plugin * libdecor_fallback_plugin_new(struct libdecor *context) { struct libdecor_plugin_fallback *plugin; plugin = zalloc(sizeof *plugin); libdecor_plugin_init(&plugin->plugin, context, &fallback_plugin_iface); plugin->context = context; libdecor_notify_plugin_ready(context); return &plugin->plugin; } libdecor-0.1.0/src/libdecor-fallback.h000066400000000000000000000025461407552333500175710ustar00rootroot00000000000000/* * 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.1.0/src/libdecor-plugin.h000066400000000000000000000137361407552333500173330ustar00rootroot00000000000000/* * Copyright © 2017-2018 Red Hat Inc. * Copyright © 2018 Jonas Ådahl * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef LIBDECOR_PLUGIN_H #define LIBDECOR_PLUGIN_H #include "libdecor.h" struct libdecor_frame_private; struct libdecor_frame { struct libdecor_frame_private *priv; struct wl_list link; }; struct libdecor_plugin_private; struct libdecor_plugin { struct libdecor_plugin_private *priv; }; typedef struct libdecor_plugin * (* libdecor_plugin_constructor)(struct libdecor *context); #define LIBDECOR_PLUGIN_PRIORITY_HIGH 1000 #define LIBDECOR_PLUGIN_PRIORITY_MEDIUM 100 #define LIBDECOR_PLUGIN_PRIORITY_LOW 0 struct libdecor_plugin_priority { const char *desktop; int priority; }; enum libdecor_plugin_capabilities { LIBDECOR_PLUGIN_CAPABILITY_BASE = 1 << 0, }; struct libdecor_plugin_description { /* API version the plugin is compatible with. */ int api_version; /* Human readable string describing the plugin. */ char *description; /* A plugin has a bitmask of capabilities. The plugin loader can use this * to load a plugin with the right capabilities. */ enum libdecor_plugin_capabilities capabilities; /* * The priorities field points to a list of per desktop priorities. * properties[i].desktop is matched against XDG_CURRENT_DESKTOP when * determining what plugin to use. The last entry in the list MUST have * the priorities[i].desktop pointer set to NULL as a default * priority. */ const struct libdecor_plugin_priority *priorities; /* Vfunc used for constructing a plugin instance. */ libdecor_plugin_constructor constructor; }; struct libdecor_plugin_interface { void (* destroy)(struct libdecor_plugin *plugin); int (* get_fd)(struct libdecor_plugin *plugin); int (* dispatch)(struct libdecor_plugin *plugin, int timeout); struct libdecor_frame * (* frame_new)(struct libdecor_plugin *plugin); void (* frame_free)(struct libdecor_plugin *plugin, struct libdecor_frame *frame); void (* frame_commit)(struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_state *state, struct libdecor_configuration *configuration); void (*frame_property_changed)(struct libdecor_plugin *plugin, struct libdecor_frame *frame); void (* frame_translate_coordinate)(struct libdecor_plugin *plugin, struct libdecor_frame *frame, int content_x, int content_y, int *window_x, int *window_y); void (* frame_popup_grab)(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name); void (* frame_popup_ungrab)(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name); bool (* frame_get_window_size_for)(struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_state *state, int *window_width, int *window_height); bool (* configuration_get_content_size)(struct libdecor_plugin *plugin, struct libdecor_configuration *configuration, struct libdecor_frame *frame, int *content_width, int *content_height); /* Reserved */ void (* reserved0)(void); void (* reserved1)(void); void (* reserved2)(void); void (* reserved3)(void); void (* reserved4)(void); void (* reserved5)(void); void (* reserved6)(void); void (* reserved7)(void); void (* reserved8)(void); void (* reserved9)(void); }; struct wl_surface * libdecor_frame_get_wl_surface(struct libdecor_frame *frame); int libdecor_frame_get_content_width(struct libdecor_frame *frame); int libdecor_frame_get_content_height(struct libdecor_frame *frame); enum libdecor_window_state libdecor_frame_get_window_state(struct libdecor_frame *frame); void libdecor_frame_set_window_geometry(struct libdecor_frame *frame, int32_t x, int32_t y, int32_t width, int32_t height); enum libdecor_capabilities libdecor_frame_get_capabilities(const struct libdecor_frame *frame); void libdecor_frame_dismiss_popup(struct libdecor_frame *frame, const char *seat_name); void libdecor_frame_toplevel_commit(struct libdecor_frame *frame); struct wl_display * libdecor_get_wl_display(struct libdecor *context); void libdecor_notify_plugin_ready(struct libdecor *context); void libdecor_notify_plugin_error(struct libdecor *context, enum libdecor_error error, const char *__restrict fmt, ...); int libdecor_state_get_content_width (struct libdecor_state *state); int libdecor_state_get_content_height (struct libdecor_state *state); enum libdecor_window_state libdecor_state_get_window_state(struct libdecor_state *state); bool libdecor_configuration_get_window_size(struct libdecor_configuration *configuration, int *width, int *height); int libdecor_plugin_init(struct libdecor_plugin *plugin, struct libdecor *context, struct libdecor_plugin_interface *iface); void libdecor_plugin_release(struct libdecor_plugin *plugin); #endif /* LIBDECOR_PLUGIN_H */ libdecor-0.1.0/src/libdecor.c000066400000000000000000001224431407552333500160260ustar00rootroot00000000000000/* * Copyright © 2017-2018 Red Hat Inc. * Copyright © 2018 Jonas Ådahl * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include "libdecor.h" #include "libdecor-fallback.h" #include "libdecor-plugin.h" #include "utils.h" #include "xdg-shell-client-protocol.h" #include "xdg-decoration-client-protocol.h" struct libdecor { int ref_count; struct libdecor_interface *iface; struct libdecor_plugin *plugin; bool plugin_ready; struct wl_display *wl_display; struct wl_registry *wl_registry; struct xdg_wm_base *xdg_wm_base; struct zxdg_decoration_manager_v1 *decoration_manager; struct wl_callback *init_callback; bool init_done; bool has_error; struct wl_list frames; }; struct libdecor_state { enum libdecor_window_state window_state; int content_width; int content_height; }; struct libdecor_limits { int min_width; int min_height; int max_width; int max_height; }; struct libdecor_configuration { uint32_t serial; bool has_window_state; enum libdecor_window_state window_state; bool has_size; int window_width; int window_height; }; struct libdecor_frame_private { int ref_count; struct libdecor *context; struct wl_surface *wl_surface; struct libdecor_frame_interface *iface; void *user_data; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct zxdg_toplevel_decoration_v1 *toplevel_decoration; bool pending_map; struct { char *app_id; char *title; struct libdecor_limits content_limits; struct xdg_toplevel *parent; } state; struct libdecor_configuration *pending_configuration; int content_width; int content_height; enum libdecor_window_state window_state; /* stored dimensions of the floating state */ int floating_width; int floating_height; enum zxdg_toplevel_decoration_v1_mode decoration_mode; enum libdecor_capabilities capabilities; /* original limits for interactive resize */ struct libdecor_limits interactive_limits; bool visible; }; struct libdecor_plugin_private { struct libdecor_plugin_interface *iface; }; /* gather all states at which a window is non-floating */ static const enum libdecor_window_state states_non_floating = LIBDECOR_WINDOW_STATE_MAXIMIZED | LIBDECOR_WINDOW_STATE_FULLSCREEN | LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT | LIBDECOR_WINDOW_STATE_TILED_TOP | LIBDECOR_WINDOW_STATE_TILED_BOTTOM; static bool streql(const char *str1, const char *str2) { return (str1 && str2) && (strcmp(str1, str2) == 0); } static void do_map(struct libdecor_frame *frame); static bool state_is_floating(enum libdecor_window_state window_state) { return !(window_state & states_non_floating); } static void constrain_content_size(const struct libdecor_frame *frame, int *width, int *height) { const struct libdecor_limits lim = frame->priv->state.content_limits; if (lim.min_width > 0) *width = MAX(lim.min_width, *width); if (lim.max_width > 0) *width = MIN(*width, lim.max_width); if (lim.min_height > 0) *height = MAX(lim.min_height, *height); if (lim.max_height > 0) *height = MIN(*height, lim.max_height); } static bool frame_has_visible_client_side_decoration(struct libdecor_frame *frame) { /* visibility by client configuration */ const bool vis_client = frame->priv->visible; /* visibility by compositor configuration */ const bool vis_server = (frame->priv->decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); return vis_client && vis_server; } LIBDECOR_EXPORT int libdecor_state_get_content_width(struct libdecor_state *state) { return state->content_width; } LIBDECOR_EXPORT int libdecor_state_get_content_height(struct libdecor_state *state) { return state->content_height; } LIBDECOR_EXPORT enum libdecor_window_state libdecor_state_get_window_state(struct libdecor_state *state) { return state->window_state; } LIBDECOR_EXPORT struct libdecor_state * libdecor_state_new(int width, int height) { struct libdecor_state *state; state = zalloc(sizeof *state); state->content_width = width; state->content_height = height; return state; } LIBDECOR_EXPORT void libdecor_state_free(struct libdecor_state *state) { free(state); } static struct libdecor_configuration * libdecor_configuration_new(void) { struct libdecor_configuration *configuration; configuration = zalloc(sizeof *configuration); return configuration; } static void libdecor_configuration_free(struct libdecor_configuration *configuration) { free(configuration); } static bool frame_get_window_size_for(struct libdecor_frame *frame, struct libdecor_state *state, int *window_width, int *window_height) { struct libdecor_frame_private *frame_priv = frame->priv; struct libdecor *context = frame_priv->context; struct libdecor_plugin *plugin = context->plugin; if (frame_has_visible_client_side_decoration(frame)) { return plugin->priv->iface->frame_get_window_size_for( plugin, frame, state, window_width, window_height); } else { *window_width = state->content_width; *window_height = state->content_height; return true; } } static bool window_size_to_content_size(struct libdecor_configuration *configuration, struct libdecor_frame *frame, int *content_width, int *content_height) { struct libdecor_frame_private *frame_priv = frame->priv; struct libdecor *context = frame_priv->context; struct libdecor_plugin *plugin = context->plugin; if (frame_has_visible_client_side_decoration(frame)) { return plugin->priv->iface->configuration_get_content_size( plugin, configuration, frame, content_width, content_height); } else { *content_width = configuration->window_width; *content_height = configuration->window_height; return true; } } LIBDECOR_EXPORT bool libdecor_configuration_get_content_size(struct libdecor_configuration *configuration, struct libdecor_frame *frame, int *width, int *height) { int content_width; int content_height; if (!configuration->has_size) return false; if (configuration->window_width == 0 || configuration->window_height == 0) return false; if (!window_size_to_content_size(configuration, frame, &content_width, &content_height)) return false; *width = content_width; *height = content_height; if (state_is_floating(configuration->window_state)) { constrain_content_size(frame, width, height); } return true; } LIBDECOR_EXPORT bool libdecor_configuration_get_window_size(struct libdecor_configuration *configuration, int *width, int *height) { if (!configuration->has_size) return false; if (configuration->window_width == 0 || configuration->window_height == 0) return false; *width = configuration->window_width; *height = configuration->window_height; return true; } LIBDECOR_EXPORT bool libdecor_configuration_get_window_state(struct libdecor_configuration *configuration, enum libdecor_window_state *window_state) { if (!configuration->has_window_state) return false; *window_state = configuration->window_state; return true; } static void xdg_surface_configure(void *user_data, struct xdg_surface *xdg_surface, uint32_t serial) { struct libdecor_frame *frame = user_data; struct libdecor_frame_private *frame_priv = frame->priv; struct libdecor_configuration *configuration; configuration = frame_priv->pending_configuration; frame_priv->pending_configuration = NULL; if (!configuration) configuration = libdecor_configuration_new(); configuration->serial = serial; frame_priv->iface->configure(frame, configuration, frame_priv->user_data); libdecor_configuration_free(configuration); } static const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_configure, }; static enum libdecor_window_state parse_states(struct wl_array *states) { enum libdecor_window_state pending_state = LIBDECOR_WINDOW_STATE_NONE; uint32_t *p; wl_array_for_each(p, states) { enum xdg_toplevel_state state = *p; switch (state) { case XDG_TOPLEVEL_STATE_FULLSCREEN: pending_state |= LIBDECOR_WINDOW_STATE_FULLSCREEN; break; case XDG_TOPLEVEL_STATE_MAXIMIZED: pending_state |= LIBDECOR_WINDOW_STATE_MAXIMIZED; break; case XDG_TOPLEVEL_STATE_ACTIVATED: pending_state |= LIBDECOR_WINDOW_STATE_ACTIVE; break; case XDG_TOPLEVEL_STATE_TILED_LEFT: pending_state |= LIBDECOR_WINDOW_STATE_TILED_LEFT; break; case XDG_TOPLEVEL_STATE_TILED_RIGHT: pending_state |= LIBDECOR_WINDOW_STATE_TILED_RIGHT; break; case XDG_TOPLEVEL_STATE_TILED_TOP: pending_state |= LIBDECOR_WINDOW_STATE_TILED_TOP; break; case XDG_TOPLEVEL_STATE_TILED_BOTTOM: pending_state |= LIBDECOR_WINDOW_STATE_TILED_BOTTOM; break; default: break; } } return pending_state; } static void xdg_toplevel_configure(void *user_data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) { struct libdecor_frame *frame = user_data; struct libdecor_frame_private *frame_priv = frame->priv; enum libdecor_window_state window_state; window_state = parse_states(states); frame_priv->pending_configuration = libdecor_configuration_new(); frame_priv->pending_configuration->has_size = true; if (width == 0 && height == 0) { /* client needs to determine window dimensions * This might happen at the first configuration or after an * unmaximizing request. In any case, we will forward the stored * unmaximized state, which will either contain values stored * by the maximizing request, or 0. */ frame_priv->pending_configuration->window_width = frame->priv->floating_width; frame_priv->pending_configuration->window_height = frame->priv->floating_height; } else { /* store current floating state */ if (state_is_floating(window_state)) { frame->priv->floating_width = width; frame->priv->floating_height = height; } frame_priv->pending_configuration->window_width = width; frame_priv->pending_configuration->window_height = height; } frame_priv->pending_configuration->has_window_state = true; frame_priv->pending_configuration->window_state = window_state; } static void xdg_toplevel_close(void *user_data, struct xdg_toplevel *xdg_toplevel) { struct libdecor_frame *frame = user_data; struct libdecor_frame_private *frame_priv = frame->priv; frame_priv->iface->close(frame, frame_priv->user_data); } static const struct xdg_toplevel_listener xdg_toplevel_listener = { xdg_toplevel_configure, xdg_toplevel_close, }; static void toplevel_decoration_configure( void *data, struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode) { ((struct libdecor_frame_private *)(data))->decoration_mode = mode; } static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = { toplevel_decoration_configure, }; void libdecor_frame_create_xdg_decoration(struct libdecor_frame_private *frame_priv) { if (!frame_priv->context->decoration_manager) return; frame_priv->toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( frame_priv->context->decoration_manager, frame_priv->xdg_toplevel); zxdg_toplevel_decoration_v1_add_listener( frame_priv->toplevel_decoration, &xdg_toplevel_decoration_listener, frame_priv); } static void init_shell_surface(struct libdecor_frame *frame) { struct libdecor_frame_private *frame_priv = frame->priv; struct libdecor *context = frame_priv->context; if (frame_priv->xdg_surface) return; frame_priv->xdg_surface = xdg_wm_base_get_xdg_surface(context->xdg_wm_base, frame_priv->wl_surface); xdg_surface_add_listener(frame_priv->xdg_surface, &xdg_surface_listener, frame); frame_priv->xdg_toplevel = xdg_surface_get_toplevel(frame_priv->xdg_surface); xdg_toplevel_add_listener(frame_priv->xdg_toplevel, &xdg_toplevel_listener, frame); frame_priv->decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; frame_priv->toplevel_decoration = NULL; libdecor_frame_create_xdg_decoration(frame_priv); if (frame_priv->state.parent) { xdg_toplevel_set_parent(frame_priv->xdg_toplevel, frame_priv->state.parent); } if (frame_priv->state.title) { xdg_toplevel_set_title(frame_priv->xdg_toplevel, frame_priv->state.title); } if (frame_priv->state.app_id) { xdg_toplevel_set_app_id(frame_priv->xdg_toplevel, frame_priv->state.app_id); } if (frame_priv->pending_map) do_map(frame); } LIBDECOR_EXPORT struct libdecor_frame * libdecor_decorate(struct libdecor *context, struct wl_surface *wl_surface, struct libdecor_frame_interface *iface, void *user_data) { struct libdecor_plugin *plugin = context->plugin; struct libdecor_frame *frame; struct libdecor_frame_private *frame_priv; if (context->has_error) return NULL; frame = plugin->priv->iface->frame_new(plugin); if (!frame) return NULL; frame_priv = zalloc(sizeof *frame_priv); frame->priv = frame_priv; frame_priv->ref_count = 1; frame_priv->context = context; frame_priv->wl_surface = wl_surface; frame_priv->iface = iface; frame_priv->user_data = user_data; wl_list_insert(&context->frames, &frame->link); libdecor_frame_set_capabilities(frame, LIBDECOR_ACTION_MOVE | LIBDECOR_ACTION_RESIZE | LIBDECOR_ACTION_MINIMIZE | LIBDECOR_ACTION_FULLSCREEN | LIBDECOR_ACTION_CLOSE); frame_priv->visible = true; if (context->init_done) init_shell_surface(frame); return frame; } LIBDECOR_EXPORT void libdecor_frame_ref(struct libdecor_frame *frame) { struct libdecor_frame_private *frame_priv = frame->priv; frame_priv->ref_count++; } LIBDECOR_EXPORT void libdecor_frame_unref(struct libdecor_frame *frame) { struct libdecor_frame_private *frame_priv = frame->priv; frame_priv->ref_count--; if (frame_priv->ref_count == 0) { struct libdecor *context = frame_priv->context; struct libdecor_plugin *plugin = context->plugin; if (frame_priv->xdg_toplevel) xdg_toplevel_destroy(frame_priv->xdg_toplevel); if (frame_priv->xdg_surface) xdg_surface_destroy(frame_priv->xdg_surface); plugin->priv->iface->frame_free(plugin, frame); free(frame_priv->state.title); free(frame_priv->state.app_id); free(frame_priv); free(frame); } } LIBDECOR_EXPORT void libdecor_frame_set_visibility(struct libdecor_frame *frame, bool visible) { struct libdecor_frame_private *frame_priv = frame->priv; struct libdecor *context = frame_priv->context; struct libdecor_plugin *plugin = context->plugin; frame_priv->visible = visible; /* enable/disable decorations that are managed by the compositor, * only xdg-decoration version 2 and above allows to toggle decoration */ if (context->decoration_manager && zxdg_decoration_manager_v1_get_version(context->decoration_manager) > 1) { if (frame_priv->visible && frame_priv->toplevel_decoration == NULL) { /* - request to SHOW decorations * - decorations are NOT HANDLED * => create new decorations for already mapped surface */ libdecor_frame_create_xdg_decoration(frame_priv); } else if (!frame_priv->visible && frame_priv->toplevel_decoration != NULL) { /* - request to HIDE decorations * - decorations are HANDLED * => destroy decorations */ zxdg_toplevel_decoration_v1_destroy(frame_priv->toplevel_decoration); frame_priv->toplevel_decoration = NULL; } } /* enable/disable decorations that are managed by a plugin */ if (frame_has_visible_client_side_decoration(frame)) { /* show client-side decorations */ plugin->priv->iface->frame_commit(plugin, frame, NULL, NULL); } else { /* destroy client-side decorations */ plugin->priv->iface->frame_free(plugin, frame); libdecor_frame_set_window_geometry(frame, 0, 0, frame_priv->content_width, frame_priv->content_height); } libdecor_frame_toplevel_commit(frame); } LIBDECOR_EXPORT bool libdecor_frame_is_visible(struct libdecor_frame *frame) { return frame->priv->visible; } LIBDECOR_EXPORT void libdecor_frame_set_parent(struct libdecor_frame *frame, struct libdecor_frame *parent) { struct libdecor_frame_private *frame_priv = frame->priv; if (!frame_priv->xdg_toplevel) return; frame_priv->state.parent = parent->priv->xdg_toplevel; xdg_toplevel_set_parent(frame_priv->xdg_toplevel, parent->priv->xdg_toplevel); } LIBDECOR_EXPORT void libdecor_frame_set_title(struct libdecor_frame *frame, const char *title) { struct libdecor_frame_private *frame_priv = frame->priv; struct libdecor_plugin *plugin = frame_priv->context->plugin; if (!streql(frame_priv->state.title, title)) { free(frame_priv->state.title); frame_priv->state.title = strdup(title); if (!frame_priv->xdg_toplevel) return; xdg_toplevel_set_title(frame_priv->xdg_toplevel, title); plugin->priv->iface->frame_property_changed(plugin, frame); } } LIBDECOR_EXPORT const char * libdecor_frame_get_title(struct libdecor_frame *frame) { return frame->priv->state.title; } LIBDECOR_EXPORT void libdecor_frame_set_app_id(struct libdecor_frame *frame, const char *app_id) { struct libdecor_frame_private *frame_priv = frame->priv; free(frame_priv->state.app_id); frame_priv->state.app_id = strdup(app_id); if (!frame_priv->xdg_toplevel) return; xdg_toplevel_set_app_id(frame_priv->xdg_toplevel, app_id); } static void notify_on_capability_change(struct libdecor_frame *frame, const enum libdecor_capabilities old_capabilities) { struct libdecor_plugin *plugin = frame->priv->context->plugin; struct libdecor_state *state; if (frame->priv->capabilities == old_capabilities) return; if (frame->priv->content_width == 0 || frame->priv->content_height == 0) return; plugin->priv->iface->frame_property_changed(plugin, frame); if (!libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE)) { frame->priv->interactive_limits = frame->priv->state.content_limits; /* set fixed window size */ libdecor_frame_set_min_content_size(frame, frame->priv->content_width, frame->priv->content_height); libdecor_frame_set_max_content_size(frame, frame->priv->content_width, frame->priv->content_height); } else { /* restore old limits */ frame->priv->state.content_limits = frame->priv->interactive_limits; } state = libdecor_state_new(frame->priv->content_width, frame->priv->content_height); libdecor_frame_commit(frame, state, NULL); libdecor_state_free(state); libdecor_frame_toplevel_commit(frame); } LIBDECOR_EXPORT void libdecor_frame_set_capabilities(struct libdecor_frame *frame, enum libdecor_capabilities capabilities) { const enum libdecor_capabilities old_capabilities = frame->priv->capabilities; frame->priv->capabilities |= capabilities; notify_on_capability_change(frame, old_capabilities); } LIBDECOR_EXPORT void libdecor_frame_unset_capabilities(struct libdecor_frame *frame, enum libdecor_capabilities capabilities) { const enum libdecor_capabilities old_capabilities = frame->priv->capabilities; frame->priv->capabilities &= ~capabilities; notify_on_capability_change(frame, old_capabilities); } LIBDECOR_EXPORT bool libdecor_frame_has_capability(struct libdecor_frame *frame, enum libdecor_capabilities capability) { return frame->priv->capabilities & capability; } LIBDECOR_EXPORT void libdecor_frame_popup_grab(struct libdecor_frame *frame, const char *seat_name) { struct libdecor_frame_private *frame_priv = frame->priv; struct libdecor *context = frame_priv->context; struct libdecor_plugin *plugin = context->plugin; plugin->priv->iface->frame_popup_grab(plugin, frame, seat_name); } LIBDECOR_EXPORT void libdecor_frame_popup_ungrab(struct libdecor_frame *frame, const char *seat_name) { struct libdecor_frame_private *frame_priv = frame->priv; struct libdecor *context = frame_priv->context; struct libdecor_plugin *plugin = context->plugin; plugin->priv->iface->frame_popup_ungrab(plugin, frame, seat_name); } LIBDECOR_EXPORT void libdecor_frame_dismiss_popup(struct libdecor_frame *frame, const char *seat_name) { struct libdecor_frame_private *frame_priv = frame->priv; frame_priv->iface->dismiss_popup(frame, seat_name, frame_priv->user_data); } LIBDECOR_EXPORT void libdecor_frame_show_window_menu(struct libdecor_frame *frame, struct wl_seat *wl_seat, uint32_t serial, int x, int y) { struct libdecor_frame_private *frame_priv = frame->priv; if (!frame_priv->xdg_toplevel) { fprintf(stderr, "Can't show window menu before being mapped\n"); return; } xdg_toplevel_show_window_menu(frame_priv->xdg_toplevel, wl_seat, serial, x, y); } LIBDECOR_EXPORT void libdecor_frame_translate_coordinate(struct libdecor_frame *frame, int content_x, int content_y, int *frame_x, int *frame_y) { struct libdecor_frame_private *frame_priv = frame->priv; struct libdecor *context = frame_priv->context; struct libdecor_plugin *plugin = context->plugin; plugin->priv->iface->frame_translate_coordinate(plugin, frame, content_x, content_y, frame_x, frame_y); } LIBDECOR_EXPORT void libdecor_frame_set_max_content_size(struct libdecor_frame *frame, int content_width, int content_height) { struct libdecor_frame_private *frame_priv = frame->priv; frame_priv->state.content_limits.max_width = content_width; frame_priv->state.content_limits.max_height = content_height; } LIBDECOR_EXPORT void libdecor_frame_set_min_content_size(struct libdecor_frame *frame, int content_width, int content_height) { struct libdecor_frame_private *frame_priv = frame->priv; frame_priv->state.content_limits.min_width = content_width; frame_priv->state.content_limits.min_height = content_height; } LIBDECOR_EXPORT void libdecor_frame_set_window_geometry(struct libdecor_frame *frame, int32_t x, int32_t y, int32_t width, int32_t height) { xdg_surface_set_window_geometry(frame->priv->xdg_surface, x, y, width, height); } LIBDECOR_EXPORT enum libdecor_capabilities libdecor_frame_get_capabilities(const struct libdecor_frame *frame) { return frame->priv->capabilities; } enum xdg_toplevel_resize_edge edge_to_xdg_edge(enum libdecor_resize_edge edge) { switch (edge) { case LIBDECOR_RESIZE_EDGE_NONE: return XDG_TOPLEVEL_RESIZE_EDGE_NONE; case LIBDECOR_RESIZE_EDGE_TOP: return XDG_TOPLEVEL_RESIZE_EDGE_TOP; case LIBDECOR_RESIZE_EDGE_BOTTOM: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; case LIBDECOR_RESIZE_EDGE_LEFT: return XDG_TOPLEVEL_RESIZE_EDGE_LEFT; case LIBDECOR_RESIZE_EDGE_TOP_LEFT: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; case LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; case LIBDECOR_RESIZE_EDGE_RIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; case LIBDECOR_RESIZE_EDGE_TOP_RIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; case LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT: return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; } abort(); } LIBDECOR_EXPORT void libdecor_frame_resize(struct libdecor_frame *frame, struct wl_seat *wl_seat, uint32_t serial, enum libdecor_resize_edge edge) { struct libdecor_frame_private *frame_priv = frame->priv; enum xdg_toplevel_resize_edge xdg_edge; xdg_edge = edge_to_xdg_edge(edge); xdg_toplevel_resize(frame_priv->xdg_toplevel, wl_seat, serial, xdg_edge); } LIBDECOR_EXPORT void libdecor_frame_move(struct libdecor_frame *frame, struct wl_seat *wl_seat, uint32_t serial) { struct libdecor_frame_private *frame_priv = frame->priv; xdg_toplevel_move(frame_priv->xdg_toplevel, wl_seat, serial); } LIBDECOR_EXPORT void libdecor_frame_set_minimized(struct libdecor_frame *frame) { xdg_toplevel_set_minimized(frame->priv->xdg_toplevel); } LIBDECOR_EXPORT void libdecor_frame_set_maximized(struct libdecor_frame *frame) { xdg_toplevel_set_maximized(frame->priv->xdg_toplevel); } LIBDECOR_EXPORT void libdecor_frame_unset_maximized(struct libdecor_frame *frame) { xdg_toplevel_unset_maximized(frame->priv->xdg_toplevel); } LIBDECOR_EXPORT void libdecor_frame_set_fullscreen(struct libdecor_frame *frame, struct wl_output *output) { xdg_toplevel_set_fullscreen(frame->priv->xdg_toplevel, output); } LIBDECOR_EXPORT void libdecor_frame_unset_fullscreen(struct libdecor_frame *frame) { xdg_toplevel_unset_fullscreen(frame->priv->xdg_toplevel); } LIBDECOR_EXPORT bool libdecor_frame_is_floating(struct libdecor_frame *frame) { return state_is_floating(frame->priv->window_state); } LIBDECOR_EXPORT void libdecor_frame_close(struct libdecor_frame *frame) { xdg_toplevel_close(frame, frame->priv->xdg_toplevel); } bool valid_limits(struct libdecor_frame_private *frame_priv) { if (frame_priv->state.content_limits.min_width > 0 && frame_priv->state.content_limits.max_width > 0 && frame_priv->state.content_limits.min_width > frame_priv->state.content_limits.max_width) return false; if (frame_priv->state.content_limits.min_height > 0 && frame_priv->state.content_limits.max_height > 0 && frame_priv->state.content_limits.min_height > frame_priv->state.content_limits.max_height) return false; return true; } static void libdecor_frame_apply_limits(struct libdecor_frame *frame, enum libdecor_window_state window_state) { struct libdecor_frame_private *frame_priv = frame->priv; if (!valid_limits(frame_priv)) { libdecor_notify_plugin_error( frame_priv->context, LIBDECOR_ERROR_INVALID_FRAME_CONFIGURATION, "minimum size (%i,%i) must be smaller than maximum size (%i,%i)", frame_priv->state.content_limits.min_width, frame_priv->state.content_limits.min_height, frame_priv->state.content_limits.max_width, frame_priv->state.content_limits.max_height); } /* If the frame is configured as non-resizable before the first * configure event is received, we have to manually set the min/max * limits with the configured content size afterwards. */ if (!libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE)) { frame_priv->state.content_limits.min_width = frame_priv->content_width; frame_priv->state.content_limits.max_width = frame_priv->content_width; frame_priv->state.content_limits.min_height = frame_priv->content_height; frame_priv->state.content_limits.max_height = frame_priv->content_height; } if (frame_priv->state.content_limits.min_width > 0 && frame_priv->state.content_limits.min_height > 0) { struct libdecor_state state_min; int win_min_width, win_min_height; state_min.content_width = frame_priv->state.content_limits.min_width; state_min.content_height = frame_priv->state.content_limits.min_height; state_min.window_state = window_state; frame_get_window_size_for(frame, &state_min, &win_min_width, &win_min_height); xdg_toplevel_set_min_size(frame_priv->xdg_toplevel, win_min_width, win_min_height); } else { xdg_toplevel_set_min_size(frame_priv->xdg_toplevel, 0, 0); } if (frame_priv->state.content_limits.max_width > 0 && frame_priv->state.content_limits.max_height > 0) { struct libdecor_state state_max; int win_max_width, win_max_height; state_max.content_width = frame_priv->state.content_limits.max_width; state_max.content_height = frame_priv->state.content_limits.max_height; state_max.window_state = window_state; frame_get_window_size_for(frame, &state_max, &win_max_width, &win_max_height); xdg_toplevel_set_max_size(frame_priv->xdg_toplevel, win_max_width, win_max_height); } else { xdg_toplevel_set_max_size(frame_priv->xdg_toplevel, 0, 0); } } static void libdecor_frame_apply_state(struct libdecor_frame *frame, struct libdecor_state *state) { struct libdecor_frame_private *frame_priv = frame->priv; frame_priv->content_width = state->content_width; frame_priv->content_height = state->content_height; /* do not set limits in non-floating states */ if (state_is_floating(state->window_state)) { libdecor_frame_apply_limits(frame, state->window_state); } } LIBDECOR_EXPORT void libdecor_frame_toplevel_commit(struct libdecor_frame *frame) { struct libdecor_frame_private *frame_priv = frame->priv; frame_priv->iface->commit(frame, frame_priv->user_data); } LIBDECOR_EXPORT void libdecor_frame_commit(struct libdecor_frame *frame, struct libdecor_state *state, struct libdecor_configuration *configuration) { struct libdecor_frame_private *frame_priv = frame->priv; struct libdecor *context = frame_priv->context; struct libdecor_plugin *plugin = context->plugin; if (configuration && configuration->has_window_state) { frame_priv->window_state = configuration->window_state; state->window_state = configuration->window_state; } else { state->window_state = frame_priv->window_state; } libdecor_frame_apply_state(frame, state); /* switch between decoration modes */ if (frame_has_visible_client_side_decoration(frame)) { plugin->priv->iface->frame_commit(plugin, frame, state, configuration); } else { plugin->priv->iface->frame_free(plugin, frame); libdecor_frame_set_window_geometry(frame, 0, 0, frame_priv->content_width, frame_priv->content_height); } /* set the floating dimensions via the application's requested content size */ if (configuration == NULL) { frame_get_window_size_for(frame, state, &frame->priv->floating_width, &frame->priv->floating_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) { } static void xdg_wm_base_ping(void *user_data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); } static const struct xdg_wm_base_listener xdg_wm_base_listener = { xdg_wm_base_ping, }; static void init_xdg_wm_base(struct libdecor *context, uint32_t id, uint32_t version) { context->xdg_wm_base = wl_registry_bind(context->wl_registry, id, &xdg_wm_base_interface, MIN(version,2)); xdg_wm_base_add_listener(context->xdg_wm_base, &xdg_wm_base_listener, context); } static void registry_handle_global(void *user_data, struct wl_registry *wl_registry, uint32_t id, const char *interface, uint32_t version) { struct libdecor *context = user_data; if (!strcmp(interface, xdg_wm_base_interface.name)) { init_xdg_wm_base(context, id, version); } else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)) { context->decoration_manager = wl_registry_bind( context->wl_registry, id, &zxdg_decoration_manager_v1_interface, MIN(version,2)); } } static void registry_handle_global_remove(void *user_data, struct wl_registry *wl_registry, uint32_t name) { } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static bool is_compositor_compatible(struct libdecor *context) { if (!context->xdg_wm_base) return false; return true; } static void notify_error(struct libdecor *context, enum libdecor_error error, const char *message) { context->has_error = true; context->iface->error(context, error, message); context->plugin->priv->iface->destroy(context->plugin); } static void finish_init(struct libdecor *context) { struct libdecor_frame *frame; wl_list_for_each(frame, &context->frames, link) init_shell_surface(frame); } static void init_wl_display_callback(void *user_data, struct wl_callback *callback, uint32_t time) { struct libdecor *context = user_data; context->init_done = true; wl_callback_destroy(callback); context->init_callback = NULL; if (!is_compositor_compatible(context)) { notify_error(context, LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, "Compositor is missing required interfaces"); } if (context->plugin_ready) { finish_init(context); } } static const struct wl_callback_listener init_wl_display_callback_listener = { init_wl_display_callback }; struct plugin_loader { struct wl_list link; void *lib; const struct libdecor_plugin_description *description; int priority; char *name; }; static int calculate_priority(const struct libdecor_plugin_description *plugin_description) { const char *current_desktop; int i; if (!plugin_description->priorities) return -1; current_desktop = getenv("XDG_CURRENT_DESKTOP"); i = 0; while (true) { struct libdecor_plugin_priority priority = plugin_description->priorities[i]; i++; if (priority.desktop) { char *tokens; char *saveptr; char *token; if (!current_desktop) continue; tokens = strdup(current_desktop); token = strtok_r(tokens, ":", &saveptr); while (token) { if (strcmp(priority.desktop, token) == 0) { free(tokens); return priority.priority; } token = strtok_r(NULL, ":", &saveptr); } free(tokens); } else { return priority.priority; } } return -1; } static struct plugin_loader * load_plugin_loader(struct libdecor *context, const char *path, const char *name) { char *filename; void *lib; const struct libdecor_plugin_description *plugin_description; int priority; struct plugin_loader *plugin_loader; if (!strstr(name, ".so")) 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) { dlclose(lib); fprintf(stderr, "Failed to load plugin '%s': no plugin description symbol\n", name); return NULL; } if (plugin_description->api_version != LIBDECOR_PLUGIN_API_VERSION) { dlclose(lib); 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); return NULL; } if (!(plugin_description->capabilities & LIBDECOR_PLUGIN_CAPABILITY_BASE)) { dlclose(lib); return NULL; } priority = calculate_priority(plugin_description); if (priority == -1) { dlclose(lib); fprintf(stderr, "Plugin '%s' found, but has an invalid description\n", name); 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; 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 = getenv("LIBDECOR_PLUGIN_DIR"); if (!plugin_dir) plugin_dir = LIBDECOR_PLUGIN_DIR; dir = opendir(plugin_dir); if (!dir) { fprintf(stderr, "Couldn't open plugin directory: %s\n", strerror(errno)); return -1; } wl_list_init(&plugin_loaders); while (true) { struct dirent *de; struct plugin_loader *plugin_loader; 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); 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.1.0/src/libdecor.h000066400000000000000000000311701407552333500160270ustar00rootroot00000000000000/* * Copyright © 2017-2018 Red Hat Inc. * Copyright © 2018 Jonas Ådahl * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef LIBDECOR_H #define LIBDECOR_H #include #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, }; enum libdecor_resize_edge { LIBDECOR_RESIZE_EDGE_NONE, LIBDECOR_RESIZE_EDGE_TOP, LIBDECOR_RESIZE_EDGE_BOTTOM, LIBDECOR_RESIZE_EDGE_LEFT, LIBDECOR_RESIZE_EDGE_TOP_LEFT, LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT, LIBDECOR_RESIZE_EDGE_RIGHT, LIBDECOR_RESIZE_EDGE_TOP_RIGHT, LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT, }; enum libdecor_capabilities { LIBDECOR_ACTION_MOVE = 1 << 0, LIBDECOR_ACTION_RESIZE = 1 << 1, LIBDECOR_ACTION_MINIMIZE = 1 << 2, LIBDECOR_ACTION_FULLSCREEN = 1 << 3, LIBDECOR_ACTION_CLOSE = 1 << 4, }; struct libdecor_interface { /** * An error event */ void (* error)(struct libdecor *context, enum libdecor_error error, const char *message); /* Reserved */ void (* reserved0)(void); void (* reserved1)(void); void (* reserved2)(void); void (* reserved3)(void); void (* reserved4)(void); void (* reserved5)(void); void (* reserved6)(void); void (* reserved7)(void); void (* reserved8)(void); void (* reserved9)(void); }; /** * Interface for integrating a Wayland surface with libdecor. */ struct libdecor_frame_interface { /** * A new configuration was received. An application should respond to * this by creating a suitable libdecor_state, and apply it using * libdecor_frame_commit. */ void (* configure)(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data); /** * The window was requested to be closed by the compositor. */ void (* close)(struct libdecor_frame *frame, void *user_data); /** * The window decoration asked to have the main surface to be * committed. This is required when the decoration is implemented using * synchronous subsurfaces. */ void (* commit)(struct libdecor_frame *frame, void *user_data); /** * Any mapped popup that has a grab on the given seat should be * dismissed. */ void (* dismiss_popup)(struct libdecor_frame *frame, const char *seat_name, void *user_data); /* Reserved */ void (* reserved0)(void); void (* reserved1)(void); void (* reserved2)(void); void (* reserved3)(void); void (* reserved4)(void); void (* reserved5)(void); void (* reserved6)(void); void (* reserved7)(void); void (* reserved8)(void); void (* reserved9)(void); }; /** * Remove a reference to the libdecor instance. When the reference count * reaches zero, it is freed. */ void libdecor_unref(struct libdecor *context); /** * Create a new libdecor context for the given wl_display. */ struct libdecor * libdecor_new(struct wl_display *display, struct libdecor_interface *iface); /** * Get the file descriptor used by libdecor. This is similar to * wl_display_get_fd(), thus should be polled, and when data is available, * libdecor_dispatch() should be called. */ int libdecor_get_fd(struct libdecor *context); /** * Dispatch events. This function should be called when data is available on * the file descriptor returned by libdecor_get_fd(). If timeout is zero, this * function will never block. */ int libdecor_dispatch(struct libdecor *context, int timeout); /** * Decorate the given content wl_surface. * * This will create an xdg_surface and an xdg_toplevel, and integrate it * properly with the windowing system, including creating appropriate * decorations when needed, as well as handle windowing integration events such * as resizing, moving, maximizing, etc. * * The passed wl_surface should only contain actual application content, * without any window decoration. */ struct libdecor_frame * libdecor_decorate(struct libdecor *context, struct wl_surface *surface, struct libdecor_frame_interface *iface, void *user_data); /** * Add a reference to the frame object. */ void libdecor_frame_ref(struct libdecor_frame *frame); /** * Remove a reference to the frame object. When the reference count reaches * zero, the frame object is destroyed. */ void libdecor_frame_unref(struct libdecor_frame *frame); /** * Set the visibility of the frame. * * If an application wants to be borderless, it can set the frame visibility to * false. */ void libdecor_frame_set_visibility(struct libdecor_frame *frame, bool visible); /** * Get the visibility of the frame. */ bool libdecor_frame_is_visible(struct libdecor_frame *frame); /** * Set the parent of the window. * * This can be used to stack multiple toplevel windows above or under each * other. */ void libdecor_frame_set_parent(struct libdecor_frame *frame, struct libdecor_frame *parent); /** * Set the title of the window. */ void libdecor_frame_set_title(struct libdecor_frame *frame, const char *title); /** * Get the title of the window. */ const char * libdecor_frame_get_title(struct libdecor_frame *frame); /** * Set the application ID of the window. */ void libdecor_frame_set_app_id(struct libdecor_frame *frame, const char *app_id); /** * Set new capabilities of the window. * * This determines whether e.g. a window decoration should show a maximize * button, etc. * * Setting a capability does not implicitly unset any other. */ void libdecor_frame_set_capabilities(struct libdecor_frame *frame, enum libdecor_capabilities capabilities); /** * Unset capabilities of the window. * * The opposite of libdecor_frame_set_capabilities. */ void libdecor_frame_unset_capabilities(struct libdecor_frame *frame, enum libdecor_capabilities capabilities); /** * Check whether the window has any of the given capabilities. */ bool libdecor_frame_has_capability(struct libdecor_frame *frame, enum libdecor_capabilities capability); /** * Show the window menu. */ void libdecor_frame_show_window_menu(struct libdecor_frame *frame, struct wl_seat *wl_seat, uint32_t serial, int x, int y); /** * Issue a popup grab on the window. Call this when a xdg_popup is mapped, so * that it can be properly dismissed by the decorations. */ void libdecor_frame_popup_grab(struct libdecor_frame *frame, const char *seat_name); /** * Release the popup grab. Call this when you unmap a popup. */ void libdecor_frame_popup_ungrab(struct libdecor_frame *frame, const char *seat_name); /** * Translate content surface local coordinates to toplevel window local * coordinates. * * This can be used to translate surface coordinates to coordinates useful for * e.g. showing the window menu, or positioning a popup. */ void libdecor_frame_translate_coordinate(struct libdecor_frame *frame, int surface_x, int surface_y, int *frame_x, int *frame_y); /** * Set the max content size. * * This translates roughly to xdg_toplevel_set_max_size(). */ void libdecor_frame_set_max_content_size(struct libdecor_frame *frame, int content_width, int content_height); /** * Set the min content size. * * This translates roughly to xdg_toplevel_set_min_size(). */ void libdecor_frame_set_min_content_size(struct libdecor_frame *frame, int content_width, int content_height); /** * Initiate an interactive resize. * * This roughly translates to xdg_toplevel_resize(). */ void libdecor_frame_resize(struct libdecor_frame *frame, struct wl_seat *wl_seat, uint32_t serial, enum libdecor_resize_edge edge); /** * Initiate an interactive move. * * This roughly translates to xdg_toplevel_move(). */ void libdecor_frame_move(struct libdecor_frame *frame, struct wl_seat *wl_seat, uint32_t serial); /** * Commit a new window state. This can be called on application driven resizes * when the window is floating, or in response to received configurations, i.e. * from e.g. interactive resizes or state changes. */ void libdecor_frame_commit(struct libdecor_frame *frame, struct libdecor_state *state, struct libdecor_configuration *configuration); /** * Minimize the window. * * Roughly translates to xdg_toplevel_set_minimized(). */ void libdecor_frame_set_minimized(struct libdecor_frame *frame); /** * Maximize the window. * * Roughly translates to xdg_toplevel_set_maximized(). */ void libdecor_frame_set_maximized(struct libdecor_frame *frame); /** * Unmaximize the window. * * Roughly translates to xdg_toplevel_unset_maximized(). */ void libdecor_frame_unset_maximized(struct libdecor_frame *frame); /** * Fullscreen the window. * * Roughly translates to xdg_toplevel_set_fullscreen(). */ void libdecor_frame_set_fullscreen(struct libdecor_frame *frame, struct wl_output *output); /** * Unfullscreen the window. * * Roughly translates to xdg_toplevel_unset_unfullscreen(). */ void libdecor_frame_unset_fullscreen(struct libdecor_frame *frame); /** * Return true if the window is floating. * * A window is floating when it's not maximized, tiled, fullscreen, or in any * similar way with a fixed size and state. */ 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.1.0/src/meson.build000066400000000000000000000051231407552333500162340ustar00rootroot00000000000000pkg = 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], ) cursor_settings_dep = declare_dependency( link_with: cursor_settings, dependencies: [dbus_dep], ) ## core decor library libdecor = shared_library(libdecor_name, sources: [ libdecor_sources, libdecor_built_sources, ], soversion: libdecor_soversion, version: libdecor_libversion, include_directories: libdecor_includes, c_args: libdecor_c_args, dependencies: [ wayland_client_dep, dl_dep, cursor_settings_dep, ], install: true ) libdecor_dep = declare_dependency( link_with: libdecor, 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, subdirs: libdecor_full_name, version: meson.project_version() ) libdecor-0.1.0/src/plugins/000077500000000000000000000000001407552333500155525ustar00rootroot00000000000000libdecor-0.1.0/src/plugins/cairo/000077500000000000000000000000001407552333500166475ustar00rootroot00000000000000libdecor-0.1.0/src/plugins/cairo/libdecor-cairo-blur.c000066400000000000000000000160011407552333500226310ustar00rootroot00000000000000/* * 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.1.0/src/plugins/cairo/libdecor-cairo-blur.h000066400000000000000000000003441407552333500226410ustar00rootroot00000000000000#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.1.0/src/plugins/cairo/libdecor-cairo.c000066400000000000000000002173161407552333500217030ustar00rootroot00000000000000/* * Copyright © 2018 Jonas Ådahl * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "libdecor-plugin.h" #include "utils.h" #include "cursor-settings.h" #include #include #include "libdecor-cairo-blur.h" static const size_t SHADOW_MARGIN = 24; /* graspable part of the border */ static const size_t TITLE_HEIGHT = 24; static const size_t BUTTON_WIDTH = 32; static const size_t SYM_DIM = 14; static const uint32_t COL_TITLE = 0xFF080706; static const uint32_t COL_TITLE_INACT = 0xFF303030; static const uint32_t COL_BUTTON_MIN = 0xFFFFBB00; static const uint32_t COL_BUTTON_MAX = 0xFF238823; static const uint32_t COL_BUTTON_CLOSE = 0xFFFB6542; static const uint32_t COL_BUTTON_INACT = 0xFF404040; static const uint32_t COL_SYM = 0xFFF4F4EF; static const uint32_t COL_SYM_ACT = 0xFF20322A; static const uint32_t COL_SYM_INACT = 0xFF909090; static const uint32_t DOUBLE_CLICK_TIME_MS = 400; static const char *cursor_names[] = { "top_side", "bottom_side", "left_side", "top_left_corner", "bottom_left_corner", "right_side", "top_right_corner", "bottom_right_corner" }; /* color conversion function from 32bit integer to double components */ double red(const uint32_t *const col) { return ((const uint8_t*)(col))[2] / (double)(255); } double green(const uint32_t *const col) { return ((const uint8_t*)(col))[1] / (double)(255); } double blue(const uint32_t *const col) { return ((const uint8_t*)(col))[0] / (double)(255); } double alpha(const uint32_t *const col) { return ((const uint8_t*)(col))[3] / (double)(255); } void cairo_set_rgba32(cairo_t *cr, const uint32_t *const c) { cairo_set_source_rgba(cr, red(c), green(c), blue(c), alpha(c)); } static bool streql(const char *str1, const char *str2) { return (str1 && str2) && (strcmp(str1, str2) == 0); } enum decoration_type { DECORATION_TYPE_NONE, DECORATION_TYPE_ALL, DECORATION_TYPE_TITLE_ONLY }; enum component { NONE = 0, SHADOW, TITLE, BUTTON_MIN, BUTTON_MAX, BUTTON_CLOSE, }; enum composite_mode { COMPOSITE_SERVER, COMPOSITE_CLIENT, }; struct seat { struct libdecor_plugin_cairo *plugin_cairo; char *name; struct wl_seat *wl_seat; struct wl_pointer *wl_pointer; struct wl_surface *cursor_surface; struct wl_cursor *current_cursor; int cursor_scale; struct wl_list cursor_outputs; struct wl_cursor_theme *cursor_theme; /* cursors for resize edges and corners */ struct wl_cursor *cursors[ARRAY_LENGTH(cursor_names)]; struct wl_cursor *cursor_left_ptr; struct wl_surface *pointer_focus; int pointer_x, pointer_y; uint32_t pointer_button_time_stamp; uint32_t serial; bool grabbed; struct wl_list link; }; struct output { struct libdecor_plugin_cairo *plugin_cairo; struct wl_output *wl_output; uint32_t id; int scale; struct wl_list link; }; struct buffer { struct wl_buffer *wl_buffer; bool in_use; bool is_detached; void *data; size_t data_size; int width; int height; int scale; int buffer_width; int buffer_height; }; struct border_component { enum component type; bool is_hidden; bool opaque; enum composite_mode composite_mode; struct { struct wl_surface *wl_surface; struct wl_subsurface *wl_subsurface; struct buffer *buffer; struct wl_list output_list; int scale; } server; struct { cairo_surface_t *image; struct border_component *parent_component; } client; struct wl_list child_components; /* border_component::link */ struct wl_list link; /* border_component::child_components */ }; struct surface_output { struct output *output; struct wl_list link; }; struct cursor_output { struct output *output; struct wl_list link; }; struct libdecor_frame_cairo { struct libdecor_frame frame; struct libdecor_plugin_cairo *plugin_cairo; int content_width; int content_height; enum decoration_type decoration_type; enum libdecor_window_state window_state; char *title; enum libdecor_capabilities capabilities; struct border_component *focus; struct border_component *active; struct border_component *grab; bool shadow_showing; struct border_component shadow; struct { bool is_showing; struct border_component title; struct border_component min; struct border_component max; struct border_component close; } title_bar; /* store pre-processed shadow tile */ cairo_surface_t *shadow_blur; struct wl_list link; }; struct libdecor_plugin_cairo { struct libdecor_plugin plugin; struct wl_callback *globals_callback; struct wl_callback *globals_callback_shm; struct libdecor *context; struct wl_registry *wl_registry; struct wl_subcompositor *wl_subcompositor; struct wl_compositor *wl_compositor; struct wl_shm *wl_shm; struct wl_callback *shm_callback; bool has_argb; struct wl_list visible_frame_list; struct wl_list seat_list; struct wl_list output_list; char *cursor_theme_name; int cursor_size; PangoFontDescription *font; }; static const char *libdecor_cairo_proxy_tag = "libdecor-cairo"; static void sync_active_component(struct libdecor_frame_cairo *frame_cairo, struct seat *seat); static void synthesize_pointer_enter(struct seat *seat); static void synthesize_pointer_leave(struct seat *seat); static bool own_proxy(struct wl_proxy *proxy) { return (wl_proxy_get_tag(proxy) == &libdecor_cairo_proxy_tag); } static bool own_surface(struct wl_surface *surface) { return own_proxy((struct wl_proxy *) surface); } static bool own_output(struct wl_output *output) { return own_proxy((struct wl_proxy *) output); } static bool moveable(struct libdecor_frame_cairo *frame_cairo) { return libdecor_frame_has_capability(&frame_cairo->frame, LIBDECOR_ACTION_MOVE); } static bool resizable(struct libdecor_frame_cairo *frame_cairo) { return libdecor_frame_has_capability(&frame_cairo->frame, LIBDECOR_ACTION_RESIZE); } static bool minimizable(struct libdecor_frame_cairo *frame_cairo) { return libdecor_frame_has_capability(&frame_cairo->frame, LIBDECOR_ACTION_MINIMIZE); } static bool closeable(struct libdecor_frame_cairo *frame_cairo) { return libdecor_frame_has_capability(&frame_cairo->frame, LIBDECOR_ACTION_CLOSE); } static void buffer_free(struct buffer *buffer); static void draw_border_component(struct libdecor_frame_cairo *frame_cairo, struct border_component *border_component); static void send_cursor(struct seat *seat); static bool update_local_cursor(struct seat *seat); static void libdecor_plugin_cairo_destroy(struct libdecor_plugin *plugin) { struct libdecor_plugin_cairo *plugin_cairo = (struct libdecor_plugin_cairo *) plugin; struct seat *seat, *seat_tmp; struct output *output, *output_tmp; struct libdecor_frame_cairo *frame, *frame_tmp; if (plugin_cairo->globals_callback) wl_callback_destroy(plugin_cairo->globals_callback); if (plugin_cairo->globals_callback_shm) wl_callback_destroy(plugin_cairo->globals_callback_shm); if (plugin_cairo->shm_callback) wl_callback_destroy(plugin_cairo->shm_callback); wl_registry_destroy(plugin_cairo->wl_registry); wl_list_for_each_safe(seat, seat_tmp, &plugin_cairo->seat_list, link) { struct cursor_output *cursor_output, *tmp; if (seat->wl_pointer) wl_pointer_destroy(seat->wl_pointer); if (seat->cursor_surface) wl_surface_destroy(seat->cursor_surface); wl_seat_destroy(seat->wl_seat); if (seat->cursor_theme) wl_cursor_theme_destroy(seat->cursor_theme); wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) { wl_list_remove(&cursor_output->link); free(cursor_output); } free(seat->name); free(seat); } wl_list_for_each_safe(output, output_tmp, &plugin_cairo->output_list, link) { wl_output_destroy(output->wl_output); free(output); } wl_list_for_each_safe(frame, frame_tmp, &plugin_cairo->visible_frame_list, link) { wl_list_remove(&frame->link); } free(plugin_cairo->cursor_theme_name); wl_shm_destroy(plugin_cairo->wl_shm); pango_font_description_free(plugin_cairo->font); wl_compositor_destroy(plugin_cairo->wl_compositor); wl_subcompositor_destroy(plugin_cairo->wl_subcompositor); libdecor_plugin_release(&plugin_cairo->plugin); free(plugin_cairo); } static void init_server_component(struct border_component *border_component, enum component type) { border_component->composite_mode = COMPOSITE_SERVER; wl_list_init(&border_component->child_components); border_component->type = type; } static void init_client_component(struct border_component *border_component, struct border_component *parent, enum component type) { border_component->composite_mode = COMPOSITE_CLIENT; wl_list_init(&border_component->child_components); wl_list_insert(parent->child_components.prev, &border_component->link); border_component->client.parent_component = parent; border_component->type = type; } static void init_components(struct libdecor_frame_cairo *frame_cairo) { init_server_component(&frame_cairo->title_bar.title, TITLE); init_client_component(&frame_cairo->title_bar.min, &frame_cairo->title_bar.title, BUTTON_MIN); init_client_component(&frame_cairo->title_bar.max, &frame_cairo->title_bar.title, BUTTON_MAX); init_client_component(&frame_cairo->title_bar.close, &frame_cairo->title_bar.title, BUTTON_CLOSE); init_server_component(&frame_cairo->shadow, SHADOW); } static struct libdecor_frame_cairo * libdecor_frame_cairo_new(struct libdecor_plugin_cairo *plugin_cairo) { struct libdecor_frame_cairo *frame_cairo = zalloc(sizeof *frame_cairo); cairo_t *cr; static const int size = 128; static const int boundary = 32; frame_cairo->plugin_cairo = plugin_cairo; frame_cairo->shadow_blur = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, size, size); wl_list_insert(&plugin_cairo->visible_frame_list, &frame_cairo->link); init_components(frame_cairo); cr = cairo_create(frame_cairo->shadow_blur); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgba(cr, 0, 0, 0, 1); cairo_rectangle(cr, boundary, boundary, size-2*boundary, size-2*boundary); cairo_fill(cr); cairo_destroy(cr); blur_surface(frame_cairo->shadow_blur, 64); return frame_cairo; } static int libdecor_plugin_cairo_get_fd(struct libdecor_plugin *plugin) { struct libdecor_plugin_cairo *plugin_cairo = (struct libdecor_plugin_cairo *) plugin; struct wl_display *wl_display = libdecor_get_wl_display(plugin_cairo->context); return wl_display_get_fd(wl_display); } static int libdecor_plugin_cairo_dispatch(struct libdecor_plugin *plugin, int timeout) { struct libdecor_plugin_cairo *plugin_cairo = (struct libdecor_plugin_cairo *) plugin; struct wl_display *wl_display = libdecor_get_wl_display(plugin_cairo->context); struct pollfd fds[1]; int ret; int dispatch_count = 0; while (wl_display_prepare_read(wl_display) != 0) dispatch_count += wl_display_dispatch_pending(wl_display); if (wl_display_flush(wl_display) < 0 && errno != EAGAIN) { wl_display_cancel_read(wl_display); return -errno; } fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN }; ret = poll(fds, ARRAY_SIZE (fds), timeout); if (ret > 0) { if (fds[0].revents & POLLIN) { wl_display_read_events(wl_display); dispatch_count += wl_display_dispatch_pending(wl_display); return dispatch_count; } else { wl_display_cancel_read(wl_display); return dispatch_count; } } else if (ret == 0) { wl_display_cancel_read(wl_display); return dispatch_count; } else { wl_display_cancel_read(wl_display); return -errno; } } static struct libdecor_frame * libdecor_plugin_cairo_frame_new(struct libdecor_plugin *plugin) { struct libdecor_plugin_cairo *plugin_cairo = (struct libdecor_plugin_cairo *) plugin; struct libdecor_frame_cairo *frame_cairo; frame_cairo = libdecor_frame_cairo_new(plugin_cairo); return &frame_cairo->frame; } static int create_anonymous_file(off_t size) { int ret; int fd; fd = memfd_create("libdecor-cairo", MFD_CLOEXEC | MFD_ALLOW_SEALING); if (fd < 0) return -1; fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK); do { ret = posix_fallocate(fd, 0, size); } while (ret == EINTR); if (ret != 0) { close(fd); errno = ret; return -1; } return fd; } static void toggle_maximized(struct libdecor_frame *const frame) { if (!resizable((struct libdecor_frame_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 = 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) { frame_cairo->shadow.opaque = false; ensure_component(frame_cairo, &frame_cairo->shadow); libdecor_frame_set_min_content_size(&frame_cairo->frame, MAX(56, 4 * BUTTON_WIDTH), MAX(56, TITLE_HEIGHT + 1)); } 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; layout = pango_cairo_create_layout(cr); pango_layout_set_text(layout, libdecor_frame_get_title((struct libdecor_frame*) frame_cairo), -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: 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_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_TITLE_ONLY: /* hide borders */ if (is_border_surfaces_showing(frame_cairo)) hide_border_surfaces(frame_cairo); /* show title bar */ ensure_title_bar_surfaces(frame_cairo); draw_title_bar(frame_cairo); /* link frame */ if (frame_cairo->link.next == NULL) wl_list_insert( &frame_cairo->plugin_cairo->visible_frame_list, &frame_cairo->link); break; } } static void set_window_geometry(struct libdecor_frame_cairo *frame_cairo) { struct libdecor_frame *frame = &frame_cairo->frame; int x = 0, y = 0, width = 0, height = 0; switch (frame_cairo->decoration_type) { case DECORATION_TYPE_NONE: x = 0; y = 0; width = libdecor_frame_get_content_width(frame); height = libdecor_frame_get_content_height(frame); break; case DECORATION_TYPE_ALL: case DECORATION_TYPE_TITLE_ONLY: x = 0; y = -(int)TITLE_HEIGHT; width = libdecor_frame_get_content_width(frame); height = libdecor_frame_get_content_height(frame) + TITLE_HEIGHT; break; } libdecor_frame_set_window_geometry(frame, x, y, width, height); } static enum decoration_type window_state_to_decoration_type(enum libdecor_window_state window_state) { if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) return DECORATION_TYPE_NONE; else if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED || 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_cairo_frame_commit(struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_state *state, struct libdecor_configuration *configuration) { struct libdecor_frame_cairo *frame_cairo = (struct libdecor_frame_cairo *) frame; enum libdecor_window_state old_window_state; enum libdecor_window_state new_window_state; int old_content_width, old_content_height; int new_content_width, new_content_height; enum decoration_type old_decoration_type; enum decoration_type new_decoration_type; old_window_state = frame_cairo->window_state; new_window_state = libdecor_frame_get_window_state(frame); old_content_width = frame_cairo->content_width; old_content_height = frame_cairo->content_height; new_content_width = libdecor_frame_get_content_width(frame); new_content_height = libdecor_frame_get_content_height(frame); old_decoration_type = frame_cairo->decoration_type; new_decoration_type = window_state_to_decoration_type(new_window_state); if (old_decoration_type == new_decoration_type && old_content_width == new_content_width && old_content_height == new_content_height && old_window_state == new_window_state) return; frame_cairo->content_width = new_content_width; frame_cairo->content_height = new_content_height; frame_cairo->decoration_type = new_decoration_type; frame_cairo->window_state = new_window_state; draw_decoration(frame_cairo); set_window_geometry(frame_cairo); } static void libdecor_plugin_cairo_frame_property_changed(struct libdecor_plugin *plugin, struct libdecor_frame *frame) { struct libdecor_frame_cairo *frame_cairo = (struct libdecor_frame_cairo *) frame; bool redraw_needed = false; const char *new_title; new_title = libdecor_frame_get_title(frame); if (frame_cairo->title_bar.is_showing) { if (!streql(frame_cairo->title, new_title)) redraw_needed = true; } if (frame_cairo->title) { free(frame_cairo->title); frame_cairo->title = NULL; } if (new_title) { frame_cairo->title = strdup(new_title); } if (frame_cairo->capabilities != libdecor_frame_get_capabilities(frame)) { frame_cairo->capabilities = libdecor_frame_get_capabilities(frame); redraw_needed = true; } if (redraw_needed) { draw_decoration(frame_cairo); libdecor_frame_toplevel_commit(frame); } } static void libdecor_plugin_cairo_frame_translate_coordinate(struct libdecor_plugin *plugin, struct libdecor_frame *frame, int content_x, int content_y, int *frame_x, int *frame_y) { struct libdecor_frame_cairo *frame_cairo = (struct libdecor_frame_cairo *) frame; *frame_x = content_x; *frame_y = content_y; if (frame_cairo->title_bar.is_showing) *frame_y += TITLE_HEIGHT; } static bool streq(const char *str1, const char *str2) { if (!str1 && !str2) return true; if (str1 && str2) return strcmp(str1, str2) == 0; return false; } static void libdecor_plugin_cairo_frame_popup_grab(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name) { struct libdecor_frame_cairo *frame_cairo = (struct libdecor_frame_cairo *) frame; struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo; struct seat *seat; wl_list_for_each(seat, &plugin_cairo->seat_list, link) { if (streq(seat->name, seat_name)) { if (seat->grabbed) { fprintf(stderr, "libdecor-WARNING: Application " "tried to grab seat twice\n"); } synthesize_pointer_leave(seat); seat->grabbed = true; return; } } fprintf(stderr, "libdecor-WARNING: Application tried to grab unknown seat\n"); } static void libdecor_plugin_cairo_frame_popup_ungrab(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name) { struct libdecor_frame_cairo *frame_cairo = (struct libdecor_frame_cairo *) frame; struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo; struct seat *seat; wl_list_for_each(seat, &plugin_cairo->seat_list, link) { if (streq(seat->name, seat_name)) { if (!seat->grabbed) { fprintf(stderr, "libdecor-WARNING: Application " "tried to ungrab seat twice\n"); } seat->grabbed = false; synthesize_pointer_enter(seat); sync_active_component(frame_cairo, seat); return; } } fprintf(stderr, "libdecor-WARNING: Application tried to ungrab unknown seat\n"); } static bool libdecor_plugin_cairo_configuration_get_content_size( struct libdecor_plugin *plugin, struct libdecor_configuration *configuration, struct libdecor_frame *frame, int *content_width, int *content_height) { int win_width, win_height; if (!libdecor_configuration_get_window_size(configuration, &win_width, &win_height)) return false; enum libdecor_window_state state; if (!libdecor_configuration_get_window_state(configuration, &state)) { return false; } switch (window_state_to_decoration_type(state)) { case DECORATION_TYPE_NONE: *content_width = win_width; *content_height = win_height; break; case DECORATION_TYPE_ALL: case DECORATION_TYPE_TITLE_ONLY: *content_width = win_width; *content_height = win_height - TITLE_HEIGHT; break; } return true; } static bool libdecor_plugin_cairo_frame_get_window_size_for( struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_state *state, int *window_width, int *window_height) { enum libdecor_window_state window_state = libdecor_state_get_window_state(state); switch (window_state_to_decoration_type(window_state)) { case DECORATION_TYPE_NONE: *window_width = libdecor_state_get_content_width(state); *window_height = libdecor_state_get_content_height(state); break; case DECORATION_TYPE_ALL: case DECORATION_TYPE_TITLE_ONLY: *window_width = libdecor_state_get_content_width(state); *window_height = libdecor_state_get_content_height(state) + TITLE_HEIGHT; break; } return true; } static struct libdecor_plugin_interface cairo_plugin_iface = { .destroy = libdecor_plugin_cairo_destroy, .get_fd = libdecor_plugin_cairo_get_fd, .dispatch = libdecor_plugin_cairo_dispatch, .frame_new = libdecor_plugin_cairo_frame_new, .frame_free = libdecor_plugin_cairo_frame_free, .frame_commit = libdecor_plugin_cairo_frame_commit, .frame_property_changed = libdecor_plugin_cairo_frame_property_changed, .frame_translate_coordinate = libdecor_plugin_cairo_frame_translate_coordinate, .frame_popup_grab = libdecor_plugin_cairo_frame_popup_grab, .frame_popup_ungrab = libdecor_plugin_cairo_frame_popup_ungrab, .configuration_get_content_size = libdecor_plugin_cairo_configuration_get_content_size, .frame_get_window_size_for = libdecor_plugin_cairo_frame_get_window_size_for, }; static void init_wl_compositor(struct libdecor_plugin_cairo *plugin_cairo, uint32_t id, uint32_t version) { plugin_cairo->wl_compositor = wl_registry_bind(plugin_cairo->wl_registry, id, &wl_compositor_interface, MIN(version, 4)); } static void init_wl_subcompositor(struct libdecor_plugin_cairo *plugin_cairo, uint32_t id, uint32_t version) { plugin_cairo->wl_subcompositor = wl_registry_bind(plugin_cairo->wl_registry, id, &wl_subcompositor_interface, 1); } static void shm_format(void *user_data, struct wl_shm *wl_shm, uint32_t format) { struct libdecor_plugin_cairo *plugin_cairo = user_data; if (format == WL_SHM_FORMAT_ARGB8888) plugin_cairo->has_argb = true; } struct wl_shm_listener shm_listener = { shm_format }; static void shm_callback(void *user_data, struct wl_callback *callback, uint32_t time) { struct libdecor_plugin_cairo *plugin_cairo = user_data; struct libdecor *context = plugin_cairo->context; wl_callback_destroy(callback); plugin_cairo->globals_callback_shm = NULL; if (!plugin_cairo->has_argb) { libdecor_notify_plugin_error( context, LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, "Compositor is missing required shm format"); return; } libdecor_notify_plugin_ready(context); } static const struct wl_callback_listener shm_callback_listener = { shm_callback }; static void init_wl_shm(struct libdecor_plugin_cairo *plugin_cairo, uint32_t id, uint32_t version) { struct libdecor *context = plugin_cairo->context; struct wl_display *wl_display = libdecor_get_wl_display(context); plugin_cairo->wl_shm = wl_registry_bind(plugin_cairo->wl_registry, id, &wl_shm_interface, 1); wl_shm_add_listener(plugin_cairo->wl_shm, &shm_listener, plugin_cairo); plugin_cairo->globals_callback_shm = wl_display_sync(wl_display); wl_callback_add_listener(plugin_cairo->globals_callback_shm, &shm_callback_listener, plugin_cairo); } static void cursor_surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct seat *seat = data; if(own_output(wl_output)) { struct cursor_output *cursor_output; cursor_output = zalloc(sizeof *cursor_output); cursor_output->output = wl_output_get_user_data(wl_output); wl_list_insert(&seat->cursor_outputs, &cursor_output->link); if (update_local_cursor(seat)) send_cursor(seat); } } static void cursor_surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) { struct seat *seat = data; if(own_output(wl_output)) { struct cursor_output *cursor_output, *tmp; wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) { if (cursor_output->output->wl_output == wl_output) { wl_list_remove(&cursor_output->link); free(cursor_output); } } if (update_local_cursor(seat)) send_cursor(seat); } } static struct wl_surface_listener cursor_surface_listener = { cursor_surface_enter, cursor_surface_leave, }; static void ensure_cursor_surface(struct seat *seat) { struct wl_compositor *wl_compositor = seat->plugin_cairo->wl_compositor; if (seat->cursor_surface) return; seat->cursor_surface = wl_compositor_create_surface(wl_compositor); wl_surface_add_listener(seat->cursor_surface, &cursor_surface_listener, seat); } static bool ensure_cursor_theme(struct seat *seat) { struct libdecor_plugin_cairo *plugin_cairo = seat->plugin_cairo; int scale = 1; struct wl_cursor_theme *theme; struct cursor_output *cursor_output; wl_list_for_each(cursor_output, &seat->cursor_outputs, link) { scale = MAX(scale, cursor_output->output->scale); } if (seat->cursor_theme && seat->cursor_scale == scale) return false; seat->cursor_scale = scale; theme = wl_cursor_theme_load(plugin_cairo->cursor_theme_name, plugin_cairo->cursor_size * scale, plugin_cairo->wl_shm); if (theme == NULL) return false; if (seat->cursor_theme) wl_cursor_theme_destroy(seat->cursor_theme); seat->cursor_theme = theme; for (unsigned int i = 0; i < ARRAY_LENGTH(cursor_names); i++) { seat->cursors[i] = wl_cursor_theme_get_cursor( seat->cursor_theme, cursor_names[i]); } seat->cursor_left_ptr = wl_cursor_theme_get_cursor(seat->cursor_theme, "left_ptr"); seat->current_cursor = seat->cursor_left_ptr; return true; } enum libdecor_resize_edge component_edge(const struct border_component *cmpnt, const int pointer_x, const int pointer_y, const int margin) { const bool top = pointer_y < margin; const bool bottom = pointer_y > (cmpnt->server.buffer->height - margin); const bool left = pointer_x < margin; const bool right = pointer_x > (cmpnt->server.buffer->width - margin); if (top) if (left) return LIBDECOR_RESIZE_EDGE_TOP_LEFT; else if (right) return LIBDECOR_RESIZE_EDGE_TOP_RIGHT; else return LIBDECOR_RESIZE_EDGE_TOP; else if (bottom) if (left) return LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT; else if (right) return LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT; else return LIBDECOR_RESIZE_EDGE_BOTTOM; else if (left) return LIBDECOR_RESIZE_EDGE_LEFT; else if (right) return LIBDECOR_RESIZE_EDGE_RIGHT; else return LIBDECOR_RESIZE_EDGE_NONE; } static bool update_local_cursor(struct seat *seat) { if (!seat->pointer_focus) { seat->current_cursor = seat->cursor_left_ptr; return false; } if (!own_surface(seat->pointer_focus)) return false; struct libdecor_frame_cairo *frame_cairo = wl_surface_get_user_data(seat->pointer_focus); struct wl_cursor *wl_cursor = NULL; if (!frame_cairo || !frame_cairo->active) { seat->current_cursor = seat->cursor_left_ptr; return false; } bool theme_updated = ensure_cursor_theme(seat); if (frame_cairo->active->type == SHADOW && is_border_surfaces_showing(frame_cairo) && resizable(frame_cairo)) { enum libdecor_resize_edge edge; edge = component_edge(frame_cairo->active, seat->pointer_x, seat->pointer_y, SHADOW_MARGIN); if (edge != LIBDECOR_RESIZE_EDGE_NONE) wl_cursor = seat->cursors[edge - 1]; } else { wl_cursor = seat->cursor_left_ptr; } if (seat->current_cursor != wl_cursor) { seat->current_cursor = wl_cursor; return true; } return theme_updated; } static void send_cursor(struct seat *seat) { struct wl_cursor_image *image; struct wl_buffer *buffer; if (seat->pointer_focus == NULL || seat->current_cursor == NULL) return; image = seat->current_cursor->images[0]; buffer = wl_cursor_image_get_buffer(image); wl_surface_attach(seat->cursor_surface, buffer, 0, 0); wl_surface_set_buffer_scale(seat->cursor_surface, seat->cursor_scale); wl_surface_damage_buffer(seat->cursor_surface, 0, 0, image->width * seat->cursor_scale, image->height * seat->cursor_scale); wl_surface_commit(seat->cursor_surface); wl_pointer_set_cursor(seat->wl_pointer, seat->serial, seat->cursor_surface, image->hotspot_x / seat->cursor_scale, image->hotspot_y / seat->cursor_scale); } static void sync_active_component(struct libdecor_frame_cairo *frame_cairo, struct seat *seat) { struct border_component *old_active; if (!seat->pointer_focus) return; old_active = frame_cairo->active; update_component_focus(frame_cairo, seat->pointer_focus, seat); if (old_active != frame_cairo->active) { draw_decoration(frame_cairo); libdecor_frame_toplevel_commit(&frame_cairo->frame); } if (update_local_cursor(seat)) send_cursor(seat); } static void synthesize_pointer_enter(struct seat *seat) { struct wl_surface *surface; struct libdecor_frame_cairo *frame_cairo; surface = seat->pointer_focus; if (!surface) return; frame_cairo = wl_surface_get_user_data(surface); if (!frame_cairo) return; update_component_focus(frame_cairo, seat->pointer_focus, seat); frame_cairo->grab = NULL; /* update decorations */ if (frame_cairo->active) { draw_decoration(frame_cairo); libdecor_frame_toplevel_commit(&frame_cairo->frame); } update_local_cursor(seat); send_cursor(seat); } static void synthesize_pointer_leave(struct seat *seat) { struct wl_surface *surface; struct libdecor_frame_cairo *frame_cairo; surface = seat->pointer_focus; if (!surface) return; frame_cairo = wl_surface_get_user_data(surface); if (!frame_cairo) return; if (!frame_cairo->active) return; frame_cairo->active = NULL; draw_decoration(frame_cairo); libdecor_frame_toplevel_commit(&frame_cairo->frame); update_local_cursor(seat); } static void pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct seat *seat = data; if (!surface) return; if (!own_surface(surface)) return; ensure_cursor_surface(seat); seat->pointer_x = wl_fixed_to_int(surface_x); seat->pointer_y = wl_fixed_to_int(surface_y); seat->serial = serial; seat->pointer_focus = surface; if (seat->grabbed) return; synthesize_pointer_enter(seat); } static void pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { struct seat *seat = data; if (!surface) return; if (!own_surface(surface)) return; synthesize_pointer_leave(seat); seat->pointer_focus = NULL; } static void pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { struct seat *seat = data; struct libdecor_frame_cairo *frame_cairo; seat->pointer_x = wl_fixed_to_int(surface_x); seat->pointer_y = wl_fixed_to_int(surface_y); if (seat->grabbed) return; if (!seat->pointer_focus) return; frame_cairo = wl_surface_get_user_data(seat->pointer_focus); sync_active_component(frame_cairo, seat); } static void pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { struct seat *seat = data; struct libdecor_frame_cairo *frame_cairo; if (!seat->pointer_focus || !own_surface(seat->pointer_focus)) return; frame_cairo = wl_surface_get_user_data(seat->pointer_focus); if (!frame_cairo) return; if (seat->grabbed) { libdecor_frame_dismiss_popup(&frame_cairo->frame, seat->name); return; } if (!frame_cairo->active) return; if (button == BTN_LEFT) { if (state == WL_POINTER_BUTTON_STATE_PRESSED) { enum libdecor_resize_edge edge = LIBDECOR_RESIZE_EDGE_NONE; frame_cairo->grab = NULL; switch (frame_cairo->active->type) { case SHADOW: edge = component_edge(frame_cairo->active, seat->pointer_x, seat->pointer_y, SHADOW_MARGIN); break; case TITLE: if (time-seat->pointer_button_time_stamp < DOUBLE_CLICK_TIME_MS) { toggle_maximized(&frame_cairo->frame); } else if (moveable(frame_cairo)) { seat->pointer_button_time_stamp = time; libdecor_frame_move(&frame_cairo->frame, seat->wl_seat, serial); } break; case BUTTON_MIN: case BUTTON_MAX: case BUTTON_CLOSE: frame_cairo->grab = frame_cairo->active; break; default: break; } if (edge != LIBDECOR_RESIZE_EDGE_NONE && resizable(frame_cairo)) { libdecor_frame_resize( &frame_cairo->frame, seat->wl_seat, serial, edge); } } else if (state == WL_POINTER_BUTTON_STATE_RELEASED && frame_cairo->grab) { if (frame_cairo->grab == frame_cairo->focus) { switch (frame_cairo->active->type) { case BUTTON_MIN: if (minimizable(frame_cairo)) libdecor_frame_set_minimized( &frame_cairo->frame); break; case BUTTON_MAX: toggle_maximized(&frame_cairo->frame); break; case BUTTON_CLOSE: if (closeable(frame_cairo)) libdecor_frame_close(&frame_cairo->frame); break; default: break; } } frame_cairo->grab = NULL; sync_active_component(frame_cairo, seat); } } else if (button == BTN_RIGHT && state == WL_POINTER_BUTTON_STATE_PRESSED && seat->pointer_focus == frame_cairo->title_bar.title.server.wl_surface) { libdecor_frame_show_window_menu(&frame_cairo->frame, seat->wl_seat, serial, seat->pointer_x, seat->pointer_y - TITLE_HEIGHT); } } static void pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { } static struct wl_pointer_listener pointer_listener = { pointer_enter, pointer_leave, pointer_motion, pointer_button, pointer_axis }; static void seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) { struct seat *seat = data; if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !seat->wl_pointer) { seat->wl_pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer) { wl_pointer_release(seat->wl_pointer); seat->wl_pointer = NULL; } } static void seat_name(void *data, struct wl_seat *wl_seat, const char *name) { struct seat *seat = data; seat->name = strdup(name); } static struct wl_seat_listener seat_listener = { seat_capabilities, seat_name }; static void init_wl_seat(struct libdecor_plugin_cairo *plugin_cairo, uint32_t id, uint32_t version) { struct seat *seat; if (version < 3) { libdecor_notify_plugin_error( plugin_cairo->context, LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, "%s version 3 required but only version %i is available\n", wl_seat_interface.name, version); } seat = zalloc(sizeof *seat); seat->cursor_scale = 1; seat->plugin_cairo = plugin_cairo; wl_list_init(&seat->cursor_outputs); wl_list_insert(&plugin_cairo->seat_list, &seat->link); seat->wl_seat = wl_registry_bind(plugin_cairo->wl_registry, id, &wl_seat_interface, 3); wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); } static void output_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { } static void output_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { } static void output_done(void *data, struct wl_output *wl_output) { struct output *output = data; struct libdecor_frame_cairo *frame_cairo; struct seat *seat; wl_list_for_each(frame_cairo, &output->plugin_cairo->visible_frame_list, link) { bool updated = false; updated |= redraw_scale(frame_cairo, &frame_cairo->shadow); updated |= redraw_scale(frame_cairo, &frame_cairo->title_bar.title); if (updated) libdecor_frame_toplevel_commit(&frame_cairo->frame); } wl_list_for_each(seat, &output->plugin_cairo->seat_list, link) { if (update_local_cursor(seat)) send_cursor(seat); } } static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) { struct output *output = data; output->scale = factor; } static struct wl_output_listener output_listener = { output_geometry, output_mode, output_done, output_scale }; static void init_wl_output(struct libdecor_plugin_cairo *plugin_cairo, uint32_t id, uint32_t version) { struct output *output; if (version < 2) { libdecor_notify_plugin_error( plugin_cairo->context, LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, "%s version 2 required but only version %i is available\n", wl_output_interface.name, version); } output = zalloc(sizeof *output); output->plugin_cairo = plugin_cairo; wl_list_insert(&plugin_cairo->output_list, &output->link); output->id = id; output->wl_output = wl_registry_bind(plugin_cairo->wl_registry, id, &wl_output_interface, 2); wl_proxy_set_tag((struct wl_proxy *) output->wl_output, &libdecor_cairo_proxy_tag); wl_output_add_listener(output->wl_output, &output_listener, output); } static void registry_handle_global(void *user_data, struct wl_registry *wl_registry, uint32_t id, const char *interface, uint32_t version) { struct libdecor_plugin_cairo *plugin_cairo = user_data; if (strcmp(interface, "wl_compositor") == 0) init_wl_compositor(plugin_cairo, id, version); else if (strcmp(interface, "wl_subcompositor") == 0) init_wl_subcompositor(plugin_cairo, id, version); else if (strcmp(interface, "wl_shm") == 0) init_wl_shm(plugin_cairo, id, version); else if (strcmp(interface, "wl_seat") == 0) init_wl_seat(plugin_cairo, id, version); else if (strcmp(interface, "wl_output") == 0) init_wl_output(plugin_cairo, id, version); } static void remove_surface_outputs(struct border_component *cmpnt, struct output *output) { struct surface_output *surface_output; wl_list_for_each(surface_output, &cmpnt->server.output_list, link) { if (surface_output->output == output) { wl_list_remove(&surface_output->link); free(surface_output); break; } } } static void output_removed(struct libdecor_plugin_cairo *plugin_cairo, struct output *output) { struct libdecor_frame_cairo *frame_cairo; struct seat *seat; wl_list_for_each(frame_cairo, &plugin_cairo->visible_frame_list, link) { remove_surface_outputs(&frame_cairo->shadow, output); remove_surface_outputs(&frame_cairo->title_bar.title, output); remove_surface_outputs(&frame_cairo->title_bar.min, output); remove_surface_outputs(&frame_cairo->title_bar.max, output); remove_surface_outputs(&frame_cairo->title_bar.close, output); } wl_list_for_each(seat, &plugin_cairo->seat_list, link) { struct cursor_output *cursor_output; 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_cairo *plugin_cairo = user_data; struct output *output; wl_list_for_each(output, &plugin_cairo->output_list, link) { if (output->id == name) { output_removed(plugin_cairo, output); break; } } } static const struct wl_registry_listener registry_listener = { registry_handle_global, registry_handle_global_remove }; static bool has_required_globals(struct libdecor_plugin_cairo *plugin_cairo) { if (!plugin_cairo->wl_compositor) return false; if (!plugin_cairo->wl_subcompositor) return false; if (!plugin_cairo->wl_shm) return false; return true; } static void globals_callback(void *user_data, struct wl_callback *callback, uint32_t time) { struct libdecor_plugin_cairo *plugin_cairo = user_data; wl_callback_destroy(callback); plugin_cairo->globals_callback = NULL; if (!has_required_globals(plugin_cairo)) { libdecor_notify_plugin_error( plugin_cairo->context, LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, "Compositor is missing required globals"); } } static const struct wl_callback_listener globals_callback_listener = { globals_callback }; static struct libdecor_plugin * libdecor_plugin_new(struct libdecor *context) { struct libdecor_plugin_cairo *plugin_cairo; struct wl_display *wl_display; plugin_cairo = zalloc(sizeof *plugin_cairo); libdecor_plugin_init(&plugin_cairo->plugin, context, &cairo_plugin_iface); plugin_cairo->context = context; wl_list_init(&plugin_cairo->visible_frame_list); wl_list_init(&plugin_cairo->seat_list); wl_list_init(&plugin_cairo->output_list); /* fetch cursor theme and size*/ if (!libdecor_get_cursor_settings(&plugin_cairo->cursor_theme_name, &plugin_cairo->cursor_size)) { plugin_cairo->cursor_theme_name = NULL; plugin_cairo->cursor_size = 24; } /* define a sens-serif bold font at symbol size */ plugin_cairo->font = pango_font_description_new(); pango_font_description_set_family(plugin_cairo->font, "sans"); pango_font_description_set_weight(plugin_cairo->font, PANGO_WEIGHT_BOLD); pango_font_description_set_size(plugin_cairo->font, SYM_DIM * PANGO_SCALE); wl_display = libdecor_get_wl_display(context); plugin_cairo->wl_registry = wl_display_get_registry(wl_display); wl_registry_add_listener(plugin_cairo->wl_registry, ®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); 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.1.0/src/plugins/cairo/meson.build000066400000000000000000000010751407552333500210140ustar00rootroot00000000000000cairo_dep = dependency('cairo') pangocairo_dep = dependency('pangocairo') wayland_cursor_dep = dependency('wayland-cursor') math_dep = cc.find_library('m', required: true) shared_module('decor-cairo', sources: [ 'libdecor-cairo.c', 'libdecor-cairo-blur.c', ], include_directories: [ top_includepath, libdecor_includepath, ], c_args: libdecor_c_args, dependencies: [ libdecor_dep, cairo_dep, pangocairo_dep, math_dep, wayland_cursor_dep, cursor_settings_dep, ], install_dir: join_paths(plugindir), install: true, ) libdecor-0.1.0/src/plugins/dummy/000077500000000000000000000000001407552333500167055ustar00rootroot00000000000000libdecor-0.1.0/src/plugins/dummy/libdecor-dummy.c000066400000000000000000000116531407552333500217730ustar00rootroot00000000000000/* * 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_translate_coordinate(struct libdecor_plugin *plugin, struct libdecor_frame *frame, int content_x, int content_y, int *frame_x, int *frame_y) { *frame_x = content_x; *frame_y = content_y; } static void libdecor_plugin_dummy_frame_popup_grab(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name) { } static void libdecor_plugin_dummy_frame_popup_ungrab(struct libdecor_plugin *plugin, struct libdecor_frame *frame, const char *seat_name) { } static bool libdecor_plugin_dummy_configuration_get_content_size( struct libdecor_plugin *plugin, struct libdecor_configuration *configuration, struct libdecor_frame *frame, int *content_width, int *content_height) { return libdecor_configuration_get_window_size(configuration, content_width, content_height); } static bool libdecor_plugin_dummy_frame_get_window_size_for( struct libdecor_plugin *plugin, struct libdecor_frame *frame, struct libdecor_state *state, int *window_width, int *window_height) { *window_width = libdecor_state_get_content_width (state); *window_height = libdecor_state_get_content_height (state); return true; } static struct libdecor_plugin_interface dummy_plugin_iface = { .destroy = libdecor_plugin_dummy_destroy, .frame_new = libdecor_plugin_dummy_frame_new, .frame_free = libdecor_plugin_dummy_frame_free, .frame_commit = libdecor_plugin_dummy_frame_commit, .frame_property_changed = libdecor_plugin_dummy_frame_property_changed, .frame_translate_coordinate = libdecor_plugin_dummy_frame_translate_coordinate, .frame_popup_grab = libdecor_plugin_dummy_frame_popup_grab, .frame_popup_ungrab = libdecor_plugin_dummy_frame_popup_ungrab, .configuration_get_content_size = libdecor_plugin_dummy_configuration_get_content_size, .frame_get_window_size_for = libdecor_plugin_dummy_frame_get_window_size_for, }; static struct libdecor_plugin * libdecor_plugin_new(struct libdecor *context) { struct libdecor_plugin_dummy *plugin_dummy; plugin_dummy = zalloc(sizeof *plugin_dummy); 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.1.0/src/plugins/dummy/meson.build000066400000000000000000000003321407552333500210450ustar00rootroot00000000000000shared_module('decor-dummy', sources: [ 'libdecor-dummy.c', ], include_directories: [ top_includepath, libdecor_includepath, ], c_args: libdecor_c_args, dependencies: [ libdecor_dep, ], ) libdecor-0.1.0/src/plugins/meson.build000066400000000000000000000000401407552333500177060ustar00rootroot00000000000000subdir('cairo') subdir('dummy') libdecor-0.1.0/src/utils.h000066400000000000000000000030211407552333500153760ustar00rootroot00000000000000/* * 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 */