{"id":13618267,"url":"https://github.com/lupyuen/zig-lvgl-nuttx","last_synced_at":"2025-07-22T15:07:45.606Z","repository":{"id":41053061,"uuid":"508125113","full_name":"lupyuen/zig-lvgl-nuttx","owner":"lupyuen","description":"Zig LVGL Touchscreen App on Apache NuttX RTOS","archived":false,"fork":false,"pushed_at":"2022-07-12T12:05:31.000Z","size":329,"stargazers_count":15,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-15T09:09:41.706Z","etag":null,"topics":["bl602","bl604","embedded","lvgl","nuttx","pinecone","pinedio","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lupyuen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["lupyuen"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["paypal.me/lupyuen"]}},"created_at":"2022-06-28T02:15:22.000Z","updated_at":"2025-03-15T01:33:22.000Z","dependencies_parsed_at":"2022-07-14T06:40:37.578Z","dependency_job_id":null,"html_url":"https://github.com/lupyuen/zig-lvgl-nuttx","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/lupyuen/zig-lvgl-nuttx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fzig-lvgl-nuttx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fzig-lvgl-nuttx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fzig-lvgl-nuttx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fzig-lvgl-nuttx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lupyuen","download_url":"https://codeload.github.com/lupyuen/zig-lvgl-nuttx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Fzig-lvgl-nuttx/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266516223,"owners_count":23941371,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bl602","bl604","embedded","lvgl","nuttx","pinecone","pinedio","zig"],"created_at":"2024-08-01T20:01:57.260Z","updated_at":"2025-07-22T15:07:45.587Z","avatar_url":"https://github.com/lupyuen.png","language":"Zig","funding_links":["https://github.com/sponsors/lupyuen","paypal.me/lupyuen"],"categories":["Zig","Multimedia \u0026 Graphics"],"sub_categories":["GUI"],"readme":"![LVGL Test App in C](https://lupyuen.github.io/images/lvgl-title.jpg)\n\n# Zig LVGL Touchscreen App on Apache NuttX RTOS\n\nRead the article...\n\n-   [\"Build an LVGL Touchscreen App with Zig\"](https://lupyuen.github.io/articles/lvgl)\n\nCan we use Zig to code an LVGL Touchscreen App for Apache NuttX RTOS?\n\nMaybe make LVGL a little safer and friendlier... By wrapping the LVGL API in Zig?\n\nOr will we get blocked by something beyond our control? (Like Bit Fields in LVGL Structs)\n\nLet's find out!\n\n# LVGL Test App in C\n\nHere's our barebones LVGL App in C (pic above): [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L107-L148) \n\n```c\nstatic void create_widgets(void) {\n  //  Get the Active Screen\n  lv_obj_t *screen = lv_scr_act();\n\n  //  Create a Label Widget\n  lv_obj_t *label = lv_label_create(screen, NULL);\n\n  //  Wrap long lines in the label text\n  lv_label_set_long_mode(label, LV_LABEL_LONG_BREAK);\n\n  //  Interpret color codes in the label text\n  lv_label_set_recolor(label, true);\n\n  //  Center align the label text\n  lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);\n\n  //  Set the label text and colors\n  lv_label_set_text(\n    label, \n    \"#ff0000 HELLO# \"    //  Red Text\n    \"#00aa00 PINEDIO# \"  //  Green Text\n    \"#0000ff STACK!# \"   //  Blue Text\n  );\n\n  //  Set the label width\n  lv_obj_set_width(label, 200);\n\n  //  Align the label to the center of the screen, shift 30 pixels up\n  lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, -30);\n\n  //  Omitted: LVGL Canvas\n}\n```\n\nNuttX compiles the LVGL Test App with this GCC command...\n\n```bash\n##  App Source Directory\ncd $HOME/nuttx/apps/examples/lvgltest\n\n##  Compile lvgltest.c with GCC\nriscv64-unknown-elf-gcc \\\n  -c \\\n  -fno-common \\\n  -Wall \\\n  -Wstrict-prototypes \\\n  -Wshadow \\\n  -Wundef \\\n  -Os \\\n  -fno-strict-aliasing \\\n  -fomit-frame-pointer \\\n  -fstack-protector-all \\\n  -ffunction-sections \\\n  -fdata-sections \\\n  -g \\\n  -march=rv32imafc \\\n  -mabi=ilp32f \\\n  -mno-relax \\\n  -isystem \"$HOME/nuttx/nuttx/include\" \\\n  -D__NuttX__ \\\n  -DNDEBUG \\\n  -DARCH_RISCV  \\\n  -pipe \\\n  -I \"$HOME/nuttx/apps/graphics/lvgl\" \\\n  -I \"$HOME/nuttx/apps/graphics/lvgl/lvgl\" \\\n  -I \"$HOME/nuttx/apps/include\" \\\n  -DLV_LVGL_H_INCLUDE_SIMPLE \\\n  -Wno-format \\\n  -Dmain=lvgltest_main \\\n  -lvgltest.c \\\n  -o lvgltest.c.home.user.nuttx.apps.examples.lvgltest.o\n```\n\n(Observed from `make --trace`)\n\nLet's convert the LVGL Test App from C to Zig...\n\n# Auto-Translate LVGL App to Zig\n\nThe Zig Compiler can auto-translate C code to Zig. [(See this)](https://ziglang.org/documentation/master/#C-Translation-CLI)\n\nHere's how we auto-translate our LVGL App [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c) from C to Zig...\n\n-   Take the GCC command from above\n\n-   Change `riscv64-unknown-elf-gcc` to `zig translate-c`\n\n-   Add the target `-target riscv32-freestanding-none -mcpu=baseline_rv32-d`\n\n-   Remove `-march=rv32imafc`\n\n-   Surround the C Flags by `-cflags` ... `--`\n\nLike this...\n\n```bash\n##  App Source Directory\ncd $HOME/nuttx/apps/examples/lvgltest\n\n##  Auto-translate lvgltest.c from C to Zig\nzig translate-c \\\n  -target riscv32-freestanding-none \\\n  -mcpu=baseline_rv32-d \\\n  -cflags \\\n    -fno-common \\\n    -Wall \\\n    -Wstrict-prototypes \\\n    -Wshadow \\\n    -Wundef \\\n    -Os \\\n    -fno-strict-aliasing \\\n    -fomit-frame-pointer \\\n    -fstack-protector-all \\\n    -ffunction-sections \\\n    -fdata-sections \\\n    -g \\\n    -mabi=ilp32f \\\n    -mno-relax \\\n    -Wno-format \\\n  -- \\\n  -isystem \"$HOME/nuttx/nuttx/include\" \\\n  -D__NuttX__ \\\n  -DNDEBUG \\\n  -DARCH_RISCV  \\\n  -I \"$HOME/nuttx/apps/graphics/lvgl\" \\\n  -I \"$HOME/nuttx/apps/graphics/lvgl/lvgl\" \\\n  -I \"$HOME/nuttx/apps/include\" \\\n  -DLV_LVGL_H_INCLUDE_SIMPLE \\\n  -Dmain=lvgltest_main  \\\n  lvgltest.c \\\n  \u003elvgltest.zig\n```\n\nTo fix the translation we need to insert this...\n\n```c\n#if defined(__NuttX__) \u0026\u0026 defined(__clang__)  //  Workaround for NuttX with zig cc\n#include \u003carch/types.h\u003e\n#include \"../../nuttx/include/limits.h\"\n#define FAR\n#endif  //  defined(__NuttX__) \u0026\u0026 defined(__clang__)\n```\n\n[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L25-L29)\n\nAnd change this...\n\n```c\nstatic void monitor_cb(lv_disp_drv_t * disp_drv, uint32_t time, uint32_t px)\n{\n#ifndef __clang__  //  Doesn't compile with zig cc\n  ginfo(\"%\" PRIu32 \" px refreshed in %\" PRIu32 \" ms\\n\", px, time);\n#endif  //  __clang__\n}\n```\n\n[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L95-L100)\n\n[(See the changes)](https://github.com/lupyuen/lvgltest-nuttx/commit/1e8b0501c800209f0fa3f35f54b3742498d0e302)\n\nHere's the original C code: [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c)\n\nAnd the auto-translation from C to Zig: [translated/lvgltest.zig](translated/lvgltest.zig)\n\n# Zig Auto-Translation is Incomplete\n\nThe Auto-Translation from C to Zig is missing 2 key functions: `lvgltest_main` and `create_widgets`...\n\n```zig\n// lvgltest.c:129:13: warning: unable to translate function, demoted to extern\npub extern fn create_widgets() callconv(.C) void;\n// lvgltest.c:227:17: warning: local variable has opaque type\n\n// (no file):353:14: warning: unable to translate function, demoted to extern\npub extern fn lvgltest_main(arg_argc: c_int, arg_argv: [*c][*c]u8) c_int;\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/9e95d800f3a429c5f35970ca35cd43bd8fbd9529/translated/lvgltest.zig#L5901-L5904)\n\nWhen we look up `lvgltest.c` line 227...\n\n```c\nint lvgltest_main(int argc, FAR char *argv[])\n{\n  // lvgltest.c:227:17: warning: local variable has opaque type\n  lv_disp_drv_t disp_drv;\n  lv_disp_buf_t disp_buf;\n  ...\n```\n\n[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/1e8b0501c800209f0fa3f35f54b3742498d0e302/lvgltest.c#L225-L228)\n\nWe see that Zig couldn't translate the type `lv_disp_drv_t` because it's opaque.\n\nLet's find out why.\n\n# Opaque Types\n\nTo find out why the type is opaque, we search for `lv_disp_drv_t` in the Zig Translation...\n\n```zig\n// nuttx/apps/graphics/lvgl/lvgl/src/lv_hal/lv_hal_disp.h:154:9: \n// warning: struct demoted to opaque type - has bitfield\npub const lv_disp_drv_t = struct__disp_drv_t; \npub const struct__disp_drv_t = opaque {};\n\n// nuttx/apps/graphics/lvgl/lvgl/src/lv_hal/lv_hal_disp.h:59:23: \n// warning: struct demoted to opaque type - has bitfield\npub const lv_disp_t = struct__disp_t;\npub const struct__disp_t = opaque {};\n\npub const lv_disp_buf_t = opaque {};\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/9e95d800f3a429c5f35970ca35cd43bd8fbd9529/translated/lvgltest.zig#L700-L704)\n\nBelow are the C definitions of `lv_disp_drv_t`, `lv_disp_t` and `lv_disp_buf_t`.\n\nThe structs couldn't be translated to Zig because they contain Bit Fields...\n\n```c\ntypedef struct _disp_drv_t {\n    uint32_t rotated : 1;\n    uint32_t dpi : 10;\n    ...\n} lv_disp_drv_t;\n\ntypedef struct _disp_t {\n    uint8_t del_prev  : 1;\n    uint32_t inv_p : 10;\n    ...\n} lv_disp_t;\n\ntypedef struct {\n    volatile uint32_t last_area : 1;\n    volatile uint32_t last_part : 1;\n    ...\n} lv_disp_buf_t;\n```\n\nLet's fix the Opaque Types.\n\n# Fix Opaque Types\n\nEarlier we saw that Zig couldn't translate and import these structs because they contain Bit Fields...\n\n-   `lv_disp_drv_t` (Display Driver)\n\n-   `lv_disp_buf_t` (Display Buffer)\n\nInstead of creating instances of these structs in Zig, we do it in C instead...\n\n```c\n/****************************************************************************\n * Name: get_disp_drv\n *\n * Description:\n *   Return the static instance of Display Driver, because Zig can't\n *   allocate structs wth bitfields inside.\n *\n ****************************************************************************/\n\nlv_disp_drv_t *get_disp_drv(void)\n{\n  static lv_disp_drv_t disp_drv;\n  return \u0026disp_drv;\n}\n\n/****************************************************************************\n * Name: get_disp_buf\n *\n * Description:\n *   Return the static instance of Display Buffer, because Zig can't\n *   allocate structs wth bitfields inside.\n *\n ****************************************************************************/\n\nlv_disp_buf_t *get_disp_buf(void)\n{\n  static lv_disp_buf_t disp_buf;\n  return \u0026disp_buf;\n}\n\n/****************************************************************************\n * Name: init_disp_drv\n *\n * Description:\n *   Initialise the Display Driver, because Zig can't access its fields.\n *\n ****************************************************************************/\n\nvoid init_disp_drv(lv_disp_drv_t *disp_drv,\n  lv_disp_buf_t *disp_buf,\n  void (*monitor_cb)(struct _disp_drv_t *, uint32_t, uint32_t))\n{\n  assert(disp_drv != NULL);\n  assert(disp_buf != NULL);\n  assert(monitor_cb != NULL);\n\n  lv_disp_drv_init(disp_drv);\n  disp_drv-\u003ebuffer = disp_buf;\n  disp_drv-\u003emonitor_cb = monitor_cb;\n}\n\n/****************************************************************************\n * Name: init_disp_buf\n *\n * Description:\n *   Initialise the Display Buffer, because Zig can't access the fields.\n *\n ****************************************************************************/\n\nvoid init_disp_buf(lv_disp_buf_t *disp_buf)\n{\n  assert(disp_buf != NULL);\n  lv_disp_buf_init(disp_buf, buffer1, buffer2, DISPLAY_BUFFER_SIZE);\n}\n```\n\n[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lcddev.c#L335-L398)\n\nThen we fetch the pointers to these structs in our Main Function and initialise the structs...\n\n```c\nint lvgltest_main(int argc, FAR char *argv[])\n{\n  lv_disp_drv_t *disp_drv = get_disp_drv();\n  lv_disp_buf_t *disp_buf = get_disp_buf();\n  ...\n  /* Basic LVGL display driver initialization */\n  init_disp_buf(disp_buf);\n  init_disp_drv(disp_drv, disp_buf, monitor_cb);\n  ...\n  /* Touchpad Initialization */\n  lv_indev_drv_t *indev_drv = get_indev_drv();\n  init_indev_drv(indev_drv, tp_read);\n```\n\n[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L214-L293)\n\n(`get_indev_drv` and `init_indev_drv` are explained in the next section)\n\nAfter this modification, our Auto-Translation from C to Zig now contains the 2 missing functions...\n\n-   [`lvgltest_main`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5913-L5944)\n\n-   [`create_widgets`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5903-L5912)\n\n# Input Driver\n\nOur Input Driver `lv_indev_drv_t` is also an Opaque Type because it contains Bit Fields.\n\nWe fix `lv_indev_drv_t` the same way as other Opaque Types: We allocate and initialise the structs in C (instead of Zig)...\n\n```c\n/****************************************************************************\n * Name: get_indev_drv\n *\n * Description:\n *   Return the static instance of Input Driver, because Zig can't\n *   allocate structs wth bitfields inside.\n *\n ****************************************************************************/\n\nlv_indev_drv_t *get_indev_drv(void)\n{\n  static lv_indev_drv_t indev_drv;\n  return \u0026indev_drv;\n}\n\n/****************************************************************************\n * Name: init_indev_drv\n *\n * Description:\n *   Initialise the Input Driver, because Zig can't access its fields.\n *\n ****************************************************************************/\n\nvoid init_indev_drv(lv_indev_drv_t *indev_drv,\n  bool (*read_cb)(struct _lv_indev_drv_t *, lv_indev_data_t *))\n{\n  assert(indev_drv != NULL);\n  assert(read_cb != NULL);\n\n  lv_indev_drv_init(indev_drv);\n  indev_drv-\u003etype = LV_INDEV_TYPE_POINTER;\n\n  /* This function will be called periodically (by the library) to get the\n   * mouse position and state.\n   */\n\n  indev_drv-\u003eread_cb = read_cb;\n  lv_indev_drv_register(indev_drv);\n}\n```\n\n[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/tp.c#L282-L320)\n\n# Color Type\n\nWe also commented out all references to `lv_color_t`...\n\n```c\n//  LVGL Canvas Demo doesn't work with zig cc because of `lv_color_t`\n#if defined(CONFIG_USE_LV_CANVAS) \u0026\u0026 !defined(__clang__)  \n\n  //  Set the Canvas Buffer (Warning: Might take a lot of RAM!)\n  static lv_color_t cbuf[LV_CANVAS_BUF_SIZE_TRUE_COLOR(CANVAS_WIDTH, CANVAS_HEIGHT)];\n  ...\n```\n\n[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L160-L165)\n\nThat's because `lv_color_t` is also an Opaque Type...\n\n```zig\npub const lv_color_t = lv_color16_t;\n\npub const lv_color16_t = extern union {\n    ch: struct_unnamed_7,\n    full: u16,\n};\n\n// nuttx/apps/graphics/lvgl/lvgl/src/lv_core/../lv_draw/../lv_misc/lv_color.h:240:18:\n// warning: struct demoted to opaque type - has bitfield\nconst struct_unnamed_7 = opaque {};\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L520-L537)\n\nThat contains Bit Fields...\n\n```c\ntypedef union {\n    struct {\n        // Bit fields for lv_color16_t (aliased to lv_color_t)\n        uint16_t blue : 5;\n        uint16_t green : 6;\n        uint16_t red : 5;\n    } ch;\n    uint16_t full;\n} lv_color16_t;\n```\n\n# LVGL App in Zig\n\nWe copy these functions from the Auto-Translated Zig code...\n\n-   [`lvgltest_main`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5913-L5944)\n\n-   [`create_widgets`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5903-L5912)\n\nAnd paste them into our Zig LVGL App...\n\nhttps://github.com/lupyuen/zig-lvgl-nuttx/blob/ec4d58e84140cbf2b8fd6a80b65c06f6da97edfc/lvgltest.zig#L1-L164\n\nWe compile our Zig LVGL App...\n\n```bash\n##  Download our Zig LVGL App for NuttX\ngit clone --recursive https://github.com/lupyuen/zig-lvgl-nuttx\ncd zig-lvgl-nuttx\n\n##  Compile the Zig App for BL602 (RV32IMACF with Hardware Floating-Point)\nzig build-obj \\\n  --verbose-cimport \\\n  -target riscv32-freestanding-none \\\n  -mcpu=baseline_rv32-d \\\n  -isystem \"$HOME/nuttx/nuttx/include\" \\\n  -I \"$HOME/nuttx/apps/graphics/lvgl\" \\\n  -I \"$HOME/nuttx/apps/graphics/lvgl/lvgl\" \\\n  -I \"$HOME/nuttx/apps/include\" \\\n  -I \"$HOME/nuttx/apps/examples/lvgltest\" \\\n  lvgltest.zig\n\n##  Patch the ELF Header of `lvgltest.o` from Soft-Float ABI to Hard-Float ABI\nxxd -c 1 lvgltest.o \\\n  | sed 's/00000024: 01/00000024: 03/' \\\n  | xxd -r -c 1 - lvgltest2.o\ncp lvgltest2.o lvgltest.o\n\n##  Copy the compiled app to NuttX and overwrite `lvgltest.o`\n##  TODO: Change \"$HOME/nuttx\" to your NuttX Project Directory\ncp lvgltest.o $HOME/nuttx/apps/examples/lvgltest/lvgltest*.o\n\n##  Build NuttX to link the Zig Object from `lvgltest.o`\n##  TODO: Change \"$HOME/nuttx\" to your NuttX Project Directory\ncd $HOME/nuttx/nuttx\nmake\n```\n\nWhen tested on PineDio Stack BL604, our Zig LVGL App correctly renders the screen and correctly handles touch input (pic below). Yay!\n\n```text\nNuttShell (NSH) NuttX-10.3.0\nnsh\u003e lvgltest\nZig LVGL Test\ntp_init: Opening /dev/input0\ncst816s_open: \ncst816s_poll_notify: \ncst816s_get_touch_data: \ncst816s_i2c_read: \ncst816s_get_touch_data: DOWN: id=0, touch=0, x=176, y=23\ncst816s_get_touch_data:   id:      0\ncst816s_get_touch_data:   flags:   19\ncst816s_get_touch_data:   x:       176\ncst816s_get_touch_data:   y:       23\n...\ntp_cal result\noffset x:23, y:14\nrange x:189, y:162\ninvert x/y:1, x:0, y:1\n```\n\n[(Source)](https://gist.github.com/lupyuen/795d7660679c3e0288e8fe5bec190890)\n\n![LVGL Test App in C](https://lupyuen.github.io/images/lvgl-title.jpg)\n\n# Clean Up\n\nAfter cleaning up our Zig LVGL App, here's our Main Function `lvgltest_main`...\n\n```zig\n/// Main Function that will be called by NuttX. We render an LVGL Screen and\n/// handle Touch Input.\npub export fn lvgltest_main(\n    _argc: c_int, \n    _argv: [*]const [*]const u8\n) c_int {\n    debug(\"Zig LVGL Test\", .{});\n    // Command-line args are not used\n    _ = _argc;\n    _ = _argv;\n\n    // Init LVGL Library\n    c.lv_init();\n\n    // Init Display Buffer\n    const disp_buf = c.get_disp_buf().?;\n    c.init_disp_buf(disp_buf);\n\n    // Init Display Driver\n    const disp_drv = c.get_disp_drv().?;\n    c.init_disp_drv(disp_drv, disp_buf, monitorCallback);\n\n    // Init LCD Driver\n    if (c.lcddev_init(disp_drv) != c.EXIT_SUCCESS) {\n        // If failed, try Framebuffer Driver\n        if (c.fbdev_init(disp_drv) != c.EXIT_SUCCESS) {\n            // No possible drivers left, fail\n            return c.EXIT_FAILURE;\n        }\n    }\n\n    // Register Display Driver\n    _ = c.lv_disp_drv_register(disp_drv);\n\n    // Init Touch Panel\n    _ = c.tp_init();\n\n    // Init Input Device. tp_read will be called periodically\n    // to get the touched position and state\n    const indev_drv = c.get_indev_drv().?;\n    c.init_indev_drv(indev_drv, c.tp_read);\n\n    // Create the widgets for display\n    createWidgetsUnwrapped()\n        catch |e| {\n            // In case of error, quit\n            std.log.err(\"createWidgets failed: {}\", .{e});\n            return c.EXIT_FAILURE;\n        };\n\n    // To call the LVGL API that's wrapped in Zig, change\n    // `createWidgetsUnwrapped` above to `createWidgetsWrapped`\n\n    // Start Touch Panel calibration\n    c.tp_cal_create();\n\n    // Loop forever handing LVGL tasks\n    while (true) {\n        // Handle LVGL tasks\n        _ = c.lv_task_handler();\n\n        // Sleep a while\n        _ = c.usleep(10000);\n    }\n    return 0;\n}\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L44-L109)\n\nAnd here's our `createWidgetsUnwrapped` function that creates widgets...\n\n```zig\n/// Create the LVGL Widgets that will be rendered on the display. Calls the\n/// LVGL API directly, without wrapping in Zig. Based on\n/// https://docs.lvgl.io/7.11/widgets/label.html#label-recoloring-and-scrolling\nfn createWidgetsUnwrapped() !void {\n\n    // Get the Active Screen\n    const screen = c.lv_scr_act().?;\n\n    // Create a Label Widget\n    const label = c.lv_label_create(screen, null).?;\n\n    // Wrap long lines in the label text\n    c.lv_label_set_long_mode(label, c.LV_LABEL_LONG_BREAK);\n\n    // Interpret color codes in the label text\n    c.lv_label_set_recolor(label, true);\n\n    // Center align the label text\n    c.lv_label_set_align(label, c.LV_LABEL_ALIGN_CENTER);\n\n    // Set the label text and colors\n    c.lv_label_set_text(\n        label, \n        \"#ff0000 HELLO# \" ++    // Red Text\n        \"#00aa00 PINEDIO# \" ++  // Green Text\n        \"#0000ff STACK!# \"      // Blue Text\n    );\n\n    // Set the label width\n    c.lv_obj_set_width(label, 200);\n\n    // Align the label to the center of the screen, shift 30 pixels up\n    c.lv_obj_align(label, null, c.LV_ALIGN_CENTER, 0, -30);\n}\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L114-L147)\n\nThe Zig Functions look very similar to C: [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L107-L318)\n\nNote that we used `.?` to check for Null Pointers returned by C Functions. Let's find out why...\n\n# Zig Checks Null Pointers\n\n_What happens if a C Function returns a Null Pointer..._\n\n```c\nlv_disp_drv_t *get_disp_drv(void) {\n  // Return a Null Pointer\n  return NULL;\n}\n```\n\n_And we call it from Zig?_\n\n```zig\nconst disp_drv = c.get_disp_drv().?;\n```\n\nNote that we used `.?` to check for Null Pointers returned by C Functions.\n\nWhen we run this code, we'll see a Zig Panic...\n\n```text\nnsh\u003e lvgltest\nZig LVGL Test\n\n!ZIG PANIC!\nattempt to use null value\nStack Trace:\n0x23023606\n```\n\nThe Stack Trace Address `23023606` points to the line of code that encountered the Null Pointer...\n\n```text\nzig-lvgl-nuttx/lvgltest.zig:50\n    const disp_drv = c.get_disp_drv().?;\n230235f4:   23089537            lui     a0,0x23089\n230235f8:   5ac50513            addi    a0,a0,1452 # 230895ac \u003c__unnamed_10\u003e\n230235fc:   4581                li      a1,0\n230235fe:   00000097            auipc   ra,0x0\n23023602:   c92080e7            jalr    -878(ra) # 23023290 \u003cpanic\u003e\n23023606:   ff042503            lw      a0,-16(s0)\n2302360a:   fea42623            sw      a0,-20(s0)\n```\n\nSo Zig really helps us to write safer programs.\n\n_What if we omit `.?` and do this?_\n\n```zig\nconst disp_drv = c.get_disp_drv();\n```\n\nThis crashes with a RISC-V Exception when the code tries to dereference the Null Pointer later. Which is not as helpful as a Zig Panic.\n\nThus we always use `.?` to check for Null Pointers returned by C Functions!\n\n(Hopefully someday we'll have a Zig Lint Tool that will warn us if we forget to use `.?`)\n\n# Simplify LVGL API\n\n_Can we simplify the LVGL API in Zig? Such that this code..._\n\n```zig\n// Get the Active Screen\nconst screen = c.lv_scr_act().?;\n\n// Create a Label Widget\nconst label = c.lv_label_create(screen, null).?;\n\n// Wrap long lines in the label text\nc.lv_label_set_long_mode(label, c.LV_LABEL_LONG_BREAK);\n\n// Interpret color codes in the label text\nc.lv_label_set_recolor(label, true);\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L114-L148)\n\n_Becomes this?_\n\n```zig\n// Get the Active Screen\nvar screen = try lvgl.getActiveScreen();\n\n// Create a Label Widget\nvar label = try screen.createLabel();\n\n// Wrap long lines in the label text\nlabel.setLongMode(c.LV_LABEL_LONG_BREAK);\n\n// Interpret color codes in the label text\nlabel.setRecolor(true);\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L150-L183)\n\nYes we can! By wrapping the LVGL API in Zig, which we'll do in the next section.\n\nNote that we now use `try` instead of `.?`.\n\n_What happens if we forget to use `try`?_\n\nIf we don't `try`, like this...\n\n```zig\n// Get the Active Screen without `try`\nvar screen = lvgl.getActiveScreen();\n\n// Attempt to use the Active Screen\n_ = screen;\n```\n\nZig Compiler stops us with an error...\n\n```text\n./lvgltest.zig:109:9:\nerror: error is discarded. \nconsider using `try`, `catch`, or `if`\n    _ = screen;\n        ^\n```\n\nThus `try` is actually safer than `.?`, Zig Compiler mandates that we check for errors.\n\n_What if the LVGL API returns a Null Pointer to our Zig App?_\n\nOur app will fail with this message...\n\n```text\nlv_scr_act failed\ncreateWidgets failed: error.UnknownError\n```\n\n# Wrap LVGL API\n\nLet's wrap the LVGL API in Zig.\n\nHere's the implementation of `getActiveScreen`, which returns the LVGL Active Screen...\n\n```zig\n/// Return the Active Screen\npub fn getActiveScreen() !Object {\n\n    // Get the Active Screen\n    const screen = c.lv_scr_act();\n\n    // If successfully fetched...\n    if (screen) |s| {\n        // Wrap Active Screen as Object and return it\n        return Object.init(s);\n    } else {\n        // Unable to get Active Screen\n        std.log.err(\"lv_scr_act failed\", .{});\n        return LvglError.UnknownError;\n    }\n}\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgl.zig#L26-L34)\n\n`Object` is a Zig Struct that wraps around an LVGL Object...\n\n```zig\n/// LVGL Object\npub const Object = struct {\n\n    /// Pointer to LVGL Object\n    obj: *c.lv_obj_t,\n\n    /// Init the Object\n    pub fn init(obj: *c.lv_obj_t) Object {\n        return .{ .obj = obj };\n    }\n\n    /// Create a Label as a child of the Object\n    pub fn createLabel(self: *Object) !Label {\n\n        // Assume we won't copy from another Object \n        const copy: ?*const c.lv_obj_t = null;\n\n        // Create the Label\n        const label = c.lv_label_create(self.obj, copy);\n\n        // If successfully created...\n        if (label) |l| {\n            // Wrap as Label and return it\n            return Label.init(l);\n        } else {\n            // Unable to create Label\n            std.log.err(\"lv_label_create failed\", .{});\n            return LvglError.UnknownError;\n        }\n    }\n};\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgl.zig#L36-L58)\n\n`Label` is a Zig Struct that wraps around an LVGL Label...\n\n```zig\n/// LVGL Label\npub const Label = struct {\n\n    /// Pointer to LVGL Label\n    obj: *c.lv_obj_t,\n\n    /// Init the Label\n    pub fn init(obj: *c.lv_obj_t) Label {\n        return .{ .obj = obj };\n    }\n\n    /// Set the wrapping of long lines in the label text\n    pub fn setLongMode(self: *Label, long_mode: c.lv_label_long_mode_t) void {\n        c.lv_label_set_long_mode(self.obj, long_mode);\n    }\n\n    /// Set the label text alignment\n    pub fn setAlign(self: *Label, alignment: c.lv_label_align_t) void {\n        c.lv_label_set_align(self.obj, alignment);\n    }\n\n    /// Enable or disable color codes in the label text\n    pub fn setRecolor(self: *Label, en: bool) void {\n        c.lv_label_set_recolor(self.obj, en);\n    }\n\n    /// Set the label text and colors\n    pub fn setText(self: *Label, text: [*c]const u8) void {\n        c.lv_label_set_text(self.obj, text);\n    }\n\n    /// Set the object width\n    pub fn setWidth(self: *Label, w: c.lv_coord_t) void {\n        c.lv_obj_set_width(self.obj, w);\n    }\n\n    /// Set the object alignment\n    pub fn alignObject(self: *Label, alignment: c.lv_align_t, x_ofs: c.lv_coord_t, y_ofs: c.lv_coord_t) void {\n        const base: ?*const c.lv_obj_t = null;\n        c.lv_obj_align(self.obj, base, alignment, x_ofs, y_ofs);\n    }\n};\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgl.zig#L60-L101)\n\nLet's call the wrapped LVGL API...\n\n# After Wrapping LVGL API\n\nWith the wrapped LVGL API, our Zig App becomes simpler and safer...\n\n```zig\n/// Create the LVGL Widgets that will be rendered on the display. Calls the\n/// LVGL API that has been wrapped in Zig. Based on\n/// https://docs.lvgl.io/7.11/widgets/label.html#label-recoloring-and-scrolling\nfn createWidgetsWrapped() !void {\n\n    // Get the Active Screen\n    var screen = try lvgl.getActiveScreen();\n\n    // Create a Label Widget\n    var label = try screen.createLabel();\n\n    // Wrap long lines in the label text\n    label.setLongMode(c.LV_LABEL_LONG_BREAK);\n\n    // Interpret color codes in the label text\n    label.setRecolor(true);\n\n    // Center align the label text\n    label.setAlign(c.LV_LABEL_ALIGN_CENTER);\n\n    // Set the label text and colors\n    label.setText(\n        \"#ff0000 HELLO# \" ++    // Red Text\n        \"#00aa00 PINEDIO# \" ++  // Green Text\n        \"#0000ff STACK!# \"      // Blue Text\n    );\n\n    // Set the label width\n    label.setWidth(200);\n\n    // Align the label to the center of the screen, shift 30 pixels up\n    label.alignObject(c.LV_ALIGN_CENTER, 0, -30);\n}\n```\n\n[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L149-L181)\n\n(TODO: Convert `LV_LABEL_LONG_BREAK`, `LV_LABEL_ALIGN_CENTER` and other constants to Enums)\n\nLet's talk about creating the Zig Wrapper...\n\n# Auto-Generate Zig Wrapper\n\n_Can we auto-generate the Wrapper Code?_\n\nWe might use Zig Type Reflection...\n\n-   [\"Zig Type Reflection\"](https://github.com/lupyuen/zig-bl602-nuttx/blob/main/README.md#zig-type-reflection)\n\nOr we can parse the Type Info JSON generated by Zig Compiler...\n\n```bash\n## Emit IR, BC and Type Info\nzig build-obj \\\n  -femit-llvm-ir \\\n  -femit-llvm-bc \\\n  -femit-analysis \\\n  --verbose-cimport \\\n  -target riscv32-freestanding-none \\\n  -mcpu=baseline_rv32-d \\\n  -isystem \"$HOME/nuttx/nuttx/include\" \\\n  -I \"$HOME/nuttx/apps/graphics/lvgl\" \\\n  -I \"$HOME/nuttx/apps/graphics/lvgl/lvgl\" \\\n  -I \"$HOME/nuttx/apps/include\" \\\n  -I \"$HOME/nuttx/apps/examples/lvgltest\" \\\n  lvgltest.zig\n```\n\nThis produces the IR, BC and Type Info JSON files: \n\n```text\nlvgltest.ll\nlvgltest.bc\nlvgltest-analysis.json\n```\n\nLet's look up the Type Info for the LVGL Function `lv_obj_align`.\n\nWe search for `lv_obj_align` in `lvgltest-analysis.json`...\n\n```json\n\"decls\":\n  ...\n  {\n   \"import\": 99,\n   \"src\": 1962,\n   \"name\": \"lv_obj_align\",\n   \"kind\": \"const\",\n   \"type\": 148,\n   \"value\": 60\n  },\n```\n\nThen we look up type 148 in `lvgltest-analysis.json`...\n\n```bash\n$ jq '.types[148]' lvgltest-analysis.json\n{\n  \"kind\": 18,\n  \"name\": \"fn(?*.cimport:10:11.struct__lv_obj_t, ?*const .cimport:10:11.struct__lv_obj_t, u8, i16, i16) callconv(.C) void\",\n  \"generic\": false,\n  \"ret\": 70,\n  \"args\": [\n    79,\n    194,\n    95,\n    134,\n    134\n  ]\n}\n```\n\nThe First Parameter has type 79, so we look up `lvgltest-analysis.json` and follow the trail...\n\n```bash\n$ jq '.types[79]' lvgltest-analysis.json\n{\n  \"kind\": 13,\n  \"child\": 120\n}\n## Kind 13 is `?` (Optional)\n\n$ jq '.types[120]' lvgltest-analysis.json\n{\n  \"kind\": 6,\n  \"elem\": 137\n}\n## Kind 6 is `*` (Pointer)\n\n$ jq '.types[137]' lvgltest-analysis.json\n{\n  \"kind\": 20,\n  \"name\": \".cimport:10:11.struct__lv_obj_t\"\n}\n## Kind 20 is `struct`???\n```\n\nWhich gives us the complete type of the First Parameter...\n\n```zig\n?*.cimport:10:11.struct__lv_obj_t\n```\n\nWe don't have the Parameter Names though, we might need to parse the `.cimport` file.\n\n[(More about jq)](https://stedolan.github.io/jq/manual/)\n\n# Object-Oriented Wrapper for LVGL\n\n_Is LVGL really Object-Oriented?_\n\nYep the LVGL API is actually Object-Oriented since it uses Inheritance.\n\nAll LVGL Widgets (Labels, Buttons, etc) have the same Base Type: `lv_obj_t`. But some LVGL Functions will work only for specific Widgets, whereas some LVGL Functions will work on any Widget...\n\n-   `lv_label_set_text` works only for Labels\n\n-   `lv_obj_set_width` works for any Widget\n\nThe LVGL Docs also say that LVGL is Object-Oriented...\n\n-   [\"Base object (lv_obj)\"](https://docs.lvgl.io/latest/en/html/widgets/obj.html)\n\nCreating an Object-Oriented Zig Wrapper for LVGL might be challenging: Our Zig Wrapper needs to support `setWidth` for all LVGL Widgets.\n\nTo do this we might use Zig Interfaces and `@fieldParentPtr`...\n\n-   [\"Interfaces in Zig\"](https://zig.news/david_vanderson/interfaces-in-zig-o1c)\n\n-   [\"Zig Interfaces for the Uninitiated\"](https://www.nmichaels.org/zig/interfaces.html)\n\nWhich look somewhat similar to VTables in C++...\n\n-   [\"Allocgate is coming in Zig 0.9\"](https://pithlessly.github.io/allocgate.html)\n\n_Are there any Object-Oriented Bindings for LVGL?_\n\nThe official Python Bindings for LVGL appear to be Object-Oriented. This could inspire our Object-Oriented Wrapper in Zig...\n\n-   [Python Bindings for LVGL](https://github.com/lvgl/lv_binding_micropython)\n\nHowever the Python Bindings are Dynamically Typed, might be tricky implementing them as Static Types in Zig.\n\nThe LVGL Wrapper in this article was inspired by the [zgt GUI Library](https://github.com/zenith391/zgt), which works with GTK, Win32 and WebAssembly...\n\n-   [\"Build a PinePhone App with Zig and zgt\"](https://lupyuen.github.io/articles/pinephone)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupyuen%2Fzig-lvgl-nuttx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flupyuen%2Fzig-lvgl-nuttx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupyuen%2Fzig-lvgl-nuttx/lists"}