diff --git a/README.md b/README.md index ca54cd5..ce666f7 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ Keep and restore fcitx state for each buffer separately when leaving/re-entering insert mode. Like always typing English in normal mode, but Chinese in insert mode. +The branch uses a server-client architecture to support cross-user usage (e.g. `sudo vim`) or even cross-host usages (not implemented yet). + Requires: * fcitx 5 * Vim with Python 3 compiled in * The python-dbus package +* Run the `fcitx-status` script as a service Links: @@ -17,11 +20,14 @@ Warning: 在离开或重新进入插入模式时自动记录和恢复每个缓冲区各自的输入法状态,以便在普通模式下始终是英文输入模式,切换回插入模式时恢复离开前的输入法输入模式。 +这个分支使用服务端/客户端架构,以便支持跨用户的用法(如 `sudo vim`),甚至是跨主机的用法(尚未实现)。 + 要求: * fcitx 5 * 带有 Python 3 支持的 Vim * python-dbus 包 +* 作为服务运行 `fcitx-status` 脚本 链接: diff --git a/fcitx-status b/fcitx-status new file mode 100755 index 0000000..6447a92 --- /dev/null +++ b/fcitx-status @@ -0,0 +1,67 @@ +#!/usr/bin/python3 + +import asyncio +import functools + +import dbus + +def may_reconnect(func): + @functools.wraps(func) + def wrapped(self, *args, **kwargs): + for _ in range(2): + try: + return func(self, *args, **kwargs) + except dbus.exceptions.DBusException: + self.connect() + return wrapped + +class FcitxComm(): + def __init__(self): + self.connect() + + def connect(self): + bus = dbus.SessionBus() + obj = bus.get_object('org.fcitx.Fcitx5', '/controller') + self.fcitx = dbus.Interface(obj, dbus_interface='org.fcitx.Fcitx.Controller1') + + @may_reconnect + def status(self): + return self.fcitx.State() == 2 + + @may_reconnect + def activate(self): + self.fcitx.Activate() + + @may_reconnect + def deactivate(self): + self.fcitx.Deactivate() + +async def fcitx_serve(Fcitx, reader, writer): + while True: + data = await reader.read(1) + if not data: + break + comm = data[0] + if comm == 0: + st = 1 if Fcitx.status() else 0 + writer.write(st.to_bytes(1, 'little')) + await writer.drain() + elif comm == 1: + Fcitx.activate() + elif comm == 2: + Fcitx.deactivate() + +async def main(): + Fcitx = FcitxComm() + server = await asyncio.start_unix_server( + functools.partial(fcitx_serve, Fcitx), path='\0fcitx-status') + async with server: + await server.serve_forever() + +if __name__ == '__main__': + try: + import setproctitle + setproctitle.setproctitle('fcitx-status') + except ImportError: + pass + asyncio.run(main()) diff --git a/plugin/fcitx.py b/plugin/fcitx.py index 9456653..448ade0 100644 --- a/plugin/fcitx.py +++ b/plugin/fcitx.py @@ -1,49 +1,72 @@ import vim -import functools +import socket +import struct -import dbus +fcitxsocketfile = '\0fcitx-status' +fcitx_loaded = False -class FcitxComm(): - def __init__(self): - bus = dbus.SessionBus() - obj = bus.get_object('org.fcitx.Fcitx5', '/controller') - self.fcitx = dbus.Interface(obj, dbus_interface='org.fcitx.Fcitx.Controller1') +class FcitxComm(object): + STATUS = b'\0' + ACTIVATE = b'\1' + DEACTIVATE = b'\2' + + def __init__(self, socketfile): + self.socketfile = socketfile + self.sock = None def status(self): - return self.fcitx.State() == 2 + return self._with_socket(self._status) def activate(self): - self.fcitx.Activate() + self._with_socket(self._command, self.ACTIVATE) def deactivate(self): - self.fcitx.Deactivate() + self._with_socket(self._command, self.DEACTIVATE) -try: - Fcitx = FcitxComm() - fcitx_loaded = True -except dbus.exceptions.DBusException as e: - vim.command('echohl WarningMsg | echom "fcitx.vim not loaded: %s" | echohl NONE' % e) - fcitx_loaded = False + def _error(self, e): + estr = str(e).replace('"', r'\"') + file = self.socketfile.replace('"', r'\"').replace('\0', '@') + vim.command('echohl WarningMsg | echo "fcitx.vim: socket %s error: %s" | echohl NONE' % (file, estr)) -def may_reconnect(func): - @functools.wraps(func) - def wrapped(): - global Fcitx - for _ in range(2): - try: - return func() - except Exception as e: - vim.command('echohl WarningMsg | echom "fcitx.vim: %s: %s" | echohl NONE' % (type(e).__name__, e)) - Fcitx = FcitxComm() - return wrapped + def _connect(self): + self.sock = sock = socket.socket(socket.AF_UNIX) + sock.settimeout(0.5) + try: + sock.connect(self.socketfile) + return True + except (socket.error, socket.timeout) as e: + self.sock = None + self._error(e) + return False + + def _with_socket(self, func, *args, **kwargs): + if not self.sock: + if not self._connect(): + return + + try: + return func(*args, **kwargs) + except (socket.error, socket.timeout, struct.error) as e: + self._error(e) + + def _status(self): + self.sock.send(self.STATUS) + return self.sock.recv(1)[0] + + def _command(self, cmd): + self.sock.send(cmd) + +Fcitx = FcitxComm(fcitxsocketfile) -@may_reconnect def fcitx2en(): - if Fcitx.status(): + st = Fcitx.status() + if st is None: + return + + if st: vim.command('let b:inputtoggle = 1') Fcitx.deactivate() -@may_reconnect def fcitx2zh(): if vim.eval('exists("b:inputtoggle")') == '1': if vim.eval('b:inputtoggle') == '1': @@ -51,3 +74,5 @@ def fcitx2zh(): vim.command('let b:inputtoggle = 0') else: vim.command('let b:inputtoggle = 0') + +fcitx_loaded = True