Browse Source

Add hammerspoon config

ubuntu_ci
Ein Verne 4 years ago
parent
commit
4ea76e54b9
No known key found for this signature in database
GPG Key ID: 926634D64ACAD792
  1. 2
      hammerspoon/.gitignore
  2. 149
      hammerspoon/README.md
  3. 79
      hammerspoon/Spoons/AClock.spoon/docs.json
  4. 57
      hammerspoon/Spoons/AClock.spoon/init.lua
  5. 41
      hammerspoon/Spoons/BingDaily.spoon/docs.json
  6. 63
      hammerspoon/Spoons/BingDaily.spoon/init.lua
  7. 41
      hammerspoon/Spoons/Calendar.spoon/docs.json
  8. 183
      hammerspoon/Spoons/Calendar.spoon/init.lua
  9. 41
      hammerspoon/Spoons/CircleClock.spoon/docs.json
  10. 116
      hammerspoon/Spoons/CircleClock.spoon/init.lua
  11. BIN
      hammerspoon/Spoons/CircleClock.spoon/watchbg.png
  12. 283
      hammerspoon/Spoons/ClipShow.spoon/docs.json
  13. 394
      hammerspoon/Spoons/ClipShow.spoon/init.lua
  14. 163
      hammerspoon/Spoons/CountDown.spoon/docs.json
  15. 129
      hammerspoon/Spoons/CountDown.spoon/init.lua
  16. 41
      hammerspoon/Spoons/HCalendar.spoon/docs.json
  17. 179
      hammerspoon/Spoons/HCalendar.spoon/init.lua
  18. 159
      hammerspoon/Spoons/HSearch.spoon/docs.json
  19. 55
      hammerspoon/Spoons/HSearch.spoon/hs_btabs.lua
  20. 73
      hammerspoon/Spoons/HSearch.spoon/hs_datamuse.lua
  21. 87
      hammerspoon/Spoons/HSearch.spoon/hs_emoji.lua
  22. 96
      hammerspoon/Spoons/HSearch.spoon/hs_note.lua
  23. 87
      hammerspoon/Spoons/HSearch.spoon/hs_time.lua
  24. 63
      hammerspoon/Spoons/HSearch.spoon/hs_v2ex.lua
  25. 94
      hammerspoon/Spoons/HSearch.spoon/hs_yddict.lua
  26. 256
      hammerspoon/Spoons/HSearch.spoon/init.lua
  27. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/chrome.png
  28. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/emoji.png
  29. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/justnote.png
  30. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/menus.png
  31. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/safari.png
  32. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/tabs.png
  33. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/taskkill.png
  34. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/thesaurus.png
  35. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/time.png
  36. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/v2ex.png
  37. BIN
      hammerspoon/Spoons/HSearch.spoon/resources/youdao.png
  38. 119
      hammerspoon/Spoons/KSheet.spoon/docs.json
  39. 214
      hammerspoon/Spoons/KSheet.spoon/init.lua
  40. 253
      hammerspoon/Spoons/ModalMgr.spoon/docs.json
  41. 189
      hammerspoon/Spoons/ModalMgr.spoon/init.lua
  42. 90
      hammerspoon/Spoons/ReloadConfiguration.spoon/docs.json
  43. 49
      hammerspoon/Spoons/ReloadConfiguration.spoon/init.lua
  44. 79
      hammerspoon/Spoons/SpeedMenu.spoon/docs.json
  45. 111
      hammerspoon/Spoons/SpeedMenu.spoon/init.lua
  46. 41
      hammerspoon/Spoons/TimeFlow.spoon/docs.json
  47. 153
      hammerspoon/Spoons/TimeFlow.spoon/init.lua
  48. BIN
      hammerspoon/Spoons/TimeFlow.spoon/timebg.png
  49. 41
      hammerspoon/Spoons/UnsplashZ.spoon/docs.json
  50. 52
      hammerspoon/Spoons/UnsplashZ.spoon/init.lua
  51. 406
      hammerspoon/Spoons/WinWin.spoon/docs.json
  52. 278
      hammerspoon/Spoons/WinWin.spoon/init.lua
  53. 25
      hammerspoon/autoscript.lua
  54. 95
      hammerspoon/config-example.lua
  55. 72
      hammerspoon/ime.lua
  56. 566
      hammerspoon/init.lua
  57. 15
      hammerspoon/usb.lua

2
hammerspoon/.gitignore vendored

@ -0,0 +1,2 @@
.DS_Store
private

149
hammerspoon/README.md

@ -0,0 +1,149 @@
# hammerspoon configuration
hammerspoon is my configuration for [Hammerspoon](http://www.hammerspoon.org/). It has highly modal-based, vim-style keybindings, provides some functionality like desktop widgets, window management, application launcher, instant search, aria2 frontend ... etc.
## Get started
- Install [Hammerspoon](http://www.hammerspoon.org/) first.
- `git clone https://github.com/einverne/dotfiles.git ~/dotfiles`
- `ln -s ~/dotfiles/hammerspoon ~/.hammerspoon`
- Reload the configutation.
## Keep update
`cd ~/dotfiles && git pull`
## How to use
Use Karabier-Elements to set <kbd>caps lock</kbd> as hyper key. Press caps lock is just like press Cmd+Control+Option+Shift at the same time.
## Reload config
- Hyper key + R, reload hammerspoon config
## Hyper key windows management
- Hyper key + H, set windows to left half of screen
- Hyper key + L, right half of screen
- Hyper key + J, bottom half
- Hyper key + K, top half
- Hyper key + F, full screen
### Windows management mode
Option+r Enter windows management:
- ASDW to move windows position
- HL/JK to set windows to left, right, up, down half of screen
- Y/O/U/I to set windows to LeftUp, RightUp, LeftDown, LeftDown corner
- Left/Right/Up/Down same as HL/JK
- F to set windows to full screen
- C to set windows to center
- Esc/Q to exit
- Tab to show help
### Switch windows
I personally use the application called Context to switch between different windows, however this config provider another way to quickly switch between windows. Try with `Option+Tab`.
## Toggle hammerspoon console
Option+z
## Move windows between monitors
- Hyper key + N, to move current window to next monitor
- Hpper key + P, to move current window to previous monitor
### Application launcher
Press option + a to enter application launcher. The shorcut information will show on the center of the screen. But I personally prefer Alfred.
Just press <kbd>opt</kbd>, plus <kbd>A</kbd> or <kbd>C</kbd> or <kbd>R</kbd>… to start. If need help, press <kbd>tab</kbd> to toggle the keybindings cheatsheet.
Press <kbd>opt</kbd> + <kbd>?</kbd> to toggle the help panel, which will show all <kbd>opt</kbd> related keybindings.
### Screenshots
These screenshots demostrate what awesome-hammerspoon is capable of. Learn more about [built-in Spoons](https://github.com/ashfinal/awesome-hammerspoon/wiki/The-built-in-Spoons).
#### Desktop widgets
![widgets](https://github.com/ashfinal/bindata/raw/master/screenshots/awesome-hammerspoon-deskwidgets.png)
#### Window manpulation <kbd></kbd> + <kbd>R</kbd>
![winresize](https://github.com/ashfinal/bindata/raw/master/screenshots/awesome-hammerspoon-winresize.gif)
#### aria2 Frontend <kbd></kbd> + <kbd>D</kbd>
![hsearch](https://github.com/ashfinal/bindata/raw/master/screenshots/awesome-hammerspoon-aria2.png)
You need to [run aria2 with RPC enabled](https://github.com/ashfinal/awesome-hammerspoon/wiki/Run-aria2-with-rpc-enabled) before using this. Config aria2 host and token in `~/.hammerspoon/private/config.lua`, then you're ready to go.
```lua
hsaria2_host = "http://localhost:6800/jsonrpc" -- default host
hsaria2_secret = "token" -- YOUR OWN SECRET
```
## Customization
```shell
cp ~/.hammerspoon/config-example.lua ~/.hammerspoon/private/config.lua
```
Then modify the file `~/.hammerspoon/private/config.lua`:
- Add/remove Spoons.
Define `hspoon_list` to decide which Spoons (a distributing format of Hammerspoon module) to be loaded. There are 15 built-in Spoons, learn about them at [here](https://github.com/ashfinal/awesome-hammerspoon/wiki/The-built-in-Spoons).
*There are more Spoons at [official spoon repository](http://www.hammerspoon.org/Spoons/) (you may need a little config before using them).*
- Customize keybindings
Please read `~/.hammerspoon/private/config.lua`for more details.
Finally press `cmd + ctrl + shift + r` to reload the configuration.
## Contribute
- Improve existing Spoons
A "Spoon" is just a directory, right-click on it -> "Show Package Contents".
Feel free to file issues or open PRs.
- Create new Spoons
Some resources you may find helpful:
[Learn Lua in Y minutes](http://learnxinyminutes.com/docs/lua/)
[Getting Started with Hammerspoon](http://www.hammerspoon.org/go/)
[Hammerspoon API Docs](http://www.hammerspoon.org/docs/index.html)
[hammerspoon/SPOONS.md at master · Hammerspoon/hammerspoon](https://github.com/Hammerspoon/hammerspoon/blob/master/SPOONS.md)
## Thanks to
[https://github.com/zzamboni/oh-my-hammerspoon](https://github.com/zzamboni/oh-my-hammerspoon)
[https://github.com/scottcs/dot_hammerspoon](https://github.com/scottcs/dot_hammerspoon)
[https://github.com/dharmapoudel/hammerspoon-config](https://github.com/dharmapoudel/hammerspoon-config)
[http://tracesof.net/uebersicht/](http://tracesof.net/uebersicht/)

79
hammerspoon/Spoons/AClock.spoon/docs.json

@ -0,0 +1,79 @@
[
{
"Constant" : [
],
"submodules" : [
],
"Function" : [
],
"Variable" : [
],
"stripped_doc" : [
],
"Deprecated" : [
],
"type" : "Module",
"desc" : "Just another clock, floating above all",
"Constructor" : [
],
"doc" : "Just another clock, floating above all\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/AClock.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/AClock.spoon.zip)",
"Field" : [
],
"items" : [
{
"doc" : "Show AClock, if already showing, just hide it.",
"stripped_doc" : [
"Show AClock, if already showing, just hide it."
],
"def" : "AClock:toggleShow()",
"parameters" : [
],
"notes" : [
],
"signature" : "AClock:toggleShow()",
"type" : "Method",
"returns" : [
],
"name" : "toggleShow",
"desc" : "Show AClock, if already showing, just hide it."
}
],
"Method" : [
{
"doc" : "Show AClock, if already showing, just hide it.",
"stripped_doc" : [
"Show AClock, if already showing, just hide it."
],
"def" : "AClock:toggleShow()",
"parameters" : [
],
"notes" : [
],
"signature" : "AClock:toggleShow()",
"type" : "Method",
"returns" : [
],
"name" : "toggleShow",
"desc" : "Show AClock, if already showing, just hide it."
}
],
"Command" : [
],
"name" : "AClock"
}
]

57
hammerspoon/Spoons/AClock.spoon/init.lua

@ -0,0 +1,57 @@
--- === AClock ===
---
--- Just another clock, floating above all
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/AClock.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/AClock.spoon.zip)
local obj={}
obj.__index = obj
-- Metadata
obj.name = "AClock"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
obj.homepage = "https://github.com/Hammerspoon/Spoons"
obj.license = "MIT - https://opensource.org/licenses/MIT"
function obj:init()
self.canvas = hs.canvas.new({x=0, y=0, w=0, h=0}):show()
self.canvas[1] = {
type = "text",
text = "",
textFont = "Impact",
textSize = 130,
textColor = {hex="#1891C3"},
textAlignment = "center",
}
end
--- AClock:toggleShow()
--- Method
--- Show AClock, if already showing, just hide it.
---
function obj:toggleShow()
if self.timer then
self.timer:stop()
self.timer = nil
self.canvas:hide()
else
local mainScreen = hs.screen.mainScreen()
local mainRes = mainScreen:fullFrame()
self.canvas:frame({
x = (mainRes.w-300)/2,
y = (mainRes.h-230)/2,
w = 300,
h = 230
})
self.canvas[1].text = os.date("%H:%M")
self.canvas:show()
self.timer = hs.timer.doAfter(4, function()
self.canvas:hide()
self.timer = nil
end)
end
end
return obj

41
hammerspoon/Spoons/BingDaily.spoon/docs.json

@ -0,0 +1,41 @@
[
{
"Constant" : [
],
"submodules" : [
],
"Function" : [
],
"Variable" : [
],
"stripped_doc" : [
],
"desc" : "Use Bing daily picture as your wallpaper, automatically.",
"type" : "Module",
"Deprecated" : [
],
"Constructor" : [
],
"doc" : "Use Bing daily picture as your wallpaper, automatically.\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/BingDaily.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/BingDaily.spoon.zip)",
"items" : [
],
"Command" : [
],
"Method" : [
],
"Field" : [
],
"name" : "BingDaily"
}
]

63
hammerspoon/Spoons/BingDaily.spoon/init.lua

@ -0,0 +1,63 @@
--- === BingDaily ===
---
--- Use Bing daily picture as your wallpaper, automatically.
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/BingDaily.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/BingDaily.spoon.zip)
local obj={}
obj.__index = obj
-- Metadata
obj.name = "BingDaily"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
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.full_url).lastPathComponent
local localpath = os.getenv("HOME") .. "/.Trash/" .. hs.http.urlParts(obj.full_url).lastPathComponent
hs.screen.mainScreen():desktopImageURL("file://" .. localpath)
else
print(stdOut, stdErr)
end
end
local function bingRequest()
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"
local json_req_url = "http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1"
hs.http.asyncGet(json_req_url, {["User-Agent"]=user_agent_str}, function(stat,body,header)
if stat == 200 then
if pcall(function() hs.json.decode(body) end) then
local decode_data = hs.json.decode(body)
local pic_url = decode_data.images[1].url
local pic_name = hs.http.urlParts(pic_url).lastPathComponent
if obj.last_pic ~= pic_name then
obj.full_url = "https://www.bing.com" .. pic_url
if obj.task then
obj.task:terminate()
obj.task = nil
end
local localpath = os.getenv("HOME") .. "/.Trash/" .. hs.http.urlParts(obj.full_url).lastPathComponent
obj.task = hs.task.new("/usr/bin/curl", curl_callback, {"-A", user_agent_str, obj.full_url, "-o", localpath})
obj.task:start()
end
end
else
print("Bing URL request failed!")
end
end)
end
function obj:init()
if obj.timer == nil then
obj.timer = hs.timer.doEvery(3*60*60, function() bingRequest() end)
obj.timer:setNextTrigger(5)
else
obj.timer:start()
end
end
return obj

41
hammerspoon/Spoons/Calendar.spoon/docs.json

@ -0,0 +1,41 @@
[
{
"Constant" : [
],
"submodules" : [
],
"Function" : [
],
"Variable" : [
],
"stripped_doc" : [
],
"Deprecated" : [
],
"type" : "Module",
"desc" : "A calendar inset into the desktop",
"Constructor" : [
],
"doc" : "A calendar inset into the desktop\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/Calendar.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/Calendar.spoon.zip)",
"Field" : [
],
"Command" : [
],
"Method" : [
],
"items" : [
],
"name" : "Calendar"
}
]

183
hammerspoon/Spoons/Calendar.spoon/init.lua

@ -0,0 +1,183 @@
--- === Calendar ===
---
--- A calendar inset into the desktop
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/Calendar.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/Calendar.spoon.zip)
local obj={}
obj.__index = obj
-- Metadata
obj.name = "Calendar"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
obj.homepage = "https://github.com/Hammerspoon/Spoons"
obj.license = "MIT - https://opensource.org/licenses/MIT"
obj.calw = 260
obj.calh = 184
local function updateCalCanvas()
local titlestr = os.date("%B %Y")
obj.canvas[2].text = titlestr
local current_year = os.date("%Y")
local current_month = os.date("%m")
local current_day = os.date("%d")
local firstday_of_nextmonth = os.time{year=current_year, month=current_month+1, day=1}
local maxday_of_currentmonth = os.date("*t", firstday_of_nextmonth-24*60*60).day
local weekday_of_firstday = os.date("*t", os.time{year=current_year, month=current_month, day=1}).wday
local needed_rownum = math.ceil((weekday_of_firstday+maxday_of_currentmonth-1)/7)
for i=1,needed_rownum do
for k=1,7 do
local caltable_idx = 7*(i-1)+k
local pushbacked_value = caltable_idx-weekday_of_firstday + 2
if pushbacked_value <= 0 or pushbacked_value > maxday_of_currentmonth then
obj.canvas[9+caltable_idx].text = ""
else
obj.canvas[9+caltable_idx].text = pushbacked_value
end
if pushbacked_value == math.tointeger(current_day) then
obj.canvas[58].frame.x = tostring((10+(obj.calw-20)/8*k)/obj.calw)
obj.canvas[58].frame.y = tostring((10+(obj.calh-20)/8*(i+1))/obj.calh)
end
end
end
-- update yearweek
local yearweek_of_firstday = hs.execute("date -v1d +'%W'")
for i=1,6 do
local yearweek_rowvalue = math.tointeger(yearweek_of_firstday)+i-1
obj.canvas[51+i].text = yearweek_rowvalue
if i > needed_rownum then
obj.canvas[51+i].text = ""
end
end
-- trim the canvas
obj.canvas:size({
w = obj.calw,
h = 20+(obj.calh-20)/8*(needed_rownum+2)
})
end
function obj:init()
local caltodaycolor = {red=1, blue=1, green=1, alpha=0.3}
local calcolor = {red=235/255, blue=235/255, green=235/255}
local calbgcolor = {red=0, blue=0, green=0, alpha=0.3}
local weeknumcolor = {red=246/255, blue=246/255, green=246/255, alpha=0.5}
local cscreen = hs.screen.mainScreen()
local cres = cscreen:fullFrame()
obj.canvas = hs.canvas.new({
x = cres.w-obj.calw-20,
y = cres.h-obj.calh-20,
w = obj.calw,
h = obj.calh
}):show()
obj.canvas:behavior(hs.canvas.windowBehaviors.canJoinAllSpaces)
obj.canvas:level(hs.canvas.windowLevels.desktopIcon)
obj.canvas[1] = {
id = "cal_bg",
type = "rectangle",
action = "fill",
fillColor = calbgcolor,
roundedRectRadii = {xRadius = 10, yRadius = 10},
}
obj.canvas[2] = {
id = "cal_title",
type = "text",
text = "",
textFont = "Courier",
textSize = 16,
textColor = calcolor,
textAlignment = "center",
frame = {
x = tostring(10/obj.calw),
y = tostring(10/obj.calw),
w = tostring(1-20/obj.calw),
h = tostring((obj.calh-20)/8/obj.calh)
}
}
local weeknames = {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
for i=1,#weeknames do
obj.canvas[2+i] = {
id = "cal_weekday",
type = "text",
text = weeknames[i],
textFont = "Courier",
textSize = 16,
textColor = calcolor,
textAlignment = "center",
frame = {
x = tostring((10+(obj.calw-20)/8*i)/obj.calw),
y = tostring((10+(obj.calh-20)/8)/obj.calh),
w = tostring((obj.calw-20)/8/obj.calw),
h = tostring((obj.calh-20)/8/obj.calh)
}
}
end
-- Create 7x6 calendar table
for i=1,6 do
for k=1,7 do
obj.canvas[9+7*(i-1)+k] = {
type = "text",
text = "",
textFont = "Courier",
textSize = 16,
textColor = calcolor,
textAlignment = "center",
frame = {
x = tostring((10+(obj.calw-20)/8*k)/obj.calw),
y = tostring((10+(obj.calh-20)/8*(i+1))/obj.calh),
w = tostring((obj.calw-20)/8/obj.calw),
h = tostring((obj.calh-20)/8/obj.calh)
}
}
end
end
-- Create yearweek column
for i=1,6 do
obj.canvas[51+i] = {
type = "text",
text = "",
textFont = "Courier",
textSize = 16,
textColor = weeknumcolor,
textAlignment = "center",
frame = {
x = tostring(10/obj.calw),
y = tostring((10+(obj.calh-20)/8*(i+1))/obj.calh),
w = tostring((obj.calw-20)/8/obj.calw),
h = tostring((obj.calh-20)/8/obj.calh)
}
}
end
-- today cover rectangle
obj.canvas[58] = {
type = "rectangle",
action = "fill",
fillColor = caltodaycolor,
roundedRectRadii = {xRadius = 3, yRadius = 3},
frame = {
x = tostring((10+(obj.calw-20)/8)/obj.calw),
y = tostring((10+(obj.calh-20)/8*2)/obj.calh),
w = tostring((obj.calw-20)/8/obj.calw),
h = tostring((obj.calh-20)/8/obj.calh)
}
}
if obj.timer == nil then
obj.timer = hs.timer.doEvery(1800, function() updateCalCanvas() end)
obj.timer:setNextTrigger(0)
else
obj.timer:start()
end
end
return obj

41
hammerspoon/Spoons/CircleClock.spoon/docs.json

@ -0,0 +1,41 @@
[
{
"Constant" : [
],
"submodules" : [
],
"Function" : [
],
"Variable" : [
],
"stripped_doc" : [
],
"type" : "Module",
"Deprecated" : [
],
"desc" : "A circleclock inset into the desktop",
"Constructor" : [
],
"Field" : [
],
"Method" : [
],
"Command" : [
],
"items" : [
],
"doc" : "A circleclock inset into the desktop\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/CircleClock.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/CircleClock.spoon.zip)",
"name" : "CircleClock"
}
]

116
hammerspoon/Spoons/CircleClock.spoon/init.lua

@ -0,0 +1,116 @@
--- === CircleClock ===
---
--- A circleclock inset into the desktop
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/CircleClock.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/CircleClock.spoon.zip)
local obj={}
obj.__index = obj
-- Metadata
obj.name = "CircleClock"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
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 updateClock()
local secnum = math.tointeger(os.date("%S"))
local minnum = math.tointeger(os.date("%M"))
local hournum = math.tointeger(os.date("%I"))
local secangle = 6*secnum
local minangle = 6*minnum+6/60*secnum
local hourangle = 30*hournum+30/60*minnum+30/60/60*secnum
obj.canvas[3].endAngle = secangle
obj.canvas[7].endAngle = minangle
-- hourangle may be larger than 360 at 12pm-1pm
if hourangle >= 360 then
hourangle = hourangle-360
end
obj.canvas[5].endAngle = hourangle
end
function obj:init()
local cscreen = hs.screen.mainScreen()
local cres = cscreen:fullFrame()
self.canvas = hs.canvas.new({
x = cres.w-300-20,
y = 100,
w = 200,
h = 200
}):show()
obj.canvas:behavior(hs.canvas.windowBehaviors.canJoinAllSpaces)
obj.canvas:level(hs.canvas.windowLevels.desktopIcon)
obj.canvas[1] = {
id = "watch_image",
type = "image",
image = hs.image.imageFromPath(self.spoonPath .. "/watchbg.png"),
}
obj.canvas[2] = {
id = "watch_circle",
type = "circle",
radius = "40%",
action = "stroke",
strokeColor = {hex="#9E9E9E", alpha=0.3},
}
obj.canvas[3] = {
id = "watch_sechand",
type = "arc",
radius = "40%",
fillColor = {hex="#9E9E9E", alpha=0.1},
strokeColor = {hex="#9E9E9E", alpha=0.3},
endAngle = 0,
}
obj.canvas[4] = {
id = "watch_hourcircle",
type = "circle",
action = "stroke",
radius = "20%",
strokeWidth = 3,
strokeColor = {hex="#FFFFFF", alpha=0.1},
}
obj.canvas[5] = {
id = "watch_hourarc",
type = "arc",
action = "stroke",
radius = "20%",
arcRadii = false,
strokeWidth = 3,
strokeColor = {hex="#EC6D27", alpha=0.75},
endAngle = 0,
}
obj.canvas[6] = {
id = "watch_mincircle",
type = "circle",
action = "stroke",
radius = "27%",
strokeWidth = 3,
strokeColor = {hex="#FFFFFF", alpha=0.1},
}
obj.canvas[7] = {
id = "watch_minarc",
type = "arc",
action = "stroke",
radius = "27%",
arcRadii = false,
strokeWidth = 3,
strokeColor = {hex="#1891C3", alpha=0.75},
endAngle = 0,
}
if obj.timer == nil then
obj.timer = hs.timer.doEvery(1, function() updateClock() end)
else
obj.timer:start()
end
end
return obj

BIN
hammerspoon/Spoons/CircleClock.spoon/watchbg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

283
hammerspoon/Spoons/ClipShow.spoon/docs.json

@ -0,0 +1,283 @@
[
{
"Constant" : [
],
"submodules" : [
],
"Function" : [
],
"Variable" : [
],
"stripped_doc" : [
],
"Deprecated" : [
],
"desc" : "Show the content of system clipboard",
"type" : "Module",
"Constructor" : [
],
"doc" : "Show the content of system clipboard\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/ClipShow.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/ClipShow.spoon.zip)",
"Method" : [
{
"desc" : "Process the content of system clipboard and show\/hide the canvas",
"def" : "ClipShow:toggleShow()",
"stripped_doc" : [
"Process the content of system clipboard and show\/hide the canvas"
],
"doc" : "Process the content of system clipboard and show\/hide the canvas",
"notes" : [
],
"signature" : "ClipShow:toggleShow()",
"type" : "Method",
"returns" : [
],
"name" : "toggleShow",
"parameters" : [
]
},
{
"desc" : "Open content of the clipboard in default browser with specific refstr.",
"def" : "ClipShow:openInBrowserWithRef(refstr)",
"stripped_doc" : [
"Open content of the clipboard in default browser with specific refstr.",
""
],
"doc" : "Open content of the clipboard in default browser with specific refstr.\n\nParameters:\n * refstr - A optional string specifying which refstr to use. If nil, then open this content in browser directly. The \"refstr\" could be something like this: `https:\/\/www.bing.com\/search?q=`.",
"notes" : [
],
"signature" : "ClipShow:openInBrowserWithRef(refstr)",
"type" : "Method",
"returns" : [
],
"name" : "openInBrowserWithRef",
"parameters" : [
" * refstr - A optional string specifying which refstr to use. If nil, then open this content in browser directly. The \"refstr\" could be something like this: `https:\/\/www.bing.com\/search?q=`."
]
},
{
"desc" : "Save clipboard session so we can restore it later",
"def" : "ClipShow:saveToSession()",
"stripped_doc" : [
"Save clipboard session so we can restore it later"
],
"doc" : "Save clipboard session so we can restore it later",
"notes" : [
],
"signature" : "ClipShow:saveToSession()",
"type" : "Method",
"returns" : [
],
"name" : "saveToSession",
"parameters" : [
]
},
{
"desc" : "Restore the lastsession of system clipboard",
"def" : "ClipShow:restoreLastSession()",
"stripped_doc" : [
"Restore the lastsession of system clipboard"
],
"doc" : "Restore the lastsession of system clipboard",
"notes" : [
],
"signature" : "ClipShow:restoreLastSession()",
"type" : "Method",
"returns" : [
],
"name" : "restoreLastSession",
"parameters" : [
]
},
{
"desc" : "Save content of current canvas to a file, the default location is `~\/Desktop\/`.",
"def" : "ClipShow:saveToFile()",
"stripped_doc" : [
"Save content of current canvas to a file, the default location is `~\/Desktop\/`."
],
"doc" : "Save content of current canvas to a file, the default location is `~\/Desktop\/`.",
"notes" : [
],
"signature" : "ClipShow:saveToFile()",
"type" : "Method",
"returns" : [
],
"name" : "saveToFile",
"parameters" : [
]
},
{
"desc" : "Open local file with specific command.",
"def" : "ClipShow:openWithCommand(command)",
"stripped_doc" : [
"Open local file with specific command.",
""
],
"doc" : "Open local file with specific command.\n\nParameters:\n * command - A string specifying which command to use. The \"command\" is something like this: `\/usr\/local\/bin\/mvim`.",
"notes" : [
],
"signature" : "ClipShow:openWithCommand(command)",
"type" : "Method",
"returns" : [
],
"name" : "openWithCommand",
"parameters" : [
" * command - A string specifying which command to use. The \"command\" is something like this: `\/usr\/local\/bin\/mvim`."
]
}
],
"Command" : [
],
"Field" : [
],
"items" : [
{
"desc" : "Open content of the clipboard in default browser with specific refstr.",
"def" : "ClipShow:openInBrowserWithRef(refstr)",
"stripped_doc" : [
"Open content of the clipboard in default browser with specific refstr.",
""
],
"doc" : "Open content of the clipboard in default browser with specific refstr.\n\nParameters:\n * refstr - A optional string specifying which refstr to use. If nil, then open this content in browser directly. The \"refstr\" could be something like this: `https:\/\/www.bing.com\/search?q=`.",
"notes" : [
],
"signature" : "ClipShow:openInBrowserWithRef(refstr)",
"type" : "Method",
"returns" : [
],
"name" : "openInBrowserWithRef",
"parameters" : [
" * refstr - A optional string specifying which refstr to use. If nil, then open this content in browser directly. The \"refstr\" could be something like this: `https:\/\/www.bing.com\/search?q=`."
]
},
{
"desc" : "Open local file with specific command.",
"def" : "ClipShow:openWithCommand(command)",
"stripped_doc" : [
"Open local file with specific command.",
""
],
"doc" : "Open local file with specific command.\n\nParameters:\n * command - A string specifying which command to use. The \"command\" is something like this: `\/usr\/local\/bin\/mvim`.",
"notes" : [
],
"signature" : "ClipShow:openWithCommand(command)",
"type" : "Method",
"returns" : [
],
"name" : "openWithCommand",
"parameters" : [
" * command - A string specifying which command to use. The \"command\" is something like this: `\/usr\/local\/bin\/mvim`."
]
},
{
"desc" : "Restore the lastsession of system clipboard",
"def" : "ClipShow:restoreLastSession()",
"stripped_doc" : [
"Restore the lastsession of system clipboard"
],
"doc" : "Restore the lastsession of system clipboard",
"notes" : [
],
"signature" : "ClipShow:restoreLastSession()",
"type" : "Method",
"returns" : [
],
"name" : "restoreLastSession",
"parameters" : [
]
},
{
"desc" : "Save content of current canvas to a file, the default location is `~\/Desktop\/`.",
"def" : "ClipShow:saveToFile()",
"stripped_doc" : [
"Save content of current canvas to a file, the default location is `~\/Desktop\/`."
],
"doc" : "Save content of current canvas to a file, the default location is `~\/Desktop\/`.",
"notes" : [
],
"signature" : "ClipShow:saveToFile()",
"type" : "Method",
"returns" : [
],
"name" : "saveToFile",
"parameters" : [
]
},
{
"desc" : "Save clipboard session so we can restore it later",
"def" : "ClipShow:saveToSession()",
"stripped_doc" : [
"Save clipboard session so we can restore it later"
],
"doc" : "Save clipboard session so we can restore it later",
"notes" : [
],
"signature" : "ClipShow:saveToSession()",
"type" : "Method",
"returns" : [
],
"name" : "saveToSession",
"parameters" : [
]
},
{
"desc" : "Process the content of system clipboard and show\/hide the canvas",
"def" : "ClipShow:toggleShow()",
"stripped_doc" : [
"Process the content of system clipboard and show\/hide the canvas"
],
"doc" : "Process the content of system clipboard and show\/hide the canvas",
"notes" : [
],
"signature" : "ClipShow:toggleShow()",
"type" : "Method",
"returns" : [
],
"name" : "toggleShow",
"parameters" : [
]
}
],
"name" : "ClipShow"
}
]

394
hammerspoon/Spoons/ClipShow.spoon/init.lua

@ -0,0 +1,394 @@
--- === ClipShow ===
---
--- Show the content of system clipboard
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ClipShow.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ClipShow.spoon.zip)
local obj={}
obj.__index = obj
-- Metadata
obj.name = "ClipShow"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
obj.homepage = "https://github.com/Hammerspoon/Spoons"
obj.license = "MIT - https://opensource.org/licenses/MIT"
obj.canvas = nil
obj.ccount = nil
obj.lastsession = nil
function obj:init()
obj.canvas = hs.canvas.new({x=0, y=0, w=0, h=0})
obj.canvas[1] = {
type = "rectangle",
action = "fill",
fillColor = {hex="#000000", alpha=0.75}
}
obj.canvas[2] = {
type = "segments",
strokeColor = {hex = "#FFFFFF", alpha = 0.1},
coordinates = {
{x="1%", y="72%"},
{x="72%", y="72%"}
}
}
obj.canvas[3] = {
type = "segments",
strokeColor = {hex = "#FFFFFF", alpha = 0.1},
coordinates = {
{x="72%", y="1%"},
{x="72%", y="99%"}
}
}
obj.canvas[4] = {type = "text", text = ""}
obj.canvas[5] = {type = "text"}
obj.canvas:level(hs.canvas.windowLevels.tornOffMenu)
end
local function isFileKinds(val, tbl)
for _, v in ipairs(tbl) do
if val == v then
return true
end
end
return false
end
-- Resize the ClipShow canvas
function obj:adjustCanvas()
local cscreen = hs.screen.mainScreen()
local cres = cscreen:fullFrame()
obj.canvas: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
})
end
-- Fill clipshowM's keybindings in sidebar
function obj:fillModalKeys()
if #obj.canvas < 6 then
local modal = spoon.ModalMgr.modal_list["clipshowM"]
if modal then
local keys_pool = {}
for _, v in ipairs(modal.keys) do
table.insert(keys_pool, v.msg)
end
for idx, val in ipairs(keys_pool) do
obj.canvas[idx + 5] = {
type = "text",
text = val,
textFont = "Courier-Bold",
textSize = 16,
textColor = {hex = "#2390FF", alpha = 1},
frame = {
x = "74%",
y = tostring(idx * 30 / (obj.canvas:frame().h - 60)),
w = "24%",
h = tostring(30 / (obj.canvas:frame().h - 60))
}
}
end
end
end
end
--- ClipShow:toggleShow()
--- Method
--- Process the content of system clipboard and show/hide the canvas
---
function obj:processClipboard()
local clip_type = hs.pasteboard.typesAvailable()
if clip_type.image then
if clip_type.URL then
local urltbl = hs.pasteboard.readURL()
if urltbl.filePath then
-- local file
local fex = urltbl.filePath:match(".*%.(%w+)$") or ""
local image_ex = {"jpeg", "jpg", "gif", "png", "bmp", "tiff", "icns"}
local text_ex = {"", "txt", "md", "markdown", "mkd", "rst", "org", "sh", "zsh", "json", "yml", "mk", "config", "conf", "pub", "gitignore"}
if isFileKinds(fex:lower(), image_ex) then
local imagedata = hs.image.imageFromPath(urltbl.filePath)
obj.canvas[4] = {
type = "image",
image = imagedata,
frame = {
x = "1%",
y = "1%",
w = "70%",
h = "70%"
}
}
elseif isFileKinds(fex:lower(), text_ex) then
-- text file format
local file_handler = io.open(urltbl.filePath)
if file_handler then
local file_content = file_handler:read(1000)
if file_content then
obj.canvas[4] = {
type = "text",
text = file_content,
textSize = 20,
frame = {
x = "1%",
y = "1%",
w = "70%",
h = "70%"
}
}
file_handler:close()
else
-- Maybe directory
local dir_name = urltbl.filePath
obj.canvas[4] = {
type = "text",
text = dir_name,
frame = {
x = "1%",
y = "1%",
w = "70%",
h = "70%"
}
}
end
else
print("-- ClipShow: No access to this file!")
end
else
local stringdata = table.concat(hs.pasteboard.readString(nil, true))
obj.canvas[4] = {
type = "text",
text = stringdata,
frame = {
x = "1%",
y = "1%",
w = "70%",
h = "70%"
}
}
end
else
-- Remote image
local imagedata = hs.pasteboard.readImage()
obj.canvas[4] = {
type = "image",
image = imagedata,
frame = {
x = "1%",
y = "1%",
w = "70%",
h = "70%"
}
}
end
else
-- Image fragement
local imagedata = hs.pasteboard.readImage()
obj.canvas[4] = {
type = "image",
image = imagedata,
frame = {
x = "1%",
y = "1%",
w = "70%",
h = "70%"
}
}
end
obj:adjustCanvas()
obj:fillModalKeys()
obj.canvas:show()
elseif clip_type.URL then
local urltbl = hs.pasteboard.readURL(nil, true)
if urltbl then
if #urltbl > 1 then
local stringdata = table.concat(hs.pasteboard.readString(nil, true))
obj.canvas[4] = {
type = "text",
text = stringdata,
frame = {
x = "1%",
y = "1%",
w = "70%",
h = "70%"
}
}
obj:adjustCanvas()
obj:fillModalKeys()
obj.canvas:show()
else
local browser = hs.urlevent.getDefaultHandler("http")
hs.urlevent.openURLWithBundle(urltbl[1].url, browser)
end
else
local browser = hs.urlevent.getDefaultHandler("http")
hs.urlevent.openURLWithBundle(urltbl[1].url, browser)
end
elseif clip_type.string then
local stringdata = table.concat(hs.pasteboard.readString(nil, true))
obj.canvas[4] = {
type = "text",
text = stringdata,
frame = {
x = "1%",
y = "1%",
w = "70%",
h = "70%"
}
}
obj:adjustCanvas()
obj:fillModalKeys()
obj.canvas:show()
end
end
function obj:toggleShow()
if obj.canvas:isShowing() then
obj.canvas:hide()
else
local change_count = hs.pasteboard.changeCount()
-- Only if content of the clipboard changed then we redraw the canvas
if change_count ~= obj.ccount then
obj:processClipboard()
obj.ccount = change_count
else
obj:adjustCanvas()
obj.canvas:show()
end
end
end
--- ClipShow:openInBrowserWithRef(refstr)
--- Method
--- Open content of the clipboard in default browser with specific refstr.
---
--- Parameters:
--- * refstr - A optional string specifying which refstr to use. If nil, then open this content in browser directly. The "refstr" could be something like this: `https://www.bing.com/search?q=`.
local function acquireText()
if obj.canvas:isShowing() then
if obj.canvas[4].type == "text" then
return obj.canvas[4].text
else
return hs.pasteboard.readString() or ""
end
else
return hs.pasteboard.readString() or ""
end
end
function obj:openInBrowserWithRef(refstr)
local querystr = acquireText()
if refstr then
local encoded_query = hs.http.encodeForQuery(refstr .. querystr)
local defaultbrowser = hs.urlevent.getDefaultHandler("http")
hs.urlevent.openURLWithBundle(encoded_query, defaultbrowser)
else
local defaultbrowser = hs.urlevent.getDefaultHandler("http")
hs.urlevent.openURLWithBundle(querystr, defaultbrowser)
end
end
--- ClipShow:saveToSession()
--- Method
--- Save clipboard session so we can restore it later
---
function obj:saveToSession()
obj.lastsession = hs.pasteboard.readAllData()
if obj.canvas:isShowing() then
if obj.canvas[4].type == "text" then
local cdraw = obj.canvas[4].text
hs.pasteboard.writeObjects(cdraw)
obj.canvas[5] = {
type = "text",
text = cdraw,
textSize = 16,
textAlignment = "center",
frame = {
x = "1%",
y = "73%",
w = "70%",
h = "26%"
}
}
elseif obj.canvas[4].type == "image" then
local cdraw = obj.canvas[4].image
hs.pasteboard.writeObjects(cdraw)
obj.canvas[5] = {
type = "image",
image = cdraw,
frame = {
x = "1%",
y = "73%",
w = "70%",
h = "26%"
}
}
end
end
end
--- ClipShow:restoreLastSession()
--- Method
--- Restore the lastsession of system clipboard
---
function obj:restoreLastSession()
if obj.lastsession then
hs.pasteboard.writeAllData(obj.lastsession)
obj:processClipboard()
obj.ccount = change_count
end
end
--- ClipShow:saveToFile()
--- Method
--- Save content of current canvas to a file, the default location is `~/Desktop/`.
---
function obj:saveToFile()
if obj.canvas:isShowing() then
if obj.canvas[4].type == "image" then
local cdraw = obj.canvas[4].image
local file_name = os.date("%Y-%m-%d %H:%M:%S")
cdraw:saveToFile("~/Desktop/" .. file_name .. ".png", true)
elseif obj.canvas[4].type == "text" then
local ctext = obj.canvas[4].text
local file_name = os.date("%Y-%m-%d %H:%M:%S")
local full_path = os.getenv("HOME") .. "/Desktop/" .. file_name .. ".txt"
local file_handler = io.open(full_path, "w")
file_handler:write(ctext)
file_handler:close()
end
end
end
--- ClipShow:openWithCommand(command)
--- Method
--- Open local file with specific command.
---
--- Parameters:
--- * command - A string specifying which command to use. The "command" is something like this: `/usr/local/bin/mvim`.
function obj:openWithCommand(command)
if obj.canvas:isShowing() then
if obj.canvas[4].type == "text" then
local urltbl = hs.pasteboard.readURL()
if urltbl then
if urltbl.filePath then
os.execute(command .. " " .. urltbl.filePath)
else
os.execute(command)
end
else
os.execute(command)
end
end
end
end
return obj

163
hammerspoon/Spoons/CountDown.spoon/docs.json

@ -0,0 +1,163 @@
[
{
"Constant" : [
],
"submodules" : [
],
"Function" : [
],
"Variable" : [
],
"stripped_doc" : [
],
"Deprecated" : [
],
"type" : "Module",
"desc" : "Tiny countdown with visual indicator",
"Constructor" : [
],
"Field" : [
],
"Method" : [
{
"parameters" : [
" * minutes - How many minutes"
],
"stripped_doc" : [
"Start a countdown for `minutes` minutes immediately. Calling this method again will kill the existing countdown instance.",
""
],
"desc" : "Start a countdown for `minutes` minutes immediately. Calling this method again will kill the existing countdown instance.",
"doc" : "Start a countdown for `minutes` minutes immediately. Calling this method again will kill the existing countdown instance.\n\nParameters:\n * minutes - How many minutes",
"notes" : [
],
"signature" : "CountDown:startFor(minutes)",
"type" : "Method",
"returns" : [
],
"name" : "startFor",
"def" : "CountDown:startFor(minutes)"
},
{
"parameters" : [
],
"stripped_doc" : [
"Pause or resume the existing countdown."
],
"desc" : "Pause or resume the existing countdown.",
"doc" : "Pause or resume the existing countdown.",
"notes" : [
],
"signature" : "CountDown:pauseOrResume()",
"type" : "Method",
"returns" : [
],
"name" : "pauseOrResume",
"def" : "CountDown:pauseOrResume()"
},
{
"parameters" : [
" * progress - an number specifying the value of progress (0.0 - 1.0)"
],
"stripped_doc" : [
"Set the progress of visual indicator to `progress`.",
""
],
"desc" : "Set the progress of visual indicator to `progress`.",
"doc" : "Set the progress of visual indicator to `progress`.\n\nParameters:\n * progress - an number specifying the value of progress (0.0 - 1.0)",
"notes" : [
],
"signature" : "CountDown:setProgress(progress)",
"type" : "Method",
"returns" : [
],
"name" : "setProgress",
"def" : "CountDown:setProgress(progress)"
}
],
"Command" : [
],
"items" : [
{
"parameters" : [
],
"stripped_doc" : [
"Pause or resume the existing countdown."
],
"desc" : "Pause or resume the existing countdown.",
"doc" : "Pause or resume the existing countdown.",
"notes" : [
],
"signature" : "CountDown:pauseOrResume()",
"type" : "Method",
"returns" : [
],
"name" : "pauseOrResume",
"def" : "CountDown:pauseOrResume()"
},
{
"parameters" : [
" * progress - an number specifying the value of progress (0.0 - 1.0)"
],
"stripped_doc" : [
"Set the progress of visual indicator to `progress`.",
""
],
"desc" : "Set the progress of visual indicator to `progress`.",
"doc" : "Set the progress of visual indicator to `progress`.\n\nParameters:\n * progress - an number specifying the value of progress (0.0 - 1.0)",
"notes" : [
],
"signature" : "CountDown:setProgress(progress)",
"type" : "Method",
"returns" : [
],
"name" : "setProgress",
"def" : "CountDown:setProgress(progress)"
},
{
"parameters" : [
" * minutes - How many minutes"
],
"stripped_doc" : [
"Start a countdown for `minutes` minutes immediately. Calling this method again will kill the existing countdown instance.",
""
],
"desc" : "Start a countdown for `minutes` minutes immediately. Calling this method again will kill the existing countdown instance.",
"doc" : "Start a countdown for `minutes` minutes immediately. Calling this method again will kill the existing countdown instance.\n\nParameters:\n * minutes - How many minutes",
"notes" : [
],
"signature" : "CountDown:startFor(minutes)",
"type" : "Method",
"returns" : [
],
"name" : "startFor",
"def" : "CountDown:startFor(minutes)"
}
],
"doc" : "Tiny countdown with visual indicator\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/CountDown.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/CountDown.spoon.zip)",
"name" : "CountDown"
}
]

129
hammerspoon/Spoons/CountDown.spoon/init.lua

@ -0,0 +1,129 @@
--- === CountDown ===
---
--- Tiny countdown with visual indicator
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/CountDown.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/CountDown.spoon.zip)
local obj = {}
obj.__index = obj
-- Metadata
obj.name = "CountDown"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
obj.homepage = "https://github.com/Hammerspoon/Spoons"
obj.license = "MIT - https://opensource.org/licenses/MIT"
obj.canvas = nil
obj.timer = nil
function obj:init()
self.canvas = hs.canvas.new({x=0, y=0, w=0, h=0}):show()
self.canvas:behavior(hs.canvas.windowBehaviors.canJoinAllSpaces)
self.canvas:level(hs.canvas.windowLevels.status)
self.canvas:alpha(0.35)
self.canvas[1] = {
type = "rectangle",
action = "fill",
fillColor = hs.drawing.color.osx_red,
frame = {x="0%", y="0%", w="0%", h="100%"}
}
self.canvas[2] = {
type = "rectangle",
action = "fill",
fillColor = hs.drawing.color.osx_green,
frame = {x="0%", y="0%", w="0%", h="100%"}
}
end
--- CountDown:startFor(minutes)
--- Method
--- Start a countdown for `minutes` minutes immediately. Calling this method again will kill the existing countdown instance.
---
--- Parameters:
--- * minutes - How many minutes
local function canvasCleanup()
if obj.timer then
obj.timer:stop()
obj.timer = nil
end
obj.canvas[1].frame.w = "0%"
obj.canvas[2].frame.x = "0%"
obj.canvas[2].frame.w = "0%"
obj.canvas:frame({x=0, y=0, w=0, h=0})
end
function obj:startFor(minutes)
if obj.timer then
canvasCleanup()
else
local mainScreen = hs.screen.mainScreen()
local mainRes = mainScreen:fullFrame()
obj.canvas:frame({x=mainRes.x, y=mainRes.y+mainRes.h-5, w=mainRes.w, h=5})
-- Set minimum visual step to 2px (i.e. Make sure every trigger updates 2px on screen at least.)
local minimumStep = 2
local secCount = math.ceil(60*minutes)
obj.loopCount = 0
if mainRes.w/secCount >= 2 then
obj.timer = hs.timer.doEvery(1, function()
obj.loopCount = obj.loopCount+1/secCount
obj:setProgress(obj.loopCount, minutes)
end)
else
local interval = 2/(mainRes.w/secCount)
obj.timer = hs.timer.doEvery(interval, function()
obj.loopCount = obj.loopCount+1/mainRes.w*2
obj:setProgress(obj.loopCount, minutes)
end)
end
end
return self
end
--- CountDown:pauseOrResume()
--- Method
--- Pause or resume the existing countdown.
---
function obj:pauseOrResume()
if obj.timer then
if obj.timer:running() then
obj.timer:stop()
else
obj.timer:start()
end
end
end
--- CountDown:setProgress(progress)
--- Method
--- Set the progress of visual indicator to `progress`.
---
--- Parameters:
--- * progress - an number specifying the value of progress (0.0 - 1.0)
function obj:setProgress(progress, notifystr)
if obj.canvas:frame().h == 0 then
-- Make the canvas actully visible
local mainScreen = hs.screen.mainScreen()
local mainRes = mainScreen:fullFrame()
obj.canvas:frame({x=mainRes.x, y=mainRes.y+mainRes.h-5, w=mainRes.w, h=5})
end
if progress >= 1 then
canvasCleanup()
if notifystr then
hs.notify.new({
title = "Time(" .. notifystr .. " mins) is up!",
informativeText = "Now is " .. os.date("%X")
}):send()
end
else
obj.canvas[1].frame.w = tostring(progress)
obj.canvas[2].frame.x = tostring(progress)
obj.canvas[2].frame.w = tostring(1-progress)
end
end
return obj

41
hammerspoon/Spoons/HCalendar.spoon/docs.json

@ -0,0 +1,41 @@
[
{
"Constant" : [
],
"submodules" : [
],
"Function" : [
],
"Variable" : [
],
"stripped_doc" : [
],
"Deprecated" : [
],
"type" : "Module",
"desc" : "A horizonal calendar inset into the desktop",
"Constructor" : [
],
"items" : [
],
"Field" : [
],
"Command" : [
],
"Method" : [
],
"doc" : "A horizonal calendar inset into the desktop\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/HCalendar.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/HCalendar.spoon.zip)",
"name" : "HCalendar"
}
]

179
hammerspoon/Spoons/HCalendar.spoon/init.lua

@ -0,0 +1,179 @@
--- === HCalendar ===
---
--- A horizonal calendar inset into the desktop
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/HCalendar.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/HCalendar.spoon.zip)
local obj={}
obj.__index = obj
-- Metadata
obj.name = "HCalendar"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
obj.homepage = "https://github.com/Hammerspoon/Spoons"
obj.license = "MIT - https://opensource.org/licenses/MIT"
obj.hcalw = 31*24+20
obj.hcalh = 100
local function updateHcalCanvas()
local titlestr = os.date("%B %Y")
obj.canvas[3].text = titlestr
local currentyear = os.date("%Y")
local currentmonth = os.date("%m")
local currentday = os.date("%d")
local weeknames = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
local firstdayofnextmonth = os.time{year=currentyear, month=currentmonth+1, day=1}
local lastdayofcurrentmonth = os.date("*t", firstdayofnextmonth-24*60*60).day
for i=1,lastdayofcurrentmonth do
local weekdayofqueriedday = os.date("*t", os.time{year=currentyear, month=currentmonth, day=i}).wday
local mappedweekdaystr = weeknames[weekdayofqueriedday]
obj.canvas[3+i].text = mappedweekdaystr
obj.canvas[65+i].text = i
if mappedweekdaystr == "Sa" or mappedweekdaystr == "Su" then
obj.canvas[3+i].textColor = {hex="#FF7878"}
obj.canvas[34+i].fillColor = {hex="#FF7878"}
obj.canvas[65+i].textColor = {hex="#FF7878"}
else
-- Restore the default colors
obj.canvas[3+i].textColor = {hex="#FFFFFF"}
obj.canvas[34+i].fillColor = {hex="#FFFFFF", alpha=0.5}
obj.canvas[65+i].textColor = {hex="#FFFFFF"}
end
if i == math.tointeger(currentday) then
obj.canvas[34+i].fillColor = {hex="#00BAFF", alpha=0.8}
obj.canvas[97].frame.x = tostring((10+24*(i-1))/obj.hcalw)
end
end
-- hide extra day
for i=lastdayofcurrentmonth+1, 31 do
obj.canvas[3+i].text = ""
obj.canvas[34+i].fillColor.alpha = 0
obj.canvas[65+i].text = ""
end
-- Adjust the size of clipmask to clip the canvas
obj.canvas[2].frame.w = tostring((lastdayofcurrentmonth*24+20)/obj.hcalw)
end
function obj:init()
local hcalbgcolor = {hex="#000000", alpha=0.3}
local hcaltitlecolor = {hex="#FFFFFF", alpha=0.3}
local todaycolor = {hex="#FFFFFF", alpha=0.2}
local midlinecolor = {hex="#FFFFFF", alpha=0.5}
local cscreen = hs.screen.mainScreen()
local cres = cscreen:fullFrame()
obj.canvas = hs.canvas.new({
x = 40,
y = cres.h-obj.hcalh-40,
w = obj.hcalw,
h = obj.hcalh,
}):show()
obj.canvas:behavior(hs.canvas.windowBehaviors.canJoinAllSpaces)
obj.canvas:level(hs.canvas.windowLevels.desktopIcon)
-- Use one pseudo element to clip the canvas
obj.canvas[1] = {
type = "rectangle",
action = "clip",
roundedRectRadii = {xRadius = 10, yRadius = 10},
}
-- *NOW* we can draw our actual "visible" parts
obj.canvas[2] = {
id = "hcal_bg",
type = "rectangle",
action = "fill",
fillColor = hcalbgcolor,
roundedRectRadii = {xRadius = 10, yRadius = 10},
}
obj.canvas[3] = {
id = "hcal_title",
type = "text",
text = "",
textSize = 18,
textColor = hcaltitlecolor,
textAlignment = "left",
frame = {
x = tostring(10/obj.hcalw),
y = tostring(10/obj.hcalh),
w = tostring(1-20/obj.hcalw),
h = "30%"
}
}
-- upside weekday string
for i=4, 4+30 do
obj.canvas[i] = {
type = "text",
text = "",
textFont = "Courier-Bold",
textSize = 13,
textAlignment = "center",
frame = {
x = tostring((10+24*(i-4))/obj.hcalw),
y = "45%",
w = tostring(24/(obj.hcalw-20)),
h = "23%"
}
}
end
-- midline rectangle
for i=35, 35+30 do
obj.canvas[i] = {
type = "rectangle",
action = "fill",
fillColor = midlinecolor,
frame = {
x = tostring((10+24*(i-35))/obj.hcalw),
y = "65%",
w = tostring(24/(obj.hcalw-20)),
h = "4%"
}
}
end
-- downside day string
for i=66, 66+30 do
obj.canvas[i] = {
type = "text",
text = "",
textFont = "Courier-Bold",
textSize = 13,
textAlignment = "center",
frame = {
x = tostring((10+24*(i-66))/obj.hcalw),
y = "70%",
w = tostring(24/(obj.hcalw-20)),
h = "23%"
}
}
end
-- today cover rectangle
obj.canvas[97] = {
type = "rectangle",
action = "fill",
fillColor = todaycolor,
roundedRectRadii = {xRadius = 3, yRadius = 3},
frame = {
x = tostring(10/obj.hcalw),
y = "44%",
w = tostring(24/(obj.hcalw-20)),
h = "46%"
}
}
if obj.timer == nil then
obj.timer = hs.timer.doEvery(1800, function() updateHcalCanvas() end)
obj.timer:setNextTrigger(0)
else
obj.timer:start()
end
end
return obj

159
hammerspoon/Spoons/HSearch.spoon/docs.json

@ -0,0 +1,159 @@
[
{
"Constant" : [
],
"submodules" : [
],
"Function" : [
],
"Variable" : [
],
"stripped_doc" : [
],
"Deprecated" : [
],
"type" : "Module",
"desc" : "Hammerspoon Search",
"Constructor" : [
],
"Field" : [
],
"Method" : [
{
"doc" : "Trigger new source according to hs.chooser's query string and keyword. Only for debug purpose in usual.",
"stripped_doc" : [
"Trigger new source according to hs.chooser's query string and keyword. Only for debug purpose in usual."
],
"parameters" : [
],
"def" : "HSearch:switchSource()",
"notes" : [
],
"signature" : "HSearch:switchSource()",
"type" : "Method",
"returns" : [
],
"desc" : "Trigger new source according to hs.chooser's query string and keyword. Only for debug purpose in usual.",
"name" : "switchSource"
},
{
"doc" : "Load new sources from `HSearch.search_path`, the search_path defaults to `~\/.hammerspoon\/private\/hsearch_dir` and the HSearch Spoon directory. Only for debug purpose in usual.",
"stripped_doc" : [
"Load new sources from `HSearch.search_path`, the search_path defaults to `~\/.hammerspoon\/private\/hsearch_dir` and the HSearch Spoon directory. Only for debug purpose in usual."
],
"parameters" : [
],
"def" : "HSearch:loadSources()",
"notes" : [
],
"signature" : "HSearch:loadSources()",
"type" : "Method",
"returns" : [
],
"desc" : "Load new sources from `HSearch.search_path`, the search_path defaults to `~\/.hammerspoon\/private\/hsearch_dir` and the HSearch Spoon directory. Only for debug purpose in usual.",
"name" : "loadSources"
},
{
"doc" : "Toggle the display of HSearch",
"stripped_doc" : [
"Toggle the display of HSearch"
],
"parameters" : [
],
"def" : "HSearch:toggleShow()",
"notes" : [
],
"signature" : "HSearch:toggleShow()",
"type" : "Method",
"returns" : [
],
"desc" : "Toggle the display of HSearch",
"name" : "toggleShow"
}
],
"Command" : [
],
"doc" : "Hammerspoon Search\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/HSearch.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/HSearch.spoon.zip)",
"items" : [
{
"doc" : "Load new sources from `HSearch.search_path`, the search_path defaults to `~\/.hammerspoon\/private\/hsearch_dir` and the HSearch Spoon directory. Only for debug purpose in usual.",
"stripped_doc" : [
"Load new sources from `HSearch.search_path`, the search_path defaults to `~\/.hammerspoon\/private\/hsearch_dir` and the HSearch Spoon directory. Only for debug purpose in usual."
],
"parameters" : [
],
"def" : "HSearch:loadSources()",
"notes" : [
],
"signature" : "HSearch:loadSources()",
"type" : "Method",
"returns" : [
],
"desc" : "Load new sources from `HSearch.search_path`, the search_path defaults to `~\/.hammerspoon\/private\/hsearch_dir` and the HSearch Spoon directory. Only for debug purpose in usual.",
"name" : "loadSources"
},
{
"doc" : "Trigger new source according to hs.chooser's query string and keyword. Only for debug purpose in usual.",
"stripped_doc" : [
"Trigger new source according to hs.chooser's query string and keyword. Only for debug purpose in usual."
],
"parameters" : [
],
"def" : "HSearch:switchSource()",
"notes" : [
],
"signature" : "HSearch:switchSource()",
"type" : "Method",
"returns" : [
],
"desc" : "Trigger new source according to hs.chooser's query string and keyword. Only for debug purpose in usual.",
"name" : "switchSource"
},
{
"doc" : "Toggle the display of HSearch",
"stripped_doc" : [
"Toggle the display of HSearch"
],
"parameters" : [
],
"def" : "HSearch:toggleShow()",
"notes" : [
],
"signature" : "HSearch:toggleShow()",
"type" : "Method",
"returns" : [
],
"desc" : "Toggle the display of HSearch",
"name" : "toggleShow"
}
],
"name" : "HSearch"
}
]

55
hammerspoon/Spoons/HSearch.spoon/hs_btabs.lua

@ -0,0 +1,55 @@
local obj={}
obj.__index = obj
obj.name = "browserTabs"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
-- 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()
-- Define the source's overview. A unique `keyword` key should exist, so this source can be found.
obj.overview = {text="Type t ⇥ to search safari/chrome Tabs.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/tabs.png"), keyword="t"}
-- Define the notice when a long-time request is being executed. It could be `nil`.
obj.notice = {text="Requesting data, please wait a while …"}
local function browserTabsRequest()
local safari_running = hs.application.applicationsForBundleID("com.apple.Safari")
local chooser_data = {}
if #safari_running > 0 then
local stat, data= hs.osascript.applescript('tell application "Safari"\nset winlist to tabs of windows\nset tablist to {}\nrepeat with i in winlist\nif (count of i) > 0 then\nrepeat with currenttab in i\nset tabinfo to {name of currenttab as unicode text, URL of currenttab}\ncopy tabinfo to the end of tablist\nend repeat\nend if\nend repeat\nreturn tablist\nend tell')
-- Notice `output` key and its `arg`. The built-in output contains `browser`, `safari`, `chrome`, `firefon`, `clipboard`, `keystrokes`. You can define new output type if you like.
if stat then
chooser_data = hs.fnutils.imap(data, function(item)
return {text=item[1], subText=item[2], image=hs.image.imageFromPath(obj.spoonPath .. "/resources/safari.png"), output="safari", arg=item[2]}
end)
end
end
local chrome_running = hs.application.applicationsForBundleID("com.google.Chrome")
if #chrome_running > 0 then
local stat, data= hs.osascript.applescript('tell application "Google Chrome"\nset winlist to tabs of windows\nset tablist to {}\nrepeat with i in winlist\nif (count of i) > 0 then\nrepeat with currenttab in i\nset tabinfo to {name of currenttab as unicode text, URL of currenttab}\ncopy tabinfo to the end of tablist\nend repeat\nend if\nend repeat\nreturn tablist\nend tell')
if stat then
for idx,val in pairs(data) do
-- Usually we want to open chrome tabs in Google Chrome.
table.insert(chooser_data, {text=val[1], subText=val[2], image=hs.image.imageFromPath(obj.spoonPath .. "/resources/chrome.png"), output="chrome", arg=val[2]})
end
end
end
-- Return specific table as hs.chooser's data, other keys except for `text` could be optional.
return chooser_data
end
-- Define the function which will be called when the `keyword` triggers a new source. The returned value is a table. Read more: http://www.hammerspoon.org/docs/hs.chooser.html#choices
obj.init_func = browserTabsRequest
-- Insert a friendly tip at the head so users know what to do next.
obj.description = {text="Browser Tabs Search", subText="Search and select one item to open in corresponding browser.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/tabs.png")}
-- As the user is typing, the callback function will be called for every keypress. The returned value is a table.
obj.callback = nil
return obj

73
hammerspoon/Spoons/HSearch.spoon/hs_datamuse.lua

@ -0,0 +1,73 @@
local obj={}
obj.__index = obj
obj.name = "thesaurusDM"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
-- 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()
-- Define the source's overview. A unique `keyword` key should exist, so this source can be found.
obj.overview = {text="Type s ⇥ to request English Thesaurus.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/thesaurus.png"), keyword="s"}
-- Define the notice when a long-time request is being executed. It could be `nil`.
obj.notice = nil
local function dmTips()
local chooser_data = {
{text="Datamuse Thesaurus", subText="Type something to get more words like it …", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/thesaurus.png")}
}
return chooser_data
end
-- Define the function which will be called when the `keyword` triggers a new source. The returned value is a table. Read more: http://www.hammerspoon.org/docs/hs.chooser.html#choices
obj.init_func = dmTips
-- Insert a friendly tip at the head so users know what to do next.
-- As this source highly relys on queryChangedCallback, we'd better tip users in callback instead of here
obj.description = nil
-- As the user is typing, the callback function will be called for every keypress. The returned value is a table.
local function thesaurusRequest(querystr)
local datamuse_baseurl = 'http://api.datamuse.com'
if string.len(querystr) > 0 then
local encoded_query = hs.http.encodeForQuery(querystr)
local query_url = datamuse_baseurl .. '/words?ml=' .. encoded_query .. '&max=20'
hs.http.asyncGet(query_url, nil, function(status, data)
if status == 200 then
if pcall(function() hs.json.decode(data) end) then
local decoded_data = hs.json.decode(data)
if #decoded_data > 0 then
local chooser_data = hs.fnutils.imap(decoded_data, function(item)
return {text = item.word, image=hs.image.imageFromPath(obj.spoonPath .. "/resources/thesaurus.png"), output="keystrokes", arg=item.word}
end)
-- Because we don't know when asyncGet will return data, we have to refresh hs.chooser choices in this callback.
if spoon.HSearch then
-- Make sure HSearch spoon is running now
spoon.HSearch.chooser:choices(chooser_data)
spoon.HSearch.chooser:refreshChoicesCallback()
end
end
end
end
end)
else
local chooser_data = {
{text="Datamuse Thesaurus", subText="Type something to get more words like it …", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/thesaurus.png")}
}
if spoon.HSearch then
spoon.HSearch.chooser:choices(chooser_data)
spoon.HSearch.chooser:refreshChoicesCallback()
end
end
end
obj.callback = thesaurusRequest
return obj

87
hammerspoon/Spoons/HSearch.spoon/hs_emoji.lua

@ -0,0 +1,87 @@
local obj={}
obj.__index = obj
obj.name = "MLemoji"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
-- 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()
-- Define the source's overview. A unique `keyword` key should exist, so this source can be found.
obj.overview = {text="Type e ⇥ to find relevant Emoji.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/emoji.png"), keyword="e"}
-- Define the notice when a long-time request is being executed. It could be `nil`.
obj.notice = nil
local function emojiTips()
local chooser_data = {
{text="Relevant Emoji", subText="Type something to find relevant emoji from text …", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/emoji.png")}
}
return chooser_data
end
-- Define the function which will be called when the `keyword` triggers a new source. The returned value is a table. Read more: http://www.hammerspoon.org/docs/hs.chooser.html#choices
obj.init_func = emojiTips
-- Insert a friendly tip at the head so users know what to do next.
-- As this source highly relys on queryChangedCallback, we'd better tip users in callback instead of here
obj.description = nil
-- As the user is typing, the callback function will be called for every keypress. The returned value is a table.
-- Some global objects
local emoji_database_path = "/System/Library/Input Methods/CharacterPalette.app/Contents/Resources/CharacterDB.sqlite3"
obj.database = hs.sqlite3.open(emoji_database_path)
obj.canvas = hs.canvas.new({x=0, y=0, w=96, h=96})
local function getEmojiDesc(arg)
for w in obj.database:rows("SELECT info FROM unihan_dict WHERE uchr=\'" .. arg .. "\'") do
return w[1]
end
end
local function emojiRequest(querystr)
local emoji_baseurl = 'https://emoji.getdango.com'
if string.len(querystr) > 0 then
local encoded_query = hs.http.encodeForQuery(querystr)
local query_url = emoji_baseurl .. '/api/emoji?q=' .. encoded_query
hs.http.asyncGet(query_url, nil, function(status, data)
if status == 200 then
if pcall(function() hs.json.decode(data) end) then
local decoded_data = hs.json.decode(data)
if decoded_data.results and #decoded_data.results > 0 then
local chooser_data = hs.fnutils.imap(decoded_data.results, function(item)
obj.canvas[1] = {type="text", text=item.text, textSize=64, frame={x="15%", y="10%", w="100%", h="100%"}}
local hexcode = string.format("%#X", utf8.codepoint(item.text))
local emoji_description = getEmojiDesc(item.text)
local formatted_desc = string.gsub(emoji_description, "|||||||||||||||", "")
return {text = formatted_desc, image=obj.canvas:imageFromCanvas(), subText="Hex Code: " .. hexcode, outputType="keystrokes", arg=item.text}
end)
-- Because we don't know when asyncGet will return data, we have to refresh hs.chooser choices in this callback.
if spoon.HSearch then
-- Make sure HSearch spoon is running now
spoon.HSearch.chooser:choices(chooser_data)
spoon.HSearch.chooser:refreshChoicesCallback()
end
end
end
end
end)
else
local chooser_data = {
{text="Relevant Emoji", subText="Type something to find relevant emoji from text …", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/emoji.png")}
}
if spoon.HSearch then
spoon.HSearch.chooser:choices(chooser_data)
spoon.HSearch.chooser:refreshChoicesCallback()
end
end
end
obj.callback = emojiRequest
return obj

96
hammerspoon/Spoons/HSearch.spoon/hs_note.lua

@ -0,0 +1,96 @@
local obj={}
obj.__index = obj
obj.name = "justNote"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
-- 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()
-- Define the source's overview. A unique `keyword` key should exist, so this source can be found.
obj.overview = {text="Type n ⇥ to Note something.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/justnote.png"), keyword="n"}
-- Define the notice when a long-time request is being executed. It could be `nil`.
obj.notice = nil
-- Define the hotkeys, which will be enabled/disabled automatically. You need to add your keybindings into this table manually.
obj.hotkeys = {}
local function justNoteRequest()
local note_history = hs.settings.get("just.another.note") or {}
if #note_history == 0 then
local chooser_data = {{text="Write something and press Enter.", subText="Your notes is automatically saved, selected item will be erased.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/justnote.png")}}
return chooser_data
else
local chooser_data = hs.fnutils.imap(note_history, function(item)
return {uuid=item.uuid, text=item.content, subText=item.ctime, image=hs.image.imageFromPath(obj.spoonPath .. "/resources/justnote.png"), output="noteremove", arg=item.uuid}
end)
return chooser_data
end
end
-- Define the function which will be called when the `keyword` triggers a new source. The returned value is a table. Read more: http://www.hammerspoon.org/docs/hs.chooser.html#choices
obj.init_func = justNoteRequest
-- Insert a friendly tip at the head so users know what to do next.
obj.description = nil
-- As the user is typing, the callback function will be called for every keypress. The returned value is a table.
local function isInNoteHistory(value, tbl)
for idx,val in pairs(tbl) do
if val.uuid == value then
return true
end
end
return false
end
local function justNoteStore()
if spoon.HSearch then
local querystr = string.gsub(spoon.HSearch.chooser:query(), "%s+$", "")
if string.len(querystr) > 0 then
local query_hash = hs.hash.SHA1(querystr)
local note_history = hs.settings.get("just.another.note") or {}
if not isInNoteHistory(query_hash, note_history) then
table.insert(note_history, {uuid=query_hash, ctime="Created at "..os.date(), content=querystr})
hs.settings.set("just.another.note", note_history)
end
end
end
end
local store_trigger = hs.hotkey.new("", "return", nil, function()
justNoteStore()
if spoon.HSearch then
local chooser_data = justNoteRequest()
spoon.HSearch.chooser:choices(chooser_data)
spoon.HSearch.chooser:query("")
end
end)
table.insert(obj.hotkeys, store_trigger)
obj.callback = nil
-- Define a new output type
local function removeNote(arg)
local note_history = hs.settings.get("just.another.note") or {}
for idx,val in pairs(note_history) do
if val.uuid == arg then
table.remove(note_history, idx)
hs.settings.set("just.another.note", note_history)
end
local chooser_data = justNoteRequest()
if spoon.HSearch then
spoon.HSearch.chooser:choices(chooser_data)
end
end
end
obj.new_output = {
name = "noteremove",
func = removeNote
}
return obj

87
hammerspoon/Spoons/HSearch.spoon/hs_time.lua

@ -0,0 +1,87 @@
local obj={}
obj.__index = obj
obj.name = "timeDelta"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
-- 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()
-- Define the source's overview. A unique `keyword` key should exist, so this source can be found.
obj.overview = {text="Type d ⇥ to format/query Date.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/time.png"), keyword="d"}
-- Define the notice when a long-time request is being executed. It could be `nil`.
obj.notice = nil
-- Some global objects
obj.exec_args = {
'+"%Y-%m-%d"',
'+"%H:%M:%S %p"',
'+"%A, %B %d, %Y"',
'+"%Y-%m-%d %H:%M:%S %p"',
'+"%a, %b %d, %y"',
'+"%m/%d/%y %H:%M %p"',
'',
'-u',
}
local function timeRequest()
local chooser_data = hs.fnutils.imap(obj.exec_args, function(item)
local exec_result = hs.execute("date " .. item)
return {text=exec_result, subText="date " .. item, image=hs.image.imageFromPath(obj.spoonPath .. "/resources/time.png"), output="keystrokes", arg=exec_result}
end)
return chooser_data
end
-- Define the function which will be called when the `keyword` triggers a new source. The returned value is a table. Read more: http://www.hammerspoon.org/docs/hs.chooser.html#choices
obj.init_func = timeRequest
-- Insert a friendly tip at the head so users know what to do next.
-- As this source highly relys on queryChangedCallback, we'd better tip users in callback instead of here
obj.description = {text="Date Query", subText="Type +/-1d (or y, m, w, H, M, S) to query date forward or backward.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/time.png")}
-- As the user is typing, the callback function will be called for every keypress. The returned value is a table.
local function splitBySpace(str)
local tmptbl = {}
for w in string.gmatch(str,"[+-]?%d+[ymdwHMS]") do table.insert(tmptbl,w) end
return tmptbl
end
local function timeDeltaRequest(querystr)
if string.len(querystr) > 0 then
local valid_inputs = splitBySpace(querystr)
if #valid_inputs > 0 then
local addv_before = hs.fnutils.imap(valid_inputs, function(item)
return "-v" .. item
end)
local vv_var = table.concat(addv_before, " ")
local chooser_data = hs.fnutils.imap(obj.exec_args, function(item)
local new_exec_command = "date " .. vv_var .. " " .. item
local new_exec_result = hs.execute(new_exec_command)
return {text=new_exec_result, subText=new_exec_command, image=hs.image.imageFromPath(obj.spoonPath .. "/resources/time.png"), output="keystrokes", arg=new_exec_result}
end)
local source_desc = {text="Date Query", subText="Type +/-1d (or y, m, w, H, M, S) to query date forward or backward.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/time.png")}
table.insert(chooser_data, 1, source_desc)
if spoon.HSearch then
-- Make sure HSearch spoon is running now
spoon.HSearch.chooser:choices(chooser_data)
end
end
else
local chooser_data = timeRequest()
local source_desc = {text="Date Query", subText="Type +/-1d (or y, m, w, H, M, S) to query date forward or backward.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/time.png")}
table.insert(chooser_data, 1, source_desc)
if spoon.HSearch then
-- Make sure HSearch spoon is running now
spoon.HSearch.chooser:choices(chooser_data)
end
end
end
obj.callback = timeDeltaRequest
return obj

63
hammerspoon/Spoons/HSearch.spoon/hs_v2ex.lua

@ -0,0 +1,63 @@
local obj={}
obj.__index = obj
obj.name = "v2exPosts"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
-- 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()
-- Define the source's overview. A unique `keyword` key should exist, so this source can be found.
obj.overview = {text="Type v ⇥ to fetch v2ex posts.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/v2ex.png"), keyword="v"}
-- Define the notice when a long-time request is being executed. It could be `nil`.
obj.notice = {text="Requesting data, please wait a while …"}
local function v2exRequest()
local query_url = 'https://www.v2ex.com/api/topics/latest.json'
local stat, body = hs.http.asyncGet(query_url, nil, function(status, data)
if status == 200 then
if pcall(function() hs.json.decode(data) end) then
local decoded_data = hs.json.decode(data)
if #decoded_data > 0 then
local chooser_data = hs.fnutils.imap(decoded_data, function(item)
local sub_content = string.gsub(item.content, "\r\n", " ")
local function trim_content()
if utf8.len(sub_content) > 40 then
return string.sub(sub_content, 1, utf8.offset(sub_content, 40)-1)
else
return sub_content
end
end
local final_content = trim_content()
return {text=item.title, subText=final_content, image=hs.image.imageFromPath(obj.spoonPath .. "/resources/v2ex.png"), outputType="browser", arg=item.url}
end)
local source_desc = {text="v2ex Posts", subText="Select some item to get it opened in default browser …", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/v2ex.png")}
table.insert(chooser_data, 1, source_desc)
-- Because we don't know when asyncGet will return data, we have to refresh hs.chooser choices in this callback.
if spoon.HSearch then
-- Make sure HSearch spoon is running now
spoon.HSearch.chooser:choices(chooser_data)
spoon.HSearch.chooser:refreshChoicesCallback()
end
end
end
end
end)
end
-- Define the function which will be called when the `keyword` triggers a new source. The returned value is a table. Read more: http://www.hammerspoon.org/docs/hs.chooser.html#choices
obj.init_func = v2exRequest
-- Insert a friendly tip at the head so users know what to do next.
-- As this source highly relys on queryChangedCallback, we'd better tip users in callback instead of here
obj.description = nil
-- As the user is typing, the callback function will be called for every keypress. The returned value is a table.
obj.callback = nil
return obj

94
hammerspoon/Spoons/HSearch.spoon/hs_yddict.lua

@ -0,0 +1,94 @@
local obj={}
obj.__index = obj
obj.name = "youdaoDict"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
-- 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()
-- Define the source's overview. A unique `keyword` key should exist, so this source can be found.
obj.overview = {text="Type y ⇥ to use Yaodao dictionary.", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/youdao.png"), keyword="y"}
-- Define the notice when a long-time request is being executed. It could be `nil`.
obj.notice = nil
local function youdaoTips()
local chooser_data = {
{text="Youdao Dictionary", subText="Type something to get it translated …", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/youdao.png")}
}
return chooser_data
end
-- Define the function which will be called when the `keyword` triggers a new source. The returned value is a table. Read more: http://www.hammerspoon.org/docs/hs.chooser.html#choices
obj.init_func = youdaoTips
-- Insert a friendly tip at the head so users know what to do next.
-- As this source highly relys on queryChangedCallback, we'd better tip users in callback instead of here
obj.description = nil
-- As the user is typing, the callback function will be called for every keypress. The returned value is a table.
local function basic_extract(arg)
if arg then return arg.explains else return {} end
end
local function web_extract(arg)
if arg then
local value = hs.fnutils.imap(arg, function(item)
return item.key .. table.concat(item.value, ",")
end)
return value
else
return {}
end
end
local function youdaoInstantTrans(querystr)
local youdao_keyfrom = 'hsearch'
local youdao_apikey = '1199732752'
local youdao_baseurl = 'http://fanyi.youdao.com/openapi.do?keyfrom=' .. youdao_keyfrom .. '&key=' .. youdao_apikey .. '&type=data&doctype=json&version=1.1&q='
if string.len(querystr) > 0 then
local encoded_query = hs.http.encodeForQuery(querystr)
local query_url = youdao_baseurl .. encoded_query
hs.http.asyncGet(query_url, nil, function(status, data)
if status == 200 then
if pcall(function() hs.json.decode(data) end) then
local decoded_data = hs.json.decode(data)
if decoded_data.errorCode == 0 then
local basictrans = basic_extract(decoded_data.basic)
local webtrans = web_extract(decoded_data.web)
local dictpool = hs.fnutils.concat(basictrans, webtrans)
if #dictpool > 0 then
local chooser_data = hs.fnutils.imap(dictpool, function(item)
return {text=item, image=hs.image.imageFromPath(obj.spoonPath .. "/resources/youdao.png"), output="clipboard", arg=item}
end)
-- Because we don't know when asyncGet will return data, we have to refresh hs.chooser choices in this callback.
if spoon.HSearch then
-- Make sure HSearch spoon is running now
spoon.HSearch.chooser:choices(chooser_data)
spoon.HSearch.chooser:refreshChoicesCallback()
end
end
end
end
end
end)
else
local chooser_data = {
{text="Youdao Dictionary", subText="Type something to get it translated …", image=hs.image.imageFromPath(obj.spoonPath .. "/resources/youdao.png")}
}
if spoon.HSearch then
spoon.HSearch.chooser:choices(chooser_data)
spoon.HSearch.chooser:refreshChoicesCallback()
end
end
end
obj.callback = youdaoInstantTrans
return obj

256
hammerspoon/Spoons/HSearch.spoon/init.lua

@ -0,0 +1,256 @@
--- === HSearch ===
---
--- Hammerspoon Search
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/HSearch.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/HSearch.spoon.zip)
local obj={}
obj.__index = obj
-- Metadata
obj.name = "HSearch"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
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()
obj.sources = {}
obj.sources_overview = {}
obj.search_path = {hs.configdir .. "/private/hsearch_dir", obj.spoonPath}
obj.hotkeys = {}
obj.source_kw = nil
function obj:restoreOutput()
obj.output_pool = {}
-- Define the built-in output type
local function openWithBrowser(arg)
local default_browser = hs.urlevent.getDefaultHandler('http')
hs.urlevent.openURLWithBundle(arg, default_browser)
end
local function openWithSafari(arg)
hs.urlevent.openURLWithBundle(arg, "com.apple.Safari")
end
local function openWithChrome(arg)
hs.urlevent.openURLWithBundle(arg, "com.google.Chrome")
end
local function openWithFirefox(arg)
hs.urlevent.openURLWithBundle(arg, "org.mozilla.firefox")
end
local function copyToClipboard(arg)
hs.pasteboard.setContents(arg)
end
local function sendKeyStrokes(arg)
local cwin = hs.window.orderedWindows()[1]
cwin:focus()
hs.eventtap.keyStrokes(arg)
end
obj.output_pool["browser"] = openWithBrowser
obj.output_pool["safari"] = openWithSafari
obj.output_pool["chrome"] = openWithChrome
obj.output_pool["firefox"] = openWithFirefox
obj.output_pool["clipboard"] = copyToClipboard
obj.output_pool["keystrokes"] = sendKeyStrokes
end
function obj:init()
obj.chooser = hs.chooser.new(function(chosen)
obj.trigger:disable()
-- Disable all hotkeys
for _,val in pairs(obj.hotkeys) do
for i=1,#val do
val[i]:disable()
end
end
if chosen ~= nil then
if chosen.output then
obj.output_pool[chosen.output](chosen.arg)
end
end
end)
obj.chooser:rows(9)
end
--- HSearch:switchSource()
--- Method
--- Tigger new source according to hs.chooser's query string and keyword. Only for debug purpose in usual.
---
function obj:switchSource()
local querystr = obj.chooser:query()
if string.len(querystr) > 0 then
local matchstr = string.match(querystr, "^%w+")
if matchstr == querystr then
-- First we try to switch source according to the querystr
if obj.sources[querystr] then
obj.source_kw = querystr
obj.chooser:query('')
obj.chooser:choices(nil)
obj.chooser:queryChangedCallback()
obj.sources[querystr]()
else
local row_content = obj.chooser:selectedRowContents()
local row_kw = row_content.keyword
-- Then try to switch source according to selected row
if obj.sources[row_kw] then
obj.source_kw = row_kw
obj.chooser:query('')
obj.chooser:choices(nil)
obj.chooser:queryChangedCallback()
obj.sources[row_kw]()
else
obj.source_kw = nil
local chooser_data = {
{text="No source found!", subText="Maybe misspelled the keyword?"},
{text="Want to add your own source?", subText="Feel free to read the code and open PRs. :)"}
}
obj.chooser:choices(chooser_data)
obj.chooser:queryChangedCallback()
hs.eventtap.keyStroke({"cmd"}, "a")
end
end
else
obj.source_kw = nil
local chooser_data = {
{text="Invalid Keyword", subText="Trigger keyword must only consist of alphanumeric characters."}
}
obj.chooser:choices(chooser_data)
obj.chooser:queryChangedCallback()
hs.eventtap.keyStroke({"cmd"}, "a")
end
else
local row_content = obj.chooser:selectedRowContents()
local row_kw = row_content.keyword
if obj.sources[row_kw] then
obj.source_kw = row_kw
obj.chooser:query('')
obj.chooser:choices(nil)
obj.chooser:queryChangedCallback()
obj.sources[row_kw]()
else
obj.source_kw = nil
-- If no matching source then show sources overview
local chooser_data = obj.sources_overview
obj.chooser:choices(chooser_data)
obj.chooser:queryChangedCallback()
end
end
if obj.source_kw then
for key,val in pairs(obj.hotkeys) do
if key == obj.source_kw then
for i=1,#val do
val[i]:enable()
end
else
for i=1,#val do
val[i]:disable()
end
end
end
else
for _,val in pairs(obj.hotkeys) do
for i=1,#val do
val[i]:disable()
end
end
end
end
--- HSearch:loadSources()
--- Method
--- Load new sources from `HSearch.search_path`, the search_path defaults to `~/.hammerspoon/private/hsearch_dir` and the HSearch Spoon directory. Only for debug purpose in usual.
---
function obj:loadSources()
obj.sources = {}
obj.sources_overview = {}
obj:restoreOutput()
for _,dir in ipairs(obj.search_path) do
local file_list = io.popen("find " .. dir .. " -type f -name '*.lua'")
for file in file_list:lines() do
-- Exclude self
if file ~= obj.spoonPath .. "/init.lua" then
local f = loadfile(file)
if f then
local source = f()
local output = source.new_output
if output then obj.output_pool[output.name] = output.func end
local overview = source.overview
-- Gather souces overview from files
table.insert(obj.sources_overview, overview)
local hotkey = source.hotkeys
if hotkey then obj.hotkeys[overview.keyword] = hotkey end
local function sourceFunc()
local notice = source.notice
if notice then obj.chooser:choices({notice}) end
local request = source.init_func
if request then
local chooser_data = request()
if chooser_data then
local desc = source.description
if desc then table.insert(chooser_data, 1, desc) end
end
obj.chooser:choices(chooser_data)
else
obj.chooser:choices(nil)
end
if source.callback then
obj.chooser:queryChangedCallback(source.callback)
else
obj.chooser:queryChangedCallback()
end
obj.chooser:searchSubText(true)
end
-- Add this source to sources pool, so it can found and triggered.
obj.sources[overview.keyword] = sourceFunc
end
end
end
end
end
--- HSearch:toggleShow()
--- Method
--- Toggle the display of HSearch
---
function obj:toggleShow()
if #obj.sources_overview == 0 then
-- If it's the first time HSearch shows itself, then load all sources from files
obj:loadSources()
-- Show sources overview, so users know what to do next.
obj.chooser:choices(obj.sources_overview)
end
if obj.chooser:isVisible() then
obj.chooser:hide()
obj.trigger:disable()
for _,val in pairs(obj.hotkeys) do
for i=1,#val do
val[i]:disable()
end
end
else
if obj.trigger == nil then
obj.trigger = hs.hotkey.bind("", "tab", nil, function() obj:switchSource() end)
else
obj.trigger:enable()
end
for key,val in pairs(obj.hotkeys) do
if key == obj.source_kw then
for i=1,#val do
val[i]:enable()
end
end
end
obj.chooser:show()
end
end
return obj

BIN
hammerspoon/Spoons/HSearch.spoon/resources/chrome.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
hammerspoon/Spoons/HSearch.spoon/resources/emoji.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
hammerspoon/Spoons/HSearch.spoon/resources/justnote.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
hammerspoon/Spoons/HSearch.spoon/resources/menus.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

BIN
hammerspoon/Spoons/HSearch.spoon/resources/safari.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
hammerspoon/Spoons/HSearch.spoon/resources/tabs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
hammerspoon/Spoons/HSearch.spoon/resources/taskkill.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
hammerspoon/Spoons/HSearch.spoon/resources/thesaurus.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
hammerspoon/Spoons/HSearch.spoon/resources/time.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
hammerspoon/Spoons/HSearch.spoon/resources/v2ex.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
hammerspoon/Spoons/HSearch.spoon/resources/youdao.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

119
hammerspoon/Spoons/KSheet.spoon/docs.json

@ -0,0 +1,119 @@
[
{
"Constant" : [
],
"submodules" : [
],
"Function" : [
],
"Variable" : [
],
"stripped_doc" : [
],
"desc" : "Keybindings cheatsheet for current application",
"Deprecated" : [
],
"type" : "Module",
"Constructor" : [
],
"doc" : "Keybindings cheatsheet for current application\n\nDownload: [https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/KSheet.spoon.zip](https:\/\/github.com\/Hammerspoon\/Spoons\/raw\/master\/Spoons\/KSheet.spoon.zip)",
"Method" : [
{
"doc" : "Show current application's keybindings in a webview",
"name" : "show",
"desc" : "Show current application's keybindings in a webview",
"stripped_doc" : [
"Show current application's keybindings in a webview"
],
"notes" : [
],
"signature" : "KSheet:show()",
"type" : "Method",
"returns" : [
],
"def" : "KSheet:show()",
"parameters" : [
]
},
{
"doc" : "Hide the cheatsheet webview",
"name" : "hide",
"desc" : "Hide the cheatsheet webview",
"stripped_doc" : [
"Hide the cheatsheet webview"
],
"notes" : [
],
"signature" : "KSheet:hide()",
"type" : "Method",
"returns" : [
],
"def" : "KSheet:hide()",
"parameters" : [
]
}
],
"Command" : [
],
"Field" : [
],
"items" : [
{
"doc" : "Hide the cheatsheet webview",
"name" : "hide",
"desc" : "Hide the cheatsheet webview",
"stripped_doc" : [
"Hide the cheatsheet webview"
],
"notes" : [
],
"signature" : "KSheet:hide()",
"type" : "Method",
"returns" : [
],
"def" : "KSheet:hide()",
"parameters" : [
]
},
{
"doc" : "Show current application's keybindings in a webview",
"name" : "show",
"desc" : "Show current application's keybindings in a webview",
"stripped_doc" : [
"Show current application's keybindings in a webview"
],
"notes" : [
],
"signature" : "KSheet:show()",
"type" : "Method",
"returns" : [
],
"def" : "KSheet:show()",
"parameters" : [
]
}
],
"name" : "KSheet"
}
]

214
hammerspoon/Spoons/KSheet.spoon/init.lua

@ -0,0 +1,214 @@
--- === KSheet ===
---
--- Keybindings cheatsheet for current application
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/KSheet.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/KSheet.spoon.zip)
local obj={}
obj.__index = obj
-- Metadata
obj.name = "KSheet"
obj.version = "1.0"
obj.author = "ashfinal <[email protected]>"
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 .. "<ul class='col col" .. pos .. "'>"
menu = menu .. "<li class='title'><strong>" .. val.AXTitle .. "</strong></li>"
menu = menu .. processMenuItems(val.AXChildren[1])
menu = menu .. "</ul>"
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 .. "<li><div class='cmdModifiers'>" .. CmdModifiers .. " " .. CmdKeys .. "</div><div class='cmdtext'>" .. " " .. val.AXTitle .. "</div></li>"
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 = [[
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
*{margin:0; padding:0;}
html, body{
background-color:#eee;
font-family: arial;
font-size: 13px;
}
a{
text-decoration:none;
color:#000;
font-size:12px;
}
li.title{ text-align:center;}
ul, li{list-style: inside none; padding: 0 0 5px;}
footer{
position: fixed;
left: 0;
right: 0;
height: 48px;
background-color:#eee;
}
header{
position: fixed;
top: 0;
left: 0;
right: 0;
height:48px;
background-color:#eee;
z-index:99;
}
footer{ bottom: 0; }
header hr,
footer hr {
border: 0;
height: 0;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}
.title{
padding: 15px;
}
li.title{padding: 0 10px 15px}
.content{
padding: 0 0 15px;
font-size:12px;
overflow:hidden;
}
.content.maincontent{
position: relative;
height: 577px;
margin-top: 46px;
}
.content > .col{
width: 23%;
padding:20px 0 20px 20px;
}
li:after{
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
.cmdModifiers{
width: 60px;
padding-right: 15px;
text-align: right;
float: left;
font-weight: bold;
}
.cmdtext{
float: left;
overflow: hidden;
width: 165px;
}
</style>
</head>
<body>
<header>
<div class="title"><strong>]] .. app_title .. [[</strong></div>
<hr />
</header>
<div class="content maincontent">]] .. allmenuitems .. [[</div>
<br>
<footer>
<hr />
<div class="content" >
<div class="col">
by <a href="https://github.com/dharmapoudel" target="_parent">dharma poudel</a>
</div>
</div>
</footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.isotope/2.2.2/isotope.pkgd.min.js"></script>
<script type="text/javascript">
var elem = document.querySelector('.content');
var iso = new Isotope( elem, {
// options
itemSelector: '.col',
layoutMode: 'masonry'
});
</script>
</body>
</html>
]]
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

253
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"
}
]

189
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 <[email protected]>"
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

90
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"
}
]

49
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 <[email protected]>"
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

79
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"
}
]

111
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 <[email protected]>"
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

41
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"
}
]

153
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 <[email protected]>"
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

BIN
hammerspoon/Spoons/TimeFlow.spoon/timebg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

41
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"
}
]

52
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 <[email protected]>"
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

406
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"
}
]

278
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 <[email protected]>"
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

25
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()

95
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"}

72
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()

566
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()

15
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()
Loading…
Cancel
Save