[hammerspoon] rewrite quitter
change
commit 7c90a58c36c248e0f3606a0189f2bdf08bee9ecf
author Alpha Chen <alpha@kejadlen.dev>
date
parent 41bc4d6b
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}