mirror of
https://github.com/Leonmmcoset/cleonos.git
synced 2026-04-27 05:34:00 +00:00
桌面环境
This commit is contained in:
@@ -36,6 +36,11 @@ set(CLEONOS_QEMU_ACCEL_ARGS "")
|
||||
if(CLEONOS_QEMU_ENABLE_KVM)
|
||||
list(APPEND CLEONOS_QEMU_ACCEL_ARGS -enable-kvm -cpu host)
|
||||
endif()
|
||||
set(CLEONOS_QEMU_ENABLE_USB_TABLET ON CACHE BOOL "Enable QEMU USB tablet for absolute mouse pointer")
|
||||
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")
|
||||
if(EXISTS "${CLEONOS_SOURCE_DIR}/CMakeLists.txt")
|
||||
@@ -381,7 +386,7 @@ if(CLEONOS_ENABLE)
|
||||
"-DLOG_LEVEL=STEP"
|
||||
"-DLOG_TEXT=launching qemu run"
|
||||
-P "${CL_LOG_EMIT_SCRIPT}"
|
||||
COMMAND ${QEMU_X86_64} ${CLEONOS_QEMU_ACCEL_ARGS} -M pc -m 1024M -boot order=d -cdrom "${ISO_IMAGE}" -drive "file=${DISK_IMAGE},format=raw,if=none,id=clksdisk,media=disk" -device "ide-hd,drive=clksdisk,bus=ide.0" -netdev "user,id=clksnet0" -device "e1000,netdev=clksnet0" -serial stdio
|
||||
COMMAND ${QEMU_X86_64} ${CLEONOS_QEMU_ACCEL_ARGS} ${CLEONOS_QEMU_INPUT_ARGS} -M pc -m 1024M -boot order=d -cdrom "${ISO_IMAGE}" -drive "file=${DISK_IMAGE},format=raw,if=none,id=clksdisk,media=disk" -device "ide-hd,drive=clksdisk,bus=ide.0" -netdev "user,id=clksnet0" -device "e1000,netdev=clksnet0" -serial stdio
|
||||
DEPENDS iso disk-image
|
||||
USES_TERMINAL
|
||||
)
|
||||
@@ -392,7 +397,7 @@ if(CLEONOS_ENABLE)
|
||||
"-DLOG_LEVEL=STEP"
|
||||
"-DLOG_TEXT=launching qemu debug (-s -S)"
|
||||
-P "${CL_LOG_EMIT_SCRIPT}"
|
||||
COMMAND ${QEMU_X86_64} ${CLEONOS_QEMU_ACCEL_ARGS} -M pc -m 1024M -boot order=d -cdrom "${ISO_IMAGE}" -drive "file=${DISK_IMAGE},format=raw,if=none,id=clksdisk,media=disk" -device "ide-hd,drive=clksdisk,bus=ide.0" -netdev "user,id=clksnet0" -device "e1000,netdev=clksnet0" -serial stdio -s -S
|
||||
COMMAND ${QEMU_X86_64} ${CLEONOS_QEMU_ACCEL_ARGS} ${CLEONOS_QEMU_INPUT_ARGS} -M pc -m 1024M -boot order=d -cdrom "${ISO_IMAGE}" -drive "file=${DISK_IMAGE},format=raw,if=none,id=clksdisk,media=disk" -device "ide-hd,drive=clksdisk,bus=ide.0" -netdev "user,id=clksnet0" -device "e1000,netdev=clksnet0" -serial stdio -s -S
|
||||
DEPENDS iso disk-image
|
||||
USES_TERMINAL
|
||||
)
|
||||
|
||||
3
Makefile
3
Makefile
@@ -239,6 +239,9 @@ help:
|
||||
> $(Q)echo "QEMU KVM toggle:"
|
||||
> $(Q)echo " make run CMAKE_EXTRA_ARGS='-DCLEONOS_QEMU_ENABLE_KVM=ON' # force on"
|
||||
> $(Q)echo " make run CMAKE_EXTRA_ARGS='-DCLEONOS_QEMU_ENABLE_KVM=OFF' # force off"
|
||||
> $(Q)echo "QEMU USB tablet toggle:"
|
||||
> $(Q)echo " make run CMAKE_EXTRA_ARGS='-DCLEONOS_QEMU_ENABLE_USB_TABLET=ON' # absolute pointer"
|
||||
> $(Q)echo " make run CMAKE_EXTRA_ARGS='-DCLEONOS_QEMU_ENABLE_USB_TABLET=OFF' # disable tablet"
|
||||
> $(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'"
|
||||
|
||||
@@ -171,7 +171,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
|
||||
vim uwm
|
||||
)
|
||||
|
||||
foreach(SRC IN LISTS USER_APP_MAIN_SOURCES)
|
||||
|
||||
@@ -16,6 +16,7 @@ static int ush_cmd_help(void) {
|
||||
ush_writeln(" bmpview <file.bmp> [cols]");
|
||||
ush_writeln(" qrcode [--ecc <L|M|Q|H>] <text>");
|
||||
ush_writeln(" vim [file] (vim-like editor: normal/insert/:w/:q/:wq)");
|
||||
ush_writeln(" uwm (user-space window manager demo)");
|
||||
ush_writeln(" wavplay <file.wav> [steps] [ticks] / wavplay --stop");
|
||||
ush_writeln(" fastfetch [--plain]");
|
||||
ush_writeln(" doom [wad_path] (framebuffer bootstrap renderer)");
|
||||
|
||||
@@ -137,6 +137,8 @@ static int ush_cmd_help(void) {
|
||||
ush_writeln(" ansi / ansitest / color");
|
||||
ush_writeln(" bmpview <file.bmp> [cols]");
|
||||
ush_writeln(" qrcode [--ecc <L|M|Q|H>] <text>");
|
||||
ush_writeln(" vim [file] (vim-like editor)");
|
||||
ush_writeln(" uwm (user-space window manager demo)");
|
||||
ush_writeln(" wavplay <file.wav> [steps] [ticks] / wavplay --stop");
|
||||
ush_writeln(" fastfetch [--plain]");
|
||||
ush_writeln(" doom [wad_path] (framebuffer bootstrap renderer)");
|
||||
|
||||
734
cleonos/c/apps/uwm_main.c
Normal file
734
cleonos/c/apps/uwm_main.c
Normal file
@@ -0,0 +1,734 @@
|
||||
#include "cmd_runtime.h"
|
||||
|
||||
#define USH_UWM_WINDOW_COUNT 3ULL
|
||||
#define USH_UWM_TITLE_HEIGHT 18
|
||||
#define USH_UWM_BORDER_THICKNESS 2
|
||||
#define USH_UWM_WINDOW_MIN_W 120
|
||||
#define USH_UWM_WINDOW_MIN_H 80
|
||||
#define USH_UWM_STARTUP_KEY_DRAIN_MAX 256ULL
|
||||
#define USH_UWM_BLIT_MAX_FAIL_STREAK 30ULL
|
||||
|
||||
#define USH_UWM_KEY_LEFT 1
|
||||
#define USH_UWM_KEY_RIGHT 2
|
||||
#define USH_UWM_KEY_UP 3
|
||||
#define USH_UWM_KEY_DOWN 4
|
||||
|
||||
#define USH_UWM_COLOR_BG 0x00161D2BUL
|
||||
#define USH_UWM_COLOR_PANEL 0x00263352UL
|
||||
#define USH_UWM_COLOR_CURSOR 0x00FFFFFFUL
|
||||
#define USH_UWM_COLOR_CURSOR_SHADOW 0x00000000UL
|
||||
#define USH_UWM_COLOR_BORDER_ACTIVE 0x00FFD166UL
|
||||
#define USH_UWM_COLOR_BORDER_INACTIVE 0x004A566EL
|
||||
#define USH_UWM_COLOR_TITLE_ACTIVE 0x005A77B7UL
|
||||
#define USH_UWM_COLOR_TITLE_INACTIVE 0x00394C78UL
|
||||
|
||||
typedef unsigned int ush_uwm_u32;
|
||||
|
||||
typedef struct ush_uwm_window {
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
int h;
|
||||
ush_uwm_u32 body_color;
|
||||
} ush_uwm_window;
|
||||
|
||||
typedef struct ush_uwm_runtime {
|
||||
cleonos_fb_info fb;
|
||||
ush_uwm_u32 *canvas;
|
||||
u64 canvas_pixels;
|
||||
int screen_w;
|
||||
int screen_h;
|
||||
int mouse_x;
|
||||
int mouse_y;
|
||||
u64 mouse_buttons;
|
||||
int mouse_ready;
|
||||
} ush_uwm_runtime;
|
||||
|
||||
static int ush_uwm_window_index_valid(int index) {
|
||||
return (index >= 0 && index < (int)USH_UWM_WINDOW_COUNT) ? 1 : 0;
|
||||
}
|
||||
|
||||
static void ush_uwm_z_order_sanitize(int *z_order) {
|
||||
int seen[USH_UWM_WINDOW_COUNT];
|
||||
int fill_index = 0;
|
||||
u64 i;
|
||||
|
||||
if (z_order == (int *)0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0ULL; i < USH_UWM_WINDOW_COUNT; i++) {
|
||||
seen[i] = 0;
|
||||
}
|
||||
|
||||
for (i = 0ULL; i < USH_UWM_WINDOW_COUNT; i++) {
|
||||
int value = z_order[i];
|
||||
|
||||
if (ush_uwm_window_index_valid(value) != 0 && seen[(u64)value] == 0) {
|
||||
seen[(u64)value] = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
while (fill_index < (int)USH_UWM_WINDOW_COUNT && seen[(u64)fill_index] != 0) {
|
||||
fill_index++;
|
||||
}
|
||||
|
||||
if (fill_index >= (int)USH_UWM_WINDOW_COUNT) {
|
||||
fill_index = (int)USH_UWM_WINDOW_COUNT - 1;
|
||||
}
|
||||
|
||||
z_order[i] = fill_index;
|
||||
seen[(u64)fill_index] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int ush_uwm_runtime_sane(const ush_uwm_runtime *rt) {
|
||||
u64 min_pixels;
|
||||
|
||||
if (rt == (const ush_uwm_runtime *)0 || rt->canvas == (ush_uwm_u32 *)0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rt->screen_w <= 0 || rt->screen_h <= 0 || rt->screen_w > 4096 || rt->screen_h > 4096) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
min_pixels = (u64)(unsigned int)rt->screen_w * (u64)(unsigned int)rt->screen_h;
|
||||
if (rt->canvas_pixels < min_pixels) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ush_uwm_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_uwm_u64_to_i32_sat(u64 value) {
|
||||
if (value > 0x7FFFFFFFULL) {
|
||||
return 0x7FFFFFFF;
|
||||
}
|
||||
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
static void ush_uwm_fill_rect(ush_uwm_runtime *rt, int x, int y, int w, int h, ush_uwm_u32 color) {
|
||||
int left;
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
int row;
|
||||
|
||||
if (ush_uwm_runtime_sane(rt) == 0 || w <= 0 || h <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
left = x;
|
||||
top = y;
|
||||
right = x + w;
|
||||
bottom = y + h;
|
||||
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
}
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
}
|
||||
if (right > rt->screen_w) {
|
||||
right = rt->screen_w;
|
||||
}
|
||||
if (bottom > rt->screen_h) {
|
||||
bottom = rt->screen_h;
|
||||
}
|
||||
|
||||
if (left >= right || top >= bottom) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (row = top; row < bottom; row++) {
|
||||
u64 offset = ((u64)(unsigned int)row * (u64)(unsigned int)rt->screen_w) + (u64)(unsigned int)left;
|
||||
u64 max_pixels = rt->canvas_pixels;
|
||||
int col;
|
||||
|
||||
if (offset >= max_pixels) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (col = left; col < right; col++) {
|
||||
if (offset >= max_pixels) {
|
||||
break;
|
||||
}
|
||||
rt->canvas[offset++] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ush_uwm_draw_window(ush_uwm_runtime *rt, const ush_uwm_window *win, int active) {
|
||||
ush_uwm_u32 border_color = (active != 0) ? USH_UWM_COLOR_BORDER_ACTIVE : USH_UWM_COLOR_BORDER_INACTIVE;
|
||||
ush_uwm_u32 title_color = (active != 0) ? USH_UWM_COLOR_TITLE_ACTIVE : USH_UWM_COLOR_TITLE_INACTIVE;
|
||||
int title_h;
|
||||
|
||||
if (rt == (ush_uwm_runtime *)0 || win == (const ush_uwm_window *)0) {
|
||||
return;
|
||||
}
|
||||
|
||||
title_h = USH_UWM_TITLE_HEIGHT;
|
||||
if (title_h > win->h) {
|
||||
title_h = win->h;
|
||||
}
|
||||
|
||||
ush_uwm_fill_rect(rt, win->x, win->y, win->w, win->h, border_color);
|
||||
ush_uwm_fill_rect(rt, win->x + USH_UWM_BORDER_THICKNESS, win->y + USH_UWM_BORDER_THICKNESS,
|
||||
win->w - (USH_UWM_BORDER_THICKNESS * 2), title_h - USH_UWM_BORDER_THICKNESS, title_color);
|
||||
ush_uwm_fill_rect(rt, win->x + USH_UWM_BORDER_THICKNESS, win->y + title_h, win->w - (USH_UWM_BORDER_THICKNESS * 2),
|
||||
win->h - title_h - USH_UWM_BORDER_THICKNESS, win->body_color);
|
||||
|
||||
ush_uwm_fill_rect(rt, win->x + win->w - 16, win->y + 4, 10, 10, 0x00E76F51UL);
|
||||
}
|
||||
|
||||
static void ush_uwm_draw_cursor(ush_uwm_runtime *rt, int x, int y) {
|
||||
int i;
|
||||
|
||||
if (rt == (ush_uwm_runtime *)0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = -6; i <= 6; i++) {
|
||||
ush_uwm_fill_rect(rt, x + i, y, 1, 1, (i == 0) ? USH_UWM_COLOR_CURSOR : USH_UWM_COLOR_CURSOR_SHADOW);
|
||||
ush_uwm_fill_rect(rt, x, y + i, 1, 1, (i == 0) ? USH_UWM_COLOR_CURSOR : USH_UWM_COLOR_CURSOR_SHADOW);
|
||||
}
|
||||
|
||||
ush_uwm_fill_rect(rt, x - 1, y - 1, 3, 3, USH_UWM_COLOR_CURSOR);
|
||||
}
|
||||
|
||||
static void ush_uwm_draw_background(ush_uwm_runtime *rt) {
|
||||
int y;
|
||||
|
||||
if (rt == (ush_uwm_runtime *)0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ush_uwm_fill_rect(rt, 0, 0, rt->screen_w, rt->screen_h, USH_UWM_COLOR_BG);
|
||||
ush_uwm_fill_rect(rt, 0, 0, rt->screen_w, 30, USH_UWM_COLOR_PANEL);
|
||||
|
||||
for (y = 60; y < rt->screen_h; y += 40) {
|
||||
ush_uwm_fill_rect(rt, 0, y, rt->screen_w, 1, 0x001E283BUL);
|
||||
}
|
||||
}
|
||||
|
||||
static int ush_uwm_window_hit_title(const ush_uwm_window *win, int x, int y) {
|
||||
int title_h;
|
||||
|
||||
if (win == (const ush_uwm_window *)0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
title_h = USH_UWM_TITLE_HEIGHT;
|
||||
if (title_h > win->h) {
|
||||
title_h = win->h;
|
||||
}
|
||||
|
||||
if (x < win->x || y < win->y) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (x >= win->x + win->w || y >= win->y + title_h) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void ush_uwm_bring_to_front(int *z_order, u64 count, int window_index) {
|
||||
u64 i;
|
||||
u64 pos = count;
|
||||
|
||||
if (z_order == (int *)0 || count == 0ULL || ush_uwm_window_index_valid(window_index) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0ULL; i < count; i++) {
|
||||
if (z_order[i] == window_index) {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos >= count || pos + 1ULL >= count) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = pos; i + 1ULL < count; i++) {
|
||||
z_order[i] = z_order[i + 1ULL];
|
||||
}
|
||||
z_order[count - 1ULL] = window_index;
|
||||
}
|
||||
|
||||
static int ush_uwm_cycle_focus(const int *z_order, u64 count, int active_window) {
|
||||
u64 i;
|
||||
u64 pos = 0ULL;
|
||||
|
||||
if (z_order == (const int *)0 || count == 0ULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ush_uwm_window_index_valid(active_window) == 0) {
|
||||
return z_order[count - 1ULL];
|
||||
}
|
||||
|
||||
for (i = 0ULL; i < count; i++) {
|
||||
if (z_order[i] == active_window) {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return z_order[(pos + 1ULL) % count];
|
||||
}
|
||||
|
||||
static int ush_uwm_poll_mouse(ush_uwm_runtime *rt) {
|
||||
cleonos_mouse_state ms;
|
||||
int max_x;
|
||||
int max_y;
|
||||
|
||||
if (rt == (ush_uwm_runtime *)0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ush_zero(&ms, (u64)sizeof(ms));
|
||||
if (cleonos_sys_mouse_state(&ms) == 0ULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
rt->mouse_buttons = ms.buttons;
|
||||
rt->mouse_ready = (ms.ready != 0ULL) ? 1 : 0;
|
||||
|
||||
if (rt->screen_w <= 0 || rt->screen_h <= 0) {
|
||||
return rt->mouse_ready;
|
||||
}
|
||||
|
||||
max_x = rt->screen_w - 1;
|
||||
max_y = rt->screen_h - 1;
|
||||
|
||||
rt->mouse_x = ush_uwm_clampi(ush_uwm_u64_to_i32_sat(ms.x), 0, max_x);
|
||||
rt->mouse_y = ush_uwm_clampi(ush_uwm_u64_to_i32_sat(ms.y), 0, max_y);
|
||||
return rt->mouse_ready;
|
||||
}
|
||||
|
||||
static int ush_uwm_present(ush_uwm_runtime *rt) {
|
||||
cleonos_fb_blit_req req;
|
||||
|
||||
if (ush_uwm_runtime_sane(rt) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
req.pixels_ptr = (u64)(usize)rt->canvas;
|
||||
req.src_width = (u64)(unsigned int)rt->screen_w;
|
||||
req.src_height = (u64)(unsigned int)rt->screen_h;
|
||||
req.src_pitch_bytes = (u64)(unsigned int)rt->screen_w * 4ULL;
|
||||
req.dst_x = 0ULL;
|
||||
req.dst_y = 0ULL;
|
||||
req.scale = 1ULL;
|
||||
return (cleonos_sys_fb_blit(&req) != 0ULL) ? 1 : 0;
|
||||
}
|
||||
|
||||
static u64 ush_uwm_drain_startup_keys(void) {
|
||||
u64 drained = 0ULL;
|
||||
|
||||
while (drained < USH_UWM_STARTUP_KEY_DRAIN_MAX) {
|
||||
u64 key = cleonos_sys_kbd_get_char();
|
||||
if (key == (u64)-1) {
|
||||
break;
|
||||
}
|
||||
drained++;
|
||||
}
|
||||
|
||||
return drained;
|
||||
}
|
||||
|
||||
/* return: 0 fail, 1 ok, 2 help */
|
||||
static int ush_uwm_parse_args(const char *arg) {
|
||||
char first[USH_PATH_MAX];
|
||||
const char *rest = "";
|
||||
|
||||
if (arg == (const char *)0 || arg[0] == '\0') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ush_split_first_and_rest(arg, first, (u64)sizeof(first), &rest) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rest != (const char *)0 && rest[0] != '\0') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ush_streq(first, "--help") != 0 || ush_streq(first, "-h") != 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ush_uwm_usage(void) {
|
||||
ush_writeln("usage: uwm");
|
||||
ush_writeln("keys: q quit, tab cycle focus, wasd move active window");
|
||||
ush_writeln("mouse: drag window by title bar");
|
||||
}
|
||||
|
||||
static int ush_cmd_uwm(const char *arg) {
|
||||
ush_uwm_runtime rt;
|
||||
ush_uwm_window windows[USH_UWM_WINDOW_COUNT];
|
||||
int z_order[USH_UWM_WINDOW_COUNT];
|
||||
int active_window = 0;
|
||||
int dragging = 0;
|
||||
int drag_window = 0;
|
||||
int drag_offset_x = 0;
|
||||
int drag_offset_y = 0;
|
||||
u64 prev_buttons = 0ULL;
|
||||
u64 blit_fail_streak = 0ULL;
|
||||
int running = 1;
|
||||
int parse_result;
|
||||
u64 drained_keys;
|
||||
|
||||
parse_result = ush_uwm_parse_args(arg);
|
||||
if (parse_result == 2) {
|
||||
ush_uwm_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (parse_result == 0) {
|
||||
ush_uwm_usage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
ush_zero(&rt, (u64)sizeof(rt));
|
||||
|
||||
if (cleonos_sys_fb_info(&rt.fb) == 0ULL || rt.fb.width == 0ULL || rt.fb.height == 0ULL || rt.fb.bpp != 32ULL) {
|
||||
ush_writeln("uwm: framebuffer unavailable");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rt.fb.width > 4096ULL || rt.fb.height > 4096ULL) {
|
||||
ush_writeln("uwm: framebuffer too large");
|
||||
return 0;
|
||||
}
|
||||
|
||||
rt.screen_w = (int)rt.fb.width;
|
||||
rt.screen_h = (int)rt.fb.height;
|
||||
rt.canvas_pixels = rt.fb.width * rt.fb.height;
|
||||
rt.canvas = (ush_uwm_u32 *)malloc((size_t)(rt.canvas_pixels * 4ULL));
|
||||
if (rt.canvas == (ush_uwm_u32 *)0) {
|
||||
ush_writeln("uwm: framebuffer buffer alloc failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
windows[0].x = 50;
|
||||
windows[0].y = 70;
|
||||
windows[0].w = rt.screen_w / 3;
|
||||
windows[0].h = rt.screen_h / 3;
|
||||
windows[0].body_color = 0x002C3E6BUL;
|
||||
|
||||
windows[1].x = 180;
|
||||
windows[1].y = 140;
|
||||
windows[1].w = rt.screen_w / 3;
|
||||
windows[1].h = rt.screen_h / 3;
|
||||
windows[1].body_color = 0x003F5B7EUL;
|
||||
|
||||
windows[2].x = 310;
|
||||
windows[2].y = 210;
|
||||
windows[2].w = rt.screen_w / 3;
|
||||
windows[2].h = rt.screen_h / 3;
|
||||
windows[2].body_color = 0x00506F89UL;
|
||||
|
||||
z_order[0] = 0;
|
||||
z_order[1] = 1;
|
||||
z_order[2] = 2;
|
||||
active_window = 2;
|
||||
|
||||
{
|
||||
u64 i;
|
||||
for (i = 0ULL; i < USH_UWM_WINDOW_COUNT; i++) {
|
||||
int max_w = rt.screen_w - 20;
|
||||
int max_h = rt.screen_h - 40;
|
||||
if (max_w < USH_UWM_WINDOW_MIN_W) {
|
||||
max_w = USH_UWM_WINDOW_MIN_W;
|
||||
}
|
||||
if (max_h < USH_UWM_WINDOW_MIN_H) {
|
||||
max_h = USH_UWM_WINDOW_MIN_H;
|
||||
}
|
||||
|
||||
if (windows[i].w < USH_UWM_WINDOW_MIN_W) {
|
||||
windows[i].w = USH_UWM_WINDOW_MIN_W;
|
||||
}
|
||||
if (windows[i].h < USH_UWM_WINDOW_MIN_H) {
|
||||
windows[i].h = USH_UWM_WINDOW_MIN_H;
|
||||
}
|
||||
if (windows[i].w > max_w) {
|
||||
windows[i].w = max_w;
|
||||
}
|
||||
if (windows[i].h > max_h) {
|
||||
windows[i].h = max_h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rt.mouse_x = rt.screen_w / 2;
|
||||
rt.mouse_y = rt.screen_h / 2;
|
||||
rt.mouse_buttons = 0ULL;
|
||||
rt.mouse_ready = 0;
|
||||
|
||||
ush_writeln("uwm: enter user window manager");
|
||||
ush_writeln("uwm: q quit, tab focus, wasd move, drag title bar with mouse");
|
||||
|
||||
(void)cleonos_sys_fb_clear(USH_UWM_COLOR_BG);
|
||||
drained_keys = ush_uwm_drain_startup_keys();
|
||||
if (drained_keys > 0ULL) {
|
||||
ush_writeln("uwm: stale keyboard input discarded");
|
||||
}
|
||||
|
||||
while (running != 0) {
|
||||
u64 key;
|
||||
int left_down;
|
||||
int left_press;
|
||||
int left_release;
|
||||
|
||||
if (ush_uwm_runtime_sane(&rt) == 0) {
|
||||
free(rt.canvas);
|
||||
ush_writeln("uwm: runtime state corrupted");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ush_uwm_z_order_sanitize(z_order);
|
||||
if (ush_uwm_window_index_valid(active_window) == 0) {
|
||||
active_window = z_order[USH_UWM_WINDOW_COUNT - 1ULL];
|
||||
}
|
||||
if (ush_uwm_window_index_valid(drag_window) == 0) {
|
||||
dragging = 0;
|
||||
drag_window = active_window;
|
||||
}
|
||||
|
||||
(void)ush_uwm_poll_mouse(&rt);
|
||||
|
||||
for (;;) {
|
||||
key = cleonos_sys_kbd_get_char();
|
||||
if (key == (u64)-1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (key == (u64)'q' || key == (u64)'Q') {
|
||||
running = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (key == 27ULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == (u64)'\t') {
|
||||
active_window = ush_uwm_cycle_focus(z_order, USH_UWM_WINDOW_COUNT, active_window);
|
||||
if (ush_uwm_window_index_valid(active_window) != 0) {
|
||||
ush_uwm_bring_to_front(z_order, USH_UWM_WINDOW_COUNT, active_window);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ush_uwm_window_index_valid(active_window) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == (u64)'a' || key == (u64)'A') {
|
||||
windows[active_window].x -= 8;
|
||||
} else if (key == (u64)'d' || key == (u64)'D') {
|
||||
windows[active_window].x += 8;
|
||||
} else if (key == (u64)'w' || key == (u64)'W') {
|
||||
windows[active_window].y -= 8;
|
||||
} else if (key == (u64)'s' || key == (u64)'S') {
|
||||
windows[active_window].y += 8;
|
||||
} else if (key == USH_UWM_KEY_LEFT) {
|
||||
rt.mouse_x -= 10;
|
||||
} else if (key == USH_UWM_KEY_RIGHT) {
|
||||
rt.mouse_x += 10;
|
||||
} else if (key == USH_UWM_KEY_UP) {
|
||||
rt.mouse_y -= 10;
|
||||
} else if (key == USH_UWM_KEY_DOWN) {
|
||||
rt.mouse_y += 10;
|
||||
}
|
||||
}
|
||||
|
||||
if (running == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
left_down = ((rt.mouse_buttons & 0x01ULL) != 0ULL) ? 1 : 0;
|
||||
left_press = (left_down != 0 && (prev_buttons & 0x01ULL) == 0ULL) ? 1 : 0;
|
||||
left_release = (left_down == 0 && (prev_buttons & 0x01ULL) != 0ULL) ? 1 : 0;
|
||||
|
||||
if (left_press != 0) {
|
||||
int hit = -1;
|
||||
i64 z;
|
||||
|
||||
for (z = (i64)USH_UWM_WINDOW_COUNT - 1LL; z >= 0LL; z--) {
|
||||
int win_index = z_order[(u64)z];
|
||||
if (ush_uwm_window_index_valid(win_index) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (ush_uwm_window_hit_title(&windows[win_index], rt.mouse_x, rt.mouse_y) != 0) {
|
||||
hit = win_index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hit >= 0) {
|
||||
active_window = hit;
|
||||
ush_uwm_bring_to_front(z_order, USH_UWM_WINDOW_COUNT, hit);
|
||||
dragging = 1;
|
||||
drag_window = hit;
|
||||
drag_offset_x = rt.mouse_x - windows[hit].x;
|
||||
drag_offset_y = rt.mouse_y - windows[hit].y;
|
||||
}
|
||||
}
|
||||
|
||||
if (left_release != 0) {
|
||||
dragging = 0;
|
||||
}
|
||||
|
||||
if (dragging != 0 && left_down != 0 && ush_uwm_window_index_valid(drag_window) != 0) {
|
||||
int new_x = rt.mouse_x - drag_offset_x;
|
||||
int new_y = rt.mouse_y - drag_offset_y;
|
||||
int max_x = rt.screen_w - windows[drag_window].w;
|
||||
int max_y = rt.screen_h - windows[drag_window].h;
|
||||
int min_y = (max_y >= 30) ? 30 : 0;
|
||||
|
||||
if (max_x < 0) {
|
||||
max_x = 0;
|
||||
}
|
||||
if (max_y < 0) {
|
||||
max_y = 0;
|
||||
}
|
||||
|
||||
windows[drag_window].x = ush_uwm_clampi(new_x, 0, max_x);
|
||||
windows[drag_window].y = ush_uwm_clampi(new_y, min_y, max_y);
|
||||
}
|
||||
|
||||
if (ush_uwm_window_index_valid(active_window) != 0) {
|
||||
int max_x = rt.screen_w - windows[active_window].w;
|
||||
int max_y = rt.screen_h - windows[active_window].h;
|
||||
int min_y = (max_y >= 30) ? 30 : 0;
|
||||
|
||||
if (max_x < 0) {
|
||||
max_x = 0;
|
||||
}
|
||||
if (max_y < 0) {
|
||||
max_y = 0;
|
||||
}
|
||||
|
||||
windows[active_window].x = ush_uwm_clampi(windows[active_window].x, 0, max_x);
|
||||
windows[active_window].y = ush_uwm_clampi(windows[active_window].y, min_y, max_y);
|
||||
}
|
||||
rt.mouse_x = ush_uwm_clampi(rt.mouse_x, 0, rt.screen_w - 1);
|
||||
rt.mouse_y = ush_uwm_clampi(rt.mouse_y, 0, rt.screen_h - 1);
|
||||
|
||||
ush_uwm_draw_background(&rt);
|
||||
{
|
||||
u64 i;
|
||||
for (i = 0ULL; i < USH_UWM_WINDOW_COUNT; i++) {
|
||||
int win_index = z_order[i];
|
||||
if (ush_uwm_window_index_valid(win_index) == 0) {
|
||||
continue;
|
||||
}
|
||||
int is_active = (win_index == active_window) ? 1 : 0;
|
||||
ush_uwm_draw_window(&rt, &windows[win_index], is_active);
|
||||
}
|
||||
}
|
||||
ush_uwm_draw_cursor(&rt, rt.mouse_x, rt.mouse_y);
|
||||
|
||||
if (ush_uwm_present(&rt) == 0) {
|
||||
blit_fail_streak++;
|
||||
if (blit_fail_streak == 1ULL) {
|
||||
ush_writeln("uwm: framebuffer blit failed, retrying");
|
||||
}
|
||||
|
||||
if (blit_fail_streak >= USH_UWM_BLIT_MAX_FAIL_STREAK) {
|
||||
free(rt.canvas);
|
||||
ush_writeln("uwm: framebuffer blit failed too many times");
|
||||
return 0;
|
||||
}
|
||||
|
||||
(void)cleonos_sys_sleep_ticks(1ULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
blit_fail_streak = 0ULL;
|
||||
|
||||
prev_buttons = rt.mouse_buttons;
|
||||
(void)cleonos_sys_sleep_ticks(1ULL);
|
||||
}
|
||||
|
||||
(void)cleonos_sys_fb_clear(0x00000000ULL);
|
||||
free(rt.canvas);
|
||||
ush_writeln("uwm: exit");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int cleonos_app_main(int argc, char **argv, char **envp) {
|
||||
ush_cmd_ctx ctx;
|
||||
ush_cmd_ret ret;
|
||||
ush_state sh;
|
||||
char initial_cwd[USH_PATH_MAX];
|
||||
char arg_buf[USH_ARG_MAX];
|
||||
int has_context = 0;
|
||||
int success = 0;
|
||||
const char *arg = "";
|
||||
|
||||
(void)envp;
|
||||
|
||||
ush_zero(&ctx, (u64)sizeof(ctx));
|
||||
ush_zero(&ret, (u64)sizeof(ret));
|
||||
ush_zero(arg_buf, (u64)sizeof(arg_buf));
|
||||
ush_init_state(&sh);
|
||||
ush_copy(initial_cwd, (u64)sizeof(initial_cwd), sh.cwd);
|
||||
|
||||
if (ush_command_ctx_read(&ctx) != 0) {
|
||||
if (ctx.cmd[0] != '\0' && ush_streq(ctx.cmd, "uwm") != 0) {
|
||||
has_context = 1;
|
||||
arg = ctx.arg;
|
||||
if (ctx.cwd[0] == '/') {
|
||||
ush_copy(sh.cwd, (u64)sizeof(sh.cwd), ctx.cwd);
|
||||
ush_copy(initial_cwd, (u64)sizeof(initial_cwd), sh.cwd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_context == 0 && argc > 1 && argv != (char **)0 && argv[1] != (char *)0) {
|
||||
ush_copy(arg_buf, (u64)sizeof(arg_buf), argv[1]);
|
||||
arg = arg_buf;
|
||||
}
|
||||
|
||||
success = ush_cmd_uwm(arg);
|
||||
|
||||
if (has_context != 0) {
|
||||
if (ush_streq(sh.cwd, initial_cwd) == 0) {
|
||||
ret.flags |= USH_CMD_RET_FLAG_CWD;
|
||||
ush_copy(ret.cwd, (u64)sizeof(ret.cwd), sh.cwd);
|
||||
}
|
||||
|
||||
if (sh.exit_requested != 0) {
|
||||
ret.flags |= USH_CMD_RET_FLAG_EXIT;
|
||||
ret.exit_code = sh.exit_code;
|
||||
}
|
||||
|
||||
(void)ush_command_ret_write(&ret);
|
||||
}
|
||||
|
||||
return (success != 0) ? 0 : 1;
|
||||
}
|
||||
@@ -50,6 +50,14 @@ typedef struct cleonos_fb_info {
|
||||
u64 bpp;
|
||||
} cleonos_fb_info;
|
||||
|
||||
typedef struct cleonos_mouse_state {
|
||||
u64 x;
|
||||
u64 y;
|
||||
u64 buttons;
|
||||
u64 packet_count;
|
||||
u64 ready;
|
||||
} cleonos_mouse_state;
|
||||
|
||||
typedef struct cleonos_fb_blit_req {
|
||||
u64 pixels_ptr;
|
||||
u64 src_width;
|
||||
@@ -202,6 +210,7 @@ typedef struct cleonos_net_tcp_recv_req {
|
||||
#define CLEONOS_SYSCALL_NET_TCP_SEND 104ULL
|
||||
#define CLEONOS_SYSCALL_NET_TCP_RECV 105ULL
|
||||
#define CLEONOS_SYSCALL_NET_TCP_CLOSE 106ULL
|
||||
#define CLEONOS_SYSCALL_MOUSE_STATE 107ULL
|
||||
|
||||
u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2);
|
||||
u64 cleonos_sys_log_write(const char *message, u64 length);
|
||||
@@ -311,5 +320,6 @@ u64 cleonos_sys_net_tcp_connect(const cleonos_net_tcp_connect_req *req);
|
||||
u64 cleonos_sys_net_tcp_send(const cleonos_net_tcp_send_req *req);
|
||||
u64 cleonos_sys_net_tcp_recv(cleonos_net_tcp_recv_req *req);
|
||||
u64 cleonos_sys_net_tcp_close(u64 poll_budget);
|
||||
u64 cleonos_sys_mouse_state(cleonos_mouse_state *out_state);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -459,3 +459,7 @@ u64 cleonos_sys_net_tcp_recv(cleonos_net_tcp_recv_req *req) {
|
||||
u64 cleonos_sys_net_tcp_close(u64 poll_budget) {
|
||||
return cleonos_syscall(CLEONOS_SYSCALL_NET_TCP_CLOSE, poll_budget, 0ULL, 0ULL);
|
||||
}
|
||||
|
||||
u64 cleonos_sys_mouse_state(cleonos_mouse_state *out_state) {
|
||||
return cleonos_syscall(CLEONOS_SYSCALL_MOUSE_STATE, (u64)out_state, 0ULL, 0ULL);
|
||||
}
|
||||
|
||||
2
clks
2
clks
Submodule clks updated: 6ac3f4e696...96a0da2b1d
@@ -26,6 +26,7 @@
|
||||
- `stage27.md`
|
||||
- `stage28.md`
|
||||
- `stage29.md`
|
||||
- `stage30.md`
|
||||
|
||||
- `syscall.md` (syscall ABI reference)
|
||||
|
||||
|
||||
52
docs/stage30.md
Normal file
52
docs/stage30.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Stage 30 - User-Space Window Manager (UWM) + Mouse Syscall
|
||||
|
||||
## Goal
|
||||
- Add a minimal user-space desktop environment entrypoint.
|
||||
- Provide a simple window manager demo (`uwm`) with focus, dragging, and rendering in framebuffer.
|
||||
- Expose mouse snapshot data to user-space through a dedicated syscall.
|
||||
|
||||
## Implementation
|
||||
- New syscall:
|
||||
- Added `MOUSE_STATE` (`id=107`) in kernel/user syscall headers.
|
||||
- Kernel side (`clks/kernel/runtime/syscall.c`) now exports mouse snapshot:
|
||||
- fields: `x`, `y`, `buttons`, `packet_count`, `ready`.
|
||||
- User wrapper added: `cleonos_sys_mouse_state()`.
|
||||
- New user app:
|
||||
- Added `cleonos/c/apps/uwm_main.c`.
|
||||
- Features:
|
||||
- Desktop background rendering.
|
||||
- Multiple windows with z-order.
|
||||
- Active window focus + border highlight.
|
||||
- Drag by title bar (left mouse).
|
||||
- Keyboard fallback controls (`Tab`, `W/A/S/D`, `Q/Esc`).
|
||||
- Build integration:
|
||||
- Added `uwm` into shell command app set in `cleonos/CMakeLists.txt`.
|
||||
- Updated shell help text and standalone `help` app output.
|
||||
- QEMU run/debug update:
|
||||
- Added optional absolute pointer device by default:
|
||||
- `-usb -device usb-tablet`
|
||||
- New CMake option:
|
||||
- `CLEONOS_QEMU_ENABLE_USB_TABLET=ON` (default).
|
||||
|
||||
## Acceptance Criteria
|
||||
- `uwm` can be started from user shell.
|
||||
- Framebuffer window demo draws multiple windows and supports dragging.
|
||||
- Mouse state is retrievable from user-space via syscall `107`.
|
||||
- `make run`/`make debug` use USB tablet by default for smoother pointer interaction.
|
||||
|
||||
## Build Targets
|
||||
- `make userapps`
|
||||
- `make iso`
|
||||
- `make run`
|
||||
|
||||
## QEMU Command
|
||||
- `make run`
|
||||
|
||||
## Debug Notes
|
||||
- If `uwm` reports framebuffer unavailable:
|
||||
- verify `FB_INFO` syscall is enabled and framebuffer bpp is `32`.
|
||||
- If pointer behavior is relative/jumpy:
|
||||
- keep `CLEONOS_QEMU_ENABLE_USB_TABLET=ON`.
|
||||
- for manual QEMU invocation, include `-usb -device usb-tablet`.
|
||||
- If mouse syscall returns `ready=0`:
|
||||
- check boot log for PS/2 mouse init status under `[INFO][MOUSE]`.
|
||||
@@ -83,7 +83,7 @@ u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2);
|
||||
- `/proc/<pid>`:指定 PID 快照文本
|
||||
- `/proc` 为只读;写入类 syscall 不支持。
|
||||
|
||||
## 4. Syscall 列表(0~106)
|
||||
## 4. Syscall 列表(0~107)
|
||||
|
||||
### 0 `CLEONOS_SYSCALL_LOG_WRITE`
|
||||
|
||||
@@ -809,6 +809,16 @@ u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2);
|
||||
- 返回:关闭成功返回 `1`,失败返回 `0`。
|
||||
- 说明:最小关闭流程支持 FIN/ACK 轮询等待,超时会强制回收连接状态。
|
||||
|
||||
### 107 `CLEONOS_SYSCALL_MOUSE_STATE`
|
||||
|
||||
- 参数:
|
||||
- `arg0`: `struct { u64 x; u64 y; u64 buttons; u64 packet_count; u64 ready; } *out_state`
|
||||
- 返回:成功返回 `1`,失败返回 `0`。
|
||||
- 说明:
|
||||
- 读取当前鼠标状态快照,坐标为 framebuffer 像素坐标系(左上角为原点)。
|
||||
- `buttons` 位掩码:`bit0=left`、`bit1=right`、`bit2=middle`。
|
||||
- `ready=1` 表示鼠标设备已上线;`ready=0` 时坐标/按键可能为默认值。
|
||||
|
||||
## 5. 用户态封装函数
|
||||
|
||||
用户态封装位于:
|
||||
@@ -844,6 +854,7 @@ u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2);
|
||||
- `cleonos_sys_net_available()` / `cleonos_sys_net_ipv4_addr()` / `cleonos_sys_net_netmask()` / `cleonos_sys_net_gateway()` / `cleonos_sys_net_dns_server()` / `cleonos_sys_net_ping()`
|
||||
- `cleonos_sys_net_udp_send()` / `cleonos_sys_net_udp_recv()`
|
||||
- `cleonos_sys_net_tcp_connect()` / `cleonos_sys_net_tcp_send()` / `cleonos_sys_net_tcp_recv()` / `cleonos_sys_net_tcp_close()`
|
||||
- `cleonos_sys_mouse_state()`
|
||||
|
||||
## 6. 开发注意事项
|
||||
|
||||
@@ -854,7 +865,7 @@ u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2);
|
||||
|
||||
## 7. Wine 兼容说明
|
||||
|
||||
- `wine/cleonos_wine_lib/runner.py` 当前已覆盖到 `0..106`(含 `DL_*`、`FB_*`、`KERNEL_VERSION`、`DISK_*`、`NET_*`)。
|
||||
- `wine/cleonos_wine_lib/runner.py` 当前已覆盖到 `0..107`(含 `DL_*`、`FB_*`、`KERNEL_VERSION`、`DISK_*`、`NET_*`、`MOUSE_STATE`)。
|
||||
- `DL_*`(`77..79`)在 Wine 中为“可运行兼容”实现:
|
||||
- `DL_OPEN`:加载 guest ELF 到当前 Unicorn 地址空间,返回稳定 `handle`,并做引用计数。
|
||||
- `DL_SYM`:解析 ELF `SYMTAB/DYNSYM` 并返回 guest 可调用地址。
|
||||
@@ -872,6 +883,7 @@ u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2);
|
||||
- `DISK_MOUNT`/`DISK_MOUNT_PATH` 支持挂载点管理,并与 `FS_MKDIR/WRITE/APPEND/REMOVE` 的路径规则联动。
|
||||
- `DISK_READ_SECTOR`/`DISK_WRITE_SECTOR`(`93..94`)在 Wine 中已实现为 512B 原始扇区读写(host 文件后端)。
|
||||
- 网络 syscall(`95..106`)在 Wine 当前为兼容占位实现(统一返回 `0`);即 Wine 运行模式下不会提供真实网络收发。
|
||||
- `MOUSE_STATE`(`107`)在 Wine 中为基础兼容实现:可返回指针数据结构;未启用窗口鼠标事件时 `ready` 可能为 `0`。
|
||||
- Wine 在运行时崩溃场景下会生成与内核一致格式的“信号编码退出状态”,可通过 `WAITPID` 读取。
|
||||
- Wine 当前音频 syscall 为占位实现:`AUDIO_AVAILABLE=0`,`AUDIO_PLAY_TONE=0`,`AUDIO_STOP=1`。
|
||||
- Wine 版本号策略固定为 `85.0.0-wine`(历史兼容号;不会随 syscall 扩展继续增长)。
|
||||
|
||||
2
kit
2
kit
Submodule kit updated: 6be905609a...1e7b5aa8d2
2
wine
2
wine
Submodule wine updated: abf4a53e78...6ffb354265
Reference in New Issue
Block a user