Lua code snippets
Code snippets
Code snippets provided on this page may prove useful as inspiration or straight-up copy-paste.
Per-layout binds
local function layout_bind(bind_table)
return function ()
local workspace = hl.get_active_special_workspace() or
hl.get_active_workspace()
if not workspace then
return
end
local layout = workspace.tiled_layout
if bind_table[layout] then
hl.dispatch(bind_table[layout])
end
end
endUsage:
hl.bind("SUPER + A", layout_bind({
scrolling = hl.dsp.layout("swapcol l"), -- Scrolling: swap column with left one
dwindle = hl.dsp.layout("swapsplit"), -- Dwindle: swap window split
monocle = hl.dsp.layout("cycleprev"), -- Monocle and master: cycle prev window
master = hl.dsp.layout("cycleprev"),
}))
hl.bind("SUPER + D", layout_bind({
scrolling = hl.dsp.layout("swapcol r"), -- Scrolling: swap column with right one
dwindle = hl.dsp.layout("togglesplit"), -- Dwindle: toggle window split
monocle = hl.dsp.layout("cyclenext"), -- Monocle and master: cycle next window
master = hl.dsp.layout("cyclenext"),
}))Minimize windows using special workspaces
This approach uses special workspaces to mimic the “minimize window” function, by using a single keybind to toggle the minimized state. Note that one keybind can only handle one window.
hl.bind("SUPER + X", function ()
if hl.get_workspace("special:minimized") then
hl.dispatch(hl.dsp.window.move({ workspace = hl.get_active_workspace(), window = "tag:minimized" }))
hl.dispatch(hl.dsp.window.clear_tags({ window = "tag:minimized" }))
else
hl.dispatch(hl.dsp.window.tag({ tag = "minimized", window = hl.get_active_window() }))
hl.dispatch(hl.dsp.window.move({ workspace = "special:minimized", follow = false }))
end
end)Smart gaps
To replicate “smart gaps”/“no gaps when only” from other WMs/compositors:
hl.workspace_rule({ workspace = "w[tv1]", gaps_out = 0, gaps_in = 0 })
hl.workspace_rule({ workspace = "f[1]", gaps_out = 0, gaps_in = 0 })
hl.window_rule({ match = { float = false, workspace = "w[tv1]" }, border_size = 0 })
hl.window_rule({ match = { float = false, workspace = "w[tv1]" }, rounding = 0 })
hl.window_rule({ match = { float = false, workspace = "f[1]" }, border_size = 0 })
hl.window_rule({ match = { float = false, workspace = "f[1]" }, rounding = 0 })Smart gaps (ignoring special workspaces)
You can combine workspace selectors for more fine-grained control, for example, to ignore special workspaces:
hl.workspace_rule({ workspace = "w[tv1]s[false]", gaps_out = 0, gaps_in = 0 })
hl.workspace_rule({ workspace = "f[1]s[false]", gaps_out = 0, gaps_in = 0 })
hl.window_rule({ match = { float = false, workspace = "w[tv1]s[false]" }, border_size = 0 })
hl.window_rule({ match = { float = false, workspace = "w[tv1]s[false]" }, rounding = 0 })
hl.window_rule({ match = { float = false, workspace = "f[1]s[false]" }, border_size = 0 })
hl.window_rule({ match = { float = false, workspace = "f[1]s[false]" }, rounding = 0 })Float browsers’ extension windows
All known browsers have the annoying “feature” of setting extension window titles to the browser’s own name. This function listens to opened windows and floats the matches:
hl.on("window.open", function(w)
if w.class ~= "firefox" then return end
if w.initial_title ~= "Mozilla Firefox" then return end
local ff_windows = hl.get_windows({ class = "firefox" })
if #ff_windows <= 1 then return end
hl.dispatch(hl.dsp.window.float({ action = "set", window = w }))
local sub
sub = hl.on("window.title", function(tw)
if tw.address ~= w.address then return end
if tw.title == ""
or tw.title == "Mozilla Firefox"
or tw.title == "about:blank"
or tw.title:match("^about:.*Mozilla Firefox$") then return end
sub:remove()
if tw.title:match("^Extension:") then
hl.dispatch(hl.dsp.window.resize({ x = 800, y = 600, window = tw }))
hl.dispatch(hl.dsp.window.center({ window = tw }))
hl.dispatch(hl.dsp.focus({ window = tw }))
else
hl.dispatch(hl.dsp.window.float({ action = "unset", window = tw }))
end
end)
end)Config versioning
Some updates add breaking changes, which can be anticipated by looking at the Hyprland version.
You can make your configs conditional using hl.version(), e.g.:
if hl.version() == "0.55.2" then
hl.config({
general = {
changed_property = "value"
}
})
else
hl.notification.create({
text = "You're using: ".. hl.version(),
timeout = 10000
})
end“Starts with” helper function
local function starts_with(string, prefix)
return string:find(prefix, 1, true) == 1
endToggle animations/blur/etc hotkey
Easily toggling animations and other effects with a bind could be useful for temporarily having less distractions, or for saving battery on a laptop:
hl.bind("SUPER + F1", function ()
local game_mode = (hl.get_config("animations.enabled") == false)
if game_mode then
hl.exec_cmd("hyprctl reload")
return
end
hl.config({
general = {
gaps_in = 0, gaps_out = 0, -- Disable gaps
border_size = 0,
},
animations = {
enabled = false, -- Disable animations
},
-- Disable blur, shadow and window rounding
decoration = {
shadow = { enabled = false },
blur = { enabled = false },
rounding = 0,
}
})
end)Per workspace layouts
You can use workspace rules to set per-workspace layouts:
hl.workspace_rule({ workspace = "2", layout = "scrolling" })
hl.workspace_rule({ workspace = "3", layout = "dwindle" })Cycle layout for current workspace
To change the layout of the current workspace, you can use this bind:
hl.bind("SUPER + tab", function ()
local layouts = { "scrolling", "dwindle", "master", "monocle" }
local workspace = hl.get_active_workspace()
if hl.get_active_special_workspace() then
workspace = hl.get_active_special_workspace()
end
local next_layout = "dwindle"
if not workspace then
return
end
for i = 1, #layouts do
if layouts[i] == workspace.tiled_layout then
local next_layout_idx = (i % #layouts) + 1
next_layout = layouts[next_layout_idx]
break
end
end
if workspace.special then
hl.workspace_rule({ workspace = tostring(workspace.name), layout = next_layout })
else
hl.workspace_rule({ workspace = tostring(workspace.id), layout = next_layout })
end
end)Windows Magnifier-like cursor zoom
These binds adjust the zoom factor, which zooms into your entire screen and moves to keep the cursor in view. This acts similarly to Windows Magnifier, in its full-screen mode.
local MAX_ZOOM = 3
local MIN_ZOOM = 1
local ZOOM_TOGGLE_FACTOR = 1.5
---@param offset number
---@return nil
local function zoom(offset)
local current = hl.get_config("cursor.zoom_factor")
if offset ~= nil then
current = current + offset
elseif current ~= MIN_ZOOM then
current = MIN_ZOOM
else
current = ZOOM_TOGGLE_FACTOR
end
current = math.max(MIN_ZOOM, math.min(MAX_ZOOM, current))
hl.config({ cursor = { zoom_factor = current } })
end
hl.bind("SUPER + Z", zoom)
hl.bind("SUPER + KP_ADD", function()
zoom(0.5)
end)
hl.bind("SUPER + minus", function()
zoom(-0.5)
end)Move focus between tiled and floating windows
This functions similarly to the i3 focus mode_toggle bind.
hl.bind("SUPER + space", function()
hl.dispatch(hl.dsp.window.cycle_next({
floating = not hl.get_active_window().floating
}))
end, { description = "Switch focus between tiled and floating windows" })Vim-like keymaps
Hyprland has so many features that you might run out of keys on your keyboard if you want to bind them all. Rest assured, you can utilize submaps to create keymaps if you want more, and they’re also easier to press than a single bind with many modidiers.
Here’s an example of managing window groups this way:
hl.bind("SUPER + G", hl.dsp.submap("group_management"), { description = "Enter a group management submap" })
local map = function(key, action, description)
hl.bind(key, function()
hl.dispatch(action)
hl.dispatch(hl.dsp.submap("reset"))
end, { description = description })
end
hl.define_submap("group_management", function()
map("g", hl.dsp.group.toggle(), "Toggle window group")
map("h", hl.dsp.window.move({ into_group = "l" }), "Move window into a group on the left")
map("j", hl.dsp.window.move({ into_group = "d" }), "Move window into a group on the bottom")
map("k", hl.dsp.window.move({ into_group = "u" }), "Move window into a group on the top")
map("l", hl.dsp.window.move({ into_group = "r" }), "Move window into a group on the right")
map("e", hl.dsp.window.move({ out_of_group = true }), "Move window out of group")
map("n", hl.dsp.group.next(), "Next window in group")
map("p", hl.dsp.group.prev(), "Previous window in group")
map("f", hl.dsp.group.move_window(), "Move window forward in the group order")
map("b", hl.dsp.group.move_window({ forward = false }), "Move window backward in the group order")
map("t", hl.dsp.group.lock_active(), "Toggle group lock")
for i = 1, 10 do
map(tostring(i % 10), hl.dsp.group.active({ index = i }), "Focus window " .. i .. " in a group")
end
hl.bind("escape", hl.dsp.submap("reset"), { description = "Quit submap" })
end)