1 /**
2  * @file lv_x11_display.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_x11.h"
10 
11 #if LV_USE_X11
12 
13 #include <stdbool.h>
14 #include <unistd.h>
15 #include <stdlib.h>
16 #include <pthread.h>
17 #include <X11/Xlib.h>
18 #include <X11/Xutil.h>
19 #include "../../core/lv_obj_pos.h"
20 
21 /*********************
22  *      DEFINES
23  *********************/
24 #define MIN(A, B) ((A) < (B) ? (A) : (B))
25 #define MAX(A, B) ((A) > (B) ? (A) : (B))
26 
27 #if LV_X11_RENDER_MODE_PARTIAL
28     #define LV_X11_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL
29 #elif defined LV_X11_RENDER_MODE_DIRECT
30     #define LV_X11_RENDER_MODE LV_DISPLAY_RENDER_MODE_DIRECT
31 #elif defined LV_X11_RENDER_MODE_FULL
32     #define LV_X11_RENDER_MODE LV_DISPLAY_RENDER_MODE_FULL
33 #endif
34 
35 /**********************
36  *      TYPEDEFS
37  **********************/
38 
39 typedef struct {
40     /* header (containing X Display + input user data pointer - keep aligned with x11_input module!) */
41     _x11_user_hdr_t hdr;
42     /* X11 related information */
43     Window          window;          /**< X11 window object */
44     GC              gc;              /**< X11 graphics context object */
45     Visual     *    visual;          /**< X11 visual */
46     int             dplanes;         /**< X11 display depth */
47     XImage     *    ximage;          /**< X11 XImage cache object for updating window content */
48     Atom            wmDeleteMessage; /**< X11 atom to window object */
49     void      *     xdata;           /**< allocated data for XImage */
50     /* LVGL related information */
51     lv_timer_t   *  timer;           /**< timer object for @ref x11_event_handler */
52     uint8_t    *    buffer[2];       /**< (double) lv display buffers, depending on @ref LV_X11_RENDER_MODE */
53     lv_area_t       flush_area;      /**< integrated area for a display update */
54     /* systemtick by thread related information */
55     pthread_t       thr_tick;        /**< pthread for SysTick simulation */
56     bool            terminated;      /**< flag to germinate SysTick simulation thread */
57 } x11_disp_data_t;
58 
59 /**********************
60  *  STATIC VARIABLES
61  **********************/
62 #if LV_X11_DIRECT_EXIT
63     static unsigned int count_windows = 0;
64 #endif
65 
66 /**********************
67  *      MACROS
68  **********************/
69 
70 /**********************
71  *   STATIC FUNCTIONS
72  **********************/
73 
74 #if   LV_COLOR_DEPTH == 32
75 typedef lv_color32_t color_t;
get_px(color_t p)76 static inline lv_color32_t get_px(color_t p)
77 {
78     return (lv_color32_t)p;
79 }
80 #elif LV_COLOR_DEPTH == 24
81 typedef lv_color_t color_t;
get_px(color_t p)82 static inline lv_color32_t get_px(color_t p)
83 {
84     lv_color32_t out = { .red = p.red, .green = p.green, .blue = p.blue };
85     return out;
86 }
87 #elif LV_COLOR_DEPTH == 16
88 typedef lv_color16_t color_t;
get_px(color_t p)89 static inline lv_color32_t get_px(color_t p)
90 {
91     lv_color32_t out = { .red = p.red << 3, .green = p.green << 2, .blue = p.blue << 3 };
92     return out;
93 }
94 #elif LV_COLOR_DEPTH == 8
95 typedef uint8_t color_t;
get_px(color_t p)96 static inline lv_color32_t get_px(color_t p)
97 {
98     lv_color32_t out = { .red = p, .green = p, .blue = p };
99     return out;
100 }
101 #warning ("LV_COLOR_DEPTH=8 delivers black data only - open issue in lvgl?")
102 #else
103 #error ("Unsupported LV_COLOR_DEPTH")
104 #endif
105 
106 /**
107  * Flush the content of the internal buffer the specific area on the display.
108  * @param[in] disp    the created X11 display object from @lv_x11_window_create
109  * @param[in] area    area to be updated
110  * @param[in] px_map  contains the rendered image as raw pixel map and it should be copied to `area` on the display.
111  * @note              @ref lv_display_flush_ready has to be called when it's finished.
112  */
x11_flush_cb(lv_display_t * disp,const lv_area_t * area,uint8_t * px_map)113 static void x11_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
114 {
115     x11_disp_data_t * xd = lv_display_get_driver_data(disp);
116     LV_ASSERT_NULL(xd);
117 
118     static const lv_area_t inv_area = { .x1 = 0xFFFF,
119                                         .x2 = 0,
120                                         .y1 = 0xFFFF,
121                                         .y2 = 0
122                                       };
123 
124     /* build display update area until lv_display_flush_is_last */
125     xd->flush_area.x1 = MIN(xd->flush_area.x1, area->x1);
126     xd->flush_area.x2 = MAX(xd->flush_area.x2, area->x2);
127     xd->flush_area.y1 = MIN(xd->flush_area.y1, area->y1);
128     xd->flush_area.y2 = MAX(xd->flush_area.y2, area->y2);
129 
130     int32_t hor_res = lv_display_get_horizontal_resolution(disp);
131 
132     uint32_t      dst_offs;
133     lv_color32_t * dst_data;
134     color_t   *   src_data = (color_t *)px_map + (LV_X11_RENDER_MODE == LV_DISPLAY_RENDER_MODE_PARTIAL ? 0 : hor_res *
135                                                   area->y1 + area->x1);
136     for(int16_t y = area->y1; y <= area->y2; y++) {
137         dst_offs = area->x1 + y * hor_res;
138         dst_data = &((lv_color32_t *)(xd->xdata))[dst_offs];
139         for(int16_t x = area->x1; x <= area->x2; x++, src_data++, dst_data++) {
140             *dst_data = get_px(*src_data);
141         }
142         src_data += (LV_X11_RENDER_MODE == LV_DISPLAY_RENDER_MODE_PARTIAL ? 0 : hor_res - (area->x2 - area->x1 + 1));
143     }
144 
145     if(lv_display_flush_is_last(disp)) {
146         LV_LOG_TRACE("(%d/%d), %dx%d)", xd->flush_area.x1, xd->flush_area.y1, xd->flush_area.x2 + 1 - xd->flush_area.x1,
147                      xd->flush_area.y2 + 1 - xd->flush_area.y1);
148 
149         /* refresh collected display update area only */
150         int16_t upd_w = xd->flush_area.x2 - xd->flush_area.x1 + 1;
151         int16_t upd_h = xd->flush_area.y2 - xd->flush_area.y1 + 1;
152         XPutImage(xd->hdr.display, xd->window, xd->gc, xd->ximage, xd->flush_area.x1, xd->flush_area.y1, xd->flush_area.x1,
153                   xd->flush_area.y1, upd_w, upd_h);
154 
155         /* invalidate collected area */
156         xd->flush_area = inv_area;
157     }
158     /* Inform the graphics library that you are ready with the flushing */
159     lv_display_flush_ready(disp);
160 }
161 
162 /**
163  * event called by lvgl display if resolution has been changed (@ref lv_display_set_resolution has been called)
164  * @param[in] e  event data, containing lv_display_t object
165  */
x11_resolution_evt_cb(lv_event_t * e)166 static void x11_resolution_evt_cb(lv_event_t * e)
167 {
168     lv_display_t * disp = lv_event_get_user_data(e);
169     x11_disp_data_t * xd = lv_display_get_driver_data(disp);
170     LV_ASSERT_NULL(xd);
171 
172     int32_t hor_res = lv_display_get_horizontal_resolution(disp);
173     int32_t ver_res = lv_display_get_vertical_resolution(disp);
174 
175     if(LV_X11_RENDER_MODE != LV_DISPLAY_RENDER_MODE_PARTIAL) {
176         /* update lvgl full-screen display draw buffers for new display size */
177         int sz_buffers = (hor_res * ver_res * (LV_COLOR_DEPTH + 7) / 8);
178         xd->buffer[0] = realloc(xd->buffer[0], sz_buffers);
179         xd->buffer[1] = (LV_X11_DOUBLE_BUFFER ?  realloc(xd->buffer[1], sz_buffers) : NULL);
180         lv_display_set_buffers(disp, xd->buffer[0], xd->buffer[1], sz_buffers, LV_X11_RENDER_MODE);
181     }
182 
183     /* re-create cache image with new size */
184     XDestroyImage(xd->ximage);
185     size_t sz_buffers = hor_res * ver_res * sizeof(lv_color32_t);
186     xd->xdata = malloc(sz_buffers); /* use clib method here, x11 memory not part of device footprint */
187     xd->ximage = XCreateImage(xd->hdr.display, xd->visual, xd->dplanes, ZPixmap, 0, xd->xdata,
188                               hor_res, ver_res, lv_color_format_get_bpp(LV_COLOR_FORMAT_ARGB8888), 0);
189 }
190 
191 /**
192  * event called by lvgl display if display has been closed (@ref lv_display_delete has been called)
193  * @param[in] e  event data, containing lv_display_t object
194  */
x11_disp_delete_evt_cb(lv_event_t * e)195 static void x11_disp_delete_evt_cb(lv_event_t * e)
196 {
197     lv_display_t * disp = lv_event_get_user_data(e);
198     x11_disp_data_t * xd = lv_display_get_driver_data(disp);
199 
200     lv_timer_delete(xd->timer);
201 
202     free(xd->buffer[0]);
203     if(LV_X11_DOUBLE_BUFFER) {
204         free(xd->buffer[1]);
205     }
206 
207     XDestroyImage(xd->ximage);
208     XFreeGC(xd->hdr.display, xd->gc);
209     XUnmapWindow(xd->hdr.display, xd->window);
210     XDestroyWindow(xd->hdr.display, xd->window);
211     XFlush(xd->hdr.display);
212 
213     lv_free(xd);
214 #if LV_X11_DIRECT_EXIT
215     if(0 == --count_windows) {
216         exit(0);
217     }
218 #endif
219 }
220 
x11_hide_cursor(lv_display_t * disp)221 static void x11_hide_cursor(lv_display_t * disp)
222 {
223     x11_disp_data_t * xd = lv_display_get_driver_data(disp);
224     LV_ASSERT_NULL(xd);
225 
226     XColor black = { .red = 0, .green = 0, .blue = 0 };
227     char empty_data[] = { 0 };
228 
229     Pixmap empty_bitmap = XCreateBitmapFromData(xd->hdr.display, xd->window, empty_data, 1, 1);
230     Cursor inv_cursor = XCreatePixmapCursor(xd->hdr.display, empty_bitmap, empty_bitmap, &black, &black, 0, 0);
231     XDefineCursor(xd->hdr.display, xd->window, inv_cursor);
232     XFreeCursor(xd->hdr.display, inv_cursor);
233     XFreePixmap(xd->hdr.display, empty_bitmap);
234 }
235 
236 /**
237  * X11 input event handler, predicated to fetch and handle only display related events
238  * (Window changes)
239  */
is_disp_event(Display * disp,XEvent * event,XPointer arg)240 static int is_disp_event(Display * disp, XEvent * event, XPointer arg)
241 {
242     LV_UNUSED(disp);
243     LV_UNUSED(arg);
244     return (event->type == Expose
245             || (event->type >= DestroyNotify && event->type <= CirculateNotify) /* events from StructureNotifyMask */
246             ||  event->type == ClientMessage);
247 }
x11_event_handler(lv_timer_t * t)248 static void x11_event_handler(lv_timer_t * t)
249 {
250     lv_display_t * disp = lv_timer_get_user_data(t);
251     x11_disp_data_t * xd = lv_display_get_driver_data(disp);
252     LV_ASSERT_NULL(xd);
253 
254     /* handle all outstanding X events */
255     XEvent event;
256     while(XCheckIfEvent(xd->hdr.display, &event, is_disp_event, NULL)) {
257         LV_LOG_TRACE("Display Event %d", event.type);
258         switch(event.type) {
259             case Expose:
260                 if(event.xexpose.count == 0) {
261                     XPutImage(xd->hdr.display, xd->window, xd->gc, xd->ximage, 0, 0, 0, 0, event.xexpose.width, event.xexpose.height);
262                 }
263                 break;
264             case ConfigureNotify:
265                 if(event.xconfigure.width  != lv_display_get_horizontal_resolution(disp)
266                    ||  event.xconfigure.height != lv_display_get_vertical_resolution(disp)) {
267                     lv_display_set_resolution(disp, event.xconfigure.width, event.xconfigure.height);
268                 }
269                 break;
270             case ClientMessage:
271                 if(event.xclient.data.l[0] == (long)xd->wmDeleteMessage) {
272                     xd->terminated = true;
273                     void * ret = NULL;
274                     pthread_join(xd->thr_tick, &ret);
275                     lv_display_delete(disp);
276                     return;
277                 }
278                 break;
279             case MapNotify:
280             case ReparentNotify:
281                 /*suppress unhandled warning*/
282                 break;
283             default:
284                 LV_LOG_WARN("unhandled x11 event: %d", event.type);
285         }
286     }
287 }
288 
x11_tick_thread(void * data)289 static void * x11_tick_thread(void * data)
290 {
291     x11_disp_data_t * xd = data;
292     LV_ASSERT_NULL(xd);
293 
294     while(!xd->terminated) {
295         usleep(5000);
296         lv_tick_inc(5);
297     }
298     return NULL;
299 }
300 
x11_window_create(lv_display_t * disp,char const * title)301 static void x11_window_create(lv_display_t * disp, char const * title)
302 {
303     x11_disp_data_t * xd = lv_display_get_driver_data(disp);
304     LV_ASSERT_NULL(xd);
305 
306     /* setup display/screen */
307     xd->hdr.display = XOpenDisplay(NULL);
308     int screen = XDefaultScreen(xd->hdr.display);
309     xd->visual = XDefaultVisual(xd->hdr.display, screen);
310 
311     /* create window */
312     int32_t hor_res = lv_display_get_horizontal_resolution(disp);
313     int32_t ver_res = lv_display_get_vertical_resolution(disp);
314 #if 0
315     /* drawing contexts for an window */
316     unsigned long col_fg = BlackPixel(xd->hdr.display, screen);
317     unsigned long col_bg = WhitePixel(xd->hdr.display, screen);
318 
319     xd->window = XCreateSimpleWindow(xd->hdr.display, DefaultRootWindow(xd->hdr.display),
320                                      0, 0, hor_res, ver_res, 0, col_fg, col_bg);
321 #else
322     xd->window = XCreateWindow(xd->hdr.display, XDefaultRootWindow(xd->hdr.display),
323                                0, 0, hor_res, ver_res, 0,
324                                XDefaultDepth(xd->hdr.display, screen), InputOutput,
325                                xd->visual, 0, NULL);
326 #endif
327     /* window manager properties (yes, use of StdProp is obsolete) */
328     XSetStandardProperties(xd->hdr.display, xd->window, title, NULL, None, NULL, 0, NULL);
329     xd->gc = XCreateGC(xd->hdr.display, xd->window, 0, 0);
330 
331     /* allow receiving mouse, keyboard and window change/close events */
332     XSelectInput(xd->hdr.display, xd->window,
333                  PointerMotionMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | ExposureMask |
334                  StructureNotifyMask);
335     xd->wmDeleteMessage = XInternAtom(xd->hdr.display, "WM_DELETE_WINDOW", False);
336     XSetWMProtocols(xd->hdr.display, xd->window, &xd->wmDeleteMessage, 1);
337 
338     x11_hide_cursor(disp);
339 
340     /* create cache XImage */
341     size_t sz_buffers = hor_res * ver_res * sizeof(lv_color32_t);
342     xd->dplanes = XDisplayPlanes(xd->hdr.display, screen);
343     xd->xdata = malloc(sz_buffers); /* use clib method here, x11 memory not part of device footprint */
344     xd->ximage = XCreateImage(xd->hdr.display, xd->visual, xd->dplanes, ZPixmap, 0, xd->xdata,
345                               hor_res, ver_res, lv_color_format_get_bpp(LV_COLOR_FORMAT_ARGB8888), 0);
346 
347     /* finally bring window on top of the other windows */
348     XMapRaised(xd->hdr.display, xd->window);
349 
350 #if LV_X11_DIRECT_EXIT
351     count_windows++;
352 #endif
353 }
354 
355 /**********************
356  *   GLOBAL FUNCTIONS
357  **********************/
358 
lv_x11_window_create(char const * title,int32_t hor_res,int32_t ver_res)359 lv_display_t * lv_x11_window_create(char const * title, int32_t hor_res, int32_t ver_res)
360 {
361     x11_disp_data_t * xd = lv_malloc_zeroed(sizeof(x11_disp_data_t));
362     LV_ASSERT_MALLOC(xd);
363     if(NULL == xd) return NULL;
364 
365     lv_display_t * disp = lv_display_create(hor_res, ver_res);
366     if(NULL == disp) {
367         lv_free(xd);
368         return NULL;
369     }
370     lv_display_set_driver_data(disp, xd);
371     lv_display_set_flush_cb(disp, x11_flush_cb);
372     lv_display_add_event_cb(disp, x11_resolution_evt_cb, LV_EVENT_RESOLUTION_CHANGED, disp);
373     lv_display_add_event_cb(disp, x11_disp_delete_evt_cb, LV_EVENT_DELETE, disp);
374 
375     x11_window_create(disp, title);
376 
377     int sz_buffers = (hor_res * ver_res * (LV_COLOR_DEPTH + 7) / 8);
378     if(LV_X11_RENDER_MODE == LV_DISPLAY_RENDER_MODE_PARTIAL) {
379         sz_buffers /= 10;
380     }
381     xd->buffer[0] = malloc(sz_buffers);
382     xd->buffer[1] = (LV_X11_DOUBLE_BUFFER ? malloc(sz_buffers) : NULL);
383     lv_display_set_buffers(disp, xd->buffer[0], xd->buffer[1], sz_buffers, LV_X11_RENDER_MODE);
384 
385     xd->timer = lv_timer_create(x11_event_handler, 5, disp);
386 
387     /* initialize Tick simulation */
388     xd->terminated = false;
389     pthread_create(&xd->thr_tick, NULL, x11_tick_thread, xd);
390 
391     return disp;
392 }
393 
394 #endif /*LV_USE_X11*/
395