feat(macos): add Dock menu shortcuts

This commit is contained in:
Peter Steinberger
2026-05-18 09:15:10 +01:00
parent 4a1745281e
commit bd69510662
4 changed files with 121 additions and 33 deletions

View File

@@ -0,0 +1,49 @@
import AppKit
@MainActor
enum AppNavigationActions {
static func openDashboard() {
NSApp.activate(ignoringOtherApps: true)
if DashboardManager.shared.showConfiguredWindowIfPossible() {
return
}
Task { @MainActor in
if DashboardManager.shared.showConfiguredWindowIfPossible() {
return
}
do {
try await DashboardManager.shared.show()
} catch {
DashboardManager.shared.showFailure(error)
}
}
}
static func openChat() {
NSApp.activate(ignoringOtherApps: true)
Task { @MainActor in
let sessionKey = await WebChatManager.shared.preferredSessionKey()
WebChatManager.shared.show(sessionKey: sessionKey)
}
}
static func toggleCanvas() {
NSApp.activate(ignoringOtherApps: true)
Task { @MainActor in
if AppStateStore.shared.canvasPanelVisible {
CanvasManager.shared.hideAll()
} else {
let sessionKey = await GatewayConnection.shared.mainSessionKey()
_ = try? CanvasManager.shared.show(sessionKey: sessionKey, path: nil)
}
}
}
static func openSettings(tab: SettingsTab = .general) {
SettingsTabRouter.request(tab)
SettingsWindowOpener.shared.open()
DispatchQueue.main.async {
NotificationCenter.default.post(name: .openclawSelectSettingsTab, object: tab)
}
}
}

View File

@@ -173,19 +173,7 @@ struct OpenClawApp: App {
private func openDashboardWindow() {
HoverHUDController.shared.setSuppressed(true)
self.isMenuPresented = false
if DashboardManager.shared.showConfiguredWindowIfPossible() {
return
}
Task { @MainActor in
if DashboardManager.shared.showConfiguredWindowIfPossible() {
return
}
do {
try await DashboardManager.shared.show()
} catch {
DashboardManager.shared.showFailure(error)
}
}
AppNavigationActions.openDashboard()
}
@MainActor
@@ -250,6 +238,59 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
private let webChatAutoLogger = Logger(subsystem: "ai.openclaw", category: "Chat")
let updaterController: UpdaterProviding = makeUpdaterController()
func applicationDockMenu(_: NSApplication) -> NSMenu? {
let menu = NSMenu()
menu.autoenablesItems = false
menu.addItem(self.dockMenuItem(
title: "Open Dashboard",
systemImage: "gauge",
action: #selector(self.openDashboardFromDockMenu(_:))))
menu.addItem(self.dockMenuItem(
title: "Open Chat",
systemImage: "bubble.left.and.bubble.right",
action: #selector(self.openChatFromDockMenu(_:))))
let canvasTitle = AppStateStore.shared.canvasPanelVisible ? "Close Canvas" : "Open Canvas"
let canvasItem = self.dockMenuItem(
title: canvasTitle,
systemImage: "rectangle.inset.filled.on.rectangle",
action: #selector(self.toggleCanvasFromDockMenu(_:)))
canvasItem.isEnabled = AppStateStore.shared.canvasEnabled
menu.addItem(canvasItem)
menu.addItem(.separator())
menu.addItem(self.dockMenuItem(
title: "Settings…",
systemImage: "gearshape",
action: #selector(self.openSettingsFromDockMenu(_:))))
return menu
}
private func dockMenuItem(title: String, systemImage: String, action: Selector) -> NSMenuItem {
let item = NSMenuItem(title: title, action: action, keyEquivalent: "")
item.target = self
item.image = NSImage(systemSymbolName: systemImage, accessibilityDescription: title)
return item
}
@objc
private func openDashboardFromDockMenu(_: Any?) {
AppNavigationActions.openDashboard()
}
@objc
private func openChatFromDockMenu(_: Any?) {
AppNavigationActions.openChat()
}
@objc
private func toggleCanvasFromDockMenu(_: Any?) {
AppNavigationActions.toggleCanvas()
}
@objc
private func openSettingsFromDockMenu(_: Any?) {
AppNavigationActions.openSettings()
}
func application(_: NSApplication, open urls: [URL]) {
Task { @MainActor in
for url in urls {

View File

@@ -111,28 +111,19 @@ struct MenuContent: View {
self.voiceWakeMicMenu
}
Divider()
Link(destination: URL(string: "openclaw://dashboard")!) {
Button {
AppNavigationActions.openDashboard()
} label: {
Label("Open Dashboard", systemImage: "gauge")
}
Button {
Task { @MainActor in
let sessionKey = await WebChatManager.shared.preferredSessionKey()
WebChatManager.shared.show(sessionKey: sessionKey)
}
AppNavigationActions.openChat()
} label: {
Label("Open Chat", systemImage: "bubble.left.and.bubble.right")
}
if self.state.canvasEnabled {
Button {
Task { @MainActor in
if self.state.canvasPanelVisible {
CanvasManager.shared.hideAll()
} else {
let sessionKey = await GatewayConnection.shared.mainSessionKey()
// Don't force a navigation on re-open: preserve the current web view state.
_ = try? CanvasManager.shared.show(sessionKey: sessionKey, path: nil)
}
}
AppNavigationActions.toggleCanvas()
} label: {
Label(
self.state.canvasPanelVisible ? "Close Canvas" : "Open Canvas",
@@ -330,12 +321,7 @@ struct MenuContent: View {
}
private func open(tab: SettingsTab) {
SettingsTabRouter.request(tab)
NSApp.activate(ignoringOtherApps: true)
self.openSettings()
DispatchQueue.main.async {
NotificationCenter.default.post(name: .openclawSelectSettingsTab, object: tab)
}
AppNavigationActions.openSettings(tab: tab)
}
private var macNodeStatus: (label: String, color: Color)? {

View File

@@ -1,3 +1,4 @@
import AppKit
import SwiftUI
import Testing
@testable import OpenClaw
@@ -38,4 +39,15 @@ struct MenuContentSmokeTests {
let view = MenuContent(state: state, updater: nil)
_ = view.body
}
@Test func `dock menu exposes primary shortcuts`() throws {
let delegate = AppDelegate()
let menu = try #require(delegate.applicationDockMenu(NSApplication.shared))
let titles = menu.items.map(\.title)
#expect(titles.contains("Open Dashboard"))
#expect(titles.contains("Open Chat"))
#expect(titles.contains("Open Canvas") || titles.contains("Close Canvas"))
#expect(titles.contains("Settings…"))
}
}