Skip to content

Commit

Permalink
Implement on-focus-changed and on-focused-monitor-changed callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitabobko committed Jul 21, 2024
1 parent 2fa4be0 commit 5d5969a
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 11 deletions.
6 changes: 5 additions & 1 deletion Sources/AppBundle/config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@ struct Config: Copyable {
var startAtLogin: Bool = false
var accordionPadding: Int = 30
var enableNormalizationOppositeOrientationForNestedContainers: Bool = true
var execOnWorkspaceChange: [String] = []
var execOnWorkspaceChange: [String] = [] // todo deprecate
var keyMapping = KeyMapping()
var execConfig: ExecConfig = ExecConfig()

var onFocusChanged: [any Command] = []
// var onFocusedWorkspaceChanged: [any Command] = []
var onFocusedMonitorChanged: [any Command] = []

var gaps: Gaps = .zero
var workspaceToMonitorForceAssignment: [String: [MonitorDescription]] = [:]
var modes: [String: Mode] = [:]
Expand Down
4 changes: 4 additions & 0 deletions Sources/AppBundle/config/parseConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ private let configParser: [String: any ParserProtocol<Config>] = [
"after-login-command": Parser(\.afterLoginCommand, { parseCommandOrCommands($0).toParsedToml($1) }),
"after-startup-command": Parser(\.afterStartupCommand, { parseCommandOrCommands($0).toParsedToml($1) }),

"on-focus-changed": Parser(\.onFocusChanged, { parseCommandOrCommands($0).toParsedToml($1) }),
"on-focused-monitor-changed": Parser(\.onFocusedMonitorChanged, { parseCommandOrCommands($0).toParsedToml($1) }),
// "on-focused-workspace-changed": Parser(\.onFocusedWorkspaceChanged, { parseCommandOrCommands($0).toParsedToml($1) }),

"enable-normalization-flatten-containers": Parser(\.enableNormalizationFlattenContainers, parseBool),
"enable-normalization-opposite-orientation-for-nested-containers": Parser(\.enableNormalizationOppositeOrientationForNestedContainers, parseBool),

Expand Down
38 changes: 28 additions & 10 deletions Sources/AppBundle/focus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,38 @@ var prevFocusedWorkspace: Workspace? { _prevFocusedWorkspaceName.map { Workspace
var _prevFocus: FrozenFocus? = nil
var prevFocus: LiveFocus? { _prevFocus?.live }

// Should be called in refreshSession
func checkOnFocusChangedCallbacks() {
let focus = focus.frozen
if focus.workspaceName != _lastKnownFocus.workspaceName {
onWorkspaceChanged(_lastKnownFocus.workspaceName, focus.workspaceName)
_prevFocusedWorkspaceName = _lastKnownFocus.workspaceName
let liveFocus = focus
let frozenFocus = liveFocus.frozen
if frozenFocus != _lastKnownFocus {
onFocusChanged(liveFocus)
_prevFocus = _lastKnownFocus
}
if focus.monitorId != _lastKnownFocus.monitorId {
// todo onMonitorChanged
if frozenFocus.workspaceName != _lastKnownFocus.workspaceName {
onWorkspaceChanged(_lastKnownFocus.workspaceName, frozenFocus.workspaceName)
_prevFocusedWorkspaceName = _lastKnownFocus.workspaceName
}
if focus != _lastKnownFocus {
// todo onFocusChanged
_prevFocus = _lastKnownFocus
if frozenFocus.monitorId != _lastKnownFocus.monitorId {
onFocusedMonitorChanged(liveFocus)
}
_lastKnownFocus = focus
_lastKnownFocus = frozenFocus
}

private var onFocusChangedRecursionGuard = false
private func onFocusedMonitorChanged(_ newFocus: LiveFocus) {
if onFocusChangedRecursionGuard { return }
onFocusChangedRecursionGuard = true
defer { onFocusChangedRecursionGuard = false }
if config.onFocusedMonitorChanged.isEmpty { return }
_ = config.onFocusedMonitorChanged.run(CommandMutableState(focus.asLeaf.asCommandSubject))
}
private func onFocusChanged(_ newFocus: LiveFocus) {
if onFocusChangedRecursionGuard { return }
onFocusChangedRecursionGuard = true
defer { onFocusChangedRecursionGuard = false }
if config.onFocusChanged.isEmpty { return }
_ = config.onFocusChanged.run(CommandMutableState(focus.asLeaf.asCommandSubject))
}

private func onWorkspaceChanged(_ oldWorkspace: String, _ newWorkspace: String) {
Expand Down
6 changes: 6 additions & 0 deletions docs/config-examples/default-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ default-root-container-orientation = 'auto'
# See https://nikitabobko.github.io/AeroSpace/guide#key-mapping
key-mapping.preset = 'qwerty'

# Mouse follows focus when focused monitor changes
# Drop it from your config, if you don't like this behavior
# See https://nikitabobko.github.io/AeroSpace/guide#on-focus-changed-callbacks
# See https://nikitabobko.github.io/AeroSpace/commands#move-mouse
on-focused-monitor-changed = ['move-mouse monitor-lazy-center']

# Gaps between windows (inner-*) and between monitor edges (outer-*).
# Possible values:
# - Constant: gaps.outer.top = 8
Expand Down
3 changes: 3 additions & 0 deletions docs/config-examples/i3-like-config-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
enable-normalization-flatten-containers = false
enable-normalization-opposite-orientation-for-nested-containers = false

# Mouse follows focus when focused monitor changes
on-focused-monitor-changed = ['move-mouse monitor-lazy-center']

[mode.main.binding]
alt-enter = 'exec-and-forget open -n /System/Applications/Utilities/Terminal.app'

Expand Down
21 changes: 21 additions & 0 deletions docs/guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,27 @@ check-further-callbacks = true
run = 'layout floating'
----

[#on-focus-changed-callbacks]
=== 'on-focus-changed' callbacks

You can track focus changes using the following callbacks: `on-focus-changed` and `on-focused-monitor-changed`.

* `on-focus-changed` is called every time focused window or workspace changes.
* `on-focused-monitor-changed` is called every time focused monitor changes.

A common use case for the callbacks is to implement "mouse follows focus" behavior. All you need is to combine the callback of your choice with xref:commands.adoc#move-mouse[move-mouse command]:
[source,toml]
----
on-focused-monitor-changed = ['move-mouse monitor-lazy-center']
# or
on-focus-changed = ['move-mouse window-lazy-center']
----

You shouldn't rely on the order callback are called, since it's an implementation detail and can change from version to version.

The callbacks are "recursion resistant", which means that any focus change within the callback won't retrigger the callback.
Changing the focus within these callbacks is a bad idea anyway, and the way it's handled will probably change in future versions.

[#exec-on-workspace-change-callback]
=== 'exec-on-workspace-change' callback

Expand Down

0 comments on commit 5d5969a

Please sign in to comment.