2581 lines
81 KiB
C
Executable File
2581 lines
81 KiB
C
Executable File
#if 0
|
|
libdecor_path="/home/mr4th/mr4th/libdecor"
|
|
root_path="$PWD"
|
|
gtk_flags="$(pkg-config --cflags --libs gtk+-3.0)"
|
|
dbus_flags="$(pkg-config --cflags --libs dbus-1)"
|
|
my_flags="-Iwayland -I$libdecor_path/src -I$libdecor_path/src/plugins -I$libdecor_path/build"
|
|
my_flags+=" -lwayland-client -lwayland-cursor -lwayland-egl -lEGL -lm"
|
|
mkdir -p build
|
|
clang -o build/demo -g $root_path/digesting_libdecor.c $gtk_flags $dbus_flags $my_flags
|
|
exit 0
|
|
#endif
|
|
|
|
/*
|
|
** Reading From:
|
|
** (1) Wayland Docs https://wayland.freedesktop.org/docs/html/
|
|
** (egl) EGL spec https://registry.khronos.org/EGL/sdk/docs/man/
|
|
** (libdecor.h) /usr/include/libdecor-0/libdecor.h
|
|
**
|
|
** (nodocs-wl_egl) I cannot find any documentation for wl_egl_ except for
|
|
** headers and example code.
|
|
*/
|
|
|
|
/* [1] IMPORTANT NOTE @see([1] in wayland_xdg_egl.c) */
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <wayland-client.h>
|
|
#include <wayland-client-core.h>
|
|
#include <wayland-cursor.h>
|
|
|
|
#include <wayland-egl.h>
|
|
/*~ NOTE: wayland-egl.h *before* EGL/ */
|
|
#include <EGL/egl.h>
|
|
#include <EGL/eglext.h>
|
|
#include <GL/glcorearb.h>
|
|
|
|
#include <errno.h>
|
|
#include <dirent.h>
|
|
#include <dlfcn.h>
|
|
#include <sys/types.h>
|
|
#include <poll.h>
|
|
#include <math.h>
|
|
#include <linux/input.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <signal.h>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <cairo/cairo.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "xdg-shell-client-protocol.h"
|
|
#include "xdg-decoration-client-protocol.h"
|
|
|
|
#include "xdg-shell-client-protocol.c"
|
|
#include "xdg-decoration-client-protocol.c"
|
|
|
|
#include "digesting_libdecor.h"
|
|
|
|
// X(N:name,R:return,P:params)
|
|
#define GL_FUNCS_XLIST(X)\
|
|
X(glDrawBuffer, void, (GLenum buf)) \
|
|
X(glViewport, void, (GLint x, GLint y, GLsizei w, GLsizei h)) \
|
|
X(glClear, void, (GLbitfield mask)) \
|
|
X(glClearColor, void, (GLfloat r, GLfloat g, GLfloat b, GLfloat a))
|
|
|
|
#define X(N,R,P) R (*N)P = 0;
|
|
GL_FUNCS_XLIST(X)
|
|
#undef X
|
|
|
|
static Ctx ctx = {0};
|
|
|
|
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);
|
|
}
|
|
|
|
const struct xdg_wm_base_listener xdg_wm_base_listener = { xdg_wm_base_ping, };
|
|
|
|
static void
|
|
shm_format(void *user_data, struct wl_shm *wl_shm, uint32_t format){
|
|
if (format == WL_SHM_FORMAT_ARGB8888){
|
|
ctx.has_argb = true;
|
|
}
|
|
}
|
|
|
|
const struct wl_shm_listener shm_listener = { shm_format };
|
|
|
|
static void
|
|
pointer_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
|
|
struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y){
|
|
struct seat *seat = data;
|
|
|
|
seat->pointer_x = wl_fixed_to_int(x);
|
|
seat->pointer_y = wl_fixed_to_int(y);
|
|
seat->serial = serial;
|
|
seat->pointer_focus = surface;
|
|
ctx.active = component_slot_from_wl_surface(surface);
|
|
|
|
if (own_proxy(surface)){
|
|
if (ctx.active != 0){
|
|
draw_decoration();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
}
|
|
|
|
seat->current_cursor = wl_cursor_from_pos(seat->pointer_x, seat->pointer_y);
|
|
if (seat->current_cursor != 0){
|
|
struct wl_cursor_image *image = seat->current_cursor->images[0];
|
|
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
|
|
wl_surface_set_buffer_scale(seat->cursor_surface, 1);
|
|
wl_surface_attach(seat->cursor_surface, buffer, 0, 0);
|
|
wl_surface_damage_buffer(seat->cursor_surface, 0, 0, image->width, image->height);
|
|
wl_surface_commit(seat->cursor_surface);
|
|
wl_pointer_set_cursor(seat->wl_pointer, seat->serial, seat->cursor_surface, image->hotspot_x, image->hotspot_y);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pointer_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface){
|
|
struct seat *seat = data;
|
|
|
|
seat->pointer_focus = 0;
|
|
|
|
if (surface != 0 && own_proxy(surface)){
|
|
ctx.titlebar_gesture.state = TITLEBAR_GESTURE_STATE_INIT;
|
|
ctx.titlebar_gesture.first_pressed_button = 0;
|
|
|
|
ctx.active = 0;
|
|
ctx.hdr_focus.widget = 0;
|
|
ctx.hdr_focus.type = HEADER_NONE;
|
|
draw_decoration();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
seat->current_cursor = wl_cursor_from_pos(seat->pointer_x, seat->pointer_y);
|
|
}
|
|
}
|
|
|
|
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 header_element_data new_focus;
|
|
|
|
if (own_proxy(seat->pointer_focus)){
|
|
seat->pointer_x = wl_fixed_to_int(surface_x);
|
|
seat->pointer_y = wl_fixed_to_int(surface_y);
|
|
seat->current_cursor = wl_cursor_from_pos(seat->pointer_x, seat->pointer_y);
|
|
if (seat->current_cursor != 0){
|
|
struct wl_cursor_image *image = seat->current_cursor->images[0];
|
|
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
|
|
wl_surface_attach(seat->cursor_surface, buffer, 0, 0);
|
|
wl_surface_set_buffer_scale(seat->cursor_surface, 1);
|
|
wl_surface_damage_buffer(seat->cursor_surface, 0, 0, image->width, image->height);
|
|
wl_surface_commit(seat->cursor_surface);
|
|
wl_pointer_set_cursor(seat->wl_pointer, seat->serial, seat->cursor_surface, image->hotspot_x, image->hotspot_y);
|
|
}
|
|
|
|
/* avoid warnings after decoration has been turned off */
|
|
if (!GTK_IS_WIDGET(ctx.header) || ctx.active != COMPONENT_SLOT_HEADER){
|
|
ctx.hdr_focus.type = HEADER_NONE;
|
|
}
|
|
|
|
new_focus = get_header_focus(GTK_HEADER_BAR(ctx.header), seat->pointer_x, seat->pointer_y);
|
|
|
|
/* only update if widget change so that we keep the state */
|
|
if (ctx.hdr_focus.widget != new_focus.widget){
|
|
ctx.hdr_focus = new_focus;
|
|
}
|
|
ctx.hdr_focus.state |= GTK_STATE_FLAG_PRELIGHT;
|
|
/* redraw with updated button visuals */
|
|
draw_title_bar();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
|
|
if (ctx.titlebar_gesture.state == TITLEBAR_GESTURE_STATE_BUTTON_PRESSED){
|
|
if (ctx.titlebar_gesture.first_pressed_button == BTN_LEFT){
|
|
int xd = ABS(seat->pointer_x - ctx.titlebar_gesture.pressed_x);
|
|
int yd = ABS(seat->pointer_y - ctx.titlebar_gesture.pressed_y);
|
|
if (xd > ctx.drag_threshold || yd > ctx.drag_threshold){
|
|
xdg_toplevel_move(ctx.xdg_toplevel, seat->wl_seat, ctx.titlebar_gesture.serial);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (own_proxy(seat->pointer_focus)){
|
|
switch (ctx.active){
|
|
case COMPONENT_SLOT_SHADOW: {
|
|
enum libdecor_resize_edge edge = edge_from_pos(seat->pointer_x, seat->pointer_y);
|
|
if (edge != LIBDECOR_RESIZE_EDGE_NONE &&
|
|
(ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE)){
|
|
xdg_toplevel_resize(ctx.xdg_toplevel, seat->wl_seat, serial, xdg_edge_from_edge(edge));
|
|
}
|
|
}break;
|
|
|
|
case COMPONENT_SLOT_HEADER: {
|
|
switch (ctx.titlebar_gesture.state){
|
|
case TITLEBAR_GESTURE_STATE_INIT: {
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED){
|
|
if (button == BTN_RIGHT){
|
|
const int title_height = gtk_widget_get_allocated_height(ctx.header);
|
|
xdg_toplevel_show_window_menu(ctx.xdg_toplevel, seat->wl_seat, serial, seat->pointer_x, -title_height);
|
|
ctx.titlebar_gesture.state = TITLEBAR_GESTURE_STATE_CONSUMED;
|
|
}
|
|
else{
|
|
if (button == BTN_LEFT &&
|
|
ctx.titlebar_gesture.first_pressed_button == BTN_LEFT &&
|
|
time - ctx.titlebar_gesture.first_pressed_time < (uint32_t)ctx.double_click_time_ms){
|
|
if ((ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE)){
|
|
toggle_maximized();
|
|
}
|
|
ctx.titlebar_gesture.state = TITLEBAR_GESTURE_STATE_CONSUMED;
|
|
}
|
|
else{
|
|
ctx.titlebar_gesture.first_pressed_button = button;
|
|
ctx.titlebar_gesture.first_pressed_time = time;
|
|
ctx.titlebar_gesture.pressed_x = seat->pointer_x;
|
|
ctx.titlebar_gesture.pressed_y = seat->pointer_y;
|
|
ctx.titlebar_gesture.serial = serial;
|
|
ctx.titlebar_gesture.state = TITLEBAR_GESTURE_STATE_BUTTON_PRESSED;
|
|
}
|
|
}
|
|
|
|
ctx.titlebar_gesture.button_pressed_count = 1;
|
|
|
|
switch (ctx.hdr_focus.type){
|
|
case HEADER_MIN:
|
|
case HEADER_MAX:
|
|
case HEADER_CLOSE: {
|
|
ctx.hdr_focus.state |= GTK_STATE_FLAG_ACTIVE;
|
|
draw_title_bar();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
}break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
}break;
|
|
|
|
case TITLEBAR_GESTURE_STATE_BUTTON_PRESSED: {
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
|
ctx.titlebar_gesture.state = TITLEBAR_GESTURE_STATE_DISCARDED;
|
|
ctx.titlebar_gesture.button_pressed_count += 1;
|
|
}
|
|
else{
|
|
ctx.titlebar_gesture.button_pressed_count -= 1;
|
|
|
|
if (ctx.titlebar_gesture.button_pressed_count == 0) {
|
|
ctx.titlebar_gesture.state = TITLEBAR_GESTURE_STATE_INIT;
|
|
if (ctx.titlebar_gesture.first_pressed_button == button &&
|
|
button == BTN_LEFT) {
|
|
switch (ctx.hdr_focus.type) {
|
|
case HEADER_MIN: {
|
|
if (ctx.frame_capabilities & LIBDECOR_ACTION_MINIMIZE){
|
|
xdg_toplevel_set_minimized(ctx.xdg_toplevel);
|
|
}
|
|
}break;
|
|
|
|
case HEADER_MAX: {
|
|
if ((ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE)){
|
|
toggle_maximized();
|
|
}
|
|
}break;
|
|
|
|
case HEADER_CLOSE: {
|
|
if (ctx.frame_capabilities & LIBDECOR_ACTION_CLOSE){
|
|
ctx.close_signal = 1;
|
|
seat->pointer_focus = 0;
|
|
}
|
|
}break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
ctx.hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE;
|
|
if (GTK_IS_WIDGET(ctx.header)){
|
|
draw_title_bar();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
ctx.hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE;
|
|
if (GTK_IS_WIDGET(ctx.header)) {
|
|
draw_title_bar();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
}
|
|
}
|
|
|
|
}
|
|
}break;
|
|
|
|
case TITLEBAR_GESTURE_STATE_CONSUMED:
|
|
case TITLEBAR_GESTURE_STATE_DISCARDED: {
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED){
|
|
ctx.titlebar_gesture.button_pressed_count++;
|
|
}
|
|
else{
|
|
ctx.titlebar_gesture.button_pressed_count--;
|
|
if (ctx.titlebar_gesture.button_pressed_count == 0) {
|
|
ctx.titlebar_gesture.state = TITLEBAR_GESTURE_STATE_INIT;
|
|
ctx.titlebar_gesture.first_pressed_button = 0;
|
|
}
|
|
}
|
|
}break;
|
|
}
|
|
}break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pointer_axis(void *data, struct wl_pointer *wl_pointer,
|
|
uint32_t time, uint32_t axis, wl_fixed_t value){}
|
|
|
|
const struct wl_pointer_listener pointer_listener = {
|
|
pointer_enter,
|
|
pointer_leave,
|
|
pointer_motion,
|
|
pointer_button,
|
|
pointer_axis
|
|
};
|
|
|
|
static void
|
|
touch_down(void *data, struct wl_touch *wl_touch, uint32_t serial,
|
|
uint32_t time, struct wl_surface *surface, int32_t id,
|
|
wl_fixed_t x, wl_fixed_t y){
|
|
struct seat *seat = data;
|
|
|
|
if (surface != 0 && own_proxy(surface)){
|
|
|
|
seat->touch_focus = surface;
|
|
ctx.touch_active = component_slot_from_wl_surface(surface);
|
|
|
|
if (ctx.touch_active){
|
|
update_touch_focus(seat, x, y);
|
|
|
|
/* update decorations */
|
|
draw_decoration();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
|
|
switch (ctx.touch_active){
|
|
case COMPONENT_SLOT_SHADOW: {
|
|
enum libdecor_resize_edge edge = edge_from_pos(wl_fixed_to_int(x), wl_fixed_to_int(y));
|
|
if (edge != LIBDECOR_RESIZE_EDGE_NONE &&
|
|
(ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE)){
|
|
xdg_toplevel_resize(ctx.xdg_toplevel, seat->wl_seat, serial, xdg_edge_from_edge(edge));
|
|
}
|
|
}break;
|
|
|
|
case COMPONENT_SLOT_HEADER: {
|
|
switch (ctx.hdr_focus.type){
|
|
case HEADER_MIN:
|
|
case HEADER_MAX:
|
|
case HEADER_CLOSE: {
|
|
ctx.hdr_focus.state |= GTK_STATE_FLAG_ACTIVE;
|
|
draw_title_bar();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
}break;
|
|
|
|
default: {
|
|
if (time - seat->touch_down_time_stamp < (uint32_t)ctx.double_click_time_ms) {
|
|
if ((ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE)){
|
|
toggle_maximized();
|
|
}
|
|
}
|
|
else{
|
|
if (ctx.frame_capabilities & LIBDECOR_ACTION_MOVE){
|
|
seat->touch_down_time_stamp = time;
|
|
xdg_toplevel_move(ctx.xdg_toplevel, seat->wl_seat, serial);
|
|
}
|
|
}
|
|
}break;
|
|
}
|
|
}break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
touch_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id){
|
|
struct seat *seat = data;
|
|
if (seat->touch_focus && own_proxy(seat->touch_focus)){
|
|
if (ctx.touch_active != 0){
|
|
if (ctx.touch_active == COMPONENT_SLOT_HEADER){
|
|
switch (ctx.hdr_focus.type) {
|
|
case HEADER_MIN: {
|
|
if (ctx.frame_capabilities & LIBDECOR_ACTION_MINIMIZE){
|
|
xdg_toplevel_set_minimized(ctx.xdg_toplevel);
|
|
}
|
|
}break;
|
|
|
|
case HEADER_MAX: {
|
|
if ((ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE)){
|
|
toggle_maximized();
|
|
}
|
|
}break;
|
|
|
|
case HEADER_CLOSE: {
|
|
if (ctx.frame_capabilities & LIBDECOR_ACTION_CLOSE){
|
|
ctx.close_signal = 1;
|
|
seat->touch_focus = 0;
|
|
}
|
|
}break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
/* unset active/clicked state once released */
|
|
ctx.hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE;
|
|
if (GTK_IS_WIDGET(ctx.header)) {
|
|
draw_title_bar();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
}
|
|
}
|
|
|
|
seat->touch_focus = 0;
|
|
ctx.touch_active = 0;
|
|
ctx.hdr_focus.widget = 0;
|
|
ctx.hdr_focus.type = HEADER_NONE;
|
|
draw_decoration();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
touch_motion(void *data, struct wl_touch *wl_touch, uint32_t time,
|
|
int32_t id, wl_fixed_t x, wl_fixed_t y){
|
|
struct seat *seat = data;
|
|
if (seat->touch_focus != 0 && own_proxy(seat->touch_focus)){
|
|
update_touch_focus(seat, x, y);
|
|
}
|
|
}
|
|
|
|
static void
|
|
touch_frame(void *data, struct wl_touch *wl_touch){}
|
|
|
|
static void
|
|
touch_cancel(void *data, struct wl_touch *wl_touch){}
|
|
|
|
const struct wl_touch_listener touch_listener = {
|
|
touch_down,
|
|
touch_up,
|
|
touch_motion,
|
|
touch_frame,
|
|
touch_cancel
|
|
};
|
|
|
|
static void
|
|
seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities){
|
|
struct seat *seat = data;
|
|
|
|
if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer == 0){
|
|
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 != 0){
|
|
wl_pointer_release(seat->wl_pointer);
|
|
seat->wl_pointer = 0;
|
|
}
|
|
|
|
if ((capabilities & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch == 0){
|
|
seat->wl_touch = wl_seat_get_touch(wl_seat);
|
|
wl_touch_add_listener(seat->wl_touch, &touch_listener, seat);
|
|
}
|
|
else if (!(capabilities & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch != 0){
|
|
wl_touch_release(seat->wl_touch);
|
|
seat->wl_touch = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
seat_name(void *data, struct wl_seat *wl_seat, const char *name){
|
|
struct seat *seat = (struct seat*)data;
|
|
seat->name = strdup(name);
|
|
}
|
|
|
|
const struct wl_seat_listener seat_listener = {
|
|
seat_capabilities,
|
|
seat_name
|
|
};
|
|
|
|
static void
|
|
wl_registry_global(void *data, struct wl_registry *wl_registry,
|
|
uint32_t name, const char *interface,
|
|
uint32_t version){
|
|
if (strcmp(interface, "wl_compositor") == 0){
|
|
ctx.wl_compositor = (struct wl_compositor*)
|
|
wl_registry_bind(wl_registry, name, &wl_compositor_interface, MIN(version, 4));
|
|
}
|
|
else if (!strcmp(interface, xdg_wm_base_interface.name)){
|
|
ctx.xdg_wm_base = wl_registry_bind(ctx.wl_registry, name, &xdg_wm_base_interface, MIN(version, 2));
|
|
xdg_wm_base_add_listener(ctx.xdg_wm_base, &xdg_wm_base_listener, 0);
|
|
}
|
|
else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)){
|
|
uint32_t desired_version = 2;
|
|
|
|
/* Find the required version for the available features. */
|
|
#ifdef XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION
|
|
desired_version = MAX(desired_version, XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION);
|
|
#endif
|
|
#ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION
|
|
desired_version = MAX(desired_version, XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION);
|
|
#endif
|
|
#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION
|
|
desired_version = MAX(desired_version, XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION);
|
|
#endif
|
|
#ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION
|
|
desired_version = MAX(desired_version, XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION);
|
|
#endif
|
|
|
|
ctx.decoration_manager = wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, MIN(version, desired_version));
|
|
}
|
|
else if (strcmp(interface, "wl_subcompositor") == 0){
|
|
ctx.wl_subcompositor = wl_registry_bind(ctx.wl_registry, name, &wl_subcompositor_interface, 1);
|
|
}
|
|
else if (strcmp(interface, "wl_shm") == 0){
|
|
ctx.wl_shm = wl_registry_bind(ctx.wl_registry, name, &wl_shm_interface, 1);
|
|
wl_shm_add_listener(ctx.wl_shm, &shm_listener, 0);
|
|
}
|
|
else if (strcmp(interface, "wl_seat") == 0){
|
|
struct seat *seat;
|
|
if (version < 3){
|
|
ctx.has_error = true;
|
|
}
|
|
|
|
seat = calloc(1, sizeof *seat);
|
|
wl_list_insert(&ctx.seat_list, &seat->link);
|
|
seat->wl_seat = wl_registry_bind(ctx.wl_registry, name, &wl_seat_interface, 3);
|
|
wl_seat_add_listener(seat->wl_seat, &seat_listener, seat);
|
|
|
|
seat->cursor_surface = wl_compositor_create_surface(ctx.wl_compositor);
|
|
}
|
|
}
|
|
|
|
static void
|
|
wl_registry_global_remove(void *data, struct wl_registry *registry,
|
|
uint32_t name){}
|
|
|
|
const struct wl_registry_listener wl_registry_listener = {
|
|
wl_registry_global,
|
|
wl_registry_global_remove,
|
|
};
|
|
|
|
static void
|
|
xdg_surface_configure(void *user_data, struct xdg_surface *xdg_surface, uint32_t serial){
|
|
struct libdecor_configuration *configuration;
|
|
|
|
configuration = ctx.pending_configuration;
|
|
ctx.pending_configuration = 0;
|
|
|
|
if (configuration == 0){
|
|
configuration = calloc(1, sizeof *configuration);
|
|
}
|
|
|
|
configuration->serial = serial;
|
|
|
|
{
|
|
int w = ctx.w;
|
|
int h = ctx.h;
|
|
if (libdecor_configuration_get_content_size(configuration, &w, &h)){
|
|
ctx.w = w;
|
|
ctx.h = h;
|
|
}
|
|
if (!ctx.configured){
|
|
ctx.configured = 1;
|
|
libdecor_frame_commit(w, h, configuration);
|
|
}
|
|
else{
|
|
ctx.has_cached_config = 1;
|
|
ctx.cached_config = *configuration;
|
|
}
|
|
}
|
|
|
|
free(configuration);
|
|
}
|
|
|
|
const struct xdg_surface_listener xdg_surface_listener = {
|
|
xdg_surface_configure,
|
|
};
|
|
|
|
static void
|
|
xdg_toplevel_configure(void *user_data, struct xdg_toplevel *xdg_toplevel,
|
|
int32_t width, int32_t height, struct wl_array *states){
|
|
enum libdecor_window_state window_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: window_state |= LIBDECOR_WINDOW_STATE_FULLSCREEN; break;
|
|
case XDG_TOPLEVEL_STATE_MAXIMIZED: window_state |= LIBDECOR_WINDOW_STATE_MAXIMIZED; break;
|
|
case XDG_TOPLEVEL_STATE_ACTIVATED: window_state |= LIBDECOR_WINDOW_STATE_ACTIVE; break;
|
|
case XDG_TOPLEVEL_STATE_TILED_LEFT: window_state |= LIBDECOR_WINDOW_STATE_TILED_LEFT; break;
|
|
case XDG_TOPLEVEL_STATE_TILED_RIGHT: window_state |= LIBDECOR_WINDOW_STATE_TILED_RIGHT; break;
|
|
case XDG_TOPLEVEL_STATE_TILED_TOP: window_state |= LIBDECOR_WINDOW_STATE_TILED_TOP; break;
|
|
case XDG_TOPLEVEL_STATE_TILED_BOTTOM: window_state |= LIBDECOR_WINDOW_STATE_TILED_BOTTOM; break;
|
|
case XDG_TOPLEVEL_STATE_RESIZING: window_state |= LIBDECOR_WINDOW_STATE_RESIZING; break;
|
|
#ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION
|
|
case XDG_TOPLEVEL_STATE_SUSPENDED: window_state |= LIBDECOR_WINDOW_STATE_SUSPENDED; break;
|
|
#endif
|
|
#ifdef XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT: window_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_LEFT; break;
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT: window_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_RIGHT; break;
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP: window_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_TOP; break;
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: window_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_BOTTOM; break;
|
|
#endif
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
ctx.pending_configuration = calloc(1, sizeof *ctx.pending_configuration);
|
|
ctx.pending_configuration->has_size = true;
|
|
ctx.pending_configuration->window_width = width;
|
|
ctx.pending_configuration->window_height = height;
|
|
ctx.pending_configuration->has_window_state = true;
|
|
ctx.pending_configuration->window_state = window_state;
|
|
}
|
|
|
|
static void
|
|
xdg_toplevel_close(void *user_data, struct xdg_toplevel *xdg_toplevel){
|
|
ctx.close_signal = 1;
|
|
}
|
|
|
|
#ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION
|
|
static void
|
|
xdg_toplevel_configure_bounds(void *user_data, struct xdg_toplevel *xdg_toplevel, int32_t w, int32_t h){
|
|
int32_t inner_w = w;
|
|
int32_t inner_h = h;
|
|
if (ctx.visible &&
|
|
ctx.decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE){
|
|
Sides2D border_size = border_size_from_window_state(0);
|
|
inner_w -= border_size.x[0] + border_size.x[1];
|
|
inner_h -= border_size.y[0] + border_size.y[1];
|
|
}
|
|
// event to user (inner_w,inner_h)
|
|
}
|
|
#endif
|
|
|
|
#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION
|
|
static void
|
|
xdg_toplevel_wm_capabilities(void *user_data,
|
|
struct xdg_toplevel *xdg_toplevel,
|
|
struct wl_array *capabilities){
|
|
enum xdg_toplevel_wm_capabilities *wm_cap;
|
|
|
|
ctx.wm_capabilities = 0;
|
|
|
|
wl_array_for_each(wm_cap, capabilities) {
|
|
switch (*wm_cap) {
|
|
case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU: {
|
|
ctx.wm_capabilities |= LIBDECOR_WM_CAPABILITIES_WINDOW_MENU;
|
|
}break;
|
|
|
|
case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: {
|
|
ctx.wm_capabilities |= LIBDECOR_WM_CAPABILITIES_MAXIMIZE;
|
|
}break;
|
|
|
|
case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: {
|
|
ctx.wm_capabilities |= LIBDECOR_WM_CAPABILITIES_FULLSCREEN;
|
|
}break;
|
|
|
|
case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: {
|
|
ctx.wm_capabilities |= LIBDECOR_WM_CAPABILITIES_MINIMIZE;
|
|
}break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
|
xdg_toplevel_configure,
|
|
xdg_toplevel_close,
|
|
#ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION
|
|
xdg_toplevel_configure_bounds,
|
|
#endif
|
|
#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION
|
|
xdg_toplevel_wm_capabilities,
|
|
#endif
|
|
};
|
|
|
|
static void
|
|
xdg_toplevel_decoration_configure(void *data, struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode){
|
|
if (!ctx.has_decoration_mode){
|
|
ctx.has_decoration_mode = true;
|
|
ctx.decoration_mode = mode;
|
|
}
|
|
}
|
|
|
|
const struct zxdg_toplevel_decoration_v1_listener
|
|
xdg_toplevel_decoration_listener = {
|
|
xdg_toplevel_decoration_configure,
|
|
};
|
|
|
|
int main(){
|
|
/* get desktop settings */
|
|
ctx.color_scheme = libdecor_get_color_scheme();
|
|
if (libdecor_get_cursor_settings(&ctx.cursor_theme_name, &ctx.cursor_size)){
|
|
ctx.cursor_theme_name = 0;
|
|
ctx.cursor_size = 24;
|
|
}
|
|
|
|
/* setup GTK context */
|
|
int gtk_init_success = 0;
|
|
{
|
|
gdk_set_allowed_backends("wayland");
|
|
gtk_disable_setlocale();
|
|
if (gtk_init_check(0, 0)){
|
|
gtk_init_success = 1;
|
|
}
|
|
|
|
g_object_set(gtk_settings_get_default(),
|
|
"gtk-application-prefer-dark-theme",
|
|
(ctx.color_scheme == LIBDECOR_COLOR_SCHEME_PREFER_DARK), NULL);
|
|
|
|
if (!gtk_init_success){
|
|
printf("failed to initialize gtk\n");
|
|
}
|
|
}
|
|
|
|
/*~ NOTE:
|
|
**~ initialize Wayland, Libdecor, & EGL
|
|
*/
|
|
|
|
{
|
|
ctx.w = 640;
|
|
ctx.h = 480;
|
|
wl_list_init(&ctx.seat_list);
|
|
}
|
|
|
|
if (gtk_init_success){
|
|
ctx.wl_display = wl_display_connect(0);
|
|
if (ctx.wl_display == 0){
|
|
printf("wl_display_connect failed\n");
|
|
}
|
|
}
|
|
|
|
if (ctx.wl_display != 0){
|
|
ctx.wl_registry = wl_display_get_registry(ctx.wl_display);
|
|
if (ctx.wl_registry == 0){
|
|
printf("wl_display_get_registry failed\n");
|
|
}
|
|
}
|
|
|
|
if (ctx.wl_registry != 0){
|
|
wl_registry_add_listener(ctx.wl_registry, &wl_registry_listener, 0);
|
|
|
|
wl_display_flush(ctx.wl_display);
|
|
wl_display_dispatch(ctx.wl_display);
|
|
wl_display_roundtrip(ctx.wl_display);
|
|
|
|
if (ctx.wl_compositor == 0){
|
|
printf("failed to get wl_compositor\n");
|
|
ctx.has_error = 1;
|
|
}
|
|
if (ctx.wl_subcompositor == 0){
|
|
printf("failed to get wl_subcompositor\n");
|
|
ctx.has_error = 1;
|
|
}
|
|
if (ctx.wl_shm == 0){
|
|
printf("failed to get wl_shm\n");
|
|
ctx.has_error = 1;
|
|
}
|
|
}
|
|
|
|
if (!ctx.has_error){
|
|
ctx.cursor_theme = wl_cursor_theme_load(ctx.cursor_theme_name, ctx.cursor_size, ctx.wl_shm);
|
|
if (ctx.cursor_theme != 0){
|
|
for (unsigned int i = 0; i < ARRAY_LENGTH(cursor_names); i += 1){
|
|
ctx.cursors[i] = wl_cursor_theme_get_cursor(ctx.cursor_theme, cursor_names[i]);
|
|
}
|
|
ctx.cursor_left_ptr = wl_cursor_theme_get_cursor(ctx.cursor_theme, "left_ptr");
|
|
}
|
|
}
|
|
|
|
int opengl_load_success = 0;
|
|
if (!ctx.has_error){
|
|
/* (egl) eglGetDisplay
|
|
** " obtains the EGL display connection for the native display "
|
|
*/
|
|
ctx.egl_display = eglGetDisplay(ctx.wl_display);
|
|
if (ctx.egl_display == 0){
|
|
printf("eglGetDisplay failed\n");
|
|
}
|
|
|
|
/* (egl) eglInitialize
|
|
** " initializes* the EGL display connection obtained with eglGetDisplay "
|
|
*/
|
|
int egl_init_success = 0;
|
|
EGLint major = 0, minor = 0;
|
|
if (ctx.egl_display != 0){
|
|
egl_init_success = eglInitialize(ctx.egl_display, &major, &minor);
|
|
if (!egl_init_success){
|
|
printf("eglInitialize failed\n");
|
|
}
|
|
}
|
|
|
|
// print version
|
|
if (egl_init_success){
|
|
printf("EGL version %d.%d\n", major, minor);
|
|
if (major < 1 || (major == 1 && minor < 5)){
|
|
printf("version 1.5 or higher\n");
|
|
egl_init_success = 0;
|
|
}
|
|
}
|
|
|
|
/* (egl) eglBindAPI
|
|
** " defines the current rendering API for EGL in the thread it is
|
|
** called from "
|
|
*/
|
|
EGLBoolean bind_api_success = 0;
|
|
if (egl_init_success){
|
|
bind_api_success = eglBindAPI(EGL_OPENGL_API);
|
|
}
|
|
|
|
/* (egl) eglCreateContext
|
|
** " creates an EGL rendering context for the current rendering API
|
|
** (as set with eglBindAPI) and returns a handle to the context "
|
|
*/
|
|
if (bind_api_success){
|
|
EGLint attr[] = {
|
|
EGL_CONTEXT_MAJOR_VERSION, 3,
|
|
EGL_CONTEXT_MINOR_VERSION, 3,
|
|
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
|
|
EGL_NONE,
|
|
};
|
|
ctx.egl_context = eglCreateContext(ctx.egl_display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attr);
|
|
if (ctx.egl_context == EGL_NO_CONTEXT){
|
|
printf("eglCreateContext failed\n");
|
|
}
|
|
}
|
|
|
|
/* (egl) eglMakeCurrent
|
|
** " binds context to the current rendering thread "
|
|
*/
|
|
EGLBoolean make_current_success = 0;
|
|
if (ctx.egl_context != 0){
|
|
make_current_success = eglMakeCurrent(ctx.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx.egl_context);
|
|
if (!make_current_success){
|
|
printf("eglMakeCurrent failed\n");
|
|
}
|
|
}
|
|
|
|
/* (egl) eglGetProcAddress
|
|
** " returns the address of the client API or EGL function "
|
|
*/
|
|
if (make_current_success){
|
|
opengl_load_success = 1;
|
|
#define X(N,R,P) N = (R(*)P)(eglGetProcAddress(#N)); if (N == 0) opengl_load_success = 0;
|
|
GL_FUNCS_XLIST(X)
|
|
#undef X
|
|
if (!opengl_load_success){
|
|
printf("GL procedure loading failed\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*~ NOTE:
|
|
**~ Create a window
|
|
*/
|
|
|
|
if (opengl_load_success){
|
|
/* (1) Appendix A: wl_compositor::create_surface
|
|
** " create new surface "
|
|
*/
|
|
ctx.wl_surface = wl_compositor_create_surface(ctx.wl_compositor);
|
|
if (ctx.wl_surface == 0){
|
|
printf("wl_compositor_create_surface failed\n");
|
|
}
|
|
}
|
|
|
|
if (ctx.wl_surface != 0){
|
|
{
|
|
static const int size = 128;
|
|
static const int boundary = 32;
|
|
cairo_surface_t *shadow_blur =
|
|
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size);
|
|
cairo_t *cr = cairo_create(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(shadow_blur, 64);
|
|
ctx.shadow_blur = shadow_blur;
|
|
}
|
|
|
|
ctx.visible = true;
|
|
ctx.wm_capabilities = (LIBDECOR_WM_CAPABILITIES_WINDOW_MENU |
|
|
LIBDECOR_WM_CAPABILITIES_MAXIMIZE |
|
|
LIBDECOR_WM_CAPABILITIES_FULLSCREEN |
|
|
LIBDECOR_WM_CAPABILITIES_MINIMIZE);
|
|
ctx.frame_capabilities = (LIBDECOR_ACTION_MOVE |
|
|
LIBDECOR_ACTION_RESIZE |
|
|
LIBDECOR_ACTION_MINIMIZE |
|
|
LIBDECOR_ACTION_FULLSCREEN |
|
|
LIBDECOR_ACTION_CLOSE);
|
|
|
|
ctx.xdg_surface = xdg_wm_base_get_xdg_surface(ctx.xdg_wm_base, ctx.wl_surface);
|
|
xdg_surface_add_listener(ctx.xdg_surface, &xdg_surface_listener, 0);
|
|
|
|
ctx.xdg_toplevel = xdg_surface_get_toplevel(ctx.xdg_surface);
|
|
xdg_toplevel_add_listener(ctx.xdg_toplevel, &xdg_toplevel_listener, 0);
|
|
|
|
ctx.decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
|
|
if (ctx.decoration_manager != 0){
|
|
ctx.toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(ctx.decoration_manager, ctx.xdg_toplevel);
|
|
zxdg_toplevel_decoration_v1_add_listener(ctx.toplevel_decoration, &xdg_toplevel_decoration_listener, 0);
|
|
}
|
|
|
|
ctx.size_bounds.x[0] = 80;
|
|
ctx.size_bounds.y[0] = 60;
|
|
ctx.size_bounds.x[1] = (1 << 30);
|
|
ctx.size_bounds.y[1] = (1 << 30);
|
|
|
|
xdg_toplevel_set_app_id(ctx.xdg_toplevel, "demo");
|
|
xdg_toplevel_set_title(ctx.xdg_toplevel, "Example Window");
|
|
ctx.title = strdup("Example Window");
|
|
|
|
draw_decoration();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
|
|
/* (nodocs-wl_egl) */
|
|
ctx.wl_egl_window = wl_egl_window_create(ctx.wl_surface, ctx.w, ctx.h);
|
|
if (ctx.wl_egl_window == EGL_NO_SURFACE){
|
|
printf("wl_egl_window_create failed\n");
|
|
}
|
|
}
|
|
|
|
if (ctx.wl_egl_window != EGL_NO_SURFACE){
|
|
/* (nodocs-wl_egl) eglChooseConfig
|
|
** " returns in configs a list of all EGL frame buffer configurations
|
|
** that match the attributes specified in attrib_list "
|
|
*/
|
|
EGLConfig configs[64];
|
|
EGLint config_cap = sizeof(configs)/sizeof(*configs);
|
|
EGLint config_count = 0;
|
|
{
|
|
EGLint attributes[] = {
|
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
|
EGL_CONFORMANT, EGL_OPENGL_BIT,
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
|
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
|
|
EGL_RED_SIZE, 8,
|
|
EGL_GREEN_SIZE, 8,
|
|
EGL_BLUE_SIZE, 8,
|
|
EGL_DEPTH_SIZE, 24,
|
|
EGL_STENCIL_SIZE, 8,
|
|
EGL_NONE,
|
|
};
|
|
if (!eglChooseConfig(ctx.egl_display, attributes, configs, config_cap, &config_count)){
|
|
printf("eglChooseConfig failed\n");
|
|
config_count = 0;
|
|
}
|
|
}
|
|
|
|
/* (egl) eglCreateWindowSurface
|
|
** " creates an on-screen EGL window surface and returns a handle to it "
|
|
*/
|
|
{
|
|
EGLint attributes[] = {
|
|
EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
|
|
EGL_NONE,
|
|
};
|
|
for (EGLint i = 0; i < config_count; i += 1){
|
|
ctx.egl_surface = eglCreateWindowSurface(ctx.egl_display, configs[i], ctx.wl_egl_window, attributes);
|
|
if (ctx.egl_surface != EGL_NO_SURFACE){
|
|
break;
|
|
}
|
|
}
|
|
if (ctx.egl_surface == EGL_NO_SURFACE){
|
|
printf("eglCreateWindowSurface failed\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* (egl) eglMakeCurrent */
|
|
EGLBoolean make_current_success2 = 0;
|
|
if (ctx.egl_surface != EGL_NO_SURFACE){
|
|
make_current_success2 = eglMakeCurrent(ctx.egl_display, ctx.egl_surface, ctx.egl_surface, ctx.egl_context);
|
|
}
|
|
|
|
/* (egl) eglSwapInterval
|
|
** " specifies the minimum number of video frame periods per buffer swap
|
|
** for the window associated with the current context "
|
|
*/
|
|
EGLBoolean swap_interval_success = 0;
|
|
if (make_current_success2){
|
|
swap_interval_success = eglSwapInterval(ctx.egl_display, 1);
|
|
if (!swap_interval_success){
|
|
printf("eglSwapInterval failed\n");
|
|
}
|
|
}
|
|
|
|
/*~ NOTE: Main loop */
|
|
int exit_loop = 0;
|
|
if (!swap_interval_success){
|
|
exit_loop = 1;
|
|
}
|
|
for (;!exit_loop;){
|
|
for (;g_main_context_iteration(0, 0);){}
|
|
|
|
{
|
|
struct pollfd fds[1] = {0};
|
|
|
|
/* register fds[0] ~ wayland events */
|
|
bool wayland_started_read = false;
|
|
bool wayland_did_read = false;
|
|
{
|
|
fds[0].fd = -1;
|
|
|
|
wl_display_dispatch_pending(ctx.wl_display);
|
|
wayland_started_read = (wl_display_prepare_read(ctx.wl_display) != -1);
|
|
if (wayland_started_read){
|
|
fds[0].fd = wl_display_get_fd(ctx.wl_display);
|
|
fds[0].events = POLLIN;
|
|
wl_display_flush(ctx.wl_display);
|
|
}
|
|
}
|
|
|
|
/* poll and handle events */
|
|
int ret = poll(fds, ARRAY_LENGTH(fds), 0);
|
|
if (ret > 0){
|
|
|
|
/* handle fds[0] ~ wayland events */
|
|
if (fds[0].revents & POLLIN){
|
|
wayland_did_read = true;
|
|
wl_display_read_events(ctx.wl_display);
|
|
wl_display_dispatch_pending(ctx.wl_display);
|
|
}
|
|
}
|
|
|
|
/* wayland event read cleanup */
|
|
if (wayland_started_read && !wayland_did_read){
|
|
wl_display_cancel_read(ctx.wl_display);
|
|
}
|
|
}
|
|
|
|
if (ctx.close_signal){
|
|
exit_loop = 1;
|
|
}
|
|
|
|
if (ctx.has_cached_config){
|
|
ctx.has_cached_config = 0;
|
|
libdecor_frame_commit(ctx.w, ctx.h, &ctx.cached_config);
|
|
}
|
|
|
|
/* (nodocs-wl_egl) */
|
|
wl_egl_window_resize(ctx.wl_egl_window, ctx.w, ctx.h, 0, 0);
|
|
|
|
/*~ NOTE: render */
|
|
{
|
|
glDrawBuffer(GL_BACK);
|
|
glViewport(0, 0, 640, 480);
|
|
glClearColor(0.40f, 0.90f, 0.15f, 1.f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
/* (egl) eglSwapBuffers
|
|
** " back-buffered window surface, then the color buffer is copied
|
|
** (posted) to the native window associated with that surface "
|
|
*/
|
|
EGLBoolean swap_success = eglSwapBuffers(ctx.egl_display, ctx.egl_surface);
|
|
if (!swap_success){
|
|
printf("eglSwapBuffers failed\n");
|
|
}
|
|
}
|
|
|
|
/* (1) #Client-classwl__display_1a9150a7e3213a58b469a6966e60a9f108
|
|
** " Close the connection to display "
|
|
*/
|
|
if (ctx.wl_display != 0){
|
|
wl_display_disconnect(ctx.wl_display);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/*libdecor.so, libdecor-gtk.so*/
|
|
/*
|
|
* Copyright © 2012 Collabora, Ltd.
|
|
* Copyright © 2012 Intel Corporation
|
|
* Copyright © 2017-2018 Red Hat Inc.
|
|
* Copyright © 2018 Jonas Ådahl
|
|
* Copyright © 2021 Christian Rauch
|
|
* Copyright © 2024 Colin Kinloch
|
|
*
|
|
* 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 "libdecor.c"
|
|
|
|
bool
|
|
libdecor_configuration_get_content_size(struct libdecor_configuration *configuration, int *width, int *height){
|
|
/* get configured toplevel dimensions */
|
|
if (!configuration->has_size)
|
|
return false;
|
|
|
|
if (configuration->window_width == 0 || configuration->window_height == 0)
|
|
return false;
|
|
|
|
*width = configuration->window_width;
|
|
*height = configuration->window_height;
|
|
|
|
if (ctx.visible != 0 &&
|
|
ctx.decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE){
|
|
ctx.frame_window_state = configuration->window_state;
|
|
if (configuration->has_window_state){
|
|
Sides2D border_size = border_size_from_window_state(configuration->window_state);
|
|
*width -= border_size.x[0] + border_size.x[1];
|
|
*height -= border_size.y[0] + border_size.y[1];
|
|
}
|
|
else{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* constrain content dimensions manually */
|
|
if (!(configuration->window_state & LIBDECOR_WINDOW_STATE_NON_FLOATING)) {
|
|
// TODO(allen):
|
|
if (ctx.size_bounds.x[0] > 0){
|
|
*width = MAX(ctx.size_bounds.x[0], *width);
|
|
}
|
|
if (ctx.size_bounds.x[1] > 0){
|
|
*width = MIN(*width, ctx.size_bounds.x[1]);
|
|
}
|
|
if (ctx.size_bounds.y[0] > 0){
|
|
*height = MAX(ctx.size_bounds.y[0], *height);
|
|
}
|
|
if (ctx.size_bounds.y[1] > 0){
|
|
*height = MIN(*height, ctx.size_bounds.y[1]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static enum decoration_type
|
|
decoration_type_from_window_state(enum libdecor_window_state window_state){
|
|
enum decoration_type result = DECORATION_TYPE_ALL;
|
|
if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN){
|
|
result = DECORATION_TYPE_NONE;
|
|
}
|
|
else if (window_state & (LIBDECOR_WINDOW_STATE_MAXIMIZED |
|
|
LIBDECOR_WINDOW_STATE_TILED_LEFT |
|
|
LIBDECOR_WINDOW_STATE_TILED_RIGHT |
|
|
LIBDECOR_WINDOW_STATE_TILED_TOP |
|
|
LIBDECOR_WINDOW_STATE_TILED_BOTTOM)){
|
|
result = DECORATION_TYPE_TITLE_ONLY;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
static void
|
|
free_border_component(struct border_component *border_component){
|
|
if (border_component->wl_surface) {
|
|
wl_subsurface_destroy(border_component->wl_subsurface);
|
|
border_component->wl_subsurface = NULL;
|
|
wl_surface_destroy(border_component->wl_surface);
|
|
border_component->wl_surface = NULL;
|
|
}
|
|
|
|
if (border_component->wl_buffer != 0){
|
|
wl_buffer_destroy(border_component->wl_buffer);
|
|
}
|
|
if (border_component->data != 0){
|
|
munmap(border_component->data, border_component->data_size);
|
|
}
|
|
border_component->wl_buffer = 0;
|
|
border_component->data = 0;
|
|
border_component->data_size = 0;
|
|
border_component->width = 0;
|
|
border_component->height = 0;
|
|
}
|
|
|
|
void
|
|
update_client_side_rendering_state(void){
|
|
if (ctx.visible && ctx.decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE){
|
|
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 = ctx.gtk_window_state;
|
|
new_window_state = ctx.frame_window_state;
|
|
|
|
old_content_width = ctx.gtk_content_width;
|
|
old_content_height = ctx.gtk_content_height;
|
|
new_content_width = ctx.frame_content_width;
|
|
new_content_height = ctx.frame_content_height;
|
|
|
|
old_decoration_type = ctx.decoration_type;
|
|
new_decoration_type = decoration_type_from_window_state(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){
|
|
|
|
ctx.gtk_content_width = new_content_width;
|
|
ctx.gtk_content_height = new_content_height;
|
|
ctx.gtk_window_state = new_window_state;
|
|
ctx.decoration_type = new_decoration_type;
|
|
|
|
draw_decoration();
|
|
|
|
/* set fixed window size */
|
|
if (!(ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE)){
|
|
ctx.size_bounds.x[0] = ctx.gtk_content_width;
|
|
ctx.size_bounds.y[0] = ctx.gtk_content_height;
|
|
ctx.size_bounds.x[1] = ctx.gtk_content_width;
|
|
ctx.size_bounds.y[1] = ctx.gtk_content_height;
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
g_clear_pointer(&ctx.header, gtk_widget_destroy);
|
|
g_clear_pointer(&ctx.window, gtk_widget_destroy);
|
|
|
|
free_border_component(&ctx.component_slot[COMPONENT_SLOT_HEADER]);
|
|
free_border_component(&ctx.component_slot[COMPONENT_SLOT_SHADOW]);
|
|
ctx.shadow_showing = false;
|
|
|
|
g_clear_pointer(&ctx.title, free);
|
|
|
|
ctx.decoration_type = DECORATION_TYPE_NONE;
|
|
}
|
|
|
|
{
|
|
Extent2D extent = {0};
|
|
extent.w = ctx.frame_content_width;
|
|
extent.h = ctx.frame_content_height;
|
|
|
|
if (ctx.visible && ctx.decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE){
|
|
Sides2D border_size = border_size_from_window_state(ctx.frame_window_state);
|
|
extent.x = -border_size.x[0];
|
|
extent.y = -border_size.y[0];
|
|
extent.w += border_size.x[0] + border_size.x[1];
|
|
extent.h += border_size.y[0] + border_size.y[1];
|
|
}
|
|
|
|
xdg_surface_set_window_geometry(ctx.xdg_surface, extent.x, extent.y, extent.w, extent.h);
|
|
}
|
|
}
|
|
|
|
void
|
|
libdecor_frame_set_visibility(bool visible){
|
|
ctx.visible = visible;
|
|
if (ctx.decoration_manager != 0 &&
|
|
ctx.toplevel_decoration != 0 &&
|
|
ctx.has_decoration_mode &&
|
|
ctx.decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE){
|
|
zxdg_toplevel_decoration_v1_set_mode(ctx.toplevel_decoration,
|
|
ctx.visible ?
|
|
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE :
|
|
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
|
|
}
|
|
|
|
if (ctx.frame_content_width > 0 &&
|
|
ctx.frame_content_height > 0){
|
|
update_client_side_rendering_state();
|
|
|
|
wl_surface_commit(ctx.wl_surface);
|
|
}
|
|
}
|
|
|
|
static enum xdg_toplevel_resize_edge
|
|
xdg_edge_from_edge(enum libdecor_resize_edge edge){
|
|
enum xdg_toplevel_resize_edge result = XDG_TOPLEVEL_RESIZE_EDGE_NONE;
|
|
switch (edge) {
|
|
default: case LIBDECOR_RESIZE_EDGE_NONE: break;
|
|
case LIBDECOR_RESIZE_EDGE_TOP: result = XDG_TOPLEVEL_RESIZE_EDGE_TOP; break;
|
|
case LIBDECOR_RESIZE_EDGE_BOTTOM: result = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; break;
|
|
case LIBDECOR_RESIZE_EDGE_LEFT: result = XDG_TOPLEVEL_RESIZE_EDGE_LEFT; break;
|
|
case LIBDECOR_RESIZE_EDGE_TOP_LEFT: result = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; break;
|
|
case LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT: result = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; break;
|
|
case LIBDECOR_RESIZE_EDGE_RIGHT: result = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; break;
|
|
case LIBDECOR_RESIZE_EDGE_TOP_RIGHT: result = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; break;
|
|
case LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT: result = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; break;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
void
|
|
libdecor_frame_set_fullscreen(struct wl_output *output){
|
|
xdg_toplevel_set_fullscreen(ctx.xdg_toplevel, output);
|
|
}
|
|
|
|
void
|
|
libdecor_frame_unset_fullscreen(void){
|
|
xdg_toplevel_unset_fullscreen(ctx.xdg_toplevel);
|
|
}
|
|
|
|
void
|
|
libdecor_frame_commit(int w, int h, struct libdecor_configuration *configuration){
|
|
if (configuration != 0 && configuration->has_window_state){
|
|
ctx.frame_window_state = configuration->window_state;
|
|
}
|
|
|
|
Sides2D border_size = border_size_from_window_state(ctx.frame_window_state);
|
|
int border_added_w = border_size.x[0] + border_size.x[1];
|
|
int border_added_h = border_size.y[0] + border_size.y[1];
|
|
|
|
struct libdecor_state state = {0};
|
|
state.content_width = w;
|
|
state.content_height = h;
|
|
state.window_state = ctx.frame_window_state;
|
|
|
|
ctx.frame_content_width = state.content_width;
|
|
ctx.frame_content_height = state.content_height;
|
|
{
|
|
if (!(ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE)){
|
|
ctx.size_bounds.x[0] = ctx.frame_content_width;
|
|
ctx.size_bounds.y[0] = ctx.frame_content_height;
|
|
ctx.size_bounds.x[1] = ctx.frame_content_width;
|
|
ctx.size_bounds.y[1] = ctx.frame_content_height;
|
|
}
|
|
|
|
for (int i = 0; i < 2; i += 1){
|
|
int w = 0;
|
|
int h = 0;
|
|
if (ctx.size_bounds.x[i] > 0 && ctx.size_bounds.y[i] > 0){
|
|
w = ctx.size_bounds.x[i];
|
|
h = ctx.size_bounds.y[i];
|
|
if (ctx.visible && ctx.decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE){
|
|
w += border_added_w;
|
|
h += border_added_h;
|
|
}
|
|
}
|
|
if (i == 0){
|
|
xdg_toplevel_set_min_size(ctx.xdg_toplevel, w, h);
|
|
}
|
|
else{
|
|
xdg_toplevel_set_max_size(ctx.xdg_toplevel, w, h);
|
|
}
|
|
}
|
|
}
|
|
|
|
update_client_side_rendering_state();
|
|
|
|
if (configuration != 0){
|
|
xdg_surface_ack_configure(ctx.xdg_surface, configuration->serial);
|
|
}
|
|
}
|
|
|
|
void
|
|
cleanup(void){
|
|
{
|
|
struct seat *seat, *seat_tmp;
|
|
wl_list_for_each_safe(seat, seat_tmp, &ctx.seat_list, link) {
|
|
|
|
if (seat->wl_pointer){
|
|
wl_pointer_destroy(seat->wl_pointer);
|
|
}
|
|
if (seat->wl_touch){
|
|
wl_touch_destroy(seat->wl_touch);
|
|
}
|
|
wl_surface_destroy(seat->cursor_surface);
|
|
wl_seat_destroy(seat->wl_seat);
|
|
if (ctx.cursor_theme != 0){
|
|
wl_cursor_theme_destroy(ctx.cursor_theme);
|
|
}
|
|
if (seat->name != 0){
|
|
free(seat->name);
|
|
}
|
|
free(seat);
|
|
}
|
|
}
|
|
|
|
if (ctx.wl_shm){
|
|
wl_shm_destroy(ctx.wl_shm);
|
|
}
|
|
|
|
if (ctx.wl_subcompositor != 0){
|
|
wl_subcompositor_destroy(ctx.wl_subcompositor);
|
|
}
|
|
if (ctx.xdg_wm_base != 0){
|
|
xdg_wm_base_destroy(ctx.xdg_wm_base);
|
|
}
|
|
if (ctx.decoration_manager != 0){
|
|
zxdg_decoration_manager_v1_destroy(ctx.decoration_manager);
|
|
}
|
|
}
|
|
|
|
//#include "libdecor-cairo-blur.c"
|
|
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);
|
|
}
|
|
|
|
//#include "os-compatibility.c"
|
|
#ifndef HAVE_MKOSTEMP
|
|
static int
|
|
set_cloexec_or_close(int fd)
|
|
{
|
|
long flags;
|
|
|
|
if (fd == -1)
|
|
return -1;
|
|
|
|
flags = fcntl(fd, F_GETFD);
|
|
if (flags == -1)
|
|
goto err;
|
|
|
|
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
|
|
goto err;
|
|
|
|
return fd;
|
|
|
|
err:
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
create_tmpfile_cloexec(char *tmpname)
|
|
{
|
|
int fd;
|
|
|
|
#ifdef HAVE_MKOSTEMP
|
|
fd = mkostemp(tmpname, O_CLOEXEC);
|
|
if (fd >= 0)
|
|
unlink(tmpname);
|
|
#else
|
|
fd = mkstemp(tmpname);
|
|
if (fd >= 0) {
|
|
fd = set_cloexec_or_close(fd);
|
|
unlink(tmpname);
|
|
}
|
|
#endif
|
|
|
|
return fd;
|
|
}
|
|
|
|
static int
|
|
os_resize_anonymous_file(int fd, off_t size)
|
|
{
|
|
#ifdef HAVE_POSIX_FALLOCATE
|
|
sigset_t mask;
|
|
sigset_t old_mask;
|
|
|
|
/* posix_fallocate() might be interrupted, so we need to check
|
|
* for EINTR and retry in that case.
|
|
* However, in the presence of an alarm, the interrupt may trigger
|
|
* repeatedly and prevent a large posix_fallocate() to ever complete
|
|
* successfully, so we need to first block SIGALRM to prevent
|
|
* this.
|
|
*/
|
|
sigemptyset(&mask);
|
|
sigaddset(&mask, SIGALRM);
|
|
sigprocmask(SIG_BLOCK, &mask, &old_mask);
|
|
/*
|
|
* Filesystems that do not support fallocate will return EINVAL or
|
|
* EOPNOTSUPP. In this case we need to fall back to ftruncate
|
|
*/
|
|
do {
|
|
errno = posix_fallocate(fd, 0, size);
|
|
} while (errno == EINTR);
|
|
sigprocmask(SIG_SETMASK, &old_mask, NULL);
|
|
if (errno == 0)
|
|
return 0;
|
|
else if (errno != EINVAL && errno != EOPNOTSUPP)
|
|
return -1;
|
|
#endif
|
|
if (ftruncate(fd, size) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Create a new, unique, anonymous file of the given size, and
|
|
* return the file descriptor for it. The file descriptor is set
|
|
* CLOEXEC. The file is immediately suitable for mmap()'ing
|
|
* the given size at offset zero.
|
|
*
|
|
* The file should not have a permanent backing store like a disk,
|
|
* but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
|
|
*
|
|
* The file name is deleted from the file system.
|
|
*
|
|
* The file is suitable for buffer sharing between processes by
|
|
* transmitting the file descriptor over Unix sockets using the
|
|
* SCM_RIGHTS methods.
|
|
*
|
|
* If the C library implements posix_fallocate(), it is used to
|
|
* guarantee that disk space is available for the file at the
|
|
* given size. If disk space is insufficient, errno is set to ENOSPC.
|
|
* If posix_fallocate() is not supported, program may receive
|
|
* SIGBUS on accessing mmap()'ed file contents instead.
|
|
*
|
|
* If the C library implements memfd_create(), it is used to create the
|
|
* file purely in memory, without any backing file name on the file
|
|
* system, and then sealing off the possibility of shrinking it. This
|
|
* can then be checked before accessing mmap()'ed file contents, to
|
|
* make sure SIGBUS can't happen. It also avoids requiring
|
|
* XDG_RUNTIME_DIR.
|
|
*/
|
|
int
|
|
libdecor_os_create_anonymous_file(off_t size){
|
|
static const char template[] = "/libdecor-shared-XXXXXX";
|
|
const char *path;
|
|
char *name;
|
|
int fd;
|
|
|
|
#ifdef HAVE_MEMFD_CREATE
|
|
fd = memfd_create("libdecor", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
|
if (fd >= 0) {
|
|
/* We can add this seal before calling posix_fallocate(), as
|
|
* the file is currently zero-sized anyway.
|
|
*
|
|
* There is also no need to check for the return value, we
|
|
* couldn't do anything with it anyway.
|
|
*/
|
|
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
|
|
} else
|
|
#endif
|
|
{
|
|
path = getenv("XDG_RUNTIME_DIR");
|
|
if (!path) {
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
|
|
name = malloc(strlen(path) + sizeof(template));
|
|
if (!name)
|
|
return -1;
|
|
|
|
strcpy(name, path);
|
|
strcat(name, template);
|
|
|
|
fd = create_tmpfile_cloexec(name);
|
|
|
|
free(name);
|
|
|
|
if (fd < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (os_resize_anonymous_file(fd, size) < 0) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
//#include "plugins/gtk/libdecor-gtk.c"
|
|
|
|
static void
|
|
gtk_fill_widget_from_name(GtkWidget *widget, void *data){
|
|
bool match = false;
|
|
if (GTK_IS_WIDGET(widget)){
|
|
struct header_element_data *elem = data;
|
|
GtkStyleContext *style_context = gtk_widget_get_style_context(widget);
|
|
char *style_ctx = gtk_style_context_to_string(style_context, GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE);
|
|
if (strstr(style_ctx, elem->name) != 0){
|
|
elem->widget = widget;
|
|
match = true;
|
|
}
|
|
free(style_ctx);
|
|
}
|
|
|
|
/* recursively traverse container */
|
|
if (!match && GTK_IS_CONTAINER(widget)){
|
|
gtk_container_forall(GTK_CONTAINER(widget), >k_fill_widget_from_name, data);
|
|
}
|
|
}
|
|
|
|
static struct header_element_data
|
|
find_widget_by_type(GtkWidget *widget, enum header_element type){
|
|
char* name = 0;
|
|
switch (type){
|
|
case HEADER_FULL: name = "headerbar.titlebar:"; break;
|
|
case HEADER_TITLE: name = "label.title:"; break;
|
|
case HEADER_MIN: name = ".minimize"; break;
|
|
case HEADER_MAX: name = ".maximize"; break;
|
|
case HEADER_CLOSE: name = ".close"; break;
|
|
default:break;
|
|
}
|
|
|
|
struct header_element_data data = {0};
|
|
data.name = name;
|
|
data.type = type;
|
|
gtk_fill_widget_from_name(widget, &data);
|
|
return(data);
|
|
}
|
|
|
|
static struct header_element_data
|
|
get_header_focus(const GtkHeaderBar *header_bar, int x, int y){
|
|
static const enum header_element elems[] = { HEADER_TITLE, HEADER_MIN, HEADER_MAX, HEADER_CLOSE };
|
|
struct header_element_data result = {0};
|
|
|
|
for (size_t i = 0; i < ARRAY_LENGTH(elems); i += 1){
|
|
struct header_element_data elem = find_widget_by_type(GTK_WIDGET(header_bar), elems[i]);
|
|
if (elem.widget){
|
|
GtkAllocation allocation;
|
|
gtk_widget_get_allocation(GTK_WIDGET(elem.widget), &allocation);
|
|
if (allocation.x <= x && x < allocation.x + allocation.width &&
|
|
allocation.y <= y && y < allocation.y + allocation.height){
|
|
result = elem;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
static bool
|
|
own_proxy(void *proxy){
|
|
bool result = false;
|
|
if (proxy != 0){
|
|
result = (wl_proxy_get_tag((struct wl_proxy*)proxy) == &libdecor_gtk_proxy_tag);
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
static void
|
|
toggle_maximized(void){
|
|
if (ctx.frame_window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED){
|
|
xdg_toplevel_unset_maximized(ctx.xdg_toplevel);
|
|
}
|
|
else{
|
|
xdg_toplevel_set_maximized(ctx.xdg_toplevel);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hide_border_component(struct border_component *border_component){
|
|
if (border_component->wl_surface){
|
|
wl_surface_attach(border_component->wl_surface, 0, 0, 0);
|
|
wl_surface_commit(border_component->wl_surface);
|
|
}
|
|
}
|
|
|
|
static enum component_slot
|
|
component_slot_from_wl_surface(const struct wl_surface *surface){
|
|
enum component_slot result = 0;
|
|
for (int i = 1; i < COMPONENT_SLOT_COUNT; i += 1){
|
|
if (ctx.component_slot[i].wl_surface == surface){
|
|
result = i;
|
|
break;
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
static void
|
|
ensure_component(struct border_component *cmpnt){
|
|
if (cmpnt->wl_surface == 0){
|
|
cmpnt->wl_surface = wl_compositor_create_surface(ctx.wl_compositor);
|
|
wl_proxy_set_tag((struct wl_proxy *)cmpnt->wl_surface, &libdecor_gtk_proxy_tag);
|
|
cmpnt->wl_subsurface = wl_subcompositor_get_subsurface(ctx.wl_subcompositor, cmpnt->wl_surface, ctx.wl_surface);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ensure_title_bar_surfaces(void){
|
|
GtkStyleContext *context_hdr;
|
|
|
|
ctx.component_slot[COMPONENT_SLOT_HEADER].opaque = false;
|
|
ensure_component(&ctx.component_slot[COMPONENT_SLOT_HEADER]);
|
|
|
|
if (ctx.component_slot[COMPONENT_SLOT_SHADOW].wl_surface){
|
|
wl_subsurface_place_above(ctx.component_slot[COMPONENT_SLOT_HEADER].wl_subsurface,
|
|
ctx.component_slot[COMPONENT_SLOT_SHADOW].wl_surface);
|
|
}
|
|
|
|
if (GTK_IS_WIDGET(ctx.header)){
|
|
gtk_widget_destroy(ctx.header);
|
|
ctx.header = 0;
|
|
}
|
|
if (GTK_IS_WIDGET(ctx.window)){
|
|
gtk_widget_destroy(ctx.window);
|
|
ctx.window = 0;
|
|
}
|
|
|
|
ctx.window = gtk_offscreen_window_new();
|
|
ctx.header = gtk_header_bar_new();
|
|
|
|
g_object_get(gtk_widget_get_settings(ctx.window),
|
|
"gtk-double-click-time", &ctx.double_click_time_ms,
|
|
"gtk-dnd-drag-threshold", &ctx.drag_threshold,
|
|
NULL);
|
|
g_object_set(ctx.header,
|
|
"title", ctx.title,
|
|
"has-subtitle", FALSE,
|
|
"show-close-button", TRUE,
|
|
NULL);
|
|
|
|
context_hdr = gtk_widget_get_style_context(ctx.header);
|
|
gtk_style_context_add_class(context_hdr, GTK_STYLE_CLASS_TITLEBAR);
|
|
gtk_style_context_add_class(context_hdr, "default-decoration");
|
|
|
|
gtk_window_set_titlebar(GTK_WINDOW(ctx.window), ctx.header);
|
|
gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(ctx.header), TRUE);
|
|
|
|
gtk_window_set_resizable(GTK_WINDOW(ctx.window), (ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE) != 0);
|
|
}
|
|
|
|
static Extent2D
|
|
extent2d_from_component_slot(enum component_slot slot){
|
|
Extent2D result = {0};
|
|
int width = ctx.frame_content_width;
|
|
int height = ctx.frame_content_height;
|
|
int title_height = 0;
|
|
|
|
if (GTK_IS_WIDGET(ctx.header)){
|
|
title_height = gtk_widget_get_allocated_height(ctx.header);
|
|
}
|
|
|
|
switch (slot){
|
|
default: case COMPONENT_SLOT_NONE: break;
|
|
|
|
case COMPONENT_SLOT_SHADOW: {
|
|
result.x = -(int)SHADOW_MARGIN;
|
|
result.y = -(int)(SHADOW_MARGIN + title_height);
|
|
result.w = width + 2*SHADOW_MARGIN;
|
|
result.h = title_height + height + 2*SHADOW_MARGIN;
|
|
}break;
|
|
|
|
case COMPONENT_SLOT_HEADER: {
|
|
result.x = 0;
|
|
result.y = -title_height;
|
|
result.w = gtk_widget_get_allocated_width(ctx.header);
|
|
result.h = title_height;
|
|
}break;
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
static void
|
|
array_append(enum header_element **array, size_t *n, enum header_element item){
|
|
(*n)++;
|
|
*array = realloc(*array, (*n) * sizeof (enum header_element));
|
|
(*array)[(*n)-1] = item;
|
|
}
|
|
|
|
static void
|
|
draw_header_button(cairo_t *cr, cairo_surface_t *surface,
|
|
enum header_element button_type){
|
|
struct header_element_data elem;
|
|
GtkWidget *button;
|
|
GtkStyleContext* button_style;
|
|
GtkStateFlags style_state;
|
|
|
|
GtkAllocation allocation;
|
|
|
|
gchar *icon_name;
|
|
int scale;
|
|
GtkWidget *icon_widget;
|
|
GtkAllocation allocation_icon;
|
|
GtkIconInfo* icon_info;
|
|
|
|
double sx, sy;
|
|
|
|
gint icon_width, icon_height;
|
|
|
|
GdkPixbuf* icon_pixbuf;
|
|
cairo_surface_t* icon_surface;
|
|
|
|
gint width = 0, height = 0;
|
|
gint left = 0, top = 0, right = 0, bottom = 0;
|
|
GtkBorder border;
|
|
GtkBorder padding;
|
|
|
|
elem = find_widget_by_type(ctx.header, button_type);
|
|
button = elem.widget;
|
|
if (button){
|
|
button_style = gtk_widget_get_style_context(button);
|
|
style_state = elem.state;
|
|
|
|
/* change style based on window state and focus */
|
|
if (!(ctx.frame_window_state & LIBDECOR_WINDOW_STATE_ACTIVE)) {
|
|
style_state |= GTK_STATE_FLAG_BACKDROP;
|
|
}
|
|
if (ctx.hdr_focus.widget == button) {
|
|
style_state |= GTK_STATE_FLAG_PRELIGHT;
|
|
if (ctx.hdr_focus.state & GTK_STATE_FLAG_ACTIVE) {
|
|
style_state |= GTK_STATE_FLAG_ACTIVE;
|
|
}
|
|
}
|
|
|
|
/* background */
|
|
gtk_widget_get_clip(button, &allocation);
|
|
|
|
gtk_style_context_save(button_style);
|
|
gtk_style_context_set_state(button_style, style_state);
|
|
gtk_render_background(button_style, cr,
|
|
allocation.x, allocation.y,
|
|
allocation.width, allocation.height);
|
|
gtk_render_frame(button_style, cr,
|
|
allocation.x, allocation.y,
|
|
allocation.width, allocation.height);
|
|
gtk_style_context_restore(button_style);
|
|
|
|
/* symbol */
|
|
switch (button_type) {
|
|
case HEADER_MIN: {
|
|
icon_name = "window-minimize-symbolic";
|
|
}break;
|
|
|
|
case HEADER_MAX:{
|
|
icon_name = (ctx.frame_window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) ?
|
|
"window-restore-symbolic" :
|
|
"window-maximize-symbolic";
|
|
}break;
|
|
|
|
case HEADER_CLOSE: {
|
|
icon_name = "window-close-symbolic";
|
|
}break;
|
|
|
|
default: {
|
|
icon_name = NULL;
|
|
}break;
|
|
}
|
|
|
|
/* get scale */
|
|
cairo_surface_get_device_scale(surface, &sx, &sy);
|
|
scale = (sx+sy) / 2.0;
|
|
|
|
/* get original icon dimensions */
|
|
icon_widget = gtk_bin_get_child(GTK_BIN(button));
|
|
gtk_widget_get_allocation(icon_widget, &allocation_icon);
|
|
|
|
/* icon info */
|
|
if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &icon_width, &icon_height)) {
|
|
icon_width = 16;
|
|
icon_height = 16;
|
|
}
|
|
icon_info = gtk_icon_theme_lookup_icon_for_scale(gtk_icon_theme_get_default(), icon_name,
|
|
icon_width, scale, (GtkIconLookupFlags)0);
|
|
|
|
/* icon pixel buffer*/
|
|
gtk_style_context_save(button_style);
|
|
gtk_style_context_set_state(button_style, style_state);
|
|
icon_pixbuf = gtk_icon_info_load_symbolic_for_context(
|
|
icon_info, button_style, NULL, NULL);
|
|
icon_surface = gdk_cairo_surface_create_from_pixbuf(icon_pixbuf, scale, NULL);
|
|
gtk_style_context_restore(button_style);
|
|
|
|
/* dimensions and position */
|
|
gtk_style_context_get(button_style, gtk_style_context_get_state(button_style),
|
|
"min-width", &width, "min-height", &height, NULL);
|
|
|
|
if (width < icon_width){
|
|
width = icon_width;
|
|
}
|
|
if (height < icon_height){
|
|
height = icon_height;
|
|
}
|
|
|
|
gtk_style_context_get_border(button_style, gtk_style_context_get_state(button_style), &border);
|
|
left += border.left;
|
|
right += border.right;
|
|
top += border.top;
|
|
bottom += border.bottom;
|
|
|
|
gtk_style_context_get_padding(button_style, gtk_style_context_get_state(button_style), &padding);
|
|
left += padding.left;
|
|
right += padding.right;
|
|
top += padding.top;
|
|
bottom += padding.bottom;
|
|
|
|
width += left + right;
|
|
height += top + bottom;
|
|
|
|
gtk_render_icon_surface(gtk_widget_get_style_context(icon_widget),
|
|
cr, icon_surface,
|
|
allocation.x + ((width - icon_width) / 2),
|
|
allocation.y + ((height - icon_height) / 2));
|
|
cairo_paint(cr);
|
|
cairo_surface_destroy(icon_surface);
|
|
g_object_unref(icon_pixbuf);
|
|
}
|
|
}
|
|
|
|
static void
|
|
draw_border_component(enum component_slot slot){
|
|
if (slot < COMPONENT_SLOT_COUNT &&
|
|
ctx.component_slot[slot].wl_surface != 0){
|
|
Extent2D extent = extent2d_from_component_slot(slot);
|
|
if (slot == COMPONENT_SLOT_SHADOW && ctx.shadow_showing){
|
|
struct wl_region *input_region;
|
|
Extent2D extent = extent2d_from_component_slot(COMPONENT_SLOT_SHADOW);
|
|
input_region = wl_compositor_create_region(ctx.wl_compositor);
|
|
wl_region_add(input_region, 0, 0, extent.w, extent.h);
|
|
wl_region_subtract(input_region, -extent.x, -extent.y, ctx.frame_content_width, ctx.frame_content_height);
|
|
wl_surface_set_input_region(ctx.component_slot[slot].wl_surface, input_region);
|
|
wl_region_destroy(input_region);
|
|
}
|
|
|
|
struct border_component *component = &ctx.component_slot[slot];
|
|
|
|
{
|
|
if (component->wl_buffer != 0){
|
|
wl_buffer_destroy(component->wl_buffer);
|
|
}
|
|
if (component->data != 0){
|
|
munmap(component->data, component->data_size);
|
|
}
|
|
component->wl_buffer = 0;
|
|
component->data = 0;
|
|
component->data_size = 0;
|
|
component->width = 0;
|
|
component->height = 0;
|
|
}
|
|
|
|
{
|
|
int width = extent.w;
|
|
int height = extent.h;
|
|
int stride = 4*width;
|
|
int size = stride*height;
|
|
|
|
int fd = libdecor_os_create_anonymous_file(size);
|
|
if (fd >= 0){
|
|
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (data != MAP_FAILED){
|
|
enum wl_shm_format fmt = (ctx.component_slot[slot].opaque ?
|
|
WL_SHM_FORMAT_XRGB8888 :
|
|
WL_SHM_FORMAT_ARGB8888);
|
|
|
|
struct wl_shm_pool *pool = wl_shm_create_pool(ctx.wl_shm, fd, size);
|
|
struct wl_buffer *wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, fmt);
|
|
wl_shm_pool_destroy(pool);
|
|
|
|
component->wl_buffer = wl_buffer;
|
|
component->data = data;
|
|
component->data_size = size;
|
|
component->width = width;
|
|
component->height = height;
|
|
}
|
|
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
memset(component->data, 0, component->data_size);
|
|
{
|
|
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, component->width);
|
|
cairo_surface_t *surface = cairo_image_surface_create_for_data(component->data, CAIRO_FORMAT_ARGB32, component->width, component->height, stride);
|
|
cairo_t *cr = cairo_create(surface);
|
|
cairo_surface_set_device_scale(surface, 1, 1);
|
|
|
|
switch (slot){
|
|
default: case COMPONENT_SLOT_NONE: break;
|
|
|
|
case COMPONENT_SLOT_SHADOW: {
|
|
render_shadow(cr, ctx.shadow_blur,
|
|
-(int)SHADOW_MARGIN/2, -(int)SHADOW_MARGIN/2,
|
|
component->width + SHADOW_MARGIN, component->height + SHADOW_MARGIN,
|
|
64, 64);
|
|
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
|
|
cairo_rectangle(cr, -extent.x, -extent.y, ctx.frame_content_width, ctx.frame_content_height);
|
|
cairo_fill(cr);
|
|
}break;
|
|
|
|
case COMPONENT_SLOT_HEADER: {
|
|
/* background */
|
|
{
|
|
GtkAllocation allocation;
|
|
GtkStyleContext* style;
|
|
gtk_widget_get_allocation(GTK_WIDGET(ctx.header), &allocation);
|
|
style = gtk_widget_get_style_context(ctx.header);
|
|
gtk_render_background(style, cr, allocation.x, allocation.y, allocation.width, allocation.height);
|
|
}
|
|
|
|
/* title */
|
|
{
|
|
GtkWidget *label;
|
|
GtkAllocation allocation;
|
|
cairo_surface_t *label_surface = NULL;
|
|
cairo_t *cr;
|
|
|
|
label = find_widget_by_type(ctx.header, HEADER_TITLE).widget;
|
|
gtk_widget_get_allocation(label, &allocation);
|
|
label_surface = cairo_surface_create_for_rectangle(surface, allocation.x, allocation.y, allocation.width, allocation.height);
|
|
cr = cairo_create(label_surface);
|
|
gtk_widget_size_allocate(label, &allocation);
|
|
gtk_widget_draw(label, cr);
|
|
cairo_destroy(cr);
|
|
cairo_surface_destroy(label_surface);
|
|
}
|
|
|
|
/* buttons */
|
|
{
|
|
enum header_element buttons[3] = {0};
|
|
size_t nbuttons = 0;
|
|
|
|
if ((ctx.frame_capabilities & LIBDECOR_ACTION_MINIMIZE)){
|
|
buttons[nbuttons] = HEADER_MIN;
|
|
nbuttons += 1;
|
|
}
|
|
if ((ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE)){
|
|
buttons[nbuttons] = HEADER_MAX;
|
|
nbuttons += 1;
|
|
}
|
|
if ((ctx.frame_capabilities & LIBDECOR_ACTION_CLOSE)){
|
|
buttons[nbuttons] = HEADER_CLOSE;
|
|
nbuttons += 1;
|
|
}
|
|
|
|
for (size_t i = 0; i < nbuttons; i += 1){
|
|
enum header_element button_type = buttons[i];
|
|
draw_header_button(cr, surface, button_type);
|
|
}
|
|
}
|
|
}break;
|
|
}
|
|
|
|
cairo_destroy(cr);
|
|
cairo_surface_destroy(surface);
|
|
}
|
|
|
|
struct wl_surface *component_surface = ctx.component_slot[slot].wl_surface;
|
|
struct wl_subsurface *component_subsurface = ctx.component_slot[slot].wl_subsurface;
|
|
|
|
wl_surface_attach(component_surface, component->wl_buffer, 0, 0);
|
|
wl_surface_set_buffer_scale(component_surface, 1);
|
|
wl_surface_commit(component_surface);
|
|
wl_surface_damage_buffer(component_surface, 0, 0, extent.w, extent.h);
|
|
wl_subsurface_set_position(component_subsurface, extent.x, extent.y);
|
|
}
|
|
}
|
|
|
|
static void
|
|
draw_title_bar(void){
|
|
GtkAllocation allocation = {0, 0, ctx.gtk_content_width, 0};
|
|
enum libdecor_window_state state;
|
|
GtkStyleContext *style;
|
|
int pref_width;
|
|
int W, H;
|
|
|
|
state = ctx.frame_window_state;
|
|
style = gtk_widget_get_style_context(ctx.window);
|
|
|
|
if (!(state & LIBDECOR_WINDOW_STATE_ACTIVE)){
|
|
gtk_widget_set_state_flags(ctx.window, GTK_STATE_FLAG_BACKDROP, true);
|
|
}
|
|
else{
|
|
gtk_widget_unset_state_flags(ctx.window, GTK_STATE_FLAG_BACKDROP);
|
|
}
|
|
|
|
if (!(ctx.frame_window_state & LIBDECOR_WINDOW_STATE_NON_FLOATING)){
|
|
gtk_style_context_remove_class(style, "maximized");
|
|
}
|
|
else{
|
|
gtk_style_context_add_class(style, "maximized");
|
|
}
|
|
|
|
gtk_widget_show_all(ctx.window);
|
|
|
|
/* set default width, using an empty title to estimate its smallest admissible value */
|
|
gtk_header_bar_set_title(GTK_HEADER_BAR(ctx.header), "");
|
|
gtk_widget_get_preferred_width(ctx.header, NULL, &pref_width);
|
|
gtk_header_bar_set_title(GTK_HEADER_BAR(ctx.header), ctx.title);
|
|
if (ctx.size_bounds.x[0] < pref_width){
|
|
ctx.size_bounds.x[0] = pref_width;
|
|
}
|
|
if (ctx.size_bounds.x[1] != 0 &&
|
|
ctx.size_bounds.x[1] < ctx.size_bounds.x[0]){
|
|
ctx.size_bounds.x[1] = ctx.size_bounds.x[0];
|
|
}
|
|
|
|
W = ctx.frame_content_width;
|
|
H = ctx.frame_content_height;
|
|
if (W < ctx.size_bounds.x[0]){
|
|
W = ctx.size_bounds.x[0];
|
|
libdecor_frame_commit(W, H, NULL);
|
|
}
|
|
else{
|
|
/* set default height */
|
|
gtk_widget_get_preferred_height(ctx.header, 0, &allocation.height);
|
|
gtk_widget_size_allocate(ctx.header, &allocation);
|
|
draw_border_component(COMPONENT_SLOT_HEADER);
|
|
}
|
|
}
|
|
|
|
static void
|
|
draw_decoration(void){
|
|
switch (ctx.decoration_type){
|
|
case DECORATION_TYPE_NONE: {
|
|
hide_border_component(&ctx.component_slot[COMPONENT_SLOT_SHADOW]);
|
|
ctx.shadow_showing = false;
|
|
hide_border_component(&ctx.component_slot[COMPONENT_SLOT_HEADER]);
|
|
}break;
|
|
|
|
case DECORATION_TYPE_ALL: {
|
|
ctx.component_slot[COMPONENT_SLOT_SHADOW].opaque = false;
|
|
ensure_component(&ctx.component_slot[COMPONENT_SLOT_SHADOW]);
|
|
draw_border_component(COMPONENT_SLOT_SHADOW);
|
|
ctx.shadow_showing = true;
|
|
ensure_title_bar_surfaces();
|
|
draw_title_bar();
|
|
}break;
|
|
|
|
case DECORATION_TYPE_TITLE_ONLY: {
|
|
hide_border_component(&ctx.component_slot[COMPONENT_SLOT_SHADOW]);
|
|
ctx.shadow_showing = false;
|
|
ensure_title_bar_surfaces();
|
|
draw_title_bar();
|
|
}break;
|
|
}
|
|
}
|
|
|
|
static Sides2D
|
|
border_size_from_window_state(enum libdecor_window_state window_state){
|
|
Sides2D border_size = {0};
|
|
switch (decoration_type_from_window_state(window_state)) {
|
|
case DECORATION_TYPE_NONE: break;
|
|
|
|
case DECORATION_TYPE_ALL: {
|
|
ctx.component_slot[COMPONENT_SLOT_SHADOW].opaque = false;
|
|
ensure_component(&ctx.component_slot[COMPONENT_SLOT_SHADOW]);
|
|
} G_GNUC_FALLTHROUGH;
|
|
|
|
case DECORATION_TYPE_TITLE_ONLY: {
|
|
if (ctx.header == 0){
|
|
ensure_title_bar_surfaces();
|
|
}
|
|
gtk_widget_show_all(ctx.window);
|
|
gtk_widget_get_preferred_height(ctx.header, 0, &border_size.y[0]);
|
|
}break;
|
|
}
|
|
return(border_size);
|
|
}
|
|
|
|
enum libdecor_resize_edge
|
|
edge_from_pos(int x, int y){
|
|
static const enum libdecor_resize_edge box[9] = {
|
|
LIBDECOR_RESIZE_EDGE_TOP_LEFT , LIBDECOR_RESIZE_EDGE_TOP , LIBDECOR_RESIZE_EDGE_TOP_RIGHT ,
|
|
LIBDECOR_RESIZE_EDGE_LEFT, LIBDECOR_RESIZE_EDGE_NONE , LIBDECOR_RESIZE_EDGE_RIGHT ,
|
|
LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT, LIBDECOR_RESIZE_EDGE_BOTTOM, LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT,
|
|
};
|
|
|
|
enum libdecor_resize_edge result = 0;
|
|
struct border_component *component = &ctx.component_slot[COMPONENT_SLOT_SHADOW];
|
|
if (component->data != 0){
|
|
int w = component->width;
|
|
int h = component->height;
|
|
|
|
int top = (y < 2*SHADOW_MARGIN);
|
|
int bottom = (!top && y > (h - 2*SHADOW_MARGIN));
|
|
int left = (x < 2*SHADOW_MARGIN);
|
|
int right = (!left && x > (w - 2*SHADOW_MARGIN));
|
|
|
|
int i = 4 + 3*(bottom - top) + (right - left);
|
|
result = box[i];
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
static struct wl_cursor *
|
|
wl_cursor_from_pos(int x, int y){
|
|
struct wl_cursor *result = ctx.cursor_left_ptr;
|
|
if (ctx.active == COMPONENT_SLOT_SHADOW &&
|
|
(ctx.frame_capabilities & LIBDECOR_ACTION_RESIZE)){
|
|
enum libdecor_resize_edge edge = edge_from_pos(x, y);
|
|
if (edge != LIBDECOR_RESIZE_EDGE_NONE){
|
|
result = ctx.cursors[edge - 1];
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
|
|
static void
|
|
update_touch_focus(struct seat *seat, wl_fixed_t x, wl_fixed_t y){
|
|
if (GTK_IS_WIDGET(ctx.header) && ctx.touch_active == COMPONENT_SLOT_HEADER){
|
|
struct header_element_data new_focus = get_header_focus(GTK_HEADER_BAR(ctx.header), wl_fixed_to_int(x), wl_fixed_to_int(y));
|
|
if (ctx.hdr_focus.widget != new_focus.widget){
|
|
ctx.hdr_focus = new_focus;
|
|
}
|
|
ctx.hdr_focus.state |= GTK_STATE_FLAG_PRELIGHT;
|
|
draw_title_bar();
|
|
wl_surface_commit(ctx.wl_surface);
|
|
}
|
|
else{
|
|
ctx.hdr_focus.type = HEADER_NONE;
|
|
}
|
|
}
|
|
|
|
//#include "desktop-settings.c"
|
|
|
|
static bool
|
|
get_cursor_settings_from_env(char **theme, int *size){
|
|
char *env_xtheme = getenv("XCURSOR_THEME");
|
|
char *env_xsize = getenv("XCURSOR_SIZE");
|
|
bool got_theme = (env_xtheme != 0 && env_xsize != 0);
|
|
|
|
if (got_theme){
|
|
*theme = strdup(env_xtheme);
|
|
*size = atoi(env_xsize);
|
|
}
|
|
|
|
return(got_theme);
|
|
}
|
|
|
|
#ifdef HAS_DBUS
|
|
#include <dbus/dbus.h>
|
|
|
|
static DBusMessage *
|
|
get_setting_sync(DBusConnection *const connection, const char *key, const char *value){
|
|
DBusMessage *reply = 0;
|
|
DBusMessage *message;
|
|
|
|
message = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
|
|
"/org/freedesktop/portal/desktop",
|
|
"org.freedesktop.portal.Settings",
|
|
"Read");
|
|
|
|
if (message != 0){
|
|
dbus_bool_t success = dbus_message_append_args(message,
|
|
DBUS_TYPE_STRING, &key,
|
|
DBUS_TYPE_STRING, &value,
|
|
DBUS_TYPE_INVALID);
|
|
if (success){
|
|
DBusError error;
|
|
dbus_error_init(&error);
|
|
reply = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error);
|
|
if (dbus_error_is_set(&error) && reply != 0){
|
|
dbus_message_unref(reply);
|
|
reply = 0;
|
|
}
|
|
dbus_error_free(&error);
|
|
}
|
|
dbus_message_unref(message);
|
|
}
|
|
|
|
return(reply);
|
|
}
|
|
|
|
static bool
|
|
parse_type(DBusMessage *const reply, const int type, void *value){
|
|
bool result = false;
|
|
DBusMessageIter iter[3];
|
|
dbus_message_iter_init(reply, &iter[0]);
|
|
if (dbus_message_iter_get_arg_type(&iter[0]) == DBUS_TYPE_VARIANT){
|
|
dbus_message_iter_recurse(&iter[0], &iter[1]);
|
|
if (dbus_message_iter_get_arg_type(&iter[1]) == DBUS_TYPE_VARIANT){
|
|
dbus_message_iter_recurse(&iter[1], &iter[2]);
|
|
if (dbus_message_iter_get_arg_type(&iter[2]) == type){
|
|
dbus_message_iter_get_basic(&iter[2], value);
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
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";
|
|
|
|
bool got_theme = false;
|
|
const char *value_theme = 0;
|
|
|
|
DBusError error;
|
|
DBusConnection *connection;
|
|
|
|
dbus_error_init(&error);
|
|
connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
|
|
|
|
if (!dbus_error_is_set(&error)){
|
|
DBusMessage *reply = get_setting_sync(connection, name, key_theme);
|
|
if (reply != 0){
|
|
if (parse_type(reply, DBUS_TYPE_STRING, &value_theme)) {
|
|
*theme = strdup(value_theme);
|
|
}
|
|
dbus_message_unref(reply);
|
|
}
|
|
}
|
|
|
|
if (value_theme != 0){
|
|
DBusMessage *reply = get_setting_sync(connection, name, key_size);
|
|
if (reply){
|
|
if (parse_type(reply, DBUS_TYPE_INT32, size)){
|
|
got_theme = true;
|
|
}
|
|
dbus_message_unref(reply);
|
|
}
|
|
}
|
|
|
|
if (!got_theme){
|
|
got_theme = get_cursor_settings_from_env(theme, size);
|
|
}
|
|
|
|
return(got_theme);
|
|
}
|
|
|
|
enum libdecor_color_scheme
|
|
libdecor_get_color_scheme(){
|
|
static const char name[] = "org.freedesktop.appearance";
|
|
static const char key_color_scheme[] = "color-scheme";
|
|
uint32_t color = 0;
|
|
|
|
DBusError error;
|
|
DBusConnection *connection;
|
|
DBusMessage *reply;
|
|
|
|
dbus_error_init(&error);
|
|
connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
|
|
|
|
if (!dbus_error_is_set(&error)){
|
|
reply = get_setting_sync(connection, name, key_color_scheme);
|
|
if (reply){
|
|
if (!parse_type(reply, DBUS_TYPE_UINT32, &color)) {
|
|
color = 0;
|
|
}
|
|
dbus_message_unref(reply);
|
|
}
|
|
}
|
|
|
|
return(color);
|
|
}
|
|
|
|
#else
|
|
|
|
bool
|
|
libdecor_get_cursor_settings(char **theme, int *size){
|
|
return(get_cursor_settings_from_env(theme, size));
|
|
}
|
|
|
|
uint32_t
|
|
libdecor_get_color_scheme(){
|
|
return(LIBDECOR_COLOR_SCHEME_DEFAULT);
|
|
}
|
|
|
|
#endif
|
|
|