294 lines
12 KiB
C
294 lines
12 KiB
C
#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);
|
||
}
|