commit 8e6491423510ac235a04493577b7a81e83f0f33f Author: alex Date: Fri Sep 16 11:38:02 2022 +0200 Squashed 'lib/lv_drivers/' content from commit 71830257 git-subtree-dir: lib/lv_drivers git-subtree-split: 71830257710f430b6d8d1c324f89f2eab52488f1 diff --git a/.github/auto-comment.yml b/.github/auto-comment.yml new file mode 100644 index 0000000..8ff0cf1 --- /dev/null +++ b/.github/auto-comment.yml @@ -0,0 +1,12 @@ +# Comment to a new issue. +pullRequestOpened: | + Thank you for raising your pull request. + + To ensure that all licensing criteria is met all repositories of the LVGL project apply a process called DCO (Developer's Certificate of Origin). + + The text of DCO can be read here: https://developercertificate.org/ + For a more detailed description see the [Documentation](https://docs.lvgl.io/latest/en/html/contributing/index.html#developer-certification-of-origin-dco) site. + + By contributing to any repositories of the LVGL project you state that your contribution corresponds with the DCO. + + No further action is required if your contribution fulfills the DCO. If you are not sure about it feel free to ask us in a comment. diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..ea1179b --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,17 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 21 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - architecture + - pinned +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + 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/.gitignore b/.gitignore new file mode 100644 index 0000000..fd73aaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/*.o +**/*.d \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fadccaa --- /dev/null +++ b/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/LICENSE b/LICENSE new file mode 100644 index 0000000..cc227ab --- /dev/null +++ b/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/README.md b/README.md new file mode 100644 index 0000000..6a2da33 --- /dev/null +++ b/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/display/GC9A01.c b/display/GC9A01.c new file mode 100644 index 0000000..005f5e1 --- /dev/null +++ b/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/display/GC9A01.h b/display/GC9A01.h new file mode 100644 index 0000000..1076983 --- /dev/null +++ b/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/display/ILI9341.c b/display/ILI9341.c new file mode 100644 index 0000000..580f0ce --- /dev/null +++ b/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/display/ILI9341.h b/display/ILI9341.h new file mode 100644 index 0000000..3120a41 --- /dev/null +++ b/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/display/R61581.c b/display/R61581.c new file mode 100644 index 0000000..4d21bff --- /dev/null +++ b/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/display/R61581.h b/display/R61581.h new file mode 100644 index 0000000..3ba4ff9 --- /dev/null +++ b/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/display/SHARP_MIP.c b/display/SHARP_MIP.c new file mode 100644 index 0000000..9301aff --- /dev/null +++ b/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/display/SHARP_MIP.h b/display/SHARP_MIP.h new file mode 100644 index 0000000..c10d845 --- /dev/null +++ b/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/display/SSD1963.c b/display/SSD1963.c new file mode 100644 index 0000000..c961066 --- /dev/null +++ b/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/display/SSD1963.h b/display/SSD1963.h new file mode 100644 index 0000000..49639f5 --- /dev/null +++ b/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/display/ST7565.c b/display/ST7565.c new file mode 100644 index 0000000..e4eac4b --- /dev/null +++ b/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/display/ST7565.h b/display/ST7565.h new file mode 100644 index 0000000..1a96df4 --- /dev/null +++ b/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/display/UC1610.c b/display/UC1610.c new file mode 100644 index 0000000..362aead --- /dev/null +++ b/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/display/UC1610.h b/display/UC1610.h new file mode 100644 index 0000000..38b1fa3 --- /dev/null +++ b/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/display/drm.c b/display/drm.c new file mode 100644 index 0000000..9cec6e3 --- /dev/null +++ b/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_ARGB8888 +#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/display/drm.h b/display/drm.h new file mode 100644 index 0000000..ebf2e28 --- /dev/null +++ b/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/display/fbdev.c b/display/fbdev.c new file mode 100644 index 0000000..fe8d2ff --- /dev/null +++ b/display/fbdev.c @@ -0,0 +1,275 @@ +/** + * @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"); + + // Make sure that the display is on. + if (ioctl(fbfd, FBIOBLANK, FB_BLANK_UNBLANK) != 0) { + perror("ioctl(FBIOBLANK)"); + return; + } + +#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/display/fbdev.h b/display/fbdev.h new file mode 100644 index 0000000..b7f2c81 --- /dev/null +++ b/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/display/monitor.h b/display/monitor.h new file mode 100644 index 0000000..6ef4d6d --- /dev/null +++ b/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/docs/astyle_c b/docs/astyle_c new file mode 100644 index 0000000..9b9d7f3 --- /dev/null +++ b/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/docs/astyle_h b/docs/astyle_h new file mode 100644 index 0000000..d9c7633 --- /dev/null +++ b/docs/astyle_h @@ -0,0 +1 @@ +--convert-tabs --indent=spaces=4 diff --git a/gtkdrv/README.md b/gtkdrv/README.md new file mode 100644 index 0000000..b4146fc --- /dev/null +++ b/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/gtkdrv/broadway.png b/gtkdrv/broadway.png new file mode 100644 index 0000000..e0448ff Binary files /dev/null and b/gtkdrv/broadway.png differ diff --git a/gtkdrv/gtkdrv.c b/gtkdrv/gtkdrv.c new file mode 100644 index 0000000..ddaac1e --- /dev/null +++ b/gtkdrv/gtkdrv.c @@ -0,0 +1,323 @@ +/** + * @file gtk.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "gtkdrv.h" + +#if USE_GTK +#define _DEFAULT_SOURCE /* needed for usleep() */ +#include +#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/gtkdrv/gtkdrv.h b/gtkdrv/gtkdrv.h new file mode 100644 index 0000000..42f4609 --- /dev/null +++ b/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/indev/AD_touch.c b/indev/AD_touch.c new file mode 100644 index 0000000..c09c359 --- /dev/null +++ b/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/indev/AD_touch.h b/indev/AD_touch.h new file mode 100644 index 0000000..7c07ab3 --- /dev/null +++ b/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/indev/FT5406EE8.c b/indev/FT5406EE8.c new file mode 100644 index 0000000..9293a54 --- /dev/null +++ b/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/indev/FT5406EE8.h b/indev/FT5406EE8.h new file mode 100644 index 0000000..2d1eda7 --- /dev/null +++ b/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/indev/XPT2046.c b/indev/XPT2046.c new file mode 100644 index 0000000..f27fa76 --- /dev/null +++ b/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/indev/XPT2046.h b/indev/XPT2046.h new file mode 100644 index 0000000..7eee8c0 --- /dev/null +++ b/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/indev/evdev.c b/indev/evdev.c new file mode 100644 index 0000000..4d46b5b --- /dev/null +++ b/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/indev/evdev.h b/indev/evdev.h new file mode 100644 index 0000000..c1b2280 --- /dev/null +++ b/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/indev/keyboard.h b/indev/keyboard.h new file mode 100644 index 0000000..f0067c4 --- /dev/null +++ b/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/indev/libinput.c b/indev/libinput.c new file mode 100644 index 0000000..fc995b1 --- /dev/null +++ b/indev/libinput.c @@ -0,0 +1,491 @@ +/** + * @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 = 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 = 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 < 0 || x > drv->hor_res || y < 0 || y > drv->ver_res) { + break; /* ignore touches that are out of bounds */ + } + state->most_recent_touch_point.x = x; + state->most_recent_touch_point.y = y; + 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_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/indev/libinput_drv.h b/indev/libinput_drv.h new file mode 100644 index 0000000..dd6e929 --- /dev/null +++ b/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/indev/mouse.h b/indev/mouse.h new file mode 100644 index 0000000..4f331f5 --- /dev/null +++ b/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/indev/mousewheel.h b/indev/mousewheel.h new file mode 100644 index 0000000..01c6dbd --- /dev/null +++ b/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/indev/xkb.c b/indev/xkb.c new file mode 100644 index 0000000..7faffef --- /dev/null +++ b/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/indev/xkb.h b/indev/xkb.h new file mode 100644 index 0000000..5018747 --- /dev/null +++ b/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/library.json b/library.json new file mode 100644 index 0000000..7299869 --- /dev/null +++ b/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/lv_drivers.mk b/lv_drivers.mk new file mode 100644 index 0000000..97d4621 --- /dev/null +++ b/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/lv_drv_conf_template.h b/lv_drv_conf_template.h new file mode 100644 index 0000000..8c70874 --- /dev/null +++ b/lv_drv_conf_template.h @@ -0,0 +1,494 @@ +/** + * @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" +#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/sdl/sdl.c b/sdl/sdl.c new file mode 100644 index 0000000..0ac8192 --- /dev/null +++ b/sdl/sdl.c @@ -0,0 +1,390 @@ +/** + * @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 +#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("TFT Simulator", + 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/sdl/sdl.h b/sdl/sdl.h new file mode 100644 index 0000000..234a5bd --- /dev/null +++ b/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/sdl/sdl_common.c b/sdl/sdl_common.c new file mode 100644 index 0000000..64f739d --- /dev/null +++ b/sdl/sdl_common.c @@ -0,0 +1,271 @@ +// +// Created by Mariotaku on 2021/10/14. +// + +#include "sdl_common.h" + +#if USE_SDL || USE_SDL_GPU +/********************* + * 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/sdl/sdl_common.h b/sdl/sdl_common.h new file mode 100644 index 0000000..b2832c4 --- /dev/null +++ b/sdl/sdl_common.h @@ -0,0 +1,104 @@ +/** + * @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 +#include SDL_INCLUDE_PATH + +#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); + +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); + +/********************** + * MACROS + **********************/ +#endif /* USE_SDL || USE_SDL_GPU */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SDL_COMMON_H */ diff --git a/sdl/sdl_gpu.c b/sdl/sdl_gpu.c new file mode 100644 index 0000000..9794cc7 --- /dev/null +++ b/sdl/sdl_gpu.c @@ -0,0 +1,278 @@ +/** + * @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 +#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/sdl/sdl_gpu.h b/sdl/sdl_gpu.h new file mode 100644 index 0000000..78590e8 --- /dev/null +++ b/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/wayland/.gitignore b/wayland/.gitignore new file mode 100644 index 0000000..2661b9b --- /dev/null +++ b/wayland/.gitignore @@ -0,0 +1,5 @@ +CMakeCache.txt +CMakeFiles/ +Makefile +cmake_install.cmake +/protocols diff --git a/wayland/CMakeLists.txt b/wayland/CMakeLists.txt new file mode 100644 index 0000000..6ac6505 --- /dev/null +++ b/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/wayland/README.md b/wayland/README.md new file mode 100644 index 0000000..ba7fe6b --- /dev/null +++ b/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/wayland/wayland.c b/wayland/wayland.c new file mode 100644 index 0000000..11ed05c --- /dev/null +++ b/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/wayland/wayland.h b/wayland/wayland.h new file mode 100644 index 0000000..578af47 --- /dev/null +++ b/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/win32drv/win32drv.c b/win32drv/win32drv.c new file mode 100644 index 0000000..7a80434 --- /dev/null +++ b/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/win32drv/win32drv.h b/win32drv/win32drv.h new file mode 100644 index 0000000..d20c664 --- /dev/null +++ b/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/win_drv.c b/win_drv.c new file mode 100644 index 0000000..f41e2ee --- /dev/null +++ b/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/win_drv.h b/win_drv.h new file mode 100644 index 0000000..784fc9d --- /dev/null +++ b/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*/