From d1c46ea92d6a81aeeb8d20e2d2b0421e4880c323 Mon Sep 17 00:00:00 2001 From: Ethan Chan Date: Tue, 4 Jun 2024 01:00:15 +0900 Subject: [PATCH 1/7] Fix Space#moveWindows on Sonoma 14.5. --- CHANGELOG.md | 4 +++ Phoenix/NSProcessInfo+PHExtension.h | 1 + Phoenix/NSProcessInfo+PHExtension.m | 5 ++++ Phoenix/PHSpace.m | 40 +++++++++++++++++++++++++++-- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d01e67d2..16fe906a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ Changelog Release: dd.mm.yyyy +### Bug Fixes + +- Fix `Space#moveWindows` on Sonoma 14.5 ([#348](https://github.com/kasper/phoenix/issues/348), [#349](https://github.com/kasper/phoenix/issues/349)). + 4.0.0 ----- diff --git a/Phoenix/NSProcessInfo+PHExtension.h b/Phoenix/NSProcessInfo+PHExtension.h index cf41bbbe..c642e148 100644 --- a/Phoenix/NSProcessInfo+PHExtension.h +++ b/Phoenix/NSProcessInfo+PHExtension.h @@ -10,5 +10,6 @@ + (BOOL)isOperatingSystemAtLeastBigSur; + (BOOL)isOperatingSystemAtLeastMonterey; ++ (BOOL)canMoveWindowsToManagedSpace; @end diff --git a/Phoenix/NSProcessInfo+PHExtension.m b/Phoenix/NSProcessInfo+PHExtension.m index 88b76f21..3cc9b423 100644 --- a/Phoenix/NSProcessInfo+PHExtension.m +++ b/Phoenix/NSProcessInfo+PHExtension.m @@ -18,4 +18,9 @@ + (BOOL)isOperatingSystemAtLeastMonterey { return [[self processInfo] isOperatingSystemAtLeastVersion:monterey]; } ++ (BOOL)canMoveWindowsToManagedSpace { + NSOperatingSystemVersion sonoma145 = {.majorVersion = 14, .minorVersion = 5, .patchVersion = 0}; + return ![[self processInfo] isOperatingSystemAtLeastVersion:sonoma145]; +} + @end diff --git a/Phoenix/PHSpace.m b/Phoenix/PHSpace.m index 650ca415..bb387c83 100644 --- a/Phoenix/PHSpace.m +++ b/Phoenix/PHSpace.m @@ -14,6 +14,7 @@ typedef NSUInteger CGSConnectionID; typedef NSUInteger CGSSpaceID; +typedef NSUInteger CGSWorkspaceID; typedef enum { kCGSSpaceIncludesCurrent = 1 << 0, @@ -36,6 +37,9 @@ @implementation PHSpace static NSString *const CGSSpaceIDKey = @"ManagedSpaceID"; static NSString *const CGSSpacesKey = @"Spaces"; +// An arbitrary ID we can use CGSMoveWindowsToManagedSpace with. +const NSUInteger MoveWindowsCompatId = 0x79616265; + // XXX: Undocumented private API to get the CGSConnectionID for the default connection for this process CGSConnectionID CGSMainConnectionID(void); @@ -61,8 +65,25 @@ @implementation PHSpace void CGSRemoveWindowsFromSpaces(CGSConnectionID connection, CFArrayRef windowIds, CFArrayRef spaceIds); // XXX: Undocumented private API to move the given windows (CGWindowIDs) to the given space +// Only works prior to MacOS 14.5! void CGSMoveWindowsToManagedSpace(CGSConnectionID connection, CFArrayRef windowIds, CGSSpaceID spaceId); +/** + * The two functions below serve as a workaround to CGSMoveWindowsToManagedSpace breaking in MacOS 14.5. + * - https://github.com/kasper/phoenix/issues/348 + * - https://github.com/koekeishiya/yabai/issues/2240#issuecomment-2116326165 + * - https://github.com/ianyh/Amethyst/issues/1643#issuecomment-2132519682 + */ + +// XXX: Undocumented private API to set a space's (legacy) compatID +CGError CGSSpaceSetCompatID(CGSConnectionID connection, CGSSpaceID spaceId, NSUInteger compatID); + +// XXX: Undocumented private API to move the given windows (CGWindowIDs) to the given space by (legacy) compatID +CGError CGSSetWindowListWorkspace(CGSConnectionID connection, + CGWindowID *windowIds, + NSUInteger windowCount, + NSUInteger compatID); + #pragma mark - Initialising - (instancetype)initWithIdentifier:(NSUInteger)identifier { @@ -223,8 +244,23 @@ - (void)removeWindows:(NSArray *)windows { } - (void)moveWindows:(NSArray *)windows { - CGSMoveWindowsToManagedSpace( - CGSMainConnectionID(), (__bridge CFArrayRef)[self identifiersForWindows:windows], self.identifier); + CGSConnectionID cid = CGSMainConnectionID(); + if ([NSProcessInfo canMoveWindowsToManagedSpace]) { + CGSMoveWindowsToManagedSpace(cid, (__bridge CFArrayRef)[self identifiersForWindows:windows], self.identifier); + return; + } + + NSArray *ids = [self identifiersForWindows:windows]; + NSUInteger numIds = [ids count]; + NSMutableData *contiguousIds = [[NSMutableData alloc] initWithCapacity:numIds * sizeof(CGWindowID)]; + for (id item in ids) { + CGWindowID value = [item unsignedIntValue]; + [contiguousIds appendBytes:&value length:sizeof(CGWindowID)]; + } + + CGSSpaceSetCompatID(cid, self.identifier, MoveWindowsCompatId); + CGSSetWindowListWorkspace(cid, (CGWindowID *)[contiguousIds bytes], numIds, MoveWindowsCompatId); + CGSSpaceSetCompatID(cid, self.identifier, 0x0); } @end From 2e97091bae43b9f3a1e2c309e0611e0044cd7cbc Mon Sep 17 00:00:00 2001 From: Ethan Chan Date: Sat, 8 Jun 2024 02:21:34 +0900 Subject: [PATCH 2/7] Address MR comments. --- Phoenix/NSProcessInfo+PHExtension.h | 2 +- Phoenix/NSProcessInfo+PHExtension.m | 4 +-- Phoenix/PHSpace.m | 49 ++++++++++++++++------------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/Phoenix/NSProcessInfo+PHExtension.h b/Phoenix/NSProcessInfo+PHExtension.h index c642e148..9e3de292 100644 --- a/Phoenix/NSProcessInfo+PHExtension.h +++ b/Phoenix/NSProcessInfo+PHExtension.h @@ -10,6 +10,6 @@ + (BOOL)isOperatingSystemAtLeastBigSur; + (BOOL)isOperatingSystemAtLeastMonterey; -+ (BOOL)canMoveWindowsToManagedSpace; ++ (BOOL)isOperatingSystemAtLeastSonoma145; @end diff --git a/Phoenix/NSProcessInfo+PHExtension.m b/Phoenix/NSProcessInfo+PHExtension.m index 3cc9b423..66a9d0c9 100644 --- a/Phoenix/NSProcessInfo+PHExtension.m +++ b/Phoenix/NSProcessInfo+PHExtension.m @@ -18,9 +18,9 @@ + (BOOL)isOperatingSystemAtLeastMonterey { return [[self processInfo] isOperatingSystemAtLeastVersion:monterey]; } -+ (BOOL)canMoveWindowsToManagedSpace { ++ (BOOL)isOperatingSystemAtLeastSonoma145 { NSOperatingSystemVersion sonoma145 = {.majorVersion = 14, .minorVersion = 5, .patchVersion = 0}; - return ![[self processInfo] isOperatingSystemAtLeastVersion:sonoma145]; + return [[self processInfo] isOperatingSystemAtLeastVersion:sonoma145]; } @end diff --git a/Phoenix/PHSpace.m b/Phoenix/PHSpace.m index bb387c83..7cd7e5f8 100644 --- a/Phoenix/PHSpace.m +++ b/Phoenix/PHSpace.m @@ -15,6 +15,7 @@ typedef NSUInteger CGSConnectionID; typedef NSUInteger CGSSpaceID; typedef NSUInteger CGSWorkspaceID; +typedef NSUInteger CGSMoveWindowCompatID; typedef enum { kCGSSpaceIncludesCurrent = 1 << 0, @@ -37,8 +38,8 @@ @implementation PHSpace static NSString *const CGSSpaceIDKey = @"ManagedSpaceID"; static NSString *const CGSSpacesKey = @"Spaces"; -// An arbitrary ID we can use CGSMoveWindowsToManagedSpace with. -const NSUInteger MoveWindowsCompatId = 0x79616265; +// An arbitrary ID we can use with CGSSpaceSetCompatID +static const CGSMoveWindowCompatID MoveWindowsCompatId = 0x79616265; // XXX: Undocumented private API to get the CGSConnectionID for the default connection for this process CGSConnectionID CGSMainConnectionID(void); @@ -68,21 +69,14 @@ @implementation PHSpace // Only works prior to MacOS 14.5! void CGSMoveWindowsToManagedSpace(CGSConnectionID connection, CFArrayRef windowIds, CGSSpaceID spaceId); -/** - * The two functions below serve as a workaround to CGSMoveWindowsToManagedSpace breaking in MacOS 14.5. - * - https://github.com/kasper/phoenix/issues/348 - * - https://github.com/koekeishiya/yabai/issues/2240#issuecomment-2116326165 - * - https://github.com/ianyh/Amethyst/issues/1643#issuecomment-2132519682 - */ - -// XXX: Undocumented private API to set a space's (legacy) compatID -CGError CGSSpaceSetCompatID(CGSConnectionID connection, CGSSpaceID spaceId, NSUInteger compatID); +// XXX: Undocumented private API to set a space’s (legacy) compatId +CGError CGSSpaceSetCompatID(CGSConnectionID connection, CGSSpaceID spaceId, CGSMoveWindowCompatID compatID); -// XXX: Undocumented private API to move the given windows (CGWindowIDs) to the given space by (legacy) compatID +// XXX: Undocumented private API to move the given windows (CGWindowIDs) to the given space by (legacy) compatId CGError CGSSetWindowListWorkspace(CGSConnectionID connection, CGWindowID *windowIds, NSUInteger windowCount, - NSUInteger compatID); + CGSMoveWindowCompatID compatId); #pragma mark - Initialising @@ -244,23 +238,34 @@ - (void)removeWindows:(NSArray *)windows { } - (void)moveWindows:(NSArray *)windows { - CGSConnectionID cid = CGSMainConnectionID(); - if ([NSProcessInfo canMoveWindowsToManagedSpace]) { - CGSMoveWindowsToManagedSpace(cid, (__bridge CFArrayRef)[self identifiersForWindows:windows], self.identifier); + if (![NSProcessInfo isOperatingSystemAtLeastSonoma145]) { + CGSMoveWindowsToManagedSpace( + CGSMainConnectionID(), (__bridge CFArrayRef)[self identifiersForWindows:windows], self.identifier); return; } + // CGSMoveWindowsToManagedSpace is broken in MacOS 14.5, so use the legacy Compat ID API instead. + [self moveWindowsWithCompatId:windows]; +} + +/** + * - https://github.com/kasper/phoenix/issues/348 + * - https://github.com/koekeishiya/yabai/issues/2240#issuecomment-2116326165 + * - https://github.com/ianyh/Amethyst/issues/1643#issuecomment-2132519682 + */ +- (void)moveWindowsWithCompatId:(NSArray *)windows { NSArray *ids = [self identifiersForWindows:windows]; - NSUInteger numIds = [ids count]; - NSMutableData *contiguousIds = [[NSMutableData alloc] initWithCapacity:numIds * sizeof(CGWindowID)]; + NSUInteger windowCount = [ids count]; + NSMutableData *windowIdSequence = [[NSMutableData alloc] initWithCapacity:windowCount * sizeof(CGWindowID)]; for (id item in ids) { CGWindowID value = [item unsignedIntValue]; - [contiguousIds appendBytes:&value length:sizeof(CGWindowID)]; + [windowIdSequence appendBytes:&value length:sizeof(CGWindowID)]; } - CGSSpaceSetCompatID(cid, self.identifier, MoveWindowsCompatId); - CGSSetWindowListWorkspace(cid, (CGWindowID *)[contiguousIds bytes], numIds, MoveWindowsCompatId); - CGSSpaceSetCompatID(cid, self.identifier, 0x0); + CGSConnectionID connection = CGSMainConnectionID(); + CGSSpaceSetCompatID(connection, self.identifier, MoveWindowsCompatId); + CGSSetWindowListWorkspace(connection, (CGWindowID *)[windowIdSequence bytes], windowCount, MoveWindowsCompatId); + CGSSpaceSetCompatID(connection, self.identifier, 0x0); } @end From 673a3fd10289bd6dec1e26122b403a31d64b0f9a Mon Sep 17 00:00:00 2001 From: Ethan Chan Date: Sat, 8 Jun 2024 02:32:58 +0900 Subject: [PATCH 3/7] Fix grammar. --- Phoenix/PHSpace.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Phoenix/PHSpace.m b/Phoenix/PHSpace.m index 7cd7e5f8..645b4774 100644 --- a/Phoenix/PHSpace.m +++ b/Phoenix/PHSpace.m @@ -72,7 +72,7 @@ @implementation PHSpace // XXX: Undocumented private API to set a space’s (legacy) compatId CGError CGSSpaceSetCompatID(CGSConnectionID connection, CGSSpaceID spaceId, CGSMoveWindowCompatID compatID); -// XXX: Undocumented private API to move the given windows (CGWindowIDs) to the given space by (legacy) compatId +// XXX: Undocumented private API to move the given windows (CGWindowIDs) to the given space by its (legacy) compatId CGError CGSSetWindowListWorkspace(CGSConnectionID connection, CGWindowID *windowIds, NSUInteger windowCount, From e7c10cd18960a5b6a1fef2662a5d6ce281cd26e6 Mon Sep 17 00:00:00 2001 From: Ethan Chan Date: Sat, 8 Jun 2024 02:43:34 +0900 Subject: [PATCH 4/7] Use array syntax for clarity. --- Phoenix/PHSpace.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Phoenix/PHSpace.m b/Phoenix/PHSpace.m index 645b4774..dd4a2884 100644 --- a/Phoenix/PHSpace.m +++ b/Phoenix/PHSpace.m @@ -74,7 +74,7 @@ @implementation PHSpace // XXX: Undocumented private API to move the given windows (CGWindowIDs) to the given space by its (legacy) compatId CGError CGSSetWindowListWorkspace(CGSConnectionID connection, - CGWindowID *windowIds, + CGWindowID windowIds[], NSUInteger windowCount, CGSMoveWindowCompatID compatId); From 1de31d8c0413fd22df42fbe6cc14801026600729 Mon Sep 17 00:00:00 2001 From: Ethan Chan Date: Sat, 8 Jun 2024 17:07:32 +0900 Subject: [PATCH 5/7] Address more feedback. --- Phoenix/PHSpace.m | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Phoenix/PHSpace.m b/Phoenix/PHSpace.m index dd4a2884..50c6c161 100644 --- a/Phoenix/PHSpace.m +++ b/Phoenix/PHSpace.m @@ -70,7 +70,7 @@ @implementation PHSpace void CGSMoveWindowsToManagedSpace(CGSConnectionID connection, CFArrayRef windowIds, CGSSpaceID spaceId); // XXX: Undocumented private API to set a space’s (legacy) compatId -CGError CGSSpaceSetCompatID(CGSConnectionID connection, CGSSpaceID spaceId, CGSMoveWindowCompatID compatID); +CGError CGSSpaceSetCompatID(CGSConnectionID connection, CGSSpaceID spaceId, CGSMoveWindowCompatID compatId); // XXX: Undocumented private API to move the given windows (CGWindowIDs) to the given space by its (legacy) compatId CGError CGSSetWindowListWorkspace(CGSConnectionID connection, @@ -237,17 +237,6 @@ - (void)removeWindows:(NSArray *)windows { (__bridge CFArrayRef) @[@(self.identifier)]); } -- (void)moveWindows:(NSArray *)windows { - if (![NSProcessInfo isOperatingSystemAtLeastSonoma145]) { - CGSMoveWindowsToManagedSpace( - CGSMainConnectionID(), (__bridge CFArrayRef)[self identifiersForWindows:windows], self.identifier); - return; - } - - // CGSMoveWindowsToManagedSpace is broken in MacOS 14.5, so use the legacy Compat ID API instead. - [self moveWindowsWithCompatId:windows]; -} - /** * - https://github.com/kasper/phoenix/issues/348 * - https://github.com/koekeishiya/yabai/issues/2240#issuecomment-2116326165 @@ -268,4 +257,15 @@ - (void)moveWindowsWithCompatId:(NSArray *)windows { CGSSpaceSetCompatID(connection, self.identifier, 0x0); } +- (void)moveWindows:(NSArray *)windows { + if (![NSProcessInfo isOperatingSystemAtLeastSonoma145]) { + CGSMoveWindowsToManagedSpace( + CGSMainConnectionID(), (__bridge CFArrayRef)[self identifiersForWindows:windows], self.identifier); + return; + } + + // CGSMoveWindowsToManagedSpace is broken in MacOS 14.5, so use the legacy Compat ID API instead. + [self moveWindowsWithCompatId:windows]; +} + @end From c5cdad4ac09cfb24514eafadd74d0a2860044ecc Mon Sep 17 00:00:00 2001 From: Ethan Chan Date: Sat, 8 Jun 2024 19:02:29 +0900 Subject: [PATCH 6/7] Address more feedback. --- Phoenix/PHSpace.m | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Phoenix/PHSpace.m b/Phoenix/PHSpace.m index 50c6c161..197548f1 100644 --- a/Phoenix/PHSpace.m +++ b/Phoenix/PHSpace.m @@ -39,7 +39,7 @@ @implementation PHSpace static NSString *const CGSSpacesKey = @"Spaces"; // An arbitrary ID we can use with CGSSpaceSetCompatID -static const CGSMoveWindowCompatID MoveWindowsCompatId = 0x79616265; +static const CGSMoveWindowCompatID PHMoveWindowsCompatId = 0x79616265; // XXX: Undocumented private API to get the CGSConnectionID for the default connection for this process CGSConnectionID CGSMainConnectionID(void); @@ -65,8 +65,8 @@ @implementation PHSpace // XXX: Undocumented private API to remove the given windows (CGWindowIDs) from the given spaces (CGSSpaceIDs) void CGSRemoveWindowsFromSpaces(CGSConnectionID connection, CFArrayRef windowIds, CFArrayRef spaceIds); -// XXX: Undocumented private API to move the given windows (CGWindowIDs) to the given space -// Only works prior to MacOS 14.5! +// XXX: Undocumented private API to move the given windows (CGWindowIDs) to the given space, +// only works prior to macOS 14.5 void CGSMoveWindowsToManagedSpace(CGSConnectionID connection, CFArrayRef windowIds, CGSSpaceID spaceId); // XXX: Undocumented private API to set a space’s (legacy) compatId @@ -243,17 +243,17 @@ - (void)removeWindows:(NSArray *)windows { * - https://github.com/ianyh/Amethyst/issues/1643#issuecomment-2132519682 */ - (void)moveWindowsWithCompatId:(NSArray *)windows { - NSArray *ids = [self identifiersForWindows:windows]; - NSUInteger windowCount = [ids count]; + NSArray *windowIds = [self identifiersForWindows:windows]; + NSUInteger windowCount = [windowIds count]; NSMutableData *windowIdSequence = [[NSMutableData alloc] initWithCapacity:windowCount * sizeof(CGWindowID)]; - for (id item in ids) { - CGWindowID value = [item unsignedIntValue]; + for (NSNumber *item in windowIds) { + CGWindowID value = item.unsignedIntValue; [windowIdSequence appendBytes:&value length:sizeof(CGWindowID)]; } CGSConnectionID connection = CGSMainConnectionID(); - CGSSpaceSetCompatID(connection, self.identifier, MoveWindowsCompatId); - CGSSetWindowListWorkspace(connection, (CGWindowID *)[windowIdSequence bytes], windowCount, MoveWindowsCompatId); + CGSSpaceSetCompatID(connection, self.identifier, PHMoveWindowsCompatId); + CGSSetWindowListWorkspace(connection, (CGWindowID *)[windowIdSequence bytes], windowCount, PHMoveWindowsCompatId); CGSSpaceSetCompatID(connection, self.identifier, 0x0); } @@ -264,7 +264,7 @@ - (void)moveWindows:(NSArray *)windows { return; } - // CGSMoveWindowsToManagedSpace is broken in MacOS 14.5, so use the legacy Compat ID API instead. + // CGSMoveWindowsToManagedSpace is broken in macOS 14.5+, so use the legacy Compat ID API instead [self moveWindowsWithCompatId:windows]; } From af916fae358c18a3fbdc2ec454c66bac1bbbf155 Mon Sep 17 00:00:00 2001 From: Ethan Chan Date: Sun, 9 Jun 2024 12:10:01 +0900 Subject: [PATCH 7/7] Rename item to windowId. --- Phoenix/PHSpace.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Phoenix/PHSpace.m b/Phoenix/PHSpace.m index 197548f1..291cd3d7 100644 --- a/Phoenix/PHSpace.m +++ b/Phoenix/PHSpace.m @@ -246,8 +246,8 @@ - (void)moveWindowsWithCompatId:(NSArray *)windows { NSArray *windowIds = [self identifiersForWindows:windows]; NSUInteger windowCount = [windowIds count]; NSMutableData *windowIdSequence = [[NSMutableData alloc] initWithCapacity:windowCount * sizeof(CGWindowID)]; - for (NSNumber *item in windowIds) { - CGWindowID value = item.unsignedIntValue; + for (NSNumber *windowId in windowIds) { + CGWindowID value = windowId.unsignedIntValue; [windowIdSequence appendBytes:&value length:sizeof(CGWindowID)]; }