Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add soft default assigned monitor #853

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ struct MoveWorkspaceToMonitorCommand: Command {
let prevMonitor = focusedWorkspace.workspaceMonitor
let sortedMonitors = sortedMonitors
guard let index = sortedMonitors.firstIndex(where: { $0.rect.topLeftCorner == prevMonitor.rect.topLeftCorner }) else { return false }
let targetMonitor = args.wrapAround
? sortedMonitors.get(wrappingIndex: args.target.val == .next ? index + 1 : index - 1)
: sortedMonitors.getOrNil(atIndex: args.target.val == .next ? index + 1 : index - 1)
let targetMonitor = args.target.val == .reset
? focusedWorkspace.forceAssignedMonitor ?? focusedWorkspace.defaultAssignedMonitor ?? mainMonitor
: args.wrapAround
? sortedMonitors.get(wrappingIndex: args.target.val == .next ? index + 1 : index - 1)
: sortedMonitors.getOrNil(atIndex: args.target.val == .next ? index + 1 : index - 1)
guard let targetMonitor else { return false }

if targetMonitor.monitorAppKitNsScreenScreensId == prevMonitor.monitorAppKitNsScreenScreensId {
return false
}

if targetMonitor.setActiveWorkspace(focusedWorkspace) {
let stubWorkspace = getStubWorkspace(for: prevMonitor)
check(prevMonitor.setActiveWorkspace(stubWorkspace),
Expand Down
1 change: 1 addition & 0 deletions Sources/AppBundle/config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct Config: Copyable {

var gaps: Gaps = .zero
var workspaceToMonitorForceAssignment: [String: [MonitorDescription]] = [:]
var workspaceToMonitorDefaultAssignment: [String: [MonitorDescription]] = [:]
var modes: [String: Mode] = [:]
var onWindowDetected: [WindowDetectedCallback] = []

Expand Down
1 change: 1 addition & 0 deletions Sources/AppBundle/config/parseConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ private let configParser: [String: any ParserProtocol<Config>] = [

"gaps": Parser(\.gaps, parseGaps),
"workspace-to-monitor-force-assignment": Parser(\.workspaceToMonitorForceAssignment, parseWorkspaceToMonitorAssignment),
"workspace-to-monitor-default-assignment": Parser(\.workspaceToMonitorDefaultAssignment, parseWorkspaceToMonitorAssignment),
"on-window-detected": Parser(\.onWindowDetected, parseOnWindowDetectedArray),

// Deprecated
Expand Down
1 change: 1 addition & 0 deletions Sources/AppBundle/tree/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ extension Workspace {
forceAssignedMonitor
?? visibleWorkspaceToScreenPoint[self]?.monitorApproximation
?? assignedMonitorPoint?.monitorApproximation
?? defaultAssignedMonitor
?? mainMonitor
}
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/AppBundle/tree/WorkspaceEx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,12 @@ extension Workspace {
.compactMap { $0.resolveMonitor(sortedMonitors: sortedMonitors) }
.first
}

var defaultAssignedMonitor: Monitor? {
guard let monitorDescriptions = config.workspaceToMonitorDefaultAssignment[name] else { return nil }
let sortedMonitors = sortedMonitors
return monitorDescriptions.lazy
.compactMap { $0.resolveMonitor(sortedMonitors: sortedMonitors) }
.first
}
}
38 changes: 38 additions & 0 deletions Sources/AppBundleTests/config/ConfigTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,44 @@ final class ConfigTest: XCTestCase {
assertEquals([:], defaultConfig.workspaceToMonitorForceAssignment)
}

func testParseWorkspaceToMonitorDefaultAssignment() {
let (parsed, errors) = parseConfig(
"""
[workspace-to-monitor-default-assignment]
workspace_name_1 = 1 # Sequence number of the monitor (from left to right, 1-based indexing)
workspace_name_2 = 'main' # main monitor
workspace_name_3 = 'secondary' # non-main monitor (in case when there are only two monitors)
workspace_name_4 = 'built-in' # case insensitive regex substring
workspace_name_5 = '^built-in retina display$' # case insensitive regex match
workspace_name_6 = ['secondary', 1] # you can specify multiple patterns. The first matching pattern will be used
7 = "foo"
w7 = ['', 'main']
w8 = 0
workspace_name_x = '2' # Sequence number of the monitor (from left to right, 1-based indexing)
"""
)
assertEquals(
parsed.workspaceToMonitorDefaultAssignment,
[
"workspace_name_1": [.sequenceNumber(1)],
"workspace_name_2": [.main],
"workspace_name_3": [.secondary],
"workspace_name_4": [.pattern("built-in")!],
"workspace_name_5": [.pattern("^built-in retina display$")!],
"workspace_name_6": [.secondary, .sequenceNumber(1)],
"workspace_name_x": [.sequenceNumber(2)],
"7": [.pattern("foo")!],
"w7": [.main],
"w8": [],
]
)
assertEquals([
"workspace-to-monitor-default-assignment.w7[0]: Empty string is an illegal monitor description",
"workspace-to-monitor-default-assignment.w8: Monitor sequence numbers uses 1-based indexing. Values less than 1 are illegal",
], errors.descriptions)
assertEquals([:], defaultConfig.workspaceToMonitorDefaultAssignment)
}

func testParseOnWindowDetected() {
let (parsed, errors) = parseConfig(
"""
Expand Down
2 changes: 1 addition & 1 deletion Sources/Cli/subcommandDescriptionsGenerated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ let subcommandDescriptions = [
[" 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."],
[" move-workspace-to-monitor", "Move the focused workspace monitor by order, or resets to the default monitor."],
[" move", "Move the focused window in the given direction"],
[" reload-config", "Reload currently active config"],
[" resize", "Resize the focused window"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ public struct MoveWorkspaceToMonitorCmdArgs: CmdArgs {
"--wrap-around": trueBoolFlag(\.wrapAround),
"--workspace": optionalWorkspaceFlag(),
],
arguments: [newArgParser(\.target, parseMonitorTarget, mandatoryArgPlaceholder: "(next|prev)")]
arguments: [newArgParser(\.target, parseMonitorTarget, mandatoryArgPlaceholder: "(next|prev|reset)")]
)

public var windowId: UInt32?
public var workspaceName: WorkspaceName?
public var wrapAround: Bool = false
public var target: Lateinit<MoveWorkspaceToMonitorCmdArgs.MonitorTarget> = .uninitialized
public enum MonitorTarget: String, CaseIterable {
case next, prev
case next, prev, reset
}
}

Expand Down
1 change: 1 addition & 0 deletions Sources/Common/cmdHelpGenerated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ let move_node_to_workspace_help_generated = """
"""
let move_workspace_to_monitor_help_generated = """
USAGE: move-workspace-to-monitor [-h|--help] [--workspace <workspace>] [--wrap-around] (next|prev)
OR: move-workspace-to-monitor [-h|--help] [--workspace <workspace>] (reset)
"""
let move_help_generated = """
USAGE: move [-h|--help] [--window-id <window-id>] (left|down|up|right)
Expand Down
7 changes: 6 additions & 1 deletion docs/aerospace-move-workspace-to-monitor.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
include::util/man-attributes.adoc[]
:manname: aerospace-move-workspace-to-monitor
// tag::purpose[]
:manpurpose: Move the focused workspace to the next or previous monitor.
:manpurpose: Move the focused workspace monitor by order, or resets to the default monitor.
// end::purpose[]

// =========================================================== Synopsis
== Synopsis
[verse]
// tag::synopsis[]
aerospace move-workspace-to-monitor [-h|--help] [--workspace <workspace>] [--wrap-around] (next|prev)
aerospace move-workspace-to-monitor [-h|--help] [--workspace <workspace>] (reset)

// end::synopsis[]

Expand Down Expand Up @@ -38,6 +39,10 @@ include::./util/conditional-arguments-header.adoc[]
Move the workspace to next or prev monitor.
'next' or 'prev' monitor is calculated relative to the monitor `<workspace>` currently belongs to.

(reset)::
Move the workspace to the xref:guide.adoc#assign-workspaces-to-monitors[default monitor].
Will default to the 'main' monitor if no default monitor is defined.

// end::body[]

// =========================================================== Footer
Expand Down
20 changes: 16 additions & 4 deletions docs/guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -643,8 +643,14 @@ When you switch to an empty workspace, AeroSpace puts the workspace on an assign
[#assign-workspaces-to-monitors]
=== Assign workspaces to monitors

You can use `workspace-to-monitor-force-assignment` syntax to assign
workspaces to always appear on particular monitors
You can use `workspace-to-monitor-force-assignment` and `workspace-to-monitor-default-assignment` syntax to assign
workspaces to particular monitors.

The default is a soft assignment, which means workspaces can still be moved to different monitors.
The workspace can always be reset to the default assigned monitor using the xref:commands.adoc#move-workspace-to-monitor[move-workspace-to-monitor] reset command.
The xref:commands.adoc#move-workspace-to-monitor[move-workspace-to-monitor] command has no effect for workspaces that have forced monitor assignment

`workspace-to-monitor-force-assignment` will always have preference over `workspace-to-monitor-default-assignment`.

[source,toml]
----
Expand All @@ -655,6 +661,14 @@ workspaces to always appear on particular monitors
4 = 'built-in' # Case insensitive regex substring
5 = '^built-in retina display$' # Case insensitive regex match
6 = ['secondary', 'dell'] # You can specify multiple patterns. The first matching pattern will be used

[workspace-to-monitor-default-assignment]
1 = 1 # Monitor sequence number from left to right. 1-based indexing
2 = 'main' # Main monitor
3 = 'secondary' # Non-main monitor in case when there are only two monitors
4 = 'built-in' # Case insensitive regex substring
5 = '^built-in retina display$' # Case insensitive regex match
6 = ['secondary', 'dell'] # You can specify multiple patterns. The first matching pattern will be used
----

* Left hand side of the assignment is the workspace name
Expand All @@ -670,8 +684,6 @@ Supported monitor patterns:
You can specify multiple patterns as an array.
The first matching pattern will be used

xref:commands.adoc#move-workspace-to-monitor[move-workspace-to-monitor] command has no effect for workspaces that have monitor assignment

== Dialog heuristics

* Apple provides accessibility API for apps to let others know which of their windows are dialogs
Expand Down
Loading