diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..eea045c --- /dev/null +++ b/.luarc.json @@ -0,0 +1,14 @@ +{ + "diagnostics": { + "globals": [ + "hl" + ] + }, + "workspace": { + "library": [ + "/usr/share/hypr/stubs", + "/usr/share/hyprland/stubs" + ], + "checkThirdParty": false + } +} diff --git a/functions.lua b/functions.lua new file mode 100644 index 0000000..904ca54 --- /dev/null +++ b/functions.lua @@ -0,0 +1,286 @@ +-- ~/.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" + + -- Choose image source based on monitor count + local img_dir = (#monitors <= 1) and april_dir or sbm_dir + + local images = {} + 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() + end + + if #images == 0 then return end + + -- Pick a unique random image for each monitor + math.randomseed(os.time()) + local used = {} + 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 img then + hl.dispatch(hl.dsp.exec_cmd('hyprctl hyprpaper reload "' .. mon.name .. ',' .. img .. '"')) + end + end +end + +return M diff --git a/hypridle.conf b/hypridle.conf index 1a329b2..1a0f793 100644 --- a/hypridle.conf +++ b/hypridle.conf @@ -40,3 +40,5 @@ listener { timeout = 900 # 10min on-timeout = systemd-ac-power || systemctl suspend # only suspend pc on battery } + +# vim:ft=hyprlang diff --git a/hyprland.lua b/hyprland.lua new file mode 100644 index 0000000..e05f7d9 --- /dev/null +++ b/hyprland.lua @@ -0,0 +1,282 @@ +-- ~/.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 && killall -SIGUSR2 waybar && 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("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 + A", function() fn.focusOrLaunch("slack", "Slack", "Slack") end) +bind("SUPER + 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) +bind("SUPER + P", dsp.exec_cmd(launcher)) +bind("SUPER + S", function() fn.focusOrLaunch("pavucontrol", "org.pulseaudio.pavucontrol", "org.pulseaudio.pavucontrol") end) +bind("SUPER + V", function() fn.focusOrLaunch("alacritty", "Alacritty") end) + +-- Window control +bind("SUPER + C", dsp.window.close()) +bind("SUPER + T", dsp.window.float({ action = "toggle" })) +bind("SUPER + space", dsp.window.fullscreen({ mode = 1 })) + +-- BUG FIX: H/J/K were redundantly bound to move_focus AND fullscreen. +bind("SUPER + H", dsp.focus({ direction = "l" })) +bind("SUPER + K", dsp.focus({ direction = "u" })) +bind("SUPER + J", dsp.focus({ direction = "d" })) + +bind("SUPER + X", dsp.exec_cmd("dunstctl close")) +bind("SUPER + Y", dsp.exec_cmd("dunstctl close-all")) + +-- Workspace control +bind("SUPER + right", function() hl.dispatch(hl.dsp.focus({ workspace = "m+1" })) end) +bind("SUPER + 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, "DP-8") end) + +-- Cliphist +bind("ALT + V", dsp.exec_cmd("cliphist list | rofi -dmenu -p 'paste' | cliphist decode | " .. copy .. " && " .. paste)) + +-- 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("uwsm app -- hyprpaper") + hl.exec_cmd("uwsm app -- waybar") + 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) diff --git a/hyprpaper.conf b/hyprpaper.conf index 0691458..b70ca1d 100644 --- a/hyprpaper.conf +++ b/hyprpaper.conf @@ -1,19 +1,20 @@ +ipc = true 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 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 + path = /home/trey/images/black.png fit_mode = cover } @@ -22,4 +23,3 @@ wallpaper { path = /home/trey/images/black.png fit_mode = cover } - diff --git a/appearance.conf b/legacy/appearance.conf similarity index 100% rename from appearance.conf rename to legacy/appearance.conf diff --git a/autostart.conf b/legacy/autostart.conf similarity index 100% rename from autostart.conf rename to legacy/autostart.conf diff --git a/debug.conf b/legacy/debug.conf similarity index 100% rename from debug.conf rename to legacy/debug.conf diff --git a/env.conf b/legacy/env.conf similarity index 100% rename from env.conf rename to legacy/env.conf diff --git a/legacy/hypridle.conf b/legacy/hypridle.conf new file mode 100644 index 0000000..1a329b2 --- /dev/null +++ b/legacy/hypridle.conf @@ -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 +} diff --git a/hyprland.conf b/legacy/hyprland.conf similarity index 100% rename from hyprland.conf rename to legacy/hyprland.conf diff --git a/legacy/hyprlock.conf b/legacy/hyprlock.conf new file mode 100644 index 0000000..5cdb30d --- /dev/null +++ b/legacy/hyprlock.conf @@ -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 "$(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 diff --git a/legacy/hyprpaper.conf b/legacy/hyprpaper.conf new file mode 100644 index 0000000..0691458 --- /dev/null +++ b/legacy/hyprpaper.conf @@ -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 +} + diff --git a/input.conf b/legacy/input.conf similarity index 100% rename from input.conf rename to legacy/input.conf diff --git a/keybindings.conf b/legacy/keybindings.conf similarity index 100% rename from keybindings.conf rename to legacy/keybindings.conf diff --git a/macros.conf b/legacy/macros.conf similarity index 100% rename from macros.conf rename to legacy/macros.conf diff --git a/monitors.conf b/legacy/monitors.conf similarity index 100% rename from monitors.conf rename to legacy/monitors.conf diff --git a/pastes/GPL.txt b/legacy/pastes/GPL.txt similarity index 100% rename from pastes/GPL.txt rename to legacy/pastes/GPL.txt diff --git a/pastes/date b/legacy/pastes/date similarity index 100% rename from pastes/date rename to legacy/pastes/date diff --git a/pastes/lastweek b/legacy/pastes/lastweek similarity index 100% rename from pastes/lastweek rename to legacy/pastes/lastweek diff --git a/pastes/lastyear b/legacy/pastes/lastyear similarity index 100% rename from pastes/lastyear rename to legacy/pastes/lastyear diff --git a/pastes/nextweek b/legacy/pastes/nextweek similarity index 100% rename from pastes/nextweek rename to legacy/pastes/nextweek diff --git a/pastes/nextyear b/legacy/pastes/nextyear similarity index 100% rename from pastes/nextyear rename to legacy/pastes/nextyear diff --git a/pastes/now b/legacy/pastes/now similarity index 100% rename from pastes/now rename to legacy/pastes/now diff --git a/pastes/today b/legacy/pastes/today similarity index 100% rename from pastes/today rename to legacy/pastes/today diff --git a/pastes/tomorrow b/legacy/pastes/tomorrow similarity index 100% rename from pastes/tomorrow rename to legacy/pastes/tomorrow diff --git a/pastes/yesterday b/legacy/pastes/yesterday similarity index 100% rename from pastes/yesterday rename to legacy/pastes/yesterday diff --git a/scripts/GPL.txt b/legacy/scripts/GPL.txt similarity index 100% rename from scripts/GPL.txt rename to legacy/scripts/GPL.txt diff --git a/scripts/battery b/legacy/scripts/battery similarity index 100% rename from scripts/battery rename to legacy/scripts/battery diff --git a/scripts/createWorkspace b/legacy/scripts/createWorkspace similarity index 100% rename from scripts/createWorkspace rename to legacy/scripts/createWorkspace diff --git a/scripts/cycleWorkspace b/legacy/scripts/cycleWorkspace similarity index 100% rename from scripts/cycleWorkspace rename to legacy/scripts/cycleWorkspace diff --git a/scripts/deleteWorkspace b/legacy/scripts/deleteWorkspace similarity index 100% rename from scripts/deleteWorkspace rename to legacy/scripts/deleteWorkspace diff --git a/scripts/fix-hyprlock.zsh b/legacy/scripts/fix-hyprlock.zsh similarity index 100% rename from scripts/fix-hyprlock.zsh rename to legacy/scripts/fix-hyprlock.zsh diff --git a/scripts/focusOrLaunch b/legacy/scripts/focusOrLaunch similarity index 100% rename from scripts/focusOrLaunch rename to legacy/scripts/focusOrLaunch diff --git a/scripts/functions b/legacy/scripts/functions similarity index 100% rename from scripts/functions rename to legacy/scripts/functions diff --git a/scripts/get-active-workspace b/legacy/scripts/get-active-workspace similarity index 100% rename from scripts/get-active-workspace rename to legacy/scripts/get-active-workspace diff --git a/scripts/gnome-keyring.sh b/legacy/scripts/gnome-keyring.sh similarity index 100% rename from scripts/gnome-keyring.sh rename to legacy/scripts/gnome-keyring.sh diff --git a/scripts/kill-hp-systray b/legacy/scripts/kill-hp-systray similarity index 100% rename from scripts/kill-hp-systray rename to legacy/scripts/kill-hp-systray diff --git a/scripts/multihead.zsh b/legacy/scripts/multihead.zsh similarity index 100% rename from scripts/multihead.zsh rename to legacy/scripts/multihead.zsh diff --git a/scripts/mv2workspace b/legacy/scripts/mv2workspace similarity index 100% rename from scripts/mv2workspace rename to legacy/scripts/mv2workspace diff --git a/scripts/randomize-wallpaper b/legacy/scripts/randomize-wallpaper similarity index 100% rename from scripts/randomize-wallpaper rename to legacy/scripts/randomize-wallpaper diff --git a/scripts/screenshot_region.zsh b/legacy/scripts/screenshot_region.zsh similarity index 100% rename from scripts/screenshot_region.zsh rename to legacy/scripts/screenshot_region.zsh diff --git a/scripts/setup-monitors.zsh b/legacy/scripts/setup-monitors.zsh similarity index 100% rename from scripts/setup-monitors.zsh rename to legacy/scripts/setup-monitors.zsh diff --git a/scripts/startup.zsh b/legacy/scripts/startup.zsh similarity index 100% rename from scripts/startup.zsh rename to legacy/scripts/startup.zsh diff --git a/scripts/timetracker-log-print b/legacy/scripts/timetracker-log-print similarity index 100% rename from scripts/timetracker-log-print rename to legacy/scripts/timetracker-log-print diff --git a/scripts/timetracker-log-prompt b/legacy/scripts/timetracker-log-prompt similarity index 100% rename from scripts/timetracker-log-prompt rename to legacy/scripts/timetracker-log-prompt diff --git a/variables.conf b/legacy/variables.conf similarity index 100% rename from variables.conf rename to legacy/variables.conf diff --git a/windowrules.conf b/legacy/windowrules.conf similarity index 100% rename from windowrules.conf rename to legacy/windowrules.conf diff --git a/legacy/workspaces.conf b/legacy/workspaces.conf new file mode 100644 index 0000000..bf2230a --- /dev/null +++ b/legacy/workspaces.conf @@ -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 diff --git a/scripts/battery-notify.sh b/scripts/battery-notify.sh new file mode 100644 index 0000000..32a7fee --- /dev/null +++ b/scripts/battery-notify.sh @@ -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 diff --git a/scripts/multihead.zsh.bak2025-12-27 b/scripts/multihead.zsh.bak2025-12-27 deleted file mode 100755 index c4fc10d..0000000 --- a/scripts/multihead.zsh.bak2025-12-27 +++ /dev/null @@ -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 diff --git a/workspace_config.lua b/workspace_config.lua new file mode 100644 index 0000000..d36c8f5 --- /dev/null +++ b/workspace_config.lua @@ -0,0 +1,8 @@ +return { + { name = "shell", monitor = "DP-8" }, + { name = "browser", monitor = "DP-8" }, + { name = "jobs", monitor = "DP-7" }, + { name = "work", monitor = "DP-7" }, + { name = "meeting", monitor = "eDP-1" }, + { name = "monitoring", monitor = "eDP-1" }, +} diff --git a/workspaces.conf b/workspaces.conf deleted file mode 100644 index 910409c..0000000 --- a/workspaces.conf +++ /dev/null @@ -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 diff --git a/workspaces.lua b/workspaces.lua new file mode 100644 index 0000000..35c19db --- /dev/null +++ b/workspaces.lua @@ -0,0 +1,37 @@ +-- ~/.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] = true + end + return names +end + +local function assign_workspaces() + local monitors = get_monitor_names() + local has_dock = monitors["DP-7"] and monitors["DP-8"] + + -- Read workspace configurations + package.loaded["workspace_config"] = nil -- force reload in case it changed + local ws_config = require("workspace_config") + + for _, ws in ipairs(ws_config) do + local target_mon = has_dock and ws.monitor or "eDP-1" + 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)