Skip to content

Instantly share code, notes, and snippets.

@kevinmbeaulieu
Created June 14, 2025 17:51
Show Gist options
  • Save kevinmbeaulieu/7630a7878a8405d01ba651f73ac8e640 to your computer and use it in GitHub Desktop.
Save kevinmbeaulieu/7630a7878a8405d01ba651f73ac8e640 to your computer and use it in GitHub Desktop.
Auto-Hide Dock If Windows Overlapping
-- 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