--- === Quitter === -- -- Quits apps if they have not been used since a specified amount of time. local obj = {} obj.__index = obj -- Metadata obj.name = "Quitter" obj.version = "0.1" obj.author = "Alpha Chen " obj.license = "MIT - https://opensource.org/licenses/MIT" obj.logger = hs.logger.new("quitter", "warning") obj.lastFocused = {} obj.state = "stopped" --- Quitter.quitAppsAfter --- Variable --- Table of applications to quit when unused. obj.quitAppsAfter = {} --- Quitter:start() --- Method --- Start Quitter --- --- Parameters: --- * None function obj:start() if self.state == "started" then return end self:reset() -- Reset if we're waking from sleep self.watcher = hs.caffeinate.watcher.new(function(event) if event ~= hs.caffeinate.watcher.systemDidWake then return end self:reset() end) -- Set last focused time for relevant apps self.windowFilter = hs.window.filter.new(false) for app, _ in pairs(self.quitAppsAfter) do self.windowFilter:allowApp(app) end self.windowFilter:subscribe( {hs.window.filter.windowFocused, hs.window.filter.windowUnfocused}, function(window, appName) local name = window:application():name() self.lastFocused[name] = os.time() end) self.timer = hs.timer.doEvery(60, function() self:reap() end):start() self.state = "started" return self end --- Quitter:stop() --- Method --- Stop Quitter --- --- Parameters: --- * None function obj:stop() if self.state == "stopped" then return end self.watcher:stop() self.windowFilter:unsubscribeAll() self.timer:stop() self.state = "stopped" end --- Quitter:toggle() --- Method --- Toggle Quitter --- --- Parameters: --- * None function obj:toggle() if self.state == "started" then self:stop() else self:start() end end function obj:reset() hs.fnutils.ieach(hs.application.runningApplications(), function(app) local name = app:name() local duration = self.quitAppsAfter[name] if duration == nil then return false end self.lastFocused[name] = os.time() end) end function obj:shouldQuit(app) if app == nil then return false end -- Don't quit the currently focused app if app:isFrontmost() then return false end local duration = self.quitAppsAfter[app:name()] if duration == nil then return false end local lastFocused = self.lastFocused[app:name()] if lastFocused == nil then return false end self.logger.d("app: " .. app:name() .. " last focused at " .. lastFocused) return (os.time() - lastFocused) > duration end function obj:reap() self.logger.d("reaping") local frontmostApp = hs.application.frontmostApplication() for appName, _ in pairs(self.quitAppsAfter) do local app = hs.application.get(appName) if self:shouldQuit(app) then hs.notify.new( function() hs.application.open(app:name()) end, { title = "Hammerspoon", informativeText = "Quitting " .. app:name(), withdrawAfter = 2, } ):send() app:kill() self.lastFocused[app:name()] = nil end end end function obj:debug() local lastFocused = hs.fnutils.map( spoon.Quitter.lastFocused, function(t) return os.date("!%Y-%m-%dT%TZ", t) end) print(hs.inspect(lastFocused)) end return obj