服务:kvm和宿主机的快捷键分配混乱

目标与现状

问题:在 KVM 里操作旧 Win10 时,宿主机与 guest 的快捷键互相干扰 —— 有的键被宿主机抢先处理,有的在 guest 里无效。

最终诉求(用户 2026-10-15 定稿,只保留这一条):只有 Win / Win+E / Win+Esc 三个键需要「激活窗口优先」 —— VM 控制台窗口聚焦时只进 guest,宿主窗口聚焦时只由宿主机处理;其余快捷键不在本机制范围内,用户自行克服。

现状:已达成。机制是一条 KWin 窗口规则(by_focus 层),不需要任何额外软件。所有快捷键的行为只分两类:host 处理 或 激活窗口处理。

前置事实(实测)

项实测值
VM 控制台窗口识别virt-manager 控制台 WM_CLASS .virt-manager-wrapped(wmname QEMU/KVM);virt-viewer WM_CLASS .virt-viewer-wrapped
压制机制KWin 窗口规则动作 disableglobalshortcuts → kwin/src/workspace.cpp:disableGlobalShortcutsForClient() → D-Bus org.kde.KGlobalAccel.blockGlobalShortcuts(true) ⇒ 该窗口聚焦期间 KGlobalAccel 整块失效
粒度窗口级,不是按键级 —— 该窗口内 KGlobalAccel 全有全无,做不到「只放开某几个键」
规则生效的必要条件每个动作必须同时写 <动作>=值 与 <动作>rule=<1..4> 策略键;缺策略键 ⇒ 整个动作被静默忽略(1=Apply 2=Force 3=Remember 4=ApplyNow)
本机环境Plasma / KWin 6(X11 会话);实时配置文件 ~/.config/kwinrulesrc(KWin 只从 KConfig 读,系统级 /etc/xdg 的规则列表合并不可靠)

实现过程

数据(单一数据源)

/etc/nixos/modules/system/service/kwin-data/kwin-hotkey-router-rules.yml 的 window_rules 段,两条:

window_rules:
- uuid: "248e7dde-…" # virt-viewer
description: "virt-viewer 窗口:忽略全局快捷键(Win 等交给 guest)"
actions: { disableglobalshortcuts: true }
match: { wmclass: "virt-viewer", wmclassmatch: "2" }
- uuid: "4b55d2a4-…" # virt-manager 控制台
description: "virt-manager 控制台窗口:忽略全局快捷键(Win 等交给 guest)"
actions: { disableglobalshortcuts: true }
match: { wmclass: "virt-manager", wmclassmatch: "2", wmname: "QEMU/KVM", wmnamematch: "2" }

生成

kwin.nix 里的 kwinRulesMerge(python + pyyaml,rebuild 激活期运行)把 yml 渲染成 KWin 的 kconfig 段,并为每个动作自动补 <动作>rule=2(Force),写进 ~/.config/kwinrulesrc,随后调 org.kde.KWin.reconfigure 让运行中的 KWin 当场重载。

覆盖语义

段级整体覆盖:yml 里出现的 uuid 段被整体替换,未定义的段原样保留 —— 用户在 KDE 界面里自己加的规则不受影响(例如 gwenview 的 Meta+Shift+E 规则)。

生效逻辑

控制台窗口聚焦 ──► KWin 规则命中 ──► blockGlobalShortcuts(true) ──► Win / Win+E / Win+Esc 流给 guest
其它窗口聚焦 ──► 规则不命中 ──► KGlobalAccel 正常工作 ──► 宿主机处理

实测证据

检查结果
~/.config/kwinrulesrc两条 nix 规则均为 disableglobalshortcuts=true + disableglobalshortcutsrule=2;count=3(含用户自己的 gwenview 规则)
VM 控制台窗口内按 Win / Win+E / Win+Esc只由 guest 响应,宿主机无动作 ✅
宿主窗口内按 Win / Win+E / Win+Esc只由宿主机响应 ✅
宿主侧原有快捷键恢复原键(CenterFitWindow=Meta+Shift+X、Window Maximize=Meta+PgUp、Switch One Desktop to the Left=Meta+Ctrl+Left 等)✅

管理命令

用途命令
看规则是否真的生效(必须有 rule= 行)grep -nE "disableglobalshortcuts" ~/.config/kwinrulesrc
改规则编辑上述 yml → /run/wrappers/bin/sudo -n nixos-rebuild switch(自动重写规则 + 重载 KWin)
只让 KWin 重载配置(不 rebuild)qdbus org.kde.KWin /KWin org.kde.KWin.reconfigure
看当前 KWin 会话信息 / 规则文件qdbus org.kde.KWin /KWin supportInformation | head;head ~/.config/kwinrulesrc

机制说明:yml 是数据,kwin.nix 是唯一生成器,rebuild 是唯一落地时机 —— 手改 kwinrulesrc 会在下次 rebuild 被 nix 定义的段覆盖(未定义段不受影响)。

踩坑记录

① 规则只有 =true、缺 rule= 策略键 ⇒ 整条动作被静默忽略

  • 现象:以为规则在生效,实际热键「两头都响应」——kwinrulesrc 里明明有 disableglobalshortcuts=true 却毫无作用。
  • 成因:KWin 规则的每个动作都要配策略键 <动作>rule=<1..4>,缺省等于不应用。
  • 改法:生成器为每个动作补 <动作>rule=2(Force;仍不生效可试 1=Apply)。在 KDE 界面里手建的规则是好的(界面会自动写 rule=),换成脚本生成后就静默失效了 —— 这是排查时最容易走偏的一步。

② 窗口规则是窗口级、不是按键级

  • 现象:想「只让某几个键交给 guest、其余宿主优先」时无法表达。
  • 成因:blockGlobalShortcuts 是布尔整块开关。
  • 处置:本需求只保留三个键,窗口级正好够用(用户 2026-10-15 定稿「其余自己克服」)。

③ evdev 层方案(keyd)会误伤宿主侧绑定

  • 现象:宿主侧 Win+Shift+X/Z、Win+Ctrl+方向 全部失效,且裸 Win 在 VM 内同时触发两侧。
  • 成因:在 evdev 层介入按键,与 KGlobalAccel 的作用域交叉抢占。
  • 处置:整个 keyd 方案撤回(modules/system/service/keyd.nix 已删除),只用窗口规则。

④ sxhkd + 解绑 KGlobalAccel 方案在 VM 窗口内无效

  • 现象:宿主窗口里生效,进 VM 控制台窗口一律失效(连应用自己抓的键也失效)。
  • 成因:SPICE 控制台控件(virt-manager 控制台与 virt-viewer 是同一个 widget)聚焦时持 X 键盘 active grab,压过所有 X 层抓键(KGlobalAccel、XGrabKey、应用自身抓键)。
  • 处置:撤回;本需求用窗口规则即可,不需要 X 层抓键。

⑤ KWin 保存自身配置会覆盖外部写入的规则段

  • 现象:count 一度从 3 掉到 2,一条 nix 规则凭空消失(随后 rebuild 自愈)。
  • 成因:KWin 按内存态重写整个 kwinrulesrc。
  • 处置:以 rebuild 为自愈手段,需要时重跑一次重建。

边界

  • 本机制只覆盖 Win / Win+E / Win+Esc 的激活窗口优先,不覆盖其它快捷键。
  • 不覆盖「在 VM 窗口内也让宿主机响应」的键 —— 那需要 evdev 层方案,已判定会误伤宿主绑定,不做。
  • 不覆盖应用自身的 X11 抓键(如微信的自定义键,在控制台窗口内受 active grab 影响)。
  • 反检测 / 性能分档等其它主题不在本篇范围。
  • 「用起来对不对」属人工验收项:按上表三条实测判据自测。

相关:完整取证在 nixos 项目台账 docs/legacy-win-vm.md §13–§19;可复用流程见 skill kwin-hotkey-routing。