Author SHA1 Message Date
trey 60fe219ff7 Final commit before Phase II enhancements 2026-07-21 23:25:07 -04:00
trey 19594ff8b3 Initial commit after everything works 2026-07-16 07:33:28 -04:00
trey 15a20af8a5 Added Ctrl-Alt-Delete menu 2026-06-28 14:42:47 -04:00
11 changed files with 231 additions and 75 deletions
+5
View File
@@ -0,0 +1,5 @@
return {
active_border = "rgba(b4d088ff)",
inactive_border = "rgba(45483daa)",
shadow = "rgba(12140ebf)",
}
+115 -17
View File
@@ -251,7 +251,7 @@ function M.randomizeWallpaper()
-- Determine if we are on the home WiFi ("keep")
local is_home_wifi = false
local f = io.popen("bash -c '/usr/bin/iwgetid -r 2>/dev/null || /usr/bin/nmcli -t -f active,ssid dev wifi 2>/dev/null | grep \"^yes\" | cut -d: -f2'")
local f = io.popen("iwgetid -r 2>/dev/null")
if f then
local ssid = f:read("*a")
f:close()
@@ -261,7 +261,7 @@ function M.randomizeWallpaper()
end
-- Select image source based on location
local img_dir = nil
local img_dir = april_dir
if #monitors > 1 then
img_dir = sbm_dir
elseif is_home_wifi then
@@ -269,26 +269,35 @@ function M.randomizeWallpaper()
end
local images = {}
if img_dir then
local handle = io.popen('find "' .. img_dir .. '" -type f \\( -name "*.jpg" -o -name "*.png" -o -name "*.jpeg" \\) 2>/dev/null')
if handle then
for line in handle:lines() do
table.insert(images, line)
end
handle:close()
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
else
handle:close()
end
if #images == 0 then
table.insert(images, black_png)
end
if #images == 0 then return end
-- Unload old wallpapers from memory
hl.dispatch(hl.dsp.exec_cmd("hyprctl hyprpaper unload all"))
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
@@ -304,11 +313,100 @@ function M.randomizeWallpaper()
if not img then img = images[1] end -- fallback if we run out of unique images
if img then
-- Standard hyprpaper IPC
hl.dispatch(hl.dsp.exec_cmd('hyprctl hyprpaper preload "' .. img .. '"'))
hl.dispatch(hl.dsp.exec_cmd('hyprctl hyprpaper wallpaper "' .. mon.name .. ',' .. img .. '"'))
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
+3 -3
View File
@@ -2,7 +2,7 @@ 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 {
@@ -26,8 +26,8 @@ listener {
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 {
+43 -17
View File
@@ -5,7 +5,7 @@
-----------------------------------------------------
local copy = "wl-copy --trim-newline"
local paste = "wl-paste"
local launcher = "rofi -show run -p 'launch'"
local launcher = "ags request \"launcher:app\""
-- local clear = "cliphist list | head -n1 | cliphist delete"
@@ -37,14 +37,16 @@ hl.env("XDG_SESSION_DESKTOP", "Hyprland")
-----------------------------------------------------
-- Appearance & Input (from appearance.conf & input.conf)
-----------------------------------------------------
local colors = require("colors")
hl.config({
general = {
gaps_in = 1,
gaps_out = 2,
border_size = 2,
col = {
active_border = "rgba(fabd2f99)",
inactive_border = "rgba(595959aa)",
active_border = colors.active_border,
inactive_border = colors.inactive_border,
},
resize_on_border = false,
allow_tearing = false,
@@ -58,9 +60,10 @@ hl.config({
inactive_opacity = 1.0,
shadow = {
enabled = true,
range = 4,
range = 25,
render_power = 3,
color = "rgba(1a1a1aee)"
color = colors.shadow,
color_inactive = colors.shadow
},
blur = {
enabled = true,
@@ -111,7 +114,7 @@ hl.config({
resolve_binds_by_sym = 1,
kb_layout = "us, us",
kb_variant = "colemak,",
kb_options = "ctrl:nocaps, compose:ralt, grp:ctrls_toggle",
kb_options = "ctrl:nocaps,compose:ralt,grp:ctrls_toggle",
follow_mouse = 1,
sensitivity = 0,
touchpad = { natural_scroll = false }
@@ -165,6 +168,14 @@ 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 } })
-----------------------------------------------------
-- Layer Rules
-----------------------------------------------------
hl.layer_rule({ blur = true, ignore_alpha = 0.1, match = { namespace = "^bar.*" } })
hl.layer_rule({ blur = true, ignore_alpha = 0.1, match = { namespace = "^dashboard" } })
hl.layer_rule({ blur = true, ignore_alpha = 0.1, match = { namespace = "^dimmer.*" } })
-----------------------------------------------------
-- Workspaces (from workspaces.conf)
@@ -179,10 +190,11 @@ local bind = hl.bind
local dsp = hl.dsp
local fn = require("functions")
bind("SUPER + Q", dsp.exec_cmd("hyprctl reload && killall -SIGUSR2 waybar && notify-send 'Hyprland' 'Config Reloaded' -u low -t 2000"))
bind("SUPER + Q", dsp.exec_cmd("hyprctl reload && hyprctl eval 'require(\"functions\").setupMonitors()' && notify-send 'Hyprland' 'Config Reloaded' -u low -t 2000; ags quit; ags run &"))
-- 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"))
@@ -208,7 +220,9 @@ bind("SUPER + ALT + H", function() fn.focusOrLaunch("qutebrowser", "org.qutebrow
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 + O", dsp.exec_cmd("ags request \"launcher:open\"")) -- Win+O for Open File
bind("SUPER + SHIFT + O", dsp.exec_cmd("ags request \"launcher:openwith\"")) -- Win+Shift+O for Open With
bind("SUPER + V", dsp.exec_cmd("ags request \"launcher:cliphist\"")) -- Win+V for Clipboard
bind("SUPER + SHIFT + S", function() fn.focusOrLaunch("pavucontrol", "org.pulseaudio.pavucontrol", "org.pulseaudio.pavucontrol") end)
-- Window control
@@ -229,7 +243,7 @@ local function cycle_layout()
hl.config({ general = { layout = new_layout } })
os.execute("echo '" .. layout_icons[new_layout] .. "' > /tmp/hypr_layout.txt")
os.execute("pkill -RTMIN+8 waybar")
-- os.execute("pkill -RTMIN+8 waybar")
end
bind("SUPER + right", function() cycle_layout() end)
@@ -243,21 +257,21 @@ 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"))
bind("SUPER + X", dsp.exec_cmd("ags request close-notification"))
bind("SUPER + Y", dsp.exec_cmd("ags request close-all-notifications"))
-- 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 + M", dsp.exec_cmd("ags request \"launcher:movetoworkspace\""))
bind("SUPER + SHIFT + N", dsp.exec_cmd("ags request \"launcher:workspace\""))
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, "DP-8") 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%"))
@@ -283,10 +297,11 @@ 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("uwsm app -- hyprpaper")
hl.exec_cmd("uwsm app -- waybar")
hl.exec_cmd("hyprpaper &")
-- Waybar is now managed entirely by functions.setupMonitors()
hl.exec_cmd("uwsm app -- ags run")
hl.exec_cmd("uwsm app -- espanso start")
hl.exec_cmd("uwsm app -- dunst")
-- hl.exec_cmd("uwsm app -- dunst") -- Replaced with AGS Notifd
hl.exec_cmd("nm-applet")
hl.exec_cmd("signal-desktop")
hl.exec_cmd("hp-systray")
@@ -303,3 +318,14 @@ hl.on("hyprland.start", function()
-- 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 = true
ipc = on
splash = false
wallpaper {
monitor = DP-7
path = /home/trey/images/black.png
monitor = eDP-1
path = /home/trey/images/Seven Beats Music/2024-07-15.jpg
fit_mode = cover
}
wallpaper {
monitor = eDP-1
path = /home/trey/images/black.png
monitor = DP-7
path = /home/trey/images/Seven Beats Music/2024-12-09.jpg
fit_mode = cover
}
wallpaper {
monitor = DP-8
path = /home/trey/images/black.png
fit_mode = cover
}
wallpaper {
monitor =
path = /home/trey/images/black.png
path = /home/trey/images/Seven Beats Music/2024-10-14.jpg
fit_mode = cover
}
+1 -1
View File
@@ -4,7 +4,7 @@
# application related scripts and commands
$app = ~/.config/hypr/scripts/focusOrLaunch
$launcher = rofi -show run -p "launch"
$launcher = ags request "launcher:app"
$timetracker_prompt = ~/.config/hypr/scripts/timetracker-log-prompt
$timetracker_print = ~/.config/hypr/scripts/timetracker-log-print
$selection = rofi -dmenu -theme-str 'inputbar {enabled: false;}' -i -matching prefix -auto-select -l 2
+11 -8
View File
@@ -20,23 +20,26 @@ while true; do
if [ "$STATUS" == "Discharging" ] && [ "$CAPACITY" -le 5 ]; then
if [ ! -f "/run/user/${UID}/battery_low" ]; then
logger "Low power! Triggering notification...."
dunstify --urgency=critical \
# -p returns the ID so we can close it later
NOTIF_ID=$(dunstify --urgency=critical \
-p \
--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"
"Battery level is at ${CAPACITY}%, plug power quick!")
echo "$NOTIF_ID" > "/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
NOTIF_ID=$(cat "/run/user/${UID}/battery_low")
if [ -n "$NOTIF_ID" ]; then
dunstify --close=$NOTIF_ID
fi
rm -f "/run/user/${UID}/battery_low"
fi
fi
+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
+4 -4
View File
@@ -1,8 +1,8 @@
return {
{ name = "shell", monitor = "DP-8" },
{ name = "browser", monitor = "DP-8" },
{ name = "jobs", monitor = "DP-7" },
{ name = "work", monitor = "DP-7" },
{ 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" },
}
+8 -3
View File
@@ -5,14 +5,18 @@
local function get_monitor_names()
local names = {}
for _, m in ipairs(hl.get_monitors()) do
names[m.name] = true
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["DP-7"] and monitors["DP-8"]
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
@@ -21,7 +25,8 @@ local function assign_workspaces()
-- 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 ws.monitor or "eDP-1"
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