kick/tabbed-windows.c
Andrew Hamilton ca40ec06ad Composite client sub-surfaces (CSD) in the EGL and shm render paths
Track sub-surface roles and positions via the wl_subcompositor so foot's
client-side decorations render: toplevel -> titlebar -> controls. Draw the
active surface's buffer at the window-geometry offset and its sub-surfaces
recursively on top, clipping to the window. Sub-surface commits of the active
tab trigger redraws but never resize the host window.
2026-08-06 18:43:35 +10:00

2862 lines
106 KiB
C

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <time.h>
#include <execinfo.h>
#include <poll.h>
#include <wayland-server.h>
#include <wayland-server-protocol.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <wayland-egl.h>
#include "xdg-shell-server.h"
#include "xdg-shell-client.h"
#include "linux-dmabuf-server.h"
#include "wl-drm-server.h"
#include <xkbcommon/xkbcommon.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#define MAX_APPS 8
#define MAX_WIN_W 2560
#define MAX_WIN_H 1600
struct tabbed_surface;
struct tabbed_compositor;
struct tabbed_pool {
int fd;
size_t size;
unsigned char *data;
int refs;
bool destroyed;
};
struct tabbed_buffer {
struct wl_resource *resource;
struct wl_list buffer_link;
/* shm path */
struct tabbed_pool *pool;
int32_t offset;
int32_t width, height, stride;
uint32_t format;
/* dmabuf path */
bool is_dmabuf;
int n_planes;
int fd[4];
uint32_t dma_offset[4];
uint32_t dma_stride[4];
uint32_t dma_mod_hi[4];
uint32_t dma_mod_lo[4];
};
struct tabbed_output {
struct wl_global *global;
struct wl_list resources;
int width, height;
unsigned char *data;
size_t stride;
};
struct tabbed_surface {
struct wl_resource *resource;
struct wl_list link;
struct tabbed_buffer *buffer;
struct tabbed_buffer *pending_release;
bool has_buffer;
struct wl_listener surface_destroy_listener;
struct wl_resource *xdg_surface;
struct wl_resource *xdg_toplevel;
char *title;
char *app_id;
bool configured;
uint32_t configure_serial;
struct wl_resource *frame_callback;
struct wl_client *client;
int tab_index;
/* shm cached pixels */
unsigned char *pixels;
int pixel_width, pixel_height, pixel_stride;
/* GPU texture (dmabuf imported, or shm uploaded at render) */
GLuint tex;
EGLImageKHR egl_image;
bool has_tex;
int tex_w, tex_h;
/* window geometry (CSD shadow handling) */
int geom_x, geom_y, geom_width, geom_height;
/* sub-surface role: when is_subsurface, sub_parent owns this surface and
sub_x/sub_y is our position in the parent's surface-local coords */
bool is_subsurface;
struct tabbed_surface *sub_parent;
struct wl_list sub_link;
struct wl_list sub_children;
int sub_x, sub_y;
struct wl_listener subsurface_destroy_listener;
};
struct tabbed_app {
char *cmd;
pid_t pid;
struct tabbed_surface *surface;
};
struct tabbed_compositor {
struct wl_display *display;
struct wl_event_loop *loop;
struct wl_list surfaces;
struct wl_list buffers;
struct tabbed_output output;
struct tabbed_surface *active_surface;
struct tabbed_app *apps[MAX_APPS];
int num_apps;
struct wl_list input_tracks;
uint32_t serial;
bool clients_launched;
/* xkb keymap shared with our clients */
struct xkb_context *xkb_ctx;
struct xkb_keymap *xkb_keymap;
char *xkb_keymap_str;
int xkb_keymap_fd;
int xkb_keymap_size;
/* keyboard focus */
struct tabbed_surface *kbd_focus;
struct wl_resource *kbd_resource;
uint32_t input_serial;
/* pointer tracking */
bool pointer_entered;
double pointer_x, pointer_y;
};
struct input_track {
struct wl_client *client;
struct wl_resource *kbd;
struct wl_resource *ptr;
struct wl_listener kbd_destroy;
struct wl_listener ptr_destroy;
struct wl_list link;
};
/* ===================== Host-side (nested) state ===================== */
struct tabbed_host {
struct wl_display *display;
struct wl_registry *registry;
struct wl_compositor *compositor;
struct wl_surface *surface;
struct xdg_wm_base *wm_base;
struct xdg_surface *xdg_surface;
struct xdg_toplevel *toplevel;
struct wl_seat *seat;
struct wl_keyboard *keyboard;
struct wl_pointer *pointer;
struct wl_shm *shm;
struct wl_shm_pool *shm_pool;
struct wl_buffer *shm_buffer;
void *shm_data;
size_t shm_size;
bool configured;
/* host window size has been set once from client content; further
client commits no longer resize the host (labwc drives it) */
bool client_sized;
int width, height;
/* EGL */
bool egl_ok;
EGLDisplay egl_display;
EGLConfig egl_config;
EGLContext egl_context;
EGLSurface egl_surface;
struct wl_egl_window *egl_window;
/* host modifiers (for tab-switch detection) */
uint32_t host_mods_depressed;
/* GL program */
GLuint gl_program;
GLint gl_pos_loc, gl_uv_loc, gl_tex_loc;
};
static struct tabbed_compositor compositor;
static struct tabbed_host host;
static char tabbed_socket[64];
static bool needs_redraw;
static int drm_fd = -1;
/* Forward declarations */
static const struct wl_compositor_interface compositor_impl;
static const struct wl_surface_interface surface_impl;
static const struct wl_region_interface region_impl;
static const struct wl_shm_interface shm_impl;
static const struct wl_shm_pool_interface pool_impl;
static const struct xdg_wm_base_interface xdg_wm_base_impl;
static const struct xdg_surface_interface xdg_surface_impl;
static const struct xdg_toplevel_interface xdg_toplevel_impl;
static const struct xdg_popup_interface xdg_popup_impl;
static const struct wl_seat_interface seat_impl;
static const struct wl_subcompositor_interface subcompositor_impl;
static const struct wl_data_device_manager_interface data_device_manager_impl;
static const struct wl_pointer_interface pointer_impl;
static const struct wl_keyboard_interface keyboard_impl;
static const struct wl_touch_interface touch_impl;
static const struct wl_subsurface_interface subsurface_impl;
static const struct wl_buffer_interface buffer_impl;
static const struct xdg_positioner_interface positioner_impl;
static const struct zwp_linux_dmabuf_v1_interface dmabuf_impl;
static const struct zwp_linux_buffer_params_v1_interface dmabuf_params_impl;
static const struct wl_drm_interface drm_impl;
static void launch_client(int idx);
static void set_active_surface(struct tabbed_surface *surface);
static void schedule_redraw(void);
static void host_resize(int width, int height);
static void fit_host_window(void);
static void dmabuf_get_feedback(struct wl_client *client,
struct wl_resource *resource,
uint32_t id);
static void dmabuf_get_surface_feedback(struct wl_client *client,
struct wl_resource *resource,
uint32_t id,
struct wl_resource *surface);
/* ===================== EGL helpers ===================== */
static PFNEGLCREATEIMAGEKHRPROC pfn_eglCreateImageKHR;
static PFNEGLDESTROYIMAGEKHRPROC pfn_eglDestroyImageKHR;
static PFNEGLGETPLATFORMDISPLAYEXTPROC pfn_eglGetPlatformDisplayEXT;
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC pfn_glEGLImageTargetTexture2DOES;
static void *egl_proc(const char *name) {
void *p = (void *)eglGetProcAddress(name);
if (!p) fprintf(stderr, "tabbed-windows: EGL proc %s unavailable\n", name);
return p;
}
/* ===================== surface destroy callback ===================== */
static void surface_destroyed_cb(struct wl_listener *listener, void *data) {
(void)data;
struct tabbed_surface *surface = wl_container_of(listener, surface, surface_destroy_listener);
/* unlink from parent if we are a sub-surface */
if (surface->sub_parent) {
wl_list_remove(&surface->sub_link);
surface->sub_parent = NULL;
surface->is_subsurface = false;
}
/* orphan any children we own */
struct tabbed_surface *child, *tmp;
wl_list_for_each_safe(child, tmp, &surface->sub_children, sub_link) {
wl_list_remove(&child->sub_link);
child->sub_parent = NULL;
child->is_subsurface = false;
}
wl_list_remove(&surface->link);
if (compositor.active_surface == surface)
compositor.active_surface = NULL;
if (compositor.kbd_focus == surface)
compositor.kbd_focus = NULL;
if (surface->pending_release)
wl_buffer_send_release(surface->pending_release->resource);
if (host.egl_ok) {
if (surface->tex)
glDeleteTextures(1, &surface->tex);
if (surface->egl_image)
pfn_eglDestroyImageKHR(host.egl_display, surface->egl_image);
}
free(surface->title);
free(surface->app_id);
free(surface->pixels);
free(surface);
}
/* ===================== wl_compositor ===================== */
static void compositor_create_surface(struct wl_client *client,
struct wl_resource *resource,
uint32_t id)
{
struct wl_resource *surf = wl_resource_create(client,
&wl_surface_interface, wl_resource_get_version(resource), id);
if (!surf) { wl_client_post_no_memory(client); return; }
struct tabbed_surface *surface = calloc(1, sizeof(*surface));
if (!surface) { wl_resource_destroy(surf); return; }
surface->resource = surf;
surface->client = client;
wl_resource_set_user_data(surf, surface);
wl_resource_set_implementation(surf, &surface_impl, surface, NULL);
surface->surface_destroy_listener.notify = surface_destroyed_cb;
wl_resource_add_destroy_listener(surf, &surface->surface_destroy_listener);
wl_list_init(&surface->sub_children);
wl_list_insert(&compositor.surfaces, &surface->link);
}
static void compositor_create_region(struct wl_client *client,
struct wl_resource *resource,
uint32_t id)
{
struct wl_resource *reg = wl_resource_create(client,
&wl_region_interface, wl_resource_get_version(resource), id);
if (!reg) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(reg, &region_impl, NULL, NULL);
}
static const struct wl_compositor_interface compositor_impl = {
.create_surface = compositor_create_surface,
.create_region = compositor_create_region,
};
/* ===================== wl_surface ===================== */
static void surface_destroy(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static void surface_attach(struct wl_client *client, struct wl_resource *resource,
struct wl_resource *buffer, int32_t x, int32_t y)
{
(void)client; (void)x; (void)y;
struct tabbed_surface *surface = wl_resource_get_user_data(resource);
if (buffer) {
struct tabbed_buffer *b = wl_resource_get_user_data(buffer);
if (b) {
surface->buffer = b;
surface->has_buffer = true;
} else {
surface->buffer = NULL;
surface->has_buffer = false;
}
} else {
surface->buffer = NULL;
surface->has_buffer = false;
}
}
static void surface_damage(struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y, int32_t w, int32_t h)
{
(void)client; (void)resource; (void)x; (void)y; (void)w; (void)h;
}
static void surface_frame(struct wl_client *client, struct wl_resource *resource,
uint32_t callback)
{
struct tabbed_surface *surface = wl_resource_get_user_data(resource);
if (surface->frame_callback) {
wl_resource_destroy(surface->frame_callback);
surface->frame_callback = NULL;
}
surface->frame_callback = wl_resource_create(client,
&wl_callback_interface, wl_resource_get_version(resource), callback);
if (!surface->frame_callback)
wl_client_post_no_memory(client);
}
static void surface_set_opaque_region(struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *region)
{
(void)client; (void)resource; (void)region;
}
static void surface_set_input_region(struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *region)
{
(void)client; (void)resource; (void)region;
}
/* Compute the source rectangle (geometry rect clamped to buffer) to blit
from the buffer/texture, mirroring tabbed-wayland's margin handling. */
static void content_rect(struct tabbed_surface *surface, int *x, int *y,
int *w, int *h)
{
int bw = surface->tex_w ? surface->tex_w : surface->pixel_width;
int bh = surface->tex_h ? surface->tex_h : surface->pixel_height;
int src_x = 0, src_y = 0, src_w = bw, src_h = bh;
if (surface->geom_width > 0 && surface->geom_height > 0) {
src_x = (surface->geom_x > 0) ? surface->geom_x : 0;
src_y = (surface->geom_y > 0) ? surface->geom_y : 0;
src_w = surface->geom_width;
src_h = surface->geom_height;
if (src_x >= bw) src_x = bw - 1;
if (src_y >= bh) src_y = bh - 1;
if (src_w > bw - src_x) src_w = bw - src_x;
if (src_h > bh - src_y) src_h = bh - src_y;
}
*x = src_x; *y = src_y; *w = src_w; *h = src_h;
}
static void send_frame_done(struct tabbed_surface *surface) {
if (!surface->frame_callback) return;
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
uint32_t ms = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
wl_callback_send_done(surface->frame_callback, ms);
wl_resource_destroy(surface->frame_callback);
surface->frame_callback = NULL;
}
static bool ensure_texture(struct tabbed_surface *s, int w, int h) {
if (!host.egl_ok) return false;
if (!s->tex) {
glGenTextures(1, &s->tex);
if (!s->tex) return false;
glBindTexture(GL_TEXTURE_2D, s->tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
glBindTexture(GL_TEXTURE_2D, s->tex);
s->tex_w = w;
s->tex_h = h;
return true;
}
static bool import_dmabuf(struct tabbed_surface *s, struct tabbed_buffer *buf) {
if (!host.egl_ok || !pfn_eglCreateImageKHR || !pfn_glEGLImageTargetTexture2DOES)
return false;
static int nimport;
fprintf(stderr, "[import] dmabuf %ux%u fmt=%x planes=%d (n=%d)\n",
buf->width, buf->height, buf->format, buf->n_planes, ++nimport);
if (s->egl_image) {
pfn_eglDestroyImageKHR(host.egl_display, s->egl_image);
s->egl_image = EGL_NO_IMAGE;
}
s->has_tex = false;
EGLint attrs[64];
int n = 0;
attrs[n++] = EGL_WIDTH; attrs[n++] = buf->width;
attrs[n++] = EGL_HEIGHT; attrs[n++] = buf->height;
attrs[n++] = EGL_LINUX_DRM_FOURCC_EXT; attrs[n++] = (EGLint)buf->format;
for (int i = 0; i < buf->n_planes && i < 4; i++) {
attrs[n++] = EGL_DMA_BUF_PLANE0_FD_EXT + i * 3; attrs[n++] = (EGLint)buf->fd[i];
attrs[n++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT + i * 3; attrs[n++] = (EGLint)buf->dma_offset[i];
attrs[n++] = EGL_DMA_BUF_PLANE0_PITCH_EXT + i * 3; attrs[n++] = (EGLint)buf->dma_stride[i];
uint32_t mhi = buf->dma_mod_hi[i], mlo = buf->dma_mod_lo[i];
bool invalid = (mhi == 0xffffffffu && mlo == 0xffffffffu);
bool set = (mhi || mlo) && !invalid;
if (set) {
attrs[n++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT + i * 3; attrs[n++] = (EGLint)mlo;
attrs[n++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT + i * 3; attrs[n++] = (EGLint)mhi;
}
}
attrs[n++] = EGL_NONE;
EGLImageKHR img = pfn_eglCreateImageKHR(host.egl_display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, attrs);
if (img == EGL_NO_IMAGE) {
fprintf(stderr, "tabbed-windows: dmabuf import failed (fourcc %x)\n",
buf->format);
return false;
}
s->egl_image = img;
if (!ensure_texture(s, buf->width, buf->height))
return false;
pfn_glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, img);
s->has_tex = true;
s->tex_w = buf->width;
s->tex_h = buf->height;
return true;
}
static void surface_commit(struct wl_client *client, struct wl_resource *resource) {
(void)client;
struct tabbed_surface *surface = wl_resource_get_user_data(resource);
if (!surface) return;
/* xdg_shell: first commit without buffer sends configure */
if (surface->xdg_toplevel && !surface->configured) {
surface->configured = true;
surface->configure_serial = ++compositor.serial;
if (surface->xdg_surface)
xdg_surface_send_configure(surface->xdg_surface, surface->configure_serial);
struct wl_array states;
wl_array_init(&states);
uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
if (s) *s = XDG_TOPLEVEL_STATE_ACTIVATED;
xdg_toplevel_send_configure(surface->xdg_toplevel,
compositor.output.width, compositor.output.height, &states);
wl_array_release(&states);
send_frame_done(surface);
return;
}
if (surface->has_buffer && surface->buffer) {
struct tabbed_buffer *buf = surface->buffer;
bool handled = false;
if (buf->is_dmabuf) {
if (import_dmabuf(surface, buf)) {
/* texture owns the imported memory; buffer can be released */
free(surface->pixels);
surface->pixels = NULL;
surface->pixel_width = surface->pixel_height = 0;
if (surface->pending_release) {
wl_buffer_send_release(surface->pending_release->resource);
surface->pending_release = NULL;
}
wl_buffer_send_release(buf->resource);
handled = true;
} else {
/* failed import: drop frame, release buffer */
wl_buffer_send_release(buf->resource);
surface->has_tex = false;
handled = true;
}
} else if (buf->pool && buf->pool->data) {
static bool logged_pixels;
if (!logged_pixels) {
fprintf(stderr, "[shm] buffer %dx%d stride=%d fmt=%x\n",
buf->width, buf->height, buf->stride, buf->format);
logged_pixels = true;
}
const size_t bpp = 4;
if (buf->offset >= 0 && buf->stride > 0 &&
buf->width > 0 && buf->height > 0 &&
buf->stride >= buf->width * (int)bpp &&
(size_t)buf->offset +
(size_t)buf->stride * (size_t)buf->height <= buf->pool->size) {
surface->pixel_width = buf->width;
surface->pixel_height = buf->height;
surface->pixel_stride = buf->stride;
size_t size = (size_t)buf->stride * (size_t)buf->height;
unsigned char *np = realloc(surface->pixels, size);
if (np) {
surface->pixels = np;
memcpy(surface->pixels, buf->pool->data + buf->offset, size);
}
surface->has_tex = false;
} else {
free(surface->pixels);
surface->pixels = NULL;
surface->pixel_width = surface->pixel_height = 0;
}
/* deferred release (GTK3 staging-buffer safety) */
if (surface->pending_release &&
surface->pending_release != buf)
wl_buffer_send_release(surface->pending_release->resource);
surface->pending_release = buf;
handled = true;
}
if (handled) {
bool active_self = (surface == compositor.active_surface);
bool active_desc = false;
for (struct tabbed_surface *p = surface->sub_parent; p; p = p->sub_parent)
if (p == compositor.active_surface) { active_desc = true; break; }
if (active_self || active_desc)
schedule_redraw();
if (active_self)
fit_host_window();
}
} else {
if (surface->pending_release)
wl_buffer_send_release(surface->pending_release->resource);
surface->pending_release = NULL;
free(surface->pixels);
surface->pixels = NULL;
surface->has_tex = false;
bool active_self = (surface == compositor.active_surface);
bool active_desc = false;
for (struct tabbed_surface *p = surface->sub_parent; p; p = p->sub_parent)
if (p == compositor.active_surface) { active_desc = true; break; }
if (active_self || active_desc)
schedule_redraw();
}
send_frame_done(surface);
}
static void surface_set_buffer_scale(struct wl_client *client,
struct wl_resource *resource,
int32_t scale)
{
(void)client; (void)resource; (void)scale;
}
static void surface_set_buffer_transform(struct wl_client *client,
struct wl_resource *resource,
int32_t transform)
{
(void)client; (void)resource; (void)transform;
}
static void surface_damage_buffer(struct wl_client *client,
struct wl_resource *resource,
int32_t x, int32_t y, int32_t w, int32_t h)
{
(void)client; (void)resource; (void)x; (void)y; (void)w; (void)h;
}
static const struct wl_surface_interface surface_impl = {
.destroy = surface_destroy,
.attach = surface_attach,
.damage = surface_damage,
.frame = surface_frame,
.set_opaque_region = surface_set_opaque_region,
.set_input_region = surface_set_input_region,
.commit = surface_commit,
.set_buffer_transform = surface_set_buffer_transform,
.set_buffer_scale = surface_set_buffer_scale,
.damage_buffer = surface_damage_buffer,
};
/* ===================== wl_region ===================== */
static void region_destroy(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static void region_add(struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y, int32_t w, int32_t h)
{ (void)client; (void)resource; (void)x; (void)y; (void)w; (void)h; }
static void region_subtract(struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y, int32_t w, int32_t h)
{ (void)client; (void)resource; (void)x; (void)y; (void)w; (void)h; }
static const struct wl_region_interface region_impl = {
.destroy = region_destroy,
.add = region_add,
.subtract = region_subtract,
};
/* ===================== wl_shm ===================== */
static void pool_destroy_resource(struct wl_resource *resource) {
struct tabbed_pool *pool = wl_resource_get_user_data(resource);
if (!pool) return;
pool->destroyed = true;
/* Buffers may still reference this pool (the client is allowed to
* destroy the pool before the buffers); keep the mapping alive until
* the last referencing buffer is destroyed. */
if (pool->refs <= 0) {
munmap(pool->data, pool->size);
close(pool->fd);
free(pool);
}
}
static void buffer_destroy_request(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static void buffer_destroy_resource(struct wl_resource *resource) {
struct tabbed_buffer *buf = wl_resource_get_user_data(resource);
wl_list_remove(&buf->buffer_link);
struct tabbed_surface *s;
wl_list_for_each(s, &compositor.surfaces, link) {
if (s->pending_release == buf)
s->pending_release = NULL;
if (s->buffer == buf) {
s->buffer = NULL;
s->has_buffer = false;
}
}
if (buf->is_dmabuf) {
for (int i = 0; i < buf->n_planes && i < 4; i++)
if (buf->fd[i] >= 0) close(buf->fd[i]);
}
if (buf->pool) {
buf->pool->refs--;
if (buf->pool->refs <= 0 && buf->pool->destroyed) {
munmap(buf->pool->data, buf->pool->size);
close(buf->pool->fd);
free(buf->pool);
}
}
free(buf);
}
static const struct wl_buffer_interface buffer_impl = {
.destroy = buffer_destroy_request,
};
static void pool_destroy(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static void pool_resize(struct wl_client *client, struct wl_resource *resource,
int32_t size)
{
(void)client;
struct tabbed_pool *pool = wl_resource_get_user_data(resource);
if (size <= (int32_t)pool->size) return;
unsigned char *ndata = mremap(pool->data, pool->size, size, MREMAP_MAYMOVE);
if (ndata == MAP_FAILED) return;
pool->data = ndata;
pool->size = size;
}
static void pool_create_buffer(struct wl_client *client, struct wl_resource *resource,
uint32_t id, int32_t offset,
int32_t width, int32_t height,
int32_t stride, uint32_t format)
{
(void)client;
struct tabbed_pool *pool = wl_resource_get_user_data(resource);
if (format != WL_SHM_FORMAT_ARGB8888 && format != WL_SHM_FORMAT_XRGB8888) {
wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FORMAT,
"unsupported format %u", format);
return;
}
const int bpp = 4;
if (offset < 0 || width <= 0 || height <= 0 ||
stride < width * bpp) {
wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_STRIDE,
"invalid buffer geometry (%d,%d,%d,off %d)",
width, height, stride, offset);
return;
}
if ((size_t)stride * (size_t)height > SIZE_MAX ||
(size_t)offset > (size_t)pool->size ||
(size_t)offset + (size_t)stride * (size_t)height > (size_t)pool->size) {
wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_STRIDE,
"buffer exceeds pool size");
return;
}
struct wl_resource *buf_res = wl_resource_create(client,
&wl_buffer_interface, wl_resource_get_version(resource), id);
if (!buf_res) { wl_client_post_no_memory(client); return; }
struct tabbed_buffer *buf = calloc(1, sizeof(*buf));
if (!buf) { wl_resource_destroy(buf_res); return; }
buf->resource = buf_res;
buf->pool = pool;
pool->refs++;
buf->offset = offset;
buf->width = width;
buf->height = height;
buf->stride = stride;
buf->format = format;
wl_list_insert(&compositor.buffers, &buf->buffer_link);
wl_resource_set_user_data(buf_res, buf);
wl_resource_set_implementation(buf_res, &buffer_impl, buf, buffer_destroy_resource);
}
static const struct wl_shm_pool_interface pool_impl = {
.destroy = pool_destroy,
.resize = pool_resize,
.create_buffer = pool_create_buffer,
};
static void shm_create_pool(struct wl_client *client, struct wl_resource *resource,
uint32_t id, int32_t fd, int32_t size)
{
(void)client;
struct tabbed_pool *pool = calloc(1, sizeof(*pool));
if (!pool) { close(fd); wl_client_post_no_memory(client); return; }
pool->fd = fd;
pool->size = size;
pool->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pool->data == MAP_FAILED) {
close(fd);
free(pool);
wl_client_post_no_memory(client);
return;
}
struct wl_resource *pool_res = wl_resource_create(client,
&wl_shm_pool_interface, wl_resource_get_version(resource), id);
if (!pool_res) {
munmap(pool->data, size); close(fd); free(pool);
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(pool_res, &pool_impl, pool, pool_destroy_resource);
}
static const struct wl_shm_interface shm_impl = {
.create_pool = shm_create_pool,
};
/* ===================== linux-dmabuf ===================== */
struct dmabuf_params {
int fd[4];
uint32_t offset[4];
uint32_t stride[4];
uint32_t mod_hi[4];
uint32_t mod_lo[4];
int n_planes;
bool used;
};
static void dmabuf_destroy(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static void dmabuf_params_cleanup(struct dmabuf_params *p) {
if (!p) return;
for (int i = 0; i < p->n_planes && i < 4; i++)
if (p->fd[i] >= 0) close(p->fd[i]);
free(p);
}
static void dmabuf_params_destroy(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static void dmabuf_params_resource_destroy(struct wl_resource *resource) {
struct dmabuf_params *p = wl_resource_get_user_data(resource);
dmabuf_params_cleanup(p);
wl_resource_set_user_data(resource, NULL);
}
static void dmabuf_params_add(struct wl_client *client, struct wl_resource *resource,
int32_t fd, uint32_t plane_idx, uint32_t offset,
uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo)
{
(void)client;
struct dmabuf_params *p = wl_resource_get_user_data(resource);
if (!p) return;
if (plane_idx >= 4) {
wl_resource_post_error(resource, ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX,
"plane index %u too large", plane_idx);
close(fd);
return;
}
p->fd[plane_idx] = fd;
p->offset[plane_idx] = offset;
p->stride[plane_idx] = stride;
p->mod_hi[plane_idx] = modifier_hi;
p->mod_lo[plane_idx] = modifier_lo;
if (plane_idx + 1 > (uint32_t)p->n_planes) p->n_planes = plane_idx + 1;
}
static struct wl_resource *dmabuf_make_buffer(struct wl_client *client,
struct wl_resource *params_res,
struct dmabuf_params *p,
int32_t width, int32_t height,
uint32_t format, uint32_t res_id,
bool immed)
{
struct tabbed_buffer *buf = calloc(1, sizeof(*buf));
if (!buf) {
if (!immed) zwp_linux_buffer_params_v1_send_failed(params_res);
return NULL;
}
buf->is_dmabuf = true;
buf->width = width;
buf->height = height;
buf->format = format;
buf->n_planes = p->n_planes;
wl_list_insert(&compositor.buffers, &buf->buffer_link);
for (int i = 0; i < p->n_planes && i < 4; i++) {
buf->fd[i] = p->fd[i];
buf->dma_offset[i] = p->offset[i];
buf->dma_stride[i] = p->stride[i];
buf->dma_mod_hi[i] = p->mod_hi[i];
buf->dma_mod_lo[i] = p->mod_lo[i];
p->fd[i] = -1; /* ownership transferred to buffer */
}
struct wl_resource *buf_res = wl_resource_create(client,
&wl_buffer_interface, wl_resource_get_version(params_res), res_id);
if (!buf_res) {
for (int i = 0; i < buf->n_planes && i < 4; i++)
if (buf->fd[i] >= 0) close(buf->fd[i]);
wl_list_remove(&buf->buffer_link);
free(buf);
if (!immed) zwp_linux_buffer_params_v1_send_failed(params_res);
return NULL;
}
buf->resource = buf_res;
wl_resource_set_user_data(buf_res, buf);
wl_resource_set_implementation(buf_res, &buffer_impl, buf,
buffer_destroy_resource);
if (!immed)
zwp_linux_buffer_params_v1_send_created(params_res, buf_res);
return buf_res;
}
static void dmabuf_params_create(struct wl_client *client, struct wl_resource *resource,
int32_t width, int32_t height,
uint32_t format, uint32_t flags)
{
(void)client; (void)flags;
struct dmabuf_params *p = wl_resource_get_user_data(resource);
if (!p) return;
if (p->used) {
wl_resource_post_error(resource, ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
"params already used");
return;
}
p->used = true;
if (width <= 0 || height <= 0 || p->n_planes < 1) {
zwp_linux_buffer_params_v1_send_failed(resource);
return;
}
dmabuf_make_buffer(client, resource, p, width, height, format, 0, false);
}
static void dmabuf_params_create_immed(struct wl_client *client,
struct wl_resource *resource,
uint32_t buffer_id,
int32_t width, int32_t height,
uint32_t format, uint32_t flags)
{
(void)flags;
struct dmabuf_params *p = wl_resource_get_user_data(resource);
if (!p) return;
if (p->used) {
wl_resource_post_error(resource, ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
"params already used");
return;
}
p->used = true;
if (width <= 0 || height <= 0 || p->n_planes < 1) {
/* fatal errors are preferred; just make a failed buffer */
wl_resource_post_error(resource, ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
"incomplete params");
return;
}
dmabuf_make_buffer(client, resource, p, width, height, format, buffer_id, true);
}
static const struct zwp_linux_buffer_params_v1_interface dmabuf_params_impl = {
.destroy = dmabuf_params_destroy,
.add = dmabuf_params_add,
.create = dmabuf_params_create,
.create_immed = dmabuf_params_create_immed,
};
static void dmabuf_create_params(struct wl_client *client, struct wl_resource *resource,
uint32_t params_id)
{
struct wl_resource *params = wl_resource_create(client,
&zwp_linux_buffer_params_v1_interface, wl_resource_get_version(resource),
params_id);
if (!params) { wl_client_post_no_memory(client); return; }
struct dmabuf_params *p = calloc(1, sizeof(*p));
if (!p) { wl_resource_destroy(params); return; }
for (int i = 0; i < 4; i++) p->fd[i] = -1;
wl_resource_set_implementation(params, &dmabuf_params_impl, p,
dmabuf_params_resource_destroy);
}
static const struct zwp_linux_dmabuf_v1_interface dmabuf_impl = {
.destroy = dmabuf_destroy,
.create_params = dmabuf_create_params,
.get_default_feedback = dmabuf_get_feedback,
.get_surface_feedback = dmabuf_get_surface_feedback,
};
static void dmabuf_feedback_destroy(struct wl_client *client,
struct wl_resource *resource)
{
(void)client;
wl_resource_destroy(resource);
}
static const struct zwp_linux_dmabuf_feedback_v1_interface feedback_impl = {
.destroy = dmabuf_feedback_destroy,
};
/* drm_fourcc + linear modifier table and device helpers */
struct dmabuf_format {
uint32_t format;
uint32_t pad;
uint64_t modifier;
};
static bool build_format_table(int *out_fd, uint32_t *out_size)
{
static const uint32_t fmts[] = {
WL_DRM_FORMAT_ARGB8888,
WL_DRM_FORMAT_XRGB8888,
WL_DRM_FORMAT_XRGB2101010,
WL_DRM_FORMAT_XBGR2101010,
};
const size_t count = sizeof(fmts) / sizeof(fmts[0]);
struct dmabuf_format table[sizeof(fmts) / sizeof(fmts[0])] = {0};
for (size_t i = 0; i < count; i++) {
table[i].format = fmts[i];
table[i].modifier = 0; /* DRM_FORMAT_MOD_LINEAR */
}
const size_t size = count * sizeof(struct dmabuf_format);
int fd = memfd_create("linux-dmabuf-format-table", 0);
if (fd < 0) return false;
if (ftruncate(fd, (off_t)size) < 0) { close(fd); return false; }
unsigned char *map = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (map == MAP_FAILED) { close(fd); return false; }
memcpy(map, table, size);
munmap(map, size);
*out_fd = fd;
*out_size = (uint32_t)size;
return true;
}
static void feedback_send_all(struct wl_resource *res)
{
int fd = -1;
uint32_t size = 0;
if (!build_format_table(&fd, &size)) {
wl_resource_destroy(res);
return;
}
zwp_linux_dmabuf_feedback_v1_send_format_table(res, fd, size);
close(fd);
struct stat st;
if (fstat(drm_fd, &st) == 0) {
struct wl_array device;
wl_array_init(&device);
dev_t dev = st.st_rdev;
dev_t *p = wl_array_add(&device, sizeof(dev));
if (p) *p = dev;
zwp_linux_dmabuf_feedback_v1_send_main_device(res, &device);
zwp_linux_dmabuf_feedback_v1_send_tranche_target_device(res, &device);
wl_array_release(&device);
zwp_linux_dmabuf_feedback_v1_send_tranche_flags(res, 0);
struct wl_array indices;
wl_array_init(&indices);
const uint16_t idx[] = { 0, 1, 2, 3 };
uint16_t *q = wl_array_add(&indices, sizeof(idx));
if (q) memcpy(q, idx, sizeof(idx));
zwp_linux_dmabuf_feedback_v1_send_tranche_formats(res, &indices);
wl_array_release(&indices);
}
zwp_linux_dmabuf_feedback_v1_send_tranche_done(res);
zwp_linux_dmabuf_feedback_v1_send_done(res);
}
static void dmabuf_create_feedback(struct wl_client *client,
struct wl_resource *resource,
uint32_t id)
{
struct wl_resource *res = wl_resource_create(client,
&zwp_linux_dmabuf_feedback_v1_interface, wl_resource_get_version(resource),
id);
if (!res) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(res, &feedback_impl, NULL, NULL);
feedback_send_all(res);
}
static void dmabuf_get_feedback(struct wl_client *client,
struct wl_resource *resource,
uint32_t id)
{
dmabuf_create_feedback(client, resource, id);
}
static void dmabuf_get_surface_feedback(struct wl_client *client,
struct wl_resource *resource,
uint32_t id,
struct wl_resource *surface)
{
(void)surface;
dmabuf_create_feedback(client, resource, id);
}
static void bind_dmabuf(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
(void)data;
struct wl_resource *res = wl_resource_create(client,
&zwp_linux_dmabuf_v1_interface, version < 5 ? version : 5, id);
if (!res) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(res, &dmabuf_impl, NULL, NULL);
}
/* ===================== wl_drm (legacy) ===================== */
static void drm_authenticate(struct wl_client *client, struct wl_resource *resource,
uint32_t name)
{
(void)client; (void)name;
/* render nodes require no authentication */
wl_drm_send_authenticated(resource);
}
static void drm_create_buffer(struct wl_client *client, struct wl_resource *resource,
uint32_t name, int32_t width, int32_t height,
uint32_t stride, uint32_t format, uint32_t id)
{
(void)name;
/* Not used by clients when the PRIME capability is advertised; create a
* harmless empty buffer so the protocol object exists. */
struct wl_resource *buf_res = wl_resource_create(client,
&wl_buffer_interface, wl_resource_get_version(resource), id);
if (!buf_res) { wl_client_post_no_memory(client); return; }
struct tabbed_buffer *buf = calloc(1, sizeof(*buf));
if (!buf) { wl_resource_destroy(buf_res); return; }
buf->width = width;
buf->height = height;
buf->format = format;
buf->is_dmabuf = false;
wl_list_insert(&compositor.buffers, &buf->buffer_link);
wl_resource_set_user_data(buf_res, buf);
wl_resource_set_implementation(buf_res, &buffer_impl, buf, buffer_destroy_resource);
}
static void drm_create_planar_buffer(struct wl_client *client, struct wl_resource *resource,
uint32_t name, int32_t width, int32_t height,
uint32_t format, int32_t o0, int32_t s0,
int32_t o1, int32_t s1, int32_t o2, int32_t s2,
uint32_t id)
{
(void)name; (void)o0; (void)o1; (void)s1; (void)o2; (void)s2;
drm_create_buffer(client, resource, name, width, height, s0, format, id);
}
static void drm_create_prime_buffer(struct wl_client *client, struct wl_resource *resource,
uint32_t name, int32_t fd, int32_t width, int32_t height,
uint32_t format, int32_t o0, int32_t s0,
int32_t o1, int32_t s1, int32_t o2, int32_t s2,
uint32_t id)
{
(void)name;
struct wl_resource *buf_res = wl_resource_create(client,
&wl_buffer_interface, wl_resource_get_version(resource), id);
if (!buf_res) { close(fd); wl_client_post_no_memory(client); return; }
struct tabbed_buffer *buf = calloc(1, sizeof(*buf));
if (!buf) { close(fd); wl_resource_destroy(buf_res); return; }
int nplanes = 1;
if (s1 > 0) nplanes = 2;
if (s2 > 0) nplanes = 3;
buf->is_dmabuf = true;
buf->width = width;
buf->height = height;
buf->format = format;
buf->n_planes = nplanes;
buf->fd[0] = fd;
buf->dma_offset[0] = o0;
buf->dma_stride[0] = s0;
if (nplanes >= 2) {
buf->fd[1] = dup(fd);
buf->dma_offset[1] = o1;
buf->dma_stride[1] = s1;
}
if (nplanes >= 3) {
buf->fd[2] = dup(fd);
buf->dma_offset[2] = o2;
buf->dma_stride[2] = s2;
}
wl_list_insert(&compositor.buffers, &buf->buffer_link);
wl_resource_set_user_data(buf_res, buf);
wl_resource_set_implementation(buf_res, &buffer_impl, buf, buffer_destroy_resource);
}
static const struct wl_drm_interface drm_impl = {
.authenticate = drm_authenticate,
.create_buffer = drm_create_buffer,
.create_planar_buffer = drm_create_planar_buffer,
.create_prime_buffer = drm_create_prime_buffer,
};
static void bind_drm(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
(void)data;
struct wl_resource *res = wl_resource_create(client,
&wl_drm_interface, version < 2 ? version : 2, id);
if (!res) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(res, &drm_impl, NULL, NULL);
wl_drm_send_device(res, drm_fd, "/dev/dri/renderD128");
static const uint32_t formats[] = {
WL_DRM_FORMAT_ARGB8888, WL_DRM_FORMAT_XRGB8888,
WL_DRM_FORMAT_XRGB2101010, WL_DRM_FORMAT_XBGR2101010,
WL_DRM_FORMAT_ARGB2101010, WL_DRM_FORMAT_RGB565,
WL_DRM_FORMAT_RGB888, WL_DRM_FORMAT_AYUV,
};
for (unsigned i = 0; i < sizeof(formats) / sizeof(formats[0]); i++)
wl_drm_send_format(res, formats[i]);
if (wl_resource_get_version(res) >= 2)
wl_drm_send_capabilities(res, WL_DRM_CAPABILITY_PRIME);
}
/* ===================== xdg_wm_base ===================== */
static void srv_xdg_wm_base_destroy(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static void positioner_destroy(struct wl_client *c, struct wl_resource *r) { (void)c; wl_resource_destroy(r); }
static void positioner_set_size(struct wl_client *c, struct wl_resource *r, int32_t w, int32_t h) { (void)c;(void)r;(void)w;(void)h; }
static void positioner_set_anchor_rect(struct wl_client *c, struct wl_resource *r, int32_t x, int32_t y, int32_t w, int32_t h) { (void)c;(void)r;(void)x;(void)y;(void)w;(void)h; }
static void positioner_set_anchor(struct wl_client *c, struct wl_resource *r, uint32_t a) { (void)c;(void)r;(void)a; }
static void positioner_set_gravity(struct wl_client *c, struct wl_resource *r, uint32_t g) { (void)c;(void)r;(void)g; }
static void positioner_set_constraint_adjustment(struct wl_client *c, struct wl_resource *r, uint32_t ca) { (void)c;(void)r;(void)ca; }
static void positioner_set_offset(struct wl_client *c, struct wl_resource *r, int32_t x, int32_t y) { (void)c;(void)r;(void)x;(void)y; }
static void positioner_set_reactive(struct wl_client *c, struct wl_resource *r) { (void)c;(void)r; }
static void positioner_set_parent_size(struct wl_client *c, struct wl_resource *r, int32_t w, int32_t h) { (void)c;(void)r;(void)w;(void)h; }
static void positioner_set_parent_configure(struct wl_client *c, struct wl_resource *r, uint32_t s) { (void)c;(void)r;(void)s; }
static const struct xdg_positioner_interface positioner_impl = {
.destroy = positioner_destroy,
.set_size = positioner_set_size,
.set_anchor_rect = positioner_set_anchor_rect,
.set_anchor = positioner_set_anchor,
.set_gravity = positioner_set_gravity,
.set_constraint_adjustment = positioner_set_constraint_adjustment,
.set_offset = positioner_set_offset,
.set_reactive = positioner_set_reactive,
.set_parent_size = positioner_set_parent_size,
.set_parent_configure = positioner_set_parent_configure,
};
static void srv_xdg_wm_base_create_positioner(struct wl_client *client,
struct wl_resource *resource,
uint32_t id)
{
struct wl_resource *pos = wl_resource_create(client,
&xdg_positioner_interface, wl_resource_get_version(resource), id);
if (!pos) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(pos, &positioner_impl, NULL, NULL);
}
static void srv_xdg_wm_base_get_xdg_surface(struct wl_client *client,
struct wl_resource *resource,
uint32_t id,
struct wl_resource *surface_resource)
{
(void)client;
struct tabbed_surface *surface = wl_resource_get_user_data(surface_resource);
if (!surface) return;
if (surface->xdg_surface) {
wl_resource_post_error(surface_resource,
XDG_WM_BASE_ERROR_ROLE, "surface already has a role");
return;
}
struct wl_resource *xdg_res = wl_resource_create(client,
&xdg_surface_interface, wl_resource_get_version(resource), id);
if (!xdg_res) { wl_client_post_no_memory(client); return; }
surface->xdg_surface = xdg_res;
wl_resource_set_user_data(xdg_res, surface);
wl_resource_set_implementation(xdg_res, &xdg_surface_impl, surface, NULL);
}
static void srv_xdg_wm_base_pong(struct wl_client *client,
struct wl_resource *resource,
uint32_t serial)
{
(void)client; (void)resource; (void)serial;
}
static const struct xdg_wm_base_interface xdg_wm_base_impl = {
.destroy = srv_xdg_wm_base_destroy,
.create_positioner = srv_xdg_wm_base_create_positioner,
.get_xdg_surface = srv_xdg_wm_base_get_xdg_surface,
.pong = srv_xdg_wm_base_pong,
};
/* ===================== xdg_surface ===================== */
static void srv_xdg_surface_destroy(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static void srv_xdg_surface_get_toplevel(struct wl_client *client,
struct wl_resource *resource,
uint32_t id)
{
struct tabbed_surface *surface = wl_resource_get_user_data(resource);
if (!surface) return;
struct wl_resource *toplevel = wl_resource_create(client,
&xdg_toplevel_interface, wl_resource_get_version(resource), id);
if (!toplevel) { wl_client_post_no_memory(client); return; }
surface->xdg_toplevel = toplevel;
wl_resource_set_user_data(toplevel, surface);
wl_resource_set_implementation(toplevel, &xdg_toplevel_impl, surface, NULL);
/* Associate with an empty app slot */
for (int i = 0; i < compositor.num_apps; i++) {
if (compositor.apps[i]->surface == NULL) {
compositor.apps[i]->surface = surface;
surface->tab_index = i;
break;
}
}
if (!compositor.active_surface)
compositor.active_surface = surface;
}
static void srv_xdg_surface_get_popup(struct wl_client *client,
struct wl_resource *resource,
uint32_t id,
struct wl_resource *parent,
struct wl_resource *positioner)
{
(void)parent; (void)positioner;
struct wl_resource *popup = wl_resource_create(client,
&xdg_popup_interface, wl_resource_get_version(resource), id);
if (!popup) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(popup, &xdg_popup_impl, NULL, NULL);
}
static void srv_xdg_surface_set_window_geometry(struct wl_client *client,
struct wl_resource *resource,
int32_t x, int32_t y,
int32_t width, int32_t height)
{
(void)client;
struct tabbed_surface *surface = wl_resource_get_user_data(resource);
if (!surface) return;
surface->geom_x = x;
surface->geom_y = y;
surface->geom_width = width;
surface->geom_height = height;
}
static void srv_xdg_surface_ack_configure(struct wl_client *client,
struct wl_resource *resource,
uint32_t serial)
{
(void)client; (void)resource; (void)serial;
}
static const struct xdg_surface_interface xdg_surface_impl = {
.destroy = srv_xdg_surface_destroy,
.get_toplevel = srv_xdg_surface_get_toplevel,
.get_popup = srv_xdg_surface_get_popup,
.set_window_geometry = srv_xdg_surface_set_window_geometry,
.ack_configure = srv_xdg_surface_ack_configure,
};
/* ===================== xdg_toplevel ===================== */
static void srv_xdg_toplevel_destroy(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static void srv_xdg_toplevel_set_parent(struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *parent)
{ (void)client; (void)resource; (void)parent; }
static void srv_xdg_toplevel_set_title(struct wl_client *client,
struct wl_resource *resource,
const char *title)
{
(void)client;
struct tabbed_surface *surface = wl_resource_get_user_data(resource);
if (!surface) return;
free(surface->title);
surface->title = title ? strdup(title) : NULL;
}
static void srv_xdg_toplevel_set_app_id(struct wl_client *client,
struct wl_resource *resource,
const char *app_id)
{
(void)client;
struct tabbed_surface *surface = wl_resource_get_user_data(resource);
if (!surface) return;
free(surface->app_id);
surface->app_id = app_id ? strdup(app_id) : NULL;
}
static void srv_xdg_toplevel_show_window_menu(struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *seat,
uint32_t serial,
int32_t x, int32_t y)
{ (void)client; (void)resource; (void)seat; (void)serial; (void)x; (void)y; }
static void srv_xdg_toplevel_move(struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *seat,
uint32_t serial)
{ (void)client; (void)resource; (void)seat; (void)serial; }
static void srv_xdg_toplevel_resize(struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *seat,
uint32_t serial,
uint32_t edges)
{ (void)client; (void)resource; (void)seat; (void)serial; (void)edges; }
static void srv_xdg_toplevel_set_max_size(struct wl_client *client,
struct wl_resource *resource,
int32_t width, int32_t height)
{ (void)client; (void)resource; (void)width; (void)height; }
static void srv_xdg_toplevel_set_min_size(struct wl_client *client,
struct wl_resource *resource,
int32_t width, int32_t height)
{ (void)client; (void)resource; (void)width; (void)height; }
static void srv_xdg_toplevel_set_maximized(struct wl_client *client,
struct wl_resource *resource)
{ (void)client; (void)resource; }
static void srv_xdg_toplevel_unset_maximized(struct wl_client *client,
struct wl_resource *resource)
{ (void)client; (void)resource; }
static void srv_xdg_toplevel_set_fullscreen(struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *output)
{ (void)client; (void)resource; (void)output; }
static void srv_xdg_toplevel_unset_fullscreen(struct wl_client *client,
struct wl_resource *resource)
{ (void)client; (void)resource; }
static void srv_xdg_toplevel_set_minimized(struct wl_client *client,
struct wl_resource *resource)
{ (void)client; (void)resource; }
static const struct xdg_toplevel_interface xdg_toplevel_impl = {
.destroy = srv_xdg_toplevel_destroy,
.set_parent = srv_xdg_toplevel_set_parent,
.set_title = srv_xdg_toplevel_set_title,
.set_app_id = srv_xdg_toplevel_set_app_id,
.show_window_menu = srv_xdg_toplevel_show_window_menu,
.move = srv_xdg_toplevel_move,
.resize = srv_xdg_toplevel_resize,
.set_max_size = srv_xdg_toplevel_set_max_size,
.set_min_size = srv_xdg_toplevel_set_min_size,
.set_maximized = srv_xdg_toplevel_set_maximized,
.unset_maximized = srv_xdg_toplevel_unset_maximized,
.set_fullscreen = srv_xdg_toplevel_set_fullscreen,
.unset_fullscreen = srv_xdg_toplevel_unset_fullscreen,
.set_minimized = srv_xdg_toplevel_set_minimized,
};
/* ===================== xdg_popup ===================== */
static void srv_xdg_popup_destroy(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static void srv_xdg_popup_grab(struct wl_client *client, struct wl_resource *resource,
struct wl_resource *seat, uint32_t serial)
{ (void)client; (void)resource; (void)seat; (void)serial; }
static void srv_xdg_popup_reposition(struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *positioner,
uint32_t token)
{ (void)client; (void)resource; (void)positioner; (void)token; }
static const struct xdg_popup_interface xdg_popup_impl = {
.destroy = srv_xdg_popup_destroy,
.grab = srv_xdg_popup_grab,
.reposition = srv_xdg_popup_reposition,
};
/* ===================== wl_output ===================== */
static void output_send_all(struct wl_resource *res) {
wl_output_send_geometry(res, 0, 0,
compositor.output.width * 6, compositor.output.height * 6,
WL_OUTPUT_SUBPIXEL_UNKNOWN,
"Tabbed", "Virtual Output",
WL_OUTPUT_TRANSFORM_NORMAL);
wl_output_send_mode(res,
WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED,
compositor.output.width, compositor.output.height, 60000);
if (wl_resource_get_version(res) >= WL_OUTPUT_SCALE_SINCE_VERSION)
wl_output_send_scale(res, 1);
if (wl_resource_get_version(res) >= WL_OUTPUT_DONE_SINCE_VERSION)
wl_output_send_done(res);
}
static void output_bind(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
(void)data;
struct wl_resource *res = wl_resource_create(client,
&wl_output_interface, version < 2 ? version : 2, id);
if (!res) { wl_client_post_no_memory(client); return; }
wl_resource_set_user_data(res, &compositor.output);
wl_list_insert(&compositor.output.resources, wl_resource_get_link(res));
output_send_all(res);
}
/* ===================== Input tracking ===================== */
static void input_track_kbd_destroy(struct wl_listener *l, void *data) {
(void)data;
struct input_track *t = wl_container_of(l, t, kbd_destroy);
t->kbd = NULL;
}
static void input_track_ptr_destroy(struct wl_listener *l, void *data) {
(void)data;
struct input_track *t = wl_container_of(l, t, ptr_destroy);
t->ptr = NULL;
}
static struct input_track *get_input_track(struct wl_client *client) {
struct input_track *t;
wl_list_for_each(t, &compositor.input_tracks, link) {
if (t->client == client) return t;
}
t = calloc(1, sizeof(*t));
if (!t) return NULL;
t->client = client;
t->kbd_destroy.notify = input_track_kbd_destroy;
t->ptr_destroy.notify = input_track_ptr_destroy;
wl_list_insert(&compositor.input_tracks, &t->link);
return t;
}
/* ===================== wl_pointer ===================== */
static void pointer_set_cursor(struct wl_client *c, struct wl_resource *r,
uint32_t serial, struct wl_resource *surface,
int32_t hx, int32_t hy)
{ (void)c;(void)r;(void)serial;(void)surface;(void)hx;(void)hy; }
static void pointer_release(struct wl_client *c, struct wl_resource *r) { (void)c; wl_resource_destroy(r); }
static const struct wl_pointer_interface pointer_impl = {
.set_cursor = pointer_set_cursor,
.release = pointer_release,
};
/* ===================== wl_keyboard ===================== */
static void keyboard_release(struct wl_client *c, struct wl_resource *r) { (void)c; wl_resource_destroy(r); }
static const struct wl_keyboard_interface keyboard_impl = {
.release = keyboard_release,
};
/* ===================== wl_touch ===================== */
static void touch_release(struct wl_client *c, struct wl_resource *r) { (void)c; wl_resource_destroy(r); }
static const struct wl_touch_interface touch_impl = {
.release = touch_release,
};
/* ===================== wl_seat ===================== */
static void seat_get_pointer(struct wl_client *client, struct wl_resource *resource,
uint32_t id)
{
struct wl_resource *ptr = wl_resource_create(client,
&wl_pointer_interface, wl_resource_get_version(resource), id);
if (!ptr) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(ptr, &pointer_impl, NULL, NULL);
struct input_track *t = get_input_track(client);
if (t) {
t->ptr = ptr;
wl_resource_add_destroy_listener(ptr, &t->ptr_destroy);
}
}
static void seat_get_keyboard(struct wl_client *client, struct wl_resource *resource,
uint32_t id)
{
struct wl_resource *kbd = wl_resource_create(client,
&wl_keyboard_interface, wl_resource_get_version(resource), id);
if (!kbd) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(kbd, &keyboard_impl, NULL, NULL);
struct input_track *t = get_input_track(client);
if (t) {
t->kbd = kbd;
wl_resource_add_destroy_listener(kbd, &t->kbd_destroy);
}
if (compositor.xkb_keymap_fd >= 0) {
lseek(compositor.xkb_keymap_fd, 0, SEEK_SET);
wl_keyboard_send_keymap(kbd, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
compositor.xkb_keymap_fd,
compositor.xkb_keymap_size);
}
}
static void seat_get_touch(struct wl_client *client, struct wl_resource *resource,
uint32_t id)
{
struct wl_resource *touch = wl_resource_create(client,
&wl_touch_interface, wl_resource_get_version(resource), id);
if (!touch) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(touch, &touch_impl, NULL, NULL);
}
static void seat_release(struct wl_client *client, struct wl_resource *resource) {
(void)client;
wl_resource_destroy(resource);
}
static const struct wl_seat_interface seat_impl = {
.get_pointer = seat_get_pointer,
.get_keyboard = seat_get_keyboard,
.get_touch = seat_get_touch,
.release = seat_release,
};
static void seat_bind(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
(void)data;
struct wl_resource *res = wl_resource_create(client,
&wl_seat_interface, version < 7 ? version : 7, id);
if (!res) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(res, &seat_impl, NULL, NULL);
wl_seat_send_capabilities(res, WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD);
if (wl_resource_get_version(res) >= 4)
wl_seat_send_name(res, "default");
}
/* ===================== subcompositor ===================== */
static void subcompositor_destroy(struct wl_client *client, struct wl_resource *res) {
(void)client;
wl_resource_destroy(res);
}
static void subsurface_destroyed_cb(struct wl_listener *listener, void *data) {
(void)data;
struct tabbed_surface *surface =
wl_container_of(listener, surface, subsurface_destroy_listener);
if (surface->sub_parent) {
wl_list_remove(&surface->sub_link);
surface->sub_parent = NULL;
surface->is_subsurface = false;
}
}
static void subcompositor_get_subsurface(struct wl_client *client,
struct wl_resource *resource,
uint32_t id,
struct wl_resource *surface,
struct wl_resource *parent)
{
(void)resource;
struct tabbed_surface *child = wl_resource_get_user_data(surface);
struct tabbed_surface *par = wl_resource_get_user_data(parent);
if (!child || !par || child == par || child->sub_parent) {
wl_resource_post_error(surface, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
"invalid surface or parent");
return;
}
struct wl_resource *sub = wl_resource_create(client,
&wl_subsurface_interface, 1, id);
if (!sub) { wl_client_post_no_memory(client); return; }
child->is_subsurface = true;
child->sub_parent = par;
child->sub_x = 0;
child->sub_y = 0;
wl_list_insert(&par->sub_children, &child->sub_link);
child->subsurface_destroy_listener.notify = subsurface_destroyed_cb;
wl_resource_add_destroy_listener(sub, &child->subsurface_destroy_listener);
wl_resource_set_implementation(sub, &subsurface_impl, child, NULL);
}
static void subsurface_destroy(struct wl_client *c, struct wl_resource *r) { (void)c; wl_resource_destroy(r); }
static void subsurface_set_position(struct wl_client *c, struct wl_resource *r, int32_t x, int32_t y) {
(void)c;
struct tabbed_surface *s = wl_resource_get_user_data(r);
if (!s || !s->sub_parent) return;
s->sub_x = x;
s->sub_y = y;
if (s->sub_parent == compositor.active_surface)
schedule_redraw();
}
static void subsurface_place_above(struct wl_client *c, struct wl_resource *r, struct wl_resource *sibling) {
(void)c;
struct tabbed_surface *s = wl_resource_get_user_data(r);
struct tabbed_surface *t = wl_resource_get_user_data(sibling);
if (!s || !s->sub_parent || !t || t->sub_parent != s->sub_parent)
return;
wl_list_remove(&s->sub_link);
wl_list_insert(&t->sub_link, &s->sub_link);
if (s->sub_parent == compositor.active_surface)
schedule_redraw();
}
static void subsurface_place_below(struct wl_client *c, struct wl_resource *r, struct wl_resource *sibling) {
(void)c;
struct tabbed_surface *s = wl_resource_get_user_data(r);
struct tabbed_surface *t = wl_resource_get_user_data(sibling);
if (!s || !s->sub_parent || !t || t->sub_parent != s->sub_parent)
return;
wl_list_remove(&s->sub_link);
wl_list_insert(t->sub_link.prev, &s->sub_link);
if (s->sub_parent == compositor.active_surface)
schedule_redraw();
}
static void subsurface_set_sync(struct wl_client *c, struct wl_resource *r) { (void)c;(void)r; }
static void subsurface_set_desync(struct wl_client *c, struct wl_resource *r) { (void)c;(void)r; }
static const struct wl_subsurface_interface subsurface_impl = {
.destroy = subsurface_destroy,
.set_position = subsurface_set_position,
.place_above = subsurface_place_above,
.place_below = subsurface_place_below,
.set_sync = subsurface_set_sync,
.set_desync = subsurface_set_desync,
};
static const struct wl_subcompositor_interface subcompositor_impl = {
.destroy = subcompositor_destroy,
.get_subsurface = subcompositor_get_subsurface,
};
/* ===================== data device manager ===================== */
static void data_offer_destroy(struct wl_client *client, struct wl_resource *res) {
(void)client; wl_resource_destroy(res);
}
static void data_offer_accept(struct wl_client *c, struct wl_resource *r,
uint32_t s, const char *t) { (void)c;(void)r;(void)s;(void)t; }
static void data_offer_receive(struct wl_client *c, struct wl_resource *r,
const char *t, int32_t fd) { (void)c;(void)r;(void)t;close(fd); }
static void data_offer_finish(struct wl_client *c, struct wl_resource *r) { (void)c;(void)r; }
static void data_offer_set_actions(struct wl_client *c, struct wl_resource *r,
uint32_t d, uint32_t a) { (void)c;(void)r;(void)d;(void)a; }
const struct wl_data_offer_interface data_offer_impl = {
.destroy = data_offer_destroy,
.accept = data_offer_accept,
.receive = data_offer_receive,
.finish = data_offer_finish,
.set_actions = data_offer_set_actions,
};
static void data_source_destroy(struct wl_client *c, struct wl_resource *r) { (void)c; wl_resource_destroy(r); }
static void data_source_offer(struct wl_client *c, struct wl_resource *r, const char *t) { (void)c;(void)r;(void)t; }
static void data_source_set_actions(struct wl_client *c, struct wl_resource *r, uint32_t a) { (void)c;(void)r;(void)a; }
static const struct wl_data_source_interface data_source_impl = {
.destroy = data_source_destroy,
.offer = data_source_offer,
.set_actions = data_source_set_actions,
};
static void data_device_start_drag(struct wl_client *c, struct wl_resource *r,
struct wl_resource *s, struct wl_resource *o,
struct wl_resource *i, uint32_t serial)
{ (void)c;(void)r;(void)s;(void)o;(void)i;(void)serial; }
static void data_device_set_selection(struct wl_client *c, struct wl_resource *r,
struct wl_resource *s, uint32_t serial)
{ (void)c;(void)r;(void)s;(void)serial; }
static void data_device_release(struct wl_client *c, struct wl_resource *r) { (void)c; wl_resource_destroy(r); }
static const struct wl_data_device_interface data_device_impl = {
.start_drag = data_device_start_drag,
.set_selection = data_device_set_selection,
.release = data_device_release,
};
static void data_device_manager_create_data_source(struct wl_client *c,
struct wl_resource *r,
uint32_t id)
{
struct wl_resource *src = wl_resource_create(c,
&wl_data_source_interface, wl_resource_get_version(r), id);
if (!src) { wl_client_post_no_memory(c); return; }
wl_resource_set_implementation(src, &data_source_impl, NULL, NULL);
}
static void data_device_manager_get_data_device(struct wl_client *c,
struct wl_resource *r,
uint32_t id,
struct wl_resource *seat)
{
(void)seat;
struct wl_resource *dev = wl_resource_create(c,
&wl_data_device_interface, wl_resource_get_version(r), id);
if (!dev) { wl_client_post_no_memory(c); return; }
wl_resource_set_implementation(dev, &data_device_impl, NULL, NULL);
}
static const struct wl_data_device_manager_interface data_device_manager_impl = {
.create_data_source = data_device_manager_create_data_source,
.get_data_device = data_device_manager_get_data_device,
};
/* ===================== Global bind functions ===================== */
static void bind_compositor(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
(void)data;
struct wl_resource *res = wl_resource_create(client,
&wl_compositor_interface, version < 5 ? version : 5, id);
if (!res) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(res, &compositor_impl, NULL, NULL);
}
static void bind_shm(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
(void)data;
struct wl_resource *res = wl_resource_create(client,
&wl_shm_interface, version < 2 ? version : 2, id);
if (!res) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(res, &shm_impl, NULL, NULL);
wl_shm_send_format(res, WL_SHM_FORMAT_ARGB8888);
wl_shm_send_format(res, WL_SHM_FORMAT_XRGB8888);
}
static void bind_xdg_wm_base(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
(void)data;
struct wl_resource *res = wl_resource_create(client,
&xdg_wm_base_interface, version < 6 ? version : 6, id);
if (!res) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(res, &xdg_wm_base_impl, NULL, NULL);
}
static void bind_subcompositor(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
(void)data;
struct wl_resource *res = wl_resource_create(client,
&wl_subcompositor_interface, version, id);
if (!res) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(res, &subcompositor_impl, NULL, NULL);
}
static void bind_data_device_manager(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
(void)data;
struct wl_resource *res = wl_resource_create(client,
&wl_data_device_manager_interface, version < 3 ? version : 3, id);
if (!res) { wl_client_post_no_memory(client); return; }
wl_resource_set_implementation(res, &data_device_manager_impl, NULL, NULL);
}
/* ===================== Output resize ===================== */
static void output_resize(int width, int height) {
if (width < 50) width = 50;
if (height < 50) height = 50;
struct tabbed_output *out = &compositor.output;
if (out->width == width && out->height == height)
return;
out->width = width;
out->height = height;
out->stride = width * 4;
free(out->data);
out->data = calloc(1, height * out->stride);
if (out->data)
memset(out->data, 0x22, height * out->stride);
struct wl_resource *res;
wl_list_for_each(res, &out->resources, link)
output_send_all(res);
struct tabbed_surface *surf;
struct wl_array states;
wl_array_init(&states);
uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
if (s) *s = XDG_TOPLEVEL_STATE_ACTIVATED;
wl_list_for_each(surf, &compositor.surfaces, link) {
if (surf->xdg_toplevel) {
compositor.serial++;
xdg_toplevel_send_configure(surf->xdg_toplevel,
width, height, &states);
if (surf->xdg_surface) {
xdg_surface_send_configure(surf->xdg_surface, compositor.serial);
surf->configure_serial = compositor.serial;
}
}
}
wl_array_release(&states);
wl_display_flush_clients(compositor.display);
}
/* ===================== Set active surface ===================== */
static struct wl_resource *kbd_for_client(struct wl_client *client) {
struct input_track *t;
wl_list_for_each(t, &compositor.input_tracks, link)
if (t->client == client) return t->kbd;
return NULL;
}
static struct wl_resource *ptr_for_client(struct wl_client *client) {
struct input_track *t;
wl_list_for_each(t, &compositor.input_tracks, link)
if (t->client == client) return t->ptr;
return NULL;
}
static void send_kbd_enter(struct tabbed_surface *surface) {
if (!surface) return;
struct wl_resource *kbd = kbd_for_client(surface->client);
if (!kbd) return;
struct wl_array keys;
wl_array_init(&keys);
compositor.input_serial++;
wl_keyboard_send_enter(kbd, compositor.input_serial,
surface->resource, &keys);
wl_array_release(&keys);
compositor.kbd_focus = surface;
compositor.kbd_resource = kbd;
wl_display_flush_clients(compositor.display);
}
static void send_kbd_leave(struct tabbed_surface *surface) {
if (!surface || !compositor.kbd_focus) return;
if (compositor.kbd_focus != surface) return;
struct wl_resource *kbd = kbd_for_client(surface->client);
if (!kbd) return;
compositor.input_serial++;
wl_keyboard_send_leave(kbd, compositor.input_serial,
surface->resource);
compositor.kbd_focus = NULL;
wl_display_flush_clients(compositor.display);
}
static void schedule_redraw(void) {
needs_redraw = true;
}
/* Resize the host (nested) window to fit active content once, 1:1 (no
scaling). Afterwards the host window size is labwc-driven only; client
commits must not resize it (that caused a shrink loop with CSD clients
like foot, which render their buffer smaller than the configured size). */
static void fit_host_window(void) {
struct tabbed_surface *a = compositor.active_surface;
if (!a || !host.configured) return;
if (host.client_sized) return;
int bw = 0, bh = 0;
if (a->has_tex) { bw = a->tex_w; bh = a->tex_h; }
else if (a->pixels) { bw = a->pixel_width; bh = a->pixel_height; }
if (bw <= 0 || bh <= 0) return;
if (bw > MAX_WIN_W) bw = MAX_WIN_W;
if (bh > MAX_WIN_H) bh = MAX_WIN_H;
host.client_sized = true;
host_resize(bw, bh);
}
static void set_active_surface(struct tabbed_surface *surface) {
if (compositor.active_surface == surface) return;
send_kbd_leave(compositor.kbd_focus);
compositor.active_surface = surface;
send_kbd_enter(surface);
schedule_redraw();
fit_host_window();
wl_display_flush_clients(compositor.display);
}
static void cycle_tab(int dir) {
if (compositor.num_apps == 0) return;
int idx = -1;
for (int i = 0; i < compositor.num_apps; i++)
if (compositor.apps[i]->surface == compositor.active_surface) {
idx = i; break;
}
for (int k = 1; k <= compositor.num_apps; k++) {
int j = ((idx < 0 ? 0 : idx) + k * dir + compositor.num_apps) % compositor.num_apps;
if (compositor.apps[j]->surface) {
set_active_surface(compositor.apps[j]->surface);
return;
}
}
}
/* ===================== Rendering ===================== */
static GLuint compile_shader(GLenum type, const char *src) {
GLuint sh = glCreateShader(type);
glShaderSource(sh, 1, &src, NULL);
glCompileShader(sh);
GLint ok;
glGetShaderiv(sh, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[512];
glGetShaderInfoLog(sh, sizeof(log), NULL, log);
fprintf(stderr, "tabbed-windows: shader error: %s\n", log);
glDeleteShader(sh);
return 0;
}
return sh;
}
static bool gl_setup(void) {
const char *vs =
"attribute vec2 a_pos;\n"
"attribute vec2 a_uv;\n"
"varying vec2 v_uv;\n"
"void main() {\n"
" v_uv = a_uv;\n"
" gl_Position = vec4(a_pos, 0.0, 1.0);\n"
"}\n";
const char *fs =
"precision mediump float;\n"
"varying vec2 v_uv;\n"
"uniform sampler2D u_tex;\n"
"void main() {\n"
" gl_FragColor = texture2D(u_tex, v_uv);\n"
"}\n";
GLuint vsh = compile_shader(GL_VERTEX_SHADER, vs);
GLuint fsh = compile_shader(GL_FRAGMENT_SHADER, fs);
if (!vsh || !fsh) return false;
host.gl_program = glCreateProgram();
glAttachShader(host.gl_program, vsh);
glAttachShader(host.gl_program, fsh);
glBindAttribLocation(host.gl_program, 0, "a_pos");
glBindAttribLocation(host.gl_program, 1, "a_uv");
glLinkProgram(host.gl_program);
GLint ok;
glGetProgramiv(host.gl_program, GL_LINK_STATUS, &ok);
glDeleteShader(vsh);
glDeleteShader(fsh);
if (!ok) {
char log[512];
glGetProgramInfoLog(host.gl_program, sizeof(log), NULL, log);
fprintf(stderr, "tabbed-windows: link error: %s\n", log);
return false;
}
host.gl_pos_loc = glGetAttribLocation(host.gl_program, "a_pos");
host.gl_uv_loc = glGetAttribLocation(host.gl_program, "a_uv");
host.gl_tex_loc = glGetUniformLocation(host.gl_program, "u_tex");
return true;
}
/* Blit a source rect of a texture into the window at the given destination
offset (window coords), clipped to the window. dst_x/dst_y may place the
texture partially off-window (e.g. CSD drop shadows beyond the frame). */
static void draw_texture_rect(GLuint tex, int tex_w, int tex_h,
int src_x, int src_y, int src_w, int src_h,
int dst_x, int dst_y, int win_w, int win_h)
{
if (win_w <= 0 || win_h <= 0) return;
if (src_w <= 0 || src_h <= 0) return;
if (src_x >= tex_w || src_y >= tex_h) return;
if (src_w > tex_w - src_x) src_w = tex_w - src_x;
if (src_h > tex_h - src_y) src_h = tex_h - src_y;
/* clip source rect to the destination window bounds */
int cx = dst_x < 0 ? -dst_x : 0; /* source cols cut on the left */
int cy = dst_y < 0 ? -dst_y : 0; /* source rows cut on the top */
if (cx >= src_w || cy >= src_h) return;
int wx0 = dst_x + cx;
int wy0 = dst_y + cy;
int cw = src_w - cx;
int ch = src_h - cy;
int maxw = win_w - wx0;
int maxh = win_h - wy0;
if (cw > maxw) cw = maxw;
if (ch > maxh) ch = maxh;
if (cw <= 0 || ch <= 0) return;
src_x += cx;
src_y += cy;
float u0 = (float)src_x / (float)tex_w;
float u1 = (float)(src_x + cw) / (float)tex_w;
/* v=0 is the first stored row = image top; window y=0 is top. */
float v0 = (float)src_y / (float)tex_h;
float v1 = (float)(src_y + ch) / (float)tex_h;
float nx0 = -1.0f + 2.0f * (float)wx0 / (float)win_w;
float ny0 = 1.0f - 2.0f * (float)wy0 / (float)win_h;
float nx1 = -1.0f + 2.0f * (float)(wx0 + cw) / (float)win_w;
float ny1 = 1.0f - 2.0f * (float)(wy0 + ch) / (float)win_h;
GLfloat verts[8] = {
nx0, ny0, /* top-left */
nx1, ny0, /* top-right */
nx1, ny1, /* bottom-right */
nx0, ny1, /* bottom-left */
};
GLfloat uvs[8] = {
u0, v0,
u1, v0,
u1, v1,
u0, v1,
};
GLushort idx[6] = { 0, 1, 2, 0, 2, 3 };
glUseProgram(host.gl_program);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
glUniform1i(host.gl_tex_loc, 0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, uvs);
glEnableVertexAttribArray(1);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, idx);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
}
/* Draw a surface's buffer at a window offset, then its sub-surfaces on top
(recursively), accumulating sub-surface offsets. */
static void draw_surface(struct tabbed_surface *s, int dst_x, int dst_y,
int win_w, int win_h)
{
if (!s) return;
if (s->has_tex) {
int sx, sy, sw, sh;
content_rect(s, &sx, &sy, &sw, &sh);
draw_texture_rect(s->tex, s->tex_w, s->tex_h, sx, sy, sw, sh,
dst_x, dst_y, win_w, win_h);
} else if (s->pixels) {
if (!ensure_texture(s, s->pixel_width, s->pixel_height))
return;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
s->pixel_width, s->pixel_height, 0,
GL_BGRA_EXT, GL_UNSIGNED_BYTE, s->pixels);
int sx, sy, sw, sh;
content_rect(s, &sx, &sy, &sw, &sh);
draw_texture_rect(s->tex, s->tex_w, s->tex_h, sx, sy, sw, sh,
dst_x, dst_y, win_w, win_h);
}
struct tabbed_surface *child;
wl_list_for_each(child, &s->sub_children, sub_link)
draw_surface(child, dst_x + child->sub_x, dst_y + child->sub_y,
win_w, win_h);
}
static void render_egl(void) {
int w = host.width, h = host.height;
glViewport(0, 0, w, h);
glClearColor(0.13f, 0.13f, 0.13f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
struct tabbed_surface *a = compositor.active_surface;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (a)
draw_surface(a, -a->geom_x, -a->geom_y, w, h);
glDisable(GL_BLEND);
eglSwapBuffers(host.egl_display, host.egl_surface);
static int dframe;
if ((dframe++ % 90) == 0) {
const char *cmd = "(none)";
for (int i = 0; i < compositor.num_apps; i++)
if (compositor.apps[i] && compositor.apps[i]->surface == a)
cmd = compositor.apps[i]->cmd;
fprintf(stderr, "[render] frame=%d active=%s size=%dx%d\n",
dframe, cmd, w, h);
}
}
/* CPU composite a surface's shm pixels into output->data at a window offset,
then its sub-surfaces on top (recursively). No alpha blending (this is the
no-EGL fallback path only). */
static void blit_surface(struct tabbed_surface *s, int dst_x, int dst_y,
int win_w, int win_h)
{
struct tabbed_output *out = &compositor.output;
if (!s || !s->pixels) return;
int sx, sy, sw, sh;
content_rect(s, &sx, &sy, &sw, &sh);
int cw = sw, ch = sh;
int cx = dst_x < 0 ? -dst_x : 0;
int cy = dst_y < 0 ? -dst_y : 0;
if (cx >= cw || cy >= ch) return;
int wx0 = dst_x + cx;
int wy0 = dst_y + cy;
cw -= cx;
ch -= cy;
int maxw = win_w - wx0;
int maxh = win_h - wy0;
if (cw > maxw) cw = maxw;
if (ch > maxh) ch = maxh;
if (cw <= 0 || ch <= 0) return;
sx += cx;
sy += cy;
for (int y = 0; y < ch; y++) {
memcpy(out->data + (wy0 + y) * out->stride + wx0 * 4,
s->pixels + (sy + y) * s->pixel_stride + sx * 4, cw * 4);
}
struct tabbed_surface *child;
wl_list_for_each(child, &s->sub_children, sub_link)
blit_surface(child, dst_x + child->sub_x, dst_y + child->sub_y,
win_w, win_h);
}
static void present_shm_fallback(void) {
struct tabbed_output *out = &compositor.output;
int w = host.width, h = host.height;
if (w <= 0 || h <= 0 || !host.shm) return;
size_t size = (size_t)w * h * 4;
if (size != host.shm_size) {
if (host.shm_pool) wl_shm_pool_destroy(host.shm_pool);
if (host.shm_buffer) wl_buffer_destroy(host.shm_buffer);
if (host.shm_data) munmap(host.shm_data, host.shm_size);
int fd = memfd_create("tabbed-host", MFD_CLOEXEC);
if (fd < 0) return;
if (ftruncate(fd, size) < 0) { close(fd); return; }
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) { close(fd); return; }
host.shm_pool = wl_shm_create_pool(host.shm, fd, size);
host.shm_buffer = wl_shm_pool_create_buffer(host.shm_pool, 0,
w, h, w * 4, WL_SHM_FORMAT_XRGB8888);
close(fd);
host.shm_data = data;
host.shm_size = size;
}
/* composite CPU pixels into output->data */
memset(out->data, 0x22, (size_t)h * out->stride);
struct tabbed_surface *a = compositor.active_surface;
if (a)
blit_surface(a, -a->geom_x, -a->geom_y, w, h);
memcpy(host.shm_data, out->data, size);
wl_surface_attach(host.surface, host.shm_buffer, 0, 0);
wl_surface_damage(host.surface, 0, 0, w, h);
wl_surface_commit(host.surface);
}
static void render_and_present(void) {
if (!host.configured) return;
if (host.egl_ok)
render_egl();
else
present_shm_fallback();
wl_display_flush(host.display);
}
/* ===================== Host resize ===================== */
static void host_resize(int width, int height) {
if (width < 50) width = 50;
if (height < 50) height = 50;
host.width = width;
host.height = height;
if (host.egl_window)
wl_egl_window_resize(host.egl_window, width, height, 0, 0);
if (host.egl_ok) {
/* inform the context of the new window size */
eglMakeCurrent(host.egl_display, host.egl_surface, host.egl_surface,
host.egl_context);
}
output_resize(width, height);
schedule_redraw();
}
/* ===================== Host-side listeners ===================== */
static void host_registry_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface,
uint32_t version)
{
(void)data;
if (strcmp(interface, wl_compositor_interface.name) == 0) {
host.compositor = wl_registry_bind(registry, name,
&wl_compositor_interface, version < 4 ? version : 4);
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
host.wm_base = wl_registry_bind(registry, name,
&xdg_wm_base_interface, version < 1 ? version : 1);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
host.shm = wl_registry_bind(registry, name,
&wl_shm_interface, version < 1 ? version : 1);
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
host.seat = wl_registry_bind(registry, name,
&wl_seat_interface, version < 5 ? version : 5);
}
}
static void host_registry_global_remove(void *data, struct wl_registry *registry,
uint32_t name)
{
(void)data; (void)registry; (void)name;
}
static const struct wl_registry_listener host_registry_listener = {
.global = host_registry_global,
.global_remove = host_registry_global_remove,
};
static void host_xdg_wm_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
(void)data;
xdg_wm_base_pong(wm, serial);
}
static const struct xdg_wm_base_listener host_xdg_wm_listener = {
.ping = host_xdg_wm_ping,
};
static void host_xdg_surface_configure(void *data, struct xdg_surface *s,
uint32_t serial)
{
(void)data;
xdg_surface_ack_configure(s, serial);
}
static const struct xdg_surface_listener host_xdg_surface_listener = {
.configure = host_xdg_surface_configure,
};
static void host_xdg_toplevel_configure(void *data, struct xdg_toplevel *t,
int32_t width, int32_t height,
struct wl_array *states)
{
(void)data; (void)t; (void)states;
if (width > 0 && height > 0)
host_resize(width, height);
host.configured = true;
}
static void host_xdg_toplevel_close(void *data, struct xdg_toplevel *t) {
(void)data; (void)t;
fprintf(stderr, "tabbed-windows: window closed\n");
exit(0);
}
static const struct xdg_toplevel_listener host_xdg_toplevel_listener = {
.configure = host_xdg_toplevel_configure,
.close = host_xdg_toplevel_close,
};
/* ---------- host keyboard ---------- */
static void host_kbd_keymap(void *data, struct wl_keyboard *kbd, uint32_t format,
int32_t fd, uint32_t size)
{
(void)data; (void)kbd; (void)format; (void)fd; (void)size;
}
static void host_kbd_enter(void *data, struct wl_keyboard *kbd, uint32_t serial,
struct wl_surface *surface, struct wl_array *keys)
{
(void)data; (void)kbd; (void)serial; (void)surface; (void)keys;
if (compositor.active_surface && !compositor.kbd_focus)
send_kbd_enter(compositor.active_surface);
}
static void host_kbd_leave(void *data, struct wl_keyboard *kbd, uint32_t serial,
struct wl_surface *surface)
{
(void)data; (void)kbd; (void)serial; (void)surface;
}
static void host_kbd_key(void *data, struct wl_keyboard *kbd, uint32_t serial,
uint32_t time, uint32_t key, uint32_t state)
{
(void)data; (void)kbd; (void)serial;
/* Ctrl+PageUp / Ctrl+PageDown cycle tabs */
bool ctrl = (host.host_mods_depressed & (1u << 2)) != 0;
if (state == WL_KEYBOARD_KEY_STATE_PRESSED && ctrl &&
(key == 104 || key == 109)) {
cycle_tab(key == 109 ? 1 : -1);
return;
}
struct tabbed_surface *a = compositor.active_surface;
if (!a) return;
if (compositor.kbd_focus != a)
send_kbd_enter(a);
struct wl_resource *kbd_res = kbd_for_client(a->client);
if (!kbd_res) return;
compositor.input_serial++;
wl_keyboard_send_key(kbd_res, compositor.input_serial, time, key, state);
wl_display_flush_clients(compositor.display);
}
static void host_kbd_modifiers(void *data, struct wl_keyboard *kbd, uint32_t serial,
uint32_t mods_depressed, uint32_t mods_latched,
uint32_t mods_locked, uint32_t group)
{
(void)data; (void)kbd; (void)serial;
host.host_mods_depressed = mods_depressed;
struct tabbed_surface *a = compositor.active_surface;
if (!a) return;
struct wl_resource *kbd_res = kbd_for_client(a->client);
if (!kbd_res) return;
compositor.input_serial++;
wl_keyboard_send_modifiers(kbd_res, compositor.input_serial,
mods_depressed, mods_latched, mods_locked, group);
wl_display_flush_clients(compositor.display);
}
static void host_kbd_repeat_info(void *data, struct wl_keyboard *kbd,
int32_t rate, int32_t delay)
{
(void)data; (void)kbd; (void)rate; (void)delay;
}
static const struct wl_keyboard_listener host_kbd_listener = {
.keymap = host_kbd_keymap,
.enter = host_kbd_enter,
.leave = host_kbd_leave,
.key = host_kbd_key,
.modifiers = host_kbd_modifiers,
.repeat_info = host_kbd_repeat_info,
};
/* ---------- host pointer ---------- */
static void host_ptr_enter(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy)
{
(void)data; (void)p; (void)serial; (void)surface;
struct tabbed_surface *a = compositor.active_surface;
if (!a) return;
struct wl_resource *ptr = ptr_for_client(a->client);
if (!ptr) return;
compositor.input_serial++;
wl_pointer_send_enter(ptr, compositor.input_serial, a->resource, sx, sy);
compositor.pointer_entered = true;
compositor.pointer_x = wl_fixed_to_double(sx);
compositor.pointer_y = wl_fixed_to_double(sy);
int ver = wl_resource_get_version(ptr);
if (ver >= WL_POINTER_FRAME_SINCE_VERSION)
wl_pointer_send_frame(ptr);
wl_display_flush_clients(compositor.display);
}
static void host_ptr_leave(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *surface)
{
(void)data; (void)p; (void)serial; (void)surface;
struct tabbed_surface *a = compositor.active_surface;
if (!a) return;
struct wl_resource *ptr = ptr_for_client(a->client);
if (!ptr) return;
compositor.input_serial++;
wl_pointer_send_leave(ptr, compositor.input_serial, a->resource);
compositor.pointer_entered = false;
int ver = wl_resource_get_version(ptr);
if (ver >= WL_POINTER_FRAME_SINCE_VERSION)
wl_pointer_send_frame(ptr);
wl_display_flush_clients(compositor.display);
}
static void host_ptr_motion(void *data, struct wl_pointer *p, uint32_t time,
wl_fixed_t sx, wl_fixed_t sy)
{
(void)data; (void)p;
struct tabbed_surface *a = compositor.active_surface;
if (!a) return;
struct wl_resource *ptr = ptr_for_client(a->client);
if (!ptr) return;
wl_pointer_send_motion(ptr, time, sx, sy);
compositor.pointer_x = wl_fixed_to_double(sx);
compositor.pointer_y = wl_fixed_to_double(sy);
int ver = wl_resource_get_version(ptr);
if (ver >= WL_POINTER_FRAME_SINCE_VERSION)
wl_pointer_send_frame(ptr);
wl_display_flush_clients(compositor.display);
}
static void host_ptr_button(void *data, struct wl_pointer *p, uint32_t serial,
uint32_t time, uint32_t button, uint32_t state)
{
(void)data; (void)p; (void)serial;
struct tabbed_surface *a = compositor.active_surface;
if (!a) return;
struct wl_resource *ptr = ptr_for_client(a->client);
if (!ptr) return;
compositor.input_serial++;
wl_pointer_send_button(ptr, compositor.input_serial, time, button, state);
int ver = wl_resource_get_version(ptr);
if (ver >= WL_POINTER_FRAME_SINCE_VERSION)
wl_pointer_send_frame(ptr);
wl_display_flush_clients(compositor.display);
}
static void host_ptr_axis(void *data, struct wl_pointer *p, uint32_t time,
uint32_t axis, wl_fixed_t value)
{
(void)data; (void)p;
struct tabbed_surface *a = compositor.active_surface;
if (!a) return;
struct wl_resource *ptr = ptr_for_client(a->client);
if (!ptr) return;
wl_pointer_send_axis(ptr, time, axis, value);
int ver = wl_resource_get_version(ptr);
if (ver >= WL_POINTER_FRAME_SINCE_VERSION)
wl_pointer_send_frame(ptr);
wl_display_flush_clients(compositor.display);
}
static void host_ptr_noop_frame(void *data, struct wl_pointer *p) {
(void)data; (void)p;
}
static void host_ptr_noop_axis_source(void *data, struct wl_pointer *p,
uint32_t axis_source) {
(void)data; (void)p; (void)axis_source;
}
static void host_ptr_noop_axis_stop(void *data, struct wl_pointer *p,
uint32_t time, uint32_t axis) {
(void)data; (void)p; (void)time; (void)axis;
}
static void host_ptr_noop_axis_discrete(void *data, struct wl_pointer *p,
uint32_t axis, int32_t discrete) {
(void)data; (void)p; (void)axis; (void)discrete;
}
static const struct wl_pointer_listener host_ptr_listener = {
.enter = host_ptr_enter,
.leave = host_ptr_leave,
.motion = host_ptr_motion,
.button = host_ptr_button,
.axis = host_ptr_axis,
.frame = host_ptr_noop_frame,
.axis_source = host_ptr_noop_axis_source,
.axis_stop = host_ptr_noop_axis_stop,
.axis_discrete = host_ptr_noop_axis_discrete,
};
/* ===================== Host setup ===================== */
static bool host_init(void) {
const char *disp = getenv("WAYLAND_DISPLAY");
if (!disp)
disp = "wayland-0";
host.display = wl_display_connect(NULL);
if (!host.display) {
fprintf(stderr, "tabbed-windows: cannot connect to host compositor "
"(WAYLAND_DISPLAY=%s). Run me inside a running Wayland session.\n",
disp);
return false;
}
host.registry = wl_display_get_registry(host.display);
wl_registry_add_listener(host.registry, &host_registry_listener, NULL);
wl_display_roundtrip(host.display);
if (!host.compositor || !host.wm_base) {
fprintf(stderr, "tabbed-windows: host lacks compositor/xdg_wm_base\n");
return false;
}
host.surface = wl_compositor_create_surface(host.compositor);
host.xdg_surface = xdg_wm_base_get_xdg_surface(host.wm_base, host.surface);
host.toplevel = xdg_surface_get_toplevel(host.xdg_surface);
xdg_toplevel_set_title(host.toplevel, "tabbed-windows");
xdg_surface_add_listener(host.xdg_surface, &host_xdg_surface_listener, NULL);
xdg_toplevel_add_listener(host.toplevel, &host_xdg_toplevel_listener, NULL);
xdg_wm_base_add_listener(host.wm_base, &host_xdg_wm_listener, NULL);
wl_surface_commit(host.surface);
wl_display_roundtrip(host.display);
if (host.seat) {
host.keyboard = wl_seat_get_keyboard(host.seat);
host.pointer = wl_seat_get_pointer(host.seat);
if (host.keyboard)
wl_keyboard_add_listener(host.keyboard, &host_kbd_listener, NULL);
if (host.pointer)
wl_pointer_add_listener(host.pointer, &host_ptr_listener, NULL);
}
/* Try EGL on the host wayland platform */
pfn_eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)
eglGetProcAddress("eglGetPlatformDisplayEXT");
if (!pfn_eglGetPlatformDisplayEXT) {
fprintf(stderr, "tabbed-windows: no EGL platform support; "
"falling back to software rendering\n");
host.egl_ok = false;
return true;
}
host.egl_display = pfn_eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT,
host.display, NULL);
if (host.egl_display == EGL_NO_DISPLAY) {
fprintf(stderr, "tabbed-windows: EGL display failed; "
"falling back to software rendering\n");
host.egl_ok = false;
return true;
}
if (!eglInitialize(host.egl_display, NULL, NULL)) {
fprintf(stderr, "tabbed-windows: eglInitialize failed; "
"falling back to software rendering\n");
host.egl_ok = false;
return true;
}
eglBindAPI(EGL_OPENGL_ES_API);
const EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 0,
EGL_NONE
};
EGLint ncfg = 0;
if (!eglChooseConfig(host.egl_display, config_attribs, &host.egl_config,
1, &ncfg) || ncfg < 1) {
fprintf(stderr, "tabbed-windows: no EGL config; software fallback\n");
host.egl_ok = false;
return true;
}
const EGLint ctx_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
host.egl_context = eglCreateContext(host.egl_display, host.egl_config,
EGL_NO_CONTEXT, ctx_attribs);
if (host.egl_context == EGL_NO_CONTEXT) {
fprintf(stderr, "tabbed-windows: EGL context failed; software fallback\n");
host.egl_ok = false;
return true;
}
host.width = 1280;
host.height = 800;
host.egl_window = wl_egl_window_create(host.surface, host.width, host.height);
host.egl_surface = eglCreateWindowSurface(host.egl_display, host.egl_config,
(EGLNativeWindowType)host.egl_window, NULL);
if (host.egl_surface == EGL_NO_SURFACE) {
fprintf(stderr, "tabbed-windows: EGL window surface failed; software fallback\n");
host.egl_ok = false;
return true;
}
if (!eglMakeCurrent(host.egl_display, host.egl_surface, host.egl_surface,
host.egl_context)) {
fprintf(stderr, "tabbed-windows: eglMakeCurrent failed; software fallback\n");
host.egl_ok = false;
return true;
}
eglSwapInterval(host.egl_display, 0);
pfn_eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)egl_proc("eglCreateImageKHR");
pfn_eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)egl_proc("eglDestroyImageKHR");
pfn_glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)
egl_proc("glEGLImageTargetTexture2DOES");
if (!gl_setup()) {
fprintf(stderr, "tabbed-windows: GL setup failed; software fallback\n");
host.egl_ok = false;
return true;
}
host.egl_ok = true;
fprintf(stderr, "tabbed-windows: EGL (GPU) rendering enabled\n");
return true;
}
/* ===================== Launch client ===================== */
static void launch_client(int idx) {
pid_t pid = fork();
if (pid == 0) {
const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
if (!xdg_runtime) xdg_runtime = "/tmp";
setenv("WAYLAND_DISPLAY", tabbed_socket, 1);
setenv("XDG_RUNTIME_DIR", xdg_runtime, 1);
setenv("GDK_BACKEND", "wayland", 1);
setenv("QT_QPA_PLATFORM", "wayland", 1);
unsetenv("DISPLAY");
execlp("/bin/sh", "sh", "-c", compositor.apps[idx]->cmd, NULL);
_exit(127);
}
if (pid > 0)
compositor.apps[idx]->pid = pid;
}
/* ===================== Crash handler ===================== */
static void crash_handler(int sig, siginfo_t *info, void *ctx) {
(void)sig; (void)info; (void)ctx;
void *bt[64];
int n = backtrace(bt, 64);
write(2, "\n=== BACKTRACE ===\n", 19);
backtrace_symbols_fd(bt, n, 2);
_exit(139);
}
static void sigchld_handler(int sig) {
(void)sig;
while (waitpid(-1, NULL, WNOHANG) > 0);
}
/* ===================== Main ===================== */
int main(int argc, char *argv[]) {
memset(&compositor, 0, sizeof(compositor));
memset(&host, 0, sizeof(host));
snprintf(tabbed_socket, sizeof(tabbed_socket), "tabbed-wayland-%d",
(int)getpid());
struct sigaction crash_sa = {
.sa_sigaction = crash_handler,
.sa_flags = SA_SIGINFO | SA_NODEFER,
};
sigaction(SIGSEGV, &crash_sa, NULL);
sigaction(SIGABRT, &crash_sa, NULL);
signal(SIGCHLD, sigchld_handler);
/* Initialize server display first so host configure events during
host_init() can safely resize the compositor output. */
compositor.display = wl_display_create();
if (!compositor.display) {
fprintf(stderr, "Failed to create Wayland display\n");
return 1;
}
drm_fd = open("/dev/dri/renderD128", O_RDWR);
if (drm_fd < 0)
fprintf(stderr, "tabbed-windows: warning: cannot open render node\n");
compositor.loop = wl_display_get_event_loop(compositor.display);
compositor.xkb_keymap_fd = -1;
wl_list_init(&compositor.surfaces);
wl_list_init(&compositor.buffers);
wl_list_init(&compositor.input_tracks);
wl_list_init(&compositor.output.resources);
/* Generate xkb keymap for our clients */
compositor.xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (compositor.xkb_ctx) {
struct xkb_rule_names names = {
.rules = NULL, .model = "pc105",
.layout = "us", .variant = NULL, .options = NULL,
};
compositor.xkb_keymap = xkb_keymap_new_from_names(
compositor.xkb_ctx, &names, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (compositor.xkb_keymap) {
compositor.xkb_keymap_str = xkb_keymap_get_as_string(
compositor.xkb_keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
if (compositor.xkb_keymap_str) {
compositor.xkb_keymap_size = strlen(compositor.xkb_keymap_str);
compositor.xkb_keymap_fd = memfd_create("xkb-keymap", MFD_CLOEXEC);
if (compositor.xkb_keymap_fd >= 0) {
write(compositor.xkb_keymap_fd, compositor.xkb_keymap_str,
compositor.xkb_keymap_size);
lseek(compositor.xkb_keymap_fd, 0, SEEK_SET);
}
}
}
}
/* Initial output size defaults; host configure will resize it. */
compositor.output.width = 1280;
compositor.output.height = 800;
compositor.output.stride = 1280 * 4;
compositor.output.data = calloc(1, (size_t)1280 * 800 * 4);
if (compositor.output.data)
memset(compositor.output.data, 0x22, (size_t)1280 * 800 * 4);
compositor.output.global = wl_global_create(compositor.display,
&wl_output_interface, 2, &compositor.output, output_bind);
wl_global_create(compositor.display, &wl_compositor_interface, 5,
NULL, bind_compositor);
wl_global_create(compositor.display, &wl_shm_interface, 2,
NULL, bind_shm);
wl_global_create(compositor.display, &zwp_linux_dmabuf_v1_interface, 5,
NULL, bind_dmabuf);
wl_global_create(compositor.display, &wl_drm_interface, 2, NULL, bind_drm);
wl_global_create(compositor.display, &xdg_wm_base_interface, 6,
NULL, bind_xdg_wm_base);
wl_global_create(compositor.display, &wl_subcompositor_interface, 1,
NULL, bind_subcompositor);
wl_global_create(compositor.display, &wl_data_device_manager_interface, 3,
NULL, bind_data_device_manager);
wl_global_create(compositor.display, &wl_seat_interface, 7,
NULL, seat_bind);
if (wl_display_add_socket(compositor.display, tabbed_socket) < 0) {
fprintf(stderr, "Failed to add socket '%s': %s\n", tabbed_socket,
strerror(errno));
return 1;
}
/* Now connect to the host compositor and present inside it. */
if (!host_init()) {
fprintf(stderr, "tabbed-windows: could not start nested session\n");
return 1;
}
printf("Wayland socket: %s\n", tabbed_socket);
/* Programs to launch come from command-line arguments */
int total_apps = argc - 1;
if (total_apps == 0) {
fprintf(stderr, "usage: %s <prog> [prog...]\n", argv[0]);
return 1;
}
if (total_apps > MAX_APPS) total_apps = MAX_APPS;
for (int i = 0; i < total_apps; i++) {
struct tabbed_app *app = calloc(1, sizeof(*app));
app->cmd = strdup(argv[i + 1]);
app->pid = -1;
app->surface = NULL;
compositor.apps[i] = app;
}
compositor.num_apps = total_apps;
for (int i = 0; i < compositor.num_apps; i++)
launch_client(i);
compositor.clients_launched = true;
/* Main event loop: service both the server and the host displays */
int server_fd = wl_event_loop_get_fd(compositor.loop);
int host_fd = wl_display_get_fd(host.display);
fprintf(stderr, "tabbed-windows: running. Ctrl+PageUp/PageDown switches tabs.\n");
for (;;) {
wl_display_dispatch_pending(host.display);
struct pollfd fds[2] = {
{ .fd = server_fd, .events = POLLIN },
{ .fd = host_fd, .events = POLLIN },
};
int timeout = needs_redraw ? 0 : -1;
if (poll(fds, 2, timeout) < 0) {
if (errno == EINTR) continue;
break;
}
if (fds[0].revents & (POLLIN | POLLERR))
wl_event_loop_dispatch(compositor.loop, 0);
wl_display_flush_clients(compositor.display);
if (fds[1].revents & (POLLIN | POLLERR))
wl_display_dispatch(host.display);
if (needs_redraw) {
needs_redraw = false;
/* first visible frame may arrive before host configure */
if (host.configured)
render_and_present();
}
wl_display_flush(host.display);
}
for (int i = 0; i < compositor.num_apps; i++) {
if (compositor.apps[i]->pid > 0)
kill(compositor.apps[i]->pid, SIGTERM);
free(compositor.apps[i]->cmd);
free(compositor.apps[i]);
}
free(compositor.output.data);
if (compositor.xkb_keymap_fd >= 0) close(compositor.xkb_keymap_fd);
free(compositor.xkb_keymap_str);
if (compositor.xkb_keymap) xkb_keymap_unref(compositor.xkb_keymap);
if (compositor.xkb_ctx) xkb_context_unref(compositor.xkb_ctx);
return 0;
}