diff --git a/Makefile b/Makefile index 1090ea2..6e52cab 100644 --- a/Makefile +++ b/Makefile @@ -119,4 +119,4 @@ run: $(ISO_IMAGE) qemu-system-x86_64 -m 2G -serial stdio -cdrom $(ISO_IMAGE) -boot d \ -audiodev coreaudio,id=audio0 -machine pcspk-audiodev=audio0 \ -netdev user,id=net0,hostfwd=udp::12345-:12345 -device e1000,netdev=net0 \ - -vga std -global VGA.xres=1280 -global VGA.yres=800 + -vga std -global VGA.xres=1920 -global VGA.yres=1080 diff --git a/brewos.iso b/brewos.iso index bce38cc..92e3f55 100644 Binary files a/brewos.iso and b/brewos.iso differ diff --git a/build/brewos.elf b/build/brewos.elf index b835dc1..44a435e 100755 Binary files a/build/brewos.elf and b/build/brewos.elf differ diff --git a/build/cmd.o b/build/cmd.o index 644eb7e..f0d4a9a 100644 Binary files a/build/cmd.o and b/build/cmd.o differ diff --git a/build/graphics.o b/build/graphics.o index a5f33cf..80db4a8 100644 Binary files a/build/graphics.o and b/build/graphics.o differ diff --git a/build/markdown.o b/build/markdown.o index f7a09ed..d0e18af 100644 Binary files a/build/markdown.o and b/build/markdown.o differ diff --git a/build/wm.o b/build/wm.o index 3e1e4f9..d0feb76 100644 Binary files a/build/wm.o and b/build/wm.o differ diff --git a/iso_root/brewos.elf b/iso_root/brewos.elf index b835dc1..44a435e 100755 Binary files a/iso_root/brewos.elf and b/iso_root/brewos.elf differ diff --git a/src/kernel/cmd.c b/src/kernel/cmd.c index 6e2231e..879a600 100644 --- a/src/kernel/cmd.c +++ b/src/kernel/cmd.c @@ -1056,7 +1056,7 @@ static void create_test_files(void) { const char *content = "# All compiled C files in this directory are openable from any other directory by typing in the name of the compiled file by typing in the name of the compiled file.\n\n" "The c file 'wordofgod.c' contains a C program similar to one in TempleOS, which Terry A. Davis (RIP) saw as 'words from god' telling him what to do with his kernel.\n" - "I made this file as a tribute to him, as he also inspired me to create this project in '24. If you want to run it you simply do cc (or compc) wordgod.c and then run ./wordgod \n"; + "I made this file as a tribute to him, as he also inspired me to create this project in '24. If you want to run it you simply do cc (or compc) wordofgod.c and then run ./wordofgod \n"; fat32_write(fh, (void *)content, cmd_strlen(content)); fat32_close(fh); } @@ -1093,16 +1093,16 @@ static void create_test_files(void) { "poke(l+40,\"day \");poke(l+44,\"night \");poke(l+48,\"waters \");poke(l+52,\"firmament \");poke(l+56,\"evening \");poke(l+60,\"morning \");poke(l+64,\"land \");poke(l+68,\"seas \");poke(l+72,\"grass \");poke(l+76,\"herb \");", "poke(l+80,\"seed \");poke(l+84,\"fruit \");poke(l+88,\"tree \");poke(l+92,\"sun \");poke(l+96,\"moon \");poke(l+100,\"stars \");poke(l+104,\"signs \");poke(l+108,\"seasons \");poke(l+112,\"days \");poke(l+116,\"years \");", "poke(l+120,\"creature \");poke(l+124,\"life \");poke(l+128,\"fowl \");poke(l+132,\"whales \");poke(l+136,\"cattle \");poke(l+140,\"creeping \");poke(l+144,\"beast \");poke(l+148,\"man \");poke(l+152,\"image \");poke(l+156,\"likeness \");", - "poke(l+160,\"dominion \");poke(l+164,\"fish \");poke(l+168,\"air \");poke(l+172,\"every \");poke(l+176,\"CIA \");poke(l+180,\"meat \");poke(l+184,\"holy \");poke(l+188,\"rest \");poke(l+192,\"dust \");poke(l+196,\"breath \");", + "poke(l+160,\"dominion \");poke(l+164,\"fish \");poke(l+168,\"air \");poke(l+172,\"every \");poke(l+176,\"CIA \");poke(l+180,\"Epstein Files \");poke(l+184,\"holy \");poke(l+188,\"rest \");poke(l+192,\"dust \");poke(l+196,\"breath \");", "poke(l+200,\"soul \");poke(l+204,\"garden \");poke(l+208,\"east \");poke(l+212,\"Eden \");poke(l+216,\"ground \");poke(l+220,\"sight \");poke(l+224,\"good \");poke(l+228,\"evil \");poke(l+232,\"river \");poke(l+236,\"gold \");", "poke(l+240,\"stone \");poke(l+244,\"woman \");poke(l+248,\"wife \");poke(l+252,\"flesh \");poke(l+256,\"bone \");poke(l+260,\"naked \");poke(l+264,\"serpent \");poke(l+268,\"subtle \");poke(l+272,\"eat \");poke(l+276,\"eyes \");", "poke(l+280,\"wise \");poke(l+284,\"cool \");poke(l+288,\"voice \");poke(l+292,\"fear \");poke(l+296,\"hid \");poke(l+300,\"cursed \");poke(l+304,\"belly \");poke(l+308,\"enmity \");poke(l+312,\"sorrow \");poke(l+316,\"conception \");", - "poke(l+320,\"children \");poke(l+324,\"desire \");poke(l+328,\"husband \");poke(l+332,\"thorns \");poke(l+336,\"thistles \");poke(l+340,\"sweat \");poke(l+344,\"bread \");poke(l+348,\"mother \");poke(l+352,\"skin \");poke(l+356,\"coats \");", - "poke(l+360,\"cherubims \");poke(l+364,\"sword \");poke(l+368,\"gate \");poke(l+372,\"offering \");poke(l+376,\"respect \");poke(l+380,\"sin \");poke(l+384,\"door \");poke(l+388,\"blood \");poke(l+392,\"brother \");poke(l+396,\"keeper \");", + "poke(l+320,\"children \");poke(l+324,\"desire \");poke(l+328,\"husband \");poke(l+332,\"lava \");poke(l+336,\"thistles \");poke(l+340,\"sweat \");poke(l+344,\"bread \");poke(l+348,\"mother \");poke(l+352,\"skin \");poke(l+356,\"coats \");", + "poke(l+360,\"cherubims \");poke(l+364,\"sword \");poke(l+368,\"gate \");poke(l+372,\"offering \");poke(l+376,\"obsidian \");poke(l+380,\"sin \");poke(l+384,\"door \");poke(l+388,\"blood \");poke(l+392,\"brother \");poke(l+396,\"keeper \");", "poke(l+400,\"voice \");poke(l+404,\"heard \");poke(l+408,\"walking \");poke(l+412,\"cool \");poke(l+416,\"day \");poke(l+420,\"where \");poke(l+424,\"art \");poke(l+428,\"thou \");poke(l+432,\"told \");poke(l+436,\"thee \");", - "poke(l+440,\"hast \");poke(l+444,\"eaten \");poke(l+448,\"tree \");poke(l+452,\"whereof \");poke(l+456,\"commanded \");poke(l+460,\"shouldest \");poke(l+464,\"not \");poke(l+468,\"eat \");poke(l+472,\"gave \");poke(l+476,\"me \");", + "poke(l+440,\"hast \");poke(l+444,\"eaten \");poke(l+448,\"tree \");poke(l+452,\"minecraft \");poke(l+456,\"commanded \");poke(l+460,\"shouldest \");poke(l+464,\"not \");poke(l+468,\"eat \");poke(l+472,\"gave \");poke(l+476,\"me \");", "poke(l+480,\"beguiled \");poke(l+484,\"belly \");poke(l+488,\"go \");poke(l+492,\"dust \");poke(l+496,\"shalt \");poke(l+500,\"eat \");poke(l+504,\"days \");poke(l+508,\"life \");poke(l+512,\"put \");poke(l+516,\"enmity \");", - "poke(l+520,\"between \");poke(l+524,\"seed \");poke(l+528,\"bruise \");poke(l+532,\"head \");poke(l+536,\"heel \");poke(l+540,\"multiply \");poke(l+544,\"sorrow \");poke(l+548,\"conception \");poke(l+552,\"forth \");poke(l+556,\"children \");", + "poke(l+520,\"between \");poke(l+524,\"seed \");poke(l+528,\"ICE \");poke(l+532,\"Detainment Facility \");poke(l+536,\"heel \");poke(l+540,\"multiply \");poke(l+544,\"sorrow \");poke(l+548,\"conception \");poke(l+552,\"forth \");poke(l+556,\"children \");", "poke(l+560,\"desire \");poke(l+564,\"rule \");poke(l+568,\"over \");poke(l+572,\"sake \");poke(l+576,\"sweat \");poke(l+580,\"face \");poke(l+584,\"till \");poke(l+588,\"return \");poke(l+592,\"ground \");poke(l+596,\"taken \");", "poke(l+600,\"mother \");poke(l+604,\"living \");poke(l+608,\"coats \");poke(l+612,\"skins \");poke(l+616,\"clothed \");poke(l+620,\"become \");poke(l+624,\"one \");poke(l+628,\"us \");poke(l+632,\"know \");poke(l+636,\"good \");", "poke(l+640,\"evil \");poke(l+644,\"lest \");poke(l+648,\"put \");poke(l+652,\"hand \");poke(l+656,\"take \");poke(l+660,\"live \");poke(l+664,\"ever \");poke(l+668,\"sent \");poke(l+672,\"garden \");poke(l+676,\"eden \");", diff --git a/src/kernel/graphics.c b/src/kernel/graphics.c index b030309..7ae59fb 100644 --- a/src/kernel/graphics.c +++ b/src/kernel/graphics.c @@ -134,9 +134,29 @@ void put_pixel(int x, int y, uint32_t color) { } void draw_rect(int x, int y, int w, int h, uint32_t color) { - for (int i = 0; i < h; i++) { - for (int j = 0; j < w; j++) { - put_pixel(x + j, y + i, color); + if (!g_fb) return; + + int x1 = x, y1 = y, x2 = x + w, y2 = y + h; + + if (g_clip_enabled) { + if (x1 < g_clip_x) x1 = g_clip_x; + if (y1 < g_clip_y) y1 = g_clip_y; + if (x2 > g_clip_x + g_clip_w) x2 = g_clip_x + g_clip_w; + if (y2 > g_clip_y + g_clip_h) y2 = g_clip_y + g_clip_h; + } + + if (x1 < 0) x1 = 0; + if (y1 < 0) y1 = 0; + if (x2 > (int)g_fb->width) x2 = g_fb->width; + if (y2 > (int)g_fb->height) y2 = g_fb->height; + + if (x1 >= x2 || y1 >= y2) return; + + for (int i = y1; i < y2; i++) { + uint32_t *row = &g_back_buffer[i * g_fb->width + x1]; + int len = x2 - x1; + for (int j = 0; j < len; j++) { + row[j] = color; } } } @@ -144,6 +164,15 @@ void draw_rect(int x, int y, int w, int h, uint32_t color) { void draw_char(int x, int y, char c, uint32_t color) { unsigned char uc = (unsigned char)c; if (uc > 127) return; + + // Fast rejection: if the character is entirely outside the clipping/dirty rect, skip it + if (g_clip_enabled) { + if (x + 8 <= g_clip_x || x >= g_clip_x + g_clip_w || + y + 8 <= g_clip_y || y >= g_clip_y + g_clip_h) { + return; + } + } + const uint8_t *glyph = font8x8_basic[uc]; for (int row = 0; row < 8; row++) { @@ -174,13 +203,18 @@ void draw_desktop_background(void) { if (!g_fb) return; if (g_use_pattern) { - // Draw tiled pattern - for (int y = 0; y < (int)g_fb->height; y++) { - for (int x = 0; x < (int)g_fb->width; x++) { - int px = x % PATTERN_SIZE; - int py = y % PATTERN_SIZE; - uint32_t color = g_bg_pattern[py * PATTERN_SIZE + px]; - put_pixel(x, y, color); + // Optimized tiled pattern: only draw within the clipping/dirty rect + int x1 = 0, y1 = 0, x2 = g_fb->width, y2 = g_fb->height; + if (g_clip_enabled) { + x1 = g_clip_x; y1 = g_clip_y; + x2 = g_clip_x + g_clip_w; y2 = g_clip_y + g_clip_h; + } + + for (int y = y1; y < y2; y++) { + uint32_t *row = &g_back_buffer[y * g_fb->width + x1]; + int py = y % PATTERN_SIZE; + for (int x = x1; x < x2; x++) { + *row++ = g_bg_pattern[py * PATTERN_SIZE + (x % PATTERN_SIZE)]; } } } else { @@ -214,20 +248,27 @@ void graphics_clear_back_buffer(uint32_t color) { } void graphics_flip_buffer(void) { - if (!g_fb) return; - - // Copy back buffer to framebuffer - uint32_t *src = g_back_buffer; - uint8_t *dst = (uint8_t *)g_fb->address; - - for (int y = 0; y < (int)g_fb->height; y++) { - // Copy one scanline - uint32_t *dst_row = (uint32_t *)dst; - for (int x = 0; x < (int)g_fb->width; x++) { - dst_row[x] = src[x]; + if (!g_fb || !g_dirty.active) return; + + int x = g_dirty.x; + int y = g_dirty.y; + int w = g_dirty.w; + int h = g_dirty.h; + + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if (x + w > (int)g_fb->width) w = g_fb->width - x; + if (y + h > (int)g_fb->height) h = g_fb->height - y; + + if (w <= 0 || h <= 0) return; + + for (int i = 0; i < h; i++) { + int curr_y = y + i; + uint32_t *src_row = &g_back_buffer[curr_y * g_fb->width + x]; + uint32_t *dst_row = (uint32_t *)((uint8_t *)g_fb->address + curr_y * g_fb->pitch) + x; + for (int j = 0; j < w; j++) { + dst_row[j] = src_row[j]; } - src += g_fb->width; - dst += g_fb->pitch; } } diff --git a/src/kernel/wm.c b/src/kernel/wm.c index 125c9a7..8dae568 100644 --- a/src/kernel/wm.c +++ b/src/kernel/wm.c @@ -88,8 +88,8 @@ static int desktop_icon_count = 0; // Desktop Settings bool desktop_snap_to_grid = true; bool desktop_auto_align = true; -int desktop_max_rows_per_col = 9; -int desktop_max_cols = 15; +int desktop_max_rows_per_col = 13; +int desktop_max_cols = 23; // Helper to check if string ends with suffix static bool str_ends_with(const char *str, const char *suffix) { @@ -697,20 +697,26 @@ void wm_paint(void) { int sw = get_screen_width(); int sh = get_screen_height(); - // Ensure no stale clipping state interferes with the new frame - graphics_clear_clipping(); - // First, erase the old cursor (before redrawing anything) - if (cursor_visible) { - erase_cursor(last_cursor_x, last_cursor_y); + DirtyRect dirty = graphics_get_dirty_rect(); + if (dirty.active) { + graphics_set_clipping(dirty.x, dirty.y, dirty.w, dirty.h); + } else { + graphics_clear_clipping(); } - + // 1. Desktop draw_desktop_background(); // Draw Desktop Icons for (int i = 0; i < desktop_icon_count; i++) { DesktopIcon *icon = &desktop_icons[i]; + if (dirty.active) { + if (icon->x + 80 <= dirty.x || icon->x >= dirty.x + dirty.w || + icon->y + 80 <= dirty.y || icon->y >= dirty.y + dirty.h) { + continue; + } + } if (icon->type == 1) draw_folder_icon(icon->x, icon->y, icon->name); else if (icon->type == 2) { // App icon - strip .app for display @@ -765,7 +771,15 @@ void wm_paint(void) { // Draw windows in z-order (lowest first) for (int i = 0; i < window_count; i++) { - draw_window(sorted_windows[i]); + Window *win = sorted_windows[i]; + if (!win->visible) continue; + if (dirty.active) { + if (win->x + win->w <= dirty.x || win->x >= dirty.x + dirty.w || + win->y + win->h <= dirty.y || win->y >= dirty.y + dirty.h) { + continue; + } + } + draw_window(win); } // 4. Taskbar @@ -917,6 +931,7 @@ void wm_bring_to_front(Window *win) { win->visible = true; win->focused = true; win->z_index = max_z + 1; + force_redraw = true; } void wm_add_window(Window *win) { @@ -989,7 +1004,6 @@ void wm_handle_click(int x, int y) { } refresh_desktop_icons(); - // If auto-align is OFF and we pasted to the background, place at click location if (!desktop_auto_align && desktop_icon_count > old_count && desktop_menu_target_icon == -1) { int new_idx = desktop_icon_count - 1; desktop_icons[new_idx].x = desktop_menu_x - 20; @@ -1292,7 +1306,7 @@ void wm_handle_right_click(int x, int y) { DesktopIcon *icon = &desktop_icons[i]; is_dragging_file = true; drag_icon_type = icon->type; - pending_desktop_icon_click = -1; // Cancel pending click since we are dragging + pending_desktop_icon_click = -1; drag_icon_orig_x = icon->x; drag_icon_orig_y = icon->y; // Construct path @@ -1312,7 +1326,6 @@ void wm_handle_right_click(int x, int y) { for (int w = 0; w < window_count; w++) { Window *win = all_windows[w]; if (win->visible && rect_contains(win->x, win->y, win->w, win->h, drag_start_x, drag_start_y)) { - // This is a bit of a hack, but we check if it's an explorer window if (str_starts_with(win->title, "File Explorer")) { drag_src_win = win; explorer_clear_click_state(win); @@ -1369,48 +1382,52 @@ void wm_handle_right_click(int x, int y) { int i = pending_desktop_icon_click; if (i < desktop_icon_count) { DesktopIcon *icon = &desktop_icons[i]; + bool handled = false; if (icon->type == 2) { // App Shortcut // Check name to launch app if (str_ends_with(icon->name, "Notepad.shortcut")) { - notepad_reset(); wm_bring_to_front(&win_notepad); + notepad_reset(); wm_bring_to_front(&win_notepad); handled = true; } else if (str_ends_with(icon->name, "Calculator.shortcut")) { - wm_bring_to_front(&win_calculator); + wm_bring_to_front(&win_calculator); handled = true; } else if (str_ends_with(icon->name, "Minesweeper.shortcut")) { - wm_bring_to_front(&win_minesweeper); + wm_bring_to_front(&win_minesweeper); handled = true; } else if (str_ends_with(icon->name, "Control Panel.shortcut")) { - wm_bring_to_front(&win_control_panel); + wm_bring_to_front(&win_control_panel); handled = true; } else if (str_ends_with(icon->name, "Terminal.shortcut")) { - wm_bring_to_front(&win_cmd); + wm_bring_to_front(&win_cmd); handled = true; } else if (str_ends_with(icon->name, "About.shortcut")) { - wm_bring_to_front(&win_about); + wm_bring_to_front(&win_about); handled = true; } else if (str_ends_with(icon->name, "Explorer.shortcut")) { - explorer_open_directory("/"); + explorer_open_directory("/"); handled = true; } else if (str_ends_with(icon->name, "Recycle Bin.shortcut")) { - explorer_open_directory("/RecycleBin"); + explorer_open_directory("/RecycleBin"); handled = true; } else if (str_ends_with(icon->name, "Paint.shortcut")) { - wm_bring_to_front(&win_paint); + wm_bring_to_front(&win_paint); handled = true; } - // Generic Shortcut Handling - char path[128] = "/Desktop/"; - int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; - - if (str_ends_with(icon->name, ".shortcut") && !str_starts_with(icon->name, "Recycle Bin")) { - FAT32_FileHandle *fh = fat32_open(path, "r"); - if (fh) { - char buf[256]; - int len = fat32_read(fh, buf, 255); - fat32_close(fh); - if (len > 0) { - buf[len] = 0; - if (fat32_is_directory(buf)) { - explorer_open_directory(buf); - } else { - editor_open_file(buf); - wm_bring_to_front(&win_editor); + if (!handled) { + // Generic Shortcut Handling + char path[128] = "/Desktop/"; + int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; + + if (str_ends_with(icon->name, ".shortcut") && !str_starts_with(icon->name, "Recycle Bin")) { + FAT32_FileHandle *fh = fat32_open(path, "r"); + if (fh) { + char buf[256]; + int len = fat32_read(fh, buf, 255); + fat32_close(fh); + if (len > 0) { + buf[len] = 0; + if (fat32_is_directory(buf)) { + explorer_open_directory(buf); + } else { + editor_open_file(buf); + wm_bring_to_front(&win_editor); + } + pending_desktop_icon_click = -1; + force_redraw = true; + return; } - pending_desktop_icon_click = -1; - return; } } } @@ -1626,7 +1643,6 @@ void wm_handle_right_click(int x, int y) { if (dx < 0) dx = -dx; if (dy < 0) dy = -dy; if (dx < 35 && dy < 35) { - // Collision with non-folder (or we would have handled it) // Revert position desktop_icons[dragged_idx].x = drag_icon_orig_x; desktop_icons[dragged_idx].y = drag_icon_orig_y; @@ -1825,11 +1841,11 @@ uint32_t wm_get_ticks(void) { void wm_timer_tick(void) { timer_ticks++; - // Auto-refresh desktop every second (approx 60 ticks) - // But NOT if we are currently dragging something, to avoid state conflicts + // Auto-refresh desktop every 5 seconds to save CPU in QEMU + // But NOT if the user is dragging a window or file. if (!is_dragging && !is_dragging_file) { desktop_refresh_timer++; - if (desktop_refresh_timer >= 60) { + if (desktop_refresh_timer >= 300) { refresh_desktop_icons(); explorer_refresh_all(); desktop_refresh_timer = 0;