Last active
September 7, 2025 16:59
-
-
Save phoolish/e2084fb7da682f1f5d30a7ba97b57f6a to your computer and use it in GitHub Desktop.
Hammerspoon lua script to enable DnD Focus on Mac on lockscreen
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
| --[[ | |
| 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