@ -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/) |
||||
|
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
After Width: | Height: | Size: 28 KiB |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 437 B |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 5.8 KiB |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
After Width: | Height: | Size: 18 KiB |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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" |
||||
} |
||||
] |
@ -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 |
@ -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() |
@ -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"} |
@ -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() |
||||
|
@ -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() |
@ -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() |