Set console font automatically when selecting language (#4356)
* Set console font automatically when selecting language Add console_font field to languages.json and Language dataclass. When a language is activated, setfont is called automatically, falling back to default8x16 on error or for languages without a custom font. Also activate translation when loading language from config file. * Support FONT environment variable for console font override When FONT env var is set, use it as the console font instead of the language-specific font mapping from languages.json. The font is applied at startup and preserved across language switches. On exit (success or failure), restore the console font to default8x16. * CI checks fix * Try to restore original console font with setfont -O * Fix for pylint * Restore console font before Textual exits application mode Move font set/restore into Textual lifecycle to prevent color artifacts from 256/512 glyph transitions. Apply FONT env var and language font in on_mount, restore in _on_exit_app. Skip font change when loading language from config (set_font=False) to defer it until TUI starts. * Fall back to language font mapping when FONT env var is invalid Add _using_env_font flag to skip mapping only when FONT was successfully applied. Show info message after TUI exits if FONT could not be set. * Use tempfile for console font backup files * Fix linter errors: use mkstemp, close fds, add @override * Fix ruff formatting * Move font state from module singleton into TranslationHandler * Refactor font handling per review feedback * Make font methods members of TranslationHandler, skip on non-ISO * ci: trigger tests * Move running_from_iso import to module level No circular dep, simpler than per-method local imports. * Add explicit ISO guard to restore_console_font Matches the existing guards in _set_font and save_console_font. Behaviour was already safe implicitly via _font_backup=None, but the explicit check makes intent obvious at the call site. * Skip apply_console_font off-ISO * Use list form for setfont SysCommand calls
This commit is contained in:
parent
7d10f9e08b
commit
03b245a77a
|
|
@ -143,6 +143,7 @@ class ArchConfig:
|
|||
|
||||
if archinstall_lang := args_config.get('archinstall-language', None):
|
||||
arch_config.archinstall_language = translation_handler.get_language_by_name(archinstall_lang)
|
||||
translation_handler.activate(arch_config.archinstall_language, set_font=False)
|
||||
|
||||
if disk_config := args_config.get('disk_config', {}):
|
||||
enc_password = args_config.get('encryption_password', '')
|
||||
|
|
|
|||
|
|
@ -105,9 +105,9 @@ async def select_archinstall_language(languages: list[Language], preset: Languag
|
|||
group = MenuItemGroup(items, sort_items=True)
|
||||
group.set_focus_by_value(preset)
|
||||
|
||||
title = 'NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n'
|
||||
title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n'
|
||||
title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n'
|
||||
title = 'NOTE: Console font will be set automatically for supported languages.\n'
|
||||
title += 'For other languages, fonts can be found in "/usr/share/kbd/consolefonts"\n'
|
||||
title += 'and set manually with: setfont <fontname>\n'
|
||||
|
||||
result = await Selection[Language](
|
||||
header=title,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,16 @@ import builtins
|
|||
import gettext
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import override
|
||||
|
||||
from archinstall.lib.command import SysCommand
|
||||
from archinstall.lib.exceptions import SysCallError
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.utils.util import running_from_iso
|
||||
|
||||
|
||||
@dataclass
|
||||
class Language:
|
||||
|
|
@ -14,6 +20,7 @@ class Language:
|
|||
translation: gettext.NullTranslations
|
||||
translation_percent: int
|
||||
translated_lang: str | None
|
||||
console_font: str | None = None
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
|
|
@ -31,10 +38,18 @@ class Language:
|
|||
return self.name_en
|
||||
|
||||
|
||||
_DEFAULT_FONT = 'default8x16'
|
||||
_ENV_FONT = os.environ.get('FONT')
|
||||
|
||||
|
||||
class TranslationHandler:
|
||||
def __init__(self) -> None:
|
||||
self._base_pot = 'base.pot'
|
||||
self._languages = 'languages.json'
|
||||
self._active_language: Language | None = None
|
||||
self._font_backup: Path | None = None
|
||||
self._cmap_backup: Path | None = None
|
||||
self._using_env_font: bool = False
|
||||
|
||||
self._total_messages = self._get_total_active_messages()
|
||||
self._translated_languages = self._get_translations()
|
||||
|
|
@ -43,6 +58,65 @@ class TranslationHandler:
|
|||
def translated_languages(self) -> list[Language]:
|
||||
return self._translated_languages
|
||||
|
||||
@property
|
||||
def active_font(self) -> str | None:
|
||||
if self._active_language is not None:
|
||||
return self._active_language.console_font
|
||||
return None
|
||||
|
||||
def _set_font(self, font_name: str | None) -> bool:
|
||||
"""Set the console font via setfont. Only runs on ISO. Returns True on success."""
|
||||
if not running_from_iso():
|
||||
return False
|
||||
|
||||
target = font_name or _DEFAULT_FONT
|
||||
try:
|
||||
SysCommand(['setfont', target])
|
||||
return True
|
||||
except SysCallError as err:
|
||||
debug(f'Failed to set console font {target}: {err}')
|
||||
return False
|
||||
|
||||
def save_console_font(self) -> None:
|
||||
"""Save the current console font (with unicode map) and console map to temp files."""
|
||||
if not running_from_iso():
|
||||
return
|
||||
|
||||
try:
|
||||
font_fd, font_path = tempfile.mkstemp(prefix='archinstall_font_')
|
||||
cmap_fd, cmap_path = tempfile.mkstemp(prefix='archinstall_cmap_')
|
||||
os.close(font_fd)
|
||||
os.close(cmap_fd)
|
||||
self._font_backup = Path(font_path)
|
||||
self._cmap_backup = Path(cmap_path)
|
||||
SysCommand(['setfont', '-O', str(self._font_backup), '-om', str(self._cmap_backup)])
|
||||
except SysCallError as err:
|
||||
debug(f'Failed to save console font: {err}')
|
||||
self._font_backup = None
|
||||
self._cmap_backup = None
|
||||
|
||||
def restore_console_font(self) -> None:
|
||||
"""Restore console font (with unicode map) and console map from backup."""
|
||||
if not running_from_iso():
|
||||
return
|
||||
|
||||
if self._font_backup is None or not self._font_backup.exists():
|
||||
return
|
||||
|
||||
cmd = ['setfont', str(self._font_backup)]
|
||||
if self._cmap_backup is not None and self._cmap_backup.exists():
|
||||
cmd += ['-m', str(self._cmap_backup)]
|
||||
try:
|
||||
SysCommand(cmd)
|
||||
except SysCallError as err:
|
||||
debug(f'Failed to restore console font: {err}')
|
||||
|
||||
self._font_backup.unlink(missing_ok=True)
|
||||
self._font_backup = None
|
||||
if self._cmap_backup is not None:
|
||||
self._cmap_backup.unlink(missing_ok=True)
|
||||
self._cmap_backup = None
|
||||
|
||||
def _get_translations(self) -> list[Language]:
|
||||
"""
|
||||
Load all translated languages and return a list of such
|
||||
|
|
@ -57,6 +131,7 @@ class TranslationHandler:
|
|||
abbr = mapping_entry['abbr']
|
||||
lang = mapping_entry['lang']
|
||||
translated_lang = mapping_entry.get('translated_lang', None)
|
||||
console_font = mapping_entry.get('console_font', None)
|
||||
|
||||
try:
|
||||
# get a translation for a specific language
|
||||
|
|
@ -71,7 +146,7 @@ class TranslationHandler:
|
|||
# prevent cases where the .pot file is out of date and the percentage is above 100
|
||||
percent = min(100, percent)
|
||||
|
||||
language = Language(abbr, lang, translation, percent, translated_lang)
|
||||
language = Language(abbr, lang, translation, percent, translated_lang, console_font)
|
||||
languages.append(language)
|
||||
except FileNotFoundError as err:
|
||||
raise FileNotFoundError(f"Could not locate language file for '{lang}': {err}")
|
||||
|
|
@ -127,12 +202,39 @@ class TranslationHandler:
|
|||
except Exception:
|
||||
raise ValueError(f'No language with abbreviation "{abbr}" found')
|
||||
|
||||
def activate(self, language: Language) -> None:
|
||||
def activate(self, language: Language, set_font: bool = True) -> None:
|
||||
"""
|
||||
Set the provided language as the current translation
|
||||
"""
|
||||
# The install() call has the side effect of assigning GNUTranslations.gettext to builtins._
|
||||
language.translation.install()
|
||||
self._active_language = language
|
||||
|
||||
if set_font and not self._using_env_font:
|
||||
self._set_font(language.console_font)
|
||||
|
||||
def apply_console_font(self) -> None:
|
||||
"""Apply console font from FONT env var or active language mapping.
|
||||
|
||||
If FONT env var is set and valid, use it and skip language mapping.
|
||||
If FONT is set but invalid, fall back to language font.
|
||||
If FONT is not set, use active language font.
|
||||
"""
|
||||
if not running_from_iso():
|
||||
return
|
||||
|
||||
if _ENV_FONT:
|
||||
if self._set_font(_ENV_FONT):
|
||||
self._using_env_font = True
|
||||
debug(f'Console font set from FONT env var: {_ENV_FONT}')
|
||||
else:
|
||||
debug(f'FONT={_ENV_FONT} could not be set, falling back to language font mapping')
|
||||
if self.active_font:
|
||||
self._set_font(self.active_font)
|
||||
debug(f'Console font set from language mapping: {self.active_font}')
|
||||
elif self.active_font:
|
||||
self._set_font(self.active_font)
|
||||
debug(f'Console font set from language mapping: {self.active_font}')
|
||||
|
||||
def _get_locales_dir(self) -> Path:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@
|
|||
{"abbr": "tr", "lang": "Turkish", "translated_lang" : "Türkçe"},
|
||||
{"abbr": "tw", "lang": "Twi"},
|
||||
{"abbr": "ug", "lang": "Uighur"},
|
||||
{"abbr": "uk", "lang": "Ukrainian"},
|
||||
{"abbr": "uk", "lang": "Ukrainian", "console_font": "UniCyr_8x16"},
|
||||
{"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو"},
|
||||
{"abbr": "uz", "lang": "Uzbek", "translated_lang": "O'zbek"},
|
||||
{"abbr": "ve", "lang": "Venda"},
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from archinstall.lib.networking import ping
|
|||
from archinstall.lib.output import debug, error, info, warn
|
||||
from archinstall.lib.packages.util import check_version_upgrade
|
||||
from archinstall.lib.pacman.pacman import Pacman
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.lib.translationhandler import tr, translation_handler
|
||||
from archinstall.lib.utils.util import running_from_iso
|
||||
from archinstall.tui.ui.components import tui
|
||||
|
||||
|
|
@ -95,6 +95,8 @@ def run() -> int:
|
|||
print(tr('Archinstall requires root privileges to run. See --help for more.'))
|
||||
return 1
|
||||
|
||||
translation_handler.save_console_font()
|
||||
|
||||
_log_sys_info()
|
||||
|
||||
if not arch_config_handler.args.offline:
|
||||
|
|
@ -159,6 +161,8 @@ def main() -> int:
|
|||
_error_message(exc)
|
||||
rc = 1
|
||||
|
||||
translation_handler.restore_console_font()
|
||||
|
||||
return rc
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1272,6 +1272,13 @@ class _AppInstance(App[ValueT]):
|
|||
super().__init__(ansi_color=True)
|
||||
self._main = main
|
||||
|
||||
@override
|
||||
async def _on_exit_app(self) -> None:
|
||||
from archinstall.lib.translationhandler import translation_handler
|
||||
|
||||
translation_handler.restore_console_font()
|
||||
await super()._on_exit_app()
|
||||
|
||||
def action_trigger_help(self) -> None:
|
||||
from textual.widgets import HelpPanel
|
||||
|
||||
|
|
@ -1281,6 +1288,9 @@ class _AppInstance(App[ValueT]):
|
|||
_ = self.screen.mount(HelpPanel())
|
||||
|
||||
def on_mount(self) -> None:
|
||||
from archinstall.lib.translationhandler import translation_handler
|
||||
|
||||
translation_handler.apply_console_font()
|
||||
_translate_bindings(self._merged_bindings, self._bindings)
|
||||
self._run_worker()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue