Skip to content

Commit

Permalink
Revive window grab space shifting (#1698)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianyh authored Dec 14, 2024
1 parent 88a2cd7 commit dc6bf0f
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 106 deletions.
61 changes: 41 additions & 20 deletions Amethyst.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
401C35B522482EAF0019ED07 /* WindowTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C35B422482EAF0019ED07 /* WindowTransitionCoordinator.swift */; };
401C35B7224831470019ED07 /* FocusTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C35B6224831470019ED07 /* FocusTransitionCoordinator.swift */; };
401F89BB2B4B5A7900DBA976 /* Silica in Frameworks */ = {isa = PBXBuildFile; productRef = 401F89BA2B4B5A7900DBA976 /* Silica */; };
401F89BE2B4B5B1C00DBA976 /* Silica in Frameworks */ = {isa = PBXBuildFile; productRef = 401F89BD2B4B5B1C00DBA976 /* Silica */; };
4029C4F11C112478001E4788 /* Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4029C4F01C112478001E4788 /* Layout.swift */; };
402DB6E21742E41A00D1C936 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 402DB6E11742E41A00D1C936 /* Cocoa.framework */; };
402DB6EC1742E41A00D1C936 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 402DB6EA1742E41A00D1C936 /* InfoPlist.strings */; };
Expand Down Expand Up @@ -110,15 +109,19 @@
40C3F91F1BD1B22E00F58660 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40C3F91B1BD1B22E00F58660 /* SystemConfiguration.framework */; };
40C3F9231BD1B35E00F58660 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 40C3F9221BD1B35E00F58660 /* libc++.tbd */; };
40C3F9251BD1B36C00F58660 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 40C3F9241BD1B36C00F58660 /* libz.tbd */; };
40C79F872D0A763D002181F1 /* Silica in Frameworks */ = {isa = PBXBuildFile; productRef = 40C79F862D0A763D002181F1 /* Silica */; };
40C79F8A2D0A8871002181F1 /* Silica in Frameworks */ = {isa = PBXBuildFile; productRef = 40C79F892D0A8871002181F1 /* Silica */; };
40CEF4301C2B8D21004C3297 /* ScreenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40CEF42F1C2B8D21004C3297 /* ScreenManager.swift */; };
40CF37C029B440A100CDB07A /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 40CF37BF29B440A100CDB07A /* ArgumentParser */; };
40CF37C229B58C7400CDB07A /* WindowsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40CF37C129B58C7400CDB07A /* WindowsInfo.swift */; };
40CF37C429BAC18300CDB07A /* ScreensInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40CF37C329BAC18300CDB07A /* ScreensInfo.swift */; };
40CF37C629BACD1800CDB07A /* AppsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40CF37C529BACD1800CDB07A /* AppsInfo.swift */; };
40D45D422CFAB4700004ADC3 /* Silica in Frameworks */ = {isa = PBXBuildFile; productRef = 40D45D412CFAB4700004ADC3 /* Silica */; };
40D491D823367590007E0CCB /* RowLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D491D723367590007E0CCB /* RowLayoutTests.swift */; };
40D491DB23367630007E0CCB /* FrameAssignmentVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D491DA23367630007E0CCB /* FrameAssignmentVerification.swift */; };
40D82FF129739C5300F3C18B /* extended.js in Resources */ = {isa = PBXBuildFile; fileRef = 40D82FF029739C5300F3C18B /* extended.js */; };
40DA8B6E27D5AA7300C291AF /* subset.js in Resources */ = {isa = PBXBuildFile; fileRef = 40DA8B6D27D5AA7300C291AF /* subset.js */; };
40DD79E12CFBDA1500395426 /* Silica in Frameworks */ = {isa = PBXBuildFile; productRef = 40DD79E02CFBDA1500395426 /* Silica */; };
40E705D8176EAD7800850DA6 /* default.amethyst in Resources */ = {isa = PBXBuildFile; fileRef = 40E705D7176EAD7800850DA6 /* default.amethyst */; };
40EC47F423F3A30100048B4F /* ScreenManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40EC47F323F3A30100048B4F /* ScreenManagerTests.swift */; };
4493EAA22139D9F000AA9623 /* ThreeColumnLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4493EAA12139D9EF00AA9623 /* ThreeColumnLayout.swift */; };
Expand Down Expand Up @@ -265,7 +268,9 @@
40AE16312A943EF900E14536 /* CoreGraphics.framework in Frameworks */,
40AE15E32A92E9AF00E14536 /* SwiftyBeaver in Frameworks */,
40C3F9231BD1B35E00F58660 /* libc++.tbd in Frameworks */,
40C79F8A2D0A8871002181F1 /* Silica in Frameworks */,
402DB6FF1742E44E00D1C936 /* Carbon.framework in Frameworks */,
40C79F872D0A763D002181F1 /* Silica in Frameworks */,
400D49002A92B0A00082750F /* Yams in Frameworks */,
40AE15E62A92EA7500E14536 /* RxCocoa in Frameworks */,
40C3F91F1BD1B22E00F58660 /* SystemConfiguration.framework in Frameworks */,
Expand All @@ -274,10 +279,11 @@
400D48F72A92AF9B0082750F /* LoginServiceKit in Frameworks */,
402DB6E21742E41A00D1C936 /* Cocoa.framework in Frameworks */,
400D48F42A92AF1E0082750F /* Cartography in Frameworks */,
401F89BE2B4B5B1C00DBA976 /* Silica in Frameworks */,
40D45D422CFAB4700004ADC3 /* Silica in Frameworks */,
40C3F91E1BD1B22E00F58660 /* Security.framework in Frameworks */,
40CF37C029B440A100CDB07A /* ArgumentParser in Frameworks */,
400D48FA2A92B0130082750F /* Sparkle in Frameworks */,
40DD79E12CFBDA1500395426 /* Silica in Frameworks */,
402F6FA62A81C9E30036B512 /* SkyLight.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -621,7 +627,10 @@
40AE15E72A92EA7500E14536 /* RxSwift */,
40AE15F42A92EE8000E14536 /* MASShortcut */,
401F89BA2B4B5A7900DBA976 /* Silica */,
401F89BD2B4B5B1C00DBA976 /* Silica */,
40D45D412CFAB4700004ADC3 /* Silica */,
40DD79E02CFBDA1500395426 /* Silica */,
40C79F862D0A763D002181F1 /* Silica */,
40C79F892D0A8871002181F1 /* Silica */,
);
productName = Amethyst;
productReference = 402DB6DE1742E41A00D1C936 /* Amethyst.app */;
Expand Down Expand Up @@ -694,7 +703,7 @@
40AE15EC2A92EBD800E14536 /* XCRemoteSwiftPackageReference "Quick" */,
40AE15EF2A92EC5300E14536 /* XCRemoteSwiftPackageReference "Nimble" */,
40AE15F32A92EE8000E14536 /* XCRemoteSwiftPackageReference "MASShortcut" */,
401F89BC2B4B5B1C00DBA976 /* XCRemoteSwiftPackageReference "silica" */,
40C79F882D0A8871002181F1 /* XCRemoteSwiftPackageReference "silica" */,
);
productRefGroup = 402DB6DF1742E41A00D1C936 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -756,7 +765,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nswiftlint --fix\nswiftlint\n";
shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nswiftlint --fix && swiftlint\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down Expand Up @@ -1010,7 +1019,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 112;
CURRENT_PROJECT_VERSION = 118;
DEAD_CODE_STRIPPING = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
Expand Down Expand Up @@ -1052,7 +1061,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 112;
CURRENT_PROJECT_VERSION = 118;
DEAD_CODE_STRIPPING = YES;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 82P2XLB4UH;
Expand Down Expand Up @@ -1222,14 +1231,6 @@
minimumVersion = 5.0.0;
};
};
401F89BC2B4B5B1C00DBA976 /* XCRemoteSwiftPackageReference "silica" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ianyh/silica";
requirement = {
branch = master;
kind = branch;
};
};
40AE15E12A92E9AF00E14536 /* XCRemoteSwiftPackageReference "SwiftyBeaver" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SwiftyBeaver/SwiftyBeaver";
Expand Down Expand Up @@ -1270,6 +1271,14 @@
kind = branch;
};
};
40C79F882D0A8871002181F1 /* XCRemoteSwiftPackageReference "silica" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ianyh/silica";
requirement = {
branch = master;
kind = branch;
};
};
40CF37BE29B440A100CDB07A /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-argument-parser.git";
Expand Down Expand Up @@ -1310,11 +1319,6 @@
isa = XCSwiftPackageProductDependency;
productName = Silica;
};
401F89BD2B4B5B1C00DBA976 /* Silica */ = {
isa = XCSwiftPackageProductDependency;
package = 401F89BC2B4B5B1C00DBA976 /* XCRemoteSwiftPackageReference "silica" */;
productName = Silica;
};
40AE15E22A92E9AF00E14536 /* SwiftyBeaver */ = {
isa = XCSwiftPackageProductDependency;
package = 40AE15E12A92E9AF00E14536 /* XCRemoteSwiftPackageReference "SwiftyBeaver" */;
Expand Down Expand Up @@ -1345,11 +1349,28 @@
package = 40AE15F32A92EE8000E14536 /* XCRemoteSwiftPackageReference "MASShortcut" */;
productName = MASShortcut;
};
40C79F862D0A763D002181F1 /* Silica */ = {
isa = XCSwiftPackageProductDependency;
productName = Silica;
};
40C79F892D0A8871002181F1 /* Silica */ = {
isa = XCSwiftPackageProductDependency;
package = 40C79F882D0A8871002181F1 /* XCRemoteSwiftPackageReference "silica" */;
productName = Silica;
};
40CF37BF29B440A100CDB07A /* ArgumentParser */ = {
isa = XCSwiftPackageProductDependency;
package = 40CF37BE29B440A100CDB07A /* XCRemoteSwiftPackageReference "swift-argument-parser" */;
productName = ArgumentParser;
};
40D45D412CFAB4700004ADC3 /* Silica */ = {
isa = XCSwiftPackageProductDependency;
productName = Silica;
};
40DD79E02CFBDA1500395426 /* Silica */ = {
isa = XCSwiftPackageProductDependency;
productName = Silica;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 402DB6D61742E41A00D1C936 /* Project object */;
Expand Down
4 changes: 2 additions & 2 deletions Amethyst.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "43f1b1e9bfd866a37b073557f24d496c85ab3a7508ae523528cda0c3afb30aa9",
"originHash" : "dacd754e4470b2e150237d6958482482a937b9ccadab57ae7aab15ece775f80c",
"pins" : [
{
"identity" : "cartography",
Expand Down Expand Up @@ -79,7 +79,7 @@
"location" : "https://github.com/ianyh/silica",
"state" : {
"branch" : "master",
"revision" : "4b0e9f62c1ea97a2b97dbcf214be4209dc9d7a71"
"revision" : "27773e60601453e9a65729b60fa5efc3322d99e0"
}
},
{
Expand Down
35 changes: 9 additions & 26 deletions Amethyst/Managers/WindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ extension WindowManager: WindowTransitionTarget {
}
markScreen(screen, forReflowWithChange: .add(window: window))
window.focus()
case let .moveWindowToSpaceAtIndex(window, spaceIndex):
case let .moveWindowToSpaceAtIndex(window, spaceIndex, sourceSpaceIndex):
guard
let screen = window.screen(),
let spaces = CGSpacesInfo<Window>.spacesForAllScreens(includeOnlyUserSpaces: true),
Expand All @@ -731,35 +731,18 @@ extension WindowManager: WindowTransitionTarget {
guard let targetScreen = CGSpacesInfo<Window>.screenForSpace(space: targetSpace) else {
return
}
if window.isFocused() {
if activeWindows(on: screen).count == 1,
let finder = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == "com.apple.finder" }) {
var psn = ProcessSerialNumber()
let status = GetProcessForPID(finder.processIdentifier, &psn)
if status != noErr {
log.error(status)
}
let cgStatus = _SLPSSetFrontProcessWithOptions(&psn, 0, kCPSNoWindows)
if cgStatus != .success {
log.error(cgStatus.rawValue)
}
} else {
focusTransitionCoordinator.moveFocusClockwise()
}
}
markScreen(screen, forReflowWithChange: .remove(window: window))
window.move(toSpace: targetSpace.id)
window.move(toSpaceAtIndex: UInt(spaceIndex + 1))
if targetScreen.screenID() != screen.screenID() {
// necessary to set frame here as window is expected to be at origin relative to targe screen when moved, can be improved.
let newFrame = targetScreen.frameWithoutDockOrMenu()
DispatchQueue.main.sync {
window.setFrame(newFrame, withThreshold: CGSize(width: 25, height: 25))
}
window.moveScaled(to: targetScreen)
markScreen(screen, forReflowWithChange: .remove(window: window))
markScreen(targetScreen, forReflowWithChange: .add(window: window))
}
if UserConfiguration.shared.followWindowsThrownBetweenSpaces() {
window.focus()
if !UserConfiguration.shared.followWindowsThrownBetweenSpaces() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
SISystemWideElement.switch(toSpace: UInt(sourceSpaceIndex + 1))
}
}
markScreen(targetScreen, forReflowWithChange: .add(window: window))
case .resetFocus:
if let screen = screens.screenManagers.first?.screen {
executeTransition(.focusScreen(screen))
Expand Down
26 changes: 20 additions & 6 deletions Amethyst/Managers/WindowTransitionCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ enum WindowTransition<Window: WindowType> {
typealias Screen = Window.Screen
case switchWindows(_ window1: Window, _ window2: Window)
case moveWindowToScreen(_ window: Window, screen: Screen)
case moveWindowToSpaceAtIndex(_ window: Window, spaceIndex: Int)
case moveWindowToSpaceAtIndex(_ window: Window, spaceIndex: Int, sourceSpaceIndex: Int)
case resetFocus
}

Expand Down Expand Up @@ -156,34 +156,48 @@ class WindowTransitionCoordinator<Target: WindowTransitionTarget> {
}

func pushFocusedWindowToSpace(_ space: Int) {
guard let currentFocusedSpace = CGSpacesInfo<Window>.currentFocusedSpace(), let spaces = CGSpacesInfo<Window>.spacesForAllScreens() else {
return
}

guard let index = spaces.firstIndex(of: currentFocusedSpace), index < spaces.count else {
return
}

pushFocusedWindowToSpace(space, sourceSpace: index)
}

func pushFocusedWindowToSpace(_ space: Int, sourceSpace: Int) {
guard let focusedWindow = Window.currentlyFocused(), focusedWindow.screen() != nil else {
return
}

target?.executeTransition(.moveWindowToSpaceAtIndex(focusedWindow, spaceIndex: space))
target?.executeTransition(.moveWindowToSpaceAtIndex(focusedWindow, spaceIndex: space, sourceSpaceIndex: sourceSpace))
}

func pushFocusedWindowToSpaceLeft() {
guard let currentFocusedSpace = CGSpacesInfo<Window>.currentFocusedSpace(), let spaces = CGSpacesInfo<Window>.spacesForAllScreens() else {
return
}

guard let index = spaces.firstIndex(of: currentFocusedSpace), index > 0 else {
let filteredSpaces = spaces.filter { $0.type == CGSSpaceTypeUser }
guard let index = filteredSpaces.firstIndex(of: currentFocusedSpace), index > 0 else {
return
}

pushFocusedWindowToSpace(index - 1)
pushFocusedWindowToSpace(index - 1, sourceSpace: index)
}

func pushFocusedWindowToSpaceRight() {
guard let currentFocusedSpace = CGSpacesInfo<Window>.currentFocusedSpace(), let spaces = CGSpacesInfo<Window>.spacesForAllScreens() else {
return
}

guard let index = spaces.firstIndex(of: currentFocusedSpace), index + 1 < spaces.count else {
let filteredSpaces = spaces.filter { $0.type == CGSSpaceTypeUser }
guard let index = filteredSpaces.firstIndex(of: currentFocusedSpace), index + 1 < spaces.count else {
return
}

pushFocusedWindowToSpace(index + 1)
pushFocusedWindowToSpace(index + 1, sourceSpace: index)
}
}
70 changes: 19 additions & 51 deletions Amethyst/Model/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,16 @@ import Foundation
import Silica

// swiftlint:disable identifier_name
@_silgen_name("GetProcessForPID") @discardableResult
func GetProcessForPID(_ pid: pid_t, _ psn: inout ProcessSerialNumber) -> OSStatus
@_silgen_name("GetProcessForPID") @discardableResult
func GetProcessForPID(_ pid: pid_t, _ psn: inout ProcessSerialNumber) -> OSStatus

@_silgen_name("_SLPSSetFrontProcessWithOptions") @discardableResult
func _SLPSSetFrontProcessWithOptions(_ psn: inout ProcessSerialNumber, _ wid: UInt32, _ mode: UInt32) -> CGError
@_silgen_name("_SLPSSetFrontProcessWithOptions") @discardableResult
func _SLPSSetFrontProcessWithOptions(_ psn: inout ProcessSerialNumber, _ wid: UInt32, _ mode: UInt32) -> CGError

@_silgen_name("SLPSPostEventRecordTo") @discardableResult
func SLPSPostEventRecordTo(_ psn: inout ProcessSerialNumber, _ bytes: inout UInt8) -> CGError
@_silgen_name("SLPSPostEventRecordTo") @discardableResult
func SLPSPostEventRecordTo(_ psn: inout ProcessSerialNumber, _ bytes: inout UInt8) -> CGError

@_silgen_name("SLSMoveWindowsToManagedSpace")
func SLSMoveWindowsToManagedSpace(_ cid: Int32, _ window_ids: CFArray, _ sid: Int)

@_silgen_name("SLSSpaceSetCompatID")
func SLSSpaceSetCompatID(_ cid: Int32, _ sid: Int, _ workspace: Int32) -> CGError
@_silgen_name("SLSSetWindowListWorkspace")
func SLSSetWindowListWorkspace(_ cid: Int32, _ window_ids: UnsafePointer<UInt32>, _ window_count: Int32, _ workspace: Int32) -> CGError
let kCPSUserGenerated: UInt32 = 0x200
let kCPSNoWindows: UInt32 = 0x400
let kCPSUserGenerated: UInt32 = 0x200
// swiftlint:enable identifier_name

/// Generic protocol for objects acting as windows in the system.
Expand Down Expand Up @@ -127,6 +117,14 @@ protocol WindowType: Equatable {
*/
func move(toSpace space: UInt)

/**
Moves the window to the space at an index.

- Parameters:
- space: The index of the space
*/
func move(toSpaceAtIndex space: UInt)

/**
Moves the window to a space.

Expand Down Expand Up @@ -372,40 +370,10 @@ extension AXWindow: WindowType {
move(to: screen.screen)
}

func move(toSpace spaceID: CGSSpaceID) {
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
if (osVersion.majorVersion >= 15) ||
(osVersion.majorVersion == 14 && osVersion.minorVersion >= 5) ||
(osVersion.majorVersion == 13 && osVersion.minorVersion >= 6) ||
(osVersion.majorVersion == 12 && osVersion.minorVersion >= 7) {
/*
See:
- https://github.com/ianyh/Amethyst/issues/1643
- https://github.com/ianyh/Amethyst/issues/1666
- https://github.com/koekeishiya/yabai/issues/2240
- https://github.com/koekeishiya/yabai/issues/2408
- https://github.com/koekeishiya/yabai/commit/98bbdbd1363f27d35f09338cded0de1ec010d830
- https://github.com/koekeishiya/yabai/commit/c8f913cbc0497d1dfe16138f40a8ba6ecaa744f8
*/
var error: CGError = .success
error = SLSSpaceSetCompatID(CGSMainConnectionID(), spaceID, 0x79616265)
defer { _ = SLSSpaceSetCompatID(CGSMainConnectionID(), spaceID, 0x0) }
guard error == .success else {
log.error("failed to set compat aside id: \(error)")
return
}
func move(toSpaceAtIndex space: UInt) {
super.move(toSpace: space)
}

var id = cgID()
error = withUnsafeMutablePointer(to: &id, { pointer -> CGError in
return SLSSetWindowListWorkspace(CGSMainConnectionID(), pointer, 1, 0x79616265)
})
guard error == .success else {
log.error("failed to throw window: \(error)")
return
}
} else {
SLSMoveWindowsToManagedSpace(CGSMainConnectionID(), [cgID()] as CFArray, spaceID)
}
func move(toSpace spaceID: CGSSpaceID) {
}
}
Loading

0 comments on commit dc6bf0f

Please sign in to comment.