Skip to content

Instantly share code, notes, and snippets.

@xieve
Last active January 15, 2025 14:43
Show Gist options
  • Save xieve/fc67361a2a0cb8fc23ab8369a8fc1170 to your computer and use it in GitHub Desktop.
Save xieve/fc67361a2a0cb8fc23ab8369a8fc1170 to your computer and use it in GitHub Desktop.
local wezterm = require "wezterm"
local module = {}
local data_dir = (os.getenv("XDG_DATA_HOME") or wezterm.home_dir .. "/.local/share") .. "/wezterm/"
local pidfile_path = data_dir .. "wuake_pid"
local last_exit_path = data_dir .. "wuake_last_exit"
local pid = wezterm.procinfo.pid()
-- returns file content as string or nil if file doesn't exist
local function read_file(file_path)
local f = io.open(file_path)
if f ~= nil then
local s = f:read()
f:close()
return s
end
end
local function write_file(file_path, s)
local f = io.open(file_path, "w")
f:write(s)
f:close()
end
local function read_pidfile()
return read_file(pidfile_path)
end
local function log_pidfile()
wezterm.log_info("pid: " .. (read_pidfile() or "nil"))
end
local function write_pidfile()
return write_file(pidfile_path, pid)
end
function module.setup(params)
local default_params = {
margin_left = 0,
margin_top = 0,
-- it is recommended to set either these two or width_factor and height_factor, otherwise
-- they might not behave in the way you expect.
margin_right = 0,
margin_bottom = 0,
-- width multiplier (1 = 100%)
width_factor = 1,
-- height multiplier default: (50% if margin_bottom is unset, else 100%)
height_factor = params.margin_bottom and 1 or 0.5,
-- name of the unix domain that wuake is associated with
domain = "wuake",
-- whether to create a unix domain. defaults to true except if domain was explicitly set
create_unix_domain = not params.domain,
-- config overrides for the dropdown window
config_overrides = {},
}
for key, value in pairs(default_params) do
params[key] = params[key] or value
end
if params.create_unix_domain then
params.config.unix_domains = params.config.unix_domains or {}
table.insert(params.config.unix_domains, {
name = params.domain
})
end
local function close_dropdown()
log_pidfile()
wezterm.log_info("wuake done")
-- write unix timestamp with high precision to file so we can check if wuake just exited
-- because the new invocation stole focus
write_file(last_exit_path, wezterm.time.now():format("%s%.f"))
-- faster and more reliable than wezterm.mux.get_domain(params.domain):detach():
os.exit()
end
-- update-status is fired earlier than gui-attached and thus is a tiny bit faster
-- also it directly gives us access to the window object
wezterm.on("update-status", function(window)
-- wezterm.log_info("update status: " .. wezterm.procinfo.pid() .. " " .. window:active_pane():get_domain_name())
-- run setup once per process
if wezterm.GLOBAL.wuake_setup_done then return end
-- don't run on normal windows
-- checking argv is faster than checking window:active_pane():get_domain_name()
local this_proc_info = wezterm.procinfo.get_info_for_pid(pid)
if this_proc_info.argv[2] ~= "connect" or this_proc_info.argv[3] ~= params.domain then
wezterm.log_info("not a wuake window")
else
local now = tonumber(wezterm.time.now():format("%s%.f"))
local pid_info = wezterm.procinfo.get_info_for_pid(read_pidfile() or 0)
-- wezterm.log_info(pid_info or "pid_info: nil")
local last_exit = read_file(last_exit_path)
-- wezterm.log_info("now: " .. now .. " last exit: " .. (last_exit or "nil"))
-- if no dropdown is open or was open in the last 500ms
if (
not (
pid_info
and pid_info.name == "wezterm-gui"
) and (
last_exit
and now - 0.5 > tonumber(last_exit)
)
) then
wezterm.log_info("starting wuake...")
write_pidfile()
local screens = wezterm.gui.screens()
window:set_position(screens.active.x + params.margin_right, screens.active.y + params.margin_top)
window:set_inner_size(
screens.active.width * params.width_factor - params.margin_right - params.margin_left,
screens.active.height * params.height_factor - params.margin_bottom - params.margin_top
)
window:set_config_overrides(params.config_overrides)
else
os.remove(pidfile_path)
close_dropdown()
end
end
wezterm.GLOBAL.wuake_setup_done = true
end)
wezterm.on("window-focus-changed", function(window, pane)
if pane:get_domain_name() == params.domain and not window:is_focused() then
wezterm.log_info("wuake: focus lost")
close_dropdown()
end
end)
end
return module

wuake is a native, as in lua-only, cross-platform dropdown-terminal module for wezterm.

To use it, simply drop wuake.lua next to your wezterm.lua and add this to your config:

-- wezterm.lua
local wezterm = require "wezterm"
local config = wezterm.config_builder()

require("wuake").setup {
  config = config,
}

Now you can toggle a dropdown terminal via

$ wezterm connect wuake

I suggest mapping this command to a global keybind via your desktop environment.

If you're using a tiling window manager, you probably want to do something like this:

$ wezterm connect wuake --class org.wezfurlong.wezterm.wuake

Now you can add org.wezfurlong.wezterm.wuake to your floating window exceptions and still use a normal tiled window when you open wezterm normally.

You can set config options for wuake in the table that you pass to setup:

-- wezterm.lua
require("wuake").setup {
  config = config,
  height_factor = 0.6,
  config_overrides = {
    hide_tab_bar_if_only_one_tab = true,
    window_padding = {
      left = "2cell",
      right = "2cell",
      top = "1cell",
      bottom = "1cell",
    },
  },
}

Passing config is mandatory, everything else is optional. Here are the default values:

-- wuake.lua
local default_params = {
  margin_left = 0,
  margin_top = 0,
  -- it is recommended to set either these two or width_factor and height_factor, otherwise
  -- they might not behave in the way you expect.
  margin_right = 0,
  margin_bottom = 0,
  -- width multiplier (1 = 100%)
  width_factor = 1,
  -- height multiplier default: (50% if margin_bottom is unset, else 100%)
  height_factor = params.margin_bottom and 1 or 0.5,
  -- name of the unix domain that wuake connects to. wuake will ensure that this domain exists
  domain = "wuake",
  -- config overrides for the dropdown window
  config_overrides = {},
}

So far, this has only been tested on Linux, but theoretically it should work on all platforms.

@typkrft
Copy link

typkrft commented Jan 12, 2025

This is working on macOS latest (18.3). How could I rename window to wuake? I tried using window:set_title but was getting nil value. I also tried changing the wezterm-gui string to no avail. I want to be able to find the window with my TWM.

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