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

362 lines
13 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/xwayland.h>
#include "../../actions.h"
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/types/wlr_layer_shell_v1.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 wl_set_cursor_image(const char *name);
/*
* Find the topmost mapped view whose surface contains screen point (px, py).
* Returns the wlr_surface and sets sx,sy to surface-local coordinates.
* Iterates toplevel_list in reverse-insertion order (last = topmost).
*/
struct wlr_surface *
view_surface_at(int px, int py, double *sx_out, double *sy_out,
struct wl_toplevel_view **view_out)
{
/* Use wlr_scene_node_at as the authority on what is visually
* topmost at (px,py). The old geometry-scan loop over
* toplevel_list caused toggling focus on overlapping windows
* because toplevel_list order diverged from scene Z-order. */
if (!wl_state.scene) return NULL;
double _sx, _sy;
struct wlr_scene_node *node = wlr_scene_node_at(
&wl_state.scene->tree.node, (double)px, (double)py, &_sx, &_sy);
if (!node) return NULL;
/* Walk up until parent is a level_tree bucket: that node is
* fb->tree (framed) or v->scene_node (unframed). */
struct wlr_scene_node *n = node;
while (n->parent && &n->parent->node != &wl_state.scene->tree.node) {
int _bucket = 0, _li;
for (_li = 0; _li < WL_NUM_LEVEL_BUCKETS; _li++) {
if (wl_state.level_trees[_li] && n->parent == wl_state.level_trees[_li])
{ _bucket = 1; break; }
}
if (_bucket) break;
n = &n->parent->node;
}
struct wl_toplevel_view *v;
wl_list_for_each(v, &wl_state.toplevel_list, link) {
/* Framed window: resolve via the fb->tree node. */
int match = 0;
if (v->frame_id) {
struct wl_frame_buf *_fb = frame_buf_find(v->frame_id);
match = (_fb && _fb->tree && &_fb->tree->node == n);
} else {
match = (v->scene_node && &v->scene_node->node == n);
}
if (!match) continue;
/* If the window is shaded, the client surface is hidden;
* do not route pointer events to it. The pointer is over
* frame decorations (titlebar) regardless of what the scene
* graph or xdg geometry says. */
{
WObjDescriptor *_desc = wm_backend->context_find(v->id, WM_CTX_CLIENT_WIN);
if (_desc && _desc->parent_type == WCLASS_WINDOW) {
WWindow *_wwin = (WWindow *)_desc->parent;
if (_wwin->flags.shaded)
continue;
}
}
if (v->xw_surface && v->xw_surface->surface) {
/* v->x/v->y is the absolute client content position
* (frame origin + frame_rel offset). Check if pointer
* is within the client area. */
double sx = px - (double)v->x;
double sy = py - (double)v->y;
if (v->frame_id) {
if (sx < 0 || sy < 0 ||
sx >= v->xw_surface->width ||
sy >= v->xw_surface->height)
continue;
}
if (sx_out) *sx_out = sx;
if (sy_out) *sy_out = sy;
if (view_out) *view_out = v;
return v->xw_surface->surface;
}
if (v->xdg_surface) {
struct wlr_box geo = v->xdg_surface->geometry;
double sx = px - (double)v->x + geo.x;
double sy = py - (double)v->y + geo.y;
double sub_sx, sub_sy;
struct wlr_surface *sub = wlr_xdg_surface_surface_at(
v->xdg_surface, sx, sy, &sub_sx, &sub_sy);
if (sub) {
if (sx_out) *sx_out = sub_sx;
if (sy_out) *sy_out = sub_sy;
if (view_out) *view_out = v;
return sub;
}
}
}
return NULL;
}
/*
* Update the seat's pointer focus to the surface at the current virtual
* pointer position. Called after every pointer position change so clients
* receive wl_pointer.enter / wl_pointer.leave events correctly.
*/
void
wl_update_pointer_focus(uint32_t time_msec)
{
if (!wl_state.seat)
return;
/* Preserve implicit grab: don't change focus while buttons are held.
* Still deliver motion to the focused surface for drag/selection. */
if (wl_state.seat->pointer_state.button_count > 0) {
struct wlr_surface *focused = wl_state.seat->pointer_state.focused_surface;
if (focused) {
double sx, sy;
struct wl_toplevel_view *v = NULL;
view_surface_at(wl_state.pointer_x, wl_state.pointer_y, &sx, &sy, &v);
wlr_seat_pointer_notify_motion(wl_state.seat, time_msec, sx, sy);
wlr_seat_pointer_notify_frame(wl_state.seat);
}
return;
}
double sx, sy;
struct wl_toplevel_view *view = NULL;
struct wlr_surface *surface = NULL;
/* Check if pointer is over a layer surface (overlays, screenshot tools). */
{
double lsx, lsy;
struct wlr_scene_node *node = wlr_scene_node_at(
&wl_state.scene->tree.node,
(double)wl_state.pointer_x, (double)wl_state.pointer_y,
&lsx, &lsy);
if (node && node->type == WLR_SCENE_NODE_BUFFER) {
struct wlr_scene_buffer *sbuf = wlr_scene_buffer_from_node(node);
struct wlr_scene_surface *ssurf = wlr_scene_surface_try_from_buffer(sbuf);
if (ssurf) {
struct wl_layer_view *lv;
wl_list_for_each(lv, &wl_state.layer_list, link) {
if (lv->layer_surface && lv->layer_surface->surface == ssurf->surface) {
surface = ssurf->surface;
sx = lsx;
sy = lsy;
goto deliver;
}
}
}
}
}
/* If pointer is over an internal frame_buf (menu, titlebar, etc.),
* don't send pointer events to any client surface — UNLESS the
* pointer is actually over the client content area within the frame. */
struct wl_frame_buf *fb = frame_buf_at(wl_state.pointer_x, wl_state.pointer_y);
if (fb) {
/* Check if this frame_buf has a client surface (toplevel view)
* and the pointer is over the client area, not the decorations. */
struct wl_toplevel_view *v = NULL;
struct wlr_surface *cs = view_surface_at(
wl_state.pointer_x, wl_state.pointer_y, &sx, &sy, &v);
if (cs) {
surface = cs;
view = v;
fb = NULL; /* not over decorations */
}
}
if (!fb && !surface) {
surface = view_surface_at(
wl_state.pointer_x, wl_state.pointer_y, &sx, &sy, &view);
}
deliver:
static struct wlr_surface *last_focused_surface = NULL;
if (surface) {
if (surface != last_focused_surface) {
last_focused_surface = surface;
}
wlr_seat_pointer_notify_enter(wl_state.seat, surface, sx, sy);
wlr_seat_pointer_notify_motion(wl_state.seat, time_msec, sx, sy);
wlr_seat_pointer_notify_frame(wl_state.seat);
} else {
if (last_focused_surface) {
last_focused_surface = NULL;
}
wlr_seat_pointer_clear_focus(wl_state.seat);
/* Reset to arrow only when not over a WINGs/WM frame_buf and not grabbing.
* WINGs views are compositor nodes (not Wayland surfaces) so surface==NULL. */
if (!wl_state.grab_type
&& !frame_buf_hit(wl_state.pointer_x, wl_state.pointer_y))
wl_set_cursor_image("left_ptr");
}
/* focus-follows-pointer — when the pointer enters a new
* managed view, deliver WM keyboard focus without raising.
* Only fires when wPreferences.focus_mode == WKF_SLOPPY. */
{
static struct wl_toplevel_view *last_ffp_view = NULL;
if (view != last_ffp_view) {
last_ffp_view = 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;
if (wPreferences.focus_mode == WKF_SLOPPY) {
wSetFocusTo(wScreenWithNumber(0), wwin);
}
}
}
}
}
}
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
wl_pointer_warp(int dx, int dy)
{
if (wl_state.cursor) {
wlr_cursor_move(wl_state.cursor, NULL, (double)dx, (double)dy);
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 += dx;
wl_state.pointer_y += dy;
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;
}
wl_update_pointer_focus(0);
}
void
wl_pointer_warp_to(int x, int y)
{
if (wl_state.cursor) {
wlr_cursor_warp(wl_state.cursor, NULL, (double)x, (double)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);
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x >= ow) x = ow - 1;
if (y >= oh) y = oh - 1;
wl_state.pointer_x = x;
wl_state.pointer_y = y;
}
wl_update_pointer_focus(0);
}
int
wl_pointer_query(WNativeWindow root, int *root_x, int *root_y)
{
(void)root;
*root_x = wl_state.pointer_x;
*root_y = wl_state.pointer_y;
return wl_state.started ? 1 : 0;
}
int
wl_pointer_query_full(WNativeWindow root,
WNativeWindow *child_out,
int *root_x, int *root_y,
unsigned int *buttons_out)
{
(void)root;
if (child_out) *child_out = WNATIVE_NONE;
if (root_x) *root_x = wl_state.pointer_x;
if (root_y) *root_y = wl_state.pointer_y;
if (buttons_out) *buttons_out = 0;
return wl_state.started ? 1 : 0;
}
void
wl_pointer_change_grab(unsigned int event_mask, int cursor_id,
unsigned long timestamp)
{
(void)event_mask; (void)timestamp;
/* If no grab is active, start a WM policy grab (type 3) so that
* wl_update_pointer_focus doesn't reset the cursor on every motion
* event when the pointer is over frame decorations.
* This handles the opaque-move path where moveres.c calls
* pointer_change_grab without first calling pointer_grab. */
if (!wl_state.grab_type) {
wl_state.grab_type = 3;
wl_state.grab_view = NULL;
if (wl_state.seat)
wlr_seat_pointer_clear_focus(wl_state.seat);
wwarning("wl_backend: grab established via pointer_change_grab");
}
/* Map WCUR_* cursor IDs to xcursor names */
const char *name;
switch (cursor_id) {
case WCUR_MOVE: name = "fleur"; break;
case WCUR_WAIT: name = "watch"; break;
case WCUR_TOPLEFTRESIZE: name = "nw-resize"; break;
case WCUR_TOPRIGHTRESIZE: name = "ne-resize"; break;
case WCUR_BOTTOMLEFTRESIZE: name = "sw-resize"; break;
case WCUR_BOTTOMRIGHTRESIZE: name = "se-resize"; break;
case WCUR_VERTICALRESIZE: name = "n-resize"; break;
case WCUR_HORIZONRESIZE: name = "e-resize"; break;
default: name = "left_ptr"; break;
}
wl_set_cursor_image(name);
}
void wl_pointer_change_cursor(int cursor_id)
{
const char *name;
switch (cursor_id) {
case 1: /* WCUR_MOVE */ name = "fleur"; break;
case 2: /* WCUR_RESIZE */ name = "bottom_right_corner"; break;
case 7: /* WCUR_WAIT */ name = "watch"; break;
case 8: /* WCUR_ARROW */ name = "left_ptr"; break;
case 9: /* WCUR_QUESTION */name = "question_arrow"; break;
case 10:/* WCUR_TEXT */ name = "xterm"; break;
default: name = "left_ptr"; break;
}
wl_set_cursor_image(name);
}
void wl_pointer_get_position(int *x, int *y) {
*x = (int)wl_state.pointer_x;
*y = (int)wl_state.pointer_y;
}