feat(driver): import Wayland driver from v8 (#6549)

This commit is contained in:
Erik Tagirov 2024-08-23 22:03:55 +02:00 committed by GitHub
parent aebb4d3ad3
commit e186b4c8b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 4069 additions and 1 deletions

1
.gitignore vendored
View File

@ -24,6 +24,7 @@ test_screenshot_error.h
build/
tests/build_*/
tests/report/
tests/wayland_protocols/
.DS_Store
.vscode
.idea

12
Kconfig
View File

@ -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

View File

@ -11,3 +11,4 @@ Drivers
X11
windows
opengles
wayland

View File

@ -0,0 +1,180 @@
=============================
Wayland Display/Inputs driver
=============================
Overview
--------
| The **Wayland** `driver <https://github.com/lvgl/lvgl/tree/master/src/drivers/wayland>`__ 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 <https://github.com/lvgl/lv_port_linux/>`__
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 <https://github.com/lvgl/lv_port_linux/>`__
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 </usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml > wayland_xdg_shell.h
wayland-scanner private-code </usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml > 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

View File

@ -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

View File

@ -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

View File

@ -43,6 +43,8 @@ extern "C" {
#include "qnx/lv_qnx.h"
#include "wayland/lv_wayland.h"
/*********************
* DEFINES
*********************/

File diff suppressed because it is too large Load Diff

View File

@ -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 */

View File

@ -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 <stddef.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#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 */

View File

@ -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 */

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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