Description
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!