#!/usr/bin/env python3 """ SUB-OS Linux Kernel-Style Interactive Kconfig Configurator (lxdialog TUI/GUI) Recreates the exact visual appearance, colors, floating dialogs, drop shadows, and interactive controls of the Linux kernel 'make menuconfig' system. """ import sys import os import shutil import subprocess CONFIG_FILE = ".config" AUTOCONF_HEADER = "include/config/autoconf.h" DEFAULTS = { # Architecture "CONFIG_ARCH": "x86_64", "CONFIG_ARCH_X86_64": True, "CONFIG_ARCH_AARCH64": False, "CONFIG_ARCH_ARMV8I": False, # General Setup "CONFIG_SUBOS_HOSTNAME": "sub-os", "CONFIG_SUBOS_VERSION": "0.2.0-lts", "CONFIG_SMP": True, "CONFIG_PREEMPT": True, "CONFIG_OPTIMIZATION": "-O2", # Memory Management "CONFIG_MM_PMM": True, "CONFIG_MM_SLAB": True, "CONFIG_MM_VMA": True, # Filesystems "CONFIG_FS_VFS": True, "CONFIG_FS_FAT32": True, "CONFIG_FS_EXT2": True, "CONFIG_FS_RAMFS": True, "CONFIG_FS_DEVFS": True, "CONFIG_FS_PROCFS": True, "CONFIG_FS_SYSFS": True, # Storage Drivers "CONFIG_DRV_NVME": True, "CONFIG_DRV_AHCI": True, "CONFIG_DRV_ATA": True, "CONFIG_DRV_VIRTIO_BLK": True, "CONFIG_DRV_RAMDISK": True, # Network Drivers & Stack "CONFIG_NET_STACK": True, "CONFIG_DRV_E1000": True, "CONFIG_DRV_RTL8139": True, "CONFIG_DRV_VIRTIO_NET": True, "CONFIG_NET_HTTPD": True, "CONFIG_NET_SSHD": True, "CONFIG_NET_FILTER": True, # Audio & Display "CONFIG_DRV_SOUND_HDA": True, "CONFIG_DRV_SOUND_AC97": True, "CONFIG_DRV_BOCHS_VBE": True, "CONFIG_DRV_CANVAS_2D": True, # Bus, Sensors & Crypto "CONFIG_DRV_USB_XHCI": True, "CONFIG_DRV_HWMON": True, "CONFIG_DRV_VIRTIO_RNG": True, "CONFIG_DRV_PTY": True, # Security & Advanced "CONFIG_SECURITY_LSM": True, "CONFIG_SECURITY_KEYRING": True, "CONFIG_BPF_VM": True, "CONFIG_IO_URING": True, "CONFIG_USERLAND_LAZYBOX": True, } MENU_TREE = [ { "id": "arch", "title": "Target architecture selection", "help": "Choose the target CPU instruction set architecture.\n\n" " x86_64: 64-bit AMD64 / Intel 64 PC architecture.\n" " aarch64: ARMv8-A 64-bit Cortex/Neoverse architecture.\n" " armv8i: 32-bit ARM (AArch32) architecture.\n\n" "Symbol: CONFIG_ARCH", "items": [ {"id": "CONFIG_ARCH_X86_64", "tag": "x86_64", "label": "x86_64 (64-Bit AMD64 / Intel 64)", "type": "radio", "group": "arch", "val": "x86_64", "help": "Builds for 64-bit x86 PCs with GDT, IDT, PIC, PIT, and Multiboot loader."}, {"id": "CONFIG_ARCH_AARCH64", "tag": "aarch64", "label": "aarch64 (ARMv8-A 64-Bit Cortex-A57/Neoverse)", "type": "radio", "group": "arch", "val": "aarch64", "help": "Builds for 64-bit ARM with GICv2, Generic Timer, and 39-bit MMU."}, {"id": "CONFIG_ARCH_ARMV8I", "tag": "armv8i", "label": "armv8i (32-Bit ARM / AArch32 Profile)", "type": "radio", "group": "arch", "val": "armv8i", "help": "Builds for 32-bit ARM with VMSAv7 Section MMU, GICv2, and VFP/NEON."}, ] }, { "id": "opt", "title": "Compiler optimizations and code generation", "help": "Select GCC optimization level for kernel compilation.\n\n" " -O2: Recommended balanced speed and size optimization.\n" " -O3: Aggressive auto-vectorization and loop unrolling.\n" " -Os: Minimal binary footprint.\n" " -O0: Debugging mode without register optimizations.\n\n" "Symbol: CONFIG_OPTIMIZATION", "items": [ {"id": "CONFIG_OPT_O2", "tag": "O2", "label": "-O2 (High performance, recommended)", "type": "radio", "group": "opt", "val": "-O2", "help": "Standard kernel optimization level."}, {"id": "CONFIG_OPT_O3", "tag": "O3", "label": "-O3 (Aggressive vectorization & loop unrolling)", "type": "radio", "group": "opt", "val": "-O3", "help": "Maximum speed optimization."}, {"id": "CONFIG_OPT_OS", "tag": "Os", "label": "-Os (Optimize for minimal binary size)", "type": "radio", "group": "opt", "val": "-Os", "help": "Smallest kernel footprint."}, {"id": "CONFIG_OPT_O0", "tag": "O0", "label": "-O0 (Disable optimizations for GDB debugging)", "type": "radio", "group": "opt", "val": "-O0", "help": "Unoptimized debug build."}, ] }, { "id": "general", "title": "General kernel setup and core subsystems", "help": "Configure core multitasking, synchronization, and kernel execution parameters.", "items": [ {"id": "CONFIG_SMP", "tag": "SMP", "label": "Symmetric Multi-Processing (SMP) support", "type": "bool", "help": "Enables multi-core scheduler and per-CPU load balancing."}, {"id": "CONFIG_PREEMPT", "tag": "PREEMPT", "label": "Preemptive kernel scheduling model", "type": "bool", "help": "Allows low-latency preemption for real-time task responsiveness."}, {"id": "CONFIG_BPF_VM", "tag": "BPF_VM", "label": "In-Kernel eBPF register virtual machine", "type": "bool", "help": "Enables in-kernel safe bytecode execution for tracing and network filters."}, {"id": "CONFIG_IO_URING", "tag": "IO_URING", "label": "High-performance io_uring ring engine", "type": "bool", "help": "Provides zero-syscall asynchronous submission and completion queues."}, ] }, { "id": "mm", "title": "Memory management & object allocators", "help": "Configure physical memory management, slab caches, and virtual address spaces.", "items": [ {"id": "CONFIG_MM_PMM", "tag": "MM_PMM", "label": "Physical Page Frame Allocator (PMM)", "type": "bool", "help": "4KB bitmap-backed physical page frame allocator."}, {"id": "CONFIG_MM_SLAB", "tag": "MM_SLAB", "label": "SLUB / SLAB high-speed object cache", "type": "bool", "help": "Fast object caches for kernel structures (VFS inodes, sockets, tasks)."}, {"id": "CONFIG_MM_VMA", "tag": "MM_VMA", "label": "Virtual Memory Areas (VMA) and mmap subsystem", "type": "bool", "help": "Process address space mappings and demand paging."}, ] }, { "id": "fs", "title": "Virtual File Systems & Storage Device Drivers", "help": "Configure storage hardware controllers and mounted file system drivers.", "items": [ {"id": "CONFIG_FS_VFS", "tag": "FS_VFS", "label": "Virtual File System (VFS) core framework", "type": "bool", "help": "Provides POSIX open, read, write, close, and mount abstraction."}, {"id": "CONFIG_FS_FAT32", "tag": "FS_FAT32", "label": "FAT32 / VFAT file system driver", "type": "bool", "help": "High-performance FAT32 filesystem driver mounted at /mnt/fat32."}, {"id": "CONFIG_FS_EXT2", "tag": "FS_EXT2", "label": "Second Extended (EXT2) Linux file system", "type": "bool", "help": "Standard Linux EXT2 filesystem driver with direct inode indexing."}, {"id": "CONFIG_FS_SYSFS", "tag": "FS_SYSFS", "label": "Sysfs KObject virtual hierarchy (/sys)", "type": "bool", "help": "Exports kernel device tree and runtime telemetry to /sys."}, {"id": "CONFIG_DRV_NVME", "tag": "DRV_NVME", "label": "NVM Express (NVMe PCIe SSD) driver", "type": "bool", "help": "High-throughput PCIe NVMe solid-state storage driver."}, {"id": "CONFIG_DRV_AHCI", "tag": "DRV_AHCI", "label": "AHCI SATA 6Gb/s controller driver", "type": "bool", "help": "Serial ATA AHCI controller driver for hard drives and SSDs."}, {"id": "CONFIG_DRV_ATA", "tag": "DRV_ATA", "label": "Legacy Parallel ATA / IDE storage driver", "type": "bool", "help": "IDE / PATA drive support for legacy hardware."}, {"id": "CONFIG_DRV_VIRTIO_BLK", "tag": "DRV_VIRTIO_BLK", "label": "VirtIO paravirtualized block device (/dev/vda)", "type": "bool", "help": "Fast virtual storage device for QEMU / KVM hypervisors."}, {"id": "CONFIG_DRV_RAMDISK", "tag": "DRV_RAMDISK", "label": "RAM disk block driver (/dev/ram0)", "type": "bool", "help": "In-memory dynamic block storage for initrd and testing."}, ] }, { "id": "net", "title": "Networking subsystem & hardware adapters", "help": "Configure network stack protocols, packet filtering, and ethernet adapters.", "items": [ {"id": "CONFIG_NET_STACK", "tag": "NET_STACK", "label": "TCP/IP protocol stack (IPv4, UDP, TCP)", "type": "bool", "help": "Kernel socket networking stack with ARP, DHCP, DNS, UDP, and TCP."}, {"id": "CONFIG_DRV_E1000", "tag": "DRV_E1000", "label": "Intel 82540EM Gigabit Ethernet NIC driver", "type": "bool", "help": "Standard Intel Gigabit PCI network interface card driver."}, {"id": "CONFIG_DRV_RTL8139", "tag": "DRV_RTL8139", "label": "Realtek RTL8139 Fast Ethernet NIC driver", "type": "bool", "help": "100Mbps Realtek PCI network interface card driver."}, {"id": "CONFIG_DRV_VIRTIO_NET", "tag": "DRV_VIRTIO_NET", "label": "VirtIO 10-Gigabit paravirtualized NIC", "type": "bool", "help": "Paravirtualized network adapter with zero-copy TX/RX rings."}, {"id": "CONFIG_NET_FILTER", "tag": "NET_FILTER", "label": "NetFilter stateful packet firewall (iptables)", "type": "bool", "help": "Stateful packet inspection firewall and NAT engine."}, {"id": "CONFIG_NET_HTTPD", "tag": "NET_HTTPD", "label": "In-kernel embedded HTTP REST web server", "type": "bool", "help": "Built-in HTTP server exposing REST APIs on port 80."}, {"id": "CONFIG_NET_SSHD", "tag": "NET_SSHD", "label": "In-kernel Secure Shell (SSH 2.0) daemon", "type": "bool", "help": "Built-in encrypted SSH server on port 22 with auth."}, ] }, { "id": "drivers", "title": "Device Drivers, Graphics & Sound", "help": "Configure multimedia, sound cards, display adapters, and USB controllers.", "items": [ {"id": "CONFIG_DRV_CANVAS_2D", "tag": "DRV_CANVAS_2D", "label": "2D TrueColor Rasterizer Canvas graphics engine", "type": "bool", "help": "Framebuffer rasterizer supporting lines, rectangles, and fonts."}, {"id": "CONFIG_DRV_BOCHS_VBE", "tag": "DRV_BOCHS_VBE", "label": "Bochs / VBE dynamic resolution video adapter", "type": "bool", "help": "VBE/Bochs graphics controller for graphical resolution modes."}, {"id": "CONFIG_DRV_SOUND_HDA", "tag": "DRV_SOUND_HDA", "label": "Intel High Definition Audio (Azalia HDA) driver", "type": "bool", "help": "PCI Intel HD Audio codec and stream controller."}, {"id": "CONFIG_DRV_SOUND_AC97", "tag": "DRV_SOUND_AC97", "label": "Analog Devices AC'97 sound codec driver", "type": "bool", "help": "Legacy PCI AC'97 sound controller."}, {"id": "CONFIG_DRV_USB_XHCI", "tag": "DRV_USB_XHCI", "label": "USB 3.0 Extensible Host Controller (xHCI)", "type": "bool", "help": "USB 3.0 Host Controller with device enumeration."}, {"id": "CONFIG_DRV_HWMON", "tag": "DRV_HWMON", "label": "HWMON CPU temperature & fan sensor driver", "type": "bool", "help": "Hardware monitoring sensors for CPU temperature and fan RPM."}, {"id": "CONFIG_DRV_VIRTIO_RNG", "tag": "DRV_VIRTIO_RNG", "label": "VirtIO true hardware random generator (/dev/hwrng)", "type": "bool", "help": "Paravirtualized entropy source feeding the kernel CSPRNG."}, {"id": "CONFIG_DRV_PTY", "tag": "DRV_PTY", "label": "Unix98 Pseudo-Terminal (PTY) subsystem (/dev/pts)", "type": "bool", "help": "Pseudo-terminals for interactive terminal sessions and SSH."}, ] }, { "id": "security", "title": "Security options & Cryptographic Keyrings", "help": "Configure kernel security modules, authentication, and root keyrings.", "items": [ {"id": "CONFIG_SECURITY_LSM", "tag": "SECURITY_LSM", "label": "Linux Security Module (LSM) access control", "type": "bool", "help": "Mandatory access control and POSIX capabilities enforcement."}, {"id": "CONFIG_SECURITY_KEYRING", "tag": "SECURITY_KEYRING", "label": "X.509 cryptographic keyring & certificates", "type": "bool", "help": "System trust keyring verifying kernel modules and signatures."}, ] }, { "id": "userland", "title": "Userland Core & LazyBox Utility Suite", "help": "Configure userland binaries, shell applets, and command-line tools.", "items": [ {"id": "CONFIG_USERLAND_LAZYBOX", "tag": "LAZYBOX", "label": "LazyBox multi-call utility suite (70+ Linux tools)", "type": "bool", "help": "Embedded BusyBox-like multi-call binary with sh, nano, neofetch, ls, etc."}, ] } ] def load_config(): cfg = dict(DEFAULTS) if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE, "r") as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue if "=" in line: k, v = line.split("=", 1) k = k.strip() v = v.strip().strip('"') if v == "y": cfg[k] = True elif v == "n": cfg[k] = False else: cfg[k] = v return cfg def save_config(cfg): os.makedirs(os.path.dirname(AUTOCONF_HEADER), exist_ok=True) with open(CONFIG_FILE, "w") as f_cfg, open(AUTOCONF_HEADER, "w") as f_hdr: f_cfg.write("# Automatically generated by SUB-OS Linux-Style Kconfig Configurator\n# Do not edit directly\n\n") f_hdr.write("/* Automatically generated by SUB-OS Kconfig. Do not edit. */\n#ifndef _AUTOCONF_H\n#define _AUTOCONF_H\n\n") # Write Architecture arch = cfg.get("CONFIG_ARCH", "x86_64") f_cfg.write(f"CONFIG_ARCH={arch}\n") f_hdr.write(f'#define CONFIG_ARCH "{arch}"\n') for k, v in sorted(cfg.items()): if k == "CONFIG_ARCH": continue if isinstance(v, bool): if v: f_cfg.write(f"{k}=y\n") f_hdr.write(f"#define {k} 1\n") else: f_cfg.write(f"# {k} is not set\n") elif isinstance(v, str): f_cfg.write(f'{k}="{v}"\n') f_hdr.write(f'#define {k} "{v}"\n') elif isinstance(v, int): f_cfg.write(f"{k}={v}\n") f_hdr.write(f"#define {k} {v}\n") f_hdr.write("\n#endif /* _AUTOCONF_H */\n") # ----------------------------------------------------------------------------- # 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 " 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" current_menu_id = "main" while True: if current_menu_id == "main": # Build Main Menu Options menu_opts = [] for menu in MENU_TREE: menu_opts.extend([menu["id"], f"{menu['title']} --->"]) cmd = [ "dialog", "--clear", "--backtitle", backtitle, "--title", "SUB-OS Kernel Configuration", "--extra-button", "--extra-label", "Save", "--cancel-label", "Exit", "--help-button", "--menu", "Arrow keys navigate the menu. selects submenus --->.\n" "Press to write .config, to finish, for information.", "20", "78", "10" ] + menu_opts res = subprocess.run(cmd, stderr=subprocess.PIPE, text=True) ret = res.returncode sel = res.stderr.strip() if ret == 0: # Select current_menu_id = sel elif ret == 1 or ret == 255: # Exit or Esc # Confirmation dialog exit_cmd = [ "dialog", "--backtitle", backtitle, "--title", "Save Kernel Configuration?", "--yesno", "Do you wish to save your new kernel configuration?", "7", "60" ] if subprocess.run(exit_cmd).returncode == 0: save_config(cfg) break elif ret == 3: # Extra button (Save) save_config(cfg) 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 # dialog reports the focused row as "HELP "; 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 submenu = next((m for m in MENU_TREE if m["id"] == current_menu_id), None) if not submenu: current_menu_id = "main" continue items = submenu["items"] item_opts = [] for item in items: itype = item.get("type") if itype == "bool": status = "ON" if cfg.get(item["id"], False) else "OFF" item_opts.extend([item["id"], item["label"], status]) elif itype == "radio": group = item.get("group") if group == "arch": status = "ON" if cfg.get("CONFIG_ARCH") == item["val"] else "OFF" elif group == "opt": status = "ON" if cfg.get("CONFIG_OPTIMIZATION") == item["val"] else "OFF" else: status = "OFF" item_opts.extend([item["id"], item["label"], status]) is_radiolist = any(i.get("type") == "radio" for i in items) dialog_type = "--radiolist" if is_radiolist else "--checklist" cmd = [ "dialog", "--clear", "--backtitle", backtitle, "--title", submenu["title"], "--help-button", "--help-status", dialog_type, "Use to toggle features [*], to confirm, for info.", "20", "78", "10" ] + item_opts res = subprocess.run(cmd, stderr=subprocess.PIPE, text=True) ret = res.returncode out = res.stderr.strip() if ret == 0: # OK / Confirm _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 ". # Re-apply the on-tags first so the toggles made before pressing # 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", help_title, "--msgbox", help_text, "16", "72"]) save_config(cfg) print(f"*** Configuration written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***") # ----------------------------------------------------------------------------- # Pure Python Curses GUI Renderer (Pixel-Perfect Linux lxdialog Replica) # ----------------------------------------------------------------------------- def run_curses_gui(stdscr): import curses try: curses.curs_set(0) except Exception: pass try: curses.use_default_colors() except Exception: pass # Authentic Linux Kernel lxdialog Palette has_color = False try: if curses.has_colors(): has_color = True curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) # Screen Backdrop curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_WHITE) # Dialog Window Body curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_WHITE) # Window Title / Hotkeys curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLUE) # Selected Highlighted Item curses.init_pair(5, curses.COLOR_BLUE, curses.COLOR_WHITE) # Subtext / Checkbox Brackets curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_CYAN) # Button Inverted Highlight curses.init_pair(7, curses.COLOR_BLACK, curses.COLOR_BLACK) # Window Drop Shadow except Exception: has_color = False cfg = load_config() menu_stack = [] current_menu = MENU_TREE cursor = 0 btn_cursor = 0 # 0: Select, 1: Exit, 2: Help, 3: Save, 4: Defaults buttons = [" Select ", " Exit ", " Help ", " Save ", "Defaults"] while True: max_y, max_x = stdscr.getmaxyx() if max_y < 20 or max_x < 70: stdscr.clear() stdscr.addstr(0, 0, "Terminal window too small for menuconfig. Please resize!") stdscr.refresh() key = stdscr.getch() if key in [ord('q'), ord('Q'), 27]: break continue # 1. Fill Screen Backdrop (Royal Blue) stdscr.bkgd(' ', curses.color_pair(1) if has_color else 0) stdscr.clear() # Top Backtitle backtitle = "SUB-OS v0.2.0-lts Modular Monolithic Linux Kernel Configuration" stdscr.addstr(0, 2, backtitle, curses.A_BOLD) arch_info = f"Arch: [{cfg.get('CONFIG_ARCH', 'x86_64')}] Opt: [{cfg.get('CONFIG_OPTIMIZATION', '-O2')}]" if max_x - len(arch_info) - 4 > len(backtitle) + 4: stdscr.addstr(0, max_x - len(arch_info) - 3, arch_info, curses.A_BOLD) # 2. Compute Floating Dialog Box Dimensions dw = min(78, max_x - 4) dh = min(22, max_y - 3) dx = (max_x - dw) // 2 dy = (max_y - dh) // 2 # 3. Draw 3D Drop Shadow if has_color: for sy in range(dy + 1, dy + dh + 1): stdscr.addstr(sy, dx + dw, " ", curses.color_pair(7)) stdscr.addstr(dy + dh, dx + 2, " " * (dw + 2), curses.color_pair(7)) # 4. Draw Dialog Window Box box_attr = curses.color_pair(2) if has_color else 0 for by in range(dy, dy + dh): stdscr.addstr(by, dx, " " * dw, box_attr) # Window Border title = " SUB-OS Kernel Configuration " if not menu_stack else f" {current_menu.get('title', 'Subsystem')} " for x in range(dx, dx + dw): stdscr.addch(dy, x, curses.ACS_HLINE, box_attr) stdscr.addch(dy + dh - 1, x, curses.ACS_HLINE, box_attr) for y in range(dy, dy + dh): stdscr.addch(y, dx, curses.ACS_VLINE, box_attr) stdscr.addch(y, dx + dw - 1, curses.ACS_VLINE, box_attr) stdscr.addch(dy, dx, curses.ACS_ULCORNER, box_attr) stdscr.addch(dy, dx + dw - 1, curses.ACS_URCORNER, box_attr) stdscr.addch(dy + dh - 1, dx, curses.ACS_LLCORNER, box_attr) stdscr.addch(dy + dh - 1, dx + dw - 1, curses.ACS_LRCORNER, box_attr) # Dialog Title Banner title_x = dx + (dw - len(title)) // 2 stdscr.addstr(dy, title_x, title, (curses.color_pair(3) | curses.A_BOLD) if has_color else curses.A_BOLD) # Instructions Header inst1 = "Arrow keys navigate the menu. selects submenus --->." inst2 = "Press to include, to exclude, to toggle [*]." stdscr.addstr(dy + 1, dx + 3, inst1[:dw - 6], box_attr) stdscr.addstr(dy + 2, dx + 3, inst2[:dw - 6], box_attr) # 5. Inner Scroll Box for Items items = current_menu if isinstance(current_menu, list) else current_menu.get("items", []) box_top = dy + 4 box_height = dh - 8 box_left = dx + 3 box_width = dw - 6 # Draw Inner Frame for by in range(box_top, box_top + box_height): stdscr.addstr(by, box_left, " " * box_width, box_attr) for x in range(box_left, box_left + box_width): stdscr.addch(box_top, x, curses.ACS_HLINE, box_attr) stdscr.addch(box_top + box_height - 1, x, curses.ACS_HLINE, box_attr) for y in range(box_top, box_top + box_height): stdscr.addch(y, box_left, curses.ACS_VLINE, box_attr) stdscr.addch(y, box_left + box_width - 1, curses.ACS_VLINE, box_attr) stdscr.addch(box_top, box_left, curses.ACS_ULCORNER, box_attr) stdscr.addch(box_top, box_left + box_width - 1, curses.ACS_URCORNER, box_attr) stdscr.addch(box_top + box_height - 1, box_left, curses.ACS_LLCORNER, box_attr) stdscr.addch(box_top + box_height - 1, box_left + box_width - 1, curses.ACS_LRCORNER, box_attr) visible_count = box_height - 2 scroll_offset = max(0, cursor - visible_count + 1) if cursor >= visible_count else 0 # Render Item Rows for i in range(visible_count): item_idx = scroll_offset + i if item_idx >= len(items): break item = items[item_idx] iy = box_top + 1 + i ix = box_left + 2 is_cur = (item_idx == cursor) row_attr = (curses.color_pair(4) | curses.A_BOLD) if (is_cur and has_color) else (box_attr | curses.A_REVERSE if is_cur else box_attr) if "items" in item: # Submenu display_text = f" {item['title']} --->" elif item.get("type") == "bool": val = cfg.get(item["id"], False) state = "[*]" if val else "[ ]" display_text = f" {state} {item['label']}" elif item.get("type") == "radio": group = item.get("group") if group == "arch": val = (cfg.get("CONFIG_ARCH") == item["val"]) elif group == "opt": val = (cfg.get("CONFIG_OPTIMIZATION") == item["val"]) else: val = False state = "(*)" if val else "( )" display_text = f" {state} {item['label']}" else: display_text = f" {item.get('title', '')}" row_str = display_text.ljust(box_width - 4)[:box_width - 4] stdscr.addstr(iy, ix, row_str, row_attr) # 6. Bottom Action Button Bar btn_y = dy + dh - 2 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, b_text in enumerate(btn_texts): if cur_bx + len(b_text) > dx + dw - 2: break is_active_btn = (b_idx == btn_cursor) 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) + gap stdscr.refresh() try: key = stdscr.getch() except Exception: break # Keyboard Navigation if key in [curses.KEY_UP, ord('k'), ord('K')]: if cursor > 0: cursor -= 1 elif key in [curses.KEY_DOWN, ord('j'), ord('J')]: if cursor < len(items) - 1: cursor += 1 elif key in [curses.KEY_LEFT, ord('h'), ord('H')]: btn_cursor = (btn_cursor - 1) % len(buttons) elif key in [curses.KEY_RIGHT, ord('l'), ord('L'), 9]: # Tab or Right btn_cursor = (btn_cursor + 1) % len(buttons) elif key in [ord('y'), ord('Y')]: if cursor < len(items): sel = items[cursor] if sel.get("type") == "bool": cfg[sel["id"]] = True elif key in [ord('n'), ord('N')]: if cursor < len(items): sel = items[cursor] if sel.get("type") == "bool": cfg[sel["id"]] = False elif key == ord(' '): if cursor < len(items): sel = items[cursor] if sel.get("type") == "bool": cfg[sel["id"]] = not cfg.get(sel["id"], False) elif sel.get("type") == "radio": group = sel.get("group") if group == "arch": cfg["CONFIG_ARCH"] = sel["val"] cfg["CONFIG_ARCH_X86_64"] = (sel["val"] == "x86_64") cfg["CONFIG_ARCH_AARCH64"] = (sel["val"] == "aarch64") cfg["CONFIG_ARCH_ARMV8I"] = (sel["val"] == "armv8i") elif group == "opt": cfg["CONFIG_OPTIMIZATION"] = sel["val"] elif key in [curses.KEY_ENTER, 10, 13]: # Trigger active bottom button if btn_cursor == 0: # Select if cursor < len(items): sel = items[cursor] if "items" in sel: menu_stack.append((current_menu, cursor)) current_menu = sel cursor = 0 elif sel.get("type") == "bool": cfg[sel["id"]] = not cfg.get(sel["id"], False) elif sel.get("type") == "radio": group = sel.get("group") if group == "arch": cfg["CONFIG_ARCH"] = sel["val"] cfg["CONFIG_ARCH_X86_64"] = (sel["val"] == "x86_64") cfg["CONFIG_ARCH_AARCH64"] = (sel["val"] == "aarch64") cfg["CONFIG_ARCH_ARMV8I"] = (sel["val"] == "armv8i") elif group == "opt": cfg["CONFIG_OPTIMIZATION"] = sel["val"] elif btn_cursor == 1: # Exit if menu_stack: current_menu, cursor = menu_stack.pop() else: save_config(cfg) break 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) elif btn_cursor == 4: # Defaults 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 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) elif key in [ord('q'), ord('Q'), 27]: # Esc or Q if menu_stack: current_menu, cursor = menu_stack.pop() else: 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) px = (max_x - pw) // 2 py = (max_y - ph) // 2 import curses # Draw popup box for y in range(py, py + ph): stdscr.addstr(y, px, " " * pw, curses.A_REVERSE) for x in range(px, px + pw): stdscr.addch(py, x, curses.ACS_HLINE, curses.A_REVERSE) stdscr.addch(py + ph - 1, x, curses.ACS_HLINE, curses.A_REVERSE) for y in range(py, py + ph): stdscr.addch(y, px, curses.ACS_VLINE, curses.A_REVERSE) stdscr.addch(y, px + pw - 1, curses.ACS_VLINE, curses.A_REVERSE) stdscr.addstr(py, px + (pw - len(title) - 2) // 2, f" {title} ", curses.A_BOLD | curses.A_REVERSE) # Word wrap message lines lines = [] for raw_line in message.split("\n"): while len(raw_line) > pw - 4: split_idx = raw_line[:pw - 4].rfind(" ") if split_idx <= 0: split_idx = pw - 4 lines.append(raw_line[:split_idx]) raw_line = raw_line[split_idx:].lstrip() lines.append(raw_line) for i, line in enumerate(lines[:ph - 4]): stdscr.addstr(py + 2 + i, px + 2, line[:pw - 4], curses.A_REVERSE) btn_str = "< OK >" stdscr.addstr(py + ph - 2, px + (pw - len(btn_str)) // 2, btn_str, curses.A_BOLD) stdscr.refresh() stdscr.getch() def defconfig(arch="x86_64"): cfg = dict(DEFAULTS) cfg["CONFIG_ARCH"] = arch if arch == "x86_64": cfg["CONFIG_ARCH_X86_64"] = True cfg["CONFIG_ARCH_AARCH64"] = False cfg["CONFIG_ARCH_ARMV8I"] = False elif arch in ["aarch64", "arm64"]: cfg["CONFIG_ARCH_X86_64"] = False cfg["CONFIG_ARCH_AARCH64"] = True cfg["CONFIG_ARCH_ARMV8I"] = False elif arch in ["armv8i", "armv81", "arm32", "arm", "armv8", "aarch32"]: cfg["CONFIG_ARCH_X86_64"] = False cfg["CONFIG_ARCH_AARCH64"] = False cfg["CONFIG_ARCH_ARMV8I"] = True save_config(cfg) print(f"*** Default configuration for '{arch}' written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***") def main(): if len(sys.argv) > 1: cmd = sys.argv[1] if cmd in ["--defconfig", "defconfig"]: arch = sys.argv[2] if len(sys.argv) > 2 else "x86_64" defconfig(arch) return elif cmd == "--x86_64": defconfig("x86_64") return elif cmd in ["--aarch64", "--armv8", "--arm64"]: defconfig("aarch64") return elif cmd in ["--armv8i", "--armv81", "--arm32", "--arm", "--aarch32"]: defconfig("armv8i") return # Check for /usr/bin/dialog for authentic Linux lxdialog GUI if shutil.which("dialog") and sys.stdin.isatty(): try: run_dialog_gui() return 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} ***") 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} ***") if __name__ == "__main__": main()