From d6baf40d0bff58ff39e90dc466acbc75ec773bb4 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sun, 21 Jan 2024 22:27:00 +0100 Subject: [PATCH] Implement LVGL with SDL for simulator (#16) * Implemented LVGL with SDL for simulator * cleanup * added SDL to build * build fix * mutex fixes * sim app cleanup and improvements * docs updated * fix for sdl? * fix for SDL cmake setup --- .github/workflows/pc.yml | 8 + CMakeLists.txt | 67 +- README.md | 8 +- app-sim/CMakeLists.txt | 15 +- app-sim/src/freertos.c | 65 + app-sim/src/hello_world/hello_world.c | 22 + app-sim/src/hello_world/hello_world.h | 5 + app-sim/src/lv_conf.h | 700 +++++ app-sim/src/lv_drv_conf.h | 496 ++++ app-sim/src/lvgl_hal.c | 75 + app-sim/src/lvgl_hal.h | 14 + app-sim/src/lvgl_task.c | 115 + app-sim/src/lvgl_task.h | 14 + app-sim/src/main.c | 45 +- docs/ideas.md | 6 + docs/pics/tactility-showcase.jpg | Bin 0 -> 86036 bytes libs/lv_drivers/.github/auto-comment.yml | 12 + libs/lv_drivers/.github/stale.yml | 17 + libs/lv_drivers/.gitignore | 2 + libs/lv_drivers/CMakeLists.txt | 58 + libs/lv_drivers/LICENSE | 21 + libs/lv_drivers/README.md | 7 + libs/lv_drivers/display/GC9A01.c | 597 ++++ libs/lv_drivers/display/GC9A01.h | 76 + libs/lv_drivers/display/ILI9341.c | 432 +++ libs/lv_drivers/display/ILI9341.h | 67 + libs/lv_drivers/display/R61581.c | 425 +++ libs/lv_drivers/display/R61581.h | 57 + libs/lv_drivers/display/SHARP_MIP.c | 182 ++ libs/lv_drivers/display/SHARP_MIP.h | 63 + libs/lv_drivers/display/SSD1963.c | 292 ++ libs/lv_drivers/display/SSD1963.h | 150 + libs/lv_drivers/display/ST7565.c | 289 ++ libs/lv_drivers/display/ST7565.h | 58 + libs/lv_drivers/display/UC1610.c | 206 ++ libs/lv_drivers/display/UC1610.h | 58 + libs/lv_drivers/display/drm.c | 801 +++++ libs/lv_drivers/display/drm.h | 60 + libs/lv_drivers/display/fbdev.c | 277 ++ libs/lv_drivers/display/fbdev.h | 65 + libs/lv_drivers/display/monitor.h | 57 + libs/lv_drivers/docs/astyle_c | 1 + libs/lv_drivers/docs/astyle_h | 1 + libs/lv_drivers/gtkdrv/README.md | 97 + libs/lv_drivers/gtkdrv/broadway.png | Bin 0 -> 28408 bytes libs/lv_drivers/gtkdrv/gtkdrv.c | 323 ++ libs/lv_drivers/gtkdrv/gtkdrv.h | 59 + libs/lv_drivers/indev/AD_touch.c | 383 +++ libs/lv_drivers/indev/AD_touch.h | 120 + libs/lv_drivers/indev/FT5406EE8.c | 179 ++ libs/lv_drivers/indev/FT5406EE8.h | 56 + libs/lv_drivers/indev/XPT2046.c | 174 ++ libs/lv_drivers/indev/XPT2046.h | 56 + libs/lv_drivers/indev/evdev.c | 251 ++ libs/lv_drivers/indev/evdev.h | 72 + libs/lv_drivers/indev/keyboard.h | 81 + libs/lv_drivers/indev/libinput.c | 501 ++++ libs/lv_drivers/indev/libinput_drv.h | 145 + libs/lv_drivers/indev/mouse.h | 81 + libs/lv_drivers/indev/mousewheel.h | 80 + libs/lv_drivers/indev/xkb.c | 217 ++ libs/lv_drivers/indev/xkb.h | 106 + libs/lv_drivers/library.json | 13 + libs/lv_drivers/lv_drivers.mk | 10 + libs/lv_drivers/lv_drv_conf_template.h | 496 ++++ libs/lv_drivers/sdl/sdl.c | 391 +++ libs/lv_drivers/sdl/sdl.h | 103 + libs/lv_drivers/sdl/sdl_common.c | 273 ++ libs/lv_drivers/sdl/sdl_common.h | 93 + libs/lv_drivers/sdl/sdl_common_internal.h | 39 + libs/lv_drivers/sdl/sdl_gpu.c | 279 ++ libs/lv_drivers/sdl/sdl_gpu.h | 96 + libs/lv_drivers/wayland/.gitignore | 5 + libs/lv_drivers/wayland/CMakeLists.txt | 39 + libs/lv_drivers/wayland/README.md | 157 + libs/lv_drivers/wayland/wayland.c | 2638 +++++++++++++++++ libs/lv_drivers/wayland/wayland.h | 77 + libs/lv_drivers/win32drv/win32drv.c | 1054 +++++++ libs/lv_drivers/win32drv/win32drv.h | 79 + libs/lv_drivers/win_drv.c | 304 ++ libs/lv_drivers/win_drv.h | 60 + libs/lvgl/CMakeLists.txt | 1 - libs/lvgl/config/lv_conf.h | 959 ------ .../cmake/custom_simple_config.cmake | 2 - run.sh | 1 + tactility-core/src/core_types.h | 2 +- tactility-core/src/mutex.c | 175 +- tactility-core/src/mutex.h | 20 +- tactility-core/src/pubsub.c | 2 +- tactility-core/src/tactility_core.h | 1 + tactility-core/src/tactility_core_config.h | 2 +- tactility-core/src/thread.h | 10 +- .../apps/system/wifi_connect/wifi_connect.h | 2 +- .../src/apps/system/wifi_manage/wifi_manage.h | 2 +- tactility-esp/src/display.c | 4 +- tactility-esp/src/display.h | 6 +- tactility-esp/src/graphics.c | 6 +- tactility-esp/src/graphics.h | 2 +- tactility-esp/src/graphics_i.h | 4 +- tactility-esp/src/hardware.c | 2 +- tactility-esp/src/{hardare.h => hardware.h} | 2 +- tactility-esp/src/hardware_i.h | 2 +- tactility-esp/src/services/wifi/wifi.c | 2 +- .../src/services/wifi/wifi_credentials.c | 69 +- tactility-esp/src/tactility-esp.h | 4 +- tactility-esp/src/touch.c | 4 +- tactility-esp/src/touch.h | 6 +- tactility/src/app.c | 4 +- tactility/src/app_i.h | 2 +- tactility/src/app_manifest_registry.c | 6 +- tactility/src/app_manifest_registry.h | 4 +- tactility/src/service.c | 6 +- tactility/src/service_i.h | 6 +- tactility/src/service_registry.c | 8 +- tactility/src/services/gui/gui.c | 2 +- tactility/src/services/gui/gui_i.h | 2 +- tactility/src/services/loader/loader.c | 2 +- tactility/src/tactility.c | 1 - 118 files changed, 15327 insertions(+), 1181 deletions(-) create mode 100644 app-sim/src/freertos.c create mode 100644 app-sim/src/hello_world/hello_world.c create mode 100644 app-sim/src/hello_world/hello_world.h create mode 100644 app-sim/src/lv_conf.h create mode 100644 app-sim/src/lv_drv_conf.h create mode 100644 app-sim/src/lvgl_hal.c create mode 100644 app-sim/src/lvgl_hal.h create mode 100644 app-sim/src/lvgl_task.c create mode 100644 app-sim/src/lvgl_task.h create mode 100644 docs/ideas.md create mode 100644 docs/pics/tactility-showcase.jpg create mode 100644 libs/lv_drivers/.github/auto-comment.yml create mode 100644 libs/lv_drivers/.github/stale.yml create mode 100644 libs/lv_drivers/.gitignore create mode 100644 libs/lv_drivers/CMakeLists.txt create mode 100644 libs/lv_drivers/LICENSE create mode 100644 libs/lv_drivers/README.md create mode 100644 libs/lv_drivers/display/GC9A01.c create mode 100644 libs/lv_drivers/display/GC9A01.h create mode 100644 libs/lv_drivers/display/ILI9341.c create mode 100644 libs/lv_drivers/display/ILI9341.h create mode 100644 libs/lv_drivers/display/R61581.c create mode 100644 libs/lv_drivers/display/R61581.h create mode 100644 libs/lv_drivers/display/SHARP_MIP.c create mode 100644 libs/lv_drivers/display/SHARP_MIP.h create mode 100644 libs/lv_drivers/display/SSD1963.c create mode 100644 libs/lv_drivers/display/SSD1963.h create mode 100644 libs/lv_drivers/display/ST7565.c create mode 100644 libs/lv_drivers/display/ST7565.h create mode 100644 libs/lv_drivers/display/UC1610.c create mode 100644 libs/lv_drivers/display/UC1610.h create mode 100644 libs/lv_drivers/display/drm.c create mode 100644 libs/lv_drivers/display/drm.h create mode 100644 libs/lv_drivers/display/fbdev.c create mode 100644 libs/lv_drivers/display/fbdev.h create mode 100644 libs/lv_drivers/display/monitor.h create mode 100644 libs/lv_drivers/docs/astyle_c create mode 100644 libs/lv_drivers/docs/astyle_h create mode 100644 libs/lv_drivers/gtkdrv/README.md create mode 100644 libs/lv_drivers/gtkdrv/broadway.png create mode 100644 libs/lv_drivers/gtkdrv/gtkdrv.c create mode 100644 libs/lv_drivers/gtkdrv/gtkdrv.h create mode 100644 libs/lv_drivers/indev/AD_touch.c create mode 100644 libs/lv_drivers/indev/AD_touch.h create mode 100644 libs/lv_drivers/indev/FT5406EE8.c create mode 100644 libs/lv_drivers/indev/FT5406EE8.h create mode 100644 libs/lv_drivers/indev/XPT2046.c create mode 100644 libs/lv_drivers/indev/XPT2046.h create mode 100644 libs/lv_drivers/indev/evdev.c create mode 100644 libs/lv_drivers/indev/evdev.h create mode 100644 libs/lv_drivers/indev/keyboard.h create mode 100644 libs/lv_drivers/indev/libinput.c create mode 100644 libs/lv_drivers/indev/libinput_drv.h create mode 100644 libs/lv_drivers/indev/mouse.h create mode 100644 libs/lv_drivers/indev/mousewheel.h create mode 100644 libs/lv_drivers/indev/xkb.c create mode 100644 libs/lv_drivers/indev/xkb.h create mode 100644 libs/lv_drivers/library.json create mode 100644 libs/lv_drivers/lv_drivers.mk create mode 100644 libs/lv_drivers/lv_drv_conf_template.h create mode 100644 libs/lv_drivers/sdl/sdl.c create mode 100644 libs/lv_drivers/sdl/sdl.h create mode 100644 libs/lv_drivers/sdl/sdl_common.c create mode 100644 libs/lv_drivers/sdl/sdl_common.h create mode 100644 libs/lv_drivers/sdl/sdl_common_internal.h create mode 100644 libs/lv_drivers/sdl/sdl_gpu.c create mode 100644 libs/lv_drivers/sdl/sdl_gpu.h create mode 100644 libs/lv_drivers/wayland/.gitignore create mode 100644 libs/lv_drivers/wayland/CMakeLists.txt create mode 100644 libs/lv_drivers/wayland/README.md create mode 100644 libs/lv_drivers/wayland/wayland.c create mode 100644 libs/lv_drivers/wayland/wayland.h create mode 100644 libs/lv_drivers/win32drv/win32drv.c create mode 100644 libs/lv_drivers/win32drv/win32drv.h create mode 100644 libs/lv_drivers/win_drv.c create mode 100644 libs/lv_drivers/win_drv.h delete mode 100644 libs/lvgl/config/lv_conf.h delete mode 100644 libs/lvgl/env_support/cmake/custom_simple_config.cmake rename tactility-esp/src/{hardare.h => hardware.h} (83%) diff --git a/.github/workflows/pc.yml b/.github/workflows/pc.yml index 686fd4f3..89b6bdae 100644 --- a/.github/workflows/pc.yml +++ b/.github/workflows/pc.yml @@ -8,9 +8,17 @@ jobs: uses: actions/checkout@v2 with: submodules: recursive + - uses: libsdl-org/setup-sdl@main + id: sdl + with: + install-linux-dependencies: true + version: 2-latest + version-sdl-image: 2-latest - name: Configure Project uses: threeal/cmake-action@v1.3.0 - name: Prepare Project run: cmake -S ./ -B build - name: Build Project + env: + USE_SDL_WITH_NAMESPACE: true run: cmake --build build diff --git a/CMakeLists.txt b/CMakeLists.txt index 2484a194..cfbd8df3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,8 +36,73 @@ add_subdirectory(tactility) add_subdirectory(tactility-core) if (NOT ESP_PLATFORM) - add_subdirectory(libs/lvgl) # Added as idf component for ESP and as library for other targets add_subdirectory(libs/freertos-kernel) add_subdirectory(libs/mbedtls) add_subdirectory(app-sim) + + # region LVGL + + add_subdirectory(libs/lvgl) # Added as idf component for ESP and as library for other targets + add_subdirectory(libs/lv_drivers) + + option(LV_USE_DRAW_SDL "Use SDL draw unit" OFF) + option(LV_USE_LIBPNG "Use libpng to decode PNG" OFF) + option(LV_USE_LIBJPEG_TURBO "Use libjpeg turbo to decode JPEG" OFF) + option(LV_USE_FFMPEG "Use libffmpeg to display video using lv_ffmpeg" OFF) + option(LV_USE_FREETYPE "Use freetype lib" OFF) + + set(CMAKE_C_STANDARD 99) # C99 # lvgl officially support C99 and above + set(CMAKE_CXX_STANDARD 17) # C17 + set(CMAKE_CXX_STANDARD_REQUIRED ON) + + set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) + + find_package(SDL2 REQUIRED CONFIG) + + add_compile_definitions($<$:LV_USE_DRAW_SDL=1>) + add_compile_definitions($<$:LV_USE_LIBPNG=1>) + add_compile_definitions($<$:LV_USE_LIBJPEG_TURBO=1>) + add_compile_definitions($<$:LV_USE_FFMPEG=1>) + + target_include_directories(lvgl + PUBLIC ${SDL2_INCLUDE_DIRS} + PUBLIC app-sim/src # for lv_conf.h and lv_drv_conf.h + ) + + if (LV_USE_DRAW_SDL) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") + # Need to install libsdl2-image-dev + # `sudo apt install libsdl2-image-dev` + # `brew install sdl2_image` + find_package(SDL2_image REQUIRED) + target_include_directories(lvgl PUBLIC ${SDL2_IMAGE_INCLUDE_DIRS}) + target_link_libraries(app-sim ${SDL2_IMAGE_LIBRARIES}) + endif(LV_USE_DRAW_SDL) + + if (LV_USE_LIBPNG) + find_package(PNG REQUIRED) + target_include_directories(lvgl PUBLIC ${PNG_INCLUDE_DIR}) + target_link_libraries(app-sim ${PNG_LIBRARY}) + endif(LV_USE_LIBPNG) + + if (LV_USE_LIBJPEG_TURBO) + # Need to install libjpeg-turbo8-dev + # `sudo apt install libjpeg-turbo8-dev` + # `brew install libjpeg-turbo` + find_package(JPEG REQUIRED) + target_include_directories(lvgl PUBLIC ${JPEG_INCLUDE_DIRS}) + target_link_libraries(app-sim ${JPEG_LIBRARIES}) + endif(LV_USE_LIBJPEG_TURBO) + + if (LV_USE_FFMPEG) + target_link_libraries(main avformat avcodec avutil swscale) + endif(LV_USE_FFMPEG) + + if (LV_USE_FREETYPE) + find_package(Freetype REQUIRED) + target_link_libraries(app-sim ${FREETYPE_LIBRARIES}) + target_include_directories(lvgl PUBLIC ${FREETYPE_INCLUDE_DIRS}) + endif(LV_USE_FREETYPE) + + #endregion endif() diff --git a/README.md b/README.md index 982707a0..bcb10723 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,17 @@ Tactility is a front-end application platform for ESP32. It is mainly intended for touchscreen devices. It provides an application framework that is based on code from the [Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware/) project. -Tactility provides: +![Tactility shown on a Lilygo T-Deck device and on PC](docs/pics/tactility-showcase.jpg) + +Tactility features: - A hardware abstraction layer - UI capabilities (via LVGL) - An application platform that can run apps and services +- PC app support to speed up development for ESP32 apps Requirements: -- ESP32 (any?) +- ESP32 (any?) with a display (connected via SPI or I2C) - [esp-idf 5.1.2](https://docs.espressif.com/projects/esp-idf/en/v5.1.2/esp32/get-started/index.html) or a newer v5.1.x -- a display (connected via SPI or I2C) **Status: Alpha** diff --git a/app-sim/CMakeLists.txt b/app-sim/CMakeLists.txt index 90036fb9..1561a5ea 100644 --- a/app-sim/CMakeLists.txt +++ b/app-sim/CMakeLists.txt @@ -1,10 +1,17 @@ cmake_minimum_required(VERSION 3.16) -file(GLOB SOURCES "src/*.c") +file(GLOB_RECURSE SOURCES "src/*.c") add_executable(app-sim ${SOURCES}) -target_link_libraries(app-sim PRIVATE tactility) -target_link_libraries(app-sim PRIVATE tactility-core) +target_link_libraries(app-sim + PRIVATE tactility + PRIVATE tactility-core + PRIVATE lvgl + PRIVATE lv_drivers +) + +find_package(SDL2 REQUIRED CONFIG) +target_link_libraries(app-sim PRIVATE ${SDL2_LIBRARIES}) +include_directories(${SDL2_INCLUDE_DIRS}) add_definitions(-D_Nullable=) add_definitions(-D_Nonnull=) - diff --git a/app-sim/src/freertos.c b/app-sim/src/freertos.c new file mode 100644 index 00000000..b441acdb --- /dev/null +++ b/app-sim/src/freertos.c @@ -0,0 +1,65 @@ +#include "tactility.h" + +#include "FreeRTOS.h" +#include "task.h" + +#define TAG "freertos" + +#define mainQUEUE_RECEIVE_TASK_PRIORITY (tskIDLE_PRIORITY + 2) + +_Noreturn void app_main(); + +bool lvgl_is_ready(); +void lvgl_task(void*); + +void app_main_task(TT_UNUSED void* parameter) { + while (!lvgl_is_ready()) { + TT_LOG_I(TAG, "waiting for lvgl task"); + vTaskDelay(50); + } + + app_main(); +} + +int main() { + // Create the main app loop, like ESP-IDF + xTaskCreate( + lvgl_task, + "lvgl", + 8192, + NULL, + mainQUEUE_RECEIVE_TASK_PRIORITY + 2, + NULL + ); + + xTaskCreate( + app_main_task, + "app_main", + 8192, + NULL, + mainQUEUE_RECEIVE_TASK_PRIORITY + 1, + NULL + ); + + // Blocks forever + vTaskStartScheduler(); +} + +/** + * Assert implementation as defined in the FreeRTOSConfig.h + * It allows you to set breakpoints and debug asserts. + */ +void vAssertCalled(TT_UNUSED unsigned long line, TT_UNUSED const char* const file) { + static portBASE_TYPE xPrinted = pdFALSE; + volatile uint32_t set_to_nonzero_in_debugger_to_continue = 0; + + TT_LOG_E(TAG, "assert triggered"); + taskENTER_CRITICAL(); + { + // Step out by attaching a debugger and setting set_to_nonzero_in_debugger_to_continue + while (set_to_nonzero_in_debugger_to_continue == 0) { + // NO-OP + } + } + taskEXIT_CRITICAL(); +} diff --git a/app-sim/src/hello_world/hello_world.c b/app-sim/src/hello_world/hello_world.c new file mode 100644 index 00000000..bb101e77 --- /dev/null +++ b/app-sim/src/hello_world/hello_world.c @@ -0,0 +1,22 @@ +#include "hello_world.h" +#include "services/gui/gui.h" +#include "services/loader/loader.h" + +static void app_show(TT_UNUSED App app, lv_obj_t* parent) { + lv_obj_t* label = lv_label_create(parent); + lv_label_set_recolor(label, true); + lv_obj_set_width(label, 200); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); + lv_label_set_text(label, "Hello, world!"); + lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); +} + +const AppManifest hello_world_app = { + .id = "helloworld", + .name = "Hello World", + .icon = NULL, + .type = AppTypeUser, + .on_start = NULL, + .on_stop = NULL, + .on_show = &app_show +}; diff --git a/app-sim/src/hello_world/hello_world.h b/app-sim/src/hello_world/hello_world.h new file mode 100644 index 00000000..f2563e22 --- /dev/null +++ b/app-sim/src/hello_world/hello_world.h @@ -0,0 +1,5 @@ +#pragma once + +#include "app_manifest.h" + +extern const AppManifest hello_world_app; diff --git a/app-sim/src/lv_conf.h b/app-sim/src/lv_conf.h new file mode 100644 index 00000000..8ab747de --- /dev/null +++ b/app-sim/src/lv_conf.h @@ -0,0 +1,700 @@ +/** + * @file lv_conf.h + * Configuration file for v8.2.0 + */ + +/* + * Copy this file as `lv_conf.h` + * 1. simply next to the `lvgl` folder + * 2. or any other places and + * - define `LV_CONF_INCLUDE_SIMPLE` + * - add the path as include path + */ + +/* clang-format off */ +#if 1 /*Set it to "1" to enable content*/ + +#ifndef LV_CONF_H +#define LV_CONF_H + +#include + +/*==================== + COLOR SETTINGS + *====================*/ + +/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/ +#define LV_COLOR_DEPTH 32 + +/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/ +#define LV_COLOR_16_SWAP 0 + +/*Enable more complex drawing routines to manage screens transparency. + *Can be used if the UI is above another layer, e.g. an OSD menu or video player. + *Requires `LV_COLOR_DEPTH = 32` colors and the screen's `bg_opa` should be set to non LV_OPA_COVER value*/ +#define LV_COLOR_SCREEN_TRANSP 0 + +/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently. + * 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */ +#define LV_COLOR_MIX_ROUND_OFS (LV_COLOR_DEPTH == 32 ? 0: 128) + +/*Images pixels with this color will not be drawn if they are chroma keyed)*/ +#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00) /*pure green*/ + +/*========================= + MEMORY SETTINGS + *=========================*/ + +/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/ +#define LV_MEM_CUSTOM 0 +#if LV_MEM_CUSTOM == 0 + /*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/ + #define LV_MEM_SIZE (128 * 1024U) /*[bytes]*/ + + /*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/ + #define LV_MEM_ADR 0 /*0: unused*/ + /*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/ + #if LV_MEM_ADR == 0 + //#define LV_MEM_POOL_INCLUDE your_alloc_library /* Uncomment if using an external allocator*/ + //#define LV_MEM_POOL_ALLOC your_alloc /* Uncomment if using an external allocator*/ + #endif + +#else /*LV_MEM_CUSTOM*/ + #define LV_MEM_CUSTOM_INCLUDE /*Header for the dynamic memory function*/ + #define LV_MEM_CUSTOM_ALLOC malloc + #define LV_MEM_CUSTOM_FREE free + #define LV_MEM_CUSTOM_REALLOC realloc +#endif /*LV_MEM_CUSTOM*/ + +/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms. + *You will see an error log message if there wasn't enough buffers. */ +#define LV_MEM_BUF_MAX_NUM 16 + +/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/ +#define LV_MEMCPY_MEMSET_STD 0 + +/*==================== + HAL SETTINGS + *====================*/ + +/*Default display refresh period. LVG will redraw changed areas with this period time*/ +#define LV_DISP_DEF_REFR_PERIOD 17 /*[ms]*/ + +/*Input device read period in milliseconds*/ +#define LV_INDEV_DEF_READ_PERIOD 17 /*[ms]*/ + +/*Use a custom tick source that tells the elapsed time in milliseconds. + *It removes the need to manually update the tick with `lv_tick_inc()`)*/ +#define LV_TICK_CUSTOM 1 +#if LV_TICK_CUSTOM + #define LV_TICK_CUSTOM_INCLUDE "SDL2/SDL.h" /*Header for the system time function*/ + #define LV_TICK_CUSTOM_SYS_TIME_EXPR (SDL_GetTicks()) /*Expression evaluating to current system time in ms*/ +#endif /*LV_TICK_CUSTOM*/ + +/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings. + *(Not so important, you can adjust it to modify default sizes and spaces)*/ +#define LV_DPI_DEF 130 /*[px/inch]*/ + +/*======================= + * FEATURE CONFIGURATION + *=======================*/ + +/*------------- + * Drawing + *-----------*/ + +/*Enable complex draw engine. + *Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/ +#define LV_DRAW_COMPLEX 1 +#if LV_DRAW_COMPLEX != 0 + + /*Allow buffering some shadow calculation. + *LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius` + *Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/ + #define LV_SHADOW_CACHE_SIZE 0 + + /* Set number of maximally cached circle data. + * The circumference of 1/4 circle are saved for anti-aliasing + * radius * 4 bytes are used per circle (the most often used radiuses are saved) + * 0: to disable caching */ + #define LV_CIRCLE_CACHE_SIZE 4 +#endif /*LV_DRAW_COMPLEX*/ + +/*Default image cache size. Image caching keeps the images opened. + *If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added) + *With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images. + *However the opened images might consume additional RAM. + *0: to disable caching*/ +#define LV_IMG_CACHE_DEF_SIZE 8 + +/*Number of stops allowed per gradient. Increase this to allow more stops. + *This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/ +#define LV_GRADIENT_MAX_STOPS 2 + +/*Default gradient buffer size. + *When LVGL calculates the gradient "maps" it can save them into a cache to avoid calculating them again. + *LV_GRAD_CACHE_DEF_SIZE sets the size of this cache in bytes. + *If the cache is too small the map will be allocated only while it's required for the drawing. + *0 mean no caching.*/ +#define LV_GRAD_CACHE_DEF_SIZE 8*1024 + +/*Allow dithering the gradients (to achieve visual smooth color gradients on limited color depth display) + *LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface + *The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */ +#define LV_DITHER_GRADIENT 1 +#if LV_DITHER_GRADIENT + /*Add support for error diffusion dithering. + *Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing. + *The increase in memory consumption is (24 bits * object's width)*/ + #define LV_DITHER_ERROR_DIFFUSION 1 +#endif + +/*Maximum buffer size to allocate for rotation. + *Only used if software rotation is enabled in the display driver.*/ +#define LV_DISP_ROT_MAX_BUF (32*1024) + +/*------------- + * GPU + *-----------*/ + +/*Use STM32's DMA2D (aka Chrom Art) GPU*/ +#define LV_USE_GPU_STM32_DMA2D 0 +#if LV_USE_GPU_STM32_DMA2D + /*Must be defined to include path of CMSIS header of target processor + e.g. "stm32f769xx.h" or "stm32f429xx.h"*/ + #define LV_GPU_DMA2D_CMSIS_INCLUDE +#endif + +/*Use NXP's PXP GPU iMX RTxxx platforms*/ +#define LV_USE_GPU_NXP_PXP 0 +#if LV_USE_GPU_NXP_PXP + /*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c) + * and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS + * has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected. + *0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init() + */ + #define LV_USE_GPU_NXP_PXP_AUTO_INIT 0 +#endif + +/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/ +#define LV_USE_GPU_NXP_VG_LITE 0 + +/*Use SDL renderer API*/ +#define LV_USE_GPU_SDL 0 +#if LV_USE_GPU_SDL + #define LV_GPU_SDL_INCLUDE_PATH + /*Texture cache size, 8MB by default*/ + #define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8) + /*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/ + #define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6)) +#endif + +/*------------- + * Logging + *-----------*/ + +/*Enable the log module*/ +#define LV_USE_LOG 1 +#if LV_USE_LOG + + /*How important log should be added: + *LV_LOG_LEVEL_TRACE A lot of logs to give detailed information + *LV_LOG_LEVEL_INFO Log important events + *LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem + *LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail + *LV_LOG_LEVEL_USER Only logs added by the user + *LV_LOG_LEVEL_NONE Do not log anything*/ + #define LV_LOG_LEVEL LV_LOG_LEVEL_WARN + + /*1: Print the log with 'printf'; + *0: User need to register a callback with `lv_log_register_print_cb()`*/ + #define LV_LOG_PRINTF 1 + + /*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/ + #define LV_LOG_TRACE_MEM 1 + #define LV_LOG_TRACE_TIMER 1 + #define LV_LOG_TRACE_INDEV 1 + #define LV_LOG_TRACE_DISP_REFR 1 + #define LV_LOG_TRACE_EVENT 1 + #define LV_LOG_TRACE_OBJ_CREATE 1 + #define LV_LOG_TRACE_LAYOUT 1 + #define LV_LOG_TRACE_ANIM 1 + +#endif /*LV_USE_LOG*/ + +/*------------- + * Asserts + *-----------*/ + +/*Enable asserts if an operation is failed or an invalid data is found. + *If LV_USE_LOG is enabled an error message will be printed on failure*/ +#define LV_USE_ASSERT_NULL 1 /*Check if the parameter is NULL. (Very fast, recommended)*/ +#define LV_USE_ASSERT_MALLOC 1 /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/ +#define LV_USE_ASSERT_STYLE 1 /*Check if the styles are properly initialized. (Very fast, recommended)*/ +#define LV_USE_ASSERT_MEM_INTEGRITY 1 /*Check the integrity of `lv_mem` after critical operations. (Slow)*/ +#define LV_USE_ASSERT_OBJ 1 /*Check the object's type and existence (e.g. not deleted). (Slow)*/ + +/*Add a custom handler when assert happens e.g. to restart the MCU*/ +#define LV_ASSERT_HANDLER_INCLUDE +#define LV_ASSERT_HANDLER while(1); /*Halt by default*/ + +/*------------- + * Others + *-----------*/ + +/*1: Show CPU usage and FPS count*/ +#define LV_USE_PERF_MONITOR 1 +#if LV_USE_PERF_MONITOR + #define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT +#endif + +/*1: Show the used memory and the memory fragmentation + * Requires LV_MEM_CUSTOM = 0*/ +#define LV_USE_MEM_MONITOR 1 +#if LV_USE_MEM_MONITOR + #define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT +#endif + +/*1: Draw random colored rectangles over the redrawn areas*/ +#define LV_USE_REFR_DEBUG 0 + +/*Change the built in (v)snprintf functions*/ +#define LV_SPRINTF_CUSTOM 0 +#if LV_SPRINTF_CUSTOM + #define LV_SPRINTF_INCLUDE + #define lv_snprintf snprintf + #define lv_vsnprintf vsnprintf +#else /*LV_SPRINTF_CUSTOM*/ + #define LV_SPRINTF_USE_FLOAT 0 +#endif /*LV_SPRINTF_CUSTOM*/ + +#define LV_USE_USER_DATA 1 + +/*Garbage Collector settings + *Used if lvgl is bound to higher level language and the memory is managed by that language*/ +#define LV_ENABLE_GC 0 +#if LV_ENABLE_GC != 0 + #define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/ +#endif /*LV_ENABLE_GC*/ + +/*===================== + * COMPILER SETTINGS + *====================*/ + +/*For big endian systems set to 1*/ +#define LV_BIG_ENDIAN_SYSTEM 0 + +/*Define a custom attribute to `lv_tick_inc` function*/ +#define LV_ATTRIBUTE_TICK_INC + +/*Define a custom attribute to `lv_timer_handler` function*/ +#define LV_ATTRIBUTE_TIMER_HANDLER + +/*Define a custom attribute to `lv_disp_flush_ready` function*/ +#define LV_ATTRIBUTE_FLUSH_READY + +/*Required alignment size for buffers*/ +#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1 + +/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default). + * E.g. __attribute__((aligned(4)))*/ +#define LV_ATTRIBUTE_MEM_ALIGN + +/*Attribute to mark large constant arrays for example font's bitmaps*/ +#define LV_ATTRIBUTE_LARGE_CONST + +/*Compiler prefix for a big array declaration in RAM*/ +#define LV_ATTRIBUTE_LARGE_RAM_ARRAY + +/*Place performance critical functions into a faster memory (e.g RAM)*/ +#define LV_ATTRIBUTE_FAST_MEM + +/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/ +#define LV_ATTRIBUTE_DMA + +/*Export integer constant to binding. This macro is used with constants in the form of LV_ that + *should also appear on LVGL binding API such as Micropython.*/ +#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/ + +/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/ +#define LV_USE_LARGE_COORD 0 + +/*================== + * FONT USAGE + *===================*/ + +/*Montserrat fonts with ASCII range and some symbols using bpp = 4 + *https://fonts.google.com/specimen/Montserrat*/ +#define LV_FONT_MONTSERRAT_8 1 +#define LV_FONT_MONTSERRAT_10 1 +#define LV_FONT_MONTSERRAT_12 1 +#define LV_FONT_MONTSERRAT_14 1 +#define LV_FONT_MONTSERRAT_16 1 +#define LV_FONT_MONTSERRAT_18 1 +#define LV_FONT_MONTSERRAT_20 1 +#define LV_FONT_MONTSERRAT_22 1 +#define LV_FONT_MONTSERRAT_24 1 +#define LV_FONT_MONTSERRAT_26 1 +#define LV_FONT_MONTSERRAT_28 1 +#define LV_FONT_MONTSERRAT_30 1 +#define LV_FONT_MONTSERRAT_32 1 +#define LV_FONT_MONTSERRAT_34 1 +#define LV_FONT_MONTSERRAT_36 1 +#define LV_FONT_MONTSERRAT_38 1 +#define LV_FONT_MONTSERRAT_40 1 +#define LV_FONT_MONTSERRAT_42 1 +#define LV_FONT_MONTSERRAT_44 1 +#define LV_FONT_MONTSERRAT_46 1 +#define LV_FONT_MONTSERRAT_48 1 + +/*Demonstrate special features*/ +#define LV_FONT_MONTSERRAT_12_SUBPX 1 +#define LV_FONT_MONTSERRAT_28_COMPRESSED 1 /*bpp = 3*/ +#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 1 /*Hebrew, Arabic, Persian letters and all their forms*/ +#define LV_FONT_SIMSUN_16_CJK 1 /*1000 most common CJK radicals*/ + +/*Pixel perfect monospace fonts*/ +#define LV_FONT_UNSCII_8 1 +#define LV_FONT_UNSCII_16 1 + +/*Optionally declare custom fonts here. + *You can use these fonts as default font too and they will be available globally. + *E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/ +#define LV_FONT_CUSTOM_DECLARE + +/*Always set a default font*/ +#define LV_FONT_DEFAULT &lv_font_montserrat_14 + +/*Enable handling large font and/or fonts with a lot of characters. + *The limit depends on the font size, font face and bpp. + *Compiler error will be triggered if a font needs it.*/ +#define LV_FONT_FMT_TXT_LARGE 1 + +/*Enables/disables support for compressed fonts.*/ +#define LV_USE_FONT_COMPRESSED 1 + +/*Enable subpixel rendering*/ +#define LV_USE_FONT_SUBPX 1 +#if LV_USE_FONT_SUBPX + /*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/ + #define LV_FONT_SUBPX_BGR 0 /*0: RGB; 1:BGR order*/ +#endif + +/*================= + * TEXT SETTINGS + *=================*/ + +/** + * Select a character encoding for strings. + * Your IDE or editor should have the same character encoding + * - LV_TXT_ENC_UTF8 + * - LV_TXT_ENC_ASCII + */ +#define LV_TXT_ENC LV_TXT_ENC_UTF8 + +/*Can break (wrap) texts on these chars*/ +#define LV_TXT_BREAK_CHARS " ,.;:-_" + +/*If a word is at least this long, will break wherever "prettiest" + *To disable, set to a value <= 0*/ +#define LV_TXT_LINE_BREAK_LONG_LEN 0 + +/*Minimum number of characters in a long word to put on a line before a break. + *Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/ +#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3 + +/*Minimum number of characters in a long word to put on a line after a break. + *Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/ +#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3 + +/*The control character to use for signalling text recoloring.*/ +#define LV_TXT_COLOR_CMD "#" + +/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts. + *The direction will be processed according to the Unicode Bidirectional Algorithm: + *https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/ +#define LV_USE_BIDI 1 +#if LV_USE_BIDI + /*Set the default direction. Supported values: + *`LV_BASE_DIR_LTR` Left-to-Right + *`LV_BASE_DIR_RTL` Right-to-Left + *`LV_BASE_DIR_AUTO` detect texts base direction*/ + #define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO +#endif + +/*Enable Arabic/Persian processing + *In these languages characters should be replaced with an other form based on their position in the text*/ +#define LV_USE_ARABIC_PERSIAN_CHARS 1 + +/*================== + * WIDGET USAGE + *================*/ + +/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/ + +#define LV_USE_ARC 1 + +#define LV_USE_ANIMIMG 1 + +#define LV_USE_BAR 1 + +#define LV_USE_BTN 1 + +#define LV_USE_BTNMATRIX 1 + +#define LV_USE_CANVAS 1 + +#define LV_USE_CHECKBOX 1 + +#define LV_USE_DROPDOWN 1 /*Requires: lv_label*/ + +#define LV_USE_IMG 1 /*Requires: lv_label*/ + +#define LV_USE_LABEL 1 +#if LV_USE_LABEL + #define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/ + #define LV_LABEL_LONG_TXT_HINT 1 /*Store some extra info in labels to speed up drawing of very long texts*/ +#endif + +#define LV_USE_LINE 1 + +#define LV_USE_ROLLER 1 /*Requires: lv_label*/ +#if LV_USE_ROLLER + #define LV_ROLLER_INF_PAGES 7 /*Number of extra "pages" when the roller is infinite*/ +#endif + +#define LV_USE_SLIDER 1 /*Requires: lv_bar*/ + +#define LV_USE_SWITCH 1 + +#define LV_USE_TEXTAREA 1 /*Requires: lv_label*/ +#if LV_USE_TEXTAREA != 0 + #define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/ +#endif + +#define LV_USE_TABLE 1 + +/*================== + * EXTRA COMPONENTS + *==================*/ + +/*----------- + * Widgets + *----------*/ +#define LV_USE_CALENDAR 1 +#if LV_USE_CALENDAR + #define LV_CALENDAR_WEEK_STARTS_MONDAY 0 + #if LV_CALENDAR_WEEK_STARTS_MONDAY + #define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"} + #else + #define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"} + #endif + + #define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} + #define LV_USE_CALENDAR_HEADER_ARROW 1 + #define LV_USE_CALENDAR_HEADER_DROPDOWN 1 +#endif /*LV_USE_CALENDAR*/ + +#define LV_USE_CHART 1 + +#define LV_USE_COLORWHEEL 1 + +#define LV_USE_IMGBTN 1 + +#define LV_USE_KEYBOARD 1 + +#define LV_USE_LED 1 + +#define LV_USE_LIST 1 + +#define LV_USE_MENU 1 + +#define LV_USE_METER 1 + +#define LV_USE_MSGBOX 1 + +#define LV_USE_SPINBOX 1 + +#define LV_USE_SPINNER 1 + +#define LV_USE_TABVIEW 1 + +#define LV_USE_TILEVIEW 1 + +#define LV_USE_WIN 1 + +#define LV_USE_SPAN 1 +#if LV_USE_SPAN + /*A line text can contain maximum num of span descriptor */ + #define LV_SPAN_SNIPPET_STACK_SIZE 64 +#endif + +/*----------- + * Themes + *----------*/ + +/*A simple, impressive and very complete theme*/ +#define LV_USE_THEME_DEFAULT 1 +#if LV_USE_THEME_DEFAULT + + /*0: Light mode; 1: Dark mode*/ + #define LV_THEME_DEFAULT_DARK 0 + + /*1: Enable grow on press*/ + #define LV_THEME_DEFAULT_GROW 1 + + /*Default transition time in [ms]*/ + #define LV_THEME_DEFAULT_TRANSITION_TIME 80 +#endif /*LV_USE_THEME_DEFAULT*/ + +/*A very simple theme that is a good starting point for a custom theme*/ +#define LV_USE_THEME_BASIC 1 + +/*A theme designed for monochrome displays*/ +#define LV_USE_THEME_MONO 1 + +/*----------- + * Layouts + *----------*/ + +/*A layout similar to Flexbox in CSS.*/ +#define LV_USE_FLEX 1 + +/*A layout similar to Grid in CSS.*/ +#define LV_USE_GRID 1 + +/*--------------------- + * 3rd party libraries + *--------------------*/ + +/*File system interfaces for common APIs */ + +/*API for fopen, fread, etc*/ +#define LV_USE_FS_STDIO 1 +#if LV_USE_FS_STDIO + #define LV_FS_STDIO_LETTER 'A' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ + #define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/ + #define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ +#endif + +/*API for open, read, etc*/ +#define LV_USE_FS_POSIX 0 +#if LV_USE_FS_POSIX + #define LV_FS_POSIX_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ + #define LV_FS_POSIX_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/ + #define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ +#endif + +/*API for CreateFile, ReadFile, etc*/ +#define LV_USE_FS_WIN32 0 +#if LV_USE_FS_WIN32 + #define LV_FS_WIN32_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ + #define LV_FS_WIN32_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/ + #define LV_FS_WIN32_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ +#endif + +/*API for FATFS (needs to be added separately). Uses f_open, f_read, etc*/ +#define LV_USE_FS_FATFS 0 +#if LV_USE_FS_FATFS + #define LV_FS_FATFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ + #define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ +#endif + +/*PNG decoder library*/ +#define LV_USE_PNG 1 + +/*BMP decoder library*/ +#define LV_USE_BMP 1 + +/* JPG + split JPG decoder library. + * Split JPG is a custom format optimized for embedded systems. */ +#define LV_USE_SJPG 1 + +/*GIF decoder library*/ +#define LV_USE_GIF 1 + +/*QR code library*/ +#define LV_USE_QRCODE 1 + +/*FreeType library*/ +#define LV_USE_FREETYPE 0 +#if LV_USE_FREETYPE + /*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/ + #define LV_FREETYPE_CACHE_SIZE (16 * 1024) + #if LV_FREETYPE_CACHE_SIZE >= 0 + /* 1: bitmap cache use the sbit cache, 0:bitmap cache use the image cache. */ + /* sbit cache:it is much more memory efficient for small bitmaps(font size < 256) */ + /* if font size >= 256, must be configured as image cache */ + #define LV_FREETYPE_SBIT_CACHE 0 + /* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */ + /* (0:use system defaults) */ + #define LV_FREETYPE_CACHE_FT_FACES 0 + #define LV_FREETYPE_CACHE_FT_SIZES 0 + #endif +#endif + +/*Rlottie library*/ +#define LV_USE_RLOTTIE 0 + +/*FFmpeg library for image decoding and playing videos + *Supports all major image formats so do not enable other image decoder with it*/ +#define LV_USE_FFMPEG 0 +#if LV_USE_FFMPEG + /*Dump input information to stderr*/ + #define LV_FFMPEG_AV_DUMP_FORMAT 0 +#endif + +/*----------- + * Others + *----------*/ + +/*1: Enable API to take snapshot for object*/ +#define LV_USE_SNAPSHOT 1 + +/*1: Enable Monkey test*/ +#define LV_USE_MONKEY 1 + +/*1: Enable grid navigation*/ +#define LV_USE_GRIDNAV 1 + +/*================== +* EXAMPLES +*==================*/ + +/*Enable the examples to be built with the library*/ +#define LV_BUILD_EXAMPLES 1 + +/*=================== + * DEMO USAGE + ====================*/ + +/*Show some widget. It might be required to increase `LV_MEM_SIZE` */ +#define LV_USE_DEMO_WIDGETS 1 +#if LV_USE_DEMO_WIDGETS +#define LV_DEMO_WIDGETS_SLIDESHOW 0 +#endif + +/*Demonstrate the usage of encoder and keyboard*/ +#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0 + +/*Benchmark your system*/ +#define LV_USE_DEMO_BENCHMARK 0 + +/*Stress test for LVGL*/ +#define LV_USE_DEMO_STRESS 0 + +/*Music player demo*/ +#define LV_USE_DEMO_MUSIC 0 +#if LV_USE_DEMO_MUSIC +# define LV_DEMO_MUSIC_SQUARE 0 +# define LV_DEMO_MUSIC_LANDSCAPE 0 +# define LV_DEMO_MUSIC_ROUND 0 +# define LV_DEMO_MUSIC_LARGE 0 +# define LV_DEMO_MUSIC_AUTO_PLAY 0 +#endif + +/*--END OF LV_CONF_H--*/ + +#endif /*LV_CONF_H*/ + +#endif /*End of "Content enable"*/ diff --git a/app-sim/src/lv_drv_conf.h b/app-sim/src/lv_drv_conf.h new file mode 100644 index 00000000..bc8f6fe9 --- /dev/null +++ b/app-sim/src/lv_drv_conf.h @@ -0,0 +1,496 @@ +/** + * @file lv_drv_conf.h + * Configuration file for v8.3.0-dev + */ + +/* + * COPY THIS FILE AS lv_drv_conf.h + */ + +/* clang-format off */ +#if 1 /*Set it to "1" to enable the content*/ + +#ifndef LV_DRV_CONF_H +#define LV_DRV_CONF_H + +#include "lv_conf.h" + +/********************* + * DELAY INTERFACE + *********************/ +#define LV_DRV_DELAY_INCLUDE /*Dummy include by default*/ +#define LV_DRV_DELAY_US(us) /*delay_us(us)*/ /*Delay the given number of microseconds*/ +#define LV_DRV_DELAY_MS(ms) /*delay_ms(ms)*/ /*Delay the given number of milliseconds*/ + +/********************* + * DISPLAY INTERFACE + *********************/ + +/*------------ + * Common + *------------*/ +#define LV_DRV_DISP_INCLUDE /*Dummy include by default*/ +#define LV_DRV_DISP_CMD_DATA(val) /*pin_x_set(val)*/ /*Set the command/data pin to 'val'*/ +#define LV_DRV_DISP_RST(val) /*pin_x_set(val)*/ /*Set the reset pin to 'val'*/ + +/*--------- + * SPI + *---------*/ +#define LV_DRV_DISP_SPI_CS(val) /*spi_cs_set(val)*/ /*Set the SPI's Chip select to 'val'*/ +#define LV_DRV_DISP_SPI_WR_BYTE(data) /*spi_wr(data)*/ /*Write a byte the SPI bus*/ +#define LV_DRV_DISP_SPI_WR_ARRAY(adr, n) /*spi_wr_mem(adr, n)*/ /*Write 'n' bytes to SPI bus from 'adr'*/ + +/*------------------ + * Parallel port + *-----------------*/ +#define LV_DRV_DISP_PAR_CS(val) /*par_cs_set(val)*/ /*Set the Parallel port's Chip select to 'val'*/ +#define LV_DRV_DISP_PAR_SLOW /*par_slow()*/ /*Set low speed on the parallel port*/ +#define LV_DRV_DISP_PAR_FAST /*par_fast()*/ /*Set high speed on the parallel port*/ +#define LV_DRV_DISP_PAR_WR_WORD(data) /*par_wr(data)*/ /*Write a word to the parallel port*/ +#define LV_DRV_DISP_PAR_WR_ARRAY(adr, n) /*par_wr_mem(adr,n)*/ /*Write 'n' bytes to Parallel ports from 'adr'*/ + +/*************************** + * INPUT DEVICE INTERFACE + ***************************/ + +/*---------- + * Common + *----------*/ +#define LV_DRV_INDEV_INCLUDE /*Dummy include by default*/ +#define LV_DRV_INDEV_RST(val) /*pin_x_set(val)*/ /*Set the reset pin to 'val'*/ +#define LV_DRV_INDEV_IRQ_READ 0 /*pn_x_read()*/ /*Read the IRQ pin*/ + +/*--------- + * SPI + *---------*/ +#define LV_DRV_INDEV_SPI_CS(val) /*spi_cs_set(val)*/ /*Set the SPI's Chip select to 'val'*/ +#define LV_DRV_INDEV_SPI_XCHG_BYTE(data) 0 /*spi_xchg(val)*/ /*Write 'val' to SPI and give the read value*/ + +/*--------- + * I2C + *---------*/ +#define LV_DRV_INDEV_I2C_START /*i2c_start()*/ /*Make an I2C start*/ +#define LV_DRV_INDEV_I2C_STOP /*i2c_stop()*/ /*Make an I2C stop*/ +#define LV_DRV_INDEV_I2C_RESTART /*i2c_restart()*/ /*Make an I2C restart*/ +#define LV_DRV_INDEV_I2C_WR(data) /*i2c_wr(data)*/ /*Write a byte to the I1C bus*/ +#define LV_DRV_INDEV_I2C_READ(last_read) 0 /*i2c_rd()*/ /*Read a byte from the I2C bud*/ + + +/********************* + * DISPLAY DRIVERS + *********************/ + +/*------------------- + * SDL + *-------------------*/ + +/* SDL based drivers for display, mouse, mousewheel and keyboard*/ +#ifndef USE_SDL +# define USE_SDL 1 +#endif + +/* Hardware accelerated SDL driver */ +#ifndef USE_SDL_GPU +# define USE_SDL_GPU 0 +#endif + +#define SDL_WINDOW_NAME "Tactility" + +#if USE_SDL || USE_SDL_GPU +# define SDL_HOR_RES 320 +# define SDL_VER_RES 240 + +/* Scale window by this factor (useful when simulating small screens) */ +# define SDL_ZOOM 1 + +/* Used to test true double buffering with only address changing. + * Use 2 draw buffers, bith with SDL_HOR_RES x SDL_VER_RES size*/ +# define SDL_DOUBLE_BUFFERED 0 + +/*Eclipse: Visual Studio: */ +# define SDL_INCLUDE_PATH + +/*Open two windows to test multi display support*/ +# define SDL_DUAL_DISPLAY 0 +#endif + +/*------------------- + * Monitor of PC + *-------------------*/ + +/*DEPRECATED: Use the SDL driver instead. */ +#ifndef USE_MONITOR +# define USE_MONITOR 0 +#endif + +#if USE_MONITOR +# define MONITOR_HOR_RES 480 +# define MONITOR_VER_RES 320 + +/* Scale window by this factor (useful when simulating small screens) */ +# define MONITOR_ZOOM 1 + +/* Used to test true double buffering with only address changing. + * Use 2 draw buffers, bith with MONITOR_HOR_RES x MONITOR_VER_RES size*/ +# define MONITOR_DOUBLE_BUFFERED 0 + +/*Eclipse: Visual Studio: */ +# define MONITOR_SDL_INCLUDE_PATH + +/*Open two windows to test multi display support*/ +# define MONITOR_DUAL 0 +#endif + +/*----------------------------------- + * Native Windows (including mouse) + *----------------------------------*/ +#ifndef USE_WINDOWS +# define USE_WINDOWS 0 +#endif + +#if USE_WINDOWS +# define WINDOW_HOR_RES 480 +# define WINDOW_VER_RES 320 +#endif + +/*---------------------------- + * Native Windows (win32drv) + *---------------------------*/ +#ifndef USE_WIN32DRV +# define USE_WIN32DRV 0 +#endif + +#if USE_WIN32DRV +/* Scale window by this factor (useful when simulating small screens) */ +# define WIN32DRV_MONITOR_ZOOM 1 +#endif + +/*---------------------------------------- + * GTK drivers (monitor, mouse, keyboard + *---------------------------------------*/ +#ifndef USE_GTK +# define USE_GTK 0 +#endif + +/*---------------------------------------- + * Wayland drivers (monitor, mouse, keyboard, touchscreen) + *---------------------------------------*/ +#ifndef USE_WAYLAND +# define USE_WAYLAND 0 +#endif + +#if USE_WAYLAND +/* Support for client-side decorations */ +# ifndef LV_WAYLAND_CLIENT_SIDE_DECORATIONS +# define LV_WAYLAND_CLIENT_SIDE_DECORATIONS 1 +# endif +/* Support for (deprecated) wl-shell protocol */ +# ifndef LV_WAYLAND_WL_SHELL +# define LV_WAYLAND_WL_SHELL 1 +# endif +/* Support for xdg-shell protocol */ +# ifndef LV_WAYLAND_XDG_SHELL +# define LV_WAYLAND_XDG_SHELL 0 +# endif +#endif + +/*---------------- + * SSD1963 + *--------------*/ +#ifndef USE_SSD1963 +# define USE_SSD1963 0 +#endif + +#if USE_SSD1963 +# define SSD1963_HOR_RES LV_HOR_RES +# define SSD1963_VER_RES LV_VER_RES +# define SSD1963_HT 531 +# define SSD1963_HPS 43 +# define SSD1963_LPS 8 +# define SSD1963_HPW 10 +# define SSD1963_VT 288 +# define SSD1963_VPS 12 +# define SSD1963_FPS 4 +# define SSD1963_VPW 10 +# define SSD1963_HS_NEG 0 /*Negative hsync*/ +# define SSD1963_VS_NEG 0 /*Negative vsync*/ +# define SSD1963_ORI 0 /*0, 90, 180, 270*/ +# define SSD1963_COLOR_DEPTH 16 +#endif + +/*---------------- + * R61581 + *--------------*/ +#ifndef USE_R61581 +# define USE_R61581 0 +#endif + +#if USE_R61581 +# define R61581_HOR_RES LV_HOR_RES +# define R61581_VER_RES LV_VER_RES +# define R61581_HSPL 0 /*HSYNC signal polarity*/ +# define R61581_HSL 10 /*HSYNC length (Not Implemented)*/ +# define R61581_HFP 10 /*Horitontal Front poarch (Not Implemented)*/ +# define R61581_HBP 10 /*Horitontal Back poarch (Not Implemented */ +# define R61581_VSPL 0 /*VSYNC signal polarity*/ +# define R61581_VSL 10 /*VSYNC length (Not Implemented)*/ +# define R61581_VFP 8 /*Vertical Front poarch*/ +# define R61581_VBP 8 /*Vertical Back poarch */ +# define R61581_DPL 0 /*DCLK signal polarity*/ +# define R61581_EPL 1 /*ENABLE signal polarity*/ +# define R61581_ORI 0 /*0, 180*/ +# define R61581_LV_COLOR_DEPTH 16 /*Fix 16 bit*/ +#endif + +/*------------------------------ + * ST7565 (Monochrome, low res.) + *-----------------------------*/ +#ifndef USE_ST7565 +# define USE_ST7565 0 +#endif + +#if USE_ST7565 +/*No settings*/ +#endif /*USE_ST7565*/ + +/*------------------------------ + * GC9A01 (color, low res.) + *-----------------------------*/ +#ifndef USE_GC9A01 +# define USE_GC9A01 0 +#endif + +#if USE_GC9A01 +/*No settings*/ +#endif /*USE_GC9A01*/ + +/*------------------------------------------ + * UC1610 (4 gray 160*[104|128]) + * (EA DOGXL160 160x104 tested) + *-----------------------------------------*/ +#ifndef USE_UC1610 +# define USE_UC1610 0 +#endif + +#if USE_UC1610 +# define UC1610_HOR_RES LV_HOR_RES +# define UC1610_VER_RES LV_VER_RES +# define UC1610_INIT_CONTRAST 33 /* init contrast, values in [%] */ +# define UC1610_INIT_HARD_RST 0 /* 1 : hardware reset at init, 0 : software reset */ +# define UC1610_TOP_VIEW 0 /* 0 : Bottom View, 1 : Top View */ +#endif /*USE_UC1610*/ + +/*------------------------------------------------- + * SHARP memory in pixel monochrome display series + * LS012B7DD01 (184x38 pixels.) + * LS013B7DH03 (128x128 pixels.) + * LS013B7DH05 (144x168 pixels.) + * LS027B7DH01 (400x240 pixels.) (tested) + * LS032B7DD02 (336x536 pixels.) + * LS044Q7DH01 (320x240 pixels.) + *------------------------------------------------*/ +#ifndef USE_SHARP_MIP +# define USE_SHARP_MIP 0 +#endif + +#if USE_SHARP_MIP +# define SHARP_MIP_HOR_RES LV_HOR_RES +# define SHARP_MIP_VER_RES LV_VER_RES +# define SHARP_MIP_SOFT_COM_INVERSION 0 +# define SHARP_MIP_REV_BYTE(b) /*((uint8_t) __REV(__RBIT(b)))*/ /*Architecture / compiler dependent byte bits order reverse*/ +#endif /*USE_SHARP_MIP*/ + +/*------------------------------------------------- + * ILI9341 240X320 TFT LCD + *------------------------------------------------*/ +#ifndef USE_ILI9341 +# define USE_ILI9341 0 +#endif + +#if USE_ILI9341 +# define ILI9341_HOR_RES LV_HOR_RES +# define ILI9341_VER_RES LV_VER_RES +# define ILI9341_GAMMA 1 +# define ILI9341_TEARING 0 +#endif /*USE_ILI9341*/ + +/*----------------------------------------- + * Linux frame buffer device (/dev/fbx) + *-----------------------------------------*/ +#ifndef USE_FBDEV +# define USE_FBDEV 0 +#endif + +#if USE_FBDEV +# define FBDEV_PATH "/dev/fb0" +#endif + +/*----------------------------------------- + * FreeBSD frame buffer device (/dev/fbx) + *.........................................*/ +#ifndef USE_BSD_FBDEV +# define USE_BSD_FBDEV 0 +#endif + +#if USE_BSD_FBDEV +# define FBDEV_PATH "/dev/fb0" +#endif + +/*----------------------------------------- + * DRM/KMS device (/dev/dri/cardX) + *-----------------------------------------*/ +#ifndef USE_DRM +# define USE_DRM 0 +#endif + +#if USE_DRM +# define DRM_CARD "/dev/dri/card0" +# define DRM_CONNECTOR_ID -1 /* -1 for the first connected one */ +#endif + +/********************* + * INPUT DEVICES + *********************/ + +/*-------------- + * XPT2046 + *--------------*/ +#ifndef USE_XPT2046 +# define USE_XPT2046 0 +#endif + +#if USE_XPT2046 +# define XPT2046_HOR_RES 480 +# define XPT2046_VER_RES 320 +# define XPT2046_X_MIN 200 +# define XPT2046_Y_MIN 200 +# define XPT2046_X_MAX 3800 +# define XPT2046_Y_MAX 3800 +# define XPT2046_AVG 4 +# define XPT2046_X_INV 0 +# define XPT2046_Y_INV 0 +# define XPT2046_XY_SWAP 0 +#endif + +/*----------------- + * FT5406EE8 + *-----------------*/ +#ifndef USE_FT5406EE8 +# define USE_FT5406EE8 0 +#endif + +#if USE_FT5406EE8 +# define FT5406EE8_I2C_ADR 0x38 /*7 bit address*/ +#endif + +/*--------------- + * AD TOUCH + *--------------*/ +#ifndef USE_AD_TOUCH +# define USE_AD_TOUCH 0 +#endif + +#if USE_AD_TOUCH +/*No settings*/ +#endif + + +/*--------------------------------------- + * Mouse or touchpad on PC (using SDL) + *-------------------------------------*/ +/*DEPRECATED: Use the SDL driver instead. */ +#ifndef USE_MOUSE +# define USE_MOUSE 0 +#endif + +#if USE_MOUSE +/*No settings*/ +#endif + +/*------------------------------------------- + * Mousewheel as encoder on PC (using SDL) + *------------------------------------------*/ +/*DEPRECATED: Use the SDL driver instead. */ +#ifndef USE_MOUSEWHEEL +# define USE_MOUSEWHEEL 0 +#endif + +#if USE_MOUSEWHEEL +/*No settings*/ +#endif + +/*------------------------------------------------- + * Touchscreen, mouse/touchpad or keyboard as libinput interface (for Linux based systems) + *------------------------------------------------*/ +#ifndef USE_LIBINPUT +# define USE_LIBINPUT 0 +#endif + +#ifndef USE_BSD_LIBINPUT +# define USE_BSD_LIBINPUT 0 +#endif + +#if USE_LIBINPUT || USE_BSD_LIBINPUT +/*If only a single device of the same type is connected, you can also auto detect it, e.g.: + *#define LIBINPUT_NAME libinput_find_dev(LIBINPUT_CAPABILITY_TOUCH, false)*/ +# define LIBINPUT_NAME "/dev/input/event0" /*You can use the "evtest" Linux tool to get the list of devices and test them*/ + +#endif /*USE_LIBINPUT || USE_BSD_LIBINPUT*/ + +/*------------------------------------------------- + * Mouse or touchpad as evdev interface (for Linux based systems) + *------------------------------------------------*/ +#ifndef USE_EVDEV +# define USE_EVDEV 0 +#endif + +#ifndef USE_BSD_EVDEV +# define USE_BSD_EVDEV 0 +#endif + +#if USE_EVDEV || USE_BSD_EVDEV +# define EVDEV_NAME "/dev/input/event0" /*You can use the "evtest" Linux tool to get the list of devices and test them*/ +# define EVDEV_SWAP_AXES 0 /*Swap the x and y axes of the touchscreen*/ + +# define EVDEV_CALIBRATE 0 /*Scale and offset the touchscreen coordinates by using maximum and minimum values for each axis*/ + +# if EVDEV_CALIBRATE +# define EVDEV_HOR_MIN 0 /*to invert axis swap EVDEV_XXX_MIN by EVDEV_XXX_MAX*/ +# define EVDEV_HOR_MAX 4096 /*"evtest" Linux tool can help to get the correct calibraion values>*/ +# define EVDEV_VER_MIN 0 +# define EVDEV_VER_MAX 4096 +# endif /*EVDEV_CALIBRATE*/ +#endif /*USE_EVDEV*/ + +/*------------------------------------------------- + * Full keyboard support for evdev and libinput interface + *------------------------------------------------*/ +# ifndef USE_XKB +# define USE_XKB 0 +# endif + +#if USE_LIBINPUT || USE_BSD_LIBINPUT || USE_EVDEV || USE_BSD_EVDEV +# if USE_XKB +# define XKB_KEY_MAP { .rules = NULL, \ + .model = "pc101", \ + .layout = "us", \ + .variant = NULL, \ + .options = NULL } /*"setxkbmap -query" can help find the right values for your keyboard*/ +# endif /*USE_XKB*/ +#endif /*USE_LIBINPUT || USE_BSD_LIBINPUT || USE_EVDEV || USE_BSD_EVDEV*/ + +/*------------------------------- + * Keyboard of a PC (using SDL) + *------------------------------*/ +/*DEPRECATED: Use the SDL driver instead. */ +#ifndef USE_KEYBOARD +# define USE_KEYBOARD 0 +#endif + +#if USE_KEYBOARD +/*No settings*/ +#endif + +#endif /*LV_DRV_CONF_H*/ + +#endif /*End of "Content enable"*/ diff --git a/app-sim/src/lvgl_hal.c b/app-sim/src/lvgl_hal.c new file mode 100644 index 00000000..60cbf81c --- /dev/null +++ b/app-sim/src/lvgl_hal.c @@ -0,0 +1,75 @@ +#include "lvgl_hal.h" + +#include "lvgl.h" +#include "tactility_core.h" +#include + +#define TAG "lvgl_hal" + +#define BUFFER_SIZE (SDL_HOR_RES * SDL_VER_RES * 3) + +static lv_disp_t* hal_init() { + /* Use the 'monitor' driver which creates window on PC's monitor to simulate a display*/ + sdl_init(); + + /*Create a display buffer*/ + static lv_disp_draw_buf_t disp_buf1; + static lv_color_t buf1_1[BUFFER_SIZE]; + lv_disp_draw_buf_init(&disp_buf1, buf1_1, NULL, BUFFER_SIZE); + + /*Create a display*/ + static lv_disp_drv_t disp_drv; + lv_disp_drv_init(&disp_drv); /*Basic initialization*/ + disp_drv.draw_buf = &disp_buf1; + disp_drv.flush_cb = sdl_display_flush; + disp_drv.hor_res = SDL_HOR_RES; + disp_drv.ver_res = SDL_VER_RES; + + lv_disp_t* disp = lv_disp_drv_register(&disp_drv); + + lv_theme_t* th = lv_theme_default_init( + disp, + lv_palette_main(LV_PALETTE_BLUE), + lv_palette_main(LV_PALETTE_RED), + LV_THEME_DEFAULT_DARK, + LV_FONT_DEFAULT + ); + + lv_disp_set_theme(disp, th); + + lv_group_t* g = lv_group_create(); + lv_group_set_default(g); + + /* Add the mouse as input device + * Use the 'mouse' driver which reads the PC's mouse*/ + static lv_indev_drv_t indev_drv_1; + lv_indev_drv_init(&indev_drv_1); /*Basic initialization*/ + indev_drv_1.type = LV_INDEV_TYPE_POINTER; + + /*This function will be called periodically (by the library) to get the mouse position and state*/ + indev_drv_1.read_cb = sdl_mouse_read; + lv_indev_t* mouse_indev = lv_indev_drv_register(&indev_drv_1); + + static lv_indev_drv_t indev_drv_2; + lv_indev_drv_init(&indev_drv_2); /*Basic initialization*/ + indev_drv_2.type = LV_INDEV_TYPE_KEYPAD; + indev_drv_2.read_cb = sdl_keyboard_read; + lv_indev_t* kb_indev = lv_indev_drv_register(&indev_drv_2); + lv_indev_set_group(kb_indev, g); + + static lv_indev_drv_t indev_drv_3; + lv_indev_drv_init(&indev_drv_3); /*Basic initialization*/ + indev_drv_3.type = LV_INDEV_TYPE_ENCODER; + indev_drv_3.read_cb = sdl_mousewheel_read; + lv_indev_t* enc_indev = lv_indev_drv_register(&indev_drv_3); + lv_indev_set_group(enc_indev, g); + + return disp; +} + +void lvgl_hal_init() { + TT_LOG_I(TAG, "init: started"); + lv_init(); + hal_init(); + TT_LOG_I(TAG, "init: complete"); +} diff --git a/app-sim/src/lvgl_hal.h b/app-sim/src/lvgl_hal.h new file mode 100644 index 00000000..a437f1e9 --- /dev/null +++ b/app-sim/src/lvgl_hal.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void lvgl_hal_init(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/app-sim/src/lvgl_task.c b/app-sim/src/lvgl_task.c new file mode 100644 index 00000000..d0416e13 --- /dev/null +++ b/app-sim/src/lvgl_task.c @@ -0,0 +1,115 @@ +#include "lvgl_task.h" + +#include "lvgl_hal.h" +#include "tactility_core.h" +#include "thread.h" +#include "ui/lvgl_sync.h" +#include + +#include "FreeRTOS.h" +#include "semphr.h" + +#define TAG "lvgl_task" + +// Mutex for LVGL drawing +static QueueHandle_t lvgl_mutex = NULL; +static uint32_t task_max_sleep_ms = 10; +// Mutex for LVGL task state (to modify task_running state) +static QueueHandle_t task_mutex = NULL; +static bool task_running = false; + +static bool task_lock(int timeout_ticks) { + assert(task_mutex != NULL); + return xSemaphoreTakeRecursive(task_mutex, timeout_ticks) == pdTRUE; +} + +static void task_unlock() { + assert(task_mutex != NULL); + xSemaphoreGiveRecursive(task_mutex); +} + +static void task_set_running(bool running) { + assert(task_lock(configTICK_RATE_HZ / 100)); + task_running = running; + task_unlock(); +} + +static bool task_is_running() { + assert(task_lock(configTICK_RATE_HZ / 100)); + bool result = task_running; + task_unlock(); + return result; +} + +static bool lvgl_lock(int timeout_ticks) { + assert(lvgl_mutex != NULL); + return xSemaphoreTakeRecursive(lvgl_mutex, timeout_ticks) == pdTRUE; +} + +static void lvgl_unlock() { + assert(lvgl_mutex != NULL); + xSemaphoreGiveRecursive(lvgl_mutex); +} + +static void lvgl_task_init() { + if (lvgl_mutex == NULL) { + TT_LOG_D(TAG, "init: creating lvgl mutex"); + lvgl_mutex = xSemaphoreCreateRecursiveMutex(); + } + + if (task_mutex == NULL) { + TT_LOG_D(TAG, "init: creating task mutex"); + task_mutex = xSemaphoreCreateRecursiveMutex(); + } + + tt_lvgl_sync_set(&lvgl_lock, &lvgl_unlock); +} + +static void lvgl_task_deinit() { + if (lvgl_mutex) { + vSemaphoreDelete(lvgl_mutex); + lvgl_mutex = NULL; + } + if (task_mutex) { + vSemaphoreDelete(task_mutex); + task_mutex = NULL; + } +#if LV_ENABLE_GC || !LV_MEM_CUSTOM + lv_deinit(); +#endif +} + +void lvgl_task(TT_UNUSED void* arg) { + lvgl_hal_init(); + lvgl_task_init(); + + uint32_t task_delay_ms = task_max_sleep_ms; + + task_set_running(true); + + while (task_is_running()) { + if (lvgl_lock(0)) { + task_delay_ms = lv_timer_handler(); + lvgl_unlock(); + } + if ((task_delay_ms > task_max_sleep_ms) || (1 == task_delay_ms)) { + task_delay_ms = task_max_sleep_ms; + } else if (task_delay_ms < 1) { + task_delay_ms = 1; + } + vTaskDelay(pdMS_TO_TICKS(task_delay_ms)); + } + + lvgl_task_deinit(); + vTaskDelete(NULL); +} + +bool lvgl_is_ready() { + return task_running; +} + +void lvgl_interrupt() { + tt_check(lvgl_lock(TtWaitForever)); + task_set_running(false); // interrupt task with boolean as flag + lvgl_unlock(); +} diff --git a/app-sim/src/lvgl_task.h b/app-sim/src/lvgl_task.h new file mode 100644 index 00000000..53386422 --- /dev/null +++ b/app-sim/src/lvgl_task.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool lvgl_is_ready(); +void lvgl_interrupt(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/app-sim/src/main.c b/app-sim/src/main.c index 40007345..da07fd3e 100644 --- a/app-sim/src/main.c +++ b/app-sim/src/main.c @@ -1,32 +1,27 @@ -#include "FreeRTOS.h" -#include "log.h" -#include "portmacro.h" +#include "hello_world/hello_world.h" +#include "lvgl_hal.h" #include "tactility.h" +#include "ui/lvgl_sync.h" + +#include "FreeRTOS.h" #include "task.h" -void vAssertCalled(TT_UNUSED unsigned long line, TT_UNUSED const char* const file) { - static portBASE_TYPE xPrinted = pdFALSE; - volatile uint32_t set_to_nonzero_in_debugger_to_continue = 0; +#define TAG "main" - taskENTER_CRITICAL(); - { - // Step out by attaching a debugger and setting set_to_nonzero_in_debugger_to_continue - while (set_to_nonzero_in_debugger_to_continue == 0) { /* NO-OP */ - } - } - taskEXIT_CRITICAL(); -} +_Noreturn void app_main() { + static const Config config = { + .apps = { + &hello_world_app + }, + .services = {}, + .auto_start_app_id = NULL + }; -int main() { -// static const Config config = { -// .apps = { -// &hello_world_app -// }, -// .services = { }, -// .auto_start_app_id = NULL -// }; -// -// tactility_start(&config); TT_LOG_I("app", "Hello, world!"); - return 0; + + tt_init(&config); + + while (true) { + vTaskDelay(1000); + } } diff --git a/docs/ideas.md b/docs/ideas.md new file mode 100644 index 00000000..aa4a08ea --- /dev/null +++ b/docs/ideas.md @@ -0,0 +1,6 @@ +# App Ideas +- Chip 8 emulator +- Discord bot +- BadUSB +- GPIO status viewer + diff --git a/docs/pics/tactility-showcase.jpg b/docs/pics/tactility-showcase.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05db464a9f3006739a1841a5b7a6dd85441b81ab GIT binary patch literal 86036 zcmeFYbwE_#x;H*_cStG>gVHcVcf$lGcmpC=Q$eVJFo48^!2<$atYP^m`#YjR zAOwO7gbxCNh(Kf*m>^sLr2q#xfXRS73pgk-us|3{Ab$cJFaT2nc^_~{{pPs@U@FYNcsfR6|Kzz3lwT%!4-Xd<$Wh0^-3`S86=ZRB zbaMoOcmVO28vK!~A~bbaV0=&kK0y&cjsQ$ZLI5fOgR($HBw%6^Fv0)Efen-s@=N

{sv&YlwZ1G0Wt`FV{!l! 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

9*Tm?HR>U1~>kzpQ9Zl~tO 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 ZT?!)`dVHD2t^?4^IFJ@Uq1c~RhY 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 + This issue or pull request has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/libs/lv_drivers/.gitignore b/libs/lv_drivers/.gitignore new file mode 100644 index 00000000..fd73aaad --- /dev/null +++ b/libs/lv_drivers/.gitignore @@ -0,0 +1,2 @@ +**/*.o +**/*.d \ No newline at end of file diff --git a/libs/lv_drivers/CMakeLists.txt b/libs/lv_drivers/CMakeLists.txt new file mode 100644 index 00000000..fadccaa1 --- /dev/null +++ b/libs/lv_drivers/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 3.12.4) + +project(lv_drivers HOMEPAGE_URL https://github.com/lvgl/lv_drivers/) + +# Option to build as shared library (as opposed to static), default: OFF +option(BUILD_SHARED_LIBS "Build shared as library (as opposed to static)" OFF) + +file(GLOB_RECURSE SOURCES ./*.c) + +if (BUILD_SHARED_LIBS) + add_library(lv_drivers SHARED ${SOURCES}) +else() + add_library(lv_drivers STATIC ${SOURCES}) +endif() + +add_library(lvgl_drivers ALIAS lv_drivers) +add_library(lvgl::drivers ALIAS lv_drivers) + +target_include_directories(lv_drivers SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +find_package(PkgConfig) +pkg_check_modules(PKG_WAYLAND wayland-client wayland-cursor wayland-protocols xkbcommon) +target_link_libraries(lv_drivers PUBLIC lvgl ${PKG_WAYLAND_LIBRARIES}) + +if("${LIB_INSTALL_DIR}" STREQUAL "") + set(LIB_INSTALL_DIR "lib") +endif() + +if("${INC_INSTALL_DIR}" STREQUAL "") + set(INC_INSTALL_DIR "include/lvgl/lv_drivers") +endif() + +install( + DIRECTORY "${CMAKE_SOURCE_DIR}/" + DESTINATION "${CMAKE_INSTALL_PREFIX}/${INC_INSTALL_DIR}/" + FILES_MATCHING + PATTERN "*.h" + PATTERN ".git*" EXCLUDE + PATTERN "CMakeFiles" EXCLUDE + PATTERN "docs" EXCLUDE + PATTERN "lib" EXCLUDE) + +file(GLOB LV_DRIVERS_PUBLIC_HEADERS "${CMAKE_SOURCE_DIR}/lv_drv_conf.h") + +set_target_properties( + lv_drivers + PROPERTIES OUTPUT_NAME lv_drivers + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + PUBLIC_HEADER "${LV_DRIVERS_PUBLIC_HEADERS}") + +install( + TARGETS lv_drivers + ARCHIVE DESTINATION "${LIB_INSTALL_DIR}" + LIBRARY DESTINATION "${LIB_INSTALL_DIR}" + RUNTIME DESTINATION "${LIB_INSTALL_DIR}" + PUBLIC_HEADER DESTINATION "${INC_INSTALL_DIR}") diff --git a/libs/lv_drivers/LICENSE b/libs/lv_drivers/LICENSE new file mode 100644 index 00000000..cc227abe --- /dev/null +++ b/libs/lv_drivers/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 LittlevGL + +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. diff --git a/libs/lv_drivers/README.md b/libs/lv_drivers/README.md new file mode 100644 index 00000000..6a2da330 --- /dev/null +++ b/libs/lv_drivers/README.md @@ -0,0 +1,7 @@ +# Display and Touch pad drivers + +Display controller and touchpad driver to can be directly used with [LittlevGL](https://littlevgl.com). + +To learn more about using drivers in LittlevGL visit the [Porting guide](https://docs.lvgl.io/latest/en/html/porting/index.html). + +If you used a new display or touchpad driver with LittlevGL please share it with other people! diff --git a/libs/lv_drivers/display/GC9A01.c b/libs/lv_drivers/display/GC9A01.c new file mode 100644 index 00000000..005f5e18 --- /dev/null +++ b/libs/lv_drivers/display/GC9A01.c @@ -0,0 +1,597 @@ +/** + * @file GC9A01.c + * + **/ + + +/********************* + * INCLUDES + *********************/ +#include "GC9A01.h" +#if USE_GC9A01 + +#include +#include +#include + +#include LV_DRV_DISP_INCLUDE +#include LV_DRV_DELAY_INCLUDE + +/********************* + * DEFINES + *********************/ +#ifndef GC9A01_XSTART +#define GC9A01_XSTART 0 +#endif +#ifndef GC9A01_YSTART +#define GC9A01_YSTART 0 +#endif + +#define GC9A01_CMD_MODE 0 +#define GC9A01_DATA_MODE 1 + +#define GC9A01_HOR_RES 240 +#define GC9A01_VER_RES 240 + +/* GC9A01 Commands that we know of. Limited documentation */ +#define GC9A01_INVOFF 0x20 +#define GC9A01_INVON 0x21 +#define GC9A01_DISPON 0x29 +#define GC9A01_CASET 0x2A +#define GC9A01_RASET 0x2B +#define GC9A01_RAMWR 0x2C +#define GC9A01_COLMOD 0x3A +#define GC9A01_MADCTL 0x36 +#define GC9A01_MADCTL_MY 0x80 +#define GC9A01_MADCTL_MX 0x40 +#define GC9A01_MADCTL_MV 0x20 +#define GC9A01_MADCTL_RGB 0x00 +#define GC9A01_DISFNCTRL 0xB6 + +/********************** + * TYPEDEFS + **********************/ + +/* Init script function */ +struct GC9A01_function { + uint16_t cmd; + uint16_t data; +}; + +/* Init script commands */ +enum GC9A01_cmd { + GC9A01_START, + GC9A01_END, + GC9A01_CMD, + GC9A01_DATA, + GC9A01_DELAY +}; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void GC9A01_command(uint8_t cmd); +static void GC9A01_data(uint8_t data); +static void GC9A01_set_addr_win(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); + +/********************** + * STATIC VARIABLES + **********************/ +// Documentation on op codes for GC9A01 are very hard to find. +// Will document should they be found. +static struct GC9A01_function GC9A01_cfg_script[] = { + { GC9A01_START, GC9A01_START}, + { GC9A01_CMD, 0xEF}, + + { GC9A01_CMD, 0xEB}, + { GC9A01_DATA, 0x14}, + + { GC9A01_CMD, 0xFE}, // Inter Register Enable1 + { GC9A01_CMD, 0xEF}, // Inter Register Enable2 + + { GC9A01_CMD, 0xEB}, + { GC9A01_DATA, 0x14}, + + { GC9A01_CMD, 0x84}, + { GC9A01_DATA, 0x40}, + + { GC9A01_CMD, 0x85}, + { GC9A01_DATA, 0xFF}, + + { GC9A01_CMD, 0x86}, + { GC9A01_DATA, 0xFF}, + + { GC9A01_CMD, 0x87}, + { GC9A01_DATA, 0xFF}, + + { GC9A01_CMD, 0x88}, + { GC9A01_DATA, 0x0A}, + + { GC9A01_CMD, 0x89}, + { GC9A01_DATA, 0x21}, + + { GC9A01_CMD, 0x8A}, + { GC9A01_DATA, 0x00}, + + { GC9A01_CMD, 0x8B}, + { GC9A01_DATA, 0x80}, + + { GC9A01_CMD, 0x8C}, + { GC9A01_DATA, 0x01}, + + { GC9A01_CMD, 0x8D}, + { GC9A01_DATA, 0x01}, + + { GC9A01_CMD, 0x8E}, + { GC9A01_DATA, 0xFF}, + + { GC9A01_CMD, 0x8F}, + { GC9A01_DATA, 0xFF}, + + { GC9A01_CMD, GC9A01_DISFNCTRL}, // Display Function Control + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0x00}, + + { GC9A01_CMD, GC9A01_MADCTL}, // Memory Access Control + { GC9A01_DATA, 0x48}, // Set the display direction 0,1,2,3 four directions + + { GC9A01_CMD, GC9A01_COLMOD}, // COLMOD: Pixel Format Set + { GC9A01_DATA, 0x05}, // 16 Bits per pixel + + { GC9A01_CMD, 0x90}, + { GC9A01_DATA, 0x08}, + { GC9A01_DATA, 0x08}, + { GC9A01_DATA, 0x08}, + { GC9A01_DATA, 0x08}, + + { GC9A01_CMD, 0xBD}, + { GC9A01_DATA, 0x06}, + + { GC9A01_CMD, 0xBC}, + { GC9A01_DATA, 0x00}, + + { GC9A01_CMD, 0xFF}, + { GC9A01_DATA, 0x60}, + { GC9A01_DATA, 0x01}, + { GC9A01_DATA, 0x04}, + + { GC9A01_CMD, 0xC3}, // Power Control 2 + { GC9A01_DATA, 0x13}, + { GC9A01_CMD, 0xC4}, // Power Control 3 + { GC9A01_DATA, 0x13}, + + { GC9A01_CMD, 0xC9}, // Power Control 4 + { GC9A01_DATA, 0x22}, + + { GC9A01_CMD, 0xBE}, + { GC9A01_DATA, 0x11}, + + { GC9A01_CMD, 0xE1}, + { GC9A01_DATA, 0x10}, + { GC9A01_DATA, 0x0E}, + + { GC9A01_CMD, 0xDF}, + { GC9A01_DATA, 0x21}, + { GC9A01_DATA, 0x0C}, + { GC9A01_DATA, 0x02}, + + { GC9A01_CMD, 0xF0}, // SET_GAMMA1 + { GC9A01_DATA, 0x45}, + { GC9A01_DATA, 0x09}, + { GC9A01_DATA, 0x08}, + { GC9A01_DATA, 0x08}, + { GC9A01_DATA, 0x26}, + { GC9A01_DATA, 0x2A}, + + { GC9A01_CMD, 0xF1}, // SET_GAMMA2 + { GC9A01_DATA, 0x43}, + { GC9A01_DATA, 0x70}, + { GC9A01_DATA, 0x72}, + { GC9A01_DATA, 0x36}, + { GC9A01_DATA, 0x37}, + { GC9A01_DATA, 0x6F}, + + { GC9A01_CMD, 0xF2}, // SET_GAMMA3 + { GC9A01_DATA, 0x45}, + { GC9A01_DATA, 0x09}, + { GC9A01_DATA, 0x08}, + { GC9A01_DATA, 0x08}, + { GC9A01_DATA, 0x26}, + { GC9A01_DATA, 0x2A}, + + { GC9A01_CMD, 0xF3}, // SET_GAMMA4 + { GC9A01_DATA, 0x43}, + { GC9A01_DATA, 0x70}, + { GC9A01_DATA, 0x72}, + { GC9A01_DATA, 0x36}, + { GC9A01_DATA, 0x37}, + { GC9A01_DATA, 0x6F}, + + { GC9A01_CMD, 0xED}, + { GC9A01_DATA, 0x1B}, + { GC9A01_DATA, 0x0B}, + + { GC9A01_CMD, 0xAE}, + { GC9A01_DATA, 0x77}, + + { GC9A01_CMD, 0xCD}, + { GC9A01_DATA, 0x63}, + + { GC9A01_CMD, 0x70}, + { GC9A01_DATA, 0x07}, + { GC9A01_DATA, 0x07}, + { GC9A01_DATA, 0x04}, + { GC9A01_DATA, 0x0E}, + { GC9A01_DATA, 0x0F}, + { GC9A01_DATA, 0x09}, + { GC9A01_DATA, 0x07}, + { GC9A01_DATA, 0x08}, + { GC9A01_DATA, 0x03}, + + { GC9A01_CMD, 0xE8}, + { GC9A01_DATA, 0x34}, + + { GC9A01_CMD, 0x62}, + { GC9A01_DATA, 0x18}, + { GC9A01_DATA, 0x0D}, + { GC9A01_DATA, 0x71}, + { GC9A01_DATA, 0xED}, + { GC9A01_DATA, 0x70}, + { GC9A01_DATA, 0x70}, + { GC9A01_DATA, 0x18}, + { GC9A01_DATA, 0x0F}, + { GC9A01_DATA, 0x71}, + { GC9A01_DATA, 0xEF}, + { GC9A01_DATA, 0x70}, + { GC9A01_DATA, 0x70}, + + { GC9A01_CMD, 0x63}, + { GC9A01_DATA, 0x18}, + { GC9A01_DATA, 0x11}, + { GC9A01_DATA, 0x71}, + { GC9A01_DATA, 0xF1}, + { GC9A01_DATA, 0x70}, + { GC9A01_DATA, 0x70}, + { GC9A01_DATA, 0x18}, + { GC9A01_DATA, 0x13}, + { GC9A01_DATA, 0x71}, + { GC9A01_DATA, 0xF3}, + { GC9A01_DATA, 0x70}, + { GC9A01_DATA, 0x70}, + + { GC9A01_CMD, 0x64}, + { GC9A01_DATA, 0x28}, + { GC9A01_DATA, 0x29}, + { GC9A01_DATA, 0xF1}, + { GC9A01_DATA, 0x01}, + { GC9A01_DATA, 0xF1}, + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0x07}, + + { GC9A01_CMD, 0x66}, + { GC9A01_DATA, 0x3C}, + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0xCD}, + { GC9A01_DATA, 0x67}, + { GC9A01_DATA, 0x45}, + { GC9A01_DATA, 0x45}, + { GC9A01_DATA, 0x10}, + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0x00}, + + { GC9A01_CMD, 0x67}, + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0x3C}, + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0x01}, + { GC9A01_DATA, 0x54}, + { GC9A01_DATA, 0x10}, + { GC9A01_DATA, 0x32}, + { GC9A01_DATA, 0x98}, + + { GC9A01_CMD, 0x74}, + { GC9A01_DATA, 0x10}, + { GC9A01_DATA, 0x85}, + { GC9A01_DATA, 0x80}, + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0x00}, + { GC9A01_DATA, 0x4E}, + { GC9A01_DATA, 0x00}, + + { GC9A01_CMD, 0x98}, + { GC9A01_DATA, 0x3E}, + { GC9A01_DATA, 0x07}, + + { GC9A01_CMD, 0x35}, // Tearing Effect Line ON + { GC9A01_CMD, 0x21}, // Display Inversion ON + + { GC9A01_CMD, 0x11}, // Sleep Out Mode + { GC9A01_DELAY, 120}, + { GC9A01_CMD, GC9A01_DISPON}, // Display ON + { GC9A01_DELAY, 255}, + { GC9A01_END, GC9A01_END}, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Write a command to the GC9A01 + * @param cmd the command + */ +static void GC9A01_command(uint8_t cmd) +{ + LV_DRV_DISP_CMD_DATA(GC9A01_CMD_MODE); + LV_DRV_DISP_SPI_WR_BYTE(cmd); +} + +/** + * Write data to the GC9A01 + * @param data the data + */ +static void GC9A01_data(uint8_t data) +{ + LV_DRV_DISP_CMD_DATA(GC9A01_DATA_MODE); + LV_DRV_DISP_SPI_WR_BYTE(data); +} + +static int GC9A01_data_array(uint8_t *buf, uint32_t len) +{ + uint8_t *pt = buf; + + for (uint32_t lp = 0; lp < len; lp++, pt++) + { + LV_DRV_DISP_SPI_WR_BYTE(*pt); + } + return 0; +} + +static int GC9A01_databuf(uint32_t len, uint8_t *buf) +{ + uint32_t byte_left = len; + uint8_t *pt = buf; + + while (byte_left) + { + if (byte_left > 64) + { + LV_DRV_DISP_SPI_WR_ARRAY((char*)pt, 64); + byte_left = byte_left - 64; + pt = pt + 64; + } + else + { + LV_DRV_DISP_SPI_WR_ARRAY((char*)pt, byte_left); + byte_left=0; + } + } + + return 0; +} + +// hard reset of the tft controller +// ---------------------------------------------------------- +static void GC9A01_hard_reset( void ) +{ + LV_DRV_DISP_SPI_CS(0); // Low to listen to us + + LV_DRV_DISP_RST(1); + LV_DRV_DELAY_MS(50); + LV_DRV_DISP_RST(0); + LV_DRV_DELAY_MS(50); + LV_DRV_DISP_RST(1); + LV_DRV_DELAY_MS(50); +} + +// Configuration of the tft controller +// ---------------------------------------------------------- +static void GC9A01_run_cfg_script(void) +{ + int i = 0; + int end_script = 0; + + do { + switch (GC9A01_cfg_script[i].cmd) + { + case GC9A01_START: + break; + case GC9A01_CMD: + GC9A01_command( GC9A01_cfg_script[i].data & 0xFF ); + break; + case GC9A01_DATA: + GC9A01_data( GC9A01_cfg_script[i].data & 0xFF ); + break; + case GC9A01_DELAY: + LV_DRV_DELAY_MS(GC9A01_cfg_script[i].data); + break; + case GC9A01_END: + end_script = 1; + } + i++; + } while (!end_script); +} + +void GC9A01_drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { + // Rudimentary clipping + if((x >= GC9A01_HOR_RES) || (y >= GC9A01_VER_RES)) return; + if((y+h-1) >= GC9A01_VER_RES) h = GC9A01_VER_RES - y; + + LV_DRV_DISP_SPI_CS(0); // Listen to us + + GC9A01_set_addr_win(x, y, x, y + h - 1); + + uint8_t hi = color >> 8, lo = color; + + while (h--) { + GC9A01_data(hi); + GC9A01_data(lo); + } + + LV_DRV_DISP_SPI_CS(1); +} + +void GC9A01_drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { + // Rudimentary clipping + if((x >= GC9A01_HOR_RES) || (y >= GC9A01_VER_RES)) return; + if((x+w-1) >= GC9A01_HOR_RES) w = GC9A01_HOR_RES - x; + + LV_DRV_DISP_SPI_CS(0); // Listen to us + + GC9A01_set_addr_win(x, y, x + w - 1, y); + + uint8_t hi = color >> 8, lo = color; + + while (w--) { + GC9A01_data(hi); + GC9A01_data(lo); + } + + LV_DRV_DISP_SPI_CS(1); +} + + +// Pass 8-bit (each) R,G,B, get back 16-bit packed color +uint16_t GC9A01_Color565(uint8_t r, uint8_t g, uint8_t b) { + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); +} + +void GC9A01_invertDisplay(bool i) +{ + GC9A01_command(i ? GC9A01_INVON : GC9A01_INVOFF); +} + +void GC9A01_drawPixel(int16_t x, int16_t y, uint16_t color) +{ + if((x < 0) ||(x >= GC9A01_HOR_RES) || (y < 0) || (y >= GC9A01_VER_RES)) return; + + LV_DRV_DISP_SPI_CS(0); // Listen to us + GC9A01_set_addr_win(x, y, x, y); + + uint8_t hi = color >> 8, lo = color; + + GC9A01_data(hi); + GC9A01_data(lo); + + LV_DRV_DISP_SPI_CS(1); +} + +void GC9A01_fillScreen(uint16_t color) { + GC9A01_fillRect(0, 0, GC9A01_HOR_RES, GC9A01_VER_RES, color); +} + +// fill a rectangle +void GC9A01_fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { + // rudimentary clipping (drawChar w/big text requires this) + if((x >= GC9A01_HOR_RES) || (y >= GC9A01_VER_RES)) return; + if((x + w - 1) >= GC9A01_HOR_RES) w = GC9A01_HOR_RES - x; + if((y + h - 1) >= GC9A01_VER_RES) h = GC9A01_VER_RES - y; + + LV_DRV_DISP_SPI_CS(0); // Listen to us + + GC9A01_set_addr_win(x, y, x + w - 1, y + h - 1); + + uint8_t hi = color >> 8, lo = color; + + for (y = h; y > 0; y--) + { + for (x = w; x > 0; x--) + { + GC9A01_data(hi); + GC9A01_data(lo); + } + } + LV_DRV_DISP_SPI_CS(1); +} + +void GC9A01_setRotation(uint8_t m) { + + GC9A01_command(GC9A01_MADCTL); + m %= 4; // can't be higher than 3 + switch (m) { + case 0: + GC9A01_data(GC9A01_MADCTL_MX | GC9A01_MADCTL_MY | GC9A01_MADCTL_RGB); + + // _xstart = _colstart; + // _ystart = _rowstart; + break; + case 1: + GC9A01_data(GC9A01_MADCTL_MY | GC9A01_MADCTL_MV | GC9A01_MADCTL_RGB); + + // _ystart = _colstart; + // _xstart = _rowstart; + break; + case 2: + GC9A01_data(GC9A01_MADCTL_RGB); + + // _xstart = _colstart; + // _ystart = _rowstart; + break; + + case 3: + GC9A01_data(GC9A01_MADCTL_MX | GC9A01_MADCTL_MV | GC9A01_MADCTL_RGB); + + // _ystart = _colstart; + // _xstart = _rowstart; + break; + } +} + +/** + * Initialize the GC9A01 + */ +int GC9A01_init(void) +{ + GC9A01_hard_reset(); + GC9A01_run_cfg_script(); + + // GC9A01_fillScreen(0x0000); // Black + // GC9A01_fillScreen(0xFFFF); // White + GC9A01_fillScreen(0xAAAA); // ? + + return 0; +} + +static void GC9A01_set_addr_win(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) +{ + uint16_t x_start = x0 + GC9A01_XSTART, x_end = x1 + GC9A01_XSTART; + uint16_t y_start = y0 + GC9A01_YSTART, y_end = y1 + GC9A01_YSTART; + + GC9A01_command(GC9A01_CASET); // Column addr set + GC9A01_data(x_start >> 8); + GC9A01_data(x_start & 0xFF); // XSTART + GC9A01_data(x_end >> 8); + GC9A01_data(x_end & 0xFF); // XEND + + GC9A01_command(GC9A01_RASET); // Row addr set + GC9A01_data(y_start >> 8); + GC9A01_data(y_start & 0xFF); // YSTART + GC9A01_data(y_end >> 8); + GC9A01_data(y_end & 0xFF); // YEND + + GC9A01_command(GC9A01_RAMWR); +} + +void GC9A01_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t *color_p) +{ + LV_DRV_DISP_SPI_CS(0); // Listen to us + + GC9A01_set_addr_win(area->x1, area->y1, area->x2, area->y2); + int32_t len = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * 2; + + LV_DRV_DISP_CMD_DATA(GC9A01_DATA_MODE); + LV_DRV_DISP_SPI_WR_ARRAY((char*)color_p, len); + + LV_DRV_DISP_SPI_CS(1); + lv_disp_flush_ready(disp_drv); /* Indicate you are ready with the flushing*/ +} + +#endif + diff --git a/libs/lv_drivers/display/GC9A01.h b/libs/lv_drivers/display/GC9A01.h new file mode 100644 index 00000000..1076983c --- /dev/null +++ b/libs/lv_drivers/display/GC9A01.h @@ -0,0 +1,76 @@ +/** + * @file GC9A01.h + * + **/ + +#ifndef GC9A01_H +#define GC9A01_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_GC9A01 + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#if LV_COLOR_DEPTH != 16 +#error "GC9A01 currently supports 'LV_COLOR_DEPTH == 16'. Set it in lv_conf.h" +#endif + +#if LV_COLOR_16_SWAP != 1 +#error "GC9A01 SPI requires LV_COLOR_16_SWAP == 1. Set it in lv_conf.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +int GC9A01_init(void); +void GC9A01_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); +void GC9A01_fill(int32_t x1, int32_t y1, int32_t x2, int32_t y2, lv_color_t color); +void GC9A01_map(int32_t x1, int32_t y1, int32_t x2, int32_t y2, lv_color_t * color_p); +void GC9A01_setRotation(uint8_t m); +void GC9A01_fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); +void GC9A01_fillScreen(uint16_t color); +uint16_t GC9A01_Color565(uint8_t r, uint8_t g, uint8_t b); +void GC9A01_invertDisplay(bool i); +void GC9A01_drawPixel(int16_t x, int16_t y, uint16_t color); +void GC9A01_drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); +void GC9A01_drawFastVLine(int16_t x, int16_t y, int16_t w, uint16_t color); + +/********************** + * MACROS + **********************/ + +#endif /* USE_GC9A01 */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* GC9A01_H */ + diff --git a/libs/lv_drivers/display/ILI9341.c b/libs/lv_drivers/display/ILI9341.c new file mode 100644 index 00000000..580f0ce5 --- /dev/null +++ b/libs/lv_drivers/display/ILI9341.c @@ -0,0 +1,432 @@ +/** + * @file ILI9341.c + * + * ILI9341.pdf [ILI9341_DS_V1.13_20110805] + * + * [references] + * - https://www.newhavendisplay.com/app_notes/ILI9341.pdf + * - Linux Source [v5.9-rc4] "drivers/staging/fbtft/fb_ili9341.c" + * - https://github.com/adafruit/Adafruit_ILI9341/blob/master/Adafruit_ILI9341.cpp + * - https://os.mbed.com/users/dreschpe/code/SPI_TFT_ILI9341 + * + */ + +/********************* + * INCLUDES + *********************/ +#include "ILI9341.h" +#if USE_ILI9341 != 0 + +#include +#include +#include LV_DRV_DISP_INCLUDE +#include LV_DRV_DELAY_INCLUDE + +/********************* + * DEFINES + *********************/ +#define ILI9341_CMD_MODE 0 +#define ILI9341_DATA_MODE 1 + +#define ILI9341_TFTWIDTH 240 +#define ILI9341_TFTHEIGHT 320 + +/* Level 1 Commands -------------- [section] Description */ + +#define ILI9341_NOP 0x00 /* [8.2.1 ] No Operation / Terminate Frame Memory Write */ +#define ILI9341_SWRESET 0x01 /* [8.2.2 ] Software Reset */ +#define ILI9341_RDDIDIF 0x04 /* [8.2.3 ] Read Display Identification Information */ +#define ILI9341_RDDST 0x09 /* [8.2.4 ] Read Display Status */ +#define ILI9341_RDDPM 0x0A /* [8.2.5 ] Read Display Power Mode */ +#define ILI9341_RDDMADCTL 0x0B /* [8.2.6 ] Read Display MADCTL */ +#define ILI9341_RDDCOLMOD 0x0C /* [8.2.7 ] Read Display Pixel Format */ +#define ILI9341_RDDIM 0x0D /* [8.2.8 ] Read Display Image Mode */ +#define ILI9341_RDDSM 0x0E /* [8.2.9 ] Read Display Signal Mode */ +#define ILI9341_RDDSDR 0x0F /* [8.2.10] Read Display Self-Diagnostic Result */ +#define ILI9341_SLPIN 0x10 /* [8.2.11] Enter Sleep Mode */ +#define ILI9341_SLPOUT 0x11 /* [8.2.12] Leave Sleep Mode */ +#define ILI9341_PTLON 0x12 /* [8.2.13] Partial Display Mode ON */ +#define ILI9341_NORON 0x13 /* [8.2.14] Normal Display Mode ON */ +#define ILI9341_DINVOFF 0x20 /* [8.2.15] Display Inversion OFF */ +#define ILI9341_DINVON 0x21 /* [8.2.16] Display Inversion ON */ +#define ILI9341_GAMSET 0x26 /* [8.2.17] Gamma Set */ +#define ILI9341_DISPOFF 0x28 /* [8.2.18] Display OFF*/ +#define ILI9341_DISPON 0x29 /* [8.2.19] Display ON*/ +#define ILI9341_CASET 0x2A /* [8.2.20] Column Address Set */ +#define ILI9341_PASET 0x2B /* [8.2.21] Page Address Set */ +#define ILI9341_RAMWR 0x2C /* [8.2.22] Memory Write */ +#define ILI9341_RGBSET 0x2D /* [8.2.23] Color Set (LUT for 16-bit to 18-bit color depth conversion) */ +#define ILI9341_RAMRD 0x2E /* [8.2.24] Memory Read */ +#define ILI9341_PTLAR 0x30 /* [8.2.25] Partial Area */ +#define ILI9341_VSCRDEF 0x33 /* [8.2.26] Veritcal Scrolling Definition */ +#define ILI9341_TEOFF 0x34 /* [8.2.27] Tearing Effect Line OFF */ +#define ILI9341_TEON 0x35 /* [8.2.28] Tearing Effect Line ON */ +#define ILI9341_MADCTL 0x36 /* [8.2.29] Memory Access Control */ +#define MADCTL_MY 0x80 /* MY row address order */ +#define MADCTL_MX 0x40 /* MX column address order */ +#define MADCTL_MV 0x20 /* MV row / column exchange */ +#define MADCTL_ML 0x10 /* ML vertical refresh order */ +#define MADCTL_MH 0x04 /* MH horizontal refresh order */ +#define MADCTL_RGB 0x00 /* RGB Order [default] */ +#define MADCTL_BGR 0x08 /* BGR Order */ +#define ILI9341_VSCRSADD 0x37 /* [8.2.30] Vertical Scrolling Start Address */ +#define ILI9341_IDMOFF 0x38 /* [8.2.31] Idle Mode OFF */ +#define ILI9341_IDMON 0x39 /* [8.2.32] Idle Mode ON */ +#define ILI9341_PIXSET 0x3A /* [8.2.33] Pixel Format Set */ +#define ILI9341_WRMEMCONT 0x3C /* [8.2.34] Write Memory Continue */ +#define ILI9341_RDMEMCONT 0x3E /* [8.2.35] Read Memory Continue */ +#define ILI9341_SETSCANTE 0x44 /* [8.2.36] Set Tear Scanline */ +#define ILI9341_GETSCAN 0x45 /* [8.2.37] Get Scanline */ +#define ILI9341_WRDISBV 0x51 /* [8.2.38] Write Display Brightness Value */ +#define ILI9341_RDDISBV 0x52 /* [8.2.39] Read Display Brightness Value */ +#define ILI9341_WRCTRLD 0x53 /* [8.2.40] Write Control Display */ +#define ILI9341_RDCTRLD 0x54 /* [8.2.41] Read Control Display */ +#define ILI9341_WRCABC 0x55 /* [8.2.42] Write Content Adaptive Brightness Control Value */ +#define ILI9341_RDCABC 0x56 /* [8.2.43] Read Content Adaptive Brightness Control Value */ +#define ILI9341_WRCABCMIN 0x5E /* [8.2.44] Write CABC Minimum Brightness */ +#define ILI9341_RDCABCMIN 0x5F /* [8.2.45] Read CABC Minimum Brightness */ +#define ILI9341_RDID1 0xDA /* [8.2.46] Read ID1 - Manufacturer ID (user) */ +#define ILI9341_RDID2 0xDB /* [8.2.47] Read ID2 - Module/Driver version (supplier) */ +#define ILI9341_RDID3 0xDC /* [8.2.48] Read ID3 - Module/Driver version (user) */ + +/* Level 2 Commands -------------- [section] Description */ + +#define ILI9341_IFMODE 0xB0 /* [8.3.1 ] Interface Mode Control */ +#define ILI9341_FRMCTR1 0xB1 /* [8.3.2 ] Frame Rate Control (In Normal Mode/Full Colors) */ +#define ILI9341_FRMCTR2 0xB2 /* [8.3.3 ] Frame Rate Control (In Idle Mode/8 colors) */ +#define ILI9341_FRMCTR3 0xB3 /* [8.3.4 ] Frame Rate control (In Partial Mode/Full Colors) */ +#define ILI9341_INVTR 0xB4 /* [8.3.5 ] Display Inversion Control */ +#define ILI9341_PRCTR 0xB5 /* [8.3.6 ] Blanking Porch Control */ +#define ILI9341_DISCTRL 0xB6 /* [8.3.7 ] Display Function Control */ +#define ILI9341_ETMOD 0xB7 /* [8.3.8 ] Entry Mode Set */ +#define ILI9341_BLCTRL1 0xB8 /* [8.3.9 ] Backlight Control 1 - Grayscale Histogram UI mode */ +#define ILI9341_BLCTRL2 0xB9 /* [8.3.10] Backlight Control 2 - Grayscale Histogram still picture mode */ +#define ILI9341_BLCTRL3 0xBA /* [8.3.11] Backlight Control 3 - Grayscale Thresholds UI mode */ +#define ILI9341_BLCTRL4 0xBB /* [8.3.12] Backlight Control 4 - Grayscale Thresholds still picture mode */ +#define ILI9341_BLCTRL5 0xBC /* [8.3.13] Backlight Control 5 - Brightness Transition time */ +#define ILI9341_BLCTRL7 0xBE /* [8.3.14] Backlight Control 7 - PWM Frequency */ +#define ILI9341_BLCTRL8 0xBF /* [8.3.15] Backlight Control 8 - ON/OFF + PWM Polarity*/ +#define ILI9341_PWCTRL1 0xC0 /* [8.3.16] Power Control 1 - GVDD */ +#define ILI9341_PWCTRL2 0xC1 /* [8.3.17] Power Control 2 - step-up factor for operating voltage */ +#define ILI9341_VMCTRL1 0xC5 /* [8.3.18] VCOM Control 1 - Set VCOMH and VCOML */ +#define ILI9341_VMCTRL2 0xC7 /* [8.3.19] VCOM Control 2 - VCOM offset voltage */ +#define ILI9341_NVMWR 0xD0 /* [8.3.20] NV Memory Write */ +#define ILI9341_NVMPKEY 0xD1 /* [8.3.21] NV Memory Protection Key */ +#define ILI9341_RDNVM 0xD2 /* [8.3.22] NV Memory Status Read */ +#define ILI9341_RDID4 0xD3 /* [8.3.23] Read ID4 - IC Device Code */ +#define ILI9341_PGAMCTRL 0xE0 /* [8.3.24] Positive Gamma Control */ +#define ILI9341_NGAMCTRL 0xE1 /* [8.3.25] Negative Gamma Correction */ +#define ILI9341_DGAMCTRL1 0xE2 /* [8.3.26] Digital Gamma Control 1 */ +#define ILI9341_DGAMCTRL2 0xE3 /* [8.3.27] Digital Gamma Control 2 */ +#define ILI9341_IFCTL 0xF6 /* [8.3.28] 16bits Data Format Selection */ + +/* Extended Commands --------------- [section] Description*/ + +#define ILI9341_PWCTRLA 0xCB /* [8.4.1] Power control A */ +#define ILI9341_PWCTRLB 0xCF /* [8.4.2] Power control B */ +#define ILI9341_TIMECTRLA_INT 0xE8 /* [8.4.3] Internal Clock Driver timing control A */ +#define ILI9341_TIMECTRLA_EXT 0xE9 /* [8.4.4] External Clock Driver timing control A */ +#define ILI9341_TIMECTRLB 0xEA /* [8.4.5] Driver timing control B (gate driver timing control) */ +#define ILI9341_PWSEQCTRL 0xED /* [8.4.6] Power on sequence control */ +#define ILI9341_GAM3CTRL 0xF2 /* [8.4.7] Enable 3 gamma control */ +#define ILI9341_PUMPRATIO 0xF7 /* [8.4.8] Pump ratio control */ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static inline void ili9341_write(int mode, uint8_t data); +static inline void ili9341_write_array(int mode, uint8_t *data, uint16_t len); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Initialize the ILI9341 display controller + */ +void ili9341_init(void) +{ + uint8_t data[15]; + + /* hardware reset */ + LV_DRV_DISP_SPI_CS(1); + LV_DRV_DISP_CMD_DATA(ILI9341_DATA_MODE); + LV_DRV_DISP_RST(0); + LV_DRV_DELAY_US(50); + LV_DRV_DISP_RST(1); + LV_DRV_DELAY_MS(5); + + /* software reset */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_SWRESET); + LV_DRV_DELAY_MS(5); + ili9341_write(ILI9341_CMD_MODE, ILI9341_DISPOFF); + + /* startup sequence */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_PWCTRLB); + data[0] = 0x00; + data[1] = 0x83; + data[2] = 0x30; + ili9341_write_array(ILI9341_DATA_MODE, data, 3); + + ili9341_write(ILI9341_CMD_MODE, ILI9341_PWSEQCTRL); + data[0] = 0x64; + data[1] = 0x03; + data[2] = 0x12; + data[3] = 0x81; + ili9341_write_array(ILI9341_DATA_MODE, data, 4); + + ili9341_write(ILI9341_CMD_MODE, ILI9341_TIMECTRLA_INT); + data[0] = 0x85; + data[1] = 0x01; + data[2] = 0x79; + ili9341_write_array(ILI9341_DATA_MODE, data, 3); + + ili9341_write(ILI9341_CMD_MODE, ILI9341_PWCTRLA); + data[0] = 0x39; + data[1] = 0x2c; + data[2] = 0x00; + data[3] = 0x34; + data[4] = 0x02; + ili9341_write_array(ILI9341_DATA_MODE, data, 5); + + ili9341_write(ILI9341_CMD_MODE, ILI9341_PUMPRATIO); + ili9341_write(ILI9341_DATA_MODE, 0x20); + + ili9341_write(ILI9341_CMD_MODE, ILI9341_TIMECTRLB); + data[0] = 0x00; + data[1] = 0x00; + ili9341_write_array(ILI9341_DATA_MODE, data, 2); + + /* power control */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_PWCTRL1); + ili9341_write(ILI9341_DATA_MODE, 0x26); + + ili9341_write(ILI9341_CMD_MODE, ILI9341_PWCTRL2); + ili9341_write(ILI9341_DATA_MODE, 0x11); + + /* VCOM */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_VMCTRL1); + data[0] = 0x35; + data[1] = 0x3e; + ili9341_write_array(ILI9341_DATA_MODE, data, 2); + + ili9341_write(ILI9341_CMD_MODE, ILI9341_VMCTRL2); + ili9341_write(ILI9341_DATA_MODE, 0xbe); + + /* set orientation */ + ili9341_rotate(0, ILI9341_BGR); + + /* 16 bit pixel */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_PIXSET); + ili9341_write(ILI9341_DATA_MODE, 0x55); + + /* frame rate */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_FRMCTR1); + data[0] = 0x00; + data[1] = 0x1b; + ili9341_write_array(ILI9341_DATA_MODE, data, 2); + +#if ILI9341_GAMMA + /* gamma curve set */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_GAMSET); + ili9341_write(ILI9341_DATA_MODE, 0x01); + + /* positive gamma correction */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_PGAMCTRL); + data[0] = 0x1f; + data[1] = 0x1a; + data[2] = 0x18; + data[3] = 0x0a; + data[4] = 0x0f; + data[5] = 0x06; + data[6] = 0x45; + data[7] = 0x87; + data[8] = 0x32; + data[9] = 0x0a; + data[10] = 0x07; + data[11] = 0x02; + data[12] = 0x07; + data[13] = 0x05; + data[14] = 0x00; + ili9341_write_array(ILI9341_DATA_MODE, data, 15); + + /* negative gamma correction */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_NGAMCTRL); + data[0] = 0x00; + data[1] = 0x25; + data[2] = 0x27; + data[3] = 0x05; + data[4] = 0x10; + data[5] = 0x09; + data[6] = 0x3a; + data[7] = 0x78; + data[8] = 0x4d; + data[9] = 0x05; + data[10] = 0x18; + data[11] = 0x0d; + data[12] = 0x38; + data[13] = 0x3a; + data[14] = 0x1f; + ili9341_write_array(ILI9341_DATA_MODE, data, 15); +#endif + + /* window horizontal */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_CASET); + data[0] = 0; + data[1] = 0; + data[2] = (ILI9341_HOR_RES - 1) >> 8; + data[3] = (ILI9341_HOR_RES - 1); + ili9341_write_array(ILI9341_DATA_MODE, data, 4); + + /* window vertical */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_PASET); + data[0] = 0; + data[1] = 0; + data[2] = (ILI9341_VER_RES - 1) >> 8; + data[3] = (ILI9341_VER_RES - 1); + ili9341_write_array(ILI9341_DATA_MODE, data, 4); + + ili9341_write(ILI9341_CMD_MODE, ILI9341_RAMWR); + +#if ILI9341_TEARING + /* tearing effect off */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_TEOFF); + + /* tearing effect on */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_TEON); +#endif + + /* entry mode set */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_ETMOD); + ili9341_write(ILI9341_DATA_MODE, 0x07); + + /* display function control */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_DISCTRL); + data[0] = 0x0a; + data[1] = 0x82; + data[2] = 0x27; + data[3] = 0x00; + ili9341_write_array(ILI9341_DATA_MODE, data, 4); + + /* exit sleep mode */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_SLPOUT); + + LV_DRV_DELAY_MS(100); + + /* display on */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_DISPON); + + LV_DRV_DELAY_MS(20); +} + +void ili9341_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p) +{ + if(area->x2 < 0 || area->y2 < 0 || area->x1 > (ILI9341_HOR_RES - 1) || area->y1 > (ILI9341_VER_RES - 1)) { + lv_disp_flush_ready(drv); + return; + } + + /* Truncate the area to the screen */ + int32_t act_x1 = area->x1 < 0 ? 0 : area->x1; + int32_t act_y1 = area->y1 < 0 ? 0 : area->y1; + int32_t act_x2 = area->x2 > ILI9341_HOR_RES - 1 ? ILI9341_HOR_RES - 1 : area->x2; + int32_t act_y2 = area->y2 > ILI9341_VER_RES - 1 ? ILI9341_VER_RES - 1 : area->y2; + + int32_t y; + uint8_t data[4]; + int32_t len = len = (act_x2 - act_x1 + 1) * 2; + lv_coord_t w = (area->x2 - area->x1) + 1; + + /* window horizontal */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_CASET); + data[0] = act_x1 >> 8; + data[1] = act_x1; + data[2] = act_x2 >> 8; + data[3] = act_x2; + ili9341_write_array(ILI9341_DATA_MODE, data, 4); + + /* window vertical */ + ili9341_write(ILI9341_CMD_MODE, ILI9341_PASET); + data[0] = act_y1 >> 8; + data[1] = act_y1; + data[2] = act_y2 >> 8; + data[3] = act_y2; + ili9341_write_array(ILI9341_DATA_MODE, data, 4); + + ili9341_write(ILI9341_CMD_MODE, ILI9341_RAMWR); + + for(y = act_y1; y <= act_y2; y++) { + ili9341_write_array(ILI9341_DATA_MODE, (uint8_t *)color_p, len); + color_p += w; + } + + lv_disp_flush_ready(drv); +} + +void ili9341_rotate(int degrees, bool bgr) +{ + uint8_t color_order = MADCTL_RGB; + + if(bgr) + color_order = MADCTL_BGR; + + ili9341_write(ILI9341_CMD_MODE, ILI9341_MADCTL); + + switch(degrees) { + case 270: + ili9341_write(ILI9341_DATA_MODE, MADCTL_MV | color_order); + break; + case 180: + ili9341_write(ILI9341_DATA_MODE, MADCTL_MY | color_order); + break; + case 90: + ili9341_write(ILI9341_DATA_MODE, MADCTL_MX | MADCTL_MY | MADCTL_MV | color_order); + break; + case 0: + /* fall-through */ + default: + ili9341_write(ILI9341_DATA_MODE, MADCTL_MX | color_order); + break; + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/** + * Write byte + * @param mode sets command or data mode for write + * @param byte the byte to write + */ +static inline void ili9341_write(int mode, uint8_t data) +{ + LV_DRV_DISP_CMD_DATA(mode); + LV_DRV_DISP_SPI_WR_BYTE(data); +} + +/** + * Write byte array + * @param mode sets command or data mode for write + * @param data the byte array to write + * @param len the length of the byte array + */ +static inline void ili9341_write_array(int mode, uint8_t *data, uint16_t len) +{ + LV_DRV_DISP_CMD_DATA(mode); + LV_DRV_DISP_SPI_WR_ARRAY(data, len); +} + +#endif diff --git a/libs/lv_drivers/display/ILI9341.h b/libs/lv_drivers/display/ILI9341.h new file mode 100644 index 00000000..3120a419 --- /dev/null +++ b/libs/lv_drivers/display/ILI9341.h @@ -0,0 +1,67 @@ +/** + * @file ILI9341.h + * + */ + +#ifndef ILI9341_H +#define ILI9341_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_ILI9341 + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#if LV_COLOR_DEPTH != 16 +#error "ILI9341 currently supports 'LV_COLOR_DEPTH == 16'. Set it in lv_conf.h" +#endif + +#if LV_COLOR_16_SWAP != 1 +#error "ILI9341 SPI requires LV_COLOR_16_SWAP == 1. Set it in lv_conf.h" +#endif + +/********************* + * DEFINES + *********************/ +#define ILI9341_BGR true +#define ILI9341_RGB false + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void ili9341_init(void); +void ili9341_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p); +void ili9341_rotate(int degrees, bool bgr); +/********************** + * MACROS + **********************/ + +#endif /* USE_ILI9341 */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ILI9341_H */ diff --git a/libs/lv_drivers/display/R61581.c b/libs/lv_drivers/display/R61581.c new file mode 100644 index 00000000..4d21bff9 --- /dev/null +++ b/libs/lv_drivers/display/R61581.c @@ -0,0 +1,425 @@ +/** + * @file R61581.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "R61581.h" +#if USE_R61581 != 0 + +#include +#include "lvgl/lv_core/lv_vdb.h" +#include LV_DRV_DISP_INCLUDE +#include LV_DRV_DELAY_INCLUDE + +/********************* + * DEFINES + *********************/ +#define R61581_CMD_MODE 0 +#define R61581_DATA_MODE 1 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void r61581_io_init(void); +static void r61581_reset(void); +static void r61581_set_tft_spec(void); +static inline void r61581_cmd_mode(void); +static inline void r61581_data_mode(void); +static inline void r61581_cmd(uint8_t cmd); +static inline void r61581_data(uint8_t data); + +/********************** + * STATIC VARIABLES + **********************/ +static bool cmd_mode = true; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Initialize the R61581 display controller + * @return HW_RES_OK or any error from hw_res_t enum + */ +void r61581_init(void) +{ + r61581_io_init(); + + /*Slow mode until the PLL is not started in the display controller*/ + LV_DRV_DISP_PAR_SLOW; + + r61581_reset(); + + r61581_set_tft_spec(); + + r61581_cmd(0x13); //SET display on + + r61581_cmd(0x29); //SET display on + LV_DRV_DELAY_MS(30); + + /*Parallel to max speed*/ + LV_DRV_DISP_PAR_FAST; +} + +void r61581_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p) +{ + /*Return if the area is out the screen*/ + if(x2 < 0) return; + if(y2 < 0) return; + if(x1 > R61581_HOR_RES - 1) return; + if(y1 > R61581_VER_RES - 1) return; + + /*Truncate the area to the screen*/ + int32_t act_x1 = x1 < 0 ? 0 : x1; + int32_t act_y1 = y1 < 0 ? 0 : y1; + int32_t act_x2 = x2 > R61581_HOR_RES - 1 ? R61581_HOR_RES - 1 : x2; + int32_t act_y2 = y2 > R61581_VER_RES - 1 ? R61581_VER_RES - 1 : y2; + + + //Set the rectangular area + r61581_cmd(0x002A); + r61581_data(act_x1 >> 8); + r61581_data(0x00FF & act_x1); + r61581_data(act_x2 >> 8); + r61581_data(0x00FF & act_x2); + + r61581_cmd(0x002B); + r61581_data(act_y1 >> 8); + r61581_data(0x00FF & act_y1); + r61581_data(act_y2 >> 8); + r61581_data(0x00FF & act_y2); + + r61581_cmd(0x2c); + + int16_t i; + uint16_t full_w = x2 - x1 + 1; + + r61581_data_mode(); + +#if LV_COLOR_DEPTH == 16 + uint16_t act_w = act_x2 - act_x1 + 1; + for(i = act_y1; i <= act_y2; i++) { + LV_DRV_DISP_PAR_WR_ARRAY((uint16_t *)color_p, act_w); + color_p += full_w; + } +#else + int16_t j; + for(i = act_y1; i <= act_y2; i++) { + for(j = 0; j <= act_x2 - act_x1 + 1; j++) { + LV_DRV_DISP_PAR_WR_WORD(lv_color_to16(color_p[j])); + color_p += full_w; + } + } +#endif + + lv_flush_ready(); +} + +void r61581_fill(int32_t x1, int32_t y1, int32_t x2, int32_t y2, lv_color_t color) +{ + /*Return if the area is out the screen*/ + if(x2 < 0) return; + if(y2 < 0) return; + if(x1 > R61581_HOR_RES - 1) return; + if(y1 > R61581_VER_RES - 1) return; + + /*Truncate the area to the screen*/ + int32_t act_x1 = x1 < 0 ? 0 : x1; + int32_t act_y1 = y1 < 0 ? 0 : y1; + int32_t act_x2 = x2 > R61581_HOR_RES - 1 ? R61581_HOR_RES - 1 : x2; + int32_t act_y2 = y2 > R61581_VER_RES - 1 ? R61581_VER_RES - 1 : y2; + + //Set the rectangular area + r61581_cmd(0x002A); + r61581_data(act_x1 >> 8); + r61581_data(0x00FF & act_x1); + r61581_data(act_x2 >> 8); + r61581_data(0x00FF & act_x2); + + r61581_cmd(0x002B); + r61581_data(act_y1 >> 8); + r61581_data(0x00FF & act_y1); + r61581_data(act_y2 >> 8); + r61581_data(0x00FF & act_y2); + + r61581_cmd(0x2c); + + r61581_data_mode(); + + uint16_t color16 = lv_color_to16(color); + uint32_t size = (act_x2 - act_x1 + 1) * (act_y2 - act_y1 + 1); + uint32_t i; + for(i = 0; i < size; i++) { + LV_DRV_DISP_PAR_WR_WORD(color16); + } +} + +void r61581_map(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p) +{ + /*Return if the area is out the screen*/ + if(x2 < 0) return; + if(y2 < 0) return; + if(x1 > R61581_HOR_RES - 1) return; + if(y1 > R61581_VER_RES - 1) return; + + /*Truncate the area to the screen*/ + int32_t act_x1 = x1 < 0 ? 0 : x1; + int32_t act_y1 = y1 < 0 ? 0 : y1; + int32_t act_x2 = x2 > R61581_HOR_RES - 1 ? R61581_HOR_RES - 1 : x2; + int32_t act_y2 = y2 > R61581_VER_RES - 1 ? R61581_VER_RES - 1 : y2; + + + //Set the rectangular area + r61581_cmd(0x002A); + r61581_data(act_x1 >> 8); + r61581_data(0x00FF & act_x1); + r61581_data(act_x2 >> 8); + r61581_data(0x00FF & act_x2); + + r61581_cmd(0x002B); + r61581_data(act_y1 >> 8); + r61581_data(0x00FF & act_y1); + r61581_data(act_y2 >> 8); + r61581_data(0x00FF & act_y2); + + r61581_cmd(0x2c); + + int16_t i; + uint16_t full_w = x2 - x1 + 1; + + r61581_data_mode(); + +#if LV_COLOR_DEPTH == 16 + uint16_t act_w = act_x2 - act_x1 + 1; + for(i = act_y1; i <= act_y2; i++) { + LV_DRV_DISP_PAR_WR_ARRAY((uint16_t *)color_p, act_w); + color_p += full_w; + } +#else + int16_t j; + for(i = act_y1; i <= act_y2; i++) { + for(j = 0; j <= act_x2 - act_x1 + 1; j++) { + LV_DRV_DISP_PAR_WR_WORD(lv_color_to16(color_p[j])); + color_p += full_w; + } + } +#endif +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/** + * Io init + */ +static void r61581_io_init(void) +{ + LV_DRV_DISP_CMD_DATA(R61581_CMD_MODE) + cmd_mode = true; +} + +/** + * Reset + */ +static void r61581_reset(void) +{ + /*Hardware reset*/ + LV_DRV_DISP_RST(1); + LV_DRV_DELAY_MS(50); + LV_DRV_DISP_RST(0); + LV_DRV_DELAY_MS(50); + LV_DRV_DISP_RST(1); + LV_DRV_DELAY_MS(50); + + /*Chip enable*/ + LV_DRV_DISP_PAR_CS(1); + LV_DRV_DELAY_MS(10); + LV_DRV_DISP_PAR_CS(0); + LV_DRV_DELAY_MS(5); + + /*Software reset*/ + r61581_cmd(0x01); + LV_DRV_DELAY_MS(20); + + r61581_cmd(0x01); + LV_DRV_DELAY_MS(20); + + r61581_cmd(0x01); + LV_DRV_DELAY_MS(20); +} + +/** + * TFT specific initialization + */ +static void r61581_set_tft_spec(void) +{ + r61581_cmd(0xB0); + r61581_data(0x00); + + r61581_cmd(0xB3); + r61581_data(0x02); + r61581_data(0x00); + r61581_data(0x00); + r61581_data(0x10); + + r61581_cmd(0xB4); + r61581_data(0x00);//0X10 + + r61581_cmd(0xB9); //PWM + r61581_data(0x01); + r61581_data(0xFF); //FF brightness + r61581_data(0xFF); + r61581_data(0x18); + + /*Panel Driving Setting*/ + r61581_cmd(0xC0); + r61581_data(0x02); + r61581_data(0x3B); + r61581_data(0x00); + r61581_data(0x00); + r61581_data(0x00); + r61581_data(0x01); + r61581_data(0x00);//NW + r61581_data(0x43); + + /*Display Timing Setting for Normal Mode */ + r61581_cmd(0xC1); + r61581_data(0x08); + r61581_data(0x15); //CLOCK + r61581_data(R61581_VFP); + r61581_data(R61581_VBP); + + /*Source/VCOM/Gate Driving Timing Setting*/ + r61581_cmd(0xC4); + r61581_data(0x15); + r61581_data(0x03); + r61581_data(0x03); + r61581_data(0x01); + + /*Interface Setting*/ + r61581_cmd(0xC6); + r61581_data((R61581_DPL << 0) | + (R61581_EPL << 1) | + (R61581_HSPL << 4) | + (R61581_VSPL << 5)); + + /*Gamma Set*/ + r61581_cmd(0xC8); + r61581_data(0x0c); + r61581_data(0x05); + r61581_data(0x0A); + r61581_data(0x6B); + r61581_data(0x04); + r61581_data(0x06); + r61581_data(0x15); + r61581_data(0x10); + r61581_data(0x00); + r61581_data(0x31); + + + r61581_cmd(0x36); + if(R61581_ORI == 0) r61581_data(0xE0); + else r61581_data(0x20); + + r61581_cmd(0x0C); + r61581_data(0x55); + + r61581_cmd(0x3A); + r61581_data(0x55); + + r61581_cmd(0x38); + + r61581_cmd(0xD0); + r61581_data(0x07); + r61581_data(0x07); + r61581_data(0x14); + r61581_data(0xA2); + + r61581_cmd(0xD1); + r61581_data(0x03); + r61581_data(0x5A); + r61581_data(0x10); + + r61581_cmd(0xD2); + r61581_data(0x03); + r61581_data(0x04); + r61581_data(0x04); + + r61581_cmd(0x11); + LV_DRV_DELAY_MS(10); + + r61581_cmd(0x2A); + r61581_data(0x00); + r61581_data(0x00); + r61581_data(((R61581_HOR_RES - 1) >> 8) & 0XFF); + r61581_data((R61581_HOR_RES - 1) & 0XFF); + + r61581_cmd(0x2B); + r61581_data(0x00); + r61581_data(0x00); + r61581_data(((R61581_VER_RES - 1) >> 8) & 0XFF); + r61581_data((R61581_VER_RES - 1) & 0XFF); + + LV_DRV_DELAY_MS(10); + + r61581_cmd(0x29); + LV_DRV_DELAY_MS(5); + + r61581_cmd(0x2C); + LV_DRV_DELAY_MS(5); +} + +/** + * Command mode + */ +static inline void r61581_cmd_mode(void) +{ + if(cmd_mode == false) { + LV_DRV_DISP_CMD_DATA(R61581_CMD_MODE) + cmd_mode = true; + } +} + +/** + * Data mode + */ +static inline void r61581_data_mode(void) +{ + if(cmd_mode != false) { + LV_DRV_DISP_CMD_DATA(R61581_DATA_MODE); + cmd_mode = false; + } +} + +/** + * Write command + * @param cmd the command + */ +static inline void r61581_cmd(uint8_t cmd) +{ + r61581_cmd_mode(); + LV_DRV_DISP_PAR_WR_WORD(cmd); +} + +/** + * Write data + * @param data the data + */ +static inline void r61581_data(uint8_t data) +{ + r61581_data_mode(); + LV_DRV_DISP_PAR_WR_WORD(data); +} +#endif diff --git a/libs/lv_drivers/display/R61581.h b/libs/lv_drivers/display/R61581.h new file mode 100644 index 00000000..3ba4ff93 --- /dev/null +++ b/libs/lv_drivers/display/R61581.h @@ -0,0 +1,57 @@ +/** + * @file R61581.h + * + */ + +#ifndef R61581_H +#define R61581_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_R61581 + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void r61581_init(void); +void r61581_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p); +void r61581_fill(int32_t x1, int32_t y1, int32_t x2, int32_t y2, lv_color_t color); +void r61581_map(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p); +/********************** + * MACROS + **********************/ + +#endif /* USE_R61581 */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* R61581_H */ diff --git a/libs/lv_drivers/display/SHARP_MIP.c b/libs/lv_drivers/display/SHARP_MIP.c new file mode 100644 index 00000000..9301affb --- /dev/null +++ b/libs/lv_drivers/display/SHARP_MIP.c @@ -0,0 +1,182 @@ +/** + * @file SHARP_MIP.c + * + */ + +/*------------------------------------------------------------------------------------------------- + * SHARP memory in pixel monochrome display series + * LS012B7DD01 (184x38 pixels.) + * LS013B7DH03 (128x128 pixels.) + * LS013B7DH05 (144x168 pixels.) + * LS027B7DH01 (400x240 pixels.) (tested) + * LS032B7DD02 (336x536 pixels.) + * LS044Q7DH01 (320x240 pixels.) + * + * These displays need periodic com inversion, there are two ways : + * - software com inversion : + * define SHARP_MIP_SOFT_COM_INVERSION 1 and set EXTMODE display pin LOW, + * call sharp_mip_com_inversion() periodically + * - hardware com inversion with EXTCOMIN display pin : + * define SHARP_MIP_SOFT_COM_INVERSION 0, + * set EXTMODE display pin HIGH and handle + * EXTCOMIN waveform (for example with mcu pwm output), + * see datasheet pages 8-12 for details + * + * draw_buf size : (LV_VER_RES / X) * (2 + LV_HOR_RES / 8) + 2 bytes, structure : + * [FRAME_HEADER (1 byte)] [GATE_ADDR (1 byte )] [LINE_DATA (LV_HOR_RES / 8 bytes)] 1st line + * [DUMMY (1 byte)] [GATE_ADDR (1 byte )] [LINE_DATA (LV_HOR_RES / 8 bytes)] 2nd line + * ........................................................................................... + * [DUMMY (1 byte)] [GATE_ADDR (1 byte )] [LINE_DATA (LV_HOR_RES / 8 bytes)] last line + * [DUMMY (2 bytes)] + * + * Since extra bytes (dummy, addresses, header) are stored in draw_buf, we need to use + * an "oversized" draw_buf. Buffer declaration in "lv_port_disp.c" becomes for example : + * static lv_disp_buf_t disp_buf; + * static uint8_t buf[(LV_VER_RES_MAX / X) * (2 + (LV_HOR_RES_MAX / 8)) + 2]; + * lv_disp_buf_init(&disp_buf, buf, NULL, LV_VER_RES_MAX * LV_HOR_RES_MAX / X); + *-----------------------------------------------------------------------------------------------*/ + +/********************* + * INCLUDES + *********************/ + +#include "SHARP_MIP.h" + +#if USE_SHARP_MIP + +#include +#include LV_DRV_DISP_INCLUDE +#include LV_DRV_DELAY_INCLUDE + +/********************* + * DEFINES + *********************/ + +#define SHARP_MIP_HEADER 0 +#define SHARP_MIP_UPDATE_RAM_FLAG (1 << 7) /* (M0) Mode flag : H -> update memory, L -> maintain memory */ +#define SHARP_MIP_COM_INVERSION_FLAG (1 << 6) /* (M1) Frame inversion flag : relevant when EXTMODE = L, */ + /* H -> outputs VCOM = H, L -> outputs VCOM = L */ +#define SHARP_MIP_CLEAR_SCREEN_FLAG (1 << 5) /* (M2) All clear flag : H -> clear all pixels */ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +#if SHARP_MIP_SOFT_COM_INVERSION +static bool_t com_output_state = false; +#endif + +/********************** + * MACROS + **********************/ + +/* + * Return the draw_buf byte index corresponding to the pixel + * relatives coordinates (x, y) in the area. + * The area is rounded to a whole screen line. + */ +#define BUFIDX(x, y) (((x) >> 3) + ((y) * (2 + (SHARP_MIP_HOR_RES >> 3))) + 2) + +/* + * Return the byte bitmask of a pixel bit corresponding + * to draw_buf arrangement (8 pixels per byte on lines). + */ +#define PIXIDX(x) SHARP_MIP_REV_BYTE(1 << ((x) & 7)) + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void sharp_mip_init(void) { + /* These displays have nothing to initialize */ +} + + +void sharp_mip_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { + + /*Return if the area is out the screen*/ + if(area->y2 < 0) return; + if(area->y1 > SHARP_MIP_VER_RES - 1) return; + + /*Truncate the area to the screen*/ + uint16_t act_y1 = area->y1 < 0 ? 0 : area->y1; + uint16_t act_y2 = area->y2 > SHARP_MIP_VER_RES - 1 ? SHARP_MIP_VER_RES - 1 : area->y2; + + uint8_t * buf = (uint8_t *) color_p; /*Get the buffer address*/ + uint16_t buf_h = (act_y2 - act_y1 + 1); /*Number of buffer lines*/ + uint16_t buf_size = buf_h * (2 + SHARP_MIP_HOR_RES / 8) + 2; /*Buffer size in bytes */ + + /* Set lines to flush dummy byte & gate address in draw_buf*/ + for(uint16_t act_y = 0 ; act_y < buf_h ; act_y++) { + buf[BUFIDX(0, act_y) - 1] = SHARP_MIP_REV_BYTE((act_y1 + act_y + 1)); + buf[BUFIDX(0, act_y) - 2] = 0; + } + + /* Set last dummy two bytes in draw_buf */ + buf[BUFIDX(0, buf_h) - 1] = 0; + buf[BUFIDX(0, buf_h) - 2] = 0; + + /* Set frame header in draw_buf */ + buf[0] = SHARP_MIP_HEADER | + SHARP_MIP_UPDATE_RAM_FLAG; + + /* Write the frame on display memory */ + LV_DRV_DISP_SPI_CS(1); + LV_DRV_DISP_SPI_WR_ARRAY(buf, buf_size); + LV_DRV_DISP_SPI_CS(0); + + lv_disp_flush_ready(disp_drv); +} + +void sharp_mip_set_px(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) { + (void) disp_drv; + (void) buf_w; + (void) opa; + + if (lv_color_to1(color) != 0) { + buf[BUFIDX(x, y)] |= PIXIDX(x); /*Set draw_buf pixel bit to 1 for other colors than BLACK*/ + } else { + buf[BUFIDX(x, y)] &= ~PIXIDX(x); /*Set draw_buf pixel bit to 0 for BLACK color*/ + } +} + +void sharp_mip_rounder(lv_disp_drv_t * disp_drv, lv_area_t * area) { + (void) disp_drv; + + /* Round area to a whole line */ + area->x1 = 0; + area->x2 = SHARP_MIP_HOR_RES - 1; +} + +#if SHARP_MIP_SOFT_COM_INVERSION +void sharp_mip_com_inversion(void) { + uint8_t inversion_header[2] = {0}; + + /* Set inversion header */ + if (com_output_state) { + com_output_state = false; + } else { + inversion_header[0] |= SHARP_MIP_COM_INVERSION_FLAG; + com_output_state = true; + } + + /* Write inversion header on display memory */ + LV_DRV_DISP_SPI_CS(1); + LV_DRV_DISP_SPI_WR_ARRAY(inversion_header, 2); + LV_DRV_DISP_SPI_CS(0); +} +#endif + +/********************** + * STATIC FUNCTIONS + **********************/ + +#endif diff --git a/libs/lv_drivers/display/SHARP_MIP.h b/libs/lv_drivers/display/SHARP_MIP.h new file mode 100644 index 00000000..c10d8459 --- /dev/null +++ b/libs/lv_drivers/display/SHARP_MIP.h @@ -0,0 +1,63 @@ +/** + * @file SHARP_MIP.h + * + */ + +#ifndef SHARP_MIP_H +#define SHARP_MIP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_SHARP_MIP + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void sharp_mip_init(void); +void sharp_mip_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); +void sharp_mip_rounder(lv_disp_drv_t * disp_drv, lv_area_t * area); +void sharp_mip_set_px(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa); +#if SHARP_MIP_SOFT_COM_INVERSION +void sharp_mip_com_inversion(void); +#endif + +/********************** + * MACROS + **********************/ + +#endif /* USE_SHARP_MIP */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SHARP_MIP_H */ diff --git a/libs/lv_drivers/display/SSD1963.c b/libs/lv_drivers/display/SSD1963.c new file mode 100644 index 00000000..c961066b --- /dev/null +++ b/libs/lv_drivers/display/SSD1963.c @@ -0,0 +1,292 @@ +/** + * @file SSD1963.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "SSD1963.h" +#if USE_SSD1963 + +#include +#include LV_DRV_DISP_INCLUDE +#include LV_DRV_DELAY_INCLUDE + +/********************* + * DEFINES + *********************/ +#define SSD1963_CMD_MODE 0 +#define SSD1963_DATA_MODE 1 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static inline void ssd1963_cmd_mode(void); +static inline void ssd1963_data_mode(void); +static inline void ssd1963_cmd(uint8_t cmd); +static inline void ssd1963_data(uint8_t data); +static void ssd1963_io_init(void); +static void ssd1963_reset(void); +static void ssd1963_set_clk(void); +static void ssd1963_set_tft_spec(void); +static void ssd1963_init_bl(void); + +/********************** + * STATIC VARIABLES + **********************/ +static bool cmd_mode = true; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void ssd1963_init(void) +{ + + LV_DRV_DISP_CMD_DATA(SSD1963_CMD_MODE); + cmd_mode = true; + + LV_DRV_DELAY_MS(250); + + + ssd1963_cmd(0x00E2); //PLL multiplier, set PLL clock to 120M + ssd1963_data(0x0023); //N=0x36 for 6.5M, 0x23 for 10M crystal + ssd1963_data(0x0002); + ssd1963_data(0x0004); + ssd1963_cmd(0x00E0); // PLL enable + ssd1963_data(0x0001); + LV_DRV_DELAY_MS(1); + ssd1963_cmd(0x00E0); + ssd1963_data(0x0003); // now, use PLL output as system clock + LV_DRV_DELAY_MS(1); + ssd1963_cmd(0x0001); // software reset + LV_DRV_DELAY_MS(1); + ssd1963_cmd(0x00E6); //PLL setting for PCLK, depends on resolution + + ssd1963_data(0x0001); //HX8257C + ssd1963_data(0x0033); //HX8257C + ssd1963_data(0x0033); //HX8257C + + + ssd1963_cmd(0x00B0); //LCD SPECIFICATION + ssd1963_data(0x0020); + ssd1963_data(0x0000); + ssd1963_data(((SSD1963_HOR_RES - 1) >> 8) & 0X00FF); //Set HDP + ssd1963_data((SSD1963_HOR_RES - 1) & 0X00FF); + ssd1963_data(((SSD1963_VER_RES - 1) >> 8) & 0X00FF); //Set VDP + ssd1963_data((SSD1963_VER_RES - 1) & 0X00FF); + ssd1963_data(0x0000); + LV_DRV_DELAY_MS(1);//Delay10us(5); + ssd1963_cmd(0x00B4); //HSYNC + ssd1963_data((SSD1963_HT >> 8) & 0X00FF); //Set HT + ssd1963_data(SSD1963_HT & 0X00FF); + ssd1963_data((SSD1963_HPS >> 8) & 0X00FF); //Set HPS + ssd1963_data(SSD1963_HPS & 0X00FF); + ssd1963_data(SSD1963_HPW); //Set HPW + ssd1963_data((SSD1963_LPS >> 8) & 0X00FF); //SetLPS + ssd1963_data(SSD1963_LPS & 0X00FF); + ssd1963_data(0x0000); + + ssd1963_cmd(0x00B6); //VSYNC + ssd1963_data((SSD1963_VT >> 8) & 0X00FF); //Set VT + ssd1963_data(SSD1963_VT & 0X00FF); + ssd1963_data((SSD1963_VPS >> 8) & 0X00FF); //Set VPS + ssd1963_data(SSD1963_VPS & 0X00FF); + ssd1963_data(SSD1963_VPW); //Set VPW + ssd1963_data((SSD1963_FPS >> 8) & 0X00FF); //Set FPS + ssd1963_data(SSD1963_FPS & 0X00FF); + + ssd1963_cmd(0x00B8); + ssd1963_data(0x000f); //GPIO is controlled by host GPIO[3:0]=output GPIO[0]=1 LCD ON GPIO[0]=1 LCD OFF + ssd1963_data(0x0001); //GPIO0 normal + + ssd1963_cmd(0x00BA); + ssd1963_data(0x0001); //GPIO[0] out 1 --- LCD display on/off control PIN + + ssd1963_cmd(0x0036); //rotation + ssd1963_data(0x0008); //RGB=BGR + + ssd1963_cmd(0x003A); //Set the current pixel format for RGB image data + ssd1963_data(0x0050); //16-bit/pixel + + ssd1963_cmd(0x00F0); //Pixel Data Interface Format + ssd1963_data(0x0003); //16-bit(565 format) data + + ssd1963_cmd(0x00BC); + ssd1963_data(0x0040); //contrast value + ssd1963_data(0x0080); //brightness value + ssd1963_data(0x0040); //saturation value + ssd1963_data(0x0001); //Post Processor Enable + + LV_DRV_DELAY_MS(1); + + ssd1963_cmd(0x0029); //display on + + ssd1963_cmd(0x00BE); //set PWM for B/L + ssd1963_data(0x0006); + ssd1963_data(0x0080); + ssd1963_data(0x0001); + ssd1963_data(0x00f0); + ssd1963_data(0x0000); + ssd1963_data(0x0000); + + ssd1963_cmd(0x00d0); + ssd1963_data(0x000d); + + //DisplayBacklightOn(); + + LV_DRV_DELAY_MS(30); +} + +void ssd1963_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) +{ + + /*Return if the area is out the screen*/ + if(area->x2 < 0) return; + if(area->y2 < 0) return; + if(area->x1 > SSD1963_HOR_RES - 1) return; + if(area->y1 > SSD1963_VER_RES - 1) return; + + /*Truncate the area to the screen*/ + int32_t act_x1 = area->x1 < 0 ? 0 : area->x1; + int32_t act_y1 = area->y1 < 0 ? 0 : area->y1; + int32_t act_x2 = area->x2 > SSD1963_HOR_RES - 1 ? SSD1963_HOR_RES - 1 : area->x2; + int32_t act_y2 = area->y2 > SSD1963_VER_RES - 1 ? SSD1963_VER_RES - 1 : area->y2; + + //Set the rectangular area + ssd1963_cmd(0x002A); + ssd1963_data(act_x1 >> 8); + ssd1963_data(0x00FF & act_x1); + ssd1963_data(act_x2 >> 8); + ssd1963_data(0x00FF & act_x2); + + ssd1963_cmd(0x002B); + ssd1963_data(act_y1 >> 8); + ssd1963_data(0x00FF & act_y1); + ssd1963_data(act_y2 >> 8); + ssd1963_data(0x00FF & act_y2); + + ssd1963_cmd(0x2c); + int16_t i; + uint16_t full_w = area->x2 - area->x1 + 1; + + ssd1963_data_mode(); + LV_DRV_DISP_PAR_CS(0); +#if LV_COLOR_DEPTH == 16 + uint16_t act_w = act_x2 - act_x1 + 1; + for(i = act_y1; i <= act_y2; i++) { + LV_DRV_DISP_PAR_WR_ARRAY((uint16_t *)color_p, act_w); + color_p += full_w; + } + LV_DRV_DISP_PAR_CS(1); +#else + int16_t j; + for(i = act_y1; i <= act_y2; i++) { + for(j = 0; j <= act_x2 - act_x1 + 1; j++) { + LV_DRV_DISP_PAR_WR_WORD(color_p[j]); + color_p += full_w; + } + } +#endif + + lv_disp_flush_ready(disp_drv); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void ssd1963_io_init(void) +{ + LV_DRV_DISP_CMD_DATA(SSD1963_CMD_MODE); + cmd_mode = true; +} + +static void ssd1963_reset(void) +{ + /*Hardware reset*/ + LV_DRV_DISP_RST(1); + LV_DRV_DELAY_MS(50); + LV_DRV_DISP_RST(0); + LV_DRV_DELAY_MS(50); + LV_DRV_DISP_RST(1); + LV_DRV_DELAY_MS(50); + + /*Chip enable*/ + LV_DRV_DISP_PAR_CS(0); + LV_DRV_DELAY_MS(10); + LV_DRV_DISP_PAR_CS(1); + LV_DRV_DELAY_MS(5); + + /*Software reset*/ + ssd1963_cmd(0x01); + LV_DRV_DELAY_MS(20); + + ssd1963_cmd(0x01); + LV_DRV_DELAY_MS(20); + + ssd1963_cmd(0x01); + LV_DRV_DELAY_MS(20); + +} + +/** + * Command mode + */ +static inline void ssd1963_cmd_mode(void) +{ + if(cmd_mode == false) { + LV_DRV_DISP_CMD_DATA(SSD1963_CMD_MODE); + cmd_mode = true; + } +} + +/** + * Data mode + */ +static inline void ssd1963_data_mode(void) +{ + if(cmd_mode != false) { + LV_DRV_DISP_CMD_DATA(SSD1963_DATA_MODE); + cmd_mode = false; + } +} + +/** + * Write command + * @param cmd the command + */ +static inline void ssd1963_cmd(uint8_t cmd) +{ + + LV_DRV_DISP_PAR_CS(0); + ssd1963_cmd_mode(); + LV_DRV_DISP_PAR_WR_WORD(cmd); + LV_DRV_DISP_PAR_CS(1); + +} + +/** + * Write data + * @param data the data + */ +static inline void ssd1963_data(uint8_t data) +{ + + LV_DRV_DISP_PAR_CS(0); + ssd1963_data_mode(); + LV_DRV_DISP_PAR_WR_WORD(data); + LV_DRV_DISP_PAR_CS(1); + +} + +#endif diff --git a/libs/lv_drivers/display/SSD1963.h b/libs/lv_drivers/display/SSD1963.h new file mode 100644 index 00000000..49639f59 --- /dev/null +++ b/libs/lv_drivers/display/SSD1963.h @@ -0,0 +1,150 @@ +/** + * @file SSD1963.h + * + */ + +#ifndef SSD1963_H +#define SSD1963_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_SSD1963 + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ +// SSD1963 command table +#define CMD_NOP 0x00 //No operation +#define CMD_SOFT_RESET 0x01 //Software reset +#define CMD_GET_PWR_MODE 0x0A //Get the current power mode +#define CMD_GET_ADDR_MODE 0x0B //Get the frame memory to the display panel read order +#define CMD_GET_PIXEL_FORMAT 0x0C //Get the current pixel format +#define CMD_GET_DISPLAY_MODE 0x0D //Returns the display mode +#define CMD_GET_SIGNAL_MODE 0x0E // +#define CMD_GET_DIAGNOSTIC 0x0F +#define CMD_ENT_SLEEP 0x10 +#define CMD_EXIT_SLEEP 0x11 +#define CMD_ENT_PARTIAL_MODE 0x12 +#define CMD_ENT_NORMAL_MODE 0x13 +#define CMD_EXIT_INVERT_MODE 0x20 +#define CMD_ENT_INVERT_MODE 0x21 +#define CMD_SET_GAMMA 0x26 +#define CMD_BLANK_DISPLAY 0x28 +#define CMD_ON_DISPLAY 0x29 +#define CMD_SET_COLUMN 0x2A +#define CMD_SET_PAGE 0x2B +#define CMD_WR_MEMSTART 0x2C +#define CMD_RD_MEMSTART 0x2E +#define CMD_SET_PARTIAL_AREA 0x30 +#define CMD_SET_SCROLL_AREA 0x33 +#define CMD_SET_TEAR_OFF 0x34 //synchronization information is not sent from the display +#define CMD_SET_TEAR_ON 0x35 //sync. information is sent from the display +#define CMD_SET_ADDR_MODE 0x36 //set fram buffer read order to the display panel +#define CMD_SET_SCROLL_START 0x37 +#define CMD_EXIT_IDLE_MODE 0x38 +#define CMD_ENT_IDLE_MODE 0x39 +#define CMD_SET_PIXEL_FORMAT 0x3A //defines how many bits per pixel is used +#define CMD_WR_MEM_AUTO 0x3C +#define CMD_RD_MEM_AUTO 0x3E +#define CMD_SET_TEAR_SCANLINE 0x44 +#define CMD_GET_SCANLINE 0x45 +#define CMD_RD_DDB_START 0xA1 +#define CMD_RD_DDB_AUTO 0xA8 +#define CMD_SET_PANEL_MODE 0xB0 +#define CMD_GET_PANEL_MODE 0xB1 +#define CMD_SET_HOR_PERIOD 0xB4 +#define CMD_GET_HOR_PERIOD 0xB5 +#define CMD_SET_VER_PERIOD 0xB6 +#define CMD_GET_VER_PERIOD 0xB7 +#define CMD_SET_GPIO_CONF 0xB8 +#define CMD_GET_GPIO_CONF 0xB9 +#define CMD_SET_GPIO_VAL 0xBA +#define CMD_GET_GPIO_STATUS 0xBB +#define CMD_SET_POST_PROC 0xBC +#define CMD_GET_POST_PROC 0xBD +#define CMD_SET_PWM_CONF 0xBE +#define CMD_GET_PWM_CONF 0xBF +#define CMD_SET_LCD_GEN0 0xC0 +#define CMD_GET_LCD_GEN0 0xC1 +#define CMD_SET_LCD_GEN1 0xC2 +#define CMD_GET_LCD_GEN1 0xC3 +#define CMD_SET_LCD_GEN2 0xC4 +#define CMD_GET_LCD_GEN2 0xC5 +#define CMD_SET_LCD_GEN3 0xC6 +#define CMD_GET_LCD_GEN3 0xC7 +#define CMD_SET_GPIO0_ROP 0xC8 +#define CMD_GET_GPIO0_ROP 0xC9 +#define CMD_SET_GPIO1_ROP 0xCA +#define CMD_GET_GPIO1_ROP 0xCB +#define CMD_SET_GPIO2_ROP 0xCC +#define CMD_GET_GPIO2_ROP 0xCD +#define CMD_SET_GPIO3_ROP 0xCE +#define CMD_GET_GPIO3_ROP 0xCF +#define CMD_SET_ABC_DBC_CONF 0xD0 +#define CMD_GET_ABC_DBC_CONF 0xD1 +#define CMD_SET_DBC_HISTO_PTR 0xD2 +#define CMD_GET_DBC_HISTO_PTR 0xD3 +#define CMD_SET_DBC_THRES 0xD4 +#define CMD_GET_DBC_THRES 0xD5 +#define CMD_SET_ABM_TMR 0xD6 +#define CMD_GET_ABM_TMR 0xD7 +#define CMD_SET_AMB_LVL0 0xD8 +#define CMD_GET_AMB_LVL0 0xD9 +#define CMD_SET_AMB_LVL1 0xDA +#define CMD_GET_AMB_LVL1 0xDB +#define CMD_SET_AMB_LVL2 0xDC +#define CMD_GET_AMB_LVL2 0xDD +#define CMD_SET_AMB_LVL3 0xDE +#define CMD_GET_AMB_LVL3 0xDF +#define CMD_PLL_START 0xE0 //start the PLL +#define CMD_PLL_STOP 0xE1 //disable the PLL +#define CMD_SET_PLL_MN 0xE2 +#define CMD_GET_PLL_MN 0xE3 +#define CMD_GET_PLL_STATUS 0xE4 //get the current PLL status +#define CMD_ENT_DEEP_SLEEP 0xE5 +#define CMD_SET_PCLK 0xE6 //set pixel clock (LSHIFT signal) frequency +#define CMD_GET_PCLK 0xE7 //get pixel clock (LSHIFT signal) freq. settings +#define CMD_SET_DATA_INTERFACE 0xF0 +#define CMD_GET_DATA_INTERFACE 0xF1 + + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void ssd1963_init(void); +void ssd1963_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); + +/********************** + * MACROS + **********************/ + +#endif /* USE_SSD1963 */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SSD1963_H */ diff --git a/libs/lv_drivers/display/ST7565.c b/libs/lv_drivers/display/ST7565.c new file mode 100644 index 00000000..e4eac4b2 --- /dev/null +++ b/libs/lv_drivers/display/ST7565.c @@ -0,0 +1,289 @@ +/** + * @file ST7565.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "ST7565.h" +#if USE_ST7565 + +#include +#include +#include +#include "lvgl/lv_core/lv_vdb.h" +#include LV_DRV_DISP_INCLUDE +#include LV_DRV_DELAY_INCLUDE + +/********************* + * DEFINES + *********************/ +#define ST7565_BAUD 2000000 /*< 2,5 MHz (400 ns)*/ + +#define ST7565_CMD_MODE 0 +#define ST7565_DATA_MODE 1 + +#define ST7565_HOR_RES 128 +#define ST7565_VER_RES 64 + +#define CMD_DISPLAY_OFF 0xAE +#define CMD_DISPLAY_ON 0xAF + +#define CMD_SET_DISP_START_LINE 0x40 +#define CMD_SET_PAGE 0xB0 + +#define CMD_SET_COLUMN_UPPER 0x10 +#define CMD_SET_COLUMN_LOWER 0x00 + +#define CMD_SET_ADC_NORMAL 0xA0 +#define CMD_SET_ADC_REVERSE 0xA1 + +#define CMD_SET_DISP_NORMAL 0xA6 +#define CMD_SET_DISP_REVERSE 0xA7 + +#define CMD_SET_ALLPTS_NORMAL 0xA4 +#define CMD_SET_ALLPTS_ON 0xA5 +#define CMD_SET_BIAS_9 0xA2 +#define CMD_SET_BIAS_7 0xA3 + +#define CMD_RMW 0xE0 +#define CMD_RMW_CLEAR 0xEE +#define CMD_INTERNAL_RESET 0xE2 +#define CMD_SET_COM_NORMAL 0xC0 +#define CMD_SET_COM_REVERSE 0xC8 +#define CMD_SET_POWER_CONTROL 0x28 +#define CMD_SET_RESISTOR_RATIO 0x20 +#define CMD_SET_VOLUME_FIRST 0x81 +#define CMD_SET_VOLUME_SECOND 0x00 +#define CMD_SET_STATIC_OFF 0xAC +#define CMD_SET_STATIC_ON 0xAD +#define CMD_SET_STATIC_REG 0x00 +#define CMD_SET_BOOSTER_FIRST 0xF8 +#define CMD_SET_BOOSTER_234 0x00 +#define CMD_SET_BOOSTER_5 0x01 +#define CMD_SET_BOOSTER_6 0x03 +#define CMD_NOP 0xE3 +#define CMD_TEST 0xF0 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void st7565_sync(int32_t x1, int32_t y1, int32_t x2, int32_t y2); +static void st7565_command(uint8_t cmd); +static void st7565_data(uint8_t data); + +/********************** + * STATIC VARIABLES + **********************/ +static uint8_t lcd_fb[ST7565_HOR_RES * ST7565_VER_RES / 8] = {0xAA, 0xAA}; +static uint8_t pagemap[] = { 7, 6, 5, 4, 3, 2, 1, 0 }; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Initialize the ST7565 + */ +void st7565_init(void) +{ + LV_DRV_DISP_RST(1); + LV_DRV_DELAY_MS(10); + LV_DRV_DISP_RST(0); + LV_DRV_DELAY_MS(10); + LV_DRV_DISP_RST(1); + LV_DRV_DELAY_MS(10); + + LV_DRV_DISP_SPI_CS(0); + + st7565_command(CMD_SET_BIAS_7); + st7565_command(CMD_SET_ADC_NORMAL); + st7565_command(CMD_SET_COM_NORMAL); + st7565_command(CMD_SET_DISP_START_LINE); + st7565_command(CMD_SET_POWER_CONTROL | 0x4); + LV_DRV_DELAY_MS(50); + + st7565_command(CMD_SET_POWER_CONTROL | 0x6); + LV_DRV_DELAY_MS(50); + + st7565_command(CMD_SET_POWER_CONTROL | 0x7); + LV_DRV_DELAY_MS(10); + + st7565_command(CMD_SET_RESISTOR_RATIO | 0x6); // Defaulted to 0x26 (but could also be between 0x20-0x27 based on display's specs) + + st7565_command(CMD_DISPLAY_ON); + st7565_command(CMD_SET_ALLPTS_NORMAL); + + /*Set brightness*/ + st7565_command(CMD_SET_VOLUME_FIRST); + st7565_command(CMD_SET_VOLUME_SECOND | (0x18 & 0x3f)); + + LV_DRV_DISP_SPI_CS(1); + + memset(lcd_fb, 0x00, sizeof(lcd_fb)); +} + + +void st7565_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p) +{ + /*Return if the area is out the screen*/ + if(x2 < 0) return; + if(y2 < 0) return; + if(x1 > ST7565_HOR_RES - 1) return; + if(y1 > ST7565_VER_RES - 1) return; + + /*Truncate the area to the screen*/ + int32_t act_x1 = x1 < 0 ? 0 : x1; + int32_t act_y1 = y1 < 0 ? 0 : y1; + int32_t act_x2 = x2 > ST7565_HOR_RES - 1 ? ST7565_HOR_RES - 1 : x2; + int32_t act_y2 = y2 > ST7565_VER_RES - 1 ? ST7565_VER_RES - 1 : y2; + + int32_t x, y; + + /*Set the first row in */ + + /*Refresh frame buffer*/ + for(y = act_y1; y <= act_y2; y++) { + for(x = act_x1; x <= act_x2; x++) { + if(lv_color_to1(*color_p) != 0) { + lcd_fb[x + (y / 8)*ST7565_HOR_RES] &= ~(1 << (7 - (y % 8))); + } else { + lcd_fb[x + (y / 8)*ST7565_HOR_RES] |= (1 << (7 - (y % 8))); + } + color_p ++; + } + + color_p += x2 - act_x2; /*Next row*/ + } + + st7565_sync(act_x1, act_y1, act_x2, act_y2); + lv_flush_ready(); +} + + + +void st7565_fill(int32_t x1, int32_t y1, int32_t x2, int32_t y2, lv_color_t color) +{ + /*Return if the area is out the screen*/ + if(x2 < 0) return; + if(y2 < 0) return; + if(x1 > ST7565_HOR_RES - 1) return; + if(y1 > ST7565_VER_RES - 1) return; + + /*Truncate the area to the screen*/ + int32_t act_x1 = x1 < 0 ? 0 : x1; + int32_t act_y1 = y1 < 0 ? 0 : y1; + int32_t act_x2 = x2 > ST7565_HOR_RES - 1 ? ST7565_HOR_RES - 1 : x2; + int32_t act_y2 = y2 > ST7565_VER_RES - 1 ? ST7565_VER_RES - 1 : y2; + + int32_t x, y; + uint8_t white = lv_color_to1(color); + + /*Refresh frame buffer*/ + for(y = act_y1; y <= act_y2; y++) { + for(x = act_x1; x <= act_x2; x++) { + if(white != 0) { + lcd_fb[x + (y / 8)*ST7565_HOR_RES] |= (1 << (7 - (y % 8))); + } else { + lcd_fb[x + (y / 8)*ST7565_HOR_RES] &= ~(1 << (7 - (y % 8))); + } + } + } + + st7565_sync(act_x1, act_y1, act_x2, act_y2); +} + +void st7565_map(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p) +{ + /*Return if the area is out the screen*/ + if(x2 < 0) return; + if(y2 < 0) return; + if(x1 > ST7565_HOR_RES - 1) return; + if(y1 > ST7565_VER_RES - 1) return; + + /*Truncate the area to the screen*/ + int32_t act_x1 = x1 < 0 ? 0 : x1; + int32_t act_y1 = y1 < 0 ? 0 : y1; + int32_t act_x2 = x2 > ST7565_HOR_RES - 1 ? ST7565_HOR_RES - 1 : x2; + int32_t act_y2 = y2 > ST7565_VER_RES - 1 ? ST7565_VER_RES - 1 : y2; + + int32_t x, y; + + /*Set the first row in */ + + /*Refresh frame buffer*/ + for(y = act_y1; y <= act_y2; y++) { + for(x = act_x1; x <= act_x2; x++) { + if(lv_color_to1(*color_p) != 0) { + lcd_fb[x + (y / 8)*ST7565_HOR_RES] &= ~(1 << (7 - (y % 8))); + } else { + lcd_fb[x + (y / 8)*ST7565_HOR_RES] |= (1 << (7 - (y % 8))); + } + color_p ++; + } + + color_p += x2 - act_x2; /*Next row*/ + } + + st7565_sync(act_x1, act_y1, act_x2, act_y2); +} +/********************** + * STATIC FUNCTIONS + **********************/ +/** + * Flush a specific part of the buffer to the display + * @param x1 left coordinate of the area to flush + * @param y1 top coordinate of the area to flush + * @param x2 right coordinate of the area to flush + * @param y2 bottom coordinate of the area to flush + */ +static void st7565_sync(int32_t x1, int32_t y1, int32_t x2, int32_t y2) +{ + + LV_DRV_DISP_SPI_CS(0); + + uint8_t c, p; + for(p = y1 / 8; p <= y2 / 8; p++) { + st7565_command(CMD_SET_PAGE | pagemap[p]); + st7565_command(CMD_SET_COLUMN_LOWER | (x1 & 0xf)); + st7565_command(CMD_SET_COLUMN_UPPER | ((x1 >> 4) & 0xf)); + st7565_command(CMD_RMW); + + for(c = x1; c <= x2; c++) { + st7565_data(lcd_fb[(ST7565_HOR_RES * p) + c]); + } + } + + LV_DRV_DISP_SPI_CS(1); +} + +/** + * Write a command to the ST7565 + * @param cmd the command + */ +static void st7565_command(uint8_t cmd) +{ + LV_DRV_DISP_CMD_DATA(ST7565_CMD_MODE); + LV_DRV_DISP_SPI_WR_BYTE(cmd); +} + +/** + * Write data to the ST7565 + * @param data the data + */ +static void st7565_data(uint8_t data) +{ + LV_DRV_DISP_CMD_DATA(ST7565_DATA_MODE); + LV_DRV_DISP_SPI_WR_BYTE(data); +} + +#endif diff --git a/libs/lv_drivers/display/ST7565.h b/libs/lv_drivers/display/ST7565.h new file mode 100644 index 00000000..1a96df43 --- /dev/null +++ b/libs/lv_drivers/display/ST7565.h @@ -0,0 +1,58 @@ +/** + * @file ST7565.h + * + */ + +#ifndef ST7565_H +#define ST7565_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_ST7565 + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void st7565_init(void); +void st7565_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p); +void st7565_fill(int32_t x1, int32_t y1, int32_t x2, int32_t y2, lv_color_t color); +void st7565_map(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p); + +/********************** + * MACROS + **********************/ + +#endif /* USE_ST7565 */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ST7565_H */ diff --git a/libs/lv_drivers/display/UC1610.c b/libs/lv_drivers/display/UC1610.c new file mode 100644 index 00000000..362aead3 --- /dev/null +++ b/libs/lv_drivers/display/UC1610.c @@ -0,0 +1,206 @@ +/** + * @file UC1610.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "UC1610.h" + +#if USE_UC1610 + +#include +#include LV_DRV_DISP_INCLUDE +#include LV_DRV_DELAY_INCLUDE + +/********************* + * DEFINES + *********************/ +#define UC1610_CMD_MODE 0 +#define UC1610_DATA_MODE 1 +#define UC1610_RESET_MODE 0 +#define UC1610_SET_MODE 1 + +/* hardware control commands */ +#define UC1610_SYSTEM_RESET 0xE2 /* software reset */ +#define UC1610_NOP 0xE3 +#define UC1610_SET_TEMP_COMP 0x24 /* set temperature compensation, default -0.05%/°C */ +#define UC1610_SET_PANEL_LOADING 0x29 /* set panel loading, default 16~21 nF */ +#define UC1610_SET_PUMP_CONTROL 0x2F /* default internal Vlcd (8x pump) */ +#define UC1610_SET_LCD_BIAS_RATIO 0xEB /* default 11 */ +#define UC1610_SET_VBIAS_POT 0x81 /* 1 byte (0~255) to follow setting the contrast, default 0x81 */ +#define UC1610_SET_LINE_RATE 0xA0 /* default 12,1 Klps */ +#define UC1610_SET_DISPLAY_ENABLE 0xAE /* + 1 / 0 : exit sleep mode / entering sleep mode */ +#define UC1610_SET_LCD_GRAY_SHADE 0xD0 /* default 24% between the two gray shade levels */ +#define UC1610_SET_COM_END 0xF1 /* set the number of used com electrodes (lines number -1) */ + +/* ram address control */ +#define UC1610_SET_AC 0x88 /* set ram address control */ +#define UC1610_AC_WA_FLAG 1 /* automatic column/page increment wrap around (1 : cycle increment) */ +#define UC1610_AC_AIO_FLAG (1 << 1) /* auto increment order (0/1 : column/page increment first) */ +#define UC1610_AC_PID_FLAG (1 << 2) /* page address auto increment order (0/1 : +1/-1) */ + +/* set cursor ram address */ +#define UC1610_SET_CA_LSB 0x00 /* + 4 LSB bits */ +#define UC1610_SET_CA_MSB 0x10 /* + 4 MSB bits // MSB + LSB values range : 0~159 */ +#define UC1610_SET_PA 0x60 /* + 5 bits // values range : 0~26 */ + +/* display control commands */ +#define UC1610_SET_FIXED_LINES 0x90 /* + 4 bits = 2xFL */ +#define UC1610_SET_SCROLL_LINES_LSB 0x40 /* + 4 LSB bits scroll up display by N (7 bits) lines */ +#define UC1610_SET_SCROLL_LINES_MSB 0x50 /* + 3 MSB bits */ +#define UC1610_SET_ALL_PIXEL_ON 0xA4 /* + 1 / 0 : set all pixel on, reverse */ +#define UC1610_SET_INVERSE_DISPLAY 0xA6 /* + 1 / 0 : inverse all data stored in ram, reverse */ +#define UC1610_SET_MAPPING_CONTROL 0xC0 /* control mirroring */ +#define UC1610_SET_MAPPING_CONTROL_LC_FLAG 1 +#define UC1610_SET_MAPPING_CONTROL_MX_FLAG (1 << 1) +#define UC1610_SET_MAPPING_CONTROL_MY_FLAG (1 << 2) + +/* window program mode */ +#define UC1610_SET_WINDOW_PROGRAM_ENABLE 0xF8 /* + 1 / 0 : enable / disable window programming mode, */ + /* reset before changing boundaries */ +#define UC1610_SET_WP_STARTING_CA 0xF4 /* 1 byte to follow for column address */ +#define UC1610_SET_WP_ENDING_CA 0xF6 /* 1 byte to follow for column address */ +#define UC1610_SET_WP_STARTING_PA 0xF5 /* 1 byte to follow for page address */ +#define UC1610_SET_WP_ENDING_PA 0xF7 /* 1 byte to follow for page address */ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static uint8_t cmd_buf[12]; + +/********************** + * MACROS + **********************/ + +/* Return the byte bitmask of a pixel color corresponding to draw_buf arrangement */ +#define PIXIDX(y, c) ((c) << (((y) & 3) << 1)) + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void uc1610_init(void) { + LV_DRV_DELAY_MS(12); + + /* initialization sequence */ +#if UC1610_INIT_HARD_RST + LV_DRV_DISP_RST(UC1610_RESET_MODE); /* hardware reset */ + LV_DRV_DELAY_MS(1); + LV_DRV_DISP_RST(UC1610_SET_MODE); +#else + cmd_buf[0] = UC1610_SYSTEM_RESET; /* software reset */ + LV_DRV_DISP_CMD_DATA(UC1610_CMD_MODE); + LV_DRV_DISP_SPI_CS(0); + LV_DRV_DISP_SPI_WR_ARRAY(cmd_buf, 1); + LV_DRV_DISP_SPI_CS(1); +#endif + + LV_DRV_DELAY_MS(2); + cmd_buf[0] = UC1610_SET_COM_END; /* set com end value */ + cmd_buf[1] = UC1610_VER_RES - 1; + cmd_buf[2] = UC1610_SET_PANEL_LOADING; + cmd_buf[3] = UC1610_SET_LCD_BIAS_RATIO; + cmd_buf[4] = UC1610_SET_VBIAS_POT; /* set contrast */ + cmd_buf[5] = (UC1610_INIT_CONTRAST * 255) / 100; +#if UC1610_TOP_VIEW + cmd_buf[6] = UC1610_SET_MAPPING_CONTROL | /* top view */ + UC1610_SET_MAPPING_CONTROL_MY_FLAG | + UC1610_SET_MAPPING_CONTROL_MX_FLAG; +#else + cmd_buf[6] = UC1610_SET_MAPPING_CONTROL; /* bottom view */ +#endif + cmd_buf[7] = UC1610_SET_SCROLL_LINES_LSB | 0; /* set scroll line on line 0 */ + cmd_buf[8] = UC1610_SET_SCROLL_LINES_MSB | 0; + cmd_buf[9] = UC1610_SET_AC | UC1610_AC_WA_FLAG; /* set auto increment wrap around */ + cmd_buf[10] = UC1610_SET_INVERSE_DISPLAY | 1; /* invert colors to complies lv color system */ + cmd_buf[11] = UC1610_SET_DISPLAY_ENABLE | 1; /* turn display on */ + + LV_DRV_DISP_CMD_DATA(UC1610_CMD_MODE); + LV_DRV_DISP_SPI_CS(0); + LV_DRV_DISP_SPI_WR_ARRAY(cmd_buf, 12); + LV_DRV_DISP_SPI_CS(1); +} + + +void uc1610_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { + /*Return if the area is out the screen*/ + if(area->x2 < 0) return; + if(area->y2 < 0) return; + if(area->x1 > UC1610_HOR_RES - 1) return; + if(area->y1 > UC1610_VER_RES - 1) return; + + /*Truncate the area to the screen*/ + uint8_t act_x1 = area->x1 < 0 ? 0 : area->x1; + uint8_t act_y1 = area->y1 < 0 ? 0 : area->y1; + uint8_t act_x2 = area->x2 > UC1610_HOR_RES - 1 ? UC1610_HOR_RES - 1 : area->x2; + uint8_t act_y2 = area->y2 > UC1610_VER_RES - 1 ? UC1610_VER_RES - 1 : area->y2; + + uint8_t * buf = (uint8_t *) color_p; + uint16_t buf_size = (act_x2 - act_x1 + 1) * (((act_y2 - act_y1) >> 2) + 1); + + /*Set display window to fill*/ + cmd_buf[0] = UC1610_SET_WINDOW_PROGRAM_ENABLE | 0; /* before changing boundaries */ + cmd_buf[1] = UC1610_SET_WP_STARTING_CA; + cmd_buf[2] = act_x1; + cmd_buf[3] = UC1610_SET_WP_ENDING_CA; + cmd_buf[4] = act_x2; + cmd_buf[5] = UC1610_SET_WP_STARTING_PA; + cmd_buf[6] = act_y1 >> 2; + cmd_buf[7] = UC1610_SET_WP_ENDING_PA; + cmd_buf[8] = act_y2 >> 2; + cmd_buf[9] = UC1610_SET_WINDOW_PROGRAM_ENABLE | 1; /* entering window programming */ + + LV_DRV_DISP_CMD_DATA(UC1610_CMD_MODE); + LV_DRV_DISP_SPI_CS(0); + LV_DRV_DISP_SPI_WR_ARRAY(cmd_buf, 10); + LV_DRV_DISP_SPI_CS(1); + + /*Flush draw_buf on display memory*/ + LV_DRV_DISP_CMD_DATA(UC1610_DATA_MODE); + LV_DRV_DISP_SPI_CS(0); + LV_DRV_DISP_SPI_WR_ARRAY(buf, buf_size); + LV_DRV_DISP_SPI_CS(1); + + lv_disp_flush_ready(disp_drv); +} + +void uc1610_set_px_cb(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) { + (void) disp_drv; + (void) opa; + + uint16_t idx = x + buf_w * (y >> 2); + + /* Convert color to depth 2 */ +#if LV_COLOR_DEPTH == 1 + uint8_t color2 = color.full * 3; +#else + uint8_t color2 = color.full >> (LV_COLOR_DEPTH - 2); +#endif + + buf[idx] &= ~PIXIDX(y, 3); /* reset pixel color */ + buf[idx] |= PIXIDX(y, color2); /* write new color */ +} + +void uc1610_rounder_cb(lv_disp_drv_t * disp_drv, lv_area_t * area) { + (void) disp_drv; + + /* Round y window to display memory page size */ + area->y1 = (area->y1 & (~3)); + area->y2 = (area->y2 & (~3)) + 3; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +#endif diff --git a/libs/lv_drivers/display/UC1610.h b/libs/lv_drivers/display/UC1610.h new file mode 100644 index 00000000..38b1fa3c --- /dev/null +++ b/libs/lv_drivers/display/UC1610.h @@ -0,0 +1,58 @@ +/** + * @file UC1610.h + * + */ + +#ifndef UC1610_H +#define UC1610_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_UC1610 + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void uc1610_init(void); +void uc1610_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); +void uc1610_rounder_cb(lv_disp_drv_t * disp_drv, lv_area_t * area); +void uc1610_set_px_cb(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa); + +/********************** + * MACROS + **********************/ + +#endif /* USE_UC1610 */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* UC1610_H */ diff --git a/libs/lv_drivers/display/drm.c b/libs/lv_drivers/display/drm.c new file mode 100644 index 00000000..239348ef --- /dev/null +++ b/libs/lv_drivers/display/drm.c @@ -0,0 +1,801 @@ +/** + * @file drm.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "drm.h" +#if USE_DRM + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DBG_TAG "drm" + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +#define print(msg, ...) fprintf(stderr, msg, ##__VA_ARGS__); +#define err(msg, ...) print("error: " msg "\n", ##__VA_ARGS__) +#define info(msg, ...) print(msg "\n", ##__VA_ARGS__) +#define dbg(msg, ...) {} //print(DBG_TAG ": " msg "\n", ##__VA_ARGS__) + +struct drm_buffer { + uint32_t handle; + uint32_t pitch; + uint32_t offset; + unsigned long int size; + void * map; + uint32_t fb_handle; +}; + +struct drm_dev { + int fd; + uint32_t conn_id, enc_id, crtc_id, plane_id, crtc_idx; + uint32_t width, height; + uint32_t mmWidth, mmHeight; + uint32_t fourcc; + drmModeModeInfo mode; + uint32_t blob_id; + drmModeCrtc *saved_crtc; + drmModeAtomicReq *req; + drmEventContext drm_event_ctx; + drmModePlane *plane; + drmModeCrtc *crtc; + drmModeConnector *conn; + uint32_t count_plane_props; + uint32_t count_crtc_props; + uint32_t count_conn_props; + drmModePropertyPtr plane_props[128]; + drmModePropertyPtr crtc_props[128]; + drmModePropertyPtr conn_props[128]; + struct drm_buffer drm_bufs[2]; /* DUMB buffers */ + struct drm_buffer *cur_bufs[2]; /* double buffering handling */ +} drm_dev; + +static uint32_t get_plane_property_id(const char *name) +{ + uint32_t i; + + dbg("Find plane property: %s", name); + + for (i = 0; i < drm_dev.count_plane_props; ++i) + if (!strcmp(drm_dev.plane_props[i]->name, name)) + return drm_dev.plane_props[i]->prop_id; + + dbg("Unknown plane property: %s", name); + + return 0; +} + +static uint32_t get_crtc_property_id(const char *name) +{ + uint32_t i; + + dbg("Find crtc property: %s", name); + + for (i = 0; i < drm_dev.count_crtc_props; ++i) + if (!strcmp(drm_dev.crtc_props[i]->name, name)) + return drm_dev.crtc_props[i]->prop_id; + + dbg("Unknown crtc property: %s", name); + + return 0; +} + +static uint32_t get_conn_property_id(const char *name) +{ + uint32_t i; + + dbg("Find conn property: %s", name); + + for (i = 0; i < drm_dev.count_conn_props; ++i) + if (!strcmp(drm_dev.conn_props[i]->name, name)) + return drm_dev.conn_props[i]->prop_id; + + dbg("Unknown conn property: %s", name); + + return 0; +} + +static void page_flip_handler(int fd, unsigned int sequence, unsigned int tv_sec, + unsigned int tv_usec, void *user_data) +{ + dbg("flip"); +} + +static int drm_get_plane_props(void) +{ + uint32_t i; + + drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_dev.fd, drm_dev.plane_id, + DRM_MODE_OBJECT_PLANE); + if (!props) { + err("drmModeObjectGetProperties failed"); + return -1; + } + dbg("Found %u plane props", props->count_props); + drm_dev.count_plane_props = props->count_props; + for (i = 0; i < props->count_props; i++) { + drm_dev.plane_props[i] = drmModeGetProperty(drm_dev.fd, props->props[i]); + dbg("Added plane prop %u:%s", drm_dev.plane_props[i]->prop_id, drm_dev.plane_props[i]->name); + } + drmModeFreeObjectProperties(props); + + return 0; +} + +static int drm_get_crtc_props(void) +{ + uint32_t i; + + drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_dev.fd, drm_dev.crtc_id, + DRM_MODE_OBJECT_CRTC); + if (!props) { + err("drmModeObjectGetProperties failed"); + return -1; + } + dbg("Found %u crtc props", props->count_props); + drm_dev.count_crtc_props = props->count_props; + for (i = 0; i < props->count_props; i++) { + drm_dev.crtc_props[i] = drmModeGetProperty(drm_dev.fd, props->props[i]); + dbg("Added crtc prop %u:%s", drm_dev.crtc_props[i]->prop_id, drm_dev.crtc_props[i]->name); + } + drmModeFreeObjectProperties(props); + + return 0; +} + +static int drm_get_conn_props(void) +{ + uint32_t i; + + drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_dev.fd, drm_dev.conn_id, + DRM_MODE_OBJECT_CONNECTOR); + if (!props) { + err("drmModeObjectGetProperties failed"); + return -1; + } + dbg("Found %u connector props", props->count_props); + drm_dev.count_conn_props = props->count_props; + for (i = 0; i < props->count_props; i++) { + drm_dev.conn_props[i] = drmModeGetProperty(drm_dev.fd, props->props[i]); + dbg("Added connector prop %u:%s", drm_dev.conn_props[i]->prop_id, drm_dev.conn_props[i]->name); + } + drmModeFreeObjectProperties(props); + + return 0; +} + +static int drm_add_plane_property(const char *name, uint64_t value) +{ + int ret; + uint32_t prop_id = get_plane_property_id(name); + + if (!prop_id) { + err("Couldn't find plane prop %s", name); + return -1; + } + + ret = drmModeAtomicAddProperty(drm_dev.req, drm_dev.plane_id, get_plane_property_id(name), value); + if (ret < 0) { + err("drmModeAtomicAddProperty (%s:%" PRIu64 ") failed: %d", name, value, ret); + return ret; + } + + return 0; +} + +static int drm_add_crtc_property(const char *name, uint64_t value) +{ + int ret; + uint32_t prop_id = get_crtc_property_id(name); + + if (!prop_id) { + err("Couldn't find crtc prop %s", name); + return -1; + } + + ret = drmModeAtomicAddProperty(drm_dev.req, drm_dev.crtc_id, get_crtc_property_id(name), value); + if (ret < 0) { + err("drmModeAtomicAddProperty (%s:%" PRIu64 ") failed: %d", name, value, ret); + return ret; + } + + return 0; +} + +static int drm_add_conn_property(const char *name, uint64_t value) +{ + int ret; + uint32_t prop_id = get_conn_property_id(name); + + if (!prop_id) { + err("Couldn't find conn prop %s", name); + return -1; + } + + ret = drmModeAtomicAddProperty(drm_dev.req, drm_dev.conn_id, get_conn_property_id(name), value); + if (ret < 0) { + err("drmModeAtomicAddProperty (%s:%" PRIu64 ") failed: %d", name, value, ret); + return ret; + } + + return 0; +} + +static int drm_dmabuf_set_plane(struct drm_buffer *buf) +{ + int ret; + static int first = 1; + uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT; + + drm_dev.req = drmModeAtomicAlloc(); + + /* On first Atomic commit, do a modeset */ + if (first) { + drm_add_conn_property("CRTC_ID", drm_dev.crtc_id); + + drm_add_crtc_property("MODE_ID", drm_dev.blob_id); + drm_add_crtc_property("ACTIVE", 1); + + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + + first = 0; + } + + drm_add_plane_property("FB_ID", buf->fb_handle); + drm_add_plane_property("CRTC_ID", drm_dev.crtc_id); + drm_add_plane_property("SRC_X", 0); + drm_add_plane_property("SRC_Y", 0); + drm_add_plane_property("SRC_W", drm_dev.width << 16); + drm_add_plane_property("SRC_H", drm_dev.height << 16); + drm_add_plane_property("CRTC_X", 0); + drm_add_plane_property("CRTC_Y", 0); + drm_add_plane_property("CRTC_W", drm_dev.width); + drm_add_plane_property("CRTC_H", drm_dev.height); + + ret = drmModeAtomicCommit(drm_dev.fd, drm_dev.req, flags, NULL); + if (ret) { + err("drmModeAtomicCommit failed: %s", strerror(errno)); + drmModeAtomicFree(drm_dev.req); + return ret; + } + + return 0; +} + +static int find_plane(unsigned int fourcc, uint32_t *plane_id, uint32_t crtc_id, uint32_t crtc_idx) +{ + drmModePlaneResPtr planes; + drmModePlanePtr plane; + unsigned int i; + unsigned int j; + int ret = 0; + unsigned int format = fourcc; + + planes = drmModeGetPlaneResources(drm_dev.fd); + if (!planes) { + err("drmModeGetPlaneResources failed"); + return -1; + } + + dbg("drm: found planes %u", planes->count_planes); + + for (i = 0; i < planes->count_planes; ++i) { + plane = drmModeGetPlane(drm_dev.fd, planes->planes[i]); + if (!plane) { + err("drmModeGetPlane failed: %s", strerror(errno)); + break; + } + + if (!(plane->possible_crtcs & (1 << crtc_idx))) { + drmModeFreePlane(plane); + continue; + } + + for (j = 0; j < plane->count_formats; ++j) { + if (plane->formats[j] == format) + break; + } + + if (j == plane->count_formats) { + drmModeFreePlane(plane); + continue; + } + + *plane_id = plane->plane_id; + drmModeFreePlane(plane); + + dbg("found plane %d", *plane_id); + + break; + } + + if (i == planes->count_planes) + ret = -1; + + drmModeFreePlaneResources(planes); + + return ret; +} + +static int drm_find_connector(void) +{ + drmModeConnector *conn = NULL; + drmModeEncoder *enc = NULL; + drmModeRes *res; + int i; + + if ((res = drmModeGetResources(drm_dev.fd)) == NULL) { + err("drmModeGetResources() failed"); + return -1; + } + + if (res->count_crtcs <= 0) { + err("no Crtcs"); + goto free_res; + } + + /* find all available connectors */ + for (i = 0; i < res->count_connectors; i++) { + conn = drmModeGetConnector(drm_dev.fd, res->connectors[i]); + if (!conn) + continue; + +#if DRM_CONNECTOR_ID >= 0 + if (conn->connector_id != DRM_CONNECTOR_ID) { + drmModeFreeConnector(conn); + continue; + } +#endif + + if (conn->connection == DRM_MODE_CONNECTED) { + dbg("drm: connector %d: connected", conn->connector_id); + } else if (conn->connection == DRM_MODE_DISCONNECTED) { + dbg("drm: connector %d: disconnected", conn->connector_id); + } else if (conn->connection == DRM_MODE_UNKNOWNCONNECTION) { + dbg("drm: connector %d: unknownconnection", conn->connector_id); + } else { + dbg("drm: connector %d: unknown", conn->connector_id); + } + + if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) + break; + + drmModeFreeConnector(conn); + conn = NULL; + }; + + if (!conn) { + err("suitable connector not found"); + goto free_res; + } + + drm_dev.conn_id = conn->connector_id; + dbg("conn_id: %d", drm_dev.conn_id); + drm_dev.mmWidth = conn->mmWidth; + drm_dev.mmHeight = conn->mmHeight; + + memcpy(&drm_dev.mode, &conn->modes[0], sizeof(drmModeModeInfo)); + + if (drmModeCreatePropertyBlob(drm_dev.fd, &drm_dev.mode, sizeof(drm_dev.mode), + &drm_dev.blob_id)) { + err("error creating mode blob"); + goto free_res; + } + + drm_dev.width = conn->modes[0].hdisplay; + drm_dev.height = conn->modes[0].vdisplay; + + for (i = 0 ; i < res->count_encoders; i++) { + enc = drmModeGetEncoder(drm_dev.fd, res->encoders[i]); + if (!enc) + continue; + + dbg("enc%d enc_id %d conn enc_id %d", i, enc->encoder_id, conn->encoder_id); + + if (enc->encoder_id == conn->encoder_id) + break; + + drmModeFreeEncoder(enc); + enc = NULL; + } + + if (enc) { + drm_dev.enc_id = enc->encoder_id; + dbg("enc_id: %d", drm_dev.enc_id); + drm_dev.crtc_id = enc->crtc_id; + dbg("crtc_id: %d", drm_dev.crtc_id); + drmModeFreeEncoder(enc); + } else { + /* Encoder hasn't been associated yet, look it up */ + for (i = 0; i < conn->count_encoders; i++) { + int crtc, crtc_id = -1; + + enc = drmModeGetEncoder(drm_dev.fd, conn->encoders[i]); + if (!enc) + continue; + + for (crtc = 0 ; crtc < res->count_crtcs; crtc++) { + uint32_t crtc_mask = 1 << crtc; + + crtc_id = res->crtcs[crtc]; + + dbg("enc_id %d crtc%d id %d mask %x possible %x", enc->encoder_id, crtc, crtc_id, crtc_mask, enc->possible_crtcs); + + if (enc->possible_crtcs & crtc_mask) + break; + } + + if (crtc_id > 0) { + drm_dev.enc_id = enc->encoder_id; + dbg("enc_id: %d", drm_dev.enc_id); + drm_dev.crtc_id = crtc_id; + dbg("crtc_id: %d", drm_dev.crtc_id); + break; + } + + drmModeFreeEncoder(enc); + enc = NULL; + } + + if (!enc) { + err("suitable encoder not found"); + goto free_res; + } + + drmModeFreeEncoder(enc); + } + + drm_dev.crtc_idx = -1; + + for (i = 0; i < res->count_crtcs; ++i) { + if (drm_dev.crtc_id == res->crtcs[i]) { + drm_dev.crtc_idx = i; + break; + } + } + + if (drm_dev.crtc_idx == -1) { + err("drm: CRTC not found"); + goto free_res; + } + + dbg("crtc_idx: %d", drm_dev.crtc_idx); + + return 0; + +free_res: + drmModeFreeResources(res); + + return -1; +} + +static int drm_open(const char *path) +{ + int fd, flags; + uint64_t has_dumb; + int ret; + + fd = open(path, O_RDWR); + if (fd < 0) { + err("cannot open \"%s\"", path); + return -1; + } + + /* set FD_CLOEXEC flag */ + if ((flags = fcntl(fd, F_GETFD)) < 0 || + fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { + err("fcntl FD_CLOEXEC failed"); + goto err; + } + + /* check capability */ + ret = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb); + if (ret < 0 || has_dumb == 0) { + err("drmGetCap DRM_CAP_DUMB_BUFFER failed or \"%s\" doesn't have dumb " + "buffer", path); + goto err; + } + + return fd; +err: + close(fd); + return -1; +} + +static int drm_setup(unsigned int fourcc) +{ + int ret; + const char *device_path = NULL; + + device_path = getenv("DRM_CARD"); + if (!device_path) + device_path = DRM_CARD; + + drm_dev.fd = drm_open(device_path); + if (drm_dev.fd < 0) + return -1; + + ret = drmSetClientCap(drm_dev.fd, DRM_CLIENT_CAP_ATOMIC, 1); + if (ret) { + err("No atomic modesetting support: %s", strerror(errno)); + goto err; + } + + ret = drm_find_connector(); + if (ret) { + err("available drm devices not found"); + goto err; + } + + ret = find_plane(fourcc, &drm_dev.plane_id, drm_dev.crtc_id, drm_dev.crtc_idx); + if (ret) { + err("Cannot find plane"); + goto err; + } + + drm_dev.plane = drmModeGetPlane(drm_dev.fd, drm_dev.plane_id); + if (!drm_dev.plane) { + err("Cannot get plane"); + goto err; + } + + drm_dev.crtc = drmModeGetCrtc(drm_dev.fd, drm_dev.crtc_id); + if (!drm_dev.crtc) { + err("Cannot get crtc"); + goto err; + } + + drm_dev.conn = drmModeGetConnector(drm_dev.fd, drm_dev.conn_id); + if (!drm_dev.conn) { + err("Cannot get connector"); + goto err; + } + + ret = drm_get_plane_props(); + if (ret) { + err("Cannot get plane props"); + goto err; + } + + ret = drm_get_crtc_props(); + if (ret) { + err("Cannot get crtc props"); + goto err; + } + + ret = drm_get_conn_props(); + if (ret) { + err("Cannot get connector props"); + goto err; + } + + drm_dev.drm_event_ctx.version = DRM_EVENT_CONTEXT_VERSION; + drm_dev.drm_event_ctx.page_flip_handler = page_flip_handler; + drm_dev.fourcc = fourcc; + + info("drm: Found plane_id: %u connector_id: %d crtc_id: %d", + drm_dev.plane_id, drm_dev.conn_id, drm_dev.crtc_id); + + info("drm: %dx%d (%dmm X% dmm) pixel format %c%c%c%c", + drm_dev.width, drm_dev.height, drm_dev.mmWidth, drm_dev.mmHeight, + (fourcc>>0)&0xff, (fourcc>>8)&0xff, (fourcc>>16)&0xff, (fourcc>>24)&0xff); + + return 0; + +err: + close(drm_dev.fd); + return -1; +} + +static int drm_allocate_dumb(struct drm_buffer *buf) +{ + struct drm_mode_create_dumb creq; + struct drm_mode_map_dumb mreq; + uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0}; + int ret; + + /* create dumb buffer */ + memset(&creq, 0, sizeof(creq)); + creq.width = drm_dev.width; + creq.height = drm_dev.height; + creq.bpp = LV_COLOR_DEPTH; + ret = drmIoctl(drm_dev.fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); + if (ret < 0) { + err("DRM_IOCTL_MODE_CREATE_DUMB fail"); + return -1; + } + + buf->handle = creq.handle; + buf->pitch = creq.pitch; + dbg("pitch %d", buf->pitch); + buf->size = creq.size; + dbg("size %d", buf->size); + + /* prepare buffer for memory mapping */ + memset(&mreq, 0, sizeof(mreq)); + mreq.handle = creq.handle; + ret = drmIoctl(drm_dev.fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); + if (ret) { + err("DRM_IOCTL_MODE_MAP_DUMB fail"); + return -1; + } + + buf->offset = mreq.offset; + + /* perform actual memory mapping */ + buf->map = mmap(0, creq.size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_dev.fd, mreq.offset); + if (buf->map == MAP_FAILED) { + err("mmap fail"); + return -1; + } + + /* clear the framebuffer to 0 (= full transparency in ARGB8888) */ + memset(buf->map, 0, creq.size); + + /* create framebuffer object for the dumb-buffer */ + handles[0] = creq.handle; + pitches[0] = creq.pitch; + offsets[0] = 0; + ret = drmModeAddFB2(drm_dev.fd, drm_dev.width, drm_dev.height, drm_dev.fourcc, + handles, pitches, offsets, &buf->fb_handle, 0); + if (ret) { + err("drmModeAddFB fail"); + return -1; + } + + return 0; +} + +static int drm_setup_buffers(void) +{ + int ret; + + /* Allocate DUMB buffers */ + ret = drm_allocate_dumb(&drm_dev.drm_bufs[0]); + if (ret) + return ret; + + ret = drm_allocate_dumb(&drm_dev.drm_bufs[1]); + if (ret) + return ret; + + /* Set buffering handling */ + drm_dev.cur_bufs[0] = NULL; + drm_dev.cur_bufs[1] = &drm_dev.drm_bufs[0]; + + return 0; +} + +void drm_wait_vsync(lv_disp_drv_t *disp_drv) +{ + int ret; + fd_set fds; + FD_ZERO(&fds); + FD_SET(drm_dev.fd, &fds); + + do { + ret = select(drm_dev.fd + 1, &fds, NULL, NULL, NULL); + } while (ret == -1 && errno == EINTR); + + if (ret < 0) { + err("select failed: %s", strerror(errno)); + drmModeAtomicFree(drm_dev.req); + drm_dev.req = NULL; + return; + } + + if (FD_ISSET(drm_dev.fd, &fds)) + drmHandleEvent(drm_dev.fd, &drm_dev.drm_event_ctx); + + drmModeAtomicFree(drm_dev.req); + drm_dev.req = NULL; +} + +void drm_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) +{ + struct drm_buffer *fbuf = drm_dev.cur_bufs[1]; + lv_coord_t w = (area->x2 - area->x1 + 1); + lv_coord_t h = (area->y2 - area->y1 + 1); + int i, y; + + dbg("x %d:%d y %d:%d w %d h %d", area->x1, area->x2, area->y1, area->y2, w, h); + + /* Partial update */ + if ((w != drm_dev.width || h != drm_dev.height) && drm_dev.cur_bufs[0]) + memcpy(fbuf->map, drm_dev.cur_bufs[0]->map, fbuf->size); + + for (y = 0, i = area->y1 ; i <= area->y2 ; ++i, ++y) { + memcpy((uint8_t *)fbuf->map + (area->x1 * (LV_COLOR_SIZE/8)) + (fbuf->pitch * i), + (uint8_t *)color_p + (w * (LV_COLOR_SIZE/8) * y), + w * (LV_COLOR_SIZE/8)); + } + + if (drm_dev.req) + drm_wait_vsync(disp_drv); + + /* show fbuf plane */ + if (drm_dmabuf_set_plane(fbuf)) { + err("Flush fail"); + return; + } + else + dbg("Flush done"); + + if (!drm_dev.cur_bufs[0]) + drm_dev.cur_bufs[1] = &drm_dev.drm_bufs[1]; + else + drm_dev.cur_bufs[1] = drm_dev.cur_bufs[0]; + + drm_dev.cur_bufs[0] = fbuf; + + lv_disp_flush_ready(disp_drv); +} + +#if LV_COLOR_DEPTH == 32 +#define DRM_FOURCC DRM_FORMAT_XRGB8888 +#elif LV_COLOR_DEPTH == 16 +#define DRM_FOURCC DRM_FORMAT_RGB565 +#else +#error LV_COLOR_DEPTH not supported +#endif + +void drm_get_sizes(lv_coord_t *width, lv_coord_t *height, uint32_t *dpi) +{ + if (width) + *width = drm_dev.width; + + if (height) + *height = drm_dev.height; + + if (dpi && drm_dev.mmWidth) + *dpi = DIV_ROUND_UP(drm_dev.width * 25400, drm_dev.mmWidth * 1000); +} + +void drm_init(void) +{ + int ret; + + ret = drm_setup(DRM_FOURCC); + if (ret) { + close(drm_dev.fd); + drm_dev.fd = -1; + return; + } + + ret = drm_setup_buffers(); + if (ret) { + err("DRM buffer allocation failed"); + close(drm_dev.fd); + drm_dev.fd = -1; + return; + } + + info("DRM subsystem and buffer mapped successfully"); +} + +void drm_exit(void) +{ + close(drm_dev.fd); + drm_dev.fd = -1; +} + +#endif diff --git a/libs/lv_drivers/display/drm.h b/libs/lv_drivers/display/drm.h new file mode 100644 index 00000000..ebf2e288 --- /dev/null +++ b/libs/lv_drivers/display/drm.h @@ -0,0 +1,60 @@ +/** + * @file drm.h + * + */ + +#ifndef DRM_H +#define DRM_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_DRM + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void drm_init(void); +void drm_get_sizes(lv_coord_t *width, lv_coord_t *height, uint32_t *dpi); +void drm_exit(void); +void drm_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p); +void drm_wait_vsync(lv_disp_drv_t * drv); + + +/********************** + * MACROS + **********************/ + +#endif /*USE_DRM*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*DRM_H*/ diff --git a/libs/lv_drivers/display/fbdev.c b/libs/lv_drivers/display/fbdev.c new file mode 100644 index 00000000..997a6a9f --- /dev/null +++ b/libs/lv_drivers/display/fbdev.c @@ -0,0 +1,277 @@ +/** + * @file fbdev.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "fbdev.h" +#if USE_FBDEV || USE_BSD_FBDEV + +#include +#include +#include +#include +#include +#include +#include + +#if USE_BSD_FBDEV +#include +#include +#include +#include +#else /* USE_BSD_FBDEV */ +#include +#endif /* USE_BSD_FBDEV */ + +/********************* + * DEFINES + *********************/ +#ifndef FBDEV_PATH +#define FBDEV_PATH "/dev/fb0" +#endif + +#ifndef DIV_ROUND_UP +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +#endif + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STRUCTURES + **********************/ + +struct bsd_fb_var_info{ + uint32_t xoffset; + uint32_t yoffset; + uint32_t xres; + uint32_t yres; + int bits_per_pixel; + }; + +struct bsd_fb_fix_info{ + long int line_length; + long int smem_len; +}; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +#if USE_BSD_FBDEV +static struct bsd_fb_var_info vinfo; +static struct bsd_fb_fix_info finfo; +#else +static struct fb_var_screeninfo vinfo; +static struct fb_fix_screeninfo finfo; +#endif /* USE_BSD_FBDEV */ +static char *fbp = 0; +static long int screensize = 0; +static int fbfd = 0; + +/********************** + * MACROS + **********************/ + +#if USE_BSD_FBDEV +#define FBIOBLANK FBIO_BLANK +#endif /* USE_BSD_FBDEV */ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void fbdev_init(void) +{ + // Open the file for reading and writing + fbfd = open(FBDEV_PATH, O_RDWR); + if(fbfd == -1) { + perror("Error: cannot open framebuffer device"); + return; + } + LV_LOG_INFO("The framebuffer device was opened successfully"); + +#if FBDEV_DISPLAY_POWER_ON + // Make sure that the display is on. + if (ioctl(fbfd, FBIOBLANK, FB_BLANK_UNBLANK) != 0) { + perror("ioctl(FBIOBLANK)"); + return; + } +#endif /* FBDEV_DISPLAY_POWER_ON */ + +#if USE_BSD_FBDEV + struct fbtype fb; + unsigned line_length; + + //Get fb type + if (ioctl(fbfd, FBIOGTYPE, &fb) != 0) { + perror("ioctl(FBIOGTYPE)"); + return; + } + + //Get screen width + if (ioctl(fbfd, FBIO_GETLINEWIDTH, &line_length) != 0) { + perror("ioctl(FBIO_GETLINEWIDTH)"); + return; + } + + vinfo.xres = (unsigned) fb.fb_width; + vinfo.yres = (unsigned) fb.fb_height; + vinfo.bits_per_pixel = fb.fb_depth; + vinfo.xoffset = 0; + vinfo.yoffset = 0; + finfo.line_length = line_length; + finfo.smem_len = finfo.line_length * vinfo.yres; +#else /* USE_BSD_FBDEV */ + + // Get fixed screen information + if(ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) == -1) { + perror("Error reading fixed information"); + return; + } + + // Get variable screen information + if(ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) { + perror("Error reading variable information"); + return; + } +#endif /* USE_BSD_FBDEV */ + + LV_LOG_INFO("%dx%d, %dbpp", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel); + + // Figure out the size of the screen in bytes + screensize = finfo.smem_len; //finfo.line_length * vinfo.yres; + + // Map the device to memory + fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0); + if((intptr_t)fbp == -1) { + perror("Error: failed to map framebuffer device to memory"); + return; + } + + // Don't initialise the memory to retain what's currently displayed / avoid clearing the screen. + // This is important for applications that only draw to a subsection of the full framebuffer. + + LV_LOG_INFO("The framebuffer device was mapped to memory successfully"); + +} + +void fbdev_exit(void) +{ + close(fbfd); +} + +/** + * Flush a buffer to the marked area + * @param drv pointer to driver where this function belongs + * @param area an area where to copy `color_p` + * @param color_p an array of pixels to copy to the `area` part of the screen + */ +void fbdev_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p) +{ + if(fbp == NULL || + area->x2 < 0 || + area->y2 < 0 || + area->x1 > (int32_t)vinfo.xres - 1 || + area->y1 > (int32_t)vinfo.yres - 1) { + lv_disp_flush_ready(drv); + return; + } + + /*Truncate the area to the screen*/ + int32_t act_x1 = area->x1 < 0 ? 0 : area->x1; + int32_t act_y1 = area->y1 < 0 ? 0 : area->y1; + int32_t act_x2 = area->x2 > (int32_t)vinfo.xres - 1 ? (int32_t)vinfo.xres - 1 : area->x2; + int32_t act_y2 = area->y2 > (int32_t)vinfo.yres - 1 ? (int32_t)vinfo.yres - 1 : area->y2; + + + lv_coord_t w = (act_x2 - act_x1 + 1); + long int location = 0; + long int byte_location = 0; + unsigned char bit_location = 0; + + /*32 or 24 bit per pixel*/ + if(vinfo.bits_per_pixel == 32 || vinfo.bits_per_pixel == 24) { + uint32_t * fbp32 = (uint32_t *)fbp; + int32_t y; + for(y = act_y1; y <= act_y2; y++) { + location = (act_x1 + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length / 4; + memcpy(&fbp32[location], (uint32_t *)color_p, (act_x2 - act_x1 + 1) * 4); + color_p += w; + } + } + /*16 bit per pixel*/ + else if(vinfo.bits_per_pixel == 16) { + uint16_t * fbp16 = (uint16_t *)fbp; + int32_t y; + for(y = act_y1; y <= act_y2; y++) { + location = (act_x1 + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length / 2; + memcpy(&fbp16[location], (uint32_t *)color_p, (act_x2 - act_x1 + 1) * 2); + color_p += w; + } + } + /*8 bit per pixel*/ + else if(vinfo.bits_per_pixel == 8) { + uint8_t * fbp8 = (uint8_t *)fbp; + int32_t y; + for(y = act_y1; y <= act_y2; y++) { + location = (act_x1 + vinfo.xoffset) + (y + vinfo.yoffset) * finfo.line_length; + memcpy(&fbp8[location], (uint32_t *)color_p, (act_x2 - act_x1 + 1)); + color_p += w; + } + } + /*1 bit per pixel*/ + else if(vinfo.bits_per_pixel == 1) { + uint8_t * fbp8 = (uint8_t *)fbp; + int32_t x; + int32_t y; + for(y = act_y1; y <= act_y2; y++) { + for(x = act_x1; x <= act_x2; x++) { + location = (x + vinfo.xoffset) + (y + vinfo.yoffset) * vinfo.xres; + byte_location = location / 8; /* find the byte we need to change */ + bit_location = location % 8; /* inside the byte found, find the bit we need to change */ + fbp8[byte_location] &= ~(((uint8_t)(1)) << bit_location); + fbp8[byte_location] |= ((uint8_t)(color_p->full)) << bit_location; + color_p++; + } + + color_p += area->x2 - act_x2; + } + } else { + /*Not supported bit per pixel*/ + } + + //May be some direct update command is required + //ret = ioctl(state->fd, FBIO_UPDATE, (unsigned long)((uintptr_t)rect)); + + lv_disp_flush_ready(drv); +} + +void fbdev_get_sizes(uint32_t *width, uint32_t *height, uint32_t *dpi) { + if (width) + *width = vinfo.xres; + + if (height) + *height = vinfo.yres; + + if (dpi && vinfo.height) + *dpi = DIV_ROUND_UP(vinfo.xres * 254, vinfo.width * 10); +} + +void fbdev_set_offset(uint32_t xoffset, uint32_t yoffset) { + vinfo.xoffset = xoffset; + vinfo.yoffset = yoffset; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +#endif diff --git a/libs/lv_drivers/display/fbdev.h b/libs/lv_drivers/display/fbdev.h new file mode 100644 index 00000000..b7f2c81c --- /dev/null +++ b/libs/lv_drivers/display/fbdev.h @@ -0,0 +1,65 @@ +/** + * @file fbdev.h + * + */ + +#ifndef FBDEV_H +#define FBDEV_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_FBDEV || USE_BSD_FBDEV + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void fbdev_init(void); +void fbdev_exit(void); +void fbdev_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p); +void fbdev_get_sizes(uint32_t *width, uint32_t *height, uint32_t *dpi); +/** + * Set the X and Y offset in the variable framebuffer info. + * @param xoffset horizontal offset + * @param yoffset vertical offset + */ +void fbdev_set_offset(uint32_t xoffset, uint32_t yoffset); + + +/********************** + * MACROS + **********************/ + +#endif /*USE_FBDEV*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*FBDEV_H*/ diff --git a/libs/lv_drivers/display/monitor.h b/libs/lv_drivers/display/monitor.h new file mode 100644 index 00000000..6ef4d6d3 --- /dev/null +++ b/libs/lv_drivers/display/monitor.h @@ -0,0 +1,57 @@ +/** + * @file monitor.h + * + */ + +#ifndef MONITOR_H +#define MONITOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_MONITOR + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void monitor_init(void); +void monitor_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); +void monitor_flush2(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); + +/********************** + * MACROS + **********************/ + +#endif /* USE_MONITOR */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* MONITOR_H */ diff --git a/libs/lv_drivers/docs/astyle_c b/libs/lv_drivers/docs/astyle_c new file mode 100644 index 00000000..9b9d7f3c --- /dev/null +++ b/libs/lv_drivers/docs/astyle_c @@ -0,0 +1 @@ +--style=kr --convert-tabs --indent=spaces=4 --indent-switches --pad-oper --unpad-paren --align-pointer=middle --suffix=.bak --lineend=linux --min-conditional-indent= diff --git a/libs/lv_drivers/docs/astyle_h b/libs/lv_drivers/docs/astyle_h new file mode 100644 index 00000000..d9c76337 --- /dev/null +++ b/libs/lv_drivers/docs/astyle_h @@ -0,0 +1 @@ +--convert-tabs --indent=spaces=4 diff --git a/libs/lv_drivers/gtkdrv/README.md b/libs/lv_drivers/gtkdrv/README.md new file mode 100644 index 00000000..b4146fc0 --- /dev/null +++ b/libs/lv_drivers/gtkdrv/README.md @@ -0,0 +1,97 @@ +# Add GTK under Linux in Eclipse + +## Install GDK + +``` +sudo apt-get install libgtk-3-dev +sudo apt-get install libglib2.0-dev +``` + +## Add GTK include paths and libraries + +In "Project properties > C/C++ Build > Settings" set the followings: + +- "Cross GCC Compiler > Command line pattern" + - Add ` ${gtk+-cflags}` to the end (add a space between the last command and this) + +- "Cross GCC Compiler > Includes" + - /usr/include/glib-2.0 + - /usr/include/gtk-3.0 + - /usr/include/pango-1.0 + - /usr/include/cairo + - /usr/include/gdk-pixbuf-2.0 + - /usr/include/atk-1.0 + +- "Cross GCC Linker > Command line pattern" + - Add ` ${gtk+-libs}` to the end (add a space between the last command and this) + + +- "Cross GCC Linker > Libraries" + - Add `pthread` + + +- In "C/C++ Build > Build variables" + - Configuration: [All Configuration] + + - Add + - Variable name: `gtk+-cflags` + - Type: `String` + - Value: `pkg-config --cflags gtk+-3.0` + - Variable name: `gtk+-libs` + - Type: `String` + - Value: `pkg-config --libs gtk+-3.0` + + +## Init GDK in LVGL + +1. In `main.c` `#include "lv_drivers/gtkdrv/gtkdrv.h"` +2. Enable the GTK driver in `lv_drv_conf.h` with `USE_GTK 1` +3. After `lv_init()` call `gdkdrv_init()`; +4. Add a display: +```c + static lv_disp_buf_t disp_buf1; + static lv_color_t buf1_1[LV_HOR_RES_MAX * LV_VER_RES_MAX]; + lv_disp_buf_init(&disp_buf1, buf1_1, NULL, LV_HOR_RES_MAX * LV_VER_RES_MAX); + + /*Create a display*/ + lv_disp_drv_t disp_drv; + lv_disp_drv_init(&disp_drv); + disp_drv.buffer = &disp_buf1; + disp_drv.flush_cb = gtkdrv_flush_cb; +``` +5. Add mouse: +```c + lv_indev_drv_t indev_drv_mouse; + lv_indev_drv_init(&indev_drv_mouse); + indev_drv_mouse.type = LV_INDEV_TYPE_POINTER; +``` +6. Add keyboard: +```c + lv_indev_drv_t indev_drv_kb; + lv_indev_drv_init(&indev_drv_kb); + indev_drv_kb.type = LV_INDEV_TYPE_KEYPAD; + indev_drv_kb.read_cb = lv_keyboard_read_cb; + lv_indev_drv_register(&indev_drv_kb); +``` +7. Configure tick in `lv_conf.h` +```c +#define LV_TICK_CUSTOM 1 +#if LV_TICK_CUSTOM == 1 +#define LV_TICK_CUSTOM_INCLUDE "lv_drivers/gtkdrv/gtkdrv.h" /*Header for the sys time function*/ +#define LV_TICK_CUSTOM_SYS_TIME_EXPR (gtkdrv_tick_get()) /*Expression evaluating to current systime in ms*/ +#endif /*LV_TICK_CUSTOM*/ +``` +8. Be sure `LV_COLOR_DEPTH` is `32` in `lv_conf.h` + +## Run in a window +Build and Run to "normally" run the UI in a window + +## Run in browser +With the help of `Broadway` the UI can be easily shown via a browser. + +1. Open Terminal and start *Broadway* with `broadwayd :5`. Leave the terminal running. +2. Navigate to where eclipse created the binary executable (my_project/Debug) and open a terminal in that folder. +In this terminal run `GDK_BACKEND=broadway BROADWAY_DISPLAY=:5 ./my_executable` (replace *my_executable* wih name of your executable) +3. Open a web browser and go to `http://localhost:8085/` + +![LVGL with GTK/GDK Broadway backend](https://github.com/lvgl/lv_drivers/blob/master/gtkdrv/broadway.png?raw=true) diff --git a/libs/lv_drivers/gtkdrv/broadway.png b/libs/lv_drivers/gtkdrv/broadway.png new file mode 100644 index 0000000000000000000000000000000000000000..e0448ff187b6bfdbdbe4f236b4b760412e08e1f8 GIT binary patch literal 28408 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 +#include +#include +#include +#include +#include +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void gtkdrv_handler(void * p); +static gboolean mouse_pressed(GtkWidget *widget, GdkEventButton *event, + gpointer user_data); +static gboolean mouse_released(GtkWidget *widget, GdkEventButton *event, + gpointer user_data); +static gboolean mouse_motion(GtkWidget *widget, GdkEventMotion *event, + gpointer user_data); +static gboolean keyboard_press(GtkWidget *widget, GdkEventKey *event, + gpointer user_data); +static gboolean keyboard_release(GtkWidget *widget, GdkEventKey *event, + gpointer user_data); + +static void quit_handler(void); + +/********************** + * STATIC VARIABLES + **********************/ +static GtkWidget *window; +static GtkWidget *event_box; + +static GtkWidget *output_image; +static GdkPixbuf *pixbuf; + +static unsigned char run_gtk; + +static lv_coord_t mouse_x; +static lv_coord_t mouse_y; +static lv_indev_state_t mouse_btn = LV_INDEV_STATE_REL; +static lv_key_t last_key; +static lv_indev_state_t last_key_state; + +static uint8_t fb[LV_HOR_RES_MAX * LV_VER_RES_MAX * 3]; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void gtkdrv_init(void) +{ + // Init GTK + gtk_init(NULL, NULL); + + /* Or just set up the widgets in code */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size(GTK_WINDOW(window), LV_HOR_RES_MAX, LV_VER_RES_MAX); + gtk_window_set_resizable (GTK_WINDOW(window), FALSE); + output_image = gtk_image_new(); + event_box = gtk_event_box_new (); // Use event_box around image, otherwise mouse position output in broadway is offset + gtk_container_add(GTK_CONTAINER (event_box), output_image); + gtk_container_add(GTK_CONTAINER (window), event_box); + + gtk_widget_add_events(event_box, GDK_BUTTON_PRESS_MASK); + gtk_widget_add_events(event_box, GDK_SCROLL_MASK); + gtk_widget_add_events(event_box, GDK_POINTER_MOTION_MASK); + gtk_widget_add_events(window, GDK_KEY_PRESS_MASK); + + g_signal_connect(window, "destroy", G_CALLBACK(quit_handler), NULL); + g_signal_connect(event_box, "button-press-event", G_CALLBACK(mouse_pressed), NULL); + g_signal_connect(event_box, "button-release-event", G_CALLBACK(mouse_released), NULL); + g_signal_connect(event_box, "motion-notify-event", G_CALLBACK(mouse_motion), NULL); + g_signal_connect(window, "key_press_event", G_CALLBACK(keyboard_press), NULL); + g_signal_connect(window, "key_release_event", G_CALLBACK(keyboard_release), NULL); + + + gtk_widget_show_all(window); + + pixbuf = gdk_pixbuf_new_from_data((guchar*)fb, GDK_COLORSPACE_RGB, false, 8, LV_HOR_RES_MAX, LV_VER_RES_MAX, LV_HOR_RES_MAX * 3, NULL, NULL); + if (pixbuf == NULL) + { + fprintf(stderr, "Creating pixbuf failed\n"); + return; + } + + pthread_t thread; + pthread_create(&thread, NULL, gtkdrv_handler, NULL); +} + + +/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/ +uint32_t gtkdrv_tick_get(void) +{ + static uint64_t start_ms = 0; + if(start_ms == 0) { + struct timeval tv_start; + gettimeofday(&tv_start, NULL); + start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000; + } + + struct timeval tv_now; + gettimeofday(&tv_now, NULL); + uint64_t now_ms; + now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000; + + uint32_t time_ms = now_ms - start_ms; + + return time_ms; +} + + +/** + * Flush a buffer to the marked area + * @param disp_drv pointer to driver where this function belongs + * @param area an area where to copy `color_p` + * @param color_p an array of pixels to copy to the `area` part of the screen + */ +void gtkdrv_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) +{ + lv_coord_t hres = disp_drv->rotated == 0 ? disp_drv->hor_res : disp_drv->ver_res; + lv_coord_t vres = disp_drv->rotated == 0 ? disp_drv->ver_res : disp_drv->hor_res; + + + /*Return if the area is out the screen*/ + if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) { + + lv_disp_flush_ready(disp_drv); + return; + } + + int32_t y; + int32_t x; + int32_t p; + for(y = area->y1; y <= area->y2 && y < disp_drv->ver_res; y++) { + p = (y * disp_drv->hor_res + area->x1) * 3; + for(x = area->x1; x <= area->x2 && x < disp_drv->hor_res; x++) { + fb[p] = color_p->ch.red; + fb[p + 1] = color_p->ch.green; + fb[p + 2] = color_p->ch.blue; + + p += 3; + color_p ++; + } + } + + /*IMPORTANT! It must be called to tell the system the flush is ready*/ + lv_disp_flush_ready(disp_drv); +} + + +void gtkdrv_mouse_read_cb(lv_indev_drv_t * drv, lv_indev_data_t * data) +{ + data->point.x = mouse_x; + data->point.y = mouse_y; + data->state = mouse_btn; +} + + +void gtkdrv_keyboard_read_cb(lv_indev_drv_t * drv, lv_indev_data_t * data) +{ + data->key = last_key; + data->state = last_key_state; +} + + + + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void gtkdrv_handler(void * p) +{ + while(1) { + gtk_image_set_from_pixbuf(GTK_IMAGE(output_image), pixbuf); // Test code + + /* Real code should: call gdk_pixbuf_new_from_data () with pointer to frame buffer + generated by LVGL. See + https://developer.gnome.org/gdk-pixbuf/2.36/gdk-pixbuf-Image-Data-in-Memory.html + */ + + gtk_main_iteration_do(FALSE); + /* Explicitly calling each iteration of the GTK main loop allows LVGL to sync frame + buffer updates with GTK. It is perhaps also possible to just call gtk_main(), but not + sure how sync will work then + */ + usleep(1*1000); + } +} + +static gboolean mouse_pressed(GtkWidget *widget, GdkEventButton *event, + gpointer user_data) +{ + mouse_btn = LV_INDEV_STATE_PR; + // Important, if this function returns TRUE the window cannot be moved around inside the browser + // when using broadway + return FALSE; +} + + +static gboolean mouse_released(GtkWidget *widget, GdkEventButton *event, + gpointer user_data) +{ + mouse_btn = LV_INDEV_STATE_REL; + // Important, if this function returns TRUE the window cannot be moved around inside the browser + // when using broadway + return FALSE; +} + +/*****************************************************************************/ + +static gboolean mouse_motion(GtkWidget *widget, GdkEventMotion *event, + gpointer user_data) +{ + mouse_x = event->x; + mouse_y = event->y; + // Important, if this function returns TRUE the window cannot be moved around inside the browser + // when using broadway + return FALSE; +} + + +static gboolean keyboard_press(GtkWidget *widget, GdkEventKey *event, + gpointer user_data) +{ + + uint32_t ascii_key = event->keyval; + /*Remap some key to LV_KEY_... to manage groups*/ + switch(event->keyval) { + case GDK_KEY_rightarrow: + case GDK_KEY_Right: + ascii_key = LV_KEY_RIGHT; + break; + + case GDK_KEY_leftarrow: + case GDK_KEY_Left: + ascii_key = LV_KEY_LEFT; + break; + + case GDK_KEY_uparrow: + case GDK_KEY_Up: + ascii_key = LV_KEY_UP; + break; + + case GDK_KEY_downarrow: + case GDK_KEY_Down: + ascii_key = LV_KEY_DOWN; + break; + + case GDK_KEY_Escape: + ascii_key = LV_KEY_ESC; + break; + + case GDK_KEY_BackSpace: + ascii_key = LV_KEY_BACKSPACE; + break; + + case GDK_KEY_Delete: + ascii_key = LV_KEY_DEL; + break; + + case GDK_KEY_Tab: + ascii_key = LV_KEY_NEXT; + break; + + case GDK_KEY_KP_Enter: + case GDK_KEY_Return: + case '\r': + ascii_key = LV_KEY_ENTER; + break; + + default: + break; + + } + + last_key = ascii_key; + last_key_state = LV_INDEV_STATE_PR; + // For other codes refer to https://developer.gnome.org/gdk3/stable/gdk3-Event-Structures.html#GdkEventKey + + return TRUE; +} + +static gboolean keyboard_release(GtkWidget *widget, GdkEventKey *event, + gpointer user_data) +{ + last_key = 0; + last_key_state = LV_INDEV_STATE_REL; + // For other codes refer to https://developer.gnome.org/gdk3/stable/gdk3-Event-Structures.html#GdkEventKey + + return TRUE; +} + +static void quit_handler(void) +{ + exit(0); + run_gtk = FALSE; +} +#endif /*USE_GTK*/ + diff --git a/libs/lv_drivers/gtkdrv/gtkdrv.h b/libs/lv_drivers/gtkdrv/gtkdrv.h new file mode 100644 index 00000000..42f4609b --- /dev/null +++ b/libs/lv_drivers/gtkdrv/gtkdrv.h @@ -0,0 +1,59 @@ +/** + * @file gtkdrv + * + */ + +#ifndef GTKDRV_H +#define GTKDRV_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_GTK + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void gtkdrv_init(void); +uint32_t gtkdrv_tick_get(void); +void gtkdrv_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); +void gtkdrv_mouse_read_cb(lv_indev_drv_t * drv, lv_indev_data_t * data); +void gtkdrv_keyboard_read_cb(lv_indev_drv_t * drv, lv_indev_data_t * data); +/********************** + * MACROS + **********************/ + +#endif /*USE_GTK*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* GTKDRV_H */ diff --git a/libs/lv_drivers/indev/AD_touch.c b/libs/lv_drivers/indev/AD_touch.c new file mode 100644 index 00000000..c09c3593 --- /dev/null +++ b/libs/lv_drivers/indev/AD_touch.c @@ -0,0 +1,383 @@ +/** + * @file AD_touch.c + * + */ + +#include "AD_touch.h" + +#if USE_AD_TOUCH + +#include LV_DRV_INDEV_INCLUDE +#include LV_DRV_DELAY_INCLUDE + +#define SAMPLE_POINTS 4 + +#define CALIBRATIONINSET 20 // range 0 <= CALIBRATIONINSET <= 40 + +#define RESISTIVETOUCH_AUTO_SAMPLE_MODE +#define TOUCHSCREEN_RESISTIVE_PRESS_THRESHOLD 350 // between 0-0x03ff the lesser this value + + +// Current ADC values for X and Y channels +int16_t adcX = 0; +int16_t adcY = 0; +volatile unsigned int adcTC = 0; + +// coefficient values +volatile long _trA; +volatile long _trB; +volatile long _trC; +volatile long _trD; + +volatile int16_t xRawTouch[SAMPLE_POINTS] = {TOUCHCAL_ULX, TOUCHCAL_URX, TOUCHCAL_LRX, TOUCHCAL_LLX}; +volatile int16_t yRawTouch[SAMPLE_POINTS] = {TOUCHCAL_ULY, TOUCHCAL_URY, TOUCHCAL_LRY, TOUCHCAL_LLY}; + +#define TOUCHSCREEN_RESISTIVE_CALIBRATION_SCALE_FACTOR 8 + +// use this scale factor to avoid working in floating point numbers +#define SCALE_FACTOR (1 << TOUCHSCREEN_RESISTIVE_CALIBRATION_SCALE_FACTOR) + +typedef enum { + IDLE, //0 + SET_X, //1 + RUN_X, //2 + GET_X, //3 + RUN_CHECK_X, //4 + CHECK_X, //5 + SET_Y, //6 + RUN_Y, //7 + GET_Y, //8 + CHECK_Y, //9 + SET_VALUES, //10 + GET_POT, //11 + RUN_POT //12 +} TOUCH_STATES; + +volatile TOUCH_STATES state = IDLE; + +#define CAL_X_INSET (((GetMaxX() + 1) * (CALIBRATIONINSET >> 1)) / 100) +#define CAL_Y_INSET (((GetMaxY() + 1) * (CALIBRATIONINSET >> 1)) / 100) + +int stat; +int16_t temp_x, temp_y; + + +static int16_t TouchGetX(void); +static int16_t TouchGetRawX(void); +static int16_t TouchGetY(void); +static int16_t TouchGetRawY(void); +static int16_t TouchDetectPosition(void); +static void TouchCalculateCalPoints(void); + + +/********************************************************************/ +void ad_touch_init(void) +{ + // Initialize ADC for auto sampling mode + AD1CON1 = 0; // reset + AD1CON2 = 0; // AVdd, AVss, int every conversion, MUXA only + AD1CON3 = 0x1FFF; // 31 Tad auto-sample, Tad = 256*Tcy + AD1CON1 = 0x80E0; // Turn on A/D module, use auto-convert + + + ADPCFG_XPOS = RESISTIVETOUCH_ANALOG; + ADPCFG_YPOS = RESISTIVETOUCH_ANALOG; + + AD1CSSL = 0; // No scanned inputs + + state = SET_X; // set the state of the state machine to start the sampling + + /*Load calibration data*/ + xRawTouch[0] = TOUCHCAL_ULX; + yRawTouch[0] = TOUCHCAL_ULY; + xRawTouch[1] = TOUCHCAL_URX; + yRawTouch[1] = TOUCHCAL_URY; + xRawTouch[3] = TOUCHCAL_LLX; + yRawTouch[3] = TOUCHCAL_LLY; + xRawTouch[2] = TOUCHCAL_LRX; + yRawTouch[2] = TOUCHCAL_LRY; + + TouchCalculateCalPoints(); +} + +/*Use this in lv_indev_drv*/ +bool ad_touch_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + static int16_t last_x = 0; + static int16_t last_y = 0; + + int16_t x, y; + + x = TouchGetX(); + y = TouchGetY(); + + if((x > 0) && (y > 0)) { + data->point.x = x; + data->point.y = y; + last_x = data->point.x; + last_y = data->point.y; + data->state = LV_INDEV_STATE_PR; + } else { + data->point.x = last_x; + data->point.y = last_y; + data->state = LV_INDEV_STATE_REL; + } + + return false; +} + +/* Call periodically (e.g. in every 1 ms) to handle reading with ADC*/ +int16_t ad_touch_handler(void) +{ + static int16_t tempX, tempY; + int16_t temp; + + switch(state) { + case IDLE: + adcX = 0; + adcY = 0; + break; + + case SET_VALUES: + if(!TOUCH_ADC_DONE) + break; + if((WORD)TOUCHSCREEN_RESISTIVE_PRESS_THRESHOLD < (WORD)ADC1BUF0) { + adcX = 0; + adcY = 0; + } else { + adcX = tempX; + adcY = tempY; + } + state = SET_X; + return 1; // touch screen acquisition is done + + case SET_X: + TOUCH_ADC_INPUT_SEL = ADC_XPOS; + + ResistiveTouchScreen_XPlus_Config_As_Input(); + ResistiveTouchScreen_YPlus_Config_As_Input(); + ResistiveTouchScreen_XMinus_Config_As_Input(); + ResistiveTouchScreen_YMinus_Drive_Low(); + ResistiveTouchScreen_YMinus_Config_As_Output(); + + ADPCFG_YPOS = RESISTIVETOUCH_DIGITAL; // set to digital pin + ADPCFG_XPOS = RESISTIVETOUCH_ANALOG; // set to analog pin + + TOUCH_ADC_START = 1; // run conversion + state = CHECK_X; + break; + + case CHECK_X: + case CHECK_Y: + + if(TOUCH_ADC_DONE == 0) { + break; + } + + if((WORD)TOUCHSCREEN_RESISTIVE_PRESS_THRESHOLD > (WORD)ADC1BUF0) { + if(state == CHECK_X) { + ResistiveTouchScreen_YPlus_Drive_High(); + ResistiveTouchScreen_YPlus_Config_As_Output(); + tempX = 0; + state = RUN_X; + } else { + ResistiveTouchScreen_XPlus_Drive_High(); + ResistiveTouchScreen_XPlus_Config_As_Output(); + tempY = 0; + state = RUN_Y; + } + } else { + adcX = 0; + adcY = 0; + state = SET_X; + return 1; // touch screen acquisition is done + break; + } + + case RUN_X: + case RUN_Y: + TOUCH_ADC_START = 1; + state = (state == RUN_X) ? GET_X : GET_Y; + // no break needed here since the next state is either GET_X or GET_Y + break; + + case GET_X: + case GET_Y: + if(!TOUCH_ADC_DONE) + break; + + temp = ADC1BUF0; + if(state == GET_X) { + if(temp != tempX) { + tempX = temp; + state = RUN_X; + break; + } + } else { + if(temp != tempY) { + tempY = temp; + state = RUN_Y; + break; + } + } + + if(state == GET_X) + ResistiveTouchScreen_YPlus_Config_As_Input(); + else + ResistiveTouchScreen_XPlus_Config_As_Input(); + TOUCH_ADC_START = 1; + state = (state == GET_X) ? SET_Y : SET_VALUES; + break; + + case SET_Y: + if(!TOUCH_ADC_DONE) + break; + + if((WORD)TOUCHSCREEN_RESISTIVE_PRESS_THRESHOLD < (WORD)ADC1BUF0) { + adcX = 0; + adcY = 0; + state = SET_X; + return 1; // touch screen acquisition is done + break; + } + + TOUCH_ADC_INPUT_SEL = ADC_YPOS; + + ResistiveTouchScreen_XPlus_Config_As_Input(); + ResistiveTouchScreen_YPlus_Config_As_Input(); + ResistiveTouchScreen_XMinus_Drive_Low(); + ResistiveTouchScreen_XMinus_Config_As_Output(); + ResistiveTouchScreen_YMinus_Config_As_Input(); + + ADPCFG_YPOS = RESISTIVETOUCH_ANALOG; // set to analog pin + ADPCFG_XPOS = RESISTIVETOUCH_DIGITAL; // set to digital pin + TOUCH_ADC_START = 1; // run conversion + state = CHECK_Y; + break; + + default: + state = SET_X; + return 1; // touch screen acquisition is done + } + stat = state; + temp_x = adcX; + temp_y = adcY; + + return 0; // touch screen acquisition is not done +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/********************************************************************/ +static int16_t TouchGetX(void) +{ + long result; + + result = TouchGetRawX(); + + if(result > 0) { + result = (long)((((long)_trC * result) + _trD) >> TOUCHSCREEN_RESISTIVE_CALIBRATION_SCALE_FACTOR); + + } + return ((int16_t)result); +} +/********************************************************************/ +static int16_t TouchGetRawX(void) +{ +#ifdef TOUCHSCREEN_RESISTIVE_SWAP_XY + return adcY; +#else + return adcX; +#endif +} + +/********************************************************************/ +static int16_t TouchGetY(void) +{ + + long result; + + result = TouchGetRawY(); + + if(result > 0) { + result = (long)((((long)_trA * result) + (long)_trB) >> TOUCHSCREEN_RESISTIVE_CALIBRATION_SCALE_FACTOR); + + } + return ((int16_t)result); +} + +/********************************************************************/ +static int16_t TouchGetRawY(void) +{ +#ifdef TOUCHSCREEN_RESISTIVE_SWAP_XY + return adcX; +#else + return adcY; +#endif +} + + +static void TouchCalculateCalPoints(void) +{ + long trA, trB, trC, trD; // variables for the coefficients + long trAhold, trBhold, trChold, trDhold; + long test1, test2; // temp variables (must be signed type) + + int16_t xPoint[SAMPLE_POINTS], yPoint[SAMPLE_POINTS]; + + yPoint[0] = yPoint[1] = CAL_Y_INSET; + yPoint[2] = yPoint[3] = (GetMaxY() - CAL_Y_INSET); + xPoint[0] = xPoint[3] = CAL_X_INSET; + xPoint[1] = xPoint[2] = (GetMaxX() - CAL_X_INSET); + + // calculate points transfer functiona + // based on two simultaneous equations solve for the + // constants + + // use sample points 1 and 4 + // Dy1 = aTy1 + b; Dy4 = aTy4 + b + // Dx1 = cTx1 + d; Dy4 = aTy4 + b + + test1 = (long)yPoint[0] - (long)yPoint[3]; + test2 = (long)yRawTouch[0] - (long)yRawTouch[3]; + + trA = ((long)((long)test1 * SCALE_FACTOR) / test2); + trB = ((long)((long)yPoint[0] * SCALE_FACTOR) - (trA * (long)yRawTouch[0])); + + test1 = (long)xPoint[0] - (long)xPoint[2]; + test2 = (long)xRawTouch[0] - (long)xRawTouch[2]; + + trC = ((long)((long)test1 * SCALE_FACTOR) / test2); + trD = ((long)((long)xPoint[0] * SCALE_FACTOR) - (trC * (long)xRawTouch[0])); + + trAhold = trA; + trBhold = trB; + trChold = trC; + trDhold = trD; + + // use sample points 2 and 3 + // Dy2 = aTy2 + b; Dy3 = aTy3 + b + // Dx2 = cTx2 + d; Dy3 = aTy3 + b + + test1 = (long)yPoint[1] - (long)yPoint[2]; + test2 = (long)yRawTouch[1] - (long)yRawTouch[2]; + + trA = ((long)(test1 * SCALE_FACTOR) / test2); + trB = ((long)((long)yPoint[1] * SCALE_FACTOR) - (trA * (long)yRawTouch[1])); + + test1 = (long)xPoint[1] - (long)xPoint[3]; + test2 = (long)xRawTouch[1] - (long)xRawTouch[3]; + + trC = ((long)((long)test1 * SCALE_FACTOR) / test2); + trD = ((long)((long)xPoint[1] * SCALE_FACTOR) - (trC * (long)xRawTouch[1])); + + // get the average and use the average + _trA = (trA + trAhold) >> 1; + _trB = (trB + trBhold) >> 1; + _trC = (trC + trChold) >> 1; + _trD = (trD + trDhold) >> 1; +} + +#endif diff --git a/libs/lv_drivers/indev/AD_touch.h b/libs/lv_drivers/indev/AD_touch.h new file mode 100644 index 00000000..7c07ab37 --- /dev/null +++ b/libs/lv_drivers/indev/AD_touch.h @@ -0,0 +1,120 @@ +/** + * @file AD_touch.h + * + */ + +#ifndef AD_TOUCH_H +#define AD_TOUCH_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_AD_TOUCH + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#define _SUPPRESS_PLIB_WARNING +#include + +#include "GenericTypeDefs.h" + +#define DISP_ORIENTATION 0 +#define DISP_HOR_RESOLUTION 320 +#define DISP_VER_RESOLUTION 240 + +/*GetMaxX Macro*/ +#if (DISP_ORIENTATION == 90) || (DISP_ORIENTATION == 270) +#define GetMaxX() (DISP_VER_RESOLUTION - 1) +#elif (DISP_ORIENTATION == 0) || (DISP_ORIENTATION == 180) +#define GetMaxX() (DISP_HOR_RESOLUTION - 1) +#endif + +/*GetMaxY Macro*/ +#if (DISP_ORIENTATION == 90) || (DISP_ORIENTATION == 270) +#define GetMaxY() (DISP_HOR_RESOLUTION - 1) +#elif (DISP_ORIENTATION == 0) || (DISP_ORIENTATION == 180) +#define GetMaxY() (DISP_VER_RESOLUTION - 1) +#endif + +/********************************************************************* + * HARDWARE PROFILE FOR THE RESISTIVE TOUCHSCREEN + *********************************************************************/ + +#define TOUCH_ADC_INPUT_SEL AD1CHS + +// ADC Sample Start +#define TOUCH_ADC_START AD1CON1bits.SAMP + +// ADC Status +#define TOUCH_ADC_DONE AD1CON1bits.DONE + +#define RESISTIVETOUCH_ANALOG 1 +#define RESISTIVETOUCH_DIGITAL 0 + +// ADC channel constants +#define ADC_XPOS ADC_CH0_POS_SAMPLEA_AN12 +#define ADC_YPOS ADC_CH0_POS_SAMPLEA_AN13 + +// ADC Port Control Bits +#define ADPCFG_XPOS AD1PCFGbits.PCFG12 //XR +#define ADPCFG_YPOS AD1PCFGbits.PCFG13 //YD + +// X port definitions +#define ResistiveTouchScreen_XPlus_Drive_High() LATBbits.LATB12 = 1 +#define ResistiveTouchScreen_XPlus_Drive_Low() LATBbits.LATB12 = 0 //LAT_XPOS +#define ResistiveTouchScreen_XPlus_Config_As_Input() TRISBbits.TRISB12 = 1 //TRIS_XPOS +#define ResistiveTouchScreen_XPlus_Config_As_Output() TRISBbits.TRISB12 = 0 + +#define ResistiveTouchScreen_XMinus_Drive_High() LATFbits.LATF0 = 1 +#define ResistiveTouchScreen_XMinus_Drive_Low() LATFbits.LATF0 = 0 //LAT_XNEG +#define ResistiveTouchScreen_XMinus_Config_As_Input() TRISFbits.TRISF0 = 1 //TRIS_XNEG +#define ResistiveTouchScreen_XMinus_Config_As_Output() TRISFbits.TRISF0 = 0 + +// Y port definitions +#define ResistiveTouchScreen_YPlus_Drive_High() LATBbits.LATB13 = 1 +#define ResistiveTouchScreen_YPlus_Drive_Low() LATBbits.LATB13 = 0 //LAT_YPOS +#define ResistiveTouchScreen_YPlus_Config_As_Input() TRISBbits.TRISB13 = 1 //TRIS_YPOS +#define ResistiveTouchScreen_YPlus_Config_As_Output() TRISBbits.TRISB13 = 0 + +#define ResistiveTouchScreen_YMinus_Drive_High() LATFbits.LATF1 = 1 +#define ResistiveTouchScreen_YMinus_Drive_Low() LATFbits.LATF1 = 0 //LAT_YNEG +#define ResistiveTouchScreen_YMinus_Config_As_Input() TRISFbits.TRISF1 = 1 //TRIS_YNEG +#define ResistiveTouchScreen_YMinus_Config_As_Output() TRISFbits.TRISF1 = 0 + +// Default calibration points +#define TOUCHCAL_ULX 0x0348 +#define TOUCHCAL_ULY 0x00CC +#define TOUCHCAL_URX 0x00D2 +#define TOUCHCAL_URY 0x00CE +#define TOUCHCAL_LLX 0x034D +#define TOUCHCAL_LLY 0x0335 +#define TOUCHCAL_LRX 0x00D6 +#define TOUCHCAL_LRY 0x032D + +void ad_touch_init(void); +bool ad_touch_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); +int16_t ad_touch_handler(void); + +#endif /* USE_AD_TOUCH */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AD_TOUCH_H */ diff --git a/libs/lv_drivers/indev/FT5406EE8.c b/libs/lv_drivers/indev/FT5406EE8.c new file mode 100644 index 00000000..9293a54e --- /dev/null +++ b/libs/lv_drivers/indev/FT5406EE8.c @@ -0,0 +1,179 @@ +/** + * @file FT5406EE8.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "FT5406EE8.h" +#if USE_FT5406EE8 + +#include +#include +#include LV_DRV_INDEV_INCLUDE +#include LV_DRV_DELAY_INCLUDE + +/********************* + * DEFINES + *********************/ + +#define I2C_WR_BIT 0x00 +#define I2C_RD_BIT 0x01 + +/*DEVICE MODES*/ +#define OPERAT_MD 0x00 +#define TEST_MD 0x04 +#define SYS_INF_MD 0x01 + +/*OPERATING MODE*/ +#define DEVICE_MODE 0x00 +#define GEST_ID 0x01 +#define TD_STATUS 0x02 + +#define FT5406EE8_FINGER_MAX 10 + +/*Register addresses*/ +#define FT5406EE8_REG_DEVICE_MODE 0x00 +#define FT5406EE8_REG_GEST_ID 0x01 +#define FT5406EE8_REG_TD_STATUS 0x02 +#define FT5406EE8_REG_YH 0x03 +#define FT5406EE8_REG_YL 0x04 +#define FT5406EE8_REG_XH 0x05 +#define FT5406EE8_REG_XL 0x06 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static bool ft5406ee8_get_touch_num(void); +static bool ft5406ee8_read_finger1(int16_t * x, int16_t * y); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * + */ +void ft5406ee8_init(void) +{ + +} + +/** + * Get the current position and state of the touchpad + * @param data store the read data here + * @return false: because no ore data to be read + */ +bool ft5406ee8_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + static int16_t x_last; + static int16_t y_last; + int16_t x; + int16_t y; + bool valid = true; + + valid = ft5406ee8_get_touch_num(); + if(valid == true) { + valid = ft5406ee8_read_finger1(&x, &y); + } + + if(valid == true) { + x = (uint32_t)((uint32_t)x * 320) / 2048; + y = (uint32_t)((uint32_t)y * 240) / 2048; + + + x_last = x; + y_last = y; + } else { + x = x_last; + y = y_last; + } + + data->point.x = x; + data->point.y = y; + data->state = valid == false ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR; + return false; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static bool ft5406ee8_get_touch_num(void) +{ + bool ok = true; + uint8_t t_num = 0; + + LV_DRV_INDEV_I2C_START; + LV_DRV_INDEV_I2C_WR((FT5406EE8_I2C_ADR << 1) | I2C_WR_BIT); + LV_DRV_INDEV_I2C_WR(FT5406EE8_REG_TD_STATUS) + LV_DRV_INDEV_I2C_RESTART; + LV_DRV_INDEV_I2C_WR((FT5406EE8_I2C_ADR << 1) | I2C_RD_BIT); + t_num = LV_DRV_INDEV_I2C_READ(0); + + /* Error if not touched or too much finger */ + if(t_num > FT5406EE8_FINGER_MAX || t_num == 0) { + ok = false; + } + + return ok; +} + +/** + * Read the x and y coordinated + * @param x store the x coordinate here + * @param y store the y coordinate here + * @return false: not valid point; true: valid point + */ +static bool ft5406ee8_read_finger1(int16_t * x, int16_t * y) +{ + uint8_t temp_xH = 0; + uint8_t temp_xL = 0; + uint8_t temp_yH = 0; + uint8_t temp_yL = 0; + + /*Read Y High and low byte*/ + LV_DRV_INDEV_I2C_START; + LV_DRV_INDEV_I2C_WR((FT5406EE8_I2C_ADR << 1) | I2C_WR_BIT); + LV_DRV_INDEV_I2C_WR(FT5406EE8_REG_YH) + LV_DRV_INDEV_I2C_RESTART; + LV_DRV_INDEV_I2C_WR((FT5406EE8_I2C_ADR << 1) | I2C_RD_BIT); + temp_yH = LV_DRV_INDEV_I2C_READ(1); + temp_yL = LV_DRV_INDEV_I2C_READ(1); + + /*The upper two bit must be 2 on valid press*/ + if(((temp_yH >> 6) & 0xFF) != 2) { + (void) LV_DRV_INDEV_I2C_READ(0); /*Dummy read to close read sequence*/ + *x = 0; + *y = 0; + return false; + } + + /*Read X High and low byte*/ + temp_xH = LV_DRV_INDEV_I2C_READ(1); + temp_xL = LV_DRV_INDEV_I2C_READ(0); + + /*Save the result*/ + *x = (temp_xH & 0x0F) << 8; + *x += temp_xL; + *y = (temp_yH & 0x0F) << 8; + *y += temp_yL; + + return true; +} + +#endif diff --git a/libs/lv_drivers/indev/FT5406EE8.h b/libs/lv_drivers/indev/FT5406EE8.h new file mode 100644 index 00000000..2d1eda7d --- /dev/null +++ b/libs/lv_drivers/indev/FT5406EE8.h @@ -0,0 +1,56 @@ +/** + * @file FT5406EE8.h + * + */ + +#ifndef FT5406EE8_H +#define FT5406EE8_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_FT5406EE8 + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void ft5406ee8_init(void); +bool ft5406ee8_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/********************** + * MACROS + **********************/ + +#endif /* USE_FT5406EE8 */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* FT5406EE8_H */ diff --git a/libs/lv_drivers/indev/XPT2046.c b/libs/lv_drivers/indev/XPT2046.c new file mode 100644 index 00000000..f27fa763 --- /dev/null +++ b/libs/lv_drivers/indev/XPT2046.c @@ -0,0 +1,174 @@ +/** + * @file XPT2046.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "XPT2046.h" +#if USE_XPT2046 + +#include +#include LV_DRV_INDEV_INCLUDE +#include LV_DRV_DELAY_INCLUDE + +/********************* + * DEFINES + *********************/ +#define CMD_X_READ 0b10010000 +#define CMD_Y_READ 0b11010000 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void xpt2046_corr(int16_t * x, int16_t * y); +static void xpt2046_avg(int16_t * x, int16_t * y); + +/********************** + * STATIC VARIABLES + **********************/ +int16_t avg_buf_x[XPT2046_AVG]; +int16_t avg_buf_y[XPT2046_AVG]; +uint8_t avg_last; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Initialize the XPT2046 + */ +void xpt2046_init(void) +{ + +} + +/** + * Get the current position and state of the touchpad + * @param data store the read data here + * @return false: because no ore data to be read + */ +bool xpt2046_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + static int16_t last_x = 0; + static int16_t last_y = 0; + uint8_t buf; + + int16_t x = 0; + int16_t y = 0; + + uint8_t irq = LV_DRV_INDEV_IRQ_READ; + + if(irq == 0) { + LV_DRV_INDEV_SPI_CS(0); + + LV_DRV_INDEV_SPI_XCHG_BYTE(CMD_X_READ); /*Start x read*/ + + buf = LV_DRV_INDEV_SPI_XCHG_BYTE(0); /*Read x MSB*/ + x = buf << 8; + buf = LV_DRV_INDEV_SPI_XCHG_BYTE(CMD_Y_READ); /*Until x LSB converted y command can be sent*/ + x += buf; + + buf = LV_DRV_INDEV_SPI_XCHG_BYTE(0); /*Read y MSB*/ + y = buf << 8; + + buf = LV_DRV_INDEV_SPI_XCHG_BYTE(0); /*Read y LSB*/ + y += buf; + + /*Normalize Data*/ + x = x >> 3; + y = y >> 3; + xpt2046_corr(&x, &y); + xpt2046_avg(&x, &y); + + last_x = x; + last_y = y; + data->state = LV_INDEV_STATE_PR; + + LV_DRV_INDEV_SPI_CS(1); + } else { + x = last_x; + y = last_y; + avg_last = 0; + data->state = LV_INDEV_STATE_REL; + } + + data->point.x = x; + data->point.y = y; + + return false; +} + +/********************** + * STATIC FUNCTIONS + **********************/ +static void xpt2046_corr(int16_t * x, int16_t * y) +{ +#if XPT2046_XY_SWAP != 0 + int16_t swap_tmp; + swap_tmp = *x; + *x = *y; + *y = swap_tmp; +#endif + + if((*x) > XPT2046_X_MIN)(*x) -= XPT2046_X_MIN; + else(*x) = 0; + + if((*y) > XPT2046_Y_MIN)(*y) -= XPT2046_Y_MIN; + else(*y) = 0; + + (*x) = (uint32_t)((uint32_t)(*x) * XPT2046_HOR_RES) / + (XPT2046_X_MAX - XPT2046_X_MIN); + + (*y) = (uint32_t)((uint32_t)(*y) * XPT2046_VER_RES) / + (XPT2046_Y_MAX - XPT2046_Y_MIN); + +#if XPT2046_X_INV != 0 + (*x) = XPT2046_HOR_RES - (*x); +#endif + +#if XPT2046_Y_INV != 0 + (*y) = XPT2046_VER_RES - (*y); +#endif + + +} + + +static void xpt2046_avg(int16_t * x, int16_t * y) +{ + /*Shift out the oldest data*/ + uint8_t i; + for(i = XPT2046_AVG - 1; i > 0 ; i--) { + avg_buf_x[i] = avg_buf_x[i - 1]; + avg_buf_y[i] = avg_buf_y[i - 1]; + } + + /*Insert the new point*/ + avg_buf_x[0] = *x; + avg_buf_y[0] = *y; + if(avg_last < XPT2046_AVG) avg_last++; + + /*Sum the x and y coordinates*/ + int32_t x_sum = 0; + int32_t y_sum = 0; + for(i = 0; i < avg_last ; i++) { + x_sum += avg_buf_x[i]; + y_sum += avg_buf_y[i]; + } + + /*Normalize the sums*/ + (*x) = (int32_t)x_sum / avg_last; + (*y) = (int32_t)y_sum / avg_last; +} + +#endif diff --git a/libs/lv_drivers/indev/XPT2046.h b/libs/lv_drivers/indev/XPT2046.h new file mode 100644 index 00000000..7eee8c00 --- /dev/null +++ b/libs/lv_drivers/indev/XPT2046.h @@ -0,0 +1,56 @@ +/** + * @file XPT2046.h + * + */ + +#ifndef XPT2046_H +#define XPT2046_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_XPT2046 + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void xpt2046_init(void); +bool xpt2046_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/********************** + * MACROS + **********************/ + +#endif /* USE_XPT2046 */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* XPT2046_H */ diff --git a/libs/lv_drivers/indev/evdev.c b/libs/lv_drivers/indev/evdev.c new file mode 100644 index 00000000..4d46b5b4 --- /dev/null +++ b/libs/lv_drivers/indev/evdev.c @@ -0,0 +1,251 @@ +/** + * @file evdev.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "evdev.h" +#if USE_EVDEV != 0 || USE_BSD_EVDEV + +#include +#include +#include +#if USE_BSD_EVDEV +#include +#else +#include +#endif + +#if USE_XKB +#include "xkb.h" +#endif /* USE_XKB */ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +int map(int x, int in_min, int in_max, int out_min, int out_max); + +/********************** + * STATIC VARIABLES + **********************/ +int evdev_fd = -1; +int evdev_root_x; +int evdev_root_y; +int evdev_button; + +int evdev_key_val; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Initialize the evdev interface + */ +void evdev_init(void) +{ + if (!evdev_set_file(EVDEV_NAME)) { + return; + } + +#if USE_XKB + xkb_init(); +#endif +} +/** + * reconfigure the device file for evdev + * @param dev_name set the evdev device filename + * @return true: the device file set complete + * false: the device file doesn't exist current system + */ +bool evdev_set_file(char* dev_name) +{ + if(evdev_fd != -1) { + close(evdev_fd); + } +#if USE_BSD_EVDEV + evdev_fd = open(dev_name, O_RDWR | O_NOCTTY); +#else + evdev_fd = open(dev_name, O_RDWR | O_NOCTTY | O_NDELAY); +#endif + + if(evdev_fd == -1) { + perror("unable to open evdev interface:"); + return false; + } + +#if USE_BSD_EVDEV + fcntl(evdev_fd, F_SETFL, O_NONBLOCK); +#else + fcntl(evdev_fd, F_SETFL, O_ASYNC | O_NONBLOCK); +#endif + + evdev_root_x = 0; + evdev_root_y = 0; + evdev_key_val = 0; + evdev_button = LV_INDEV_STATE_REL; + + return true; +} +/** + * Get the current position and state of the evdev + * @param data store the evdev data here + */ +void evdev_read(lv_indev_drv_t * drv, lv_indev_data_t * data) +{ + struct input_event in; + + while(read(evdev_fd, &in, sizeof(struct input_event)) > 0) { + if(in.type == EV_REL) { + if(in.code == REL_X) + #if EVDEV_SWAP_AXES + evdev_root_y += in.value; + #else + evdev_root_x += in.value; + #endif + else if(in.code == REL_Y) + #if EVDEV_SWAP_AXES + evdev_root_x += in.value; + #else + evdev_root_y += in.value; + #endif + } else if(in.type == EV_ABS) { + if(in.code == ABS_X) + #if EVDEV_SWAP_AXES + evdev_root_y = in.value; + #else + evdev_root_x = in.value; + #endif + else if(in.code == ABS_Y) + #if EVDEV_SWAP_AXES + evdev_root_x = in.value; + #else + evdev_root_y = in.value; + #endif + else if(in.code == ABS_MT_POSITION_X) + #if EVDEV_SWAP_AXES + evdev_root_y = in.value; + #else + evdev_root_x = in.value; + #endif + else if(in.code == ABS_MT_POSITION_Y) + #if EVDEV_SWAP_AXES + evdev_root_x = in.value; + #else + evdev_root_y = in.value; + #endif + else if(in.code == ABS_MT_TRACKING_ID) { + if(in.value == -1) + evdev_button = LV_INDEV_STATE_REL; + else if(in.value == 0) + evdev_button = LV_INDEV_STATE_PR; + } + } else if(in.type == EV_KEY) { + if(in.code == BTN_MOUSE || in.code == BTN_TOUCH) { + if(in.value == 0) + evdev_button = LV_INDEV_STATE_REL; + else if(in.value == 1) + evdev_button = LV_INDEV_STATE_PR; + } else if(drv->type == LV_INDEV_TYPE_KEYPAD) { +#if USE_XKB + data->key = xkb_process_key(in.code, in.value != 0); +#else + switch(in.code) { + case KEY_BACKSPACE: + data->key = LV_KEY_BACKSPACE; + break; + case KEY_ENTER: + data->key = LV_KEY_ENTER; + break; + case KEY_PREVIOUS: + data->key = LV_KEY_PREV; + break; + case KEY_NEXT: + data->key = LV_KEY_NEXT; + break; + case KEY_UP: + data->key = LV_KEY_UP; + break; + case KEY_LEFT: + data->key = LV_KEY_LEFT; + break; + case KEY_RIGHT: + data->key = LV_KEY_RIGHT; + break; + case KEY_DOWN: + data->key = LV_KEY_DOWN; + break; + case KEY_TAB: + data->key = LV_KEY_NEXT; + break; + default: + data->key = 0; + break; + } +#endif /* USE_XKB */ + if (data->key != 0) { + /* Only record button state when actual output is produced to prevent widgets from refreshing */ + data->state = (in.value) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL; + } + evdev_key_val = data->key; + evdev_button = data->state; + return; + } + } + } + + if(drv->type == LV_INDEV_TYPE_KEYPAD) { + /* No data retrieved */ + data->key = evdev_key_val; + data->state = evdev_button; + return; + } + if(drv->type != LV_INDEV_TYPE_POINTER) + return ; + /*Store the collected data*/ + +#if EVDEV_CALIBRATE + data->point.x = map(evdev_root_x, EVDEV_HOR_MIN, EVDEV_HOR_MAX, 0, drv->disp->driver->hor_res); + data->point.y = map(evdev_root_y, EVDEV_VER_MIN, EVDEV_VER_MAX, 0, drv->disp->driver->ver_res); +#else + data->point.x = evdev_root_x; + data->point.y = evdev_root_y; +#endif + + data->state = evdev_button; + + if(data->point.x < 0) + data->point.x = 0; + if(data->point.y < 0) + data->point.y = 0; + if(data->point.x >= drv->disp->driver->hor_res) + data->point.x = drv->disp->driver->hor_res - 1; + if(data->point.y >= drv->disp->driver->ver_res) + data->point.y = drv->disp->driver->ver_res - 1; + + return ; +} + +/********************** + * STATIC FUNCTIONS + **********************/ +int map(int x, int in_min, int in_max, int out_min, int out_max) +{ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +#endif diff --git a/libs/lv_drivers/indev/evdev.h b/libs/lv_drivers/indev/evdev.h new file mode 100644 index 00000000..c1b2280d --- /dev/null +++ b/libs/lv_drivers/indev/evdev.h @@ -0,0 +1,72 @@ +/** + * @file evdev.h + * + */ + +#ifndef EVDEV_H +#define EVDEV_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_EVDEV || USE_BSD_EVDEV + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize the evdev + */ +void evdev_init(void); +/** + * reconfigure the device file for evdev + * @param dev_name set the evdev device filename + * @return true: the device file set complete + * false: the device file doesn't exist current system + */ +bool evdev_set_file(char* dev_name); +/** + * Get the current position and state of the evdev + * @param data store the evdev data here + */ +void evdev_read(lv_indev_drv_t * drv, lv_indev_data_t * data); + + +/********************** + * MACROS + **********************/ + +#endif /* USE_EVDEV */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* EVDEV_H */ diff --git a/libs/lv_drivers/indev/keyboard.h b/libs/lv_drivers/indev/keyboard.h new file mode 100644 index 00000000..f0067c47 --- /dev/null +++ b/libs/lv_drivers/indev/keyboard.h @@ -0,0 +1,81 @@ +/** + * @file keyboard.h + * + */ + +#ifndef KEYBOARD_H +#define KEYBOARD_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_KEYBOARD + +#warning "Deprecated, use the SDL driver instead. See lv_drivers/sdl/sdl.c" + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#if USE_SDL_GPU +#include "../sdl/sdl_gpu.h" +#else +#include "../sdl/sdl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize the keyboard + */ +static inline void keyboard_init(void) +{ + /*Nothing to do*/ +} + +/** + * Get the last pressed or released character from the PC's keyboard + * @param indev_drv pointer to the related input device driver + * @param data store the read data here + */ +static inline void keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + sdl_keyboard_read(indev_drv, data); +} + + +/********************** + * MACROS + **********************/ + +#endif /*USE_KEYBOARD*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*KEYBOARD_H*/ diff --git a/libs/lv_drivers/indev/libinput.c b/libs/lv_drivers/indev/libinput.c new file mode 100644 index 00000000..63302737 --- /dev/null +++ b/libs/lv_drivers/indev/libinput.c @@ -0,0 +1,501 @@ +/** + * @file libinput.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "libinput_drv.h" +#if USE_LIBINPUT || USE_BSD_LIBINPUT + +#include +#include +#include +#include +#include +#include +#include +#include + +#if USE_BSD_LIBINPUT +#include +#else +#include +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +struct input_device { + libinput_capability capabilities; + char *path; +}; + +/********************** + * STATIC PROTOTYPES + **********************/ +static bool rescan_devices(void); +static bool add_scanned_device(char *path, libinput_capability capabilities); +static void reset_scanned_devices(void); + +static void read_pointer(libinput_drv_state_t *state, struct libinput_event *event); +static void read_keypad(libinput_drv_state_t *state, struct libinput_event *event); + +static int open_restricted(const char *path, int flags, void *user_data); +static void close_restricted(int fd, void *user_data); + +/********************** + * STATIC VARIABLES + **********************/ +static struct input_device *devices = NULL; +static size_t num_devices = 0; + +static libinput_drv_state_t default_state = { .most_recent_touch_point = { .x = 0, .y = 0 } }; + +static const int timeout = 0; // do not block +static const nfds_t nfds = 1; + +static const struct libinput_interface interface = { + .open_restricted = open_restricted, + .close_restricted = close_restricted, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * find connected input device with specific capabilities + * @param capabilities required device capabilities + * @param force_rescan erase the device cache (if any) and rescan the file system for available devices + * @return device node path (e.g. /dev/input/event0) for the first matching device or NULL if no device was found. + * The pointer is safe to use until the next forceful device search. + */ +char *libinput_find_dev(libinput_capability capabilities, bool force_rescan) { + char *path = NULL; + libinput_find_devs(capabilities, &path, 1, force_rescan); + return path; +} + +/** + * find connected input devices with specific capabilities + * @param capabilities required device capabilities + * @param devices pre-allocated array to store the found device node paths (e.g. /dev/input/event0). The pointers are + * safe to use until the next forceful device search. + * @param count maximum number of devices to find (the devices array should be at least this long) + * @param force_rescan erase the device cache (if any) and rescan the file system for available devices + * @return number of devices that were found + */ +size_t libinput_find_devs(libinput_capability capabilities, char **found, size_t count, bool force_rescan) { + if ((!devices || force_rescan) && !rescan_devices()) { + return 0; + } + + size_t num_found = 0; + + for (size_t i = 0; i < num_devices && num_found < count; ++i) { + if (devices[i].capabilities & capabilities) { + found[num_found] = devices[i].path; + num_found++; + } + } + + return num_found; +} + +/** + * Reconfigure the device file for libinput using the default driver state. Use this function if you only want + * to connect a single device. + * @param dev_name input device node path (e.g. /dev/input/event0) + * @return true: the device file set complete + * false: the device file doesn't exist current system + */ +bool libinput_set_file(char* dev_name) +{ + return libinput_set_file_state(&default_state, dev_name); +} + +/** + * Reconfigure the device file for libinput using a specific driver state. Use this function if you want to + * connect multiple devices. + * @param state the driver state to configure + * @param dev_name input device node path (e.g. /dev/input/event0) + * @return true: the device file set complete + * false: the device file doesn't exist current system + */ +bool libinput_set_file_state(libinput_drv_state_t *state, char* dev_name) +{ + // This check *should* not be necessary, yet applications crashes even on NULL handles. + // citing libinput.h:libinput_path_remove_device: + // > If no matching device exists, this function does nothing. + if (state->libinput_device) { + state->libinput_device = libinput_device_unref(state->libinput_device); + libinput_path_remove_device(state->libinput_device); + } + + state->libinput_device = libinput_path_add_device(state->libinput_context, dev_name); + if(!state->libinput_device) { + perror("unable to add device to libinput context:"); + return false; + } + state->libinput_device = libinput_device_ref(state->libinput_device); + if(!state->libinput_device) { + perror("unable to reference device within libinput context:"); + return false; + } + + state->button = LV_INDEV_STATE_REL; + state->key_val = 0; + + return true; +} + +/** + * Prepare for reading input via libinput using the default driver state. Use this function if you only want + * to connect a single device. + */ +void libinput_init(void) +{ + libinput_init_state(&default_state, LIBINPUT_NAME); +} + +/** + * Prepare for reading input via libinput using the a specific driver state. Use this function if you want to + * connect multiple devices. + * @param state driver state to initialize + * @param path input device node path (e.g. /dev/input/event0) + */ +void libinput_init_state(libinput_drv_state_t *state, char* path) +{ + state->libinput_device = NULL; + state->libinput_context = libinput_path_create_context(&interface, NULL); + + if(path == NULL || !libinput_set_file_state(state, path)) { + fprintf(stderr, "unable to add device \"%s\" to libinput context: %s\n", path ? path : "NULL", strerror(errno)); + return; + } + state->fd = libinput_get_fd(state->libinput_context); + + /* prepare poll */ + state->fds[0].fd = state->fd; + state->fds[0].events = POLLIN; + state->fds[0].revents = 0; + +#if USE_XKB + xkb_init_state(&(state->xkb_state)); +#endif +} + +/** + * Read available input events via libinput using the default driver state. Use this function if you only want + * to connect a single device. + * @param indev_drv driver object itself + * @param data store the libinput data here + */ +void libinput_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + libinput_read_state(&default_state, indev_drv, data); +} + +/** + * Read available input events via libinput using a specific driver state. Use this function if you want to + * connect multiple devices. + * @param state the driver state to use + * @param indev_drv driver object itself + * @param data store the libinput data here + */ +void libinput_read_state(libinput_drv_state_t * state, lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + struct libinput_event *event; + int rc = 0; + + rc = poll(state->fds, nfds, timeout); + switch (rc){ + case -1: + perror(NULL); + case 0: + goto report_most_recent_state; + default: + break; + } + libinput_dispatch(state->libinput_context); + while((event = libinput_get_event(state->libinput_context)) != NULL) { + switch (indev_drv->type) { + case LV_INDEV_TYPE_POINTER: + read_pointer(state, event); + break; + case LV_INDEV_TYPE_KEYPAD: + read_keypad(state, event); + break; + default: + break; + } + libinput_event_destroy(event); + } +report_most_recent_state: + data->point.x = state->most_recent_touch_point.x; + data->point.y = state->most_recent_touch_point.y; + data->state = state->button; + data->key = state->key_val; +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + +/** + * rescan all attached evdev devices and store capable ones into the static devices array for quick later filtering + * @return true if the operation succeeded + */ +static bool rescan_devices(void) { + reset_scanned_devices(); + + DIR *dir; + struct dirent *ent; + if (!(dir = opendir("/dev/input"))) { + perror("unable to open directory /dev/input"); + return false; + } + + struct libinput *context = libinput_path_create_context(&interface, NULL); + + while ((ent = readdir(dir))) { + if (strncmp(ent->d_name, "event", 5) != 0) { + continue; + } + + /* 11 characters for /dev/input/ + length of name + 1 NUL terminator */ + char *path = malloc((11 + strlen(ent->d_name) + 1) * sizeof(char)); + if (!path) { + perror("could not allocate memory for device node path"); + libinput_unref(context); + reset_scanned_devices(); + return false; + } + strcpy(path, "/dev/input/"); + strcat(path, ent->d_name); + + struct libinput_device *device = libinput_path_add_device(context, path); + if(!device) { + perror("unable to add device to libinput context"); + free(path); + continue; + } + + /* The device pointer is guaranteed to be valid until the next libinput_dispatch. Since we're not dispatching events + * as part of this function, we don't have to increase its reference count to keep it alive. + * https://wayland.freedesktop.org/libinput/doc/latest/api/group__base.html#gaa797496f0150b482a4e01376bd33a47b */ + + libinput_capability capabilities = LIBINPUT_CAPABILITY_NONE; + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD) + && (libinput_device_keyboard_has_key(device, KEY_ENTER) || libinput_device_keyboard_has_key(device, KEY_KPENTER))) + { + capabilities |= LIBINPUT_CAPABILITY_KEYBOARD; + } + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) { + capabilities |= LIBINPUT_CAPABILITY_POINTER; + } + if (libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) { + capabilities |= LIBINPUT_CAPABILITY_TOUCH; + } + + libinput_path_remove_device(device); + + if (capabilities == LIBINPUT_CAPABILITY_NONE) { + free(path); + continue; + } + + if (!add_scanned_device(path, capabilities)) { + free(path); + libinput_unref(context); + reset_scanned_devices(); + return false; + } + } + + libinput_unref(context); + return true; +} + +/** + * add a new scanned device to the static devices array, growing its size when necessary + * @param path device file path + * @param capabilities device input capabilities + * @return true if the operation succeeded + */ +static bool add_scanned_device(char *path, libinput_capability capabilities) { + /* Double array size every 2^n elements */ + if ((num_devices & (num_devices + 1)) == 0) { + struct input_device *tmp = realloc(devices, (2 * num_devices + 1) * sizeof(struct input_device)); + if (!tmp) { + perror("could not reallocate memory for devices array"); + return false; + } + devices = tmp; + } + + devices[num_devices].path = path; + devices[num_devices].capabilities = capabilities; + num_devices++; + + return true; +} + +/** + * reset the array of scanned devices and free any dynamically allocated memory + */ +static void reset_scanned_devices(void) { + if (!devices) { + return; + } + + for (size_t i = 0; i < num_devices; ++i) { + free(devices[i].path); + } + free(devices); + + devices = NULL; + num_devices = 0; +} + +/** + * Handle libinput touch / pointer events + * @param state driver state to use + * @param event libinput event + */ +static void read_pointer(libinput_drv_state_t *state, struct libinput_event *event) { + struct libinput_event_touch *touch_event = NULL; + struct libinput_event_pointer *pointer_event = NULL; + enum libinput_event_type type = libinput_event_get_type(event); + + /* We need to read unrotated display dimensions directly from the driver because libinput won't account + * for any rotation inside of LVGL */ + lv_disp_drv_t *drv = lv_disp_get_default()->driver; + + switch (type) { + case LIBINPUT_EVENT_TOUCH_MOTION: + case LIBINPUT_EVENT_TOUCH_DOWN: + touch_event = libinput_event_get_touch_event(event); + lv_coord_t x_touch = libinput_event_touch_get_x_transformed(touch_event, drv->physical_hor_res > 0 ? drv->physical_hor_res : drv->hor_res) - drv->offset_x; + lv_coord_t y_touch = libinput_event_touch_get_y_transformed(touch_event, drv->physical_ver_res > 0 ? drv->physical_ver_res : drv->ver_res) - drv->offset_y; + if (x_touch < 0 || x_touch > drv->hor_res || y_touch < 0 || y_touch > drv->ver_res) { + break; /* ignore touches that are out of bounds */ + } + state->most_recent_touch_point.x = x_touch; + state->most_recent_touch_point.y = y_touch; + state->button = LV_INDEV_STATE_PR; + break; + case LIBINPUT_EVENT_TOUCH_UP: + state->button = LV_INDEV_STATE_REL; + break; + case LIBINPUT_EVENT_POINTER_MOTION: + pointer_event = libinput_event_get_pointer_event(event); + state->most_recent_touch_point.x += libinput_event_pointer_get_dx(pointer_event); + state->most_recent_touch_point.y += libinput_event_pointer_get_dy(pointer_event); + state->most_recent_touch_point.x = LV_CLAMP(0, state->most_recent_touch_point.x, drv->hor_res - 1); + state->most_recent_touch_point.y = LV_CLAMP(0, state->most_recent_touch_point.y, drv->ver_res - 1); + break; + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + pointer_event = libinput_event_get_pointer_event(event); + lv_coord_t x_pointer = libinput_event_pointer_get_absolute_x_transformed(pointer_event, drv->physical_hor_res > 0 ? drv->physical_hor_res : drv->hor_res) - drv->offset_x; + lv_coord_t y_pointer = libinput_event_pointer_get_absolute_y_transformed(pointer_event, drv->physical_ver_res > 0 ? drv->physical_ver_res : drv->ver_res) - drv->offset_y; + if (x_pointer < 0 || x_pointer > drv->hor_res || y_pointer < 0 || y_pointer > drv->ver_res) { + break; /* ignore pointer events that are out of bounds */ + } + state->most_recent_touch_point.x = x_pointer; + state->most_recent_touch_point.y = y_pointer; + break; + case LIBINPUT_EVENT_POINTER_BUTTON: + pointer_event = libinput_event_get_pointer_event(event); + enum libinput_button_state button_state = libinput_event_pointer_get_button_state(pointer_event); + state->button = button_state == LIBINPUT_BUTTON_STATE_RELEASED ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR; + break; + default: + break; + } +} + +/** + * Handle libinput keyboard events + * @param state driver state to use + * @param event libinput event + */ +static void read_keypad(libinput_drv_state_t *state, struct libinput_event *event) { + struct libinput_event_keyboard *keyboard_event = NULL; + enum libinput_event_type type = libinput_event_get_type(event); + switch (type) { + case LIBINPUT_EVENT_KEYBOARD_KEY: + keyboard_event = libinput_event_get_keyboard_event(event); + enum libinput_key_state key_state = libinput_event_keyboard_get_key_state(keyboard_event); + uint32_t code = libinput_event_keyboard_get_key(keyboard_event); +#if USE_XKB + state->key_val = xkb_process_key_state(&(state->xkb_state), code, key_state == LIBINPUT_KEY_STATE_PRESSED); +#else + switch(code) { + case KEY_BACKSPACE: + state->key_val = LV_KEY_BACKSPACE; + break; + case KEY_ENTER: + state->key_val = LV_KEY_ENTER; + break; + case KEY_PREVIOUS: + state->key_val = LV_KEY_PREV; + break; + case KEY_NEXT: + state->key_val = LV_KEY_NEXT; + break; + case KEY_UP: + state->key_val = LV_KEY_UP; + break; + case KEY_LEFT: + state->key_val = LV_KEY_LEFT; + break; + case KEY_RIGHT: + state->key_val = LV_KEY_RIGHT; + break; + case KEY_DOWN: + state->key_val = LV_KEY_DOWN; + break; + case KEY_TAB: + state->key_val = LV_KEY_NEXT; + break; + default: + state->key_val = 0; + break; + } +#endif /* USE_XKB */ + if (state->key_val != 0) { + /* Only record button state when actual output is produced to prevent widgets from refreshing */ + state->button = (key_state == LIBINPUT_KEY_STATE_RELEASED) ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR; + } + break; + default: + break; + } +} + +static int open_restricted(const char *path, int flags, void *user_data) +{ + LV_UNUSED(user_data); + int fd = open(path, flags); + return fd < 0 ? -errno : fd; +} + +static void close_restricted(int fd, void *user_data) +{ + LV_UNUSED(user_data); + close(fd); +} + +#endif /* USE_LIBINPUT || USE_BSD_LIBINPUT */ diff --git a/libs/lv_drivers/indev/libinput_drv.h b/libs/lv_drivers/indev/libinput_drv.h new file mode 100644 index 00000000..dd6e929f --- /dev/null +++ b/libs/lv_drivers/indev/libinput_drv.h @@ -0,0 +1,145 @@ +/** + * @file libinput.h + * + */ + +#ifndef LVGL_LIBINPUT_H +#define LVGL_LIBINPUT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_LIBINPUT || USE_BSD_LIBINPUT + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#include + +#if USE_XKB +#include "xkb.h" +#endif /* USE_XKB */ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef enum { + LIBINPUT_CAPABILITY_NONE = 0, + LIBINPUT_CAPABILITY_KEYBOARD = 1U << 0, + LIBINPUT_CAPABILITY_POINTER = 1U << 1, + LIBINPUT_CAPABILITY_TOUCH = 1U << 2 +} libinput_capability; + +typedef struct { + int fd; + struct pollfd fds[1]; + + int button; + int key_val; + lv_point_t most_recent_touch_point; + + struct libinput *libinput_context; + struct libinput_device *libinput_device; + +#if USE_XKB + xkb_drv_state_t xkb_state; +#endif /* USE_XKB */ +} libinput_drv_state_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * find connected input device with specific capabilities + * @param capabilities required device capabilities + * @param force_rescan erase the device cache (if any) and rescan the file system for available devices + * @return device node path (e.g. /dev/input/event0) for the first matching device or NULL if no device was found. + * The pointer is safe to use until the next forceful device search. + */ +char *libinput_find_dev(libinput_capability capabilities, bool force_rescan); +/** + * find connected input devices with specific capabilities + * @param capabilities required device capabilities + * @param devices pre-allocated array to store the found device node paths (e.g. /dev/input/event0). The pointers are + * safe to use until the next forceful device search. + * @param count maximum number of devices to find (the devices array should be at least this long) + * @param force_rescan erase the device cache (if any) and rescan the file system for available devices + * @return number of devices that were found + */ +size_t libinput_find_devs(libinput_capability capabilities, char **found, size_t count, bool force_rescan); +/** + * Prepare for reading input via libinput using the default driver state. Use this function if you only want + * to connect a single device. + */ +void libinput_init(void); +/** + * Prepare for reading input via libinput using a specific driver state. Use this function if you want to + * connect multiple devices. + * @param state driver state to initialize + * @param path input device node path (e.g. /dev/input/event0) + */ +void libinput_init_state(libinput_drv_state_t *state, char* path); +/** + * Reconfigure the device file for libinput using the default driver state. Use this function if you only want + * to connect a single device. + * @param dev_name input device node path (e.g. /dev/input/event0) + * @return true: the device file set complete + * false: the device file doesn't exist current system + */ +bool libinput_set_file(char* dev_name); +/** + * Reconfigure the device file for libinput using a specific driver state. Use this function if you want to + * connect multiple devices. + * @param state the driver state to configure + * @param dev_name input device node path (e.g. /dev/input/event0) + * @return true: the device file set complete + * false: the device file doesn't exist current system + */ +bool libinput_set_file_state(libinput_drv_state_t *state, char* dev_name); +/** + * Read available input events via libinput using the default driver state. Use this function if you only want + * to connect a single device. + * @param indev_drv driver object itself + * @param data store the libinput data here + */ +void libinput_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); +/** + * Read available input events via libinput using a specific driver state. Use this function if you want to + * connect multiple devices. + * @param state the driver state to use + * @param indev_drv driver object itself + * @param data store the libinput data here + */ +void libinput_read_state(libinput_drv_state_t * state, lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/********************** + * MACROS + **********************/ + +#endif /* USE_LIBINPUT || USE_BSD_LIBINPUT */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LVGL_LIBINPUT_H */ diff --git a/libs/lv_drivers/indev/mouse.h b/libs/lv_drivers/indev/mouse.h new file mode 100644 index 00000000..4f331f5a --- /dev/null +++ b/libs/lv_drivers/indev/mouse.h @@ -0,0 +1,81 @@ +/** + * @file mouse.h + * + */ + +#ifndef MOUSE_H +#define MOUSE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_MOUSE + +#warning "Deprecated, use the SDL driver instead. See lv_drivers/sdl/sdl.c" + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#if USE_SDL_GPU +#include "../sdl/sdl_gpu.h" +#else +#include "../sdl/sdl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + + +/** + * Initialize the mouse + */ +static inline void mouse_init(void) +{ + /*Nothing to do*/ +} + +/** + * Get the current position and state of the mouse + * @param indev_drv pointer to the related input device driver + * @param data store the mouse data here + */ +void mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + sdl_mouse_read(indev_drv, data); +} + +/********************** + * MACROS + **********************/ + +#endif /* USE_MOUSE */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* MOUSE_H */ diff --git a/libs/lv_drivers/indev/mousewheel.h b/libs/lv_drivers/indev/mousewheel.h new file mode 100644 index 00000000..01c6dbd3 --- /dev/null +++ b/libs/lv_drivers/indev/mousewheel.h @@ -0,0 +1,80 @@ +/** + * @file mousewheel.h + * + */ + +#ifndef MOUSEWHEEL_H +#define MOUSEWHEEL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_MOUSEWHEEL + +#warning "Deprecated, use the SDL driver instead. See lv_drivers/sdl/sdl.c" + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#if USE_SDL_GPU +#include "../sdl/sdl_gpu.h" +#else +#include "../sdl/sdl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize the encoder + */ +static inline void mousewheel_init(void) +{ + /*Nothing to do*/ +} + +/** + * Get encoder (i.e. mouse wheel) ticks difference and pressed state + * @param indev_drv pointer to the related input device driver + * @param data store the read data here + */ +static inline void mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + sdl_mousewheel_read(indev_drv, data); +} + +/********************** + * MACROS + **********************/ + +#endif /*USE_MOUSEWHEEL*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*MOUSEWHEEL_H*/ diff --git a/libs/lv_drivers/indev/xkb.c b/libs/lv_drivers/indev/xkb.c new file mode 100644 index 00000000..7faffefb --- /dev/null +++ b/libs/lv_drivers/indev/xkb.c @@ -0,0 +1,217 @@ +/** + * @file xkb.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "xkb.h" +#if USE_XKB + +#include +#include +#include +#include +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static struct xkb_context *context = NULL; +static xkb_drv_state_t default_state = { .keymap = NULL, .state = NULL }; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Initialise the XKB system using the default driver state. Use this function if you only want + * to connect a single device. + * @return true if the initialisation was successful + */ +bool xkb_init(void) { + return xkb_init_state(&default_state); +} + +/** + * Initialise the XKB system using a specific driver state. Use this function if you want to + * connect multiple devices. + * @param state XKB driver state to use + * @return true if the initialisation was successful + */ +bool xkb_init_state(xkb_drv_state_t *state) { + if (!context) { + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!context) { + perror("could not create new XKB context"); + return false; + } + } + +#ifdef XKB_KEY_MAP + struct xkb_rule_names names = XKB_KEY_MAP; + return xkb_set_keymap_state(state, names); +#else + return false; /* Keymap needs to be set manually using xkb_set_keymap_state to complete initialisation */ +#endif +} + +/** + * Set a new keymap to be used for processing future key events using the default driver state. Use + * this function if you only want to connect a single device. + * @param names XKB rule names structure (use NULL components for default values) + * @return true if creating the keymap and associated state succeeded + */ +bool xkb_set_keymap(struct xkb_rule_names names) { + return xkb_set_keymap_state(&default_state, names); +} + +/** + * Set a new keymap to be used for processing future key events using a specific driver state. Use + * this function if you want to connect multiple devices. + * @param state XKB driver state to use + * @param names XKB rule names structure (use NULL components for default values) + * @return true if creating the keymap and associated state succeeded + */ +bool xkb_set_keymap_state(xkb_drv_state_t *state, struct xkb_rule_names names) { + if (state->keymap) { + xkb_keymap_unref(state->keymap); + state->keymap = NULL; + } + + state->keymap = xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!state->keymap) { + perror("could not create XKB keymap"); + return false; + } + + state->keymap = xkb_keymap_ref(state->keymap); + if (!state->keymap) { + perror("could not reference XKB keymap"); + return false; + } + + if (state->state) { + xkb_state_unref(state->state); + state->state = NULL; + } + + state->state = xkb_state_new(state->keymap); + if (!state->state) { + perror("could not create XKB state"); + return false; + } + + state->state = xkb_state_ref(state->state); + if (!state->state) { + perror("could not reference XKB state"); + return false; + } + + return true; +} + +/** + * Process an evdev scancode using the default driver state. Use this function if you only want to + * connect a single device. + * @param scancode evdev scancode to process + * @param down true if the key was pressed, false if it was releases + * @return the (first) UTF-8 character produced by the event or 0 if no output was produced + */ +uint32_t xkb_process_key(uint32_t scancode, bool down) { + return xkb_process_key_state(&default_state, scancode, down); +} + +/** + * Process an evdev scancode using a specific driver state. Use this function if you want to connect + * multiple devices. + * @param state XKB driver state to use + * @param scancode evdev scancode to process + * @param down true if the key was pressed, false if it was releases + * @return the (first) UTF-8 character produced by the event or 0 if no output was produced + */ +uint32_t xkb_process_key_state(xkb_drv_state_t *state, uint32_t scancode, bool down) { + /* Offset the evdev scancode by 8, see https://xkbcommon.org/doc/current/xkbcommon_8h.html#ac29aee92124c08d1953910ab28ee1997 */ + xkb_keycode_t keycode = scancode + 8; + + uint32_t result = 0; + + switch (xkb_state_key_get_one_sym(state->state, keycode)) { + case XKB_KEY_BackSpace: + result = LV_KEY_BACKSPACE; + break; + case XKB_KEY_Return: + case XKB_KEY_KP_Enter: + result = LV_KEY_ENTER; + break; + case XKB_KEY_Prior: + case XKB_KEY_KP_Prior: + result = LV_KEY_PREV; + break; + case XKB_KEY_Next: + case XKB_KEY_KP_Next: + result = LV_KEY_NEXT; + break; + case XKB_KEY_Up: + case XKB_KEY_KP_Up: + result = LV_KEY_UP; + break; + case XKB_KEY_Left: + case XKB_KEY_KP_Left: + result = LV_KEY_LEFT; + break; + case XKB_KEY_Right: + case XKB_KEY_KP_Right: + result = LV_KEY_RIGHT; + break; + case XKB_KEY_Down: + case XKB_KEY_KP_Down: + result = LV_KEY_DOWN; + break; + case XKB_KEY_Tab: + case XKB_KEY_KP_Tab: + result = LV_KEY_NEXT; + break; + case XKB_KEY_ISO_Left_Tab: /* Sent on SHIFT + TAB */ + result = LV_KEY_PREV; + break; + default: + break; + } + + if (result == 0) { + char buffer[4] = { 0, 0, 0, 0 }; + int size = xkb_state_key_get_utf8(state->state, keycode, NULL, 0) + 1; + if (size > 1) { + xkb_state_key_get_utf8(state->state, keycode, buffer, size); + memcpy(&result, buffer, 4); + } + } + + xkb_state_update_key(state->state, keycode, down ? XKB_KEY_DOWN : XKB_KEY_UP); + + return result; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +#endif /* USE_XKB */ diff --git a/libs/lv_drivers/indev/xkb.h b/libs/lv_drivers/indev/xkb.h new file mode 100644 index 00000000..50187473 --- /dev/null +++ b/libs/lv_drivers/indev/xkb.h @@ -0,0 +1,106 @@ +/** + * @file xkb.h + * + */ + +#ifndef XKB_H +#define XKB_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_XKB + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +struct xkb_rule_names; + +typedef struct { + struct xkb_keymap *keymap; + struct xkb_state *state; +} xkb_drv_state_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialise the XKB system using the default driver state. Use this function if you only want + * to connect a single device. + * @return true if the initialisation was successful + */ +bool xkb_init(void); +/** + * Initialise the XKB system using a specific driver state. Use this function if you want to + * connect multiple devices. + * @param state XKB driver state to use + * @return true if the initialisation was successful + */ +bool xkb_init_state(xkb_drv_state_t *state); +/** + * Set a new keymap to be used for processing future key events using the default driver state. Use + * this function if you only want to connect a single device. + * @param names XKB rule names structure (use NULL components for default values) + * @return true if creating the keymap and associated state succeeded + */ +bool xkb_set_keymap(struct xkb_rule_names names); +/** + * Set a new keymap to be used for processing future key events using a specific driver state. Use + * this function if you want to connect multiple devices. + * @param state XKB driver state to use + * @param names XKB rule names structure (use NULL components for default values) + * @return true if creating the keymap and associated state succeeded + */ +bool xkb_set_keymap_state(xkb_drv_state_t *state, struct xkb_rule_names names); +/** + * Process an evdev scancode using the default driver state. Use this function if you only want to + * connect a single device. + * @param scancode evdev scancode to process + * @param down true if the key was pressed, false if it was releases + * @return the (first) UTF-8 character produced by the event or 0 if no output was produced + */ +uint32_t xkb_process_key(uint32_t scancode, bool down); +/** + * Process an evdev scancode using a specific driver state. Use this function if you want to connect + * multiple devices. + * @param state XKB driver state to use + * @param scancode evdev scancode to process + * @param down true if the key was pressed, false if it was releases + * @return the (first) UTF-8 character produced by the event or 0 if no output was produced + */ +uint32_t xkb_process_key_state(xkb_drv_state_t *state, uint32_t scancode, bool down); + +/********************** + * MACROS + **********************/ + +#endif /* USE_XKB */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* XKB_H */ diff --git a/libs/lv_drivers/library.json b/libs/lv_drivers/library.json new file mode 100644 index 00000000..72998694 --- /dev/null +++ b/libs/lv_drivers/library.json @@ -0,0 +1,13 @@ +{ + "name": "lv_drivers", + "version": "8.3.0", + "keywords": "littlevgl, lvgl, driver, display, touchpad", + "description": "Drivers for LittlevGL graphics library.", + "repository": { + "type": "git", + "url": "https://github.com/littlevgl/lv_drivers.git" + }, + "build": { + "includeDir": "." + } +} diff --git a/libs/lv_drivers/lv_drivers.mk b/libs/lv_drivers/lv_drivers.mk new file mode 100644 index 00000000..97d4621d --- /dev/null +++ b/libs/lv_drivers/lv_drivers.mk @@ -0,0 +1,10 @@ +LV_DRIVERS_DIR_NAME ?= lv_drivers + +override CFLAGS := -I$(LVGL_DIR) $(CFLAGS) + +CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/*.c) +CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/wayland/*.c) +CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/indev/*.c) +CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/gtkdrv/*.c) +CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/display/*.c) +CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/sdl/*.c) diff --git a/libs/lv_drivers/lv_drv_conf_template.h b/libs/lv_drivers/lv_drv_conf_template.h new file mode 100644 index 00000000..db648209 --- /dev/null +++ b/libs/lv_drivers/lv_drv_conf_template.h @@ -0,0 +1,496 @@ +/** + * @file lv_drv_conf.h + * Configuration file for v8.3.0 + */ + +/* + * COPY THIS FILE AS lv_drv_conf.h + */ + +/* clang-format off */ +#if 0 /*Set it to "1" to enable the content*/ + +#ifndef LV_DRV_CONF_H +#define LV_DRV_CONF_H + +#include "lv_conf.h" + +/********************* + * DELAY INTERFACE + *********************/ +#define LV_DRV_DELAY_INCLUDE /*Dummy include by default*/ +#define LV_DRV_DELAY_US(us) /*delay_us(us)*/ /*Delay the given number of microseconds*/ +#define LV_DRV_DELAY_MS(ms) /*delay_ms(ms)*/ /*Delay the given number of milliseconds*/ + +/********************* + * DISPLAY INTERFACE + *********************/ + +/*------------ + * Common + *------------*/ +#define LV_DRV_DISP_INCLUDE /*Dummy include by default*/ +#define LV_DRV_DISP_CMD_DATA(val) /*pin_x_set(val)*/ /*Set the command/data pin to 'val'*/ +#define LV_DRV_DISP_RST(val) /*pin_x_set(val)*/ /*Set the reset pin to 'val'*/ + +/*--------- + * SPI + *---------*/ +#define LV_DRV_DISP_SPI_CS(val) /*spi_cs_set(val)*/ /*Set the SPI's Chip select to 'val'*/ +#define LV_DRV_DISP_SPI_WR_BYTE(data) /*spi_wr(data)*/ /*Write a byte the SPI bus*/ +#define LV_DRV_DISP_SPI_WR_ARRAY(adr, n) /*spi_wr_mem(adr, n)*/ /*Write 'n' bytes to SPI bus from 'adr'*/ + +/*------------------ + * Parallel port + *-----------------*/ +#define LV_DRV_DISP_PAR_CS(val) /*par_cs_set(val)*/ /*Set the Parallel port's Chip select to 'val'*/ +#define LV_DRV_DISP_PAR_SLOW /*par_slow()*/ /*Set low speed on the parallel port*/ +#define LV_DRV_DISP_PAR_FAST /*par_fast()*/ /*Set high speed on the parallel port*/ +#define LV_DRV_DISP_PAR_WR_WORD(data) /*par_wr(data)*/ /*Write a word to the parallel port*/ +#define LV_DRV_DISP_PAR_WR_ARRAY(adr, n) /*par_wr_mem(adr,n)*/ /*Write 'n' bytes to Parallel ports from 'adr'*/ + +/*************************** + * INPUT DEVICE INTERFACE + ***************************/ + +/*---------- + * Common + *----------*/ +#define LV_DRV_INDEV_INCLUDE /*Dummy include by default*/ +#define LV_DRV_INDEV_RST(val) /*pin_x_set(val)*/ /*Set the reset pin to 'val'*/ +#define LV_DRV_INDEV_IRQ_READ 0 /*pn_x_read()*/ /*Read the IRQ pin*/ + +/*--------- + * SPI + *---------*/ +#define LV_DRV_INDEV_SPI_CS(val) /*spi_cs_set(val)*/ /*Set the SPI's Chip select to 'val'*/ +#define LV_DRV_INDEV_SPI_XCHG_BYTE(data) 0 /*spi_xchg(val)*/ /*Write 'val' to SPI and give the read value*/ + +/*--------- + * I2C + *---------*/ +#define LV_DRV_INDEV_I2C_START /*i2c_start()*/ /*Make an I2C start*/ +#define LV_DRV_INDEV_I2C_STOP /*i2c_stop()*/ /*Make an I2C stop*/ +#define LV_DRV_INDEV_I2C_RESTART /*i2c_restart()*/ /*Make an I2C restart*/ +#define LV_DRV_INDEV_I2C_WR(data) /*i2c_wr(data)*/ /*Write a byte to the I1C bus*/ +#define LV_DRV_INDEV_I2C_READ(last_read) 0 /*i2c_rd()*/ /*Read a byte from the I2C bud*/ + + +/********************* + * DISPLAY DRIVERS + *********************/ + +/*------------------- + * SDL + *-------------------*/ + +/* SDL based drivers for display, mouse, mousewheel and keyboard*/ +#ifndef USE_SDL +# define USE_SDL 0 +#endif + +/* Hardware accelerated SDL driver */ +#ifndef USE_SDL_GPU +# define USE_SDL_GPU 0 +#endif + +#if USE_SDL || USE_SDL_GPU +# define SDL_HOR_RES 480 +# define SDL_VER_RES 320 + +/* Scale window by this factor (useful when simulating small screens) */ +# define SDL_ZOOM 1 + +/* Used to test true double buffering with only address changing. + * Use 2 draw buffers, bith with SDL_HOR_RES x SDL_VER_RES size*/ +# define SDL_DOUBLE_BUFFERED 0 + +/*Eclipse: Visual Studio: */ +# define SDL_INCLUDE_PATH + +/*Open two windows to test multi display support*/ +# define SDL_DUAL_DISPLAY 0 +#endif + +/*------------------- + * Monitor of PC + *-------------------*/ + +/*DEPRECATED: Use the SDL driver instead. */ +#ifndef USE_MONITOR +# define USE_MONITOR 0 +#endif + +#if USE_MONITOR +# define MONITOR_HOR_RES 480 +# define MONITOR_VER_RES 320 + +/* Scale window by this factor (useful when simulating small screens) */ +# define MONITOR_ZOOM 1 + +/* Used to test true double buffering with only address changing. + * Use 2 draw buffers, bith with MONITOR_HOR_RES x MONITOR_VER_RES size*/ +# define MONITOR_DOUBLE_BUFFERED 0 + +/*Eclipse: Visual Studio: */ +# define MONITOR_SDL_INCLUDE_PATH + +/*Open two windows to test multi display support*/ +# define MONITOR_DUAL 0 +#endif + +/*----------------------------------- + * Native Windows (including mouse) + *----------------------------------*/ +#ifndef USE_WINDOWS +# define USE_WINDOWS 0 +#endif + +#if USE_WINDOWS +# define WINDOW_HOR_RES 480 +# define WINDOW_VER_RES 320 +#endif + +/*---------------------------- + * Native Windows (win32drv) + *---------------------------*/ +#ifndef USE_WIN32DRV +# define USE_WIN32DRV 0 +#endif + +#if USE_WIN32DRV +/* Scale window by this factor (useful when simulating small screens) */ +# define WIN32DRV_MONITOR_ZOOM 1 +#endif + +/*---------------------------------------- + * GTK drivers (monitor, mouse, keyboard + *---------------------------------------*/ +#ifndef USE_GTK +# define USE_GTK 0 +#endif + +/*---------------------------------------- + * Wayland drivers (monitor, mouse, keyboard, touchscreen) + *---------------------------------------*/ +#ifndef USE_WAYLAND +# define USE_WAYLAND 0 +#endif + +#if USE_WAYLAND +/* Support for client-side decorations */ +# ifndef LV_WAYLAND_CLIENT_SIDE_DECORATIONS +# define LV_WAYLAND_CLIENT_SIDE_DECORATIONS 1 +# endif +/* Support for (deprecated) wl-shell protocol */ +# ifndef LV_WAYLAND_WL_SHELL +# define LV_WAYLAND_WL_SHELL 1 +# endif +/* Support for xdg-shell protocol */ +# ifndef LV_WAYLAND_XDG_SHELL +# define LV_WAYLAND_XDG_SHELL 0 +# endif +#endif + +/*---------------- + * SSD1963 + *--------------*/ +#ifndef USE_SSD1963 +# define USE_SSD1963 0 +#endif + +#if USE_SSD1963 +# define SSD1963_HOR_RES LV_HOR_RES +# define SSD1963_VER_RES LV_VER_RES +# define SSD1963_HT 531 +# define SSD1963_HPS 43 +# define SSD1963_LPS 8 +# define SSD1963_HPW 10 +# define SSD1963_VT 288 +# define SSD1963_VPS 12 +# define SSD1963_FPS 4 +# define SSD1963_VPW 10 +# define SSD1963_HS_NEG 0 /*Negative hsync*/ +# define SSD1963_VS_NEG 0 /*Negative vsync*/ +# define SSD1963_ORI 0 /*0, 90, 180, 270*/ +# define SSD1963_COLOR_DEPTH 16 +#endif + +/*---------------- + * R61581 + *--------------*/ +#ifndef USE_R61581 +# define USE_R61581 0 +#endif + +#if USE_R61581 +# define R61581_HOR_RES LV_HOR_RES +# define R61581_VER_RES LV_VER_RES +# define R61581_HSPL 0 /*HSYNC signal polarity*/ +# define R61581_HSL 10 /*HSYNC length (Not Implemented)*/ +# define R61581_HFP 10 /*Horitontal Front poarch (Not Implemented)*/ +# define R61581_HBP 10 /*Horitontal Back poarch (Not Implemented */ +# define R61581_VSPL 0 /*VSYNC signal polarity*/ +# define R61581_VSL 10 /*VSYNC length (Not Implemented)*/ +# define R61581_VFP 8 /*Vertical Front poarch*/ +# define R61581_VBP 8 /*Vertical Back poarch */ +# define R61581_DPL 0 /*DCLK signal polarity*/ +# define R61581_EPL 1 /*ENABLE signal polarity*/ +# define R61581_ORI 0 /*0, 180*/ +# define R61581_LV_COLOR_DEPTH 16 /*Fix 16 bit*/ +#endif + +/*------------------------------ + * ST7565 (Monochrome, low res.) + *-----------------------------*/ +#ifndef USE_ST7565 +# define USE_ST7565 0 +#endif + +#if USE_ST7565 +/*No settings*/ +#endif /*USE_ST7565*/ + +/*------------------------------ + * GC9A01 (color, low res.) + *-----------------------------*/ +#ifndef USE_GC9A01 +# define USE_GC9A01 0 +#endif + +#if USE_GC9A01 +/*No settings*/ +#endif /*USE_GC9A01*/ + +/*------------------------------------------ + * UC1610 (4 gray 160*[104|128]) + * (EA DOGXL160 160x104 tested) + *-----------------------------------------*/ +#ifndef USE_UC1610 +# define USE_UC1610 0 +#endif + +#if USE_UC1610 +# define UC1610_HOR_RES LV_HOR_RES +# define UC1610_VER_RES LV_VER_RES +# define UC1610_INIT_CONTRAST 33 /* init contrast, values in [%] */ +# define UC1610_INIT_HARD_RST 0 /* 1 : hardware reset at init, 0 : software reset */ +# define UC1610_TOP_VIEW 0 /* 0 : Bottom View, 1 : Top View */ +#endif /*USE_UC1610*/ + +/*------------------------------------------------- + * SHARP memory in pixel monochrome display series + * LS012B7DD01 (184x38 pixels.) + * LS013B7DH03 (128x128 pixels.) + * LS013B7DH05 (144x168 pixels.) + * LS027B7DH01 (400x240 pixels.) (tested) + * LS032B7DD02 (336x536 pixels.) + * LS044Q7DH01 (320x240 pixels.) + *------------------------------------------------*/ +#ifndef USE_SHARP_MIP +# define USE_SHARP_MIP 0 +#endif + +#if USE_SHARP_MIP +# define SHARP_MIP_HOR_RES LV_HOR_RES +# define SHARP_MIP_VER_RES LV_VER_RES +# define SHARP_MIP_SOFT_COM_INVERSION 0 +# define SHARP_MIP_REV_BYTE(b) /*((uint8_t) __REV(__RBIT(b)))*/ /*Architecture / compiler dependent byte bits order reverse*/ +#endif /*USE_SHARP_MIP*/ + +/*------------------------------------------------- + * ILI9341 240X320 TFT LCD + *------------------------------------------------*/ +#ifndef USE_ILI9341 +# define USE_ILI9341 0 +#endif + +#if USE_ILI9341 +# define ILI9341_HOR_RES LV_HOR_RES +# define ILI9341_VER_RES LV_VER_RES +# define ILI9341_GAMMA 1 +# define ILI9341_TEARING 0 +#endif /*USE_ILI9341*/ + +/*----------------------------------------- + * Linux frame buffer device (/dev/fbx) + *-----------------------------------------*/ +#ifndef USE_FBDEV +# define USE_FBDEV 0 +#endif + +#if USE_FBDEV +# define FBDEV_PATH "/dev/fb0" +# define FBDEV_DISPLAY_POWER_ON 1 /* 1 to force display power during initialization */ +#endif + +/*----------------------------------------- + * FreeBSD frame buffer device (/dev/fbx) + *.........................................*/ +#ifndef USE_BSD_FBDEV +# define USE_BSD_FBDEV 0 +#endif + +#if USE_BSD_FBDEV +# define FBDEV_PATH "/dev/fb0" +# define FBDEV_DISPLAY_POWER_ON 1 /* 1 to force display power during initialization */ +#endif + +/*----------------------------------------- + * DRM/KMS device (/dev/dri/cardX) + *-----------------------------------------*/ +#ifndef USE_DRM +# define USE_DRM 0 +#endif + +#if USE_DRM +# define DRM_CARD "/dev/dri/card0" +# define DRM_CONNECTOR_ID -1 /* -1 for the first connected one */ +#endif + +/********************* + * INPUT DEVICES + *********************/ + +/*-------------- + * XPT2046 + *--------------*/ +#ifndef USE_XPT2046 +# define USE_XPT2046 0 +#endif + +#if USE_XPT2046 +# define XPT2046_HOR_RES 480 +# define XPT2046_VER_RES 320 +# define XPT2046_X_MIN 200 +# define XPT2046_Y_MIN 200 +# define XPT2046_X_MAX 3800 +# define XPT2046_Y_MAX 3800 +# define XPT2046_AVG 4 +# define XPT2046_X_INV 0 +# define XPT2046_Y_INV 0 +# define XPT2046_XY_SWAP 0 +#endif + +/*----------------- + * FT5406EE8 + *-----------------*/ +#ifndef USE_FT5406EE8 +# define USE_FT5406EE8 0 +#endif + +#if USE_FT5406EE8 +# define FT5406EE8_I2C_ADR 0x38 /*7 bit address*/ +#endif + +/*--------------- + * AD TOUCH + *--------------*/ +#ifndef USE_AD_TOUCH +# define USE_AD_TOUCH 0 +#endif + +#if USE_AD_TOUCH +/*No settings*/ +#endif + + +/*--------------------------------------- + * Mouse or touchpad on PC (using SDL) + *-------------------------------------*/ +/*DEPRECATED: Use the SDL driver instead. */ +#ifndef USE_MOUSE +# define USE_MOUSE 0 +#endif + +#if USE_MOUSE +/*No settings*/ +#endif + +/*------------------------------------------- + * Mousewheel as encoder on PC (using SDL) + *------------------------------------------*/ +/*DEPRECATED: Use the SDL driver instead. */ +#ifndef USE_MOUSEWHEEL +# define USE_MOUSEWHEEL 0 +#endif + +#if USE_MOUSEWHEEL +/*No settings*/ +#endif + +/*------------------------------------------------- + * Touchscreen, mouse/touchpad or keyboard as libinput interface (for Linux based systems) + *------------------------------------------------*/ +#ifndef USE_LIBINPUT +# define USE_LIBINPUT 0 +#endif + +#ifndef USE_BSD_LIBINPUT +# define USE_BSD_LIBINPUT 0 +#endif + +#if USE_LIBINPUT || USE_BSD_LIBINPUT +/*If only a single device of the same type is connected, you can also auto detect it, e.g.: + *#define LIBINPUT_NAME libinput_find_dev(LIBINPUT_CAPABILITY_TOUCH, false)*/ +# define LIBINPUT_NAME "/dev/input/event0" /*You can use the "evtest" Linux tool to get the list of devices and test them*/ + +#endif /*USE_LIBINPUT || USE_BSD_LIBINPUT*/ + +/*------------------------------------------------- + * Mouse or touchpad as evdev interface (for Linux based systems) + *------------------------------------------------*/ +#ifndef USE_EVDEV +# define USE_EVDEV 0 +#endif + +#ifndef USE_BSD_EVDEV +# define USE_BSD_EVDEV 0 +#endif + +#if USE_EVDEV || USE_BSD_EVDEV +# define EVDEV_NAME "/dev/input/event0" /*You can use the "evtest" Linux tool to get the list of devices and test them*/ +# define EVDEV_SWAP_AXES 0 /*Swap the x and y axes of the touchscreen*/ + +# define EVDEV_CALIBRATE 0 /*Scale and offset the touchscreen coordinates by using maximum and minimum values for each axis*/ + +# if EVDEV_CALIBRATE +# define EVDEV_HOR_MIN 0 /*to invert axis swap EVDEV_XXX_MIN by EVDEV_XXX_MAX*/ +# define EVDEV_HOR_MAX 4096 /*"evtest" Linux tool can help to get the correct calibraion values>*/ +# define EVDEV_VER_MIN 0 +# define EVDEV_VER_MAX 4096 +# endif /*EVDEV_CALIBRATE*/ +#endif /*USE_EVDEV*/ + +/*------------------------------------------------- + * Full keyboard support for evdev and libinput interface + *------------------------------------------------*/ +# ifndef USE_XKB +# define USE_XKB 0 +# endif + +#if USE_LIBINPUT || USE_BSD_LIBINPUT || USE_EVDEV || USE_BSD_EVDEV +# if USE_XKB +# define XKB_KEY_MAP { .rules = NULL, \ + .model = "pc101", \ + .layout = "us", \ + .variant = NULL, \ + .options = NULL } /*"setxkbmap -query" can help find the right values for your keyboard*/ +# endif /*USE_XKB*/ +#endif /*USE_LIBINPUT || USE_BSD_LIBINPUT || USE_EVDEV || USE_BSD_EVDEV*/ + +/*------------------------------- + * Keyboard of a PC (using SDL) + *------------------------------*/ +/*DEPRECATED: Use the SDL driver instead. */ +#ifndef USE_KEYBOARD +# define USE_KEYBOARD 0 +#endif + +#if USE_KEYBOARD +/*No settings*/ +#endif + +#endif /*LV_DRV_CONF_H*/ + +#endif /*End of "Content enable"*/ diff --git a/libs/lv_drivers/sdl/sdl.c b/libs/lv_drivers/sdl/sdl.c new file mode 100644 index 00000000..87444779 --- /dev/null +++ b/libs/lv_drivers/sdl/sdl.c @@ -0,0 +1,391 @@ +/** + * @file sdl.h + * + */ + +/********************* + * INCLUDES + *********************/ +#include "sdl.h" +#if USE_MONITOR || USE_SDL + +#if LV_USE_GPU_SDL +# error "LV_USE_GPU_SDL must not be enabled" +#endif + +#if USE_MONITOR +# warning "MONITOR is deprecated, use SDL instead. See lv_drivers/sdl/sdl.c" +#endif + +#if USE_KEYBOARD +# warning "KEYBOARD is deprecated, use SDL instead. See lv_drivers/sdl/sdl.c" +#endif + +#if USE_MOUSE +# warning "MOUSE is deprecated, use SDL instead. See lv_drivers/sdl/sdl.c" +#endif + +#if USE_MOUSEWHEEL +# warning "MOUSEWHEEL is deprecated, use SDL instead that. See lv_drivers/sdl/sdl.c" +#endif + +#if USE_MONITOR && USE_SDL +# error "Cannot enable both MONITOR and SDL at the same time. " +#endif + +#if USE_MONITOR +# define SDL_HOR_RES MONITOR_HOR_RES +# define SDL_VER_RES MONITOR_VER_RES +# define SDL_ZOOM MONITOR_ZOOM +# define SDL_DOUBLE_BUFFERED MONITOR_DOUBLE_BUFFERED +# define SDL_INCLUDE_PATH MONITOR_SDL_INCLUDE_PATH +# define SDL_VIRTUAL_MACHINE MONITOR_VIRTUAL_MACHINE +# define SDL_DUAL_DISPLAY MONITOR_DUAL +#endif + +#ifndef SDL_FULLSCREEN +# define SDL_FULLSCREEN 0 +#endif + +#include "sdl_common_internal.h" +#include +#include +#include +#include SDL_INCLUDE_PATH + +/********************* + * DEFINES + *********************/ +#ifndef KEYBOARD_BUFFER_SIZE +#define KEYBOARD_BUFFER_SIZE SDL_TEXTINPUTEVENT_TEXT_SIZE +#endif + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + SDL_Window * window; + SDL_Renderer * renderer; + SDL_Texture * texture; + volatile bool sdl_refr_qry; +#if SDL_DOUBLE_BUFFERED + uint32_t * tft_fb_act; +#else + uint32_t * tft_fb; +#endif +}monitor_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void window_create(monitor_t * m); +static void window_update(monitor_t * m); +static void monitor_sdl_clean_up(void); +static void sdl_event_handler(lv_timer_t * t); +static void monitor_sdl_refr(lv_timer_t * t); + +/*********************** + * GLOBAL PROTOTYPES + ***********************/ + +/********************** + * STATIC VARIABLES + **********************/ +monitor_t monitor; + +#if SDL_DUAL_DISPLAY +monitor_t monitor2; +#endif + +static volatile bool sdl_inited = false; + +static bool left_button_down = false; +static int16_t last_x = 0; +static int16_t last_y = 0; + +static int16_t wheel_diff = 0; +static lv_indev_state_t wheel_state = LV_INDEV_STATE_RELEASED; + +static char buf[KEYBOARD_BUFFER_SIZE]; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void sdl_init(void) +{ + /*Initialize the SDL*/ + SDL_Init(SDL_INIT_VIDEO); + + SDL_SetEventFilter(quit_filter, NULL); + + window_create(&monitor); +#if SDL_DUAL_DISPLAY + window_create(&monitor2); + int x, y; + SDL_GetWindowPosition(monitor2.window, &x, &y); + SDL_SetWindowPosition(monitor.window, x + (SDL_HOR_RES * SDL_ZOOM) / 2 + 10, y); + SDL_SetWindowPosition(monitor2.window, x - (SDL_HOR_RES * SDL_ZOOM) / 2 - 10, y); +#endif + + SDL_StartTextInput(); + + lv_timer_create(sdl_event_handler, 10, NULL); +} + +/** + * Flush a buffer to the marked area + * @param disp_drv pointer to driver where this function belongs + * @param area an area where to copy `color_p` + * @param color_p an array of pixels to copy to the `area` part of the screen + */ +void sdl_display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) +{ + const lv_coord_t hres = disp_drv->physical_hor_res == -1 ? disp_drv->hor_res : disp_drv->physical_hor_res; + const lv_coord_t vres = disp_drv->physical_ver_res == -1 ? disp_drv->ver_res : disp_drv->physical_ver_res; + +// printf("x1:%d,y1:%d,x2:%d,y2:%d\n", area->x1, area->y1, area->x2, area->y2); + + /*Return if the area is out the screen*/ + if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) { + lv_disp_flush_ready(disp_drv); + return; + } + +#if SDL_DOUBLE_BUFFERED + monitor.tft_fb_act = (uint32_t *)color_p; +#else /*SDL_DOUBLE_BUFFERED*/ + + int32_t y; +#if LV_COLOR_DEPTH != 24 && LV_COLOR_DEPTH != 32 /*32 is valid but support 24 for backward compatibility too*/ + int32_t x; + for(y = area->y1; y <= area->y2 && y < vres; y++) { + for(x = area->x1; x <= area->x2; x++) { + monitor.tft_fb[y * hres + x] = lv_color_to32(*color_p); + color_p++; + } + + } +#else + uint32_t w = lv_area_get_width(area); + for(y = area->y1; y <= area->y2 && y < vres; y++) { + memcpy(&monitor.tft_fb[y * hres + area->x1], color_p, w * sizeof(lv_color_t)); + color_p += w; + } +#endif +#endif /*SDL_DOUBLE_BUFFERED*/ + + monitor.sdl_refr_qry = true; + + /* TYPICALLY YOU DO NOT NEED THIS + * If it was the last part to refresh update the texture of the window.*/ + if(lv_disp_flush_is_last(disp_drv)) { + monitor_sdl_refr(NULL); + } + + /*IMPORTANT! It must be called to tell the system the flush is ready*/ + lv_disp_flush_ready(disp_drv); + +} + + +#if SDL_DUAL_DISPLAY + +/** + * Flush a buffer to the marked area + * @param disp_drv pointer to driver where this function belongs + * @param area an area where to copy `color_p` + * @param color_p an array of pixels to copy to the `area` part of the screen + */ +void sdl_display_flush2(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) +{ + const lv_coord_t hres = disp_drv->physical_hor_res == -1 ? disp_drv->hor_res : disp_drv->physical_hor_res; + const lv_coord_t vres = disp_drv->physical_ver_res == -1 ? disp_drv->ver_res : disp_drv->physical_ver_res; + + /*Return if the area is out the screen*/ + if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) { + lv_disp_flush_ready(disp_drv); + return; + } + +#if SDL_DOUBLE_BUFFERED + monitor2.tft_fb_act = (uint32_t *)color_p; + + monitor2.sdl_refr_qry = true; + + /*IMPORTANT! It must be called to tell the system the flush is ready*/ + lv_disp_flush_ready(disp_drv); +#else + + int32_t y; +#if LV_COLOR_DEPTH != 24 && LV_COLOR_DEPTH != 32 /*32 is valid but support 24 for backward compatibility too*/ + int32_t x; + for(y = area->y1; y <= area->y2 && y < vres; y++) { + for(x = area->x1; x <= area->x2; x++) { + monitor2.tft_fb[y * hres + x] = lv_color_to32(*color_p); + color_p++; + } + + } +#else + uint32_t w = lv_area_get_width(area); + for(y = area->y1; y <= area->y2 && y < vres; y++) { + memcpy(&monitor2.tft_fb[y * hres + area->x1], color_p, w * sizeof(lv_color_t)); + color_p += w; + } +#endif + + monitor2.sdl_refr_qry = true; + + /* TYPICALLY YOU DO NOT NEED THIS + * If it was the last part to refresh update the texture of the window.*/ + if(lv_disp_flush_is_last(disp_drv)) { + monitor_sdl_refr(NULL); + } + + /*IMPORTANT! It must be called to tell the system the flush is ready*/ + lv_disp_flush_ready(disp_drv); +#endif +} +#endif + +/********************** + * STATIC FUNCTIONS + **********************/ + + +/** + * SDL main thread. All SDL related task have to be handled here! + * It initializes SDL, handles drawing and the mouse. + */ + +static void sdl_event_handler(lv_timer_t * t) +{ + (void)t; + + /*Refresh handling*/ + SDL_Event event; + while(SDL_PollEvent(&event)) { + mouse_handler(&event); + mousewheel_handler(&event); + keyboard_handler(&event); + + if((&event)->type == SDL_WINDOWEVENT) { + switch((&event)->window.event) { +#if SDL_VERSION_ATLEAST(2, 0, 5) + case SDL_WINDOWEVENT_TAKE_FOCUS: +#endif + case SDL_WINDOWEVENT_EXPOSED: + window_update(&monitor); +#if SDL_DUAL_DISPLAY + window_update(&monitor2); +#endif + break; + default: + break; + } + } + } + + /*Run until quit event not arrives*/ + if(sdl_quit_qry) { + monitor_sdl_clean_up(); + exit(0); + } +} + +/** + * SDL main thread. All SDL related task have to be handled here! + * It initializes SDL, handles drawing and the mouse. + */ + +static void monitor_sdl_refr(lv_timer_t * t) +{ + (void)t; + + /*Refresh handling*/ + if(monitor.sdl_refr_qry != false) { + monitor.sdl_refr_qry = false; + window_update(&monitor); + } + +#if SDL_DUAL_DISPLAY + if(monitor2.sdl_refr_qry != false) { + monitor2.sdl_refr_qry = false; + window_update(&monitor2); + } +#endif +} + +static void monitor_sdl_clean_up(void) +{ + SDL_DestroyTexture(monitor.texture); + SDL_DestroyRenderer(monitor.renderer); + SDL_DestroyWindow(monitor.window); + +#if SDL_DUAL_DISPLAY + SDL_DestroyTexture(monitor2.texture); + SDL_DestroyRenderer(monitor2.renderer); + SDL_DestroyWindow(monitor2.window); + +#endif + + SDL_Quit(); +} + +static void window_create(monitor_t * m) +{ + + int flag = 0; +#if SDL_FULLSCREEN + flag |= SDL_WINDOW_FULLSCREEN; +#endif + + m->window = SDL_CreateWindow(SDL_WINDOW_NAME, + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + SDL_HOR_RES * SDL_ZOOM, SDL_VER_RES * SDL_ZOOM, flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/ + + m->renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_SOFTWARE); + m->texture = SDL_CreateTexture(m->renderer, + SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, SDL_HOR_RES, SDL_VER_RES); + SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_BLEND); + + /*Initialize the frame buffer to gray (77 is an empirical value) */ +#if SDL_DOUBLE_BUFFERED + SDL_UpdateTexture(m->texture, NULL, m->tft_fb_act, SDL_HOR_RES * sizeof(uint32_t)); +#else + m->tft_fb = (uint32_t *)malloc(sizeof(uint32_t) * SDL_HOR_RES * SDL_VER_RES); + memset(m->tft_fb, 0x44, SDL_HOR_RES * SDL_VER_RES * sizeof(uint32_t)); +#endif + + m->sdl_refr_qry = true; + +} + +static void window_update(monitor_t * m) +{ +#if SDL_DOUBLE_BUFFERED == 0 + SDL_UpdateTexture(m->texture, NULL, m->tft_fb, SDL_HOR_RES * sizeof(uint32_t)); +#else + if(m->tft_fb_act == NULL) return; + SDL_UpdateTexture(m->texture, NULL, m->tft_fb_act, SDL_HOR_RES * sizeof(uint32_t)); +#endif + SDL_RenderClear(m->renderer); + lv_disp_t * d = _lv_refr_get_disp_refreshing(); + if(d->driver->screen_transp) { + SDL_SetRenderDrawColor(m->renderer, 0xff, 0, 0, 0xff); + SDL_Rect r; + r.x = 0; r.y = 0; r.w = SDL_HOR_RES; r.h = SDL_VER_RES; + SDL_RenderDrawRect(m->renderer, &r); + } + + /*Update the renderer with the texture containing the rendered image*/ + SDL_RenderCopy(m->renderer, m->texture, NULL, NULL); + SDL_RenderPresent(m->renderer); +} + +#endif /*USE_MONITOR || USE_SDL*/ diff --git a/libs/lv_drivers/sdl/sdl.h b/libs/lv_drivers/sdl/sdl.h new file mode 100644 index 00000000..234a5bd6 --- /dev/null +++ b/libs/lv_drivers/sdl/sdl.h @@ -0,0 +1,103 @@ +/** + * @file sdl.h + * + */ + +#ifndef SDL_H +#define SDL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_MONITOR || USE_SDL + +#include "sdl_common.h" + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize SDL to be used as display, mouse and mouse wheel drivers. + */ +void sdl_init(void); + +/** + * Flush a buffer to the marked area + * @param disp_drv pointer to driver where this function belongs + * @param area an area where to copy `color_p` + * @param color_p an array of pixels to copy to the `area` part of the screen + */ +void sdl_display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); + +/** + * Flush a buffer to the marked area + * @param disp_drv pointer to driver where this function belongs + * @param area an area where to copy `color_p` + * @param color_p an array of pixels to copy to the `area` part of the screen + */ +void sdl_display_flush2(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); + +/** + * Get the current position and state of the mouse + * @param indev_drv pointer to the related input device driver + * @param data store the mouse data here + */ +void sdl_mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/** + * Get encoder (i.e. mouse wheel) ticks difference and pressed state + * @param indev_drv pointer to the related input device driver + * @param data store the read data here + */ +void sdl_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/** + * Get input from the keyboard. + * @param indev_drv pointer to the related input device driver + * @param data store the red data here + */ +void sdl_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/*For backward compatibility. Will be removed.*/ +#define monitor_init sdl_init +#define monitor_flush sdl_display_flush +#define monitor_flush2 sdl_display_flush2 + +/********************** + * MACROS + **********************/ + +#endif /* USE_MONITOR || USE_SDL */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SDL_H */ diff --git a/libs/lv_drivers/sdl/sdl_common.c b/libs/lv_drivers/sdl/sdl_common.c new file mode 100644 index 00000000..b34c7fee --- /dev/null +++ b/libs/lv_drivers/sdl/sdl_common.c @@ -0,0 +1,273 @@ +// +// Created by Mariotaku on 2021/10/14. +// + +#include "sdl_common.h" + +#if USE_SDL || USE_SDL_GPU +#include "sdl_common_internal.h" + +/********************* + * DEFINES + *********************/ + +#ifndef KEYBOARD_BUFFER_SIZE +#define KEYBOARD_BUFFER_SIZE SDL_TEXTINPUTEVENT_TEXT_SIZE +#endif + +/********************** + * STATIC PROTOTYPES + **********************/ + + +/********************** + * STATIC VARIABLES + **********************/ + +volatile bool sdl_quit_qry = false; + +static bool left_button_down = false; +static int16_t last_x = 0; +static int16_t last_y = 0; + +static int16_t wheel_diff = 0; +static lv_indev_state_t wheel_state = LV_INDEV_STATE_RELEASED; + +static char buf[KEYBOARD_BUFFER_SIZE]; + +/********************** + * GLOBAL FUNCTIONS + **********************/ +/** + * Get the current position and state of the mouse + * @param indev_drv pointer to the related input device driver + * @param data store the mouse data here + */ +void sdl_mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + (void) indev_drv; /*Unused*/ + + /*Store the collected data*/ + data->point.x = last_x; + data->point.y = last_y; + data->state = left_button_down ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; +} + + +/** + * Get encoder (i.e. mouse wheel) ticks difference and pressed state + * @param indev_drv pointer to the related input device driver + * @param data store the read data here + */ +void sdl_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + (void) indev_drv; /*Unused*/ + + data->state = wheel_state; + data->enc_diff = wheel_diff; + wheel_diff = 0; +} + +/** + * Get input from the keyboard. + * @param indev_drv pointer to the related input device driver + * @param data store the red data here + */ +void sdl_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + (void) indev_drv; /*Unused*/ + + static bool dummy_read = false; + const size_t len = strlen(buf); + + /*Send a release manually*/ + if (dummy_read) { + dummy_read = false; + data->state = LV_INDEV_STATE_RELEASED; + data->continue_reading = len > 0; + } + /*Send the pressed character*/ + else if (len > 0) { + dummy_read = true; + data->state = LV_INDEV_STATE_PRESSED; + data->key = buf[0]; + memmove(buf, buf + 1, len); + data->continue_reading = true; + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +int quit_filter(void * userdata, SDL_Event * event) +{ + (void)userdata; + + if(event->type == SDL_QUIT) { + sdl_quit_qry = true; + } + + return 1; +} + +void mouse_handler(SDL_Event * event) +{ + switch(event->type) { + case SDL_MOUSEBUTTONUP: + if(event->button.button == SDL_BUTTON_LEFT) + left_button_down = false; + break; + case SDL_MOUSEBUTTONDOWN: + if(event->button.button == SDL_BUTTON_LEFT) { + left_button_down = true; + last_x = event->motion.x / SDL_ZOOM; + last_y = event->motion.y / SDL_ZOOM; + } + break; + case SDL_MOUSEMOTION: + last_x = event->motion.x / SDL_ZOOM; + last_y = event->motion.y / SDL_ZOOM; + break; + + case SDL_FINGERUP: + left_button_down = false; + last_x = LV_HOR_RES * event->tfinger.x / SDL_ZOOM; + last_y = LV_VER_RES * event->tfinger.y / SDL_ZOOM; + break; + case SDL_FINGERDOWN: + left_button_down = true; + last_x = LV_HOR_RES * event->tfinger.x / SDL_ZOOM; + last_y = LV_VER_RES * event->tfinger.y / SDL_ZOOM; + break; + case SDL_FINGERMOTION: + last_x = LV_HOR_RES * event->tfinger.x / SDL_ZOOM; + last_y = LV_VER_RES * event->tfinger.y / SDL_ZOOM; + break; + } + +} + + +/** + * It is called periodically from the SDL thread to check mouse wheel state + * @param event describes the event + */ +void mousewheel_handler(SDL_Event * event) +{ + switch(event->type) { + case SDL_MOUSEWHEEL: + // Scroll down (y = -1) means positive encoder turn, + // so invert it +#ifdef __EMSCRIPTEN__ + /*Escripten scales it wrong*/ + if(event->wheel.y < 0) wheel_diff++; + if(event->wheel.y > 0) wheel_diff--; +#else + wheel_diff = -event->wheel.y; +#endif + break; + case SDL_MOUSEBUTTONDOWN: + if(event->button.button == SDL_BUTTON_MIDDLE) { + wheel_state = LV_INDEV_STATE_PRESSED; + } + break; + case SDL_MOUSEBUTTONUP: + if(event->button.button == SDL_BUTTON_MIDDLE) { + wheel_state = LV_INDEV_STATE_RELEASED; + } + break; + default: + break; + } +} + + +/** + * Called periodically from the SDL thread, store text input or control characters in the buffer. + * @param event describes the event + */ +void keyboard_handler(SDL_Event * event) +{ + /* We only care about SDL_KEYDOWN and SDL_TEXTINPUT events */ + switch(event->type) { + case SDL_KEYDOWN: /*Button press*/ + { + const uint32_t ctrl_key = keycode_to_ctrl_key(event->key.keysym.sym); + if (ctrl_key == '\0') + return; + const size_t len = strlen(buf); + if (len < KEYBOARD_BUFFER_SIZE - 1) { + buf[len] = ctrl_key; + buf[len + 1] = '\0'; + } + break; + } + case SDL_TEXTINPUT: /*Text input*/ + { + const size_t len = strlen(buf) + strlen(event->text.text); + if (len < KEYBOARD_BUFFER_SIZE - 1) + strcat(buf, event->text.text); + } + break; + default: + break; + + } +} + + +/** + * Convert a SDL key code to it's LV_KEY_* counterpart or return '\0' if it's not a control character. + * @param sdl_key the key code + * @return LV_KEY_* control character or '\0' + */ +uint32_t keycode_to_ctrl_key(SDL_Keycode sdl_key) +{ + /*Remap some key to LV_KEY_... to manage groups*/ + + SDL_Keymod mode = SDL_GetModState(); + + switch(sdl_key) { + case SDLK_RIGHT: + case SDLK_KP_PLUS: + return LV_KEY_RIGHT; + + case SDLK_LEFT: + case SDLK_KP_MINUS: + return LV_KEY_LEFT; + + case SDLK_UP: + return LV_KEY_UP; + + case SDLK_DOWN: + return LV_KEY_DOWN; + + case SDLK_ESCAPE: + return LV_KEY_ESC; + + case SDLK_BACKSPACE: + return LV_KEY_BACKSPACE; + + case SDLK_DELETE: + return LV_KEY_DEL; + + case SDLK_KP_ENTER: + case '\r': + return LV_KEY_ENTER; + + case SDLK_TAB: + return (mode & KMOD_SHIFT)? LV_KEY_PREV: LV_KEY_NEXT; + + case SDLK_PAGEDOWN: + return LV_KEY_NEXT; + + case SDLK_PAGEUP: + return LV_KEY_PREV; + + default: + return '\0'; + } +} + +#endif /* USE_SDL || USD_SDL_GPU */ diff --git a/libs/lv_drivers/sdl/sdl_common.h b/libs/lv_drivers/sdl/sdl_common.h new file mode 100644 index 00000000..4556595b --- /dev/null +++ b/libs/lv_drivers/sdl/sdl_common.h @@ -0,0 +1,93 @@ +/** + * @file sdl_common.h + * + */ + +#ifndef SDL_COMMON_H +#define SDL_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#if USE_SDL || USE_SDL_GPU + +#ifndef SDL_INCLUDE_PATH +#define SDL_INCLUDE_PATH MONITOR_SDL_INCLUDE_PATH +#endif + +#ifndef SDL_ZOOM +#define SDL_ZOOM MONITOR_ZOOM +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +extern volatile bool sdl_quit_qry; + +/** + * Initialize SDL to be used as display, mouse and mouse wheel drivers. + */ +void sdl_init(void); + +/** + * Flush a buffer to the marked area + * @param drv pointer to driver where this function belongs + * @param area an area where to copy `color_p` + * @param color_p an array of pixel to copy to the `area` part of the screen + */ +void sdl_display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); + +/** + * Get the current position and state of the mouse + * @param indev_drv pointer to the related input device driver + * @param data store the mouse data here + */ +void sdl_mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/** + * Get encoder (i.e. mouse wheel) ticks difference and pressed state + * @param indev_drv pointer to the related input device driver + * @param data store the read data here + */ +void sdl_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/** + * Get input from the keyboard. + * @param indev_drv pointer to the related input device driver + * @param data store the red data here + */ +void sdl_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +#endif /* USE_SDL || USE_SDL_GPU */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SDL_COMMON_H */ diff --git a/libs/lv_drivers/sdl/sdl_common_internal.h b/libs/lv_drivers/sdl/sdl_common_internal.h new file mode 100644 index 00000000..0c5cce63 --- /dev/null +++ b/libs/lv_drivers/sdl/sdl_common_internal.h @@ -0,0 +1,39 @@ +/** + * @file sdl_common_internal.h + * Provides SDL related functions which are only used internal. + * + */ + +#ifndef SDL_COMMON_INTERNAL_H +#define SDL_COMMON_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "sdl_common.h" + +#if USE_SDL || USE_SDL_GPU + +#include SDL_INCLUDE_PATH + +/********************** + * GLOBAL PROTOTYPES + **********************/ +int quit_filter(void * userdata, SDL_Event * event); + +void mouse_handler(SDL_Event * event); +void mousewheel_handler(SDL_Event * event); +uint32_t keycode_to_ctrl_key(SDL_Keycode sdl_key); +void keyboard_handler(SDL_Event * event); + +#endif /* USE_SDL || USE_SDL_GPU */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SDL_COMMON_INTERNAL_H */ diff --git a/libs/lv_drivers/sdl/sdl_gpu.c b/libs/lv_drivers/sdl/sdl_gpu.c new file mode 100644 index 00000000..1a459f60 --- /dev/null +++ b/libs/lv_drivers/sdl/sdl_gpu.c @@ -0,0 +1,279 @@ +/** + * @file sdl_gpu.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "sdl_gpu.h" +#if USE_SDL_GPU + +#if LV_USE_GPU_SDL == 0 +# error "LV_USE_DRAW_SDL must be enabled" +#endif + +#if USE_KEYBOARD +# warning "KEYBOARD is deprecated, use SDL instead. See lv_drivers/sdl/sdl.c" +#endif + +#if USE_MOUSE +# warning "MOUSE is deprecated, use SDL instead. See lv_drivers/sdl/sdl.c" +#endif + +#if USE_MOUSEWHEEL +# warning "MOUSEWHEEL is deprecated, use SDL instead that. See lv_drivers/sdl/sdl.c" +#endif + +#if USE_MONITOR +# error "Cannot enable both MONITOR and SDL at the same time. " +#endif + +#include "sdl_common_internal.h" +#include +#include +#include +#include +#include SDL_INCLUDE_PATH + +/********************* + * DEFINES + *********************/ +#ifndef KEYBOARD_BUFFER_SIZE +#define KEYBOARD_BUFFER_SIZE SDL_TEXTINPUTEVENT_TEXT_SIZE +#endif + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_draw_sdl_drv_param_t drv_param; + SDL_Window * window; + SDL_Texture * texture; +}monitor_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void window_create(monitor_t * m); +static void window_update(lv_disp_drv_t *disp_drv, void * buf); +static void monitor_sdl_clean_up(void); +static void sdl_event_handler(lv_timer_t * t); + +/*********************** + * GLOBAL PROTOTYPES + ***********************/ + +static volatile bool sdl_inited = false; + + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void sdl_init(void) +{ + /*Initialize the SDL*/ + SDL_Init(SDL_INIT_VIDEO); + + SDL_SetEventFilter(quit_filter, NULL); + + sdl_inited = true; + + SDL_StartTextInput(); + + lv_timer_create(sdl_event_handler, 1, NULL); +} + +void sdl_disp_drv_init(lv_disp_drv_t * disp_drv, lv_coord_t hor_res, lv_coord_t ver_res) +{ + monitor_t *m = lv_mem_alloc(sizeof(monitor_t)); + window_create(m); + lv_disp_drv_init(disp_drv); + disp_drv->direct_mode = 1; + disp_drv->flush_cb = monitor_flush; + disp_drv->hor_res = hor_res; + disp_drv->ver_res = ver_res; + lv_disp_draw_buf_t *disp_buf = lv_mem_alloc(sizeof(lv_disp_draw_buf_t)); + lv_disp_draw_buf_init(disp_buf, m->texture, NULL, hor_res * ver_res); + disp_drv->draw_buf = disp_buf; + disp_drv->antialiasing = 1; + disp_drv->user_data = &m->drv_param; +} + +/** + * Flush a buffer to the marked area + * @param disp_drv pointer to driver where this function belongs + * @param area an area where to copy `color_p` + * @param color_p an array of pixels to copy to the `area` part of the screen + */ +void sdl_display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) +{ + lv_coord_t hres = disp_drv->hor_res; + lv_coord_t vres = disp_drv->ver_res; + +// printf("x1:%d,y1:%d,x2:%d,y2:%d\n", area->x1, area->y1, area->x2, area->y2); + + /*Return if the area is out the screen*/ + if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) { + lv_disp_flush_ready(disp_drv); + return; + } + + /* TYPICALLY YOU DO NOT NEED THIS + * If it was the last part to refresh update the texture of the window.*/ + if(lv_disp_flush_is_last(disp_drv)) { + window_update(disp_drv, color_p); + } + + /*IMPORTANT! It must be called to tell the system the flush is ready*/ + lv_disp_flush_ready(disp_drv); + +} + +void sdl_display_resize(lv_disp_t *disp, int width, int height) +{ + lv_disp_drv_t *driver = disp->driver; + SDL_Renderer *renderer = ((lv_draw_sdl_drv_param_t *) driver->user_data)->renderer; + if (driver->draw_buf->buf1) { + SDL_DestroyTexture(driver->draw_buf->buf1); + } + SDL_Texture *texture = lv_draw_sdl_create_screen_texture(renderer, width, height); + lv_disp_draw_buf_init(driver->draw_buf, texture, NULL, width * height); + driver->hor_res = (lv_coord_t) width; + driver->ver_res = (lv_coord_t) height; + SDL_RendererInfo renderer_info; + SDL_GetRendererInfo(renderer, &renderer_info); + SDL_assert(renderer_info.flags & SDL_RENDERER_TARGETTEXTURE); + SDL_SetRenderTarget(renderer, texture); + lv_disp_drv_update(disp, driver); +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + + +/** + * SDL main thread. All SDL related task have to be handled here! + * It initializes SDL, handles drawing and the mouse. + */ + +static void sdl_event_handler(lv_timer_t * t) +{ + (void)t; + + /*Refresh handling*/ + SDL_Event event; + while(SDL_PollEvent(&event)) { + mouse_handler(&event); + mousewheel_handler(&event); + keyboard_handler(&event); + + switch (event.type) { + case SDL_WINDOWEVENT: { + SDL_Window * window = SDL_GetWindowFromID(event.window.windowID); + switch (event.window.event) { +#if SDL_VERSION_ATLEAST(2, 0, 5) + case SDL_WINDOWEVENT_TAKE_FOCUS: +#endif + case SDL_WINDOWEVENT_EXPOSED: + for (lv_disp_t *cur = lv_disp_get_next(NULL); cur; cur = lv_disp_get_next(cur)) { + window_update(cur->driver, cur->driver->draw_buf->buf_act); + } + break; + case SDL_WINDOWEVENT_SIZE_CHANGED: { + for (lv_disp_t *cur = lv_disp_get_next(NULL); cur; cur = lv_disp_get_next(cur)) { + lv_draw_sdl_drv_param_t *param = cur->driver->user_data; + SDL_Renderer *renderer = SDL_GetRenderer(window); + if (param->renderer != renderer) continue; + int w, h; + SDL_GetWindowSize(window, &w, &h); + sdl_display_resize(cur, w, h); + } + break; + } + case SDL_WINDOWEVENT_CLOSE: { + for (lv_disp_t *cur = lv_disp_get_next(NULL); cur; ) { + lv_disp_t * tmp = cur; + cur = lv_disp_get_next(tmp); + monitor_t * m = tmp->driver->user_data; + SDL_Renderer *renderer = SDL_GetRenderer(window); + if (m->drv_param.renderer != renderer) continue; + SDL_DestroyTexture(tmp->driver->draw_buf->buf1); + SDL_DestroyRenderer(m->drv_param.renderer); + lv_disp_remove(tmp); + } + + break; + } + default: + break; + } + break; + } + } + } + + /*Run until quit event not arrives*/ + if(sdl_quit_qry) { + monitor_sdl_clean_up(); + exit(0); + } +} + +static void monitor_sdl_clean_up(void) +{ + for (lv_disp_t *cur = lv_disp_get_next(NULL); cur; ) { + lv_disp_t * tmp = cur; + monitor_t * m = tmp->driver->user_data; + SDL_DestroyTexture(tmp->driver->draw_buf->buf1); + SDL_DestroyRenderer(m->drv_param.renderer); + cur = lv_disp_get_next(cur); + lv_disp_remove(tmp); + } + + SDL_Quit(); +} + +static void window_create(monitor_t * m) +{ +// SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1"); + m->window = SDL_CreateWindow("TFT Simulator", + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + SDL_HOR_RES * SDL_ZOOM, SDL_VER_RES * SDL_ZOOM, SDL_WINDOW_RESIZABLE); + + m->drv_param.renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_ACCELERATED); + + m->texture = lv_draw_sdl_create_screen_texture(m->drv_param.renderer, SDL_HOR_RES, SDL_VER_RES); + /* For first frame */ + SDL_SetRenderTarget(m->drv_param.renderer, m->texture); +} + +static void window_update(lv_disp_drv_t *disp_drv, void * buf) +{ + SDL_Renderer *renderer = ((lv_draw_sdl_drv_param_t *) disp_drv->user_data)->renderer; + SDL_Texture *texture = buf; + SDL_SetRenderTarget(renderer, NULL); + SDL_RenderClear(renderer); +#if LV_COLOR_SCREEN_TRANSP + SDL_SetRenderDrawColor(renderer, 0xff, 0, 0, 0xff); + SDL_Rect r; + r.x = 0; r.y = 0; r.w = SDL_HOR_RES; r.h = SDL_VER_RES; + SDL_RenderDrawRect(renderer, &r); +#endif + + /*Update the renderer with the texture containing the rendered image*/ + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); + SDL_RenderSetClipRect(renderer, NULL); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + SDL_SetRenderTarget(renderer, texture); +} + +#endif /*USE_SDL_GPU*/ diff --git a/libs/lv_drivers/sdl/sdl_gpu.h b/libs/lv_drivers/sdl/sdl_gpu.h new file mode 100644 index 00000000..78590e8f --- /dev/null +++ b/libs/lv_drivers/sdl/sdl_gpu.h @@ -0,0 +1,96 @@ +/** + * @file sdl_gpu.h + * + */ + +#ifndef SDL_GPU_H +#define SDL_GPU_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_SDL_GPU + +#include "sdl_common.h" + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize SDL to be used as display, mouse and mouse wheel drivers. + */ +void sdl_init(void); + +void sdl_disp_drv_init(lv_disp_drv_t * disp_drv, lv_coord_t hor_res, lv_coord_t ver_res); + +/** + * Flush a buffer to the marked area + * @param disp_drv pointer to driver where this function belongs + * @param area an area where to copy `color_p` + * @param color_p an array of pixels to copy to the `area` part of the screen + */ +void sdl_display_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p); + +/** + * Get the current position and state of the mouse + * @param indev_drv pointer to the related input device driver + * @param data store the mouse data here + */ +void sdl_mouse_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/** + * Get encoder (i.e. mouse wheel) ticks difference and pressed state + * @param indev_drv pointer to the related input device driver + * @param data store the read data here + */ +void sdl_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/** + * Get input from the keyboard. + * @param indev_drv pointer to the related input device driver + * @param data store the red data here + */ +void sdl_keyboard_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); + +/*For backward compatibility. Will be removed.*/ +#define monitor_init sdl_init +#define monitor_flush sdl_display_flush + +/********************** + * MACROS + **********************/ + +#endif /* USE_SDL_GPU */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SDL_GPU_H */ diff --git a/libs/lv_drivers/wayland/.gitignore b/libs/lv_drivers/wayland/.gitignore new file mode 100644 index 00000000..2661b9b8 --- /dev/null +++ b/libs/lv_drivers/wayland/.gitignore @@ -0,0 +1,5 @@ +CMakeCache.txt +CMakeFiles/ +Makefile +cmake_install.cmake +/protocols diff --git a/libs/lv_drivers/wayland/CMakeLists.txt b/libs/lv_drivers/wayland/CMakeLists.txt new file mode 100644 index 00000000..6ac65050 --- /dev/null +++ b/libs/lv_drivers/wayland/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 2.8.12) +project(lv_wayland) + +find_package(PkgConfig) +pkg_check_modules(wayland-client REQUIRED wayland-client) +pkg_check_modules(wayland-cursor REQUIRED wayland-cursor) +pkg_check_modules(xkbcommon REQUIRED xkbcommon) + +# Wayland protocols +find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner) +pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.15) +pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir) + +macro(wayland_generate protocol_xml_file output_dir target) + get_filename_component(output_file_base ${protocol_xml_file} NAME_WE) + set(output_file_noext "${output_dir}/wayland-${output_file_base}-client-protocol") + add_custom_command(OUTPUT "${output_file_noext}.h" + COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_xml_file}" "${output_file_noext}.h" + DEPENDS "${protocol_xml_file}" + VERBATIM) + + add_custom_command(OUTPUT "${output_file_noext}.c" + COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${protocol_xml_file}" "${output_file_noext}.c" + DEPENDS "${protocol_xml_file}" + VERBATIM) + + if(NOT EXISTS ${protocol_xml_file}) + message("Protocol XML file not found: " ${protocol_xml_file}) + else() + set_property(TARGET ${target} APPEND PROPERTY SOURCES "${output_file_noext}.h" "${output_file_noext}.c") + endif() +endmacro() + +set(WAYLAND_PROTOCOLS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/protocols") +file(MAKE_DIRECTORY ${WAYLAND_PROTOCOLS_DIR}) + +add_custom_target(generate_protocols ALL) + +wayland_generate("${WAYLAND_PROTOCOLS_BASE}/stable/xdg-shell/xdg-shell.xml" ${WAYLAND_PROTOCOLS_DIR} generate_protocols) diff --git a/libs/lv_drivers/wayland/README.md b/libs/lv_drivers/wayland/README.md new file mode 100644 index 00000000..ba7fe6bc --- /dev/null +++ b/libs/lv_drivers/wayland/README.md @@ -0,0 +1,157 @@ +# Wayland display and input driver + +Wayland display and input driver, with support for keyboard, pointer (i.e. mouse) and touchscreen. +Keyboard support is based on libxkbcommon. + +Following shell are supported: + +* wl_shell (deprecated) +* xdg_shell + +> xdg_shell requires an extra build step; see section _Generate protocols_ below. + + +Basic client-side window decorations (simple title bar, minimize and close buttons) +are supported, while integration with desktop environments is not. + + +## Install headers and libraries + +### Ubuntu + +``` +sudo apt-get install libwayland-dev libxkbcommon-dev libwayland-bin wayland-protocols +``` + +### Fedora + +``` +sudo dnf install wayland-devel libxkbcommon-devel wayland-utils wayland-protocols-devel +``` + + +## Generate protocols + +Support for non-basic shells (i.e. other than _wl_shell_) requires additional +source files to be generated before the first build of the project. To do so, +navigate to the _wayland_ folder (the one which includes this file) and issue +the following commands: + +``` +cmake . +make +``` + + +## Build configuration under Eclipse + +In "Project properties > C/C++ Build > Settings" set the followings: + +- "Cross GCC Compiler > Command line pattern" + - Add ` ${wayland-cflags}` and ` ${xkbcommon-cflags}` to the end (add a space between the last command and this) + + +- "Cross GCC Linker > Command line pattern" + - Add ` ${wayland-libs}` and ` ${xkbcommon-libs}` to the end (add a space between the last command and this) + + +- In "C/C++ Build > Build variables" + - Configuration: [All Configuration] + + - Add + - Variable name: `wayland-cflags` + - Type: `String` + - Value: `pkg-config --cflags wayland-client` + - Variable name: `wayland-libs` + - Type: `String` + - Value: `pkg-config --libs wayland-client` + - Variable name: `xkbcommon-cflags` + - Type: `String` + - Value: `pkg-config --cflags xkbcommon` + - Variable name: `xkbcommon-libs` + - Type: `String` + - Value: `pkg-config --libs xkbcommon` + + +## Init Wayland in LVGL + +1. In `main.c` `#incude "lv_drivers/wayland/wayland.h"` +2. Enable the Wayland driver in `lv_drv_conf.h` with `USE_WAYLAND 1` and + configure its features below, enabling at least support for one shell. +3. `LV_COLOR_DEPTH` should be set either to `32` or `16` in `lv_conf.h`; + support for `8` and `1` depends on target platform. +4. After `lv_init()` call `lv_wayland_init()`. +5. Add a display (or more than one) using `lv_wayland_create_window()`, + possibly with a close callback to track the status of each display: +```c + #define H_RES (800) + #define V_RES (480) + + /* Create a display */ + lv_disp_t * disp = lv_wayland_create_window(H_RES, V_RES, "Window Title", close_cb); +``` + As part of the above call, the Wayland driver will register four input devices + for each display: + - a KEYPAD connected to Wayland keyboard events + - a POINTER connected to Wayland touch events + - a POINTER connected to Wayland pointer events + - a ENCODER connected to Wayland pointer axis events + Handles for input devices of each display can be get using respectively + `lv_wayland_get_indev_keyboard()`, `lv_wayland_get_indev_touchscreen()`, + `lv_wayland_get_indev_pointer()` and `lv_wayland_get_indev_pointeraxis()`, using + `disp` as argument. +5. After `lv_deinit()` (if used), or in any case during de-initialization, call + `lv_wayland_deinit()`. + +### Fullscreen mode + +In order to set one window as fullscreen or restore it as a normal one, +call the `lv_wayland_window_set_fullscreen()` function respectively with `true` +or `false` as `fullscreen` argument. + +### Disable window client-side decoration at runtime + +Even when client-side decorations are enabled at compile time, they can be +disabled at runtime setting the `LV_WAYLAND_DISABLE_WINDOWDECORATION` +environment variable to `1`. + +### Event-driven timer handler + +Set `LV_WAYLAND_TIMER_HANDLER` in `lv_drv_conf.h` and call `lv_wayland_timer_handler()` +in your timer loop (in place of `lv_timer_handler()`). + +You can now sleep/wait until the next timer/event is ready, e.g.: +``` +/* [After initialization and display creation] */ +#include +#include +#include + +struct pollfd pfd; +uint32_t time_till_next; +int sleep; + +pfd.fd = lv_wayland_get_fd(); +pfd.events = POLLIN; + +while (1) { + /* Handle any Wayland/LVGL timers/events */ + time_till_next = lv_wayland_timer_handler(); + + /* Run until the last window closes */ + if (!lv_wayland_window_is_open(NULL)) { + break; + } + + /* Wait for something interesting to happen */ + if (time_till_next == LV_NO_TIMER_READY) { + sleep = -1; + } else if (time_till_next > INT_MAX) { + sleep = INT_MAX; + } else { + sleep = time_till_next; + } + + while ((poll(&pfd, 1, sleep) < 0) && (errno == EINTR)); +} +``` diff --git a/libs/lv_drivers/wayland/wayland.c b/libs/lv_drivers/wayland/wayland.c new file mode 100644 index 00000000..11ed05cf --- /dev/null +++ b/libs/lv_drivers/wayland/wayland.c @@ -0,0 +1,2638 @@ +/** + * @file wayland.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "wayland.h" + +#if USE_WAYLAND + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#if !(LV_WAYLAND_XDG_SHELL || LV_WAYLAND_WL_SHELL) +#error "Please select at least one shell integration for Wayland driver" +#endif + +#if LV_WAYLAND_XDG_SHELL +#include "protocols/wayland-xdg-shell-client-protocol.h" +#endif + +/********************* + * DEFINES + *********************/ + +#define BYTES_PER_PIXEL ((LV_COLOR_DEPTH + 7) / 8) + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS +#define TITLE_BAR_HEIGHT 24 +#define BORDER_SIZE 2 +#define BUTTON_MARGIN LV_MAX((TITLE_BAR_HEIGHT / 6), BORDER_SIZE) +#define BUTTON_PADDING LV_MAX((TITLE_BAR_HEIGHT / 8), BORDER_SIZE) +#define BUTTON_SIZE (TITLE_BAR_HEIGHT - (2 * BUTTON_MARGIN)) +#endif + +#ifndef LV_WAYLAND_CYCLE_PERIOD +#define LV_WAYLAND_CYCLE_PERIOD LV_MIN(LV_DISP_DEF_REFR_PERIOD,1) +#endif + +/********************** + * TYPEDEFS + **********************/ + +enum object_type { + OBJECT_TITLEBAR, + OBJECT_BUTTON_CLOSE, +#if LV_WAYLAND_XDG_SHELL + OBJECT_BUTTON_MAXIMIZE, + OBJECT_BUTTON_MINIMIZE, +#endif + OBJECT_BORDER_TOP, + OBJECT_BORDER_BOTTOM, + OBJECT_BORDER_LEFT, + OBJECT_BORDER_RIGHT, + FIRST_DECORATION = OBJECT_TITLEBAR, + LAST_DECORATION = OBJECT_BORDER_RIGHT, + OBJECT_WINDOW, +}; + +#define NUM_DECORATIONS (LAST_DECORATION-FIRST_DECORATION+1) + +struct window; +struct input +{ + struct + { + lv_coord_t x; + lv_coord_t y; + lv_indev_state_t left_button; + lv_indev_state_t right_button; + lv_indev_state_t wheel_button; + int16_t wheel_diff; + } pointer; + + struct + { + lv_key_t key; + lv_indev_state_t state; + } keyboard; + + struct + { + lv_coord_t x; + lv_coord_t y; + lv_indev_state_t state; + } touch; +}; + +struct seat +{ + struct wl_touch *wl_touch; + struct wl_pointer *wl_pointer; + struct wl_keyboard *wl_keyboard; + + struct + { + struct xkb_keymap *keymap; + struct xkb_state *state; + } xkb; +}; + +struct buffer_hdl +{ + void *base; + int size; + struct wl_buffer *wl_buffer; + bool busy; +}; + +struct buffer_allocator +{ + int shm_mem_fd; + int shm_mem_size; + int shm_file_free_size; + struct wl_shm_pool *shm_pool; +}; + +struct graphic_object +{ + struct window *window; + + struct wl_surface *surface; + bool surface_configured; + struct wl_subsurface *subsurface; + + enum object_type type; + int width; + int height; + + struct buffer_hdl buffer; + + struct input input; +}; + +struct application +{ + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_subcompositor *subcompositor; + struct wl_shm *shm; + struct wl_seat *wl_seat; + + struct wl_cursor_theme *cursor_theme; + struct wl_surface *cursor_surface; + +#if LV_WAYLAND_WL_SHELL + struct wl_shell *wl_shell; +#endif + +#if LV_WAYLAND_XDG_SHELL + struct xdg_wm_base *xdg_wm; +#endif + + const char *xdg_runtime_dir; + +#ifdef LV_WAYLAND_CLIENT_SIDE_DECORATIONS + bool opt_disable_decorations; +#endif + + uint32_t format; + + struct xkb_context *xkb_context; + + struct seat seat; + + struct graphic_object *touch_obj; + struct graphic_object *pointer_obj; + struct graphic_object *keyboard_obj; + + lv_ll_t window_ll; + lv_timer_t * cycle_timer; + + bool cursor_flush_pending; +}; + +struct window +{ + lv_disp_drv_t lv_disp_drv; + lv_disp_draw_buf_t lv_disp_draw_buf; + lv_disp_t *lv_disp; + + lv_indev_drv_t lv_indev_drv_pointer; + lv_indev_t * lv_indev_pointer; + + lv_indev_drv_t lv_indev_drv_pointeraxis; + lv_indev_t * lv_indev_pointeraxis; + + lv_indev_drv_t lv_indev_drv_touch; + lv_indev_t * lv_indev_touch; + + lv_indev_drv_t lv_indev_drv_keyboard; + lv_indev_t * lv_indev_keyboard; + + lv_wayland_display_close_f_t close_cb; + + struct application *application; + +#if LV_WAYLAND_WL_SHELL + struct wl_shell_surface *wl_shell_surface; +#endif + +#if LV_WAYLAND_XDG_SHELL + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; +#endif + + struct buffer_allocator allocator; + + struct graphic_object * body; + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + struct graphic_object * decoration[NUM_DECORATIONS]; +#endif + + int width; + int height; + + bool resize_pending; + int resize_width; + int resize_height; + + bool flush_pending; + bool shall_close; + bool closed; + bool maximized; + bool fullscreen; +}; + +/********************************* + * STATIC VARIABLES and FUNTIONS + *********************************/ + +static struct application application; + +static inline bool _is_digit(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + +static unsigned int _atoi(const char ** str) +{ + unsigned int i = 0U; + while (_is_digit(**str)) + { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + +static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct application *app = data; + + switch (format) + { +#if (LV_COLOR_DEPTH == 32) + case WL_SHM_FORMAT_ARGB8888: + app->format = format; + break; + case WL_SHM_FORMAT_XRGB8888: + if (app->format != WL_SHM_FORMAT_ARGB8888) + { + app->format = format; + } + break; +#elif (LV_COLOR_DEPTH == 16) + case WL_SHM_FORMAT_RGB565: + app->format = format; + break; +#elif (LV_COLOR_DEPTH == 8) + case WL_SHM_FORMAT_RGB332: + app->format = format; + break; +#elif (LV_COLOR_DEPTH == 1) + case WL_SHM_FORMAT_RGB332: + app->format = format; + break; +#endif + default: + break; + } +} + +static const struct wl_shm_listener shm_listener = { + shm_format +}; + +static void pointer_handle_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct application *app = data; + const char * cursor = "left_ptr"; + int pos_x = wl_fixed_to_int(sx); + int pos_y = wl_fixed_to_int(sy); + + if (!surface) + { + app->pointer_obj = NULL; + return; + } + + app->pointer_obj = wl_surface_get_user_data(surface); + + app->pointer_obj->input.pointer.x = pos_x; + app->pointer_obj->input.pointer.y = pos_y; + +#if (LV_WAYLAND_CLIENT_SIDE_DECORATIONS && LV_WAYLAND_XDG_SHELL) + if (!app->pointer_obj->window->xdg_toplevel || app->opt_disable_decorations) + { + return; + } + + struct window *window = app->pointer_obj->window; + + switch (app->pointer_obj->type) + { + case OBJECT_BORDER_TOP: + if (window->maximized) + { + // do nothing + } + else if (pos_x < (BORDER_SIZE * 5)) + { + cursor = "top_left_corner"; + } + else if (pos_x >= (window->width + BORDER_SIZE - (BORDER_SIZE * 5))) + { + cursor = "top_right_corner"; + } + else + { + cursor = "top_side"; + } + break; + case OBJECT_BORDER_BOTTOM: + if (window->maximized) + { + // do nothing + } + else if (pos_x < (BORDER_SIZE * 5)) + { + cursor = "bottom_left_corner"; + } + else if (pos_x >= (window->width + BORDER_SIZE - (BORDER_SIZE * 5))) + { + cursor = "bottom_right_corner"; + } + else + { + cursor = "bottom_side"; + } + break; + case OBJECT_BORDER_LEFT: + if (window->maximized) + { + // do nothing + } + else if (pos_y < (BORDER_SIZE * 5)) + { + cursor = "top_left_corner"; + } + else if (pos_y >= (window->height + BORDER_SIZE - (BORDER_SIZE * 5))) + { + cursor = "bottom_left_corner"; + } + else + { + cursor = "left_side"; + } + break; + case OBJECT_BORDER_RIGHT: + if (window->maximized) + { + // do nothing + } + else if (pos_y < (BORDER_SIZE * 5)) + { + cursor = "top_right_corner"; + } + else if (pos_y >= (window->height + BORDER_SIZE - (BORDER_SIZE * 5))) + { + cursor = "bottom_right_corner"; + } + else + { + cursor = "right_side"; + } + break; + default: + break; + } +#endif + + if (app->cursor_surface) + { + struct wl_cursor_image *cursor_image = wl_cursor_theme_get_cursor(app->cursor_theme, cursor)->images[0]; + wl_pointer_set_cursor(pointer, serial, app->cursor_surface, cursor_image->hotspot_x, cursor_image->hotspot_y); + wl_surface_attach(app->cursor_surface, wl_cursor_image_get_buffer(cursor_image), 0, 0); + wl_surface_damage(app->cursor_surface, 0, 0, cursor_image->width, cursor_image->height); + wl_surface_commit(app->cursor_surface); + app->cursor_flush_pending = true; + } +} + +static void pointer_handle_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) +{ + struct application *app = data; + + if (!surface || (app->pointer_obj == wl_surface_get_user_data(surface))) + { + app->pointer_obj = NULL; + } +} + +static void pointer_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t sx, wl_fixed_t sy) +{ + struct application *app = data; + int max_x, max_y; + + if (!app->pointer_obj) + { + return; + } + + app->pointer_obj->input.pointer.x = LV_MAX(0, LV_MIN(wl_fixed_to_int(sx), app->pointer_obj->width - 1)); + app->pointer_obj->input.pointer.y = LV_MAX(0, LV_MIN(wl_fixed_to_int(sy), app->pointer_obj->height - 1)); +} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) +{ + struct application *app = data; + const lv_indev_state_t lv_state = + (state == WL_POINTER_BUTTON_STATE_PRESSED) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL; + + if (!app->pointer_obj) + { + return; + } + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + struct window *window = app->pointer_obj->window; + int pos_x = app->pointer_obj->input.pointer.x; + int pos_y = app->pointer_obj->input.pointer.y; +#endif + + switch (app->pointer_obj->type) + { + case OBJECT_WINDOW: + switch (button) + { + case BTN_LEFT: + app->pointer_obj->input.pointer.left_button = lv_state; + break; + case BTN_RIGHT: + app->pointer_obj->input.pointer.right_button = lv_state; + break; + case BTN_MIDDLE: + app->pointer_obj->input.pointer.wheel_button = lv_state; + break; + default: + break; + } + break; +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + case OBJECT_TITLEBAR: + if ((button == BTN_LEFT) && (state == WL_POINTER_BUTTON_STATE_PRESSED)) + { +#if LV_WAYLAND_XDG_SHELL + if (window->xdg_toplevel) + { + xdg_toplevel_move(window->xdg_toplevel, app->wl_seat, serial); + window->flush_pending = true; + } +#endif +#if LV_WAYLAND_WL_SHELL + if (window->wl_shell_surface) + { + wl_shell_surface_move(window->wl_shell_surface, app->wl_seat, serial); + window->flush_pending = true; + } +#endif + } + break; + case OBJECT_BUTTON_CLOSE: + if ((button == BTN_LEFT) && (state == WL_POINTER_BUTTON_STATE_RELEASED)) + { + window->shall_close = true; + } + break; +#if LV_WAYLAND_XDG_SHELL + case OBJECT_BUTTON_MAXIMIZE: + if ((button == BTN_LEFT) && (state == WL_POINTER_BUTTON_STATE_RELEASED)) + { + if (window->xdg_toplevel) + { + if (window->maximized) + { + xdg_toplevel_unset_maximized(window->xdg_toplevel); + } + else + { + xdg_toplevel_set_maximized(window->xdg_toplevel); + } + window->maximized ^= true; + window->flush_pending = true; + } + } + break; + case OBJECT_BUTTON_MINIMIZE: + if ((button == BTN_LEFT) && (state == WL_POINTER_BUTTON_STATE_RELEASED)) + { + if (window->xdg_toplevel) + { + xdg_toplevel_set_minimized(window->xdg_toplevel); + window->flush_pending = true; + } + } + break; + case OBJECT_BORDER_TOP: + if ((button == BTN_LEFT) && (state == WL_POINTER_BUTTON_STATE_PRESSED)) + { + if (window->xdg_toplevel && !window->maximized) + { + uint32_t edge; + if (pos_x < (BORDER_SIZE * 5)) + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; + } + else if (pos_x >= (window->width + BORDER_SIZE - (BORDER_SIZE * 5))) + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; + } + else + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP; + } + xdg_toplevel_resize(window->xdg_toplevel, + window->application->wl_seat, serial, edge); + window->flush_pending = true; + } + } + break; + case OBJECT_BORDER_BOTTOM: + if ((button == BTN_LEFT) && (state == WL_POINTER_BUTTON_STATE_PRESSED)) + { + if (window->xdg_toplevel && !window->maximized) + { + uint32_t edge; + if (pos_x < (BORDER_SIZE * 5)) + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; + } + else if (pos_x >= (window->width + BORDER_SIZE - (BORDER_SIZE * 5))) + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; + } + else + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + } + xdg_toplevel_resize(window->xdg_toplevel, + window->application->wl_seat, serial, edge); + window->flush_pending = true; + } + } + break; + case OBJECT_BORDER_LEFT: + if ((button == BTN_LEFT) && (state == WL_POINTER_BUTTON_STATE_PRESSED)) + { + if (window->xdg_toplevel && !window->maximized) + { + uint32_t edge; + if (pos_y < (BORDER_SIZE * 5)) + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; + } + else if (pos_y >= (window->height + BORDER_SIZE - (BORDER_SIZE * 5))) + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; + } + else + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_LEFT; + } + xdg_toplevel_resize(window->xdg_toplevel, + window->application->wl_seat, serial, edge); + window->flush_pending = true; + } + } + break; + case OBJECT_BORDER_RIGHT: + if ((button == BTN_LEFT) && (state == WL_POINTER_BUTTON_STATE_PRESSED)) + { + if (window->xdg_toplevel && !window->maximized) + { + uint32_t edge; + if (pos_y < (BORDER_SIZE * 5)) + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; + } + else if (pos_y >= (window->height + BORDER_SIZE - (BORDER_SIZE * 5))) + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; + } + else + { + edge = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; + } + xdg_toplevel_resize(window->xdg_toplevel, + window->application->wl_seat, serial, edge); + window->flush_pending = true; + } + } + break; +#endif // LV_WAYLAND_XDG_SHELL +#endif // LV_WAYLAND_CLIENT_SIDE_DECORATIONS + default: + break; + } +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ + struct application *app = data; + const int diff = wl_fixed_to_int(value); + + if (!app->pointer_obj) + { + return; + } + + if (axis == 0) + { + if (diff > 0) + { + app->pointer_obj->input.pointer.wheel_diff++; + } + else if (diff < 0) + { + app->pointer_obj->input.pointer.wheel_diff--; + } + } +} + +static const struct wl_pointer_listener pointer_listener = { + .enter = pointer_handle_enter, + .leave = pointer_handle_leave, + .motion = pointer_handle_motion, + .button = pointer_handle_button, + .axis = pointer_handle_axis, +}; + +static lv_key_t keycode_xkb_to_lv(xkb_keysym_t xkb_key) +{ + lv_key_t key = 0; + + if (((xkb_key >= XKB_KEY_space) && (xkb_key <= XKB_KEY_asciitilde))) + { + key = xkb_key; + } + else if (((xkb_key >= XKB_KEY_KP_0) && (xkb_key <= XKB_KEY_KP_9))) + { + key = (xkb_key & 0x003f); + } + else + { + switch (xkb_key) + { + case XKB_KEY_BackSpace: + key = LV_KEY_BACKSPACE; + break; + case XKB_KEY_Return: + case XKB_KEY_KP_Enter: + key = LV_KEY_ENTER; + break; + case XKB_KEY_Escape: + key = LV_KEY_ESC; + break; + case XKB_KEY_Delete: + case XKB_KEY_KP_Delete: + key = LV_KEY_DEL; + break; + case XKB_KEY_Home: + case XKB_KEY_KP_Home: + key = LV_KEY_HOME; + break; + case XKB_KEY_Left: + case XKB_KEY_KP_Left: + key = LV_KEY_LEFT; + break; + case XKB_KEY_Up: + case XKB_KEY_KP_Up: + key = LV_KEY_UP; + break; + case XKB_KEY_Right: + case XKB_KEY_KP_Right: + key = LV_KEY_RIGHT; + break; + case XKB_KEY_Down: + case XKB_KEY_KP_Down: + key = LV_KEY_DOWN; + break; + case XKB_KEY_Prior: + case XKB_KEY_KP_Prior: + key = LV_KEY_PREV; + break; + case XKB_KEY_Next: + case XKB_KEY_KP_Next: + case XKB_KEY_Tab: + case XKB_KEY_KP_Tab: + key = LV_KEY_NEXT; + break; + case XKB_KEY_End: + case XKB_KEY_KP_End: + key = LV_KEY_END; + break; + default: + break; + } + } + + return key; +} + +static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, + uint32_t format, int fd, uint32_t size) +{ + struct application *app = data; + + struct xkb_keymap *keymap; + struct xkb_state *state; + char *map_str; + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) + { + close(fd); + return; + } + + map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map_str == MAP_FAILED) + { + close(fd); + return; + } + + /* Set up XKB keymap */ + keymap = xkb_keymap_new_from_string(app->xkb_context, map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, 0); + munmap(map_str, size); + close(fd); + + if (!keymap) + { + LV_LOG_ERROR("failed to compile keymap"); + return; + } + + /* Set up XKB state */ + state = xkb_state_new(keymap); + if (!state) + { + LV_LOG_ERROR("failed to create XKB state"); + xkb_keymap_unref(keymap); + return; + } + + xkb_keymap_unref(app->seat.xkb.keymap); + xkb_state_unref(app->seat.xkb.state); + app->seat.xkb.keymap = keymap; + app->seat.xkb.state = state; +} + +static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ + struct application *app = data; + + if (!surface) + { + app->keyboard_obj = NULL; + } + else + { + app->keyboard_obj = wl_surface_get_user_data(surface); + } +} + +static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ + struct application *app = data; + + if (!surface || (app->keyboard_obj == wl_surface_get_user_data(surface))) + { + app->keyboard_obj = NULL; + } +} + +static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ + struct application *app = data; + const uint32_t code = (key + 8); + const xkb_keysym_t *syms; + xkb_keysym_t sym = XKB_KEY_NoSymbol; + + if (!app->keyboard_obj || !app->seat.xkb.state) + { + return; + } + + if (xkb_state_key_get_syms(app->seat.xkb.state, code, &syms) == 1) + { + sym = syms[0]; + } + + const lv_key_t lv_key = keycode_xkb_to_lv(sym); + const lv_indev_state_t lv_state = + (state == WL_KEYBOARD_KEY_STATE_PRESSED) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL; + + if (lv_key != 0) + { + app->keyboard_obj->input.keyboard.key = lv_key; + app->keyboard_obj->input.keyboard.state = lv_state; + } +} + +static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ + struct application *app = data; + + /* If we're not using a keymap, then we don't handle PC-style modifiers */ + if (!app->seat.xkb.keymap) + { + return; + } + + xkb_state_update_mask(app->seat.xkb.state, + mods_depressed, mods_latched, mods_locked, 0, 0, group); +} + +static const struct wl_keyboard_listener keyboard_listener = { + .keymap = keyboard_handle_keymap, + .enter = keyboard_handle_enter, + .leave = keyboard_handle_leave, + .key = keyboard_handle_key, + .modifiers = keyboard_handle_modifiers, +}; + +static void touch_handle_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, struct wl_surface *surface, + int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct application *app = data; + + if (!surface) + { + app->touch_obj = NULL; + return; + } + + app->touch_obj = wl_surface_get_user_data(surface); + + app->touch_obj->input.touch.x = wl_fixed_to_int(x_w); + app->touch_obj->input.touch.y = wl_fixed_to_int(y_w); + app->touch_obj->input.touch.state = LV_INDEV_STATE_PR; + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + struct window *window = app->touch_obj->window; + switch (app->touch_obj->type) + { + case OBJECT_TITLEBAR: +#if LV_WAYLAND_XDG_SHELL + if (window->xdg_toplevel) + { + xdg_toplevel_move(window->xdg_toplevel, app->wl_seat, serial); + window->flush_pending = true; + } +#endif +#if LV_WAYLAND_WL_SHELL + if (window->wl_shell_surface) + { + wl_shell_surface_move(window->wl_shell_surface, app->wl_seat, serial); + window->flush_pending = true; + } +#endif + break; + default: + break; + } +#endif +} + +static void touch_handle_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, int32_t id) +{ + struct application *app = data; + + if (!app->touch_obj) + { + return; + } + + app->touch_obj->input.touch.state = LV_INDEV_STATE_REL; + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + struct window *window = app->touch_obj->window; + switch (app->touch_obj->type) + { + case OBJECT_BUTTON_CLOSE: + window->shall_close = true; + break; +#if LV_WAYLAND_XDG_SHELL + case OBJECT_BUTTON_MAXIMIZE: + if (window->xdg_toplevel) + { + if (window->maximized) + { + xdg_toplevel_unset_maximized(window->xdg_toplevel); + } + else + { + xdg_toplevel_set_maximized(window->xdg_toplevel); + } + window->maximized ^= true; + } + break; + case OBJECT_BUTTON_MINIMIZE: + if (window->xdg_toplevel) + { + xdg_toplevel_set_minimized(window->xdg_toplevel); + window->flush_pending = true; + } +#endif // LV_WAYLAND_XDG_SHELL + default: + break; + } +#endif // LV_WAYLAND_CLIENT_SIDE_DECORATIONS + + app->touch_obj = NULL; +} + +static void touch_handle_motion(void *data, struct wl_touch *wl_touch, + uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct application *app = data; + + if (!app->touch_obj) + { + return; + } + + app->touch_obj->input.touch.x = wl_fixed_to_int(x_w); + app->touch_obj->input.touch.y = wl_fixed_to_int(y_w); +} + +static void touch_handle_frame(void *data, struct wl_touch *wl_touch) +{ +} + +static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) +{ +} + +static const struct wl_touch_listener touch_listener = { + .down = touch_handle_down, + .up = touch_handle_up, + .motion = touch_handle_motion, + .frame = touch_handle_frame, + .cancel = touch_handle_cancel, +}; + +static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum wl_seat_capability caps) +{ + struct application *app = data; + struct seat *seat = &app->seat; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !seat->wl_pointer) + { + seat->wl_pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, app); + app->cursor_surface = wl_compositor_create_surface(app->compositor); + if (!app->cursor_surface) + { + LV_LOG_WARN("failed to create cursor surface"); + } + } + else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer) + { + wl_pointer_destroy(seat->wl_pointer); + if (app->cursor_surface) + { + wl_surface_destroy(app->cursor_surface); + } + seat->wl_pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !seat->wl_keyboard) + { + seat->wl_keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, app); + } + else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && seat->wl_keyboard) + { + wl_keyboard_destroy(seat->wl_keyboard); + seat->wl_keyboard = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !seat->wl_touch) + { + seat->wl_touch = wl_seat_get_touch(wl_seat); + wl_touch_add_listener(seat->wl_touch, &touch_listener, app); + } + else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch) + { + wl_touch_destroy(seat->wl_touch); + seat->wl_touch = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + .capabilities = seat_handle_capabilities, +}; + +#if LV_WAYLAND_WL_SHELL +static void wl_shell_handle_ping(void *data, struct wl_shell_surface *shell_surface, uint32_t serial) +{ + return wl_shell_surface_pong(shell_surface, serial); +} + +static void wl_shell_handle_configure(void *data, struct wl_shell_surface *shell_surface, + uint32_t edges, int32_t width, int32_t height) +{ + struct window *window = (struct window *)data; + + if ((width <= 0) || (height <= 0)) + { + return; + } + else if ((width != window->width) || (height != window->height)) + { + window->resize_width = width; + window->resize_height = height; + window->resize_pending = true; + } +} + +static const struct wl_shell_surface_listener shell_surface_listener = { + .ping = wl_shell_handle_ping, + .configure = wl_shell_handle_configure, +}; +#endif + +#if LV_WAYLAND_XDG_SHELL +static void xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) +{ + struct window *window = (struct window *)data; + struct buffer_hdl *buffer = &window->body->buffer; + + xdg_surface_ack_configure(xdg_surface, serial); + + if ((!window->body->surface_configured) && (buffer->busy)) { + // Flush occured before surface was configured, so add buffer here + wl_surface_attach(window->body->surface, buffer->wl_buffer, 0, 0); + wl_surface_commit(window->body->surface); + window->flush_pending = true; + } + + window->body->surface_configured = true; +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_handle_configure, +}; + +static void xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height, struct wl_array *states) +{ + struct window *window = (struct window *)data; + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + if (!window->application->opt_disable_decorations && !window->fullscreen) + { + width -= (2 * BORDER_SIZE); + height -= (TITLE_BAR_HEIGHT + (2 * BORDER_SIZE)); + } +#endif + + if ((width <= 0) || (height <= 0)) + { + return; + } + + if ((width != window->width) || (height != window->height)) + { + window->resize_width = width; + window->resize_height = height; + window->resize_pending = true; + } +} + +static void xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel) +{ + struct window *window = (struct window *)data; + window->shall_close = true; +} + +static void xdg_toplevel_handle_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height) +{ + struct window *window = (struct window *)data; + /* Optional: Could set window width/height upper bounds, however, currently + * we'll honor the set width/height. + */ +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, + .configure_bounds = xdg_toplevel_handle_configure_bounds +}; + +static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) +{ + return xdg_wm_base_pong(xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping +}; +#endif + +static void handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + struct application *app = data; + + if (strcmp(interface, wl_compositor_interface.name) == 0) + { + app->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); + } + else if (strcmp(interface, wl_subcompositor_interface.name) == 0) + { + app->subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1); + } + else if (strcmp(interface, wl_shm_interface.name) == 0) + { + app->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + wl_shm_add_listener(app->shm, &shm_listener, app); + app->cursor_theme = wl_cursor_theme_load(NULL, 32, app->shm); + } + else if (strcmp(interface, wl_seat_interface.name) == 0) + { + app->wl_seat = wl_registry_bind(app->registry, name, &wl_seat_interface, 1); + wl_seat_add_listener(app->wl_seat, &seat_listener, app); + } +#if LV_WAYLAND_WL_SHELL + else if (strcmp(interface, wl_shell_interface.name) == 0) + { + app->wl_shell = wl_registry_bind(registry, name, &wl_shell_interface, 1); + } +#endif +#if LV_WAYLAND_XDG_SHELL + else if (strcmp(interface, xdg_wm_base_interface.name) == 0) + { + app->xdg_wm = wl_registry_bind(app->registry, name, &xdg_wm_base_interface, version); + xdg_wm_base_add_listener(app->xdg_wm, &xdg_wm_base_listener, app); + } +#endif +} + +static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ + +} + +static const struct wl_registry_listener registry_listener = { + .global = handle_global, + .global_remove = handle_global_remove +}; + +static void handle_wl_buffer_release(void *data, struct wl_buffer *wl_buffer) +{ + struct buffer_hdl *buffer_hdl = (struct buffer_hdl *)data; + buffer_hdl->busy = false; +} + +static const struct wl_buffer_listener wl_buffer_listener = { + .release = handle_wl_buffer_release, +}; + +static bool initialize_allocator(struct buffer_allocator *allocator, const char *dir) +{ + static const char template[] = "/lvgl-wayland-XXXXXX"; + char *name; + + // Create file for shared memory allocation + name = lv_mem_alloc(strlen(dir) + sizeof(template)); + LV_ASSERT_MSG(name, "cannot allocate memory for name"); + if (!name) + { + return false; + } + + strcpy(name, dir); + strcat(name, template); + + allocator->shm_mem_fd = mkstemp(name); + + unlink(name); + lv_mem_free(name); + + LV_ASSERT_MSG((allocator->shm_mem_fd >= 0), "cannot create tmpfile"); + if (allocator->shm_mem_fd < 0) + { + return false; + } + + allocator->shm_mem_size = 0; + allocator->shm_file_free_size = 0; + + return true; +} + +static void deinitialize_allocator(struct buffer_allocator *allocator) +{ + if (allocator->shm_pool) + { + wl_shm_pool_destroy(allocator->shm_pool); + } + + if (allocator->shm_mem_fd >= 0) + { + close(allocator->shm_mem_fd); + allocator->shm_mem_fd = -1; + } +} + +static bool initialize_buffer(struct window *window, struct buffer_hdl *buffer_hdl, + int width, int height) +{ + struct application *app = window->application; + struct buffer_allocator *allocator = &window->allocator; + int allocated_size = 0; + int ret; + long sz = sysconf(_SC_PAGESIZE); + + buffer_hdl->size = (((width * height * BYTES_PER_PIXEL) + sz - 1) / sz) * sz; + + LV_LOG_TRACE("initializing buffer %dx%d (alloc size: %d)", + width, height, buffer_hdl->size); + + if (allocator->shm_file_free_size < buffer_hdl->size) + { + do + { + ret = ftruncate(allocator->shm_mem_fd, + allocator->shm_mem_size + (buffer_hdl->size - allocator->shm_file_free_size)); + } + while ((ret < 0) && (errno == EINTR)); + + if (ret < 0) + { + LV_LOG_ERROR("ftruncate failed: %s", strerror(errno)); + goto err_out; + } + else + { + allocated_size = (buffer_hdl->size - allocator->shm_file_free_size); + } + + LV_ASSERT_MSG((allocated_size >= 0), "allocated_size is negative"); + } + + buffer_hdl->base = mmap(NULL, buffer_hdl->size, + PROT_READ | PROT_WRITE, MAP_SHARED, + allocator->shm_mem_fd, + allocator->shm_mem_size - allocator->shm_file_free_size); + if (buffer_hdl->base == MAP_FAILED) + { + LV_LOG_ERROR("mmap failed: %s", strerror(errno)); + goto err_inc_free; + } + + if (!allocator->shm_pool) + { + // Create SHM pool + allocator->shm_pool = wl_shm_create_pool(app->shm, + allocator->shm_mem_fd, + allocator->shm_mem_size + allocated_size); + if (!allocator->shm_pool) + { + LV_LOG_ERROR("cannot create shm pool"); + goto err_unmap; + } + } + else if (allocated_size > 0) + { + // Resize SHM pool + wl_shm_pool_resize(allocator->shm_pool, + allocator->shm_mem_size + allocated_size); + } + + // Create buffer + buffer_hdl->wl_buffer = wl_shm_pool_create_buffer(allocator->shm_pool, + allocator->shm_mem_size - allocator->shm_file_free_size, + width, height, + width * BYTES_PER_PIXEL, + app->format); + if (!buffer_hdl->wl_buffer) + { + LV_LOG_ERROR("cannot create shm buffer"); + goto err_unmap; + } + wl_buffer_add_listener(buffer_hdl->wl_buffer, &wl_buffer_listener, buffer_hdl); + + /* Update size of SHM */ + allocator->shm_mem_size += allocated_size; + allocator->shm_file_free_size = LV_MAX(0, (allocator->shm_file_free_size - buffer_hdl->size)); + + lv_memset_00(buffer_hdl->base, buffer_hdl->size); + + return true; + +err_unmap: + munmap(buffer_hdl->base, buffer_hdl->size); + +err_inc_free: + allocator->shm_file_free_size += allocated_size; + +err_out: + return false; +} + +static bool deinitialize_buffer(struct window *window, struct buffer_hdl *buffer_hdl) +{ + struct buffer_allocator *allocator = &window->allocator; + + if (buffer_hdl->wl_buffer) + { + wl_buffer_destroy(buffer_hdl->wl_buffer); + buffer_hdl->wl_buffer = NULL; + } + + if (buffer_hdl->size > 0) + { + munmap(buffer_hdl->base, buffer_hdl->size); + allocator->shm_file_free_size += buffer_hdl->size; + buffer_hdl->base = 0; + buffer_hdl->size = 0; + } + + return true; +} + +static struct graphic_object * create_graphic_obj(struct application *app, struct window *window, + enum object_type type, + struct graphic_object *parent) +{ + struct graphic_object *obj; + + obj = lv_mem_alloc(sizeof(*obj)); + LV_ASSERT_MALLOC(obj); + if (!obj) + { + return NULL; + } + + lv_memset(obj, 0x00, sizeof(struct graphic_object)); + + obj->window = window; + obj->type = type; + + obj->surface = wl_compositor_create_surface(app->compositor); + if (!obj->surface) + { + LV_LOG_ERROR("cannot create surface for graphic object"); + goto err_out; + } + + obj->surface_configured = true; + wl_surface_set_user_data(obj->surface, obj); + + return obj; + +err_destroy_surface: + wl_surface_destroy(obj->surface); + +err_free: + lv_mem_free(obj); + +err_out: + return NULL; +} + +static void destroy_graphic_obj(struct graphic_object * obj) +{ + if (obj->subsurface) + { + wl_subsurface_destroy(obj->subsurface); + } + + wl_surface_destroy(obj->surface); + + lv_mem_free(obj); +} + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS +static bool create_decoration(struct window *window, + struct graphic_object * decoration, + int window_width, int window_height) +{ + struct buffer_hdl * buffer = &decoration->buffer; + int x, y; + + switch (decoration->type) + { + case OBJECT_TITLEBAR: + decoration->width = window_width; + decoration->height = TITLE_BAR_HEIGHT; + break; + case OBJECT_BUTTON_CLOSE: + decoration->width = BUTTON_SIZE; + decoration->height = BUTTON_SIZE; + break; +#if LV_WAYLAND_XDG_SHELL + case OBJECT_BUTTON_MAXIMIZE: + decoration->width = BUTTON_SIZE; + decoration->height = BUTTON_SIZE; + break; + case OBJECT_BUTTON_MINIMIZE: + decoration->width = BUTTON_SIZE; + decoration->height = BUTTON_SIZE; + break; +#endif + case OBJECT_BORDER_TOP: + decoration->width = window_width + 2 * (BORDER_SIZE); + decoration->height = BORDER_SIZE; + break; + case OBJECT_BORDER_BOTTOM: + decoration->width = window_width + 2 * (BORDER_SIZE); + decoration->height = BORDER_SIZE; + break; + case OBJECT_BORDER_LEFT: + decoration->width = BORDER_SIZE; + decoration->height = window_height + TITLE_BAR_HEIGHT; + break; + case OBJECT_BORDER_RIGHT: + decoration->width = BORDER_SIZE; + decoration->height = window_height + TITLE_BAR_HEIGHT; + break; + default: + LV_ASSERT_MSG(0, "Invalid object type"); + return false; + } + + if (!initialize_buffer(window, buffer, decoration->width, decoration->height)) + { + LV_LOG_ERROR("cannot create buffer for decoration"); + return false; + } + + switch (decoration->type) + { + case OBJECT_TITLEBAR: + lv_color_fill((lv_color_t *)buffer->base, + lv_color_make(0x66, 0x66, 0x66), (decoration->width * decoration->height)); + break; + case OBJECT_BUTTON_CLOSE: + lv_color_fill((lv_color_t *)buffer->base, + lv_color_make(0xCC, 0xCC, 0xCC), (decoration->width * decoration->height)); + for (y = 0; y < decoration->height; y++) + { + for (x = 0; x < decoration->width; x++) + { + lv_color_t *pixel = ((lv_color_t *)buffer->base + (y * decoration->width) + x); + if ((x >= BUTTON_PADDING) && (x < decoration->width - BUTTON_PADDING)) + { + if ((x == y) || (x == decoration->width - 1 - y)) + { + *pixel = lv_color_make(0x33, 0x33, 0x33); + } + else if ((x == y - 1) || (x == decoration->width - y)) + { + *pixel = lv_color_make(0x66, 0x66, 0x66); + } + } + } + } + break; +#if LV_WAYLAND_XDG_SHELL + case OBJECT_BUTTON_MAXIMIZE: + lv_color_fill((lv_color_t *)buffer->base, + lv_color_make(0xCC, 0xCC, 0xCC), (decoration->width * decoration->height)); + for (y = 0; y < decoration->height; y++) + { + for (x = 0; x < decoration->width; x++) + { + lv_color_t *pixel = ((lv_color_t *)buffer->base + (y * decoration->width) + x); + if (((x == BUTTON_PADDING) && (y >= BUTTON_PADDING) && (y < decoration->height - BUTTON_PADDING)) || + ((x == (decoration->width - BUTTON_PADDING)) && (y >= BUTTON_PADDING) && (y <= decoration->height - BUTTON_PADDING)) || + ((y == BUTTON_PADDING) && (x >= BUTTON_PADDING) && (x < decoration->width - BUTTON_PADDING)) || + ((y == (BUTTON_PADDING + 1)) && (x >= BUTTON_PADDING) && (x < decoration->width - BUTTON_PADDING)) || + ((y == (decoration->height - BUTTON_PADDING)) && (x >= BUTTON_PADDING) && (x < decoration->width - BUTTON_PADDING))) + { + *pixel = lv_color_make(0x33, 0x33, 0x33); + } + } + } + break; + case OBJECT_BUTTON_MINIMIZE: + lv_color_fill((lv_color_t *)buffer->base, + lv_color_make(0xCC, 0xCC, 0xCC), (decoration->width * decoration->height)); + for (y = 0; y < decoration->height; y++) + { + for (x = 0; x < decoration->width; x++) + { + lv_color_t *pixel = ((lv_color_t *)buffer->base + (y * decoration->width) + x); + if ((x >= BUTTON_PADDING) && (x < decoration->width - BUTTON_PADDING) && + (y > decoration->height - (2 * BUTTON_PADDING)) && (y < decoration->height - BUTTON_PADDING)) + { + *pixel = lv_color_make(0x33, 0x33, 0x33); + } + } + } + break; +#endif + case OBJECT_BORDER_TOP: + /* fallthrough */ + case OBJECT_BORDER_BOTTOM: + /* fallthrough */ + case OBJECT_BORDER_LEFT: + /* fallthrough */ + case OBJECT_BORDER_RIGHT: + lv_color_fill((lv_color_t *)buffer->base, + lv_color_make(0x66, 0x66, 0x66), (decoration->width * decoration->height)); + break; + default: + LV_ASSERT_MSG(0, "Invalid object type"); + return false; + } + + return true; +} + +static bool attach_decoration(struct window *window, struct graphic_object * decoration, + struct graphic_object * parent) +{ + struct buffer_hdl * buffer = &decoration->buffer; + int pos_x, pos_y; + int x, y; + + switch (decoration->type) + { + case OBJECT_TITLEBAR: + pos_x = 0; + pos_y = -TITLE_BAR_HEIGHT; + break; + case OBJECT_BUTTON_CLOSE: + pos_x = parent->width - 1 * (BUTTON_MARGIN + BUTTON_SIZE); + pos_y = -1 * (BUTTON_MARGIN + BUTTON_SIZE + (BORDER_SIZE / 2)); + break; +#if LV_WAYLAND_XDG_SHELL + case OBJECT_BUTTON_MAXIMIZE: + pos_x = parent->width - 2 * (BUTTON_MARGIN + BUTTON_SIZE); + pos_y = -1 * (BUTTON_MARGIN + BUTTON_SIZE + (BORDER_SIZE / 2)); + break; + case OBJECT_BUTTON_MINIMIZE: + pos_x = parent->width - 3 * (BUTTON_MARGIN + BUTTON_SIZE); + pos_y = -1 * (BUTTON_MARGIN + BUTTON_SIZE + (BORDER_SIZE / 2)); + break; +#endif + case OBJECT_BORDER_TOP: + pos_x = -BORDER_SIZE; + pos_y = -(BORDER_SIZE + TITLE_BAR_HEIGHT); + break; + case OBJECT_BORDER_BOTTOM: + pos_x = -BORDER_SIZE; + pos_y = parent->height; + break; + case OBJECT_BORDER_LEFT: + pos_x = -BORDER_SIZE; + pos_y = -TITLE_BAR_HEIGHT; + break; + case OBJECT_BORDER_RIGHT: + pos_x = parent->width; + pos_y = -TITLE_BAR_HEIGHT; + break; + default: + LV_ASSERT_MSG(0, "Invalid object type"); + return false; + } + + decoration->subsurface = wl_subcompositor_get_subsurface(window->application->subcompositor, + decoration->surface, + parent->surface); + if (!decoration->subsurface) + { + LV_LOG_ERROR("cannot get subsurface for decoration"); + goto err_destroy_surface; + } + + wl_subsurface_set_desync(decoration->subsurface); + wl_subsurface_set_position(decoration->subsurface, pos_x, pos_y); + + wl_surface_attach(decoration->surface, buffer->wl_buffer, 0, 0); + wl_surface_commit(decoration->surface); + buffer->busy = true; + + return true; + +err_destroy_surface: + wl_surface_destroy(decoration->surface); + decoration->surface = NULL; + + return false; +} + +static void detach_decoration(struct window *window, + struct graphic_object * decoration) +{ + if (decoration->subsurface) + { + wl_subsurface_destroy(decoration->subsurface); + decoration->subsurface = NULL; + } +} +#endif + +static bool resize_window(struct window *window, int width, int height) +{ + struct buffer_hdl *buffer = &window->body->buffer; + + LV_LOG_TRACE("resize window %dx%d", width, height); + + // De-initialize previous buffers + if (buffer->busy) + { + LV_LOG_WARN("Deinitializing busy window buffer..."); + wl_surface_attach(window->body->surface, NULL, 0, 0); + wl_surface_commit(window->body->surface); + buffer->busy = false; + } + + if (!deinitialize_buffer(window, buffer)) + { + LV_LOG_ERROR("failed to deinitialize window buffer"); + return false; + } + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + int b; + for (b = 0; b < NUM_DECORATIONS; b++) + { + if (window->decoration[b] != NULL) + { + detach_decoration(window, window->decoration[b]); + deinitialize_buffer(window, &window->decoration[b]->buffer); + } + } +#endif + + // Initialize backing buffer + if (!initialize_buffer(window, buffer, width, height)) + { + LV_LOG_ERROR("failed to initialize window buffer"); + return false; + } + + window->width = width; + window->height = height; + + window->body->width = width; + window->body->height = height; + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + if (!window->application->opt_disable_decorations && !window->fullscreen) + { + for (b = 0; b < NUM_DECORATIONS; b++) + { + if (!create_decoration(window, window->decoration[b], + window->body->width, window->body->height)) + { + LV_LOG_ERROR("failed to create decoration %d", b); + } + else if (!attach_decoration(window, window->decoration[b], window->body)) + { + LV_LOG_ERROR("failed to attach decoration %d", b); + } + } + } +#endif + + if (window->lv_disp != NULL) + { + // Propagate resize to upper layers + window->lv_disp_drv.hor_res = width; + window->lv_disp_drv.ver_res = height; + lv_disp_drv_update(window->lv_disp, &window->lv_disp_drv); + + window->body->input.pointer.x = LV_MIN(window->body->input.pointer.x, (width - 1)); + window->body->input.pointer.y = LV_MIN(window->body->input.pointer.y, (height - 1)); + } + + return true; +} + +static struct window *create_window(struct application *app, int width, int height, const char *title) +{ + struct window *window; + + window = _lv_ll_ins_tail(&app->window_ll); + LV_ASSERT_MALLOC(window); + if (!window) + { + return NULL; + } + + lv_memset(window, 0x00, sizeof(struct window)); + + window->application = app; + + // Initialize buffer allocator + if (!initialize_allocator(&window->allocator, app->xdg_runtime_dir)) + { + LV_LOG_ERROR("cannot init memory allocator"); + goto err_free_window; + } + + // Create wayland buffer and surface + window->body = create_graphic_obj(app, window, OBJECT_WINDOW, NULL); + if (!window->body) + { + LV_LOG_ERROR("cannot create window body"); + goto err_deinit_allocator; + } + + // Create shell surface + if (0) + { + // Needed for #if madness below + } +#if LV_WAYLAND_XDG_SHELL + else if (app->xdg_wm) + { + window->xdg_surface = xdg_wm_base_get_xdg_surface(app->xdg_wm, window->body->surface); + if (!window->xdg_surface) + { + LV_LOG_ERROR("cannot create XDG surface"); + goto err_destroy_surface; + } + + xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window); + + window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); + if (!window->xdg_toplevel) + { + LV_LOG_ERROR("cannot get XDG toplevel surface"); + goto err_destroy_shell_surface; + } + + xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window); + xdg_toplevel_set_title(window->xdg_toplevel, title); + xdg_toplevel_set_app_id(window->xdg_toplevel, title); + + // XDG surfaces need to be configured before a buffer can be attached. + // An (XDG) surface commit (without an attached buffer) triggers this + // configure event + window->body->surface_configured = false; + wl_surface_commit(window->body->surface); + } +#endif +#if LV_WAYLAND_WL_SHELL + else if (app->wl_shell) + { + window->wl_shell_surface = wl_shell_get_shell_surface(app->wl_shell, window->body->surface); + if (!window->wl_shell_surface) + { + LV_LOG_ERROR("cannot create WL shell surface"); + goto err_destroy_surface; + } + + wl_shell_surface_add_listener(window->wl_shell_surface, &shell_surface_listener, window); + wl_shell_surface_set_toplevel(window->wl_shell_surface); + wl_shell_surface_set_title(window->wl_shell_surface, title); + } +#endif + else + { + LV_LOG_ERROR("No shell available"); + goto err_destroy_surface; + } + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + if (!app->opt_disable_decorations) + { + int d; + for (d = 0; d < NUM_DECORATIONS; d++) + { + window->decoration[d] = create_graphic_obj(app, window, (FIRST_DECORATION+d), window->body); + if (!window->decoration[d]) + { + LV_LOG_ERROR("Failed to create decoration %d", d); + } + } + } +#endif + + if (!resize_window(window, width, height)) + { + LV_LOG_ERROR("Failed to resize window"); + goto err_destroy_shell_surface2; + } + + return window; + +err_destroy_shell_surface2: +#if LV_WAYLAND_XDG_SHELL + if (window->xdg_toplevel) + { + xdg_toplevel_destroy(window->xdg_toplevel); + } +#endif + +err_destroy_shell_surface: +#if LV_WAYLAND_WL_SHELL + if (window->wl_shell_surface) + { + wl_shell_surface_destroy(window->wl_shell_surface); + } +#endif +#if LV_WAYLAND_XDG_SHELL + if (window->xdg_surface) + { + xdg_surface_destroy(window->xdg_surface); + } +#endif + +err_destroy_surface: + wl_surface_destroy(window->body->surface); + +err_deinit_allocator: + deinitialize_allocator(&window->allocator); + +err_free_window: + _lv_ll_remove(&app->window_ll, window); + lv_mem_free(window); + return NULL; +} + +static void destroy_window(struct window *window) +{ + if (!window) + { + return; + } + +#if LV_WAYLAND_WL_SHELL + if (window->wl_shell_surface) + { + wl_shell_surface_destroy(window->wl_shell_surface); + } +#endif +#if LV_WAYLAND_XDG_SHELL + if (window->xdg_toplevel) + { + xdg_toplevel_destroy(window->xdg_toplevel); + xdg_surface_destroy(window->xdg_surface); + } +#endif + +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + int b; + for (b = 0; b < NUM_DECORATIONS; b++) + { + if (window->decoration[b]) + { + deinitialize_buffer(window, &window->decoration[b]->buffer); + destroy_graphic_obj(window->decoration[b]); + window->decoration[b] = NULL; + } + } +#endif + + deinitialize_buffer(window, &window->body->buffer); + destroy_graphic_obj(window->body); + + deinitialize_allocator(&window->allocator); +} + +static void _lv_wayland_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) +{ + struct window *window = disp_drv->user_data; + struct buffer_hdl *buffer = &window->body->buffer; + + const lv_coord_t hres = (disp_drv->rotated == 0) ? (disp_drv->hor_res) : (disp_drv->ver_res); + const lv_coord_t vres = (disp_drv->rotated == 0) ? (disp_drv->ver_res) : (disp_drv->hor_res); + + /* If private data is not set, it means window has not been initialized */ + if (!window) + { + LV_LOG_ERROR("please intialize wayland display using lv_wayland_create_window()"); + return; + } + /* If window has been / is being closed, or is not visible, skip rendering */ + else if (window->closed || window->shall_close) + { + lv_disp_flush_ready(disp_drv); + return; + } + /* Return if the area is out the screen */ + else if ((area->x2 < 0) || (area->y2 < 0) || (area->x1 > hres - 1) || (area->y1 > vres - 1)) + { + lv_disp_flush_ready(disp_drv); + return; + } + else if (window->resize_pending) + { + LV_LOG_TRACE("skip flush since resize is pending"); + lv_disp_flush_ready(disp_drv); + return; + } + else if (buffer->busy) + { + LV_LOG_WARN("skip flush since wayland backing buffer is busy"); + lv_disp_flush_ready(disp_drv); + return; + } + + int32_t x; + int32_t y; + + for (y = area->y1; y <= area->y2 && y < disp_drv->ver_res; y++) + { + for (x = area->x1; x <= area->x2 && x < disp_drv->hor_res; x++) + { + int offset = (y * disp_drv->hor_res) + x; +#if (LV_COLOR_DEPTH == 32) + uint32_t * const buf = (uint32_t *)buffer->base + offset; + *buf = color_p->full; +#elif (LV_COLOR_DEPTH == 16) + uint16_t * const buf = (uint16_t *)buffer->base + offset; + *buf = color_p->full; +#elif (LV_COLOR_DEPTH == 8) + uint8_t * const buf = (uint8_t *)buffer->base + offset; + *buf = color_p->full; +#elif (LV_COLOR_DEPTH == 1) + uint8_t * const buf = (uint8_t *)buffer->base + offset; + *buf = ((0x07 * color_p->ch.red) << 5) | + ((0x07 * color_p->ch.green) << 2) | + ((0x03 * color_p->ch.blue) << 0); +#endif + color_p++; + } + } + + wl_surface_damage(window->body->surface, area->x1, area->y1, + (area->x2 - area->x1 + 1), (area->y2 - area->y1 + 1)); + + if (lv_disp_flush_is_last(disp_drv)) + { + if (window->body->surface_configured) { + wl_surface_attach(window->body->surface, buffer->wl_buffer, 0, 0); + wl_surface_commit(window->body->surface); + } + buffer->busy = true; + window->flush_pending = true; + } + + lv_disp_flush_ready(disp_drv); +} + +static void _lv_wayland_handle_input(void) +{ + while (wl_display_prepare_read(application.display) != 0) + { + wl_display_dispatch_pending(application.display); + } + + wl_display_read_events(application.display); + wl_display_dispatch_pending(application.display); +} + +static void _lv_wayland_handle_output(void) +{ + struct window *window; + bool shall_flush = application.cursor_flush_pending; + + _LV_LL_READ(&application.window_ll, window) + { + if ((window->shall_close) && (window->close_cb != NULL)) + { + window->shall_close = window->close_cb(window->lv_disp); + } + + if (window->closed) + { + continue; + } + else if (window->shall_close) + { + destroy_window(window); + window->closed = true; + window->shall_close = false; + shall_flush = true; + + window->body->input.touch.x = 0; + window->body->input.touch.y = 0; + window->body->input.touch.state = LV_INDEV_STATE_RELEASED; + if (window->application->touch_obj == window->body) + { + window->application->touch_obj = NULL; + } + + window->body->input.pointer.x = 0; + window->body->input.pointer.y = 0; + window->body->input.pointer.left_button = LV_INDEV_STATE_RELEASED; + window->body->input.pointer.right_button = LV_INDEV_STATE_RELEASED; + window->body->input.pointer.wheel_button = LV_INDEV_STATE_RELEASED; + window->body->input.pointer.wheel_diff = 0; + if (window->application->pointer_obj == window->body) + { + window->application->pointer_obj = NULL; + } + + window->body->input.keyboard.key = 0; + window->body->input.keyboard.state = LV_INDEV_STATE_RELEASED; + if (window->application->keyboard_obj == window->body) + { + window->application->keyboard_obj = NULL; + } + } + else if (window->resize_pending) + { + bool do_resize = !window->body->buffer.busy; +#if LV_WAYLAND_CLIENT_SIDE_DECORATIONS + if (!window->application->opt_disable_decorations && !window->fullscreen) + { + int d; + for (d = 0; d < NUM_DECORATIONS; d++) + { + if ((window->decoration[d] != NULL) && (window->decoration[d]->buffer.busy)) + { + do_resize = false; + break; + } + } + } +#endif + if (do_resize && resize_window(window, window->resize_width, window->resize_height)) + { + window->resize_width = window->width; + window->resize_height = window->height; + window->resize_pending = false; + shall_flush = true; + } + } + + shall_flush |= window->flush_pending; + } + + if (shall_flush) + { + if (wl_display_flush(application.display) == -1) + { + if (errno != EAGAIN) + { + LV_LOG_ERROR("failed to flush wayland display"); + } + } + else + { + /* All data flushed */ + application.cursor_flush_pending = false; + _LV_LL_READ(&application.window_ll, window) + { + window->flush_pending = false; + } + } + } +} + +static void _lv_wayland_cycle(lv_timer_t * tmr) +{ + LV_UNUSED(tmr); + _lv_wayland_handle_input(); + _lv_wayland_handle_output(); +} + +static void _lv_wayland_pointer_read(lv_indev_drv_t *drv, lv_indev_data_t *data) +{ + struct window *window = drv->disp->driver->user_data; + if (!window || window->closed) + { + return; + } + + data->point.x = window->body->input.pointer.x; + data->point.y = window->body->input.pointer.y; + data->state = window->body->input.pointer.left_button; +} + +static void _lv_wayland_pointeraxis_read(lv_indev_drv_t *drv, lv_indev_data_t *data) +{ + struct window *window = drv->disp->driver->user_data; + if (!window || window->closed) + { + return; + } + + data->state = window->body->input.pointer.wheel_button; + data->enc_diff = window->body->input.pointer.wheel_diff; + + window->body->input.pointer.wheel_diff = 0; +} + +static void _lv_wayland_keyboard_read(lv_indev_drv_t *drv, lv_indev_data_t *data) +{ + struct window *window = drv->disp->driver->user_data; + if (!window || window->closed) + { + return; + } + + data->key = window->body->input.keyboard.key; + data->state = window->body->input.keyboard.state; +} + +static void _lv_wayland_touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data) +{ + struct window *window = drv->disp->driver->user_data; + if (!window || window->closed) + { + return; + } + + data->point.x = window->body->input.touch.x; + data->point.y = window->body->input.touch.y; + data->state = window->body->input.touch.state; +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Initialize Wayland driver + */ +void lv_wayland_init(void) +{ + application.xdg_runtime_dir = getenv("XDG_RUNTIME_DIR"); + LV_ASSERT_MSG(application.xdg_runtime_dir, "cannot get XDG_RUNTIME_DIR"); + + // Create XKB context + application.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + LV_ASSERT_MSG(application.xkb_context, "failed to create XKB context"); + if (application.xkb_context == NULL) + { + return; + } + + // Connect to Wayland display + application.display = wl_display_connect(NULL); + LV_ASSERT_MSG(application.display, "failed to connect to Wayland server"); + if (application.display == NULL) + { + return; + } + + /* Add registry listener and wait for registry reception */ + application.format = 0xFFFFFFFF; + application.registry = wl_display_get_registry(application.display); + wl_registry_add_listener(application.registry, ®istry_listener, &application); + wl_display_dispatch(application.display); + wl_display_roundtrip(application.display); + + LV_ASSERT_MSG(application.compositor, "Wayland compositor not available"); + if (application.compositor == NULL) + { + return; + } + + LV_ASSERT_MSG(application.shm, "Wayland SHM not available"); + if (application.shm == NULL) + { + return; + } + + LV_ASSERT_MSG((application.format != 0xFFFFFFFF), "WL_SHM_FORMAT not available"); + if (application.format == 0xFFFFFFFF) + { + return; + } + +#ifdef LV_WAYLAND_CLIENT_SIDE_DECORATIONS + const char * env_disable_decorations = getenv("LV_WAYLAND_DISABLE_WINDOWDECORATION"); + application.opt_disable_decorations = ((env_disable_decorations != NULL) && + (env_disable_decorations[0] != '0')); +#endif + + _lv_ll_init(&application.window_ll, sizeof(struct window)); + + application.cycle_timer = lv_timer_create(_lv_wayland_cycle, LV_WAYLAND_CYCLE_PERIOD, NULL); + LV_ASSERT_MSG(application.cycle_timer, "failed to create cycle timer"); + if (!application.cycle_timer) + { + return; + } +} + +/** + * De-initialize Wayland driver + */ +void lv_wayland_deinit(void) +{ + struct window *window = NULL; + + _LV_LL_READ(&application.window_ll, window) + { + if (!window->closed) + { + destroy_window(window); + } + } + + if (application.shm) + { + wl_shm_destroy(application.shm); + } + +#if LV_WAYLAND_XDG_SHELL + if (application.xdg_wm) + { + xdg_wm_base_destroy(application.xdg_wm); + } +#endif + +#if LV_WAYLAND_WL_SHELL + if (application.wl_shell) + { + wl_shell_destroy(application.wl_shell); + } +#endif + + if (application.wl_seat) + { + wl_seat_destroy(application.wl_seat); + } + + if (application.subcompositor) + { + wl_subcompositor_destroy(application.subcompositor); + } + + if (application.compositor) + { + wl_compositor_destroy(application.compositor); + } + + wl_registry_destroy(application.registry); + wl_display_flush(application.display); + wl_display_disconnect(application.display); + + _lv_ll_clear(&application.window_ll); +} + +/** + * Get Wayland display file descriptor + * @return Wayland display file descriptor + */ +int lv_wayland_get_fd(void) +{ + return wl_display_get_fd(application.display); +} + +/** + * Create wayland window + * @param hor_res initial horizontal window size in pixels + * @param ver_res initial vertical window size in pixels + * @param title window title + * @param close_cb function to be called when the window gets closed by the user (optional) + * @return new display backed by a Wayland window, or NULL on error + */ +lv_disp_t * lv_wayland_create_window(lv_coord_t hor_res, lv_coord_t ver_res, char *title, + lv_wayland_display_close_f_t close_cb) +{ + lv_color_t * buf1 = NULL; + struct window *window; + + window = create_window(&application, hor_res, ver_res, title); + if (!window) + { + LV_LOG_ERROR("failed to create wayland window"); + return NULL; + } + + window->close_cb = close_cb; + + /* Initialize draw buffer */ + buf1 = lv_mem_alloc(hor_res * ver_res * sizeof(lv_color_t)); + if (!buf1) + { + LV_LOG_ERROR("failed to allocate draw buffer"); + destroy_window(window); + return NULL; + } + + lv_disp_draw_buf_init(&window->lv_disp_draw_buf, buf1, NULL, hor_res * ver_res); + + /* Initialize display driver */ + lv_disp_drv_init(&window->lv_disp_drv); + window->lv_disp_drv.draw_buf = &window->lv_disp_draw_buf; + window->lv_disp_drv.hor_res = hor_res; + window->lv_disp_drv.ver_res = ver_res; + window->lv_disp_drv.flush_cb = _lv_wayland_flush; + window->lv_disp_drv.user_data = window; + + /* Register display */ + window->lv_disp = lv_disp_drv_register(&window->lv_disp_drv); + + /* Register input */ + lv_indev_drv_init(&window->lv_indev_drv_pointer); + window->lv_indev_drv_pointer.type = LV_INDEV_TYPE_POINTER; + window->lv_indev_drv_pointer.read_cb = _lv_wayland_pointer_read; + window->lv_indev_drv_pointer.disp = window->lv_disp; + window->lv_indev_pointer = lv_indev_drv_register(&window->lv_indev_drv_pointer); + if (!window->lv_indev_pointer) + { + LV_LOG_ERROR("failed to register pointer indev"); + } + + lv_indev_drv_init(&window->lv_indev_drv_pointeraxis); + window->lv_indev_drv_pointeraxis.type = LV_INDEV_TYPE_ENCODER; + window->lv_indev_drv_pointeraxis.read_cb = _lv_wayland_pointeraxis_read; + window->lv_indev_drv_pointeraxis.disp = window->lv_disp; + window->lv_indev_pointeraxis = lv_indev_drv_register(&window->lv_indev_drv_pointeraxis); + if (!window->lv_indev_pointeraxis) + { + LV_LOG_ERROR("failed to register pointeraxis indev"); + } + + lv_indev_drv_init(&window->lv_indev_drv_touch); + window->lv_indev_drv_touch.type = LV_INDEV_TYPE_POINTER; + window->lv_indev_drv_touch.read_cb = _lv_wayland_touch_read; + window->lv_indev_drv_touch.disp = window->lv_disp; + window->lv_indev_touch = lv_indev_drv_register(&window->lv_indev_drv_touch); + if (!window->lv_indev_touch) + { + LV_LOG_ERROR("failed to register touch indev"); + } + + lv_indev_drv_init(&window->lv_indev_drv_keyboard); + window->lv_indev_drv_keyboard.type = LV_INDEV_TYPE_KEYPAD; + window->lv_indev_drv_keyboard.read_cb = _lv_wayland_keyboard_read; + window->lv_indev_drv_keyboard.disp = window->lv_disp; + window->lv_indev_keyboard = lv_indev_drv_register(&window->lv_indev_drv_keyboard); + if (!window->lv_indev_keyboard) + { + LV_LOG_ERROR("failed to register keyboard indev"); + } + + return window->lv_disp; +} + +/** + * Close wayland window + * @param disp LVGL display using window to be closed + */ +void lv_wayland_close_window(lv_disp_t * disp) +{ + struct window *window = disp->driver->user_data; + if (!window || window->closed) + { + return; + } + window->shall_close = true; + window->close_cb = NULL; +} + +/** + * Check if a Wayland window is open on the specified display. Otherwise (if + * argument is NULL), check if any Wayland window is open. + * @return true if window open, false otherwise + */ +bool lv_wayland_window_is_open(lv_disp_t * disp) +{ + struct window *window; + bool open = false; + + if (disp == NULL) + { + _LV_LL_READ(&application.window_ll, window) + { + if (!window->closed) + { + open = true; + break; + } + } + } + else + { + window = disp->driver->user_data; + open = (!window->closed); + } + + return open; +} + +/** + * Check if a Wayland flush is outstanding (i.e. data still needs to be sent to + * the compositor, but the compositor pipe/connection is unable to take more + * data at this time) for a window on the specified display. Otherwise (if + * argument is NULL), check if any window flush is outstanding. + * @return true if a flush is outstanding, false otherwise + */ +bool lv_wayland_window_is_flush_pending(lv_disp_t * disp) +{ + struct window *window; + bool flush_pending = false; + + if (disp == NULL) + { + _LV_LL_READ(&application.window_ll, window) + { + if (window->flush_pending) + { + flush_pending = true; + break; + } + } + } + else + { + window = disp->driver->user_data; + flush_pending = window->flush_pending; + } + + return flush_pending; +} + +/** + * Set/unset window fullscreen mode + * @param disp LVGL display using window to be set/unset fullscreen + * @param fullscreen requested status (true = fullscreen) + */ +void lv_wayland_window_set_fullscreen(lv_disp_t * disp, bool fullscreen) +{ + struct window *window = disp->driver->user_data; + if (!window || window->closed) + { + return; + } + + if (window->fullscreen != fullscreen) + { + if (0) + { + // Needed for #if madness below + } +#if LV_WAYLAND_XDG_SHELL + else if (window->xdg_toplevel) + { + if (fullscreen) + { + xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); + } + else + { + xdg_toplevel_unset_fullscreen(window->xdg_toplevel); + } + window->fullscreen = fullscreen; + window->flush_pending = true; + } +#endif +#if LV_WAYLAND_WL_SHELL + else if (window->wl_shell_surface) + { + if (fullscreen) + { + wl_shell_surface_set_fullscreen(window->wl_shell_surface, + WL_SHELL_SURFACE_FULLSCREEN_METHOD_SCALE, + 0, NULL); + } + else + { + wl_shell_surface_set_toplevel(window->wl_shell_surface); + } + window->fullscreen = fullscreen; + window->flush_pending = true; + } +#endif + else + { + LV_LOG_WARN("Wayland fullscreen mode not supported"); + } + } +} + +/** + * Get pointer input device for given LVGL display + * @param disp LVGL display + * @return input device connected to pointer events, or NULL on error + */ +lv_indev_t * lv_wayland_get_pointer(lv_disp_t * disp) +{ + struct window *window = disp->driver->user_data; + if (!window) + { + return NULL; + } + return window->lv_indev_pointer; +} + +/** + * Get pointer axis input device for given LVGL display + * @param disp LVGL display + * @return input device connected to pointer axis events, or NULL on error + */ +lv_indev_t * lv_wayland_get_pointeraxis(lv_disp_t * disp) +{ + struct window *window = disp->driver->user_data; + if (!window) + { + return NULL; + } + return window->lv_indev_pointeraxis; +} + +/** + * Get keyboard input device for given LVGL display + * @param disp LVGL display + * @return input device connected to keyboard, or NULL on error + */ +lv_indev_t * lv_wayland_get_keyboard(lv_disp_t * disp) +{ + struct window *window = disp->driver->user_data; + if (!window) + { + return NULL; + } + return window->lv_indev_keyboard; +} + +/** + * Get touchscreen input device for given LVGL display + * @param disp LVGL display + * @return input device connected to touchscreen, or NULL on error + */ +lv_indev_t * lv_wayland_get_touchscreen(lv_disp_t * disp) +{ + struct window *window = disp->driver->user_data; + if (!window) + { + return NULL; + } + return window->lv_indev_touch; +} + +#ifdef LV_WAYLAND_TIMER_HANDLER +/** + * Wayland specific timer handler (use in place of LVGL lv_timer_handler) + * @return time until next timer expiry in milliseconds + */ +uint32_t lv_wayland_timer_handler(void) +{ + int i; + struct window *window; + lv_timer_t *input_timer[4]; + uint32_t time_till_next; + + /* Remove cycle timer (as this function is doing its work) */ + if (application.cycle_timer != NULL) + { + lv_timer_del(application.cycle_timer); + application.cycle_timer = NULL; + } + + /* Wayland input handling */ + _lv_wayland_handle_input(); + + /* Ready input timers (to probe for any input recieved) */ + _LV_LL_READ(&application.window_ll, window) + { + input_timer[0] = window->lv_indev_pointer->driver->read_timer; + input_timer[1] = window->lv_indev_pointeraxis->driver->read_timer; + input_timer[2] = window->lv_indev_keyboard->driver->read_timer; + input_timer[3] = window->lv_indev_touch->driver->read_timer; + + for (i = 0; i < 4; i++) + { + if (input_timer[i]) + { + lv_timer_ready(input_timer[i]); + } + } + } + + /* LVGL handling */ + time_till_next = lv_timer_handler(); + + /* Wayland output handling */ + _lv_wayland_handle_output(); + + return time_till_next; +} +#endif +#endif // USE_WAYLAND diff --git a/libs/lv_drivers/wayland/wayland.h b/libs/lv_drivers/wayland/wayland.h new file mode 100644 index 00000000..578af472 --- /dev/null +++ b/libs/lv_drivers/wayland/wayland.h @@ -0,0 +1,77 @@ +/** + * @file wayland + * + */ + +#ifndef WAYLAND_H +#define WAYLAND_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_WAYLAND + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#if LV_USE_USER_DATA == 0 +#error "Support for user data is required by wayland driver. Set LV_USE_USER_DATA to 1 in lv_conf.h" +#endif + + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef bool (*lv_wayland_display_close_f_t)(lv_disp_t * disp); + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_wayland_init(void); +void lv_wayland_deinit(void); +int lv_wayland_get_fd(void); +lv_disp_t * lv_wayland_create_window(lv_coord_t hor_res, lv_coord_t ver_res, char *title, + lv_wayland_display_close_f_t close_cb); +void lv_wayland_close_window(lv_disp_t * disp); +bool lv_wayland_window_is_open(lv_disp_t * disp); +bool lv_wayland_window_is_flush_pending(lv_disp_t * disp); +void lv_wayland_window_set_fullscreen(lv_disp_t * disp, bool fullscreen); +lv_indev_t * lv_wayland_get_pointer(lv_disp_t * disp); +lv_indev_t * lv_wayland_get_pointeraxis(lv_disp_t * disp); +lv_indev_t * lv_wayland_get_keyboard(lv_disp_t * disp); +lv_indev_t * lv_wayland_get_touchscreen(lv_disp_t * disp); +#ifdef LV_WAYLAND_TIMER_HANDLER +uint32_t lv_wayland_timer_handler(void); +#endif + +/********************** + * MACROS + **********************/ + +#endif /* USE_WAYLAND */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* WAYLAND_H */ diff --git a/libs/lv_drivers/win32drv/win32drv.c b/libs/lv_drivers/win32drv/win32drv.c new file mode 100644 index 00000000..7a804348 --- /dev/null +++ b/libs/lv_drivers/win32drv/win32drv.c @@ -0,0 +1,1054 @@ +/** + * @file win32drv.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "win32drv.h" + +#if USE_WIN32DRV + +#include +#include +#include +#include + +/********************* + * DEFINES + *********************/ + +#define WINDOW_EX_STYLE \ + WS_EX_CLIENTEDGE + +#define WINDOW_STYLE \ + (WS_OVERLAPPEDWINDOW & ~(WS_SIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME)) + +#ifndef WIN32DRV_MONITOR_ZOOM +#define WIN32DRV_MONITOR_ZOOM 1 +#endif + +#ifndef USER_DEFAULT_SCREEN_DPI +#define USER_DEFAULT_SCREEN_DPI 96 +#endif + +/********************** + * TYPEDEFS + **********************/ + +typedef struct _WINDOW_THREAD_PARAMETER +{ + HANDLE window_mutex; + HINSTANCE instance_handle; + HICON icon_handle; + lv_coord_t hor_res; + lv_coord_t ver_res; + int show_window_mode; +} WINDOW_THREAD_PARAMETER, * PWINDOW_THREAD_PARAMETER; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/** + * @brief Creates a B8G8R8A8 frame buffer. + * @param WindowHandle A handle to the window for the creation of the frame + * buffer. If this value is NULL, the entire screen will be + * referenced. + * @param Width The width of the frame buffer. + * @param Height The height of the frame buffer. + * @param PixelBuffer The raw pixel buffer of the frame buffer you created. + * @param PixelBufferSize The size of the frame buffer you created. + * @return If the function succeeds, the return value is a handle to the device + * context (DC) for the frame buffer. If the function fails, the return + * value is NULL, and PixelBuffer parameter is NULL. +*/ +static HDC lv_win32_create_frame_buffer( + _In_opt_ HWND WindowHandle, + _In_ LONG Width, + _In_ LONG Height, + _Out_ UINT32** PixelBuffer, + _Out_ SIZE_T* PixelBufferSize); + +/** + * @brief Enables WM_DPICHANGED message for child window for the associated + * window. + * @param WindowHandle The window you want to enable WM_DPICHANGED message for + * child window. + * @return If the function succeeds, the return value is non-zero. If the + * function fails, the return value is zero. + * @remarks You need to use this function in Windows 10 Threshold 1 or Windows + * 10 Threshold 2. +*/ +static BOOL lv_win32_enable_child_window_dpi_message( + _In_ HWND WindowHandle); + +/** + * @brief Registers a window as being touch-capable. + * @param hWnd The handle of the window being registered. + * @param ulFlags A set of bit flags that specify optional modifications. + * @return If the function succeeds, the return value is nonzero. If the + * function fails, the return value is zero. + * @remark For more information, see RegisterTouchWindow. +*/ +static BOOL lv_win32_register_touch_window( + HWND hWnd, + ULONG ulFlags); + +/** + * @brief Retrieves detailed information about touch inputs associated with a + * particular touch input handle. + * @param hTouchInput The touch input handle received in the LPARAM of a touch + * message. + * @param cInputs The number of structures in the pInputs array. + * @param pInputs A pointer to an array of TOUCHINPUT structures to receive + * information about the touch points associated with the + * specified touch input handle. + * @param cbSize The size, in bytes, of a single TOUCHINPUT structure. + * @return If the function succeeds, the return value is nonzero. If the + * function fails, the return value is zero. + * @remark For more information, see GetTouchInputInfo. +*/ +static BOOL lv_win32_get_touch_input_info( + HTOUCHINPUT hTouchInput, + UINT cInputs, + PTOUCHINPUT pInputs, + int cbSize); + +/** + * @brief Closes a touch input handle, frees process memory associated with it, + and invalidates the handle. + * @param hTouchInput The touch input handle received in the LPARAM of a touch + * message. + * @return If the function succeeds, the return value is nonzero. If the + * function fails, the return value is zero. + * @remark For more information, see CloseTouchInputHandle. +*/ +static BOOL lv_win32_close_touch_input_handle( + HTOUCHINPUT hTouchInput); + +/** + * @brief Returns the dots per inch (dpi) value for the associated window. + * @param WindowHandle The window you want to get information about. + * @return The DPI for the window. +*/ +static UINT lv_win32_get_dpi_for_window( + _In_ HWND WindowHandle); + +static void lv_win32_display_driver_flush_callback( + lv_disp_drv_t* disp_drv, + const lv_area_t* area, + lv_color_t* color_p); + +static void lv_win32_display_refresh_handler( + lv_timer_t* param); + +static void lv_win32_pointer_driver_read_callback( + lv_indev_drv_t* indev_drv, + lv_indev_data_t* data); + +static void lv_win32_keypad_driver_read_callback( + lv_indev_drv_t* indev_drv, + lv_indev_data_t* data); + +static void lv_win32_encoder_driver_read_callback( + lv_indev_drv_t* indev_drv, + lv_indev_data_t* data); + +static LRESULT CALLBACK lv_win32_window_message_callback( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam); + +static unsigned int __stdcall lv_win32_window_thread_entrypoint( + void* raw_parameter); + +/********************** + * GLOBAL VARIABLES + **********************/ + +EXTERN_C bool lv_win32_quit_signal = false; + +EXTERN_C lv_indev_t* lv_win32_pointer_device_object = NULL; +EXTERN_C lv_indev_t* lv_win32_keypad_device_object = NULL; +EXTERN_C lv_indev_t* lv_win32_encoder_device_object = NULL; + +/********************** + * STATIC VARIABLES + **********************/ + +static HWND g_window_handle = NULL; + +static HDC g_buffer_dc_handle = NULL; +static UINT32* g_pixel_buffer = NULL; +static SIZE_T g_pixel_buffer_size = 0; + +static lv_disp_t* g_display = NULL; +static bool volatile g_display_refreshing = false; + +static bool volatile g_mouse_pressed = false; +static LPARAM volatile g_mouse_value = 0; + +static bool volatile g_mousewheel_pressed = false; +static int16_t volatile g_mousewheel_value = 0; + +static bool volatile g_keyboard_pressed = false; +static WPARAM volatile g_keyboard_value = 0; + +static int volatile g_dpi_value = USER_DEFAULT_SCREEN_DPI; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +EXTERN_C void lv_win32_add_all_input_devices_to_group( + lv_group_t* group) +{ + if (!group) + { + LV_LOG_WARN( + "The group object is NULL. Get the default group object instead."); + + group = lv_group_get_default(); + if (!group) + { + LV_LOG_WARN( + "The default group object is NULL. Create a new group object " + "and set it to default instead."); + + group = lv_group_create(); + if (group) + { + lv_group_set_default(group); + } + } + } + + LV_ASSERT_MSG(group, "Cannot obtain an available group object."); + + lv_indev_set_group(lv_win32_pointer_device_object, group); + lv_indev_set_group(lv_win32_keypad_device_object, group); + lv_indev_set_group(lv_win32_encoder_device_object, group); +} + +EXTERN_C bool lv_win32_init( + HINSTANCE instance_handle, + int show_window_mode, + lv_coord_t hor_res, + lv_coord_t ver_res, + HICON icon_handle) +{ + PWINDOW_THREAD_PARAMETER parameter = + (PWINDOW_THREAD_PARAMETER)malloc(sizeof(WINDOW_THREAD_PARAMETER)); + parameter->window_mutex = CreateEventExW(NULL, NULL, 0, EVENT_ALL_ACCESS); + parameter->instance_handle = instance_handle; + parameter->icon_handle = icon_handle; + parameter->hor_res = hor_res; + parameter->ver_res = ver_res; + parameter->show_window_mode = show_window_mode; + + _beginthreadex( + NULL, + 0, + lv_win32_window_thread_entrypoint, + parameter, + 0, + NULL); + + WaitForSingleObjectEx(parameter->window_mutex, INFINITE, FALSE); + + static lv_disp_draw_buf_t display_buffer; +#if (LV_COLOR_DEPTH == 32) || \ + (LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP == 0) || \ + (LV_COLOR_DEPTH == 8) || \ + (LV_COLOR_DEPTH == 1) + lv_disp_draw_buf_init( + &display_buffer, + (lv_color_t*)g_pixel_buffer, + NULL, + hor_res * ver_res); +#else + lv_disp_draw_buf_init( + &display_buffer, + (lv_color_t*)malloc(hor_res * ver_res * sizeof(lv_color_t)), + NULL, + hor_res * ver_res); +#endif + + static lv_disp_drv_t display_driver; + lv_disp_drv_init(&display_driver); + display_driver.hor_res = hor_res; + display_driver.ver_res = ver_res; + display_driver.flush_cb = lv_win32_display_driver_flush_callback; + display_driver.draw_buf = &display_buffer; + display_driver.direct_mode = 1; + g_display = lv_disp_drv_register(&display_driver); + lv_timer_del(g_display->refr_timer); + g_display->refr_timer = NULL; + lv_timer_create(lv_win32_display_refresh_handler, 0, NULL); + + static lv_indev_drv_t pointer_driver; + lv_indev_drv_init(&pointer_driver); + pointer_driver.type = LV_INDEV_TYPE_POINTER; + pointer_driver.read_cb = lv_win32_pointer_driver_read_callback; + lv_win32_pointer_device_object = lv_indev_drv_register(&pointer_driver); + + static lv_indev_drv_t keypad_driver; + lv_indev_drv_init(&keypad_driver); + keypad_driver.type = LV_INDEV_TYPE_KEYPAD; + keypad_driver.read_cb = lv_win32_keypad_driver_read_callback; + lv_win32_keypad_device_object = lv_indev_drv_register(&keypad_driver); + + static lv_indev_drv_t encoder_driver; + lv_indev_drv_init(&encoder_driver); + encoder_driver.type = LV_INDEV_TYPE_ENCODER; + encoder_driver.read_cb = lv_win32_encoder_driver_read_callback; + lv_win32_encoder_device_object = lv_indev_drv_register(&encoder_driver); + + return true; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static HDC lv_win32_create_frame_buffer( + HWND WindowHandle, + LONG Width, + LONG Height, + UINT32** PixelBuffer, + SIZE_T* PixelBufferSize) +{ + HDC hFrameBufferDC = NULL; + + if (PixelBuffer && PixelBufferSize) + { + HDC hWindowDC = GetDC(WindowHandle); + if (hWindowDC) + { + hFrameBufferDC = CreateCompatibleDC(hWindowDC); + ReleaseDC(WindowHandle, hWindowDC); + } + + if (hFrameBufferDC) + { +#if LV_COLOR_DEPTH == 32 + BITMAPINFO BitmapInfo = { 0 }; +#elif LV_COLOR_DEPTH == 16 + typedef struct _BITMAPINFO_16BPP { + BITMAPINFOHEADER bmiHeader; + DWORD bmiColorMask[3]; + } BITMAPINFO_16BPP, *PBITMAPINFO_16BPP; + + BITMAPINFO_16BPP BitmapInfo = { 0 }; +#elif LV_COLOR_DEPTH == 8 + typedef struct _BITMAPINFO_8BPP { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[256]; + } BITMAPINFO_8BPP, *PBITMAPINFO_8BPP; + + BITMAPINFO_8BPP BitmapInfo = { 0 }; +#elif LV_COLOR_DEPTH == 1 + typedef struct _BITMAPINFO_1BPP { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[2]; + } BITMAPINFO_1BPP, *PBITMAPINFO_1BPP; + + BITMAPINFO_1BPP BitmapInfo = { 0 }; +#else + BITMAPINFO BitmapInfo = { 0 }; +#endif + + BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + BitmapInfo.bmiHeader.biWidth = Width; + BitmapInfo.bmiHeader.biHeight = -Height; + BitmapInfo.bmiHeader.biPlanes = 1; +#if LV_COLOR_DEPTH == 32 + BitmapInfo.bmiHeader.biBitCount = 32; + BitmapInfo.bmiHeader.biCompression = BI_RGB; +#elif LV_COLOR_DEPTH == 16 + BitmapInfo.bmiHeader.biBitCount = 16; + BitmapInfo.bmiHeader.biCompression = BI_BITFIELDS; + BitmapInfo.bmiColorMask[0] = 0xF800; + BitmapInfo.bmiColorMask[1] = 0x07E0; + BitmapInfo.bmiColorMask[2] = 0x001F; +#elif LV_COLOR_DEPTH == 8 + BitmapInfo.bmiHeader.biBitCount = 8; + BitmapInfo.bmiHeader.biCompression = BI_RGB; + for (size_t i = 0; i < 256; ++i) + { + lv_color8_t color; + color.full = i; + + BitmapInfo.bmiColors[i].rgbRed = LV_COLOR_GET_R(color) * 36; + BitmapInfo.bmiColors[i].rgbGreen = LV_COLOR_GET_G(color) * 36; + BitmapInfo.bmiColors[i].rgbBlue = LV_COLOR_GET_B(color) * 85; + BitmapInfo.bmiColors[i].rgbReserved = 0xFF; + } +#elif LV_COLOR_DEPTH == 1 + BitmapInfo.bmiHeader.biBitCount = 8; + BitmapInfo.bmiHeader.biCompression = BI_RGB; + BitmapInfo.bmiHeader.biClrUsed = 2; + BitmapInfo.bmiHeader.biClrImportant = 2; + BitmapInfo.bmiColors[0].rgbRed = 0x00; + BitmapInfo.bmiColors[0].rgbGreen = 0x00; + BitmapInfo.bmiColors[0].rgbBlue = 0x00; + BitmapInfo.bmiColors[0].rgbReserved = 0xFF; + BitmapInfo.bmiColors[1].rgbRed = 0xFF; + BitmapInfo.bmiColors[1].rgbGreen = 0xFF; + BitmapInfo.bmiColors[1].rgbBlue = 0xFF; + BitmapInfo.bmiColors[1].rgbReserved = 0xFF; +#else + BitmapInfo.bmiHeader.biBitCount = 32; + BitmapInfo.bmiHeader.biCompression = BI_RGB; +#endif + + HBITMAP hBitmap = CreateDIBSection( + hFrameBufferDC, + (PBITMAPINFO)(&BitmapInfo), + DIB_RGB_COLORS, + (void**)PixelBuffer, + NULL, + 0); + if (hBitmap) + { +#if LV_COLOR_DEPTH == 32 + *PixelBufferSize = Width * Height * sizeof(UINT32); +#elif LV_COLOR_DEPTH == 16 + *PixelBufferSize = Width * Height * sizeof(UINT16); +#elif LV_COLOR_DEPTH == 8 + *PixelBufferSize = Width * Height * sizeof(UINT8); +#elif LV_COLOR_DEPTH == 1 + *PixelBufferSize = Width * Height * sizeof(UINT8); +#else + *PixelBufferSize = Width * Height * sizeof(UINT32); +#endif + + DeleteObject(SelectObject(hFrameBufferDC, hBitmap)); + DeleteObject(hBitmap); + } + else + { + DeleteDC(hFrameBufferDC); + hFrameBufferDC = NULL; + } + } + } + + return hFrameBufferDC; +} + +static BOOL lv_win32_enable_child_window_dpi_message( + HWND WindowHandle) +{ + // This hack is only for Windows 10 TH1/TH2 only. + // We don't need this hack if the Per Monitor Aware V2 is existed. + OSVERSIONINFOEXW OSVersionInfoEx = { 0 }; + OSVersionInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); + OSVersionInfoEx.dwMajorVersion = 10; + OSVersionInfoEx.dwMinorVersion = 0; + OSVersionInfoEx.dwBuildNumber = 14393; + if (!VerifyVersionInfoW( + &OSVersionInfoEx, + VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, + VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, + VER_MAJORVERSION, + VER_GREATER_EQUAL), + VER_MINORVERSION, + VER_GREATER_EQUAL), + VER_BUILDNUMBER, + VER_LESS))) + { + return FALSE; + } + + HMODULE ModuleHandle = GetModuleHandleW(L"user32.dll"); + if (!ModuleHandle) + { + return FALSE; + } + + typedef BOOL(WINAPI* FunctionType)(HWND, BOOL); + + FunctionType pFunction = (FunctionType)( + GetProcAddress(ModuleHandle, "EnableChildWindowDpiMessage")); + if (!pFunction) + { + return FALSE; + } + + return pFunction(WindowHandle, TRUE); +} + +static BOOL lv_win32_register_touch_window( + HWND hWnd, + ULONG ulFlags) +{ + HMODULE ModuleHandle = GetModuleHandleW(L"user32.dll"); + if (!ModuleHandle) + { + return FALSE; + } + + typedef BOOL(WINAPI* FunctionType)(HWND, ULONG); + + FunctionType pFunction = (FunctionType)( + GetProcAddress(ModuleHandle, "RegisterTouchWindow")); + if (!pFunction) + { + return FALSE; + } + + return pFunction(hWnd, ulFlags); +} + +static BOOL lv_win32_get_touch_input_info( + HTOUCHINPUT hTouchInput, + UINT cInputs, + PTOUCHINPUT pInputs, + int cbSize) +{ + HMODULE ModuleHandle = GetModuleHandleW(L"user32.dll"); + if (!ModuleHandle) + { + return FALSE; + } + + typedef BOOL(WINAPI* FunctionType)(HTOUCHINPUT, UINT, PTOUCHINPUT, int); + + FunctionType pFunction = (FunctionType)( + GetProcAddress(ModuleHandle, "GetTouchInputInfo")); + if (!pFunction) + { + return FALSE; + } + + return pFunction(hTouchInput, cInputs, pInputs, cbSize); +} + +static BOOL lv_win32_close_touch_input_handle( + HTOUCHINPUT hTouchInput) +{ + HMODULE ModuleHandle = GetModuleHandleW(L"user32.dll"); + if (!ModuleHandle) + { + return FALSE; + } + + typedef BOOL(WINAPI* FunctionType)(HTOUCHINPUT); + + FunctionType pFunction = (FunctionType)( + GetProcAddress(ModuleHandle, "CloseTouchInputHandle")); + if (!pFunction) + { + return FALSE; + } + + return pFunction(hTouchInput); +} + +static UINT lv_win32_get_dpi_for_window( + _In_ HWND WindowHandle) +{ + UINT Result = (UINT)(-1); + + HMODULE ModuleHandle = LoadLibraryW(L"SHCore.dll"); + if (ModuleHandle) + { + typedef enum MONITOR_DPI_TYPE_PRIVATE { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI + } MONITOR_DPI_TYPE_PRIVATE; + + typedef HRESULT(WINAPI* FunctionType)( + HMONITOR, MONITOR_DPI_TYPE_PRIVATE, UINT*, UINT*); + + FunctionType pFunction = (FunctionType)( + GetProcAddress(ModuleHandle, "GetDpiForMonitor")); + if (pFunction) + { + HMONITOR MonitorHandle = MonitorFromWindow( + WindowHandle, + MONITOR_DEFAULTTONEAREST); + + UINT dpiX = 0; + UINT dpiY = 0; + if (SUCCEEDED(pFunction( + MonitorHandle, + MDT_EFFECTIVE_DPI, + &dpiX, + &dpiY))) + { + Result = dpiX; + } + } + + FreeLibrary(ModuleHandle); + } + + if (Result == (UINT)(-1)) + { + HDC hWindowDC = GetDC(WindowHandle); + if (hWindowDC) + { + Result = GetDeviceCaps(hWindowDC, LOGPIXELSX); + ReleaseDC(WindowHandle, hWindowDC); + } + } + + if (Result == (UINT)(-1)) + { + Result = USER_DEFAULT_SCREEN_DPI; + } + + return Result; +} + +static void lv_win32_display_driver_flush_callback( + lv_disp_drv_t* disp_drv, + const lv_area_t* area, + lv_color_t* color_p) +{ + if (lv_disp_flush_is_last(disp_drv)) + { +#if (LV_COLOR_DEPTH == 32) || \ + (LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP == 0) || \ + (LV_COLOR_DEPTH == 8) || \ + (LV_COLOR_DEPTH == 1) + UNREFERENCED_PARAMETER(color_p); +#elif (LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP != 0) + SIZE_T count = g_pixel_buffer_size / sizeof(UINT16); + PUINT16 source = (PUINT16)color_p; + PUINT16 destination = (PUINT16)g_pixel_buffer; + for (SIZE_T i = 0; i < count; ++i) + { + UINT16 current = *source; + *destination = (LOBYTE(current) << 8) | HIBYTE(current); + + ++source; + ++destination; + } +#else + for (int y = area->y1; y <= area->y2; ++y) + { + for (int x = area->x1; x <= area->x2; ++x) + { + g_pixel_buffer[y * disp_drv->hor_res + x] = + lv_color_to32(*color_p); + color_p++; + } + } +#endif + + InvalidateRect(g_window_handle, NULL, FALSE); + } + + lv_disp_flush_ready(disp_drv); +} + +static void lv_win32_display_refresh_handler( + lv_timer_t* param) +{ + UNREFERENCED_PARAMETER(param); + + if (!g_display_refreshing) + { + _lv_disp_refr_timer(NULL); + } +} + +static void lv_win32_pointer_driver_read_callback( + lv_indev_drv_t* indev_drv, + lv_indev_data_t* data) +{ + UNREFERENCED_PARAMETER(indev_drv); + + data->state = (lv_indev_state_t)( + g_mouse_pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL); + + data->point.x = MulDiv( + GET_X_LPARAM(g_mouse_value), + USER_DEFAULT_SCREEN_DPI, + WIN32DRV_MONITOR_ZOOM * g_dpi_value); + data->point.y = MulDiv( + GET_Y_LPARAM(g_mouse_value), + USER_DEFAULT_SCREEN_DPI, + WIN32DRV_MONITOR_ZOOM * g_dpi_value); + + if (data->point.x < 0) + { + data->point.x = 0; + } + if (data->point.x > g_display->driver->hor_res - 1) + { + data->point.x = g_display->driver->hor_res - 1; + } + if (data->point.y < 0) + { + data->point.y = 0; + } + if (data->point.y > g_display->driver->ver_res - 1) + { + data->point.y = g_display->driver->ver_res - 1; + } +} + +static void lv_win32_keypad_driver_read_callback( + lv_indev_drv_t* indev_drv, + lv_indev_data_t* data) +{ + UNREFERENCED_PARAMETER(indev_drv); + + data->state = (lv_indev_state_t)( + g_keyboard_pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL); + + WPARAM KeyboardValue = g_keyboard_value; + + switch (KeyboardValue) + { + case VK_UP: + data->key = LV_KEY_UP; + break; + case VK_DOWN: + data->key = LV_KEY_DOWN; + break; + case VK_LEFT: + data->key = LV_KEY_LEFT; + break; + case VK_RIGHT: + data->key = LV_KEY_RIGHT; + break; + case VK_ESCAPE: + data->key = LV_KEY_ESC; + break; + case VK_DELETE: + data->key = LV_KEY_DEL; + break; + case VK_BACK: + data->key = LV_KEY_BACKSPACE; + break; + case VK_RETURN: + data->key = LV_KEY_ENTER; + break; + case VK_NEXT: + data->key = LV_KEY_NEXT; + break; + case VK_PRIOR: + data->key = LV_KEY_PREV; + break; + case VK_HOME: + data->key = LV_KEY_HOME; + break; + case VK_END: + data->key = LV_KEY_END; + break; + default: + if (KeyboardValue >= 'A' && KeyboardValue <= 'Z') + { + KeyboardValue += 0x20; + } + + data->key = (uint32_t)KeyboardValue; + + break; + } +} + +static void lv_win32_encoder_driver_read_callback( + lv_indev_drv_t* indev_drv, + lv_indev_data_t* data) +{ + UNREFERENCED_PARAMETER(indev_drv); + + data->state = (lv_indev_state_t)( + g_mousewheel_pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL); + data->enc_diff = g_mousewheel_value; + g_mousewheel_value = 0; +} + +static LRESULT CALLBACK lv_win32_window_message_callback( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + switch (uMsg) + { + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + { + g_mouse_value = lParam; + if (uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP) + { + g_mouse_pressed = (uMsg == WM_LBUTTONDOWN); + } + else if (uMsg == WM_MBUTTONDOWN || uMsg == WM_MBUTTONUP) + { + g_mousewheel_pressed = (uMsg == WM_MBUTTONDOWN); + } + return 0; + } + case WM_KEYDOWN: + case WM_KEYUP: + { + g_keyboard_pressed = (uMsg == WM_KEYDOWN); + g_keyboard_value = wParam; + break; + } + case WM_MOUSEWHEEL: + { + g_mousewheel_value = -(GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA); + break; + } + case WM_TOUCH: + { + UINT cInputs = LOWORD(wParam); + HTOUCHINPUT hTouchInput = (HTOUCHINPUT)(lParam); + + PTOUCHINPUT pInputs = malloc(cInputs * sizeof(TOUCHINPUT)); + if (pInputs) + { + if (lv_win32_get_touch_input_info( + hTouchInput, + cInputs, + pInputs, + sizeof(TOUCHINPUT))) + { + for (UINT i = 0; i < cInputs; ++i) + { + POINT Point; + Point.x = TOUCH_COORD_TO_PIXEL(pInputs[i].x); + Point.y = TOUCH_COORD_TO_PIXEL(pInputs[i].y); + if (!ScreenToClient(hWnd, &Point)) + { + continue; + } + + uint16_t x = (uint16_t)(Point.x & 0xffff); + uint16_t y = (uint16_t)(Point.y & 0xffff); + + DWORD MousePressedMask = + TOUCHEVENTF_MOVE | TOUCHEVENTF_DOWN; + + g_mouse_value = (y << 16) | x; + g_mouse_pressed = (pInputs[i].dwFlags & MousePressedMask); + } + } + + free(pInputs); + } + + lv_win32_close_touch_input_handle(hTouchInput); + + break; + } + case WM_DPICHANGED: + { + g_dpi_value = HIWORD(wParam); + + LPRECT SuggestedRect = (LPRECT)lParam; + + SetWindowPos( + hWnd, + NULL, + SuggestedRect->left, + SuggestedRect->top, + SuggestedRect->right, + SuggestedRect->bottom, + SWP_NOZORDER | SWP_NOACTIVATE); + + RECT ClientRect; + GetClientRect(hWnd, &ClientRect); + + int WindowWidth = MulDiv( + g_display->driver->hor_res * WIN32DRV_MONITOR_ZOOM, + g_dpi_value, + USER_DEFAULT_SCREEN_DPI); + int WindowHeight = MulDiv( + g_display->driver->ver_res * WIN32DRV_MONITOR_ZOOM, + g_dpi_value, + USER_DEFAULT_SCREEN_DPI); + + SetWindowPos( + hWnd, + NULL, + SuggestedRect->left, + SuggestedRect->top, + SuggestedRect->right + (WindowWidth - ClientRect.right), + SuggestedRect->bottom + (WindowHeight - ClientRect.bottom), + SWP_NOZORDER | SWP_NOACTIVATE); + + break; + } + case WM_PAINT: + { + g_display_refreshing = true; + + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hWnd, &ps); + + if (g_display) + { + SetStretchBltMode(hdc, HALFTONE); + + StretchBlt( + hdc, + ps.rcPaint.left, + ps.rcPaint.top, + ps.rcPaint.right - ps.rcPaint.left, + ps.rcPaint.bottom - ps.rcPaint.top, + g_buffer_dc_handle, + 0, + 0, + MulDiv( + ps.rcPaint.right - ps.rcPaint.left, + USER_DEFAULT_SCREEN_DPI, + WIN32DRV_MONITOR_ZOOM * g_dpi_value), + MulDiv( + ps.rcPaint.bottom - ps.rcPaint.top, + USER_DEFAULT_SCREEN_DPI, + WIN32DRV_MONITOR_ZOOM * g_dpi_value), + SRCCOPY); + } + + EndPaint(hWnd, &ps); + + g_display_refreshing = false; + + break; + } + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + + return 0; +} + +static unsigned int __stdcall lv_win32_window_thread_entrypoint( + void* raw_parameter) +{ + PWINDOW_THREAD_PARAMETER parameter = + (PWINDOW_THREAD_PARAMETER)raw_parameter; + + WNDCLASSEXW window_class; + window_class.cbSize = sizeof(WNDCLASSEXW); + window_class.style = 0; + window_class.lpfnWndProc = lv_win32_window_message_callback; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = parameter->instance_handle; + window_class.hIcon = parameter->icon_handle; + window_class.hCursor = LoadCursorW(NULL, IDC_ARROW); + window_class.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + window_class.lpszMenuName = NULL; + window_class.lpszClassName = L"lv_sim_visual_studio"; + window_class.hIconSm = parameter->icon_handle; + if (!RegisterClassExW(&window_class)) + { + return 0; + } + + HWND window_handle = CreateWindowExW( + WINDOW_EX_STYLE, + window_class.lpszClassName, + L"LVGL Simulator for Windows Desktop", + WINDOW_STYLE, + CW_USEDEFAULT, + 0, + CW_USEDEFAULT, + 0, + NULL, + NULL, + parameter->instance_handle, + NULL); + + if (!window_handle) + { + return 0; + } + + g_dpi_value = lv_win32_get_dpi_for_window(window_handle); + + RECT window_size; + + window_size.left = 0; + window_size.right = MulDiv( + parameter->hor_res * WIN32DRV_MONITOR_ZOOM, + g_dpi_value, + USER_DEFAULT_SCREEN_DPI); + window_size.top = 0; + window_size.bottom = MulDiv( + parameter->ver_res * WIN32DRV_MONITOR_ZOOM, + g_dpi_value, + USER_DEFAULT_SCREEN_DPI); + + AdjustWindowRectEx( + &window_size, + WINDOW_STYLE, + FALSE, + WINDOW_EX_STYLE); + OffsetRect( + &window_size, + -window_size.left, + -window_size.top); + + SetWindowPos( + window_handle, + NULL, + 0, + 0, + window_size.right, + window_size.bottom, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE); + + lv_win32_register_touch_window(window_handle, 0); + + lv_win32_enable_child_window_dpi_message(window_handle); + + HDC hNewBufferDC = lv_win32_create_frame_buffer( + window_handle, + parameter->hor_res, + parameter->ver_res, + &g_pixel_buffer, + &g_pixel_buffer_size); + + DeleteDC(g_buffer_dc_handle); + g_buffer_dc_handle = hNewBufferDC; + + ShowWindow(window_handle, parameter->show_window_mode); + UpdateWindow(window_handle); + g_window_handle = window_handle; + + SetEvent(parameter->window_mutex); + + MSG message; + while (GetMessageW(&message, NULL, 0, 0)) + { + TranslateMessage(&message); + DispatchMessageW(&message); + } + + lv_win32_quit_signal = true; + + return 0; +} + +#endif /*USE_WIN32DRV*/ diff --git a/libs/lv_drivers/win32drv/win32drv.h b/libs/lv_drivers/win32drv/win32drv.h new file mode 100644 index 00000000..d20c6642 --- /dev/null +++ b/libs/lv_drivers/win32drv/win32drv.h @@ -0,0 +1,79 @@ +/** + * @file win32drv.h + * + */ + +#ifndef LV_WIN32DRV_H +#define LV_WIN32DRV_H + +/********************* + * INCLUDES + *********************/ + +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../../lv_drv_conf.h" +#endif +#endif + +#if USE_WIN32DRV + +#include + +#if _MSC_VER >= 1200 + // Disable compilation warnings. +#pragma warning(push) +// nonstandard extension used : bit field types other than int +#pragma warning(disable:4214) +// 'conversion' conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable:4244) +#endif + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#if _MSC_VER >= 1200 +// Restore compilation warnings. +#pragma warning(pop) +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +EXTERN_C bool lv_win32_quit_signal; + +EXTERN_C lv_indev_t* lv_win32_pointer_device_object; +EXTERN_C lv_indev_t* lv_win32_keypad_device_object; +EXTERN_C lv_indev_t* lv_win32_encoder_device_object; + +EXTERN_C void lv_win32_add_all_input_devices_to_group( + lv_group_t* group); + +EXTERN_C bool lv_win32_init( + HINSTANCE instance_handle, + int show_window_mode, + lv_coord_t hor_res, + lv_coord_t ver_res, + HICON icon_handle); + +/********************** + * MACROS + **********************/ + +#endif /*USE_WIN32DRV*/ + +#endif /*LV_WIN32DRV_H*/ diff --git a/libs/lv_drivers/win_drv.c b/libs/lv_drivers/win_drv.c new file mode 100644 index 00000000..f41e2ee6 --- /dev/null +++ b/libs/lv_drivers/win_drv.c @@ -0,0 +1,304 @@ +/** + * @file win_drv.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "win_drv.h" +#if USE_WINDOWS + +#include +#include +#include "lvgl/lvgl.h" + +#if LV_COLOR_DEPTH < 16 +#error Windows driver only supports true RGB colors at this time +#endif + +/********************** + * DEFINES + **********************/ + + #define WINDOW_STYLE (WS_OVERLAPPEDWINDOW & ~(WS_SIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME)) + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void do_register(void); +static void win_drv_flush(lv_disp_t *drv, lv_area_t *area, const lv_color_t * color_p); +static void win_drv_fill(int32_t x1, int32_t y1, int32_t x2, int32_t y2, lv_color_t color); +static void win_drv_map(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p); +static void win_drv_read(lv_indev_t *drv, lv_indev_data_t * data); +static void msg_handler(void *param); + +static COLORREF lv_color_to_colorref(const lv_color_t color); + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + +/********************** + * GLOBAL VARIABLES + **********************/ + +bool lv_win_exit_flag = false; +lv_disp_t *lv_windows_disp; + +/********************** + * STATIC VARIABLES + **********************/ +static HWND hwnd; +static uint32_t *fbp = NULL; /* Raw framebuffer memory */ +static bool mouse_pressed; +static int mouse_x, mouse_y; + + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +const char g_szClassName[] = "LVGL"; + +HWND windrv_init(void) +{ + WNDCLASSEX wc; + RECT winrect; + HICON lvgl_icon; + + //Step 1: Registering the Window Class + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = 0; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(NULL); + lvgl_icon = (HICON) LoadImage( NULL, "lvgl_icon.bmp", IMAGE_ICON, 0, 0, LR_LOADFROMFILE); + + if(lvgl_icon == NULL) + lvgl_icon = LoadIcon(NULL, IDI_APPLICATION); + + wc.hIcon = lvgl_icon; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wc.lpszMenuName = NULL; + wc.lpszClassName = g_szClassName; + wc.hIconSm = lvgl_icon; + + if(!RegisterClassEx(&wc)) + { + return NULL; + } + + winrect.left = 0; + winrect.right = WINDOW_HOR_RES - 1; + winrect.top = 0; + winrect.bottom = WINDOW_VER_RES - 1; + AdjustWindowRectEx(&winrect, WINDOW_STYLE, FALSE, WS_EX_CLIENTEDGE); + OffsetRect(&winrect, -winrect.left, -winrect.top); + // Step 2: Creating the Window + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, + g_szClassName, + "LVGL Simulator", + WINDOW_STYLE, + CW_USEDEFAULT, CW_USEDEFAULT, winrect.right, winrect.bottom, + NULL, NULL, GetModuleHandle(NULL), NULL); + + if(hwnd == NULL) + { + return NULL; + } + + ShowWindow(hwnd, SW_SHOWDEFAULT); + UpdateWindow(hwnd); + + + lv_task_create(msg_handler, 0, LV_TASK_PRIO_HIGHEST, NULL); + lv_win_exit_flag = false; + do_register(); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void do_register(void) +{ + static lv_disp_draw_buf_t disp_buf_1; + static lv_color_t buf1_1[WINDOW_HOR_RES * 100]; /*A buffer for 10 rows*/ + lv_disp_draw_buf_init(&disp_draw_buf_1, buf1_1, NULL, WINDOW_HOR_RES * 100); /*Initialize the display buffer*/ + + + /*----------------------------------- + * Register the display in LVGLGL + *----------------------------------*/ + + static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/ + lv_disp_drv_init(&disp_drv); /*Basic initialization*/ + + /*Set up the functions to access to your display*/ + + /*Set the resolution of the display*/ + disp_drv.hor_res = WINDOW_HOR_RES; + disp_drv.ver_res = WINDOW_VER_RES; + + /*Used to copy the buffer's content to the display*/ + disp_drv.flush_cb = win_drv_flush; + + /*Set a display buffer*/ + disp_drv.draw_buf = &disp_buf_1; + + /*Finally register the driver*/ + lv_windows_disp = lv_disp_drv_register(&disp_drv); + static lv_indev_drv_t indev_drv; + lv_indev_drv_init(&indev_drv); + indev_drv.type = LV_INDEV_TYPE_POINTER; + indev_drv.read_cb = win_drv_read; + lv_indev_drv_register(&indev_drv); +} + +static void msg_handler(void *param) +{ + (void)param; + + MSG msg; + BOOL bRet; + if( (bRet = PeekMessage( &msg, NULL, 0, 0, TRUE )) != 0) + { + if (bRet == -1) + { + return; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if(msg.message == WM_QUIT) + lv_win_exit_flag = true; + } +} + + static void win_drv_read(lv_indev_t *drv, lv_indev_data_t * data) +{ + data->state = mouse_pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL; + data->point.x = mouse_x; + data->point.y = mouse_y; +} + + static void on_paint(void) + { + HBITMAP bmp = CreateBitmap(WINDOW_HOR_RES, WINDOW_VER_RES, 1, 32, fbp); + PAINTSTRUCT ps; + + HDC hdc = BeginPaint(hwnd, &ps); + + HDC hdcMem = CreateCompatibleDC(hdc); + HBITMAP hbmOld = SelectObject(hdcMem, bmp); + + BitBlt(hdc, 0, 0, WINDOW_HOR_RES, WINDOW_VER_RES, hdcMem, 0, 0, SRCCOPY); + + SelectObject(hdcMem, hbmOld); + DeleteDC(hdcMem); + + EndPaint(hwnd, &ps); + DeleteObject(bmp); + +} +/** + * Flush a buffer to the marked area + * @param x1 left coordinate + * @param y1 top coordinate + * @param x2 right coordinate + * @param y2 bottom coordinate + * @param color_p an array of colors + */ +static void win_drv_flush(lv_disp_t *drv, lv_area_t *area, const lv_color_t * color_p) +{ + win_drv_map(area->x1, area->y1, area->x2, area->y2, color_p); + lv_disp_flush_ready(drv); +} + +/** + * Put a color map to the marked area + * @param x1 left coordinate + * @param y1 top coordinate + * @param x2 right coordinate + * @param y2 bottom coordinate + * @param color_p an array of colors + */ +static void win_drv_map(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p) +{ + for(int y = y1; y <= y2; y++) + { + for(int x = x1; x <= x2; x++) + { + fbp[y*WINDOW_HOR_RES+x] = lv_color_to32(*color_p); + color_p++; + } + } + InvalidateRect(hwnd, NULL, FALSE); + UpdateWindow(hwnd); +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HDC hdc; + PAINTSTRUCT ps; + switch(msg) { + case WM_CREATE: + fbp = malloc(4*WINDOW_HOR_RES*WINDOW_VER_RES); + if(fbp == NULL) + return 1; + SetTimer(hwnd, 0, 10, (TIMERPROC)lv_task_handler); + SetTimer(hwnd, 1, 25, NULL); + + return 0; + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + mouse_x = GET_X_LPARAM(lParam); + mouse_y = GET_Y_LPARAM(lParam); + if(msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP) { + mouse_pressed = (msg == WM_LBUTTONDOWN); + } + return 0; + case WM_CLOSE: + free(fbp); + fbp = NULL; + DestroyWindow(hwnd); + return 0; + case WM_PAINT: + on_paint(); + return 0; + case WM_TIMER: + lv_tick_inc(25); + return 0; + case WM_DESTROY: + PostQuitMessage(0); + return 0; + default: + break; + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} +static COLORREF lv_color_to_colorref(const lv_color_t color) +{ + uint32_t raw_color = lv_color_to32(color); + lv_color32_t tmp; + tmp.full = raw_color; + uint32_t colorref = RGB(tmp.ch.red, tmp.ch.green, tmp.ch.blue); + return colorref; +} +#endif + + + diff --git a/libs/lv_drivers/win_drv.h b/libs/lv_drivers/win_drv.h new file mode 100644 index 00000000..784fc9df --- /dev/null +++ b/libs/lv_drivers/win_drv.h @@ -0,0 +1,60 @@ +/** + * @file fbdev.h + * + */ + +#ifndef WINDRV_H +#define WINDRV_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#ifndef LV_DRV_NO_CONF +#ifdef LV_CONF_INCLUDE_SIMPLE +#include "lv_drv_conf.h" +#else +#include "../lv_drv_conf.h" +#endif +#endif + +#if USE_WINDOWS + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +extern bool lv_win_exit_flag; +extern lv_disp_t *lv_windows_disp; + +HWND windrv_init(void); + +/********************** + * MACROS + **********************/ + +#endif /*USE_WINDOWS*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*WIN_DRV_H*/ diff --git a/libs/lvgl/CMakeLists.txt b/libs/lvgl/CMakeLists.txt index a8255d13..4a92e59b 100644 --- a/libs/lvgl/CMakeLists.txt +++ b/libs/lvgl/CMakeLists.txt @@ -14,6 +14,5 @@ elseif(MICROPY_DIR) include(${CMAKE_CURRENT_LIST_DIR}/env_support/cmake/micropython.cmake) else() include(${CMAKE_CURRENT_LIST_DIR}/env_support/cmake/custom.cmake) - include(${CMAKE_CURRENT_LIST_DIR}/env_support/cmake/custom_simple_config.cmake) endif() diff --git a/libs/lvgl/config/lv_conf.h b/libs/lvgl/config/lv_conf.h deleted file mode 100644 index fa4ee89c..00000000 --- a/libs/lvgl/config/lv_conf.h +++ /dev/null @@ -1,959 +0,0 @@ -/** - * @file lv_conf.h - * Configuration file for v9.0.0-dev - */ - -/* - * Copy this file as `lv_conf.h` - * 1. simply next to the `lvgl` folder - * 2. or any other places and - * - define `LV_CONF_INCLUDE_SIMPLE` - * - add the path as include path - */ - -/* clang-format off */ -#if 1 /*Set it to "1" to enable content*/ - -#ifndef LV_CONF_H -#define LV_CONF_H - -/*==================== - COLOR SETTINGS - *====================*/ - -/*Color depth: 8 (A8), 16 (RGB565), 24 (RGB888), 32 (XRGB8888)*/ -#define LV_COLOR_DEPTH 16 - -/*========================= - STDLIB WRAPPER SETTINGS - *=========================*/ - -/* Possible values - * - LV_STDLIB_BUILTIN: LVGL's built in implementation - * - LV_STDLIB_CLIB: Standard C functions, like malloc, strlen, etc - * - LV_STDLIB_MICROPYTHON: MicroPython implementation - * - LV_STDLIB_RTTHREAD: RT-Thread implementation - * - LV_STDLIB_CUSTOM: Implement the functions externally - */ -#define LV_USE_STDLIB_MALLOC LV_STDLIB_BUILTIN -#define LV_USE_STDLIB_STRING LV_STDLIB_BUILTIN -#define LV_USE_STDLIB_SPRINTF LV_STDLIB_BUILTIN - - -#if LV_USE_STDLIB_MALLOC == LV_STDLIB_BUILTIN - /*Size of the memory available for `lv_malloc()` in bytes (>= 2kB)*/ - #define LV_MEM_SIZE (256 * 1024U) /*[bytes]*/ - - /*Size of the memory expand for `lv_malloc()` in bytes*/ - #define LV_MEM_POOL_EXPAND_SIZE 0 - - /*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/ - #define LV_MEM_ADR 0 /*0: unused*/ - /*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/ - #if LV_MEM_ADR == 0 - #undef LV_MEM_POOL_INCLUDE - #undef LV_MEM_POOL_ALLOC - #endif -#endif /*LV_USE_MALLOC == LV_STDLIB_BUILTIN*/ - -/*==================== - HAL SETTINGS - *====================*/ - -/*Default display refresh, input device read and animation step period.*/ -#define LV_DEF_REFR_PERIOD 33 /*[ms]*/ - -/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings. - *(Not so important, you can adjust it to modify default sizes and spaces)*/ -#define LV_DPI_DEF 130 /*[px/inch]*/ - -/*================= - * OPERATING SYSTEM - *=================*/ -/*Select an operating system to use. Possible options: - * - LV_OS_NONE - * - LV_OS_PTHREAD - * - LV_OS_FREERTOS - * - LV_OS_CMSIS_RTOS2 - * - LV_OS_RTTHREAD - * - LV_OS_WINDOWS - * - LV_OS_CUSTOM */ -#define LV_USE_OS LV_OS_NONE - -#if LV_USE_OS == LV_OS_CUSTOM - #define LV_OS_CUSTOM_INCLUDE -#endif - -/*======================== - * RENDERING CONFIGURATION - *========================*/ - -/*Align the stride of all layers and images to this bytes*/ -#define LV_DRAW_BUF_STRIDE_ALIGN 1 - -/*Align the start address of draw_buf addresses to this bytes*/ -#define LV_DRAW_BUF_ALIGN 4 - -#define LV_USE_DRAW_SW 1 -#if LV_USE_DRAW_SW == 1 - /* Set the number of draw unit. - * > 1 requires an operating system enabled in `LV_USE_OS` - * > 1 means multiply threads will render the screen in parallel */ - #define LV_DRAW_SW_DRAW_UNIT_CNT 1 - - /* If a widget has `style_opa < 255` (not `bg_opa`, `text_opa` etc) or not NORMAL blend mode - * it is buffered into a "simple" layer before rendering. The widget can be buffered in smaller chunks. - * "Transformed layers" (if `transform_angle/zoom` are set) use larger buffers - * and can't be drawn in chunks. */ - - /*The target buffer size for simple layer chunks.*/ - #define LV_DRAW_SW_LAYER_SIMPLE_BUF_SIZE (24 * 1024) /*[bytes]*/ - - /* 0: use a simple renderer capable of drawing only simple rectangles with gradient, images, texts, and straight lines only - * 1: use a complex renderer capable of drawing rounded corners, shadow, skew lines, and arcs too */ - #define LV_DRAW_SW_COMPLEX 1 - - #if LV_DRAW_SW_COMPLEX == 1 - /*Allow buffering some shadow calculation. - *LV_DRAW_SW_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius` - *Caching has LV_DRAW_SW_SHADOW_CACHE_SIZE^2 RAM cost*/ - #define LV_DRAW_SW_SHADOW_CACHE_SIZE 0 - - /* Set number of maximally cached circle data. - * The circumference of 1/4 circle are saved for anti-aliasing - * radius * 4 bytes are used per circle (the most often used radiuses are saved) - * 0: to disable caching */ - #define LV_DRAW_SW_CIRCLE_CACHE_SIZE 4 - #endif - - #define LV_USE_DRAW_SW_ASM LV_DRAW_SW_ASM_NONE - - #if LV_USE_DRAW_SW_ASM == LV_DRAW_SW_ASM_CUSTOM - #define LV_DRAW_SW_ASM_CUSTOM_INCLUDE "" - #endif -#endif - -/* Use Arm-2D on Cortex-M based devices. Please only enable it for Helium Powered devices for now */ -#define LV_USE_DRAW_ARM2D 0 - -/* Use NXP's VG-Lite GPU on iMX RTxxx platforms. */ -#define LV_USE_DRAW_VGLITE 0 - -#if LV_USE_DRAW_VGLITE - /* Enable blit quality degradation workaround recommended for screen's dimension > 352 pixels. */ - #define LV_USE_VGLITE_BLIT_SPLIT 0 - - #if LV_USE_OS - /* Enable VGLite draw async. Queue multiple tasks and flash them once to the GPU. */ - #define LV_USE_VGLITE_DRAW_ASYNC 1 - #endif - - /* Enable VGLite asserts. */ - #define LV_USE_VGLITE_ASSERT 0 -#endif - -/* Use NXP's PXP on iMX RTxxx platforms. */ -#define LV_USE_DRAW_PXP 0 - -#if LV_USE_DRAW_PXP - /* Enable PXP asserts. */ - #define LV_USE_PXP_ASSERT 0 -#endif - -/* Use Renesas Dave2D on RA platforms. */ -#define LV_USE_DRAW_DAVE2D 0 - -/* Draw using cached SDL textures*/ -#define LV_USE_DRAW_SDL 0 - -/* Use VG-Lite GPU. */ -#define LV_USE_DRAW_VG_LITE 0 - -#if LV_USE_DRAW_VG_LITE -/* Enable VG-Lite custom external 'gpu_init()' function */ -#define LV_VG_LITE_USE_GPU_INIT 0 - -/* Enable VG-Lite assert. */ -#define LV_VG_LITE_USE_ASSERT 0 - -#endif - -/*======================= - * FEATURE CONFIGURATION - *=======================*/ - -/*------------- - * Logging - *-----------*/ - -/*Enable the log module*/ -#define LV_USE_LOG 1 -#if LV_USE_LOG - - /*How important log should be added: - *LV_LOG_LEVEL_TRACE A lot of logs to give detailed information - *LV_LOG_LEVEL_INFO Log important events - *LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem - *LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail - *LV_LOG_LEVEL_USER Only logs added by the user - *LV_LOG_LEVEL_NONE Do not log anything*/ - #define LV_LOG_LEVEL LV_LOG_LEVEL_WARN - - /*1: Print the log with 'printf'; - *0: User need to register a callback with `lv_log_register_print_cb()`*/ - #define LV_LOG_PRINTF 1 - - /*1: Enable print timestamp; - *0: Disable print timestamp*/ - #define LV_LOG_USE_TIMESTAMP 1 - - /*1: Print file and line number of the log; - *0: Do not print file and line number of the log*/ - #define LV_LOG_USE_FILE_LINE 1 - - /*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/ - #define LV_LOG_TRACE_MEM 1 - #define LV_LOG_TRACE_TIMER 1 - #define LV_LOG_TRACE_INDEV 1 - #define LV_LOG_TRACE_DISP_REFR 1 - #define LV_LOG_TRACE_EVENT 1 - #define LV_LOG_TRACE_OBJ_CREATE 1 - #define LV_LOG_TRACE_LAYOUT 1 - #define LV_LOG_TRACE_ANIM 1 - #define LV_LOG_TRACE_CACHE 1 - -#endif /*LV_USE_LOG*/ - -/*------------- - * Asserts - *-----------*/ - -/*Enable asserts if an operation is failed or an invalid data is found. - *If LV_USE_LOG is enabled an error message will be printed on failure*/ -#define LV_USE_ASSERT_NULL 1 /*Check if the parameter is NULL. (Very fast, recommended)*/ -#define LV_USE_ASSERT_MALLOC 1 /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/ -#define LV_USE_ASSERT_STYLE 1 /*Check if the styles are properly initialized. (Very fast, recommended)*/ -#define LV_USE_ASSERT_MEM_INTEGRITY 1 /*Check the integrity of `lv_mem` after critical operations. (Slow)*/ -#define LV_USE_ASSERT_OBJ 1 /*Check the object's type and existence (e.g. not deleted). (Slow)*/ - -/*Add a custom handler when assert happens e.g. to restart the MCU*/ -#define LV_ASSERT_HANDLER_INCLUDE -#define LV_ASSERT_HANDLER while(1); /*Halt by default*/ - -/*------------- - * Debug - *-----------*/ - -/*1: Draw random colored rectangles over the redrawn areas*/ -#define LV_USE_REFR_DEBUG 0 - -/*1: Draw a red overlay for ARGB layers and a green overlay for RGB layers*/ -#define LV_USE_LAYER_DEBUG 0 - -/*1: Draw overlays with different colors for each draw_unit's tasks. - *Also add the index number of the draw unit on white background. - *For layers add the index number of the draw unit on black background.*/ -#define LV_USE_PARALLEL_DRAW_DEBUG 0 - -/*------------- - * Others - *-----------*/ - -#define LV_ENABLE_GLOBAL_CUSTOM 0 -#if LV_ENABLE_GLOBAL_CUSTOM - /*Header to include for the custom 'lv_global' function"*/ - #define LV_GLOBAL_CUSTOM_INCLUDE -#endif - -/*Default cache size in bytes. - *Used by image decoders such as `lv_lodepng` to keep the decoded image in the memory. - *If size is not set to 0, the decoder will fail to decode when the cache is full. - *If size is 0, the cache function is not enabled and the decoded mem will be released immediately after use.*/ -#define LV_CACHE_DEF_SIZE 0 - -/*Number of stops allowed per gradient. Increase this to allow more stops. - *This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/ -#define LV_GRADIENT_MAX_STOPS 2 - -/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently. - * 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */ -#define LV_COLOR_MIX_ROUND_OFS 0 - -/* Add 2 x 32 bit variables to each lv_obj_t to speed up getting style properties */ -#define LV_OBJ_STYLE_CACHE 0 - -/* Add `id` field to `lv_obj_t` */ -#define LV_USE_OBJ_ID 0 - -/* Use lvgl builtin method for obj ID */ -#define LV_USE_OBJ_ID_BUILTIN 0 - -/*Use obj property set/get API*/ -#define LV_USE_OBJ_PROPERTY 0 - -/* VG-Lite Simulator */ -/*Requires: LV_USE_THORVG_INTERNAL or LV_USE_THORVG_EXTERNAL */ -#define LV_USE_VG_LITE_THORVG 0 - -#if LV_USE_VG_LITE_THORVG - - /*Enable LVGL's blend mode support*/ - #define LV_VG_LITE_THORVG_LVGL_BLEND_SUPPORT 0 - - /*Enable YUV color format support*/ - #define LV_VG_LITE_THORVG_YUV_SUPPORT 0 - - /*Enable 16 pixels alignment*/ - #define LV_VG_LITE_THORVG_16PIXELS_ALIGN 1 - - /*Enable multi-thread render*/ - #define LV_VG_LITE_THORVG_THREAD_RENDER 0 - -#endif - -/*===================== - * COMPILER SETTINGS - *====================*/ - -/*For big endian systems set to 1*/ -#define LV_BIG_ENDIAN_SYSTEM 0 - -/*Define a custom attribute to `lv_tick_inc` function*/ -#define LV_ATTRIBUTE_TICK_INC - -/*Define a custom attribute to `lv_timer_handler` function*/ -#define LV_ATTRIBUTE_TIMER_HANDLER - -/*Define a custom attribute to `lv_display_flush_ready` function*/ -#define LV_ATTRIBUTE_FLUSH_READY - -/*Required alignment size for buffers*/ -#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1 - -/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default). - * E.g. __attribute__((aligned(4)))*/ -#define LV_ATTRIBUTE_MEM_ALIGN - -/*Attribute to mark large constant arrays for example font's bitmaps*/ -#define LV_ATTRIBUTE_LARGE_CONST - -/*Compiler prefix for a big array declaration in RAM*/ -#define LV_ATTRIBUTE_LARGE_RAM_ARRAY - -/*Place performance critical functions into a faster memory (e.g RAM)*/ -#define LV_ATTRIBUTE_FAST_MEM - -/*Export integer constant to binding. This macro is used with constants in the form of LV_ that - *should also appear on LVGL binding API such as Micropython.*/ -#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/ - -/*Prefix all global extern data with this*/ -#define LV_ATTRIBUTE_EXTERN_DATA - -/* Use `float` as `lv_value_precise_t` */ -#define LV_USE_FLOAT 0 - -/*================== - * FONT USAGE - *===================*/ - -/*Montserrat fonts with ASCII range and some symbols using bpp = 4 - *https://fonts.google.com/specimen/Montserrat*/ -#define LV_FONT_MONTSERRAT_8 1 -#define LV_FONT_MONTSERRAT_10 1 -#define LV_FONT_MONTSERRAT_12 1 -#define LV_FONT_MONTSERRAT_14 1 -#define LV_FONT_MONTSERRAT_16 1 -#define LV_FONT_MONTSERRAT_18 1 -#define LV_FONT_MONTSERRAT_20 1 -#define LV_FONT_MONTSERRAT_22 1 -#define LV_FONT_MONTSERRAT_24 1 -#define LV_FONT_MONTSERRAT_26 1 -#define LV_FONT_MONTSERRAT_28 1 -#define LV_FONT_MONTSERRAT_30 1 -#define LV_FONT_MONTSERRAT_32 1 -#define LV_FONT_MONTSERRAT_34 1 -#define LV_FONT_MONTSERRAT_36 1 -#define LV_FONT_MONTSERRAT_38 1 -#define LV_FONT_MONTSERRAT_40 1 -#define LV_FONT_MONTSERRAT_42 1 -#define LV_FONT_MONTSERRAT_44 1 -#define LV_FONT_MONTSERRAT_46 1 -#define LV_FONT_MONTSERRAT_48 1 - -/*Demonstrate special features*/ -#define LV_FONT_MONTSERRAT_28_COMPRESSED 1 /*bpp = 3*/ -#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 1 /*Hebrew, Arabic, Persian letters and all their forms*/ -#define LV_FONT_SIMSUN_16_CJK 1 /*1000 most common CJK radicals*/ - -/*Pixel perfect monospace fonts*/ -#define LV_FONT_UNSCII_8 1 -#define LV_FONT_UNSCII_16 1 - -/*Optionally declare custom fonts here. - *You can use these fonts as default font too and they will be available globally. - *E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/ -#define LV_FONT_CUSTOM_DECLARE - -/*Always set a default font*/ -#define LV_FONT_DEFAULT &lv_font_montserrat_14 - -/*Enable handling large font and/or fonts with a lot of characters. - *The limit depends on the font size, font face and bpp. - *Compiler error will be triggered if a font needs it.*/ -#define LV_FONT_FMT_TXT_LARGE 1 - -/*Enables/disables support for compressed fonts.*/ -#define LV_USE_FONT_COMPRESSED 1 - -/*Enable drawing placeholders when glyph dsc is not found*/ -#define LV_USE_FONT_PLACEHOLDER 1 - -/*================= - * TEXT SETTINGS - *=================*/ - -/** - * Select a character encoding for strings. - * Your IDE or editor should have the same character encoding - * - LV_TXT_ENC_UTF8 - * - LV_TXT_ENC_ASCII - */ -#define LV_TXT_ENC LV_TXT_ENC_UTF8 - -/*Can break (wrap) texts on these chars*/ -#define LV_TXT_BREAK_CHARS " ,.;:-_)]}" - -/*If a word is at least this long, will break wherever "prettiest" - *To disable, set to a value <= 0*/ -#define LV_TXT_LINE_BREAK_LONG_LEN 0 - -/*Minimum number of characters in a long word to put on a line before a break. - *Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/ -#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3 - -/*Minimum number of characters in a long word to put on a line after a break. - *Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/ -#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3 - -/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts. - *The direction will be processed according to the Unicode Bidirectional Algorithm: - *https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/ -#define LV_USE_BIDI 0 -#if LV_USE_BIDI - /*Set the default direction. Supported values: - *`LV_BASE_DIR_LTR` Left-to-Right - *`LV_BASE_DIR_RTL` Right-to-Left - *`LV_BASE_DIR_AUTO` detect texts base direction*/ - #define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO -#endif - -/*Enable Arabic/Persian processing - *In these languages characters should be replaced with an other form based on their position in the text*/ -#define LV_USE_ARABIC_PERSIAN_CHARS 0 - -/*================== - * WIDGETS - *================*/ - -/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/ - -#define LV_WIDGETS_HAS_DEFAULT_VALUE 1 - -#define LV_USE_ANIMIMG 1 - -#define LV_USE_ARC 1 - -#define LV_USE_BAR 1 - -#define LV_USE_BUTTON 1 - -#define LV_USE_BUTTONMATRIX 1 - -#define LV_USE_CALENDAR 1 -#if LV_USE_CALENDAR - #define LV_CALENDAR_WEEK_STARTS_MONDAY 0 - #if LV_CALENDAR_WEEK_STARTS_MONDAY - #define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"} - #else - #define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"} - #endif - - #define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} - #define LV_USE_CALENDAR_HEADER_ARROW 1 - #define LV_USE_CALENDAR_HEADER_DROPDOWN 1 -#endif /*LV_USE_CALENDAR*/ - -#define LV_USE_CANVAS 1 - -#define LV_USE_CHART 1 - -#define LV_USE_CHECKBOX 1 - -#define LV_USE_DROPDOWN 1 /*Requires: lv_label*/ - -#define LV_USE_IMAGE 1 /*Requires: lv_label*/ - -#define LV_USE_IMAGEBUTTON 1 - -#define LV_USE_KEYBOARD 1 - -#define LV_USE_LABEL 1 -#if LV_USE_LABEL - #define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/ - #define LV_LABEL_LONG_TXT_HINT 1 /*Store some extra info in labels to speed up drawing of very long texts*/ - #define LV_LABEL_WAIT_CHAR_COUNT 3 /*The count of wait chart*/ -#endif - -#define LV_USE_LED 1 - -#define LV_USE_LINE 1 - -#define LV_USE_LIST 1 - -#define LV_USE_MENU 1 - -#define LV_USE_MSGBOX 1 - -#define LV_USE_ROLLER 1 /*Requires: lv_label*/ - -#define LV_USE_SCALE 1 - -#define LV_USE_SLIDER 1 /*Requires: lv_bar*/ - -#define LV_USE_SPAN 1 -#if LV_USE_SPAN - /*A line text can contain maximum num of span descriptor */ - #define LV_SPAN_SNIPPET_STACK_SIZE 64 -#endif - -#define LV_USE_SPINBOX 1 - -#define LV_USE_SPINNER 1 - -#define LV_USE_SWITCH 1 - -#define LV_USE_TEXTAREA 1 /*Requires: lv_label*/ -#if LV_USE_TEXTAREA != 0 - #define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/ -#endif - -#define LV_USE_TABLE 1 - -#define LV_USE_TABVIEW 1 - -#define LV_USE_TILEVIEW 1 - -#define LV_USE_WIN 1 - -/*================== - * THEMES - *==================*/ - -/*A simple, impressive and very complete theme*/ -#define LV_USE_THEME_DEFAULT 1 -#if LV_USE_THEME_DEFAULT - - /*0: Light mode; 1: Dark mode*/ - #define LV_THEME_DEFAULT_DARK 0 - - /*1: Enable grow on press*/ - #define LV_THEME_DEFAULT_GROW 1 - - /*Default transition time in [ms]*/ - #define LV_THEME_DEFAULT_TRANSITION_TIME 80 -#endif /*LV_USE_THEME_DEFAULT*/ - -/*A very simple theme that is a good starting point for a custom theme*/ -#define LV_USE_THEME_SIMPLE 1 - -/*A theme designed for monochrome displays*/ -#define LV_USE_THEME_MONO 1 - -/*================== - * LAYOUTS - *==================*/ - -/*A layout similar to Flexbox in CSS.*/ -#define LV_USE_FLEX 1 - -/*A layout similar to Grid in CSS.*/ -#define LV_USE_GRID 1 - -/*==================== - * 3RD PARTS LIBRARIES - *====================*/ - -/*File system interfaces for common APIs */ - -/*API for fopen, fread, etc*/ -#define LV_USE_FS_STDIO 1 -#if LV_USE_FS_STDIO - #define LV_FS_STDIO_LETTER 'A' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ - #define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/ - #define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ -#endif - -/*API for open, read, etc*/ -#define LV_USE_FS_POSIX 0 -#if LV_USE_FS_POSIX - #define LV_FS_POSIX_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ - #define LV_FS_POSIX_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/ - #define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ -#endif - -/*API for CreateFile, ReadFile, etc*/ -#define LV_USE_FS_WIN32 0 -#if LV_USE_FS_WIN32 - #define LV_FS_WIN32_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ - #define LV_FS_WIN32_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/ - #define LV_FS_WIN32_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ -#endif - -/*API for FATFS (needs to be added separately). Uses f_open, f_read, etc*/ -#define LV_USE_FS_FATFS 0 -#if LV_USE_FS_FATFS - #define LV_FS_FATFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ - #define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ -#endif - -/*API for memory-mapped file access. */ -#define LV_USE_FS_MEMFS 1 -#if LV_USE_FS_MEMFS - #define LV_FS_MEMFS_LETTER 'M' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ -#endif - -/*LODEPNG decoder library*/ -#define LV_USE_LODEPNG 1 - -/*PNG decoder(libpng) library*/ -#define LV_USE_LIBPNG 0 - -/*BMP decoder library*/ -#define LV_USE_BMP 1 - -/* JPG + split JPG decoder library. - * Split JPG is a custom format optimized for embedded systems. */ -#define LV_USE_TJPGD 1 - -/* libjpeg-turbo decoder library. - * Supports complete JPEG specifications and high-performance JPEG decoding. */ -#define LV_USE_LIBJPEG_TURBO 0 - -/*GIF decoder library*/ -#define LV_USE_GIF 1 -#if LV_USE_GIF -/*GIF decoder accelerate*/ -#define LV_GIF_CACHE_DECODE_DATA 0 -#endif - - -/*Decode bin images to RAM*/ -#define LV_BIN_DECODER_RAM_LOAD 0 - -/*RLE decompress library*/ -#define LV_USE_RLE 1 - -/*QR code library*/ -#define LV_USE_QRCODE 1 - -/*Barcode code library*/ -#define LV_USE_BARCODE 1 - -/*FreeType library*/ -#ifndef LV_USE_FREETYPE -#define LV_USE_FREETYPE 0 -#endif - -#if LV_USE_FREETYPE - /*Memory used by FreeType to cache characters in kilobytes*/ - #define LV_FREETYPE_CACHE_SIZE 768 - - /*Let FreeType to use LVGL memory and file porting*/ - #define LV_FREETYPE_USE_LVGL_PORT 0 - - /* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */ - /* (0:use system defaults) */ - #define LV_FREETYPE_CACHE_FT_FACES 8 - #define LV_FREETYPE_CACHE_FT_SIZES 8 - #define LV_FREETYPE_CACHE_FT_GLYPH_CNT 256 -#endif - -/* Built-in TTF decoder */ -#define LV_USE_TINY_TTF 1 -#if LV_USE_TINY_TTF - /* Enable loading TTF data from files */ - #define LV_TINY_TTF_FILE_SUPPORT 0 -#endif - -/*Rlottie library*/ -#define LV_USE_RLOTTIE 0 - -/*Enable Vector Graphic APIs*/ -#define LV_USE_VECTOR_GRAPHIC 1 - -/* Enable ThorVG (vector graphics library) from the src/libs folder */ -#define LV_USE_THORVG_INTERNAL 1 - -/* Enable ThorVG by assuming that its installed and linked to the project */ -#define LV_USE_THORVG_EXTERNAL 0 - -/*Enable LZ4 compress/decompress lib*/ -#define LV_USE_LZ4 1 - -/*Use lvgl built-in LZ4 lib*/ -#define LV_USE_LZ4_INTERNAL 1 - -/*Use external LZ4 library*/ -#define LV_USE_LZ4_EXTERNAL 0 - -/*FFmpeg library for image decoding and playing videos - *Supports all major image formats so do not enable other image decoder with it*/ -#define LV_USE_FFMPEG 0 -#if LV_USE_FFMPEG - /*Dump input information to stderr*/ - #define LV_FFMPEG_DUMP_FORMAT 0 -#endif - -/*================== - * OTHERS - *==================*/ - -/*1: Enable API to take snapshot for object*/ -#define LV_USE_SNAPSHOT 1 - -/*1: Enable system monitor component*/ -#define LV_USE_SYSMON 1 - -#if LV_USE_SYSMON - - /*1: Show CPU usage and FPS count - * Requires `LV_USE_SYSMON = 1`*/ - #define LV_USE_PERF_MONITOR 0 - #if LV_USE_PERF_MONITOR - #define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT - - /*0: Displays performance data on the screen, 1: Prints performance data using log.*/ - #define LV_USE_PERF_MONITOR_LOG_MODE 0 - #endif - - /*1: Show the used memory and the memory fragmentation - * Requires `LV_USE_BUILTIN_MALLOC = 1` - * Requires `LV_USE_SYSMON = 1`*/ - #define LV_USE_MEM_MONITOR 0 - #if LV_USE_MEM_MONITOR - #define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT - #endif - -#endif /*LV_USE_SYSMON*/ - -/*1: Enable the runtime performance profiler*/ -#define LV_USE_PROFILER 0 -#if LV_USE_PROFILER - /*1: Enable the built-in profiler*/ - #define LV_USE_PROFILER_BUILTIN 1 - #if LV_USE_PROFILER_BUILTIN - /*Default profiler trace buffer size*/ - #define LV_PROFILER_BUILTIN_BUF_SIZE (16 * 1024) /*[bytes]*/ - #endif - - /*Header to include for the profiler*/ - #define LV_PROFILER_INCLUDE "lvgl/src/misc/lv_profiler_builtin.h" - - /*Profiler start point function*/ - #define LV_PROFILER_BEGIN LV_PROFILER_BUILTIN_BEGIN - - /*Profiler end point function*/ - #define LV_PROFILER_END LV_PROFILER_BUILTIN_END - - /*Profiler start point function with custom tag*/ - #define LV_PROFILER_BEGIN_TAG LV_PROFILER_BUILTIN_BEGIN_TAG - - /*Profiler end point function with custom tag*/ - #define LV_PROFILER_END_TAG LV_PROFILER_BUILTIN_END_TAG -#endif - -/*1: Enable Monkey test*/ -#define LV_USE_MONKEY 1 - -/*1: Enable grid navigation*/ -#define LV_USE_GRIDNAV 1 - -/*1: Enable lv_obj fragment*/ -#define LV_USE_FRAGMENT 1 - -/*1: Support using images as font in label or span widgets */ -#define LV_USE_IMGFONT 1 -#if LV_USE_IMGFONT - /*Imgfont image file path maximum length*/ - #define LV_IMGFONT_PATH_MAX_LEN 64 - - /*1: Use img cache to buffer header information*/ - #define LV_IMGFONT_USE_IMAGE_CACHE_HEADER 0 -#endif - -/*1: Enable an observer pattern implementation*/ -#define LV_USE_OBSERVER 1 - -/*1: Enable Pinyin input method*/ -/*Requires: lv_keyboard*/ -#define LV_USE_IME_PINYIN 1 -#if LV_USE_IME_PINYIN - /*1: Use default thesaurus*/ - /*If you do not use the default thesaurus, be sure to use `lv_ime_pinyin` after setting the thesauruss*/ - #define LV_IME_PINYIN_USE_DEFAULT_DICT 1 - /*Set the maximum number of candidate panels that can be displayed*/ - /*This needs to be adjusted according to the size of the screen*/ - #define LV_IME_PINYIN_CAND_TEXT_NUM 6 - - /*Use 9 key input(k9)*/ - #define LV_IME_PINYIN_USE_K9_MODE 1 - #if LV_IME_PINYIN_USE_K9_MODE == 1 - #define LV_IME_PINYIN_K9_CAND_TEXT_NUM 3 - #endif /*LV_IME_PINYIN_USE_K9_MODE*/ -#endif - -/*1: Enable file explorer*/ -/*Requires: lv_table*/ -#define LV_USE_FILE_EXPLORER 1 -#if LV_USE_FILE_EXPLORER - /*Maximum length of path*/ - #define LV_FILE_EXPLORER_PATH_MAX_LEN (128) - /*Quick access bar, 1:use, 0:not use*/ - /*Requires: lv_list*/ - #define LV_FILE_EXPLORER_QUICK_ACCESS 1 -#endif - -/*================== - * DEVICES - *==================*/ - -/*Use SDL to open window on PC and handle mouse and keyboard*/ -#define LV_USE_SDL 1 -#if LV_USE_SDL - #define LV_SDL_INCLUDE_PATH - #define LV_SDL_RENDER_MODE LV_DISPLAY_RENDER_MODE_DIRECT /*LV_DISPLAY_RENDER_MODE_DIRECT is recommended for best performance*/ - #define LV_SDL_BUF_COUNT 1 /*1 or 2*/ - #define LV_SDL_FULLSCREEN 0 /*1: Make the window full screen by default*/ - #define LV_SDL_DIRECT_EXIT 1 /*1: Exit the application when all SDL windows are closed*/ -#endif - -/*Use X11 to open window on Linux desktop and handle mouse and keyboard*/ -#define LV_USE_X11 0 -#if LV_USE_X11 - #define LV_X11_DIRECT_EXIT 1 /*Exit the application when all X11 windows have been closed*/ - #define LV_X11_DOUBLE_BUFFER 1 /*Use double buffers for endering*/ - /*select only 1 of the following render modes (LV_X11_RENDER_MODE_PARTIAL preferred!)*/ - #define LV_X11_RENDER_MODE_PARTIAL 1 /*Partial render mode (preferred)*/ - #define LV_X11_RENDER_MODE_DIRECT 0 /*direct render mode*/ - #define LV_X11_RENDER_MODE_FULL 0 /*Full render mode*/ -#endif - -/*Driver for /dev/fb*/ -#define LV_USE_LINUX_FBDEV 0 -#if LV_USE_LINUX_FBDEV - #define LV_LINUX_FBDEV_BSD 0 - #define LV_LINUX_FBDEV_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL - #define LV_LINUX_FBDEV_BUFFER_COUNT 0 - #define LV_LINUX_FBDEV_BUFFER_SIZE 60 -#endif - -/*Use Nuttx to open window and handle touchscreen*/ -#define LV_USE_NUTTX 0 - -#if LV_USE_NUTTX - #define LV_USE_NUTTX_LIBUV 0 - - /*Use Nuttx custom init API to open window and handle touchscreen*/ - #define LV_USE_NUTTX_CUSTOM_INIT 0 - - /*Driver for /dev/lcd*/ - #define LV_USE_NUTTX_LCD 0 - #if LV_USE_NUTTX_LCD - #define LV_NUTTX_LCD_BUFFER_COUNT 0 - #define LV_NUTTX_LCD_BUFFER_SIZE 60 - #endif - - /*Driver for /dev/input*/ - #define LV_USE_NUTTX_TOUCHSCREEN 0 - -#endif - -/*Driver for /dev/dri/card*/ -#define LV_USE_LINUX_DRM 0 - -/*Interface for TFT_eSPI*/ -#define LV_USE_TFT_ESPI 0 - -/*Driver for evdev input devices*/ -#define LV_USE_EVDEV 0 - -/*Drivers for LCD devices connected via SPI/parallel port*/ -#define LV_USE_ST7735 0 -#define LV_USE_ST7789 0 -#define LV_USE_ST7796 0 -#define LV_USE_ILI9341 0 - -#define LV_USE_GENERIC_MIPI (LV_USE_ST7735 | LV_USE_ST7789 | LV_USE_ST7796 | LV_USE_ILI9341) - -/* LVGL Windows backend */ -#define LV_USE_WINDOWS 0 - -/*================== -* EXAMPLES -*==================*/ - -/*Enable the examples to be built with the library*/ -#define LV_BUILD_EXAMPLES 1 - -/*=================== - * DEMO USAGE - ====================*/ - -/*Show some widget. It might be required to increase `LV_MEM_SIZE` */ -#define LV_USE_DEMO_WIDGETS 1 -#if LV_USE_DEMO_WIDGETS - #define LV_DEMO_WIDGETS_SLIDESHOW 0 -#endif - -/*Demonstrate the usage of encoder and keyboard*/ -#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0 - -/*Benchmark your system*/ -#define LV_USE_DEMO_BENCHMARK 1 - -/*Render test for each primitives. Requires at least 480x272 display*/ -#define LV_USE_DEMO_RENDER 1 - -/*Stress test for LVGL*/ -#define LV_USE_DEMO_STRESS 1 - -/*Music player demo*/ -#define LV_USE_DEMO_MUSIC 0 -#if LV_USE_DEMO_MUSIC - #define LV_DEMO_MUSIC_SQUARE 0 - #define LV_DEMO_MUSIC_LANDSCAPE 0 - #define LV_DEMO_MUSIC_ROUND 0 - #define LV_DEMO_MUSIC_LARGE 0 - #define LV_DEMO_MUSIC_AUTO_PLAY 0 -#endif - -/*Flex layout demo*/ -#define LV_USE_DEMO_FLEX_LAYOUT 1 - -/*Smart-phone like multi-language demo*/ -#define LV_USE_DEMO_MULTILANG 1 - -/*Widget transformation demo*/ -#define LV_USE_DEMO_TRANSFORM 1 - -/*Demonstrate scroll settings*/ -#define LV_USE_DEMO_SCROLL 1 - -/*Vector graphic demo*/ -#define LV_USE_DEMO_VECTOR_GRAPHIC 1 -/*--END OF LV_CONF_H--*/ - -#endif /*LV_CONF_H*/ - -#endif /*End of "Content enable"*/ diff --git a/libs/lvgl/env_support/cmake/custom_simple_config.cmake b/libs/lvgl/env_support/cmake/custom_simple_config.cmake deleted file mode 100644 index c63d5873..00000000 --- a/libs/lvgl/env_support/cmake/custom_simple_config.cmake +++ /dev/null @@ -1,2 +0,0 @@ -target_compile_definitions(lvgl PUBLIC "-DLV_CONF_INCLUDE_SIMPLE") -target_include_directories(lvgl PUBLIC ${LVGL_ROOT_DIR}/config) diff --git a/run.sh b/run.sh index 105dd134..e7c27f54 100755 --- a/run.sh +++ b/run.sh @@ -2,6 +2,7 @@ if [[ -v ESP_IDF_VERSION ]]; then idf.py flash monitor $@ else + set -e cmake -S ./ -B build-sim cmake --build build-sim -j 12 build-sim/app-sim/app-sim diff --git a/tactility-core/src/core_types.h b/tactility-core/src/core_types.h index 372982dd..f9c5e550 100644 --- a/tactility-core/src/core_types.h +++ b/tactility-core/src/core_types.h @@ -2,7 +2,7 @@ #include #include -#include +#include "tactility_core_config.h" #ifdef __cplusplus extern "C" { diff --git a/tactility-core/src/mutex.c b/tactility-core/src/mutex.c index 5e0cec2b..02c3bc0a 100644 --- a/tactility-core/src/mutex.c +++ b/tactility-core/src/mutex.c @@ -12,122 +12,127 @@ #include "semphr.h" #endif +typedef struct { + SemaphoreHandle_t handle; + MutexType type; +} MutexData; -Mutex* tt_mutex_alloc(MutexType type) { - tt_assert(!TT_IS_IRQ_MODE()); +#define MUTEX_DEBUGGING false - SemaphoreHandle_t hMutex = NULL; - - if (type == MutexTypeNormal) { - hMutex = xSemaphoreCreateMutex(); - } else if (type == MutexTypeRecursive) { - hMutex = xSemaphoreCreateRecursiveMutex(); +#if MUTEX_DEBUGGING +#define TAG "mutex" +void tt_mutex_info(Mutex mutex, const char* label) { + MutexData* data = (MutexData*)mutex; + if (data == NULL) { + TT_LOG_I(TAG, "mutex %s: is NULL", label); } else { - tt_crash("Programming error"); + TT_LOG_I(TAG, "mutex %s: handle=%0X type=%d owner=%0x", label, data->handle, data->type, tt_mutex_get_owner(mutex)); + } +} +#else +#define tt_mutex_info(mutex, text) +#endif + +Mutex tt_mutex_alloc(MutexType type) { + tt_assert(!TT_IS_IRQ_MODE()); + MutexData* data = malloc(sizeof(MutexData)); + + data->type = type; + + switch (type) { + case MutexTypeNormal: + data->handle = xSemaphoreCreateMutex(); + break; + case MutexTypeRecursive: + data->handle = xSemaphoreCreateRecursiveMutex(); + break; + default: + tt_crash("mutex type unknown/corrupted"); } - tt_check(hMutex != NULL); - - if (type == MutexTypeRecursive) { - /* Set LSB as 'recursive mutex flag' */ - hMutex = (SemaphoreHandle_t)((uint32_t)hMutex | 1U); - } - - /* Return mutex ID */ - return ((Mutex*)hMutex); + tt_check(data->handle != NULL); + tt_mutex_info(data, "alloc "); + return (Mutex)data; } -void tt_mutex_free(Mutex* instance) { +void tt_mutex_free(Mutex mutex) { tt_assert(!TT_IS_IRQ_MODE()); - tt_assert(instance); + tt_assert(mutex); - vSemaphoreDelete((SemaphoreHandle_t)((uint32_t)instance & ~1U)); + MutexData* data = (MutexData*)mutex; + vSemaphoreDelete(data->handle); + data->handle = NULL; // If the mutex is used after release, this might help debugging + data->type = 0xBAADF00D; // Set to an invalid value + free(data); } -TtStatus tt_mutex_acquire(Mutex* instance, uint32_t timeout) { - SemaphoreHandle_t hMutex; - TtStatus stat; - uint32_t rmtx; +TtStatus tt_mutex_acquire(Mutex mutex, uint32_t timeout) { + tt_assert(mutex); + tt_assert(!TT_IS_IRQ_MODE()); + MutexData* data = (MutexData*)mutex; + tt_assert(data->handle); + TtStatus status = TtStatusOk; - hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U); + tt_mutex_info(mutex, "acquire"); - /* Extract recursive mutex flag */ - rmtx = (uint32_t)instance & 1U; - - stat = TtStatusOk; - - if (TT_IS_IRQ_MODE()) { - stat = TtStatusErrorISR; - } else if (hMutex == NULL) { - stat = TtStatusErrorParameter; - } else { - if (rmtx != 0U) { - if (xSemaphoreTakeRecursive(hMutex, timeout) != pdPASS) { + switch (data->type) { + case MutexTypeNormal: + if (xSemaphoreTake(data->handle, timeout) != pdPASS) { if (timeout != 0U) { - stat = TtStatusErrorTimeout; + status = TtStatusErrorTimeout; } else { - stat = TtStatusErrorResource; + status = TtStatusErrorResource; } } - } else { - if (xSemaphoreTake(hMutex, timeout) != pdPASS) { + break; + case MutexTypeRecursive: + if (xSemaphoreTakeRecursive(data->handle, timeout) != pdPASS) { if (timeout != 0U) { - stat = TtStatusErrorTimeout; + status = TtStatusErrorTimeout; } else { - stat = TtStatusErrorResource; + status = TtStatusErrorResource; } } - } + break; + default: + tt_crash("mutex type unknown/corrupted"); } - /* Return execution status */ - return (stat); + return status; } -TtStatus tt_mutex_release(Mutex* instance) { - SemaphoreHandle_t hMutex; - TtStatus stat; - uint32_t rmtx; +TtStatus tt_mutex_release(Mutex mutex) { + tt_assert(mutex); + assert(!TT_IS_IRQ_MODE()); + MutexData* data = (MutexData*)mutex; + tt_assert(data->handle); + TtStatus status = TtStatusOk; - hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U); + tt_mutex_info(mutex, "release"); - /* Extract recursive mutex flag */ - rmtx = (uint32_t)instance & 1U; - - stat = TtStatusOk; - - if (TT_IS_IRQ_MODE()) { - stat = TtStatusErrorISR; - } else if (hMutex == NULL) { - stat = TtStatusErrorParameter; - } else { - if (rmtx != 0U) { - if (xSemaphoreGiveRecursive(hMutex) != pdPASS) { - stat = TtStatusErrorResource; - } - } else { - if (xSemaphoreGive(hMutex) != pdPASS) { - stat = TtStatusErrorResource; + switch (data->type) { + case MutexTypeNormal: { + if (xSemaphoreGive(data->handle) != pdPASS) { + status = TtStatusErrorResource; } + break; } + case MutexTypeRecursive: + if (xSemaphoreGiveRecursive(data->handle) != pdPASS) { + status = TtStatusErrorResource; + } + break; + default: + tt_crash("mutex type unknown/corrupted %d"); } - /* Return execution status */ - return (stat); + return status; } -ThreadId tt_mutex_get_owner(Mutex* instance) { - SemaphoreHandle_t hMutex; - ThreadId owner; - - hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U); - - if ((TT_IS_IRQ_MODE()) || (hMutex == NULL)) { - owner = 0; - } else { - owner = (ThreadId)xSemaphoreGetMutexHolder(hMutex); - } - - /* Return owner thread ID */ - return (owner); +ThreadId tt_mutex_get_owner(Mutex mutex) { + tt_assert(mutex != NULL); + tt_assert(!TT_IS_IRQ_MODE()); + MutexData* data = (MutexData*)mutex; + tt_assert(data->handle); + return (ThreadId)xSemaphoreGetMutexHolder(data->handle); } diff --git a/tactility-core/src/mutex.h b/tactility-core/src/mutex.h index 79fcc86c..f9e42a7a 100644 --- a/tactility-core/src/mutex.h +++ b/tactility-core/src/mutex.h @@ -16,7 +16,7 @@ typedef enum { MutexTypeRecursive, } MutexType; -typedef void Mutex; +typedef void* Mutex; /** Allocate Mutex * @@ -24,38 +24,38 @@ typedef void Mutex; * * @return pointer to Mutex instance */ -Mutex* tt_mutex_alloc(MutexType type); +Mutex tt_mutex_alloc(MutexType type); /** Free Mutex * - * @param instance The pointer to Mutex instance + * @param mutex The Mutex instance */ -void tt_mutex_free(Mutex* instance); +void tt_mutex_free(Mutex mutex); /** Acquire mutex * - * @param instance The pointer to Mutex instance + * @param mutex The Mutex instance * @param[in] timeout The timeout * * @return The status. */ -TtStatus tt_mutex_acquire(Mutex* instance, uint32_t timeout); +TtStatus tt_mutex_acquire(Mutex mutex, uint32_t timeout); /** Release mutex * - * @param instance The pointer to Mutex instance + * @param mutex The Mutex instance * * @return The status. */ -TtStatus tt_mutex_release(Mutex* instance); +TtStatus tt_mutex_release(Mutex mutex); /** Get mutex owner thread id * - * @param instance The pointer to Mutex instance + * @param mutex The Mutex instance * * @return The thread identifier. */ -ThreadId tt_mutex_get_owner(Mutex* instance); +ThreadId tt_mutex_get_owner(Mutex mutex); #ifdef __cplusplus } diff --git a/tactility-core/src/pubsub.c b/tactility-core/src/pubsub.c index 5f193202..7bb65f7f 100644 --- a/tactility-core/src/pubsub.c +++ b/tactility-core/src/pubsub.c @@ -19,7 +19,7 @@ struct PubSub { PubSub* tt_pubsub_alloc() { PubSub* pubsub = malloc(sizeof(PubSub)); - pubsub->mutex = tt_mutex_alloc(MutexTypeRecursive); + pubsub->mutex = tt_mutex_alloc(MutexTypeNormal); tt_assert(pubsub->mutex); PubSubSubscriptionList_init(pubsub->items); diff --git a/tactility-core/src/tactility_core.h b/tactility-core/src/tactility_core.h index 91aa4988..ef68b474 100644 --- a/tactility-core/src/tactility_core.h +++ b/tactility-core/src/tactility_core.h @@ -9,4 +9,5 @@ #include "core_types.h" #include "critical.h" #include "event_flag.h" +#include "kernel.h" #include "log.h" diff --git a/tactility-core/src/tactility_core_config.h b/tactility-core/src/tactility_core_config.h index a47fbfbd..09729e39 100644 --- a/tactility-core/src/tactility_core_config.h +++ b/tactility-core/src/tactility_core_config.h @@ -1,3 +1,3 @@ #pragma once -#define TT_CONFIG_THREAD_MAX_PRIORITIES (32) \ No newline at end of file +#define TT_CONFIG_THREAD_MAX_PRIORITIES 10 \ No newline at end of file diff --git a/tactility-core/src/thread.h b/tactility-core/src/thread.h index 00a8a3c6..f8241f18 100644 --- a/tactility-core/src/thread.h +++ b/tactility-core/src/thread.h @@ -21,11 +21,11 @@ typedef enum { typedef enum { ThreadPriorityNone = 0, /**< Uninitialized, choose system default */ ThreadPriorityIdle = 1, /**< Idle priority */ - ThreadPriorityLowest = 14, /**< Lowest */ - ThreadPriorityLow = 15, /**< Low */ - ThreadPriorityNormal = 16, /**< Normal */ - ThreadPriorityHigh = 17, /**< High */ - ThreadPriorityHighest = 18, /**< Highest */ + ThreadPriorityLowest = 2, /**< Lowest */ + ThreadPriorityLow = 3, /**< Low */ + ThreadPriorityNormal = 4, /**< Normal */ + ThreadPriorityHigh = 5, /**< High */ + ThreadPriorityHighest = 6, /**< Highest */ ThreadPriorityIsr = (TT_CONFIG_THREAD_MAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */ } ThreadPriority; diff --git a/tactility-esp/src/apps/system/wifi_connect/wifi_connect.h b/tactility-esp/src/apps/system/wifi_connect/wifi_connect.h index c7f4da9a..bdd76f29 100644 --- a/tactility-esp/src/apps/system/wifi_connect/wifi_connect.h +++ b/tactility-esp/src/apps/system/wifi_connect/wifi_connect.h @@ -12,7 +12,7 @@ extern "C" { typedef struct { PubSubSubscription* wifi_subscription; - Mutex* mutex; + Mutex mutex; WifiConnectState state; WifiConnectView view; bool view_enabled; diff --git a/tactility-esp/src/apps/system/wifi_manage/wifi_manage.h b/tactility-esp/src/apps/system/wifi_manage/wifi_manage.h index bc7ab626..0701302e 100644 --- a/tactility-esp/src/apps/system/wifi_manage/wifi_manage.h +++ b/tactility-esp/src/apps/system/wifi_manage/wifi_manage.h @@ -10,7 +10,7 @@ extern "C" { typedef struct { PubSubSubscription* wifi_subscription; - Mutex* mutex; + Mutex mutex; WifiManageState state; WifiManageView view; bool view_enabled; diff --git a/tactility-esp/src/display.c b/tactility-esp/src/display.c index bed0c5a8..accd40ee 100644 --- a/tactility-esp/src/display.c +++ b/tactility-esp/src/display.c @@ -1,8 +1,8 @@ #include "check.h" #include "display.h" -DisplayDevice _Nonnull* tt_display_device_alloc(DisplayDriver _Nonnull* driver) { - DisplayDevice _Nonnull* display = malloc(sizeof(DisplayDevice)); +DisplayDevice* tt_display_device_alloc(DisplayDriver* driver) { + DisplayDevice* display = malloc(sizeof(DisplayDevice)); memset(display, 0, sizeof(DisplayDevice)); tt_check(driver->create_display_device(display), "failed to create display"); tt_check(display->io_handle != NULL); diff --git a/tactility-esp/src/display.h b/tactility-esp/src/display.h index 515e8bb0..79a2fd52 100644 --- a/tactility-esp/src/display.h +++ b/tactility-esp/src/display.h @@ -7,8 +7,8 @@ extern "C" { #endif typedef struct { - esp_lcd_panel_io_handle_t _Nonnull io_handle; - esp_lcd_panel_handle_t _Nonnull display_handle; + esp_lcd_panel_io_handle_t io_handle; + esp_lcd_panel_handle_t display_handle; uint16_t horizontal_resolution; uint16_t vertical_resolution; uint16_t draw_buffer_height; @@ -31,7 +31,7 @@ typedef struct { * @param[in] driver * @return allocated display object */ -DisplayDevice _Nonnull* tt_display_device_alloc(DisplayDriver _Nonnull* driver); +DisplayDevice* tt_display_device_alloc(DisplayDriver* driver); #ifdef __cplusplus } diff --git a/tactility-esp/src/graphics.c b/tactility-esp/src/graphics.c index f110a3cb..363bb3bf 100644 --- a/tactility-esp/src/graphics.c +++ b/tactility-esp/src/graphics.c @@ -5,7 +5,7 @@ #define TAG "lvgl" -Lvgl tt_graphics_init(Hardware _Nonnull* hardware) { +Lvgl tt_graphics_init(Hardware* hardware) { const lvgl_port_cfg_t lvgl_cfg = { .task_priority = 4, .task_stack = 8096, @@ -15,7 +15,7 @@ Lvgl tt_graphics_init(Hardware _Nonnull* hardware) { }; tt_check(lvgl_port_init(&lvgl_cfg) == ESP_OK, "lvgl port init failed"); - DisplayDevice _Nonnull* display = hardware->display; + DisplayDevice* display = hardware->display; // Add display TT_LOG_I(TAG, "lvgl add display"); @@ -37,7 +37,7 @@ Lvgl tt_graphics_init(Hardware _Nonnull* hardware) { } }; - lv_disp_t _Nonnull* disp = lvgl_port_add_disp(&disp_cfg); + lv_disp_t* disp = lvgl_port_add_disp(&disp_cfg); tt_check(disp != NULL, "failed to add display"); lv_indev_t _Nullable* touch_indev = NULL; diff --git a/tactility-esp/src/graphics.h b/tactility-esp/src/graphics.h index fddc59db..cfca7ab1 100644 --- a/tactility-esp/src/graphics.h +++ b/tactility-esp/src/graphics.h @@ -7,7 +7,7 @@ extern "C" { #endif typedef struct { - lv_disp_t* _Nonnull disp; + lv_disp_t* disp; lv_indev_t* _Nullable touch_indev; } Lvgl; diff --git a/tactility-esp/src/graphics_i.h b/tactility-esp/src/graphics_i.h index c4517a4c..19155b7c 100644 --- a/tactility-esp/src/graphics_i.h +++ b/tactility-esp/src/graphics_i.h @@ -1,13 +1,13 @@ #pragma once #include "graphics.h" -#include "hardare.h" +#include "hardware.h" #ifdef __cplusplus extern "C" { #endif -Lvgl tt_graphics_init(Hardware _Nonnull* hardware); +Lvgl tt_graphics_init(Hardware* hardware); #ifdef __cplusplus } diff --git a/tactility-esp/src/hardware.c b/tactility-esp/src/hardware.c index 39691037..74259da2 100644 --- a/tactility-esp/src/hardware.c +++ b/tactility-esp/src/hardware.c @@ -4,7 +4,7 @@ #define TAG "hardware" -Hardware tt_hardware_init(const HardwareConfig _Nonnull* config) { +Hardware tt_hardware_init(const HardwareConfig* config) { if (config->bootstrap != NULL) { TT_LOG_I(TAG, "Bootstrapping"); config->bootstrap(); diff --git a/tactility-esp/src/hardare.h b/tactility-esp/src/hardware.h similarity index 83% rename from tactility-esp/src/hardare.h rename to tactility-esp/src/hardware.h index 40cf5b2f..3ca3f512 100644 --- a/tactility-esp/src/hardare.h +++ b/tactility-esp/src/hardware.h @@ -8,7 +8,7 @@ extern "C" { #endif typedef struct { - DisplayDevice* _Nonnull display; + DisplayDevice* display; TouchDevice* _Nullable touch; } Hardware; diff --git a/tactility-esp/src/hardware_i.h b/tactility-esp/src/hardware_i.h index cf686e02..34fd33e2 100644 --- a/tactility-esp/src/hardware_i.h +++ b/tactility-esp/src/hardware_i.h @@ -6,7 +6,7 @@ extern "C" { #endif -Hardware tt_hardware_init(const HardwareConfig _Nonnull* config); +Hardware tt_hardware_init(const HardwareConfig* config); #ifdef __cplusplus } diff --git a/tactility-esp/src/services/wifi/wifi.c b/tactility-esp/src/services/wifi/wifi.c index c3ffef31..6bbbb3bc 100644 --- a/tactility-esp/src/services/wifi/wifi.c +++ b/tactility-esp/src/services/wifi/wifi.c @@ -17,7 +17,7 @@ typedef struct { /** @brief Locking mechanism for modifying the Wifi instance */ - Mutex* mutex; + Mutex mutex; /** @brief The public event bus */ PubSub* pubsub; /** @brief The internal message queue */ diff --git a/tactility-esp/src/services/wifi/wifi_credentials.c b/tactility-esp/src/services/wifi/wifi_credentials.c index d23dc752..69801558 100644 --- a/tactility-esp/src/services/wifi/wifi_credentials.c +++ b/tactility-esp/src/services/wifi/wifi_credentials.c @@ -11,35 +11,40 @@ #define TT_NVS_NAMESPACE "tt_wifi_cred" // limited by NVS_KEY_NAME_MAX_SIZE #define TT_NVS_PARTITION "nvs" -static void tt_wifi_credentials_mutex_lock(); -static void tt_wifi_credentials_mutex_unlock(); +static void hash_reset_all(); // region Hash -static Mutex* hash_mutex = NULL; -static int8_t ssid_hash_index = -1; -static uint32_t ssid_hashes[TT_WIFI_CREDENTIALS_LIMIT] = { 0 }; +static Mutex hash_mutex = NULL; +static int8_t hash_index = -1; +static uint32_t hashes[TT_WIFI_CREDENTIALS_LIMIT] = { 0 }; + +static void hash_init() { + tt_assert(hash_mutex == NULL); + hash_mutex = tt_mutex_alloc(MutexTypeNormal); + hash_reset_all(); +} + +static void tt_hash_mutex_lock() { + tt_assert(tt_mutex_acquire(hash_mutex, 100) == TtStatusOk); +} + +static void tt_hash_mutex_unlock() { + tt_assert(tt_mutex_release(hash_mutex) == TtStatusOk); +} static int hash_find_value(uint32_t hash) { - tt_wifi_credentials_mutex_lock(); - for (int i = 0; i < ssid_hash_index; ++i) { - if (ssid_hashes[i] == hash) { + tt_hash_mutex_lock(); + for (int i = 0; i < hash_index; ++i) { + if (hashes[i] == hash) { + tt_hash_mutex_unlock(); return i; } } - tt_wifi_credentials_mutex_unlock(); + tt_hash_mutex_unlock(); return -1; } -static int hash_find_string(const char* ssid) { - uint32_t hash = tt_hash_string_djb2(ssid); - return hash_find_value(hash); -} - -static bool hash_contains_string(const char* ssid) { - return hash_find_string(ssid) != -1; -} - static bool hash_contains_value(uint32_t value) { return hash_find_value(value) != -1; } @@ -47,30 +52,22 @@ static bool hash_contains_value(uint32_t value) { static void hash_add(const char* ssid) { uint32_t hash = tt_hash_string_djb2(ssid); if (!hash_contains_value(hash)) { - tt_wifi_credentials_mutex_lock(); - tt_check((ssid_hash_index + 1) < TT_WIFI_CREDENTIALS_LIMIT, "exceeding wifi credentials list size"); - ssid_hash_index++; - ssid_hashes[ssid_hash_index] = hash; - tt_wifi_credentials_mutex_unlock(); + tt_hash_mutex_lock(); + tt_check((hash_index + 1) < TT_WIFI_CREDENTIALS_LIMIT, "exceeding wifi credentials list size"); + hash_index++; + hashes[hash_index] = hash; + tt_hash_mutex_unlock(); } } static void hash_reset_all() { - ssid_hash_index = -1; + hash_index = -1; } // endregion Hash // region Wi-Fi Credentials - static -static void tt_wifi_credentials_mutex_lock() { - tt_mutex_acquire(hash_mutex, TtWaitForever); -} - -static void tt_wifi_credentials_mutex_unlock() { - tt_mutex_release(hash_mutex); -} - static esp_err_t tt_wifi_credentials_nvs_open(nvs_handle_t* handle, nvs_open_mode_t mode) { return nvs_open(TT_NVS_NAMESPACE, NVS_READWRITE, handle); } @@ -122,11 +119,8 @@ bool tt_wifi_credentials_contains(const char* ssid) { } void tt_wifi_credentials_init() { - hash_reset_all(); - - if (hash_mutex == NULL) { - hash_mutex = tt_mutex_alloc(MutexTypeRecursive); - } + TT_LOG_I(TAG, "init started"); + hash_init(); nvs_handle_t handle; esp_err_t result = tt_wifi_credentials_nvs_open(&handle, NVS_READWRITE); @@ -146,6 +140,7 @@ void tt_wifi_credentials_init() { nvs_release_iterator(iterator); tt_wifi_credentials_nvs_close(handle); + TT_LOG_I(TAG, "init finished"); } bool tt_wifi_credentials_get(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) { diff --git a/tactility-esp/src/tactility-esp.h b/tactility-esp/src/tactility-esp.h index e66c659e..1314a116 100644 --- a/tactility-esp/src/tactility-esp.h +++ b/tactility-esp/src/tactility-esp.h @@ -1,6 +1,6 @@ #pragma once -#include "hardare.h" +#include "hardware.h" #include "tactility.h" #ifdef __cplusplus @@ -16,7 +16,7 @@ typedef struct { // Optional bootstrapping method (e.g. to turn peripherals on) const Bootstrap _Nullable bootstrap; // Required driver for display - const CreateDisplayDriver _Nonnull display_driver; + const CreateDisplayDriver display_driver; // Optional driver for touch input const CreateTouchDriver _Nullable touch_driver; } HardwareConfig; diff --git a/tactility-esp/src/touch.c b/tactility-esp/src/touch.c index 78c1aa39..5db3b8b7 100644 --- a/tactility-esp/src/touch.c +++ b/tactility-esp/src/touch.c @@ -1,8 +1,8 @@ #include "check.h" #include "touch.h" -TouchDevice _Nonnull* tt_touch_alloc(TouchDriver _Nonnull* driver) { - TouchDevice _Nonnull* touch = malloc(sizeof(TouchDevice)); +TouchDevice* tt_touch_alloc(TouchDriver* driver) { + TouchDevice* touch = malloc(sizeof(TouchDevice)); bool success = driver->create_touch_device( &(touch->io_handle), &(touch->touch_handle) diff --git a/tactility-esp/src/touch.h b/tactility-esp/src/touch.h index 638cb3ba..3a8ee048 100644 --- a/tactility-esp/src/touch.h +++ b/tactility-esp/src/touch.h @@ -15,15 +15,15 @@ typedef struct { } TouchDriver; typedef struct { - esp_lcd_panel_io_handle_t _Nonnull io_handle; - esp_lcd_touch_handle_t _Nonnull touch_handle; + esp_lcd_panel_io_handle_t io_handle; + esp_lcd_touch_handle_t touch_handle; } TouchDevice; /** * @param[in] driver * @return a newly allocated instance */ -TouchDevice _Nonnull* tt_touch_alloc(TouchDriver _Nonnull* driver); +TouchDevice* tt_touch_alloc(TouchDriver* driver); #ifdef __cplusplus } diff --git a/tactility/src/app.c b/tactility/src/app.c index 84fdc2a9..b93473a6 100644 --- a/tactility/src/app.c +++ b/tactility/src/app.c @@ -10,7 +10,7 @@ static AppFlags tt_app_get_flags_default(AppType type); App tt_app_alloc(const AppManifest* manifest, Bundle* _Nullable parameters) { AppData* data = malloc(sizeof(AppData)); *data = (AppData) { - .mutex = tt_mutex_alloc(MutexTypeRecursive), + .mutex = tt_mutex_alloc(MutexTypeNormal), .state = APP_STATE_INITIAL, .flags = tt_app_get_flags_default(manifest->type), .manifest = manifest, @@ -34,7 +34,7 @@ void tt_app_free(App app) { // region Internal static void tt_app_lock(AppData* data) { - tt_mutex_acquire(data->mutex, MutexTypeRecursive); + tt_mutex_acquire(data->mutex, TtWaitForever); } static void tt_app_unlock(AppData* data) { diff --git a/tactility/src/app_i.h b/tactility/src/app_i.h index 127661c8..97573586 100644 --- a/tactility/src/app_i.h +++ b/tactility/src/app_i.h @@ -11,7 +11,7 @@ extern "C" { #endif typedef struct { - Mutex* mutex; + Mutex mutex; const AppManifest* manifest; AppState state; AppFlags flags; diff --git a/tactility/src/app_manifest_registry.c b/tactility/src/app_manifest_registry.c index 9e037d8b..4af0d852 100644 --- a/tactility/src/app_manifest_registry.c +++ b/tactility/src/app_manifest_registry.c @@ -21,7 +21,7 @@ DICT_DEF2(AppManifestDict, const char*, M_CSTR_DUP_OPLIST, const AppManifest*, M } AppManifestDict_t app_manifest_dict; -Mutex* hash_mutex = NULL; +Mutex hash_mutex = NULL; void tt_app_manifest_registry_init() { tt_assert(hash_mutex == NULL); @@ -39,7 +39,7 @@ void app_registry_unlock() { tt_mutex_release(hash_mutex); } -void tt_app_manifest_registry_add(const AppManifest _Nonnull* manifest) { +void tt_app_manifest_registry_add(const AppManifest* manifest) { TT_LOG_I(TAG, "adding %s", manifest->id); app_registry_lock(); @@ -47,7 +47,7 @@ void tt_app_manifest_registry_add(const AppManifest _Nonnull* manifest) { app_registry_unlock(); } -void tt_app_manifest_registry_remove(const AppManifest _Nonnull* manifest) { +void tt_app_manifest_registry_remove(const AppManifest* manifest) { TT_LOG_I(TAG, "removing %s", manifest->id); app_registry_lock(); AppManifestDict_erase(app_manifest_dict, manifest->id); diff --git a/tactility/src/app_manifest_registry.h b/tactility/src/app_manifest_registry.h index 068a8f5b..ac4fa23b 100644 --- a/tactility/src/app_manifest_registry.h +++ b/tactility/src/app_manifest_registry.h @@ -9,8 +9,8 @@ extern "C" { typedef void (*AppManifestCallback)(const AppManifest*, void* context); void tt_app_manifest_registry_init(); -void tt_app_manifest_registry_add(const AppManifest _Nonnull* manifest); -void tt_app_manifest_registry_remove(const AppManifest _Nonnull* manifest); +void tt_app_manifest_registry_add(const AppManifest* manifest); +void tt_app_manifest_registry_remove(const AppManifest* manifest); const AppManifest _Nullable* tt_app_manifest_registry_find_by_id(const char* id); void tt_app_manifest_registry_for_each(AppManifestCallback callback, void* _Nullable context); void tt_app_manifest_registry_for_each_of_type(AppType type, void* _Nullable context, AppManifestCallback callback); diff --git a/tactility/src/service.c b/tactility/src/service.c index bb3bbe9c..6c327654 100644 --- a/tactility/src/service.c +++ b/tactility/src/service.c @@ -4,11 +4,11 @@ // region Alloc/free -ServiceData* tt_service_alloc(const ServiceManifest* _Nonnull manifest) { +ServiceData* tt_service_alloc(const ServiceManifest* manifest) { ServiceData* data = malloc(sizeof(ServiceData)); *data = (ServiceData) { .manifest = manifest, - .mutex = tt_mutex_alloc(MutexTypeRecursive), + .mutex = tt_mutex_alloc(MutexTypeNormal), .data = NULL }; return data; @@ -25,7 +25,7 @@ void tt_service_free(ServiceData* data) { // region Internal static void tt_service_lock(ServiceData * data) { - tt_mutex_acquire(data->mutex, MutexTypeRecursive); + tt_mutex_acquire(data->mutex, TtWaitForever); } static void tt_service_unlock(ServiceData* data) { diff --git a/tactility/src/service_i.h b/tactility/src/service_i.h index 54a6b91e..5c9119f6 100644 --- a/tactility/src/service_i.h +++ b/tactility/src/service_i.h @@ -6,10 +6,10 @@ #include "service_manifest.h" typedef struct { - Mutex* mutex; + Mutex mutex; const ServiceManifest* manifest; void* data; } ServiceData; -ServiceData* tt_service_alloc(const ServiceManifest* _Nonnull manifest); -void tt_service_free(ServiceData* _Nonnull service); +ServiceData* tt_service_alloc(const ServiceManifest* manifest); +void tt_service_free(ServiceData* service); diff --git a/tactility/src/service_registry.c b/tactility/src/service_registry.c index b211dc3e..073af6b6 100644 --- a/tactility/src/service_registry.c +++ b/tactility/src/service_registry.c @@ -25,8 +25,8 @@ DICT_DEF2(ServiceInstanceDict, const char*, M_CSTR_DUP_OPLIST, const ServiceData static ServiceManifestDict_t service_manifest_dict; static ServiceInstanceDict_t service_instance_dict; -static Mutex* manifest_mutex = NULL; -static Mutex* instance_mutex = NULL; +static Mutex manifest_mutex = NULL; +static Mutex instance_mutex = NULL; void tt_service_registry_init() { tt_assert(manifest_mutex == NULL); @@ -57,7 +57,7 @@ void service_registry_manifest_unlock() { tt_assert(manifest_mutex != NULL); tt_mutex_release(manifest_mutex); } -void tt_service_registry_add(const ServiceManifest _Nonnull* manifest) { +void tt_service_registry_add(const ServiceManifest* manifest) { TT_LOG_I(TAG, "adding %s", manifest->id); service_registry_manifest_lock(); @@ -65,7 +65,7 @@ void tt_service_registry_add(const ServiceManifest _Nonnull* manifest) { service_registry_manifest_unlock(); } -void tt_service_registry_remove(const ServiceManifest _Nonnull* manifest) { +void tt_service_registry_remove(const ServiceManifest* manifest) { TT_LOG_I(TAG, "removing %s", manifest->id); service_registry_manifest_lock(); ServiceManifestDict_erase(service_manifest_dict, manifest->id); diff --git a/tactility/src/services/gui/gui.c b/tactility/src/services/gui/gui.c index 35b90b27..ed481385 100644 --- a/tactility/src/services/gui/gui.c +++ b/tactility/src/services/gui/gui.c @@ -30,7 +30,7 @@ Gui* gui_alloc() { &gui_main, NULL ); - instance->mutex = tt_mutex_alloc(MutexTypeRecursive); + instance->mutex = tt_mutex_alloc(MutexTypeNormal); instance->keyboard = NULL; tt_check(tt_lvgl_lock(100)); diff --git a/tactility/src/services/gui/gui_i.h b/tactility/src/services/gui/gui_i.h index 20ecad3b..595e17dd 100644 --- a/tactility/src/services/gui/gui_i.h +++ b/tactility/src/services/gui/gui_i.h @@ -17,7 +17,7 @@ struct Gui { // Thread and lock Thread* thread; - Mutex* mutex; + Mutex mutex; // Layers and Canvas lv_obj_t* lvgl_parent; diff --git a/tactility/src/services/loader/loader.c b/tactility/src/services/loader/loader.c index e5ee881c..7e099f21 100644 --- a/tactility/src/services/loader/loader.c +++ b/tactility/src/services/loader/loader.c @@ -169,7 +169,7 @@ static void app_transition_to_state(App app, AppState state) { } LoaderStatus loader_do_start_app_with_manifest( - const AppManifest* _Nonnull manifest, + const AppManifest* manifest, Bundle* _Nullable bundle ) { TT_LOG_I(TAG, "start with manifest %s", manifest->id); diff --git a/tactility/src/tactility.c b/tactility/src/tactility.c index f6b73aa9..605d38e8 100644 --- a/tactility/src/tactility.c +++ b/tactility/src/tactility.c @@ -74,7 +74,6 @@ TT_UNUSED void tt_init(const Config* config) { start_system_services(); register_and_start_user_services(config->services); - TT_LOG_I(TAG, "tt_init starting desktop app"); loader_start_app(desktop_app.id, true, NULL);