diff --git a/clks/drivers/video/framebuffer.c b/clks/drivers/video/framebuffer.c index c86b2e5..6914e43 100644 --- a/clks/drivers/video/framebuffer.c +++ b/clks/drivers/video/framebuffer.c @@ -89,21 +89,73 @@ struct clks_framebuffer_info clks_fb_info(void) { return clks_fb.info; } -void clks_fb_clear(u32 rgb) { - u32 x; - u32 y; +void clks_fb_draw_pixel(u32 x, u32 y, u32 rgb) { + clks_fb_put_pixel(x, y, rgb); +} + +clks_bool clks_fb_read_pixel(u32 x, u32 y, u32 *out_rgb) { + volatile u8 *row; + volatile u32 *pixel; + + if (out_rgb == CLKS_NULL) { + return CLKS_FALSE; + } + + if (clks_fb.ready == CLKS_FALSE) { + return CLKS_FALSE; + } + + if (x >= clks_fb.info.width || y >= clks_fb.info.height) { + return CLKS_FALSE; + } + + if (clks_fb.info.bpp != 32) { + return CLKS_FALSE; + } + + row = clks_fb.address + ((usize)y * (usize)clks_fb.info.pitch); + pixel = (volatile u32 *)(row + ((usize)x * 4U)); + *out_rgb = *pixel; + return CLKS_TRUE; +} + +void clks_fb_fill_rect(u32 x, u32 y, u32 width, u32 height, u32 rgb) { + u32 px; + u32 py; + u32 end_x; + u32 end_y; + u64 end_x64; + u64 end_y64; if (clks_fb.ready == CLKS_FALSE) { return; } - for (y = 0U; y < clks_fb.info.height; y++) { - for (x = 0U; x < clks_fb.info.width; x++) { - clks_fb_put_pixel(x, y, rgb); + if (width == 0U || height == 0U) { + return; + } + + if (x >= clks_fb.info.width || y >= clks_fb.info.height) { + return; + } + + end_x64 = (u64)x + (u64)width; + end_y64 = (u64)y + (u64)height; + + end_x = (end_x64 > (u64)clks_fb.info.width) ? clks_fb.info.width : (u32)end_x64; + end_y = (end_y64 > (u64)clks_fb.info.height) ? clks_fb.info.height : (u32)end_y64; + + for (py = y; py < end_y; py++) { + for (px = x; px < end_x; px++) { + clks_fb_put_pixel(px, py, rgb); } } } +void clks_fb_clear(u32 rgb) { + clks_fb_fill_rect(0U, 0U, clks_fb.info.width, clks_fb.info.height, rgb); +} + void clks_fb_scroll_up(u32 pixel_rows, u32 fill_rgb) { usize row_bytes; usize move_bytes; diff --git a/clks/include/clks/desktop.h b/clks/include/clks/desktop.h new file mode 100644 index 0000000..c063725 --- /dev/null +++ b/clks/include/clks/desktop.h @@ -0,0 +1,10 @@ +#ifndef CLKS_DESKTOP_H +#define CLKS_DESKTOP_H + +#include + +void clks_desktop_init(void); +void clks_desktop_tick(u64 tick); +clks_bool clks_desktop_ready(void); + +#endif diff --git a/clks/include/clks/framebuffer.h b/clks/include/clks/framebuffer.h index b7bea95..5d963a0 100644 --- a/clks/include/clks/framebuffer.h +++ b/clks/include/clks/framebuffer.h @@ -16,9 +16,12 @@ clks_bool clks_fb_ready(void); struct clks_framebuffer_info clks_fb_info(void); void clks_fb_clear(u32 rgb); void clks_fb_scroll_up(u32 pixel_rows, u32 fill_rgb); +void clks_fb_draw_pixel(u32 x, u32 y, u32 rgb); +clks_bool clks_fb_read_pixel(u32 x, u32 y, u32 *out_rgb); +void clks_fb_fill_rect(u32 x, u32 y, u32 width, u32 height, u32 rgb); void clks_fb_draw_char(u32 x, u32 y, char ch, u32 fg_rgb, u32 bg_rgb); clks_bool clks_fb_load_psf_font(const void *blob, u64 blob_size); u32 clks_fb_cell_width(void); u32 clks_fb_cell_height(void); -#endif \ No newline at end of file +#endif diff --git a/clks/include/clks/mouse.h b/clks/include/clks/mouse.h new file mode 100644 index 0000000..e8d045b --- /dev/null +++ b/clks/include/clks/mouse.h @@ -0,0 +1,25 @@ +#ifndef CLKS_MOUSE_H +#define CLKS_MOUSE_H + +#include + +#define CLKS_MOUSE_BTN_LEFT 0x01U +#define CLKS_MOUSE_BTN_RIGHT 0x02U +#define CLKS_MOUSE_BTN_MIDDLE 0x04U + +struct clks_mouse_state { + i32 x; + i32 y; + u8 buttons; + u64 packet_count; + clks_bool ready; +}; + +void clks_mouse_init(void); +void clks_mouse_handle_byte(u8 data_byte); +void clks_mouse_snapshot(struct clks_mouse_state *out_state); +clks_bool clks_mouse_ready(void); +u64 clks_mouse_packet_count(void); +u64 clks_mouse_drop_count(void); + +#endif diff --git a/clks/kernel/desktop.c b/clks/kernel/desktop.c new file mode 100644 index 0000000..cba1ede --- /dev/null +++ b/clks/kernel/desktop.c @@ -0,0 +1,406 @@ +#include +#include +#include +#include +#include +#include + +#define CLKS_DESKTOP_TTY_INDEX 1U + +#define CLKS_DESKTOP_BG_COLOR 0x001B2430U +#define CLKS_DESKTOP_TOPBAR_COLOR 0x00293447U +#define CLKS_DESKTOP_DOCK_COLOR 0x00232C3AU +#define CLKS_DESKTOP_WINDOW_COLOR 0x00313E52U +#define CLKS_DESKTOP_TITLE_COLOR 0x003B4A61U +#define CLKS_DESKTOP_TEXT_FG 0x00E6EDF7U +#define CLKS_DESKTOP_TEXT_BG 0x003B4A61U + +#define CLKS_DESKTOP_CURSOR_FILL 0x00F5F8FFU +#define CLKS_DESKTOP_CURSOR_OUTLINE 0x00101010U +#define CLKS_DESKTOP_CURSOR_ACTIVE 0x00FFCE6EU +#define CLKS_DESKTOP_CURSOR_W 16U +#define CLKS_DESKTOP_CURSOR_H 16U + +struct clks_desktop_layout { + u32 width; + u32 height; + u32 topbar_h; + u32 dock_w; + u32 win_x; + u32 win_y; + u32 win_w; + u32 win_h; + u32 win_title_h; +}; + +static struct clks_desktop_layout clks_desktop = { + .width = 0U, + .height = 0U, + .topbar_h = 32U, + .dock_w = 72U, + .win_x = 0U, + .win_y = 0U, + .win_w = 0U, + .win_h = 0U, + .win_title_h = 28U, +}; + +static clks_bool clks_desktop_ready_flag = CLKS_FALSE; +static clks_bool clks_desktop_active_last = CLKS_FALSE; +static clks_bool clks_desktop_scene_drawn = CLKS_FALSE; +static clks_bool clks_desktop_cursor_drawn = CLKS_FALSE; +static i32 clks_desktop_last_mouse_x = -1; +static i32 clks_desktop_last_mouse_y = -1; +static u8 clks_desktop_last_buttons = 0U; +static clks_bool clks_desktop_last_ready = CLKS_FALSE; +static u32 clks_desktop_cursor_under[CLKS_DESKTOP_CURSOR_W * CLKS_DESKTOP_CURSOR_H]; + +static void clks_desktop_draw_text(u32 x, u32 y, const char *text, u32 fg, u32 bg) { + u32 step; + usize i = 0U; + + if (text == CLKS_NULL) { + return; + } + + step = clks_fb_cell_width(); + + if (step == 0U) { + step = 8U; + } + + while (text[i] != '\0') { + clks_fb_draw_char(x + ((u32)i * step), y, text[i], fg, bg); + i++; + } +} + +static clks_bool clks_desktop_in_bounds(i32 x, i32 y) { + if (x < 0 || y < 0) { + return CLKS_FALSE; + } + + if ((u32)x >= clks_desktop.width || (u32)y >= clks_desktop.height) { + return CLKS_FALSE; + } + + return CLKS_TRUE; +} + +static void clks_desktop_compute_layout(void) { + struct clks_framebuffer_info info = clks_fb_info(); + u32 right_margin; + u32 bottom_margin; + + clks_desktop.width = info.width; + clks_desktop.height = info.height; + + if (clks_desktop.topbar_h >= clks_desktop.height) { + clks_desktop.topbar_h = clks_desktop.height; + } + + if (clks_desktop.dock_w >= clks_desktop.width) { + clks_desktop.dock_w = clks_desktop.width; + } + + clks_desktop.win_x = clks_desktop.width / 6U; + clks_desktop.win_y = clks_desktop.height / 6U; + + if (clks_desktop.win_x < 96U) { + clks_desktop.win_x = 96U; + } + + if (clks_desktop.win_y < 72U) { + clks_desktop.win_y = 72U; + } + + if (clks_desktop.win_x >= clks_desktop.width) { + clks_desktop.win_x = clks_desktop.width > 0U ? (clks_desktop.width - 1U) : 0U; + } + + if (clks_desktop.win_y >= clks_desktop.height) { + clks_desktop.win_y = clks_desktop.height > 0U ? (clks_desktop.height - 1U) : 0U; + } + + right_margin = clks_desktop.width / 10U; + bottom_margin = clks_desktop.height / 8U; + + if (right_margin < 48U) { + right_margin = 48U; + } + + if (bottom_margin < 48U) { + bottom_margin = 48U; + } + + if (clks_desktop.width > clks_desktop.win_x + right_margin) { + clks_desktop.win_w = clks_desktop.width - clks_desktop.win_x - right_margin; + } else { + clks_desktop.win_w = clks_desktop.width - clks_desktop.win_x; + } + + if (clks_desktop.height > clks_desktop.win_y + bottom_margin) { + clks_desktop.win_h = clks_desktop.height - clks_desktop.win_y - bottom_margin; + } else { + clks_desktop.win_h = clks_desktop.height - clks_desktop.win_y; + } + + if (clks_desktop.win_w < 220U) { + clks_desktop.win_w = (clks_desktop.width > (clks_desktop.win_x + 20U)) + ? (clks_desktop.width - clks_desktop.win_x - 20U) + : (clks_desktop.width - clks_desktop.win_x); + } + + if (clks_desktop.win_h < 140U) { + clks_desktop.win_h = (clks_desktop.height > (clks_desktop.win_y + 20U)) + ? (clks_desktop.height - clks_desktop.win_y - 20U) + : (clks_desktop.height - clks_desktop.win_y); + } + + if (clks_desktop.win_w > (clks_desktop.width - clks_desktop.win_x)) { + clks_desktop.win_w = clks_desktop.width - clks_desktop.win_x; + } + + if (clks_desktop.win_h > (clks_desktop.height - clks_desktop.win_y)) { + clks_desktop.win_h = clks_desktop.height - clks_desktop.win_y; + } + + if (clks_desktop.win_title_h >= clks_desktop.win_h) { + clks_desktop.win_title_h = clks_desktop.win_h; + } +} + +static void clks_desktop_draw_status_widgets(const struct clks_mouse_state *mouse) { + u32 ready_x = 0U; + u32 left_x = 0U; + + if (mouse == CLKS_NULL) { + return; + } + + if (clks_desktop.width > 28U) { + ready_x = clks_desktop.width - 28U; + } + + if (clks_desktop.width > 46U) { + left_x = clks_desktop.width - 46U; + } + + if (mouse->ready == CLKS_TRUE) { + clks_fb_fill_rect(ready_x, 10U, 10U, 10U, 0x006FE18BU); + } else { + clks_fb_fill_rect(ready_x, 10U, 10U, 10U, 0x00E06A6AU); + } + + if ((mouse->buttons & CLKS_MOUSE_BTN_LEFT) != 0U) { + clks_fb_fill_rect(left_x, 10U, 10U, 10U, 0x00FFCE6EU); + } else { + clks_fb_fill_rect(left_x, 10U, 10U, 10U, 0x004A5568U); + } +} + +static void clks_desktop_draw_static_scene(const struct clks_mouse_state *mouse) { + clks_fb_clear(CLKS_DESKTOP_BG_COLOR); + + if (clks_desktop.topbar_h > 0U) { + clks_fb_fill_rect(0U, 0U, clks_desktop.width, clks_desktop.topbar_h, CLKS_DESKTOP_TOPBAR_COLOR); + } + + if (clks_desktop.height > clks_desktop.topbar_h) { + clks_fb_fill_rect(0U, clks_desktop.topbar_h, clks_desktop.dock_w, + clks_desktop.height - clks_desktop.topbar_h, CLKS_DESKTOP_DOCK_COLOR); + } + + clks_fb_fill_rect(clks_desktop.win_x, clks_desktop.win_y, clks_desktop.win_w, clks_desktop.win_h, + CLKS_DESKTOP_WINDOW_COLOR); + clks_fb_fill_rect(clks_desktop.win_x, clks_desktop.win_y, clks_desktop.win_w, clks_desktop.win_title_h, + CLKS_DESKTOP_TITLE_COLOR); + + clks_desktop_draw_text(12U, 6U, "CLeonOS Desktop TTY2", CLKS_DESKTOP_TEXT_FG, CLKS_DESKTOP_TOPBAR_COLOR); + clks_desktop_draw_text(clks_desktop.win_x + 12U, clks_desktop.win_y + 6U, "Mouse Input Ready", + CLKS_DESKTOP_TEXT_FG, CLKS_DESKTOP_TITLE_COLOR); + clks_desktop_draw_text(clks_desktop.win_x + 16U, clks_desktop.win_y + clks_desktop.win_title_h + 16U, + "Stage25: Alt+F2 desktop, Alt+F1 shell", CLKS_DESKTOP_TEXT_FG, CLKS_DESKTOP_WINDOW_COLOR); + + clks_desktop_draw_status_widgets(mouse); + clks_desktop_scene_drawn = CLKS_TRUE; +} + +static clks_bool clks_desktop_cursor_pixel(u32 lx, u32 ly, u8 buttons, u32 *out_color) { + u32 fill = CLKS_DESKTOP_CURSOR_FILL; + + if (out_color == CLKS_NULL) { + return CLKS_FALSE; + } + + if ((buttons & CLKS_MOUSE_BTN_LEFT) != 0U) { + fill = CLKS_DESKTOP_CURSOR_ACTIVE; + } + + if (ly < 12U) { + u32 span = (ly / 2U) + 1U; + + if (lx < span) { + clks_bool border = (lx == 0U || (lx + 1U) == span || ly == 11U) ? CLKS_TRUE : CLKS_FALSE; + *out_color = (border == CLKS_TRUE) ? CLKS_DESKTOP_CURSOR_OUTLINE : fill; + return CLKS_TRUE; + } + } + + if (ly >= 8U && ly < 16U && lx >= 2U && lx < 5U) { + clks_bool border = (lx == 2U || lx == 4U || ly == 15U) ? CLKS_TRUE : CLKS_FALSE; + *out_color = (border == CLKS_TRUE) ? CLKS_DESKTOP_CURSOR_OUTLINE : fill; + return CLKS_TRUE; + } + + return CLKS_FALSE; +} + +static void clks_desktop_capture_cursor_under(i32 x, i32 y) { + u32 ly; + + for (ly = 0U; ly < CLKS_DESKTOP_CURSOR_H; ly++) { + u32 lx; + + for (lx = 0U; lx < CLKS_DESKTOP_CURSOR_W; lx++) { + i32 gx = x + (i32)lx; + i32 gy = y + (i32)ly; + usize idx = ((usize)ly * (usize)CLKS_DESKTOP_CURSOR_W) + (usize)lx; + u32 pixel = CLKS_DESKTOP_BG_COLOR; + + if (clks_desktop_in_bounds(gx, gy) == CLKS_TRUE) { + (void)clks_fb_read_pixel((u32)gx, (u32)gy, &pixel); + } + + clks_desktop_cursor_under[idx] = pixel; + } + } +} + +static void clks_desktop_restore_cursor_under(void) { + u32 ly; + + if (clks_desktop_cursor_drawn == CLKS_FALSE) { + return; + } + + for (ly = 0U; ly < CLKS_DESKTOP_CURSOR_H; ly++) { + u32 lx; + + for (lx = 0U; lx < CLKS_DESKTOP_CURSOR_W; lx++) { + i32 gx = clks_desktop_last_mouse_x + (i32)lx; + i32 gy = clks_desktop_last_mouse_y + (i32)ly; + usize idx = ((usize)ly * (usize)CLKS_DESKTOP_CURSOR_W) + (usize)lx; + + if (clks_desktop_in_bounds(gx, gy) == CLKS_TRUE) { + clks_fb_draw_pixel((u32)gx, (u32)gy, clks_desktop_cursor_under[idx]); + } + } + } + + clks_desktop_cursor_drawn = CLKS_FALSE; +} + +static void clks_desktop_draw_cursor(i32 x, i32 y, u8 buttons) { + u32 ly; + + for (ly = 0U; ly < CLKS_DESKTOP_CURSOR_H; ly++) { + u32 lx; + + for (lx = 0U; lx < CLKS_DESKTOP_CURSOR_W; lx++) { + i32 gx = x + (i32)lx; + i32 gy = y + (i32)ly; + u32 color = 0U; + + if (clks_desktop_cursor_pixel(lx, ly, buttons, &color) == CLKS_FALSE) { + continue; + } + + if (clks_desktop_in_bounds(gx, gy) == CLKS_TRUE) { + clks_fb_draw_pixel((u32)gx, (u32)gy, color); + } + } + } + + clks_desktop_cursor_drawn = CLKS_TRUE; +} + +static void clks_desktop_present_cursor(const struct clks_mouse_state *mouse) { + if (mouse == CLKS_NULL) { + return; + } + + clks_desktop_restore_cursor_under(); + clks_desktop_capture_cursor_under(mouse->x, mouse->y); + clks_desktop_draw_cursor(mouse->x, mouse->y, mouse->buttons); + + clks_desktop_last_mouse_x = mouse->x; + clks_desktop_last_mouse_y = mouse->y; + clks_desktop_last_buttons = mouse->buttons; + clks_desktop_last_ready = mouse->ready; +} + +void clks_desktop_init(void) { + if (clks_fb_ready() == CLKS_FALSE) { + clks_desktop_ready_flag = CLKS_FALSE; + clks_log(CLKS_LOG_WARN, "DESK", "FRAMEBUFFER NOT READY; DESKTOP DISABLED"); + return; + } + + clks_desktop_compute_layout(); + clks_desktop_ready_flag = CLKS_TRUE; + clks_desktop_active_last = CLKS_FALSE; + clks_desktop_scene_drawn = CLKS_FALSE; + clks_desktop_cursor_drawn = CLKS_FALSE; + clks_desktop_last_mouse_x = -1; + clks_desktop_last_mouse_y = -1; + clks_desktop_last_buttons = 0U; + clks_desktop_last_ready = CLKS_FALSE; + + clks_log(CLKS_LOG_INFO, "DESK", "TTY2 DESKTOP ONLINE"); + clks_log(CLKS_LOG_INFO, "DESK", "MOUSE-FIRST MODE ENABLED"); +} + +void clks_desktop_tick(u64 tick) { + struct clks_mouse_state mouse = {0, 0, 0U, 0ULL, CLKS_FALSE}; + + (void)tick; + + if (clks_desktop_ready_flag == CLKS_FALSE) { + return; + } + + if (clks_tty_active() != CLKS_DESKTOP_TTY_INDEX) { + clks_desktop_active_last = CLKS_FALSE; + clks_desktop_scene_drawn = CLKS_FALSE; + clks_desktop_cursor_drawn = CLKS_FALSE; + return; + } + + clks_mouse_snapshot(&mouse); + + if (clks_desktop_active_last == CLKS_FALSE || clks_desktop_scene_drawn == CLKS_FALSE) { + clks_desktop_compute_layout(); + clks_desktop_draw_static_scene(&mouse); + clks_desktop_present_cursor(&mouse); + clks_desktop_active_last = CLKS_TRUE; + return; + } + + if (mouse.ready != clks_desktop_last_ready || mouse.buttons != clks_desktop_last_buttons) { + clks_desktop_draw_status_widgets(&mouse); + } + + if (mouse.x != clks_desktop_last_mouse_x || mouse.y != clks_desktop_last_mouse_y || + mouse.buttons != clks_desktop_last_buttons) { + clks_desktop_present_cursor(&mouse); + } else { + clks_desktop_last_ready = mouse.ready; + } + + clks_desktop_active_last = CLKS_TRUE; +} + +clks_bool clks_desktop_ready(void) { + return clks_desktop_ready_flag; +} diff --git a/clks/kernel/interrupts.c b/clks/kernel/interrupts.c index ec65204..27c7a39 100644 --- a/clks/kernel/interrupts.c +++ b/clks/kernel/interrupts.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #define CLKS_IRQ_BASE 32U #define CLKS_IRQ_TIMER 32U #define CLKS_IRQ_KEYBOARD 33U +#define CLKS_IRQ_MOUSE 44U #define CLKS_IRQ_LAST 47U #define CLKS_SYSCALL_VECTOR 128U @@ -198,8 +200,8 @@ static void clks_pic_remap_and_mask(void) { (void)master_mask; (void)slave_mask; - clks_outb(CLKS_PIC1_DATA, 0xFCU); - clks_outb(CLKS_PIC2_DATA, 0xFFU); + clks_outb(CLKS_PIC1_DATA, 0xF8U); + clks_outb(CLKS_PIC2_DATA, 0xEFU); } static void clks_pic_send_eoi(u64 vector) { @@ -259,6 +261,11 @@ void clks_interrupt_dispatch(struct clks_interrupt_frame *frame) { u8 scancode = clks_inb(CLKS_PS2_DATA_PORT); clks_keyboard_handle_scancode(scancode); } + } else if (vector == CLKS_IRQ_MOUSE) { + if (clks_ps2_has_output() == CLKS_TRUE) { + u8 data_byte = clks_inb(CLKS_PS2_DATA_PORT); + clks_mouse_handle_byte(data_byte); + } } if (vector >= CLKS_IRQ_BASE && vector <= CLKS_IRQ_LAST) { diff --git a/clks/kernel/keyboard.c b/clks/kernel/keyboard.c index 19f8cb6..770223b 100644 --- a/clks/kernel/keyboard.c +++ b/clks/kernel/keyboard.c @@ -101,6 +101,10 @@ static clks_bool clks_keyboard_queue_push(char ch) { return CLKS_TRUE; } +static clks_bool clks_keyboard_shell_input_enabled(void) { + return (clks_tty_active() == 0U) ? CLKS_TRUE : CLKS_FALSE; +} + static char clks_keyboard_translate_scancode(u8 code) { clks_bool shift_active = (clks_kbd_lshift_down == CLKS_TRUE || clks_kbd_rshift_down == CLKS_TRUE) ? CLKS_TRUE @@ -169,7 +173,7 @@ void clks_keyboard_handle_scancode(u8 scancode) { char ext = clks_keyboard_translate_ext_scancode(code); clks_kbd_e0_prefix = CLKS_FALSE; - if (ext != '\0') { + if (ext != '\0' && clks_keyboard_shell_input_enabled() == CLKS_TRUE) { if (clks_keyboard_queue_push(ext) == CLKS_TRUE) { clks_shell_pump_input(1U); } @@ -199,7 +203,7 @@ void clks_keyboard_handle_scancode(u8 scancode) { { char translated = clks_keyboard_translate_scancode(code); - if (translated != '\0') { + if (translated != '\0' && clks_keyboard_shell_input_enabled() == CLKS_TRUE) { if (clks_keyboard_queue_push(translated) == CLKS_TRUE) { clks_shell_pump_input(1U); } diff --git a/clks/kernel/kmain.c b/clks/kernel/kmain.c index 70d314b..0cceac6 100644 --- a/clks/kernel/kmain.c +++ b/clks/kernel/kmain.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +67,7 @@ static void clks_task_kelfd(u64 tick) { static void clks_task_usrd(u64 tick) { clks_service_heartbeat(CLKS_SERVICE_USER, tick); clks_userland_tick(tick); + clks_desktop_tick(tick); clks_tty_tick(tick); clks_shell_tick(tick); } @@ -96,7 +99,7 @@ void clks_kernel_main(void) { clks_tty_init(); } - clks_log(CLKS_LOG_INFO, "BOOT", "CLEONOS Stage24 START"); + clks_log(CLKS_LOG_INFO, "BOOT", "CLEONOS Stage25 START"); if (boot_fb == CLKS_NULL) { clks_log(CLKS_LOG_WARN, "VIDEO", "NO FRAMEBUFFER FROM LIMINE"); @@ -172,6 +175,8 @@ void clks_kernel_main(void) { clks_exec_init(); clks_keyboard_init(); + clks_mouse_init(); + clks_desktop_init(); if (clks_userland_init() == CLKS_FALSE) { clks_log(CLKS_LOG_ERROR, "USER", "USERLAND INIT FAILED"); diff --git a/clks/kernel/mouse.c b/clks/kernel/mouse.c new file mode 100644 index 0000000..37fa92e --- /dev/null +++ b/clks/kernel/mouse.c @@ -0,0 +1,308 @@ +#include +#include +#include +#include + +#define CLKS_PS2_DATA_PORT 0x60U +#define CLKS_PS2_STATUS_PORT 0x64U +#define CLKS_PS2_CMD_PORT 0x64U +#define CLKS_PS2_STATUS_OBF 0x01U +#define CLKS_PS2_STATUS_IBF 0x02U + +#define CLKS_PS2_CMD_ENABLE_AUX 0xA8U +#define CLKS_PS2_CMD_READ_CFG 0x20U +#define CLKS_PS2_CMD_WRITE_CFG 0x60U +#define CLKS_PS2_CMD_WRITE_AUX 0xD4U + +#define CLKS_PS2_MOUSE_CMD_RESET_DEFAULTS 0xF6U +#define CLKS_PS2_MOUSE_CMD_ENABLE_STREAM 0xF4U +#define CLKS_PS2_MOUSE_ACK 0xFAU + +#define CLKS_MOUSE_IO_TIMEOUT 100000U +#define CLKS_MOUSE_DRAIN_MAX 64U +#define CLKS_MOUSE_SYNC_BIT 0x08U +#define CLKS_MOUSE_OVERFLOW_MASK 0xC0U +#define CLKS_MOUSE_BUTTON_MASK 0x07U + +struct clks_mouse_runtime { + i32 x; + i32 y; + u32 max_x; + u32 max_y; + u8 buttons; + u8 packet[3]; + u8 packet_index; + u64 packet_count; + u64 drop_count; + clks_bool ready; +}; + +static struct clks_mouse_runtime clks_mouse = { + .x = 0, + .y = 0, + .max_x = 0U, + .max_y = 0U, + .buttons = 0U, + .packet = {0U, 0U, 0U}, + .packet_index = 0U, + .packet_count = 0ULL, + .drop_count = 0ULL, + .ready = CLKS_FALSE, +}; + +static inline void clks_mouse_outb(u16 port, u8 value) { + __asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +static inline u8 clks_mouse_inb(u16 port) { + u8 value; + __asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +static clks_bool clks_mouse_wait_input_empty(void) { + u32 i; + + for (i = 0U; i < CLKS_MOUSE_IO_TIMEOUT; i++) { + if ((clks_mouse_inb(CLKS_PS2_STATUS_PORT) & CLKS_PS2_STATUS_IBF) == 0U) { + return CLKS_TRUE; + } + } + + return CLKS_FALSE; +} + +static clks_bool clks_mouse_wait_output_ready(void) { + u32 i; + + for (i = 0U; i < CLKS_MOUSE_IO_TIMEOUT; i++) { + if ((clks_mouse_inb(CLKS_PS2_STATUS_PORT) & CLKS_PS2_STATUS_OBF) != 0U) { + return CLKS_TRUE; + } + } + + return CLKS_FALSE; +} + +static clks_bool clks_mouse_write_cmd(u8 cmd) { + if (clks_mouse_wait_input_empty() == CLKS_FALSE) { + return CLKS_FALSE; + } + + clks_mouse_outb(CLKS_PS2_CMD_PORT, cmd); + return CLKS_TRUE; +} + +static clks_bool clks_mouse_write_data(u8 value) { + if (clks_mouse_wait_input_empty() == CLKS_FALSE) { + return CLKS_FALSE; + } + + clks_mouse_outb(CLKS_PS2_DATA_PORT, value); + return CLKS_TRUE; +} + +static clks_bool clks_mouse_read_data(u8 *out_value) { + if (out_value == CLKS_NULL) { + return CLKS_FALSE; + } + + if (clks_mouse_wait_output_ready() == CLKS_FALSE) { + return CLKS_FALSE; + } + + *out_value = clks_mouse_inb(CLKS_PS2_DATA_PORT); + return CLKS_TRUE; +} + +static void clks_mouse_drain_output(void) { + u32 i; + + for (i = 0U; i < CLKS_MOUSE_DRAIN_MAX; i++) { + if ((clks_mouse_inb(CLKS_PS2_STATUS_PORT) & CLKS_PS2_STATUS_OBF) == 0U) { + break; + } + + (void)clks_mouse_inb(CLKS_PS2_DATA_PORT); + } +} + +static clks_bool clks_mouse_send_device_cmd(u8 cmd, u8 *out_ack) { + u8 ack = 0U; + + if (clks_mouse_write_cmd(CLKS_PS2_CMD_WRITE_AUX) == CLKS_FALSE) { + return CLKS_FALSE; + } + + if (clks_mouse_write_data(cmd) == CLKS_FALSE) { + return CLKS_FALSE; + } + + if (clks_mouse_read_data(&ack) == CLKS_FALSE) { + return CLKS_FALSE; + } + + if (out_ack != CLKS_NULL) { + *out_ack = ack; + } + + return CLKS_TRUE; +} + +static void clks_mouse_reset_runtime(void) { + struct clks_framebuffer_info info; + + clks_mouse.x = 0; + clks_mouse.y = 0; + clks_mouse.max_x = 0U; + clks_mouse.max_y = 0U; + clks_mouse.buttons = 0U; + clks_mouse.packet[0] = 0U; + clks_mouse.packet[1] = 0U; + clks_mouse.packet[2] = 0U; + clks_mouse.packet_index = 0U; + clks_mouse.packet_count = 0ULL; + clks_mouse.drop_count = 0ULL; + clks_mouse.ready = CLKS_FALSE; + + if (clks_fb_ready() == CLKS_TRUE) { + info = clks_fb_info(); + + if (info.width > 0U) { + clks_mouse.max_x = info.width - 1U; + } + + if (info.height > 0U) { + clks_mouse.max_y = info.height - 1U; + } + + clks_mouse.x = (i32)(clks_mouse.max_x / 2U); + clks_mouse.y = (i32)(clks_mouse.max_y / 2U); + } +} + +void clks_mouse_init(void) { + u8 config = 0U; + u8 ack = 0U; + + clks_mouse_reset_runtime(); + clks_mouse_drain_output(); + + if (clks_mouse_write_cmd(CLKS_PS2_CMD_ENABLE_AUX) == CLKS_FALSE) { + clks_log(CLKS_LOG_WARN, "MOUSE", "PS2 ENABLE AUX FAILED"); + return; + } + + if (clks_mouse_write_cmd(CLKS_PS2_CMD_READ_CFG) == CLKS_FALSE || + clks_mouse_read_data(&config) == CLKS_FALSE) { + clks_log(CLKS_LOG_WARN, "MOUSE", "PS2 READ CFG FAILED"); + return; + } + + config |= 0x02U; + config &= (u8)~0x20U; + + if (clks_mouse_write_cmd(CLKS_PS2_CMD_WRITE_CFG) == CLKS_FALSE || + clks_mouse_write_data(config) == CLKS_FALSE) { + clks_log(CLKS_LOG_WARN, "MOUSE", "PS2 WRITE CFG FAILED"); + return; + } + + if (clks_mouse_send_device_cmd(CLKS_PS2_MOUSE_CMD_RESET_DEFAULTS, &ack) == CLKS_FALSE || + ack != CLKS_PS2_MOUSE_ACK) { + clks_log(CLKS_LOG_WARN, "MOUSE", "PS2 RESET DEFAULTS FAILED"); + return; + } + + if (clks_mouse_send_device_cmd(CLKS_PS2_MOUSE_CMD_ENABLE_STREAM, &ack) == CLKS_FALSE || + ack != CLKS_PS2_MOUSE_ACK) { + clks_log(CLKS_LOG_WARN, "MOUSE", "PS2 ENABLE STREAM FAILED"); + return; + } + + clks_mouse.ready = CLKS_TRUE; + clks_log(CLKS_LOG_INFO, "MOUSE", "PS2 POINTER ONLINE"); + clks_log_hex(CLKS_LOG_INFO, "MOUSE", "MAX_X", (u64)clks_mouse.max_x); + clks_log_hex(CLKS_LOG_INFO, "MOUSE", "MAX_Y", (u64)clks_mouse.max_y); +} + +void clks_mouse_handle_byte(u8 data_byte) { + i32 dx; + i32 dy; + i32 next_x; + i32 next_y; + u8 status; + + if (clks_mouse.ready == CLKS_FALSE) { + return; + } + + if (clks_mouse.packet_index == 0U && (data_byte & CLKS_MOUSE_SYNC_BIT) == 0U) { + clks_mouse.drop_count++; + return; + } + + clks_mouse.packet[clks_mouse.packet_index] = data_byte; + clks_mouse.packet_index++; + + if (clks_mouse.packet_index < 3U) { + return; + } + + clks_mouse.packet_index = 0U; + clks_mouse.packet_count++; + + status = clks_mouse.packet[0]; + clks_mouse.buttons = (u8)(status & CLKS_MOUSE_BUTTON_MASK); + + if ((status & CLKS_MOUSE_OVERFLOW_MASK) != 0U) { + clks_mouse.drop_count++; + return; + } + + dx = (i32)((i8)clks_mouse.packet[1]); + dy = (i32)((i8)clks_mouse.packet[2]); + + next_x = clks_mouse.x + dx; + next_y = clks_mouse.y - dy; + + if (next_x < 0) { + clks_mouse.x = 0; + } else if ((u32)next_x > clks_mouse.max_x) { + clks_mouse.x = (i32)clks_mouse.max_x; + } else { + clks_mouse.x = next_x; + } + + if (next_y < 0) { + clks_mouse.y = 0; + } else if ((u32)next_y > clks_mouse.max_y) { + clks_mouse.y = (i32)clks_mouse.max_y; + } else { + clks_mouse.y = next_y; + } +} + +void clks_mouse_snapshot(struct clks_mouse_state *out_state) { + if (out_state == CLKS_NULL) { + return; + } + + out_state->x = clks_mouse.x; + out_state->y = clks_mouse.y; + out_state->buttons = clks_mouse.buttons; + out_state->packet_count = clks_mouse.packet_count; + out_state->ready = clks_mouse.ready; +} + +clks_bool clks_mouse_ready(void) { + return clks_mouse.ready; +} + +u64 clks_mouse_packet_count(void) { + return clks_mouse.packet_count; +} + +u64 clks_mouse_drop_count(void) { + return clks_mouse.drop_count; +} diff --git a/clks/kernel/tty.c b/clks/kernel/tty.c index f728354..7cb1fe9 100644 --- a/clks/kernel/tty.c +++ b/clks/kernel/tty.c @@ -11,6 +11,7 @@ #define CLKS_TTY_BG 0x00101010U #define CLKS_TTY_CURSOR_BLINK_INTERVAL_TICKS 5ULL #define CLKS_TTY_BLINK_TICK_UNSET 0xFFFFFFFFFFFFFFFFULL +#define CLKS_TTY_DESKTOP_INDEX 1U static char clks_tty_cells[CLKS_TTY_COUNT][CLKS_TTY_MAX_ROWS][CLKS_TTY_MAX_COLS]; static u32 clks_tty_cursor_row[CLKS_TTY_COUNT]; @@ -72,6 +73,11 @@ static void clks_tty_draw_cursor(void) { return; } + if (clks_tty_active_index == CLKS_TTY_DESKTOP_INDEX) { + clks_tty_cursor_visible = CLKS_FALSE; + return; + } + row = clks_tty_cursor_row[clks_tty_active_index]; col = clks_tty_cursor_col[clks_tty_active_index]; @@ -311,6 +317,11 @@ void clks_tty_tick(u64 tick) { return; } + if (clks_tty_active_index == CLKS_TTY_DESKTOP_INDEX) { + clks_tty_cursor_visible = CLKS_FALSE; + return; + } + if (clks_tty_blink_last_tick == CLKS_TTY_BLINK_TICK_UNSET) { clks_tty_blink_last_tick = tick; diff --git a/docs/README.md b/docs/README.md index 3dd2797..64b81d2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,8 @@ - `stage22.md` - `stage23.md` - `stage24.md` +- `stage25.md` ## Notes - Stage docs use a fixed template: goal, implementation, acceptance criteria, build targets, QEMU command, and debugging notes. -- Stages 16~19 are currently not documented in this folder; add them later using the same template to keep history continuous. \ No newline at end of file +- Stages 16~19 are currently not documented in this folder; add them later using the same template to keep history continuous. diff --git a/docs/stage25.md b/docs/stage25.md new file mode 100644 index 0000000..fe0d6c9 --- /dev/null +++ b/docs/stage25.md @@ -0,0 +1,64 @@ +# CLeonOS Stage25 + +## Stage Goal +- Make `tty2` (Alt+F2) enter a basic desktop environment. +- Add first-step mouse support (PS/2 mouse IRQ and on-screen pointer). +- Keep kernel shell on `tty1` (Alt+F1) unchanged. + +## What Was Implemented +- New kernel modules: + - `clks/kernel/mouse.c` + - `clks/kernel/desktop.c` +- New public headers: + - `clks/include/clks/mouse.h` + - `clks/include/clks/desktop.h` +- Interrupt and PIC updates: + - Enable PIC cascade + mouse IRQ line. + - Route IRQ12 (`vector 44`) to mouse byte handler. +- PS/2 mouse bring-up: + - Enable aux device. + - Set controller config for mouse IRQ. + - Send mouse commands (`F6`, `F4`) and verify ACK. + - Decode 3-byte PS/2 packets into X/Y/buttons. +- Desktop on tty2: + - `clks_desktop_tick()` renders a simple graphical desktop scene. + - Draw software mouse cursor and button state indicator. + - Refresh when pointer changes and periodically to keep desktop clean. +- TTY adjustments: + - Disable text cursor blinking on `tty2` to avoid desktop overlay artifacts. +- Keyboard routing adjustment: + - Shell input queue only accepts character input while active TTY is `tty1` (index 0). + - Alt+F1..F4 switching remains available globally. +- Framebuffer primitives: + - Added `clks_fb_draw_pixel()` and `clks_fb_fill_rect()` for desktop rendering. +- Kernel flow integration: + - Stage banner -> `CLEONOS Stage25 START`. + - Initialize mouse + desktop during boot. + - Call desktop tick in `usrd` task. + +## Acceptance Criteria +- Boot log shows `CLEONOS Stage25 START`. +- Alt+F1 enters shell TTY; shell input works as before. +- Alt+F2 switches to desktop view (non-text UI). +- Mouse movement updates on-screen pointer in tty2. +- Left mouse button changes pointer/indicator state. + +## Build Targets +- `make setup` +- `make userapps` +- `make iso` +- `make run` +- `make debug` + +## QEMU Command +- `qemu-system-x86_64 -M q35 -m 1024M -cdrom build/CLeonOS-x86_64.iso -serial stdio` + +## Common Bugs and Debugging +- Desktop not visible after Alt+F2: + - Check `clks_desktop_init()` log lines and confirm framebuffer is ready. +- Mouse pointer does not move: + - Check mouse init logs (`PS2 POINTER ONLINE`) and IRQ12 routing in `interrupts.c`. +- Frequent text artifacts on desktop: + - Ensure desktop periodic redraw is active in `clks_desktop_tick()`. +- Shell unexpectedly receives input while on desktop: + - Verify keyboard routing guard uses `clks_tty_active() == 0`.