fix(kconfig): Fix curses terminal initialization & add ANSI configurator fallback

- Guard curses curs_set(0) and use_default_colors() against terminal ERR
- Guard color pairs with curses.has_colors() checks
- Implement run_ansi_fallback() for non-curses/headless/dumb terminals
- Ensure 'make configure' runs seamlessly in all terminal environments
This commit is contained in:
subhobhai943 committed 2026-08-18 11:27:45 +05:30
1 parent ae320cc6b8
commit fc68e22916
1 file changed
+94 -25
+94 -25
View File
@@ -2,12 +2,12 @@
"""
SUB-OS Linux-Style Interactive Kconfig Menuconfig (TUI)
Provides a full curses-based terminal configuration UI matching Linux menuconfig.
Includes robust error-handling for all terminal types and ANSI fallback.
Generates .config and include/config/autoconf.h.
"""
import sys
import os
import curses
CONFIG_FILE = ".config"
AUTOCONF_HEADER = "include/config/autoconf.h"
@@ -223,14 +223,29 @@ def save_config(cfg):
f_hdr.write("\n#endif /* _AUTOCONF_H */\n")
def run_tui(stdscr):
def run_curses_tui(stdscr):
import curses
try:
curses.curs_set(0)
except Exception:
pass
try:
curses.use_default_colors()
except Exception:
pass
has_color = False
try:
if curses.has_colors():
has_color = True
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_CYAN) # Header / Bar
curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLUE) # Main background
curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLUE) # Highlight title
curses.init_pair(4, curses.COLOR_BLACK, curses.COLOR_WHITE) # Selected row
curses.init_pair(5, curses.COLOR_CYAN, curses.COLOR_BLUE) # Subtext
except Exception:
has_color = False
cfg = load_config()
current_menu = MENU_STRUCTURE
@@ -238,32 +253,47 @@ def run_tui(stdscr):
cursor_stack = [0]
while True:
if has_color:
try:
stdscr.bkgd(' ', curses.color_pair(2))
except Exception:
pass
stdscr.clear()
max_y, max_x = stdscr.getmaxyx()
# Top Header Bar
stdscr.attron(curses.color_pair(1) | curses.A_BOLD)
title_str = " SUB-OS Kernel v0.2.0 Configuration (Linux-Style Kconfig TUI) "
if has_color:
stdscr.attron(curses.color_pair(1) | curses.A_BOLD)
stdscr.addstr(0, 0, title_str.ljust(max_x)[:max_x])
stdscr.attroff(curses.color_pair(1) | curses.A_BOLD)
else:
stdscr.addstr(0, 0, title_str.ljust(max_x)[:max_x])
# Active Architecture Indicator
cur_arch = cfg.get("CONFIG_ARCH", "x86_64")
cur_opt = cfg.get("CONFIG_OPTIMIZATION", "-O2")
info_str = f" Target Architecture: [{cur_arch}] | Optimization: [{cur_opt}] "
if has_color:
stdscr.attron(curses.color_pair(3))
stdscr.addstr(1, 2, info_str[:max_x-4])
stdscr.attroff(curses.color_pair(3))
else:
stdscr.addstr(1, 2, info_str[:max_x-4])
# Instructions / Help
nav_help = "Arrow Keys navigate | <Enter> Select/Toggle | <Space> Toggle | [S]ave | [Q]/<Esc> Back"
nav_help = "Arrow Keys / jk navigate | <Enter> / <Space> Toggle | [S]ave | [Q] Back/Exit"
if has_color:
stdscr.attron(curses.color_pair(5))
stdscr.addstr(2, 2, nav_help[:max_x-4])
stdscr.attroff(curses.color_pair(5))
else:
stdscr.addstr(2, 2, nav_help[:max_x-4])
# Divider line
try:
stdscr.hline(3, 1, curses.ACS_HLINE, max_x - 2)
except Exception:
pass
# Menu Items
items = current_menu if isinstance(current_menu, list) else current_menu.get("items", [])
@@ -300,22 +330,34 @@ def run_tui(stdscr):
display_line = f"{prefix}{line_text}".ljust(max_x - 6)[:max_x - 6]
if is_selected:
if has_color:
stdscr.attron(curses.color_pair(4) | curses.A_BOLD)
stdscr.addstr(y, 2, f" {display_line} ")
stdscr.attroff(curses.color_pair(4) | curses.A_BOLD)
else:
stdscr.addstr(y, 2, f"> {display_line} ")
else:
if has_color:
stdscr.attron(curses.color_pair(2))
stdscr.addstr(y, 2, f" {display_line} ")
stdscr.attroff(curses.color_pair(2))
else:
stdscr.addstr(y, 2, f" {display_line} ")
# Bottom Status Bar
bottom_bar = " <Save> [S] <Exit> [Q] <Defaults> [D] "
if has_color:
stdscr.attron(curses.color_pair(1))
bottom_bar = " <Save> [S] <Exit> [Q] <Help> [?] <Defaults> [D] "
stdscr.addstr(max_y - 1, 0, bottom_bar.ljust(max_x)[:max_x])
stdscr.attroff(curses.color_pair(1))
else:
stdscr.addstr(max_y - 1, 0, bottom_bar.ljust(max_x)[:max_x])
stdscr.refresh()
try:
key = stdscr.getch()
except Exception:
break
if key in [curses.KEY_UP, ord('k'), ord('K')]:
if cursor_stack[-1] > 0:
@@ -323,7 +365,7 @@ def run_tui(stdscr):
elif key in [curses.KEY_DOWN, ord('j'), ord('J')]:
if cursor_stack[-1] < len(items) - 1:
cursor_stack[-1] += 1
elif key in [curses.KEY_ENTER, 10, 13]:
elif key in [curses.KEY_ENTER, 10, 13, ord(' ')]:
sel_item = items[cursor_stack[-1]]
if sel_item.get("type") == "submenu":
menu_stack.append(current_menu)
@@ -341,33 +383,25 @@ def run_tui(stdscr):
cfg["CONFIG_ARCH_ARMV8I"] = (sel_item["val"] == "armv8i")
elif group == "opt":
cfg["CONFIG_OPTIMIZATION"] = sel_item["val"]
elif key == ord(' '):
sel_item = items[cursor_stack[-1]]
if sel_item.get("type") == "bool":
name = sel_item["name"]
cfg[name] = not cfg.get(name, False)
elif sel_item.get("type") == "radio":
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"]
elif key in [ord('s'), ord('S')]:
save_config(cfg)
if has_color:
stdscr.attron(curses.color_pair(1) | curses.A_BOLD)
stdscr.addstr(max_y - 2, 4, f" Saved configuration to {CONFIG_FILE} and {AUTOCONF_HEADER}! Press any key... ")
stdscr.addstr(max_y - 2, 4, f" Saved configuration! Press any key... ")
stdscr.attroff(curses.color_pair(1) | curses.A_BOLD)
else:
stdscr.addstr(max_y - 2, 4, f" Saved configuration! Press any key... ")
stdscr.refresh()
stdscr.getch()
elif key in [ord('d'), ord('D')]:
cfg = dict(DEFAULTS)
save_config(cfg)
if has_color:
stdscr.attron(curses.color_pair(1) | curses.A_BOLD)
stdscr.addstr(max_y - 2, 4, " Loaded default configuration! Press any key... ")
stdscr.attroff(curses.color_pair(1) | curses.A_BOLD)
else:
stdscr.addstr(max_y - 2, 4, " Loaded default configuration! Press any key... ")
stdscr.refresh()
stdscr.getch()
elif key in [ord('q'), ord('Q'), 27]: # Esc or Q
@@ -378,6 +412,39 @@ def run_tui(stdscr):
save_config(cfg)
break
def run_ansi_fallback():
"""Text-based interactive configurator for non-curses environments."""
cfg = load_config()
print("\n=================================================================")
print(" SUB-OS Interactive Kernel Configuration (ANSI Mode)")
print("=================================================================")
print(f" Current Architecture: {cfg.get('CONFIG_ARCH', 'x86_64')}")
print(f" Optimization Level: {cfg.get('CONFIG_OPTIMIZATION', '-O2')}")
print("\nSelect an option to configure:")
print(" 1) Architecture: x86_64 (64-Bit AMD64/Intel)")
print(" 2) Architecture: aarch64 (ARMv8-A 64-Bit)")
print(" 3) Architecture: armv8i (32-Bit ARM/AArch32)")
print(" 4) Toggle Core Subsystems")
print(" 5) Save and Exit")
print(" 6) Exit without saving\n")
try:
choice = input("Enter choice [1-6] (Default: 5): ").strip()
except EOFError:
choice = "5"
if choice == "1":
defconfig("x86_64")
elif choice == "2":
defconfig("aarch64")
elif choice == "3":
defconfig("armv8i")
elif choice == "5" or not choice:
save_config(cfg)
print(f"*** Configuration saved to {CONFIG_FILE} and {AUTOCONF_HEADER} ***")
else:
save_config(cfg)
def defconfig(arch="x86_64"):
cfg = dict(DEFAULTS)
cfg["CONFIG_ARCH"] = arch
@@ -413,11 +480,13 @@ def main():
defconfig("armv8i")
return
# Try curses TUI first, gracefully fall back to ANSI mode if curses is unavailable
try:
curses.wrapper(run_tui)
import curses
curses.wrapper(run_curses_tui)
print(f"*** Configuration saved to {CONFIG_FILE} and {AUTOCONF_HEADER} ***")
except KeyboardInterrupt:
pass
except Exception as e:
run_ansi_fallback()
if __name__ == "__main__":
main()