Skip to content

fix tray backend on MacOS - #34

Merged
MasonRemaley merged 1 commit into
allyourcodebase:mainfrom
natyv-io:fix-tray-backend-macos
Sep 24, 2026
Merged

MasonRemaley merged 1 commit into
allyourcodebase:mainfrom
natyv-io:fix-tray-backend-macos

Conversation

@syke99

@syke99 syke99 commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

This revision fixes an issue where the Unix tray backend is compiled for MacOS builds, so SDL_CreateTray fails with "Could not load AppIndicator libraries" and no tray icon ever appears.

Unlike tray/cocoa/SDL_tray.m (#ifdef SDL_PLATFORM_MACOS) and tray/dummy/SDL_tray.c (#ifdef SDL_TRAY_DUMMY), tray/unix/SDL_tray.c carries no platform guard, so it provides SDL_CreateTray unconditionally wherever it is listed. Listing it in the unix source group therefore makes it the implementation MacOS gets, and the cocoa backend was not listed in any group at all.

This fix adds SDL_tray.m to the cocoa source group and adds a new unix_tray source group to contain the unix tray backend, mirroring the unix_dialog split in #32 — same class of bug, same shape of fix.

Verified on MacOS (aarch64, Zig 0.16.0, SDL 3.4.14): a real NSStatusItem appears in the menu bar with its icon, menu entries fire their SDL_SetTrayEntryCallback callbacks, and SDL_HasActiveTrays then correctly suppresses the quit-on-last-window-close behaviour in SDL_windowevents.c, so an app can outlive its own window — which it could not before, since no tray was ever registered.

This revision fixes an issue where the Unix tray backend is compiled for
MacOS builds, so creating a tray fails with "Could not load AppIndicator
libraries" and no tray icon ever appears.

Unlike tray/cocoa/SDL_tray.m (`#ifdef SDL_PLATFORM_MACOS`) and
tray/dummy/SDL_tray.c (`#ifdef SDL_TRAY_DUMMY`), tray/unix/SDL_tray.c
carries no platform guard, so it provides SDL_CreateTray unconditionally
wherever it is listed. Listing it in the `unix` source group therefore
makes it the implementation MacOS gets, and the cocoa backend was not
listed in any group at all.

This fix adds SDL_tray.m to the `cocoa` source group and adds a new
`unix_tray` source group to contain the unix tray backend, mirroring the
`unix_dialog` split in allyourcodebase#32.

Verified on MacOS: a real NSStatusItem appears in the menu bar, its menu
entries fire their callbacks, and SDL_HasActiveTrays correctly suppresses
the quit-on-last-window-close behaviour so an app can outlive its window.
syke99 added a commit to natyv-io/core that referenced this pull request Sep 24, 2026
SDL3 ships a cross-platform tray API and natyv exposed none of it -- the
last of the three hard gaps from the 2026-09-20 capability audit that were
host-level features rather than widget composition.

Three files, following the Persist.zig/PersistStore.zig split:

- TrayRegistry.zig  state, plus a FIFO of not-yet-applied operations
- capabilities/Tray.zig  nine guest-facing host functions, pure JSON glue
- TrayDrain.zig  the only file in this codebase that calls SDL_Tray*

Every SDL_Tray* call is documented main-thread-only while every host
function runs on the dispatch worker, so operations are queued and drained
once per frame -- the same hand-off pending_file_dialog_request and
pending_window_requests already established. FIFO order is load-bearing,
not incidental: a submenu's entries are queued behind the submenu entry
that owns them, and applying out of order would target a handle that does
not exist yet.

Ids come from the new WidgetHost.reserveId, sharing the widget id space
deliberately. Dispatch never validates a widget_id against the registry,
so a private counter would eventually hand a tray entry the same number as
a real widget and route one's clicks to the other's handler. Sharing makes
that structurally impossible rather than unlikely -- and OnClick then works
on a tray entry exactly as it does on a Button, for free.

Checkbox state is mirrored host-side. SDL flips a checkbox itself when the
user clicks it, so the host cannot just remember what the guest last set,
and reading it back is main-thread-only -- the callback is already on the
main thread, so it reads and records there, and the guest's Checked()
answers from the mirror with no round trip.

The icon reuses conf.natyv.json's existing `icon` rather than adding a
tray-specific setting: an app shipping a tray already ships an app icon,
and SDL takes a decoded surface either way. Staged through the same
file-swap choreography as -Dapp-font and -Dwindow-style.

The lifecycle half, natyv_set_window_visible and
natyv_set_quit_on_last_window_close, is not tray-specific but exists
because of it -- a tray is usually there so an app can keep running with
nothing on screen. Hiding is not closing: the window keeps its widgets,
scroll positions and text. The startup window has no root_widget_id, so
there was nobody to notify and quitting was the only option; a guest now
nominates a widget id it already owns. That id may not be 0, because
Dispatch already pushes synthetic no-op events there on the documented
assumption that no real app registers a handler for id 0, and honouring 0
would quietly deliver those to a guest's close handler. quit=false with no
notify id is rejected outright rather than producing a close button that
does nothing and no way to quit.

A quit-path trace sits behind NATYV_STARTUP_TRACE. Once an app can outlive
its own window, a vetoed close, SDL's own quit-on-last-window-close and a
real Cmd+Q are indistinguishable from outside -- added after exactly that
ambiguity cost a debugging round.

build.zig.zon now pins natyv-io/SDL, a fork of allyourcodebase/SDL, for one
four-line fix. tray/unix/SDL_tray.c carries no platform guard, unlike the
cocoa and dummy backends, and upstream lists it in the shared `unix` source
group that macos.zig also compiles while listing tray/cocoa/SDL_tray.m
nowhere at all -- so SDL_CreateTray on macOS ran the Linux AppIndicator
backend and always failed. Submitted upstream as allyourcodebase/SDL#34,
mirroring their own PR #32 which fixed the identical bug for file dialogs.
Re-pin to a release tag and drop the fork once it merges; until then the
fork needs upstream merged into it periodically, since nothing automates
that.

Verified live on macOS: the icon renders in the menu bar, a menu click
travels SDL callback to EventQueue to worker dispatch to guest handler and
back through a host call to mutate a real Label on screen, and hide, show
and a vetoed window close all work with zero SDL_EVENT_QUIT. Windows and
Linux use upstream's own unmodified backends and have not been run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@MasonRemaley

Copy link
Copy Markdown
Contributor

Thanks for the PR!

@MasonRemaley
MasonRemaley merged commit 0a9d5c3 into allyourcodebase:main Sep 24, 2026
3 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants