进一步的 kvmd 国际化(汉化)支持

This commit is contained in:
mofeng-git
2024-08-12 22:58:01 +08:00
parent 0af0e2b4d0
commit 5b25b3661f
11 changed files with 599 additions and 90 deletions

View File

@@ -34,6 +34,8 @@ import aiohttp
from PIL import Image as PilImage
from ...lanuages import Lanuages
from ...logging import get_logger
from ... import tools
@@ -226,6 +228,9 @@ class Streamer: # pylint: disable=too-many-instance-attributes
self.__notifier = aiotools.AioNotifier()
self.gettext=Lanuages().gettext
# =====
@aiotools.atomic_fg
@@ -237,15 +242,15 @@ class Streamer: # pylint: disable=too-many-instance-attributes
if not self.__stop_wip:
self.__stop_task.cancel()
await asyncio.gather(self.__stop_task, return_exceptions=True)
logger.info("Streamer stop cancelled")
logger.info(self.gettext("Streamer stop cancelled"))
return
else:
await asyncio.gather(self.__stop_task, return_exceptions=True)
if reset and self.__reset_delay > 0:
logger.info("Waiting %.2f seconds for reset delay ...", self.__reset_delay)
logger.info(self.gettext("Waiting %.2f seconds for reset delay ..."), self.__reset_delay)
await asyncio.sleep(self.__reset_delay)
logger.info("Starting streamer ...")
logger.info(self.gettext("Starting streamer ..."))
await self.__inner_start()
@aiotools.atomic_fg
@@ -258,12 +263,12 @@ class Streamer: # pylint: disable=too-many-instance-attributes
if not self.__stop_wip:
self.__stop_task.cancel()
await asyncio.gather(self.__stop_task, return_exceptions=True)
logger.info("Stopping streamer immediately ...")
logger.info(self.gettext("Stopping streamer immediately ..."))
await self.__inner_stop()
else:
await asyncio.gather(self.__stop_task, return_exceptions=True)
else:
logger.info("Stopping streamer immediately ...")
logger.info(self.gettext("Stopping streamer immediately ..."))
await self.__inner_stop()
elif not self.__stop_task:
@@ -272,13 +277,13 @@ class Streamer: # pylint: disable=too-many-instance-attributes
try:
await asyncio.sleep(self.__shutdown_delay)
self.__stop_wip = True
logger.info("Stopping streamer after delay ...")
logger.info(self.gettext("Stopping streamer after delay ..."))
await self.__inner_stop()
finally:
self.__stop_task = None
self.__stop_wip = False
logger.info("Planning to stop streamer in %.2f seconds ...", self.__shutdown_delay)
logger.info(self.gettext("Planning to stop streamer in %.2f seconds ..."), self.__shutdown_delay)
self.__stop_task = asyncio.create_task(delayed_stop())
def is_working(self) -> bool:
@@ -307,7 +312,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
except (aiohttp.ClientConnectionError, aiohttp.ServerConnectionError):
pass
except Exception:
get_logger().exception("Invalid streamer response from /state")
get_logger().exception(self.gettext("Invalid streamer response from /state"))
snapshot: (dict | None) = None
if self.__snapshot:
@@ -325,10 +330,10 @@ class Streamer: # pylint: disable=too-many-instance-attributes
async def poll_state(self) -> AsyncGenerator[dict, None]:
def signal_handler(*_: Any) -> None:
get_logger(0).info("Got SIGUSR2, checking the stream state ...")
get_logger(0).info(self.gettext("Got SIGUSR2, checking the stream state ..."))
self.__notifier.notify()
get_logger(0).info("Installing SIGUSR2 streamer handler ...")
get_logger(0).info(self.gettext("Installing SIGUSR2 streamer handler ..."))
asyncio.get_event_loop().add_signal_handler(signal.SIGUSR2, signal_handler)
waiter_task: (asyncio.Task | None) = None
@@ -384,12 +389,12 @@ class Streamer: # pylint: disable=too-many-instance-attributes
self.__snapshot = snapshot
self.__notifier.notify()
return snapshot
logger.error("Stream is offline, no signal or so")
logger.error(self.gettext("Stream is offline, no signal or so"))
except (aiohttp.ClientConnectionError, aiohttp.ServerConnectionError) as err:
logger.error("Can't connect to streamer: %s", tools.efmt(err))
logger.error(self.gettext("Can't connect to streamer: %s"), tools.efmt(err))
except Exception:
logger.exception("Invalid streamer response from /snapshot")
logger.exception(self.gettext("Invalid streamer response from /snapshot"))
return None
def remove_snapshot(self) -> None:
@@ -446,14 +451,14 @@ class Streamer: # pylint: disable=too-many-instance-attributes
await self.__start_streamer_proc()
assert self.__streamer_proc is not None
await aioproc.log_stdout_infinite(self.__streamer_proc, logger)
raise RuntimeError("Streamer unexpectedly died")
raise RuntimeError(self.gettext("Streamer unexpectedly died"))
except asyncio.CancelledError:
break
except Exception:
if self.__streamer_proc:
logger.exception("Unexpected streamer error: pid=%d", self.__streamer_proc.pid)
logger.exception(self.gettext("Unexpected streamer error: pid=%d"), self.__streamer_proc.pid)
else:
logger.exception("Can't start streamer")
logger.exception(self.gettext("Can't start streamer"))
await self.__kill_streamer_proc()
await asyncio.sleep(1)
@@ -474,13 +479,13 @@ class Streamer: # pylint: disable=too-many-instance-attributes
try:
await aioproc.log_process(cmd, logger, prefix=name)
except Exception as err:
logger.exception("Can't execute command: %s", err)
logger.exception(self.gettext("Can't execute command: %s"), err)
async def __start_streamer_proc(self) -> None:
assert self.__streamer_proc is None
cmd = self.__make_cmd(self.__cmd)
self.__streamer_proc = await aioproc.run_process(cmd)
get_logger(0).info("Started streamer pid=%d: %s", self.__streamer_proc.pid, tools.cmdfmt(cmd))
get_logger(0).info(self.gettext("Started streamer pid=%d: %s"), self.__streamer_proc.pid, tools.cmdfmt(cmd))
async def __kill_streamer_proc(self) -> None:
if self.__streamer_proc:

View File

@@ -26,6 +26,8 @@ from typing import AsyncGenerator
from typing import Callable
from typing import Any
from ...lanuages import Lanuages
from ...logging import get_logger
from ...errors import IsBusyError
@@ -46,22 +48,22 @@ from ...yamlconf import Section
# =====
class GpioChannelNotFoundError(GpioOperationError):
def __init__(self) -> None:
super().__init__("GPIO channel is not found")
super().__init__(Lanuages().gettext("GPIO channel is not found"))
class GpioSwitchNotSupported(GpioOperationError):
def __init__(self) -> None:
super().__init__("This GPIO channel does not support switching")
super().__init__(Lanuages().gettext("This GPIO channel does not support switching"))
class GpioPulseNotSupported(GpioOperationError):
def __init__(self) -> None:
super().__init__("This GPIO channel does not support pulsing")
super().__init__(Lanuages().gettext("This GPIO channel does not support pulsing"))
class GpioChannelIsBusyError(IsBusyError, GpioError):
def __init__(self) -> None:
super().__init__("Performing another GPIO operation on this channel, please try again later")
super().__init__(Lanuages().gettext("Performing another GPIO operation on this channel, please try again later"))
# =====
@@ -80,6 +82,8 @@ class _GpioInput:
self.__driver = driver
self.__driver.register_input(self.__pin, config.debounce)
self.gettext=Lanuages().gettext
def get_scheme(self) -> dict:
return {
"hw": {
@@ -188,7 +192,7 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
await func(*args)
else:
await aiotools.run_region_task(
f"Can't perform {name} of {self} or operation was not completed",
self.gettext(f"Can't perform {name} of {self} or operation was not completed"),
self.__region, self.__action_task_wrapper, name, func, *args,
)
@@ -197,12 +201,12 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
try:
return (await func(*args))
except GpioDriverOfflineError:
get_logger(0).error("Can't perform %s of %s or operation was not completed: driver offline", name, self)
get_logger(0).error(self.gettext("Can't perform %s of %s or operation was not completed: driver offline"), name, self)
@aiotools.atomic_fg
async def __inner_switch(self, state: bool) -> None:
await self.__write(state)
get_logger(0).info("Ensured switch %s to state=%d", self, state)
get_logger(0).info(self.gettext("Ensured switch %s to state=%d"), self, state)
await asyncio.sleep(self.__busy_delay)
@aiotools.atomic_fg
@@ -213,7 +217,7 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
finally:
await self.__write(False)
await asyncio.sleep(self.__busy_delay)
get_logger(0).info("Pulsed %s with delay=%.2f", self, delay)
get_logger(0).info(self.gettext("Pulsed %s with delay=%.2f"), self, delay)
# =====
@@ -224,7 +228,7 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
await self.__driver.write(self.__pin, (state ^ self.__inverted))
def __str__(self) -> str:
return f"Output({self.__channel}, driver={self.__driver}, pin={self.__pin})"
return self.gettext(f"Output({self.__channel}, driver={self.__driver}, pin={self.__pin})")
__repr__ = __str__
@@ -249,6 +253,8 @@ class UserGpio:
self.__inputs: dict[str, _GpioInput] = {}
self.__outputs: dict[str, _GpioOutput] = {}
self.gettext=Lanuages().gettext
for (channel, ch_config) in tools.sorted_kvs(config.scheme):
driver = self.__drivers[ch_config.driver]
if ch_config.mode == UserGpioModes.INPUT:
@@ -289,12 +295,12 @@ class UserGpio:
await self.__notifier.wait()
def sysprep(self) -> None:
get_logger(0).info("Preparing User-GPIO drivers ...")
get_logger(0).info(self.gettext("Preparing User-GPIO drivers ..."))
for (_, driver) in tools.sorted_kvs(self.__drivers):
driver.prepare()
async def systask(self) -> None:
get_logger(0).info("Running User-GPIO drivers ...")
get_logger(0).info(self.gettext("Running User-GPIO drivers ..."))
await asyncio.gather(*[
driver.run()
for (_, driver) in tools.sorted_kvs(self.__drivers)
@@ -305,7 +311,7 @@ class UserGpio:
try:
await driver.cleanup()
except Exception:
get_logger().exception("Can't cleanup driver %s", driver)
get_logger().exception(self.gettext("Can't cleanup driver %s"), driver)
async def switch(self, channel: str, state: bool, wait: bool) -> None:
gout = self.__outputs.get(channel)

View File

@@ -27,7 +27,9 @@ import json
import time
import argparse
from os.path import join # pylint: disable=ungrouped-imports
from os.path import join
from ...lanuages import Lanuages
from ...logging import get_logger
@@ -201,13 +203,14 @@ def _cmd_start(config: Section) -> None: # pylint: disable=too-many-statements,
# https://www.isticktoit.net/?p=1383
logger = get_logger()
gettext=Lanuages().gettext
_check_config(config)
udc = usb.find_udc(config.otg.udc)
logger.info("Using UDC %s", udc)
logger.info(gettext("Using UDC %s"), udc)
logger.info("Creating gadget %r ...", config.otg.gadget)
logger.info(gettext("Creating gadget %r ..."), config.otg.gadget)
gadget_path = usb.get_gadget_path(config.otg.gadget)
_mkdir(gadget_path)
@@ -248,39 +251,39 @@ def _cmd_start(config: Section) -> None: # pylint: disable=too-many-statements,
cod = config.otg.devices
if cod.serial.enabled:
logger.info("===== Serial =====")
logger.info(gettext("===== Serial ====="))
gc.add_serial(cod.serial.start)
if cod.ethernet.enabled:
logger.info("===== Ethernet =====")
logger.info(gettext("===== Ethernet ====="))
gc.add_ethernet(**cod.ethernet._unpack(ignore=["enabled"]))
if config.kvmd.hid.type == "otg":
logger.info("===== HID-Keyboard =====")
logger.info(gettext("===== HID-Keyboard ====="))
gc.add_keyboard(cod.hid.keyboard.start, config.otg.remote_wakeup)
logger.info("===== HID-Mouse =====")
logger.info(gettext("===== HID-Mouse ====="))
gc.add_mouse(cod.hid.mouse.start, config.otg.remote_wakeup, config.kvmd.hid.mouse.absolute, config.kvmd.hid.mouse.horizontal_wheel)
if config.kvmd.hid.mouse_alt.device:
logger.info("===== HID-Mouse-Alt =====")
logger.info(gettext("===== HID-Mouse-Alt ====="))
gc.add_mouse(cod.hid.mouse.start, config.otg.remote_wakeup, (not config.kvmd.hid.mouse.absolute), config.kvmd.hid.mouse.horizontal_wheel)
if config.kvmd.msd.type == "otg":
logger.info("===== MSD =====")
logger.info(gettext("===== MSD ====="))
gc.add_msd(cod.msd.start, config.otg.user, **cod.msd.default._unpack())
if cod.drives.enabled:
for count in range(cod.drives.count):
logger.info("===== MSD Extra: %d =====", count + 1)
logger.info(gettext("===== MSD Extra: %d ====="), count + 1)
gc.add_msd(cod.drives.start, "root", **cod.drives.default._unpack())
logger.info("===== Preparing complete =====")
logger.info(gettext("===== Preparing complete ====="))
logger.info("Enabling the gadget ...")
logger.info(gettext("Enabling the gadget ..."))
_write(join(gadget_path, "UDC"), udc)
time.sleep(config.otg.init_delay)
_chown(join(gadget_path, "UDC"), config.otg.user)
_chown(profile_path, config.otg.user)
logger.info("Ready to work")
logger.info(gettext("Ready to work"))
# =====
@@ -293,7 +296,7 @@ def _cmd_stop(config: Section) -> None:
gadget_path = usb.get_gadget_path(config.otg.gadget)
logger.info("Disabling gadget %r ...", config.otg.gadget)
logger.info(Lanuages().gettext("Disabling gadget %r ..."), config.otg.gadget)
_write(join(gadget_path, "UDC"), "\n")
_unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), optional=True)

View File

@@ -26,6 +26,8 @@ import dataclasses
import itertools
import argparse
from ...lanuages import Lanuages
from ...logging import get_logger
from ...yamlconf import Section
@@ -87,6 +89,8 @@ class _Service: # pylint: disable=too-many-instance-attributes
self.__gadget: str = config.otg.gadget
self.__driver: str = config.otg.devices.ethernet.driver
self.gettext=Lanuages().gettext
def start(self) -> None:
asyncio.run(self.__run(True))
@@ -121,20 +125,20 @@ class _Service: # pylint: disable=too-many-instance-attributes
for ctl in ctls:
if not (await self.__run_ctl(ctl, True)):
raise SystemExit(1)
get_logger(0).info("Ready to work")
get_logger(0).info(self.gettext("Ready to work"))
else:
for ctl in reversed(ctls):
await self.__run_ctl(ctl, False)
get_logger(0).info("Bye-bye")
get_logger(0).info(self.gettext("Bye-bye"))
async def __run_ctl(self, ctl: BaseCtl, direct: bool) -> bool:
logger = get_logger()
cmd = ctl.get_command(direct)
logger.info("CMD: %s", tools.cmdfmt(cmd))
logger.info(self.gettext("CMD: %s"), tools.cmdfmt(cmd))
try:
return (not (await aioproc.log_process(cmd, logger)).returncode)
except Exception as err:
logger.exception("Can't execute command: %s", err)
logger.exception(self.gettext("Can't execute command: %s"), err)
return False
# =====
@@ -143,10 +147,10 @@ class _Service: # pylint: disable=too-many-instance-attributes
iface = self.__find_iface()
logger = get_logger()
logger.info("Using IPv4 network %s ...", self.__iface_net)
logger.info(self.gettext("Using IPv4 network %s ..."), self.__iface_net)
net = ipaddress.IPv4Network(self.__iface_net)
if net.prefixlen > 31:
raise RuntimeError("Too small network, required at least /31")
raise RuntimeError(self.gettext("Too small network, required at least /31"))
if net.prefixlen == 31:
iface_ip = str(net[0])
@@ -166,7 +170,7 @@ class _Service: # pylint: disable=too-many-instance-attributes
dhcp_ip_end=dhcp_ip_end,
dhcp_option_3=(f"3,{iface_ip}" if self.__forward_iface else "3"),
)
logger.info("Calculated %r address is %s/%d", iface, iface_ip, netcfg.net_prefix)
logger.info(self.gettext("Calculated %r address is %s/%d"), iface, iface_ip, netcfg.net_prefix)
return netcfg
def __find_iface(self) -> str:
@@ -175,10 +179,10 @@ class _Service: # pylint: disable=too-many-instance-attributes
if self.__driver == "rndis5":
real_driver = "rndis"
path = usb.get_gadget_path(self.__gadget, usb.G_FUNCTIONS, f"{real_driver}.usb0/ifname")
logger.info("Using OTG gadget %r ...", self.__gadget)
logger.info(self.gettext("Using OTG gadget %r ..."), self.__gadget)
with open(path) as file:
iface = file.read().strip()
logger.info("Using OTG Ethernet interface %r ...", iface)
logger.info(self.gettext("Using OTG Ethernet interface %r ..."), iface)
assert iface
return iface