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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Localizations: add Spanish and Catalan language packs and fill missing localization keys (#1041). Thanks @seifreed!

### Fixed
- Menu: keep lower action rows stable when Refresh is highlighted or pressed (#1071). Thanks @MadanChaollaPark!
- Linux CLI: avoid linking JetBrains provider parsing against `libxml2.so.2`, improving compatibility with newer distros that ship libxml2 2.15+ (#1046). Thanks @semsemyonoff!
- Claude: remove the obsolete peak-hours indicator and setting now that Anthropic no longer applies peak-hour limits (#1023). Thanks @rohitjavvadi!
- Antigravity: verify cloud model lists that report every quota as full against the user quota endpoint before showing remote OAuth usage (#1063). Thanks @devpras22!
Expand Down
16 changes: 16 additions & 0 deletions Sources/CodexBar/StatusItemController+Actions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ extension StatusItemController: StatusItemMenuPersistentActionDelegate {
}
}

nonisolated func performPersistentSettingsAction() {
Task { @MainActor [weak self] in
guard let self else { return }
self.closeOpenMenusFromShortcutIfNeeded()
self.showSettingsGeneral()
}
}

nonisolated func performPersistentQuitAction() {
Task { @MainActor [weak self] in
guard let self else { return }
self.closeOpenMenusFromShortcutIfNeeded()
self.quit()
}
}

nonisolated func performProviderNavigation(_ direction: StatusItemMenuProviderNavigationDirection) {
Task { @MainActor [weak self] in
self?.navigateProviderSwitcher(direction)
Expand Down
30 changes: 16 additions & 14 deletions Sources/CodexBar/StatusItemController+Menu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -729,8 +729,12 @@ extension StatusItemController {
}
menu.addItem(item)
case let .action(title, action):
if case .refresh = action {
menu.addItem(self.makePersistentMenuActionItem(title: title, action: action, width: width))
if self.usesPersistentMenuActionItem(for: action) {
menu.addItem(self.makePersistentMenuActionItem(
Comment on lines +732 to +733

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve keyboard activation for persistent meta actions

This change routes non-refresh actions through the persistent view-backed row path, but those rows are created as NSMenuItem entries with no selector and only mouse onClick handling. Since StatusItemMenu.performKeyEquivalent explicitly handles only Cmd+R/Cmd+,/Cmd+Q, keyboard users can no longer activate rows like About or Install Update (and highlighted-row Return activation has no action to dispatch). That is a user-visible regression in keyboard/accessibility behavior; keep these as native action-backed menu items or add explicit keyboard/selection dispatch for the custom rows.

Useful? React with 👍 / 👎.

title: title,
action: action,
menu: menu,
width: width))
continue
}

Expand Down Expand Up @@ -799,33 +803,31 @@ extension StatusItemController {
private func makePersistentMenuActionItem(
title: String,
action: MenuDescriptor.MenuAction,
menu: NSMenu,
width: CGFloat) -> NSMenuItem
{
let shortcut = self.shortcut(for: action)
let row = PersistentMenuActionItemView(
title: title,
systemImageName: action.systemImageName,
systemImageName: self.persistentMenuActionSystemImageName(for: action),
shortcutText: shortcut.map { self.shortcutLabel(for: $0) },
width: width,
onClick: { [weak self] in
self?.performPersistentMenuAction(action)
onClick: { [weak self, weak menu] in
self?.performPersistentMenuAction(action, in: menu)
})

let item = NSMenuItem(title: title, action: nil, keyEquivalent: shortcut?.key ?? "")
item.keyEquivalentModifierMask = shortcut?.modifiers ?? NSEvent.ModifierFlags()
item.isEnabled = true
item.view = row
item.toolTip = title
return item
}

private func performPersistentMenuAction(_ action: MenuDescriptor.MenuAction) {
switch action {
case .refresh:
self.refreshNow()
default:
break
if action != .refresh {
let (selector, represented) = self.selector(for: action)
item.action = selector
item.target = self
item.representedObject = represented
}
return item
}

private func shortcutLabel(for shortcut: (key: String, modifiers: NSEvent.ModifierFlags)) -> String {
Expand Down
20 changes: 12 additions & 8 deletions Sources/CodexBar/StatusItemController+MenuPresentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ final class PersistentMenuActionItemView: NSView, MenuCardHighlighting {
private let backgroundView = NSView()
private let imageView = NSImageView()
private let titleField: NSTextField
private let shortcutField: NSTextField?
private let shortcutField: NSTextField
private let onClick: () -> Void

override var intrinsicContentSize: NSSize {
Expand All @@ -199,7 +199,7 @@ final class PersistentMenuActionItemView: NSView, MenuCardHighlighting {
onClick: @escaping () -> Void)
{
self.titleField = NSTextField(labelWithString: title)
self.shortcutField = shortcutText.map(NSTextField.init(labelWithString:))
self.shortcutField = NSTextField(labelWithString: shortcutText ?? "")
self.onClick = onClick
super.init(frame: NSRect(origin: .zero, size: NSSize(width: width, height: Self.rowHeight)))
self.setupView(systemImageName: systemImageName)
Expand All @@ -225,7 +225,7 @@ final class PersistentMenuActionItemView: NSView, MenuCardHighlighting {
let secondaryColor = highlighted ? NSColor.selectedMenuItemTextColor : NSColor.secondaryLabelColor
self.backgroundView.isHidden = !highlighted
self.titleField.textColor = primaryColor
self.shortcutField?.textColor = secondaryColor
self.shortcutField.textColor = secondaryColor
self.imageView.contentTintColor = primaryColor
}

Expand All @@ -250,6 +250,13 @@ final class PersistentMenuActionItemView: NSView, MenuCardHighlighting {
self.titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
self.titleField.translatesAutoresizingMaskIntoConstraints = false

self.shortcutField.font = NSFont.menuFont(ofSize: NSFont.smallSystemFontSize)
self.shortcutField.alignment = .right
self.shortcutField.lineBreakMode = .byTruncatingTail
self.shortcutField.setContentHuggingPriority(.required, for: .horizontal)
self.shortcutField.setContentCompressionResistancePriority(.required, for: .horizontal)
self.shortcutField.translatesAutoresizingMaskIntoConstraints = false

let spacer = NSView()
spacer.translatesAutoresizingMaskIntoConstraints = false
spacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
Expand All @@ -262,11 +269,7 @@ final class PersistentMenuActionItemView: NSView, MenuCardHighlighting {
stack.addArrangedSubview(self.imageView)
stack.addArrangedSubview(self.titleField)
stack.addArrangedSubview(spacer)
if let shortcutField {
shortcutField.font = NSFont.menuFont(ofSize: NSFont.smallSystemFontSize)
shortcutField.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(shortcutField)
}
stack.addArrangedSubview(self.shortcutField)
self.addSubview(stack)

NSLayoutConstraint.activate([
Expand All @@ -277,6 +280,7 @@ final class PersistentMenuActionItemView: NSView, MenuCardHighlighting {

self.imageView.widthAnchor.constraint(equalToConstant: 18),
self.imageView.heightAnchor.constraint(equalToConstant: 18),
self.shortcutField.widthAnchor.constraint(equalToConstant: 38),

stack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 12),
stack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -12),
Expand Down
56 changes: 56 additions & 0 deletions Sources/CodexBar/StatusItemController+PersistentMenuActions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import AppKit

extension StatusItemController {
func usesPersistentMenuActionItem(for action: MenuDescriptor.MenuAction) -> Bool {
switch action {
case .installUpdate, .refresh, .settings, .about, .quit:
true
default:
false
}
}

func persistentMenuActionSystemImageName(for action: MenuDescriptor.MenuAction) -> String? {
switch action {
case .installUpdate:
"arrow.down.circle"
case .refresh:
MenuDescriptor.MenuActionSystemImage.refresh.rawValue
case .settings:
MenuDescriptor.MenuActionSystemImage.settings.rawValue
case .about:
MenuDescriptor.MenuActionSystemImage.about.rawValue
case .quit:
MenuDescriptor.MenuActionSystemImage.quit.rawValue
default:
action.systemImageName
}
}

func performPersistentMenuAction(_ action: MenuDescriptor.MenuAction, in menu: NSMenu?) {
switch action {
case .refresh:
self.refreshNow()
case .installUpdate:
self.closeMenuForPersistentAction(menu)
self.installUpdate()
case .settings:
self.closeMenuForPersistentAction(menu)
self.showSettingsGeneral()
case .about:
self.closeMenuForPersistentAction(menu)
self.showSettingsAbout()
case .quit:
self.closeMenuForPersistentAction(menu)
self.quit()
default:
break
}
}

private func closeMenuForPersistentAction(_ menu: NSMenu?) {
guard let menu else { return }
menu.cancelTrackingWithoutAnimation()
self.forgetClosedMenu(menu)
}
}
37 changes: 31 additions & 6 deletions Sources/CodexBar/StatusItemMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,24 @@ enum StatusItemMenuProviderNavigationDirection {

protocol StatusItemMenuPersistentActionDelegate: AnyObject {
func performPersistentRefreshAction()
func performPersistentSettingsAction()
func performPersistentQuitAction()
func performProviderNavigation(_ direction: StatusItemMenuProviderNavigationDirection)
}

final class StatusItemMenu: NSMenu {
weak var persistentActionDelegate: StatusItemMenuPersistentActionDelegate?

override func performKeyEquivalent(with event: NSEvent) -> Bool {
if Self.isRefreshKeyEquivalent(event) {
self.persistentActionDelegate?.performPersistentRefreshAction()
if let action = Self.persistentAction(for: event) {
switch action {
case .refresh:
self.persistentActionDelegate?.performPersistentRefreshAction()
case .settings:
self.persistentActionDelegate?.performPersistentSettingsAction()
case .quit:
self.persistentActionDelegate?.performPersistentQuitAction()
}
return true
}
if let direction = Self.providerNavigationDirection(for: event),
Expand All @@ -28,12 +37,28 @@ final class StatusItemMenu: NSMenu {
return super.performKeyEquivalent(with: event)
}

private nonisolated static func isRefreshKeyEquivalent(_ event: NSEvent) -> Bool {
guard event.type == .keyDown else { return false }
guard event.charactersIgnoringModifiers?.lowercased() == "r" else { return false }
private enum PersistentAction {
case refresh
case settings
case quit
}

private nonisolated static func persistentAction(for event: NSEvent) -> PersistentAction? {
guard event.type == .keyDown else { return nil }

let relevantModifiers = event.modifierFlags.intersection([.command, .option, .control, .shift])
return relevantModifiers == .command
guard relevantModifiers == .command else { return nil }

switch event.charactersIgnoringModifiers?.lowercased() {
case "r":
return .refresh
case ",":
return .settings
case "q":
return .quit
default:
return nil
}
}

private nonisolated static func providerNavigationDirection(
Expand Down
Loading