From e2f048a6a9e5dc81515336b692a97657f4e3cc54 Mon Sep 17 00:00:00 2001 From: Window Maker Date: Tue, 2 Jun 2026 16:27:08 +0200 Subject: [PATCH] decompose wl_backend.c: extract wl_handlers.c --- src/Makefile.am | 3 +- src/backend/wayland/wl_backend.c | 484 +----------------------------- src/backend/wayland/wl_handlers.c | 298 ++++++++++++++++++ 3 files changed, 312 insertions(+), 473 deletions(-) create mode 100644 src/backend/wayland/wl_handlers.c diff --git a/src/Makefile.am b/src/Makefile.am index 3775ffd..9db8cfa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -192,13 +192,14 @@ wmaker_SOURCES += \ backend/wayland/wl_popup.c \ backend/wayland/wl_text_input.c \ backend/wayland/wl_xdg_activation.c \ + backend/wayland/wl_layer_shell.c \ backend/wayland/wl_output_mgmt.c \ backend/wayland/wl_output.c \ backend/wayland/wl_xdg.c \ backend/wayland/wl_xwayland.c \ backend/wayland/wl_dock.c \ - backend/wayland/wl_layer_shell.c \ backend/wayland/wl_virtual_pointer.c \ + backend/wayland/wl_handlers.c \ backend/wayland/wl_input_fwd.c endif diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index aa92b20..014649f 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -304,11 +304,11 @@ extern struct wlr_surface *view_surface_at(int px, int py, double *sx_out, /* Phase 24: client cursor-surface request handler. */ extern void handle_seat_request_cursor(struct wl_listener *listener, void *data); /* Phase 25: client clipboard selection request handler. */ -static void handle_seat_request_set_selection(struct wl_listener *listener, void *data); +extern void handle_seat_request_set_selection(struct wl_listener *listener, void *data); /* Phase 25b: drag-and-drop request handler. */ void handle_seat_request_start_drag(struct wl_listener *listener, void *data); /* Phase 26: primary selection request handler. */ -static void handle_seat_request_primary_selection(struct wl_listener *listener, void *data); +extern void handle_seat_request_primary_selection(struct wl_listener *listener, void *data); /* Compositor-internal clipboard hooks for WINGs. */ void wl_clipboard_store_to_seat(const char *text, int len); char *wl_clipboard_fetch_from_seat(int *len_out); @@ -319,13 +319,13 @@ void wl_drag_icon_move(int x, int y); void wl_drag_icon_hide(void); void wl_drag_cursor_feedback(Bool accepted); /* Phase 28: idle inhibitor created/destroyed handler. */ -static void handle_idle_new_inhibitor(struct wl_listener *listener, void *data); +extern void handle_idle_new_inhibitor(struct wl_listener *listener, void *data); /* Phase 29: scroll-wheel axis event handler. */ extern void handle_pointer_axis(struct wl_listener *listener, void *data); /* Phase 30: new pointer constraint handler. */ -static void handle_new_pointer_constraint(struct wl_listener *listener, void *data); +extern void handle_new_pointer_constraint(struct wl_listener *listener, void *data); /* Phase 31: new keyboard shortcuts inhibitor handler. */ -static void handle_new_shortcuts_inhibitor(struct wl_listener *listener, void *data); +extern void handle_new_shortcuts_inhibitor(struct wl_listener *listener, void *data); /* Phase 33: output management apply/test + output power set_mode handlers. */ extern void handle_output_mgr_apply(struct wl_listener *listener, void *data); extern void handle_output_mgr_test(struct wl_listener *listener, void *data); @@ -337,11 +337,11 @@ extern void handle_virtual_keyboard_new(struct wl_listener *listener, void *data /* Phase 35: xdg_activation request_activate handler. */ extern void handle_xdg_activation_request(struct wl_listener *listener, void *data); /* Phase 36: layer shell new_surface handler. */ -static void handle_layer_shell_new_surface(struct wl_listener *listener, void *data); +extern void handle_layer_shell_new_surface(struct wl_listener *listener, void *data); extern void handle_xdg_new_toplevel(struct wl_listener *listener, void *data); extern void handle_new_toplevel_decoration(struct wl_listener *listener, void *data); /* Phase 37: virtual pointer handler. */ -extern void handle_virtual_pointer_new(struct wl_listener *listener, void *data); +static void handle_virtual_pointer_new(struct wl_listener *listener, void *data); /* Phase 17: XWayland new surface forward declaration. */ extern void handle_xwayland_new_surface(struct wl_listener *listener, void *data); extern void handle_xwayland_ready(struct wl_listener *listener, void *data); @@ -1595,472 +1595,7 @@ wl_event_loop_terminate(void) * (i.e. Ctrl-C or other clipboard write). Forward to wlr_seat so the * selection becomes available to the next client that reads it. */ -static void -handle_seat_request_set_selection(struct wl_listener *listener, void *data) -{ - (void)listener; - struct wlr_seat_request_set_selection_event *ev = data; - if (!wl_state.seat) - return; - wlr_seat_set_selection(wl_state.seat, ev->source, ev->serial); -} - - -/* - * Phase 26: seat request_set_primary_selection — client writes to the - * primary selection buffer (e.g. by selecting text). Forward to - * wlr_seat so middle-click paste works in other clients. - */ -static void -handle_seat_request_primary_selection(struct wl_listener *listener, void *data) -{ - (void)listener; - struct wlr_seat_request_set_primary_selection_event *ev = data; - if (!wl_state.seat) - return; - wlr_seat_set_primary_selection(wl_state.seat, ev->source, ev->serial); -} - -/* - * Phase 28: idle_inhibit new_inhibitor — a client (e.g. mpv, a game) created - * an idle-inhibit object. wlroots tracks the inhibitor list internally; - * the compositor can query wl_state.idle_inhibit_mgr->inhibitors to decide - * whether to suppress screen blanking. For now we just log the event so - * the capability is advertised and the inhibitor object doesn't error out. - */ -static void -handle_idle_new_inhibitor(struct wl_listener *listener, void *data) -{ - (void)listener; - struct wlr_idle_inhibitor_v1 *inhibitor = data; - (void)inhibitor; - wwarning("wl_backend: idle inhibitor created (client suppressing screen blank)"); -} - -/* - * Phase 29 (fix): scroll-wheel axis event handler. - * wlr_event_pointer_axis carries orientation (vertical/horizontal), delta, - * delta_discrete (notches), and source (wheel vs finger). Forward directly - * to the seat so the focused Wayland client receives wl_pointer.axis events. - * Without this, scrolling is silently dropped in every Wayland client. - */ void -handle_pointer_axis(struct wl_listener *listener, void *data) -{ - (void)listener; - struct wlr_pointer_axis_event *ev = data; - if (!wl_state.seat) - return; - /* Drop axis events during a compositor grab so the grabbed window - * does not receive unexpected scroll events mid-move/resize. */ - if (wl_state.grab_type) - return; - wlr_seat_pointer_notify_axis(wl_state.seat, - ev->time_msec, - ev->orientation, - ev->delta, - ev->delta_discrete, - ev->source, - ev->relative_direction); -} - -/* - * Phase 30: new pointer constraint — a client requested pointer lock or - * pointer confinement (e.g. a game locking the cursor to its window). - * We activate the constraint immediately so the client gets confirmed. - * In headless mode the pointer is virtual so there is nothing physically - * to lock, but activating keeps the client happy. - */ -static void -handle_new_pointer_constraint(struct wl_listener *listener, void *data) -{ - (void)listener; - struct wlr_pointer_constraint_v1 *constraint = data; - (void)constraint; - /* Activate immediately — in nested mode the real pointer is bounded - * by the host compositor's window anyway. */ - wlr_pointer_constraint_v1_send_activated(constraint); - wwarning("wl_backend: pointer constraint activated"); -} - -/* - * Phase 31: new keyboard shortcuts inhibitor — a fullscreen app or remote - * desktop client wants to suppress WM keyboard shortcuts. - * Activate the inhibitor immediately so the client knows it is granted. - * The existing wWaylandKeyPress() dispatcher in handle_keyboard_key already - * forwards all non-shortcut keys; with the inhibitor active we should skip - * the shortcut check for that focused client. For now we simply activate - * and log; per-client inhibitor tracking can be refined later. - */ -static void -handle_new_shortcuts_inhibitor(struct wl_listener *listener, void *data) -{ - (void)listener; - struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data; - wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor); - wwarning("wl_backend: keyboard shortcuts inhibitor activated"); -} - -/* ------------------------------------------------------------------ */ -/* ------------------------------------------------------------------ */ - -/* ------------------------------------------------------------------ */ -/* Phase 36: layer shell — panels, docks, wallpapers, overlays */ -/* ------------------------------------------------------------------ */ - -#define LAYER_VIEW_FROM(l, field) \ - ((struct wl_layer_view *)((char *)(l) - offsetof(struct wl_layer_view, field))) - -/* - * Compute the geometry for a layer surface given its anchor, margins, - * desired size and the output dimensions. - */ -static void -layer_surface_compute_geometry(struct wlr_layer_surface_v1 *ls, - uint32_t ow, uint32_t oh, - uint32_t *out_w, uint32_t *out_h) -{ - const struct wlr_layer_surface_v1_state *s = &ls->current; - uint32_t w = s->desired_width; - uint32_t h = s->desired_height; - - bool anchor_lr = (s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) && - (s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); - bool anchor_tb = (s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) && - (s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM); - - if (w == 0 && anchor_lr) - w = ow - s->margin.left - s->margin.right; - if (h == 0 && anchor_tb) - h = oh - s->margin.top - s->margin.bottom; - - /* Ensure minimum 1×1. */ - *out_w = w > 0 ? w : 1; - *out_h = h > 0 ? h : 1; -} - -/* - * Compute the position of a layer surface on the output given its - * anchor, margins and final dimensions. - */ -static void -layer_surface_compute_position(struct wlr_layer_surface_v1 *ls, - uint32_t ow, uint32_t oh, - uint32_t fw, uint32_t fh, - int *out_x, int *out_y) -{ - const struct wlr_layer_surface_v1_state *s = &ls->current; - int x = 0, y = 0; - - bool anchor_l = s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; - bool anchor_r = s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - bool anchor_t = s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; - bool anchor_b = s->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - - if (anchor_l && anchor_r) - x = s->margin.left; - else if (anchor_l) - x = s->margin.left; - else if (anchor_r) - x = (int)ow - (int)fw - s->margin.right; - else - x = ((int)ow - (int)fw) / 2; - - if (anchor_t && anchor_b) - y = s->margin.top; - else if (anchor_t) - y = s->margin.top; - else if (anchor_b) - y = (int)oh - (int)fh - s->margin.bottom; - else - y = ((int)oh - (int)fh) / 2; - - *out_x = x; - *out_y = y; -} - -static void -handle_layer_surface_map(struct wl_listener *listener, void *data) -{ - (void)data; - struct wl_layer_view *lv = LAYER_VIEW_FROM(listener, map_listener); - wwarning("wl_backend: layer surface mapped (layer=%d namespace=%s)", - lv->layer_surface->current.layer, - lv->layer_surface->namespace ? lv->layer_surface->namespace : ""); -} - -static void -handle_layer_surface_unmap(struct wl_listener *listener, void *data) -{ - (void)data; - struct wl_layer_view *lv = LAYER_VIEW_FROM(listener, unmap_listener); - wwarning("wl_backend: layer surface unmapped"); - if (lv->scene_node) { - wlr_scene_node_destroy(&lv->scene_node->node); - lv->scene_node = NULL; - } -} - -static void -handle_layer_surface_destroy(struct wl_listener *listener, void *data) -{ - (void)data; - struct wl_layer_view *lv = LAYER_VIEW_FROM(listener, destroy_listener); - wl_list_remove(&lv->map_listener.link); - wl_list_remove(&lv->unmap_listener.link); - wl_list_remove(&lv->destroy_listener.link); - if (lv->scene_node) { - wlr_scene_node_destroy(&lv->scene_node->node); - lv->scene_node = NULL; - } - wl_list_remove(&lv->link); - free(lv); -} - -/* - * handle_layer_shell_new_surface — a client created a layer surface. - * Assign the output if unset, compute geometry, send configure, and - * wire map/unmap/destroy lifecycle. - */ -static void -handle_layer_shell_new_surface(struct wl_listener *listener, void *data) -{ - (void)listener; - struct wlr_layer_surface_v1 *ls = data; - - /* Assign output if client did not specify one. */ - if (!ls->output && wl_state.output) - ls->output = wl_state.output; - - struct wl_layer_view *lv = calloc(1, sizeof(*lv)); - if (!lv) { - wlr_layer_surface_v1_destroy(ls); - return; - } - lv->layer_surface = ls; - - /* Add to scene graph in the correct level bucket. - * Layer-shell layer enum maps to WL_LAYER_* buckets: - * BACKGROUND -> WL_LAYER_BACKGROUND - * BOTTOM -> WL_LAYER_DESKTOP - * TOP -> WL_LAYER_DOCK - * OVERLAY -> WL_LAYER_OVERLAY */ - if (wl_state.scene) { - int _lb; - switch (ls->pending.layer) { - case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: _lb = WL_LAYER_BACKGROUND; break; - case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: _lb = WL_LAYER_DESKTOP; break; - case ZWLR_LAYER_SHELL_V1_LAYER_TOP: _lb = WL_LAYER_DOCK; break; - case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: _lb = WL_LAYER_OVERLAY; break; - default: _lb = WL_LAYER_NORMAL; break; - } - struct wlr_scene_tree *_lp = wl_state.level_trees[_lb] - ? wl_state.level_trees[_lb] : &wl_state.scene->tree; - lv->scene_node = wlr_scene_subsurface_tree_create(_lp, ls->surface); - } - - /* Compute and send initial configure. */ - uint32_t ow = 1920, oh = 1080; - if (ls->output) { - ow = ls->output->width > 0 ? (uint32_t)ls->output->width : 1920; - oh = ls->output->height > 0 ? (uint32_t)ls->output->height : 1080; - } - uint32_t fw, fh; - layer_surface_compute_geometry(ls, ow, oh, &fw, &fh); - wlr_layer_surface_v1_configure(ls, fw, fh); - - /* Position the scene node. */ - if (lv->scene_node) { - int sx, sy; - layer_surface_compute_position(ls, ow, oh, fw, fh, &sx, &sy); - wlr_scene_node_set_position(&lv->scene_node->node, sx, sy); - } - - /* Wire lifecycle listeners. */ - lv->map_listener.notify = handle_layer_surface_map; - lv->unmap_listener.notify = handle_layer_surface_unmap; - lv->destroy_listener.notify = handle_layer_surface_destroy; - wl_signal_add(&ls->surface->events.map, &lv->map_listener); - wl_signal_add(&ls->surface->events.unmap, &lv->unmap_listener); - wl_signal_add(&ls->events.destroy, &lv->destroy_listener); - - wl_list_insert(&wl_state.layer_list, &lv->link); - - wwarning("wl_backend: layer surface new layer=%d %ux%u namespace=%s", - ls->current.layer, fw, fh, - ls->namespace ? ls->namespace : ""); -} - -/* ------------------------------------------------------------------ */ -void -handle_pointer_button(struct wl_listener *listener, void *data) -{ - (void)listener; - struct wlr_pointer_button_event *ev = data; - if (!wl_state.seat) - return; - /* Phase 22: any button release ends an active compositor grab (move/resize). - * Type 3 (WM policy grab) is NOT ended here — the policy code calls - * pointer_ungrab itself after processing the button release event. */ - if (wl_state.grab_type && wl_state.grab_type != 3 && - ev->state == WLR_BUTTON_RELEASED) { - wwarning("wl_backend: grab ended (button release) grab_type=%d", wl_state.grab_type); - wl_grab_end(); - wl_update_pointer_focus(ev->time_msec); - return; - } - /* Phase 24: click-to-focus — on button press, find the view under the - * pointer and focus + raise it via the WM policy layer. - * This mirrors what the X11 backend does via ButtonPress on frame windows. */ - if (ev->state == WLR_BUTTON_PRESSED) { - double sx, sy; - struct wl_toplevel_view *view = NULL; - view_surface_at(wl_state.pointer_x, wl_state.pointer_y, - &sx, &sy, &view); - if (view) { - WObjDescriptor *desc = - wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); - if (desc && desc->parent_type == WCLASS_WINDOW) { - WWindow *wwin = (WWindow *)desc->parent; - WScreen *scr = wScreenWithNumber(0); - wSetFocusTo(scr, wwin); - /* Raise entire window assembly via tree */ - if (view->frame_id) { - struct wl_frame_buf *fb = frame_buf_find(view->frame_id); - if (fb && fb->tree) - wlr_scene_node_raise_to_top(&fb->tree->node); - } else if (view->scene_node) { - wlr_scene_node_raise_to_top(&view->scene_node->node); - } - /* Also raise it in our toplevel list so view_surface_at() - * returns it first on subsequent hits. */ - wl_list_remove(&view->link); - wl_list_insert(&wl_state.toplevel_list, &view->link); - } - } else { - /* No toplevel surface — check if a WINGs dialog frame_buf was clicked */ - struct wl_frame_buf *hit = frame_buf_hit(wl_state.pointer_x, - wl_state.pointer_y); - if (hit) { - /* Walk up to the parent frame to find the WWindow */ - WNativeWindow lookup = hit->parent_id ? hit->parent_id : hit->id; - WObjDescriptor *desc = - wm_backend->context_find(lookup, WM_CTX_CLIENT_WIN); - if (desc && desc->parent_type == WCLASS_WINDOW) { - WWindow *wwin = (WWindow *)desc->parent; - WScreen *scr = wScreenWithNumber(0); - wSetFocusTo(scr, wwin); - } - } - } - } - - /* Phase A: enqueue native WMEvent for WINGs */ - WMScreen *wmscr = wl_get_wmscreen(); - if (wmscr) { - WMEvent wev; - memset(&wev, 0, sizeof(wev)); - wev.type = (ev->state == WLR_BUTTON_PRESSED) - ? WME_BUTTON_PRESS : WME_BUTTON_RELEASE; - - /* Hit-test: determine which WM window was clicked. - * Use frame_buf_hit so button sub-windows (invisible to the scene - * graph because their pixels are composited into the titlebar buffer) - * are resolved to their own window IDs, letting handleButtonPress - * route the event to buttonMouseDown instead of titlebarMouseDown. */ - struct wl_frame_buf *fb = frame_buf_hit(wl_state.pointer_x, - wl_state.pointer_y); - if (fb) { - wev.window = fb->id; - wev.u.button.window = fb->id; - wev.u.button.x = wl_state.pointer_x - fb->x; - wev.u.button.y = wl_state.pointer_y - fb->y; - } else { - WScreen *scr0 = wScreenWithNumber(0); - WNativeWindow root = scr0 ? scr0->root_win : (WNativeWindow)0x0FFFFFFF; - wev.window = root; - wev.u.button.window = root; - wev.u.button.x = wl_state.pointer_x; - wev.u.button.y = wl_state.pointer_y; - } - wev.u.button.time = ev->time_msec; - wev.u.button.x_root = wl_state.pointer_x; - wev.u.button.y_root = wl_state.pointer_y; - /* Linux evdev BTN_LEFT=0x110, BTN_RIGHT=0x111, BTN_MIDDLE=0x112 - * Button1=1(left), Button2=2(middle), Button3=3(right) */ - switch (ev->button) { - case 0x110: wev.u.button.button = 1; break; - case 0x111: wev.u.button.button = 3; break; - case 0x112: wev.u.button.button = 2; break; - default: wev.u.button.button = (ev->button - 0x110) + 1; break; - } - /* For WINGs views, resolve the child widget at click coordinates */ - struct W_View *target = NULL; - if (fb && fb->id >= 0x20000000) { - extern W_View *W_GetViewForWindow(unsigned long id); - extern struct W_View *W_FindViewAtPoint(struct W_View *parent, int x, int y); - W_View *toplevel = W_GetViewForWindow((unsigned long)fb->id); - if (toplevel) { - target = W_FindViewAtPoint(toplevel, - wev.u.button.x, wev.u.button.y); - if (!target) - target = toplevel; - /* Adjust event coordinates to target view's local space */ - if (target != toplevel) { - W_View *p = target; - int ox = 0, oy = 0; - while (p && p != toplevel) { - ox += p->pos.x; - oy += p->pos.y; - p = p->parent; - } - wev.u.button.x -= ox; - wev.u.button.y -= oy; - } - } - } - W_WL_EnqueueEvent(wmscr, &wev, target); - } - - /* Refresh pointer focus before delivering button event. - * The last wl_update_pointer_focus call was during the most recent - * motion event. If no motion happened since then, the XWayland - * surface might not have pointer focus, causing - * wlr_seat_pointer_notify_button to be a no-op. */ - wl_update_pointer_focus(ev->time_msec); - - wlr_seat_pointer_notify_button(wl_state.seat, ev->time_msec, - ev->button, ev->state); - wlr_seat_pointer_notify_frame(wl_state.seat); -} - -void -handle_pointer_frame(struct wl_listener *listener, void *data) -{ - (void)listener; (void)data; - if (wl_state.seat) - wlr_seat_pointer_notify_frame(wl_state.seat); -} - - - -/* ------------------------------------------------------------------ */ -/* Keyboard grabs */ - - - - - -/* ------------------------------------------------------------------ */ -/* Screen internals */ -/* ------------------------------------------------------------------ */ - - int wl_screen_replace_wm(WScreen *scr) { (void)scr; return 1; } - int wl_screen_select_input(WScreen *scr, long mask) { (void)scr; (void)mask; return 1; } - void wl_screen_set_root_cursor(WScreen *scr, int cursor_id) { (void)scr; (void)cursor_id; } - void wl_screen_set_icon_sizes(WScreen *scr, int min_size, int max_size) { (void)scr; (void)min_size; (void)max_size; } - void wl_screen_setup_noticeboard(WScreen *scr) { (void)scr; } - void wl_screen_alloc_gcs(WScreen *scr) { XGCValues gcv; @@ -2628,3 +2163,8 @@ wl_monitors_query(WScreen *scr, WHeadGeometry **heads_out, int *count_out) /* ------------------------------------------------------------------ */ + + int wl_screen_select_input(WScreen *scr, long mask) { (void)scr; (void)mask; return 1; } + void wl_screen_set_root_cursor(WScreen *scr, int cursor_id) { (void)scr; (void)cursor_id; } + void wl_screen_set_icon_sizes(WScreen *scr, int min_size, int max_size) { (void)scr; (void)min_size; (void)max_size; } + void wl_screen_setup_noticeboard(WScreen *scr) { (void)scr; } diff --git a/src/backend/wayland/wl_handlers.c b/src/backend/wayland/wl_handlers.c new file mode 100644 index 0000000..35d6a8c --- /dev/null +++ b/src/backend/wayland/wl_handlers.c @@ -0,0 +1,298 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../screen.h" +#include "wl_types.h" +#include "../../actions.h" +#include + +void wl_grab_end(void); +void wl_update_pointer_focus(uint32_t time_msec); +struct wlr_surface *view_surface_at(int px, int py, double *sx_out, double *sy_out, struct wl_toplevel_view **view_out); +void wm_dock_app_remove(WNativeWindow id); + +void +handle_seat_request_set_selection(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_seat_request_set_selection_event *ev = data; + if (!wl_state.seat) + return; + wlr_seat_set_selection(wl_state.seat, ev->source, ev->serial); +} + + +/* + * Phase 26: seat request_set_primary_selection — client writes to the + * primary selection buffer (e.g. by selecting text). Forward to + * wlr_seat so middle-click paste works in other clients. + */ +void +handle_seat_request_primary_selection(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_seat_request_set_primary_selection_event *ev = data; + if (!wl_state.seat) + return; + wlr_seat_set_primary_selection(wl_state.seat, ev->source, ev->serial); +} + +/* + * Phase 28: idle_inhibit new_inhibitor — a client (e.g. mpv, a game) created + * an idle-inhibit object. wlroots tracks the inhibitor list internally; + * the compositor can query wl_state.idle_inhibit_mgr->inhibitors to decide + * whether to suppress screen blanking. For now we just log the event so + * the capability is advertised and the inhibitor object doesn't error out. + */ +void +handle_idle_new_inhibitor(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_idle_inhibitor_v1 *inhibitor = data; + (void)inhibitor; + wwarning("wl_backend: idle inhibitor created (client suppressing screen blank)"); +} + +/* + * Phase 29 (fix): scroll-wheel axis event handler. + * wlr_event_pointer_axis carries orientation (vertical/horizontal), delta, + * delta_discrete (notches), and source (wheel vs finger). Forward directly + * to the seat so the focused Wayland client receives wl_pointer.axis events. + * Without this, scrolling is silently dropped in every Wayland client. + */ +void +handle_pointer_axis(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_pointer_axis_event *ev = data; + if (!wl_state.seat) + return; + /* Drop axis events during a compositor grab so the grabbed window + * does not receive unexpected scroll events mid-move/resize. */ + if (wl_state.grab_type) + return; + wlr_seat_pointer_notify_axis(wl_state.seat, + ev->time_msec, + ev->orientation, + ev->delta, + ev->delta_discrete, + ev->source, + ev->relative_direction); +} + +/* + * Phase 30: new pointer constraint — a client requested pointer lock or + * pointer confinement (e.g. a game locking the cursor to its window). + * We activate the constraint immediately so the client gets confirmed. + * In headless mode the pointer is virtual so there is nothing physically + * to lock, but activating keeps the client happy. + */ +void +handle_new_pointer_constraint(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_pointer_constraint_v1 *constraint = data; + (void)constraint; + /* Activate immediately — in nested mode the real pointer is bounded + * by the host compositor's window anyway. */ + wlr_pointer_constraint_v1_send_activated(constraint); + wwarning("wl_backend: pointer constraint activated"); +} + +/* + * Phase 31: new keyboard shortcuts inhibitor — a fullscreen app or remote + * desktop client wants to suppress WM keyboard shortcuts. + * Activate the inhibitor immediately so the client knows it is granted. + * The existing wWaylandKeyPress() dispatcher in handle_keyboard_key already + * forwards all non-shortcut keys; with the inhibitor active we should skip + * the shortcut check for that focused client. For now we simply activate + * and log; per-client inhibitor tracking can be refined later. + */ +void +handle_new_shortcuts_inhibitor(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor = data; + wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor); + wwarning("wl_backend: keyboard shortcuts inhibitor activated"); +} + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + +void +handle_pointer_button(struct wl_listener *listener, void *data) +{ + (void)listener; + struct wlr_pointer_button_event *ev = data; + if (!wl_state.seat) + return; + /* Phase 22: any button release ends an active compositor grab (move/resize). + * Type 3 (WM policy grab) is NOT ended here — the policy code calls + * pointer_ungrab itself after processing the button release event. */ + if (wl_state.grab_type && wl_state.grab_type != 3 && + ev->state == WLR_BUTTON_RELEASED) { + wwarning("wl_backend: grab ended (button release) grab_type=%d", wl_state.grab_type); + wl_grab_end(); + wl_update_pointer_focus(ev->time_msec); + return; + } + /* Phase 24: click-to-focus — on button press, find the view under the + * pointer and focus + raise it via the WM policy layer. + * This mirrors what the X11 backend does via ButtonPress on frame windows. */ + if (ev->state == WLR_BUTTON_PRESSED) { + double sx, sy; + struct wl_toplevel_view *view = NULL; + view_surface_at(wl_state.pointer_x, wl_state.pointer_y, + &sx, &sy, &view); + if (view) { + WObjDescriptor *desc = + wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN); + if (desc && desc->parent_type == WCLASS_WINDOW) { + WWindow *wwin = (WWindow *)desc->parent; + WScreen *scr = wScreenWithNumber(0); + wSetFocusTo(scr, wwin); + /* Raise entire window assembly via tree */ + if (view->frame_id) { + struct wl_frame_buf *fb = frame_buf_find(view->frame_id); + if (fb && fb->tree) + wlr_scene_node_raise_to_top(&fb->tree->node); + } else if (view->scene_node) { + wlr_scene_node_raise_to_top(&view->scene_node->node); + } + /* Also raise it in our toplevel list so view_surface_at() + * returns it first on subsequent hits. */ + wl_list_remove(&view->link); + wl_list_insert(&wl_state.toplevel_list, &view->link); + } + } else { + /* No toplevel surface — check if a WINGs dialog frame_buf was clicked */ + struct wl_frame_buf *hit = frame_buf_hit(wl_state.pointer_x, + wl_state.pointer_y); + if (hit) { + /* Walk up to the parent frame to find the WWindow */ + WNativeWindow lookup = hit->parent_id ? hit->parent_id : hit->id; + WObjDescriptor *desc = + wm_backend->context_find(lookup, WM_CTX_CLIENT_WIN); + if (desc && desc->parent_type == WCLASS_WINDOW) { + WWindow *wwin = (WWindow *)desc->parent; + WScreen *scr = wScreenWithNumber(0); + wSetFocusTo(scr, wwin); + } + } + } + } + + /* Phase A: enqueue native WMEvent for WINGs */ + WMScreen *wmscr = wl_get_wmscreen(); + if (wmscr) { + WMEvent wev; + memset(&wev, 0, sizeof(wev)); + wev.type = (ev->state == WLR_BUTTON_PRESSED) + ? WME_BUTTON_PRESS : WME_BUTTON_RELEASE; + + /* Hit-test: determine which WM window was clicked. + * Use frame_buf_hit so button sub-windows (invisible to the scene + * graph because their pixels are composited into the titlebar buffer) + * are resolved to their own window IDs, letting handleButtonPress + * route the event to buttonMouseDown instead of titlebarMouseDown. */ + struct wl_frame_buf *fb = frame_buf_hit(wl_state.pointer_x, + wl_state.pointer_y); + if (fb) { + wev.window = fb->id; + wev.u.button.window = fb->id; + wev.u.button.x = wl_state.pointer_x - fb->x; + wev.u.button.y = wl_state.pointer_y - fb->y; + } else { + WScreen *scr0 = wScreenWithNumber(0); + WNativeWindow root = scr0 ? scr0->root_win : (WNativeWindow)0x0FFFFFFF; + wev.window = root; + wev.u.button.window = root; + wev.u.button.x = wl_state.pointer_x; + wev.u.button.y = wl_state.pointer_y; + } + wev.u.button.time = ev->time_msec; + wev.u.button.x_root = wl_state.pointer_x; + wev.u.button.y_root = wl_state.pointer_y; + /* Linux evdev BTN_LEFT=0x110, BTN_RIGHT=0x111, BTN_MIDDLE=0x112 + * Button1=1(left), Button2=2(middle), Button3=3(right) */ + switch (ev->button) { + case 0x110: wev.u.button.button = 1; break; + case 0x111: wev.u.button.button = 3; break; + case 0x112: wev.u.button.button = 2; break; + default: wev.u.button.button = (ev->button - 0x110) + 1; break; + } + /* For WINGs views, resolve the child widget at click coordinates */ + struct W_View *target = NULL; + if (fb && fb->id >= 0x20000000) { + extern W_View *W_GetViewForWindow(unsigned long id); + extern struct W_View *W_FindViewAtPoint(struct W_View *parent, int x, int y); + W_View *toplevel = W_GetViewForWindow((unsigned long)fb->id); + if (toplevel) { + target = W_FindViewAtPoint(toplevel, + wev.u.button.x, wev.u.button.y); + if (!target) + target = toplevel; + /* Adjust event coordinates to target view's local space */ + if (target != toplevel) { + W_View *p = target; + int ox = 0, oy = 0; + while (p && p != toplevel) { + ox += p->pos.x; + oy += p->pos.y; + p = p->parent; + } + wev.u.button.x -= ox; + wev.u.button.y -= oy; + } + } + } + W_WL_EnqueueEvent(wmscr, &wev, target); + } + + /* Refresh pointer focus before delivering button event. + * The last wl_update_pointer_focus call was during the most recent + * motion event. If no motion happened since then, the XWayland + * surface might not have pointer focus, causing + * wlr_seat_pointer_notify_button to be a no-op. */ + wl_update_pointer_focus(ev->time_msec); + + wlr_seat_pointer_notify_button(wl_state.seat, ev->time_msec, + ev->button, ev->state); + wlr_seat_pointer_notify_frame(wl_state.seat); +} + +void +handle_pointer_frame(struct wl_listener *listener, void *data) +{ + (void)listener; (void)data; + if (wl_state.seat) + wlr_seat_pointer_notify_frame(wl_state.seat); +} + + + +/* ------------------------------------------------------------------ */ +/* Keyboard grabs */ + + + + + +/* ------------------------------------------------------------------ */ +/* Screen internals */ +/* ------------------------------------------------------------------ */ + + int wl_screen_replace_wm(WScreen *scr) { (void)scr; return 1; }