windowmaker-wl/WINGs/wevent_wl.c

506 lines
16 KiB
C

#define WRASTER_NO_XLIB /* Wayland-only file: no libX11 deps */
/*
* wevent_wl.c — Wayland event backend for WINGs.
*
* Events arrive via wlroots input callbacks (keyboard, pointer, touch) in
* wl_backend.c, which call W_WL_EnqueueEvent(). This backend dequeues them
* in WMNextEvent / WMMaskEvent without touching any Xlib function.
*
* For modal loops (e.g. dialog.c) a dispatch callback is registered via
* W_WL_SetLoopDispatch() so the compositor event loop can be ticked while
* waiting for the next WINGs event.
*/
#include "wconfig.h"
#include "WINGsP.h"
#include <string.h>
#include <strings.h>
#include <xkbcommon/xkbcommon.h>
/* ------------------------------------------------------------------ */
#define WL_EVENT_QUEUE_CAP 512
typedef struct {
WMEvent ev;
W_View *target; /* direct view routing; NULL = use ev.window */
} WLQEntry;
typedef struct {
WLQEntry buf[WL_EVENT_QUEUE_CAP];
int head; /* next read position */
int tail; /* next write position */
int loop_fd; /* wl_event_loop fd for W_HandleInputEvents */
void (*dispatch_fn)(int timeout_ms, void *ud);
void *dispatch_ud;
} WLEventCtx;
/* ------------------------------------------------------------------ */
static void *
wl_screen_init(W_Screen *scr)
{
(void)scr;
WLEventCtx *ctx = wmalloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
ctx->loop_fd = -1;
return ctx;
}
static void
wl_screen_fini(void *ectx)
{
wfree(ectx);
}
static Bool
wl_pending(void *ectx)
{
WLEventCtx *ctx = ectx;
return ctx->head != ctx->tail;
}
static void
wl_fetch(void *ectx, WMEvent *ev)
{
WLEventCtx *ctx = ectx;
WLQEntry *e = &ctx->buf[ctx->head];
*ev = e->ev;
ev->_view_target = e->target;
if (e->target && !W_IsValidView(e->target))
ev->_view_target = NULL;
if (e->target) {
W_Screen *scr = e->target->screen;
if (scr)
scr->event_target = e->target;
}
ctx->head = (ctx->head + 1) % WL_EVENT_QUEUE_CAP;
}
/* Event mask table for matching — same values as wevent.c's wmeventMasks */
static unsigned long
wme_mask(int type)
{
switch (type) {
case WME_KEY_PRESS: return WM_EVMASK_KEY_PRESS;
case WME_KEY_RELEASE: return WM_EVMASK_KEY_RELEASE;
case WME_BUTTON_PRESS: return WM_EVMASK_BUTTON_PRESS;
case WME_BUTTON_RELEASE: return WM_EVMASK_BUTTON_RELEASE;
case WME_MOTION: return WM_EVMASK_POINTER_MOTION | WM_EVMASK_BUTTON_MOTION;
case WME_ENTER: return WM_EVMASK_ENTER_WINDOW;
case WME_LEAVE: return WM_EVMASK_LEAVE_WINDOW;
case WME_EXPOSE: return WM_EVMASK_EXPOSURE;
case WME_CONFIGURE: return WM_EVMASK_STRUCTURE_NOTIFY;
case WME_MAP: return WM_EVMASK_STRUCTURE_NOTIFY;
case WME_UNMAP: return WM_EVMASK_STRUCTURE_NOTIFY;
case WME_DESTROY: return WM_EVMASK_STRUCTURE_NOTIFY;
case WME_FOCUS_IN:
case WME_FOCUS_OUT: return WM_EVMASK_FOCUS_CHANGE;
case WME_PROPERTY: return WM_EVMASK_PROPERTY_CHANGE;
case WME_COLORMAP: return WM_EVMASK_COLORMAP_CHANGE;
case WME_VISIBILITY: return WM_EVMASK_VISIBILITY_CHANGE;
default: return 0;
}
}
static Bool
wl_check_mask(void *ectx, long mask, WMEvent *ev)
{
WLEventCtx *ctx = ectx;
int i = ctx->head;
while (i != ctx->tail) {
unsigned long emask = wme_mask(ctx->buf[i].ev.type);
if (emask & (unsigned long)mask) {
*ev = ctx->buf[i].ev;
if (ev->_view_target && !W_IsValidView(ev->_view_target))
ev->_view_target = NULL;
/* Remove entry by shifting */
int next = (i + 1) % WL_EVENT_QUEUE_CAP;
while (next != ctx->tail) {
ctx->buf[i] = ctx->buf[next];
i = next;
next = (next + 1) % WL_EVENT_QUEUE_CAP;
}
ctx->tail = i;
return True;
}
i = (i + 1) % WL_EVENT_QUEUE_CAP;
}
return False;
}
static Bool
wl_check_typed_window(void *ectx, WNativeWindow w, int type, WMEvent *ev)
{
WLEventCtx *ctx = ectx;
int i = ctx->head;
while (i != ctx->tail) {
if (ctx->buf[i].ev.type == type &&
ctx->buf[i].ev.window == w) {
*ev = ctx->buf[i].ev;
if (ev->_view_target && !W_IsValidView(ev->_view_target))
ev->_view_target = NULL;
int next = (i + 1) % WL_EVENT_QUEUE_CAP;
while (next != ctx->tail) {
ctx->buf[i] = ctx->buf[next];
i = next;
next = (next + 1) % WL_EVENT_QUEUE_CAP;
}
ctx->tail = i;
return True;
}
i = (i + 1) % WL_EVENT_QUEUE_CAP;
}
return False;
}
extern void W_WLClientCommitAllToplevels(void);
static void
wl_flush(void *ectx, Bool discard)
{
WLEventCtx *ctx = ectx;
(void)discard;
/* Commit any toplevel surfaces that may have been painted into
* since the last flush (direct fill_rect, draw_line, etc. don't
* trigger individual commits). */
W_WLClientCommitAllToplevels();
/* Pump the wlroots event loop so pointer/key events arrive in our queue.
* This is called from waitForEvent/WMMaskEvent when the queue is empty. */
if (ctx->dispatch_fn) {
ctx->dispatch_fn(16, ctx->dispatch_ud);
if (ctx->head != ctx->tail)
wwarning("wl_flush: after dispatch, queue has events (head=%d tail=%d)", ctx->head, ctx->tail);
}
}
static int
wl_fd(void *ectx)
{
WLEventCtx *ctx = ectx;
return ctx->loop_fd;
}
static void
wl_put_back(void *ectx, WMEvent *ev)
{
WLEventCtx *ctx = ectx;
int prev = (ctx->head - 1 + WL_EVENT_QUEUE_CAP) % WL_EVENT_QUEUE_CAP;
if (prev == ctx->tail)
return; /* queue full — drop */
ctx->buf[prev].ev = *ev;
ctx->buf[prev].target = NULL;
ctx->head = prev;
}
/* ------------------------------------------------------------------ */
/* Stubs for new vtable methods (Wayland: not yet implemented) */
/* ------------------------------------------------------------------ */
static Bool wl_grab_keyboard(void *ectx, WNativeWindow w)
{ (void)ectx; (void)w; return False; }
static void wl_ungrab_keyboard(void *ectx) { (void)ectx; }
static void wl_allow_events(void *ectx) { (void)ectx; }
static Bool wl_grab_pointer(void *ectx, WNativeWindow w, WMNativePixmap c)
{ (void)ectx; (void)w; (void)c; return False; }
static void wl_ungrab_pointer(void *ectx) { (void)ectx; }
static WNativeWindow wl_query_pointer(void *ectx, WNativeWindow w,
int *rx, int *ry, int *wx, int *wy, unsigned int *m)
{ (void)ectx; (void)w;
if (rx) *rx = 0; if (ry) *ry = 0;
if (wx) *wx = 0; if (wy) *wy = 0; if (m) *m = 0;
return 0; }
static Bool wl_get_window_geometry(void *ectx, WNativeWindow w,
int *x, int *y, unsigned int *width, unsigned int *height)
{
(void)ectx;
extern struct W_View *W_GetViewForWindow(WNativeWindow id);
struct W_View *v = W_GetViewForWindow(w);
if (!v) {
if (x) *x = 0; if (y) *y = 0;
if (width) *width = 0; if (height) *height = 0;
return False;
}
if (x) *x = v->pos.x;
if (y) *y = v->pos.y;
if (width) *width = v->size.width;
if (height) *height = v->size.height;
return True;
}
extern void W_WLClientPopupSetPosition(WNativeWindow w, int x, int y);
static void wl_move_window(void *ectx, WNativeWindow w, int x, int y)
{
(void)ectx;
W_WLClientPopupSetPosition(w, x, y);
}
/* Implemented in wclient_wl.c — calls xdg_toplevel_move on the client
* connection so the compositor handles the drag. */
extern Bool W_WLClientBeginMove(WNativeWindow window);
static Bool wl_window_begin_move(void *ectx, WNativeWindow window)
{
(void)ectx;
return W_WLClientBeginMove(window);
}
extern struct W_View *W_GetViewForWindow(WNativeWindow id);
static WNativeWindow wl_translate_coordinates(void *ectx,
WNativeWindow src, WNativeWindow dst, int sx, int sy, int *dx, int *dy)
{
(void)ectx; (void)dst;
/* Translate (sx, sy) from src view's coord space to the toplevel's
* coord space (the closest thing to "screen" coords on Wayland —
* actual screen position is unknown to clients). Walks up from src,
* accumulating pos.x/y. */
int ox = 0, oy = 0;
struct W_View *v = W_GetViewForWindow(src);
while (v) {
ox += v->pos.x;
oy += v->pos.y;
if (v->flags.topLevel) break;
v = v->parent;
}
if (dx) *dx = sx + ox;
if (dy) *dy = sy + oy;
return src;
}
static WNativeWindow wl_get_root_window(void *ectx)
{ (void)ectx; return 0; }
static Bool wl_query_tree(void *ectx, WNativeWindow w,
WNativeWindow **children, unsigned int *nc)
{ (void)ectx; (void)w;
if (children) *children = NULL; if (nc) *nc = 0; return False; }
static void wl_get_pointer_control(void *ectx, int *an, int *ad, int *t)
{ (void)ectx; if (an) *an = 2; if (ad) *ad = 1; if (t) *t = 4; }
static void wl_set_pointer_control(void *ectx, int an, int ad, int t)
{ (void)ectx; (void)an; (void)ad; (void)t; }
static int wl_modifier_from_key(void *ectx, const char *key)
{
(void)ectx;
/* Sensible defaults matching common Wayland compositors */
if (strcasecmp(key, "SHIFT") == 0) return 0;
if (strcasecmp(key, "CONTROL") == 0) return 2;
if (strcasecmp(key, "ALT") == 0) return 3;
if (strcasecmp(key, "META") == 0) return 3;
if (strcasecmp(key, "SUPER") == 0) return 6;
if (strcasecmp(key, "HYPER") == 0) return 0;
if (strcasecmp(key, "MOD1") == 0) return 3;
if (strcasecmp(key, "MOD2") == 0) return 4;
if (strcasecmp(key, "MOD3") == 0) return 5;
if (strcasecmp(key, "MOD4") == 0) return 6;
if (strcasecmp(key, "MOD5") == 0) return 7;
return -1;
}
static const char *wl_keysym_to_string(void *ectx, WMNativeKeysym ks)
{
(void)ectx;
static char buf[64];
xkb_keysym_get_name((xkb_keysym_t)ks, buf, sizeof(buf));
return buf;
}
static int wl_get_modifier_keysyms(void *ectx, WMNativeKeysym *out, int max_per_mod)
{
(void)ectx;
memset(out, 0, 8 * max_per_mod * sizeof(WMNativeKeysym));
/* Shift=0, Lock=1, Control=2, Mod1=3, Mod2=4, Mod3=5, Mod4=6, Mod5=7 */
out[0 * max_per_mod] = XKB_KEY_Shift_L;
out[1 * max_per_mod] = XKB_KEY_Caps_Lock;
out[2 * max_per_mod] = XKB_KEY_Control_L;
out[3 * max_per_mod] = XKB_KEY_Alt_L;
out[4 * max_per_mod] = XKB_KEY_Num_Lock;
out[5 * max_per_mod] = XKB_KEY_Scroll_Lock;
out[6 * max_per_mod] = XKB_KEY_Super_L;
out[7 * max_per_mod] = XKB_KEY_Hyper_L;
return 8;
}
static unsigned int wl_keysym_to_keycode(void *ectx, WMNativeKeysym ks)
{
(void)ectx;
extern struct xkb_keymap *W_WLClientGetKeymap(void);
struct xkb_keymap *km = W_WLClientGetKeymap();
if (!km) return 0;
xkb_keycode_t min = xkb_keymap_min_keycode(km);
xkb_keycode_t max = xkb_keymap_max_keycode(km);
for (xkb_keycode_t kc = min; kc <= max; kc++) {
const xkb_keysym_t *syms;
int num_levels = xkb_keymap_num_levels_for_key(km, kc, 0);
for (int level = 0; level < num_levels; level++) {
int nsyms = xkb_keymap_key_get_syms_by_level(km, kc, 0, level, &syms);
for (int i = 0; i < nsyms; i++) {
if (syms[i] == (xkb_keysym_t)ks)
return (unsigned int)kc;
}
}
}
return 0;
}
static WMNativeKeysym wl_keycode_to_keysym(void *ectx, unsigned int kc, int idx)
{
(void)ectx;
extern struct xkb_keymap *W_WLClientGetKeymap(void);
struct xkb_keymap *km = W_WLClientGetKeymap();
if (!km) return 0;
const xkb_keysym_t *syms;
int nsyms = xkb_keymap_key_get_syms_by_level(km, (xkb_keycode_t)kc, 0, idx, &syms);
return (nsyms > 0) ? (WMNativeKeysym)syms[0] : 0;
}
static void wl_keysym_convert_case(void *ectx, WMNativeKeysym sym,
WMNativeKeysym *lower, WMNativeKeysym *upper)
{
(void)ectx;
if (lower) *lower = (WMNativeKeysym)xkb_keysym_to_lower((xkb_keysym_t)sym);
if (upper) *upper = (WMNativeKeysym)xkb_keysym_to_upper((xkb_keysym_t)sym);
}
static void wl_send_wm_command(void *ectx, const char *cmd)
{
(void)ectx;
(void)cmd;
/* Wayland backend relies on inotify to detect defaults file changes.
* No explicit signal needed — wmaker's event loop polls inotify. */
}
static void wl_set_window_hints(void *ectx, WNativeWindow w,
int minw, int minh, int maxw, int maxh, int bw, int bh)
{ (void)ectx; (void)w; (void)minw; (void)minh; (void)maxw; (void)maxh; (void)bw; (void)bh; }
static void wl_set_window_property_string(void *ectx, WNativeWindow w,
const char *atom, const char *val)
{ (void)ectx; (void)w; (void)atom; (void)val; }
static void wl_set_window_property_data(void *ectx, WNativeWindow w,
WMNativeAtom prop, WMNativeAtom type, int fmt, const void *data, int nel)
{ (void)ectx; (void)w; (void)prop; (void)type; (void)fmt; (void)data; (void)nel; }
static void wl_recolor_cursor(void *ectx, WMNativePixmap c,
unsigned short fr, unsigned short fg, unsigned short fb,
unsigned short br, unsigned short bg, unsigned short bb)
{ (void)ectx; (void)c; (void)fr; (void)fg; (void)fb; (void)br; (void)bg; (void)bb; }
static void wl_set_error_handler(void *ectx, void *h)
{ (void)ectx; (void)h; }
static void wl_restore_error_handler(void *ectx)
{ (void)ectx; }
const W_EventBackend W_EventBackendWL = {
.screen_init = wl_screen_init,
.screen_fini = wl_screen_fini,
.pending = wl_pending,
.fetch = wl_fetch,
.check_mask = wl_check_mask,
.check_typed_window = wl_check_typed_window,
.flush = wl_flush,
.fd = wl_fd,
.put_back = wl_put_back,
.post_dispatch = NULL,
.grab_keyboard = wl_grab_keyboard,
.ungrab_keyboard = wl_ungrab_keyboard,
.allow_events = wl_allow_events,
.grab_pointer = wl_grab_pointer,
.ungrab_pointer = wl_ungrab_pointer,
.query_pointer = wl_query_pointer,
.get_window_geometry = wl_get_window_geometry,
.move_window = wl_move_window,
.window_begin_move = wl_window_begin_move,
.translate_coordinates = wl_translate_coordinates,
.get_root_window = wl_get_root_window,
.query_tree = wl_query_tree,
.get_pointer_control = wl_get_pointer_control,
.set_pointer_control = wl_set_pointer_control,
.modifier_from_key = wl_modifier_from_key,
.keysym_to_string = wl_keysym_to_string,
.keysym_to_keycode = wl_keysym_to_keycode,
.keycode_to_keysym = wl_keycode_to_keysym,
.keysym_convert_case = wl_keysym_convert_case,
.get_modifier_keysyms = wl_get_modifier_keysyms,
.send_wm_command = wl_send_wm_command,
.set_window_hints = wl_set_window_hints,
.set_window_property_string = wl_set_window_property_string,
.set_window_property_data = wl_set_window_property_data,
.recolor_cursor = wl_recolor_cursor,
.set_error_handler = wl_set_error_handler,
.restore_error_handler = wl_restore_error_handler,
};
/* ------------------------------------------------------------------ */
/* Called from wl_backend.c */
/* ------------------------------------------------------------------ */
void
W_WL_EnqueueEvent(W_Screen *scr, const WMEvent *ev, W_View *target)
{
if (!scr || !scr->eventBackendCtx)
return;
WLEventCtx *ctx = scr->eventBackendCtx;
int next_tail = (ctx->tail + 1) % WL_EVENT_QUEUE_CAP;
if (next_tail == ctx->head)
return; /* queue full — drop */
ctx->buf[ctx->tail].ev = *ev;
ctx->buf[ctx->tail].target = target;
ctx->tail = next_tail;
}
void
W_WL_SetLoopDispatch(W_Screen *scr,
int loop_fd,
void (*dispatch_fn)(int timeout_ms, void *ud),
void *userdata)
{
if (!scr || !scr->eventBackendCtx)
return;
WLEventCtx *ctx = scr->eventBackendCtx;
ctx->loop_fd = loop_fd;
ctx->dispatch_fn = dispatch_fn;
ctx->dispatch_ud = userdata;
}
/* Tick the Wayland compositor event loop once (used by modal loops). */
void
W_WL_InvalidateView(W_Screen *scr, W_View *target)
{
if (!scr || !scr->eventBackendCtx)
return;
WLEventCtx *ctx = scr->eventBackendCtx;
int i = ctx->head;
while (i != ctx->tail) {
if (ctx->buf[i].target == target) {
ctx->buf[i].target = NULL;
}
i = (i + 1) % WL_EVENT_QUEUE_CAP;
}
}
void
W_WL_DispatchLoop(W_Screen *scr, int timeout_ms)
{
if (!scr || !scr->eventBackendCtx)
return;
WLEventCtx *ctx = scr->eventBackendCtx;
if (ctx->dispatch_fn)
ctx->dispatch_fn(timeout_ms, ctx->dispatch_ud);
}