#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include "../../screen.h" #include "wl_types.h" #include /* Forward declarations for functions defined in other wl_*.c files */ void W_WL_EnqueueEvent(struct W_Screen *scr, const WMEvent *ev, struct W_View *target); void wl_grab_apply_motion(void); void wl_update_pointer_focus(uint32_t time_msec); WMScreen *wl_get_wmscreen(void); static void wl_pointer_get_bounds(int *ow, int *oh) { WScreen *scr = wl_state.scr; if (scr && scr->scr_width > 0 && scr->scr_height > 0) { *ow = scr->scr_width; *oh = scr->scr_height; } else if (wl_state.output) { *ow = (int)wl_state.output->width; *oh = (int)wl_state.output->height; } else { *ow = 1920; *oh = 1080; } } void handle_pointer_motion(struct wl_listener *listener, void *data) { (void)listener; struct wlr_pointer_motion_event *ev = data; /* Use wlr_cursor_move to apply the delta. wlr_cursor_move respects * the output layout geometry and constrains the pointer to the union * of all outputs, which is the correct bounding region for multi-head. * The old code manually accumulated delta into pointer_x/y and clamped * to scr_width×scr_height (a bounding-box rectangle), which is wrong * for non-rectangular layouts (e.g. monitors at different heights). * After the move, read back the authoritative position from wlr_cursor. */ if (wl_state.cursor) { wlr_cursor_move(wl_state.cursor, &ev->pointer->base, ev->delta_x, ev->delta_y); wl_state.pointer_x = (int)wl_state.cursor->x; wl_state.pointer_y = (int)wl_state.cursor->y; } else { int ow, oh; wl_pointer_get_bounds(&ow, &oh); wl_state.pointer_x += (int)ev->delta_x; wl_state.pointer_y += (int)ev->delta_y; if (wl_state.pointer_x < 0) wl_state.pointer_x = 0; if (wl_state.pointer_y < 0) wl_state.pointer_y = 0; if (wl_state.pointer_x >= ow) wl_state.pointer_x = ow - 1; if (wl_state.pointer_y >= oh) wl_state.pointer_y = oh - 1; } /* Phase A: enqueue motion event for WINGs. * Use frame_buf_hit so button sub-windows (which have no active scene_buf * buffer and are invisible to wlr_scene_node_at) are resolved correctly. */ WMScreen *wmscr = wl_get_wmscreen(); if (wmscr) { WMEvent wev; memset(&wev, 0, sizeof(wev)); wev.type = WME_MOTION; struct wl_frame_buf *fb = frame_buf_hit(wl_state.pointer_x, wl_state.pointer_y); WNativeWindow new_id; if (fb) { wev.window = fb->id; wev.u.motion.window = fb->id; wev.u.motion.x = wl_state.pointer_x - fb->x; wev.u.motion.y = wl_state.pointer_y - fb->y; new_id = fb->id; } else { WScreen *scr0 = wScreenWithNumber(0); WNativeWindow root = scr0 ? scr0->root_win : (WNativeWindow)0x0FFFFFFF; wev.window = root; wev.u.motion.window = root; wev.u.motion.x = wl_state.pointer_x; wev.u.motion.y = wl_state.pointer_y; new_id = root; } wev.u.motion.time = ev->time_msec; wev.u.motion.x_root = wl_state.pointer_x; wev.u.motion.y_root = wl_state.pointer_y; wev.u.motion.state = wl_state.current_mods | wl_state.current_buttons; /* Resolve WINGs child view (slider, etc.) at pointer for * motion routing during drag operations. */ struct W_View *mtarget = 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) { mtarget = W_FindViewAtPoint(toplevel, wev.u.motion.x, wev.u.motion.y); /* Adjust event coordinates to target view's local space */ if (mtarget && mtarget != toplevel) { W_View *p = mtarget; int ox = 0, oy = 0; while (p && p != toplevel) { ox += p->pos.x; oy += p->pos.y; p = p->parent; } wev.u.motion.x -= ox; wev.u.motion.y -= oy; } } } W_WL_EnqueueEvent(wmscr, &wev, mtarget); /* Synthesize ENTER/LEAVE for WINGs child view transitions. */ if (mtarget != wl_state.last_hovered_view) { WMEvent cev; memset(&cev, 0, sizeof(cev)); if (wl_state.last_hovered_view) { cev.type = WME_LEAVE; cev.window = wl_state.last_hovered_view->window; cev.u.crossing.window = cev.window; cev.u.crossing.mode = WM_NOTIFY_NORMAL; cev.u.crossing.detail = WM_NOTIFY_ANCESTOR; W_WL_EnqueueEvent(wmscr, &cev, wl_state.last_hovered_view); } if (mtarget) { cev.type = WME_ENTER; cev.window = mtarget->window; cev.u.crossing.window = cev.window; cev.u.crossing.mode = WM_NOTIFY_NORMAL; cev.u.crossing.detail = WM_NOTIFY_ANCESTOR; W_WL_EnqueueEvent(wmscr, &cev, mtarget); } wl_state.last_hovered_view = mtarget; } /* Emit synthetic LEAVE/ENTER when pointer crosses WM sub-window * boundaries (titlebar ↔ button, etc.). These feed the inner * press-track loop in framewin.c:buttonMouseDown so the cancel- * on-leave behaviour works correctly. */ if (new_id != wl_state.last_hovered_id) { WMEvent xev; memset(&xev, 0, sizeof(xev)); if (wl_state.last_hovered_id) { xev.type = WME_LEAVE; xev.window = wl_state.last_hovered_id; xev.u.crossing.window = wl_state.last_hovered_id; xev.u.crossing.x_root = wl_state.pointer_x; xev.u.crossing.y_root = wl_state.pointer_y; xev.u.crossing.mode = WM_NOTIFY_NORMAL; xev.u.crossing.detail = WM_NOTIFY_ANCESTOR; W_WL_EnqueueEvent(wmscr, &xev, NULL); } xev.type = WME_ENTER; xev.window = new_id; xev.u.crossing.window = new_id; xev.u.crossing.x_root = wl_state.pointer_x; xev.u.crossing.y_root = wl_state.pointer_y; xev.u.crossing.mode = WM_NOTIFY_NORMAL; xev.u.crossing.detail = WM_NOTIFY_ANCESTOR; W_WL_EnqueueEvent(wmscr, &xev, NULL); wl_state.last_hovered_id = new_id; } } if (wl_state.grab_type) { wl_grab_apply_motion(); return; } wl_update_pointer_focus(ev->time_msec); /* Update drag icon position if a drag is active */ extern struct wlr_scene_tree *drag_icon_tree; if (drag_icon_tree) wlr_scene_node_set_position(&drag_icon_tree->node, wl_state.pointer_x, wl_state.pointer_y); } void handle_pointer_motion_absolute(struct wl_listener *listener, void *data) { (void)listener; struct wlr_pointer_motion_absolute_event *ev = data; /* In nested multi-output mode, each host output window has its own * wl_pointer device with output_name set (e.g. "WL-1", "WL-2"). * wlr_cursor_absolute_to_layout_coords uses the device→output mapping * to convert the per-window 0..1 range to the correct output's layout * coordinates. The old early-return here discarded these events, * leaving only relative deltas — which have no concept of which host * window the mouse is in, causing the cursor to drift to the wrong * output on every window transition. */ if (wl_state.cursor) { double lx = 0.0, ly = 0.0; wlr_cursor_absolute_to_layout_coords( wl_state.cursor, &ev->pointer->base, ev->x, ev->y, &lx, &ly); wlr_cursor_warp(wl_state.cursor, NULL, lx, ly); /* Read back from wlr_cursor — it is the single source of truth * for pointer position in layout space. */ wl_state.pointer_x = (int)wl_state.cursor->x; wl_state.pointer_y = (int)wl_state.cursor->y; } else { int ow = 0, oh = 0; wl_pointer_get_bounds(&ow, &oh); wl_state.pointer_x = (int)(ev->x * (double)ow); wl_state.pointer_y = (int)(ev->y * (double)oh); if (wl_state.pointer_x < 0) wl_state.pointer_x = 0; if (wl_state.pointer_y < 0) wl_state.pointer_y = 0; } /* Phase A: enqueue motion event for WINGs. * Use frame_buf_hit so button sub-windows (which have no active scene_buf * buffer and are invisible to wlr_scene_node_at) are resolved correctly. */ WMScreen *wmscr2 = wl_get_wmscreen(); if (wmscr2) { WMEvent wev; memset(&wev, 0, sizeof(wev)); wev.type = WME_MOTION; struct wl_frame_buf *fb = frame_buf_hit(wl_state.pointer_x, wl_state.pointer_y); WNativeWindow new_id; if (fb) { wev.window = fb->id; wev.u.motion.window = fb->id; wev.u.motion.x = wl_state.pointer_x - fb->x; wev.u.motion.y = wl_state.pointer_y - fb->y; new_id = fb->id; } else { WScreen *scr0 = wScreenWithNumber(0); WNativeWindow root = scr0 ? scr0->root_win : (WNativeWindow)0x0FFFFFFF; wev.window = root; wev.u.motion.window = root; wev.u.motion.x = wl_state.pointer_x; wev.u.motion.y = wl_state.pointer_y; new_id = root; } wev.u.motion.time = ev->time_msec; wev.u.motion.x_root = wl_state.pointer_x; wev.u.motion.y_root = wl_state.pointer_y; wev.u.motion.state = wl_state.current_mods | wl_state.current_buttons; W_WL_EnqueueEvent(wmscr2, &wev, NULL); /* Emit synthetic LEAVE/ENTER on sub-window boundary crossings. */ if (new_id != wl_state.last_hovered_id) { WMEvent xev; memset(&xev, 0, sizeof(xev)); if (wl_state.last_hovered_id) { xev.type = WME_LEAVE; xev.window = wl_state.last_hovered_id; xev.u.crossing.window = wl_state.last_hovered_id; xev.u.crossing.x_root = wl_state.pointer_x; xev.u.crossing.y_root = wl_state.pointer_y; xev.u.crossing.mode = WM_NOTIFY_NORMAL; xev.u.crossing.detail = WM_NOTIFY_ANCESTOR; W_WL_EnqueueEvent(wmscr2, &xev, NULL); } xev.type = WME_ENTER; xev.window = new_id; xev.u.crossing.window = new_id; xev.u.crossing.x_root = wl_state.pointer_x; xev.u.crossing.y_root = wl_state.pointer_y; xev.u.crossing.mode = WM_NOTIFY_NORMAL; xev.u.crossing.detail = WM_NOTIFY_ANCESTOR; W_WL_EnqueueEvent(wmscr2, &xev, NULL); wl_state.last_hovered_id = new_id; } } if (wl_state.grab_type) { wl_grab_apply_motion(); return; } wl_update_pointer_focus(ev->time_msec); } /* * seat request_set_cursor — client wants to set a custom cursor * image (e.g. I-beam in a text field, hand over a link). Validate that * the request comes from the currently focused pointer client, then hand * the surface to wlr_cursor so the hardware cursor is updated on all * outputs (which in nested mode sends wl_pointer.set_cursor to the host). */ void handle_seat_request_cursor(struct wl_listener *listener, void *data) { (void)listener; struct wlr_seat_pointer_request_set_cursor_event *ev = data; if (!wl_state.seat || !wl_state.cursor) return; /* Only honour cursor requests from the client that currently has * pointer focus; reject requests from unfocused clients. */ struct wlr_seat_client *focused = wl_state.seat->pointer_state.focused_client; if (ev->seat_client != focused) return; wlr_cursor_set_surface(wl_state.cursor, ev->surface, ev->hotspot_x, ev->hotspot_y); }