1021 lines
31 KiB
C
1021 lines
31 KiB
C
#define WRASTER_NO_XLIB /* Wayland-only file: no libX11 deps */
|
|
|
|
/*
|
|
* 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>
|
|
#include <string.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;
|
|
/* Optional hook: destroy wl_surface + xdg_toplevel on popup unmap
|
|
* so the compositor has no stale state. Set by the WM backend. */
|
|
void (*W_ViewDestroyBackingFn)(unsigned long id) = NULL;
|
|
void (*W_ViewSceneNodeSetEnabledFn)(unsigned long id, int enabled) = NULL;
|
|
|
|
/* Callback for wclient_wl.c to trigger a full redisplay after resize */
|
|
static void wl_redisplay(void *vctx, W_View *view);
|
|
static void wl_client_redisplay_wrapper(W_View *view) {
|
|
if (view && view->screen)
|
|
wl_redisplay(view->screen->viewBackendCtx, view);
|
|
}
|
|
void (*W_WLClientRedisplayFn)(W_View *view) = wl_client_redisplay_wrapper;
|
|
|
|
/* 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;
|
|
|
|
/* Drag icon overlay hooks set by WM backend.
|
|
* Create/show a scene-graph node for the drag image, move it with the
|
|
* cursor, and hide/destroy it when the drag ends.
|
|
* x,y are in output (root-view) coordinates. */
|
|
void (*W_DragIconShowFn)(W_View *view, int x, int y) = NULL;
|
|
void (*W_DragIconMoveFn)(int x, int y) = NULL;
|
|
void (*W_DragIconHideFn)(void) = NULL;
|
|
|
|
/* Drag cursor feedback — called during drag loop when the view under the
|
|
* cursor changes. accepted=True means the view accepts the drop. */
|
|
void (*W_DragCursorFeedbackFn)(Bool accepted) = NULL;
|
|
void (*W_SetCursorImageFn)(const char *name) = NULL;
|
|
|
|
extern void wl_view_destroy_backing_impl(unsigned long id);
|
|
|
|
/* Simple view lookup table (ID → W_View*) */
|
|
#define VIEW_TABLE_SIZE 256
|
|
#define VIEW_TABLE_TOMBSTONE ((WNativeWindow)1) /* never a valid view/fb ID */
|
|
static struct { WNativeWindow id; W_View *view; void *backing; } view_table[VIEW_TABLE_SIZE];
|
|
|
|
static void view_table_put(WNativeWindow 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 == VIEW_TABLE_TOMBSTONE) {
|
|
view_table[slot].id = id;
|
|
view_table[slot].view = view;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
W_View *W_GetViewForWindow(WNativeWindow 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)
|
|
return NULL;
|
|
/* skip tombstones */
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Get/set backing pixman image for a view ID */
|
|
void *W_GetViewBacking(WNativeWindow 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(WNativeWindow 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;
|
|
}
|
|
}
|
|
|
|
void view_table_remove(WNativeWindow 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 = VIEW_TABLE_TOMBSTONE;
|
|
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(WNativeWindow id, void *img)
|
|
{
|
|
/* Only register WINGs view IDs (0x20000000+) in the table.
|
|
* Frame_buf IDs (0x10000000-0x1FFFFFFF) are no longer stored here;
|
|
* callers pass raw pixman pointers for text drawing instead. */
|
|
if ((unsigned long)id >= 0x10000000 && (unsigned long)id < 0x20000000)
|
|
return;
|
|
view_table_put(id, NULL);
|
|
W_SetViewBacking(id, img);
|
|
}
|
|
|
|
/* W_UnregisterBacking - remove a backing registration (on frame_buf destroy). */
|
|
void W_UnregisterBacking(WNativeWindow id)
|
|
{
|
|
if ((unsigned long)id >= 0x10000000 && (unsigned long)id < 0x20000000)
|
|
return;
|
|
view_table_remove(id);
|
|
}
|
|
|
|
static void *wl_screen_init(W_Screen *scr)
|
|
{
|
|
/* Store cursor names as WMNativeCursor — cast back in wl_set_cursor */
|
|
scr->textCursor = (WMNativeCursor)(uintptr_t)"xterm";
|
|
scr->defaultCursor = (WMNativeCursor)(uintptr_t)"left_ptr";
|
|
scr->invisibleCursor = (WMNativeCursor)(uintptr_t)"none";
|
|
return scr;
|
|
}
|
|
|
|
static void wl_screen_fini(void *vctx)
|
|
{
|
|
(void)vctx;
|
|
}
|
|
|
|
static void wl_create_root(void *vctx, W_View *view)
|
|
{
|
|
(void)vctx;
|
|
|
|
view->window = (WNativeWindow)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;
|
|
}
|
|
|
|
/* Optional hook set by wclient_wl.c (Wayland-client mode) so that
|
|
* top-level views allocate their backing as wl_shm-mapped pixman images
|
|
* paired with a wl_surface + xdg_toplevel. When NULL (compositor mode
|
|
* or X11), wl_realize falls back to a free-floating pixman image. */
|
|
void *(*W_WLClientToplevelRealizeFn)(W_View *view, int width, int height) = NULL;
|
|
void (*W_WLClientToplevelResizeFn)(W_View *view, int width, int height) = NULL;
|
|
void (*W_WLClientToplevelDestroyFn)(W_View *view) = NULL;
|
|
|
|
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 = (WNativeWindow)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 = NULL;
|
|
if (view->flags.topLevel && W_WLClientToplevelRealizeFn)
|
|
img = (pixman_image_t *)W_WLClientToplevelRealizeFn(view, w, h);
|
|
if (!img)
|
|
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 (W_ViewSceneNodeSetEnabledFn) {
|
|
W_ViewSceneNodeSetEnabledFn((unsigned long)view->window, 0);
|
|
} else if (view->flags.topLevel && view->flags.popup) {
|
|
/* W_ViewSceneNodeSetEnabledFn is NULL in client-only builds (e.g.
|
|
* WPrefs). Fall back to the standard Wayland unmap. */
|
|
extern void wl_client_view_unmap(WNativeWindow id);
|
|
wl_client_view_unmap((WNativeWindow)view->window);
|
|
}
|
|
|
|
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);
|
|
|
|
int wl_redisplay_depth = 0;
|
|
int W_WL_Realizing = 0;
|
|
|
|
static void wl_map(void *vctx, W_View *view)
|
|
{
|
|
(void)vctx;
|
|
view->flags.mapped = 1;
|
|
if (!view->flags.realized)
|
|
return;
|
|
/* Auto-realize children recursively before redisplay.
|
|
* W_RealizeView recurses into the full subtree, so calling it
|
|
* on each direct child handles arbitrary nesting depth. */
|
|
{
|
|
W_View *child = view->childrenList;
|
|
while (child) {
|
|
if (!child->flags.realized)
|
|
W_RealizeView(child);
|
|
child = child->nextSister;
|
|
}
|
|
}
|
|
|
|
if (W_ViewSceneNodeSetEnabledFn)
|
|
W_ViewSceneNodeSetEnabledFn((unsigned long)view->window, 1);
|
|
|
|
if (view->flags.topLevel) {
|
|
/* Re-apply popup position/raise on remap so the surface appears
|
|
* above parent windows. handle_toplevel_map only fires on the
|
|
* initial xdg_surface map event, not on scene-node re-enable. */
|
|
if (view->flags.popup) {
|
|
extern void W_WLClientPopupSetPosition(WNativeWindow w, int x, int y);
|
|
W_WLClientPopupSetPosition((WNativeWindow)view->window,
|
|
view->pos.x, view->pos.y);
|
|
}
|
|
wl_redisplay(vctx, view);
|
|
/* Dispatch any pending Wayland events (e.g. the configure
|
|
* from a NULL-buffer unmap) so the next commit succeeds. */
|
|
extern void W_WLClientDispatchPending(void);
|
|
W_WLClientDispatchPending();
|
|
return;
|
|
}
|
|
/* Non-toplevel: find the toplevel and redisplay from there,
|
|
* but only if all ancestors are realized (not mid-realization). */
|
|
W_View *top = view->parent;
|
|
while (top && !top->flags.topLevel) {
|
|
if (!top->flags.realized)
|
|
return;
|
|
top = top->parent;
|
|
}
|
|
if (top && top->flags.realized && top->flags.mapped
|
|
&& wl_redisplay_depth == 0 && !W_WL_Realizing) {
|
|
wl_redisplay(vctx, top);
|
|
}
|
|
}
|
|
|
|
static void wl_map_subviews(void *vctx, W_View *view)
|
|
{
|
|
W_View *child = view->childrenList;
|
|
while (child) {
|
|
if (!child->flags.realized) {
|
|
/* Auto-realize children that were created after their
|
|
* parent was already realized. This is needed for e.g.
|
|
* switch panel icon frames added via addIconForWindow
|
|
* after WMRealizeWidget(panel->win). */
|
|
W_RealizeView(child);
|
|
}
|
|
if (child->flags.realized) {
|
|
child->flags.mapped = 1;
|
|
}
|
|
if (child->childrenList)
|
|
wl_map_subviews(vctx, child);
|
|
child = child->nextSister;
|
|
}
|
|
/* Redisplay the parent which will recurse into all now-mapped children */
|
|
if (view->flags.mapped)
|
|
wl_redisplay(vctx, view);
|
|
}
|
|
|
|
static void wl_move(void *vctx, W_View *view, int x, int y)
|
|
{
|
|
(void)vctx;
|
|
view->pos.x = x;
|
|
view->pos.y = y;
|
|
/* For popup-style toplevels, drive position via wmaker-popup-v1.
|
|
* The client function is a no-op if the toplevel hasn't been
|
|
* realized yet — pos.x/y is captured into view->pos for the
|
|
* realize-time call. */
|
|
if (view->flags.topLevel && view->flags.popup && view->flags.realized) {
|
|
extern void W_WLClientPopupSetPosition(WNativeWindow w, int x, int y);
|
|
W_WLClientPopupSetPosition((WNativeWindow)view->window, x, 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;
|
|
|
|
int bw = w > 0 ? (int)w : 1;
|
|
int bh = h > 0 ? (int)h : 1;
|
|
|
|
if (view->flags.topLevel && W_WLClientToplevelResizeFn) {
|
|
/* Wayland-client mode: backend swaps to a freshly allocated
|
|
* shm-backed pixman image and updates the surface. */
|
|
W_WLClientToplevelResizeFn(view, bw, bh);
|
|
} else {
|
|
/* Default path: free old image, allocate a new one. */
|
|
pixman_image_t *old = (pixman_image_t *)W_GetViewBacking(view->window);
|
|
if (old)
|
|
pixman_image_unref(old);
|
|
pixman_image_t *img = pixman_image_create_bits(PIXMAN_a8r8g8b8, bw, bh, NULL, 0);
|
|
W_SetViewBacking(view->window, img);
|
|
}
|
|
|
|
if (view->flags.mapped) {
|
|
/* Notify parent of child resize (mirrors X11 ConfigureNotify
|
|
* which scroll views use to update scroller proportions). */
|
|
if (view->parent) {
|
|
WMEvent cev;
|
|
memset(&cev, 0, sizeof(cev));
|
|
cev.type = WME_CONFIGURE;
|
|
cev.window = view->window;
|
|
cev.u.configure.window = view->window;
|
|
cev.u.configure.event = view->parent->window;
|
|
cev.u.configure.width = w;
|
|
cev.u.configure.height = h;
|
|
cev._view_target = view->parent;
|
|
WMHandleEvent(&cev);
|
|
}
|
|
/* Only redisplay if not already inside a redisplay (prevents
|
|
* infinite recursion when expose handlers trigger resizes). */
|
|
if (wl_redisplay_depth == 0)
|
|
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_WLClientToplevelDestroyFn)
|
|
W_WLClientToplevelDestroyFn(view);
|
|
if (view->flags.topLevel && W_ViewDestroyBackingFn)
|
|
W_ViewDestroyBackingFn((unsigned long)view->window);
|
|
/* Free backing image and remove from view table */
|
|
pixman_image_t *img = (pixman_image_t *)W_GetViewBacking(view->window);
|
|
if (img)
|
|
pixman_image_unref(img);
|
|
view_table_remove(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, WMNativeCursor cursor)
|
|
{
|
|
(void)vctx;
|
|
(void)view;
|
|
if (!cursor) return;
|
|
if (!W_SetCursorImageFn) return;
|
|
const char *name = (const char *)(uintptr_t)cursor;
|
|
if (strcmp(name, "none") == 0) name = "default";
|
|
W_SetCursorImageFn(name);
|
|
}
|
|
|
|
static void wl_redisplay(void *vctx, W_View *view)
|
|
{
|
|
WMEvent ev;
|
|
|
|
(void)vctx;
|
|
|
|
if (!view->flags.mapped || view->flags.alreadyDead)
|
|
return;
|
|
|
|
wl_redisplay_depth++;
|
|
|
|
/* Fill background color */
|
|
pixman_image_t *backing = (pixman_image_t *)W_GetViewBacking(view->window);
|
|
if (backing) {
|
|
int bw = pixman_image_get_width(backing);
|
|
int bh = pixman_image_get_height(backing);
|
|
pixman_color_t bg;
|
|
if (view->backColor) {
|
|
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;
|
|
} else {
|
|
bg = (pixman_color_t){0xAAAA, 0xAAAA, 0xAAAA, 0xFFFF};
|
|
}
|
|
pixman_image_fill_rectangles(PIXMAN_OP_SRC, backing, &bg,
|
|
1, &(pixman_rectangle16_t){0, 0, bw, bh});
|
|
}
|
|
|
|
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);
|
|
|
|
/* Re-composite all mapped children into this view's backing */
|
|
W_View *child = view->childrenList;
|
|
while (child) {
|
|
W_View *next = child->nextSister;
|
|
if (child->flags.mapped && child->flags.realized && !child->flags.alreadyDead) {
|
|
wl_redisplay(vctx, child);
|
|
}
|
|
child = next;
|
|
}
|
|
|
|
/* Composite this view's backing into its parent's backing */
|
|
if (view->parent && view->size.width > 0 && view->size.height > 0) {
|
|
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) {
|
|
int sw = pixman_image_get_width(src);
|
|
int sh = pixman_image_get_height(src);
|
|
int dw = pixman_image_get_width(dst);
|
|
int dh = pixman_image_get_height(dst);
|
|
int cw = sw < (int)view->size.width ? sw : (int)view->size.width;
|
|
int ch = sh < (int)view->size.height ? sh : (int)view->size.height;
|
|
if (view->pos.x + cw > dw) cw = dw - view->pos.x;
|
|
if (view->pos.y + ch > dh) ch = dh - view->pos.y;
|
|
if (cw > 0 && ch > 0 && view->pos.x >= 0 && view->pos.y >= 0)
|
|
pixman_image_composite32(PIXMAN_OP_OVER, src, NULL, dst,
|
|
0, 0, 0, 0,
|
|
view->pos.x, view->pos.y, cw, ch);
|
|
}
|
|
|
|
/* Cascade: composite all the way up to the toplevel.
|
|
*
|
|
* When wl_redisplay is called directly (depth==1, e.g. from
|
|
* W_RedisplayView for a slider widget), only the IMMEDIATE
|
|
* parent's backing is updated above. The toplevel's backing
|
|
* (shared-memory wl_buffer) remains STALE, so the subsequent
|
|
* wl_surface.commit shows the old content — widget visual
|
|
* updates are invisible.
|
|
*
|
|
* By continuing the composite up the tree here we propagate
|
|
* the child's pixel change all the way to the toplevel before
|
|
* the commit below. We skip the cascade when depth > 1
|
|
* (recursive call during a full parent→child redisplay)
|
|
* because the parent will composite all siblings in order
|
|
* and the depth-1 invocation will handle the cascade once. */
|
|
if (wl_redisplay_depth == 1) {
|
|
W_View *v = view->parent;
|
|
while (v->parent && !v->flags.topLevel) {
|
|
pixman_image_t *v_src = W_GetViewBacking(v->window);
|
|
pixman_image_t *v_dst = W_GetViewBacking(v->parent->window);
|
|
if (v_src && v_dst) {
|
|
int v_sw = pixman_image_get_width(v_src);
|
|
int v_sh = pixman_image_get_height(v_src);
|
|
int v_dw = pixman_image_get_width(v_dst);
|
|
int v_dh = pixman_image_get_height(v_dst);
|
|
if (v_sw < 1 || v_sh < 1 || v_dw < 1 || v_dh < 1) {
|
|
fprintf(stderr, "wl_redisplay cascade: skip zero backing (%p src=%dx%d dst=%dx%d)\n",
|
|
(void*)v, v_sw, v_sh, v_dw, v_dh);
|
|
v = v->parent;
|
|
continue;
|
|
}
|
|
int v_cw = v_sw < (int)v->size.width ? v_sw : (int)v->size.width;
|
|
int v_ch = v_sh < (int)v->size.height ? v_sh : (int)v->size.height;
|
|
if (v->pos.x + v_cw > v_dw) v_cw = v_dw - v->pos.x;
|
|
if (v->pos.y + v_ch > v_dh) v_ch = v_dh - v->pos.y;
|
|
if (v_cw < 0) v_cw = 0;
|
|
if (v_ch < 0) v_ch = 0;
|
|
if (v_cw > 0 && v_ch > 0 && v->pos.x >= 0 && v->pos.y >= 0)
|
|
pixman_image_composite32(PIXMAN_OP_OVER, v_src, NULL, v_dst,
|
|
0, 0, 0, 0,
|
|
v->pos.x, v->pos.y, v_cw, v_ch);
|
|
}
|
|
v = v->parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
|
|
wl_redisplay_depth--;
|
|
}
|
|
|
|
/* Find the deepest child view at (x, y) relative to the given view.
|
|
* When multiple children overlap the same point, prefer the one that
|
|
* has children (or deeper descendents) over a leaf view, so that
|
|
* widgets nested inside a sibling that overlaps a parent container
|
|
* are found correctly on Wayland where subwindows are absent. */
|
|
W_View *W_FindViewAtPoint(W_View *parent, int x, int y)
|
|
{
|
|
if (!parent) return NULL;
|
|
W_View *best = 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) {
|
|
W_View *deeper = W_FindViewAtPoint(child,
|
|
x - child->pos.x, y - child->pos.y);
|
|
if (deeper)
|
|
return deeper;
|
|
if (!best)
|
|
best = child;
|
|
/* Keep looking — a later sibling at the same coords
|
|
* may have deeper children. */
|
|
}
|
|
child = child->nextSister;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
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, WMNativeAtom 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, WMNativeAtom selection)
|
|
{
|
|
(void)vctx; (void)view; (void)selection;
|
|
}
|
|
|
|
static char *wl_selection_fetch(void *vctx, WMNativeAtom 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;
|
|
if (!view || !text || !text[0]) return;
|
|
|
|
W_Screen *scr = view->screen;
|
|
WMFont *font = scr->normalFont;
|
|
int fh = WMFontHeight(font);
|
|
|
|
/* Count lines and find widest line for multi-line text */
|
|
int maxWidth = 0, numLines = 1;
|
|
const char *p = text;
|
|
while (*p) {
|
|
const char *nl = strchr(p, '\n');
|
|
int lineLen = nl ? (int)(nl - p) : (int)strlen(p);
|
|
int lw = WMWidthOfString(font, p, lineLen);
|
|
if (lw > maxWidth) maxWidth = lw;
|
|
p = nl ? nl + 1 : p + lineLen;
|
|
if (nl) numLines++;
|
|
}
|
|
|
|
int bw = maxWidth + 16;
|
|
int bh = numLines * fh + 8;
|
|
if (bw < bh) bw = bh;
|
|
|
|
int bx = x + w / 2 - bw / 2;
|
|
int by = y - bh - 4;
|
|
if (by < 0) by = y + h + 4;
|
|
if (bx < 0) bx = 0;
|
|
|
|
W_ResizeView(view, bw, bh);
|
|
W_MoveView(view, bx, by);
|
|
|
|
if (!view->flags.mapped)
|
|
W_MapView(view);
|
|
|
|
pixman_image_t *img = (pixman_image_t *)W_GetViewBacking(view->window);
|
|
if (!img) return;
|
|
|
|
pixman_color_t yellow = {0xFFFF, 0xFFFF, 0x8888, 0xFFFF};
|
|
pixman_image_fill_rectangles(PIXMAN_OP_SRC, img, &yellow,
|
|
1, &(pixman_rectangle16_t){0, 0, bw, bh});
|
|
|
|
pixman_color_t black = {0, 0, 0, 0xFFFF};
|
|
pixman_image_fill_rectangles(PIXMAN_OP_SRC, img, &black, 4,
|
|
(pixman_rectangle16_t[]){
|
|
{0, 0, bw, 1},
|
|
{0, bh - 1, bw, 1},
|
|
{0, 0, 1, bh},
|
|
{bw - 1, 0, 1, bh}
|
|
});
|
|
|
|
/* Draw each line */
|
|
p = text;
|
|
int line = 0;
|
|
while (*p) {
|
|
const char *nl = strchr(p, '\n');
|
|
int lineLen = nl ? (int)(nl - p) : (int)strlen(p);
|
|
int ty = 4 + line * fh;
|
|
WMDrawString(scr, (WMDrawable)view->window, scr->black, font,
|
|
8, ty, p, lineLen);
|
|
p = nl ? nl + 1 : p + lineLen;
|
|
line++;
|
|
}
|
|
}
|
|
|
|
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, WM_EVMASK_BUTTON_MOTION | WM_EVMASK_BUTTON_RELEASE, &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);
|
|
}
|
|
|
|
/* Show drag icon overlay at cursor position */
|
|
int mx = ev.u.motion.x_root;
|
|
int my = ev.u.motion.y_root;
|
|
if (W_DragIconShowFn)
|
|
W_DragIconShowFn(view, mx, my);
|
|
|
|
/* Drag loop */
|
|
W_View *destView = NULL;
|
|
Bool deposited = False;
|
|
int done = 0;
|
|
|
|
while (!done) {
|
|
WMMaskEvent(scr, WM_EVMASK_BUTTON_MOTION | WM_EVMASK_BUTTON_RELEASE, &ev);
|
|
switch (ev.type) {
|
|
case WME_MOTION:
|
|
mx = ev.u.motion.x_root;
|
|
my = ev.u.motion.y_root;
|
|
/* Move drag icon to follow cursor */
|
|
if (W_DragIconMoveFn)
|
|
W_DragIconMoveFn(mx, my);
|
|
/* Find view under cursor */
|
|
{
|
|
W_View *top = scr->rootView;
|
|
destView = W_FindViewAtPoint(top, mx, my);
|
|
/* Cursor feedback: does the view accept drops? */
|
|
if (W_DragCursorFeedbackFn) {
|
|
Bool accepted = (destView && destView->dragDestinationProcs
|
|
&& destView->droppableTypes);
|
|
W_DragCursorFeedbackFn(accepted);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* Hide/destroy drag icon overlay */
|
|
if (W_DragIconHideFn)
|
|
W_DragIconHideFn();
|
|
|
|
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;
|
|
if (!view || !title) return;
|
|
extern void W_WLClientSetTitle(WNativeWindow window, const char *title);
|
|
W_WLClientSetTitle(view->window, 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;
|
|
}
|
|
|
|
/* Function pointer set by the compositor to install a pointer grab
|
|
* during press-drag-release sequences (popup menus, interactive moves).
|
|
* The compositor uses this to set wl_state.grab_type and suppress
|
|
* unwanted focus/cursor changes while the grab is active. */
|
|
int (*W_WLPointerGrabFn)(WNativeWindow grab_win, WNativeWindow confine_to,
|
|
unsigned int event_mask, int cursor_id,
|
|
int owner_events, unsigned long timestamp) = NULL;
|
|
|
|
static void wl_pointer_grab(void *vctx, W_View *view, int owner_events, unsigned long time)
|
|
{
|
|
(void)vctx;
|
|
if (W_WLPointerGrabFn)
|
|
W_WLPointerGrabFn(view ? view->window : WNATIVE_NONE,
|
|
WNATIVE_NONE, 0, 0, owner_events, time);
|
|
}
|
|
|
|
void (*W_WLPointerUngrabFn)(unsigned long timestamp) = NULL;
|
|
|
|
static void wl_pointer_ungrab(void *vctx, unsigned long time)
|
|
{
|
|
(void)vctx;
|
|
if (W_WLPointerUngrabFn)
|
|
W_WLPointerUngrabFn(time);
|
|
}
|
|
|
|
static void wl_window_set_geometry(void *vctx, W_View *view)
|
|
{
|
|
(void)vctx;
|
|
if (!view || !view->flags.topLevel) return;
|
|
extern void W_WLClientSetMinMaxSize(W_View *view);
|
|
W_WLClientSetMinMaxSize(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 = wl_pointer_grab,
|
|
.pointer_ungrab = wl_pointer_ungrab,
|
|
.bell = NULL,
|
|
.balloon_draw = wl_balloon_draw,
|
|
.text_init = wl_text_init,
|
|
.xor_fill_rect = NULL,
|
|
.magnify_callback = NULL,
|
|
};
|