windowmaker-wl/WINGs/wview_wl.c

701 lines
19 KiB
C

/*
* wview_wl.c - Wayland view backend for WINGs
*
* Views are logical layout containers in Wayland mode.
* No X11 dependencies. Rendering is handled by frame_paint (pixman buffers).
*/
#include "WINGsP.h"
#include <pixman.h>
/* Synthetic window IDs for Wayland views (distinct from frame buffer IDs) */
#define WL_VIEW_ID_BASE 0x20000000
static unsigned int wl_view_next_id = WL_VIEW_ID_BASE;
/* Callback set by WM backend to commit frame_buf after paint */
void (*W_ViewCommitBackingFn)(unsigned long id) = NULL;
void (*W_ViewDestroyBackingFn)(unsigned long id) = NULL;
/* Clipboard hooks set by WM backend for cross-client clipboard */
void (*W_ClipboardStoreFn)(const char *text, int len) = NULL;
char *(*W_ClipboardFetchFn)(int *len_out) = NULL;
extern void wl_view_destroy_backing_impl(unsigned long id);
/* Simple view lookup table (ID → W_View*) */
#define VIEW_TABLE_SIZE 256
static struct { Window id; W_View *view; void *backing; } view_table[VIEW_TABLE_SIZE];
static void view_table_put(Window id, W_View *view)
{
unsigned idx = ((unsigned)id) % VIEW_TABLE_SIZE;
for (int i = 0; i < VIEW_TABLE_SIZE; i++) {
unsigned slot = (idx + i) % VIEW_TABLE_SIZE;
if (view_table[slot].id == 0 || view_table[slot].id == id) {
view_table[slot].id = id;
view_table[slot].view = view;
return;
}
}
}
W_View *W_GetViewForWindow(Window id)
{
unsigned idx = ((unsigned)id) % VIEW_TABLE_SIZE;
for (int i = 0; i < VIEW_TABLE_SIZE; i++) {
unsigned slot = (idx + i) % VIEW_TABLE_SIZE;
if (view_table[slot].id == id)
return view_table[slot].view;
if (view_table[slot].id == 0) {
//wwarning("W_GetViewForWindow: id=0x%lx NOT FOUND (slot %u empty)", (unsigned long)id, slot);
return NULL;
}
}
return NULL;
}
/* Get/set backing pixman image for a view ID */
void *W_GetViewBacking(Window id)
{
unsigned idx = ((unsigned)id) % VIEW_TABLE_SIZE;
for (int i = 0; i < VIEW_TABLE_SIZE; i++) {
unsigned slot = (idx + i) % VIEW_TABLE_SIZE;
if (view_table[slot].id == id)
return view_table[slot].backing;
if (view_table[slot].id == 0)
return NULL;
}
return NULL;
}
void W_SetViewBacking(Window id, void *img)
{
unsigned idx = ((unsigned)id) % VIEW_TABLE_SIZE;
for (int i = 0; i < VIEW_TABLE_SIZE; i++) {
unsigned slot = (idx + i) % VIEW_TABLE_SIZE;
if (view_table[slot].id == id) {
view_table[slot].backing = img;
return;
}
if (view_table[slot].id == 0)
return;
}
}
static void view_table_remove(Window id)
{
unsigned idx = ((unsigned)id) % VIEW_TABLE_SIZE;
for (int i = 0; i < VIEW_TABLE_SIZE; i++) {
unsigned slot = (idx + i) % VIEW_TABLE_SIZE;
if (view_table[slot].id == id) {
view_table[slot].id = 0;
view_table[slot].view = NULL;
view_table[slot].backing = NULL;
return;
}
if (view_table[slot].id == 0)
return;
}
}
Bool W_IsValidView(W_View *view)
{
if (!view) return False;
unsigned idx = ((unsigned)view->window) % VIEW_TABLE_SIZE;
for (int i = 0; i < VIEW_TABLE_SIZE; i++) {
unsigned slot = (idx + i) % VIEW_TABLE_SIZE;
if (view_table[slot].view == view)
return True;
if (view_table[slot].id == 0)
return False;
}
return False;
}
/*
* W_RegisterBacking - register a pixman_image_t backing for any synthetic
* window ID (WINGs views OR WM frame_buf windows). Used by wl_backend.c so
* that WMDrawString() can resolve a WM window ID to a pixman_image_t when
* drawing text into frame_buf images (e.g. miniwindow title).
*/
void W_RegisterBacking(Window id, void *img)
{
view_table_put(id, NULL);
W_SetViewBacking(id, img);
}
/* W_UnregisterBacking - remove a backing registration (on frame_buf destroy). */
void W_UnregisterBacking(Window id)
{
view_table_remove(id);
}
static void *wl_screen_init(W_Screen *scr)
{
(void)scr;
return scr; /* use scr as token */
}
static void wl_screen_fini(void *vctx)
{
(void)vctx;
}
static void wl_create_root(void *vctx, W_View *view)
{
(void)vctx;
view->window = (Window)wl_view_next_id++;
view_table_put(view->window, view);
/* Default screen size — the WM backend will update this */
view->size.width = 1920;
view->size.height = 1080;
}
static void wl_realize(void *vctx, W_View *view)
{
(void)vctx;
/* Assign a synthetic ID so W_DispatchMessage doesn't skip this view */
view->window = (Window)wl_view_next_id++;
view_table_put(view->window, view);
/* Allocate a backing pixman image for this view */
int w = view->size.width > 0 ? view->size.width : 1;
int h = view->size.height > 0 ? view->size.height : 1;
pixman_image_t *img = pixman_image_create_bits(PIXMAN_a8r8g8b8, w, h, NULL, 0);
W_SetViewBacking(view->window, img);
}
static void wl_redisplay(void *vctx, W_View *view);
static void wl_unmap(void *vctx, W_View *view)
{
(void)vctx;
view->flags.mapped = 0;
if (view->parent && view->parent->flags.mapped)
wl_redisplay(vctx, view->parent);
}
static void wl_unmap_subviews(void *vctx, W_View *view)
{
W_View *child = view->childrenList;
while (child) {
child->flags.mapped = 0;
if (child->childrenList)
wl_unmap_subviews(vctx, child);
child = child->nextSister;
}
}
static void wl_raise(void *vctx, W_View *view)
{
(void)vctx;
W_View *parent = view->parent;
if (!parent) return;
/* Remove from list */
if (parent->childrenList == view) {
parent->childrenList = view->nextSister;
} else {
W_View *prev = parent->childrenList;
while (prev && prev->nextSister != view)
prev = prev->nextSister;
if (prev)
prev->nextSister = view->nextSister;
}
/* Append to end (drawn last = on top) */
view->nextSister = NULL;
if (!parent->childrenList) {
parent->childrenList = view;
} else {
W_View *last = parent->childrenList;
while (last->nextSister)
last = last->nextSister;
last->nextSister = view;
}
if (parent->flags.mapped)
wl_redisplay(vctx, parent);
}
static void wl_lower(void *vctx, W_View *view)
{
(void)vctx;
W_View *parent = view->parent;
if (!parent) return;
/* Remove from list */
if (parent->childrenList == view) {
parent->childrenList = view->nextSister;
} else {
W_View *prev = parent->childrenList;
while (prev && prev->nextSister != view)
prev = prev->nextSister;
if (prev)
prev->nextSister = view->nextSister;
}
/* Prepend to front (drawn first = on bottom) */
view->nextSister = parent->childrenList;
parent->childrenList = view;
if (parent->flags.mapped)
wl_redisplay(vctx, parent);
}
static void wl_map_subviews(void *vctx, W_View *view);
static void wl_map(void *vctx, W_View *view)
{
(void)vctx;
view->flags.mapped = 1;
if (view->flags.topLevel && view->flags.realized) {
wl_redisplay(vctx, view);
wl_map_subviews(vctx, view);
/* Re-composite top-level after children have drawn into it */
if (view->parent) {
pixman_image_t *src = (pixman_image_t *)W_GetViewBacking(view->window);
pixman_image_t *dst = (pixman_image_t *)W_GetViewBacking(view->parent->window);
if (src && dst) {
pixman_image_composite32(PIXMAN_OP_OVER, src, NULL, dst,
0, 0, 0, 0,
view->pos.x, view->pos.y,
view->size.width, view->size.height);
}
}
}
}
static void wl_map_subviews(void *vctx, W_View *view)
{
W_View *child = view->childrenList;
while (child) {
if (child->flags.realized) {
child->flags.mapped = 1;
wl_redisplay(vctx, child);
/* Recurse into grandchildren */
if (child->childrenList)
wl_map_subviews(vctx, child);
/* Re-composite child after grandchildren have drawn into it */
pixman_image_t *src = (pixman_image_t *)W_GetViewBacking(child->window);
pixman_image_t *dst = (pixman_image_t *)W_GetViewBacking(view->window);
if (src && dst) {
pixman_image_composite32(PIXMAN_OP_OVER, src, NULL, dst,
0, 0, 0, 0,
child->pos.x, child->pos.y,
child->size.width, child->size.height);
}
} else {
if (child->childrenList)
wl_map_subviews(vctx, child);
}
child = child->nextSister;
}
}
static void wl_move(void *vctx, W_View *view, int x, int y)
{
(void)vctx;
view->pos.x = x;
view->pos.y = y;
if (view->parent && view->flags.mapped)
wl_redisplay(vctx, view->parent);
}
static void wl_resize(void *vctx, W_View *view, unsigned int w, unsigned int h)
{
(void)vctx;
view->size.width = w;
view->size.height = h;
/* Reallocate backing pixman buffer */
pixman_image_t *old = (pixman_image_t *)W_GetViewBacking(view->window);
if (old)
pixman_image_unref(old);
int bw = w > 0 ? (int)w : 1;
int bh = h > 0 ? (int)h : 1;
pixman_image_t *img = pixman_image_create_bits(PIXMAN_a8r8g8b8, bw, bh, NULL, 0);
W_SetViewBacking(view->window, img);
if (view->flags.mapped)
wl_redisplay(vctx, view);
}
static void wl_reparent(void *vctx, W_View *view, W_View *newParent, int x, int y)
{
(void)vctx;
W_View *oldParent = view->parent;
/* Remove from old parent's children list */
if (oldParent) {
if (oldParent->childrenList == view) {
oldParent->childrenList = view->nextSister;
} else {
W_View *prev = oldParent->childrenList;
while (prev && prev->nextSister != view)
prev = prev->nextSister;
if (prev)
prev->nextSister = view->nextSister;
}
}
/* Insert into new parent's children list */
view->nextSister = newParent->childrenList;
newParent->childrenList = view;
view->parent = newParent;
view->pos.x = x;
view->pos.y = y;
if (oldParent && oldParent->flags.mapped)
wl_redisplay(vctx, oldParent);
if (view->flags.mapped)
wl_redisplay(vctx, newParent);
}
static void wl_destroy(void *vctx, W_View *view)
{
(void)vctx;
W_WL_InvalidateView(view->screen, view);
if (view->flags.topLevel && W_ViewDestroyBackingFn)
W_ViewDestroyBackingFn((unsigned long)view->window);
}
static void wl_set_background_color(void *vctx, W_View *view, WMColor *color)
{
(void)vctx;
(void)view;
(void)color;
/* Background is handled by pixman rendering in frame_paint */
}
static void wl_set_cursor(void *vctx, W_View *view, Cursor cursor)
{
(void)vctx;
(void)view;
(void)cursor;
/* Cursor is managed by wlroots seat */
}
static void wl_redisplay(void *vctx, W_View *view)
{
WMEvent ev;
(void)vctx;
if (!view->flags.mapped)
return;
/* Fill background color */
pixman_image_t *backing = (pixman_image_t *)W_GetViewBacking(view->window);
if (backing && view->backColor) {
pixman_color_t bg;
bg.red = view->backColor->color.red;
bg.green = view->backColor->color.green;
bg.blue = view->backColor->color.blue;
bg.alpha = view->backColor->alpha ? view->backColor->alpha : 0xFFFF;
pixman_image_fill_rectangles(PIXMAN_OP_SRC, backing, &bg,
1, &(pixman_rectangle16_t){0, 0, view->size.width, view->size.height});
} else if (backing) {
/* Default: light grey background */
pixman_color_t bg = {0xAAAA, 0xAAAA, 0xAAAA, 0xFFFF};
pixman_image_fill_rectangles(PIXMAN_OP_SRC, backing, &bg,
1, &(pixman_rectangle16_t){0, 0, view->size.width, view->size.height});
}
memset(&ev, 0, sizeof(ev));
ev.type = WME_EXPOSE;
ev.window = (WNativeWindow)view->window;
ev.u.expose.window = (WNativeWindow)view->window;
ev.u.expose.count = 0;
ev._x11_raw = NULL;
ev._view_target = view;
WMHandleEvent(&ev);
/* Composite this view's backing into its parent's backing */
if (view->parent) {
pixman_image_t *src = (pixman_image_t *)W_GetViewBacking(view->window);
pixman_image_t *dst = (pixman_image_t *)W_GetViewBacking(view->parent->window);
if (src && dst) {
pixman_image_composite32(PIXMAN_OP_OVER, src, NULL, dst,
0, 0, 0, 0,
view->pos.x, view->pos.y,
view->size.width, view->size.height);
}
}
/* Notify WM backend to commit the top-level view's backing buffer */
/* Walk up to find the top-level view */
W_View *top = view;
while (top->parent && !top->flags.topLevel)
top = top->parent;
extern void (*W_ViewCommitBackingFn)(unsigned long id);
if (W_ViewCommitBackingFn)
W_ViewCommitBackingFn((unsigned long)top->window);
}
/* Find the deepest child view at (x, y) relative to the given view. */
W_View *W_FindViewAtPoint(W_View *parent, int x, int y)
{
if (!parent) return NULL;
W_View *child = parent->childrenList;
while (child) {
if (child->flags.mapped && child->flags.realized &&
x >= child->pos.x && x < child->pos.x + child->size.width &&
y >= child->pos.y && y < child->pos.y + child->size.height) {
/* Recurse into this child with local coordinates */
W_View *deeper = W_FindViewAtPoint(child,
x - child->pos.x, y - child->pos.y);
return deeper ? deeper : child;
}
child = child->nextSister;
}
return NULL;
}
static WMPoint wl_get_screen_position(void *vctx, W_View *view)
{
W_Screen *scr = (W_Screen *)vctx;
int x = 0, y = 0;
W_View *v;
/* Walk up the view tree accumulating positions */
for (v = view; v && v != scr->rootView; v = v->parent) {
x += v->pos.x;
y += v->pos.y;
}
return wmkpoint(x, y);
}
static Bool wl_selection_acquire(void *vctx, W_View *view, Atom selection, Time timestamp)
{
(void)vctx; (void)view; (void)selection; (void)timestamp;
/* TODO: implement via wl_data_device */
return True;
}
static void wl_selection_release(void *vctx, W_View *view, Atom selection)
{
(void)vctx; (void)view; (void)selection;
}
static char *wl_selection_fetch(void *vctx, Atom selection, int *len_out)
{
(void)vctx; (void)selection;
/* Use the clipboard fetch path (reads from seat selection via pipe) */
if (W_ClipboardFetchFn)
return W_ClipboardFetchFn(len_out);
if (len_out)
*len_out = 0;
return NULL;
}
static char *wl_clipboard_buf = NULL;
static int wl_clipboard_len = 0;
static void wl_clipboard_store(void *vctx, const char *text, int len)
{
(void)vctx;
if (wl_clipboard_buf)
wfree(wl_clipboard_buf);
if (text && len > 0) {
wl_clipboard_buf = wmalloc(len + 1);
memcpy(wl_clipboard_buf, text, len);
wl_clipboard_buf[len] = '\0';
wl_clipboard_len = len;
if (W_ClipboardStoreFn)
W_ClipboardStoreFn(text, len);
} else {
wl_clipboard_buf = NULL;
wl_clipboard_len = 0;
}
}
static char *wl_clipboard_fetch(void *vctx, int *len_out)
{
(void)vctx;
/* Try seat selection first (cross-client paste) */
if (W_ClipboardFetchFn) {
int n = 0;
char *text = W_ClipboardFetchFn(&n);
if (text) {
if (len_out)
*len_out = n;
return text;
}
}
/* Fall back to internal buffer */
if (wl_clipboard_buf && wl_clipboard_len > 0) {
char *copy = wmalloc(wl_clipboard_len + 1);
memcpy(copy, wl_clipboard_buf, wl_clipboard_len);
copy[wl_clipboard_len] = '\0';
if (len_out)
*len_out = wl_clipboard_len;
return copy;
}
if (len_out)
*len_out = 0;
return NULL;
}
static void wl_text_init(void *vctx, W_View *view, void *stipple_gc_out)
{
(void)vctx; (void)view; (void)stipple_gc_out;
}
static void wl_balloon_draw(void *vctx, W_View *view, int x, int y,
int w, int h, const char *text)
{
(void)vctx; (void)view; (void)x; (void)y; (void)w; (void)h; (void)text;
}
static void wl_drag_begin(void *vctx, W_View *view, WMEvent *event)
{
W_Screen *scr = (W_Screen *)vctx;
if (!view->dragSourceProcs || event->type != WME_BUTTON_PRESS)
return;
/* Wait for motion to confirm drag */
WMEvent ev;
WMMaskEvent(scr, ButtonMotionMask | ButtonReleaseMask, &ev);
if (ev.type != WME_MOTION)
return;
if (view->dragSourceProcs->beganDrag) {
WMPoint pt = wmkpoint(ev.u.motion.x_root, ev.u.motion.y_root);
view->dragSourceProcs->beganDrag(view, &pt);
}
/* Drag loop */
W_View *destView = NULL;
Bool deposited = False;
int done = 0;
while (!done) {
WMMaskEvent(scr, ButtonMotionMask | ButtonReleaseMask, &ev);
switch (ev.type) {
case WME_MOTION:
/* Find view under cursor */
{
W_View *top = scr->rootView;
int mx = ev.u.motion.x_root;
int my = ev.u.motion.y_root;
destView = W_FindViewAtPoint(top, mx, my);
}
break;
case WME_BUTTON_RELEASE:
if (destView && destView->dragDestinationProcs &&
destView->droppableTypes && view->dragSourceProcs->dropDataTypes) {
WMArray *types = view->dragSourceProcs->dropDataTypes(view);
WMArray *dropData = WMCreateArray(WMGetArrayItemCount(types));
int i;
for (i = 0; i < WMGetArrayItemCount(types); i++) {
char *type = WMGetFromArray(types, i);
WMData *data = view->dragSourceProcs->fetchDragData(view, type);
if (data)
WMAddToArray(dropData, data);
}
if (WMGetArrayItemCount(dropData) > 0 &&
destView->dragDestinationProcs->performDragOperation) {
WMPoint pt = wmkpoint(ev.u.button.x_root, ev.u.button.y_root);
destView->dragDestinationProcs->performDragOperation(
destView, dropData, NULL, &pt);
deposited = True;
}
WMFreeArray(dropData);
}
done = 1;
break;
}
}
if (view->dragSourceProcs->endedDrag) {
WMPoint pt = wmkpoint(ev.u.button.x_root, ev.u.button.y_root);
view->dragSourceProcs->endedDrag(view, &pt, deposited);
}
}
static void wl_flush(void *vctx)
{
(void)vctx;
}
static void wl_window_set_title(void *vctx, W_View *view, const char *title)
{
(void)vctx; (void)view; (void)title;
}
static void wl_window_set_icon_title(void *vctx, W_View *view, const char *title)
{
(void)vctx; (void)view; (void)title;
}
static void wl_window_set_icon_image(void *vctx, W_View *view, RImage *image)
{
(void)vctx; (void)view; (void)image;
}
static void wl_window_realize(void *vctx, W_View *view, const char *name,
const char *class, W_View *owner)
{
(void)vctx; (void)view; (void)name; (void)class; (void)owner;
}
static void wl_window_set_geometry(void *vctx, W_View *view)
{
(void)vctx; (void)view;
}
static void wl_window_withdraw(void *vctx, W_View *view)
{
(void)vctx; (void)view;
}
const W_ViewBackend W_ViewBackendWL = {
.screen_init = wl_screen_init,
.screen_fini = wl_screen_fini,
.create_root = wl_create_root,
.realize = wl_realize,
.map = wl_map,
.unmap = wl_unmap,
.map_subviews = wl_map_subviews,
.unmap_subviews = wl_unmap_subviews,
.move = wl_move,
.resize = wl_resize,
.raise = wl_raise,
.lower = wl_lower,
.reparent = wl_reparent,
.destroy = wl_destroy,
.set_background_color = wl_set_background_color,
.set_cursor = wl_set_cursor,
.redisplay = wl_redisplay,
.get_screen_position = wl_get_screen_position,
.selection_acquire = wl_selection_acquire,
.selection_release = wl_selection_release,
.selection_request = NULL,
.selection_fetch = wl_selection_fetch,
.platform_init = NULL,
.flush = wl_flush,
.clipboard_store = wl_clipboard_store,
.clipboard_fetch = wl_clipboard_fetch,
.drag_begin = wl_drag_begin,
.window_set_title = wl_window_set_title,
.window_set_icon_title = wl_window_set_icon_title,
.window_set_icon_image = wl_window_set_icon_image,
.window_realize = wl_window_realize,
.window_set_geometry = wl_window_set_geometry,
.window_withdraw = wl_window_withdraw,
.pointer_grab = NULL,
.pointer_ungrab = NULL,
.bell = NULL,
.balloon_draw = wl_balloon_draw,
.text_init = wl_text_init,
.xor_fill_rect = NULL,
.magnify_callback = NULL,
};