Skip to content

Instantly share code, notes, and snippets.

@philsnow
Created March 15, 2023 18:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save philsnow/abd71ec9a056976d1a24248b7bc1c913 to your computer and use it in GitHub Desktop.
Save philsnow/abd71ec9a056976d1a24248b7bc1c913 to your computer and use it in GitHub Desktop.
Switching spaces in Hammerspoon, without showing the Mission Control UI

Wherein I continue to try to recreate my long-loved but long-lost Linux window manager UX.

Switching spaces in Hammerspoon, without showing the Mission Control UI

The bits of MacOS that handle space naming and switching (Mission Control? Dock.app?) aren't publicly consumable / there isn't a public API for them. That sucks because it leads to workarounds, some of them harmful, like the (otherwise-fantasic) yabai needing SIP to be disabled for full functionality (it's in the very name, "yabai" means dangerous).

In Hammerspoon it works differently. For several of its operations, hs.spaces brings up the Mission Control UI and then scrapes its accessibility axuielements, then interacts with those. This works and I appreciate that I don't have to disable SIP... but it's slow and causes distracting stuff to briefly appear on the screen.

I miss how instantaneous switching workspaces is in XMonad, ion3, pretty much any X windows window manager. I don't really use any of the other parts of Mission Control (I don't use the weird-to-me concept of a "full screen" app just making itself a new Space, I don't name spaces or ever really re-order them), so maybe there's something I can do to make switching spaces faster?

I finally realized that since I already have system keystrokes that switch between spaces

I can just make Hammerspoon emit those keystrokes instead of using hs.spaces.gotoSpace(). The only issue is that I have to do a little bit of bookkeeping to keep track of space numbers.

This should work even if you use different shortcuts like the common "hyper" thing where you twist your hand into a claw because MacOS doesn't have a modifier key like Mod4 that you can dedicate to window management, you just have to change the hs.eventtap.keyStroke call in previous_spaces.lua.

local current_spaces = {} -- map<screen_UUID -> current space id>
local previous_spaces = {} -- map<screen_UUID -> previous space id>
local function update_previous_spaces()
local active_ids = hs.spaces.activeSpaces() -- map<screen_UUID -> active_space_id>
local all_spaces = hs.spaces.allSpaces() -- map<screen_UUID -> all_space_ids_on_screen>
previous_spaces = current_spaces
current_spaces = {}
for scr, this_screen_spaces in pairs(all_spaces) do
local active_space_id = active_ids[scr]
for i, space_id in ipairs(this_screen_spaces) do
if space_id == active_space_id then
current_spaces[scr] = i
break
end
end
end
end
update_previous_spaces()
local function go_to_previous_space()
local main_screen_uuid = hs.screen.mainScreen():getUUID()
local previous_space = previous_spaces[main_screen_uuid]
if previous_space ~= nil then
-- note, this is where you have to translate the screen number into a keystroke
hs.eventtap.keyStroke({"cmd"}, tostring(previous_space))
end
end
local spaces_watcher = hs.spaces.watcher.new(update_previous_spaces):start()
local module = {
-- bind this function to cmd+` or so:
["go_to_previous_space"] = go_to_previous_space,
-- exported so it can be referenced in init.lua to avoid GC:
["spaces_watcher"] = spaces_watcher,
}
return module
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment