diff --git a/CMakeLists.txt b/CMakeLists.txt index c602dec..bffd43d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,18 @@ set(CLEONOS_QEMU_INPUT_ARGS "") if(CLEONOS_QEMU_ENABLE_USB_TABLET) list(APPEND CLEONOS_QEMU_INPUT_ARGS -usb -device usb-tablet) endif() - -set(CLEONOS_SOURCE_DIR "${CMAKE_SOURCE_DIR}/cleonos") +set(CLEONOS_OPT_LEVEL "" CACHE STRING "Optional C/C++ optimization level, e.g. 2 or O2") +set(CLEONOS_OPT_FLAG "") +if(NOT "${CLEONOS_OPT_LEVEL}" STREQUAL "") + set(_cleonos_opt_level "${CLEONOS_OPT_LEVEL}") + string(REGEX REPLACE "^-?O" "" _cleonos_opt_level "${_cleonos_opt_level}") + if(NOT _cleonos_opt_level MATCHES "^(0|1|2|3|s|z|g|fast)$") + cl_log_error("unsupported CLEONOS_OPT_LEVEL=${CLEONOS_OPT_LEVEL}; use 0/1/2/3/s/z/g/fast or O2-style values") + endif() + set(CLEONOS_OPT_FLAG "-O${_cleonos_opt_level}") +endif() + +set(CLEONOS_SOURCE_DIR "${CMAKE_SOURCE_DIR}/cleonos") if(EXISTS "${CLEONOS_SOURCE_DIR}/CMakeLists.txt") set(_cleonos_enable_default ON) else() @@ -162,41 +172,59 @@ if(EXISTS "${CLEONOS_MENUCONFIG_CMAKE}") cl_log_info("menuconfig loaded from ${CLEONOS_MENUCONFIG_CMAKE}") endif() -# CLKS compile flags and feature-to-macro expansion now live in the CLKS submodule build system. -set(USER_CFLAGS - -std=c11 - -ffreestanding - -fno-stack-protector - -fno-builtin - -Wall - -Wextra - -Werror - "-I${CMAKE_SOURCE_DIR}/cleonos/c/include" -) - +# CLKS compile flags and feature-to-macro expansion now live in the CLKS submodule build system. +set(USER_CFLAGS + -std=c11 + -ffreestanding + -fno-stack-protector + -fno-builtin + -ffunction-sections + -fdata-sections + -U_FORTIFY_SOURCE + -D_FORTIFY_SOURCE=0 + -Wall + -Wextra + -Werror + "-I${CMAKE_SOURCE_DIR}/cleonos/c/include" +) +if(NOT "${CLEONOS_OPT_FLAG}" STREQUAL "") + list(APPEND USER_CFLAGS "${CLEONOS_OPT_FLAG}") +endif() + set(USER_CFLAGS_DOOM - -std=c11 - -ffreestanding - -fno-stack-protector - -fno-builtin - -Wall - -Wextra - -Wno-error - -Wno-unused-parameter - -Wno-missing-field-initializers - -Wno-sign-compare - -D_DEFAULT_SOURCE - -D_POSIX_C_SOURCE=200809L - "-I${CMAKE_SOURCE_DIR}/cleonos/third-party/doomgeneric/doomgeneric" - "-include" + -std=c11 + -ffreestanding + -fno-stack-protector + -fno-builtin + -ffunction-sections + -fdata-sections + -U_FORTIFY_SOURCE + -D_FORTIFY_SOURCE=0 + -Wall + -Wextra + -Wno-error + -Wno-unused-parameter + -Wno-missing-field-initializers + -Wno-sign-compare + -D_DEFAULT_SOURCE + -D_POSIX_C_SOURCE=200809L + "-I${CMAKE_SOURCE_DIR}/cleonos/third-party/doomgeneric/doomgeneric" + "-include" "${CMAKE_SOURCE_DIR}/cleonos/c/apps/doom/doom_shim.h" ) +if(NOT "${CLEONOS_OPT_FLAG}" STREQUAL "") + list(APPEND USER_CFLAGS_DOOM "${CLEONOS_OPT_FLAG}") +endif() set(USER_CFLAGS_BROWSER -std=c11 -ffreestanding -fno-stack-protector -fno-builtin + -ffunction-sections + -fdata-sections + -U_FORTIFY_SOURCE + -D_FORTIFY_SOURCE=0 -Wall -Wextra -Wno-error @@ -208,14 +236,18 @@ set(USER_CFLAGS_BROWSER "-I${CMAKE_SOURCE_DIR}/cleonos/third-party/litehtml/src/gumbo/include" "-I${CMAKE_SOURCE_DIR}/cleonos/third-party/litehtml/src/gumbo/include/gumbo" ) +if(NOT "${CLEONOS_OPT_FLAG}" STREQUAL "") + list(APPEND USER_CFLAGS_BROWSER "${CLEONOS_OPT_FLAG}") +endif() -set(USER_LDFLAGS - -nostdlib - -z - max-page-size=0x1000 - -T - "${USER_LINKER_SCRIPT}" -) +set(USER_LDFLAGS + -nostdlib + -z + max-page-size=0x1000 + --gc-sections + -T + "${USER_LINKER_SCRIPT}" +) set(KELF_LDFLAGS -nostdlib @@ -240,6 +272,7 @@ set(CLKS_SUBMODULE_CMAKE_ARGS "-DCLKS_ARCH=${CLKS_ARCH}" "-DCLKS_BUILD_ROOT=${BUILD_ROOT}" "-DCLKS_MENUCONFIG_CMAKE=${CMAKE_SOURCE_DIR}/configs/menuconfig/config.cmake" + "-DCLKS_OPT_LEVEL=${CLEONOS_OPT_LEVEL}" "-DCC=${CC}" "-DKERNEL_CXX=${KERNEL_CXX}" "-DLD=${LD}" diff --git a/Makefile b/Makefile index 406da71..7611e3a 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ CMAKE_BUILD_DIR ?= build-cmake CMAKE_BUILD_TYPE ?= Release CMAKE_GENERATOR ?= CMAKE_EXTRA_ARGS ?= +OPT_LEVEL ?= +O2 ?= 0 NO_COLOR ?= 0 LIMINE_SKIP_CONFIGURE ?= LIMINE_REF ?= @@ -32,7 +34,7 @@ CMAKE_CONFIG_VERBOSE_ARG := -DCMAKE_VERBOSE_MAKEFILE=ON else Q := @ CMAKE_BUILD_VERBOSE_ARG := -CMAKE_CONFIG_VERBOSE_ARG := +CMAKE_CONFIG_VERBOSE_ARG := -DCMAKE_VERBOSE_MAKEFILE=OFF endif ifeq ($(strip $(CMAKE_GENERATOR)),) @@ -44,6 +46,13 @@ endif CMAKE_PASSTHROUGH_ARGS := MENUCONFIG_PRESET_ARG := $(if $(strip $(MENUCONFIG_PRESET)),--preset $(MENUCONFIG_PRESET),) CLEONOS_SOURCE_PRESENT := $(if $(wildcard cleonos/CMakeLists.txt),1,0) +OPT_LEVEL_EFFECTIVE := $(strip $(OPT_LEVEL)) + +ifeq ($(OPT_LEVEL_EFFECTIVE),) +ifneq ($(filter 1 ON on TRUE true YES yes Y y,$(O2)),) +OPT_LEVEL_EFFECTIVE := 2 +endif +endif ifeq ($(strip $(CLEONOS_ENABLE)),auto) ifeq ($(CLEONOS_SOURCE_PRESENT),1) @@ -70,6 +79,7 @@ endif CMAKE_PASSTHROUGH_ARGS += -DCLEONOS_ENABLE=$(CLEONOS_ENABLE_EFFECTIVE) CMAKE_PASSTHROUGH_ARGS += -DCLEONOS_QEMU_ENABLE_USB_TABLET=$(CLEONOS_QEMU_ENABLE_USB_TABLET) +CMAKE_PASSTHROUGH_ARGS += -DCLEONOS_OPT_LEVEL=$(OPT_LEVEL_EFFECTIVE) ifneq ($(strip $(LIMINE_SKIP_CONFIGURE)),) CMAKE_PASSTHROUGH_ARGS += -DLIMINE_SKIP_CONFIGURE=$(LIMINE_SKIP_CONFIGURE) @@ -112,7 +122,7 @@ configure: reconfigure: > $(Q)rm -rf $(CMAKE_BUILD_DIR) -> $(Q)$(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)" CLEONOS_ENABLE="$(CLEONOS_ENABLE)" +> $(Q)$(MAKE) configure CMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) CMAKE_GENERATOR="$(CMAKE_GENERATOR)" CMAKE_EXTRA_ARGS="$(CMAKE_EXTRA_ARGS)" OPT_LEVEL="$(OPT_LEVEL)" O2="$(O2)" 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)" CLEONOS_ENABLE="$(CLEONOS_ENABLE)" CLEONOS_QEMU_ENABLE_USB_TABLET="$(CLEONOS_QEMU_ENABLE_USB_TABLET)" menuconfig: > $(Q)if command -v $(PYTHON) >/dev/null 2>&1; then \ @@ -244,6 +254,9 @@ help: > $(Q)echo "QEMU USB tablet toggle:" > $(Q)echo " make run CLEONOS_QEMU_ENABLE_USB_TABLET=OFF # default: PS/2 mouse path" > $(Q)echo " make run CLEONOS_QEMU_ENABLE_USB_TABLET=ON # USB tablet, requires USB HID support" +> $(Q)echo "Optimization:" +> $(Q)echo " make run O2=1" +> $(Q)echo " make run OPT_LEVEL=2" > $(Q)echo "" > $(Q)echo "Pass custom CMake cache args via:" > $(Q)echo " make configure CMAKE_EXTRA_ARGS='-DLIMINE_SKIP_CONFIGURE=1 -DOBJCOPY_FOR_TARGET=objcopy'" diff --git a/cleonos/CMakeLists.txt b/cleonos/CMakeLists.txt index 5cb7a1e..b63763b 100644 --- a/cleonos/CMakeLists.txt +++ b/cleonos/CMakeLists.txt @@ -176,7 +176,7 @@ set(USER_SHELL_COMMAND_APPS diskinfo mkfsfat32 mount partctl shutdown restart exit clear ansi ansitest wavplay fastfetch memstat fsstat taskstat userstat shstat stats tty dmesg kbdstat mkdir touch write append cp mv rm kdbg bmpview qrcode browser - vim uwm + vim uwm terminal ) foreach(SRC IN LISTS USER_APP_MAIN_SOURCES) @@ -280,7 +280,7 @@ foreach(SRC IN LISTS USER_APP_MAIN_SOURCES) ) list(APPEND USER_APP_OUTPUTS "${_app_out}") - if(_app_name STREQUAL "file_explorer") + if(_app_name STREQUAL "file_explorer" OR _app_name STREQUAL "terminal" OR _app_name STREQUAL "taskmgr") list(APPEND RAMDISK_UWM_APPS "${_app_out}") elseif(_app_name MATCHES ".*drv$") list(APPEND RAMDISK_DRIVER_APPS "${_app_out}") diff --git a/cleonos/c/apps/app_heap.inc b/cleonos/c/apps/app_heap.inc new file mode 100644 index 0000000..ec226f8 --- /dev/null +++ b/cleonos/c/apps/app_heap.inc @@ -0,0 +1,232 @@ +#include +#include + +#ifndef CLEONOS_APP_HEAP_CAPACITY +#define CLEONOS_APP_HEAP_CAPACITY (2U * 1024U * 1024U) +#endif + +#define CLEONOS_APP_HEAP_ALIGN 8U +#define CLEONOS_APP_HEAP_MAGIC 0xC10A5A7EU + +typedef union cleonos_app_heap_storage { + unsigned long long align; + unsigned char bytes[CLEONOS_APP_HEAP_CAPACITY]; +} cleonos_app_heap_storage; + +typedef struct cleonos_app_heap_block { + size_t size; + unsigned int used; + unsigned int magic; + struct cleonos_app_heap_block *next; + struct cleonos_app_heap_block *prev; +} cleonos_app_heap_block; + +static cleonos_app_heap_storage cleonos_app_heap; +static cleonos_app_heap_block *cleonos_app_heap_head = (cleonos_app_heap_block *)0; +static int cleonos_app_heap_ready = 0; + +static size_t cleonos_app_heap_align_up(size_t value, size_t align) { + size_t mask; + + if (align == 0U) { + return value; + } + + mask = align - 1U; + return (value + mask) & ~mask; +} + +static void cleonos_app_heap_init(void) { + size_t header_size = cleonos_app_heap_align_up(sizeof(cleonos_app_heap_block), CLEONOS_APP_HEAP_ALIGN); + + if (cleonos_app_heap_ready != 0) { + return; + } + + if ((size_t)CLEONOS_APP_HEAP_CAPACITY <= header_size + CLEONOS_APP_HEAP_ALIGN) { + return; + } + + cleonos_app_heap_head = (cleonos_app_heap_block *)(void *)cleonos_app_heap.bytes; + cleonos_app_heap_head->size = (size_t)CLEONOS_APP_HEAP_CAPACITY - header_size; + cleonos_app_heap_head->used = 0U; + cleonos_app_heap_head->magic = CLEONOS_APP_HEAP_MAGIC; + cleonos_app_heap_head->next = (cleonos_app_heap_block *)0; + cleonos_app_heap_head->prev = (cleonos_app_heap_block *)0; + cleonos_app_heap_ready = 1; +} + +static void cleonos_app_heap_split(cleonos_app_heap_block *block, size_t need) { + size_t header_size = cleonos_app_heap_align_up(sizeof(cleonos_app_heap_block), CLEONOS_APP_HEAP_ALIGN); + unsigned char *next_addr; + cleonos_app_heap_block *next; + + if (block == (cleonos_app_heap_block *)0 || block->size <= need + header_size + CLEONOS_APP_HEAP_ALIGN) { + return; + } + + next_addr = ((unsigned char *)(void *)block) + header_size + need; + next = (cleonos_app_heap_block *)(void *)next_addr; + next->size = block->size - need - header_size; + next->used = 0U; + next->magic = CLEONOS_APP_HEAP_MAGIC; + next->next = block->next; + next->prev = block; + if (next->next != (cleonos_app_heap_block *)0) { + next->next->prev = next; + } + + block->next = next; + block->size = need; +} + +static void cleonos_app_heap_merge_next(cleonos_app_heap_block *block) { + size_t header_size = cleonos_app_heap_align_up(sizeof(cleonos_app_heap_block), CLEONOS_APP_HEAP_ALIGN); + cleonos_app_heap_block *next; + + if (block == (cleonos_app_heap_block *)0 || block->used != 0U) { + return; + } + + next = block->next; + if (next == (cleonos_app_heap_block *)0 || next->magic != CLEONOS_APP_HEAP_MAGIC || next->used != 0U) { + return; + } + + block->size += header_size + next->size; + block->next = next->next; + if (block->next != (cleonos_app_heap_block *)0) { + block->next->prev = block; + } +} + +static cleonos_app_heap_block *cleonos_app_heap_block_from_ptr(void *ptr) { + size_t header_size = cleonos_app_heap_align_up(sizeof(cleonos_app_heap_block), CLEONOS_APP_HEAP_ALIGN); + unsigned char *raw = (unsigned char *)ptr; + unsigned char *begin = cleonos_app_heap.bytes; + unsigned char *end = cleonos_app_heap.bytes + (size_t)CLEONOS_APP_HEAP_CAPACITY; + cleonos_app_heap_block *block; + + if (ptr == (void *)0 || raw < begin + header_size || raw >= end) { + return (cleonos_app_heap_block *)0; + } + + block = (cleonos_app_heap_block *)(void *)(raw - header_size); + if (block->magic != CLEONOS_APP_HEAP_MAGIC || block->used == 0U) { + return (cleonos_app_heap_block *)0; + } + + return block; +} + +void *malloc(size_t size) { + cleonos_app_heap_block *current; + size_t need = cleonos_app_heap_align_up(size, CLEONOS_APP_HEAP_ALIGN); + size_t header_size = cleonos_app_heap_align_up(sizeof(cleonos_app_heap_block), CLEONOS_APP_HEAP_ALIGN); + + if (size == 0U) { + return (void *)0; + } + + cleonos_app_heap_init(); + if (cleonos_app_heap_ready == 0) { + return (void *)0; + } + + current = cleonos_app_heap_head; + while (current != (cleonos_app_heap_block *)0) { + if (current->magic == CLEONOS_APP_HEAP_MAGIC && current->used == 0U && current->size >= need) { + cleonos_app_heap_split(current, need); + current->used = 1U; + return (void *)(((unsigned char *)(void *)current) + header_size); + } + current = current->next; + } + + return (void *)0; +} + +void free(void *ptr) { + cleonos_app_heap_block *block = cleonos_app_heap_block_from_ptr(ptr); + + if (block == (cleonos_app_heap_block *)0) { + return; + } + + block->used = 0U; + cleonos_app_heap_merge_next(block); + if (block->prev != (cleonos_app_heap_block *)0) { + cleonos_app_heap_merge_next(block->prev); + } +} + +void *calloc(size_t count, size_t size) { + size_t total; + void *ptr; + + if (count == 0U || size == 0U) { + return (void *)0; + } + + if (count > ((size_t)-1) / size) { + return (void *)0; + } + + total = count * size; + ptr = malloc(total); + if (ptr == (void *)0) { + return (void *)0; + } + + (void)memset(ptr, 0, total); + return ptr; +} + +void *realloc(void *ptr, size_t size) { + cleonos_app_heap_block *block; + void *out; + size_t old_size; + + if (ptr == (void *)0) { + return malloc(size); + } + + if (size == 0U) { + free(ptr); + return (void *)0; + } + + block = cleonos_app_heap_block_from_ptr(ptr); + if (block == (cleonos_app_heap_block *)0) { + return (void *)0; + } + + old_size = block->size; + size = cleonos_app_heap_align_up(size, CLEONOS_APP_HEAP_ALIGN); + if (old_size >= size) { + cleonos_app_heap_split(block, size); + return ptr; + } + + if (block->next != (cleonos_app_heap_block *)0 && block->next->used == 0U) { + cleonos_app_heap_merge_next(block); + if (block->size >= size) { + cleonos_app_heap_split(block, size); + return ptr; + } + } + + out = malloc(size); + if (out == (void *)0) { + return (void *)0; + } + + if (old_size < size) { + (void)memcpy(out, ptr, old_size); + } else { + (void)memcpy(out, ptr, size); + } + + free(ptr); + return out; +} diff --git a/cleonos/c/apps/browser_heap.c b/cleonos/c/apps/browser_heap.c new file mode 100644 index 0000000..cb24a52 --- /dev/null +++ b/cleonos/c/apps/browser_heap.c @@ -0,0 +1,2 @@ +#define CLEONOS_APP_HEAP_CAPACITY (3U * 1024U * 1024U) +#include "app_heap.inc" diff --git a/cleonos/c/apps/doom/doom_compat.c b/cleonos/c/apps/doom/doom_compat.c index cc20692..8dd2ebf 100644 --- a/cleonos/c/apps/doom/doom_compat.c +++ b/cleonos/c/apps/doom/doom_compat.c @@ -13,6 +13,10 @@ #include "../../include/cleonos_syscall.h" +#ifdef putc +#undef putc +#endif + #define DG_HEAP_SIZE (32U * 1024U * 1024U) #define DG_MAX_MEM_FD 64 #define DG_PATH_MAX 192 @@ -55,6 +59,8 @@ static struct dg_stream g_dg_stdout_stream = {DG_STDIO_MAGIC, 1, 0, 0}; static struct dg_stream g_dg_stderr_stream = {DG_STDIO_MAGIC, 2, 0, 0}; static int g_dg_errno = 0; static unsigned short g_dg_ctype_table[384]; +static int32_t g_dg_toupper_table[384]; +static int32_t g_dg_tolower_table[384]; static int g_dg_ctype_ready = 0; FILE *stdin = (FILE *)(void *)&g_dg_stdin_stream; @@ -103,6 +109,13 @@ static void dg_init_ctype_table(void) { for (c = -128; c < 256; c++) { unsigned short flags = 0U; int ch = (c < 0) ? (c + 256) : c; + int upper = dg_is_lower(ch) ? (ch - 'a' + 'A') : ch; + int lower = dg_is_upper(ch) ? (ch - 'A' + 'a') : ch; + + if (c == -1) { + upper = -1; + lower = -1; + } if (dg_is_upper(ch) != 0) { flags |= _ISupper; @@ -142,6 +155,8 @@ static void dg_init_ctype_table(void) { } g_dg_ctype_table[c + 128] = flags; + g_dg_toupper_table[c + 128] = (int32_t)upper; + g_dg_tolower_table[c + 128] = (int32_t)lower; } g_dg_ctype_ready = 1; @@ -153,6 +168,18 @@ const unsigned short int **__ctype_b_loc(void) { return &table_ptr; } +const int32_t **__ctype_toupper_loc(void) { + static const int32_t *table_ptr = (const int32_t *)(void *)(g_dg_toupper_table + 128); + dg_init_ctype_table(); + return &table_ptr; +} + +const int32_t **__ctype_tolower_loc(void) { + static const int32_t *table_ptr = (const int32_t *)(void *)(g_dg_tolower_table + 128); + dg_init_ctype_table(); + return &table_ptr; +} + int system(const char *command) { (void)command; errno = ENOSYS; @@ -1180,6 +1207,11 @@ size_t dg_fwrite(const void *buf, size_t size, size_t nmemb, FILE *stream) { return (size_t)wrote / size; } +int putc(int ch, FILE *stream) { + unsigned char out = (unsigned char)(ch & 0xFF); + return (dg_fwrite(&out, 1U, 1U, stream) == 1U) ? (int)out : EOF; +} + int dg_fseek(FILE *stream, long offset, int whence) { struct dg_stream *s = dg_stream_ptr(stream); diff --git a/cleonos/c/apps/file_explorer_heap.c b/cleonos/c/apps/file_explorer_heap.c new file mode 100644 index 0000000..cb24a52 --- /dev/null +++ b/cleonos/c/apps/file_explorer_heap.c @@ -0,0 +1,2 @@ +#define CLEONOS_APP_HEAP_CAPACITY (3U * 1024U * 1024U) +#include "app_heap.inc" diff --git a/cleonos/c/apps/help_main.c b/cleonos/c/apps/help_main.c index 30b83a2..bc1ffe4 100644 --- a/cleonos/c/apps/help_main.c +++ b/cleonos/c/apps/help_main.c @@ -16,7 +16,7 @@ static int ush_cmd_help(void) { ush_writeln(" bmpview [cols]"); ush_writeln(" qrcode [--ecc ] "); ush_writeln(" vim [file] (vim-like editor: normal/insert/:w/:q/:wq)"); - ush_writeln(" uwm (user-space window manager)"); + ush_writeln(" uwm (user-space window manager; Start includes Task Manager)"); ush_writeln(" wavplay [steps] [ticks] / wavplay --stop"); ush_writeln(" fastfetch [--plain]"); ush_writeln(" doom [wad_path] (framebuffer bootstrap renderer)"); @@ -43,7 +43,7 @@ static int ush_cmd_help(void) { ush_writeln(" spawn [args...] / bg [args...]"); ush_writeln(" wait / fg [pid]"); ush_writeln(" kill [signal]"); - ush_writeln(" jobs [-a] / ps [-a] [-u] / procstat [pid|self] [-a]"); + ush_writeln(" jobs [-a] / ps [-a] [-u] / procstat [pid|self] [-a]"); ush_writeln(" top [--once] [-n loops] [-d ticks] / sysstat [-a] [-n N]"); ush_writeln(" kdbg sym / kdbg bt / kdbg regs"); ush_writeln(" sleep "); diff --git a/cleonos/c/apps/shell/shell_cmd.c b/cleonos/c/apps/shell/shell_cmd.c index be82dad..7bb5968 100644 --- a/cleonos/c/apps/shell/shell_cmd.c +++ b/cleonos/c/apps/shell/shell_cmd.c @@ -138,7 +138,7 @@ static int ush_cmd_help(void) { ush_writeln(" bmpview [cols]"); ush_writeln(" qrcode [--ecc ] "); ush_writeln(" vim [file] (vim-like editor)"); - ush_writeln(" uwm (user-space window manager)"); + ush_writeln(" uwm (user-space window manager; Start includes Task Manager)"); ush_writeln(" wavplay [steps] [ticks] / wavplay --stop"); ush_writeln(" fastfetch [--plain]"); ush_writeln(" doom [wad_path] (framebuffer bootstrap renderer)"); diff --git a/cleonos/c/apps/taskmgr_main.c b/cleonos/c/apps/taskmgr_main.c new file mode 100644 index 0000000..addf939 --- /dev/null +++ b/cleonos/c/apps/taskmgr_main.c @@ -0,0 +1,1060 @@ +#include + +typedef long long i64; +typedef unsigned int tm_u32; + +#define TM_TTY_DISPLAY 1ULL +#define TM_CANVAS_MAX_W 800U +#define TM_CANVAS_MAX_H 540U +#define TM_DEFAULT_W 760 +#define TM_DEFAULT_H 500 +#define TM_MIN_W 520 +#define TM_MIN_H 340 +#define TM_TITLE_H 32 +#define TM_TOOLBAR_H 50 +#define TM_HEADER_H 26 +#define TM_STATUS_H 28 +#define TM_ROW_H 24 +#define TM_CLOSE_W 46 +#define TM_MAX_ROWS 96U +#define TM_EVENT_BUDGET 96ULL +#define TM_REFRESH_TICKS 45ULL + +#define TM_KEY_LEFT 1ULL +#define TM_KEY_RIGHT 2ULL +#define TM_KEY_UP 3ULL +#define TM_KEY_DOWN 4ULL + +#define TM_COLOR_WHITE 0x00FFFFFFU +#define TM_COLOR_BG 0x00F3F3F3U +#define TM_COLOR_PANEL 0x00FFFFFFU +#define TM_COLOR_TITLE 0x000078D7U +#define TM_COLOR_CLOSE 0x00E81123U +#define TM_COLOR_TEXT 0x00232323U +#define TM_COLOR_MUTED 0x00666666U +#define TM_COLOR_BORDER 0x00D0D0D0U +#define TM_COLOR_ROW_ALT 0x00FAFAFAU +#define TM_COLOR_SELECT 0x00CDE8FFU +#define TM_COLOR_SELECT_BORDER 0x0078BDE8U +#define TM_COLOR_BUTTON 0x00E7E7E7U +#define TM_COLOR_BUTTON_HOT 0x00D8EBFAU +#define TM_COLOR_WARN 0x00FFF4CEU +#define TM_COLOR_BAD 0x00FDE7E9U +#define TM_COLOR_GOOD 0x00DFF6DDU + +#define TM_GLYPH7(r0, r1, r2, r3, r4, r5, r6) \ + (((u64)(r0) << 30U) | ((u64)(r1) << 25U) | ((u64)(r2) << 20U) | ((u64)(r3) << 15U) | ((u64)(r4) << 10U) | \ + ((u64)(r5) << 5U) | (u64)(r6)) + +typedef struct tm_app { + int screen_w; + int screen_h; + int x; + int y; + int w; + int h; + u64 window_id; + u64 old_tty; + int tty_switched; + int running; + int dragging; + int drag_dx; + int drag_dy; + int include_exited; + int selected; + int scroll; + u64 selected_pid; + u64 total_mem; + u64 proc_count_raw; + u64 last_refresh_tick; + cleonos_proc_snapshot rows[TM_MAX_ROWS]; + u64 row_count; + char status[160]; +} tm_app; + +static tm_u32 tm_canvas[TM_CANVAS_MAX_H][TM_CANVAS_MAX_W]; + +static void tm_zero(void *ptr, u64 size) { + unsigned char *out = (unsigned char *)ptr; + u64 i; + + if (ptr == (void *)0) { + return; + } + + for (i = 0ULL; i < size; i++) { + out[i] = 0U; + } +} + +static u64 tm_strlen(const char *text) { + u64 len = 0ULL; + + if (text == (const char *)0) { + return 0ULL; + } + + while (text[len] != '\0') { + len++; + } + + return len; +} + +static void tm_copy(char *dst, u64 dst_size, const char *src) { + u64 i = 0ULL; + + if (dst == (char *)0 || dst_size == 0ULL) { + return; + } + + if (src == (const char *)0) { + dst[0] = '\0'; + return; + } + + while (src[i] != '\0' && i + 1ULL < dst_size) { + dst[i] = src[i]; + i++; + } + dst[i] = '\0'; +} + +static void tm_append(char *dst, u64 dst_size, const char *src) { + u64 pos; + u64 i = 0ULL; + + if (dst == (char *)0 || dst_size == 0ULL || src == (const char *)0) { + return; + } + + pos = tm_strlen(dst); + while (src[i] != '\0' && pos + 1ULL < dst_size) { + dst[pos++] = src[i++]; + } + dst[pos] = '\0'; +} + +static void tm_append_u64_dec(char *dst, u64 dst_size, u64 value) { + char tmp[24]; + u64 len = 0ULL; + + if (value == 0ULL) { + tm_append(dst, dst_size, "0"); + return; + } + + while (value != 0ULL && len < (u64)sizeof(tmp)) { + tmp[len++] = (char)('0' + (value % 10ULL)); + value /= 10ULL; + } + + while (len > 0ULL) { + char one[2]; + one[0] = tmp[--len]; + one[1] = '\0'; + tm_append(dst, dst_size, one); + } +} + +static void tm_u64_to_dec(char *out, u64 out_size, u64 value) { + if (out == (char *)0 || out_size == 0ULL) { + return; + } + out[0] = '\0'; + tm_append_u64_dec(out, out_size, value); +} + +static int tm_clampi(int value, int min_value, int max_value) { + if (max_value < min_value) { + return min_value; + } + if (value < min_value) { + return min_value; + } + if (value > max_value) { + return max_value; + } + return value; +} + +static int tm_u64_as_i32(u64 raw) { + return (int)(i64)raw; +} + +static char tm_upper_char(char ch) { + if (ch >= 'a' && ch <= 'z') { + return (char)(ch - ('a' - 'A')); + } + return ch; +} + +static const char *tm_state_name(u64 state) { + if (state == CLEONOS_PROC_STATE_PENDING) { + return "PENDING"; + } + if (state == CLEONOS_PROC_STATE_RUNNING) { + return "RUNNING"; + } + if (state == CLEONOS_PROC_STATE_STOPPED) { + return "STOPPED"; + } + if (state == CLEONOS_PROC_STATE_EXITED) { + return "EXITED"; + } + return "UNKNOWN"; +} + +static u64 tm_glyph_mask(char ch) { + switch (tm_upper_char(ch)) { + case 'A': + return TM_GLYPH7(14U, 17U, 17U, 31U, 17U, 17U, 17U); + case 'B': + return TM_GLYPH7(30U, 17U, 17U, 30U, 17U, 17U, 30U); + case 'C': + return TM_GLYPH7(14U, 17U, 16U, 16U, 16U, 17U, 14U); + case 'D': + return TM_GLYPH7(30U, 17U, 17U, 17U, 17U, 17U, 30U); + case 'E': + return TM_GLYPH7(31U, 16U, 16U, 30U, 16U, 16U, 31U); + case 'F': + return TM_GLYPH7(31U, 16U, 16U, 30U, 16U, 16U, 16U); + case 'G': + return TM_GLYPH7(14U, 17U, 16U, 23U, 17U, 17U, 15U); + case 'H': + return TM_GLYPH7(17U, 17U, 17U, 31U, 17U, 17U, 17U); + case 'I': + return TM_GLYPH7(31U, 4U, 4U, 4U, 4U, 4U, 31U); + case 'J': + return TM_GLYPH7(1U, 1U, 1U, 1U, 17U, 17U, 14U); + case 'K': + return TM_GLYPH7(17U, 18U, 20U, 24U, 20U, 18U, 17U); + case 'L': + return TM_GLYPH7(16U, 16U, 16U, 16U, 16U, 16U, 31U); + case 'M': + return TM_GLYPH7(17U, 27U, 21U, 21U, 17U, 17U, 17U); + case 'N': + return TM_GLYPH7(17U, 25U, 21U, 19U, 17U, 17U, 17U); + case 'O': + return TM_GLYPH7(14U, 17U, 17U, 17U, 17U, 17U, 14U); + case 'P': + return TM_GLYPH7(30U, 17U, 17U, 30U, 16U, 16U, 16U); + case 'Q': + return TM_GLYPH7(14U, 17U, 17U, 17U, 21U, 18U, 13U); + case 'R': + return TM_GLYPH7(30U, 17U, 17U, 30U, 20U, 18U, 17U); + case 'S': + return TM_GLYPH7(15U, 16U, 16U, 14U, 1U, 1U, 30U); + case 'T': + return TM_GLYPH7(31U, 4U, 4U, 4U, 4U, 4U, 4U); + case 'U': + return TM_GLYPH7(17U, 17U, 17U, 17U, 17U, 17U, 14U); + case 'V': + return TM_GLYPH7(17U, 17U, 17U, 17U, 17U, 10U, 4U); + case 'W': + return TM_GLYPH7(17U, 17U, 17U, 21U, 21U, 21U, 10U); + case 'X': + return TM_GLYPH7(17U, 17U, 10U, 4U, 10U, 17U, 17U); + case 'Y': + return TM_GLYPH7(17U, 17U, 10U, 4U, 4U, 4U, 4U); + case 'Z': + return TM_GLYPH7(31U, 1U, 2U, 4U, 8U, 16U, 31U); + case '0': + return TM_GLYPH7(14U, 17U, 19U, 21U, 25U, 17U, 14U); + case '1': + return TM_GLYPH7(4U, 12U, 4U, 4U, 4U, 4U, 14U); + case '2': + return TM_GLYPH7(14U, 17U, 1U, 2U, 4U, 8U, 31U); + case '3': + return TM_GLYPH7(30U, 1U, 1U, 14U, 1U, 1U, 30U); + case '4': + return TM_GLYPH7(2U, 6U, 10U, 18U, 31U, 2U, 2U); + case '5': + return TM_GLYPH7(31U, 16U, 16U, 30U, 1U, 1U, 30U); + case '6': + return TM_GLYPH7(14U, 16U, 16U, 30U, 17U, 17U, 14U); + case '7': + return TM_GLYPH7(31U, 1U, 2U, 4U, 8U, 8U, 8U); + case '8': + return TM_GLYPH7(14U, 17U, 17U, 14U, 17U, 17U, 14U); + case '9': + return TM_GLYPH7(14U, 17U, 17U, 15U, 1U, 1U, 14U); + case '-': + return TM_GLYPH7(0U, 0U, 0U, 31U, 0U, 0U, 0U); + case '_': + return TM_GLYPH7(0U, 0U, 0U, 0U, 0U, 0U, 31U); + case '.': + return TM_GLYPH7(0U, 0U, 0U, 0U, 0U, 12U, 12U); + case ':': + return TM_GLYPH7(0U, 12U, 12U, 0U, 12U, 12U, 0U); + case '/': + return TM_GLYPH7(1U, 1U, 2U, 4U, 8U, 16U, 16U); + case '+': + return TM_GLYPH7(0U, 4U, 4U, 31U, 4U, 4U, 0U); + case '=': + return TM_GLYPH7(0U, 0U, 31U, 0U, 31U, 0U, 0U); + case '<': + return TM_GLYPH7(1U, 2U, 4U, 8U, 4U, 2U, 1U); + case '>': + return TM_GLYPH7(16U, 8U, 4U, 2U, 4U, 8U, 16U); + case '[': + return TM_GLYPH7(14U, 8U, 8U, 8U, 8U, 8U, 14U); + case ']': + return TM_GLYPH7(14U, 2U, 2U, 2U, 2U, 2U, 14U); + case '(': + return TM_GLYPH7(2U, 4U, 8U, 8U, 8U, 4U, 2U); + case ')': + return TM_GLYPH7(8U, 4U, 2U, 2U, 2U, 4U, 8U); + case '|': + return TM_GLYPH7(4U, 4U, 4U, 4U, 4U, 4U, 4U); + case '!': + return TM_GLYPH7(4U, 4U, 4U, 4U, 4U, 0U, 4U); + case '?': + return TM_GLYPH7(14U, 17U, 1U, 2U, 4U, 0U, 4U); + case '*': + return TM_GLYPH7(0U, 21U, 14U, 31U, 14U, 21U, 0U); + default: + return 0ULL; + } +} + +static void tm_fill_rect(int canvas_w, int canvas_h, int x, int y, int w, int h, tm_u32 color) { + int left = x; + int top = y; + int right = x + w; + int bottom = y + h; + int row; + + if (canvas_w <= 0 || canvas_h <= 0 || canvas_w > (int)TM_CANVAS_MAX_W || canvas_h > (int)TM_CANVAS_MAX_H || + w <= 0 || h <= 0) { + return; + } + + if (left < 0) { + left = 0; + } + if (top < 0) { + top = 0; + } + if (right > canvas_w) { + right = canvas_w; + } + if (bottom > canvas_h) { + bottom = canvas_h; + } + if (left >= right || top >= bottom) { + return; + } + + for (row = top; row < bottom; row++) { + int col; + for (col = left; col < right; col++) { + tm_canvas[row][col] = color; + } + } +} + +static void tm_stroke_rect(int canvas_w, int canvas_h, int x, int y, int w, int h, tm_u32 color) { + tm_fill_rect(canvas_w, canvas_h, x, y, w, 1, color); + tm_fill_rect(canvas_w, canvas_h, x, y + h - 1, w, 1, color); + tm_fill_rect(canvas_w, canvas_h, x, y, 1, h, color); + tm_fill_rect(canvas_w, canvas_h, x + w - 1, y, 1, h, color); +} + +static void tm_draw_char(int canvas_w, int canvas_h, int x, int y, char ch, int scale, tm_u32 color) { + u64 mask = tm_glyph_mask(ch); + int row; + + if (mask == 0ULL || scale <= 0) { + return; + } + + for (row = 0; row < 7; row++) { + int col; + for (col = 0; col < 5; col++) { + unsigned int bit_index = (unsigned int)((6 - row) * 5 + (4 - col)); + if ((mask & (1ULL << bit_index)) != 0ULL) { + tm_fill_rect(canvas_w, canvas_h, x + (col * scale), y + (row * scale), scale, scale, color); + } + } + } +} + +static void tm_draw_text_limit(int canvas_w, int canvas_h, int x, int y, const char *text, int scale, tm_u32 color, + int max_x) { + int cursor_x = x; + + if (text == (const char *)0 || scale <= 0) { + return; + } + + while (*text != '\0' && cursor_x + (5 * scale) <= max_x) { + if (*text != ' ') { + tm_draw_char(canvas_w, canvas_h, cursor_x, y, *text, scale, color); + } + cursor_x += 6 * scale; + text++; + } +} + +static void tm_draw_text(int canvas_w, int canvas_h, int x, int y, const char *text, int scale, tm_u32 color) { + tm_draw_text_limit(canvas_w, canvas_h, x, y, text, scale, color, canvas_w - 4); +} + +static void tm_draw_button(int canvas_w, int canvas_h, int x, int y, int w, int h, const char *label, int hot) { + tm_fill_rect(canvas_w, canvas_h, x, y, w, h, hot != 0 ? TM_COLOR_BUTTON_HOT : TM_COLOR_BUTTON); + tm_stroke_rect(canvas_w, canvas_h, x, y, w, h, TM_COLOR_BORDER); + tm_draw_text_limit(canvas_w, canvas_h, x + 10, y + ((h - 7) / 2), label, 1, TM_COLOR_TEXT, x + w - 6); +} + +static int tm_visible_rows(const tm_app *app) { + int list_top; + int list_bottom; + int rows; + + if (app == (const tm_app *)0) { + return 0; + } + + list_top = TM_TITLE_H + TM_TOOLBAR_H + TM_HEADER_H; + list_bottom = app->h - TM_STATUS_H; + rows = (list_bottom - list_top) / TM_ROW_H; + return (rows > 0) ? rows : 0; +} + +static void tm_set_status(tm_app *app, const char *text) { + if (app != (tm_app *)0) { + tm_copy(app->status, (u64)sizeof(app->status), text); + } +} + +static void tm_build_summary_status(tm_app *app) { + if (app == (tm_app *)0) { + return; + } + + app->status[0] = '\0'; + tm_append(app->status, (u64)sizeof(app->status), "PROCESSES "); + tm_append_u64_dec(app->status, (u64)sizeof(app->status), app->row_count); + tm_append(app->status, (u64)sizeof(app->status), "/"); + tm_append_u64_dec(app->status, (u64)sizeof(app->status), app->proc_count_raw); + tm_append(app->status, (u64)sizeof(app->status), " | ACTIVE MEM "); + tm_append_u64_dec(app->status, (u64)sizeof(app->status), app->total_mem / 1024ULL); + tm_append(app->status, (u64)sizeof(app->status), " KB | R REFRESH A ALL DEL END TASK"); +} + +static void tm_clamp_selection(tm_app *app) { + int visible; + int max_scroll; + + if (app == (tm_app *)0) { + return; + } + + if (app->row_count == 0ULL) { + app->selected = -1; + app->selected_pid = 0ULL; + app->scroll = 0; + return; + } + + if (app->selected < 0 || (u64)(unsigned int)app->selected >= app->row_count) { + app->selected = 0; + } + + app->selected_pid = app->rows[app->selected].pid; + visible = tm_visible_rows(app); + if (visible <= 0) { + app->scroll = 0; + return; + } + + max_scroll = ((int)app->row_count > visible) ? ((int)app->row_count - visible) : 0; + app->scroll = tm_clampi(app->scroll, 0, max_scroll); + if (app->selected < app->scroll) { + app->scroll = app->selected; + } + if (app->selected >= app->scroll + visible) { + app->scroll = app->selected - visible + 1; + } +} + +static void tm_reload(tm_app *app) { + u64 proc_count; + u64 previous_pid; + u64 i; + int previous_index = -1; + + if (app == (tm_app *)0) { + return; + } + + previous_pid = app->selected_pid; + app->row_count = 0ULL; + app->total_mem = 0ULL; + proc_count = cleonos_sys_proc_count(); + app->proc_count_raw = proc_count; + + for (i = 0ULL; i < proc_count && app->row_count < (u64)TM_MAX_ROWS; i++) { + u64 pid = 0ULL; + cleonos_proc_snapshot snap; + + tm_zero(&snap, (u64)sizeof(snap)); + if (cleonos_sys_proc_pid_at(i, &pid) == 0ULL || pid == 0ULL) { + continue; + } + if (cleonos_sys_proc_snapshot(pid, &snap, (u64)sizeof(snap)) == 0ULL) { + continue; + } + if (app->include_exited == 0 && snap.state == CLEONOS_PROC_STATE_EXITED) { + continue; + } + if (snap.state == CLEONOS_PROC_STATE_RUNNING || snap.state == CLEONOS_PROC_STATE_PENDING || + snap.state == CLEONOS_PROC_STATE_STOPPED) { + app->total_mem += snap.mem_bytes; + } + if (snap.pid == previous_pid) { + previous_index = (int)app->row_count; + } + app->rows[app->row_count++] = snap; + } + + if (previous_index >= 0) { + app->selected = previous_index; + } else if (app->row_count != 0ULL) { + app->selected = 0; + } else { + app->selected = -1; + } + + tm_clamp_selection(app); + tm_build_summary_status(app); + app->last_refresh_tick = cleonos_sys_timer_ticks(); +} + +static int tm_present(const tm_app *app) { + cleonos_wm_present_req req; + + if (app == (const tm_app *)0 || app->window_id == 0ULL || app->w <= 0 || app->h <= 0) { + return 0; + } + + req.window_id = app->window_id; + req.pixels_ptr = (u64)(usize)&tm_canvas[0][0]; + req.src_width = (u64)(unsigned int)app->w; + req.src_height = (u64)(unsigned int)app->h; + req.src_pitch_bytes = (u64)TM_CANVAS_MAX_W * 4ULL; + return (cleonos_sys_wm_present(&req) != 0ULL) ? 1 : 0; +} + +static void tm_draw_titlebar(const tm_app *app) { + int close_x; + + if (app == (const tm_app *)0) { + return; + } + + close_x = app->w - TM_CLOSE_W; + tm_fill_rect(app->w, app->h, 0, 0, app->w, TM_TITLE_H, TM_COLOR_TITLE); + tm_draw_text(app->w, app->h, 14, 12, "TASK MANAGER", 1, TM_COLOR_WHITE); + tm_fill_rect(app->w, app->h, close_x, 0, TM_CLOSE_W, TM_TITLE_H, TM_COLOR_CLOSE); + tm_draw_text(app->w, app->h, close_x + 18, 12, "X", 1, TM_COLOR_WHITE); +} + +static void tm_draw_toolbar(const tm_app *app) { + int y = TM_TITLE_H; + int kill_x; + + if (app == (const tm_app *)0) { + return; + } + + kill_x = app->w - 122; + tm_fill_rect(app->w, app->h, 0, y, app->w, TM_TOOLBAR_H, TM_COLOR_PANEL); + tm_fill_rect(app->w, app->h, 0, y + TM_TOOLBAR_H - 1, app->w, 1, TM_COLOR_BORDER); + tm_draw_text(app->w, app->h, 18, y + 10, "PROCESSES", 2, TM_COLOR_TEXT); + tm_draw_button(app->w, app->h, 164, y + 10, 86, 28, "REFRESH", 0); + tm_draw_button(app->w, app->h, 260, y + 10, 94, 28, app->include_exited != 0 ? "ALL ON" : "ALL OFF", app->include_exited); + if (kill_x > 370) { + tm_draw_button(app->w, app->h, kill_x, y + 10, 104, 28, "END TASK", app->selected >= 0); + } +} + +static void tm_draw_header(const tm_app *app) { + int y = TM_TITLE_H + TM_TOOLBAR_H; + + if (app == (const tm_app *)0) { + return; + } + + tm_fill_rect(app->w, app->h, 0, y, app->w, TM_HEADER_H, 0x00EFEFEFU); + tm_fill_rect(app->w, app->h, 0, y + TM_HEADER_H - 1, app->w, 1, TM_COLOR_BORDER); + tm_draw_text(app->w, app->h, 18, y + 9, "PID", 1, TM_COLOR_MUTED); + tm_draw_text(app->w, app->h, 82, y + 9, "STATE", 1, TM_COLOR_MUTED); + tm_draw_text(app->w, app->h, 172, y + 9, "MEM KB", 1, TM_COLOR_MUTED); + tm_draw_text(app->w, app->h, 260, y + 9, "TICKS", 1, TM_COLOR_MUTED); + tm_draw_text(app->w, app->h, 350, y + 9, "IMAGE", 1, TM_COLOR_MUTED); +} + +static void tm_draw_row(const tm_app *app, int row_y, int row_index) { + const cleonos_proc_snapshot *snap; + char value[32]; + tm_u32 row_bg; + tm_u32 state_bg; + int selected; + + if (app == (const tm_app *)0 || row_index < 0 || (u64)(unsigned int)row_index >= app->row_count) { + return; + } + + snap = &app->rows[row_index]; + selected = (row_index == app->selected) ? 1 : 0; + row_bg = selected != 0 ? TM_COLOR_SELECT : ((row_index & 1) != 0 ? TM_COLOR_ROW_ALT : TM_COLOR_PANEL); + + tm_fill_rect(app->w, app->h, 0, row_y, app->w, TM_ROW_H, row_bg); + if (selected != 0) { + tm_fill_rect(app->w, app->h, 0, row_y, 4, TM_ROW_H, TM_COLOR_SELECT_BORDER); + } + tm_fill_rect(app->w, app->h, 0, row_y + TM_ROW_H - 1, app->w, 1, 0x00E9E9E9U); + + tm_u64_to_dec(value, (u64)sizeof(value), snap->pid); + tm_draw_text_limit(app->w, app->h, 18, row_y + 8, value, 1, TM_COLOR_TEXT, 78); + + state_bg = TM_COLOR_GOOD; + if (snap->state == CLEONOS_PROC_STATE_STOPPED) { + state_bg = TM_COLOR_WARN; + } else if (snap->state == CLEONOS_PROC_STATE_EXITED) { + state_bg = TM_COLOR_BAD; + } + tm_fill_rect(app->w, app->h, 78, row_y + 4, 78, 16, state_bg); + tm_stroke_rect(app->w, app->h, 78, row_y + 4, 78, 16, 0x00D9D9D9U); + tm_draw_text_limit(app->w, app->h, 84, row_y + 9, tm_state_name(snap->state), 1, TM_COLOR_TEXT, 152); + + tm_u64_to_dec(value, (u64)sizeof(value), snap->mem_bytes / 1024ULL); + tm_draw_text_limit(app->w, app->h, 172, row_y + 8, value, 1, TM_COLOR_TEXT, 250); + + tm_u64_to_dec(value, (u64)sizeof(value), snap->runtime_ticks); + tm_draw_text_limit(app->w, app->h, 260, row_y + 8, value, 1, TM_COLOR_TEXT, 342); + + tm_draw_text_limit(app->w, app->h, 350, row_y + 8, snap->path, 1, TM_COLOR_TEXT, app->w - 16); +} + +static void tm_draw_rows(const tm_app *app) { + int list_top; + int visible; + int i; + + if (app == (const tm_app *)0) { + return; + } + + list_top = TM_TITLE_H + TM_TOOLBAR_H + TM_HEADER_H; + visible = tm_visible_rows(app); + tm_fill_rect(app->w, app->h, 0, list_top, app->w, app->h - list_top - TM_STATUS_H, TM_COLOR_PANEL); + + if (app->row_count == 0ULL) { + tm_draw_text(app->w, app->h, 28, list_top + 32, "NO PROCESS SNAPSHOTS", 2, TM_COLOR_MUTED); + return; + } + + for (i = 0; i < visible; i++) { + int row_index = app->scroll + i; + if ((u64)(unsigned int)row_index >= app->row_count) { + break; + } + tm_draw_row(app, list_top + (i * TM_ROW_H), row_index); + } + + if (app->scroll > 0) { + tm_draw_text(app->w, app->h, app->w - 28, list_top + 8, "^", 1, TM_COLOR_MUTED); + } + if ((u64)(unsigned int)(app->scroll + visible) < app->row_count) { + tm_draw_text(app->w, app->h, app->w - 28, app->h - TM_STATUS_H - 18, "V", 1, TM_COLOR_MUTED); + } +} + +static void tm_draw_status(const tm_app *app) { + int y; + + if (app == (const tm_app *)0) { + return; + } + + y = app->h - TM_STATUS_H; + tm_fill_rect(app->w, app->h, 0, y, app->w, TM_STATUS_H, 0x00F7F7F7U); + tm_fill_rect(app->w, app->h, 0, y, app->w, 1, TM_COLOR_BORDER); + tm_draw_text_limit(app->w, app->h, 14, y + 10, app->status, 1, TM_COLOR_MUTED, app->w - 12); +} + +static void tm_render(tm_app *app) { + if (app == (tm_app *)0) { + return; + } + + tm_fill_rect(app->w, app->h, 0, 0, app->w, app->h, TM_COLOR_BG); + tm_draw_titlebar(app); + tm_draw_toolbar(app); + tm_draw_header(app); + tm_draw_rows(app); + tm_draw_status(app); + (void)tm_present(app); +} + +static void tm_select_row(tm_app *app, int index) { + if (app == (tm_app *)0) { + return; + } + + if (app->row_count == 0ULL) { + app->selected = -1; + app->selected_pid = 0ULL; + return; + } + + app->selected = tm_clampi(index, 0, (int)app->row_count - 1); + tm_clamp_selection(app); +} + +static void tm_move_selection(tm_app *app, int delta) { + if (app == (tm_app *)0 || app->row_count == 0ULL) { + return; + } + tm_select_row(app, app->selected + delta); +} + +static void tm_kill_selected(tm_app *app) { + u64 self_pid; + u64 target_pid; + + if (app == (tm_app *)0 || app->selected < 0 || (u64)(unsigned int)app->selected >= app->row_count) { + tm_set_status(app, "NO PROCESS SELECTED"); + return; + } + + self_pid = cleonos_sys_getpid(); + target_pid = app->rows[app->selected].pid; + if (target_pid == 0ULL) { + tm_set_status(app, "INVALID PROCESS"); + return; + } + if (target_pid == self_pid) { + tm_set_status(app, "REFUSED TO END TASK MANAGER ITSELF"); + return; + } + + if (cleonos_sys_proc_kill(target_pid, CLEONOS_SIGTERM) == 0ULL) { + tm_set_status(app, "END TASK FAILED"); + return; + } + + tm_reload(app); + tm_set_status(app, "SIGTERM SENT"); +} + +static int tm_hit_close(const tm_app *app, int local_x, int local_y) { + if (app == (const tm_app *)0) { + return 0; + } + return (local_x >= app->w - TM_CLOSE_W && local_x < app->w && local_y >= 0 && local_y < TM_TITLE_H) ? 1 : 0; +} + +static int tm_hit_rect(int local_x, int local_y, int x, int y, int w, int h) { + return (local_x >= x && local_x < x + w && local_y >= y && local_y < y + h) ? 1 : 0; +} + +static void tm_handle_toolbar_click(tm_app *app, int local_x, int local_y) { + int y = TM_TITLE_H; + int kill_x; + + if (app == (tm_app *)0 || local_y < y || local_y >= y + TM_TOOLBAR_H) { + return; + } + + kill_x = app->w - 122; + if (tm_hit_rect(local_x, local_y, 164, y + 10, 86, 28) != 0) { + tm_reload(app); + tm_render(app); + return; + } + if (tm_hit_rect(local_x, local_y, 260, y + 10, 94, 28) != 0) { + app->include_exited = (app->include_exited == 0) ? 1 : 0; + tm_reload(app); + tm_render(app); + return; + } + if (kill_x > 370 && tm_hit_rect(local_x, local_y, kill_x, y + 10, 104, 28) != 0) { + tm_kill_selected(app); + tm_render(app); + } +} + +static void tm_handle_list_click(tm_app *app, int local_y) { + int list_top; + int list_bottom; + int row_index; + + if (app == (tm_app *)0) { + return; + } + + list_top = TM_TITLE_H + TM_TOOLBAR_H + TM_HEADER_H; + list_bottom = app->h - TM_STATUS_H; + if (local_y < list_top || local_y >= list_bottom) { + return; + } + + row_index = app->scroll + ((local_y - list_top) / TM_ROW_H); + if (row_index >= 0 && (u64)(unsigned int)row_index < app->row_count) { + tm_select_row(app, row_index); + tm_render(app); + } +} + +static void tm_handle_key(tm_app *app, u64 key) { + if (app == (tm_app *)0) { + return; + } + + if (key == (u64)'q' || key == (u64)'Q' || key == 27ULL) { + app->running = 0; + return; + } + if (key == (u64)'r' || key == (u64)'R') { + tm_reload(app); + tm_render(app); + return; + } + if (key == (u64)'a' || key == (u64)'A') { + app->include_exited = (app->include_exited == 0) ? 1 : 0; + tm_reload(app); + tm_render(app); + return; + } + if (key == (u64)'k' || key == (u64)'K' || key == 127ULL) { + tm_kill_selected(app); + tm_render(app); + return; + } + if (key == TM_KEY_UP || key == (u64)'w' || key == (u64)'W') { + tm_move_selection(app, -1); + tm_render(app); + return; + } + if (key == TM_KEY_DOWN || key == (u64)'s' || key == (u64)'S') { + tm_move_selection(app, 1); + tm_render(app); + return; + } + if (key == TM_KEY_LEFT) { + tm_move_selection(app, -tm_visible_rows(app)); + tm_render(app); + return; + } + if (key == TM_KEY_RIGHT) { + tm_move_selection(app, tm_visible_rows(app)); + tm_render(app); + } +} + +static void tm_handle_event(tm_app *app, const cleonos_wm_event *event) { + if (app == (tm_app *)0 || event == (const cleonos_wm_event *)0) { + return; + } + + if (event->type == CLEONOS_WM_EVENT_KEY) { + tm_handle_key(app, event->arg0); + return; + } + + if (event->type == CLEONOS_WM_EVENT_MOUSE_BUTTON) { + u64 buttons = event->arg0; + u64 changed = event->arg1; + int local_x = tm_u64_as_i32(event->arg2); + int local_y = tm_u64_as_i32(event->arg3); + int left_changed = ((changed & 0x1ULL) != 0ULL) ? 1 : 0; + int left_down = ((buttons & 0x1ULL) != 0ULL) ? 1 : 0; + + if (left_changed == 0) { + return; + } + if (left_down == 0) { + app->dragging = 0; + return; + } + if (tm_hit_close(app, local_x, local_y) != 0) { + app->running = 0; + return; + } + if (local_y >= 0 && local_y < TM_TITLE_H) { + app->dragging = 1; + app->drag_dx = local_x; + app->drag_dy = local_y; + return; + } + tm_handle_toolbar_click(app, local_x, local_y); + tm_handle_list_click(app, local_y); + return; + } + + if (event->type == CLEONOS_WM_EVENT_MOUSE_MOVE && app->dragging != 0) { + cleonos_wm_move_req req; + app->x = tm_u64_as_i32(event->arg0) - app->drag_dx; + app->y = tm_u64_as_i32(event->arg1) - app->drag_dy; + req.window_id = app->window_id; + req.x = (u64)(i64)app->x; + req.y = (u64)(i64)app->y; + (void)cleonos_sys_wm_move(&req); + } +} + +static void tm_loop(tm_app *app) { + if (app == (tm_app *)0) { + return; + } + + while (app->running != 0) { + u64 budget = 0ULL; + int handled = 0; + + while (budget < TM_EVENT_BUDGET) { + cleonos_wm_event event; + tm_zero(&event, (u64)sizeof(event)); + if (cleonos_sys_wm_poll_event(app->window_id, &event) == 0ULL) { + break; + } + tm_handle_event(app, &event); + handled = 1; + if (app->running == 0) { + break; + } + budget++; + } + + if (app->running == 0) { + break; + } + + if (cleonos_sys_timer_ticks() - app->last_refresh_tick >= TM_REFRESH_TICKS) { + tm_reload(app); + tm_render(app); + handled = 1; + } + + if (handled != 0) { + (void)cleonos_sys_yield(); + } else { + (void)cleonos_sys_sleep_ticks(1ULL); + } + } +} + +static int tm_choose_geometry(tm_app *app) { + cleonos_fb_info fb; + int max_w; + int max_h; + + if (app == (tm_app *)0) { + return 0; + } + + tm_zero(&fb, (u64)sizeof(fb)); + if (cleonos_sys_fb_info(&fb) == 0ULL || fb.width == 0ULL || fb.height == 0ULL || fb.bpp != 32ULL) { + return 0; + } + if (fb.width > 4096ULL || fb.height > 4096ULL) { + return 0; + } + + app->screen_w = (int)fb.width; + app->screen_h = (int)fb.height; + max_w = app->screen_w - 80; + max_h = app->screen_h - 120; + if (max_w < TM_MIN_W) { + max_w = app->screen_w; + } + if (max_h < TM_MIN_H) { + max_h = app->screen_h; + } + if (max_w > (int)TM_CANVAS_MAX_W) { + max_w = (int)TM_CANVAS_MAX_W; + } + if (max_h > (int)TM_CANVAS_MAX_H) { + max_h = (int)TM_CANVAS_MAX_H; + } + + app->w = tm_clampi(TM_DEFAULT_W, TM_MIN_W, max_w); + app->h = tm_clampi(TM_DEFAULT_H, TM_MIN_H, max_h); + app->x = (app->screen_w > app->w) ? ((app->screen_w - app->w) / 2) : 0; + app->y = (app->screen_h > app->h) ? ((app->screen_h - app->h) / 2) : 0; + return 1; +} + +static int tm_create_window(tm_app *app) { + cleonos_wm_create_req req; + + if (app == (tm_app *)0) { + return 0; + } + + req.x = (u64)(i64)app->x; + req.y = (u64)(i64)app->y; + req.width = (u64)(unsigned int)app->w; + req.height = (u64)(unsigned int)app->h; + req.flags = 0ULL; + app->window_id = cleonos_sys_wm_create(&req); + return (app->window_id != 0ULL) ? 1 : 0; +} + +static void tm_switch_to_display(tm_app *app) { + if (app == (tm_app *)0) { + return; + } + + app->old_tty = cleonos_sys_tty_active(); + if (app->old_tty != TM_TTY_DISPLAY) { + (void)cleonos_sys_tty_switch(TM_TTY_DISPLAY); + app->tty_switched = 1; + } +} + +static void tm_restore_tty(tm_app *app) { + if (app == (tm_app *)0) { + return; + } + + if (app->tty_switched != 0) { + (void)cleonos_sys_tty_switch(app->old_tty); + app->tty_switched = 0; + } +} + +int cleonos_app_main(int argc, char **argv, char **envp) { + tm_app app; + + (void)argc; + (void)argv; + (void)envp; + + tm_zero(&app, (u64)sizeof(app)); + app.selected = -1; + app.running = 1; + tm_set_status(&app, "STARTING TASK MANAGER"); + + if (tm_choose_geometry(&app) == 0) { + return 1; + } + + tm_switch_to_display(&app); + tm_reload(&app); + if (tm_create_window(&app) == 0) { + tm_restore_tty(&app); + return 1; + } + + tm_render(&app); + (void)cleonos_sys_wm_set_focus(app.window_id); + tm_loop(&app); + (void)cleonos_sys_wm_destroy(app.window_id); + tm_restore_tty(&app); + return 0; +} diff --git a/cleonos/c/apps/terminal/terminal.c b/cleonos/c/apps/terminal/terminal.c new file mode 100644 index 0000000..5a8371c --- /dev/null +++ b/cleonos/c/apps/terminal/terminal.c @@ -0,0 +1,1351 @@ +#include "terminal.h" + +#define TERM_TTY_DISPLAY 1ULL +#define TERM_TOP_CLAMP_Y 24 +#define TERM_TASKBAR_H 48 +#define TERM_TITLE_H 32 +#define TERM_BOTTOM_H 30 +#define TERM_CONTROL_W 46 +#define TERM_RESIZE_GRIP 18 +#define TERM_MIN_W 360 +#define TERM_MIN_H 220 +#define TERM_DEFAULT_W 720 +#define TERM_DEFAULT_H 420 +#define TERM_CANVAS_MAX_PIXELS (1280ULL * 800ULL) +#define TERM_LINES 128U +#define TERM_LINE_MAX 160U +#define TERM_INPUT_MAX 160U +#define TERM_ANSI_MAX 24U +#define TERM_EVENT_BUDGET 128ULL +#define TERM_IDLE_SPINS 24 + +#define TERM_COLOR_WHITE 0x00FFFFFFU +#define TERM_COLOR_WIN_BLUE 0x000078D7U +#define TERM_COLOR_CLOSE 0x00E81123U +#define TERM_COLOR_TITLE_INACTIVE 0x00F3F3F3U +#define TERM_COLOR_TEXT 0x00232323U +#define TERM_COLOR_MUTED 0x00666666U +#define TERM_COLOR_BORDER 0x00D0D0D0U +#define TERM_COLOR_BG 0x000C0C0CU +#define TERM_COLOR_BAR 0x00111111U +#define TERM_COLOR_DEFAULT 0x00DCDCDCU +#define TERM_COLOR_PROMPT 0x0086D98AU + +#define TERM_GLYPH7(r0, r1, r2, r3, r4, r5, r6) \ + (((u64)(r0) << 30U) | ((u64)(r1) << 25U) | ((u64)(r2) << 20U) | ((u64)(r3) << 15U) | ((u64)(r4) << 10U) | \ + ((u64)(r5) << 5U) | (u64)(r6)) + +typedef unsigned int term_u32; + +typedef struct term_app { + int screen_w; + int screen_h; + int x; + int y; + int w; + int h; + int restore_x; + int restore_y; + int restore_w; + int restore_h; + int running; + int focused; + int dragging; + int drag_dx; + int drag_dy; + int resizing; + int resize_start_x; + int resize_start_y; + int resize_start_w; + int resize_start_h; + int maximized; + u64 window_id; + u64 old_tty; + int tty_switched; + term_u32 *pixels; + u64 pixel_count; + char cwd[USH_PATH_MAX]; + char input[TERM_INPUT_MAX]; + u64 input_len; + char lines[TERM_LINES][TERM_LINE_MAX]; + term_u32 line_colors[TERM_LINES]; + u64 line_count; + term_u32 color; + int ansi_state; + char ansi_buf[TERM_ANSI_MAX]; + u64 ansi_len; +} term_app; + +static int term_clampi(int value, int min_value, int max_value) { + if (value < min_value) { + return min_value; + } + if (value > max_value) { + return max_value; + } + return value; +} + +static int term_u64_as_i32(u64 raw) { + return (int)(i64)raw; +} + +static void term_append_to(char *dst, u64 dst_size, const char *src) { + u64 len; + u64 i = 0ULL; + + if (dst == (char *)0 || dst_size == 0ULL || src == (const char *)0) { + return; + } + + len = ush_strlen(dst); + while (src[i] != '\0' && len + 1ULL < dst_size) { + dst[len++] = src[i++]; + } + dst[len] = '\0'; +} + +static char term_upper_char(char ch) { + if (ch >= 'a' && ch <= 'z') { + return (char)(ch - ('a' - 'A')); + } + return ch; +} + +static u64 term_glyph_mask(char ch) { + switch (term_upper_char(ch)) { + case 'A': + return TERM_GLYPH7(14U, 17U, 17U, 31U, 17U, 17U, 17U); + case 'B': + return TERM_GLYPH7(30U, 17U, 17U, 30U, 17U, 17U, 30U); + case 'C': + return TERM_GLYPH7(14U, 17U, 16U, 16U, 16U, 17U, 14U); + case 'D': + return TERM_GLYPH7(30U, 17U, 17U, 17U, 17U, 17U, 30U); + case 'E': + return TERM_GLYPH7(31U, 16U, 16U, 30U, 16U, 16U, 31U); + case 'F': + return TERM_GLYPH7(31U, 16U, 16U, 30U, 16U, 16U, 16U); + case 'G': + return TERM_GLYPH7(14U, 17U, 16U, 23U, 17U, 17U, 15U); + case 'H': + return TERM_GLYPH7(17U, 17U, 17U, 31U, 17U, 17U, 17U); + case 'I': + return TERM_GLYPH7(31U, 4U, 4U, 4U, 4U, 4U, 31U); + case 'J': + return TERM_GLYPH7(1U, 1U, 1U, 1U, 17U, 17U, 14U); + case 'K': + return TERM_GLYPH7(17U, 18U, 20U, 24U, 20U, 18U, 17U); + case 'L': + return TERM_GLYPH7(16U, 16U, 16U, 16U, 16U, 16U, 31U); + case 'M': + return TERM_GLYPH7(17U, 27U, 21U, 21U, 17U, 17U, 17U); + case 'N': + return TERM_GLYPH7(17U, 25U, 21U, 19U, 17U, 17U, 17U); + case 'O': + return TERM_GLYPH7(14U, 17U, 17U, 17U, 17U, 17U, 14U); + case 'P': + return TERM_GLYPH7(30U, 17U, 17U, 30U, 16U, 16U, 16U); + case 'Q': + return TERM_GLYPH7(14U, 17U, 17U, 17U, 21U, 18U, 13U); + case 'R': + return TERM_GLYPH7(30U, 17U, 17U, 30U, 20U, 18U, 17U); + case 'S': + return TERM_GLYPH7(15U, 16U, 16U, 14U, 1U, 1U, 30U); + case 'T': + return TERM_GLYPH7(31U, 4U, 4U, 4U, 4U, 4U, 4U); + case 'U': + return TERM_GLYPH7(17U, 17U, 17U, 17U, 17U, 17U, 14U); + case 'V': + return TERM_GLYPH7(17U, 17U, 17U, 17U, 17U, 10U, 4U); + case 'W': + return TERM_GLYPH7(17U, 17U, 17U, 21U, 21U, 21U, 10U); + case 'X': + return TERM_GLYPH7(17U, 17U, 10U, 4U, 10U, 17U, 17U); + case 'Y': + return TERM_GLYPH7(17U, 17U, 10U, 4U, 4U, 4U, 4U); + case 'Z': + return TERM_GLYPH7(31U, 1U, 2U, 4U, 8U, 16U, 31U); + case '0': + return TERM_GLYPH7(14U, 17U, 19U, 21U, 25U, 17U, 14U); + case '1': + return TERM_GLYPH7(4U, 12U, 4U, 4U, 4U, 4U, 14U); + case '2': + return TERM_GLYPH7(14U, 17U, 1U, 2U, 4U, 8U, 31U); + case '3': + return TERM_GLYPH7(30U, 1U, 1U, 14U, 1U, 1U, 30U); + case '4': + return TERM_GLYPH7(2U, 6U, 10U, 18U, 31U, 2U, 2U); + case '5': + return TERM_GLYPH7(31U, 16U, 16U, 30U, 1U, 1U, 30U); + case '6': + return TERM_GLYPH7(14U, 16U, 16U, 30U, 17U, 17U, 14U); + case '7': + return TERM_GLYPH7(31U, 1U, 2U, 4U, 8U, 8U, 8U); + case '8': + return TERM_GLYPH7(14U, 17U, 17U, 14U, 17U, 17U, 14U); + case '9': + return TERM_GLYPH7(14U, 17U, 17U, 15U, 1U, 1U, 14U); + case '-': + return TERM_GLYPH7(0U, 0U, 0U, 31U, 0U, 0U, 0U); + case '>': + return TERM_GLYPH7(16U, 8U, 4U, 2U, 4U, 8U, 16U); + case '<': + return TERM_GLYPH7(1U, 2U, 4U, 8U, 4U, 2U, 1U); + case '$': + return TERM_GLYPH7(4U, 15U, 20U, 14U, 5U, 30U, 4U); + case '#': + return TERM_GLYPH7(10U, 31U, 10U, 10U, 31U, 10U, 10U); + case '?': + return TERM_GLYPH7(14U, 17U, 1U, 2U, 4U, 0U, 4U); + case '!': + return TERM_GLYPH7(4U, 4U, 4U, 4U, 4U, 0U, 4U); + case ',': + return TERM_GLYPH7(0U, 0U, 0U, 0U, 0U, 4U, 8U); + case ';': + return TERM_GLYPH7(0U, 4U, 4U, 0U, 0U, 4U, 8U); + case '*': + return TERM_GLYPH7(0U, 21U, 14U, 31U, 14U, 21U, 0U); + case '(': + return TERM_GLYPH7(2U, 4U, 8U, 8U, 8U, 4U, 2U); + case ')': + return TERM_GLYPH7(8U, 4U, 2U, 2U, 2U, 4U, 8U); + case '[': + return TERM_GLYPH7(14U, 8U, 8U, 8U, 8U, 8U, 14U); + case ']': + return TERM_GLYPH7(14U, 2U, 2U, 2U, 2U, 2U, 14U); + case '@': + return TERM_GLYPH7(14U, 17U, 23U, 21U, 23U, 16U, 15U); + case '%': + return TERM_GLYPH7(24U, 25U, 2U, 4U, 8U, 19U, 3U); + case '&': + return TERM_GLYPH7(12U, 18U, 20U, 8U, 21U, 18U, 13U); + case '~': + return TERM_GLYPH7(0U, 0U, 8U, 21U, 2U, 0U, 0U); + case '\\': + return TERM_GLYPH7(16U, 16U, 8U, 4U, 2U, 1U, 1U); + case '"': + return TERM_GLYPH7(10U, 10U, 10U, 0U, 0U, 0U, 0U); + case '\'': + return TERM_GLYPH7(4U, 4U, 8U, 0U, 0U, 0U, 0U); + case '_': + return TERM_GLYPH7(0U, 0U, 0U, 0U, 0U, 0U, 31U); + case '.': + return TERM_GLYPH7(0U, 0U, 0U, 0U, 0U, 12U, 12U); + case ':': + return TERM_GLYPH7(0U, 12U, 12U, 0U, 12U, 12U, 0U); + case '/': + return TERM_GLYPH7(1U, 1U, 2U, 4U, 8U, 16U, 16U); + case '+': + return TERM_GLYPH7(0U, 4U, 4U, 31U, 4U, 4U, 0U); + case '=': + return TERM_GLYPH7(0U, 0U, 31U, 0U, 31U, 0U, 0U); + case '^': + return TERM_GLYPH7(4U, 10U, 17U, 0U, 0U, 0U, 0U); + case '|': + return TERM_GLYPH7(4U, 4U, 4U, 4U, 4U, 4U, 4U); + default: + return 0ULL; + } +} + +static void term_fill_rect(term_app *app, int x, int y, int w, int h, term_u32 color) { + int left; + int top; + int right; + int bottom; + int row; + + if (app == (term_app *)0 || app->pixels == (term_u32 *)0 || app->w <= 0 || app->h <= 0 || w <= 0 || h <= 0) { + return; + } + + left = term_clampi(x, 0, app->w); + top = term_clampi(y, 0, app->h); + right = term_clampi(x + w, 0, app->w); + bottom = term_clampi(y + h, 0, app->h); + if (left >= right || top >= bottom) { + return; + } + + for (row = top; row < bottom; row++) { + u64 base = (u64)(unsigned int)row * (u64)(unsigned int)app->w; + int col; + + if (base + (u64)(unsigned int)right > app->pixel_count) { + return; + } + for (col = left; col < right; col++) { + app->pixels[base + (u64)(unsigned int)col] = color; + } + } +} + +static void term_stroke_rect(term_app *app, int x, int y, int w, int h, term_u32 color) { + term_fill_rect(app, x, y, w, 1, color); + term_fill_rect(app, x, y + h - 1, w, 1, color); + term_fill_rect(app, x, y, 1, h, color); + term_fill_rect(app, x + w - 1, y, 1, h, color); +} + +static void term_draw_char(term_app *app, int x, int y, char ch, int scale, term_u32 color) { + u64 mask = term_glyph_mask(ch); + int row; + + if (mask == 0ULL || scale <= 0) { + return; + } + + for (row = 0; row < 7; row++) { + int col; + for (col = 0; col < 5; col++) { + unsigned int bit_index = (unsigned int)((6 - row) * 5 + (4 - col)); + if ((mask & (1ULL << bit_index)) != 0ULL) { + term_fill_rect(app, x + (col * scale), y + (row * scale), scale, scale, color); + } + } + } +} + +static void term_draw_text_limit(term_app *app, int x, int y, const char *text, int scale, term_u32 color, int max_x) { + int cursor_x = x; + + if (app == (term_app *)0 || text == (const char *)0 || scale <= 0) { + return; + } + if (max_x <= 0 || max_x > app->w) { + max_x = app->w; + } + + while (*text != '\0' && cursor_x + (5 * scale) <= max_x) { + if (*text != ' ') { + term_draw_char(app, cursor_x, y, *text, scale, color); + } + cursor_x += 6 * scale; + text++; + } +} + +static void term_draw_text(term_app *app, int x, int y, const char *text, int scale, term_u32 color) { + term_draw_text_limit(app, x, y, text, scale, color, app != (term_app *)0 ? app->w : 0); +} + +static void term_draw_control_button(term_app *app, int x, int active, int kind) { + term_u32 bg = (kind == 2) ? TERM_COLOR_CLOSE : (active != 0 ? 0x001A5EA0U : 0x00E5E5E5U); + term_u32 fg = (kind == 2 || active != 0) ? TERM_COLOR_WHITE : TERM_COLOR_TEXT; + int cy = TERM_TITLE_H / 2; + int cx = x + (TERM_CONTROL_W / 2); + + term_fill_rect(app, x, 0, TERM_CONTROL_W, TERM_TITLE_H, bg); + if (kind == 0) { + term_fill_rect(app, cx - 6, cy + 4, 12, 1, fg); + } else if (kind == 1) { + term_stroke_rect(app, cx - 6, cy - 6, 12, 12, fg); + term_fill_rect(app, cx - 6, cy - 6, 12, 2, fg); + } else if (kind == 3) { + term_stroke_rect(app, cx - 4, cy - 7, 10, 10, fg); + term_fill_rect(app, cx - 4, cy - 7, 10, 2, fg); + term_stroke_rect(app, cx - 7, cy - 3, 10, 10, fg); + term_fill_rect(app, cx - 7, cy - 3, 10, 2, fg); + } else { + int i; + for (i = 0; i < 11; i++) { + term_fill_rect(app, cx - 5 + i, cy - 5 + i, 1, 1, fg); + term_fill_rect(app, cx + 5 - i, cy - 5 + i, 1, 1, fg); + } + } +} + +static term_u32 term_ansi_color(int code) { + switch (code) { + case 30: + return 0x00404040U; + case 31: + return 0x00F44747U; + case 32: + return 0x0060D060U; + case 33: + return 0x00DCDCAAU; + case 34: + return 0x00569CD6U; + case 35: + return 0x00C586C0U; + case 36: + return 0x004EC9B0U; + case 37: + return 0x00DCDCDCU; + case 90: + return 0x00808080U; + case 91: + return 0x00FF7070U; + case 92: + return 0x0080FF80U; + case 93: + return 0x00FFFF80U; + case 94: + return 0x0080C0FFU; + case 95: + return 0x00FF80FFU; + case 96: + return 0x0080FFFFU; + case 97: + return 0x00FFFFFFU; + default: + return TERM_COLOR_DEFAULT; + } +} + +static void term_clear(term_app *app) { + u64 i; + + if (app == (term_app *)0) { + return; + } + + ush_zero(app->lines, (u64)sizeof(app->lines)); + for (i = 0ULL; i < (u64)TERM_LINES; i++) { + app->line_colors[i] = app->color; + } + app->line_count = 0ULL; +} + +static void term_ensure_line(term_app *app) { + if (app != (term_app *)0 && app->line_count == 0ULL) { + app->line_count = 1ULL; + app->lines[0][0] = '\0'; + app->line_colors[0] = app->color; + } +} + +static void term_shift_lines(term_app *app) { + u64 i; + + for (i = 1ULL; i < (u64)TERM_LINES; i++) { + ush_copy(app->lines[i - 1ULL], (u64)TERM_LINE_MAX, app->lines[i]); + app->line_colors[i - 1ULL] = app->line_colors[i]; + } + app->lines[TERM_LINES - 1U][0] = '\0'; + app->line_colors[TERM_LINES - 1U] = app->color; +} + +static void term_newline(term_app *app) { + if (app == (term_app *)0) { + return; + } + if (app->line_count == 0ULL) { + term_ensure_line(app); + return; + } + if (app->line_count < (u64)TERM_LINES) { + app->lines[app->line_count][0] = '\0'; + app->line_colors[app->line_count] = app->color; + app->line_count++; + return; + } + term_shift_lines(app); +} + +static void term_append_char(term_app *app, char ch) { + char *line; + u64 len; + + if (app == (term_app *)0) { + return; + } + if (ch == '\r') { + return; + } + if (ch == '\n') { + term_newline(app); + return; + } + if (ch == '\t') { + ch = ' '; + } + if (ch < ' ' || ch > '~') { + ch = '?'; + } + + term_ensure_line(app); + line = app->lines[app->line_count - 1ULL]; + app->line_colors[app->line_count - 1ULL] = app->color; + len = ush_strlen(line); + if (len + 1ULL >= (u64)TERM_LINE_MAX) { + term_newline(app); + line = app->lines[app->line_count - 1ULL]; + app->line_colors[app->line_count - 1ULL] = app->color; + len = 0ULL; + } + line[len] = ch; + line[len + 1ULL] = '\0'; +} + +static int term_ansi_number(const char *text, u64 *offset, int *out_value) { + int value = 0; + int any = 0; + + if (text == (const char *)0 || offset == (u64 *)0 || out_value == (int *)0) { + return 0; + } + while (text[*offset] >= '0' && text[*offset] <= '9') { + value = (value * 10) + (text[*offset] - '0'); + (*offset)++; + any = 1; + } + *out_value = value; + return any; +} + +static void term_apply_sgr(term_app *app, const char *params) { + u64 pos = 0ULL; + + if (app == (term_app *)0 || params == (const char *)0 || params[0] == '\0') { + if (app != (term_app *)0) { + app->color = TERM_COLOR_DEFAULT; + } + return; + } + + while (params[pos] != '\0') { + int code = 0; + int has_number = term_ansi_number(params, &pos, &code); + + if (has_number == 0) { + code = 0; + } + if (code == 0 || code == 39) { + app->color = TERM_COLOR_DEFAULT; + } else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) { + app->color = term_ansi_color(code); + } + if (params[pos] == ';') { + pos++; + continue; + } + break; + } +} + +static void term_apply_ansi(term_app *app, char final_ch) { + if (app == (term_app *)0) { + return; + } + app->ansi_buf[app->ansi_len] = '\0'; + if (final_ch == 'm') { + term_apply_sgr(app, app->ansi_buf); + } else if (final_ch == 'J') { + u64 pos = 0ULL; + int code = 0; + + if (term_ansi_number(app->ansi_buf, &pos, &code) != 0 && (code == 2 || code == 3)) { + term_clear(app); + } + } + app->ansi_state = 0; + app->ansi_len = 0ULL; +} + +static void term_append_ansi_char(term_app *app, char ch) { + if (app == (term_app *)0) { + return; + } + + if (app->ansi_state == 1) { + if (ch == '[') { + app->ansi_state = 2; + app->ansi_len = 0ULL; + return; + } + app->ansi_state = 0; + app->ansi_len = 0ULL; + return; + } + + if (app->ansi_state == 2) { + if ((ch >= '0' && ch <= '9') || ch == ';' || ch == '?' || ch == '=') { + if (app->ansi_len + 1ULL < (u64)sizeof(app->ansi_buf)) { + app->ansi_buf[app->ansi_len++] = ch; + } + return; + } + term_apply_ansi(app, ch); + return; + } + + if (ch == 27) { + app->ansi_state = 1; + app->ansi_len = 0ULL; + return; + } + term_append_char(app, ch); +} + +static void term_append_text(term_app *app, const char *text) { + u64 i = 0ULL; + + if (app == (term_app *)0 || text == (const char *)0) { + return; + } + while (text[i] != '\0') { + term_append_ansi_char(app, text[i]); + i++; + } +} + +static void term_append_line(term_app *app, const char *text) { + term_append_text(app, text); + term_append_char(app, '\n'); +} + +static void term_append_hex(term_app *app, u64 value) { + char buf[19]; + i64 nibble; + u64 pos = 0ULL; + + buf[pos++] = '0'; + buf[pos++] = 'X'; + for (nibble = 15; nibble >= 0; nibble--) { + u64 current = (value >> (u64)(nibble * 4)) & 0xFULL; + buf[pos++] = (current < 10ULL) ? (char)('0' + current) : (char)('A' + (current - 10ULL)); + } + buf[pos] = '\0'; + term_append_text(app, buf); +} + +static void term_append_prompt_line(term_app *app, const char *line) { + term_append_text(app, app->cwd); + term_append_text(app, " > "); + term_append_line(app, line); +} + +static int term_present(term_app *app) { + cleonos_wm_present_req req; + + if (app == (term_app *)0 || app->window_id == 0ULL || app->pixels == (term_u32 *)0) { + return 0; + } + req.window_id = app->window_id; + req.pixels_ptr = (u64)(usize)app->pixels; + req.src_width = (u64)(unsigned int)app->w; + req.src_height = (u64)(unsigned int)app->h; + req.src_pitch_bytes = (u64)(unsigned int)app->w * 4ULL; + return (cleonos_sys_wm_present(&req) != 0ULL) ? 1 : 0; +} + +static void term_render(term_app *app) { + int visible_lines; + int usable_h; + int line_h = 11; + u64 start = 0ULL; + u64 i; + char prompt[USH_PATH_MAX + TERM_INPUT_MAX + 8U]; + term_u32 title_bg; + term_u32 title_fg; + + if (app == (term_app *)0 || app->pixels == (term_u32 *)0) { + return; + } + + title_bg = (app->focused != 0) ? TERM_COLOR_WIN_BLUE : TERM_COLOR_TITLE_INACTIVE; + title_fg = (app->focused != 0) ? TERM_COLOR_WHITE : TERM_COLOR_TEXT; + term_fill_rect(app, 0, 0, app->w, app->h, TERM_COLOR_BG); + term_fill_rect(app, 0, 0, app->w, TERM_TITLE_H, title_bg); + term_fill_rect(app, 0, TERM_TITLE_H, app->w, 1, TERM_COLOR_BORDER); + term_draw_text_limit(app, 12, 12, "TERMINAL", 1, title_fg, app->w - (TERM_CONTROL_W * 3) - 8); + term_draw_control_button(app, app->w - (TERM_CONTROL_W * 3), app->focused, 0); + term_draw_control_button(app, app->w - (TERM_CONTROL_W * 2), app->focused, app->maximized != 0 ? 3 : 1); + term_draw_control_button(app, app->w - TERM_CONTROL_W, app->focused, 2); + term_fill_rect(app, 0, TERM_TITLE_H + 1, app->w, 30, 0x00111111U); + term_draw_text(app, 12, TERM_TITLE_H + 11, "CLEONOS PSEUDO TTY", 1, TERM_COLOR_PROMPT); + term_fill_rect(app, 8, TERM_TITLE_H + 31, app->w - 16, 1, 0x00242424U); + + usable_h = app->h - TERM_TITLE_H - 44 - TERM_BOTTOM_H; + if (usable_h < line_h) { + usable_h = line_h; + } + visible_lines = usable_h / line_h; + if (visible_lines < 1) { + visible_lines = 1; + } + if (app->line_count > (u64)visible_lines) { + start = app->line_count - (u64)visible_lines; + } + + for (i = start; i < app->line_count; i++) { + int row = (int)(i - start); + int y = TERM_TITLE_H + 50 + (row * line_h); + term_u32 color = app->line_colors[i]; + + if (y + 8 >= app->h - TERM_BOTTOM_H) { + break; + } + if (color == 0U) { + color = TERM_COLOR_DEFAULT; + } + term_draw_text_limit(app, 10, y, app->lines[i], 1, color, app->w - 10); + } + + term_fill_rect(app, 0, app->h - TERM_BOTTOM_H, app->w, TERM_BOTTOM_H, TERM_COLOR_BAR); + term_fill_rect(app, 0, app->h - TERM_BOTTOM_H, app->w, 1, 0x00333333U); + prompt[0] = '\0'; + ush_copy(prompt, (u64)sizeof(prompt), app->cwd); + term_append_to(prompt, (u64)sizeof(prompt), " > "); + term_append_to(prompt, (u64)sizeof(prompt), app->input); + term_append_to(prompt, (u64)sizeof(prompt), "|"); + term_draw_text_limit(app, 10, app->h - 19, prompt, 1, TERM_COLOR_PROMPT, app->w - 10); + + if (app->maximized == 0) { + term_fill_rect(app, app->w - 14, app->h - 3, 11, 1, TERM_COLOR_MUTED); + term_fill_rect(app, app->w - 10, app->h - 7, 7, 1, TERM_COLOR_MUTED); + term_fill_rect(app, app->w - 6, app->h - 11, 3, 1, TERM_COLOR_MUTED); + } +} + +static int term_render_present(term_app *app) { + term_render(app); + return term_present(app); +} + +static int term_alloc_pixels(term_app *app, int width, int height) { + u64 count; + + if (app == (term_app *)0 || width <= 0 || height <= 0) { + return 0; + } + count = (u64)(unsigned int)width * (u64)(unsigned int)height; + if (count == 0ULL || count > TERM_CANVAS_MAX_PIXELS || count > (((u64)-1) / 4ULL)) { + return 0; + } + app->pixels = (term_u32 *)malloc((size_t)(count * 4ULL)); + if (app->pixels == (term_u32 *)0) { + return 0; + } + app->pixel_count = count; + app->w = width; + app->h = height; + ush_zero(app->pixels, count * 4ULL); + return 1; +} + +static void term_release_pixels(term_app *app) { + if (app == (term_app *)0) { + return; + } + if (app->pixels != (term_u32 *)0) { + free(app->pixels); + } + app->pixels = (term_u32 *)0; + app->pixel_count = 0ULL; +} + +static int term_apply_geometry(term_app *app, int target_x, int target_y, int target_w, int target_h) { + cleonos_wm_resize_req resize_req; + cleonos_wm_move_req move_req; + term_u32 *new_pixels; + term_u32 *old_pixels; + u64 count; + u64 old_count; + int work_bottom; + int new_w; + int new_h; + int new_x; + int new_y; + int old_w; + int old_h; + + if (app == (term_app *)0 || app->window_id == 0ULL) { + return 0; + } + + work_bottom = app->screen_h - TERM_TASKBAR_H; + if (work_bottom < TERM_TOP_CLAMP_Y + TERM_MIN_H) { + work_bottom = app->screen_h; + } + new_w = term_clampi(target_w, TERM_MIN_W, app->screen_w); + new_h = term_clampi(target_h, TERM_MIN_H, work_bottom - TERM_TOP_CLAMP_Y); + new_x = term_clampi(target_x, 0, app->screen_w - new_w); + new_y = term_clampi(target_y, TERM_TOP_CLAMP_Y, work_bottom - new_h); + + if (new_w != app->w || new_h != app->h) { + count = (u64)(unsigned int)new_w * (u64)(unsigned int)new_h; + if (count == 0ULL || count > TERM_CANVAS_MAX_PIXELS || count > (((u64)-1) / 4ULL)) { + return 0; + } + old_pixels = app->pixels; + old_count = app->pixel_count; + old_w = app->w; + old_h = app->h; + app->pixels = (term_u32 *)0; + app->pixel_count = 0ULL; + free(old_pixels); + new_pixels = (term_u32 *)malloc((size_t)(count * 4ULL)); + if (new_pixels == (term_u32 *)0) { + app->pixels = (term_u32 *)malloc((size_t)(old_count * 4ULL)); + app->pixel_count = (app->pixels != (term_u32 *)0) ? old_count : 0ULL; + app->w = old_w; + app->h = old_h; + if (app->pixels != (term_u32 *)0) { + (void)term_render_present(app); + } + return 0; + } + ush_zero(new_pixels, count * 4ULL); + resize_req.window_id = app->window_id; + resize_req.width = (u64)(unsigned int)new_w; + resize_req.height = (u64)(unsigned int)new_h; + if (cleonos_sys_wm_resize(&resize_req) == 0ULL) { + free(new_pixels); + app->pixels = (term_u32 *)malloc((size_t)(old_count * 4ULL)); + app->pixel_count = (app->pixels != (term_u32 *)0) ? old_count : 0ULL; + app->w = old_w; + app->h = old_h; + if (app->pixels != (term_u32 *)0) { + (void)term_render_present(app); + } + return 0; + } + app->pixels = new_pixels; + app->pixel_count = count; + app->w = new_w; + app->h = new_h; + } + + if (new_x != app->x || new_y != app->y) { + move_req.window_id = app->window_id; + move_req.x = (u64)(i64)new_x; + move_req.y = (u64)(i64)new_y; + if (cleonos_sys_wm_move(&move_req) == 0ULL) { + return 0; + } + app->x = new_x; + app->y = new_y; + } + (void)term_render_present(app); + return 1; +} + +static void term_toggle_maximize(term_app *app) { + int work_h; + + if (app == (term_app *)0 || app->window_id == 0ULL) { + return; + } + work_h = app->screen_h - TERM_TASKBAR_H - TERM_TOP_CLAMP_Y; + if (work_h < TERM_MIN_H) { + work_h = app->screen_h - TERM_TOP_CLAMP_Y; + } + if (app->maximized == 0) { + app->restore_x = app->x; + app->restore_y = app->y; + app->restore_w = app->w; + app->restore_h = app->h; + app->maximized = 1; + if (term_apply_geometry(app, 0, TERM_TOP_CLAMP_Y, app->screen_w, work_h) == 0) { + app->maximized = 0; + (void)term_render_present(app); + } + return; + } + app->maximized = 0; + if (term_apply_geometry(app, app->restore_x, app->restore_y, app->restore_w, app->restore_h) == 0) { + app->maximized = 1; + (void)term_render_present(app); + } +} + +static const char *term_alias(const char *cmd) { + if (cmd == (const char *)0) { + return (const char *)0; + } + if (ush_streq(cmd, "dir") != 0) { + return "ls"; + } + if (ush_streq(cmd, "cls") != 0) { + return "clear"; + } + if (ush_streq(cmd, "poweroff") != 0) { + return "shutdown"; + } + if (ush_streq(cmd, "reboot") != 0) { + return "restart"; + } + return cmd; +} + +static int term_command_ctx_write(const char *cmd, const char *arg, const char *cwd) { + ush_cmd_ctx ctx; + + ush_zero(&ctx, (u64)sizeof(ctx)); + ush_copy(ctx.cmd, (u64)sizeof(ctx.cmd), cmd); + ush_copy(ctx.arg, (u64)sizeof(ctx.arg), arg); + ush_copy(ctx.cwd, (u64)sizeof(ctx.cwd), cwd); + return (cleonos_sys_fs_write(USH_CMD_CTX_PATH, (const char *)&ctx, (u64)sizeof(ctx)) != 0ULL) ? 1 : 0; +} + +static int term_command_ret_read(ush_cmd_ret *out_ret) { + u64 got; + + if (out_ret == (ush_cmd_ret *)0) { + return 0; + } + ush_zero(out_ret, (u64)sizeof(*out_ret)); + got = cleonos_sys_fs_read(USH_CMD_RET_PATH, (char *)out_ret, (u64)sizeof(*out_ret)); + return (got == (u64)sizeof(*out_ret)) ? 1 : 0; +} + +static void term_apply_ret(term_app *app, const ush_cmd_ret *ret) { + if (app == (term_app *)0 || ret == (const ush_cmd_ret *)0) { + return; + } + if ((ret->flags & USH_CMD_RET_FLAG_CWD) != 0ULL && ret->cwd[0] == '/') { + ush_copy(app->cwd, (u64)sizeof(app->cwd), ret->cwd); + } + if ((ret->flags & USH_CMD_RET_FLAG_EXIT) != 0ULL) { + app->running = 0; + } +} + +static void term_drain_fd(term_app *app, u64 fd) { + char buf[192]; + u64 guard = 0ULL; + + while (guard < 128ULL) { + u64 got = cleonos_sys_fd_read(fd, buf, (u64)sizeof(buf)); + + if (got == 0ULL || got == (u64)-1) { + break; + } + if (got < (u64)sizeof(buf)) { + buf[got] = '\0'; + term_append_text(app, buf); + } else { + u64 i; + for (i = 0ULL; i < got; i++) { + term_append_ansi_char(app, buf[i]); + } + } + guard++; + } +} + +static void term_emit_status(term_app *app, u64 status) { + if (status == 0ULL) { + return; + } + if ((status & (1ULL << 63)) != 0ULL) { + term_append_text(app, "PROCESS TERMINATED: "); + term_append_hex(app, status); + term_append_char(app, '\n'); + return; + } + term_append_text(app, "EXIT STATUS: "); + term_append_hex(app, status); + term_append_char(app, '\n'); +} + +static void term_exec_external(term_app *app, const char *cmd, const char *arg) { + ush_state sh; + ush_cmd_ret ret; + const char *canonical; + char path[USH_PATH_MAX]; + char env_line[USH_PATH_MAX + USH_CMD_MAX + 96ULL]; + u64 pty_fd; + u64 stdin_fd; + u64 status; + + ush_init_state(&sh); + ush_copy(sh.cwd, (u64)sizeof(sh.cwd), app->cwd); + canonical = term_alias(cmd); + if (canonical == (const char *)0 || ush_resolve_exec_path(&sh, canonical, path, (u64)sizeof(path)) == 0 || + cleonos_sys_fs_stat_type(path) != 1ULL) { + term_append_text(app, "COMMAND NOT FOUND: "); + term_append_line(app, cmd); + return; + } + + pty_fd = cleonos_sys_pty_open(); + if (pty_fd == (u64)-1) { + term_append_line(app, "PTY OPEN FAILED"); + return; + } + stdin_fd = cleonos_sys_fd_open("/dev/null", CLEONOS_O_RDONLY, 0ULL); + (void)cleonos_sys_fs_remove(USH_CMD_CTX_PATH); + (void)cleonos_sys_fs_remove(USH_CMD_RET_PATH); + + if (term_command_ctx_write(canonical, arg, app->cwd) == 0) { + term_append_line(app, "COMMAND CONTEXT WRITE FAILED"); + if (stdin_fd != (u64)-1) { + (void)cleonos_sys_fd_close(stdin_fd); + } + (void)cleonos_sys_fd_close(pty_fd); + return; + } + + env_line[0] = '\0'; + term_append_to(env_line, (u64)sizeof(env_line), "PWD="); + term_append_to(env_line, (u64)sizeof(env_line), app->cwd); + term_append_to(env_line, (u64)sizeof(env_line), ";CMD="); + term_append_to(env_line, (u64)sizeof(env_line), canonical); + + (void)term_render_present(app); + status = cleonos_sys_exec_pathv_io(path, arg, env_line, (stdin_fd == (u64)-1) ? CLEONOS_FD_INHERIT : stdin_fd, + pty_fd, pty_fd); + term_drain_fd(app, pty_fd); + + if (stdin_fd != (u64)-1) { + (void)cleonos_sys_fd_close(stdin_fd); + } + (void)cleonos_sys_fd_close(pty_fd); + if (status == (u64)-1) { + term_append_line(app, "EXEC REQUEST FAILED"); + } else { + if (term_command_ret_read(&ret) != 0) { + term_apply_ret(app, &ret); + } + term_emit_status(app, status); + } + (void)cleonos_sys_fs_remove(USH_CMD_CTX_PATH); + (void)cleonos_sys_fs_remove(USH_CMD_RET_PATH); +} + +static void term_exec_line(term_app *app, const char *line) { + ush_state sh; + char local[USH_LINE_MAX]; + char cmd[USH_CMD_MAX]; + char arg[USH_ARG_MAX]; + char path[USH_PATH_MAX]; + const char *canonical; + + if (app == (term_app *)0 || line == (const char *)0) { + return; + } + ush_copy(local, (u64)sizeof(local), line); + ush_trim_line(local); + if (local[0] == '\0') { + term_append_prompt_line(app, ""); + return; + } + + ush_parse_line(local, cmd, (u64)sizeof(cmd), arg, (u64)sizeof(arg)); + canonical = term_alias(cmd); + if (canonical != (const char *)0 && ush_streq(canonical, "clear") != 0) { + term_clear(app); + return; + } + + term_append_prompt_line(app, local); + if (canonical != (const char *)0 && ush_streq(canonical, "help") != 0) { + term_append_line(app, "BUILTINS: HELP CLEAR PWD CD EXIT"); + term_append_line(app, "EXTERNAL COMMANDS RUN THROUGH PTY OUTPUT CAPTURE"); + return; + } + if (canonical != (const char *)0 && ush_streq(canonical, "pwd") != 0) { + term_append_line(app, app->cwd); + return; + } + if (canonical != (const char *)0 && ush_streq(canonical, "cd") != 0) { + ush_init_state(&sh); + ush_copy(sh.cwd, (u64)sizeof(sh.cwd), app->cwd); + if (ush_resolve_path(&sh, (arg[0] == '\0') ? "/" : arg, path, (u64)sizeof(path)) == 0) { + term_append_line(app, "CD: INVALID PATH"); + return; + } + if (cleonos_sys_fs_stat_type(path) != 2ULL) { + term_append_line(app, "CD: DIRECTORY NOT FOUND"); + return; + } + ush_copy(app->cwd, (u64)sizeof(app->cwd), path); + return; + } + if (canonical != (const char *)0 && ush_streq(canonical, "exit") != 0) { + app->running = 0; + return; + } + term_exec_external(app, cmd, arg); +} + +static void term_handle_key(term_app *app, u64 key) { + if (app == (term_app *)0) { + return; + } + if (key == 8ULL || key == 127ULL) { + if (app->input_len > 0ULL) { + app->input_len--; + app->input[app->input_len] = '\0'; + } + return; + } + if (key == (u64)'\n' || key == (u64)'\r') { + char line[TERM_INPUT_MAX]; + + ush_copy(line, (u64)sizeof(line), app->input); + app->input[0] = '\0'; + app->input_len = 0ULL; + term_exec_line(app, line); + return; + } + if (key >= 32ULL && key <= 126ULL && app->input_len + 1ULL < (u64)sizeof(app->input)) { + app->input[app->input_len++] = (char)key; + app->input[app->input_len] = '\0'; + } +} + +static int term_hit_resize(const term_app *app, int x, int y) { + return (app != (const term_app *)0 && x >= app->w - TERM_RESIZE_GRIP && y >= app->h - TERM_RESIZE_GRIP) ? 1 : 0; +} + +static void term_handle_mouse_button(term_app *app, const cleonos_wm_event *event) { + cleonos_mouse_state mouse; + int local_x; + int local_y; + int left_changed; + int left_down; + + if (app == (term_app *)0 || event == (const cleonos_wm_event *)0) { + return; + } + local_x = term_u64_as_i32(event->arg2); + local_y = term_u64_as_i32(event->arg3); + left_changed = ((event->arg1 & 0x1ULL) != 0ULL) ? 1 : 0; + left_down = ((event->arg0 & 0x1ULL) != 0ULL) ? 1 : 0; + if (left_changed == 0) { + return; + } + if (left_down == 0) { + app->dragging = 0; + app->resizing = 0; + return; + } + + ush_zero(&mouse, (u64)sizeof(mouse)); + if (cleonos_sys_mouse_state(&mouse) != 0ULL && mouse.ready != 0ULL) { + local_x = term_u64_as_i32(mouse.x) - app->x; + local_y = term_u64_as_i32(mouse.y) - app->y; + } + + if (local_y >= 0 && local_y < TERM_TITLE_H) { + if (local_x >= app->w - TERM_CONTROL_W) { + app->running = 0; + return; + } + if (local_x >= app->w - (TERM_CONTROL_W * 2) && local_x < app->w - TERM_CONTROL_W) { + term_toggle_maximize(app); + return; + } + if (local_x >= app->w - (TERM_CONTROL_W * 3) && local_x < app->w - (TERM_CONTROL_W * 2)) { + return; + } + if (app->maximized == 0) { + app->dragging = 1; + app->drag_dx = local_x; + app->drag_dy = local_y; + } + return; + } + + if (app->maximized == 0 && term_hit_resize(app, local_x, local_y) != 0) { + app->resizing = 1; + app->resize_start_x = app->x + local_x; + app->resize_start_y = app->y + local_y; + app->resize_start_w = app->w; + app->resize_start_h = app->h; + } +} + +static void term_handle_mouse_move(term_app *app, const cleonos_wm_event *event) { + int global_x; + int global_y; + + if (app == (term_app *)0 || event == (const cleonos_wm_event *)0) { + return; + } + global_x = term_u64_as_i32(event->arg0); + global_y = term_u64_as_i32(event->arg1); + if (app->dragging != 0 && app->maximized == 0) { + (void)term_apply_geometry(app, global_x - app->drag_dx, global_y - app->drag_dy, app->w, app->h); + return; + } + if (app->resizing != 0 && app->maximized == 0) { + int next_w = app->resize_start_w + (global_x - app->resize_start_x); + int next_h = app->resize_start_h + (global_y - app->resize_start_y); + (void)term_apply_geometry(app, app->x, app->y, next_w, next_h); + } +} + +static void term_loop(term_app *app) { + int idle_spins = 0; + + while (app->running != 0) { + int dirty = 0; + int handled = 0; + u64 budget = 0ULL; + + while (budget < TERM_EVENT_BUDGET) { + cleonos_wm_event event; + + ush_zero(&event, (u64)sizeof(event)); + if (cleonos_sys_wm_poll_event(app->window_id, &event) == 0ULL) { + break; + } + handled = 1; + dirty = 1; + if (event.type == CLEONOS_WM_EVENT_FOCUS_GAINED) { + app->focused = 1; + } else if (event.type == CLEONOS_WM_EVENT_FOCUS_LOST) { + app->focused = 0; + app->dragging = 0; + app->resizing = 0; + } else if (event.type == CLEONOS_WM_EVENT_KEY) { + term_handle_key(app, event.arg0); + } else if (event.type == CLEONOS_WM_EVENT_MOUSE_BUTTON) { + term_handle_mouse_button(app, &event); + } else if (event.type == CLEONOS_WM_EVENT_MOUSE_MOVE) { + term_handle_mouse_move(app, &event); + dirty = 0; + } + if (app->running == 0) { + break; + } + budget++; + } + + if (dirty != 0 && app->running != 0) { + (void)term_render_present(app); + } + if (handled != 0 || app->dragging != 0 || app->resizing != 0) { + idle_spins = 0; + (void)cleonos_sys_yield(); + continue; + } + if (idle_spins < TERM_IDLE_SPINS) { + idle_spins++; + (void)cleonos_sys_yield(); + continue; + } + idle_spins = 0; + (void)cleonos_sys_sleep_ticks(1ULL); + } +} + +static int term_load_screen_info(term_app *app) { + cleonos_fb_info fb; + + if (app == (term_app *)0) { + return 0; + } + + ush_zero(&fb, (u64)sizeof(fb)); + if (cleonos_sys_fb_info(&fb) == 0ULL || fb.width == 0ULL || fb.height == 0ULL || fb.bpp != 32ULL || + fb.width > 4096ULL || fb.height > 4096ULL) { + return 0; + } + app->screen_w = (int)fb.width; + app->screen_h = (int)fb.height; + return 1; +} + +static void term_set_geometry(term_app *app, int wanted_w, int wanted_h) { + int max_w; + int max_h; + + if (app == (term_app *)0) { + return; + } + max_w = app->screen_w - 96; + max_h = app->screen_h - 128; + if (max_w < TERM_MIN_W) { + max_w = app->screen_w; + } + if (max_h < TERM_MIN_H) { + max_h = app->screen_h; + } + app->w = term_clampi(wanted_w, TERM_MIN_W, max_w); + app->h = term_clampi(wanted_h, TERM_MIN_H, max_h); + app->x = (app->screen_w > app->w) ? ((app->screen_w - app->w) / 2) : 0; + app->y = (app->screen_h > app->h) ? ((app->screen_h - app->h) / 2) : TERM_TOP_CLAMP_Y; + if (app->y < TERM_TOP_CLAMP_Y) { + app->y = TERM_TOP_CLAMP_Y; + } +} + +static int term_init_window(term_app *app) { + static const int fallback_sizes[][2] = { + {TERM_DEFAULT_W, TERM_DEFAULT_H}, + {640, 360}, + {480, 300}, + }; + cleonos_wm_create_req req; + u64 i; + + if (term_load_screen_info(app) == 0) { + return 0; + } + + app->old_tty = cleonos_sys_tty_active(); + if (app->old_tty != TERM_TTY_DISPLAY) { + (void)cleonos_sys_tty_switch(TERM_TTY_DISPLAY); + app->tty_switched = 1; + } + + for (i = 0ULL; i < (u64)(sizeof(fallback_sizes) / sizeof(fallback_sizes[0])); i++) { + term_release_pixels(app); + term_set_geometry(app, fallback_sizes[i][0], fallback_sizes[i][1]); + if (term_alloc_pixels(app, app->w, app->h) == 0) { + continue; + } + + req.x = (u64)(i64)app->x; + req.y = (u64)(i64)app->y; + req.width = (u64)(unsigned int)app->w; + req.height = (u64)(unsigned int)app->h; + req.flags = 0ULL; + app->window_id = cleonos_sys_wm_create(&req); + if (app->window_id != 0ULL) { + (void)cleonos_sys_wm_set_focus(app->window_id); + app->focused = 1; + return 1; + } + } + + term_release_pixels(app); + return 0; +} + +static void term_shutdown(term_app *app) { + if (app == (term_app *)0) { + return; + } + if (app->window_id != 0ULL) { + (void)cleonos_sys_wm_destroy(app->window_id); + app->window_id = 0ULL; + } + if (app->pixels != (term_u32 *)0) { + term_release_pixels(app); + } + if (app->tty_switched != 0) { + (void)cleonos_sys_tty_switch(app->old_tty); + app->tty_switched = 0; + } +} + +int cleonos_terminal_run(void) { + term_app app; + + ush_zero(&app, (u64)sizeof(app)); + app.running = 1; + app.color = TERM_COLOR_DEFAULT; + ush_copy(app.cwd, (u64)sizeof(app.cwd), "/"); + term_clear(&app); + term_append_line(&app, "CLEONOS TERMINAL READY"); + term_append_line(&app, "TYPE HELP, PWD, LS, FASTFETCH, IFCONFIG..."); + + if (term_init_window(&app) == 0) { + term_shutdown(&app); + ush_writeln("terminal: wm init failed"); + return 0; + } + (void)term_render_present(&app); + term_loop(&app); + term_shutdown(&app); + return 1; +} diff --git a/cleonos/c/apps/terminal/terminal.h b/cleonos/c/apps/terminal/terminal.h new file mode 100644 index 0000000..aa7ca18 --- /dev/null +++ b/cleonos/c/apps/terminal/terminal.h @@ -0,0 +1,8 @@ +#ifndef CLEONOS_TERMINAL_H +#define CLEONOS_TERMINAL_H + +#include "../cmd_runtime.h" + +int cleonos_terminal_run(void); + +#endif diff --git a/cleonos/c/apps/terminal/terminal_heap.c b/cleonos/c/apps/terminal/terminal_heap.c new file mode 100644 index 0000000..4f7bdc8 --- /dev/null +++ b/cleonos/c/apps/terminal/terminal_heap.c @@ -0,0 +1,2 @@ +#define CLEONOS_APP_HEAP_CAPACITY (4U * 1024U * 1024U) +#include "../app_heap.inc" diff --git a/cleonos/c/apps/terminal_main.c b/cleonos/c/apps/terminal_main.c new file mode 100644 index 0000000..dc55f14 --- /dev/null +++ b/cleonos/c/apps/terminal_main.c @@ -0,0 +1,9 @@ +#include "terminal/terminal.h" + +int cleonos_app_main(int argc, char **argv, char **envp) { + (void)argc; + (void)argv; + (void)envp; + + return (cleonos_terminal_run() != 0) ? 0 : 1; +} diff --git a/cleonos/c/apps/uwm/uwm.h b/cleonos/c/apps/uwm/uwm.h index 974f74d..f6b4b44 100644 --- a/cleonos/c/apps/uwm/uwm.h +++ b/cleonos/c/apps/uwm/uwm.h @@ -4,6 +4,8 @@ #include "../cmd_runtime.h" #define USH_UWM_APP_COUNT 3U +#define USH_UWM_TERMINAL_INDEX 1 +#define USH_UWM_TASKMGR_INDEX 2 #define USH_UWM_TASKBAR_INDEX 3 #define USH_UWM_START_INDEX 4 #define USH_UWM_WINDOW_COUNT 5U @@ -57,6 +59,11 @@ typedef struct ush_uwm_window { int minimized; int closed; int topmost; + int maximized; + int restore_x; + int restore_y; + int restore_w; + int restore_h; int dirty; ush_uwm_window_kind kind; ush_uwm_u32 accent; @@ -110,6 +117,7 @@ void ush_uwm_minimize_window(ush_uwm_session *sess, int index); void ush_uwm_close_window(ush_uwm_session *sess, int index); void ush_uwm_restore_window(ush_uwm_session *sess, int index); void ush_uwm_toggle_topmost(ush_uwm_session *sess, int index); +void ush_uwm_toggle_maximize(ush_uwm_session *sess, int index); void ush_uwm_toggle_start(ush_uwm_session *sess); void ush_uwm_close_start(ush_uwm_session *sess); void ush_uwm_refresh_taskbar(ush_uwm_session *sess); diff --git a/cleonos/c/apps/uwm/uwm_app.c b/cleonos/c/apps/uwm/uwm_app.c index c65e9ec..6f59d57 100644 --- a/cleonos/c/apps/uwm/uwm_app.c +++ b/cleonos/c/apps/uwm/uwm_app.c @@ -82,6 +82,11 @@ static void ush_uwm_init_window(ush_uwm_window *win, ush_uwm_window_kind kind, c win->minimized = 0; win->closed = closed; win->topmost = topmost; + win->maximized = 0; + win->restore_x = x; + win->restore_y = y; + win->restore_w = w; + win->restore_h = h; win->dirty = 1; win->kind = kind; win->accent = accent; @@ -128,10 +133,10 @@ int ush_uwm_prepare_session(ush_uwm_session *sess) { int start_h; int app_gap; int i; - const char *titles[USH_UWM_APP_COUNT] = {"FILE EXPLORER", "NOTEPAD", "EDGE"}; - const char *subtitles[USH_UWM_APP_COUNT] = {"LOCAL DISK AND SYSTEM FILES", "EDIT TEXT INSIDE CLEONOS", - "WEB PREVIEW AND HTTP TOOLS"}; - const ush_uwm_u32 accents[USH_UWM_APP_COUNT] = {0x000078D7U, 0x0000A300U, 0x00007ACCU}; + const char *titles[USH_UWM_APP_COUNT] = {"FILE EXPLORER", "TERMINAL", "TASK MANAGER"}; + const char *subtitles[USH_UWM_APP_COUNT] = {"LOCAL DISK AND SYSTEM FILES", "DESKTOP TERMINAL APP", + "PROCESS MONITOR AND END TASK"}; + const ush_uwm_u32 accents[USH_UWM_APP_COUNT] = {0x000078D7U, 0x0000A300U, 0x000078D7U}; if (sess == (ush_uwm_session *)0) { return 0; diff --git a/cleonos/c/apps/uwm/uwm_events.c b/cleonos/c/apps/uwm/uwm_events.c index 9296bcb..d30294b 100644 --- a/cleonos/c/apps/uwm/uwm_events.c +++ b/cleonos/c/apps/uwm/uwm_events.c @@ -1,6 +1,8 @@ #include "uwm.h" #define USH_UWM_FILE_EXPLORER_PATH "/shell/uwm/file_explorer.elf" +#define USH_UWM_TASKMGR_PATH "/shell/uwm/taskmgr.elf" +#define USH_UWM_TERMINAL_PATH "/shell/uwm/terminal.elf" static int ush_uwm_launch_file_explorer(void) { u64 pid = cleonos_sys_spawn_pathv(USH_UWM_FILE_EXPLORER_PATH, "", "LAUNCHED_BY=uwm"); @@ -8,6 +10,18 @@ static int ush_uwm_launch_file_explorer(void) { return (pid != 0ULL && pid != (u64)-1) ? 1 : 0; } +static int ush_uwm_launch_terminal(void) { + u64 pid = cleonos_sys_spawn_pathv(USH_UWM_TERMINAL_PATH, "", "LAUNCHED_BY=uwm"); + + return (pid != 0ULL && pid != (u64)-1) ? 1 : 0; +} + +static int ush_uwm_launch_taskmgr(void) { + u64 pid = cleonos_sys_spawn_pathv(USH_UWM_TASKMGR_PATH, "", "LAUNCHED_BY=uwm"); + + return (pid != 0ULL && pid != (u64)-1) ? 1 : 0; +} + static void ush_uwm_launch_or_restore_app(ush_uwm_session *sess, int index) { if (sess == (ush_uwm_session *)0 || ush_uwm_app_index_valid(index) == 0) { return; @@ -18,6 +32,16 @@ static void ush_uwm_launch_or_restore_app(ush_uwm_session *sess, int index) { return; } + if (index == USH_UWM_TERMINAL_INDEX) { + (void)ush_uwm_launch_terminal(); + return; + } + + if (index == USH_UWM_TASKMGR_INDEX) { + (void)ush_uwm_launch_taskmgr(); + return; + } + ush_uwm_restore_window(sess, index); } @@ -46,6 +70,72 @@ static int ush_uwm_hit_resize(const ush_uwm_window *win, int x, int y) { : 0; } +static int ush_uwm_point_in_window(const ush_uwm_window *win, int x, int y) { + if (win == (const ush_uwm_window *)0 || win->alive == 0 || win->closed != 0 || win->id == 0ULL) { + return 0; + } + + return (x >= win->x && y >= win->y && x < win->x + win->w && y < win->y + win->h) ? 1 : 0; +} + +static int ush_uwm_hit_session_window_at(const ush_uwm_session *sess, int global_x, int global_y) { + int i; + + if (sess == (const ush_uwm_session *)0) { + return -1; + } + + if (ush_uwm_point_in_window(&sess->windows[USH_UWM_START_INDEX], global_x, global_y) != 0) { + return USH_UWM_START_INDEX; + } + + for (i = (int)USH_UWM_APP_COUNT - 1; i >= 0; i--) { + if (sess->windows[i].topmost != 0 && ush_uwm_point_in_window(&sess->windows[i], global_x, global_y) != 0) { + return i; + } + } + + if (ush_uwm_app_index_valid(sess->active_window) != 0 && + ush_uwm_point_in_window(&sess->windows[sess->active_window], global_x, global_y) != 0) { + return sess->active_window; + } + + for (i = (int)USH_UWM_APP_COUNT - 1; i >= 0; i--) { + if (ush_uwm_point_in_window(&sess->windows[i], global_x, global_y) != 0) { + return i; + } + } + + if (ush_uwm_point_in_window(&sess->windows[USH_UWM_TASKBAR_INDEX], global_x, global_y) != 0) { + return USH_UWM_TASKBAR_INDEX; + } + + return -1; +} + +static void ush_uwm_repair_mouse_target(ush_uwm_session *sess, int *window_index, int *local_x, int *local_y) { + cleonos_mouse_state mouse; + int hit; + + if (sess == (ush_uwm_session *)0 || window_index == (int *)0 || local_x == (int *)0 || local_y == (int *)0) { + return; + } + + ush_zero(&mouse, (u64)sizeof(mouse)); + if (cleonos_sys_mouse_state(&mouse) == 0ULL || mouse.ready == 0ULL) { + return; + } + + hit = ush_uwm_hit_session_window_at(sess, ush_uwm_u64_as_i32(mouse.x), ush_uwm_u64_as_i32(mouse.y)); + if (ush_uwm_window_index_valid(hit) == 0) { + return; + } + + *window_index = hit; + *local_x = ush_uwm_u64_as_i32(mouse.x) - sess->windows[hit].x; + *local_y = ush_uwm_u64_as_i32(mouse.y) - sess->windows[hit].y; +} + static int ush_uwm_taskbar_app_x(const ush_uwm_window *taskbar) { int search_w = USH_UWM_TASKBAR_SEARCH_W; @@ -87,13 +177,14 @@ static void ush_uwm_handle_key_event(ush_uwm_session *sess, u64 key, int *runnin return; } - if (key == (u64)'q' || key == (u64)'Q') { - *running = 0; + if (key == (u64)' ') { + ush_uwm_focus_next(sess); return; } - if (key == (u64)' ') { - ush_uwm_focus_next(sess); + idx = sess->active_window; + if (key == (u64)'q' || key == (u64)'Q') { + *running = 0; return; } @@ -102,7 +193,6 @@ static void ush_uwm_handle_key_event(ush_uwm_session *sess, u64 key, int *runnin return; } - idx = sess->active_window; if (ush_uwm_app_index_valid(idx) == 0 || sess->windows[idx].alive == 0) { return; } @@ -122,6 +212,10 @@ static void ush_uwm_handle_key_event(ush_uwm_session *sess, u64 key, int *runnin return; } + if (sess->windows[idx].maximized != 0) { + return; + } + if (key == (u64)'+' || key == (u64)'=') { (void)ush_uwm_window_resize(sess, idx, sess->windows[idx].w + 32, sess->windows[idx].h + 24); return; @@ -228,6 +322,14 @@ static void ush_uwm_handle_mouse_button(ush_uwm_session *sess, int window_index, left_changed = ((changed & 0x1ULL) != 0ULL) ? 1 : 0; left_down = ((buttons & 0x1ULL) != 0ULL) ? 1 : 0; + if (left_changed != 0 && left_down != 0) { + ush_uwm_repair_mouse_target(sess, &window_index, &local_x, &local_y); + if (ush_uwm_window_index_valid(window_index) == 0) { + return; + } + win = &sess->windows[window_index]; + } + if (left_changed == 0) { return; } @@ -274,11 +376,15 @@ static void ush_uwm_handle_mouse_button(ush_uwm_session *sess, int window_index, } if (ush_uwm_hit_topmost(win, local_x, local_y) != 0) { - ush_uwm_toggle_topmost(sess, window_index); + if (window_index == USH_UWM_TERMINAL_INDEX) { + ush_uwm_toggle_maximize(sess, window_index); + } else { + ush_uwm_toggle_topmost(sess, window_index); + } return; } - if (ush_uwm_hit_resize(win, local_x, local_y) != 0) { + if (win->maximized == 0 && ush_uwm_hit_resize(win, local_x, local_y) != 0) { sess->resizing = 1; sess->resize_window = window_index; sess->mouse_packet_seen = 0ULL; @@ -291,7 +397,7 @@ static void ush_uwm_handle_mouse_button(ush_uwm_session *sess, int window_index, return; } - if (local_y >= 0 && local_y < USH_UWM_TITLE_H) { + if (win->maximized == 0 && local_y >= 0 && local_y < USH_UWM_TITLE_H) { sess->dragging = 1; sess->drag_window = window_index; sess->mouse_packet_seen = 0ULL; @@ -312,13 +418,15 @@ static void ush_uwm_handle_mouse_move(ush_uwm_session *sess, int window_index, c global_x = ush_uwm_u64_as_i32(event->arg0); global_y = ush_uwm_u64_as_i32(event->arg1); - if (sess->resizing != 0 && sess->resize_window == window_index && ush_uwm_app_index_valid(window_index) != 0) { + if (sess->resizing != 0 && sess->resize_window == window_index && ush_uwm_app_index_valid(window_index) != 0 && + sess->windows[window_index].maximized == 0) { sess->resize_pending_w = sess->resize_start_w + (global_x - sess->resize_start_x); sess->resize_pending_h = sess->resize_start_h + (global_y - sess->resize_start_y); return; } - if (sess->dragging != 0 && sess->drag_window == window_index && ush_uwm_app_index_valid(window_index) != 0) { + if (sess->dragging != 0 && sess->drag_window == window_index && ush_uwm_app_index_valid(window_index) != 0 && + sess->windows[window_index].maximized == 0) { (void)ush_uwm_window_move_clamped(sess, window_index, global_x - sess->drag_offset_x, global_y - sess->drag_offset_y); } @@ -357,13 +465,15 @@ static int ush_uwm_drive_direct_pointer(ush_uwm_session *sess) { global_x = ush_uwm_u64_as_i32(mouse.x); global_y = ush_uwm_u64_as_i32(mouse.y); - if (sess->resizing != 0 && ush_uwm_app_index_valid(sess->resize_window) != 0) { + if (sess->resizing != 0 && ush_uwm_app_index_valid(sess->resize_window) != 0 && + sess->windows[sess->resize_window].maximized == 0) { sess->resize_pending_w = sess->resize_start_w + (global_x - sess->resize_start_x); sess->resize_pending_h = sess->resize_start_h + (global_y - sess->resize_start_y); return 1; } - if (sess->dragging != 0 && ush_uwm_app_index_valid(sess->drag_window) != 0) { + if (sess->dragging != 0 && ush_uwm_app_index_valid(sess->drag_window) != 0 && + sess->windows[sess->drag_window].maximized == 0) { (void)ush_uwm_window_move_clamped(sess, sess->drag_window, global_x - sess->drag_offset_x, global_y - sess->drag_offset_y); return 1; diff --git a/cleonos/c/apps/uwm/uwm_heap.c b/cleonos/c/apps/uwm/uwm_heap.c new file mode 100644 index 0000000..ea6ed76 --- /dev/null +++ b/cleonos/c/apps/uwm/uwm_heap.c @@ -0,0 +1,2 @@ +#define CLEONOS_APP_HEAP_CAPACITY (3U * 1024U * 1024U) +#include "../app_heap.inc" diff --git a/cleonos/c/apps/uwm/uwm_window.c b/cleonos/c/apps/uwm/uwm_window.c index 687b8ea..fd61758 100644 --- a/cleonos/c/apps/uwm/uwm_window.c +++ b/cleonos/c/apps/uwm/uwm_window.c @@ -13,6 +13,10 @@ #define UWM_COLOR_MUTED 0x00666666U #define UWM_COLOR_BORDER 0x00D0D0D0U +static int ush_uwm_window_pointer_valid(const ush_uwm_window *win) { + return ((u64)(usize)win >= 4096ULL) ? 1 : 0; +} + static int ush_uwm_work_bottom(const ush_uwm_session *sess) { int bottom; @@ -67,8 +71,8 @@ static void ush_uwm_fill_rect(ush_uwm_window *win, int x, int y, int w, int h, u int bottom; int row; - if (win == (ush_uwm_window *)0 || win->pixels == (ush_uwm_u32 *)0 || win->w <= 0 || win->h <= 0 || w <= 0 || - h <= 0) { + if (ush_uwm_window_pointer_valid(win) == 0 || win->pixels == (ush_uwm_u32 *)0 || win->w <= 0 || win->h <= 0 || + w <= 0 || h <= 0) { return; } @@ -208,6 +212,46 @@ static u64 ush_uwm_glyph_mask(char ch) { return UWM_GLYPH7(14U, 17U, 17U, 15U, 1U, 1U, 14U); case '-': return UWM_GLYPH7(0U, 0U, 0U, 31U, 0U, 0U, 0U); + case '>': + return UWM_GLYPH7(16U, 8U, 4U, 2U, 4U, 8U, 16U); + case '<': + return UWM_GLYPH7(1U, 2U, 4U, 8U, 4U, 2U, 1U); + case '$': + return UWM_GLYPH7(4U, 15U, 20U, 14U, 5U, 30U, 4U); + case '#': + return UWM_GLYPH7(10U, 31U, 10U, 10U, 31U, 10U, 10U); + case '?': + return UWM_GLYPH7(14U, 17U, 1U, 2U, 4U, 0U, 4U); + case '!': + return UWM_GLYPH7(4U, 4U, 4U, 4U, 4U, 0U, 4U); + case ',': + return UWM_GLYPH7(0U, 0U, 0U, 0U, 0U, 4U, 8U); + case ';': + return UWM_GLYPH7(0U, 4U, 4U, 0U, 0U, 4U, 8U); + case '*': + return UWM_GLYPH7(0U, 21U, 14U, 31U, 14U, 21U, 0U); + case '(': + return UWM_GLYPH7(2U, 4U, 8U, 8U, 8U, 4U, 2U); + case ')': + return UWM_GLYPH7(8U, 4U, 2U, 2U, 2U, 4U, 8U); + case '[': + return UWM_GLYPH7(14U, 8U, 8U, 8U, 8U, 8U, 14U); + case ']': + return UWM_GLYPH7(14U, 2U, 2U, 2U, 2U, 2U, 14U); + case '@': + return UWM_GLYPH7(14U, 17U, 23U, 21U, 23U, 16U, 15U); + case '%': + return UWM_GLYPH7(24U, 25U, 2U, 4U, 8U, 19U, 3U); + case '&': + return UWM_GLYPH7(12U, 18U, 20U, 8U, 21U, 18U, 13U); + case '~': + return UWM_GLYPH7(0U, 0U, 8U, 21U, 2U, 0U, 0U); + case '\\': + return UWM_GLYPH7(16U, 16U, 8U, 4U, 2U, 1U, 1U); + case '"': + return UWM_GLYPH7(10U, 10U, 10U, 0U, 0U, 0U, 0U); + case '\'': + return UWM_GLYPH7(4U, 4U, 8U, 0U, 0U, 0U, 0U); case '_': return UWM_GLYPH7(0U, 0U, 0U, 0U, 0U, 0U, 31U); case '.': @@ -296,6 +340,14 @@ static void ush_uwm_draw_control_button(ush_uwm_window *win, int x, int active, ush_uwm_fill_rect(win, cx - 4, cy - 6, 8, 2, fg); ush_uwm_fill_rect(win, cx - 1, cy - 4, 2, 9, fg); ush_uwm_fill_rect(win, cx - 6, cy + 4, 12, 2, fg); + } else if (kind == 3) { + ush_uwm_stroke_rect(win, cx - 6, cy - 6, 12, 12, fg); + ush_uwm_fill_rect(win, cx - 6, cy - 6, 12, 2, fg); + } else if (kind == 4) { + ush_uwm_stroke_rect(win, cx - 4, cy - 7, 10, 10, fg); + ush_uwm_fill_rect(win, cx - 4, cy - 7, 10, 2, fg); + ush_uwm_stroke_rect(win, cx - 7, cy - 3, 10, 10, fg); + ush_uwm_fill_rect(win, cx - 7, cy - 3, 10, 2, fg); } else { int i; for (i = 0; i < 11; i++) { @@ -305,7 +357,7 @@ static void ush_uwm_draw_control_button(ush_uwm_window *win, int x, int active, } } -static void ush_uwm_draw_window_controls(ush_uwm_window *win, int active) { +static void ush_uwm_draw_window_controls(ush_uwm_window *win, int active, int index) { int close_x; int pin_x; int min_x; @@ -317,7 +369,11 @@ static void ush_uwm_draw_window_controls(ush_uwm_window *win, int active) { close_x = win->w - USH_UWM_CONTROL_W; min_x = close_x - USH_UWM_CONTROL_W; pin_x = min_x - USH_UWM_CONTROL_W; - ush_uwm_draw_control_button(win, pin_x, active, 1); + if (index == USH_UWM_TERMINAL_INDEX) { + ush_uwm_draw_control_button(win, pin_x, active, (win->maximized != 0) ? 4 : 3); + } else { + ush_uwm_draw_control_button(win, pin_x, active, 1); + } ush_uwm_draw_control_button(win, min_x, active, 0); ush_uwm_draw_control_button(win, close_x, active, 2); } @@ -358,50 +414,40 @@ static void ush_uwm_render_files(ush_uwm_window *win) { ush_uwm_draw_text(win, 118, USH_UWM_TITLE_H + 166, "TEMP", 1, UWM_COLOR_TEXT); } -static void ush_uwm_render_editor(ush_uwm_window *win) { - int y; - - ush_uwm_fill_rect(win, 0, USH_UWM_TITLE_H, win->w, 24, 0x00F9F9F9U); - ush_uwm_fill_rect(win, 0, USH_UWM_TITLE_H + 23, win->w, 1, UWM_COLOR_BORDER); - ush_uwm_draw_text(win, 12, USH_UWM_TITLE_H + 8, "FILE EDIT VIEW HELP", 1, UWM_COLOR_TEXT); - ush_uwm_fill_rect(win, 0, USH_UWM_TITLE_H + 24, 46, win->h - USH_UWM_TITLE_H - 24, 0x00F0F0F0U); - ush_uwm_fill_rect(win, 46, USH_UWM_TITLE_H + 24, 1, win->h - USH_UWM_TITLE_H - 24, 0x00DDDDDDU); - - for (y = 0; y < 7; y++) { - char label[4]; - label[0] = (char)('1' + y); - label[1] = 0; - ush_uwm_draw_text(win, 18, USH_UWM_TITLE_H + 42 + (y * 18), label, 1, UWM_COLOR_MUTED); +static void ush_uwm_render_terminal_launcher(ush_uwm_window *win) { + if (win == (ush_uwm_window *)0) { + return; } - ush_uwm_draw_text(win, 62, USH_UWM_TITLE_H + 42, "CLEONOS UWM REWRITE", 1, UWM_COLOR_TEXT); - ush_uwm_draw_text(win, 62, USH_UWM_TITLE_H + 60, "PIXEL RENDERER ONLINE", 1, 0x00008000U); - ush_uwm_draw_text(win, 62, USH_UWM_TITLE_H + 78, "WINDOWS 10 STYLE SHELL", 1, UWM_COLOR_TEXT); - ush_uwm_draw_text(win, 62, USH_UWM_TITLE_H + 96, "DRAG RESIZE MINIMIZE CLOSE", 1, UWM_COLOR_TEXT); - ush_uwm_fill_rect(win, 62, USH_UWM_TITLE_H + 120, 92, 2, UWM_COLOR_WIN_BLUE); + ush_uwm_fill_rect(win, 0, USH_UWM_TITLE_H, win->w, win->h - USH_UWM_TITLE_H, 0x000C0C0CU); + ush_uwm_fill_rect(win, 18, USH_UWM_TITLE_H + 22, win->w - 36, win->h - USH_UWM_TITLE_H - 44, 0x00111111U); + ush_uwm_stroke_rect(win, 18, USH_UWM_TITLE_H + 22, win->w - 36, win->h - USH_UWM_TITLE_H - 44, 0x00333333U); + ush_uwm_draw_text(win, 34, USH_UWM_TITLE_H + 46, "TERMINAL IS A SEPARATE ELF NOW", 1, 0x0086D98AU); + ush_uwm_draw_text(win, 34, USH_UWM_TITLE_H + 72, "/SHELL/UWM/TERMINAL.ELF", 1, 0x00DCDCDCU); + ush_uwm_draw_text(win, 34, USH_UWM_TITLE_H + 98, "OPEN IT FROM START OR TASKBAR", 1, 0x00808080U); } -static void ush_uwm_render_browser(ush_uwm_window *win) { +static void ush_uwm_render_taskmgr_launcher(ush_uwm_window *win) { int card_w; ush_uwm_fill_rect(win, 0, USH_UWM_TITLE_H, win->w, 42, 0x00F7F7F7U); ush_uwm_fill_rect(win, 0, USH_UWM_TITLE_H + 41, win->w, 1, UWM_COLOR_BORDER); ush_uwm_fill_rect(win, 14, USH_UWM_TITLE_H + 10, win->w - 28, 22, UWM_COLOR_WHITE); ush_uwm_stroke_rect(win, 14, USH_UWM_TITLE_H + 10, win->w - 28, 22, UWM_COLOR_BORDER); - ush_uwm_draw_text_limit(win, 24, USH_UWM_TITLE_H + 17, "HTTP://EXAMPLE.COM", 1, UWM_COLOR_MUTED, win->w - 24); + ush_uwm_draw_text_limit(win, 24, USH_UWM_TITLE_H + 17, "/SHELL/UWM/TASKMGR.ELF", 1, UWM_COLOR_MUTED, win->w - 24); - ush_uwm_draw_text(win, 22, USH_UWM_TITLE_H + 66, "WELCOME TO CLEONOS", 2, UWM_COLOR_TEXT); - ush_uwm_draw_text(win, 24, USH_UWM_TITLE_H + 94, "NETWORK AND HTML DEMOS LIVE HERE", 1, UWM_COLOR_MUTED); + ush_uwm_draw_text(win, 22, USH_UWM_TITLE_H + 66, "TASK MANAGER", 2, UWM_COLOR_TEXT); + ush_uwm_draw_text(win, 24, USH_UWM_TITLE_H + 94, "PROCESS LIST AND END TASK LIVE HERE", 1, UWM_COLOR_MUTED); card_w = (win->w - 58) / 2; if (card_w < 80) { card_w = 80; } ush_uwm_fill_rect(win, 22, USH_UWM_TITLE_H + 122, card_w, 54, 0x00EAF4FFU); ush_uwm_stroke_rect(win, 22, USH_UWM_TITLE_H + 122, card_w, 54, 0x00B7D8F4U); - ush_uwm_draw_text(win, 34, USH_UWM_TITLE_H + 142, "HTTPGET", 1, UWM_COLOR_TEXT); + ush_uwm_draw_text(win, 34, USH_UWM_TITLE_H + 142, "PROCESSES", 1, UWM_COLOR_TEXT); ush_uwm_fill_rect(win, 36 + card_w, USH_UWM_TITLE_H + 122, card_w, 54, 0x00EAF7EAU); ush_uwm_stroke_rect(win, 36 + card_w, USH_UWM_TITLE_H + 122, card_w, 54, 0x00B7E0B7U); - ush_uwm_draw_text(win, 48 + card_w, USH_UWM_TITLE_H + 142, "NSLOOKUP", 1, UWM_COLOR_TEXT); + ush_uwm_draw_text(win, 48 + card_w, USH_UWM_TITLE_H + 142, "END TASK", 1, UWM_COLOR_TEXT); } static void ush_uwm_render_app_window(ush_uwm_session *sess, int index) { @@ -423,22 +469,24 @@ static void ush_uwm_render_app_window(ush_uwm_session *sess, int index) { ush_uwm_fill_rect(win, 0, 0, win->w, USH_UWM_TITLE_H, title_bg); ush_uwm_fill_rect(win, 0, USH_UWM_TITLE_H, win->w, 1, UWM_COLOR_BORDER); ush_uwm_draw_text_limit(win, 12, 12, win->title, 1, title_fg, win->w - (USH_UWM_CONTROL_W * 3) - 8); - if (win->topmost != 0) { + if (win->topmost != 0 && index != USH_UWM_TERMINAL_INDEX) { ush_uwm_draw_text(win, win->w - (USH_UWM_CONTROL_W * 3) - 18, 12, "^", 1, title_fg); } - ush_uwm_draw_window_controls(win, active); + ush_uwm_draw_window_controls(win, active, index); if (index == 0) { ush_uwm_render_files(win); } else if (index == 1) { - ush_uwm_render_editor(win); + ush_uwm_render_terminal_launcher(win); } else { - ush_uwm_render_browser(win); + ush_uwm_render_taskmgr_launcher(win); } - ush_uwm_fill_rect(win, win->w - 14, win->h - 3, 11, 1, UWM_COLOR_MUTED); - ush_uwm_fill_rect(win, win->w - 10, win->h - 7, 7, 1, UWM_COLOR_MUTED); - ush_uwm_fill_rect(win, win->w - 6, win->h - 11, 3, 1, UWM_COLOR_MUTED); + if (win->maximized == 0) { + ush_uwm_fill_rect(win, win->w - 14, win->h - 3, 11, 1, UWM_COLOR_MUTED); + ush_uwm_fill_rect(win, win->w - 10, win->h - 7, 7, 1, UWM_COLOR_MUTED); + ush_uwm_fill_rect(win, win->w - 6, win->h - 11, 3, 1, UWM_COLOR_MUTED); + } } static void ush_uwm_render_taskbar(ush_uwm_session *sess) { @@ -507,7 +555,7 @@ static void ush_uwm_render_taskbar(ush_uwm_session *sess) { static void ush_uwm_render_start(ush_uwm_session *sess) { ush_uwm_window *start; int i; - const char *labels[USH_UWM_APP_COUNT] = {"FILE EXPLORER", "NOTEPAD", "EDGE"}; + const char *labels[USH_UWM_APP_COUNT] = {"FILE EXPLORER", "TERMINAL", "TASK MANAGER"}; if (sess == (ush_uwm_session *)0) { return; @@ -675,6 +723,7 @@ void ush_uwm_destroy_window(ush_uwm_window *win) { win->pixel_count = 0ULL; win->minimized = 0; win->closed = 1; + win->maximized = 0; win->dirty = 1; } @@ -791,6 +840,135 @@ int ush_uwm_window_resize(ush_uwm_session *sess, int index, int target_w, int ta return 1; } +static int ush_uwm_window_recreate_geometry(ush_uwm_session *sess, int index, int target_x, int target_y, int target_w, + int target_h) { + ush_uwm_window *win; + int max_x; + int max_y; + int new_x; + int new_y; + int new_w; + int new_h; + + if (sess == (ush_uwm_session *)0 || ush_uwm_app_index_valid(index) == 0) { + return 0; + } + + win = &sess->windows[index]; + if (win->closed != 0 || win->minimized != 0) { + return 0; + } + + new_w = ush_uwm_clampi(target_w, USH_UWM_MIN_WINDOW_W, sess->screen_w); + new_h = ush_uwm_clampi(target_h, USH_UWM_MIN_WINDOW_H, ush_uwm_work_bottom(sess) - USH_UWM_TOP_CLAMP_Y); + max_x = sess->screen_w - new_w; + max_y = ush_uwm_work_bottom(sess) - new_h; + if (max_x < 0) { + max_x = 0; + } + if (max_y < USH_UWM_TOP_CLAMP_Y) { + max_y = USH_UWM_TOP_CLAMP_Y; + } + + new_x = ush_uwm_clampi(target_x, 0, max_x); + new_y = ush_uwm_clampi(target_y, USH_UWM_TOP_CLAMP_Y, max_y); + + if (ush_uwm_replace_pixels(win, new_w, new_h) == 0) { + return 0; + } + + if (win->id != 0ULL || win->alive != 0) { + ush_uwm_destroy_kernel_window(win); + } + + win->x = new_x; + win->y = new_y; + win->closed = 0; + win->minimized = 0; + ush_uwm_render_window(sess, index); + if (ush_uwm_create_window(win) == 0) { + win->closed = 1; + return 0; + } + + if (ush_uwm_present_window(win) == 0) { + ush_uwm_destroy_kernel_window(win); + win->closed = 1; + return 0; + } + + sess->active_window = index; + (void)cleonos_sys_wm_set_focus(win->id); + ush_uwm_refresh_taskbar(sess); + return 1; +} + +void ush_uwm_toggle_maximize(ush_uwm_session *sess, int index) { + ush_uwm_window *win; + int target_w; + int target_h; + int restore_x; + int restore_y; + int restore_w; + int restore_h; + + if (sess == (ush_uwm_session *)0 || index != USH_UWM_TERMINAL_INDEX || ush_uwm_app_index_valid(index) == 0) { + return; + } + + win = &sess->windows[index]; + if (win->alive == 0 || win->id == 0ULL || win->closed != 0 || win->minimized != 0) { + return; + } + + target_w = sess->screen_w; + target_h = ush_uwm_work_bottom(sess) - USH_UWM_TOP_CLAMP_Y; + if (target_w < USH_UWM_MIN_WINDOW_W) { + target_w = USH_UWM_MIN_WINDOW_W; + } + if (target_h < USH_UWM_MIN_WINDOW_H) { + target_h = USH_UWM_MIN_WINDOW_H; + } + + if (win->maximized == 0) { + win->restore_x = win->x; + win->restore_y = win->y; + win->restore_w = win->w; + win->restore_h = win->h; + win->maximized = 1; + if (ush_uwm_window_recreate_geometry(sess, index, 0, USH_UWM_TOP_CLAMP_Y, target_w, target_h) == 0) { + win->maximized = 0; + ush_uwm_refresh_window(sess, index); + return; + } + return; + } + + restore_x = win->restore_x; + restore_y = win->restore_y; + restore_w = win->restore_w; + restore_h = win->restore_h; + if (restore_w < USH_UWM_MIN_WINDOW_W) { + restore_w = USH_UWM_MIN_WINDOW_W; + } + if (restore_h < USH_UWM_MIN_WINDOW_H) { + restore_h = USH_UWM_MIN_WINDOW_H; + } + if (restore_w > sess->screen_w) { + restore_w = sess->screen_w; + } + if (restore_h > target_h) { + restore_h = target_h; + } + + win->maximized = 0; + if (ush_uwm_window_recreate_geometry(sess, index, restore_x, restore_y, restore_w, restore_h) == 0) { + win->maximized = 1; + ush_uwm_refresh_window(sess, index); + return; + } +} + void ush_uwm_set_active(ush_uwm_session *sess, int index) { ush_uwm_window *win; int old_active; @@ -857,13 +1035,24 @@ void ush_uwm_minimize_window(ush_uwm_session *sess, int index) { } void ush_uwm_close_window(ush_uwm_session *sess, int index) { + ush_uwm_window *win; + if (sess == (ush_uwm_session *)0 || ush_uwm_app_index_valid(index) == 0) { return; } - ush_uwm_destroy_kernel_window(&sess->windows[index]); - sess->windows[index].closed = 1; - sess->windows[index].minimized = 0; + win = &sess->windows[index]; + ush_uwm_destroy_kernel_window(win); + if (win->maximized != 0) { + win->x = win->restore_x; + win->y = win->restore_y; + win->maximized = 0; + if (win->restore_w >= USH_UWM_MIN_WINDOW_W && win->restore_h >= USH_UWM_MIN_WINDOW_H) { + (void)ush_uwm_replace_pixels(win, win->restore_w, win->restore_h); + } + } + win->closed = 1; + win->minimized = 0; if (sess->active_window == index) { ush_uwm_focus_next(sess); } diff --git a/cleonos/c/include/cleonos_syscall.h b/cleonos/c/include/cleonos_syscall.h index 4581e72..b0bcfe5 100644 --- a/cleonos/c/include/cleonos_syscall.h +++ b/cleonos/c/include/cleonos_syscall.h @@ -260,9 +260,10 @@ typedef struct cleonos_net_tcp_recv_req { #define CLEONOS_SYSCALL_WM_PRESENT 110ULL #define CLEONOS_SYSCALL_WM_POLL_EVENT 111ULL #define CLEONOS_SYSCALL_WM_MOVE 112ULL -#define CLEONOS_SYSCALL_WM_SET_FOCUS 113ULL -#define CLEONOS_SYSCALL_WM_SET_FLAGS 114ULL -#define CLEONOS_SYSCALL_WM_RESIZE 115ULL +#define CLEONOS_SYSCALL_WM_SET_FOCUS 113ULL +#define CLEONOS_SYSCALL_WM_SET_FLAGS 114ULL +#define CLEONOS_SYSCALL_WM_RESIZE 115ULL +#define CLEONOS_SYSCALL_PTY_OPEN 116ULL u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2); u64 cleonos_sys_log_write(const char *message, u64 length); @@ -378,8 +379,9 @@ u64 cleonos_sys_wm_destroy(u64 window_id); u64 cleonos_sys_wm_present(const cleonos_wm_present_req *req); u64 cleonos_sys_wm_poll_event(u64 window_id, cleonos_wm_event *out_event); u64 cleonos_sys_wm_move(const cleonos_wm_move_req *req); -u64 cleonos_sys_wm_set_focus(u64 window_id); -u64 cleonos_sys_wm_set_flags(u64 window_id, u64 flags); -u64 cleonos_sys_wm_resize(const cleonos_wm_resize_req *req); - -#endif +u64 cleonos_sys_wm_set_focus(u64 window_id); +u64 cleonos_sys_wm_set_flags(u64 window_id, u64 flags); +u64 cleonos_sys_wm_resize(const cleonos_wm_resize_req *req); +u64 cleonos_sys_pty_open(void); + +#endif diff --git a/cleonos/c/include/stdlib.h b/cleonos/c/include/stdlib.h index 5611671..db0f5dd 100644 --- a/cleonos/c/include/stdlib.h +++ b/cleonos/c/include/stdlib.h @@ -26,6 +26,7 @@ long strtol(const char *text, char **out_end, int base); unsigned long strtoul(const char *text, char **out_end, int base); long long strtoll(const char *text, char **out_end, int base); unsigned long long strtoull(const char *text, char **out_end, int base); +double strtod(const char *text, char **out_end); void *malloc(size_t size); void free(void *ptr); diff --git a/cleonos/c/src/libc_stdlib.c b/cleonos/c/src/libc_stdlib.c index 50100c5..294400e 100644 --- a/cleonos/c/src/libc_stdlib.c +++ b/cleonos/c/src/libc_stdlib.c @@ -72,6 +72,81 @@ long long atoll(const char *text) { return strtoll(text, (char **)0, 10); } +double strtod(const char *text, char **out_end) { + const char *p = clib_skip_space(text); + double value = 0.0; + double scale = 1.0; + int negative = 0; + int any = 0; + int exp_negative = 0; + int exp_value = 0; + + if (out_end != (char **)0) { + *out_end = (char *)text; + } + + if (p == (const char *)0) { + return 0.0; + } + + if (*p == '+' || *p == '-') { + negative = (*p == '-') ? 1 : 0; + p++; + } + + while (isdigit((unsigned char)*p) != 0) { + value = (value * 10.0) + (double)(*p - '0'); + any = 1; + p++; + } + + if (*p == '.') { + p++; + while (isdigit((unsigned char)*p) != 0) { + scale *= 0.1; + value += (double)(*p - '0') * scale; + any = 1; + p++; + } + } + + if (any != 0 && (*p == 'e' || *p == 'E')) { + const char *exp_start = p; + + p++; + if (*p == '+' || *p == '-') { + exp_negative = (*p == '-') ? 1 : 0; + p++; + } + + if (isdigit((unsigned char)*p) == 0) { + p = exp_start; + } else { + while (isdigit((unsigned char)*p) != 0) { + if (exp_value < 308) { + exp_value = (exp_value * 10) + (*p - '0'); + } + p++; + } + + while (exp_value > 0) { + value = (exp_negative != 0) ? (value * 0.1) : (value * 10.0); + exp_value--; + } + } + } + + if (any == 0) { + return 0.0; + } + + if (out_end != (char **)0) { + *out_end = (char *)p; + } + + return (negative != 0) ? -value : value; +} + unsigned long strtoul(const char *text, char **out_end, int base) { const char *p = clib_skip_space(text); int negative = 0; @@ -241,7 +316,7 @@ unsigned long long strtoull(const char *text, char **out_end, int base) { } #ifndef CLIB_HEAP_CAPACITY -#define CLIB_HEAP_CAPACITY (2U * 1024U * 1024U) +#define CLIB_HEAP_CAPACITY (256U * 1024U) #endif #define CLIB_HEAP_ALIGN 8U diff --git a/cleonos/c/src/syscall.c b/cleonos/c/src/syscall.c index e326161..4e42c0e 100644 --- a/cleonos/c/src/syscall.c +++ b/cleonos/c/src/syscall.c @@ -492,6 +492,10 @@ u64 cleonos_sys_wm_set_flags(u64 window_id, u64 flags) { return cleonos_syscall(CLEONOS_SYSCALL_WM_SET_FLAGS, window_id, flags, 0ULL); } -u64 cleonos_sys_wm_resize(const cleonos_wm_resize_req *req) { - return cleonos_syscall(CLEONOS_SYSCALL_WM_RESIZE, (u64)req, 0ULL, 0ULL); -} +u64 cleonos_sys_wm_resize(const cleonos_wm_resize_req *req) { + return cleonos_syscall(CLEONOS_SYSCALL_WM_RESIZE, (u64)req, 0ULL, 0ULL); +} + +u64 cleonos_sys_pty_open(void) { + return cleonos_syscall(CLEONOS_SYSCALL_PTY_OPEN, 0ULL, 0ULL, 0ULL); +} diff --git a/clks b/clks index 1ad59e9..83377b4 160000 --- a/clks +++ b/clks @@ -1 +1 @@ -Subproject commit 1ad59e99eae8fffa083b081f87cf9b7754c6b8be +Subproject commit 83377b472a50d4fc54c9ba0a963a653a8ab988f4 diff --git a/docs/syscall.md b/docs/syscall.md index d2795be..d8f9f90 100644 --- a/docs/syscall.md +++ b/docs/syscall.md @@ -893,6 +893,14 @@ UserSafeController(USC)危险 syscall 确认: - 返回:成功 `1`,失败 `0` - 说明: - 调整窗口尺寸并保持 `window_id` 不变;调整后用户态应重新 `WM_PRESENT` 一次提交新尺寸内容。 + +### 116 `CLEONOS_SYSCALL_PTY_OPEN` + +- 参数:无 +- 返回:成功返回一个可读写 FD,失败返回 `(u64)-1` +- 说明: +- 创建桌面伪 tty 输出端。用户态可把该 FD 传给 `EXEC_PATHV_IO` 的 stdout/stderr,然后通过 `FD_READ` 从同一 FD 读取子进程输出。 +- 当前 PTY 是“命令输出捕获型”最小实现,不提供完整主从终端会话语义;阻塞式交互 shell 仍应继续使用普通 TTY。 ## 5. 用户态封装函数 @@ -932,6 +940,7 @@ UserSafeController(USC)危险 syscall 确认: - `cleonos_sys_mouse_state()` - `cleonos_sys_wm_create()` / `cleonos_sys_wm_destroy()` / `cleonos_sys_wm_present()` - `cleonos_sys_wm_poll_event()` / `cleonos_sys_wm_move()` / `cleonos_sys_wm_set_focus()` +- `cleonos_sys_pty_open()` ## 6. 开发注意事项 @@ -942,7 +951,7 @@ UserSafeController(USC)危险 syscall 确认: ## 7. Wine 兼容说明 -- `wine/cleonos_wine_lib/runner.py` 当前已覆盖到 `0..115`(含 `DL_*`、`FB_*`、`KERNEL_VERSION`、`DISK_*`、`NET_*`、`MOUSE_STATE`、`WM_*`)。 +- `wine/cleonos_wine_lib/runner.py` 当前已覆盖到 `0..116`(含 `DL_*`、`FB_*`、`KERNEL_VERSION`、`DISK_*`、`NET_*`、`MOUSE_STATE`、`WM_*`、`PTY_OPEN`)。 - `DL_*`(`77..79`)在 Wine 中为“可运行兼容”实现: - `DL_OPEN`:加载 guest ELF 到当前 Unicorn 地址空间,返回稳定 `handle`,并做引用计数。 - `DL_SYM`:解析 ELF `SYMTAB/DYNSYM` 并返回 guest 可调用地址。 @@ -962,6 +971,7 @@ UserSafeController(USC)危险 syscall 确认: - 网络 syscall(`95..106`)在 Wine 当前为兼容占位实现(统一返回 `0`);即 Wine 运行模式下不会提供真实网络收发。 - `MOUSE_STATE`(`107`)在 Wine 中为基础兼容实现:可返回指针数据结构;未启用窗口鼠标事件时 `ready` 可能为 `0`。 - `WM_*`(`108..115`)在 Wine 当前为兼容占位实现(统一返回 `0`);不会创建真实窗口服务。 +- `PTY_OPEN`(`116`)在 Wine 中创建内存缓冲 FD;写入端通过 `FD_WRITE` 追加,读取端通过 `FD_READ` 消费,用于桌面 Terminal 捕获子进程输出。 - Wine 在运行时崩溃场景下会生成与内核一致格式的“信号编码退出状态”,可通过 `WAITPID` 读取。 - Wine 当前音频 syscall 为占位实现:`AUDIO_AVAILABLE=0`,`AUDIO_PLAY_TONE=0`,`AUDIO_STOP=1`。 - Wine 版本号策略固定为 `85.0.0-wine`(历史兼容号;不会随 syscall 扩展继续增长)。 diff --git a/kit b/kit index 46c6d4a..3f29819 160000 --- a/kit +++ b/kit @@ -1 +1 @@ -Subproject commit 46c6d4a547e4d8c9a0240a64e07b18c34d81228d +Subproject commit 3f298195989388ce9207745c51fbadd3d6343d56 diff --git a/wine b/wine index c31867a..fe46a15 160000 --- a/wine +++ b/wine @@ -1 +1 @@ -Subproject commit c31867a23ab1baf5558b0dcb5fa3988530193fcd +Subproject commit fe46a157c22b4c5b9c6219838c94d32cbd9806d0