From a4b606505fe773db4727cb7f2a409ad2836645f0 Mon Sep 17 00:00:00 2001 From: Leonmmcoset Date: Sun, 26 Apr 2026 02:16:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=B5=8F=E8=A7=88=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cleonos/CMakeLists.txt | 11 +- cleonos/c/apps/file_explorer_main.c | 1116 +++++++++++++++++++++++++++ cleonos/c/apps/qrcode_main.c | 445 +++++++++-- cleonos/c/apps/uwm/uwm_app.c | 27 +- cleonos/c/apps/uwm/uwm_events.c | 27 +- cleonos/c/apps/uwm/uwm_window.c | 2 +- clks | 2 +- docs/syscall.md | 6 + 8 files changed, 1534 insertions(+), 102 deletions(-) create mode 100644 cleonos/c/apps/file_explorer_main.c diff --git a/cleonos/CMakeLists.txt b/cleonos/CMakeLists.txt index 34f0097..5cb7a1e 100644 --- a/cleonos/CMakeLists.txt +++ b/cleonos/CMakeLists.txt @@ -117,6 +117,7 @@ list(REMOVE_DUPLICATES USER_KELF_APP_NAMES) set(USER_APP_OUTPUTS) set(USER_APP_NAMES) set(RAMDISK_SHELL_APPS) +set(RAMDISK_UWM_APPS) set(RAMDISK_DRIVER_APPS) set(RAMDISK_SYSTEM_APPS) set(RAMDISK_ROOT_APPS) @@ -279,7 +280,9 @@ foreach(SRC IN LISTS USER_APP_MAIN_SOURCES) ) list(APPEND USER_APP_OUTPUTS "${_app_out}") - if(_app_name MATCHES ".*drv$") + if(_app_name STREQUAL "file_explorer") + list(APPEND RAMDISK_UWM_APPS "${_app_out}") + elseif(_app_name MATCHES ".*drv$") list(APPEND RAMDISK_DRIVER_APPS "${_app_out}") elseif(_app_name STREQUAL "hello") list(APPEND RAMDISK_ROOT_APPS "${_app_out}") @@ -349,6 +352,10 @@ foreach(_app IN LISTS RAMDISK_SHELL_APPS) get_filename_component(_app_file "${_app}" NAME) list(APPEND RAMDISK_COPY_COMMANDS COMMAND ${CMAKE_COMMAND} -E copy "${_app}" "${RAMDISK_ROOT}/shell/${_app_file}") endforeach() +foreach(_app IN LISTS RAMDISK_UWM_APPS) + get_filename_component(_app_file "${_app}" NAME) + list(APPEND RAMDISK_COPY_COMMANDS COMMAND ${CMAKE_COMMAND} -E copy "${_app}" "${RAMDISK_ROOT}/shell/uwm/${_app_file}") +endforeach() foreach(_app IN LISTS RAMDISK_ROOT_APPS) get_filename_component(_app_file "${_app}" NAME) list(APPEND RAMDISK_COPY_COMMANDS COMMAND ${CMAKE_COMMAND} -E copy "${_app}" "${RAMDISK_ROOT}/${_app_file}") @@ -367,7 +374,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E rm -rf "${RAMDISK_ROOT}" COMMAND ${CMAKE_COMMAND} -E make_directory "${RAMDISK_ROOT}" COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/ramdisk" "${RAMDISK_ROOT}" - COMMAND ${CMAKE_COMMAND} -E make_directory "${RAMDISK_ROOT}/system" "${RAMDISK_ROOT}/shell" "${RAMDISK_ROOT}/driver" "${RAMDISK_ROOT}/dev" + COMMAND ${CMAKE_COMMAND} -E make_directory "${RAMDISK_ROOT}/system" "${RAMDISK_ROOT}/shell" "${RAMDISK_ROOT}/shell/uwm" "${RAMDISK_ROOT}/driver" "${RAMDISK_ROOT}/dev" COMMAND ${CMAKE_COMMAND} -E copy "${KERNEL_SYMBOLS_FILE}" "${RAMDISK_ROOT}/system/kernel.sym" ${RAMDISK_COPY_COMMANDS} COMMAND ${CMAKE_COMMAND} -E touch "${RAMDISK_ROOT_STAMP}" diff --git a/cleonos/c/apps/file_explorer_main.c b/cleonos/c/apps/file_explorer_main.c new file mode 100644 index 0000000..e3e0d45 --- /dev/null +++ b/cleonos/c/apps/file_explorer_main.c @@ -0,0 +1,1116 @@ +#include + +#include +#include + +typedef long long i64; +typedef unsigned int fx_u32; + +#define FX_TTY_DISPLAY 1ULL +#define FX_PATH_MAX 192U +#define FX_NAME_MAX 96U +#define FX_ENTRY_MAX 96U +#define FX_PREVIEW_MAX 768U +#define FX_CANVAS_MAX_PIXELS (900ULL * 640ULL) + +#define FX_TITLE_H 32 +#define FX_TOOLBAR_H 44 +#define FX_STATUS_H 24 +#define FX_SIDEBAR_W 136 +#define FX_ROW_H 24 +#define FX_HEADER_H 24 +#define FX_PREVIEW_H 104 +#define FX_CLOSE_W 44 +#define FX_MIN_W 420 +#define FX_MIN_H 360 +#define FX_DEFAULT_W 760 +#define FX_DEFAULT_H 520 + +#define FX_KEY_LEFT 1ULL +#define FX_KEY_RIGHT 2ULL +#define FX_KEY_UP 3ULL +#define FX_KEY_DOWN 4ULL + +#define FX_COLOR_WHITE 0x00FFFFFFU +#define FX_COLOR_WIN_BLUE 0x000078D7U +#define FX_COLOR_CLOSE 0x00E81123U +#define FX_COLOR_TITLE_INACTIVE 0x00F3F3F3U +#define FX_COLOR_BG 0x00F3F3F3U +#define FX_COLOR_PANEL 0x00FFFFFFU +#define FX_COLOR_SIDEBAR 0x00F7F7F7U +#define FX_COLOR_TEXT 0x00232323U +#define FX_COLOR_MUTED 0x00666666U +#define FX_COLOR_BORDER 0x00D0D0D0U +#define FX_COLOR_SELECT 0x00CDE8FFU +#define FX_COLOR_BUTTON 0x00E7E7E7U +#define FX_COLOR_BUTTON_HOT 0x00D8EBFAU + +#define FX_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 fx_entry { + char name[FX_NAME_MAX]; + char path[FX_PATH_MAX]; + u64 type; + u64 size; +} fx_entry; + +typedef struct fx_app { + int screen_w; + int screen_h; + int x; + int y; + int w; + int h; + u64 window_id; + fx_u32 *pixels; + u64 pixel_count; + int running; + int dragging; + int drag_dx; + int drag_dy; + u64 old_tty; + int tty_switched; + char cwd[FX_PATH_MAX]; + fx_entry entries[FX_ENTRY_MAX]; + u64 entry_count; + int selected; + int scroll; + int preview_open; + char preview[FX_PREVIEW_MAX]; + char status[160]; + int last_click_index; + u64 last_click_tick; +} fx_app; + +static int fx_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 fx_u64_as_i32(u64 raw) { + return (int)(i64)raw; +} + +static u64 fx_strlen(const char *text) { + u64 len = 0ULL; + + if (text == (const char *)0) { + return 0ULL; + } + while (text[len] != '\0') { + len++; + } + return len; +} + +static void fx_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 fx_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 = fx_strlen(dst); + while (src[i] != '\0' && pos + 1ULL < dst_size) { + dst[pos++] = src[i++]; + } + dst[pos] = '\0'; +} + +static void fx_append_u64_dec(char *dst, u64 dst_size, u64 value) { + char tmp[24]; + u64 len = 0ULL; + + if (value == 0ULL) { + fx_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'; + fx_append(dst, dst_size, one); + } +} + +static void fx_set_status(fx_app *app, const char *text) { + if (app != (fx_app *)0) { + fx_copy(app->status, (u64)sizeof(app->status), text); + } +} + +static int fx_streq(const char *a, const char *b) { + u64 i = 0ULL; + + if (a == (const char *)0 || b == (const char *)0) { + return 0; + } + while (a[i] != '\0' && b[i] != '\0') { + if (a[i] != b[i]) { + return 0; + } + i++; + } + return (a[i] == '\0' && b[i] == '\0') ? 1 : 0; +} + +static char fx_upper_char(char ch) { + if (ch >= 'a' && ch <= 'z') { + return (char)(ch - ('a' - 'A')); + } + return ch; +} + +static int fx_char_equal_icase(char a, char b) { + return (fx_upper_char(a) == fx_upper_char(b)) ? 1 : 0; +} + +static int fx_has_suffix_icase(const char *text, const char *suffix) { + u64 text_len = fx_strlen(text); + u64 suffix_len = fx_strlen(suffix); + u64 i; + + if (text_len < suffix_len) { + return 0; + } + for (i = 0ULL; i < suffix_len; i++) { + if (fx_char_equal_icase(text[text_len - suffix_len + i], suffix[i]) == 0) { + return 0; + } + } + return 1; +} + +static int fx_name_compare(const char *a, const char *b) { + u64 i = 0ULL; + + while (a[i] != '\0' && b[i] != '\0') { + char ac = fx_upper_char(a[i]); + char bc = fx_upper_char(b[i]); + if (ac < bc) { + return -1; + } + if (ac > bc) { + return 1; + } + i++; + } + if (a[i] == '\0' && b[i] == '\0') { + return 0; + } + return (a[i] == '\0') ? -1 : 1; +} + +static int fx_join_path(const char *dir, const char *name, char *out, u64 out_size) { + u64 p = 0ULL; + u64 i; + + if (dir == (const char *)0 || name == (const char *)0 || out == (char *)0 || out_size == 0ULL) { + return 0; + } + if (dir[0] == '/' && dir[1] == '\0') { + if (p + 1ULL >= out_size) { + return 0; + } + out[p++] = '/'; + } else { + for (i = 0ULL; dir[i] != '\0'; i++) { + if (p + 1ULL >= out_size) { + return 0; + } + out[p++] = dir[i]; + } + if (p > 0ULL && out[p - 1ULL] != '/') { + if (p + 1ULL >= out_size) { + return 0; + } + out[p++] = '/'; + } + } + for (i = 0ULL; name[i] != '\0'; i++) { + if (p + 1ULL >= out_size) { + return 0; + } + out[p++] = name[i]; + } + out[p] = '\0'; + return 1; +} + +static void fx_parent_path(const char *path, char *out, u64 out_size) { + u64 len; + + if (out == (char *)0 || out_size == 0ULL) { + return; + } + fx_copy(out, out_size, path); + len = fx_strlen(out); + if (len <= 1ULL) { + fx_copy(out, out_size, "/"); + return; + } + while (len > 1ULL && out[len - 1ULL] == '/') { + out[--len] = '\0'; + } + while (len > 1ULL && out[len - 1ULL] != '/') { + out[--len] = '\0'; + } + if (len > 1ULL && out[len - 1ULL] == '/') { + out[len - 1ULL] = '\0'; + } + if (out[0] == '\0') { + fx_copy(out, out_size, "/"); + } +} + +static int fx_valid_dir(const char *path) { + return (cleonos_sys_fs_stat_type(path) == 2ULL) ? 1 : 0; +} + +static void fx_sort_entries(fx_app *app) { + u64 i; + + if (app == (fx_app *)0) { + return; + } + for (i = 1ULL; i < app->entry_count; i++) { + fx_entry key = app->entries[i]; + u64 j = i; + + while (j > 0ULL) { + fx_entry *prev = &app->entries[j - 1ULL]; + int move = 0; + + if (prev->type != 2ULL && key.type == 2ULL) { + move = 1; + } else if (prev->type == key.type && fx_name_compare(prev->name, key.name) > 0) { + move = 1; + } + if (move == 0) { + break; + } + app->entries[j] = app->entries[j - 1ULL]; + j--; + } + app->entries[j] = key; + } +} + +static int fx_load_dir(fx_app *app, const char *path) { + u64 count; + u64 i; + + if (app == (fx_app *)0 || path == (const char *)0 || fx_valid_dir(path) == 0) { + return 0; + } + + fx_copy(app->cwd, (u64)sizeof(app->cwd), path); + app->entry_count = 0ULL; + app->selected = 0; + app->scroll = 0; + app->preview_open = 0; + app->preview[0] = '\0'; + + count = cleonos_sys_fs_child_count(path); + if (count > FX_ENTRY_MAX) { + count = FX_ENTRY_MAX; + } + + for (i = 0ULL; i < count; i++) { + fx_entry *entry = &app->entries[app->entry_count]; + + entry->name[0] = '\0'; + if (cleonos_sys_fs_get_child_name(path, i, entry->name) == 0ULL) { + continue; + } + if (entry->name[0] == '\0') { + continue; + } + if (fx_join_path(path, entry->name, entry->path, (u64)sizeof(entry->path)) == 0) { + continue; + } + entry->type = cleonos_sys_fs_stat_type(entry->path); + entry->size = (entry->type == 1ULL) ? cleonos_sys_fs_stat_size(entry->path) : 0ULL; + app->entry_count++; + } + + fx_sort_entries(app); + if (app->entry_count == 0ULL) { + app->selected = -1; + fx_set_status(app, "Folder is empty"); + } else { + fx_set_status(app, "Ready"); + } + return 1; +} + +static void fx_format_size(char *out, u64 out_size, u64 size) { + if (out == (char *)0 || out_size == 0ULL) { + return; + } + out[0] = '\0'; + if (size >= 1048576ULL) { + fx_append_u64_dec(out, out_size, size / 1048576ULL); + fx_append(out, out_size, " MB"); + } else if (size >= 1024ULL) { + fx_append_u64_dec(out, out_size, size / 1024ULL); + fx_append(out, out_size, " KB"); + } else { + fx_append_u64_dec(out, out_size, size); + fx_append(out, out_size, " B"); + } +} + +static void fx_sanitize_preview(char *buffer, u64 *io_len) { + u64 r; + u64 w = 0ULL; + u64 len; + + if (buffer == (char *)0 || io_len == (u64 *)0) { + return; + } + len = *io_len; + for (r = 0ULL; r < len && w + 1ULL < (u64)FX_PREVIEW_MAX; r++) { + unsigned char ch = (unsigned char)buffer[r]; + if (ch == '\r') { + continue; + } + if (ch == '\n' || ch == '\t' || (ch >= 32U && ch <= 126U)) { + buffer[w++] = (char)((ch == '\t') ? ' ' : ch); + } else { + buffer[w++] = '.'; + } + } + buffer[w] = '\0'; + *io_len = w; +} + +static void fx_open_selected(fx_app *app) { + fx_entry *entry; + + if (app == (fx_app *)0 || app->selected < 0 || (u64)app->selected >= app->entry_count) { + return; + } + entry = &app->entries[(u64)app->selected]; + if (entry->type == 2ULL) { + if (fx_load_dir(app, entry->path) == 0) { + fx_set_status(app, "Open folder failed"); + } + return; + } + if (entry->type != 1ULL) { + fx_set_status(app, "Unknown entry type"); + return; + } + if (fx_has_suffix_icase(entry->name, ".elf") != 0) { + u64 pid = cleonos_sys_spawn_pathv(entry->path, "", "LAUNCHED_BY=file_explorer"); + app->preview_open = 0; + if (pid == (u64)-1 || pid == 0ULL) { + fx_set_status(app, "Launch failed"); + } else { + fx_copy(app->status, (u64)sizeof(app->status), "Launched "); + fx_append(app->status, (u64)sizeof(app->status), entry->name); + } + return; + } + + { + u64 got = cleonos_sys_fs_read(entry->path, app->preview, (u64)sizeof(app->preview) - 1ULL); + if (got == (u64)-1) { + app->preview_open = 0; + fx_set_status(app, "Read failed"); + return; + } + fx_sanitize_preview(app->preview, &got); + app->preview_open = 1; + fx_copy(app->status, (u64)sizeof(app->status), "Preview "); + fx_append(app->status, (u64)sizeof(app->status), entry->name); + } +} + +static void fx_go_parent(fx_app *app) { + char parent[FX_PATH_MAX]; + + if (app == (fx_app *)0) { + return; + } + fx_parent_path(app->cwd, parent, (u64)sizeof(parent)); + if (fx_load_dir(app, parent) == 0) { + fx_set_status(app, "Parent folder unavailable"); + } +} + +static u64 fx_glyph_mask(char ch) { + switch (fx_upper_char(ch)) { + case 'A': + return FX_GLYPH7(14U, 17U, 17U, 31U, 17U, 17U, 17U); + case 'B': + return FX_GLYPH7(30U, 17U, 17U, 30U, 17U, 17U, 30U); + case 'C': + return FX_GLYPH7(14U, 17U, 16U, 16U, 16U, 17U, 14U); + case 'D': + return FX_GLYPH7(30U, 17U, 17U, 17U, 17U, 17U, 30U); + case 'E': + return FX_GLYPH7(31U, 16U, 16U, 30U, 16U, 16U, 31U); + case 'F': + return FX_GLYPH7(31U, 16U, 16U, 30U, 16U, 16U, 16U); + case 'G': + return FX_GLYPH7(14U, 17U, 16U, 23U, 17U, 17U, 15U); + case 'H': + return FX_GLYPH7(17U, 17U, 17U, 31U, 17U, 17U, 17U); + case 'I': + return FX_GLYPH7(31U, 4U, 4U, 4U, 4U, 4U, 31U); + case 'J': + return FX_GLYPH7(1U, 1U, 1U, 1U, 17U, 17U, 14U); + case 'K': + return FX_GLYPH7(17U, 18U, 20U, 24U, 20U, 18U, 17U); + case 'L': + return FX_GLYPH7(16U, 16U, 16U, 16U, 16U, 16U, 31U); + case 'M': + return FX_GLYPH7(17U, 27U, 21U, 21U, 17U, 17U, 17U); + case 'N': + return FX_GLYPH7(17U, 25U, 21U, 19U, 17U, 17U, 17U); + case 'O': + return FX_GLYPH7(14U, 17U, 17U, 17U, 17U, 17U, 14U); + case 'P': + return FX_GLYPH7(30U, 17U, 17U, 30U, 16U, 16U, 16U); + case 'Q': + return FX_GLYPH7(14U, 17U, 17U, 17U, 21U, 18U, 13U); + case 'R': + return FX_GLYPH7(30U, 17U, 17U, 30U, 20U, 18U, 17U); + case 'S': + return FX_GLYPH7(15U, 16U, 16U, 14U, 1U, 1U, 30U); + case 'T': + return FX_GLYPH7(31U, 4U, 4U, 4U, 4U, 4U, 4U); + case 'U': + return FX_GLYPH7(17U, 17U, 17U, 17U, 17U, 17U, 14U); + case 'V': + return FX_GLYPH7(17U, 17U, 17U, 17U, 17U, 10U, 4U); + case 'W': + return FX_GLYPH7(17U, 17U, 17U, 21U, 21U, 21U, 10U); + case 'X': + return FX_GLYPH7(17U, 17U, 10U, 4U, 10U, 17U, 17U); + case 'Y': + return FX_GLYPH7(17U, 17U, 10U, 4U, 4U, 4U, 4U); + case 'Z': + return FX_GLYPH7(31U, 1U, 2U, 4U, 8U, 16U, 31U); + case '0': + return FX_GLYPH7(14U, 17U, 19U, 21U, 25U, 17U, 14U); + case '1': + return FX_GLYPH7(4U, 12U, 4U, 4U, 4U, 4U, 14U); + case '2': + return FX_GLYPH7(14U, 17U, 1U, 2U, 4U, 8U, 31U); + case '3': + return FX_GLYPH7(30U, 1U, 1U, 14U, 1U, 1U, 30U); + case '4': + return FX_GLYPH7(2U, 6U, 10U, 18U, 31U, 2U, 2U); + case '5': + return FX_GLYPH7(31U, 16U, 16U, 30U, 1U, 1U, 30U); + case '6': + return FX_GLYPH7(14U, 16U, 16U, 30U, 17U, 17U, 14U); + case '7': + return FX_GLYPH7(31U, 1U, 2U, 4U, 8U, 8U, 8U); + case '8': + return FX_GLYPH7(14U, 17U, 17U, 14U, 17U, 17U, 14U); + case '9': + return FX_GLYPH7(14U, 17U, 17U, 15U, 1U, 1U, 14U); + case '-': + return FX_GLYPH7(0U, 0U, 0U, 31U, 0U, 0U, 0U); + case '_': + return FX_GLYPH7(0U, 0U, 0U, 0U, 0U, 0U, 31U); + case '.': + return FX_GLYPH7(0U, 0U, 0U, 0U, 0U, 12U, 12U); + case ':': + return FX_GLYPH7(0U, 12U, 12U, 0U, 12U, 12U, 0U); + case '/': + return FX_GLYPH7(1U, 1U, 2U, 4U, 8U, 16U, 16U); + case '+': + return FX_GLYPH7(0U, 4U, 4U, 31U, 4U, 4U, 0U); + default: + return 0ULL; + } +} + +static void fx_fill_rect(fx_app *app, int x, int y, int w, int h, fx_u32 color) { + int left; + int top; + int right; + int bottom; + int row; + + if (app == (fx_app *)0 || app->pixels == (fx_u32 *)0 || w <= 0 || h <= 0) { + return; + } + left = fx_clampi(x, 0, app->w); + top = fx_clampi(y, 0, app->h); + right = fx_clampi(x + w, 0, app->w); + bottom = fx_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; + for (col = left; col < right; col++) { + app->pixels[base + (u64)(unsigned int)col] = color; + } + } +} + +static void fx_stroke_rect(fx_app *app, int x, int y, int w, int h, fx_u32 color) { + fx_fill_rect(app, x, y, w, 1, color); + fx_fill_rect(app, x, y + h - 1, w, 1, color); + fx_fill_rect(app, x, y, 1, h, color); + fx_fill_rect(app, x + w - 1, y, 1, h, color); +} + +static void fx_draw_char(fx_app *app, int x, int y, char ch, int scale, fx_u32 color) { + u64 mask = fx_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 = (unsigned int)((6 - row) * 5 + (4 - col)); + if ((mask & (1ULL << bit)) != 0ULL) { + fx_fill_rect(app, x + (col * scale), y + (row * scale), scale, scale, color); + } + } + } +} + +static void fx_draw_text_limit(fx_app *app, int x, int y, const char *text, int scale, fx_u32 color, int max_x) { + int cursor_x = x; + + if (app == (fx_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 != ' ') { + fx_draw_char(app, cursor_x, y, *text, scale, color); + } + cursor_x += 6 * scale; + text++; + } +} + +static void fx_draw_text(fx_app *app, int x, int y, const char *text, int scale, fx_u32 color) { + fx_draw_text_limit(app, x, y, text, scale, color, app != (fx_app *)0 ? app->w : 0); +} + +static void fx_draw_button(fx_app *app, int x, int y, int w, int h, const char *label, int hot) { + fx_fill_rect(app, x, y, w, h, hot != 0 ? FX_COLOR_BUTTON_HOT : FX_COLOR_BUTTON); + fx_stroke_rect(app, x, y, w, h, FX_COLOR_BORDER); + fx_draw_text_limit(app, x + 10, y + ((h - 7) / 2), label, 1, FX_COLOR_TEXT, x + w - 8); +} + +static void fx_draw_preview(fx_app *app, int y, int h) { + int line = 0; + int cursor_y; + u64 i = 0ULL; + char tmp[96]; + u64 p = 0ULL; + + fx_fill_rect(app, FX_SIDEBAR_W, y, app->w - FX_SIDEBAR_W, h, 0x00FAFAFAU); + fx_stroke_rect(app, FX_SIDEBAR_W, y, app->w - FX_SIDEBAR_W, h, FX_COLOR_BORDER); + fx_draw_text(app, FX_SIDEBAR_W + 12, y + 10, "PREVIEW", 1, FX_COLOR_MUTED); + cursor_y = y + 28; + + while (app->preview[i] != '\0' && line < 4) { + char ch = app->preview[i++]; + if (ch == '\n' || p + 1ULL >= (u64)sizeof(tmp)) { + tmp[p] = '\0'; + fx_draw_text_limit(app, FX_SIDEBAR_W + 12, cursor_y, tmp, 1, FX_COLOR_TEXT, app->w - 12); + p = 0ULL; + cursor_y += 16; + line++; + continue; + } + tmp[p++] = ch; + } + if (p > 0ULL && line < 4) { + tmp[p] = '\0'; + fx_draw_text_limit(app, FX_SIDEBAR_W + 12, cursor_y, tmp, 1, FX_COLOR_TEXT, app->w - 12); + } +} + +static int fx_visible_rows(const fx_app *app) { + int top = FX_TITLE_H + FX_TOOLBAR_H + FX_HEADER_H; + int bottom = app->h - FX_STATUS_H - (app->preview_open != 0 ? FX_PREVIEW_H : 0); + int rows = (bottom - top) / FX_ROW_H; + + return (rows < 1) ? 1 : rows; +} + +static void fx_ensure_selected_visible(fx_app *app) { + int rows; + + if (app == (fx_app *)0 || app->selected < 0) { + return; + } + rows = fx_visible_rows(app); + if (app->selected < app->scroll) { + app->scroll = app->selected; + } + if (app->selected >= app->scroll + rows) { + app->scroll = app->selected - rows + 1; + } + if (app->scroll < 0) { + app->scroll = 0; + } +} + +static void fx_render(fx_app *app) { + int list_top = FX_TITLE_H + FX_TOOLBAR_H; + int rows_top = list_top + FX_HEADER_H; + int preview_y = app->h - FX_STATUS_H - (app->preview_open != 0 ? FX_PREVIEW_H : 0); + int list_bottom = preview_y; + int rows; + int i; + const char *quick_names[] = {"ROOT", "SYSTEM", "SHELL", "UWM", "TEMP", "DRIVER", "DEV"}; + const char *quick_paths[] = {"/", "/system", "/shell", "/shell/uwm", "/temp", "/driver", "/dev"}; + + fx_fill_rect(app, 0, 0, app->w, app->h, FX_COLOR_BG); + fx_fill_rect(app, 0, 0, app->w, FX_TITLE_H, FX_COLOR_WIN_BLUE); + fx_draw_text(app, 12, 12, "FILE EXPLORER", 1, FX_COLOR_WHITE); + fx_fill_rect(app, app->w - FX_CLOSE_W, 0, FX_CLOSE_W, FX_TITLE_H, FX_COLOR_CLOSE); + fx_fill_rect(app, app->w - 28, 14, 14, 2, FX_COLOR_WHITE); + fx_fill_rect(app, app->w - 22, 9, 2, 12, FX_COLOR_WHITE); + + fx_fill_rect(app, 0, FX_TITLE_H, app->w, FX_TOOLBAR_H, FX_COLOR_PANEL); + fx_fill_rect(app, 0, FX_TITLE_H + FX_TOOLBAR_H - 1, app->w, 1, FX_COLOR_BORDER); + fx_draw_button(app, 10, FX_TITLE_H + 8, 52, 28, "UP", 0); + fx_draw_button(app, 70, FX_TITLE_H + 8, 66, 28, "ROOT", 0); + fx_draw_button(app, 144, FX_TITLE_H + 8, 66, 28, "TEMP", 0); + fx_draw_button(app, 218, FX_TITLE_H + 8, 84, 28, "REFRESH", 0); + fx_fill_rect(app, 314, FX_TITLE_H + 8, app->w - 326, 28, 0x00F8F8F8U); + fx_stroke_rect(app, 314, FX_TITLE_H + 8, app->w - 326, 28, FX_COLOR_BORDER); + fx_draw_text_limit(app, 324, FX_TITLE_H + 18, app->cwd, 1, FX_COLOR_TEXT, app->w - 14); + + fx_fill_rect(app, 0, list_top, FX_SIDEBAR_W, app->h - list_top - FX_STATUS_H, FX_COLOR_SIDEBAR); + fx_fill_rect(app, FX_SIDEBAR_W - 1, list_top, 1, app->h - list_top - FX_STATUS_H, FX_COLOR_BORDER); + for (i = 0; i < 7; i++) { + int y = list_top + 16 + (i * 32); + int active = fx_streq(app->cwd, quick_paths[i]); + if (active != 0) { + fx_fill_rect(app, 8, y - 8, FX_SIDEBAR_W - 16, 26, FX_COLOR_SELECT); + } + fx_draw_text_limit(app, 18, y, quick_names[i], 1, active != 0 ? FX_COLOR_TEXT : FX_COLOR_MUTED, + FX_SIDEBAR_W - 10); + } + + fx_fill_rect(app, FX_SIDEBAR_W, list_top, app->w - FX_SIDEBAR_W, FX_HEADER_H, 0x00EFEFEFU); + fx_fill_rect(app, FX_SIDEBAR_W, list_top + FX_HEADER_H - 1, app->w - FX_SIDEBAR_W, 1, FX_COLOR_BORDER); + fx_draw_text(app, FX_SIDEBAR_W + 12, list_top + 9, "NAME", 1, FX_COLOR_MUTED); + fx_draw_text(app, app->w - 202, list_top + 9, "TYPE", 1, FX_COLOR_MUTED); + fx_draw_text(app, app->w - 102, list_top + 9, "SIZE", 1, FX_COLOR_MUTED); + + rows = (list_bottom - rows_top) / FX_ROW_H; + if (rows < 1) { + rows = 1; + } + for (i = 0; i < rows; i++) { + int entry_index = app->scroll + i; + int y = rows_top + (i * FX_ROW_H); + fx_entry *entry; + char size_text[32]; + + if ((u64)entry_index >= app->entry_count) { + break; + } + entry = &app->entries[(u64)entry_index]; + if (entry_index == app->selected) { + fx_fill_rect(app, FX_SIDEBAR_W + 2, y, app->w - FX_SIDEBAR_W - 4, FX_ROW_H, FX_COLOR_SELECT); + } else if ((i & 1) != 0) { + fx_fill_rect(app, FX_SIDEBAR_W, y, app->w - FX_SIDEBAR_W, FX_ROW_H, 0x00FAFAFAU); + } + fx_draw_text_limit(app, FX_SIDEBAR_W + 12, y + 8, entry->name, 1, FX_COLOR_TEXT, app->w - 214); + fx_draw_text(app, app->w - 202, y + 8, entry->type == 2ULL ? "FOLDER" : "FILE", 1, + entry->type == 2ULL ? FX_COLOR_WIN_BLUE : FX_COLOR_MUTED); + size_text[0] = '\0'; + if (entry->type == 1ULL) { + fx_format_size(size_text, (u64)sizeof(size_text), entry->size); + fx_draw_text_limit(app, app->w - 102, y + 8, size_text, 1, FX_COLOR_MUTED, app->w - 8); + } + } + if (app->entry_count == 0ULL) { + fx_draw_text(app, FX_SIDEBAR_W + 18, rows_top + 24, "EMPTY FOLDER", 1, FX_COLOR_MUTED); + } + + if (app->preview_open != 0) { + fx_draw_preview(app, preview_y, FX_PREVIEW_H); + } + + fx_fill_rect(app, 0, app->h - FX_STATUS_H, app->w, FX_STATUS_H, 0x00EDEDEDU); + fx_fill_rect(app, 0, app->h - FX_STATUS_H, app->w, 1, FX_COLOR_BORDER); + fx_draw_text_limit(app, 12, app->h - 16, app->status, 1, FX_COLOR_MUTED, app->w - 12); +} + +static int fx_present(fx_app *app) { + cleonos_wm_present_req req; + + if (app == (fx_app *)0 || app->window_id == 0ULL || app->pixels == (fx_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 int fx_render_present(fx_app *app) { + fx_render(app); + return fx_present(app); +} + +static int fx_hit_button(int x, int y, int bx, int by, int bw, int bh) { + return (x >= bx && x < bx + bw && y >= by && y < by + bh) ? 1 : 0; +} + +static int fx_sidebar_index_at(int local_y) { + int top = FX_TITLE_H + FX_TOOLBAR_H + 16 - 8; + int rel = local_y - top; + int idx; + + if (rel < 0) { + return -1; + } + idx = rel / 32; + if (idx < 0 || idx >= 7) { + return -1; + } + if ((rel % 32) >= 26) { + return -1; + } + return idx; +} + +static int fx_row_at(const fx_app *app, int local_x, int local_y) { + int rows_top = FX_TITLE_H + FX_TOOLBAR_H + FX_HEADER_H; + int row; + + if (app == (const fx_app *)0 || local_x < FX_SIDEBAR_W || local_y < rows_top) { + return -1; + } + row = (local_y - rows_top) / FX_ROW_H; + if (row < 0 || row >= fx_visible_rows(app)) { + return -1; + } + row += app->scroll; + if (row < 0 || (u64)row >= app->entry_count) { + return -1; + } + return row; +} + +static void fx_select_delta(fx_app *app, int delta) { + if (app == (fx_app *)0 || app->entry_count == 0ULL) { + return; + } + if (app->selected < 0) { + app->selected = 0; + } else { + app->selected = fx_clampi(app->selected + delta, 0, (int)app->entry_count - 1); + } + fx_ensure_selected_visible(app); +} + +static void fx_navigate_path(fx_app *app, const char *path) { + if (fx_load_dir(app, path) == 0) { + fx_set_status(app, "Folder unavailable"); + } +} + +static void fx_refresh(fx_app *app) { + char path[FX_PATH_MAX]; + + if (app == (fx_app *)0) { + return; + } + fx_copy(path, (u64)sizeof(path), app->cwd); + if (fx_load_dir(app, path) == 0) { + fx_set_status(app, "Refresh failed"); + } +} + +static void fx_handle_key(fx_app *app, u64 key) { + if (key == (u64)'q' || key == (u64)'Q' || key == 27ULL) { + app->running = 0; + } else if (key == FX_KEY_UP || key == (u64)'w' || key == (u64)'W') { + fx_select_delta(app, -1); + } else if (key == FX_KEY_DOWN || key == (u64)'s' || key == (u64)'S') { + fx_select_delta(app, 1); + } else if (key == 13ULL) { + fx_open_selected(app); + } else if (key == 8ULL || key == 127ULL || key == (u64)'u' || key == (u64)'U') { + fx_go_parent(app); + } else if (key == (u64)'r' || key == (u64)'R') { + fx_refresh(app); + } else if (key == (u64)'h' || key == (u64)'H') { + fx_navigate_path(app, "/"); + } +} + +static void fx_handle_mouse_button(fx_app *app, const cleonos_wm_event *event) { + static const char *quick_paths[] = {"/", "/system", "/shell", "/shell/uwm", "/temp", "/driver", "/dev"}; + u64 buttons = event->arg0; + u64 changed = event->arg1; + int local_x = fx_u64_as_i32(event->arg2); + int local_y = fx_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 (local_y >= 0 && local_y < FX_TITLE_H) { + if (local_x >= app->w - FX_CLOSE_W) { + app->running = 0; + return; + } + app->dragging = 1; + app->drag_dx = local_x; + app->drag_dy = local_y; + return; + } + if (fx_hit_button(local_x, local_y, 10, FX_TITLE_H + 8, 52, 28) != 0) { + fx_go_parent(app); + return; + } + if (fx_hit_button(local_x, local_y, 70, FX_TITLE_H + 8, 66, 28) != 0) { + fx_navigate_path(app, "/"); + return; + } + if (fx_hit_button(local_x, local_y, 144, FX_TITLE_H + 8, 66, 28) != 0) { + fx_navigate_path(app, "/temp"); + return; + } + if (fx_hit_button(local_x, local_y, 218, FX_TITLE_H + 8, 84, 28) != 0) { + fx_refresh(app); + return; + } + if (local_x < FX_SIDEBAR_W) { + int idx = fx_sidebar_index_at(local_y); + if (idx >= 0) { + fx_navigate_path(app, quick_paths[idx]); + } + return; + } + { + int row = fx_row_at(app, local_x, local_y); + if (row >= 0) { + u64 now = cleonos_sys_timer_ticks(); + if (row == app->selected && row == app->last_click_index && now - app->last_click_tick < 40ULL) { + fx_open_selected(app); + } else { + app->selected = row; + fx_ensure_selected_visible(app); + app->preview_open = 0; + } + app->last_click_index = row; + app->last_click_tick = now; + } + } +} + +static void fx_handle_mouse_move(fx_app *app, const cleonos_wm_event *event) { + if (app->dragging != 0) { + cleonos_wm_move_req req; + app->x = fx_u64_as_i32(event->arg0) - app->drag_dx; + app->y = fx_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 fx_loop(fx_app *app) { + while (app->running != 0) { + int dirty = 0; + int handled = 0; + u64 budget = 0ULL; + + while (budget < 96ULL) { + cleonos_wm_event event; + memset(&event, 0, sizeof(event)); + if (cleonos_sys_wm_poll_event(app->window_id, &event) == 0ULL) { + break; + } + handled = 1; + dirty = 1; + if (event.type == CLEONOS_WM_EVENT_KEY) { + fx_handle_key(app, event.arg0); + } else if (event.type == CLEONOS_WM_EVENT_MOUSE_BUTTON) { + fx_handle_mouse_button(app, &event); + } else if (event.type == CLEONOS_WM_EVENT_MOUSE_MOVE) { + fx_handle_mouse_move(app, &event); + } + if (app->running == 0) { + break; + } + budget++; + } + + if (dirty != 0 && app->running != 0) { + (void)fx_render_present(app); + } + if (handled != 0 || app->dragging != 0) { + (void)cleonos_sys_yield(); + } else { + (void)cleonos_sys_sleep_ticks(1ULL); + } + } +} + +static int fx_choose_geometry(fx_app *app) { + cleonos_fb_info fb; + int max_w; + int max_h; + + memset(&fb, 0, 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; + max_w = app->screen_w - 96; + max_h = app->screen_h - 120; + if (max_w < FX_MIN_W) { + max_w = app->screen_w; + } + if (max_h < FX_MIN_H) { + max_h = app->screen_h; + } + app->w = fx_clampi(FX_DEFAULT_W, FX_MIN_W, max_w); + app->h = fx_clampi(FX_DEFAULT_H, FX_MIN_H, max_h); + if ((u64)(unsigned int)app->w * (u64)(unsigned int)app->h > FX_CANVAS_MAX_PIXELS) { + app->w = 760; + app->h = 520; + } + 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 fx_init_window(fx_app *app) { + cleonos_wm_create_req req; + u64 count; + + if (fx_choose_geometry(app) == 0) { + return 0; + } + count = (u64)(unsigned int)app->w * (u64)(unsigned int)app->h; + app->pixels = (fx_u32 *)malloc((size_t)(count * 4ULL)); + if (app->pixels == (fx_u32 *)0) { + return 0; + } + app->pixel_count = count; + memset(app->pixels, 0, (size_t)(count * 4ULL)); + + app->old_tty = cleonos_sys_tty_active(); + if (app->old_tty != FX_TTY_DISPLAY) { + (void)cleonos_sys_tty_switch(FX_TTY_DISPLAY); + app->tty_switched = 1; + } + + 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) { + return 0; + } + (void)cleonos_sys_wm_set_focus(app->window_id); + return 1; +} + +static void fx_destroy(fx_app *app) { + if (app == (fx_app *)0) { + return; + } + if (app->window_id != 0ULL) { + (void)cleonos_sys_wm_destroy(app->window_id); + app->window_id = 0ULL; + } + if (app->pixels != (fx_u32 *)0) { + free(app->pixels); + app->pixels = (fx_u32 *)0; + } + 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) { + fx_app app; + const char *start_path = "/"; + + (void)envp; + memset(&app, 0, sizeof(app)); + app.selected = -1; + app.last_click_index = -1; + app.running = 1; + + if (argc > 1 && argv != (char **)0 && argv[1] != (char *)0 && argv[1][0] == '/') { + start_path = argv[1]; + } + if (fx_load_dir(&app, start_path) == 0) { + (void)fx_load_dir(&app, "/"); + } + if (fx_init_window(&app) == 0) { + fx_destroy(&app); + return 1; + } + if (fx_render_present(&app) == 0) { + fx_destroy(&app); + return 1; + } + fx_loop(&app); + fx_destroy(&app); + return 0; +} diff --git a/cleonos/c/apps/qrcode_main.c b/cleonos/c/apps/qrcode_main.c index d7cc8a6..7d9ee58 100644 --- a/cleonos/c/apps/qrcode_main.c +++ b/cleonos/c/apps/qrcode_main.c @@ -5,13 +5,155 @@ #define USH_QRCODE_MAX_VERSION 15 #define USH_QRCODE_BORDER 4U #define USH_QRCODE_MAX_MODULES ((USH_QRCODE_MAX_VERSION * 4U) + 17U + (USH_QRCODE_BORDER * 2U)) -#define USH_QRCODE_CANVAS_MAX 1024U +#define USH_QRCODE_CANVAS_MAX 640U +#define USH_QRCODE_TTY_DISPLAY 1ULL +#define USH_QRCODE_TITLE_H 34 +#define USH_QRCODE_FOOTER_H 34 +#define USH_QRCODE_PAD 24 +#define USH_QRCODE_CLOSE_W 44 +#define USH_QRCODE_WINDOW_DEFAULT_W 520 +#define USH_QRCODE_WINDOW_DEFAULT_H 600 +#define USH_QRCODE_WINDOW_MIN_W 320 +#define USH_QRCODE_WINDOW_MIN_H 380 -#define USH_QRCODE_COLOR_DARK 0x00000000ULL -#define USH_QRCODE_COLOR_LIGHT 0x00FFFFFFULL +#define USH_QRCODE_COLOR_DARK 0x00000000U +#define USH_QRCODE_COLOR_LIGHT 0x00FFFFFFU +#define USH_QRCODE_COLOR_DESKTOP 0x00F3F3F3U +#define USH_QRCODE_COLOR_TITLE 0x000078D7U +#define USH_QRCODE_COLOR_CLOSE 0x00E81123U +#define USH_QRCODE_COLOR_TEXT 0x00232323U +#define USH_QRCODE_COLOR_MUTED 0x00666666U +#define USH_QRCODE_COLOR_BORDER 0x00D0D0D0U +#define USH_QRCODE_COLOR_PANEL 0x00FFFFFFU + +#define USH_QRCODE_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)) static unsigned int ush_qrcode_canvas[USH_QRCODE_CANVAS_MAX][USH_QRCODE_CANVAS_MAX]; +static int ush_qrcode_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 ush_qrcode_u64_as_i32(u64 raw) { + return (int)(i64)raw; +} + +static char ush_qrcode_upper_char(char ch) { + if (ch >= 'a' && ch <= 'z') { + return (char)(ch - ('a' - 'A')); + } + + return ch; +} + +static u64 ush_qrcode_glyph_mask(char ch) { + switch (ush_qrcode_upper_char(ch)) { + case 'C': + return USH_QRCODE_GLYPH7(14U, 17U, 16U, 16U, 16U, 17U, 14U); + case 'D': + return USH_QRCODE_GLYPH7(30U, 17U, 17U, 17U, 17U, 17U, 30U); + case 'E': + return USH_QRCODE_GLYPH7(31U, 16U, 16U, 30U, 16U, 16U, 31U); + case 'L': + return USH_QRCODE_GLYPH7(16U, 16U, 16U, 16U, 16U, 16U, 31U); + case 'O': + return USH_QRCODE_GLYPH7(14U, 17U, 17U, 17U, 17U, 17U, 14U); + case 'P': + return USH_QRCODE_GLYPH7(30U, 17U, 17U, 30U, 16U, 16U, 16U); + case 'Q': + return USH_QRCODE_GLYPH7(14U, 17U, 17U, 17U, 21U, 18U, 13U); + case 'R': + return USH_QRCODE_GLYPH7(30U, 17U, 17U, 30U, 20U, 18U, 17U); + case 'S': + return USH_QRCODE_GLYPH7(15U, 16U, 16U, 14U, 1U, 1U, 30U); + case 'T': + return USH_QRCODE_GLYPH7(31U, 4U, 4U, 4U, 4U, 4U, 4U); + default: + return 0ULL; + } +} + +static void ush_qrcode_fill_rect(int canvas_w, int canvas_h, int x, int y, int w, int h, unsigned int color) { + int left; + int top; + int right; + int bottom; + int row; + + if (canvas_w <= 0 || canvas_h <= 0 || canvas_w > (int)USH_QRCODE_CANVAS_MAX || + canvas_h > (int)USH_QRCODE_CANVAS_MAX || w <= 0 || h <= 0) { + return; + } + + left = ush_qrcode_clampi(x, 0, canvas_w); + top = ush_qrcode_clampi(y, 0, canvas_h); + right = ush_qrcode_clampi(x + w, 0, canvas_w); + bottom = ush_qrcode_clampi(y + h, 0, canvas_h); + if (left >= right || top >= bottom) { + return; + } + + for (row = top; row < bottom; row++) { + int col; + for (col = left; col < right; col++) { + ush_qrcode_canvas[row][col] = color; + } + } +} + +static void ush_qrcode_stroke_rect(int canvas_w, int canvas_h, int x, int y, int w, int h, unsigned int color) { + ush_qrcode_fill_rect(canvas_w, canvas_h, x, y, w, 1, color); + ush_qrcode_fill_rect(canvas_w, canvas_h, x, y + h - 1, w, 1, color); + ush_qrcode_fill_rect(canvas_w, canvas_h, x, y, 1, h, color); + ush_qrcode_fill_rect(canvas_w, canvas_h, x + w - 1, y, 1, h, color); +} + +static void ush_qrcode_draw_char(int canvas_w, int canvas_h, int x, int y, char ch, int scale, unsigned int color) { + u64 mask = ush_qrcode_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) { + ush_qrcode_fill_rect(canvas_w, canvas_h, x + (col * scale), y + (row * scale), scale, scale, color); + } + } + } +} + +static void ush_qrcode_draw_text(int canvas_w, int canvas_h, int x, int y, const char *text, int scale, + unsigned int color) { + int cursor_x = x; + + if (text == (const char *)0 || scale <= 0) { + return; + } + + while (*text != '\0' && cursor_x + (5 * scale) < canvas_w) { + if (*text != ' ') { + ush_qrcode_draw_char(canvas_w, canvas_h, cursor_x, y, *text, scale, color); + } + cursor_x += 6 * scale; + text++; + } +} + static int ush_qrcode_streq_ignore_case(const char *left, const char *right) { u64 i = 0ULL; @@ -200,109 +342,268 @@ static void ush_qrcode_emit_ascii(const uint8_t qrcode[]) { } } -static int ush_qrcode_build_canvas(const uint8_t qrcode[], u64 module_pixels, u64 *out_side_pixels) { - int qr_size = qrcodegen_getSize(qrcode); - u64 side_modules; - u64 side_pixels; - u64 y; - u64 x; +static void ush_qrcode_choose_window_geometry(const cleonos_fb_info *fb_info, int *out_x, int *out_y, int *out_w, + int *out_h) { + int screen_w = 800; + int screen_h = 600; + int max_w; + int max_h; + int win_w; + int win_h; - if (out_side_pixels == (u64 *)0 || module_pixels == 0ULL || qr_size <= 0) { + if (fb_info != (const cleonos_fb_info *)0 && fb_info->width > 0ULL && fb_info->height > 0ULL && + fb_info->width <= 4096ULL && fb_info->height <= 4096ULL) { + screen_w = (int)fb_info->width; + screen_h = (int)fb_info->height; + } + + max_w = screen_w - 80; + max_h = screen_h - 96; + if (max_w < USH_QRCODE_WINDOW_MIN_W) { + max_w = screen_w; + } + if (max_h < USH_QRCODE_WINDOW_MIN_H) { + max_h = screen_h; + } + + win_w = ush_qrcode_clampi(USH_QRCODE_WINDOW_DEFAULT_W, USH_QRCODE_WINDOW_MIN_W, max_w); + win_h = ush_qrcode_clampi(USH_QRCODE_WINDOW_DEFAULT_H, USH_QRCODE_WINDOW_MIN_H, max_h); + if (win_w > (int)USH_QRCODE_CANVAS_MAX) { + win_w = (int)USH_QRCODE_CANVAS_MAX; + } + if (win_h > (int)USH_QRCODE_CANVAS_MAX) { + win_h = (int)USH_QRCODE_CANVAS_MAX; + } + + *out_w = win_w; + *out_h = win_h; + *out_x = (screen_w > win_w) ? ((screen_w - win_w) / 2) : 0; + *out_y = (screen_h > win_h) ? ((screen_h - win_h) / 2) : 0; +} + +static int ush_qrcode_draw_window_canvas(const uint8_t qrcode[], int canvas_w, int canvas_h) { + int qr_size = qrcodegen_getSize(qrcode); + int border = (int)USH_QRCODE_BORDER; + int side_modules; + int content_w; + int content_h; + int qr_area; + int module_pixels; + int side_pixels; + int qr_x; + int qr_y; + int y; + + if (qrcode == (const uint8_t *)0 || qr_size <= 0 || canvas_w <= 0 || canvas_h <= 0 || + canvas_w > (int)USH_QRCODE_CANVAS_MAX || canvas_h > (int)USH_QRCODE_CANVAS_MAX) { return 0; } - side_modules = (u64)qr_size + (u64)(USH_QRCODE_BORDER * 2U); - if (side_modules == 0ULL || side_modules > (u64)USH_QRCODE_MAX_MODULES) { + side_modules = qr_size + (border * 2); + if (side_modules <= 0 || side_modules > (int)USH_QRCODE_MAX_MODULES) { + return 0; + } + + content_w = canvas_w - (USH_QRCODE_PAD * 2); + content_h = canvas_h - USH_QRCODE_TITLE_H - USH_QRCODE_FOOTER_H - (USH_QRCODE_PAD * 2); + qr_area = (content_w < content_h) ? content_w : content_h; + module_pixels = qr_area / side_modules; + if (module_pixels <= 0) { return 0; } side_pixels = side_modules * module_pixels; - if (side_pixels == 0ULL || side_pixels > (u64)USH_QRCODE_CANVAS_MAX) { - return 0; - } + qr_x = (canvas_w - side_pixels) / 2; + qr_y = USH_QRCODE_TITLE_H + USH_QRCODE_PAD + ((content_h - side_pixels) / 2); - for (y = 0ULL; y < side_pixels; y++) { - for (x = 0ULL; x < side_pixels; x++) { - int qx = (int)(x / module_pixels) - (int)USH_QRCODE_BORDER; - int qy = (int)(y / module_pixels) - (int)USH_QRCODE_BORDER; - int dark = - (qx >= 0 && qy >= 0 && qx < qr_size && qy < qr_size && qrcodegen_getModule(qrcode, qx, qy)) ? 1 : 0; - ush_qrcode_canvas[y][x] = - (dark != 0) ? (unsigned int)USH_QRCODE_COLOR_DARK : (unsigned int)USH_QRCODE_COLOR_LIGHT; + ush_qrcode_fill_rect(canvas_w, canvas_h, 0, 0, canvas_w, canvas_h, USH_QRCODE_COLOR_DESKTOP); + ush_qrcode_fill_rect(canvas_w, canvas_h, 0, 0, canvas_w, USH_QRCODE_TITLE_H, USH_QRCODE_COLOR_TITLE); + ush_qrcode_fill_rect(canvas_w, canvas_h, canvas_w - USH_QRCODE_CLOSE_W, 0, USH_QRCODE_CLOSE_W, + USH_QRCODE_TITLE_H, USH_QRCODE_COLOR_CLOSE); + ush_qrcode_draw_text(canvas_w, canvas_h, 14, 10, "QRCODE", 1, USH_QRCODE_COLOR_LIGHT); + ush_qrcode_fill_rect(canvas_w, canvas_h, canvas_w - 28, 11, 14, 2, USH_QRCODE_COLOR_LIGHT); + ush_qrcode_fill_rect(canvas_w, canvas_h, canvas_w - 28, 22, 14, 2, USH_QRCODE_COLOR_LIGHT); + ush_qrcode_fill_rect(canvas_w, canvas_h, canvas_w - 22, 15, 2, 6, USH_QRCODE_COLOR_LIGHT); + + ush_qrcode_fill_rect(canvas_w, canvas_h, qr_x - 10, qr_y - 10, side_pixels + 20, side_pixels + 20, + USH_QRCODE_COLOR_PANEL); + ush_qrcode_stroke_rect(canvas_w, canvas_h, qr_x - 10, qr_y - 10, side_pixels + 20, side_pixels + 20, + USH_QRCODE_COLOR_BORDER); + ush_qrcode_fill_rect(canvas_w, canvas_h, qr_x, qr_y, side_pixels, side_pixels, USH_QRCODE_COLOR_LIGHT); + + for (y = -border; y < qr_size + border; y++) { + int x; + for (x = -border; x < qr_size + border; x++) { + int dark = (x >= 0 && y >= 0 && x < qr_size && y < qr_size && qrcodegen_getModule(qrcode, x, y)) ? 1 : 0; + if (dark != 0) { + int px = qr_x + ((x + border) * module_pixels); + int py = qr_y + ((y + border) * module_pixels); + ush_qrcode_fill_rect(canvas_w, canvas_h, px, py, module_pixels, module_pixels, + USH_QRCODE_COLOR_DARK); + } } } - *out_side_pixels = side_pixels; + ush_qrcode_draw_text(canvas_w, canvas_h, 22, canvas_h - 24, "PRESS Q TO CLOSE", 1, USH_QRCODE_COLOR_MUTED); return 1; } -static int ush_qrcode_emit_pixels(const uint8_t qrcode[]) { - int qr_size = qrcodegen_getSize(qrcode); - u64 side_modules; - cleonos_fb_info fb_info; - cleonos_fb_blit_req req; - u64 module_pixels_x; - u64 module_pixels_y; - u64 module_pixels; - u64 module_pixels_canvas_cap; - u64 side_pixels = 0ULL; - u64 draw_w; - u64 draw_h; +static int ush_qrcode_present_window(u64 window_id, int width, int height) { + cleonos_wm_present_req req; - if (qr_size <= 0) { + if (window_id == 0ULL || width <= 0 || height <= 0) { return 0; } - side_modules = (u64)qr_size + (u64)(USH_QRCODE_BORDER * 2U); - if (side_modules == 0ULL || side_modules > (u64)USH_QRCODE_MAX_MODULES) { + req.window_id = window_id; + req.pixels_ptr = (u64)(usize)&ush_qrcode_canvas[0][0]; + req.src_width = (u64)(unsigned int)width; + req.src_height = (u64)(unsigned int)height; + req.src_pitch_bytes = (u64)USH_QRCODE_CANVAS_MAX * 4ULL; + return (cleonos_sys_wm_present(&req) != 0ULL) ? 1 : 0; +} + +static int ush_qrcode_close_hit(int width, int local_x, int local_y) { + return (local_x >= width - USH_QRCODE_CLOSE_W && local_x < width && local_y >= 0 && + local_y < USH_QRCODE_TITLE_H) + ? 1 + : 0; +} + +static void ush_qrcode_window_loop(u64 window_id, int width, int height, int x, int y) { + int running = 1; + int dragging = 0; + int drag_offset_x = 0; + int drag_offset_y = 0; + + (void)height; + + while (running != 0) { + u64 budget = 0ULL; + int handled = 0; + + while (budget < 64ULL) { + cleonos_wm_event event; + ush_zero(&event, (u64)sizeof(event)); + if (cleonos_sys_wm_poll_event(window_id, &event) == 0ULL) { + break; + } + + handled = 1; + if (event.type == CLEONOS_WM_EVENT_KEY) { + if (event.arg0 == (u64)'q' || event.arg0 == (u64)'Q' || event.arg0 == 27ULL || + event.arg0 == 13ULL) { + running = 0; + break; + } + } else if (event.type == CLEONOS_WM_EVENT_MOUSE_BUTTON) { + u64 buttons = event.arg0; + u64 changed = event.arg1; + int local_x = ush_qrcode_u64_as_i32(event.arg2); + int local_y = ush_qrcode_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) { + if (left_down == 0) { + dragging = 0; + } else if (ush_qrcode_close_hit(width, local_x, local_y) != 0) { + running = 0; + break; + } else if (local_y >= 0 && local_y < USH_QRCODE_TITLE_H) { + dragging = 1; + drag_offset_x = local_x; + drag_offset_y = local_y; + } + } + } else if (event.type == CLEONOS_WM_EVENT_MOUSE_MOVE && dragging != 0) { + cleonos_wm_move_req move_req; + x = ush_qrcode_u64_as_i32(event.arg0) - drag_offset_x; + y = ush_qrcode_u64_as_i32(event.arg1) - drag_offset_y; + move_req.window_id = window_id; + move_req.x = (u64)(i64)x; + move_req.y = (u64)(i64)y; + (void)cleonos_sys_wm_move(&move_req); + } + + budget++; + } + + if (running == 0) { + break; + } + + if (handled != 0) { + (void)cleonos_sys_yield(); + } else { + (void)cleonos_sys_sleep_ticks(1ULL); + } + } +} + +static int ush_qrcode_emit_window(const uint8_t qrcode[]) { + int qr_size = qrcodegen_getSize(qrcode); + cleonos_fb_info fb_info; + cleonos_wm_create_req create_req; + u64 old_tty; + u64 window_id; + int win_x; + int win_y; + int win_w; + int win_h; + + if (qr_size <= 0) { return 0; } ush_zero(&fb_info, (u64)sizeof(fb_info)); if (cleonos_sys_fb_info(&fb_info) == 0ULL || fb_info.width == 0ULL || fb_info.height == 0ULL || fb_info.bpp != 32ULL) { - ush_writeln("qrcode: framebuffer unavailable, fallback to ascii"); + ush_writeln("qrcode: desktop unavailable, fallback to ascii"); ush_qrcode_emit_ascii(qrcode); return 1; } - module_pixels_x = fb_info.width / side_modules; - module_pixels_y = fb_info.height / side_modules; - module_pixels = (module_pixels_x < module_pixels_y) ? module_pixels_x : module_pixels_y; - - if (module_pixels == 0ULL) { - ush_writeln("qrcode: framebuffer too small"); + ush_qrcode_choose_window_geometry(&fb_info, &win_x, &win_y, &win_w, &win_h); + if (ush_qrcode_draw_window_canvas(qrcode, win_w, win_h) == 0) { + ush_writeln("qrcode: desktop window too small"); return 0; } - module_pixels_canvas_cap = (u64)USH_QRCODE_CANVAS_MAX / side_modules; - if (module_pixels_canvas_cap == 0ULL) { + old_tty = cleonos_sys_tty_active(); + if (old_tty != USH_QRCODE_TTY_DISPLAY) { + (void)cleonos_sys_tty_switch(USH_QRCODE_TTY_DISPLAY); + } + + create_req.x = (u64)(i64)win_x; + create_req.y = (u64)(i64)win_y; + create_req.width = (u64)(unsigned int)win_w; + create_req.height = (u64)(unsigned int)win_h; + create_req.flags = CLEONOS_WM_FLAG_TOPMOST; + window_id = cleonos_sys_wm_create(&create_req); + if (window_id == 0ULL) { + if (old_tty != USH_QRCODE_TTY_DISPLAY) { + (void)cleonos_sys_tty_switch(old_tty); + } + ush_writeln("qrcode: wm window create failed, fallback to ascii"); + ush_qrcode_emit_ascii(qrcode); + return 1; + } + + if (ush_qrcode_present_window(window_id, win_w, win_h) == 0) { + (void)cleonos_sys_wm_destroy(window_id); + if (old_tty != USH_QRCODE_TTY_DISPLAY) { + (void)cleonos_sys_tty_switch(old_tty); + } + ush_writeln("qrcode: wm present failed"); return 0; } - if (module_pixels > module_pixels_canvas_cap) { - module_pixels = module_pixels_canvas_cap; - } - - if (ush_qrcode_build_canvas(qrcode, module_pixels, &side_pixels) == 0) { - return 0; - } - - draw_w = side_pixels; - draw_h = side_pixels; - - req.pixels_ptr = (u64)(void *)&ush_qrcode_canvas[0][0]; - req.src_width = side_pixels; - req.src_height = side_pixels; - req.src_pitch_bytes = (u64)USH_QRCODE_CANVAS_MAX * 4ULL; - req.dst_x = (fb_info.width > draw_w) ? ((fb_info.width - draw_w) / 2ULL) : 0ULL; - req.dst_y = (fb_info.height > draw_h) ? ((fb_info.height - draw_h) / 2ULL) : 0ULL; - req.scale = 1ULL; - - (void)cleonos_sys_fb_clear(USH_QRCODE_COLOR_LIGHT); - if (cleonos_sys_fb_blit(&req) == 0ULL) { - ush_writeln("qrcode: framebuffer blit failed"); - return 0; + (void)cleonos_sys_wm_set_focus(window_id); + ush_qrcode_window_loop(window_id, win_w, win_h, win_x, win_y); + (void)cleonos_sys_wm_destroy(window_id); + if (old_tty != USH_QRCODE_TTY_DISPLAY) { + (void)cleonos_sys_tty_switch(old_tty); } return 1; @@ -335,7 +636,7 @@ static int ush_cmd_qrcode(const char *arg) { return 0; } - return ush_qrcode_emit_pixels(qrcode); + return ush_qrcode_emit_window(qrcode); } int cleonos_app_main(void) { diff --git a/cleonos/c/apps/uwm/uwm_app.c b/cleonos/c/apps/uwm/uwm_app.c index 749127e..c65e9ec 100644 --- a/cleonos/c/apps/uwm/uwm_app.c +++ b/cleonos/c/apps/uwm/uwm_app.c @@ -2,7 +2,7 @@ static void ush_uwm_usage(void) { ush_writeln("usage: uwm"); - ush_writeln("keys: q quit, tab focus, 1/2/3 restore, wasd/arrow move"); + ush_writeln("keys: q quit, tab focus, 1/2/3 open apps, wasd/arrow move"); ush_writeln("keys: m minimize, x close, t pin top, +/- resize"); ush_writeln("mouse: drag titlebar, resize bottom-right, use taskbar/start"); } @@ -150,7 +150,7 @@ int ush_uwm_prepare_session(ush_uwm_session *sess) { sess->screen_w = (int)fb.width; sess->screen_h = (int)fb.height; - sess->active_window = 0; + sess->active_window = -1; sess->drag_window = -1; sess->resize_window = -1; sess->tty_before = cleonos_sys_tty_active(); @@ -179,7 +179,7 @@ int ush_uwm_prepare_session(ush_uwm_session *sess) { } ush_uwm_init_window(&sess->windows[i], USH_UWM_KIND_APP, titles[i], subtitles[i], ush_uwm_clampi(x, 0, max_x), - ush_uwm_clampi(y, USH_UWM_TOP_CLAMP_Y, max_y), base_w, base_h, accents[i], 0, 0); + ush_uwm_clampi(y, USH_UWM_TOP_CLAMP_Y, max_y), base_w, base_h, accents[i], 0, 1); } ush_uwm_init_window(&sess->windows[USH_UWM_TASKBAR_INDEX], USH_UWM_KIND_TASKBAR, "TASKBAR", "", 0, @@ -200,9 +200,6 @@ int ush_uwm_prepare_session(ush_uwm_session *sess) { } int ush_uwm_start(ush_uwm_session *sess) { - int i; - int started = 0; - if (sess == (ush_uwm_session *)0) { return 0; } @@ -211,23 +208,7 @@ int ush_uwm_start(ush_uwm_session *sess) { return ush_uwm_fail(sess, "uwm: taskbar create failed"); } - for (i = 0; i < (int)USH_UWM_APP_COUNT; i++) { - if (ush_uwm_boot_window(sess, i) != 0) { - started++; - } - } - - if (started == 0) { - return ush_uwm_fail(sess, "uwm: app window create failed"); - } - - for (i = (int)USH_UWM_APP_COUNT - 1; i >= 0; i--) { - if (sess->windows[i].alive != 0) { - ush_uwm_set_active(sess, i); - break; - } - } - + ush_uwm_toggle_start(sess); ush_uwm_refresh_taskbar(sess); return 1; } diff --git a/cleonos/c/apps/uwm/uwm_events.c b/cleonos/c/apps/uwm/uwm_events.c index 9502870..9296bcb 100644 --- a/cleonos/c/apps/uwm/uwm_events.c +++ b/cleonos/c/apps/uwm/uwm_events.c @@ -1,5 +1,26 @@ #include "uwm.h" +#define USH_UWM_FILE_EXPLORER_PATH "/shell/uwm/file_explorer.elf" + +static int ush_uwm_launch_file_explorer(void) { + u64 pid = cleonos_sys_spawn_pathv(USH_UWM_FILE_EXPLORER_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; + } + + if (index == 0) { + (void)ush_uwm_launch_file_explorer(); + return; + } + + ush_uwm_restore_window(sess, index); +} + static int ush_uwm_hit_close(const ush_uwm_window *win, int x, int y) { return (win != (const ush_uwm_window *)0 && x >= win->w - USH_UWM_CONTROL_W && y >= 0 && y < USH_UWM_TITLE_H) ? 1 : 0; @@ -77,7 +98,7 @@ static void ush_uwm_handle_key_event(ush_uwm_session *sess, u64 key, int *runnin } if (key == (u64)'1' || key == (u64)'2' || key == (u64)'3') { - ush_uwm_restore_window(sess, (int)(key - (u64)'1')); + ush_uwm_launch_or_restore_app(sess, (int)(key - (u64)'1')); return; } @@ -154,7 +175,7 @@ static void ush_uwm_handle_taskbar_click(ush_uwm_session *sess, int local_x, int if (app->alive != 0 && app->minimized == 0 && sess->active_window == i) { ush_uwm_minimize_window(sess, i); } else { - ush_uwm_restore_window(sess, i); + ush_uwm_launch_or_restore_app(sess, i); } return; } @@ -178,8 +199,8 @@ static void ush_uwm_handle_start_click(ush_uwm_session *sess, int local_x, int l int y = 78 + (i * 44); if (local_x >= 66 && local_x < sess->windows[USH_UWM_START_INDEX].w - 16 && local_y >= y && local_y < y + 34) { - ush_uwm_restore_window(sess, i); ush_uwm_close_start(sess); + ush_uwm_launch_or_restore_app(sess, i); return; } } diff --git a/cleonos/c/apps/uwm/uwm_window.c b/cleonos/c/apps/uwm/uwm_window.c index 9f382d9..687b8ea 100644 --- a/cleonos/c/apps/uwm/uwm_window.c +++ b/cleonos/c/apps/uwm/uwm_window.c @@ -486,7 +486,7 @@ static void ush_uwm_render_taskbar(ush_uwm_session *sess) { if (app->closed != 0) { bg = 0x001F1F1FU; - fg = 0x007C7C7CU; + fg = 0x008F8F8FU; } else if (app->minimized != 0) { bg = 0x002F2F2FU; } else if (sess->active_window == i) { diff --git a/clks b/clks index 6b45f48..e56b4df 160000 --- a/clks +++ b/clks @@ -1 +1 @@ -Subproject commit 6b45f4805afbe732b8526f6d72beb381d42014ed +Subproject commit e56b4dfa85ed91f9d5f78a719fbdbe3e75704444 diff --git a/docs/syscall.md b/docs/syscall.md index 98f3149..d2795be 100644 --- a/docs/syscall.md +++ b/docs/syscall.md @@ -75,6 +75,12 @@ u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2); - `FS_MKDIR` / `FS_WRITE` / `FS_APPEND` / `FS_REMOVE` 仅允许 `/temp` 树下路径,或已挂载磁盘路径树下(默认挂载点通常为 `/temp/disk`)。 +UserSafeController(USC)危险 syscall 确认: + +- USC 拦截到危险 syscall 时仍支持“仅本次 / 本会话 / 永久 / 拒绝”四种结果。 +- 如果内核窗口管理器已初始化且桌面 TTY 正在前台,确认请求会以置顶桌面弹窗显示;可点击按钮,也可按 `1/O`、`2/S`、`3/P`、`N/Esc/Enter` 选择。 +- 如果当前不在桌面环境、键盘被禁用,或弹窗创建失败,则回退到原 TTY/串口确认流程。 + `/proc` 虚拟目录(由 syscall 层动态导出): - `/proc`:目录(children = `self`、`list`、以及全部 PID 名称)