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