hypr: migrate to Lua config (v0.55) with custom scroll layouts
Replace hyprlang hyprland.d/ with Lua-based hyprland.d.lua/ modules: - theme.lua.tmpl: apex-neon + apex-aeon color tables, col.* bracket keys - general.lua: config, bezier curves, animations (bezier=/spring= fixed) - monitors.lua.tmpl, workspaces.lua.tmpl, input.lua.tmpl, rules.lua.tmpl - keybinds.lua.tmpl: +/SHIFT/CTRL format, monitor focus (Super+I/O), scroll binds for custom layouts - layout.lua: master-scroll, slave-master-scroll, center-master-scroll (peek-hint scrolling slave columns, focus-triggered auto-scroll) Entry point uses package.path + require() for per-file error isolation. Old hyprlang configs preserved under hyprland.conf.bak/. Add .luarc.json for hyprland stub autocompletion in editors.
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"workspace": {
|
||||
"library": ["/usr/share/hypr/stubs"]
|
||||
},
|
||||
"diagnostics": {
|
||||
"globals": ["hl"]
|
||||
}
|
||||
}
|
||||
@@ -71,3 +71,16 @@ No established Git history. Use concise, imperative commit subjects with optiona
|
||||
- Screenshots for visible UI changes.
|
||||
|
||||
## Agent-Specific Notes
|
||||
|
||||
### Lua config (`hyprland.lua.tmpl` + `hyprland.d.lua/`)
|
||||
|
||||
The Lua config entry point is `hyprland.lua.tmpl`. It is rendered by chezmoi to `~/.config/hypr/hyprland.lua`, which Hyprland v0.55+ loads in preference to `hyprland.conf`.
|
||||
|
||||
`theme.lua.tmpl` reads `.chezmoi.config.data.theme` to select the color palette. This key is **not** in the tracked AGENTS.md reference but must be present in `~/.config/chezmoi/chezmoi.toml`:
|
||||
|
||||
```toml
|
||||
[data]
|
||||
theme = "apex-neon" # or "apex-aeon"
|
||||
```
|
||||
|
||||
Valid values: `"apex-neon"` (dark), `"apex-aeon"` (light). If the key is absent, the theme table will be empty and all border/groupbar colors will be unset.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
### based on the example config from hyprland.org
|
||||
|
||||
general {
|
||||
lock_cmd = pgrep -x swaylock >/dev/null || swaylock -f # avoid starting multiple swaylock instances.
|
||||
lock_cmd = pgrep -x hyprlock >/dev/null || hyprlock -f # avoid starting multiple swaylock instances.
|
||||
before_sleep_cmd = loginctl lock-session # lock before suspend.
|
||||
after_sleep_cmd = hyprctl dispatch dpms on # to avoid having to press a key twice to turn on the display.
|
||||
ignore_dbus_inhibit = false # whether to ignore dbus-sent idle-inhibit requests (used by e.g. firefox or steam)
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
hl.config({
|
||||
general = {
|
||||
gaps_in = 5,
|
||||
gaps_out = { top = 5, left = 5, right = 5, bottom = 5 },
|
||||
border_size = 2,
|
||||
layout = "master",
|
||||
allow_tearing = false
|
||||
},
|
||||
render = {
|
||||
new_render_scheduling = true
|
||||
},
|
||||
cursor = {
|
||||
hide_on_key_press = true,
|
||||
persistent_warps = true,
|
||||
warp_on_change_workspace = true,
|
||||
enable_hyprcursor = true,
|
||||
sync_gsettings_theme = true,
|
||||
no_hardware_cursors = true
|
||||
},
|
||||
decoration = {
|
||||
rounding = 5,
|
||||
active_opacity = 1.0,
|
||||
inactive_opacity = 1.0,
|
||||
dim_modal = true,
|
||||
dim_inactive = true,
|
||||
dim_strength = 0.1,
|
||||
blur = {
|
||||
enabled = true,
|
||||
size = 3,
|
||||
passes = 1,
|
||||
vibrancy = 0.1696,
|
||||
popups = true -- NEW: Blur for menus and tooltips
|
||||
},
|
||||
shadow = {
|
||||
enabled = false,
|
||||
range = 4,
|
||||
render_power = 3,
|
||||
-- color integrated via theme
|
||||
},
|
||||
glow = { -- NEW in v0.55
|
||||
enabled = true,
|
||||
range = 10,
|
||||
-- color integrated via theme
|
||||
}
|
||||
},
|
||||
misc = {
|
||||
force_default_wallpaper = -1,
|
||||
disable_hyprland_logo = false,
|
||||
vrr = 2,
|
||||
mouse_move_enables_dpms = true,
|
||||
key_press_enables_dpms = true,
|
||||
layers_hog_keyboard_focus = true,
|
||||
mouse_move_focuses_monitor = true
|
||||
},
|
||||
ecosystem = {
|
||||
enforce_permissions = true -- NEW: Enable Android-style permissions
|
||||
}
|
||||
})
|
||||
|
||||
-- Curves
|
||||
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("linear", { type = "bezier", points = { {0, 0}, {1, 1} } })
|
||||
hl.curve("almostLinear", { type = "bezier", points = { {0.5, 0.5}, {0.75, 1.0} } })
|
||||
hl.curve("quick", { type = "bezier", points = { {0.15, 0}, {0.1, 1} } })
|
||||
hl.curve("bouncy", { type = "spring", mass = 1, stiffness = 100, dampening = 15 }) -- NEW: Spring curve
|
||||
|
||||
-- Animations
|
||||
hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" })
|
||||
hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" })
|
||||
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, bezier = "easeOutQuint" })
|
||||
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, bezier = "easeOutQuint", style = "popin 87%" })
|
||||
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" })
|
||||
hl.animation({ leaf = "fadeIn", enabled = true, speed = 1.73, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "fadeOut", enabled = true, speed = 1.46, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" })
|
||||
hl.animation({ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" })
|
||||
hl.animation({ leaf = "layersIn", enabled = true, speed = 4, bezier = "easeOutQuint", style = "fade" })
|
||||
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.5, bezier = "linear", style = "fade" })
|
||||
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 1.79, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 1.39, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "workspaces", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
|
||||
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 1.21, bezier = "almostLinear", style = "fade" })
|
||||
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
|
||||
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 1.5, spring = "bouncy", style = "slidevert" })
|
||||
@@ -0,0 +1,30 @@
|
||||
{{- $tags := .chezmoi.config.data.tags -}}
|
||||
|
||||
hl.config({
|
||||
input = {
|
||||
kb_layout = "ultimatekeys",
|
||||
kb_options = "caps:escape_shifted_capslock",
|
||||
numlock_by_default = true,
|
||||
repeat_rate = 25,
|
||||
repeat_delay = 600,
|
||||
follow_mouse = 1,
|
||||
focus_on_close = 2,
|
||||
|
||||
mouse_refocus = true,
|
||||
float_switch_override_focus = 2,
|
||||
special_fallthrough = true,
|
||||
touchpad = {
|
||||
disable_while_typing = true,
|
||||
scroll_factor = 1.0,
|
||||
tap_to_click = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
{{- if (index $tags "desktop") }}
|
||||
hl.device({
|
||||
name = "Logitech Gaming Mouse G502",
|
||||
sensitivity = 0.0,
|
||||
accel_profile = "flat"
|
||||
})
|
||||
{{- end }}
|
||||
@@ -0,0 +1,156 @@
|
||||
local mainMod = "SUPER"
|
||||
|
||||
-- Apps
|
||||
local terminal = "uwsm app -- kitty"
|
||||
local term_tmux = "uwsm app -- kitty -e tmux"
|
||||
local term_tmux_append = "uwsm app -- kitty -e tmux a"
|
||||
local editor = "uwsm app -- kitty -e nvim"
|
||||
local alteditor = "uwsm app -- zeditor"
|
||||
local filemanager = "uwsm app -- nautilus"
|
||||
local launcher = "uwsm app -- owlry -p app,cmd,system,ssh"
|
||||
local clipman = "uwsm app -- owlry -m clipboard"
|
||||
local browser = "uwsm app -- firefox"
|
||||
local taskman = "uwsm app -- owlry -m uuctl"
|
||||
local pwdmgr = "uwsm app -- bitwarden-desktop"
|
||||
local soundctl = "uwsm app -- pwvucontrol"
|
||||
|
||||
local notcenter = "uwsm app -- swaync-client -t -sw"
|
||||
local notdnd = "uwsm app -- swaync-client -d"
|
||||
local nothide = "uwsm app -- swaync-client --hide-latest"
|
||||
|
||||
-- First-class launchers
|
||||
hl.bind(mainMod .. " + Return", hl.dsp.exec_cmd(terminal))
|
||||
hl.bind(mainMod .. " + SHIFT + Return", hl.dsp.exec_cmd(term_tmux))
|
||||
hl.bind(mainMod .. " + CTRL + Return", hl.dsp.exec_cmd(term_tmux_append))
|
||||
hl.bind(mainMod .. " + F1", hl.dsp.exec_cmd("hypr-show-binds"))
|
||||
hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(filemanager))
|
||||
hl.bind(mainMod .. " + W", hl.dsp.exec_cmd(browser))
|
||||
hl.bind(mainMod .. " + Space", hl.dsp.exec_cmd(launcher))
|
||||
|
||||
-- Secondary launchers
|
||||
hl.bind(mainMod .. " + SHIFT + E", hl.dsp.exec_cmd(editor))
|
||||
hl.bind(mainMod .. " + CTRL + E", hl.dsp.exec_cmd(alteditor))
|
||||
hl.bind(mainMod .. " + X", hl.dsp.exec_cmd(taskman))
|
||||
hl.bind(mainMod .. " + C", hl.dsp.exec_cmd(clipman))
|
||||
hl.bind(mainMod .. " + F4", hl.dsp.exec_cmd(soundctl))
|
||||
|
||||
-- Quick Workspaces submap
|
||||
hl.bind(mainMod .. " + A", hl.dsp.submap("quickws"))
|
||||
hl.define_submap("quickws", function()
|
||||
hl.bind("z", function() hl.dispatch(hl.dsp.focus({ workspace = 1 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
||||
hl.bind("d", function() hl.dispatch(hl.dsp.focus({ workspace = 2 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
||||
hl.bind("a", function() hl.dispatch(hl.dsp.focus({ workspace = 3 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
||||
hl.bind("x", function() hl.dispatch(hl.dsp.focus({ workspace = 4 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
||||
hl.bind("s", function() hl.dispatch(hl.dsp.focus({ workspace = 5 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
||||
hl.bind("c", function() hl.dispatch(hl.dsp.focus({ workspace = 6 })); hl.dispatch(hl.dsp.submap("reset")) end)
|
||||
hl.bind("Escape", hl.dsp.submap("reset"))
|
||||
hl.bind("Return", hl.dsp.submap("reset"))
|
||||
end)
|
||||
|
||||
-- Notifications
|
||||
hl.bind(mainMod .. " + grave", hl.dsp.exec_cmd(notcenter))
|
||||
hl.bind(mainMod .. " + SHIFT + grave", hl.dsp.exec_cmd(notdnd))
|
||||
hl.bind(mainMod .. " + CTRL + grave", hl.dsp.exec_cmd(nothide))
|
||||
|
||||
-- Session
|
||||
hl.bind(mainMod .. " + Pause", hl.dsp.exec_cmd("hyprlock"))
|
||||
hl.bind(mainMod .. " + SHIFT + Pause", hl.dsp.exec_cmd("owlry-power-menu"))
|
||||
hl.bind(mainMod .. " + End", hl.dsp.exec_cmd("hyprlock"))
|
||||
hl.bind(mainMod .. " + SHIFT + End", hl.dsp.exec_cmd("owlry-power-menu"))
|
||||
|
||||
-- Window management
|
||||
hl.bind(mainMod .. " + Q", hl.dsp.window.kill())
|
||||
hl.bind(mainMod .. " + SHIFT + Q", hl.dsp.window.kill())
|
||||
hl.bind(mainMod .. " + F", hl.dsp.window.float())
|
||||
hl.bind(mainMod .. " + SHIFT + F", hl.dsp.window.fullscreen({ action = "toggle" }))
|
||||
hl.bind(mainMod .. " + P", hl.dsp.window.pin())
|
||||
hl.bind(mainMod .. " + U", hl.dsp.focus({ urgent_or_last = true }))
|
||||
hl.bind(mainMod .. " + V", hl.dsp.window.center())
|
||||
|
||||
-- Special workspaces
|
||||
hl.bind(mainMod .. " + SHIFT + Space", hl.dsp.workspace.toggle_special())
|
||||
hl.bind(mainMod .. " + CTRL + Space", hl.dsp.window.move({ workspace = "special" }))
|
||||
hl.bind(mainMod .. " + N", hl.dsp.workspace.toggle_special("passwordmgr"))
|
||||
|
||||
-- Monitor focus
|
||||
hl.bind(mainMod .. " + I", hl.dsp.focus({ monitor = "l" }))
|
||||
hl.bind(mainMod .. " + O", hl.dsp.focus({ monitor = "r" }))
|
||||
hl.bind(mainMod .. " + SHIFT + I", hl.dsp.workspace.move({ monitor = "l" }))
|
||||
hl.bind(mainMod .. " + SHIFT + O", hl.dsp.workspace.move({ monitor = "r" }))
|
||||
|
||||
-- Layout
|
||||
hl.bind(mainMod .. " + comma", hl.dsp.exec_cmd("hypr-workspace-layout toggle-ms"))
|
||||
hl.bind(mainMod .. " + period", hl.dsp.exec_cmd("hypr-workspace-layout cycle"))
|
||||
|
||||
-- Smart Gaps Toggle
|
||||
hl.bind(mainMod .. " + SHIFT + G", function()
|
||||
local gapsIn = hl.get_config("general.gaps_in")
|
||||
local current = type(gapsIn) == "table" and (gapsIn.top or 0) or (gapsIn or 0)
|
||||
if current ~= 0 then
|
||||
hl.config({ general = { gaps_in = 0, gaps_out = 0 } })
|
||||
hl.notification.create({ text = "Gaps: OFF", timeout = 2000, icon = "info" })
|
||||
else
|
||||
hl.config({ general = { gaps_in = 5, gaps_out = 5 } })
|
||||
hl.notification.create({ text = "Gaps: ON", timeout = 2000, icon = "ok" })
|
||||
end
|
||||
end)
|
||||
|
||||
-- Global Focus Notification (Lua Event)
|
||||
hl.on("window.active", function(w)
|
||||
if w ~= nil and w.title ~= nil then
|
||||
print("Focused: " .. w.title)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Vim-like navigation
|
||||
hl.bind(mainMod .. " + H", hl.dsp.focus({ direction = "l" }))
|
||||
hl.bind(mainMod .. " + L", hl.dsp.focus({ direction = "r" }))
|
||||
hl.bind(mainMod .. " + K", hl.dsp.focus({ direction = "u" }))
|
||||
hl.bind(mainMod .. " + J", hl.dsp.focus({ direction = "d" }))
|
||||
|
||||
-- Move window
|
||||
hl.bind(mainMod .. " + SHIFT + H", hl.dsp.exec_cmd("hypr-workspace-layout move-left"))
|
||||
hl.bind(mainMod .. " + SHIFT + L", hl.dsp.exec_cmd("hypr-workspace-layout move-right"))
|
||||
hl.bind(mainMod .. " + SHIFT + K", hl.dsp.exec_cmd("hypr-workspace-layout move-up"))
|
||||
hl.bind(mainMod .. " + SHIFT + J", hl.dsp.exec_cmd("hypr-workspace-layout move-down"))
|
||||
|
||||
-- Resize submap
|
||||
hl.bind(mainMod .. " + R", hl.dsp.submap("resize"))
|
||||
hl.define_submap("resize", function()
|
||||
hl.bind("h", hl.dsp.window.resize({ x = -25, y = 0, relative = true }), { repeating = true })
|
||||
hl.bind("l", hl.dsp.window.resize({ x = 25, y = 0, relative = true }), { repeating = true })
|
||||
hl.bind("k", hl.dsp.window.resize({ x = 0, y = -25, relative = true }), { repeating = true })
|
||||
hl.bind("j", hl.dsp.window.resize({ x = 0, y = 25, relative = true }), { repeating = true })
|
||||
hl.bind("Escape", hl.dsp.submap("reset"))
|
||||
end)
|
||||
|
||||
-- Workspace cycling
|
||||
hl.bind(mainMod .. " + Tab", hl.dsp.focus({ workspace = "m+1" }))
|
||||
hl.bind(mainMod .. " + SHIFT + Tab", hl.dsp.focus({ workspace = "m-1" }))
|
||||
|
||||
-- Switch Workspaces 1-10 (IDs 21-30 in setup)
|
||||
for i = 1, 9 do
|
||||
hl.bind(mainMod .. " + " .. i, hl.dsp.focus({ workspace = 20 + i }))
|
||||
hl.bind(mainMod .. " + SHIFT + " .. i, hl.dsp.window.move({ workspace = 20 + i }))
|
||||
end
|
||||
hl.bind(mainMod .. " + 0", hl.dsp.focus({ workspace = 30 }))
|
||||
hl.bind(mainMod .. " + SHIFT + 0", hl.dsp.window.move({ workspace = 30 }))
|
||||
|
||||
-- Groups
|
||||
hl.bind(mainMod .. " + Z", hl.dsp.group.next())
|
||||
hl.bind(mainMod .. " + SHIFT + Z", hl.dsp.group.prev())
|
||||
|
||||
-- Mouse binds
|
||||
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
|
||||
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
|
||||
|
||||
-- master-scroll slave scrolling (mouse wheel + bracket fallback)
|
||||
hl.bind(mainMod .. " + mouse_down", hl.dsp.layout("scrollup"), { mouse = true })
|
||||
hl.bind(mainMod .. " + mouse_up", hl.dsp.layout("scrolldown"), { mouse = true })
|
||||
hl.bind(mainMod .. " + bracketright", hl.dsp.layout("scrolldown"))
|
||||
hl.bind(mainMod .. " + bracketleft", hl.dsp.layout("scrollup"))
|
||||
|
||||
-- Multimedia
|
||||
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"), { repeating = true })
|
||||
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { repeating = true })
|
||||
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true })
|
||||
@@ -0,0 +1,239 @@
|
||||
hl.config({
|
||||
master = {
|
||||
orientation = "left",
|
||||
mfact = 0.60,
|
||||
new_status = "slave",
|
||||
new_on_top = true,
|
||||
new_on_active = "after",
|
||||
allow_small_split = true,
|
||||
special_scale_factor = 0.8,
|
||||
drop_at_cursor = true
|
||||
},
|
||||
scrolling = {
|
||||
fullscreen_on_one_column = true,
|
||||
focus_fit_method = 0,
|
||||
follow_focus = true,
|
||||
follow_min_visible = 0.1,
|
||||
column_width = 0.7
|
||||
}
|
||||
})
|
||||
|
||||
-- ─── Scrolling slave column helper ───────────────────────────────────────────
|
||||
-- Shared by all master-scroll variants.
|
||||
-- state = { visible, peek, offset, peek_top_addr, peek_bottom_addr }
|
||||
-- slave_area = HL.Box for this column
|
||||
-- targets = ctx.targets
|
||||
-- slave_indices = ordered list of indices into `targets` for this column
|
||||
local function place_scroll_col(state, slave_area, targets, slave_indices)
|
||||
local n = #slave_indices
|
||||
state.peek_top_addr = nil
|
||||
state.peek_bottom_addr = nil
|
||||
if n == 0 then return end
|
||||
|
||||
local max_off = math.max(0, n - state.visible)
|
||||
state.offset = math.max(0, math.min(state.offset, max_off))
|
||||
|
||||
if n <= state.visible then
|
||||
local h = slave_area.h / n
|
||||
for j = 1, n do
|
||||
targets[slave_indices[j]]:place({
|
||||
x = slave_area.x, y = slave_area.y + (j - 1) * h,
|
||||
w = slave_area.w, h = h,
|
||||
})
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local has_top = state.offset > 0
|
||||
local has_bot = state.offset < max_off
|
||||
local top_f = has_top and state.peek or 0
|
||||
local bot_f = has_bot and state.peek or 0
|
||||
-- h chosen so top_peek + visible*h + bot_peek == slave_area.h exactly
|
||||
local h = slave_area.h / (state.visible + top_f + bot_f)
|
||||
|
||||
if has_top then
|
||||
local w = targets[slave_indices[state.offset]].window
|
||||
if w then state.peek_top_addr = w.address end
|
||||
end
|
||||
if has_bot then
|
||||
local ti = slave_indices[state.offset + state.visible + 1]
|
||||
if ti then
|
||||
local w = targets[ti].window
|
||||
if w then state.peek_bottom_addr = w.address end
|
||||
end
|
||||
end
|
||||
|
||||
for j = 1, n do
|
||||
local t = targets[slave_indices[j]]
|
||||
if has_top and j == state.offset then
|
||||
-- Peek at top: extends above slave_area; safe because master is in a different x-range
|
||||
t:place({ x=slave_area.x, y=slave_area.y - h*(1-state.peek), w=slave_area.w, h=h })
|
||||
elseif has_bot and j == state.offset + state.visible + 1 then
|
||||
-- Peek at bottom: extends below the last visible slot
|
||||
t:place({ x=slave_area.x, y=slave_area.y + (top_f + state.visible)*h, w=slave_area.w, h=h })
|
||||
elseif j >= state.offset + 1 and j <= state.offset + state.visible then
|
||||
local k = j - state.offset - 1
|
||||
t:place({ x=slave_area.x, y=slave_area.y + (top_f + k)*h, w=slave_area.w, h=h })
|
||||
else
|
||||
-- Fully off-screen: park below work area
|
||||
t:set_box({ x=slave_area.x, y=slave_area.y + slave_area.h + h, w=slave_area.w, h=h })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ─── Layout states ────────────────────────────────────────────────────────────
|
||||
local mfact = 0.60 -- master width fraction (shared across single-master variants)
|
||||
|
||||
local ms = { visible=2, peek=0.10, offset=0, peek_top_addr=nil, peek_bottom_addr=nil }
|
||||
local sm = { visible=2, peek=0.10, offset=0, peek_top_addr=nil, peek_bottom_addr=nil }
|
||||
|
||||
local cm = { side_w=0.20, visible=2, peek=0.10 }
|
||||
local cm_left = { visible=cm.visible, peek=cm.peek, offset=0, peek_top_addr=nil, peek_bottom_addr=nil }
|
||||
local cm_right = { visible=cm.visible, peek=cm.peek, offset=0, peek_top_addr=nil, peek_bottom_addr=nil }
|
||||
local cm_left_addrs = {}
|
||||
local cm_right_addrs = {}
|
||||
|
||||
-- ─── master-scroll: master left, slaves right ─────────────────────────────────
|
||||
hl.layout.register("master-scroll", {
|
||||
recalculate = function(ctx)
|
||||
local targets = ctx.targets
|
||||
local n = #targets
|
||||
if n == 0 then return end
|
||||
if n == 1 then targets[1]:place(ctx.area); return end
|
||||
|
||||
local slave_area = ctx:split(ctx.area, "right", 1.0 - mfact)
|
||||
local master_area = { x=ctx.area.x, y=ctx.area.y, w=slave_area.x-ctx.area.x, h=ctx.area.h }
|
||||
targets[1]:place(master_area)
|
||||
|
||||
local idx = {}
|
||||
for i = 2, n do idx[#idx+1] = i end
|
||||
place_scroll_col(ms, slave_area, targets, idx)
|
||||
end,
|
||||
|
||||
layout_msg = function(ctx, msg)
|
||||
local max_off = math.max(0, #ctx.targets - 1 - ms.visible)
|
||||
if msg == "scrolldown" then ms.offset = math.min(ms.offset + 1, max_off); return true
|
||||
elseif msg == "scrollup" then ms.offset = math.max(ms.offset - 1, 0); return true
|
||||
elseif msg == "reset" then ms.offset = 0; return true
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- ─── slave-master-scroll: slaves left, master right ──────────────────────────
|
||||
hl.layout.register("slave-master-scroll", {
|
||||
recalculate = function(ctx)
|
||||
local targets = ctx.targets
|
||||
local n = #targets
|
||||
if n == 0 then return end
|
||||
if n == 1 then targets[1]:place(ctx.area); return end
|
||||
|
||||
local slave_area = ctx:split(ctx.area, "left", 1.0 - mfact)
|
||||
local master_area = {
|
||||
x = slave_area.x + slave_area.w, y = ctx.area.y,
|
||||
w = ctx.area.w - slave_area.w, h = ctx.area.h,
|
||||
}
|
||||
targets[1]:place(master_area)
|
||||
|
||||
local idx = {}
|
||||
for i = 2, n do idx[#idx+1] = i end
|
||||
place_scroll_col(sm, slave_area, targets, idx)
|
||||
end,
|
||||
|
||||
layout_msg = function(ctx, msg)
|
||||
local max_off = math.max(0, #ctx.targets - 1 - sm.visible)
|
||||
if msg == "scrolldown" then sm.offset = math.min(sm.offset + 1, max_off); return true
|
||||
elseif msg == "scrollup" then sm.offset = math.max(sm.offset - 1, 0); return true
|
||||
elseif msg == "reset" then sm.offset = 0; return true
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- ─── center-master-scroll: center master, both columns scroll ─────────────────
|
||||
-- Slaves alternate: odd (1,3,5…) → left column, even (2,4,6…) → right column.
|
||||
-- scrolldown/scrollup applies to whichever column the active window is in.
|
||||
hl.layout.register("center-master-scroll", {
|
||||
recalculate = function(ctx)
|
||||
local targets = ctx.targets
|
||||
local n = #targets
|
||||
cm_left_addrs = {}
|
||||
cm_right_addrs = {}
|
||||
if n == 0 then return end
|
||||
if n == 1 then targets[1]:place(ctx.area); return end
|
||||
|
||||
local left_area = ctx:split(ctx.area, "left", cm.side_w)
|
||||
local right_area = ctx:split(ctx.area, "right", cm.side_w)
|
||||
local master_area = {
|
||||
x = left_area.x + left_area.w, y = ctx.area.y,
|
||||
w = right_area.x - (left_area.x + left_area.w), h = ctx.area.h,
|
||||
}
|
||||
targets[1]:place(master_area)
|
||||
|
||||
local left_idx, right_idx = {}, {}
|
||||
for i = 2, n do
|
||||
local slave_pos = i - 1 -- 1-indexed slave number
|
||||
if slave_pos % 2 == 1 then
|
||||
left_idx[#left_idx+1] = i
|
||||
else
|
||||
right_idx[#right_idx+1] = i
|
||||
end
|
||||
local w = targets[i].window
|
||||
if w then
|
||||
if slave_pos % 2 == 1 then cm_left_addrs[w.address] = true
|
||||
else cm_right_addrs[w.address] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
place_scroll_col(cm_left, left_area, targets, left_idx)
|
||||
place_scroll_col(cm_right, right_area, targets, right_idx)
|
||||
end,
|
||||
|
||||
layout_msg = function(ctx, msg)
|
||||
local aw = hl.get_active_window()
|
||||
local addr = aw and aw.address
|
||||
|
||||
local function scroll_col(state, delta)
|
||||
local max_off = math.max(0, #state - state.visible) -- recalculated below
|
||||
-- Count windows in this column from targets
|
||||
local col_n = 0
|
||||
for i = 2, #ctx.targets do
|
||||
local w = ctx.targets[i].window
|
||||
if w and ((state == cm_left and cm_left_addrs[w.address])
|
||||
or (state == cm_right and cm_right_addrs[w.address])) then
|
||||
col_n = col_n + 1
|
||||
end
|
||||
end
|
||||
local col_max = math.max(0, col_n - state.visible)
|
||||
if delta > 0 then state.offset = math.min(state.offset + 1, col_max)
|
||||
else state.offset = math.max(state.offset - 1, 0)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if msg == "scrolldown" then
|
||||
if addr and cm_left_addrs[addr] then return scroll_col(cm_left, 1) end
|
||||
if addr and cm_right_addrs[addr] then return scroll_col(cm_right, 1) end
|
||||
elseif msg == "scrollup" then
|
||||
if addr and cm_left_addrs[addr] then return scroll_col(cm_left, -1) end
|
||||
if addr and cm_right_addrs[addr] then return scroll_col(cm_right, -1) end
|
||||
elseif msg == "reset" then
|
||||
cm_left.offset = 0; cm_right.offset = 0; return true
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- ─── Auto-scroll on focus ─────────────────────────────────────────────────────
|
||||
-- When a peek-slot window gets focus (Super+J/K or mouse click on the strip),
|
||||
-- dispatch the appropriate scroll so it slides into full view.
|
||||
local all_col_states = { ms, sm, cm_left, cm_right }
|
||||
|
||||
hl.on("window.active", function(w)
|
||||
if w == nil or w.address == nil then return end
|
||||
for _, state in ipairs(all_col_states) do
|
||||
if w.address == state.peek_bottom_addr then
|
||||
hl.dispatch(hl.dsp.layout("scrolldown")); return
|
||||
elseif w.address == state.peek_top_addr then
|
||||
hl.dispatch(hl.dsp.layout("scrollup")); return
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,35 @@
|
||||
{{- range .monitors }}
|
||||
hl.monitor({
|
||||
output = "{{ .name }}",
|
||||
mode = "{{ .width }}x{{ .height }}@{{ .refresh_rate }}",
|
||||
position = "{{ .position }}",
|
||||
scale = {{ .scale }}
|
||||
{{- if hasKey . "vrr" }},
|
||||
vrr = {{ .vrr }}
|
||||
{{- end }}
|
||||
{{- if hasKey . "transform" }},
|
||||
transform = {{ .transform }}
|
||||
{{- end }}
|
||||
{{- if hasKey . "bitdepth" }},
|
||||
bitdepth = {{ .bitdepth }}
|
||||
{{- end }}
|
||||
{{- if hasKey . "cm" }},
|
||||
cm = "{{ .cm }}"
|
||||
{{- end }}
|
||||
{{- if hasKey . "supports_hdr" }},
|
||||
supports_hdr = {{ .supports_hdr }}
|
||||
{{- end }}
|
||||
{{- if hasKey . "sdrbrightness" }},
|
||||
sdrbrightness = {{ .sdrbrightness }}
|
||||
{{- end }}
|
||||
{{- if hasKey . "sdrsaturation" }},
|
||||
sdrsaturation = {{ .sdrsaturation }}
|
||||
{{- end }}
|
||||
{{- if hasKey . "max_luminance" }},
|
||||
max_luminance = {{ .max_luminance }}
|
||||
{{- end }}
|
||||
})
|
||||
{{- end }}
|
||||
{{- if not .monitors }}
|
||||
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = 1 })
|
||||
{{- end }}
|
||||
@@ -0,0 +1,82 @@
|
||||
{{- $tags := .chezmoi.config.data.tags -}}
|
||||
|
||||
hl.config({
|
||||
group = {
|
||||
auto_group = true,
|
||||
insert_after_current = true,
|
||||
focus_removed_window = true,
|
||||
drag_into_group = 1,
|
||||
merge_groups_on_drag = true,
|
||||
merge_groups_on_groupbar = true,
|
||||
merge_floated_into_tiled_on_groupbar = true,
|
||||
group_on_movetoworkspace = false,
|
||||
-- colors to be integrated via theme table
|
||||
groupbar = {
|
||||
enabled = true,
|
||||
height = 12,
|
||||
font_family = "GeistMono Nerd Font",
|
||||
font_size = 8,
|
||||
font_weight_active = "semibold",
|
||||
font_weight_inactive = "normal",
|
||||
stacked = false,
|
||||
gradients = true,
|
||||
gradient_rounding = 5,
|
||||
indicator_height = 0,
|
||||
rounding = 0,
|
||||
gradient_round_only_edges = true,
|
||||
render_titles = true,
|
||||
scrolling = true,
|
||||
priority = 3
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
-- Permissions (NEW in v0.55)
|
||||
hl.permission({ binary = "/usr/bin/grim", type = "screencopy", mode = "allow" })
|
||||
|
||||
-- Window Rules
|
||||
hl.window_rule({ match = { class = ".*" }, suppress_event = "maximize" })
|
||||
|
||||
-- XWayland fixes
|
||||
hl.window_rule({
|
||||
match = { class = "^$", title = "^$", xwayland = true, float = true, fullscreen = false, pin = false },
|
||||
no_focus = true
|
||||
})
|
||||
hl.window_rule({ match = { xwayland = true }, no_initial_focus = true })
|
||||
|
||||
-- Communication
|
||||
hl.window_rule({ match = { class = "^(info\\.mumble\\.Mumble|discord|vesktop|teamspeak-client|TeamSpeak|TeamSpeak 3|teamspeak3)$" }, workspace = "2" })
|
||||
hl.window_rule({ match = { class = "^(Element)$" }, workspace = "3" })
|
||||
|
||||
-- Mail
|
||||
hl.window_rule({ match = { class = "^(org\\.mozilla\\.Thunderbird)$" }, workspace = "1" })
|
||||
|
||||
-- Notes
|
||||
hl.window_rule({ match = { class = "^(@joplin/app-desktop)$" }, workspace = "4" })
|
||||
|
||||
{{- if index $tags "entertainment" }}
|
||||
-- Multimedia
|
||||
hl.window_rule({ match = { class = "Spotify" }, workspace = "6" })
|
||||
{{- end }}
|
||||
|
||||
{{- if index $tags "cs2" }}
|
||||
-- Gaming
|
||||
hl.window_rule({ match = { class = "^(steam)$" }, workspace = "5" })
|
||||
{{- end }}
|
||||
|
||||
-- System
|
||||
hl.window_rule({ match = { class = "com.saivert.pwvucontrol" }, float = true })
|
||||
|
||||
hl.window_rule({
|
||||
match = { class = "scrrec" },
|
||||
float = true, pin = true, idle_inhibit = "always",
|
||||
rounding = 10, opacity = 0.6, border_size = 0,
|
||||
size = { 300, 100 }, move = { "1%", "1%" }, monitor = 0,
|
||||
no_initial_focus = true
|
||||
})
|
||||
|
||||
hl.window_rule({ match = { class = "com.gabm.satty" }, float = true, min_size = { 700, 400 } })
|
||||
|
||||
-- Layer Rules
|
||||
hl.layer_rule({ match = { namespace = "quickshell:.*" }, blur = true, ignore_alpha = 0.79 })
|
||||
hl.layer_rule({ match = { namespace = "quickshell:popout" }, animation = "bouncy" })
|
||||
@@ -0,0 +1,110 @@
|
||||
{{- $themeName := .chezmoi.config.data.theme -}}
|
||||
-- Theme Bridge: {{ $themeName }}
|
||||
|
||||
local theme = {}
|
||||
|
||||
{{- if eq $themeName "apex-neon" }}
|
||||
theme.base = "rgb(050505)"
|
||||
theme.surface = "rgb(141414)"
|
||||
theme.overlay = "rgb(262626)"
|
||||
theme.muted = "rgb(404040)"
|
||||
theme.text = "rgb(ededed)"
|
||||
theme.love = "rgb(ff0044)"
|
||||
theme.gold = "rgb(ffb700)"
|
||||
theme.pine = "rgb(00ff99)"
|
||||
theme.foam = "rgb(00eaff)"
|
||||
theme.iris = "rgb(9d00ff)"
|
||||
theme.rose = "rgb(ff8899)"
|
||||
|
||||
theme.splash_text = "rgba(edededee)"
|
||||
theme.dec_shadow = "rgba(000000ee)"
|
||||
theme.glow = "rgba(ff0044ee)"
|
||||
|
||||
theme.border_active = { colors = { "rgba(ff0044ff)", "rgba(9d00ffff)" }, angle = 45 }
|
||||
theme.border_inactive = { colors = { "rgba(40404044)", "rgba(14141444)" }, angle = 45 }
|
||||
|
||||
theme.border_nogroup_active = { colors = { "rgba(00eaffff)", "rgba(00ff99ff)" }, angle = 45 }
|
||||
theme.border_nogroup_inactive = { colors = { "rgba(40404044)", "rgba(14141444)" }, angle = 45 }
|
||||
|
||||
theme.border_group_active = { colors = { "rgba(ffb700ff)", "rgba(ff0044ff)" }, angle = 45 }
|
||||
theme.border_group_inactive = { colors = { "rgba(40404066)", "rgba(14141466)" }, angle = 45 }
|
||||
|
||||
theme.border_grouplocked_active = { colors = { "rgba(00eaffff)", "rgba(9d00ffff)" }, angle = 45 }
|
||||
theme.border_grouplocked_inactive = { colors = { "rgba(00eaff66)", "rgba(9d00ff66)" }, angle = 45 }
|
||||
|
||||
theme.groupbar_text = "rgba(050505ff)"
|
||||
theme.groupbar_active = { colors = { "rgba(ff0044ff)", "rgba(9d00ffaa)" } }
|
||||
theme.groupbar_inactive = { colors = { "rgba(141414ee)", "rgba(404040aa)" } }
|
||||
theme.groupbar_grouplocked_active = { colors = { "rgba(00eaffff)", "rgba(9d00ffff)" } }
|
||||
theme.groupbar_grouplocked_inactive = { colors = { "rgba(00eaffaa)", "rgba(9d00ffaa)" } }
|
||||
{{- else if eq $themeName "apex-aeon" }}
|
||||
theme.base = "rgb(f5f5f5)"
|
||||
theme.surface = "rgb(e8e8e8)"
|
||||
theme.overlay = "rgb(ffffff)"
|
||||
theme.muted = "rgb(737373)"
|
||||
theme.text = "rgb(0a0a0a)"
|
||||
theme.love = "rgb(ff0044)"
|
||||
theme.gold = "rgb(d18f00)"
|
||||
theme.pine = "rgb(00b377)"
|
||||
theme.foam = "rgb(007a88)"
|
||||
theme.iris = "rgb(7a3cff)"
|
||||
theme.rose = "rgb(ff4d6d)"
|
||||
|
||||
theme.splash_text = "rgba(0a0a0aee)"
|
||||
theme.dec_shadow = "rgba(73737388)"
|
||||
theme.glow = "rgba(ff0044ee)"
|
||||
|
||||
theme.border_active = { colors = { "rgba(ff0044ff)", "rgba(7a3cffff)" }, angle = 45 }
|
||||
theme.border_inactive = { colors = { "rgba(73737344)", "rgba(e8e8e844)" }, angle = 45 }
|
||||
|
||||
theme.border_nogroup_active = { colors = { "rgba(007a88ff)", "rgba(00b377ff)" }, angle = 45 }
|
||||
theme.border_nogroup_inactive = { colors = { "rgba(73737344)", "rgba(e8e8e844)" }, angle = 45 }
|
||||
|
||||
theme.border_group_active = { colors = { "rgba(d18f00ff)", "rgba(ff0044ff)" }, angle = 45 }
|
||||
theme.border_group_inactive = { colors = { "rgba(73737366)", "rgba(e8e8e866)" }, angle = 45 }
|
||||
|
||||
theme.border_grouplocked_active = { colors = { "rgba(007a88ff)", "rgba(7a3cffff)" }, angle = 45 }
|
||||
theme.border_grouplocked_inactive = { colors = { "rgba(007a8866)", "rgba(7a3cff66)" }, angle = 45 }
|
||||
|
||||
theme.groupbar_text = "rgba(0a0a0aff)"
|
||||
theme.groupbar_active = { colors = { "rgba(ff0044ff)", "rgba(7a3cffaa)" } }
|
||||
theme.groupbar_inactive = { colors = { "rgba(e8e8e8ee)", "rgba(737373aa)" } }
|
||||
theme.groupbar_grouplocked_active = { colors = { "rgba(007a88ff)", "rgba(7a3cffff)" } }
|
||||
theme.groupbar_grouplocked_inactive = { colors = { "rgba(007a88aa)", "rgba(7a3cffaa)" } }
|
||||
{{- end }}
|
||||
|
||||
-- Apply colors to config
|
||||
hl.config({
|
||||
general = {
|
||||
["col.active_border"] = theme.border_active,
|
||||
["col.inactive_border"] = theme.border_inactive,
|
||||
["col.nogroup_border"] = theme.border_nogroup_inactive,
|
||||
["col.nogroup_border_active"] = theme.border_nogroup_active
|
||||
},
|
||||
decoration = {
|
||||
shadow = {
|
||||
color = theme.dec_shadow
|
||||
},
|
||||
glow = {
|
||||
color = theme.glow
|
||||
}
|
||||
},
|
||||
group = {
|
||||
["col.border_active"] = theme.border_group_active,
|
||||
["col.border_inactive"] = theme.border_group_inactive,
|
||||
["col.border_locked_active"] = theme.border_grouplocked_active,
|
||||
["col.border_locked_inactive"] = theme.border_grouplocked_inactive,
|
||||
groupbar = {
|
||||
text_color = theme.groupbar_text,
|
||||
["col.active"] = theme.groupbar_active,
|
||||
["col.inactive"] = theme.groupbar_inactive,
|
||||
["col.locked_active"] = theme.groupbar_grouplocked_active,
|
||||
["col.locked_inactive"] = theme.groupbar_grouplocked_inactive
|
||||
}
|
||||
},
|
||||
misc = {
|
||||
["col.splash"] = theme.splash_text
|
||||
}
|
||||
})
|
||||
|
||||
return theme
|
||||
@@ -0,0 +1,41 @@
|
||||
{{- $tags := .chezmoi.config.data.tags -}}
|
||||
|
||||
-- Special Workspaces
|
||||
hl.workspace_rule({ workspace = "special:passwordmgr", on_created_empty = "uwsm app -- bitwarden-desktop" })
|
||||
|
||||
-- Named Workspaces (IDs 1-6, sorted before numbered)
|
||||
hl.workspace_rule({ workspace = "1", default_name = "mail" })
|
||||
hl.workspace_rule({ workspace = "2", default_name = "comms", monitor = "DP-2", layout = "scrolling", layout_opts = { direction = "down" } })
|
||||
hl.workspace_rule({ workspace = "3", default_name = "element", monitor = "DP-2", layout = "scrolling", layout_opts = { direction = "down" } })
|
||||
hl.workspace_rule({ workspace = "4", default_name = "joplin" })
|
||||
|
||||
{{- if index $tags "cs2" }}
|
||||
hl.workspace_rule({ workspace = "5", default_name = "steam", layout = "scrolling" })
|
||||
{{- end }}
|
||||
|
||||
{{- if index $tags "entertainment" }}
|
||||
hl.workspace_rule({ workspace = "6", default_name = "spotify", monitor = "DP-2", layout = "monocle", on_created_empty = "uwsm app -- spotify-launcher" })
|
||||
{{- end }}
|
||||
|
||||
-- Monitor Workspaces
|
||||
{{- range $monitor := .monitors }}
|
||||
{{- range $index, $ws := $monitor.workspaces }}
|
||||
{{- if kindIs "map" $ws }}
|
||||
hl.workspace_rule({
|
||||
workspace = "{{ $ws.id }}",
|
||||
monitor = "{{ $monitor.name }}",
|
||||
default = {{ if eq $index 0 }}true{{ else }}false{{ end }}
|
||||
{{- if index $ws "name" }}, default_name = "{{ $ws.name }}"{{ end }}
|
||||
{{- if index $ws "layout" }}, layout = "{{ $ws.layout }}"{{ end }}
|
||||
{{- if index $ws "layoutopts" }}, layout_opts = {
|
||||
{{- range $i, $opt := index $ws "layoutopts" }}
|
||||
{{- $parts := splitList ":" $opt }}
|
||||
{{ if $i }}, {{ end }}{{ index $parts 0 }} = "{{ index $parts 1 }}"
|
||||
{{- end }}
|
||||
}{{ end }}
|
||||
})
|
||||
{{- else }}
|
||||
hl.workspace_rule({ workspace = "{{ $ws }}", monitor = "{{ $monitor.name }}", default = {{ if eq $index 0 }}true{{ else }}false{{ end }} })
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,14 @@
|
||||
{{- $tags := .chezmoi.config.data.tags -}}
|
||||
-- Main Hyprland Lua Config Entry Point (v0.55+)
|
||||
|
||||
local cfgdir = os.getenv("HOME") .. "/.config/hypr"
|
||||
package.path = package.path .. ";" .. cfgdir .. "/hyprland.d.lua/?.lua"
|
||||
|
||||
require("theme")
|
||||
require("general")
|
||||
require("monitors")
|
||||
require("workspaces")
|
||||
require("input")
|
||||
require("layout")
|
||||
require("rules")
|
||||
require("keybinds")
|
||||
Reference in New Issue
Block a user