362 lines
13 KiB
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;
|
|
}
|
|
|