Add PL/EN i18n with runtime toggle (L key) and --lang flag
All UI strings translated: section titles, popups, status messages, help screen, hints. Switch language live with L or start with --lang pl. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
242
systats.py
242
systats.py
@@ -26,6 +26,132 @@ AMD_GPU_PATH = "/sys/class/drm"
|
|||||||
|
|
||||||
REFRESH_INTERVAL = 1.0
|
REFRESH_INTERVAL = 1.0
|
||||||
|
|
||||||
|
# ─── i18n ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
LANGS = ["en", "pl"]
|
||||||
|
current_lang = "en"
|
||||||
|
|
||||||
|
TRANSLATIONS = {
|
||||||
|
# Header
|
||||||
|
"up": {"en": "Up", "pl": "Czas"},
|
||||||
|
"load": {"en": "Load", "pl": "Obc."},
|
||||||
|
"procs": {"en": "Procs", "pl": "Proc"},
|
||||||
|
"run": {"en": "run", "pl": "dzia."},
|
||||||
|
# Sections
|
||||||
|
"memory": {"en": "MEMORY", "pl": "PAMIEC"},
|
||||||
|
"network": {"en": "NETWORK", "pl": "SIEC"},
|
||||||
|
"disk": {"en": "DISK", "pl": "DYSK"},
|
||||||
|
"gpu": {"en": "GPU", "pl": "GPU"},
|
||||||
|
"temps": {"en": "TEMPS", "pl": "TEMP"},
|
||||||
|
"processes": {"en": "PROCESSES", "pl": "PROCESY"},
|
||||||
|
# Disk
|
||||||
|
"read": {"en": "Read", "pl": "Odczyt"},
|
||||||
|
"write": {"en": "Write", "pl": "Zapis"},
|
||||||
|
# Network
|
||||||
|
"total": {"en": "Total", "pl": "Suma"},
|
||||||
|
# Process table
|
||||||
|
"filter": {"en": "filter", "pl": "filtr"},
|
||||||
|
"name": {"en": "NAME", "pl": "NAZWA"},
|
||||||
|
# Process hints
|
||||||
|
"hint_sort": {"en": "sort", "pl": "sort."},
|
||||||
|
"hint_reverse": {"en": "reverse", "pl": "odwroc"},
|
||||||
|
"hint_filter": {"en": "filter", "pl": "filtr"},
|
||||||
|
"hint_signals": {"en": "signals", "pl": "sygnaly"},
|
||||||
|
"hint_details": {"en": "details", "pl": "szczeg."},
|
||||||
|
"hint_nice": {"en": "nice", "pl": "priorytet"},
|
||||||
|
"hint_quit": {"en": "quit", "pl": "wyjscie"},
|
||||||
|
# Kill menu
|
||||||
|
"send_signal_to": {"en": "Send signal to", "pl": "Wyslij sygnal do"},
|
||||||
|
"send": {"en": "send", "pl": "wyslij"},
|
||||||
|
"cancel": {"en": "cancel", "pl": "anuluj"},
|
||||||
|
"apply": {"en": "apply", "pl": "zastosuj"},
|
||||||
|
# Details
|
||||||
|
"proc_details": {"en": "Process Details", "pl": "Szczegoly procesu"},
|
||||||
|
"created": {"en": "Created", "pl": "Utworzony"},
|
||||||
|
"connections": {"en": "Connections", "pl": "Polaczenia"},
|
||||||
|
"close": {"en": "close", "pl": "zamknij"},
|
||||||
|
# Renice
|
||||||
|
"nice_value": {"en": "Nice value (-20..19)", "pl": "Priorytet (-20..19)"},
|
||||||
|
# Filter bar
|
||||||
|
"filter_label": {"en": "Filter", "pl": "Filtr"},
|
||||||
|
# Status messages
|
||||||
|
"no_proc_selected": {"en": "No process selected", "pl": "Nie wybrano procesu"},
|
||||||
|
"sent_to": {"en": "Sent {sig} to {name} (PID {pid})", "pl": "Wyslano {sig} do {name} (PID {pid})"},
|
||||||
|
"proc_not_found": {"en": "Process {pid} not found", "pl": "Proces {pid} nie znaleziony"},
|
||||||
|
"perm_denied": {"en": "Permission denied: {name} (PID {pid})", "pl": "Brak uprawnien: {name} (PID {pid})"},
|
||||||
|
"set_nice": {"en": "Set nice {val} for {name} (PID {pid})", "pl": "Ustawiono priorytet {val} dla {name} (PID {pid})"},
|
||||||
|
"invalid_nice": {"en": "Invalid nice value", "pl": "Nieprawidlowy priorytet"},
|
||||||
|
# Help
|
||||||
|
"help_title": {"en": "SYSTATS - Keyboard Shortcuts", "pl": "SYSTATS - Skroty klawiszowe"},
|
||||||
|
"help_close": {"en": "Press h, F1 or ? to close", "pl": "Nacisnij h, F1 lub ? aby zamknac"},
|
||||||
|
"help_nav": {"en": "NAVIGATION", "pl": "NAWIGACJA"},
|
||||||
|
"help_down": {"en": "Move cursor down", "pl": "Kursor w dol"},
|
||||||
|
"help_up": {"en": "Move cursor up", "pl": "Kursor w gore"},
|
||||||
|
"help_pgdn": {"en": "Scroll down 20", "pl": "Przewin 20 w dol"},
|
||||||
|
"help_pgup": {"en": "Scroll up 20", "pl": "Przewin 20 w gore"},
|
||||||
|
"help_home": {"en": "Go to top", "pl": "Na poczatek"},
|
||||||
|
"help_end": {"en": "Go to bottom", "pl": "Na koniec"},
|
||||||
|
"help_sorting": {"en": "SORTING", "pl": "SORTOWANIE"},
|
||||||
|
"help_sort_cpu": {"en": "Sort by CPU%", "pl": "Sortuj wg CPU%"},
|
||||||
|
"help_sort_mem": {"en": "Sort by MEM%", "pl": "Sortuj wg MEM%"},
|
||||||
|
"help_sort_pid": {"en": "Sort by PID", "pl": "Sortuj wg PID"},
|
||||||
|
"help_sort_name": {"en": "Sort by name", "pl": "Sortuj wg nazwy"},
|
||||||
|
"help_reverse": {"en": "Reverse sort order", "pl": "Odwroc sortowanie"},
|
||||||
|
"help_proc": {"en": "PROCESS MGMT", "pl": "ZARZ. PROCESAMI"},
|
||||||
|
"help_sigterm": {"en": "Send SIGTERM to process", "pl": "Wyslij SIGTERM"},
|
||||||
|
"help_sigkill": {"en": "Send SIGKILL to process", "pl": "Wyslij SIGKILL"},
|
||||||
|
"help_sigmenu": {"en": "Signal menu (choose signal)", "pl": "Menu sygnalow"},
|
||||||
|
"help_nice_pos": {"en": "Renice process", "pl": "Zmien priorytet"},
|
||||||
|
"help_nice_neg": {"en": "Renice process (negative)", "pl": "Zmien priorytet (ujemny)"},
|
||||||
|
"help_details": {"en": "Process details", "pl": "Szczegoly procesu"},
|
||||||
|
"help_filter": {"en": "Filter processes", "pl": "Filtruj procesy"},
|
||||||
|
"help_general": {"en": "GENERAL", "pl": "OGOLNE"},
|
||||||
|
"help_help": {"en": "Toggle this help", "pl": "Pokaz/ukryj pomoc"},
|
||||||
|
"help_lang": {"en": "Switch language (EN/PL)", "pl": "Zmien jezyk (EN/PL)"},
|
||||||
|
"help_quit": {"en": "Quit", "pl": "Wyjscie"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def t(key, **kwargs):
|
||||||
|
"""Get translated string."""
|
||||||
|
s = TRANSLATIONS.get(key, {}).get(current_lang, key)
|
||||||
|
if kwargs:
|
||||||
|
s = s.format(**kwargs)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def get_help_lines():
|
||||||
|
return [
|
||||||
|
(t("help_nav"), ""),
|
||||||
|
("j / Down", t("help_down")),
|
||||||
|
("k / Up", t("help_up")),
|
||||||
|
("PgDn", t("help_pgdn")),
|
||||||
|
("PgUp", t("help_pgup")),
|
||||||
|
("Home", t("help_home")),
|
||||||
|
("End", t("help_end")),
|
||||||
|
("", ""),
|
||||||
|
(t("help_sorting"), ""),
|
||||||
|
("c", t("help_sort_cpu")),
|
||||||
|
("m", t("help_sort_mem")),
|
||||||
|
("p", t("help_sort_pid")),
|
||||||
|
("n", t("help_sort_name")),
|
||||||
|
("r", t("help_reverse")),
|
||||||
|
("", ""),
|
||||||
|
(t("help_proc"), ""),
|
||||||
|
("T", t("help_sigterm")),
|
||||||
|
("K", t("help_sigkill")),
|
||||||
|
("F9", t("help_sigmenu")),
|
||||||
|
("+ / F8", t("help_nice_pos")),
|
||||||
|
("- / F7", t("help_nice_neg")),
|
||||||
|
("d / F5 / Enter", t("help_details")),
|
||||||
|
("/ / f", t("help_filter")),
|
||||||
|
("", ""),
|
||||||
|
(t("help_general"), ""),
|
||||||
|
("h / F1 / ?", t("help_help")),
|
||||||
|
("L", t("help_lang")),
|
||||||
|
("q / Q / Esc", t("help_quit")),
|
||||||
|
]
|
||||||
|
|
||||||
# History length for sparklines
|
# History length for sparklines
|
||||||
HIST_LEN = 60
|
HIST_LEN = 60
|
||||||
|
|
||||||
@@ -437,37 +563,6 @@ C_POPUP_BG = 12
|
|||||||
C_POPUP_HL = 13
|
C_POPUP_HL = 13
|
||||||
|
|
||||||
|
|
||||||
HELP_LINES = [
|
|
||||||
("NAVIGATION", ""),
|
|
||||||
("j / Down", "Move cursor down"),
|
|
||||||
("k / Up", "Move cursor up"),
|
|
||||||
("PgDn", "Scroll down 20"),
|
|
||||||
("PgUp", "Scroll up 20"),
|
|
||||||
("Home", "Go to top"),
|
|
||||||
("End", "Go to bottom"),
|
|
||||||
("", ""),
|
|
||||||
("SORTING", ""),
|
|
||||||
("c", "Sort by CPU%"),
|
|
||||||
("m", "Sort by MEM%"),
|
|
||||||
("p", "Sort by PID"),
|
|
||||||
("n", "Sort by name"),
|
|
||||||
("r", "Reverse sort order"),
|
|
||||||
("", ""),
|
|
||||||
("PROCESS MGMT", ""),
|
|
||||||
("T", "Send SIGTERM to process"),
|
|
||||||
("K", "Send SIGKILL to process"),
|
|
||||||
("F9", "Signal menu (choose signal)"),
|
|
||||||
("+ / F8", "Renice process"),
|
|
||||||
("- / F7", "Renice process (negative)"),
|
|
||||||
("d / F5 / Enter", "Process details"),
|
|
||||||
("/ / f", "Filter processes"),
|
|
||||||
("", ""),
|
|
||||||
("GENERAL", ""),
|
|
||||||
("h / F1 / ?", "Toggle this help"),
|
|
||||||
("q / Q / Esc", "Quit"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessManager:
|
class ProcessManager:
|
||||||
"""Interactive process management: kill, nice, filter, details."""
|
"""Interactive process management: kill, nice, filter, details."""
|
||||||
|
|
||||||
@@ -515,36 +610,36 @@ class ProcessManager:
|
|||||||
def kill_selected(self, processes, sig):
|
def kill_selected(self, processes, sig):
|
||||||
proc = self.get_selected_proc(processes)
|
proc = self.get_selected_proc(processes)
|
||||||
if not proc:
|
if not proc:
|
||||||
self._set_status("No process selected")
|
self._set_status(t("no_proc_selected"))
|
||||||
return
|
return
|
||||||
pid = proc.get("pid")
|
pid = proc.get("pid")
|
||||||
name = proc.get("name", "?")
|
name = proc.get("name", "?")
|
||||||
try:
|
try:
|
||||||
os.kill(pid, sig)
|
os.kill(pid, sig)
|
||||||
sig_name = signal.Signals(sig).name
|
sig_name = signal.Signals(sig).name
|
||||||
self._set_status(f"Sent {sig_name} to {name} (PID {pid})")
|
self._set_status(t("sent_to", sig=sig_name, name=name, pid=pid))
|
||||||
except ProcessLookupError:
|
except ProcessLookupError:
|
||||||
self._set_status(f"Process {pid} not found")
|
self._set_status(t("proc_not_found", pid=pid))
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
self._set_status(f"Permission denied: {name} (PID {pid})")
|
self._set_status(t("perm_denied", name=name, pid=pid))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._set_status(f"Error: {e}")
|
self._set_status(f"Error: {e}")
|
||||||
|
|
||||||
def renice_selected(self, processes, nice_val):
|
def renice_selected(self, processes, nice_val):
|
||||||
proc = self.get_selected_proc(processes)
|
proc = self.get_selected_proc(processes)
|
||||||
if not proc:
|
if not proc:
|
||||||
self._set_status("No process selected")
|
self._set_status(t("no_proc_selected"))
|
||||||
return
|
return
|
||||||
pid = proc.get("pid")
|
pid = proc.get("pid")
|
||||||
name = proc.get("name", "?")
|
name = proc.get("name", "?")
|
||||||
try:
|
try:
|
||||||
p = psutil.Process(pid)
|
p = psutil.Process(pid)
|
||||||
p.nice(nice_val)
|
p.nice(nice_val)
|
||||||
self._set_status(f"Set nice {nice_val} for {name} (PID {pid})")
|
self._set_status(t("set_nice", val=nice_val, name=name, pid=pid))
|
||||||
except psutil.NoSuchProcess:
|
except psutil.NoSuchProcess:
|
||||||
self._set_status(f"Process {pid} not found")
|
self._set_status(t("proc_not_found", pid=pid))
|
||||||
except psutil.AccessDenied:
|
except psutil.AccessDenied:
|
||||||
self._set_status(f"Permission denied: {name} (PID {pid})")
|
self._set_status(t("perm_denied", name=name, pid=pid))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._set_status(f"Error: {e}")
|
self._set_status(f"Error: {e}")
|
||||||
|
|
||||||
@@ -665,9 +760,9 @@ class Renderer:
|
|||||||
d = self.data
|
d = self.data
|
||||||
hostname = os.uname().nodename
|
hostname = os.uname().nodename
|
||||||
kernel = os.uname().release
|
kernel = os.uname().release
|
||||||
header = f" SYSTATS | {hostname} | Linux {kernel} | Up: {d.uptime} "
|
header = f" SYSTATS | {hostname} | Linux {kernel} | {t('up')}: {d.uptime} "
|
||||||
header += f"| Load: {d.load_avg[0]:.2f} {d.load_avg[1]:.2f} {d.load_avg[2]:.2f} "
|
header += f"| {t('load')}: {d.load_avg[0]:.2f} {d.load_avg[1]:.2f} {d.load_avg[2]:.2f} "
|
||||||
header += f"| Procs: {d.proc_count} ({d.proc_running} run) "
|
header += f"| {t('procs')}: {d.proc_count} ({d.proc_running} {t('run')}) "
|
||||||
brand = " Electric Entropy Lab "
|
brand = " Electric Entropy Lab "
|
||||||
header = header.ljust(w - len(brand))[:w - len(brand)]
|
header = header.ljust(w - len(brand))[:w - len(brand)]
|
||||||
safe_addstr(self.scr, y, 0, header, curses.color_pair(C_HEADER) | curses.A_BOLD)
|
safe_addstr(self.scr, y, 0, header, curses.color_pair(C_HEADER) | curses.A_BOLD)
|
||||||
@@ -715,7 +810,7 @@ class Renderer:
|
|||||||
def _draw_memory(self, y, x, w):
|
def _draw_memory(self, y, x, w):
|
||||||
d = self.data
|
d = self.data
|
||||||
box_h = 5
|
box_h = 5
|
||||||
draw_box(self.scr, y, x, box_h, w, "MEMORY")
|
draw_box(self.scr, y, x, box_h, w, t("memory"))
|
||||||
bar_w = w - 4
|
bar_w = w - 4
|
||||||
iy = y + 1
|
iy = y + 1
|
||||||
|
|
||||||
@@ -742,7 +837,7 @@ class Renderer:
|
|||||||
def _draw_network(self, y, x, w):
|
def _draw_network(self, y, x, w):
|
||||||
d = self.data
|
d = self.data
|
||||||
box_h = 6
|
box_h = 6
|
||||||
draw_box(self.scr, y, x, box_h, w, "NETWORK")
|
draw_box(self.scr, y, x, box_h, w, t("network"))
|
||||||
iy = y + 1
|
iy = y + 1
|
||||||
bar_w = w - 4
|
bar_w = w - 4
|
||||||
|
|
||||||
@@ -764,7 +859,7 @@ class Renderer:
|
|||||||
# Net total
|
# Net total
|
||||||
total = d.prev_net
|
total = d.prev_net
|
||||||
safe_addstr(self.scr, iy, x + 2,
|
safe_addstr(self.scr, iy, x + 2,
|
||||||
f"Total RX: {fmt_bytes_short(total.bytes_recv)} TX: {fmt_bytes_short(total.bytes_sent)}",
|
f"{t('total')} RX: {fmt_bytes_short(total.bytes_recv)} TX: {fmt_bytes_short(total.bytes_sent)}",
|
||||||
curses.color_pair(C_DIM))
|
curses.color_pair(C_DIM))
|
||||||
iy += 1
|
iy += 1
|
||||||
|
|
||||||
@@ -790,12 +885,12 @@ class Renderer:
|
|||||||
d = self.data
|
d = self.data
|
||||||
num_parts = min(len(d.partitions), 4)
|
num_parts = min(len(d.partitions), 4)
|
||||||
box_h = 3 + num_parts * 2
|
box_h = 3 + num_parts * 2
|
||||||
draw_box(self.scr, y, x, box_h, w, "DISK")
|
draw_box(self.scr, y, x, box_h, w, t("disk"))
|
||||||
iy = y + 1
|
iy = y + 1
|
||||||
bar_w = w - 4
|
bar_w = w - 4
|
||||||
|
|
||||||
# IO rates
|
# IO rates
|
||||||
io_str = f"Read: {fmt_bytes_short(d.disk_read_rate)}/s Write: {fmt_bytes_short(d.disk_write_rate)}/s"
|
io_str = f"{t('read')}: {fmt_bytes_short(d.disk_read_rate)}/s {t('write')}: {fmt_bytes_short(d.disk_write_rate)}/s"
|
||||||
safe_addstr(self.scr, iy, x + 2, io_str, curses.color_pair(C_ACCENT))
|
safe_addstr(self.scr, iy, x + 2, io_str, curses.color_pair(C_ACCENT))
|
||||||
iy += 1
|
iy += 1
|
||||||
|
|
||||||
@@ -814,7 +909,7 @@ class Renderer:
|
|||||||
def _draw_gpu(self, y, x, w):
|
def _draw_gpu(self, y, x, w):
|
||||||
d = self.data
|
d = self.data
|
||||||
box_h = 2 + len(d.gpus) * 4
|
box_h = 2 + len(d.gpus) * 4
|
||||||
draw_box(self.scr, y, x, box_h, w, "GPU")
|
draw_box(self.scr, y, x, box_h, w, t("gpu"))
|
||||||
iy = y + 1
|
iy = y + 1
|
||||||
bar_w = w - 4
|
bar_w = w - 4
|
||||||
|
|
||||||
@@ -858,7 +953,7 @@ class Renderer:
|
|||||||
return y
|
return y
|
||||||
|
|
||||||
box_h = min(2 + len(entries), 8)
|
box_h = min(2 + len(entries), 8)
|
||||||
draw_box(self.scr, y, x, box_h, w, "TEMPS")
|
draw_box(self.scr, y, x, box_h, w, t("temps"))
|
||||||
iy = y + 1
|
iy = y + 1
|
||||||
|
|
||||||
cols = max(1, w // 30)
|
cols = max(1, w // 30)
|
||||||
@@ -893,13 +988,13 @@ class Renderer:
|
|||||||
processes = pm.filtered_processes(d.processes)
|
processes = pm.filtered_processes(d.processes)
|
||||||
|
|
||||||
# Title with keybindings
|
# Title with keybindings
|
||||||
filter_info = f" filter: '{pm.filter_text}'" if pm.filter_text else ""
|
filter_info = f" {t('filter')}: '{pm.filter_text}'" if pm.filter_text else ""
|
||||||
title = f"PROCESSES [{d.process_sort}] F9:kill F7/F8:nice F5:details /:filter{filter_info}"
|
title = f"{t('processes')} [{d.process_sort}] F9:kill F7/F8:{t('hint_nice')} F5:{t('hint_details')} /:{t('hint_filter')}{filter_info}"
|
||||||
draw_box(self.scr, y, x, box_h, w, title)
|
draw_box(self.scr, y, x, box_h, w, title)
|
||||||
|
|
||||||
iy = y + 1
|
iy = y + 1
|
||||||
# Header
|
# Header
|
||||||
hdr = f"{'PID':>7s} {'USER':<10s} {'CPU%':>6s} {'MEM%':>6s} {'THR':>4s} {'RES':>8s} {'S':1s} {'NAME'}"
|
hdr = f"{'PID':>7s} {'USER':<10s} {'CPU%':>6s} {'MEM%':>6s} {'THR':>4s} {'RES':>8s} {'S':1s} {t('name')}"
|
||||||
safe_addstr(self.scr, iy, x + 1, hdr[:w - 2], curses.color_pair(C_HEADER))
|
safe_addstr(self.scr, iy, x + 1, hdr[:w - 2], curses.color_pair(C_HEADER))
|
||||||
iy += 1
|
iy += 1
|
||||||
|
|
||||||
@@ -946,7 +1041,7 @@ class Renderer:
|
|||||||
safe_addstr(self.scr, status_y, x + 2, status[:w - 4],
|
safe_addstr(self.scr, status_y, x + 2, status[:w - 4],
|
||||||
curses.color_pair(C_CRIT) | curses.A_BOLD)
|
curses.color_pair(C_CRIT) | curses.A_BOLD)
|
||||||
else:
|
else:
|
||||||
hint = "q:quit c/m/p/n:sort r:reverse /:filter K:KILL T:TERM F9:signals d:details +/-:nice"
|
hint = f"q:{t('hint_quit')} c/m/p/n:{t('hint_sort')} r:{t('hint_reverse')} /:{t('hint_filter')} K:KILL T:TERM F9:{t('hint_signals')} d:{t('hint_details')} +/-:{t('hint_nice')}"
|
||||||
safe_addstr(self.scr, status_y, x + 2, hint[:w - 4], curses.color_pair(C_DIM))
|
safe_addstr(self.scr, status_y, x + 2, hint[:w - 4], curses.color_pair(C_DIM))
|
||||||
|
|
||||||
# Scrollbar indicator
|
# Scrollbar indicator
|
||||||
@@ -982,7 +1077,7 @@ class Renderer:
|
|||||||
safe_addstr(self.scr, my + row, mx, " " * menu_w, curses.color_pair(C_POPUP_BG))
|
safe_addstr(self.scr, my + row, mx, " " * menu_w, curses.color_pair(C_POPUP_BG))
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
title = f" Send signal to {proc_name}({proc_pid}) "
|
title = f" {t('send_signal_to')} {proc_name}({proc_pid}) "
|
||||||
safe_addstr(self.scr, my, mx, title[:menu_w].center(menu_w),
|
safe_addstr(self.scr, my, mx, title[:menu_w].center(menu_w),
|
||||||
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
|
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
|
||||||
safe_addstr(self.scr, my + 1, mx, "\u2500" * menu_w, curses.color_pair(C_POPUP_BG))
|
safe_addstr(self.scr, my + 1, mx, "\u2500" * menu_w, curses.color_pair(C_POPUP_BG))
|
||||||
@@ -996,7 +1091,8 @@ class Renderer:
|
|||||||
safe_addstr(self.scr, row_y, mx + 1, f" {name} ".ljust(menu_w - 2),
|
safe_addstr(self.scr, row_y, mx + 1, f" {name} ".ljust(menu_w - 2),
|
||||||
curses.color_pair(C_POPUP_BG))
|
curses.color_pair(C_POPUP_BG))
|
||||||
|
|
||||||
safe_addstr(self.scr, my + menu_h - 1, mx, " Enter:send Esc:cancel ".center(menu_w),
|
safe_addstr(self.scr, my + menu_h - 1, mx,
|
||||||
|
f" Enter:{t('send')} Esc:{t('cancel')} ".center(menu_w),
|
||||||
curses.color_pair(C_POPUP_BG) | curses.A_DIM)
|
curses.color_pair(C_POPUP_BG) | curses.A_DIM)
|
||||||
|
|
||||||
def _draw_details_popup(self, y, x, w, avail_h):
|
def _draw_details_popup(self, y, x, w, avail_h):
|
||||||
@@ -1015,7 +1111,7 @@ class Renderer:
|
|||||||
for row in range(popup_h):
|
for row in range(popup_h):
|
||||||
safe_addstr(self.scr, my + row, mx, " " * popup_w, curses.color_pair(C_POPUP_BG))
|
safe_addstr(self.scr, my + row, mx, " " * popup_w, curses.color_pair(C_POPUP_BG))
|
||||||
|
|
||||||
title = f" Process Details: {details['name']} (PID {details['pid']}) "
|
title = f" {t('proc_details')}: {details['name']} (PID {details['pid']}) "
|
||||||
safe_addstr(self.scr, my, mx, title[:popup_w].center(popup_w),
|
safe_addstr(self.scr, my, mx, title[:popup_w].center(popup_w),
|
||||||
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
|
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
|
||||||
safe_addstr(self.scr, my + 1, mx, "\u2500" * popup_w, curses.color_pair(C_POPUP_BG))
|
safe_addstr(self.scr, my + 1, mx, "\u2500" * popup_w, curses.color_pair(C_POPUP_BG))
|
||||||
@@ -1027,8 +1123,8 @@ class Renderer:
|
|||||||
f" CPU: {details['cpu_percent']:.1f}% MEM: {details['memory_percent']:.1f}%",
|
f" CPU: {details['cpu_percent']:.1f}% MEM: {details['memory_percent']:.1f}%",
|
||||||
f" RSS: {details['rss']} VMS: {details['vms']}",
|
f" RSS: {details['rss']} VMS: {details['vms']}",
|
||||||
f" IO R: {details['io_read']} IO W: {details['io_write']}",
|
f" IO R: {details['io_read']} IO W: {details['io_write']}",
|
||||||
f" FDs: {details['num_fds']} Connections: {details['connections']}",
|
f" FDs: {details['num_fds']} {t('connections')}: {details['connections']}",
|
||||||
f" Created: {details['create_time']}",
|
f" {t('created')}: {details['create_time']}",
|
||||||
f" Exe: {details.get('exe', '?')}",
|
f" Exe: {details.get('exe', '?')}",
|
||||||
f" CWD: {details.get('cwd', '?')}",
|
f" CWD: {details.get('cwd', '?')}",
|
||||||
f" Cmd: {details.get('cmdline', '?')}",
|
f" Cmd: {details.get('cmdline', '?')}",
|
||||||
@@ -1038,7 +1134,8 @@ class Renderer:
|
|||||||
safe_addstr(self.scr, my + 2 + i, mx, line[:popup_w].ljust(popup_w),
|
safe_addstr(self.scr, my + 2 + i, mx, line[:popup_w].ljust(popup_w),
|
||||||
curses.color_pair(C_POPUP_BG))
|
curses.color_pair(C_POPUP_BG))
|
||||||
|
|
||||||
safe_addstr(self.scr, my + popup_h - 1, mx, " Press Esc or d to close ".center(popup_w),
|
safe_addstr(self.scr, my + popup_h - 1, mx,
|
||||||
|
f" Esc/d: {t('close')} ".center(popup_w),
|
||||||
curses.color_pair(C_POPUP_BG) | curses.A_DIM)
|
curses.color_pair(C_POPUP_BG) | curses.A_DIM)
|
||||||
|
|
||||||
def _draw_nice_dialog(self, y, x, w, avail_h):
|
def _draw_nice_dialog(self, y, x, w, avail_h):
|
||||||
@@ -1059,15 +1156,16 @@ class Renderer:
|
|||||||
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
|
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
|
||||||
safe_addstr(self.scr, my + 1, mx, "\u2500" * popup_w, curses.color_pair(C_POPUP_BG))
|
safe_addstr(self.scr, my + 1, mx, "\u2500" * popup_w, curses.color_pair(C_POPUP_BG))
|
||||||
safe_addstr(self.scr, my + 2, mx + 2,
|
safe_addstr(self.scr, my + 2, mx + 2,
|
||||||
f"Nice value (-20..19): {pm.nice_value}_".ljust(popup_w - 4),
|
f"{t('nice_value')}: {pm.nice_value}_".ljust(popup_w - 4),
|
||||||
curses.color_pair(C_POPUP_BG))
|
curses.color_pair(C_POPUP_BG))
|
||||||
safe_addstr(self.scr, my + 4, mx,
|
safe_addstr(self.scr, my + 4, mx,
|
||||||
" Enter:apply Esc:cancel ".center(popup_w),
|
f" Enter:{t('apply')} Esc:{t('cancel')} ".center(popup_w),
|
||||||
curses.color_pair(C_POPUP_BG) | curses.A_DIM)
|
curses.color_pair(C_POPUP_BG) | curses.A_DIM)
|
||||||
|
|
||||||
def _draw_help(self, h, w):
|
def _draw_help(self, h, w):
|
||||||
|
help_lines = get_help_lines()
|
||||||
popup_w = min(w - 4, 52)
|
popup_w = min(w - 4, 52)
|
||||||
popup_h = min(h - 2, len(HELP_LINES) + 4)
|
popup_h = min(h - 2, len(help_lines) + 4)
|
||||||
mx = (w - popup_w) // 2
|
mx = (w - popup_w) // 2
|
||||||
my = (h - popup_h) // 2
|
my = (h - popup_h) // 2
|
||||||
|
|
||||||
@@ -1076,12 +1174,12 @@ class Renderer:
|
|||||||
safe_addstr(self.scr, my + row, mx, " " * popup_w, curses.color_pair(C_POPUP_BG))
|
safe_addstr(self.scr, my + row, mx, " " * popup_w, curses.color_pair(C_POPUP_BG))
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
title = " SYSTATS - Keyboard Shortcuts "
|
title = f" {t('help_title')} "
|
||||||
safe_addstr(self.scr, my, mx, title.center(popup_w),
|
safe_addstr(self.scr, my, mx, title.center(popup_w),
|
||||||
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
|
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
|
||||||
safe_addstr(self.scr, my + 1, mx, "\u2500" * popup_w, curses.color_pair(C_POPUP_BG))
|
safe_addstr(self.scr, my + 1, mx, "\u2500" * popup_w, curses.color_pair(C_POPUP_BG))
|
||||||
|
|
||||||
for i, (key, desc) in enumerate(HELP_LINES):
|
for i, (key, desc) in enumerate(help_lines):
|
||||||
row_y = my + 2 + i
|
row_y = my + 2 + i
|
||||||
if row_y >= my + popup_h - 1:
|
if row_y >= my + popup_h - 1:
|
||||||
break
|
break
|
||||||
@@ -1097,12 +1195,12 @@ class Renderer:
|
|||||||
curses.color_pair(C_POPUP_BG))
|
curses.color_pair(C_POPUP_BG))
|
||||||
|
|
||||||
safe_addstr(self.scr, my + popup_h - 1, mx,
|
safe_addstr(self.scr, my + popup_h - 1, mx,
|
||||||
" Press h, F1 or ? to close ".center(popup_w),
|
f" {t('help_close')} ".center(popup_w),
|
||||||
curses.color_pair(C_POPUP_BG) | curses.A_DIM)
|
curses.color_pair(C_POPUP_BG) | curses.A_DIM)
|
||||||
|
|
||||||
def _draw_filter_bar(self, y, x, w):
|
def _draw_filter_bar(self, y, x, w):
|
||||||
pm = self.proc_mgr
|
pm = self.proc_mgr
|
||||||
bar = f" Filter: {pm.filter_text}_ "
|
bar = f" {t('filter_label')}: {pm.filter_text}_ "
|
||||||
safe_addstr(self.scr, y, x + 1, bar.ljust(w - 2)[:w - 2],
|
safe_addstr(self.scr, y, x + 1, bar.ljust(w - 2)[:w - 2],
|
||||||
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
|
curses.color_pair(C_POPUP_HL) | curses.A_BOLD)
|
||||||
|
|
||||||
@@ -1165,7 +1263,7 @@ def main(stdscr):
|
|||||||
val = max(-20, min(19, val))
|
val = max(-20, min(19, val))
|
||||||
pm.renice_selected(processes, val)
|
pm.renice_selected(processes, val)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pm._set_status("Invalid nice value")
|
pm._set_status(t("invalid_nice"))
|
||||||
pm.show_nice_dialog = False
|
pm.show_nice_dialog = False
|
||||||
pm.nice_value = ""
|
pm.nice_value = ""
|
||||||
elif key in (curses.KEY_BACKSPACE, 127, 8):
|
elif key in (curses.KEY_BACKSPACE, 127, 8):
|
||||||
@@ -1201,6 +1299,10 @@ def main(stdscr):
|
|||||||
break
|
break
|
||||||
elif key in (ord("h"), ord("?"), curses.KEY_F1):
|
elif key in (ord("h"), ord("?"), curses.KEY_F1):
|
||||||
renderer.show_help = True
|
renderer.show_help = True
|
||||||
|
elif key == ord("L"):
|
||||||
|
global current_lang
|
||||||
|
idx = LANGS.index(current_lang)
|
||||||
|
current_lang = LANGS[(idx + 1) % len(LANGS)]
|
||||||
elif key == ord("c"):
|
elif key == ord("c"):
|
||||||
data.process_sort = "cpu"
|
data.process_sort = "cpu"
|
||||||
data.process_sort_reverse = True
|
data.process_sort_reverse = True
|
||||||
@@ -1260,6 +1362,12 @@ def main(stdscr):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser(description="systats - Terminal system monitor | Electric Entropy Lab")
|
||||||
|
parser.add_argument("--lang", choices=LANGS, default="en",
|
||||||
|
help="UI language: en (English) or pl (Polski)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
current_lang = args.lang
|
||||||
try:
|
try:
|
||||||
curses.wrapper(main)
|
curses.wrapper(main)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|||||||
Reference in New Issue
Block a user