2

I'd like to assign a keyboard shortcut to execute the following logic:

Find empty space (with no windows, so just my desktop) and go to it. If there is no empty space, create one and then go to it. I use yabai and skhd, which might help. I think there is a way to do it with yabai, but it would require disabling System Integrity Protection, which I don't want to do.

1 Answer 1

3

Holy, this was quite a ride to achieve seemingly such a simple result.


TLDR

Requirements:

  • yabai - for getting info about spaces
  • skhd or something else for connecting keyboard shortcuts with shell scripts
  • jq - for parsing json output in the shell
  • (optionally) SpaceLauncher - if you use it anyway and would like the shortcut to be e.g. Space+D ("D" like Desktop)

If you don't use yabai then installing all of the above may be too much. This solution is perhaps only acceptable if you already use yabai or intend to use it.

Good news - my solution does not require disabling SIP (System Integrity Protection).

1. Ok, let's go. Create the files below in some folder (I put them in ~/.local/scripts/).

get_empty_space.sh

#!/bin/bash

my_array=($(yabai -m query --spaces --display 1 | jq '.[].windows | any'))
any_space_empty=false
for ((i = 0; i < ${#my_array[@]}; i++)); do
    if ! ${my_array[i]}; then
        echo $((i + 1))
        any_space_empty=true
        break
    fi
done

if ! $any_space_empty; then
    echo false
fi

create_space.sh

open -a 'Mission Control'
sleep 0.1
osascript -e 'tell application "System Events" to click (every button whose value of attribute "AXDescription" is "add desktop") of group 2 of group 1 of group 1 of process "Dock"'
sleep 0.1
osascript -e 'tell application "System Events" to key code 53'

switch_space.sh

#!/bin/bash

declare -a desktophash
desktophash[1]=18
desktophash[2]=19
desktophash[3]=20
desktophash[4]=21
desktophash[5]=23
desktophash[6]=22
desktophash[7]=26
desktophash[8]=28
desktophash[9]=25
desktophash[10]=29
desktopkey=${desktophash[$1]}
osascript -e "tell application \"System Events\" to key code $desktopkey using option down"

go_to_desktop.sh

#!/bin/bash

empty_space=$(/Users/yourname/.local/scripts/get_empty_space.sh)
if [ "$empty_space" == "false" ]; then
    /Users/yourname/.local/scripts/create_space.sh
    sleep 0.15
fi
empty_space=$(/Users/yourname/.local/scripts/get_empty_space.sh)
/Users/yourname/.local/scripts/switch_space.sh $empty_space

2. Next, give the files above executive permissions.

chmod +x ~/.local/scripts/get_empty_space.sh
chmod +x ~/.local/scripts/create_space.sh
chmod +x ~/.local/scripts/switch_space.sh
chmod +x ~/.local/scripts/go_to_desktop.sh

3. Assign macOS system shortcuts for spaces.

  • Create spaces/desktops so that at least 10 exist before going to the next step.
  • Go to System Preferences > Keyboard > Shortcuts > Mission Control.
  • Assign shortcuts for all 10 Switch to Desktop N entries.
  • The shortcuts that do not require changing my code above are Opt+N, so Opt+1 goes to space 1 and so on. You can choose different shortcuts but then you will have to modify switch_space.sh correspondingly, most likely just the last line of it.

4. Assign a shortcut in skhd to run go_to_desktop.sh script.

In .skhdrc file add a line like this:

f4 : /Users/yourname/.local/scripts/go_to_desktop.sh

4a. (optional step) In SpaceLauncher bind Space+D to press F4.

  • Key: D
  • Action: Simulate Keystroke
  • Key: F4

5. Press the shortcut - you might need to add some permissions to skhd before it starts to work.


Explanation and debugging

1. If you have a different OS language than US English, you might need to modify create_space.sh script.

Based on the StackOverflow thread where I got my script from, the script below has good chances of working, but is not tested:

open -b 'com.apple.exposelauncher'
sleep 0.5
osascript -e 'tell application id "com.apple.systemevents" tell (every application process whose bundle identifier = "com.apple.dock") to click (button 1 of group 2 of group 1 of group 1)'
sleep 0.5
osascript -e 'key code 53 -- esc key'

The AppleScript from the thread:

do shell script "open -b 'com.apple.exposelauncher'"
delay 0.5
tell application id "com.apple.systemevents"
    tell (every application process ¬
        whose bundle identifier = "com.apple.dock") to ¬
        click (button 1 of group 2 of group 1 of group 1)
    delay 0.5
    key code 53 -- esc key
end tell

2. You might need to increase the sleep times in create_space.sh and go_to_desktop.sh for it to work properly on your mac.

By trial and error and starting from 0.5 seconds, I got down to the times in my scripts. I use an M1 MacBook Pro.

3. A short explanation of the scripts.

You can test all the scripts separately to see if they work and what they do.

  • create_space.sh - self explanatory. The space created will be at the end.
  • switch_space.sh - mimicks clicking Opt+N on the keyboard. It is used like ./switch_space.sh 5 to go to space no. 5.
  • get_empty_space.sh - outputs the integer number of an empty space. If there is no empty space it outputs false. The output of this will be an input to switch_space.sh.
  • go_to_desktop.sh - the code I think is reasonably readable to explain itself.

Most of the magic happens in the 1st line of get_empty_space.sh. The function yabai -m query --spaces --display returns something like:

[{
    "index":1,
    "windows":[1350901],
    ...
},
...
]

The windows key of every space can tell us whether there are some windows there or not. We pass it to jq with this line yabai -m query --spaces --display | jq '.[].windows' and get just the windows lists.

[
  1350901
]
[
  1357661
]
...

Enhance it further with | any and this line yabai -m query --spaces --display | jq '.[].windows | any' gives us a list of booleans, where true means there are some windows in the given space.

true
true
...

The rest is quite standard bash.


Final thoughts

Isn't it too much for such a "simple" task? It may be. It could probably be achieved with just one bash script instead of 4, but:

  • a) I spent a lot of time learning bash to make it work and this way was easier for me
  • b) those scripts do just one small task each and I expect to find a use for them in other automations as well

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .