feat(XML/fsdrv): add frogfs and XML load from blob (#8870)

This commit is contained in:
Liam Howatt
2025-09-26 21:21:35 +02:00
committed by GitHub
parent c4cdfe7043
commit 4b6eb0d817
26 changed files with 2212 additions and 12 deletions
+4
View File
@@ -61,3 +61,7 @@ For the licenses, see the corresponding `LICENSE.txt` file in each librarys f
**AnimatedGIF (GIF decoder library)**
- Path: src/libs/gif/AnimatedGIF
- Source: https://github.com/bitbank2/AnimatedGIF
**FrogFS (read-only filesystem)**
- Path: src/libs/frogfs
- Source: https://github.com/jkent/frogfs
+7
View File
@@ -1386,6 +1386,13 @@ menu "LVGL configuration"
default 0
depends on LV_USE_FS_UEFI
config LV_USE_FS_FROGFS
bool "FrogFS read-only filesystem"
config LV_FS_FROGFS_LETTER
int "Set an upper-cased driver-identifier letter for this driver (e.g. 65 for 'A')"
default 0
depends on LV_USE_FS_FROGFS
config LV_USE_LODEPNG
bool "PNG decoder library"
+156
View File
@@ -0,0 +1,156 @@
.. _frogfs:
======
FrogFS
======
`frogfs <https://github.com/jkent/frogfs>`__
is a read-only :ref:`filesystem <file_system>` for packing a directory
tree of files into a single "blob" which can be distributed easily or
built into a firmware. It is suitable for packaging XML files into your
app. See the intro to :ref:`XML in LVGL <xml_intro>` if you are
interested in using XML.
A copy of the frogfs source is inside LVGL. It has been
reduced to a subset of the original functionality for simple loading
of blobs in LVGL.
Create a frogfs blob
********************
You can create a frogfs blob for use in LVGL by using the upstream
frogfs project. Get it from `GitHub <https://github.com/jkent/frogfs>`__
with ``git clone``.
.. code-block:: shell
git clone https://github.com/jkent/frogfs
cd frogfs
Create a ``frogfs.yaml`` file inside.
Here is the minimum required content of the file.
``files`` in this case is a directory next to ``frogfs.yaml``
that has all the files you want to include in the blob.
The directory structure will be preserved.
.. code-block:: yaml
collect:
- files/*
.. code-block:: shell
rm -rf build_dir
mkdir build_dir
python3 tools/mkfrogfs.py frogfs.yaml build_dir frogfs.bin
If it succeeds, you will have ``frogfs.bin`` which is the
filesystem blob to embed in your LVGL application. One way to get it
in your C app is to convert the binary file to a C array.
.. code-block:: shell
echo const > frogfs_bin.c
xxd -i frogfs.bin >> frogfs_bin.c
This method will create a C file with two global variables called
``frogfs_bin`` and ``frogfs_bin_len`` which can be declared in other
C files like:
.. code-block:: c
extern const unsigned char frogfs_bin[];
extern unsigned int frogfs_bin_len;
See the frogfs ``README.md`` for info about using CMake to automate
blob builds.
Usage in LVGL
*************
Set ``LV_USE_FS_FROGFS`` to ``1`` in your ``lv_conf.h``. Set
``LV_FS_FROGFS_LETTER`` to a letter like ``'F'``.
.. code-block:: c
extern const unsigned char frogfs_bin[];
lv_fs_frogfs_register_blob(frogfs_bin, "main_blob"); /* returns LV_RESULT_OK if the blob is ok */
lv_obj_t * img = lv_image_create(lv_screen_active());
lv_image_set_src(img, "F:main_blob/my_image.png"); /* the blob is specified by the path prefix */
extern const unsigned char frogfs_bin_2[];
lv_fs_frogfs_register_blob(frogfs_bin_2, "another_blob");
lv_obj_t * img2 = lv_image_create(lv_screen_active());
lv_image_set_src(img2, "F:another_blob/my_image.png");
lv_obj_delete(img);
lv_fs_frogfs_unregister_blob("main_blob"); /* optional ability to unregister blobs */
If you have a frogfs blob stored in a different :ref:`filesystem <file_system>`
accessible to LVGL, you can load the blob into ram and register it from there.
Ensure there is enough ram for the blob. Driver letter ``'A'`` in this example
could be an SD card or an OS filesystem, etc.
See the :ref:`other filesystems available in LVGL <libs_filesystem>`.
.. code-block:: c
uint32_t blob_size;
lv_fs_path_get_size("A:blobs/new_blob.bin", &blob_size);
void * blob = malloc(blob_size);
lv_fs_load_to_buf(blob, blob_size, "A:blobs/new_blob.bin");
lv_fs_frogfs_register_blob(blob, "new_blob");
Compression and Minification
****************************
The frogfs in LVGL does not have the decompression support that the upstream
frogfs has. Typically, asset formats like PNG are already compressed.
Compared to image assets, XML files have a relatively small size even when
the described UI is complex. Regardless, the XML file size can be reduced
through so-called "minification". In the simplest case, it means removing
all comments and insignificant whitespace. This is a form of compression
that does not require a decompression step. The only drawback is that the
incorrect line number relative to the un-minified version will be logged if
there is a parse failure.
See the intro to :ref:`XML in LVGL <xml_intro>`.
You can prepare a frogfs blob with minified XML files. In your ``frogfs``
clone directory make a copy of ``tools/transform-html-minifier.js`` called
``transform-xml-minifier.js`` and set the ``const options = { ... };``
to the below. Not all the original aggressive HTML minifications are standard
XML, so this is a subset that preserves standard XML syntax.
.. code-block:: javascript
const options = {
keepClosingSlash: true,
collapseWhitespace: true,
removeComments: true
};
You can then add the filter to your ``frogfs.yaml``. Minified versions of the
XML files in the ``files/`` directory in this example will be put in the blob.
.. code-block:: yaml
collect:
- files/*
filter:
'*.xml':
- xml-minifier
You can create custom filters very easily. See the frogfs ``README.md`` for
more info. Any Python or JavaScript file in
``tools`` that begins with ``transform-`` will be treated as a possible
transform which will be passed the file to minify via ``stdin`` and the
minified output is expected from ``stdout``. The ``filter-name`` part of
``transform-filter-name.py`` shall be given in your ``frogfs.yaml`` ``filter``
list for certain types or all types of files.
+1
View File
@@ -15,6 +15,7 @@
bmp
ffmpeg
freetype
frogfs
fs
gif
gstreamer
+27
View File
@@ -197,6 +197,33 @@ Registering XML files and creating instances is not memory-hungry nor slow. The
memory overhead is that the ``<view>`` of the Components is saved in RAM (typically
12 kB per component).
Load many XML files and assets
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As mentioned, :cpp:expr:`lv_xml_load_all_from_path("A:path/to/dir")`
will traverse a directory and register all the XML components,
screens, globals, and translations.
There are some additional XML loading functions available when using :ref:`frogfs`.
If you have a frogfs blob you just want to load all the XMLs and assets from,
you can directly load from the blob using :cpp:func:`lv_xml_load_all_from_data`
without registering it with :cpp:func:`lv_fs_frogfs_register_blob` first.
.. code-block:: c
extern const unsigned char frogfs_bin[];
extern unsigned int frogfs_bin_len;
lv_xml_load_t * handle = lv_xml_load_all_from_data(frogfs_bin, frogfs_bin_len);
/* `handle` can optionally be passed to `lv_xml_unload` later */
There is one more function provided for the special use case when a frogfs blob
is in another filesystem like an SD card.
.. code-block:: c
lv_xml_load_t * handle = lv_xml_load_all_from_file("A:path/to/frogfs.bin");
/* `handle` can optionally be passed to `lv_xml_unload` later */
Export C and H Files
--------------------
+5
View File
@@ -915,6 +915,11 @@
#define LV_FS_UEFI_LETTER '\0' /**< Set an upper-case driver-identifier letter for this driver (e.g. 'A'). */
#endif
#define LV_USE_FS_FROGFS 0
#if LV_USE_FS_FROGFS
#define LV_FS_FROGFS_LETTER '\0'
#endif
/** LODEPNG decoder library */
#define LV_USE_LODEPNG 0
+1
View File
@@ -38,6 +38,7 @@
--exclude=../src/core/lv_obj_style_gen.c
--exclude=../src/core/lv_obj_style_gen.h
--exclude=../src/libs/gif/AnimatedGIF
--exclude=../src/libs/frogfs
--exclude=../src/libs/lodepng/lodepng.c
--exclude=../src/libs/lodepng/lodepng.h
--exclude=../src/libs/qrcode/qrcodegen.c
+5
View File
@@ -193,6 +193,10 @@ typedef struct _lv_global_t {
lv_fs_drv_t arduino_sd_fs_drv;
#endif
#if LV_USE_FS_FROGFS
lv_fs_drv_t frogfs_fs_drv;
#endif
#if LV_USE_FREETYPE
struct _lv_freetype_context_t * ft_context;
#endif
@@ -267,6 +271,7 @@ typedef struct _lv_global_t {
#if LV_USE_XML
const char * xml_path_prefix;
uint32_t lv_event_xml_store_timeline;
lv_ll_t xml_loads;
#endif
#if LV_USE_DRAW_EVE
+362
View File
@@ -0,0 +1,362 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.
+260
View File
@@ -0,0 +1,260 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "../../../../lv_conf_internal.h"
#include LV_STDDEF_INCLUDE
#include LV_STDINT_INCLUDE
#include "frogfs_types.h"
/**
* \brief Magic number used in the frogfs file header
*/
#define FROGFS_MAGIC 0x474F5246 /** FROG */
/**
* \brief Major version this source distribution supports
*/
#define FROGFS_VER_MAJOR 1
/**
* \brief Minor version this source distribution supports
*/
#define FROGFS_VER_MINOR 0
/**
* \brief Flag for \a frogfs_open to open any file as raw. Useful to
* pass compressed data over a transport such as HTTP.
*/
#define FROGFS_OPEN_RAW (1 << 0)
/**
* \brief Enum of frogfs entry types
*/
typedef enum frogfs_entry_type_t {
FROGFS_ENTRY_TYPE_DIR,
FROGFS_ENTRY_TYPE_FILE,
} frogfs_entry_type_t;
/**
* \brief Compression algorithm ids
*/
typedef enum frogfs_comp_algo_t {
FROGFS_COMP_ALGO_NONE,
FROGFS_COMP_ALGO_ZLIB,
FROGFS_COMP_ALGO_HEATSHRINK,
FROGFS_COMP_ALGO_GZIP,
} frogfs_comp_algo_t;
/**
* \brief Configuration for the \a frogfs_init function
*/
typedef struct frogfs_config_t {
const void *addr; /**< address of an frogfs filesystem in memory */
} frogfs_config_t;
/**
* \brief A frogfs filesystem handle
*/
typedef struct frogfs_fs_t frogfs_fs_t;
/**
* \brief Structure filled by the \a frogfs_stat function
*/
typedef struct frogfs_stat_t {
frogfs_entry_type_t type; /**< entry type */
size_t size; /**< uncompressed file size */
frogfs_comp_algo_t compression; /**< compression type */
size_t compressed_sz; /**< compressed file size */
} frogfs_stat_t;
/**
* \brief Fiilesystem entry pointer
*/
typedef struct frogfs_entry_t frogfs_entry_t;
typedef struct frogfs_dh_t frogfs_dh_t;
typedef struct frogfs_fh_t frogfs_fh_t;
#if !defined(FROGFS_PRIVATE_STRUCTS)
/**
* \brief A frogfs directory handle
*/
struct frogfs_dh_t {
const frogfs_fs_t *fs; /**< filesystem handle */
frogfs_entry_t *entry; /**< directory entry */
};
/**
* \brief A frogfs file handle
*/
struct frogfs_fh_t {
const frogfs_fs_t *fs; /**< filesystem handle */
frogfs_entry_t *entry; /**< file entry */
};
#endif
/**
* \brief Initialize and return a \a frogfs_fs_t instance
* \param[in] config frogfs configuration
* \return \a frogfs_fs_t pointer or \a NULL on error
*/
frogfs_fs_t *frogfs_init(const frogfs_config_t *conf);
/**
* \brief Tear down a \a frogfs_fs_t instance
* \param[in] fs \a frogfs_fs_t pointer
*/
void frogfs_deinit(frogfs_fs_t *fs);
/**
* \brief Get frogfs entry for path
* \param[in] fs \a frogfs_fs_t pointer
* \param[in] path path string
* \return \a frogfs_entry_t pointer or \a NULL if path was not
* found
*/
const frogfs_entry_t *frogfs_get_entry(const frogfs_fs_t *fs,
const char *path);
/**
* \brief Get name for frogfs entry
* \param[in] entry \a frogfs_entry_t pointer
* \return name string, caller is expected to free
*/
char *frogfs_get_name(const frogfs_entry_t *entry);
/**
* \brief Get full path for frogfs entry
* \param[in] fs \a frogfs_fs_t pointer
* \param[in] entry \a frogfs_entry_t pointer
* \return full path string or \a NULL if entry is NULL, caller is
* expected to free
*/
char *frogfs_get_path(const frogfs_fs_t *fs, const frogfs_entry_t *entry);
/**
* \brief Return if entry is a directory
* \param[in] entry \a frogfs_entry_t pointer
* \return 1 if directory, 0 otherwise
*/
int frogfs_is_dir(const frogfs_entry_t *entry);
/**
* \brief Return if entry is a file
* \param[in] entry \a frogfs_entry_t pointer
* \return 1 if file, 0 otherwise
*/
int frogfs_is_file(const frogfs_entry_t *entry);
/**
* \brief Get information about a frogfs entry
* \param[in] fs \a frogfs_fs_t pointer
* \param[in] entry \a frogfs_entry_t pointer
* \param[out] st \a frogfs_stat_t structure
*/
void frogfs_stat(const frogfs_fs_t *fs, const frogfs_entry_t *entry,
frogfs_stat_t *st);
/**
* \brief Open a frogfs entry as a file from a \a frogfs_fs_t instance
* \param[in] fs \a frogfs_fs_t poitner
* \param[in] entry \a frogfs_entry_t pointer
* \param[in] flags open flags
* \return \a frogfs_fh_t or \a NULL if not found
*/
frogfs_fh_t *frogfs_open(const frogfs_fs_t *fs, const frogfs_entry_t *entry,
unsigned int flags);
/**
* \brief Close an open file entry
* \param[in] f \a frogfs_fh_t pointer
*/
void frogfs_close(frogfs_fh_t *fh);
/**
* \brief Determine if file handle is opened raw.
* \param[in] f \a frogfs_fh_t pointer
* \return 1 if file is open raw, 0 otherwise
*/
int frogfs_is_raw(frogfs_fh_t *fh);
/**
* \brief Read data from an open file entry
* \param[in] f \a frogfs_fh_t pointer
* \param[out] buf buffer to read into
* \param[in] len maximum number of bytes to read
* \return actual number of bytes read, zero if end of file
* reached
*/
ssize_t frogfs_read(frogfs_fh_t *fh, void *buf, size_t len);
/**
* \brief Seek to a position within an open file entry
* \param[in] f \a frogfs_fh_t pointer
* \param[in] offset file position (relative or absolute)
* \param[in] mode \a SEEK_SET, \a SEEK_CUR, or \a SEEK_END
* \return current position in file or < 0 upon error
*/
ssize_t frogfs_seek(frogfs_fh_t *fh, long offset, int mode);
/**
* \brief Get the current position in an open file entry
* \param[in] f \a frogfs_fh_t pointer
* \return current position in file or < 0 upon error
*/
size_t frogfs_tell(frogfs_fh_t *fh);
/**
* \brief Get raw memory for raw file entry
* \param[in] f \a frogfs_fh_t pointer
* \param[out] buf pointer pointer to buf
* \return length of raw data
*/
size_t frogfs_access(frogfs_fh_t *fh, const void **buf);
/**
* \brief Open a directory for reading child entrys
* \param[in] fs \a frogfs_fs_t pointer
* \param[in] entry \a frogfs_entry_t pointer to root director
* \return \a frogfs_dh_t pointer or \a NULL if invalid
*/
frogfs_dh_t *frogfs_opendir(frogfs_fs_t *fs, const frogfs_entry_t *entry);
/**
* \brief Close a directory
* \param[in] d \a frogfs_dh_t pointer
*/
void frogfs_closedir(frogfs_dh_t *dh);
/**
* \brief Get the next child entry in directory
* \param[in] d \a frogfs_dh_t pointer
* \return \a frogfs_entry_t pointer or \a NULL if end has been
* reached
*/
const frogfs_entry_t *frogfs_readdir(frogfs_dh_t *dh);
/**
* \brief Set dir entry index to a value returned by \a frogfs_telldir
* for the current \a frogfs_dh_t pointer
* \param[in] d \a frogfs_dh_t pointer
* \param[in] loc entry index
*/
void frogfs_seekdir(frogfs_dh_t *dh, long loc);
/**
* \brief Return the current entry index for a directory
* \param[in] d \a frogfs_dh_t pointer
* \return entry index
*/
long frogfs_telldir(frogfs_dh_t *dh);
#ifdef __cplusplus
} /* extern "C" */
#endif
@@ -0,0 +1,6 @@
#pragma once
#include "../../../../lv_conf_internal.h"
#include LV_STDINT_INCLUDE
typedef intptr_t ssize_t;
+77
View File
@@ -0,0 +1,77 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "../../../lv_conf_internal.h"
#include LV_STDDEF_INCLUDE
#include LV_STDINT_INCLUDE
#include "../../../stdlib/lv_string.h"
#include "../../../misc/lv_fs.h"
#include "frogfs_priv.h"
#include "frogfs_format.h"
#include "../include/frogfs/frogfs.h"
static ssize_t read_raw(frogfs_fh_t *f, void *buf, size_t len)
{
size_t remaining = f->data_sz - ((char *)f->data_ptr - (char *)f->data_start);
if (len > remaining) {
len = remaining;
}
if (buf) {
lv_memcpy(buf, f->data_ptr, len);
}
f->data_ptr = (char *)f->data_ptr + len;
return len;
}
static ssize_t seek_raw(frogfs_fh_t *f, long offset, int mode)
{
ssize_t new_pos = (char *)f->data_ptr - (char *)f->data_start;
if (mode == LV_FS_SEEK_SET) {
if (offset < 0) {
return -1;
}
if ((size_t)offset > f->data_sz) {
offset = f->data_sz;
}
new_pos = offset;
} else if (mode == LV_FS_SEEK_CUR) {
if (new_pos + offset < 0) {
new_pos = 0;
} else if ((size_t)new_pos > f->data_sz) {
new_pos = f->data_sz;
} else {
new_pos += offset;
}
} else if (mode == LV_FS_SEEK_END) {
if (offset > 0) {
return -1;
}
if (offset < -(ssize_t) f->data_sz) {
offset = 0;
}
new_pos = f->data_sz + offset;
} else {
return -1;
}
f->data_ptr = (char *)f->data_start + new_pos;
return new_pos;
}
static size_t tell_raw(frogfs_fh_t *f)
{
return (char *)f->data_ptr - (char *)f->data_start;
}
const frogfs_decomp_funcs_t frogfs_decomp_raw = {
.read = read_raw,
.seek = seek_raw,
.tell = tell_raw,
};
+449
View File
@@ -0,0 +1,449 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This is a read-only filesystem that uses a sorted hash table to locate
* entries in a monolithic binary. The binary is generated by the mkfrogfs
* tool that comes with this source distribution.
*/
#include "../../../lv_conf_internal.h"
#if LV_USE_FS_FROGFS
#include LV_INTTYPES_INCLUDE
#include LV_LIMITS_INCLUDE
#include LV_STDINT_INCLUDE
#include "../../../stdlib/lv_string.h"
#include "../../../stdlib/lv_mem.h"
#include "../../../misc/lv_log.h"
#include "../../../misc/lv_assert.h"
#include "../../../misc/lv_fs.h"
#include "frogfs_priv.h"
#include "frogfs_format.h"
#include "../include/frogfs/frogfs.h"
typedef struct frogfs_fs_t {
const frogfs_head_t *head; /**< fs header pointer */
const frogfs_hash_t *hash; /**< hash table pointer */
const frogfs_dir_t *root; /**< root directory entry */
int num_entries; /**< total number of file system entries */
} frogfs_fs_t;
// Returns the current or next highest multiple of 4.
static inline size_t align(size_t n)
{
return ((n + 4 - 1) / 4) * 4;
}
// String hashing function.
static inline uint32_t djb2_hash(const char *s)
{
unsigned long hash = 5381;
while (*s) {
/* hash = hash * 33 ^ c */
hash = ((hash << 5) + hash) ^ *s++;
}
return hash;
}
static const char *get_name(const frogfs_entry_t *entry)
{
if (FROGFS_IS_DIR(entry)) {
return (const void *) entry + 8 + (entry->u.child_count * 4);
} else if (FROGFS_IS_FILE(entry) && !FROGFS_IS_COMP(entry)) {
return (const void *) entry + 16;
} else {
return (const void *) entry + 20;
}
}
frogfs_fs_t *frogfs_init(const frogfs_config_t *conf)
{
frogfs_fs_t *fs = lv_calloc(1, sizeof(frogfs_fs_t));
if (fs == NULL) {
LV_LOG_ERROR("calloc failed");
return NULL;
}
LV_LOG_TRACE("%p", fs);
fs->head = (const void *) conf->addr;
if (fs->head == NULL) {
LV_LOG_ERROR("flash mmap not enabled and addr is NULL");
goto err_out;
}
if (fs->head->magic != FROGFS_MAGIC) {
LV_LOG_ERROR("frogfs magic not found");
goto err_out;
}
if (fs->head->ver_major != FROGFS_VER_MAJOR) {
LV_LOG_ERROR("frogfs major version mismatch. filesystem is v%d.%d and this "
"library is v%d.%d", fs->head->ver_major, fs->head->ver_minor,
FROGFS_VER_MAJOR, FROGFS_VER_MINOR);
goto err_out;
}
fs->num_entries = fs->head->num_entries;
fs->hash = (const void *) fs->head + sizeof(frogfs_head_t);
fs->root = (const void *) fs->hash + (sizeof(frogfs_hash_t) * fs->num_entries);
return fs;
err_out:
frogfs_deinit(fs);
return NULL;
}
void frogfs_deinit(frogfs_fs_t *fs)
{
LV_LOG_TRACE("%p", fs);
lv_free(fs);
}
const frogfs_entry_t *frogfs_get_entry(const frogfs_fs_t *fs, const char *path)
{
LV_ASSERT_NULL(fs);
LV_ASSERT_NULL(path);
while (*path == '/') {
path++;
}
LV_LOG_TRACE("'%s'", path);
uint32_t hash = djb2_hash(path);
LV_LOG_TRACE("hash %08"PRIx32, hash);
int first = 0;
int last = fs->num_entries - 1;
int middle;
const frogfs_hash_t *e;
while (first <= last) {
middle = first + (last - first) / 2;
e = &fs->hash[middle];
if (e->hash == hash) {
break;
} else if (e->hash < hash) {
first = middle + 1;
} else {
last = middle - 1;
}
}
if (first > last) {
LV_LOG_TRACE("no match");
return NULL;
}
/* move e to the first match */
while (middle > 0) {
e = fs->hash + middle;
if ((e - 1)->hash != hash) {
break;
}
middle--;
}
/* walk through canidates and look for a match */
const frogfs_entry_t *entry;
do {
entry = (const void *) fs->head + e->offs;
char *match = frogfs_get_path(fs, entry);
if (lv_strcmp(path, match) == 0) {
lv_free(match);
LV_LOG_TRACE("entry %d", middle);
return entry;
}
lv_free(match);
entry++;
middle++;
} while ((middle < last) && (e->hash == hash));
LV_LOG_WARN("unable to find entry");
return NULL;
}
char *frogfs_get_name(const frogfs_entry_t *entry)
{
char *name = lv_malloc(entry->seg_sz + 1);
lv_memcpy(name, get_name(entry), entry->seg_sz);
name[entry->seg_sz] = '\0';
return name;
}
char *frogfs_get_path(const frogfs_fs_t *fs, const frogfs_entry_t *entry)
{
LV_ASSERT_NULL(entry);
char *path = lv_calloc(LV_FS_MAX_PATH_LENGTH, 1);
size_t len = 0;
if (!path) {
return NULL;
}
if (entry->parent == 0) {
return path;
}
while (entry->parent != 0 && len + entry->seg_sz + 1 < LV_FS_MAX_PATH_LENGTH - 1) {
const frogfs_entry_t *parent = (const void *) fs->head + entry->parent;
if ((const void *) parent == (const void *) fs->root) {
lv_memmove(path + entry->seg_sz, path, len);
lv_memcpy(path, get_name(entry), entry->seg_sz);
len += entry->seg_sz;
break;
} else {
lv_memmove(path + entry->seg_sz + 1, path, len + 1);
path[0] = '/';
lv_memcpy(path + 1, get_name(entry), entry->seg_sz);
len += entry->seg_sz + 1;
}
entry = parent;
}
return path;
}
int frogfs_is_dir(const frogfs_entry_t *entry)
{
return FROGFS_IS_DIR(entry);
}
int frogfs_is_file(const frogfs_entry_t *entry)
{
return FROGFS_IS_FILE(entry);
}
void frogfs_stat(const frogfs_fs_t *fs, const frogfs_entry_t *entry,
frogfs_stat_t *st)
{
LV_ASSERT_NULL(fs);
LV_ASSERT_NULL(entry);
lv_memset(st, 0, sizeof(*st));
if (FROGFS_IS_DIR(entry)) {
st->type = FROGFS_ENTRY_TYPE_DIR;
} else {
st->type = FROGFS_ENTRY_TYPE_FILE;
const frogfs_file_t *file = (const void *) entry;
st->compression = entry->u.compression;
if (st->compression) {
const frogfs_comp_t *comp = (const void *) entry;
st->compressed_sz = comp->data_sz;
st->size = comp->real_sz;
} else {
st->compressed_sz = file->data_sz;
st->size = file->data_sz;
}
}
}
frogfs_fh_t *frogfs_open(const frogfs_fs_t *fs, const frogfs_entry_t *entry,
unsigned int flags)
{
LV_ASSERT_NULL(fs);
LV_ASSERT_NULL(entry);
if (FROGFS_IS_DIR(entry)) {
return NULL;
}
const frogfs_file_t *file = (const void *) entry;
frogfs_fh_t *fh = lv_calloc(1, sizeof(frogfs_fh_t));
if (fh == NULL) {
LV_LOG_ERROR("calloc failed");
goto err_out;
}
LV_LOG_TRACE("%p", fh);
fh->fs = fs;
fh->file = file;
fh->data_start = (const void *) fs->head + file->data_offs;
fh->data_ptr = fh->data_start;
fh->data_sz = file->data_sz;
fh->flags = flags;
if (entry->u.compression == 0 || flags & FROGFS_OPEN_RAW) {
fh->real_sz = file->data_sz;
fh->decomp_funcs = &frogfs_decomp_raw;
}
#if CONFIG_FROGFS_USE_MINIZ == 1
else if ((entry->u.compression == FROGFS_COMP_ALGO_GZIP) ||
(entry->u.compression == FROGFS_COMP_ALGO_ZLIB)) {
fh->real_sz = ((frogfs_comp_t *) file)->real_sz;
fh->decomp_funcs = &frogfs_decomp_miniz;
}
#endif
#if CONFIG_FROGFS_USE_ZLIB == 1
else if ((entry->u.compression == FROGFS_COMP_ALGO_GZIP) ||
(entry->u.compression == FROGFS_COMP_ALGO_ZLIB)) {
fh->real_sz = ((frogfs_comp_t *) file)->real_sz;
fh->decomp_funcs = &frogfs_decomp_zlib;
}
#endif
#if CONFIG_FROGFS_USE_HEATSHRINK == 1
else if (entry->u.compression == FROGFS_COMP_ALGO_HEATSHRINK) {
fh->real_sz = ((frogfs_comp_t *) file)->real_sz;
fh->decomp_funcs = &frogfs_decomp_heatshrink;
}
#endif
else {
LV_LOG_ERROR("unsupported compression type %d", entry->u.compression);
goto err_out;
}
if (fh->decomp_funcs->open) {
if (fh->decomp_funcs->open(fh, flags) < 0) {
LV_LOG_ERROR("decomp_funcs->fopen");
goto err_out;
}
}
return fh;
err_out:
frogfs_close(fh);
return NULL;
}
void frogfs_close(frogfs_fh_t *fh)
{
if (fh == NULL) {
/* do nothing */
return;
}
if (fh->decomp_funcs && fh->decomp_funcs->close) {
fh->decomp_funcs->close(fh);
}
LV_LOG_TRACE("%p", fh);
lv_free(fh);
}
int frogfs_is_raw(frogfs_fh_t *fh)
{
return !!(fh->flags & FROGFS_OPEN_RAW);
}
ssize_t frogfs_read(frogfs_fh_t *fh, void *buf, size_t len)
{
LV_ASSERT_NULL(fh);
if (fh->decomp_funcs->read) {
return fh->decomp_funcs->read(fh, buf, len);
}
return -1;
}
ssize_t frogfs_seek(frogfs_fh_t *fh, long offset, int mode)
{
LV_ASSERT_NULL(fh);
if (fh->decomp_funcs->seek) {
return fh->decomp_funcs->seek(fh, offset, mode);
}
return -1;
}
size_t frogfs_tell(frogfs_fh_t *fh)
{
LV_ASSERT_NULL(fh);
if (fh->decomp_funcs->tell) {
return fh->decomp_funcs->tell(fh);
}
return -1;
}
size_t frogfs_access(frogfs_fh_t *fh, const void **buf)
{
LV_ASSERT_NULL(fh);
*buf = fh->data_start;
return fh->data_sz;
}
frogfs_dh_t *frogfs_opendir(frogfs_fs_t *fs, const frogfs_entry_t *entry)
{
LV_ASSERT_NULL(fs);
if (entry != NULL && FROGFS_IS_FILE(entry)) {
return NULL;
}
frogfs_dh_t *dh = lv_calloc(1, sizeof(frogfs_dh_t));
if (dh == NULL) {
LV_LOG_ERROR("calloc failed");
return NULL;
}
dh->fs = fs;
if (entry == NULL) {
dh->dir = fs->root;
} else {
dh->dir = (const void *) entry;
}
return dh;
}
void frogfs_closedir(frogfs_dh_t *dh)
{
if (dh == NULL) {
return;
}
lv_free(dh);
}
const frogfs_entry_t *frogfs_readdir(frogfs_dh_t *dh)
{
const frogfs_entry_t *entry = NULL;
if (dh->dir == NULL) {
return NULL;
}
if (dh->index < dh->dir->entry.u.child_count) {
entry = (const void *) dh->fs->head + dh->dir->children[dh->index];
dh->index++;
}
return entry;
}
void frogfs_seekdir(frogfs_dh_t *dh, long loc)
{
LV_ASSERT_NULL(dh);
LV_ASSERT(loc >= 0);
if (loc < dh->dir->entry.u.child_count) {
dh->index = loc;
} else {
dh->index = dh->dir->entry.u.child_count;
}
}
long frogfs_telldir(frogfs_dh_t *dh)
{
LV_ASSERT_NULL(dh);
return dh->index;
}
#endif /*LV_USE_FS_FROGFS*/
+90
View File
@@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#include "../../../lv_conf_internal.h"
#include LV_STDINT_INCLUDE
/**
* \brief Is entry a directory?
*/
#define FROGFS_IS_DIR(e) (e->u.child_count < 0xFF00)
/**
* \brief Is entry a file?
*/
#define FROGFS_IS_FILE(e) (e->u.child_count >= 0xFF00)
/**
* \brief Is entry a compressed file?
*/
#define FROGFS_IS_COMP(e) (e->u.child_count > 0xFF00)
/**
* \brief Filesystem header
*/
typedef struct frogfs_head_t {
uint32_t magic; /**< filesystem magic */
uint8_t ver_major; /**< major version */
uint8_t ver_minor; /**< minor version */
uint16_t num_entries; /** entry count */
uint32_t bin_sz; /**< binary length */
} frogfs_head_t;
/**
* \brief Hash table entry
*/
typedef struct frogfs_hash_t {
uint32_t hash; /**< path hash */
uint32_t offs; /**< object offset */
} frogfs_hash_t;
/**
* \brief Entry header
*/
struct frogfs_entry_t {
uint32_t parent; /**< parent entry offset */
union {
uint16_t child_count; /**< child entry count */
uint8_t compression; /**< compression algorithm */
} u;
uint8_t seg_sz; /**< path segment size (before alignment) */
uint8_t opts; /**< compression opts */
};
/**
* \brief Directory object header
*/
typedef struct frogfs_dir_t {
const frogfs_entry_t entry;
uint32_t children[];
} frogfs_dir_t;
/**
* \brief File object header
*/
typedef struct frogfs_file_t {
const frogfs_entry_t entry;
uint32_t data_offs;
uint32_t data_sz;
} frogfs_file_t;
/**
* \brief Compressed file object header
*/
typedef struct frogfs_comp_t {
const frogfs_entry_t entry;
uint32_t data_offs;
uint32_t data_sz; /**< data size (before alignment) */
uint32_t real_sz; /**< expanded size */
} frogfs_comp_t;
/**
* \brief Filesystem footer
*/
typedef struct frogfs_foot_t {
uint32_t crc32; /**< crc32 of entire file without this field */
} frogfs_foot_t;
+71
View File
@@ -0,0 +1,71 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#include "../include/frogfs/frogfs_types.h"
#define FROGFS_PRIVATE_STRUCTS
#include "../include/frogfs/frogfs.h"
#include "frogfs_format.h"
typedef struct frogfs_decomp_funcs_t frogfs_decomp_funcs_t;
/**
* \brief Structure describing a frogfs file entry
*/
struct frogfs_fh_t {
const frogfs_fs_t *fs; /**< frogfs fs pointer */
const frogfs_file_t *file; /**< file header pointer */
const void *data_start; /**< data start pointer */
const void *data_ptr; /**< current data pointer */
size_t data_sz; /**< data size */
size_t real_sz; /**< real (expanded) size */
unsigned int flags; /** open flags */
const frogfs_decomp_funcs_t *decomp_funcs; /**< decompresor funcs */
void *decomp_priv; /**< decompressor private data */
};
/**
* \brief Structure describing a frogfs directory entry
*/
struct frogfs_dh_t {
const frogfs_fs_t *fs; /**< frogfs fs pointer */
const frogfs_dir_t *dir; /**< frogfs entry */
long index; /**< current index */
};
/**
* \brief Structure of function pointers that describe a decompressor
*/
struct frogfs_decomp_funcs_t {
int (*open)(frogfs_fh_t *f, unsigned int flags);
void (*close)(frogfs_fh_t *f);
ssize_t (*read)(frogfs_fh_t *f, void *buf, size_t len);
ssize_t (*seek)(frogfs_fh_t *f, long offset, int mode);
size_t (*tell)(frogfs_fh_t *f);
};
/**
* \brief Raw decompressor functions
*/
extern const frogfs_decomp_funcs_t frogfs_decomp_raw;
/**
* \brief Heatshrink decompressor functions
*/
extern const frogfs_decomp_funcs_t frogfs_decomp_heatshrink;
/**
* \brief Miniz decompressor functions
*/
extern const frogfs_decomp_funcs_t frogfs_decomp_miniz;
/**
* \brief Zlib decompressor functions
*/
extern const frogfs_decomp_funcs_t frogfs_decomp_zlib;
#include "../include/frogfs/frogfs.h"
+341
View File
@@ -0,0 +1,341 @@
/**
* @file lv_fs_frogfs.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../../lvgl.h"
#if LV_USE_FS_FROGFS
#include "../frogfs/include/frogfs/frogfs.h"
#include "../../core/lv_global.h"
#include "../../misc/lv_ll.h"
#if !LV_FS_IS_VALID_LETTER(LV_FS_FROGFS_LETTER)
#error "Invalid drive letter"
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
typedef struct {
lv_ll_t blob_ll;
} fs_drv_data_t;
typedef struct {
char * path_prefix;
frogfs_fs_t * blob_fs;
} blob_t;
/**********************
* STATIC PROTOTYPES
**********************/
static bool get_blob_and_entry(const char * path, blob_t ** blob_dst, const frogfs_entry_t ** entry_dst);
static void destroy_blob(blob_t * blob);
static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode);
static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p);
static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br);
static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence);
static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p);
static void * fs_dir_open(lv_fs_drv_t * drv, const char * path);
static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn, uint32_t fn_len);
static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
#define frogfs_fs_drv (&(LV_GLOBAL_DEFAULT()->frogfs_fs_drv))
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_fs_frogfs_init(void)
{
fs_drv_data_t * data = lv_malloc(sizeof(*data));
LV_ASSERT_MALLOC(data);
lv_ll_init(&data->blob_ll, sizeof(blob_t));
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
lv_fs_drv_init(fs_drv_p);
fs_drv_p->letter = LV_FS_FROGFS_LETTER;
fs_drv_p->open_cb = fs_open;
fs_drv_p->close_cb = fs_close;
fs_drv_p->read_cb = fs_read;
fs_drv_p->seek_cb = fs_seek;
fs_drv_p->tell_cb = fs_tell;
fs_drv_p->dir_close_cb = fs_dir_close;
fs_drv_p->dir_open_cb = fs_dir_open;
fs_drv_p->dir_read_cb = fs_dir_read;
fs_drv_p->user_data = data;
lv_fs_drv_register(fs_drv_p);
}
void lv_fs_frogfs_deinit(void)
{
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
fs_drv_data_t * data = fs_drv_p->user_data;
lv_ll_clear_custom(&data->blob_ll, (void (*)(void *)) destroy_blob);
lv_free(data);
}
lv_result_t lv_fs_frogfs_register_blob(const void * blob, const char * path_prefix)
{
if(path_prefix[0] == '\0') {
LV_LOG_WARN("path prefix should not be zero-length");
return LV_RESULT_INVALID;
}
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
fs_drv_data_t * data = fs_drv_p->user_data;
frogfs_config_t frogfs_config = {
.addr = blob,
};
frogfs_fs_t * blob_fs = frogfs_init(&frogfs_config);
if(blob_fs == NULL) {
LV_LOG_WARN("Could not register frogfs blob 0x%p", blob);
return LV_RESULT_INVALID;
}
blob_t * hdl = lv_ll_ins_head(&data->blob_ll);
LV_ASSERT_MALLOC(hdl);
hdl->path_prefix = lv_strdup(path_prefix);
LV_ASSERT_MALLOC(hdl->path_prefix);
hdl->blob_fs = blob_fs;
return LV_RESULT_OK;
}
void lv_fs_frogfs_unregister_blob(const char * path_prefix)
{
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
fs_drv_data_t * data = fs_drv_p->user_data;
blob_t * blob_handle;
LV_LL_READ(&data->blob_ll, blob_handle) {
if(lv_streq(path_prefix, blob_handle->path_prefix)) {
break;
}
}
if(blob_handle == NULL) {
LV_LOG_WARN("No frogfs blob with path prefix '%s' to unregister", path_prefix);
return;
}
destroy_blob(blob_handle);
}
/**********************
* STATIC FUNCTIONS
**********************/
static bool get_blob_and_entry(const char * path, blob_t ** blob_dst, const frogfs_entry_t ** entry_dst)
{
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
fs_drv_data_t * data = fs_drv_p->user_data;
blob_t * blob;
size_t path_prefix_length;
LV_LL_READ(&data->blob_ll, blob) {
path_prefix_length = lv_strlen(blob->path_prefix);
if(0 == lv_strncmp(path, blob->path_prefix, path_prefix_length)
&& (blob->path_prefix[path_prefix_length - 1] == '/'
|| blob->path_prefix[path_prefix_length - 1] == '\\'
|| path[path_prefix_length] == '\0'
|| path[path_prefix_length] == '/'
|| path[path_prefix_length] == '\\')) {
break;
}
}
if(blob == NULL) {
LV_LOG_WARN("Path '%s' does not have a prefix that matches any of the registered frogfs blobs", path);
return false;
}
path += path_prefix_length;
if(path[0] == '/' || path[0] == '\\') path++;
const frogfs_entry_t * entry = frogfs_get_entry(blob->blob_fs, path);
if(entry == NULL) {
LV_LOG_WARN("No entry '%s' in frogfs blob registered under '%s'", path, blob->path_prefix);
return false;
}
*blob_dst = blob;
*entry_dst = entry;
return true;
}
static void destroy_blob(blob_t * blob)
{
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
fs_drv_data_t * data = fs_drv_p->user_data;
lv_free(blob->path_prefix);
frogfs_deinit(blob->blob_fs);
lv_ll_remove(&data->blob_ll, blob);
lv_free(blob);
}
static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode)
{
LV_UNUSED(drv);
if(mode & LV_FS_MODE_WR) {
LV_LOG_WARN("Cannot open files for writing with frogfs");
return NULL;
}
blob_t * blob;
const frogfs_entry_t * entry;
if(!get_blob_and_entry(path, &blob, &entry)) {
return NULL;
}
if(frogfs_is_dir(entry)) {
LV_LOG_WARN("Cannot open directory as file with frogfs");
return NULL;
}
frogfs_fh_t * fh = frogfs_open(blob->blob_fs, entry, 0);
if(fh == NULL) {
LV_LOG_WARN("Could not open '%s' even though the entry exists in frogfs", path);
return NULL;
}
return fh;
}
static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p)
{
LV_UNUSED(drv);
frogfs_close(file_p);
return LV_FS_RES_OK;
}
static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br)
{
LV_UNUSED(drv);
ssize_t res = frogfs_read(file_p, buf, btr);
if(res < 0) {
LV_LOG_WARN("Error reading frogfs file");
*br = 0;
return LV_FS_RES_UNKNOWN;
}
*br = res;
return LV_FS_RES_OK;
}
static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence)
{
LV_UNUSED(drv);
ssize_t res = frogfs_seek(file_p, pos, whence);
if(res < 0) {
LV_LOG_WARN("Error `seek`ing frogfs file");
return LV_FS_RES_UNKNOWN;
}
return LV_FS_RES_OK;
}
static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{
LV_UNUSED(drv);
size_t res = frogfs_tell(file_p);
if(res == (size_t) -1) {
LV_LOG_WARN("Error `tell`ing frogfs file");
return LV_FS_RES_UNKNOWN;
}
*pos_p = res;
return LV_FS_RES_OK;
}
static void * fs_dir_open(lv_fs_drv_t * drv, const char * path)
{
LV_UNUSED(drv);
blob_t * blob;
const frogfs_entry_t * entry;
if(!get_blob_and_entry(path, &blob, &entry)) {
return NULL;
}
if(!frogfs_is_dir(entry)) {
LV_LOG_WARN("Cannot open non-directory as directory with frogfs");
return NULL;
}
frogfs_dh_t * dh = frogfs_opendir(blob->blob_fs, entry);
if(dh == NULL) {
LV_LOG_WARN("Could not open directory '%s' even though the entry exists in frogfs", path);
return NULL;
}
return dh;
}
static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn, uint32_t fn_len)
{
LV_UNUSED(drv);
if(fn_len == 0) return LV_FS_RES_INV_PARAM;
const frogfs_entry_t * entry = frogfs_readdir(dir_p);
if(entry == NULL) {
fn[0] = '\0';
return LV_FS_RES_OK;
}
char * name = frogfs_get_name(entry);
if(frogfs_is_dir(entry)) {
lv_snprintf(fn, fn_len, "/%s", name);
}
else {
lv_strlcpy(fn, name, fn_len);
}
lv_free(name); /* frogfs `malloc`d it */
return LV_FS_RES_OK;
}
static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p)
{
LV_UNUSED(drv);
frogfs_closedir(dir_p);
return LV_FS_RES_OK;
}
#endif /*LV_USE_FS_FROGFS*/
+23
View File
@@ -67,6 +67,29 @@ void lv_fs_arduino_sd_init(void);
void lv_fs_uefi_init(void);
#endif
#if LV_USE_FS_FROGFS
void lv_fs_frogfs_init(void);
void lv_fs_frogfs_deinit(void);
/**
* Mount a frogfs blob at the path prefix. If there is a file "foo.txt"
* in the blob and the blob is registered with `path_prefix` as "my_blob",
* it can be opened later at path "my_blob/foo.txt".
* @param blob a frogfs blob/image from mkfrogfs.py
* @param path_prefix a prefix that will be used to refer to this blob when accessing it.
* @return LV_RESULT_OK or LV_RESULT_INVALID if there was an issue with the blob
*/
lv_result_t lv_fs_frogfs_register_blob(const void * blob, const char * path_prefix);
/**
* Unmount a frogfs blob that was previously mounted by `lv_fs_frogfs_register_blob`.
* All files and dirs should be closed before calling this.
* @param path_prefix the path prefix that the blob was registered with
*/
void lv_fs_frogfs_unregister_blob(const char * path_prefix);
#endif /*LV_USE_FS_FROGFS*/
/**********************
* MACROS
**********************/
+17
View File
@@ -2938,6 +2938,23 @@
#endif
#endif
#ifndef LV_USE_FS_FROGFS
#ifdef CONFIG_LV_USE_FS_FROGFS
#define LV_USE_FS_FROGFS CONFIG_LV_USE_FS_FROGFS
#else
#define LV_USE_FS_FROGFS 0
#endif
#endif
#if LV_USE_FS_FROGFS
#ifndef LV_FS_FROGFS_LETTER
#ifdef CONFIG_LV_FS_FROGFS_LETTER
#define LV_FS_FROGFS_LETTER CONFIG_LV_FS_FROGFS_LETTER
#else
#define LV_FS_FROGFS_LETTER '\0'
#endif
#endif
#endif
/** LODEPNG decoder library */
#ifndef LV_USE_LODEPNG
#ifdef CONFIG_LV_USE_LODEPNG
+10 -2
View File
@@ -369,6 +369,10 @@ void lv_init(void)
lv_fs_uefi_init();
#endif
#if LV_USE_FS_FROGFS
lv_fs_frogfs_init();
#endif
/*Use the earlier initialized position of FFmpeg decoder as a fallback decoder*/
#if LV_USE_FFMPEG
lv_ffmpeg_init();
@@ -499,8 +503,6 @@ void lv_deinit(void)
lv_layout_deinit();
lv_fs_deinit();
lv_timer_core_deinit();
#if LV_USE_PROFILER && LV_USE_PROFILER_BUILTIN
@@ -519,6 +521,12 @@ void lv_deinit(void)
lv_translation_deinit();
#endif
#if LV_USE_FS_FROGFS
lv_fs_frogfs_deinit();
#endif
lv_fs_deinit();
lv_mem_deinit();
lv_initialized = false;
+72
View File
@@ -341,6 +341,78 @@ lv_fs_res_t lv_fs_tell(lv_fs_file_t * file_p, uint32_t * pos)
return res;
}
lv_fs_res_t lv_fs_get_size(lv_fs_file_t * file_p, uint32_t * size_res)
{
uint32_t original_pos;
lv_fs_res_t ret = lv_fs_tell(file_p, &original_pos);
if(ret != LV_FS_RES_OK) {
return ret;
}
ret = lv_fs_seek(file_p, 0, LV_FS_SEEK_END);
if(ret != LV_FS_RES_OK) {
return ret;
}
ret = lv_fs_tell(file_p, size_res);
if(ret != LV_FS_RES_OK || *size_res != original_pos) {
lv_fs_res_t seek_res = lv_fs_seek(file_p, original_pos, LV_FS_SEEK_SET);
if(ret == LV_FS_RES_OK) {
ret = seek_res;
}
}
return ret;
}
lv_fs_res_t lv_fs_path_get_size(const char * path, uint32_t * size_res)
{
lv_fs_file_t file;
lv_fs_res_t ret = lv_fs_open(&file, path, LV_FS_MODE_RD);
if(ret != LV_FS_RES_OK) {
return ret;
}
ret = lv_fs_seek(&file, 0, LV_FS_SEEK_END);
if(ret == LV_FS_RES_OK) {
ret = lv_fs_tell(&file, size_res);
}
lv_fs_res_t close_res = lv_fs_close(&file);
if(ret == LV_FS_RES_OK) {
ret = close_res;
}
return ret;
}
lv_fs_res_t lv_fs_load_to_buf(void * buf, uint32_t buf_size, const char * path)
{
lv_fs_file_t file;
lv_fs_res_t ret = lv_fs_open(&file, path, LV_FS_MODE_RD);
if(ret != LV_FS_RES_OK) {
return ret;
}
uint32_t bytes_read;
ret = lv_fs_read(&file, buf, buf_size, &bytes_read);
if(ret == LV_FS_RES_OK && bytes_read != buf_size) {
LV_LOG_WARN("Only %"LV_PRIu32" bytes out of %"LV_PRIu32" were read from the file to the buffer",
bytes_read, buf_size);
ret = LV_FS_RES_UNKNOWN;
}
lv_fs_res_t close_res = lv_fs_close(&file);
if(ret == LV_FS_RES_OK) {
ret = close_res;
}
return ret;
}
lv_fs_res_t lv_fs_dir_open(lv_fs_dir_t * rddir_p, const char * path)
{
if(path == NULL) return LV_FS_RES_INV_PARAM;
+28
View File
@@ -222,6 +222,34 @@ lv_fs_res_t lv_fs_seek(lv_fs_file_t * file_p, uint32_t pos, lv_fs_whence_t whenc
*/
lv_fs_res_t lv_fs_tell(lv_fs_file_t * file_p, uint32_t * pos);
/**
* Get the size in bytes of an open file.
* The file read/write position will not be affected.
* @param file_p pointer to a lv_fs_file_t variable
* @param size_res pointer to store the file size
* @return LV_FS_RES_OK or any error from `lv_fs_res_t`
*/
lv_fs_res_t lv_fs_get_size(lv_fs_file_t * file_p, uint32_t * size_res);
/**
* Get the size in bytes of a file at the given path.
* @param path the path of the file
* @param size_res pointer to store the file size
* @return LV_FS_RES_OK or any error from `lv_fs_res_t`
*/
lv_fs_res_t lv_fs_path_get_size(const char * path, uint32_t * size_res);
/**
* Read the contents of a file at the given path into a buffer.
* @param buf a buffer to read the contents of the file into
* @param buf_size the size of the buffer and the amount to read from the file
* @param path the path of the file
* @return LV_FS_RES_OK on success, LV_FS_RES_UNKNOWN if fewer than
* `buf_size` bytes could be read from the file,
* or any error from `lv_fs_res_t`
*/
lv_fs_res_t lv_fs_load_to_buf(void * buf, uint32_t buf_size, const char * path);
/**
* Initialize a 'fs_dir_t' variable for directory reading
* @param rddir_p pointer to a 'lv_fs_dir_t' variable
+2
View File
@@ -376,6 +376,8 @@ typedef struct _lv_xml_component_scope_t lv_xml_component_scope_t;
typedef struct _lv_xml_parser_state_t lv_xml_parser_state_t;
typedef struct _lv_xml_load_t lv_xml_load_t;
#if LV_USE_EVDEV
typedef struct _lv_evdev_discovery_t lv_evdev_discovery_t;
#endif
+5
View File
@@ -27,6 +27,7 @@
#include "lv_xml_style.h"
#include "lv_xml_translation.h"
#include "lv_xml_utils.h"
#include "lv_xml_load_private.h"
#include "lv_xml_private.h"
#include "parsers/lv_xml_obj_parser.h"
#include "parsers/lv_xml_button_parser.h"
@@ -233,6 +234,8 @@ void lv_xml_init(void)
lv_xml_widget_register("lv_obj-bind_state_if_lt", lv_obj_xml_bind_state_create, lv_obj_xml_bind_state_apply);
lv_xml_widget_register("lv_obj-bind_state_if_ge", lv_obj_xml_bind_state_create, lv_obj_xml_bind_state_apply);
lv_xml_widget_register("lv_obj-bind_state_if_le", lv_obj_xml_bind_state_create, lv_obj_xml_bind_state_apply);
lv_xml_load_init();
}
void lv_xml_deinit(void)
@@ -241,6 +244,8 @@ void lv_xml_deinit(void)
lv_xml_test_unregister();
#endif
lv_xml_load_deinit();
lv_free((void *)xml_path_prefix);
}
+121 -10
View File
@@ -7,16 +7,24 @@
* INCLUDES
*********************/
#include "lv_xml_load.h"
#include "lv_xml_load_private.h"
#if LV_USE_XML
#include "lv_xml_private.h"
#include "../../core/lv_global.h"
#include "../../misc/lv_fs.h"
#include "../../libs/fsdrv/lv_fsdrv.h"
#include "../../misc/lv_ll.h"
#include "../../libs/expat/expat.h"
/*********************
* DEFINES
*********************/
#define xml_loads LV_GLOBAL_DEFAULT()->xml_loads
#define PATH_PREFIX_BUF_SIZE 32
#define PATH_PREFIX_FMT "__LV_XML_%p"
/**********************
* TYPEDEFS
**********************/
@@ -30,16 +38,20 @@ typedef enum {
typedef struct {
XML_Parser parser;
lv_xml_type_t type;
} load_from_file_parser_data_t;
} load_from_path_parser_data_t;
struct _lv_xml_load_t {
const void * blob;
};
/**********************
* STATIC PROTOTYPES
**********************/
static lv_result_t load_all_recursive(char * path_buf, const char * path);
static void load_from_file(const char * path);
static void load_from_path(const char * path);
static char * path_filename_without_extension(const char * path);
static void load_from_file_start_element_handler(void * user_data, const char * name, const char ** attrs);
static void load_from_path_start_element_handler(void * user_data, const char * name, const char ** attrs);
/**********************
* STATIC VARIABLES
@@ -53,6 +65,18 @@ static void load_from_file_start_element_handler(void * user_data, const char *
* GLOBAL FUNCTIONS
**********************/
void lv_xml_load_init(void)
{
lv_ll_init(&xml_loads, sizeof(lv_xml_load_t));
}
void lv_xml_load_deinit(void)
{
#if LV_USE_FS_FROGFS
lv_xml_unload(NULL);
#endif
}
lv_result_t lv_xml_load_all_from_path(const char * path)
{
char path_buf[LV_FS_MAX_PATH_LENGTH];
@@ -71,6 +95,93 @@ lv_result_t lv_xml_load_all_from_path(const char * path)
return load_all_recursive(path_buf, path);
}
#if LV_USE_FS_FROGFS
lv_xml_load_t * lv_xml_load_all_from_data(const void * buf, uint32_t buf_size)
{
LV_UNUSED(buf_size); /* keep it as a parameter for future implementations */
lv_xml_load_t * load = lv_ll_ins_head(&xml_loads);
LV_ASSERT_MALLOC(load);
if(load == NULL) return NULL;
lv_memzero(load, sizeof(*load));
char path_prefix_with_letter[PATH_PREFIX_BUF_SIZE];
lv_snprintf(path_prefix_with_letter, sizeof(path_prefix_with_letter), "%c:"PATH_PREFIX_FMT, LV_FS_FROGFS_LETTER, load);
char * path_prefix = path_prefix_with_letter + 2;
lv_result_t res = lv_fs_frogfs_register_blob(buf, path_prefix);
if(res != LV_RESULT_OK) {
LV_LOG_WARN("could not register the XML data blob");
lv_ll_remove(&xml_loads, load);
lv_free(load);
return NULL;
}
res = lv_xml_load_all_from_path(path_prefix_with_letter);
if(res != LV_RESULT_OK) {
lv_fs_frogfs_unregister_blob(path_prefix);
lv_ll_remove(&xml_loads, load);
lv_free(load);
return NULL;
}
return load;
}
lv_xml_load_t * lv_xml_load_all_from_file(const char * file_path)
{
lv_fs_res_t fs_res;
uint32_t blob_size;
fs_res = lv_fs_path_get_size(file_path, &blob_size);
if(fs_res != LV_FS_RES_OK) {
LV_LOG_WARN("could not get the size of the file to load XML data from");
return NULL;
}
void * blob = lv_malloc(blob_size);
LV_ASSERT_MALLOC(blob);
if(blob == NULL) {
LV_LOG_WARN("could not allocate memory for the XML file blob");
return NULL;
}
fs_res = lv_fs_load_to_buf(blob, blob_size, file_path);
if(fs_res != LV_FS_RES_OK) {
LV_LOG_WARN("could not read the XML file blob");
lv_free(blob);
return NULL;
}
lv_xml_load_t * load = lv_xml_load_all_from_data(blob, blob_size);
if(load == NULL) {
lv_free(blob);
return NULL;
}
load->blob = blob;
return load;
}
void lv_xml_unload(lv_xml_load_t * load)
{
if(load == NULL) {
lv_ll_clear_custom(&xml_loads, (void (*)(void *)) lv_xml_unload);
return;
}
char path_prefix[PATH_PREFIX_BUF_SIZE];
lv_snprintf(path_prefix, sizeof(path_prefix), PATH_PREFIX_FMT, load);
lv_fs_frogfs_unregister_blob(path_prefix);
lv_free((void *) load->blob); /* it may be NULL */
lv_ll_remove(&xml_loads, load);
lv_free(load);
}
#endif /*LV_USE_FS_FROGFS*/
/**********************
* STATIC FUNCTIONS
**********************/
@@ -108,7 +219,7 @@ static lv_result_t load_all_recursive(char * path_buf, const char * path)
ret = load_all_recursive(path_buf, full_path);
}
else {
load_from_file(full_path);
load_from_path(full_path);
}
lv_free(full_path);
@@ -128,7 +239,7 @@ dir_close_out:
return ret;
}
static void load_from_file(const char * path)
static void load_from_path(const char * path)
{
const char * ext = lv_fs_get_ext(path);
@@ -170,7 +281,7 @@ static void load_from_file(const char * path)
/* Null-terminate the buffer */
xml_buf[rn] = '\0';
load_from_file_parser_data_t parser_data;
load_from_path_parser_data_t parser_data;
XML_Memory_Handling_Suite mem_handlers;
mem_handlers.malloc_fcn = lv_malloc;
@@ -180,7 +291,7 @@ static void load_from_file(const char * path)
parser_data.parser = parser;
parser_data.type = LV_XML_TYPE_UNKNOWN;
XML_SetUserData(parser, &parser_data);
XML_SetStartElementHandler(parser, load_from_file_start_element_handler);
XML_SetStartElementHandler(parser, load_from_path_start_element_handler);
/* Parse the XML */
enum XML_Status parser_res = XML_Parse(parser, xml_buf, file_size, XML_TRUE);
@@ -232,10 +343,10 @@ static char * path_filename_without_extension(const char * path)
return filename;
}
static void load_from_file_start_element_handler(void * user_data, const char * name, const char ** attrs)
static void load_from_path_start_element_handler(void * user_data, const char * name, const char ** attrs)
{
LV_UNUSED(attrs);
load_from_file_parser_data_t * data = user_data;
load_from_path_parser_data_t * data = user_data;
if(lv_streq(name, "component") || lv_streq(name, "screen") || lv_streq(name, "globals")) {
data->type = LV_XML_TYPE_COMPONENT;
+26
View File
@@ -38,6 +38,32 @@ extern "C" {
*/
lv_result_t lv_xml_load_all_from_path(const char * path);
#if LV_USE_FS_FROGFS
/**
* Mount a data blob and recurse through it, loading all XML components,
* screens, globals, and translations.
* @param buf the data blob
* @param buf_size the size of the data blob
* @return a handle that can be used to unload it later
*/
lv_xml_load_t * lv_xml_load_all_from_data(const void * buf, uint32_t buf_size);
/**
* Mount a data blob located at `file_path` in a filesystem and recurse through it,
* loading all XML components, screens, globals, and translations.
* @param file_path the path of the data blob
* @return a handle that can be used to unload it later
*/
lv_xml_load_t * lv_xml_load_all_from_file(const char * file_path);
/**
* Unload XML data that was loaded by a function that returned `lv_xml_load_t *`.
* Any assets in the loaded data will not be accessible anymore.
* @param load a loaded XML data handle, or `NULL` to unload all.
*/
void lv_xml_unload(lv_xml_load_t * load);
#endif /*LV_USE_FS_FROGFS*/
/**********************
* MACROS
**********************/
+46
View File
@@ -0,0 +1,46 @@
/**
* @file lv_xml_load_private.h
*
*/
#ifndef LV_XML_LOAD_PRIVATE_H
#define LV_XML_LOAD_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_xml_load.h"
#if LV_USE_XML
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
void lv_xml_load_init(void);
void lv_xml_load_deinit(void);
/**********************
* MACROS
**********************/
#endif /*LV_USE_XML*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_XML_LOAD_PRIVATE_H*/