From 56734b2c7154372f0c913fc352e349d6f76a3513 Mon Sep 17 00:00:00 2001 From: "Kevin J. Lynagh" Date: Sat, 9 Nov 2024 22:16:53 +0100 Subject: [PATCH] Add `--when-visible (focus|swap)` option to the summon-workspace command. What should happen when the summoned workspace is visible on another monitor? This option introduces a choice to either: - `focus` the monitor on which the target workspace is already visible (the current behavior) - `swap` the workspaces between the monitors, so that the summoned workspace appears on the focused monitor. The default is `focus`, for backwards compatibility. Ref: https://github.com/nikitabobko/AeroSpace/issues/603 --- .../command/impl/SummonWorkspaceCommand.swift | 29 +++++++++++++++---- .../cmdArgs/impl/SummonWorkspaceCmdArgs.swift | 21 ++++++++++++++ Sources/Common/cmdHelpGenerated.swift | 2 +- docs/aerospace-summon-workspace.adoc | 7 ++++- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift b/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift index d2d66ac0..316dd47b 100644 --- a/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift +++ b/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift @@ -7,15 +7,34 @@ struct SummonWorkspaceCommand: Command { func run(_ env: CmdEnv, _ io: CmdIo) -> Bool { check(Thread.current.isMainThread) let workspace = Workspace.get(byName: args.target.val.raw) - let monitor = focus.workspace.workspaceMonitor - if monitor.activeWorkspace == workspace { + let focusedMonitor = focus.workspace.workspaceMonitor + + if focusedMonitor.activeWorkspace == workspace { io.err("Workspace '\(workspace.name)' is already visible on the focused monitor. Tip: use --fail-if-noop to exit with non-zero code") return !args.failIfNoop } - if monitor.setActiveWorkspace(workspace) { - return workspace.focusWorkspace() + + if !workspace.isVisible { + // then we just need to summon the workspace to the focused monitor + if focusedMonitor.setActiveWorkspace(workspace) { + return workspace.focusWorkspace() + } else { + return io.err("Can't move workspace '\(workspace.name)' to monitor '\(focusedMonitor.name)'. workspace-to-monitor-force-assignment doesn't allow it") + } } else { - return io.err("Can't move workspace '\(workspace.name)' to monitor '\(monitor.name)'. workspace-to-monitor-force-assignment doesn't allow it") + let otherMonitor = workspace.workspaceMonitor + let currentWorkspace = focusedMonitor.activeWorkspace + + switch args.whenVisible { + case .swap: + if otherMonitor.setActiveWorkspace(currentWorkspace) && focusedMonitor.setActiveWorkspace(workspace) { + return workspace.focusWorkspace() + } else { + return io.err("Can't swap workspaces due to monitor force assignment restrictions") + } + case .focus: + return workspace.focusWorkspace() + } } } } diff --git a/Sources/Common/cmdArgs/impl/SummonWorkspaceCmdArgs.swift b/Sources/Common/cmdArgs/impl/SummonWorkspaceCmdArgs.swift index 8feb2fe2..c3444a57 100644 --- a/Sources/Common/cmdArgs/impl/SummonWorkspaceCmdArgs.swift +++ b/Sources/Common/cmdArgs/impl/SummonWorkspaceCmdArgs.swift @@ -1,3 +1,5 @@ +private let actio = "" + public struct SummonWorkspaceCmdArgs: CmdArgs { public let rawArgs: EquatableNoop<[String]> public init(rawArgs: [String]) { self.rawArgs = .init(rawArgs) } @@ -7,6 +9,7 @@ public struct SummonWorkspaceCmdArgs: CmdArgs { help: summon_workspace_help_generated, options: [ "--fail-if-noop": trueBoolFlag(\.failIfNoop), + "--when-visible": ArgParser(\.rawWhenVisibleAction, upcastArgParserFun(parseWhenVisibleAction)), ], arguments: [newArgParser(\.target, parseWorkspaceName, mandatoryArgPlaceholder: "")] ) @@ -16,8 +19,26 @@ public struct SummonWorkspaceCmdArgs: CmdArgs { public var target: Lateinit = .uninitialized public var failIfNoop: Bool = false + public var rawWhenVisibleAction: WhenVisible? = nil + + public enum WhenVisible: String, CaseIterable, Equatable { + case focus = "focus" + case swap = "swap" + } +} + +public extension SummonWorkspaceCmdArgs { + var whenVisible: WhenVisible { rawWhenVisibleAction ?? .focus } } private func parseWorkspaceName(arg: String, nextArgs: inout [String]) -> Parsed { WorkspaceName.parse(arg) } + +private func parseWhenVisibleAction(arg: String, nextArgs: inout [String]) -> Parsed { + if let arg = nextArgs.nextNonFlagOrNil() { + return parseEnum(arg, SummonWorkspaceCmdArgs.WhenVisible.self) + } else { + return .failure("\(actio) is mandatory") + } +} diff --git a/Sources/Common/cmdHelpGenerated.swift b/Sources/Common/cmdHelpGenerated.swift index e27a6b68..8b2fd3a3 100644 --- a/Sources/Common/cmdHelpGenerated.swift +++ b/Sources/Common/cmdHelpGenerated.swift @@ -121,7 +121,7 @@ let split_help_generated = """ USAGE: split [-h|--help] [--window-id ] (horizontal|vertical|opposite) """ let summon_workspace_help_generated = """ - USAGE: summon-workspace [-h|--help] [--fail-if-noop] + USAGE: summon-workspace [-h|--help] [--fail-if-noop] [--when-visible (focus|swap)] """ let trigger_binding_help_generated = """ USAGE: trigger-binding [-h|--help] --mode diff --git a/docs/aerospace-summon-workspace.adoc b/docs/aerospace-summon-workspace.adoc index 67f7388d..ca6b3f82 100644 --- a/docs/aerospace-summon-workspace.adoc +++ b/docs/aerospace-summon-workspace.adoc @@ -9,7 +9,7 @@ include::util/man-attributes.adoc[] == Synopsis [verse] // tag::synopsis[] -aerospace summon-workspace [-h|--help] [--fail-if-noop] +aerospace summon-workspace [-h|--help] [--fail-if-noop] [--when-visible (focus|swap)] // end::synopsis[] @@ -30,6 +30,11 @@ include::./util/conditional-options-header.adoc[] -h, --help:: Print help --fail-if-noop:: Exit with non-zero exit code if the workspace already visible on the focused monitor. +--when-visible :: +Defines the behavior if the workspace is already visible on another monitor. +`` possible values: `(focus|swap)`. + +The default is: `focus` + // =========================================================== Arguments include::./util/conditional-arguments-header.adoc[]