为中文输入法 fcitx 提供 Wayland 桌面通知功能的 lua 脚本

a fcitx5 addon lua script that enables wayland notification

zping

luafcitxwaylandmako

1119 字

2025-04-11 11:11 +0000


本篇中脚本依赖特定应用,仅仅提供一个实现的思路供路人参考

特定的依赖,包括 libnotify 和 mako ,特别是 makoctl list 输出格式的更新,已经让我不得不专门对本篇进行了一次更新。 这次更新可以参考这个gist

之前写过一个 Lua 脚本,通过 fcitx5 插件(addon)功能,让其在输入法状态切换时显示桌面通知。这两天我在更新自己工作环境,除了新装机以外,也更新了日常使用的操作系统和桌面环境。所以之前的脚本也需要更新。

这个功能的目的和由来之前的分享里已经提过。主要是让自己能够更好地感知输入法的状态。特别是有三四种输入法时,切换时提示醒目一点不是坏事。

我这次选择了 Wayland/Sway 这个桌面环境,通知服务主要选择是 makoswaync 。本来一开始我测了一下 mako ,发现 mako 功能有点少,想多整点花活,于是又装了 swaync 。然而,我发现 swaync 基于 gtk 实现,其 css 和日常 W3Ccss e 。调了半天连最简单的文本居中都没有调出来,怒而改回用 mako

最后配置路径仍和之前差不多,插件配置文件的具体路径是 ~/.local/share/fcitx5/addon/notify.conf

[Addon]
Name=Notify send Lua
Comment=Lua script for displaying notification
Category=Module
Type=Lua
OnDemand=False
Configurable=False
Library=notify.lua

[Addon/Dependencies]
0=luaaddonloader

还有 Lua 脚本的具体路径则是 ~/.local/share/fcitx5/lua/notify/notify.lua

local fcitx = require("fcitx")

local notify_app_name = "fcitx5"

function deepcopy(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in pairs(orig) do
            copy[deepcopy(orig_key)] = deepcopy(orig_value)
        end
    else -- number, string, boolean, etc.
        copy = orig
    end
    return copy
end

local args = {
   "${HOME}/.nix-profile/bin/notify-send",
   "--print-id",
   "-a", "\"" .. notify_app_name .. "\"",
   "-t", "2000",
   "\"" .. notify_app_name .. "\""
}

fcitx.watchEvent(fcitx.EventType.KeyEvent, "caps_lock")
fcitx.watchEvent(fcitx.EventType.SwitchInputMethod, "switch_im")

-- 获取指定 LED 的当前状态
local function get_led_state(led_name)
    local file = io.open("/sys/class/leds/input0::"..led_name.."/brightness", "r")
    if not file then return nil end
    local state = file:read("*n")  -- 读取数值
    print(state)
    file:close()
    return state == 1 and "ON" or "OFF"
end

local function notify(msg_body)
   local notify_id_file = "/tmp/last-" .. notify_app_name .. "-notify-id"
   local notify_id = nil
   local file = io.open(notify_id_file, "r")
   local cargs = deepcopy(args)
   table.insert(cargs, msg_body)

   if file then
      notify_id = file:read("*l")
      file:close()
   end

   local has_notify_id = false
   if notify_id then
      -- 检查 makoctl 列表中是否存在该通知
      local handle = io.popen("${HOME}/.nix-profile/bin/makoctl list")
      local mako_output = handle:read("*a")
      handle:close()

      if mako_output and mako_output:find('Notification ' .. notify_id) then
         has_notify_id = true
      end
   end
   -- 发送通知
   local new_notify_id = nil
   if notify_id and has_notify_id then
      table.insert(cargs, 2, notify_id)
      table.insert(cargs, 2, "-r")
      local cmd = table.concat(cargs, " ")
      local handle = io.popen(cmd)
      new_notify_id = handle:read("*l")
      handle:close()
   else
      -- 发送新通知
      local cmd = table.concat(cargs, " ")
      local handle = io.popen(cmd)
      new_notify_id = handle:read("*l")
      handle:close()

      -- 保存新通知 ID
      if new_notify_id then
         file = io.open(notify_id_file, "w")
         if file then
            file:write(new_notify_id)
            file:close()
         end
      end
   end
end

function caps_lock(sym, state, release)
   if sym == 65509 and release then
      local capslock_state_msg = "\"CapsLock <b>" ..  get_led_state("capslock") .. "</b>\""
      print(capslock_state_msg)
      notify(capslock_state_msg)
      -- print(string.format("change state of CapsLock: %s", enable))
   end
end

function switch_im()
   local msg = string.format("\"InputMethod switched to <b>%s</b>\"", fcitx.currentInputMethod())
   print(msg)
   notify(msg)
end

既然过了几年,还是脚本就再增加一些功能。这里主要就是增加 --replace-id 参数。这样在频繁切换时,会替换掉之前消息框,不会出现之前一堆消息框堆在一起的情况。当然,这个实现其实用 swaync 做比 mako 简单。因为 swaync 的逻辑是只要带上 replace-id 那返回的消息 ID 就是 replace-id ,和之前发送的消息的 ID 没有任何关系。因此通知脚本无需追踪先前通知的状态。 mako 的行为则是它拿到的 replace-id 必须是有效的,否则它会返回新 ID ,这显著增加了实现复杂度。

脚本依赖包括 mako libnotify fcitx5-lua 这三项。还有两点要注意的,一点是我并不熟悉 lua ,五年只写过四个。这样的情况所以像那个 deepcopy 函数,直接拿人工智能写的。这个实现可能并不合理。另一点是这个查询大小写状态我直接去读 /sys/class/leds/input0::capslock/brightness ,我也没有花太多时间想没有更好的实现,也是直接让人工智能写的。反正现在能跑通。

这次我的配置已经用上了 nix home manager ,相关的配置是写在 home.nix 里面的。这次涉及的配置片断是这样的:

home.file = {
  ".local/share/fcitx5/addon".source = config.lib.file.mkOutOfStoreSymlink ./fcitx5-addon;
  ".local/share/fcitx5/lua".source = config.lib.file.mkOutOfStoreSymlink ./fcitx5-lua;
};

这篇有点长,先到这里。