Compare commits

...
8 Commits
Author SHA1 Message Date
Aaron fde535d7c7 feat(filebrowser): add delete button to toolbar, fix FAT32 delete/rename with raw sector scanning
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
- Add SYM_TRASH toolbar button wired to fb_delete_cb (was missing entirely)
- Filter '.' and '..' entries in fb_collect_cb so they never appear
- Rewrite deleteFile/deleteDir/renameFile to use raw sector scanning
  instead of getDirEntries linked-list indices (fixes  gap mismatch)
- Handle root directory correctly (use bootRecord^.rootCluster instead
  of assuming entry 0 is '.')
2026-03-11 19:28:49 +00:00
Aaron 1633a1e76c fix: FAT32 >1024 byte read/write truncation, VFS write notifications & dir watch
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
- FAT32 writeFile: replace hardcoded spc=4 with bootRecord^.spc for
  cluster allocation and per-sector write loop
- FAT32 writeFile: update directory entry byteSize on disk after write
- FAT32 readFile: use dir^.byteSize instead of cluster-based estimate,
  fix off-by-one in read loop, remove spurious +sectorSize allocation
- VFS: fire VFS_OnMutation on sync/async writes (cache invalidate + watch)
- VFS: invalidate all cache entries for volume on mutation (relPath mismatch)
- VFS: add vfsParentDir helper, store VFSDir in file descriptors
- FDTable: add VFSDir field, free on close
- File browser: enable directory watch & poll timer, two-step new file
  creation (open + write), remove io.syslog import
- Linker: add --no-warn-execstack to suppress ld exit code 1 on warning
- Add core.strings.helpers unit (getFileExtension, fmtFileSize, sort)
2026-03-11 07:39:00 +00:00
Aaron b20b88abfd refactor: rename core.stringhelpers to core.strings.helpers
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2026-03-10 19:10:19 +00:00
Aaron 80f30d8e69 refactor: rename core.search to core.stringhelpers
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2026-03-10 19:07:13 +00:00
Aaron 587177f6bb remove debug file
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2026-03-10 18:56:55 +00:00
Aaron badcf8df5e removed unused icons
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2026-03-10 18:47:50 +00:00
Aaron 76eaf6c698 fix: replace raw mouse hook with LVGL long-press for file browser context menu
Removed driver.hid.mouse dependency that caused page fault on close.
Context menu now triggers via LV_EVENT_LONG_PRESSED on content rows.
Cleaned up onClose teardown ordering and removed debug syslog markers.
2026-03-10 18:47:09 +00:00
Aaron 2fb50a5366 feat: file dispatch system, file-type icons, multi-instance notepad
- Add extension-based dispatch to driver.storage.filedispatch with typed
  union (variant record THandlerKind = hkMagic/hkExtension)
- Extract file-type icon constants and mapping to core.gfx.fileicons
- Add core.search utility (getFileExtension, fmtFileSize, sortStringArray)
- Generate PUA bitmap icon fonts (32/64/128px) from Font Awesome via
  gen_file_icons.py + compile_icons.sh toolchain
- Integrate file dispatch into file browser (double-click triggers dispatch)
- Register notepad as handler for text file extensions
- Fix init order in app.mgr (filedispatch.init before app inits)
- Refactor notepad from singleton to multi-instance (up to 8 windows)
  using instance array, findStateByWid, and LVGL user_data on msgboxes
2026-03-10 18:47:08 +00:00
36 changed files with 70870 additions and 435 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN dpkg --add-architecture i386
RUN apt-get update && apt-get install -y \
curl dos2unix wget git make nasm binutils xorriso grub-pc-bin gcc gcc-multilib \
python3 python3-pip && \
python3 python3-pip python3-pil && \
apt-get clean my room
RUN pip3 install --no-cache-dir --break-system-packages "mkdocs>=1.6,<2" mkdocs-material
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+49056
View File
File diff suppressed because it is too large Load Diff
+3456
View File
File diff suppressed because it is too large Load Diff
+12576
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -30,8 +30,8 @@
#define LV_LIMITS_INCLUDE <limits.h>
#define LV_STDARG_INCLUDE <stdarg.h>
/* Built-in memory pool: 256 KB should be plenty for our UI */
#define LV_MEM_SIZE (256 * 1024U)
/* Built-in memory pool: 1 MB for complex UI (file browser etc.) */
#define LV_MEM_SIZE (1024 * 1024U)
#define LV_MEM_POOL_EXPAND_SIZE 0
#define LV_MEM_ADR 0
File diff suppressed because it is too large Load Diff
+4 -116
View File
@@ -61,6 +61,7 @@ uses
driver.video.lvgl,
driver.storage.types,
core.strings,
core.strings.helpers,
io.syslog,
debug.tracer,
core.util, arch.x86.util,
@@ -262,119 +263,6 @@ begin
if cb <> nil then cb(sel_path, userdata);
end;
{ ============================================================
fmtFileSize — returns a kalloc'd human-readable size string.
Caller must kfree the result.
============================================================ }
function fmtFileSize(sz: uint32): pchar;
var
whole, frac : uint32;
s1, s2, s3, res : pchar;
begin
if sz < 1024 then begin
s1 := intToString(sz);
res := stringConcat(s1, ' B');
kfree(void(s1));
end else if sz < uint32(1024 * 1024) then begin
whole := sz div 1024;
frac := (sz mod 1024) * 10 div 1024;
s1 := intToString(whole);
s2 := stringConcat(s1, '.'); kfree(void(s1));
s3 := intToString(frac);
s1 := stringConcat(s2, s3); kfree(void(s2)); kfree(void(s3));
res := stringConcat(s1, ' KB'); kfree(void(s1));
end else begin
whole := sz div (1024 * 1024);
frac := (sz mod (1024 * 1024)) * 10 div (1024 * 1024);
s1 := intToString(whole);
s2 := stringConcat(s1, '.'); kfree(void(s1));
s3 := intToString(frac);
s1 := stringConcat(s2, s3); kfree(void(s2)); kfree(void(s3));
res := stringConcat(s1, ' MB'); kfree(void(s1));
end;
fmtFileSize := res;
end;
{ ============================================================
matchesFilter
Returns true when name ends with the filter extension, or
when filter is nil (show everything).
============================================================ }
function matchesFilter(name: pchar; fltr: pchar): boolean;
var
nlen, flen, i: uint32;
begin
matchesFilter := true;
if fltr = nil then exit;
nlen := stringSize(name);
flen := stringSize(fltr);
if flen = 0 then exit;
if nlen < flen then begin matchesFilter := false; exit; end;
for i := 0 to flen - 1 do
if name[nlen - flen + i] <> fltr[i] then begin
matchesFilter := false;
exit;
end;
end;
{ ============================================================
strLess — case-sensitive alphabetical comparison for sort
============================================================ }
function strLess(a, b: pchar): boolean;
var
i: uint32;
begin
strLess := false;
if (a = nil) or (b = nil) then exit;
i := 0;
while (a[i] <> #0) and (b[i] <> #0) do begin
if ord(a[i]) < ord(b[i]) then begin strLess := true; exit; end;
if ord(a[i]) > ord(b[i]) then exit;
i := i + 1;
end;
strLess := (a[i] = #0) and (b[i] <> #0);
end;
{ Simple insertion sort on a pchar array of length n }
procedure sortNames(var arr: array of pchar; n: uint32);
var
i, j : uint32;
tmp : pchar;
begin
if n < 2 then exit;
for i := 1 to n - 1 do begin
tmp := arr[i];
j := i;
while (j > 0) and strLess(tmp, arr[j - 1]) do begin
arr[j] := arr[j - 1];
j := j - 1;
end;
arr[j] := tmp;
end;
end;
{ Insertion sort on name array, swapping a parallel size array in lockstep }
procedure sortNamesWithSizes(var names: array of pchar; var sizes: array of uint32; n: uint32);
var
i, j : uint32;
tmpN : pchar;
tmpS : uint32;
begin
if n < 2 then exit;
for i := 1 to n - 1 do begin
tmpN := names[i];
tmpS := sizes[i];
j := i;
while (j > 0) and strLess(tmpN, names[j - 1]) do begin
names[j] := names[j - 1];
sizes[j] := sizes[j - 1];
j := j - 1;
end;
names[j] := tmpN;
sizes[j] := tmpS;
end;
end;
{ ============================================================
fp_collect_cb
core.ds.hashmap.forEach callback: classifies each VFS entry as dir or file
@@ -395,7 +283,7 @@ begin
ctx^.dir_count := ctx^.dir_count + 1;
end;
otFILE, otVFILE:
if matchesFilter(key, ctx^.filter) then
if stringEndsWith(key, ctx^.filter) then
if ctx^.file_count < ENTRY_MAX then begin
ctx^.file_names^[ctx^.file_count] := key;
ctx^.file_sizes^[ctx^.file_count] := obj^.FileSize;
@@ -487,8 +375,8 @@ begin
io.syslog.logln('FPCIK', 'do_refresh: collection done, sorting');
{ --- Sort both collections alphabetically --- }
if dir_count > 0 then sortNames(dir_names^, dir_count);
if file_count > 0 then sortNamesWithSizes(file_names^, file_sizes^, file_count);
if dir_count > 0 then sortStringArray(@dir_names^[0], dir_count);
if file_count > 0 then sortStringArrayWithData(@file_names^[0], @file_sizes^[0], file_count);
{ --- Build directory rows --- }
if dir_count > 0 then
+8 -4
View File
@@ -44,7 +44,8 @@ uses
core.version,
//command provider units
arch.x86.cpu,
app.diskcmd, driver.bus.usb.core, app.diskutil, app.notepad, app.partcmd, app.volcmd;
app.diskcmd, driver.bus.usb.core, app.diskutil, app.notepad, app.partcmd, app.volcmd,
app.filebrowser;
procedure init();
begin
@@ -63,6 +64,10 @@ begin
io.stdio.registerCommand('TCPHTTP', @driver.net.proto.tcp.terminal_command_tcphttp, 'Send HTTP GET to a host IP (port 80 default).');
io.stdio.registerCommand('USB', @driver.bus.usb.core.terminal_command_usb, 'driver.bus.usb subsystem information.');
{ File dispatch — must init before apps that register handlers }
driver.storage.ctl.ram.init();
driver.storage.filedispatch.init();
{ Initialize baked-in programs }
app.diskcmd.init();
app.partcmd.init();
@@ -76,11 +81,10 @@ begin
app.testcmd.init();
app.ping.init();
app.meminfo.init();
{ File dispatch & WASM integration }
driver.storage.ctl.ram.init();
driver.storage.filedispatch.init();
{ WASM & remaining apps }
app.wasm.runner.init();
app.setres.init();
app.filebrowser.init();
end;
end.
+182 -47
View File
File diff suppressed because it is too large Load Diff
+20 -12
View File
@@ -64,6 +64,7 @@ var
hist_count : uint32; { samples collected so far }
visible : boolean;
toggle_pending : uint32; { set by ISR, checked by update in gfxd context }
{ FPS tracking }
frame_count : uint32;
@@ -432,24 +433,31 @@ end;
============================================================ }
procedure update;
begin
{ Handle deferred toggle from ISR (Ctrl+D keyboard hook).
Must run here in gfxd context, NOT in ISR — LVGL is not reentrant. }
if puint32(@toggle_pending)^ <> 0 then begin
puint32(@toggle_pending)^ := 0;
if visible then begin
destroyOverlay;
visible := false;
end else begin
frame_count := 0;
last_tick := lvgl_get_ticks;
current_fps := 0;
hist_index := 0;
hist_count := 0;
createOverlay;
visible := true;
end;
end;
if visible then
refreshLabels;
end;
procedure toggle;
begin
if visible then begin
destroyOverlay;
visible := false;
end else begin
frame_count := 0;
last_tick := lvgl_get_ticks;
current_fps := 0;
hist_index := 0;
hist_count := 0;
createOverlay;
visible := true;
end;
{ Called from keyboard ISR — just set flag, actual work happens in update() }
puint32(@toggle_pending)^ := 1;
end;
function isVisible: boolean;
+2
View File
@@ -84,6 +84,7 @@ uses
driver.storage.mgr,
driver.storage.test,
core.strings,
core.strings.helpers,
io.syslog,
driver.exp.testdriver,
proc.testprocs,
@@ -327,6 +328,7 @@ begin
{ Run unit tests }
boot.splash.update(65, 'Running test suite...');
core.strings.UnitTest;
core.strings.helpers.UnitTest;
driver.bus.usb.types.UnitTest;
driver.bus.usb.core.UnitTest;
boot.splash.update(70, 'Running test suite...');

Some files were not shown because too many files have changed in this diff Show More