287 lines
9.8 KiB
Lua
287 lines
9.8 KiB
Lua
-- ~/.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
|