Hammerspoon implementation to restore organization of windows throughout spaces on MacOS.
- Better than DisplayMaid because it cycles through every space to save the state of all of them.
- Less resource-intensive than Workspaces.
- No need to disable SIP like yabai.
-
If you do not have Homebrew yet, install it and install Hammerspoon for Mac automation:
brew install --cask hammerspoon
already including the spaces module in its latest version.
⚠️ Note: As of macOS 14.5, somespaces
functions do not work correctly on Hammerspoon 0.9.100 and below. As a temporary solution, the Hammerspoon build by usergartnera
can be used instead. For Hammerspoon 1.0.0 and above, this has been fixed (vide item Sonoma 14.5 in Known Issues).
-
Run
install.sh
to copy theinit.lua
file and therestore_spaces
folder and into your.hammerspoon
directory:zsh install.sh
or just copy
restore_spaces
into.hammerspoon/hs
, and import the module in your own.hammerspoon/init.lua
file to avoid conflicts with other modules.
-
Set your preferred configurations and hotkey combinations, for example:
local hs = {} hs.hotkey = require "hs.hotkey" hs.restore_spaces = require 'hs.restore.spaces.restore_spaces' -- Configure 'restore_spaces' hs.restore_spaces.verbose = false hs.restore_spaces.space_pause = 0.3 hs.restore_spaces.screen_pause = 0.4 -- Bind hotkeys for 'restore_spaces' hs.hotkey.bind({"cmd", "alt", "ctrl"}, "S", hs.restore_spaces.saveEnvironmentState) hs.hotkey.bind({"cmd", "alt", "ctrl"}, "A", hs.restore_spaces.applyEnvironmentState)
- Open the Hammerspoon app, enable it in Accessibility, restart it and select
Reload Config
.
-
Run the commands a few times to check whether the
space_pause
andscreen_pause
settings comply with your mac. They might need to be increased if the console prints:... attempt to index a nil value (local 'child') stack traceback: ...: in function 'hs.spaces.gotoSpace' ...
-
Run the commands a few times with multi-tab apps open to check whether the settings of the function defined in
getVisibleTabs.applescript
comply with your mac. In particular, thetabDelay
setting inscp/scp_config.json
might need to be increased if cycling through tabs seems to be stopping before all tabs are cycled through. This is can be true if, e.g. loading unloaded tabs takes longer than the value intabDelay
. For more information, vide item Window IDs change... in Known Issues.
-
Press the "save" hotkey combination to save current state.
e.g.: Ctrl + Opt + Cmd + S
-
Press the "apply" hotkey combination to restore that state.
e.g.: Ctrl + Opt + Cmd + A
Following features have already been implemented:
- Add functionality for multiple monitors (e.g. move space to other monitor).
- Save JSON files with multiple save-state, for different work environments (e.g. office and home office), based on the list of monitors connected to Mac. The saved states are unique for each set and order of monitors (_i.e., an "envirnment").
- Ask name of environment when saving state for inclusion in JSON files.
- Fix restore for when current number of screens is different than the number of saved ones.
- Check functionalities if/when space IDs change with space deletion/creation.
- Add a workaround to deal with the phantom dashboard space (vide Known Issues).
- Fix restore for windows that have empty title.
- Fix restore for a list of spaces that has their order changed, by using a
space_map
variable to store the order of space IDs. - Fix restore for windows that changed their ID after app restart (vide item Window IDs change... in Known Issues).
Current features under development; any help is appreciated:
- Add a
notifyUser
call ifgotoSpace
fails due too short pause setting. - Add better logging to produce debugging reports.
- Fix restore for two windows in Fullscreen, Tile left and Tile right, which current does not work (afaik, cannot be implemented).
- Ensure save/apply do not consider hidden windows.
- Ask if the name of new environment should overwrite an environment already saved.
- Add a user variable that enforces a maximum number of spaces or a list of space indexes, per screen, that are restored by the functions (so that spaces manipulation is limited).
- Modify functions to create "profiles" instead of "environment" states, as to allow for multiply profiles in the same environment.
- Add tests with mocking, due to the dependence on mac features.
- ...
To use a development version of the spaces
module, install it from its
repo package:
- Download
spaces-v0.x-universal.tar.gz
- Extract it in your
.hammerspoon
folder:
cd ~/.hammerspoon
tar -xzf ~/Downloads/spaces-v0.x.tar.gz
-
The "Dashboard" feature may create a hidden space that cannot be accessed, and thus breaks the
hs.spaces.gotoSpace()
method, even in MacOS versions that do not feature Dashboard anymore. This can be see in the spaces plist file, for example:defaults read com.apple.spaces.plist > ... { ManagedSpaceID = 23; id64 = 23; pid = 321; type = 2; uuid = dashboard; } ...
The solution of forcefully deactivating the dashboard has been studied, but it does not work and there are no alternative official solutions so far:
defaults write com.apple.dashboard mcx-disabled -boolean YES killall Dock
Instead, that
plist
file is read and used to validate the list of spaces used to save the environment state. -
There are no official APIs for putting two windows in split-view fullscreen mode, so the current approach is to place all windows that had a fullscreen state (split-view or not) into single fullscreen. Split-view has to be set manually by the user (with the mouse) after calling
applyEnvironmentState
. -
The function
spaces.moveWindowtoSpace
function stopped working in MacOS 14.5. The current solution is to use the Hammerspoon app build by thegartnera
user in Github. This solution does not work when spaces are distributed across multiple screens/monitors, so a follow-up solution was proposed bycunha
, which slightly increases the delay time of processing each Space. To avoid this increase in unnecessary cases, a switch for this is implemented as thespaces_fixed_after_macOS14_5
global variable. This switch has become obsolete after thespaces
extension was updated to work with Sonoma 14.5 in the Hammerspoon repo itself, and will be removed in a future release. -
Window IDs change when you close/open an app. The current implementation is able to restore a window to a desired space during
apply
if that window title is the same as it was during thesave
call. If the title changes for any reason, that window will not be recognized.In the particular case of apps in which windows can have multiple tabs, the title tends to coincide with the title of the tab currently selected. This is taken into account by the module with the
multitab_apps
settings, which specifies for which apps thesave
andapply
functions may cycle through all tabs of each window to gather a list of titles. This is done using a custom function developed inapplescript
. Windows are then restored duringapply
by determining whether they have a counterpart in the saved states, by comparing the current list of tabs with the saved one. Thresholds for what is considered a "counterpart" are defined by themultitab_comparison
setting, by establishing a fraction of the list of tabs that must coincide (regardless of the order). Two fractions are defined, for comparing "short" and "long" tab lists, with the number of tabs that switches between them defined incritical_tab_count
.Unfortunately, the process of cycling through all tabs also loads them, which might be undesireable. No workaround has been found yet.
-
The function defined in
getVisibleTabs.applescript
used to cycle through visible tabs in "multitab apps" needs to be modified to work with multiple monitors. It currently behaves poorly when more than one visible Desktop (i.e. a Space in focus) contains windows of apps defined as "multitab" apps; the applescript cycles through visible tabs in all monitors, in a similar way to how it cycles through visible tabs of multiple windows in a single Desktop. This means the generated tab lists do not distinguish between different displays. Suggestions on how to possibly circumvent this issue are appreciated.