mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-26 17:12:35 +00:00
fix(kconfig): Repair menuconfig help buttons and preserve pending toggles
The configurator had four defects that made buttons and selection behave
erratically in `make configure`.
lxdialog front-end:
- Pressing <Help> in a subsystem menu discarded every toggle made since the
menu was opened, because dialog reports only the focused tag and the result
was thrown away. Pass --help-status and fold the returned state back into
the config before showing help.
- <Help> always showed the generic subsystem blurb even though all 41 options
carry their own help text. Resolve the focused tag and show that item's help.
- The main menu prompt promised <Exit> but dialog rendered <Cancel>; label the
button to match.
curses fallback:
- The Help button raised AttributeError and killed the UI at top level: the
root node is the MENU_TREE list itself, and the fallback argument
`current_menu.get("help", ...)` is evaluated eagerly even when the item has
its own help.
- The '?' hotkey was nested inside the <Enter> branch where `key` can only be
a newline, so it was unreachable dead code.
- The button-bar spacing went negative on a narrow dialog and pushed the last
buttons through the right border; centre and clamp the row instead.
Front-end selection no longer swallows exceptions silently, so a crash reports
itself instead of looking like an unresponsive UI.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0161Jh7uCEMLYEMNPsjC1LKf
This commit is contained in:
1 parent
4d9223c8cc
commit
7929e48b4e
1 file changed
+123
-33
+123
-33
@@ -282,6 +282,59 @@ def save_config(cfg):
|
||||
# -----------------------------------------------------------------------------
|
||||
# Linux lxdialog Runner (Uses /usr/bin/dialog for authentic Linux GUI Look)
|
||||
# -----------------------------------------------------------------------------
|
||||
def _help_tokens(payload):
|
||||
"""Split a dialog --help-button payload into bare tags.
|
||||
|
||||
dialog emits "HELP <focused-tag>" and, with --help-status, appends the
|
||||
tags that are currently on. Quoting depends on the dialog build, so the
|
||||
tokens are unquoted here rather than at each call site.
|
||||
"""
|
||||
tokens = [t.strip('"') for t in payload.split()]
|
||||
if tokens and tokens[0] == "HELP":
|
||||
tokens = tokens[1:]
|
||||
return tokens
|
||||
|
||||
|
||||
def _help_tag(payload):
|
||||
"""Return just the focused tag from a dialog help payload."""
|
||||
tokens = _help_tokens(payload)
|
||||
return tokens[0] if tokens else None
|
||||
|
||||
|
||||
def _apply_selection(cfg, items, selected_tags, is_radiolist):
|
||||
"""Fold a dialog checklist/radiolist result back into the config dict."""
|
||||
selected = {t.strip('"') for t in selected_tags}
|
||||
if is_radiolist:
|
||||
sel_item = next((i for i in items if i["id"] in selected), None)
|
||||
if not sel_item:
|
||||
return
|
||||
group = sel_item.get("group")
|
||||
if group == "arch":
|
||||
cfg["CONFIG_ARCH"] = sel_item["val"]
|
||||
cfg["CONFIG_ARCH_X86_64"] = (sel_item["val"] == "x86_64")
|
||||
cfg["CONFIG_ARCH_AARCH64"] = (sel_item["val"] == "aarch64")
|
||||
cfg["CONFIG_ARCH_ARMV8I"] = (sel_item["val"] == "armv8i")
|
||||
elif group == "opt":
|
||||
cfg["CONFIG_OPTIMIZATION"] = sel_item["val"]
|
||||
else:
|
||||
# Only fold in the toggleable rows; anything else in the menu keeps
|
||||
# whatever value it already had.
|
||||
for item in items:
|
||||
if item.get("type") == "bool":
|
||||
cfg[item["id"]] = (item["id"] in selected)
|
||||
|
||||
|
||||
def _menu_help(menu, default="No help available."):
|
||||
"""Help text for a menu node.
|
||||
|
||||
The curses browser uses the MENU_TREE list itself as the root node, so
|
||||
this has to tolerate a list as well as a menu dict.
|
||||
"""
|
||||
if isinstance(menu, dict):
|
||||
return menu.get("help", default)
|
||||
return default
|
||||
|
||||
|
||||
def run_dialog_gui():
|
||||
cfg = load_config()
|
||||
backtitle = "SUB-OS v0.2.0-lts Modular Monolithic Linux Kernel Configuration"
|
||||
@@ -301,6 +354,7 @@ def run_dialog_gui():
|
||||
"--backtitle", backtitle,
|
||||
"--title", "SUB-OS Kernel Configuration",
|
||||
"--extra-button", "--extra-label", "Save",
|
||||
"--cancel-label", "Exit",
|
||||
"--help-button",
|
||||
"--menu",
|
||||
"Arrow keys navigate the menu. <Enter> selects submenus --->.\n"
|
||||
@@ -330,8 +384,20 @@ def run_dialog_gui():
|
||||
msg_cmd = ["dialog", "--backtitle", backtitle, "--msgbox", f"Configuration saved successfully to {CONFIG_FILE} and {AUTOCONF_HEADER}!", "7", "65"]
|
||||
subprocess.run(msg_cmd)
|
||||
elif ret == 2: # Help button
|
||||
help_text = "SUB-OS Kernel Configuration Utility (lxdialog GUI)\n\nNavigate through each subsystem and configure features according to your target hardware needs."
|
||||
subprocess.run(["dialog", "--backtitle", backtitle, "--title", "Main Menu Help", "--msgbox", help_text, "12", "70"])
|
||||
# dialog reports the focused row as "HELP <tag>"; show that
|
||||
# subsystem's own help rather than one generic blurb.
|
||||
focus = _help_tag(sel)
|
||||
focused = next((m for m in MENU_TREE if m["id"] == focus), None)
|
||||
if focused:
|
||||
help_title = f"{focused['title']} - Help"
|
||||
help_text = focused.get("help", "No additional help available for this subsystem.")
|
||||
else:
|
||||
help_title = "Main Menu Help"
|
||||
help_text = ("SUB-OS Kernel Configuration Utility (lxdialog GUI)\n\n"
|
||||
"Navigate through each subsystem and configure features "
|
||||
"according to your target hardware needs.")
|
||||
subprocess.run(["dialog", "--backtitle", backtitle, "--title", help_title,
|
||||
"--msgbox", help_text, "16", "72"])
|
||||
|
||||
else:
|
||||
# Submenu Display
|
||||
@@ -367,6 +433,7 @@ def run_dialog_gui():
|
||||
"--backtitle", backtitle,
|
||||
"--title", submenu["title"],
|
||||
"--help-button",
|
||||
"--help-status",
|
||||
dialog_type,
|
||||
"Use <Space> to toggle features [*], <Enter> to confirm, <Help> for info.",
|
||||
"20", "78", "10"
|
||||
@@ -377,31 +444,28 @@ def run_dialog_gui():
|
||||
out = res.stderr.strip()
|
||||
|
||||
if ret == 0: # OK / Confirm
|
||||
selected_tags = [t.strip('"') for t in out.split()]
|
||||
if is_radiolist:
|
||||
if selected_tags:
|
||||
sel_id = selected_tags[0]
|
||||
sel_item = next((i for i in items if i["id"] == sel_id), None)
|
||||
if sel_item:
|
||||
group = sel_item.get("group")
|
||||
if group == "arch":
|
||||
cfg["CONFIG_ARCH"] = sel_item["val"]
|
||||
cfg["CONFIG_ARCH_X86_64"] = (sel_item["val"] == "x86_64")
|
||||
cfg["CONFIG_ARCH_AARCH64"] = (sel_item["val"] == "aarch64")
|
||||
cfg["CONFIG_ARCH_ARMV8I"] = (sel_item["val"] == "armv8i")
|
||||
elif group == "opt":
|
||||
cfg["CONFIG_OPTIMIZATION"] = sel_item["val"]
|
||||
else:
|
||||
for item in items:
|
||||
cfg[item["id"]] = (item["id"] in selected_tags)
|
||||
_apply_selection(cfg, items, [t.strip('"') for t in out.split()], is_radiolist)
|
||||
current_menu_id = "main"
|
||||
|
||||
elif ret == 1 or ret == 255: # Cancel or Esc
|
||||
current_menu_id = "main"
|
||||
|
||||
elif ret == 2: # Help button
|
||||
# With --help-status the payload is "HELP <focused-tag> <on-tags...>".
|
||||
# Re-apply the on-tags first so the toggles made before pressing
|
||||
# <Help> survive the round trip, then show the focused item's help.
|
||||
tokens = _help_tokens(out)
|
||||
focus = tokens[0] if tokens else None
|
||||
_apply_selection(cfg, items, tokens[1:], is_radiolist)
|
||||
focused = next((i for i in items if i["id"] == focus), None)
|
||||
if focused:
|
||||
help_title = f"{focused['label']} - Help"
|
||||
help_text = focused.get("help", "No additional help available for this option.")
|
||||
else:
|
||||
help_title = f"{submenu['title']} - Help"
|
||||
help_text = submenu.get("help", "No additional help available for this subsystem.")
|
||||
subprocess.run(["dialog", "--backtitle", backtitle, "--title", f"{submenu['title']} - Help", "--msgbox", help_text, "14", "72"])
|
||||
subprocess.run(["dialog", "--backtitle", backtitle, "--title", help_title,
|
||||
"--msgbox", help_text, "16", "72"])
|
||||
|
||||
save_config(cfg)
|
||||
print(f"*** Configuration written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***")
|
||||
@@ -564,15 +628,21 @@ def run_curses_gui(stdscr):
|
||||
|
||||
# 6. Bottom Action Button Bar
|
||||
btn_y = dy + dh - 2
|
||||
btn_spacing = (dw - sum(len(b) + 4 for b in buttons)) // (len(buttons) + 1)
|
||||
cur_bx = dx + btn_spacing
|
||||
btn_texts = [f"< {b} >" for b in buttons]
|
||||
# Centre the row and clamp it: the old spacing maths went negative on a
|
||||
# narrow dialog and pushed the last buttons through the right border.
|
||||
total_w = sum(len(t) for t in btn_texts)
|
||||
gap = max(1, (dw - 4 - total_w) // max(1, len(btn_texts) - 1))
|
||||
row_w = total_w + gap * (len(btn_texts) - 1)
|
||||
cur_bx = dx + max(2, (dw - row_w) // 2)
|
||||
|
||||
for b_idx, btn_name in enumerate(buttons):
|
||||
for b_idx, b_text in enumerate(btn_texts):
|
||||
if cur_bx + len(b_text) > dx + dw - 2:
|
||||
break
|
||||
is_active_btn = (b_idx == btn_cursor)
|
||||
b_text = f"< {btn_name} >"
|
||||
b_attr = (curses.color_pair(6) | curses.A_BOLD) if (is_active_btn and has_color) else (curses.A_REVERSE if is_active_btn else box_attr)
|
||||
stdscr.addstr(btn_y, cur_bx, b_text, b_attr)
|
||||
cur_bx += len(b_text) + max(2, btn_spacing)
|
||||
cur_bx += len(b_text) + gap
|
||||
|
||||
stdscr.refresh()
|
||||
try:
|
||||
@@ -641,10 +711,8 @@ def run_curses_gui(stdscr):
|
||||
else:
|
||||
save_config(cfg)
|
||||
break
|
||||
elif btn_cursor == 2 or key == ord('?'): # Help
|
||||
sel = items[cursor] if cursor < len(items) else None
|
||||
help_msg = sel.get("help", current_menu.get("help", "No help available.")) if sel else "SUB-OS Kernel Configurator"
|
||||
show_popup(stdscr, "Option Help", help_msg, max_y, max_x)
|
||||
elif btn_cursor == 2: # Help
|
||||
_show_help(stdscr, current_menu, items, cursor, max_y, max_x)
|
||||
elif btn_cursor == 3: # Save
|
||||
save_config(cfg)
|
||||
show_popup(stdscr, "Save Configuration", f"Configuration successfully saved to {CONFIG_FILE} and {AUTOCONF_HEADER}!", max_y, max_x)
|
||||
@@ -652,6 +720,10 @@ def run_curses_gui(stdscr):
|
||||
cfg = dict(DEFAULTS)
|
||||
save_config(cfg)
|
||||
show_popup(stdscr, "Reset Defaults", "Default configuration options loaded!", max_y, max_x)
|
||||
elif key == ord('?'):
|
||||
# Previously nested under the <Enter> branch, where it could never
|
||||
# match, so the documented '?' help hotkey did nothing.
|
||||
_show_help(stdscr, current_menu, items, cursor, max_y, max_x)
|
||||
elif key in [ord('s'), ord('S')]:
|
||||
save_config(cfg)
|
||||
show_popup(stdscr, "Save Configuration", f"Configuration successfully saved to {CONFIG_FILE} and {AUTOCONF_HEADER}!", max_y, max_x)
|
||||
@@ -662,6 +734,17 @@ def run_curses_gui(stdscr):
|
||||
save_config(cfg)
|
||||
break
|
||||
|
||||
def _show_help(stdscr, current_menu, items, cursor, max_y, max_x):
|
||||
"""Show help for the focused row, falling back to the enclosing menu."""
|
||||
sel = items[cursor] if 0 <= cursor < len(items) else None
|
||||
if sel is None:
|
||||
show_popup(stdscr, "Help", _menu_help(current_menu, "SUB-OS Kernel Configurator"), max_y, max_x)
|
||||
return
|
||||
title = sel.get("label") or sel.get("title") or "Option"
|
||||
help_msg = sel.get("help") or _menu_help(current_menu)
|
||||
show_popup(stdscr, f"{title} - Help", help_msg, max_y, max_x)
|
||||
|
||||
|
||||
def show_popup(stdscr, title, message, max_y, max_x):
|
||||
pw = min(64, max_x - 6)
|
||||
ph = min(12, max_y - 6)
|
||||
@@ -740,16 +823,23 @@ def main():
|
||||
try:
|
||||
run_dialog_gui()
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Falling through silently used to make a crash look like the UI
|
||||
# simply refusing to respond; say what happened before retrying.
|
||||
print(f"menuconfig: lxdialog front-end failed ({exc}); "
|
||||
f"falling back to the curses UI.", file=sys.stderr)
|
||||
|
||||
# Fallback to Curses GUI replica
|
||||
try:
|
||||
import curses
|
||||
curses.wrapper(run_curses_gui)
|
||||
print(f"*** Configuration saved to {CONFIG_FILE} and {AUTOCONF_HEADER} ***")
|
||||
except Exception:
|
||||
# Fallback if no TTY
|
||||
return
|
||||
except Exception as exc:
|
||||
print(f"menuconfig: curses UI unavailable ({exc}); "
|
||||
f"writing the current configuration non-interactively.", file=sys.stderr)
|
||||
|
||||
# Last resort: no usable TTY at all.
|
||||
cfg = load_config()
|
||||
save_config(cfg)
|
||||
print(f"*** Configuration written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***")
|
||||
|
||||
Reference in new issue
Block a user