From 8efc804dfbd9414c05ca78aab1ebcf99454262c4 Mon Sep 17 00:00:00 2001 From: Kaede Akino Date: Tue, 6 Oct 2026 05:59:45 +0800 Subject: [PATCH] fix(shortcuts): complete iOS commands and remove redundant window close --- TFI/App/MobileCommands.swift | 15 +++++++++++++++ TFI/App/TermindIOSApp.swift | 4 +++- TFITests/MobileShortcutTests.swift | 21 +++++++++++++++++++++ TFM/App/TermindCommands.swift | 6 ------ 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/TFI/App/MobileCommands.swift b/TFI/App/MobileCommands.swift index 8e034ede..be3561a1 100644 --- a/TFI/App/MobileCommands.swift +++ b/TFI/App/MobileCommands.swift @@ -15,7 +15,9 @@ extension FocusedValues { nonisolated enum MobileShortcut { static let settings = KeyboardShortcut(",", modifiers: .command) + static let newWindow = KeyboardShortcut("n", modifiers: .command) static let connect = KeyboardShortcut("t", modifiers: .command) + static let batchStartup = KeyboardShortcut("b", modifiers: [.command, .shift]) static let newChat = KeyboardShortcut("t", modifiers: [.command, .option]) static let newHost = KeyboardShortcut("n", modifiers: [.command, .shift]) static let generateCommand = KeyboardShortcut("i", modifiers: .command) @@ -38,6 +40,8 @@ nonisolated enum MobileShortcut { /// Scene commands mirror the macOS key map while targeting only the frontmost /// iOS/iPadOS scene through `FocusedValues.mobileScene`. struct MobileCommands: Commands { + @Environment(\.openWindow) private var openWindow + @Environment(\.supportsMultipleWindows) private var supportsMultipleWindows @FocusedValue(\.mobileScene) private var scene var body: some Commands { @@ -48,9 +52,20 @@ struct MobileCommands: Commands { } CommandGroup(replacing: .newItem) { + Button(AppStrings.Workspace.newWindow.swiftUIKey, systemImage: "macwindow.badge.plus") { + openWindow(id: TermindIOSApp.mainWindowID) + } + .keyboardShortcut(MobileShortcut.newWindow) + .disabled(!supportsMultipleWindows) + + Divider() + Button(AppStrings.Common.connect.swiftUIKey, systemImage: "network") { scene?.openConnectionPicker() } .keyboardShortcut(MobileShortcut.connect) .disabled(scene == nil) + Button(AppStrings.Vault.batchStartup.swiftUIKey, systemImage: "wind") { scene?.openBatchStartup() } + .keyboardShortcut(MobileShortcut.batchStartup) + .disabled(scene == nil) Button(AppStrings.Assistant.newChat.swiftUIKey, systemImage: "plus.bubble") { scene?.openNewConversation() } diff --git a/TFI/App/TermindIOSApp.swift b/TFI/App/TermindIOSApp.swift index 41a86d38..418b4882 100644 --- a/TFI/App/TermindIOSApp.swift +++ b/TFI/App/TermindIOSApp.swift @@ -3,6 +3,8 @@ import TermindApplication @main struct TermindIOSApp: App { + static let mainWindowID = "main" + @UIApplicationDelegateAdaptor(MobileAppDelegate.self) private var appDelegate private let startup: Result @@ -16,7 +18,7 @@ struct TermindIOSApp: App { } var body: some Scene { - WindowGroup(makeContent: MobileStartupView.sceneContent(for: startup)) + WindowGroup(id: Self.mainWindowID, makeContent: MobileStartupView.sceneContent(for: startup)) .commands { MobileCommands() } } } diff --git a/TFITests/MobileShortcutTests.swift b/TFITests/MobileShortcutTests.swift index b3edb20d..d0430256 100644 --- a/TFITests/MobileShortcutTests.swift +++ b/TFITests/MobileShortcutTests.swift @@ -12,7 +12,9 @@ import struct TermindDomain.Host struct MobileShortcutTests { @Test func shortcutMapMatchesDesktopAndHasNoDuplicates() { #expect(MobileShortcut.settings == KeyboardShortcut(",", modifiers: .command)) + #expect(MobileShortcut.newWindow == KeyboardShortcut("n", modifiers: .command)) #expect(MobileShortcut.connect == KeyboardShortcut("t", modifiers: .command)) + #expect(MobileShortcut.batchStartup == KeyboardShortcut("b", modifiers: [.command, .shift])) #expect(MobileShortcut.newChat == KeyboardShortcut("t", modifiers: [.command, .option])) #expect(MobileShortcut.newHost == KeyboardShortcut("n", modifiers: [.command, .shift])) #expect(MobileShortcut.generateCommand == KeyboardShortcut("i", modifiers: .command)) @@ -30,7 +32,9 @@ struct MobileShortcutTests { let shortcuts = [ MobileShortcut.settings, + MobileShortcut.newWindow, MobileShortcut.connect, + MobileShortcut.batchStartup, MobileShortcut.newChat, MobileShortcut.newHost, MobileShortcut.generateCommand, @@ -82,6 +86,23 @@ struct MobileShortcutTests { #expect(scene.isConnectionPickerPresented) } + @Test func batchStartupCommandPresentsThePickerOnlyInItsScene() throws { + let app = try MobileAppModel(inMemory: true) + let scene = MobileSceneModel(app: app, sessionID: "first") + let otherScene = MobileSceneModel(app: app, sessionID: "second") + scene.openTransfers() + otherScene.openConnectionPicker() + + scene.openBatchStartup() + + #expect(scene.selectedDestination == .workspaces) + #expect(scene.workspaceDestination == nil) + #expect(scene.workspaceSheet == .batchStartup) + #expect(otherScene.selectedDestination == .workspaces) + #expect(otherScene.workspaceSheet == .connectionPicker) + #expect(app.workspaces.isEmpty) + } + @Test func homeScreenHistoryActionOpensThePreferredTerminalAndReportsAMissingHost() throws { let app = try MobileAppModel(inMemory: true) let host = Host(name: "Files", host: "files.example.invalid") diff --git a/TFM/App/TermindCommands.swift b/TFM/App/TermindCommands.swift index a235b845..fd638316 100644 --- a/TFM/App/TermindCommands.swift +++ b/TFM/App/TermindCommands.swift @@ -170,12 +170,6 @@ struct TermindCommands: Commands { } .termindShortcut("w") .disabled(window == nil) - Button(AppStrings.Workspace.closeWindow.swiftUIKey) { - guard let window else { return } - app.mainWindows.closeWindow(in: window) - } - .termindShortcut("w", modifiers: [.command, .shift]) - .disabled(window == nil) Button(AppStrings.Workspace.closeOtherWorkspaces.swiftUIKey) { guard let window, let id = window.selectedWorkspaceID else { return } window.closeOthers(than: id)