Skip to content

Instantly share code, notes, and snippets.

@RoyalIcing
Last active November 20, 2023 03:48
Show Gist options
  • Save RoyalIcing/e896c44d0a764d197a8673e981da89de to your computer and use it in GitHub Desktop.
Save RoyalIcing/e896c44d0a764d197a8673e981da89de to your computer and use it in GitHub Desktop.
My Hammerspoon config
-- 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)
@RoyalIcing
Copy link
Author

hs.alert.show("Config loaded")
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "R", function()
  hs.reload()
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "Left", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()
  local screen = win:screen()
  local max = screen:frame()

  f.x = max.x
  f.y = max.y
  f.w = max.w / 2
  f.h = max.h
  win:setFrame(f)
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "U", function()
  make = hs.task.new("/usr/bin/make", function(exitCode, stdOut, stdErr)
    hs.alert.show(stdOut)
  end)
  make:setWorkingDirectory(os.getenv("HOME") .. "/Work/asdf-versions/")
  make:start()
end)

hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function()
  webview = hs.webview.newBrowser()
  webview:size({w = 1000, h = 500})
  webview:shadow(true)
  webview:url("https://icing.space/guides/")
  webview:show()
  hs.alert.show("Browse away")
end)

@RoyalIcing
Copy link
Author

To update, run:

curl -o ~/.hammerspoon/init.lua https://gist.githubusercontent.com/BurntCaramel/e896c44d0a764d197a8673e981da89de/raw/init.lua

@RoyalIcing
Copy link
Author

Pressing Control-Escape opens this menu for working with the Clipboard:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment