aboutsummaryrefslogtreecommitdiff
path: root/working/hyprland-lua-port/hyprland.lua.generated
diff options
context:
space:
mode:
Diffstat (limited to 'working/hyprland-lua-port/hyprland.lua.generated')
-rw-r--r--working/hyprland-lua-port/hyprland.lua.generated674
1 files changed, 674 insertions, 0 deletions
diff --git a/working/hyprland-lua-port/hyprland.lua.generated b/working/hyprland-lua-port/hyprland.lua.generated
new file mode 100644
index 0000000..04b30b9
--- /dev/null
+++ b/working/hyprland-lua-port/hyprland.lua.generated
@@ -0,0 +1,674 @@
+-- Hyprland Configuration
+-- Translated from DWM config.def.h and sxhkdrc
+-- Craig Jennings <c@cjennings.net>
+
+-- ============================================================================
+-- Monitor Configuration
+-- ============================================================================
+-- Generated by hyprlang2lua. Review TODOs before reloading Hyprland.
+
+-- hyprlang2lua polyfills — runtime helpers reproducing
+-- hyprlang behaviour the typed Lua API doesn't expose directly.
+
+local function hl_source_glob(pattern)
+ -- 'source = path/*.conf' had hyprlang glob and inline-expand the
+ -- matches. require() can't glob, so we shell out to ls (matching
+ -- the user's brace-expansion behaviour) and dofile each result.
+ -- Paths with spaces or shell metacharacters in the directory
+ -- portion will misparse; typical ~/.config/hypr/ layouts don't
+ -- hit this. Swap to lfs.dir() or find -name if you need fancier.
+ local p = io.popen("ls " .. pattern .. " 2>/dev/null")
+ if not p then return end
+ for f in p:lines() do
+ local chunk, err = loadfile(f)
+ if chunk then chunk()
+ else io.stderr:write("hl_source_glob: " .. tostring(err) .. "\n") end
+ end
+ p:close()
+end
+
+hl.monitor({
+ output = "",
+ mode = "preferred",
+ position = "auto",
+ scale = "auto",
+})
+
+-- Waybar's strip (6px top margin + 54px bar) is reserved statically by
+-- waybar-reserve, and waybar runs with "exclusive": false. The bar's own
+-- exclusive zone would vanish and reappear on every SIGUSR2 reload (the
+-- collapse mechanism) and on hide/crash/relaunch, snapping every tiled window
+-- up and back down. The static reservation holds the clients in place; only
+-- the bar itself changes. `exec` (not exec-once) reruns it on every config
+-- reload, which is exactly when Hyprland resets dynamic reservations. The
+-- script is idempotent, and a catch-all `monitor=,addreserved,...` rule can't
+-- replace it (empty-name addreserved silently no-ops).
+--
+-- Run three times over ~0.6s, not once: on reload Hyprland clears the
+-- reservation AND re-fires this exec, and the two race. A single run that
+-- fires before the clear no-ops (reserved still looks correct), the clear then
+-- wins, and the non-exclusive bar drops off-screen. Re-applying past the clear
+-- window makes the restore reliable; the script is idempotent so extra runs are
+-- free. Applying a monitor rule (e.g. the DP-4 pin) also clears the reservation,
+-- so this covers a reload that re-asserts monitors too.
+
+-- ============================================================================
+-- Startup Applications
+-- ============================================================================
+-- Portal and D-Bus setup FIRST, then waybar (needs portal for appearance query)
+-- Start hyprland-session.target FIRST: it pulls up graphical-session.target,
+-- which xdg-desktop-portal 1.22+ hard-requires (Requisite=). A bare-exec Hyprland
+-- session has no session manager to raise that target, so without this the portal
+-- fails its dependency at every login (screen-share + file pickers dead).
+-- 'systemctl start' blocks until active, so the ';' sequence guarantees the target
+-- is up before the portal restart runs.
+--
+-- Portal restart (not start) reconnects stale portals on Hyprland restart.
+-- Backend portals (GTK, Hyprland) restart BEFORE the main portal to avoid a 50s
+-- GTK settings proxy timeout; the sequence keeps that ordering. Separated by ';'
+-- not '&&' so a failing portal restart can't stop waybar from launching — waybar
+-- degrades gracefully without the portal (only the appearance query is missed),
+-- and gating the bar behind the portal left the desktop bar-less whenever
+-- xdg-desktop-portal failed its dependency at login. Waybar stays gated on its
+-- own config generation (waybar-active-config && waybar).
+
+-- Core services
+
+-- Desktop appearance
+-- `settings restore` replays the remembered toggles and reapplies the stored
+-- wallpaper. It replaced `waypaper --restore` on 2026-08-14: waypaper keeps
+-- its own config.ini and the settings store keeps another, neither knew about
+-- the other, and the login replay always won — so a wallpaper chosen in the
+-- panel came back as whatever the shell had last set. The store is the only
+-- one of the two that can hold a sun pair, a video or a projected face, so it
+-- owns the restore. set-wallpaper records into it for choices made outside
+-- the panel.
+--
+-- waypaper --restore stays as the fallback, not the owner. If the stored
+-- wallpaper cannot be applied (an image deleted, a drive not mounted yet),
+-- `settings restore` exits 3 and waypaper's independent copy still puts
+-- something on the screen. Dropping it outright would trade this bug for a
+-- bare desktop.
+--
+-- The wallpaper half only. The toggle half runs from its own exec-once further
+-- down, after hypridle and dunst — caffeine *is* "hypridle isn't running" and
+-- DND *is* dunst's pause level, so replaying them here would spend the whole
+-- re-assert budget correcting backings that have not launched yet, and would
+-- replay them a second time besides. This slot exists for awww's timing, not
+-- theirs.
+
+-- Background services
+-- hypridle is reaped on both exit paths, because it outlives its compositor
+-- otherwise. An orphaned daemon keeps firing idle actions at whatever session
+-- is live next, and it holds its old logind session scope open (the scope can't
+-- close while a process sits in it), so orphans accumulate one per abnormal
+-- session death. On 2026-07-22 velox reached five concurrent hypridle daemons;
+-- two of them racing to lock produced "Cannot re-lock" and a session wedged
+-- locked with no client able to draw a password prompt — recoverable only from
+-- another console. exec-shutdown covers a clean compositor exit; the pkill in
+-- exec-once covers the paths where it never runs (crash, SIGKILL, TTY logout).
+hl.on("hyprland.shutdown", function()
+ hl.exec_cmd("pkill -x hypridle")
+end)
+
+-- hypridle.conf is rendered here rather than tracked, because its contents
+-- are this machine's stage times and hibernate setting. Tracking the render
+-- meant every panel change dirtied the repo, and whichever machine
+-- committed last imposed its policy on the others: a desktop ended up
+-- carrying a laptop's suspend-then-hibernate line that it cannot run.
+-- Rendering at session start makes the store the only source of truth and
+-- the file a build artifact.
+--
+-- hypridle-start owns the render, the fallback, and the ordering between
+-- them, because that ordering is subtle enough to get wrong in a config
+-- line nothing can test: a render can fail on purpose (a damaged store, to
+-- avoid overwriting a real policy with defaults), and a fallback that
+-- fired there would perform exactly the overwrite the render refused.
+-- Replay the toggles that have no durable state of their own. Caffeine *is*
+-- "hypridle isn't running" and DND *is* dunst's pause level, so the two
+-- exec-once lines above (and dunst's) recreate both at a fixed default every
+-- start — a deliberately-set caffeine was silently discarded on every login.
+-- Ordered after those launches so it corrects a backing that exists; it also
+-- re-asserts for a few seconds, which covers a backing that comes up late.
+-- Logged like its neighbours: a silent exec-once failure here would look
+-- exactly like the bug it fixes, and gammastep is the standing proof that a
+-- launch dying quietly at session start can go unnoticed for a long time.
+
+-- Pyprland (scratchpads, magnify, etc.)
+
+-- Tray apps. wait-for-tray blocks until waybar's systray host is up (a fixed
+-- sleep can't cover a slow cold-start waybar), so these register their icons
+-- instead of opening as windows. Caps at ~30s, then launches anyway.
+-- QT_FONT_DPI bumps the bridge's QML UI font (qt6ct General font is ignored by Qt Quick)
+
+-- ============================================================================
+-- Environment Variables
+-- ============================================================================
+hl.env("XCURSOR_SIZE", "24")
+hl.env("XCURSOR_THEME", "Bibata-Modern-Ice")
+hl.env("XDG_CURRENT_DESKTOP", "Hyprland")
+hl.env("XDG_SESSION_TYPE", "wayland")
+hl.env("XDG_SESSION_DESKTOP", "Hyprland")
+hl.env("_JAVA_AWT_WM_NONREPARENTING", "1")
+
+-- ============================================================================
+-- Appearance (matching DWM colors)
+-- ============================================================================
+-- DWM colors: gray1=#222222, gray2=#444444, gray3=#bbbbbb, gray4=#eeeeee, cyan=#daa520
+
+hl.curve("myBezier", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
+hl.animation({
+ leaf = "windows",
+ enabled = true,
+ speed = 2,
+ bezier = "myBezier",
+})
+hl.animation({
+ leaf = "windowsOut",
+ enabled = true,
+ speed = 2,
+ bezier = "default",
+ style = "popin 80%",
+})
+hl.animation({
+ leaf = "fade",
+ enabled = true,
+ speed = 2,
+ bezier = "default",
+})
+hl.animation({
+ leaf = "workspaces",
+ enabled = true,
+ speed = 2,
+ bezier = "default",
+})
+hl.animation({
+ leaf = "specialWorkspace",
+ enabled = true,
+ speed = 2,
+ bezier = "default",
+ style = "slidevert",
+})
+
+hl.window_rule({
+ match = {
+ class = "^(xdg-desktop-portal-gtk)$",
+ },
+ float = true,
+})
+
+hl.window_rule({
+ match = {
+ class = "^(Gimp)$",
+ },
+ float = true,
+})
+
+hl.window_rule({
+ match = {
+ class = "^(caffeine)$",
+ },
+ float = true,
+})
+
+hl.window_rule({
+ match = {
+ class = "^(qalculate-gtk)$",
+ },
+ float = true,
+})
+
+hl.window_rule({
+ match = {
+ title = "^(Event Tester)$",
+ },
+ float = true,
+})
+
+hl.window_rule({
+ match = {
+ class = "^(net\\.cjennings\\.netpanel)$",
+ },
+ float = true,
+ move = "100%-464 50",
+})
+
+hl.window_rule({
+ match = {
+ class = "^(net\\.cjennings\\.btpanel)$",
+ },
+ float = true,
+ move = "100%-424 50",
+})
+
+hl.window_rule({
+ match = {
+ class = "^(net\\.cjennings\\.audiopanel)$",
+ },
+ float = true,
+ move = "100%-444 50",
+})
+
+hl.window_rule({
+ match = {
+ class = "^(net\\.cjennings\\.timerpanel)$",
+ },
+ float = true,
+ move = "100%-444 50",
+})
+
+hl.window_rule({
+ match = {
+ class = "^(net\\.cjennings\\.settingspanel)$",
+ },
+ float = true,
+ move = "100%-584 50",
+})
+
+hl.window_rule({
+ match = {
+ class = "^(net\\.cjennings\\.weatherpanel)$",
+ },
+ float = true,
+ move = "100%-464 50",
+})
+
+hl.window_rule({
+ match = {
+ class = "^(net\\.cjennings\\.maintpanel)$",
+ },
+ float = true,
+ move = "100%-1004 50",
+})
+
+hl.window_rule({
+ match = {
+ title = "^(org-capture)$",
+ },
+ float = true,
+ center = true,
+})
+
+hl.window_rule({
+ match = {
+ title = "^(dirvish)$",
+ },
+ float = true,
+ size = "1100 700",
+ center = true,
+})
+
+hl.window_rule({
+ match = {
+ class = "^(Civ5XP)$",
+ },
+ fullscreen = true,
+})
+
+local mod = "SUPER"
+
+hl.bind(mod .. " + T", hl.dsp.exec_cmd("foot"))
+hl.bind(mod .. " + E", hl.dsp.exec_cmd("emacsclient -c -a \"\" || emacs"))
+hl.bind(mod .. " + SHIFT + E", hl.dsp.exec_cmd("emacs"))
+hl.bind(mod .. " + N", hl.dsp.exec_cmd("quick-capture"))
+hl.bind(mod .. " + W", hl.dsp.exec_cmd("$BROWSER"))
+hl.bind(mod .. " + F", hl.dsp.exec_cmd("dirvish-popup"))
+hl.bind(mod .. " + SHIFT + F", hl.dsp.exec_cmd("layout-cycle float-toggle"))
+
+hl.bind(mod .. " + SPACE", hl.dsp.exec_cmd("fuzzel-toggle"))
+hl.bind(mod .. " + SHIFT + W", hl.dsp.exec_cmd("$ALTBROWSER"))
+hl.bind(mod .. " + P", hl.dsp.exec_cmd("media-toggle-all"))
+hl.bind(mod .. " + SHIFT + L", hl.dsp.exec_cmd("calibre"))
+hl.bind(mod .. " + SHIFT + P", hl.dsp.exec_cmd("toggle-touchpad"))
+
+hl.bind(mod .. " + J", hl.dsp.exec_cmd("layout-navigate next"))
+hl.bind(mod .. " + K", hl.dsp.exec_cmd("layout-navigate prev"))
+hl.bind(mod .. " + SHIFT + J", hl.dsp.exec_cmd("layout-navigate next move"))
+hl.bind(mod .. " + SHIFT + K", hl.dsp.exec_cmd("layout-navigate prev move"))
+hl.bind(mod .. " + H", hl.dsp.exec_cmd("layout-resize shrink"))
+hl.bind(mod .. " + L", hl.dsp.exec_cmd("layout-resize grow"))
+hl.bind(mod .. " + RETURN", hl.dsp.layout("swapwithmaster && sleep 0.05 && hyprctl dispatch layoutmsg focusmaster master"))
+hl.bind(mod .. " + G", hl.dsp.window.center())
+hl.bind(mod .. " + TAB", hl.dsp.focus({ workspace = "previous" }))
+hl.bind(mod .. " + SHIFT + C", hl.dsp.window.close())
+
+hl.bind(mod .. " + SHIFT + RIGHT", hl.dsp.exec_cmd("layout-cycle next"))
+hl.bind(mod .. " + SHIFT + LEFT", hl.dsp.exec_cmd("layout-cycle prev"))
+hl.bind(mod .. " + SHIFT + T", hl.dsp.exec_cmd("hyprctl keyword general:layout master && hyprctl keyword master:orientation left"))
+hl.bind(mod .. " + SHIFT + M", hl.dsp.exec_cmd("hyprctl keyword general:layout monocle"))
+hl.bind(mod .. " + SHIFT + SPACE", hl.dsp.window.float({ action = "toggle" }))
+
+hl.bind(mod .. " + U", hl.dsp.layout("addmaster"))
+hl.bind(mod .. " + D", hl.dsp.layout("removemaster"))
+
+hl.bind(mod .. " + O", hl.dsp.exec_cmd("stash-window"))
+hl.bind(mod .. " + ALT + O", hl.dsp.exec_cmd("stash-others"))
+hl.bind(mod .. " + SHIFT + O", hl.dsp.exec_cmd("stash-restore"))
+
+hl.bind(mod .. " + MINUS", hl.dsp.exec_cmd("window-gaps narrow"))
+hl.bind(mod .. " + EQUAL", hl.dsp.exec_cmd("window-gaps widen"))
+hl.bind(mod .. " + SHIFT + EQUAL", hl.dsp.exec_cmd("window-gaps reset"))
+hl.bind(mod .. " + SHIFT + MINUS", hl.dsp.exec_cmd("window-gaps zero"))
+
+hl.bind(mod .. " + SHIFT + D", hl.dsp.exec_cmd("dim-toggle"))
+hl.bind(mod .. " + SHIFT + G", hl.dsp.exec_cmd("settings-panel"))
+
+hl.bind(mod .. " + I", hl.dsp.exec_cmd("caffeine-toggle"))
+
+hl.bind(mod .. " + SHIFT + X", hl.dsp.exec_cmd("airplane-mode"))
+
+hl.bind(mod .. " + B", hl.dsp.exec_cmd("waybar-toggle"))
+
+hl.bind(mod .. " + bracketleft", hl.dsp.exec_cmd("waybar-collapse left"))
+hl.bind(mod .. " + bracketright", hl.dsp.exec_cmd("waybar-collapse right"))
+
+hl.bind(mod .. " + F11", hl.dsp.window.fullscreen({ mode = "fullscreen", action = "toggle" }))
+
+hl.bind(mod .. " + 1", hl.dsp.focus({ workspace = 1 }))
+hl.bind(mod .. " + 2", hl.dsp.focus({ workspace = 2 }))
+hl.bind(mod .. " + 3", hl.dsp.focus({ workspace = 3 }))
+hl.bind(mod .. " + 4", hl.dsp.focus({ workspace = 4 }))
+hl.bind(mod .. " + 5", hl.dsp.focus({ workspace = 5 }))
+hl.bind(mod .. " + 6", hl.dsp.focus({ workspace = 6 }))
+hl.bind(mod .. " + 7", hl.dsp.focus({ workspace = 7 }))
+hl.bind(mod .. " + 8", hl.dsp.focus({ workspace = 8 }))
+hl.bind(mod .. " + 9", hl.dsp.focus({ workspace = 9 }))
+
+hl.bind(mod .. " + SHIFT + 1", hl.dsp.window.move({ workspace = 1, follow = false }))
+hl.bind(mod .. " + SHIFT + 2", hl.dsp.window.move({ workspace = 2, follow = false }))
+hl.bind(mod .. " + SHIFT + 3", hl.dsp.window.move({ workspace = 3, follow = false }))
+hl.bind(mod .. " + SHIFT + 4", hl.dsp.window.move({ workspace = 4, follow = false }))
+hl.bind(mod .. " + SHIFT + 5", hl.dsp.window.move({ workspace = 5, follow = false }))
+hl.bind(mod .. " + SHIFT + 6", hl.dsp.window.move({ workspace = 6, follow = false }))
+hl.bind(mod .. " + SHIFT + 7", hl.dsp.window.move({ workspace = 7, follow = false }))
+hl.bind(mod .. " + SHIFT + 8", hl.dsp.window.move({ workspace = 8, follow = false }))
+hl.bind(mod .. " + SHIFT + 9", hl.dsp.window.move({ workspace = 9, follow = false }))
+
+hl.bind(mod .. " + COMMA", hl.dsp.focus({ monitor = -1 }))
+hl.bind(mod .. " + PERIOD", hl.dsp.focus({ monitor = "+1" }))
+hl.bind(mod .. " + SHIFT + COMMA", hl.dsp.window.move({ monitor = "-1" }))
+hl.bind(mod .. " + SHIFT + PERIOD", hl.dsp.window.move({ monitor = "+1" }))
+
+hl.bind(mod .. " + SHIFT + RETURN", hl.dsp.exec_cmd("pypr toggle term"))
+hl.bind(mod .. " + A", hl.dsp.exec_cmd("audio-panel"))
+hl.bind(mod .. " + R", hl.dsp.exec_cmd("pypr toggle monitor"))
+hl.bind(mod .. " + SHIFT + N", hl.dsp.exec_cmd("net panel"))
+hl.bind(mod .. " + SLASH", hl.dsp.exec_cmd("pypr toggle music"))
+
+hl.bind(mod .. " + Z", hl.dsp.exec_cmd("pypr zoom"))
+hl.bind(mod .. " + Z", hl.dsp.submap("zoom"))
+
+hl.define_submap("zoom", function()
+ hl.bind("ESCAPE", hl.dsp.exec_cmd("pypr zoom 1"))
+ hl.bind("ESCAPE", hl.dsp.submap("reset"))
+ hl.bind(mod .. " + Z", hl.dsp.exec_cmd("pypr zoom 1"))
+ hl.bind(mod .. " + Z", hl.dsp.submap("reset"))
+end)
+
+hl.bind(mod .. " + X", hl.dsp.exec_cmd("calc-toggle"))
+hl.bind(mod .. " + C", hl.dsp.exec_cmd("hyprpicker -a"))
+hl.bind(mod .. " + CONTROL + C", hl.dsp.exec_cmd("clock-panel toggle"))
+
+hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("pactl set-sink-volume @DEFAULT_SINK@ +5%"), { locked = true, repeating = true })
+hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("pactl set-sink-volume @DEFAULT_SINK@ -5%"), { locked = true, repeating = true })
+hl.bind("XF86AudioMute", hl.dsp.exec_cmd("audio quick-mute"), { locked = true })
+hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl s +10%"), { locked = true, repeating = true })
+hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl s 10%-"), { locked = true, repeating = true })
+
+hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("mic-toggle"), { locked = true })
+
+hl.bind(mod .. " + SHIFT + A", hl.dsp.exec_cmd("audio ptt-toggle"))
+
+hl.bind(mod .. " + SHIFT + B", hl.dsp.exec_cmd("bt-panel"))
+
+hl.bind(mod .. " + S", hl.dsp.exec_cmd("screenshot region"))
+hl.bind(mod .. " + SHIFT + S", hl.dsp.exec_cmd("screenshot fullscreen"))
+hl.bind("CTRL" .. mod .. " + S", hl.dsp.exec_cmd("screenshot fullscreen"))
+
+hl.bind(mod .. " + ESCAPE", hl.dsp.exec_cmd("hyprlock"))
+
+hl.bind(mod .. " + M", hl.dsp.exec_cmd("audio-cycle"))
+
+hl.bind(mod .. " + SHIFT + Q", hl.dsp.exec_cmd("pgrep -x wlogout || wlogout-menu"))
+hl.bind(mod .. " + SHIFT + BACKSPACE", hl.dsp.submap("exitconfirm"))
+
+hl.define_submap("exitconfirm", function()
+ hl.bind("BACKSPACE", hl.dsp.exit())
+ hl.bind("ESCAPE", hl.dsp.submap("reset"))
+ hl.bind("catchall", hl.dsp.submap("reset"))
+end)
+
+hl.bind(mod .. " + SHIFT + ESCAPE", hl.dsp.exec_cmd("hyprctl reload"))
+hl.bind("CTRL + ALT" .. mod .. " + K", hl.dsp.exec_cmd("hyprctl kill"))
+
+hl.bind(mod .. " + mouse:272", hl.dsp.window.drag())
+hl.bind(mod .. " + mouse:273", hl.dsp.window.resize())
+hl.bind(mod .. " + SHIFT + mouse:272", hl.dsp.window.resize())
+
+-- Source: $HOME/.config/hypr/conf.d/*.conf (glob; resolved at runtime). Each matched .conf must be converted to .lua.
+hl_source_glob("$HOME/.config/hypr/conf.d/*.lua")
+hl.config({
+ general = {
+ gaps_in = 25,
+ gaps_out = 30,
+ border_size = 2,
+ col = {
+ active_border = "rgba(daa520ff)",
+ inactive_border = "rgba(444444ff)",
+ nogroup_border_active = "rgba(daa520ff)",
+ nogroup_border = "rgba(444444ff)",
+ },
+ -- Pyprland 3.4+ applies `group deny` to scratchpads, which routes their
+ -- border through col.nogroup_border* instead of col.*_border. Without
+ -- these overrides Hyprland's defaults paint scratchpads bright magenta.
+ layout = "master",
+ resize_on_border = true,
+ },
+ decoration = {
+ rounding = 10,
+ dim_inactive = true,
+ dim_strength = 0.4,
+ dim_special = 0.2,
+ blur = {
+ enabled = false,
+ },
+ shadow = {
+ enabled = false,
+ },
+ },
+ animations = {
+ enabled = true,
+ },
+ -- ============================================================================
+ -- Layout (master-stack like DWM tile)
+ -- ============================================================================
+ master = {
+ new_status = "master",
+ new_on_top = true,
+ mfact = 0.55,
+ },
+ dwindle = {
+ preserve_split = true,
+ },
+ -- ============================================================================
+ -- Input
+ -- ============================================================================
+ cursor = {
+ no_warps = true,
+ inactive_timeout = 2.0,
+ },
+ input = {
+ kb_layout = "us",
+ kb_options = "ctrl:nocaps",
+ numlock_by_default = true,
+ follow_mouse = 0,
+ -- 0, not the default 1: with follow_mouse off we never want focus to follow
+ -- the cursor. At 1, focus still jumps to the window under the pointer when it
+ -- crosses a floating<->tiled boundary, so launching a floating scratchpad (or
+ -- the org-capture popup) re-enabled focus-follows-mouse onto tiled windows.
+ float_switch_override_focus = 0,
+ mouse_refocus = false,
+ natural_scroll = true,
+ touchpad = {
+ natural_scroll = false,
+ },
+ },
+ -- ============================================================================
+ -- Misc
+ -- ============================================================================
+ misc = {
+ force_default_wallpaper = 0,
+ disable_hyprland_logo = true,
+ -- false so apps can't pull focus via activation requests. New windows still
+ -- focus on open (separate path); this stops e.g. a browser yanking focus
+ -- back off a freshly opened emacs frame.
+ focus_on_activate = false,
+ -- Let a fresh lock client adopt a session whose previous one died. The
+ -- default (off) is the strict reading of ext-session-lock: a dead lock
+ -- client leaves the session locked forever and refuses every replacement
+ -- ("Cannot re-lock"), so the screen stays up with nothing able to draw a
+ -- password prompt and the only way back in is another console. That is a
+ -- hard lockout, and it cost a session on velox 2026-07-22. On means a
+ -- replacement hyprlock re-attaches and prompts normally. The screen stays
+ -- locked either way — this decides whether the lock is recoverable, never
+ -- whether it holds.
+ allow_session_lock_restore = true,
+ },
+ -- ============================================================================
+ -- Debug (temporary - disable when stable)
+ -- ============================================================================
+ debug = {
+ disable_logs = false,
+ },
+ -- ============================================================================
+ -- XWayland
+ -- ============================================================================
+ xwayland = {
+ force_zero_scaling = true,
+ },
+ -- ============================================================================
+ -- Window Rules (Hyprland 0.53+ syntax: match:CONDITION, RULE)
+ -- ============================================================================
+ -- Floating windows (from DWM rules)
+ -- net / bluetooth instrument-console panels. Normal floating windows (formerly
+ -- gtk4-layer-shell overlays) so they drag to move and corner-drag to resize.
+ -- Opened top-right to match their old anchored spot: the panel is right-aligned
+ -- with a 44px gap, so x = 100% - (window width + 44). net is 420 wide, bt 380.
+ -- maintenance console: the wide board (960), same right-aligned convention.
+ -- org-capture popup frame (quick-capture script names the frame)
+ -- Size is per-host in <host>/conf.d/local.conf: native window rules ignore
+ -- percentages (only pyprland honors them), so the popup is sized in absolute
+ -- pixels matching that host's terminal scratchpad. No size rule here means a
+ -- host without an override falls back to the script's char-cell geometry.
+ -- dirvish popup frame (dirvish-popup script names the frame). No stay_focused —
+ -- it's a file manager that launches files into other apps, so focus must be free
+ -- to follow; q (cj/dirvish-popup-quit) closes the frame.
+ -- NOTE: center windowrules removed 2026-03-04 per pyprland maintainer suggestion
+ -- Testing whether pyprland handles scratchpad re-centering natively (issue #211)
+ -- Gaming
+ -- ============================================================================
+ -- Key Bindings
+ -- ============================================================================
+ -- Terminal and core apps (from DWM)
+ -- Standalone emacs: its own process, not a frame on the daemon, so killing it
+ -- takes no other frame with it. init.el guards server-start on server-running-p,
+ -- so while the daemon holds the socket this process leaves it alone. With no
+ -- daemon up it finds no server and becomes one -- the guard lives in init.el, and
+ -- a keybind can't override it, since --eval runs after init.
+ -- From sxhkdrc
+ -- Window management (from DWM)
+ -- Layout-aware navigation (works across master, scrolling)
+ -- Swap focused window with master, then force focus onto the master slot.
+ -- swapwithmaster's own `master` focus param doesn't stick when invoked from
+ -- the master, so focusmaster master pins focus afterward. The 50ms sleep is
+ -- load-bearing: swapwithmaster fires an async focus event ~1-2ms after it
+ -- returns; without the delay that event lands AFTER focusmaster and flips
+ -- focus back to the detail. The sleep lets the swap's focus settle so
+ -- focusmaster runs last and wins. Proven via instrumented capture (19/19).
+ -- Layouts: master -> monocle
+ -- Cycle with Shift+arrows, or jump directly with Shift+T/M
+ -- (scrolling layout disabled until frame-fit + wrap-around work lands)
+ -- Master layout adjustments
+ -- Stash windows (hide to special workspace)
+ -- O = stash focused / Alt+O = stash others / Shift+O = restore all
+ -- Gaps between windows only; window-gaps leaves the monitor-edge gap
+ -- (general:gaps_out) fixed, so widening/narrowing moves the space between
+ -- windows, not the screen-edge margin.
+ -- Auto-dim toggle (D = dim). Same action as clicking the waybar custom/dim icon.
+ -- Caffeine (keep-awake) toggle. Same action as clicking the waybar
+ -- custom/caffeine icon — flips the hypridle daemon so the screen will / won't
+ -- lock. Stays on $mod+I ($mod+C is taken by hyprpicker; no free caffeine key).
+ -- Airplane mode (low-power: wifi off + CPU/brightness/services). A deliberate
+ -- keybind, not a bar click — engaging it disconnects you, so it shouldn't be a
+ -- misclick away. The custom/net module shows the state; this toggles it.
+ -- On Super+Shift+X ("X" = everything off); Super+Shift+A toggles push-to-talk.
+ -- Toggle bar visibility, or relaunch waybar if it crashed (no exec-once respawn).
+ -- Collapse / expand the left or right side of the bar to its base set
+ -- (same action as clicking the side's arrowhead). [ = left, ] = right.
+ -- Fullscreen
+ -- Workspaces 1-9 (from DWM TAGKEYS)
+ -- Move window to workspace (from DWM tag)
+ -- Monitor focus (from DWM focusmon)
+ -- ============================================================================
+ -- Scratchpads (via pyprland)
+ -- ============================================================================
+ -- Configured in ~/.config/hypr/pyprland.toml
+ -- Uses normal workspaces (not special), so new windows won't be captured
+ -- Magnify (zoom)
+ -- mod+Z zooms and enters the "zoom" submap; inside it, Escape or mod+Z
+ -- unzooms and returns to the normal keymap. Exit forces `pypr zoom 1`
+ -- (factor 1) so submap state and zoom state can't desync. Note: while
+ -- zoomed, other Hyprland binds pause until you exit the submap.
+ -- Calculator (not a scratchpad, just launches app)
+ -- Media/hardware keys
+ -- Microphone mute toggle (waybar pulseaudio#mic indicator follows via PipeWire events).
+ -- On the hardware mic-mute key. Super+Shift+A used to duplicate this; it now
+ -- toggles push-to-talk mode instead (mic-toggle stays reachable on the hw key).
+ -- Push-to-talk toggle: enter PTT (mic muted, hold key armed) / exit (restore).
+ -- The hold key is configurable (audio config ptt_key = Control_R or mouse:NNN).
+ -- Bluetooth panel (blueman retired in favor of the bt panel)
+ -- Screenshots (grim + slurp + fuzzel menu)
+ -- Shift+S captures the whole desktop with no pointer interaction, so it
+ -- works on scratchpads and popups that region-select would dismiss
+ -- Lock screen
+ -- Audio mute cycle (M for Mute): one key walks volume/mic through all four
+ -- on/off combinations. The waybar pulseaudio modules follow via PipeWire.
+ -- Exit/session
+ -- mod+Shift+Backspace no longer exits outright — it enters the "exitconfirm"
+ -- submap, where a second Backspace confirms and anything else backs out.
+ -- The bare bind killed two live sessions on 2026-07-22: it sits one key from
+ -- the mod+Shift cluster (Q wlogout, C killactive, Return terminal, G settings)
+ -- and took the whole session down with no prompt. wlogout owns the normal
+ -- exit path; this stays as the keyboard escape hatch for when wlogout won't
+ -- come up, so it's gated rather than removed.
+ -- Mouse bindings (from DWM buttons)
+ -- ============================================================================
+ -- Machine-local overrides
+ -- ============================================================================
+ -- Sourced last so machine-specific settings (monitor scale, gaps, keybinds)
+ -- override the defaults above. See conf.d/local.conf.
+})
+
+hl.on("hyprland.start", function()
+ hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE")
+ hl.exec_cmd("systemctl --user start hyprland-session.target; systemctl --user restart xdg-desktop-portal-hyprland xdg-desktop-portal-gtk; systemctl --user restart xdg-desktop-portal; waybar-active-config && waybar -c \"$XDG_RUNTIME_DIR/waybar/config\" -s ~/.config/waybar/style.css 2>&1 | grep -v \"LIBDBUSMENU-GLIB-WARNING\" > ~/.local/var/log/waybar-$(date +%Y-%m-%d-%H%M%S).log")
+ hl.exec_cmd("/usr/lib/polkit-kde-authentication-agent-1")
+ hl.exec_cmd("/usr/bin/gnome-keyring-daemon --start --components=pkcs11,secrets,ssh")
+ hl.exec_cmd("dunst > ~/.local/var/log/dunst-$(date +%Y-%m-%d-%H%M%S).log 2>&1")
+ hl.exec_cmd("awww-daemon & sleep 1 && { settings restore-wallpaper || waypaper --restore; }")
+ hl.exec_cmd("touchpad-auto")
+ hl.exec_cmd("pkill -x hypridle; hypridle-start > ~/.local/var/log/hypridle-$(date +%Y-%m-%d-%H%M%S).log 2>&1")
+ hl.exec_cmd("/usr/lib/geoclue-2.0/demos/agent")
+ hl.exec_cmd("gammastep > ~/.local/var/log/gammastep-$(date +%Y-%m-%d-%H%M%S).log 2>&1")
+ hl.exec_cmd("mpd")
+ hl.exec_cmd("settings restore > ~/.local/var/log/settings-restore-$(date +%Y-%m-%d-%H%M%S).log 2>&1")
+ hl.exec_cmd("pypr > ~/.local/var/log/pypr-$(date +%Y-%m-%d-%H%M%S).log 2>&1")
+ hl.exec_cmd("hypr-refocus-scratchpad")
+ hl.exec_cmd("wait-for-tray && signal-desktop --start-in-tray --ozone-platform=wayland")
+ hl.exec_cmd("env QT_FONT_DPI=108 protonmail-bridge --no-window")
+end)
+
+hl.on("config.reloaded", function()
+ hl.exec_cmd("for i in 1 2 3; do sleep 0.2; waybar-reserve; done")
+end)
+