Skip to content

Extended fs_driver.py with support for directories (patch attached) + fixed lv_fs.c + fixed file_explorer #398

Open
@ThomasFarstrike

Description

@ThomasFarstrike

I added support for directories to your fs_driver.py so that the file_explorer widget is able to browse the filesystem.

Here's the proposed patch:

--- ./api_drivers/py_api_drivers/fs_driver.py   2025-07-05 01:29:19.923626386 +0200
+++ ../MicroPythonOS/internal_filesystem/lib/mpos/fs_driver.py  2025-07-05 01:53:28.199800772 +0200
@@ -74,6 +74,41 @@
 
     return lv.FS_RES.OK
 
+def _fs_dir_open_cb(drv, path):
+    #print(f"_fs_dir_open_cb for path '{path}'")
+    try:
+        import os # for ilistdir()
+        return {'iterator' : os.ilistdir(path)}
+    except Exception as e:
+        print(f"_fs_dir_open_cb exception: {e}")
+        return None
+
+def _fs_dir_read_cb(drv, lv_fs_dir_t, buf, btr):
+    try:
+        iterator = lv_fs_dir_t.__cast__()['iterator']
+        nextfile = iterator.__next__()
+        #print(f"nextfile: {nextfile}")
+        filename = nextfile[0]
+        entry_type = nextfile[1]  # Type field
+        if entry_type == 0x4000:
+            #print(f"{filename} is a directory")
+            filename = f"/{filename}"
+        # Convert filename to bytes with null terminator
+        tmp_data_bytes = filename.encode() + b'\x00'
+        buf.__dereference__(btr)[0:len(tmp_data_bytes)] = tmp_data_bytes
+        return lv.FS_RES.OK
+    except StopIteration:
+        # Clear buffer and return FS_ERR when iteration ends
+        buf.__dereference__(btr)[0:1] = b'\x00'  # Empty string (null byte)
+        return lv.FS_RES.NOT_EX  # Next entry "does not exist"
+    except Exception as e:
+        print(f"_fs_dir_read_cb exception: {e}")
+        return lv.FS_RES.UNKNOWN
+
+def _fs_dir_close_cb(drv, lv_fs_dir_t):
+    #print(f"_fs_dir_close_cb called")
+    # No need to cleanup the iterator so nothing to do
+    return lv.FS_RES.OK
 
 def fs_register(fs_drv, letter, cache_size=500):
 
@@ -85,6 +120,9 @@
     fs_drv.seek_cb = _fs_seek_cb
     fs_drv.tell_cb = _fs_tell_cb
     fs_drv.close_cb = _fs_close_cb
+    fs_drv.dir_open_cb = _fs_dir_open_cb
+    fs_drv.dir_read_cb = _fs_dir_read_cb
+    #fs_drv.dir_close_cb = _fs_dir_close_cb
 
     if cache_size >= 0:
         fs_drv.cache_size = cache_size

I validated it on the unix and esp32 targets with something like this:

import fs_driver
fs_drv = lv.fs_drv_t()
fs_driver.fs_register(fs_drv, 'M')
file_explorer = lv.file_explorer(lv.screen_active())
file_explorer.explorer_open_dir('M:/')
def file_explorer_event_cb(event):
    path = file_explorer.explorer_get_current_path()
    file = file_explorer.explorer_get_selected_file_name()
    print(f"Selected: {path}{file}")
file_explorer.add_event_cb(file_explorer_event_cb, lv.EVENT.VALUE_CHANGED, None)

One major gotcha is that the current LVGL dir_read_cb's "fn" argument (an output buffer for the next filename) is defined as a char * instead of void *, which results in a UnicodeError when a C char * containing non-printable characters like 0x00 is converted to a MicroPython str by the binding. Also, str doesn't have the __dereference__() function etc...

Here's my fix for that issue:

diff --git a/src/misc/lv_fs.c b/src/misc/lv_fs.c
index f713698..9d07b45 100644
--- a/src/misc/lv_fs.c
+++ b/src/misc/lv_fs.c
@@ -341,19 +341,19 @@ lv_fs_res_t lv_fs_dir_open(lv_fs_dir_t * rddir_p, const char * path)
     return LV_FS_RES_OK;
 }
 
-lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, char * fn, uint32_t fn_len)
+lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, void * fn, uint32_t fn_len)
 {
     if(fn_len == 0) {
         return LV_FS_RES_INV_PARAM;
     }
 
     if(rddir_p->drv == NULL || rddir_p->dir_d == NULL) {
-        fn[0] = '\0';
+        *(char *)fn = 0x00;
         return LV_FS_RES_INV_PARAM;
     }
 
     if(rddir_p->drv->dir_read_cb == NULL) {
-        fn[0] = '\0';
+        *(char *)fn = 0x00;
         return LV_FS_RES_NOT_IMP;
     }
 
diff --git a/src/misc/lv_fs.h b/src/misc/lv_fs.h
index c6f938b..500e405 100644
--- a/src/misc/lv_fs.h
+++ b/src/misc/lv_fs.h
@@ -79,7 +79,7 @@ struct lv_fs_drv_t {
     lv_fs_res_t (*tell_cb)(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p);
 
     void * (*dir_open_cb)(lv_fs_drv_t * drv, const char * path);
-    lv_fs_res_t (*dir_read_cb)(lv_fs_drv_t * drv, void * rddir_p, char * fn, uint32_t fn_len);
+    lv_fs_res_t (*dir_read_cb)(lv_fs_drv_t * drv, void * rddir_p, void * fn, uint32_t fn_len);
     lv_fs_res_t (*dir_close_cb)(lv_fs_drv_t * drv, void * rddir_p);
 
     void * user_data; /**< Custom file user data*/
@@ -210,7 +210,7 @@ lv_fs_res_t lv_fs_dir_open(lv_fs_dir_t * rddir_p, const char * path);
  * @param fn_len    length of the buffer to store the filename
  * @return          LV_FS_RES_OK or any error from lv_fs_res_t enum
  */
-lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, char * fn, uint32_t fn_len);
+lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, void * fn, uint32_t fn_len);
 
 /**
  * Close the directory reading

I wanted to get a second opinion before proposing this change to upstream LVGL, so don't hold back ;-) Maybe there's a way to make it work without changing the function signature?

Another issue I encountered is that the file_explorer's navigation was buggy when entering/leaving directories repeatedly, and to fix that I cherry-picked the fix from upstream.

Anyway, the file_explorer seems to work well now, so that's going to make a nice addition to MicroPythonOS ;-) Ty!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions