mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
browser
This commit is contained in:
parent
d38e8b97e2
commit
80fce3d0e9
15 changed files with 1047 additions and 99 deletions
BIN
boredos.iso
BIN
boredos.iso
Binary file not shown.
BIN
build/cmd.o
BIN
build/cmd.o
Binary file not shown.
BIN
build/ps2.o
BIN
build/ps2.o
Binary file not shown.
BIN
disk.img
BIN
disk.img
Binary file not shown.
378
src/kernel/cmd.c
378
src/kernel/cmd.c
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -552,10 +609,80 @@ void cmd_putchar(char c) {
|
|||
fat32_write(redirect_file, &c, 1);
|
||||
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,24 +1900,24 @@ 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];
|
||||
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;
|
||||
}
|
||||
while (len > 0 && cmd_buf[len-1] == ' ') len--;
|
||||
cmd_buf[len] = 0;
|
||||
if (c == '\n') { // Enter
|
||||
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 < 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;
|
||||
|
||||
cmd_putchar('\n');
|
||||
current_color = shell_config.default_text_color;
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -154,6 +155,16 @@ void mouse_init(void) {
|
|||
// Set Defaults
|
||||
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);
|
||||
|
|
@ -171,29 +182,36 @@ 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++;
|
||||
mouse_byte[0] = b;
|
||||
mouse_cycle++;
|
||||
}
|
||||
} 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;
|
||||
int8_t dx = mouse_byte[1];
|
||||
int8_t dy = mouse_byte[2];
|
||||
wm_handle_mouse(dx, -dy, mouse_byte[0] & 0x07, 0);
|
||||
}
|
||||
} else if (mouse_cycle == 3) {
|
||||
mouse_byte[3] = b;
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -164,8 +164,10 @@ 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;
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 $@
|
||||
|
||||
|
|
|
|||
587
src/kernel/userland/browser.c
Normal file
587
src/kernel/userland/browser.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
110
src/kernel/wm.c
110
src/kernel/wm.c
|
|
@ -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")) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue