2
This commit is contained in:
58
minimal-niri-dotfiles/.config/niri/scripts/niri-binds
Normal file
58
minimal-niri-dotfiles/.config/niri/scripts/niri-binds
Normal file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
NIRI_DIR="$HOME/.config/niri"
|
||||
|
||||
# === 界面尺寸设置 (在这里微调,下方的所有终端会自动应用) ===
|
||||
MENU_WIDTH=90
|
||||
MENU_HEIGHT=20
|
||||
|
||||
# 检查目录
|
||||
if [[ ! -d "$NIRI_DIR" ]]; then
|
||||
echo "Error: 找不到配置目录 $NIRI_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 提取并使用 column 对齐
|
||||
MENU_ITEMS=$(grep -Rh 'hotkey-overlay-title=' "$NIRI_DIR" --include="*.kdl" | \
|
||||
grep -v '^[ \t]*//' | \
|
||||
sed -n -E 's/^[ \t]*(.*)[ \t]+hotkey-overlay-title="([^"]+)".*/\1|\2/p' | \
|
||||
sed -E 's/[ \t]*\|/\|/' | \
|
||||
column -t -s '|')
|
||||
|
||||
if [[ -z "$MENU_ITEMS" ]]; then
|
||||
echo "没有找到有效的快捷键配置。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Fzf 核心命令
|
||||
FZF_CMD="echo \"$MENU_ITEMS\" | fzf --reverse --prompt=' 快捷键: ' --info=hidden --border=none > /dev/null"
|
||||
|
||||
# 动态检测终端并使用对应的参数启动
|
||||
if command -v kitty >/dev/null 2>&1; then
|
||||
kitty --class "niri-hotkey-menu" --title "快捷键菜单" \
|
||||
-o remember_window_size=no -o initial_window_width=${MENU_WIDTH}c -o initial_window_height=${MENU_HEIGHT}c \
|
||||
bash -c "$FZF_CMD"
|
||||
|
||||
elif command -v foot >/dev/null 2>&1; then
|
||||
foot --app-id "niri-hotkey-menu" --title "快捷键菜单" \
|
||||
--window-size-chars=${MENU_WIDTH}x${MENU_HEIGHT} \
|
||||
bash -c "$FZF_CMD"
|
||||
|
||||
elif command -v alacritty >/dev/null 2>&1; then
|
||||
alacritty --class "niri-hotkey-menu" --title "快捷键菜单" \
|
||||
-o window.dimensions.columns=${MENU_WIDTH} -o window.dimensions.lines=${MENU_HEIGHT} \
|
||||
-e bash -c "$FZF_CMD"
|
||||
|
||||
elif command -v wezterm >/dev/null 2>&1; then
|
||||
wezterm start --class "niri-hotkey-menu" -- bash -c "$FZF_CMD"
|
||||
|
||||
elif [[ -n "$TERMINAL" ]]; then
|
||||
$TERMINAL -e bash -c "$FZF_CMD"
|
||||
|
||||
else
|
||||
echo "Error: 未检测到支持的终端模拟器。"
|
||||
if command -v fuzzel >/dev/null 2>&1; then
|
||||
# Fuzzel 降级方案也会自动读取顶层变量
|
||||
echo "$MENU_ITEMS" | fuzzel --dmenu -i -p " 快捷键: " -w ${MENU_WIDTH} > /dev/null
|
||||
fi
|
||||
fi
|
||||
@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
import shutil
|
||||
import time # <--- 新增:用于等待窗口关闭生效
|
||||
|
||||
# ================= Configuration =================
|
||||
EXCLUDE_APPS = ["fuzzel", "quick-switch", "niri-quick-switch"]
|
||||
FUZZEL_ARGS = [
|
||||
"--dmenu",
|
||||
"--index",
|
||||
"--width", "60",
|
||||
"--lines", "15",
|
||||
"--prompt", "Switch: ",
|
||||
# 修改了这里:提示 Ctrl+L 跳转,Ctrl+H 关闭
|
||||
"--placeholder", "Search... [Ctrl+J/K: Select | Ctrl+L: Switch | Ctrl+H: Close]"
|
||||
]
|
||||
# =================================================
|
||||
|
||||
def run_cmd(cmd):
|
||||
try:
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||
if result.returncode != 0: return None
|
||||
if "-j" in cmd:
|
||||
return json.loads(result.stdout)
|
||||
return result.stdout
|
||||
except Exception: return None
|
||||
|
||||
def get_active_output_workspace_ids():
|
||||
"""
|
||||
获取当前活动显示器(output)上的所有工作区 ID
|
||||
"""
|
||||
workspaces = run_cmd("niri msg -j workspaces")
|
||||
if not workspaces: return set()
|
||||
|
||||
# 1. 找到当前聚焦的工作区所在的显示器名称
|
||||
active_output = None
|
||||
for ws in workspaces:
|
||||
if ws.get("is_focused"):
|
||||
active_output = ws.get("output")
|
||||
break
|
||||
|
||||
if not active_output: return set()
|
||||
|
||||
# 2. 收集该显示器上的所有工作区 ID
|
||||
valid_ws_ids = set()
|
||||
for ws in workspaces:
|
||||
if ws.get("output") == active_output:
|
||||
valid_ws_ids.add(ws.get("id"))
|
||||
|
||||
return valid_ws_ids
|
||||
|
||||
def get_window_sort_key(w):
|
||||
# 将 workspace_id 作为第一排序优先级,确保不同工作区的窗口按组排列
|
||||
ws_id = w.get("workspace_id", 0)
|
||||
|
||||
if w.get("is_floating"):
|
||||
return (ws_id, 99999, 0, w.get("id"))
|
||||
try:
|
||||
layout = w.get("layout", {})
|
||||
if not layout: return (ws_id, 9999, 0, w.get("id"))
|
||||
pos = layout.get("pos_in_scrolling_layout")
|
||||
if pos and isinstance(pos, list) and len(pos) >= 2:
|
||||
return (ws_id, pos[0], pos[1], w.get("id"))
|
||||
except Exception:
|
||||
pass
|
||||
return (ws_id, 9999, 0, w.get("id"))
|
||||
|
||||
def main():
|
||||
if not shutil.which("fuzzel"):
|
||||
print("Error: Fuzzel not found")
|
||||
sys.exit(1)
|
||||
|
||||
# === 核心改动:开启死循环 ===
|
||||
while True:
|
||||
# 1. 每次循环重新获取当前显示器上的所有 Workspace ID
|
||||
valid_ws_ids = get_active_output_workspace_ids()
|
||||
if not valid_ws_ids: break
|
||||
|
||||
# 2. 每次循环都重新获取最新的窗口列表
|
||||
windows = run_cmd("niri msg -j windows")
|
||||
if not windows: break
|
||||
|
||||
current_windows = []
|
||||
for w in windows:
|
||||
# 判断窗口是否在允许的工作区集合内
|
||||
if w.get("workspace_id") not in valid_ws_ids: continue
|
||||
app_id = w.get("app_id") or ""
|
||||
if app_id in EXCLUDE_APPS: continue
|
||||
current_windows.append(w)
|
||||
|
||||
# 如果没有窗口了,直接退出
|
||||
if not current_windows: break
|
||||
|
||||
current_windows.sort(key=get_window_sort_key)
|
||||
|
||||
input_str = ""
|
||||
for w in current_windows:
|
||||
app_id = w.get("app_id") or "Wayland"
|
||||
title = w.get("title", "No Title").replace("\n", " ")
|
||||
display_str = f"[{app_id}] {title}"
|
||||
line = f"{display_str}\0icon\x1f{app_id}"
|
||||
input_str += f"{line}\n"
|
||||
|
||||
try:
|
||||
# 3. 启动 Fuzzel
|
||||
proc = subprocess.Popen(
|
||||
["fuzzel"] + FUZZEL_ARGS,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
stdout, _ = proc.communicate(input=input_str)
|
||||
|
||||
return_code = proc.returncode
|
||||
raw_output = stdout.strip()
|
||||
|
||||
# 情况 A: 用户按 ESC 取消 -> 退出循环
|
||||
if return_code not in [0, 10] or not raw_output:
|
||||
break
|
||||
|
||||
try:
|
||||
selected_idx = int(raw_output)
|
||||
except ValueError:
|
||||
break
|
||||
|
||||
if 0 <= selected_idx < len(current_windows):
|
||||
target_window = current_windows[selected_idx]
|
||||
target_id = target_window.get("id")
|
||||
|
||||
if return_code == 0:
|
||||
# 动作: 切换窗口 -> 任务完成,退出循环
|
||||
subprocess.run(["niri", "msg", "action", "focus-window", "--id", str(target_id)])
|
||||
break
|
||||
|
||||
elif return_code == 10:
|
||||
# 动作: 关闭窗口 -> 执行关闭,然后 CONTINUE (继续循环)
|
||||
subprocess.run(["niri", "msg", "action", "close-window", "--id", str(target_id)])
|
||||
|
||||
# 关键:稍微等一下,让 niri 有时间处理关闭动作,
|
||||
# 否则立刻刷新列表可能还会看到那个已经被杀死的窗口
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
break
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# =================配置区域=================
|
||||
SOUND="/usr/share/sounds/freedesktop/stereo/camera-shutter.oga"
|
||||
# 这是一个“扳机”文件,存于内存中 (/dev/shm),读写极快
|
||||
TRIGGER_FILE="/dev/shm/niri_screenshot_armed"
|
||||
# 有效期:按下截图键后,多少秒内产生了图片才响?(防止你取消截图后,下次复制图片误响)
|
||||
TIMEOUT_SEC=15
|
||||
# =========================================
|
||||
|
||||
# 环境检查
|
||||
if ! command -v pw-play >/dev/null; then
|
||||
notify-send "错误: 未找到 pw-play"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# =========================================
|
||||
# 1. 定义信号处理 (收到信号 = 上膛)
|
||||
# =========================================
|
||||
arm_trigger() {
|
||||
# 更新文件的修改时间,或者创建它
|
||||
touch "$TRIGGER_FILE"
|
||||
}
|
||||
|
||||
# 注册信号:收到 USR1 就执行 arm_trigger
|
||||
trap arm_trigger SIGUSR1
|
||||
|
||||
# =========================================
|
||||
# 2. 启动剪贴板监听 (后台运行)
|
||||
# =========================================
|
||||
# 只有当剪贴板真正发生变化时,这个子进程才会醒来
|
||||
wl-paste --watch bash -c "
|
||||
# A. 检查是不是图片
|
||||
if wl-paste --list-types 2>/dev/null | grep -q 'image/'; then
|
||||
|
||||
# B. 检查有没有“上膛” (文件是否存在)
|
||||
if [ -f \"$TRIGGER_FILE\" ]; then
|
||||
|
||||
# C. 检查“上膛”是否过期 (利用文件修改时间)
|
||||
# $(date +%s) - stat获取的时间
|
||||
NOW=\$(date +%s)
|
||||
FILE_TIME=\$(stat -c %Y \"$TRIGGER_FILE\")
|
||||
DIFF=\$((NOW - FILE_TIME))
|
||||
|
||||
if [ \$DIFF -lt $TIMEOUT_SEC ]; then
|
||||
# 조건을 满足:是图片 + 已上膛 + 没过期
|
||||
pw-play \"$SOUND\" &
|
||||
|
||||
# D. 销毁扳机 (防止连响)
|
||||
rm -f \"$TRIGGER_FILE\"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
" &
|
||||
# 获取 wl-paste 的 PID,以便脚本退出时杀掉它
|
||||
WATCHER_PID=$!
|
||||
|
||||
# =========================================
|
||||
# 3. 守护进程主循环 (0 CPU 占用)
|
||||
# =========================================
|
||||
# 这里的 trap 负责在脚本退出时清理子进程
|
||||
trap "kill $WATCHER_PID; exit" INT TERM EXIT
|
||||
|
||||
# 写入当前 PID 方便调试 (可选)
|
||||
# echo $$ > /tmp/niri-sound.pid
|
||||
|
||||
echo "截图音效服务已启动,等待 SIGUSR1 信号..."
|
||||
|
||||
# 无限睡眠,只响应信号
|
||||
while true; do
|
||||
sleep infinity & wait $!
|
||||
done
|
||||
8
minimal-niri-dotfiles/.config/niri/scripts/swayidle.sh
Normal file
8
minimal-niri-dotfiles/.config/niri/scripts/swayidle.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 5分钟锁屏,10分钟熄屏,20分钟休眠
|
||||
exec swayidle -w \
|
||||
timeout 600 'hyprlock -c ~/.config/niri/hyprlock.conf &' \
|
||||
timeout 900 'niri msg action power-off-monitors' \
|
||||
resume 'niri msg action power-on-monitors' \
|
||||
timeout 1800 'systemctl suspend'
|
||||
27
minimal-niri-dotfiles/.config/niri/scripts/toggle-wlsunset
Normal file
27
minimal-niri-dotfiles/.config/niri/scripts/toggle-wlsunset
Normal file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ===============================================================
|
||||
# 配置区
|
||||
# ===============================================================
|
||||
TEMP_LOW="5000" # 夜间/护眼色温
|
||||
TEMP_HIGH="6500" # 日间/标准色温
|
||||
TRANSITION="1800" # 过渡时间 (30分钟)
|
||||
|
||||
# 经纬度
|
||||
LAT="36.5"
|
||||
LONG="128.0"
|
||||
|
||||
# ===============================================================
|
||||
# 逻辑区
|
||||
# ===============================================================
|
||||
if pgrep -x "wlsunset" > /dev/null; then
|
||||
# 如果正在运行,则关闭
|
||||
pkill wlsunset
|
||||
notify-send -t 2000 -a "System" "护眼模式" "已关闭"
|
||||
else
|
||||
# 如果未运行,则开启
|
||||
# 这里使用的是经纬度模式,如果你想用固定时间模式,
|
||||
# 请把下面这行改为: wlsunset -S 07:00 -s 19:00 -t $TEMP_LOW -T $TEMP_HIGH -d $TRANSITION &
|
||||
wlsunset -l $LAT -L $LONG -t $TEMP_LOW -T $TEMP_HIGH -d $TRANSITION &
|
||||
notify-send -t 2000 -a "System" "护眼模式" "已开启,目标色温 ${TEMP_LOW}K"
|
||||
fi
|
||||
Reference in New Issue
Block a user