From 7c90a58c36c248e0f3606a0189f2bdf08bee9ecf Mon Sep 17 00:00:00 2001 From: Alpha Chen Date: Tue, 28 Jan 2025 17:55:15 -0800 Subject: [PATCH] [hammerspoon] rewrite quitter --- .hammerspoon/Spoons/Quitter.spoon/init.lua | 151 --------------------- .hammerspoon/init.fnl | 28 +--- .hammerspoon/quitter.fnl | 51 +++++++ 3 files changed, 57 insertions(+), 173 deletions(-) delete mode 100644 .hammerspoon/Spoons/Quitter.spoon/init.lua create mode 100644 .hammerspoon/quitter.fnl diff --git a/.hammerspoon/Spoons/Quitter.spoon/init.lua b/.hammerspoon/Spoons/Quitter.spoon/init.lua deleted file mode 100644 index 8c665da..0000000 --- a/.hammerspoon/Spoons/Quitter.spoon/init.lua +++ /dev/null @@ -1,151 +0,0 @@ ---- === 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 diff --git a/.hammerspoon/init.fnl b/.hammerspoon/init.fnl index f0cf112..f5bf6db 100644 --- a/.hammerspoon/init.fnl +++ b/.hammerspoon/init.fnl @@ -16,11 +16,6 @@ (local {: mash : smash : modal-bind} (require :hotkey)) (local {: chomp : paste : replace-selection : run} (require :utils)) -;;; quitter - -(let [{: start} (require :quitter-v2)] - (start)) - ;; debugging ; (hotkey.bind mash "d" #(hs.notify.show (: (hs.window.frontmostWindow) :title) "" "")) @@ -47,23 +42,12 @@ :l nil (fn [] - (with-selection #(.. "[" $1 "]" "(" $2 ")")))]]) - -;; cmd-shift-c: copy current url -; (let [{: activated : deactivated} application.watcher -; firefox (hotkey.new [:cmd :shift] :c #(eventtap.keyStrokes :yy)) -; safari-applescript "tell application \"Safari\" to get URL of front document" -; safari (hotkey.new [:cmd :shift] :c -; #(let [(_ obj _) (osascript.applescript safari-applescript)] -; (pasteboard.setContents obj))) -; per-app {"Firefox Developer Edition" firefox :Safari safari} -; update-app (fn [name event _app] -; (match [(. per-app name) event] -; [app-config activated] (app-config:enable) -; [app-config deactivated] (app-config:disable))) -; watcher (application.watcher.new update-app)] -; ;; hold onto watcher as a global so it doesn't get GC'ed -; (set _G.per-app-watcher (watcher:start))) + (replace-selection #(.. "[" $1 "]" "(" $2 ")")))]]) + +;;; quitter + +(let [quitter (require :quitter)] + (quitter:start)) ;;; Spoons diff --git a/.hammerspoon/quitter.fnl b/.hammerspoon/quitter.fnl new file mode 100644 index 0000000..0b359bf --- /dev/null +++ b/.hammerspoon/quitter.fnl @@ -0,0 +1,51 @@ +(local {: application : caffeinate : logger : timer : window} hs) + +(local log (logger.new :quitter :info)) + +(local to-kill {}) + +(fn kill [app] + (when (not (app:isFrontmost)) + (log.i (.. "killing " (app:name))) + (app:kill))) + +(fn mark [app] + (when (= (app:kind) 1) + (log.i (.. "marking " (app:name))) + (set (. to-kill (app:bundleID)) (timer.doAfter 300 #(kill app))))) + +(fn unmark [win] + (let [app (win:application) + bundle-id (app:bundleID) + t (?. to-kill bundle-id)] + (when (not= t nil) + (log.i (.. "unmarking " (app:name))) + (t:stop) + (set (. to-kill bundle-id) nil)))) + +(fn on-wake [event] + (when (= event caffeinate.watcher.systemDidWake) + (each [_ app (ipairs (application.find ""))] + (mark app)))) + +(local cw (caffeinate.watcher.new on-wake)) + +(local wf (window.filter.new {:Safari false + :Arc false + "Firefox Developer Edition" false + :Ghostty false + :Miniflux false + :Phanpy false + :Obsidian false + :default true})) + +(fn start [] + (log.i :starting) + (cw:start) + (wf:subscribe {window.filter.windowFocused unmark + window.filter.windowUnfocused #(mark ($1:application))})) + +;; use global so this isn't GC'ed +(set _G.quitter {: to-kill}) + +{: start}