add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
CHANGELOG_LAST.md
CHANGELOG_LAST.rst

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
#!/bin/bash
set -e
# These variables allow us to specify an alternate repository URL and commit reference
# This is particularly useful when running in CI environments for pull requests
# where we need to build from the contributor's forked repository
REPO_URL="${1:-}"
COMMIT_REF="${2:-}"
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
rm -rf emscripten_builder
git clone https://github.com/lvgl/lv_sim_emscripten.git emscripten_builder
scripts/genexamplelist.sh > emscripten_builder/examplelist.c
cd emscripten_builder
git submodule update --init -- lvgl
cd lvgl
if [ -n "$REPO_URL" ] && [ -n "$COMMIT_REF" ]; then
echo "Using provided repo URL: $REPO_URL and commit ref: $COMMIT_REF for lvgl submodule"
git remote set-url origin "$REPO_URL"
git fetch origin
git checkout "$COMMIT_REF"
else
CURRENT_REF="$(git rev-parse HEAD)"
echo "Using current commit ref: $CURRENT_REF for lvgl"
git checkout "$CURRENT_REF"
fi
cd ..
# Generate lv_conf
LV_CONF_PATH=`pwd`/lvgl/configs/ci/docs/lv_conf_docs.h
cp lvgl/lv_conf_template.h $LV_CONF_PATH
python ./lvgl/scripts/generate_lv_conf.py \
--template lvgl/lv_conf_template.h \
--config $LV_CONF_PATH \
--defaults lvgl/configs/ci/docs/lv_conf_docs.defaults
mkdir cmbuild
cd cmbuild
emcmake cmake .. -DLV_BUILD_CONF_PATH=$LV_CONF_PATH -DLVGL_CHOSEN_DEMO=lv_example_noop -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
emmake make -j$(nproc)
rm -rf CMakeFiles
cd ../..
cp -a emscripten_builder/cmbuild docs/src/_static/built_lv_examples

View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python3
import argparse
from argparse import RawTextHelpFormatter
import os
import sys
parser = argparse.ArgumentParser(description="""Create fonts for LVGL including the built-in symbols. lv_font_conv needs to be installed. See https://github.com/lvgl/lv_font_conv
Example: python built_in_font_gen.py --size 16 -o lv_font_roboto_16.c --bpp 4 -r 0x20-0x7F""", formatter_class=RawTextHelpFormatter)
parser.add_argument('-s', '--size',
type=int,
metavar = 'px',
nargs='?',
help='Size of the font in px')
parser.add_argument('--bpp',
type=int,
metavar = '1,2,4',
nargs='?',
help='Bit per pixel')
parser.add_argument('-r', '--range',
nargs='+',
metavar = 'start-end',
default=['0x20-0x7F,0xB0,0x2022'],
help='Ranges and/or characters to include. Default is 0x20-7F (ASCII). E.g. -r 0x20-0x7F, 0x200, 324')
parser.add_argument('--symbols',
nargs='+',
metavar = 'sym',
default=[''],
help=u'Symbols to include. E.g. -s ÁÉŐ'.encode('utf-8'))
parser.add_argument('--font',
metavar = 'file',
nargs='?',
default='Montserrat-Medium.ttf',
help='A TTF or WOFF file')
parser.add_argument('-o', '--output',
nargs='?',
metavar='file',
help='Output file name. E.g. my_font_20.c')
parser.add_argument('--compressed', action='store_true',
help='Compress the bitmaps')
parser.add_argument('--subpx', action='store_true',
help='3 times wider letters for sub pixel rendering')
args = parser.parse_args()
if args.compressed == False:
compr = "--no-compress --no-prefilter"
else:
compr = ""
if len(args.symbols[0]) != 0:
args.symbols[0] = "--symbols " + args.symbols[0]
subpx = ""
if args.subpx: subpx = "--lcd"
#Built in symbols
syms = "61441,61448,61451,61452,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650"
#Run the command (Add degree and bullet symbol)
cmd = "lv_font_conv {} {} --bpp {} --size {} --font {} -r {} {} --font FontAwesome5-Solid+Brands+Regular.woff -r {} --format lvgl -o {} --force-fast-kern-format".format(subpx, compr, args.bpp, args.size, args.font, args.range[0], args.symbols[0], syms, args.output)
os.system(cmd)

View File

@@ -0,0 +1,99 @@
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
Bitstream Vera Fonts Copyright
------------------------------
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute the
Font Software, including without limitation the rights to use, copy, merge,
publish, distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to the
following conditions:
The above copyright and trademark notices and this permission notice shall
be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional glyphs or characters may be added to the Fonts, only if the fonts
are renamed to names not containing either the words "Bitstream" or the word
"Vera".
This License becomes null and void to the extent applicable to Fonts or Font
Software that has been modified and is distributed under the "Bitstream
Vera" names.
The Font Software may be sold as part of a larger software package but no
copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font Software
without prior written authorization from the Gnome Foundation or Bitstream
Inc., respectively. For further information, contact: fonts at gnome dot
org.
Arev Fonts Copyright
------------------------------
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license ("Fonts") and
associated documentation files (the "Font Software"), to reproduce
and distribute the modifications to the Bitstream Vera Font Software,
including without limitation the rights to use, copy, merge, publish,
distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to
the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words "Tavmjong Bah" or the word "Arev".
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
"Tavmjong Bah Arev" names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not
be used in advertising or otherwise to promote the sale, use or other
dealings in this Font Software without prior written authorization
from Tavmjong Bah. For further information, contact: tavmjong @ free
. fr.
$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $

View File

@@ -0,0 +1,165 @@
Fonticons, Inc. (https://fontawesome.com)
--------------------------------------------------------------------------------
Font Awesome Free License
Font Awesome Free is free, open source, and GPL friendly. You can use it for
commercial projects, open source projects, or really almost whatever you want.
Full Font Awesome Free license: https://fontawesome.com/license/free.
--------------------------------------------------------------------------------
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
The Font Awesome Free download is licensed under a Creative Commons
Attribution 4.0 International License and applies to all icons packaged
as SVG and JS file types.
--------------------------------------------------------------------------------
# Fonts: SIL OFL 1.1 License
In the Font Awesome Free download, the SIL OFL license applies to all icons
packaged as web and desktop font files.
Copyright (c) 2022 Fonticons, Inc. (https://fontawesome.com)
with Reserved Font Name: "Font Awesome".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
SIL OPEN FONT LICENSE
Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting — in part or in whole — any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
--------------------------------------------------------------------------------
# Code: MIT License (https://opensource.org/licenses/MIT)
In the Font Awesome Free download, the MIT license applies to all non-font and
non-icon files.
Copyright 2022 Fonticons, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
# Attribution
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
Awesome Free files already contain embedded comments with sufficient
attribution, so you shouldn't need to do anything additional when using these
files normally.
We've kept attribution comments terse, so we ask that you do not actively work
to remove them from files, especially code. They're a great way for folks to
learn about Font Awesome.
--------------------------------------------------------------------------------
# Brand Icons
All brand icons are trademarks of their respective owners. The use of these
trademarks does not indicate endorsement of the trademark holder by Font
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
to represent the company, product, or service to which they refer.**

View File

@@ -0,0 +1,93 @@
Copyright 2011 The Montserrat Project Authors (https://github.com/JulietaUla/Montserrat)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,96 @@
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font
Name 'Source'. Source is a trademark of Adobe in the United States
and/or other countries.
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,275 @@
<html>
<meta charset="UTF-8" />
<head>
<title>Unscii - a bitmapped Unicode font for blocky graphics</title>
<style>
<!--
@font-face{font-family:"unscii16";
src: url("unscii-16.woff") format("woff"),
url("unscii-16.ttf") format("ttf"); }
@font-face{font-family:"unscii8";
src: url("unscii-8.woff") format("woff"),
url("unscii-8.ttf") format("ttf"); }
a{color:#003;}
ul{list-style-type:square;}
h1{font-family:"unscii16",monospace;font-size:32px;text-align:center;}
body{font-family:"unscii16",monospace; background-color:#ccc;color:#000;width:640px;}
pre{font-family:"unscii16",monospace; font-size:16px;line-height:16px}
-->
</style>
<body>
<p align="center"><img src="unsciilogo.png" alt="UNSCII" /></p>
<p>Unscii is a set of bitmapped Unicode fonts based on classic system fonts.
Unscii attempts to support character cell art well while also being suitable
for terminal and programming use.</p>
<p>The two main variants are unscii-8 (8×8 pixels per glyph) and unscii-16
(8×16). There are also several alternative styles for unscii-8, as well as
an 8x16 "full" variant that incorporates missing Unicode glyphs from
Fixedsys Excelsior and GNU Unifont. "unscii-16-full" falls under GPL because
of how Unifont is licensed; the other variants are in the Public Domain.</p>
<p>Unscii was created by Viznut.</p>
<br />
<h1>UNSCII 2.0</h1>
<p>In 2020-03-10, the new <a
href="http://www.unicode.org/versions/Unicode13.0.0/">Unicode version
13.0</a> added 214 graphics characters for "legacy computing" (including,
among all, the missing PETSCII characters, and a majority of missing
Teletext/Videotex characters). Most of these were already included in Unscii
1.x, but now I have been able to give them proper Unicode mappings as well.
This is the main reason for the Unscii 2.0 release.</p>
<p>Additionally, Unscii 2.0 fixes errors in some characters, legibility in
some others and adds a bunch of new ones.</p>
<p>A test picture representing what is currently available in Unicode (feel
free to copy-paste it to your editor to see what it looks like in other
fonts):</p>
<pre>
╎┆┊ ╱🭽▔🭾╲ 🮲🮳 🮸🮀🮵🮶🮀🮁🮁🮀🮼🯁🯂🯃 ▵ ↑ ◬
╶─╴╺━╸ ═ ╎┆┊ ⎹ ⎸▣⎹ ⎸ ▝▛▀▜▘ 🯲🯷🯶 △ ▴ ╽ ◭⬘◮
╷┌┬┐┍┯┑╒╤╕╏┇┋ 🮷 🭼▁🭿 ⎸ ▚▌█▐▞ 🯹🯵🯱 🯰 ▁▂▃▄▅▆▇█ ◃◅◁╳▷▻▹ ▲ ←╼╋╾→ ◩⬒⬔
│├┼┤┝┿┥╞╪╡╏┇┋ ⎹╱ ╲⎸ ▗▙▄▟▖ 🯴🯳🯸 █🮆🮅🮄▀🮃🮂▔⎹ ▽ ◂◄◀🮽▶►▸╿ ⮝ ⬖◧◫◨⬗
╵└┴┘┕┷┙╘╧╛┞╀┦ ▔▔▔▔▔ 🬑🬜🬰🬪🬟 🮞🮟 ▕▉ ◞◡◯◡ ◎🭵 ▿ ▼ ↓ ⮜◈⮞ ⬕⬓◪
╻┎┰┒┏┳┓ ┭╆╈╅┮╍╍╌╌ 🬥🬦🬍🬲🬵🬹🬱🬷🬌🬓🬙 🮝🮜 🮇▊◝◠◯◉◯◡◟🭴 ▾ ⮟ ◕ ⬙ ◔
┃┠╂┨┣╋┫ ╺┽╊◙╉┾┅┅┄┄ 🬔🬡🬖🬻🬞🬭🬏🬺🬢🬒🬧 🮈▋◍ ◠◯◠◜ 🭳 ◿◺
╹┖┸┚┗┻┛ ━┵╄╇╃┶┉┉┈┈ 🬃🬤🬫🬴🬠🬋🬐🬸🬛🬗🬇 🭇🬼 ▐▌ ◌🮣🮢 🮦 🭲 ◹◸ 🭯 🮀⚞⚟🮀 🯊 ◙◛◶─◵
╓╥╖ ╔╦╗┢╁┪ ┟┱┲┧ 🬣🬯🬈🬬🬁🬂🬀🬝🬅🬮🬘 🭢🭗 🮉▍ 🮤🮪🮫🮥🮧 🭱 🭯 🭮◙🭬╭──╮⎫🮻⎧ ◘◙│◲┼◱╭◒╮
║╟╫╢🮐🮒🮐╠╬╣ ╹┃ ┡┹┺┩ 🬳🬉🬩🬕🬊🬎🬆🬨🬚🬄🬶 🭊🬿 🮊▎ 🮩🮬🮭🮨 🭰 ◢🭫◣ 🮚 │ ▢ ⎮🏎⎪ ◙◚◷┼◴│◑╋◐
╙╨╜🮔 🮓╚╩╝ 🯆 🯅 🯇 🮣🮢 🯉 🯈 🭥🭚 🮋▏🮮 🮡🮠 ⎸🭮🭪◆🭨🮛🮿🭬╰─🮯─╯⎬⎯⎨ ◳─◰╰◓╯
░░🮐🮑🮐▓▓██🮗🮗▤▤▥▥▦▦▩▩▧▧🮘🮘🮙🮙▨▨🮕🮕🮖🮖 🭋🭀 █▁🭻🭺🭹🭸🭷🭶▔ ◥🭩◤ 🭭 ⎮⯊⎪ ▱▰ ▭▬
░░▒🮎▒▓▓██🮗🮗▤▤▥▥▦▦▩▩▧▧🮘🮘🮙🮙▨▨🮕🮕🮖🮖 🭦🭛 🮰 🭇🬼🭭 🭊🬿 🭋🭀 ⎭⯋⎩ ▯▮ ▫◻□■◼▪⬝·
🮌█🮍 ╲╱ 🭇🬼🭈🬽🭉🬾◢◣🭇🭃🭎🬼🭈🭆🭂🭍🭑🬽🭉🭁🭌🬾🭈🭄🭏🬽🭅🭐 ◦○◯⬤◖◗ ⬫⬦⬨♢◊◇◆♦⬧⬥⬩⬪
▒🮏▒ 🭢🭗🭣🭘🭤🭙◥◤🭢🭔🭟🭗🭣🭧🭓🭞🭜🭘🭤🭒🭝🭙🭣🭕🭠🭘🭖🭡 ∘⭘●
🭢🭗 🭥🭚 🭦🭛 •
</pre>
<h1>EXAMPLES</h1>
<p>Here are some conversions of legacy character set art into Unscii.</p>
<p>Amiga ansi: Divine Stylers by Hellbeard, as rendered with unscii-16.
<a href="http://sixteencolors.net/pack/impure54/xz-dvn.ans">Source</a></p>
<img src="xz-dvn.png" />
<p>PC ansi: Ansi Love by Rad Man, as rendered with unscii-16.
<a
href="http://sixteencolors.net/pack/blocktronics_blockalypse/rad-LOVE.ANS">Source</a></p>
<img src="rad-love.png" />
<p>Commodore 64 petscii pictures as rendered with unscii-8, using the
256-color xterm palette: I Has Floppy by Redcrab; The First Ball by
Dr.TerrorZ; Gary by Mermaid.</p>
<p align="center">
<img src="ihasfloppy.png" />
<img src="thefirstball.png" />
<img src="gary.png" />
</p>
<p>The source code package includes a generic bitmap-to-unscii converter. Here's an
example of a conversion to unscii-8 using the 256-color xterm
palette, without dithering:</p>
<p align="center">
<img src="tta.png" />
</p>
<br />
<h1>DOWNLOADS</h1>
<p>HEX and PCF are the only actual bitmapped formats here. HEX is the same
simple hexdump format as used by the Unifont project. TTF, OTF and WOFF
are vectorized.</p>
<p>NOTE: Due to format limitations, the PCF versions lack all the characters
above U+FFFF! However, all the new graphics characters are provided in the
good old PUA range as well. A mapping is in the file <a
href="uns2uni.tr">uns2uni.tr</a>.</p>
<table>
<tr>
<td>
<img src="unscii16.png" alt="[Some Unscii16 glyphs]" />
</td><td>
<p>unscii-16: <a href="unscii-16.hex">hex</a> <a href="unscii-16.pcf">pcf</a> <a href="unscii-16.ttf">ttf</a> <a href="unscii-16.otf">otf</a> <a href="unscii-16.woff">woff</a><br />
unscii-16-full: <a href="unscii-16-full.hex">hex</a> <a href="unscii-16-full.pcf">pcf</a> <a href="unscii-16-full.ttf">ttf</a> <a href="unscii-16-full.otf">otf</a> <a href="unscii-16-full.woff">woff</a><br />
8x16. The latter is recommended for serious terminal use where a large
Unicode coverage is needed. (Warning: unscii16-full files range from 2
to 12 megabytes in size; the others range from 40 to 400 kilobytes.)</p>
</td></tr>
<tr bgcolor="#999">
<td>
<img src="unscii8.png" alt="[Some Unscii8 glyphs]" />
</td><td>
<p>unscii-8: <a href="unscii-8.hex">hex</a> <a href="unscii-8.pcf">pcf</a> <a href="unscii-8.ttf">ttf</a> <a href="unscii-8.otf">otf</a> <a href="unscii-8.woff">woff</a></p>
</td></tr>
<tr>
<td>
<img src="unscii8-tall.png" alt="[Ascii range in Unscii8-tall" />
</td><td>
<p>unscii-8-tall: <a href="unscii-8-tall.hex">hex</a> <a href="unscii-8-tall.pcf">pcf</a> <a href="unscii-8-tall.ttf">ttf</a> <a href="unscii-8-tall.otf">otf</a> <a href="unscii-8-tall.woff">woff</a><br />
Double-height version of unscii8.</p>
</td></tr>
<tr bgcolor="#999">
<td>
<img src="unscii8-thin.png" alt="[Ascii range in Unscii8-tall" />
</td><td>
<p>unscii-8-thin: <a href="unscii-8-thin.hex">hex</a> <a href="unscii-8-thin.pcf">pcf</a> <a href="unscii-8-thin.ttf">ttf</a> <a href="unscii-8-thin.otf">otf</a> <a href="unscii-8-thin.woff">woff</a><br />
Based on system fonts with 1-pixel-wide lines.</p>
</td></tr>
<tr>
<td>
<img src="unscii8-alt.png" alt="[Ascii range in Unscii8-alt" />
</td><td>
<p>unscii-8-alt: <a href="unscii-8-alt.hex">hex</a> <a href="unscii-8-alt.pcf">pcf</a> <a href="unscii-8-alt.ttf">ttf</a> <a href="unscii-8-alt.otf">otf</a> <a href="unscii-8-alt.woff">woff</a><br />
Based on the more peculiar glyph forms of the reference fonts.</p>
</td></tr>
<tr bgcolor="#999">
<td>
<img src="unscii8-mcr.png" alt="[Ascii range in Unscii8-alt" />
</td><td>
<p>unscii-8-mcr: <a href="unscii-8-mcr.hex">hex</a> <a href="unscii-8-mcr.pcf">pcf</a> <a href="unscii-8-mcr.ttf">ttf</a> <a href="unscii-8-mcr.otf">otf</a> <a href="unscii-8-mcr.woff">woff</a><br />
Based on retrofuturistic MCR-like 8×8 fonts used in various games, demos,
etc.</p>
</td></tr>
<tr>
<td>
<img src="unscii8-fantasy.png" alt="[Ascii range in Unscii8-alt" />
</td><td>
<p>unscii-8-fantasy: <a href="unscii-8-fantasy.hex">hex</a> <a href="unscii-8-fantasy.pcf">pcf</a> <a href="unscii-8-fantasy.ttf">ttf</a> <a href="unscii-8-fantasy.otf">otf</a> <a href="unscii-8-fantasy.woff">woff</a><br />
Based on fonts used in fantasy games.</p>
</td></tr>
<tr bgcolor="#999">
<td>
&nbsp;
</td><td>
<p><a href="unscii-2.1-src.tar.gz">Source code for current Unscii version (2.1)</a></p>
<p><a href="unscii-2.0-src.tar.gz">Source code for Unscii 2.0</a></p>
<p><a href="unscii-1.1-src.tar.gz">Source code for Unscii 1.1</a></p>
</td></tr>
</table>
<br />
<h1>BACKSTORY</h1>
<p>Years ago, I noticed that Unicode had a bunch of pseudographic characters
that could be used to enrichen Ansi art. However, no one seemed to use them.
Even MUDs that used the 256-color Xterm palette and had no issues with
Unicode still preferred to stick to the blocks available in the MS-DOS
codepage 437.</p>
<p>After looking into existing Unicode fonts, the reason became obvious: the
implementation of non-CP437 graphics characters was shaky at best. Unicode
Consortium doesn't even care how pseudographics are implemented. It was a
kind of chicken-and-egg problem: No commonly accepted Unicode graphics font,
no Unicode art scene; no art scene, no font support. The idea of an
art-compatible Unicode font was born.</p>
<p>For Unscii, I studied a bunch of classic system fonts and how their
characters had been used in Ascii and "extended-Ascii" art.</p>
<p>8×8 system fonts can be divided in two major categories according to
their line thickness: 1-pixel and 2-pixel. 2-pixel-wide lines are used in
more prominent classic systems, so I chose it. Also, 2-pixel 8×8 system
fonts are surprisingly similar to one another which made it easier to choose
neutral shapes.</p>
<p>The basic look of the 8×8 variant of Unscii is based on the following
systems:</p>
<ul>
<li>Amiga (Topaz-8)</li>
<li>Amstrad CPC</li>
<li>Atari 8-bit (as in 800, XL etc.)</li>
<li>Atari Arcade (the iconic ROM font)</li>
<li>Atari 32-bit (as in ST etc.)</li>
<li>BBC Micro (graphics mode font)</li>
<li>Commodore 64</li>
<li>IBM PC (the 8×8 ROM font as in CGA, or VGA 80×50)</li>
</ul>
<p>The 8×16 variant of Unscii has been mostly derived from the 8×8 variant
by using a set of transformation principles. When in doubt, the following
fonts have been looked at for additional reference:</p>
<ul>
<li>Windows Fixedsys 8×15 (and its modern successor Fixedsys Excelsior)</li>
<li>IBM PC VGA ROM font(s) (and their modern successor U_VGA)</li>
<li>X Window System fonts 8x13(B) and 9x15(B)</li>
<li>Classic Macintosh 12-point Monaco</li>
<li>Digital VT420 10×16 font (used in the 80×24 mode)</li>
<li>Modern monospaced vector fonts: DejaVu Sans Mono, Lucida Console,
Inconsolata</li>
</ul>
<p>In general, neutral shapes are preferred, unless art, legibility or
readability require otherwise: The characters /\XY are connective because of
their connetive use in ascii art, and the serifs in iIl are longer than in
most classic systems.</p>
<p>Whenever a 8×16 shape has not been defined, Unscii falls back to
height-doubled 8×8.</p>
<p>I also studied game fonts and thin-line system fonts. This resulted in
the variants unscii-8-thin, unscii-8-mcr and unscii-8-fantasy.</p>
<p>When studying legacy character sets, I found literally hundreds of
characters without proper Unicode codepoints. These are mapped in the PUA
range as follows:</p>
<ul>
<li>U+E080..E0FF: Teletext/Videotex block mosaics.</li>
<li>U+E100..: The most prominent and useful non-Unicode pseudographics:
everything found in PETSCII, Videotex smooth mosaics, extra shades,
round corners, X/Y doublers.</li>
<li>U+E800..: Somewhat stranger but still potentially useful: junctions with
border-aligned lines, diagonal line junctions, non-straight lines, weirder
fill patterns, etc.</li>
<li>U+EC00..: Total oddities. Mostly game-oriented bitmaps and other
depictive characters from Sharp MZ, Aquarius, etc.</li>
</ul>
<p>Since Unicode 13.0, many of these are also available in Unicode, but
the PUA mappings are retained for compatibility.</p>
<br />
</body>
</html>

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
import os
print("Generating 8 px")
os.system("./built_in_font_gen.py --size 8 -o lv_font_montserrat_8.c --bpp 4")
print("\nGenerating 10 px")
os.system("./built_in_font_gen.py --size 10 -o lv_font_montserrat_10.c --bpp 4")
print("\nGenerating 12 px")
os.system("./built_in_font_gen.py --size 12 -o lv_font_montserrat_12.c --bpp 4")
print("\nGenerating 14 px")
os.system("./built_in_font_gen.py --size 14 -o lv_font_montserrat_14.c --bpp 4")
print("\nGenerating 16 px")
os.system("./built_in_font_gen.py --size 16 -o lv_font_montserrat_16.c --bpp 4")
print("\nGenerating 18 px")
os.system("./built_in_font_gen.py --size 18 -o lv_font_montserrat_18.c --bpp 4")
print("\nGenerating 20 px")
os.system("./built_in_font_gen.py --size 20 -o lv_font_montserrat_20.c --bpp 4")
print("\nGenerating 22 px")
os.system("./built_in_font_gen.py --size 22 -o lv_font_montserrat_22.c --bpp 4")
print("\nGenerating 24 px")
os.system("./built_in_font_gen.py --size 24 -o lv_font_montserrat_24.c --bpp 4")
print("\nGenerating 26 px")
os.system("./built_in_font_gen.py --size 26 -o lv_font_montserrat_26.c --bpp 4")
print("\nGenerating 28 px")
os.system("./built_in_font_gen.py --size 28 -o lv_font_montserrat_28.c --bpp 4")
print("\nGenerating 30 px")
os.system("./built_in_font_gen.py --size 30 -o lv_font_montserrat_30.c --bpp 4")
print("\nGenerating 32 px")
os.system("./built_in_font_gen.py --size 32 -o lv_font_montserrat_32.c --bpp 4")
print("\nGenerating 34 px")
os.system("./built_in_font_gen.py --size 34 -o lv_font_montserrat_34.c --bpp 4")
print("\nGenerating 36 px")
os.system("./built_in_font_gen.py --size 36 -o lv_font_montserrat_36.c --bpp 4")
print("\nGenerating 38 px")
os.system("./built_in_font_gen.py --size 38 -o lv_font_montserrat_38.c --bpp 4")
print("\nGenerating 40 px")
os.system("./built_in_font_gen.py --size 40 -o lv_font_montserrat_40.c --bpp 4")
print("\nGenerating 42 px")
os.system("./built_in_font_gen.py --size 42 -o lv_font_montserrat_42.c --bpp 4")
print("\nGenerating 44 px")
os.system("./built_in_font_gen.py --size 44 -o lv_font_montserrat_44.c --bpp 4")
print("\nGenerating 46 px")
os.system("./built_in_font_gen.py --size 46 -o lv_font_montserrat_46.c --bpp 4")
print("\nGenerating 48 px")
os.system("./built_in_font_gen.py --size 48 -o lv_font_montserrat_48.c --bpp 4")
print("\nGenerating 12 px subpx")
os.system("./built_in_font_gen.py --size 12 -o lv_font_montserrat_12_subpx.c --bpp 4 --subpx")
print("\nGenerating 28 px compressed")
os.system("./built_in_font_gen.py --size 28 -o lv_font_montserrat_28_compressed.c --bpp 4 --compressed")
print("\nGenerating 16 px Hebrew, Persian")
os.system("./built_in_font_gen.py --size 16 -o lv_font_dejavu_16_persian_hebrew.c --bpp 4 --font DejaVuSans.ttf -r 0x20-0x7f,0x5d0-0x5ea,0x600-0x6FF,0xFB50-0xFDFF,0xFE70-0xFEFF")
print("\nGenerating 14 px CJK")
os.system(u"./built_in_font_gen.py --size 14 -o lv_font_source_han_sans_sc_14_cjk.c --bpp 4 --font SourceHanSansSC-Normal.otf -r 0x20-0x7f --symbols 盗提陽帯鼻画輕ッ冊ェル写父ぁフ結想正四O夫源庭場天續鳥れ講猿苦階給了製守8祝己妳薄泣塩帰ぺ吃変輪那着仍嗯爭熱創味保字宿捨準查達肯ァ薬得査障該降察ね網加昼料等図邪秋コ態品屬久原殊候路願楽確針上被怕悲風份重歡っ附ぷ既4黨價娘朝凍僅際洋止右航よ专角應酸師個比則響健昇豐筆歷適修據細忙跟管長令家ザ期般花越ミ域泳通些油乏ラ。營ス返調農叫樹刊愛間包知把ヤ貧橋拡普聞前ジ建当繰ネ送習渇用補ィ覺體法遊宙ョ酔余利壊語くつ払皆時辺追奇そ們只胸械勝住全沈力光ん深溝二類北面社值試9和五勵ゃ貿幾逐打課ゲて領3鼓辦発評渉詳暇込计駄供嘛郵頃腦反構絵お容規借身妻国慮剛急乗静必議置克土オ乎荷更肉還混古渡授合主離條値決季晴東大尚央州が嗎験流先医亦林田星晩拿60旅婦量為痛テ孫う環友況玩務其ぼち揺坐一肩腰犯タょ希即果ぶ物練待み高九找やヶ都グ去」サ、气仮雑酒許終企笑録形リ銀切ギ快問滿役単黄集森毎實研喜蘇司鉛洲川条媽才兩話言雖媒出客づ卻現異故り誌逮同訊已視本題ぞを横開音第席費持眾怎選元退限ー賽処喝就残無いガ多ケ沒義遠歌隣錢某雪析嬉採自透き側員予ゼ白婚电へ顯呀始均畫似懸格車騒度わ親店週維億締慣免帳電甚來園浴ゅ愈京と杯各海怒ぜ排敗挙老買7極模実紀ヒ携隻告シ並屋這孩讓質ワブ富賃争康由辞マ火於短樣削弟材注節另室ダ招擁ぃ若套底波行勤關著泊背疲狭作念推ぐ民貸祖介說ビ代温契你我レ入描變再札ソ派頭智遅私聽舉灣山伸放直安ト誕煙付符幅ふ絡她届耳飲忘参革團仕様載ど歩獲嫌息の汚交興魚指資雙與館初学年幸史位柱族走括び考青也共腕Lで販擔理病イ今逃當寺猫邊菓係ム秘示解池影ド文例斷曾事茶寫明科桃藝売便え導禁財飛替而亡到し具空寝辛業ウ府セ國何基菜厳市努張缺雲根外だ断万砂ゴ超使台实ぽ礼最慧算軟界段律像夕丈窓助刻月夏政呼ぴざ擇趣除動従涼方勉名線対存請子氏將5少否諸論美感或西者定食御表は參歳緑命進易性錯房も捕皿判中觀戦ニ緩町ピ番ず金千ろ?不た象治関ャ每看徒卒統じ手範訪押座步号ベ旁以母すほ密減成往歲件緒読歯效院种七謂凝濃嵌震喉繼クュ拭死円2積水欲如ポにさ寒道區精啦姐ア聯能足及停思壓春且メ裏株官答概黒過氷柿戻厚ぱ党祭織引計け委暗複誘港バ失下村較続神ぇ尤強秀膝兒来績十書済化服破新廠1紹您情半式產系好教暑早め樂地休協良な哪常要揮周かエ麗境働避護ンツ香夜太見設非改広聲他検求危清彼經未在起葉控靴所差內造寄南望尺換向展備眠點完約ぎ裡分説申童優伝島机須塊日立拉,鉄軽單気信很転識支布数紙此迎受心輸坊モ處「訳三曇兄野顔戰增ナ伊列又髪両有取左毛至困吧昔赤狀相夠整別士経頼然簡ホ会發隨営需脱ヨば接永居冬迫圍甘醫誰部充消連弱宇會咲覚姉麼的増首统帶糖朋術商担移景功育庫曲總劃牛程駅犬報ロ學責因パ嚴八世後平負公げ曜陸專午之閉ぬ談ご災昨冷職悪謝對它近射敢意運船臉局難什産頗!球真記ま但蔵究制機案湖臺ひ害券男留内木驗雨施種特復句末濟キ色訴依せ百型る石牠討呢时任執飯歐宅組傳配小活ゆべ暖ズ漸站素らボ束価チ浅回女片独妹英目從認生違策僕楚ペ米こ掛む爸六状落漢プ投カ校做啊洗声探あ割体項履触々訓技ハ低工映是標速善点人デ口次可廿节宵植树端阳旦腊妇费愚劳动儿军师庆圣诞闰".encode('utf-8'))
print("\nGenerating 16 px CJK")
os.system(u"./built_in_font_gen.py --size 16 -o lv_font_source_han_sans_sc_16_cjk.c --bpp 4 --font SourceHanSansSC-Normal.otf -r 0x20-0x7f --symbols 盗提陽帯鼻画輕ッ冊ェル写父ぁフ結想正四O夫源庭場天續鳥れ講猿苦階給了製守8祝己妳薄泣塩帰ぺ吃変輪那着仍嗯爭熱創味保字宿捨準查達肯ァ薬得査障該降察ね網加昼料等図邪秋コ態品屬久原殊候路願楽確針上被怕悲風份重歡っ附ぷ既4黨價娘朝凍僅際洋止右航よ专角應酸師個比則響健昇豐筆歷適修據細忙跟管長令家ザ期般花越ミ域泳通些油乏ラ。營ス返調農叫樹刊愛間包知把ヤ貧橋拡普聞前ジ建当繰ネ送習渇用補ィ覺體法遊宙ョ酔余利壊語くつ払皆時辺追奇そ們只胸械勝住全沈力光ん深溝二類北面社值試9和五勵ゃ貿幾逐打課ゲて領3鼓辦発評渉詳暇込计駄供嘛郵頃腦反構絵お容規借身妻国慮剛急乗静必議置克土オ乎荷更肉還混古渡授合主離條値決季晴東大尚央州が嗎験流先医亦林田星晩拿60旅婦量為痛テ孫う環友況玩務其ぼち揺坐一肩腰犯タょ希即果ぶ物練待み高九找やヶ都グ去」サ、气仮雑酒許終企笑録形リ銀切ギ快問滿役単黄集森毎實研喜蘇司鉛洲川条媽才兩話言雖媒出客づ卻現異故り誌逮同訊已視本題ぞを横開音第席費持眾怎選元退限ー賽処喝就残無いガ多ケ沒義遠歌隣錢某雪析嬉採自透き側員予ゼ白婚电へ顯呀始均畫似懸格車騒度わ親店週維億締慣免帳電甚來園浴ゅ愈京と杯各海怒ぜ排敗挙老買7極模実紀ヒ携隻告シ並屋這孩讓質ワブ富賃争康由辞マ火於短樣削弟材注節另室ダ招擁ぃ若套底波行勤關著泊背疲狭作念推ぐ民貸祖介說ビ代温契你我レ入描變再札ソ派頭智遅私聽舉灣山伸放直安ト誕煙付符幅ふ絡她届耳飲忘参革團仕様載ど歩獲嫌息の汚交興魚指資雙與館初学年幸史位柱族走括び考青也共腕Lで販擔理病イ今逃當寺猫邊菓係ム秘示解池影ド文例斷曾事茶寫明科桃藝売便え導禁財飛替而亡到し具空寝辛業ウ府セ國何基菜厳市努張缺雲根外だ断万砂ゴ超使台实ぽ礼最慧算軟界段律像夕丈窓助刻月夏政呼ぴざ擇趣除動従涼方勉名線対存請子氏將5少否諸論美感或西者定食御表は參歳緑命進易性錯房も捕皿判中觀戦ニ緩町ピ番ず金千ろ?不た象治関ャ每看徒卒統じ手範訪押座步号ベ旁以母すほ密減成往歲件緒読歯效院种七謂凝濃嵌震喉繼クュ拭死円2積水欲如ポにさ寒道區精啦姐ア聯能足及停思壓春且メ裏株官答概黒過氷柿戻厚ぱ党祭織引計け委暗複誘港バ失下村較続神ぇ尤強秀膝兒来績十書済化服破新廠1紹您情半式產系好教暑早め樂地休協良な哪常要揮周かエ麗境働避護ンツ香夜太見設非改広聲他検求危清彼經未在起葉控靴所差內造寄南望尺換向展備眠點完約ぎ裡分説申童優伝島机須塊日立拉,鉄軽單気信很転識支布数紙此迎受心輸坊モ處「訳三曇兄野顔戰增ナ伊列又髪両有取左毛至困吧昔赤狀相夠整別士経頼然簡ホ会發隨営需脱ヨば接永居冬迫圍甘醫誰部充消連弱宇會咲覚姉麼的増首统帶糖朋術商担移景功育庫曲總劃牛程駅犬報ロ學責因パ嚴八世後平負公げ曜陸專午之閉ぬ談ご災昨冷職悪謝對它近射敢意運船臉局難什産頗!球真記ま但蔵究制機案湖臺ひ害券男留内木驗雨施種特復句末濟キ色訴依せ百型る石牠討呢时任執飯歐宅組傳配小活ゆべ暖ズ漸站素らボ束価チ浅回女片独妹英目從認生違策僕楚ペ米こ掛む爸六状落漢プ投カ校做啊洗声探あ割体項履触々訓技ハ低工映是標速善点人デ口次可廿节宵植树端阳旦腊妇费愚劳动儿军师庆圣诞闰".encode('utf-8'))
print("\nGenerating 8 px unscii")
os.system("lv_font_conv --no-compress --no-prefilter --bpp 1 --size 8 --font unscii-8.ttf -r 0x20-0x7F --format lvgl -o lv_font_unscii_8.c --force-fast-kern-format")
print("\nGenerating 16 px unscii")
os.system("lv_font_conv --no-compress --no-prefilter --bpp 1 --size 16 --font unscii-8.ttf -r 0x20-0x7F --format lvgl -o lv_font_unscii_16.c --force-fast-kern-format")
os.system('sed -i \'s|#include "lvgl/lvgl.h"|#include "../../lvgl.h"|\' lv_font_*.c')
os.system('astyle --ignore-exclude-errors --options=../code-format.cfg "lv_font_*.c"')
os.system('mv lv_font_*.c ../../src/font/')

View File

@@ -0,0 +1,122 @@
{{#each releases}}
`{{title}} <{{href}}>`__ {{niceDate}}
---------------------------------------------------------------------------------------------------------------------------------------------------
Breaking Changes
~~~~~~~~~~~~~~~~
{{#commit-list merges heading='' message='BREAKING CHANGE'}}
- .. warning: {{message}} `{{id}} <{{href}}>`__
{{/commit-list}}
{{#commit-list commits heading='' message='BREAKING CHANGE'}}
- .. warning: {{subject}} `{{shorthash}} <{{href}}>`__
{{/commit-list}}
{{#commit-list fixes heading='' message='BREAKING CHANGE'}}
- **{{commit.subject}}** `{{commit.shorthash}} <{{commit.href}}>`__
{{/commit-list}}
Architectural
~~~~~~~~~~~~~
{{#commit-list merges heading='' message='^arch' exclude='BREAKING CHANGE'}}
- **{{message}}** `{{id}} <{{href}}>`__
{{/commit-list}}
{{#commit-list commits heading='' message='^arch' exclude='BREAKING CHANGE'}}
- **{{subject}}** `{{shorthash}} <{{href}}>`__
{{/commit-list}}
{{#commit-list fixes heading='' message='^arch' exclude='BREAKING CHANGE'}}
- **{{commit.subject}}** `{{commit.shorthash}} <{{commit.href}}>`__
{{/commit-list}}
New Features
~~~~~~~~~~~~
{{#commit-list merges heading='' message='^feat' exclude='BREAKING CHANGE'}}
- **{{message}}** `{{id}} <{{href}}>`__
{{/commit-list}}
{{#commit-list commits heading='' message='^feat' exclude='BREAKING CHANGE'}}
- **{{subject}}** `{{shorthash}} <{{href}}>`__
{{/commit-list}}
{{#commit-list fixes heading='' message='^feat' exclude='BREAKING CHANGE'}}
- **{{commit.subject}}** `{{commit.shorthash}} <{{commit.href}}>`__
{{/commit-list}}
Performance
~~~~~~~~~~~
{{#commit-list merges heading='' message='^perf' exclude='BREAKING CHANGE'}}
- **{{message}}** `{{id}} <{{href}}>`__
{{/commit-list}}
{{#commit-list commits heading='' message='^perf' exclude='BREAKING CHANGE'}}
- **{{subject}}** `{{shorthash}} <{{href}}>`__
{{/commit-list}}
{{#commit-list fixes heading='' message='^perf' exclude='BREAKING CHANGE'}}
- **{{commit.subject}}** `{{commit.shorthash}} <{{commit.href}}>`__
{{/commit-list}}
Fixes
~~~~~
{{#commit-list merges heading='' message='^fix' exclude='(^fix conflict|^fix warning|BREAKING CHANGE)'}}
- **{{message}}** `{{id}} <{{href}}>`__
{{/commit-list}}
{{#commit-list commits heading='' message='^fix' exclude='(^fix conflict|^fix warning|BREAKING CHANGE)'}}
- **{{subject}}** `{{shorthash}} <{{href}}>`__
{{/commit-list}}
{{#commit-list fixes heading='' message='^fix' exclude='(^fix conflict|^fix warning|BREAKING CHANGE)'}}
- **{{commit.subject}}** `{{commit.shorthash}} <{{commit.href}}>`__
{{/commit-list}}
Examples
~~~~~~~~
{{#commit-list merges heading='' message='^example'}}
- **{{message}}** `{{id}} <({{href}})>`__
{{/commit-list}}
{{#commit-list commits heading='' message='^example'}}
- **{{subject}}** `{{shorthash}} <{{href}}>`__
{{/commit-list}}
{{#commit-list fixes heading='' message='^example'}}
- **{{commit.subject}}** `{{commit.shorthash}} <{{commit.href}}>`__
{{/commit-list}}
Docs
~~~~
{{#commit-list merges heading='' message='^docs'}}
- **{{message}}** `{{id}} <{{href}}>`__
{{/commit-list}}
{{#commit-list commits heading='' message='^docs'}}
- **{{subject}}** `{{shorthash}} <{{href}}>`__
{{/commit-list}}
{{#commit-list fixes heading='' message='^docs'}}
- **{{commit.subject}}** `{{commit.shorthash}} <{{commit.href}}>`__
{{/commit-list}}
CI and tests
~~~~~~~~~~~~
{{#commit-list merges heading='' message='(^ci|^test)'}}
- **{{message}}** `{{id}} <{{href}}>`__
{{/commit-list}}
{{#commit-list commits heading='' message='(^ci|^test)'}}
- **{{subject}}** `{{shorthash }} <{{href}}>`__
{{/commit-list}}
{{#commit-list fixes heading='' message='(^ci|^test)'}}
- **{{commit.subject}}** `{{commit.shorthash}} <{{commit.href}}>`__
{{/commit-list}}
Others
~~~~~~
{{#commit-list merges heading='' exclude='(^fix|^feat|^perf|^docs|^example|^ci|^test)'}}
- **{{message}}** `{{id }} <{{href}}>`__
{{/commit-list}}
{{#commit-list commits heading='' exclude='(^fix|^feat|^perf|^docs|^example|^ci|^test)'}}
- **{{subject}}** `{{shorthash }} <{{href}}>`__
{{/commit-list}}
{{#commit-list fixes heading='' exclude='(^fix|^feat|^perf|^docs|^example|^ci|^test)'}}
- **{{commit.subject}}** `{{commit.shorthash}} <{{commit.href}}>`__
{{/commit-list}}
{{/each}}

View File

@@ -0,0 +1,16 @@
#!/bin/sh
# Generate CHANGELOG_LAST.md from changes since the last version tag. (vx.y.z-dev tags are ignored)
# CHANGELOG_LAST.md can be edited manually if required and manually added to docs/CHANGELOG.md
#
# Requirements:
# npm install -g auto-changelog
#
# Usage:
# changelog-gen <next-version>
#
# Example: if the latest version is v5.6.7 the following can be used for bugfix, minor or major versions:
# changelog-gen v5.6.8
# changelog-gen v5.7.0
# changelog-gen v6.0.0
auto-changelog -t changelog-template.hbs -l false --latest-version $1 --unreleased-only --tag-pattern ^v[0-9]+.[0-9]+.[0-9]+$ -o CHANGELOG_LAST.rst

View File

@@ -0,0 +1,60 @@
--style=kr
--indent=spaces=4
--indent-classes
--indent-switches
--indent-cases
--indent-preproc-block
--indent-preproc-define
--indent-col1-comments
--pad-oper
--unpad-paren
--align-pointer=middle
--align-reference=middle
--convert-tabs
--max-code-length=120
--break-after-logical
--break-closing-braces
--attach-closing-while
--min-conditional-indent=0
--max-continuation-indent=120
--mode=c
# Allows each platform to use its own line endings. This then does
# what Git does out of the box: uses platform line endings in the
# working directory, but uses only LF line endings in the repository.
# `astyle` also natively uses line the endings it finds in the files,
# so this will be LF-only on Linux and CR/LF on Windows, in alignment
# with Git's default behavior. This prevents `astyle` from modifying
# every source file, which Git perceives as a change on Windows platforms.
#--lineend=linux
--suffix=none
--preserve-date
--formatted
--ignore-exclude-errors
--ignore-exclude-errors-x
--exclude=assets
--exclude=test_assets
--exclude=test_fonts
--exclude=../src/lv_conf_internal.h
--exclude=../src/core/lv_obj_style_gen.c
--exclude=../src/core/lv_obj_style_gen.h
--exclude=../src/libs/gif/gifdec.c
--exclude=../src/libs/gif/gifdec.h
--exclude=../src/libs/lodepng/lodepng.c
--exclude=../src/libs/lodepng/lodepng.h
--exclude=../src/libs/qrcode/qrcodegen.c
--exclude=../src/libs/qrcode/qrcodegen.h
--exclude=../src/libs/tjpgd/tjpgd.c
--exclude=../src/libs/tjpgd/tjpgd.h
--exclude=../src/libs/tjpgd/tjpgdcnf.h
--exclude=../src/libs/thorvg
--exclude=../src/libs/expat
--exclude=../src/libs/lz4
--exclude=../src/others/vg_lite_tvg/vg_lite.h
--exclude=../demos/high_res/fonts
--exclude=../tests/unity/unity.c
--exclude=../tests/unity/unity_internals.h
--exclude=../tests/unity/unity_support.c
--exclude=../tests/unity/unity_support.h
--exclude=../tests/test_images
--exclude=../tests/build_test_defheap
--exclude=../tests/build_test_sysheap

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3
import os
import sys
# Argument enhancement: to only run `astyle` on a specified directory, to
# only include changed source code, these arguments have been added. If
# run with no arguments, all the normal directories are examined as before:
# - /demos/
# - /examples/
# - /src/
# - /tests/
#
# Args:
# If ANY args are specified, ONLY run `astyle` on the specified directories.
# Any combination can be specified.
include_demos = True
include_examples = True
include_src = True
include_tests = True
args = sys.argv[1:]
# Have any args been specified?
if args:
include_demos = False
include_examples = False
include_src = False
include_tests = False
for arg in args:
if arg == "demos":
include_demos = True
elif arg == "examples":
include_examples = True
elif arg == "src":
include_src = True
elif arg == "tests":
include_tests = True
else:
print(f'Argument [{arg}] not recognized.')
print('Usage:')
print(' python code-format.py [dir [dir ...]]')
print(' where: dir can be demos, examples, src or tests.')
exit(1)
script_dir = os.path.realpath(__file__)
script_dir = os.path.dirname(script_dir)
cfg_file = os.path.join(script_dir, 'code-format.cfg')
if include_demos:
print("\nFormatting demos")
os.system(f'astyle --options={cfg_file} --recursive "{script_dir}/../demos/*.c,*.cpp,*.h"')
if include_examples:
print("\nFormatting examples")
os.system(f'astyle --options={cfg_file} --recursive "{script_dir}/../examples/*.c,*.cpp,*.h"')
if include_src:
print("\nFormatting src")
os.system(f'astyle --options={cfg_file} --recursive "{script_dir}/../src/*.c,*.cpp,*.h"')
if include_tests:
print("\nFormatting tests")
os.system(f'astyle --options={cfg_file} --recursive "{script_dir}/../tests/*.c,*.cpp,*.h"')

View File

@@ -0,0 +1,3 @@
#!/bin/sh
cppcheck -j8 --template="{severity}\t{file}:{line}\t{id}: {message}" --enable=all ../src/ --output-file=cppcheck_res.txt --suppress=unusedFunction --suppress=preprocessorErrorDirective --force

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env python3
import sys
import textwrap
import re
with open(sys.argv[1], 'r') as file:
s = file.read()
b = bytearray()
if '--filter-character' in sys.argv:
s = re.sub(r'[^\x00-\xff]', '', s)
if '--null-terminate' in sys.argv:
s += '\x00'
b.extend(map(ord, s))
print(textwrap.fill(', '.join([hex(a) for a in b]), 96))

View File

@@ -0,0 +1,4 @@
#!/bin/sh
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 || exit ; pwd -P )"
sed -n '/LVGL_VERSION_MAJOR/ {N;s@#define \+LVGL_VERSION_M.... \+@@g;s@\n@.@p}' "$SCRIPTPATH/../lv_version.h"

View File

@@ -0,0 +1,110 @@
#!/usr/bin/env python3
import argparse
from typing import List, Tuple
import sys
import os
try:
from fontTools.ttLib import TTFont
except ImportError:
print("Need fonttools package, do `pip3 install fonttools`")
sys.exit(1)
# Fonts that are excluded from the license check
# Only add fonts that are known to be public domain or have a compatible license
_EXCLUDED_FONTS = {
"OpenTypeTest GPOS One",
}
# Font name mapping to remove any style suffix
_FONT_NAME_MAP = {
"Montserrat Medium": "Montserrat",
"Montserrat SemiBold": "Montserrat",
"Montserrat Bold": "Montserrat",
"Source Han Sans SC Normal": "Source Han Sans SC",
}
def get_font_full_name(font_path: str) -> str:
font = TTFont(font_path)
name_records = font["name"].names
fallback = None
for record in name_records:
if record.nameID == 1: # ID 1 corresponds to the font family name and will be used if full name doesn't exists
fallback = record.toStr()
if record.nameID == 4: # ID 4 corresponds to the full font name
return record.toStr()
return fallback
def list_intree_fonts(path: str) -> List[Tuple[str, str]]:
fonts = []
for root, _, files in os.walk(path):
for file in files:
if file.lower().endswith((".ttf", ".otf", ".woff", ".woff2")):
font_path = os.path.join(root, file)
font_name = get_font_full_name(font_path).strip()
if font_name:
# Add a no-strict mode to ignore missing license files
fonts.append((font_path, font_name))
return fonts
def has_intree_license(license_root_folder: str, font_name: str) -> bool:
if font_name in _EXCLUDED_FONTS:
return True
# Prepare candidate folder names, ignoring casing
candidates = {
font_name.lower(),
font_name.replace(" ", "_").lower(),
font_name.replace(" ", "").lower(),
}
if font_name in _FONT_NAME_MAP:
candidates.add(_FONT_NAME_MAP[font_name].lower())
candidates.add(_FONT_NAME_MAP[font_name].replace(" ", "_").lower())
candidates.add(_FONT_NAME_MAP[font_name].replace(" ", "").lower())
# List all directories in the license_root_folder
for entry in os.listdir(license_root_folder):
entry_path = os.path.join(license_root_folder, entry)
if os.path.isdir(entry_path):
# Compare the directory name in lowercase with candidates
if entry.lower() in candidates:
# Check if the directory contains at least one file (ignoring subdirectories)
for item in os.listdir(entry_path):
item_path = os.path.join(entry_path, item)
if os.path.isfile(item_path):
return True
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Verify font licenses")
parser.add_argument(
"--no-strict",
action="store_true",
help="Ignore missing license files",
default=False,
)
args = parser.parse_args()
SCRIPT_DIR = os.path.dirname(__file__)
# List of font files in the tree
fonts = list_intree_fonts(os.path.join(SCRIPT_DIR, ".."))
has_font_without_license = False
print("Verifying license for fonts:")
for path, name in fonts:
if has_intree_license(
os.path.join(SCRIPT_DIR, "built_in_font", "font_license"), name
):
print(f" [OK] '{name}'")
else:
print(f" [MISSING] '{name}' - {path}")
has_font_without_license = True
if has_font_without_license and not args.no_strict:
sys.exit(1)

View File

@@ -0,0 +1,13 @@
import sys
from os import path
here = path.dirname(path.abspath(__file__))
if __name__ == "__main__":
if here not in sys.path:
sys.path.insert(0, here)
for key in tuple(filter(lambda m: m.startswith("lvglgdb"), sys.modules.keys())):
del sys.modules[key]
import lvglgdb # noqa: F401

View File

@@ -0,0 +1,16 @@
from .lvgl import *
from .value import *
from .debugger import *
# Debugger
Debugger()
# Dumps
DumpObj()
# Infos
InfoStyle()
InfoDrawUnit()
# Set instance
set_lvgl_instance(None)

View File

@@ -0,0 +1,73 @@
import argparse
import gdb
class Debugger(gdb.Command):
"""Start debugpy server or connect to a debug server, so we can debug python code from IDE like PyCharm/VSCode"""
def __init__(self, host="localhost", port=11451):
self.__host = host
self.__port = port
super().__init__("debugger", gdb.COMMAND_USER)
def invoke(self, args, from_tty):
parser = argparse.ArgumentParser(description=Debugger.__doc__)
parser.add_argument(
"--host",
type=str,
help="Server listening host",
)
parser.add_argument(
"-p",
"--port",
type=int,
help="Server listening port",
)
parser.add_argument(
"-t",
"--type",
choices=["pycharm", "vscode", "eclipse"],
help="Debugger type",
required=True,
)
try:
args = parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
if args.host:
self.__host = args.host
if args.port:
self.__port = args.port
if args.type == "pycharm":
self.connect_to_pycharm()
elif args.type == "vscode":
self.connect_to_vscode()
elif args.type == "eclipse":
self.connect_to_eclipse()
def connect_to_pycharm(self):
try:
import pydevd_pycharm
except ImportError:
print("pydevd_pycharm module not found. Please install it using pip.")
return
pydevd_pycharm.settrace(self.__host, port=self.__port, stdoutToServer=True, stderrToServer=True)
def connect_to_vscode(self):
try:
import debugpy
except ImportError:
print("debugpy module not found. Please install it using pip.")
return
debugpy.listen((self.__host, self.__port))
debugpy.wait_for_client()
def connect_to_eclipse(self):
print("Eclipse is not implemented yet")

View File

@@ -0,0 +1,335 @@
import argparse
from typing import Iterator, Union, Optional
import gdb
from .value import Value
gdb.execute("set pagination off")
gdb.write("set pagination off\n")
gdb.execute("set python print-stack full")
gdb.write("set python print-stack full\n")
g_lvgl_instance = None
class LVList(Value):
"""LVGL linked list iterator"""
def __init__(self, ll: Value, nodetype: Union[gdb.Type, str] = None):
if not ll:
raise ValueError("Invalid linked list")
super().__init__(ll)
self.nodetype = (
gdb.lookup_type(nodetype).pointer()
if isinstance(nodetype, str)
else nodetype
)
self.lv_ll_node_t = gdb.lookup_type("lv_ll_node_t").pointer()
self.current = self.head
self._next_offset = self.n_size + self.lv_ll_node_t.sizeof
self._prev_offset = self.n_size
def _next(self, node):
next_value = Value(int(node) + self._next_offset)
return next_value.cast(self.lv_ll_node_t, ptr=True).dereference()
def _prev(self, node):
prev_value = Value(int(node) + self._prev_offset)
return prev_value.cast(self.lv_ll_node_t, ptr=True).dereference()
def __iter__(self):
return self
def __next__(self):
if not self.current:
raise StopIteration
nodetype = self.nodetype if self.nodetype else self.lv_ll_node_t
node = self.current.cast(nodetype)
self.current = self._next(self.current)
return node
@property
def len(self):
len = 0
node = self.head
while node:
len += 1
node = self._next(node)
return len
class LVObject(Value):
"""LVGL object"""
def __init__(self, obj: Value):
super().__init__(obj.cast("lv_obj_t", ptr=True))
@property
def class_name(self):
name = self.class_p.name
return name.string() if name else "unknown"
@property
def x1(self):
return int(self.coords.x1)
@property
def y1(self):
return int(self.coords.y1)
@property
def x2(self):
return int(self.coords.x2)
@property
def y2(self):
return int(self.coords.y2)
@property
def child_count(self):
return self.spec_attr.child_cnt if self.spec_attr else 0
@property
def childs(self):
if not self.spec_attr:
return
for i in range(self.child_count):
child = self.spec_attr.children[i]
yield LVObject(child)
@property
def styles(self):
LV_STYLE_PROP_INV = 0
LV_STYLE_PROP_ANY = 0xFF
count = self.style_cnt
if count == 0:
return
styles = self.super_value("styles")
for i in range(count):
style = styles[i].style
prop_cnt = style.prop_cnt
values_and_props = style.values_and_props.cast("lv_style_const_prop_t", ptr=True)
for j in range(prop_cnt):
prop = values_and_props[j].prop
if prop == LV_STYLE_PROP_INV or prop == LV_STYLE_PROP_ANY:
continue
yield values_and_props[j]
def get_child(self, index: int):
return (
self.spec_attr.children[index] if self.spec_attr else None
)
class LVDisplay(Value):
"""LVGL display"""
def __init__(self, disp: Value):
super().__init__(disp)
@property
def screens(self):
screens = self.super_value("screens")
for i in range(self.screen_cnt):
yield LVObject(screens[i])
class LVGL:
"""LVGL instance"""
def __init__(self, lv_global: Value):
self.lv_global = lv_global.cast("lv_global_t", ptr=True)
def displays(self) -> Iterator[LVDisplay]:
ll = self.lv_global.disp_ll
if not ll:
return
for disp in LVList(ll, "lv_display_t"):
yield LVDisplay(disp)
def screen_active(self):
disp = self.lv_global.disp_default
return disp.act_scr if disp else None
def draw_units(self):
unit = self.lv_global.draw_info.unit_head
# Iterate through all draw units
while unit:
yield unit
unit = unit.next
def set_lvgl_instance(lv_global: Union[gdb.Value, Value, None]):
global g_lvgl_instance
if not lv_global:
try:
lv_global = Value(gdb.parse_and_eval("lv_global").address)
except gdb.error as e:
print(f"Failed to get lv_global: {e}")
return
if not isinstance(lv_global, Value):
lv_global = Value(lv_global)
inited = lv_global.inited
if not inited:
print("\x1b[31mlvgl is not initialized yet. Please call `set_lvgl_instance(None)` later.\x1b[0m")
return
g_lvgl_instance = LVGL(lv_global)
def dump_obj_info(obj: LVObject):
clzname = obj.class_name
coords = f"{obj.x1},{obj.y1},{obj.x2},{obj.y2}"
print(f"{clzname}@{hex(obj)} {coords}")
# Dump lv_style_const_prop_t
def dump_style_info(style: Value):
prop = int(style.prop)
value = style.value
print(f"{prop} = {value}")
class DumpObj(gdb.Command):
"""dump obj tree from specified obj"""
def __init__(self):
super(DumpObj, self).__init__(
"dump obj", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def dump_obj(self, obj: LVObject, depth=0, limit=None):
if not obj:
return
# dump self
print(" " * depth, end="")
dump_obj_info(obj)
if limit is not None and depth >= limit:
return
# dump children
for child in obj.childs:
self.dump_obj(child, depth + 1, limit=limit)
def invoke(self, args, from_tty):
parser = argparse.ArgumentParser(description="Dump lvgl obj tree.")
parser.add_argument(
"-L",
"--level",
type=int,
default=None,
help="Limit the depth of the tree.",
)
parser.add_argument(
"root",
type=str,
nargs="?",
default=None,
help="Optional root obj to dump.",
)
try:
args = parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
if args.root:
root = gdb.parse_and_eval(args.root)
root = LVObject(root)
self.dump_obj(root, limit=args.level)
else:
# dump all displays
depth = 0
for disp in g_lvgl_instance.displays():
print(f"Display {hex(disp)}")
for screen in disp.screens:
print(f'{" " * (depth + 1)}Screen@{hex(screen)}')
self.dump_obj(screen, depth=depth + 1, limit=args.level)
class InfoStyle(gdb.Command):
"""dump obj style value for specified obj"""
def __init__(self):
super(InfoStyle, self).__init__(
"info style", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def invoke(self, args, from_tty):
parser = argparse.ArgumentParser(description="Dump lvgl obj local style.")
parser.add_argument(
"obj",
type=str,
help="obj to show style.",
)
try:
args = parser.parse_args(gdb.string_to_argv(args))
except SystemExit:
return
obj = gdb.parse_and_eval(args.obj)
if not obj:
print("Invalid obj: ", args.obj)
return
obj = Value(obj)
# show all styles applied to this obj
for style in LVObject(obj).styles:
print(" ", end="")
dump_style_info(style)
class InfoDrawUnit(gdb.Command):
"""dump draw unit info"""
def __init__(self):
super(InfoDrawUnit, self).__init__(
"info draw_unit", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION
)
def dump_draw_unit(self, draw_unit: Value):
# Dereference to get the string content of the name from draw_unit
name = draw_unit.name.string()
# Print draw_unit information and the name
print(f"Draw Unit: {draw_unit}, Name: {name}")
# Handle different draw_units based on the name
def lookup_type(name):
try:
return gdb.lookup_type(name)
except gdb.error:
return None
types = {
"DMA2D": lookup_type("lv_draw_dma2d_unit_t"),
"NEMA_GFX": lookup_type("lv_draw_nema_gfx_unit_t"),
"NXP_PXP": lookup_type("lv_draw_pxp_unit_t"),
"NXP_VGLITE": lookup_type("lv_draw_vglite_unit_t"),
"OPENGLES": lookup_type("lv_draw_opengles_unit_t"),
"DAVE2D": lookup_type("lv_draw_dave2d_unit_t"),
"SDL": lookup_type("lv_draw_sdl_unit_t"),
"SW": lookup_type("lv_draw_sw_unit_t"),
"VG_LITE": lookup_type("lv_draw_vg_lite_unit_t"),
}
type = types.get(name, lookup_type("lv_draw_unit_t"))
print(draw_unit.cast(type, ptr=True).dereference().format_string(pretty_structs=True, symbols=True))
def invoke(self, args, from_tty):
for unit in g_lvgl_instance.draw_units():
self.dump_draw_unit(unit)

View File

@@ -0,0 +1,32 @@
import gdb
from typing import Optional, Union
class Value(gdb.Value):
def __init__(self, value: Union[gdb.Value, 'Value']):
super().__init__(value)
def __getitem__(self, key):
try:
value = super().__getitem__(key)
except gdb.error:
value = super().__getattr__(key)
return Value(value)
def __getattr__(self, key):
if hasattr(super(), key):
return Value(super().__getattribute__(key))
return Value(super().__getitem__(key))
def cast(self, type_name: str | gdb.Type, ptr: bool = False) -> Optional['Value']:
try:
gdb_type = gdb.lookup_type(type_name) if isinstance(type_name, str) else type_name
if ptr:
gdb_type = gdb_type.pointer()
return Value(super().cast(gdb_type))
except gdb.error:
return None
def super_value(self, attr: str) -> 'Value':
return self[attr]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,335 @@
import os
import sys
import argparse
import shutil
import tempfile
import json
import subprocess
base_path = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, base_path)
project_dir = os.path.abspath(os.path.join(base_path, '..', '..'))
docs_path = os.path.join(project_dir, 'docs')
sys.path.insert(0, docs_path)
import create_fake_lib_c # NOQA
import pycparser_monkeypatch # NOQA
import pycparser # NOQA
doxyfile_filename = 'Doxyfile'
DEVELOP = False
intermediate_dir = tempfile.mkdtemp(suffix='.lvgl_json')
def run(output_path, lv_conf_file, output_to_stdout, target_header, filter_private, no_docstrings, *compiler_args):
pycparser_monkeypatch.FILTER_PRIVATE = filter_private
lvgl_dir = project_dir
lvgl_src_dir = os.path.join(lvgl_dir, 'src')
int_lvgl_dir = os.path.join(intermediate_dir, 'lvgl')
lv_conf_dest_file = os.path.join(intermediate_dir, 'lv_conf.h')
target_header_base_name = (
os.path.splitext(os.path.split(target_header)[-1])[0]
)
try:
os.mkdir(int_lvgl_dir)
shutil.copytree(lvgl_src_dir, os.path.join(int_lvgl_dir, 'src'))
shutil.copyfile(os.path.join(lvgl_dir, 'lvgl.h'), os.path.join(int_lvgl_dir, 'lvgl.h'))
pp_file = os.path.join(intermediate_dir, target_header_base_name + '.pp')
if lv_conf_file is None:
lv_conf_templ_file = os.path.join(lvgl_dir, 'lv_conf_template.h')
with open(lv_conf_templ_file, 'rb') as f:
lines = f.read().decode('utf-8').split('\n')
for i, line in enumerate(lines):
if line.startswith('#if 0'):
lines[i] = '#if 1'
else:
for item in (
'LV_USE_LOG',
'LV_USE_OBJ_ID',
'LV_USE_OBJ_ID_BUILTIN',
'LV_USE_FLOAT',
'LV_USE_BIDI',
'LV_USE_LODEPNG',
'LV_USE_LIBPNG',
'LV_USE_BMP',
'LV_USE_TJPGD',
'LV_USE_LIBJPEG_TURBO',
'LV_USE_GIF',
'LV_BIN_DECODER_RAM_LOAD',
'LV_USE_RLE',
'LV_USE_QRCODE',
'LV_USE_BARCODE',
'LV_USE_TINY_TTF',
'LV_USE_GRIDNAV',
'LV_USE_FRAGMENT',
'LV_USE_IMGFONT',
'LV_USE_SNAPSHOT',
'LV_USE_FREETYPE'
):
if line.startswith(f'#define {item} '):
lines[i] = f'#define {item} 1'
break
with open(lv_conf_dest_file, 'wb') as f:
f.write('\n'.join(lines).encode('utf-8'))
else:
shutil.copyfile(lv_conf_file, lv_conf_dest_file)
include_dirs = [intermediate_dir, project_dir]
if sys.platform.startswith('win'):
import get_sdl2
try:
import pyMSVC # NOQA
except ImportError:
sys.stderr.write(
'\nThe pyMSVC library is missing, '
'please run "pip install pyMSVC" to install it.\n'
)
sys.stderr.flush()
sys.exit(-500)
env = pyMSVC.setup_environment() # NOQA
cpp_cmd = ['cl', '/std:c11', '/nologo', '/P']
output_pp = f'/Fi"{pp_file}"'
sdl2_include, _ = get_sdl2.get_sdl2(intermediate_dir)
include_dirs.append(sdl2_include)
include_path_env_key = 'INCLUDE'
elif sys.platform.startswith('darwin'):
include_path_env_key = 'C_INCLUDE_PATH'
cpp_cmd = [
'clang', '-std=c11', '-E', '-DINT32_MIN=0x80000000',
]
output_pp = f' >> "{pp_file}"'
else:
include_path_env_key = 'C_INCLUDE_PATH'
cpp_cmd = [
'gcc', '-std=c11', '-E', '-Wno-incompatible-pointer-types',
]
output_pp = f' >> "{pp_file}"'
fake_libc_path = create_fake_lib_c.run(intermediate_dir)
if include_path_env_key not in os.environ:
os.environ[include_path_env_key] = ''
os.environ[include_path_env_key] = (
f'{fake_libc_path}{os.pathsep}{os.environ[include_path_env_key]}'
)
if 'PATH' not in os.environ:
os.environ['PATH'] = ''
os.environ['PATH'] = (
f'{fake_libc_path}{os.pathsep}{os.environ["PATH"]}'
)
cpp_cmd.extend(compiler_args)
cpp_cmd.extend([
'-DLV_LVGL_H_INCLUDE_SIMPLE',
'-DLV_CONF_INCLUDE_SIMPLE',
'-DLV_USE_DEV_VERSION'
])
cpp_cmd.extend(['-DPYCPARSER', f'"-I{fake_libc_path}"'])
cpp_cmd.extend([f'"-I{item}"' for item in include_dirs])
cpp_cmd.append(f'"{target_header}"')
if sys.platform.startswith('win'):
cpp_cmd.insert(len(cpp_cmd) - 2, output_pp)
else:
cpp_cmd.append(output_pp)
cpp_cmd = ' '.join(cpp_cmd)
p = subprocess.Popen(
cpp_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=os.environ,
shell=True
)
out, err = p.communicate()
exit_code = p.returncode
if not os.path.exists(pp_file):
sys.stdout.write(out.decode('utf-8').strip() + '\n')
sys.stdout.write('EXIT CODE: ' + str(exit_code) + '\n')
sys.stderr.write(err.decode('utf-8').strip() + '\n')
sys.stdout.flush()
sys.stderr.flush()
raise RuntimeError('Unknown Failure')
with open(pp_file, 'r') as f:
pp_data = f.read()
cparser = pycparser.CParser()
ast = cparser.parse(pp_data, target_header)
doxyfile_src_file = os.path.join(docs_path, doxyfile_filename)
ast.setup_docs(no_docstrings, lvgl_src_dir,
intermediate_dir, doxyfile_src_file, output_to_stdout)
if not output_to_stdout and output_path is None:
if not DEVELOP:
shutil.rmtree(intermediate_dir)
return ast
elif output_to_stdout:
# stdout.reset()
print(json.dumps(ast.to_dict(), indent=4))
else:
if not os.path.exists(output_path):
os.makedirs(output_path)
output_path = os.path.join(output_path, target_header_base_name + '.json')
with open(output_path, 'w') as f:
f.write(json.dumps(ast.to_dict(), indent=4))
except Exception as err:
try:
print(cpp_cmd) # NOQA
print()
except: # NOQA
pass
for key, value in os.environ.items():
print(key + ':', value)
print()
import traceback
traceback.print_exc()
print()
exceptions = [
ArithmeticError,
AssertionError,
AttributeError,
EOFError,
FloatingPointError,
GeneratorExit,
ImportError,
IndentationError,
IndexError,
KeyError,
KeyboardInterrupt,
LookupError,
MemoryError,
NameError,
NotImplementedError,
OverflowError,
ReferenceError,
RuntimeError,
StopIteration,
SyntaxError,
TabError,
SystemExit,
TypeError,
UnboundLocalError,
UnicodeError,
UnicodeEncodeError,
UnicodeDecodeError,
UnicodeTranslateError,
ValueError,
ZeroDivisionError,
SystemError
]
if isinstance(err, OSError):
error = err.errno
else:
if type(err) in exceptions:
error = ~exceptions.index(type(err))
else:
error = -100
else:
error = 0
if DEVELOP:
print('temporary file path:', intermediate_dir)
else:
shutil.rmtree(intermediate_dir)
sys.exit(error)
if __name__ == '__main__':
parser = argparse.ArgumentParser('-')
parser.add_argument(
'--output-path',
dest='output_path',
help=(
'output directory for JSON file. If one is not '
'supplied then it will be output stdout'
),
action='store',
default=None
)
parser.add_argument(
'--lvgl-config',
dest='lv_conf',
help=(
'path to lv_conf.h (including file name), if this is not set then '
'a config file will be generated that has everything turned on.'
),
action='store',
default=None
)
parser.add_argument(
'--develop',
dest='develop',
help='this option leaves the temporary folder in place.',
action='store_true',
)
parser.add_argument(
"--target-header",
dest="target_header",
help=(
"path to a custom header file. When using this to supply a custom"
"header file you MUST insure that any LVGL includes are done so "
"they are relitive to the LVGL repository root folder.\n\n"
'#include "src/lvgl_private.h"\n\n'
"If you have includes to header files that are not LVGL then you "
"will need to add the include locations for those header files "
"when running this script. It is done using the same manner that "
"is used when calling a C compiler\n\n"
"You need to provide the absolute path to the header file when "
"using this feature."
),
action="store",
default=os.path.join(intermediate_dir, "lvgl", "lvgl.h")
)
parser.add_argument(
'--filter-private',
dest='filter_private',
help='Internal Use',
action='store_true',
)
parser.add_argument(
'--no-docstrings',
dest='no_docstrings',
help='Internal Use',
action='store_true',
)
args, extra_args = parser.parse_known_args()
DEVELOP = args.develop
run(args.output_path, args.lv_conf, args.output_path is None, args.target_header, args.filter_private, args.no_docstrings, *extra_args)

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
import zipfile
import io
import os
SDL2_URL = 'https://github.com/libsdl-org/SDL/releases/download/release-2.26.5/SDL2-devel-2.26.5-VC.zip' # NOQA
def get_path(name: str, p: str) -> str:
for file in os.listdir(p):
file = os.path.join(p, file)
if file.endswith(name):
return file
if os.path.isdir(file):
if res := get_path(name, file):
return res
def get_sdl2(path, url=SDL2_URL):
import requests # NOQA
stream = io.BytesIO()
with requests.get(url, stream=True) as r:
r.raise_for_status()
content_length = int(r.headers['Content-Length'])
chunks = 0
# print()
# sys.stdout.write('\r' + str(chunks) + '/' + str(content_length))
# sys.stdout.flush()
for chunk in r.iter_content(chunk_size=1024):
stream.write(chunk)
chunks += len(chunk)
# sys.stdout.write('\r' + str(chunks) + '/' + str(content_length))
# sys.stdout.flush()
# print()
stream.seek(0)
zf = zipfile.ZipFile(stream)
for z_item in zf.infolist():
for ext in ('.h', '.dll', '.lib'):
if not z_item.filename.endswith(ext):
continue
zf.extract(z_item, path=path)
break
include_path = get_path('include', path)
lib_path = get_path('lib\\x64', path)
dll_path = get_path('SDL2.dll', lib_path)
sdl_include_path = os.path.split(include_path)[0]
if not os.path.exists(os.path.join(sdl_include_path, 'SDL2')):
os.rename(include_path, os.path.join(sdl_include_path, 'SDL2'))
zf.close()
stream.close()
return os.path.abspath(sdl_include_path), dll_path

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
pycparser>=2.22
pyMSVC>=0.5.3; platform_system == "Windows"
Sphinx
breathe
imagesize
importlib-metadata
sphinx-rtd-theme
sphinx-sitemap
sphinxcontrib-applehelp
sphinxcontrib-devhelp
sphinxcontrib-htmlhelp
sphinxcontrib-jsmath
sphinxcontrib-qthelp
sphinxcontrib-serializinghtml
sphinx-rtd-dark-mode
typing-extensions

View File

@@ -0,0 +1,132 @@
#!/usr/bin/env python3
#
# Generate the cmake variables CONFIG_LV_USE_* or CONFIG_LV_BUILD_* from the
# preprocessed lv_conf_internal.h
#
# Author: David TRUAN (david.truan@edgemtech.ch)
# Author: Erik Tagirov (erik.tagirov@edgemtech.ch)
#
import os
import argparse
import re
def fatal(msg):
print()
print("ERROR! " + msg)
exit(1)
def get_args():
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=""
"Convert the expanded lv_conf_internal.h to cmake variables."
"It converts all LV_USE_*, LV_BUILD_* configurations."
)
parser.add_argument('--input', type=str, required=True, nargs='?',
help='Path of the macro expanded lv_conf_internal.h, which should be generated during a cmake build')
parser.add_argument('--output', type=str, required=True, nargs='?',
help='Path of the output file, where the cmake variables declaration will be written (ex: build/lv_conf.cmake)')
parser.add_argument("--kconfig", action="store_true", help="Enable kconfig flag")
parser.add_argument("--debug", action="store_true", required=False, help="Show unhandled expressions")
parser.add_argument("--parentscope", action="store_true", required=False, help="Additionaly set the variables in the parent scope")
args = parser.parse_args()
# The input must exist
if not os.path.exists(args.input):
fatal(f"Input {args.input} not found")
return args
def write_set_cmd(fout, expr, is_parent_scope):
fout.write(f'set({expr})\n')
# This makes the variable usable from the top level directory
if is_parent_scope == True:
fout.write(f'set({expr} PARENT_SCOPE)\n')
def generate_cmake_variables(path_input: str, path_output: str, kconfig: bool, debug: bool, is_parent_scope: bool):
fin = open(path_input)
fout = open(path_output, "w", newline='')
# If we use Kconfig, we must check the CONFIG_LV_USE_* and
# CONFIG_LV_BUILD_* defines
if kconfig:
CONFIG_PATTERN="^#define +(CONFIG_LV_USE|CONFIG_LV_BUILD)"
CONFIG_PREFIX=""
# Otherwise check the LV_USE_* and LV_BUILD_* defines
else:
CONFIG_PATTERN="^#define +(LV_USE|LV_BUILD)"
CONFIG_PREFIX="CONFIG_"
# Using the expanded lv_conf_internal, we don't have to deal with regexp,
# as all the #define will be aligned on the left with a single space before the value
for line in fin.read().splitlines():
# Treat the LV_USE_STDLIB_* configs in a special way, as we need
# to convert the define to full config with 1 value when enabled
if re.search(f'{CONFIG_PATTERN}_STDLIB', line):
parts = line.split()
if len(parts) < 3:
continue
name = parts[1]
value = parts[2].strip()
type = value.split("LV_STDLIB_")[1]
name = name.replace("STDLIB", type)
write_set_cmd(fout, f'{CONFIG_PREFIX}{name} 1', is_parent_scope)
# Treat the LV_USE_OS config in a special way, as we need
# to convert the define to full config with 1 value when enabled
if re.search(f'{CONFIG_PATTERN}_OS', line):
parts = line.split()
if len(parts) < 3:
continue
name = parts[1]
value = parts[2].strip()
type = value.split("LV_OS")[1]
name += type
write_set_cmd(fout, f'{CONFIG_PREFIX}{name} 1', is_parent_scope)
# For the rest of the configs, simply add CONFIG_ and write the name of the define
# all LV_USE_* or LV_BUILD_* configs where the value is 0 or 1,
# as these are the ones that are needed in cmake
# To detect the configuration of LVGL to perform conditional compilation/linking
elif re.search(f'{CONFIG_PATTERN}.* +[01] *$', line):
parts = line.split()
if len(parts) < 3:
continue
name = parts[1]
value = parts[2].strip()
write_set_cmd(fout, f'{CONFIG_PREFIX}{name} {value}', is_parent_scope)
else:
# Useful for debugging expressions that are unhandled,
# if the script fails in 'unexpected ways'
if debug == True:
print(f"DBG: Skipping expression: '{line} - not handled'")
if __name__ == '__main__':
args = get_args()
generate_cmake_variables(args.input, args.output, args.kconfig, args.debug, args.parentscope)

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python3
import os
import re
import argparse
DIR_SCRIPTS = os.path.dirname(__file__)
REPO_ROOT = os.path.join(DIR_SCRIPTS, "..")
DIR_CWD = os.getcwd()
def fatal(msg):
print()
print("ERROR! " + msg)
exit(1)
def get_args():
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=""
"Update the configuration file of your existing project based on default parameters and latest LVGL template. Eg.:\n"
" python3 generate_lv_conf.py ./src \n"
" python3 generate_lv_conf.py --template modules/lvgl/lv_conf_template.h ./my_config_folder"
)
parser.add_argument('--template', type=str, default=REPO_ROOT, nargs='?',
help='Path of "lv_conf_template.h" file or the folder containing it\n(optional, 1 folder above the python script by default)')
parser.add_argument('--config', type=str, default=None, nargs='?',
help='Path of "lv_conf.h" file (optional)')
parser.add_argument('--defaults', type=str, default=None, nargs='?',
help='Path of "lv_conf.defaults" file (optional)')
parser.add_argument('target', metavar='target', type=str, default=DIR_CWD, nargs='?',
help='Folder containing "lv_conf.h" and "lv_conf.defaults" files\n(optional, current work folder by default)')
args = parser.parse_args()
if os.path.isdir(args.template):
args.template = os.path.join(args.template, "lv_conf_template.h")
if not args.config:
args.config = os.path.join(args.target, "lv_conf.h")
if not args.defaults:
args.defaults = os.path.join(args.target, "lv_conf.defaults")
if not os.path.exists(args.template):
fatal(f"Template file not found at {args.template}")
if not os.path.exists(args.config):
fatal(f"User config file not found at {args.config}")
if not os.path.exists(args.defaults):
fatal(f"User defaults not found at {args.defaults}")
return args
def parse_defaults(path: str):
defaults = {}
with open(path, 'r', encoding='utf-8') as file:
for line in file.readlines():
if len(line.strip()) == 0 or line.startswith('#'):
continue
groups = re.search(r'([A-Z0-9_]+)\s+(.+)', line).groups()
defaults[groups[0]] = groups[1]
return defaults
def generate_config(path_destination: str, path_source: str, defaults: dict):
with open(path_source, 'r', encoding='utf-8') as f_src:
src_lines = f_src.readlines()
keys_used = set()
dst_lines = []
for src_line in src_lines:
res = re.search(r'#define\s+([A-Z0-9_]+)\s+(.+)', src_line)
key = res.groups()[0] if res else None
if key in defaults.keys():
value = defaults[key]
pattern = r'(#define\s+[A-Z0-9_]+\s+)(.+)'
repl = r'\g<1>' + value
dst_line, _ = re.subn(pattern, repl, src_line)
if not dst_line:
fatal(f"Failed to apply key '{key}' to line '{src_line}'")
print(f"Applying: {key} = {value}")
keys_used.add(key)
elif 'Set this to "1" to enable content' in src_line:
dst_line = '#if 1 /* Enable content */'
else:
dst_line = src_line
dst_lines.append(dst_line)
if len(keys_used) != len(defaults):
unused_keys = [k for k in defaults.keys() if k not in keys_used]
fatal('The following keys are deprecated:\n ' + '\n '.join(unused_keys))
with open(path_destination, 'w', encoding='utf-8') as f_dst:
for dst_line in dst_lines:
f_dst.write(dst_line)
if __name__ == '__main__':
args = get_args()
print("Template:", args.template)
print("User config:", args.config)
print("User defaults:", args.defaults)
defaults = parse_defaults(args.defaults)
print(f"Loaded {len(defaults)} defaults")
generate_config(args.config, args.template, defaults)
print()
print('New config successfully generated!')

View File

@@ -0,0 +1,15 @@
#!/bin/bash
echo "/* Autogenerated */"
echo '#include <stddef.h>'
echo '#include "examplelist.h"'
TMPFILE=$(mktemp)
find examples demos -name \*.h | xargs grep -hE "^void lv_(example|demo)" | sed 's/(/ /g' | awk '{print $2}' > $TMPFILE
cat $TMPFILE | while read -r line; do
echo "extern void ${line}(void);"
done
echo "const struct lv_ci_example lv_ci_example_list[] = {"
cat $TMPFILE | while read -r line; do
echo " { \"$line\", $line },";
done
echo " { NULL, NULL }"
echo "};"

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python3
from LVGLImage import LVGLImage
import argparse
import logging
import os
logging.basicConfig(level=logging.INFO)
parser = argparse.ArgumentParser(description="LVGL Binary Image Viewer")
parser.add_argument("file", help="the .bin image file")
args = parser.parse_args()
name, ext = os.path.splitext(args.file)
if ext != ".bin":
raise ValueError("Only support LVGL .bin image file")
output = name + ".png"
img = LVGLImage().from_bin(args.file)
img.to_png(output)
logging.info(f"convert {args.file} to {output}")
if os.name == "posix":
os.system(f"open {output}")
else:
try:
from PIL import Image
except ImportError:
raise ImportError("Need pillow package, do `pip3 install pillow`")
image = Image.open(output)
image.show(title=output)

View File

@@ -0,0 +1,10 @@
#!/bin/sh
# https://github.com/facebook/infer
#
# Install:
# VERSION=0.17.0; \
# curl -sSL "https://github.com/facebook/infer/releases/download/v$VERSION/infer-linux64-v$VERSION.tar.xz" \
# | sudo tar -C /opt -xJ && \
# sudoln -s "/opt/infer-linux64-v$VERSION/bin/infer" /usr/local/bin/infer
infer run -- make -j8

View File

@@ -0,0 +1,4 @@
vcpkg install vcpkg-tool-ninja libpng freetype opengl glfw3 glew
if %errorlevel% neq 0 exit /b %errorlevel%
pip install pypng lz4 kconfiglib
if %errorlevel% neq 0 exit /b %errorlevel%

View File

@@ -0,0 +1,15 @@
#!/bin/sh
# Install Linux package prerequisites needed for LVGL development
# and testing. Some less-common development packages are not included
# here, such as MicroPython and PC simulator packages.
#
# Note: This script is run by the CI workflows.
SCRIPT_PATH=$(readlink -f $0)
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
sudo dpkg --add-architecture i386
sudo apt update
cat $SCRIPT_DIR/prerequisites-apt.txt | xargs sudo apt install -y
pip3 install --user -r $SCRIPT_DIR/prerequisites-pip.txt

View File

@@ -0,0 +1,16 @@
#!/bin/sh
# Check if the script is being run as root
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root or with sudo" >&2
exit 1
fi
rm -rf astyle
git clone https://github.com/lvgl/astyle.git
cd astyle/build/gcc
git checkout v3.4.12
make -j
make install
cd ../../..
rm -rf astyle

View File

@@ -0,0 +1,11 @@
#!/bin/sh
rm -rf pngquant
git clone https://github.com/kornelski/pngquant
cd pngquant
git checkout 2.17.0
./configure
make -j
sudo make install
cd ..
rm -rf pngquant

View File

@@ -0,0 +1,140 @@
#!/usr/bin/env python3
##################################################################
# sjpeg converter script version 1.0
# Dependencies: (PYTHON-3)
##################################################################
SJPG_FILE_FORMAT_VERSION = "V1.00" #
JPEG_SPLIT_HEIGHT = 16
##################################################################
import math, os, sys, time
from PIL import Image
OUTPUT_FILE_NAME = ""
INPUT_FILE = ""
if len(sys.argv) == 2:
INPUT_FILE = sys.argv[1]
OUTPUT_FILE_NAME = INPUT_FILE.split("/")[-1].split("\\")[-1].split(".")[0]
else:
print("usage:\n\t python " + sys.argv[0] + " input_file.jpg")
sys.exit(0)
try:
im = Image.open(INPUT_FILE)
except:
print("\nFile not found!")
sys.exit(0)
print("\nConversion started...\n")
start_time = time.time()
width, height = im.size
print("Input:")
print("\t" + INPUT_FILE)
print("\tRES = " + str(width) + " x " + str(height) + '\n')
lenbuf = []
block_size = JPEG_SPLIT_HEIGHT;
splits = math.ceil(height/block_size)
c_code = '''//LVGL SJPG C ARRAY\n#include "lvgl/lvgl.h"\n\nconst uint8_t ''' + OUTPUT_FILE_NAME + '''_map[] = {\n'''
sjpeg_data = bytearray()
sjpeg = bytearray()
row_remaining = height;
for i in range(splits):
if row_remaining < block_size:
crop = im.crop((0, i*block_size, width, row_remaining + i*block_size))
else:
crop = im.crop((0, i*block_size, width, block_size + i*block_size))
row_remaining = row_remaining - block_size;
crop.save(str(i)+".jpg", quality=90)
for i in range(splits):
f = open(str(i)+".jpg", "rb")
a = f.read()
f.close()
sjpeg_data = sjpeg_data + a
lenbuf.append(len(a))
header = bytearray()
#4 BYTES
header = header + bytearray("_SJPG__".encode("UTF-8"));
#6 BYTES VERSION
header = header + bytearray(("\x00" + SJPG_FILE_FORMAT_VERSION + "\x00").encode("UTF-8"));
#WIDTH 2 BYTES
header = header + width.to_bytes(2, byteorder='little');
#HEIGHT 2 BYTES
header = header + height.to_bytes(2, byteorder='little');
#NUMBER OF ITEMS 2 BYTES
header = header + splits.to_bytes(2, byteorder='little');
#NUMBER OF ITEMS 2 BYTES
header = header + int(JPEG_SPLIT_HEIGHT).to_bytes(2, byteorder='little');
for item_len in lenbuf:
# WIDTH 2 BYTES
header = header + item_len.to_bytes(2, byteorder='little');
data = bytearray()
sjpeg = header + sjpeg_data;
if 1:
for i in range(len(lenbuf)):
os.remove(str(i) + ".jpg")
f = open(OUTPUT_FILE_NAME+".sjpg","wb");
f.write(sjpeg)
f.close()
new_line_threshold = 0
for i in range(len(sjpeg)):
c_code = c_code + "\t" + str(hex(sjpeg[i])) + ","
new_line_threshold = new_line_threshold + 1
if (new_line_threshold >= 16):
c_code = c_code + "\n"
new_line_threshold = 0
c_code = c_code + "\n};\n\nlv_image_dsc_t "
c_code = c_code + OUTPUT_FILE_NAME + " = {\n"
c_code = c_code + "\t.header.always_zero = 0,\n"
c_code = c_code + "\t.header.w = " + str(width) + ",\n"
c_code = c_code + "\t.header.h = " + str(height) + ",\n"
c_code = c_code + "\t.data_size = " + str(len(sjpeg)) + ",\n"
c_code = c_code + "\t.header.cf = LV_IMG_CF_RAW,\n"
c_code = c_code + "\t.data = " + OUTPUT_FILE_NAME+"_map" + ",\n};"
f = open(OUTPUT_FILE_NAME + '.c', 'w')
f.write(c_code)
f.close()
time_taken = (time.time() - start_time)
print("Output:")
print("\tTime taken = " + str(round(time_taken,2)) + " sec")
print("\tbin size = " + str(round(len(sjpeg)/1024, 1)) + " KB" )
print("\t" + OUTPUT_FILE_NAME + ".sjpg\t(bin file)" + "\n\t" + OUTPUT_FILE_NAME + ".c\t\t(c array)")
print("\nAll good!")

View File

@@ -0,0 +1,346 @@
#!/usr/bin/env python3
# Originally modified from:
# https://github.com/zephyrproject-rtos/zephyr/blob/main/scripts/kconfig/kconfig.py
# SPDX-License-Identifier: ISC
# Writes/updates the lvgl/.config configuration file by merging configuration
# files passed as arguments
#
# When fragments haven't changed, lvgl/.config is both the input and the
# output, which just updates it. This is handled in the CMake files.
#
# Also does various checks (most via Kconfiglib warnings).
import argparse
import os
import re
import sys
import textwrap
# Lvgl doesn't use tristate symbols. They're supported here just to make the
# script a bit more generic.
from kconfiglib import (
Kconfig,
split_expr,
expr_value,
expr_str,
BOOL,
TRISTATE,
TRI_TO_STR,
AND,
OR,
)
def main():
print(sys.argv)
args = parse_args()
print("Parsing " + args.kconfig_file)
kconf = Kconfig(args.kconfig_file, warn_to_stderr=False, suppress_traceback=True)
if args.handwritten_input_configs:
# Warn for assignments to undefined symbols, but only for handwritten
# fragments, to avoid warnings-turned-errors when using an old
# configuration file together with updated Kconfig files
kconf.warn_assign_undef = True
# prj.conf may override settings from the board configuration, so
# disable warnings about symbols being assigned more than once
kconf.warn_assign_override = False
kconf.warn_assign_redun = False
if args.forced_input_configs:
# Do not warn on a redundant config.
# The reason is that a regular .config will be followed by the forced
# config which under normal circumstances should be identical to the
# configured setting.
# Only if user has modified to a value that gets overruled by the forced
# a warning shall be issued.
kconf.warn_assign_redun = False
# Load files
print(kconf.load_config(args.configs_in[0]))
for config in args.configs_in[1:]:
# replace=False creates a merged configuration
print(kconf.load_config(config, replace=False))
if args.handwritten_input_configs:
# Check that there are no assignments to promptless symbols, which
# have no effect.
#
# This only makes sense when loading handwritten fragments and not when
# loading lvgl/.config, because lvgl/.config is configuration
# output and also assigns promptless symbols.
check_no_promptless_assign(kconf)
# Print warnings for symbols that didn't get the assigned value. Only
# do this for handwritten input too, to avoid likely unhelpful warnings
# when using an old configuration and updating Kconfig files.
check_assigned_sym_values(kconf)
check_assigned_choice_values(kconf)
if kconf.syms.get("WARN_DEPRECATED", kconf.y).tri_value == 2:
check_deprecated(kconf)
if kconf.syms.get("WARN_EXPERIMENTAL", kconf.y).tri_value == 2:
check_experimental(kconf)
# Hack: Force all symbols to be evaluated, to catch warnings generated
# during evaluation. Wait till the end to write the actual output files, so
# that we don't generate any output if there are warnings-turned-errors.
#
# Kconfiglib caches calculated symbol values internally, so this is still
# fast.
kconf.write_config(os.devnull)
warn_only = r"warning:.*set more than once."
if kconf.warnings:
if args.forced_input_configs:
error_out = False
else:
error_out = True
# Put a blank line between warnings to make them easier to read
for warning in kconf.warnings:
print("\n" + warning, file=sys.stderr)
if not error_out and not re.search(warn_only, warning):
# The warning is not a warn_only, fail the Kconfig.
error_out = True
# Turn all warnings into errors, so that e.g. assignments to undefined
# Kconfig symbols become errors.
#
# A warning is generated by this script whenever a symbol gets a
# different value than the one it was assigned. Keep that one as just a
# warning for now.
if error_out:
err("Aborting due to Kconfig warnings")
# Write the merged configuration and the C header
print(kconf.write_config(args.config_out))
print(kconf.write_autoconf(args.header_out))
# Write the list of parsed Kconfig files to a file
write_kconfig_filenames(kconf, args.kconfig_list_out)
def check_no_promptless_assign(kconf):
# Checks that no promptless symbols are assigned
for sym in kconf.unique_defined_syms:
if sym.user_value is not None and promptless(sym):
err(
f"""\
{sym.name_and_loc} is assigned in a configuration file, but is not directly
user-configurable (has no prompt). It gets its value indirectly from other
symbols. """
+ SYM_INFO_HINT.format(sym)
)
def check_assigned_sym_values(kconf):
# Verifies that the values assigned to symbols "took" (matches the value
# the symbols actually got), printing warnings otherwise. Choice symbols
# are checked separately, in check_assigned_choice_values().
for sym in kconf.unique_defined_syms:
if sym.choice:
continue
user_value = sym.user_value
if user_value is None:
continue
# Tristate values are represented as 0, 1, 2. Having them as "n", "m",
# "y" is more convenient here, so convert.
if sym.type in (BOOL, TRISTATE):
user_value = TRI_TO_STR[user_value]
if user_value != sym.str_value:
msg = (
f"{sym.name_and_loc} was assigned the value '{user_value}'"
f" but got the value '{sym.str_value}'. "
)
# List any unsatisfied 'depends on' dependencies in the warning
mdeps = missing_deps(sym)
if mdeps:
expr_strs = []
for expr in mdeps:
estr = expr_str(expr)
if isinstance(expr, tuple):
# Add () around dependencies that aren't plain symbols.
# Gives '(FOO || BAR) (=n)' instead of
# 'FOO || BAR (=n)', which might be clearer.
estr = f"({estr})"
expr_strs.append(f"{estr} " f"(={TRI_TO_STR[expr_value(expr)]})")
msg += (
"Check these unsatisfied dependencies: "
+ ", ".join(expr_strs)
+ ". "
)
warn(msg + SYM_INFO_HINT.format(sym))
def missing_deps(sym):
# check_assigned_sym_values() helper for finding unsatisfied dependencies.
#
# Given direct dependencies
#
# depends on <expr> && <expr> && ... && <expr>
#
# on 'sym' (which can also come from e.g. a surrounding 'if'), returns a
# list of all <expr>s with a value less than the value 'sym' was assigned
# ("less" instead of "not equal" just to be general and handle tristates,
# even though lvgl doesn't use them).
#
# For string/int/hex symbols, just looks for <expr> = n.
#
# Note that <expr>s can be something more complicated than just a symbol,
# like 'FOO || BAR' or 'FOO = "string"'.
deps = split_expr(sym.direct_dep, AND)
if sym.type in (BOOL, TRISTATE):
return [dep for dep in deps if expr_value(dep) < sym.user_value]
# string/int/hex
return [dep for dep in deps if expr_value(dep) == 0]
def check_assigned_choice_values(kconf):
# Verifies that any choice symbols that were selected (by setting them to
# y) ended up as the selection, printing warnings otherwise.
#
# We check choice symbols separately to avoid warnings when two different
# choice symbols within the same choice are set to y. This might happen if
# a choice selection from a board defconfig is overridden in a prj.conf,
# for example. The last choice symbol set to y becomes the selection (and
# all other choice symbols get the value n).
#
# Without special-casing choices, we'd detect that the first symbol set to
# y ended up as n, and print a spurious warning.
for choice in kconf.unique_choices:
if choice.user_selection and choice.user_selection is not choice.selection:
warn(
f"""\
The choice symbol {choice.user_selection.name_and_loc} was selected (set =y),
but {choice.selection.name_and_loc if choice.selection else "no symbol"} ended
up as the choice selection. """
+ SYM_INFO_HINT.format(choice.user_selection)
)
# Hint on where to find symbol information. Used like
# SYM_INFO_HINT.format(sym).
SYM_INFO_HINT = """\
See https://docs.lvgl.io/master/intro/add-lvgl-to-your-project/configuration.html
look up {0.name} in the menuconfig/guiconfig interface. The Application
Development Primer, Setting Configuration Values, and Kconfig - Tips and Best
Practices sections of the manual might be helpful too.\
"""
def check_deprecated(kconf):
deprecated = kconf.syms.get("DEPRECATED")
dep_expr = kconf.n if deprecated is None else deprecated.rev_dep
if dep_expr is not kconf.n:
selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2]
for selector in selectors:
selector_name = split_expr(selector, AND)[0].name
warn(f"Deprecated symbol {selector_name} is enabled.")
def check_experimental(kconf):
experimental = kconf.syms.get("EXPERIMENTAL")
dep_expr = kconf.n if experimental is None else experimental.rev_dep
if dep_expr is not kconf.n:
selectors = [s for s in split_expr(dep_expr, OR) if expr_value(s) == 2]
for selector in selectors:
selector_name = split_expr(selector, AND)[0].name
warn(f"Experimental symbol {selector_name} is enabled.")
def promptless(sym):
# Returns True if 'sym' has no prompt. Since the symbol might be defined in
# multiple locations, we need to check all locations.
return not any(node.prompt for node in sym.nodes)
def write_kconfig_filenames(kconf, kconfig_list_path):
# Writes a sorted list with the absolute paths of all parsed Kconfig files
# to 'kconfig_list_path'. The paths are realpath()'d, and duplicates are
# removed. This file is used by CMake to look for changed Kconfig files. It
# needs to be deterministic.
with open(kconfig_list_path, "w") as out:
for path in sorted(
{
os.path.realpath(os.path.join(kconf.srctree, path))
for path in kconf.kconfig_filenames
}
):
print(path, file=out)
def parse_args():
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument(
"--handwritten-input-configs",
action="store_true",
help="Assume the input configuration fragments are "
"handwritten fragments and do additional checks "
"on them, like no promptless symbols being "
"assigned",
)
parser.add_argument(
"--forced-input-configs",
action="store_true",
help="Indicate the input configuration files are "
"followed by an forced configuration file."
"The forced configuration is used to forcefully "
"set specific configuration settings to a "
"pre-defined value and thereby remove any user "
" adjustments.",
)
parser.add_argument("kconfig_file", help="Top-level Kconfig file")
parser.add_argument("config_out", help="Output configuration file")
parser.add_argument("header_out", help="Output header file")
parser.add_argument(
"kconfig_list_out", help="Output file for list of parsed Kconfig files"
)
parser.add_argument(
"configs_in",
nargs="+",
help="Input configuration fragments. Will be merged " "together.",
)
return parser.parse_args()
def warn(msg):
# Use a large fill() width to try to avoid linebreaks in the symbol
# reference link, and add some extra newlines to set the message off from
# surrounding text (this usually gets printed as part of spammy CMake
# output)
print("\n" + textwrap.fill("warning: " + msg, 100) + "\n", file=sys.stderr)
def err(msg):
sys.exit("\n" + textwrap.fill("error: " + msg, 100) + "\n")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python3
import sys
try:
import kconfiglib
except ImportError:
print("Need kconfiglib package, do `pip3 install kconfiglib`")
sys.exit(1)
def verify_kconfig(kconfig_file):
kconf = kconfiglib.Kconfig(kconfig_file)
if kconf.warnings:
print("Warnings found:")
for warning in kconf.warnings:
print(warning)
sys.exit(1)
else:
print("No warnings found.")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python check_kconfig.py <Kconfig_file>")
sys.exit(1)
verify_kconfig(sys.argv[1])

View File

@@ -0,0 +1,286 @@
#!/usr/bin/env python3
'''
Generates lv_conf_internal.h from lv_conf_template.h to provide default values
'''
import os
import sys
import re
SCRIPT_DIR = os.path.dirname(__file__)
LV_CONF_TEMPLATE = os.path.join(SCRIPT_DIR, "..", "lv_conf_template.h")
LV_CONF_INTERNAL = os.path.join(SCRIPT_DIR, "..", "src", "lv_conf_internal.h")
if sys.version_info < (3,6,0):
print("Python >=3.6 is required", file=sys.stderr)
exit(1)
def check_for_tabs(file_path):
errors = []
with open(file_path, 'r') as file:
for line_number, line in enumerate(file, 1):
if '\t' in line:
errors.append(f" {file_path}:{line_number}")
if errors:
print(f"Tabs found in the following files:", file=sys.stderr)
for error in errors:
print(error, file=sys.stderr)
print("Please replace tabs with spaces.", file=sys.stderr)
exit(1)
check_for_tabs(LV_CONF_TEMPLATE)
fin = open(LV_CONF_TEMPLATE)
fout = open(LV_CONF_INTERNAL, "w", newline='')
fout.write(
'''/**
* GENERATED FILE, DO NOT EDIT IT!
* @file lv_conf_internal.h
* This file ensures all defines of lv_conf.h have a default value.
*/
#ifndef LV_CONF_INTERNAL_H
#define LV_CONF_INTERNAL_H
/* clang-format off */
/* Config options */
#define LV_OS_NONE 0
#define LV_OS_PTHREAD 1
#define LV_OS_FREERTOS 2
#define LV_OS_CMSIS_RTOS2 3
#define LV_OS_RTTHREAD 4
#define LV_OS_WINDOWS 5
#define LV_OS_MQX 6
#define LV_OS_SDL2 7
#define LV_OS_CUSTOM 255
#define LV_STDLIB_BUILTIN 0
#define LV_STDLIB_CLIB 1
#define LV_STDLIB_MICROPYTHON 2
#define LV_STDLIB_RTTHREAD 3
#define LV_STDLIB_CUSTOM 255
#define LV_DRAW_SW_ASM_NONE 0
#define LV_DRAW_SW_ASM_NEON 1
#define LV_DRAW_SW_ASM_HELIUM 2
#define LV_DRAW_SW_ASM_CUSTOM 255
#define LV_NEMA_HAL_CUSTOM 0
#define LV_NEMA_HAL_STM32 1
/** Handle special Kconfig options. */
#ifndef LV_KCONFIG_IGNORE
#include "lv_conf_kconfig.h"
#if defined(CONFIG_LV_CONF_SKIP) && !defined(LV_CONF_SKIP)
#define LV_CONF_SKIP
#endif
#endif
/* If "lv_conf.h" is available from here try to use it later. */
#ifdef __has_include
#if __has_include("lv_conf.h")
#ifndef LV_CONF_INCLUDE_SIMPLE
#define LV_CONF_INCLUDE_SIMPLE
#endif
#endif
#endif
/* If lv_conf.h is not skipped, include it. */
#if !defined(LV_CONF_SKIP) || defined(LV_CONF_PATH)
#ifdef LV_CONF_PATH /* If there is a path defined for lv_conf.h, use it */
#include LV_CONF_PATH /* Note: Make sure to define custom CONF_PATH as a string */
#elif defined(LV_CONF_INCLUDE_SIMPLE) /* Or simply include lv_conf.h is enabled. */
#include "lv_conf.h"
#else
#include "../../lv_conf.h" /* Else assume lv_conf.h is next to the lvgl folder. */
#endif
#if !defined(LV_CONF_H) && !defined(LV_CONF_SUPPRESS_DEFINE_CHECK)
/* #include will sometimes silently fail when __has_include is used */
/* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80753 */
#pragma message("Possible failure to include lv_conf.h, please read the comment in this file if you get errors")
#endif
#endif
#ifdef CONFIG_LV_COLOR_DEPTH
#define LV_KCONFIG_PRESENT
#endif
/*----------------------------------
* Start parsing lv_conf_template.h
-----------------------------------*/
'''
)
started = 0
for line in fin.read().splitlines():
if not started:
if '#define LV_CONF_H' in line:
started = 1
continue
else:
continue
if '/*--END OF LV_CONF_H--*/' in line: break
#Is there a #define in this line?
r = re.search(r'^([\s]*)#[\s]*(undef|define)[\s]+([^\s]+).*$', line) # \s means any white space character
if r:
indent = r[1]
name = r[3]
name = re.sub(r'\(.*?\)', '', name, count=1) #remove parentheses from macros. E.g. MY_FUNC(5) -> MY_FUNC
line = re.sub(r'[\s]*', '', line, count=1)
#If the value should be 1 (enabled) by default use a more complex structure for Kconfig checks because
#if a not defined CONFIG_... value should be interpreted as 0 and not the LVGL default
is_one = re.search(r'#[\s]*define[\s]*[A-Z0-9_]+[\s]+1([\s]*$|[\s]+)', line)
if is_one:
#1. Use the value if already set from lv_conf.h or anything else (i.e. do nothing)
#2. In Kconfig environment use the CONFIG_... value if set, else use 0
#3. In not Kconfig environment use the LVGL's default value
fout.write(
f'{indent}#ifndef {name}\n'
f'{indent} #ifdef LV_KCONFIG_PRESENT\n'
f'{indent} #ifdef CONFIG_{name.upper()}\n'
f'{indent} #define {name} CONFIG_{name.upper()}\n'
f'{indent} #else\n'
f'{indent} #define {name} 0\n'
f'{indent} #endif\n'
f'{indent} #else\n'
f'{indent} {line}\n'
f'{indent} #endif\n'
f'{indent}#endif\n'
)
else:
#1. Use the value if already set from lv_conf.h or anything else (i.e. do nothing)
#2. Use the Kconfig value if set
#3. Use the LVGL's default value
fout.write(
f'{indent}#ifndef {name}\n'
f'{indent} #ifdef CONFIG_{name.upper()}\n'
f'{indent} #define {name} CONFIG_{name.upper()}\n'
f'{indent} #else\n'
f'{indent} {line}\n'
f'{indent} #endif\n'
f'{indent}#endif\n'
)
elif re.search('^ *typedef .*;.*$', line):
continue #ignore typedefs to avoid redeclaration
else:
fout.write(f'{line}\n')
fout.write(
r'''
/*----------------------------------
* End of parsing lv_conf_template.h
-----------------------------------*/
/*Fix inconsistent name*/
#define LV_USE_ANIMIMAGE LV_USE_ANIMIMG
#ifndef __ASSEMBLY__
LV_EXPORT_CONST_INT(LV_DPI_DEF);
LV_EXPORT_CONST_INT(LV_DRAW_BUF_STRIDE_ALIGN);
LV_EXPORT_CONST_INT(LV_DRAW_BUF_ALIGN);
#endif
#undef LV_KCONFIG_PRESENT
/* Set some defines if a dependency is disabled. */
#if LV_USE_LOG == 0
#define LV_LOG_LEVEL LV_LOG_LEVEL_NONE
#define LV_LOG_TRACE_MEM 0
#define LV_LOG_TRACE_TIMER 0
#define LV_LOG_TRACE_INDEV 0
#define LV_LOG_TRACE_DISP_REFR 0
#define LV_LOG_TRACE_EVENT 0
#define LV_LOG_TRACE_OBJ_CREATE 0
#define LV_LOG_TRACE_LAYOUT 0
#define LV_LOG_TRACE_ANIM 0
#endif /*LV_USE_LOG*/
#if LV_USE_WAYLAND == 0
#define LV_WAYLAND_USE_DMABUF 0
#define LV_WAYLAND_WINDOW_DECORATIONS 0
#define LV_WAYLAND_WL_SHELL 0
#endif /* LV_USE_WAYLAND */
#if LV_USE_SYSMON == 0
#define LV_USE_PERF_MONITOR 0
#define LV_USE_MEM_MONITOR 0
#endif /*LV_USE_SYSMON*/
#if LV_USE_PERF_MONITOR == 0
#define LV_USE_PERF_MONITOR_LOG_MODE 0
#endif /*LV_USE_PERF_MONITOR*/
#if LV_BUILD_DEMOS == 0
#define LV_USE_DEMO_WIDGETS 0
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
#define LV_USE_DEMO_BENCHMARK 0
#define LV_USE_DEMO_RENDER 0
#define LV_USE_DEMO_STRESS 0
#define LV_USE_DEMO_MUSIC 0
#define LV_USE_DEMO_VECTOR_GRAPHIC 0
#define LV_USE_DEMO_FLEX_LAYOUT 0
#define LV_USE_DEMO_MULTILANG 0
#define LV_USE_DEMO_TRANSFORM 0
#define LV_USE_DEMO_SCROLL 0
#define LV_USE_DEMO_EBIKE 0
#define LV_USE_DEMO_HIGH_RES 0
#define LV_USE_DEMO_SMARTWATCH 0
#endif /* LV_BUILD_DEMOS */
#ifndef LV_USE_LZ4
#if (LV_USE_LZ4_INTERNAL || LV_USE_LZ4_EXTERNAL)
#define LV_USE_LZ4 1
#else
#define LV_USE_LZ4 0
#endif
#endif
#ifndef LV_USE_THORVG
#if (LV_USE_THORVG_INTERNAL || LV_USE_THORVG_EXTERNAL)
#define LV_USE_THORVG 1
#else
#define LV_USE_THORVG 0
#endif
#endif
#if LV_USE_OS
#if (LV_USE_FREETYPE || LV_USE_THORVG) && LV_DRAW_THREAD_STACK_SIZE < (32 * 1024)
#warning "Increase LV_DRAW_THREAD_STACK_SIZE to at least 32KB for FreeType or ThorVG."
#endif
#if defined(LV_DRAW_THREAD_STACKSIZE) && !defined(LV_DRAW_THREAD_STACK_SIZE)
#warning "LV_DRAW_THREAD_STACKSIZE was renamed to LV_DRAW_THREAD_STACK_SIZE. Please update lv_conf.h or run menuconfig again."
#define LV_DRAW_THREAD_STACK_SIZE LV_DRAW_THREAD_STACKSIZE
#endif
#endif
/*Allow only upper case letters and '/' ('/' is a special case for backward compatibility)*/
#define LV_FS_IS_VALID_LETTER(l) ((l) == '/' || ((l) >= 'A' && (l) <= 'Z'))
/* If running without lv_conf.h, add typedefs with default value. */
#ifdef LV_CONF_SKIP
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) /*Disable warnings for Visual Studio*/
#define _CRT_SECURE_NO_WARNINGS
#endif
#endif /*defined(LV_CONF_SKIP)*/
#endif /*LV_CONF_INTERNAL_H*/
'''
)
fin.close()
fout.close()

View File

@@ -0,0 +1,192 @@
#!/usr/bin/env python3
#
# Preprocess the lv_conf_internal.h to generate a header file
# containing the evaluated definitions. This output will be used to
# generate the cmake variables
#
# Author: David TRUAN (david.truan@edgemtech.ch)
# Author: Erik Tagirov (erik.tagirov@edgemtech.ch)
#
import sys
import subprocess
import os
import argparse
import re
import importlib.util
def get_args():
parser = argparse.ArgumentParser(description="Preprocess a C header file and remove indentation.")
parser.add_argument("--input", help="Path to the input C header file", required=True)
parser.add_argument("--tmp_file", help="Path to save the preprocessed output", required=True)
parser.add_argument("--output", help="Path to save the cleaned output with removed indentation", required=True)
parser.add_argument("--workfolder", help="Path used to create a python environnement", required=True)
parser.add_argument(
"--defs",
nargs='+',
default=[],
help="Definitions to be added to pcpp (flag -D)"
)
parser.add_argument(
"--include",
nargs='+',
default=[],
help="Paths to include directories for the preprocessor (flag -I)"
)
return parser.parse_args()
def preprocess_file(pcpp_exe, input_file, tmp_file, output_file, include_dirs, defs):
try:
pcpp_command = [pcpp_exe, "-o", tmp_file, "--passthru-defines", "--line-directive=", input_file]
for include_path in include_dirs:
pcpp_command.append(f"-I{include_path}")
for definition in defs:
pcpp_command.append(f"-D{definition}")
subprocess.run(pcpp_command, check=True)
print(f"Preprocessing completed. Output saved to {tmp_file}")
except subprocess.CalledProcessError as e:
print(f"Error during preprocessing: {e}")
exit(1)
# Read the temporary files and return an array of lines
def read_lines(tmp_file):
try:
with open(tmp_file, "r") as f:
lines = f.readlines()
except Exception as e:
print(f"Failed to pre-process file: {e}")
exit(1)
return lines
def remove_indentation(lines):
clean_lines = []
try:
for line in lines:
stripped = line.lstrip()
# Remove extra spaces after #
if stripped.startswith("#"):
stripped = re.sub(r"^#\s+", "#", stripped)
clean_lines.append(stripped)
except Exception as e:
print(f"Error during indentation removal: {e}")
exit(1)
return clean_lines
# This is required - to avoid include errors when Kconfig is used and
# LVGL is installed on the system - i.e when lvgl.h is used as a system include
def add_include_guards(lines):
lines.insert(0, "#define LV_CONF_H\n\n")
lines.insert(0, "#ifndef LV_CONF_H\n")
lines.append("#endif /* END LV_CONF_H */\n")
return lines
def init_venv(venv_path):
"""
Creates a virtual env named .venv in the `workfolder` directory
It is usually set to the path of the build directory.
If the .venv already exists it enters the venv
Returns the path to the venv
"""
try:
if os.path.exists(venv_path) == False:
# Create venv
subprocess.check_call([sys.executable, "-m", "venv", venv_path])
# Enter venv
venv_path = os.path.join(venv_path, ".venv")
subprocess.check_call([sys.executable, "-m", "venv", venv_path])
except subprocess.CalledProcessError as e:
print(f"Error during the setup of python venv: {e}")
exit(1)
return venv_path
def install_pcpp_in_venv(venv_path):
"""
Install pcpp a python implementation of the C pre-processor
On success - Returns the path to pcpp, None on failure
"""
if sys.platform == "win32":
venv_pip = os.path.join(venv_path, "Scripts", "pip.exe")
venv_pcpp = os.path.join(venv_path, "Scripts", "pcpp.exe")
else:
venv_pip = os.path.join(venv_path, "bin", "pip")
venv_pcpp = os.path.join(venv_path, "bin", "pcpp")
if os.path.exists(venv_pcpp) == False:
# Install pcpp
try:
subprocess.check_call([venv_pip, "install", "pcpp"])
except subprocess.CalledProcessError as e:
print(f"Failed to install PCPP: {e}")
return None
else:
print("PCPP is already installed in venv")
return venv_pcpp
def main():
args = get_args()
# Check if PCPP is already present on the system
# if it's not - create a python venv inside the workfolder directory
# and install it there
res = subprocess.run(["which", "pcpp"], capture_output=True)
if res.returncode == 0:
pcpp_exe = res.stdout.decode().replace("\n", "")
print(f"Found PCPP: {pcpp_exe}")
else:
print("Failed to locate pcpp - installing it")
venv_path = init_venv(args.workfolder)
pcpp_exe = install_pcpp_in_venv(venv_path)
if pcpp_exe is None:
exit(1)
preprocess_file(pcpp_exe, args.input, args.tmp_file, args.output, args.include, args.defs)
lines = read_lines(args.tmp_file)
lines = remove_indentation(lines)
lines = add_include_guards(lines)
# Write the resulting output header file with include guards and no indentation
try:
with open(args.output, "w") as f:
f.writelines(lines)
except Exception as e:
print(f"Writing resulting file failed: {e}")
exit(1)
print(f"Expanded configuration header saved to {args.output}")
os.remove(args.tmp_file)
print(f"Temporary preprocessed file {args.tmp_file} removed.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,37 @@
git
gcc
gcc-multilib
g++-multilib
ninja-build
libpng-dev
libjpeg-turbo8-dev
libfreetype6-dev
libglew-dev
libglfw3-dev
libsdl2-dev
libpng-dev:i386
libjpeg-dev:i386
libfreetype6-dev:i386
ruby-full
gcovr
cmake
python3
python3-pip
libinput-dev
libxkbcommon-dev
libdrm-dev
pkg-config
wayland-protocols
libwayland-dev
libwayland-bin
libwayland-dev:i386
libxkbcommon-dev:i386
libudev-dev
libavformat-dev
libavcodec-dev
libswscale-dev
libavutil-dev
libavformat-dev:i386
libavcodec-dev:i386
libswscale-dev:i386
libavutil-dev:i386

View File

@@ -0,0 +1,3 @@
pypng
lz4
kconfiglib

View File

@@ -0,0 +1,217 @@
#!/usr/bin/env python3
import os
import re
import argparse
from collections import defaultdict
style_properties_type = {
"LV_STYLE_BG_COLOR": "LV_PROPERTY_TYPE_COLOR",
"LV_STYLE_BG_GRAD_COLOR": "LV_PROPERTY_TYPE_COLOR",
"LV_STYLE_BG_IMAGE_SRC": "LV_PROPERTY_TYPE_IMGSRC",
"LV_STYLE_BG_IMAGE_RECOLOR": "LV_PROPERTY_TYPE_COLOR",
"LV_STYLE_BORDER_COLOR": "LV_PROPERTY_TYPE_COLOR",
"LV_STYLE_OUTLINE_COLOR": "LV_PROPERTY_TYPE_COLOR",
"LV_STYLE_SHADOW_COLOR": "LV_PROPERTY_TYPE_COLOR",
"LV_STYLE_IMAGE_RECOLOR": "LV_PROPERTY_TYPE_COLOR",
"LV_STYLE_ARCH_IMAGE_SRC": "LV_PROPERTY_TYPE_IMGSRC",
"LV_STYLE_ARCH_COLOR": "LV_PROPERTY_TYPE_COLOR",
"LV_STYLE_TEXT_COLOR": "LV_PROPERTY_TYPE_COLOR",
"LV_STYLE_TEXT_FONT": "LV_PROPERTY_TYPE_FONT",
"LV_STYLE_LINE_COLOR": "LV_PROPERTY_TYPE_COLOR",
}
class Property:
def __init__(self, widget, name, type, index, id):
self.widget = widget
self.name = name
self.type = type
self.index = index
self.id = id
def find_headers(directory):
if os.path.isfile(directory):
yield directory
return
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.h'):
yield os.path.join(root, file)
def read_widget_properties(directory):
def match_properties(file_path):
pattern = r'^\s*LV_PROPERTY_ID2?\((\w+),\s*(\w+),\s*(\w+)(,\s*(\w+))?,\s*(\d+)\)'
with open(file_path, 'r') as file:
for line in file.readlines():
match = re.match(pattern, line)
if match:
id = f"LV_PROPERTY_{match.group(1).upper()}_{match.group(2).upper()}"
yield Property(
match.group(1).lower(),
match.group(2).lower(), match.group(3), match.group(4),
id)
def match_styles(file_path):
pattern = r'^\s+LV_STYLE_(\w+)\s*=\s*(\d+),'
with open(file_path, 'r') as file:
for line in file.readlines():
match = re.match(pattern, line)
if match:
name = match.group(1).upper()
id = f"LV_PROPERTY_STYLE_{name}"
yield Property("style",
match.group(1).lower(), "style",
match.group(2), id)
properties_by_widget = defaultdict(list)
for file_path in find_headers(directory):
for property in match_properties(file_path):
properties_by_widget[property.widget].append(property)
for property in match_styles(file_path):
properties_by_widget[property.widget].append(property)
for widget, properties in properties_by_widget.items():
# sort properties by property name
properties.sort(key=lambda x: x.name)
properties_by_widget[widget] = properties
return properties_by_widget
def write_widget_properties(output, properties_by_widget):
# Open header file for update.
with open(f'{output}/lv_obj_property_names.h', "w") as header:
header.write(f'''
/**
* @file lv_obj_property_names.h
* GENERATED FILE, DO NOT EDIT IT!
*/
#ifndef LV_OBJ_PROPERTY_NAMES_H
#define LV_OBJ_PROPERTY_NAMES_H
#include "../../misc/lv_types.h"
#if LV_USE_OBJ_PROPERTY && LV_USE_OBJ_PROPERTY_NAME
''')
for widget in sorted(properties_by_widget.keys()):
properties = properties_by_widget[widget]
file_name = f'lv_{widget}_properties.c'
output_file = f'{output}/{file_name}'
count = len(properties)
if widget == 'style':
include = "lv_style_properties.h"
guard = None
elif widget == "obj":
include = "../../core/lv_obj.h"
guard = None
else:
include = f'../{widget}/lv_{widget}.h'
guard = f"#if LV_USE_{widget.upper()}"
with open(output_file, 'w') as f:
f.write(f'''
/**
* GENERATED FILE, DO NOT EDIT IT!
* @file {file_name}
*/
#include "{include}"
#if LV_USE_OBJ_PROPERTY && LV_USE_OBJ_PROPERTY_NAME
{guard if guard else ""}
/**
* {widget.capitalize() + ' widget' if widget != 'style' else 'Style'} property names, name must be in order.
* Generated code from properties.py
*/
/* *INDENT-OFF* */
const lv_property_name_t lv_{widget}_property_names[{count}] = {{
''')
for property in properties:
name = property.name
name_str = '"' + name + '",'
f.write(f" {{{name_str :25} {property.id},}},\n")
f.write('};\n')
if guard:
f.write(f"#endif /*LV_USE_{widget.upper()}*/\n\n")
f.write("/* *INDENT-ON* */\n")
f.write('#endif\n')
header.write(
f' extern const lv_property_name_t lv_{widget}_property_names[{count}];\n'
)
header.write('#endif\n')
header.write('#endif\n')
def write_style_header(output, properties_by_widget):
properties = properties_by_widget['style']
output_file = f'{output}/lv_style_properties.h'
with open(output_file, 'w') as f:
f.write(f'''
/**
* GENERATED FILE, DO NOT EDIT IT!
* @file lv_style_properties.h
*/
#ifndef LV_STYLE_PROPERTIES_H
#define LV_STYLE_PROPERTIES_H
#include "../../core/lv_obj_property.h"
#if LV_USE_OBJ_PROPERTY
/* *INDENT-OFF* */
enum {{
''')
for property in properties:
name = property.name
id_type = style_properties_type.get(f"LV_STYLE_{name.upper()}",
"LV_PROPERTY_TYPE_INT")
f.write(
f" LV_PROPERTY_ID(STYLE, {name.upper() + ',' :25} {id_type+',' :28} LV_STYLE_{name.upper()}),\n"
)
f.write('};\n\n')
f.write('#endif\n')
f.write('#endif\n')
def main(directory, output):
property = read_widget_properties(directory)
write_widget_properties(output, property)
write_style_header(output, property)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Search files and filter lines.')
parser.add_argument('-d', '--directory',
help='Directory to lvgl root path')
parser.add_argument(
'-o', '--output', help='Folders to write generated properties for all widgets.')
args = parser.parse_args()
# default directory is the lvgl root path of where this script sits
if args.directory is None:
args.directory = os.path.join(os.path.dirname(__file__), "../")
if args.output is None:
args.output = os.path.join(args.directory, "src/widgets/property/")
# create output directory if it doesn't exist
os.makedirs(args.output, exist_ok=True)
main(args.directory, args.output)

View File

@@ -0,0 +1,260 @@
import subprocess
import re
import argparse
import os
import shutil
import sys
# v10.0.0 -> fail if release/v10.0 is not there
# v10.0.1 -> update release/v10.0
# v10.1.0 -> create release/v10.1 from release/v10.0 and update it
# v10.1.1 -> update release/v10.1
LOG = "[release_branch_updater.py]"
def git_repository(repository: str, token: str):
if not token:
return f"https://{repository}"
return f"https://{token}@{repository}"
def main():
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("--port-clone-tmpdir", default="port_tmpdir")
arg_parser.add_argument("--port-urls-path", default=os.path.join(os.path.dirname(__file__), "release_branch_updater_port_urls.txt"))
arg_parser.add_argument("--lvgl-path", default=os.path.join(os.path.dirname(__file__), ".."))
arg_parser.add_argument("--dry-run", action="store_true")
arg_parser.add_argument("--oldest-major", type=int)
arg_parser.add_argument("--github-token", type=str)
arg_parser.add_argument("--skip-master", action="store_true")
args = arg_parser.parse_args()
port_clone_tmpdir = args.port_clone_tmpdir
port_urls_path = args.port_urls_path
lvgl_path = args.lvgl_path
dry_run = args.dry_run
oldest_major = args.oldest_major
skip_master = args.skip_master
if not args.github_token and not dry_run:
print(LOG, "Warning: No github token was provided for this production run. Continuing anyway...")
lvgl_release_branches, lvgl_default_branch = get_release_branches(lvgl_path)
print(LOG, "LVGL release branches:", ", ".join(fmt_release(br) for br in lvgl_release_branches) or "(none)")
assert lvgl_default_branch is not None
print(LOG, "LVGL default branch:", lvgl_default_branch)
if oldest_major is not None:
lvgl_release_branches = [br for br in lvgl_release_branches if br[0] >= oldest_major]
print(LOG, 'LVGL release branches after "oldest-major" filter:',
", ".join(fmt_release(br) for br in lvgl_release_branches) or "(none)")
with open(port_urls_path) as f:
urls = f.read()
urls = [url for url in map(str.strip, urls.splitlines()) if url]
# ensure this script creates the directory i.e. it doesn't belong to the user since it will rm -rf at the end
assert not os.path.exists(port_clone_tmpdir), "the port clone tmpdir should not exist yet"
for url in urls:
print(LOG, "working with port:", url)
if dry_run:
port_clone_tmpdir = url[len("https://github.com/lvgl/"): ]
print("port_clone_tmpdir: " + port_clone_tmpdir)
# It's very important to not leak the github_token here
# So make sure the stdout and stderr are piped here
subprocess.run(("git", "clone",
git_repository(url.replace("https://", ""), args.github_token),
port_clone_tmpdir),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
port_release_branches, port_default_branch = get_release_branches(port_clone_tmpdir)
print(LOG, "port release branches:", ", ".join(fmt_release(br) for br in port_release_branches) or "(none)")
print(LOG, "port default branch:", port_default_branch if port_default_branch is not None else "(none)")
# we want to
# 1. create (if necessary) the port's release branch
# 2. update the LVGL submodule to match the LVGL's release branch version
# 3. update the lv_conf.h based on the lv_conf.defaults
branches_to_update = lvgl_release_branches
if not skip_master:
branches_to_update += [lvgl_default_branch]
# from oldest to newest release...
for lvgl_branch in branches_to_update:
if isinstance(lvgl_branch, tuple):
port_branch = lvgl_branch
print(LOG, f"attempting to update release branch {fmt_release(port_branch)} ...")
else:
port_branch = port_default_branch
if port_branch is None:
print(LOG, "the port does not have a default branch to update. continuing to next.")
continue
print(LOG, f"attempting to update default branch {port_branch} ...")
port_does_not_have_the_branch = False
port_submodule_was_updated = False
port_lv_conf_h_was_updated = False
# if the branch does not exist in the port, create it from
# the closest minor of the same major.
if port_branch in port_release_branches:
print(LOG, "... this port has a matching release branch.")
subprocess.run(("git", "-C", port_clone_tmpdir, "branch", "--track",
fmt_release(port_branch),
f"origin/{fmt_release(port_branch)}"))
elif port_branch != port_default_branch:
print(LOG, "... this port does not have this release branch minor ...")
port_does_not_have_the_branch = True
# get the port branch with this major and the next smallest minor
create_from = next((
br
for br in reversed(port_release_branches) # reverse it to get the newest (largest) minor
if br[0] == port_branch[0] # same major
and br[1] < port_branch[1] # smaller minor because exact minor does not exist
), None)
if create_from is None:
# there are no branches in the port that are this major
# version. One must be created manually.
print(LOG, "... this port has no major from which to create the minor. one must be created manually. continuing to next.")
continue
print(LOG, f"... creating the new branch {fmt_release(port_branch)} "
f"from {fmt_release(create_from)}")
res = subprocess.run(("git", "-C", port_clone_tmpdir, "branch",
fmt_release(port_branch), # new branch name
fmt_release(create_from))) # start point
if res.returncode != 0: continue
port_release_branches.append(port_branch)
port_release_branches.sort()
# checkout the same release in both LVGL and the port
subprocess.check_call(("git", "-C", lvgl_path, "checkout", f"origin/{fmt_release(lvgl_branch)}"))
subprocess.check_call(("git", "-C", port_clone_tmpdir, "checkout", fmt_release(port_branch)))
# update the submodule in the port if it exists
port_lvgl_submodule_path = None
if os.path.exists(os.path.join(port_clone_tmpdir, ".gitmodules")):
out = subprocess.check_output(("git", "-C", port_clone_tmpdir, "config", "--file",
".gitmodules", "--get-regexp", "path"))
port_lvgl_submodule_path = next((
line.partition("lvgl.path ")[2]
for line
in out.decode().strip().splitlines()
if "lvgl.path " in line
), None)
if port_lvgl_submodule_path is None:
print(LOG, "this port has no LVGL submodule")
else:
print(LOG, "lvgl submodule found in port at:", port_lvgl_submodule_path)
# get the SHA of LVGL in this release of LVGL
out = subprocess.check_output(("git", "-C", lvgl_path, "rev-parse", "--verify", "--quiet", "HEAD"))
lvgl_sha = out.decode().strip()
print(LOG, "the SHA of LVGL in this release should be:", lvgl_sha)
# get the SHA of LVGL this port wants to use in this release
out = subprocess.check_output(("git", "-C", port_clone_tmpdir, "rev-parse",
"--verify", "--quiet", f"HEAD:{port_lvgl_submodule_path}"))
port_lvgl_submodule_sha = out.decode().strip()
print(LOG, "the SHA of LVGL in the submodule of this port is:", port_lvgl_submodule_sha)
if lvgl_sha == port_lvgl_submodule_sha:
print(LOG, "the submodule's version of LVGL is already up to date")
else:
print(LOG, "the submodule's version of LVGL is NOT up to date")
port_submodule_was_updated = True
# update the version of the submodule in the index. no need to `git submodule update --init` it.
# also no need to `git add .` afterwards because it stages the change.
# 160000 is a git file mode which means submodule.
subprocess.check_call(("git", "-C", port_clone_tmpdir, "update-index", "--cacheinfo",
f"160000,{lvgl_sha},{port_lvgl_submodule_path}"))
# update the lv_conf.h if there's an lv_conf.defaults
out = subprocess.check_output(("find", ".", "-name", "lv_conf.defaults", "-print", "-quit"), cwd=port_clone_tmpdir)
port_lv_conf_defaults = next(iter(out.decode().strip().splitlines()), None)
if port_lv_conf_defaults is None:
print(LOG, "this port has no lv_conf.defaults")
else:
out = subprocess.check_output(("find", ".", "-name", "lv_conf.h", "-print", "-quit"), cwd=port_clone_tmpdir)
port_lv_conf_h = next(iter(out.decode().strip().splitlines()), None)
if port_lv_conf_h is None:
print(LOG, "this port has an lv_conf.defaults but no lv_conf.h")
else:
subprocess.check_call((sys.executable, os.path.join(lvgl_path, "scripts/generate_lv_conf.py"),
"--defaults", os.path.abspath(os.path.join(port_clone_tmpdir, port_lv_conf_defaults)),
"--config", os.path.abspath(os.path.join(port_clone_tmpdir, port_lv_conf_h)), ))
# check if lv_conf.h actually changed. it will not detect the submodule change as a false positive.
out = subprocess.check_output(("git", "-C", port_clone_tmpdir, "diff"))
diff = out.decode().strip()
if not diff:
print(LOG, "this port's lv_conf.h did NOT change")
else:
print(LOG, "this port's lv_conf.h changed")
port_lv_conf_h_was_updated = True
subprocess.check_call(("git", "-C", port_clone_tmpdir, "add", port_lv_conf_h))
out = subprocess.check_output(("git", "-C", port_clone_tmpdir, "diff"))
diff = out.decode().strip()
assert not diff
if port_does_not_have_the_branch or port_submodule_was_updated or port_lv_conf_h_was_updated:
print(LOG, "changes were made. ready to push.")
# keep it brief for commit message 50 character limit suggestion.
# max length will be 50 characters in this case: "bot: New branch. Update LVGL submodule. lv_conf.h."
commit_msg = ("bot:"
+ (" New branch." if port_does_not_have_the_branch else "")
+ (" Update LVGL submodule." if port_submodule_was_updated else "")
+ (" lv_conf.h." if port_lv_conf_h_was_updated else "")
)
print(LOG, f"commit message: '{commit_msg}'")
subprocess.check_call(("git", "-C", port_clone_tmpdir, "commit", "--allow-empty", "-m", commit_msg))
if dry_run:
print(LOG, "this is a dry run so nothing will be pushed")
else:
subprocess.check_call(("git", "-C", port_clone_tmpdir, "push", "origin", fmt_release(port_branch)))
print(LOG, "the changes were pushed.")
else:
print(LOG, "nothing to push for this release. it is up to date.")
if not dry_run:
shutil.rmtree(port_clone_tmpdir)
print(LOG, "port update complete:", url)
def get_release_branches(working_dir):
out = subprocess.check_output(("git", "-C", working_dir, "branch", "--quiet", "--format", "%(refname)", "--all"))
branches = out.decode().strip().splitlines()
release_versions = []
for branch_name in branches:
release_branch = re.fullmatch(r"refs/remotes/origin/release/v([0-9]+)\.([0-9]+)", branch_name)
if release_branch is None:
continue
release_versions.append((int(release_branch[1]), int(release_branch[2])))
release_versions.sort()
default_branch = None
if "refs/remotes/origin/master" in branches:
default_branch = "master"
elif "refs/remotes/origin/main" in branches:
default_branch = "main"
return release_versions, default_branch
def fmt_release(release_tuple):
return f"release/v{release_tuple[0]}.{release_tuple[1]}" if isinstance(release_tuple, tuple) else release_tuple
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,9 @@
https://github.com/lvgl/lv_port_linux
https://github.com/lvgl/lv_port_pc_eclipse
https://github.com/lvgl/lv_port_pc_vscode
https://github.com/lvgl/lv_port_renesas_ek-ra8d1_gcc
https://github.com/lvgl/lv_port_riverdi_stm32u5
https://github.com/lvgl/lv_nuttx
https://github.com/lvgl/lv_zephyr
https://github.com/lvgl/lv_esp_idf
https://github.com/lvgl/lv_port_actions_technology

View File

@@ -0,0 +1,759 @@
#!/usr/bin/env python3
import os
import re
import sys
props = [
{'section': 'Size and position', 'dsc':'Properties related to size, position, alignment and layout of Widgets.' },
{'name': 'WIDTH',
'style_type': 'num', 'var_type': 'int32_t' , 'default':'Widget dependent', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets width of Widget. Pixel, percentage and `LV_SIZE_CONTENT` values can be used. Percentage values are relative to the width of the parent's content area."},
{'name': 'MIN_WIDTH',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets a minimal width. Pixel and percentage values can be used. Percentage values are relative to the width of the parent's content area."},
{'name': 'MAX_WIDTH',
'style_type': 'num', 'var_type': 'int32_t' , 'default':'LV_COORD_MAX', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets a maximal width. Pixel and percentage values can be used. Percentage values are relative to the width of the parent's content area."},
{'name': 'HEIGHT',
'style_type': 'num', 'var_type': 'int32_t' , 'default':'Widget dependent', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets height of Widget. Pixel, percentage and `LV_SIZE_CONTENT` can be used. Percentage values are relative to the height of the parent's content area."},
{'name': 'MIN_HEIGHT',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets a minimal height. Pixel and percentage values can be used. Percentage values are relative to the width of the parent's content area."},
{'name': 'MAX_HEIGHT',
'style_type': 'num', 'var_type': 'int32_t' , 'default':'LV_COORD_MAX', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets a maximal height. Pixel and percentage values can be used. Percentage values are relative to the height of the parent's content area."},
{'name': 'LENGTH',
'style_type': 'num', 'var_type': 'int32_t' , 'default':'0', 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Its meaning depends on the type of Widget. For example in case of lv_scale it means the length of the ticks."},
{'name': 'X',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set X coordinate of Widget considering the ``align`` setting. Pixel and percentage values can be used. Percentage values are relative to the width of the parent's content area."},
{'name': 'Y',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set Y coordinate of Widget considering the ``align`` setting. Pixel and percentage values can be used. Percentage values are relative to the height of the parent's content area."},
{'name': 'ALIGN',
'style_type': 'num', 'var_type': 'lv_align_t', 'default':'`LV_ALIGN_DEFAULT`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set the alignment which tells from which point of the parent the X and Y coordinates should be interpreted. Possible values are: `LV_ALIGN_DEFAULT`, `LV_ALIGN_TOP_LEFT/MID/RIGHT`, `LV_ALIGN_BOTTOM_LEFT/MID/RIGHT`, `LV_ALIGN_LEFT/RIGHT_MID`, `LV_ALIGN_CENTER`. `LV_ALIGN_DEFAULT` means `LV_ALIGN_TOP_LEFT` with LTR base direction and `LV_ALIGN_TOP_RIGHT` with RTL base direction."},
{'name': 'TRANSFORM_WIDTH',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Make Widget wider on both sides with this value. Pixel and percentage (with `lv_pct(x)`) values can be used. Percentage values are relative to Widget's width." },
{'name': 'TRANSFORM_HEIGHT',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Make Widget higher on both sides with this value. Pixel and percentage (with `lv_pct(x)`) values can be used. Percentage values are relative to Widget's height." },
{'name': 'TRANSLATE_X',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Move Widget with this value in X direction. Applied after layouts, aligns and other positioning. Pixel and percentage (with `lv_pct(x)`) values can be used. Percentage values are relative to Widget's width." },
{'name': 'TRANSLATE_Y',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Move Widget with this value in Y direction. Applied after layouts, aligns and other positioning. Pixel and percentage (with `lv_pct(x)`) values can be used. Percentage values are relative to Widget's height." },
{'name': 'TRANSLATE_RADIAL',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Move object around the centre of the parent object (e.g. around the circumference of a scale)"},
{'name': 'TRANSFORM_SCALE_X',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 1,
'dsc': "Zoom Widget horizontally. The value 256 (or `LV_SCALE_NONE`) means normal size, 128 half size, 512 double size, and so on" },
{'name': 'TRANSFORM_SCALE_Y',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 1,
'dsc': "Zoom Widget vertically. The value 256 (or `LV_SCALE_NONE`) means normal size, 128 half size, 512 double size, and so on" },
{'name': 'TRANSFORM_ROTATION',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 1,
'dsc': "Rotate Widget. The value is interpreted in 0.1 degree units. E.g. 450 means 45 deg."},
{'name': 'TRANSFORM_PIVOT_X',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set pivot point's X coordinate for transformations. Relative to Widget's top left corner'"},
{'name': 'TRANSFORM_PIVOT_Y',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set pivot point's Y coordinate for transformations. Relative to Widget's top left corner'"},
{'name': 'TRANSFORM_SKEW_X',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 1,
'dsc': "Skew Widget horizontally. The value is interpreted in 0.1 degree units. E.g. 450 means 45 deg."},
{'name': 'TRANSFORM_SKEW_Y',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 1,
'dsc': "Skew Widget vertically. The value is interpreted in 0.1 degree units. E.g. 450 means 45 deg."},
{'section': 'Padding', 'dsc' : "Properties to describe spacing between the parent's sides and the children and among the children. Very similar to the padding properties in HTML."},
{'name': 'PAD_TOP',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets the padding on the top. It makes the content area smaller in this direction."},
{'name': 'PAD_BOTTOM',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets the padding on the bottom. It makes the content area smaller in this direction."},
{'name': 'PAD_LEFT',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets the padding on the left. It makes the content area smaller in this direction."},
{'name': 'PAD_RIGHT',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets the padding on the right. It makes the content area smaller in this direction."},
{'name': 'PAD_ROW',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets the padding between the rows. Used by the layouts."},
{'name': 'PAD_COLUMN',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets the padding between the columns. Used by the layouts."},
{'name': 'PAD_RADIAL',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Pad text labels away from the scale ticks/remainder of the ``LV_PART_``"},
{'section': 'Margin', 'dsc' : "Properties to describe spacing around a Widget. Very similar to the margin properties in HTML."},
{'name': 'MARGIN_TOP',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets margin on the top. Widget will keep this space from its siblings in layouts."},
{'name': 'MARGIN_BOTTOM',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets margin on the bottom. Widget will keep this space from its siblings in layouts."},
{'name': 'MARGIN_LEFT',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets margin on the left. Widget will keep this space from its siblings in layouts."},
{'name': 'MARGIN_RIGHT',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Sets margin on the right. Widget will keep this space from its siblings in layouts."},
{'section': 'Background', 'dsc':'Properties to describe the background color and image of Widget.' },
{'name': 'BG_COLOR',
'style_type': 'color', 'var_type': 'lv_color_t', 'default':'`0xffffff`', 'inherited': 0, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Set background color of Widget."},
{'name': 'BG_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t', 'default':'`LV_OPA_TRANSP`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set opacity of the background. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency."},
{'name': 'BG_GRAD_COLOR',
'style_type': 'color', 'var_type': 'lv_color_t', 'default':'`0x000000`', 'inherited': 0, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Set gradient color of the background. Used only if `grad_dir` is not `LV_GRAD_DIR_NONE`"},
{'name': 'BG_GRAD_DIR',
'style_type': 'num', 'var_type': 'lv_grad_dir_t', 'default':'`LV_GRAD_DIR_NONE`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set direction of the gradient of the background. Possible values are `LV_GRAD_DIR_NONE/HOR/VER`."},
{'name': 'BG_MAIN_STOP',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set point from which background color should start for gradients. 0 means to top/left side, 255 the bottom/right side, 128 the center, and so on"},
{'name': 'BG_GRAD_STOP',
'style_type': 'num', 'var_type': 'int32_t', 'default':255, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set point from which background's gradient color should start. 0 means to top/left side, 255 the bottom/right side, 128 the center, and so on"},
{'name': 'BG_MAIN_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t', 'default':255, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set opacity of the first gradient color"},
{'name': 'BG_GRAD_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t', 'default':255, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set opacity of the second gradient color"},
{'name': 'BG_GRAD',
'style_type': 'ptr', 'var_type': 'const lv_grad_dsc_t *', 'default':'`NULL`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set gradient definition. The pointed instance must exist while Widget is alive. NULL to disable. It wraps `BG_GRAD_COLOR`, `BG_GRAD_DIR`, `BG_MAIN_STOP` and `BG_GRAD_STOP` into one descriptor and allows creating gradients with more colors as well. If it's set other gradient related properties will be ignored'"},
{'name': 'BG_IMAGE_SRC',
'style_type': 'ptr', 'var_type': 'const void *', 'default':'`NULL`', 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set a background image. Can be a pointer to `lv_image_dsc_t`, a path to a file or an `LV_SYMBOL_...`"},
{'name': 'BG_IMAGE_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t', 'default':'`LV_OPA_COVER`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set opacity of the background image. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency."},
{'name': 'BG_IMAGE_RECOLOR',
'style_type': 'color', 'var_type': 'lv_color_t', 'default':'`0x000000`', 'inherited': 0, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Set a color to mix to the background image."},
{'name': 'BG_IMAGE_RECOLOR_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t', 'default':'`LV_OPA_TRANSP`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set intensity of background image recoloring. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means no mixing, 255, `LV_OPA_100` or `LV_OPA_COVER` means full recoloring, other values or LV_OPA_10, LV_OPA_20, etc are interpreted proportionally."},
{'name': 'BG_IMAGE_TILED',
'style_type': 'num', 'var_type': 'bool', 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "If enabled the background image will be tiled. Possible values are `true` or `false`."},
{'section': 'Border', 'dsc':'Properties to describe the borders' },
{'name': 'BORDER_COLOR',
'style_type': 'color', 'var_type': 'lv_color_t', 'default':'`0x000000`', 'inherited': 0, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Set color of the border"},
{'name': 'BORDER_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t' , 'default':'`LV_OPA_COVER`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set opacity of the border. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency."},
{'name': 'BORDER_WIDTH',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set width of the border. Only pixel values can be used."},
{'name': 'BORDER_SIDE',
'style_type': 'num', 'var_type': 'lv_border_side_t', 'default':'`LV_BORDER_SIDE_NONE`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set only which side(s) the border should be drawn. Possible values are `LV_BORDER_SIDE_NONE/TOP/BOTTOM/LEFT/RIGHT/INTERNAL`. OR-ed values can be used as well, e.g. `LV_BORDER_SIDE_TOP | LV_BORDER_SIDE_LEFT`."},
{'name': 'BORDER_POST',
'style_type': 'num', 'var_type': 'bool' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Sets whether the border should be drawn before or after the children are drawn. `true`: after children, `false`: before children"},
{'section': 'Outline', 'dsc':'Properties to describe the outline. It\'s like a border but drawn outside of the rectangles.' },
{'name': 'OUTLINE_WIDTH',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set width of outline in pixels."},
{'name': 'OUTLINE_COLOR',
'style_type': 'color', 'var_type': 'lv_color_t' , 'default':'`0x000000`', 'inherited': 0, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Set color of outline."},
{'name': 'OUTLINE_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t' , 'default':'`LV_OPA_COVER`', 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set opacity of outline. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency."},
{'name': 'OUTLINE_PAD',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set padding of outline, i.e. the gap between Widget and the outline."},
{'section': 'Shadow', 'dsc':'Properties to describe the shadow drawn under the rectangles.' },
{'name': 'SHADOW_WIDTH',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set width of the shadow in pixels. The value should be >= 0."},
{'name': 'SHADOW_OFFSET_X',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set an offset on the shadow in pixels in X direction."},
{'name': 'SHADOW_OFFSET_Y',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set an offset on the shadow in pixels in Y direction."},
{'name': 'SHADOW_SPREAD',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Make shadow calculation to use a larger or smaller rectangle as base. The value can be in pixels to make the area larger/smaller"},
{'name': 'SHADOW_COLOR',
'style_type': 'color', 'var_type': 'lv_color_t' , 'default':'`0x000000`', 'inherited': 0, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Set color of shadow"},
{'name': 'SHADOW_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t' , 'default':'`LV_OPA_COVER`', 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set opacity of shadow. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency."},
{'section': 'Image', 'dsc':'Properties to describe the images' },
{'name': 'IMAGE_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t' , 'default':'`LV_OPA_COVER`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set opacity of an image. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency."},
{'name': 'IMAGE_RECOLOR',
'style_type': 'color', 'var_type': 'lv_color_t', 'default':'`0x000000`', 'inherited': 0, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Set color to mix with the image."},
{'name': 'IMAGE_RECOLOR_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set intensity of color mixing. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency."},
{'section': 'Line', 'dsc':'Properties to describe line-like Widgets' },
{'name': 'LINE_WIDTH',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set width of lines in pixels."},
{'name': 'LINE_DASH_WIDTH',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set width of dashes in pixels. Note that dash works only on horizontal and vertical lines"},
{'name': 'LINE_DASH_GAP',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set gap between dashes in pixels. Note that dash works only on horizontal and vertical lines"},
{'name': 'LINE_ROUNDED',
'style_type': 'num', 'var_type': 'bool' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Make end points of the lines rounded. `true`: rounded, `false`: perpendicular line ending"},
{'name': 'LINE_COLOR',
'style_type': 'color', 'var_type': 'lv_color_t' , 'default':'`0x000000`', 'inherited': 0, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Set color of lines."},
{'name': 'LINE_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t' , 'default':'`LV_OPA_COVER`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set opacity of lines."},
{'section': 'Arc', 'dsc':'TODO' },
{'name': 'ARC_WIDTH',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set width (thickness) of arcs in pixels."},
{'name': 'ARC_ROUNDED',
'style_type': 'num', 'var_type': 'bool' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Make end points of arcs rounded. `true`: rounded, `false`: perpendicular line ending"},
{'name': 'ARC_COLOR',
'style_type': 'color', 'var_type': 'lv_color_t', 'default':'`0x000000`', 'inherited': 0, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Set color of arc."},
{'name': 'ARC_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t' , 'default':'`LV_OPA_COVER`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set opacity of arcs."},
{'name': 'ARC_IMAGE_SRC',
'style_type': 'ptr', 'var_type': 'const void *', 'default':'`NULL`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set an image from which arc will be masked out. It's useful to display complex effects on the arcs. Can be a pointer to `lv_image_dsc_t` or a path to a file"},
{'section': 'Text', 'dsc':'Properties to describe the properties of text. All these properties are inherited.' },
{'name': 'TEXT_COLOR',
'style_type': 'color', 'var_type': 'lv_color_t', 'default':'`0x000000`', 'inherited': 1, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Sets color of text."},
{'name': 'TEXT_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t', 'default':'`LV_OPA_COVER`', 'inherited': 1, 'layout': 0, 'ext_draw': 0,
'dsc': "Set opacity of text. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency."},
{'name': 'TEXT_FONT',
'style_type': 'ptr', 'var_type': 'const lv_font_t *', 'default':'`LV_FONT_DEFAULT`', 'inherited': 1, 'layout': 1, 'ext_draw': 0,
'dsc': "Set font of text (a pointer `lv_font_t *`)."},
{'name': 'TEXT_LETTER_SPACE',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 1, 'layout': 1, 'ext_draw': 0,
'dsc': "Set letter space in pixels"},
{'name': 'TEXT_LINE_SPACE',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 1, 'layout': 1, 'ext_draw': 0,
'dsc': "Set line space in pixels."},
{'name': 'TEXT_DECOR',
'style_type': 'num', 'var_type': 'lv_text_decor_t' , 'default':'`LV_TEXT_DECOR_NONE`', 'inherited': 1, 'layout': 0, 'ext_draw': 0,
'dsc': "Set decoration for the text. Possible values are `LV_TEXT_DECOR_NONE/UNDERLINE/STRIKETHROUGH`. OR-ed values can be used as well." },
{'name': 'TEXT_ALIGN',
'style_type': 'num', 'var_type': 'lv_text_align_t' , 'default':'`LV_TEXT_ALIGN_AUTO`', 'inherited': 1, 'layout': 1, 'ext_draw': 0,
'dsc': "Set how to align the lines of the text. Note that it doesn't align the Widget itself, only the lines inside the Widget. Possible values are `LV_TEXT_ALIGN_LEFT/CENTER/RIGHT/AUTO`. `LV_TEXT_ALIGN_AUTO` detect the text base direction and uses left or right alignment accordingly"},
{'name': 'TEXT_OUTLINE_STROKE_COLOR',
'style_type': 'color', 'var_type': 'lv_color_t', 'default':'`0x000000`', 'inherited': 1, 'layout': 0, 'ext_draw': 0, 'filtered': 1,
'dsc': "Sets the color of letter outline stroke."},
{'name': 'TEXT_OUTLINE_STROKE_WIDTH',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 1, 'layout': 1, 'ext_draw': 0,
'dsc': "Set the letter outline stroke width in pixels."},
{'name': 'TEXT_OUTLINE_STROKE_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t', 'default':'`LV_OPA_COVER`', 'inherited': 1, 'layout': 0, 'ext_draw': 0,
'dsc': "Set the opacity of the letter outline stroke. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency."},
{'section': 'Miscellaneous', 'dsc':'Mixed properties for various purposes.' },
{'name': 'RADIUS',
'style_type': 'num', 'var_type': 'int32_t', 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set radius on every corner. The value is interpreted in pixels (>= 0) or `LV_RADIUS_CIRCLE` for max. radius"},
{'name': 'RADIAL_OFFSET',
'style_type': 'num', 'var_type': 'int32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Move start point of object (e.g. scale tick) radially"},
{'name': 'CLIP_CORNER',
'style_type': 'num', 'var_type': 'bool', 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Enable to clip the overflowed content on the rounded corner. Can be `true` or `false`." },
{'name': 'OPA',
'style_type': 'num', 'var_type': 'lv_opa_t', 'default':'`LV_OPA_COVER`', 'inherited': 1, 'layout': 0, 'ext_draw': 0,
'dsc': "Scale down all opacity values of the Widget by this factor. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency." },
{'name': 'OPA_LAYERED',
'style_type': 'num', 'var_type': 'lv_opa_t', 'default':'`LV_OPA_COVER`', 'inherited': 1, 'layout': 0, 'ext_draw': 0,
'dsc': "First draw Widget on the layer, then scale down layer opacity factor. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency." },
{'name': 'COLOR_FILTER_DSC',
'style_type': 'ptr', 'var_type': 'const lv_color_filter_dsc_t *', 'default':'`NULL`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Mix a color with all colors of the Widget." },
{'name': 'COLOR_FILTER_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t' , 'default':'`LV_OPA_TRANSP`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "The intensity of mixing of color filter."},
{'name': 'RECOLOR',
'style_type': 'color', 'var_type': 'lv_color_t', 'default':'`0x000000`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set a color to mix to the obj."},
{'name': 'RECOLOR_OPA',
'style_type': 'num', 'var_type': 'lv_opa_t', 'default':'`LV_OPA_TRANSP`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set intensity of color mixing. Value 0, `LV_OPA_0` or `LV_OPA_TRANSP` means fully transparent, 255, `LV_OPA_100` or `LV_OPA_COVER` means fully covering, other values or LV_OPA_10, LV_OPA_20, etc means semi transparency."},
{'name': 'ANIM',
'style_type': 'ptr', 'var_type': 'const lv_anim_t *', 'default':'`NULL`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Animation template for Widget's animation. Should be a pointer to `lv_anim_t`. The animation parameters are widget specific, e.g. animation time could be the E.g. blink time of the cursor on the Text Area or scroll time of a roller. See Widgets' documentation to learn more."},
{'name': 'ANIM_DURATION',
'style_type': 'num', 'var_type': 'uint32_t' , 'default':0, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Animation duration in milliseconds. Its meaning is widget specific. E.g. blink time of the cursor on the Text Area or scroll time of a roller. See Widgets' documentation to learn more."},
{'name': 'TRANSITION',
'style_type': 'ptr', 'var_type': 'const lv_style_transition_dsc_t *' , 'default':'`NULL`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "An initialized ``lv_style_transition_dsc_t`` to describe a transition."},
{'name': 'BLEND_MODE',
'style_type': 'num', 'var_type': 'lv_blend_mode_t' , 'default':'`LV_BLEND_MODE_NORMAL`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Describes how to blend the colors to the background. Possible values are `LV_BLEND_MODE_NORMAL/ADDITIVE/SUBTRACTIVE/MULTIPLY/DIFFERENCE`"},
{'name': 'LAYOUT',
'style_type': 'num', 'var_type': 'uint16_t', 'default':0, 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set layout of Widget. Children will be repositioned and resized according to policies set for the layout. For possible values see documentation of the layouts."},
{'name': 'BASE_DIR',
'style_type': 'num', 'var_type': 'lv_base_dir_t', 'default':'`LV_BASE_DIR_AUTO`', 'inherited': 1, 'layout': 1, 'ext_draw': 0,
'dsc': "Set base direction of Widget. Possible values are `LV_BIDI_DIR_LTR/RTL/AUTO`."},
{'name': 'BITMAP_MASK_SRC',
'style_type': 'ptr', 'var_type': 'const void *', 'default':'`NULL`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "If set, a layer will be created for the widget and the layer will be masked with this A8 bitmap mask."},
{'name': 'ROTARY_SENSITIVITY',
'style_type': 'num', 'var_type': 'uint32_t', 'default':'`256`', 'inherited': 1, 'layout': 0, 'ext_draw': 0,
'dsc': "Adjust sensitivity for rotary encoders in 1/256 unit. It means, 128: slow down the rotary to half, 512: speeds up to double, 256: no change"},
{'section': 'Flex', 'dsc':'Flex layout properties.', 'guard':'LV_USE_FLEX'},
{'name': 'FLEX_FLOW',
'style_type': 'num', 'var_type': 'lv_flex_flow_t', 'default':'`LV_FLEX_FLOW_NONE`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Defines in which direct the flex layout should arrange the children"},
{'name': 'FLEX_MAIN_PLACE',
'style_type': 'num', 'var_type': 'lv_flex_align_t', 'default':'`LV_FLEX_ALIGN_NONE`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Defines how to align the children in the direction of flex flow"},
{'name': 'FLEX_CROSS_PLACE',
'style_type': 'num', 'var_type': 'lv_flex_align_t', 'default':'`LV_FLEX_ALIGN_NONE`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Defines how to align the children perpendicular to the direction of flex flow"},
{'name': 'FLEX_TRACK_PLACE',
'style_type': 'num', 'var_type': 'lv_flex_align_t', 'default':'`LV_FLEX_ALIGN_NONE`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Defines how to align the tracks of the flow"},
{'name': 'FLEX_GROW',
'style_type': 'num', 'var_type': 'uint8_t', 'default':'`LV_FLEX_ALIGN_ROW`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Defines how much space to take proportionally from the free space of the Widget's track"},
{'section': 'Grid', 'dsc':'Grid layout properties.', 'guard':'LV_USE_GRID'},
{'name': 'GRID_COLUMN_DSC_ARRAY',
'style_type': 'ptr', 'var_type': 'const int32_t *', 'default':'`NULL`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "An array to describe the columns of the grid. Should be LV_GRID_TEMPLATE_LAST terminated"},
{'name': 'GRID_COLUMN_ALIGN',
'style_type': 'num', 'var_type': 'lv_grid_align_t', 'default':'`LV_GRID_ALIGN_START`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Defines how to distribute the columns"},
{'name': 'GRID_ROW_DSC_ARRAY',
'style_type': 'ptr', 'var_type': 'const int32_t *', 'default':'`NULL`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "An array to describe the rows of the grid. Should be LV_GRID_TEMPLATE_LAST terminated"},
{'name': 'GRID_ROW_ALIGN',
'style_type': 'num', 'var_type': 'lv_grid_align_t', 'default':'`LV_GRID_ALIGN_START`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Defines how to distribute the rows."},
{'name': 'GRID_CELL_COLUMN_POS',
'style_type': 'num', 'var_type': 'int32_t', 'default':'`LV_GRID_ALIGN_START`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set column in which Widget should be placed."},
{'name': 'GRID_CELL_X_ALIGN',
'style_type': 'num', 'var_type': 'lv_grid_align_t', 'default':'`LV_GRID_ALIGN_START`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set how to align Widget horizontally."},
{'name': 'GRID_CELL_COLUMN_SPAN',
'style_type': 'num', 'var_type': 'int32_t', 'default':'`LV_GRID_ALIGN_START`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set how many columns Widget should span. Needs to be >= 1."},
{'name': 'GRID_CELL_ROW_POS',
'style_type': 'num', 'var_type': 'int32_t', 'default':'`LV_GRID_ALIGN_START`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set row in which Widget should be placed."},
{'name': 'GRID_CELL_Y_ALIGN',
'style_type': 'num', 'var_type': 'lv_grid_align_t', 'default':'`LV_GRID_ALIGN_START`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set how to align Widget vertically."},
{'name': 'GRID_CELL_ROW_SPAN',
'style_type': 'num', 'var_type': 'int32_t', 'default':'`LV_GRID_ALIGN_START`', 'inherited': 0, 'layout': 1, 'ext_draw': 0,
'dsc': "Set how many rows Widget should span. Needs to be >= 1."},
]
def style_get_cast(style_type, var_type):
cast = ""
if style_type != 'color':
cast = "(" + var_type + ")"
return cast
def obj_style_get(p):
if 'section' in p: return
cast = style_get_cast(p['style_type'], p['var_type'])
print("static inline " + p['var_type'] + " lv_obj_get_style_" + p['name'].lower() +"(const lv_obj_t * obj, lv_part_t part)")
print("{")
print(" lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_" + p['name'] + ");")
print(" return " + cast + "v." + p['style_type'] + ";")
print("}")
print("")
if 'filtered' in p and p['filtered']:
print("static inline " + p['var_type'] + " lv_obj_get_style_" + p['name'].lower() +"_filtered(const lv_obj_t * obj, lv_part_t part)")
print("{")
print(" lv_style_value_t v = lv_obj_style_apply_color_filter(obj, part, lv_obj_get_style_prop(obj, part, LV_STYLE_" + p['name'] + "));")
print(" return " + cast + "v." + p['style_type'] + ";")
print("}")
print("")
def style_set_cast(style_type):
cast = ""
if style_type == 'num':
cast = "(int32_t)"
return cast
def style_set_c(p):
if 'section' in p: return
cast = style_set_cast(p['style_type'])
print("")
print("void lv_style_set_" + p['name'].lower() +"(lv_style_t * style, "+ p['var_type'] +" value)")
print("{")
print(" lv_style_value_t v = {")
print(" ." + p['style_type'] +" = " + cast + "value")
print(" };")
print(" lv_style_set_prop(style, LV_STYLE_" + p['name'] +", v);")
print("}")
def style_set_h(p):
if 'section' in p: return
print("void lv_style_set_" + p['name'].lower() +"(lv_style_t * style, "+ p['var_type'] +" value);")
def local_style_set_c(p):
if 'section' in p: return
cast = style_set_cast(p['style_type'])
print("")
print("void lv_obj_set_style_" + p['name'].lower() + "(lv_obj_t * obj, " + p['var_type'] +" value, lv_style_selector_t selector)")
print("{")
print(" lv_style_value_t v = {")
print(" ." + p['style_type'] +" = " + cast + "value")
print(" };")
print(" lv_obj_set_local_style_prop(obj, LV_STYLE_" + p['name'] +", v, selector);")
print("}")
def local_style_set_h(p):
if 'section' in p: return
print("void lv_obj_set_style_" + p['name'].lower() + "(lv_obj_t * obj, " + p['var_type'] +" value, lv_style_selector_t selector);")
def style_const_set(p):
if 'section' in p: return
cast = style_set_cast(p['style_type'])
print("")
print("#define LV_STYLE_CONST_" + p['name'] + "(val) \\")
print(" { \\")
print(" .prop = LV_STYLE_" + p['name'] + ", .value = { ." + p['style_type'] +" = " + cast + "val } \\")
print(" }")
def docs(p):
if "section" in p:
print("")
print(p['section'])
print("-" * len(p['section']))
print("")
print(p['dsc'])
return
if "default" not in p: return
d = str(p["default"])
i = "No"
if p["inherited"]: i = "Yes"
l = "No"
if p["layout"]: l = "Yes"
e = "No"
if p["ext_draw"]: e = "Yes"
li_style = "style='display:inline-block; margin-right: 20px; margin-left: 0px"
dsc = p['dsc']
print("")
print(p["name"].lower())
print("~" * len(p["name"].lower()))
print("")
print(dsc)
print("")
print(".. raw:: html")
print("")
print(" <ul>")
print(" <li " + li_style + "'><strong>Default</strong> " + d + "</li>")
print(" <li " + li_style + "'><strong>Inherited</strong> " + i + "</li>")
print(" <li " + li_style + "'><strong>Layout</strong> " + l + "</li>")
print(" <li " + li_style + "'><strong>Ext. draw</strong> " + e + "</li>")
print(" </ul>")
def guard_proc(p):
global guard
if 'section' in p:
if guard:
guard_close()
if 'guard' in p:
guard = p['guard']
print(f"#if {guard}")
def guard_close():
global guard
if guard:
print(f"#endif /*{guard}*/\n")
guard = ""
base_dir = os.path.abspath(os.path.dirname(__file__))
sys.stdout = open(base_dir + '/../src/core/lv_obj_style_gen.h', 'w')
HEADING = f'''
/*
**********************************************************************
* DO NOT EDIT
* This file is automatically generated by "{os.path.split(__file__)[-1]}"
**********************************************************************
*/
'''
print(HEADING)
print('#ifndef LV_OBJ_STYLE_GEN_H')
print('#define LV_OBJ_STYLE_GEN_H')
print()
print('''\
#ifdef __cplusplus
extern "C" {
#endif
''')
print("#include \"../misc/lv_area.h\"")
print("#include \"../misc/lv_style.h\"")
print("#include \"../core/lv_obj_style.h\"")
print("#include \"../misc/lv_types.h\"")
print()
guard = ""
for p in props:
guard_proc(p)
obj_style_get(p)
guard_close()
for p in props:
guard_proc(p)
local_style_set_h(p)
guard_close()
print()
print('''\
#ifdef __cplusplus
} /* extern "C" */
#endif
''')
print('#endif /* LV_OBJ_STYLE_GEN_H */')
sys.stdout = open(base_dir + '/../src/core/lv_obj_style_gen.c', 'w')
print(HEADING)
print("#include \"lv_obj.h\"")
print()
for p in props:
guard_proc(p)
local_style_set_c(p)
guard_close()
sys.stdout = open(base_dir + '/../src/misc/lv_style_gen.c', 'w')
print(HEADING)
print("#include \"lv_style.h\"")
print()
for p in props:
guard_proc(p)
style_set_c(p)
guard_close()
sys.stdout = open(base_dir + '/../src/misc/lv_style_gen.h', 'w')
print(HEADING)
print('#ifndef LV_STYLE_GEN_H')
print('#define LV_STYLE_GEN_H')
print()
print('''\
#ifdef __cplusplus
extern "C" {
#endif
''')
for p in props:
guard_proc(p)
style_set_h(p)
guard_close()
for p in props:
guard_proc(p)
style_const_set(p)
guard_close()
print()
print('''\
#ifdef __cplusplus
} /* extern "C" */
#endif
''')
print('#endif /* LV_STYLE_GEN_H */')
sys.stdout = open(base_dir + '/../docs/details/base-widget/styles/style-properties.rst', 'w')
print('.. _style_properties:')
print()
print('================')
print('Style Properties')
print('================')
for p in props:
docs(p)

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3
import argparse
import re
from pathlib import Path
MARK_LIST = ['tracing_mark_write']
def get_arg():
parser = argparse.ArgumentParser(description='Filter a log file to a trace file.')
parser.add_argument('log_file', metavar='log_file', type=str,
help='The input log file to process.')
parser.add_argument('trace_file', metavar='trace_file', type=str, nargs='?',
help='The output trace file. If not provided, defaults to \'<log_file>.systrace\'.')
args = parser.parse_args()
return args
if __name__ == '__main__':
args = get_arg()
if not args.trace_file:
log_file = Path(args.log_file)
args.trace_file = log_file.with_suffix('.systrace').as_posix()
print('log_file :', args.log_file)
print('trace_file:', args.trace_file)
with open(args.log_file, 'r') as f:
content = f.read()
# compile regex pattern
pattern = re.compile(r'(^.+-[0-9]+\s\[[0-9]]\s[0-9]+\.[0-9]+:\s('
+ "|".join(MARK_LIST)
+ r'):\s[B|E]\|[0-9]+\|.+$)', re.M)
matches = pattern.findall(content)
# write to args.trace_file
with open(args.trace_file, 'w') as f:
f.write('# tracer: nop\n#\n')
for match in matches:
f.write(match[0] + '\n')

View File

@@ -0,0 +1,177 @@
#!/usr/bin/env python3
import os
import re
import argparse
from typing import List, Union
def get_arg():
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description=""
"Apply the specified version to affected source files. Eg.:\n"
" python3 update_version.py 9.1.2-dev\n"
" python3 update_version.py 9.2.0"
)
parser.add_argument('version', metavar='version', type=str,
help='The version to apply')
return parser.parse_args()
class Version:
RE_PATTERN = r"(\d+)\.(\d+)\.(\d+)(-[\w\d]+)?"
def __init__(self, user_input: str):
if not re.match(r'^' + self.RE_PATTERN + r'$', user_input):
raise Exception(f"Invalid version format: {user_input}")
groups = re.search(self.RE_PATTERN, user_input).groups()
self.major = groups[0]
self.minor = groups[1]
self.patch = groups[2]
self.info = groups[3].lstrip('-') if groups[3] else ""
self.is_release = len(self.info) == 0
self.as_string = user_input
def __str__(self):
return self.as_string
class RepoFileVersionReplacer:
DIR_SCRIPTS = os.path.dirname(__file__)
DIR_REPO_ROOT = os.path.join(DIR_SCRIPTS, "..")
def __init__(self, relative_path_segments: List[str], expected_occurrences: int):
self.path_relative = os.path.join(*relative_path_segments)
self.path = os.path.join(self.DIR_REPO_ROOT, self.path_relative)
self.expected_occurrences = expected_occurrences
def applyVersionToLine(self, line: str, version: Version) -> Union[str, None]:
return None
def applyVersion(self, version: Version):
with open(self.path, 'r', encoding='utf-8') as file:
lines = file.readlines()
occurrences = 0
for i, line in enumerate(lines):
line_with_version = self.applyVersionToLine(line, version)
if line_with_version:
lines[i] = line_with_version
occurrences += 1
# not perfect, but will catch obvious pitfalls
if occurrences != self.expected_occurrences:
raise Exception(f"Bad lines in {self.path_relative}")
with open(self.path, 'w', encoding='utf-8') as file:
file.writelines(lines)
class PrefixReplacer(RepoFileVersionReplacer):
def __init__(self, relative_path_segments: List[str], prefix: str, expected_occurrences=1):
super().__init__(relative_path_segments, expected_occurrences)
self.prefix = prefix
def applyVersionToLine(self, line: str, version: Version):
pattern = r'(' + re.escape(self.prefix) + ')' + Version.RE_PATTERN
repl = r'\g<1>' + str(version)
replaced, n = re.subn(pattern, repl, line)
return replaced if n > 0 else None
class MacroReplacer(RepoFileVersionReplacer):
def __init__(self, relative_path_segments: List[str]):
super().__init__(relative_path_segments, 4)
def applyVersionToLine(self, line: str, version: Version):
targets = {
'LVGL_VERSION_MAJOR': version.major,
'LVGL_VERSION_MINOR': version.minor,
'LVGL_VERSION_PATCH': version.patch,
'LVGL_VERSION_INFO': version.info,
}
for key, val in targets.items():
pattern = self.getPattern(key)
repl = self.getReplacement(val)
replaced, n = re.subn(pattern, repl, line)
if n > 0:
return replaced
return None
def getPattern(self, key: str):
return r'(^#define ' + key + r' +).+'
def getReplacement(self, val: str):
if not val.isnumeric():
val = f'"{val}"'
return r'\g<1>' + val
class CmakeReplacer(MacroReplacer):
def getPattern(self, key: str):
return r'(^set\(' + key + r' +")([^"]*)(.+)'
def getReplacement(self, val: str):
return r'\g<1>' + val + r'\g<3>'
class KconfigReplacer(RepoFileVersionReplacer):
"""Replace version info in Kconfig file"""
def __init__(self, relative_path_segments: List[str]):
super().__init__(relative_path_segments, 3)
def applyVersionToLine(self, line: str, version: Version):
targets = {
'LVGL_VERSION_MAJOR': version.major,
'LVGL_VERSION_MINOR': version.minor,
'LVGL_VERSION_PATCH': version.patch,
}
for key, val in targets.items():
pattern = self.getPattern(key)
repl = self.getReplacement(val)
replaced, n = re.subn(pattern, repl, line)
if n > 0:
return replaced
return None
def getPattern(self, key: str):
# Match the version fields in Kconfig file
return rf'(^\s+default\s+)(\d+) # ({key})'
def getReplacement(self, val: str):
# Replace the version value
return r'\g<1>' + val + r' # \g<3>'
if __name__ == '__main__':
args = get_arg()
version = Version(args.version)
print(f"Applying version {version} to:")
targets = [
MacroReplacer(['lv_version.h']),
CmakeReplacer(['env_support', 'cmake', 'version.cmake']),
PrefixReplacer(['lv_conf_template.h'], 'Configuration file for v'),
KconfigReplacer(['Kconfig']),
]
if version.is_release:
targets.extend([
PrefixReplacer(['library.json'], '"version": "'),
PrefixReplacer(['library.properties'], 'version='),
PrefixReplacer(['Kconfig'], 'Kconfig file for LVGL v'),
])
for target in targets:
print(f" - {target.path_relative}")
target.applyVersion(version)