diff --git a/.DS_Store b/.DS_Store index d1eab41..264aee4 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 8420e88..c7fa5b6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ limine 2/Makefile limine 2/limine limine 2/limine.dSYM/Contents/Resources/DWARF/limine limine 2/limine.exe +build/* \ No newline at end of file diff --git a/README.md b/README.md index b864c3d..588b4e6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# BoredOS 1.51 +# BoredOS BoredOS has now exited Beta stage and is "stable enough" to be put out as a "stable" product.
@@ -9,6 +9,7 @@ It features a DE (and WM), a FAT32 filesystem, customizable UI and much much mor *this screenshot might be outdated* ## Features +- JPG image support - Disk manager - Drag and drop mouse centered UI - Customizable UI diff --git a/boredos.iso b/boredos.iso index 57b164f..687c646 100644 Binary files a/boredos.iso and b/boredos.iso differ diff --git a/build/about.o b/build/about.o index 7fc3c4b..0693b34 100644 Binary files a/build/about.o and b/build/about.o differ diff --git a/build/boredos.elf b/build/boredos.elf index ceb34e0..4ec1247 100755 Binary files a/build/boredos.elf and b/build/boredos.elf differ diff --git a/build/cli_apps/boredver.o b/build/cli_apps/boredver.o index 0cfe091..f659202 100644 Binary files a/build/cli_apps/boredver.o and b/build/cli_apps/boredver.o differ diff --git a/build/cmd.o b/build/cmd.o index 46239d1..1da30dd 100644 Binary files a/build/cmd.o and b/build/cmd.o differ diff --git a/build/disk_manager.o b/build/disk_manager.o index 96806fe..98b67ce 100644 Binary files a/build/disk_manager.o and b/build/disk_manager.o differ diff --git a/build/explorer.o b/build/explorer.o index 5242bd7..084b397 100644 Binary files a/build/explorer.o and b/build/explorer.o differ diff --git a/build/fat32.o b/build/fat32.o index bbf1650..3b108a2 100644 Binary files a/build/fat32.o and b/build/fat32.o differ diff --git a/build/wm.o b/build/wm.o index c9cba77..b5aa53c 100644 Binary files a/build/wm.o and b/build/wm.o differ diff --git a/disk.img b/disk.img index 2c13218..6c579df 100644 Binary files a/disk.img and b/disk.img differ diff --git a/iso_root/README.md b/iso_root/README.md index b864c3d..588b4e6 100644 --- a/iso_root/README.md +++ b/iso_root/README.md @@ -1,4 +1,4 @@ -# BoredOS 1.51 +# BoredOS BoredOS has now exited Beta stage and is "stable enough" to be put out as a "stable" product.
@@ -9,6 +9,7 @@ It features a DE (and WM), a FAT32 filesystem, customizable UI and much much mor *this screenshot might be outdated* ## Features +- JPG image support - Disk manager - Drag and drop mouse centered UI - Customizable UI diff --git a/iso_root/boredos.elf b/iso_root/boredos.elf index ceb34e0..4ec1247 100755 Binary files a/iso_root/boredos.elf and b/iso_root/boredos.elf differ diff --git a/screenshot.jpg b/screenshot.jpg index b0b8a89..c83ab84 100644 Binary files a/screenshot.jpg and b/screenshot.jpg differ diff --git a/src/.DS_Store b/src/.DS_Store index 68de793..395d9bf 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/kernel/.DS_Store b/src/kernel/.DS_Store index 647c966..5a47950 100644 Binary files a/src/kernel/.DS_Store and b/src/kernel/.DS_Store differ diff --git a/src/kernel/about.c b/src/kernel/about.c index 8922505..25ff5f5 100644 --- a/src/kernel/about.c +++ b/src/kernel/about.c @@ -14,8 +14,8 @@ static void about_paint(Window *win) { // Version info draw_string(offset_x, offset_y + 105, "BoredOS 'Panda'", COLOR_WHITE); - draw_string(offset_x, offset_y + 120, "BoredOS Version 1.62", COLOR_WHITE); - draw_string(offset_x, offset_y + 135, "Kernel Version 2.5.2", COLOR_WHITE); + draw_string(offset_x, offset_y + 120, "BoredOS Version 1.63", COLOR_WHITE); + draw_string(offset_x, offset_y + 135, "Kernel Version 2.5.3", COLOR_WHITE); // Copyright draw_string(offset_x, offset_y + 150, "(C) 2026 boreddevnl.", COLOR_WHITE); diff --git a/src/kernel/cli_apps/boredver.c b/src/kernel/cli_apps/boredver.c index 2389d93..706df41 100644 --- a/src/kernel/cli_apps/boredver.c +++ b/src/kernel/cli_apps/boredver.c @@ -2,6 +2,6 @@ void cli_cmd_boredver(char *args) { (void)args; - cli_write("BoredOS v1.62\n"); - cli_write("BoredOS Kernel V2.5.2\n"); + cli_write("BoredOS v1.63\n"); + cli_write("BoredOS Kernel V2.5.3\n"); } diff --git a/src/kernel/disk.h b/src/kernel/disk.h index ad7ed7f..ccced27 100644 --- a/src/kernel/disk.h +++ b/src/kernel/disk.h @@ -18,6 +18,7 @@ typedef struct Disk { DiskType type; bool is_fat32; char name[32]; + uint32_t partition_lba_offset; // LBA offset of FAT32 partition (0 for raw) // Function pointers for driver operations int (*read_sector)(struct Disk *disk, uint32_t sector, uint8_t *buffer); diff --git a/src/kernel/disk_manager.c b/src/kernel/disk_manager.c index a32278d..1a7da1d 100644 --- a/src/kernel/disk_manager.c +++ b/src/kernel/disk_manager.c @@ -217,6 +217,7 @@ void disk_manager_init(void) { ramdisk->read_sector = ramdisk_read; ramdisk->write_sector = ramdisk_write; ramdisk->driver_data = NULL; + ramdisk->partition_lba_offset = 0; disk_register(ramdisk); } @@ -243,25 +244,95 @@ Disk* disk_get_by_index(int index) { } -// Check for FAT32 Signature in MBR/VBR -static bool check_fat32_signature(Disk *disk) { +// === MBR Partition Table Structures === + +typedef struct { + uint8_t status; // 0x80 = bootable, 0x00 = inactive + uint8_t chs_first[3]; // CHS of first sector + uint8_t type; // Partition type + uint8_t chs_last[3]; // CHS of last sector + uint32_t lba_start; // LBA of first sector + uint32_t sector_count; // Number of sectors +} __attribute__((packed)) MBR_PartitionEntry; + +// FAT32 partition type codes +#define PART_TYPE_FAT32 0x0B +#define PART_TYPE_FAT32_LBA 0x0C + +// Check if sector contains a valid FAT32 BPB (Volume Boot Record) +static bool is_fat32_bpb(const uint8_t *sector) { + // Must have 0xAA55 boot signature + if (sector[510] != 0x55 || sector[511] != 0xAA) return false; + + // Check for FAT32 filesystem string at offset 82 + // "FAT32 " in the fs_type field of the BPB + if (sector[82] == 'F' && sector[83] == 'A' && sector[84] == 'T' && + sector[85] == '3' && sector[86] == '2') { + return true; + } + + // Also accept if bytes_per_sector is 512 and sectors_per_fat_16 is 0 + // (FAT32 always has sectors_per_fat_16 == 0) + uint16_t bps = *(uint16_t*)§or[11]; + uint16_t spf16 = *(uint16_t*)§or[22]; + uint32_t spf32 = *(uint32_t*)§or[36]; + if (bps == 512 && spf16 == 0 && spf32 > 0) { + return true; + } + + return false; +} + +// Parse MBR partition table and find a FAT32 partition. +// Sets disk->partition_lba_offset and returns true if found. +static bool detect_fat32_partition(Disk *disk) { uint8_t *buffer = (uint8_t*)kmalloc(512); if (!buffer) return false; - // Read Sector 0 + // Read sector 0 (MBR or raw BPB) if (disk->read_sector(disk, 0, buffer) != 0) { kfree(buffer); return false; } - // Check boot signature 0x55 0xAA at offset 510 + // Must have 0xAA55 boot signature if (buffer[510] != 0x55 || buffer[511] != 0xAA) { kfree(buffer); return false; } + // Check MBR partition table entries (4 entries at offset 446) + MBR_PartitionEntry *partitions = (MBR_PartitionEntry*)&buffer[446]; + + for (int i = 0; i < 4; i++) { + if (partitions[i].type == PART_TYPE_FAT32 || + partitions[i].type == PART_TYPE_FAT32_LBA) { + + uint32_t part_lba = partitions[i].lba_start; + + // Read the partition's first sector to verify it's a valid FAT32 BPB + uint8_t *pbuf = (uint8_t*)kmalloc(512); + if (!pbuf) { kfree(buffer); return false; } + + if (disk->read_sector(disk, part_lba, pbuf) == 0 && is_fat32_bpb(pbuf)) { + disk->partition_lba_offset = part_lba; + kfree(pbuf); + kfree(buffer); + return true; + } + kfree(pbuf); + } + } + + // Fallback: check if sector 0 itself is a raw FAT32 BPB (no partition table) + if (is_fat32_bpb(buffer)) { + disk->partition_lba_offset = 0; + kfree(buffer); + return true; + } + kfree(buffer); - return true; + return false; } static void try_add_ata_drive(uint16_t port, bool slave, const char *name) { @@ -279,13 +350,13 @@ static void try_add_ata_drive(uint16_t port, bool slave, const char *name) { new_disk->read_sector = ata_read_sector; new_disk->write_sector = ata_write_sector; new_disk->driver_data = data; + new_disk->partition_lba_offset = 0; - // Check filesystem - if (check_fat32_signature(new_disk)) { + // Detect FAT32 (with MBR partition support) + if (detect_fat32_partition(new_disk)) { new_disk->is_fat32 = true; disk_register(new_disk); } else { - kfree(data); kfree(new_disk); } diff --git a/src/kernel/explorer.c b/src/kernel/explorer.c index ce8bda9..3e1d7c6 100644 --- a/src/kernel/explorer.c +++ b/src/kernel/explorer.c @@ -366,7 +366,14 @@ bool explorer_delete_permanently(const char *path) { } bool explorer_delete_recursive(const char *path) { - if (explorer_str_starts_with(path, "/RecycleBin")) { + // Check if path is on an external drive (not A:) + bool is_external = false; + if (path[0] && path[1] == ':' && path[0] != 'A' && path[0] != 'a') { + is_external = true; + } + + if (is_external || explorer_str_starts_with(path, "/RecycleBin")) { + // External drives have no RecycleBin — delete permanently return explorer_delete_permanently(path); } else { // Move to Recycle Bin @@ -885,7 +892,7 @@ static void explorer_open_item(Window *win, int index) { } // Draw a simple file icon -static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, const char *filename) { +static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, const char *filename, const char *current_path) { if (is_dir) { if (explorer_strcmp(filename, "RecycleBin") == 0) draw_recycle_bin_icon(x + 5, y + 5, ""); else draw_folder_icon(x + 5, y + 5, ""); @@ -903,7 +910,12 @@ static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, c } else if (explorer_str_ends_with(filename, ".pnt")) { draw_paint_icon(x + 5, y + 5, ""); } else if (explorer_str_ends_with(filename, ".jpg") || explorer_str_ends_with(filename, ".JPG")) { - draw_image_icon(x + 5, y + 5, filename); + // Build full path for thumbnail loading + char full_path[256]; + explorer_strcpy(full_path, current_path); + if (full_path[explorer_strlen(full_path) - 1] != '/') explorer_strcat(full_path, "/"); + explorer_strcat(full_path, filename); + draw_image_icon(x + 5, y + 5, full_path); } else { draw_document_icon(x + 5, y + 5, ""); } @@ -989,7 +1001,7 @@ static void explorer_paint(Window *win) { draw_rounded_rect_filled(item_x, item_y, EXPLORER_ITEM_WIDTH, EXPLORER_ITEM_HEIGHT, 6, bg_color); // Draw icon (larger area) - explorer_draw_file_icon(item_x + 5, item_y + 5, state->items[i].is_directory, state->items[i].color, state->items[i].name); + explorer_draw_file_icon(item_x + 5, item_y + 5, state->items[i].is_directory, state->items[i].color, state->items[i].name, state->current_path); // Draw name using intelligent wrapping const char *display_name = state->items[i].name; @@ -1098,91 +1110,96 @@ static void explorer_paint(Window *win) { int dlg_x = win->x + win->w / 2 - 150; int dlg_y = win->y + win->h / 2 - 60; - // Dialog background - draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY); - draw_bevel_rect(dlg_x, dlg_y, 300, 110, true); + // Dialog background (modern dark, rounded) + draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL); // Title const char *title = state->dialog_target_is_dir ? "Delete Folder?" : "Delete File?"; - draw_string(dlg_x + 10, dlg_y + 10, title, COLOR_BLACK); + draw_string(dlg_x + 10, dlg_y + 10, title, COLOR_WHITE); // Message if (explorer_str_starts_with(state->current_path, "/RecycleBin")) { - draw_string(dlg_x + 10, dlg_y + 35, "This action cannot be undone.", COLOR_BLACK); - draw_string(dlg_x + 10, dlg_y + 48, "Delete forever?", COLOR_BLACK); + draw_string(dlg_x + 10, dlg_y + 35, "This action cannot be undone.", 0xFFAAAAAA); + draw_string(dlg_x + 10, dlg_y + 48, "Delete forever?", 0xFFAAAAAA); } else { - draw_string(dlg_x + 10, dlg_y + 35, "This file will be moved to", COLOR_BLACK); - draw_string(dlg_x + 10, dlg_y + 45, "the recycle bin.", COLOR_BLACK); + draw_string(dlg_x + 10, dlg_y + 35, "This file will be moved to", 0xFFAAAAAA); + draw_string(dlg_x + 10, dlg_y + 45, "the recycle bin.", 0xFFAAAAAA); } - // Buttons - draw_button(dlg_x + 50, dlg_y + 65, 80, 25, "Delete", false); - draw_button(dlg_x + 170, dlg_y + 65, 80, 25, "Cancel", false); + // Buttons (rounded, delete button red-tinted) + draw_rounded_rect_filled(dlg_x + 50, dlg_y + 65, 80, 25, 6, 0xFF8B2020); + draw_string(dlg_x + 68, dlg_y + 72, "Delete", COLOR_WHITE); + draw_rounded_rect_filled(dlg_x + 170, dlg_y + 65, 80, 25, 6, COLOR_DARK_BORDER); + draw_string(dlg_x + 185, dlg_y + 72, "Cancel", COLOR_WHITE); } else if (state->dialog_state == DIALOG_REPLACE_CONFIRM) { int dlg_x = win->x + win->w / 2 - 150; int dlg_y = win->y + win->h / 2 - 60; - // Dialog background - draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY); - draw_bevel_rect(dlg_x, dlg_y, 300, 110, true); + // Dialog background (modern dark, rounded) + draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL); // Title - draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_BLACK); + draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_WHITE); // Message - draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", COLOR_BLACK); - draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", COLOR_BLACK); + draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", 0xFFAAAAAA); + draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA); - // Buttons - draw_button(dlg_x + 50, dlg_y + 70, 80, 25, "Replace", false); - draw_button(dlg_x + 170, dlg_y + 70, 80, 25, "Cancel", false); + // Buttons (rounded) + draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); + draw_string(dlg_x + 63, dlg_y + 77, "Replace", COLOR_WHITE); + draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); + draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE); } else if (state->dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) { int dlg_x = win->x + win->w / 2 - 150; int dlg_y = win->y + win->h / 2 - 60; - // Dialog background - draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY); - draw_bevel_rect(dlg_x, dlg_y, 300, 110, true); + // Dialog background (modern dark, rounded) + draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL); // Title - draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_BLACK); + draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_WHITE); // Message - draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", COLOR_BLACK); - draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", COLOR_BLACK); + draw_string(dlg_x + 10, dlg_y + 35, "Replace existing file?", 0xFFAAAAAA); + draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA); - // Buttons - draw_button(dlg_x + 50, dlg_y + 70, 80, 25, "Replace", false); - draw_button(dlg_x + 170, dlg_y + 70, 80, 25, "Cancel", false); + // Buttons (rounded) + draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); + draw_string(dlg_x + 63, dlg_y + 77, "Replace", COLOR_WHITE); + draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); + draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE); } else if (state->dialog_state == DIALOG_CREATE_REPLACE_CONFIRM) { int dlg_x = win->x + win->w / 2 - 150; int dlg_y = win->y + win->h / 2 - 60; - // Dialog background - draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY); - draw_bevel_rect(dlg_x, dlg_y, 300, 110, true); + // Dialog background (modern dark, rounded) + draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL); // Title - draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_BLACK); + draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_WHITE); // Message - draw_string(dlg_x + 10, dlg_y + 35, "Overwrite existing file?", COLOR_BLACK); - draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", COLOR_BLACK); + draw_string(dlg_x + 10, dlg_y + 35, "Overwrite existing file?", 0xFFAAAAAA); + draw_string(dlg_x + 10, dlg_y + 48, "This cannot be undone.", 0xFFAAAAAA); - // Buttons - draw_button(dlg_x + 50, dlg_y + 70, 80, 25, "Overwrite", false); - draw_button(dlg_x + 170, dlg_y + 70, 80, 25, "Cancel", false); + // Buttons (rounded) + draw_rounded_rect_filled(dlg_x + 50, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); + draw_string(dlg_x + 57, dlg_y + 77, "Overwrite", COLOR_WHITE); + draw_rounded_rect_filled(dlg_x + 170, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); + draw_string(dlg_x + 185, dlg_y + 77, "Cancel", COLOR_WHITE); } else if (state->dialog_state == DIALOG_ERROR) { int dlg_x = win->x + win->w / 2 - 150; int dlg_y = win->y + win->h / 2 - 60; - draw_rect(dlg_x - 5, dlg_y - 5, 310, 120, COLOR_LTGRAY); - draw_bevel_rect(dlg_x, dlg_y, 300, 110, true); + // Dialog background (modern dark, rounded) + draw_rounded_rect_filled(dlg_x, dlg_y, 300, 110, 8, COLOR_DARK_PANEL); - draw_string(dlg_x + 10, dlg_y + 10, "Error", COLOR_RED); - draw_string(dlg_x + 10, dlg_y + 40, state->dialog_input, COLOR_BLACK); + draw_string(dlg_x + 10, dlg_y + 10, "Error", 0xFFFF6B6B); + draw_string(dlg_x + 10, dlg_y + 40, state->dialog_input, 0xFFAAAAAA); - // OK Button - draw_button(dlg_x + 110, dlg_y + 70, 80, 25, "OK", false); + // OK Button (rounded) + draw_rounded_rect_filled(dlg_x + 110, dlg_y + 70, 80, 25, 6, COLOR_DARK_BORDER); + draw_string(dlg_x + 138, dlg_y + 77, "OK", COLOR_WHITE); } else if (state->dialog_state == DIALOG_RENAME) { int dlg_x = win->x + win->w / 2 - 150; int dlg_y = win->y + win->h / 2 - 60; diff --git a/src/kernel/fat32.c b/src/kernel/fat32.c index ff8d208..13cdb0c 100644 --- a/src/kernel/fat32.c +++ b/src/kernel/fat32.c @@ -44,6 +44,7 @@ typedef struct { uint32_t root_cluster; uint32_t fat_size; // sectors uint32_t total_sectors; + uint32_t partition_offset; // LBA offset of partition start bool mounted; } FAT32_Volume; @@ -51,6 +52,17 @@ static FAT32_Volume volumes[26]; // A-Z // === Helper Functions (Shared) === +// Serial debug output +static void fs_serial_char(char c) { + while (!(inb(0x3F8 + 5) & 0x20)); + outb(0x3F8, c); +} +static void fs_serial_str(const char *s) { while (*s) fs_serial_char(*s++); } +static void fs_serial_num(uint32_t n) { + if (n >= 10) fs_serial_num(n / 10); + fs_serial_char('0' + (n % 10)); +} + static size_t fs_strlen(const char *str) { size_t len = 0; while (str[len]) len++; @@ -373,31 +385,49 @@ static bool realfs_mount(char drive) { Disk *disk = disk_get_by_letter(drive); if (!disk) return false; + // Use partition LBA offset from disk (set during MBR parsing) + uint32_t part_offset = disk->partition_lba_offset; + uint8_t *sect0 = (uint8_t*)kmalloc(512); if (!sect0) return false; - if (disk->read_sector(disk, 0, sect0) != 0) { + // Read BPB from partition start (sector 0 for raw, partition LBA for MBR) + if (disk->read_sector(disk, part_offset, sect0) != 0) { kfree(sect0); return false; } FAT32_BootSector *bpb = (FAT32_BootSector*)sect0; - // Simple verification (could be more robust) if (bpb->boot_signature_value != 0xAA55) { kfree(sect0); return false; } volumes[idx].disk = disk; - volumes[idx].fat_begin_lba = bpb->reserved_sectors; - volumes[idx].cluster_begin_lba = bpb->reserved_sectors + (bpb->num_fats * bpb->sectors_per_fat_32); + volumes[idx].partition_offset = part_offset; + volumes[idx].fat_begin_lba = part_offset + bpb->reserved_sectors; + volumes[idx].cluster_begin_lba = part_offset + bpb->reserved_sectors + (bpb->num_fats * bpb->sectors_per_fat_32); volumes[idx].sectors_per_cluster = bpb->sectors_per_cluster; volumes[idx].root_cluster = bpb->root_cluster; volumes[idx].fat_size = bpb->sectors_per_fat_32; volumes[idx].total_sectors = bpb->total_sectors_32; volumes[idx].mounted = true; + fs_serial_str("[FAT32] mounted drive "); + fs_serial_char(drive); + fs_serial_str(": part_offset="); + fs_serial_num(part_offset); + fs_serial_str(" fat_lba="); + fs_serial_num(volumes[idx].fat_begin_lba); + fs_serial_str(" cluster_lba="); + fs_serial_num(volumes[idx].cluster_begin_lba); + fs_serial_str(" spc="); + fs_serial_num(volumes[idx].sectors_per_cluster); + fs_serial_str(" root_cl="); + fs_serial_num(volumes[idx].root_cluster); + fs_serial_str("\n"); + kfree(sect0); return true; } @@ -488,6 +518,14 @@ static FAT32_FileHandle* realfs_open(char drive, const char *path, const char *m const char *p = path; if (*p == '/') p++; + fs_serial_str("[FAT32] realfs_open drive="); + fs_serial_char(drive); + fs_serial_str(" path='"); + fs_serial_str(path); + fs_serial_str("' mode="); + fs_serial_char(mode[0]); + fs_serial_str("\n"); + if (*p == 0) { // Root dir if (mode[0] == 'w') return NULL; // Cannot write to root as file @@ -535,6 +573,8 @@ static FAT32_FileHandle* realfs_open(char drive, const char *path, const char *m for (int e = 0; e < entries_per_cluster; e++) { if (entry[e].filename[0] == 0) break; // End of dir if (entry[e].filename[0] == 0xE5) continue; // Deleted + if (entry[e].attributes == 0x0F) continue; // LFN entry + if (entry[e].attributes & ATTR_VOLUME_ID) continue; // Volume label // Compare name (simplistic 8.3 matching) char name[12]; @@ -558,6 +598,13 @@ static FAT32_FileHandle* realfs_open(char drive, const char *path, const char *m if (match) { uint32_t cluster = (entry[e].start_cluster_high << 16) | entry[e].start_cluster_low; + fs_serial_str("[FAT32] MATCH '"); + fs_serial_str(name); + fs_serial_str("' cluster="); + fs_serial_num(cluster); + fs_serial_str(" size="); + fs_serial_num(entry[e].file_size); + fs_serial_str("\n"); uint32_t lba = vol->cluster_begin_lba + (search_cluster - 2) * vol->sectors_per_cluster; int sect_in_cluster = (e * 32) / 512; @@ -922,6 +969,8 @@ static bool realfs_delete(char drive, const char *path) { for (int e = 0; e < entries_per_cluster; e++) { if (entry[e].filename[0] == 0) break; if (entry[e].filename[0] == 0xE5) continue; + if (entry[e].attributes == 0x0F) continue; // Skip LFN entries + if (entry[e].attributes & ATTR_VOLUME_ID) continue; // Skip volume label // Format name and compare char name[12]; @@ -1062,6 +1111,8 @@ static int realfs_list_directory(char drive, const char *path, FAT32_FileInfo *e for (int e = 0; e < entries_per_cluster && count < max_entries; e++) { if (entry[e].filename[0] == 0) break; if (entry[e].filename[0] == 0xE5) continue; + if (entry[e].attributes == 0x0F) continue; // Skip LFN entries + if (entry[e].attributes & ATTR_VOLUME_ID) continue; // Skip volume label // Format name char name[13]; diff --git a/src/kernel/images/.DS_Store b/src/kernel/images/.DS_Store index da96730..3eca0ac 100644 Binary files a/src/kernel/images/.DS_Store and b/src/kernel/images/.DS_Store differ diff --git a/src/kernel/images/wallpapers/.DS_Store b/src/kernel/images/wallpapers/.DS_Store index a913904..5de30c9 100644 Binary files a/src/kernel/images/wallpapers/.DS_Store and b/src/kernel/images/wallpapers/.DS_Store differ diff --git a/src/kernel/wm.c b/src/kernel/wm.c index 670d213..4215ced 100644 --- a/src/kernel/wm.c +++ b/src/kernel/wm.c @@ -16,6 +16,7 @@ #include "about.h" #include "minesweeper.h" #include "fat32.h" +#include "nanojpeg.h" #include "memory_manager.h" #include "paint.h" #include "disk.h" @@ -474,12 +475,109 @@ void draw_document_icon(int x, int y, const char *label) { draw_icon_label(x, y, label); } +// === Dynamic thumbnail cache for JPG explorer icons === +#define THUMB_CACHE_SIZE 8 +#define THUMB_PIXELS (48 * 48) +static struct { + char path[256]; + uint32_t pixels[THUMB_PIXELS]; + bool valid; + bool failed; // Mark as failed so we don't retry +} thumb_cache[THUMB_CACHE_SIZE]; +static int thumb_cache_next = 0; // Round-robin eviction + +static uint32_t* thumb_cache_lookup(const char *path) { + for (int i = 0; i < THUMB_CACHE_SIZE; i++) { + if (thumb_cache[i].valid && str_eq(thumb_cache[i].path, path) == 0) { + return thumb_cache[i].pixels; + } + } + return NULL; +} + +static bool thumb_cache_is_failed(const char *path) { + for (int i = 0; i < THUMB_CACHE_SIZE; i++) { + if (thumb_cache[i].failed && str_eq(thumb_cache[i].path, path) == 0) { + return true; + } + } + return false; +} + +static uint32_t* thumb_cache_decode(const char *path) { + // Open and read the JPG file + FAT32_FileHandle *fh = fat32_open(path, "r"); + if (!fh) return NULL; + + uint32_t file_size = fh->size; + if (file_size == 0 || file_size > 2 * 1024 * 1024) { + fat32_close(fh); + return NULL; + } + + unsigned char *buf = (unsigned char*)kmalloc(file_size); + if (!buf) { fat32_close(fh); return NULL; } + + int total = 0; + while (total < (int)file_size) { + int chunk = fat32_read(fh, buf + total, (int)file_size - total); + if (chunk <= 0) break; + total += chunk; + } + fat32_close(fh); + + if (total <= 0) { kfree(buf); return NULL; } + + // Decode JPEG + njInit(); + if (njDecode(buf, total) != NJ_OK) { + njDone(); + kfree(buf); + return NULL; + } + + int img_w = njGetWidth(); + int img_h = njGetHeight(); + unsigned char *img = njGetImage(); + + // Store in cache — downscale to 48x48 + int slot = thumb_cache_next; + thumb_cache_next = (thumb_cache_next + 1) % THUMB_CACHE_SIZE; + + // Copy path + int p = 0; + while (path[p] && p < 255) { thumb_cache[slot].path[p] = path[p]; p++; } + thumb_cache[slot].path[p] = 0; + + // Downscale image to 48x48 with aspect-fill + for (int ty = 0; ty < 48; ty++) { + for (int tx = 0; tx < 48; tx++) { + int sx = tx * img_w / 48; + int sy = ty * img_h / 48; + if (sx >= img_w) sx = img_w - 1; + if (sy >= img_h) sy = img_h - 1; + int idx = (sy * img_w + sx) * 3; + uint32_t r = img[idx], g = img[idx+1], b = img[idx+2]; + thumb_cache[slot].pixels[ty * 48 + tx] = 0xFF000000 | (r << 16) | (g << 8) | b; + } + } + + thumb_cache[slot].valid = true; + thumb_cache[slot].failed = false; + + njDone(); + kfree(buf); + + return thumb_cache[slot].pixels; +} + void draw_image_icon(int x, int y, const char *label) { uint32_t icon_buf[48 * 48]; for (int i = 0; i < 48 * 48; i++) icon_buf[i] = 0xFFFF00FF; graphics_set_render_target(icon_buf, 48, 48); uint32_t *thumb = NULL; + // Fast path: check hardcoded wallpaper names if (str_eq(label, "moon.jpg") == 0) thumb = wallpaper_get_thumb(0); else if (str_eq(label, "mountain.jpg") == 0) thumb = wallpaper_get_thumb(1); else if (str_eq(label, "moon") == 0) thumb = wallpaper_get_thumb(0); @@ -490,15 +588,52 @@ void draw_image_icon(int x, int y, const char *label) { else if (str_ends_with(label, "mountain.jpg")) thumb = wallpaper_get_thumb(1); } + // Dynamic path: try thumbnail cache for any JPG file path + if (!thumb && !thumb_cache_is_failed(label)) { + thumb = thumb_cache_lookup(label); + if (!thumb) { + // Try to decode and cache + graphics_set_render_target(NULL, 0, 0); // Restore before file I/O + thumb = thumb_cache_decode(label); + if (!thumb) { + // Mark as failed so we don't retry every frame + int slot = thumb_cache_next; + int p = 0; + while (label[p] && p < 255) { thumb_cache[slot].path[p] = label[p]; p++; } + thumb_cache[slot].path[p] = 0; + thumb_cache[slot].valid = false; + thumb_cache[slot].failed = true; + thumb_cache_next = (thumb_cache_next + 1) % THUMB_CACHE_SIZE; + } + // Re-set render target for icon drawing + for (int i = 0; i < 48 * 48; i++) icon_buf[i] = 0xFFFF00FF; + graphics_set_render_target(icon_buf, 48, 48); + } + } + if (thumb) { // White border draw_rounded_rect_filled(0, 0, 48, 48, 4, 0xFFFFFFFF); + // Draw thumbnail into icon - handle both 100x60 wallpaper thumbs and 48x48 dynamic thumbs + bool is_wallpaper_thumb = false; + if (str_ends_with(label, "moon.jpg") || str_ends_with(label, "mountain.jpg") || + str_eq(label, "moon") == 0 || str_eq(label, "mountain") == 0) { + is_wallpaper_thumb = true; + } int dst_w = 44, dst_h = 44; for (int ty = 0; ty < dst_h; ty++) { for (int tx = 0; tx < dst_w; tx++) { - int sx = tx * 100 / dst_w; - int sy = ty * 60 / dst_h; - put_pixel(2 + tx, 2 + ty, thumb[sy * 100 + sx]); + uint32_t pixel; + if (is_wallpaper_thumb) { + int sx = tx * 100 / dst_w; + int sy = ty * 60 / dst_h; + pixel = thumb[sy * 100 + sx]; + } else { + int sx = tx * 48 / dst_w; + int sy = ty * 48 / dst_h; + pixel = thumb[sy * 48 + sx]; + } + put_pixel(2 + tx, 2 + ty, pixel); } } } else { @@ -969,6 +1104,7 @@ void wm_paint(void) { if (str_ends_with(icon->name, ".pnt")) draw_paint_icon(icon->x, icon->y, icon->name); else if (str_ends_with(icon->name, ".jpg") || str_ends_with(icon->name, ".JPG")) { draw_image_icon(icon->x, icon->y, icon->name); + draw_icon_label(icon->x, icon->y, icon->name); } else draw_document_icon(icon->x, icon->y, icon->name); }