diff --git a/Sources/AppBundle/command/MoveMouseCommand.swift b/Sources/AppBundle/command/MoveMouseCommand.swift new file mode 100644 index 00000000..f30cbbcf --- /dev/null +++ b/Sources/AppBundle/command/MoveMouseCommand.swift @@ -0,0 +1,51 @@ +import AppKit +import Common + +struct MoveMouseCommand: Command { + let args: MoveMouseCmdArgs + + func _run(_ state: CommandMutableState, stdin: String) -> Bool { + check(Thread.current.isMainThread) + // todo bug it's bad that we operate on the "ax physical" state directly. command seq won't work correctly + // focus command has the similar problem + let mouse = mouseLocation + let point: Result = switch args.mouseTarget.val { + case .windowLazyCenter: + windowSubjectRect(state) + .flatMap { $0.takeIf { !$0.contains(mouse) }.orFailure("The mouse already belongs to the window") } + .map(\.center) + case .windowForceCenter: + windowSubjectRect(state).map(\.center) + case .monitorLazyCenter: + Result.success(state.subject.workspace.workspaceMonitor.rect) + .flatMap { $0.takeIf { !$0.contains(mouse) }.orFailure("The mouse already belongs to the monitor") } + .map(\.center) + case .monitorForceCenter: + .success(state.subject.workspace.workspaceMonitor.rect.center) + } + switch point { + case .success(let point): + CGEvent( + mouseEventSource: nil, + mouseType: CGEventType.mouseMoved, + mouseCursorPosition: point, + mouseButton: CGMouseButton.left + )?.post(tap: CGEventTapLocation.cghidEventTap) + return true + case .failure(let msg): + return state.failCmd(msg: msg) + } + } +} + +private func windowSubjectRect(_ state: CommandMutableState) -> Result { + if let window: Window = state.subject.windowOrNil { + if let rect = window.getRect() { + return .success(rect) + } else { + return .failure("Failed to get rect of window '\(window.windowId)'") + } + } else { + return .failure(noWindowIsFocused) + } +} diff --git a/Sources/AppBundle/command/other/parseCommand.swift b/Sources/AppBundle/command/other/parseCommand.swift index 17681ade..e88e463a 100644 --- a/Sources/AppBundle/command/other/parseCommand.swift +++ b/Sources/AppBundle/command/other/parseCommand.swift @@ -63,12 +63,14 @@ extension CmdArgs { command = MacosNativeMinimizeCommand(args: self as! MacosNativeMinimizeCmdArgs) case .mode: command = ModeCommand(args: self as! ModeCmdArgs) + case .move: + command = MoveCommand(args: self as! MoveCmdArgs) + case .moveMouse: + command = MoveMouseCommand(args: self as! MoveMouseCmdArgs) case .moveNodeToMonitor: command = MoveNodeToMonitorCommand(args: self as! MoveNodeToMonitorCmdArgs) case .moveNodeToWorkspace: command = MoveNodeToWorkspaceCommand(args: self as! MoveNodeToWorkspaceCmdArgs) - case .move: - command = MoveCommand(args: self as! MoveCmdArgs) case .moveWorkspaceToMonitor: command = MoveWorkspaceToMonitorCommand(args: self as! MoveWorkspaceToMonitorCmdArgs) case .reloadConfig: diff --git a/Sources/Cli/subcommandDescriptionsGenerated.swift b/Sources/Cli/subcommandDescriptionsGenerated.swift index 449c60ac..2497c5bb 100644 --- a/Sources/Cli/subcommandDescriptionsGenerated.swift +++ b/Sources/Cli/subcommandDescriptionsGenerated.swift @@ -21,6 +21,7 @@ let subcommandDescriptions = [ [" macos-native-fullscreen", "Toggle macOS fullscreen for the focused window"], [" macos-native-minimize", "Toggle macOS minimize for the focused window"], [" mode", "Activate the specified binding mode"], + [" move-mouse", "Move mouse to the requested position"], [" move-node-to-monitor", "Move window to monitor targeted by relative direction, by order, or by pattern"], [" move-node-to-workspace", "Move the focused window to the specified workspace"], [" move-workspace-to-monitor", "Move the focused workspace to the next or previous monitor"], diff --git a/Sources/Common/cmdArgs/MoveMouseCmdArgs.swift b/Sources/Common/cmdArgs/MoveMouseCmdArgs.swift new file mode 100644 index 00000000..de8abef9 --- /dev/null +++ b/Sources/Common/cmdArgs/MoveMouseCmdArgs.swift @@ -0,0 +1,33 @@ +public struct MoveMouseCmdArgs: CmdArgs, RawCmdArgs { + public let rawArgs: EquatableNoop<[String]> + init(rawArgs: [String]) { self.rawArgs = .init(rawArgs) } + public static let parser: CmdParser = cmdParser( + kind: .moveMouse, + allowInConfig: true, + help: """ + USAGE: move-mouse [-h|--help] + + OPTIONS: + -h, --help Print help + + ARGUMENTS: + Position to move mouse to. See the man page for the possible values. + """, + options: [:], + arguments: [newArgParser(\.mouseTarget, parseMouseTarget, mandatoryArgPlaceholder: "")] + ) + + public var mouseTarget: Lateinit = .uninitialized +} + +func parseMouseTarget(arg: String, nextArgs: inout [String]) -> Parsed { + parseEnum(arg, MouseTarget.self) +} + +public enum MouseTarget: String, CaseIterable { + case monitorLazyCenter = "monitor-lazy-center" + case monitorForceCenter = "monitor-force-center" + + case windowLazyCenter = "window-lazy-center" + case windowForceCenter = "window-force-center" +} diff --git a/Sources/Common/cmdArgs/other/CmdKind.swift b/Sources/Common/cmdArgs/other/CmdKind.swift index b533574c..7154470b 100644 --- a/Sources/Common/cmdArgs/other/CmdKind.swift +++ b/Sources/Common/cmdArgs/other/CmdKind.swift @@ -1,5 +1,6 @@ public enum CmdKind: String, CaseIterable, Equatable { // Sorted + case balanceSizes = "balance-sizes" case close case closeAllWindowsButCurrent = "close-all-windows-but-current" @@ -23,6 +24,7 @@ public enum CmdKind: String, CaseIterable, Equatable { case macosNativeMinimize = "macos-native-minimize" case mode case move = "move" + case moveMouse = "move-mouse" case moveNodeToMonitor = "move-node-to-monitor" case moveNodeToWorkspace = "move-node-to-workspace" case moveWorkspaceToMonitor = "move-workspace-to-monitor" diff --git a/Sources/Common/cmdArgs/other/parseCmdArgs.swift b/Sources/Common/cmdArgs/other/parseCmdArgs.swift index 3b313ecf..235753b1 100644 --- a/Sources/Common/cmdArgs/other/parseCmdArgs.swift +++ b/Sources/Common/cmdArgs/other/parseCmdArgs.swift @@ -58,14 +58,16 @@ private func initSubcommands() -> [String: any SubCommandParserProtocol] { result[kind.rawValue] = defaultSubCommandParser(MacosNativeMinimizeCmdArgs.init) case .mode: result[kind.rawValue] = defaultSubCommandParser(ModeCmdArgs.init) - case .moveNodeToMonitor: - result[kind.rawValue] = SubCommandParser(parseMoveNodeToMonitorCmdArgs) - case .moveNodeToWorkspace: - result[kind.rawValue] = SubCommandParser(parseMoveNodeToWorkspaceCmdArgs) case .move: result[kind.rawValue] = SubCommandParser(parseMoveCmdArgs) // deprecated result["move-through"] = SubCommandParser(parseMoveCmdArgs) + case .moveMouse: + result[kind.rawValue] = defaultSubCommandParser(MoveMouseCmdArgs.init) + case .moveNodeToMonitor: + result[kind.rawValue] = SubCommandParser(parseMoveNodeToMonitorCmdArgs) + case .moveNodeToWorkspace: + result[kind.rawValue] = SubCommandParser(parseMoveNodeToWorkspaceCmdArgs) case .moveWorkspaceToMonitor: result[kind.rawValue] = defaultSubCommandParser(MoveWorkspaceToMonitorCmdArgs.init) // deprecated diff --git a/docs/aerospace-move-mouse.adoc b/docs/aerospace-move-mouse.adoc new file mode 100644 index 00000000..45a05953 --- /dev/null +++ b/docs/aerospace-move-mouse.adoc @@ -0,0 +1,59 @@ += aerospace-move-mouse(1) +include::./util/man-attributes.adoc[] +// tag::purpose[] +:manpurpose: Move mouse to the requested position +// end::purpose[] +:manname: aerospace-move-mouse + +// =========================================================== Synopsis +== Synopsis +// tag::synopsis[] +aerospace move-mouse [-h|--help] +// end::synopsis[] + +// =========================================================== Description +== Description + +// tag::body[] +{manpurpose} + +// =========================================================== Options +include::./util/conditional-options-header.adoc[] + +-h, --help:: Print help + +// =========================================================== Arguments +include::./util/conditional-arguments-header.adoc[] + +:: +Position to move mouse to. +Possible values: ++ +[cols="1,3"] +|=== +|Value |Description + +|`monitor-lazy-center` +|Move mouse to the center of the focused monitor, *unless* it is already within the monitor boundaries + +|`monitor-force-center` +|Move mouse to the center of the focused monitor + +|`window-lazy-center` +|Move mouse to the center of the focused window, *unless* it is already within the window boundaries + +|`window-force-center` +|Move mouse to the center of the focused window + +|=== + +// =========================================================== Examples +include::util/conditional-examples-header.adoc[] + +* Try to move mouse to the center of the window. If there is no window in focus, move mouse to the center of the monitor: + +`aerospace move-mouse window-lazy-center || aerospace move-mouse monitor-lazy-center` + +// end::body[] + +// =========================================================== Footer +include::./util/man-footer.adoc[] diff --git a/docs/aerospace-trigger-binding.adoc b/docs/aerospace-trigger-binding.adoc index 9fdc2f1e..7b678868 100644 --- a/docs/aerospace-trigger-binding.adoc +++ b/docs/aerospace-trigger-binding.adoc @@ -34,9 +34,9 @@ include::util/conditional-arguments-header.adoc[] // =========================================================== Examples include::util/conditional-examples-header.adoc[] -* Run alphabetically first binding from config (useless and synthetic example) + +* Run alphabetically first binding from config (useless and synthetic example): + `aerospace trigger-binding --mode main "$(aerospace config --get mode.main.binding --keys | head -1)"` -* Trigger `alt-tab` binding + +* Trigger `alt-tab` binding: + `aerospace trigger-binding --mode main alt-tab` // end::body[] diff --git a/docs/commands.adoc b/docs/commands.adoc index 9ade1908..a0bf03b6 100644 --- a/docs/commands.adoc +++ b/docs/commands.adoc @@ -119,6 +119,13 @@ include::aerospace-move.adoc[tags=synopsis] include::aerospace-move.adoc[tags=purpose] include::aerospace-move.adoc[tags=body] +== move-mouse +---- +include::./aerospace-move-mouse.adoc[tags=synopsis] +---- +include::./aerospace-move-mouse.adoc[tags=purpose] +include::./aerospace-move-mouse.adoc[tags=body] + == move-node-to-monitor ---- include::aerospace-move-node-to-monitor.adoc[tags=synopsis] diff --git a/grammar/commands-bnf-grammar.txt b/grammar/commands-bnf-grammar.txt index 7e892e8f..68685e4a 100644 --- a/grammar/commands-bnf-grammar.txt +++ b/grammar/commands-bnf-grammar.txt @@ -36,6 +36,8 @@ aerospace -h; | move (left|down|up|right) + | move-mouse (monitor-lazy-center|monitor-force-center|window-lazy-center|window-force-center) + | move-node-to-monitor [--wrap-around] (left|down|up|right) [--wrap-around] | move-node-to-monitor [--wrap-around] (next|prev) [--wrap-around] | move-node-to-monitor ...