Compare commits

...

3 Commits

Author SHA1 Message Date
Leonmmcoset
e7062c3045 menuconfig预设 2026-04-18 19:31:19 +08:00
Leonmmcoset
af477d0602 menuconfig++ 2026-04-18 19:22:25 +08:00
Leonmmcoset
8803d5a5d4 /dev 2026-04-18 18:03:52 +08:00
24 changed files with 857 additions and 28 deletions

View File

@@ -3,6 +3,21 @@ name: Build CLeonOS
on:
push:
pull_request:
workflow_dispatch:
inputs:
menuconfig_preset:
description: "menuconfig preset (full/minimal/dev)"
type: choice
options:
- full
- minimal
- dev
required: false
default: "full"
menuconfig_overrides:
description: "Optional extra menuconfig args, e.g. --set CLEONOS_CLKS_ENABLE_AUDIO=OFF"
required: false
default: ""
permissions:
contents: read
@@ -10,6 +25,9 @@ permissions:
jobs:
build-os:
runs-on: ubuntu-latest
env:
MENUCONFIG_PRESET: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.menuconfig_preset || 'full' }}
MENUCONFIG_OVERRIDES: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.menuconfig_overrides || '' }}
steps:
- name: Checkout
@@ -39,6 +57,18 @@ jobs:
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Generate menuconfig (shared preset)
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 --preset "${MENUCONFIG_PRESET}" "${extra_args[@]}"
echo "menuconfig generated with preset=${MENUCONFIG_PRESET}"
- name: Configure
run: |
cmake -S . -B build-cmake \

2
.gitignore vendored
View File

@@ -16,3 +16,5 @@ Testing/
# menuconfig generated
configs/menuconfig/.config.json
configs/menuconfig/config.cmake
venv/

View File

@@ -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
@@ -636,7 +666,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E rm -rf "${RAMDISK_ROOT}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${RAMDISK_ROOT}"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/ramdisk" "${RAMDISK_ROOT}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${RAMDISK_ROOT}/system" "${RAMDISK_ROOT}/shell" "${RAMDISK_ROOT}/driver"
COMMAND ${CMAKE_COMMAND} -E make_directory "${RAMDISK_ROOT}/system" "${RAMDISK_ROOT}/shell" "${RAMDISK_ROOT}/driver" "${RAMDISK_ROOT}/dev"
COMMAND ${CMAKE_COMMAND} -E copy "${KERNEL_SYMBOLS_FILE}" "${RAMDISK_ROOT}/system/kernel.sym"
${RAMDISK_COPY_COMMANDS}
COMMAND ${CMAKE_COMMAND} -E touch "${RAMDISK_ROOT_STAMP}"
@@ -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"

View File

@@ -17,6 +17,7 @@ OBJDUMP_FOR_TARGET ?=
READELF_FOR_TARGET ?=
PYTHON ?= python3
MENUCONFIG_ARGS ?=
MENUCONFIG_PRESET ?=
ifeq ($(strip $(CMAKE_GENERATOR)),)
GEN_ARG :=
@@ -25,6 +26,7 @@ GEN_ARG := -G "$(CMAKE_GENERATOR)"
endif
CMAKE_PASSTHROUGH_ARGS :=
MENUCONFIG_PRESET_ARG := $(if $(strip $(MENUCONFIG_PRESET)),--preset $(MENUCONFIG_PRESET),)
ifneq ($(strip $(LIMINE_SKIP_CONFIGURE)),)
CMAKE_PASSTHROUGH_ARGS += -DLIMINE_SKIP_CONFIGURE=$(LIMINE_SKIP_CONFIGURE)
@@ -51,7 +53,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
@@ -64,9 +66,20 @@ reconfigure:
menuconfig:
> @if command -v $(PYTHON) >/dev/null 2>&1; then \
> $(PYTHON) scripts/menuconfig.py $(MENUCONFIG_ARGS); \
> $(PYTHON) scripts/menuconfig.py $(MENUCONFIG_PRESET_ARG) $(MENUCONFIG_ARGS); \
> elif command -v python >/dev/null 2>&1; then \
> python scripts/menuconfig.py $(MENUCONFIG_ARGS); \
> python scripts/menuconfig.py $(MENUCONFIG_PRESET_ARG) $(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)"
menuconfig-gui:
> @if command -v $(PYTHON) >/dev/null 2>&1; then \
> $(PYTHON) scripts/menuconfig.py --gui $(MENUCONFIG_PRESET_ARG) $(MENUCONFIG_ARGS); \
> elif command -v python >/dev/null 2>&1; then \
> python scripts/menuconfig.py --gui $(MENUCONFIG_PRESET_ARG) $(MENUCONFIG_ARGS); \
> else \
> echo "python3/python not found"; \
> exit 1; \
@@ -121,6 +134,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"
@@ -133,3 +147,7 @@ help:
> @echo " make configure CMAKE_EXTRA_ARGS='-DLIMINE_SKIP_CONFIGURE=1 -DOBJCOPY_FOR_TARGET=objcopy'"
> @echo "Direct passthrough is also supported:"
> @echo " make run LIMINE_SKIP_CONFIGURE=1"
> @echo "Preset examples:"
> @echo " make menuconfig MENUCONFIG_PRESET=full"
> @echo " make menuconfig MENUCONFIG_PRESET=minimal"
> @echo " make menuconfig-gui MENUCONFIG_PRESET=dev"

View File

@@ -7,6 +7,7 @@ static int ush_cmd_fsstat(void) {
ush_print_kv_hex(" SHELL_CHILDREN", cleonos_sys_fs_child_count("/shell"));
ush_print_kv_hex(" TEMP_CHILDREN", cleonos_sys_fs_child_count("/temp"));
ush_print_kv_hex(" DRIVER_CHILDREN", cleonos_sys_fs_child_count("/driver"));
ush_print_kv_hex(" DEV_CHILDREN", cleonos_sys_fs_child_count("/dev"));
return 1;
}

View File

@@ -2033,6 +2033,7 @@ static int ush_cmd_fsstat(void) {
ush_print_kv_hex(" SHELL_CHILDREN", cleonos_sys_fs_child_count("/shell"));
ush_print_kv_hex(" TEMP_CHILDREN", cleonos_sys_fs_child_count("/temp"));
ush_print_kv_hex(" DRIVER_CHILDREN", cleonos_sys_fs_child_count("/driver"));
ush_print_kv_hex(" DEV_CHILDREN", cleonos_sys_fs_child_count("/dev"));
return 1;
}

View File

@@ -16,6 +16,7 @@ static int ush_cmd_fsstat(void) {
ush_print_kv_hex(" SHELL_CHILDREN", cleonos_sys_fs_child_count("/shell"));
ush_print_kv_hex(" TEMP_CHILDREN", cleonos_sys_fs_child_count("/temp"));
ush_print_kv_hex(" DRIVER_CHILDREN", cleonos_sys_fs_child_count("/driver"));
ush_print_kv_hex(" DEV_CHILDREN", cleonos_sys_fs_child_count("/dev"));
return 1;
}

View File

@@ -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'
@@ -55,6 +59,9 @@ enum clks_exec_fd_kind {
CLKS_EXEC_FD_KIND_NONE = 0,
CLKS_EXEC_FD_KIND_TTY = 1,
CLKS_EXEC_FD_KIND_FILE = 2,
CLKS_EXEC_FD_KIND_DEV_NULL = 3,
CLKS_EXEC_FD_KIND_DEV_ZERO = 4,
CLKS_EXEC_FD_KIND_DEV_RANDOM = 5,
};
enum clks_exec_proc_state {
@@ -159,6 +166,7 @@ static u64 clks_exec_requests = 0ULL;
static u64 clks_exec_success = 0ULL;
static u32 clks_exec_running_depth = 0U;
static clks_bool clks_exec_pending_dispatch_active = CLKS_FALSE;
static u64 clks_exec_random_state = 0xA5A55A5A12345678ULL;
static struct clks_exec_proc_record clks_exec_proc_table[CLKS_EXEC_MAX_PROCS];
static u64 clks_exec_next_pid = 1ULL;
@@ -189,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) {
@@ -199,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) {
@@ -800,6 +819,34 @@ static clks_bool clks_exec_path_is_dev_tty(const char *path) {
return (path != CLKS_NULL && clks_strcmp(path, "/dev/tty") == 0) ? CLKS_TRUE : CLKS_FALSE;
}
static clks_bool clks_exec_path_is_dev_null(const char *path) {
return (path != CLKS_NULL && clks_strcmp(path, "/dev/null") == 0) ? CLKS_TRUE : CLKS_FALSE;
}
static clks_bool clks_exec_path_is_dev_zero(const char *path) {
return (path != CLKS_NULL && clks_strcmp(path, "/dev/zero") == 0) ? CLKS_TRUE : CLKS_FALSE;
}
static clks_bool clks_exec_path_is_dev_random(const char *path) {
if (path == CLKS_NULL) {
return CLKS_FALSE;
}
if (clks_strcmp(path, "/dev/random") == 0) {
return CLKS_TRUE;
}
return (clks_strcmp(path, "/dev/urandom") == 0) ? CLKS_TRUE : CLKS_FALSE;
}
static u8 clks_exec_random_next_byte(void) {
clks_exec_random_state ^= (clks_interrupts_timer_ticks() + 0x9E3779B97F4A7C15ULL);
clks_exec_random_state ^= (clks_exec_random_state << 13);
clks_exec_random_state ^= (clks_exec_random_state >> 7);
clks_exec_random_state ^= (clks_exec_random_state << 17);
return (u8)(clks_exec_random_state & 0xFFULL);
}
static void clks_exec_fd_init_defaults(struct clks_exec_proc_record *proc) {
if (proc == CLKS_NULL) {
return;
@@ -1457,6 +1504,7 @@ void clks_exec_init(void) {
clks_exec_success = 0ULL;
clks_exec_running_depth = 0U;
clks_exec_pending_dispatch_active = CLKS_FALSE;
clks_exec_random_state = 0xA5A55A5A12345678ULL;
clks_exec_next_pid = 1ULL;
clks_exec_pid_stack_depth = 0U;
clks_memset(clks_exec_pid_stack, 0, sizeof(clks_exec_pid_stack));
@@ -1647,6 +1695,29 @@ u64 clks_exec_fd_open(const char *path, u64 flags, u64 mode) {
return (u64)fd_slot;
}
if (clks_exec_path_is_dev_null(path) == CLKS_TRUE ||
clks_exec_path_is_dev_zero(path) == CLKS_TRUE ||
clks_exec_path_is_dev_random(path) == CLKS_TRUE) {
struct clks_exec_fd_entry *entry = &proc->fds[(u32)fd_slot];
clks_memset(entry, 0, sizeof(*entry));
entry->used = CLKS_TRUE;
entry->flags = flags;
entry->offset = 0ULL;
entry->tty_index = proc->tty_index;
entry->path[0] = '\0';
if (clks_exec_path_is_dev_null(path) == CLKS_TRUE) {
entry->kind = CLKS_EXEC_FD_KIND_DEV_NULL;
} else if (clks_exec_path_is_dev_zero(path) == CLKS_TRUE) {
entry->kind = CLKS_EXEC_FD_KIND_DEV_ZERO;
} else {
entry->kind = CLKS_EXEC_FD_KIND_DEV_RANDOM;
}
return (u64)fd_slot;
}
if (clks_fs_stat(path, &info) == CLKS_FALSE) {
if ((flags & CLKS_EXEC_O_CREAT) == 0ULL || clks_exec_fd_can_write(flags) == CLKS_FALSE) {
return (u64)-1;
@@ -1722,6 +1793,28 @@ u64 clks_exec_fd_read(u64 fd, void *out_buffer, u64 size) {
return count;
}
if (entry->kind == CLKS_EXEC_FD_KIND_DEV_NULL) {
return 0ULL;
}
if (entry->kind == CLKS_EXEC_FD_KIND_DEV_ZERO) {
clks_memset(out_buffer, 0, (usize)size);
entry->offset += size;
return size;
}
if (entry->kind == CLKS_EXEC_FD_KIND_DEV_RANDOM) {
u8 *dst = (u8 *)out_buffer;
u64 i;
for (i = 0ULL; i < size; i++) {
dst[(usize)i] = clks_exec_random_next_byte();
}
entry->offset += size;
return size;
}
if (entry->kind == CLKS_EXEC_FD_KIND_FILE) {
return clks_exec_fd_file_read(entry, out_buffer, size);
}
@@ -1756,6 +1849,13 @@ u64 clks_exec_fd_write(u64 fd, const void *buffer, u64 size) {
return size;
}
if (entry->kind == CLKS_EXEC_FD_KIND_DEV_NULL ||
entry->kind == CLKS_EXEC_FD_KIND_DEV_ZERO ||
entry->kind == CLKS_EXEC_FD_KIND_DEV_RANDOM) {
entry->offset += size;
return size;
}
if (entry->kind == CLKS_EXEC_FD_KIND_FILE) {
return clks_exec_fd_file_write(entry, buffer, size);
}

View File

@@ -535,8 +535,12 @@ void clks_fs_init(void) {
return;
}
if (clks_fs_require_directory("/dev") == CLKS_FALSE) {
return;
}
clks_fs_ready = CLKS_TRUE;
clks_log(CLKS_LOG_INFO, "FS", "LAYOUT /SYSTEM /SHELL /TEMP /DRIVER OK");
clks_log(CLKS_LOG_INFO, "FS", "LAYOUT /SYSTEM /SHELL /TEMP /DRIVER /DEV OK");
}
clks_bool clks_fs_is_ready(void) {
@@ -915,4 +919,4 @@ u64 clks_fs_node_count(void) {
}
return used;
}
}

View File

@@ -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;

View File

@@ -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());

View File

@@ -1173,6 +1173,7 @@ static clks_bool clks_shell_cmd_fsstat(void) {
clks_shell_print_kv_hex(" SHELL_CHILDREN", clks_fs_count_children("/shell"));
clks_shell_print_kv_hex(" TEMP_CHILDREN", clks_fs_count_children("/temp"));
clks_shell_print_kv_hex(" DRIVER_CHILDREN", clks_fs_count_children("/driver"));
clks_shell_print_kv_hex(" DEV_CHILDREN", clks_fs_count_children("/dev"));
return CLKS_TRUE;
}

View File

@@ -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;
@@ -279,6 +283,20 @@ static clks_bool clks_syscall_procfs_is_root(const char *path) {
return (path != CLKS_NULL && clks_strcmp(path, "/proc") == 0) ? CLKS_TRUE : CLKS_FALSE;
}
static clks_bool clks_syscall_fs_is_root(const char *path) {
return (path != CLKS_NULL && clks_strcmp(path, "/") == 0) ? CLKS_TRUE : CLKS_FALSE;
}
static clks_bool clks_syscall_fs_has_real_proc_dir(void) {
struct clks_fs_node_info info;
if (clks_fs_stat("/proc", &info) == CLKS_FALSE) {
return CLKS_FALSE;
}
return (info.type == CLKS_FS_NODE_DIR) ? CLKS_TRUE : CLKS_FALSE;
}
static clks_bool clks_syscall_procfs_is_self(const char *path) {
return (path != CLKS_NULL && clks_strcmp(path, "/proc/self") == 0) ? CLKS_TRUE : CLKS_FALSE;
}
@@ -1059,16 +1077,29 @@ static clks_bool clks_syscall_procfs_render_file(const char *path,
static u64 clks_syscall_fs_child_count(u64 arg0) {
char path[CLKS_SYSCALL_PATH_MAX];
u64 base_count;
if (clks_syscall_copy_user_string(arg0, path, sizeof(path)) == CLKS_FALSE) {
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();
}
return clks_fs_count_children(path);
base_count = clks_fs_count_children(path);
if (base_count == (u64)-1) {
return (u64)-1;
}
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;
}
return base_count;
}
static u64 clks_syscall_fs_get_child_name(u64 arg0, u64 arg1, u64 arg2) {
@@ -1082,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);
@@ -1117,6 +1148,22 @@ static u64 clks_syscall_fs_get_child_name(u64 arg0, u64 arg1, u64 arg2) {
}
}
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);
return 1ULL;
}
if (clks_fs_get_child_name(path, arg1 - 1ULL, (char *)arg2, (usize)CLKS_SYSCALL_NAME_MAX) == CLKS_FALSE) {
return 0ULL;
}
return 1ULL;
}
if (clks_fs_get_child_name(path, arg1, (char *)arg2, (usize)CLKS_SYSCALL_NAME_MAX) == CLKS_FALSE) {
return 0ULL;
}
@@ -1138,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;
@@ -1440,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;
}
@@ -1469,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;
}

View File

@@ -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");

View File

@@ -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/<pid>) 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
}
]
}

View File

@@ -565,7 +565,7 @@ u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2);
- `arg2`: `u64 mode`(当前保留)
- 返回:成功返回 `fd`,失败返回 `-1`
- 说明:
- 当前支持普通文件与 `/dev/tty`
- 当前支持普通文件与设备文件:`/dev/tty``/dev/null``/dev/zero``/dev/random``/dev/urandom`
- 默认进程会预置 `fd 0/1/2`stdin/stdout/stderr
- 标志位兼容子集:`O_RDONLY/O_WRONLY/O_RDWR/O_CREAT/O_TRUNC/O_APPEND`

View File

@@ -4,6 +4,7 @@ CLeonOS ramdisk root layout
/shell : user shell and command ELF apps
/temp : runtime temp/cache files
/driver : hardware and peripheral drivers
/dev : device interface nodes (/dev/tty, /dev/null, /dev/zero, /dev/random)
Root ELF demos:
/hello.elf : Hello world user ELF

0
ramdisk/dev/null Normal file
View File

0
ramdisk/dev/random Normal file
View File

0
ramdisk/dev/tty Normal file
View File

0
ramdisk/dev/urandom Normal file
View File

0
ramdisk/dev/zero Normal file
View File

View File

@@ -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"
@@ -160,6 +169,108 @@ def init_values(options: Iterable[OptionItem], previous: Dict[str, bool], use_de
return values
def _set_option_if_exists(values: Dict[str, bool], key: str, enabled: bool) -> None:
if key in values:
values[key] = enabled
def _set_all_options(values: Dict[str, bool], options: List[OptionItem], enabled: bool) -> None:
for item in options:
values[item.key] = enabled
def apply_preset(preset: str, clks_options: List[OptionItem], user_options: List[OptionItem], values: Dict[str, bool]) -> None:
preset_name = preset.strip().lower()
if preset_name == "full":
_set_all_options(values, clks_options, True)
_set_all_options(values, user_options, True)
return
if preset_name == "dev":
_set_all_options(values, clks_options, True)
_set_all_options(values, user_options, True)
_set_option_if_exists(values, "CLEONOS_CLKS_ENABLE_USERLAND_AUTO_EXEC", False)
_set_option_if_exists(values, "CLEONOS_CLKS_ENABLE_EXEC_SERIAL_LOG", True)
_set_option_if_exists(values, "CLEONOS_CLKS_ENABLE_PROCFS", True)
_set_option_if_exists(values, "CLEONOS_CLKS_ENABLE_IDLE_DEBUG_LOG", True)
return
if preset_name == "minimal":
_set_all_options(values, clks_options, True)
_set_all_options(values, user_options, False)
clks_disable = [
"CLEONOS_CLKS_ENABLE_AUDIO",
"CLEONOS_CLKS_ENABLE_MOUSE",
"CLEONOS_CLKS_ENABLE_DESKTOP",
"CLEONOS_CLKS_ENABLE_DRIVER_MANAGER",
"CLEONOS_CLKS_ENABLE_KELF",
"CLEONOS_CLKS_ENABLE_EXTERNAL_PSF",
"CLEONOS_CLKS_ENABLE_ELFRUNNER_PROBE",
"CLEONOS_CLKS_ENABLE_KLOGD_TASK",
"CLEONOS_CLKS_ENABLE_KWORKER_TASK",
"CLEONOS_CLKS_ENABLE_BOOT_VIDEO_LOG",
"CLEONOS_CLKS_ENABLE_PMM_STATS_LOG",
"CLEONOS_CLKS_ENABLE_HEAP_STATS_LOG",
"CLEONOS_CLKS_ENABLE_FS_ROOT_LOG",
"CLEONOS_CLKS_ENABLE_ELFRUNNER_INIT",
"CLEONOS_CLKS_ENABLE_SYSCALL_TICK_QUERY",
"CLEONOS_CLKS_ENABLE_TTY_READY_LOG",
"CLEONOS_CLKS_ENABLE_IDLE_DEBUG_LOG",
"CLEONOS_CLKS_ENABLE_USER_SYSTEM_APP_PROBE",
"CLEONOS_CLKS_ENABLE_SCHED_TASK_COUNT_LOG",
]
for key in clks_disable:
_set_option_if_exists(values, key, False)
clks_enable = [
"CLEONOS_CLKS_ENABLE_KEYBOARD",
"CLEONOS_CLKS_ENABLE_USRD_TASK",
"CLEONOS_CLKS_ENABLE_USERLAND_AUTO_EXEC",
"CLEONOS_CLKS_ENABLE_HEAP_SELFTEST",
"CLEONOS_CLKS_ENABLE_SYSTEM_DIR_CHECK",
"CLEONOS_CLKS_ENABLE_PROCFS",
"CLEONOS_CLKS_ENABLE_EXEC_SERIAL_LOG",
"CLEONOS_CLKS_ENABLE_KBD_TTY_SWITCH_HOTKEY",
"CLEONOS_CLKS_ENABLE_KBD_CTRL_SHORTCUTS",
"CLEONOS_CLKS_ENABLE_KBD_FORCE_STOP_HOTKEY",
"CLEONOS_CLKS_ENABLE_USER_INIT_SCRIPT_PROBE",
"CLEONOS_CLKS_ENABLE_INTERRUPT_READY_LOG",
"CLEONOS_CLKS_ENABLE_SHELL_MODE_LOG",
]
for key in clks_enable:
_set_option_if_exists(values, key, True)
user_enable_tokens = [
"SHELL",
"HELP",
"LS",
"CD",
"PWD",
"CAT",
"CLEAR",
"EXIT",
"EXEC",
"DMESG",
"TTY",
"PID",
"PS",
"KILL",
"JOBS",
"FG",
"BG",
"RESTART",
"SHUTDOWN",
"TTYDRV",
]
for token in user_enable_tokens:
_set_option_if_exists(values, f"CLEONOS_USER_APP_{token}", True)
return
raise RuntimeError(f"unknown preset: {preset}")
def print_section(title: str, options: List[OptionItem], values: Dict[str, bool]) -> None:
print()
print(f"== {title} ==")
@@ -652,6 +763,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: <multiple>")
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 +1149,12 @@ 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(
"--preset",
choices=["full", "minimal", "dev"],
help="apply a built-in preset before interactive edit or save",
)
parser.add_argument(
"--set",
action="append",
@@ -732,22 +1168,32 @@ 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
previous = load_previous_values()
values = init_values(all_options, previous, use_defaults=args.defaults)
if args.preset:
apply_preset(args.preset, clks_options, user_options, values)
parse_set_overrides(values, args.set)
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")