[hammerspoon] quitter
change
commit 5312b0551c206712beac9422a6352d0f78bc5689
author Alpha Chen <alpha@kejadlen.dev>
date
parent 7a66c444
diff --git a/.hammerspoon/Spoons/Quitter.spoon/init.lua b/.hammerspoon/Spoons/Quitter.spoon/init.lua
new file mode 100644
index 00000000..3e31d695
--- /dev/null
+++ b/.hammerspoon/Spoons/Quitter.spoon/init.lua
@@ -0,0 +1,93 @@
+--- === Quitter ===
+--
+-- Quits configured 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", "debug")
+obj.lastFocused = {}
+
+--- Quitter.quitAppsAfter
+--- Variable
+--- Table of application bundle IDs to quit when unused. Use `mdls -name
+--- --kMDItemCFBundleIdentifier -r /path/to/app` to find unknown bundle IDs.
+
+obj.quitAppsAfter = {}
+
+--- Quitter:start()
+--- Method
+--- Start Quitter
+---
+--- Parameters:
+---  * None
+function obj:start()
+  self:reset()
+
+  -- Reset if we're waking from sleep
+  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
+  hs.window.filter.default:subscribe(hs.window.filter.windowFocused, function(window, appName)
+    local bundleID = window:application():bundleID()
+    if not self.quitAppsAfter[bundleID] then return end
+    self.lastFocused[bundleID] = os.time()
+  end)
+
+  self.timer = hs.timer.doEvery(60, function()
+    self:reap()
+  end):start()
+
+  return self
+end
+
+function obj:reset()
+  hs.fnutils.ieach(hs.application.runningApplications(), function(app)
+    local bundleID = app:bundleID()
+
+    local duration = self.quitAppsAfter[bundleID]
+    if not duration then return false end
+
+    self.lastFocused[bundleID] = os.time()
+  end)
+end
+
+function obj:reap()
+  self.logger.d("reaping")
+  local appsToQuit = hs.fnutils.ifilter(hs.application.runningApplications(), function(app)
+    -- Don't quit the currently focused app
+    if app:isFrontmost() then return false end
+
+    local duration = self.quitAppsAfter[app:bundleID()]
+    if not duration then return false end
+
+    local lastFocused = self.lastFocused[app:bundleID()]
+    if not lastFocused then return false end
+
+    self.logger.d("app: " .. app:name() .. " last focused at " .. lastFocused)
+
+    return (os.time() - lastFocused) > duration
+  end)
+
+  for _,app in pairs(appsToQuit) do
+    hs.notify.new({
+      title = "Hammerspoon",
+      informativeText = "Quitting " .. app:name(),
+      withdrawAfter = 2,
+    }):send()
+    app:kill()
+    self.lastFocused[app:bundleID()] = nil
+  end
+end
+
+return obj
diff --git a/.hammerspoon/init.lua b/.hammerspoon/init.lua
index e9d4dce2..6af5a083 100644
--- a/.hammerspoon/init.lua
+++ b/.hammerspoon/init.lua
@@ -1,7 +1,8 @@
 local mash = {"cmd", "alt", "ctrl"}
 local smash = {"cmd", "alt", "ctrl", "shift"}
 
--- Window management
+-- Window management --
+
 wm = {
   left = hs.layout.left50,
   right = hs.layout.right50,
@@ -22,7 +23,33 @@ hs.hotkey.bind(mash, "h", function() move("left") end)
 hs.hotkey.bind(mash, "l", function() move("right") end)
 hs.hotkey.bind(mash, "m", function() move("max") end)
 
+-- Defeat paste blocking --
+
+hs.hotkey.bind({"cmd", "alt"}, "v", function() hs.eventtap.keyStrokes(hs.pasteboard.getContents()) end)
+
+-- Debugging --
+
+-- hs.hotkey.bind(mash, "d", function()
+-- end)
+
+-- Spoons --
+
+hs.loadSpoon("Quitter")
+spoon.Quitter.quitAppsAfter = {
+  -- mdls -name kMDItemCFBundleIdentifier -r /path/to/app
+  ["com.apple.iChat"]           = 600,
+  ["com.freron.MailMate"]       = 600,
+  ["com.kapeli.dashdoc"]        = 600,
+  ["com.reederapp.macOS"]       = 600,
+  ["com.tinyspeck.slackmacgap"] = 600,
+}
+spoon.Quitter:start()
+
 hs.loadSpoon("ReloadConfiguration")
 spoon.ReloadConfiguration:start()
 
-hs.alert.show("Config loaded")
+hs.notify.new({
+  title = "Hammerspoon",
+  informativeText = "Config loaded",
+  withdrawAfter = 2,
+}):send()