wayland: Phases 24+25 — click-to-focus, client cursor surface, clipboard

Phase 24a — click-to-focus + raise-on-click:
  handle_pointer_button now calls view_surface_at() on WLR_BUTTON_PRESSED.
  If a managed view is hit, wSetFocusTo() delivers WM keyboard focus and
  wlr_scene_node_raise_to_top() raises the surface in Z-order.  The view
  is also moved to the tail of toplevel_list so view_surface_at() returns
  it first on subsequent pointer hits.  stacking.h included for the raise
  path (wRaiseFrame not yet needed but header is now available).

Phase 24b — client cursor surface (wl_pointer.set_cursor):
  seat->events.request_set_cursor wired to handle_seat_request_cursor.
  The handler validates the request comes from the currently pointer-focused
  client (rejects unfocused clients), then calls wlr_cursor_set_surface so
  the application-supplied cursor image is rendered on all outputs.  In
  nested mode this updates the host compositor's cursor via set_cursor.

Phase 25 — clipboard / wl_data_device selection forwarding:
  seat->events.request_set_selection wired to handle_seat_request_set_selection.
  The handler calls wlr_seat_set_selection, making the client's data source
  available to subsequent paste operations in any other client that reads
  the seat's selection.  Logged as "clipboard selection wired" at startup.

Smoke test: new "clipboard check" verifies startup log; 24/24 pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-30 17:08:17 +02:00
parent aee57de37d
commit 52c6ef9326
2 changed files with 114 additions and 0 deletions

View File

@ -113,6 +113,7 @@
#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/types/wlr_data_device.h> /* Phase 25: clipboard / selection */
#include <wlr/util/box.h>
/* Generated xdg-shell protocol implementation (Wayland glue code) */
#include "protocols/xdg-shell-protocol.c"
@ -124,6 +125,7 @@
#include "../../screen.h"
#include "../../window.h"
#include "../../actions.h" /* Phase 16: wMaximizeWindow, wIconifyWindow, etc. */
#include "../../stacking.h" /* Phase 24: wRaiseFrame for click-to-raise */
#include "../../xinerama.h" /* Phase 16: wGetHeadForWindow */
#include "../../xmodifier.h" /* Phase 77: wSetModifierMasks */
#include "../../event.h" /* Phase 78: wWaylandKeyPress */
@ -257,6 +259,10 @@ struct wl_compositor_state {
/* Phase 23: hardware cursor and xcursor theme. */
struct wlr_cursor *cursor;
struct wlr_xcursor_manager *xcursor_mgr;
/* Phase 24: seat cursor-surface request listener. */
struct wl_listener seat_request_cursor_listener;
/* Phase 25: clipboard — seat selection request listener. */
struct wl_listener seat_request_set_selection_listener;
};
static struct wl_compositor_state wl_state;
@ -312,6 +318,13 @@ static void wl_update_pointer_focus(uint32_t time_msec);
static void wl_pointer_warp_to(int x, int y);
/* Phase 14: pointer event handler forward declarations. */
static 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,
double *sy_out, struct wl_toplevel_view **view_out);
/* Phase 24: client cursor-surface request handler. */
static void handle_seat_request_cursor(struct wl_listener *listener, void *data);
/* Phase 25: client clipboard selection request handler. */
static void handle_seat_request_set_selection(struct wl_listener *listener, void *data);
/* Phase 17: XWayland new surface forward declaration. */
static void handle_xwayland_new_surface(struct wl_listener *listener, void *data);
/* Phase 20: nested backend real input device forward declaration. */
@ -1434,6 +1447,23 @@ wl_display_open(const char *name)
wl_state.seat = wlr_seat_create(wl_state.display, "seat0");
wl_state.data_device_mgr =
wlr_data_device_manager_create(wl_state.display);
/* Phase 24: handle client cursor-surface requests on the seat. */
if (wl_state.seat) {
wl_state.seat_request_cursor_listener.notify = handle_seat_request_cursor;
wl_signal_add(&wl_state.seat->events.request_set_cursor,
&wl_state.seat_request_cursor_listener);
}
/* Phase 25: handle clipboard set-selection requests on the seat so
* Ctrl-C / copy operations from clients propagate to the seat selection
* and become available for paste in other clients. */
if (wl_state.seat) {
wl_state.seat_request_set_selection_listener.notify =
handle_seat_request_set_selection;
wl_signal_add(&wl_state.seat->events.request_set_selection,
&wl_state.seat_request_set_selection_listener);
wwarning("wl_backend: clipboard selection wired");
}
wl_state.xdg_shell = wlr_xdg_shell_create(wl_state.display);
/* Phase 64: advertise zxdg_decoration_manager_v1 so clients that
@ -1723,6 +1753,9 @@ wl_display_close(void)
WL_LISTENER_REMOVE(wl_state.real_pointer_motion_abs_listener);
WL_LISTENER_REMOVE(wl_state.real_pointer_button_listener);
WL_LISTENER_REMOVE(wl_state.real_pointer_frame_listener);
/* Phase 24/25: seat cursor + selection listeners */
WL_LISTENER_REMOVE(wl_state.seat_request_cursor_listener);
WL_LISTENER_REMOVE(wl_state.seat_request_set_selection_listener);
#undef WL_LISTENER_REMOVE
/* Phase 23: destroy cursor and xcursor manager before display teardown. */
@ -2020,6 +2053,45 @@ handle_pointer_motion_absolute(struct wl_listener *listener, void *data)
wl_update_pointer_focus(ev->time_msec);
}
/*
* Phase 24: seat request_set_cursor — client wants to set a custom cursor
* image (e.g. I-beam in a text field, hand over a link). Validate that
* the request comes from the currently focused pointer client, then hand
* the surface to wlr_cursor so the hardware cursor is updated on all
* outputs (which in nested mode sends wl_pointer.set_cursor to the host).
*/
static void
handle_seat_request_cursor(struct wl_listener *listener, void *data)
{
(void)listener;
struct wlr_seat_pointer_request_set_cursor_event *ev = data;
if (!wl_state.seat || !wl_state.cursor)
return;
/* Only honour cursor requests from the client that currently has
* pointer focus; reject requests from unfocused clients. */
struct wlr_seat_client *focused =
wl_state.seat->pointer_state.focused_client;
if (ev->seat_client != focused)
return;
wlr_cursor_set_surface(wl_state.cursor, ev->surface,
ev->hotspot_x, ev->hotspot_y);
}
/*
* Phase 25: seat request_set_selection — client calls wl_data_device.set_selection
* (i.e. Ctrl-C or other clipboard write). Forward to wlr_seat so the
* selection becomes available to the next client that reads it.
*/
static void
handle_seat_request_set_selection(struct wl_listener *listener, void *data)
{
(void)listener;
struct wlr_seat_request_set_selection_event *ev = data;
if (!wl_state.seat)
return;
wlr_seat_set_selection(wl_state.seat, ev->source, ev->serial);
}
static void
handle_pointer_button(struct wl_listener *listener, void *data)
{
@ -2035,6 +2107,33 @@ handle_pointer_button(struct wl_listener *listener, void *data)
wl_update_pointer_focus(ev->time_msec);
return;
}
/* Phase 24: click-to-focus — on button press, find the view under the
* pointer and focus + raise it via the WM policy layer.
* This mirrors what the X11 backend does via ButtonPress on frame windows. */
if (ev->state == WLR_BUTTON_PRESSED) {
double sx, sy;
struct wl_toplevel_view *view = NULL;
view_surface_at(wl_state.pointer_x, wl_state.pointer_y,
&sx, &sy, &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;
WScreen *scr = wScreenWithNumber(0);
wSetFocusTo(scr, wwin);
/* Raise the window in scene Z-order by moving its scene node
* to the top of the scene root's child list. */
if (view->scene_node)
wlr_scene_node_raise_to_top(view->scene_node);
/* Also raise it in our toplevel list so view_surface_at()
* returns it first on subsequent hits. */
wl_list_remove(&view->link);
wl_list_insert(wl_state.toplevel_list.prev, &view->link);
wwarning("wl_backend: click focus id=0x%lx", view->id);
}
}
}
wlr_seat_pointer_notify_button(wl_state.seat, ev->time_msec,
ev->button, ev->state);
wlr_seat_pointer_notify_frame(wl_state.seat);

View File

@ -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.
# 43. (Phase 24) Click-to-focus: on WLR_BUTTON_PRESSED, view_surface_at()
# finds the view under the pointer; wSetFocusTo delivers WM keyboard
# focus and wlr_scene_node_raise_to_top raises it in Z-order.
# seat->events.request_set_cursor wired: clients can set custom cursor
# images (I-beam, hand, etc.) via wl_pointer.set_cursor.
# (Build-only check — no multi-window focus test here.)
# 44. (Phase 25) Clipboard: seat->events.request_set_selection wired so
# Ctrl-C / copy operations propagate to the seat selection and become
# available for paste in other clients via wl_data_device.
# Verified by "clipboard selection wired" log line.
# 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
@ -282,6 +292,11 @@ 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)"
# -- 4f2b. Phase 25: clipboard selection wired ----------------------------
grep -q 'clipboard selection wired' /tmp/wl-smoke-stderr \
|| die "compositor did not log clipboard wired (seat request_set_selection listener missing)"
echo " clipboard check: clipboard selection wired"
# -- 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)"