Wayland/feature parity m0 m5#80
Conversation
Wayland: lazily attach EGL/OpenGL context to window when view becomes available
Add Wayland vs X11 feature-gap report and Wayland implementation plan
Updated the integration harness script to use ambrosia Wayland compositor instead of Weston.
Milestone 0 — Baseline instrumentation: - Replace bare NSDebugLog with targeted NSDebugMLLog/NSDebugFLLog categories (WaylandDnD, WaylandIME, WaylandPointer, WaylandScroll, WaylandOutput) across WaylandDragView, WaylandInputServer, WaylandServer+Cursor, WaylandServer+Keyboard, and WaylandServer+Output. - Add default: case logging for unhandled pointer buttons (BTN_SIDE/EXTRA etc.) and stub logging for pointer_handle_frame/axis_stop/axis_discrete. - Add Tools/wayland-smoke-test.sh: integration harness that starts Ambrosia compositor nested in the current Wayland session, discovers its socket from the log, and runs 11 smoke checks covering compositor liveness, protocol globals, source code snapshots for each feature bucket, and log sanity. - Fix pre-existing WaylandCairoShmSurface.m ivar redeclaration build error. Milestone 1 — Inter-process DnD via wl_data_device: - WaylandServer.h: add wl_data_device_manager, wl_data_device, and full DnD state (dnd_source, dnd_offer, MIME list, action, position, target) to WaylandConfig. - WaylandServer.m: bind wl_data_device_manager in registry handler (v≤3), create wl_data_device and attach listener after the initial roundtrip. - WaylandDragView.m: complete implementation — Outbound (GNUstep → external): dragImage: creates wl_data_source, offers all MIME types from the pasteboard, calls wl_data_device_start_drag, then runs GSDragView's event loop; data_source_send writes pasteboard data to the pipe FD; cancelled/dnd_finished post a fake NSLeftMouseUp to exit the loop cleanly. Inbound (external → GNUstep): device_data_offer accumulates MIME types; device_enter accepts the best type, sets wl_data_offer_set_actions (v3), sets up NSDraggingInfo on the shared drag view, and posts GSAppKitDraggingEnter; device_motion posts GSAppKitDraggingUpdate; device_leave posts GSAppKitDraggingExit and destroys the offer; device_drop reads data via pipe, populates NSDragPboard, posts GSAppKitDraggingDrop, and calls wl_data_offer_finish (v3). MIME mapping: text/plain↔NSStringPboardType, text/uri-list↔NSFilenamesPboardType, application/rtf↔NSRTFPboardType, image/tiff↔NSTIFFPboardType, image/png↔NSPNGPboardType. Action mapping: wl copy/move ↔ NSDragOperationCopy/Move. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Protocol plumbing: - Generate text-input-unstable-v3 client header and protocol .c file via wayland-scanner; add the .c to wayland_C_FILES in GNUmakefile. - Include text-input-unstable-v3-client-protocol.h from WaylandServer.h. - Add zwp_text_input_manager_v3 and zwp_text_input_v3 pointers plus preedit state (pending_preedit, pending_commit, preedit_rect/spot, text_input_active, ime_serial) to WaylandConfig. - Bind zwp_text_input_manager_v3 in registry handler. - Create zwp_text_input_v3 from manager+seat after the initial roundtrip and attach the listener. Keyboard / text_input_v3 listener (WaylandServer+Keyboard.m): - Fix longstanding XKB bug: replace the broken `&sym` (keysym address cast to UTF-8) with xkb_state_key_get_utf8, which correctly handles the full dead-key composition pipeline (e.g. dead_acute + e → é). - Add zwp_text_input_v3_listener with all 9 callbacks (6 v1 + 3 v2 stubs). - keyboard_handle_enter/leave: call enable()/disable() + commit() on the text input object so the compositor IM knows when focus changes. - text_input_enter/leave: track active state; clear preedit on leave. - text_input_preedit_string / commit_string: buffer pending IM events. - text_input_done: apply buffered preedit via setMarkedText:selectedRange: and commit string via insertText: (with key-event fallback); clear marked text when preedit is NULL. - delete_surrounding_text, action, language, preedit_hint: logged stubs. WaylandInputServer (WaylandInputServer.m / .h): - Add wlconfig back-pointer; wire it from _initWaylandContext. - statusArea: return the bottom strip of the focused window. - preeditArea/preeditSpot: return the stored geometry values or fall back to the client window rect / window centre. - setPreeditSpot:/setPreeditArea:: store geometry and forward to the compositor via zwp_text_input_v3_set_cursor_rectangle + commit so the IM candidate window tracks the text cursor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Button mapping: - Handle BTN_SIDE (0x113), BTN_EXTRA (0x114), BTN_FORWARD (0x115), BTN_BACK (0x116), BTN_TASK (0x117) and any further evdev button codes as NSOtherMouseDown/Up instead of silently discarding them. - Fix buttonNumber: was passing the raw evdev code (272, 273, …); now uses button - BTN_LEFT (0=left, 1=right, 2=middle, 3=side, 4=extra, …) matching AppKit convention and the X11 backend's XGetPointerMapping logic. - pointer_handle_motion: default case now produces NSOtherMouseDragged for any held button beyond right, covering middle/side/extra drags. Per-frame scroll accumulation: - Bump wl_seat binding from v1 to v5 so the compositor delivers wl_pointer.frame, axis_source, axis_stop, and axis_discrete events. - Add frame accumulation fields to struct pointer in WaylandConfig: frame_has_axis, frame_deltaX/Y, frame_discrete_x/y, frame_time. - pointer_handle_axis: accumulate delta into the frame state instead of dispatching immediately; avoids two separate NSScrollWheel events when both vertical and horizontal axes arrive in the same compositor frame. - pointer_handle_axis_discrete: store the integer step count per axis in the frame state (logged alongside the smooth delta in frame dispatch). - pointer_handle_axis_source: unchanged logic; now also logs source. - pointer_handle_frame: dispatch the accumulated axes as one NSScrollWheel event then reset all per-frame state; includes debug log with deltas, discrete steps, and source for traceability under WaylandScroll. - pointer_handle_axis_stop: emit a zero-delta NSScrollWheel so AppKit sees that a touchpad gesture ended (basis for future momentum support). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
struct output additions (WaylandServer.h):
- effective_width / effective_height: logical pixel dimensions after
applying scale factor and 90°/270° rotation transform.
- name / description: human-readable strings from wl_output v4 events.
- configured: YES after handle_done has fired at least once.
WaylandServer+Output.m — full rewrite:
- output_compute_effective_size(): divide physical mode dimensions by
the scale factor, then swap width/height for 90° and 270° transforms
(including the flipped variants).
- handle_done(): rewritten as the integration point for the full
configuration batch —
1. Recompute effective_width/height.
2. Call reposition_windows_for_output() to clamp any window whose
pos_x/y fell outside the new logical bounds; setFrameOrigin: is
called on the NSWindow so AppKit tracks the corrected position.
3. Post NSApplicationDidChangeScreenParametersNotification so
NSScreen reloads its geometry (guarded against pre-NSApp calls).
4. Skip steps 2-3 if nothing changed (suppresses spurious redraws).
- handle_name() / handle_description(): store v4 connector name and
human-readable description; logged under WaylandOutput.
- Listener updated to all 6 entries (adds handle_name, handle_description).
- wl_output bound at version 4 (was 2) to receive name/description.
WaylandServer.m:
- handle_global_remove(): was empty; now finds the removed output by
server_output_id, reassigns all its windows to the first remaining
output (preventing NULL-deref in coordinate helpers), removes the
output from output_list, destroys the wl_output proxy, frees all
heap strings, and posts NSApplicationDidChangeScreenParametersNotification.
- boundsForScreen:: now finds the specific output matching the screen ID
and returns NSMakeRect(alloc_x, alloc_y, effective_w, effective_h),
falling back to the first output only when the ID is not found.
Previously always returned the first output at (0,0).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pool_buffer struct (WaylandCairoShmSurface.h):
- Add needs_repaint (bool): set when handleExposeRect skips attach because
the compositor still holds the buffer; cleared in release callback.
- Add owner_surface / owner_display: allow the release callback to
re-attach and commit without going through the ObjC object.
WaylandCairoShmSurface.m — buffer lifecycle rewrite:
- finishBuffer: add close(buf->poolfd) to fix a file-descriptor leak that
accumulated one open FD per window allocation over the session lifetime.
- createShmBuffer: use calloc so all fields start zeroed; initialise
poolfd = -1 so the close() in finishBuffer is safe on error paths.
Destroy wl_shm_pool immediately after buffer creation (correct and
documented in the protocol); set buf->pool = NULL to avoid confusion.
Return NULL on buffer creation failure instead of crashing.
- buffer_handle_release: if needs_repaint is set, re-attach + damage +
commit the buffer immediately on release so a frame missed during a
busy period is never permanently lost; only calls finishBuffer when
no repaint is pending.
- initWithDevice: set owner_surface/owner_display back-pointers; add
wl_surface_damage before the initial commit (without it the compositor
treats the commit as a no-op); mark busy = true on attach.
- handleExposeRect: add three guards before attaching:
1. Size mismatch: if pbuffer dimensions differ from window size, set
needs_repaint and return — AppKit will allocate a correctly-sized
surface; avoids attaching a wrong-sized buffer during resize churn.
2. Busy check: if the compositor still holds the buffer, set
needs_repaint and return; prevents a Wayland protocol error.
3. Use the actual exposed NSRect (Y-flipped into Wayland coords) for
wl_surface_damage instead of always damaging the full surface.
- dealloc: clear owner_surface/owner_display/needs_repaint before calling
finishBuffer so the release callback cannot write to freed memory.
Also clear window->wcs to avoid a dangling ObjC pointer.
- Add clearOwnerSurface method: called by destroySurfaceRole: just before
wl_surface_destroy so the async release callback cannot dereference a
destroyed Wayland proxy.
WaylandServer.m:
- destroySurfaceRole: call [wcs clearOwnerSurface] before destroying the
wl_surface; then destroy wl_surface itself (role objects destroyed
first per protocol — xdg_surface/layer_surface were already handled).
Add #include for WaylandCairoShmSurface.h.
WaylandServer+Xdgshell.m:
- xdg_surface_on_configure terminated path: remove the duplicate
wl_list_remove call. termwindow: already removed the window from
window_list; calling wl_list_remove a second time corrupts the list
in debug builds and is logically wrong regardless. Now only free().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Header and banner updated from "Milestone 0" to "M0–M5" to reflect
cumulative coverage.
T9 pattern fixed: the "Milestone 3" TODO comment was replaced with real
button-mapping code in M3; the check now looks for BTN_SIDE which is
present in the buttonNumber documentation block.
New M5 tests (T12–T19):
T12 — Buffer FD lifecycle: verify finishBuffer closes poolfd (FD-leak
fix), pool_buffer is zero-initialised via calloc, needs_repaint
and owner back-pointer fields are present.
T13 — handleExposeRect safety: verify busy guard is present (prevents
attaching a compositor-held buffer), repaint-on-release path exists
in buffer_handle_release, and size-mismatch guard is present.
T14 — Precise damage rect: verify handleExposeRect uses NSMinX/NSMaxY
for the damaged region instead of always the full surface, and
that the initial commit in initWithDevice is preceded by damage.
T15 — Use-after-free prevention: verify clearOwnerSurface is called
before wl_surface_destroy in destroySurfaceRole, and that
wl_surface_destroy itself is present in that function.
T16 — No double wl_list_remove: verify the terminated path in
xdg_surface_on_configure calls free(window) (pattern escaped
as "free.window." to avoid ERE capturing-group confusion) and
does NOT call wl_list_remove( (tightened to the actual call
rather than the identifier, so comments mentioning the function
name do not trigger a false fail).
T17 — wl_shm_pool lifecycle: verify the pool is destroyed immediately
after buffer creation (wl_shm_pool_destroy present) and that
buf->pool is not stored for later use.
T18 — Sustained-operation stress: fire wayland-info 20 times in rapid
succession; compositor must survive without crash or hang.
T19 — Post-stress health: compositor process still alive; log contains
no crash, abort, SIGSEGV, double-free, or disconnect markers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bumping the wl_seat binding from v1 to v5 in Milestone 3 caused wl_seat.name (opcode 1, introduced in v2) to be delivered during the initial roundtrip. The seat_listener struct only declared one slot (capabilities), leaving the name slot NULL. libwayland-client aborts with "listener function for opcode 1 of wl_seat is NULL" when it encounters a NULL dispatcher. Fix: add seat_handle_name which logs the seat name under the WaylandIME debug category and satisfies the listener struct contract. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wayland-client 1.24 defines wl_pointer_listener with 11 event slots:
0 enter (v1)
1 leave (v1)
2 motion (v1)
3 button (v1)
4 axis (v1)
5 frame (v5)
6 axis_source (v5)
7 axis_stop (v5)
8 axis_discrete (v5)
9 axis_value120 (v8) ← was NULL → abort
10 axis_relative_direction (v9) ← was NULL → abort
We bound wl_seat at v5 (Milestone 3) which should gate v8/v9 events, but
wlroots 0.20 / Ambrosia sends these events regardless when the hardware
generates high-resolution scroll input. libwayland-client aborts on any
NULL dispatcher slot when that event arrives during wl_display_roundtrip.
Fixes:
pointer_handle_axis_value120: converts 1/120-unit steps to logical
scroll deltas (÷120 × mouse_scroll_multiplier) and accumulates them
in the per-frame state alongside the smooth axis value.
pointer_handle_axis_relative_direction: logs the natural-scroll hint
under WaylandScroll; direction inversion is a future improvement.
Both handlers added to pointer_listener in correct opcode order.
Also fixes wl_seat_listener which had only one slot (capabilities) after
the v5 bump added the wl_seat.name event (opcode 1). seat_handle_name
was already committed in the previous fix; this commit ensures both
listener arrays are complete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three bugs prevented click-and-drag from working correctly within the same GNUstep application on Wayland while it worked fine on X11: 1. postDragEvent: discarded all events except NSLeftMouseUp Once _waylandExternalDragActive was set (which happened for every drag when wl_data_device is available), the override returned immediately for any event type. This meant the GSAppKitDraggingEnter/Update/Drop events posted by the device_enter/motion/drop callbacks were dequeued by GSDragView's event loop and then silently thrown away, so the target view's draggingEntered:/draggingUpdated:/performDragOperation: methods were never called. Fix: pass NSAppKitDefined events through to [super postDragEvent:] so they are forwarded to the target window. NSLeftMouseUp still exits the loop; all other types (pointer motion, which stops arriving during a Wayland drag) are suppressed. 2. device_enter/motion/leave posted events to the app queue for in-process drops. GSDragView's running event loop dequeued them, called postDragEvent:, and (before fix 1) discarded them. Even with fix 1 the queue path adds latency and could interact with GSDragView logic. Fix: detect in-process drops (dnd_source != NULL means we are the drag source, so the target is one of our own windows). For in-process, send events directly to the target NSWindow via [nswindow sendEvent:] bypassing the app queue entirely. 3. device_enter called setupInboundDragWithPasteboard: unconditionally. This overwrote dragPasteboard from the live source pasteboard to an empty NSDragPboard, which would have corrupted data_source_send for any concurrent external receiver. For in-process drops the shared WaylandDragView already carries the correct source info from dragImage:. Fix: skip setupInboundDragWithPasteboard: when inProcess == YES. 4. device_drop used a pipe for in-process data transfer. Same-client DnD deadlocks: wl_data_offer_receive asks the compositor to call wl_data_source.send back to us, but we are blocked in read() inside a dispatch callback and cannot dispatch that event. Fix: for in-process drops, copy types and data directly from the source pasteboard (WaylandDragView.dragPasteboard) to NSDragPboard in memory, with no pipe and no roundtrip. The drop event is then delivered via [nswindow sendEvent:] so performDragOperation: sees the correct data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…views Pointer events for a wl_subsurface arrive with sx/sy relative to that subsurface's own origin, not the parent window origin. The previous approach stored the subsurface offset in a single gl_subsurface_x/y pair on struct window, which was overwritten by whichever WaylandGLContext ran last — breaking coordinate translation for all other GL views in the same window (e.g. the three NSOpenGLViews in MyGL.app each picking up the wrong view's offset, causing hitTest: to always route mouse events to the same view regardless of which one was clicked). Replace the per-window offset fields with a struct wl_surface_binding stored as the wl_surface user data for every GNUstep-owned surface: - Main window surfaces embed their binding (offset 0,0) directly inside struct window — no allocation required. - Each WaylandGLContext mallocs its own binding with its actual subsurface position; the binding is updated on resize and freed with the surface. pointer_handle_enter reads the per-surface offset via surface_get_offset() and stores it in pointer.focus_offset_x/y. pointer_handle_motion applies that offset unconditionally (it is zero for main surfaces). All other wl_surface_get_user_data call sites are updated to surface_get_window(). Also folds in the earlier keyboard_handle_enter NULL-surface guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| @@ -0,0 +1,173 @@ | |||
| # Wayland Backend Feature Implementation Plan (vs X11 parity) | |||
There was a problem hiding this comment.
It is surely helpful for you to have these two files, but we should store them someplace else. Either in the Documentation folder or in the wayland folder itself.
There was a problem hiding this comment.
I've removed the automated testing files from the folder. The Smoke test was specifically used to verify that the changes to text input/feature parity with the X11 backend were working. The planning.MD files were created by Chat GPT when I asked it to compare the X11 backend and the Wayland backend for feature differences. Those plan files are no longer required as the plans were executed by claude and were successful.
| @@ -0,0 +1,1815 @@ | |||
| #! /bin/sh | |||
There was a problem hiding this comment.
Can you please explain what this and the next file are for and why they live in a folder with a rather strange name? And why this went unnoticed? This could give the impression that you never even looked through the changes your AI did produce. And if you didn't , why should I?
I really would like to see the changes from this PR to be applied but this will require more commitment from your side.
There was a problem hiding this comment.
That entire folder is unnecessary. I've removed it from the Source tree. It appears that it was the $GNUSTEP_MAKEFILES folder e.g /usr/GNUstep/System/Library/Makefiles. At some point rather than link/refer to the folder the folder contents were copied into that location. Subsequent runs never used it.
| # Additional library directories the linker should search | ||
| #ADDITIONAL_LIB_DIRS += | ||
| CONFIG_SYSTEM_LIB_DIR += $(GRAPHIC_LFLAGS) | ||
| CONFIG_SYSTEM_LIB_DIR += $(GRAPHIC_LFLAGS) -lGL -lEGL -lwayland-egl |
There was a problem hiding this comment.
We should not add libraries here. This should rather be done in the configure script when the libraries are requested and present.
|
|
||
| AC_INIT | ||
| AC_PREREQ([2.73]) | ||
| AC_PREREQ([2.72]) |
There was a problem hiding this comment.
Why did you go back to the older version here? You otherwise had no changes to this file and so a regeneration of the configure file should not be necessary.
There was a problem hiding this comment.
I manually regenerated the file and when I was merging it I was getting a mismatch preventing it from merging. I'm on Debian 14 with Autoconf 2.72.
|
In its current form this PR is almost unreviewable. We need to split it up. It looks like you are building up on already existing other changes. Should we try to get this other changes in one by one first? Are these mergable by themselves or are there changes required? |
|
Thanks Fred for taking some time on this, I will try to split the changes up by feature and resubmit the requests. Now that I know some of the problems I'll try to make this easier to merge. There are still issues in the Wayland backend, but they are mostly around popup/right click menus, and popup windows like file open/save dialogs disappearing. This was the case before I made my changes. |
Sounds great. Looking forward to help you with this. |
This adds support for NSOpenGL on Wayland as well as fixes some issues with drag and drop and Keyboard and Mouse input handling. I'm looking for assistance/guidance on what needs to be done to get this merged. I'm expecting to have to go back and edit this.
For the OpenGL support I had Claude compare the X11/Wayland drivers and implement the Wayland GL support based on the X11 implementation. This is working in the MyGL.app test application as well as other GL applications running on Wayland.
I had ChatGPT run a report against the X11/Wayland drivers and then had Claude implement missing features from Wayland. This fixed Keyboard handling in terminal.app as I can now use tab-autocompletion where it was broken before. It also fixed drag/drop support on Wayland. I can now drag/drop tabs in Firefox properly and It partially fixed drag/drop support in Gorm.app.