Files
dotfiles/.config/hypr/hyprland.lua
T
2026-07-21 15:30:05 +02:00

147 lines
7.3 KiB
Lua

-- Shared Hyprland profile. yadm deploys machine.lua for the active class.
--
-- Scope: preserve launch, media, directional focus/move, workspace, scratchpad,
-- floating, fullscreen, and window-placement muscle memory. i3's split-tree,
-- stacking/tabbed, and resize-mode behavior is intentionally deferred.
local machine = dofile(os.getenv("HOME") .. "/.config/hypr/machine.lua")
for _, monitor in ipairs(machine.monitors or {}) do
hl.monitor(monitor)
end
local main_mod = "SUPER"
local terminal = "alacritty"
local input = {
kb_layout = "us",
kb_variant = "intl",
numlock_by_default = true,
}
for key, value in pairs(machine.input or {}) do
input[key] = value
end
hl.config({
input = input,
general = {
gaps_in = 4,
-- Keep a thin black perimeter so the bottom border remains on-screen.
gaps_out = 2,
border_size = 2,
col = {
-- Muted Ember tones: visible focus without a high-brightness OLED edge.
active_border = "rgba(c9782ed9)",
inactive_border = "rgba(2a190fa6)",
},
},
decoration = {
rounding = 0,
},
animations = {
enabled = false,
},
})
-- Dynamically hide the border when a workspace has exactly one tiled app.
hl.workspace_rule({
workspace = "w[t1]",
no_border = true,
})
-- Application and session bindings.
local noctalia_ipc = "noctalia msg "
-- Tap and release Super on its own to open Noctalia's control-center home.
hl.bind(main_mod .. " + SUPER_L", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle control-center"), { release = true })
hl.bind(main_mod .. " + RETURN", hl.dsp.exec_cmd(terminal))
hl.bind(main_mod .. " + CTRL + RETURN", hl.dsp.exec_cmd(terminal .. " -e zellij"))
hl.bind(main_mod .. " + SHIFT + RETURN", hl.dsp.exec_cmd("rofi -show combi"))
hl.bind(main_mod .. " + SPACE", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle launcher"))
hl.bind(main_mod .. " + S", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle control-center"))
hl.bind(main_mod .. " + SHIFT + F", hl.dsp.exec_cmd(noctalia_ipc .. "bar-toggle main"))
hl.bind(main_mod .. " + SHIFT + R", hl.dsp.exec_cmd("hyprctl reload"))
hl.bind(main_mod .. " + CTRL + L", hl.dsp.exec_cmd("command -v hyprlock >/dev/null && hyprlock || loginctl lock-session"))
hl.bind(main_mod .. " + CTRL + SHIFT + ALT + S", hl.dsp.exec_cmd("systemctl suspend"))
hl.bind(main_mod .. " + CTRL + SHIFT + ALT + H", hl.dsp.exec_cmd("systemctl hibernate"))
hl.bind(main_mod .. " + SHIFT + C", hl.dsp.window.close())
hl.bind(main_mod .. " + CTRL + SHIFT + F", hl.dsp.exec_cmd("env LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 flameshot gui"))
-- Audio, media, brightness, and calculator keys.
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true })
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true })
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true })
hl.bind(main_mod .. " + XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
hl.bind(main_mod .. " + XF86AudioRaiseVolume", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind(main_mod .. " + XF86AudioLowerVolume", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd('light -A 5'), { locked = true })
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd('light -U 5'), { locked = true })
hl.bind(main_mod .. " + XF86MonBrightnessDown", hl.dsp.exec_cmd('light -S 0.1 && notify-send "Brightness set to min"'), { locked = true })
hl.bind(main_mod .. " + XF86MonBrightnessUp", hl.dsp.exec_cmd('light -S 100 && notify-send "Brightness set to max"'), { locked = true })
hl.bind("XF86Calculator", hl.dsp.exec_cmd("qalculate-qt"), { locked = true })
-- Retain i3's hjkl and arrow-key directional workflow.
for _, direction in ipairs({ "left", "down", "up", "right" }) do
local key = ({ left = "H", down = "J", up = "K", right = "L" })[direction]
hl.bind(main_mod .. " + " .. key, hl.dsp.focus({ direction = direction }))
hl.bind(main_mod .. " + " .. direction, hl.dsp.focus({ direction = direction }))
end
for key, direction in pairs({ H = "l", J = "d", K = "u", L = "r", LEFT = "l", DOWN = "d", UP = "u", RIGHT = "r" }) do
hl.bind(main_mod .. " + SHIFT + " .. key, hl.dsp.window.move({ direction = direction }))
end
-- Numbered workspaces match the i3 profile. Shift moves; Ctrl moves and follows.
for workspace = 1, 9 do
local key = tostring(workspace)
hl.bind(main_mod .. " + " .. key, hl.dsp.focus({ workspace = workspace }))
hl.bind(main_mod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = workspace }))
hl.bind(main_mod .. " + CTRL + " .. key, hl.dsp.window.move({ workspace = workspace, follow = true }))
end
-- Hyprland has no direct i3-style back_and_forth dispatcher; use numbered workspaces.
hl.bind(main_mod .. " + CTRL + RIGHT", hl.dsp.focus({ workspace = "+1" }))
hl.bind(main_mod .. " + CTRL + LEFT", hl.dsp.focus({ workspace = "-1" }))
-- Hyprland special workspaces are the Wayland analogue of the i3 scratchpad.
hl.bind(main_mod .. " + MINUS", hl.dsp.workspace.toggle_special("scratchpad"))
hl.bind(main_mod .. " + SHIFT + MINUS", hl.dsp.window.move({ workspace = "special:scratchpad" }))
-- Keep the i3 floating and fullscreen shortcuts. Layout-specific binds are deferred.
hl.bind(main_mod .. " + F", hl.dsp.window.fullscreen({ action = "toggle" }))
hl.bind(main_mod .. " + SHIFT + SPACE", hl.dsp.window.float({ action = "toggle" }))
hl.bind(main_mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
hl.bind(main_mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
-- i3 for_window/assign translations. Verify Wayland class/title values with `hyprctl clients`.
local function float_rule(name, match, border_size)
hl.window_rule({
name = name,
match = match,
float = true,
border_size = border_size,
})
end
hl.window_rule({ name = "thunderbird-workspace", match = { class = "^Thunderbird$" }, workspace = "9" })
float_rule("alsamixer", { title = "^alsamixer$" }, 1)
float_rule("qalculate", { title = "^Qalculate!$" })
float_rule("psensor", { class = "^Psensor$" })
float_rule("lxrandr", { title = "^lxrandr$" })
float_rule("blueman", { class = "^Blueman-manager$" })
float_rule("gparted", { class = "^GParted$" })
float_rule("pavucontrol", { class = "^pavucontrol$" })
float_rule("volume-control", { title = "^Volume Control$" })
float_rule("virtualbox", { class = ".*VirtualBox.*" })
hl.window_rule({ name = "virtualbox-manager-tiles", match = { class = ".*VirtualBox Manager.*" }, tile = true })
hl.window_rule({ name = "virtualbox-machine-tiles", match = { class = ".*VirtualBox Machine.*" }, tile = true })
-- Send music clients to the existing scratchpad without stealing startup focus.
hl.window_rule({ name = "spotify-music", match = { class = "^Spotify$" }, workspace = "special:scratchpad silent", no_initial_focus = true })
hl.window_rule({ name = "deezer-music", match = { class = "^Deezer$" }, workspace = "special:scratchpad silent", no_initial_focus = true })
hl.on("hyprland.start", function()
for _, command in ipairs(machine.startup or {}) do
hl.exec_cmd(command)
end
end)