windowmaker-wl/src/backend/wayland/wl_pointer_motion.c

294 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/log.h>
#include "../../screen.h"
#include "wl_types.h"
#include <WINGs/WINGsP.h>
/* 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);
}