V1.63 bug fixes

This commit is contained in:
boreddevnl
2026-02-25 15:27:29 +01:00
parent 6e2f0d8c1a
commit cc950974e8
27 changed files with 348 additions and 69 deletions

BIN
src/kernel/.DS_Store vendored

Binary file not shown.

View File

@@ -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);

View File

@@ -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");
}

View File

@@ -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);

View File

@@ -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*)&sector[11];
uint16_t spf16 = *(uint16_t*)&sector[22];
uint32_t spf32 = *(uint32_t*)&sector[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);
}

View File

@@ -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;

View File

@@ -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];

Binary file not shown.

Binary file not shown.

View File

@@ -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);
}