1511 lines
47 KiB
C
Executable File
1511 lines
47 KiB
C
Executable File
#if 0
|
|
libdecor_path="/home/mr4th/mr4th/libdecor"
|
|
root_path="$PWD"
|
|
gtk_flags="$(pkg-config --cflags --libs gtk+-3.0)"
|
|
dbus_flags="$(pkg-config --cflags --libs dbus-1)"
|
|
my_flags="-Iwayland -I$libdecor_path/src -I$libdecor_path/src/plugins -I$libdecor_path/build"
|
|
my_flags+=" -lwayland-client -lwayland-cursor -lwayland-egl -lEGL -lm"
|
|
mkdir -p build
|
|
clang -o build/demo -g $root_path/wayland_gtk_egl.c $gtk_flags $dbus_flags $my_flags
|
|
exit 0
|
|
#endif
|
|
|
|
#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 <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 "wayland_gtk_egl.h"
|
|
|
|
// X(N:name,R:return,P:params)
|
|
#define GL_FUNCS_XLIST(X)\
|
|
X(glDrawBuffer, void, (GLenum buf)) \
|
|
X(glViewport, void, (GLint x, GLint y, GLsizei w, GLsizei h)) \
|
|
X(glClear, void, (GLbitfield mask)) \
|
|
X(glClearColor, void, (GLfloat r, GLfloat g, GLfloat b, GLfloat a))
|
|
|
|
#define X(N,R,P) R (*N)P = 0;
|
|
GL_FUNCS_XLIST(X)
|
|
#undef X
|
|
|
|
static Ctx ctx = {0};
|
|
|
|
static void
|
|
shm_format(void *udata, struct wl_shm *wl_shm, uint32_t format){
|
|
}
|
|
|
|
const struct wl_shm_listener shm_listener = {
|
|
shm_format
|
|
};
|
|
|
|
static void
|
|
xdg_wm_base_ping(void *udata, 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
|
|
pointer_enter(void *udata, struct wl_pointer *wl_pointer, uint32_t serial,
|
|
struct wl_surface *surface, wl_fixed_t fx, wl_fixed_t fy){
|
|
int32_t x = wl_fixed_to_int(fx);
|
|
int32_t y = wl_fixed_to_int(fy);
|
|
if (surface == ctx.main_wl_surface){
|
|
printf("pointer_enter (main) %d,%d\n", x, y);
|
|
}
|
|
else if (surface == ctx.gtk_window.titlebar.wl_surface){
|
|
x += ctx.gtk_window.titlebar.p[0];
|
|
y += ctx.gtk_window.titlebar.p[1];
|
|
printf("pointer_enter (titlebar) %d,%d\n", x, y);
|
|
}
|
|
else if (surface == ctx.gtk_window.shadow.wl_surface){
|
|
x += ctx.gtk_window.shadow.p[0];
|
|
y += ctx.gtk_window.shadow.p[1];
|
|
printf("pointer_enter (shadow) %d,%d\n", x, y);
|
|
}
|
|
else{
|
|
printf("pointer_enter (unidentified) %d,%d\n", x, y);
|
|
}
|
|
ctx.serial = serial;
|
|
ctx.hover_surface = surface;
|
|
}
|
|
|
|
static void
|
|
pointer_leave(void *udata, struct wl_pointer *wl_pointer, uint32_t serial,
|
|
struct wl_surface *surface){
|
|
if (surface == ctx.main_wl_surface){
|
|
printf("pointer_leave (main)\n");
|
|
}
|
|
else if (surface == ctx.gtk_window.titlebar.wl_surface){
|
|
printf("pointer_leave (titlebar)\n");
|
|
}
|
|
else if (surface == ctx.gtk_window.shadow.wl_surface){
|
|
printf("pointer_leave (shadow)\n");
|
|
}
|
|
else{
|
|
printf("pointer_leave (unidentified)\n");
|
|
}
|
|
ctx.hover_surface = 0;
|
|
}
|
|
|
|
static void
|
|
pointer_motion(void *udata, struct wl_pointer *wl_pointer, uint32_t time,
|
|
wl_fixed_t fx, wl_fixed_t fy){
|
|
int32_t x = wl_fixed_to_int(fx);
|
|
int32_t y = wl_fixed_to_int(fy);
|
|
if (ctx.hover_surface == ctx.main_wl_surface){
|
|
printf("pointer_motion (main) %d,%d\n", x, y);
|
|
}
|
|
else if (ctx.hover_surface == ctx.gtk_window.titlebar.wl_surface){
|
|
x += ctx.gtk_window.titlebar.p[0];
|
|
y += ctx.gtk_window.titlebar.p[1];
|
|
printf("pointer_motion (titlebar) %d,%d\n", x, y);
|
|
}
|
|
else if (ctx.hover_surface == ctx.gtk_window.shadow.wl_surface){
|
|
x += ctx.gtk_window.shadow.p[0];
|
|
y += ctx.gtk_window.shadow.p[1];
|
|
printf("pointer_motion (shadow) %d,%d\n", x, y);
|
|
}
|
|
else{
|
|
printf("pointer_motion (unidentified) %d,%d\n", x, y);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pointer_button(void *udata, struct wl_pointer *wl_pointer, uint32_t serial,
|
|
uint32_t time, uint32_t button, uint32_t state){
|
|
char *state_str = (state == 1?"press":"release");
|
|
if (button == BTN_LEFT){
|
|
printf("pointer_button (left) %s\n", state_str);
|
|
}
|
|
else if (button == BTN_RIGHT){
|
|
printf("pointer_button (right) %s\n", state_str);
|
|
}
|
|
else if (button == BTN_MIDDLE){
|
|
printf("pointer_button (middle) %s\n", state_str);
|
|
}
|
|
else{
|
|
printf("pointer_button (unidentified) %s\n", state_str);
|
|
}
|
|
if (state){
|
|
ctx.gtk_window.button = button;
|
|
}
|
|
ctx.serial = serial;
|
|
}
|
|
|
|
static void
|
|
pointer_axis(void *udata, struct wl_pointer *wl_pointer,
|
|
uint32_t time, uint32_t axis, wl_fixed_t fv){
|
|
int v = wl_fixed_to_int(fv);
|
|
printf("pointer_axis %u,%d\n", axis, v);
|
|
}
|
|
|
|
const struct wl_pointer_listener pointer_listener = {
|
|
pointer_enter,
|
|
pointer_leave,
|
|
pointer_motion,
|
|
pointer_button,
|
|
pointer_axis
|
|
};
|
|
|
|
static void
|
|
seat_capabilities(void *udata, struct wl_seat *wl_seat, uint32_t capabilities){
|
|
if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && ctx.wl_pointer == 0){
|
|
ctx.wl_pointer = wl_seat_get_pointer(ctx.wl_seat);
|
|
wl_pointer_add_listener(ctx.wl_pointer, &pointer_listener, 0);
|
|
}
|
|
else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && ctx.wl_pointer != 0){
|
|
wl_pointer_release(ctx.wl_pointer);
|
|
ctx.wl_pointer = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
seat_name(void *udata, struct wl_seat *wl_seat, const char *name){
|
|
}
|
|
|
|
const struct wl_seat_listener seat_listener = {
|
|
seat_capabilities,
|
|
seat_name
|
|
};
|
|
|
|
static void
|
|
registry_global(void *udata, 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, "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, 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.zxdg_decoration_manager = wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, Min(version, 2));
|
|
}
|
|
else if (strcmp(interface, "wl_seat") == 0){
|
|
ctx.wl_seat = wl_registry_bind(ctx.wl_registry, name, &wl_seat_interface, 3);
|
|
wl_seat_add_listener(ctx.wl_seat, &seat_listener, 0);
|
|
ctx.cursor_surface = wl_compositor_create_surface(ctx.wl_compositor);
|
|
}
|
|
}
|
|
|
|
static void
|
|
registry_global_remove(void *udata, struct wl_registry *registry,
|
|
uint32_t name){
|
|
}
|
|
|
|
const struct wl_registry_listener registry_listener = {
|
|
registry_global,
|
|
registry_global_remove,
|
|
};
|
|
|
|
static void
|
|
xdg_surface_configure(void *udata, struct xdg_surface *xdg_surface,
|
|
uint32_t serial){
|
|
ctx.config_staged.serial = serial;
|
|
}
|
|
|
|
const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_configure, };
|
|
|
|
static void
|
|
xdg_toplevel_configure(void *udata, struct xdg_toplevel *xdg_toplevel,
|
|
int32_t width, int32_t height, struct wl_array *states){
|
|
if (width != 0 && height != 0){
|
|
ctx.config_staged.dim[0] = width;
|
|
ctx.config_staged.dim[1] = height;
|
|
}
|
|
else{
|
|
ctx.config_staged.dim[0] = ctx.config.dim[0];
|
|
ctx.config_staged.dim[1] = ctx.config.dim[1];
|
|
}
|
|
ctx.config_staged.flags = csd_window_flags_from_states_array(states);
|
|
}
|
|
|
|
static void
|
|
xdg_toplevel_close(void *udata, struct xdg_toplevel *xdg_toplevel){
|
|
ctx.close_signal = 1;
|
|
}
|
|
|
|
static void
|
|
xdg_toplevel_configure_bounds(void *udata, struct xdg_toplevel *xdg_toplevel,
|
|
int32_t w, int32_t h){
|
|
}
|
|
|
|
static void
|
|
xdg_toplevel_wm_capabilities(void *udata, struct xdg_toplevel *xdg_toplevel,
|
|
struct wl_array *capabilities){
|
|
}
|
|
|
|
const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
|
xdg_toplevel_configure,
|
|
xdg_toplevel_close,
|
|
xdg_toplevel_configure_bounds,
|
|
xdg_toplevel_wm_capabilities,
|
|
};
|
|
|
|
static void
|
|
xdg_toplevel_decoration_configure(void *udata, struct zxdg_toplevel_decoration_v1 *toplevel,
|
|
uint32_t mode){
|
|
ctx.config_staged.decoration_mode = mode;
|
|
}
|
|
|
|
const struct zxdg_toplevel_decoration_v1_listener zxdg_toplevel_decoration_listener = {
|
|
xdg_toplevel_decoration_configure
|
|
};
|
|
|
|
int main(){
|
|
/* desktop settings */
|
|
ctx.color_scheme = ds_get_color_scheme();
|
|
ctx.cursor_theme = ds_get_cursor_theme();
|
|
|
|
/* setup gtk */
|
|
csd_gtk_init();
|
|
|
|
/* setup Wayland */
|
|
{
|
|
ctx.wl_display = wl_display_connect(0);
|
|
ctx.wl_registry = wl_display_get_registry(ctx.wl_display);
|
|
wl_registry_add_listener(ctx.wl_registry, ®istry_listener, 0);
|
|
|
|
wl_display_flush(ctx.wl_display);
|
|
wl_display_dispatch(ctx.wl_display);
|
|
|
|
ctx.wl_cursor_theme = wl_cursor_theme_load(ctx.cursor_theme.name,
|
|
ctx.cursor_theme.size,
|
|
ctx.wl_shm);
|
|
|
|
#define X(k,n) ctx.wl_cursors[k] = wl_cursor_theme_get_cursor(ctx.wl_cursor_theme, n);
|
|
X(CursorShape_Pointer, "left_ptr");
|
|
X(CursorShape_Resize_Top, "top_side");
|
|
X(CursorShape_Resize_Bottom, "bottom_side");
|
|
X(CursorShape_Resize_Left, "left_side");
|
|
X(CursorShape_Resize_Right, "right_side");
|
|
X(CursorShape_Resize_TopLeft, "top_left_corner");
|
|
X(CursorShape_Resize_BottomLeft, "bottom_left_corner");
|
|
X(CursorShape_Resize_TopRight, "top_right_corner");
|
|
X(CursorShape_Resize_BottomRight, "bottom_right_corner");
|
|
#undef X
|
|
}
|
|
|
|
/* setup EGL */
|
|
{
|
|
ctx.egl_display = eglGetDisplay(ctx.wl_display);
|
|
|
|
EGLint major = 0, minor = 0;
|
|
eglInitialize(ctx.egl_display, &major, &minor);
|
|
eglBindAPI(EGL_OPENGL_API);
|
|
|
|
{
|
|
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);
|
|
}
|
|
|
|
eglMakeCurrent(ctx.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx.egl_context);
|
|
|
|
#define X(N,R,P) N = (R(*)P)(eglGetProcAddress(#N));
|
|
GL_FUNCS_XLIST(X)
|
|
#undef X
|
|
}
|
|
|
|
/* create a window */
|
|
{
|
|
/* window main surface */
|
|
ctx.main_wl_surface = wl_compositor_create_surface(ctx.wl_compositor);
|
|
ctx.main_xdg_surface = xdg_wm_base_get_xdg_surface(ctx.xdg_wm_base, ctx.main_wl_surface);
|
|
xdg_surface_add_listener(ctx.main_xdg_surface, &xdg_surface_listener, 0);
|
|
ctx.main_xdg_toplevel = xdg_surface_get_toplevel(ctx.main_xdg_surface);
|
|
xdg_toplevel_add_listener(ctx.main_xdg_toplevel, &xdg_toplevel_listener, 0);
|
|
|
|
ctx.control_flags = ~0;
|
|
|
|
ctx.dim[0] = 640;
|
|
ctx.dim[1] = 480;
|
|
ctx.config_staged.decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
|
|
if (ctx.zxdg_decoration_manager != 0){
|
|
ctx.main_zxdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(ctx.zxdg_decoration_manager, ctx.main_xdg_toplevel);
|
|
zxdg_toplevel_decoration_v1_add_listener(ctx.main_zxdg_toplevel_decoration, &zxdg_toplevel_decoration_listener, 0);
|
|
}
|
|
|
|
for (int k = 0; k < 2; k += 1){
|
|
ctx.mmbox[k][0] = 0;
|
|
ctx.mmbox[k][1] = (1 << 30);
|
|
}
|
|
|
|
xdg_toplevel_set_app_id(ctx.main_xdg_toplevel, "demo");
|
|
xdg_toplevel_set_title(ctx.main_xdg_toplevel, "Example Window");
|
|
wl_surface_commit(ctx.main_wl_surface);
|
|
|
|
/* window subsurface */
|
|
ctx.gtk_window.shadow = csd_subsurface_new();
|
|
ctx.gtk_window.titlebar = csd_subsurface_new();
|
|
|
|
/* window gtk */
|
|
csd_gtk_window_init();
|
|
|
|
/* window egl */
|
|
ctx.main_wl_egl_window = wl_egl_window_create(ctx.main_wl_surface,
|
|
ctx.dim[0], ctx.dim[1]);
|
|
|
|
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,
|
|
};
|
|
eglChooseConfig(ctx.egl_display, attributes, configs, config_cap, &config_count);
|
|
}
|
|
|
|
{
|
|
EGLint attributes[] = {
|
|
EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
|
|
EGL_NONE,
|
|
};
|
|
for (EGLint i = 0; i < config_count; i += 1){
|
|
ctx.main_egl_surface = eglCreateWindowSurface(ctx.egl_display, configs[i], ctx.main_wl_egl_window, attributes);
|
|
if (ctx.main_egl_surface != EGL_NO_SURFACE){
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
eglMakeCurrent(ctx.egl_display, ctx.main_egl_surface,
|
|
ctx.main_egl_surface, ctx.egl_context);
|
|
|
|
eglSwapInterval(ctx.egl_display, 1);
|
|
}
|
|
|
|
/* main loop */
|
|
int exit_loop = 0;
|
|
for (;!exit_loop;){
|
|
/* poll for events */
|
|
{
|
|
struct pollfd fds[1] = {0};
|
|
|
|
/* register fds[0] ~ wayland events */
|
|
bool wayland_started_read = false;
|
|
bool wayland_did_read = false;
|
|
{
|
|
fds[0].fd = -1;
|
|
|
|
wl_display_dispatch_pending(ctx.wl_display);
|
|
wayland_started_read = (wl_display_prepare_read(ctx.wl_display) != -1);
|
|
if (wayland_started_read){
|
|
fds[0].fd = wl_display_get_fd(ctx.wl_display);
|
|
fds[0].events = POLLIN;
|
|
wl_display_flush(ctx.wl_display);
|
|
}
|
|
}
|
|
|
|
/* poll and handle events */
|
|
int ret = poll(fds, sizeof(fds)/sizeof(*fds), 0);
|
|
if (ret > 0){
|
|
|
|
/* handle fds[0] ~ wayland events */
|
|
if (fds[0].revents & POLLIN){
|
|
wayland_did_read = true;
|
|
wl_display_read_events(ctx.wl_display);
|
|
wl_display_dispatch_pending(ctx.wl_display);
|
|
}
|
|
}
|
|
|
|
/* wayland event read cleanup */
|
|
if (wayland_started_read && !wayland_did_read){
|
|
wl_display_cancel_read(ctx.wl_display);
|
|
}
|
|
}
|
|
|
|
/* apply config */
|
|
if (ctx.config.serial != ctx.config_staged.serial){
|
|
ctx.config = ctx.config_staged;
|
|
xdg_surface_ack_configure(ctx.main_xdg_surface, ctx.config.serial);
|
|
}
|
|
|
|
int csd = (ctx.config.decoration_mode ==
|
|
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
|
|
|
|
/* window sizing */
|
|
memset(&ctx.csd_frame, 0, sizeof ctx.csd_frame);
|
|
memset(ctx.csd_dim, 0, sizeof ctx.csd_dim);
|
|
if (csd){
|
|
ctx.csd_frame = csd_gtk_calculate_frame();
|
|
}
|
|
for (int i = 0; i < 2; i += 1){
|
|
ctx.csd_dim[i] = ctx.csd_frame.border[i][0] + ctx.csd_frame.border[i][1];
|
|
}
|
|
for (int i = 0; i < 2; i += 1){
|
|
int32_t d = ctx.config.dim[i] - ctx.csd_dim[i];
|
|
if (!ctx.handled_first_size){
|
|
d = ctx.dim[i];
|
|
ctx.config.dim[i] = d + ctx.csd_dim[i];
|
|
}
|
|
d = ClampBot(d, ctx.mmbox[i][0]);
|
|
d = ClampTop(d, ctx.mmbox[i][1]);
|
|
d = ClampBot(d, ctx.csd_frame.minbox[i]);
|
|
ctx.dim[i] = d;
|
|
}
|
|
ctx.handled_first_size = 1;
|
|
|
|
/* window size commit */
|
|
if (!(ctx.control_flags & WindowControlFlag_Resize)){
|
|
xdg_toplevel_set_min_size(ctx.main_xdg_toplevel, ctx.dim[0], ctx.dim[1]);
|
|
xdg_toplevel_set_max_size(ctx.main_xdg_toplevel, ctx.dim[0], ctx.dim[1]);
|
|
}
|
|
else{
|
|
for (int i = 0; i < 2; i += 1){
|
|
int32_t mw = ClampBot(ctx.mmbox[0][i], ctx.csd_frame.minbox[i]) + ctx.csd_dim[0];
|
|
int32_t mh = ctx.mmbox[1][i] + ctx.csd_dim[1];
|
|
if (i == 0){
|
|
xdg_toplevel_set_min_size(ctx.main_xdg_toplevel, mw, mh);
|
|
}
|
|
else{
|
|
xdg_toplevel_set_max_size(ctx.main_xdg_toplevel, mw, mh);
|
|
}
|
|
}
|
|
}
|
|
xdg_surface_set_window_geometry(ctx.main_xdg_surface,
|
|
-ctx.csd_frame.border[0][0],
|
|
-ctx.csd_frame.border[1][0],
|
|
ctx.dim[0] + ctx.csd_dim[0],
|
|
ctx.dim[1] + ctx.csd_dim[1]);
|
|
wl_egl_window_resize(ctx.main_wl_egl_window, ctx.dim[0], ctx.dim[1], 0, 0);
|
|
|
|
/* frame update and render */
|
|
ctx.cursor_shape = CursorShape_Pointer;
|
|
csd_gtk_update_and_render();
|
|
|
|
/* app update & render */
|
|
{
|
|
if (ctx.close_signal){
|
|
exit_loop = 1;
|
|
}
|
|
|
|
glDrawBuffer(GL_BACK);
|
|
glViewport(0, 0, ctx.dim[0], ctx.dim[1]);
|
|
glClearColor(0.40f, 0.90f, 0.15f, 1.f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
}
|
|
eglSwapBuffers(ctx.egl_display, ctx.main_egl_surface);
|
|
wl_surface_commit(ctx.main_wl_surface);
|
|
|
|
/* commit new cursor */
|
|
{
|
|
struct wl_cursor *cursor = ctx.wl_cursors[ctx.cursor_shape];
|
|
if (cursor != 0){
|
|
struct wl_cursor_image *cursor_image = cursor->images[0];
|
|
struct wl_buffer *cursor_buffer = wl_cursor_image_get_buffer(cursor_image);
|
|
wl_surface_set_buffer_scale(ctx.cursor_surface, 1);
|
|
wl_surface_attach(ctx.cursor_surface, cursor_buffer, 0, 0);
|
|
wl_surface_damage_buffer(ctx.cursor_surface, 0, 0,
|
|
cursor_image->width, cursor_image->height);
|
|
wl_surface_commit(ctx.cursor_surface);
|
|
wl_pointer_set_cursor(ctx.wl_pointer, ctx.serial, ctx.cursor_surface,
|
|
cursor_image->hotspot_x, cursor_image->hotspot_y);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/* csd helpers */
|
|
|
|
static CSD_SubSurface
|
|
csd_subsurface_new(void){
|
|
CSD_SubSurface result = {0};
|
|
result.wl_surface = wl_compositor_create_surface(ctx.wl_compositor);
|
|
result.wl_subsurface =
|
|
wl_subcompositor_get_subsurface(ctx.wl_subcompositor,
|
|
result.wl_surface,
|
|
ctx.main_wl_surface);
|
|
return(result);
|
|
}
|
|
|
|
static void
|
|
csd_subsurface_buffer_clear(CSD_SubSurface *subsurface){
|
|
if (subsurface->data != 0){
|
|
munmap(subsurface->data, subsurface->size);
|
|
subsurface->data = 0;
|
|
}
|
|
if (subsurface->wl_buffer != 0){
|
|
wl_buffer_destroy(subsurface->wl_buffer);
|
|
subsurface->wl_buffer = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
csd_subsurface_buffer_alloc(CSD_SubSurface *subsurface, int32_t dim[2]){
|
|
int stride = 4*dim[0];
|
|
uint64_t size = 4*dim[0]*dim[1];
|
|
|
|
int fd = os_create_anonymous_file(size);
|
|
if (fd >= 0){
|
|
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (data != MAP_FAILED){
|
|
struct wl_shm_pool *pool = wl_shm_create_pool(ctx.wl_shm, fd, size);
|
|
struct wl_buffer *wl_buffer =
|
|
wl_shm_pool_create_buffer(pool, 0, dim[0], dim[1],
|
|
stride, WL_SHM_FORMAT_ARGB8888);
|
|
wl_shm_pool_destroy(pool);
|
|
|
|
subsurface->wl_buffer = wl_buffer;
|
|
subsurface->data = data;
|
|
subsurface->size = size;
|
|
for (int i = 0; i < 2; i += 1){
|
|
subsurface->dim[i] = dim[i];
|
|
}
|
|
}
|
|
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
csd_subsurface_set_position(CSD_SubSurface *subsurface, int32_t x, int32_t y){
|
|
wl_subsurface_set_position(subsurface->wl_subsurface, x, y);
|
|
subsurface->p[0] = x;
|
|
subsurface->p[1] = y;
|
|
}
|
|
|
|
static void
|
|
csd_subsurface_commit(CSD_SubSurface *subsurface){
|
|
wl_surface_attach(subsurface->wl_surface, subsurface->wl_buffer, 0, 0);
|
|
wl_surface_set_buffer_scale(subsurface->wl_surface, 1);
|
|
wl_surface_damage_buffer(subsurface->wl_surface, 0, 0,
|
|
subsurface->dim[0], subsurface->dim[1]);
|
|
wl_surface_commit(subsurface->wl_surface);
|
|
}
|
|
|
|
static WindowFlags
|
|
csd_window_flags_from_states_array(struct wl_array *states){
|
|
WindowFlags flags = 0;
|
|
uint32_t *p;
|
|
wl_array_for_each(p, states){
|
|
switch (*p) {
|
|
case XDG_TOPLEVEL_STATE_FULLSCREEN: flags |= WindowFlag_IsFullscreen; break;
|
|
case XDG_TOPLEVEL_STATE_MAXIMIZED: flags |= WindowFlag_IsMax; break;
|
|
case XDG_TOPLEVEL_STATE_ACTIVATED: flags |= WindowFlag_IsActivated; break;
|
|
case XDG_TOPLEVEL_STATE_TILED_LEFT: flags |= WindowFlag_IsTiledLeft; break;
|
|
case XDG_TOPLEVEL_STATE_TILED_RIGHT: flags |= WindowFlag_IsTiledRight; break;
|
|
case XDG_TOPLEVEL_STATE_TILED_TOP: flags |= WindowFlag_IsTiledTop; break;
|
|
case XDG_TOPLEVEL_STATE_TILED_BOTTOM: flags |= WindowFlag_IsTiledBottom; break;
|
|
case XDG_TOPLEVEL_STATE_RESIZING: flags |= WindowFlag_IsResizing; break;
|
|
case XDG_TOPLEVEL_STATE_SUSPENDED: flags |= WindowFlag_IsSuspended; break;
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT: flags |= WindowFlag_IsConstrainedLeft; break;
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT: flags |= WindowFlag_IsConstrainedRight; break;
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP: flags |= WindowFlag_IsConstrainedTop; break;
|
|
case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: flags |= WindowFlag_IsConstrainedBottom; break;
|
|
default: break;
|
|
}
|
|
}
|
|
return(flags);
|
|
}
|
|
|
|
/* os */
|
|
|
|
static int
|
|
os_resize_anonymous_file(int fd, off_t size){
|
|
#ifdef HAVE_POSIX_FALLOCATE
|
|
|
|
sigset_t mask;
|
|
sigset_t old_mask;
|
|
sigemptyset(&mask);
|
|
sigaddset(&mask, SIGALRM);
|
|
sigprocmask(SIG_BLOCK, &mask, &old_mask);
|
|
|
|
do {
|
|
errno = posix_fallocate(fd, 0, size);
|
|
} while (errno == EINTR);
|
|
sigprocmask(SIG_SETMASK, &old_mask, NULL);
|
|
|
|
int result = 0;
|
|
if (errno != 0){
|
|
result = -1;
|
|
if (errno == EINVAL || errno == EOPNOTSUPP){
|
|
if (ftruncate(fd, size) >= 0){
|
|
result = 0;
|
|
}
|
|
}
|
|
}
|
|
return(result);
|
|
|
|
#else
|
|
|
|
int result = -1;
|
|
if (ftruncate(fd, size) >= 0){
|
|
result = 0;
|
|
}
|
|
return(result);
|
|
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
os_create_anonymous_file(off_t size){
|
|
static const char key[] = "/libdecor-shared-XXXXXX";
|
|
int fd = -1;
|
|
|
|
#ifdef HAVE_MEMFD_CREATE
|
|
fd = memfd_create("libdecor", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
|
if (fd >= 0){
|
|
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
|
|
}
|
|
#endif
|
|
|
|
if (fd < 0){
|
|
const char *path = getenv("XDG_RUNTIME_DIR");
|
|
if (path == 0){
|
|
errno = ENOENT;
|
|
}
|
|
else{
|
|
char *name = malloc(strlen(path) + sizeof(key));
|
|
if (name != 0){
|
|
strcpy(name, path);
|
|
strcat(name, key);
|
|
|
|
#ifdef HAVE_MKOSTEMP
|
|
fd = mkostemp(name, O_CLOEXEC);
|
|
if (fd >= 0){
|
|
unlink(name);
|
|
}
|
|
#else
|
|
fd = mkstemp(name);
|
|
|
|
if (fd >= 0){
|
|
if (fcntl(fd, F_GETFD) == -1){
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
else if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1){
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
}
|
|
|
|
if (fd >= 0) {
|
|
unlink(name);
|
|
}
|
|
#endif
|
|
|
|
free(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fd >= 0 && os_resize_anonymous_file(fd, size) < 0){
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
|
|
return(fd);
|
|
}
|
|
|
|
/* desktop settings */
|
|
|
|
static CursorTheme
|
|
ds__get_cursor_theme_from_env(void){
|
|
CursorTheme result = {0};
|
|
char *env_xtheme = getenv("XCURSOR_THEME");
|
|
char *env_xsize = getenv("XCURSOR_SIZE");
|
|
if (env_xtheme != 0 && env_xsize != 0){
|
|
result.name = strdup(env_xtheme);
|
|
result.size = atoi(env_xsize);
|
|
}
|
|
else{
|
|
result.name = 0;
|
|
result.size = 24;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
#ifdef HAS_DBUS
|
|
#include <dbus/dbus.h>
|
|
|
|
static DBusMessage *
|
|
ds__get_setting_sync(DBusConnection *const connection, const char *key1, const char *key2){
|
|
DBusMessage *reply = 0;
|
|
DBusMessage *message =
|
|
dbus_message_new_method_call("org.freedesktop.portal.Desktop",
|
|
"/org/freedesktop/portal/desktop",
|
|
"org.freedesktop.portal.Settings",
|
|
"Read");
|
|
|
|
if (message != 0){
|
|
dbus_bool_t success = dbus_message_append_args(message,
|
|
DBUS_TYPE_STRING, &key1,
|
|
DBUS_TYPE_STRING, &key2,
|
|
DBUS_TYPE_INVALID);
|
|
if (success){
|
|
DBusError error;
|
|
dbus_error_init(&error);
|
|
reply = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error);
|
|
if (dbus_error_is_set(&error) && reply != 0){
|
|
dbus_message_unref(reply);
|
|
reply = 0;
|
|
}
|
|
dbus_error_free(&error);
|
|
}
|
|
dbus_message_unref(message);
|
|
}
|
|
|
|
return(reply);
|
|
}
|
|
|
|
static int
|
|
ds__parse_type(DBusMessage *const reply, const int type, void *value){
|
|
int result = 0;
|
|
DBusMessageIter iter[3];
|
|
dbus_message_iter_init(reply, &iter[0]);
|
|
if (dbus_message_iter_get_arg_type(&iter[0]) == DBUS_TYPE_VARIANT){
|
|
dbus_message_iter_recurse(&iter[0], &iter[1]);
|
|
if (dbus_message_iter_get_arg_type(&iter[1]) == DBUS_TYPE_VARIANT){
|
|
dbus_message_iter_recurse(&iter[1], &iter[2]);
|
|
if (dbus_message_iter_get_arg_type(&iter[2]) == type){
|
|
dbus_message_iter_get_basic(&iter[2], value);
|
|
result = 1;
|
|
}
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
static CursorTheme
|
|
ds_get_cursor_theme(void){
|
|
static const char key[] = "org.gnome.desktop.interface";
|
|
static const char key_theme[] = "cursor-theme";
|
|
static const char key_size[] = "cursor-size";
|
|
|
|
char *name = 0;
|
|
int size = 0;
|
|
int success = 0;
|
|
|
|
DBusError error;
|
|
dbus_error_init(&error);
|
|
DBusConnection *connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
|
|
|
|
if (!dbus_error_is_set(&error)){
|
|
DBusMessage *reply = ds__get_setting_sync(connection, key, key_theme);
|
|
if (reply != 0){
|
|
if (!ds__parse_type(reply, DBUS_TYPE_STRING, &name)){
|
|
name = 0;
|
|
}
|
|
dbus_message_unref(reply);
|
|
}
|
|
}
|
|
|
|
if (name != 0){
|
|
DBusMessage *reply = ds__get_setting_sync(connection, key, key_size);
|
|
if (reply){
|
|
if (ds__parse_type(reply, DBUS_TYPE_INT32, &size)){
|
|
success = 1;
|
|
}
|
|
dbus_message_unref(reply);
|
|
}
|
|
}
|
|
|
|
CursorTheme result = {0};
|
|
if (success){
|
|
result.name = name;
|
|
result.size = size;
|
|
}
|
|
else{
|
|
result = ds__get_cursor_theme_from_env();
|
|
}
|
|
|
|
return(result);
|
|
}
|
|
|
|
static ColorScheme
|
|
ds_get_color_scheme(){
|
|
static const char name[] = "org.freedesktop.appearance";
|
|
static const char key_color_scheme[] = "color-scheme";
|
|
uint32_t color = 0;
|
|
|
|
DBusError error;
|
|
dbus_error_init(&error);
|
|
DBusConnection *connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
|
|
|
|
if (!dbus_error_is_set(&error)){
|
|
DBusMessage *reply = ds__get_setting_sync(connection, name, key_color_scheme);
|
|
if (reply){
|
|
if (!ds__parse_type(reply, DBUS_TYPE_UINT32, &color)) {
|
|
color = 0;
|
|
}
|
|
dbus_message_unref(reply);
|
|
}
|
|
}
|
|
return(color);
|
|
}
|
|
|
|
#else
|
|
|
|
static CursorTheme
|
|
ds_get_cursor_them(void){
|
|
return(ds__get_cursor_theme_from_env());
|
|
}
|
|
|
|
static ColorScheme
|
|
ds_get_color_scheme(){
|
|
return(ColorScheme_Default);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* csd gtk implementation */
|
|
|
|
static void
|
|
csd_gtk_init(void){
|
|
gdk_set_allowed_backends("wayland");
|
|
gtk_disable_setlocale();
|
|
gtk_init_check(0, 0);
|
|
|
|
g_object_set(gtk_settings_get_default(),
|
|
"gtk-application-prefer-dark-theme",
|
|
(ctx.color_scheme == ColorScheme_Dark),
|
|
NULL);
|
|
|
|
{
|
|
static const int size = 128;
|
|
static const int boundary = 32;
|
|
cairo_surface_t *shadow_blur =
|
|
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size);
|
|
cairo_t *cr = cairo_create(shadow_blur);
|
|
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
|
|
cairo_set_source_rgba(cr, 0, 0, 0, 1);
|
|
cairo_rectangle(cr, boundary, boundary, size - 2*boundary, size - 2*boundary);
|
|
cairo_fill(cr);
|
|
cairo_destroy(cr);
|
|
|
|
cr_blur_surface(shadow_blur, 64);
|
|
ctx.gtk_ctx.shadow_blur = shadow_blur;
|
|
}
|
|
}
|
|
|
|
static void
|
|
csd_gtk_window_init(void){
|
|
GTK_Window *gtk_window = &ctx.gtk_window;
|
|
|
|
gtk_window->header = gtk_header_bar_new();
|
|
gtk_window->window = gtk_offscreen_window_new();
|
|
|
|
g_object_get(gtk_widget_get_settings(gtk_window->window),
|
|
"gtk-double-click-time", >k_window->double_click_time_ms,
|
|
NULL);
|
|
g_object_set(gtk_window->header,
|
|
"title", "Example Window",
|
|
"has-subtitle", FALSE,
|
|
"show-close-button", TRUE,
|
|
NULL);
|
|
|
|
GtkStyleContext *context_hdr = gtk_widget_get_style_context(gtk_window->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(gtk_window->window), gtk_window->header);
|
|
gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(gtk_window->header), TRUE);
|
|
gtk_widget_show_all(gtk_window->window);
|
|
}
|
|
|
|
static CSD_Frame
|
|
csd_gtk_calculate_frame(void){
|
|
GTK_Window *gtk_window = &ctx.gtk_window;
|
|
CSD_Frame frame = {0};
|
|
bool show_title = (!(ctx.config.flags & WindowFlag_IsFullscreen));
|
|
if (show_title){
|
|
gtk_widget_get_preferred_height(gtk_window->header, 0, &frame.border[1][0]);
|
|
gtk_header_bar_set_title(GTK_HEADER_BAR(gtk_window->header), "");
|
|
gtk_widget_get_preferred_width(gtk_window->header, 0, &frame.minbox[0]);
|
|
gtk_header_bar_set_title(GTK_HEADER_BAR(gtk_window->header), "Example Window");
|
|
}
|
|
return(frame);
|
|
}
|
|
|
|
typedef struct CSD_GTK_FindWidgetVars{
|
|
char *name;
|
|
GtkWidget *widget;
|
|
} CSD_GTK_FindWidgetVars;
|
|
|
|
static void
|
|
csd_gtk__widget_from_name__r(GtkWidget *root, void *data){
|
|
CSD_GTK_FindWidgetVars *vars = (CSD_GTK_FindWidgetVars*)data;
|
|
if (vars->widget == 0 && GTK_IS_WIDGET(root)){
|
|
GtkStyleContext *stylectx = gtk_widget_get_style_context(root);
|
|
char *style_str = gtk_style_context_to_string(stylectx, GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE);
|
|
if (strstr(style_str, vars->name) != 0){
|
|
vars->widget = root;
|
|
}
|
|
free(style_str);
|
|
}
|
|
if (vars->widget == 0 && GTK_IS_CONTAINER(root)){
|
|
gtk_container_forall(GTK_CONTAINER(root), csd_gtk__widget_from_name__r, data);
|
|
}
|
|
}
|
|
|
|
static GtkWidget*
|
|
csd_gtk__widget_from_name(GtkWidget *root, char *name){
|
|
CSD_GTK_FindWidgetVars slot = {0};
|
|
slot.name = name;
|
|
csd_gtk__widget_from_name__r(root, &slot);
|
|
return(slot.widget);
|
|
}
|
|
|
|
#define SHADOW_MARGIN 24
|
|
|
|
static void
|
|
csd_gtk_update_and_render(void){
|
|
GTK_Window *gtk_window = &ctx.gtk_window;
|
|
|
|
if (ctx.control_flags & WindowControlFlag_Resize){
|
|
static const CursorShape cursor_box[] = {
|
|
CursorShape_Resize_TopLeft, CursorShape_Resize_Top, CursorShape_Resize_TopRight,
|
|
CursorShape_Resize_Left, CursorShape_Pointer, CursorShape_Resize_Right,
|
|
CursorShape_Resize_BottomLeft, CursorShape_Resize_Bottom, CursorShape_Resize_BottomRight,
|
|
};
|
|
|
|
static const enum xdg_toplevel_resize_edge xedge_box[] = {
|
|
XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT, XDG_TOPLEVEL_RESIZE_EDGE_TOP, XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT,
|
|
XDG_TOPLEVEL_RESIZE_EDGE_LEFT, XDG_TOPLEVEL_RESIZE_EDGE_NONE, XDG_TOPLEVEL_RESIZE_EDGE_RIGHT,
|
|
XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT,
|
|
};
|
|
|
|
int l = (gtk_window->p[0] < 10);
|
|
int r = (!l && gtk_window->p[0] >= ctx.dim[0] + 10);
|
|
int t = (gtk_window->p[1] < 10);
|
|
int b = (!t && gtk_window->p[1] >= ctx.dim[1] + 10);
|
|
int loc = 3*(b - t) + (r - l);
|
|
ctx.cursor_shape = cursor_box[4 + loc];
|
|
if (ctx.gtk_window.button == BTN_LEFT){
|
|
ctx.gtk_window.button = 0;
|
|
if (loc != 0){
|
|
xdg_toplevel_resize(ctx.main_xdg_toplevel, ctx.wl_seat,
|
|
ctx.serial, xedge_box[4 + loc]);
|
|
}
|
|
else{
|
|
xdg_toplevel_move(ctx.main_xdg_toplevel, ctx.wl_seat, ctx.serial);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
CSD_SubSurface *subsurface = >k_window->shadow;
|
|
|
|
int32_t shadow_dim[2];
|
|
shadow_dim[0] = ctx.dim[0] + 2*SHADOW_MARGIN;
|
|
shadow_dim[1] = ctx.dim[1] + 2*SHADOW_MARGIN + ctx.csd_frame.border[1][0];
|
|
|
|
csd_subsurface_buffer_clear(subsurface);
|
|
csd_subsurface_buffer_alloc(subsurface, shadow_dim);
|
|
memset(subsurface->data, 0, subsurface->size);
|
|
|
|
{
|
|
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, shadow_dim[0]);
|
|
cairo_surface_t *surface =
|
|
cairo_image_surface_create_for_data(subsurface->data, CAIRO_FORMAT_ARGB32,
|
|
shadow_dim[0], shadow_dim[1], stride);
|
|
cairo_t *cr = cairo_create(surface);
|
|
cairo_surface_set_device_scale(surface, 1, 1);
|
|
|
|
cr_render_shadow(cr, ctx.gtk_ctx.shadow_blur,
|
|
-(int)SHADOW_MARGIN/2, -(int)SHADOW_MARGIN/2,
|
|
shadow_dim[0] + SHADOW_MARGIN, shadow_dim[1] + SHADOW_MARGIN,
|
|
64, 64);
|
|
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
|
|
cairo_rectangle(cr,
|
|
SHADOW_MARGIN, SHADOW_MARGIN + ctx.csd_frame.border[1][0],
|
|
ctx.dim[0], ctx.dim[1]);
|
|
cairo_fill(cr);
|
|
|
|
cairo_destroy(cr);
|
|
cairo_surface_destroy(surface);
|
|
}
|
|
|
|
csd_subsurface_set_position(subsurface, -SHADOW_MARGIN, -SHADOW_MARGIN - ctx.csd_frame.border[1][0]);
|
|
csd_subsurface_commit(subsurface);
|
|
}
|
|
|
|
{
|
|
CSD_SubSurface *subsurface = >k_window->titlebar;
|
|
|
|
int32_t title_dim[2];
|
|
title_dim[0] = ctx.dim[0];
|
|
title_dim[1] = ctx.csd_frame.border[1][0];
|
|
|
|
csd_subsurface_buffer_clear(subsurface);
|
|
csd_subsurface_buffer_alloc(subsurface, title_dim);
|
|
memset(subsurface->data, 0, subsurface->size);
|
|
|
|
{
|
|
int is_resizable = ((ctx.control_flags & WindowControlFlag_Resize) != 0);
|
|
gtk_window_set_resizable(GTK_WINDOW(gtk_window->window), is_resizable);
|
|
if (!(ctx.config.flags & WindowFlag_IsActivated)){
|
|
gtk_widget_set_state_flags(gtk_window->window, GTK_STATE_FLAG_BACKDROP, true);
|
|
}
|
|
else{
|
|
gtk_widget_unset_state_flags(gtk_window->window, GTK_STATE_FLAG_BACKDROP);
|
|
}
|
|
}
|
|
|
|
{
|
|
GtkStyleContext *style = gtk_widget_get_style_context(gtk_window->window);
|
|
if (ctx.config.flags & WindowMask_IsAnchored){
|
|
gtk_style_context_add_class(style, "maximized");
|
|
}
|
|
else{
|
|
gtk_style_context_remove_class(style, "maximized");
|
|
}
|
|
}
|
|
|
|
{
|
|
GtkAllocation allocation = { 0, 0, title_dim[0], title_dim[1]};
|
|
gtk_widget_size_allocate(gtk_window->header, &allocation);
|
|
}
|
|
|
|
{
|
|
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, title_dim[0]);
|
|
cairo_surface_t *cr_surface =
|
|
cairo_image_surface_create_for_data(subsurface->data, CAIRO_FORMAT_ARGB32,
|
|
title_dim[0], title_dim[1], stride);
|
|
cairo_t *cr = cairo_create(cr_surface);
|
|
cairo_surface_set_device_scale(cr_surface, 1, 1);
|
|
|
|
/* background */
|
|
{
|
|
GtkAllocation allocation;
|
|
gtk_widget_get_allocation(GTK_WIDGET(gtk_window->header), &allocation);
|
|
GtkStyleContext* style = gtk_widget_get_style_context(gtk_window->header);
|
|
gtk_render_background(style, cr,
|
|
allocation.x, allocation.y,
|
|
allocation.width, allocation.height);
|
|
}
|
|
|
|
/* title */
|
|
{
|
|
GtkWidget *label_title =
|
|
csd_gtk__widget_from_name(gtk_window->header, "label.title:");
|
|
|
|
GtkAllocation allocation;
|
|
gtk_widget_get_allocation(label_title, &allocation);
|
|
|
|
cairo_surface_t *cr_label_surface =
|
|
cairo_surface_create_for_rectangle(cr_surface,
|
|
allocation.x, allocation.y,
|
|
allocation.width, allocation.height);
|
|
cairo_t *cr2 = cairo_create(cr_label_surface);
|
|
gtk_widget_draw(label_title, cr2);
|
|
cairo_destroy(cr2);
|
|
cairo_surface_destroy(cr_label_surface);
|
|
}
|
|
|
|
/* buttons */
|
|
uint32_t buttons[3] = {0};
|
|
int button_count = 0;
|
|
if (ctx.control_flags & WindowControlFlag_Min){
|
|
buttons[button_count] = 0;
|
|
button_count += 1;
|
|
}
|
|
if ((ctx.control_flags & WindowControlFlag_Max) &&
|
|
(ctx.control_flags & WindowControlFlag_Resize)){
|
|
buttons[button_count] = 1;
|
|
button_count += 1;
|
|
}
|
|
if (ctx.control_flags & WindowControlFlag_Close){
|
|
buttons[button_count] = 2;
|
|
button_count += 1;
|
|
}
|
|
|
|
for (int i = 0; i < button_count; i += 1){
|
|
uint32_t button_code = buttons[i];
|
|
|
|
char *button_name = "";
|
|
char *icon_name = "";
|
|
switch (button_code){
|
|
case 0: {
|
|
button_name = ".minimize";
|
|
icon_name = "window-minimize-symbolic";
|
|
}break;
|
|
case 1: {
|
|
button_name = ".maximize";
|
|
icon_name = ((ctx.config.flags & WindowFlag_IsMax) ?
|
|
"window-restore-symbolic" : "window-maximize-symbolic");
|
|
}break;
|
|
case 2: {
|
|
button_name = ".close";
|
|
icon_name = "window-close-symbolic";
|
|
}break;
|
|
}
|
|
|
|
GtkWidget *button_widget =
|
|
csd_gtk__widget_from_name(gtk_window->header, button_name);
|
|
|
|
if (button_widget != 0){
|
|
GtkStyleContext *style = gtk_widget_get_style_context(button_widget);
|
|
|
|
/* change style based on window state and focus */
|
|
GtkStateFlags style_state = 0;
|
|
if (!(ctx.config.flags & WindowFlag_IsActivated)){
|
|
style_state |= GTK_STATE_FLAG_BACKDROP;
|
|
}
|
|
if (gtk_window->hover_button_code == button_code){
|
|
style_state |= GTK_STATE_FLAG_PRELIGHT;
|
|
if (gtk_window->active_button_code == button_code){
|
|
style_state |= GTK_STATE_FLAG_ACTIVE;
|
|
}
|
|
}
|
|
|
|
/* background */
|
|
GtkAllocation allocation;
|
|
gtk_widget_get_clip(button_widget, &allocation);
|
|
|
|
gtk_style_context_save(style);
|
|
gtk_style_context_set_state(style, style_state);
|
|
gtk_render_background(style, cr, allocation.x, allocation.y,
|
|
allocation.width, allocation.height);
|
|
gtk_render_frame(style, cr, allocation.x, allocation.y,
|
|
allocation.width, allocation.height);
|
|
gtk_style_context_restore(style);
|
|
|
|
/* get scale */
|
|
double sx, sy;
|
|
cairo_surface_get_device_scale(cr_surface, &sx, &sy);
|
|
int scale = (sx + sy)/2.0;
|
|
|
|
/* get original icon dimensions */
|
|
GtkWidget *icon_widget = gtk_bin_get_child(GTK_BIN(button_widget));
|
|
|
|
/* icon info */
|
|
gint icon_width, icon_height;
|
|
if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &icon_width, &icon_height)){
|
|
icon_width = 16;
|
|
icon_height = 16;
|
|
}
|
|
GtkIconInfo* icon_info =
|
|
gtk_icon_theme_lookup_icon_for_scale(gtk_icon_theme_get_default(),
|
|
icon_name, icon_width, scale, 0);
|
|
|
|
/* icon pixel buffer*/
|
|
gtk_style_context_save(style);
|
|
gtk_style_context_set_state(style, style_state);
|
|
GdkPixbuf *icon_pixbuf = gtk_icon_info_load_symbolic_for_context(icon_info, style, 0, 0);
|
|
cairo_surface_t *icon_surface = gdk_cairo_surface_create_from_pixbuf(icon_pixbuf, scale, 0);
|
|
gtk_style_context_restore(style);
|
|
|
|
/* dimensions and position */
|
|
gint width = 0;
|
|
gint height = 0;
|
|
gtk_style_context_get(style,
|
|
gtk_style_context_get_state(style),
|
|
"min-width", &width,
|
|
"min-height", &height, NULL);
|
|
width = ClampBot(width, icon_width);
|
|
height = ClampBot(height, icon_height);
|
|
|
|
gint left = 0;
|
|
gint top = 0;
|
|
gint right = 0;
|
|
gint bottom = 0;
|
|
|
|
GtkBorder border;
|
|
gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border);
|
|
left += border.left;
|
|
right += border.right;
|
|
top += border.top;
|
|
bottom += border.bottom;
|
|
|
|
GtkBorder padding;
|
|
gtk_style_context_get_padding(style, gtk_style_context_get_state(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);
|
|
}
|
|
}
|
|
|
|
cairo_destroy(cr);
|
|
cairo_surface_destroy(cr_surface);
|
|
}
|
|
|
|
csd_subsurface_set_position(subsurface, 0, -ctx.csd_frame.border[1][0]);
|
|
csd_subsurface_commit(subsurface);
|
|
}
|
|
}
|
|
|
|
/* cairo shadow rendering */
|
|
|
|
static int
|
|
cr_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 = sizeof(kernel)/sizeof(*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 / (sizeof(kernel)/sizeof(*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;
|
|
}
|
|
|
|
static void
|
|
cr_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);
|
|
} |