Skip to content

Instantly share code, notes, and snippets.

@phoolish
Last active September 7, 2025 16:59
Show Gist options
  • Save phoolish/e2084fb7da682f1f5d30a7ba97b57f6a to your computer and use it in GitHub Desktop.
Save phoolish/e2084fb7da682f1f5d30a7ba97b57f6a to your computer and use it in GitHub Desktop.
Hammerspoon lua script to enable DnD Focus on Mac on lockscreen
--[[
DnD Automation Script for Hammerspoon
Purpose:
Automatically enables Do Not Disturb when the screen is locked and disables it
when unlocked, but only if Hammerspoon was the one that enabled it originally.
This prevents interfering with manually-set DnD preferences.
Requirements:
- Hammerspoon installed and running
- My shortcut https://www.icloud.com/shortcuts/2a4ae272a3104373a230065059500794, but any shortcuts app shortcut named "Focus" that accepts:
- "on" - enables Do Not Disturb
- "off" - disables Do Not Disturb
- "state" - returns current DnD state
Behavior:
- Screen Lock: Check current DnD state, enable if off, track that we enabled it
- Screen Unlock: Only disable DnD if we previously enabled it automatically
- Manual DnD: If user manually enables DnD, we won't disable it on unlock
Logging:
- Warning level by default (quiet operation)
- Shows startup, successes, and any errors
- Log level available are 'info', 'debug', 'warning', or 'error'
Author: phoolish & Claude AI
Based on https://mskelton.dev/bytes/managing-focus-with-hammerspoon
--]]
local log = hs.logger.new('dnd-automation', 'warning')
-- Track if we enabled DnD automatically
local hammerspoonEnabledDnd = false
local function focus(state)
return function()
local command = "shortcuts run 'Focus' <<<'" .. state .. "'"
local output, status, type, rc = hs.execute(command, true)
if not status then
log.e("Focus shortcut failed for state: " .. state)
else
log.i("Focus changed to: " .. state)
end
return output, status
end
end
local function getCurrentFocusState()
local output, status = focus("state")()
if status and output then
local trimmed = output:match("^%s*(.-)%s*$") -- Trim whitespace
-- Check if DnD is currently active
if trimmed:lower():find("do not disturb") or trimmed:lower():find("dnd") then
return "on"
else
return "off"
end
else
log.e("Failed to get current focus state")
return "unknown"
end
end
hs.caffeinate.watcher.new(function(eventType)
if eventType == hs.caffeinate.watcher.screensDidLock then
local currentState = getCurrentFocusState()
if currentState ~= "on" then
focus("on")()
hammerspoonEnabledDnd = true -- Mark that we enabled it
else
hammerspoonEnabledDnd = false -- We didn't enable it
end
elseif eventType == hs.caffeinate.watcher.screensDidUnlock then
local currentState = getCurrentFocusState()
if currentState == "on" and hammerspoonEnabledDnd then
focus("off")()
hammerspoonEnabledDnd = false -- Reset flag
elseif currentState == "on" and not hammerspoonEnabledDnd then
log.i("DnD active but was manually enabled, leaving on")
else
hammerspoonEnabledDnd = false -- Reset flag just in case
end
end
end):start()
log.w("DnD automation watcher started")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment