8 Commits

56 changed files with 1033 additions and 76 deletions
+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 { general {
lock_cmd = pidof hyprlock || hyprlock # avoid starting multiple hyprlock instances. 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. 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 { listener {
@@ -17,26 +18,21 @@ listener {
on-resume = brightnessctl -rd rgb:kbd_backlight # turn on 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 { listener {
timeout = 300 timeout = 300
# 1. Lock the vault via DBus, 2. Start the lock screen # 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" && hyprlock 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
# When hyprlock exits (via fingerprint or password), instantly run the unlock script
on-resume = ~/.local/bin/unlock-ring.sh
} }
listener { listener {
timeout = 330 # 5.5min timeout = 330 # 5.5min
on-timeout = hyprctl dispatch dpms off # screen off when timeout has passed on-timeout = hyprctl eval 'hl.dispatch(hl.dsp.dpms({ action = "off" }))' # screen off when timeout has passed
on-resume = hyprctl dispatch dpms on # screen on when activity is detected after timeout has fired. on-resume = hyprctl eval 'hl.dispatch(hl.dsp.dpms({ action = "on" }))' # screen on when activity is detected after timeout has fired.
} }
listener { listener {
timeout = 900 # 10min timeout = 900 # 10min
on-timeout = systemd-ac-power || systemctl suspend # only suspend pc on battery 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 { label {
text = cmd[update:60] uptime -p text = cmd[update:60000] uptime -p
color = rgb(ebdbb2) color = rgb(ebdbb2)
offont_size = 10 offont_size = 10
font_family = monospace font_family = monospace
@@ -56,15 +56,6 @@ label {
valign = 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 # vim:ft=hyprlang
+7 -12
View File
@@ -1,25 +1,20 @@
ipc = on
splash = false splash = false
wallpaper { wallpaper {
monitor = eDP-1 monitor = eDP-1
path = /home/trey/images/black.png path = /home/trey/images/Seven Beats Music/2025-02-24.jpg
fit_mode = cover fit_mode = cover
} }
wallpaper { wallpaper {
monitor = DP-7 monitor = DP-9
path = /home/trey/images/Seven Beats Music/2024-09-16.jpg path = /home/trey/images/Seven Beats Music/2024-09-09.jpg
fit_mode = cover fit_mode = cover
} }
wallpaper { wallpaper {
monitor = DP-8 monitor = DP-10
path = /home/trey/images/Seven Beats Music/2024-10-14.jpg path = /home/trey/images/Seven Beats Music/2026-01-26.jpg
fit_mode = cover fit_mode = cover
} }
wallpaper {
monitor =
path = /home/trey/images/black.png
fit_mode = cover
}
View File
View File
+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
}
+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
View File
View File
View File
+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)