From af477d06026cf060b5e4e965ee190289ce29916b Mon Sep 17 00:00:00 2001 From: Leonmmcoset Date: Sat, 18 Apr 2026 19:22:25 +0800 Subject: [PATCH] menuconfig++ --- .github/workflows/build-os.yml | 20 + .gitignore | 2 + CMakeLists.txt | 39 ++ Makefile | 14 +- clks/kernel/exec.c | 15 + clks/kernel/keyboard.c | 40 +- clks/kernel/kmain.c | 29 +- clks/kernel/syscall.c | 35 +- clks/kernel/userland.c | 16 + configs/menuconfig/clks_features.json | 60 +++ .../__pycache__/menuconfig.cpython-312.pyc | Bin 35863 -> 56306 bytes scripts/menuconfig.py | 345 +++++++++++++++++- 12 files changed, 592 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build-os.yml b/.github/workflows/build-os.yml index 3209ce2..d83f397 100644 --- a/.github/workflows/build-os.yml +++ b/.github/workflows/build-os.yml @@ -3,6 +3,12 @@ name: Build CLeonOS on: push: pull_request: + workflow_dispatch: + inputs: + menuconfig_overrides: + description: "Optional extra menuconfig args, e.g. --set CLEONOS_CLKS_ENABLE_AUDIO=OFF" + required: false + default: "" permissions: contents: read @@ -10,6 +16,8 @@ permissions: jobs: build-os: runs-on: ubuntu-latest + env: + MENUCONFIG_OVERRIDES: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.menuconfig_overrides || '' }} steps: - name: Checkout @@ -39,6 +47,18 @@ jobs: - name: Setup Rust uses: dtolnay/rust-toolchain@stable + - name: Generate menuconfig (default all enabled) + shell: bash + run: | + set -euo pipefail + extra_args=() + if [[ -n "${MENUCONFIG_OVERRIDES}" ]]; then + # shellcheck disable=SC2206 + extra_args=(${MENUCONFIG_OVERRIDES}) + fi + python3 scripts/menuconfig.py --defaults --non-interactive "${extra_args[@]}" + echo "menuconfig generated with defaults=all-enabled" + - name: Configure run: | cmake -S . -B build-cmake \ diff --git a/.gitignore b/.gitignore index 0aeb3fc..254e464 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ Testing/ # menuconfig generated configs/menuconfig/.config.json configs/menuconfig/config.cmake + +venv/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bdc1f6..93afb8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,6 +162,16 @@ cl_set_bool_cache(CLEONOS_CLKS_ENABLE_ELFRUNNER_INIT ON "Initialize ELFRUNNER fr cl_set_bool_cache(CLEONOS_CLKS_ENABLE_SYSCALL_TICK_QUERY ON "Query syscall timer ticks at boot") cl_set_bool_cache(CLEONOS_CLKS_ENABLE_TTY_READY_LOG ON "Print TTY ready logs") cl_set_bool_cache(CLEONOS_CLKS_ENABLE_IDLE_DEBUG_LOG ON "Print idle loop debug log") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_PROCFS ON "Enable virtual /proc procfs syscall layer") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_EXEC_SERIAL_LOG ON "Enable EXEC info logs on serial output") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_KBD_TTY_SWITCH_HOTKEY ON "Enable ALT+F1..F4 TTY switch hotkey") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_KBD_CTRL_SHORTCUTS ON "Enable Ctrl+A/C/V keyboard shortcuts") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_KBD_FORCE_STOP_HOTKEY ON "Enable Ctrl+Alt+C force-stop hotkey") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USER_INIT_SCRIPT_PROBE ON "Probe /shell/init.cmd during userland init") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_USER_SYSTEM_APP_PROBE ON "Probe /system apps (elfrunner/memc) during userland init") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_SCHED_TASK_COUNT_LOG ON "Print scheduler task count during boot") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_INTERRUPT_READY_LOG ON "Print interrupt ready log after IDT/PIC init") +cl_set_bool_cache(CLEONOS_CLKS_ENABLE_SHELL_MODE_LOG ON "Print default shell mode log during boot") cl_bool_to_int(CLEONOS_CLKS_ENABLE_AUDIO CLKS_CFG_AUDIO_INT) cl_bool_to_int(CLEONOS_CLKS_ENABLE_MOUSE CLKS_CFG_MOUSE_INT) @@ -185,6 +195,16 @@ cl_bool_to_int(CLEONOS_CLKS_ENABLE_ELFRUNNER_INIT CLKS_CFG_ELFRUNNER_INIT_INT) cl_bool_to_int(CLEONOS_CLKS_ENABLE_SYSCALL_TICK_QUERY CLKS_CFG_SYSCALL_TICK_QUERY_INT) cl_bool_to_int(CLEONOS_CLKS_ENABLE_TTY_READY_LOG CLKS_CFG_TTY_READY_LOG_INT) cl_bool_to_int(CLEONOS_CLKS_ENABLE_IDLE_DEBUG_LOG CLKS_CFG_IDLE_DEBUG_LOG_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_PROCFS CLKS_CFG_PROCFS_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_EXEC_SERIAL_LOG CLKS_CFG_EXEC_SERIAL_LOG_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_KBD_TTY_SWITCH_HOTKEY CLKS_CFG_KBD_TTY_SWITCH_HOTKEY_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_KBD_CTRL_SHORTCUTS CLKS_CFG_KBD_CTRL_SHORTCUTS_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_KBD_FORCE_STOP_HOTKEY CLKS_CFG_KBD_FORCE_STOP_HOTKEY_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USER_INIT_SCRIPT_PROBE CLKS_CFG_USER_INIT_SCRIPT_PROBE_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_USER_SYSTEM_APP_PROBE CLKS_CFG_USER_SYSTEM_APP_PROBE_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_SCHED_TASK_COUNT_LOG CLKS_CFG_SCHED_TASK_COUNT_LOG_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_INTERRUPT_READY_LOG CLKS_CFG_INTERRUPT_READY_LOG_INT) +cl_bool_to_int(CLEONOS_CLKS_ENABLE_SHELL_MODE_LOG CLKS_CFG_SHELL_MODE_LOG_INT) set(CFLAGS_COMMON -std=c11 @@ -222,6 +242,16 @@ set(ARCH_CFLAGS "-DCLKS_CFG_SYSCALL_TICK_QUERY=${CLKS_CFG_SYSCALL_TICK_QUERY_INT}" "-DCLKS_CFG_TTY_READY_LOG=${CLKS_CFG_TTY_READY_LOG_INT}" "-DCLKS_CFG_IDLE_DEBUG_LOG=${CLKS_CFG_IDLE_DEBUG_LOG_INT}" + "-DCLKS_CFG_PROCFS=${CLKS_CFG_PROCFS_INT}" + "-DCLKS_CFG_EXEC_SERIAL_LOG=${CLKS_CFG_EXEC_SERIAL_LOG_INT}" + "-DCLKS_CFG_KBD_TTY_SWITCH_HOTKEY=${CLKS_CFG_KBD_TTY_SWITCH_HOTKEY_INT}" + "-DCLKS_CFG_KBD_CTRL_SHORTCUTS=${CLKS_CFG_KBD_CTRL_SHORTCUTS_INT}" + "-DCLKS_CFG_KBD_FORCE_STOP_HOTKEY=${CLKS_CFG_KBD_FORCE_STOP_HOTKEY_INT}" + "-DCLKS_CFG_USER_INIT_SCRIPT_PROBE=${CLKS_CFG_USER_INIT_SCRIPT_PROBE_INT}" + "-DCLKS_CFG_USER_SYSTEM_APP_PROBE=${CLKS_CFG_USER_SYSTEM_APP_PROBE_INT}" + "-DCLKS_CFG_SCHED_TASK_COUNT_LOG=${CLKS_CFG_SCHED_TASK_COUNT_LOG_INT}" + "-DCLKS_CFG_INTERRUPT_READY_LOG=${CLKS_CFG_INTERRUPT_READY_LOG_INT}" + "-DCLKS_CFG_SHELL_MODE_LOG=${CLKS_CFG_SHELL_MODE_LOG_INT}" -m64 -mno-red-zone -mcmodel=kernel @@ -814,17 +844,26 @@ if(Python3_Interpreter_FOUND) COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/scripts/menuconfig.py" USES_TERMINAL ) + add_custom_target(menuconfig-gui + COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/scripts/menuconfig.py" --gui + USES_TERMINAL + ) else() add_custom_target(menuconfig COMMAND ${CMAKE_COMMAND} -E echo "python3 not found; run scripts/menuconfig.py manually" USES_TERMINAL ) + add_custom_target(menuconfig-gui + COMMAND ${CMAKE_COMMAND} -E echo "python3 not found; run scripts/menuconfig.py --gui manually" + USES_TERMINAL + ) endif() add_custom_target(cleonos-help COMMAND ${CMAKE_COMMAND} -E echo "CLeonOS CMake build system (x86_64 only)" COMMAND ${CMAKE_COMMAND} -E echo " cmake -S . -B build-cmake" COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build-cmake --target menuconfig" + COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build-cmake --target menuconfig-gui" COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build-cmake --target setup" COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build-cmake --target userapps" COMMAND ${CMAKE_COMMAND} -E echo " cmake --build build-cmake --target iso" diff --git a/Makefile b/Makefile index e886c25..d27064c 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ ifneq ($(strip $(READELF_FOR_TARGET)),) CMAKE_PASSTHROUGH_ARGS += -DREADELF_FOR_TARGET=$(READELF_FOR_TARGET) endif -.PHONY: all configure reconfigure menuconfig setup setup-tools setup-limine kernel userapps ramdisk-root ramdisk iso run debug clean clean-all help +.PHONY: all configure reconfigure menuconfig menuconfig-gui setup setup-tools setup-limine kernel userapps ramdisk-root ramdisk iso run debug clean clean-all help all: iso @@ -73,6 +73,17 @@ menuconfig: > fi > @$(MAKE) configure CMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) CMAKE_GENERATOR="$(CMAKE_GENERATOR)" CMAKE_EXTRA_ARGS="$(CMAKE_EXTRA_ARGS)" NO_COLOR="$(NO_COLOR)" LIMINE_SKIP_CONFIGURE="$(LIMINE_SKIP_CONFIGURE)" LIMINE_REF="$(LIMINE_REF)" LIMINE_REPO="$(LIMINE_REPO)" LIMINE_DIR="$(LIMINE_DIR)" LIMINE_BIN_DIR="$(LIMINE_BIN_DIR)" OBJCOPY_FOR_TARGET="$(OBJCOPY_FOR_TARGET)" OBJDUMP_FOR_TARGET="$(OBJDUMP_FOR_TARGET)" READELF_FOR_TARGET="$(READELF_FOR_TARGET)" +menuconfig-gui: +> @if command -v $(PYTHON) >/dev/null 2>&1; then \ +> $(PYTHON) scripts/menuconfig.py --gui $(MENUCONFIG_ARGS); \ +> elif command -v python >/dev/null 2>&1; then \ +> python scripts/menuconfig.py --gui $(MENUCONFIG_ARGS); \ +> else \ +> echo "python3/python not found"; \ +> exit 1; \ +> fi +> @$(MAKE) configure CMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) CMAKE_GENERATOR="$(CMAKE_GENERATOR)" CMAKE_EXTRA_ARGS="$(CMAKE_EXTRA_ARGS)" NO_COLOR="$(NO_COLOR)" LIMINE_SKIP_CONFIGURE="$(LIMINE_SKIP_CONFIGURE)" LIMINE_REF="$(LIMINE_REF)" LIMINE_REPO="$(LIMINE_REPO)" LIMINE_DIR="$(LIMINE_DIR)" LIMINE_BIN_DIR="$(LIMINE_BIN_DIR)" OBJCOPY_FOR_TARGET="$(OBJCOPY_FOR_TARGET)" OBJDUMP_FOR_TARGET="$(OBJDUMP_FOR_TARGET)" READELF_FOR_TARGET="$(READELF_FOR_TARGET)" + setup: configure > @$(CMAKE) --build $(CMAKE_BUILD_DIR) --target setup @@ -121,6 +132,7 @@ help: > @echo "CLeonOS (CMake-backed wrapper)" > @echo " make configure" > @echo " make menuconfig" +> @echo " make menuconfig-gui" > @echo " make setup" > @echo " make userapps" > @echo " make iso" diff --git a/clks/kernel/exec.c b/clks/kernel/exec.c index f082089..1aa941b 100644 --- a/clks/kernel/exec.c +++ b/clks/kernel/exec.c @@ -37,6 +37,10 @@ typedef u64 (*clks_exec_entry_fn)(void); #define CLKS_EXEC_FD_INHERIT ((u64)-1) #define CLKS_EXEC_DYNLIB_MAX 32U +#ifndef CLKS_CFG_EXEC_SERIAL_LOG +#define CLKS_CFG_EXEC_SERIAL_LOG 1 +#endif + #define CLKS_EXEC_ELF64_MAGIC_0 0x7FU #define CLKS_EXEC_ELF64_MAGIC_1 'E' #define CLKS_EXEC_ELF64_MAGIC_2 'L' @@ -193,6 +197,11 @@ static void clks_exec_serial_write_hex64(u64 value) { } static void clks_exec_log_info_serial(const char *message) { + if (CLKS_CFG_EXEC_SERIAL_LOG == 0) { + (void)message; + return; + } + clks_serial_write("[INFO][EXEC] "); if (message != CLKS_NULL) { @@ -203,6 +212,12 @@ static void clks_exec_log_info_serial(const char *message) { } static void clks_exec_log_hex_serial(const char *label, u64 value) { + if (CLKS_CFG_EXEC_SERIAL_LOG == 0) { + (void)label; + (void)value; + return; + } + clks_serial_write("[INFO][EXEC] "); if (label != CLKS_NULL) { diff --git a/clks/kernel/keyboard.c b/clks/kernel/keyboard.c index ff6e847..f56e826 100644 --- a/clks/kernel/keyboard.c +++ b/clks/kernel/keyboard.c @@ -30,6 +30,18 @@ #define CLKS_KBD_TTY_MAX 8U #define CLKS_KBD_DROP_LOG_EVERY 64ULL +#ifndef CLKS_CFG_KBD_TTY_SWITCH_HOTKEY +#define CLKS_CFG_KBD_TTY_SWITCH_HOTKEY 1 +#endif + +#ifndef CLKS_CFG_KBD_CTRL_SHORTCUTS +#define CLKS_CFG_KBD_CTRL_SHORTCUTS 1 +#endif + +#ifndef CLKS_CFG_KBD_FORCE_STOP_HOTKEY +#define CLKS_CFG_KBD_FORCE_STOP_HOTKEY 1 +#endif + static const char clks_kbd_map[128] = { [2] = '1', [3] = '2', [4] = '3', [5] = '4', [6] = '5', [7] = '6', [8] = '7', [9] = '8', [10] = '9', [11] = '0', [12] = '-', [13] = '=', [14] = '\b', [15] = '\t', @@ -178,6 +190,10 @@ static clks_bool clks_keyboard_ctrl_active(void) { static clks_bool clks_keyboard_try_emit_ctrl_shortcut(u8 code, u32 tty_index) { char shortcut = '\0'; + if (CLKS_CFG_KBD_CTRL_SHORTCUTS == 0) { + return CLKS_FALSE; + } + if (clks_keyboard_ctrl_active() == CLKS_FALSE) { return CLKS_FALSE; } @@ -208,6 +224,10 @@ static clks_bool clks_keyboard_try_force_stop_hotkey(u8 code) { u64 pid = 0ULL; u64 stop_ret; + if (CLKS_CFG_KBD_FORCE_STOP_HOTKEY == 0) { + return CLKS_FALSE; + } + if (code != CLKS_SC_C) { return CLKS_FALSE; } @@ -254,7 +274,20 @@ void clks_keyboard_init(void) { clks_kbd_pop_count = 0ULL; clks_kbd_drop_count = 0ULL; - clks_log(CLKS_LOG_INFO, "KBD", "ALT+F1..F4 TTY HOTKEY ONLINE"); + if (CLKS_CFG_KBD_TTY_SWITCH_HOTKEY != 0) { + clks_log(CLKS_LOG_INFO, "KBD", "ALT+F1..F4 TTY HOTKEY ONLINE"); + } else { + clks_log(CLKS_LOG_WARN, "KBD", "TTY SWITCH HOTKEY DISABLED BY MENUCONFIG"); + } + + if (CLKS_CFG_KBD_CTRL_SHORTCUTS == 0) { + clks_log(CLKS_LOG_WARN, "KBD", "CTRL SHORTCUTS DISABLED BY MENUCONFIG"); + } + + if (CLKS_CFG_KBD_FORCE_STOP_HOTKEY == 0) { + clks_log(CLKS_LOG_WARN, "KBD", "CTRL+ALT+C FORCE STOP DISABLED BY MENUCONFIG"); + } + clks_log(CLKS_LOG_INFO, "KBD", "PS2 INPUT QUEUE ONLINE"); clks_log_hex(CLKS_LOG_INFO, "KBD", "QUEUE_CAP", CLKS_KBD_INPUT_CAP); } @@ -350,7 +383,10 @@ void clks_keyboard_handle_scancode(u8 scancode) { return; } - if (clks_kbd_alt_down == CLKS_TRUE && code >= CLKS_SC_F1 && code <= CLKS_SC_F4) { + if (CLKS_CFG_KBD_TTY_SWITCH_HOTKEY != 0 && + clks_kbd_alt_down == CLKS_TRUE && + code >= CLKS_SC_F1 && + code <= CLKS_SC_F4) { u32 target = (u32)(code - CLKS_SC_F1); u32 before = clks_tty_active(); u32 after; diff --git a/clks/kernel/kmain.c b/clks/kernel/kmain.c index 6e74a13..4e70ae6 100644 --- a/clks/kernel/kmain.c +++ b/clks/kernel/kmain.c @@ -110,6 +110,18 @@ #define CLKS_CFG_IDLE_DEBUG_LOG 1 #endif +#ifndef CLKS_CFG_SCHED_TASK_COUNT_LOG +#define CLKS_CFG_SCHED_TASK_COUNT_LOG 1 +#endif + +#ifndef CLKS_CFG_INTERRUPT_READY_LOG +#define CLKS_CFG_INTERRUPT_READY_LOG 1 +#endif + +#ifndef CLKS_CFG_SHELL_MODE_LOG +#define CLKS_CFG_SHELL_MODE_LOG 1 +#endif + #if CLKS_CFG_KLOGD_TASK static void clks_task_klogd(u64 tick) { static u64 last_emit = 0ULL; @@ -173,7 +185,6 @@ void clks_kernel_main(void) { const struct limine_memmap_response *boot_memmap; struct clks_pmm_stats pmm_stats; struct clks_heap_stats heap_stats; - struct clks_scheduler_stats sched_stats; struct clks_fs_node_info fs_system_dir = {0}; u64 syscall_ticks; u64 fs_root_children; @@ -372,8 +383,14 @@ void clks_kernel_main(void) { clks_log(CLKS_LOG_WARN, "SCHED", "USRD TASK DISABLED BY MENUCONFIG"); #endif - sched_stats = clks_scheduler_get_stats(); - clks_log_hex(CLKS_LOG_INFO, "SCHED", "TASK_COUNT", sched_stats.task_count); +#if CLKS_CFG_SCHED_TASK_COUNT_LOG + { + struct clks_scheduler_stats sched_stats = clks_scheduler_get_stats(); + clks_log_hex(CLKS_LOG_INFO, "SCHED", "TASK_COUNT", sched_stats.task_count); + } +#else + clks_log(CLKS_LOG_WARN, "CFG", "SCHED TASK COUNT LOG DISABLED BY MENUCONFIG"); +#endif clks_service_init(); @@ -398,7 +415,9 @@ void clks_kernel_main(void) { clks_syscall_init(); clks_interrupts_init(); +#if CLKS_CFG_INTERRUPT_READY_LOG clks_log(CLKS_LOG_INFO, "INT", "IDT + PIC INITIALIZED"); +#endif #if CLKS_CFG_SYSCALL_TICK_QUERY syscall_ticks = clks_syscall_invoke_kernel(CLKS_SYSCALL_TIMER_TICKS, 0ULL, 0ULL, 0ULL); @@ -411,14 +430,18 @@ void clks_kernel_main(void) { clks_shell_init(); #if CLKS_CFG_USRD_TASK +#if CLKS_CFG_SHELL_MODE_LOG if (clks_userland_shell_auto_exec_enabled() == CLKS_TRUE) { clks_log(CLKS_LOG_INFO, "SHELL", "DEFAULT ENTER USER SHELL MODE"); } else { clks_log(CLKS_LOG_INFO, "SHELL", "KERNEL SHELL ACTIVE"); } +#endif #else +#if CLKS_CFG_SHELL_MODE_LOG clks_log(CLKS_LOG_WARN, "SHELL", "USRD TASK DISABLED; INTERACTIVE SHELL TICK OFF"); #endif +#endif #if CLKS_CFG_TTY_READY_LOG clks_log_hex(CLKS_LOG_INFO, "TTY", "COUNT", (u64)clks_tty_count()); diff --git a/clks/kernel/syscall.c b/clks/kernel/syscall.c index 7d36d12..8bb7015 100644 --- a/clks/kernel/syscall.c +++ b/clks/kernel/syscall.c @@ -35,6 +35,10 @@ #define CLKS_SYSCALL_STATS_MAX_ID CLKS_SYSCALL_EXEC_PATHV_IO #define CLKS_SYSCALL_STATS_RING_SIZE 256U +#ifndef CLKS_CFG_PROCFS +#define CLKS_CFG_PROCFS 1 +#endif + struct clks_syscall_frame { u64 rax; u64 rbx; @@ -1079,7 +1083,7 @@ static u64 clks_syscall_fs_child_count(u64 arg0) { return (u64)-1; } - if (clks_syscall_procfs_is_root(path) == CLKS_TRUE) { + if (CLKS_CFG_PROCFS != 0 && clks_syscall_procfs_is_root(path) == CLKS_TRUE) { return 2ULL + clks_exec_proc_count(); } @@ -1089,7 +1093,9 @@ static u64 clks_syscall_fs_child_count(u64 arg0) { return (u64)-1; } - if (clks_syscall_fs_is_root(path) == CLKS_TRUE && clks_syscall_fs_has_real_proc_dir() == CLKS_FALSE) { + if (CLKS_CFG_PROCFS != 0 && + clks_syscall_fs_is_root(path) == CLKS_TRUE && + clks_syscall_fs_has_real_proc_dir() == CLKS_FALSE) { return base_count + 1ULL; } @@ -1107,7 +1113,7 @@ static u64 clks_syscall_fs_get_child_name(u64 arg0, u64 arg1, u64 arg2) { return 0ULL; } - if (clks_syscall_procfs_is_root(path) == CLKS_TRUE) { + if (CLKS_CFG_PROCFS != 0 && clks_syscall_procfs_is_root(path) == CLKS_TRUE) { if (arg1 == 0ULL) { clks_memset((void *)arg2, 0, CLKS_SYSCALL_NAME_MAX); clks_memcpy((void *)arg2, "self", 5U); @@ -1142,7 +1148,9 @@ static u64 clks_syscall_fs_get_child_name(u64 arg0, u64 arg1, u64 arg2) { } } - if (clks_syscall_fs_is_root(path) == CLKS_TRUE && clks_syscall_fs_has_real_proc_dir() == CLKS_FALSE) { + if (CLKS_CFG_PROCFS != 0 && + clks_syscall_fs_is_root(path) == CLKS_TRUE && + clks_syscall_fs_has_real_proc_dir() == CLKS_FALSE) { if (arg1 == 0ULL) { clks_memset((void *)arg2, 0, CLKS_SYSCALL_NAME_MAX); clks_memcpy((void *)arg2, "proc", 5U); @@ -1177,9 +1185,10 @@ static u64 clks_syscall_fs_read(u64 arg0, u64 arg1, u64 arg2) { return 0ULL; } - if (clks_syscall_procfs_is_list(path) == CLKS_TRUE || - clks_syscall_procfs_is_self(path) == CLKS_TRUE || - clks_syscall_procfs_parse_pid(path, &file_size) == CLKS_TRUE) { + if (CLKS_CFG_PROCFS != 0 && + (clks_syscall_procfs_is_list(path) == CLKS_TRUE || + clks_syscall_procfs_is_self(path) == CLKS_TRUE || + clks_syscall_procfs_parse_pid(path, &file_size) == CLKS_TRUE)) { char proc_text[CLKS_SYSCALL_PROCFS_TEXT_MAX]; usize proc_len = 0U; @@ -1479,15 +1488,16 @@ static u64 clks_syscall_fs_stat_type(u64 arg0) { return (u64)-1; } - if (clks_syscall_procfs_is_root(path) == CLKS_TRUE) { + if (CLKS_CFG_PROCFS != 0 && clks_syscall_procfs_is_root(path) == CLKS_TRUE) { return (u64)CLKS_FS_NODE_DIR; } - if (clks_syscall_procfs_is_list(path) == CLKS_TRUE || clks_syscall_procfs_is_self(path) == CLKS_TRUE) { + if (CLKS_CFG_PROCFS != 0 && + (clks_syscall_procfs_is_list(path) == CLKS_TRUE || clks_syscall_procfs_is_self(path) == CLKS_TRUE)) { return (u64)CLKS_FS_NODE_FILE; } - if (clks_syscall_procfs_snapshot_for_path(path, &snap) == CLKS_TRUE) { + if (CLKS_CFG_PROCFS != 0 && clks_syscall_procfs_snapshot_for_path(path, &snap) == CLKS_TRUE) { return (u64)CLKS_FS_NODE_FILE; } @@ -1508,11 +1518,12 @@ static u64 clks_syscall_fs_stat_size(u64 arg0) { return (u64)-1; } - if (clks_syscall_procfs_is_root(path) == CLKS_TRUE) { + if (CLKS_CFG_PROCFS != 0 && clks_syscall_procfs_is_root(path) == CLKS_TRUE) { return 0ULL; } - if (clks_syscall_procfs_render_file(path, proc_text, sizeof(proc_text), &proc_len) == CLKS_TRUE) { + if (CLKS_CFG_PROCFS != 0 && + clks_syscall_procfs_render_file(path, proc_text, sizeof(proc_text), &proc_len) == CLKS_TRUE) { return (u64)proc_len; } diff --git a/clks/kernel/userland.c b/clks/kernel/userland.c index a360964..1057245 100644 --- a/clks/kernel/userland.c +++ b/clks/kernel/userland.c @@ -11,6 +11,14 @@ #define CLKS_CFG_USERLAND_AUTO_EXEC 1 #endif +#ifndef CLKS_CFG_USER_INIT_SCRIPT_PROBE +#define CLKS_CFG_USER_INIT_SCRIPT_PROBE 1 +#endif + +#ifndef CLKS_CFG_USER_SYSTEM_APP_PROBE +#define CLKS_CFG_USER_SYSTEM_APP_PROBE 1 +#endif + static clks_bool clks_user_shell_ready = CLKS_FALSE; static clks_bool clks_user_shell_exec_requested_flag = CLKS_FALSE; static clks_bool clks_user_shell_exec_enabled = CLKS_FALSE; @@ -101,8 +109,13 @@ clks_bool clks_userland_init(void) { clks_user_shell_ready = CLKS_TRUE; clks_log(CLKS_LOG_INFO, "USER", "SHELL COMMAND ABI READY"); +#if CLKS_CFG_USER_INIT_SCRIPT_PROBE clks_userland_probe_init_script(); +#else + clks_log(CLKS_LOG_WARN, "USER", "INIT SCRIPT PROBE DISABLED BY MENUCONFIG"); +#endif +#if CLKS_CFG_USER_SYSTEM_APP_PROBE if (clks_userland_probe_elf("/system/elfrunner.elf", "ELFRUNNER ELF READY") == CLKS_FALSE) { return CLKS_FALSE; } @@ -110,6 +123,9 @@ clks_bool clks_userland_init(void) { if (clks_userland_probe_elf("/system/memc.elf", "MEMC ELF READY") == CLKS_FALSE) { return CLKS_FALSE; } +#else + clks_log(CLKS_LOG_WARN, "USER", "SYSTEM APP PROBE DISABLED BY MENUCONFIG"); +#endif if (clks_user_shell_exec_enabled == CLKS_TRUE) { clks_log(CLKS_LOG_INFO, "USER", "USER SHELL AUTO EXEC ENABLED"); diff --git a/configs/menuconfig/clks_features.json b/configs/menuconfig/clks_features.json index 7ff4d63..32d4bdc 100644 --- a/configs/menuconfig/clks_features.json +++ b/configs/menuconfig/clks_features.json @@ -131,6 +131,66 @@ "title": "Idle Loop Debug Log", "description": "Print debug log before entering kernel idle loop.", "default": true + }, + { + "key": "CLEONOS_CLKS_ENABLE_PROCFS", + "title": "Virtual /proc", + "description": "Enable virtual procfs paths (/proc, /proc/list, /proc/self, /proc/) in syscall FS layer.", + "default": true + }, + { + "key": "CLEONOS_CLKS_ENABLE_EXEC_SERIAL_LOG", + "title": "EXEC Serial Logs", + "description": "Print EXEC run/return/path logs to serial output.", + "default": true + }, + { + "key": "CLEONOS_CLKS_ENABLE_KBD_TTY_SWITCH_HOTKEY", + "title": "Keyboard TTY Switch Hotkey", + "description": "Enable ALT+F1..F4 keyboard hotkey for active TTY switching.", + "default": true + }, + { + "key": "CLEONOS_CLKS_ENABLE_KBD_CTRL_SHORTCUTS", + "title": "Keyboard Ctrl Shortcuts", + "description": "Enable Ctrl+A/C/V shortcuts for input selection/copy/paste.", + "default": true + }, + { + "key": "CLEONOS_CLKS_ENABLE_KBD_FORCE_STOP_HOTKEY", + "title": "Keyboard Force-Stop Hotkey", + "description": "Enable Ctrl+Alt+C force-stop for current running user process.", + "default": true + }, + { + "key": "CLEONOS_CLKS_ENABLE_USER_INIT_SCRIPT_PROBE", + "title": "User Init Script Probe", + "description": "Probe and log /shell/init.cmd presence during userland init.", + "default": true + }, + { + "key": "CLEONOS_CLKS_ENABLE_USER_SYSTEM_APP_PROBE", + "title": "User System App Probe", + "description": "Probe /system/elfrunner.elf and /system/memc.elf during userland init.", + "default": true + }, + { + "key": "CLEONOS_CLKS_ENABLE_SCHED_TASK_COUNT_LOG", + "title": "Scheduler Task Count Log", + "description": "Print scheduler task count after scheduler initialization.", + "default": true + }, + { + "key": "CLEONOS_CLKS_ENABLE_INTERRUPT_READY_LOG", + "title": "Interrupt Ready Log", + "description": "Print IDT/PIC initialized log after interrupt setup.", + "default": true + }, + { + "key": "CLEONOS_CLKS_ENABLE_SHELL_MODE_LOG", + "title": "Shell Mode Log", + "description": "Print whether boot default mode is user shell or kernel shell.", + "default": true } ] } diff --git a/scripts/__pycache__/menuconfig.cpython-312.pyc b/scripts/__pycache__/menuconfig.cpython-312.pyc index e93bca78723a940d4262080f6b0dbae28123e91d..33898a018d87ec4b1f7aa36a74092e1c5391df13 100644 GIT binary patch delta 22541 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 N=CaNr^Xo;3{~wXK7%>0< diff --git a/scripts/menuconfig.py b/scripts/menuconfig.py index b328be2..06a77b1 100644 --- a/scripts/menuconfig.py +++ b/scripts/menuconfig.py @@ -28,6 +28,15 @@ try: except Exception: curses = None +try: + from PySide6 import QtCore, QtWidgets +except Exception: + try: + from PySide2 import QtCore, QtWidgets + except Exception: + QtCore = None + QtWidgets = None + ROOT_DIR = Path(__file__).resolve().parent.parent APPS_DIR = ROOT_DIR / "cleonos" / "c" / "apps" @@ -652,6 +661,325 @@ def interactive_menu_ncurses(clks_options: List[OptionItem], user_options: List[ return bool(curses.wrapper(lambda stdscr: _run_ncurses_main(stdscr, clks_options, user_options, values))) +def interactive_menu_gui(clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, bool]) -> bool: + if QtWidgets is None or QtCore is None: + raise RuntimeError("python PySide unavailable (install PySide6, or use --plain)") + + if os.name != "nt" and not os.environ.get("DISPLAY") and not os.environ.get("WAYLAND_DISPLAY"): + raise RuntimeError("GUI mode requires a desktop display (DISPLAY/WAYLAND_DISPLAY)") + + app = QtWidgets.QApplication.instance() + owns_app = False + + if app is None: + app = QtWidgets.QApplication(["menuconfig-gui"]) + owns_app = True + + qt_checked = getattr(QtCore.Qt, "Checked", QtCore.Qt.CheckState.Checked) + qt_unchecked = getattr(QtCore.Qt, "Unchecked", QtCore.Qt.CheckState.Unchecked) + qt_horizontal = getattr(QtCore.Qt, "Horizontal", QtCore.Qt.Orientation.Horizontal) + qt_item_enabled = getattr(QtCore.Qt, "ItemIsEnabled", QtCore.Qt.ItemFlag.ItemIsEnabled) + qt_item_selectable = getattr(QtCore.Qt, "ItemIsSelectable", QtCore.Qt.ItemFlag.ItemIsSelectable) + qt_item_checkable = getattr(QtCore.Qt, "ItemIsUserCheckable", QtCore.Qt.ItemFlag.ItemIsUserCheckable) + + resize_to_contents = getattr( + QtWidgets.QHeaderView, + "ResizeToContents", + QtWidgets.QHeaderView.ResizeMode.ResizeToContents, + ) + stretch_mode = getattr( + QtWidgets.QHeaderView, + "Stretch", + QtWidgets.QHeaderView.ResizeMode.Stretch, + ) + select_rows = getattr( + QtWidgets.QAbstractItemView, + "SelectRows", + QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows, + ) + extended_selection = getattr( + QtWidgets.QAbstractItemView, + "ExtendedSelection", + QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection, + ) + + result = {"save": False} + + dialog = QtWidgets.QDialog() + dialog.setWindowTitle("CLeonOS menuconfig (PySide)") + dialog.resize(1180, 760) + dialog.setMinimumSize(920, 560) + + if os.name == "nt": + dialog.setWindowState(dialog.windowState() | QtCore.Qt.WindowMaximized) + + root_layout = QtWidgets.QVBoxLayout(dialog) + root_layout.setContentsMargins(12, 10, 12, 12) + root_layout.setSpacing(8) + + header_title = QtWidgets.QLabel("CLeonOS menuconfig") + header_font = header_title.font() + header_font.setPointSize(header_font.pointSize() + 4) + header_font.setBold(True) + header_title.setFont(header_font) + root_layout.addWidget(header_title) + + root_layout.addWidget(QtWidgets.QLabel("Window mode (PySide): configure CLKS features and user apps, then save.")) + + summary_label = QtWidgets.QLabel("") + root_layout.addWidget(summary_label) + + tabs = QtWidgets.QTabWidget() + root_layout.addWidget(tabs, 1) + + def update_summary() -> None: + clks_on = sum(1 for item in clks_options if values.get(item.key, item.default)) + user_on = sum(1 for item in user_options if values.get(item.key, item.default)) + total = len(clks_options) + len(user_options) + summary_label.setText( + f"CLKS: {clks_on}/{len(clks_options)} enabled " + f"User: {user_on}/{len(user_options)} enabled " + f"Total: {clks_on + user_on}/{total}" + ) + + class _SectionPanel(QtWidgets.QWidget): + def __init__(self, title: str, options: List[OptionItem]): + super().__init__() + self.options = options + self._updating = False + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(8) + + toolbar = QtWidgets.QHBoxLayout() + title_label = QtWidgets.QLabel(title) + title_font = title_label.font() + title_font.setBold(True) + title_label.setFont(title_font) + toolbar.addWidget(title_label) + toolbar.addStretch(1) + + toggle_btn = QtWidgets.QPushButton("Toggle Selected") + enable_all_btn = QtWidgets.QPushButton("Enable All") + disable_all_btn = QtWidgets.QPushButton("Disable All") + toolbar.addWidget(enable_all_btn) + toolbar.addWidget(disable_all_btn) + toolbar.addWidget(toggle_btn) + layout.addLayout(toolbar) + + splitter = QtWidgets.QSplitter(qt_horizontal) + layout.addWidget(splitter, 1) + + left = QtWidgets.QWidget() + left_layout = QtWidgets.QVBoxLayout(left) + left_layout.setContentsMargins(0, 0, 0, 0) + self.table = QtWidgets.QTableWidget(len(options), 2) + self.table.setHorizontalHeaderLabels(["On", "Option"]) + self.table.verticalHeader().setVisible(False) + self.table.horizontalHeader().setSectionResizeMode(0, resize_to_contents) + self.table.horizontalHeader().setSectionResizeMode(1, stretch_mode) + self.table.setSelectionBehavior(select_rows) + self.table.setSelectionMode(extended_selection) + self.table.setAlternatingRowColors(True) + left_layout.addWidget(self.table) + splitter.addWidget(left) + + right = QtWidgets.QWidget() + right_layout = QtWidgets.QVBoxLayout(right) + right_layout.setContentsMargins(0, 0, 0, 0) + self.state_label = QtWidgets.QLabel("State: -") + self.key_label = QtWidgets.QLabel("Key: -") + self.detail_text = QtWidgets.QPlainTextEdit() + self.detail_text.setReadOnly(True) + right_layout.addWidget(self.state_label) + right_layout.addWidget(self.key_label) + right_layout.addWidget(self.detail_text, 1) + splitter.addWidget(right) + splitter.setStretchFactor(0, 3) + splitter.setStretchFactor(1, 2) + + toggle_btn.clicked.connect(self.toggle_selected) + enable_all_btn.clicked.connect(self.enable_all) + disable_all_btn.clicked.connect(self.disable_all) + self.table.itemSelectionChanged.connect(self._on_selection_changed) + self.table.itemChanged.connect(self._on_item_changed) + + self.refresh(keep_selection=False) + if self.options: + self.table.selectRow(0) + self._show_detail(0) + + def _selected_rows(self) -> List[int]: + rows = [] + model = self.table.selectionModel() + + if model is None: + return rows + + for idx in model.selectedRows(): + row = idx.row() + if row not in rows: + rows.append(row) + + rows.sort() + return rows + + def _show_detail(self, row: int) -> None: + if row < 0 or row >= len(self.options): + self.state_label.setText("State: -") + self.key_label.setText("Key: -") + self.detail_text.setPlainText("") + return + + item = self.options[row] + enabled = values.get(item.key, item.default) + self.state_label.setText(f"State: {'ENABLED' if enabled else 'DISABLED'}") + self.key_label.setText(f"Key: {item.key}") + self.detail_text.setPlainText(f"{item.title}\n\n{item.description}") + + def _on_selection_changed(self) -> None: + rows = self._selected_rows() + + if len(rows) == 1: + self._show_detail(rows[0]) + return + + if len(rows) > 1: + self.state_label.setText(f"State: {len(rows)} items selected") + self.key_label.setText("Key: ") + self.detail_text.setPlainText("Multiple options selected.\nUse Toggle Selected to flip all selected entries.") + return + + self._show_detail(-1) + + def _on_item_changed(self, changed_item) -> None: + if self._updating or changed_item is None: + return + + if changed_item.column() != 0: + return + + row = changed_item.row() + + if row < 0 or row >= len(self.options): + return + + self.options[row] + values[self.options[row].key] = changed_item.checkState() == qt_checked + self._on_selection_changed() + update_summary() + + def refresh(self, keep_selection: bool = True) -> None: + prev_rows = self._selected_rows() if keep_selection else [] + self._updating = True + + self.table.setRowCount(len(self.options)) + + for row, item in enumerate(self.options): + enabled = values.get(item.key, item.default) + check_item = self.table.item(row, 0) + + if check_item is None: + check_item = QtWidgets.QTableWidgetItem("") + check_item.setFlags(qt_item_enabled | qt_item_selectable | qt_item_checkable) + self.table.setItem(row, 0, check_item) + + check_item.setCheckState(qt_checked if enabled else qt_unchecked) + + title_item = self.table.item(row, 1) + if title_item is None: + title_item = QtWidgets.QTableWidgetItem(item.title) + title_item.setFlags(qt_item_enabled | qt_item_selectable) + self.table.setItem(row, 1, title_item) + else: + title_item.setText(item.title) + + self._updating = False + + self.table.clearSelection() + if keep_selection: + for row in prev_rows: + if 0 <= row < len(self.options): + self.table.selectRow(row) + + self._on_selection_changed() + update_summary() + + def toggle_selected(self) -> None: + rows = self._selected_rows() + if not rows: + return + + self._updating = True + for row in rows: + item = self.options[row] + new_state = not values.get(item.key, item.default) + values[item.key] = new_state + check_item = self.table.item(row, 0) + if check_item is not None: + check_item.setCheckState(qt_checked if new_state else qt_unchecked) + self._updating = False + self._on_selection_changed() + update_summary() + + def enable_all(self) -> None: + self._updating = True + for row, item in enumerate(self.options): + values[item.key] = True + check_item = self.table.item(row, 0) + if check_item is not None: + check_item.setCheckState(qt_checked) + self._updating = False + self._on_selection_changed() + update_summary() + + def disable_all(self) -> None: + self._updating = True + for row, item in enumerate(self.options): + values[item.key] = False + check_item = self.table.item(row, 0) + if check_item is not None: + check_item.setCheckState(qt_unchecked) + self._updating = False + self._on_selection_changed() + update_summary() + + clks_panel = _SectionPanel("CLKS Features", clks_options) + user_panel = _SectionPanel("User Apps", user_options) + tabs.addTab(clks_panel, "CLKS") + tabs.addTab(user_panel, "USER") + update_summary() + + footer = QtWidgets.QHBoxLayout() + footer.addWidget(QtWidgets.QLabel("Tip: select rows and click Toggle Selected.")) + footer.addStretch(1) + + save_btn = QtWidgets.QPushButton("Save and Exit") + quit_btn = QtWidgets.QPushButton("Quit without Saving") + footer.addWidget(save_btn) + footer.addWidget(quit_btn) + root_layout.addLayout(footer) + + def _on_save() -> None: + result["save"] = True + dialog.accept() + + def _on_quit() -> None: + result["save"] = False + dialog.reject() + + save_btn.clicked.connect(_on_save) + quit_btn.clicked.connect(_on_quit) + + dialog.exec() + + if owns_app: + app.quit() + + return result["save"] + + def write_outputs(all_values: Dict[str, bool], ordered_options: List[OptionItem]) -> None: MENUCONFIG_DIR.mkdir(parents=True, exist_ok=True) @@ -719,6 +1047,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--defaults", action="store_true", help="ignore previous .config and use defaults") parser.add_argument("--non-interactive", action="store_true", help="save config without opening interactive menu") parser.add_argument("--plain", action="store_true", help="use legacy plain-text menu instead of ncurses") + parser.add_argument("--gui", action="store_true", help="use GUI window mode (PySide)") parser.add_argument( "--set", action="append", @@ -732,6 +1061,9 @@ def parse_args() -> argparse.Namespace: def main() -> int: args = parse_args() + if args.gui and args.plain: + raise RuntimeError("--gui and --plain cannot be used together") + clks_options = load_clks_options() user_options = discover_user_apps() all_options = clks_options + user_options @@ -742,12 +1074,15 @@ def main() -> int: should_save = args.non_interactive if not args.non_interactive: - if not sys.stdin.isatty(): - raise RuntimeError("menuconfig requires interactive tty (or use --non-interactive)") - if args.plain: - should_save = interactive_menu(clks_options, user_options, values) + if args.gui: + should_save = interactive_menu_gui(clks_options, user_options, values) else: - should_save = interactive_menu_ncurses(clks_options, user_options, values) + if not sys.stdin.isatty(): + raise RuntimeError("menuconfig requires interactive tty (or use --non-interactive or --gui)") + if args.plain: + should_save = interactive_menu(clks_options, user_options, values) + else: + should_save = interactive_menu_ncurses(clks_options, user_options, values) if not should_save: print("menuconfig: no changes saved")