[hammerspoon] rewrite quitter
diff --git a/.hammerspoon/Spoons/Quitter.spoon/init.lua b/.hammerspoon/Spoons/Quitter.spoon/init.lua
deleted file mode 100644
index 8c665dab..00000000
--- 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 <alpha@kejadlen.dev>"
-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 f0cf1126..f5bf6dbc 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 00000000..0b359bf2
--- /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}