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