Skip to content

rio-notifier: present notifications while Rio is frontmost on macOS - #1832

Open
KennethLj wants to merge 1 commit into
raphamorim:mainfrom
KennethLj:fix/macos-notification-banner
Open

KennethLj wants to merge 1 commit into
raphamorim:mainfrom
KennethLj:fix/macos-notification-banner

Conversation

@KennethLj

Copy link
Copy Markdown

Fixes #1831.

On macOS, notifications triggered from inside Rio (OSC 9 / OSC 777) are delivered but never presented: no banner appears, and the notification is only discoverable by opening Notification Center.

A terminal notification is emitted by a program running inside the terminal, so Rio is the frontmost app at the moment it is posted. Under the UserNotifications framework a notification arriving while its own app is in the foreground is presented only if the center's delegate implements userNotificationCenter(_:willPresent:withCompletionHandler:) and calls the completion handler with presentation options. rio-notifier never installed a delegate, so every notification was delivered without being presented.

The change

  • rio-notifier/src/lib.rs: declare a minimal UNUserNotificationCenterDelegate that asks for Banner | List | Sound, and assign it with setDelegate inside the existing Once in request_authorization(). The center holds its delegate weakly, so the object is deliberately leaked for the lifetime of the process.
  • rio-notifier/Cargo.toml: add UNNotification to the objc2-user-notifications features — willPresentNotification is gated on it and it is not implied by the features already enabled.

No dependency bumps: everything needed already exists in the pinned objc2 0.5.2 / objc2-user-notifications 0.2.2.

Apple's guidance is to assign the delegate before the app finishes launching. request_authorization() is called from Application::new (frontends/rioterm/src/main.rs:242), which runs before application.run(window_event_loop) on line 248, so this is satisfied without touching rioterm.

Verification

Manually verified on macOS 26.6 (Apple Silicon), since there is no automated coverage for notification presentation.

A release build was bundled from misc/osx/Rio.app, given its own bundle identifier, signed, installed to /Applications and launched through LaunchServices, then driven with printf '\033]777;notify;BannerTest N;patched delegate\a' on a timer. The screen was captured every 3s and each frame checked for both the banner and the frontmost app in the menu bar.

With the patched build frontmost, every fire produced a banner. Frames where focus had moved to another Rio window were discarded — a backgrounded app banners regardless of the delegate, so those prove nothing.

Worth noting for anyone reproducing this: an ad-hoc signed bundle is refused by the notification daemon (requestAuthorization returns granted=false and every addNotificationRequest returns an error), so nothing is delivered at all and the result looks like a failure of the patch. A real signing identity is needed to test this.

Notes

  • Presentation is unconditional here, matching the behaviour of other terminals. Happy to put it behind a config key instead if you'd prefer.
  • didReceiveNotificationResponse (focusing the window/tab when a notification is clicked) is deliberately out of scope; it seems better as a separate change.

Notifications triggered from inside Rio were delivered silently on macOS:
they appeared in Notification Center but never showed a banner.

A terminal notification is emitted by a program running inside the
terminal, so Rio is the frontmost app when the notification is posted.
Under the UserNotifications framework, a notification arriving while its
own app is in the foreground is presented only if the center's delegate
implements userNotificationCenter:willPresentNotification: and calls the
completion handler with presentation options. Rio never installed a
delegate, so every notification was delivered without being presented.

Declare a minimal UNUserNotificationCenterDelegate that asks for
Banner | List | Sound, and assign it in request_authorization(), which
runs from Application::new before the event loop starts and therefore
before the app finishes launching.

UNNotification is added to the objc2-user-notifications features because
willPresentNotification is gated on it and it is not implied by the
features already enabled.

Closes raphamorim#1831

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@KennethLj
KennethLj force-pushed the fix/macos-notification-banner branch from 0f996cf to a2ea99c Compare August 8, 2026 09:04
@KennethLj

Copy link
Copy Markdown
Author

Heads-up on the red check here: it is not caused by this PR.

The failure is cargo fmt -- --check (step 8 of Test), not a test — clippy and cargo test are skipped as a result. It comes from a stray blank line at the end of librio/src/lib.rs, introduced in c58a305a on main, which is the commit this branch is based on. main itself is red for the same reason.

This branch touches only rio-notifier/Cargo.toml and rio-notifier/src/lib.rs; git diff origin/main -- librio/ is empty.

#1833 fixes the formatting on its own, and this should go green once that lands and this branch is rebased. Happy to rebase whenever suits you.

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.

macOS: desktop notifications are delivered silently — they land in Notification Center but never show a banner

1 participant