Files
STM32F4-Base/Lib/lvgl/src/drivers/x11/lv_x11_display.c
T
2026-08-30 08:57:07 +08:00

396 lines
14 KiB
C

/**
* @file lv_x11_display.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_x11.h"
#if LV_USE_X11
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "../../core/lv_obj_pos.h"
/*********************
* DEFINES
*********************/
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#if LV_X11_RENDER_MODE_PARTIAL
#define LV_X11_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL
#elif defined LV_X11_RENDER_MODE_DIRECT
#define LV_X11_RENDER_MODE LV_DISPLAY_RENDER_MODE_DIRECT
#elif defined LV_X11_RENDER_MODE_FULL
#define LV_X11_RENDER_MODE LV_DISPLAY_RENDER_MODE_FULL
#endif
/**********************
* TYPEDEFS
**********************/
typedef struct {
/* header (containing X Display + input user data pointer - keep aligned with x11_input module!) */
_x11_user_hdr_t hdr;
/* X11 related information */
Window window; /**< X11 window object */
GC gc; /**< X11 graphics context object */
Visual * visual; /**< X11 visual */
int dplanes; /**< X11 display depth */
XImage * ximage; /**< X11 XImage cache object for updating window content */
Atom wmDeleteMessage; /**< X11 atom to window object */
void * xdata; /**< allocated data for XImage */
/* LVGL related information */
lv_timer_t * timer; /**< timer object for @ref x11_event_handler */
uint8_t * buffer[2]; /**< (double) lv display buffers, depending on @ref LV_X11_RENDER_MODE */
lv_area_t flush_area; /**< integrated area for a display update */
/* systemtick by thread related information */
pthread_t thr_tick; /**< pthread for SysTick simulation */
bool terminated; /**< flag to germinate SysTick simulation thread */
} x11_disp_data_t;
/**********************
* STATIC VARIABLES
**********************/
#if LV_X11_DIRECT_EXIT
static unsigned int count_windows = 0;
#endif
/**********************
* MACROS
**********************/
/**********************
* STATIC FUNCTIONS
**********************/
#if LV_COLOR_DEPTH == 32
typedef lv_color32_t color_t;
static inline lv_color32_t get_px(color_t p)
{
return (lv_color32_t)p;
}
#elif LV_COLOR_DEPTH == 24
typedef lv_color_t color_t;
static inline lv_color32_t get_px(color_t p)
{
lv_color32_t out = { .red = p.red, .green = p.green, .blue = p.blue };
return out;
}
#elif LV_COLOR_DEPTH == 16
typedef lv_color16_t color_t;
static inline lv_color32_t get_px(color_t p)
{
lv_color32_t out = { .red = p.red << 3, .green = p.green << 2, .blue = p.blue << 3 };
return out;
}
#elif LV_COLOR_DEPTH == 8
typedef uint8_t color_t;
static inline lv_color32_t get_px(color_t p)
{
lv_color32_t out = { .red = p, .green = p, .blue = p };
return out;
}
#warning ("LV_COLOR_DEPTH=8 delivers black data only - open issue in lvgl?")
#else
#error ("Unsupported LV_COLOR_DEPTH")
#endif
/**
* Flush the content of the internal buffer the specific area on the display.
* @param[in] disp the created X11 display object from @lv_x11_window_create
* @param[in] area area to be updated
* @param[in] px_map contains the rendered image as raw pixel map and it should be copied to `area` on the display.
* @note @ref lv_display_flush_ready has to be called when it's finished.
*/
static void x11_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
{
x11_disp_data_t * xd = lv_display_get_driver_data(disp);
LV_ASSERT_NULL(xd);
static const lv_area_t inv_area = { .x1 = 0xFFFF,
.x2 = 0,
.y1 = 0xFFFF,
.y2 = 0
};
/* build display update area until lv_display_flush_is_last */
xd->flush_area.x1 = MIN(xd->flush_area.x1, area->x1);
xd->flush_area.x2 = MAX(xd->flush_area.x2, area->x2);
xd->flush_area.y1 = MIN(xd->flush_area.y1, area->y1);
xd->flush_area.y2 = MAX(xd->flush_area.y2, area->y2);
int32_t hor_res = lv_display_get_horizontal_resolution(disp);
uint32_t dst_offs;
lv_color32_t * dst_data;
color_t * src_data = (color_t *)px_map + (LV_X11_RENDER_MODE == LV_DISPLAY_RENDER_MODE_PARTIAL ? 0 : hor_res *
area->y1 + area->x1);
for(int16_t y = area->y1; y <= area->y2; y++) {
dst_offs = area->x1 + y * hor_res;
dst_data = &((lv_color32_t *)(xd->xdata))[dst_offs];
for(int16_t x = area->x1; x <= area->x2; x++, src_data++, dst_data++) {
*dst_data = get_px(*src_data);
}
src_data += (LV_X11_RENDER_MODE == LV_DISPLAY_RENDER_MODE_PARTIAL ? 0 : hor_res - (area->x2 - area->x1 + 1));
}
if(lv_display_flush_is_last(disp)) {
LV_LOG_TRACE("(%d/%d), %dx%d)", xd->flush_area.x1, xd->flush_area.y1, xd->flush_area.x2 + 1 - xd->flush_area.x1,
xd->flush_area.y2 + 1 - xd->flush_area.y1);
/* refresh collected display update area only */
int16_t upd_w = xd->flush_area.x2 - xd->flush_area.x1 + 1;
int16_t upd_h = xd->flush_area.y2 - xd->flush_area.y1 + 1;
XPutImage(xd->hdr.display, xd->window, xd->gc, xd->ximage, xd->flush_area.x1, xd->flush_area.y1, xd->flush_area.x1,
xd->flush_area.y1, upd_w, upd_h);
/* invalidate collected area */
xd->flush_area = inv_area;
}
/* Inform the graphics library that you are ready with the flushing */
lv_display_flush_ready(disp);
}
/**
* event called by lvgl display if resolution has been changed (@ref lv_display_set_resolution has been called)
* @param[in] e event data, containing lv_display_t object
*/
static void x11_resolution_evt_cb(lv_event_t * e)
{
lv_display_t * disp = lv_event_get_user_data(e);
x11_disp_data_t * xd = lv_display_get_driver_data(disp);
LV_ASSERT_NULL(xd);
int32_t hor_res = lv_display_get_horizontal_resolution(disp);
int32_t ver_res = lv_display_get_vertical_resolution(disp);
if(LV_X11_RENDER_MODE != LV_DISPLAY_RENDER_MODE_PARTIAL) {
/* update lvgl full-screen display draw buffers for new display size */
int sz_buffers = (hor_res * ver_res * (LV_COLOR_DEPTH + 7) / 8);
xd->buffer[0] = realloc(xd->buffer[0], sz_buffers);
xd->buffer[1] = (LV_X11_DOUBLE_BUFFER ? realloc(xd->buffer[1], sz_buffers) : NULL);
lv_display_set_buffers(disp, xd->buffer[0], xd->buffer[1], sz_buffers, LV_X11_RENDER_MODE);
}
/* re-create cache image with new size */
XDestroyImage(xd->ximage);
size_t sz_buffers = hor_res * ver_res * sizeof(lv_color32_t);
xd->xdata = malloc(sz_buffers); /* use clib method here, x11 memory not part of device footprint */
xd->ximage = XCreateImage(xd->hdr.display, xd->visual, xd->dplanes, ZPixmap, 0, xd->xdata,
hor_res, ver_res, lv_color_format_get_bpp(LV_COLOR_FORMAT_ARGB8888), 0);
}
/**
* event called by lvgl display if display has been closed (@ref lv_display_delete has been called)
* @param[in] e event data, containing lv_display_t object
*/
static void x11_disp_delete_evt_cb(lv_event_t * e)
{
lv_display_t * disp = lv_event_get_user_data(e);
x11_disp_data_t * xd = lv_display_get_driver_data(disp);
lv_timer_delete(xd->timer);
free(xd->buffer[0]);
if(LV_X11_DOUBLE_BUFFER) {
free(xd->buffer[1]);
}
XDestroyImage(xd->ximage);
XFreeGC(xd->hdr.display, xd->gc);
XUnmapWindow(xd->hdr.display, xd->window);
XDestroyWindow(xd->hdr.display, xd->window);
XFlush(xd->hdr.display);
XCloseDisplay(xd->hdr.display);
lv_free(xd);
#if LV_X11_DIRECT_EXIT
if(0 == --count_windows) {
exit(0);
}
#endif
}
static void x11_hide_cursor(lv_display_t * disp)
{
x11_disp_data_t * xd = lv_display_get_driver_data(disp);
LV_ASSERT_NULL(xd);
XColor black = { .red = 0, .green = 0, .blue = 0 };
char empty_data[] = { 0 };
Pixmap empty_bitmap = XCreateBitmapFromData(xd->hdr.display, xd->window, empty_data, 1, 1);
Cursor inv_cursor = XCreatePixmapCursor(xd->hdr.display, empty_bitmap, empty_bitmap, &black, &black, 0, 0);
XDefineCursor(xd->hdr.display, xd->window, inv_cursor);
XFreeCursor(xd->hdr.display, inv_cursor);
XFreePixmap(xd->hdr.display, empty_bitmap);
}
/**
* X11 input event handler, predicated to fetch and handle only display related events
* (Window changes)
*/
static int is_disp_event(Display * disp, XEvent * event, XPointer arg)
{
LV_UNUSED(disp);
LV_UNUSED(arg);
return (event->type == Expose
|| (event->type >= DestroyNotify && event->type <= CirculateNotify) /* events from StructureNotifyMask */
|| event->type == ClientMessage);
}
static void x11_event_handler(lv_timer_t * t)
{
lv_display_t * disp = lv_timer_get_user_data(t);
x11_disp_data_t * xd = lv_display_get_driver_data(disp);
LV_ASSERT_NULL(xd);
/* handle all outstanding X events */
XEvent event;
while(XCheckIfEvent(xd->hdr.display, &event, is_disp_event, NULL)) {
LV_LOG_TRACE("Display Event %d", event.type);
switch(event.type) {
case Expose:
if(event.xexpose.count == 0) {
XPutImage(xd->hdr.display, xd->window, xd->gc, xd->ximage, 0, 0, 0, 0, event.xexpose.width, event.xexpose.height);
}
break;
case ConfigureNotify:
if(event.xconfigure.width != lv_display_get_horizontal_resolution(disp)
|| event.xconfigure.height != lv_display_get_vertical_resolution(disp)) {
lv_display_set_resolution(disp, event.xconfigure.width, event.xconfigure.height);
}
break;
case ClientMessage:
if(event.xclient.data.l[0] == (long)xd->wmDeleteMessage) {
xd->terminated = true;
void * ret = NULL;
pthread_join(xd->thr_tick, &ret);
lv_display_delete(disp);
return;
}
break;
case MapNotify:
case ReparentNotify:
/*suppress unhandled warning*/
break;
default:
LV_LOG_WARN("unhandled x11 event: %d", event.type);
}
}
}
static void * x11_tick_thread(void * data)
{
x11_disp_data_t * xd = data;
LV_ASSERT_NULL(xd);
while(!xd->terminated) {
usleep(5000);
lv_tick_inc(5);
}
return NULL;
}
static void x11_window_create(lv_display_t * disp, char const * title)
{
x11_disp_data_t * xd = lv_display_get_driver_data(disp);
LV_ASSERT_NULL(xd);
/* setup display/screen */
xd->hdr.display = XOpenDisplay(NULL);
int screen = XDefaultScreen(xd->hdr.display);
xd->visual = XDefaultVisual(xd->hdr.display, screen);
/* create window */
int32_t hor_res = lv_display_get_horizontal_resolution(disp);
int32_t ver_res = lv_display_get_vertical_resolution(disp);
#if 0
/* drawing contexts for an window */
unsigned long col_fg = BlackPixel(xd->hdr.display, screen);
unsigned long col_bg = WhitePixel(xd->hdr.display, screen);
xd->window = XCreateSimpleWindow(xd->hdr.display, DefaultRootWindow(xd->hdr.display),
0, 0, hor_res, ver_res, 0, col_fg, col_bg);
#else
xd->window = XCreateWindow(xd->hdr.display, XDefaultRootWindow(xd->hdr.display),
0, 0, hor_res, ver_res, 0,
XDefaultDepth(xd->hdr.display, screen), InputOutput,
xd->visual, 0, NULL);
#endif
/* window manager properties (yes, use of StdProp is obsolete) */
XSetStandardProperties(xd->hdr.display, xd->window, title, NULL, None, NULL, 0, NULL);
xd->gc = XCreateGC(xd->hdr.display, xd->window, 0, 0);
/* allow receiving mouse, keyboard and window change/close events */
XSelectInput(xd->hdr.display, xd->window,
PointerMotionMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | ExposureMask |
StructureNotifyMask);
xd->wmDeleteMessage = XInternAtom(xd->hdr.display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(xd->hdr.display, xd->window, &xd->wmDeleteMessage, 1);
x11_hide_cursor(disp);
/* create cache XImage */
size_t sz_buffers = hor_res * ver_res * sizeof(lv_color32_t);
xd->dplanes = XDisplayPlanes(xd->hdr.display, screen);
xd->xdata = malloc(sz_buffers); /* use clib method here, x11 memory not part of device footprint */
xd->ximage = XCreateImage(xd->hdr.display, xd->visual, xd->dplanes, ZPixmap, 0, xd->xdata,
hor_res, ver_res, lv_color_format_get_bpp(LV_COLOR_FORMAT_ARGB8888), 0);
/* finally bring window on top of the other windows */
XMapRaised(xd->hdr.display, xd->window);
#if LV_X11_DIRECT_EXIT
count_windows++;
#endif
}
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_display_t * lv_x11_window_create(char const * title, int32_t hor_res, int32_t ver_res)
{
x11_disp_data_t * xd = lv_malloc_zeroed(sizeof(x11_disp_data_t));
LV_ASSERT_MALLOC(xd);
if(NULL == xd) return NULL;
lv_display_t * disp = lv_display_create(hor_res, ver_res);
if(NULL == disp) {
lv_free(xd);
return NULL;
}
lv_display_set_driver_data(disp, xd);
lv_display_set_flush_cb(disp, x11_flush_cb);
lv_display_add_event_cb(disp, x11_resolution_evt_cb, LV_EVENT_RESOLUTION_CHANGED, disp);
lv_display_add_event_cb(disp, x11_disp_delete_evt_cb, LV_EVENT_DELETE, disp);
x11_window_create(disp, title);
int sz_buffers = (hor_res * ver_res * (LV_COLOR_DEPTH + 7) / 8);
if(LV_X11_RENDER_MODE == LV_DISPLAY_RENDER_MODE_PARTIAL) {
sz_buffers /= 10;
}
xd->buffer[0] = malloc(sz_buffers);
xd->buffer[1] = (LV_X11_DOUBLE_BUFFER ? malloc(sz_buffers) : NULL);
lv_display_set_buffers(disp, xd->buffer[0], xd->buffer[1], sz_buffers, LV_X11_RENDER_MODE);
xd->timer = lv_timer_create(x11_event_handler, 5, disp);
/* initialize Tick simulation */
xd->terminated = false;
pthread_create(&xd->thr_tick, NULL, x11_tick_thread, xd);
return disp;
}
#endif /*LV_USE_X11*/