如何修改XMobar的Kbd监控插件实现点击循环切换布局
XMobar点击切换键盘布局实现方案
方案一:自定义MyOwnKbd插件(纯Haskell实现)
如果希望把点击逻辑和插件业务完全绑定,最可靠的方式是自定义插件,通过Haskell维护布局切换状态,无需外部脚本。
插件代码实现
在XMobar配置文件中添加以下代码(需确保导入必要模块):
import Xmobar import System.Process (callProcess) import Data.IORef (IORef, newIORef, readIORef, writeIORef) import Data.List (elemIndex) -- 自定义插件数据类型,存储布局列表和当前索引状态 data MyOwnKbd = MyOwnKbd [(String, String)] (IORef Int) instance Runner MyOwnKbd where -- 初始化插件:创建索引引用,设置初始布局,返回点击处理函数 run (MyOwnKbd layouts ref) _ updateDisplay = do let (initLayout, _) = layouts !! 0 callProcess "setxkbmap" (splitLayout initLayout) refreshDisplay layouts ref updateDisplay return $ handleClick layouts ref updateDisplay alias _ = "mykbd" -- 解析布局字符串(如"ru(phonetic)"拆分为["ru", "-variant", "phonetic"]) splitLayout :: String -> [String] splitLayout s = case break (== '(') s of (lang, "") -> [lang] (lang, '(':rest) -> lang : "-variant" : init rest -- 更新状态栏显示当前布局别名 refreshDisplay :: [(String, String)] -> IORef Int -> (String -> IO ()) -> IO () refreshDisplay layouts ref update = do idx <- readIORef ref let (_, alias) = layouts !! idx update alias -- 点击事件处理:循环切换到下一个布局 handleClick :: [(String, String)] -> IORef Int -> (String -> IO ()) -> Button -> IO () handleClick layouts ref update _ = do currentIdx <- readIORef ref let nextIdx = (currentIdx + 1) `mod` length layouts (targetLayout, _) = layouts !! nextIdx -- 执行布局切换 callProcess "setxkbmap" (splitLayout targetLayout) -- 更新状态索引和显示 writeIORef ref nextIdx refreshDisplay layouts ref update -- 构造插件的便捷函数 myKbd :: [(String, String)] -> IO MyOwnKbd myKbd layouts = MyOwnKbd layouts <$> newIORef 0
配置使用
在XMobar的commands列表中添加插件,模板中引用别名:
commands = [ -- 其他插件... Run $ myKbd [("it", "IT"), ("ru(phonetic)", "RU")] ] template = "%mykbd% | 其他状态栏元素..."
点击%mykbd%区域时,会在指定布局列表中循环切换,状态由插件内部维护,无需外部依赖。
方案二:原生Kbd插件+内嵌Haskell脚本
如果不想编写完整自定义插件,可以利用XMobar的<action>标签,通过内嵌Haskell脚本实现切换逻辑,无需额外外部脚本文件。
配置示例
保留原生Kbd插件,修改模板为:
commands = [ Run $ Kbd [("it", "IT"), ("ru(phonetic)", "RU")] ] template = "<action=`runhaskell -e \"import System.Process; import Data.List; getCurrentLayout = head . words <$> readProcess \\\"setxkbmap\\\" [\\\"-query\\\"] \\\"\\\"; let layouts = [\\\"it\\\", \\\"ru(phonetic)\\\"]; getCurrentLayout >>= \\l -> let Just idx = elemIndex l layouts; next = layouts !! ((idx+1) `mod` length layouts) in callProcess \\\"setxkbmap\\\" (splitLayout next) where splitLayout s = case break (== '(') s of (lang,\\\"\\\") -> [lang]; (lang,'(':rest) -> lang : \\\"-variant\\\" : init rest\"`>%kbd%</action>"
逻辑说明
点击%kbd%区域时,执行内嵌的Haskell代码:
- 调用
setxkbmap -query获取当前布局 - 在指定布局列表中找到当前布局的索引
- 计算下一个布局的索引(循环)
- 调用
setxkbmap切换到目标布局,原生Kbd插件会自动同步显示
关键说明
原生Kbd插件仅负责监控布局变化并显示,无法直接将点击事件反馈到自身业务逻辑,因此必须通过自定义插件或内嵌脚本的方式实现点击切换需求。
内容的提问来源于stack exchange,提问作者Enlico
相关产品推荐
相关产品推荐

