iTerm2 has this great feature called the hotkey window. It's a persistent window that can be accessed from any desktop with a hotkey. When you toggle it on, the iTerm2 hotkey window shows up on top of all of your existing windows in full screen. And when you toggle it off it disappears. It's convenient because it makes a lot of the: create a terminal, do something, close the terminal, kind of workflows a lot easier.
I recently switched to Alacritty, which is a snappier and more lightweight shell. It doesn't have a hotkey window and has no plans of adding one. So, missing this feature, I came up with a hacky hotkey window for Alacritty myself.
Requirements:
Code: GitHub
If you're familiar with Yabai, you might know that you can use queries:
yabai -m query <topic> <options>
to query for information about windows, spaces, and displays.
# Example: find information about the "focused" window.
yabai -m query --windows --window
# {
# "id":142173,
# "pid":21428,
# "app":"Alacritty",
# "title":"yabai -m query --win ~",
# "frame":{
# "x":3.0000,
# "y":35.0000,
# "w":1722.0000,
# "h":1047.0000
# },
# [...]
# }
Each window has a unique id
, so we use the id
of our hotkey window to identify it. First, we
create the hotkey window and get its id
. On MacOS, an application can be opened programmatically
using the open
command.
open -a "Alacritty" -n
# -a: Application to open
# -n: Open a new instance, if one is already open
We can then get the pid
of the Alacritty shell we just created:
pgrep -n -f "Alacritty"
# -n: Select only the newest of the matching processes
# -f: Match against "full argument lists", which includes the program name
We can then query, using Yabai, for the window with the corresponding pid
to find
its window id. Once we have the window id
, we write it to a file, ~/.config/hotkey_window/window_id.txt
, for future reference.
Using hammerspoon's APIs, this all looks like:
local function createHotkeyWindow()
-- Create a new Alacritty window and get its PID.
hs.execute("open -a " .. utils.quote("Alacritty") .. " -n")
utils.sleep(1) -- wait for the application to launch
local windowPid = hs.execute('pgrep -n -f "Alacritty"')
if windowPid == nil then
print("Failed to get pid of hotkey window")
return nil
end
windowPid = math.floor(windowPid)
print("Alacritty hotkey window pid: ", windowPid)
-- Find the window id of the new window and write it to the file.
local windows = yabai.query("windows")
local windowId = nil
-- Check if any of the active windows are the hotkey window.
for _, window in ipairs(windows) do
if math.floor(window["pid"]) == windowPid then
windowId = math.floor(window["id"])
end
end
if windowId == nil then
print("Failed to get window id for hotkey window")
return nil
end
local file = io.open(HOTKEY_WINDOW_ID_FILE, "w")
if not file then
print("Failed to write to hotkey window id file")
return nil
end
file:write(windowId)
file:close()
-- Make the window: see-through, unmanaged, and full screen.
yabai.command("-m window " .. windowId .. " --opacity 0.9")
yabai.command("-m window " .. windowId .. " --toggle float")
yabai.command("-m window " .. windowId .. " --grid 1:1:0:0:1:1")
return windowId
end
Note:
yabai.*
andutils.*
are custom helper functions - see full source for details.
When we trigger our hotkey window, we first want to check if we already have one, before making a new one.
Any hotkey window we've made with have an id
that's
been stored in ~/.config/hotkey_window/window_id.txt
, so we read the id
and
then use Yabai to check if there are any windows that have that id
. If there
aren't, then our hotkey window got destroyed.
-- Check if a hotkey window has already been created.
local function exists()
local file = io.open(HOTKEY_WINDOW_ID_FILE, "r")
if not file then
return false
end
local hotkeyWindowId = math.floor(file:read("*all"))
file:close()
local windows = yabai.query("windows")
-- Check if any of the active windows are the hotkey window.
for _, window in ipairs(windows) do
local id = math.floor(window["id"])
if id == hotkeyWindowId then
return true, hotkeyWindowId
end
end
return false
end
We check whether we have a hotkey window, and make one if we don't. Now
we've got to check whether we're toggling the window on
(making it visible)
or off
(hiding it).
To determine whether the hotkey window is focused we check whether the focused window has the same id as the hotkey window.
If we're toggling on
the window we:
Otherwise, we hide the hotkey window by moving it to a space that is not currently visible.
local function toggle()
-- Query for the focused window and space before doing anything else because
-- showing/hinding the hotkey window may change the focused space.
--
-- Note: focusedWindow will be nil if there is no focused window.
local focusedSpace = yabai.query("spaces", "--space")
local focusedWindow = yabai.query("windows", "--window")
local windowExists, windowId = exists()
if not windowExists then
print("Creating new hotkey window")
windowId = createHotkeyWindow()
end
if windowId == nil then
print("Failed to toggle hotkey window.")
return
end
if focusedWindow ~= nil and focusedWindow["id"] == windowId then
-- Hide the hotkey window.
yabai.command("-m window " .. windowId .. " --space 9")
else
-- Show the hotkey window.
--
-- 1. Move it to the focused space.
-- 2. Make it focused.
-- 3. Make it full screen.
yabai.command("-m window " .. windowId .. " --space " .. math.floor(focusedSpace["index"]))
yabai.command("-m window " .. windowId .. " --focus")
yabai.command("-m window " .. windowId .. " --grid 1:1:0:0:1:1")
end
end
All that remains to be done is hook up the toggle
function to a hotkey:
hs.hotkey.bind({ "ctrl", "cmd" }, 'k', toggle)
Now, hitting CTRL+CMD+K
will toggle an Alacritty hotkey window.
I glanced over some of the details, like querying Yabai from Lua, or how this code can fit into your HammerSpoon configuration, so I've linked the full source code below. Cheers.
Code: GitHub