-- Hyprland configuration, in the style of Protesilaos Stavrou's Sway config. -- Ref: https://github.com/protesilaos/dotfiles/tree/master/wayland-tc/.config/sway -- -- Prot runs Sway (i3-style, manual tiling); this is Hyprland (dwindle, -- dynamic tiling). Bindings and colours are carried over faithfully; the -- places where Hyprland has no equivalent are marked "SUBSTITUTION" below. --------------------- ---- MY PROGRAMS ---- --------------------- -- Prot uses gnome-terminal. SUBSTITUTION: ghostty, which is already -- installed here (and you said no kitty). local terminal = "ghostty" local fileManager = "dolphin" -- Prot's exact wofi invocation, minus the `| xargs swaymsg exec --` pipe, -- which exists only to hand the command back to Sway. Hyprland execs directly. local menu = "wofi -G -I -i -n --show drun" -- Prot points at ~/Pictures/light.jpg, which does not exist here. -- SUBSTITUTION: one of your own wallpapers. Change this one string to swap it. local wallpaper = os.getenv("HOME") .. "/Pictures/wallhaven-8gdrq2_2560x1440.png" local font = "FiraCode Nerd Font" ------------------ ---- MONITORS ---- ------------------ hl.monitor({ output = "", mode = "preferred", position = "auto", scale = "auto", }) ------------------- ---- AUTOSTART ---- ------------------- hl.on("hyprland.start", function() hl.exec_cmd("waybar") hl.exec_cmd("mako") hl.exec_cmd("swaybg -m fill -i " .. wallpaper) hl.exec_cmd("hypridle") hl.exec_cmd("/usr/libexec/kf6/polkit-kde-authentication-agent-1") end) ------------------------------- ---- ENVIRONMENT VARIABLES ---- ------------------------------- hl.env("XCURSOR_SIZE", "24") hl.env("HYPRCURSOR_SIZE", "24") -- Qt/KDE apps (dolphin, konsole) render natively rather than through XWayland. hl.env("QT_QPA_PLATFORM", "wayland") hl.env("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1") hl.env("MOZ_ENABLE_WAYLAND", "1") ----------------------- ---- LOOK AND FEEL ---- ----------------------- -- Prot's colours, from his 'modus-themes' for Emacs: -- #ffffff white focused border (matches mako border-color) -- #f78fe7 magenta focused-but-inactive border -- #575757 grey unfocused border -- #a60000 red urgent -- #ffa200 orange indicator -- -- SUBSTITUTION: Sway's `client.*` lines set five colours per window state -- (border, background, text, indicator, child_border) because Sway draws -- title bars. Hyprland has no title bars, so only the border colours carry -- over. The backgrounds/text/indicator values have nowhere to go, and -- Hyprland has no global "urgent" border colour at all. hl.config({ general = { -- Prot: `gaps inner 10px` / `gaps outer 0` gaps_in = 5, -- Hyprland counts this per-edge, so 5 => 10px between windows gaps_out = 0, -- Prot: `default_border pixel 3` border_size = 2, col = { active_border = "rgba(ffffffff)", inactive_border = "rgba(575757ff)", }, resize_on_border = false, allow_tearing = false, layout = "dwindle", }, decoration = { -- Prot's Sway config has no rounding, shadows or blur. Keeping that: -- square corners, no effects. rounding = 0, active_opacity = 1.0, inactive_opacity = 1.0, shadow = { enabled = false }, blur = { enabled = false }, }, animations = { enabled = true, }, -- Prot: `workspace_auto_back_and_forth yes` binds = { workspace_back_and_forth = true, }, dwindle = { preserve_split = true, }, misc = { force_default_wallpaper = 0, disable_hyprland_logo = true, }, }) -- Groups are Hyprland's equivalent of Sway's tabbed layout (see $mod+t below). -- Coloured to match Prot's focused/unfocused window states. hl.config({ group = { col = { border_active = "rgba(ffffffff)", border_inactive = "rgba(575757ff)", }, groupbar = { enabled = true, font_family = font, font_size = 11, col = { active = "rgba(2f337fff)", inactive = "rgba(575757ff)", }, }, }, }) hl.curve("easeOutQuint", { type = "bezier", points = { {0.23, 1}, {0.32, 1} } }) hl.animation({ leaf = "global", enabled = true, speed = 8, bezier = "easeOutQuint" }) hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" }) hl.animation({ leaf = "workspaces", enabled = true, speed = 4, bezier = "easeOutQuint", style = "slide" }) --------------- ---- INPUT ---- --------------- hl.config({ input = { -- Prot uses `xkb_layout "us,gr"` with win+space to toggle, because he -- writes Greek. SUBSTITUTION: us only, matching your current -- localectl setting. Add ",gr" and the kb_options line back if you -- ever want the second layout. kb_layout = "us", -- Prot: `focus_follows_mouse yes` follow_mouse = 1, sensitivity = 0, }, }) --------------------- ---- KEYBINDINGS ---- --------------------- local mod = "SUPER" -- Prot's home-row direction keys, like vim. local left, down, up, right = "H", "J", "K", "L" hl.bind(mod .. " + Return", hl.dsp.exec_cmd(terminal)) hl.bind(mod .. " + Q", hl.dsp.window.close()) hl.bind(mod .. " + D", hl.dsp.exec_cmd(menu)) hl.bind(mod .. " + E", hl.dsp.exec_cmd(fileManager)) hl.bind(mod .. " + SHIFT + C", hl.dsp.exec_cmd("hyprctl reload")) -- Prot wraps exit in a swaynag confirmation prompt. SUBSTITUTION: Hyprland -- has no swaynag, so this exits immediately with no confirmation dialog. hl.bind(mod .. " + SHIFT + E", hl.dsp.exit()) -- ADDITION: Prot's config has no lock binding (his swayidle block is -- commented out). $mod+L is taken by "focus right", so lock goes on Escape. hl.bind(mod .. " + Escape", hl.dsp.exec_cmd("hyprlock")) -- Move focus, vim keys and arrows both hl.bind(mod .. " + " .. left, hl.dsp.focus({ direction = "left" })) hl.bind(mod .. " + " .. down, hl.dsp.focus({ direction = "down" })) hl.bind(mod .. " + " .. up, hl.dsp.focus({ direction = "up" })) hl.bind(mod .. " + " .. right, hl.dsp.focus({ direction = "right" })) hl.bind(mod .. " + left", hl.dsp.focus({ direction = "left" })) hl.bind(mod .. " + down", hl.dsp.focus({ direction = "down" })) hl.bind(mod .. " + up", hl.dsp.focus({ direction = "up" })) hl.bind(mod .. " + right", hl.dsp.focus({ direction = "right" })) -- Move the focused window, same keys plus Shift hl.bind(mod .. " + SHIFT + " .. left, hl.dsp.window.move({ direction = "left" })) hl.bind(mod .. " + SHIFT + " .. down, hl.dsp.window.move({ direction = "down" })) hl.bind(mod .. " + SHIFT + " .. up, hl.dsp.window.move({ direction = "up" })) hl.bind(mod .. " + SHIFT + " .. right, hl.dsp.window.move({ direction = "right" })) hl.bind(mod .. " + SHIFT + left", hl.dsp.window.move({ direction = "left" })) hl.bind(mod .. " + SHIFT + down", hl.dsp.window.move({ direction = "down" })) hl.bind(mod .. " + SHIFT + up", hl.dsp.window.move({ direction = "up" })) hl.bind(mod .. " + SHIFT + right", hl.dsp.window.move({ direction = "right" })) -- Workspaces for i = 1, 10 do local key = i % 10 -- 10 maps to key 0 hl.bind(mod .. " + " .. key, hl.dsp.focus({ workspace = i })) hl.bind(mod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i })) end ---- Layout ---- -- Prot: `$mod+s split toggle` — flips the current split's orientation. hl.bind(mod .. " + S", hl.dsp.layout("togglesplit")) -- Prot: `$mod+t layout toggle tabbed split`. -- SUBSTITUTION: Hyprland calls this a "group"; the groupbar above is its -- tab bar. hl.bind(mod .. " + T", hl.dsp.group.toggle()) -- Sway cycles tabs with the ordinary focus keys; in Hyprland those navigate -- out of the group instead, so grouped windows need their own bindings. hl.bind(mod .. " + Tab", hl.dsp.group.next()) hl.bind(mod .. " + SHIFT + Tab", hl.dsp.group.prev()) hl.bind(mod .. " + F", hl.dsp.window.fullscreen()) hl.bind(mod .. " + SHIFT + space", hl.dsp.window.float({ action = "toggle" })) -- Prot: `floating_modifier $mod normal` hl.bind(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true }) hl.bind(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) ---- ADDITIONS (not in Prot's config) ---- -- Screenshots. Requires grim and slurp. hl.bind("Print", hl.dsp.exec_cmd("grim - | wl-copy")) hl.bind("SHIFT + Print", hl.dsp.exec_cmd("grim -g \"$(slurp)\" - | wl-copy")) -- Media keys hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true, repeating = true }) hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true, repeating = true }) hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true }) hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true }) hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true }) hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true }) -------------------------------- ---- WINDOWS AND WORKSPACES ---- -------------------------------- hl.window_rule({ name = "suppress-maximize-events", match = { class = ".*" }, suppress_event = "maximize", }) hl.window_rule({ -- Fix some dragging issues with XWayland name = "fix-xwayland-drags", match = { class = "^$", title = "^$", xwayland = true, float = true, fullscreen = false, pin = false, }, no_focus = true, }) hl.window_rule({ name = "float-pavucontrol", match = { class = "^(pavucontrol)$" }, float = true, }) hl.window_rule({ name = "float-polkit-agent", match = { class = "^(org\\.kde\\.polkit-kde-authentication-agent-1)$" }, float = true, })