Created
June 14, 2025 17:51
-
-
Save kevinmbeaulieu/7630a7878a8405d01ba651f73ac8e640 to your computer and use it in GitHub Desktop.
Auto-Hide Dock If Windows Overlapping
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Auto-hide dock when windows overlap with it, matching iPadOS behavior. | |
-- | |
-- For use with Hammerspoon (https://www.hammerspoon.org) | |
-- Save this as ~/.hammerspoon/init.lua or add to your existing config | |
local dockHidden = false | |
local dockChanging = false | |
local cachedDockBounds = nil | |
local windowWatcher = nil | |
local spaceWatcher = nil | |
local screenWatcher = nil | |
local checkDebounceTimer = nil | |
local DEBOUNCE_DELAY = 0.5 | |
-- Get current dock autohide state | |
local function getDockAutohideState() | |
local result = hs.execute("defaults read com.apple.dock autohide") | |
return result and result:match("1") == "1" | |
end | |
-- Get dock size from system | |
local function getDockSize() | |
local result = hs.execute("defaults read com.apple.dock tilesize") | |
local tilesize = tonumber(result) or 64 | |
return math.max(tilesize + 20, 60) | |
end | |
-- Calculate dock bounds (always assumes visible dock) | |
local function calculateDockBounds() | |
local screen = hs.screen.mainScreen() | |
local screenFrame = screen:fullFrame() -- Use fullFrame to ignore dock autohide changes | |
local dockPosition = hs.execute("defaults read com.apple.dock orientation") | |
if dockPosition then | |
dockPosition = dockPosition:gsub("%s+", "") | |
else | |
dockPosition = "bottom" | |
end | |
local dockSize = getDockSize() | |
if dockPosition == "left" then | |
return { x = screenFrame.x, y = screenFrame.y, w = dockSize, h = screenFrame.h } | |
elseif dockPosition == "right" then | |
return { x = screenFrame.x + screenFrame.w - dockSize, y = screenFrame.y, w = dockSize, h = screenFrame.h } | |
else -- bottom (default) | |
return { x = screenFrame.x, y = screenFrame.y + screenFrame.h - dockSize, w = screenFrame.w, h = dockSize } | |
end | |
end | |
-- Get dock bounds (with caching to prevent feedback loop) | |
local function getDockBounds() | |
if not cachedDockBounds or not dockHidden then | |
cachedDockBounds = calculateDockBounds() | |
end | |
return cachedDockBounds | |
end | |
-- Check if two rectangles overlap | |
local function rectsOverlap(rect1, rect2) | |
-- Only count as overlap if the window's bottom is at least 2 pixels below the dock's top | |
return not ( | |
rect1.x + rect1.w <= rect2.x or | |
rect2.x + rect2.w <= rect1.x or | |
rect1.y + rect1.h <= rect2.y + 2 or | |
rect2.y + rect2.h <= rect1.y | |
) | |
end | |
-- Set dock autohide state | |
local function setDockAutohide(shouldHide) | |
if dockChanging then return end | |
if shouldHide and not dockHidden then | |
dockChanging = true | |
local script = [[ | |
tell application "System Events" | |
tell dock preferences | |
set autohide to true | |
end tell | |
end tell | |
]] | |
local success = hs.osascript.applescript(script) | |
if success then | |
dockHidden = true | |
print("Dock hidden due to window overlap") | |
else | |
hs.execute("defaults write com.apple.dock autohide -bool true") | |
hs.execute("pkill -SIGUSR1 Dock") | |
dockHidden = true | |
print("Dock hidden due to window overlap (fallback)") | |
end | |
hs.timer.doAfter(2.0, function() dockChanging = false end) | |
elseif not shouldHide and dockHidden then | |
dockChanging = true | |
local script = [[ | |
tell application "System Events" | |
tell dock preferences | |
set autohide to false | |
end tell | |
end tell | |
]] | |
local success = hs.osascript.applescript(script) | |
if success then | |
dockHidden = false | |
cachedDockBounds = nil | |
print("Dock shown - no window overlap") | |
else | |
hs.execute("defaults write com.apple.dock autohide -bool false") | |
hs.execute("pkill -SIGUSR1 Dock") | |
dockHidden = false | |
cachedDockBounds = nil | |
print("Dock shown - no window overlap (fallback)") | |
end | |
hs.timer.doAfter(2.0, function() dockChanging = false end) | |
end | |
end | |
-- Check for window overlap with dock (debounced) | |
local function checkWindowOverlapDebounced() | |
if checkDebounceTimer then checkDebounceTimer:stop() end | |
checkDebounceTimer = hs.timer.doAfter(DEBOUNCE_DELAY, checkWindowOverlap) | |
end | |
-- Check for window overlap with dock | |
function checkWindowOverlap() | |
if dockChanging then return end | |
local dockBounds = getDockBounds() | |
local windows = hs.window.visibleWindows() | |
local hasOverlap = false | |
for _, window in ipairs(windows) do | |
local app = window:application() | |
if app then | |
local appName = app:name() | |
local windowTitle = window:title() or "" | |
local skipApps = { | |
Dock = true, Desktop = true, SystemUIServer = true, | |
NotificationCenter = true, ControlCenter = true, WindowServer = true | |
} | |
if not skipApps[appName] and windowTitle ~= "Desktop" and (windowTitle ~= "" or window:isStandard()) then | |
local windowFrame = window:frame() | |
local windowRect = { x = windowFrame.x, y = windowFrame.y, w = windowFrame.w, h = windowFrame.h } | |
if rectsOverlap(windowRect, dockBounds) then | |
hasOverlap = true | |
break | |
end | |
end | |
end | |
end | |
setDockAutohide(hasOverlap) | |
end | |
-- Start watching | |
dockHidden = getDockAutohideState() | |
dockChanging = false | |
windowWatcher = hs.window.filter.new() | |
:setDefaultFilter({}) | |
:setSortOrder(hs.window.filter.sortByFocused) | |
local function onWindowEvent(win, appName, event) | |
if win then checkWindowOverlapDebounced() end | |
end | |
windowWatcher:subscribe({ | |
"windowCreated", | |
"windowDestroyed", | |
"windowMoved", | |
"windowMinimized", | |
"windowUnminimized", | |
"windowFocused", | |
"windowUnfocused", | |
"windowVisible", | |
"windowNotVisible" | |
}, onWindowEvent) | |
spaceWatcher = hs.spaces.watcher.new(function() checkWindowOverlapDebounced() end) | |
spaceWatcher:start() | |
screenWatcher = hs.screen.watcher.new(function() cachedDockBounds = nil; checkWindowOverlapDebounced() end) | |
screenWatcher:start() | |
checkWindowOverlap() | |
print("Hammerspoon dock auto-hide started") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment