mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-31 10:06:33 +00:00
fix(mac): restore right-click status-item menu on macOS 27 (#472)
Present the right-click menu from a global right-mouse-down monitor (hit-tested against the status-item window) via NSMenu.popUp, since macOS 27 no longer routes right-mouse events to the status-item action. Legacy action path retained for macOS <= 26, guarded by a debounce. Remove the global monitor on terminate.
This commit is contained in:
parent
f1bf7a197b
commit
81fdeee5d6
1 changed files with 38 additions and 3 deletions
|
|
@ -32,6 +32,8 @@ struct CodeBurnApp: App {
|
|||
final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
||||
private var statusItem: NSStatusItem!
|
||||
private var popover: NSPopover!
|
||||
private var rightClickMonitor: Any?
|
||||
private var lastContextMenuPresentedAt: Date = .distantPast
|
||||
fileprivate let store = AppStore()
|
||||
let updateChecker = UpdateChecker()
|
||||
/// Held for the lifetime of the app to opt out of App Nap and Automatic Termination.
|
||||
|
|
@ -50,6 +52,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||
private var codexQuotaRefreshTask: Task<Bool, Never>?
|
||||
private var refreshLoopHeartbeatAt: Date = .distantPast
|
||||
|
||||
func applicationWillTerminate(_ notification: Notification) {
|
||||
if let monitor = rightClickMonitor {
|
||||
NSEvent.removeMonitor(monitor)
|
||||
rightClickMonitor = nil
|
||||
}
|
||||
}
|
||||
|
||||
func applicationWillFinishLaunching(_ notification: Notification) {
|
||||
// Set accessory policy before the app's focus chain forms. On macOS Tahoe
|
||||
// (26.x), setting it after didFinishLaunching causes ghost status items
|
||||
|
|
@ -622,8 +631,25 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||
|
||||
button.target = self
|
||||
button.action = #selector(handleButtonClick(_:))
|
||||
// Left-click drives the popover. We keep .rightMouseUp in the mask so the
|
||||
// legacy action path below still fires on macOS <= 26; on macOS 27 the
|
||||
// system consumes the right button entirely and this never fires, so the
|
||||
// global monitor (below) is what restores the right-click menu there.
|
||||
button.sendAction(on: [.leftMouseUp, .rightMouseUp])
|
||||
|
||||
// macOS 27 no longer routes any right-mouse event to the status-item
|
||||
// button's target/action. A global monitor still observes right-mouse-down;
|
||||
// we hit-test it against our own status-item window and present the menu
|
||||
// ourselves. Harmless and stable on 15/26 too (the debounce in
|
||||
// showContextMenu prevents a double-present if the legacy path also fires).
|
||||
rightClickMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.rightMouseDown]) { [weak self] _ in
|
||||
guard let self,
|
||||
let button = self.statusItem.button,
|
||||
let window = button.window,
|
||||
window.frame.contains(NSEvent.mouseLocation) else { return }
|
||||
DispatchQueue.main.async { self.showContextMenu(from: button) }
|
||||
}
|
||||
|
||||
// Defer the full attributed title setup to ensure initial render completes
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.refreshStatusButton()
|
||||
|
|
@ -783,6 +809,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||
guard let button = statusItem.button,
|
||||
let event = NSApp.currentEvent else { return }
|
||||
|
||||
// Legacy right-click path for macOS <= 26 (no-op on 27, where the action
|
||||
// never receives a right-mouse event — the global monitor handles it).
|
||||
if event.type == .rightMouseUp {
|
||||
showContextMenu(from: button)
|
||||
return
|
||||
|
|
@ -816,6 +844,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||
}
|
||||
|
||||
private func showContextMenu(from button: NSStatusBarButton) {
|
||||
// Debounce: on macOS <= 26 both the legacy action path and the global
|
||||
// monitor can fire for a single right-click. Present at most once per click.
|
||||
let now = Date()
|
||||
guard now.timeIntervalSince(lastContextMenuPresentedAt) > 0.3 else { return }
|
||||
lastContextMenuPresentedAt = now
|
||||
|
||||
let menu = NSMenu()
|
||||
|
||||
let settingsItem = NSMenuItem(title: "Settings…", action: #selector(openSettings), keyEquivalent: ",")
|
||||
|
|
@ -835,9 +869,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||
quitItem.target = self
|
||||
menu.addItem(quitItem)
|
||||
|
||||
statusItem.menu = menu
|
||||
button.performClick(nil)
|
||||
statusItem.menu = nil
|
||||
// Present directly. The previous `statusItem.menu = menu; button.performClick`
|
||||
// trick relies on the click -> action path that macOS 27 changed; popUp is
|
||||
// version-stable.
|
||||
menu.popUp(positioning: nil, at: NSPoint(x: 0, y: button.bounds.height + 4), in: button)
|
||||
}
|
||||
|
||||
private var settingsWindowController: NSWindowController?
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue