This commit is contained in:
2026-04-25 12:19:51 +02:00
parent e3c273516e
commit 88c522e9d1
2 changed files with 187 additions and 4 deletions
+2 -1
View File
@@ -68,6 +68,7 @@ binds {
Mod+Tab repeat=false { toggle-overview; }
Mod+Q repeat=false { close-window; }
Mod+Shift+Q repeat=false { spawn "niri-force-kill-window"; }
Alt+F4 repeat=false { close-window; } // can't imagine this does not come as default
Mod+Left { focus-column-left; }
@@ -161,7 +162,7 @@ binds {
Mod+Escape allow-inhibiting=false repeat=false { toggle-keyboard-shortcuts-inhibit; }
// Session
Mod+Shift+Q allow-inhibiting=false repeat=false { quit; }
Mod+K allow-inhibiting=false repeat=false { quit; }
Mod+Shift+P allow-inhibiting=false repeat=false { spawn-sh "hyprlock & niri msg action power-off-monitors"; }
Mod+L allow-inhibiting=false repeat=false { spawn "loginctl" "lock-session"; }
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# https://github.com/SHORiN-KiWATA/shorin-contrib
#
# niri-force-kill-window
# 通过鼠标点击强制结束 (SIGKILL) 任意窗口。
# 完美支持 Wayland 原生与 XWayland 代理窗口的独立精准击杀。
# 具备多语言自适应 (i18n) 与智能依赖提示功能。
#
# 实现原理:
# 1. 通过 niri msg pick-window 点选窗口
# 2. 利用窗口的 PID通过进程名称判断它是 xwayland 还是 wayland
# 3. 获取真实的底层 PIDWayland 直接取用XWayland 利用 xprop 获取焦点窗口的真实 PID
# 4. 向上追溯该真实 PID找到所属应用的根进程 (App Root),并利用防火墙逻辑避开系统进程
# 5. 从根进程向下遍历收集所有子孙进程,利用 kill -9 执行“九头蛇绞杀”,防止多进程软件死灰复燃
# ==========================================
# 0. 多语言 (Locale) 自适应支持
# ==========================================
if [[ "${LANG}" == zh_* ]]; then
STR_ERR_DEP_TITLE="依赖缺失"
STR_ERR_DEP_MSG="缺少必需命令: %s\n请尝试安装包: %s"
STR_ERR_INFO_TITLE="获取信息失败"
STR_ERR_INFO_MSG="无法获取窗口或进程信息。"
STR_ERR_KILL_TITLE="结束失败"
STR_ERR_KILL_MSG="无法提取目标真实 PID。"
STR_SUCC_TITLE="强制杀死 (%s)"
STR_SUCC_MSG="应用: %s\n连根拔起: 成功摧毁 %s 个相关进程。"
STR_UNKNOWN_APP="未知应用"
else
STR_ERR_DEP_TITLE="Missing Dependency"
STR_ERR_DEP_MSG="Command not found: %s\nPlease install package: %s"
STR_ERR_INFO_TITLE="Info Error"
STR_ERR_INFO_MSG="Could not determine window or process information."
STR_ERR_KILL_TITLE="Kill Failed"
STR_ERR_KILL_MSG="Could not extract target real PID."
STR_SUCC_TITLE="Headshot! (%s)"
STR_SUCC_MSG="App: %s\nHydra Kill: %s processes terminated."
STR_UNKNOWN_APP="Unknown App"
fi
# ==========================================
# 1. 环境与依赖检查
# ==========================================
check_dependency() {
local cmd="$1"
local pkg="$2"
if ! command -v "$cmd" &> /dev/null; then
local msg
printf -v msg "$STR_ERR_DEP_MSG" "$cmd" "$pkg"
# 如果 notify-send 存在,则发送桌面通知;否则输出到终端标准错误
if command -v notify-send &> /dev/null; then
notify-send "$STR_ERR_DEP_TITLE" "$msg" -u critical -i dialog-error
else
echo -e "[${STR_ERR_DEP_TITLE}]\n${msg}" >&2
fi
exit 1
fi
}
# 检查三大核心依赖并提示对应软件包
check_dependency "niri" "niri"
check_dependency "notify-send" "libnotify"
check_dependency "xprop" "xorg-xprop"
# ==========================================
# 2. 辅助函数:发送通知与音效 (非阻塞)
# ==========================================
notify_and_play() {
local title="$1"
local msg="$2"
notify-send "$title" "$msg" -a "Window Killer" -i application-exit
# pw-play 不是强依赖,如果有则播放音效,放入后台运行防止阻塞
if command -v pw-play &> /dev/null; then
pw-play /usr/share/sounds/freedesktop/stereo/dialog-error.oga >/dev/null 2>&1 &
fi
}
# ==========================================
# 3. 抓取目标窗口信息
# ==========================================
# 执行命令并捕获退出码。如果用户按 Esc 取消,返回非零退出码,静默退出
if ! output=$(niri msg pick-window 2>/dev/null); then
exit 0
fi
# 如果没有任何输出,直接退出
if [[ -z "$output" ]]; then
exit 0
fi
# 提取 Niri 视角下的 PID 和 App ID
pid=$(grep -oP 'PID:\s*\K\d+' <<< "$output")
app_id=$(grep -oP 'App ID:\s*"\K[^"]+' <<< "$output")
app_name="${app_id:-$STR_UNKNOWN_APP}"
# 如果正则没有提取到 PID未命中合法窗口静默退出
if [[ -z "$pid" ]]; then
exit 0
fi
# 如果确实抓到了 PID但此时进程已不存在发出异常通知
if [[ ! -f "/proc/$pid/comm" ]]; then
notify-send "$STR_ERR_INFO_TITLE" "$STR_ERR_INFO_MSG" -a "Window Killer" -i dialog-error
exit 1
fi
# ==========================================
# 4. 判定协议类型并获取真实 PID
# ==========================================
process_name=$(cat "/proc/$pid/comm")
process_name_lower="${process_name,,}"
if [[ "$process_name_lower" == *"xwayland"* ]]; then
proto_str="XWayland"
# 给内核与 X11 服务端预留 50 毫秒的时间传递和同步焦点
sleep 0.05
# 顺着焦点,询问 X11 当前活动的窗口 ID
active_wid=$(xprop -root -notype _NET_ACTIVE_WINDOW 2>/dev/null | grep -o '0x[0-9a-fA-F]\+')
# 顺藤摸瓜:提取这个 X11 窗口绑定的真实底层 Linux PID
real_pid=$(xprop -id "$active_wid" -notype _NET_WM_PID 2>/dev/null | grep -oP '\d+')
else
proto_str="Wayland"
real_pid="$pid"
fi
if [[ -z "$real_pid" ]]; then
notify-send "$STR_ERR_KILL_TITLE" "$STR_ERR_KILL_MSG" -a "Window Killer" -i dialog-error
exit 1
fi
# ==========================================
# 5. 九头蛇绞杀逻辑 (Hydra Kill)
# ==========================================
# 向上追溯,寻找进程家族的老祖宗 (App Root)
app_root=$real_pid
current=$real_pid
while true; do
ppid=$(ps -o ppid= -p "$current" 2>/dev/null | tr -d ' ')
# 如果找不到父进程,或者父进程是系统最高层 PID 1停止追溯
if [[ -z "$ppid" || "$ppid" == "1" ]]; then
break
fi
pname=$(ps -o comm= -p "$ppid" 2>/dev/null)
# 【核心防火墙】:遇到桌面环境、终端、系统核心服务,立刻停止溯源!
if [[ "$pname" =~ ^(systemd|niri|bash|zsh|fish|tmux|screen|xwayland.*|sshd|login|init|sway|hyprland)$ ]]; then
break
fi
app_root=$ppid
current=$ppid
done
# 向下递归,收集家族所有子孙 PID
get_descendants() {
local p=$1
echo "$p"
# pgrep -P 获取直接子进程
for c in $(pgrep -P "$p" 2>/dev/null); do
get_descendants "$c"
done
}
family_pids=$(get_descendants "$app_root")
pid_count=$(echo "$family_pids" | wc -w)
# 执行联合绞杀:把所有收集到的 PID 一次性全部强制终止
kill -9 $family_pids 2>/dev/null
# ==========================================
# 6. 发送战果通知与音效
# ==========================================
printf -v final_title "$STR_SUCC_TITLE" "$proto_str"
printf -v final_msg "$STR_SUCC_MSG" "$app_name" "$pid_count"
notify_and_play "$final_title" "$final_msg"