Per kdroidFilter's review on #664:
- Drop jdk.jlink filtering from AbstractJLinkTask (JEP 493 fix) — unrelated
to this PR, belongs in its own change.
- Drop nativeDistributions.appContent / jpackage --app-content — incomplete
API (no KDoc, no tests, JVM-only, flattens subdirectories) and redundant
with appResourcesRootDir + afterInstall for the systemd-unit use case this
was meant for.
- Expand KDoc on the four linux { afterInstall/afterRemove/beforeInstall/
beforeRemove } properties to document, on the properties themselves
rather than only in CLAUDE.md: electron-builder's single-quote-only macro
substitution for afterInstall/afterRemove, that beforeInstall/beforeRemove
get no macro substitution at all (raw fpm args), and the Pacman pre_remove
upgrade-detection caveat.
Include apps that take a single module (notifications, tray, packaging)
alongside full-stack desktop apps, so the list shows the runtime is usable
piecewise.
Closes#670.
The X11 and Wayland attach paths asked for one thing only — a GL 3.3
compatibility-profile context — and returned 0 when the driver refused, which
aborts the window on the JVM side. Mesa r600 on an AMD RV610 (Debian 13) caps
the compat profile at 3.0, so the app crashed on launch.
- `create_gl_context()` (shared by both paths) asks for 3.3 compat, then 3.3
core. Core is strictly more available (a 3.2+ driver must offer it, compat
is optional), nothing here or in Skia needs the legacy entry points, and
r600 reaches 3.3 in core — so this GPU stays hardware-accelerated.
- When neither profile is available, `fall_back_to_software()` sets
`LIBGL_ALWAYS_SOFTWARE=1`, terminates the display and the attach re-enters
itself once from `eglGetPlatformDisplay`: Mesa reads the variable in
`eglInitialize` (driver load), not at context creation, so a bare retry of
`eglCreateContext` would not pick llvmpipe up. Process-wide and one-shot;
skipped when the user already set the variable.
- Wayland tears its subsurface chain down before retrying (flag consumed at
`fail_after_subsurface`).
Both branches log one line on stderr so the chosen path is visible in a
bug report. Existing hardware that accepts the compat request is untouched.
Wire the after-install/after-remove/before-install/before-remove scripts
for the GraalVM native-image packaging path too, matching the JVM/jpackage
path (previously only JVM was wired).
One screen in nucleus-demo to test the macOS trackpad work by hand:
a root inspector (every Scroll / PanStart / PanMove / PanEnd with gaps,
counters and one summary per gesture incl. how long after the last move
the PanEnd came), vertical and horizontal strips for sign and magnitude,
a map canvas that pans on Pan and zooms on Scroll, a scrollable
DropdownMenu (inline, or NSPanel via "Open with native popup layers"),
and an embedded WKWebView whose page draws its own scrollY / wheel HUD.
The header shows density, px per wheel unit and the trackpadPanEvents
flag. NUCLEUS_DEMO_TAB=<tab> opens the demo straight on a tab.
Adds the composewebview dependency to nucleus-demo (in-tree Nucleus
modules excluded, as in tao-demo).
- native_view.m: an AppKit event is marked spent only once its delta has
actually travelled (replayed, or synthesised by the step that carries it),
so an Ended-with-delta arriving with no pan open (PanStart + PanMove from
one event) no longer loses the finger movement; per-child gesture state,
keyed on the child handle rather than a raw NSView*, keeps a deferred
PanEnd from sending a second terminal phase and resets the sub-point
residue at gesture boundaries
- NativeView hands pan offsets over in scene px; the macOS host converts
them back with its own scale, so an app-level LocalDensity override no
longer skews the embedded view's distance. dispatchScrollToNative is back
to its original signature (Windows / Linux untouched)
- TaoTrackpadPanRouter: the pan end is a deadline, one timer in flight;
finger and momentum steps only move the deadline, the grace re-schedules
once (earlier), the timer re-arms for the remainder — no coroutine, wake
and cancel per 120 Hz step
- TaoSceneScrollRouter: a zero-delta orphaned tail end is not turned into a
Scroll (AWT drops zero deltas); once-only announce on a plain volatile;
the redundant sceneBundle guard in onPointerScroll is gone (the router's
cancelled flag is the contract)
- TaoPopupSceneLayer documents why the pan uses the layer density while the
surface uses host.scale; TaoWindow uses import aliases for the ABI alias
constants; the injector checks the main thread before its static init
- headful: shared ScrollableColumn / ScrollableRow fixtures and a harness
awaitUntilOrTimeout replace the copies; the wheel-notch baseline is taken
right before the injection; the library drift test no longer reads the
demo source
- native_view.m remembers the AppKit event it last handed to a child
(NSApp.currentEvent is not cleared while the app idles, and one event can
yield two Compose steps), so a deferred PanEnd or a PanStart+PanMove pair
never applies a delta twice; the synthesised fallback keeps a per-view
sub-point residue instead of rounding slow drags to zero
- popup_panel.m and the vendored view.rs test NSEventPhase bits instead of
switching on the NS_OPTIONS mask
- TaoSceneScrollRouter leaves the pan position alone for MayBegin (its
PanEnd belongs to the previous gesture); an orphaned momentum tail is
delivered as AWT-shaped Scroll instead of dropped; once-only CAS behind a
plain read; scrollRouter declared with the rest of the host state
- TaoStandalonePopupHostMac.dispose() is idempotent on the invalid path too
- TaoWindow keeps AWT_PIXEL_TO_ROTATION / MACOS_AWT_SCROLL_AMOUNT as
aliases of the shared constants (public statics in the validated ABI)
- demo ScrollTestScreen finalises a gesture inline on PanEnd / PanStart
- TaoScrollWireDriftTest also guards the kNv* native-view wire and the
10-units-per-wheel factor in events.rs, native_view.m and the demo;
TaoSceneScrollTest pins MacOSCocoaConfig's 10 dp per unit on macOS;
harness routers get one timer slot each
- NativeView forwards PanStart / PanMove / PanEnd with a phase; native_view.m
replays the live AppKit event only when its phase class matches and
otherwise synthesises a phased scroll, so an embedded scroll view sees the
gesture begin and end (rubber-band, scroller fade) — Windows / Linux ignore
the phase
- popups keep the gesture phase whatever hasPreciseScrollingDeltas says,
like the Rust window path
- TaoTrackpadPanRouter: momentum steps only continue an open pan; a tail
arriving after the grace already closed it is dropped instead of stacked on
Compose's fling
- TaoSceneScrollRouter: lazily created timer scope with
TaoNonFatalCoroutineExceptionHandler (a broken deferred PanEnd costs one
gesture, not the app); TaoScrollGesturePhase.fromWire is a map lookup
- TaoStandalonePopupHostMac: finishPan() runs inside the frame pump; the
invalid-host dispose path also shuts the render executor down
- nativeDiagInjectScrollWheel targets the NSView it is handed, is nil-safe on
the primary screen, inlined; documented as whole-point only (CGEvent delta
fields are integers — verified) and the headful momentum tail uses whole
points
- demo ScrollTestScreen closes a gesture on PanEnd / next PanStart and names
the 10 dp factor; harness closes the pan after the cursor-move guard like
the host; TaoScrollWireDriftTest resolves its sources per test with a
readable failure; stray @Suppress dropped
- TaoTrackpadPanRouter: an open pan always has an end timer (150 ms grace
after the finger Ended, 1 s stall watchdog otherwise); MayBegin during the
momentum tail closes the pan at once; finishNow() for clicks / wheel
notches; TaoScrollGesturePhase is now an enum (wire codes attached),
distinct from the public TaoTrackpadPhase, unknown codes degrade to a
plain precise scroll
- loop: the phase decides the route for the whole gesture — a step that
arrives in lines is scaled to its point equivalent instead of being
dropped as a stray wheel Scroll; dispatch_scroll_gesture loses its bogus
dead_code allow; view.rs drops the dead ViewState binding
- TaoSceneScrollRouter: pan position / modifiers come from gesture steps
only (PanEnd must hit the node that got the PanMoves), a click or a wheel
notch closes the pan, cancel() makes late events no-ops, the timer scope
carries TaoFatalCoroutineExceptionHandler; TaoPopupSceneLayer reads the
live layer density; TaoStandalonePopupHostMac drops the native callback
before cancelling the router; first Pan routing logs one CONFIG line
- NativeView consumes PanStart / PanEnd over an embedded view so the
ancestor scrollable never opens a session of its own
- popup_panel.m: NucleusScrollGesture enum instead of literals;
TaoScrollWireDriftTest compares Rust, ObjC and Kotlin wire codes plus the
JNI descriptors
- nativeDiagInjectScrollWheel is inert unless NUCLEUS_TAO_INPUT_INJECTION=1
(set by taoHeadfulTest) and main-thread only
- README / CLAUDE.md document the Pan behaviour and
-Dnucleus.tao.trackpadPanEvents=false
Review follow-ups on #652 / #653 / #654:
- TaoSceneScrollRouter: single front door for wheel / trackpad input shared
by the window host and both NSPanel popup hosts; popup_panel.m now forwards
the AppKit phase so a two-finger swipe over a popup list behaves like the
window behind it. `-Dnucleus.tao.trackpadPanEvents=false` restores
AWT-style Scroll events for handlers that only know PointerEventType.Scroll
- tao patch 0007: PixelDelta carries AppKit's logical points, so the loop no
longer locks WINDOWS per event nor round-trips through two scale caches
- TaoTrackpadPanRouter: terminal steps with a delta still pan when no gesture
is open; momentum grace 150 ms, tunable via
-Dnucleus.tao.trackpadMomentumGraceMillis; isPanning removed
- NativeView forwards only non-zero PanMove (PanStart / PanEnd replayed
NSApp.currentEvent, stale for the deferred end)
- host detach cancels the router's timer scope and ignores scrolls after
the scene is gone
- TaoWindow: one preciseScrollEvent() shaping helper, shared AWT constants
(apiDump: the two leaked private-companion fields are gone)
- headful cases: recorder reset per run, display-mode restore even when the
window never reports the new scale; JNI descriptor drift guard for
popup_panel.m; demo ScrollTestScreen logs Pan steps too
Horizontal trackpad scrolling was reversed and precise deltas were scaled
by the display factor, both relative to the AWT backend; trackpad gestures
were indistinguishable from wheel notches.
- vendored tao (patch 0007): stop negating scrollingDeltaX in the macOS
scroll_wheel (AppKit already follows the MouseScrollDelta convention, as
winit does) and carry the full AppKit phase / momentumPhase on
WindowEvent::MouseWheel as `scroll_phase: ScrollPhase`
- loop: hand SCROLL_PIXEL over in logical points (AWT never applies the
display scale to preciseWheelRotation) and route phased steps to a new
EventCallback.onScrollGesture
- host: TaoTrackpadPanRouter turns the gesture stream into Compose
PanStart / PanMove / PanEnd with panOffset = AWT delta x 10 dp, keeping
the pan open across AppKit's momentum tail (deferred PanEnd) so Compose
does not stack its own fling; wheel notches and phase-less precise
scrolls stay Scroll events; NativeView forwards Pan to the native view
- popups: appKitWheelToAwtScrollDelta flips both axes and drops the scale
- headful e2e: MacOsTrackpadScrollHeadfulCases inject real scrollWheel:
NSEvents into the tao content view (nativeDiagInjectScrollWheel), the
#653 case flipping the display to its HiDPI twin; plus router, scene and
wire unit tests
The window and dialog openers had their composition target inferred, so a
single non-UI-targeted composable called in the `nucleusApplication` scope
(the Compose compiler bakes `@ComposableTarget` onto any unmarked factory
forwarding a target-marked content lambda) reclassified the whole scope —
nested windows included — and every `@UiComposable` call in it warned, which
is fatal under `-Werror`.
Declare each opener `@ComposableOpenTarget(-1)` with a `@UiComposable`
content lambda, the way Compose Desktop's own `Window`/`Dialog` are open:
they are callable from any applier and always compose UI content in the new
window's own composition, so neither direction of the applier leak survives.
Covers `DecoratedWindow`/`DecoratedDialog`, the `NucleusWindowHost` /
`NucleusDialogHost` interfaces with their default implementations,
`HostedWindow`/`HostedDialog`, and Tao's `DecoratedWindow`,
`DecoratedDialog` and `TaoStandalonePopup`. The Material/Jewel wrappers
inherit the same scheme through inference (`[_[UiComposable]]`).
`COMPOSE_APPLIER_CALL_MISMATCH` is only a warning, so both test compilations
escalate it to an error and a fixture per module compiles the reported shape
(non-UI factory in the application scope, UI content in the windows).
Two more instances of the #640 bug class, both fixed upstream and ported by
hand — a non-reentrant lock held across a call that pumps or synchronously
sends messages, so a nested window-procedure dispatch re-locks it and the
event-loop thread parks in `WaitOnAddress` for good.
- IME: the window-state lock was held across `ImeHandler::process_message`,
which peeks the queue on `WM_IME_ENDCOMPOSITION` (the Korean IME sends the
committed string after the end message, so the queue decides whether to
defer). The peek now happens in `ime::peek_commit_queued` before the lock is
taken, and the result is passed in — mirroring upstream, where "IME
character-pending detection happens before acquiring window state locks".
Wider blast radius than #640: there only the keyboard arm took the lock,
here nearly every arm does, so any nested message deadlocked.
Upstream: tauri-apps/tao#1215.
- `TaskbarCreated`: the window-state lock was held across `set_skip_taskbar`,
which goes through `CoCreateInstance` + `ITaskbarList`. An STA COM call pumps
the message queue while it waits, so the window procedure is re-entered.
The flag is now read out before the call. Upstream: tauri-apps/tao#1264.
`Imm32Source` carries the precomputed flag instead of peeking on demand;
`ImeSource` is unchanged, so the IME state-machine tests keep driving the same
sequences through their stub.
`PeekMessageW` delivers pending cross-thread *sent* messages inline: the
kernel re-enters the window procedure through `KiUserCallbackDispatcher`
before the peek returns. Two non-reentrant `parking_lot::Mutex`es were held
across such a peek, so the nested dispatch deadlocked the event-loop thread
against itself — it parked in `WaitOnAddress` and never pumped a message
again, leaving the window permanently "Not Responding".
- `event_loop.rs`: the keyboard callback held the global `KEY_EVENT_BUILDERS`
map across `KeyEventBuilder::process_message`, which peeks. The builder is
now taken out of the map for the duration and put back afterwards (only if
the slot still exists, so a window dropped re-entrantly is not resurrected).
Its map value became an `Option` slot to make that take/put-back possible.
- `keyboard.rs`: the key-press and key-release arms held `LAYOUT_CACHE` across
their peek, while every other arm — and `update_modifiers`, reached from any
mouse or focus message — locks it too. The guard is now scoped to end before
the peek and re-acquired after it.
Reported as a freeze when moving a window between Windows 11 virtual
desktops: that path is keyboard-triggered and makes the shell send messages
to the window mid-peek. The reporter's stack shows the two nested window
procedure chains around `NtUserPeekMessage` and the park in `WaitOnAddress`.
Only injected or sent key messages can nest here — real keyboard input is
posted to the queue rather than sent, and the peeks use `PM_NOREMOVE` — so a
nested key message now finds an empty builder slot and is dropped instead of
hanging the process.