Files
aibrashandClaude Sonnet 5.5 64aba7caf4 Theme-linked glow and terminal, tabs, fonts and layout tweaks
- Real window glow (decoration.glow) plus borders and tab bar take their
  colours from DMS (hypr/dms/colors.lua); Hyprland reloads when DMS changes
  the theme.
- kitty: colours and tab colours come from DMS (dank-theme/dank-tabs),
  no more hard-coded palette.
- Group (tab) mode: Mod+W tabs the whole workspace, thinner tab bar.
- Fullscreen programs keep the DMS bar (Mod+F maximizes, Mod+Shift+F is real
  fullscreen); per-monitor workspace switching on Mod+Tab.
- Less jelly animations, smaller gaps, weaker glow.
- JetBrains Mono is the default monospace font (fontconfig + install.sh).
- DMS settings: purple theme, popup transparency 0.98.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 16:12:34 +05:00

348 lines
19 KiB
Lua

-- Hyprland 0.57 (Lua). Clean dark work setup; shell = DankMaterialShell.
pcall(require, "dms.colors") -- DMS theme colours; DMS rewrites that file on every theme change and Hyprland reloads when it does
-- Theme colours as plain hex, read from the file DMS regenerates (fallbacks = the pink theme).
local function dms_hex(key, fallback)
local f = io.open(os.getenv("HOME") .. "/.config/hypr/dms/colors.lua")
local t = f and f:read("*a") or ""
if f then f:close() end
return t:match("%f[%w_]" .. key .. '%s*=%s*"rgb%((%x%x%x%x%x%x)%)"') or fallback
end
local function lighten(hex, k) -- mix a colour with white
local function m(c) return math.floor(c + (255 - c) * k + 0.5) end
return string.format("%02x%02x%02x", m(tonumber(hex:sub(1, 2), 16)), m(tonumber(hex:sub(3, 4), 16)), m(tonumber(hex:sub(5, 6), 16)))
end
local primary = dms_hex("active_border", "e91e63")
local outline = dms_hex("inactive_border", "9f8c8e")
local light = lighten(primary, 0.55)
-- Fallback login session: GNOME (via GDM).
local terminal = "kitty"
local mod = "SUPER"
----------------
---- OUTPUT ----
----------------
-- eDP-1 at 1.25 scale is 1536 logical px wide, so HDMI sits right of it.
hl.monitor({ output = "eDP-1", mode = "preferred", position = "0x0", scale = 1.25 })
hl.monitor({ output = "HDMI-A-1", mode = "preferred", position = "1536x0", scale = 1 })
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = "auto" })
-----------------
---- STARTUP ----
-----------------
hl.env("XCURSOR_SIZE", "24")
hl.env("HYPRCURSOR_SIZE", "24")
hl.env("XDG_CURRENT_DESKTOP", "Hyprland")
hl.env("XDG_SESSION_TYPE", "wayland")
hl.env("QT_QPA_PLATFORM", "wayland;xcb")
hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto")
hl.on("hyprland.start", function()
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_TYPE")
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_TYPE")
-- portals (file pickers, screen sharing, Nautilus) need graphical-session.target, which is not started outside uwsm
hl.exec_cmd("systemctl --user start hyprland-session.target")
hl.exec_cmd("sh -c 'pgrep -x dms >/dev/null || exec dms run'")
hl.exec_cmd("systemctl --user start hyprpolkitagent")
hl.exec_cmd("hypridle")
hl.exec_cmd("nm-applet --indicator")
hl.exec_cmd("wl-paste --type text --watch cliphist store")
hl.exec_cmd("wl-paste --type image --watch cliphist store")
hl.exec_cmd(terminal .. " --class scratch")
hl.exec_cmd("sh -c 'sleep 5; flatpak info com.nextcloud.desktopclient.nextcloud >/dev/null 2>&1 && ! pgrep -f \"[n]extcloud.*--background\" >/dev/null && flatpak run com.nextcloud.desktopclient.nextcloud --background'")
hl.exec_cmd("sh -c 'sleep 5; flatpak info org.remmina.Remmina >/dev/null 2>&1 && ! pgrep -f \"[R]emmina.* -i\" >/dev/null && flatpak run org.remmina.Remmina -i'")
hl.exec_cmd("sh -c 'sleep 8; command -v geary >/dev/null && ! pgrep -x geary >/dev/null && geary --gapplication-service'")
end)
-----------------------
---- LOOK AND FEEL ----
-----------------------
hl.config({
general = {
gaps_in = 4,
gaps_out = 10,
border_size = 2,
col = {
active_border = { colors = { "rgba(" .. primary .. "ff)", "rgba(" .. light .. "ff)", "rgba(" .. primary .. "ff)" }, angle = 45 },
inactive_border = "rgba(" .. outline .. "40)",
},
resize_on_border = true,
allow_tearing = false,
layout = "dwindle",
},
decoration = {
rounding = 16,
rounding_power = 4, -- squircle corners
active_opacity = 1.0,
inactive_opacity = 0.96,
dim_inactive = true,
dim_strength = 0.06,
shadow = {
enabled = true,
range = 20,
render_power = 3,
color = 0x55000000,
color_inactive = 0x44000000,
},
glow = { -- light around the focused window, colour follows the DMS theme
enabled = true,
range = 14,
render_power = 3,
color = "rgba(" .. primary .. "55)",
color_inactive = "rgba(00000000)",
},
blur = {
enabled = true,
size = 10,
passes = 4,
noise = 0.02,
vibrancy = 0.3,
brightness = 1.05,
popups = true,
},
},
group = {
auto_group = true, -- a new window opened while a group is focused becomes another tab
insert_after_current = true,
focus_removed_window = true,
col = { border_active = "rgba(" .. primary .. "ff)", border_inactive = "rgba(" .. outline .. "40)" },
groupbar = {
enabled = true, height = 16, font_size = 10, font_family = "JetBrains Mono",
render_titles = true, gradients = true, rounding = 6, gaps_in = 2, gaps_out = 2,
indicator_height = 0, scrolling = true, middle_click_close = true,
text_color = "rgba(ffffffff)", text_color_inactive = "rgba(b8a4a9ff)",
col = { active = "rgba(" .. primary .. "dd)", inactive = "rgba(2a2024cc)" },
},
},
animations = { enabled = true },
binds = { workspace_back_and_forth = true },
dwindle = { preserve_split = true, force_split = 2 }, -- new window always opens to the right/below
xwayland = { force_zero_scaling = true }, -- crisp X11 apps on the 1.25x laptop panel
cursor = { hide_on_key_press = true, inactive_timeout = 5 },
misc = {
force_default_wallpaper = 0,
disable_hyprland_logo = true,
focus_on_activate = true,
key_press_enables_dpms = true, -- wake the screen on any key / mouse move (hypridle turns it off)
mouse_move_enables_dpms = true,
animate_manual_resizes = true,
animate_mouse_windowdragging = true,
},
})
hl.curve("easeOutQuint", { type = "bezier", points = { {0.23, 1}, {0.32, 1} } })
hl.curve("easeInOutCubic", { type = "bezier", points = { {0.65, 0.05}, {0.36, 1} } })
hl.curve("quick", { type = "bezier", points = { {0.15, 0}, {0.1, 1} } })
hl.curve("linear", { type = "bezier", points = { {0, 0}, {1, 1} } })
hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "border", enabled = true, speed = 4, bezier = "easeOutQuint" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 60, bezier = "linear", style = "loop" }) -- slowly rotating gradient border
hl.animation({ leaf = "windows", enabled = true, speed = 3.0, bezier = "easeOutQuint" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 2.6, bezier = "easeOutQuint", style = "popin 94%" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.2, bezier = "quick", style = "popin 88%" })
hl.animation({ leaf = "fade", enabled = true, speed = 2, bezier = "quick" })
hl.animation({ leaf = "fadeDim", enabled = true, speed = 2.5, bezier = "quick" })
hl.animation({ leaf = "layers", enabled = true, speed = 2.6, bezier = "easeOutQuint" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 2.6, bezier = "easeOutQuint", style = "slide" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.0, bezier = "quick", style = "fade" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 3.0, bezier = "easeInOutCubic", style = "slide" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 2.8, bezier = "easeOutQuint", style = "slidevert" })
-- No gaps/border/rounding for a single tiled window.
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({ name = "smart-gaps-1", match = { float = false, workspace = "w[tv1]" }, border_size = 0, rounding = 0 })
hl.window_rule({ name = "smart-gaps-2", match = { float = false, workspace = "f[1]" }, border_size = 0, rounding = 0 })
---------------
---- INPUT ----
---------------
hl.config({
input = {
kb_layout = "us,ru",
kb_options = "grp:alt_shift_toggle,grp_led:scroll",
follow_mouse = 2, -- click to focus; the cursor can still cross other windows
sensitivity = -0.5,
touchpad = {
natural_scroll = true,
tap_to_click = true,
tap_button_map = "lrm",
disable_while_typing = true,
middle_button_emulation = true,
},
},
})
hl.device({ name = "type:touchpad", sensitivity = 0.3 })
hl.gesture({ fingers = 3, direction = "horizontal", action = "workspace" })
---------------------
---- KEYBINDINGS ----
---------------------
local function b(keys, dsp, opts) hl.bind(keys, dsp, opts) end
local function dms(args) return hl.dsp.exec_cmd("dms ipc call " .. args) end
b(mod .. " + Return", hl.dsp.exec_cmd(terminal))
b(mod .. " + SHIFT + Q", hl.dsp.window.close())
b(mod .. " + D", dms("spotlight toggle"))
b(mod .. " + SHIFT + E", dms("powermenu toggle"))
b(mod .. " + SHIFT + P", dms("powermenu toggle"))
b(mod .. " + SHIFT + C", hl.dsp.reload_config())
b(mod .. " + N", dms("control-center toggle"))
b(mod .. " + SHIFT + N", dms("notifications toggle"))
b(mod .. " + SHIFT + V", dms("clipboard toggle"))
b(mod .. " + SHIFT + X", hl.dsp.exec_cmd("~/.config/hypr/scripts/lock.sh"))
b(mod .. " + O", hl.dsp.exec_cmd("nautilus --new-window"))
-- focus / move (vim keys + arrows)
local dirs = { h = "left", j = "down", k = "up", l = "right", left = "left", down = "down", up = "up", right = "right" }
for key, d in pairs(dirs) do
b(mod .. " + " .. key, hl.dsp.focus({ direction = d }))
b(mod .. " + SHIFT + " .. key, hl.dsp.window.move({ direction = d }))
end
-- layout
-- Mod+F maximizes (the DMS bar stays visible); Mod+Shift+F is real fullscreen without the bar.
b(mod .. " + F", hl.dsp.window.fullscreen({ mode = "maximized" }))
b(mod .. " + SHIFT + F", function()
local w = hl.get_active_window()
if not w then return end
if w.fullscreen ~= 2 then HL_FS_REAL = true end -- let the handler below leave this one alone
hl.dispatch(hl.dsp.window.fullscreen({ mode = "fullscreen" }))
end)
b(mod .. " + SHIFT + SPACE", hl.dsp.window.float({ action = "toggle" }))
b(mod .. " + C", hl.dsp.window.center())
b(mod .. " + E", hl.dsp.layout("togglesplit"))
b(mod .. " + P", hl.dsp.window.pseudo())
-- Tabs: Mod+W puts every tiled window of this workspace into one tabbed group; press it again to split them back.
-- While a group is focused, new windows join it as tabs. Mod+[ / Mod+] or the mouse wheel on the tab bar switch tabs.
function HL_toggle_tabs()
local a = hl.get_active_window()
if not a then return end
if a.group and a.group.size > 1 then
hl.dispatch(hl.dsp.group.toggle())
return
end
local wins = hl.get_windows({ workspace = a.workspace.id, floating = false, mapped = true })
if #wins < 2 then
hl.dispatch(hl.dsp.group.toggle()) -- single window: just start a group, the next windows become its tabs
return
end
if not a.group then hl.dispatch(hl.dsp.group.toggle()) end
local g = hl.get_window("address:" .. a.address).group
for _, w in ipairs(wins) do
if w.address ~= a.address and not w.group then g:add(w) end
end
hl.dispatch(hl.dsp.focus({ window = "address:" .. a.address }))
end
b(mod .. " + W", HL_toggle_tabs)
b("ALT + TAB", hl.dsp.exec_cmd("~/.config/hypr/scripts/prev-window.sh")) -- previous window (press again to go back)
b(mod .. " + ALT + TAB", hl.dsp.window.cycle_next()) -- next window on this workspace
b(mod .. " + TAB", hl.dsp.focus({ workspace = "m+1" })) -- next open workspace on THIS monitor
b(mod .. " + SHIFT + TAB", hl.dsp.focus({ workspace = "m-1" }))
b(mod .. " + mouse_down", hl.dsp.focus({ workspace = "m+1" }))
b(mod .. " + mouse_up", hl.dsp.focus({ workspace = "m-1" }))
-- workspaces 1..10
for i = 1, 10 do
local key = i % 10
b(mod .. " + " .. key, hl.dsp.focus({ workspace = i }))
b(mod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i }))
end
-- scratchpad
b(mod .. " + grave", hl.dsp.workspace.toggle_special("scratch"))
b(mod .. " + SHIFT + minus", hl.dsp.window.move({ workspace = "special:scratch" }))
b(mod .. " + minus", hl.dsp.workspace.toggle_special("scratch"))
-- mouse
b(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
b(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
-- resize the focused window without a mode: Mod+Ctrl + h/j/k/l or arrows (hold to repeat)
local rs = { h = {-40, 0}, l = {40, 0}, k = {0, -40}, j = {0, 40}, left = {-40, 0}, right = {40, 0}, up = {0, -40}, down = {0, 40} }
for key, d in pairs(rs) do
b(mod .. " + CTRL + " .. key, hl.dsp.window.resize({ x = d[1], y = d[2], relative = true }), { repeating = true })
end
-- sysadmin toolbox
b(mod .. " + S", hl.dsp.exec_cmd("kitty --class ssh-menu -o font_size=12 -o env=SSH_MENU=1 --title 'ssh menu' ~/.config/hypr/scripts/ssh-menu.sh")) -- SSH hub (fzf)
local raise = "~/.config/hypr/scripts/raise.sh "
b(mod .. " + T", hl.dsp.exec_cmd(raise .. "'^org.telegram.desktop$' flatpak run org.telegram.desktop")) -- focus if open, else launch
b(mod .. " + B", hl.dsp.exec_cmd(raise .. "'^google-chrome$' flatpak run com.google.Chrome")) -- again = next Chrome window
b(mod .. " + SHIFT + B", hl.dsp.exec_cmd("flatpak run com.google.Chrome --new-window")) -- always a new window
b(mod .. " + F1", hl.dsp.exec_cmd("~/.config/hypr/scripts/cheatsheet.sh")) -- hotkey reference
b(mod .. " + K", hl.dsp.exec_cmd(raise .. "'^org.keepassxc.KeePassXC$' keepassxc"))
b(mod .. " + SHIFT + T", hl.dsp.window.pin()) -- keep a floating window on top
b(mod .. " + bracketright", hl.dsp.group.next())
b(mod .. " + bracketleft", hl.dsp.group.prev())
-- two monitors: laptop <-> external (focus only)
b(mod .. " + comma", hl.dsp.focus({ monitor = "-1" }))
b(mod .. " + period", hl.dsp.focus({ monitor = "+1" }))
-- screenshots (grim + slurp; saved to ~/Pictures/Screenshots and copied)
local shot = "mkdir -p ~/Pictures/Screenshots; f=~/Pictures/Screenshots/$(date +%F_%H-%M-%S).png; "
b("Print", hl.dsp.exec_cmd(shot .. "grim -g \"$(slurp)\" - | tee $f | wl-copy"))
b(mod .. " + SHIFT + S", hl.dsp.exec_cmd(shot .. "grim -g \"$(slurp)\" - | tee $f | wl-copy"))
b("SHIFT + Print", hl.dsp.exec_cmd(shot .. "grim - | tee $f | wl-copy"))
b("CTRL + Print", hl.dsp.exec_cmd(shot .. "grim -g \"$(hyprctl activewindow -j | jq -r '\"\\(.at[0]),\\(.at[1]) \\(.size[0])x\\(.size[1])\"')\" - | tee $f | wl-copy"))
b(mod .. " + CTRL + C", hl.dsp.exec_cmd("hyprpicker -a")) -- color picker
b(mod .. " + SHIFT + O", hl.dsp.exec_cmd("~/.config/hypr/scripts/ocr.sh")) -- screen region -> text in clipboard
-- media / brightness / volume via DMS (shows OSD)
b("XF86AudioRaiseVolume", dms("audio increment 5"), { locked = true, repeating = true })
b("XF86AudioLowerVolume", dms("audio decrement 5"), { locked = true, repeating = true })
b("XF86AudioMute", dms("audio mute"), { locked = true })
b("XF86AudioMicMute", dms("audio micmute"), { locked = true })
b("XF86MonBrightnessUp", dms("brightness increment 5 \"\""), { locked = true, repeating = true })
b("XF86MonBrightnessDown", dms("brightness decrement 5 \"\""), { locked = true, repeating = true })
b("XF86AudioPlay", dms("mpris playPause"), { locked = true })
b("XF86AudioNext", dms("mpris next"), { locked = true })
b("XF86AudioPrev", dms("mpris previous"), { locked = true })
-- Programs that go fullscreen on their own (F11, RDP, video) keep the DMS bar visible: Hyprland shows them
-- maximized (1) while the program still believes it is fullscreen (2).
HL_FS_REAL = false
hl.on("window.fullscreen", function(w)
if not w or w.fullscreen ~= 2 then return end
if HL_FS_REAL then HL_FS_REAL = false; return end -- requested with Mod+Shift+F: keep it real
hl.dispatch(hl.dsp.window.fullscreen_state({ internal = 1, client = w.fullscreen_client, window = "address:" .. w.address }))
end)
-----------------
---- RULES ------
-----------------
hl.window_rule({ name = "suppress-maximize", match = { class = ".*" }, suppress_event = "maximize" })
hl.window_rule({
name = "xwayland-drag-fix",
match = { class = "^$", title = "^$", xwayland = true, float = true, fullscreen = false, pin = false },
no_focus = true,
})
hl.window_rule({
name = "float-utils",
match = { class = "^(pavucontrol|nm-connection-editor|blueman-manager|org.pulseaudio.pavucontrol)$" },
float = true, size = "700 500", center = true,
})
hl.window_rule({ name = "float-mpv", match = { class = "^mpv$" }, float = true })
hl.window_rule({ name = "idle-inhibit-fs", match = { class = "^(firefox|mpv|google-chrome|org.telegram.desktop)$" }, idle_inhibit = "fullscreen" })
-- no auto-lock in the middle of an RDP/VNC session
hl.window_rule({ name = "idle-inhibit-rdp", match = { class = "^(org.remmina.Remmina|com.freerdp.FreeRDP|xfreerdp)$" }, idle_inhibit = "focus" })
-- file dialogs from the portal and Chrome picture-in-picture
hl.window_rule({ name = "float-portal-dialogs", match = { class = "^xdg-desktop-portal-gtk$" }, float = true, size = "900 600", center = true })
hl.window_rule({ name = "pip", match = { title = "^Picture in picture$" }, float = true, pin = true, size = "480 270", move = "monitor_w-500 monitor_h-290" })
hl.window_rule({
name = "scratch-term",
match = { class = "^scratch$" },
workspace = "special:scratch silent", float = true, size = "1000 560", center = true,
})
hl.window_rule({ name = "float-ssh-menu", match = { class = "^ssh-menu$" }, float = true, size = "1400 780", center = true })
hl.window_rule({ name = "float-keepass", match = { class = "^org.keepassxc.KeePassXC$" }, float = true, size = "1300 860", center = true })
-- Blur the DMS surfaces (bar, launcher, popups, notifications).
hl.layer_rule({ name = "dms-blur", match = { namespace = "^dms:.*" }, blur = true, blur_popups = true, ignore_alpha = 0.3 })
hl.layer_rule({ name = "noanim-slurp", match = { namespace = "^selection$" }, no_anim = true })