Skip to content

Commit 03b245a

Browse files
authored
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
1 parent 7d10f9e commit 03b245a

File tree

6 files changed

+124
-7
lines changed

6 files changed

+124
-7
lines changed

archinstall/lib/args.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def from_config(cls, args_config: dict[str, Any], args: Arguments) -> Self:
143143

144144
if archinstall_lang := args_config.get('archinstall-language', None):
145145
arch_config.archinstall_language = translation_handler.get_language_by_name(archinstall_lang)
146+
translation_handler.activate(arch_config.archinstall_language, set_font=False)
146147

147148
if disk_config := args_config.get('disk_config', {}):
148149
enc_password = args_config.get('encryption_password', '')

archinstall/lib/general/general_menu.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ async def select_archinstall_language(languages: list[Language], preset: Languag
105105
group = MenuItemGroup(items, sort_items=True)
106106
group.set_focus_by_value(preset)
107107

108-
title = 'NOTE: If a language can not displayed properly, a proper font must be set manually in the console.\n'
109-
title += 'All available fonts can be found in "/usr/share/kbd/consolefonts"\n'
110-
title += 'e.g. setfont LatGrkCyr-8x16 (to display latin/greek/cyrillic characters)\n'
108+
title = 'NOTE: Console font will be set automatically for supported languages.\n'
109+
title += 'For other languages, fonts can be found in "/usr/share/kbd/consolefonts"\n'
110+
title += 'and set manually with: setfont <fontname>\n'
111111

112112
result = await Selection[Language](
113113
header=title,

archinstall/lib/translationhandler.py

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
import gettext
33
import json
44
import os
5+
import tempfile
56
from dataclasses import dataclass
67
from pathlib import Path
78
from typing import override
89

10+
from archinstall.lib.command import SysCommand
11+
from archinstall.lib.exceptions import SysCallError
12+
from archinstall.lib.output import debug
13+
from archinstall.lib.utils.util import running_from_iso
14+
915

1016
@dataclass
1117
class Language:
@@ -14,6 +20,7 @@ class Language:
1420
translation: gettext.NullTranslations
1521
translation_percent: int
1622
translated_lang: str | None
23+
console_font: str | None = None
1724

1825
@property
1926
def display_name(self) -> str:
@@ -31,10 +38,18 @@ def json(self) -> str:
3138
return self.name_en
3239

3340

41+
_DEFAULT_FONT = 'default8x16'
42+
_ENV_FONT = os.environ.get('FONT')
43+
44+
3445
class TranslationHandler:
3546
def __init__(self) -> None:
3647
self._base_pot = 'base.pot'
3748
self._languages = 'languages.json'
49+
self._active_language: Language | None = None
50+
self._font_backup: Path | None = None
51+
self._cmap_backup: Path | None = None
52+
self._using_env_font: bool = False
3853

3954
self._total_messages = self._get_total_active_messages()
4055
self._translated_languages = self._get_translations()
@@ -43,6 +58,65 @@ def __init__(self) -> None:
4358
def translated_languages(self) -> list[Language]:
4459
return self._translated_languages
4560

61+
@property
62+
def active_font(self) -> str | None:
63+
if self._active_language is not None:
64+
return self._active_language.console_font
65+
return None
66+
67+
def _set_font(self, font_name: str | None) -> bool:
68+
"""Set the console font via setfont. Only runs on ISO. Returns True on success."""
69+
if not running_from_iso():
70+
return False
71+
72+
target = font_name or _DEFAULT_FONT
73+
try:
74+
SysCommand(['setfont', target])
75+
return True
76+
except SysCallError as err:
77+
debug(f'Failed to set console font {target}: {err}')
78+
return False
79+
80+
def save_console_font(self) -> None:
81+
"""Save the current console font (with unicode map) and console map to temp files."""
82+
if not running_from_iso():
83+
return
84+
85+
try:
86+
font_fd, font_path = tempfile.mkstemp(prefix='archinstall_font_')
87+
cmap_fd, cmap_path = tempfile.mkstemp(prefix='archinstall_cmap_')
88+
os.close(font_fd)
89+
os.close(cmap_fd)
90+
self._font_backup = Path(font_path)
91+
self._cmap_backup = Path(cmap_path)
92+
SysCommand(['setfont', '-O', str(self._font_backup), '-om', str(self._cmap_backup)])
93+
except SysCallError as err:
94+
debug(f'Failed to save console font: {err}')
95+
self._font_backup = None
96+
self._cmap_backup = None
97+
98+
def restore_console_font(self) -> None:
99+
"""Restore console font (with unicode map) and console map from backup."""
100+
if not running_from_iso():
101+
return
102+
103+
if self._font_backup is None or not self._font_backup.exists():
104+
return
105+
106+
cmd = ['setfont', str(self._font_backup)]
107+
if self._cmap_backup is not None and self._cmap_backup.exists():
108+
cmd += ['-m', str(self._cmap_backup)]
109+
try:
110+
SysCommand(cmd)
111+
except SysCallError as err:
112+
debug(f'Failed to restore console font: {err}')
113+
114+
self._font_backup.unlink(missing_ok=True)
115+
self._font_backup = None
116+
if self._cmap_backup is not None:
117+
self._cmap_backup.unlink(missing_ok=True)
118+
self._cmap_backup = None
119+
46120
def _get_translations(self) -> list[Language]:
47121
"""
48122
Load all translated languages and return a list of such
@@ -57,6 +131,7 @@ def _get_translations(self) -> list[Language]:
57131
abbr = mapping_entry['abbr']
58132
lang = mapping_entry['lang']
59133
translated_lang = mapping_entry.get('translated_lang', None)
134+
console_font = mapping_entry.get('console_font', None)
60135

61136
try:
62137
# get a translation for a specific language
@@ -71,7 +146,7 @@ def _get_translations(self) -> list[Language]:
71146
# prevent cases where the .pot file is out of date and the percentage is above 100
72147
percent = min(100, percent)
73148

74-
language = Language(abbr, lang, translation, percent, translated_lang)
149+
language = Language(abbr, lang, translation, percent, translated_lang, console_font)
75150
languages.append(language)
76151
except FileNotFoundError as err:
77152
raise FileNotFoundError(f"Could not locate language file for '{lang}': {err}")
@@ -127,12 +202,39 @@ def get_language_by_abbr(self, abbr: str) -> Language:
127202
except Exception:
128203
raise ValueError(f'No language with abbreviation "{abbr}" found')
129204

130-
def activate(self, language: Language) -> None:
205+
def activate(self, language: Language, set_font: bool = True) -> None:
131206
"""
132207
Set the provided language as the current translation
133208
"""
134209
# The install() call has the side effect of assigning GNUTranslations.gettext to builtins._
135210
language.translation.install()
211+
self._active_language = language
212+
213+
if set_font and not self._using_env_font:
214+
self._set_font(language.console_font)
215+
216+
def apply_console_font(self) -> None:
217+
"""Apply console font from FONT env var or active language mapping.
218+
219+
If FONT env var is set and valid, use it and skip language mapping.
220+
If FONT is set but invalid, fall back to language font.
221+
If FONT is not set, use active language font.
222+
"""
223+
if not running_from_iso():
224+
return
225+
226+
if _ENV_FONT:
227+
if self._set_font(_ENV_FONT):
228+
self._using_env_font = True
229+
debug(f'Console font set from FONT env var: {_ENV_FONT}')
230+
else:
231+
debug(f'FONT={_ENV_FONT} could not be set, falling back to language font mapping')
232+
if self.active_font:
233+
self._set_font(self.active_font)
234+
debug(f'Console font set from language mapping: {self.active_font}')
235+
elif self.active_font:
236+
self._set_font(self.active_font)
237+
debug(f'Console font set from language mapping: {self.active_font}')
136238

137239
def _get_locales_dir(self) -> Path:
138240
"""

archinstall/locales/languages.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@
169169
{"abbr": "tr", "lang": "Turkish", "translated_lang" : "Türkçe"},
170170
{"abbr": "tw", "lang": "Twi"},
171171
{"abbr": "ug", "lang": "Uighur"},
172-
{"abbr": "uk", "lang": "Ukrainian"},
172+
{"abbr": "uk", "lang": "Ukrainian", "console_font": "UniCyr_8x16"},
173173
{"abbr": "ur", "lang": "Urdu", "translated_lang": "اردو"},
174174
{"abbr": "uz", "lang": "Uzbek", "translated_lang": "O'zbek"},
175175
{"abbr": "ve", "lang": "Venda"},

archinstall/main.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from archinstall.lib.output import debug, error, info, warn
1717
from archinstall.lib.packages.util import check_version_upgrade
1818
from archinstall.lib.pacman.pacman import Pacman
19-
from archinstall.lib.translationhandler import tr
19+
from archinstall.lib.translationhandler import tr, translation_handler
2020
from archinstall.lib.utils.util import running_from_iso
2121
from archinstall.tui.ui.components import tui
2222

@@ -95,6 +95,8 @@ def run() -> int:
9595
print(tr('Archinstall requires root privileges to run. See --help for more.'))
9696
return 1
9797

98+
translation_handler.save_console_font()
99+
98100
_log_sys_info()
99101

100102
if not arch_config_handler.args.offline:
@@ -159,6 +161,8 @@ def main() -> int:
159161
_error_message(exc)
160162
rc = 1
161163

164+
translation_handler.restore_console_font()
165+
162166
return rc
163167

164168

archinstall/tui/ui/components.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,13 @@ def __init__(self, main: InstanceRunnable[ValueT] | Callable[[], Awaitable[Value
12721272
super().__init__(ansi_color=True)
12731273
self._main = main
12741274

1275+
@override
1276+
async def _on_exit_app(self) -> None:
1277+
from archinstall.lib.translationhandler import translation_handler
1278+
1279+
translation_handler.restore_console_font()
1280+
await super()._on_exit_app()
1281+
12751282
def action_trigger_help(self) -> None:
12761283
from textual.widgets import HelpPanel
12771284

@@ -1281,6 +1288,9 @@ def action_trigger_help(self) -> None:
12811288
_ = self.screen.mount(HelpPanel())
12821289

12831290
def on_mount(self) -> None:
1291+
from archinstall.lib.translationhandler import translation_handler
1292+
1293+
translation_handler.apply_console_font()
12841294
_translate_bindings(self._merged_bindings, self._bindings)
12851295
self._run_worker()
12861296

0 commit comments

Comments
 (0)