Initial commit of Hammerspoon config
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
-- SETTINGS: Customize your engines and display names here
|
||||
local engines = {
|
||||
-- primary = { name = "Google", url = "https://google.ca/search?q=" },
|
||||
primary = { name = "Google/StartPage", url = "https://www.startpage.com/do/search?query=" },
|
||||
secondary = { name = "Wikipedia", url = "https://wikipedia.org/wiki/Special:Search?search="},
|
||||
tertiary = { name = "YouTube", url = "https://youtube.com/results?search_query=" },
|
||||
chatgpt = { name = "ChatGPT", url = "https://chatgpt.com/?q=" },
|
||||
maps = { name = "Maps", url = "https://www.google.com/maps/search/" },
|
||||
spotify = { name = "Spotify", url = "https://open.spotify.com/search/" }
|
||||
}
|
||||
|
||||
local closeTimeout = 1.0
|
||||
local gracePeriod = 3.0
|
||||
|
||||
-- Global variables
|
||||
searchView = nil
|
||||
searchTimer = nil
|
||||
escWatcher = nil
|
||||
clickOutsideWatcher = nil
|
||||
|
||||
local timeSinceLeft = 0
|
||||
local hasEnteredWindow = false
|
||||
local autoKillTime = 0
|
||||
|
||||
------------------------------------------------------------
|
||||
-- CLOSE SEARCH
|
||||
------------------------------------------------------------
|
||||
local function closeSearch()
|
||||
if searchView then searchView:delete() searchView = nil end
|
||||
if searchTimer then searchTimer:stop() searchTimer = nil end
|
||||
if escWatcher then escWatcher:stop() escWatcher = nil end
|
||||
if clickOutsideWatcher then clickOutsideWatcher:stop() clickOutsideWatcher = nil end
|
||||
|
||||
timeSinceLeft = 0
|
||||
hasEnteredWindow = false
|
||||
autoKillTime = 0
|
||||
end
|
||||
|
||||
------------------------------------------------------------
|
||||
-- PERFORM SEARCH
|
||||
------------------------------------------------------------
|
||||
local function performSearch(engine)
|
||||
hs.alert.show("Searching " .. engine.name .. "...", 1)
|
||||
|
||||
hs.eventtap.keyStroke({"cmd"}, "c")
|
||||
|
||||
hs.timer.doAfter(0.2, function()
|
||||
|
||||
local query = hs.pasteboard.getContents()
|
||||
if not query or query == "" then return end
|
||||
|
||||
closeSearch()
|
||||
|
||||
-- RESET TRACKERS FOR NEW SEARCH
|
||||
timeSinceLeft = 0
|
||||
hasEnteredWindow = false
|
||||
autoKillTime = 0
|
||||
|
||||
local url = engine.url .. hs.http.encodeForQuery(query)
|
||||
|
||||
local screen = hs.mouse.getCurrentScreen()
|
||||
local screenFrame = screen:frame()
|
||||
|
||||
local width, height = 500, 600
|
||||
local x = screenFrame.x + (screenFrame.w - width) / 2
|
||||
local y = screenFrame.y + (screenFrame.h - height) / 2
|
||||
|
||||
searchView = hs.webview.new({
|
||||
x = x,
|
||||
y = y,
|
||||
w = width,
|
||||
h = height
|
||||
})
|
||||
|
||||
searchView:windowStyle({"titled", "utility", "closable", "resizable"})
|
||||
searchView:level(hs.drawing.windowLevels.floating)
|
||||
|
||||
searchView:userAgent(
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)"
|
||||
)
|
||||
|
||||
searchView:url(url)
|
||||
searchView:show()
|
||||
searchView:bringToFront()
|
||||
|
||||
--------------------------------------------------------
|
||||
-- ESC CLOSE
|
||||
--------------------------------------------------------
|
||||
escWatcher = hs.eventtap.new(
|
||||
{hs.eventtap.event.types.keyDown},
|
||||
function(event)
|
||||
|
||||
if event:getKeyCode() == 53 then
|
||||
closeSearch()
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
):start()
|
||||
|
||||
--------------------------------------------------------
|
||||
-- CLICK OUTSIDE CLOSE
|
||||
--------------------------------------------------------
|
||||
clickOutsideWatcher = hs.eventtap.new(
|
||||
{hs.eventtap.event.types.leftMouseDown},
|
||||
function()
|
||||
|
||||
if not searchView then return false end
|
||||
|
||||
local mousePos = hs.mouse.absolutePosition()
|
||||
local winFrame = searchView:frame()
|
||||
|
||||
local isOutside =
|
||||
mousePos.x < winFrame.x or
|
||||
mousePos.x > (winFrame.x + winFrame.w) or
|
||||
mousePos.y < winFrame.y or
|
||||
mousePos.y > (winFrame.y + winFrame.h)
|
||||
|
||||
if isOutside then closeSearch() end
|
||||
|
||||
return false
|
||||
end
|
||||
):start()
|
||||
|
||||
--------------------------------------------------------
|
||||
-- AUTO CLOSE TIMER
|
||||
--------------------------------------------------------
|
||||
searchTimer = hs.timer.doEvery(0.1, function()
|
||||
|
||||
if not searchView then return end
|
||||
|
||||
local mousePos = hs.mouse.absolutePosition()
|
||||
local winFrame = searchView:frame()
|
||||
|
||||
local isOutside =
|
||||
mousePos.x < winFrame.x or
|
||||
mousePos.x > (winFrame.x + winFrame.w) or
|
||||
mousePos.y < winFrame.y or
|
||||
mousePos.y > (winFrame.y + winFrame.h)
|
||||
|
||||
if not isOutside then
|
||||
hasEnteredWindow = true
|
||||
timeSinceLeft = 0
|
||||
else
|
||||
if hasEnteredWindow then
|
||||
|
||||
timeSinceLeft = timeSinceLeft + 0.1
|
||||
|
||||
if timeSinceLeft >= closeTimeout then
|
||||
closeSearch()
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
autoKillTime = autoKillTime + 0.1
|
||||
|
||||
if autoKillTime >= gracePeriod then
|
||||
closeSearch()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
end)
|
||||
end
|
||||
|
||||
------------------------------------------------------------
|
||||
-- RIGHT CLICK TRIGGERS
|
||||
------------------------------------------------------------
|
||||
if clickWatcher then clickWatcher:stop() end
|
||||
|
||||
clickWatcher = hs.eventtap.new(
|
||||
{hs.eventtap.event.types.rightMouseDown},
|
||||
function(event)
|
||||
|
||||
local flags = event:getFlags()
|
||||
|
||||
if flags.cmd and flags.shift then
|
||||
performSearch(engines.maps)
|
||||
return true
|
||||
|
||||
elseif flags.alt and flags.shift then
|
||||
performSearch(engines.spotify)
|
||||
return true
|
||||
|
||||
elseif flags.cmd then
|
||||
performSearch(engines.primary)
|
||||
return true
|
||||
|
||||
elseif flags.alt then
|
||||
performSearch(engines.secondary)
|
||||
return true
|
||||
|
||||
elseif flags.ctrl then
|
||||
performSearch(engines.tertiary)
|
||||
return true
|
||||
|
||||
elseif flags.shift then
|
||||
performSearch(engines.chatgpt)
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
):start()
|
||||
|
||||
------------------------------------------------------------
|
||||
-- CMD + ENTER → OPEN CURRENT PAGE
|
||||
------------------------------------------------------------
|
||||
hs.hotkey.bind({"cmd"}, "return", function()
|
||||
|
||||
if searchView then
|
||||
|
||||
local currentPage = searchView:url()
|
||||
|
||||
if currentPage then
|
||||
hs.urlevent.openURL(currentPage)
|
||||
else
|
||||
hs.alert.show("No page loaded")
|
||||
end
|
||||
|
||||
else
|
||||
hs.alert.show("No search window open")
|
||||
end
|
||||
|
||||
end)
|
||||
Reference in New Issue
Block a user