diff --git a/README.md b/README.md index fbeb795..5c05b20 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# Brew OS 1.20 Alpha +# Brew OS 1.40 Beta +BrewOS is now in a Beta stage as i have brought over all apps from brewkernel and have made the DE a lot more usable and stable. ## Brewkernel is now BrewOS! Brewkernel will from now on be deprecated as it's core became too messy. I have built a less bloated kernel and wrote a DE above it, which is why it is now an OS instead of a kernel (in my opinion). @@ -9,12 +10,16 @@ Brew Kernel is a simple x86_64 hobbyist operating system. It features a DE (and WM), a FAT32 filesystem, customizable UI and much much more! ## Features +- Drag and drop mouse centered UI +- Customizable UI - Basic Networking Stack - Brew WM - Fat 32 FS - 64-bit long mode support - Multiboot2 compliant - Text editor +- Markdown Viewer +- Minesweeper - IDT - Ability to run on actual x86_64 hardware - CLI diff --git a/brewos.iso b/brewos.iso index 79f39b4..274e660 100644 Binary files a/brewos.iso and b/brewos.iso differ diff --git a/build/about.o b/build/about.o index 2481993..d454b90 100644 Binary files a/build/about.o and b/build/about.o differ diff --git a/build/brewos.elf b/build/brewos.elf index c33e912..c9b6505 100755 Binary files a/build/brewos.elf and b/build/brewos.elf differ diff --git a/build/calculator.o b/build/calculator.o index 032a0a9..a4d2783 100644 Binary files a/build/calculator.o and b/build/calculator.o differ diff --git a/build/cli_apps/about.o b/build/cli_apps/about.o index 19001a8..2a236cb 100644 Binary files a/build/cli_apps/about.o and b/build/cli_apps/about.o differ diff --git a/build/cli_apps/beep.o b/build/cli_apps/beep.o index 27522fa..4143f9b 100644 Binary files a/build/cli_apps/beep.o and b/build/cli_apps/beep.o differ diff --git a/build/cli_apps/blind.o b/build/cli_apps/blind.o index 0b3e866..65fbfd7 100644 Binary files a/build/cli_apps/blind.o and b/build/cli_apps/blind.o differ diff --git a/build/cli_apps/cc.o b/build/cli_apps/cc.o index 3f0d347..92046fd 100644 Binary files a/build/cli_apps/cc.o and b/build/cli_apps/cc.o differ diff --git a/build/cli_apps/clear.o b/build/cli_apps/clear.o index ff6ff00..2d930d7 100644 Binary files a/build/cli_apps/clear.o and b/build/cli_apps/clear.o differ diff --git a/build/cli_apps/cli_utils.o b/build/cli_apps/cli_utils.o index eb10a93..c57edcb 100644 Binary files a/build/cli_apps/cli_utils.o and b/build/cli_apps/cli_utils.o differ diff --git a/build/cli_apps/cowsay.o b/build/cli_apps/cowsay.o index 9f174a8..9ba4f1a 100644 Binary files a/build/cli_apps/cowsay.o and b/build/cli_apps/cowsay.o differ diff --git a/build/cli_apps/date.o b/build/cli_apps/date.o index 58b1106..4b16898 100644 Binary files a/build/cli_apps/date.o and b/build/cli_apps/date.o differ diff --git a/build/cli_apps/exit.o b/build/cli_apps/exit.o index 64d31fc..267fcca 100644 Binary files a/build/cli_apps/exit.o and b/build/cli_apps/exit.o differ diff --git a/build/cli_apps/fs_commands.o b/build/cli_apps/fs_commands.o index 9c32906..435ce84 100644 Binary files a/build/cli_apps/fs_commands.o and b/build/cli_apps/fs_commands.o differ diff --git a/build/cli_apps/help.o b/build/cli_apps/help.o index 5a6069a..7fb0a5e 100644 Binary files a/build/cli_apps/help.o and b/build/cli_apps/help.o differ diff --git a/build/cli_apps/license.o b/build/cli_apps/license.o index 2c9dad6..6fb7762 100644 Binary files a/build/cli_apps/license.o and b/build/cli_apps/license.o differ diff --git a/build/cli_apps/man.o b/build/cli_apps/man.o index 3470d6e..7a28400 100644 Binary files a/build/cli_apps/man.o and b/build/cli_apps/man.o differ diff --git a/build/cli_apps/math.o b/build/cli_apps/math.o index b0e6922..088ee24 100644 Binary files a/build/cli_apps/math.o and b/build/cli_apps/math.o differ diff --git a/build/cli_apps/memcmd.o b/build/cli_apps/memcmd.o index 254256b..b2f4541 100644 Binary files a/build/cli_apps/memcmd.o and b/build/cli_apps/memcmd.o differ diff --git a/build/cli_apps/meminfo.o b/build/cli_apps/meminfo.o index b639223..6479211 100644 Binary files a/build/cli_apps/meminfo.o and b/build/cli_apps/meminfo.o differ diff --git a/build/cli_apps/net.o b/build/cli_apps/net.o index c49162d..0e8518f 100644 Binary files a/build/cli_apps/net.o and b/build/cli_apps/net.o differ diff --git a/build/cli_apps/pci_list.o b/build/cli_apps/pci_list.o index df733a7..4f170fc 100644 Binary files a/build/cli_apps/pci_list.o and b/build/cli_apps/pci_list.o differ diff --git a/build/cli_apps/readtheman.o b/build/cli_apps/readtheman.o index 320bef1..162b0f6 100644 Binary files a/build/cli_apps/readtheman.o and b/build/cli_apps/readtheman.o differ diff --git a/build/cli_apps/reboot.o b/build/cli_apps/reboot.o index 2734c35..8a4114c 100644 Binary files a/build/cli_apps/reboot.o and b/build/cli_apps/reboot.o differ diff --git a/build/cli_apps/shutdown.o b/build/cli_apps/shutdown.o index 16fa3ce..0e2b9f7 100644 Binary files a/build/cli_apps/shutdown.o and b/build/cli_apps/shutdown.o differ diff --git a/build/cli_apps/txtedit.o b/build/cli_apps/txtedit.o index b3ad107..83f3e98 100644 Binary files a/build/cli_apps/txtedit.o and b/build/cli_apps/txtedit.o differ diff --git a/build/cli_apps/uptime.o b/build/cli_apps/uptime.o index e155568..800fc06 100644 Binary files a/build/cli_apps/uptime.o and b/build/cli_apps/uptime.o differ diff --git a/build/cmd.o b/build/cmd.o index ae4a6d9..542a0a1 100644 Binary files a/build/cmd.o and b/build/cmd.o differ diff --git a/build/control_panel.o b/build/control_panel.o index b33233f..46bddb0 100644 Binary files a/build/control_panel.o and b/build/control_panel.o differ diff --git a/build/dns.o b/build/dns.o index fccdc31..3f03630 100644 Binary files a/build/dns.o and b/build/dns.o differ diff --git a/build/e1000.o b/build/e1000.o index efe2821..b500956 100644 Binary files a/build/e1000.o and b/build/e1000.o differ diff --git a/build/editor.o b/build/editor.o index b2b2b1f..7a6633f 100644 Binary files a/build/editor.o and b/build/editor.o differ diff --git a/build/explorer.o b/build/explorer.o index 7b522d1..dab1437 100644 Binary files a/build/explorer.o and b/build/explorer.o differ diff --git a/build/fat32.o b/build/fat32.o index 9b608cc..ba9f85f 100644 Binary files a/build/fat32.o and b/build/fat32.o differ diff --git a/build/graphics.o b/build/graphics.o index e063cc7..c7c9a63 100644 Binary files a/build/graphics.o and b/build/graphics.o differ diff --git a/build/http.o b/build/http.o index b6d5e81..099259c 100644 Binary files a/build/http.o and b/build/http.o differ diff --git a/build/icmp.o b/build/icmp.o index 0f25c43..8d1834e 100644 Binary files a/build/icmp.o and b/build/icmp.o differ diff --git a/build/idt.o b/build/idt.o index 4f13af2..606eacb 100644 Binary files a/build/idt.o and b/build/idt.o differ diff --git a/build/licensewr.o b/build/licensewr.o index 48e9b82..0b16132 100644 Binary files a/build/licensewr.o and b/build/licensewr.o differ diff --git a/build/main.o b/build/main.o index 3c87b9b..d9be3e7 100644 Binary files a/build/main.o and b/build/main.o differ diff --git a/build/markdown.o b/build/markdown.o index 21e749e..0728afa 100644 Binary files a/build/markdown.o and b/build/markdown.o differ diff --git a/build/memory_manager.o b/build/memory_manager.o index efb2b26..7ee93e5 100644 Binary files a/build/memory_manager.o and b/build/memory_manager.o differ diff --git a/build/minesweeper.o b/build/minesweeper.o index afcb07c..3c4bd5d 100644 Binary files a/build/minesweeper.o and b/build/minesweeper.o differ diff --git a/build/network.o b/build/network.o index b42e845..fdc7189 100644 Binary files a/build/network.o and b/build/network.o differ diff --git a/build/notepad.o b/build/notepad.o index 30d99db..d1fc901 100644 Binary files a/build/notepad.o and b/build/notepad.o differ diff --git a/build/pci.o b/build/pci.o index 284b0aa..0df8fe0 100644 Binary files a/build/pci.o and b/build/pci.o differ diff --git a/build/platform.o b/build/platform.o index e63f482..2131d19 100644 Binary files a/build/platform.o and b/build/platform.o differ diff --git a/build/ps2.o b/build/ps2.o index f2832c9..22fca5e 100644 Binary files a/build/ps2.o and b/build/ps2.o differ diff --git a/build/rtc.o b/build/rtc.o index 88921b4..ffe2d25 100644 Binary files a/build/rtc.o and b/build/rtc.o differ diff --git a/build/tcp.o b/build/tcp.o index 5509d88..e99ca42 100644 Binary files a/build/tcp.o and b/build/tcp.o differ diff --git a/build/vm.o b/build/vm.o index 7a4f913..860ac6f 100644 Binary files a/build/vm.o and b/build/vm.o differ diff --git a/build/wm.o b/build/wm.o index 84b9702..37a6d8a 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 c33e912..c9b6505 100755 Binary files a/iso_root/brewos.elf and b/iso_root/brewos.elf differ diff --git a/src/kernel/about.c b/src/kernel/about.c index a3c3a3e..a94ecd9 100644 --- a/src/kernel/about.c +++ b/src/kernel/about.c @@ -35,8 +35,8 @@ static void about_paint(Window *win) { // Version info draw_string(offset_x, offset_y + 105, "BrewOS", COLOR_BLACK); - draw_string(offset_x, offset_y + 120, "BrewOS Version 1.30", COLOR_BLACK); - draw_string(offset_x, offset_y + 135, "Kernel Version 2.3.0", COLOR_BLACK); + draw_string(offset_x, offset_y + 120, "BrewOS Version 1.40", COLOR_BLACK); + draw_string(offset_x, offset_y + 135, "Kernel Version 2.3.1", COLOR_BLACK); // Copyright draw_string(offset_x, offset_y + 150, "(C) 2026 boreddevnl.", COLOR_BLACK); diff --git a/src/kernel/cli_apps/about.c b/src/kernel/cli_apps/about.c index 93df31c..a19298e 100644 --- a/src/kernel/cli_apps/about.c +++ b/src/kernel/cli_apps/about.c @@ -2,6 +2,6 @@ void cli_cmd_brewver(char *args) { (void)args; - cli_write("BrewOS v1.30 Alpha\n"); - cli_write("BrewOS Kernel V2.3.0 Pre-Alpha\n"); + cli_write("BrewOS v1.40 Beta\n"); + cli_write("BrewOS Kernel V2.3.1 Beta\n"); } diff --git a/src/kernel/cmd.c b/src/kernel/cmd.c index 511fbf3..dab25cf 100644 --- a/src/kernel/cmd.c +++ b/src/kernel/cmd.c @@ -158,8 +158,6 @@ static void cmd_set_line_content(const char *str) { } } -// Manual and license pages are now in the individual command files - // --- Terminal Emulation --- static void cmd_scroll_up() { @@ -293,8 +291,8 @@ void pager_wrap_content(const char **lines, int count) { int chunk_len = remaining; if (chunk_len > CMD_COLS) chunk_len = CMD_COLS; - // If we are cutting a word, backtrack to last space - if (chunk_len < remaining) { // Only check if we are actually wrapping + // If cutting a word, backtrack to last space + if (chunk_len < remaining) { // Only check if actually wrapping int split_point = chunk_len; while (split_point > 0 && line[processed + split_point] != ' ') { split_point--; @@ -328,6 +326,45 @@ void pager_set_mode(void) { current_mode = MODE_PAGER; } +// Internal LS command to avoid stack overflow in external module +static void internal_cmd_ls(char *args) { + char path[256]; + if (args && *args) { + int i=0; + while(args[i] && i < 255) { path[i] = args[i]; i++; } + path[i] = 0; + } else { + path[0] = '.'; path[1] = 0; + } + + int max_files = 64; + FAT32_FileInfo *files = (FAT32_FileInfo*)kmalloc(max_files * sizeof(FAT32_FileInfo)); + if (!files) { + cmd_write("Error: Out of memory\n"); + return; + } + + int count = fat32_list_directory(path, files, max_files); + + for (int i = 0; i < count; i++) { + if (files[i].is_directory) { + cmd_write("[DIR] "); + } else { + cmd_write("[FILE] "); + } + + cmd_write(files[i].name); + if (!files[i].is_directory) { + cmd_write(" "); + cmd_write_int(files[i].size); + cmd_write("b"); + } + cmd_write("\n"); + } + + kfree(files); +} + // --- Commands (now delegated to cli_apps/) --- // Command dispatch table @@ -374,8 +411,8 @@ static const CommandEntry commands[] = { {"cd", cli_cmd_cd}, {"PWD", cli_cmd_pwd}, {"pwd", cli_cmd_pwd}, - {"LS", cli_cmd_ls}, - {"ls", cli_cmd_ls}, + {"LS", internal_cmd_ls}, + {"ls", internal_cmd_ls}, {"MKDIR", cli_cmd_mkdir}, {"mkdir", cli_cmd_mkdir}, {"RM", cli_cmd_rm}, @@ -895,12 +932,26 @@ void cmd_reset(void) { } static void create_test_files(void) { - fat32_mkdir("Documents"); - fat32_mkdir("Projects"); - fat32_mkdir("Documents/Important"); - fat32_mkdir("Apps"); + if (!fat32_exists("Documents")) fat32_mkdir("Documents"); + if (!fat32_exists("Projects")) fat32_mkdir("Projects"); + if (!fat32_exists("Documents/Important")) fat32_mkdir("Documents/Important"); + if (!fat32_exists("Apps")) fat32_mkdir("Apps"); + if (!fat32_exists("Desktop")) fat32_mkdir("Desktop"); + if (!fat32_exists("RecycleBin")) fat32_mkdir("RecycleBin"); - FAT32_FileHandle *fh = fat32_open("README.md", "w"); + // Create Desktop Shortcuts + FAT32_FileHandle *fh; + fh = fat32_open("Desktop/Explorer.shortcut", "w"); if(fh) fat32_close(fh); + fh = fat32_open("Desktop/Notepad.shortcut", "w"); if(fh) fat32_close(fh); + fh = fat32_open("Desktop/Calculator.shortcut", "w"); if(fh) fat32_close(fh); + fh = fat32_open("Desktop/Minesweeper.shortcut", "w"); if(fh) fat32_close(fh); + fh = fat32_open("Desktop/Control Panel.shortcut", "w"); if(fh) fat32_close(fh); + fh = fat32_open("Desktop/Terminal.shortcut", "w"); if(fh) fat32_close(fh); + fh = fat32_open("Desktop/About.shortcut", "w"); if(fh) fat32_close(fh); + fh = fat32_open("Desktop/Recycle Bin.shortcut", "w"); if(fh) fat32_close(fh); + + // Always try to write README to ensure content exists + fh = fat32_open("README.md", "w"); if (fh) { const char *content = "# Brew OS 1.01 Alpha\n\n" @@ -992,8 +1043,8 @@ static void create_test_files(void) { "The above attribution requirements are informational and intended to\n" "ensure proper credit is given. They do not alter or supersede the\n" "terms of the GNU General Public License (GPL), which governs this work.\n"; - fat32_write(fh, (void *)content, cmd_strlen(content)); - fat32_close(fh); + fat32_write(fh, (void *)content, cmd_strlen(content)); + fat32_close(fh); } fh = fat32_open("Apps/README.md", "w"); @@ -1001,7 +1052,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) wordofgod.c and then run ./wordofgod \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"; fat32_write(fh, (void *)content, cmd_strlen(content)); fat32_close(fh); } @@ -1024,74 +1075,60 @@ static void create_test_files(void) { fh = fat32_open("Apps/wordofgod.c", "w"); if (fh) { - char *h = "int main(){int l;l=malloc(1200);"; - fat32_write(fh, h, cmd_strlen(h)); - char *w1 = "poke(l+0,\"In \");poke(l+4,\"the \");poke(l+8,\"beginning \");poke(l+12,\"God \");poke(l+16,\"created \");poke(l+20,\"heaven \");poke(l+24,\"and \");poke(l+28,\"earth \");poke(l+32,\"light \");poke(l+36,\"darkness \");"; - fat32_write(fh, w1, cmd_strlen(w1)); - char *w2 = "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 \");"; - fat32_write(fh, w2, cmd_strlen(w2)); - char *w3 = "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 \");"; - fat32_write(fh, w3, cmd_strlen(w3)); - char *w4 = "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 \");"; - fat32_write(fh, w4, cmd_strlen(w4)); - char *w5 = "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 \");"; - fat32_write(fh, w5, cmd_strlen(w5)); - char *w6 = "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 \");"; - fat32_write(fh, w6, cmd_strlen(w6)); - char *w7 = "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 \");"; - fat32_write(fh, w7, cmd_strlen(w7)); - char *w8 = "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 \");"; - fat32_write(fh, w8, cmd_strlen(w8)); - char *w9 = "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 \");"; - fat32_write(fh, w9, cmd_strlen(w9)); - char *w10 = "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 \");"; - fat32_write(fh, w10, cmd_strlen(w10)); - char *w11 = "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 \");"; - fat32_write(fh, w11, cmd_strlen(w11)); - char *w12 = "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 \");"; - fat32_write(fh, w12, cmd_strlen(w12)); - char *w13 = "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 \");"; - fat32_write(fh, w13, cmd_strlen(w13)); - char *w14 = "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 \");"; - fat32_write(fh, w14, cmd_strlen(w14)); - char *w15 = "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 \");"; - fat32_write(fh, w15, cmd_strlen(w15)); - char *w16 = "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 \");"; - fat32_write(fh, w16, cmd_strlen(w16)); - char *w17 = "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 \");"; - fat32_write(fh, w17, cmd_strlen(w17)); - char *w18 = "poke(l+680,\"flaming \");poke(l+684,\"sword \");poke(l+688,\"turned \");poke(l+692,\"way \");poke(l+696,\"knew \");poke(l+700,\"conceived \");poke(l+704,\"bare \");poke(l+708,\"cain \");poke(l+712,\"said \");poke(l+716,\"gotten \");"; - fat32_write(fh, w18, cmd_strlen(w18)); - char *w19 = "poke(l+720,\"lord \");poke(l+724,\"again \");poke(l+728,\"abel \");poke(l+732,\"sheep \");poke(l+736,\"tiller \");poke(l+740,\"process \");poke(l+744,\"time \");poke(l+748,\"pass \");poke(l+752,\"brought \");poke(l+756,\"fruit \");"; - fat32_write(fh, w19, cmd_strlen(w19)); - char *w20 = "poke(l+760,\"offering \");poke(l+764,\"firstlings \");poke(l+768,\"flock \");poke(l+772,\"fat \");poke(l+776,\"thereof \");poke(l+780,\"respect \");poke(l+784,\"wroth \");poke(l+788,\"countenance \");poke(l+792,\"fallen \");poke(l+796,\"well \");"; - fat32_write(fh, w20, cmd_strlen(w20)); - char *w21 = "poke(l+800,\"accepted \");poke(l+804,\"not \");poke(l+808,\"sin \");poke(l+812,\"lieth \");poke(l+816,\"door \");poke(l+820,\"unto \");poke(l+824,\"rule \");poke(l+828,\"talked \");poke(l+832,\"field \");poke(l+836,\"rose \");"; - fat32_write(fh, w21, cmd_strlen(w21)); - char *w22 = "poke(l+840,\"slew \");poke(l+844,\"done \");poke(l+848,\"crieth \");poke(l+852,\"mouth \");poke(l+856,\"receive \");poke(l+860,\"strength \");poke(l+864,\"fugitive \");poke(l+868,\"vagabond \");poke(l+872,\"punishment \");poke(l+876,\"greater \");"; - fat32_write(fh, w22, cmd_strlen(w22)); - char *w23 = "poke(l+880,\"bear \");poke(l+884,\"driven \");poke(l+888,\"hid \");poke(l+892,\"findeth \");poke(l+896,\"slay \");poke(l+900,\"vengeance \");poke(l+904,\"sevenfold \");poke(l+908,\"mark \");poke(l+912,\"finding \");poke(l+916,\"kill \");"; - fat32_write(fh, w23, cmd_strlen(w23)); - char *w24 = "poke(l+920,\"presence \");poke(l+924,\"dwelt \");poke(l+928,\"nod \");poke(l+932,\"enoch \");poke(l+936,\"city \");poke(l+940,\"irad \");poke(l+944,\"mehujael \");poke(l+948,\"methusael \");poke(l+952,\"lamech \");poke(l+956,\"adah \");"; - fat32_write(fh, w24, cmd_strlen(w24)); - char *w25 = "poke(l+960,\"zillah \");poke(l+964,\"jabal \");poke(l+968,\"tent \");poke(l+972,\"cattle \");poke(l+976,\"jubal \");poke(l+980,\"harp \");poke(l+984,\"organ \");poke(l+988,\"tubalcain \");poke(l+992,\"brass \");poke(l+996,\"iron \");"; - fat32_write(fh, w25, cmd_strlen(w25)); - char *w26 = "poke(l+1000,\"naamah \");poke(l+1004,\"wives \");poke(l+1008,\"hear \");poke(l+1012,\"speech \");poke(l+1016,\"hearken \");poke(l+1020,\"young \");poke(l+1024,\"hurt \");poke(l+1028,\"wounding \");poke(l+1032,\"avenged \");poke(l+1036,\"seventy \");"; - fat32_write(fh, w26, cmd_strlen(w26)); - char *w27 = "poke(l+1040,\"seth \");poke(l+1044,\"appointed \");poke(l+1048,\"enos \");poke(l+1052,\"began \");poke(l+1056,\"call \");poke(l+1060,\"name \");poke(l+1064,\"generations \");poke(l+1068,\"adam \");poke(l+1072,\"likeness \");poke(l+1076,\"blessed \");"; - fat32_write(fh, w27, cmd_strlen(w27)); - char *w28 = "poke(l+1080,\"begat \");poke(l+1084,\"sons \");poke(l+1088,\"daughters \");poke(l+1092,\"lived \");poke(l+1096,\"died \");poke(l+1100,\"cainan \");poke(l+1104,\"mahalaleel \");poke(l+1108,\"jared \");poke(l+1112,\"walked \");poke(l+1116,\"three \");"; - fat32_write(fh, w28, cmd_strlen(w28)); - char *w29 = "poke(l+1120,\"hundred \");poke(l+1124,\"sixty \");poke(l+1128,\"five \");poke(l+1132,\"methuselah \");poke(l+1136,\"lamech \");poke(l+1140,\"noah \");poke(l+1144,\"comfort \");poke(l+1148,\"work \");poke(l+1152,\"toil \");poke(l+1156,\"hands \");"; - fat32_write(fh, w29, cmd_strlen(w29)); - char *w30 = "poke(l+1160,\"shem \");poke(l+1164,\"ham \");poke(l+1168,\"japheth \");poke(l+1172,\"men \");poke(l+1176,\"daughters \");poke(l+1180,\"born \");poke(l+1184,\"fair \");poke(l+1188,\"chose \");poke(l+1192,\"spirit \");poke(l+1196,\"strive \");"; - fat32_write(fh, w30, cmd_strlen(w30)); - char *e = "int c;int r;r=abs(rand());r=r-(r/5)*5;c=14+r;int i;i=0;while(ix + 8; + int offset_y = win->y + 30; + + // Back button + draw_string(offset_x, offset_y, "< Back", 0xFF000080); + draw_string(offset_x, offset_y + 25, "Desktop Settings:", 0xFF000000); + + int section_y = offset_y + 50; + + // Snap to Grid + draw_rect(offset_x, section_y, 15, 15, 0xFFFFFFFF); + draw_rect(offset_x, section_y, 15, 1, COLOR_BLACK); + draw_rect(offset_x, section_y, 1, 15, COLOR_BLACK); + draw_rect(offset_x + 14, section_y, 1, 15, COLOR_BLACK); + draw_rect(offset_x, section_y + 14, 15, 1, COLOR_BLACK); + if (desktop_snap_to_grid) draw_string(offset_x + 3, section_y + 3, "X", COLOR_BLACK); + draw_string(offset_x + 25, section_y + 3, "Snap to Grid", COLOR_BLACK); + + // Auto Align + section_y += 25; + draw_rect(offset_x, section_y, 15, 15, 0xFFFFFFFF); + draw_rect(offset_x, section_y, 15, 1, COLOR_BLACK); + draw_rect(offset_x, section_y, 1, 15, COLOR_BLACK); + draw_rect(offset_x + 14, section_y, 1, 15, COLOR_BLACK); + draw_rect(offset_x, section_y + 14, 15, 1, COLOR_BLACK); + if (desktop_auto_align) draw_string(offset_x + 3, section_y + 3, "X", COLOR_BLACK); + draw_string(offset_x + 25, section_y + 3, "Auto Align Icons", COLOR_BLACK); + + // Max Rows + section_y += 25; + draw_string(offset_x, section_y + 3, "Apps per column:", COLOR_BLACK); + draw_button(offset_x + 130, section_y, 20, 20, "-", false); + char num[4]; num[0] = '0' + (desktop_max_rows_per_col / 10); num[1] = '0' + (desktop_max_rows_per_col % 10); num[2] = 0; + if (num[0] == '0') { num[0] = num[1]; num[1] = 0; } + draw_string(offset_x + 160, section_y + 5, num, COLOR_BLACK); + draw_button(offset_x + 180, section_y, 20, 20, "+", false); + + // Max Cols + section_y += 25; + draw_string(offset_x, section_y + 3, "Columns:", COLOR_BLACK); + draw_button(offset_x + 130, section_y, 20, 20, "-", false); + char num_c[4]; num_c[0] = '0' + (desktop_max_cols / 10); num_c[1] = '0' + (desktop_max_cols % 10); num_c[2] = 0; + if (num_c[0] == '0') { num_c[0] = num_c[1]; num_c[1] = 0; } + draw_string(offset_x + 160, section_y + 5, num_c, COLOR_BLACK); + draw_button(offset_x + 180, section_y, 20, 20, "+", false); +} + static void control_panel_paint(Window *win) { if (current_view == VIEW_MAIN) { control_panel_paint_main(win); @@ -416,6 +475,8 @@ static void control_panel_paint(Window *win) { control_panel_paint_wallpaper(win); } else if (current_view == VIEW_NETWORK) { control_panel_paint_network(win); + } else if (current_view == VIEW_DESKTOP) { + control_panel_paint_desktop(win); } } @@ -440,6 +501,13 @@ static void control_panel_handle_click(Window *win, int x, int y) { current_view = VIEW_NETWORK; focused_field = -1; } + + // Check desktop button + int desk_offset_y = net_offset_y + 35; + if (x >= offset_x + 5 && x < offset_x + 120 && + y >= desk_offset_y && y < desk_offset_y + 25) { + current_view = VIEW_DESKTOP; + } } else if (current_view == VIEW_WALLPAPER) { int offset_x = 8; int offset_y = 30; @@ -727,6 +795,73 @@ static void control_panel_handle_click(Window *win, int x, int y) { } return; } + } else if (current_view == VIEW_DESKTOP) { + int offset_x = 8; + int offset_y = 30; + + // Back button + if (x >= offset_x && x < offset_x + 40 && y >= offset_y && y < offset_y + 15) { + current_view = VIEW_MAIN; + return; + } + + int section_y = offset_y + 50; + // Snap toggle + if (x >= offset_x && x < offset_x + 150 && y >= section_y && y < section_y + 20) { + desktop_snap_to_grid = !desktop_snap_to_grid; + // If Snap is turned OFF, Auto Align must be OFF + if (!desktop_snap_to_grid) { + desktop_auto_align = false; + } + wm_refresh_desktop(); + return; + } + + // Auto Align toggle + section_y += 25; + if (x >= offset_x && x < offset_x + 150 && y >= section_y && y < section_y + 20) { + desktop_auto_align = !desktop_auto_align; + // If Auto Align is turned ON, Snap must be ON + if (desktop_auto_align) { + desktop_snap_to_grid = true; + } + wm_refresh_desktop(); + return; + } + + // Rows adjust + section_y += 25; + if (x >= offset_x + 130 && x < offset_x + 150 && y >= section_y && y < section_y + 20) { + if (desktop_max_rows_per_col > 1) { + if (desktop_max_cols * (desktop_max_rows_per_col - 1) < wm_get_desktop_icon_count()) { + wm_show_message("Error", "Cannot reduce rows: too many files!"); + } else { + desktop_max_rows_per_col--; + wm_refresh_desktop(); + } + } + } + if (x >= offset_x + 180 && x < offset_x + 200 && y >= section_y && y < section_y + 20) { + if (desktop_max_rows_per_col < 15) desktop_max_rows_per_col++; + wm_refresh_desktop(); + } + + // Cols adjust + section_y += 25; + if (x >= offset_x + 130 && x < offset_x + 150 && y >= section_y && y < section_y + 20) { + if (desktop_max_cols > 1) { + if ((desktop_max_cols - 1) * desktop_max_rows_per_col < wm_get_desktop_icon_count()) { + wm_show_message("Error", "Cannot reduce cols: too many files!"); + } else { + desktop_max_cols--; + wm_refresh_desktop(); + } + } + } + if (x >= offset_x + 180 && x < offset_x + 200 && y >= section_y && y < section_y + 20) { + if (desktop_max_cols < 20) desktop_max_cols++; + wm_refresh_desktop(); + } } } diff --git a/src/kernel/explorer.c b/src/kernel/explorer.c index fcba2cf..b54ef3d 100644 --- a/src/kernel/explorer.c +++ b/src/kernel/explorer.c @@ -2,8 +2,15 @@ #include "graphics.h" #include "fat32.h" #include "wm.h" +#include "memory_manager.h" #include "editor.h" #include "markdown.h" +#include "cmd.h" +#include "notepad.h" +#include "calculator.h" +#include "minesweeper.h" +#include "control_panel.h" +#include "about.h" #include #include @@ -22,7 +29,13 @@ Window win_explorer; #define DIALOG_CREATE_FILE 1 #define DIALOG_CREATE_FOLDER 2 #define DIALOG_DELETE_CONFIRM 3 +#define DIALOG_REPLACE_CONFIRM 4 +#define DIALOG_REPLACE_MOVE_CONFIRM 5 +#define DIALOG_CREATE_REPLACE_CONFIRM 6 #define DIALOG_INPUT_MAX 256 +#define ACTION_RESTORE 108 +#define DIALOG_ERROR 7 +#define ACTION_CREATE_SHORTCUT 107 typedef struct { char name[256]; @@ -45,6 +58,7 @@ static int selected_item = -1; static char current_path[256] = "/"; static int last_clicked_item = -1; static uint32_t last_click_time = 0; +static int explorer_scroll_row = 0; // Dialog state static int dialog_state = DIALOG_NONE; @@ -52,6 +66,9 @@ static char dialog_input[DIALOG_INPUT_MAX] = ""; static int dialog_input_cursor = 0; static char dialog_target_path[256] = ""; // For delete confirmations static bool dialog_target_is_dir = false; // For delete confirmations +static char dialog_dest_dir[256] = ""; // For replace confirmations +static char dialog_creation_path[256] = ""; // For new file/folder creation +static char dialog_move_src[256] = ""; // For drag-drop replace // Dropdown menu state static bool dropdown_menu_visible = false; @@ -66,8 +83,20 @@ static int file_context_menu_y = 0; static int file_context_menu_item = -1; // Which item is being right-clicked #define FILE_CONTEXT_MENU_WIDTH 140 #define FILE_CONTEXT_MENU_HEIGHT 50 +#define CONTEXT_MENU_ITEM_HEIGHT 25 + +// Clipboard state +static char clipboard_path[256] = ""; +static int clipboard_action = 0; // 0=None, 1=Copy, 2=Cut #define FILE_CONTEXT_ITEMS 2 // "Open with Text Editor" and "Open with Markdown Viewer" +typedef struct { + const char *label; + int action_id; // 100+ for actions + bool enabled; + uint32_t color; +} ExplorerContextItem; + // === Helper Functions === static size_t explorer_strlen(const char *str); @@ -77,6 +106,9 @@ static void explorer_strcat(char *dest, const char *src); static void explorer_load_directory(const char *path); static void explorer_handle_right_click(Window *win, int x, int y); static void explorer_handle_file_context_menu_click(Window *win, int x, int y); +static void explorer_perform_paste(const char *dest_dir); +static void explorer_perform_move_internal(const char *source_path, const char *dest_dir); +static void explorer_copy_recursive(const char *src_path, const char *dest_path); static size_t explorer_strlen(const char *str) { size_t len = 0; @@ -124,18 +156,103 @@ static bool explorer_is_markdown_file(const char *filename) { return explorer_strcmp(ext, "md") == 0; } +// Helper to check if string starts with prefix +static bool explorer_str_starts_with(const char *str, const char *prefix) { + while(*prefix) { + if (*prefix++ != *str++) return false; + } + return true; +} + +// Helper to check if string ends with suffix +static bool explorer_str_ends_with(const char *str, const char *suffix) { + int str_len = explorer_strlen(str); + int suf_len = explorer_strlen(suffix); + if (suf_len > str_len) return false; + return explorer_strcmp(str + str_len - suf_len, suffix) == 0; +} + +// Helper for label drawing (adapted from wm.c) +static void explorer_draw_icon_label(int x, int y, const char *label) { + char line1[10] = {0}; + char line2[10] = {0}; + int len = 0; while(label[len]) len++; + + if (len <= 8) { + int i=0; while(i= 1; i--) { + if (label[i] == ' ' || label[i] == '.') { + best_split = i; + break; + } + } + + if (best_split != -1) split = best_split; + + int i; + for (i = 0; i < split; i++) line1[i] = label[i]; + line1[i] = 0; + + int start2 = split; + if (label[split] == ' ') start2++; + + int j = 0; + while (label[start2 + j] && j < 8) { + line2[j] = label[start2 + j]; + j++; + } + line2[j] = 0; + + if (label[start2 + j] != 0) { + if (j > 6) { line2[6] = '.'; line2[7] = '.'; line2[8] = 0; } + else { line2[j++] = '.'; line2[j++] = '.'; line2[j] = 0; } + } + } + + // Center in EXPLORER_ITEM_WIDTH + int l1_len = 0; while(line1[l1_len]) l1_len++; + int l1_w = l1_len * 8; + draw_string(x + (EXPLORER_ITEM_WIDTH - l1_w)/2, y + 50, line1, COLOR_BLACK); + + if (line2[0]) { + int l2_len = 0; while(line2[l2_len]) l2_len++; + int l2_w = l2_len * 8; + draw_string(x + (EXPLORER_ITEM_WIDTH - l2_w)/2, y + 60, line2, COLOR_BLACK); + } +} + // === Dialog and File Operations === -static void dialog_open_create_file(void) { +static bool check_desktop_limit_explorer(void) { + if (explorer_str_starts_with(current_path, "/Desktop")) { + // Check if root desktop + if (explorer_strcmp(current_path, "/Desktop") == 0 || explorer_strcmp(current_path, "/Desktop/") == 0) { + if (item_count >= desktop_max_cols * desktop_max_rows_per_col) { + dialog_state = DIALOG_ERROR; + explorer_strcpy(dialog_input, "Desktop is full!"); + return false; + } + } + } + return true; +} + +static void dialog_open_create_file(const char *path) { dialog_state = DIALOG_CREATE_FILE; dialog_input[0] = 0; dialog_input_cursor = 0; + explorer_strcpy(dialog_creation_path, path); } -static void dialog_open_create_folder(void) { +static void dialog_open_create_folder(const char *path) { dialog_state = DIALOG_CREATE_FOLDER; dialog_input[0] = 0; dialog_input_cursor = 0; + explorer_strcpy(dialog_creation_path, path); } static void dialog_open_delete_confirm(int item_idx) { @@ -162,13 +279,20 @@ static void dialog_close(void) { static void dialog_confirm_create_file(void) { if (dialog_input[0] == 0) return; + if (!check_desktop_limit_explorer()) return; + char full_path[256]; - explorer_strcpy(full_path, current_path); + explorer_strcpy(full_path, dialog_creation_path); if (full_path[explorer_strlen(full_path) - 1] != '/') { explorer_strcat(full_path, "/"); } explorer_strcat(full_path, dialog_input); + if (fat32_exists(full_path)) { + dialog_state = DIALOG_CREATE_REPLACE_CONFIRM; + return; + } + // Create empty file FAT32_FileHandle *file = fat32_open(full_path, "w"); if (file) { @@ -179,11 +303,29 @@ static void dialog_confirm_create_file(void) { dialog_close(); } +static void dialog_force_create_file(void) { + char full_path[256]; + explorer_strcpy(full_path, dialog_creation_path); + if (full_path[explorer_strlen(full_path) - 1] != '/') { + explorer_strcat(full_path, "/"); + } + explorer_strcat(full_path, dialog_input); + + FAT32_FileHandle *file = fat32_open(full_path, "w"); + if (file) { + fat32_close(file); + explorer_load_directory(current_path); + } + dialog_close(); +} + static void dialog_confirm_create_folder(void) { if (dialog_input[0] == 0) return; + if (!check_desktop_limit_explorer()) return; + char full_path[256]; - explorer_strcpy(full_path, current_path); + explorer_strcpy(full_path, dialog_creation_path); if (full_path[explorer_strlen(full_path) - 1] != '/') { explorer_strcat(full_path, "/"); } @@ -198,13 +340,17 @@ static void dialog_confirm_create_folder(void) { } // Recursive delete for directories -static bool explorer_delete_recursive(const char *path) { +bool explorer_delete_permanently(const char *path) { if (fat32_is_directory(path)) { // List contents and delete recursively - FAT32_FileInfo entries[64]; + FAT32_FileInfo *entries = (FAT32_FileInfo*)kmalloc(64 * sizeof(FAT32_FileInfo)); + if (!entries) return false; + int count = fat32_list_directory(path, entries, 64); for (int i = 0; i < count; i++) { + if (explorer_strcmp(entries[i].name, ".") == 0 || explorer_strcmp(entries[i].name, "..") == 0) continue; + char child_path[256]; explorer_strcpy(child_path, path); if (child_path[explorer_strlen(child_path) - 1] != '/') { @@ -213,11 +359,12 @@ static bool explorer_delete_recursive(const char *path) { explorer_strcat(child_path, entries[i].name); if (entries[i].is_directory) { - explorer_delete_recursive(child_path); + explorer_delete_permanently(child_path); } else { fat32_delete(child_path); } } + kfree(entries); // Delete the directory itself return fat32_rmdir(path); } else { @@ -226,16 +373,255 @@ static bool explorer_delete_recursive(const char *path) { } } +bool explorer_delete_recursive(const char *path) { + if (explorer_str_starts_with(path, "/RecycleBin")) { + return explorer_delete_permanently(path); + } else { + // Move to Recycle Bin + char filename[256]; + int len = explorer_strlen(path); + int i = len - 1; + while (i >= 0 && path[i] != '/') i--; + int j = 0; + for (int k = i + 1; k < len; k++) filename[j++] = path[k]; + filename[j] = 0; + + char dest_path[256]; + explorer_strcpy(dest_path, "/RecycleBin/"); + explorer_strcat(dest_path, filename); + + // Save origin + char origin_path[256]; + explorer_strcpy(origin_path, dest_path); + explorer_strcat(origin_path, ".origin"); + FAT32_FileHandle *fh = fat32_open(origin_path, "w"); + if (fh) { + fat32_write(fh, path, explorer_strlen(path)); + fat32_close(fh); + } + + // Use copy + delete (permanent) to simulate move + explorer_copy_recursive(path, dest_path); + explorer_delete_permanently(path); + return true; + } +} + static void dialog_confirm_delete(void) { explorer_delete_recursive(dialog_target_path); explorer_load_directory(current_path); dialog_close(); } +static void dialog_confirm_replace(void) { + explorer_perform_paste(dialog_dest_dir); + dialog_close(); +} + +static void dialog_confirm_replace_move(void) { + explorer_perform_move_internal(dialog_move_src, dialog_dest_dir); + dialog_close(); +} + +// === Clipboard Functions === + +void explorer_clipboard_copy(const char *path) { + explorer_strcpy(clipboard_path, path); + clipboard_action = 1; // Copy +} + +void explorer_clipboard_cut(const char *path) { + explorer_strcpy(clipboard_path, path); + clipboard_action = 2; // Cut +} + +bool explorer_clipboard_has_content(void) { + return clipboard_action != 0 && clipboard_path[0] != 0; +} + +static void explorer_copy_recursive(const char *src_path, const char *dest_path) { + if (fat32_is_directory(src_path)) { + fat32_mkdir(dest_path); + FAT32_FileInfo *files = (FAT32_FileInfo*)kmalloc(64 * sizeof(FAT32_FileInfo)); + if (!files) return; + + int count = fat32_list_directory(src_path, files, 64); + for (int i = 0; i < count; i++) { + if (explorer_strcmp(files[i].name, ".") == 0 || explorer_strcmp(files[i].name, "..") == 0) continue; + + char s_sub[256], d_sub[256]; + explorer_strcpy(s_sub, src_path); + if (s_sub[explorer_strlen(s_sub)-1] != '/') explorer_strcat(s_sub, "/"); + explorer_strcat(s_sub, files[i].name); + + explorer_strcpy(d_sub, dest_path); + if (d_sub[explorer_strlen(d_sub)-1] != '/') explorer_strcat(d_sub, "/"); + explorer_strcat(d_sub, files[i].name); + + explorer_copy_recursive(s_sub, d_sub); + } + kfree(files); + } else { + // Copy file + FAT32_FileHandle *src = fat32_open(src_path, "r"); + FAT32_FileHandle *dst = fat32_open(dest_path, "w"); + if (src && dst) { + uint8_t *buf = (uint8_t*)kmalloc(4096); + if (buf) { + int bytes; + while ((bytes = fat32_read(src, buf, 4096)) > 0) fat32_write(dst, buf, bytes); + kfree(buf); + } + } + if (src) fat32_close(src); + if (dst) fat32_close(dst); + } +} + +static void explorer_copy_file_internal(const char *src_path, const char *dest_dir) { + char filename[256]; + int len = explorer_strlen(src_path); + int i = len - 1; + while (i >= 0 && src_path[i] != '/') i--; + int j = 0; + for (int k = i + 1; k < len; k++) filename[j++] = src_path[k]; + filename[j] = 0; + + char dest_path[256]; + explorer_strcpy(dest_path, dest_dir); + if (dest_path[explorer_strlen(dest_path) - 1] != '/') { + explorer_strcat(dest_path, "/"); + } + explorer_strcat(dest_path, filename); + + if (explorer_strcmp(src_path, dest_path) == 0) return; + + explorer_copy_recursive(src_path, dest_path); +} + +static void explorer_perform_paste(const char *dest_dir) { + explorer_copy_file_internal(clipboard_path, dest_dir); + + if (clipboard_action == 2) { // Cut + // Delete source + if (fat32_is_directory(clipboard_path)) { + explorer_delete_permanently(clipboard_path); + } else { + fat32_delete(clipboard_path); + } + clipboard_action = 0; // Clear clipboard after cut-paste + } + explorer_refresh(); +} + +void explorer_clipboard_paste(const char *dest_dir) { + if (!explorer_clipboard_has_content()) return; + + // Check for collision + char filename[256]; + int len = explorer_strlen(clipboard_path); + int i = len - 1; + while (i >= 0 && clipboard_path[i] != '/') i--; + int j = 0; + for (int k = i + 1; k < len; k++) filename[j++] = clipboard_path[k]; + filename[j] = 0; + + char dest_path[256]; + explorer_strcpy(dest_path, dest_dir); + if (dest_path[explorer_strlen(dest_path) - 1] != '/') { + explorer_strcat(dest_path, "/"); + } + explorer_strcat(dest_path, filename); + + if (fat32_exists(dest_path)) { + dialog_state = DIALOG_REPLACE_CONFIRM; + explorer_strcpy(dialog_dest_dir, dest_dir); + return; + } + + explorer_perform_paste(dest_dir); +} + +void explorer_create_shortcut(const char *target_path) { + char filename[256]; + int len = explorer_strlen(target_path); + int i = len - 1; + while (i >= 0 && target_path[i] != '/') i--; + int j = 0; + for (int k = i + 1; k < len; k++) filename[j++] = target_path[k]; + filename[j] = 0; + + char shortcut_path[256]; + explorer_strcpy(shortcut_path, current_path); + if (shortcut_path[explorer_strlen(shortcut_path)-1] != '/') explorer_strcat(shortcut_path, "/"); + explorer_strcat(shortcut_path, filename); + explorer_strcat(shortcut_path, ".shortcut"); + + FAT32_FileHandle *fh = fat32_open(shortcut_path, "w"); + if (fh) { + fat32_write(fh, target_path, explorer_strlen(target_path)); + fat32_close(fh); + explorer_load_directory(current_path); + } +} + static void dropdown_menu_toggle(void) { dropdown_menu_visible = !dropdown_menu_visible; } +// === Context Menu Builder === +static int explorer_build_context_menu(ExplorerContextItem *items_out) { + int count = 0; + if (file_context_menu_item == -1) { + if (explorer_str_starts_with(current_path, "/RecycleBin")) { + // Dead space in Recycle Bin - no actions for now + return 0; + } + // Dead space + items_out[count++] = (ExplorerContextItem){"New File", 101, true, COLOR_BLACK}; + items_out[count++] = (ExplorerContextItem){"New Folder", 102, true, COLOR_BLACK}; + items_out[count++] = (ExplorerContextItem){"Paste", 103, explorer_clipboard_has_content(), explorer_clipboard_has_content() ? COLOR_BLACK : COLOR_DKGRAY}; + } else { + if (explorer_str_starts_with(current_path, "/RecycleBin")) { + items_out[count++] = (ExplorerContextItem){"Restore", ACTION_RESTORE, true, COLOR_BLACK}; + items_out[count++] = (ExplorerContextItem){"Delete Forever", 106, true, COLOR_RED}; + return count; + } + + bool is_dir = items[file_context_menu_item].is_directory; + + if (!is_dir) { + items_out[count++] = (ExplorerContextItem){"Open", 100, true, COLOR_BLACK}; + if (explorer_is_markdown_file(items[file_context_menu_item].name)) { + items_out[count++] = (ExplorerContextItem){"Open w/ Markdown", 109, true, COLOR_BLACK}; + } + } + + items_out[count++] = (ExplorerContextItem){"Cut", 104, true, COLOR_BLACK}; + items_out[count++] = (ExplorerContextItem){"Copy", 105, true, COLOR_BLACK}; + + if (is_dir) { + items_out[count++] = (ExplorerContextItem){"Paste", 103, explorer_clipboard_has_content(), explorer_clipboard_has_content() ? COLOR_BLACK : COLOR_DKGRAY}; + } + + items_out[count++] = (ExplorerContextItem){"Delete", 106, true, COLOR_RED}; + items_out[count++] = (ExplorerContextItem){"Create Shortcut", ACTION_CREATE_SHORTCUT, true, COLOR_BLACK}; + + if (is_dir) { + items_out[count++] = (ExplorerContextItem){"New File", 101, true, COLOR_BLACK}; + items_out[count++] = (ExplorerContextItem){"New Folder", 102, true, COLOR_BLACK}; + // Separator logic handled in paint + items_out[count++] = (ExplorerContextItem){"---", 0, false, 0}; // Marker + items_out[count++] = (ExplorerContextItem){"Blue", 200, true, COLOR_APPLE_BLUE}; + items_out[count++] = (ExplorerContextItem){"Red", 201, true, COLOR_RED}; + items_out[count++] = (ExplorerContextItem){"Yellow", 202, true, COLOR_APPLE_YELLOW}; + items_out[count++] = (ExplorerContextItem){"Green", 203, true, COLOR_APPLE_GREEN}; + items_out[count++] = (ExplorerContextItem){"Black", 204, true, COLOR_BLACK}; + } + } + return count; +} + // === Helper Functions (continued) // === Explorer Logic === @@ -275,10 +661,42 @@ static void explorer_set_folder_color(const char *folder_path, uint32_t color) { } } +static void explorer_restore_file(int item_idx) { + if (item_idx < 0 || item_idx >= item_count) return; + + char recycle_path[256]; + explorer_strcpy(recycle_path, current_path); + if (recycle_path[explorer_strlen(recycle_path) - 1] != '/') explorer_strcat(recycle_path, "/"); + explorer_strcat(recycle_path, items[item_idx].name); + + char origin_file_path[256]; + explorer_strcpy(origin_file_path, recycle_path); + explorer_strcat(origin_file_path, ".origin"); + + char original_path[256] = {0}; + FAT32_FileHandle *fh = fat32_open(origin_file_path, "r"); + if (fh) { + int len = fat32_read(fh, original_path, 255); + if (len > 0) original_path[len] = 0; + fat32_close(fh); + } + + if (original_path[0] == 0) return; // No origin info + + // Restore + explorer_copy_recursive(recycle_path, original_path); + explorer_delete_permanently(recycle_path); + fat32_delete(origin_file_path); + + explorer_refresh(); +} + static void explorer_load_directory(const char *path) { explorer_strcpy(current_path, path); - FAT32_FileInfo entries[EXPLORER_MAX_FILES]; + FAT32_FileInfo *entries = (FAT32_FileInfo*)kmalloc(EXPLORER_MAX_FILES * sizeof(FAT32_FileInfo)); + if (!entries) return; + int count = fat32_list_directory(path, entries, EXPLORER_MAX_FILES); item_count = 0; @@ -287,6 +705,11 @@ static void explorer_load_directory(const char *path) { if (explorer_strcmp(entries[i].name, ".color") == 0) { continue; } + + // Skip .origin files + if (explorer_str_ends_with(entries[i].name, ".origin")) { + continue; + } explorer_strcpy(items[item_count].name, entries[i].name); items[item_count].is_directory = entries[i].is_directory; @@ -306,7 +729,9 @@ static void explorer_load_directory(const char *path) { item_count++; } + kfree(entries); selected_item = -1; + explorer_scroll_row = 0; } static void explorer_navigate_to(const char *dirname) { @@ -343,8 +768,111 @@ static void explorer_navigate_to(const char *dirname) { explorer_load_directory(new_path); } +void explorer_open_directory(const char *path) { + explorer_load_directory(path); + win_explorer.visible = true; + win_explorer.focused = true; +} + +static void explorer_open_target(const char *path) { + if (fat32_is_directory(path)) { + explorer_open_directory(path); + } else { + int max_z = 0; + if (win_explorer.z_index > max_z) max_z = win_explorer.z_index; + if (win_cmd.z_index > max_z) max_z = win_cmd.z_index; + if (win_notepad.z_index > max_z) max_z = win_notepad.z_index; + if (win_calculator.z_index > max_z) max_z = win_calculator.z_index; + if (win_editor.z_index > max_z) max_z = win_editor.z_index; + if (win_markdown.z_index > max_z) max_z = win_markdown.z_index; + if (win_control_panel.z_index > max_z) max_z = win_control_panel.z_index; + if (win_about.z_index > max_z) max_z = win_about.z_index; + if (win_minesweeper.z_index > max_z) max_z = win_minesweeper.z_index; + + if (explorer_is_markdown_file(path)) { + win_markdown.visible = true; win_markdown.focused = true; + win_markdown.z_index = max_z + 1; + markdown_open_file(path); + } else { + win_editor.visible = true; win_editor.focused = true; + win_editor.z_index = max_z + 1; + editor_open_file(path); + } + } +} + +static void explorer_open_item(int index) { + if (index < 0 || index >= item_count) return; + + if (items[index].is_directory) { + explorer_navigate_to(items[index].name); + return; + } + + 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, items[index].name); + + // Check if shortcut + if (explorer_str_ends_with(items[index].name, ".shortcut")) { + Window *target = NULL; + if (explorer_strcmp(items[index].name, "Notepad.shortcut") == 0) { + target = &win_notepad; notepad_reset(); + } else if (explorer_strcmp(items[index].name, "Calculator.shortcut") == 0) { + target = &win_calculator; + } else if (explorer_strcmp(items[index].name, "Terminal.shortcut") == 0) { + target = &win_cmd; cmd_reset(); + } else if (explorer_strcmp(items[index].name, "Minesweeper.shortcut") == 0) { + target = &win_minesweeper; + } else if (explorer_strcmp(items[index].name, "Control Panel.shortcut") == 0) { + target = &win_control_panel; + } else if (explorer_strcmp(items[index].name, "About.shortcut") == 0) { + target = &win_about; + } else if (explorer_strcmp(items[index].name, "Explorer.shortcut") == 0) { + target = &win_explorer; explorer_reset(); + } else if (explorer_strcmp(items[index].name, "Recycle Bin.shortcut") == 0) { + target = &win_explorer; explorer_load_directory("/RecycleBin"); + } + + if (target) { + target->visible = true; target->focused = true; + int max_z = 0; + if (win_explorer.z_index > max_z) max_z = win_explorer.z_index; + if (win_cmd.z_index > max_z) max_z = win_cmd.z_index; + if (win_notepad.z_index > max_z) max_z = win_notepad.z_index; + if (win_calculator.z_index > max_z) max_z = win_calculator.z_index; + if (win_editor.z_index > max_z) max_z = win_editor.z_index; + if (win_markdown.z_index > max_z) max_z = win_markdown.z_index; + if (win_minesweeper.z_index > max_z) max_z = win_minesweeper.z_index; + if (win_control_panel.z_index > max_z) max_z = win_control_panel.z_index; + if (win_about.z_index > max_z) max_z = win_about.z_index; + target->z_index = max_z + 1; + return; + } + + // Generic shortcut + FAT32_FileHandle *fh = fat32_open(full_path, "r"); + if (fh) { + char buf[256]; + int len = fat32_read(fh, buf, 255); + fat32_close(fh); + if (len > 0) { + buf[len] = 0; + explorer_open_target(buf); + return; + } + } + } + + // Default open + explorer_open_target(full_path); +} + // Draw a simple file icon -static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color) { +static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color, const char *filename) { if (is_dir) { // Folder icon (colored folder) - Desktop style // Folder tab @@ -359,6 +887,23 @@ static void explorer_draw_file_icon(int x, int y, bool is_dir, uint32_t color) { draw_rect(x + 10, y + 16, 1, 15, COLOR_BLACK); draw_rect(x + 34, y + 16, 1, 15, COLOR_BLACK); draw_rect(x + 10, y + 30, 25, 1, COLOR_BLACK); + } else if (explorer_str_ends_with(filename, ".shortcut")) { + // App Shortcut - Draw specific icon + // Strip extension for check + // Draw icon at x+5, y+5 + // The draw_*_icon functions in wm.c draw at x, y + // Pass a label, but avoid text drawn by the icon function inside the explorer item + // because explorer draws its own text. Pass "" as label. + if (explorer_strcmp(filename, "Notepad.shortcut") == 0) draw_notepad_icon(x + 5, y + 5, ""); + else if (explorer_strcmp(filename, "Calculator.shortcut") == 0) draw_calculator_icon(x + 5, y + 5, ""); + else if (explorer_strcmp(filename, "Terminal.shortcut") == 0) draw_terminal_icon(x + 5, y + 5, ""); + else if (explorer_strcmp(filename, "Minesweeper.shortcut") == 0) draw_minesweeper_icon(x + 5, y + 5, ""); + else if (explorer_strcmp(filename, "Control Panel.shortcut") == 0) draw_control_panel_icon(x + 5, y + 5, ""); + else if (explorer_strcmp(filename, "About.shortcut") == 0) draw_about_icon(x + 5, y + 5, ""); + else if (explorer_strcmp(filename, "Explorer.shortcut") == 0) draw_folder_icon(x + 5, y + 5, ""); + else if (explorer_strcmp(filename, "Recycle Bin.shortcut") == 0) draw_recycle_bin_icon(x + 5, y + 5, ""); + else if (explorer_strcmp(filename, "RecycleBin") == 0) draw_recycle_bin_icon(x + 5, y + 5, ""); + else draw_icon(x + 5, y + 5, ""); } else { // Document icon - larger draw_rect(x + 12, y + 10, 20, 25, COLOR_WHITE); @@ -395,6 +940,10 @@ static void explorer_paint(Window *win) { // Draw back button (right-aligned) draw_button(win->x + win->w - 40, offset_y + 4, 30, 30, "<", false); + // Draw scroll buttons (left of dropdown) + draw_button(win->x + win->w - 160, offset_y + 4, 30, 30, "^", false); + draw_button(win->x + win->w - 125, offset_y + 4, 30, 30, "v", false); + // Draw dropdown menu if visible if (dropdown_menu_visible) { int menu_x = dropdown_btn_x; @@ -413,12 +962,19 @@ static void explorer_paint(Window *win) { // Draw file list int content_start_y = offset_y + 40; + // Clip content to window area (excluding borders and top bar) + graphics_set_clipping(win->x + 4, content_start_y, win->w - 8, win->h - 64 - 4); + for (int i = 0; i < item_count; i++) { int row = i / EXPLORER_COLS; int col = i % EXPLORER_COLS; + // Apply scrolling + if (row < explorer_scroll_row) continue; + if (row >= explorer_scroll_row + EXPLORER_ROWS) break; + int item_x = offset_x + 10 + (col * (EXPLORER_ITEM_WIDTH + EXPLORER_PADDING)); - int item_y = content_start_y + (row * (EXPLORER_ITEM_HEIGHT + EXPLORER_PADDING)); + int item_y = content_start_y + ((row - explorer_scroll_row) * (EXPLORER_ITEM_HEIGHT + EXPLORER_PADDING)); // Draw item background uint32_t bg_color = (i == selected_item) ? COLOR_BLUE : COLOR_WHITE; @@ -426,32 +982,18 @@ static void explorer_paint(Window *win) { draw_rect(item_x + 2, item_y + 2, EXPLORER_ITEM_WIDTH - 4, EXPLORER_ITEM_HEIGHT - 4, bg_color); // Draw icon (larger area) - explorer_draw_file_icon(item_x + 5, item_y + 5, items[i].is_directory, items[i].color); + explorer_draw_file_icon(item_x + 5, item_y + 5, items[i].is_directory, items[i].color, items[i].name); - // Draw name below icon with text wrapping - uint32_t text_color = (i == selected_item) ? COLOR_WHITE : COLOR_BLACK; - int name_len = explorer_strlen(items[i].name); - int text_x = item_x + 5; - int text_y = item_y + 50; - int max_name_width = EXPLORER_ITEM_WIDTH - 10; // 110 pixels available for text - int chars_per_line = max_name_width / 8; // 8 pixels per character - - // Draw wrapped filename - int line_offset = 0; - char line_buffer[25]; - for (int j = 0; j < name_len; j++) { - int pos_in_line = j % chars_per_line; - line_buffer[pos_in_line] = items[i].name[j]; - line_buffer[pos_in_line + 1] = 0; - - // Draw line when we reach end of line or end of name - if (pos_in_line == chars_per_line - 1 || j == name_len - 1) { - draw_string(text_x, text_y + (line_offset * 10), line_buffer, text_color); - line_offset++; - } + // Draw name using intelligent wrapping + const char *display_name = items[i].name; + if (explorer_strcmp(items[i].name, "RecycleBin") == 0) { + display_name = "Recycle Bin"; } + explorer_draw_icon_label(item_x, item_y, display_name); } + graphics_clear_clipping(); + // Draw dialogs if (dialog_state == DIALOG_CREATE_FILE) { int dlg_x = win->x + win->w / 2 - 150; @@ -504,49 +1046,111 @@ static void explorer_paint(Window *win) { draw_string(dlg_x + 10, dlg_y + 10, title, COLOR_BLACK); // Message - draw_string(dlg_x + 10, dlg_y + 35, "This action cannot be undone.", COLOR_BLACK); - + if (explorer_str_starts_with(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); + } 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); + } // 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); + } else if (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); + + // Title + draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_BLACK); + + // 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); + + // 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); + } else if (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); + + // Title + draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_BLACK); + + // 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); + + // 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); + } else if (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); + + // Title + draw_string(dlg_x + 10, dlg_y + 10, "File Exists", COLOR_BLACK); + + // 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); + + // 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); + } else if (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); + + draw_string(dlg_x + 10, dlg_y + 10, "Error", COLOR_RED); + draw_string(dlg_x + 10, dlg_y + 40, dialog_input, COLOR_BLACK); + + // OK Button + draw_button(dlg_x + 110, dlg_y + 70, 80, 25, "OK", false); } - // Draw file context menu if visible - if (file_context_menu_visible && file_context_menu_item >= 0) { + // Draw context menu if visible + if (file_context_menu_visible) { // Convert window-relative coordinates to screen coordinates for drawing int menu_screen_x = win->x + file_context_menu_x; int menu_screen_y = win->y + file_context_menu_y; - if (items[file_context_menu_item].is_directory) { - // Folder context menu (Color selection) - int menu_height = 25 * 5; // 5 items, 25px each - - // Draw menu background - draw_rect(menu_screen_x, menu_screen_y, FILE_CONTEXT_MENU_WIDTH, menu_height, COLOR_LTGRAY); - draw_bevel_rect(menu_screen_x, menu_screen_y, FILE_CONTEXT_MENU_WIDTH, menu_height, true); - - // Draw menu items - int item_h = 25; - draw_string(menu_screen_x + 5, menu_screen_y + 5, "Blue", COLOR_APPLE_BLUE); - draw_string(menu_screen_x + 5, menu_screen_y + item_h + 5, "Red", COLOR_RED); - draw_string(menu_screen_x + 5, menu_screen_y + item_h * 2 + 5, "Yellow", COLOR_APPLE_YELLOW); // Text might be hard to read, but requested - draw_string(menu_screen_x + 5, menu_screen_y + item_h * 3 + 5, "Green", COLOR_APPLE_GREEN); - draw_string(menu_screen_x + 5, menu_screen_y + item_h * 4 + 5, "Black", COLOR_BLACK); - } else { - // File context menu - // Draw menu background - draw_rect(menu_screen_x, menu_screen_y, FILE_CONTEXT_MENU_WIDTH, FILE_CONTEXT_MENU_HEIGHT, COLOR_LTGRAY); - draw_bevel_rect(menu_screen_x, menu_screen_y, FILE_CONTEXT_MENU_WIDTH, FILE_CONTEXT_MENU_HEIGHT, true); - - // Draw menu items - int item_height = FILE_CONTEXT_MENU_HEIGHT / FILE_CONTEXT_ITEMS; - - // Item 1: "Open with Text Editor" - draw_string(menu_screen_x + 5, menu_screen_y + 5, "Open w/ Editor", COLOR_BLACK); - - // Item 2: "Open with Markdown Viewer" (only show if file is .md) - if (explorer_is_markdown_file(items[file_context_menu_item].name)) { - draw_string(menu_screen_x + 5, menu_screen_y + item_height + 5, "Open w/ Markdown", COLOR_BLACK); + ExplorerContextItem menu_items[20]; + int count = explorer_build_context_menu(menu_items); + + int menu_height = 0; + for (int i = 0; i < count; i++) { + if (menu_items[i].action_id == 0) menu_height += 5; // Separator + else menu_height += CONTEXT_MENU_ITEM_HEIGHT; + } + + // Draw menu background + draw_rect(menu_screen_x, menu_screen_y, FILE_CONTEXT_MENU_WIDTH, menu_height, COLOR_LTGRAY); + draw_bevel_rect(menu_screen_x, menu_screen_y, FILE_CONTEXT_MENU_WIDTH, menu_height, true); + + int y_offset = 0; + for (int i = 0; i < count; i++) { + if (menu_items[i].action_id == 0) { + draw_rect(menu_screen_x + 2, menu_screen_y + y_offset + 2, FILE_CONTEXT_MENU_WIDTH - 4, 1, COLOR_DKGRAY); + y_offset += 5; + } else { + draw_string(menu_screen_x + 5, menu_screen_y + y_offset + 5, menu_items[i].label, menu_items[i].color); + y_offset += CONTEXT_MENU_ITEM_HEIGHT; } } } @@ -610,6 +1214,53 @@ static void explorer_handle_click(Window *win, int x, int y) { dialog_close(); return; } + } else if (dialog_state == DIALOG_REPLACE_CONFIRM) { + int dlg_x = win->w / 2 - 150; + int dlg_y = win->h / 2 - 60; + + if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) { + dialog_confirm_replace(); + return; + } + + if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) { + dialog_close(); + return; + } + } else if (dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) { + int dlg_x = win->w / 2 - 150; + int dlg_y = win->h / 2 - 60; + + if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) { + dialog_confirm_replace_move(); + return; + } + + if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) { + dialog_close(); + return; + } + } else if (dialog_state == DIALOG_CREATE_REPLACE_CONFIRM) { + int dlg_x = win->w / 2 - 150; + int dlg_y = win->h / 2 - 60; + + if (x >= dlg_x + 50 && x < dlg_x + 130 && y >= dlg_y + 70 && y < dlg_y + 95) { + dialog_force_create_file(); + return; + } + + if (x >= dlg_x + 170 && x < dlg_x + 250 && y >= dlg_y + 70 && y < dlg_y + 95) { + dialog_close(); + return; + } + } else if (dialog_state == DIALOG_ERROR) { + int dlg_x = win->w / 2 - 150; + int dlg_y = win->h / 2 - 60; + + if (x >= dlg_x + 110 && x < dlg_x + 190 && y >= dlg_y + 70 && y < dlg_y + 95) { + dialog_close(); + return; + } } // Handle dropdown menu clicks @@ -621,7 +1272,7 @@ static void explorer_handle_click(Window *win, int x, int y) { if (x >= dropdown_btn_x && x < dropdown_btn_x + DROPDOWN_MENU_WIDTH && y >= menu_y && y < menu_y + dropdown_menu_item_height) { dropdown_menu_toggle(); - dialog_open_create_file(); + dialog_open_create_file(current_path); return; } @@ -630,7 +1281,7 @@ static void explorer_handle_click(Window *win, int x, int y) { y >= menu_y + dropdown_menu_item_height && y < menu_y + dropdown_menu_item_height * 2) { dropdown_menu_toggle(); - dialog_open_create_folder(); + dialog_open_create_folder(current_path); return; } @@ -668,6 +1319,23 @@ static void explorer_handle_click(Window *win, int x, int y) { return; } + // Check scroll buttons + // Up: w-160 + if (x >= win->w - 160 && x < win->w - 130 && + y >= button_y && y < button_y + 30) { + if (explorer_scroll_row > 0) explorer_scroll_row--; + return; + } + + // Down: w-125 + if (x >= win->w - 125 && x < win->w - 95 && + y >= button_y && y < button_y + 30) { + int total_rows = (item_count + EXPLORER_COLS - 1) / EXPLORER_COLS; + if (total_rows == 0) total_rows = 1; + if (explorer_scroll_row < total_rows - (EXPLORER_ROWS - 1)) explorer_scroll_row++; + return; + } + // File items start at y=64 relative to window int content_start_y = 64; int offset_x = 4; @@ -676,8 +1344,12 @@ static void explorer_handle_click(Window *win, int x, int y) { int row = i / EXPLORER_COLS; int col = i % EXPLORER_COLS; + // Apply scrolling logic for hit test + if (row < explorer_scroll_row) continue; + if (row >= explorer_scroll_row + EXPLORER_ROWS) break; + int item_x = offset_x + 10 + (col * (EXPLORER_ITEM_WIDTH + EXPLORER_PADDING)); - int item_y = content_start_y + (row * (EXPLORER_ITEM_HEIGHT + EXPLORER_PADDING)); + int item_y = content_start_y + ((row - explorer_scroll_row) * (EXPLORER_ITEM_HEIGHT + EXPLORER_PADDING)); if (x >= item_x && x < item_x + EXPLORER_ITEM_WIDTH && y >= item_y && y < item_y + EXPLORER_ITEM_HEIGHT) { @@ -685,44 +1357,7 @@ static void explorer_handle_click(Window *win, int x, int y) { // Check for double-click if (last_clicked_item == i) { // Double-click detected - if (items[i].is_directory) { - explorer_navigate_to(items[i].name); - } else { - // Open file - check type - 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, items[i].name); - - // Check if markdown file - if (explorer_is_markdown_file(items[i].name)) { - // Open with markdown viewer - win_markdown.visible = true; - win_markdown.focused = true; - int max_z = 0; - if (win_explorer.z_index > max_z) max_z = win_explorer.z_index; - if (win_cmd.z_index > max_z) max_z = win_cmd.z_index; - if (win_notepad.z_index > max_z) max_z = win_notepad.z_index; - if (win_calculator.z_index > max_z) max_z = win_calculator.z_index; - if (win_editor.z_index > max_z) max_z = win_editor.z_index; - win_markdown.z_index = max_z + 1; - markdown_open_file(full_path); - } else { - // Open with text editor - win_editor.visible = true; - win_editor.focused = true; - int max_z = 0; - if (win_explorer.z_index > max_z) max_z = win_explorer.z_index; - if (win_cmd.z_index > max_z) max_z = win_cmd.z_index; - if (win_notepad.z_index > max_z) max_z = win_notepad.z_index; - if (win_calculator.z_index > max_z) max_z = win_calculator.z_index; - if (win_markdown.z_index > max_z) max_z = win_markdown.z_index; - win_editor.z_index = max_z + 1; - editor_open_file(full_path); - } - } + explorer_open_item(i); last_clicked_item = -1; } else { // Single-click - select @@ -782,6 +1417,32 @@ static void explorer_handle_key(Window *win, char c) { return; } return; + } else if (dialog_state == DIALOG_REPLACE_CONFIRM) { + if (c == 27) { // ESC + dialog_close(); + } else if (c == '\n') { // Enter + dialog_confirm_replace(); + } + return; + } else if (dialog_state == DIALOG_REPLACE_MOVE_CONFIRM) { + if (c == 27) { // ESC + dialog_close(); + } else if (c == '\n') { // Enter + dialog_confirm_replace_move(); + } + return; + } else if (dialog_state == DIALOG_CREATE_REPLACE_CONFIRM) { + if (c == 27) { // ESC + dialog_close(); + } else if (c == '\n') { // Enter + dialog_force_create_file(); + } + return; + } else if (dialog_state == DIALOG_ERROR) { + if (c == 27 || c == '\n') { + dialog_close(); + } + return; } if (c == 'q' || c == 'Q') { @@ -799,11 +1460,17 @@ static void explorer_handle_key(Window *win, char c) { if (selected_item > 0) { selected_item -= EXPLORER_COLS; if (selected_item < 0) selected_item = 0; + // Scroll if needed + int row = selected_item / EXPLORER_COLS; + if (row < explorer_scroll_row) explorer_scroll_row = row; } } else if (c == 18) { // DOWN if (selected_item < item_count - 1) { selected_item += EXPLORER_COLS; if (selected_item >= item_count) selected_item = item_count - 1; + // Scroll if needed + int row = selected_item / EXPLORER_COLS; + if (row >= explorer_scroll_row + (EXPLORER_ROWS - 1)) explorer_scroll_row = row - (EXPLORER_ROWS - 1) + 1; } } else if (c == 19) { // LEFT if (selected_item > 0) { @@ -816,7 +1483,7 @@ static void explorer_handle_key(Window *win, char c) { } else if (c == '\n') { // ENTER if (selected_item >= 0 && selected_item < item_count) { if (items[selected_item].is_directory) { - explorer_navigate_to(items[selected_item].name); + explorer_open_item(selected_item); } } } else if (c == 'd' || c == 'D') { // Delete key @@ -824,15 +1491,16 @@ static void explorer_handle_key(Window *win, char c) { dialog_open_delete_confirm(selected_item); } } else if (c == 'n' || c == 'N') { // New file - dialog_open_create_file(); + dialog_open_create_file(current_path); } else if (c == 'f' || c == 'F') { // New folder - dialog_open_create_folder(); + dialog_open_create_folder(current_path); } } // === Right-Click Handler === static void explorer_handle_right_click(Window *win, int x, int y) { + (void)win; // File items start at y=64 relative to window int content_start_y = 64; int offset_x = 4; @@ -841,8 +1509,12 @@ static void explorer_handle_right_click(Window *win, int x, int y) { int row = i / EXPLORER_COLS; int col = i % EXPLORER_COLS; + // Apply scrolling logic for hit test + if (row < explorer_scroll_row) continue; + if (row >= explorer_scroll_row + EXPLORER_ROWS) break; + int item_x = offset_x + 10 + (col * (EXPLORER_ITEM_WIDTH + EXPLORER_PADDING)); - int item_y = content_start_y + (row * (EXPLORER_ITEM_HEIGHT + EXPLORER_PADDING)); + int item_y = content_start_y + ((row - explorer_scroll_row) * (EXPLORER_ITEM_HEIGHT + EXPLORER_PADDING)); if (x >= item_x && x < item_x + EXPLORER_ITEM_WIDTH && y >= item_y && y < item_y + EXPLORER_ITEM_HEIGHT) { @@ -857,15 +1529,17 @@ static void explorer_handle_right_click(Window *win, int x, int y) { } } - // Close menu if clicking elsewhere - file_context_menu_visible = false; - file_context_menu_item = -1; + // Clicked on empty space + file_context_menu_visible = true; + file_context_menu_item = -1; // Background + file_context_menu_x = x; + file_context_menu_y = y; } static void explorer_handle_file_context_menu_click(Window *win, int x, int y) { - (void)win; // Suppress unused warning - we use absolute coordinates instead + (void)win; // Suppress unused warning - absolute coordinates used instead - if (!file_context_menu_visible || file_context_menu_item < 0) { + if (!file_context_menu_visible) { return; } @@ -873,11 +1547,11 @@ static void explorer_handle_file_context_menu_click(Window *win, int x, int y) { int relative_x = x - file_context_menu_x; int relative_y = y - file_context_menu_y; - int menu_height; - if (items[file_context_menu_item].is_directory) { - menu_height = 25 * 5; - } else { - menu_height = FILE_CONTEXT_MENU_HEIGHT; + ExplorerContextItem menu_items[20]; + int count = explorer_build_context_menu(menu_items); + int menu_height = 0; + for (int i = 0; i < count; i++) { + if (menu_items[i].action_id == 0) menu_height += 5; else menu_height += CONTEXT_MENU_ITEM_HEIGHT; } if (relative_x < 0 || relative_x > FILE_CONTEXT_MENU_WIDTH || @@ -888,69 +1562,190 @@ static void explorer_handle_file_context_menu_click(Window *win, int x, int y) { return; } - if (items[file_context_menu_item].is_directory) { - int clicked_item = relative_y / 25; + // Find clicked item + int current_y = 0; + int clicked_action = 0; + + for (int i = 0; i < count; i++) { + int h = (menu_items[i].action_id == 0) ? 5 : CONTEXT_MENU_ITEM_HEIGHT; + if (relative_y >= current_y && relative_y < current_y + h) { + if (menu_items[i].enabled && menu_items[i].action_id != 0) { + clicked_action = menu_items[i].action_id; + } + break; + } + current_y += h; + } + + if (clicked_action == 0) return; + + // Execute Action + char full_path[256]; + if (file_context_menu_item >= 0) { + explorer_strcpy(full_path, current_path); + if (full_path[explorer_strlen(full_path) - 1] != '/') explorer_strcat(full_path, "/"); + explorer_strcat(full_path, items[file_context_menu_item].name); + } + + if (clicked_action == 100) { // Open + explorer_open_item(file_context_menu_item); + } else if (clicked_action == 109) { // Open MD + explorer_open_item(file_context_menu_item); + } else if (clicked_action == 101) { // New File + if (file_context_menu_item >= 0 && items[file_context_menu_item].is_directory) { + dialog_open_create_file(full_path); + } else { + dialog_open_create_file(current_path); + } + } else if (clicked_action == 102) { // New Folder + if (file_context_menu_item >= 0 && items[file_context_menu_item].is_directory) { + dialog_open_create_folder(full_path); + } else { + dialog_open_create_folder(current_path); + } + } else if (clicked_action == 103) { // Paste + if (file_context_menu_item >= 0 && items[file_context_menu_item].is_directory) { + explorer_clipboard_paste(full_path); + } else { + explorer_clipboard_paste(current_path); + } + } else if (clicked_action == 104) { // Cut + explorer_clipboard_cut(full_path); + } else if (clicked_action == 105) { // Copy + explorer_clipboard_copy(full_path); + } else if (clicked_action == 106) { // Delete + dialog_open_delete_confirm(file_context_menu_item); + } else if (clicked_action == ACTION_RESTORE) { + explorer_restore_file(file_context_menu_item); + } else if (clicked_action == ACTION_CREATE_SHORTCUT) { + explorer_create_shortcut(full_path); + } else if (clicked_action >= 200 && clicked_action <= 204) { // Colors uint32_t new_color = items[file_context_menu_item].color; - - if (clicked_item == 0) new_color = COLOR_APPLE_BLUE; - else if (clicked_item == 1) new_color = COLOR_RED; - else if (clicked_item == 2) new_color = COLOR_APPLE_YELLOW; - else if (clicked_item == 3) new_color = COLOR_APPLE_GREEN; - else if (clicked_item == 4) new_color = COLOR_BLACK; - + if (clicked_action == 200) new_color = COLOR_APPLE_BLUE; + else if (clicked_action == 201) new_color = COLOR_RED; + else if (clicked_action == 202) new_color = COLOR_APPLE_YELLOW; + else if (clicked_action == 203) new_color = COLOR_APPLE_GREEN; + else if (clicked_action == 204) new_color = COLOR_BLACK; items[file_context_menu_item].color = new_color; - - // Save to file - 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, items[file_context_menu_item].name); explorer_set_folder_color(full_path, new_color); - } else { - int item_height = FILE_CONTEXT_MENU_HEIGHT / FILE_CONTEXT_ITEMS; - int clicked_item = relative_y / item_height; - - // Build full path - 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, items[file_context_menu_item].name); - - if (clicked_item == 0) { - // "Open with Text Editor" - win_editor.visible = true; - win_editor.focused = true; - int max_z = 0; - if (win_explorer.z_index > max_z) max_z = win_explorer.z_index; - if (win_cmd.z_index > max_z) max_z = win_cmd.z_index; - if (win_notepad.z_index > max_z) max_z = win_notepad.z_index; - if (win_calculator.z_index > max_z) max_z = win_calculator.z_index; - if (win_markdown.z_index > max_z) max_z = win_markdown.z_index; - win_editor.z_index = max_z + 1; - editor_open_file(full_path); - } else if (clicked_item == 1 && explorer_is_markdown_file(items[file_context_menu_item].name)) { - // "Open with Markdown Viewer" - win_markdown.visible = true; - win_markdown.focused = true; - int max_z = 0; - if (win_explorer.z_index > max_z) max_z = win_explorer.z_index; - if (win_cmd.z_index > max_z) max_z = win_cmd.z_index; - if (win_notepad.z_index > max_z) max_z = win_notepad.z_index; - if (win_calculator.z_index > max_z) max_z = win_calculator.z_index; - if (win_editor.z_index > max_z) max_z = win_editor.z_index; - win_markdown.z_index = max_z + 1; - markdown_open_file(full_path); - } } file_context_menu_visible = false; file_context_menu_item = -1; } +// === Drag and Drop Support === + +bool explorer_get_file_at(int screen_x, int screen_y, char *out_path, bool *is_dir) { + if (!win_explorer.visible) return false; + + // Convert screen coordinates to window relative + int rel_x = screen_x - win_explorer.x; + int rel_y = screen_y - win_explorer.y; + + // Check if inside content area + if (rel_x < 4 || rel_x > win_explorer.w - 4 || rel_y < 64 || rel_y > win_explorer.h - 4) { + return false; + } + + int content_start_y = 64; + int offset_x = 4; + + for (int i = 0; i < item_count; i++) { + int row = i / EXPLORER_COLS; + int col = i % EXPLORER_COLS; + + // Apply scrolling logic for hit test + if (row < explorer_scroll_row) continue; + if (row >= explorer_scroll_row + EXPLORER_ROWS) break; + + int item_x = offset_x + 10 + (col * (EXPLORER_ITEM_WIDTH + EXPLORER_PADDING)); + int item_y = content_start_y + ((row - explorer_scroll_row) * (EXPLORER_ITEM_HEIGHT + EXPLORER_PADDING)); + + if (rel_x >= item_x && rel_x < item_x + EXPLORER_ITEM_WIDTH && + rel_y >= item_y && rel_y < item_y + EXPLORER_ITEM_HEIGHT) { + + explorer_strcpy(out_path, current_path); + if (out_path[explorer_strlen(out_path) - 1] != '/') { + explorer_strcat(out_path, "/"); + } + explorer_strcat(out_path, items[i].name); + *is_dir = items[i].is_directory; + return true; + } + } + return false; +} + +void explorer_clear_click_state(void) { + last_clicked_item = -1; +} + +void explorer_refresh(void) { + explorer_load_directory(current_path); +} + +static void explorer_perform_move_internal(const char *source_path, const char *dest_dir) { + // 1. Extract filename + char filename[256]; + int len = explorer_strlen(source_path); + int i = len - 1; + while (i >= 0 && source_path[i] != '/') i--; + int j = 0; + for (int k = i + 1; k < len; k++) filename[j++] = source_path[k]; + filename[j] = 0; + + // 2. Build dest path + char dest_path[256]; + explorer_strcpy(dest_path, dest_dir); + if (dest_path[explorer_strlen(dest_path) - 1] != '/') { + explorer_strcat(dest_path, "/"); + } + explorer_strcat(dest_path, filename); + + // Check if source and dest are the same to prevent deletion + if (explorer_strcmp(source_path, dest_path) == 0) { + return; + } + + explorer_copy_recursive(source_path, dest_path); + + // 4. Delete source (Move operation) + explorer_delete_permanently(source_path); + + // Refresh + explorer_refresh(); +} + +void explorer_import_file_to(const char *source_path, const char *dest_dir) { + // Check for collision + char filename[256]; + int len = explorer_strlen(source_path); + int i = len - 1; + while (i >= 0 && source_path[i] != '/') i--; + int j = 0; + for (int k = i + 1; k < len; k++) filename[j++] = source_path[k]; + filename[j] = 0; + + char dest_path[256]; + explorer_strcpy(dest_path, dest_dir); + if (dest_path[explorer_strlen(dest_path) - 1] != '/') explorer_strcat(dest_path, "/"); + explorer_strcat(dest_path, filename); + + if (fat32_exists(dest_path) && explorer_strcmp(source_path, dest_path) != 0) { + explorer_strcpy(dialog_move_src, source_path); + explorer_strcpy(dialog_dest_dir, dest_dir); + dialog_state = DIALOG_REPLACE_MOVE_CONFIRM; + return; + } + + explorer_perform_move_internal(source_path, dest_dir); +} + +void explorer_import_file(const char *source_path) { + explorer_import_file_to(source_path, current_path); +} + // === Initialization === void explorer_init(void) { @@ -973,4 +1768,5 @@ void explorer_reset(void) { // Reset explorer to root directory on close/reopen explorer_load_directory("/"); win_explorer.focused = false; + explorer_scroll_row = 0; } \ No newline at end of file diff --git a/src/kernel/explorer.h b/src/kernel/explorer.h index 874905c..fea6413 100644 --- a/src/kernel/explorer.h +++ b/src/kernel/explorer.h @@ -12,5 +12,25 @@ extern Window win_markdown; void explorer_init(void); void explorer_reset(void); +void explorer_refresh(void); +void explorer_clear_click_state(void); + +// Drag and Drop support +bool explorer_get_file_at(int screen_x, int screen_y, char *out_path, bool *is_dir); +void explorer_import_file(const char *source_path); +void explorer_import_file_to(const char *source_path, const char *dest_dir); + +// Clipboard +void explorer_clipboard_copy(const char *path); +void explorer_clipboard_cut(const char *path); +void explorer_clipboard_paste(const char *dest_dir); +bool explorer_clipboard_has_content(void); + +// File Operations +bool explorer_delete_permanently(const char *path); +bool explorer_delete_recursive(const char *path); +void explorer_create_shortcut(const char *target_path); + +void explorer_open_directory(const char *path); #endif diff --git a/src/kernel/fat32.c b/src/kernel/fat32.c index 2b5c6f9..5bee13f 100644 --- a/src/kernel/fat32.c +++ b/src/kernel/fat32.c @@ -29,6 +29,7 @@ static FileEntry files[MAX_FILES]; static uint32_t next_cluster = 3; // Start after reserved clusters 0, 1, 2 static FAT32_FileHandle open_handles[MAX_OPEN_HANDLES]; static char current_dir[FAT32_MAX_PATH] = "/"; +static int desktop_file_limit = -1; // === Helper Functions === @@ -208,6 +209,34 @@ static uint32_t allocate_cluster(void) { return cluster; } +// Check desktop limit +static bool check_desktop_limit(const char *normalized_path) { + if (desktop_file_limit < 0) return true; + + // Check if path is directly in /Desktop (not subfolder) + // Path should start with /Desktop/ and have no other slashes + if (fs_strlen(normalized_path) > 9 && + normalized_path[0] == '/' && + normalized_path[1] == 'D' && normalized_path[2] == 'e' && + normalized_path[3] == 's' && normalized_path[4] == 'k' && + normalized_path[5] == 't' && normalized_path[6] == 'o' && + normalized_path[7] == 'p' && normalized_path[8] == '/') { + + // Check for subfolders + const char *p = normalized_path + 9; + while (*p) { + if (*p == '/') return true; // Subfolder, allow + p++; + } + + // Count files in /Desktop + FAT32_FileInfo info[256]; // Temp buffer + int count = fat32_list_directory("/Desktop", info, 256); + if (count >= desktop_file_limit) return false; + } + return true; +} + // === Public API === void fat32_init(void) { @@ -235,6 +264,10 @@ void fat32_init(void) { current_dir[1] = 0; } +void fat32_set_desktop_limit(int limit) { + desktop_file_limit = limit; +} + FAT32_FileHandle* fat32_open(const char *path, const char *mode) { char normalized[FAT32_MAX_PATH]; fat32_normalize_path(path, normalized); @@ -249,6 +282,10 @@ FAT32_FileHandle* fat32_open(const char *path, const char *mode) { } else if (mode[0] == 'w' || (mode[0] == 'a')) { // Write/append mode - create if not exists if (!entry) { + if (!check_desktop_limit(normalized)) { + return NULL; + } + entry = find_free_entry(); if (!entry) return NULL; @@ -355,7 +392,7 @@ int fat32_write(FAT32_FileHandle *handle, const void *buffer, int size) { int bytes_written = 0; const uint8_t *buf = (const uint8_t *)buffer; - // Check if we are at a cluster boundary from a previous write + // Check for cluster boundary from a previous write if (handle->position > 0 && (handle->position % FAT32_CLUSTER_SIZE) == 0) { uint32_t next = fat_table[handle->cluster]; if (next >= 0xFFFFFFF8) { @@ -441,6 +478,10 @@ bool fat32_mkdir(const char *path) { return false; // Already exists } + if (!check_desktop_limit(normalized)) { + return false; + } + FileEntry *entry = find_free_entry(); if (!entry) return false; diff --git a/src/kernel/fat32.h b/src/kernel/fat32.h index f4eb119..87909d6 100644 --- a/src/kernel/fat32.h +++ b/src/kernel/fat32.h @@ -83,6 +83,8 @@ typedef struct { uint32_t size; // File size uint32_t mode; // 0=read, 1=write, 2=append bool valid; // Is this handle valid? + uint32_t dir_sector; // Sector containing the directory entry + uint32_t dir_offset; // Offset within that sector } FAT32_FileHandle; // Directory Entry Info (for listing) @@ -124,4 +126,7 @@ void fat32_get_current_dir(char *buffer, int size); // Utilities void fat32_normalize_path(const char *path, char *normalized); +// Desktop Limit +void fat32_set_desktop_limit(int limit); + #endif diff --git a/src/kernel/graphics.c b/src/kernel/graphics.c index be4d413..99256ce 100644 --- a/src/kernel/graphics.c +++ b/src/kernel/graphics.c @@ -14,12 +14,16 @@ static bool g_use_pattern = false; static DirtyRect g_dirty = {0, 0, 0, 0, false}; // Double buffering - allocate a back buffer -// Max screen size: 2048x2048 @ 32bpp = 16MB, but we'll allocate for common sizes +// Max screen size: 2048x2048 @ 32bpp = 16MB, but allocate for common sizes // Using a simple approach: allocate max size buffer #define MAX_FB_WIDTH 2048 #define MAX_FB_HEIGHT 2048 static uint32_t g_back_buffer[MAX_FB_WIDTH * MAX_FB_HEIGHT] __attribute__((aligned(4096))); +// Clipping state +static int g_clip_x = 0, g_clip_y = 0, g_clip_w = 0, g_clip_h = 0; +static bool g_clip_enabled = false; + void graphics_init(struct limine_framebuffer *fb) { g_fb = fb; g_dirty.active = false; @@ -106,6 +110,13 @@ void put_pixel(int x, int y, uint32_t color) { if (!g_fb) return; if (x < 0 || x >= (int)g_fb->width || y < 0 || y >= (int)g_fb->height) return; + if (g_clip_enabled) { + if (x < g_clip_x || x >= g_clip_x + g_clip_w || + y < g_clip_y || y >= g_clip_y + g_clip_h) { + return; + } + } + // Draw to back buffer uint32_t pixel_offset = y * g_fb->width + x; g_back_buffer[pixel_offset] = color; @@ -208,3 +219,15 @@ void graphics_flip_buffer(void) { dst += g_fb->pitch; } } + +void graphics_set_clipping(int x, int y, int w, int h) { + g_clip_x = x; + g_clip_y = y; + g_clip_w = w; + g_clip_h = h; + g_clip_enabled = true; +} + +void graphics_clear_clipping(void) { + g_clip_enabled = false; +} diff --git a/src/kernel/graphics.h b/src/kernel/graphics.h index 6f34c76..7a3ec6a 100644 --- a/src/kernel/graphics.h +++ b/src/kernel/graphics.h @@ -34,4 +34,8 @@ void graphics_clear_dirty(void); void graphics_flip_buffer(void); void graphics_clear_back_buffer(uint32_t color); +// Clipping +void graphics_set_clipping(int x, int y, int w, int h); +void graphics_clear_clipping(void); + #endif diff --git a/src/kernel/idt.c b/src/kernel/idt.c index 665e3ec..c60828e 100644 --- a/src/kernel/idt.c +++ b/src/kernel/idt.c @@ -47,10 +47,6 @@ static void pic_remap(void) { outb(0x21, 0x01); io_wait(); outb(0xA1, 0x01); io_wait(); - // Restore masks (but verify we don't mask IRQ 1 and 12) - // Actually, simple OSs often just mask everything except what they want. - // Let's unmask IRQ 1 (Keyboard) and IRQ 12 (Mouse) explicitly and mask others. - // 0xFD = 1111 1101 (IRQ 1 unmasked) // 0xEF = 1110 1111 (IRQ 12 (4 on slave) unmasked) outb(0x21, 0xF9); // Unmask Keyboard (IRQ1) and Cascade (IRQ2) diff --git a/src/kernel/main.c b/src/kernel/main.c index 3e0a1a9..63fce30 100644 --- a/src/kernel/main.c +++ b/src/kernel/main.c @@ -49,9 +49,6 @@ static void hcf(void) { void kmain(void) { platform_init(); // 1. Graphics Init - if (LIMINE_BASE_REVISION_SUPPORTED == false) { - // Warning - } if (framebuffer_request.response == NULL || framebuffer_request.response->framebuffer_count < 1) { hcf(); diff --git a/src/kernel/markdown.c b/src/kernel/markdown.c index 2e48bb9..7731610 100644 --- a/src/kernel/markdown.c +++ b/src/kernel/markdown.c @@ -66,7 +66,6 @@ static int md_strncpy(char *dest, const char *src, int n) { static int md_strcmp(const char *s1, const char *s2) { (void)s1; // Suppress unused warning (void)s2; // Suppress unused warning - // Reserved for future use return 0; } @@ -74,7 +73,6 @@ static int md_strcmp(const char *s1, const char *s2) { static bool md_starts_with(const char *str, const char *pattern) { (void)str; // Suppress unused warning (void)pattern; // Suppress unused warning - // Reserved for future use return false; } diff --git a/src/kernel/memory_manager.c b/src/kernel/memory_manager.c index 998be70..5256ba0 100644 --- a/src/kernel/memory_manager.c +++ b/src/kernel/memory_manager.c @@ -289,7 +289,7 @@ MemStats memory_get_stats(void) { void memory_print_stats(void) { MemStats stats = memory_get_stats(); - // We need to use the CLI write functions - declare them as extern + // Use CLI write functions - declare as extern extern void cmd_write(const char *str); extern void cmd_write_int(int n); extern void cmd_putchar(char c); diff --git a/src/kernel/network.c b/src/kernel/network.c index 5e7fcd4..43df3e4 100644 --- a/src/kernel/network.c +++ b/src/kernel/network.c @@ -222,8 +222,6 @@ int ipv4_send_packet(const ipv4_address_t* dest_ip,uint8_t protocol,const void* } else { int ok=arp_lookup(&target_ip,&dest_mac); if(ok!=0){ - // ARP failed, maybe broadcast? Or fail? - // For now, keep existing behavior of broadcasting if ARP fails for(int i=0;i<6;i++) dest_mac.bytes[i]=0xFF; } } diff --git a/src/kernel/vm.c b/src/kernel/vm.c index 7c6d9ae..adddf04 100644 --- a/src/kernel/vm.c +++ b/src/kernel/vm.c @@ -162,7 +162,7 @@ static void vm_syscall(int id) { break; } case SYS_STRCAT: { - // Not implemented in cli_utils, skip + // Not implemented in cli_utils pop(); pop(); push(0); break; } @@ -188,8 +188,7 @@ static void vm_syscall(int id) { } // Simplified Heap (using top of memory growing down?) // For now, static allocation or mapped. - // Let's implement a dummy malloc that returns an index into memory - // Starting at 1024 (reserve first 1K for globals) + // Dummy malloc that returns an index into memory starting at 1024 case SYS_MALLOC: { int size = pop(); int res = vm_heap_ptr; @@ -317,8 +316,8 @@ static void vm_syscall(int id) { push(0); break; } - case SYS_EXEC: pop(); push(-1); break; // Not impl - case SYS_SYSTEM: pop(); push(-1); break; // Not impl + case SYS_EXEC: pop(); push(-1); break; + case SYS_SYSTEM: pop(); push(-1); break; // --- New Builtins --- case SYS_ISALNUM: { @@ -601,7 +600,6 @@ int vm_exec(const uint8_t *code, int code_size) { break; } case OP_POP: - // cmd_write("DEBUG: POP\n"); pop(); break; default: diff --git a/src/kernel/wm.c b/src/kernel/wm.c index 348563c..3a314a6 100644 --- a/src/kernel/wm.c +++ b/src/kernel/wm.c @@ -13,11 +13,26 @@ #include "control_panel.h" #include "about.h" #include "minesweeper.h" +#include "fat32.h" +#include "memory_manager.h" // --- State --- static int mx = 400, my = 300; // Mouse Pos static int prev_mx = 400, prev_my = 300; // Previous mouse position static bool start_menu_open = false; +static char *start_menu_pending_app = NULL; // For click vs drag detection +static int pending_desktop_icon_click = -1; // For desktop icon click vs drag + +// Desktop Context Menu +static bool desktop_menu_visible = false; +static int desktop_menu_x = 0; +static int desktop_menu_y = 0; +static int desktop_menu_target_icon = -1; // -1 for background + +// Message Box +static bool msg_box_visible = false; +static char msg_box_title[64]; +static char msg_box_text[64]; // Hook definition void (*wm_custom_paint_hook)(void) = NULL; @@ -28,6 +43,15 @@ static Window *drag_window = NULL; static int drag_offset_x = 0; static int drag_offset_y = 0; +// File Dragging State +static bool is_dragging_file = false; +static char drag_file_path[256]; +static int drag_icon_type = 0; // 0=File, 1=Folder, 2=App +static int drag_start_x = 0; +static int drag_start_y = 0; +static int drag_icon_orig_x = 0; +static int drag_icon_orig_y = 0; + // Windows array for z-order management static Window *all_windows[9]; static int window_count = 0; @@ -35,12 +59,277 @@ static int window_count = 0; // Redraw system static bool force_redraw = true; // Force full redraw on next tick static uint32_t timer_ticks = 0; +static int desktop_refresh_timer = 0; // Cursor state static bool cursor_visible = true; static int last_cursor_x = 400; static int last_cursor_y = 300; +// --- Desktop State --- +#define MAX_DESKTOP_ICONS 32 +typedef struct { + char name[64]; + int x, y; + int type; // 0=File, 1=Folder, 2=App + bool selected; +} DesktopIcon; + +static DesktopIcon desktop_icons[MAX_DESKTOP_ICONS]; +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; + +// Helper to check if string ends with suffix +static bool str_ends_with(const char *str, const char *suffix) { + int str_len = 0; while(str[str_len]) str_len++; + int suf_len = 0; while(suffix[suf_len]) suf_len++; + if (suf_len > str_len) return false; + + for (int i = 0; i < suf_len; i++) { + if (str[str_len - suf_len + i] != suffix[i]) return false; + } + return true; +} + +// Helper to check if string starts with prefix +static bool str_starts_with(const char *str, const char *prefix) { + while(*prefix) { + if (*prefix++ != *str++) return false; + } + return true; +} + +static int str_eq(const char *s1, const char *s2) { + while (*s1 && (*s1 == *s2)) { s1++; s2++; } + return *(const unsigned char*)s1 - *(const unsigned char*)s2; +} + +static void refresh_desktop_icons(void) { + // Update limit in FS + fat32_set_desktop_limit(desktop_max_cols * desktop_max_rows_per_col); + + FAT32_FileInfo *files = (FAT32_FileInfo*)kmalloc(MAX_DESKTOP_ICONS * sizeof(FAT32_FileInfo)); + if (!files) return; + + int file_count = fat32_list_directory("/Desktop", files, MAX_DESKTOP_ICONS); + + // Temp array to hold new state + DesktopIcon new_icons[MAX_DESKTOP_ICONS]; + int new_count = 0; + bool file_processed[MAX_DESKTOP_ICONS]; + for(int i=0; i= MAX_DESKTOP_ICONS) break; + + DesktopIcon *dest = &new_icons[new_count]; + int k = 0; while(files[i].name[k] && k < 63) { dest->name[k] = files[i].name[k]; k++; } + dest->name[k] = 0; + + if (files[i].is_directory) dest->type = 1; + else if (str_ends_with(dest->name, ".shortcut")) dest->type = 2; + else dest->type = 0; + + dest->selected = false; + dest->x = -1; // Mark as new for layout + dest->y = -1; + + new_count++; + } + } + + desktop_icon_count = new_count; + for(int i=0; i= desktop_max_rows_per_col) { + grid_y = 0; + grid_x++; + // Skip if we reached the recycle bin column/row? + // No, just fill. If we reach max files, we stop adding. + } + } + } else { + // Place new icons in first available spot + bool occupied[16][16] = {false}; + for (int i = 0; i < desktop_icon_count; i++) { + if (desktop_icons[i].x != -1) { + int col = (desktop_icons[i].x - 20) / 80; + int row = (desktop_icons[i].y - 20) / 80; + if (col >= 0 && col < 16 && row >= 0 && row < 16) occupied[col][row] = true; + } + } + + for (int i = 0; i < desktop_icon_count; i++) { + if (desktop_icons[i].x == -1) { + int found_col = -1, found_row = -1; + for (int c = 0; c < 16; c++) { + for (int r = 0; r < desktop_max_rows_per_col; r++) { + if (!occupied[c][r]) { + found_col = c; found_row = r; + goto found; + } + } + } + found: + if (found_col != -1) { + desktop_icons[i].x = 20 + found_col * 80; + desktop_icons[i].y = 20 + found_row * 80; + occupied[found_col][found_row] = true; + } + } + } + } +} + +void wm_refresh_desktop(void) { + refresh_desktop_icons(); + force_redraw = true; +} + +static void create_desktop_shortcut(const char *app_name) { + char path[128] = "/Desktop/"; + int p = 9; + int n = 0; while(app_name[n]) path[p++] = app_name[n++]; + const char *ext = ".shortcut"; + int e = 0; while(ext[e]) path[p++] = ext[e++]; + path[p] = 0; + + FAT32_FileHandle *fh = fat32_open(path, "w"); + if (fh) fat32_close(fh); + refresh_desktop_icons(); +} + +int wm_get_desktop_icon_count(void) { + return desktop_icon_count; +} + +void wm_show_message(const char *title, const char *message) { + int i=0; while(title[i] && i<63) { msg_box_title[i] = title[i]; i++; } msg_box_title[i] = 0; + i=0; while(message[i] && i<63) { msg_box_text[i] = message[i]; i++; } msg_box_text[i] = 0; + msg_box_visible = true; + force_redraw = true; +} + +static void draw_icon_label(int x, int y, const char *label) { + char line1[10] = {0}; + char line2[10] = {0}; + int len = 0; while(label[len]) len++; + + if (len <= 8) { + int i=0; while(i split at 7) + int best_split = -1; + for (int i = 7; i >= 1; i--) { + if (label[i] == ' ' || label[i] == '.') { + best_split = i; + break; + } + } + + if (best_split != -1) { + split = best_split; + } + + // Copy line 1 + int i; + for (i = 0; i < split; i++) line1[i] = label[i]; + line1[i] = 0; + + // Copy line 2 + int start2 = split; + if (label[split] == ' ') start2++; // Skip space at start of line 2 + + int j = 0; + while (label[start2 + j] && j < 8) { + line2[j] = label[start2 + j]; + j++; + } + line2[j] = 0; + + // Truncate with .. if longer than 16 (or if line 2 overflows) + if (label[start2 + j] != 0) { + if (j > 6) { line2[6] = '.'; line2[7] = '.'; line2[8] = 0; } + else { line2[j++] = '.'; line2[j++] = '.'; line2[j] = 0; } + } + } + + // Draw Line 1 Centered in 80px cell + int l1_len = 0; while(line1[l1_len]) l1_len++; + int l1_w = l1_len * 8; + // x passed is cell left. Center is x + 40. Text start is x + 40 - w/2 + draw_string(x + (80 - l1_w)/2, y + 30, line1, COLOR_WHITE); + + // Draw Line 2 Centered + if (line2[0]) { + int l2_len = 0; while(line2[l2_len]) l2_len++; + int l2_w = l2_len * 8; + draw_string(x + (80 - l2_w)/2, y + 40, line2, COLOR_WHITE); + } +} + // --- Drawing Helpers --- // Draw a bevelled box (Win 3.1 style) @@ -71,11 +360,9 @@ void draw_button(int x, int y, int w, int h, const char *text, bool pressed) { } void draw_coffee_cup(int x, int y, int size) { - // Coffee cup icon - small retro style int cup_w = size; int cup_h = size - 2; - // Cup body (tan/cream color) draw_rect(x + 1, y + 2, cup_w - 2, cup_h - 3, COLOR_LTGRAY); // Cup outline @@ -106,50 +393,173 @@ void draw_coffee_cup(int x, int y, int size) { void draw_icon(int x, int y, const char *label) { // Simple "File" Icon - draw_rect(x + 10, y, 20, 25, COLOR_WHITE); - draw_rect(x + 10, y, 20, 1, COLOR_BLACK); - draw_rect(x + 10, y, 1, 25, COLOR_BLACK); - draw_rect(x + 30, y, 1, 25, COLOR_BLACK); - draw_rect(x + 10, y + 25, 21, 1, COLOR_BLACK); + draw_rect(x + 29, y, 20, 25, COLOR_WHITE); + draw_rect(x + 29, y, 20, 1, COLOR_BLACK); + draw_rect(x + 29, y, 1, 25, COLOR_BLACK); + draw_rect(x + 49, y, 1, 25, COLOR_BLACK); + draw_rect(x + 29, y + 25, 21, 1, COLOR_BLACK); // Label - draw_string(x, y + 30, label, COLOR_WHITE); + draw_icon_label(x, y, label); } void draw_folder_icon(int x, int y, const char *label) { // Folder icon (yellow folder) // Folder tab - draw_rect(x + 5, y, 15, 6, COLOR_LTGRAY); - draw_rect(x + 5, y, 15, 1, COLOR_BLACK); - draw_rect(x + 5, y, 1, 6, COLOR_BLACK); - draw_rect(x + 19, y, 1, 6, COLOR_BLACK); + draw_rect(x + 27, y, 15, 6, COLOR_LTGRAY); + draw_rect(x + 27, y, 15, 1, COLOR_BLACK); + draw_rect(x + 27, y, 1, 6, COLOR_BLACK); + draw_rect(x + 41, y, 1, 6, COLOR_BLACK); // Folder body - draw_rect(x + 5, y + 6, 25, 15, COLOR_APPLE_YELLOW); - draw_rect(x + 5, y + 6, 25, 1, COLOR_BLACK); - draw_rect(x + 5, y + 6, 1, 15, COLOR_BLACK); - draw_rect(x + 29, y + 6, 1, 15, COLOR_BLACK); - draw_rect(x + 5, y + 20, 25, 1, COLOR_BLACK); + draw_rect(x + 27, y + 6, 25, 15, COLOR_APPLE_YELLOW); + draw_rect(x + 27, y + 6, 25, 1, COLOR_BLACK); + draw_rect(x + 27, y + 6, 1, 15, COLOR_BLACK); + draw_rect(x + 51, y + 6, 1, 15, COLOR_BLACK); + draw_rect(x + 27, y + 20, 25, 1, COLOR_BLACK); // Label - draw_string(x, y + 30, label, COLOR_WHITE); + draw_icon_label(x, y, label); } void draw_document_icon(int x, int y, const char *label) { // Document icon (white paper with lines) - draw_rect(x + 10, y, 20, 25, COLOR_WHITE); - draw_rect(x + 10, y, 20, 1, COLOR_BLACK); - draw_rect(x + 10, y, 1, 25, COLOR_BLACK); - draw_rect(x + 30, y, 1, 25, COLOR_BLACK); - draw_rect(x + 10, y + 25, 21, 1, COLOR_BLACK); + draw_rect(x + 29, y, 20, 25, COLOR_WHITE); + draw_rect(x + 29, y, 20, 1, COLOR_BLACK); + draw_rect(x + 29, y, 1, 25, COLOR_BLACK); + draw_rect(x + 49, y, 1, 25, COLOR_BLACK); + draw_rect(x + 29, y + 25, 21, 1, COLOR_BLACK); // Lines on document - draw_rect(x + 14, y + 8, 12, 1, COLOR_BLACK); - draw_rect(x + 14, y + 12, 12, 1, COLOR_BLACK); - draw_rect(x + 14, y + 16, 12, 1, COLOR_BLACK); + draw_rect(x + 33, y + 8, 12, 1, COLOR_BLACK); + draw_rect(x + 33, y + 12, 12, 1, COLOR_BLACK); + draw_rect(x + 33, y + 16, 12, 1, COLOR_BLACK); // Label - draw_string(x, y + 30, label, COLOR_WHITE); + draw_icon_label(x, y, label); +} + +void draw_notepad_icon(int x, int y, const char *label) { + // Notepad icon (Blue notebook) + draw_rect(x + 29, y, 20, 25, COLOR_BLUE); + draw_rect(x + 29, y, 20, 1, COLOR_BLACK); + draw_rect(x + 29, y, 1, 25, COLOR_BLACK); + draw_rect(x + 49, y, 1, 25, COLOR_BLACK); + draw_rect(x + 29, y + 25, 21, 1, COLOR_BLACK); + + // White page inside + draw_rect(x + 31, y + 2, 17, 22, COLOR_WHITE); + // Lines + draw_rect(x + 33, y + 6, 13, 1, COLOR_GRAY); + draw_rect(x + 33, y + 10, 13, 1, COLOR_GRAY); + draw_rect(x + 33, y + 14, 13, 1, COLOR_GRAY); + + draw_icon_label(x, y, label); +} + +void draw_calculator_icon(int x, int y, const char *label) { + // Calculator icon + draw_rect(x + 29, y, 20, 25, COLOR_DKGRAY); + draw_rect(x + 29, y, 20, 1, COLOR_BLACK); + draw_rect(x + 29, y, 1, 25, COLOR_BLACK); + draw_rect(x + 49, y, 1, 25, COLOR_BLACK); + draw_rect(x + 29, y + 25, 21, 1, COLOR_BLACK); + + // Screen + draw_rect(x + 32, y + 3, 14, 6, COLOR_APPLE_GREEN); + + // Buttons + for(int r=0; r<3; r++) { + for(int c=0; c<3; c++) { + draw_rect(x + 32 + c*5, y + 12 + r*4, 3, 2, COLOR_WHITE); + } + } + + draw_icon_label(x, y, label); +} + +void draw_terminal_icon(int x, int y, const char *label) { + // Terminal icon + draw_rect(x + 27, y + 2, 24, 20, COLOR_BLACK); + draw_rect(x + 27, y + 2, 24, 1, COLOR_GRAY); + draw_rect(x + 27, y + 2, 1, 20, COLOR_GRAY); + draw_rect(x + 51, y + 2, 1, 20, COLOR_GRAY); + draw_rect(x + 27, y + 22, 25, 1, COLOR_GRAY); + + // Prompt + draw_rect(x + 31, y + 6, 4, 1, COLOR_APPLE_GREEN); // > + draw_rect(x + 32, y + 7, 2, 1, COLOR_APPLE_GREEN); + draw_rect(x + 31, y + 8, 4, 1, COLOR_APPLE_GREEN); + draw_rect(x + 37, y + 6, 6, 1, COLOR_APPLE_GREEN); // _ + + draw_icon_label(x, y, label); +} + +void draw_minesweeper_icon(int x, int y, const char *label) { + // Mine icon + draw_rect(x + 29, y, 20, 25, COLOR_LTGRAY); + draw_rect(x + 29, y, 20, 1, COLOR_BLACK); + draw_rect(x + 29, y, 1, 25, COLOR_BLACK); + draw_rect(x + 49, y, 1, 25, COLOR_BLACK); + draw_rect(x + 29, y + 25, 21, 1, COLOR_BLACK); + + // Mine + draw_rect(x + 36, y + 8, 6, 8, COLOR_BLACK); + draw_rect(x + 34, y + 10, 10, 4, COLOR_BLACK); + // Spikes + draw_rect(x + 39, y + 6, 1, 12, COLOR_BLACK); + draw_rect(x + 33, y + 12, 12, 1, COLOR_BLACK); + + draw_icon_label(x, y, label); +} + +void draw_control_panel_icon(int x, int y, const char *label) { + // Control Panel (Gear/Sliders) + draw_rect(x + 29, y, 20, 25, COLOR_GRAY); + draw_rect(x + 29, y, 20, 1, COLOR_BLACK); + draw_rect(x + 29, y, 1, 25, COLOR_BLACK); + draw_rect(x + 49, y, 1, 25, COLOR_BLACK); + draw_rect(x + 29, y + 25, 21, 1, COLOR_BLACK); + + // Sliders + draw_rect(x + 34, y + 5, 2, 15, COLOR_DKGRAY); + draw_rect(x + 33, y + 10, 4, 3, COLOR_WHITE); // Knob + + draw_rect(x + 42, y + 5, 2, 15, COLOR_DKGRAY); + draw_rect(x + 41, y + 16, 4, 3, COLOR_WHITE); // Knob + + draw_icon_label(x, y, label); +} + +void draw_about_icon(int x, int y, const char *label) { + // About icon (Info) + draw_rect(x + 29, y, 20, 25, COLOR_WHITE); + draw_rect(x + 29, y, 20, 1, COLOR_BLACK); + draw_rect(x + 29, y, 1, 25, COLOR_BLACK); + draw_rect(x + 49, y, 1, 25, COLOR_BLACK); + draw_rect(x + 29, y + 25, 21, 1, COLOR_BLACK); + + // 'i' + draw_rect(x + 38, y + 5, 3, 3, COLOR_BLUE); // Dot + draw_rect(x + 38, y + 10, 3, 10, COLOR_BLUE); // Body + + draw_icon_label(x, y, label); +} + +void draw_recycle_bin_icon(int x, int y, const char *label) { + // Recycle Bin (Trash can) + draw_rect(x + 29, y, 20, 25, COLOR_LTGRAY); + draw_rect(x + 29, y, 20, 1, COLOR_BLACK); + draw_rect(x + 29, y, 1, 25, COLOR_BLACK); + draw_rect(x + 49, y, 1, 25, COLOR_BLACK); + draw_rect(x + 29, y + 25, 21, 1, COLOR_BLACK); + + // Ribs + draw_rect(x + 32, y + 5, 2, 15, COLOR_DKGRAY); + draw_rect(x + 38, y + 5, 2, 15, COLOR_DKGRAY); + draw_rect(x + 44, y + 5, 2, 15, COLOR_DKGRAY); + + draw_icon_label(x, y, label); } void draw_window(Window *win) { @@ -159,7 +569,7 @@ void draw_window(Window *win) { draw_bevel_rect(win->x, win->y, win->w, win->h, false); // Title Bar - uint32_t title_color = win->focused ? COLOR_BLUE : COLOR_DKGRAY; + uint32_t title_color = win->focused ? COLOR_RED : COLOR_DKGRAY; draw_rect(win->x + 3, win->y + 3, win->w - 6, 18, title_color); draw_string(win->x + 8, win->y + 8, win->title, COLOR_WHITE); @@ -217,7 +627,7 @@ static void erase_cursor(int x, int y) { // Desktop or window area - draw teal background draw_rect(x1, y1, w, h, COLOR_TEAL); } else { - // Taskbar area - draw gray background + // Taskbar draw_rect(x1, y1, w, h, COLOR_GRAY); } } @@ -270,8 +680,32 @@ void wm_paint(void) { // 1. Desktop draw_desktop_background(); - draw_folder_icon(20, 20, "Explorer"); - draw_document_icon(20, 80, "Notepad"); + // Draw Desktop Icons + for (int i = 0; i < desktop_icon_count; i++) { + DesktopIcon *icon = &desktop_icons[i]; + if (icon->type == 1) draw_folder_icon(icon->x, icon->y, icon->name); + else if (icon->type == 2) { + // App icon - strip .app for display + char label[64]; + int len = 0; + while(icon->name[len] && len < 63) { label[len] = icon->name[len]; len++; } + label[len] = 0; + // Remove .app suffix if present + if (len > 9 && str_ends_with(label, ".shortcut")) { + label[len-9] = 0; + } + + if (str_starts_with(icon->name, "Notepad")) draw_notepad_icon(icon->x, icon->y, label); + else if (str_starts_with(icon->name, "Calculator")) draw_calculator_icon(icon->x, icon->y, label); + else if (str_starts_with(icon->name, "Terminal")) draw_terminal_icon(icon->x, icon->y, label); + else if (str_starts_with(icon->name, "Minesweeper")) draw_minesweeper_icon(icon->x, icon->y, label); + else if (str_starts_with(icon->name, "Control Panel")) draw_control_panel_icon(icon->x, icon->y, label); + else if (str_starts_with(icon->name, "About")) draw_about_icon(icon->x, icon->y, label); + else if (str_starts_with(icon->name, "Recycle Bin")) draw_recycle_bin_icon(icon->x, icon->y, label); + else if (str_starts_with(icon->name, "Explorer")) draw_folder_icon(icon->x, icon->y, label); + else draw_icon(icon->x, icon->y, label); + } else draw_document_icon(icon->x, icon->y, icon->name); + } // 3. Windows - sort by z-index and draw // Simple bubble sort by z-index (5 windows max) @@ -299,11 +733,11 @@ void wm_paint(void) { draw_rect(0, sh - 28, sw, 28, COLOR_GRAY); draw_rect(0, sh - 28, sw, 2, COLOR_WHITE); // Top highlight - // 5. Start Button with Coffee Cup Icon + // 5. Start Button draw_bevel_rect(2, sh - 26, 90, 24, start_menu_open); - // Draw coffee cup icon on the button + // Draw BrewOS logo draw_coffee_cup(5, sh - 24, 20); - // Draw BrewOS text with extra spacing on the left + // Draw BrewOS text draw_string(35, sh - 18, "BrewOS", COLOR_BLACK); // Clock @@ -333,11 +767,59 @@ void wm_paint(void) { draw_string(8, menu_y + 195, "Restart", COLOR_BLACK); } + // Desktop Context Menu + if (desktop_menu_visible) { + int menu_w = 140; + int menu_h = 100; // 4 items * 25 + + draw_rect(desktop_menu_x, desktop_menu_y, menu_w, menu_h, COLOR_LTGRAY); + draw_bevel_rect(desktop_menu_x, desktop_menu_y, menu_w, menu_h, true); + + bool can_cut_copy = (desktop_menu_target_icon != -1); + bool can_paste = explorer_clipboard_has_content(); + + // If target is a file (not folder), paste is disabled + if (desktop_menu_target_icon != -1) { + DesktopIcon *icon = &desktop_icons[desktop_menu_target_icon]; + if (icon->type != 1) can_paste = false; // 1 is folder + } + + int item_h = 25; + draw_string(desktop_menu_x + 5, desktop_menu_y + 5, "Cut", can_cut_copy ? COLOR_BLACK : COLOR_DKGRAY); + draw_string(desktop_menu_x + 5, desktop_menu_y + 5 + item_h, "Copy", can_cut_copy ? COLOR_BLACK : COLOR_DKGRAY); + draw_string(desktop_menu_x + 5, desktop_menu_y + 5 + item_h * 2, "Paste", can_paste ? COLOR_BLACK : COLOR_DKGRAY); + draw_string(desktop_menu_x + 5, desktop_menu_y + 5 + item_h * 3, "Delete", can_cut_copy ? COLOR_RED : COLOR_DKGRAY); + } + + // Message Box + if (msg_box_visible) { + int mw = 320; + int mh = 100; + int mx = (sw - mw) / 2; + int my = (sh - mh) / 2; + + draw_rect(mx, my, mw, mh, COLOR_LTGRAY); + draw_bevel_rect(mx, my, mw, mh, false); + draw_rect(mx + 3, my + 3, mw - 6, 20, COLOR_BLUE); + draw_string(mx + 8, my + 8, msg_box_title, COLOR_WHITE); + draw_string(mx + 10, my + 40, msg_box_text, COLOR_BLACK); + draw_button(mx + mw/2 - 30, my + 70, 60, 20, "OK", false); + } + // Custom Overlay (VM Graphics) if (wm_custom_paint_hook) { wm_custom_paint_hook(); } + // Draw Dragged Icon + if (is_dragging_file) { + // Extract filename for label + // Just draw a generic icon at mouse pos + if (drag_icon_type == 1) draw_folder_icon(mx - 20, my - 20, "Moving..."); + else if (drag_icon_type == 2) draw_icon(mx - 20, my - 20, "Moving..."); + else draw_document_icon(mx - 20, my - 20, "Moving..."); + } + // 7. Mouse cursor (draw last so it's on top) draw_cursor(mx, my); last_cursor_x = mx; @@ -355,127 +837,81 @@ bool rect_contains(int x, int y, int w, int h, int px, int py) { void wm_handle_click(int x, int y) { int sh = get_screen_height(); + int sw = get_screen_width(); + + if (msg_box_visible) { + int mw = 320; + int mh = 100; + int mx = (sw - mw) / 2; + int my = (sh - mh) / 2; + if (rect_contains(mx + mw/2 - 30, my + 70, 60, 20, x, y)) { + msg_box_visible = false; + force_redraw = true; + } + return; + } + + // Handle Desktop Context Menu Click + if (desktop_menu_visible) { + int menu_w = 140; + int menu_h = 100; + if (rect_contains(desktop_menu_x, desktop_menu_y, menu_w, menu_h, x, y)) { + int rel_y = y - desktop_menu_y; + int item = rel_y / 25; + + if (item == 0 && desktop_menu_target_icon != -1) { // Cut + DesktopIcon *icon = &desktop_icons[desktop_menu_target_icon]; + char path[128] = "/Desktop/"; + int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; + explorer_clipboard_cut(path); + } else if (item == 1 && desktop_menu_target_icon != -1) { // Copy + DesktopIcon *icon = &desktop_icons[desktop_menu_target_icon]; + char path[128] = "/Desktop/"; + int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; + explorer_clipboard_copy(path); + } else if (item == 2) { // Paste + bool can_paste = explorer_clipboard_has_content(); + if (desktop_menu_target_icon != -1) { + DesktopIcon *icon = &desktop_icons[desktop_menu_target_icon]; + if (icon->type != 1) can_paste = false; + } + + if (can_paste) { + if (desktop_menu_target_icon != -1 && desktop_icons[desktop_menu_target_icon].type == 1) { + // Paste into folder + char path[128] = "/Desktop/"; + DesktopIcon *icon = &desktop_icons[desktop_menu_target_icon]; + int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; + explorer_clipboard_paste(path); + } else { + // Paste to desktop + explorer_clipboard_paste("/Desktop"); + } + refresh_desktop_icons(); + } + } + else if (item == 3 && desktop_menu_target_icon != -1) { // Delete + DesktopIcon *icon = &desktop_icons[desktop_menu_target_icon]; + char path[128] = "/Desktop/"; + int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; + explorer_delete_recursive(path); + refresh_desktop_icons(); + } + } + desktop_menu_visible = false; + force_redraw = true; + return; + } // Check Start Button if (rect_contains(2, sh - 26, 90, 24, x, y)) { start_menu_open = !start_menu_open; force_redraw = true; + pending_desktop_icon_click = -1; return; } - // Check Start Menu Items - if (start_menu_open) { - int menu_h = 230; - int menu_y = sh - 28 - menu_h; - if (rect_contains(0, menu_y, 120, menu_h, x, y)) { - // Clear focus from all windows first - for (int i = 0; i < window_count; i++) { - all_windows[i]->focused = false; - } - - // Find which item was clicked - if (y < menu_y + 25) { // Explorer - win_explorer.visible = true; - win_explorer.focused = true; - explorer_reset(); - // Bring to front - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_explorer.z_index = max_z + 1; - start_menu_open = false; - } else if (y < menu_y + 45) { // Notepad - win_notepad.visible = true; - win_notepad.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_notepad.z_index = max_z + 1; - start_menu_open = false; - } else if (y < menu_y + 65) { // Editor - win_editor.visible = true; - win_editor.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_editor.z_index = max_z + 1; - start_menu_open = false; - } else if (y < menu_y + 85) { // CMD - win_cmd.visible = true; - win_cmd.focused = true; - cmd_reset(); - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_cmd.z_index = max_z + 1; - start_menu_open = false; - } else if (y < menu_y + 105) { // Calculator - win_calculator.visible = true; - win_calculator.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_calculator.z_index = max_z + 1; - start_menu_open = false; - } else if (y < menu_y + 125) { // Minesweeper - win_minesweeper.visible = true; - win_minesweeper.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_minesweeper.z_index = max_z + 1; - start_menu_open = false; - } else if (y < menu_y + 145) { // Control Panel - win_control_panel.visible = true; - win_control_panel.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_control_panel.z_index = max_z + 1; - start_menu_open = false; - } else if (y < menu_y + 165) { // About BrewOS - win_about.visible = true; - win_about.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_about.z_index = max_z + 1; - start_menu_open = false; - } else if (y < menu_y + 185) { // Shutdown - cli_cmd_shutdown(NULL); - start_menu_open = false; - } else { // Restart - cli_cmd_reboot(NULL); - start_menu_open = false; - } - force_redraw = true; - return; - } - } + // Start Menu items handled in wm_handle_mouse (on up/drag) to support dragging shortcuts. // Find topmost window at click location Window *topmost = NULL; @@ -531,65 +967,23 @@ void wm_handle_click(int x, int y) { topmost->handle_click(topmost, x - topmost->x, y - topmost->y); } } + pending_desktop_icon_click = -1; } else { // No window clicked - check desktop icons // Clear focus from all windows first - for (int i = 0; i < window_count; i++) { - all_windows[i]->focused = false; + for (int w = 0; w < window_count; w++) { + all_windows[w]->focused = false; } - if (rect_contains(20, 20, 40, 40, x, y)) { - // Explorer icon - win_explorer.visible = true; - win_explorer.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } + pending_desktop_icon_click = -1; + + for (int i = 0; i < desktop_icon_count; i++) { + DesktopIcon *icon = &desktop_icons[i]; + if (rect_contains(icon->x + 20, icon->y, 40, 40, x, y)) { + // Handle click - Defer to mouse up to allow dragging + pending_desktop_icon_click = i; + return; } - win_explorer.z_index = max_z + 1; - } else if (rect_contains(20, 80, 40, 40, x, y)) { - // Notepad icon - win_notepad.visible = true; - win_notepad.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_notepad.z_index = max_z + 1; - } else if (rect_contains(20, 140, 40, 40, x, y)) { - win_calculator.visible = true; - win_calculator.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_calculator.z_index = max_z + 1; - } else if (rect_contains(20, 200, 40, 40, x, y)) { - win_explorer.visible = true; - win_explorer.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_explorer.z_index = max_z + 1; - } else if (rect_contains(20, 260, 40, 40, x, y)) { - win_editor.visible = true; - win_editor.focused = true; - int max_z = 0; - for (int i = 0; i < window_count; i++) { - if (all_windows[i]->z_index > max_z) { - max_z = all_windows[i]->z_index; - } - } - win_editor.z_index = max_z + 1; } } @@ -603,8 +997,7 @@ void wm_handle_click(int x, int y) { // Handle right click (context menu or special actions) void wm_handle_right_click(int x, int y) { - int sh = get_screen_height(); - + desktop_menu_visible = false; // Close if open // Find topmost window at click location Window *topmost = NULL; int topmost_z = -1; @@ -628,6 +1021,20 @@ void wm_handle_right_click(int x, int y) { topmost->handle_right_click(topmost, x - topmost->x, y - topmost->y); } } + } else { + // Desktop Right Click + desktop_menu_visible = true; + desktop_menu_x = x; + desktop_menu_y = y; + desktop_menu_target_icon = -1; + + // Check if clicked on an icon + for (int i = 0; i < desktop_icon_count; i++) { + if (rect_contains(desktop_icons[i].x + 20, desktop_icons[i].y, 40, 40, x, y)) { + desktop_menu_target_icon = i; + break; + } + } } force_redraw = true; @@ -652,7 +1059,30 @@ void wm_handle_right_click(int x, int y) { bool right = buttons & 0x02; if (left && !prev_left) { - wm_handle_click(mx, my); + // Mouse Down + drag_start_x = mx; + drag_start_y = my; + // Check Start Menu for potential drag + if (start_menu_open) { + int menu_h = 230; + int menu_y = sh - 28 - menu_h; + if (rect_contains(0, menu_y, 120, menu_h, mx, my)) { + if (my < menu_y + 25) start_menu_pending_app = "Explorer"; + else if (my < menu_y + 45) start_menu_pending_app = "Notepad"; + else if (my < menu_y + 65) start_menu_pending_app = "Editor"; + else if (my < menu_y + 85) start_menu_pending_app = "Terminal"; + else if (my < menu_y + 105) start_menu_pending_app = "Calculator"; + else if (my < menu_y + 125) start_menu_pending_app = "Minesweeper"; + else if (my < menu_y + 145) start_menu_pending_app = "Control Panel"; + else if (my < menu_y + 165) start_menu_pending_app = "About"; + else if (my < menu_y + 185) start_menu_pending_app = "Shutdown"; + else start_menu_pending_app = "Restart"; + } else { + wm_handle_click(mx, my); + } + } else { + wm_handle_click(mx, my); + } } else if (right && !prev_right) { wm_handle_right_click(mx, my); } else if (left && is_dragging && drag_window) { @@ -660,13 +1090,375 @@ 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 && (dx != 0 || dy != 0)) { - // Mouse is moving while left button is held, but window isn't being dragged - force_redraw = true; - } else if (!left && is_dragging) { - is_dragging = false; - drag_window = NULL; - // Mark for full redraw since dragging ended + } else if (left && !is_dragging && !is_dragging_file && (dx != 0 || dy != 0)) { + // Check deadzone + int dist_x = mx - drag_start_x; + int dist_y = my - drag_start_y; + if (dist_x < 0) dist_x = -dist_x; + if (dist_y < 0) dist_y = -dist_y; + + if (dist_x >= 5 || dist_y >= 5) { + // Check for Start Menu Drag + if (start_menu_pending_app) { + // Start dragging app from start menu + is_dragging_file = true; + drag_icon_type = 2; + // Construct special path for app drag + char *p = drag_file_path; + const char *prefix = "::APP::"; + while(*prefix) *p++ = *prefix++; + char *n = start_menu_pending_app; + while(*n) *p++ = *n++; + *p = 0; + start_menu_pending_app = NULL; + } + + // Mouse moving with left button, check for file drag start + // 1. Check Desktop Icons + if (pending_desktop_icon_click != -1) { + int i = pending_desktop_icon_click; + 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 + drag_icon_orig_x = icon->x; + drag_icon_orig_y = icon->y; + // Construct path + char path[128] = "/Desktop/"; + int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; + int k=0; while(path[k]) { drag_file_path[k] = path[k]; k++; } drag_file_path[k]=0; + } + // 2. Check Explorer Items + if (!is_dragging_file) { + bool is_dir; + if (explorer_get_file_at(drag_start_x, drag_start_y, drag_file_path, &is_dir)) { + is_dragging_file = true; + drag_icon_type = is_dir ? 1 : 0; + explorer_clear_click_state(); + } + } + + if (is_dragging_file) force_redraw = true; + } + + } else if (!left) { + if (is_dragging) { + is_dragging = false; + drag_window = NULL; + force_redraw = true; + } + + // Handle Start Menu Click (Mouse Up without Drag) + if (start_menu_pending_app) { + // Launch App + if (str_starts_with(start_menu_pending_app, "Explorer")) { + win_explorer.visible = true; win_explorer.focused = true; explorer_reset(); + } else if (str_starts_with(start_menu_pending_app, "Notepad")) { + win_notepad.visible = true; win_notepad.focused = true; + } else if (str_starts_with(start_menu_pending_app, "Editor")) { + win_editor.visible = true; win_editor.focused = true; + } else if (str_starts_with(start_menu_pending_app, "Terminal")) { + win_cmd.visible = true; win_cmd.focused = true; cmd_reset(); + } else if (str_starts_with(start_menu_pending_app, "Calculator")) { + win_calculator.visible = true; win_calculator.focused = true; + } else if (str_starts_with(start_menu_pending_app, "Minesweeper")) { + win_minesweeper.visible = true; win_minesweeper.focused = true; + } else if (str_starts_with(start_menu_pending_app, "Control Panel")) { + win_control_panel.visible = true; win_control_panel.focused = true; + } else if (str_starts_with(start_menu_pending_app, "About")) { + win_about.visible = true; win_about.focused = true; + } else if (str_starts_with(start_menu_pending_app, "Shutdown")) { + cli_cmd_shutdown(NULL); + } else if (str_starts_with(start_menu_pending_app, "Restart")) { + cli_cmd_reboot(NULL); + } + + // Bring launched window to front + int max_z = 0; + for (int i = 0; i < window_count; i++) { + if (all_windows[i]->z_index > max_z) max_z = all_windows[i]->z_index; + } + if (win_explorer.visible && win_explorer.focused) win_explorer.z_index = max_z + 1; + if (win_notepad.visible && win_notepad.focused) win_notepad.z_index = max_z + 1; + if (win_editor.visible && win_editor.focused) win_editor.z_index = max_z + 1; + if (win_cmd.visible && win_cmd.focused) win_cmd.z_index = max_z + 1; + if (win_calculator.visible && win_calculator.focused) win_calculator.z_index = max_z + 1; + if (win_minesweeper.visible && win_minesweeper.focused) win_minesweeper.z_index = max_z + 1; + if (win_control_panel.visible && win_control_panel.focused) win_control_panel.z_index = max_z + 1; + if (win_about.visible && win_about.focused) win_about.z_index = max_z + 1; + + start_menu_open = false; + start_menu_pending_app = NULL; + force_redraw = true; + } + + // Handle Desktop Icon Click (Mouse Up) + if (pending_desktop_icon_click != -1) { + int i = pending_desktop_icon_click; + if (i < desktop_icon_count) { + DesktopIcon *icon = &desktop_icons[i]; + if (icon->type == 2) { // App Shortcut + // Check name to launch app + if (str_ends_with(icon->name, "Notepad.shortcut")) { + win_notepad.visible = true; win_notepad.focused = true; + notepad_reset(); + } else if (str_ends_with(icon->name, "Calculator.shortcut")) { + win_calculator.visible = true; win_calculator.focused = true; + } else if (str_ends_with(icon->name, "Minesweeper.shortcut")) { + win_minesweeper.visible = true; win_minesweeper.focused = true; + } else if (str_ends_with(icon->name, "Control Panel.shortcut")) { + win_control_panel.visible = true; win_control_panel.focused = true; + } else if (str_ends_with(icon->name, "Terminal.shortcut")) { + win_cmd.visible = true; win_cmd.focused = true; + } else if (str_ends_with(icon->name, "About.shortcut")) { + win_about.visible = true; win_about.focused = true; + } else if (str_ends_with(icon->name, "Explorer.shortcut")) { + win_explorer.visible = true; win_explorer.focused = true; + explorer_reset(); + } else if (str_ends_with(icon->name, "Recycle Bin.shortcut")) { + explorer_open_directory("/RecycleBin"); + } + + // 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 { + win_editor.visible = true; win_editor.focused = true; + editor_open_file(buf); + } + pending_desktop_icon_click = -1; + return; + } + } + } + // Bring to front + int max_z = 0; + for (int w = 0; w < window_count; w++) { + if (all_windows[w]->z_index > max_z) max_z = all_windows[w]->z_index; + } + if (win_notepad.visible && win_notepad.focused) win_notepad.z_index = max_z + 1; + if (win_calculator.visible && win_calculator.focused) win_calculator.z_index = max_z + 1; + if (win_minesweeper.visible && win_minesweeper.focused) win_minesweeper.z_index = max_z + 1; + if (win_control_panel.visible && win_control_panel.focused) win_control_panel.z_index = max_z + 1; + if (win_cmd.visible && win_cmd.focused) win_cmd.z_index = max_z + 1; + if (win_about.visible && win_about.focused) win_about.z_index = max_z + 1; + if (win_explorer.visible && win_explorer.focused) win_explorer.z_index = max_z + 1; + } else if (icon->type == 1) { // Folder + char path[128] = "/Desktop/"; + int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; + explorer_open_directory(path); + + int max_z = 0; + for (int w = 0; w < window_count; w++) { + if (all_windows[w]->z_index > max_z) max_z = all_windows[w]->z_index; + } + win_explorer.z_index = max_z + 1; + } else { // File + win_editor.visible = true; win_editor.focused = true; + char path[128] = "/Desktop/"; + int p=9; int n=0; while(icon->name[n]) path[p++] = icon->name[n++]; path[p]=0; + editor_open_file(path); + } + } + pending_desktop_icon_click = -1; + } + + if (is_dragging_file) { + // Drop logic + + // Check drop target + if (win_explorer.visible && rect_contains(win_explorer.x, win_explorer.y, win_explorer.w, win_explorer.h, mx, my)) { + // Dropped on Explorer + // If source was desktop, we need to refresh desktop to remove the icon + bool from_desktop = str_starts_with(drag_file_path, "/Desktop/"); + + if (drag_file_path[0] != ':') { + char target_path[256]; + bool is_dir; + // Check if dropped on a folder inside explorer + if (explorer_get_file_at(mx, my, target_path, &is_dir) && is_dir) { + explorer_import_file_to(drag_file_path, target_path); + } else { + // Dropped in current dir + explorer_import_file(drag_file_path); + } + + } + if (from_desktop) { + refresh_desktop_icons(); + } + } else { + // Dropped on Desktop (or elsewhere) + if (drag_file_path[0] == ':' && drag_file_path[1] == ':' && drag_file_path[2] == 'A') { + // Dropped Start Menu App -> Create Shortcut + create_desktop_shortcut(drag_file_path + 7); // Skip ::APP:: + } else { + // If source was NOT desktop, move to desktop + // Check if path starts with /Desktop/ + bool from_desktop = (drag_file_path[0]=='/' && drag_file_path[1]=='D' && drag_file_path[2]=='e'); + if (!from_desktop) { + // Dragged from Explorer to Desktop + // Check limit first + if (desktop_icon_count >= desktop_max_cols * desktop_max_rows_per_col) { + wm_show_message("Error", "Desktop is full!"); + } else { + explorer_import_file_to(drag_file_path, "/Desktop"); + refresh_desktop_icons(); + } + + // Handle insertion at specific position + if (desktop_auto_align && !msg_box_visible) { + // Find the newly added icon (it will be at the end) + // Extract filename from drag_file_path + char filename[64]; + int len = 0; while(drag_file_path[len]) len++; + int s = len - 1; while(s >= 0 && drag_file_path[s] != '/') s--; + s++; + int d = 0; while(drag_file_path[s] && d < 63) filename[d++] = drag_file_path[s++]; + filename[d] = 0; + + int new_idx = -1; + for(int i=0; i= desktop_icon_count) target_idx = desktop_icon_count - 1; + + // Move new_idx to target_idx + DesktopIcon temp = desktop_icons[new_idx]; + // Shift down to make space + for (int i = new_idx; i > target_idx; i--) desktop_icons[i] = desktop_icons[i-1]; + desktop_icons[target_idx] = temp; + + refresh_desktop_icons(); // Re-apply layout + } + } + } else { + // Moved within desktop + // Find which icon was dragged + int dragged_idx = -1; + for(int i=0; i (int)(cell_h * 0.2)) { + target_row++; + } + + int target_idx = target_col * desktop_max_rows_per_col + target_row; + if (target_idx >= desktop_icon_count) target_idx = desktop_icon_count - 1; + + // Shift items + DesktopIcon temp = desktop_icons[dragged_idx]; + if (target_idx > dragged_idx) { + for (int i = dragged_idx; i < target_idx; i++) desktop_icons[i] = desktop_icons[i+1]; + } else { + for (int i = dragged_idx; i > target_idx; i--) desktop_icons[i] = desktop_icons[i-1]; + } + desktop_icons[target_idx] = temp; + refresh_desktop_icons(); // Re-applies layout + } else if (!dropped_on_folder) { + desktop_icons[dragged_idx].x = mx - 20; + desktop_icons[dragged_idx].y = my - 20; + if (desktop_snap_to_grid) { + int col = (desktop_icons[dragged_idx].x - 20 + 40) / 80; + int row = (desktop_icons[dragged_idx].y - 20 + 40) / 80; + if (col < 0) col = 0; if (row < 0) row = 0; + desktop_icons[dragged_idx].x = 20 + col * 80; + desktop_icons[dragged_idx].y = 20 + row * 80; + } + + // Check for collision with other icons + // Folders already checked above, this is for overlap prevention + for (int i = 0; i < desktop_icon_count; i++) { + if (i == dragged_idx) continue; + // Simple distance check or rect overlap + int dx = desktop_icons[i].x - desktop_icons[dragged_idx].x; + int dy = desktop_icons[i].y - desktop_icons[dragged_idx].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; + break; + } + } + } + } + } + } + } + is_dragging_file = false; + force_redraw = true; + } + } + + if (is_dragging_file) { force_redraw = true; } @@ -743,6 +1535,8 @@ void wm_init(void) { about_init(); minesweeper_init(); + refresh_desktop_icons(); + // Initialize z-indices win_notepad.z_index = 0; win_cmd.z_index = 1; @@ -791,6 +1585,17 @@ void wm_init(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 + if (!is_dragging && !is_dragging_file) { + desktop_refresh_timer++; + if (desktop_refresh_timer >= 60) { + refresh_desktop_icons(); + desktop_refresh_timer = 0; + force_redraw = true; + } + } + // Only redraw if there are dirty areas (clock updates at most every second, cursor rarely moves in timer only) // Most of the time, nothing changes between ticks diff --git a/src/kernel/wm.h b/src/kernel/wm.h index d2682f1..059f1e0 100644 --- a/src/kernel/wm.h +++ b/src/kernel/wm.h @@ -51,7 +51,10 @@ void wm_process_input(void); void wm_mark_dirty(int x, int y, int w, int h); void wm_refresh(void); void wm_paint(void); +void wm_refresh_desktop(void); void wm_timer_tick(void); +int wm_get_desktop_icon_count(void); +void wm_show_message(const char *title, const char *message); // Hook for external rendering (e.g. VM overlay) extern void (*wm_custom_paint_hook)(void); @@ -59,5 +62,21 @@ extern void (*wm_custom_paint_hook)(void); // Drawing helpers void draw_bevel_rect(int x, int y, int w, int h, bool sunken); void draw_button(int x, int y, int w, int h, const char *text, bool pressed); +void draw_icon(int x, int y, const char *label); +void draw_folder_icon(int x, int y, const char *label); +void draw_document_icon(int x, int y, const char *label); +void draw_notepad_icon(int x, int y, const char *label); +void draw_calculator_icon(int x, int y, const char *label); +void draw_terminal_icon(int x, int y, const char *label); +void draw_minesweeper_icon(int x, int y, const char *label); +void draw_control_panel_icon(int x, int y, const char *label); +void draw_about_icon(int x, int y, const char *label); +void draw_recycle_bin_icon(int x, int y, const char *label); + +// Desktop Settings +extern bool desktop_snap_to_grid; +extern bool desktop_auto_align; +extern int desktop_max_rows_per_col; +extern int desktop_max_cols; #endif