进一步的 kvmd 国际化(汉化)支持,添加配置入口

yaml 配置示例:
```
languages:
    console: zh
    web: zh
```
This commit is contained in:
mofeng-git 2024-08-14 22:54:12 +08:00
parent 5b25b3661f
commit 35397c5414
47 changed files with 567 additions and 181 deletions

View File

@ -22,7 +22,7 @@
import subprocess import subprocess
from .lanuages import Lanuages from .languages import Languages
from .logging import get_logger from .logging import get_logger
@ -38,13 +38,13 @@ async def remount(name: str, base_cmd: list[str], rw: bool) -> bool:
part.format(mode=mode) part.format(mode=mode)
for part in base_cmd for part in base_cmd
] ]
logger.info(Lanuages().gettext("Remounting %s storage to %s: %s ..."), name, mode.upper(), tools.cmdfmt(cmd)) logger.info(Languages().gettext("Remounting %s storage to %s: %s ..."), name, mode.upper(), tools.cmdfmt(cmd))
try: try:
proc = await aioproc.log_process(cmd, logger) proc = await aioproc.log_process(cmd, logger)
if proc.returncode != 0: if proc.returncode != 0:
assert proc.returncode is not None assert proc.returncode is not None
raise subprocess.CalledProcessError(proc.returncode, cmd) raise subprocess.CalledProcessError(proc.returncode, cmd)
except Exception as err: except Exception as err:
logger.error(Lanuages().gettext("Can't remount %s storage: %s"), name, tools.efmt(err)) logger.error(Languages().gettext("Can't remount %s storage: %s"), name, tools.efmt(err))
return False return False
return True return True

View File

@ -26,7 +26,7 @@ import asyncio
import asyncio.subprocess import asyncio.subprocess
import logging import logging
from .lanuages import Lanuages from .languages import Languages
import setproctitle import setproctitle
from .logging import get_logger from .logging import get_logger
@ -86,7 +86,7 @@ async def log_stdout_infinite(proc: asyncio.subprocess.Process, logger: logging.
else: else:
empty += 1 empty += 1
if empty == 100: # asyncio bug if empty == 100: # asyncio bug
raise RuntimeError(Lanuages().gettext("Asyncio process: too many empty lines")) raise RuntimeError(Languages().gettext("Asyncio process: too many empty lines"))
async def kill_process(proc: asyncio.subprocess.Process, wait: float, logger: logging.Logger) -> None: # pylint: disable=no-member async def kill_process(proc: asyncio.subprocess.Process, wait: float, logger: logging.Logger) -> None: # pylint: disable=no-member
@ -101,14 +101,14 @@ async def kill_process(proc: asyncio.subprocess.Process, wait: float, logger: lo
if proc.returncode is not None: if proc.returncode is not None:
raise raise
await proc.wait() await proc.wait()
logger.info(Lanuages().gettext("Process killed: retcode=%d"), proc.returncode) logger.info(Languages().gettext("Process killed: retcode=%d"), proc.returncode)
except asyncio.CancelledError: except asyncio.CancelledError:
pass pass
except Exception: except Exception:
if proc.returncode is None: if proc.returncode is None:
logger.exception(Lanuages().gettext("Can't kill process pid=%d"), proc.pid) logger.exception(Languages().gettext("Can't kill process pid=%d"), proc.pid)
else: else:
logger.info(Lanuages().gettext("Process killed: retcode=%d"), proc.returncode) logger.info(Languages().gettext("Process killed: retcode=%d"), proc.returncode)
def rename_process(suffix: str, prefix: str="kvmd") -> None: def rename_process(suffix: str, prefix: str="kvmd") -> None:
@ -117,7 +117,7 @@ def rename_process(suffix: str, prefix: str="kvmd") -> None:
def settle(name: str, suffix: str, prefix: str="kvmd") -> logging.Logger: def settle(name: str, suffix: str, prefix: str="kvmd") -> logging.Logger:
logger = get_logger(1) logger = get_logger(1)
logger.info(Lanuages().gettext("Started %s pid=%d"), name, os.getpid()) logger.info(Languages().gettext("Started %s pid=%d"), name, os.getpid())
os.setpgrp() os.setpgrp()
rename_process(suffix, prefix) rename_process(suffix, prefix)
return logger return logger

View File

@ -105,7 +105,9 @@ from ..validators.hw import valid_otg_gadget
from ..validators.hw import valid_otg_id from ..validators.hw import valid_otg_id
from ..validators.hw import valid_otg_ethernet from ..validators.hw import valid_otg_ethernet
from ..lanuages import Lanuages from ..validators.languages import valid_languages
from ..languages import Languages
# ===== # =====
def init( def init(
@ -127,16 +129,16 @@ def init(
add_help=add_help, add_help=add_help,
formatter_class=argparse.ArgumentDefaultsHelpFormatter, formatter_class=argparse.ArgumentDefaultsHelpFormatter,
) )
_ = translation(domain="message",localedir="/kvmd/i18n",languages=["zh"]).gettext
parser.add_argument("-c", "--config", default="/etc/kvmd/main.yaml", type=valid_abs_file, parser.add_argument("-c", "--config", default="/etc/kvmd/main.yaml", type=valid_abs_file,
help=_("Set config file path"), metavar="<file>") help="Set config file path", metavar="<file>")
parser.add_argument("-o", "--set-options", default=[], nargs="+", parser.add_argument("-o", "--set-options", default=[], nargs="+",
help=_("Override config options list (like sec/sub/opt=value)"), metavar="<k=v>",) help="Override config options list (like sec/sub/opt=value)", metavar="<k=v>",)
parser.add_argument("-m", "--dump-config", action="store_true", parser.add_argument("-m", "--dump-config", action="store_true",
help=_("View current configuration (include all overrides)")) help="View current configuration (include all overrides)")
if check_run: if check_run:
parser.add_argument("--run", dest="run", action="store_true", parser.add_argument("--run", dest="run", action="store_true",
help=_("Run the service")) help="Run the service")
(options, remaining) = parser.parse_known_args(argv) (options, remaining) = parser.parse_known_args(argv)
if options.dump_config: if options.dump_config:
@ -151,9 +153,18 @@ def init(
)) ))
raise SystemExit() raise SystemExit()
config = _init_config(options.config, options.set_options, **load) config = _init_config(options.config, options.set_options, **load)
logging.captureWarnings(True) logging.captureWarnings(True)
logging.config.dictConfig(config.logging) logging.config.dictConfig(config.logging)
if isinstance(config.get("languages"), dict) and isinstance(config["languages"].get("console"), str):
i18n_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+"/i18n"
Languages.init("message", i18n_path, config["languages"]["console"])
gettext = Languages().gettext
logging.addLevelName(20, gettext("INFO"))
logging.addLevelName(30, gettext("WARNING"))
logging.addLevelName(40, gettext("ERROR"))
if cli_logging: if cli_logging:
logging.getLogger().handlers[0].setFormatter(logging.Formatter( logging.getLogger().handlers[0].setFormatter(logging.Formatter(
"-- {levelname:>7} -- {message}", "-- {levelname:>7} -- {message}",
@ -162,7 +173,7 @@ def init(
if check_run and not options.run: if check_run and not options.run:
raise SystemExit( raise SystemExit(
_("To prevent accidental startup, you must specify the --run option to start.\n")+_("Try the --help option to find out what this service does.\n")+_("Make sure you understand exactly what you are doing!")) gettext("To prevent accidental startup, you must specify the --run option to start.\n")+gettext("Try the --help option to find out what this service does.\n")+gettext("Make sure you understand exactly what you are doing!"))
return (parser, remaining, config) return (parser, remaining, config)
@ -787,4 +798,9 @@ def _get_config_scheme() -> dict:
"timeout": Option(300, type=valid_int_f1), "timeout": Option(300, type=valid_int_f1),
"interval": Option(30, type=valid_int_f1), "interval": Option(30, type=valid_int_f1),
}, },
"languages": {
"console": Option("default", type=valid_languages),
"web": Option("default", type=valid_languages),
},
} }

View File

@ -26,7 +26,7 @@ from ...plugins.hid import get_hid_class
from ...plugins.atx import get_atx_class from ...plugins.atx import get_atx_class
from ...plugins.msd import get_msd_class from ...plugins.msd import get_msd_class
from ...lanuages import Lanuages from ...languages import Languages
from .. import init from .. import init
@ -112,4 +112,4 @@ def main(argv: (list[str] | None)=None) -> None:
stream_forever=config.streamer.forever, stream_forever=config.streamer.forever,
).run(**config.server._unpack()) ).run(**config.server._unpack())
get_logger(0).info(Lanuages().gettext("Bye-bye")) get_logger(0).info(Languages().gettext("Bye-bye"))

View File

@ -34,7 +34,7 @@ from ...plugins.auth import get_auth_service_class
from ...htserver import HttpExposed from ...htserver import HttpExposed
from ...lanuages import Lanuages from ...languages import Languages
# ===== # =====
class AuthManager: class AuthManager:
@ -52,7 +52,7 @@ class AuthManager:
totp_secret_path: str, totp_secret_path: str,
) -> None: ) -> None:
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
self.__enabled = enabled self.__enabled = enabled
if not enabled: if not enabled:
get_logger().warning(self.gettext("AUTHORIZATION IS DISABLED")) get_logger().warning(self.gettext("AUTHORIZATION IS DISABLED"))

View File

@ -33,6 +33,8 @@ from aiohttp.web import Request
from aiohttp.web import Response from aiohttp.web import Response
from aiohttp.web import WebSocketResponse from aiohttp.web import WebSocketResponse
from ...languages import Languages
from ...logging import get_logger from ...logging import get_logger
from ...errors import OperationError from ...errors import OperationError
@ -84,17 +86,17 @@ from .api.redfish import RedfishApi
# ===== # =====
class StreamerQualityNotSupported(OperationError): class StreamerQualityNotSupported(OperationError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("This streamer does not support quality settings") super().__init__(Languages().gettext("This streamer does not support quality settings"))
class StreamerResolutionNotSupported(OperationError): class StreamerResolutionNotSupported(OperationError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("This streamer does not support resolution settings") super().__init__(Languages().gettext("This streamer does not support resolution settings"))
class StreamerH264NotSupported(OperationError): class StreamerH264NotSupported(OperationError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("This streamer does not support H264") super().__init__(Languages().gettext("This streamer does not support H264"))
# ===== # =====
@ -210,6 +212,8 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
self.__reset_streamer = False self.__reset_streamer = False
self.__new_streamer_params: dict = {} self.__new_streamer_params: dict = {}
self.gettext=Languages().gettext
# ===== STREAMER CONTROLLER # ===== STREAMER CONTROLLER
@exposed_http("POST", "/streamer/set_params") @exposed_http("POST", "/streamer/set_params")
@ -291,24 +295,24 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
async def _on_shutdown(self) -> None: async def _on_shutdown(self) -> None:
logger = get_logger(0) logger = get_logger(0)
logger.info("Waiting short tasks ...") logger.info(self.gettext("Waiting short tasks ..."))
await aiotools.wait_all_short_tasks() await aiotools.wait_all_short_tasks()
logger.info("Stopping system tasks ...") logger.info(self.gettext("Stopping system tasks ..."))
await aiotools.stop_all_deadly_tasks() await aiotools.stop_all_deadly_tasks()
logger.info("Disconnecting clients ...") logger.info(self.gettext("Disconnecting clients ..."))
await self._close_all_wss() await self._close_all_wss()
logger.info("On-Shutdown complete") logger.info(self.gettext("On-Shutdown complete"))
async def _on_cleanup(self) -> None: async def _on_cleanup(self) -> None:
logger = get_logger(0) logger = get_logger(0)
for sub in self.__subsystems: for sub in self.__subsystems:
if sub.cleanup: if sub.cleanup:
logger.info("Cleaning up %s ...", sub.name) logger.info(self.gettext("Cleaning up %s ..."), sub.name)
try: try:
await sub.cleanup() # type: ignore await sub.cleanup() # type: ignore
except Exception: except Exception:
logger.exception("Cleanup error on %s", sub.name) logger.exception(self.gettext("Cleanup error on %s"), sub.name)
logger.info("On-Cleanup complete") logger.info(self.gettext("On-Cleanup complete"))
async def _on_ws_opened(self) -> None: async def _on_ws_opened(self) -> None:
self.__streamer_notifier.notify() self.__streamer_notifier.notify()

View File

@ -34,7 +34,7 @@ import aiohttp
from PIL import Image as PilImage from PIL import Image as PilImage
from ...lanuages import Lanuages from ...languages import Languages
from ...logging import get_logger from ...logging import get_logger
@ -228,7 +228,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
self.__notifier = aiotools.AioNotifier() self.__notifier = aiotools.AioNotifier()
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
# ===== # =====

View File

@ -26,7 +26,7 @@ from typing import AsyncGenerator
from typing import Callable from typing import Callable
from typing import Any from typing import Any
from ...lanuages import Lanuages from ...languages import Languages
from ...logging import get_logger from ...logging import get_logger
@ -48,22 +48,22 @@ from ...yamlconf import Section
# ===== # =====
class GpioChannelNotFoundError(GpioOperationError): class GpioChannelNotFoundError(GpioOperationError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(Lanuages().gettext("GPIO channel is not found")) super().__init__(Languages().gettext("GPIO channel is not found"))
class GpioSwitchNotSupported(GpioOperationError): class GpioSwitchNotSupported(GpioOperationError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(Lanuages().gettext("This GPIO channel does not support switching")) super().__init__(Languages().gettext("This GPIO channel does not support switching"))
class GpioPulseNotSupported(GpioOperationError): class GpioPulseNotSupported(GpioOperationError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(Lanuages().gettext("This GPIO channel does not support pulsing")) super().__init__(Languages().gettext("This GPIO channel does not support pulsing"))
class GpioChannelIsBusyError(IsBusyError, GpioError): class GpioChannelIsBusyError(IsBusyError, GpioError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(Lanuages().gettext("Performing another GPIO operation on this channel, please try again later")) super().__init__(Languages().gettext("Performing another GPIO operation on this channel, please try again later"))
# ===== # =====
@ -82,7 +82,7 @@ class _GpioInput:
self.__driver = driver self.__driver = driver
self.__driver.register_input(self.__pin, config.debounce) self.__driver.register_input(self.__pin, config.debounce)
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
def get_scheme(self) -> dict: def get_scheme(self) -> dict:
return { return {
@ -253,7 +253,7 @@ class UserGpio:
self.__inputs: dict[str, _GpioInput] = {} self.__inputs: dict[str, _GpioInput] = {}
self.__outputs: dict[str, _GpioOutput] = {} self.__outputs: dict[str, _GpioOutput] = {}
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
for (channel, ch_config) in tools.sorted_kvs(config.scheme): for (channel, ch_config) in tools.sorted_kvs(config.scheme):
driver = self.__drivers[ch_config.driver] driver = self.__drivers[ch_config.driver]

View File

@ -29,7 +29,7 @@ import argparse
from os.path import join from os.path import join
from ...lanuages import Lanuages from ...languages import Languages
from ...logging import get_logger from ...logging import get_logger
@ -203,7 +203,7 @@ def _cmd_start(config: Section) -> None: # pylint: disable=too-many-statements,
# https://www.isticktoit.net/?p=1383 # https://www.isticktoit.net/?p=1383
logger = get_logger() logger = get_logger()
gettext=Lanuages().gettext gettext=Languages().gettext
_check_config(config) _check_config(config)
@ -296,7 +296,7 @@ def _cmd_stop(config: Section) -> None:
gadget_path = usb.get_gadget_path(config.otg.gadget) gadget_path = usb.get_gadget_path(config.otg.gadget)
logger.info(Lanuages().gettext("Disabling gadget %r ..."), config.otg.gadget) logger.info(Languages().gettext("Disabling gadget %r ..."), config.otg.gadget)
_write(join(gadget_path, "UDC"), "\n") _write(join(gadget_path, "UDC"), "\n")
_unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), optional=True) _unlink(join(gadget_path, "os_desc", usb.G_PROFILE_NAME), optional=True)

View File

@ -26,7 +26,7 @@ import dataclasses
import itertools import itertools
import argparse import argparse
from ...lanuages import Lanuages from ...languages import Languages
from ...logging import get_logger from ...logging import get_logger
@ -89,7 +89,7 @@ class _Service: # pylint: disable=too-many-instance-attributes
self.__gadget: str = config.otg.gadget self.__gadget: str = config.otg.gadget
self.__driver: str = config.otg.devices.ethernet.driver self.__driver: str = config.otg.devices.ethernet.driver
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
def start(self) -> None: def start(self) -> None:
asyncio.run(self.__run(True)) asyncio.run(self.__run(True))

View File

@ -28,6 +28,8 @@ import contextlib
import aiohttp import aiohttp
from ...languages import Languages
from ...logging import get_logger from ...logging import get_logger
from ...keyboard.keysym import SymmapModifiers from ...keyboard.keysym import SymmapModifiers
@ -133,6 +135,8 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
self.__modifiers = 0 self.__modifiers = 0
self.gettext=Languages().gettext
# ===== # =====
async def run(self) -> None: async def run(self) -> None:
@ -156,13 +160,13 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
logger = get_logger(0) logger = get_logger(0)
await self.__stage1_authorized.wait_passed() await self.__stage1_authorized.wait_passed()
logger.info("%s [kvmd]: Waiting for the SetEncodings message ...", self._remote) logger.info(self.gettext("%s [kvmd]: Waiting for the SetEncodings message ..."), self._remote)
if not (await self.__stage2_encodings_accepted.wait_passed(timeout=5)): if not (await self.__stage2_encodings_accepted.wait_passed(timeout=5)):
raise RfbError("No SetEncodings message recieved from the client in 5 secs") raise RfbError(self.gettext("No SetEncodings message recieved from the client in 5 secs"))
assert self.__kvmd_session assert self.__kvmd_session
try: try:
logger.info("%s [kvmd]: Applying HID params: mouse_output=%s ...", self._remote, self.__mouse_output) logger.info(self.gettext("%s [kvmd]: Applying HID params: mouse_output=%s ..."), self._remote, self.__mouse_output)
await self.__kvmd_session.hid.set_params(mouse_output=self.__mouse_output) await self.__kvmd_session.hid.set_params(mouse_output=self.__mouse_output)
async with self.__kvmd_session.ws() as self.__kvmd_ws: async with self.__kvmd_session.ws() as self.__kvmd_ws:
@ -170,7 +174,7 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
self.__stage3_ws_connected.set_passed() self.__stage3_ws_connected.set_passed()
async for (event_type, event) in self.__kvmd_ws.communicate(): async for (event_type, event) in self.__kvmd_ws.communicate():
await self.__process_ws_event(event_type, event) await self.__process_ws_event(event_type, event)
raise RfbError("KVMD closed the websocket (the server may have been stopped)") raise RfbError(self.gettext("KVMD closed the websocket (the server may have been stopped)"))
finally: finally:
self.__kvmd_ws = None self.__kvmd_ws = None
@ -204,19 +208,19 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
while True: while True:
frame = await read_frame(not self.__fb_has_key) frame = await read_frame(not self.__fb_has_key)
if not streaming: if not streaming:
logger.info("%s [streamer]: Streaming ...", self._remote) logger.info(self.gettext("%s [streamer]: Streaming ..."), self._remote)
streaming = True streaming = True
if frame["online"]: if frame["online"]:
await self.__queue_frame(frame) await self.__queue_frame(frame)
else: else:
await self.__queue_frame("No signal") await self.__queue_frame(self.gettext("No signal"))
except StreamerError as err: except StreamerError as err:
if isinstance(err, StreamerPermError): if isinstance(err, StreamerPermError):
streamer = self.__get_default_streamer() streamer = self.__get_default_streamer()
logger.info("%s [streamer]: Permanent error: %s; switching to %s ...", self._remote, err, streamer) logger.info(self.gettext("%s [streamer]: Permanent error: %s; switching to %s ..."), self._remote, err, streamer)
else: else:
logger.info("%s [streamer]: Waiting for stream: %s", self._remote, err) logger.info(self.gettext("%s [streamer]: Waiting for stream: %s"), self._remote, err)
await self.__queue_frame("Waiting for stream ...") await self.__queue_frame(self.gettext("Waiting for stream ..."))
await asyncio.sleep(1) await asyncio.sleep(1)
def __get_preferred_streamer(self) -> BaseStreamerClient: def __get_preferred_streamer(self) -> BaseStreamerClient:
@ -227,13 +231,13 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
streamer: (BaseStreamerClient | None) = None streamer: (BaseStreamerClient | None) = None
for streamer in self.__streamers: for streamer in self.__streamers:
if getattr(self._encodings, formats[streamer.get_format()]): if getattr(self._encodings, formats[streamer.get_format()]):
get_logger(0).info("%s [streamer]: Using preferred %s", self._remote, streamer) get_logger(0).info(self.gettext("%s [streamer]: Using preferred %s"), self._remote, streamer)
return streamer return streamer
raise RuntimeError("No streamers found") raise RuntimeError("No streamers found")
def __get_default_streamer(self) -> BaseStreamerClient: def __get_default_streamer(self) -> BaseStreamerClient:
streamer = self.__streamers[-1] streamer = self.__streamers[-1]
get_logger(0).info("%s [streamer]: Using default %s", self._remote, streamer) get_logger(0).info(self.gettext("%s [streamer]: Using default %s"), self._remote, streamer)
return streamer return streamer
async def __queue_frame(self, frame: (dict | str)) -> None: async def __queue_frame(self, frame: (dict | str)) -> None:
@ -298,13 +302,13 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
await self._send_fb_jpeg(last["data"]) await self._send_fb_jpeg(last["data"])
elif last["format"] == StreamFormats.H264: elif last["format"] == StreamFormats.H264:
if not self._encodings.has_h264: if not self._encodings.has_h264:
raise RfbError("The client doesn't want to accept H264 anymore") raise RfbError(self.gettext("The client doesn't want to accept H264 anymore"))
if self.__fb_has_key: if self.__fb_has_key:
await self._send_fb_h264(last["data"]) await self._send_fb_h264(last["data"])
else: else:
await self._send_fb_allow_again() await self._send_fb_allow_again()
else: else:
raise RuntimeError(f"Unknown format: {last['format']}") raise RuntimeError(self.gettext(f"Unknown format: {last['format']}"))
last["data"] = b"" last["data"] = b""
# ===== # =====
@ -410,7 +414,7 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
has_quality = (await self.__kvmd_session.streamer.get_state())["features"]["quality"] has_quality = (await self.__kvmd_session.streamer.get_state())["features"]["quality"]
quality = (self._encodings.tight_jpeg_quality if has_quality else None) quality = (self._encodings.tight_jpeg_quality if has_quality else None)
get_logger(0).info("%s [main]: Applying streamer params: jpeg_quality=%s; desired_fps=%d ...", get_logger(0).info(self.gettext("%s [main]: Applying streamer params: jpeg_quality=%s; desired_fps=%d ..."),
self._remote, quality, self.__desired_fps) self._remote, quality, self.__desired_fps)
await self.__kvmd_session.streamer.set_params(quality, self.__desired_fps) await self.__kvmd_session.streamer.set_params(quality, self.__desired_fps)
@ -456,14 +460,16 @@ class VncServer: # pylint: disable=too-many-instance-attributes
shared_params = _SharedParams() shared_params = _SharedParams()
self.gettext=Languages().gettext
async def cleanup_client(writer: asyncio.StreamWriter) -> None: async def cleanup_client(writer: asyncio.StreamWriter) -> None:
if (await aiotools.close_writer(writer)): if (await aiotools.close_writer(writer)):
get_logger(0).info("%s [entry]: Connection is closed in an emergency", rfb_format_remote(writer)) get_logger(0).info(self.gettext("%s [entry]: Connection is closed in an emergency"), rfb_format_remote(writer))
async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
logger = get_logger(0) logger = get_logger(0)
remote = rfb_format_remote(writer) remote = rfb_format_remote(writer)
logger.info("%s [entry]: Connected client", remote) logger.info(self.gettext("%s [entry]: Connected client"), remote)
try: try:
sock = writer.get_extra_info("socket") sock = writer.get_extra_info("socket")
if no_delay: if no_delay:
@ -482,7 +488,7 @@ class VncServer: # pylint: disable=too-many-instance-attributes
async with kvmd.make_session("", "") as kvmd_session: async with kvmd.make_session("", "") as kvmd_session:
none_auth_only = await kvmd_session.auth.check() none_auth_only = await kvmd_session.auth.check()
except (aiohttp.ClientError, asyncio.TimeoutError) as err: except (aiohttp.ClientError, asyncio.TimeoutError) as err:
logger.error("%s [entry]: Can't check KVMD auth mode: %s", remote, tools.efmt(err)) logger.error(self.gettext("%s [entry]: Can't check KVMD auth mode: %s"), remote, tools.efmt(err))
return return
await _Client( await _Client(
@ -504,7 +510,7 @@ class VncServer: # pylint: disable=too-many-instance-attributes
shared_params=shared_params, shared_params=shared_params,
).run() ).run()
except Exception: except Exception:
logger.exception("%s [entry]: Unhandled exception in client task", remote) logger.exception(self.gettext("%s [entry]: Unhandled exception in client task"), remote)
finally: finally:
await aiotools.shield_fg(cleanup_client(writer)) await aiotools.shield_fg(cleanup_client(writer))
@ -514,7 +520,7 @@ class VncServer: # pylint: disable=too-many-instance-attributes
if not (await self.__vnc_auth_manager.read_credentials())[1]: if not (await self.__vnc_auth_manager.read_credentials())[1]:
raise SystemExit(1) raise SystemExit(1)
get_logger(0).info("Listening VNC on TCP [%s]:%d ...", self.__host, self.__port) get_logger(0).info(self.gettext("Listening VNC on TCP [%s]:%d ..."), self.__host, self.__port)
(family, _, _, _, addr) = socket.getaddrinfo(self.__host, self.__port, type=socket.SOCK_STREAM)[0] (family, _, _, _, addr) = socket.getaddrinfo(self.__host, self.__port, type=socket.SOCK_STREAM)[0]
with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as sock: with contextlib.closing(socket.socket(family, socket.SOCK_STREAM)) as sock:
if family == socket.AF_INET6: if family == socket.AF_INET6:
@ -532,4 +538,4 @@ class VncServer: # pylint: disable=too-many-instance-attributes
def run(self) -> None: def run(self) -> None:
aiotools.run(self.__inner_run()) aiotools.run(self.__inner_run())
get_logger().info("Bye-bye") get_logger().info(self.gettext("Bye-bye"))

View File

@ -22,6 +22,8 @@
import dataclasses import dataclasses
from ...languages import Languages
from ...logging import get_logger from ...logging import get_logger
from ... import aiotools from ... import aiotools
@ -30,7 +32,7 @@ from ... import aiotools
# ===== # =====
class VncAuthError(Exception): class VncAuthError(Exception):
def __init__(self, path: str, lineno: int, msg: str) -> None: def __init__(self, path: str, lineno: int, msg: str) -> None:
super().__init__(f"Syntax error at {path}:{lineno}: {msg}") super().__init__(Languages().gettext(f"Syntax error at {path}:{lineno}: {msg}"))
# ===== # =====
@ -49,6 +51,7 @@ class VncAuthManager:
self.__path = path self.__path = path
self.__enabled = enabled self.__enabled = enabled
self.gettext=Languages().gettext
async def read_credentials(self) -> tuple[dict[str, VncAuthKvmdCredentials], bool]: async def read_credentials(self) -> tuple[dict[str, VncAuthKvmdCredentials], bool]:
if self.__enabled: if self.__enabled:
@ -57,7 +60,7 @@ class VncAuthManager:
except VncAuthError as err: except VncAuthError as err:
get_logger(0).error(str(err)) get_logger(0).error(str(err))
except Exception: except Exception:
get_logger(0).exception("Unhandled exception while reading VNCAuth passwd file") get_logger(0).exception(self.gettext("Unhandled exception while reading VNCAuth passwd file"))
return ({}, (not self.__enabled)) return ({}, (not self.__enabled))
async def __inner_read_credentials(self) -> dict[str, VncAuthKvmdCredentials]: async def __inner_read_credentials(self) -> dict[str, VncAuthKvmdCredentials]:
@ -68,19 +71,19 @@ class VncAuthManager:
continue continue
if " -> " not in line: if " -> " not in line:
raise VncAuthError(self.__path, lineno, "Missing ' -> ' operator") raise VncAuthError(self.__path, lineno, self.gettext("Missing ' -> ' operator"))
(vnc_passwd, kvmd_userpass) = map(str.lstrip, line.split(" -> ", 1)) (vnc_passwd, kvmd_userpass) = map(str.lstrip, line.split(" -> ", 1))
if ":" not in kvmd_userpass: if ":" not in kvmd_userpass:
raise VncAuthError(self.__path, lineno, "Missing ':' operator in KVMD credentials (right part)") raise VncAuthError(self.__path, lineno, self.gettext("Missing ':' operator in KVMD credentials (right part)"))
(kvmd_user, kvmd_passwd) = kvmd_userpass.split(":") (kvmd_user, kvmd_passwd) = kvmd_userpass.split(":")
kvmd_user = kvmd_user.strip() kvmd_user = kvmd_user.strip()
if len(kvmd_user) == 0: if len(kvmd_user) == 0:
raise VncAuthError(self.__path, lineno, "Empty KVMD user (right part)") raise VncAuthError(self.__path, lineno, self.gettext("Empty KVMD user (right part)"))
if vnc_passwd in credentials: if vnc_passwd in credentials:
raise VncAuthError(self.__path, lineno, "Duplicating VNC password (left part)") raise VncAuthError(self.__path, lineno, self.gettext("Duplicating VNC password (left part)"))
credentials[vnc_passwd] = VncAuthKvmdCredentials(kvmd_user, kvmd_passwd) credentials[vnc_passwd] = VncAuthKvmdCredentials(kvmd_user, kvmd_passwd)
return credentials return credentials

View File

@ -28,7 +28,7 @@ from typing import AsyncGenerator
import aiohttp import aiohttp
import aiohttp.multipart import aiohttp.multipart
from .lanuages import Lanuages from .languages import Languages
from . import __version__ from . import __version__
@ -60,7 +60,7 @@ def get_filename(response: aiohttp.ClientResponse) -> str:
try: try:
return os.path.basename(response.url.path) return os.path.basename(response.url.path)
except Exception: except Exception:
raise aiohttp.ClientError(Lanuages().gettext("Can't determine filename")) raise aiohttp.ClientError(Languages().gettext("Can't determine filename"))
@contextlib.asynccontextmanager @contextlib.asynccontextmanager

View File

@ -52,7 +52,7 @@ from .errors import IsBusyError
from .validators import ValidatorError from .validators import ValidatorError
from .lanuages import Lanuages from .languages import Languages
from . import aiotools from . import aiotools
@ -282,7 +282,7 @@ class HttpServer:
self.__ws_bin_handlers: dict[int, Callable] = {} self.__ws_bin_handlers: dict[int, Callable] = {}
self.__ws_sessions: list[WsSession] = [] self.__ws_sessions: list[WsSession] = []
self.__ws_sessions_lock = asyncio.Lock() self.__ws_sessions_lock = asyncio.Lock()
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
def run( def run(
self, self,

Binary file not shown.

View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-08-12 22:07+0800\n" "POT-Creation-Date: 2024-08-14 22:40+0800\n"
"PO-Revision-Date: 2024-08-12 22:07+0800\n" "PO-Revision-Date: 2024-08-14 22:40+0800\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language: zh\n" "Language: zh\n"
"Language-Team: zh <LL@li.org>\n" "Language-Team: zh <LL@li.org>\n"
@ -90,37 +90,34 @@ msgstr "因收到忽略标识而取消监视 %s"
msgid "Can't find any UDC" msgid "Can't find any UDC"
msgstr "未找到任何 UDC" msgstr "未找到任何 UDC"
#: kvmd/apps/__init__.py:132 #: kvmd/apps/__init__.py:164
msgid "Set config file path" msgid "INFO"
msgstr "设置配置文件路径" msgstr "消息"
#: kvmd/apps/__init__.py:134
msgid "Override config options list (like sec/sub/opt=value)"
msgstr "覆盖配置文件选项列表(如 sec/sub/opt=value"
#: kvmd/apps/__init__.py:136
msgid "View current configuration (include all overrides)"
msgstr "查看当前配置(包括所有覆盖配置文件)"
#: kvmd/apps/__init__.py:139
msgid "Run the service"
msgstr "启动此服务"
#: kvmd/apps/__init__.py:165 #: kvmd/apps/__init__.py:165
msgid "WARNING"
msgstr "警告"
#: kvmd/apps/__init__.py:166
msgid "ERROR"
msgstr "错误"
#: kvmd/apps/__init__.py:176
msgid "" msgid ""
"To prevent accidental startup, you must specify the --run option to " "To prevent accidental startup, you must specify the --run option to "
"start.\n" "start.\n"
msgstr "为了防止意外启动,必须在启动时指定 --run 选项。\n" msgstr "为了防止意外启动,必须在启动时指定 --run 选项。\n"
#: kvmd/apps/__init__.py:165 #: kvmd/apps/__init__.py:176
msgid "Try the --help option to find out what this service does.\n" msgid "Try the --help option to find out what this service does.\n"
msgstr "尝试使用 --help 选项来了解某项服务的功能。\n" msgstr "尝试使用 --help 选项来了解某项服务的功能。\n"
#: kvmd/apps/__init__.py:165 #: kvmd/apps/__init__.py:176
msgid "Make sure you understand exactly what you are doing!" msgid "Make sure you understand exactly what you are doing!"
msgstr "请确定你自己在做什么!" msgstr "请确定你自己在做什么!"
#: kvmd/apps/kvmd/__init__.py:115 kvmd/apps/otgnet/__init__.py:132 #: kvmd/apps/kvmd/__init__.py:115 kvmd/apps/otgnet/__init__.py:132
#: kvmd/apps/vnc/server.py:541
msgid "Bye-bye" msgid "Bye-bye"
msgstr "再见" msgstr "再见"
@ -172,6 +169,48 @@ msgstr "无法生成新的唯一令牌"
msgid "Logged out user %r (%d)" msgid "Logged out user %r (%d)"
msgstr "已注销用户 %r (%d)" msgstr "已注销用户 %r (%d)"
#: kvmd/apps/kvmd/server.py:89
msgid "This streamer does not support quality settings"
msgstr "该 streamer 不支持质量设置"
#: kvmd/apps/kvmd/server.py:94
msgid "This streamer does not support resolution settings"
msgstr "该 streamer 不支持分辨率设置"
#: kvmd/apps/kvmd/server.py:99
msgid "This streamer does not support H264"
msgstr "该 streamer 不支持 H264 设置"
#: kvmd/apps/kvmd/server.py:298
msgid "Waiting short tasks ..."
msgstr "正在等待短时任务结束......"
#: kvmd/apps/kvmd/server.py:300
msgid "Stopping system tasks ..."
msgstr "正在停止系统任务 ......"
#: kvmd/apps/kvmd/server.py:302
msgid "Disconnecting clients ..."
msgstr "断开客户端连接 ......"
#: kvmd/apps/kvmd/server.py:304
msgid "On-Shutdown complete"
msgstr "全部服务关闭完成"
#: kvmd/apps/kvmd/server.py:310
#, python-format
msgid "Cleaning up %s ..."
msgstr "正在清理 %s ......"
#: kvmd/apps/kvmd/server.py:314
#, python-format
msgid "Cleanup error on %s"
msgstr "在 %s 上发生清理错误"
#: kvmd/apps/kvmd/server.py:315
msgid "On-Cleanup complete"
msgstr "全部清理完毕"
#: kvmd/apps/kvmd/streamer.py:245 #: kvmd/apps/kvmd/streamer.py:245
msgid "Streamer stop cancelled" msgid "Streamer stop cancelled"
msgstr "Streamer 停止已取消" msgstr "Streamer 停止已取消"
@ -375,6 +414,115 @@ msgstr "使用 OTG gadget %r ......"
msgid "Using OTG Ethernet interface %r ..." msgid "Using OTG Ethernet interface %r ..."
msgstr "使用 OTG 以太网接口 %r ......" msgstr "使用 OTG 以太网接口 %r ......"
#: kvmd/apps/vnc/server.py:163
#, python-format
msgid "%s [kvmd]: Waiting for the SetEncodings message ..."
msgstr "%s [kvmd] 等待 SetEncodings 信息 ......"
#: kvmd/apps/vnc/server.py:165
msgid "No SetEncodings message recieved from the client in 5 secs"
msgstr "5 秒内未收到客户端的 SetEncodings 信息"
#: kvmd/apps/vnc/server.py:169
#, python-format
msgid "%s [kvmd]: Applying HID params: mouse_output=%s ..."
msgstr "%s [kvmd] 应用 HID 参数mouse_output=%s ......"
#: kvmd/apps/vnc/server.py:177
msgid "KVMD closed the websocket (the server may have been stopped)"
msgstr "KVMD 关闭了 websocket服务器可能已停止运行"
#: kvmd/apps/vnc/server.py:211
#, python-format
msgid "%s [streamer]: Streaming ..."
msgstr "%s [streamer]:获取视频流中 ......"
#: kvmd/apps/vnc/server.py:216
msgid "No signal"
msgstr "无信号"
#: kvmd/apps/vnc/server.py:220
#, python-format
msgid "%s [streamer]: Permanent error: %s; switching to %s ..."
msgstr "%s [streamer] 持续错误: %s; 切换到 %s ......"
#: kvmd/apps/vnc/server.py:222
#, python-format
msgid "%s [streamer]: Waiting for stream: %s"
msgstr "%s [streamer] 正在等待数据流:%s"
#: kvmd/apps/vnc/server.py:223
msgid "Waiting for stream ..."
msgstr "正在启动 streamer ......"
#: kvmd/apps/vnc/server.py:234
#, python-format
msgid "%s [streamer]: Using preferred %s"
msgstr "%s [streamer] 使用首选 %s"
#: kvmd/apps/vnc/server.py:240
#, python-format
msgid "%s [streamer]: Using default %s"
msgstr "%s [streamer] 使用默认 %s"
#: kvmd/apps/vnc/server.py:305
msgid "The client doesn't want to accept H264 anymore"
msgstr "客户端不接受 H264 视频"
#: kvmd/apps/vnc/server.py:311
msgid "format"
msgstr "格式"
#: kvmd/apps/vnc/server.py:417
#, python-format
msgid "%s [main]: Applying streamer params: jpeg_quality=%s; desired_fps=%d ..."
msgstr "%s [main] 应用流媒体参数: jpeg_quality=%s; desired_fps=%d ..."
#: kvmd/apps/vnc/server.py:467
#, python-format
msgid "%s [entry]: Connection is closed in an emergency"
msgstr "%s [entry] 连接因紧急情况关闭"
#: kvmd/apps/vnc/server.py:472
#, python-format
msgid "%s [entry]: Connected client"
msgstr "%s [entry] 已连接客户端"
#: kvmd/apps/vnc/server.py:491
#, python-format
msgid "%s [entry]: Can't check KVMD auth mode: %s"
msgstr "%s [entry] 无法检查 KVMD 身份验证模式: %s"
#: kvmd/apps/vnc/server.py:513
#, python-format
msgid "%s [entry]: Unhandled exception in client task"
msgstr "%s [entry] 客户端任务中出现无法处理的异常"
#: kvmd/apps/vnc/server.py:523
#, python-format
msgid "Listening VNC on TCP [%s]:%d ..."
msgstr "正在监听 TCP [%s]:%d 上的 VNC 服务 ......"
#: kvmd/apps/vnc/vncauth.py:63
msgid "Unhandled exception while reading VNCAuth passwd file"
msgstr "读取 VNCAuth 密码文件时出现无法处理的异常"
#: kvmd/apps/vnc/vncauth.py:74
msgid "Missing ' -> ' operator"
msgstr "缺少\"->\"运算符"
#: kvmd/apps/vnc/vncauth.py:78
msgid "Missing ':' operator in KVMD credentials (right part)"
msgstr "KVMD 证书中缺少\": \"运算符(右侧部分)"
#: kvmd/apps/vnc/vncauth.py:83
msgid "Empty KVMD user (right part)"
msgstr "空的 KVMD 用户(右侧部分)"
#: kvmd/apps/vnc/vncauth.py:86
msgid "Duplicating VNC password (left part)"
msgstr "复制 VNC 密码(左侧部分)"
#: kvmd/keyboard/keysym.py:69 #: kvmd/keyboard/keysym.py:69
#, python-format #, python-format
msgid "Invalid modifier key at mapping %s: %s / %s" msgid "Invalid modifier key at mapping %s: %s / %s"
@ -668,3 +816,15 @@ msgstr "无法执行重新挂载辅助程序"
msgid "Failed ANELPWR POST request to pin %s: %s" msgid "Failed ANELPWR POST request to pin %s: %s"
msgstr "向引脚 %s 发送 ANELPWR POST 请求失败:%s" msgstr "向引脚 %s 发送 ANELPWR POST 请求失败:%s"
#~ msgid "Set config file path"
#~ msgstr "设置配置文件路径"
#~ msgid "Override config options list (like sec/sub/opt=value)"
#~ msgstr "覆盖配置文件选项列表(如 sec/sub/opt=value"
#~ msgid "View current configuration (include all overrides)"
#~ msgstr "查看当前配置(包括所有覆盖配置文件)"
#~ msgid "Run the service"
#~ msgstr "启动此服务"

View File

@ -34,7 +34,7 @@ from typing import Generator
from .logging import get_logger from .logging import get_logger
from .lanuages import Lanuages from .languages import Languages
from . import aiotools from . import aiotools
from . import libc from . import libc
@ -196,7 +196,7 @@ class Inotify:
for path in paths: for path in paths:
path = os.path.normpath(path) path = os.path.normpath(path)
assert path not in self.__wd_by_path, path assert path not in self.__wd_by_path, path
get_logger().info(Lanuages().gettext("Watching for %s"), path) get_logger().info(Languages().gettext("Watching for %s"), path)
# Асинхронно, чтобы не висло на NFS # Асинхронно, чтобы не висло на NFS
wd = _inotify_check(await aiotools.run_async(libc.inotify_add_watch, self.__fd, _fs_encode(path), mask)) wd = _inotify_check(await aiotools.run_async(libc.inotify_add_watch, self.__fd, _fs_encode(path), mask))
self.__wd_by_path[path] = wd self.__wd_by_path[path] = wd
@ -255,7 +255,7 @@ class Inotify:
if event.mask & InotifyMask.IGNORED: if event.mask & InotifyMask.IGNORED:
ignored_path = self.__path_by_wd[event.wd] ignored_path = self.__path_by_wd[event.wd]
if self.__wd_by_path[ignored_path] == event.wd: if self.__wd_by_path[ignored_path] == event.wd:
logger.info(Lanuages().gettext("Unwatching %s because IGNORED was received"), ignored_path) logger.info(Languages().gettext("Unwatching %s because IGNORED was received"), ignored_path)
del self.__wd_by_path[ignored_path] del self.__wd_by_path[ignored_path]
continue continue

View File

@ -27,7 +27,7 @@ import importlib.machinery
import Xlib.keysymdef import Xlib.keysymdef
from ..lanuages import Lanuages from ..languages import Languages
from ..logging import get_logger from ..logging import get_logger
@ -66,7 +66,7 @@ def build_symmap(path: str) -> dict[int, dict[int, str]]: # x11 keysym -> [(mod
or (web_name in WebModifiers.ALTS and key.altgr) or (web_name in WebModifiers.ALTS and key.altgr)
or (web_name in WebModifiers.CTRLS and key.ctrl) or (web_name in WebModifiers.CTRLS and key.ctrl)
): ):
logger.error(Lanuages().gettext("Invalid modifier key at mapping %s: %s / %s"), src, web_name, key) logger.error(Languages().gettext("Invalid modifier key at mapping %s: %s / %s"), src, web_name, key)
continue continue
modifiers = ( modifiers = (
@ -119,7 +119,7 @@ def _resolve_keysym(name: str) -> int:
def _read_keyboard_layout(path: str) -> dict[int, list[At1Key]]: # Keysym to evdev (at1) def _read_keyboard_layout(path: str) -> dict[int, list[At1Key]]: # Keysym to evdev (at1)
logger = get_logger(0) logger = get_logger(0)
logger.info(Lanuages().gettext("Reading keyboard layout %s ..."), path) logger.info(Languages().gettext("Reading keyboard layout %s ..."), path)
with open(path) as file: with open(path) as file:
lines = list(map(str.strip, file.read().split("\n"))) lines = list(map(str.strip, file.read().split("\n")))

18
kvmd/languages.py Normal file
View File

@ -0,0 +1,18 @@
from gettext import translation
class Languages:
use_ttranslation = None
languages = "default"
@classmethod
def gettext(cls, string: str) -> str:
if cls.languages == "default" or cls.languages == "en" :
return string
else:
return cls.use_ttranslation(string)
@classmethod
def init(cls, domain:str, localedir: str, languages: str) -> None:
cls.languages = languages
cls.use_ttranslation = translation(domain=domain, localedir=localedir, languages=[cls.languages]).gettext

View File

@ -1,7 +0,0 @@
from gettext import translation
class Lanuages:
t = translation(domain="message",localedir="/kvmd/i18n",languages=["zh"]).gettext
def gettext(self,string: str) -> str:
return self.t(string)

View File

@ -25,7 +25,7 @@ from typing import AsyncGenerator
from ...errors import OperationError from ...errors import OperationError
from ...errors import IsBusyError from ...errors import IsBusyError
from ...lanuages import Lanuages from ...languages import Languages
from .. import BasePlugin from .. import BasePlugin
from .. import get_plugin_class from .. import get_plugin_class
@ -42,7 +42,7 @@ class AtxOperationError(OperationError, AtxError):
class AtxIsBusyError(IsBusyError, AtxError): class AtxIsBusyError(IsBusyError, AtxError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(Lanuages().gettext("Performing another ATX operation, please try again later")) super().__init__(Languages().gettext("Performing another ATX operation, please try again later"))
# ===== # =====

View File

@ -39,7 +39,7 @@ from ...validators.basic import valid_float_f01
from ...validators.os import valid_abs_path from ...validators.os import valid_abs_path
from ...validators.hw import valid_gpio_pin from ...validators.hw import valid_gpio_pin
from ...lanuages import Lanuages from ...languages import Languages
from . import AtxIsBusyError from . import AtxIsBusyError
from . import BaseAtx from . import BaseAtx
@ -193,7 +193,7 @@ class Plugin(BaseAtx): # pylint: disable=too-many-instance-attributes
await self.__inner_click(name, pin, delay) await self.__inner_click(name, pin, delay)
else: else:
await aiotools.run_region_task( await aiotools.run_region_task(
Lanuages().gettext(f"Can't perform ATX {name} click or operation was not completed"), Languages().gettext(f"Can't perform ATX {name} click or operation was not completed"),
self.__region, self.__inner_click, name, pin, delay, self.__region, self.__inner_click, name, pin, delay,
) )
@ -206,4 +206,4 @@ class Plugin(BaseAtx): # pylint: disable=too-many-instance-attributes
finally: finally:
self.__line_request.set_value(pin, gpiod.line.Value(False)) self.__line_request.set_value(pin, gpiod.line.Value(False))
await asyncio.sleep(1) await asyncio.sleep(1)
get_logger(0).info(Lanuages().gettext("Clicked ATX button %r"), name) get_logger(0).info(Languages().gettext("Clicked ATX button %r"), name)

View File

@ -32,7 +32,7 @@ from ...logging import get_logger
from ... import htclient from ... import htclient
from ...lanuages import Lanuages from ...languages import Languages
from . import BaseAuthService from . import BaseAuthService
@ -91,7 +91,7 @@ class Plugin(BaseAuthService):
htclient.raise_not_200(response) htclient.raise_not_200(response)
return True return True
except Exception: except Exception:
get_logger().exception(Lanuages().gettext("Failed HTTP auth request for user %r"), user) get_logger().exception(Languages().gettext("Failed HTTP auth request for user %r"), user)
return False return False
async def cleanup(self) -> None: async def cleanup(self) -> None:

View File

@ -33,7 +33,7 @@ from ...logging import get_logger
from ... import tools from ... import tools
from ... import aiotools from ... import aiotools
from ...lanuages import Lanuages from ...languages import Languages
from . import BaseAuthService from . import BaseAuthService
@ -103,9 +103,9 @@ class Plugin(BaseAuthService):
except ldap.INVALID_CREDENTIALS: except ldap.INVALID_CREDENTIALS:
pass pass
except ldap.SERVER_DOWN as err: except ldap.SERVER_DOWN as err:
get_logger().error(Lanuages().gettext("LDAP server is down: %s"), tools.efmt(err)) get_logger().error(Languages().gettext("LDAP server is down: %s"), tools.efmt(err))
except Exception as err: except Exception as err:
get_logger().error(Lanuages().gettext("Unexpected LDAP error: %s"), tools.efmt(err)) get_logger().error(Languages().gettext("Unexpected LDAP error: %s"), tools.efmt(err))
finally: finally:
if conn is not None: if conn is not None:
try: try:

View File

@ -34,7 +34,7 @@ from ...logging import get_logger
from ... import aiotools from ... import aiotools
from ...lanuages import Lanuages from ...languages import Languages
from . import BaseAuthService from . import BaseAuthService
@ -88,13 +88,13 @@ class Plugin(BaseAuthService):
return False return False
else: else:
if uid < self.__allow_uids_at: if uid < self.__allow_uids_at:
get_logger().error(Lanuages().gettext("Unallowed UID of user %r: uid=%d < allow_uids_at=%d"), get_logger().error(Languages().gettext("Unallowed UID of user %r: uid=%d < allow_uids_at=%d"),
user, uid, self.__allow_uids_at) user, uid, self.__allow_uids_at)
return False return False
pam_obj = pam.pam() pam_obj = pam.pam()
if not pam_obj.authenticate(user, passwd, service=self.__service): if not pam_obj.authenticate(user, passwd, service=self.__service):
get_logger().error(Lanuages().gettext("Can't authorize user %r using PAM: code=%d; reason=%s"), get_logger().error(Languages().gettext("Can't authorize user %r using PAM: code=%d; reason=%s"),
user, pam_obj.code, pam_obj.reason) user, pam_obj.code, pam_obj.reason)
return False return False
return True return True

View File

@ -36,7 +36,7 @@ from ...logging import get_logger
from ... import aiotools from ... import aiotools
from ...lanuages import Lanuages from ...languages import Languages
from . import BaseAuthService from . import BaseAuthService
@ -442,5 +442,5 @@ class Plugin(BaseAuthService):
response = client.SendPacket(request) response = client.SendPacket(request)
return (response.code == pyrad.packet.AccessAccept) return (response.code == pyrad.packet.AccessAccept)
except Exception: except Exception:
get_logger().exception(Lanuages().gettext("Failed RADIUS auth request for user %r"), user) get_logger().exception(Languages().gettext("Failed RADIUS auth request for user %r"), user)
return False return False

View File

@ -46,7 +46,7 @@ from ....validators.basic import valid_float_f01
from ....validators.os import valid_abs_path from ....validators.os import valid_abs_path
from ....validators.hw import valid_gpio_pin_optional from ....validators.hw import valid_gpio_pin_optional
from ....lanuages import Lanuages from ....languages import Languages
from .. import BaseHid from .. import BaseHid
@ -146,7 +146,7 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many-
self.__stop_event = multiprocessing.Event() self.__stop_event = multiprocessing.Event()
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
@classmethod @classmethod
def get_plugin_options(cls) -> dict: def get_plugin_options(cls) -> dict:

View File

@ -27,7 +27,7 @@ import gpiod
from ....logging import get_logger from ....logging import get_logger
from ....lanuages import Lanuages from ....languages import Languages
# ===== # =====
class Gpio: # pylint: disable=too-many-instance-attributes class Gpio: # pylint: disable=too-many-instance-attributes
@ -51,7 +51,7 @@ class Gpio: # pylint: disable=too-many-instance-attributes
self.__line_request: (gpiod.LineRequest | None) = None self.__line_request: (gpiod.LineRequest | None) = None
self.__last_power: (bool | None) = None self.__last_power: (bool | None) = None
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
def __enter__(self) -> None: def __enter__(self) -> None:
if self.__power_detect_pin >= 0 or self.__reset_pin >= 0: if self.__power_detect_pin >= 0 or self.__reset_pin >= 0:

View File

@ -40,7 +40,7 @@ from .... import aiotools
from .... import aiomulti from .... import aiomulti
from .... import aioproc from .... import aioproc
from ....lanuages import Lanuages from ....languages import Languages
from .. import BaseHid from .. import BaseHid
@ -109,7 +109,7 @@ class Plugin(BaseHid): # pylint: disable=too-many-instance-attributes
stop_event=self.__stop_event, stop_event=self.__stop_event,
) )
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
@classmethod @classmethod
def get_plugin_options(cls) -> dict: def get_plugin_options(cls) -> dict:

View File

@ -38,7 +38,7 @@ from .... import aiomulti
from ....keyboard.mappings import UsbKey from ....keyboard.mappings import UsbKey
from ....lanuages import Lanuages from ....languages import Languages
from ..otg.events import BaseEvent from ..otg.events import BaseEvent
from ..otg.events import ClearEvent from ..otg.events import ClearEvent
@ -117,7 +117,7 @@ class BtServer: # pylint: disable=too-many-instance-attributes
self.__keys: list[UsbKey | None] = [None] * 6 self.__keys: list[UsbKey | None] = [None] * 6
self.__mouse_buttons = 0 self.__mouse_buttons = 0
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
def run(self) -> None: def run(self) -> None:
with self.__iface: with self.__iface:

View File

@ -41,7 +41,7 @@ from ....validators.basic import valid_float_f01
from ....validators.os import valid_abs_path from ....validators.os import valid_abs_path
from ....validators.hw import valid_tty_speed from ....validators.hw import valid_tty_speed
from ....lanuages import Lanuages from ....languages import Languages
from .. import BaseHid from .. import BaseHid
@ -84,7 +84,7 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst
self.__keyboard = Keyboard() self.__keyboard = Keyboard()
self.__mouse = Mouse() self.__mouse = Mouse()
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
@classmethod @classmethod
def get_plugin_options(cls) -> dict: def get_plugin_options(cls) -> dict:

View File

@ -25,7 +25,7 @@ import contextlib
from typing import Generator from typing import Generator
from ....lanuages import Lanuages from ....languages import Languages
# ===== # =====
@ -37,7 +37,7 @@ class ChipResponseError(Exception):
class ChipConnection: class ChipConnection:
def __init__(self, tty: serial.Serial) -> None: def __init__(self, tty: serial.Serial) -> None:
self.__tty = tty self.__tty = tty
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
def xfer(self, cmd: bytes) -> int: def xfer(self, cmd: bytes) -> int:
self.__send(cmd) self.__send(cmd)

View File

@ -24,7 +24,7 @@ from typing import Iterable
from typing import AsyncGenerator from typing import AsyncGenerator
from typing import Any from typing import Any
from ....lanuages import Lanuages from ....languages import Languages
from ....logging import get_logger from ....logging import get_logger
@ -120,7 +120,7 @@ class Plugin(BaseHid): # pylint: disable=too-many-instance-attributes
def sysprep(self) -> None: def sysprep(self) -> None:
udc = usb.find_udc(self.__udc) udc = usb.find_udc(self.__udc)
get_logger(0).info(Lanuages().gettext("Using UDC %s"), udc) get_logger(0).info(Languages().gettext("Using UDC %s"), udc)
self.__keyboard_proc.start(udc) self.__keyboard_proc.start(udc)
self.__mouse_proc.start(udc) self.__mouse_proc.start(udc)
if self.__mouse_alt_proc: if self.__mouse_alt_proc:

View File

@ -37,7 +37,7 @@ from .... import aiomulti
from .... import aioproc from .... import aioproc
from .... import usb from .... import usb
from ....lanuages import Lanuages from ....languages import Languages
from .events import BaseEvent from .events import BaseEvent
@ -78,7 +78,7 @@ class BaseDeviceProcess(multiprocessing.Process): # pylint: disable=too-many-in
self.__logger: (logging.Logger | None) = None self.__logger: (logging.Logger | None) = None
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
def start(self, udc: str) -> None: # type: ignore # pylint: disable=arguments-differ def start(self, udc: str) -> None: # type: ignore # pylint: disable=arguments-differ
self.__udc_state_path = usb.get_udc_path(udc, usb.U_STATE) self.__udc_state_path = usb.get_udc_path(udc, usb.U_STATE)

View File

@ -41,7 +41,7 @@ from .events import get_led_scroll
from .events import get_led_num from .events import get_led_num
from .events import make_keyboard_report from .events import make_keyboard_report
from ....lanuages import Lanuages from ....languages import Languages
# ===== # =====
class KeyboardProcess(BaseDeviceProcess): class KeyboardProcess(BaseDeviceProcess):
@ -55,7 +55,7 @@ class KeyboardProcess(BaseDeviceProcess):
self.__pressed_modifiers: set[UsbKey] = set() self.__pressed_modifiers: set[UsbKey] = set()
self.__pressed_keys: list[UsbKey | None] = [None] * 6 self.__pressed_keys: list[UsbKey | None] = [None] * 6
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
def cleanup(self) -> None: def cleanup(self) -> None:
self._stop() self._stop()

View File

@ -36,7 +36,7 @@ from .events import MouseRelativeEvent
from .events import MouseWheelEvent from .events import MouseWheelEvent
from .events import make_mouse_report from .events import make_mouse_report
from ....lanuages import Lanuages from ....languages import Languages
# ===== # =====
class MouseProcess(BaseDeviceProcess): class MouseProcess(BaseDeviceProcess):
@ -55,7 +55,7 @@ class MouseProcess(BaseDeviceProcess):
self.__x = 0 # For absolute self.__x = 0 # For absolute
self.__y = 0 self.__y = 0
self.__win98_fix = False self.__win98_fix = False
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
def is_absolute(self) -> bool: def is_absolute(self) -> bool:
return self.__absolute return self.__absolute

View File

@ -41,7 +41,7 @@ from ...validators.basic import valid_int_f1
from ...validators.basic import valid_float_f01 from ...validators.basic import valid_float_f01
from ...validators.hw import valid_gpio_pin_optional from ...validators.hw import valid_gpio_pin_optional
from ...lanuages import Lanuages from ...languages import Lanuages
from ._mcu import BasePhyConnection from ._mcu import BasePhyConnection
from ._mcu import BasePhy from ._mcu import BasePhy

View File

@ -40,7 +40,7 @@ from ... import aiotools
from .. import BasePlugin from .. import BasePlugin
from .. import get_plugin_class from .. import get_plugin_class
from ...lanuages import Lanuages from ...languages import Languages
# ===== # =====
class MsdError(Exception): class MsdError(Exception):
@ -53,43 +53,43 @@ class MsdOperationError(OperationError, MsdError):
class MsdIsBusyError(IsBusyError, MsdError): class MsdIsBusyError(IsBusyError, MsdError):
def __init__(self) -> None: def __init__(self) -> None:
gettext=Lanuages().gettext gettext=Languages().gettext
super().__init__(gettext("Performing another MSD operation, please try again later")) super().__init__(gettext("Performing another MSD operation, please try again later"))
class MsdOfflineError(MsdOperationError): class MsdOfflineError(MsdOperationError):
def __init__(self) -> None: def __init__(self) -> None:
gettext=Lanuages().gettext gettext=Languages().gettext
super().__init__(gettext("MSD is not found")) super().__init__(gettext("MSD is not found"))
class MsdConnectedError(MsdOperationError): class MsdConnectedError(MsdOperationError):
def __init__(self) -> None: def __init__(self) -> None:
gettext=Lanuages().gettext gettext=Languages().gettext
super().__init__(gettext("MSD is connected to Server, but shouldn't for this operation")) super().__init__(gettext("MSD is connected to Server, but shouldn't for this operation"))
class MsdDisconnectedError(MsdOperationError): class MsdDisconnectedError(MsdOperationError):
def __init__(self) -> None: def __init__(self) -> None:
gettext=Lanuages().gettext gettext=Languages().gettext
super().__init__(gettext("MSD is disconnected from Server, but should be for this operation")) super().__init__(gettext("MSD is disconnected from Server, but should be for this operation"))
class MsdImageNotSelected(MsdOperationError): class MsdImageNotSelected(MsdOperationError):
def __init__(self) -> None: def __init__(self) -> None:
gettext=Lanuages().gettext gettext=Languages().gettext
super().__init__(gettext("The image is not selected")) super().__init__(gettext("The image is not selected"))
class MsdUnknownImageError(MsdOperationError): class MsdUnknownImageError(MsdOperationError):
def __init__(self) -> None: def __init__(self) -> None:
gettext=Lanuages().gettext gettext=Languages().gettext
super().__init__(gettext("The image is not found in the storage")) super().__init__(gettext("The image is not found in the storage"))
class MsdImageExistsError(MsdOperationError): class MsdImageExistsError(MsdOperationError):
def __init__(self) -> None: def __init__(self) -> None:
gettext=Lanuages().gettext gettext=Languages().gettext
super().__init__(gettext("This image is already exists")) super().__init__(gettext("This image is already exists"))

View File

@ -31,7 +31,7 @@ from . import BaseMsdReader
from . import BaseMsdWriter from . import BaseMsdWriter
from . import BaseMsd from . import BaseMsd
from ...lanuages import Lanuages from ...languages import Lanuages
# ===== # =====
class MsdDisabledError(MsdOperationError): class MsdDisabledError(MsdOperationError):

View File

@ -28,7 +28,7 @@ import time
from typing import AsyncGenerator from typing import AsyncGenerator
from ....lanuages import Lanuages from ....languages import Languages
from ....logging import get_logger from ....logging import get_logger
from ....inotify import InotifyMask from ....inotify import InotifyMask
@ -44,7 +44,7 @@ from ....validators.kvm import valid_msd_image_name
from .... import aiotools from .... import aiotools
from .... import fstab from .... import fstab
from ....lanuages import Lanuages from ....languages import Languages
from .. import MsdIsBusyError from .. import MsdIsBusyError
from .. import MsdOfflineError from .. import MsdOfflineError
@ -142,7 +142,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
self.__notifier = aiotools.AioNotifier() self.__notifier = aiotools.AioNotifier()
self.__state = _State(self.__notifier) self.__state = _State(self.__notifier)
self.gettext=Lanuages().gettext self.gettext=Languages().gettext
logger = get_logger(0) logger = get_logger(0)
logger.info(self.gettext("Using OTG gadget %r as MSD"), gadget) logger.info(self.gettext("Using OTG gadget %r as MSD"), gadget)

View File

@ -27,13 +27,13 @@ from .... import usb
from .. import MsdOperationError from .. import MsdOperationError
from ....lanuages import Lanuages from ....languages import Languages
# ===== # =====
class MsdDriveLockedError(MsdOperationError): class MsdDriveLockedError(MsdOperationError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(Lanuages().gettext("MSD drive is locked on IO operation")) super().__init__(Languages().gettext("MSD drive is locked on IO operation"))
# ===== # =====

View File

@ -31,7 +31,7 @@ from typing import Optional
import aiofiles import aiofiles
import aiofiles.os import aiofiles.os
from ....lanuages import Lanuages from ....languages import Languages
from .... import aiotools from .... import aiotools
from .... import aiohelpers from .... import aiohelpers
@ -294,4 +294,4 @@ class Storage(_StorageDc):
async def remount_rw(self, rw: bool, fatal: bool=True) -> None: async def remount_rw(self, rw: bool, fatal: bool=True) -> None:
if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)): if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)):
if fatal: if fatal:
raise MsdError(Lanuages().gettext("Can't execute remount helper")) raise MsdError(Languages().gettext("Can't execute remount helper"))

View File

@ -41,7 +41,7 @@ from ...validators.basic import valid_bool
from ...validators.basic import valid_number from ...validators.basic import valid_number
from ...validators.basic import valid_float_f01 from ...validators.basic import valid_float_f01
from ...lanuages import Lanuages from ...languages import Languages
from . import BaseUserGpioDriver from . import BaseUserGpioDriver
from . import GpioDriverOfflineError from . import GpioDriverOfflineError
@ -149,7 +149,7 @@ class Plugin(BaseUserGpioDriver): # pylint: disable=too-many-instance-attribute
htclient.raise_not_200(response) htclient.raise_not_200(response)
except Exception as err: except Exception as err:
get_logger().error(Lanuages().gettext("Failed ANELPWR POST request to pin %s: %s"), pin, tools.efmt(err)) get_logger().error(Languages().gettext("Failed ANELPWR POST request to pin %s: %s"), pin, tools.efmt(err))
raise GpioDriverOfflineError(self) raise GpioDriverOfflineError(self)
self.__update_notifier.notify() self.__update_notifier.notify()

View File

@ -22,7 +22,7 @@
import os import os
from .lanuages import Lanuages from .languages import Languages
from . import env from . import env
@ -33,10 +33,10 @@ def find_udc(udc: str) -> str:
candidates = sorted(os.listdir(path)) candidates = sorted(os.listdir(path))
if not udc: if not udc:
if len(candidates) == 0: if len(candidates) == 0:
raise RuntimeError(Lanuages().gettext("Can't find any UDC")) raise RuntimeError(Languages().gettext("Can't find any UDC"))
udc = candidates[0] udc = candidates[0]
elif udc not in candidates: elif udc not in candidates:
raise RuntimeError(Lanuages().gettext(f"Can't find selected UDC: {udc}")) raise RuntimeError(Languages().gettext(f"Can't find selected UDC: {udc}"))
return udc # fe980000.usb return udc # fe980000.usb

View File

@ -0,0 +1,34 @@
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
from typing import Any
from . import check_string_in_list
from .basic import valid_number
# =====
def valid_languages(arg: Any) -> str:
return check_string_in_list(arg, "Languages", ["zh", "en", "default"])

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-08-12 22:07+0800\n" "POT-Creation-Date: 2024-08-14 22:40+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -89,37 +89,34 @@ msgstr ""
msgid "Can't find any UDC" msgid "Can't find any UDC"
msgstr "" msgstr ""
#: kvmd/apps/__init__.py:132 #: kvmd/apps/__init__.py:164
msgid "Set config file path" msgid "INFO"
msgstr ""
#: kvmd/apps/__init__.py:134
msgid "Override config options list (like sec/sub/opt=value)"
msgstr ""
#: kvmd/apps/__init__.py:136
msgid "View current configuration (include all overrides)"
msgstr ""
#: kvmd/apps/__init__.py:139
msgid "Run the service"
msgstr "" msgstr ""
#: kvmd/apps/__init__.py:165 #: kvmd/apps/__init__.py:165
msgid "WARNING"
msgstr ""
#: kvmd/apps/__init__.py:166
msgid "ERROR"
msgstr ""
#: kvmd/apps/__init__.py:176
msgid "" msgid ""
"To prevent accidental startup, you must specify the --run option to " "To prevent accidental startup, you must specify the --run option to "
"start.\n" "start.\n"
msgstr "" msgstr ""
#: kvmd/apps/__init__.py:165 #: kvmd/apps/__init__.py:176
msgid "Try the --help option to find out what this service does.\n" msgid "Try the --help option to find out what this service does.\n"
msgstr "" msgstr ""
#: kvmd/apps/__init__.py:165 #: kvmd/apps/__init__.py:176
msgid "Make sure you understand exactly what you are doing!" msgid "Make sure you understand exactly what you are doing!"
msgstr "" msgstr ""
#: kvmd/apps/kvmd/__init__.py:115 kvmd/apps/otgnet/__init__.py:132 #: kvmd/apps/kvmd/__init__.py:115 kvmd/apps/otgnet/__init__.py:132
#: kvmd/apps/vnc/server.py:541
msgid "Bye-bye" msgid "Bye-bye"
msgstr "" msgstr ""
@ -171,6 +168,48 @@ msgstr ""
msgid "Logged out user %r (%d)" msgid "Logged out user %r (%d)"
msgstr "" msgstr ""
#: kvmd/apps/kvmd/server.py:89
msgid "This streamer does not support quality settings"
msgstr ""
#: kvmd/apps/kvmd/server.py:94
msgid "This streamer does not support resolution settings"
msgstr ""
#: kvmd/apps/kvmd/server.py:99
msgid "This streamer does not support H264"
msgstr ""
#: kvmd/apps/kvmd/server.py:298
msgid "Waiting short tasks ..."
msgstr ""
#: kvmd/apps/kvmd/server.py:300
msgid "Stopping system tasks ..."
msgstr ""
#: kvmd/apps/kvmd/server.py:302
msgid "Disconnecting clients ..."
msgstr ""
#: kvmd/apps/kvmd/server.py:304
msgid "On-Shutdown complete"
msgstr ""
#: kvmd/apps/kvmd/server.py:310
#, python-format
msgid "Cleaning up %s ..."
msgstr ""
#: kvmd/apps/kvmd/server.py:314
#, python-format
msgid "Cleanup error on %s"
msgstr ""
#: kvmd/apps/kvmd/server.py:315
msgid "On-Cleanup complete"
msgstr ""
#: kvmd/apps/kvmd/streamer.py:245 #: kvmd/apps/kvmd/streamer.py:245
msgid "Streamer stop cancelled" msgid "Streamer stop cancelled"
msgstr "" msgstr ""
@ -374,6 +413,115 @@ msgstr ""
msgid "Using OTG Ethernet interface %r ..." msgid "Using OTG Ethernet interface %r ..."
msgstr "" msgstr ""
#: kvmd/apps/vnc/server.py:163
#, python-format
msgid "%s [kvmd]: Waiting for the SetEncodings message ..."
msgstr ""
#: kvmd/apps/vnc/server.py:165
msgid "No SetEncodings message recieved from the client in 5 secs"
msgstr ""
#: kvmd/apps/vnc/server.py:169
#, python-format
msgid "%s [kvmd]: Applying HID params: mouse_output=%s ..."
msgstr ""
#: kvmd/apps/vnc/server.py:177
msgid "KVMD closed the websocket (the server may have been stopped)"
msgstr ""
#: kvmd/apps/vnc/server.py:211
#, python-format
msgid "%s [streamer]: Streaming ..."
msgstr ""
#: kvmd/apps/vnc/server.py:216
msgid "No signal"
msgstr ""
#: kvmd/apps/vnc/server.py:220
#, python-format
msgid "%s [streamer]: Permanent error: %s; switching to %s ..."
msgstr ""
#: kvmd/apps/vnc/server.py:222
#, python-format
msgid "%s [streamer]: Waiting for stream: %s"
msgstr ""
#: kvmd/apps/vnc/server.py:223
msgid "Waiting for stream ..."
msgstr ""
#: kvmd/apps/vnc/server.py:234
#, python-format
msgid "%s [streamer]: Using preferred %s"
msgstr ""
#: kvmd/apps/vnc/server.py:240
#, python-format
msgid "%s [streamer]: Using default %s"
msgstr ""
#: kvmd/apps/vnc/server.py:305
msgid "The client doesn't want to accept H264 anymore"
msgstr ""
#: kvmd/apps/vnc/server.py:311
msgid "format"
msgstr ""
#: kvmd/apps/vnc/server.py:417
#, python-format
msgid "%s [main]: Applying streamer params: jpeg_quality=%s; desired_fps=%d ..."
msgstr ""
#: kvmd/apps/vnc/server.py:467
#, python-format
msgid "%s [entry]: Connection is closed in an emergency"
msgstr ""
#: kvmd/apps/vnc/server.py:472
#, python-format
msgid "%s [entry]: Connected client"
msgstr ""
#: kvmd/apps/vnc/server.py:491
#, python-format
msgid "%s [entry]: Can't check KVMD auth mode: %s"
msgstr ""
#: kvmd/apps/vnc/server.py:513
#, python-format
msgid "%s [entry]: Unhandled exception in client task"
msgstr ""
#: kvmd/apps/vnc/server.py:523
#, python-format
msgid "Listening VNC on TCP [%s]:%d ..."
msgstr ""
#: kvmd/apps/vnc/vncauth.py:63
msgid "Unhandled exception while reading VNCAuth passwd file"
msgstr ""
#: kvmd/apps/vnc/vncauth.py:74
msgid "Missing ' -> ' operator"
msgstr ""
#: kvmd/apps/vnc/vncauth.py:78
msgid "Missing ':' operator in KVMD credentials (right part)"
msgstr ""
#: kvmd/apps/vnc/vncauth.py:83
msgid "Empty KVMD user (right part)"
msgstr ""
#: kvmd/apps/vnc/vncauth.py:86
msgid "Duplicating VNC password (left part)"
msgstr ""
#: kvmd/keyboard/keysym.py:69 #: kvmd/keyboard/keysym.py:69
#, python-format #, python-format
msgid "Invalid modifier key at mapping %s: %s / %s" msgid "Invalid modifier key at mapping %s: %s / %s"

View File

@ -65,3 +65,7 @@ nginx:
janus: janus:
cmd: cmd:
- "/bin/true" - "/bin/true"
languages:
console: zh
web: zh