diff --git a/functions.lua b/functions.lua index 5329f8e..bd8fcd7 100644 --- a/functions.lua +++ b/functions.lua @@ -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 diff --git a/hypridle.conf b/hypridle.conf index 4b78362..2bedc93 100644 --- a/hypridle.conf +++ b/hypridle.conf @@ -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 { diff --git a/hyprland.lua b/hyprland.lua index 6522fad..f266df9 100644 --- a/hyprland.lua +++ b/hyprland.lua @@ -179,7 +179,7 @@ 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")) -- 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")) @@ -258,7 +258,7 @@ 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%")) @@ -284,8 +284,8 @@ 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 -- espanso start") hl.exec_cmd("uwsm app -- dunst") hl.exec_cmd("nm-applet") @@ -304,3 +304,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) diff --git a/hyprlock.conf b/hyprlock.conf index 5cdb30d..4690530 100644 --- a/hyprlock.conf +++ b/hyprlock.conf @@ -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 diff --git a/hyprpaper.conf b/hyprpaper.conf index b70ca1d..4699886 100644 --- a/hyprpaper.conf +++ b/hyprpaper.conf @@ -1,25 +1,20 @@ -ipc = true +ipc = on splash = false -wallpaper { - monitor = DP-7 - path = /home/trey/images/black.png - fit_mode = cover -} 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-8 - path = /home/trey/images/black.png + monitor = DP-9 + path = /home/trey/images/Seven Beats Music/2024-09-09.jpg fit_mode = cover } wallpaper { - monitor = - path = /home/trey/images/black.png + monitor = DP-10 + path = /home/trey/images/Seven Beats Music/2026-01-26.jpg fit_mode = cover } diff --git a/workspace_config.lua b/workspace_config.lua index d36c8f5..49bcaac 100644 --- a/workspace_config.lua +++ b/workspace_config.lua @@ -1,8 +1,9 @@ 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" }, + { name = "blah", monitor = "DP-8" }, } diff --git a/workspaces.lua b/workspaces.lua index 27c58fc..dc27443 100644 --- a/workspaces.lua +++ b/workspaces.lua @@ -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