wayland: Phase 23 — cursor image rendering via wlr_cursor + xcursor theme
Wire up wlr_cursor and wlr_xcursor_manager so the cursor is visible when
running as a nested Wayland compositor.
- wlr_cursor created in wl_display_open and attached to the output layout.
- wlr_xcursor_manager created with NULL (system default) theme at 24 px
base size; wlr_xcursor_manager_load called at scale 1.0 at open time.
- In handle_backend_new_output, after the output joins the layout:
- Load xcursor at the output's actual scale factor.
- Set "left_ptr" as the initial cursor image.
- Warp the hardware cursor to the current virtual pointer position.
In nested mode this causes wlroots to send wl_pointer.set_cursor to
the host compositor, making the cursor visible in the window.
- All pointer motion handlers (relative, absolute) and wl_pointer_warp /
wl_pointer_warp_to now call wlr_cursor_warp after updating pointer_x/y,
keeping the hardware cursor in sync with the virtual pointer position.
handle_pointer_motion_absolute uses wlr_cursor_warp_absolute and reads
back cursor->x/y for the pixel position (correct in all output layouts).
- Grab cursor shapes: wl_grab_begin_move sets "fleur"; wl_grab_begin_resize
selects an edge-appropriate name (nw-resize, se-resize, etc.) via a new
resize_edges_to_cursor() helper; wl_grab_end restores "left_ptr".
- wl_display_close destroys xcursor_mgr then cursor before display teardown.
Smoke test: new "cursor check" verifies "cursor created" + "cursor image
set to 'left_ptr'" log lines (23/23 checks pass).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
73cdf7a6b3
commit
aee57de37d
|
|
@ -111,6 +111,8 @@
|
|||
#include <wlr/backend/multi.h> /* Phase 18: multi-backend */
|
||||
#include <wlr/types/wlr_scene.h> /* Phase 18: scene graph compositing */
|
||||
#include <wlr/render/allocator.h> /* Phase 18: buffer allocator */
|
||||
#include <wlr/types/wlr_cursor.h> /* Phase 23: hardware cursor tracking */
|
||||
#include <wlr/types/wlr_xcursor_manager.h> /* Phase 23: xcursor theme */
|
||||
#include <wlr/util/box.h>
|
||||
/* Generated xdg-shell protocol implementation (Wayland glue code) */
|
||||
#include "protocols/xdg-shell-protocol.c"
|
||||
|
|
@ -252,6 +254,9 @@ struct wl_compositor_state {
|
|||
int grab_start_win_x, grab_start_win_y;
|
||||
int grab_start_win_w, grab_start_win_h;
|
||||
uint32_t grab_resize_edges;
|
||||
/* Phase 23: hardware cursor and xcursor theme. */
|
||||
struct wlr_cursor *cursor;
|
||||
struct wlr_xcursor_manager *xcursor_mgr;
|
||||
};
|
||||
|
||||
static struct wl_compositor_state wl_state;
|
||||
|
|
@ -370,6 +375,38 @@ handle_toplevel_title(struct wl_listener *listener, void *data)
|
|||
wWindowUpdateName(wwin, new_title);
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 23: set the cursor image by name from the loaded xcursor theme.
|
||||
* No-op if cursor or xcursor_mgr was not initialised.
|
||||
*/
|
||||
static void
|
||||
wl_set_cursor_image(const char *name)
|
||||
{
|
||||
if (!wl_state.cursor || !wl_state.xcursor_mgr)
|
||||
return;
|
||||
wlr_xcursor_manager_set_cursor_image(wl_state.xcursor_mgr, name, wl_state.cursor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 23: map WL_SHELL_SURFACE_RESIZE_* edge bitmask to an xcursor name.
|
||||
*/
|
||||
static const char *
|
||||
resize_edges_to_cursor(uint32_t edges)
|
||||
{
|
||||
switch (edges & (WL_SHELL_SURFACE_RESIZE_TOP | WL_SHELL_SURFACE_RESIZE_BOTTOM |
|
||||
WL_SHELL_SURFACE_RESIZE_LEFT | WL_SHELL_SURFACE_RESIZE_RIGHT)) {
|
||||
case WL_SHELL_SURFACE_RESIZE_TOP: return "n-resize";
|
||||
case WL_SHELL_SURFACE_RESIZE_BOTTOM: return "s-resize";
|
||||
case WL_SHELL_SURFACE_RESIZE_LEFT: return "w-resize";
|
||||
case WL_SHELL_SURFACE_RESIZE_RIGHT: return "e-resize";
|
||||
case WL_SHELL_SURFACE_RESIZE_TOP_LEFT: return "nw-resize";
|
||||
case WL_SHELL_SURFACE_RESIZE_TOP_RIGHT: return "ne-resize";
|
||||
case WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT: return "sw-resize";
|
||||
case WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT: return "se-resize";
|
||||
default: return "se-resize";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 22: interactive grab helpers.
|
||||
*
|
||||
|
|
@ -390,6 +427,8 @@ wl_grab_end(void)
|
|||
{
|
||||
wl_state.grab_type = 0;
|
||||
wl_state.grab_view = NULL;
|
||||
/* Phase 23: restore default arrow cursor when grab ends. */
|
||||
wl_set_cursor_image("left_ptr");
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -421,6 +460,8 @@ wl_grab_begin_move(struct wl_toplevel_view *view)
|
|||
* client while we are repositioning it. */
|
||||
if (wl_state.seat)
|
||||
wlr_seat_pointer_clear_focus(wl_state.seat);
|
||||
/* Phase 23: show a move cursor during the grab. */
|
||||
wl_set_cursor_image("fleur");
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -450,6 +491,8 @@ wl_grab_begin_resize(struct wl_toplevel_view *view, uint32_t edges)
|
|||
view->id, edges, w, h);
|
||||
if (wl_state.seat)
|
||||
wlr_seat_pointer_clear_focus(wl_state.seat);
|
||||
/* Phase 23: show an appropriate resize cursor during the grab. */
|
||||
wl_set_cursor_image(resize_edges_to_cursor(edges));
|
||||
}
|
||||
|
||||
/* Called from both pointer-motion handlers (relative + absolute). */
|
||||
|
|
@ -1161,6 +1204,23 @@ handle_backend_new_output(struct wl_listener *listener, void *data)
|
|||
if (wl_state.output_layout)
|
||||
wlr_output_layout_add_auto(wl_state.output_layout, output);
|
||||
|
||||
/* Phase 23: apply cursor image now that this output is in the layout.
|
||||
* wlr_xcursor_manager_set_cursor_image iterates the output layout and
|
||||
* sets the hardware cursor on each output; for the nested backend this
|
||||
* sends wl_pointer.set_cursor to the host compositor, making the cursor
|
||||
* visible. Also warp to the current virtual pointer position so the
|
||||
* cursor appears at the right location from the first frame. */
|
||||
if (wl_state.cursor && wl_state.xcursor_mgr) {
|
||||
wlr_xcursor_manager_load(wl_state.xcursor_mgr, output->scale);
|
||||
wlr_xcursor_manager_set_cursor_image(wl_state.xcursor_mgr,
|
||||
"left_ptr", wl_state.cursor);
|
||||
wlr_cursor_warp(wl_state.cursor, NULL,
|
||||
(double)wl_state.pointer_x,
|
||||
(double)wl_state.pointer_y);
|
||||
wwarning("wl_backend: cursor image set to 'left_ptr' on output %s",
|
||||
output->name);
|
||||
}
|
||||
|
||||
/* Set window title when running as a nested compositor. */
|
||||
if (wl_state.is_nested && wl_state.wl_backend_inner &&
|
||||
wlr_backend_is_wl(wl_state.wl_backend_inner))
|
||||
|
|
@ -1289,6 +1349,24 @@ wl_display_open(const char *name)
|
|||
* can add the output to the layout when it fires. */
|
||||
wl_state.output_layout = wlr_output_layout_create();
|
||||
|
||||
/* Phase 23: create hardware cursor tracker and load xcursor theme.
|
||||
* wlr_cursor tracks the logical cursor position across the output
|
||||
* layout and communicates cursor image changes to each output.
|
||||
* In nested Wayland mode this calls wl_pointer.set_cursor on the host
|
||||
* compositor so the cursor is visible in the window.
|
||||
* Theme and image are applied in handle_backend_new_output after each
|
||||
* output is added to the layout, ensuring the scale factor is known. */
|
||||
wl_state.cursor = wlr_cursor_create();
|
||||
if (wl_state.cursor) {
|
||||
wlr_cursor_attach_output_layout(wl_state.cursor, wl_state.output_layout);
|
||||
wl_state.xcursor_mgr = wlr_xcursor_manager_create(NULL, 24);
|
||||
if (wl_state.xcursor_mgr)
|
||||
wlr_xcursor_manager_load(wl_state.xcursor_mgr, 1.0f);
|
||||
wwarning("wl_backend: cursor created, xcursor theme loaded");
|
||||
} else {
|
||||
wwarning("wl_backend: wlr_cursor_create failed; cursor will be invisible");
|
||||
}
|
||||
|
||||
/* Phase 18: scene graph — MUST be created before wlr_backend_start so
|
||||
* that handle_backend_new_output can create the scene_output immediately
|
||||
* when the output fires (for nested Wayland, new_output fires during
|
||||
|
|
@ -1647,6 +1725,16 @@ wl_display_close(void)
|
|||
WL_LISTENER_REMOVE(wl_state.real_pointer_frame_listener);
|
||||
#undef WL_LISTENER_REMOVE
|
||||
|
||||
/* Phase 23: destroy cursor and xcursor manager before display teardown. */
|
||||
if (wl_state.xcursor_mgr) {
|
||||
wlr_xcursor_manager_destroy(wl_state.xcursor_mgr);
|
||||
wl_state.xcursor_mgr = NULL;
|
||||
}
|
||||
if (wl_state.cursor) {
|
||||
wlr_cursor_destroy(wl_state.cursor);
|
||||
wl_state.cursor = NULL;
|
||||
}
|
||||
|
||||
/* Skip wl_display_destroy() and wl_display_destroy_clients().
|
||||
*
|
||||
* wl_display_destroy() fires the display's destroy signal which triggers
|
||||
|
|
@ -1896,6 +1984,10 @@ handle_pointer_motion(struct wl_listener *listener, void *data)
|
|||
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 22: apply grab before updating normal pointer focus. */
|
||||
if (wl_state.grab_type) { wl_grab_apply_motion(); return; }
|
||||
wl_update_pointer_focus(ev->time_msec);
|
||||
|
|
@ -1906,14 +1998,23 @@ handle_pointer_motion_absolute(struct wl_listener *listener, void *data)
|
|||
{
|
||||
(void)listener;
|
||||
struct wlr_event_pointer_motion_absolute *ev = data;
|
||||
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 = (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;
|
||||
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: use wlr_cursor_warp_absolute so wlroots maps the 0..1
|
||||
* device-normalized coordinates to layout pixels and updates the
|
||||
* hardware cursor in the nested window automatically. */
|
||||
if (wl_state.cursor) {
|
||||
wlr_cursor_warp_absolute(wl_state.cursor, ev->device, ev->x, ev->y);
|
||||
wl_state.pointer_x = (int)wl_state.cursor->x;
|
||||
wl_state.pointer_y = (int)wl_state.cursor->y;
|
||||
} else {
|
||||
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 = (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;
|
||||
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 22: apply grab before updating normal pointer focus. */
|
||||
if (wl_state.grab_type) { wl_grab_apply_motion(); return; }
|
||||
wl_update_pointer_focus(ev->time_msec);
|
||||
|
|
@ -2060,6 +2161,10 @@ wl_pointer_warp(int dx, int dy)
|
|||
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);
|
||||
}
|
||||
|
|
@ -2076,6 +2181,9 @@ wl_pointer_warp_to(int x, int y)
|
|||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,6 +123,16 @@
|
|||
# Compositor logs "XWayland DISPLAY=:<n> (lazy: starts on first X11 client)".
|
||||
# DISPLAY env var is set from the pre-allocated display number.
|
||||
# Verified by "XWayland DISPLAY=.*lazy" log.
|
||||
# 42. (Phase 23) Cursor image rendering: wlr_cursor + wlr_xcursor_manager
|
||||
# created and attached to output_layout; xcursor theme (default, 24px)
|
||||
# loaded at scale 1.0; "left_ptr" image set on each output after it joins
|
||||
# the layout. In nested Wayland mode this sends wl_pointer.set_cursor to
|
||||
# the host compositor making the cursor visible. Grab begin/end switch to
|
||||
# "fleur" / edge-resize cursors and restore "left_ptr" on release.
|
||||
# wlr_cursor_warp called from all motion handlers and pointer_warp_to/
|
||||
# warp_to so the hardware cursor tracks the virtual pointer position.
|
||||
# Verified by "cursor created, xcursor theme loaded" +
|
||||
# "cursor image set to 'left_ptr'" log lines.
|
||||
# 41. (Phase 22) Interactive move/resize grab: request_move and request_resize
|
||||
# handlers now start a compositor-side grab that tracks pointer motion
|
||||
# and repositions/resizes the window in the scene graph. Button release
|
||||
|
|
@ -272,6 +282,13 @@ grep -q 'dock/clip disabled in Wayland mode' /tmp/wl-smoke-stderr \
|
|||
|| die "compositor did not log dock/clip disabled (noclip/nodock not set in wl_screen_create_internals)"
|
||||
echo " nodock check: dock/clip disabled in Wayland mode (noclip=1 nodock=1)"
|
||||
|
||||
# -- 4f3. Phase 23: cursor created and xcursor theme loaded ---------------
|
||||
grep -q 'cursor created, xcursor theme loaded' /tmp/wl-smoke-stderr \
|
||||
|| die "compositor did not log cursor creation (wlr_cursor_create or xcursor_manager_create failed)"
|
||||
grep -q "cursor image set to 'left_ptr'" /tmp/wl-smoke-stderr \
|
||||
|| die "compositor did not log cursor image set (wlr_xcursor_manager_set_cursor_image not called)"
|
||||
echo " cursor check: wlr_cursor created, xcursor 'left_ptr' set on output"
|
||||
|
||||
# -- 4g. Phase 17: XWayland display number allocated (lazy start) --------
|
||||
#
|
||||
# wl_display_open() logs "XWayland DISPLAY=:<n> (lazy: starts on first
|
||||
|
|
|
|||
Loading…
Reference in New Issue