基于xkb-switch实现客户端独立键盘布局切换异常,疑存竞态问题
问题
我想用xkb-switch工具切换键盘布局,实现IBus中「所有应用共享同一输入法 - 禁用」的效果,即每个窗口保留独立的键盘布局,切换窗口时自动切回对应窗口的布局。
我的实现思路:
- 窗口失去焦点时,保存当前xkb-switch输出的布局(例如
us(altgr-intl)、am、ru) - 窗口获得焦点时,调用
xkb-switch --switch <layout>命令切回该窗口保存的布局
我编写的Lua尝试代码如下:
client.connect_signal("unfocus", function(c) awful.spawn.easy_async_with_shell("xkb-switch", function(stdout) if c.valid then c.keyboard_layout = stdout end end) end) client.connect_signal("focus", function(c) c = awful.screen.focused({client = true}) if c.keyboard_layout == nil then c.keyboard_layout = "us(altgr-intl)" end awful.spawn.with_shell("xkb-switch -s "..c.keyboard_layout) end)
当前问题:通知弹窗显示的语言符合预期,但实际切换的语言错误,怀疑存在竞态风险?
解决方案
确实存在竞态风险,同时代码还有几处逻辑问题:
- 竞态问题:
unfocus信号中使用的easy_async_with_shell是异步调用,当窗口快速切换时,前一个窗口的异步回调可能在新窗口获得焦点后才执行,导致新窗口的布局被旧窗口的回调覆盖。 - 焦点客户端逻辑错误:
focus信号的参数c已经是当前获得焦点的客户端,重新赋值为awful.screen.focused({client = true})会在多屏幕等场景下获取到错误的客户端。 - 输出格式问题:
xkb-switch的输出自带换行符,直接赋值给c.keyboard_layout会导致切换命令中包含无效的换行字符,引发执行错误。
修改后的代码:
-- 窗口失去焦点时同步保存布局,避免异步竞态 client.connect_signal("unfocus", function(c) if c.valid then -- 同步执行xkb-switch,读取输出并清理末尾空白(含换行) local handle = io.popen("xkb-switch") local layout = handle:read("*a"):gsub("%s+$", "") handle:close() c.keyboard_layout = layout end end) client.connect_signal("focus", function(c) -- 直接使用参数传入的当前焦点客户端 if not c.keyboard_layout then c.keyboard_layout = "us(altgr-intl)" end -- 清理布局字符串并包裹单引号,避免特殊字符干扰 local clean_layout = c.keyboard_layout:gsub("%s+$", "") awful.spawn.with_shell("xkb-switch -s '" .. clean_layout .. "'") end)
关键改动说明:
- 将
unfocus中的异步调用替换为同步的io.popen,确保布局保存操作在窗口失去焦点时立即完成,彻底解决竞态问题。 - 移除
focus信号中重新获取客户端的代码,直接使用参数传递的当前焦点窗口,保证操作对象正确。 - 对
xkb-switch的输出进行空白字符清理,并在切换命令中用单引号包裹布局字符串,避免因特殊字符导致命令执行失败。
内容的提问来源于stack exchange,提问作者RobberFokZ
相关产品推荐
相关产品推荐

