#ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "../../actions.h" #include #include #include #include #include #include "../../screen.h" #include "wl_types.h" #include /* 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; }