local state = "unknown" local pollTimer = nil local manualOverride = nil -- Home Assistant webhooks local busyUrl = "http://192.168.1.2:8123/api/webhook/meeting_busy" local freeUrl = "http://192.168.1.2:8123/api/webhook/meeting_free" local offlineUrl = "http://192.168.1.2:8123/api/webhook/meeting_offline" -- Menu bar item local menuBar = hs.menubar.new() function post(url) hs.http.asyncPost(url, "", {}, function() end) end -- Refresh menu bar appearance and dropdown function refreshMenu() local icon = "❔" if state == "busy" then icon = "🔴" elseif state == "free" then icon = "🟢" elseif state == "offline" then icon = "⚫" end menuBar:setTitle(icon) local modeText if manualOverride then modeText = string.format("%s (Manual Override)", state) else modeText = string.format("%s (Automatic)", state) end menuBar:setMenu({ { title = "Current Status: " .. modeText, disabled = true }, { title = "-" }, { title = "Set Busy", fn = function() manualOverride = "busy" print("Manual Override: BUSY") setState("busy") end }, { title = "Set Free", fn = function() manualOverride = "free" print("Manual Override: FREE") setState("free") end }, { title = "Set Offline", fn = function() manualOverride = "offline" print("Manual Override: OFFLINE") setState("offline") end }, { title = "-" }, { title = "Resume Automatic Monitoring", fn = function() manualOverride = nil state = "unknown" print("Manual Override Cleared") checkStatus() end }, { title = "-" }, { title = "Reload Hammerspoon", fn = function() hs.reload() end } }) menuBar:setTooltip("Meeting Status: " .. modeText) end function setState(newState) if state == newState then refreshMenu() return end state = newState if newState == "busy" then print("BUSY") post(busyUrl) elseif newState == "free" then print("FREE") post(freeUrl) elseif newState == "offline" then print("OFFLINE") post(offlineUrl) end refreshMenu() end function checkStatus() -- Skip automatic checks while manual override is active if manualOverride then return end local mic = hs.audiodevice.defaultInputDevice() -- Mic in use = Busy if mic and mic:inUse() then setState("busy") return end -- Teams open but not using mic = Free local teamsRunning = hs.application.get("Microsoft Teams") ~= nil if teamsRunning then setState("free") else setState("offline") end end function startPolling() if pollTimer then pollTimer:stop() end pollTimer = hs.timer.doEvery(5, checkStatus) -- Run immediately checkStatus() end -- Start monitoring refreshMenu() startPolling() -- Monitor sleep/wake and session events local caffeineWatcher = hs.caffeinate.watcher.new(function(event) if event == hs.caffeinate.watcher.systemDidWake or event == hs.caffeinate.watcher.sessionDidBecomeActive then print("Restarting status monitor after wake/unlock") -- Force next update state = "unknown" startPolling() end end) caffeineWatcher:start() ------------------------------------------------------------------ -- Hotkeys ------------------------------------------------------------------ hs.hotkey.bind({"ctrl", "alt", "cmd"}, "B", function() manualOverride = "busy" print("Manual Override: BUSY") setState("busy") end) hs.hotkey.bind({"ctrl", "alt", "cmd"}, "F", function() manualOverride = "free" print("Manual Override: FREE") setState("free") end) hs.hotkey.bind({"ctrl", "alt", "cmd"}, "O", function() manualOverride = "offline" print("Manual Override: OFFLINE") setState("offline") end) hs.hotkey.bind({"ctrl", "alt", "cmd"}, "R", function() manualOverride = nil state = "unknown" print("Manual Override Cleared") checkStatus() end)