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