diff --git a/.changes/macos-autosave-name.md b/.changes/macos-autosave-name.md new file mode 100644 index 00000000..023c2b87 --- /dev/null +++ b/.changes/macos-autosave-name.md @@ -0,0 +1,5 @@ +--- +"tray-icon": minor +--- + +Add `TrayIconBuilder::with_autosave_name` (and corresponding `TrayIconAttributes::autosave_name` field) so callers can hand AppKit an `NSStatusItem.setAutosaveName` key on macOS. With the autosave name set, AppKit persists the user's ⌘+drag position across launches via `NSUserDefaults`. Without it, the status item resets to the default leftmost slot every launch — which on notched MacBooks (M1+ Pro/Max/14"/16") collides with the camera cutout. No-op on Linux / Windows. diff --git a/src/lib.rs b/src/lib.rs index 81ed8c64..450ef9d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,6 +203,22 @@ pub struct TrayIconAttributes { /// on the user's panel. This may not be shown in all visualizations. /// - **Windows:** Unsupported. pub title: Option, + + /// Autosave name passed to `NSStatusItem.setAutosaveName`. AppKit + /// uses this key to persist the user's ⌘+drag position across + /// app launches via NSUserDefaults. Without it, the status item + /// snaps back to the default leftmost slot every launch — which + /// on notched MacBooks (M1+ Pro/Max/14"/16") collides with the + /// camera cutout. + /// + /// Pick a stable, app-unique string (typically a reverse-DNS + /// identifier like `"com.example.app.tray"`); changing it + /// invalidates the saved position. + /// + /// ## Platform-specific + /// + /// - **macOS only.** No-op on Linux / Windows. + pub autosave_name: Option, } impl Default for TrayIconAttributes { @@ -216,6 +232,7 @@ impl Default for TrayIconAttributes { menu_on_left_click: true, menu_on_right_click: true, title: None, + autosave_name: None, } } } @@ -305,6 +322,24 @@ impl TrayIconBuilder { self } + /// Set the autosave name handed to `NSStatusItem.setAutosaveName`. + /// AppKit uses this key to persist the user's ⌘+drag position + /// across launches via NSUserDefaults. Without it, the status + /// item snaps back to the default leftmost slot every launch — + /// which on notched MacBooks collides with the camera cutout. + /// + /// Pick a stable, app-unique string (typically a reverse-DNS + /// identifier like `"com.example.app.tray"`); changing it + /// invalidates the saved position. + /// + /// ## Platform-specific + /// + /// - **macOS only.** No-op on Linux / Windows. + pub fn with_autosave_name>(mut self, name: S) -> Self { + self.attrs.autosave_name = Some(name.as_ref().to_string()); + self + } + /// Whether to show the tray menu on left click or not, default is `true`. /// /// ## Platform-specific: diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 58891f87..ffde3228 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -55,6 +55,18 @@ impl TrayIcon { NSStatusBar::systemStatusBar().statusItemWithLength(NSVariableStatusItemLength) }; + // When the caller supplied an autosave name, hand it to + // AppKit so the user's ⌘+drag position survives app + // launches via NSUserDefaults. Without this, every launch + // the status item resets to the default leftmost slot, + // which on notched MacBooks (M1+ Pro/Max/14"/16") collides + // with the camera cutout. + if let Some(name) = attrs.autosave_name.as_deref() { + unsafe { + ns_status_item.setAutosaveName(Some(&NSString::from_str(name))); + } + } + set_icon_for_ns_status_item_button( &ns_status_item, attrs.icon.clone(),