1821 lines
69 KiB
C
1821 lines
69 KiB
C
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/un.h>
|
|
#include <sys/socket.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
#include <execinfo.h>
|
|
|
|
#include <wayland-server.h>
|
|
#include <wayland-server-protocol.h>
|
|
#include "xdg-shell-server.h"
|
|
#include <gtk/gtk.h>
|
|
#include <xkbcommon/xkbcommon.h>
|
|
|
|
#define MAX_APPS 8
|
|
|
|
static const char *default_apps[] = {
|
|
NULL /* no default apps */
|
|
};
|
|
|
|
struct tabbed_surface;
|
|
struct tabbed_compositor;
|
|
|
|
struct tabbed_pool {
|
|
int fd;
|
|
size_t size;
|
|
unsigned char *data;
|
|
};
|
|
|
|
struct tabbed_buffer {
|
|
struct wl_resource *resource;
|
|
struct tabbed_pool *pool;
|
|
int32_t offset;
|
|
int32_t width, height, stride;
|
|
uint32_t format;
|
|
};
|
|
|
|
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;
|
|
GtkWidget *tab_label;
|
|
int tab_index;
|
|
unsigned char *pixels;
|
|
int pixel_width, pixel_height, pixel_stride;
|
|
int geom_x, geom_y, geom_width, geom_height;
|
|
};
|
|
|
|
struct tabbed_app {
|
|
char *cmd;
|
|
pid_t pid;
|
|
GtkWidget *tab_label;
|
|
int tab_index;
|
|
struct tabbed_surface *surface;
|
|
};
|
|
|
|
struct tabbed_compositor {
|
|
struct wl_display *display;
|
|
struct wl_event_loop *loop;
|
|
struct wl_list surfaces;
|
|
struct tabbed_output output;
|
|
struct tabbed_surface *active_surface;
|
|
struct tabbed_app *apps[MAX_APPS];
|
|
int num_apps;
|
|
struct wl_list input_tracks;
|
|
GtkWidget *window;
|
|
GtkWidget *notebook;
|
|
GtkWidget *canvas;
|
|
uint32_t serial;
|
|
bool clients_launched;
|
|
bool resize_pending;
|
|
int resize_w, resize_h;
|
|
/* xkb */
|
|
struct xkb_context *xkb_ctx;
|
|
struct xkb_keymap *xkb_keymap;
|
|
struct xkb_state *xkb_state;
|
|
char *xkb_keymap_str;
|
|
int xkb_keymap_fd;
|
|
int xkb_keymap_size;
|
|
/* keyboard focus */
|
|
struct tabbed_surface *kbd_focus;
|
|
struct wl_resource *kbd_resource;
|
|
/* pointer tracking */
|
|
bool pointer_entered;
|
|
double pointer_x, pointer_y;
|
|
uint32_t input_serial;
|
|
};
|
|
|
|
static struct tabbed_compositor compositor;
|
|
|
|
/* Forward declarations for interface implementations */
|
|
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_seat_interface seat_impl;
|
|
static const struct wl_buffer_interface buffer_impl;
|
|
static const struct xdg_positioner_interface positioner_impl;
|
|
static void launch_client(const char *cmd, int tab_index);
|
|
|
|
/* 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);
|
|
wl_list_remove(&surface->link);
|
|
if (compositor.active_surface == surface)
|
|
compositor.active_surface = NULL;
|
|
if (surface->pending_release)
|
|
wl_buffer_send_release(surface->pending_release->resource);
|
|
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_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;
|
|
}
|
|
|
|
static void copy_buffer_to_display(struct tabbed_surface *surface) {
|
|
struct tabbed_output *out = &compositor.output;
|
|
if (!out->data || !surface->pixels || out->width <= 0 || out->height <= 0 || out->stride <= 0)
|
|
return;
|
|
|
|
size_t out_size = (size_t)out->height * (size_t)out->stride;
|
|
memset(out->data, 0x22, out_size);
|
|
|
|
/* Blit the client's content. GTK3 apps render CSD shadow margins around
|
|
their content and report where the content starts via
|
|
xdg_surface.set_window_geometry (e.g. galculator: geom 25,25). Without
|
|
honoring that we would show the shadow as a margin on the left/top.
|
|
Clients that never set geometry leave geom_width/height == 0; fall
|
|
back to the full buffer then. foot reports geom_y = -26, so clamp
|
|
negative offsets to 0. */
|
|
int src_x = 0, src_y = 0;
|
|
int src_w = surface->pixel_width;
|
|
int src_h = surface->pixel_height;
|
|
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 >= surface->pixel_width) src_x = surface->pixel_width - 1;
|
|
if (src_y >= surface->pixel_height) src_y = surface->pixel_height - 1;
|
|
if (src_w > surface->pixel_width - src_x)
|
|
src_w = surface->pixel_width - src_x;
|
|
if (src_h > surface->pixel_height - src_y)
|
|
src_h = surface->pixel_height - src_y;
|
|
}
|
|
|
|
int cw = (src_w < out->width) ? src_w : out->width;
|
|
int ch = (src_h < out->height) ? src_h : out->height;
|
|
|
|
if (cw <= 0 || ch <= 0) return;
|
|
if (surface->pixel_stride <= 0 || out->stride <= 0) return;
|
|
if (surface->pixels == NULL || out->data == NULL) return;
|
|
|
|
for (int y = 0; y < ch; y++) {
|
|
void *dst = out->data + y * out->stride;
|
|
void *src = surface->pixels + (src_y + y) * surface->pixel_stride
|
|
+ src_x * 4;
|
|
if ((unsigned char*)dst < (unsigned char*)out->data ||
|
|
(unsigned char*)dst + cw*4 > (unsigned char*)out->data + out->height*out->stride) break;
|
|
if ((unsigned char*)src < (unsigned char*)surface->pixels ||
|
|
(unsigned char*)src + cw*4 > (unsigned char*)surface->pixels + surface->pixel_height*surface->pixel_stride) break;
|
|
memcpy(dst, src, cw * 4);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
if (surface->tab_label && surface->title)
|
|
gtk_label_set_text(GTK_LABEL(surface->tab_label), surface->title);
|
|
|
|
/* Dispatch the pending frame callback. Several clients (notably
|
|
foot) request wl_surface.frame on the first commit and will not
|
|
render their real buffer until wl_callback.done is sent, so it
|
|
must fire even on this early configure path. */
|
|
if (surface->frame_callback) {
|
|
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;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Buffer commit — cache pixels for safe access later */
|
|
if (surface->has_buffer && surface->buffer) {
|
|
struct tabbed_buffer *buf = surface->buffer;
|
|
if (buf->pool && buf->pool->data) {
|
|
const size_t bpp = 4;
|
|
/* Defensive check: never read outside the mapped pool even if a
|
|
client sent a bad attach after a valid create_buffer. */
|
|
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;
|
|
surface->pixels = malloc(size);
|
|
if (surface->pixels)
|
|
memcpy(surface->pixels, buf->pool->data + buf->offset, size);
|
|
} else {
|
|
/* Invalid geometry: drop the frame, keep a clean surface. */
|
|
free(surface->pixels);
|
|
surface->pixels = NULL;
|
|
wl_client_post_implementation_error(surface->client,
|
|
"invalid buffer geometry on commit");
|
|
}
|
|
/* We deep-copy the above so we no longer need the client's
|
|
buffer. However we must NOT release the buffer currently
|
|
being committed: GTK3 reuses a released committed buffer as
|
|
its next staging surface, so releasing it again on a
|
|
recommit would hit its staging/destroy bookkeeping and
|
|
abort the client (cairo refcount assert on resize). Instead
|
|
we defer: on this commit, release only the buffer from the
|
|
previous commit (if any and different), then remember the
|
|
current one to release on the next commit. */
|
|
if (surface->pending_release &&
|
|
surface->pending_release != buf)
|
|
wl_buffer_send_release(surface->pending_release->resource);
|
|
surface->pending_release = buf;
|
|
}
|
|
if (surface == compositor.active_surface) {
|
|
copy_buffer_to_display(surface);
|
|
gtk_widget_queue_draw(compositor.canvas);
|
|
}
|
|
} else {
|
|
if (surface->pending_release)
|
|
wl_buffer_send_release(surface->pending_release->resource);
|
|
surface->pending_release = NULL;
|
|
free(surface->pixels);
|
|
surface->pixels = NULL;
|
|
if (surface == compositor.active_surface) {
|
|
struct tabbed_output *o = &compositor.output;
|
|
if (o->data && o->height > 0 && o->stride > 0)
|
|
memset(o->data, 0x22, (size_t)o->height * (size_t)o->stride);
|
|
gtk_widget_queue_draw(compositor.canvas);
|
|
}
|
|
}
|
|
|
|
/* frame callback */
|
|
if (surface->frame_callback) {
|
|
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 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;
|
|
/* Clear any surface caches that reference this pool's buffers */
|
|
struct tabbed_surface *s;
|
|
wl_list_for_each(s, &compositor.surfaces, link) {
|
|
if (s->pending_release && s->pending_release->pool == pool)
|
|
s->pending_release = NULL;
|
|
if (s->buffer && s->buffer->pool == pool) {
|
|
s->buffer = NULL;
|
|
s->has_buffer = false;
|
|
free(s->pixels);
|
|
s->pixels = NULL;
|
|
}
|
|
}
|
|
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);
|
|
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;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
/* Validate the client-supplied layout against the pool before accepting
|
|
it. Any negative size/offset or a stride smaller than one scanline per
|
|
BPP is malformed; the buffer must also fit inside the pool. Rejecting
|
|
here prevents out-of-bounds reads at commit time (see surface_commit). */
|
|
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;
|
|
buf->offset = offset;
|
|
buf->width = width;
|
|
buf->height = height;
|
|
buf->stride = stride;
|
|
buf->format = format;
|
|
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 = malloc(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,
|
|
};
|
|
|
|
/* ===================== xdg_wm_base ===================== */
|
|
|
|
static void 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 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 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 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 = xdg_wm_base_destroy,
|
|
.create_positioner = xdg_wm_base_create_positioner,
|
|
.get_xdg_surface = xdg_wm_base_get_xdg_surface,
|
|
.pong = xdg_wm_base_pong,
|
|
};
|
|
|
|
|
|
|
|
/* ===================== xdg_surface ===================== */
|
|
|
|
static void xdg_surface_destroy(struct wl_client *client, struct wl_resource *resource) {
|
|
(void)client;
|
|
wl_resource_destroy(resource);
|
|
}
|
|
|
|
static void 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 = compositor.apps[i]->tab_index;
|
|
surface->tab_label = compositor.apps[i]->tab_label;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Make first surface active */
|
|
if (!compositor.active_surface)
|
|
compositor.active_surface = surface;
|
|
}
|
|
|
|
static void 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 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 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 = xdg_surface_destroy,
|
|
.get_toplevel = xdg_surface_get_toplevel,
|
|
.get_popup = xdg_surface_get_popup,
|
|
.set_window_geometry = xdg_surface_set_window_geometry,
|
|
.ack_configure = xdg_surface_ack_configure,
|
|
};
|
|
|
|
/* ===================== xdg_toplevel ===================== */
|
|
|
|
static void xdg_toplevel_destroy(struct wl_client *client, struct wl_resource *resource) {
|
|
(void)client;
|
|
wl_resource_destroy(resource);
|
|
}
|
|
|
|
static void xdg_toplevel_set_parent(struct wl_client *client,
|
|
struct wl_resource *resource,
|
|
struct wl_resource *parent)
|
|
{
|
|
(void)client; (void)resource; (void)parent;
|
|
}
|
|
|
|
static void 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 = g_strdup(title);
|
|
|
|
if (surface->tab_label && title && title[0])
|
|
gtk_label_set_text(GTK_LABEL(surface->tab_label), title);
|
|
}
|
|
|
|
static void 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 = g_strdup(app_id);
|
|
}
|
|
|
|
static void 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 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 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 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 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 xdg_toplevel_set_maximized(struct wl_client *client,
|
|
struct wl_resource *resource)
|
|
{
|
|
(void)client; (void)resource;
|
|
}
|
|
static void xdg_toplevel_unset_maximized(struct wl_client *client,
|
|
struct wl_resource *resource)
|
|
{
|
|
(void)client; (void)resource;
|
|
}
|
|
static void xdg_toplevel_set_fullscreen(struct wl_client *client,
|
|
struct wl_resource *resource,
|
|
struct wl_resource *output)
|
|
{
|
|
(void)client; (void)resource; (void)output;
|
|
}
|
|
static void xdg_toplevel_unset_fullscreen(struct wl_client *client,
|
|
struct wl_resource *resource)
|
|
{
|
|
(void)client; (void)resource;
|
|
}
|
|
static void 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 = xdg_toplevel_destroy,
|
|
.set_parent = xdg_toplevel_set_parent,
|
|
.set_title = xdg_toplevel_set_title,
|
|
.set_app_id = xdg_toplevel_set_app_id,
|
|
.show_window_menu = xdg_toplevel_show_window_menu,
|
|
.move = xdg_toplevel_move,
|
|
.resize = xdg_toplevel_resize,
|
|
.set_max_size = xdg_toplevel_set_max_size,
|
|
.set_min_size = xdg_toplevel_set_min_size,
|
|
.set_maximized = xdg_toplevel_set_maximized,
|
|
.unset_maximized = xdg_toplevel_unset_maximized,
|
|
.set_fullscreen = xdg_toplevel_set_fullscreen,
|
|
.unset_fullscreen = xdg_toplevel_unset_fullscreen,
|
|
.set_minimized = xdg_toplevel_set_minimized,
|
|
};
|
|
|
|
/* ===================== xdg_popup ===================== */
|
|
|
|
static void xdg_popup_destroy(struct wl_client *client, struct wl_resource *resource) {
|
|
(void)client;
|
|
wl_resource_destroy(resource);
|
|
}
|
|
|
|
static void 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 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 = xdg_popup_destroy,
|
|
.grab = xdg_popup_grab,
|
|
.reposition = 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 Compositor", "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);
|
|
}
|
|
|
|
/* ===================== wl_seat ===================== */
|
|
|
|
static void seat_destroy(struct wl_client *client, struct wl_resource *resource) {
|
|
(void)client;
|
|
wl_resource_destroy(resource);
|
|
}
|
|
|
|
/* ===================== Input tracking ===================== */
|
|
|
|
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;
|
|
};
|
|
|
|
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 subcompositor_get_subsurface(struct wl_client *client,
|
|
struct wl_resource *resource,
|
|
uint32_t id,
|
|
struct wl_resource *surface,
|
|
struct wl_resource *parent)
|
|
{
|
|
(void)resource; (void)surface; (void)parent;
|
|
struct wl_resource *sub = wl_resource_create(client,
|
|
&wl_subsurface_interface, 1, id);
|
|
if (!sub) { wl_client_post_no_memory(client); return; }
|
|
wl_resource_set_implementation(sub, &subsurface_impl, NULL, NULL);
|
|
}
|
|
|
|
/* ===================== wl_subsurface ===================== */
|
|
|
|
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;(void)r;(void)x;(void)y; }
|
|
static void subsurface_place_above(struct wl_client *c, struct wl_resource *r, struct wl_resource *sibling) { (void)c;(void)r;(void)sibling; }
|
|
static void subsurface_place_below(struct wl_client *c, struct wl_resource *r, struct wl_resource *sibling) { (void)c;(void)r;(void)sibling; }
|
|
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; }
|
|
static 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);
|
|
}
|
|
|
|
static gboolean flush_resize_cb(gpointer data);
|
|
|
|
/* ===================== 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;
|
|
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);
|
|
|
|
/* Notify all outputs */
|
|
struct wl_resource *res;
|
|
wl_list_for_each(res, &out->resources, link)
|
|
output_send_all(res);
|
|
|
|
/* Reconfigure ALL surfaces with new size */
|
|
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 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);
|
|
|
|
if (surface && surface->pixels) {
|
|
copy_buffer_to_display(surface);
|
|
} else {
|
|
struct tabbed_output *o = &compositor.output;
|
|
if (o->data && o->height > 0 && o->stride > 0)
|
|
memset(o->data, 0x22, (size_t)o->height * (size_t)o->stride);
|
|
}
|
|
gtk_widget_queue_draw(compositor.canvas);
|
|
wl_display_flush_clients(compositor.display);
|
|
}
|
|
|
|
/* ===================== GTK canvas draw ===================== */
|
|
|
|
static gboolean canvas_draw_cb(GtkWidget *widget, cairo_t *cr, gpointer user_data) {
|
|
(void)widget; (void)user_data;
|
|
struct tabbed_output *out = &compositor.output;
|
|
if (!out->data || out->width <= 0 || out->height <= 0) {
|
|
cairo_set_source_rgb(cr, 0x22 / 255.0, 0x22 / 255.0, 0x22 / 255.0);
|
|
cairo_paint(cr);
|
|
return FALSE;
|
|
}
|
|
|
|
cairo_surface_t *image = cairo_image_surface_create_for_data(
|
|
out->data, CAIRO_FORMAT_ARGB32, out->width, out->height, out->stride);
|
|
cairo_set_source_surface(cr, image, 0, 0);
|
|
cairo_paint(cr);
|
|
cairo_surface_destroy(image);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean canvas_resize_cb(GtkWidget *w, GtkAllocation *alloc, gpointer data) {
|
|
(void)w; (void)data;
|
|
if (alloc->width < 50 || alloc->height < 50) return FALSE;
|
|
compositor.resize_pending = true;
|
|
compositor.resize_w = alloc->width;
|
|
compositor.resize_h = alloc->height;
|
|
g_idle_add(flush_resize_cb, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
/* ===================== Input forwarding ===================== */
|
|
|
|
static gboolean canvas_key_cb(GtkWidget *w, GdkEventKey *ev, gpointer data) {
|
|
(void)w; (void)data;
|
|
if (!compositor.active_surface || !compositor.active_surface->client) return FALSE;
|
|
struct wl_resource *kbd = kbd_for_client(compositor.active_surface->client);
|
|
if (!kbd) {
|
|
fprintf(stderr, "DEBUG: no kbd for active client\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (compositor.kbd_focus != compositor.active_surface)
|
|
send_kbd_enter(compositor.active_surface);
|
|
|
|
uint32_t keycode = ev->hardware_keycode;
|
|
if (keycode >= 8) keycode -= 8;
|
|
compositor.input_serial++;
|
|
uint32_t time = ev->time;
|
|
uint32_t wl_state = (ev->type == GDK_KEY_PRESS) ?
|
|
WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED;
|
|
fprintf(stderr, "DEBUG: key %s hw=%u evdev=%u\n",
|
|
wl_state ? "press" : "release",
|
|
(unsigned)ev->hardware_keycode, (unsigned)keycode);
|
|
|
|
/* Track modifiers manually — xkb indices: 0=Shift,1=Lock,2=Ctrl,3=Alt */
|
|
static uint32_t mod_depressed = 0;
|
|
static uint32_t mod_locked = 0;
|
|
if (keycode == 42 || keycode == 54) /* shift */
|
|
mod_depressed = (mod_depressed & ~(1<<0)) | (wl_state << 0);
|
|
if (keycode == 29 || keycode == 97) /* ctrl */
|
|
mod_depressed = (mod_depressed & ~(1<<2)) | (wl_state << 2);
|
|
if (keycode == 56 || keycode == 100) /* alt */
|
|
mod_depressed = (mod_depressed & ~(1<<3)) | (wl_state << 3);
|
|
if (keycode == 58) { /* caps lock — toggles on release, index 1 */
|
|
if (!wl_state) mod_locked ^= (1 << 1);
|
|
}
|
|
wl_keyboard_send_modifiers(kbd, compositor.input_serial,
|
|
mod_depressed, 0, mod_locked, 0);
|
|
|
|
wl_keyboard_send_key(kbd, compositor.input_serial,
|
|
time, keycode, wl_state);
|
|
|
|
wl_display_flush_clients(compositor.display);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean canvas_button_cb(GtkWidget *w, GdkEventButton *ev, gpointer data) {
|
|
(void)w; (void)data;
|
|
gtk_widget_grab_focus(w);
|
|
if (!compositor.active_surface || !compositor.active_surface->client) return FALSE;
|
|
struct wl_resource *ptr = ptr_for_client(compositor.active_surface->client);
|
|
if (!ptr) {
|
|
fprintf(stderr, "DEBUG: no ptr for active client\n");
|
|
return FALSE;
|
|
}
|
|
|
|
compositor.input_serial++;
|
|
wl_pointer_send_enter(ptr, compositor.input_serial,
|
|
compositor.active_surface->resource,
|
|
wl_fixed_from_double(ev->x),
|
|
wl_fixed_from_double(ev->y));
|
|
|
|
uint32_t button = 0;
|
|
if (ev->button == 1) button = 0x110;
|
|
else if (ev->button == 2) button = 0x111;
|
|
else if (ev->button == 3) button = 0x112;
|
|
|
|
if (button) {
|
|
compositor.input_serial++;
|
|
wl_pointer_send_button(ptr, compositor.input_serial, ev->time,
|
|
button, ev->type == GDK_BUTTON_PRESS ? 1 : 0);
|
|
}
|
|
|
|
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);
|
|
fprintf(stderr, "DEBUG: button %u %s at (%.0f,%.0f)\n",
|
|
(unsigned)ev->button,
|
|
ev->type == GDK_BUTTON_PRESS ? "press" : "release",
|
|
ev->x, ev->y);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean canvas_motion_cb(GtkWidget *w, GdkEventMotion *ev, gpointer data) {
|
|
(void)w; (void)data;
|
|
if (!compositor.active_surface || !compositor.active_surface->client) return FALSE;
|
|
struct wl_resource *ptr = ptr_for_client(compositor.active_surface->client);
|
|
if (!ptr) return FALSE;
|
|
|
|
wl_pointer_send_motion(ptr, ev->time,
|
|
wl_fixed_from_double(ev->x),
|
|
wl_fixed_from_double(ev->y));
|
|
|
|
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);
|
|
return TRUE;
|
|
}
|
|
|
|
/* ===================== GTK tab switch ===================== */
|
|
|
|
static void tab_switch_cb(GtkNotebook *nb, GtkWidget *page, gint page_num,
|
|
gpointer user_data)
|
|
{
|
|
(void)nb; (void)page; (void)user_data;
|
|
for (int i = 0; i < compositor.num_apps; i++) {
|
|
if (compositor.apps[i]->tab_index == page_num) {
|
|
set_active_surface(compositor.apps[i]->surface);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ===================== Launch client ===================== */
|
|
|
|
static void launch_client(const char *cmd, int tab_index) {
|
|
(void)tab_index;
|
|
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-wayland-0", 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", cmd, NULL);
|
|
_exit(127);
|
|
}
|
|
|
|
if (pid > 0)
|
|
compositor.apps[tab_index]->pid = pid;
|
|
}
|
|
|
|
/* ===================== Wayland event loop integration ===================== */
|
|
|
|
/* Idle callback to apply pending resize & launch clients */
|
|
static gboolean flush_resize_cb(gpointer data) {
|
|
(void)data;
|
|
if (!compositor.resize_pending) return FALSE;
|
|
compositor.resize_pending = false;
|
|
output_resize(compositor.resize_w, compositor.resize_h);
|
|
if (!compositor.clients_launched) {
|
|
compositor.clients_launched = true;
|
|
for (int i = 0; i < compositor.num_apps; i++)
|
|
launch_client(compositor.apps[i]->cmd, i);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean wayland_io_cb(GIOChannel *channel, GIOCondition condition,
|
|
gpointer data)
|
|
{
|
|
(void)channel; (void)condition;
|
|
struct wl_display *display = data;
|
|
wl_event_loop_dispatch(wl_display_get_event_loop(display), 0);
|
|
wl_display_flush_clients(display);
|
|
if (compositor.resize_pending)
|
|
flush_resize_cb(NULL);
|
|
return TRUE;
|
|
}
|
|
|
|
/* ===================== 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);
|
|
}
|
|
|
|
/* ===================== SIGCHLD handler ===================== */
|
|
|
|
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));
|
|
|
|
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);
|
|
|
|
/* Parse CLI: apps after -- */
|
|
int num_requested = 0;
|
|
const char *requested_apps[MAX_APPS] = {0};
|
|
bool found_ddash = false;
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "--") == 0) { found_ddash = true; continue; }
|
|
if (found_ddash && num_requested < MAX_APPS)
|
|
requested_apps[num_requested++] = argv[i];
|
|
}
|
|
|
|
/* Initialize Wayland display */
|
|
compositor.display = wl_display_create();
|
|
if (!compositor.display) {
|
|
fprintf(stderr, "Failed to create Wayland display\n");
|
|
return 1;
|
|
}
|
|
compositor.loop = wl_display_get_event_loop(compositor.display);
|
|
compositor.xkb_keymap_fd = -1;
|
|
|
|
/* Generate xkb keymap */
|
|
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_state = xkb_state_new(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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set up output — minimal size, grows on first size-allocate */
|
|
compositor.output.width = 320;
|
|
compositor.output.height = 240;
|
|
compositor.output.stride = 320 * 4;
|
|
compositor.output.data = calloc(1, 320 * 240 * 4);
|
|
if (compositor.output.data)
|
|
memset(compositor.output.data, 0x22, 320 * 240 * 4);
|
|
wl_list_init(&compositor.output.resources);
|
|
compositor.output.global = wl_global_create(compositor.display,
|
|
&wl_output_interface, 2, &compositor.output, output_bind);
|
|
|
|
/* Register globals */
|
|
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, &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);
|
|
|
|
wl_list_init(&compositor.surfaces);
|
|
wl_list_init(&compositor.input_tracks);
|
|
|
|
/* Add Wayland socket */
|
|
if (wl_display_add_socket(compositor.display, "tabbed-wayland-0") < 0) {
|
|
fprintf(stderr, "Failed to add socket 'tabbed-wayland-0': %s\n",
|
|
strerror(errno));
|
|
return 1;
|
|
}
|
|
|
|
printf("Wayland socket: tabbed-wayland-0\n");
|
|
printf("Set WAYLAND_DISPLAY=tabbed-wayland-0 to connect\n");
|
|
|
|
/* Read apps from tab-commands file, CLI --args, or defaults */
|
|
char *file_apps[MAX_APPS];
|
|
int total_apps = 0;
|
|
const char **apps_to_launch = NULL;
|
|
|
|
FILE *fp = fopen("tab-commands", "r");
|
|
if (fp) {
|
|
char line[512];
|
|
while (fgets(line, sizeof(line), fp) && total_apps < MAX_APPS) {
|
|
char *nl = strchr(line, '\n');
|
|
if (nl) *nl = '\0';
|
|
char *cr = strchr(line, '\r');
|
|
if (cr) *cr = '\0';
|
|
if (line[0] == '#' || line[0] == '\0') continue;
|
|
file_apps[total_apps] = strdup(line);
|
|
total_apps++;
|
|
}
|
|
fclose(fp);
|
|
apps_to_launch = (const char **)file_apps;
|
|
}
|
|
|
|
if (num_requested > 0) {
|
|
apps_to_launch = requested_apps;
|
|
total_apps = num_requested;
|
|
}
|
|
|
|
if (!apps_to_launch) {
|
|
apps_to_launch = default_apps;
|
|
total_apps = 0;
|
|
while (apps_to_launch[total_apps]) total_apps++;
|
|
}
|
|
|
|
if (total_apps > MAX_APPS) total_apps = MAX_APPS;
|
|
|
|
/* Force X11 backend — compositor must not use Wayland for its own UI */
|
|
gdk_set_allowed_backends("x11");
|
|
gtk_init(&argc, &argv);
|
|
|
|
/* Build UI */
|
|
compositor.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_title(GTK_WINDOW(compositor.window), "Tabbed Wayland");
|
|
gtk_window_set_default_size(GTK_WINDOW(compositor.window), 900, 700);
|
|
g_signal_connect(compositor.window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
|
|
|
|
GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
|
gtk_container_add(GTK_CONTAINER(compositor.window), vbox);
|
|
|
|
compositor.notebook = gtk_notebook_new();
|
|
gtk_notebook_set_scrollable(GTK_NOTEBOOK(compositor.notebook), TRUE);
|
|
gtk_notebook_set_show_border(GTK_NOTEBOOK(compositor.notebook), FALSE);
|
|
gtk_box_pack_start(GTK_BOX(vbox), compositor.notebook, FALSE, FALSE, 0);
|
|
|
|
compositor.canvas = gtk_drawing_area_new();
|
|
gtk_widget_set_hexpand(compositor.canvas, TRUE);
|
|
gtk_widget_set_vexpand(compositor.canvas, TRUE);
|
|
gtk_widget_set_can_focus(compositor.canvas, TRUE);
|
|
gtk_widget_add_events(compositor.canvas,
|
|
GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
|
|
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
|
|
GDK_POINTER_MOTION_MASK);
|
|
g_signal_connect(compositor.canvas, "draw",
|
|
G_CALLBACK(canvas_draw_cb), NULL);
|
|
g_signal_connect(compositor.canvas, "size-allocate",
|
|
G_CALLBACK(canvas_resize_cb), NULL);
|
|
g_signal_connect(compositor.canvas, "key-press-event",
|
|
G_CALLBACK(canvas_key_cb), NULL);
|
|
g_signal_connect(compositor.canvas, "key-release-event",
|
|
G_CALLBACK(canvas_key_cb), NULL);
|
|
g_signal_connect(compositor.canvas, "button-press-event",
|
|
G_CALLBACK(canvas_button_cb), NULL);
|
|
g_signal_connect(compositor.canvas, "button-release-event",
|
|
G_CALLBACK(canvas_button_cb), NULL);
|
|
g_signal_connect(compositor.canvas, "motion-notify-event",
|
|
G_CALLBACK(canvas_motion_cb), NULL);
|
|
gtk_box_pack_start(GTK_BOX(vbox), compositor.canvas, TRUE, TRUE, 0);
|
|
|
|
/* Apply dark theme */
|
|
GtkCssProvider *css = gtk_css_provider_new();
|
|
gtk_css_provider_load_from_data(css,
|
|
"window { background-color: #222; color: #ccc; }"
|
|
"notebook header { background-color: #333; }"
|
|
"notebook tab { background-color: #444; color: #ddd; padding: 4px 12px; }"
|
|
"notebook tab:checked { background-color: #555; }"
|
|
"label { color: #ccc; }", -1, NULL);
|
|
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
|
|
GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
|
|
|
/* Create app entries and tabs (defer launch to after first resize) */
|
|
compositor.num_apps = total_apps;
|
|
for (int i = 0; i < total_apps; i++) {
|
|
struct tabbed_app *app = calloc(1, sizeof(*app));
|
|
app->cmd = g_strdup(apps_to_launch[i]);
|
|
app->pid = -1;
|
|
app->tab_index = i;
|
|
|
|
GtkWidget *label = gtk_label_new(apps_to_launch[i]);
|
|
app->tab_label = label;
|
|
|
|
GtkWidget *placeholder = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
|
gtk_notebook_append_page(GTK_NOTEBOOK(compositor.notebook),
|
|
placeholder, label);
|
|
|
|
compositor.apps[i] = app;
|
|
}
|
|
|
|
g_signal_connect(compositor.notebook, "switch-page",
|
|
G_CALLBACK(tab_switch_cb), NULL);
|
|
|
|
/* Register Wayland watch — clients can connect, but won't be processed
|
|
until gtk_main runs (no blocking, no corruption) */
|
|
int wayland_fd = wl_event_loop_get_fd(compositor.loop);
|
|
GIOChannel *channel = g_io_channel_unix_new(wayland_fd);
|
|
g_io_add_watch(channel, G_IO_IN, wayland_io_cb, compositor.display);
|
|
g_io_channel_unref(channel);
|
|
|
|
gtk_widget_show_all(compositor.window);
|
|
gtk_widget_grab_focus(compositor.canvas);
|
|
/* Clients are launched by canvas_resize_cb on first size-allocate */
|
|
gtk_main();
|
|
|
|
/* Cleanup */
|
|
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_state) xkb_state_unref(compositor.xkb_state);
|
|
if (compositor.xkb_keymap) xkb_keymap_unref(compositor.xkb_keymap);
|
|
if (compositor.xkb_ctx) xkb_context_unref(compositor.xkb_ctx);
|
|
|
|
return 0;
|
|
}
|