Files
2026-05-14 18:59:23 -04:00

229 lines
6.8 KiB
Lua

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