"
+obj.homepage = "https://github.com/Hammerspoon/Spoons"
+obj.license = "MIT - https://opensource.org/licenses/MIT"
+
+-- Workaround for "Dictation" menuitem
+hs.application.menuGlyphs[148]="fn fn"
+
+obj.commandEnum = {
+ cmd = '⌘',
+ shift = '⇧',
+ alt = '⌥',
+ ctrl = '⌃',
+}
+
+function obj:init()
+ self.sheetView = hs.webview.new({x=0, y=0, w=0, h=0})
+ self.sheetView:windowTitle("CheatSheets")
+ self.sheetView:windowStyle("utility")
+ self.sheetView:allowGestures(true)
+ self.sheetView:allowNewWindows(false)
+ self.sheetView:level(hs.drawing.windowLevels.modalPanel)
+end
+
+local function processMenuItems(menustru)
+ local menu = ""
+ for pos,val in pairs(menustru) do
+ if type(val) == "table" then
+ -- TODO: Remove menubar items with no shortcuts in them
+ if val.AXRole == "AXMenuBarItem" and type(val.AXChildren) == "table" then
+ menu = menu .. ""
+ menu = menu .. "- " .. val.AXTitle .. "
"
+ menu = menu .. processMenuItems(val.AXChildren[1])
+ menu = menu .. "
"
+ elseif val.AXRole == "AXMenuItem" and not val.AXChildren then
+ if not (val.AXMenuItemCmdChar == '' and val.AXMenuItemCmdGlyph == '') then
+ local CmdModifiers = ''
+ for key, value in pairs(val.AXMenuItemCmdModifiers) do
+ CmdModifiers = CmdModifiers .. obj.commandEnum[value]
+ end
+ local CmdChar = val.AXMenuItemCmdChar
+ local CmdGlyph = hs.application.menuGlyphs[val.AXMenuItemCmdGlyph] or ''
+ local CmdKeys = CmdChar .. CmdGlyph
+ menu = menu .. "" .. CmdModifiers .. " " .. CmdKeys .. "
" .. " " .. val.AXTitle .. "
"
+ end
+ elseif val.AXRole == "AXMenuItem" and type(val.AXChildren) == "table" then
+ menu = menu .. processMenuItems(val.AXChildren[1])
+ end
+ end
+ end
+ return menu
+end
+
+local function generateHtml(application)
+ local app_title = application:title()
+ local menuitems_tree = application:getMenuItems()
+ local allmenuitems = processMenuItems(menuitems_tree)
+
+ local html = [[
+
+
+
+
+
+
+
+ ]] .. app_title .. [[
+
+
+ ]] .. allmenuitems .. [[
+
+
+
+
+
+
+
+ ]]
+
+ return html
+end
+
+--- KSheet:show()
+--- Method
+--- Show current application's keybindings in a webview
+---
+
+function obj:show()
+ local capp = hs.application.frontmostApplication()
+ local cscreen = hs.screen.mainScreen()
+ local cres = cscreen:fullFrame()
+ self.sheetView:frame({
+ x = cres.x+cres.w*0.15/2,
+ y = cres.y+cres.h*0.25/2,
+ w = cres.w*0.85,
+ h = cres.h*0.75
+ })
+ local webcontent = generateHtml(capp)
+ self.sheetView:html(webcontent)
+ self.sheetView:show()
+end
+
+--- KSheet:hide()
+--- Method
+--- Hide the cheatsheet webview
+---
+
+function obj:hide()
+ self.sheetView:hide()
+end
+
+return obj
diff --git a/hammerspoon/Spoons/ModalMgr.spoon/docs.json b/hammerspoon/Spoons/ModalMgr.spoon/docs.json
new file mode 100644
index 0000000..3e4f0bd
--- /dev/null
+++ b/hammerspoon/Spoons/ModalMgr.spoon/docs.json
@@ -0,0 +1,253 @@
+[
+ {
+ "Constant" : [
+
+ ],
+ "submodules" : [
+
+ ],
+ "Function" : [
+
+ ],
+ "Variable" : [
+
+ ],
+ "stripped_doc" : [
+
+ ],
+ "Deprecated" : [
+
+ ],
+ "type" : "Module",
+ "desc" : "Modal keybindings environment management. Just an wrapper of `hs.hotkey.modal`.",
+ "Constructor" : [
+
+ ],
+ "doc" : "Modal keybindings environment management. Just an wrapper of `hs.hotkey.modal`.\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/ModalMgr.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/ModalMgr.spoon.zip)",
+ "Method" : [
+ {
+ "desc" : "Create a new modal keybindings environment",
+ "stripped_doc" : [
+ "Create a new modal keybindings environment",
+ ""
+ ],
+ "parameters" : [
+ " * id - A string specifying ID of new modal keybindings"
+ ],
+ "doc" : "Create a new modal keybindings environment\n\nParameters:\n * id - A string specifying ID of new modal keybindings",
+ "notes" : [
+
+ ],
+ "signature" : "ModalMgr:new(id)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "ModalMgr:new(id)",
+ "name" : "new"
+ },
+ {
+ "desc" : "Toggle the cheatsheet display of current modal environments's keybindings.",
+ "stripped_doc" : [
+ "Toggle the cheatsheet display of current modal environments's keybindings.",
+ ""
+ ],
+ "parameters" : [
+ " * iterList - An table specifying IDs of modal environments or active_list. Optional, defaults to all active environments.",
+ " * force - A optional boolean value to force show cheatsheet, defaults to `nil` (automatically)."
+ ],
+ "doc" : "Toggle the cheatsheet display of current modal environments's keybindings.\n\nParameters:\n * iterList - An table specifying IDs of modal environments or active_list. Optional, defaults to all active environments.\n * force - A optional boolean value to force show cheatsheet, defaults to `nil` (automatically).",
+ "notes" : [
+
+ ],
+ "signature" : "ModalMgr:toggleCheatsheet([idList], [force])",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "ModalMgr:toggleCheatsheet([idList], [force])",
+ "name" : "toggleCheatsheet"
+ },
+ {
+ "desc" : "Activate all modal environment in `idList`.",
+ "stripped_doc" : [
+ "Activate all modal environment in `idList`.",
+ ""
+ ],
+ "parameters" : [
+ " * idList - An table specifying IDs of modal environments",
+ " * trayColor - An optional string (e.g. #000000) specifying the color of modalTray, defaults to `nil`.",
+ " * showKeys - A optional boolean value to show all available keybindings, defaults to `nil`."
+ ],
+ "doc" : "Activate all modal environment in `idList`.\n\nParameters:\n * idList - An table specifying IDs of modal environments\n * trayColor - An optional string (e.g. #000000) specifying the color of modalTray, defaults to `nil`.\n * showKeys - A optional boolean value to show all available keybindings, defaults to `nil`.",
+ "notes" : [
+
+ ],
+ "signature" : "ModalMgr:activate(idList, [trayColor], [showKeys])",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "ModalMgr:activate(idList, [trayColor], [showKeys])",
+ "name" : "activate"
+ },
+ {
+ "desc" : "Deactivate modal environments in `idList`.",
+ "stripped_doc" : [
+ "Deactivate modal environments in `idList`.",
+ ""
+ ],
+ "parameters" : [
+ " * idList - An table specifying IDs of modal environments"
+ ],
+ "doc" : "Deactivate modal environments in `idList`.\n\nParameters:\n * idList - An table specifying IDs of modal environments",
+ "notes" : [
+
+ ],
+ "signature" : "ModalMgr:deactivate(idList)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "ModalMgr:deactivate(idList)",
+ "name" : "deactivate"
+ },
+ {
+ "desc" : "Deactivate all active modal environments.",
+ "stripped_doc" : [
+ "Deactivate all active modal environments."
+ ],
+ "parameters" : [
+
+ ],
+ "doc" : "Deactivate all active modal environments.",
+ "notes" : [
+
+ ],
+ "signature" : "ModalMgr:deactivateAll()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "ModalMgr:deactivateAll()",
+ "name" : "deactivateAll"
+ }
+ ],
+ "Command" : [
+
+ ],
+ "Field" : [
+
+ ],
+ "items" : [
+ {
+ "desc" : "Activate all modal environment in `idList`.",
+ "stripped_doc" : [
+ "Activate all modal environment in `idList`.",
+ ""
+ ],
+ "parameters" : [
+ " * idList - An table specifying IDs of modal environments",
+ " * trayColor - An optional string (e.g. #000000) specifying the color of modalTray, defaults to `nil`.",
+ " * showKeys - A optional boolean value to show all available keybindings, defaults to `nil`."
+ ],
+ "doc" : "Activate all modal environment in `idList`.\n\nParameters:\n * idList - An table specifying IDs of modal environments\n * trayColor - An optional string (e.g. #000000) specifying the color of modalTray, defaults to `nil`.\n * showKeys - A optional boolean value to show all available keybindings, defaults to `nil`.",
+ "notes" : [
+
+ ],
+ "signature" : "ModalMgr:activate(idList, [trayColor], [showKeys])",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "ModalMgr:activate(idList, [trayColor], [showKeys])",
+ "name" : "activate"
+ },
+ {
+ "desc" : "Deactivate modal environments in `idList`.",
+ "stripped_doc" : [
+ "Deactivate modal environments in `idList`.",
+ ""
+ ],
+ "parameters" : [
+ " * idList - An table specifying IDs of modal environments"
+ ],
+ "doc" : "Deactivate modal environments in `idList`.\n\nParameters:\n * idList - An table specifying IDs of modal environments",
+ "notes" : [
+
+ ],
+ "signature" : "ModalMgr:deactivate(idList)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "ModalMgr:deactivate(idList)",
+ "name" : "deactivate"
+ },
+ {
+ "desc" : "Deactivate all active modal environments.",
+ "stripped_doc" : [
+ "Deactivate all active modal environments."
+ ],
+ "parameters" : [
+
+ ],
+ "doc" : "Deactivate all active modal environments.",
+ "notes" : [
+
+ ],
+ "signature" : "ModalMgr:deactivateAll()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "ModalMgr:deactivateAll()",
+ "name" : "deactivateAll"
+ },
+ {
+ "desc" : "Create a new modal keybindings environment",
+ "stripped_doc" : [
+ "Create a new modal keybindings environment",
+ ""
+ ],
+ "parameters" : [
+ " * id - A string specifying ID of new modal keybindings"
+ ],
+ "doc" : "Create a new modal keybindings environment\n\nParameters:\n * id - A string specifying ID of new modal keybindings",
+ "notes" : [
+
+ ],
+ "signature" : "ModalMgr:new(id)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "ModalMgr:new(id)",
+ "name" : "new"
+ },
+ {
+ "desc" : "Toggle the cheatsheet display of current modal environments's keybindings.",
+ "stripped_doc" : [
+ "Toggle the cheatsheet display of current modal environments's keybindings.",
+ ""
+ ],
+ "parameters" : [
+ " * iterList - An table specifying IDs of modal environments or active_list. Optional, defaults to all active environments.",
+ " * force - A optional boolean value to force show cheatsheet, defaults to `nil` (automatically)."
+ ],
+ "doc" : "Toggle the cheatsheet display of current modal environments's keybindings.\n\nParameters:\n * iterList - An table specifying IDs of modal environments or active_list. Optional, defaults to all active environments.\n * force - A optional boolean value to force show cheatsheet, defaults to `nil` (automatically).",
+ "notes" : [
+
+ ],
+ "signature" : "ModalMgr:toggleCheatsheet([idList], [force])",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "ModalMgr:toggleCheatsheet([idList], [force])",
+ "name" : "toggleCheatsheet"
+ }
+ ],
+ "name" : "ModalMgr"
+ }
+]
\ No newline at end of file
diff --git a/hammerspoon/Spoons/ModalMgr.spoon/init.lua b/hammerspoon/Spoons/ModalMgr.spoon/init.lua
new file mode 100644
index 0000000..2c7a61d
--- /dev/null
+++ b/hammerspoon/Spoons/ModalMgr.spoon/init.lua
@@ -0,0 +1,189 @@
+--- === ModalMgr ===
+---
+--- Modal keybindings environment management. Just an wrapper of `hs.hotkey.modal`.
+---
+--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ModalMgr.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ModalMgr.spoon.zip)
+
+local obj = {}
+obj.__index = obj
+
+-- Metadata
+obj.name = "ModalMgr"
+obj.version = "1.0"
+obj.author = "ashfinal "
+obj.homepage = "https://github.com/Hammerspoon/Spoons"
+obj.license = "MIT - https://opensource.org/licenses/MIT"
+
+obj.modal_tray = nil
+obj.which_key = nil
+obj.modal_list = {}
+obj.active_list = {}
+obj.supervisor = nil
+
+function obj:init()
+ hsupervisor_keys = hsupervisor_keys or {{"cmd", "shift", "ctrl"}, "Q"}
+ obj.supervisor = hs.hotkey.modal.new(hsupervisor_keys[1], hsupervisor_keys[2], 'Initialize Modal Environment')
+ obj.supervisor:bind(hsupervisor_keys[1], hsupervisor_keys[2], "Reset Modal Environment", function() obj.supervisor:exit() end)
+ hshelp_keys = hshelp_keys or {{"alt", "shift"}, "/"}
+ obj.supervisor:bind(hshelp_keys[1], hshelp_keys[2], "Toggle Help Panel", function() obj:toggleCheatsheet({all=obj.supervisor}) end)
+ obj.modal_tray = hs.canvas.new({x = 0, y = 0, w = 0, h = 0})
+ obj.modal_tray:level(hs.canvas.windowLevels.tornOffMenu)
+ obj.modal_tray[1] = {
+ type = "circle",
+ action = "fill",
+ fillColor = {hex = "#FFFFFF", alpha = 0.7},
+ }
+ obj.which_key = hs.canvas.new({x = 0, y = 0, w = 0, h = 0})
+ obj.which_key:level(hs.canvas.windowLevels.tornOffMenu)
+ obj.which_key[1] = {
+ type = "rectangle",
+ action = "fill",
+ fillColor = {hex = "#EEEEEE", alpha = 0.95},
+ roundedRectRadii = {xRadius = 10, yRadius = 10},
+ }
+end
+
+--- ModalMgr:new(id)
+--- Method
+--- Create a new modal keybindings environment
+---
+--- Parameters:
+--- * id - A string specifying ID of new modal keybindings
+
+function obj:new(id)
+ obj.modal_list[id] = hs.hotkey.modal.new()
+end
+
+--- ModalMgr:toggleCheatsheet([idList], [force])
+--- Method
+--- Toggle the cheatsheet display of current modal environments's keybindings.
+---
+--- Parameters:
+--- * iterList - An table specifying IDs of modal environments or active_list. Optional, defaults to all active environments.
+--- * force - A optional boolean value to force show cheatsheet, defaults to `nil` (automatically).
+
+function obj:toggleCheatsheet(iterList, force)
+ if obj.which_key:isShowing() and not force then
+ obj.which_key:hide()
+ else
+ local cscreen = hs.screen.mainScreen()
+ local cres = cscreen:fullFrame()
+ obj.which_key:frame({
+ x = cres.x + cres.w / 5,
+ y = cres.y + cres.h / 5,
+ w = cres.w / 5 * 3,
+ h = cres.h / 5 * 3
+ })
+ local keys_pool = {}
+ local tmplist = iterList or obj.active_list
+ for i, v in pairs(tmplist) do
+ if type(v) == "string" then
+ -- It appears to be idList
+ for _, m in ipairs(obj.modal_list[v].keys) do
+ table.insert(keys_pool, m.msg)
+ end
+ elseif type(i) == "string" then
+ -- It appears to be active_list
+ for _, m in pairs(v.keys) do
+ table.insert(keys_pool, m.msg)
+ end
+ end
+ end
+ for idx, val in ipairs(keys_pool) do
+ if idx % 2 == 1 then
+ obj.which_key[idx + 1] = {
+ type = "text",
+ text = keys_pool[idx],
+ textFont = "Courier-Bold",
+ textSize = 16,
+ textColor = {hex = "#2390FF", alpha = 1},
+ textAlignment = "left",
+ frame = {
+ x = tostring(40 / (cres.w / 5 * 3)),
+ y = tostring((30 + (idx - math.ceil(idx / 2)) * math.ceil((cres.h / 5 * 3 - 60) / #keys_pool) * 2) / (cres.h / 5 * 3)),
+ w = tostring((1 - 80 / (cres.w / 5 * 3)) / 2),
+ h = tostring(math.ceil((cres.h / 5 * 3 - 60) / #keys_pool) * 2 / (cres.h / 5 * 3))
+ }
+ }
+ else
+ obj.which_key[idx + 1] = {
+ type = "text",
+ text = keys_pool[idx],
+ textFont = "Courier-Bold",
+ textSize = 16,
+ textColor = {hex = "#2390FF"},
+ textAlignment = "right",
+ frame = {
+ x = "50%",
+ y = tostring((30 + (idx - math.ceil(idx / 2) - 1) * math.ceil((cres.h / 5 * 3 - 60) / #keys_pool) * 2) / (cres.h / 5 * 3)),
+ w = tostring((1 - 80 / (cres.w / 5 * 3)) / 2),
+ h = tostring(math.ceil((cres.h / 5 * 3 - 60) / #keys_pool) * 2 / (cres.h / 5 * 3))
+ }
+ }
+ end
+ end
+ obj.which_key:show()
+ end
+end
+
+--- ModalMgr:activate(idList, [trayColor], [showKeys])
+--- Method
+--- Activate all modal environment in `idList`.
+---
+--- Parameters:
+--- * idList - An table specifying IDs of modal environments
+--- * trayColor - An optional string (e.g. #000000) specifying the color of modalTray, defaults to `nil`.
+--- * showKeys - A optional boolean value to show all available keybindings, defaults to `nil`.
+
+function obj:activate(idList, trayColor, showKeys)
+ for _, val in ipairs(idList) do
+ obj.modal_list[val]:enter()
+ obj.active_list[val] = obj.modal_list[val]
+ end
+ if trayColor then
+ local cscreen = hs.screen.mainScreen()
+ local cres = cscreen:fullFrame()
+ local lcres = cscreen:absoluteToLocal(cres)
+ obj.modal_tray:frame(cscreen:localToAbsolute{
+ x = cres.w - 40,
+ y = cres.h - 40,
+ w = 20,
+ h = 20
+ })
+ obj.modal_tray[1].fillColor = {hex = trayColor, alpha = 0.7}
+ obj.modal_tray:show()
+ end
+ if showKeys then
+ obj:toggleCheatsheet(idList, true)
+ end
+end
+
+--- ModalMgr:deactivate(idList)
+--- Method
+--- Deactivate modal environments in `idList`.
+---
+--- Parameters:
+--- * idList - An table specifying IDs of modal environments
+
+function obj:deactivate(idList)
+ for _, val in ipairs(idList) do
+ obj.modal_list[val]:exit()
+ obj.active_list[val] = nil
+ end
+ obj.modal_tray:hide()
+ for i = 2, #obj.which_key do
+ obj.which_key:removeElement(2)
+ end
+ obj.which_key:hide()
+end
+
+--- ModalMgr:deactivateAll()
+--- Method
+--- Deactivate all active modal environments.
+---
+
+function obj:deactivateAll()
+ obj:deactivate(obj.active_list)
+end
+
+return obj
diff --git a/hammerspoon/Spoons/ReloadConfiguration.spoon/docs.json b/hammerspoon/Spoons/ReloadConfiguration.spoon/docs.json
new file mode 100644
index 0000000..50c62f2
--- /dev/null
+++ b/hammerspoon/Spoons/ReloadConfiguration.spoon/docs.json
@@ -0,0 +1,90 @@
+[
+ {
+ "Command": [],
+ "Constant": [],
+ "Constructor": [],
+ "Deprecated": [],
+ "Field": [],
+ "Function": [],
+ "Method": [
+ {
+ "def": "ReloadConfiguration:bindHotkeys(mapping)",
+ "desc": "Binds hotkeys for ReloadConfiguration",
+ "doc": "Binds hotkeys for ReloadConfiguration\n\nParameters:\n * mapping - A table containing hotkey modifier/key details for the following items:\n * reloadConfiguration - This will cause the configuration to be reloaded",
+ "name": "bindHotkeys",
+ "parameters": [
+ " * mapping - A table containing hotkey modifier/key details for the following items:",
+ " * reloadConfiguration - This will cause the configuration to be reloaded"
+ ],
+ "signature": "ReloadConfiguration:bindHotkeys(mapping)",
+ "stripped_doc": "",
+ "type": "Method"
+ },
+ {
+ "def": "ReloadConfiguration:start()",
+ "desc": "Start ReloadConfiguration",
+ "doc": "Start ReloadConfiguration\n\nParameters:\n * None",
+ "name": "start",
+ "parameters": [
+ " * None"
+ ],
+ "signature": "ReloadConfiguration:start()",
+ "stripped_doc": "",
+ "type": "Method"
+ }
+ ],
+ "Variable": [
+ {
+ "def": "ReloadConfiguration.watch_paths",
+ "desc": "List of directories to watch for changes, defaults to hs.configdir",
+ "doc": "List of directories to watch for changes, defaults to hs.configdir",
+ "name": "watch_paths",
+ "signature": "ReloadConfiguration.watch_paths",
+ "stripped_doc": "",
+ "type": "Variable"
+ }
+ ],
+ "desc": "Adds a hotkey to reload the hammerspoon configuration, and a pathwatcher to automatically reload on changes.",
+ "doc": "Adds a hotkey to reload the hammerspoon configuration, and a pathwatcher to automatically reload on changes.\n\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ReloadConfiguration.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ReloadConfiguration.spoon.zip)",
+ "items": [
+ {
+ "def": "ReloadConfiguration:bindHotkeys(mapping)",
+ "desc": "Binds hotkeys for ReloadConfiguration",
+ "doc": "Binds hotkeys for ReloadConfiguration\n\nParameters:\n * mapping - A table containing hotkey modifier/key details for the following items:\n * reloadConfiguration - This will cause the configuration to be reloaded",
+ "name": "bindHotkeys",
+ "parameters": [
+ " * mapping - A table containing hotkey modifier/key details for the following items:",
+ " * reloadConfiguration - This will cause the configuration to be reloaded"
+ ],
+ "signature": "ReloadConfiguration:bindHotkeys(mapping)",
+ "stripped_doc": "",
+ "type": "Method"
+ },
+ {
+ "def": "ReloadConfiguration:start()",
+ "desc": "Start ReloadConfiguration",
+ "doc": "Start ReloadConfiguration\n\nParameters:\n * None",
+ "name": "start",
+ "parameters": [
+ " * None"
+ ],
+ "signature": "ReloadConfiguration:start()",
+ "stripped_doc": "",
+ "type": "Method"
+ },
+ {
+ "def": "ReloadConfiguration.watch_paths",
+ "desc": "List of directories to watch for changes, defaults to hs.configdir",
+ "doc": "List of directories to watch for changes, defaults to hs.configdir",
+ "name": "watch_paths",
+ "signature": "ReloadConfiguration.watch_paths",
+ "stripped_doc": "",
+ "type": "Variable"
+ }
+ ],
+ "name": "ReloadConfiguration",
+ "stripped_doc": "\nDownload: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ReloadConfiguration.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ReloadConfiguration.spoon.zip)",
+ "submodules": [],
+ "type": "Module"
+ }
+]
\ No newline at end of file
diff --git a/hammerspoon/Spoons/ReloadConfiguration.spoon/init.lua b/hammerspoon/Spoons/ReloadConfiguration.spoon/init.lua
new file mode 100644
index 0000000..bb83f67
--- /dev/null
+++ b/hammerspoon/Spoons/ReloadConfiguration.spoon/init.lua
@@ -0,0 +1,49 @@
+--- === ReloadConfiguration ===
+---
+--- Adds a hotkey to reload the hammerspoon configuration, and a pathwatcher to automatically reload on changes.
+---
+--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ReloadConfiguration.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ReloadConfiguration.spoon.zip)
+
+local obj = {}
+obj.__index = obj
+
+-- Metadata
+obj.name = "ReloadConfiguration"
+obj.version = "1.0"
+obj.author = "Jon Lorusso "
+obj.homepage = "https://github.com/Hammerspoon/Spoons"
+obj.license = "MIT - https://opensource.org/licenses/MIT"
+
+
+--- ReloadConfiguration.watch_paths
+--- Variable
+--- List of directories to watch for changes, defaults to hs.configdir
+obj.watch_paths = { hs.configdir }
+
+--- ReloadConfiguration:bindHotkeys(mapping)
+--- Method
+--- Binds hotkeys for ReloadConfiguration
+---
+--- Parameters:
+--- * mapping - A table containing hotkey modifier/key details for the following items:
+--- * reloadConfiguration - This will cause the configuration to be reloaded
+function obj:bindHotkeys(mapping)
+ local def = { reloadConfiguration = hs.fnutils.partial(hs.reload, self) }
+ hs.spoons.bindHotkeysToSpec(def, mapping)
+end
+
+--- ReloadConfiguration:start()
+--- Method
+--- Start ReloadConfiguration
+---
+--- Parameters:
+--- * None
+function obj:start()
+ self.watchers = {}
+ for _,dir in pairs(self.watch_paths) do
+ self.watchers[dir] = hs.pathwatcher.new(dir, hs.reload):start()
+ end
+ return self
+end
+
+return obj
diff --git a/hammerspoon/Spoons/SpeedMenu.spoon/docs.json b/hammerspoon/Spoons/SpeedMenu.spoon/docs.json
new file mode 100644
index 0000000..aa34d28
--- /dev/null
+++ b/hammerspoon/Spoons/SpeedMenu.spoon/docs.json
@@ -0,0 +1,79 @@
+[
+ {
+ "Constant" : [
+
+ ],
+ "submodules" : [
+
+ ],
+ "Function" : [
+
+ ],
+ "Variable" : [
+
+ ],
+ "stripped_doc" : [
+
+ ],
+ "desc" : "Menubar netspeed meter",
+ "Deprecated" : [
+
+ ],
+ "type" : "Module",
+ "Constructor" : [
+
+ ],
+ "doc" : "Menubar netspeed meter\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/SpeedMenu.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/SpeedMenu.spoon.zip)",
+ "Field" : [
+
+ ],
+ "Command" : [
+
+ ],
+ "items" : [
+ {
+ "doc" : "Redetect the active interface, darkmode …And redraw everything.",
+ "desc" : "Redetect the active interface, darkmode …And redraw everything.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "Redetect the active interface, darkmode …And redraw everything."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "SpeedMenu:rescan()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "SpeedMenu:rescan()",
+ "name" : "rescan"
+ }
+ ],
+ "Method" : [
+ {
+ "doc" : "Redetect the active interface, darkmode …And redraw everything.",
+ "desc" : "Redetect the active interface, darkmode …And redraw everything.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "Redetect the active interface, darkmode …And redraw everything."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "SpeedMenu:rescan()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "SpeedMenu:rescan()",
+ "name" : "rescan"
+ }
+ ],
+ "name" : "SpeedMenu"
+ }
+]
\ No newline at end of file
diff --git a/hammerspoon/Spoons/SpeedMenu.spoon/init.lua b/hammerspoon/Spoons/SpeedMenu.spoon/init.lua
new file mode 100644
index 0000000..d4ecdac
--- /dev/null
+++ b/hammerspoon/Spoons/SpeedMenu.spoon/init.lua
@@ -0,0 +1,111 @@
+--- === SpeedMenu ===
+---
+--- Menubar netspeed meter
+---
+--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpeedMenu.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpeedMenu.spoon.zip)
+
+local obj={}
+obj.__index = obj
+
+-- Metadata
+obj.name = "SpeedMenu"
+obj.version = "1.0"
+obj.author = "ashfinal "
+obj.homepage = "https://github.com/Hammerspoon/Spoons"
+obj.license = "MIT - https://opensource.org/licenses/MIT"
+
+function obj:init()
+ self.menubar = hs.menubar.new()
+ obj:rescan()
+end
+
+local function data_diff()
+ local in_seq = hs.execute(obj.instr)
+ local out_seq = hs.execute(obj.outstr)
+ local in_diff = in_seq - obj.inseq
+ local out_diff = out_seq - obj.outseq
+ if in_diff/1024 > 1024 then
+ obj.kbin = string.format("%6.2f", in_diff/1024/1024) .. ' mb/s'
+ else
+ obj.kbin = string.format("%6.2f", in_diff/1024) .. ' kb/s'
+ end
+ if out_diff/1024 > 1024 then
+ obj.kbout = string.format("%6.2f", out_diff/1024/1024) .. ' mb/s'
+ else
+ obj.kbout = string.format("%6.2f", out_diff/1024) .. ' kb/s'
+ end
+ local disp_str = '⥄ ' .. obj.kbout .. '\n⥂ ' .. obj.kbin
+ if obj.darkmode then
+ obj.disp_str = hs.styledtext.new(disp_str, {font={size=9.0, color={hex="#FFFFFF"}}})
+ else
+ obj.disp_str = hs.styledtext.new(disp_str, {font={size=9.0, color={hex="#000000"}}})
+ end
+ obj.menubar:setTitle(obj.disp_str)
+ obj.inseq = in_seq
+ obj.outseq = out_seq
+end
+
+--- SpeedMenu:rescan()
+--- Method
+--- Redetect the active interface, darkmode …And redraw everything.
+---
+
+function obj:rescan()
+ obj.interface = hs.network.primaryInterfaces()
+ obj.darkmode = hs.osascript.applescript('tell application "System Events"\nreturn dark mode of appearance preferences\nend tell')
+ local menuitems_table = {}
+ if obj.interface then
+ -- Inspect active interface and create menuitems
+ local interface_detail = hs.network.interfaceDetails(obj.interface)
+ if interface_detail.AirPort then
+ local ssid = interface_detail.AirPort.SSID
+ table.insert(menuitems_table, {
+ title = "SSID: " .. ssid,
+ tooltip = "Copy SSID to clipboard",
+ fn = function() hs.pasteboard.setContents(ssid) end
+ })
+ end
+ if interface_detail.IPv4 then
+ local ipv4 = interface_detail.IPv4.Addresses[1]
+ table.insert(menuitems_table, {
+ title = "IPv4: " .. ipv4,
+ tooltip = "Copy IPv4 to clipboard",
+ fn = function() hs.pasteboard.setContents(ipv4) end
+ })
+ end
+ if interface_detail.IPv6 then
+ local ipv6 = interface_detail.IPv6.Addresses[1]
+ table.insert(menuitems_table, {
+ title = "IPv6: " .. ipv6,
+ tooltip = "Copy IPv6 to clipboard",
+ fn = function() hs.pasteboard.setContents(ipv6) end
+ })
+ end
+ local macaddr = hs.execute('ifconfig ' .. obj.interface .. ' | grep ether | awk \'{print $2}\'')
+ table.insert(menuitems_table, {
+ title = "MAC Addr: " .. macaddr,
+ tooltip = "Copy MAC Address to clipboard",
+ fn = function() hs.pasteboard.setContents(macaddr) end
+ })
+ -- Start watching the netspeed delta
+ obj.instr = 'netstat -ibn | grep -e ' .. obj.interface .. ' -m 1 | awk \'{print $7}\''
+ obj.outstr = 'netstat -ibn | grep -e ' .. obj.interface .. ' -m 1 | awk \'{print $10}\''
+
+ obj.inseq = hs.execute(obj.instr)
+ obj.outseq = hs.execute(obj.outstr)
+
+ if obj.timer then
+ obj.timer:stop()
+ obj.timer = nil
+ end
+ obj.timer = hs.timer.doEvery(1, data_diff)
+ end
+ table.insert(menuitems_table, {
+ title = "Rescan Network Interfaces",
+ fn = function() obj:rescan() end
+ })
+ obj.menubar:setTitle("⚠︎")
+ obj.menubar:setMenu(menuitems_table)
+end
+
+return obj
diff --git a/hammerspoon/Spoons/TimeFlow.spoon/docs.json b/hammerspoon/Spoons/TimeFlow.spoon/docs.json
new file mode 100644
index 0000000..1e4f630
--- /dev/null
+++ b/hammerspoon/Spoons/TimeFlow.spoon/docs.json
@@ -0,0 +1,41 @@
+[
+ {
+ "Constant" : [
+
+ ],
+ "submodules" : [
+
+ ],
+ "Function" : [
+
+ ],
+ "Variable" : [
+
+ ],
+ "stripped_doc" : [
+
+ ],
+ "Deprecated" : [
+
+ ],
+ "type" : "Module",
+ "desc" : "A widget showing time flown in one year.",
+ "Constructor" : [
+
+ ],
+ "doc" : "A widget showing time flown in one year.\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/TimeFlow.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/TimeFlow.spoon.zip)",
+ "Field" : [
+
+ ],
+ "items" : [
+
+ ],
+ "Command" : [
+
+ ],
+ "Method" : [
+
+ ],
+ "name" : "TimeFlow"
+ }
+]
\ No newline at end of file
diff --git a/hammerspoon/Spoons/TimeFlow.spoon/init.lua b/hammerspoon/Spoons/TimeFlow.spoon/init.lua
new file mode 100644
index 0000000..e508e11
--- /dev/null
+++ b/hammerspoon/Spoons/TimeFlow.spoon/init.lua
@@ -0,0 +1,153 @@
+--- === TimeFlow ===
+---
+--- A widget showing time flown in one year.
+---
+--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/TimeFlow.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/TimeFlow.spoon.zip)
+
+local obj={}
+obj.__index = obj
+
+-- Metadata
+obj.name = "TimeFlow"
+obj.version = "1.0"
+obj.author = "ashfinal "
+obj.homepage = "https://github.com/Hammerspoon/Spoons"
+obj.license = "MIT - https://opensource.org/licenses/MIT"
+
+-- Internal function used to find our location, so we know where to load files from
+local function script_path()
+ local str = debug.getinfo(2, "S").source:sub(2)
+ return str:match("(.*/)")
+end
+
+obj.spoonPath = script_path()
+
+local function updateElapsedCanvas()
+ local nowtable = os.date("*t")
+ local nowyday = nowtable.yday
+ local nowhour = string.format("%2s", nowtable.hour)
+ local nowmin = string.format("%2s", nowtable.min)
+ local nowsec = string.format("%2s", nowtable.sec)
+ local timestr = nowyday.." days "..nowhour.." hours "..nowmin.." min "..nowsec.." sec"
+ local secs_since_epoch = os.time()
+ local nowyear = nowtable.year
+ local yearstartsecs_since_epoch = os.time({year=nowyear, month=1, day=1, hour=0})
+ local nowyear_elapsed_secs = secs_since_epoch - yearstartsecs_since_epoch
+ local yearendsecs_since_epoch = os.time({year=nowyear+1, month=1, day=1, hour=0})
+ local nowyear_total_secs = yearendsecs_since_epoch - yearstartsecs_since_epoch
+ local elapsed_percent = nowyear_elapsed_secs/nowyear_total_secs
+ if obj.canvas:isShowing() then
+ obj.canvas[3].text = timestr
+ obj.canvas[6].frame.w = tostring(240/280*elapsed_percent)
+ end
+end
+
+function obj:init()
+ local cscreen = hs.screen.mainScreen()
+ local cres = cscreen:fullFrame()
+ obj.canvas = hs.canvas.new({
+ x = cres.w-280-20,
+ y = 400,
+ w = 280,
+ h = 125
+ }):show()
+ obj.canvas:behavior(hs.canvas.windowBehaviors.canJoinAllSpaces)
+ obj.canvas:level(hs.canvas.windowLevels.desktopIcon)
+ -- canvas background
+ obj.canvas[1] = {
+ action = "fill",
+ type = "rectangle",
+ fillColor = {hex="#000000", alpha=0.2},
+ roundedRectRadii = {xRadius=5, yRadius=5},
+ }
+ -- title
+ obj.canvas[2] = {
+ type = "text",
+ text = "Time Elapsed",
+ textSize = 14,
+ textColor = {hex="#FFFFFF", alpha=0.3},
+ frame = {
+ x = tostring(10/280),
+ y = tostring(10/125),
+ w = tostring(260/280),
+ h = tostring(25/125),
+ }
+ }
+ -- time
+ obj.canvas[3] = {
+ type = "text",
+ text = "",
+ textColor = {hex="#A6AAC3"},
+ textSize = 17,
+ textAlignment = "center",
+ frame = {
+ x = tostring(0/280),
+ y = tostring(35/125),
+ w = tostring(280/280),
+ h = tostring(25/125),
+ }
+ }
+ -- indicator background
+ obj.canvas[4] = {
+ type = "image",
+ image = hs.image.imageFromPath(self.spoonPath .. "/timebg.png"),
+ frame = {
+ x = tostring(10/280),
+ y = tostring(65/125),
+ w = tostring(260/280),
+ h = tostring(50/125),
+ }
+ }
+ -- light indicator
+ obj.canvas[5] = {
+ action = "fill",
+ type = "rectangle",
+ fillColor = {hex="#FFFFFF", alpha=0.2},
+ frame = {
+ x = tostring(20/280),
+ y = tostring(75/125),
+ w = tostring(240/280),
+ h = tostring(20/125),
+ }
+ }
+ -- indicator mask
+ obj.canvas[6] = {
+ action = "fill",
+ type = "rectangle",
+ frame = {
+ x = tostring(20/280),
+ y = tostring(75/125),
+ w = tostring(240/280),
+ h = tostring(20/125),
+ }
+ }
+ -- color indicator
+ obj.canvas[7] = {
+ action = "fill",
+ type = "rectangle",
+ frame = {
+ x = tostring(20/280),
+ y = tostring(75/125),
+ w = tostring(240/280),
+ h = tostring(20/125),
+ },
+ fillGradient="linear",
+ fillGradientColors = {
+ {hex = "#00A0F7"},
+ {hex = "#92D2E5"},
+ {hex = "#4BE581"},
+ {hex = "#EAF25E"},
+ {hex = "#F4CA55"},
+ {hex = "#E04E4E"},
+ },
+ }
+ obj.canvas[7].compositeRule = "sourceAtop"
+
+ if obj.timer == nil then
+ obj.timer = hs.timer.doEvery(1, function() updateElapsedCanvas() end)
+ else
+ obj.timer:start()
+ end
+end
+
+return obj
diff --git a/hammerspoon/Spoons/TimeFlow.spoon/timebg.png b/hammerspoon/Spoons/TimeFlow.spoon/timebg.png
new file mode 100644
index 0000000..c34723a
Binary files /dev/null and b/hammerspoon/Spoons/TimeFlow.spoon/timebg.png differ
diff --git a/hammerspoon/Spoons/UnsplashZ.spoon/docs.json b/hammerspoon/Spoons/UnsplashZ.spoon/docs.json
new file mode 100644
index 0000000..507aa0e
--- /dev/null
+++ b/hammerspoon/Spoons/UnsplashZ.spoon/docs.json
@@ -0,0 +1,41 @@
+[
+ {
+ "Constant" : [
+
+ ],
+ "submodules" : [
+
+ ],
+ "Function" : [
+
+ ],
+ "Variable" : [
+
+ ],
+ "stripped_doc" : [
+
+ ],
+ "Deprecated" : [
+
+ ],
+ "desc" : "Use unsplash images as wallpaper",
+ "type" : "Module",
+ "Constructor" : [
+
+ ],
+ "doc" : "Use unsplash images as wallpaper\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/UnsplashZ.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/UnsplashZ.spoon.zip)",
+ "Method" : [
+
+ ],
+ "Command" : [
+
+ ],
+ "items" : [
+
+ ],
+ "Field" : [
+
+ ],
+ "name" : "UnsplashZ"
+ }
+]
\ No newline at end of file
diff --git a/hammerspoon/Spoons/UnsplashZ.spoon/init.lua b/hammerspoon/Spoons/UnsplashZ.spoon/init.lua
new file mode 100644
index 0000000..473f255
--- /dev/null
+++ b/hammerspoon/Spoons/UnsplashZ.spoon/init.lua
@@ -0,0 +1,52 @@
+--- === UnsplashZ ===
+---
+--- Use unsplash images as wallpaper
+---
+--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/UnsplashZ.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/UnsplashZ.spoon.zip)
+
+local obj={}
+obj.__index = obj
+
+-- Metadata
+obj.name = "UnsplashZ"
+obj.version = "1.0"
+obj.author = "ashfinal "
+obj.homepage = "https://github.com/Hammerspoon/Spoons"
+obj.license = "MIT - https://opensource.org/licenses/MIT"
+
+local function curl_callback(exitCode, stdOut, stdErr)
+ if exitCode == 0 then
+ obj.task = nil
+ obj.last_pic = hs.http.urlParts(obj.pic_url).lastPathComponent
+ local localpath = os.getenv("HOME") .. "/.Trash/" .. hs.http.urlParts(obj.pic_url).lastPathComponent
+
+ hs.screen.mainScreen():desktopImageURL("file://" .. localpath)
+ else
+ print(stdOut, stdErr)
+ end
+end
+
+local function unsplashRequest()
+ local user_agent_str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4"
+ obj.pic_url = hs.execute([[ /usr/bin/curl 'https://source.unsplash.com/1600x900/?nature' | perl -ne ' print "$1" if /href="([^"]+)"/ ' ]])
+ if obj.last_pic ~= obj.pic_url then
+ if obj.task then
+ obj.task:terminate()
+ obj.task = nil
+ end
+ local localpath = os.getenv("HOME") .. "/.Trash/" .. hs.http.urlParts(obj.pic_url).lastPathComponent
+ obj.task = hs.task.new("/usr/bin/curl", curl_callback, {"-A", user_agent_str, obj.pic_url, "-o", localpath})
+ obj.task:start()
+ end
+end
+
+function obj:init()
+ if obj.timer == nil then
+ obj.timer = hs.timer.doEvery(3*60*60, function() unsplashRequest() end)
+ obj.timer:setNextTrigger(5)
+ else
+ obj.timer:start()
+ end
+end
+
+return obj
diff --git a/hammerspoon/Spoons/WinWin.spoon/docs.json b/hammerspoon/Spoons/WinWin.spoon/docs.json
new file mode 100644
index 0000000..56b73b0
--- /dev/null
+++ b/hammerspoon/Spoons/WinWin.spoon/docs.json
@@ -0,0 +1,406 @@
+[
+ {
+ "Constant" : [
+
+ ],
+ "submodules" : [
+
+ ],
+ "Function" : [
+
+ ],
+ "Variable" : [
+ {
+ "doc" : "An integer specifying how many gridparts the screen should be divided into. Defaults to 30.",
+ "desc" : "An integer specifying how many gridparts the screen should be divided into. Defaults to 30.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "An integer specifying how many gridparts the screen should be divided into. Defaults to 30."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin.gridparts",
+ "type" : "Variable",
+ "returns" : [
+
+ ],
+ "def" : "WinWin.gridparts",
+ "name" : "gridparts"
+ }
+ ],
+ "stripped_doc" : [
+
+ ],
+ "Deprecated" : [
+
+ ],
+ "type" : "Module",
+ "desc" : "Windows manipulation",
+ "Constructor" : [
+
+ ],
+ "doc" : "Windows manipulation\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/WinWin.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/WinWin.spoon.zip)",
+ "Method" : [
+ {
+ "doc" : "Move the focused window in the `direction` by on step. The step scale equals to the width\/height of one gridpart.\n\nParameters:\n * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`.",
+ "desc" : "Move the focused window in the `direction` by on step. The step scale equals to the width\/height of one gridpart.",
+ "parameters" : [
+ " * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`."
+ ],
+ "stripped_doc" : [
+ "Move the focused window in the `direction` by on step. The step scale equals to the width\/height of one gridpart.",
+ ""
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:stepMove(direction)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:stepMove(direction)",
+ "name" : "stepMove"
+ },
+ {
+ "doc" : "Resize the focused window in the `direction` by on step.\n\nParameters:\n * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`.",
+ "desc" : "Resize the focused window in the `direction` by on step.",
+ "parameters" : [
+ " * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`."
+ ],
+ "stripped_doc" : [
+ "Resize the focused window in the `direction` by on step.",
+ ""
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:stepResize(direction)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:stepResize(direction)",
+ "name" : "stepResize"
+ },
+ {
+ "doc" : "Stash current windows's position and size.",
+ "desc" : "Stash current windows's position and size.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "Stash current windows's position and size."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:stash()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:stash()",
+ "name" : "stash"
+ },
+ {
+ "doc" : "Move and resize the focused window.\n\nParameters:\n * option - A string specifying the option, valid strings are: `halfleft`, `halfright`, `halfup`, `halfdown`, `cornerNW`, `cornerSW`, `cornerNE`, `cornerSE`, `center`, `fullscreen`, `expand`, `shrink`.",
+ "desc" : "Move and resize the focused window.",
+ "parameters" : [
+ " * option - A string specifying the option, valid strings are: `halfleft`, `halfright`, `halfup`, `halfdown`, `cornerNW`, `cornerSW`, `cornerNE`, `cornerSE`, `center`, `fullscreen`, `expand`, `shrink`."
+ ],
+ "stripped_doc" : [
+ "Move and resize the focused window.",
+ ""
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:moveAndResize(option)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:moveAndResize(option)",
+ "name" : "moveAndResize"
+ },
+ {
+ "doc" : "Move the focused window between all of the screens in the `direction`.\n\nParameters:\n * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`, `next`.",
+ "desc" : "Move the focused window between all of the screens in the `direction`.",
+ "parameters" : [
+ " * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`, `next`."
+ ],
+ "stripped_doc" : [
+ "Move the focused window between all of the screens in the `direction`.",
+ ""
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:moveToScreen(direction)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:moveToScreen(direction)",
+ "name" : "moveToScreen"
+ },
+ {
+ "doc" : "Undo the last window manipulation. Only those \"moveAndResize\" manipulations can be undone.",
+ "desc" : "Undo the last window manipulation. Only those \"moveAndResize\" manipulations can be undone.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "Undo the last window manipulation. Only those \"moveAndResize\" manipulations can be undone."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:undo()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:undo()",
+ "name" : "undo"
+ },
+ {
+ "doc" : "Redo the window manipulation. Only those \"moveAndResize\" manipulations can be undone.",
+ "desc" : "Redo the window manipulation. Only those \"moveAndResize\" manipulations can be undone.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "Redo the window manipulation. Only those \"moveAndResize\" manipulations can be undone."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:redo()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:redo()",
+ "name" : "redo"
+ },
+ {
+ "doc" : "Center the cursor on the focused window.",
+ "desc" : "Center the cursor on the focused window.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "Center the cursor on the focused window."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:centerCursor()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:centerCursor()",
+ "name" : "centerCursor"
+ }
+ ],
+ "Field" : [
+
+ ],
+ "items" : [
+ {
+ "doc" : "An integer specifying how many gridparts the screen should be divided into. Defaults to 30.",
+ "desc" : "An integer specifying how many gridparts the screen should be divided into. Defaults to 30.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "An integer specifying how many gridparts the screen should be divided into. Defaults to 30."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin.gridparts",
+ "type" : "Variable",
+ "returns" : [
+
+ ],
+ "def" : "WinWin.gridparts",
+ "name" : "gridparts"
+ },
+ {
+ "doc" : "Center the cursor on the focused window.",
+ "desc" : "Center the cursor on the focused window.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "Center the cursor on the focused window."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:centerCursor()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:centerCursor()",
+ "name" : "centerCursor"
+ },
+ {
+ "doc" : "Move and resize the focused window.\n\nParameters:\n * option - A string specifying the option, valid strings are: `halfleft`, `halfright`, `halfup`, `halfdown`, `cornerNW`, `cornerSW`, `cornerNE`, `cornerSE`, `center`, `fullscreen`, `expand`, `shrink`.",
+ "desc" : "Move and resize the focused window.",
+ "parameters" : [
+ " * option - A string specifying the option, valid strings are: `halfleft`, `halfright`, `halfup`, `halfdown`, `cornerNW`, `cornerSW`, `cornerNE`, `cornerSE`, `center`, `fullscreen`, `expand`, `shrink`."
+ ],
+ "stripped_doc" : [
+ "Move and resize the focused window.",
+ ""
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:moveAndResize(option)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:moveAndResize(option)",
+ "name" : "moveAndResize"
+ },
+ {
+ "doc" : "Move the focused window between all of the screens in the `direction`.\n\nParameters:\n * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`, `next`.",
+ "desc" : "Move the focused window between all of the screens in the `direction`.",
+ "parameters" : [
+ " * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`, `next`."
+ ],
+ "stripped_doc" : [
+ "Move the focused window between all of the screens in the `direction`.",
+ ""
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:moveToScreen(direction)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:moveToScreen(direction)",
+ "name" : "moveToScreen"
+ },
+ {
+ "doc" : "Redo the window manipulation. Only those \"moveAndResize\" manipulations can be undone.",
+ "desc" : "Redo the window manipulation. Only those \"moveAndResize\" manipulations can be undone.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "Redo the window manipulation. Only those \"moveAndResize\" manipulations can be undone."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:redo()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:redo()",
+ "name" : "redo"
+ },
+ {
+ "doc" : "Stash current windows's position and size.",
+ "desc" : "Stash current windows's position and size.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "Stash current windows's position and size."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:stash()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:stash()",
+ "name" : "stash"
+ },
+ {
+ "doc" : "Move the focused window in the `direction` by on step. The step scale equals to the width\/height of one gridpart.\n\nParameters:\n * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`.",
+ "desc" : "Move the focused window in the `direction` by on step. The step scale equals to the width\/height of one gridpart.",
+ "parameters" : [
+ " * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`."
+ ],
+ "stripped_doc" : [
+ "Move the focused window in the `direction` by on step. The step scale equals to the width\/height of one gridpart.",
+ ""
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:stepMove(direction)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:stepMove(direction)",
+ "name" : "stepMove"
+ },
+ {
+ "doc" : "Resize the focused window in the `direction` by on step.\n\nParameters:\n * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`.",
+ "desc" : "Resize the focused window in the `direction` by on step.",
+ "parameters" : [
+ " * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`."
+ ],
+ "stripped_doc" : [
+ "Resize the focused window in the `direction` by on step.",
+ ""
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:stepResize(direction)",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:stepResize(direction)",
+ "name" : "stepResize"
+ },
+ {
+ "doc" : "Undo the last window manipulation. Only those \"moveAndResize\" manipulations can be undone.",
+ "desc" : "Undo the last window manipulation. Only those \"moveAndResize\" manipulations can be undone.",
+ "parameters" : [
+
+ ],
+ "stripped_doc" : [
+ "Undo the last window manipulation. Only those \"moveAndResize\" manipulations can be undone."
+ ],
+ "notes" : [
+
+ ],
+ "signature" : "WinWin:undo()",
+ "type" : "Method",
+ "returns" : [
+
+ ],
+ "def" : "WinWin:undo()",
+ "name" : "undo"
+ }
+ ],
+ "Command" : [
+
+ ],
+ "name" : "WinWin"
+ }
+]
\ No newline at end of file
diff --git a/hammerspoon/Spoons/WinWin.spoon/init.lua b/hammerspoon/Spoons/WinWin.spoon/init.lua
new file mode 100644
index 0000000..ef7204c
--- /dev/null
+++ b/hammerspoon/Spoons/WinWin.spoon/init.lua
@@ -0,0 +1,278 @@
+--- === WinWin ===
+---
+--- Windows manipulation
+---
+--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/WinWin.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/WinWin.spoon.zip)
+
+local obj={}
+obj.__index = obj
+
+-- Metadata
+obj.name = "WinWin"
+obj.version = "1.0"
+obj.author = "ashfinal "
+obj.homepage = "https://github.com/Hammerspoon/Spoons"
+obj.license = "MIT - https://opensource.org/licenses/MIT"
+
+-- Windows manipulation history. Only the last operation is stored.
+obj.history = {}
+
+--- WinWin.gridparts
+--- Variable
+--- An integer specifying how many gridparts the screen should be divided into. Defaults to 30.
+obj.gridparts = 30
+
+--- WinWin:stepMove(direction)
+--- Method
+--- Move the focused window in the `direction` by on step. The step scale equals to the width/height of one gridpart.
+---
+--- Parameters:
+--- * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`.
+function obj:stepMove(direction)
+ local cwin = hs.window.focusedWindow()
+ if cwin then
+ local cscreen = cwin:screen()
+ local cres = cscreen:fullFrame()
+ local stepw = cres.w/obj.gridparts
+ local steph = cres.h/obj.gridparts
+ local wtopleft = cwin:topLeft()
+ if direction == "left" then
+ cwin:setTopLeft({x=wtopleft.x-stepw, y=wtopleft.y})
+ elseif direction == "right" then
+ cwin:setTopLeft({x=wtopleft.x+stepw, y=wtopleft.y})
+ elseif direction == "up" then
+ cwin:setTopLeft({x=wtopleft.x, y=wtopleft.y-steph})
+ elseif direction == "down" then
+ cwin:setTopLeft({x=wtopleft.x, y=wtopleft.y+steph})
+ end
+ else
+ hs.alert.show("No focused window!")
+ end
+end
+
+--- WinWin:stepResize(direction)
+--- Method
+--- Resize the focused window in the `direction` by on step.
+---
+--- Parameters:
+--- * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`.
+function obj:stepResize(direction)
+ local cwin = hs.window.focusedWindow()
+ if cwin then
+ local cscreen = cwin:screen()
+ local cres = cscreen:fullFrame()
+ local stepw = cres.w/obj.gridparts
+ local steph = cres.h/obj.gridparts
+ local wsize = cwin:size()
+ if direction == "left" then
+ cwin:setSize({w=wsize.w-stepw, h=wsize.h})
+ elseif direction == "right" then
+ cwin:setSize({w=wsize.w+stepw, h=wsize.h})
+ elseif direction == "up" then
+ cwin:setSize({w=wsize.w, h=wsize.h-steph})
+ elseif direction == "down" then
+ cwin:setSize({w=wsize.w, h=wsize.h+steph})
+ end
+ else
+ hs.alert.show("No focused window!")
+ end
+end
+
+--- WinWin:stash()
+--- Method
+--- Stash current windows's position and size.
+---
+
+local function isInHistory(windowid)
+ for idx,val in ipairs(obj.history) do
+ if val[1] == windowid then
+ return idx
+ end
+ end
+ return false
+end
+
+function obj:stash()
+ local cwin = hs.window.focusedWindow()
+ local winid = cwin:id()
+ local winf = cwin:frame()
+ local id_idx = isInHistory(winid)
+ if id_idx then
+ -- Bring recently used window id up, so they wouldn't get removed because of exceeding capacity
+ if id_idx == 100 then
+ local tmptable = obj.history[id_idx]
+ table.remove(obj.history, id_idx)
+ table.insert(obj.history, 1, tmptable)
+ -- Make sure the history for each application doesn't reach the maximum (100 items)
+ local id_history = obj.history[1][2]
+ if #id_history > 100 then table.remove(id_history) end
+ table.insert(id_history, 1, winf)
+ else
+ local id_history = obj.history[id_idx][2]
+ if #id_history > 100 then table.remove(id_history) end
+ table.insert(id_history, 1, winf)
+ end
+ else
+ -- Make sure the history of window id doesn't reach the maximum (100 items).
+ if #obj.history > 100 then table.remove(obj.history) end
+ -- Stash new window id and its first history
+ local newtable = {winid, {winf}}
+ table.insert(obj.history, 1, newtable)
+ end
+end
+
+--- WinWin:moveAndResize(option)
+--- Method
+--- Move and resize the focused window.
+---
+--- Parameters:
+--- * option - A string specifying the option, valid strings are: `halfleft`, `halfright`, `halfup`, `halfdown`, `cornerNW`, `cornerSW`, `cornerNE`, `cornerSE`, `center`, `fullscreen`, `expand`, `shrink`.
+
+function obj:moveAndResize(option)
+ local cwin = hs.window.focusedWindow()
+ if cwin then
+ local cscreen = cwin:screen()
+ local cres = cscreen:fullFrame()
+ local stepw = cres.w/obj.gridparts
+ local steph = cres.h/obj.gridparts
+ local wf = cwin:frame()
+ if option == "halfleft" then
+ cwin:setFrame({x=cres.x, y=cres.y, w=cres.w/2, h=cres.h})
+ elseif option == "halfright" then
+ cwin:setFrame({x=cres.x+cres.w/2, y=cres.y, w=cres.w/2, h=cres.h})
+ elseif option == "halfup" then
+ cwin:setFrame({x=cres.x, y=cres.y, w=cres.w, h=cres.h/2})
+ elseif option == "halfdown" then
+ cwin:setFrame({x=cres.x, y=cres.y+cres.h/2, w=cres.w, h=cres.h/2})
+ elseif option == "cornerNW" then
+ cwin:setFrame({x=cres.x, y=cres.y, w=cres.w/2, h=cres.h/2})
+ elseif option == "cornerNE" then
+ cwin:setFrame({x=cres.x+cres.w/2, y=cres.y, w=cres.w/2, h=cres.h/2})
+ elseif option == "cornerSW" then
+ cwin:setFrame({x=cres.x, y=cres.y+cres.h/2, w=cres.w/2, h=cres.h/2})
+ elseif option == "cornerSE" then
+ cwin:setFrame({x=cres.x+cres.w/2, y=cres.y+cres.h/2, w=cres.w/2, h=cres.h/2})
+ elseif option == "fullscreen" then
+ cwin:setFrame({x=cres.x, y=cres.y, w=cres.w, h=cres.h})
+ elseif option == "center" then
+ cwin:centerOnScreen()
+ elseif option == "expand" then
+ cwin:setFrame({x=wf.x-stepw, y=wf.y-steph, w=wf.w+(stepw*2), h=wf.h+(steph*2)})
+ elseif option == "shrink" then
+ cwin:setFrame({x=wf.x+stepw, y=wf.y+steph, w=wf.w-(stepw*2), h=wf.h-(steph*2)})
+ end
+ else
+ hs.alert.show("No focused window!")
+ end
+end
+
+--- WinWin:moveToScreen(direction)
+--- Method
+--- Move the focused window between all of the screens in the `direction`.
+---
+--- Parameters:
+--- * direction - A string specifying the direction, valid strings are: `left`, `right`, `up`, `down`, `next`.
+function obj:moveToScreen(direction)
+ local cwin = hs.window.focusedWindow()
+ if cwin then
+ local cscreen = cwin:screen()
+ if direction == "up" then
+ cwin:moveOneScreenNorth()
+ elseif direction == "down" then
+ cwin:moveOneScreenSouth()
+ elseif direction == "left" then
+ cwin:moveOneScreenWest()
+ elseif direction == "right" then
+ cwin:moveOneScreenEast()
+ elseif direction == "next" then
+ cwin:moveToScreen(cscreen:next())
+ end
+ else
+ hs.alert.show("No focused window!")
+ end
+end
+
+--- WinWin:undo()
+--- Method
+--- Undo the last window manipulation. Only those "moveAndResize" manipulations can be undone.
+---
+
+function obj:undo()
+ local cwin = hs.window.focusedWindow()
+ local winid = cwin:id()
+ -- Has this window been stored previously?
+ local id_idx = isInHistory(winid)
+ if id_idx then
+ -- Bring recently used window id up, so they wouldn't get removed because of exceeding capacity
+ if id_idx == 100 then
+ local tmptable = obj.history[id_idx]
+ table.remove(obj.history, id_idx)
+ table.insert(obj.history, 1, tmptable)
+ local id_history = obj.history[1][2]
+ cwin:setFrame(id_history[1])
+ -- Rewind the history
+ local tmpframe = id_history[1]
+ table.remove(id_history, 1)
+ table.insert(id_history, tmpframe)
+ else
+ local id_history = obj.history[id_idx][2]
+ cwin:setFrame(id_history[1])
+ local tmpframe = id_history[1]
+ table.remove(id_history, 1)
+ table.insert(id_history, tmpframe)
+ end
+ end
+end
+
+--- WinWin:redo()
+--- Method
+--- Redo the window manipulation. Only those "moveAndResize" manipulations can be undone.
+---
+
+function obj:redo()
+ local cwin = hs.window.focusedWindow()
+ local winid = cwin:id()
+ -- Has this window been stored previously?
+ local id_idx = isInHistory(winid)
+ if id_idx then
+ -- Bring recently used window id up, so they wouldn't get removed because of exceeding capacity
+ if id_idx == 100 then
+ local tmptable = obj.history[id_idx]
+ table.remove(obj.history, id_idx)
+ table.insert(obj.history, 1, tmptable)
+ local id_history = obj.history[1][2]
+ cwin:setFrame(id_history[#id_history])
+ -- Play the history
+ local tmpframe = id_history[#id_history]
+ table.remove(id_history)
+ table.insert(id_history, 1, tmpframe)
+ else
+ local id_history = obj.history[id_idx][2]
+ cwin:setFrame(id_history[#id_history])
+ local tmpframe = id_history[#id_history]
+ table.remove(id_history)
+ table.insert(id_history, 1, tmpframe)
+ end
+ end
+end
+
+--- WinWin:centerCursor()
+--- Method
+--- Center the cursor on the focused window.
+---
+
+function obj:centerCursor()
+ local cwin = hs.window.focusedWindow()
+ local wf = cwin:frame()
+ local cscreen = cwin:screen()
+ local cres = cscreen:fullFrame()
+ if cwin then
+ -- Center the cursor one the focused window
+ hs.mouse.setAbsolutePosition({x=wf.x+wf.w/2, y=wf.y+wf.h/2})
+ else
+ -- Center the cursor on the screen
+ hs.mouse.setAbsolutePosition({x=cres.x+cres.w/2, y=cres.y+cres.h/2})
+ end
+end
+
+return obj
diff --git a/hammerspoon/autoscript.lua b/hammerspoon/autoscript.lua
new file mode 100644
index 0000000..6db4fcb
--- /dev/null
+++ b/hammerspoon/autoscript.lua
@@ -0,0 +1,25 @@
+log = hs.logger.new('autoscript', 'debug')
+local cmdArr = {
+ "cd /Users/einverne/Sync/wiki/ && /bin/bash auto-push.sh",
+}
+
+function shell(cmd)
+ hs.alert.show("execute")
+ log.i('execute')
+ output, status, t, rc = hs.execute(string.format("%s", cmd), true)
+ -- result, output, err = hs.osascript.applescript(string.format('do shell script "%s"', cmd))
+ log.i(output)
+ log.i(status)
+ log.i(t)
+ log.i(rc)
+end
+
+function runAutoScripts()
+ for key, cmd in ipairs(cmdArr) do
+ shell(cmd)
+ end
+end
+
+
+myTimer = hs.timer.doEvery(1800, runAutoScripts)
+myTimer:start()
diff --git a/hammerspoon/config-example.lua b/hammerspoon/config-example.lua
new file mode 100644
index 0000000..14c25a1
--- /dev/null
+++ b/hammerspoon/config-example.lua
@@ -0,0 +1,95 @@
+-- Specify Spoons which will be loaded
+hspoon_list = {
+ "AClock",
+ "BingDaily",
+ -- "Calendar",
+ "CircleClock",
+ "ClipShow",
+ "CountDown",
+ "FnMate",
+ "HCalendar",
+ "HSaria2",
+ "HSearch",
+ -- "KSheet",
+ "SpeedMenu",
+ -- "TimeFlow",
+ -- "UnsplashZ",
+ "WinWin",
+}
+
+-- appM environment keybindings. Bundle `id` is prefered, but application `name` will be ok.
+hsapp_list = {
+ {key = 'a', name = 'Atom'},
+ {key = 'c', id = 'com.google.Chrome'},
+ {key = 'd', name = 'ShadowsocksX'},
+ {key = 'e', name = 'Emacs'},
+ {key = 'f', name = 'Finder'},
+ {key = 'i', name = 'iTerm'},
+ {key = 'k', name = 'KeyCastr'},
+ {key = 'l', name = 'Sublime Text'},
+ {key = 'm', name = 'MacVim'},
+ {key = 'o', name = 'LibreOffice'},
+ {key = 'p', name = 'mpv'},
+ {key = 'r', name = 'VimR'},
+ {key = 's', name = 'Safari'},
+ {key = 't', name = 'Terminal'},
+ {key = 'v', id = 'com.apple.ActivityMonitor'},
+ {key = 'w', name = 'Mweb'},
+ {key = 'y', id = 'com.apple.systempreferences'},
+}
+
+-- Modal supervisor keybinding, which can be used to temporarily disable ALL modal environments.
+hsupervisor_keys = {{"cmd", "shift", "ctrl"}, "Q"}
+
+-- Reload Hammerspoon configuration
+hsreload_keys = {{"cmd", "shift", "ctrl"}, "R"}
+
+-- Toggle help panel of this configuration.
+hshelp_keys = {{"alt", "shift"}, "/"}
+
+-- aria2 RPC host address
+hsaria2_host = "http://localhost:6800/jsonrpc"
+-- aria2 RPC host secret
+hsaria2_secret = "token"
+
+----------------------------------------------------------------------------------------------------
+-- Those keybindings below could be disabled by setting to {"", ""} or {{}, ""}
+
+-- Window hints keybinding: Focuse to any window you want
+hswhints_keys = {"alt", "tab"}
+
+-- appM environment keybinding: Application Launcher
+hsappM_keys = {"alt", "A"}
+
+-- clipshowM environment keybinding: System clipboard reader
+hsclipsM_keys = {"alt", "C"}
+
+-- Toggle the display of aria2 frontend
+hsaria2_keys = {"alt", "D"}
+
+-- Launch Hammerspoon Search
+hsearch_keys = {"alt", "G"}
+
+-- Read Hammerspoon and Spoons API manual in default browser
+hsman_keys = {"alt", "H"}
+
+-- countdownM environment keybinding: Visual countdown
+hscountdM_keys = {"alt", "I"}
+
+-- Lock computer's screen
+hslock_keys = {"alt", "L"}
+
+-- resizeM environment keybinding: Windows manipulation
+hsresizeM_keys = {"alt", "R"}
+
+-- cheatsheetM environment keybinding: Cheatsheet copycat
+hscheats_keys = {"alt", "S"}
+
+-- Show digital clock above all windows
+hsaclock_keys = {"alt", "T"}
+
+-- Type the URL and title of the frontmost web page open in Google Chrome or Safari.
+hstype_keys = {"alt", "V"}
+
+-- Toggle Hammerspoon console
+hsconsole_keys = {"alt", "Z"}
diff --git a/hammerspoon/ime.lua b/hammerspoon/ime.lua
new file mode 100644
index 0000000..15504a4
--- /dev/null
+++ b/hammerspoon/ime.lua
@@ -0,0 +1,72 @@
+local function Chinese()
+ hs.keycodes.currentSourceID("im.rime.inputmethod.Squirrel.Rime")
+end
+
+local function English()
+ hs.keycodes.currentSourceID("com.apple.keylayout.ABC")
+end
+
+-- app to expected ime config
+local app2Ime = {
+ {'/System/Library/CoreServices/Finder.app', 'English'},
+ {'/Applications/Alfred 4.app', 'English'},
+ {'/Applications/Bitwarden.app', 'English'},
+ -- {'/Applications/iTerm.app', 'English'},
+ {'/Applications/Xcode.app', 'English'},
+ {'/Applications/GoldenDict.app', 'English'},
+ {'/Applications/Google Chrome.app', 'Chinese'},
+ {'/Applications/DingTalk.app', 'Chinese'},
+ {'/Applications/Kindle.app', 'English'},
+ {'/Applications/NeteaseMusic.app', 'Chinese'},
+ {'/Applications/WeChat.app', 'Chinese'},
+ {'/Applications/Lark.app', 'Chinese'},
+ {'/Applications/System Preferences.app', 'English'},
+ {'/Applications/Dash.app', 'English'},
+ {'/Applications/MindNode.app', 'Chinese'},
+ {'/Applications/Preview.app', 'Chinese'},
+ {'/Applications/Obsidian.app', 'Chinese'},
+ {'/Applications/wechatwebdevtools.app', 'English'},
+ {'/Applications/Sketch.app', 'English'},
+ {'/Users/einverne/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.8538.31/IntelliJ IDEA.app', 'Chinese'},
+}
+
+function updateFocusAppInputMethod()
+ local focusAppPath = hs.window.frontmostWindow():application():path()
+ -- hs.alert.show(focusAppPath)
+ for index, app in pairs(app2Ime) do
+ local appPath = app[1]
+ local expectedIme = app[2]
+
+ if focusAppPath == appPath then
+ if expectedIme == 'English' then
+ English()
+ else
+ Chinese()
+ end
+ break
+ end
+ end
+end
+
+-- helper hotkey to figure out the app path and name of current focused window
+hs.hotkey.bind({'ctrl', 'cmd'}, ".", function()
+ hs.alert.show("App path: "
+ ..hs.window.focusedWindow():application():path()
+ .."\n"
+ .."App name: "
+ ..hs.window.focusedWindow():application():name()
+ .."\n"
+ .."IM source id: "
+ ..hs.keycodes.currentSourceID())
+end)
+
+-- Handle cursor focus and application's screen manage.
+function applicationWatcher(appName, eventType, appObject)
+ if (eventType == hs.application.watcher.activated) then
+ updateFocusAppInputMethod()
+ end
+end
+
+appWatcher = hs.application.watcher.new(applicationWatcher)
+appWatcher:start()
+
diff --git a/hammerspoon/init.lua b/hammerspoon/init.lua
new file mode 100644
index 0000000..dd060e4
--- /dev/null
+++ b/hammerspoon/init.lua
@@ -0,0 +1,566 @@
+local LOGLEVEL = 'debug'
+
+log = hs.logger.new('init', 'debug')
+
+require 'autoscript'
+require 'ime'
+-- require 'usb'
+
+-- hs.loadSpoon("ReloadConfiguration")
+-- spoon.ReloadConfiguration:start()
+-- hs.alert.show("Config reload!")
+
+-- Hyper key in Sierra
+k = hs.hotkey.modal.new({}, "F17")
+
+-- Enter Hyper Mode when F18 (Hyper/Capslock) is pressed
+pressedF18 = function() k:enter() end
+
+-- Leave Hyper Mode when F18 (Hyper/Capslock) is pressed,
+-- send ESCAPE if no other keys are pressed.
+releasedF18 = function() k:exit() end
+
+f18 = hs.hotkey.bind({}, 'F18', pressedF18, releasedF18)
+
+
+
+hyper = {"ctrl", "alt", "cmd", "shift"}
+function moveWindow(direction)
+ return function()
+ local win = hs.window.focusedWindow()
+ local app = win:application()
+ local app_name = app:name()
+ local f = win:frame()
+ local screen = win:screen()
+ local max = screen:frame()
+ if direction == "left" then
+ f.x = max.x
+ f.y = max.y
+ f.w = (max.w / 2)
+ f.h = max.h
+ elseif direction == "right" then
+ f.x = (max.x + (max.w / 2))
+ f.y = max.y
+ f.w = (max.w / 2)
+ f.h = max.h
+ elseif direction == "full" then
+ f.x = max.x
+ f.y = max.y
+ f.w = max.w
+ f.h = max.h
+ elseif direction == "up" then
+ f.x = max.x
+ f.y = max.y
+ f.w = max.w
+ f.h = (max.h / 2)
+ elseif direction == "down" then
+ f.x = max.x
+ f.y = max.y + (max.h / 2)
+ f.w = max.w
+ f.h = max.h / 2
+ elseif direction == "normal" then
+ f.x = (max.x + (max.w / 8)) + 6
+ f.y = max.y
+ f.w = (max.w * 3 / 4) - 12
+ f.h = max.h
+ end
+ win:setFrame(f, 0.0)
+ end
+end
+hs.hotkey.bind(hyper, "H", moveWindow("left"))
+hs.hotkey.bind(hyper, "L", moveWindow("right"))
+hs.hotkey.bind(hyper, "K", moveWindow("up"))
+hs.hotkey.bind(hyper, "J", moveWindow("down"))
+hs.hotkey.bind(hyper, "F", moveWindow("full"))
+
+-- hs.hotkey.bind(hyper, "C", function() hs.application.launchOrFocus("Google Chrome") end)
+function moveWindowToDisplay(d)
+ return function()
+ local displays = hs.screen.allScreens()
+ log.i(displays)
+ local win = hs.window.focusedWindow()
+ win:moveToScreen(displays[d], false, true)
+ end
+end
+
+-- hs.hotkey.bind(hyper, "m", moveWindowToDisplay(1))
+-- hs.hotkey.bind(hyper, "8", moveWindowToDisplay(2))
+-- hs.hotkey.bind(hyper, "9", moveWindowToDisplay(3))
+
+function movieWinBetweenMonitors(d)
+ return function()
+ local win = hs.window.focusedWindow()
+ -- get the screen where the focused window is displayed, a.k.a current screen
+ local screen = win:screen()
+ -- compute the unitRect of the focused window relative to the current screen
+ -- and move the window to the next screen setting the same unitRect
+ -- https://www.hammerspoon.org/docs/hs.window.html#move
+ if d == 'next' then
+ win:move(win:frame():toUnitRect(screen:frame()), screen:next(), true, 0)
+ elseif d == 'previous' then
+ win:move(win:frame():toUnitRect(screen:frame()), screen:previous(), true, 0)
+ end
+ end
+end
+
+hs.hotkey.bind(hyper, 'N', movieWinBetweenMonitors('next'))
+hs.hotkey.bind(hyper, 'P', movieWinBetweenMonitors('previous'))
+
+
+
+local grid = require 'hs.grid'
+hs.hotkey.bind(hyper, ",", grid.show)
+
+middle_monitor="DELL U2414H"
+left_monitor="DELL P2417H"
+right_monitor="DELL U2412M"
+
+local reading_layout = {
+ {"Google Chrome", nil, middle_monitor, hs.layout.maximized, nil, nil},
+ {"iTerm", nil, left_monitor, hs.layout.maximized, nil, nil},
+ {"Miwork", nil, right_monitor, hs.layout.top50, nil, nil},
+}
+
+hs.hotkey.bind(hyper, "1", function()
+ hs.application.launchOrFocus('Google Chrome')
+ hs.application.launchOrFocus('iTerm')
+ hs.application.launchOrFocus('Miwork')
+
+ hs.layout.apply(reading_layout)
+end)
+
+local coding_layout = {
+ {"IntelliJ IDEA Ultimate", nil, middle_monitor, hs.layout.maximized, nil, nil},
+ {"iTerm", nil, left_monitor, hs.layout.maximized, nil, nil},
+}
+
+hs.hotkey.bind(hyper, "2", function()
+ hs.application.launchOrFocus('IntelliJ IDEA')
+
+ hs.layout.apply(coding_layout)
+end)
+
+
+wifiWatcher = nil
+homeSSID = "EinVerne_5G"
+lastSSID = hs.wifi.currentNetwork()
+
+workSSID = "MIOffice-5G"
+
+function ssidChangedCallback()
+ newSSID = hs.wifi.currentNetwork()
+
+ if newSSID == homeSSID and lastSSID ~= homeSSID then
+ -- We just joined our home WiFi network
+ hs.audiodevice.defaultOutputDevice():setVolume(25)
+ hs.alert.show("Welcome home!")
+ -- result = hs.network.configuration:setLocation("Home")
+ -- hs.alert.show(result)
+ elseif newSSID ~= homeSSID and lastSSID == homeSSID then
+ -- We just departed our home WiFi network
+ hs.audiodevice.defaultOutputDevice():setVolume(0)
+ hs.alert.show("left home!")
+ -- result = hs.network.configuration:setLocation("Automatic")
+ -- hs.alert.show(result)
+ end
+
+ if newSSID == workSSID then
+ hs.alert.show("work karabiner setup")
+ hs.execute("'/Library/Application Support/org.pqrs/Karabiner-Elements/bin/karabiner_cli' --select-profile Work")
+ else
+ hs.alert.show("built-in karabiner setup")
+ hs.execute("'/Library/Application Support/org.pqrs/Karabiner-Elements/bin/karabiner_cli' --select-profile Built-in")
+ end
+
+ lastSSID = newSSID
+end
+
+wifiWatcher = hs.wifi.watcher.new(ssidChangedCallback)
+wifiWatcher:start()
+
+networkConf = hs.network.configuration.open()
+location = networkConf:location()
+
+hs.hotkey.bind({"cmd", "alt", "ctrl"}, "H", function()
+ hs.execute('/usr/sbin/networksetup -switchtolocation Home')
+end)
+hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function()
+ hs.execute('/usr/sbin/networksetup -switchtolocation Automatic')
+end)
+
+
+
+hs.hotkey.alertDuration = 0
+hs.hints.showTitleThresh = 0
+hs.window.animationDuration = 0
+
+-- Use the standardized config location, if present
+custom_config = hs.fs.pathToAbsolute(os.getenv("HOME") .. '/.config/hammerspoon/private/config.lua')
+if custom_config then
+ print("Loading custom config")
+ dofile( os.getenv("HOME") .. "/.config/hammerspoon/private/config.lua")
+ privatepath = hs.fs.pathToAbsolute(hs.configdir .. '/private/config.lua')
+ if privatepath then
+ hs.alert("You have config in both .config/hammerspoon and .hammerspoon/private.\nThe .config/hammerspoon one will be used.")
+ end
+else
+ -- otherwise fallback to 'classic' location.
+ if not privatepath then
+ privatepath = hs.fs.pathToAbsolute(hs.configdir .. '/private')
+ -- Create `~/.hammerspoon/private` directory if not exists.
+ hs.fs.mkdir(hs.configdir .. '/private')
+ end
+ privateconf = hs.fs.pathToAbsolute(hs.configdir .. '/private/config.lua')
+ if privateconf then
+ -- Load awesomeconfig file if exists
+ require('private/config')
+ end
+end
+
+hsreload_keys = {hyper, "R"}
+hsreload_keys = hsreload_keys or {{"cmd", "shift", "ctrl"}, "R"}
+if string.len(hsreload_keys[2]) > 0 then
+ hs.hotkey.bind(hsreload_keys[1], hsreload_keys[2], "Reload Configuration", function() hs.reload() end)
+ hs.notify.new({title="Hammerspoon config reloaded", informativeText="Manually trigged via keyboard shortcut"}):send()
+end
+
+-- ModalMgr Spoon must be loaded explicitly, because this repository heavily relies upon it.
+hs.loadSpoon("ModalMgr")
+
+-- Define default Spoons which will be loaded later
+if not hspoon_list then
+ hspoon_list = {
+ "AClock",
+ "BingDaily",
+ "CircleClock",
+ "ClipShow",
+ "CountDown",
+ "HCalendar",
+ --"HSaria2",
+ "SpeedMenu",
+ "WinWin",
+ }
+end
+
+-- Load those Spoons
+for _, v in pairs(hspoon_list) do
+ hs.loadSpoon(v)
+end
+
+----------------------------------------------------------------------------------------------------
+-- Then we create/register all kinds of modal keybindings environments.
+----------------------------------------------------------------------------------------------------
+-- Register windowHints (Register a keybinding which is NOT modal environment with modal supervisor)
+hswhints_keys = hswhints_keys or {"alt", "tab"}
+if string.len(hswhints_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hswhints_keys[1], hswhints_keys[2], 'Show Window Hints', function()
+ spoon.ModalMgr:deactivateAll()
+ hs.hints.windowHints()
+ end)
+end
+
+----------------------------------------------------------------------------------------------------
+-- appM modal environment
+spoon.ModalMgr:new("appM")
+local cmodal = spoon.ModalMgr.modal_list["appM"]
+cmodal:bind('', 'escape', 'Deactivate appM', function() spoon.ModalMgr:deactivate({"appM"}) end)
+cmodal:bind('', 'Q', 'Deactivate appM', function() spoon.ModalMgr:deactivate({"appM"}) end)
+cmodal:bind('', 'tab', 'Toggle Cheatsheet', function() spoon.ModalMgr:toggleCheatsheet() end)
+if not hsapp_list then
+ hsapp_list = {
+ {key = 'f', name = 'Finder'},
+ {key = 'c', name = 'Google Chrome'},
+ {key = 's', name = 'Safari'},
+ {key = 't', name = 'Terminal'},
+ {key = 'v', id = 'com.apple.ActivityMonitor'},
+ {key = 'y', id = 'com.apple.systempreferences'},
+ }
+end
+for _, v in ipairs(hsapp_list) do
+ if v.id then
+ local located_name = hs.application.nameForBundleID(v.id)
+ if located_name then
+ cmodal:bind('', v.key, located_name, function()
+ hs.application.launchOrFocusByBundleID(v.id)
+ spoon.ModalMgr:deactivate({"appM"})
+ end)
+ end
+ elseif v.name then
+ cmodal:bind('', v.key, v.name, function()
+ hs.application.launchOrFocus(v.name)
+ spoon.ModalMgr:deactivate({"appM"})
+ end)
+ end
+end
+
+-- Then we register some keybindings with modal supervisor
+hsappM_keys = hsappM_keys or {"alt", "A"}
+if string.len(hsappM_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hsappM_keys[1], hsappM_keys[2], "Enter AppM Environment", function()
+ spoon.ModalMgr:deactivateAll()
+ -- Show the keybindings cheatsheet once appM is activated
+ spoon.ModalMgr:activate({"appM"}, "#FFBD2E", true)
+ end)
+end
+
+----------------------------------------------------------------------------------------------------
+-- clipshowM modal environment
+if spoon.ClipShow then
+ spoon.ModalMgr:new("clipshowM")
+ local cmodal = spoon.ModalMgr.modal_list["clipshowM"]
+ cmodal:bind('', 'escape', 'Deactivate clipshowM', function()
+ spoon.ClipShow:toggleShow()
+ spoon.ModalMgr:deactivate({"clipshowM"})
+ end)
+ cmodal:bind('', 'Q', 'Deactivate clipshowM', function()
+ spoon.ClipShow:toggleShow()
+ spoon.ModalMgr:deactivate({"clipshowM"})
+ end)
+ cmodal:bind('', 'N', 'Save this Session', function()
+ spoon.ClipShow:saveToSession()
+ end)
+ cmodal:bind('', 'R', 'Restore last Session', function()
+ spoon.ClipShow:restoreLastSession()
+ end)
+ cmodal:bind('', 'B', 'Open in Browser', function()
+ spoon.ClipShow:openInBrowserWithRef()
+ spoon.ClipShow:toggleShow()
+ spoon.ModalMgr:deactivate({"clipshowM"})
+ end)
+ cmodal:bind('', 'S', 'Search with Bing', function()
+ spoon.ClipShow:openInBrowserWithRef("https://www.bing.com/search?q=")
+ spoon.ClipShow:toggleShow()
+ spoon.ModalMgr:deactivate({"clipshowM"})
+ end)
+ cmodal:bind('', 'M', 'Open in MacVim', function()
+ spoon.ClipShow:openWithCommand("/usr/local/bin/mvim")
+ spoon.ClipShow:toggleShow()
+ spoon.ModalMgr:deactivate({"clipshowM"})
+ end)
+ cmodal:bind('', 'F', 'Save to Desktop', function()
+ spoon.ClipShow:saveToFile()
+ spoon.ClipShow:toggleShow()
+ spoon.ModalMgr:deactivate({"clipshowM"})
+ end)
+ cmodal:bind('', 'H', 'Search in Github', function()
+ spoon.ClipShow:openInBrowserWithRef("https://github.com/search?q=")
+ spoon.ClipShow:toggleShow()
+ spoon.ModalMgr:deactivate({"clipshowM"})
+ end)
+ cmodal:bind('', 'G', 'Search with Google', function()
+ spoon.ClipShow:openInBrowserWithRef("https://www.google.com/search?q=")
+ spoon.ClipShow:toggleShow()
+ spoon.ModalMgr:deactivate({"clipshowM"})
+ end)
+ cmodal:bind('', 'L', 'Open in Sublime Text', function()
+ spoon.ClipShow:openWithCommand("/usr/local/bin/subl")
+ spoon.ClipShow:toggleShow()
+ spoon.ModalMgr:deactivate({"clipshowM"})
+ end)
+
+ -- Register clipshowM with modal supervisor
+ hsclipsM_keys = hsclipsM_keys or {"alt", "C"}
+ if string.len(hsclipsM_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hsclipsM_keys[1], hsclipsM_keys[2], "Enter clipshowM Environment", function()
+ -- We need to take action upon hsclipsM_keys is pressed, since pressing another key to showing ClipShow panel is redundant.
+ spoon.ClipShow:toggleShow()
+ -- Need a little trick here. Since the content type of system clipboard may be "URL", in which case we don't need to activate clipshowM.
+ if spoon.ClipShow.canvas:isShowing() then
+ spoon.ModalMgr:deactivateAll()
+ spoon.ModalMgr:activate({"clipshowM"})
+ end
+ end)
+ end
+end
+
+----------------------------------------------------------------------------------------------------
+-- Register HSaria2
+if spoon.HSaria2 then
+ -- First we need to connect to aria2 rpc host
+ hsaria2_host = hsaria2_host or "http://localhost:6800/jsonrpc"
+ hsaria2_secret = hsaria2_secret or "token"
+ spoon.HSaria2:connectToHost(hsaria2_host, hsaria2_secret)
+
+ hsaria2_keys = hsaria2_keys or {"alt", "D"}
+ if string.len(hsaria2_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hsaria2_keys[1], hsaria2_keys[2], 'Toggle aria2 Panel', function() spoon.HSaria2:togglePanel() end)
+ end
+end
+
+----------------------------------------------------------------------------------------------------
+-- Register Hammerspoon Search
+if spoon.HSearch then
+ hsearch_keys = hsearch_keys or {"alt", "G"}
+ if string.len(hsearch_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hsearch_keys[1], hsearch_keys[2], 'Launch Hammerspoon Search', function() spoon.HSearch:toggleShow() end)
+ end
+end
+
+----------------------------------------------------------------------------------------------------
+-- Register Hammerspoon API manual: Open Hammerspoon manual in default browser
+hsman_keys = hsman_keys or {"alt", "H"}
+if string.len(hsman_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hsman_keys[1], hsman_keys[2], "Read Hammerspoon Manual", function()
+ hs.doc.hsdocs.forceExternalBrowser(true)
+ hs.doc.hsdocs.moduleEntitiesInSidebar(true)
+ hs.doc.hsdocs.help()
+ end)
+end
+
+----------------------------------------------------------------------------------------------------
+-- countdownM modal environment
+if spoon.CountDown then
+ spoon.ModalMgr:new("countdownM")
+ local cmodal = spoon.ModalMgr.modal_list["countdownM"]
+ cmodal:bind('', 'escape', 'Deactivate countdownM', function() spoon.ModalMgr:deactivate({"countdownM"}) end)
+ cmodal:bind('', 'Q', 'Deactivate countdownM', function() spoon.ModalMgr:deactivate({"countdownM"}) end)
+ cmodal:bind('', 'tab', 'Toggle Cheatsheet', function() spoon.ModalMgr:toggleCheatsheet() end)
+ cmodal:bind('', '0', '5 Minutes Countdown', function()
+ spoon.CountDown:startFor(5)
+ spoon.ModalMgr:deactivate({"countdownM"})
+ end)
+ for i = 1, 9 do
+ cmodal:bind('', tostring(i), string.format("%s Minutes Countdown", 10 * i), function()
+ spoon.CountDown:startFor(10 * i)
+ spoon.ModalMgr:deactivate({"countdownM"})
+ end)
+ end
+ cmodal:bind('', 'return', '25 Minutes Countdown', function()
+ spoon.CountDown:startFor(25)
+ spoon.ModalMgr:deactivate({"countdownM"})
+ end)
+ cmodal:bind('', 'space', 'Pause/Resume CountDown', function()
+ spoon.CountDown:pauseOrResume()
+ spoon.ModalMgr:deactivate({"countdownM"})
+ end)
+
+ -- Register countdownM with modal supervisor
+ hscountdM_keys = hscountdM_keys or {"alt", "I"}
+ if string.len(hscountdM_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hscountdM_keys[1], hscountdM_keys[2], "Enter countdownM Environment", function()
+ spoon.ModalMgr:deactivateAll()
+ -- Show the keybindings cheatsheet once countdownM is activated
+ spoon.ModalMgr:activate({"countdownM"}, "#FF6347", true)
+ end)
+ end
+end
+
+----------------------------------------------------------------------------------------------------
+-- Register lock screen
+hslock_keys = hslock_keys or {"alt", "L"}
+if string.len(hslock_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hslock_keys[1], hslock_keys[2], "Lock Screen", function()
+ hs.caffeinate.lockScreen()
+ end)
+end
+
+----------------------------------------------------------------------------------------------------
+-- resizeM modal environment
+if spoon.WinWin then
+ spoon.ModalMgr:new("resizeM")
+ local cmodal = spoon.ModalMgr.modal_list["resizeM"]
+ cmodal:bind('', 'escape', 'Deactivate resizeM', function() spoon.ModalMgr:deactivate({"resizeM"}) end)
+ cmodal:bind('', 'Q', 'Deactivate resizeM', function() spoon.ModalMgr:deactivate({"resizeM"}) end)
+ cmodal:bind('', 'tab', 'Toggle Cheatsheet', function() spoon.ModalMgr:toggleCheatsheet() end)
+ cmodal:bind('', 'A', 'Move Leftward', function() spoon.WinWin:stepMove("left") end, nil, function() spoon.WinWin:stepMove("left") end)
+ cmodal:bind('', 'D', 'Move Rightward', function() spoon.WinWin:stepMove("right") end, nil, function() spoon.WinWin:stepMove("right") end)
+ cmodal:bind('', 'W', 'Move Upward', function() spoon.WinWin:stepMove("up") end, nil, function() spoon.WinWin:stepMove("up") end)
+ cmodal:bind('', 'S', 'Move Downward', function() spoon.WinWin:stepMove("down") end, nil, function() spoon.WinWin:stepMove("down") end)
+ cmodal:bind('', 'H', 'Lefthalf of Screen', function() spoon.WinWin:stash() spoon.WinWin:moveAndResize("halfleft") end)
+ cmodal:bind('', 'L', 'Righthalf of Screen', function() spoon.WinWin:stash() spoon.WinWin:moveAndResize("halfright") end)
+ cmodal:bind('', 'K', 'Uphalf of Screen', function() spoon.WinWin:stash() spoon.WinWin:moveAndResize("halfup") end)
+ cmodal:bind('', 'J', 'Downhalf of Screen', function() spoon.WinWin:stash() spoon.WinWin:moveAndResize("halfdown") end)
+ cmodal:bind('', 'Y', 'NorthWest Corner', function() spoon.WinWin:stash() spoon.WinWin:moveAndResize("cornerNW") end)
+ cmodal:bind('', 'O', 'NorthEast Corner', function() spoon.WinWin:stash() spoon.WinWin:moveAndResize("cornerNE") end)
+ cmodal:bind('', 'U', 'SouthWest Corner', function() spoon.WinWin:stash() spoon.WinWin:moveAndResize("cornerSW") end)
+ cmodal:bind('', 'I', 'SouthEast Corner', function() spoon.WinWin:stash() spoon.WinWin:moveAndResize("cornerSE") end)
+ cmodal:bind('', 'F', 'Fullscreen', function() spoon.WinWin:stash() spoon.WinWin:moveAndResize("fullscreen") end)
+ cmodal:bind('', 'C', 'Center Window', function() spoon.WinWin:stash() spoon.WinWin:moveAndResize("center") end)
+ cmodal:bind('', '=', 'Stretch Outward', function() spoon.WinWin:moveAndResize("expand") end, nil, function() spoon.WinWin:moveAndResize("expand") end)
+ cmodal:bind('', '-', 'Shrink Inward', function() spoon.WinWin:moveAndResize("shrink") end, nil, function() spoon.WinWin:moveAndResize("shrink") end)
+ cmodal:bind('shift', 'H', 'Move Leftward', function() spoon.WinWin:stepResize("left") end, nil, function() spoon.WinWin:stepResize("left") end)
+ cmodal:bind('shift', 'L', 'Move Rightward', function() spoon.WinWin:stepResize("right") end, nil, function() spoon.WinWin:stepResize("right") end)
+ cmodal:bind('shift', 'K', 'Move Upward', function() spoon.WinWin:stepResize("up") end, nil, function() spoon.WinWin:stepResize("up") end)
+ cmodal:bind('shift', 'J', 'Move Downward', function() spoon.WinWin:stepResize("down") end, nil, function() spoon.WinWin:stepResize("down") end)
+ cmodal:bind('', 'left', 'Move to Left Monitor', function() spoon.WinWin:stash() spoon.WinWin:moveToScreen("left") end)
+ cmodal:bind('', 'right', 'Move to Right Monitor', function() spoon.WinWin:stash() spoon.WinWin:moveToScreen("right") end)
+ cmodal:bind('', 'up', 'Move to Above Monitor', function() spoon.WinWin:stash() spoon.WinWin:moveToScreen("up") end)
+ cmodal:bind('', 'down', 'Move to Below Monitor', function() spoon.WinWin:stash() spoon.WinWin:moveToScreen("down") end)
+ cmodal:bind('', 'space', 'Move to Next Monitor', function() spoon.WinWin:stash() spoon.WinWin:moveToScreen("next") end)
+ cmodal:bind('', '[', 'Undo Window Manipulation', function() spoon.WinWin:undo() end)
+ cmodal:bind('', ']', 'Redo Window Manipulation', function() spoon.WinWin:redo() end)
+ cmodal:bind('', '`', 'Center Cursor', function() spoon.WinWin:centerCursor() end)
+
+ -- Register resizeM with modal supervisor
+ hsresizeM_keys = hsresizeM_keys or {"alt", "R"}
+ if string.len(hsresizeM_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hsresizeM_keys[1], hsresizeM_keys[2], "Enter resizeM Environment", function()
+ -- Deactivate some modal environments or not before activating a new one
+ spoon.ModalMgr:deactivateAll()
+ -- Show an status indicator so we know we're in some modal environment now
+ spoon.ModalMgr:activate({"resizeM"}, "#B22222")
+ end)
+ end
+end
+
+----------------------------------------------------------------------------------------------------
+-- cheatsheetM modal environment (Because KSheet Spoon is NOT loaded, cheatsheetM will NOT be activated)
+if spoon.KSheet then
+ spoon.ModalMgr:new("cheatsheetM")
+ local cmodal = spoon.ModalMgr.modal_list["cheatsheetM"]
+ cmodal:bind('', 'escape', 'Deactivate cheatsheetM', function()
+ spoon.KSheet:hide()
+ spoon.ModalMgr:deactivate({"cheatsheetM"})
+ end)
+ cmodal:bind('', 'Q', 'Deactivate cheatsheetM', function()
+ spoon.KSheet:hide()
+ spoon.ModalMgr:deactivate({"cheatsheetM"})
+ end)
+
+ -- Register cheatsheetM with modal supervisor
+ hscheats_keys = hscheats_keys or {"alt", "S"}
+ if string.len(hscheats_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hscheats_keys[1], hscheats_keys[2], "Enter cheatsheetM Environment", function()
+ spoon.KSheet:show()
+ spoon.ModalMgr:deactivateAll()
+ spoon.ModalMgr:activate({"cheatsheetM"})
+ end)
+ end
+end
+
+----------------------------------------------------------------------------------------------------
+-- Register AClock
+if spoon.AClock then
+ hsaclock_keys = hsaclock_keys or {"alt", "T"}
+ if string.len(hsaclock_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hsaclock_keys[1], hsaclock_keys[2], "Toggle Floating Clock", function() spoon.AClock:toggleShow() end)
+ end
+end
+
+----------------------------------------------------------------------------------------------------
+-- Register browser tab typist: Type URL of current tab of running browser in markdown format. i.e. [title](link)
+hstype_keys = hstype_keys or {"alt", "V"}
+if string.len(hstype_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hstype_keys[1], hstype_keys[2], "Type Browser Link", function()
+ local safari_running = hs.application.applicationsForBundleID("com.apple.Safari")
+ local chrome_running = hs.application.applicationsForBundleID("com.google.Chrome")
+ if #safari_running > 0 then
+ local stat, data = hs.applescript('tell application "Safari" to get {URL, name} of current tab of window 1')
+ if stat then hs.eventtap.keyStrokes("[" .. data[2] .. "](" .. data[1] .. ")") end
+ elseif #chrome_running > 0 then
+ local stat, data = hs.applescript('tell application "Google Chrome" to get {URL, title} of active tab of window 1')
+ if stat then hs.eventtap.keyStrokes("[" .. data[2] .. "](" .. data[1] .. ")") end
+ end
+ end)
+end
+
+----------------------------------------------------------------------------------------------------
+-- Register Hammerspoon console
+hsconsole_keys = hsconsole_keys or {"alt", "Z"}
+if string.len(hsconsole_keys[2]) > 0 then
+ spoon.ModalMgr.supervisor:bind(hsconsole_keys[1], hsconsole_keys[2], "Toggle Hammerspoon Console", function() hs.toggleConsole() end)
+end
+
+----------------------------------------------------------------------------------------------------
+-- Finally we initialize ModalMgr supervisor
+spoon.ModalMgr.supervisor:enter()
diff --git a/hammerspoon/usb.lua b/hammerspoon/usb.lua
new file mode 100644
index 0000000..28bb32b
--- /dev/null
+++ b/hammerspoon/usb.lua
@@ -0,0 +1,15 @@
+
+log = hs.logger.new('autoscript', 'debug')
+
+function keyboardCallback(data)
+-- if data.eventType == "added" then
+-- log.i(data.productName .. data.vendorName .. data.vendorID .. data.productID .. "added")
+-- end
+-- if data.eventType == "removed" then
+-- log.i(data.productName .. data.vendorName .. data.vendorID .. data.productID .. "removed")
+-- end
+ log.i(data)
+end
+
+local keyboardWatcher = hs.usb.watcher.new(keyboardCallback)
+keyboardWatcher:start()