Last active
November 20, 2023 03:48
-
-
Save RoyalIcing/e896c44d0a764d197a8673e981da89de to your computer and use it in GitHub Desktop.
My Hammerspoon config
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Runs using the open source Mac app https://www.hammerspoon.org | |
-- Install Hammerspoon, and then copy this Gist into a file at ~/.hammerspoon/init.lua | |
require("hs.ipc") | |
hs.ipc.cliInstall() | |
local math = require("hs.math") | |
currentSpeech = nil | |
mouseCircle = nil | |
mouseCircleTimer = nil | |
function mouseHighlight() | |
-- Delete an existing highlight if it exists | |
if mouseCircle then | |
mouseCircle:delete() | |
if mouseCircleTimer then | |
mouseCircleTimer:stop() | |
end | |
end | |
-- Get the current co-ordinates of the mouse pointer | |
mousepoint = hs.mouse.getAbsolutePosition() | |
-- Prepare a big red circle around the mouse pointer | |
mouseCircle = hs.drawing.circle(hs.geometry.rect(mousepoint.x-40, mousepoint.y-40, 80, 80)) | |
mouseCircle:setStrokeColor({["red"]=1,["blue"]=0,["green"]=0,["alpha"]=1}) | |
mouseCircle:setFill(false) | |
mouseCircle:setStrokeWidth(5) | |
mouseCircle:show() | |
-- Set a timer to delete the circle after 3 seconds | |
mouseCircleTimer = hs.timer.doAfter(3, function() | |
mouseCircle:delete() | |
mouseCircle = nil | |
end) | |
end | |
hs.hotkey.bind({"cmd","alt","shift"}, "4", mouseHighlight) | |
function compressImageCommand() | |
image = hs.pasteboard.readImage() | |
if image then | |
hs.alert.show("IMAGE") | |
tempDir = hs.fs.temporaryDirectory() | |
imagePath = tempDir .. hs.host.uuid() .. ".png" | |
hs.alert.show(imagePath) | |
image:saveToFile(imagePath) | |
byteSize = hs.fs.attributes(imagePath, "size") | |
hs.alert.show(byteSize .. " bytes") | |
-- Open ImageOptim | |
hs.urlevent.openURLWithBundle(hs.fs.urlFromPath(imagePath), "net.pornel.ImageOptim") | |
end | |
-- hs.notify.new(nil, { | |
-- contentImage=image | |
-- }):send() | |
end | |
function resizeImageCommand() | |
image = hs.pasteboard.readImage() | |
if image then | |
-- fullSize = image:size() | |
button, newSizeInput = hs.dialog.textPrompt("Enter size", "Resize to the specified size.", "", "OK", "Cancel") | |
if button == "Cancel" then | |
return | |
end | |
newSize = math.tointeger(tonumber(newSizeInput)) | |
if newSize > 0 then | |
hs.alert.show("New size: " .. newSize) | |
image = image:copy():size({ w = newSize, h = newSize }) | |
imageURLEncoded = image:encodeAsURLString() | |
image = hs.image.imageFromURL(imageURLEncoded) | |
hs.pasteboard.clearContents() | |
hs.pasteboard.writeObjects(image) | |
end | |
end | |
end | |
function copyImageAsURL() | |
image = hs.pasteboard.readImage() | |
if image then | |
imageURLEncoded = image:encodeAsURLString() | |
hs.pasteboard.clearContents() | |
hs.pasteboard.writeObjects(imageURLEncoded) | |
end | |
end | |
function trimText(text) | |
if text then | |
return text:gsub("^%s*(.-)%s*$", "%1") | |
else | |
return "" | |
end | |
end | |
function copyEnteredText() | |
defaultText = hs.pasteboard.readString() | |
button, text = hs.dialog.textPrompt("Enter text to copy", "", defaultText, "Copy Text", "Cancel") | |
if button == "Copy" then | |
hs.pasteboard.writeObjects(text) | |
end | |
end | |
function copyEnteredJavaScript() | |
defaultSource = hs.pasteboard.readString() | |
button, source = hs.dialog.textPrompt("Enter JavaScript to Evaluate", "The result of running this code will be copied to the clipboard.", defaultSource, "Copy Result", "Cancel") | |
if button == "Copy Result" then | |
success, result, descriptor = hs.osascript.javascript(source) | |
if success then | |
hs.alert.show(hs.inspect(result)) | |
hs.alert.show(hs.inspect(descriptor)) | |
if type(result) == "number" or type(result) == "string" then | |
hs.pasteboard.writeObjects("" .. result) | |
else | |
-- asJSON = hs.json.encode(result, true) | |
hs.pasteboard.writeObjects(descriptor) | |
end | |
else | |
hs.alert.show("ERROR") | |
hs.alert.show(hs.inspect(descriptor)) | |
end | |
end | |
end | |
function copyFormattedJSON() | |
text = hs.pasteboard.readString() | |
json = hs.json.decode(text) | |
if json then | |
hs.alert.show("Valid JSON") | |
encoded = hs.json.encode(json, true) | |
hs.pasteboard.writeObjects(encoded) | |
end | |
end | |
function copyUnformattedText() | |
text = hs.pasteboard.readString() | |
if text then | |
hs.pasteboard.clearContents() | |
hs.pasteboard.writeObjects(text) | |
end | |
end | |
function copyTrimmedText() | |
text = hs.pasteboard.readString() | |
if text then | |
text = trimText(text) | |
hs.pasteboard.writeObjects(text) | |
end | |
end | |
function copyTextWithReplacement() | |
text = hs.pasteboard.readString() | |
button, search = hs.dialog.textPrompt("Enter search term", "", "", "Next", "Cancel") | |
if button == "Cancel" then | |
return | |
end | |
button, replacement = hs.dialog.textPrompt("Enter replacement", "", "", "Replace", "Cancel") | |
if button == "Cancel" then | |
return | |
end | |
newText = string.gsub(text, search, replacement) | |
hs.pasteboard.writeObjects(newText) | |
end | |
function copyAlphanumericText() | |
text = hs.pasteboard.readString() | |
if text then | |
hs.pasteboard.clearContents() | |
text = text:gsub('[^0-9a-zA-Z]', "") | |
hs.pasteboard.writeObjects(text) | |
end | |
end | |
function copyUTF8Length() | |
text = hs.pasteboard.readString() | |
if text then | |
hs.pasteboard.clearContents() | |
hs.pasteboard.writeObjects(text:len()) | |
end | |
end | |
function copyBase64Encoded() | |
text = hs.pasteboard.readString() | |
if text then | |
hs.pasteboard.writeObjects(hs.base64.encode(text)) | |
end | |
end | |
function copyBase64Decoded() | |
text = hs.pasteboard.readString() | |
if text then | |
hs.pasteboard.writeObjects(hs.base64.decode(text)) | |
end | |
end | |
function copySHA256Digest() | |
text = hs.pasteboard.readString() | |
if text then | |
hs.pasteboard.writeObjects(hs.hash.SHA256(text)) | |
end | |
end | |
function stopSpeaking() | |
if currentSpeech then | |
currentSpeech:stop() | |
currentSpeech = nil | |
end | |
end | |
function speakText(text) | |
stopSpeaking() | |
currentSpeech = hs.speech.new() | |
currentSpeech:speak(text) | |
end | |
function showClipboardMenu() | |
stopSpeaking() | |
menubar = hs.menubar.new(false) | |
menubar:setMenu(function() | |
local text = hs.pasteboard.readString() | |
local menuItems = {} | |
local uuid = hs.host.uuid() | |
local firstWord = string.gsub(text, "^(%S+).*", "%1") | |
local urlParts = hs.http.urlParts(firstWord) | |
local validURL = urlParts["scheme"] and urlParts["absoluteString"] | |
if text then | |
table.insert(menuItems, { title = text, disabled = true }) | |
table.insert(menuItems, { title = "UTF-8 Length: " .. text:len(), fn = copyUTF8Length }) | |
table.insert(menuItems, { title = "SHA256: " .. hs.hash.SHA256(text):sub(1, 8), fn = copySHA256Digest }) | |
table.insert(menuItems, { title = "Speak…", fn = function() speakText(text) end }) | |
table.insert(menuItems, { title = "-" }) | |
if validURL then | |
table.insert(menuItems, { title = "HEAD " .. validURL, shortcut = "h", fn = function() | |
local start = hs.timer.secondsSinceEpoch() | |
local status, body, headers = hs.http.doRequest(validURL, "HEAD") | |
local duration = hs.timer.secondsSinceEpoch() - start | |
-- hs.alert.show(duration) | |
local summary = validURL .. " " .. tostring(status) .. " in " .. tostring(duration) .. "s" .. "\n" .. hs.inspect(headers) | |
hs.pasteboard.writeObjects(summary) | |
end }) | |
table.insert(menuItems, { title = "GET " .. validURL, shortcut = "g", fn = function() | |
status, body, headers = hs.http.doRequest(validURL, "GET") | |
hs.pasteboard.writeObjects(body) | |
end }) | |
table.insert(menuItems, { title = "-" }) | |
end | |
table.insert(menuItems, { title = "Trim Whitespace and Formatting", shortcut = "w", fn = copyTrimmedText }) | |
table.insert(menuItems, { title = "Make Lowercase", shortcut = "l", fn = function() hs.pasteboard.writeObjects(text:lower()) end }) | |
table.insert(menuItems, { title = "Make Uppercase", shortcut = "u", fn = function() hs.pasteboard.writeObjects(text:upper()) end }) | |
table.insert(menuItems, { title = "Remove Non-Alphanumeric", fn = copyAlphanumericText }) | |
table.insert(menuItems, { title = "Find and Replace…", fn = copyTextWithReplacement }) | |
table.insert(menuItems, { title = "Edit Text…", fn = copyEnteredText }) | |
table.insert(menuItems, { title = "Evaluate JavaScript…", shortcut = "j", fn = copyEnteredJavaScript }) | |
table.insert(menuItems, { title = "Copy as File URL", fn = function() | |
url = hs.fs.urlFromPath(text) | |
if url then hs.pasteboard.writeDataForUTI(nil, "public.file-url", url) end | |
end }) | |
table.insert(menuItems, { title = "-" }) | |
table.insert(menuItems, { title = "UUID: " .. uuid, fn = function() hs.pasteboard.writeObjects(uuid) end }) | |
table.insert(menuItems, { title = "Base64", menu = { | |
{ title = "Encode to Base64", fn = copyBase64Encoded }, | |
{ title = "Decode to Base64", fn = copyBase64Decoded } | |
} }) | |
-- table.insert(menuItems, { title = "-" }) | |
--table.insert(menuItems, { title = "Format JSON", fn = copyFormattedJSON }) | |
table.insert(menuItems, { title = "-" }) | |
end | |
-- url = hs.pasteboard.readURL() | |
fileURL = hs.pasteboard.readDataForUTI(nil, "public.file-url") | |
if fileURL then | |
path = hs.http.urlParts(fileURL)["fileSystemRepresentation"] | |
if path then | |
attrs = hs.fs.attributes(path) | |
fileInfo = "" | |
hs.task.new( | |
"/usr/bin/file", | |
function(exitCode, stdOut, stdErr) | |
fileInfo = stdOut | |
end, | |
{ "-b", path } | |
):start():waitUntilExit() | |
table.insert(menuItems, { title = "File: " .. path, disabled = true }) | |
table.insert(menuItems, { title = attrs["size"] .. " bytes", disabled = true }) | |
table.insert(menuItems, { title = fileInfo, disabled = true }) | |
table.insert(menuItems, { title = "-" }) | |
end | |
end | |
image = hs.pasteboard.readImage() | |
if image and fileURL == null then | |
fullSize = image:size() | |
-- hs.alert.show("Image " .. math.tointeger(hs.inspect(fullSize["h"]))) | |
-- hs.alert.show(hs.inspect(fullSize["h"])) | |
resizedImage = image:copy():size({ w = 500, h = 500 }) | |
if resizedImage then | |
table.insert(menuItems, { title = "Image " .. fullSize["w"] .. " × " .. fullSize["h"], disabled = true }) | |
table.insert(menuItems, { title = "", image = resizedImage }) | |
table.insert(menuItems, { title = "Optimize Image…", fn = compressImageCommand }) | |
table.insert(menuItems, { title = "Resize Image…", fn = resizeImageCommand }) | |
table.insert(menuItems, { title = "Copy as data:image/png", fn = copyImageAsURL }) | |
table.insert(menuItems, { title = "-" }) | |
end | |
end | |
table.insert(menuItems, { title = hs.inspect(hs.pasteboard.typesAvailable()), disabled = true }) | |
table.insert(menuItems, { title = hs.inspect(hs.pasteboard.contentTypes()), disabled = true }) | |
--table.insert(menuItems, { title = hs.inspect(hs.pasteboard.changeCount()), disabled = true }) | |
return menuItems | |
end) | |
menubar:popupMenu(hs.mouse.absolutePosition()) | |
end | |
hs.hotkey.bind({"control"}, "escape", showClipboardMenu) | |
function openWebBrowser(url) | |
local init_w = 400 | |
local init_h = 800 | |
local screen = hs.mouse.getCurrentScreen() | |
local init_x = (screen:fullFrame().w/2) - (init_w/2) | |
local init_y = (screen:fullFrame().h/3) - (init_h/3) | |
preferences = { developerExtrasEnabled = true } | |
browser = hs.webview.newBrowser(hs.geometry.rect(init_x,init_y,init_w,init_h), preferences) | |
browser:userAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1") | |
browser:shadow(true) | |
browser:allowNavigationGestures(true) | |
browser:url(url) | |
browser:show():bringToFront(false) | |
end | |
function showChoreMenu() | |
stopSpeaking() | |
hs.alert.closeAll() | |
menubar = hs.menubar.new(false) | |
menubar:setMenu(function() | |
menuItems = {} | |
clipboardText = trimText(hs.pasteboard.readString()) | |
table.insert(menuItems, { title = "DuckDuckGo…", fn = function() openWebBrowser("https://duckduckgo.com") end }) | |
table.insert(menuItems, { title = "Google…", fn = function() openWebBrowser("https://google.com") end }) | |
table.insert(menuItems, { title = "Open URL…", fn = function() | |
button, url = hs.dialog.textPrompt("Enter a URL", "A web browser will open at the URL you enter.", "", "OK", "Cancel") | |
if button == "Cancel" then | |
return | |
end | |
openWebBrowser(url) | |
end }) | |
if clipboardText and clipboardText:find("^https?:") then | |
table.insert(menuItems, { title = "Open " .. clipboardText, fn = function() | |
openWebBrowser(clipboardText) | |
end }) | |
else | |
table.insert(menuItems, { title = "Open URL on Clipboard…", disabled = true }) | |
end | |
table.insert(menuItems, { title = "-" }) | |
table.insert(menuItems, { title = "asdf list…", fn = function() | |
asdfList = "" | |
hs.task.new( | |
"/opt/homebrew/bin/fish", | |
function(exitCode, stdOut, stdErr) | |
asdfList = stdOut .. stdErr | |
end, | |
{ "-c", "cd && make list" } | |
):start():waitUntilExit() | |
hs.alert.show(asdfList, 100) | |
end | |
}) | |
table.insert(menuItems, { title = "-" }) | |
table.insert(menuItems, { title = "Reload Hammerspoon…", fn = function() hs.reload() end }) | |
return menuItems | |
end) | |
menubar:popupMenu(hs.mouse.absolutePosition()) | |
end | |
hs.hotkey.bind({"control", "shift"}, "escape", showChoreMenu) |
Author
RoyalIcing
commented
Apr 16, 2021
To update, run:
curl -o ~/.hammerspoon/init.lua https://gist.githubusercontent.com/BurntCaramel/e896c44d0a764d197a8673e981da89de/raw/init.lua
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment