In macOS, if you click on an application’s icon in the dock, it’ll either:
- open it, if it isn’t already running,
- gently bring it to the foreground if it has a window in the current space, or
- violently throw you to who knows where if the application has a window open in a different space that you forgot about.
The third behavior always drove me nuts, although I can see why you’d prefer it, e.g. if you associate particular applications with specific spaces.
You can get around this by remembering to always right-click the icon and select “New Window” if the application supports this.
But the unpredictability always annoyed me to no end.
I wrote a cursed Hammerspoon script today to get around this.
It prevents macOS from throwing you to a different space and instead opens a new window in the current one.
It only works for apps hardcoded in the newWindowCommands
table;
in a previous version I tried sending make new window
by default, but sometimes that would cause macOS to act like I held down the mouse button on the dock icon, which is weird.
🚨 Warning: this code will run any time you click anywhere.
It will add a delay, albeit a very small one, to how long it takes macOS to respond to mouse clicks.
I don’t actually recommend using this script unless you’re like me and the costs outweigh the benefits.
I like to use different spaces for different work, which means that I’ll have e.g. Firefox open in two different spaces.
I personally don’t notice the delay at all, but caveat emptor.
If you comment in the block which only enables the new behavior when the Shift key is held down, the impact on your clicks should be even lower.
function initNewWindowShortcut()
local fileNewWindow = {
menu = "File",
item = "New Window",
}
local newWindowCommands = {
Safari = "make new document",
TextEdit = "make new document",
["Google Chrome"] = "make new window",
["Microsoft Edge"] = "make new window",
kitty = {
menu = "Shell",
item = "New OS Window",
},
Firefox = fileNewWindow,
["Firefox Nightly"] = fileNewWindow,
Orion = fileNewWindow,
["Orion RC"] = fileNewWindow,
Mail = {
menu = "File",
item = "New Viewer Window",
},
Things = {
menu = "File",
item = "New Things Window",
},
}
local log = hs.logger.new("newWindow", "debug")
local bundleIDCache = {}
function handler(event)
local element = hs.axuielement.systemElementAtPosition(hs.mouse.absolutePosition())
if not element or element.AXRole ~= "AXDockItem" then
return false
end
local title = element.AXTitle
local infoPlistPath = element.AXURL.filePath .. "/Contents/Info.plist"
local bundleID = bundleIDCache[infoPlistPath]
if not bundleID then
bundleID = hs.plist.read(infoPlistPath).CFBundleIdentifier
end
local script
local command = newWindowCommands[title]
if not command then
return false
end
if type(command) == "table" then
script = [[
tell application "System Events" to tell process "]] .. title .. [["
click menu item "]] .. command.item .. [[" of menu 1 of menu bar item "]] .. command.menu .. [[" of menu bar 1
activate
end tell
]]
else
script = [[
tell application "]] .. element.AXTitle .. [["
]] .. command .. "\n" .. [[
activate
end tell
]]
end
local app = hs.application.find(bundleID, true, true)
if not app or #app:allWindows() > 0 then
return false
end
log.i(script)
hs.osascript.applescript(script)
app:activate()
return true
end
local tap = hs.eventtap.new({ hs.eventtap.event.types.leftMouseDown }, function(event)
local ok, deleteOrError = pcall(function()
return handler(event)
end)
if not ok then
log.e(deleteOrError)
return false
else
return deleteOrError
end
end)
tap:start()
return tap
end
newWindowTap = initNewWindowShortcut()
In the future, it’d be nice to make this work for all methods of activating an app, e.g. opening an app through Spotlight.