This commit is contained in:
boreddevnl 2026-03-02 21:27:44 +01:00
parent d38e8b97e2
commit 80fce3d0e9
15 changed files with 1047 additions and 99 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
disk.img

Binary file not shown.

View file

@ -22,15 +22,15 @@
#include "net_defs.h"
#include "man_entries.h"
#define CMD_COLS 116
#define CMD_ROWS 41
#define DEFAULT_CMD_COLS 116
#define DEFAULT_CMD_ROWS 41
#define MAX_CMD_COLS 256
#define LINE_HEIGHT 10
#define CHAR_WIDTH 8
#define COLOR_RED 0xFFFF0000
#define TXT_BUFFER_SIZE 4096
#define TXT_VISIBLE_LINES (CMD_ROWS - 2)
// --- Structs ---
typedef struct {
@ -82,16 +82,40 @@ typedef struct {
Window win_cmd;
// Shell State
static CharCell screen_buffer[CMD_ROWS][CMD_COLS];
static int terminal_cols = DEFAULT_CMD_COLS;
static int terminal_rows = DEFAULT_CMD_ROWS;
static CharCell *screen_buffer = NULL;
static int cursor_row = 0;
static int cursor_col = 0;
static uint32_t current_color = COLOR_DARK_TEXT;
static uint32_t current_color = 0xFFFFFFFF; // Default light text
static uint32_t current_bg_color = 0; // 0 = translucent/default
static CmdState *cmd_state = NULL; // Will be set in cmd_init
static int current_prompt_len = 0;
static size_t cmd_strlen(const char *s) {
size_t len = 0;
while (s[len]) len++;
return len;
}
static int cmd_strcmp(const char *s1, const char *s2) {
while (*s1 && (*s1 == *s2)) {
s1++;
s2++;
}
return *(const unsigned char*)s1 - *(const unsigned char*)s2;
}
// ANSI State Machine
static int ansi_state = 0;
#define ANSI_MAX_PARAMS 8
static int ansi_params[ANSI_MAX_PARAMS];
static int ansi_param_count = 0;
static bool ansi_private_mode = false;
// Pager State
static CmdMode current_mode = MODE_SHELL;
static char pager_wrapped_lines[2000][CMD_COLS + 1];
static char pager_wrapped_lines[2000][MAX_CMD_COLS + 1];
static int pager_total_lines = 0;
static int pager_top_line = 0;
@ -123,19 +147,52 @@ void cmd_reset_msg_count(void) {
msg_count = 0;
}
// --- Helpers ---
static size_t cmd_strlen(const char *str) {
size_t len = 0;
while (str[len]) len++;
return len;
static uint32_t ansi_get_256_color(int n) {
if (n < 16) {
static uint32_t base[16] = {
0xFF000000, 0xFFAA0000, 0xFF00AA00, 0xFFAA5500, 0xFF0000AA, 0xFFAA00AA, 0xFF00AAAA, 0xFFAAAAAA,
0xFF555555, 0xFFFF5555, 0xFF55FF55, 0xFFFFFF55, 0xFF5555FF, 0xFFFF55FF, 0xFF55FFFF, 0xFFFFFFFF
};
return base[n];
}
if (n >= 232) {
uint8_t v = (n - 232) * 10 + 8;
return 0xFF000000 | (v << 16) | (v << 8) | v;
}
n -= 16;
static uint8_t levels[] = {0, 95, 135, 175, 215, 255};
int r = levels[n / 36];
int g = levels[(n / 6) % 6];
int b = levels[n % 6];
return 0xFF000000 | (r << 16) | (g << 8) | b;
}
static int cmd_strcmp(const char *s1, const char *s2) {
while (*s1 && (*s1 == *s2)) {
s1++;
s2++;
static void ansi_handle_sgr() {
if (ansi_param_count == 0) {
current_color = 0xFFFFFFFF;
current_bg_color = 0;
return;
}
for (int i = 0; i < ansi_param_count; i++) {
int p = ansi_params[i];
if (p == 0) { current_color = 0xFFFFFFFF; current_bg_color = 0; }
else if (p >= 30 && p <= 37) current_color = ansi_get_256_color(p - 30);
else if (p >= 40 && p <= 47) current_bg_color = ansi_get_256_color(p - 40);
else if (p >= 90 && p <= 97) current_color = ansi_get_256_color(p - 90 + 8);
else if (p >= 100 && p <= 107) current_bg_color = ansi_get_256_color(p - 100 + 8);
else if (p == 38 || p == 48) {
bool is_fg = (p == 38);
if (i + 2 < ansi_param_count && ansi_params[i+1] == 5) {
uint32_t c = ansi_get_256_color(ansi_params[i+2]);
if (is_fg) current_color = c; else current_bg_color = c;
i += 2;
} else if (i + 4 < ansi_param_count && ansi_params[i+1] == 2) {
uint32_t c = 0xFF000000 | ((ansi_params[i+2] & 0xFF) << 16) | ((ansi_params[i+3] & 0xFF) << 8) | (ansi_params[i+4] & 0xFF);
if (is_fg) current_color = c; else current_bg_color = c;
i += 4;
}
}
}
return *(const unsigned char*)s1 - *(const unsigned char*)s2;
}
static void cmd_strcpy(char *dest, const char *src) {
@ -453,12 +510,12 @@ void cmd_set_current_color(uint32_t color) {
}
// --- History ---
#define HISTORY_MAX 16
static char cmd_history[HISTORY_MAX][CMD_COLS + 1];
#define HISTORY_MAX 64
static char cmd_history[HISTORY_MAX][MAX_CMD_COLS + 1];
static int history_head = 0;
static int history_len = 0;
static int history_pos = -1;
static char history_save_buf[CMD_COLS + 1];
static char history_save_buf[MAX_CMD_COLS + 1];
static void cmd_history_add(const char *cmd) {
if (!cmd || !*cmd) return;
@ -505,18 +562,18 @@ void cmd_print_prompt(void) {
void cmd_clear_line_content(void) {
int prompt_len = current_prompt_len;
for (int i = prompt_len; i < CMD_COLS; i++) {
screen_buffer[cursor_row][i].c = ' ';
screen_buffer[cursor_row][i].color = current_color;
for (int i = prompt_len; i < terminal_cols; i++) {
screen_buffer[cursor_row * terminal_cols + i].c = ' ';
screen_buffer[cursor_row * terminal_cols + i].color = current_color;
}
cursor_col = prompt_len;
}
static void cmd_set_line_content(const char *str) {
cmd_clear_line_content();
while (*str && cursor_col < CMD_COLS) {
screen_buffer[cursor_row][cursor_col].c = *str;
screen_buffer[cursor_row][cursor_col].color = current_color;
while (*str && cursor_col < terminal_cols) {
screen_buffer[cursor_row * terminal_cols + cursor_col].c = *str;
screen_buffer[cursor_row * terminal_cols + cursor_col].color = current_color;
cursor_col++;
str++;
}
@ -524,15 +581,15 @@ static void cmd_set_line_content(const char *str) {
static void cmd_scroll_up() {
for (int r = 1; r < CMD_ROWS; r++) {
for (int c = 0; c < CMD_COLS; c++) {
screen_buffer[r - 1][c] = screen_buffer[r][c];
for (int r = 1; r < terminal_rows; r++) {
for (int c = 0; c < terminal_cols; c++) {
screen_buffer[(r - 1) * terminal_cols + c] = screen_buffer[r * terminal_cols + c];
}
}
// Clear bottom row
for (int c = 0; c < CMD_COLS; c++) {
screen_buffer[CMD_ROWS - 1][c].c = ' ';
screen_buffer[CMD_ROWS - 1][c].color = shell_config.default_text_color;
for (int c = 0; c < terminal_cols; c++) {
screen_buffer[(terminal_rows - 1) * terminal_cols + c].c = ' ';
screen_buffer[(terminal_rows - 1) * terminal_cols + c].color = shell_config.default_text_color;
}
}
@ -553,9 +610,79 @@ void cmd_putchar(char c) {
return;
}
if (ansi_state == 0) {
if (c == '\x1b') {
ansi_state = 1;
return;
}
} else if (ansi_state == 1) {
if (c == '[') {
ansi_state = 2;
ansi_param_count = 0;
for(int i=0; i<ANSI_MAX_PARAMS; i++) ansi_params[i] = 0;
ansi_private_mode = false;
return;
} else {
ansi_state = 0; // Unsupported ESC sequence
}
} else if (ansi_state == 2) {
if (c == '?') {
ansi_private_mode = true;
return;
} else if (c >= '0' && c <= '9') {
if (ansi_param_count < ANSI_MAX_PARAMS) {
ansi_params[ansi_param_count] = ansi_params[ansi_param_count] * 10 + (c - '0');
}
return;
} else if (c == ';') {
ansi_param_count++;
return;
} else {
// End of sequence
if (ansi_param_count < ANSI_MAX_PARAMS) ansi_param_count++;
if (c == 'm') {
ansi_handle_sgr();
} else if (c == 'H' || c == 'f') {
int r = ansi_params[0] > 0 ? ansi_params[0] - 1 : 0;
int col = ansi_params[1] > 0 ? ansi_params[1] - 1 : 0;
if (r >= terminal_rows) r = terminal_rows - 1;
if (col >= terminal_cols) col = terminal_cols - 1;
cursor_row = r;
cursor_col = col;
} else if (c == 'A') {
int n = ansi_params[0] > 0 ? ansi_params[0] : 1;
cursor_row -= n; if (cursor_row < 0) cursor_row = 0;
} else if (c == 'B') {
int n = ansi_params[0] > 0 ? ansi_params[0] : 1;
cursor_row += n; if (cursor_row >= terminal_rows) cursor_row = terminal_rows - 1;
} else if (c == 'C') {
int n = ansi_params[0] > 0 ? ansi_params[0] : 1;
cursor_col += n; if (cursor_col >= terminal_cols) cursor_col = terminal_cols - 1;
} else if (c == 'D') {
int n = ansi_params[0] > 0 ? ansi_params[0] : 1;
cursor_col -= n; if (cursor_col < 0) cursor_col = 0;
} else if (c == 'J') {
if (ansi_params[0] == 2) {
cmd_screen_clear();
}
} else if (c == 'K') {
for (int i = cursor_col; i < terminal_cols; i++) {
screen_buffer[cursor_row * terminal_cols + i].c = ' ';
screen_buffer[cursor_row * terminal_cols + i].color = current_color;
}
}
ansi_state = 0;
return;
}
}
if (c == '\n') {
cursor_col = 0;
cursor_row++;
} else if (c == '\r') {
cursor_col = 0;
} else if (c == '\t') {
int spaces = 4 - (cursor_col % 4);
for (int i = 0; i < spaces; i++) cmd_putchar(' ');
@ -563,28 +690,29 @@ void cmd_putchar(char c) {
} else if (c == '\b') {
if (cursor_col > 0) {
cursor_col--;
screen_buffer[cursor_row][cursor_col].c = ' ';
screen_buffer[cursor_row][cursor_col].color = shell_config.default_text_color;
screen_buffer[cursor_row * terminal_cols + cursor_col].c = ' ';
screen_buffer[cursor_row * terminal_cols + cursor_col].color = shell_config.default_text_color;
}
} else {
if (cursor_col >= CMD_COLS) {
if (cursor_col >= terminal_cols) {
cursor_col = 0;
cursor_row++;
}
if (cursor_row >= CMD_ROWS) {
if (cursor_row >= terminal_rows) {
cmd_scroll_up();
cursor_row = CMD_ROWS - 1;
cursor_row = terminal_rows - 1;
}
screen_buffer[cursor_row][cursor_col].c = c;
screen_buffer[cursor_row][cursor_col].color = current_color;
screen_buffer[cursor_row * terminal_cols + cursor_col].c = c;
screen_buffer[cursor_row * terminal_cols + cursor_col].color = current_color;
// Optionally set background color if we support it in CharCell
cursor_col++;
}
if (cursor_row >= CMD_ROWS) {
if (cursor_row >= terminal_rows) {
cmd_scroll_up();
cursor_row = CMD_ROWS - 1;
cursor_row = terminal_rows - 1;
}
// Trigger repaint so output from syscalls is immediately visible
@ -644,10 +772,10 @@ int cmd_get_cursor_col(void) {
// Public for CLI apps to use - clear the terminal screen
void cmd_screen_clear() {
for(int r=0; r<CMD_ROWS; r++) {
for(int c=0; c<CMD_COLS; c++) {
screen_buffer[r][c].c = ' ';
screen_buffer[r][c].color = COLOR_DARK_TEXT;
for(int r=0; r<terminal_rows; r++) {
for(int c=0; c<terminal_cols; c++) {
screen_buffer[r * terminal_cols + c].c = ' ';
screen_buffer[r * terminal_cols + c].color = COLOR_DARK_TEXT;
}
}
cursor_row = 0;
@ -681,7 +809,7 @@ void pager_wrap_content(const char **lines, int count) {
int remaining = len - processed;
int chunk_len = remaining;
if (chunk_len > CMD_COLS) chunk_len = CMD_COLS;
if (chunk_len > terminal_cols) chunk_len = terminal_cols;
// If cutting a word, backtrack to last space
if (chunk_len < remaining) { // Only check if actually wrapping
@ -1649,12 +1777,13 @@ static void cmd_paint(Window *win) {
if (current_mode == MODE_PAGER) {
// Draw Pager Content (Wrapped)
for (int i = 0; i < CMD_ROWS && (pager_top_line + i) < pager_total_lines; i++) {
for (int i = 0; i < terminal_rows && (pager_top_line + i) < pager_total_lines; i++) {
draw_string(start_x, start_y + (i * LINE_HEIGHT), pager_wrapped_lines[pager_top_line + i], shell_config.default_text_color);
}
// Status Bar
draw_string(start_x, start_y + (CMD_ROWS * LINE_HEIGHT), "-- Press Q to quit --", shell_config.default_text_color);
// Status Bar
draw_string(start_x, start_y + (terminal_rows * LINE_HEIGHT), "-- Press Q to quit --", shell_config.default_text_color);
} else {
// Draw Cursor
if (win->focused) {
@ -1663,11 +1792,11 @@ static void cmd_paint(Window *win) {
}
// Draw Shell Buffer
for (int r = 0; r < CMD_ROWS; r++) {
for (int c = 0; c < CMD_COLS; c++) {
char ch = screen_buffer[r][c].c;
for (int r = 0; r < terminal_rows; r++) {
for (int c = 0; c < terminal_cols; c++) {
char ch = screen_buffer[r * terminal_cols + c].c;
if (ch != 0 && ch != ' ') {
uint32_t color = screen_buffer[r][c].color;
uint32_t color = screen_buffer[r * terminal_cols + c].color;
// If cursor is on this character, and cursor color is bright, use background color for char
if (r == cursor_row && c == cursor_col && win->focused) {
color = shell_config.bg_color;
@ -1677,6 +1806,90 @@ static void cmd_paint(Window *win) {
}
}
}
// Column/Row Indicator in top-right
char size_buf[32];
itoa(terminal_cols, size_buf);
int p = 0; while(size_buf[p]) p++; size_buf[p++] = 'x'; itoa(terminal_rows, size_buf + p);
draw_string(win->x + win->w - 80, win->y + 5, size_buf, 0xFFAAAAAA);
}
void cmd_handle_resize(Window *win, int w, int h) {
(void)win;
int new_cols = (w - 20) / CHAR_WIDTH;
int new_rows = (h - 50) / LINE_HEIGHT;
if (new_cols < 20) new_cols = 20;
if (new_rows < 5) new_rows = 5;
if (new_cols == terminal_cols && new_rows == terminal_rows) return;
CharCell *new_buffer = (CharCell*)kmalloc(new_rows * new_cols * sizeof(CharCell));
for (int i = 0; i < new_rows * new_cols; i++) {
new_buffer[i].c = ' ';
new_buffer[i].color = shell_config.default_text_color;
}
// Reflow Logic: Copy characters and track cursor
int new_r = 0;
int new_c = 0;
int target_cursor_r = 0;
int target_cursor_c = 0;
for (int r = 0; r < terminal_rows; r++) {
// Find logical end of this line for copying
int last_c = terminal_cols - 1;
while (last_c >= 0 && screen_buffer[r * terminal_cols + last_c].c == ' ') last_c--;
// If this is the cursor row, we MUST copy up to the cursor col at least
if (r == cursor_row) {
if (last_c < cursor_col) last_c = cursor_col;
}
for (int c = 0; c <= last_c; c++) {
if (r == cursor_row && c == cursor_col) {
target_cursor_r = new_r;
target_cursor_c = new_c;
}
if (new_c >= new_cols) {
new_c = 0;
new_r++;
}
if (new_r < new_rows) {
new_buffer[new_r * new_cols + new_c] = screen_buffer[r * terminal_cols + c];
new_c++;
}
}
// If it was the cursor row, we've found our target position
if (r == cursor_row) break;
// Force newline after each source line if it wasn't a wrap
if (last_c < terminal_cols - 1) {
new_c = 0;
new_r++;
}
}
kfree(screen_buffer);
screen_buffer = new_buffer;
terminal_rows = new_rows;
terminal_cols = new_cols;
cursor_row = target_cursor_r;
cursor_col = target_cursor_c;
if (cursor_row >= terminal_rows) cursor_row = terminal_rows - 1;
if (cursor_col >= terminal_cols) cursor_col = terminal_cols - 1;
}
void cmd_handle_click(Window *win, int x, int y) {
(void)win;
// Basic mouse support: convert pixel to char coords
int c = (x - 10) / CHAR_WIDTH;
int r = (y - 25) / LINE_HEIGHT;
if (r >= 0 && r < terminal_rows && c >= 0 && c < terminal_cols) {
// If telnet or other app wants mouse reporting, we'd handle it here
}
}
static void cmd_key(Window *target, char c) {
@ -1687,21 +1900,21 @@ static void cmd_key(Window *target, char c) {
} else if (c == 17) { // UP
if (pager_top_line > 0) pager_top_line--;
} else if (c == 18) { // DOWN
if (pager_top_line < pager_total_lines - CMD_ROWS) pager_top_line++;
if (pager_top_line < pager_total_lines - terminal_rows) pager_top_line++;
}
return;
}
// Shell Mode
if (c == '\n') { // Enter
char cmd_buf[CMD_COLS + 1];
char cmd_buf[512]; // Use a fixed large enough buffer or dynamic
int len = 0;
int prompt_len = current_prompt_len;
for (int i = prompt_len; i < CMD_COLS; i++) {
char ch = screen_buffer[cursor_row][i].c;
if (ch == 0 || (ch == ' ' && i > prompt_len && screen_buffer[cursor_row][i+1].c == 0)) break;
cmd_buf[len++] = ch;
for (int i = prompt_len; i < terminal_cols; i++) {
char ch = screen_buffer[cursor_row * terminal_cols + i].c;
if (ch == 0 || (ch == ' ' && i > prompt_len && (i + 1 < terminal_cols) && screen_buffer[cursor_row * terminal_cols + i + 1].c == 0)) break;
if (len < 511) cmd_buf[len++] = ch;
}
while (len > 0 && cmd_buf[len-1] == ' ') len--;
cmd_buf[len] = 0;
@ -1723,9 +1936,9 @@ static void cmd_key(Window *target, char c) {
// Save current line
int len = 0;
int prompt_len = current_prompt_len;
for (int i = prompt_len; i < CMD_COLS; i++) {
char ch = screen_buffer[cursor_row][i].c;
if (ch == 0 || (ch == ' ' && i > prompt_len && screen_buffer[cursor_row][i+1].c == 0)) break;
for (int i = prompt_len; i < terminal_cols; i++) {
char ch = screen_buffer[cursor_row * terminal_cols + i].c;
if (ch == 0 || (ch == ' ' && i > prompt_len && (i + 1 < terminal_cols) && screen_buffer[cursor_row * terminal_cols + i + 1].c == 0)) break;
history_save_buf[len++] = ch;
}
while (len > 0 && history_save_buf[len-1] == ' ') len--;
@ -1756,27 +1969,27 @@ static void cmd_key(Window *target, char c) {
cursor_col--;
}
} else if (c == 20) { // RIGHT
if (cursor_col < CMD_COLS - 1) {
if (cursor_col < terminal_cols - 1) {
cursor_col++;
}
} else if (c == '\b') { // Backspace
if (cursor_col > current_prompt_len) {
// Shift characters to the left
for (int i = cursor_col; i < CMD_COLS; i++) {
screen_buffer[cursor_row][i - 1] = screen_buffer[cursor_row][i];
for (int i = cursor_col; i < terminal_cols; i++) {
screen_buffer[cursor_row * terminal_cols + i - 1] = screen_buffer[cursor_row * terminal_cols + i];
}
screen_buffer[cursor_row][CMD_COLS - 1].c = ' ';
screen_buffer[cursor_row * terminal_cols + terminal_cols - 1].c = ' ';
cursor_col--;
wm_mark_dirty(win_cmd.x, win_cmd.y, win_cmd.w, win_cmd.h);
}
} else {
if (c >= 32 && c <= 126) {
// Shift characters to the right
for (int i = CMD_COLS - 1; i > cursor_col; i--) {
screen_buffer[cursor_row][i] = screen_buffer[cursor_row][i - 1];
for (int i = terminal_cols - 1; i > cursor_col; i--) {
screen_buffer[cursor_row * terminal_cols + i] = screen_buffer[cursor_row * terminal_cols + i - 1];
}
screen_buffer[cursor_row][cursor_col].c = c;
screen_buffer[cursor_row][cursor_col].color = current_color;
screen_buffer[cursor_row * terminal_cols + cursor_col].c = c;
screen_buffer[cursor_row * terminal_cols + cursor_col].color = current_color;
cursor_col++;
wm_mark_dirty(win_cmd.x, win_cmd.y, win_cmd.w, win_cmd.h);
}
@ -2109,19 +2322,28 @@ void cmd_init(void) {
// Load config after files are created
cmd_load_config();
// Default sizes
terminal_cols = DEFAULT_CMD_COLS;
terminal_rows = DEFAULT_CMD_ROWS;
// Allocate screen buffer
screen_buffer = (CharCell*)kmalloc(terminal_cols * terminal_rows * sizeof(CharCell));
win_cmd.title = shell_config.title_text;
win_cmd.x = 50;
win_cmd.y = 50;
win_cmd.w = (CMD_COLS * CHAR_WIDTH) + 20;
win_cmd.h = (CMD_ROWS * LINE_HEIGHT) + 50;
win_cmd.w = (terminal_cols * CHAR_WIDTH) + 20;
win_cmd.h = (terminal_rows * LINE_HEIGHT) + 50;
win_cmd.visible = false;
win_cmd.focused = false;
win_cmd.z_index = 0;
win_cmd.paint = cmd_paint;
win_cmd.handle_key = cmd_key;
win_cmd.handle_click = NULL;
win_cmd.handle_click = cmd_handle_click;
win_cmd.handle_right_click = NULL;
win_cmd.handle_resize = cmd_handle_resize;
win_cmd.resizable = true;
// Initialize cmd state (per-window context)
CmdState *state = (CmdState*)kmalloc(sizeof(CmdState));

View file

@ -22,6 +22,9 @@ void cmd_screen_clear(void);
void cmd_increment_msg_count(void);
void cmd_reset_msg_count(void);
void cmd_handle_resize(Window *win, int w, int h);
void cmd_handle_click(Window *win, int x, int y);
uint32_t cmd_get_config_value(const char *key);
void cmd_set_current_color(uint32_t color);

View file

@ -399,3 +399,12 @@ void process_push_gui_event(process_t *proc, gui_event_t *ev) {
proc->gui_event_tail = next_tail;
}
process_t* process_get_by_ui_window(void *win) {
for (int i = 0; i < MAX_PROCESSES; i++) {
if (processes[i].pid != 0xFFFFFFFF && processes[i].ui_window == win) {
return &processes[i];
}
}
return NULL;
}

View file

@ -54,6 +54,7 @@ uint64_t process_schedule(uint64_t current_rsp);
uint64_t process_terminate_current(void);
void process_push_gui_event(process_t *proc, gui_event_t *ev);
process_t* process_get_by_ui_window(void* win);
#endif

View file

@ -107,7 +107,8 @@ uint64_t keyboard_handler(registers_t *regs) {
// --- Mouse ---
static uint8_t mouse_cycle = 0;
static int8_t mouse_byte[3];
static int8_t mouse_byte[4];
static bool mouse_has_wheel = false;
void mouse_wait(uint8_t type) {
uint32_t timeout = 100000;
@ -155,6 +156,16 @@ void mouse_init(void) {
mouse_write(0xF6);
mouse_read();
// Enable Wheel - Magic Sequence
mouse_write(0xF3); mouse_read(); mouse_write(200); mouse_read();
mouse_write(0xF3); mouse_read(); mouse_write(100); mouse_read();
mouse_write(0xF3); mouse_read(); mouse_write(80); mouse_read();
mouse_write(0xF2);
mouse_read();
uint8_t id = mouse_read();
if (id == 3) mouse_has_wheel = true;
// Enable Streaming
mouse_write(0xF4);
mouse_read();
@ -171,8 +182,8 @@ uint64_t mouse_handler(registers_t *regs) {
uint8_t b = inb(0x60);
if (mouse_cycle == 0) {
if ((b & 0x08) == 0) { // Sync check
// Skip
if ((b & 0x08) == 0) {
// Out of sync
} else {
mouse_byte[0] = b;
mouse_cycle++;
@ -180,20 +191,27 @@ uint64_t mouse_handler(registers_t *regs) {
} else if (mouse_cycle == 1) {
mouse_byte[1] = b;
mouse_cycle++;
} else {
} else if (mouse_cycle == 2) {
mouse_byte[2] = b;
if (mouse_has_wheel) {
mouse_cycle++;
} else {
mouse_cycle = 0;
// Packet Full
int8_t dx = mouse_byte[1];
int8_t dy = mouse_byte[2];
// Send to WM
wm_handle_mouse(dx, -dy, mouse_byte[0] & 0x07);
wm_handle_mouse(dx, -dy, mouse_byte[0] & 0x07, 0);
}
} else if (mouse_cycle == 3) {
mouse_byte[3] = b;
mouse_cycle = 0;
int8_t dx = mouse_byte[1];
int8_t dy = mouse_byte[2];
int8_t dz = mouse_byte[3];
wm_handle_mouse(dx, -dy, mouse_byte[0] & 0x07, -dz);
}
outb(0x20, 0x20);
outb(0xA0, 0x20); // Slave EOI
outb(0xA0, 0x20);
return (uint64_t)regs;
}

View file

@ -164,9 +164,11 @@ static uint64_t syscall_handler_inner(uint64_t syscall_num, uint64_t arg1, uint6
serial_write("Kernel: Error - kmalloc failed for Window\n");
return 0;
}
serial_write("Kernel: Window allocated.\n");
extern void mem_memset(void *dest, int val, size_t len);
mem_memset(win, 0, sizeof(Window));
// Copy title from user space to kernel space so wm.c can access it safely
int title_len = 0;
if (title) {
@ -240,6 +242,8 @@ static uint64_t syscall_handler_inner(uint64_t syscall_num, uint64_t arg1, uint6
win->handle_right_click = user_window_right_click;
win->handle_close = user_window_close;
win->handle_key = user_window_key;
win->handle_resize = NULL;
win->resizable = false;
proc->ui_window = win;
wm_add_window(win);

View file

@ -39,6 +39,9 @@ $(BIN_DIR)/viewer.elf: $(LIBC_OBJS) $(BIN_DIR)/viewer.o $(BIN_DIR)/nanojpeg.o
$(BIN_DIR)/settings.elf: $(LIBC_OBJS) $(BIN_DIR)/settings.o $(BIN_DIR)/nanojpeg.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/browser.elf: $(LIBC_OBJS) $(BIN_DIR)/browser.o $(BIN_DIR)/nanojpeg.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/%.elf: $(LIBC_OBJS) $(BIN_DIR)/%.o
$(LD) $(LDFLAGS) $^ -o $@

View file

@ -0,0 +1,587 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
#include "libc/syscall.h"
#include "libc/libui.h"
#include "nanojpeg.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#define WIN_W 1280
#define WIN_H 960
#define URL_BAR_H 30
#define SCROLL_BAR_W 16
#define RESP_BUF_SIZE (1024 * 1024)
#define COLOR_URL_BAR 0xFF303030
#define COLOR_URL_TEXT 0xFFF0F0F0
#define COLOR_BG 0xFFFFFFFF
#define COLOR_TEXT 0xFF000000
#define COLOR_LINK 0xFF0000EE
#define COLOR_SCROLL_BG 0xFFEEEEEE
#define COLOR_SCROLL_BTN 0xFFCCCCCC
static char* strstr(const char* haystack, const char* needle) {
if (!*needle) return (char*)haystack;
for (; *haystack; haystack++) {
const char *h = haystack;
const char *n = needle;
while (*h && *n && *h == *n) {
h++; n++;
}
if (!*n) return (char*)haystack;
}
return NULL;
}
static char* str_istrstr(const char* haystack, const char* needle) {
if (!*needle) return (char*)haystack;
for (; *haystack; haystack++) {
const char *h = haystack;
const char *n = needle;
while (*h && *n) {
char ch = *h; char cn = *n;
if (ch >= 'A' && ch <= 'Z') ch += 32;
if (cn >= 'A' && cn <= 'Z') cn += 32;
if (ch != cn) break;
h++; n++;
}
if (!*n) return (char*)haystack;
}
return NULL;
}
static long strtol(const char* nptr, char** endptr, int base) {
long res = 0;
while (*nptr == ' ' || *nptr == '\t' || *nptr == '\n' || *nptr == '\r') nptr++;
bool neg = false;
if (*nptr == '-') { neg = true; nptr++; }
else if (*nptr == '+') nptr++;
while (*nptr) {
int v = -1;
if (*nptr >= '0' && *nptr <= '9') v = *nptr - '0';
else if (*nptr >= 'a' && *nptr <= 'z') v = *nptr - 'a' + 10;
else if (*nptr >= 'A' && *nptr <= 'Z') v = *nptr - 'A' + 10;
if (v < 0 || v >= base) break;
res = res * base + v;
nptr++;
}
if (endptr) *endptr = (char*)nptr;
return neg ? -res : res;
}
typedef enum { TAG_NONE, TAG_IMG, TAG_INPUT, TAG_BUTTON } HTMLTag;
typedef struct {
char content[1024];
int x, y, w, h;
HTMLTag tag;
char link_url[256];
char attr_value[256];
uint32_t color;
bool centered;
bool bold;
uint32_t *img_pixels;
int img_w, img_h;
} RenderElement;
#define MAX_ELEMENTS 8192
static RenderElement elements[MAX_ELEMENTS];
static int element_count = 0;
static char url_input_buffer[512] = "http://frogfind.com";
static int url_cursor = 19;
static char current_host[256] = "frogfind.com";
static ui_window_t win_browser;
static int scroll_y = 0;
static int total_content_height = 0;
static int focused_element = -1; // -1 for URL bar, >= 0 for page elements
static void browser_clear(void) {
for (int i = 0; i < element_count; i++) {
if (elements[i].img_pixels) free(elements[i].img_pixels);
}
element_count = 0;
total_content_height = 0;
}
static bool str_istarts_with(const char *str, const char *prefix) {
while (*prefix) {
char s = *str; char p = *prefix;
if (s >= 'A' && s <= 'Z') s += 32;
if (p >= 'A' && p <= 'Z') p += 32;
if (s != p) return false;
str++; prefix++;
}
return true;
}
static int fetch_content(const char *url, char *dest_buf, int max_len) {
const char* host_start = url;
if (url[0] == 'h' && url[1] == 't' && url[2] == 't' && url[3] == 'p') {
if (url[4] == 's') host_start = url + 8;
else host_start = url + 7;
}
char hostname[256];
int i = 0;
while (host_start[i] && host_start[i] != '/' && i < 255) {
hostname[i] = host_start[i];
i++;
}
hostname[i] = 0;
if (i > 0) {
int k=0; while(hostname[k]) { current_host[k] = hostname[k]; k++; } current_host[k] = 0;
}
net_ipv4_address_t ip;
if (sys_dns_lookup(hostname, &ip) != 0) return 0;
if (sys_tcp_connect(&ip, 80) != 0) return 0;
const char* path = host_start + i;
if (*path == 0) path = "/";
char request[2048];
char* r = request;
const char* s;
s = "GET "; while(*s) *r++ = *s++;
s = path; while(*s) *r++ = *s++;
s = " HTTP/1.1\r\nHost: "; while(*s) *r++ = *s++;
s = hostname; while(*s) *r++ = *s++;
s = "\r\nUser-Agent: BoredOS/1.0\r\nAccept: */*\r\nConnection: close\r\n\r\n"; while(*s) *r++ = *s++;
sys_tcp_send(request, r - request);
int total = 0;
while (1) {
int len = sys_tcp_recv(dest_buf + total, max_len - 1 - total);
if (len <= 0) break;
total += len;
if (total >= max_len - 1) break;
}
dest_buf[total] = 0;
sys_tcp_close();
return total;
}
static void decode_jpeg(unsigned char *data, int len, RenderElement *el) {
njInit();
if (njDecode(data, len) == NJ_OK) {
int w = njGetWidth(); int h = njGetHeight();
unsigned char *rgb = njGetImage();
if (rgb) {
int fit_w = w; int fit_h = h;
if (fit_w > WIN_W - 60) { fit_h = fit_h * (WIN_W - 60) / fit_w; fit_w = WIN_W - 60; }
if (fit_h > 400) { fit_w = fit_w * 400 / fit_h; fit_h = 400; }
el->img_pixels = malloc(fit_w * fit_h * sizeof(uint32_t));
if (el->img_pixels) {
for (int y = 0; y < fit_h; y++) {
int sy = y * h / fit_h;
for (int x = 0; x < fit_w; x++) {
int sx = x * w / fit_w;
int idx = (sy * w + sx) * 3;
el->img_pixels[y * fit_w + x] = 0xFF000000 | (rgb[idx] << 16) | (rgb[idx+1] << 8) | rgb[idx+2];
}
}
el->img_w = fit_w; el->img_h = fit_h;
}
}
}
njDone();
}
static int decode_chunked_bin(char *body, int total_len) {
char *src = body; char *dst = body;
int remaining = total_len;
int final_len = 0;
while (remaining > 0) {
char *endptr;
int chunk_size = (int)strtol(src, &endptr, 16);
int head_len = endptr - src;
src = endptr;
if (*src == '\r') { src++; head_len++; }
if (*src == '\n') { src++; head_len++; }
remaining -= head_len;
if (chunk_size == 0) break;
if (remaining < chunk_size) chunk_size = remaining;
for (int i = 0; i < chunk_size; i++) *dst++ = *src++;
final_len += chunk_size;
remaining -= chunk_size;
if (remaining > 0 && *src == '\r') { src++; remaining--; }
if (remaining > 0 && *src == '\n') { src++; remaining--; }
}
*dst = 0;
return final_len;
}
static void load_image(RenderElement *el) {
char url[512];
if (str_istarts_with(el->attr_value, "http")) {
int k=0; while(el->attr_value[k]) { url[k] = el->attr_value[k]; k++; } url[k] = 0;
} else {
char *u = url;
const char *s = "http://"; while(*s) *u++ = *s++;
s = current_host; while(*s) *u++ = *s++;
if (el->attr_value[0] != '/') *u++ = '/';
s = el->attr_value; while(*s) *u++ = *s++;
*u = 0;
}
static char img_resp[RESP_BUF_SIZE];
int resp_len = fetch_content(url, img_resp, sizeof(img_resp));
char *body = strstr(img_resp, "\r\n\r\n");
if (body) {
body += 4;
int hdr_len = body - img_resp;
int body_len = resp_len - hdr_len;
if (strstr(img_resp, "Transfer-Encoding: chunked")) {
body_len = decode_chunked_bin(body, body_len);
}
decode_jpeg((unsigned char*)body, body_len, el);
}
}
static int line_elements[512];
static int line_element_count = 0;
static int cur_line_y = 10;
static int cur_line_x = 10;
static void flush_line(bool centered) {
if (line_element_count == 0) return;
int line_w = 0;
for (int i = 0; i < line_element_count; i++) line_w += elements[line_elements[i]].w;
int offset_x = centered ? (WIN_W - SCROLL_BAR_W - line_w) / 2 : 10;
if (offset_x < 10) offset_x = 10;
int max_h = 16;
for (int i = 0; i < line_element_count; i++) {
RenderElement *el = &elements[line_elements[i]];
el->x = offset_x;
el->y = cur_line_y;
offset_x += el->w;
if (el->tag == TAG_IMG && el->img_h + 10 > max_h) max_h = el->img_h + 10;
if ((el->tag == TAG_INPUT || el->tag == TAG_BUTTON) && 24 + 10 > max_h) max_h = 24 + 10;
}
cur_line_y += max_h;
cur_line_x = 10;
line_element_count = 0;
total_content_height = cur_line_y + 50;
}
static void parse_html(const char *html) {
browser_clear();
cur_line_y = 10; cur_line_x = 10; line_element_count = 0;
int i = 0; bool is_centered = false; bool is_bold = false; uint32_t current_color = COLOR_TEXT; char current_link[256] = "";
bool skip_content = false;
while (html[i] && element_count < MAX_ELEMENTS) {
if (html[i] == '<') {
if (html[i+1] == '!' && html[i+2] == '-' && html[i+3] == '-') {
i += 4;
while (html[i] && !(html[i] == '-' && html[i+1] == '-' && html[i+2] == '>')) i++;
if (html[i]) i += 3;
continue;
}
i++; char tag_name[64]; int tag_idx = 0;
while (html[i] && html[i] != '>' && html[i] != ' ' && tag_idx < 63) tag_name[tag_idx++] = html[i++];
tag_name[tag_idx] = 0;
char attr_buf[1024] = "";
if (html[i] == ' ') {
i++; int a_idx = 0;
while (html[i] && html[i] != '>' && a_idx < 1023) attr_buf[a_idx++] = html[i++];
attr_buf[a_idx] = 0;
}
if (html[i] == '>') i++;
if (tag_name[0] == '/') {
if (str_istarts_with(tag_name+1, "center")) { flush_line(is_centered); is_centered = false; }
else if (tag_name[1] == 'h' && tag_name[2] >= '1' && tag_name[2] <= '6') { flush_line(is_centered); cur_line_y += 10; is_bold = false; }
else if (str_istarts_with(tag_name+1, "a")) current_link[0] = 0;
else if (str_istarts_with(tag_name+1, "p") || str_istarts_with(tag_name+1, "li") || str_istarts_with(tag_name+1, "ol") || str_istarts_with(tag_name+1, "form") || str_istarts_with(tag_name+1, "div")) flush_line(is_centered);
else if (str_istarts_with(tag_name+1, "font")) current_color = COLOR_TEXT;
else if (str_istarts_with(tag_name+1, "head") || (tag_name[1] == 's' && tag_name[2] == 'c') || (tag_name[1] == 's' && tag_name[2] == 't') || str_istarts_with(tag_name+1, "title") || str_istarts_with(tag_name+1, "noscript") || str_istarts_with(tag_name+1, "style")) skip_content = false;
} else {
if (str_istarts_with(tag_name, "center")) { flush_line(is_centered); is_centered = true; }
else if (tag_name[0] == 'h' && tag_name[1] >= '1' && tag_name[1] <= '6') { flush_line(is_centered); cur_line_y += 10; is_bold = true; }
else if (str_istarts_with(tag_name, "br")) flush_line(is_centered);
else if (str_istarts_with(tag_name, "h") || str_istarts_with(tag_name, "p") || str_istarts_with(tag_name, "hr") || str_istarts_with(tag_name, "li") || str_istarts_with(tag_name, "ol") || str_istarts_with(tag_name, "form") || str_istarts_with(tag_name, "div")) flush_line(is_centered);
else if (str_istarts_with(tag_name, "head") || str_istarts_with(tag_name, "script") || str_istarts_with(tag_name, "style") || str_istarts_with(tag_name, "title") || str_istarts_with(tag_name, "noscript") || str_istarts_with(tag_name, "meta") || str_istarts_with(tag_name, "link") || str_istarts_with(tag_name, "!doctype")) skip_content = true;
else if (str_istarts_with(tag_name, "body")) skip_content = false;
else if (str_istarts_with(tag_name, "a")) {
char *href = str_istrstr(attr_buf, "href=\"");
if (href) {
href += 6; int l = 0;
while(href[l] && href[l] != '\"' && l < 255) { current_link[l] = href[l]; l++; }
current_link[l] = 0;
}
} else if (str_istarts_with(tag_name, "img")) {
RenderElement *el = &elements[element_count++];
int idx = element_count - 1;
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
el->tag = TAG_IMG; el->w = 100; el->h = 80; el->centered = is_centered;
char *src = str_istrstr(attr_buf, "src=\"");
if (src) {
src += 5; int l = 0;
while(src[l] && src[l] != '\"' && l < 255) { el->attr_value[l] = src[l]; l++; }
el->attr_value[l] = 0; load_image(el);
}
if (el->img_pixels) { el->w = el->img_w; el->h = el->img_h; }
line_elements[line_element_count++] = element_count - 1;
if (is_centered || cur_line_x + el->w > WIN_W - SCROLL_BAR_W - 20) flush_line(is_centered);
else cur_line_x += el->w;
} else if (str_istarts_with(tag_name, "input")) {
RenderElement *el = &elements[element_count++];
int idx = element_count - 1;
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
el->tag = TAG_INPUT; el->w = 160; el->h = 24; el->centered = is_centered;
char *val = str_istrstr(attr_buf, "value=\"");
char *ph = str_istrstr(attr_buf, "placeholder=\"");
char *type = str_istrstr(attr_buf, "type=\"");
if (type && str_istarts_with(type+6, "submit")) el->tag = TAG_BUTTON;
if (val) {
val += 7; int l = 0;
while(val[l] && val[l] != '\"' && l < 255) { el->attr_value[l] = val[l]; l++; }
el->attr_value[l] = 0;
} else if (ph) {
ph += 13; int l = 0;
while(ph[l] && ph[l] != '\"' && l < 255) { el->attr_value[l] = ph[l]; l++; }
el->attr_value[l] = 0;
} else el->attr_value[0] = 0;
if (el->tag == TAG_BUTTON) el->w = (int)strlen(el->attr_value) * 8 + 20;
line_elements[line_element_count++] = element_count - 1;
if (is_centered || cur_line_x + el->w > WIN_W - SCROLL_BAR_W - 20) flush_line(is_centered);
else cur_line_x += el->w;
}
}
} else {
if (!skip_content) {
while (html[i] && html[i] != '<') {
while (html[i] && (html[i] == ' ' || html[i] == '\n' || html[i] == '\r')) i++;
if (!html[i] || html[i] == '<') break;
char word[256]; int w_idx = 0;
while (html[i] && html[i] != '<' && html[i] != ' ' && html[i] != '\n' && html[i] != '\r' && w_idx < 255) {
word[w_idx++] = html[i++];
}
word[w_idx] = 0;
if (w_idx > 0) {
if (element_count >= MAX_ELEMENTS) break;
int word_w = w_idx * 8;
if (cur_line_x + word_w > WIN_W - SCROLL_BAR_W - 20) flush_line(is_centered);
RenderElement *el = &elements[element_count++];
for (int k=0; k<(int)sizeof(RenderElement); k++) ((char*)el)[k] = 0;
int k=0; while(word[k]) { el->content[k] = word[k]; k++; }
el->content[k++] = ' '; el->content[k] = 0;
el->w = (w_idx + 1) * 8; el->h = 16;
el->tag = TAG_NONE; el->color = current_link[0] ? COLOR_LINK : current_color;
el->centered = is_centered; el->bold = is_bold;
if (current_link[0]) { int k=0; while(current_link[k]) { el->link_url[k] = current_link[k]; k++; } el->link_url[k] = 0; }
line_elements[line_element_count++] = element_count - 1;
cur_line_x += el->w;
}
}
} else {
while (html[i] && html[i] != '<') i++;
}
}
}
flush_line(is_centered);
}
static void browser_paint(void) {
ui_draw_rect(win_browser, 0, 0, WIN_W, WIN_H, COLOR_BG);
ui_draw_rect(win_browser, 0, 0, WIN_W, URL_BAR_H, COLOR_URL_BAR);
ui_draw_string(win_browser, 10, 8, url_input_buffer, COLOR_URL_TEXT);
if (focused_element == -1) {
ui_draw_rect(win_browser, 10 + url_cursor * 8, 22, 8, 2, COLOR_URL_TEXT);
}
// Scroll bar
ui_draw_rect(win_browser, WIN_W - SCROLL_BAR_W, URL_BAR_H, SCROLL_BAR_W, WIN_H - URL_BAR_H, COLOR_SCROLL_BG);
int thumb_h = (WIN_H - URL_BAR_H) * (WIN_H - URL_BAR_H) / (total_content_height > WIN_H ? total_content_height : WIN_H);
if (thumb_h < 20) thumb_h = 20;
int thumb_y = URL_BAR_H + (scroll_y * (WIN_H - URL_BAR_H - thumb_h)) / (total_content_height > WIN_H - URL_BAR_H ? total_content_height - (WIN_H - URL_BAR_H) : 1);
ui_draw_rect(win_browser, WIN_W - SCROLL_BAR_W + 2, thumb_y, SCROLL_BAR_W - 4, thumb_h, COLOR_SCROLL_BTN);
for (int i = 0; i < element_count; i++) {
RenderElement *el = &elements[i];
int draw_y = el->y - scroll_y + URL_BAR_H;
if (draw_y < URL_BAR_H - 1000 || draw_y > WIN_H) continue;
if (el->tag == TAG_IMG) {
if (el->img_pixels) ui_draw_image(win_browser, el->x, draw_y, el->img_w, el->img_h, el->img_pixels);
else ui_draw_rect(win_browser, el->x, draw_y, 100, 80, 0xFFCCCCCC);
} else if (el->tag == TAG_INPUT) {
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFFFFFFFF);
uint32_t border = (focused_element == i) ? 0xFF0000FF : 0xFF808080;
ui_draw_rect(win_browser, el->x, draw_y, el->w, 1, border);
ui_draw_rect(win_browser, el->x, draw_y + el->h - 1, el->w, 1, border);
ui_draw_rect(win_browser, el->x, draw_y, 1, el->h, border);
ui_draw_rect(win_browser, el->x + el->w - 1, draw_y, 1, el->h, border);
ui_draw_string(win_browser, el->x + 5, draw_y + 4, el->attr_value, (focused_element == i) ? 0xFF000000 : 0xFF808080);
if (focused_element == i) {
int ilen = 0; while(el->attr_value[ilen]) ilen++;
ui_draw_rect(win_browser, el->x + 5 + ilen * 8, draw_y + 18, 8, 2, 0xFF000000);
}
} else if (el->tag == TAG_BUTTON) {
ui_draw_rect(win_browser, el->x, draw_y, el->w, el->h, 0xFFDDDDDD);
ui_draw_rect(win_browser, el->x, draw_y, el->w, 1, 0xFFFFFFFF);
ui_draw_rect(win_browser, el->x, draw_y + el->h - 1, el->w, 1, 0xFF888888);
ui_draw_rect(win_browser, el->x, draw_y, 1, el->h, 0xFFFFFFFF);
ui_draw_rect(win_browser, el->x + el->w - 1, draw_y, 1, el->h, 0xFF888888);
ui_draw_string(win_browser, el->x + 10, draw_y + 4, el->attr_value, 0xFF000000);
} else {
ui_draw_string(win_browser, el->x, draw_y, el->content, el->color);
if (el->bold) ui_draw_string(win_browser, el->x + 1, draw_y, el->content, el->color);
}
}
}
static void navigate(const char *url) {
static char main_resp[RESP_BUF_SIZE];
int resp_len = fetch_content(url, main_resp, sizeof(main_resp));
if (resp_len <= 0) return;
char *body = strstr(main_resp, "\r\n\r\n");
if (body) {
body += 4;
int hdr_len = body - main_resp;
int body_len = resp_len - hdr_len;
if (strstr(main_resp, "Transfer-Encoding: chunked")) {
body_len = decode_chunked_bin(body, body_len);
}
parse_html(body);
}
}
static void net_init_if_needed(void) {
if (!sys_network_is_initialized()) sys_network_init();
if (!sys_network_has_ip()) sys_network_dhcp_acquire();
}
int main(int argc, char **argv) {
win_browser = ui_window_create("Web Browser", 50, 50, WIN_W, WIN_H);
net_init_if_needed();
if (argc > 1) { int k=0; while(argv[1][k]) { url_input_buffer[k] = argv[1][k]; k++; } url_input_buffer[k] = 0; url_cursor = k; }
navigate(url_input_buffer);
browser_paint(); ui_mark_dirty(win_browser, 0, 0, WIN_W, WIN_H);
gui_event_t ev;
while (1) {
if (ui_get_event(win_browser, &ev)) {
if (ev.type == GUI_EVENT_PAINT) { browser_paint(); ui_mark_dirty(win_browser, 0, 0, WIN_W, WIN_H); }
else if (ev.type == GUI_EVENT_CLICK) {
int mx = ev.arg1;
if (mx >= WIN_W - SCROLL_BAR_W) {
if (ev.arg2 < URL_BAR_H + (WIN_H - URL_BAR_H)/2) scroll_y -= 100;
else scroll_y += 100;
if (scroll_y < 0) scroll_y = 0;
browser_paint(); ui_mark_dirty(win_browser, 0, 0, WIN_W, WIN_H);
continue;
}
if (ev.arg2 < URL_BAR_H) { focused_element = -1; browser_paint(); ui_mark_dirty(win_browser, 0, 0, WIN_W, WIN_H); continue; }
int my = ev.arg2 - URL_BAR_H + scroll_y;
bool found = false;
for (int i = 0; i < element_count; i++) {
RenderElement *el = &elements[i];
if (mx >= el->x && mx < el->x + el->w && my >= el->y && my < el->y + el->h) {
if (el->tag == TAG_INPUT) {
focused_element = i; found = true; browser_paint(); ui_mark_dirty(win_browser, 0, 0, WIN_W, WIN_H); break;
}
if (el->tag == TAG_BUTTON) {
// Find the first input in the page and use its value for search?
// For simplicity, find the first focused-style input or just search frogfind
int search_idx = -1;
for (int k=0; k<element_count; k++) if (elements[k].tag == TAG_INPUT) { search_idx = k; break; }
if (search_idx >= 0) {
char search_url[1024] = "http://frogfind.com/?q=";
int k = 23;
for (int m=0; elements[search_idx].attr_value[m] && k < 1020; m++) {
char sc = elements[search_idx].attr_value[m];
if (sc == ' ') search_url[k++] = '+';
else search_url[k++] = sc;
}
search_url[k] = 0;
int j=0; while(search_url[j]) { url_input_buffer[j] = search_url[j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
browser_paint(); ui_mark_dirty(win_browser, 0, 0, WIN_W, WIN_H);
found = true; break;
}
}
if (el->link_url[0]) {
char new_url[512];
if (el->link_url[0] == '/') {
char *u = new_url; const char *s = "http://"; while(*s) *u++ = *s++;
s = current_host; while(*s) *u++ = *s++;
s = el->link_url; while(*s) *u++ = *s++; *u = 0;
} else if (str_istarts_with(el->link_url, "http")) {
int k=0; while(el->link_url[k]) { new_url[k] = el->link_url[k]; k++; } new_url[k] = 0;
} else {
char *u = new_url; const char *s = "http://"; while(*s) *u++ = *s++;
s = current_host; while(*s) *u++ = *s++; if (current_host[0] && current_host[0] != '/') *u++ = '/';
s = el->link_url; while(*s) *u++ = *s++; *u = 0;
}
int j=0; while(new_url[j]) { url_input_buffer[j] = new_url[j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
browser_paint(); ui_mark_dirty(win_browser, 0, 0, WIN_W, WIN_H);
found = true; break;
}
}
}
if (!found) { focused_element = -1; browser_paint(); ui_mark_dirty(win_browser, 0, 0, WIN_W, WIN_H); }
} else if (ev.type == GUI_EVENT_KEY) {
char c = (char)ev.arg1;
if (focused_element == -1) {
if (c == 13 || c == 10) { navigate(url_input_buffer); scroll_y = 0; }
else if (c == 127 || c == 8) { if (url_cursor > 0) url_input_buffer[--url_cursor] = 0; }
else if (c >= 32 && c <= 126 && url_cursor < 511) { url_input_buffer[url_cursor++] = c; url_input_buffer[url_cursor] = 0; }
} else {
RenderElement *el = &elements[focused_element];
int len = 0; while(el->attr_value[len]) len++;
if (c == 13 || c == 10) {
if (strstr(url_input_buffer, "frogfind.com")) {
char search_url[1024] = "http://frogfind.com/?q=";
int k = 23;
for (int m=0; el->attr_value[m] && k < 1020; m++) {
char sc = el->attr_value[m];
if (sc == ' ') search_url[k++] = '+';
else search_url[k++] = sc;
}
search_url[k] = 0;
int j=0; while(search_url[j]) { url_input_buffer[j] = search_url[j]; j++; } url_input_buffer[j] = 0; url_cursor = j;
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
} else {
focused_element = -1;
}
}
else if (c == 127 || c == 8) { if (len > 0) el->attr_value[--len] = 0; }
else if (c >= 32 && c <= 126 && len < 255) { el->attr_value[len++] = c; el->attr_value[len] = 0; }
}
if (c == 17) { scroll_y -= 40; }
else if (c == 18) { scroll_y += 40; }
else if (c == 19) { scroll_y -= 200; } // Page Up
else if (c == 20) { scroll_y += 200; } // Page Down
int max_scroll = total_content_height - (WIN_H - URL_BAR_H);
if (max_scroll < 0) max_scroll = 0;
if (scroll_y > max_scroll) scroll_y = max_scroll;
if (scroll_y < 0) scroll_y = 0;
browser_paint(); ui_mark_dirty(win_browser, 0, 0, WIN_W, WIN_H);
} else if (ev.type == 9) { // GUI_EVENT_MOUSE_WHEEL
scroll_y += ev.arg1 * 20;
int max_scroll = total_content_height - (WIN_H - URL_BAR_H);
if (max_scroll < 0) max_scroll = 0;
if (scroll_y > max_scroll) scroll_y = max_scroll;
if (scroll_y < 0) scroll_y = 0;
browser_paint(); ui_mark_dirty(win_browser, 0, 0, WIN_W, WIN_H);
} else if (ev.type == GUI_EVENT_CLOSE) sys_exit(0);
} else { for(volatile int x=0; x<10000; x++); }
}
return 0;
}

View file

@ -24,6 +24,7 @@
#define GUI_EVENT_MOUSE_DOWN 6
#define GUI_EVENT_MOUSE_UP 7
#define GUI_EVENT_MOUSE_MOVE 8
#define GUI_EVENT_MOUSE_WHEEL 9
typedef struct {
int type;

View file

@ -57,6 +57,9 @@ void (*wm_custom_paint_hook)(void) = NULL;
// Dragging State
static bool is_dragging = false;
static bool is_resizing = false;
static int drag_start_w = 0;
static int drag_start_h = 0;
static Window *drag_window = NULL;
static int drag_offset_x = 0;
static int drag_offset_y = 0;
@ -855,6 +858,18 @@ static void draw_dock_settings(int x, int y) {
draw_filled_circle(cx, cy, 4, 0xFF3A3A3A);
}
static long long isqrt(long long n) {
if (n < 0) return -1;
if (n == 0) return 0;
long long x = n;
long long y = 1;
while (x > y) {
x = (x + y) / 2;
y = n / x;
}
return x;
}
static void draw_dock_notepad(int x, int y) {
draw_rounded_rect_filled(x, y, 48, 48, 10, 0xFFCC9A00);
draw_rounded_rect_filled(x + 1, y + 1, 46, 28, 9, 0xFFFFD700);
@ -959,6 +974,28 @@ static void draw_dock_paint(int x, int y) {
draw_rounded_rect_filled(x + 30, y + 22, 3, 7, 1, 0xFF1A1A1A);
}
static void draw_dock_browser(int x, int y) {
draw_rounded_rect_filled(x, y, 48, 48, 10, 0xFF0D47A1);
draw_rounded_rect_filled(x + 1, y + 1, 46, 28, 9, 0xFF1976D2);
draw_rounded_rect_filled(x + 1, y + 24, 46, 23, 9, 0xFF1565C0);
int cx = x + 24, cy = y + 24;
draw_filled_circle(cx, cy, 18, 0xFF64B5F6);
draw_filled_circle(cx, cy, 16, 0xFF2196F3);
// Simple globe lines
draw_rect(cx - 16, cy, 32, 1, 0xFFBBDEFB);
draw_rect(cx, cy - 16, 1, 32, 0xFFBBDEFB);
for(int i=0; i<32; i++) {
int r = (i-16);
if (r*r > 16*16) continue;
int w = isqrt(16*16 - r*r);
put_pixel(cx - w, cy + r, 0xFFBBDEFB);
put_pixel(cx + w, cy + r, 0xFFBBDEFB);
}
}
static void draw_dock_clock(int x, int y) {
draw_rounded_rect_filled(x, y, 48, 48, 10, 0xFF4A4A4A);
draw_rounded_rect_filled(x + 1, y + 1, 46, 28, 9, 0xFF6E6E6E);
@ -1027,6 +1064,18 @@ void draw_window(Window *win) {
if (win->paint) {
win->paint(win);
}
// Draw Resize Handle for resizable windows (MacOS 9 style)
if (win->resizable) {
int hx = win->x + win->w - 16;
int hy = win->y + win->h - 16;
for (int i = 0; i < 3; i++) {
for (int j = 0; j <= i; j++) {
// Draw a small 2x2 "dot" for the knurling
draw_rect(hx + 12 - i*4 + j*4, hy + 12 - j*4, 2, 2, 0xFF888888);
}
}
}
}
// Draw Mouse Cursor (Simple Arrow)
@ -1216,7 +1265,7 @@ void wm_paint(void) {
int dock_y = sh - dock_h - 6;
int dock_item_size = 48;
int dock_spacing = 10;
int total_dock_width = 8 * (dock_item_size + dock_spacing);
int total_dock_width = 9 * (dock_item_size + dock_spacing);
int dock_bg_x = (sw - total_dock_width) / 2 - 12;
int dock_bg_w = total_dock_width + 24;
draw_rounded_rect_filled(dock_bg_x, dock_y, dock_bg_w, dock_h, 18, COLOR_DOCK_BG);
@ -1238,6 +1287,8 @@ void wm_paint(void) {
dock_x += dock_item_size + dock_spacing;
draw_dock_paint(dock_x, dock_item_y);
dock_x += dock_item_size + dock_spacing;
draw_dock_browser(dock_x, dock_item_y);
dock_x += dock_item_size + dock_spacing;
draw_dock_clock(dock_x, dock_item_y);
// Editor removed from dock
@ -1632,6 +1683,14 @@ void wm_handle_click(int x, int y) {
if (topmost == &win_explorer) {
explorer_reset();
}
} else if (topmost->resizable && x >= topmost->x + topmost->w - 20 && y >= topmost->y + topmost->h - 20) {
// Dragging the resize handle
is_resizing = true;
drag_window = topmost;
drag_offset_x = x - topmost->x;
drag_offset_y = y - topmost->y;
drag_start_w = topmost->w;
drag_start_h = topmost->h;
} else if (y < topmost->y + 30) {
// Dragging the title bar
is_dragging = true;
@ -1715,7 +1774,7 @@ void wm_handle_right_click(int x, int y) {
}
force_redraw = true;
}void wm_handle_mouse(int dx, int dy, uint8_t buttons) {
}void wm_handle_mouse(int dx, int dy, uint8_t buttons, int dz) {
int sw = get_screen_width();
int sh = get_screen_height();
@ -1739,6 +1798,24 @@ void wm_handle_right_click(int x, int y) {
if (mx >= sw) mx = sw - 1;
if (my >= sh) my = sh - 1;
if (dz != 0) {
// Find focused window and send wheel event
for (int w = 0; w < window_count; w++) {
Window *win = all_windows[w];
if (win->focused && win->visible) {
// Map to userland process
process_t* proc = process_get_by_ui_window(win);
if (proc) {
gui_event_t ev;
ev.type = 9; // GUI_EVENT_MOUSE_WHEEL
ev.arg1 = dz;
process_push_gui_event(proc, &ev);
}
break;
}
}
}
static bool prev_left = false;
static bool prev_right = false;
bool left = buttons & 0x01;
@ -1751,7 +1828,7 @@ void wm_handle_right_click(int x, int y) {
int dock_y = sh - dock_h - 6;
int dock_item_size = 48;
int dock_spacing = 10;
int total_dock_width = 8 * (dock_item_size + dock_spacing);
int total_dock_width = 9 * (dock_item_size + dock_spacing);
int dock_bg_x = (sw - total_dock_width) / 2 - 12;
int dock_bg_w = total_dock_width + 24;
@ -1769,7 +1846,8 @@ void wm_handle_right_click(int x, int y) {
else if (item == 4) start_menu_pending_app = "Terminal";
else if (item == 5) start_menu_pending_app = "Minesweeper";
else if (item == 6) start_menu_pending_app = "Paint";
else if (item == 7) start_menu_pending_app = "Clock";
else if (item == 7) start_menu_pending_app = "Browser";
else if (item == 8) start_menu_pending_app = "Clock";
}
} else {
wm_handle_click(mx, my);
@ -1781,7 +1859,22 @@ void wm_handle_right_click(int x, int y) {
drag_window->y = my - drag_offset_y;
// Mark for full redraw since window moved
force_redraw = true;
} else if (left && !is_dragging && !is_dragging_file && (dx != 0 || dy != 0)) {
} else if (left && is_resizing && drag_window) {
int new_w = mx - drag_window->x + (drag_start_w - drag_offset_x);
int new_h = my - drag_window->y + (drag_start_h - drag_offset_y);
if (new_w < 150) new_w = 150;
if (new_h < 100) new_h = 100;
if (new_w != drag_window->w || new_h != drag_window->h) {
drag_window->w = new_w;
drag_window->h = new_h;
if (drag_window->handle_resize) {
drag_window->handle_resize(drag_window, new_w, new_h);
}
force_redraw = true;
}
} else if (left && !is_dragging && !is_resizing && !is_dragging_file && (dx != 0 || dy != 0)) {
// Check deadzone
int dist_x = mx - drag_start_x;
int dist_y = my - drag_start_y;
@ -1842,8 +1935,9 @@ void wm_handle_right_click(int x, int y) {
}
} else if (!left) {
if (is_dragging) {
if (is_dragging || is_resizing) {
is_dragging = false;
is_resizing = false;
drag_window = NULL;
force_redraw = true;
}
@ -1889,6 +1983,10 @@ void wm_handle_right_click(int x, int y) {
Window *existing = wm_find_window_by_title("Clock");
if (existing) wm_bring_to_front(existing);
else process_create_elf("/bin/clock.elf", NULL);
} else if (str_starts_with(start_menu_pending_app, "Browser")) {
Window *existing = wm_find_window_by_title("Web Browser");
if (existing) wm_bring_to_front(existing);
else process_create_elf("/bin/browser.elf", NULL);
} else if (str_starts_with(start_menu_pending_app, "About")) {
process_create_elf("/bin/about.elf", NULL);
} else if (str_starts_with(start_menu_pending_app, "Shutdown")) {

View file

@ -59,10 +59,12 @@ struct Window {
void (*handle_click)(Window *win, int x, int y);
void (*handle_right_click)(Window *win, int x, int y);
void (*handle_close)(Window *win);
void (*handle_resize)(Window *win, int w, int h);
bool resizable;
};
void wm_init(void);
void wm_handle_mouse(int dx, int dy, uint8_t buttons);
void wm_handle_mouse(int dx, int dy, uint8_t buttons, int dz);
void wm_handle_key(char c);
void wm_handle_click(int x, int y);
void wm_handle_right_click(int x, int y);