From 8b106e219625cd896c34afec746808d67650ccf1 Mon Sep 17 00:00:00 2001 From: Erik Tagirov <162967732+etag4048@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:30:08 +0200 Subject: [PATCH] feat(wayland): add Wayland support (#51) * fix(wayland) Rebase master to get the latest changes * chore(wayland) adjust example code * chore(wayland) align with driver, wait only if the cycle was fully completed * feat(wayland) Simplify CMakeLists file Use external scripts to obtain the selected backend in lv_conf.h Generate wayland protocol via a shell script instead of CMake * fix(wayland) - Remove test script * doc(wayland) Update documentation Remove the step that tells to edit the CMakeLists.txt file and manually enable the option to select the backend * feat(wayland) Move wayland display init and runloop to a separate file Solve merge conflit with the main branch * fix(wayland) Re-apply the delay timer execution only until the next timer fires up * fix(wayland) adjust CI * fix(wayland) fix CI --- .github/workflows/build.yml | 7 +- CMakeLists.txt | 84 +++++++++++++++------- README.md | 77 ++++++++++++-------- backends/interface.h | 6 ++ backends/settings.h | 11 +++ backends/wayland.c | 50 +++++++++++++ main.c | 138 ++++++++++++++++++++++++++++-------- scripts/backend_conf.sh | 2 + scripts/gen_wl_protocols.sh | 16 +++++ 9 files changed, 306 insertions(+), 85 deletions(-) create mode 100644 backends/interface.h create mode 100644 backends/settings.h create mode 100644 backends/wayland.c create mode 100755 scripts/backend_conf.sh create mode 100755 scripts/gen_wl_protocols.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a8ff5e6..e4ba6f41 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,7 +41,7 @@ jobs: sed -i "s/^#define LV_USE_LINUX_DRM.*$/#define LV_USE_LINUX_DRM 0/g" lv_conf.h && \ sed -i "s/^#define LV_USE_SDL.*$/#define LV_USE_SDL 0/g" lv_conf.h && \ docker run --rm -v "$(pwd)":/workdir -t test_${{ matrix.os.image }} \ - /bin/bash -ec "mkdir build/ && cd build/ && cmake .. && make -j && ldd ../bin/main" + /bin/bash -ec "cmake -B build -S . && make -j -C build && ldd bin/lvglsim" - name: DRM Test building the project run: | @@ -50,7 +50,7 @@ jobs: sed -i "s/^#define LV_USE_LINUX_DRM.*$/#define LV_USE_LINUX_DRM 1/g" lv_conf.h && \ sed -i "s/^#define LV_USE_SDL.*$/#define LV_USE_SDL 0/g" lv_conf.h && \ docker run --rm -v "$(pwd)":/workdir -t test_${{ matrix.os.image }} \ - /bin/bash -ec "mkdir build/ && cd build/ && cmake .. && make -j && ldd ../bin/main" + /bin/bash -ec "cmake -B build -S . && make -j -C build && ldd bin/lvglsim" - name: SDL Test building the project run: | @@ -59,4 +59,5 @@ jobs: sed -i "s/^#define LV_USE_LINUX_DRM.*$/#define LV_USE_LINUX_DRM 0/g" lv_conf.h && \ sed -i "s/^#define LV_USE_SDL.*$/#define LV_USE_SDL 1/g" lv_conf.h && \ docker run --rm -v "$(pwd)":/workdir -t test_${{ matrix.os.image }} \ - /bin/bash -ec "mkdir build/ && cd build/ && cmake .. && make -j && ldd ../bin/main" + /bin/bash -ec "cmake -B build -S . && make -j -C build && ldd bin/lvglsim" + diff --git a/CMakeLists.txt b/CMakeLists.txt index 220cb758..e13cf29e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,33 +1,67 @@ cmake_minimum_required(VERSION 3.10) project(lvgl) -set(CMAKE_C_STANDARD 99)#C99 # lvgl officially support C99 and above -set(CMAKE_CXX_STANDARD 17)#C17 -set(CMAKE_CXX_STANDARD_REQUIRED ON) +foreach(BACKEND_NAME "SDL" "LINUX_DRM" "LINUX_FBDEV" "X11" "WAYLAND") + execute_process(COMMAND "scripts/backend_conf.sh" ${BACKEND_NAME} OUTPUT_VARIABLE IS_BACKEND_ENABLED) + set("LV_USE_${BACKEND_NAME}" ${IS_BACKEND_ENABLED}) +endforeach() -set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) +# Uncomment if the program needs debugging +#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -ggdb") +set(CMAKE_C_STANDARD 99) # LVGL officially supports C99 and above +set(CMAKE_CXX_STANDARD 17) #C17 +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) add_subdirectory(lvgl) -add_executable(main main.c mouse_cursor_icon.c) +target_include_directories(lvgl PUBLIC ${PROJECT_SOURCE_DIR}) -find_package(PkgConfig REQUIRED) # We use the platform independent pkg-config to find all the libs -pkg_check_modules(LIBDRM REQUIRED libdrm) -pkg_check_modules(SDL2 REQUIRED sdl2) -pkg_check_modules(SDL2_image REQUIRED SDL2_image) +if (LV_USE_LINUX_DRM) -target_include_directories(lvgl PUBLIC ${PROJECT_SOURCE_DIR}) -target_include_directories(lvgl PRIVATE ${SDL2_INCLUDE_DIRS}) -target_include_directories(lvgl PRIVATE ${SDL2_IMAGE_INCLUDE_DIRS}) -target_include_directories(lvgl PRIVATE ${LIBDRM_INCLUDE_DIRS}) - -target_link_libraries(main PRIVATE lvgl) # Add '-static' if you want a standalone binary -target_link_libraries(main PRIVATE lvgl::examples) -target_link_libraries(main PRIVATE lvgl::demos) -target_link_libraries(main PRIVATE lvgl::thorvg) -target_link_libraries(main PRIVATE ${SDL2_LIBRARIES}) -target_link_libraries(main PRIVATE ${SDL2_IMAGE_LIBRARIES}) -target_link_libraries(main PRIVATE ${LIBDRM_LIBRARIES}) -target_link_libraries(main PRIVATE m) -target_link_libraries(main PRIVATE pthread) - -add_custom_target(run COMMAND ${EXECUTABLE_OUTPUT_PATH}/main DEPENDS main) + find_package(PkgConfig REQUIRED) + pkg_check_modules(LIBDRM REQUIRED libdrm) + + target_include_directories(lvgl PUBLIC ${LIBDRM_INCLUDE_DIRS}) + add_executable(lvglsim main.c mouse_cursor_icon.c) + target_link_libraries(lvglsim lvgl lvgl::examples lvgl::demos lvgl::thorvg ${LIBDRM_LIBRARIES} m pthread) + +elseif (LV_USE_SDL) + + find_package(PkgConfig REQUIRED) + pkg_check_modules(SDL2 REQUIRED sdl2) + pkg_check_modules(SDL2_image REQUIRED SDL2_image) + + target_include_directories(lvgl PRIVATE ${SDL2_INCLUDE_DIRS}) + target_include_directories(lvgl PRIVATE ${SDL2_IMAGE_INCLUDE_DIRS}) + add_executable(lvglsim main.c mouse_cursor_icon.c) + target_link_libraries(lvglsim lvgl lvgl::examples lvgl::demos lvgl::thorvg ${SDL2_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} m pthread) + +elseif (LV_USE_WAYLAND) + + find_package(PkgConfig REQUIRED) + pkg_check_modules(wayland-client REQUIRED wayland-client) + pkg_check_modules(wayland-cursor REQUIRED wayland-cursor) + pkg_check_modules(xkbcommon REQUIRED xkbcommon) + + # Wayland protocols + pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols>=1.25) + pkg_get_variable(WAYLAND_PROTOCOLS_BASE wayland-protocols pkgdatadir) + + execute_process(COMMAND "scripts/gen_wl_protocols.sh" OUTPUT_VARIABLE WAYLAND_PROTOCOLS_SRC) + + target_include_directories(lvgl PRIVATE ${PROJECT_SOURCE_DIR} + "${PROJECT_SOURCE_DIR}/wl_protocols") + add_executable(lvglsim main.c ${WAYLAND_PROTOCOLS_SRC} + mouse_cursor_icon.c backends/wayland.c) + target_compile_definitions(lvglsim PRIVATE LV_CONF_INCLUDE_SIMPLE) + target_link_libraries(lvglsim lvgl lvgl::examples lvgl::demos lvgl::thorvg m + wayland-client wayland-cursor xkbcommon) + +else() + + # No specific build steps required for FBDEV + add_executable(lvglsim main.c mouse_cursor_icon.c) + target_link_libraries(lvglsim lvgl lvgl::examples lvgl::demos lvgl::thorvg m pthread) +endif() + +add_custom_target (run COMMAND ${EXECUTABLE_OUTPUT_PATH}/lvglsim DEPENDS lvglsim) diff --git a/README.md b/README.md index bb4ffd4b..e46deec7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # LVGL on top of Linux graphics stack Example project to use LVGL on top of Linux graphics stack. -Currently supported backends are either legacy framebuffer -(fbdev), modern DRM/KMS, or SDL2. +Currently supported backends are legacy framebuffer +(fbdev), modern DRM/KMS, Wayland or SDL2. By default, legacy framebuffer backend uses `/dev/fb0` device node, DRM/KMS backend uses '/dev/dri/card0' card node, SDL2 uses window @@ -24,13 +24,14 @@ cd lv_port_linux/ git submodule update --init --recursive ``` -## Select graphics backend (optional) +## Select graphics backend To use legacy framebuffer (fbdev) support, adjust `lv_conf.h` as follows: ``` #define LV_USE_LINUX_FBDEV 1 #define LV_USE_LINUX_DRM 0 #define LV_USE_SDL 0 +#define LV_USE_WAYLAND 0 ``` To use modern DRM/KMS support, adjust `lv_conf.h` as follows: @@ -38,6 +39,7 @@ To use modern DRM/KMS support, adjust `lv_conf.h` as follows: #define LV_USE_LINUX_FBDEV 0 #define LV_USE_LINUX_DRM 1 #define LV_USE_SDL 0 +#define LV_USE_WAYLAND 0 ``` To use SDL2 support, adjust `lv_conf.h` as follows: @@ -45,17 +47,24 @@ To use SDL2 support, adjust `lv_conf.h` as follows: #define LV_USE_LINUX_FBDEV 0 #define LV_USE_LINUX_DRM 0 #define LV_USE_SDL 1 +#define LV_USE_WAYLAND 0 +``` + + +To use wayland, adjust `lv_conf.h` as follows: +``` +#define LV_USE_LINUX_FBDEV 0 +#define LV_USE_LINUX_DRM 0 +#define LV_USE_SDL 0 +#define LV_USE_WAYLAND 1 ``` -## Build the project (cmake or Makefile) ### cmake ``` -mkdir build -cd build -cmake .. -make -j +cmake -B build -S . +make -C build -j ``` ### Makefile @@ -64,10 +73,25 @@ make -j make -j ``` +## Command line options + +Command line options are used to modify behavior of the demo, they take precedence over environment variables. + +### Wayland + +- `-f` - enters fullscreen on startup +- `-m` - maximizes window on startup +- `-w ` - set the width of the window +- `-h ` - set the height of the window + +### SDL2 + +- `-w ` - set the width of the window +- `-h ` - set the height of the window + ## Environment variables -Environment variables can be set to modify behavior of the demo. -The following variables are supported. +Environment variables can be set to modify the behavior of the demo. ### Legacy framebuffer (fbdev) @@ -84,19 +108,25 @@ The following variables are supported. ### SDL2 -- `LV_SDL_VIDEO_WIDTH` - width of SDL2 surface (default `800`). -- `LV_SDL_VIDEO_HEIGHT` - height of SDL2 surface (default `480`). +- `LV_SIM_WINDOW_WIDTH` - width of SDL2 surface (default `800`). +- `LV_SIM_WINDOW_HEIGHT` - height of SDL2 surface (default `480`). + +### Wayland + +- `LV_SIM_WINDOW_WIDTH` - width of the window (default `800`). +- `LV_SIM_WINDOW_HEIGHT` - height of the window (default `480`). + ## Run the demo application -### As root +### FBDEV -Normal users don't have access to `/dev/fb0` so use `sudo` (or see below) : +Unpriviledged users don't have access to the framebuffer device `/dev/fb0` +`sudo` or `su` must be used. cmake: ``` -cd ../bin -sudo main +sudo ./bin/lvglsim ``` Makefile: @@ -105,23 +135,12 @@ cd build/bin/ sudo main ``` -### Userland - -You can give a normal user access to the framebuffer by adding them to the `video` group : - +Access to the framebuffer device can be granted by adding the unpriviledged user to the `video` group cmake: ``` sudo adduser $USER video newgrp video -cd ../bin -./main +./bin/lvglsim ``` -Makefile: -``` -sudo adduser $USER video -newgrp video -cd build/bin/ -./main -``` diff --git a/backends/interface.h b/backends/interface.h new file mode 100644 index 00000000..d762a1ec --- /dev/null +++ b/backends/interface.h @@ -0,0 +1,6 @@ + +/* Enters the run loop of the selected backend */ +void lv_linux_run_loop(void); + +/* Initializes the display */ +void lv_linux_disp_init(void); diff --git a/backends/settings.h b/backends/settings.h new file mode 100644 index 00000000..dfe2c606 --- /dev/null +++ b/backends/settings.h @@ -0,0 +1,11 @@ +#include +#include + +/* Settings defined in main.c */ + +extern uint16_t window_width; +extern uint16_t window_height; + +extern bool fullscreen; +extern bool maximize; + diff --git a/backends/wayland.c b/backends/wayland.c new file mode 100644 index 00000000..ea39f032 --- /dev/null +++ b/backends/wayland.c @@ -0,0 +1,50 @@ +#include +#include + +#include "lvgl/lvgl.h" +#include "settings.h" + +/* Currently, the wayland driver calls lv_timer_handler internaly */ +void lv_linux_run_loop(void) +{ + + bool completed; + + /* Handle LVGL tasks */ + while (1) { + + completed = lv_wayland_timer_handler(); + + if (completed) { + /* wait only if the cycle was completed */ + usleep(LV_DEF_REFR_PERIOD * 1000); + } + + /* Run until the last window closes */ + if (!lv_wayland_window_is_open(NULL)) { + break; + } + } + +} + +void lv_linux_disp_init(void) +{ + lv_display_t *disp; + lv_group_t *g; + + disp = lv_wayland_window_create(window_width, window_height, + "LVGL Simulator", NULL); + + if (fullscreen) { + lv_wayland_window_set_fullscreen(disp, fullscreen); + } else if (maximize) { + lv_wayland_window_set_maximized(disp, maximize); + } + + g = lv_group_create(); + lv_group_set_default(g); + lv_indev_set_group(lv_wayland_get_keyboard(disp), g); + lv_indev_set_group(lv_wayland_get_pointeraxis(disp), g); + +} diff --git a/main.c b/main.c index b67206b9..5b997f67 100644 --- a/main.c +++ b/main.c @@ -1,11 +1,23 @@ -#include "lvgl/lvgl.h" -#include "lvgl/demos/lv_demos.h" #include #include #include #include #include +#include "lvgl/lvgl.h" +#include "lvgl/demos/lv_demos.h" + +#if LV_USE_WAYLAND +#include "backends/interface.h" +#endif + +uint16_t window_width; +uint16_t window_height; +bool fullscreen; +bool maximize; + +static void configure_simulator(int argc, char **argv); + static const char *getenv_default(const char *name, const char *dflt) { return getenv(name) ? : dflt; @@ -14,18 +26,25 @@ static const char *getenv_default(const char *name, const char *dflt) #if LV_USE_EVDEV static void lv_linux_init_input_pointer(lv_display_t *disp) { - // Enables a pointer (touchscreen/mouse) input device - // Use 'evtest' to find the correct input device. /dev/input/by-id/ is recommeded if possible - // Use /dev/input/by-id/my-mouse-or-touchscreen or /dev/input/eventX - const char *input_device = getenv_default("LV_LINUX_EVDEV_POINTER_DEVICE", "/dev/input/by-id/my-mouse-or-touchscreen"); - lv_indev_t *touch = lv_evdev_create(LV_INDEV_TYPE_POINTER, input_device); + /* Enables a pointer (touchscreen/mouse) input device + * Use 'evtest' to find the correct input device. /dev/input/by-id/ is recommended if possible + * Use /dev/input/by-id/my-mouse-or-touchscreen or /dev/input/eventX + */ + const char *input_device = getenv("LV_LINUX_EVDEV_POINTER_DEVICE"); + + if (input_device == NULL) { + fprintf(stderr, "please set the LV_LINUX_EVDEV_POINTER_DEVICE environment variable\n"); + exit(1); + } + + lv_indev_t *touch = lv_evdev_create(LV_INDEV_TYPE_POINTER, input_device); lv_indev_set_display(touch, disp); - // Disable this if you want no cursor - LV_IMAGE_DECLARE(mouse_cursor_icon); /*Declare the image source.*/ - lv_obj_t * cursor_obj = lv_image_create(lv_screen_active()); /*Create an image object for the cursor */ - lv_image_set_src(cursor_obj, &mouse_cursor_icon); /*Set the image source*/ - lv_indev_set_cursor(touch, cursor_obj); /*Connect the image object to the driver*/ + /* Set the cursor icon */ + LV_IMAGE_DECLARE(mouse_cursor_icon); + lv_obj_t * cursor_obj = lv_image_create(lv_screen_active()); + lv_image_set_src(cursor_obj, &mouse_cursor_icon); + lv_indev_set_cursor(touch, cursor_obj); } #endif @@ -34,11 +53,11 @@ static void lv_linux_disp_init(void) { const char *device = getenv_default("LV_LINUX_FBDEV_DEVICE", "/dev/fb0"); lv_display_t * disp = lv_linux_fbdev_create(); - - #if LV_USE_EVDEV + +#if LV_USE_EVDEV lv_linux_init_input_pointer(disp); - #endif - +#endif + lv_linux_fbdev_set_file(disp, device); } #elif LV_USE_LINUX_DRM @@ -47,40 +66,103 @@ static void lv_linux_disp_init(void) const char *device = getenv_default("LV_LINUX_DRM_CARD", "/dev/dri/card0"); lv_display_t * disp = lv_linux_drm_create(); - #if LV_USE_EVDEV +#if LV_USE_EVDEV lv_linux_init_input_pointer(disp); - #endif +#endif lv_linux_drm_set_file(disp, device, -1); } #elif LV_USE_SDL static void lv_linux_disp_init(void) { - const int width = atoi(getenv("LV_SDL_VIDEO_WIDTH") ? : "800"); - const int height = atoi(getenv("LV_SDL_VIDEO_HEIGHT") ? : "480"); - lv_sdl_window_create(width, height); + lv_sdl_window_create(window_width, window_height); + } +#elif LV_USE_WAYLAND + /* see backend/wayland.c */ #else #error Unsupported configuration #endif -int main(void) +#if LV_USE_WAYLAND == 0 +void lv_linux_run_loop(void) +{ + uint32_t idle_time; + + /*Handle LVGL tasks*/ + while(1) { + + idle_time = lv_timer_handler(); /*Returns the time to the next timer execution*/ + usleep(idle_time * 1000); + } +} +#endif + +/* + * Process command line arguments and environment + * variables to configure the simulator + */ +static void configure_simulator(int argc, char **argv) { + + int opt = 0; + bool err = false; + + /* Default values */ + fullscreen = maximize = false; + window_width = atoi(getenv("LV_SIM_WINDOW_WIDTH") ? : "800"); + window_height = atoi(getenv("LV_SIM_WINDOW_HEIGHT") ? : "480"); + + /* Parse the command-line options. */ + while ((opt = getopt (argc, argv, "fmw:h:")) != -1) { + switch (opt) { + case 'f': + fullscreen = true; + if (LV_USE_WAYLAND == 0) { + fprintf(stderr, "The SDL driver doesn't support fullscreen mode on start\n"); + exit(1); + } + break; + case 'm': + maximize = true; + if (LV_USE_WAYLAND == 0) { + fprintf(stderr, "The SDL driver doesn't support maximized mode on start\n"); + exit(1); + } + break; + case 'w': + window_width = atoi(optarg); + break; + case 'h': + window_height = atoi(optarg); + break; + case ':': + fprintf (stderr, "Option -%c requires an argument.\n", optopt); + exit(1); + case '?': + fprintf (stderr, "Unknown option -%c.\n", optopt); + exit(1); + } + } +} + +int main(int argc, char **argv) +{ + + configure_simulator(argc, argv); + + /* Initialize LVGL. */ lv_init(); - /*Linux display device init*/ + /* Initialize the configured backend SDL2, FBDEV, libDRM or wayland */ lv_linux_disp_init(); /*Create a Demo*/ lv_demo_widgets(); lv_demo_widgets_start_slideshow(); - /*Handle LVGL tasks*/ - while(1) { - uint32_t idle_time = lv_timer_handler(); /*Returns the time to the next timer execution*/ - usleep(idle_time * 1000); - } + lv_linux_run_loop(); return 0; } diff --git a/scripts/backend_conf.sh b/scripts/backend_conf.sh new file mode 100755 index 00000000..750a3a38 --- /dev/null +++ b/scripts/backend_conf.sh @@ -0,0 +1,2 @@ +#!/bin/sh +grep "^#define LV_USE_$1" lv_conf.h | sed 's/#define //g' | awk '{ if ($2=="1") { printf "ON" } else { printf "OFF" }}' diff --git a/scripts/gen_wl_protocols.sh b/scripts/gen_wl_protocols.sh new file mode 100755 index 00000000..02d8b5eb --- /dev/null +++ b/scripts/gen_wl_protocols.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# Generate wayland xdg shell protocol + +if ! test -d /usr/share/wayland-protocols +then + exit 1 +fi + +if ! test -d wl_protocols +then + mkdir wl_protocols + wayland-scanner client-header "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml" "wl_protocols/wayland_xdg_shell.h" + wayland-scanner private-code "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml" "wl_protocols/wayland_xdg_shell.c" +fi + +printf "wl_protocols/wayland_xdg_shell.c"