kasper / phoenix

A lightweight macOS window and app manager scriptable with JavaScript
https://kasper.github.io/phoenix/
Other
4.38k stars 127 forks source link

Moving spaces no longer possible after update to 12.2 #289

Closed ckruse closed 2 years ago

ckruse commented 2 years ago

Hey,

Is it possible that the current macOS update broke Phoenix? Suddenly my window management doesn't work anymore; I have a set of scripts which moves windows to different spaces when attaching / detaching a monitor.

Best regards, CK

kasper commented 2 years ago

@ckruse 👋 Hello! Sorry to hear. Quite possible, since the Spaces features use private APIs from Apple. Would you mind posting a snippet of what you are doing? Have you debugged what call doesn’t work?

ckruse commented 2 years ago

Hey there,

I tried debugging the issue, but it just seems that Space.addWindows() and Space.removeWindows() doesn't have any effect.

This is the Script I'm using to move screens to different spaces in single screen config:

const singleScreenConfig = [
  ["iTerm2", 2],
  ["Element", 3],
  ["Signal", 3],
  ["Nachrichten", 3],
  ["Google Chat", 3],
  ["Mastonaut", 3],
  ["Tweetbot", 3],
  ["MailMate", 4],
  ["Things", 4],
];

function moveToTargetSpace(window, targetSpace) {
  const otherSpaces = window.spaces();
  targetSpace.addWindows([window]);

  otherSpaces.forEach((space) => {
    if (space.hash() != targetSpace.hash()) {
      space.removeWindows([window]);
    }
  });

  console.log(
    targetSpace.hash(),
    otherSpaces.map((space) => space.hash()),
    window.spaces().map((space) => space.hash())
  );
}

function setSingleScreenConfig() {
  const spaces = Space.all();
  console.log(spaces.map((space) => space.hash()));

  singleScreenConfig.forEach(([appName, spaceIdx]) => {
    const app = App.get(appName);
    if (!app) {
      Phoenix.log("app not found", appName);
      return;
    }

    console.log(appName);
    moveToTargetSpace(app.mainWindow(), spaces[spaceIdx]);
  });
}

Event.on("screensDidChange", function () {
  const noOfScreens = Screen.all().length;

  if (noOfScreens == 1) {
    setSingleScreenConfig();
  }
});

Key.on("p", ["control", "alt", "cmd"], setSingleScreenConfig);

The output of the console.log in moveToTargetSpace is e.g. this:

2022-01-27 20:35:47.042116+0100 0x7bc87    Default     0x0                  40828  0    Phoenix: iTerm2
2022-01-27 20:35:47.051998+0100 0x7bc87    Default     0x0                  40828  0    Phoenix: 4 3 3

So iTerm's spaces configuration doesn't change at all, does it?

jason0x43 commented 2 years ago

I also ran into this issue right after updating to 12.2. I have a simple movement function bound to a shortcut:

function moveToSpace(direction, window) {
  window = window || Window.focused();
  if (!window) {
    return;
  }

  const space = Space.active();
  if (!space) {
    return;
  }

  const targetSpace = direction == RIGHT ? space.next() : space.previous();
  if (!targetSpace) {
    return;
  }

  targetSpace.addWindows([window]);
  space.removeWindows([window]);
  window.focus();
}

It was working fine until the 12.2 update. No errors or warnings come up in the logs, but the addWindows and removeWindows functions have no apparent effect.

Amethyst appears to be having similar issues: https://github.com/ianyh/Amethyst/issues/1192

kasper commented 2 years ago

Thanks! Need to dig what Apple has changed. 😄

kasper commented 2 years ago

I was able to use both addWindows and removeWindows from a master build successfully. So my guess is that you need to build the app on macOS 12.2 to link the private APIs. Could you try building the app from source to confirm this? If this is true, then the next release should fix this.

kaatt commented 2 years ago

didn't work for me.

xcodebuild -showsdks shows:

macOS SDKs:
    macOS 12.3                      -sdk macosx12.3

sw_vers:

ProductName:    macOS
ProductVersion: 12.2
BuildVersion:   21D49

built with

xcodebuild -workspace Phoenix.xcworkspace \
           -scheme Phoenix \
           -configuration Release \
           SYMROOT="$PWD/build/" \
           clean build

(also with -sdk macosx12.3)

function moveToTargetSpace(window, targetSpace) {
  const otherSpaces = window.spaces();
  targetSpace.addWindows([window]);

  otherSpaces.forEach((space) => {
    if (space.hash() != targetSpace.hash()) {
      space.removeWindows([window]);
    }
  });

  console.log(
    targetSpace.hash(),
    otherSpaces.map((space) => space.hash()),
    window.spaces().map((space) => space.hash())
  );
}

Key.on("p", ["alt", "cmd"], () => {
  const spaces = Space.all();
  const space = spaces[spaces.length - 1];
  console.log("moving to space", space.hash());
  moveToTargetSpace(Window.focused(), space);
});
ckruse commented 2 years ago

Can confirm. Doesn't work on master. I built it like documented, but still:

2022-01-29 19:06:23.373598+0100 0x18a565   Default     0x0                  60317  0    Phoenix: iTerm2
2022-01-29 19:06:23.378843+0100 0x18a565   Default     0x0                  60317  0    Phoenix: 4 1 1

(Second log row: Target space, current space, current space after move)

kasper commented 2 years ago

@kaatt Intriguing. The config you provided does work for me from a master build, but it only works when moving from the first space to the second space. I think it must be something else than the pure addWindows and removeWindows functions.

ckruse commented 2 years ago

@kasper the config @kaatt provided doesn't do anything for me with a master build 🤯

kaatt commented 2 years ago

@kasper

  1. is your macOS build 21D49?
  2. which sdk is it using? (xcodebuild -showsdks)
  3. which xcode version are you using?
  4. are you building with xcode or xcodebuild? (i used xcodebuild and didn't install cocoapods etc.)
kasper commented 2 years ago

@ckruse @kaatt Sorry, I somehow missed the x.2 completely. 🙄 I’m on 12.1. So just to confirm, this has worked in Monterey, but the latest 12.2 update broke it? I guess I need to update... 😂

ckruse commented 2 years ago

Correct. It worked with Monterey but stopped with 12.2

kasper commented 2 years ago

Thanks! Classic, I bet Apple broke something, would be weird if they changed the APIs in between, but I’ll run the investigations on 12.2 then.

kaatt commented 2 years ago

might be worth looking into the SkyLight framework which is what exports the CGS APIs. as of macOS 12.1, CGS were all aliases for SLS

stark (phoenix like tool) uses SLS APIs: https://github.com/tombell/stark/blob/main/Stark/Source/API/Space.swift


diffing

/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/System/Library/PrivateFrameworks/SkyLight.framework/SkyLight.tbd

and /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/PrivateFrameworks/SkyLight.framework/SkyLight.tbd

results in:

>                        _SLSDisplayCreateImage,
775a777
>                        _SLSHMDEnabled,
781a784,789
>                        _SLSHMDModeConfigurationCopyCurrentCGSDisplayMode,
>                        _SLSHMDModeConfigurationTransactionAbort,
>                        _SLSHMDModeConfigurationTransactionCommit,
>                        _SLSHMDModeConfigurationTransactionInitialize,
>                        _SLSHMDModeConfigurationTransactionSetCGSDisplayMode,
>                        _SLSHMDModeConfigurationTransactionValidate,
783a792
>                        _SLSHMDSetEnabled,
980a990
>                        _SLSScreenCaptureAccessCheck,
1393a1404
>                        _SLSTransactionSystemStatusBarSetItemPrivacyIndicator,
1557a1569
>                        _kSLCaptureStreamAlwaysScaleToFit,
1561a1574,1585
>                        _kSLContentStreamAlwaysScaleToFitKey,
>                        _kSLContentStreamBackgroundColorKey,
>                        _kSLContentStreamColorSpaceKey,
>                        _kSLContentStreamDestinationRectKey,
>                        _kSLContentStreamFrameSizeKey,
>                        _kSLContentStreamMinimumFrameTimeKey,
>                        _kSLContentStreamPixelFormatKey,
>                        _kSLContentStreamPreserveAspectRatioKey,
>                        _kSLContentStreamQueueDepthKey,
>                        _kSLContentStreamShowCursorKey,
>                        _kSLContentStreamSourceRectKey,
>                        _kSLContentStreamYCbCrMatrixKey,
1633a1658
>                        _kSLSHMDCapabilityCGDisplayModes,
1735a1761,1763
>                        SLContentFilter,
>                        SLContentStream,
>                        SLContentStreamUpdate,

however, /System/Library/Frameworks/CoreGraphics.framework/Versions/Current/CoreGraphics.tbd hasn't been changed between 12.1 and 12.3

kasper commented 2 years ago

@kaatt Indeed, thanks! I was checking it out, but I can’t find anything that would be relevant to the existing APIs used by Phoenix.

kaatt commented 2 years ago

i tried replacing CGSAddWindowsToSpaces/CGSRemoveWindowsFromSpaces with SLSAddWindowsToSpaces/SLSRemoveWindowsFromSpaces but it had no effect.

also tried stark's add/remove windows from spaces function but it had no effect either. stark exclusively uses SLS functions (no CGS)

kaatt commented 2 years ago

perhaps @tombell may have suggestions on what to do here?

kasper commented 2 years ago

I think SLS* and CGS* should still map one-to-one. The function headers seem identical. 🤔

kaatt commented 2 years ago

maybe it's a regression? in https://developer.apple.com/documentation/macos-release-notes/macos-12_3-release-notes, apple initially wrote SkyLight instead of ScreenCaptureKit (now fixed)

image

they must have modified SkyLight for ScreenCaptureKit

tombell commented 2 years ago

I think SLS* and CGS* should still map one-to-one. The function headers seem identical. 🤔

This is correct, SkyLight is just what the library is called now, as far as I know.

perhaps @tombell may have suggestions on what to do here?

I've updated to Monterey 12.2, and my Stark code doesn't work anymore either.

kaatt commented 2 years ago

SkyLight has a bunch of SLSTransaction* methods that may work:

SLSTransactionAddWindowToSpace
SLSTransactionAddWindowToSpaceAndRemoveFromSpaces
SLSTransactionBindSurface
SLSTransactionClearMenuBarSystemOverrideAlphas
SLSTransactionClearWindowLockedBounds
SLSTransactionClearWindowSystemLevel
SLSTransactionCommit
SLSTransactionCommitCoalescing
SLSTransactionCreate
SLSTransactionDeferWindowMoveEvents
SLSTransactionDestroySpace
SLSTransactionGetTypeID
SLSTransactionHideSpace
SLSTransactionMoveWindowWithGroup
SLSTransactionMoveWindowsToManagedSpace
SLSTransactionOrderSurface
SLSTransactionOrderWindow
SLSTransactionOrderWindowGroup
SLSTransactionOverrideAppSleepNotifications
SLSTransactionPostBroadcastNotification
SLSTransactionPostNotificationToConnection
SLSTransactionRemoveWindowFromSpace
SLSTransactionRemoveWindowFromSpaces
SLSTransactionResetSpaceMenuBar
SLSTransactionResetWindow
SLSTransactionResetWindowSubLevel
SLSTransactionSetBackdropChameleonContribution
SLSTransactionSetChameleonUpdatesEnabled
SLSTransactionSetClientAdvisory
SLSTransactionSetEventCapture
SLSTransactionSetManagedDisplayCurrentSpace
SLSTransactionSetManagedDisplayIsAnimating
SLSTransactionSetMenuBarBounds
SLSTransactionSetMenuBarSystemOverrideAlpha
SLSTransactionSetSafeApertureOverride
SLSTransactionSetSafeApertureSpaceReveal
SLSTransactionSetSpaceAbsoluteLevel
SLSTransactionSetSpaceAlpha
SLSTransactionSetSpaceOrderingWeight
SLSTransactionSetSpaceReveal
SLSTransactionSetSpaceShape
SLSTransactionSetSpaceTransform
SLSTransactionSetSurfaceBounds
SLSTransactionSetSurfaceOpacity
SLSTransactionSetSurfaceResolution
SLSTransactionSetWindowAlpha
SLSTransactionSetWindowBrightness
SLSTransactionSetWindowGlobalClipShape
SLSTransactionSetWindowLevel
SLSTransactionSetWindowLockedBounds
SLSTransactionSetWindowOpaqueShape
SLSTransactionSetWindowProperty
SLSTransactionSetWindowReleasesBackingOnOrderOut
SLSTransactionSetWindowResolution
SLSTransactionSetWindowShape
SLSTransactionSetWindowSubLevel
SLSTransactionSetWindowSystemAlpha
SLSTransactionSetWindowSystemLevel
SLSTransactionSetWindowTransform
SLSTransactionSetWindowWarp
SLSTransactionShowSpace
SLSTransactionSpaceTileMoveToSpaceAtIndex
SLSTransactionSystemStatusBarRegisterReplicantWindow
SLSTransactionSystemStatusBarRegisterSortedWindow
SLSTransactionSystemStatusBarSetDropPriority
SLSTransactionSystemStatusBarSetItemPrivacyIndicator
SLSTransactionSystemStatusBarSetSelectedContentFrame
SLSTransactionSystemStatusBarUnregisterWindow
SLSTransactionUpdateRegion
SLSTransactionWillSwitchSpaces

attempt at the headers, all but SLSTransactionCreate segfault for me:

typedef int SLSConnectionID;
typedef size_t SLSSpaceID;
typedef NSObject SLSTransaction;

SLSTransaction* SLSTransactionCreate(SLSConnectionID cid);
int SLSTransactionCommit(SLSConnectionID cid, SLSTransaction* txID);

void SLSTransactionAddWindowToSpace(SLSConnectionID cid, CGWindowID window, SLSSpaceID space);
void SLSTransactionRemoveWindowFromSpace(SLSConnectionID cid, CGWindowID window, SLSSpaceID space);
void SLSTransactionRemoveWindowFromSpaces(SLSConnectionID cid, CGWindowID window, CFArrayRef spaces);

https://bugs.chromium.org/p/chromium/issues/detail?id=74812 mentions CGSSpaceAddWindowsAndRemoveFromSpaces which could also work

kasper commented 2 years ago

If I would have to guess, Apple has been working on the library and created a regression along the way. I would find it surprising that they would drop some support between minor versions (even for internal stuff). I will keep digging if any information surfaces.

kaatt commented 2 years ago

the chromium page also mentioned CGSMoveWorkspaceWindowList, couldn't find it exported from CoreGraphics but found SLSMoveWorkspaceWindowList exported from SkyLight.

it's working for moving a window to the primary space (1). unsure how to move to other spaces.

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <CoreGraphics/CoreGraphics.h>

typedef int CGSConnection;
typedef int CGSWindow;
typedef int CGSWorkspaceID;

extern OSStatus SLSMoveWorkspaceWindowList(const CGSConnection connection,
                                       CGSWindow *wids,
                                       int count,
                                       CGSWorkspaceID toWorkspace);
extern CGSConnection _CGSDefaultConnection(void);

int main(int argc, char **argv) {
    CGSConnection con = _CGSDefaultConnection();

    CGSWindow wid = 12130;
    CGSWindow wids[] = {wid};

    CGSWorkspaceID spaceId = 4;
    printf("%i\n",SLSMoveWorkspaceWindowList(con, wids, 1, spaceId));

    return 0;
}

compiled with: clang -framework AppKit -framework SkyLight -framework CoreGraphics -iframework /System/Library/PrivateFrameworks/ source.m

tombell commented 2 years ago

I got this working in my project using SLSMoveWindowsToManagedSpace from SkyLight.

The prototype:

// ...

extern void SLSMoveWindowsToManagedSpace(int cid, CFArrayRef windows, uint64 sid);

The usage:

public func moveWindows(_ windows: [Window]) {
  SLSMoveWindowsToManagedSpace(Space.connectionID, windows.map(\.identifier) as CFArray, identifier)
}
kaatt commented 2 years ago

nice, the aliased CGSMoveWindowsToManagedSpace is also working

kasper commented 2 years ago

Awesome, thanks for digging in! We will need to make this conditional 12.0 upwards.

kasper commented 2 years ago

Thanks everyone for helping! 🙌 This should now be fixed in master (build 115).

kaatt commented 2 years ago

did you test moveWindows on big sur? CGSMoveWindowsToManagedSpace should be working.

found usages from 2018 and 2019:

https://github.com/kinglouie/CoreGraphicsExtension/blob/da8d4e53849099edb69dd78377d4e9140ddd9d77/Sources/CoreGraphicsExtension/NSWindow%2BCGE.m https://github.com/fsestini/macos-corelibs/blob/53345c4b3f05b7a51026b3e166ceeb9ed3cddc7a/src/MacSdk/Framework/CoreGraphics/Space.hs https://github.com/p10q/wmctrl/blob/290fd635fedcadedc2194fbd204f4a98fa2d5fef/common/accessibility/display.mm https://github.com/saforem2/chunkwm/blob/7d078f6f44fd23040e1ba5b9650a9e0df3fe526f/src/common/accessibility/display.mm

kasper commented 2 years ago

@kaatt I did not! I wanted to be more on the safe side. If someone can confirm, we can adjust the documentation and conditional. 👍

kaatt commented 2 years ago

commit from 2017: https://github.com/saforem2/chunkwm/commit/48b268a509349750dd5e99a4ae419a5963a1b872

must be working since high sierra at least

ckruse commented 2 years ago

Can confirm, it works with 12.2 again. Thank you very much! ❤️

kasper commented 2 years ago

@kaatt Cool, thanks! Adjusted the conditional and documentation to 10.13+.

NightMachinery commented 2 years ago

I changed

mainWindow.spaces().forEach((space) => {
  space.removeWindows([mainWindow]);
});

activeSpace.addWindows([mainWindow]);

to

activeSpace.moveWindows([mainWindow]);

But it doesn't seem to work?

The overall goal is to create a hotkey (tilde, dropdown) window effect. Does anyone have a working example of this for the newer macOS versions?

kasper commented 2 years ago

@NightMachinary Hey! Sorry to hear, could you share a bit more of you script? What is activeSpace and mainWindow?

NightMachinery commented 2 years ago

@NightMachinary Hey! Sorry to hear, could you share a bit more of you script? What is activeSpace and mainWindow?

My .phoenix.js pretty much has this single function, but it's a bit messy. I myself only altered the script a bit; it was written by someone else.

The relevant function is moveAppToActiveSpace at line 381. activeSpace is the current space. mainWindow is the main window of the app that the hotkey is supposed to show on top of the current app.

It was working previously (it still mostly works except that the windows don't move to the current space), but after upgrading macOS, it regressed.

kasper commented 2 years ago

@NightMachinary Just to confirm, you have upgraded to Phoenix 3.0.0? The script is indeed a bit messy, so it’s hard to follow what might be the issue, but I would suggest you debug it line-by-line to see where does the expectations break.

NightMachinery commented 2 years ago

@kasper Yes, I'm on Version 3.0.0 (127). I'll do some debugging when I have the time.

lwouis commented 2 years ago

@kasper the CGSMoveWindowsToManagedSpace doesn't seem to move fullscreen windows. The CGSAddWindowsToSpaces API did move them.

How did you work around this limitation? It's a blocker for my project (https://github.com/lwouis/alt-tab-macos), and I'm curious how you guys dealt with CGSAddWindowsToSpaces being broken from macOS 12.2 onward.

kasper commented 2 years ago

@lwouis I might have missed this limitation. I guess that is considered reordering Spaces and doesn’t work because of that?

lwouis commented 2 years ago

I'm not sure why it doesn't work with fullscreen windows but it doesn't, which means it's not a 1-to-1 replacement of the other API

kasper commented 2 years ago

Yep, I wish Apple would support Spaces APIs officially.