Files
openclaw/apps/macos/Sources/OpenClaw/MenuGatewaysInjector.swift
Peter Steinberger f0b7542a22 feat(mac): gateway menu-bar surfaces and Gateways main menu (#114038)
* feat(mac): add gateway menu surfaces

* docs(mac): document gateway menu surfaces

* test(mac): make dashboard frame assertions display-independent
2026-07-26 02:13:13 -04:00

96 lines
3.2 KiB
Swift

import AppKit
@MainActor
final class MenuGatewaysInjector: NSObject {
static let shared = MenuGatewaysInjector()
private let tag = 9_415_559
func inject(into menu: NSMenu) {
for item in menu.items where item.tag == self.tag {
menu.removeItem(item)
}
let items = DashboardGatewayMenuModel.items(from: DashboardManager.shared.gatewayEntries)
guard !items.isEmpty,
var cursor = menu.items.lastIndex(where: \.isSeparatorItem)
else {
return
}
let header = NSMenuItem.sectionHeader(title: String(localized: "Gateways"))
header.tag = self.tag
menu.insertItem(header, at: cursor)
cursor += 1
for gateway in items {
let item = NSMenuItem(
title: gateway.name,
action: #selector(self.openGateway(_:)),
keyEquivalent: "")
item.tag = self.tag
item.target = self
item.representedObject = gateway.id
item.image = Self.healthImage(for: gateway.health, name: gateway.name)
item.state = gateway.isPrimary ? .on : .off
menu.insertItem(item, at: cursor)
cursor += 1
if gateway.canPromote {
let alternate = NSMenuItem(
title: String(localized: "Set \(gateway.name) as Primary…"),
action: #selector(self.setPrimary(_:)),
keyEquivalent: "")
alternate.tag = self.tag
alternate.target = self
alternate.representedObject = gateway.id
alternate.isAlternate = true
alternate.keyEquivalentModifierMask = [.option]
menu.insertItem(alternate, at: cursor)
cursor += 1
}
}
}
private static func healthImage(for health: DashboardGatewayHealth, name: String) -> NSImage? {
let (symbolName, color): (String, NSColor) = switch health {
case .ok:
("circle.fill", .systemGreen)
case .error:
("exclamationmark.circle.fill", .systemRed)
case .unknown:
("circle", .tertiaryLabelColor)
}
let accessibilityDescription = switch health {
case .ok:
String(localized: "\(name), healthy")
case .error:
String(localized: "\(name), health error")
case .unknown:
String(localized: "\(name), health unknown")
}
let image = NSImage(systemSymbolName: symbolName, accessibilityDescription: accessibilityDescription)
return image?.withSymbolConfiguration(.init(paletteColors: [color]))
}
@objc
private func openGateway(_ sender: NSMenuItem) {
guard let id = sender.representedObject as? String,
let target = DashboardGatewayTarget(bridgeID: id)
else {
return
}
DashboardManager.shared.openOrFocusDashboard(for: target)
}
@objc
private func setPrimary(_ sender: NSMenuItem) {
guard let id = sender.representedObject as? String,
let target = DashboardGatewayTarget(bridgeID: id)
else {
return
}
DashboardManager.shared.confirmSetPrimary(target)
}
}