Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@
# patches/*.patch with `\n`-joined patterns. Normalize to LF in the working
# copy on every platform; the stored blobs are already LF.
* text=auto eol=lf

# Patched crates.io copies (nil-safe NSOpenPanel). Not first-party source.
desktop/src-tauri/vendor/** linguist-vendored
38 changes: 17 additions & 21 deletions desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ webkit2gtk = { version = "=2.0.2", features = ["v2_22"] }
[target.'cfg(target_os = "macos")'.dependencies]
block2 = { version = "0.6", default-features = false, features = ["std"] }
objc2 = { version = "0.6.4", default-features = false }
objc2-app-kit = { version = "0.3.2", default-features = false, features = ["NSEvent", "NSHapticFeedback", "NSMenu", "NSMenuItem", "NSStatusItem", "block2"] }
objc2-app-kit = { version = "0.3.2", default-features = false, features = ["NSApplication", "NSEvent", "NSHapticFeedback", "NSMenu", "NSMenuItem", "NSStatusItem", "block2"] }
objc2-foundation = { version = "0.3.2", default-features = false, features = ["NSDictionary", "NSError", "NSBundle", "NSObject", "NSProcessInfo", "NSString"] }
objc2-user-notifications = { version = "0.3.2", default-features = false, features = ["block2", "UNNotification", "UNNotificationContent", "UNNotificationRequest", "UNNotificationResponse", "UNNotificationSettings", "UNNotificationTrigger", "UNUserNotificationCenter"] }
keyring = { version = "3.6.3", default-features = false, features = ["apple-native", "vendored"], optional = true }
Expand Down Expand Up @@ -155,3 +155,11 @@ tokio = { version = "1", features = ["test-util"] }
# The relay's media validation, so the snapshot-sharing tests can prove the
# full export → sanitize → relay-accept → import contract end to end.
buzz_media_pkg = { package = "buzz-media", path = "../../crates/buzz-media" }

# wry 0.55.1 / rfd 0.16.0 still panic when +[NSOpenPanel openPanel] returns
# nil (macOS 26, ad-hoc local builds). Upstream wry#1716 is unmerged and
# wry 0.56.1 still calls the typed binding. Patch in-tree; drop when a
# crates.io release retains nil and cancels instead of aborting.
[patch.crates-io]
wry = { path = "vendor/wry" }
rfd = { path = "vendor/rfd" }
7 changes: 7 additions & 0 deletions desktop/src-tauri/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
<string>Hula Buzz</string>
<key>CFBundleName</key>
<string>Hula Buzz</string>
<!-- Regular activating UI app. LSUIElement would mark the process
Background and AppKit's file-dialog XPC can then return a nil
NSOpenPanel (crash on attach). -->
<key>LSUIElement</key>
<false/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSMicrophoneUsageDescription</key>
<string>Buzz needs microphone access for voice huddles.</string>
<key>NSCameraUsageDescription</key>
Expand Down
70 changes: 70 additions & 0 deletions desktop/src-tauri/src/macos_file_panel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! macOS file-panel crash guard.
//!
//! Composer attach uses a hidden `<input type="file">`. WKWebView asks Wry's
//! `run_file_upload_panel`, which called `NSOpenPanel::openPanel()`. On macOS
//! 26 Tahoe — especially ad-hoc/unsigned local `.app` builds — that class
//! method can return nil. The typed objc2 binding treats nil as a hard fail
//! (`none_fail`) and the process SIGABRTs before a picker appears.
//!
//! Native `tauri-plugin-dialog` / `rfd` hits the same constructor. Both crates
//! are patched in `vendor/` to retain the raw return and cancel instead of
//! aborting. This module keeps the process a regular activating app so AppKit
//! is willing to present the panel at all (crash reports showed Role:
//! Background).

#[cfg(target_os = "macos")]
pub(crate) fn ensure_regular_activation() {
use objc2::MainThreadMarker;
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy};

let Some(mtm) = MainThreadMarker::new() else {
return;
};
let app = NSApplication::sharedApplication(mtm);
let _ = app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
NSApplication::activate(&app);
}

#[cfg(test)]
mod tests {
const WRY_UI_DELEGATE: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/vendor/wry/src/wkwebview/class/wry_web_view_ui_delegate.rs"
));
const RFD_PANEL: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/vendor/rfd/src/backend/macos/file_dialog/panel_ffi.rs"
));

#[test]
fn patched_wry_cancels_when_nsopenpanel_is_nil() {
assert!(
WRY_UI_DELEGATE.contains("Retained::retain(ptr)"),
"wry must retain +[NSOpenPanel openPanel] without the typed nil panic"
);
assert!(
WRY_UI_DELEGATE.contains("(*handler).call((null_mut(),))"),
"nil panel must cancel the WebKit upload instead of aborting"
);
assert!(
!WRY_UI_DELEGATE.contains("NSOpenPanel::openPanel(mtm)"),
"typed NSOpenPanel::openPanel panics on nil"
);
}

#[test]
fn patched_rfd_does_not_use_typed_open_panel() {
assert!(
RFD_PANEL.contains("fn try_open_panel"),
"rfd must go through a nil-safe open-panel helper"
);
assert!(
!RFD_PANEL.contains("NSOpenPanel::openPanel(mtm)"),
"typed NSOpenPanel::openPanel panics on nil"
);
assert!(
!RFD_PANEL.contains("NSSavePanel::savePanel(mtm)"),
"typed NSSavePanel::savePanel panics on nil"
);
}
}
8 changes: 6 additions & 2 deletions desktop/src-tauri/src/tray_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
//! The webview owns the live agent-turn state. It sends the small display
//! projection here so the native menu can remain useful while Buzz is hidden.

// Mouse back/forward (X1/X2 buttons and swipe) is also macOS-only native I/O;
// group it here so both platform-layer init paths share one call site in lib.rs.
// macOS-only native I/O lives beside the tray init so lib.rs stays one call
// site (file-size ratchet). Mouse back/forward and the NSOpenPanel crash
// guard both run from `init`.
#[path = "macos_file_panel.rs"]
mod macos_file_panel;
#[path = "mouse_nav.rs"]
pub(crate) mod mouse_nav;

Expand Down Expand Up @@ -474,6 +477,7 @@ fn handle_menu_event<R: Runtime>(app: &AppHandle<R>, id: &str) {

/// Installs the persistent Buzz tray icon with the initial empty activity menu.
pub fn init<R: Runtime>(app: &AppHandle<R>) -> tauri::Result<()> {
macos_file_panel::ensure_regular_activation();
let preview_activities = preview_activities();
let preview_recent_activities = preview_recent_activities();
let activities = preview_activities.as_deref().unwrap_or(&[]);
Expand Down
7 changes: 7 additions & 0 deletions desktop/src-tauri/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
use chrono::Utc;

// tray_menu (and its file-panel child) is macOS-only. Include the same
// module under test so Linux CI still checks the vendored nil-NSOpenPanel
// guards.
#[cfg(test)]
#[path = "macos_file_panel.rs"]
mod macos_file_panel;

pub fn now_iso() -> String {
Utc::now().to_rfc3339()
}
Expand Down
17 changes: 17 additions & 0 deletions desktop/src-tauri/vendor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Vendored desktop patches

Pinned copies of crates.io `wry` 0.55.1 and `rfd` 0.16.0 with a nil-safe
`NSOpenPanel` / `NSSavePanel` constructor.

`+[NSOpenPanel openPanel]` can return nil (macOS 26 Tahoe, ad-hoc/unsigned
local `.app` builds, code-signature mismatch after an in-place replace).
The typed `objc2-app-kit` binding treats that as a programming error and
aborts the process. Composer attach goes through Wry's WKWebView upload
panel; `pick_and_upload_*` goes through rfd.

The patches retain the raw Objective-C return, cancel the picker when it
is nil, and promote the app to `NSApplicationActivationPolicyRegular`
before asking AppKit for the panel.

Replace with a crates.io bump when tauri-apps/wry#1716 (or equivalent) is
released and rfd does the same.
6 changes: 6 additions & 0 deletions desktop/src-tauri/vendor/rfd/.cargo_vcs_info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "5d32eec3a7930eb43b7e864eb773831bbd3d91b4"
},
"path_in_vcs": ""
}
19 changes: 19 additions & 0 deletions desktop/src-tauri/vendor/rfd/.editorconifg
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
max_line_length = 100

[*.{rs, toml}]
indent_size = 4

[*.md]
trim_trailing_whitespace = true
indent_size = 4

[Dockerfile]
indent_size = 4
Loading