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
This commit is contained in:
1 parent
140d3d6d68
commit
5628c1ddbf
78 files changed
+74877
-238
No files matched your search
File diff suppressed because it is too large.
Load diff
+4
-116
@@ -61,6 +61,7 @@ uses
|
||||
driver.video.lvgl,
|
||||
driver.storage.types,
|
||||
core.strings,
|
||||
core.search,
|
||||
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
@@ -45,7 +45,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
|
||||
@@ -64,6 +65,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();
|
||||
@@ -77,12 +82,11 @@ 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.divzero.init();
|
||||
app.filebrowser.init();
|
||||
end;
|
||||
|
||||
end.
|
||||
+182
-47
File diff suppressed because it is too large.
Load diff
+20
-12
@@ -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;
|
||||
|
||||
@@ -84,6 +84,7 @@ uses
|
||||
driver.storage.mgr,
|
||||
driver.storage.test,
|
||||
core.strings,
|
||||
core.search,
|
||||
io.syslog,
|
||||
driver.exp.testdriver,
|
||||
proc.testprocs,
|
||||
@@ -328,6 +329,7 @@ begin
|
||||
{ Run unit tests }
|
||||
boot.splash.update(65, 'Running test suite...');
|
||||
core.strings.UnitTest;
|
||||
core.search.UnitTest;
|
||||
driver.bus.usb.types.UnitTest;
|
||||
driver.bus.usb.core.UnitTest;
|
||||
boot.splash.update(70, 'Running test suite...');
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -119,10 +119,10 @@ begin
|
||||
if map^.count > max_load then begin
|
||||
NewSize:= map^.Size * 2;
|
||||
NewTable:= DPHashItem(kalloc(sizeof(PHashItem) * NewSize));
|
||||
for i:=0 to NewSize-1 do begin
|
||||
NewTable[i]:= nil;
|
||||
end;
|
||||
If NewTable <> nil then begin
|
||||
for i:=0 to NewSize-1 do begin
|
||||
NewTable[i]:= nil;
|
||||
end;
|
||||
for i:=0 to map^.size-1 do begin
|
||||
item:= map^.table[i];
|
||||
while item <> nil do begin
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -111,36 +111,6 @@ begin
|
||||
entry^.active := true;
|
||||
end;
|
||||
|
||||
{ ============================================================
|
||||
Case-insensitive substring match (no allocation)
|
||||
============================================================ }
|
||||
function toLowerC(c: char): char;
|
||||
begin
|
||||
if (c >= 'A') and (c <= 'Z') then
|
||||
toLowerC := char(ord(c) + 32)
|
||||
else
|
||||
toLowerC := c;
|
||||
end;
|
||||
|
||||
function ciContains(haystack, needle: pchar): boolean;
|
||||
var
|
||||
h, hs, ns : pchar;
|
||||
begin
|
||||
ciContains := false;
|
||||
if needle^ = #0 then begin ciContains := true; exit; end;
|
||||
h := haystack;
|
||||
while h^ <> #0 do begin
|
||||
hs := h;
|
||||
ns := needle;
|
||||
while (hs^ <> #0) and (ns^ <> #0) and (toLowerC(hs^) = toLowerC(ns^)) do begin
|
||||
inc(hs);
|
||||
inc(ns);
|
||||
end;
|
||||
if ns^ = #0 then begin ciContains := true; exit; end;
|
||||
inc(h);
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ============================================================
|
||||
Build HH:MM string from driver.timer.rtc
|
||||
============================================================ }
|
||||
@@ -569,7 +539,7 @@ begin
|
||||
fentry := PProgEntry(LL_Get(programs, i));
|
||||
if (fentry = nil) or (not fentry^.active) then continue;
|
||||
|
||||
if (query = nil) or (query^ = #0) or ciContains(fentry^.name, query) then begin
|
||||
if (query = nil) or (query^ = #0) or stringContainsCI(fentry^.name, query) then begin
|
||||
lv_label_set_text(result_lbls[vis], fentry^.name);
|
||||
result_prog[vis] := i;
|
||||
lv_obj_remove_flag(result_btns[vis], LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
@@ -558,6 +558,9 @@ var
|
||||
lv_font_montserrat_14: uint8; cvar; external;
|
||||
lv_font_fa_solid_16: uint8; cvar; external;
|
||||
hack_14: uint8; cvar; external;
|
||||
asuro_icons_32: uint8; cvar; external;
|
||||
asuro_icons_64: uint8; cvar; external;
|
||||
asuro_icons_128: uint8; cvar; external;
|
||||
|
||||
{ ============================================================
|
||||
Core Init / Tick / Timer
|
||||
@@ -1674,6 +1677,12 @@ function lvgl_get_ticks: uint32;
|
||||
function lvgl_get_kb_group: Plv_group;
|
||||
procedure lvgl_update_resolution(screen_w, screen_h: uint32);
|
||||
|
||||
{ LVGL mutex — protects all lv_* calls from concurrent access.
|
||||
Uses LOCK XCHG for future SMP safety + psAwaiting park for
|
||||
single-core efficiency. Recursive: same process can lock multiple times. }
|
||||
procedure lvgl_lock;
|
||||
procedure lvgl_unlock;
|
||||
|
||||
{ Color helper (Pascal wrapper for inline C function) }
|
||||
function lv_color_make(r, g, b: uint8): lv_color_t;
|
||||
|
||||
@@ -1683,7 +1692,7 @@ function lv_color_make(r, g, b: uint8): lv_color_t;
|
||||
implementation
|
||||
|
||||
uses
|
||||
app.uidebug;
|
||||
app.uidebug, proc.types, proc.mgr;
|
||||
|
||||
{ ============================================================
|
||||
Wrappers for LVGL inline/macro functions
|
||||
@@ -1749,6 +1758,94 @@ begin
|
||||
lv_color_make.alpha := 255;
|
||||
end;
|
||||
|
||||
{ ============================================================
|
||||
LVGL Mutex — blocking, recursive, SMP-safe
|
||||
============================================================ }
|
||||
const
|
||||
LVGL_MTX_MAX_WAITERS = 16;
|
||||
|
||||
var
|
||||
lvgl_mtx_locked : uint32 = 0;
|
||||
lvgl_mtx_owner : pointer = nil;
|
||||
lvgl_mtx_depth : uint32 = 0;
|
||||
lvgl_mtx_waiters : array[0..LVGL_MTX_MAX_WAITERS-1] of pointer;
|
||||
lvgl_mtx_nwait : uint32 = 0;
|
||||
|
||||
procedure lvgl_lock;
|
||||
var
|
||||
old : uint32;
|
||||
cur : pointer;
|
||||
begin
|
||||
cur := pointer(proc.mgr.CurrentProcess);
|
||||
|
||||
{ Recursive lock — same owner, just bump depth }
|
||||
if (lvgl_mtx_locked <> 0) and (lvgl_mtx_owner = cur) then begin
|
||||
lvgl_mtx_depth := lvgl_mtx_depth + 1;
|
||||
exit;
|
||||
end;
|
||||
|
||||
while true do begin
|
||||
{ CLI to prevent scheduler preemption during lock check + park }
|
||||
asm pushfd; cli end;
|
||||
|
||||
{ Atomic test-and-set (SMP-safe via LOCK prefix) }
|
||||
old := 1;
|
||||
asm
|
||||
mov eax, 1
|
||||
lock xchg dword ptr [lvgl_mtx_locked], eax
|
||||
mov old, eax
|
||||
end;
|
||||
|
||||
if old = 0 then begin
|
||||
{ Acquired }
|
||||
lvgl_mtx_owner := cur;
|
||||
lvgl_mtx_depth := 1;
|
||||
asm popfd end;
|
||||
exit;
|
||||
end;
|
||||
|
||||
{ Failed — park ourselves so the scheduler skips us.
|
||||
The lock holder will wake us on unlock. }
|
||||
if lvgl_mtx_nwait < LVGL_MTX_MAX_WAITERS then begin
|
||||
lvgl_mtx_waiters[lvgl_mtx_nwait] := cur;
|
||||
lvgl_mtx_nwait := lvgl_mtx_nwait + 1;
|
||||
end;
|
||||
PProcessContext(cur)^.State := psAwaiting;
|
||||
asm popfd end;
|
||||
|
||||
{ Yield — scheduler will skip us (psAwaiting).
|
||||
Resume here when the lock holder wakes us (sets psReady). }
|
||||
asm hlt end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure lvgl_unlock;
|
||||
var
|
||||
i : uint32;
|
||||
begin
|
||||
{ Recursive unlock — just decrement depth }
|
||||
if lvgl_mtx_depth > 1 then begin
|
||||
lvgl_mtx_depth := lvgl_mtx_depth - 1;
|
||||
exit;
|
||||
end;
|
||||
|
||||
asm pushfd; cli end;
|
||||
|
||||
lvgl_mtx_locked := 0;
|
||||
lvgl_mtx_owner := nil;
|
||||
lvgl_mtx_depth := 0;
|
||||
|
||||
{ Wake all waiters — they will compete for the lock }
|
||||
if lvgl_mtx_nwait > 0 then begin
|
||||
for i := 0 to lvgl_mtx_nwait - 1 do
|
||||
if lvgl_mtx_waiters[i] <> nil then
|
||||
PProcessContext(lvgl_mtx_waiters[i])^.State := psReady;
|
||||
lvgl_mtx_nwait := 0;
|
||||
end;
|
||||
|
||||
asm popfd end;
|
||||
end;
|
||||
|
||||
{ ============================================================
|
||||
Flush callback — copies LVGL render buffer to driver.video back buffer
|
||||
Handles 32bpp (direct copy) and 16bpp (ARGB8888 -> RGB565).
|
||||
|
||||
@@ -54,10 +54,12 @@ begin
|
||||
should_update := should_update or (CURRENT_TICK < LAST_UPDATE); { handle tick counter wraparound }
|
||||
if (should_update) then begin
|
||||
LAST_UPDATE := CURRENT_TICK;
|
||||
lvgl_lock;
|
||||
driver.video.windows.reapOrphanedWindows;
|
||||
driver.video.desktop.update;
|
||||
app.uidebug.update;
|
||||
lvgl_handler;
|
||||
lvgl_unlock;
|
||||
driver.video.Flush;
|
||||
end;
|
||||
if SHOULD_CRASH then begin
|
||||
|
||||
Reference in new issue
Block a user