Use LVGL with Python3

Is there a way to use LVGL with Python3 (not micropython) ?

There is no official binding now. I’ve tried to create binding with SWIG but it failed with some erros I haven’t continued the investigation.

If you made any progress with an autogenerated Python3 binding please let us know. I’m sure it’d be interesting for a lot of people.

don’t need swig to get it done. Just need to be able to compile LVGL into a shared library with it’s functions exported. The binding between Python and LVGL can be made using the same mechanics the MicroPython binding is made.

Use what you are familiar with to get the job done. I can handle how the python files should be written in order to interact with a shared library. I can even have it handle building an export manifest for windows, Not sure what is needed when compiling using gcc but I am sure it’s nothing too complicated.

This is one area I am exceedingly proficient at. Have a look.

That’s over a million lines of code from the Windows 10 SDK that has been ported to Python. It if for reference purposes and is simply too large to run but it shows how Python is able to bind to a shared/dynamic library and access what is inside of that library. Just a matter of exporting the functions. The structures would need to be built using ctypes and that is pretty simple to do.

There is a script that is attached to that repo linked to above that I wrote that reads C header files and outputs Python code. I wrote it for parsing Windows SDK files and it does a decent job converting most things. It could be tweaked to handle LVGL fairly easily and it should be able to generate the needed python source files without much issue.

A few years ago we were talking about an LVGL-Python binding and IIRC correctly the amin problem was how to free the un-referenced LVGL widgets and other data. MicroPython solves it with it’s garbage collector which is integrated into LVGL but with normal Python it works differently.

a way to free the memory would need to be added if python’s garbage collection doesn’t handle it. It shouldn’t be all that hard to do. I actually spent a little bit of time last night researching pycparser and using it to generate the ctypes code to access a shared library. It is not going to be that hard to do actually. I think that compiling LVGL into a shared library shouldn’t be too difficult either. would have to work out free the memory and that wouldn’t be that hard to do either. any python objects that are created have a reference number set to them when that reference number drops to zero the python object gets garbage collected and the del method gets called. it’s in that method that a function can be called to free the use of parts in LVGL.

Wow, it really doesn’t sound that complicated. I’m really interested in a Python binding but let’s focus on one thing at a time.
So let’s get back to it when the docs update is merged.

I know I know I got to get back to working on the documentation. I was getting burned out on it. It’s a massive update

I don’t wonder, it’s really a big update. Don’t worry we are not in rush.

I found a way to easily make a Python port of LVGL. Using a combination of pycparser and also cffi. I am running into an issue compiling it because I am on Windows and trying to compile it using MSVC which doesn’t support C99. I am going to try and compile it under Linux to see if that is in fact what is going on. I feel pretty confident that is what the problem is because the errors I am getting are mostly syntax errors.

1 Like

Great news! Let me know if I can help with some Linux-based testing.

I did get it to compile on both Windows and also Linux. However I do not like how everything is interfaced using ffi.It’s really technical to use. However it does handle all of the referencing and garbage collection. I would rather have everything be automatic and that is what I have been tinkering with a bit. Python code needs to be written either automatically or manually that will make using the library more like using Python and less like writing code in C. wrapper functions need to be written to hide all of the FFI portions of the code.

Not a big deal to do just have to put some thought into how to organize things. I don’t know if making the code “Pythonic” would be the best approach. like what has been done with the MicroPython binding. or if keeping the same API as the C form of LVGL. I am almost thinking keeping it the same as the C API would be better. I am thinking the latter because of documentation. It also makes it easier to maintain as well. Less entry of glitches due to naming changes being made.

This is the kind of code I am talking about with how to use cffi

from lvgl import ffi, lib



def lv_point_transform(angle, zoom, pivot):
    p = ffi.new("lv_point_t *", {'x': 0, 'y': 0})  # makes an lv_point_t instance and keeps a reference to it so it lives past the function call.
    pivot = ffi.new("const lv_point_t *", {'x': pivot.x, 'y': pivot.y)
    angle = ffi.new("int32_t", angle)
    zoom = ffi.new("int32_t", zoom)
    lib.lv_point_transform(p, angle, zoom, pivot)

    return p

This example could technically be used like this.

def lv_point_transform(angle, zoom, pivot):
    p = ffi.new("lv_point_t *", {'x': 0, 'y': 0})  # makes an lv_point_t instance and keeps a reference to it so it lives past the function call.
    lib.lv_point_transform(p, ffi.new("int32_t", angle), ffi.new("int32_t", zoom), ffi.new("const lv_point_t *", {'x': pivot.x, 'y': pivot.y))
    return p

what happens there is the items being passed that have not been set into a python variable will get marked for garbage collection as soon as the lib.lv_point_transform returns. so in the case if this function the lv_point_t that is the first parameter is what gets filled with data for the user to use so this we do not want getting marked for garbage collection once the function returns, so we create the object and set it into a python variable to stop this from happening. so if I used the data in that object inside of the python function and I did not have a need for it any more once the python function exits causing the python variable to go out of scope the object gets marked for garbage collection.

The cffi code is not what I call “user friendly” and as such it should be hidden from the user. so classes need to be made to handle all of this kind of stuff…

we would compile lvgl into a module called _lvgl_lib and the builder would make a ptyhon source file called lvgl with code that would look something like this.

from _lvgl_lib import (
    lib as _lib,
    ffi as _ffi
)


class lv_point_t(object):
    _ctype = 'lv_point_t'

    def __new__(cls, *args, **kwargs):
        if len(list(kwargs.keys())) == 1 and len(args) == 0:
            key = list(kwargs.keys())[0]
            value = kwargs[key]
            if not isinstance(value, int):
                return lv_point_t(value.x, value.y)

        elif len(args) == 1 and len(list(kwargs.keys())) == 0:
            value = args[0]
            if not isinstance(value, int):
                return lv_point_t(value.x, value.y)

        super().__new__(cls)

    def __init__(self, x=0, y=0):
        self._obj = _ffi.new(self._ctype, {'x': x, 'y': y})

    @property
    def x(self):
        return self._obj.x

    @x.setter
    def x(self, value):
        self._obj.x = value

    @property
    def y(self):
        return self._obj.y

    @y.setter
    def y(self, value):
        self._obj.y = value


def pointer(obj):
    namespace = {'_ctype': obj._ctype + '*'}
    cls = type('pointer_' + obj.__class__.__name__, (obj.__class__,), namespace)
    return cls(obj)


def lv_point_transform(p, angle, zoom, pivot):
    _lib.lv_point_transform(p._obj, _ffi.new("int32_t", angle), _ffi.new("int32_t", zoom), pivot._obj)

and the user would use it like this.

from lvgl import *

point = pointer(lv_point_t())
pivot = pointer(lv_point_t(75, 25))

lv_point_transform(point, 1800, 255, pivot)

print(point.x)
print(point.y)

using from lvgl import * will import everything that is not prefixed with an underscore, so only the things that are “public”

That code hides the complicated cffi backend while keeping some of the C style coding syntax. the pointer function is a class factory of sorts. by calling type we are able to dynamically build a class from the original class modifying the C data type that gets passed to ffi.new. we use the original class as the base class because we want things like isinstance to work correctly if a user has a need to check the type of a variable. we do however change the name of the class so if repr(instance) was to be called on the dynamically created class it would reflect that the instance is a pointer to lv_point_t (not in python but in C code it is)

the __new__ method in lv_point_t provides a way to easily copy the data from one class into another class without the pointer function having to have specific knowledge of the lv_pointer_t class

1 Like

I vote for it. I think the advantages of keeping the C API outweigh the disadvantages.

Can we do import lvgl as lv instead? And on function calls: lv.point_transform();

pivot = pointer(lv_point_t(75, 25))

Would something like this be possible: pivot = lv.point(75, 25)?

@kisvegabor In the past, when discussing the development of LVGL Python Bindings with @rreilink (GitHub - rreilink/pylvgl: Python bindings for the LittlevGL graphics library) one important consideration was keeping syntax compatibility between Python and Micropython. This was to ensure that the same examples and demos could be run on both Python flavours, and to avoid confusion for users who might encounter two different syntax systems of LVGL Python bindings.

Therefore, I disagree with the suggestion to “keep the C API” for the sake of documentation and ease of maintenance. While these are important factors, they should not come at the expense of compatibility between Python flavours. I believe it would be better to prioritize syntax compatibility, as it benefits a wider range of users and supports a more cohesive LVGL community.

1 Like

It makes sense to keep the Python and MicroPython API compatible. However I think without a detailed MicroPython / Python API documentation it’s hard to use the current API. We use MicroPython for SquareLine Studio and sometimes it’s a real challenge to figure out the MicroPython version of the C functions or enums. Maybe it’s just our ignorance or not Pythonic thinking but I think if it’s difficult for us, it’s difficult for others too.

So in the name of simplification and ease of use I propose following the C API more closely with both MicroPython and Python. It’s not clear where is the line though:

  1. Pure C style? E.g. lv.chart_set_range(chart1, 10, 100) and lv.CHART_AXIS_PRIMARY_Y
  2. Classes with member functions only? E.g. chart1.set_range(10, 100), but keep the constants as lv.CHART_AXIS_PRIMARY_Y
  3. Constants in classes too? chart1.AXIS_PRIMARY_X or lv.chart.AXIS_PRIMARY_X

Currently we have lv.chart.AXIS.PRIMARY_X. A novice user might assume:

  • lv.CHART_AXIX_PRIMARY_X
  • lv.chart.AXIS_PRIMARY_X
  • lv.chart.AXIS.PRIMARY.X
  • and other combinations too

@amirgon already mentioned in an other discussion that he prefers the lv.chart.AXIS.PRIMARY_X style, but I think it’s hard to figure out from the C API. For auto complete lv.CHART_AXIS_PRIMARY_X or lv.chart.AXIS_PRIMARY_X would work similarly well, but would be a cleaner mapping between C and MicroPython.

I am decently proficient in Python and even after using LVGL for the past 6 months I still find that I have to open up the generated lv_mp.c file to sift through it so I can locate what I need. I have to do this quite a lot actually. But then again I am also messing around with rendering directly to the buffer and not using widgets. That might be more on the advanced side of things.

to bridge the C code to Python in a more “Pythonic” manner would have been not creating a completely new class for each widget but instead aligning the functions as methods inside the structures and the structure fields become instance attributes. This is basically how the C code works. The structures that have been set up for the different widgets are what get passed to the functions as the first parameter. so that would be considered “self” in Python. When I first started using LVGL this is how I thought it was set up. It is not tho because I am not able to access any of the fields that are in any of the widgets structures when I construct an instance of a widget.

The other issue with a CPython binding is the need to keep track of references. if a reference goes out of scope it will be garbage collected. Putting it on the user to be responsible for keeping track of those references would make it really easy to make the binding.

If the C API is wanted and the user is to be responsible for keeping track of the references that I am finished with the binding. It has been tested on Windows and also on Ubuntu. There are 2 small code changes that need to be made to LVGL in order for it to work.

lv_anim_timeline_dsc_t and lv_anim_timeline_t need to be moved from the c source file to the header file.

/*Data of anim_timeline_dsc*/
typedef struct {
    lv_anim_t anim;
    uint32_t start_time;
} lv_anim_timeline_dsc_t;

/*Data of anim_timeline*/
typedef struct  {
    lv_anim_timeline_dsc_t * anim_dsc;  /**< Dynamically allocated anim dsc array*/
    uint32_t anim_dsc_cnt;              /**< The length of anim dsc array*/
    bool reverse;                       /**< Reverse playback*/
} lv_anim_timeline_t;

and _lv_event_dsc_t needs to be moved out of the c source file into the header file. This one is very specific about where in the header file it needs to be placed. just after lv_event_code_t

typedef struct {
    struct _lv_event_dsc_t * dsc;
    uint32_t cnt;
} lv_event_list_t;

typedef struct _lv_event_t {
    void * current_target;
    void * original_target;
    lv_event_code_t code;
    void * user_data;
    void * param;
    struct _lv_event_t * prev;
    uint8_t deleted : 1;
    uint8_t stop_processing : 1;
    uint8_t stop_bubbling : 1;
} lv_event_t;

/**
 * @brief Event callback.
 * Events are used to notify the user of some action being taken on the object.
 * For details, see ::lv_event_t.
 */
typedef void (*lv_event_cb_t)(lv_event_t * e);

typedef struct  _lv_event_dsc_t {
    lv_event_cb_t cb;
    void * user_data;
    uint32_t filter;
} lv_event_dsc_t;

otherwise cffi is unable to set a proper size on those structures and they will not be usable in the binding.

I still have to do a little bit of work on the callback functions and I just figured out how they are stored in pycparser.

Once I mapped out the entire structure of what LVGL is represented as in pycparser it ended up being easier to do then I thought it would be.

This is the actual tree of how all items in LVGL are represented in pycparser

FileAST
    Typedef.type
        TypeDecl.type
            IdentifierType.names
            Struct.decls
                Decl.type
                    TypeDecl.type
                        IdentifierType.names
                        Union.decls
                            Decl.type
                                TypeDecl.type
                                    IdentifierType.names
                                    Struct.decls
                                        Decl.type
                                            PtrDecl.type
                                                TypeDecl.type
                                                    IdentifierType.names
                                            TypeDecl.type
                                                IdentifierType.names
                                PtrDecl.type
                                    TypeDecl.type
                                        IdentifierType.names
                                ArrayDecl.type
                                    TypeDecl.type
                                        IdentifierType.names
                        Struct.decls
                            Decl.type
                                TypeDecl.type
                                    IdentifierType.names
                                PtrDecl.type
                                    TypeDecl.type
                                        IdentifierType.names
                    PtrDecl.type
                        TypeDecl.type
                            IdentifierType.names
                        FuncDecl.args
                            ParamList.params
                                Typename.type
                                    PtrDecl.type
                                        TypeDecl.type
                                            IdentifierType.names
                                    TypeDecl.type
                                        IdentifierType.names
                                Decl.type
                                    TypeDecl.type
                                        IdentifierType.names
                                    PtrDecl.type
                                        TypeDecl.type
                                            IdentifierType.names
                                    ArrayDecl.type
                                        TypeDecl.type
                                            IdentifierType.names
                        FuncDecl.type
                            TypeDecl.type
                                IdentifierType.names
                            PtrDecl.type
                                TypeDecl.type
                                    IdentifierType.names
                        PtrDecl.type
                            TypeDecl.type
                                IdentifierType.names
                    ArrayDecl.type
                        TypeDecl.type
                            IdentifierType.names
                        PtrDecl.type
                            TypeDecl.type
                                IdentifierType.names
                        ArrayDecl.type
                            TypeDecl.type
                                IdentifierType.names
            Enum.values
                EnumeratorList.enumerators
                    Enumerator.value
            Union.decls
                Decl.type
                    TypeDecl.type
                        IdentifierType.names
                    PtrDecl.type
                        TypeDecl.type
                            IdentifierType.names
        PtrDecl.type
            TypeDecl.type
                IdentifierType.names
            FuncDecl.args
                ParamList.params
                    Typename.type
                        PtrDecl.type
                            TypeDecl.type
                                IdentifierType.names
                        TypeDecl.type
                            IdentifierType.names
                    Decl.type
                        PtrDecl.type
                            TypeDecl.type
                                IdentifierType.names
                        TypeDecl.type
                            IdentifierType.names
            FuncDecl.type
                TypeDecl.type
                    IdentifierType.names
        ArrayDecl.type
            TypeDecl.type
                IdentifierType.names
    Decl.type
        Enum.values
            EnumeratorList.enumerators
                Enumerator.value
        FuncDecl.args
            ParamList.params
                Decl.type
                    TypeDecl.type
                        IdentifierType.names
                    PtrDecl.type
                        TypeDecl.type
                            IdentifierType.names
                        PtrDecl.type
                            TypeDecl.type
                                IdentifierType.names
                        FuncDecl.args
                            ParamList.params
                                Decl.type
                                    PtrDecl.type
                                        TypeDecl.type
                                            IdentifierType.names
                        FuncDecl.type
                            TypeDecl.type
                                IdentifierType.names
                    ArrayDecl.type
                        TypeDecl.type
                            IdentifierType.names
                        PtrDecl.type
                            TypeDecl.type
                                IdentifierType.names
                Typename.type
                    TypeDecl.type
                        IdentifierType.names
        FuncDecl.type
            TypeDecl.type
                IdentifierType.names
            PtrDecl.type
                TypeDecl.type
                    IdentifierType.names
                PtrDecl.type
                    TypeDecl.type
                        IdentifierType.names
        TypeDecl.type
            IdentifierType.names
        PtrDecl.type
            FuncDecl.args
                ParamList.params
                    Typename.type
                        PtrDecl.type
                            TypeDecl.type
                                IdentifierType.names
                        TypeDecl.type
                            IdentifierType.names
                    Decl.type
                        TypeDecl.type
                            IdentifierType.names
            FuncDecl.type
                TypeDecl.type
                    IdentifierType.names
        Struct.decls
            Decl.type
                TypeDecl.type
                    IdentifierType.names
                    Struct.decls
                        Decl.type
                            TypeDecl.type
                                IdentifierType.names
                            PtrDecl.type
                                TypeDecl.type
                PtrDecl.type
                    TypeDecl.type
                        IdentifierType.names
                    FuncDecl.args
                        ParamList.params
                            Decl.type
                                PtrDecl.type
                                    TypeDecl.type
                                        IdentifierType.names
                                TypeDecl.type
                                    IdentifierType.names
                    FuncDecl.type
                        TypeDecl.type
                            IdentifierType.names
                    PtrDecl.type
                        TypeDecl.type
                ArrayDecl.type
                    TypeDecl.type
                        IdentifierType.names
    FuncDef.decl
        Decl.type
            FuncDecl.args
                ParamList.params
                    Decl.type
                        TypeDecl.type
                            IdentifierType.names
                        PtrDecl.type
                            TypeDecl.type
                                IdentifierType.names
                    Typename.type
                        TypeDecl.type
                            IdentifierType.names
            FuncDecl.type
                TypeDecl.type
                    IdentifierType.names
                PtrDecl.type
                    TypeDecl.type
                        IdentifierType.names
                    PtrDecl.type
                        TypeDecl.type
                            IdentifierType.names

from that doing a for loop through the base items and then instance checks from there is easy.

I am also letting pycparser handle the preprocessing. I am only preprocessing the header files and not the source files. if it’s public it’s in a header and if not it is in a source file so no need to expose anything that is in the source files at all.

I did have to fix the “fake lib c” files included with pycparser. they have everything plopped into a single file and all of the rest of the header files do an include to the one header where everything is located. so you end up with all kinds of things that aren’t needed or used. So I corrected that issue by moving things into the header file they should be located in. Once I got past the problems created by that I had to then strip out all of the declarations that pycparser picked up from those files.

Here is the source code to build the binding

import os
import sys

if sys.platform.startswith('win'):

    # this library is what make building on Windows possible. both
    # distutils and setuptools do not properly set up a build environment
    # on Windows using the MSVC compiler. It does everything that is needed
    # to set up the build system and it works, plain and simple.
    # the library is available on pypi
    # https://pypi.org/project/pyMSVC/
    # and it is also on Github
    # https://github.com/kdschlosser/python_msvc

    import pyMSVC

    environment = pyMSVC.setup_environment()
    print(environment)

    # compiler to use
    cpp_path = 'cl'
else:
    # compiler to use
    cpp_path = 'gcc'


# file/directory paths
base_path = os.path.dirname(__file__)
lvgl_path = os.path.join(base_path, 'lvgl')
lvgl_src_path = os.path.join(lvgl_path, 'src')
lvgl_header_path = os.path.join(lvgl_path, 'lvgl.h')
pycparser_path = os.path.join(base_path, 'pycparser')

# make sure we are in the correct directory
os.chdir(base_path)
# technically we do not need to package pycparser with the builder. 
# The fake lib c files are not included if pycparser is installed into python
# and that is the reason for packaging it with the binding. Since I 
# had to modify those files they are packaged. I just left it as it is but it 
# will be removed in the future.

sys.path.insert(0, pycparser_path)

from cffi import FFI  # NOQA
import pycparser  # NOQA
from pycparser import c_generator  # NOQA

# some paths/files we do not need to compile the source files for.
IGNORE_DIRS = ('disp', 'arm2d', 'gd32_ipa', 'nxp', 'stm32_dma2d', 'swm341_dma2d')
IGNORE_FILES = ()


# # function that iterates over the LVGL/src directory to
# locate all of the ".c" files as these files need to be compiled
def iter_sources(p):
    res = []
    folders = []
    for f in os.listdir(p):
        file = os.path.join(p, f)
        if os.path.isdir(file):
            if f in IGNORE_DIRS:
                continue

            folders.append(file)
        elif f not in IGNORE_FILES:
            if not f.endswith('.c'):
                continue

            res.append(file)

    for folder in folders:
        res.extend(iter_sources(folder))

    return res


# read the lvgl header files so we can generate the information that CFFI needs
# I know that LVGL was written for c99 and thata I am using c11. This is
# because the MSVC compiler does not support c99. c11 seems to work without
# any issues.
ast = pycparser.parse_file(
    lvgl_header_path,
    use_cpp=True,
    cpp_path=cpp_path,
    cpp_args=['-E', '-std:c11', '-DPYCPARSER', '-Ifake_libc_include'],  # '/E',
    parser=None
)

# monkeypatched functions in pycparser.c_generator.CGenerator
# I used the CGenerator to output declarations that CFFI uses to make
# the Python C extension. I am using only the header files and not the C
# source files for the preprocessing. There are however some functions that are
# defined in the header files and I needed to change those to declaractions.

# this function removes any of the declarations that get added from the
# fake lib c header files. we do not wnat to use that information because it
# declares things like uint_8t as int which it 3 bytes larger then it should be.
# this causes problems in the c extension.
def visit(self, node):
    if hasattr(node, 'coord') and node.coord is not None:
        if 'fake_libc_include' in node.coord.file:
            return ''

    method = 'visit_' + node.__class__.__name__
    res = getattr(self, method, self.generic_visit)(node)
    if res.strip() == ';':
        return ''

    return res


# turns function definitions into declarations.
def visit_FuncDef(self, n):
    decl = self.visit(n.decl)
    self.indent_level = 0
    # body = self.visit(n.body)
    if n.param_decls:
        knrdecls = ';\n'.join(self.visit(p) for p in n.param_decls)
        return decl + '\n' + knrdecls + ';\n'  # + body + '\n'
    else:
        return decl + ';\n'  # + body + '\n'


# saving the old generator functions so they can be put back in place before
# cffi runs. cffi uses pycparser and I do not know if it uses the generator at
# all. so better safe then sorry
old_visit = c_generator.CGenerator.visit
old_visit_FuncDef = c_generator.CGenerator.visit_FuncDef

# putting the updated functions in place
setattr(c_generator.CGenerator, 'visit', visit)
setattr(c_generator.CGenerator, 'visit_FuncDef', visit_FuncDef)

generator = c_generator.CGenerator()
ffibuilder = FFI()

# create the definitions that cffi needs to make the binding
cdef = """
#define INT32_MAX 2147483647
typedef char* va_list;

{ast}

"""
cdef = cdef.format(ast=str(generator.visit(ast)))

# my monkey patchs were not perfect and when I removed all of the
# typedefs put in place from the fale lib c header files I was not able to
# remove the trailing semicolon. So thaat is what is being done here.
cdef = '\n'.join(line for line in cdef.split('\n') if line.strip() != ';')

# putting the old functions back in place
setattr(c_generator.CGenerator, 'visit', old_visit)
setattr(c_generator.CGenerator, 'visit_FuncDef', old_visit_FuncDef)

# set the definitions into cffi
ffibuilder.cdef(cdef)

# set the name of the c extension and also tell cffi what we need to compile
ffibuilder.set_source(
    "lvgl",
    '#include "lvgl.h"',
    sources=iter_sources(lvgl_src_path),
)

# change the path into the lvgl directory
os.chdir(lvgl_path)

# start the compilation
ffibuilder.compile(verbose=True)

I tried out the binding with this directory structure:

.
├── binding.py
├── lv_conf.h
└── lvgl

Added the modifications you have mentioned to LVGL but got this error:

$python3 binding.py 
Traceback (most recent call last):
  File "/home/kisvegabor/projects/lvgl/python3/binding.py", line 81, in <module>
    ast = pycparser.parse_file(
  File "/home/kisvegabor/.local/lib/python3.9/site-packages/pycparser/__init__.py", line 90, in parse_file
    return parser.parse(text, filename)
  File "/home/kisvegabor/.local/lib/python3.9/site-packages/pycparser/c_parser.py", line 147, in parse
    return self.cparser.parse(
  File "/home/kisvegabor/.local/lib/python3.9/site-packages/pycparser/ply/yacc.py", line 331, in parse
    return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
  File "/home/kisvegabor/.local/lib/python3.9/site-packages/pycparser/ply/yacc.py", line 1199, in parseopt_notrack
    tok = call_errorfunc(self.errorfunc, errtoken, self)
  File "/home/kisvegabor/.local/lib/python3.9/site-packages/pycparser/ply/yacc.py", line 193, in call_errorfunc
    r = errorfunc(token)
  File "/home/kisvegabor/.local/lib/python3.9/site-packages/pycparser/c_parser.py", line 1931, in p_error
    self._parse_error(
  File "/home/kisvegabor/.local/lib/python3.9/site-packages/pycparser/plyparser.py", line 67, in _parse_error
    raise ParseError("%s: %s" % (coord, msg))
pycparser.plyparser.ParseError: /usr/lib/gcc/x86_64-linux-gnu/10/include/stddef.h:416:28: before: __attribute__

I’m not sure it matters, but I’m on Linux.

yeah that’s because you don’t have the modified fake lib c header files. when pycparser is running the std lib c headers are being used because the include path for the fake ones does not exist. That is why you are getting the error.

line 85 in the binding.py script

cpp_args=['-E', '-std:c11', '-DPYCPARSER', '-Ifake_libc_include'],

-E is for preprocessor output only, you know what the -std is for the -D you also know what it’s for and I can actually remove that as it is not used and then you have the -I for the include of the fake lib c headers.

I know with the script being only 170 lines of code it seem like it should be impossible for it to work, LOL. It does tho. Let me make a repo that has all of the bits needed for it to run properly.

support for the callback functions I still have to add and I have to test SDL2 to see if there are any issues that come up from that. But you are able to create instances in python of the structures and access the functions in LVGL. It’s not 100% yet but it does compile properly and I am able to access the bits and pieces to LVGL from inside of Python.

2 Likes

I mentioned this on GitHub but I also want to mention it here so people know this is possible. This binding is not only for being able to use LVGL on a desktop PC (which is a big help with development). It is also for using on MCUs

https://onion.io/omega2/

That MCU runs a Linux kernel and is able to run CPython. so this binding very much applies to MCUs as well. The company that makes that MCU has supplied python libraries to access some of the MCUs functionality like SPI and I2C. IDK if it supports anything like I8080 or if it has DMA memory and hammering those things out that are specific to that MCU would have to be done. If we can get SPI working then it is another MCU that can be added to the list of ones that are supported. By making a CPython binding it makes development easier but it also allows the user to have access to most of the libraries that are available for CPython.

1 Like

I got SDL2 with LVGL to compile properly under Windows. I believe it should compile properly for Linux out of the box so long as SDL2 has been properly installed into Linux.

The Windows variant doesn’t have the header files located in a directory called “SDL2”. it is in a directory called “include” So the requirement when compiling under Windows is that SDL2 must be placed into the same folder as the build script and the script will handle copying the needed files and placing them into the directory structure that LVGL wants to see. Not a big deal to handle.

Again I do not know if there are any special requirements needed for compiler and linker flags when compiling under Linux. It compiled properly without SDL2 but if there is something special needed it may not with SDL2 added. This is information I will need from you @kisvegabor

When it is deemed that everything looks good with the binding I will write a build script that will generate wheels and set up a CI for it. This way it can be added to PyPi with the extension module already compiled. This way anyone who wants to use it on Windows, Linux or Crapintosh will not have to mess about with compiling the extension module. Doing this also keeps the number of people that have issues because the extension module is compiled in a proper sandbox environment with everything set up the way it should be.

2 Likes