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.
2862 lines
106 KiB
C
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, ®ion_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;
|
|
}
|