linux-windowing/digesting_libdecor.c

4767 lines
136 KiB
C
Executable File

#if 0
libdecor_path="/home/mr4th/mr4th/libdecor"
gtk_flags="$(pkg-config --cflags --libs gtk+-3.0)"
dbus_flags="$(pkg-config --cflags --libs dbus-1)"
#echo "gtk_flags: $gtk_flags"
#echo "dbus_flags: $dbus_flags"
root_path="$PWD"
mkdir -p build
clang -o build/demo -g $root_path/digesting_libdecor.c $gtk_flags $dbus_flags -Iwayland -I$libdecor_path/src -I$libdecor_path/src/plugins -I$libdecor_path/build -lwayland-client -lwayland-cursor -lwayland-egl -lEGL -lm
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"
//@global vtables
const struct wl_registry_listener wl_registry_listener;
const struct xdg_surface_listener xdg_surface_listener;
const struct xdg_toplevel_listener xdg_toplevel_listener;
const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener;
const struct xdg_wm_base_listener xdg_wm_base_listener;
const struct wl_callback_listener init_wl_display_callback_listener;
const struct wl_buffer_listener buffer_listener;
const struct wl_callback_listener shm_callback_listener;
const struct wl_shm_listener shm_listener;
const struct wl_seat_listener seat_listener;
const struct wl_pointer_listener pointer_listener;
const struct wl_touch_listener touch_listener;
const struct wl_output_listener output_listener;
// 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
wlevent__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)){
ctx.decoration_manager = wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, MIN(version,2));
}
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){
libdecor_notify_plugin_error(LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
"%s version 3 required but only version %i is available\n", interface, version);
}
seat = calloc(1, sizeof *seat);
seat->cursor_scale = 1;
wl_list_init(&seat->cursor_outputs);
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);
}
else if (strcmp(interface, "wl_output") == 0){
struct output *output;
if (version < 2){
libdecor_notify_plugin_error(LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE,
"%s version 2 required but only version %i is available\n", interface, version);
}
output = calloc(1, sizeof *output);
wl_list_insert(&ctx.output_list, &output->link);
output->id = name;
output->wl_output = wl_registry_bind(ctx.wl_registry, name, &wl_output_interface, MIN(version, 3));
wl_proxy_set_tag((struct wl_proxy *)output->wl_output,
&libdecor_gtk_proxy_tag);
wl_output_add_listener(output->wl_output, &output_listener, output);
}
}
static void
wlevent__wl_registry_global_remove(void *data, struct wl_registry *registry,
uint32_t name){
struct output *output;
wl_list_for_each(output, &ctx.output_list, link){
if (output->id == name){
output_removed(output);
break;
}
}
}
const struct wl_registry_listener wl_registry_listener = {
wlevent__wl_registry_global,
wlevent__wl_registry_global_remove,
};
static void
libdecorevent__error(enum libdecor_error error, const char *msg){}
static void
libdecorevent__frame_configure(struct libdecor_frame *frame,
struct libdecor_configuration *config,
void *udata){
int w = ctx.w;
int h = ctx.h;
if (libdecor_configuration_get_content_size(config, frame, &w, &h)){
ctx.w = w;
ctx.h = h;
}
if (!ctx.configured){
ctx.configured = 1;
struct libdecor_state *state = libdecor_state_new(w, h);
libdecor_frame_commit(frame, state, config);
libdecor_state_free(state);
}
else{
ctx.has_cached_config = 1;
ctx.cached_config = *config;
}
}
static void
libdecorevent__frame_close(struct libdecor_frame *frame, void *udata){
ctx.close_signal = 1;
}
static void
libdecorevent__frame_commit(struct libdecor_frame *frame, void *udata){
wl_surface_commit(ctx.wl_surface);
}
static void
libdecorevent__frame_dismiss_popup(struct libdecor_frame *frame,
const char *seat_name,
void *user_data){
}
static void
libdecorevent__frame_bounds(struct libdecor_frame *frame,
int width,
int height,
void *user_data){
}
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
init_wl_display_callback(void *user_data,
struct wl_callback *callback,
uint32_t time){
struct libdecor *context = user_data;
ctx.init_done = true;
wl_callback_destroy(callback);
ctx.wl_callback = 0;
if (ctx.plugin_ready){
finish_init();
}
if (ctx.has_argb){
libdecor_notify_plugin_ready();
}
}
const struct wl_callback_listener init_wl_display_callback_listener = {
init_wl_display_callback
};
static void
seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities){
struct seat *seat = data;
if ((capabilities & WL_SEAT_CAPABILITY_POINTER) &&
!seat->wl_pointer) {
seat->wl_pointer = wl_seat_get_pointer(wl_seat);
wl_pointer_add_listener(seat->wl_pointer,
&pointer_listener, seat);
} else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) &&
seat->wl_pointer) {
wl_pointer_release(seat->wl_pointer);
seat->wl_pointer = NULL;
}
if ((capabilities & WL_SEAT_CAPABILITY_TOUCH) &&
!seat->wl_touch) {
seat->wl_touch = wl_seat_get_touch(wl_seat);
wl_touch_add_listener(seat->wl_touch,
&touch_listener, seat);
} else if (!(capabilities & WL_SEAT_CAPABILITY_TOUCH) &&
seat->wl_touch) {
wl_touch_release(seat->wl_touch);
seat->wl_touch = NULL;
}
}
static void
seat_name(void *data, struct wl_seat *wl_seat, const char *name)
{
/* avoid warning messages when opening/closing popup window */
struct seat *seat = (struct seat*)data;
seat->name = strdup(name);
}
const struct wl_seat_listener seat_listener = {
seat_capabilities,
seat_name
};
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
*/
{
wl_list_init(&ctx.frames);
wl_list_init(&ctx.visible_frame_list);
wl_list_init(&ctx.seat_list);
wl_list_init(&ctx.output_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_dispatch(ctx.wl_display);
wl_display_roundtrip(ctx.wl_display);
if (ctx.wl_compositor == 0){
printf("failed to get wl_compositor\n");
}
if (ctx.wl_subcompositor == 0){
printf("failed to get wl_subcompositor\n");
}
if (ctx.wl_shm == 0){
printf("failed to get wl_shm\n");
}
}
if (ctx.wl_compositor != 0 &&
ctx.wl_subcompositor != 0 &&
ctx.wl_shm != 0){
ctx.wl_callback = wl_display_sync(ctx.wl_display);
wl_callback_add_listener(ctx.wl_callback,
&init_wl_display_callback_listener,
0);
wl_display_roundtrip(ctx.wl_display);
wl_display_flush(ctx.wl_display);
}
int opengl_load_success = 0;
if (ctx.wl_compositor != 0 &&
ctx.wl_subcompositor != 0 &&
ctx.wl_shm != 0){
/* (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){
ctx.w = 640;
ctx.h = 480;
/* (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){
/* (libdecor.h) " Decorate the given content wl_surface. " */
ctx.libdecor_frame = libdecor_decorate(ctx.wl_surface, 0);
}
if (ctx.libdecor_frame != 0){
/* (libdecor.h) " Set the title of the window. " */
libdecor_frame_set_title(ctx.libdecor_frame, "Example Window");
/* (libdecor.h) " This translates roughly to xdg_toplevel_set_min_size() "
**~ NOTE: I recommend setting this to something greater than 0 on each
** axis, to prevent some artifacts when resize goes 0 or negative.
*/
libdecor_frame_set_min_content_size(ctx.libdecor_frame, 80, 60);
/* (libdecor.h) " Map the window. " */
libdecor_frame_map(ctx.libdecor_frame);
/* (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;){
/* (libdecor.h)
** " 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. "
*/
libdecor_dispatch(0);
if (ctx.close_signal){
exit_loop = 1;
}
if (ctx.has_cached_config){
ctx.has_cached_config = 0;
struct libdecor_state *state = libdecor_state_new(ctx.w, ctx.h);
libdecor_frame_commit(ctx.libdecor_frame, state, &ctx.cached_config);
libdecor_state_free(state);
}
/* (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"
/* 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;
}
int
libdecor_state_get_content_width(struct libdecor_state *state)
{
return state->content_width;
}
int
libdecor_state_get_content_height(struct libdecor_state *state)
{
return state->content_height;
}
enum libdecor_window_state
libdecor_state_get_window_state(struct libdecor_state *state)
{
return state->window_state;
}
struct libdecor_state *
libdecor_state_new(int width,
int height)
{
struct libdecor_state *state;
state = calloc(1, sizeof *state);
state->content_width = width;
state->content_height = height;
return state;
}
void
libdecor_state_free(struct libdecor_state *state)
{
free(state);
}
static struct libdecor_configuration *
libdecor_configuration_new(void)
{
struct libdecor_configuration *configuration;
configuration = calloc(1, 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;
*window_width = state->content_width;
*window_height = state->content_height;
if (frame_has_visible_client_side_decoration(frame)) {
int left, right, top, bottom;
if (!libdecor_plugin_gtk_frame_get_border_size(frame, NULL, &left, &right, &top, &bottom)){
return false;
}
*window_width += left + right;
*window_height += top + bottom;
}
return true;
}
static void
frame_set_window_geometry(struct libdecor_frame *frame,
int32_t content_width, int32_t content_height)
{
int x, y, width, height;
int left, right, top, bottom;
if (frame_has_visible_client_side_decoration(frame) &&
libdecor_plugin_gtk_frame_get_border_size(frame, NULL, &left, &right, &top, &bottom)) {
x = -left;
y = -top;
width = content_width + left + right;
height = content_height + top + bottom;
} else {
x = 0;
y = 0;
width = content_width;
height = content_height;
}
xdg_surface_set_window_geometry(frame->priv->xdg_surface, x, y, width, height);
}
bool
libdecor_configuration_get_content_size(struct libdecor_configuration *configuration,
struct libdecor_frame *frame,
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;
/* remove plugin-specific border size */
if (frame_has_visible_client_side_decoration(frame)) {
int left, right, top, bottom;
/* Update window state for correct border size calculation */
frame->priv->window_state = configuration->window_state;
if (!libdecor_plugin_gtk_frame_get_border_size(frame, configuration, &left, &right, &top, &bottom)){
return false;
}
*width -= (left + right);
*height -= (top + bottom);
}
/* constrain content dimensions manually */
if (state_is_floating(configuration->window_state)) {
constrain_content_size(frame, width, height);
}
return true;
}
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;
libdecorevent__frame_configure(frame, configuration, frame_priv->user_data);
libdecor_configuration_free(configuration);
}
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;
case XDG_TOPLEVEL_STATE_RESIZING:
pending_state |= LIBDECOR_WINDOW_STATE_RESIZING;
break;
#ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION
case XDG_TOPLEVEL_STATE_SUSPENDED:
pending_state |= LIBDECOR_WINDOW_STATE_SUSPENDED;
break;
#endif
#ifdef XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION
case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT:
pending_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_LEFT;
break;
case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT:
pending_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_RIGHT;
break;
case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP:
pending_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_TOP;
break;
case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM:
pending_state |= LIBDECOR_WINDOW_STATE_CONSTRAINED_BOTTOM;
break;
#endif
default:
break;
}
}
return pending_state;
}
static void
xdg_toplevel_configure(void *user_data,
struct xdg_toplevel *xdg_toplevel,
int32_t width,
int32_t height,
struct wl_array *states)
{
struct libdecor_frame *frame = user_data;
struct libdecor_frame_private *frame_priv = frame->priv;
enum libdecor_window_state window_state;
window_state = parse_states(states);
frame_priv->pending_configuration = libdecor_configuration_new();
frame_priv->pending_configuration->has_size = true;
frame_priv->pending_configuration->window_width = width;
frame_priv->pending_configuration->window_height = height;
frame_priv->pending_configuration->has_window_state = true;
frame_priv->pending_configuration->window_state = window_state;
}
static void
xdg_toplevel_close(void *user_data,
struct xdg_toplevel *xdg_toplevel)
{
struct libdecor_frame *frame = user_data;
struct libdecor_frame_private *frame_priv = frame->priv;
libdecorevent__frame_close(frame, frame_priv->user_data);
}
#ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION
static void
xdg_toplevel_configure_bounds(void *user_data,
struct xdg_toplevel *xdg_toplevel,
int32_t width,
int32_t height)
{
struct libdecor_frame *frame = user_data;
struct libdecor_frame_private *frame_priv = frame->priv;
int left = 0, top = 0, right = 0, bottom = 0;
if (frame_has_visible_client_side_decoration(frame)) {
libdecor_plugin_gtk_frame_get_border_size(frame, NULL, &left, &right, &top, &bottom);
}
width -= left + right;
height -= top + bottom;
libdecorevent__frame_bounds(frame, width, height, frame_priv->user_data);
}
#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)
{
struct libdecor_frame *frame = user_data;
struct libdecor_frame_private *frame_priv = frame->priv;
enum xdg_toplevel_wm_capabilities *wm_cap;
frame_priv->wm_capabilities = 0;
wl_array_for_each(wm_cap, capabilities) {
switch (*wm_cap) {
case XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU:
frame_priv->wm_capabilities |= LIBDECOR_WM_CAPABILITIES_WINDOW_MENU;
break;
case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE:
frame_priv->wm_capabilities |= LIBDECOR_WM_CAPABILITIES_MAXIMIZE;
break;
case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN:
frame_priv->wm_capabilities |= LIBDECOR_WM_CAPABILITIES_FULLSCREEN;
break;
case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE:
frame_priv->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
toplevel_decoration_configure(
void *data,
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
uint32_t mode)
{
struct libdecor_frame_private *frame_priv = (struct libdecor_frame_private *)data;
/* Ignore any _configure calls after the first, they will be
* from our set_mode call. */
if (!frame_priv->has_decoration_mode) {
frame_priv->has_decoration_mode = true;
frame_priv->decoration_mode = mode;
}
}
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 (!ctx.decoration_manager)
return;
frame_priv->toplevel_decoration =
zxdg_decoration_manager_v1_get_toplevel_decoration(ctx.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;
if (frame_priv->xdg_surface)
return;
frame_priv->xdg_surface =
xdg_wm_base_get_xdg_surface(ctx.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);
}
struct libdecor_frame *
libdecor_decorate(struct wl_surface *wl_surface, void *user_data){
struct libdecor_frame *frame;
struct libdecor_frame_private *frame_priv;
if (ctx.has_error){
return NULL;
}
frame = libdecor_plugin_gtk_frame_new();
if (!frame){
return NULL;
}
frame_priv = calloc(1, sizeof *frame_priv);
frame->priv = frame_priv;
frame_priv->ref_count = 1;
frame_priv->wl_surface = wl_surface;
frame_priv->user_data = user_data;
frame_priv->wm_capabilities = LIBDECOR_WM_CAPABILITIES_WINDOW_MENU |
LIBDECOR_WM_CAPABILITIES_MAXIMIZE |
LIBDECOR_WM_CAPABILITIES_FULLSCREEN |
LIBDECOR_WM_CAPABILITIES_MINIMIZE;
wl_list_insert(&ctx.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 (ctx.init_done)
init_shell_surface(frame);
return frame;
}
void
libdecor_frame_ref(struct libdecor_frame *frame)
{
struct libdecor_frame_private *frame_priv = frame->priv;
frame_priv->ref_count++;
}
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) {
if (ctx.decoration_manager && frame_priv->toplevel_decoration) {
zxdg_toplevel_decoration_v1_destroy(frame_priv->toplevel_decoration);
frame_priv->toplevel_decoration = NULL;
}
wl_list_remove(&frame->link);
if (frame_priv->xdg_toplevel)
xdg_toplevel_destroy(frame_priv->xdg_toplevel);
if (frame_priv->xdg_surface)
xdg_surface_destroy(frame_priv->xdg_surface);
libdecor_plugin_gtk_frame_free(frame);
free(frame_priv->state.title);
free(frame_priv->state.app_id);
free(frame_priv);
free(frame);
}
}
void *
libdecor_frame_get_user_data(struct libdecor_frame *frame)
{
return frame->priv->user_data;
}
void
libdecor_frame_set_user_data(struct libdecor_frame *frame, void *user_data)
{
frame->priv->user_data = user_data;
}
void
libdecor_frame_set_visibility(struct libdecor_frame *frame,
bool visible)
{
struct libdecor_frame_private *frame_priv = frame->priv;
frame_priv->visible = visible;
/* enable/disable decorations that are managed by the compositor.
* Note that, as of xdg_decoration v1, this is just a hint and there is
* no reliable way of disabling all decorations. In practice this should
* work but per spec this is not guaranteed.
*
* See also: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/17
*/
if (ctx.decoration_manager &&
frame_priv->toplevel_decoration &&
frame_priv->has_decoration_mode &&
frame_priv->decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) {
zxdg_toplevel_decoration_v1_set_mode(frame_priv->toplevel_decoration,
frame->priv->visible
? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE
: ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
}
if (frame_priv->content_width <= 0 ||
frame_priv->content_height <= 0)
return;
/* enable/disable decorations that are managed by a plugin */
if (frame_has_visible_client_side_decoration(frame)) {
/* show client-side decorations */
libdecor_plugin_gtk_frame_commit(frame, NULL, NULL);
} else {
/* destroy client-side decorations */
libdecor_plugin_gtk_frame_free(frame);
}
frame_set_window_geometry(frame,
frame_priv->content_width,
frame_priv->content_height);
libdecor_frame_toplevel_commit(frame);
}
bool
libdecor_frame_is_visible(struct libdecor_frame *frame)
{
return frame->priv->visible;
}
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 ? parent->priv->xdg_toplevel : NULL;
xdg_toplevel_set_parent(frame_priv->xdg_toplevel,
frame_priv->state.parent);
}
void
libdecor_frame_set_title(struct libdecor_frame *frame,
const char *title)
{
struct libdecor_frame_private *frame_priv = frame->priv;
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);
libdecor_plugin_gtk_frame_property_changed(frame);
}
}
const char *
libdecor_frame_get_title(struct libdecor_frame *frame)
{
return frame->priv->state.title;
}
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_state *state;
if (frame->priv->capabilities == old_capabilities)
return;
if (frame->priv->content_width == 0 ||
frame->priv->content_height == 0)
return;
libdecor_plugin_gtk_frame_property_changed(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);
}
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);
}
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);
}
bool
libdecor_frame_has_capability(struct libdecor_frame *frame, enum libdecor_capabilities capability){
return frame->priv->capabilities & capability;
}
void
libdecor_frame_popup_grab(struct libdecor_frame *frame, const char *seat_name){
struct libdecor_frame_private *frame_priv = frame->priv;
libdecor_plugin_gtk_frame_popup_grab(frame, seat_name);
}
void
libdecor_frame_popup_ungrab(struct libdecor_frame *frame, const char *seat_name){
struct libdecor_frame_private *frame_priv = frame->priv;
libdecor_plugin_gtk_frame_popup_ungrab(frame, seat_name);
}
void
libdecor_frame_dismiss_popup(struct libdecor_frame *frame,
const char *seat_name)
{
struct libdecor_frame_private *frame_priv = frame->priv;
libdecorevent__frame_dismiss_popup(frame, seat_name, frame_priv->user_data);
}
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);
}
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;
*frame_x = content_x;
*frame_y = content_y;
if (frame_has_visible_client_side_decoration(frame)){
int left, top;
libdecor_plugin_gtk_frame_get_border_size(frame, NULL, &left, NULL, &top, NULL);
*frame_x += left;
*frame_y += top;
}
}
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;
}
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;
}
void
libdecor_frame_get_min_content_size(const struct libdecor_frame *frame,
int *content_width,
int *content_height)
{
struct libdecor_frame_private *frame_priv = frame->priv;
*content_width = frame_priv->state.content_limits.min_width;
*content_height = frame_priv->state.content_limits.min_height;
}
void
libdecor_frame_get_max_content_size(const struct libdecor_frame *frame,
int *content_width,
int *content_height)
{
struct libdecor_frame_private *frame_priv = frame->priv;
*content_width = frame_priv->state.content_limits.max_width;
*content_height = frame_priv->state.content_limits.max_height;
}
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();
}
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);
}
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);
}
void
libdecor_frame_set_minimized(struct libdecor_frame *frame)
{
xdg_toplevel_set_minimized(frame->priv->xdg_toplevel);
}
void
libdecor_frame_set_maximized(struct libdecor_frame *frame)
{
xdg_toplevel_set_maximized(frame->priv->xdg_toplevel);
}
void
libdecor_frame_unset_maximized(struct libdecor_frame *frame)
{
xdg_toplevel_unset_maximized(frame->priv->xdg_toplevel);
}
void
libdecor_frame_set_fullscreen(struct libdecor_frame *frame,
struct wl_output *output)
{
xdg_toplevel_set_fullscreen(frame->priv->xdg_toplevel, output);
}
void
libdecor_frame_unset_fullscreen(struct libdecor_frame *frame)
{
xdg_toplevel_unset_fullscreen(frame->priv->xdg_toplevel);
}
bool
libdecor_frame_is_floating(struct libdecor_frame *frame)
{
return state_is_floating(frame->priv->window_state);
}
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(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;
libdecor_frame_apply_limits(frame, state->window_state);
}
void
libdecor_frame_toplevel_commit(struct libdecor_frame *frame)
{
struct libdecor_frame_private *frame_priv = frame->priv;
libdecorevent__frame_commit(frame, frame_priv->user_data);
}
void
libdecor_frame_commit(struct libdecor_frame *frame,
struct libdecor_state *state,
struct libdecor_configuration *configuration)
{
struct libdecor_frame_private *frame_priv = frame->priv;
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)) {
libdecor_plugin_gtk_frame_commit(frame, state,
configuration);
} else {
libdecor_plugin_gtk_frame_free(frame);
}
frame_set_window_geometry(frame,
frame_priv->content_width,
frame_priv->content_height);
if (configuration) {
xdg_surface_ack_configure(frame_priv->xdg_surface,
configuration->serial);
}
}
static void
do_map(struct libdecor_frame *frame)
{
struct libdecor_frame_private *frame_priv = frame->priv;
frame_priv->pending_map = false;
wl_surface_commit(frame_priv->wl_surface);
}
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);
}
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;
}
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;
}
struct xdg_toplevel *
libdecor_frame_get_xdg_toplevel(struct libdecor_frame *frame)
{
return frame->priv->xdg_toplevel;
}
int
libdecor_frame_get_content_width(struct libdecor_frame *frame)
{
struct libdecor_frame_private *frame_priv = frame->priv;
return frame_priv->content_width;
}
int
libdecor_frame_get_content_height(struct libdecor_frame *frame)
{
struct libdecor_frame_private *frame_priv = frame->priv;
return frame_priv->content_height;
}
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;
}
enum libdecor_wm_capabilities
libdecor_frame_get_wm_capabilities(struct libdecor_frame *frame)
{
struct libdecor_frame_private *frame_priv = frame->priv;
return frame_priv->wm_capabilities;
}
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
notify_error(enum libdecor_error error, const char *message)
{
ctx.has_error = true;
libdecorevent__error(error, message);
libdecor_plugin_gtk_destroy();
}
static void
finish_init(void){
struct libdecor_frame *frame;
wl_list_for_each(frame, &ctx.frames, link){
init_shell_surface(frame);
}
}
int
libdecor_dispatch(int timeout){
return libdecor_plugin_gtk_dispatch(timeout);
}
void
libdecor_notify_plugin_ready(void){
ctx.plugin_ready = true;
if (ctx.init_done){
finish_init();
}
}
void
libdecor_notify_plugin_error(enum libdecor_error error,
const char *__restrict fmt,
...)
{
char *msg = NULL;
int nbytes = 0;
va_list argp;
if (ctx.has_error)
return;
va_start(argp, fmt);
nbytes = vasprintf(&msg, fmt, argp);
va_end(argp);
if (nbytes > 0){
notify_error(error, msg);
}
if (msg)
free(msg);
}
void
cleanup(void){
libdecor_plugin_gtk_destroy();
if (ctx.wl_subcompositor != 0){
wl_subcompositor_destroy(ctx.wl_subcompositor);
}
if (ctx.wl_callback != 0){
wl_callback_destroy(ctx.wl_callback);
}
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
find_widget_by_name(GtkWidget *widget, void *data)
{
if (GTK_IS_WIDGET(widget)) {
char *style_ctx = gtk_style_context_to_string(
gtk_widget_get_style_context(widget),
GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE);
if (strstr(style_ctx, ((struct header_element_data *)data)->name)) {
((struct header_element_data *)data)->widget = widget;
free(style_ctx);
return;
}
free(style_ctx);
}
if (GTK_IS_CONTAINER(widget)) {
/* recursively traverse container */
gtk_container_forall(GTK_CONTAINER(widget), &find_widget_by_name, data);
}
}
static struct header_element_data
find_widget_by_type(GtkWidget *widget, enum header_element type)
{
char* name = NULL;
switch (type) {
case HEADER_FULL:
name = "headerbar.titlebar:";
break;
case HEADER_TITLE:
name = "label.title:";
break;
case HEADER_MIN:
name = ".minimize";
break;
case HEADER_MAX:
name = ".maximize";
break;
case HEADER_CLOSE:
name = ".close";
break;
default:
break;
}
struct header_element_data data = {.name = name, .type = type, .widget = NULL};
find_widget_by_name(widget, &data);
return data;
}
static bool
in_region(const cairo_rectangle_int_t *rect, const int *x, const int *y)
{
return (*x>=rect->x) & (*y>=rect->y) &
(*x<(rect->x+rect->width)) & (*y<(rect->y+rect->height));
}
static struct header_element_data
get_header_focus(const GtkHeaderBar *header_bar, const int x, const int y)
{
/* we have to check child widgets (buttons, title) before the 'HDR_HDR' root widget */
static const enum header_element elems[] =
{HEADER_TITLE, HEADER_MIN, HEADER_MAX, HEADER_CLOSE};
for (size_t i = 0; i < ARRAY_LENGTH(elems); i++) {
struct header_element_data elem =
find_widget_by_type(GTK_WIDGET(header_bar), elems[i]);
if (elem.widget) {
GtkAllocation allocation;
gtk_widget_get_allocation(GTK_WIDGET(elem.widget), &allocation);
if (in_region(&allocation, &x, &y))
return elem;
}
}
struct header_element_data elem_none = { .widget=NULL};
return elem_none;
}
static bool
streq(const char *str1,
const char *str2)
{
if (!str1 && !str2)
return true;
if (str1 && str2)
return strcmp(str1, str2) == 0;
return false;
}
static bool
own_proxy(struct wl_proxy *proxy)
{
if (!proxy)
return false;
return (wl_proxy_get_tag(proxy) == &libdecor_gtk_proxy_tag);
}
static bool
own_surface(struct wl_surface *surface)
{
return own_proxy((struct wl_proxy *) surface);
}
static bool
own_output(struct wl_output *output)
{
return own_proxy((struct wl_proxy *) output);
}
static bool
moveable(struct libdecor_frame_gtk *frame_gtk) {
return libdecor_frame_has_capability(&frame_gtk->frame,
LIBDECOR_ACTION_MOVE);
}
static bool
resizable(struct libdecor_frame_gtk *frame_gtk) {
return libdecor_frame_has_capability(&frame_gtk->frame,
LIBDECOR_ACTION_RESIZE);
}
static bool
minimizable(struct libdecor_frame_gtk *frame_gtk) {
return libdecor_frame_has_capability(&frame_gtk->frame,
LIBDECOR_ACTION_MINIMIZE);
}
static bool
closeable(struct libdecor_frame_gtk *frame_gtk) {
return libdecor_frame_has_capability(&frame_gtk->frame,
LIBDECOR_ACTION_CLOSE);
}
static void
buffer_free(struct buffer *buffer);
static void
draw_border_component(struct libdecor_frame_gtk *frame_gtk,
struct border_component *border_component,
enum component component);
static void
send_cursor(struct seat *seat);
static bool
update_local_cursor(struct seat *seat);
static void
libdecor_plugin_gtk_destroy(void)
{
struct seat *seat, *seat_tmp;
struct output *output, *output_tmp;
struct libdecor_frame_gtk *frame, *frame_tmp;
wl_list_for_each_safe(seat, seat_tmp, &ctx.seat_list, link) {
struct cursor_output *cursor_output, *tmp;
if (seat->wl_pointer)
wl_pointer_destroy(seat->wl_pointer);
if (seat->wl_touch)
wl_touch_destroy(seat->wl_touch);
if (seat->cursor_surface)
wl_surface_destroy(seat->cursor_surface);
wl_seat_destroy(seat->wl_seat);
if (seat->cursor_theme)
wl_cursor_theme_destroy(seat->cursor_theme);
wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) {
wl_list_remove(&cursor_output->link);
free(cursor_output);
}
free(seat->name);
free(seat);
}
wl_list_for_each_safe(output, output_tmp,
&ctx.output_list, link) {
if (wl_output_get_version (output->wl_output) >=
WL_OUTPUT_RELEASE_SINCE_VERSION)
wl_output_release(output->wl_output);
else
wl_output_destroy(output->wl_output);
free(output);
}
wl_list_for_each_safe(frame, frame_tmp,
&ctx.visible_frame_list, link) {
wl_list_remove(&frame->link);
}
if (ctx.wl_shm){
wl_shm_destroy(ctx.wl_shm);
}
}
static struct libdecor_frame_gtk *
libdecor_frame_gtk_new(void){
struct libdecor_frame_gtk *frame_gtk = calloc(1, sizeof *frame_gtk);
cairo_t *cr;
static const int size = 128;
static const int boundary = 32;
frame_gtk->shadow_blur = cairo_image_surface_create(
CAIRO_FORMAT_ARGB32, size, size);
wl_list_insert(&ctx.visible_frame_list, &frame_gtk->link);
cr = cairo_create(frame_gtk->shadow_blur);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_set_source_rgba(cr, 0, 0, 0, 1);
cairo_rectangle(cr, boundary, boundary,
size - 2 * boundary,
size - 2 * boundary);
cairo_fill(cr);
cairo_destroy(cr);
blur_surface(frame_gtk->shadow_blur, 64);
return frame_gtk;
}
static int
libdecor_plugin_gtk_get_fd(void){
return wl_display_get_fd(ctx.wl_display);
}
static int
libdecor_plugin_gtk_dispatch(int timeout){
struct wl_display *wl_display = ctx.wl_display;
struct pollfd fds[1];
int ret;
int dispatch_count = 0;
while (g_main_context_iteration(NULL, FALSE));
while (wl_display_prepare_read(wl_display) != 0)
dispatch_count += wl_display_dispatch_pending(wl_display);
if (wl_display_flush(wl_display) < 0 &&
errno != EAGAIN) {
wl_display_cancel_read(wl_display);
return -errno;
}
fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN };
ret = poll(fds, ARRAY_LENGTH(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 void
libdecor_plugin_gtk_set_handle_application_cursor(bool handle_cursor){
ctx.handle_cursor = handle_cursor;
}
static struct libdecor_frame *
libdecor_plugin_gtk_frame_new(void){
struct libdecor_frame_gtk *frame_gtk;
frame_gtk = libdecor_frame_gtk_new();
return &frame_gtk->frame;
}
static void
toggle_maximized(struct libdecor_frame *const frame)
{
if (!resizable((struct libdecor_frame_gtk *)frame))
return;
if (!(libdecor_frame_get_window_state(frame) &
LIBDECOR_WINDOW_STATE_MAXIMIZED))
libdecor_frame_set_maximized(frame);
else
libdecor_frame_unset_maximized(frame);
}
static void
buffer_release(void *user_data,
struct wl_buffer *wl_buffer)
{
struct buffer *buffer = user_data;
if (buffer->is_detached)
buffer_free(buffer);
else
buffer->in_use = false;
}
const struct wl_buffer_listener buffer_listener = {
buffer_release
};
static struct buffer *
create_shm_buffer(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 = libdecor_os_create_anonymous_file(size);
if (fd < 0) {
fprintf(stderr, "creating a buffer file for %d B failed: %s\n",
size, strerror(errno));
return NULL;
}
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
fprintf(stderr, "mmap failed: %s\n", strerror(errno));
close(fd);
return NULL;
}
buf_fmt = opaque ? WL_SHM_FORMAT_XRGB8888 : WL_SHM_FORMAT_ARGB8888;
pool = wl_shm_create_pool(ctx.wl_shm, fd, size);
buffer = calloc(1, sizeof *buffer);
buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0,
buffer_width, buffer_height,
stride,
buf_fmt);
wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
wl_shm_pool_destroy(pool);
close(fd);
buffer->data = data;
buffer->data_size = size;
buffer->width = width;
buffer->height = height;
buffer->scale = scale;
buffer->buffer_width = buffer_width;
buffer->buffer_height = buffer_height;
return buffer;
}
static void
buffer_free(struct buffer *buffer)
{
if (buffer->wl_buffer) {
wl_buffer_destroy(buffer->wl_buffer);
munmap(buffer->data, buffer->data_size);
buffer->wl_buffer = NULL;
buffer->in_use = false;
}
free(buffer);
}
static void
free_border_component(struct border_component *border_component)
{
if (border_component->wl_surface) {
wl_subsurface_destroy(border_component->wl_subsurface);
border_component->wl_subsurface = NULL;
wl_surface_destroy(border_component->wl_surface);
border_component->wl_surface = NULL;
}
if (border_component->buffer) {
buffer_free(border_component->buffer);
border_component->buffer = NULL;
}
if (border_component->output_list.next != NULL) {
struct surface_output *surface_output, *surface_output_tmp;
wl_list_for_each_safe(surface_output, surface_output_tmp,
&border_component->output_list, link) {
wl_list_remove(&surface_output->link);
free(surface_output);
}
}
}
static void
libdecor_plugin_gtk_frame_free(struct libdecor_frame *frame){
struct libdecor_frame_gtk *frame_gtk =
(struct libdecor_frame_gtk *) frame;
g_clear_pointer (&frame_gtk->header, gtk_widget_destroy);
g_clear_pointer (&frame_gtk->window, gtk_widget_destroy);
free_border_component(&frame_gtk->headerbar);
free_border_component(&frame_gtk->shadow);
frame_gtk->shadow_showing = false;
g_clear_pointer (&frame_gtk->shadow_blur, cairo_surface_destroy);
g_clear_pointer (&frame_gtk->title, free);
frame_gtk->decoration_type = DECORATION_TYPE_NONE;
if (frame_gtk->link.next != NULL)
wl_list_remove(&frame_gtk->link);
}
static bool
is_border_surfaces_showing(struct libdecor_frame_gtk *frame_gtk)
{
return frame_gtk->shadow_showing;
}
static void
hide_border_component(struct border_component *border_component)
{
if (!border_component->wl_surface)
return;
wl_surface_attach(border_component->wl_surface, NULL, 0, 0);
wl_surface_commit(border_component->wl_surface);
}
static void
hide_border_surfaces(struct libdecor_frame_gtk *frame_gtk)
{
hide_border_component(&frame_gtk->shadow);
frame_gtk->shadow_showing = false;
}
static struct border_component *
get_component_for_surface(struct libdecor_frame_gtk *frame_gtk,
const struct wl_surface *surface)
{
if (frame_gtk->shadow.wl_surface == surface)
return &frame_gtk->shadow;
if (frame_gtk->headerbar.wl_surface == surface)
return &frame_gtk->headerbar;
return NULL;
}
static bool
redraw_scale(struct libdecor_frame_gtk *frame_gtk,
struct border_component *cmpnt)
{
struct surface_output *surface_output;
int scale = 1;
if (cmpnt->wl_surface == NULL)
return false;
wl_list_for_each(surface_output, &cmpnt->output_list, link) {
scale = MAX(scale, surface_output->output->scale);
}
if (scale != cmpnt->scale) {
cmpnt->scale = scale;
if ((frame_gtk->decoration_type != DECORATION_TYPE_NONE) &&
((cmpnt->type != SHADOW) || is_border_surfaces_showing(frame_gtk))) {
draw_border_component(frame_gtk, cmpnt, cmpnt->type);
return true;
}
}
return false;
}
static bool
add_surface_output(struct 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 = calloc(1, sizeof *surface_output);
surface_output->output = output;
wl_list_insert(list, &surface_output->link);
return true;
}
static void
surface_enter(void *data,
struct wl_surface *wl_surface,
struct wl_output *wl_output)
{
struct libdecor_frame_gtk *frame_gtk = data;
struct border_component *cmpnt;
if (!(own_surface(wl_surface) && own_output(wl_output)))
return;
cmpnt = get_component_for_surface(frame_gtk, wl_surface);
if (cmpnt == NULL)
return;
if (!add_surface_output(wl_output, &cmpnt->output_list))
return;
if (redraw_scale(frame_gtk, cmpnt))
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}
static bool
remove_surface_output(struct wl_list *list, const struct wl_output *wl_output)
{
struct surface_output *surface_output;
wl_list_for_each(surface_output, list, link) {
if (surface_output->output->wl_output == wl_output) {
wl_list_remove(&surface_output->link);
free(surface_output);
return true;
}
}
return false;
}
static void
surface_leave(void *data,
struct wl_surface *wl_surface,
struct wl_output *wl_output)
{
struct libdecor_frame_gtk *frame_gtk = data;
struct border_component *cmpnt;
if (!(own_surface(wl_surface) && own_output(wl_output)))
return;
cmpnt = get_component_for_surface(frame_gtk, wl_surface);
if (cmpnt == NULL)
return;
if (!remove_surface_output(&cmpnt->output_list, wl_output))
return;
if (redraw_scale(frame_gtk, cmpnt))
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}
static struct wl_surface_listener surface_listener = {
surface_enter,
surface_leave,
};
static void
create_surface_subsurface_pair(struct libdecor_frame_gtk *frame_gtk,
struct wl_surface **out_wl_surface,
struct wl_subsurface **out_wl_subsurface)
{
struct libdecor_frame *frame = &frame_gtk->frame;
struct wl_compositor *wl_compositor = ctx.wl_compositor;
struct wl_surface *wl_surface;
struct wl_surface *parent;
struct wl_subsurface *wl_subsurface;
wl_surface = wl_compositor_create_surface(wl_compositor);
wl_proxy_set_tag((struct wl_proxy *) wl_surface,
&libdecor_gtk_proxy_tag);
parent = libdecor_frame_get_wl_surface(frame);
wl_subsurface = wl_subcompositor_get_subsurface(ctx.wl_subcompositor,
wl_surface,
parent);
*out_wl_surface = wl_surface;
*out_wl_subsurface = wl_subsurface;
}
static void
ensure_component(struct libdecor_frame_gtk *frame_gtk,
struct border_component *cmpnt)
{
if (!cmpnt->wl_surface) {
wl_list_init(&cmpnt->output_list);
cmpnt->scale = 1;
create_surface_subsurface_pair(frame_gtk,
&cmpnt->wl_surface,
&cmpnt->wl_subsurface);
wl_surface_add_listener(cmpnt->wl_surface, &surface_listener,
frame_gtk);
}
}
static void
ensure_border_surfaces(struct libdecor_frame_gtk *frame_gtk)
{
frame_gtk->shadow.type = SHADOW;
frame_gtk->shadow.opaque = false;
ensure_component(frame_gtk, &frame_gtk->shadow);
}
static void
ensure_title_bar_surfaces(struct libdecor_frame_gtk *frame_gtk)
{
GtkStyleContext *context_hdr;
frame_gtk->headerbar.type = HEADER;
frame_gtk->headerbar.opaque = false;
ensure_component(frame_gtk, &frame_gtk->headerbar);
if (frame_gtk->shadow.wl_surface) {
wl_subsurface_place_above(frame_gtk->headerbar.wl_subsurface,
frame_gtk->shadow.wl_surface);
}
/* create an offscreen window with a header bar */
/* TODO: This should only be done once at frame consutrction, but then
* the window and headerbar would not change style (e.g. backdrop)
* after construction. So we just destroy and re-create them.
*/
/* avoid warning when restoring previously turned off decoration */
if (GTK_IS_WIDGET(frame_gtk->header)) {
gtk_widget_destroy(frame_gtk->header);
frame_gtk->header = NULL;
}
/* avoid warning when restoring previously turned off decoration */
if (GTK_IS_WIDGET(frame_gtk->window)) {
gtk_widget_destroy(frame_gtk->window);
frame_gtk->window = NULL;
}
frame_gtk->window = gtk_offscreen_window_new();
frame_gtk->header = gtk_header_bar_new();
g_object_get(gtk_widget_get_settings(frame_gtk->window),
"gtk-double-click-time",
&ctx.double_click_time_ms,
"gtk-dnd-drag-threshold",
&ctx.drag_threshold,
NULL);
/* set as "default" decoration */
g_object_set(frame_gtk->header,
"title", libdecor_frame_get_title(&frame_gtk->frame),
"has-subtitle", FALSE,
"show-close-button", TRUE,
NULL);
context_hdr = gtk_widget_get_style_context(frame_gtk->header);
gtk_style_context_add_class(context_hdr, GTK_STYLE_CLASS_TITLEBAR);
gtk_style_context_add_class(context_hdr, "default-decoration");
gtk_window_set_titlebar(GTK_WINDOW(frame_gtk->window), frame_gtk->header);
gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(frame_gtk->header), TRUE);
gtk_window_set_resizable(GTK_WINDOW(frame_gtk->window), resizable(frame_gtk));
}
static void
calculate_component_size(struct libdecor_frame_gtk *frame_gtk,
enum component component,
int *component_x,
int *component_y,
int *component_width,
int *component_height)
{
struct libdecor_frame *frame = &frame_gtk->frame;
int content_width, content_height;
content_width = libdecor_frame_get_content_width(frame);
content_height = libdecor_frame_get_content_height(frame);
/* avoid warning when restoring previously turned off decoration */
const int title_height =
GTK_IS_WIDGET(frame_gtk->header)
? gtk_widget_get_allocated_height(frame_gtk->header) : 0;
switch (component) {
case NONE:
*component_width = 0;
*component_height = 0;
return;
case SHADOW:
*component_x = -(int)SHADOW_MARGIN;
*component_y = -(int)(SHADOW_MARGIN+title_height);
*component_width = content_width + 2 * SHADOW_MARGIN;
*component_height = content_height
+ 2 * SHADOW_MARGIN
+ title_height;
return;
case HEADER:
*component_x = 0;
/* reuse product of function call above */
*component_y = - title_height;
*component_width = gtk_widget_get_allocated_width(frame_gtk->header);
/* reuse product of function call above */
*component_height = title_height;
return;
}
abort();
}
static void
array_append(enum header_element **array, size_t *n, enum header_element item)
{
(*n)++;
*array = realloc(*array, (*n) * sizeof (enum header_element));
(*array)[(*n)-1] = item;
}
static void
draw_header_background(struct libdecor_frame_gtk *frame_gtk,
cairo_t *cr)
{
/* background */
GtkAllocation allocation;
GtkStyleContext* style;
gtk_widget_get_allocation(GTK_WIDGET(frame_gtk->header), &allocation);
style = gtk_widget_get_style_context(frame_gtk->header);
gtk_render_background(style, cr, allocation.x, allocation.y, allocation.width, allocation.height);
}
static void
draw_header_title(struct libdecor_frame_gtk *frame_gtk,
cairo_surface_t *surface)
{
/* title */
GtkWidget *label;
GtkAllocation allocation;
cairo_surface_t *label_surface = NULL;
cairo_t *cr;
label = find_widget_by_type(frame_gtk->header, HEADER_TITLE).widget;
gtk_widget_get_allocation(label, &allocation);
/* create subsection in which to draw label */
label_surface = cairo_surface_create_for_rectangle(
surface,
allocation.x, allocation.y,
allocation.width, allocation.height);
cr = cairo_create(label_surface);
gtk_widget_size_allocate(label, &allocation);
gtk_widget_draw(label, cr);
cairo_destroy(cr);
cairo_surface_destroy(label_surface);
}
static void
draw_header_button(struct libdecor_frame_gtk *frame_gtk,
cairo_t *cr,
cairo_surface_t *surface,
enum header_element button_type,
enum libdecor_window_state window_state)
{
struct header_element_data elem;
GtkWidget *button;
GtkStyleContext* button_style;
GtkStateFlags style_state;
GtkAllocation allocation;
gchar *icon_name;
int scale;
GtkWidget *icon_widget;
GtkAllocation allocation_icon;
GtkIconInfo* icon_info;
double sx, sy;
gint icon_width, icon_height;
GdkPixbuf* icon_pixbuf;
cairo_surface_t* icon_surface;
gint width = 0, height = 0;
gint left = 0, top = 0, right = 0, bottom = 0;
GtkBorder border;
GtkBorder padding;
elem = find_widget_by_type(frame_gtk->header, button_type);
button = elem.widget;
if (!button)
return;
button_style = gtk_widget_get_style_context(button);
style_state = elem.state;
/* change style based on window state and focus */
if (!(window_state & LIBDECOR_WINDOW_STATE_ACTIVE)) {
style_state |= GTK_STATE_FLAG_BACKDROP;
}
if (frame_gtk->hdr_focus.widget == button) {
style_state |= GTK_STATE_FLAG_PRELIGHT;
if (frame_gtk->hdr_focus.state & GTK_STATE_FLAG_ACTIVE) {
style_state |= GTK_STATE_FLAG_ACTIVE;
}
}
/* background */
gtk_widget_get_clip(button, &allocation);
gtk_style_context_save(button_style);
gtk_style_context_set_state(button_style, style_state);
gtk_render_background(button_style, cr,
allocation.x, allocation.y,
allocation.width, allocation.height);
gtk_render_frame(button_style, cr,
allocation.x, allocation.y,
allocation.width, allocation.height);
gtk_style_context_restore(button_style);
/* symbol */
switch (button_type) {
case HEADER_MIN:
icon_name = "window-minimize-symbolic";
break;
case HEADER_MAX:
icon_name = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) ?
"window-restore-symbolic" :
"window-maximize-symbolic";
break;
case HEADER_CLOSE:
icon_name = "window-close-symbolic";
break;
default:
icon_name = NULL;
break;
}
/* get scale */
cairo_surface_get_device_scale(surface, &sx, &sy);
scale = (sx+sy) / 2.0;
/* get original icon dimensions */
icon_widget = gtk_bin_get_child(GTK_BIN(button));
gtk_widget_get_allocation(icon_widget, &allocation_icon);
/* icon info */
if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &icon_width, &icon_height)) {
icon_width = 16;
icon_height = 16;
}
icon_info = gtk_icon_theme_lookup_icon_for_scale(
gtk_icon_theme_get_default(), icon_name,
icon_width, scale, (GtkIconLookupFlags)0);
/* icon pixel buffer*/
gtk_style_context_save(button_style);
gtk_style_context_set_state(button_style, style_state);
icon_pixbuf = gtk_icon_info_load_symbolic_for_context(
icon_info, button_style, NULL, NULL);
icon_surface = gdk_cairo_surface_create_from_pixbuf(icon_pixbuf, scale, NULL);
gtk_style_context_restore(button_style);
/* dimensions and position */
gtk_style_context_get(button_style, gtk_style_context_get_state(button_style),
"min-width", &width, "min-height", &height, NULL);
if (width < icon_width)
width = icon_width;
if (height < icon_height)
height = icon_height;
gtk_style_context_get_border(button_style, gtk_style_context_get_state(button_style), &border);
left += border.left;
right += border.right;
top += border.top;
bottom += border.bottom;
gtk_style_context_get_padding(button_style, gtk_style_context_get_state(button_style), &padding);
left += padding.left;
right += padding.right;
top += padding.top;
bottom += padding.bottom;
width += left + right;
height += top + bottom;
gtk_render_icon_surface(gtk_widget_get_style_context(icon_widget),
cr, icon_surface,
allocation.x + ((width - icon_width) / 2),
allocation.y + ((height - icon_height) / 2));
cairo_paint(cr);
cairo_surface_destroy(icon_surface);
g_object_unref(icon_pixbuf);
}
static void
draw_header_buttons(struct libdecor_frame_gtk *frame_gtk,
cairo_t *cr,
cairo_surface_t *surface)
{
/* buttons */
enum libdecor_window_state window_state;
enum header_element *buttons = NULL;
size_t nbuttons = 0;
window_state = libdecor_frame_get_window_state(
(struct libdecor_frame*)frame_gtk);
/* set buttons by capability */
if (minimizable(frame_gtk))
array_append(&buttons, &nbuttons, HEADER_MIN);
if (resizable(frame_gtk))
array_append(&buttons, &nbuttons, HEADER_MAX);
if (closeable(frame_gtk))
array_append(&buttons, &nbuttons, HEADER_CLOSE);
for (size_t i = 0; i < nbuttons; i++) {
draw_header_button(frame_gtk, cr, surface, buttons[i], window_state);
} /* loop buttons */
free(buttons);
}
static void
draw_header(struct libdecor_frame_gtk *frame_gtk,
cairo_t *cr,
cairo_surface_t *surface)
{
draw_header_background(frame_gtk, cr);
draw_header_title(frame_gtk, surface);
draw_header_buttons(frame_gtk, cr, surface);
}
static void
draw_component_content(struct libdecor_frame_gtk *frame_gtk,
struct buffer *buffer,
int component_width,
int component_height,
enum component component)
{
cairo_surface_t *surface;
cairo_t *cr;
/* clear buffer */
memset(buffer->data, 0, buffer->data_size);
surface = cairo_image_surface_create_for_data(
buffer->data, CAIRO_FORMAT_ARGB32,
buffer->buffer_width, buffer->buffer_height,
cairo_format_stride_for_width(
CAIRO_FORMAT_ARGB32,
buffer->buffer_width)
);
cr = cairo_create(surface);
cairo_surface_set_device_scale(surface, buffer->scale, buffer->scale);
/* background */
switch (component) {
case NONE:
break;
case SHADOW:
render_shadow(cr,
frame_gtk->shadow_blur,
-(int)SHADOW_MARGIN/2,
-(int)SHADOW_MARGIN/2,
buffer->width + SHADOW_MARGIN,
buffer->height + SHADOW_MARGIN,
64,
64);
break;
case HEADER:
draw_header(frame_gtk, cr, surface);
break;
}
/* mask the toplevel surface */
if (component == SHADOW) {
int component_x, component_y, component_width, component_height;
calculate_component_size(frame_gtk, component,
&component_x, &component_y,
&component_width, &component_height);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_rectangle(cr, -component_x, -component_y,
libdecor_frame_get_content_width(
&frame_gtk->frame),
libdecor_frame_get_content_height(
&frame_gtk->frame));
cairo_fill(cr);
}
cairo_destroy(cr);
cairo_surface_destroy(surface);
}
static void
set_component_input_region(struct libdecor_frame_gtk *frame_gtk,
struct border_component *border_component)
{
if (border_component->type == SHADOW && frame_gtk->shadow_showing) {
struct wl_region *input_region;
int component_x;
int component_y;
int component_width;
int component_height;
calculate_component_size(frame_gtk, border_component->type,
&component_x, &component_y,
&component_width, &component_height);
/*
* the input region is the outer surface size minus the inner
* content size
*/
input_region = wl_compositor_create_region(ctx.wl_compositor);
wl_region_add(input_region, 0, 0,
component_width, component_height);
wl_region_subtract(input_region, -component_x, -component_y,
libdecor_frame_get_content_width(&frame_gtk->frame),
libdecor_frame_get_content_height(&frame_gtk->frame));
wl_surface_set_input_region(border_component->wl_surface,
input_region);
wl_region_destroy(input_region);
}
}
static void
draw_border_component(struct libdecor_frame_gtk *frame_gtk,
struct border_component *border_component,
enum component component)
{
struct buffer *old_buffer;
struct buffer *buffer = NULL;
int component_x;
int component_y;
int component_width;
int component_height;
int scale = border_component->scale;
if (border_component->wl_surface == NULL)
return;
calculate_component_size(frame_gtk, component,
&component_x, &component_y,
&component_width, &component_height);
set_component_input_region(frame_gtk, border_component);
old_buffer = border_component->buffer;
if (old_buffer) {
if (!old_buffer->in_use &&
old_buffer->buffer_width == component_width * scale &&
old_buffer->buffer_height == component_height * scale) {
buffer = old_buffer;
} else {
buffer_free(old_buffer);
border_component->buffer = NULL;
}
}
if (!buffer)
buffer = create_shm_buffer(component_width,
component_height,
border_component->opaque,
border_component->scale);
draw_component_content(frame_gtk, buffer,
component_width, component_height,
component);
wl_surface_attach(border_component->wl_surface, buffer->wl_buffer, 0, 0);
wl_surface_set_buffer_scale(border_component->wl_surface, buffer->scale);
buffer->in_use = true;
wl_surface_commit(border_component->wl_surface);
wl_surface_damage_buffer(border_component->wl_surface, 0, 0,
component_width * scale,
component_height * scale);
wl_subsurface_set_position(border_component->wl_subsurface,
component_x, component_y);
border_component->buffer = buffer;
}
static void
draw_border(struct libdecor_frame_gtk *frame_gtk)
{
draw_border_component(frame_gtk, &frame_gtk->shadow, SHADOW);
frame_gtk->shadow_showing = true;
}
static void
draw_title_bar(struct libdecor_frame_gtk *frame_gtk)
{
GtkAllocation allocation = {0, 0, frame_gtk->content_width, 0};
enum libdecor_window_state state;
GtkStyleContext *style;
int pref_width;
int current_min_w, current_min_h, current_max_w, current_max_h, W, H;
state = libdecor_frame_get_window_state((struct libdecor_frame*)frame_gtk);
style = gtk_widget_get_style_context(frame_gtk->window);
if (!(state & LIBDECOR_WINDOW_STATE_ACTIVE)) {
gtk_widget_set_state_flags(frame_gtk->window, GTK_STATE_FLAG_BACKDROP, true);
} else {
gtk_widget_unset_state_flags(frame_gtk->window, GTK_STATE_FLAG_BACKDROP);
}
if (libdecor_frame_is_floating(&frame_gtk->frame)) {
gtk_style_context_remove_class(style, "maximized");
} else {
gtk_style_context_add_class(style, "maximized");
}
gtk_widget_show_all(frame_gtk->window);
/* set default width, using an empty title to estimate its smallest admissible value */
gtk_header_bar_set_title(GTK_HEADER_BAR(frame_gtk->header), "");
gtk_widget_get_preferred_width(frame_gtk->header, NULL, &pref_width);
gtk_header_bar_set_title(GTK_HEADER_BAR(frame_gtk->header),
libdecor_frame_get_title(&frame_gtk->frame));
libdecor_frame_get_min_content_size(&frame_gtk->frame, &current_min_w, &current_min_h);
if (current_min_w < pref_width) {
current_min_w = pref_width;
libdecor_frame_set_min_content_size(&frame_gtk->frame, current_min_w, current_min_h);
}
libdecor_frame_get_max_content_size(&frame_gtk->frame, &current_max_w, &current_max_h);
if (current_max_w && current_max_w < current_min_w) {
libdecor_frame_set_max_content_size(&frame_gtk->frame, current_min_w, current_max_h);
}
W = libdecor_frame_get_content_width(&frame_gtk->frame);
H = libdecor_frame_get_content_height(&frame_gtk->frame);
if (W < current_min_w) {
W = current_min_w;
struct libdecor_state *libdecor_state = libdecor_state_new(W, H);
libdecor_frame_commit(&frame_gtk->frame, libdecor_state, NULL);
libdecor_state_free(libdecor_state);
return;
}
/* set default height */
gtk_widget_get_preferred_height(frame_gtk->header, NULL, &allocation.height);
gtk_widget_size_allocate(frame_gtk->header, &allocation);
draw_border_component(frame_gtk, &frame_gtk->headerbar, HEADER);
}
static void
draw_decoration(struct libdecor_frame_gtk *frame_gtk)
{
switch (frame_gtk->decoration_type) {
case DECORATION_TYPE_NONE:
if (frame_gtk->link.next != NULL)
wl_list_remove(&frame_gtk->link);
if (is_border_surfaces_showing(frame_gtk))
hide_border_surfaces(frame_gtk);
hide_border_component(&frame_gtk->headerbar);
break;
case DECORATION_TYPE_ALL: {
/* show borders */
ensure_border_surfaces(frame_gtk);
draw_border(frame_gtk);
/* show title bar */
ensure_title_bar_surfaces(frame_gtk);
draw_title_bar(frame_gtk);
/* link frame */
if (frame_gtk->link.next == NULL){
wl_list_insert(&ctx.visible_frame_list, &frame_gtk->link);
}
}break;
case DECORATION_TYPE_TITLE_ONLY:{
/* hide borders */
if (is_border_surfaces_showing(frame_gtk))
hide_border_surfaces(frame_gtk);
/* show title bar */
ensure_title_bar_surfaces(frame_gtk);
draw_title_bar(frame_gtk);
/* link frame */
if (frame_gtk->link.next == NULL){
wl_list_insert(&ctx.visible_frame_list, &frame_gtk->link);
}
}break;
}
}
static enum decoration_type
window_state_to_decoration_type(enum libdecor_window_state window_state)
{
if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN)
return DECORATION_TYPE_NONE;
else if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED ||
window_state & LIBDECOR_WINDOW_STATE_TILED_LEFT ||
window_state & LIBDECOR_WINDOW_STATE_TILED_RIGHT ||
window_state & LIBDECOR_WINDOW_STATE_TILED_TOP ||
window_state & LIBDECOR_WINDOW_STATE_TILED_BOTTOM)
/* title bar, no shadows */
return DECORATION_TYPE_TITLE_ONLY;
else
/* title bar, shadows */
return DECORATION_TYPE_ALL;
}
static void
libdecor_plugin_gtk_frame_commit(struct libdecor_frame *frame,
struct libdecor_state *state,
struct libdecor_configuration *configuration)
{
struct libdecor_frame_gtk *frame_gtk =
(struct libdecor_frame_gtk *) frame;
enum libdecor_window_state old_window_state;
enum libdecor_window_state new_window_state;
int old_content_width, old_content_height;
int new_content_width, new_content_height;
enum decoration_type old_decoration_type;
enum decoration_type new_decoration_type;
old_window_state = frame_gtk->window_state;
new_window_state = libdecor_frame_get_window_state(frame);
old_content_width = frame_gtk->content_width;
old_content_height = frame_gtk->content_height;
new_content_width = libdecor_frame_get_content_width(frame);
new_content_height = libdecor_frame_get_content_height(frame);
old_decoration_type = frame_gtk->decoration_type;
new_decoration_type = window_state_to_decoration_type(new_window_state);
if (old_decoration_type == new_decoration_type &&
old_content_width == new_content_width &&
old_content_height == new_content_height &&
old_window_state == new_window_state)
return;
frame_gtk->content_width = new_content_width;
frame_gtk->content_height = new_content_height;
frame_gtk->window_state = new_window_state;
frame_gtk->decoration_type = new_decoration_type;
draw_decoration(frame_gtk);
/* set fixed window size */
if (!resizable(frame_gtk)) {
libdecor_frame_set_min_content_size(frame,
frame_gtk->content_width,
frame_gtk->content_height);
libdecor_frame_set_max_content_size(frame,
frame_gtk->content_width,
frame_gtk->content_height);
}
}
static void
libdecor_plugin_gtk_frame_property_changed(struct libdecor_frame *frame)
{
struct libdecor_frame_gtk *frame_gtk =
(struct libdecor_frame_gtk *) frame;
bool redraw_needed = false;
const char *new_title;
/*
* when in SSD mode, the window title is not to be managed by GTK;
* this is detected by frame_gtk->header not being a proper GTK widget
*/
if (!GTK_IS_WIDGET(frame_gtk->header)) return;
new_title = libdecor_frame_get_title(frame);
if (!streq(frame_gtk->title, new_title))
redraw_needed = true;
free(frame_gtk->title);
frame_gtk->title = NULL;
if (new_title)
frame_gtk->title = strdup(new_title);
if (frame_gtk->capabilities != libdecor_frame_get_capabilities(frame)) {
frame_gtk->capabilities = libdecor_frame_get_capabilities(frame);
redraw_needed = true;
}
if (redraw_needed) {
draw_decoration(frame_gtk);
libdecor_frame_toplevel_commit(frame);
}
}
static void
update_component_focus(struct libdecor_frame_gtk *frame_gtk,
struct wl_surface *surface,
struct seat *seat)
{
static struct border_component *border_component;
static struct border_component *child_component;
static struct border_component *focus_component;
border_component = get_component_for_surface(frame_gtk, surface);
focus_component = border_component;
wl_list_for_each(child_component, &border_component->child_components, link) {
int component_x = 0, component_y = 0;
int component_width = 0, component_height = 0;
calculate_component_size(frame_gtk, child_component->type,
&component_x, &component_y,
&component_width, &component_height);
if (seat->pointer_x >= component_x &&
seat->pointer_x < component_x + component_width &&
seat->pointer_y >= component_y &&
seat->pointer_y < component_y + component_height) {
focus_component = child_component;
break;
}
}
if (frame_gtk->grab)
frame_gtk->active = frame_gtk->grab;
else
frame_gtk->active = focus_component;
frame_gtk->focus = focus_component;
}
static void
sync_active_component(struct libdecor_frame_gtk *frame_gtk,
struct seat *seat)
{
struct border_component *old_active;
if (!seat->pointer_focus)
return;
old_active = frame_gtk->active;
update_component_focus(frame_gtk, seat->pointer_focus, seat);
if (old_active != frame_gtk->active) {
draw_decoration(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}
if (update_local_cursor(seat))
send_cursor(seat);
}
static void
synthesize_pointer_enter(struct seat *seat)
{
struct wl_surface *surface;
struct libdecor_frame_gtk *frame_gtk;
surface = seat->pointer_focus;
if (!surface || !own_surface(surface))
return;
frame_gtk = wl_surface_get_user_data(surface);
if (!frame_gtk)
return;
update_component_focus(frame_gtk, seat->pointer_focus, seat);
frame_gtk->grab = NULL;
/* update decorations */
if (frame_gtk->active) {
draw_decoration(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}
update_local_cursor(seat);
send_cursor(seat);
}
static void
synthesize_pointer_leave(struct seat *seat)
{
struct wl_surface *surface;
struct libdecor_frame_gtk *frame_gtk;
surface = seat->pointer_focus;
if (!surface || !own_surface(surface))
return;
frame_gtk = wl_surface_get_user_data(surface);
if (!frame_gtk)
return;
if (!frame_gtk->active)
return;
frame_gtk->active = NULL;
draw_decoration(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
update_local_cursor(seat);
}
static void
libdecor_plugin_gtk_frame_popup_grab(struct libdecor_frame *frame,
const char *seat_name)
{
struct libdecor_frame_gtk *frame_gtk =
(struct libdecor_frame_gtk *) frame;
struct seat *seat;
wl_list_for_each(seat, &ctx.seat_list, link) {
if (streq(seat->name, seat_name)) {
if (seat->grabbed) {
fprintf(stderr, "libdecor-WARNING: Application "
"tried to grab seat twice\n");
}
synthesize_pointer_leave(seat);
seat->grabbed = true;
return;
}
}
fprintf(stderr,
"libdecor-WARNING: Application tried to grab unknown seat\n");
}
static void
libdecor_plugin_gtk_frame_popup_ungrab(struct libdecor_frame *frame,
const char *seat_name)
{
struct libdecor_frame_gtk *frame_gtk =
(struct libdecor_frame_gtk *) frame;
struct seat *seat;
wl_list_for_each(seat, &ctx.seat_list, link) {
if (streq(seat->name, seat_name)) {
if (!seat->grabbed) {
fprintf(stderr, "libdecor-WARNING: Application "
"tried to ungrab seat twice\n");
}
seat->grabbed = false;
synthesize_pointer_enter(seat);
sync_active_component(frame_gtk, seat);
return;
}
}
fprintf(stderr,
"libdecor-WARNING: Application tried to ungrab unknown seat\n");
}
static bool
libdecor_plugin_gtk_frame_get_border_size(struct libdecor_frame *frame,
struct libdecor_configuration *configuration,
int *left,
int *right,
int *top,
int *bottom)
{
struct libdecor_frame_gtk *frame_gtk =
(struct libdecor_frame_gtk *) frame;
enum libdecor_window_state window_state;
if (configuration) {
if (!libdecor_configuration_get_window_state(
configuration, &window_state))
return false;
} else {
window_state = libdecor_frame_get_window_state(frame);
}
if (left)
*left = 0;
if (right)
*right = 0;
if (bottom)
*bottom = 0;
if (top) {
enum decoration_type type = window_state_to_decoration_type(window_state);
switch (type) {
case DECORATION_TYPE_NONE:
*top = 0;
break;
case DECORATION_TYPE_ALL:
ensure_border_surfaces(frame_gtk);
G_GNUC_FALLTHROUGH;
case DECORATION_TYPE_TITLE_ONLY:
if (!frame_gtk->header)
ensure_title_bar_surfaces(frame_gtk);
gtk_widget_show_all(frame_gtk->window);
gtk_widget_get_preferred_height(frame_gtk->header, NULL, top);
break;
}
}
return true;
}
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 = calloc(1, 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 = ctx.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)
{
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(ctx.cursor_theme_name, ctx.cursor_size*scale,
ctx.wl_shm);
if (theme == NULL)
return false;
if (seat->cursor_theme)
wl_cursor_theme_destroy(seat->cursor_theme);
seat->cursor_theme = theme;
for (unsigned int i = 0; i < ARRAY_LENGTH(cursor_names); i++) {
seat->cursors[i] = wl_cursor_theme_get_cursor(
seat->cursor_theme,
cursor_names[i]);
}
seat->cursor_left_ptr = wl_cursor_theme_get_cursor(seat->cursor_theme,
"left_ptr");
seat->current_cursor = seat->cursor_left_ptr;
return true;
}
enum libdecor_resize_edge
component_edge(const struct border_component *cmpnt,
const int pointer_x,
const int pointer_y,
const int margin)
{
const bool top = pointer_y < margin * 2;
const bool bottom = pointer_y > (cmpnt->buffer->height - margin * 2);
const bool left = pointer_x < margin * 2;
const bool right = pointer_x > (cmpnt->buffer->width - margin * 2);
if (top) {
if (left)
return LIBDECOR_RESIZE_EDGE_TOP_LEFT;
else if (right)
return LIBDECOR_RESIZE_EDGE_TOP_RIGHT;
else
return LIBDECOR_RESIZE_EDGE_TOP;
} else if (bottom) {
if (left)
return LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT;
else if (right)
return LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT;
else
return LIBDECOR_RESIZE_EDGE_BOTTOM;
} else if (left) {
return LIBDECOR_RESIZE_EDGE_LEFT;
} else if (right) {
return LIBDECOR_RESIZE_EDGE_RIGHT;
} else {
return LIBDECOR_RESIZE_EDGE_NONE;
}
}
static bool
update_local_cursor(struct seat *seat)
{
if (!seat->pointer_focus) {
seat->current_cursor = seat->cursor_left_ptr;
return false;
}
if (!own_surface(seat->pointer_focus))
return false;
struct libdecor_frame_gtk *frame_gtk =
wl_surface_get_user_data(seat->pointer_focus);
struct wl_cursor *wl_cursor = NULL;
if (!frame_gtk || !frame_gtk->active) {
seat->current_cursor = seat->cursor_left_ptr;
return false;
}
bool theme_updated = ensure_cursor_theme(seat);
if (frame_gtk->active->type == SHADOW &&
is_border_surfaces_showing(frame_gtk) &&
resizable(frame_gtk)) {
enum libdecor_resize_edge edge;
edge = component_edge(frame_gtk->active,
seat->pointer_x,
seat->pointer_y, SHADOW_MARGIN);
if (edge != LIBDECOR_RESIZE_EDGE_NONE)
wl_cursor = seat->cursors[edge - 1];
} else {
wl_cursor = seat->cursor_left_ptr;
}
if (seat->current_cursor != wl_cursor) {
seat->current_cursor = wl_cursor;
return true;
}
return theme_updated;
}
static void
send_cursor(struct seat *seat)
{
struct wl_cursor_image *image;
struct wl_buffer *buffer;
if (seat->pointer_focus == NULL || seat->current_cursor == NULL)
return;
image = seat->current_cursor->images[0];
buffer = wl_cursor_image_get_buffer(image);
wl_surface_attach(seat->cursor_surface, buffer, 0, 0);
wl_surface_set_buffer_scale(seat->cursor_surface, seat->cursor_scale);
wl_surface_damage_buffer(seat->cursor_surface, 0, 0,
image->width * seat->cursor_scale,
image->height * seat->cursor_scale);
wl_surface_commit(seat->cursor_surface);
wl_pointer_set_cursor(seat->wl_pointer, seat->serial,
seat->cursor_surface,
image->hotspot_x / seat->cursor_scale,
image->hotspot_y / seat->cursor_scale);
}
static void
pointer_enter(void *data,
struct wl_pointer *wl_pointer,
uint32_t serial,
struct wl_surface *surface,
wl_fixed_t surface_x,
wl_fixed_t surface_y)
{
if (!surface)
return;
struct seat *seat = data;
struct libdecor_frame_gtk *frame_gtk = NULL;
if (!own_surface(surface)) {
struct seat *seat = wl_pointer_get_user_data(wl_pointer);
if (!ctx.handle_cursor)
return;
} else {
frame_gtk = wl_surface_get_user_data(surface);
}
ensure_cursor_surface(seat);
seat->pointer_x = wl_fixed_to_int(surface_x);
seat->pointer_y = wl_fixed_to_int(surface_y);
seat->serial = serial;
seat->pointer_focus = surface;
if (!frame_gtk)
return;
frame_gtk->active = get_component_for_surface(frame_gtk, surface);
/* update decorations */
if (frame_gtk->active) {
draw_decoration(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}
update_local_cursor(seat);
send_cursor(seat);
}
static void
pointer_leave(void *data,
struct wl_pointer *wl_pointer,
uint32_t serial,
struct wl_surface *surface)
{
struct seat *seat = data;
struct libdecor_frame_gtk *frame_gtk;
seat->pointer_focus = NULL;
if (!surface)
return;
if (!own_surface(surface))
return;
frame_gtk = wl_surface_get_user_data(surface);
if (frame_gtk) {
frame_gtk->titlebar_gesture.state =
TITLEBAR_GESTURE_STATE_INIT;
frame_gtk->titlebar_gesture.first_pressed_button = 0;
frame_gtk->active = NULL;
frame_gtk->hdr_focus.widget = NULL;
frame_gtk->hdr_focus.type = HEADER_NONE;
draw_decoration(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
update_local_cursor(seat);
}
}
static void
pointer_motion(void *data,
struct wl_pointer *wl_pointer,
uint32_t time,
wl_fixed_t surface_x,
wl_fixed_t surface_y)
{
struct seat *seat = data;
struct libdecor_frame_gtk *frame_gtk;
struct header_element_data new_focus;
if (!seat->pointer_focus || !own_surface(seat->pointer_focus))
return;
seat->pointer_x = wl_fixed_to_int(surface_x);
seat->pointer_y = wl_fixed_to_int(surface_y);
if (update_local_cursor(seat))
send_cursor(seat);
frame_gtk = wl_surface_get_user_data(seat->pointer_focus);
/* avoid warnings after decoration has been turned off */
if (!GTK_IS_WIDGET(frame_gtk->header) || frame_gtk->active->type != HEADER) {
frame_gtk->hdr_focus.type = HEADER_NONE;
}
new_focus = get_header_focus(GTK_HEADER_BAR(frame_gtk->header),
seat->pointer_x, seat->pointer_y);
/* only update if widget change so that we keep the state */
if (frame_gtk->hdr_focus.widget != new_focus.widget) {
frame_gtk->hdr_focus = new_focus;
}
frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_PRELIGHT;
/* redraw with updated button visuals */
draw_title_bar(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
switch (frame_gtk->titlebar_gesture.state) {
case TITLEBAR_GESTURE_STATE_BUTTON_PRESSED:
if (frame_gtk->titlebar_gesture.first_pressed_button == BTN_LEFT) {
if (ABS ((double) seat->pointer_x -
(double) frame_gtk->titlebar_gesture.pressed_x) >
ctx.drag_threshold ||
ABS ((double) seat->pointer_y -
(double) frame_gtk->titlebar_gesture.pressed_y) >
ctx.drag_threshold) {
libdecor_frame_move(&frame_gtk->frame,
seat->wl_seat,
frame_gtk->titlebar_gesture.pressed_serial);
}
}
case TITLEBAR_GESTURE_STATE_INIT:
case TITLEBAR_GESTURE_STATE_CONSUMED:
case TITLEBAR_GESTURE_STATE_DISCARDED:
break;
}
}
static void
handle_button_on_shadow(struct libdecor_frame_gtk *frame_gtk,
struct seat *seat,
uint32_t serial,
uint32_t time,
uint32_t button,
uint32_t state)
{
enum libdecor_resize_edge edge = LIBDECOR_RESIZE_EDGE_NONE;
edge = component_edge(frame_gtk->active,
seat->pointer_x,
seat->pointer_y,
SHADOW_MARGIN);
if (edge != LIBDECOR_RESIZE_EDGE_NONE && resizable(frame_gtk)) {
libdecor_frame_resize(&frame_gtk->frame,
seat->wl_seat,
serial,
edge);
}
}
static void
handle_titlebar_gesture(struct libdecor_frame_gtk *frame_gtk,
struct seat *seat,
uint32_t serial,
enum titlebar_gesture gesture)
{
switch (gesture) {
case TITLEBAR_GESTURE_DOUBLE_CLICK:
toggle_maximized(&frame_gtk->frame);
break;
case TITLEBAR_GESTURE_MIDDLE_CLICK:
break;
case TITLEBAR_GESTURE_RIGHT_CLICK:
{
const int title_height = gtk_widget_get_allocated_height(frame_gtk->header);
libdecor_frame_show_window_menu(&frame_gtk->frame,
seat->wl_seat,
serial,
seat->pointer_x,
seat->pointer_y
-title_height);
}
break;
}
}
static void
handle_button_on_header(struct libdecor_frame_gtk *frame_gtk,
struct seat *seat,
uint32_t serial,
uint32_t time,
uint32_t button,
uint32_t state)
{
switch (frame_gtk->titlebar_gesture.state) {
case TITLEBAR_GESTURE_STATE_INIT:
if (state != WL_POINTER_BUTTON_STATE_PRESSED)
return;
if (button == BTN_RIGHT) {
handle_titlebar_gesture(frame_gtk,
seat,
serial,
TITLEBAR_GESTURE_RIGHT_CLICK);
frame_gtk->titlebar_gesture.state =
TITLEBAR_GESTURE_STATE_CONSUMED;
} else {
if (button == BTN_LEFT &&
frame_gtk->titlebar_gesture.first_pressed_button == BTN_LEFT &&
time - frame_gtk->titlebar_gesture.first_pressed_time <
(uint32_t) ctx.double_click_time_ms) {
handle_titlebar_gesture(frame_gtk,
seat,
serial,
TITLEBAR_GESTURE_DOUBLE_CLICK);
frame_gtk->titlebar_gesture.state =
TITLEBAR_GESTURE_STATE_CONSUMED;
} else {
frame_gtk->titlebar_gesture.first_pressed_button = button;
frame_gtk->titlebar_gesture.first_pressed_time = time;
frame_gtk->titlebar_gesture.pressed_x = seat->pointer_x;
frame_gtk->titlebar_gesture.pressed_y = seat->pointer_y;
frame_gtk->titlebar_gesture.pressed_serial = serial;
frame_gtk->titlebar_gesture.state =
TITLEBAR_GESTURE_STATE_BUTTON_PRESSED;
}
}
frame_gtk->titlebar_gesture.button_pressed_count = 1;
switch (frame_gtk->hdr_focus.type) {
case HEADER_MIN:
case HEADER_MAX:
case HEADER_CLOSE:
frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_ACTIVE;
draw_title_bar(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
break;
default:
break;
}
break;
case TITLEBAR_GESTURE_STATE_BUTTON_PRESSED:
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
frame_gtk->titlebar_gesture.state =
TITLEBAR_GESTURE_STATE_DISCARDED;
frame_gtk->titlebar_gesture.button_pressed_count++;
} else {
frame_gtk->titlebar_gesture.button_pressed_count--;
if (frame_gtk->titlebar_gesture.button_pressed_count == 0) {
frame_gtk->titlebar_gesture.state =
TITLEBAR_GESTURE_STATE_INIT;
if (frame_gtk->titlebar_gesture.first_pressed_button == button &&
button == BTN_LEFT) {
libdecor_frame_ref(&frame_gtk->frame);
switch (frame_gtk->hdr_focus.type) {
case HEADER_MIN:
if (minimizable(frame_gtk))
libdecor_frame_set_minimized(
&frame_gtk->frame);
break;
case HEADER_MAX:
toggle_maximized(&frame_gtk->frame);
break;
case HEADER_CLOSE:
if (closeable(frame_gtk)) {
libdecor_frame_close(
&frame_gtk->frame);
seat->pointer_focus = NULL;
}
break;
default:
break;
}
frame_gtk->hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE;
if (GTK_IS_WIDGET(frame_gtk->header)) {
draw_title_bar(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}
libdecor_frame_unref(&frame_gtk->frame);
}
} else {
frame_gtk->hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE;
if (GTK_IS_WIDGET(frame_gtk->header)) {
draw_title_bar(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}
}
}
break;
case TITLEBAR_GESTURE_STATE_CONSUMED:
case TITLEBAR_GESTURE_STATE_DISCARDED:
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
frame_gtk->titlebar_gesture.button_pressed_count++;
} else {
frame_gtk->titlebar_gesture.button_pressed_count--;
if (frame_gtk->titlebar_gesture.button_pressed_count == 0) {
frame_gtk->titlebar_gesture.state =
TITLEBAR_GESTURE_STATE_INIT;
frame_gtk->titlebar_gesture.first_pressed_button = 0;
}
}
break;
}
}
static void
pointer_button(void *data,
struct wl_pointer *wl_pointer,
uint32_t serial,
uint32_t time,
uint32_t button,
uint32_t state)
{
struct seat *seat = data;
struct libdecor_frame_gtk *frame_gtk;
if (!seat->pointer_focus || !own_surface(seat->pointer_focus))
return;
frame_gtk = wl_surface_get_user_data(seat->pointer_focus);
if (!frame_gtk)
return;
switch (frame_gtk->active->type) {
case SHADOW:
handle_button_on_shadow (frame_gtk, seat, serial, time, button, state);
break;
case HEADER:
handle_button_on_header (frame_gtk, seat, serial, time, button, state);
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
update_touch_focus(struct seat *seat,
struct libdecor_frame_gtk *frame_gtk,
wl_fixed_t x,
wl_fixed_t y)
{
/* avoid warnings after decoration has been turned off */
if (GTK_IS_WIDGET(frame_gtk->header) && frame_gtk->touch_active->type == HEADER) {
struct header_element_data new_focus = get_header_focus(
GTK_HEADER_BAR(frame_gtk->header),
wl_fixed_to_int(x), wl_fixed_to_int(y));
/* only update if widget change so that we keep the state */
if (frame_gtk->hdr_focus.widget != new_focus.widget) {
frame_gtk->hdr_focus = new_focus;
}
frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_PRELIGHT;
/* redraw with updated button visuals */
draw_title_bar(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
} else {
frame_gtk->hdr_focus.type = HEADER_NONE;
}
}
static void
touch_down(void *data,
struct wl_touch *wl_touch,
uint32_t serial,
uint32_t time,
struct wl_surface *surface,
int32_t id,
wl_fixed_t x,
wl_fixed_t y)
{
struct seat *seat = data;
struct libdecor_frame_gtk *frame_gtk;
if (!surface || !own_surface(surface))
return;
frame_gtk = wl_surface_get_user_data(surface);
if (!frame_gtk)
return;
seat->touch_focus = surface;
frame_gtk->touch_active = get_component_for_surface(frame_gtk, surface);
if (!frame_gtk->touch_active)
return;
update_touch_focus(seat, frame_gtk, x, y);
/* update decorations */
draw_decoration(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
enum libdecor_resize_edge edge = LIBDECOR_RESIZE_EDGE_NONE;
switch (frame_gtk->touch_active->type) {
case SHADOW: {
edge = component_edge(frame_gtk->touch_active,
wl_fixed_to_int(x),
wl_fixed_to_int(y),
SHADOW_MARGIN);
}break;
case HEADER: {
switch (frame_gtk->hdr_focus.type){
case HEADER_MIN:
case HEADER_MAX:
case HEADER_CLOSE: {
frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_ACTIVE;
draw_title_bar(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}break;
default: {
if (time - seat->touch_down_time_stamp < (uint32_t)ctx.double_click_time_ms) {
toggle_maximized(&frame_gtk->frame);
}
else if (moveable(frame_gtk)) {
seat->touch_down_time_stamp = time;
libdecor_frame_move(&frame_gtk->frame,
seat->wl_seat,
serial);
}
}break;
}
}break;
default: break;
}
if (edge != LIBDECOR_RESIZE_EDGE_NONE &&
resizable(frame_gtk)) {
libdecor_frame_resize(
&frame_gtk->frame,
seat->wl_seat,
serial,
edge);
}
}
static void
touch_up(void *data,
struct wl_touch *wl_touch,
uint32_t serial,
uint32_t time,
int32_t id)
{
struct seat *seat = data;
struct libdecor_frame_gtk *frame_gtk;
if (!seat->touch_focus || !own_surface(seat->touch_focus))
return;
frame_gtk = wl_surface_get_user_data(seat->touch_focus);
if (!frame_gtk)
return;
if (!frame_gtk->touch_active)
return;
switch (frame_gtk->touch_active->type) {
case HEADER:
libdecor_frame_ref(&frame_gtk->frame);
switch (frame_gtk->hdr_focus.type) {
case HEADER_MIN:
if (minimizable(frame_gtk)) {
libdecor_frame_set_minimized(
&frame_gtk->frame);
}
break;
case HEADER_MAX:
toggle_maximized(&frame_gtk->frame);
break;
case HEADER_CLOSE:
if (closeable(frame_gtk)) {
libdecor_frame_close(
&frame_gtk->frame);
seat->touch_focus = NULL;
}
break;
default:
break;
}
/* unset active/clicked state once released */
frame_gtk->hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE;
if (GTK_IS_WIDGET(frame_gtk->header)) {
draw_title_bar(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}
libdecor_frame_unref(&frame_gtk->frame);
break;
default:
break;
}
seat->touch_focus = NULL;
frame_gtk->touch_active = NULL;
frame_gtk->hdr_focus.widget = NULL;
frame_gtk->hdr_focus.type = HEADER_NONE;
draw_decoration(frame_gtk);
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}
static void
touch_motion(void *data,
struct wl_touch *wl_touch,
uint32_t time,
int32_t id,
wl_fixed_t x,
wl_fixed_t y)
{
struct seat *seat = data;
struct libdecor_frame_gtk *frame_gtk;
if (!seat->touch_focus || !own_surface(seat->touch_focus))
return;
frame_gtk = wl_surface_get_user_data(seat->touch_focus);
if (!frame_gtk)
return;
update_touch_focus(seat, frame_gtk, x, y);
}
static void
touch_frame(void *data,
struct wl_touch *wl_touch)
{
}
static void
touch_cancel(void *data,
struct wl_touch *wl_touch)
{
}
const struct wl_touch_listener touch_listener = {
touch_down,
touch_up,
touch_motion,
touch_frame,
touch_cancel
};
static void
output_geometry(void *data,
struct wl_output *wl_output,
int32_t x,
int32_t y,
int32_t physical_width,
int32_t physical_height,
int32_t subpixel,
const char *make,
const char *model,
int32_t transform)
{
}
static void
output_mode(void *data,
struct wl_output *wl_output,
uint32_t flags,
int32_t width,
int32_t height,
int32_t refresh)
{
}
static void
output_done(void *data,
struct wl_output *wl_output)
{
struct output *output = data;
struct libdecor_frame_gtk *frame_gtk;
struct seat *seat;
wl_list_for_each(frame_gtk, &ctx.visible_frame_list, link){
bool updated = false;
updated |= redraw_scale(frame_gtk, &frame_gtk->shadow);
if (updated)
libdecor_frame_toplevel_commit(&frame_gtk->frame);
}
wl_list_for_each(seat, &ctx.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;
}
const struct wl_output_listener output_listener = {
output_geometry,
output_mode,
output_done,
output_scale
};
static void
remove_surface_outputs(struct border_component *cmpnt, const struct output *output)
{
struct surface_output *surface_output;
wl_list_for_each(surface_output, &cmpnt->output_list, link) {
if (surface_output->output == output) {
wl_list_remove(&surface_output->link);
free(surface_output);
break;
}
}
}
static void
output_removed(struct output *output)
{
struct libdecor_frame_gtk *frame_gtk;
struct seat *seat;
wl_list_for_each(frame_gtk, &ctx.visible_frame_list, link) {
remove_surface_outputs(&frame_gtk->shadow, output);
}
wl_list_for_each(seat, &ctx.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);
}
//#include "desktop-settings.c"
static bool
get_cursor_settings_from_env(char **theme, int *size)
{
char *env_xtheme;
char *env_xsize;
env_xtheme = getenv("XCURSOR_THEME");
if (env_xtheme != NULL)
*theme = strdup(env_xtheme);
env_xsize = getenv("XCURSOR_SIZE");
if (env_xsize != NULL)
*size = atoi(env_xsize);
return env_xtheme != NULL && env_xsize != NULL;
}
#ifdef HAS_DBUS
#include <dbus/dbus.h>
static DBusMessage *
get_setting_sync(DBusConnection *const connection,
const char *key, const char *value)
{
DBusError error;
dbus_bool_t success;
DBusMessage *message;
DBusMessage *reply;
message = dbus_message_new_method_call(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
"Read");
success = dbus_message_append_args(message,
DBUS_TYPE_STRING, &key,
DBUS_TYPE_STRING, &value,
DBUS_TYPE_INVALID);
if (!success)
return NULL;
dbus_error_init(&error);
reply = dbus_connection_send_with_reply_and_block(
connection,
message,
DBUS_TIMEOUT_USE_DEFAULT,
&error);
dbus_message_unref(message);
if (dbus_error_is_set(&error)) {
dbus_error_free(&error);
return NULL;
}
dbus_error_free(&error);
return reply;
}
static bool
parse_type(DBusMessage *const reply, const int type, void *value){
DBusMessageIter iter[3];
dbus_message_iter_init(reply, &iter[0]);
if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT)
return false;
dbus_message_iter_recurse(&iter[0], &iter[1]);
if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT)
return false;
dbus_message_iter_recurse(&iter[1], &iter[2]);
if (dbus_message_iter_get_arg_type(&iter[2]) != type)
return false;
dbus_message_iter_get_basic(&iter[2], value);
return true;
}
bool
libdecor_get_cursor_settings(char **theme, int *size){
static const char name[] = "org.gnome.desktop.interface";
static const char key_theme[] = "cursor-theme";
static const char key_size[] = "cursor-size";
DBusError error;
DBusConnection *connection;
DBusMessage *reply;
const char *value_theme = NULL;
dbus_error_init(&error);
connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
if (dbus_error_is_set(&error))
goto fallback;
reply = get_setting_sync(connection, name, key_theme);
if (!reply)
goto fallback;
if (!parse_type(reply, DBUS_TYPE_STRING, &value_theme)) {
dbus_message_unref(reply);
goto fallback;
}
*theme = strdup(value_theme);
dbus_message_unref(reply);
reply = get_setting_sync(connection, name, key_size);
if (!reply)
goto fallback;
if (!parse_type(reply, DBUS_TYPE_INT32, size)) {
dbus_message_unref(reply);
goto fallback;
}
dbus_message_unref(reply);
return true;
fallback:
return get_cursor_settings_from_env(theme, size);
}
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))
return 0;
reply = get_setting_sync(connection, name, key_color_scheme);
if (!reply)
return 0;
if (!parse_type(reply, DBUS_TYPE_UINT32, &color)) {
dbus_message_unref(reply);
return 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