mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-26 09:02:46 +00:00
feat(kconfig): Implement authentic Linux lxdialog GUI menuconfig
- Add Linux kernel lxdialog GUI integration using dialog tool - Support royal blue backdrop, floating gray dialog windows, 3D drop shadows - Add bottom button bar: < Select >, < Exit >, < Help >, < Save >, < Defaults > - Add full context-sensitive Help dialogs for every kernel subsystem and driver - Provide pixel-perfect Python Curses GUI replica with Tab/Arrow/Hotkeys navigation - Auto-generate .config and include/config/autoconf.h
This commit is contained in:
1 parent
fc68e22916
commit
c02920d11b
1 file changed
+493
-227
+493
-227
@@ -1,13 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
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.
|
||||
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"
|
||||
@@ -76,100 +77,155 @@ DEFAULTS = {
|
||||
"CONFIG_USERLAND_LAZYBOX": True,
|
||||
}
|
||||
|
||||
MENU_STRUCTURE = [
|
||||
MENU_TREE = [
|
||||
{
|
||||
"title": "Target Architecture Selection",
|
||||
"type": "submenu",
|
||||
"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": [
|
||||
{"name": "CONFIG_ARCH_X86_64", "label": "x86_64 (64-Bit AMD64/Intel x86-64)", "type": "radio", "group": "arch", "val": "x86_64"},
|
||||
{"name": "CONFIG_ARCH_AARCH64", "label": "aarch64 (ARMv8-A 64-Bit Cortex/Neoverse)", "type": "radio", "group": "arch", "val": "aarch64"},
|
||||
{"name": "CONFIG_ARCH_ARMV8I", "label": "armv8i (32-Bit ARM/AArch32 Profile)", "type": "radio", "group": "arch", "val": "armv8i"},
|
||||
{"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."},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Compiler Optimizations & Code Generation",
|
||||
"type": "submenu",
|
||||
"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": [
|
||||
{"name": "CONFIG_OPT_O2", "label": "-O2 (Recommended: High Performance Optimization)", "type": "radio", "group": "opt", "val": "-O2"},
|
||||
{"name": "CONFIG_OPT_O3", "label": "-O3 (Aggressive Vectorization & Loop Unrolling)", "type": "radio", "group": "opt", "val": "-O3"},
|
||||
{"name": "CONFIG_OPT_OS", "label": "-Os (Optimize for Smallest Binary Footprint)", "type": "radio", "group": "opt", "val": "-Os"},
|
||||
{"name": "CONFIG_OPT_O0", "label": "-O0 (Disable Optimizations for GDB Debugging)", "type": "radio", "group": "opt", "val": "-O0"},
|
||||
{"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."},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "General Kernel Setup",
|
||||
"type": "submenu",
|
||||
"id": "general",
|
||||
"title": "General kernel setup and core subsystems",
|
||||
"help": "Configure core multitasking, synchronization, and kernel execution parameters.",
|
||||
"items": [
|
||||
{"name": "CONFIG_SMP", "label": "Symmetric Multi-Processing (SMP) Support", "type": "bool"},
|
||||
{"name": "CONFIG_PREEMPT", "label": "Preemptive Kernel Scheduling Model", "type": "bool"},
|
||||
{"name": "CONFIG_BPF_VM", "label": "In-Kernel eBPF Register Virtual Machine", "type": "bool"},
|
||||
{"name": "CONFIG_IO_URING", "label": "High-Performance io_uring Ring Buffers", "type": "bool"},
|
||||
{"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."},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Memory Management & Object Allocators",
|
||||
"type": "submenu",
|
||||
"id": "mm",
|
||||
"title": "Memory management & object allocators",
|
||||
"help": "Configure physical memory management, slab caches, and virtual address spaces.",
|
||||
"items": [
|
||||
{"name": "CONFIG_MM_PMM", "label": "Physical Page Frame Allocator (PMM)", "type": "bool"},
|
||||
{"name": "CONFIG_MM_SLAB", "label": "High-Performance SLUB/SLAB Object Cache", "type": "bool"},
|
||||
{"name": "CONFIG_MM_VMA", "label": "Virtual Memory Areas (VMA) & mmap Subsystem", "type": "bool"},
|
||||
{"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."},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Virtual File Systems & Storage Drivers",
|
||||
"type": "submenu",
|
||||
"id": "fs",
|
||||
"title": "Virtual File Systems & Storage Device Drivers",
|
||||
"help": "Configure storage hardware controllers and mounted file system drivers.",
|
||||
"items": [
|
||||
{"name": "CONFIG_FS_VFS", "label": "Virtual File System (VFS) Core Subsystem", "type": "bool"},
|
||||
{"name": "CONFIG_FS_FAT32", "label": "FAT32 / VFAT File System Driver", "type": "bool"},
|
||||
{"name": "CONFIG_FS_EXT2", "label": "Second Extended (EXT2) Linux File System", "type": "bool"},
|
||||
{"name": "CONFIG_FS_SYSFS", "label": "Sysfs KObject Virtual Hierarchy (/sys)", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_NVME", "label": "NVM Express (NVMe PCIe SSD) Driver", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_AHCI", "label": "AHCI SATA 6Gb/s SSD/HDD Controller Driver", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_ATA", "label": "Legacy IDE/ATA Parallel Storage Driver", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_VIRTIO_BLK", "label": "VirtIO Paravirtualized Block Storage (/dev/vda)", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_RAMDISK", "label": "In-Memory Virtual Ramdisk Storage (/dev/ram0)", "type": "bool"},
|
||||
{"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."},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Network Subsystem & Hardware Adapters",
|
||||
"type": "submenu",
|
||||
"id": "net",
|
||||
"title": "Networking subsystem & hardware adapters",
|
||||
"help": "Configure network stack protocols, packet filtering, and ethernet adapters.",
|
||||
"items": [
|
||||
{"name": "CONFIG_NET_STACK", "label": "TCP/IP Network Stack (IPv4, UDP, TCP)", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_E1000", "label": "Intel 82540EM Gigabit Ethernet NIC Driver", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_RTL8139", "label": "Realtek RTL8139 Fast Ethernet NIC Driver", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_VIRTIO_NET", "label": "VirtIO 10-Gigabit Paravirtualized NIC", "type": "bool"},
|
||||
{"name": "CONFIG_NET_FILTER", "label": "NetFilter Stateful Packet Firewall (iptables)", "type": "bool"},
|
||||
{"name": "CONFIG_NET_HTTPD", "label": "In-Kernel Embedded HTTP REST Web Server", "type": "bool"},
|
||||
{"name": "CONFIG_NET_SSHD", "label": "In-Kernel Secure Shell (SSH 2.0) Daemon", "type": "bool"},
|
||||
{"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."},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Device Drivers & Multimedia",
|
||||
"type": "submenu",
|
||||
"id": "drivers",
|
||||
"title": "Device Drivers, Graphics & Sound",
|
||||
"help": "Configure multimedia, sound cards, display adapters, and USB controllers.",
|
||||
"items": [
|
||||
{"name": "CONFIG_DRV_CANVAS_2D", "label": "2D TrueColor Rasterizer Canvas Graphics Engine", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_BOCHS_VBE", "label": "Bochs / VBE Dynamic Resolution Display Adapter", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_SOUND_HDA", "label": "Intel High Definition Audio (Azalia HDA) Driver", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_SOUND_AC97", "label": "Analog Devices AC'97 Sound Codec Driver", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_USB_XHCI", "label": "USB 3.0 Extensible Host Controller (xHCI)", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_HWMON", "label": "HWMON CPU CoreTemp & Fan Tachometer Sensors", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_VIRTIO_RNG", "label": "VirtIO True Hardware Random Generator (/dev/hwrng)", "type": "bool"},
|
||||
{"name": "CONFIG_DRV_PTY", "label": "Unix98 Pseudo-Terminal (PTY) Subsystem (/dev/pts)", "type": "bool"},
|
||||
{"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."},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Security, LSM & Cryptographic Keyrings",
|
||||
"type": "submenu",
|
||||
"id": "security",
|
||||
"title": "Security options & Cryptographic Keyrings",
|
||||
"help": "Configure kernel security modules, authentication, and root keyrings.",
|
||||
"items": [
|
||||
{"name": "CONFIG_SECURITY_LSM", "label": "Linux Security Module (LSM) Mandatory Access Control", "type": "bool"},
|
||||
{"name": "CONFIG_SECURITY_KEYRING", "label": "X.509 Cryptographic Keyring & Certificates", "type": "bool"},
|
||||
{"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",
|
||||
"type": "submenu",
|
||||
"help": "Configure userland binaries, shell applets, and command-line tools.",
|
||||
"items": [
|
||||
{"name": "CONFIG_USERLAND_LAZYBOX", "label": "LazyBox Multi-Call Userland Utility Suite (70+ Tools)", "type": "bool"},
|
||||
{"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."},
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -197,7 +253,7 @@ def load_config():
|
||||
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 Kconfig TUI Configurator\n# Do not edit directly\n\n")
|
||||
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
|
||||
@@ -223,97 +279,273 @@ def save_config(cfg):
|
||||
|
||||
f_hdr.write("\n#endif /* _AUTOCONF_H */\n")
|
||||
|
||||
def run_curses_tui(stdscr):
|
||||
# -----------------------------------------------------------------------------
|
||||
# Linux lxdialog Runner (Uses /usr/bin/dialog for authentic Linux GUI Look)
|
||||
# -----------------------------------------------------------------------------
|
||||
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",
|
||||
"--help-button",
|
||||
"--menu",
|
||||
"Arrow keys navigate the menu. <Enter> selects submenus --->.\n"
|
||||
"Press <Save> to write .config, <Exit> to finish, <Help> 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
|
||||
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"])
|
||||
|
||||
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",
|
||||
dialog_type,
|
||||
"Use <Space> to toggle features [*], <Enter> to confirm, <Help> 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
|
||||
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)
|
||||
current_menu_id = "main"
|
||||
|
||||
elif ret == 1 or ret == 255: # Cancel or Esc
|
||||
current_menu_id = "main"
|
||||
|
||||
elif ret == 2: # Help button
|
||||
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"])
|
||||
|
||||
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_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
|
||||
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()
|
||||
current_menu = MENU_STRUCTURE
|
||||
menu_stack = []
|
||||
cursor_stack = [0]
|
||||
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:
|
||||
if has_color:
|
||||
try:
|
||||
stdscr.bkgd(' ', curses.color_pair(2))
|
||||
except Exception:
|
||||
pass
|
||||
stdscr.clear()
|
||||
max_y, max_x = stdscr.getmaxyx()
|
||||
|
||||
# Top Header Bar
|
||||
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 / 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])
|
||||
|
||||
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", [])
|
||||
cursor = cursor_stack[-1]
|
||||
start_y = 5
|
||||
|
||||
for idx, item in enumerate(items):
|
||||
y = start_y + idx
|
||||
if y >= max_y - 3:
|
||||
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
|
||||
|
||||
prefix = " "
|
||||
is_selected = (idx == cursor)
|
||||
# 1. Fill Screen Backdrop (Royal Blue)
|
||||
stdscr.bkgd(' ', curses.color_pair(1) if has_color else 0)
|
||||
stdscr.clear()
|
||||
|
||||
if item.get("type") == "submenu":
|
||||
line_text = f"---> {item['title']}"
|
||||
# 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. <Enter> selects submenus --->."
|
||||
inst2 = "Press <Y> to include, <N> to exclude, <Space> 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["name"], False)
|
||||
val = cfg.get(item["id"], False)
|
||||
state = "[*]" if val else "[ ]"
|
||||
line_text = f"{state} {item['label']}"
|
||||
display_text = f" {state} {item['label']}"
|
||||
elif item.get("type") == "radio":
|
||||
group = item.get("group")
|
||||
if group == "arch":
|
||||
@@ -323,35 +555,24 @@ def run_curses_tui(stdscr):
|
||||
else:
|
||||
val = False
|
||||
state = "(*)" if val else "( )"
|
||||
line_text = f"{state} {item['label']}"
|
||||
display_text = f" {state} {item['label']}"
|
||||
else:
|
||||
line_text = item.get("title", "")
|
||||
display_text = f" {item.get('title', '')}"
|
||||
|
||||
display_line = f"{prefix}{line_text}".ljust(max_x - 6)[:max_x - 6]
|
||||
row_str = display_text.ljust(box_width - 4)[:box_width - 4]
|
||||
stdscr.addstr(iy, ix, row_str, row_attr)
|
||||
|
||||
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} ")
|
||||
# 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
|
||||
|
||||
# Bottom Status Bar
|
||||
bottom_bar = " <Save> [S] <Exit> [Q] <Defaults> [D] "
|
||||
if has_color:
|
||||
stdscr.attron(curses.color_pair(1))
|
||||
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])
|
||||
for b_idx, btn_name in enumerate(buttons):
|
||||
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)
|
||||
|
||||
stdscr.refresh()
|
||||
try:
|
||||
@@ -359,91 +580,125 @@ def run_curses_tui(stdscr):
|
||||
except Exception:
|
||||
break
|
||||
|
||||
# Keyboard Navigation
|
||||
if key in [curses.KEY_UP, ord('k'), ord('K')]:
|
||||
if cursor_stack[-1] > 0:
|
||||
cursor_stack[-1] -= 1
|
||||
if cursor > 0:
|
||||
cursor -= 1
|
||||
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, ord(' ')]:
|
||||
sel_item = items[cursor_stack[-1]]
|
||||
if sel_item.get("type") == "submenu":
|
||||
menu_stack.append(current_menu)
|
||||
cursor_stack.append(0)
|
||||
current_menu = sel_item
|
||||
elif 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 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_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")
|
||||
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_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! Press any key... ")
|
||||
stdscr.attroff(curses.color_pair(1) | curses.A_BOLD)
|
||||
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:
|
||||
stdscr.addstr(max_y - 2, 4, f" Saved configuration! Press any key... ")
|
||||
stdscr.refresh()
|
||||
stdscr.getch()
|
||||
elif key in [ord('d'), ord('D')]:
|
||||
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 == 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)
|
||||
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()
|
||||
show_popup(stdscr, "Reset Defaults", "Default configuration options loaded!", 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 = menu_stack.pop()
|
||||
cursor_stack.pop()
|
||||
current_menu, cursor = menu_stack.pop()
|
||||
else:
|
||||
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")
|
||||
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
|
||||
|
||||
try:
|
||||
choice = input("Enter choice [1-6] (Default: 5): ").strip()
|
||||
except EOFError:
|
||||
choice = "5"
|
||||
# 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)
|
||||
|
||||
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)
|
||||
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)
|
||||
@@ -480,13 +735,24 @@ def main():
|
||||
defconfig("armv8i")
|
||||
return
|
||||
|
||||
# Try curses TUI first, gracefully fall back to ANSI mode if curses is unavailable
|
||||
# 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:
|
||||
pass
|
||||
|
||||
# Fallback to Curses GUI replica
|
||||
try:
|
||||
import curses
|
||||
curses.wrapper(run_curses_tui)
|
||||
curses.wrapper(run_curses_gui)
|
||||
print(f"*** Configuration saved to {CONFIG_FILE} and {AUTOCONF_HEADER} ***")
|
||||
except Exception as e:
|
||||
run_ansi_fallback()
|
||||
except Exception:
|
||||
# Fallback if no TTY
|
||||
cfg = load_config()
|
||||
save_config(cfg)
|
||||
print(f"*** Configuration written to {CONFIG_FILE} and {AUTOCONF_HEADER} ***")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user