Skip to content

Instantly share code, notes, and snippets.

@0xdevalias
Last active June 16, 2024 00:26
Show Gist options
  • Save 0xdevalias/8bc497546d5f036cbaeae5d0e389aa35 to your computer and use it in GitHub Desktop.
Save 0xdevalias/8bc497546d5f036cbaeae5d0e389aa35 to your computer and use it in GitHub Desktop.
The following shows how you can tell macOS to assign an application to always show on all desktops, one specific desktop, or to unbind it.

macOS - Setting 'Assign to Desktop' via Terminal

The following shows how you can tell macOS to assign an application to show on one specific desktop, all desktops, or just a single desktop.

Show Current Config

⇒ defaults read com.apple.spaces app-bindings
{
    "com.apple.activitymonitor" = AllSpaces;
    "com.googlecode.iterm2" = AllSpaces;
    "com.riotgames.riotgames.riotclientux" = AllSpaces;
    "com.toggl.toggldesktop.toggldesktop" = AllSpaces;
    "org.audacityteam.audacity" = "7B8802D8-756C-445B-AE72-23FCABFC74C4";
    "org.whispersystems.signal-desktop" = AllSpaces;
    "ru.keepcoder.telegram" = AllSpaces;
}
⇒ /usr/libexec/PlistBuddy -c "Print :app-bindings" ~/Library/Preferences/com.apple.spaces.plist
Dict {
    ru.keepcoder.telegram = AllSpaces
    com.googlecode.iterm2 = AllSpaces
    com.riotgames.riotgames.riotclientux = AllSpaces
    com.toggl.toggldesktop.toggldesktop = AllSpaces
    org.whispersystems.signal-desktop = AllSpaces
    com.apple.activitymonitor = AllSpaces
    org.audacityteam.audacity = 7B8802D8-756C-445B-AE72-23FCABFC74C4
}
⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '."app-bindings"'
{
  "ru.keepcoder.telegram": "AllSpaces",
  "com.googlecode.iterm2": "AllSpaces",
  "com.riotgames.riotgames.riotclientux": "AllSpaces",
  "com.toggl.toggldesktop.toggldesktop": "AllSpaces",
  "org.whispersystems.signal-desktop": "AllSpaces",
  "com.apple.activitymonitor": "AllSpaces",
  "org.audacityteam.audacity": "7B8802D8-756C-445B-AE72-23FCABFC74C4"
}

Assign to 'None'

⇒ /usr/libexec/PlistBuddy -c "Delete :app-bindings:im.beeper" ~/Library/Preferences/com.apple.spaces.plist
# successfully deleted

⇒ killall Dock
# to ensure it is updated properly based on the plist changes
⇒ /usr/libexec/PlistBuddy -c "Delete :app-bindings:im.beeper" ~/Library/Preferences/com.apple.spaces.plist
Delete: Entry, ":app-bindings:im.beeper", Does Not Exist

Assign to 'All Desktops'

⇒ /usr/libexec/PlistBuddy -c "Add :app-bindings:im.beeper string AllSpaces" ~/Library/Preferences/com.apple.spaces.plist

⇒ killall Dock
# to ensure it is updated properly based on the plist changes

Assign to 'This Desktop' (or a specific desktop)

Note: I haven't tested this

⇒ /usr/libexec/PlistBuddy -c "Add :app-bindings:im.beeper string TODO-THE-UUID-OF-THE-DESKTOP" ~/Library/Preferences/com.apple.spaces.plist

⇒ killall Dock
# to ensure it is updated properly based on the plist changes

Getting the Desktop Space UUIDs

To get the current desktop space:

⇒ /usr/libexec/PlistBuddy -c "Print :SpacesDisplayConfiguration:'Management Data':Monitors:0:'Current Space'" ~/Library/Preferences/com.apple.spaces.plist
Dict {
    id64 = 617
    ManagedSpaceID = 617
    type = 0
    uuid = 671BB3AE-0E2C-42C9-B259-C5004B30DEE1
}

Or specifically the uuid for it:

⇒ /usr/libexec/PlistBuddy -c "Print :SpacesDisplayConfiguration:'Management Data':Monitors:0:'Current Space':uuid" ~/Library/Preferences/com.apple.spaces.plist
671BB3AE-0E2C-42C9-B259-C5004B30DEE1

The uuids for the other spaces are available as well, but it's harder to filter for them specifically with PlistBuddy alone:

⇒ /usr/libexec/PlistBuddy -c "Print :SpacesDisplayConfiguration:'Management Data':Monitors:0:Spaces" ~/Library/Preferences/com.apple.spaces.plist
# Lots of data

You can crudely filter for this with a grep like this, but it still leaves a lot of noise:

⇒ /usr/libexec/PlistBuddy -c "Print :SpacesDisplayConfiguration:'Management Data':Monitors:0:Spaces" ~/Library/Preferences/com.apple.spaces.plist | grep -B 4 'uuid ='
# More focussed data, but still noisy

You could also potentially get the uuids for all of the spaces with a command like this, but it doesn't leave a lot of context as to which one maps to which:

⇒ /usr/libexec/PlistBuddy -c "Print :SpacesDisplayConfiguration:'Space Properties'" ~/Library/Preferences/com.apple.spaces.plist
Array {
    Dict {
        name = 9160DD0A-9F52-4B1E-A4C9-B4F6ADD92A78
        windows = Array {
            74392
            325
            142
            6672
            41189
            45074
            14759
        }
    }
    Dict {
        name = 60D3C0EA-F836-4033-90C3-461D667C13FA
        windows = Array {
            74392
            325
            142
            264
            268
            14761
        }
    }
# ..snip..

Another way to achieve this is by using plutil to convert the plist to JSON, then pipe that to jq, which allows for much more powerful filtering/manipulation.

Get the current space:

⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors[0]."Current Space"'
{
  "id64": 617,
  "ManagedSpaceID": 617,
  "type": 0,
  "uuid": "671BB3AE-0E2C-42C9-B259-C5004B30DEE1"
}

Or just the uuid of it:

⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors[0]."Current Space".uuid'
"671BB3AE-0E2C-42C9-B259-C5004B30DEE1"

# Or if we want to make this more robust even if the first item in the array isn't the one with the 'Current Space'
⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors | map(select(has("Current Space"))."Current Space") | first | .uuid'
"671BB3AE-0E2C-42C9-B259-C5004B30DEE1"

We can also be a little bit fancier, and get the uuids from all of the elements within the Management Data key, as well as the Display Identifier associated with that space:

⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors[] | {displayIdentifier: ."Display Identifier", uuid: (if has("Current Space") then ."Current Space".uuid else ."Collapsed Space".uuid end)}'
{
  "displayIdentifier": "Main",
  "uuid": "671BB3AE-0E2C-42C9-B259-C5004B30DEE1"
}
{
  "displayIdentifier": "62F91CDC-6EA4-194B-17B7-E58AA676B272",
  "uuid": "AA7F793A-5D1D-4752-B46E-39FFBEBDEA09"
}
{
  "displayIdentifier": "B17DC16B-9DA0-4B53-B720-A946ADC56B8C",
  "uuid": "AFFB5DFA-AD24-4ED7-A1DA-A44BEFDDFEE0"
}
{
  "displayIdentifier": "C8E42782-7DDB-C015-81BD-04B21E4C01F2",
  "uuid": "8911EACB-5CED-48B0-8FA5-88994B43C732"
}

We can see details about all of the current spaces that are open with:

⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors | map(select(has("Current Space"))) | first | .Spaces'
# Lots of data

But to narrow that down more, we can seperate between 'normal' desktop spaces (which seem to be type: 0):

# Full data
⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors | map(select(has("Current Space"))) | first | .Spaces | map(select(has("TileLayoutManager") | not))'
[
  {
    "id64": 617,
    "ManagedSpaceID": 617,
    "type": 0,
    "uuid": "671BB3AE-0E2C-42C9-B259-C5004B30DEE1"
  },
  {
    "id64": 14,
    "ManagedSpaceID": 14,
    "type": 0,
    "uuid": "9160DD0A-9F52-4B1E-A4C9-B4F6ADD92A78"
  },
  # ..snip..
]

# Just the UUIDs
⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors | map(select(has("Current Space"))) | first | .Spaces | map(select(has("TileLayoutManager") | not).uuid)'
[
  "671BB3AE-0E2C-42C9-B259-C5004B30DEE1",
  "9160DD0A-9F52-4B1E-A4C9-B4F6ADD92A78",
  "6A913ADE-99CD-47CA-94AC-F7D3D41C3475",
  "60D3C0EA-F836-4033-90C3-461D667C13FA",
  "56BB3123-46D8-410A-AC13-C1702362A25C",
  "02F34F40-3FE7-40E6-9DDF-A3B2AA65B862",
  "6D19DEE8-FD58-44EB-B1C2-5ED709AA7A6F",
  "7B8802D8-756C-445B-AE72-23FCABFC74C4"
]

And those that are based on a maximised window (or multiple maximised windows):

# Full data
⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors | map(select(has("Current Space"))) | first | .Spaces | map(select(has("TileLayoutManager")))'
[
  {
    "fs_wid": 168,
    "id64": 27,
    "TileLayoutManager": { /* ..snip..*/ },
    "uuid": "0ADEC86B-0C91-47A4-892E-995CA8CC008A",
    "ManagedSpaceID": 27,
    "type": 4,
    "WallSpace": {
      "id64": 28,
      "ManagedSpaceID": 27,
      "type": 6,
      "uuid": "C3DB5651-B361-4392-B0F6-C8716F9FADA2"
    },
    "pid": 727
  },
  {
    "id64": 98,
    "TileLayoutManager": { /* ..snip..*/ },
    "uuid": "918481C6-3272-4BE6-B981-107BA85FA8D9",
    "ManagedSpaceID": 98,
    "type": 4,
    "WallSpace": {
      "id64": 99,
      "ManagedSpaceID": 98,
      "type": 6,
      "uuid": "2C1163D2-081E-4484-9DD0-9385640649BB"
    },
    "pid": [
      78743,
      14348
    ]
  },
  // ..snip..
]

# Just the UUIDs
⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors | map(select(has("Current Space"))) | first | .Spaces | map(select(has("TileLayoutManager")).uuid)'
[
  "0ADEC86B-0C91-47A4-892E-995CA8CC008A",
  "918481C6-3272-4BE6-B981-107BA85FA8D9",
  "9E77A145-5756-44D2-9877-3CD3F3FBB01A"
]

We can see that the spaces of maximised windows (which appear to be type: 4) additionally contain a TileLayoutManager object, a WallSpace object, the pid of the maximised window(s) (either integer, or array of integers), a fs_wid

The TileSpaces in particular seems to contain interesting data, so let's filter in on that.

Here is an example of the TileLayoutManager data for a space that contains a single maximised window:

⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors | map(select(has("Current Space"))) | first | .Spaces | map(select(has("TileLayoutManager") and (.pid | type != "array"))) | first | .TileLayoutManager'
{
  "Age": 483877.12677943,
  "PreferredLayout": [
    {
      "FirstTileSize": {
        "Width": 1792,
        "Height": 1120
      },
      "DisplaySize": {
        "Width": 1792,
        "Height": 1120
      }
    },
    {
      "FirstTileSize": {
        "Width": 3440,
        "Height": 1440
      },
      "DisplaySize": {
        "Width": 3440,
        "Height": 1440
      }
    }
  ],
  "Layout Rect": {
    "Width": 3440,
    "Height": 1440,
    "Y": 0,
    "X": 0
  },
  "Max Tile Config": {
    "Width": 2,
    "Height": 1
  },
  "ReservedArea": {
    "Right": 0,
    "Left": 0,
    "Bottom": 0,
    "Top": 0
  },
  "TileSpaces": [
    {
      "uuid": "242659BA-8816-433B-BB28-27506B129806",
      "id64": 29,
      "TileLimitedClipping": true,
      "TileType": "Primary",
      "ManagedSpaceID": 27,
      "fromSpace": "9160DD0A-9F52-4B1E-A4C9-B4F6ADD92A78",
      "TileWindowID": 168,
      "fs_wid": 168,
      "type": 5,
      "SizeConstraints": {
        "Min": {
          "Width": 86,
          "Height": 29
        },
        "Max": {
          "Width": 100000,
          "Height": 100000
        },
        "Preferred": {
          "Width": 1600,
          "Height": 928
        }
      },
      "appName": "Spotify",
      "TileRect": {
        "Width": 3440,
        "Height": 1440,
        "Y": 0,
        "X": 0
      },
      "pid": 727,
      "name": "Spotify Premium",
      "com.apple.appkit.disablemcexit": false
    }
  ],
  "ConfigAge": 483877.126187566,
  "Inter-Tile Spacing": {
    "Width": 12,
    "Height": 12
  }
}

And this is an example of the TileLayoutManager data for a space that contains a multiple maximised windows:

⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors | map(select(has("Current Space"))) | first | .Spaces | map(select(has("TileLayoutManager") and (.pid | type == "array"))) | first | .TileLayoutManager'
{
  "Age": 480896.491663617,
  "PreferredLayout": [
    {
      "FirstTileSize": {
        "Width": 1965,
        "Height": 1440
      },
      "DisplaySize": {
        "Width": 3440,
        "Height": 1440
      }
    }
  ],
  "Layout Rect": {
    "Width": 3440,
    "Height": 1440,
    "Y": 0,
    "X": 0
  },
  "Max Tile Config": {
    "Width": 2,
    "Height": 1
  },
  "ReservedArea": {
    "Right": 0,
    "Left": 0,
    "Bottom": 0,
    "Top": 0
  },
  "TileSpaces": [
    {
      "uuid": "95AB747E-C62C-4ABE-B274-687250EC2592",
      "id64": 818,
      "TileLimitedClipping": true,
      "TileType": "Primary",
      "ManagedSpaceID": 98,
      "fromSpace": "671BB3AE-0E2C-42C9-B259-C5004B30DEE1",
      "TileWindowID": 42954,
      "fs_wid": 42954,
      "type": 5,
      "SizeConstraints": {
        "Min": {
          "Width": 727,
          "Height": 480
        },
        "Max": {
          "Width": 100000,
          "Height": 100000
        },
        "Preferred": {
          "Width": 1965,
          "Height": 1440
        }
      },
      "appName": "Fantastical",
      "TileRect": {
        "Width": 1965,
        "Height": 1440,
        "Y": 0,
        "X": 0
      },
      "pid": 78743,
      "name": "Fantastical",
      "com.apple.appkit.disablemcexit": false
    },
    {
      "uuid": "94253DF6-03DB-49FB-8608-BD86932B4267",
      "id64": 108,
      "TileLimitedClipping": true,
      "TileType": "Primary",
      "ManagedSpaceID": 98,
      "fromSpace": "9160DD0A-9F52-4B1E-A4C9-B4F6ADD92A78",
      "TileWindowID": 1111,
      "fs_wid": 1111,
      "type": 5,
      "SizeConstraints": {
        "Min": {
          "Width": 351,
          "Height": 524
        },
        "Max": {
          "Width": 100000,
          "Height": 100000
        },
        "Preferred": {
          "Width": 1463,
          "Height": 1440
        }
      },
      "appName": "Reminders",
      "TileRect": {
        "Width": 1463,
        "Height": 1440,
        "Y": 0,
        "X": 1977
      },
      "pid": 14348,
      "name": "Reminders",
      "com.apple.appkit.disablemcexit": false
    }
  ],
  "ConfigAge": 216451.82905787101,
  "Inter-Tile Spacing": {
    "Width": 12,
    "Height": 12
  }
}

Notice that the TileLayoutManager's TileSpaces array appears to have an entry for each maximised window, and they appear to be of type: 5. Within that are keys including:

  • SizeContraints object that has Min/Max/Preferred objects, each with a Width/Height
  • TileRect object contains the Width/Height/X/Y of each window (which can also be used to determine which maximised window is on the left, and which on the right)
  • pid/name/appName contains the process id / name of the application
  • fromSpace appears to contain the UUID that the window was maximised from
  • etc

Based on the above, we can filter some of the noise down to easily see the which apps are the maximised window(s) that are in each of these spaces, the window size, the uuid of the space the windows came from, and the uuid for that 'maximised window' space, among other things:

⇒ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data".Monitors | map(select(has("Current Space"))) | first | .Spaces | map(select(.TileLayoutManager) | (.TileLayoutManager.TileSpaces | map(pick(.type, .uuid, .fromSpace, .pid, .appName, .name, .TileRect))) as $TileSpaces | pick(.type, .uuid) + { TileLayoutManager: { $TileSpaces } } )'
[
  {
    "type": 4,
    "uuid": "0ADEC86B-0C91-47A4-892E-995CA8CC008A",
    "TileLayoutManager": {
      "TileSpaces": [
        {
          "type": 5,
          "uuid": "242659BA-8816-433B-BB28-27506B129806",
          "fromSpace": "9160DD0A-9F52-4B1E-A4C9-B4F6ADD92A78",
          "pid": 727,
          "appName": "Spotify",
          "name": "Spotify Premium",
          "TileRect": {
            "Width": 3440,
            "Height": 1440,
            "Y": 0,
            "X": 0
          }
        }
      ]
    }
  },
  {
    "type": 4,
    "uuid": "918481C6-3272-4BE6-B981-107BA85FA8D9",
    "TileLayoutManager": {
      "TileSpaces": [
        {
          "type": 5,
          "uuid": "95AB747E-C62C-4ABE-B274-687250EC2592",
          "fromSpace": "671BB3AE-0E2C-42C9-B259-C5004B30DEE1",
          "pid": 78743,
          "appName": "Fantastical",
          "name": "Fantastical",
          "TileRect": {
            "Width": 1965,
            "Height": 1440,
            "Y": 0,
            "X": 0
          }
        },
        {
          "type": 5,
          "uuid": "94253DF6-03DB-49FB-8608-BD86932B4267",
          "fromSpace": "9160DD0A-9F52-4B1E-A4C9-B4F6ADD92A78",
          "pid": 14348,
          "appName": "Reminders",
          "name": "Reminders",
          "TileRect": {
            "Width": 1463,
            "Height": 1440,
            "Y": 0,
            "X": 1977
          }
        }
      ]
    }
  },
  // ..snip..
]
@vsuharnikov
Copy link

Thanks for the work you've done. But paradoxically, editing these settings does not affect anything. I have three monitors, and the main one is external, not built-in. Following the instructions, I specify that Discord should be launched on the main monitor. I close Finder, launch Discord from the third monitor, it launches on the internal monitor, and the Monitor 1 checkbox is indicated in Finder.
I'm surprised how bad the window manager is on macOS. It is literally 20 years behind what Linux offers. It pains me to watch smart people like you and @koekeishiya try with great effort to do something sane. Apple is terrible, and nothing seems to help. The system literally does everything to make me counterproductive.

@0xdevalias
Copy link
Author

But paradoxically, editing these settings does not affect anything.

@vsuharnikov It's been a while since I looked at this, but I think it was working for me when I was first researching it. Though to be fair, I didn't test it too deeply, as for most of my use cases currently (of which there are only a couple), I would just manually do it through the UI. From memory, I think the reason I wanted to figure out of it was possible was because sometimes it glitches out, and a hacky fix I found was to set it back to single desktop, then all desktops again; so wanted to script that.

I have three monitors, and the main one is external, not built-in.

@vsuharnikov I only have a single external, and mostly use my laptop with the lid closed, so usually it's only really having to 'reason about' a single screen (with multiple spaces); not sure if that will make a difference at all.

Following the instructions, I specify that Discord should be launched on the main monitor. I close Finder, launch Discord from the third monitor, it launches on the internal monitor, and the Monitor 1 checkbox is indicated in Finder.

@vsuharnikov I'm guessing that 'Monitor 1' in Finder isn't the same as the 'main monitor' you wanted it to show on? Does it work properly if you set it purely from the finder UI? And if so, if you then dump the plist settings/etc from that state, does it match what you were trying to set it to before?

I've also found, even just using it natively, that there are some apps that just don't seem to work well with the feature, and seem to ignore it. I'm not really sure what the core issue is that causes that though.

I'm surprised how bad the window manager is on macOS. It is literally 20 years behind what Linux offers.

@vsuharnikov Yeah, unfortunately that doesn't surprise me. I feel like a lot of macOS users tend to be fine with using things mostly 'as is'; whereas linux attracts people who want full customisation/tweakability; so for the relatively more niche (compared to mainstream) needs, it's likely always going to be much further ahead.

It pains me to watch smart people like you and @koekeishiya try with great effort to do something sane. Apple is terrible, and nothing seems to help. The system literally does everything to make me counterproductive.

@vsuharnikov Ha, yeah.. sometimes it definitely feels that way, particularly when I run into something that feels overly limiting, or so basic that I can't imagine why it wasn't there from the beginning (like a 'move to top' in the default Reminders app), or that just seems like a weird/poor oversight (like the official SDKs supporting half the features, but not a bunch of other ones; eg. tags in Reminders).

But that said, on the whole, I still quite like the macOS ecosystem; and for the most part, even where those limitations/'rough edges' exist, it usually doesn't end up being an 'end of the world' impact on my productivity. Sometimes I can hack around it; sometimes it takes a few failed attempts of frustration before I figure out how; sometimes I just need to learn to live with it.

@vsuharnikov
Copy link

I'm guessing that 'Monitor 1' in Finder isn't the same as the 'main monitor' you wanted it to show on?

Right, the main display (sorry, for mixing terminology, it is "Display", not a "Monitor") is "Display 2". My configuration is:

❯ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '.SpacesDisplayConfiguration."Management Data"."Monitors" | .[] | ."Display Identifier", ."Current Space"'
"Main" // Display 1, secondary
{
  "id64": 3,
  "ManagedSpaceID": 3,
  "type": 0,
  "uuid": "BFFCA3DA-B326-4DF0-B73F-35C3ED8C372D"
}
"97A47193-87DC-4B29-8DA7-5EF235A31011" // Display 2, main
{
  "id64": 1023,
  "ManagedSpaceID": 1023,
  "type": 0,
  "uuid": "D857D6B4-F575-4754-ADE2-210389F5BBEC"
}
"4A37E72E-CD4A-44F2-B128-A451F65EA409" // Display 3, secondary
{
  "id64": 1024,
  "ManagedSpaceID": 1024,
  "type": 0,
  "uuid": "C3E3CCD2-F91F-42B0-86E2-BA0B2A04E475"
}

Does it work properly if you set it purely from the finder UI?

It works if I stop yabai. Something strange happens otherwise, it seems a bug in yabai :)

And if so, if you then dump the plist settings/etc from that state, does it match what you were trying to set it to before?

Yep. For example (yabai is closed):

❯ open -a Notes # Opens on "Display 3"
❯ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '."app-bindings"."com.apple.notes"'
"C3E3CCD2-F91F-42B0-86E2-BA0B2A04E475" # It's "Current space" on "Display 3" ✅

# Assign to "This Desktop" of "Display 1"
❯ /usr/libexec/PlistBuddy -c "Set :app-bindings:com.apple.notes BFFCA3DA-B326-4DF0-B73F-35C3ED8C372D" ~/Library/Preferences/com.apple.spaces.plist
❯ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '."app-bindings"."com.apple.notes"'
"BFFCA3DA-B326-4DF0-B73F-35C3ED8C372D"
# The app is still on "Display 3"
❯ killall Dock
# The app is still on "Display 3", same UUID in "app-bindings", in I see in Dock two checked options for Notes: "Desktop on Display 3" and "None"

❯ killall Notes
❯ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '."app-bindings"."com.apple.notes"'
"BFFCA3DA-B326-4DF0-B73F-35C3ED8C372D" # Still the same ✅

❯ open -a Notes
❯ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '."app-bindings"."com.apple.notes"'
"C3E3CCD2-F91F-42B0-86E2-BA0B2A04E475" # Changed to "Display 1" ❗️

# Let's assign again when the app is closed
❯ killall Notes
❯ /usr/libexec/PlistBuddy -c "Set :app-bindings:com.apple.notes BFFCA3DA-B326-4DF0-B73F-35C3ED8C372D" ~/Library/Preferences/com.apple.spaces.plist
❯ plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '."app-bindings"."com.apple.notes"'
"BFFCA3DA-B326-4DF0-B73F-35C3ED8C372D"
❯ open -a Notes
plutil -convert json -o - ~/Library/Preferences/com.apple.spaces.plist | jq '."app-bindings"."com.apple.notes"'
"C3E3CCD2-F91F-42B0-86E2-BA0B2A04E475" # Changed to "Display 1" ❗️

Anyway, Sequoia comes, probably something will be changed.

@0xdevalias
Copy link
Author

0xdevalias commented Jun 16, 2024

It works if I stop yabai. Something strange happens otherwise, it seems a bug in yabai :)

@vsuharnikov True true. I don't use yabai (I tend to just do my window management with BetterTouchTool), so unfortunately can't help figure that part out at all.

# The app is still on "Display 3", same UUID in "app-bindings", in I see in Dock two checked options for Notes: "Desktop on Display 3" and "None"

@vsuharnikov Interesting that there are 2 checked options there.. looking in the plists, can you see 2 entries that might explain that? Or if you dump the whole plist, then manually change it in the macOS GUI so only a single tick is shown (ideally a 3rd option that wasn't already ticked), then dump the plist again and diff them; that might help show what entries were there and potentially causing the weird state.

# Changed to "Display 1"

@vsuharnikov Oh interesting. That's good to know that we need to kill/close the app as well to make it apply.

I'm not 100% sure if the killall Dock part was something that I figured out actually needed to be done, or if I was just assuming that it might be based on the plist files we were changing (and how in some other cases like that we need to kill the process to force it to re-read them from disk rather than use what it may have cached in memory already)

I guess in some sense it kind of makes sense that the app may need to be closed as well before that setting will 'take effect', particularly if the app (or more likely the underlying UI framework) is looking at those plist settings to determine where to position itself. Would probably need to do some deeper digging/reverse engineering if we wanted to try and figure out/confirm the specific mechanisms there.

What is interesting though, is that if I make a change through the macOS GUI, it seems to apply straight away, without needing to close/kill the Dock or the app itself; so it would be interesting to figure out what method is used to 'communicate' that change to the app to make it move straight away.

eg. If I open Notes on my 'Desktop 1' space, switch to 'Desktop 2' space, and then from the Dock, right click on Notes -> Options -> This Desktop; it will assign it to 'Desktop 2' (since that is the space I am currently on), and the Notes window jumps to that space. If I then switch back to the 'Desktop 1' space, and look at the Dock menu again, it shows the options: All Desktops, This Desktop, Desktop 2 (which is ticked), None.

Sequoia comes, probably something will be changed.

@vsuharnikov Haha, quite possibly. Though thankfully many of the 'deeper internals' type things I've looked into over the years often don't change too dramatically; so even if it does, hopefully it won't take much extra effort to figure it out :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment