56 changed files with 1033 additions and 76 deletions

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
{
"diagnostics": {
"globals": [
"hl"
]
},
"workspace": {
"library": [
"/usr/share/hypr/stubs",
"/usr/share/hyprland/stubs"
],
"checkThirdParty": false
}
}
+412
View File
@@ -0,0 +1,412 @@
-- ~/.config/hypr/functions.lua
local M = {}
-- focusOrLaunch: checks if a window with the given class exists.
-- If it does, focuses it (and sends specific shortcuts if it's qutebrowser).
-- If not, optionally switches to a workspace and launches the app.
function M.focusOrLaunch(app_cmd, class_name, workspace_name)
local clients = hl.get_windows()
local found = false
for _, client in ipairs(clients) do
if client.class == class_name then
hl.dispatch(hl.dsp.focus({ window = "class:" .. class_name }))
-- Specific qutebrowser logic ported from your zsh script
if app_cmd:match("qutebrowser") then
hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "Escape", window = "class:" .. class_name }))
hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "0", window = "class:" .. class_name }))
hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "1", window = "class:" .. class_name }))
hl.dispatch(hl.dsp.send_shortcut({ mods = "ALT", key = "J", window = "class:" .. class_name }))
hl.dispatch(hl.dsp.send_shortcut({ mods = "", key = "Escape", window = "class:" .. class_name }))
end
found = true
break
end
end
if not found then
if workspace_name and #workspace_name > 0 then
hl.dispatch(hl.dsp.focus({ workspace = workspace_name }))
end
hl.dispatch(hl.dsp.exec_cmd(app_cmd))
end
end
function M.getActiveWorkspaceName()
local f = io.popen("hyprctl -j activeworkspace | jq -r '.name'")
local active_ws_name = f:read("*a"):gsub("%s+", "")
f:close()
return active_ws_name
end
function M.mv2workspace()
local cmd = [[
bash -c '
WORKSPACES=$(hyprctl -j workspaces | jq -r ".[] | .name")
ACTIVE="$(hyprctl -j activewindow)"
CLASS="$(jq -r ".class" <<< "$ACTIVE")"
TITLE="$(jq -r ".title" <<< "$ACTIVE")"
ADDR="$(jq -r ".address" <<< "$ACTIVE")"
PROMPT="Move window of ${CLASS} with title '\''${TITLE}'\'' to which workspace? "
NEW_WS="$(echo "$WORKSPACES" | rofi -dmenu -p "$PROMPT")"
if [ -n "$NEW_WS" ]; then
hyprctl dispatch "hl.dsp.window.move({ workspace = \"$NEW_WS\", window = \"address:$ADDR\", follow = false })"
fi
'
]]
hl.dispatch(hl.dsp.exec_cmd(cmd))
end
function M.createWorkspace(after_name)
local after_str = after_name or ""
local cmd = string.format([[
bash -c '
NEW_NAME=$(rofi -dmenu -p "new workspace name" -l 0 < /dev/null | tr -d "[:space:]")
if [ -z "$NEW_NAME" ]; then exit 0; fi
ACTIVE_MON=$(hyprctl -j monitors | jq -r ".[] | select(.focused) | .name")
NEW_LINE=" { name = \"$NEW_NAME\", monitor = \"$ACTIVE_MON\" },"
FILE="$HOME/.config/hypr/workspace_config.lua"
AFTER="%s"
if [ -n "$AFTER" ] && grep -q "name *= *\"$AFTER\"" "$FILE"; then
sed -i "/name *= *\"$AFTER\"/a \\$NEW_LINE" "$FILE"
else
sed -i "/^}/i \\$NEW_LINE" "$FILE"
fi
hyprctl reload
'
]], after_str)
hl.dispatch(hl.dsp.exec_cmd(cmd))
end
function M.deleteWorkspace()
local cmd = [[
bash -c '
ACTIVE_WS=$(hyprctl -j activeworkspace | jq -r ".name")
if [ -z "$ACTIVE_WS" ]; then exit 0; fi
FILE="$HOME/.config/hypr/workspace_config.lua"
sed -i "/name *= *\"$ACTIVE_WS\"/d" "$FILE"
hyprctl dispatch "hl.dsp.focus({ workspace = \"m-1\" })"
hyprctl reload
'
]]
hl.dispatch(hl.dsp.exec_cmd(cmd))
end
function M.moveWorkspace(direction, target_monitor)
-- If no target_monitor is provided and direction is nil, prompt interactively via async bash to avoid deadlocks
if (not target_monitor or target_monitor == "") and not direction then
local cmd = [[
bash -c '
ACTIVE_WS=$(hyprctl -j activeworkspace | jq -r ".name")
if [ -z "$ACTIVE_WS" ]; then exit 0; fi
MONITORS=$(hyprctl -j monitors | jq -r ".[].name")
PROMPT="Move workspace '\''$ACTIVE_WS'\'' to which monitor? "
NEW_MON=$(echo "$MONITORS" | rofi -dmenu -p "$PROMPT")
if [ -n "$NEW_MON" ]; then
FILE="$HOME/.config/hypr/workspace_config.lua"
sed -i -E "/name *= *\"$ACTIVE_WS\"/ s/monitor *= *\"[^\"]+\"/monitor = \"$NEW_MON\"/" "$FILE"
hyprctl reload
fi
'
]]
hl.dispatch(hl.dsp.exec_cmd(cmd))
return
end
-- NATIVE LUA PATH for directional shifts or explicit monitor arguments passed from keybinds
local active_ws_name = M.getActiveWorkspaceName()
if not active_ws_name or active_ws_name == "" then return end
local path = os.getenv("HOME") .. "/.config/hypr/workspace_config.lua"
local f_in = io.open(path, "r")
if not f_in then return end
local lines = {}
local ws_idx = nil
for line in f_in:lines() do
table.insert(lines, line)
if line:match('name%s*=%s*"' .. active_ws_name .. '"') then
ws_idx = #lines
end
end
f_in:close()
if not ws_idx then return end
if target_monitor and target_monitor ~= "" then
lines[ws_idx] = lines[ws_idx]:gsub('monitor%s*=%s*"[^"]+"', 'monitor = "' .. target_monitor .. '"')
end
if direction then
local first_idx = 2
local last_idx = #lines - 1
while first_idx < #lines and not lines[first_idx]:match("name%s*=") do first_idx = first_idx + 1 end
while last_idx > 1 and not lines[last_idx]:match("name%s*=") do last_idx = last_idx - 1 end
if first_idx <= last_idx then
local current_line = table.remove(lines, ws_idx)
local new_idx = ws_idx
if direction > 0 then
new_idx = ws_idx + 1
if new_idx > last_idx then
new_idx = first_idx
end
elseif direction < 0 then
new_idx = ws_idx - 1
if new_idx < first_idx then
new_idx = last_idx
end
end
table.insert(lines, new_idx, current_line)
end
end
local f_out = io.open(path, "w")
for _, line in ipairs(lines) do
f_out:write(line .. "\n")
end
f_out:close()
hl.dispatch(hl.dsp.exec_cmd("hyprctl reload"))
end
function M.pasteMacro(script_path)
local cmd = string.format([[
bash -c '
exec > /tmp/paste.log 2>&1
out="$(%s)"
echo -n "$out" | wl-copy
# 1. Wait a moment for physical keys to be released
sleep 0.3
# 2. Release modifiers logically, then execute CTRL+V
wtype -m logo -m ctrl -m shift -m alt -M ctrl -k v -m ctrl
# 3. Wait a full second to ensure Wayland apps finish reading the clipboard asynchronously
sleep 1.0
# 4. Scrub the password from cliphist securely
PREV=$(cliphist list | awk "NR==2")
cliphist list | awk "NR==1" | cliphist delete
if [ -n "$PREV" ]; then
echo "$PREV" | cliphist decode | wl-copy
else
wl-copy -c
fi
'
]], script_path)
hl.dispatch(hl.dsp.exec_cmd(cmd))
end
function M.pasteText(text)
local cmd = string.format([[
bash -c '
exec &> /tmp/$(date +%%FT%%H%%M%%S)_paste.log
echo -n "%s" | wl-copy
# Wait a moment for physical keys to be released, then execute CTRL+V
sleep 0.3
wtype -m logo -m ctrl -m shift -m alt -M ctrl -k v -m ctrl
# Note: We do not scrub cliphist here because dates are not sensitive passwords,
# which completely eliminates the paste race condition for dates!
'
]], text)
hl.dispatch(hl.dsp.exec_cmd(cmd))
end
function M.timetracker()
local cmd = [[
bash -c '
CHOICE=$(echo -e "Begin...\nEnd..." | rofi -dmenu -theme-str "inputbar {enabled: false;}" -i -matching prefix -auto-select -l 2)
if [ "$CHOICE" = "Begin..." ]; then
DATE_STR=$(date "+%F %T")
wtype -m logo -m ctrl -m shift -m alt -k Escape
wtype "I${DATE_STR}: Begin "
elif [ "$CHOICE" = "End..." ]; then
DATE_STR=$(date "+%F %T")
wtype -m logo -m ctrl -m shift -m alt -k Escape
wtype "I${DATE_STR}: End "
wtype -k Escape
wtype "kly\$jpo"
wtype "${DATE_STR}: Begin "
fi
'
]]
hl.dispatch(hl.dsp.exec_cmd(cmd))
end
function M.randomizeWallpaper()
local monitors = hl.get_monitors()
local home = os.getenv("HOME")
local april_dir = home .. "/images/April"
local sbm_dir = home .. "/images/Seven Beats Music"
local black_png = home .. "/images/black.png"
-- Determine if we are on the home WiFi ("keep")
local is_home_wifi = false
local f = io.popen("iwgetid -r 2>/dev/null")
if f then
local ssid = f:read("*a")
f:close()
if ssid and ssid:match("keep") then
is_home_wifi = true
end
end
-- Select image source based on location
local img_dir = april_dir
if #monitors > 1 then
img_dir = sbm_dir
elseif is_home_wifi then
img_dir = april_dir
end
local images = {}
local handle = io.popen('find "' .. img_dir .. '" -type f \\( -iname "*.jpg" -o -iname "*.png" -o -iname "*.jpeg" \\) 2>/dev/null')
if handle then
for line in handle:lines() do
table.insert(images, line)
end
handle:close()
end
if #images == 0 then
table.insert(images, black_png)
end
local conf_lines = {
"ipc = on",
"splash = false",
""
}
-- Pick a unique random image for each monitor
math.randomseed(os.time())
local used = {}
local matugen_cmds = {}
local primary_logical_name = "eDP-1"
for _, mon in ipairs(monitors) do
if mon.description and mon.description:match("E3LMTF071391") then
primary_logical_name = "DP-8"
end
end
for _, mon in ipairs(monitors) do
local img = nil
local attempts = 0
while attempts < #images do
local idx = math.random(1, #images)
if not used[idx] then
img = images[idx]
used[idx] = true
break
end
attempts = attempts + 1
end
if not img then img = images[1] end -- fallback if we run out of unique images
if img then
table.insert(conf_lines, "wallpaper {")
table.insert(conf_lines, " monitor = " .. mon.name)
table.insert(conf_lines, " path = " .. img)
table.insert(conf_lines, " fit_mode = cover")
table.insert(conf_lines, "}")
table.insert(conf_lines, "")
local logical_name = mon.name
if mon.description then
if mon.description:match("E3LMTF071438") then logical_name = "DP-7" end
if mon.description:match("E3LMTF071391") then logical_name = "DP-8" end
end
local matugen_cmd = "matugen image '" .. img .. "' -c /home/trey/src/dotfiles/matugen/matugen-" .. logical_name .. ".toml --source-color-index 0"
-- Push primary monitor to the end of the array so its global configs (like tmux) overwrite the others
if logical_name == primary_logical_name then
table.insert(matugen_cmds, matugen_cmd)
else
table.insert(matugen_cmds, 1, matugen_cmd)
end
end
end
if #matugen_cmds > 0 then
local all_cmds = table.concat(matugen_cmds, " ; ")
local reload_cmd = M.reloadAppsCmd()
hl.dispatch(hl.dsp.exec_cmd("bash -c \"" .. all_cmds .. " ; " .. reload_cmd .. "\""))
end
local c = io.open(home .. "/.config/hypr/hyprpaper.conf", "w")
if c then
c:write(table.concat(conf_lines, "\n"))
c:close()
end
-- Restart hyprpaper natively to avoid uwsm systemd rate limits on rapid monitor hotplugs
hl.dispatch(hl.dsp.exec_cmd("killall -q hyprpaper; sleep 0.2; hyprpaper &"))
end
function M.reloadAppsCmd()
-- Return a shell command to reload apps that don't auto-reload (executed sequentially after matugen)
local cmds = {
"tmux source ~/.config/tmux/tmux.conf",
"for pane in $(tmux list-panes -a -F '#{pane_id} #{pane_current_command}' | awk '$2 == \"vim\" {print $1}'); do tmux send-keys -t $pane Escape ':colorscheme matugen' Enter; done",
"killall -q -USR1 zsh"
}
return table.concat(cmds, " ; ")
end
function M.setupMonitors()
-- Automatically handle the wallpaper update natively
M.randomizeWallpaper()
local monitors = hl.get_monitors()
local logical_to_physical = {
["eDP-1"] = "eDP-1"
}
for _, mon in ipairs(monitors) do
if mon.description then
if mon.description:match("E3LMTF071438") then logical_to_physical["DP-7"] = mon.name end
if mon.description:match("E3LMTF071391") then logical_to_physical["DP-8"] = mon.name end
end
end
local home = os.getenv("HOME")
for logical, physical in pairs(logical_to_physical) do
local config_path = home .. "/.config/waybar/config-" .. logical .. ".jsonc"
local f = io.open(config_path, "r")
if f then
local content = f:read("*a")
f:close()
-- Replace any existing output value with the actual physical monitor name
content = content:gsub('"output":%s*"[^"]+"', '"output": "' .. physical .. '"')
local w = io.open(config_path, "w")
if w then
w:write(content)
w:close()
end
end
end
-- Dynamically spawn waybar only for connected monitors, and use a robust kill sequence
-- inside the background script to prevent race conditions during rapid hotplug events
local waybar_cmds = {
"killall -q waybar",
"sleep 0.5",
"killall -q waybar"
}
for logical, _ in pairs(logical_to_physical) do
table.insert(waybar_cmds, string.format("nohup waybar -c ~/.config/waybar/config-%s.jsonc -s ~/.config/waybar/style-%s.css >/dev/null 2>&1 &", logical, logical))
end
hl.dispatch(hl.dsp.exec_cmd("bash -c '" .. table.concat(waybar_cmds, "\n") .. "'"))
end
return M
+8 -12
View File
@@ -1,7 +1,8 @@
general {
lock_cmd = pidof hyprlock || hyprlock # avoid starting multiple hyprlock instances.
unlock_cmd = ~/.local/bin/unlock-ring.sh # run when session is unlocked (hyprlock exits)
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.
after_sleep_cmd = hyprctl eval 'hl.dispatch(hl.dsp.dpms({ action = "on" }))' # to avoid having to press a key twice to turn on the display.
}
listener {
@@ -17,26 +18,21 @@ listener {
on-resume = brightnessctl -rd rgb:kbd_backlight # turn on keyboard backlight.
}
#listener {
# timeout = 300 # 5min
# on-timeout = loginctl lock-session # lock screen when timeout has passed
#}
listener {
timeout = 300
# 1. Lock the vault via DBus, 2. Start the lock screen
on-timeout = busctl --user call org.freedesktop.secrets /org/freedesktop/secrets org.freedesktop.Secret.Service Lock "as" 1 "/org/freedesktop/secrets/aliases/default" && hyprlock
# When hyprlock exits (via fingerprint or password), instantly run the unlock script
on-resume = ~/.local/bin/unlock-ring.sh
# 1. Lock the vault via DBus, 2. Start the lock screen (using ';' so it locks even if the vault fails/is already locked)
on-timeout = busctl --user call org.freedesktop.secrets /org/freedesktop/secrets org.freedesktop.Secret.Service Lock "as" 1 "/org/freedesktop/secrets/aliases/default" ; loginctl lock-session
}
listener {
timeout = 330 # 5.5min
on-timeout = hyprctl dispatch dpms off # screen off when timeout has passed
on-resume = hyprctl dispatch dpms on # screen on when activity is detected after timeout has fired.
on-timeout = hyprctl eval 'hl.dispatch(hl.dsp.dpms({ action = "off" }))' # screen off when timeout has passed
on-resume = hyprctl eval 'hl.dispatch(hl.dsp.dpms({ action = "on" }))' # screen on when activity is detected after timeout has fired.
}
listener {
timeout = 900 # 10min
on-timeout = systemd-ac-power || systemctl suspend # only suspend pc on battery
}
# vim:ft=hyprlang
+317
View File
@@ -0,0 +1,317 @@
-- ~/.config/hypr/hyprland.luaA
-----------------------------------------------------
-- Variables (from variables.conf)
-----------------------------------------------------
local copy = "wl-copy --trim-newline"
local paste = "wl-paste"
local launcher = "rofi -show run -p 'launch'"
-- local clear = "cliphist list | head -n1 | cliphist delete"
-----------------------------------------------------
-- Monitors (from monitors.conf)
-----------------------------------------------------
hl.monitor({ output = "desc:Ancor Communications Inc ASUS VS228 E3LMTF071438", mode = "preferred", position = "0x0", scale = "1" }) -- DP-7, left
hl.monitor({ output = "desc:Samsung Display Corp. 0x4193", mode = "preferred", position = "1920x0", scale = "2" }) -- eDP-1, center
hl.monitor({ output = "desc:Ancor Communications Inc ASUS VS228 E3LMTF071391", mode = "preferred", position = "3360x0", scale = "1" }) -- DP-8, right
hl.monitor({ output = "desc:Stargate Technology FHD", mode = "preferred", position = "auto", scale = "auto" }) -- portable monitor
-----------------------------------------------------
-- Environment Variables (from env.conf)
-----------------------------------------------------
hl.env("GDK_BACKEND", "wayland,x11,*")
hl.env("GUI_ENABLE_WAYLAND", "1")
hl.env("HYPRCURSOR_SIZE", "18")
hl.env("QT_QPA_PLATFORM", "wayland;xcb")
hl.env("QT_QPA_PLATFORMTHEME", "qt6ct")
hl.env("QT_AUTO_SCREEN_SCALE_FACTOR", "1")
hl.env("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1")
hl.env("XCURSOR_SIZE", "18")
hl.env("XDG_CURRENT_DESKTOP", "Hyprland")
hl.env("XDG_SESSION_TYPE", "wayland")
hl.env("XDG_SESSION_DESKTOP", "Hyprland")
-----------------------------------------------------
-- Appearance & Input (from appearance.conf & input.conf)
-----------------------------------------------------
hl.config({
general = {
gaps_in = 1,
gaps_out = 2,
border_size = 2,
col = {
active_border = "rgba(fabd2f99)",
inactive_border = "rgba(595959aa)",
},
resize_on_border = false,
allow_tearing = false,
layout = "dwindle"
},
decoration = {
rounding = 10,
rounding_power = 2,
active_opacity = 1.0,
inactive_opacity = 1.0,
shadow = {
enabled = true,
range = 4,
render_power = 3,
color = "rgba(1a1a1aee)"
},
blur = {
enabled = true,
size = 3,
passes = 1,
vibrancy = 0.1696
}
},
animations = {
enabled = true,
bezier = {
"easeOutQuint,0.23,1,0.32,1",
"easeInOutCubic,0.65,0.05,0.36,1",
"linear,0,0,1,1",
"almostLinear,0.5,0.5,0.75,1.0",
"quick,0.15,0,0.1,1"
},
animation = {
"global, 1, 10, default",
"border, 1, 5.39, easeOutQuint",
"windows, 1, 4.79, easeOutQuint",
"windowsIn, 1, 4.1, easeOutQuint, popin 87%",
"windowsOut, 1, 1.49, linear, popin 87%",
"fadeIn, 1, 1.73, almostLinear",
"fadeOut, 1, 1.46, almostLinear",
"fade, 1, 3.03, quick",
"layers, 1, 3.81, easeOutQuint",
"layersIn, 1, 4, easeOutQuint, fade",
"layersOut, 1, 1.5, linear, fade",
"fadeLayersIn, 1, 1.79, almostLinear",
"fadeLayersOut, 1, 1.39, almostLinear",
"workspaces, 1, 1.94, almostLinear, fade",
"workspacesIn, 1, 1.21, almostLinear, fade",
"workspacesOut, 1, 1.94, almostLinear, fade"
}
},
dwindle = { preserve_split = true },
master = { new_status = "master" },
misc = {
on_focus_under_fullscreen = 1,
force_default_wallpaper = 0,
disable_hyprland_logo = true
},
input = {
resolve_binds_by_sym = 1,
kb_layout = "us, us",
kb_variant = "colemak,",
kb_options = "ctrl:nocaps, compose:ralt, grp:ctrls_toggle",
follow_mouse = 1,
sensitivity = 0,
touchpad = { natural_scroll = false }
}})
hl.device({
name = "epic-mouse-v1",
sensitivity = -0.5
})
-----------------------------------------------------
-- Window Rules (from windowrules.conf)
-----------------------------------------------------
-- XWayland dragging
hl.window_rule({
no_focus = true,
match = { xwayland = true, float = true, fullscreen = false, pin = false }
})
-- Floating rules
local float_class_regex = "(gcr-prompter|qt5ct|qt6ct|simple-scan|espanso|nm-applet)"
local float_title_regex = "(\"HP Device Manager - Plug-in Installer\")"
local float_regex = "(" .. float_class_regex .. "|" .. float_title_regex .. ")"
hl.window_rule({ float = true, match = { class = float_class_regex } })
hl.window_rule({ float = true, match = { title = float_title_regex } })
-- Pin rules
local pin_regex = "(gcr-prompter|nm-applet)"
hl.window_rule({ pin = true, match = { class = pin_regex } })
-- Maximize everything else (fixed typo fioat_regex -> float_regex)
hl.window_rule({
maximize = true,
match = { class = "negative:" .. float_regex }
})
-- Espanso on special
hl.window_rule({
workspace = "special",
match = { title = "Espanso Sync Tool" }
})
-- App-specific workspaces
local browsers = "(.*qutebrowser.*|.*[Ff]irefox.*)"
local meeting = "(.*[Zz]oom.*)"
hl.window_rule({ workspace = "name:shell", match = { class = ".*[Aa]lacritty.*" } })
hl.window_rule({ workspace = "name:browser silent", match = { class = browsers } })
hl.window_rule({ workspace = "name:jobs silent", match = { class = ".*[Vv]ivaldi-stable.*" } })
hl.window_rule({ workspace = "name:meeting silent", match = { class = meeting } })
-----------------------------------------------------
-- Workspaces (from workspaces.conf)
-----------------------------------------------------
require("workspaces")
-----------------------------------------------------
-- Keybindings & Macros (from keybindings.conf & macros.conf)
-----------------------------------------------------
local bind = hl.bind
local dsp = hl.dsp
local fn = require("functions")
bind("SUPER + Q", dsp.exec_cmd("hyprctl reload && hyprctl eval 'require(\"functions\").setupMonitors()' && notify-send 'Hyprland' 'Config Reloaded' -u low -t 2000"))
-- BUG FIX: SUPER + SHIFT + Q was bound to both tmux kill and uwsm stop.
-- bind("SUPER + SHIFT + Q", dsp.exec_cmd("tmux kill-server"))
bind("SUPER + SHIFT + Q", dsp.exec_cmd("killall -q signal-desktop; uwsm stop"))
bind("CTRL + ALT + Delete", dsp.exec_cmd("bash ~/.config/hypr/scripts/sysmenu.sh"))
bind("SUPER + L", dsp.exec_cmd("hyprlock"))
bind("switch:Lid Switch", dsp.exec_cmd("hyprlock"))
-- Screenshots (evaluated natively in Lua at keypress time)
bind("CTRL + SHIFT + 3", function()
local cmd = string.format("grim 'images/screenshots/%s_screenshot.png' && paplay ~/wav/camera.wav", os.date("%FT%H%M%S"))
hl.dispatch(hl.dsp.exec_cmd(cmd))
end)
bind("CTRL + SHIFT + 4", function()
local cmd = string.format("slurp | grim -g - '%s_screenshot.png' && paplay ~/wav/camera.wav", os.date("%FT%H%M%S"))
hl.dispatch(hl.dsp.exec_cmd(cmd))
end)
-- App launchers using our custom Lua focusOrLaunch function
bind("SUPER + return", function() fn.focusOrLaunch("alacritty", "Alacritty") end)
bind("SUPER + SHIFT + A", function() fn.focusOrLaunch("slack", "Slack", "Slack") end)
bind("SUPER + SHIFT + D", function() fn.focusOrLaunch("gnome-dictionary", "org.gnome.Dictionary", "org.gnome.Dictionary") end)
bind("SUPER + G", function() fn.focusOrLaunch("vivaldi-stable", "vivaldi-stable", "vivaldi-stable") end)
bind("SUPER + ALT + H", function() fn.focusOrLaunch("qutebrowser", "org.qutebrowser.qutebrowser") end)
-- Windows 11 equivalent shortcuts
bind("SUPER + S", dsp.exec_cmd(launcher)) -- Win+S for Search/Launcher
bind("SUPER + R", dsp.exec_cmd(launcher)) -- Win+R for Run
bind("SUPER + E", dsp.exec_cmd("nautilus")) -- Win+E for Explorer
bind("SUPER + V", dsp.exec_cmd("cliphist list | rofi -dmenu -p 'paste' | cliphist decode | " .. copy .. " && " .. paste)) -- Win+V for Clipboard
bind("SUPER + SHIFT + S", function() fn.focusOrLaunch("pavucontrol", "org.pulseaudio.pavucontrol", "org.pulseaudio.pavucontrol") end)
-- Window control
bind("SUPER + C", dsp.window.close())
bind("ALT + F4", dsp.window.close())
bind("SUPER + T", dsp.window.float({ action = "toggle" }))
bind("SUPER + space", dsp.window.fullscreen({ mode = 1 }))
-- Cycle Layouts
local layouts = { "dwindle", "master", "scrolling" }
local layout_icons = { dwindle = "", master = "", scrolling = "" }
local layout_idx = 1
local function cycle_layout()
layout_idx = (layout_idx % #layouts) + 1
local new_layout = layouts[layout_idx]
hl.config({ general = { layout = new_layout } })
os.execute("echo '" .. layout_icons[new_layout] .. "' > /tmp/hypr_layout.txt")
os.execute("pkill -RTMIN+8 waybar")
end
bind("SUPER + right", function() cycle_layout() end)
bind("SUPER + left", function() cycle_layout() end)
bind("SUPER + up", dsp.window.fullscreen({ mode = 1 }))
bind("SUPER + down", hl.dsp.window.cycle_next())
-- XMonad-style cycle & Alt+Tab (using native dsp dispatchers)
bind("SUPER + J", hl.dsp.window.cycle_next())
bind("SUPER + K", hl.dsp.window.cycle_next({ prev = true }))
bind("ALT + Tab", hl.dsp.window.cycle_next())
bind("ALT + SHIFT + Tab", hl.dsp.window.cycle_next({ prev = true }))
bind("SUPER + X", dsp.exec_cmd("dunstctl close"))
bind("SUPER + Y", dsp.exec_cmd("dunstctl close-all"))
-- Workspace control
bind("SUPER + CTRL + right", function() hl.dispatch(hl.dsp.focus({ workspace = "m+1" })) end)
bind("SUPER + CTRL + left", function() hl.dispatch(hl.dsp.focus({ workspace = "m-1" })) end)
bind("SUPER + SHIFT + H", function() hl.dispatch(hl.dsp.focus({ workspace = "m-1" })) end)
bind("SUPER + SHIFT + L", function() hl.dispatch(hl.dsp.focus({ workspace = "m+1" })) end)
bind("SUPER + SHIFT + M", function() fn.mv2workspace() end)
bind("SUPER + SHIFT + N", function() fn.createWorkspace() end)
bind("SUPER + SHIFT + X", function() fn.deleteWorkspace() end)
bind("SUPER + SHIFT + Z", function() fn.createWorkspace(fn.getActiveWorkspaceName()) end)
bind("SUPER + SHIFT + left", function() fn.moveWorkspace(-1) end)
bind("SUPER + SHIFT + right", function() fn.moveWorkspace(1) end)
bind("SUPER + ALT + DOWN", function() fn.moveWorkspace(nil, "desc:Ancor Communications Inc ASUS VS228 E3LMTF071391") end)
-- Laptop keys
bind("XF86MonBrightnessUp", dsp.exec_cmd("brightnessctl -- set +10%"))
bind("XF86MonBrightnessDown", dsp.exec_cmd("brightnessctl -- set -10%"))
-- Macros (uses fn.pasteText and native Lua os.time/os.date instead of bash scripts)
bind("SUPER + CTRL + S", function() fn.pasteText(os.date("%F")) end)
bind("SUPER + CTRL + N", function() fn.pasteText(os.date("%FT%T %z")) end)
bind("SUPER + CTRL + Y", function() fn.pasteText(os.date("%F", os.time() - 86400)) end)
bind("SUPER + CTRL + T", function() fn.pasteText(os.date("%F", os.time() + 86400)) end)
-- Timetracker Macro
bind("CTRL + SHIFT + S", function() fn.timetracker() end)
-- Bitwarden Macro
bind("SUPER + CTRL + 1", function() fn.pasteMacro("secret-tool lookup app bw") end)
-----------------------------------------------------
-- Autostart (from autostart.conf)
-----------------------------------------------------
hl.on("hyprland.start", function()
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
hl.exec_cmd("hyprpolkitagent")
hl.exec_cmd("uwsm app -- hypridle")
hl.exec_cmd("hyprpaper &")
-- Waybar is now managed entirely by functions.setupMonitors()
hl.exec_cmd("uwsm app -- espanso start")
hl.exec_cmd("uwsm app -- dunst")
hl.exec_cmd("nm-applet")
hl.exec_cmd("signal-desktop")
hl.exec_cmd("hp-systray")
hl.exec_cmd("blueman-applet")
hl.exec_cmd("wl-paste --watch cliphist store")
hl.exec_cmd("wl-clip-persist --clipboard regular")
hl.exec_cmd("qutebrowser")
hl.exec_cmd("vivaldi-stable")
hl.exec_cmd("alacritty")
-- Start the battery low-level notifier
hl.exec_cmd("bash ~/.config/hypr/scripts/battery-notify.sh")
-- Randomize wallpapers after hyprpaper has time to start
hl.exec_cmd("sleep 2 && hyprctl eval 'require(\"functions\").randomizeWallpaper()'")
end)
-----------------------------------------------------
-- Monitor Hotplug Events
-----------------------------------------------------
hl.on("monitor.added", function()
require("functions").setupMonitors()
end)
hl.on("monitor.removed", function()
require("functions").setupMonitors()
end)
+1 -10
View File
@@ -46,7 +46,7 @@ label {
}
label {
text = cmd[update:60] uptime -p
text = cmd[update:60000] uptime -p
color = rgb(ebdbb2)
offont_size = 10
font_family = monospace
@@ -56,15 +56,6 @@ label {
valign = center
}
listener {
timeout = 300 # 5 minutes
on-timeout = sleep 60 && busctl --user call org.freedesktop.secrets /org/freedesktop/secrets org.freedesktop.Secret.Service Lock "as" 1 "/org/freedesktop/secrets/aliases/default" && hyprlock
}
listener {
timeout = 330 # 5.5 minutes
on-timeout = hyprctl dispatch dpms off # screen off
on-resume = hyprctl dispatch dpms on # screen on
}
# vim:ft=hyprlang
+7 -12
View File
@@ -1,25 +1,20 @@
ipc = on
splash = false
wallpaper {
monitor = eDP-1
path = /home/trey/images/black.png
path = /home/trey/images/Seven Beats Music/2025-02-24.jpg
fit_mode = cover
}
wallpaper {
monitor = DP-7
path = /home/trey/images/Seven Beats Music/2024-09-16.jpg
monitor = DP-9
path = /home/trey/images/Seven Beats Music/2024-09-09.jpg
fit_mode = cover
}
wallpaper {
monitor = DP-8
path = /home/trey/images/Seven Beats Music/2024-10-14.jpg
monitor = DP-10
path = /home/trey/images/Seven Beats Music/2026-01-26.jpg
fit_mode = cover
}
wallpaper {
monitor =
path = /home/trey/images/black.png
fit_mode = cover
}
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
+42
View File
@@ -0,0 +1,42 @@
general {
lock_cmd = pidof hyprlock || hyprlock # avoid starting multiple hyprlock 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.
}
listener {
timeout = 150 # 2.5min.
on-timeout = brightnessctl -s set 10 # set monitor backlight to minimum, avoid 0 on OLED monitor.
on-resume = brightnessctl -r # monitor backlight restore.
}
# turn off keyboard backlight, comment out this section if you dont have a keyboard backlight.
listener {
timeout = 150 # 2.5min.
on-timeout = brightnessctl -sd rgb:kbd_backlight set 0 # turn off keyboard backlight.
on-resume = brightnessctl -rd rgb:kbd_backlight # turn on keyboard backlight.
}
#listener {
# timeout = 300 # 5min
# on-timeout = loginctl lock-session # lock screen when timeout has passed
#}
listener {
timeout = 300
# 1. Lock the vault via DBus, 2. Start the lock screen
on-timeout = busctl --user call org.freedesktop.secrets /org/freedesktop/secrets org.freedesktop.Secret.Service Lock "as" 1 "/org/freedesktop/secrets/aliases/default" && hyprlock
# When hyprlock exits (via fingerprint or password), instantly run the unlock script
on-resume = ~/.local/bin/unlock-ring.sh
}
listener {
timeout = 330 # 5.5min
on-timeout = hyprctl dispatch dpms off # screen off when timeout has passed
on-resume = hyprctl dispatch dpms on # screen on when activity is detected after timeout has fired.
}
listener {
timeout = 900 # 10min
on-timeout = systemd-ac-power || systemctl suspend # only suspend pc on battery
}
File renamed without changes.
+70
View File
@@ -0,0 +1,70 @@
auth {
fingerprint {
enabled = true
}
}
background {
# path = screenshot
color = rgb(000000)
}
input-field {
size = 200, 30
outline_thickness = 1
rounding = 10
outer_color = rgb(665c54)
inner_color = rgb(3c3836)
font_color = rgb(ebdbb2)
check_color = rgb(d79921)
fail_color = rgb(cc241d)
fade_on_empty = false
placeholder_text = password
fail_text =
}
label {
text = cmd[update:500]date +"%FT%H:%M:%S"
color = rgb(ebdbb2)
font_size = 45
font_family = monospace
position = 0, 60
halign = center
valign = center
}
label {
text = cmd[update:3000]pidof spotify > /dev/null && echo "<span foreground='##b8bb26' size='25000' rise='-4.5pt'> </span>$(playerctl -p spotify metadata xesam:title) - $(playerctl -p spotify metadata xesam:artist)"
color = rgb(ebdbb2)
font_size = 14
font_family = JetBrains Mono Nerd Font Mono Semibold
position = 0, -420
halign = center
valign = center
}
label {
text = cmd[update:60] uptime -p
color = rgb(ebdbb2)
offont_size = 10
font_family = monospace
position = 0, -450
halign = center
valign = center
}
listener {
timeout = 300 # 5 minutes
on-timeout = sleep 60 && busctl --user call org.freedesktop.secrets /org/freedesktop/secrets org.freedesktop.Secret.Service Lock "as" 1 "/org/freedesktop/secrets/aliases/default" && hyprlock
}
listener {
timeout = 330 # 5.5 minutes
on-timeout = hyprctl dispatch dpms off # screen off
on-resume = hyprctl dispatch dpms on # screen on
}
# vim:ft=hyprlang
+25
View File
@@ -0,0 +1,25 @@
splash = false
wallpaper {
monitor = eDP-1
path = /home/trey/images/black.png
fit_mode = cover
}
wallpaper {
monitor = DP-7
path = /home/trey/images/Seven Beats Music/2024-09-16.jpg
fit_mode = cover
}
wallpaper {
monitor = DP-8
path = /home/trey/images/Seven Beats Music/2024-10-14.jpg
fit_mode = cover
}
wallpaper {
monitor =
path = /home/trey/images/black.png
fit_mode = cover
}
View File
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+7
View File
@@ -0,0 +1,7 @@
workspace = 1, defaultName:shell, monitor:eDP-1, persistent:true, default:true
workspace = 3, defaultName:browser, monitor:eDP-1, persistent:true, default:true
workspace = 4, defaultName:work, monitor:eDP-1, persistent:true, default:true
workspace = 5, defaultName:jobs, monitor:eDP-1, persistent:true, default:true
workspace = 6, defaultName:meeting, monitor:eDP-1, persistent:true, default:true
workspace = 7, defaultName:monitoring, monitor:eDP-1, persistent:true, default:true
workspace = 8, defaultName:ai, monitor:eDP-1, persistent:true, default:true
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Background loop to monitor battery status and trigger dunstify on low power.
while true; do
if [ ! -d /sys/class/power_supply/BAT0 ]; then
sleep 60
continue
fi
STATUS=$(cat /sys/class/power_supply/BAT0/status 2>/dev/null)
# Using the native kernel capacity file instead of relying on ibam
CAPACITY=$(cat /sys/class/power_supply/BAT0/capacity 2>/dev/null)
if [ -z "$CAPACITY" ]; then
sleep 60
continue
fi
# Trigger notification at 5% or below
if [ "$STATUS" == "Discharging" ] && [ "$CAPACITY" -le 5 ]; then
if [ ! -f "/run/user/${UID}/battery_low" ]; then
logger "Low power! Triggering notification...."
dunstify --urgency=critical \
--icon=/usr/share/icons/breeze-dark/emblems/16/emblem-warning.svg \
"LOW BATTERY!" \
"Battery level is at ${CAPACITY}%, plug power quick!"
touch "/run/user/${UID}/battery_low"
fi
# Clear the notification if we plug back in and rise above 5%
elif [ "$STATUS" == "Charging" ] && [ "$CAPACITY" -gt 5 ]; then
if [ -f "/run/user/${UID}/battery_low" ]; then
logger "Power reconnected and above 5 percent, clearing notification state."
rm -f "/run/user/${UID}/battery_low"
# Find and close the specific dunst notification by ID
for id in $(dunstctl history | jq '.data[0][] | select(.summary.data == "LOW BATTERY!") | .id.data'); do
dunstify --close=${id}
done
fi
fi
sleep 60
done
-35
View File
@@ -1,35 +0,0 @@
#!/usr/bin/env zsh
#set -x
old_profile="$(kanshictl status | jq -r '.current_profile')"
new_profile="${1}"
shift
if [[ "${old_profile}" == "${new_profile}" ]]; then
if [[ "${1}" != "--force" ]] && [[ "${1}" != '-f' ]]; then
exit 0
fi
fi
case "${new_profile}" in
home_office)
perl -pi -e 'if (/shell/) {s/monitor:.*?,/monitor:DP-6,/}' ${XDG_CONFIG_HOME}/hypr/workspaces.conf
perl -pi -e 'if (/browser/) {s/monitor:.*?,/monitor:DP-6,/}' ${XDG_CONFIG_HOME}/hypr/workspaces.conf
perl -pi -e 'if (/work/) {s/monitor:.*?,/monitor:HDMI-A-1,/}' ${XDG_CONFIG_HOME}/hypr/workspaces.conf
perl -pi -e 'if (/jobs/) {s/monitor:.*?,/monitor:HDMI-A-1,/}' ${XDG_CONFIG_HOME}/hypr/workspaces.conf
kanshictl switch home_office
hyprctl reload
killall -s USR2 waybar
;;
single_headed)
perl -pi -e 's/monitor:.*?,/monitor:eDP-1,/' ${XDG_CONFIG_HOME}/hypr/workspaces.conf
kanshictl switch single_headed
hyprctl reload
killall -s USR2 waybar
;;
*)
print "Invalid profile!" >&2
;;
esac
#set +x
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Options for the menu
lock="Lock"
logout="Sign-Out"
reboot="Reboot"
poweroff="Power Off"
options="$lock\n$logout\n$reboot\n$poweroff"
# Fullscreen rofi menu with large centered text mimicking Windows 11
chosen=$(echo -e "$options" | rofi -dmenu -i -p "CTRL-ALT-DEL" \
-theme-str 'window { fullscreen: true; background-color: #000000cc; }' \
-theme-str 'mainbox { children: [listview]; padding: 25% 35%; }' \
-theme-str 'listview { columns: 1; lines: 4; spacing: 15px; scrollbar: false; }' \
-theme-str 'element { padding: 15px; border-radius: 10px; }' \
-theme-str 'element-text { horizontal-align: 0.5; vertical-align: 0.5; font: "sans-serif 20"; }' \
-theme-str 'element selected { background-color: #3366ff; text-color: white; }')
case $chosen in
$lock)
loginctl lock-session
;;
$logout)
hyprctl dispatch exit
;;
$reboot)
zsh -c 'source ~/.zsh_functions && reboot'
;;
$poweroff)
systemctl poweroff
;;
esac
+9
View File
@@ -0,0 +1,9 @@
return {
{ name = "shell", monitor = "desc:Ancor Communications Inc ASUS VS228 E3LMTF071391" },
{ name = "browser", monitor = "desc:Ancor Communications Inc ASUS VS228 E3LMTF071391" },
{ name = "jobs", monitor = "desc:Ancor Communications Inc ASUS VS228 E3LMTF071438" },
{ name = "work", monitor = "desc:Ancor Communications Inc ASUS VS228 E3LMTF071438" },
{ name = "meeting", monitor = "eDP-1" },
{ name = "monitoring", monitor = "eDP-1" },
{ name = "blah", monitor = "DP-8" },
}
-7
View File
@@ -1,7 +0,0 @@
workspace = 1, defaultName:shell, monitor:DP-8, persistent:true, default:true
workspace = 3, defaultName:browser, monitor:DP-8, persistent:true, default:true
workspace = 4, defaultName:work, monitor:DP-7, persistent:true, default:true
workspace = 5, defaultName:jobs, monitor:DP-7, persistent:true, default:true
workspace = 6, defaultName:meeting, monitor:eDP-1, persistent:true, default:true
workspace = 7, defaultName:monitoring, monitor:eDP-1, persistent:true, default:true
workspace = 8, defaultName:ai, monitor:eDP-1, persistent:true, default:true
+44
View File
@@ -0,0 +1,44 @@
-- ~/.config/hypr/workspaces.lua
-- Dynamic workspace assignment: replaces kanshi + scripts
-- Detects monitor configuration and assigns workspaces accordingly.
local function get_monitor_names()
local names = {}
for _, m in ipairs(hl.get_monitors()) do
names[m.name] = m.name
if m.description then
local clean_desc = m.description:gsub(" %([^%)]+%)$", "")
names["desc:" .. clean_desc] = m.name
end
end
return names
end
local function assign_workspaces()
local monitors = get_monitor_names()
local has_dock = monitors["desc:Ancor Communications Inc ASUS VS228 E3LMTF071438"] and monitors["desc:Ancor Communications Inc ASUS VS228 E3LMTF071391"]
-- Read workspace configurations
package.loaded["workspace_config"] = nil -- force reload in case it changed
local ws_config = require("workspace_config")
-- Iterate in REVERSE to assign the most negative IDs to the first items, counteracting Waybar's low-to-high sorting!
for i = #ws_config, 1, -1 do
local ws = ws_config[i]
local target_mon = has_dock and monitors[ws.monitor] or "eDP-1"
if not target_mon then target_mon = "eDP-1" end
hl.workspace_rule({ workspace = "name:" .. ws.name, monitor = target_mon, persistent = true, default = true })
end
end
-- Apply on initial load
assign_workspaces()
-- Re-apply when monitors change
hl.on("monitor.added", function(monitor)
assign_workspaces()
end)
hl.on("monitor.removed", function(monitor)
assign_workspaces()
end)