#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 #define HAS_DBUS #define HAVE_MKOSTEMP #define HAVE_MEMFD_CREATE #define HAVE_POSIX_FALLOCATE #include #include #include #include /*~ NOTE: wayland-egl.h *before* EGL includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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, CSD_U32 format){} static const struct wl_shm_listener shm_listener = { shm_format }; static void xdg_wm_base_ping(void *udata, struct xdg_wm_base *xdg_wm_base, CSD_U32 serial){ xdg_wm_base_pong(xdg_wm_base, serial); } static const struct xdg_wm_base_listener xdg_wm_base_listener = { xdg_wm_base_ping }; static void pointer_enter(void *udata, struct wl_pointer *wl_pointer, CSD_U32 serial, struct wl_surface *surface, wl_fixed_t fx, wl_fixed_t fy){ CSD_Seat *seat = &ctx.seat; CSD_Window *window = &ctx.window; CSD_GTK_Window *gtk_window = &ctx.gtk_window; CSD_S32 surface_off[2] = {0}; if (csd_gtk_calc_surface_off(gtk_window, surface, surface_off)){ // handled by gtk frame } else if (surface == window->main_wl_surface){ // app's surface_off = {0,0} } seat->hover_surface = surface; seat->serial = serial; seat->hover_p[0] = surface_off[0] + wl_fixed_to_int(fx); seat->hover_p[1] = surface_off[1] + wl_fixed_to_int(fy); } static void pointer_leave(void *udata, struct wl_pointer *wl_pointer, CSD_U32 serial, struct wl_surface *surface){ CSD_Seat *seat = &ctx.seat; seat->hover_surface = 0; seat->serial = serial; } static void pointer_motion(void *udata, struct wl_pointer *wl_pointer, CSD_U32 time, wl_fixed_t fx, wl_fixed_t fy){ CSD_Seat *seat = &ctx.seat; CSD_Window *window = &ctx.window; CSD_GTK_Window *gtk_window = &ctx.gtk_window; CSD_S32 surface_off[2] = {0}; if (csd_gtk_calc_surface_off(gtk_window, seat->hover_surface, surface_off)){ // handled by gtk frame } else if (seat->hover_surface == window->main_wl_surface){ // app's surface_off = {0,0} } seat->hover_p[0] = surface_off[0] + wl_fixed_to_int(fx); seat->hover_p[1] = surface_off[1] + wl_fixed_to_int(fy); } static void pointer_button(void *udata, struct wl_pointer *wl_pointer, CSD_U32 serial, CSD_U32 time, CSD_U32 button, CSD_U32 state){ CSD_Seat *seat = &ctx.seat; CSD_Window *window = &ctx.window; CSD_GTK_Window *gtk_window = &ctx.gtk_window; { ctx.has_event = 1; ctx.event_button = button; ctx.event_button_state = state; } seat->serial = serial; } static void pointer_axis(void *udata, struct wl_pointer *wl_pointer, CSD_U32 time, CSD_U32 axis, wl_fixed_t fv){ CSD_S32 v = wl_fixed_to_int(fv); } static 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, CSD_U32 capabilities){ CSD_Seat *seat = &ctx.seat; if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer == 0){ seat->wl_pointer = wl_seat_get_pointer(seat->wl_seat); wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, 0); } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer != 0){ wl_pointer_release(seat->wl_pointer); seat->wl_pointer = 0; } } static void seat_name(void *udata, struct wl_seat *wl_seat, const char *name){} static const struct wl_seat_listener seat_listener = { seat_capabilities, seat_name }; static void registry_global(void *udata, struct wl_registry *wl_registry, CSD_U32 name, const char *interface, CSD_U32 version){ if (strcmp(interface, "wl_compositor") == 0){ ctx.wl_compositor = (struct wl_compositor*) wl_registry_bind(wl_registry, name, &wl_compositor_interface, CSD_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, CSD_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, CSD_Min(version, 2)); } else if (strcmp(interface, "wl_seat") == 0){ CSD_Seat *seat = &ctx.seat; if (seat->wl_seat == 0){ seat->wl_seat = wl_registry_bind(ctx.wl_registry, name, &wl_seat_interface, 3); wl_seat_add_listener(seat->wl_seat, &seat_listener, 0); seat->cursor_surface = wl_compositor_create_surface(ctx.wl_compositor); } } } static void registry_global_remove(void *udata, struct wl_registry *registry, uint32_t name){} static 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){ CSD_Window *window = &ctx.window; window->config_staged.serial = serial; } static 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){ CSD_Window *window = &ctx.window; if (width != 0 && height != 0){ window->config_staged.dim[0] = width; window->config_staged.dim[1] = height; } else{ window->config_staged.dim[0] = window->config.dim[0]; window->config_staged.dim[1] = window->config.dim[1]; } window->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){} static 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){ CSD_Window *window = &ctx.window; window->config_staged.decoration_mode = mode; } static const struct zxdg_toplevel_decoration_v1_listener zxdg_toplevel_decoration_listener = { xdg_toplevel_decoration_configure }; int main(){ /* desktop settings */ ctx.color_scheme = csd_desktop_get_color_scheme(); ctx.cursor_theme = csd_desktop_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(CSD_CursorShape_Pointer, "left_ptr"); X(CSD_CursorShape_Resize_Top, "top_side"); X(CSD_CursorShape_Resize_Bottom, "bottom_side"); X(CSD_CursorShape_Resize_Left, "left_side"); X(CSD_CursorShape_Resize_Right, "right_side"); X(CSD_CursorShape_Resize_TopLeft, "top_left_corner"); X(CSD_CursorShape_Resize_BottomLeft, "bottom_left_corner"); X(CSD_CursorShape_Resize_TopRight, "top_right_corner"); X(CSD_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 */ csd_window_init(&ctx.window); /* window gtk */ csd_gtk_window_init(&ctx.gtk_window); } /* window egl */ { CSD_Window *window = &ctx.window; ctx.main_wl_egl_window = wl_egl_window_create(window->main_wl_surface, window->dim[0], window->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); } } /* setup seats list for frame */ CSD_Seat *seats[] = {&ctx.seat}; CSD_U32 seat_count = 1; for (CSD_U32 si = 0; si < seat_count; si += 1){ seats[si]->cursor_shape = CSD_CursorShape_Hidden; } /* apply new window config */ csd_window_apply_new_config(&ctx.window); /* calculate csd frame */ CSD_Frame csd_frame = csd_gtk_calc_frame(&ctx.window, &ctx.gtk_window); /* window size update */ csd_window_size_update(&ctx.window, &csd_frame); /* egl size update */ wl_egl_window_resize(ctx.main_wl_egl_window, ctx.window.dim[0], ctx.window.dim[1], 0, 0); /* frame update and render */ csd_gtk_update_and_render(&ctx.window, &ctx.gtk_window, seats, seat_count); /* app update & render */ { CSD_Window *window = &ctx.window; if (ctx.close_signal){ exit_loop = 1; } else if (ctx.minimize_signal){ ctx.minimize_signal = 0; xdg_toplevel_set_minimized(window->main_xdg_toplevel); } else if (ctx.maximize_signal){ ctx.maximize_signal = 0; if (window->config.flags & CSD_WindowFlag_IsMax){ xdg_toplevel_unset_maximized(window->main_xdg_toplevel); } else{ xdg_toplevel_set_maximized(window->main_xdg_toplevel); } } glDrawBuffer(GL_BACK); glViewport(0, 0, window->dim[0], window->dim[1]); glClearColor(0.40f, 0.90f, 0.15f, 1.f); glClear(GL_COLOR_BUFFER_BIT); } { CSD_Window *window = &ctx.window; eglSwapBuffers(ctx.egl_display, ctx.main_egl_surface); wl_surface_commit(window->main_wl_surface); } /* commit new cursor */ { CSD_Seat *seat = &ctx.seat; struct wl_cursor *cursor = ctx.wl_cursors[seat->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(seat->cursor_surface, 1); wl_surface_attach(seat->cursor_surface, cursor_buffer, 0, 0); wl_surface_damage_buffer(seat->cursor_surface, 0, 0, cursor_image->width, cursor_image->height); wl_surface_commit(seat->cursor_surface); wl_pointer_set_cursor(seat->wl_pointer, seat->serial, seat->cursor_surface, cursor_image->hotspot_x, cursor_image->hotspot_y); } } } return(0); } /* csd helpers */ static void csd_window_init(CSD_Window *window){ window->main_wl_surface = wl_compositor_create_surface(ctx.wl_compositor); window->main_xdg_surface = xdg_wm_base_get_xdg_surface(ctx.xdg_wm_base, window->main_wl_surface); xdg_surface_add_listener(window->main_xdg_surface, &xdg_surface_listener, 0); window->main_xdg_toplevel = xdg_surface_get_toplevel(window->main_xdg_surface); xdg_toplevel_add_listener(window->main_xdg_toplevel, &xdg_toplevel_listener, 0); window->control_flags = ~0; window->dim[0] = 640; window->dim[1] = 480; window->config_staged.decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; if (ctx.zxdg_decoration_manager != 0){ window->main_zxdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(ctx.zxdg_decoration_manager, window->main_xdg_toplevel); zxdg_toplevel_decoration_v1_add_listener(window->main_zxdg_toplevel_decoration, &zxdg_toplevel_decoration_listener, 0); } for (int k = 0; k < 2; k += 1){ window->mmbox[k][0] = 1; window->mmbox[k][1] = (1 << 30); } xdg_toplevel_set_app_id(window->main_xdg_toplevel, "demo"); xdg_toplevel_set_title(window->main_xdg_toplevel, "Example Window"); wl_surface_commit(window->main_wl_surface); } static void csd_window_apply_new_config(CSD_Window *window){ if (window->config.serial != window->config_staged.serial){ window->config = window->config_staged; xdg_surface_ack_configure(window->main_xdg_surface, window->config.serial); } } static void csd_window_size_update(CSD_Window *window, CSD_Frame *csd_frame){ /* window sizing */ memset(&window->csd_frame, 0, sizeof window->csd_frame); memset(window->csd_dim, 0, sizeof window->csd_dim); if (window->config.decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE){ window->csd_frame = *csd_frame; for (CSD_U32 i = 0; i < 2; i += 1){ window->csd_dim[i] = (csd_frame->border[i][0] + csd_frame->border[i][1]); } } for (CSD_U32 i = 0; i < 2; i += 1){ int32_t d = window->config.dim[i] - window->csd_dim[i]; if (!window->handled_first_size){ d = window->dim[i]; window->config.dim[i] = d + window->csd_dim[i]; } d = CSD_ClampBot(d, window->mmbox[i][0]); d = CSD_ClampTop(d, window->mmbox[i][1]); d = CSD_ClampBot(d, window->csd_frame.minbox[i]); window->dim[i] = d; } window->handled_first_size = 1; /* window size commit */ if (!(window->control_flags & CSD_WindowControlFlag_Resize)){ xdg_toplevel_set_min_size(window->main_xdg_toplevel, window->dim[0], window->dim[1]); xdg_toplevel_set_max_size(window->main_xdg_toplevel, window->dim[0], window->dim[1]); } else{ for (CSD_S32 i = 0; i < 2; i += 1){ CSD_S32 m[2]; for (CSD_S32 k = 0; k < 2; k += 1){ m[k] = window->mmbox[k][i]; if (i == 0){ m[k] = CSD_ClampBot(m[k], window->csd_frame.minbox[k]); } m[k] += window->csd_dim[k]; } if (i == 0){ xdg_toplevel_set_min_size(window->main_xdg_toplevel, m[0], m[1]); } else{ xdg_toplevel_set_max_size(window->main_xdg_toplevel, m[0], m[1]); } } } xdg_surface_set_window_geometry(window->main_xdg_surface, -window->csd_frame.border[0][0], -window->csd_frame.border[1][0], window->dim[0] + window->csd_dim[0], window->dim[1] + window->csd_dim[1]); } static CSD_SubSurface csd_subsurface_new(void){ CSD_Window *window = &ctx.window; 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, window->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, CSD_S32 dim[2]){ int stride = 4*dim[0]; uint64_t size = 4*dim[0]*dim[1]; int fd = csd_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, CSD_S32 x, CSD_S32 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 CSD_WindowFlags csd_window_flags_from_states_array(struct wl_array *states){ CSD_WindowFlags flags = 0; uint32_t *p; wl_array_for_each(p, states){ switch (*p) { case XDG_TOPLEVEL_STATE_FULLSCREEN: flags |= CSD_WindowFlag_IsFullscreen; break; case XDG_TOPLEVEL_STATE_MAXIMIZED: flags |= CSD_WindowFlag_IsMax; break; case XDG_TOPLEVEL_STATE_ACTIVATED: flags |= CSD_WindowFlag_IsActivated; break; case XDG_TOPLEVEL_STATE_TILED_LEFT: flags |= CSD_WindowFlag_IsTiledLeft; break; case XDG_TOPLEVEL_STATE_TILED_RIGHT: flags |= CSD_WindowFlag_IsTiledRight; break; case XDG_TOPLEVEL_STATE_TILED_TOP: flags |= CSD_WindowFlag_IsTiledTop; break; case XDG_TOPLEVEL_STATE_TILED_BOTTOM: flags |= CSD_WindowFlag_IsTiledBottom; break; case XDG_TOPLEVEL_STATE_RESIZING: flags |= CSD_WindowFlag_IsResizing; break; case XDG_TOPLEVEL_STATE_SUSPENDED: flags |= CSD_WindowFlag_IsSuspended; break; case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT: flags |= CSD_WindowFlag_IsConstrainedLeft; break; case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT: flags |= CSD_WindowFlag_IsConstrainedRight; break; case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP: flags |= CSD_WindowFlag_IsConstrainedTop; break; case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: flags |= CSD_WindowFlag_IsConstrainedBottom; break; default: break; } } return(flags); } /* os */ static int csd_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 csd_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 && csd_os_resize_anonymous_file(fd, size) < 0){ close(fd); fd = -1; } return(fd); } /* desktop settings */ static CSD_CursorTheme csd_desktop_get_cursor_theme_from_env(void){ CSD_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 static DBusMessage * csd_desktop__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 csd_desktop__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 CSD_CursorTheme csd_desktop_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 = csd_desktop__get_setting_sync(connection, key, key_theme); if (reply != 0){ if (!csd_desktop__parse_type(reply, DBUS_TYPE_STRING, &name)){ name = 0; } dbus_message_unref(reply); } } if (name != 0){ DBusMessage *reply = csd_desktop__get_setting_sync(connection, key, key_size); if (reply){ if (csd_desktop__parse_type(reply, DBUS_TYPE_INT32, &size)){ success = 1; } dbus_message_unref(reply); } } CSD_CursorTheme result = {0}; if (success){ result.name = name; result.size = size; } else{ result = csd_desktop_get_cursor_theme_from_env(); } return(result); } static CSD_ColorScheme csd_desktop_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 = csd_desktop__get_setting_sync(connection, name, key_color_scheme); if (reply){ if (!csd_desktop__parse_type(reply, DBUS_TYPE_UINT32, &color)) { color = 0; } dbus_message_unref(reply); } } return(color); } #else static CSD_CursorTheme csd_desktop_get_cursor_theme(void){ return(csd_desktop_get_cursor_theme_from_env()); } static CSD_ColorScheme csd_desktop_get_color_scheme(){ return(CSD_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 == CSD_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); csd_gtk_blur_surface(shadow_blur, 64); ctx.gtk_ctx.shadow_blur = shadow_blur; } } static void csd_gtk_window_init(CSD_GTK_Window *gtk_window){ gtk_window->shadow = csd_subsurface_new(); gtk_window->titlebar = csd_subsurface_new(); 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_calc_frame(CSD_Window *window, CSD_GTK_Window *gtk_window){ CSD_Frame frame = {0}; CSD_B32 show_title = (!(window->config.flags & CSD_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); } static CSD_B32 csd_gtk_calc_surface_off(CSD_GTK_Window *gtk_window, struct wl_surface *surface, CSD_S32 *off){ CSD_B32 result = 0; if (surface == gtk_window->titlebar.wl_surface){ memcpy(off, gtk_window->titlebar.p, sizeof(gtk_window->titlebar.p)); result = 1; } else if (surface == gtk_window->shadow.wl_surface){ memcpy(off, gtk_window->shadow.p, sizeof(gtk_window->shadow.p)); result = 1; } return(result); } 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(CSD_Window *window, CSD_GTK_Window *gtk_window, CSD_Seat **seats, CSD_U32 seat_count){ /* setup buttons */ enum{ HEADER_BUTTON_NULL, HEADER_BUTTON_MIN, HEADER_BUTTON_MAX, HEADER_BUTTON_CLOSE, HEADER_BUTTON_COUNT }; static char* header_button_name[] = { 0, ".minimize", ".maximize", ".close" }; CSD_B32 header_button_active[] = { /*NULL*/ 0, /*MIN*/ (window->control_flags & CSD_WindowControlFlag_Min), /*MAX*/ ((window->control_flags & CSD_WindowControlFlag_Max) && (window->control_flags & CSD_WindowControlFlag_Resize)), /*CLOSE*/ (window->control_flags & CSD_WindowControlFlag_Close) }; /* per-cursor locals */ enum{ LOCATION_NULL, LOCATION_SHADOW, LOCATION_TITLEBAR, }; struct CursorVars{ CSD_U32 loc; CSD_S32 shadow_loc; CSD_U32 header_button_hover; } CursorVars; struct CursorVars *cursors = alloca(sizeof(struct CursorVars)*seat_count); memset(cursors, 0, sizeof(struct CursorVars)*seat_count); /* determine cursor location information */ for (CSD_U32 si = 0; si < seat_count; si += 1){ CSD_Seat *seat = seats[si]; if (seat->hover_surface == window->main_wl_surface || seat->hover_surface == gtk_window->titlebar.wl_surface || seat->hover_surface == gtk_window->shadow.wl_surface){ CSD_B32 l = (seat->hover_p[0] < 0); CSD_B32 r = (!l && seat->hover_p[0] >= window->dim[0]); CSD_B32 t = (seat->hover_p[1] < -window->csd_frame.border[1][0]); CSD_B32 b = (!t && seat->hover_p[1] >= window->dim[1]); cursors[si].shadow_loc = 3*(b - t) + (r - l); if (cursors[si].shadow_loc != 0){ cursors[si].loc = LOCATION_SHADOW; } else if (seat->hover_p[1] < 0){ cursors[si].loc = LOCATION_TITLEBAR; } } } /* draw shadow */ { CSD_SubSurface *subsurface = >k_window->shadow; CSD_S32 shadow_dim[2]; shadow_dim[0] = window->dim[0] + 2*SHADOW_MARGIN; shadow_dim[1] = (window->dim[1] + 2*SHADOW_MARGIN + window->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); csd_gtk_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 + window->csd_frame.border[1][0], window->dim[0], window->dim[1]); cairo_fill(cr); cairo_destroy(cr); cairo_surface_destroy(surface); } csd_subsurface_set_position(subsurface, -SHADOW_MARGIN, -SHADOW_MARGIN - window->csd_frame.border[1][0]); csd_subsurface_commit(subsurface); } /* draw frame */ { CSD_SubSurface *subsurface = >k_window->titlebar; CSD_S32 title_dim[2]; title_dim[0] = window->dim[0]; title_dim[1] = window->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 = ((window->control_flags & CSD_WindowControlFlag_Resize) != 0); gtk_window_set_resizable(GTK_WINDOW(gtk_window->window), is_resizable); if (!(window->config.flags & CSD_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 (window->config.flags & CSD_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); } { CSD_S32 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); /* draw 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); } /* draw 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); } /* draw buttons */ for (CSD_U32 i = 1; i < HEADER_BUTTON_COUNT; i += 1){ if (header_button_active[i]){ char *icon_name = ""; switch (i){ case HEADER_BUTTON_MIN: { icon_name = "window-minimize-symbolic"; }break; case HEADER_BUTTON_MAX: { icon_name = ((window->config.flags & CSD_WindowFlag_IsMax) ? "window-restore-symbolic" : "window-maximize-symbolic"); }break; case HEADER_BUTTON_CLOSE: { icon_name = "window-close-symbolic"; }break; } GtkWidget *widget = csd_gtk__widget_from_name(gtk_window->header, header_button_name[i]); if (widget != 0){ /* layout */ GtkAllocation rect; gtk_widget_get_clip(widget, &rect); CSD_S32 x0 = rect.x; CSD_S32 y0 = rect.y - title_dim[1]; CSD_S32 x1 = rect.x + rect.width; CSD_S32 y1 = rect.y + rect.height - title_dim[1]; /* check pointer hover */ CSD_B32 is_hovered = 0; for (CSD_U32 si = 0; si < seat_count; si += 1){ if (cursors[si].loc == LOCATION_TITLEBAR){ CSD_Seat *seat = seats[si]; if (x0 <= seat->hover_p[0] && seat->hover_p[0] < x1 && y0 <= seat->hover_p[1] && seat->hover_p[1] < y1){ cursors[si].header_button_hover = i; is_hovered = 1; } } } /* change style based on window state and focus */ GtkStyleContext *style = gtk_widget_get_style_context(widget); GtkStateFlags style_state = 0; if (!(window->config.flags & CSD_WindowFlag_IsActivated)){ style_state |= GTK_STATE_FLAG_BACKDROP; } if (is_hovered){ style_state |= GTK_STATE_FLAG_PRELIGHT; if (i == gtk_window->active_header_button){ style_state |= GTK_STATE_FLAG_ACTIVE; } } /* background */ gtk_style_context_save(style); gtk_style_context_set_state(style, style_state); gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height); gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.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(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 = CSD_ClampBot(width, icon_width); height = CSD_ClampBot(height, icon_height); GtkBorder border; gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border); GtkBorder padding; gtk_style_context_get_padding(style, gtk_style_context_get_state(style), &padding); gint left = border.left + padding.left; gint right = border.right + padding.right; gint top = border.top + padding.top; gint bottom = border.bottom + padding.bottom; width += left + right; height += top + bottom; gtk_render_icon_surface(gtk_widget_get_style_context(icon_widget), cr, icon_surface, rect.x + (width - icon_width)/2, rect.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, -window->csd_frame.border[1][0]); csd_subsurface_commit(subsurface); } /* process events */ if (ctx.has_event){ /* handle left-button press */ if (ctx.event_button == BTN_LEFT && ctx.event_button_state == 1){ CSD_U32 si = 0; switch (cursors[si].loc){ default: case LOCATION_NULL: break; case LOCATION_SHADOW: { #define X(N) XDG_TOPLEVEL_RESIZE_EDGE_##N static const enum xdg_toplevel_resize_edge xedge_box[] = { X(TOP_LEFT), X(TOP), X(TOP_RIGHT), X(LEFT), X(NONE), X(RIGHT), X(BOTTOM_LEFT), X(BOTTOM), X(BOTTOM_RIGHT), }; #undef X xdg_toplevel_resize(window->main_xdg_toplevel, seats[si]->wl_seat, seats[si]->serial, xedge_box[4 + cursors[si].shadow_loc]); ctx.has_event = 0; }break; case LOCATION_TITLEBAR: { if (cursors[si].header_button_hover == 0){ xdg_toplevel_move(window->main_xdg_toplevel, seats[si]->wl_seat, seats[si]->serial); } else{ gtk_window->active_header_button = cursors[si].header_button_hover; } ctx.has_event = 0; }break; } } /* handle left-button release */ if (ctx.event_button == BTN_LEFT && ctx.event_button_state == 0){ CSD_U32 si = 0; if (gtk_window->active_header_button != 0 && gtk_window->active_header_button == cursors[si].header_button_hover){ switch (gtk_window->active_header_button){ case HEADER_BUTTON_MIN: { ctx.minimize_signal = 1; } break; case HEADER_BUTTON_MAX: { ctx.maximize_signal = 1; } break; case HEADER_BUTTON_CLOSE: { ctx.close_signal = 1; } break; } gtk_window->active_header_button = 0; } } } /* set cursor shape */ for (CSD_U32 si = 0; si < seat_count; si += 1){ if (window->control_flags & CSD_WindowControlFlag_Resize){ #define X(N) CSD_CursorShape_##N static const CSD_CursorShape cursor_box[] = { X(Resize_TopLeft), X(Resize_Top), X(Resize_TopRight), X(Resize_Left), X(Pointer), X(Resize_Right), X(Resize_BottomLeft), X(Resize_Bottom), X(Resize_BottomRight), }; #undef X seats[si]->cursor_shape = cursor_box[4 + cursors[si].shadow_loc]; } } } /* cairo shadow rendering */ static int csd_gtk_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 csd_gtk_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); }