diff --git a/.gitignore b/.gitignore index 66281c80e..47d79044d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ test_screenshot_error.h build/ tests/build_*/ tests/report/ +tests/wayland_protocols/ .DS_Store .vscode .idea diff --git a/Kconfig b/Kconfig index 0384c073c..7f63e1d52 100644 --- a/Kconfig +++ b/Kconfig @@ -1562,6 +1562,18 @@ menu "LVGL configuration" With 2 buffers in flush_cb only and address change is required. endchoice + config LV_USE_WAYLAND + bool "Use the wayland client to open a window and handle inputs on Linux or BSD" + default n + config LV_WAYLAND_WINDOW_DECORATIONS + bool "Draw client side window decorations, only necessary on Mutter (GNOME)" + depends on LV_USE_WAYLAND + default n + config LV_WAYLAND_WL_SHELL + bool "Support the legacy wl_shell instead of the default XDG Shell protocol" + depends on LV_USE_WAYLAND + default n + config LV_USE_LINUX_FBDEV bool "Use Linux framebuffer device" default n diff --git a/docs/integration/driver/index.rst b/docs/integration/driver/index.rst index ea9788091..7188ce8d3 100644 --- a/docs/integration/driver/index.rst +++ b/docs/integration/driver/index.rst @@ -11,3 +11,4 @@ Drivers X11 windows opengles + wayland diff --git a/docs/integration/driver/wayland.rst b/docs/integration/driver/wayland.rst new file mode 100644 index 000000000..f9860f0f7 --- /dev/null +++ b/docs/integration/driver/wayland.rst @@ -0,0 +1,180 @@ +============================= +Wayland Display/Inputs driver +============================= + +Overview +-------- + +| The **Wayland** `driver `__ offers support for simulating the LVGL display and keyboard/mouse inputs in a desktop window. +| It is an alternative to **X11** or **SDL2** + +The main purpose for this driver is for testing/debugging the LVGL application, it can also be used to run applications in 'kiosk mode' + +Dependencies +------------ + +The wayland driver requires some dependencies. + +On Ubuntu + +.. code:: bash + + sudo apt-get install libwayland-dev libxkbcommon-dev libwayland-bin wayland-protocols + +On Fedora + +.. code:: bash + + sudo dnf install wayland-devel libxkbcommon-devel wayland-utils wayland-protocols-devel + + +Configuring the wayland driver +------------------------------ + +1. Enable the wayland driver in ``lv_conf.h`` + +.. code:: c + + #define LV_USE_WAYLAND 1 + +2. Optional configuration options: + +- Enable window decorations, only required on GNOME because out of all the available wayland compositors + **only** Mutter/GNOME enforces the use of client side decorations + +.. code:: c + + #define LV_WAYLAND_WINDOW_DECORATIONS 1 + +- Enable support for the deprecated 'wl_shell', Only useful when the BSP on the target has weston ``9.x`` + +.. code:: c + + #define LV_WAYLAND_WL_SHELL 1 + +Example +------- + +An example simulator is available in this `repo `__ + +Usage +----- + +#. In ``main.c`` ``#incude "lv_drivers/wayland/wayland.h"`` +#. Enable the Wayland driver in ``lv_conf.h`` with ``LV_USE_WAYLAND 1`` + +#. ``LV_COLOR_DEPTH`` should be set either to ``32`` or ``16`` in ``lv_conf.h`` + +#. Add a display using ``lv_wayland_window_create()``, + possibly with a close callback to track the status of each display: + +.. code:: 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 +* an ENCODER connected to Wayland pointer axis events + +Handles for input devices of each display can be obtained using +``lv_wayland_get_indev_keyboard()``, ``lv_wayland_get_indev_touchscreen()``, +``lv_wayland_get_indev_pointer()`` and ``lv_wayland_get_indev_pointeraxis()`` respectively. + +Fullscreen mode +^^^^^^^^^^^^^^^ + +To programmatically fullscreen the window, +use the ``lv_wayland_window_set_fullscreen()`` function respectively with ``true`` +or ``false`` for the ``fullscreen`` argument. + +Maximized mode +^^^^^^^^^^^^^^ + +To programmatically maximize the window, +use the ``lv_wayland_window_set_maximized()`` function respectively with ``true`` +or ``false`` for the ``maximized`` argument. + + +Custom timer handler +^^^^^^^^^^^^^^^^^^^^ + +Always call ``lv_wayland_timer_handler()`` in your timer loop instead of the regular ``lv_timer_handler()``. + +**Note:** ``lv_wayland_timer_handler()`` internally calls ``lv_timer_handler()`` + +This allows the wayland client to work on well on weston, resizing shared memory buffers during +a commit does not work well on weston. + +Wrapping the call to ``lv_timer_hander()`` is a necessity to have more control over +when the LVGL flush callback is called. + +The custom timer handler returns ``false`` if the frame from previous cycle is not rendered. +When this happens, it usually means that the application is minimized or hidden behind another window. +Causing the driver to wait until the arrival of any message on the wayland socket, the process is in interruptible sleep. + +Building the wayland driver +--------------------------- + +An example simulator is available in this `repo `__ + +If there is a need to use driver with another build system. The source and header files for the XDG shell +must be generated from the definitions for the XDG shell protocol. + +In the example Cmake is used to perform the operation by invoking the ``wayland-scanner`` utility + +To achieve this manually, + +Make sure the dependencies listed at the start of the article are installed. + +The wayland protocol is defined using XML files which are present in ``/usr/share/wayland-protocols`` + +To generate the required files run the following commands: + +.. code-block:: sh + + wayland-scanner client-header wayland_xdg_shell.h + wayland-scanner private-code wayland_xdg_shell.c + +The resulting files can then be integrated into the project, it's better to re-run ``wayland-scanner`` on +each build to ensure that the correct versions are generated, they must match the version of the ``wayland-client`` +dynamically linked library installed on the system. + + +Current state and objectives +---------------------------- + +* Add direct rendering mode +* Refactor the shell integrations to avoid excessive conditional compilation +* Technically, the wayland driver allows to create multiple windows - but this feature is experimental. +* Eventually add enhanced support for XDG shell to allow the creation of desktop apps on Unix-like platforms, + similar to what the win32 driver does. +* Add a support for Mesa, currently wl_shm is used and it's not the most effective technique. + + +Bug reports +----------- + +The wayland driver is currently under construction, bug reports, contributions and feedback is always welcome. + +It is however important to create detailed issues when a problem is encountered, logs and screenshots of the problem are of great help. + +Please enable ``LV_USE_LOG`` and launch the simulator executable like so + +.. code:: + + WAYLAND_DEBUG=1 ./path/to/simulator_executable > /tmp/debug 2>&1 + +This will create a log file called ``debug`` in the ``/tmp`` directory, copy-paste the content of the file in the github issue. +The log file contains LVGL logs and the wayland messages. + +Be sure to replicate the problem quickly otherwise the logs become too big + diff --git a/lv_conf_template.h b/lv_conf_template.h index 9a5915cf3..cf936ee9d 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -955,6 +955,13 @@ #define LV_X11_RENDER_MODE_FULL 0 /*Full render mode*/ #endif +/*Use Wayland to open a window and handle input on Linux or BSD desktops */ +#define LV_USE_WAYLAND 0 +#if LV_USE_WAYLAND + #define LV_WAYLAND_WINDOW_DECORATIONS 0 /*Draw client side window decorations only necessary on Mutter/GNOME*/ + #define LV_WAYLAND_WL_SHELL 0 /*Use the legacy wl_shell protocol instead of the default XDG shell*/ +#endif + /*Driver for /dev/fb*/ #define LV_USE_LINUX_FBDEV 0 #if LV_USE_LINUX_FBDEV diff --git a/scripts/install-prerequisites.sh b/scripts/install-prerequisites.sh index 05d1f1a44..7a6321159 100755 --- a/scripts/install-prerequisites.sh +++ b/scripts/install-prerequisites.sh @@ -11,5 +11,7 @@ sudo apt install gcc gcc-multilib g++-multilib ninja-build \ libpng-dev libjpeg-turbo8-dev libfreetype6-dev \ libglew-dev libglfw3-dev libsdl2-dev libsdl2-image-dev \ libpng-dev:i386 libjpeg-dev:i386 libfreetype6-dev:i386 \ - ruby-full gcovr cmake python3 pngquant libinput-dev libxkbcommon-dev libdrm-dev pkg-config + ruby-full gcovr cmake python3 pngquant libinput-dev libxkbcommon-dev \ + libdrm-dev pkg-config wayland-protocols libwayland-dev libwayland-bin \ + libwayland-dev:i386 libxkbcommon-dev:i386 pip3 install pypng lz4 diff --git a/src/drivers/lv_drivers.h b/src/drivers/lv_drivers.h index 935292947..08c76e777 100644 --- a/src/drivers/lv_drivers.h +++ b/src/drivers/lv_drivers.h @@ -43,6 +43,8 @@ extern "C" { #include "qnx/lv_qnx.h" +#include "wayland/lv_wayland.h" + /********************* * DEFINES *********************/ diff --git a/src/drivers/wayland/lv_wayland.c b/src/drivers/wayland/lv_wayland.c new file mode 100644 index 000000000..dc71f6677 --- /dev/null +++ b/src/drivers/wayland/lv_wayland.c @@ -0,0 +1,2860 @@ +/******************************************************************* + * + * @file lv_wayland.c - The Wayland client for LVGL applications + * + * Based on the original file from the repository. + * + * Porting to LVGL 9.1 + * EDGEMTech Ltd. by Erik Tagirov (erik.tagirov@edgemtech.ch) + * + * See LICENCE.txt for details + * + ******************************************************************/ + +typedef int dummy_t; /* Make GCC on windows happy, avoid empty translation unit */ + +#ifndef _WIN32 + +/********************* + * INCLUDES + *********************/ +#include "lv_wayland.h" +#include "lv_wayland_smm.h" + +#if LV_USE_WAYLAND + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lvgl.h" + + +#if !LV_WAYLAND_WL_SHELL + #include "wayland_xdg_shell.h" + #define LV_WAYLAND_XDG_SHELL 1 +#else + #define LV_WAYLAND_XDG_SHELL 0 +#endif + + +/********************* + * DEFINES + *********************/ + +#define LVGL_DRAW_BUFFER_DIV (8) +#define DMG_CACHE_CAPACITY (32) +#define TAG_LOCAL (0) +#define TAG_BUFFER_DAMAGE (1) + +#if LV_WAYLAND_WINDOW_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_DEF_REFR_PERIOD,1) +#endif + +#define SHM_FORMAT_UNKNOWN 0xFFFFFF + +#if (LV_COLOR_DEPTH == 8 || LV_COLOR_DEPTH == 1) + #error [wayland] Unsupported LV_COLOR_DEPTH +#endif + +/********************** + * TYPEDEFS + **********************/ + +enum object_type { + OBJECT_TITLEBAR = 0, + 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, + OBJECT_WINDOW, +}; + +#define FIRST_DECORATION (OBJECT_TITLEBAR) +#define LAST_DECORATION (OBJECT_BORDER_RIGHT) +#define NUM_DECORATIONS (LAST_DECORATION-FIRST_DECORATION+1) + +struct window; +struct input { + struct { + uint32_t x; + uint32_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 { + uint32_t x; + uint32_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 graphic_object { + struct window * window; + + struct wl_surface * surface; + bool surface_configured; + smm_buffer_t * pending_buffer; + smm_group_t * buffer_group; + struct wl_subsurface * subsurface; + + enum object_type type; + int width; + int height; + + 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_WINDOW_DECORATIONS + bool opt_disable_decorations; +#endif + + uint32_t shm_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 pollfd wayland_pfd; +}; + +struct window { + lv_display_t * lv_disp; + lv_draw_buf_t * lv_disp_draw_buf; + + lv_indev_t * lv_indev_pointer; + lv_indev_t * lv_indev_pointeraxis; + lv_indev_t * lv_indev_touch; + 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; + uint32_t wm_capabilities; +#endif + + struct graphic_object * body; + struct { + lv_area_t cache[DMG_CACHE_CAPACITY]; + unsigned char start; + unsigned char end; + unsigned size; + } dmg_cache; + +#if LV_WAYLAND_WINDOW_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; + uint32_t frame_counter; + bool frame_done; +}; + +/********************************* + * STATIC VARIABLES and FUNTIONS + *********************************/ + +static struct application application; + +static void color_fill(void * pixels, lv_color_t color, uint32_t width, uint32_t height); +static void color_fill_XRGB8888(void * pixels, lv_color_t color, uint32_t width, uint32_t height); +static void color_fill_RGB565(void * pixels, lv_color_t color, uint32_t width, uint32_t height); + +static const struct wl_callback_listener wl_surface_frame_listener; +static bool resize_window(struct window * window, int width, int height); +static struct graphic_object * create_graphic_obj(struct application * app, struct window * window, + enum object_type type, + struct graphic_object * parent); + +static uint32_t tick_get_cb(void); + +static void wayland_init(void); +static void wayland_deinit(void); + +/** + * The frame callback called when the compositor has finished rendering + * a frame.It increments the frame counter and sets up the callback + * for the next frame the frame counter is used to avoid needlessly + * committing frames too fast on a slow system + * + * NOTE: this function is invoked by the wayland-server library within the compositor + * the event is added to the queue, and then upon the next timer call it's + * called indirectly from _lv_wayland_handle_input (via wl_display_dispatch_queue) + * @param void data the user object defined that was tied to this event during + * the configuration of the callback + * @param struct wl_callback The callback that needs to be destroyed and re-created + * @param time Timestamp of the event (unused) + */ +static void graphic_obj_frame_done(void * data, struct wl_callback * cb, uint32_t time) +{ + struct graphic_object * obj; + struct window * window; + + LV_UNUSED(time); + + wl_callback_destroy(cb); + + obj = (struct graphic_object *)data; + window = obj->window; + window->frame_counter++; + + LV_LOG_TRACE("frame: %d done, new frame: %d", + window->frame_counter - 1, window->frame_counter); + + window->frame_done = true; + +} + +static const struct wl_callback_listener wl_surface_frame_listener = { + .done = graphic_obj_frame_done, +}; + +static inline bool _is_digit(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + +/* + * shm_format + * @description called by the compositor to advertise the supported + * color formats for SHM buffers, there is a call per supported format + */ +static void shm_format(void * data, struct wl_shm * wl_shm, uint32_t format) +{ + struct application * app = data; + + LV_UNUSED(wl_shm); + + LV_LOG_TRACE("Supported color space fourcc.h code: %08X", format); + + if(LV_COLOR_DEPTH == 32 && format == WL_SHM_FORMAT_ARGB8888) { + + /* Wayland compositors MUST support ARGB8888 */ + app->shm_format = format; + + } + else if(LV_COLOR_DEPTH == 32 && + format == WL_SHM_FORMAT_XRGB8888 && + app->shm_format != WL_SHM_FORMAT_ARGB8888) { + + /* Select XRGB only if the compositor doesn't support transprancy */ + app->shm_format = format; + + } + else if(LV_COLOR_DEPTH == 16 && format == WL_SHM_FORMAT_RGB565) { + + app->shm_format = format; + + } +} + +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_WINDOW_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; + + LV_UNUSED(pointer); + LV_UNUSED(serial); + + 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; + + LV_UNUSED(pointer); + LV_UNUSED(time); + + 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; + + LV_UNUSED(serial); + LV_UNUSED(wl_pointer); + LV_UNUSED(time); + + const lv_indev_state_t lv_state = + (state == WL_POINTER_BUTTON_STATE_PRESSED) ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; + + if(!app->pointer_obj) { + return; + } + + +#if LV_WAYLAND_WINDOW_DECORATIONS + struct window * 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_WINDOW_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_WINDOW_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); + + LV_UNUSED(time); + LV_UNUSED(wl_pointer); + + 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; + + LV_UNUSED(keyboard); + + 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; + + LV_UNUSED(keyboard); + LV_UNUSED(serial); + LV_UNUSED(keys); + + 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; + + LV_UNUSED(serial); + LV_UNUSED(keyboard); + + 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; + + LV_UNUSED(serial); + LV_UNUSED(time); + LV_UNUSED(keyboard); + + 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_PRESSED : LV_INDEV_STATE_RELEASED; + + 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; + + LV_UNUSED(serial); + LV_UNUSED(keyboard); + + /* 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; + + LV_UNUSED(id); + LV_UNUSED(time); + LV_UNUSED(serial); + LV_UNUSED(wl_touch); + + 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_PRESSED; + +#if LV_WAYLAND_WINDOW_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; + + LV_UNUSED(serial); + LV_UNUSED(time); + LV_UNUSED(id); + LV_UNUSED(wl_touch); + + if(!app->touch_obj) { + return; + } + + app->touch_obj->input.touch.state = LV_INDEV_STATE_RELEASED; + +#if LV_WAYLAND_WINDOW_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_WINDOW_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; + + LV_UNUSED(time); + LV_UNUSED(id); + LV_UNUSED(wl_touch); + + 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) +{ + LV_UNUSED(wl_touch); + LV_UNUSED(data); + +} + +static void touch_handle_cancel(void * data, struct wl_touch * wl_touch) +{ + LV_UNUSED(wl_touch); + LV_UNUSED(data); +} + +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, +}; + +static void draw_window(struct window * window, uint32_t width, uint32_t height) +{ + +#if LV_WAYLAND_WINDOW_DECORATIONS + if(application.opt_disable_decorations == false) { + int d; + for(d = 0; d < NUM_DECORATIONS; d++) { + window->decoration[d] = create_graphic_obj(&application, window, (FIRST_DECORATION + d), window->body); + if(!window->decoration[d]) { + LV_LOG_ERROR("Failed to create decoration %d", d); + } + } + } +#endif + + /* First resize */ + if(!resize_window(window, width, height)) { + LV_LOG_ERROR("Failed to resize window"); +#if LV_WAYLAND_XDG_SHELL + if(window->xdg_toplevel) { + xdg_toplevel_destroy(window->xdg_toplevel); + } +#endif + } + + lv_refr_now(window->lv_disp); + +} + +#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; + + LV_UNUSED(edges); + + 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; + + xdg_surface_ack_configure(xdg_surface, serial); + + if(window->body->surface_configured == false) { + /* This branch is executed at launch */ + if(window->resize_pending == false) { + /* Use the size passed to the create_window function */ + draw_window(window, window->width, window->height); + } + else { + + /* Handle early maximization or fullscreen, */ + /* by using the size communicated by the compositor */ + /* when the initial xdg configure event arrives */ + draw_window(window, window->resize_width, window->resize_height); + window->width = window->resize_width; + window->height = window->resize_height; + window->resize_pending = false; + } + } + 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; + + LV_UNUSED(xdg_toplevel); + LV_UNUSED(states); + LV_UNUSED(width); + LV_UNUSED(height); + + LV_LOG_TRACE("w:%d h:%d", width, height); + LV_LOG_TRACE("current body w:%d h:%d", window->body->width, window->body->height); + LV_LOG_TRACE("window w:%d h:%d", window->width, window->height); + + + if((width <= 0) || (height <= 0)) { + LV_LOG_TRACE("will not resize to w:%d h:%d", width, height); + return; + } + + if((width != window->width) || (height != window->height)) { + window->resize_width = width; + window->resize_height = height; + window->resize_pending = true; + LV_LOG_TRACE("resize_pending is set, will resize to w:%d h:%d", width, height); + } + else { + LV_LOG_TRACE("resize_pending not set w:%d h:%d", width, height); + } +} + +static void xdg_toplevel_handle_close(void * data, struct xdg_toplevel * xdg_toplevel) +{ + struct window * window = (struct window *)data; + window->shall_close = true; + + LV_UNUSED(xdg_toplevel); +} + +static void xdg_toplevel_handle_configure_bounds(void * data, struct xdg_toplevel * xdg_toplevel, + int32_t width, int32_t height) +{ + + LV_UNUSED(width); + LV_UNUSED(height); + LV_UNUSED(data); + LV_UNUSED(xdg_toplevel); + + /* 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) +{ + LV_UNUSED(data); + + xdg_wm_base_pong(xdg_wm_base, serial); + + return; +} + +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; + + LV_UNUSED(version); + LV_UNUSED(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) { + /* Explicitly support version 4 of the xdg protocol */ + app->xdg_wm = wl_registry_bind(app->registry, name, &xdg_wm_base_interface, 4); + 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) +{ + + LV_UNUSED(data); + LV_UNUSED(registry); + LV_UNUSED(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) +{ + const struct smm_buffer_properties * props; + struct graphic_object * obj; + struct window * window; + smm_buffer_t * buf; + + buf = (smm_buffer_t *)data; + props = SMM_BUFFER_PROPERTIES(buf); + obj = SMM_GROUP_PROPERTIES(props->group)->tag[TAG_LOCAL]; + window = obj->window; + + LV_LOG_TRACE("releasing buffer %p wl_buffer %p w:%d h:%d frame: %d", (smm_buffer_t *)data, (void *)wl_buffer, + obj->width, + obj->height, window->frame_counter); + smm_release((smm_buffer_t *)data); +} + +static const struct wl_buffer_listener wl_buffer_listener = { + .release = handle_wl_buffer_release, +}; + +static void cache_clear(struct window * window) +{ + window->dmg_cache.start = window->dmg_cache.end; + window->dmg_cache.size = 0; +} + +static void cache_purge(struct window * window, smm_buffer_t * buf) +{ + lv_area_t * next_dmg; + smm_buffer_t * next_buf = smm_next(buf); + + /* Remove all damage areas up until start of next buffers damage */ + if(next_buf == NULL) { + cache_clear(window); + } + else { + next_dmg = SMM_BUFFER_PROPERTIES(next_buf)->tag[TAG_BUFFER_DAMAGE]; + while((window->dmg_cache.cache + window->dmg_cache.start) != next_dmg) { + window->dmg_cache.start++; + window->dmg_cache.start %= DMG_CACHE_CAPACITY; + window->dmg_cache.size--; + } + } +} + +static void cache_add_area(struct window * window, smm_buffer_t * buf, const lv_area_t * area) +{ + if(SMM_BUFFER_PROPERTIES(buf)->tag[TAG_BUFFER_DAMAGE] == NULL) { + /* Buffer damage beyond cache capacity */ + goto done; + } + + if((window->dmg_cache.start == window->dmg_cache.end) && + (window->dmg_cache.size)) { + /* This buffer has more damage then the cache's capacity, so + * clear cache and leave buffer damage unrecorded + */ + cache_clear(window); + SMM_TAG(buf, TAG_BUFFER_DAMAGE, NULL); + goto done; + } + + /* Add damage area to cache */ + memcpy(window->dmg_cache.cache + window->dmg_cache.end, + area, + sizeof(lv_area_t)); + window->dmg_cache.end++; + window->dmg_cache.end %= DMG_CACHE_CAPACITY; + window->dmg_cache.size++; + +done: + return; +} + +static void cache_apply_areas(struct window * window, void * dest, void * src, smm_buffer_t * src_buf) +{ + unsigned long offset; + unsigned char start; + int32_t y; + lv_area_t * dmg; + lv_area_t * next_dmg; + smm_buffer_t * next_buf = smm_next(src_buf); + const struct smm_buffer_properties * props = SMM_BUFFER_PROPERTIES(src_buf); + struct graphic_object * obj = SMM_GROUP_PROPERTIES(props->group)->tag[TAG_LOCAL]; + uint8_t bpp; + + if(next_buf == NULL) { + next_dmg = (window->dmg_cache.cache + window->dmg_cache.end); + } + else { + next_dmg = SMM_BUFFER_PROPERTIES(next_buf)->tag[TAG_BUFFER_DAMAGE]; + } + + bpp = lv_color_format_get_size(LV_COLOR_FORMAT_NATIVE); + + /* Apply all buffer damage areas */ + start = ((lv_area_t *)SMM_BUFFER_PROPERTIES(src_buf)->tag[TAG_BUFFER_DAMAGE] - window->dmg_cache.cache); + while((window->dmg_cache.cache + start) != next_dmg) { + /* Copy an area from source to destination (line-by-line) */ + dmg = (window->dmg_cache.cache + start); + for(y = dmg->y1; y <= dmg->y2; y++) { + offset = (dmg->x1 + (y * obj->width)) * bpp; + + memcpy(((char *)dest) + offset, + ((char *)src) + offset, + ((dmg->x2 - dmg->x1 + 1) * bpp)); + } + + + start++; + start %= DMG_CACHE_CAPACITY; + } + +} + +static bool sme_new_pool(void * ctx, smm_pool_t * pool) +{ + struct wl_shm_pool * wl_pool; + struct application * app = ctx; + const struct smm_pool_properties * props = SMM_POOL_PROPERTIES(pool); + + LV_UNUSED(ctx); + + wl_pool = wl_shm_create_pool(app->shm, + props->fd, + props->size); + + SMM_TAG(pool, TAG_LOCAL, wl_pool); + return (wl_pool == NULL); +} + +static void sme_expand_pool(void * ctx, smm_pool_t * pool) +{ + const struct smm_pool_properties * props = SMM_POOL_PROPERTIES(pool); + + LV_UNUSED(ctx); + + wl_shm_pool_resize(props->tag[TAG_LOCAL], props->size); +} + +static void sme_free_pool(void * ctx, smm_pool_t * pool) +{ + struct wl_shm_pool * wl_pool = SMM_POOL_PROPERTIES(pool)->tag[TAG_LOCAL]; + + LV_UNUSED(ctx); + + wl_shm_pool_destroy(wl_pool); +} + +static bool sme_new_buffer(void * ctx, smm_buffer_t * buf) +{ + struct wl_buffer * wl_buf; + bool fail_alloc = true; + const struct smm_buffer_properties * props = SMM_BUFFER_PROPERTIES(buf); + struct wl_shm_pool * wl_pool = SMM_POOL_PROPERTIES(props->pool)->tag[TAG_LOCAL]; + struct application * app = ctx; + struct graphic_object * obj = SMM_GROUP_PROPERTIES(props->group)->tag[TAG_LOCAL]; + uint8_t bpp; + + LV_LOG_TRACE("create new buffer of width %d height %d", obj->width, obj->height); + + bpp = lv_color_format_get_size(LV_COLOR_FORMAT_NATIVE); + wl_buf = wl_shm_pool_create_buffer(wl_pool, + props->offset, + obj->width, + obj->height, + obj->width * bpp, + app->shm_format); + + if(wl_buf != NULL) { + wl_buffer_add_listener(wl_buf, &wl_buffer_listener, buf); + SMM_TAG(buf, TAG_LOCAL, wl_buf); + SMM_TAG(buf, TAG_BUFFER_DAMAGE, NULL); + fail_alloc = false; + } + + return fail_alloc; +} + +static bool sme_init_buffer(void * ctx, smm_buffer_t * buf) +{ + smm_buffer_t * src; + void * src_base; + bool fail_init = true; + bool dmg_missing = false; + void * buf_base = smm_map(buf); + const struct smm_buffer_properties * props = SMM_BUFFER_PROPERTIES(buf); + struct graphic_object * obj = SMM_GROUP_PROPERTIES(props->group)->tag[TAG_LOCAL]; + uint8_t bpp; + + LV_UNUSED(ctx); + + if(buf_base == NULL) { + LV_LOG_ERROR("cannot map in buffer to initialize"); + goto done; + } + + /* Determine if all subsequent buffers damage is recorded */ + for(src = smm_next(buf); src != NULL; src = smm_next(src)) { + if(SMM_BUFFER_PROPERTIES(src)->tag[TAG_BUFFER_DAMAGE] == NULL) { + dmg_missing = true; + break; + } + } + + bpp = lv_color_format_get_size(LV_COLOR_FORMAT_NATIVE); + + if((smm_next(buf) == NULL) || dmg_missing) { + /* Missing subsequent buffer damage, initialize by copying the most + * recently acquired buffers data + */ + src = smm_latest(props->group); + if((src != NULL) && + (src != buf)) { + /* Map and copy latest buffer data */ + src_base = smm_map(src); + if(src_base == NULL) { + LV_LOG_ERROR("cannot map most recent buffer to copy"); + goto done; + } + + memcpy(buf_base, + src_base, + (obj->width * bpp) * obj->height); + } + } + else { + /* All subsequent buffers damage is recorded, initialize by applying + * their damage to this buffer + */ + for(src = smm_next(buf); src != NULL; src = smm_next(src)) { + src_base = smm_map(src); + if(src_base == NULL) { + LV_LOG_ERROR("cannot map source buffer to copy from"); + goto done; + } + + cache_apply_areas(obj->window, buf_base, src_base, src); + } + + /* Purge out-of-date cached damage (up to and including next buffer) */ + src = smm_next(buf); + if(src == NULL) { + cache_purge(obj->window, src); + } + } + + fail_init = false; +done: + return fail_init; +} + +static void sme_free_buffer(void * ctx, smm_buffer_t * buf) +{ + struct wl_buffer * wl_buf = SMM_BUFFER_PROPERTIES(buf)->tag[TAG_LOCAL]; + + LV_UNUSED(ctx); + + wl_buffer_destroy(wl_buf); +} + +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; + + LV_UNUSED(parent); + + obj = lv_malloc(sizeof(*obj)); + LV_ASSERT_MALLOC(obj); + if(!obj) { + goto err_out; + } + + lv_memset(obj, 0x00, sizeof(struct graphic_object)); + + obj->surface = wl_compositor_create_surface(app->compositor); + if(!obj->surface) { + LV_LOG_ERROR("cannot create surface for graphic object"); + goto err_free; + } + + obj->buffer_group = smm_create(); + if(obj->buffer_group == NULL) { + LV_LOG_ERROR("cannot create buffer group for graphic object"); + goto err_destroy_surface; + } + + obj->window = window; + obj->type = type; + obj->surface_configured = true; + obj->pending_buffer = NULL; + wl_surface_set_user_data(obj->surface, obj); + SMM_TAG(obj->buffer_group, TAG_LOCAL, obj); + + return obj; + +err_destroy_surface: + wl_surface_destroy(obj->surface); + +err_free: + lv_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); + smm_destroy(obj->buffer_group); + lv_free(obj); +} + +#if LV_WAYLAND_WINDOW_DECORATIONS +static bool attach_decoration(struct window * window, struct graphic_object * decoration, + smm_buffer_t * decoration_buffer, struct graphic_object * parent) +{ + struct wl_buffer * wl_buf = SMM_BUFFER_PROPERTIES(decoration_buffer)->tag[TAG_LOCAL]; + + int pos_x, pos_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; + } + + /* Enable this, to make it function on weston 10.0.2 */ + /* It's not elegant but it forces weston to size the surfaces before */ + /* the conversion to a subsurface takes place */ + + /* Likely related to this issue, some patches were merged into 10.0.0 */ + /* https://gitlab.freedesktop.org/wayland/weston/-/issues/446 */ + /* Moreover, it crashes on GNOME */ + +#if 0 + wl_surface_attach(decoration->surface, wl_buf, 0, 0); + wl_surface_commit(decoration->surface); +#endif + + if(decoration->subsurface == NULL) { + /* Create the subsurface only once */ + + 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_position(decoration->subsurface, pos_x, pos_y); + wl_surface_attach(decoration->surface, wl_buf, 0, 0); + wl_surface_commit(decoration->surface); + + return true; + +err_destroy_surface: + wl_surface_destroy(decoration->surface); + decoration->surface = NULL; + + return false; +} + +/* + * Fills a buffer with a color + * @description Used to draw the decorations, by writing directly to the SHM buffer, + * most wayland compositors support the ARGB8888, XRGB8888, RGB565 formats + * + * For color depths usually not natively supported by wayland i.e RGB332, Grayscale + * A conversion is performed to match the format of the SHM buffer read by the compositor. + * + * This function can also be used as a visual debugging aid to see how damage is applied + * + * @param pixels pointer to the buffer to fill + * @param lv_color_t color the color that will be used for the fill + * @param width width of the filled area + * @param height height of the filled area + * + */ +static void color_fill(void * pixels, lv_color_t color, uint32_t width, uint32_t height) +{ + + switch(application.shm_format) { + case WL_SHM_FORMAT_ARGB8888: + color_fill_XRGB8888(pixels, color, width, height); + break; + case WL_SHM_FORMAT_RGB565: + color_fill_RGB565(pixels, color, width, height); + break; + default: + LV_ASSERT_MSG(0, "Unsupported WL_SHM_FORMAT"); + break; + } +} + +static void color_fill_XRGB8888(void * pixels, lv_color_t color, uint32_t width, uint32_t height) +{ + unsigned char * buf = pixels; + unsigned char * buf_end; + + buf_end = (unsigned char *)((uint32_t *)buf + width * height); + + while(buf < buf_end) { + *(buf++) = color.blue; + *(buf++) = color.green; + *(buf++) = color.red; + *(buf++) = 0xFF; + } + +} + +static void color_fill_RGB565(void * pixels, lv_color_t color, uint32_t width, uint32_t height) +{ + uint16_t * buf = pixels; + uint16_t * buf_end; + + buf_end = (uint16_t *)buf + width * height; + + while(buf < buf_end) { + *(buf++) = lv_color_to_u16(color); + } +} + +static bool create_decoration(struct window * window, + struct graphic_object * decoration, + int window_width, int window_height) +{ + smm_buffer_t * buf; + void * buf_base; + int x, y; + lv_color_t * pixel; + uint8_t bpp; + + 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; + } + + bpp = lv_color_format_get_size(LV_COLOR_FORMAT_NATIVE); + + LV_LOG_TRACE("decoration window %dx%d", decoration->width, decoration->height); + + smm_resize(decoration->buffer_group, + (decoration->width * bpp) * decoration->height); + + buf = smm_acquire(decoration->buffer_group); + + if(buf == NULL) { + LV_LOG_ERROR("cannot allocate buffer for decoration"); + return false; + } + + buf_base = smm_map(buf); + if(buf_base == NULL) { + LV_LOG_ERROR("cannot map in allocated decoration buffer"); + smm_release(buf); + return false; + } + + switch(decoration->type) { + case OBJECT_TITLEBAR: + color_fill(buf_base, lv_color_make(0x66, 0x66, 0x66), decoration->width, decoration->height); + break; + case OBJECT_BUTTON_CLOSE: + color_fill(buf_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++) { + pixel = (lv_color_t *)((unsigned char *)buf_base + (y * (decoration->width * bpp)) + x * bpp); + if((x >= BUTTON_PADDING) && (x < decoration->width - BUTTON_PADDING)) { + if((x == y) || (x == decoration->width - 1 - y)) { + color_fill(pixel, lv_color_make(0x33, 0x33, 0x33), 1, 1); + } + else if((x == y - 1) || (x == decoration->width - y)) { + color_fill(pixel, lv_color_make(0x66, 0x66, 0x66), 1, 1); + } + } + } + } + break; +#if LV_WAYLAND_XDG_SHELL + case OBJECT_BUTTON_MAXIMIZE: + color_fill(buf_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++) { + pixel = (lv_color_t *)((unsigned char *)buf_base + (y * (decoration->width * bpp)) + x * bpp); + 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))) { + color_fill(pixel, lv_color_make(0x33, 0x33, 0x33), 1, 1); + } + } + } + break; + case OBJECT_BUTTON_MINIMIZE: + color_fill(buf_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++) { + pixel = (lv_color_t *)((unsigned char *)buf_base + (y * (decoration->width * bpp)) + x * bpp); + if((x >= BUTTON_PADDING) && (x < decoration->width - BUTTON_PADDING) && + (y > decoration->height - (2 * BUTTON_PADDING)) && (y < decoration->height - BUTTON_PADDING)) { + color_fill(pixel, lv_color_make(0x33, 0x33, 0x33), 1, 1); + } + } + } + break; +#endif + case OBJECT_BORDER_TOP: + /* fallthrough */ + case OBJECT_BORDER_BOTTOM: + /* fallthrough */ + case OBJECT_BORDER_LEFT: + /* fallthrough */ + case OBJECT_BORDER_RIGHT: + color_fill(buf_base, lv_color_make(0x66, 0x66, 0x66), decoration->width, decoration->height); + break; + default: + LV_ASSERT_MSG(0, "Invalid object type"); + return false; + } + + return attach_decoration(window, decoration, buf, window->body); +} + +static void detach_decoration(struct window * window, + struct graphic_object * decoration) +{ + + LV_UNUSED(window); + + if(decoration->subsurface) { + wl_subsurface_destroy(decoration->subsurface); + decoration->subsurface = NULL; + } +} +#endif + +static bool resize_window(struct window * window, int width, int height) +{ + struct smm_buffer_t * body_buf1; + struct smm_buffer_t * body_buf2; + uint32_t stride; + uint8_t bpp; +#if LV_WAYLAND_WINDOW_DECORATIONS + int b; +#endif + + + window->width = width; + window->height = height; + +#if LV_WAYLAND_WINDOW_DECORATIONS + if(!window->application->opt_disable_decorations && !window->fullscreen) { + width -= (2 * BORDER_SIZE); + height -= (TITLE_BAR_HEIGHT + (2 * BORDER_SIZE)); + } +#endif + + bpp = lv_color_format_get_size(LV_COLOR_FORMAT_NATIVE); + + /* Update size for newly allocated buffers */ + smm_resize(window->body->buffer_group, ((width * bpp) * height) * 2); + + window->body->width = width; + window->body->height = height; + + /* Pre-allocate two buffers for the window body here */ + body_buf1 = smm_acquire(window->body->buffer_group); + body_buf2 = smm_acquire(window->body->buffer_group); + + if(smm_map(body_buf2) == NULL) { + LV_LOG_ERROR("Cannot pre-allocate backing buffers for window body"); + wl_surface_destroy(window->body->surface); + return false; + } + + /* Moves the buffers to the the unused list of the group */ + smm_release(body_buf1); + smm_release(body_buf2); + + +#if LV_WAYLAND_WINDOW_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(!window->application->opt_disable_decorations) { + /* Entering fullscreen, detach decorations to prevent xdg_wm_base error 4 */ + /* requested geometry larger than the configured fullscreen state */ + for(b = 0; b < NUM_DECORATIONS; b++) { + detach_decoration(window, window->decoration[b]); + } + + } +#endif + + LV_LOG_TRACE("resize window:%dx%d body:%dx%d frame: %d rendered: %d", + window->width, window->height, + window->body->width, window->body->height, + window->frame_counter, window->frame_done); + + width = window->body->width; + height = window->body->height; + + if(window->lv_disp != NULL) { + /* Resize draw buffer */ + stride = lv_draw_buf_width_to_stride(width, + lv_display_get_color_format(window->lv_disp)); + + window->lv_disp_draw_buf = lv_draw_buf_reshape( + window->lv_disp_draw_buf, + lv_display_get_color_format(window->lv_disp), + width, height / LVGL_DRAW_BUFFER_DIV, stride); + + lv_display_set_resolution(window->lv_disp, width, height); + + window->body->input.pointer.x = LV_MIN((int32_t)window->body->input.pointer.x, (width - 1)); + window->body->input.pointer.y = LV_MIN((int32_t)window->body->input.pointer.y, (height - 1)); + } + + return true; +} + +/* Create a window + * @description Creates the graphical context for the window body, and then create a toplevel + * wayland surface and commit it to obtain an XDG configuration event + * @param width the height of the window w/decorations + * @param height the width of the window w/decorations +*/ +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; + + // Create wayland buffer and surface + window->body = create_graphic_obj(app, window, OBJECT_WINDOW, NULL); + window->width = width; + window->height = height; + + if(!window->body) { + LV_LOG_ERROR("cannot create window body"); + goto err_free_window; + } + + // 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; + } +#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); + + /* For wl_shell, just draw the window, weston doesn't send it */ + draw_window(window, window->width, window->height); + } +#endif + else { + LV_LOG_ERROR("No shell available"); + goto err_destroy_surface; + } + + + return window; + +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_free_window: + lv_ll_remove(&app->window_ll, window); + lv_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_WINDOW_DECORATIONS + int b; + for(b = 0; b < NUM_DECORATIONS; b++) { + if(window->decoration[b]) { + destroy_graphic_obj(window->decoration[b]); + window->decoration[b] = NULL; + } + } +#endif + + destroy_graphic_obj(window->body); +} + +static void _lv_wayland_flush(lv_display_t * disp, const lv_area_t * area, unsigned char * color_p) +{ + unsigned long offset; + void * buf_base; + struct wl_buffer * wl_buf; + uint32_t src_width; + uint32_t src_height; + struct window * window; + smm_buffer_t * buf; + struct wl_callback * cb; + lv_display_rotation_t rot; + uint8_t bpp; + int32_t y; + int32_t w; + int32_t h; + int32_t hres; + int32_t vres; + + window = lv_display_get_user_data(disp); + buf = window->body->pending_buffer; + src_width = (area->x2 - area->x1 + 1); + src_height = (area->y2 - area->y1 + 1); + bpp = lv_color_format_get_size(LV_COLOR_FORMAT_NATIVE); + + rot = lv_display_get_rotation(disp); + w = lv_display_get_horizontal_resolution(disp); + h = lv_display_get_vertical_resolution(disp); + + /* TODO actually test what happens if the rotation is 90 or 270 or 180 ? */ + hres = (rot == LV_DISPLAY_ROTATION_0) ? w : h; + vres = (rot == LV_DISPLAY_ROTATION_0) ? h : w; + + /* If window has been / is being closed, or is not visible, skip flush */ + if(window->closed || window->shall_close) { + goto skip; + } + /* Skip if the area is out the screen */ + else if((area->x2 < 0) || (area->y2 < 0) || (area->x1 > hres - 1) || (area->y1 > vres - 1)) { + goto skip; + } + + /* Acquire and map a buffer to attach/commit to surface */ + if(buf == NULL) { + buf = smm_acquire(window->body->buffer_group); + if(buf == NULL) { + LV_LOG_ERROR("cannot acquire a window body buffer"); + goto skip; + } + + window->body->pending_buffer = buf; + SMM_TAG(buf, + TAG_BUFFER_DAMAGE, + window->dmg_cache.cache + window->dmg_cache.end); + } + + buf_base = smm_map(buf); + if(buf_base == NULL) { + LV_LOG_ERROR("cannot map in window body buffer"); + goto skip; + } + + /* Modify specified area in buffer */ + for(y = area->y1; y <= area->y2; y++) { + offset = ((area->x1 + (y * hres)) * bpp); + memcpy(((char *)buf_base) + offset, + color_p, + src_width * bpp); + color_p += src_width * bpp; + } + + /* Mark surface damage */ + wl_surface_damage(window->body->surface, + area->x1, + area->y1, + src_width, + src_height); + + cache_add_area(window, buf, area); + + + if(lv_display_flush_is_last(disp)) { + /* Finally, attach buffer and commit to surface */ + wl_buf = SMM_BUFFER_PROPERTIES(buf)->tag[TAG_LOCAL]; + wl_surface_attach(window->body->surface, wl_buf, 0, 0); + wl_surface_commit(window->body->surface); + window->body->pending_buffer = NULL; + window->frame_done = false; + + cb = wl_surface_frame(window->body->surface); + wl_callback_add_listener(cb, &wl_surface_frame_listener, window->body); + LV_LOG_TRACE("last flush frame: %d", window->frame_counter); + + window->flush_pending = true; + } + + lv_display_flush_ready(disp); + return; +skip: + if(buf != NULL) { + /* Cleanup any intermediate state (in the event that this flush being + * skipped is in the middle of a flush sequence) + */ + cache_clear(window); + SMM_TAG(buf, TAG_BUFFER_DAMAGE, NULL); + smm_release(buf); + window->body->pending_buffer = NULL; + } +} + +static void _lv_wayland_handle_input(void) +{ + int prepare_read = wl_display_prepare_read(application.display); + while(prepare_read != 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) { + 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; + } + destroy_window(window); + } + + 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_pointer_read(lv_indev_t * drv, lv_indev_data_t * data) +{ + struct window * window = lv_display_get_user_data(lv_indev_get_display(drv)); + + 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_t * drv, lv_indev_data_t * data) +{ + struct window * window = lv_display_get_user_data(lv_indev_get_display(drv)); + + 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_t * drv, lv_indev_data_t * data) +{ + struct window * window = lv_display_get_user_data(lv_indev_get_display(drv)); + 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_t * drv, lv_indev_data_t * data) +{ + struct window * window = lv_display_get_user_data(lv_indev_get_display(drv)); + 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 + */ +static void wayland_init(void) +{ + struct smm_events evs = { + NULL, + sme_new_pool, + sme_expand_pool, + sme_free_pool, + sme_new_buffer, + sme_init_buffer, + sme_free_buffer + }; + + 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.shm_format = SHM_FORMAT_UNKNOWN; + 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.shm_format != SHM_FORMAT_UNKNOWN), "WL_SHM_FORMAT not available"); + if(application.shm_format == SHM_FORMAT_UNKNOWN) { + LV_LOG_TRACE("Unable to match a suitable SHM format for selected LVGL color depth"); + return; + } + + smm_init(&evs); + smm_setctx(&application); + +#ifdef LV_WAYLAND_WINDOW_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)); + + lv_tick_set_cb(tick_get_cb); + + /* Used to wait for events when the window is minimized or hidden */ + application.wayland_pfd.fd = wl_display_get_fd(application.display); + application.wayland_pfd.events = POLLIN; + +} + +/** + * De-initialize Wayland driver + */ +static void wayland_deinit(void) +{ + struct window * window = NULL; + + LV_LL_READ(&application.window_ll, window) { + if(!window->closed) { + destroy_window(window); + } + } + + smm_deinit(); + + 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); + +} + +static uint32_t tick_get_cb(void) +{ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + uint64_t time_ms = t.tv_sec * 1000 + (t.tv_nsec / 1000000); + return time_ms; +} + +/** + * 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 body size in pixels + * @param ver_res initial vertical window body 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_display_t * lv_wayland_window_create(uint32_t hor_res, uint32_t ver_res, char * title, + lv_wayland_display_close_f_t close_cb) +{ + struct window * window; + int32_t window_width; + int32_t window_height; + int32_t stride; + + wayland_init(); + + window_width = hor_res; + window_height = ver_res; + +#if LV_WAYLAND_WINDOW_DECORATIONS + + /* Decorations are enabled, caculate the body size */ + if(!application.opt_disable_decorations) { + window_width = hor_res + (2 * BORDER_SIZE); + window_height = ver_res + (TITLE_BAR_HEIGHT + (2 * BORDER_SIZE)); + } + +#endif + + window = create_window(&application, window_width, window_height, title); + if(!window) { + LV_LOG_ERROR("failed to create wayland window"); + return NULL; + } + + window->close_cb = close_cb; + + /* Initialize display driver */ + window->lv_disp = lv_display_create(hor_res, ver_res); + if(window->lv_disp == NULL) { + LV_LOG_ERROR("failed to create lvgl display"); + return NULL; + } + + stride = lv_draw_buf_width_to_stride(hor_res, + lv_display_get_color_format(window->lv_disp)); + + window->lv_disp_draw_buf = lv_draw_buf_create( + hor_res, + ver_res / LVGL_DRAW_BUFFER_DIV, + lv_display_get_color_format(window->lv_disp), + stride); + + + lv_display_set_draw_buffers(window->lv_disp, window->lv_disp_draw_buf, NULL); + lv_display_set_render_mode(window->lv_disp, LV_DISPLAY_RENDER_MODE_PARTIAL); + lv_display_set_flush_cb(window->lv_disp, _lv_wayland_flush); + lv_display_set_user_data(window->lv_disp, window); + + /* Register input */ + window->lv_indev_pointer = lv_indev_create(); + lv_indev_set_type(window->lv_indev_pointer, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(window->lv_indev_pointer, _lv_wayland_pointer_read); + lv_indev_set_display(window->lv_indev_pointer, window->lv_disp); + + if(!window->lv_indev_pointer) { + LV_LOG_ERROR("failed to register pointer indev"); + } + + window->lv_indev_pointeraxis = lv_indev_create(); + lv_indev_set_type(window->lv_indev_pointeraxis, LV_INDEV_TYPE_ENCODER); + lv_indev_set_read_cb(window->lv_indev_pointeraxis, _lv_wayland_pointeraxis_read); + lv_indev_set_display(window->lv_indev_pointeraxis, window->lv_disp); + + if(!window->lv_indev_pointeraxis) { + LV_LOG_ERROR("failed to register pointeraxis indev"); + } + + window->lv_indev_touch = lv_indev_create(); + lv_indev_set_type(window->lv_indev_touch, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(window->lv_indev_touch, _lv_wayland_touch_read); + lv_indev_set_display(window->lv_indev_touch, window->lv_disp); + + if(!window->lv_indev_touch) { + LV_LOG_ERROR("failed to register touch indev"); + } + + window->lv_indev_keyboard = lv_indev_create(); + lv_indev_set_type(window->lv_indev_keyboard, LV_INDEV_TYPE_KEYPAD); + lv_indev_set_read_cb(window->lv_indev_keyboard, _lv_wayland_keyboard_read); + lv_indev_set_display(window->lv_indev_keyboard, window->lv_disp); + + 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_window_close(lv_display_t * disp) +{ + struct window * window = lv_display_get_user_data(disp); + if(!window || window->closed) { + return; + } + window->shall_close = true; + window->close_cb = NULL; + wayland_deinit(); +} + +/** + * 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_display_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 = lv_display_get_user_data(disp); + open = (!window->closed); + } + + return open; +} +/** + * Set/unset window maximization mode + * @description Maximization is nearly the same as fullscreen, except + * window decorations and the compositor's panels must remain visible + * @param disp LVGL display using window to be set/unset maximization + * @param Maximization requested status (true = maximized) + */ +void lv_wayland_window_set_maximized(lv_display_t * disp, bool maximized) +{ + struct window * window = lv_display_get_user_data(disp); + if(!window || window->closed) { + return; + } + + if(window->maximized != maximized) { + +#if LV_WAYLAND_WL_SHELL + if(window->wl_shell_surface) { + if(maximized) { + /* Maximizing the wl_shell is possible, but requires binding to wl_output */ + /* The wl_shell has been deperacted */ + //wl_shell_surface_set_maximized(window->wl_shell_surface); + } + else { + wl_shell_surface_set_toplevel(window->wl_shell_surface); + } + window->maximized = maximized; + window->flush_pending = true; + } +#endif + +#if LV_WAYLAND_XDG_SHELL + if(window->xdg_toplevel) { + if(maximized) { + xdg_toplevel_set_maximized(window->xdg_toplevel); + } + else { + xdg_toplevel_unset_maximized(window->xdg_toplevel); + } + + window->maximized = maximized; + window->flush_pending = true; + } +#endif + } +} + +/** + * 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_display_t * disp, bool fullscreen) +{ + struct window * window = lv_display_get_user_data(disp); + 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_display_t * disp) +{ + struct window * window = lv_display_get_user_data(disp); + 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_display_t * disp) +{ + struct window * window = lv_display_get_user_data(disp); + 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_display_t * disp) +{ + struct window * window = lv_display_get_user_data(disp); + 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_display_t * disp) +{ + struct window * window = lv_display_get_user_data(disp); + if(!window) { + return NULL; + } + return window->lv_indev_touch; +} + +/** + * Wayland specific timer handler (use in place of LVGL lv_timer_handler) + */ +bool lv_wayland_timer_handler(void) +{ + struct window * window; + + /* Wayland input handling - it will also trigger the frame done handler */ + _lv_wayland_handle_input(); + + /* Ready input timers (to probe for any input received) */ + LV_LL_READ(&application.window_ll, window) { + LV_LOG_TRACE("handle timer frame: %d", window->frame_counter); + + if(window != NULL && window->frame_done == false + && window->frame_counter > 0) { + /* The last frame was not rendered */ + LV_LOG_TRACE("The window is hidden or minimized"); + + /* Simply blocks until a frame done message arrives */ + poll(&application.wayland_pfd, 1, -1); + + /* Resume lvgl on the next cycle */ + return false; + + } + else if(window != NULL && window->body->surface_configured == false) { + /* Initial commit to trigger the configure event */ + /* Manually dispatching the queue is necessary, */ + /* to emit the configure event straight away */ + wl_surface_commit(window->body->surface); + wl_display_dispatch(application.display); + } + else if(window != NULL && window->resize_pending) { + if(resize_window(window, window->resize_width, window->resize_height)) { + window->resize_width = window->width; + window->resize_height = window->height; + window->resize_pending = false; + + } + else { + + LV_LOG_TRACE("Failed to resize window frame: %d", + window->frame_counter); + } + } + else if(window->shall_close == true) { + + /* Destroy graphical context and execute close_cb */ + _lv_wayland_handle_output(); + wayland_deinit(); + return false; + } + } + + /* LVGL handling */ + lv_timer_handler(); + + /* Wayland output handling */ + _lv_wayland_handle_output(); + + /* Set 'errno' 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). + */ + LV_LL_READ(&application.window_ll, window) { + if(window->flush_pending) { + errno = EAGAIN; + break; + } + } + + return true; +} + +#endif /* LV_USE_WAYLAND */ +#endif /* _WIN32 */ diff --git a/src/drivers/wayland/lv_wayland.h b/src/drivers/wayland/lv_wayland.h new file mode 100644 index 000000000..f3d340b0d --- /dev/null +++ b/src/drivers/wayland/lv_wayland.h @@ -0,0 +1,142 @@ +/******************************************************************* + * + * @file lv_wayland.h - Public functions of the LVGL Wayland client + * + * Based on the original file from the repository. + * + * Porting to LVGL 9.1 + * 2024 EDGEMTech Ltd. + * + * See LICENCE.txt for details + * + * Author(s): EDGEMTech Ltd, Erik Tagirov (erik.tagirov@edgemtech.ch) + * + ******************************************************************/ +#ifndef LV_WAYLAND_H +#define LV_WAYLAND_H + +#ifndef _WIN32 + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../display/lv_display.h" +#include "../../indev/lv_indev.h" + +#if LV_USE_WAYLAND + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef bool (*lv_wayland_display_close_f_t)(lv_display_t * disp); + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Retrieves the file descriptor of the wayland socket + */ +int lv_wayland_get_fd(void); + +/** + * Creates a window + * @param hor_res The width of the window in pixels + * @param ver_res The height of the window in pixels + * @param title The title of the window + * @param close_cb The callback that will be execute when the user closes the window + * @return The LVGL display associated to the window + */ +lv_display_t * lv_wayland_window_create(uint32_t hor_res, uint32_t ver_res, char * title, + lv_wayland_display_close_f_t close_cb); + +/** + * Closes the window programmatically + * @param disp Reference to the LVGL display associated to the window + */ +void lv_wayland_window_close(lv_display_t * disp); + +/** + * Check if the window is open + * @param disp Reference to the LVGL display associated to the window + * @return true: The window is open + */ +bool lv_wayland_window_is_open(lv_display_t * disp); + +/** + * Sets the fullscreen state of the window + * @param disp Reference to the LVGL display associated to the window + * @param fullscreen If true the window enters fullscreen + */ +void lv_wayland_window_set_fullscreen(lv_display_t * disp, bool fullscreen); + +/** + * Sets the maximized state of the window + * @param disp Reference to the LVGL display associated to the window + * @param fullscreen If true the window is maximized + */ +void lv_wayland_window_set_maximized(lv_display_t * disp, bool maximize); + +/** + * Obtains the input device of the mouse pointer + * @note It is used to create an input group on application start + * @param disp Reference to the LVGL display associated to the window + * @return The input device + */ +lv_indev_t * lv_wayland_get_pointer(lv_display_t * disp); + +/** + * Obtains the input device of the encoder + * @note It is used to create an input group on application start + * @param disp Reference to the LVGL display associated to the window + * @return The input device + */ +lv_indev_t * lv_wayland_get_pointeraxis(lv_display_t * disp); + +/** + * Obtains the input device of the keyboard + * @note It is used to create an input group on application start + * @param disp Reference to the LVGL display associated to the window + * @return The input device + */ +lv_indev_t * lv_wayland_get_keyboard(lv_display_t * disp); + +/** + * Obtains the input device of the touch screen + * @note It is used to create an input group on application start + * @param disp Reference to the LVGL display associated to the window + * @return The input device + */ +lv_indev_t * lv_wayland_get_touchscreen(lv_display_t * disp); + +/** + * Wrapper around lv_timer_handler + * @note Must be called in the application run loop instead of the + * regular lv_timer_handler provided by LVGL + * @return true: if the cycle was completed, false if the application + * went to sleep because the last frame wasn't completed + */ +bool lv_wayland_timer_handler(void); + +/********************** + * MACROS + **********************/ + +#endif /* LV_USE_WAYLAND */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* _WIN32 */ +#endif /* WAYLAND_H */ diff --git a/src/drivers/wayland/lv_wayland_smm.c b/src/drivers/wayland/lv_wayland_smm.c new file mode 100644 index 000000000..7cb982a0f --- /dev/null +++ b/src/drivers/wayland/lv_wayland_smm.c @@ -0,0 +1,675 @@ +/** + * @file lv_wayland_smm.c + * + */ + +typedef int dummy_t; /* Make GCC on windows happy, avoid empty translation unit */ + +#ifndef _WIN32 + +#include "lv_wayland_smm.h" +#include "../../display/lv_display.h" + +#if LV_USE_WAYLAND + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_NAME_ATTEMPTS (5) +#define PREFER_NUM_BUFFERS (3) + +#define ROUND_UP(n, b) (((((n) ? (n) : 1) + (b) - 1) / (b)) * (b)) +#define LLHEAD(type) \ + struct { \ + struct type *first; \ + struct type *last; \ + } + +#define LLLINK(type) \ + struct { \ + struct type *next; \ + struct type *prev; \ + } + +#define LL_FIRST(head) ((head)->first) +#define LL_LAST(head) ((head)->last) +#define LL_IS_EMPTY(head) (LL_FIRST(head) == NULL) +#define LL_NEXT(src, member) ((src)->member.next) +#define LL_PREV(src, member) ((src)->member.prev) + +#define LL_INIT(head) do { \ + (head)->first = NULL; \ + (head)->last = NULL; \ + } while (0) + +#define LL_ENQUEUE(head, src, member) do { \ + (src)->member.next = NULL; \ + (src)->member.prev = (head)->last; \ + if ((head)->last == NULL) { \ + (head)->first = (src); \ + } else { \ + (head)->last->member.next = (src); \ + } \ + (head)->last = (src); \ + } while (0) + +#define LL_DEQUEUE(entry, head, member) do { \ + (entry) = LL_FIRST(head); \ + LL_REMOVE(head, entry, member); \ + } while (0) + +#define LL_INSERT_AFTER(head, dest, src, member) do { \ + (src)->member.prev = (dest); \ + (src)->member.next = (dest)->member.next; \ + if ((dest)->member.next != NULL) { \ + (dest)->member.next->member.prev = (src); \ + } else { \ + (head)->last = (src); \ + } \ + (dest)->member.next = (src); \ + } while (0) + +#define LL_REMOVE(head, src, member) do { \ + if ((src)->member.prev != NULL) { \ + (src)->member.prev->member.next = (src)->member.next; \ + } else { \ + (head)->first = (src)->member.next; \ + } \ + if ((src)->member.next != NULL) { \ + (src)->member.next->member.prev = (src)->member.prev; \ + } else { \ + (head)->last = (src)->member.prev; \ + } \ + } while (0) + +#define LL_FOREACH(entry, head, member) \ + for ((entry) = LL_FIRST(head); \ + (entry) != NULL; \ + (entry) = LL_NEXT(entry, member)) + +#define WAYLAND_FD_NAME "/" SMM_FD_NAME "-XXXXX" + +struct smm_pool { + struct smm_pool_properties props; + LLHEAD(smm_buffer) allocd; + void * map; + size_t map_size; + bool map_outdated; +}; + +struct smm_buffer { + struct smm_buffer_properties props; + bool group_resized; + LLLINK(smm_buffer) pool; + LLLINK(smm_buffer) use; + LLLINK(smm_buffer) age; +}; + +struct smm_group { + struct smm_group_properties props; + size_t size; + unsigned char num_buffers; + LLHEAD(smm_buffer) unused; + LLHEAD(smm_buffer) inuse; + LLHEAD(smm_buffer) history; + LLLINK(smm_group) link; +}; + +static size_t calc_buffer_size(struct smm_buffer * buf); +static void purge_history(struct smm_buffer * buf); +static struct smm_buffer * get_from_pool(struct smm_group * grp); +static void return_to_pool(struct smm_buffer * buf); +static struct smm_pool * alloc_pool(void); +static void free_pool(struct smm_pool * pool); +static struct smm_buffer * alloc_buffer(struct smm_buffer * last, size_t offset); +static void free_buffer(struct smm_buffer * buf); + +static struct { + unsigned long page_sz; + struct smm_events cbs; + struct smm_pool * active; + LLHEAD(smm_group) groups; + struct { + size_t active_used; + } statistics; +} smm_instance; + + +void smm_init(struct smm_events * evs) +{ + memcpy(&smm_instance.cbs, evs, sizeof(struct smm_events)); + srand((unsigned int)clock()); + smm_instance.page_sz = (unsigned long)sysconf(_SC_PAGESIZE); + LL_INIT(&smm_instance.groups); +} + + +void smm_deinit(void) +{ + struct smm_group * grp; + + /* Destroy all buffer groups */ + while(!LL_IS_EMPTY(&smm_instance.groups)) { + LL_DEQUEUE(grp, &smm_instance.groups, link); + smm_destroy(grp); + } +} + + +void smm_setctx(void * ctx) +{ + smm_instance.cbs.ctx = ctx; +} + + +smm_group_t * smm_create(void) +{ + struct smm_group * grp; + + /* Allocate and intialize a new buffer group */ + grp = malloc(sizeof(struct smm_group)); + if(grp != NULL) { + grp->size = smm_instance.page_sz; + grp->num_buffers = 0; + LL_INIT(&grp->unused); + LL_INIT(&grp->inuse); + LL_INIT(&grp->history); + + /* Add to instance groups queue */ + LL_ENQUEUE(&smm_instance.groups, grp, link); + } + + return grp; +} + + +void smm_resize(smm_group_t * grp, size_t sz) +{ + struct smm_buffer * buf; + struct smm_group * rgrp = grp; + + /* Round allocation size up to a sysconf(_SC_PAGE_SIZE) boundary */ + rgrp->size = ROUND_UP(sz, smm_instance.page_sz); + + /* Return all unused buffers to pool (to be re-allocated at the new size) */ + while(!LL_IS_EMPTY(&rgrp->unused)) { + LL_DEQUEUE(buf, &rgrp->unused, use); + return_to_pool(buf); + } + + /* Mark all buffers in use to be freed to pool when possible */ + LL_FOREACH(buf, &rgrp->inuse, use) { + buf->group_resized = true; + purge_history(buf); + } +} + + +void smm_destroy(smm_group_t * grp) +{ + struct smm_buffer * buf; + struct smm_group * dgrp = grp; + + /* Return unused buffers */ + while(!LL_IS_EMPTY(&dgrp->unused)) { + LL_DEQUEUE(buf, &dgrp->unused, use); + return_to_pool(buf); + } + + /* Return buffers that are still in use (ideally this queue should be empty + * at this time) + */ + while(!LL_IS_EMPTY(&dgrp->inuse)) { + LL_DEQUEUE(buf, &dgrp->inuse, use); + return_to_pool(buf); + } + + /* Remove from instance groups queue */ + LL_REMOVE(&smm_instance.groups, dgrp, link); + free(dgrp); +} + + +smm_buffer_t * smm_acquire(smm_group_t * grp) +{ + struct smm_buffer * buf; + struct smm_group * agrp = grp; + + if(LL_IS_EMPTY(&agrp->unused)) { + /* No unused buffer available, so get a new one from pool */ + buf = get_from_pool(agrp); + } + else { + /* Otherwise, reuse an unused buffer */ + LL_DEQUEUE(buf, &agrp->unused, use); + } + + if(buf != NULL) { + /* Add buffer to in-use queue */ + LL_ENQUEUE(&agrp->inuse, buf, use); + + /* Emit 'init buffer' event */ + if(smm_instance.cbs.init_buffer != NULL) { + if(smm_instance.cbs.init_buffer(smm_instance.cbs.ctx, &buf->props)) { + smm_release(buf); + buf = NULL; + } + } + + if(buf != NULL) { + /* Remove from history */ + purge_history(buf); + + /* Add to history a-new */ + LL_ENQUEUE(&agrp->history, buf, age); + } + } + + return buf; +} + + +void * smm_map(smm_buffer_t * buf) +{ + struct smm_buffer * mbuf = buf; + struct smm_pool * pool = mbuf->props.pool; + void * map = pool->map; + + if(pool->map_outdated) { + /* Update mapping to current pool size */ + if(pool->map != NULL) { + munmap(pool->map, pool->map_size); + } + + map = mmap(NULL, + pool->props.size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + pool->props.fd, + 0); + + if(map == MAP_FAILED) { + map = NULL; + pool->map = NULL; + } + else { + pool->map = map; + pool->map_size = pool->props.size; + pool->map_outdated = false; + } + } + + /* Calculate buffer mapping (from offset in pool) */ + if(map != NULL) { + map = (((char *)map) + mbuf->props.offset); + } + + return map; +} + + +void smm_release(smm_buffer_t * buf) +{ + struct smm_buffer * rbuf = buf; + struct smm_group * grp = rbuf->props.group; + + /* Remove from in-use queue */ + LL_REMOVE(&grp->inuse, rbuf, use); + + if(rbuf->group_resized) { + /* Buffer group was resized while this buffer was in-use, thus it must be + * returned to it's pool + */ + rbuf->group_resized = false; + return_to_pool(rbuf); + } + else { + /* Move to unused queue */ + LL_ENQUEUE(&grp->unused, rbuf, use); + + /* Try to limit total number of buffers to preferred number */ + while((grp->num_buffers > PREFER_NUM_BUFFERS) && + (!LL_IS_EMPTY(&grp->unused))) { + LL_DEQUEUE(rbuf, &grp->unused, use); + return_to_pool(rbuf); + } + } +} + + +smm_buffer_t * smm_latest(smm_group_t * grp) +{ + struct smm_group * lgrp = grp; + + return LL_LAST(&lgrp->history); +} + + +smm_buffer_t * smm_next(smm_buffer_t * buf) +{ + struct smm_buffer * ibuf; + struct smm_buffer * nbuf = buf; + struct smm_group * grp = nbuf->props.group; + + LL_FOREACH(ibuf, &grp->history, age) { + if(ibuf == nbuf) { + ibuf = LL_NEXT(ibuf, age); + break; + } + } + + return ibuf; +} + +void purge_history(struct smm_buffer * buf) +{ + struct smm_buffer * ibuf; + struct smm_group * grp = buf->props.group; + + /* Remove from history (and any older) */ + LL_FOREACH(ibuf, &grp->history, age) { + if(ibuf == buf) { + do { + LL_DEQUEUE(ibuf, &grp->history, age); + } while(ibuf != buf); + break; + } + } +} + + +size_t calc_buffer_size(struct smm_buffer * buf) +{ + size_t buf_sz; + struct smm_pool * buf_pool = buf->props.pool; + + if(buf == LL_LAST(&buf_pool->allocd)) { + buf_sz = (buf_pool->props.size - buf->props.offset); + } + else { + buf_sz = (LL_NEXT(buf, pool)->props.offset - buf->props.offset); + } + + return buf_sz; +} + + +struct smm_buffer * get_from_pool(struct smm_group * grp) +{ + int ret; + size_t buf_sz; + struct smm_buffer * buf; + struct smm_buffer * last = NULL; + + /* TODO: Determine when to allocate a new active pool (i.e. memory shrink) */ + + if(smm_instance.active == NULL) { + /* Allocate a new active pool */ + smm_instance.active = alloc_pool(); + smm_instance.statistics.active_used = 0; + } + + if(smm_instance.active == NULL) { + buf = NULL; + } + else { + /* Search for a free buffer large enough for allocation */ + LL_FOREACH(buf, &smm_instance.active->allocd, pool) { + last = buf; + if(buf->props.group == NULL) { + buf_sz = calc_buffer_size(buf); + if(buf_sz == grp->size) { + break; + } + else if(buf_sz > grp->size) { + if((buf != LL_LAST(&smm_instance.active->allocd)) && + (LL_NEXT(buf, pool)->props.group == NULL)) { + /* Pull back next buffer to use unallocated size */ + LL_NEXT(buf, pool)->props.offset -= (buf_sz - grp->size); + } + else { + /* Allocate another buffer to hold unallocated size */ + alloc_buffer(buf, buf->props.offset + grp->size); + } + + break; + } + } + } + + if(buf == NULL) { + /* No buffer found to meet allocation size, expand pool */ + if((last != NULL) && + (last->props.group == NULL)) { + /* Use last free buffer */ + buf_sz = (grp->size - buf_sz); + } + else { + /* Allocate new buffer */ + buf_sz = grp->size; + if(last == NULL) { + buf = alloc_buffer(NULL, 0); + } + else { + buf = alloc_buffer(last, smm_instance.active->props.size); + } + last = buf; + } + + if(last != NULL) { + /* Expand pool backing memory */ + ret = ftruncate(smm_instance.active->props.fd, + smm_instance.active->props.size + buf_sz); + if(ret) { + if(buf != NULL) { + free_buffer(buf); + buf = NULL; + } + } + else { + smm_instance.active->props.size += buf_sz; + smm_instance.active->map_outdated = true; + buf = last; + + if(!(smm_instance.active->props.size - buf_sz)) { + /* Emit 'new pool' event */ + if((smm_instance.cbs.new_pool != NULL) && + (smm_instance.cbs.new_pool(smm_instance.cbs.ctx, + &smm_instance.active->props))) { + free_buffer(buf); + free_pool(smm_instance.active); + smm_instance.active = NULL; + buf = NULL; + } + } + else { + /* Emit 'expand pool' event */ + if(smm_instance.cbs.expand_pool != NULL) { + smm_instance.cbs.expand_pool(smm_instance.cbs.ctx, + &smm_instance.active->props); + } + } + } + } + } + } + + if(buf != NULL) { + /* Set buffer group */ + memcpy((void *)&buf->props.group, &grp, sizeof(struct smm_group *)); + + /* Emit 'new buffer' event */ + if(smm_instance.cbs.new_buffer != NULL) { + if(smm_instance.cbs.new_buffer(smm_instance.cbs.ctx, &buf->props)) { + grp = NULL; + memcpy((void *)&buf->props.group, &grp, sizeof(struct smm_group *)); + buf = NULL; + } + } + + if(buf != NULL) { + /* Update active pool usage statistic */ + smm_instance.statistics.active_used += grp->size; + grp->num_buffers++; + } + } + + return buf; +} + + +void return_to_pool(struct smm_buffer * buf) +{ + struct smm_group * grp = buf->props.group; + struct smm_pool * pool = buf->props.pool; + + /* Emit 'free buffer' event */ + if(smm_instance.cbs.free_buffer != NULL) { + smm_instance.cbs.free_buffer(smm_instance.cbs.ctx, &buf->props); + } + + /* Buffer is no longer part of history */ + purge_history(buf); + + /* Buffer is no longer part of group */ + grp->num_buffers--; + grp = NULL; + memcpy((void *)&buf->props.group, &grp, sizeof(struct smm_group *)); + + /* Update active pool usage statistic */ + if(smm_instance.active == pool) { + smm_instance.statistics.active_used -= calc_buffer_size(buf); + } + + /* Coalesce with ungrouped buffers beside this one */ + if((buf != LL_LAST(&pool->allocd)) && + (LL_NEXT(buf, pool)->props.group == NULL)) { + free_buffer(LL_NEXT(buf, pool)); + } + if((buf != LL_FIRST(&pool->allocd)) && + (LL_PREV(buf, pool)->props.group == NULL)) { + buf = LL_PREV(buf, pool); + pool = buf->props.pool; + free_buffer(LL_NEXT(buf, pool)); + } + + /* Free buffer (and pool), if only remaining buffer in pool */ + if((buf == LL_FIRST(&pool->allocd)) && + (buf == LL_LAST(&pool->allocd))) { + free_buffer(buf); + + /* Emit 'free pool' event */ + if(smm_instance.cbs.free_pool != NULL) { + smm_instance.cbs.free_pool(smm_instance.cbs.ctx, &pool->props); + } + + free_pool(pool); + if(smm_instance.active == pool) { + smm_instance.active = NULL; + } + } +} + + +struct smm_pool * alloc_pool(void) +{ + struct smm_pool * pool; + char name[] = WAYLAND_FD_NAME; + unsigned char attempts = 0; + bool opened = false; + + pool = malloc(sizeof(struct smm_pool)); + if(pool != NULL) { + do { + /* A randomized pool name should help reduce collisions */ + sprintf(name + sizeof(SMM_FD_NAME) + 1, "%05X", rand() & 0xFFFF); + pool->props.fd = shm_open(name, + O_RDWR | O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR); + if(pool->props.fd >= 0) { + shm_unlink(name); + pool->props.size = 0; + pool->map = NULL; + pool->map_size = 0; + pool->map_outdated = false; + LL_INIT(&pool->allocd); + opened = true; + break; + } + else { + if(errno != EEXIST) { + break; + } + attempts++; + } + } while(attempts < MAX_NAME_ATTEMPTS); + + if(!opened) { + free(pool); + pool = NULL; + } + } + + return pool; +} + + +void free_pool(struct smm_pool * pool) +{ + if(pool->map != NULL) { + munmap(pool->map, pool->map_size); + } + + close(pool->props.fd); + free(pool); +} + + +struct smm_buffer * alloc_buffer(struct smm_buffer * last, size_t offset) +{ + struct smm_buffer * buf; + struct smm_buffer_properties initial_props = { + {NULL}, + NULL, + smm_instance.active, + offset + }; + + /* Allocate and intialize a new buffer (including linking in to pool) */ + buf = malloc(sizeof(struct smm_buffer)); + if(buf != NULL) { + memcpy(&buf->props, &initial_props, sizeof(struct smm_buffer_properties)); + buf->group_resized = false; + + if(last == NULL) { + LL_ENQUEUE(&smm_instance.active->allocd, buf, pool); + } + else { + LL_INSERT_AFTER(&smm_instance.active->allocd, last, buf, pool); + } + } + + return buf; +} + + +void free_buffer(struct smm_buffer * buf) +{ + struct smm_pool * buf_pool = buf->props.pool; + + /* Remove from pool */ + LL_REMOVE(&buf_pool->allocd, buf, pool); + free(buf); +} + +#endif /* LV_USE_WAYLAND */ +#endif /* _WIN32 */ diff --git a/src/drivers/wayland/lv_wayland_smm.h b/src/drivers/wayland/lv_wayland_smm.h new file mode 100644 index 000000000..617244f17 --- /dev/null +++ b/src/drivers/wayland/lv_wayland_smm.h @@ -0,0 +1,105 @@ +/** + * @file lv_wayland_smm.h + * + */ +#ifndef LV_WAYLAND_SMM_H +#define LV_WAYLAND_SMM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _WIN32 + +/********************* + * INCLUDES + *********************/ + +#include "../../display/lv_display.h" +#include LV_STDDEF_INCLUDE +#include LV_STDBOOL_INCLUDE + +#if LV_USE_WAYLAND + +/********************* + * DEFINES + *********************/ + +#define SMM_FD_NAME "lvgl-wayland" +#define SMM_POOL_TAGS (1) +#define SMM_BUFFER_TAGS (2) +#define SMM_GROUP_TAGS (1) + +/********************** + * TYPEDEFS + **********************/ + +typedef void smm_pool_t; +typedef void smm_buffer_t; +typedef void smm_group_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +struct smm_events { + void * ctx; + bool (*new_pool)(void * ctx, smm_pool_t * pool); + void (*expand_pool)(void * ctx, smm_pool_t * pool); + void (*free_pool)(void * ctx, smm_pool_t * pool); + bool (*new_buffer)(void * ctx, smm_buffer_t * buf); + bool (*init_buffer)(void * ctx, smm_buffer_t * buf); + void (*free_buffer)(void * ctx, smm_buffer_t * buf); +}; + +struct smm_pool_properties { + void * tag[SMM_POOL_TAGS]; + size_t size; + int fd; +}; + +struct smm_buffer_properties { + void * tag[SMM_BUFFER_TAGS]; + smm_group_t * const group; + smm_pool_t * const pool; + size_t offset; +}; + +struct smm_group_properties { + void * tag[SMM_GROUP_TAGS]; +}; + +void smm_init(struct smm_events * evs); +void smm_setctx(void * ctx); +void smm_deinit(void); +smm_group_t * smm_create(void); +void smm_resize(smm_group_t * grp, size_t sz); +void smm_destroy(smm_group_t * grp); +smm_buffer_t * smm_acquire(smm_group_t * grp); +void * smm_map(smm_buffer_t * buf); +void smm_release(smm_buffer_t * buf); +smm_buffer_t * smm_latest(smm_group_t * grp); +smm_buffer_t * smm_next(smm_buffer_t * buf); + +/********************** + * MACROS + **********************/ + +#define SMM_POOL_PROPERTIES(p) ((const struct smm_pool_properties *)(p)) +#define SMM_BUFFER_PROPERTIES(b) ((const struct smm_buffer_properties *)(b)) +#define SMM_GROUP_PROPERTIES(g) ((const struct smm_group_properties *)(g)) +#define SMM_TAG(o, n, v) \ + do { \ + void **smm_tag = (void **)((char *)o + (n * sizeof(void *))); \ + *smm_tag = (v); \ + } while(0) + + +#endif /* LV_USE_WAYLAND */ +#endif /* _WIN32 */ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /* LV_WAYLAND_SMM_H */ diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index 5eaed2a63..de645d9ac 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -3140,6 +3140,31 @@ #endif #endif +/*Use Wayland to open a window and handle input on Linux or BSD desktops */ +#ifndef LV_USE_WAYLAND + #ifdef CONFIG_LV_USE_WAYLAND + #define LV_USE_WAYLAND CONFIG_LV_USE_WAYLAND + #else + #define LV_USE_WAYLAND 0 + #endif +#endif +#if LV_USE_WAYLAND + #ifndef LV_WAYLAND_WINDOW_DECORATIONS + #ifdef CONFIG_LV_WAYLAND_WINDOW_DECORATIONS + #define LV_WAYLAND_WINDOW_DECORATIONS CONFIG_LV_WAYLAND_WINDOW_DECORATIONS + #else + #define LV_WAYLAND_WINDOW_DECORATIONS 0 /*Draw client side window decorations only necessary on Mutter/GNOME*/ + #endif + #endif + #ifndef LV_WAYLAND_WL_SHELL + #ifdef CONFIG_LV_WAYLAND_WL_SHELL + #define LV_WAYLAND_WL_SHELL CONFIG_LV_WAYLAND_WL_SHELL + #else + #define LV_WAYLAND_WL_SHELL 0 /*Use the legacy wl_shell protocol instead of the default XDG shell*/ + #endif + #endif +#endif + /*Driver for /dev/fb*/ #ifndef LV_USE_LINUX_FBDEV #ifdef CONFIG_LV_USE_LINUX_FBDEV diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3651ac07a..714780d9f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -326,6 +326,19 @@ if(NOT WIN32) # libjpeg is required for the jpeg test case find_package(JPEG REQUIRED) include_directories(${JPEG_INCLUDE_DIR}) + + # 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) + + link_libraries(wayland-client wayland-cursor xkbcommon) + + # Add auto generated source required for XDG shell + include_directories("${LVGL_TEST_DIR}/wayland_protocols") + target_sources(test_common PUBLIC "wayland_protocols/wayland_xdg_shell.c") + endif() # libpng is required for the png test case diff --git a/tests/main.py b/tests/main.py index c0bab8df5..1f400b349 100755 --- a/tests/main.py +++ b/tests/main.py @@ -14,6 +14,9 @@ lvgl_test_dir = os.path.dirname(os.path.realpath(__file__)) lvgl_script_path = os.path.join(lvgl_test_dir, "../scripts") sys.path.append(lvgl_script_path) +wayland_dir = os.path.join(lvgl_test_dir, "wayland_protocols") +wayland_protocols_dir = os.path.realpath("/usr/share/wayland-protocols") + from LVGLImage import LVGLImage, ColorFormat, CompressMethod # Key values must match variable names in CMakeLists.txt. @@ -70,6 +73,37 @@ def get_build_dir(options_name): global lvgl_test_dir return os.path.join(lvgl_test_dir, get_base_build_dir(options_name)) +def gen_wayland_protocols(clean): + '''Generates the xdg shell interface from wayland protocol definitions''' + + if clean: + delete_dir_ignore_missing(wayland_dir) + + if not os.path.isdir(wayland_dir): + os.mkdir(wayland_dir) + subprocess.check_call(['wayland-scanner', + 'client-header', + os.path.join(wayland_protocols_dir, "stable/xdg-shell/xdg-shell.xml"), + os.path.join(wayland_dir, "wayland_xdg_shell.h.original"), + ]) + + subprocess.check_call(['wayland-scanner', + 'private-code', + os.path.join(wayland_protocols_dir, "stable/xdg-shell/xdg-shell.xml"), + os.path.join(wayland_dir, "wayland_xdg_shell.c.original"), + ]) + + # Insert guards + with open(os.path.join(wayland_dir, "wayland_xdg_shell.h"), "w") as outfile: + subprocess.check_call(['sed','-e', "1i #if LV_BUILD_TEST", '-e', '$a #endif', + os.path.join(wayland_dir, "wayland_xdg_shell.h.original")], stdout=outfile) + + with open(os.path.join(wayland_dir, "wayland_xdg_shell.c"), "w") as outfile: + subprocess.check_call(['sed','-e', "1i #if LV_BUILD_TEST", '-e', '$a #endif', + os.path.join(wayland_dir, "wayland_xdg_shell.c.original")], stdout=outfile) + + subprocess.check_call(['rm', os.path.join(wayland_dir, "wayland_xdg_shell.c.original")]) + subprocess.check_call(['rm', os.path.join(wayland_dir, "wayland_xdg_shell.h.original")]) def build_tests(options_name, build_type, clean): '''Build all tests for the specified options name.''' @@ -89,6 +123,10 @@ def build_tests(options_name, build_type, clean): delete_dir_ignore_missing(build_dir) os.chdir(lvgl_test_dir) + + if platform.system() != 'Windows': + gen_wayland_protocols(clean) + created_build_dir = False if not os.path.isdir(build_dir): os.mkdir(build_dir) diff --git a/tests/src/lv_test_conf_full.h b/tests/src/lv_test_conf_full.h index 116ae9c6c..17d3fe68b 100644 --- a/tests/src/lv_test_conf_full.h +++ b/tests/src/lv_test_conf_full.h @@ -126,6 +126,11 @@ #define LV_USE_LINUX_FBDEV 1 #endif +#ifndef LV_USE_WAYLAND + #define LV_USE_WAYLAND 1 + #define LV_WAYLAND_WINDOW_DECORATIONS 1 +#endif + #define LV_USE_ILI9341 1 #define LV_USE_ST7735 1 #define LV_USE_ST7789 1