diff --git a/src/Makefile.am b/src/Makefile.am index 1f9edc1..1473655 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -185,7 +185,8 @@ wmaker_SOURCES += \ backend/wayland/wl_grabs.c \ backend/wayland/wl_grabstate.c \ backend/wayland/wl_dragicon.c \ - backend/wayland/wl_pointer_motion.c + backend/wayland/wl_pointer_motion.c \ + backend/wayland/wl_pointer_focus.c endif if USE_NESTED_FUNC diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index d45d95b..b9e193d 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -298,7 +298,7 @@ void wmaker_popup_apply(struct wl_toplevel_view *view); /* Phase 14: pointer event handler forward declarations. */ extern void handle_pointer_motion(struct wl_listener *listener, void *data); /* Phase 24: click-to-focus helper — defined in Phase 13 pointer section. */ -static struct wlr_surface *view_surface_at(int px, int py, double *sx_out, +extern struct wlr_surface *view_surface_at(int px, int py, double *sx_out, double *sy_out, struct wl_toplevel_view **view_out); /* Phase 24: client cursor-surface request handler. */ extern void handle_seat_request_cursor(struct wl_listener *listener, void *data); @@ -4207,251 +4207,6 @@ handle_pointer_frame(struct wl_listener *listener, void *data) } -/* ------------------------------------------------------------------ */ -/* Phase 13: pointer focus delivery to Wayland surfaces */ -/* ------------------------------------------------------------------ */ - -/* - * 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). - */ -static 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 (v->xw_surface && v->xw_surface->surface) { - double sx = px - (double)v->x; - double sy = py - (double)v->y; - 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) { - double sx = px - (double)v->x; - double sy = py - (double)v->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; - - double sx, sy; - struct wl_toplevel_view *view = NULL; - struct wlr_surface *surface = NULL; - - /* 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); - } - - 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); - /* When pointer leaves all surfaces, reset to default arrow. - * Don't reset during grabs — the grab system manages the cursor. */ - if (!wl_state.grab_type) - wl_set_cursor_image("left_ptr"); - } - /* Phase 29 (FFP): 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); - } - } - } - } - } -} - - void -wl_pointer_warp(int dx, int dy) -{ - /* Phase 54: relative warp — clamp to output bounds. */ - int ow = wl_state.output ? (int)wl_state.output->width : 1920; - int oh = wl_state.output ? (int)wl_state.output->height : 1080; - 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; - /* Phase 23: update hardware cursor position. */ - if (wl_state.cursor) - wlr_cursor_warp(wl_state.cursor, NULL, - (double)wl_state.pointer_x, (double)wl_state.pointer_y); - /* Phase 13: update pointer focus after relative warp */ - wl_update_pointer_focus(0); -} - -void -wl_pointer_warp_to(int x, int y) -{ - /* Phase 54: absolute warp — clamp to output bounds. */ - int ow = wl_state.output ? (int)wl_state.output->width : 1920; - int oh = wl_state.output ? (int)wl_state.output->height : 1080; - 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; - /* Phase 23: update hardware cursor position. */ - if (wl_state.cursor) - wlr_cursor_warp(wl_state.cursor, NULL, (double)x, (double)y); - /* Phase 13: update pointer focus after absolute warp */ - wl_update_pointer_focus(0); -} - - int -wl_pointer_query(WNativeWindow root, int *root_x, int *root_y) -{ - /* Phase 54: return tracked virtual pointer position. */ - (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) -{ - /* Phase 54: return tracked virtual pointer position. */ - (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); -} /* ------------------------------------------------------------------ */ /* Keyboard grabs */ diff --git a/src/backend/wayland/wl_pointer_focus.c b/src/backend/wayland/wl_pointer_focus.c new file mode 100644 index 0000000..53a2906 --- /dev/null +++ b/src/backend/wayland/wl_pointer_focus.c @@ -0,0 +1,265 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include "../../actions.h" + +#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); + +/* ------------------------------------------------------------------ */ +/* Phase 13: pointer focus delivery to Wayland surfaces */ +/* ------------------------------------------------------------------ */ + +/* + * 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 (v->xw_surface && v->xw_surface->surface) { + double sx = px - (double)v->x; + double sy = py - (double)v->y; + 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) { + double sx = px - (double)v->x; + double sy = py - (double)v->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; + + double sx, sy; + struct wl_toplevel_view *view = NULL; + struct wlr_surface *surface = NULL; + + /* 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); + } + + 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); + /* When pointer leaves all surfaces, reset to default arrow. + * Don't reset during grabs — the grab system manages the cursor. */ + if (!wl_state.grab_type) + wl_set_cursor_image("left_ptr"); + } + /* Phase 29 (FFP): 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); + } + } + } + } + } +} + + void +wl_pointer_warp(int dx, int dy) +{ + /* Phase 54: relative warp — clamp to output bounds. */ + int ow = wl_state.output ? (int)wl_state.output->width : 1920; + int oh = wl_state.output ? (int)wl_state.output->height : 1080; + 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; + /* Phase 23: update hardware cursor position. */ + if (wl_state.cursor) + wlr_cursor_warp(wl_state.cursor, NULL, + (double)wl_state.pointer_x, (double)wl_state.pointer_y); + /* Phase 13: update pointer focus after relative warp */ + wl_update_pointer_focus(0); +} + +void +wl_pointer_warp_to(int x, int y) +{ + /* Phase 54: absolute warp — clamp to output bounds. */ + int ow = wl_state.output ? (int)wl_state.output->width : 1920; + int oh = wl_state.output ? (int)wl_state.output->height : 1080; + 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; + /* Phase 23: update hardware cursor position. */ + if (wl_state.cursor) + wlr_cursor_warp(wl_state.cursor, NULL, (double)x, (double)y); + /* Phase 13: update pointer focus after absolute warp */ + wl_update_pointer_focus(0); +} + + int +wl_pointer_query(WNativeWindow root, int *root_x, int *root_y) +{ + /* Phase 54: return tracked virtual pointer position. */ + (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) +{ + /* Phase 54: return tracked virtual pointer position. */ + (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); +}