update
This commit is contained in:
@@ -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
@@ -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. 获取真实的底层 PID(Wayland 直接取用,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"
|
||||
Reference in New Issue
Block a user