1 /**
2 * @file lv_libinput.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9
10 #include "../../indev/lv_indev_private.h"
11 #include "lv_libinput_private.h"
12
13 #if LV_USE_LIBINPUT
14
15 #include "../../display/lv_display_private.h"
16
17 #include <stdio.h>
18 #include <unistd.h>
19 #include <linux/limits.h>
20 #include <fcntl.h>
21 #include <errno.h>
22 #include <stdbool.h>
23 #include <dirent.h>
24 #include <libinput.h>
25 #include <pthread.h>
26 #include <string.h>
27
28 #if LV_LIBINPUT_BSD
29 #include <dev/evdev/input.h>
30 #else
31 #include <linux/input.h>
32 #endif
33
34 /*********************
35 * DEFINES
36 *********************/
37
38 /**********************
39 * TYPEDEFS
40 **********************/
41
42 struct _lv_libinput_device {
43 lv_libinput_capability capabilities;
44 char * path;
45 };
46
47 /**********************
48 * STATIC PROTOTYPES
49 **********************/
50
51 static bool _rescan_devices(void);
52 static bool _add_scanned_device(char * path, lv_libinput_capability capabilities);
53 static void _reset_scanned_devices(void);
54
55 static void * _poll_thread(void * data);
56
57 lv_libinput_event_t * _get_event(lv_libinput_t * state);
58 bool _event_pending(lv_libinput_t * state);
59 lv_libinput_event_t * _create_event(lv_libinput_t * state);
60
61 static void _read(lv_indev_t * indev, lv_indev_data_t * data);
62 static void _read_pointer(lv_libinput_t * state, struct libinput_event * event);
63 static void _read_keypad(lv_libinput_t * state, struct libinput_event * event);
64
65 static int _open_restricted(const char * path, int flags, void * user_data);
66 static void _close_restricted(int fd, void * user_data);
67
68 static void _delete(lv_libinput_t * dsc);
69
70 /**********************
71 * STATIC VARIABLES
72 **********************/
73
74 static struct _lv_libinput_device * devices = NULL;
75 static size_t num_devices = 0;
76
77 static const int timeout = 100; // ms
78 static const nfds_t nfds = 1;
79
80 static const struct libinput_interface interface = {
81 .open_restricted = _open_restricted,
82 .close_restricted = _close_restricted,
83 };
84
85 /**********************
86 * MACROS
87 **********************/
88
89 /**********************
90 * GLOBAL FUNCTIONS
91 **********************/
92
lv_libinput_query_capability(struct libinput_device * device)93 lv_libinput_capability lv_libinput_query_capability(struct libinput_device * device)
94 {
95 lv_libinput_capability capability = LV_LIBINPUT_CAPABILITY_NONE;
96 if(libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_KEYBOARD)
97 && (libinput_device_keyboard_has_key(device, KEY_ENTER) || libinput_device_keyboard_has_key(device, KEY_KPENTER))) {
98 capability |= LV_LIBINPUT_CAPABILITY_KEYBOARD;
99 }
100 if(libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_POINTER)) {
101 capability |= LV_LIBINPUT_CAPABILITY_POINTER;
102 }
103 if(libinput_device_has_capability(device, LIBINPUT_DEVICE_CAP_TOUCH)) {
104 capability |= LV_LIBINPUT_CAPABILITY_TOUCH;
105 }
106 return capability;
107 }
108
lv_libinput_find_dev(lv_libinput_capability capabilities,bool force_rescan)109 char * lv_libinput_find_dev(lv_libinput_capability capabilities, bool force_rescan)
110 {
111 char * path = NULL;
112 lv_libinput_find_devs(capabilities, &path, 1, force_rescan);
113 return path;
114 }
115
lv_libinput_find_devs(lv_libinput_capability capabilities,char ** found,size_t count,bool force_rescan)116 size_t lv_libinput_find_devs(lv_libinput_capability capabilities, char ** found, size_t count, bool force_rescan)
117 {
118 if((!devices || force_rescan) && !_rescan_devices()) {
119 return 0;
120 }
121
122 size_t num_found = 0;
123
124 for(size_t i = 0; i < num_devices && num_found < count; ++i) {
125 if(devices[i].capabilities & capabilities) {
126 found[num_found] = devices[i].path;
127 num_found++;
128 }
129 }
130
131 return num_found;
132 }
133
lv_libinput_create(lv_indev_type_t indev_type,const char * dev_path)134 lv_indev_t * lv_libinput_create(lv_indev_type_t indev_type, const char * dev_path)
135 {
136 lv_libinput_t * dsc = lv_malloc_zeroed(sizeof(lv_libinput_t));
137 LV_ASSERT_MALLOC(dsc);
138 if(dsc == NULL) return NULL;
139
140 dsc->libinput_context = libinput_path_create_context(&interface, NULL);
141 if(!dsc->libinput_context) {
142 LV_LOG_ERROR("libinput_path_create_context failed: %s", strerror(errno));
143 _delete(dsc);
144 return NULL;
145 }
146
147 dsc->libinput_device = libinput_path_add_device(dsc->libinput_context, dev_path);
148 if(!dsc->libinput_device) {
149 _delete(dsc);
150 return NULL;
151 }
152
153 dsc->libinput_device = libinput_device_ref(dsc->libinput_device);
154 if(!dsc->libinput_device) {
155 _delete(dsc);
156 return NULL;
157 }
158
159 dsc->fd = libinput_get_fd(dsc->libinput_context);
160
161 /* Prepare poll */
162 dsc->fds[0].fd = dsc->fd;
163 dsc->fds[0].events = POLLIN;
164 dsc->fds[0].revents = 0;
165
166 #if LV_LIBINPUT_XKB
167 struct xkb_rule_names names = LV_LIBINPUT_XKB_KEY_MAP;
168 lv_xkb_init(&(dsc->xkb), names);
169 #endif /* LV_LIBINPUT_XKB */
170
171 /* Create indev */
172 lv_indev_t * indev = lv_indev_create();
173 if(!indev) {
174 _delete(dsc);
175 return NULL;
176 }
177 lv_indev_set_type(indev, indev_type);
178 lv_indev_set_read_cb(indev, _read);
179 lv_indev_set_driver_data(indev, dsc);
180
181 /* Set up thread & lock */
182 pthread_mutex_init(&dsc->event_lock, NULL);
183 pthread_create(&dsc->worker_thread, NULL, _poll_thread, dsc);
184
185 return indev;
186 }
187
lv_libinput_delete(lv_indev_t * indev)188 void lv_libinput_delete(lv_indev_t * indev)
189 {
190 _delete(lv_indev_get_driver_data(indev));
191 lv_indev_delete(indev);
192 }
193
194 /**********************
195 * STATIC FUNCTIONS
196 **********************/
197
198 /**
199 * rescan all attached evdev devices and store capable ones into the static devices array for quick later filtering
200 * @return true if the operation succeeded
201 */
_rescan_devices(void)202 static bool _rescan_devices(void)
203 {
204 _reset_scanned_devices();
205
206 DIR * dir;
207 struct dirent * ent;
208 if(!(dir = opendir("/dev/input"))) {
209 perror("unable to open directory /dev/input");
210 return false;
211 }
212
213 struct libinput * context = libinput_path_create_context(&interface, NULL);
214
215 while((ent = readdir(dir))) {
216 if(strncmp(ent->d_name, "event", 5) != 0) {
217 continue;
218 }
219
220 /* 11 characters for /dev/input/ + length of name + 1 NUL terminator */
221 char * path = malloc((11 + strlen(ent->d_name) + 1) * sizeof(char));
222 if(!path) {
223 perror("could not allocate memory for device node path");
224 libinput_unref(context);
225 _reset_scanned_devices();
226 return false;
227 }
228 strcpy(path, "/dev/input/");
229 strcat(path, ent->d_name);
230
231 struct libinput_device * device = libinput_path_add_device(context, path);
232 if(!device) {
233 perror("unable to add device to libinput context");
234 free(path);
235 continue;
236 }
237
238 /* The device pointer is guaranteed to be valid until the next libinput_dispatch. Since we're not dispatching events
239 * as part of this function, we don't have to increase its reference count to keep it alive.
240 * https://wayland.freedesktop.org/libinput/doc/latest/api/group__base.html#gaa797496f0150b482a4e01376bd33a47b */
241
242 lv_libinput_capability capabilities = lv_libinput_query_capability(device);
243
244 libinput_path_remove_device(device);
245
246 if(capabilities == LV_LIBINPUT_CAPABILITY_NONE) {
247 free(path);
248 continue;
249 }
250
251 if(!_add_scanned_device(path, capabilities)) {
252 free(path);
253 libinput_unref(context);
254 _reset_scanned_devices();
255 return false;
256 }
257 }
258
259 libinput_unref(context);
260 return true;
261 }
262
263 /**
264 * add a new scanned device to the static devices array, growing its size when necessary
265 * @param path device file path
266 * @param capabilities device input capabilities
267 * @return true if the operation succeeded
268 */
_add_scanned_device(char * path,lv_libinput_capability capabilities)269 static bool _add_scanned_device(char * path, lv_libinput_capability capabilities)
270 {
271 /* Double array size every 2^n elements */
272 if((num_devices & (num_devices + 1)) == 0) {
273 struct _lv_libinput_device * tmp = realloc(devices, (2 * num_devices + 1) * sizeof(struct _lv_libinput_device));
274 if(!tmp) {
275 perror("could not reallocate memory for devices array");
276 return false;
277 }
278 devices = tmp;
279 }
280
281 devices[num_devices].path = path;
282 devices[num_devices].capabilities = capabilities;
283 num_devices++;
284
285 return true;
286 }
287
288 /**
289 * reset the array of scanned devices and free any dynamically allocated memory
290 */
_reset_scanned_devices(void)291 static void _reset_scanned_devices(void)
292 {
293 if(!devices) {
294 return;
295 }
296
297 for(size_t i = 0; i < num_devices; ++i) {
298 free(devices[i].path);
299 }
300 free(devices);
301
302 devices = NULL;
303 num_devices = 0;
304 }
305
_poll_thread(void * data)306 static void * _poll_thread(void * data)
307 {
308 lv_libinput_t * dsc = (lv_libinput_t *)data;
309 struct libinput_event * event;
310 int rc = 0;
311
312 LV_LOG_INFO("libinput: poll worker started");
313
314 while(true) {
315 rc = poll(dsc->fds, nfds, timeout);
316 switch(rc) {
317 case -1:
318 perror(NULL);
319 __attribute__((fallthrough));
320 case 0:
321 if(dsc->deinit) {
322 dsc->deinit = false; /* Signal that we're done */
323 return NULL;
324 }
325 continue;
326 default:
327 break;
328 }
329 libinput_dispatch(dsc->libinput_context);
330 pthread_mutex_lock(&dsc->event_lock);
331 while((event = libinput_get_event(dsc->libinput_context)) != NULL) {
332 _read_pointer(dsc, event);
333 _read_keypad(dsc, event);
334 libinput_event_destroy(event);
335 }
336 pthread_mutex_unlock(&dsc->event_lock);
337 LV_LOG_INFO("libinput: event read");
338 }
339
340 return NULL;
341 }
342
_get_event(lv_libinput_t * dsc)343 lv_libinput_event_t * _get_event(lv_libinput_t * dsc)
344 {
345 if(dsc->start == dsc->end) {
346 return NULL;
347 }
348
349 lv_libinput_event_t * evt = &dsc->points[dsc->start];
350
351 if(++dsc->start == LV_LIBINPUT_MAX_EVENTS)
352 dsc->start = 0;
353
354 return evt;
355 }
356
_event_pending(lv_libinput_t * dsc)357 bool _event_pending(lv_libinput_t * dsc)
358 {
359 return dsc->start != dsc->end;
360 }
361
_create_event(lv_libinput_t * dsc)362 lv_libinput_event_t * _create_event(lv_libinput_t * dsc)
363 {
364 lv_libinput_event_t * evt = &dsc->points[dsc->end];
365
366 if(++dsc->end == LV_LIBINPUT_MAX_EVENTS)
367 dsc->end = 0;
368
369 /* We have overflowed the buffer, start overwriting
370 * old events.
371 */
372 if(dsc->end == dsc->start) {
373 LV_LOG_INFO("libinput: overflowed event buffer!");
374 if(++dsc->start == LV_LIBINPUT_MAX_EVENTS)
375 dsc->start = 0;
376 }
377
378 memset(evt, 0, sizeof(lv_libinput_event_t));
379
380 return evt;
381 }
382
_read(lv_indev_t * indev,lv_indev_data_t * data)383 static void _read(lv_indev_t * indev, lv_indev_data_t * data)
384 {
385 lv_libinput_t * dsc = lv_indev_get_driver_data(indev);
386 LV_ASSERT_NULL(dsc);
387
388 pthread_mutex_lock(&dsc->event_lock);
389
390 lv_libinput_event_t * evt = _get_event(dsc);
391
392 if(!evt)
393 evt = &dsc->last_event; /* indev expects us to report the most recent state */
394
395 data->point = evt->point;
396 data->state = evt->pressed;
397 data->key = evt->key_val;
398 data->continue_reading = _event_pending(dsc);
399
400 dsc->last_event = *evt; /* Remember the last event for the next call */
401
402 pthread_mutex_unlock(&dsc->event_lock);
403
404 if(evt)
405 LV_LOG_TRACE("libinput_read: (%04d, %04d): %d continue_reading? %d", data->point.x, data->point.y, data->state,
406 data->continue_reading);
407 }
408
_read_pointer(lv_libinput_t * dsc,struct libinput_event * event)409 static void _read_pointer(lv_libinput_t * dsc, struct libinput_event * event)
410 {
411 struct libinput_event_touch * touch_event = NULL;
412 struct libinput_event_pointer * pointer_event = NULL;
413 lv_libinput_event_t * evt = NULL;
414 enum libinput_event_type type = libinput_event_get_type(event);
415 int slot = 0;
416
417 switch(type) {
418 case LIBINPUT_EVENT_TOUCH_MOTION:
419 case LIBINPUT_EVENT_TOUCH_DOWN:
420 case LIBINPUT_EVENT_TOUCH_UP:
421 touch_event = libinput_event_get_touch_event(event);
422 break;
423 case LIBINPUT_EVENT_POINTER_MOTION:
424 case LIBINPUT_EVENT_POINTER_BUTTON:
425 case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
426 pointer_event = libinput_event_get_pointer_event(event);
427 break;
428 default:
429 return; /* We don't care about this events */
430 }
431
432 /* We need to read unrotated display dimensions directly from the driver because libinput won't account
433 * for any rotation inside of LVGL */
434 lv_display_t * disp = lv_display_get_default();
435
436 /* ignore more than 2 fingers as it will only confuse LVGL */
437 if(touch_event && (slot = libinput_event_touch_get_slot(touch_event)) > 1)
438 return;
439
440 evt = _create_event(dsc);
441
442 const int32_t hor_res = disp->physical_hor_res > 0 ? disp->physical_hor_res : disp->hor_res;
443 const int32_t ver_res = disp->physical_ver_res > 0 ? disp->physical_ver_res : disp->ver_res;
444
445 switch(type) {
446 case LIBINPUT_EVENT_TOUCH_MOTION:
447 case LIBINPUT_EVENT_TOUCH_DOWN: {
448 lv_point_t point;
449 point.x = (int32_t)LV_CLAMP(INT32_MIN, libinput_event_touch_get_x_transformed(touch_event, hor_res) - disp->offset_x,
450 INT32_MAX);
451 point.y = (int32_t)LV_CLAMP(INT32_MIN, libinput_event_touch_get_y_transformed(touch_event, ver_res) - disp->offset_y,
452 INT32_MAX);
453 if(point.x < 0 || point.x > disp->hor_res || point.y < 0 || point.y > disp->ver_res) {
454 break; /* ignore touches that are out of bounds */
455 }
456 evt->point = point;
457 evt->pressed = LV_INDEV_STATE_PRESSED;
458 dsc->slots[slot].point = evt->point;
459 dsc->slots[slot].pressed = evt->pressed;
460 break;
461 }
462 case LIBINPUT_EVENT_TOUCH_UP:
463 /*
464 * We don't support "multitouch", but libinput does. To make fast typing with two thumbs
465 * on a keyboard feel good, it's necessary to handle two fingers individually. The edge
466 * case here is if you press a key with one finger and then press a second key with another
467 * finger. No matter which finger you release, it will count as the second finger releasing
468 * and ignore the first because LVGL only stores a single (the latest) pressed state.
469 *
470 * To work around this, we detect the case where one finger is released while the other is
471 * still pressed and insert dummy events so that both release events trigger at the correct
472 * position.
473 */
474 if(slot == 0 && dsc->slots[1].pressed == LV_INDEV_STATE_PRESSED) {
475 /* The first finger is released while the second finger is still pressed.
476 * We turn P1 > P2 > R1 > R2 into P1 > P2 > (P1) > R1 > (P2) > R2.
477 */
478
479 /* Inject the dummy press event for the first finger */
480 lv_libinput_event_t * synth_evt = evt;
481 synth_evt->pressed = LV_INDEV_STATE_PRESSED;
482 synth_evt->point = dsc->slots[0].point;
483
484 /* Append the real release event for the first finger */
485 evt = _create_event(dsc);
486 evt->pressed = LV_INDEV_STATE_RELEASED;
487 evt->point = dsc->slots[0].point;
488
489 /* Inject the dummy press event for the second finger */
490 synth_evt = _create_event(dsc);
491 synth_evt->pressed = LV_INDEV_STATE_PRESSED;
492 synth_evt->point = dsc->slots[1].point;
493 }
494 else if(slot == 1 && dsc->slots[0].pressed == LV_INDEV_STATE_PRESSED) {
495 /* The second finger is released while the first finger is still pressed.
496 * We turn P1 > P2 > R2 > R1 into P1 > P2 > R2 > (P1) > R1.
497 */
498
499 /* Append the real release event for the second finger */
500 evt->pressed = LV_INDEV_STATE_RELEASED;
501 evt->point = dsc->slots[1].point;
502
503 /* Inject the dummy press event for the first finger */
504 lv_libinput_event_t * synth_evt = _create_event(dsc);
505 synth_evt->pressed = LV_INDEV_STATE_PRESSED;
506 synth_evt->point = dsc->slots[0].point;
507 }
508 else {
509 evt->pressed = LV_INDEV_STATE_RELEASED;
510 evt->point = dsc->slots[slot].point;
511 }
512
513 dsc->slots[slot].pressed = evt->pressed;
514 break;
515 case LIBINPUT_EVENT_POINTER_MOTION:
516 dsc->pointer_position.x = (int32_t)LV_CLAMP(0, dsc->pointer_position.x + libinput_event_pointer_get_dx(pointer_event),
517 disp->hor_res - 1);
518 dsc->pointer_position.y = (int32_t)LV_CLAMP(0, dsc->pointer_position.y + libinput_event_pointer_get_dy(pointer_event),
519 disp->ver_res - 1);
520 evt->point.x = dsc->pointer_position.x;
521 evt->point.y = dsc->pointer_position.y;
522 evt->pressed = dsc->pointer_button_down;
523 break;
524 case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: {
525 lv_point_t point;
526 point.x = (int32_t)LV_CLAMP(INT32_MIN, libinput_event_pointer_get_absolute_x_transformed(pointer_event,
527 hor_res) - disp->offset_x, INT32_MAX);
528 point.y = (int32_t)LV_CLAMP(INT32_MIN, libinput_event_pointer_get_absolute_y_transformed(pointer_event,
529 ver_res) - disp->offset_y, INT32_MAX);
530 if(point.x < 0 || point.x > disp->hor_res || point.y < 0 || point.y > disp->ver_res) {
531 break; /* ignore pointer events that are out of bounds */
532 }
533 evt->point = point;
534 evt->pressed = dsc->pointer_button_down;
535 break;
536 }
537 case LIBINPUT_EVENT_POINTER_BUTTON: {
538 enum libinput_button_state button_state = libinput_event_pointer_get_button_state(pointer_event);
539 dsc->pointer_button_down = button_state == LIBINPUT_BUTTON_STATE_RELEASED ? LV_INDEV_STATE_RELEASED :
540 LV_INDEV_STATE_PRESSED;
541 evt->point.x = dsc->pointer_position.x;
542 evt->point.y = dsc->pointer_position.y;
543 evt->pressed = dsc->pointer_button_down;
544 }
545 default:
546 break;
547 }
548 }
549
_read_keypad(lv_libinput_t * dsc,struct libinput_event * event)550 static void _read_keypad(lv_libinput_t * dsc, struct libinput_event * event)
551 {
552 struct libinput_event_keyboard * keyboard_event = NULL;
553 enum libinput_event_type type = libinput_event_get_type(event);
554 lv_libinput_event_t * evt = NULL;
555 switch(type) {
556 case LIBINPUT_EVENT_KEYBOARD_KEY:
557 evt = _create_event(dsc);
558 keyboard_event = libinput_event_get_keyboard_event(event);
559 enum libinput_key_state key_state = libinput_event_keyboard_get_key_state(keyboard_event);
560 uint32_t code = libinput_event_keyboard_get_key(keyboard_event);
561 #if LV_LIBINPUT_XKB
562 evt->key_val = lv_xkb_process_key(&(dsc->xkb), code, key_state == LIBINPUT_KEY_STATE_PRESSED);
563 #else
564 switch(code) {
565 case KEY_BACKSPACE:
566 evt->key_val = LV_KEY_BACKSPACE;
567 break;
568 case KEY_ENTER:
569 evt->key_val = LV_KEY_ENTER;
570 break;
571 case KEY_PREVIOUS:
572 evt->key_val = LV_KEY_PREV;
573 break;
574 case KEY_NEXT:
575 evt->key_val = LV_KEY_NEXT;
576 break;
577 case KEY_UP:
578 evt->key_val = LV_KEY_UP;
579 break;
580 case KEY_LEFT:
581 evt->key_val = LV_KEY_LEFT;
582 break;
583 case KEY_RIGHT:
584 evt->key_val = LV_KEY_RIGHT;
585 break;
586 case KEY_DOWN:
587 evt->key_val = LV_KEY_DOWN;
588 break;
589 case KEY_TAB:
590 evt->key_val = LV_KEY_NEXT;
591 break;
592 case KEY_HOME:
593 evt->key_val = LV_KEY_HOME;
594 break;
595 case KEY_END:
596 evt->key_val = LV_KEY_END;
597 break;
598 case KEY_ESC:
599 evt->key_val = LV_KEY_ESC;
600 break;
601 default:
602 evt->key_val = 0;
603 break;
604 }
605 #endif /* LV_LIBINPUT_XKB */
606 if(evt->key_val != 0) {
607 /* Only record button state when actual output is produced to prevent widgets from refreshing */
608 evt->pressed = (key_state == LIBINPUT_KEY_STATE_RELEASED) ? LV_INDEV_STATE_RELEASED : LV_INDEV_STATE_PRESSED;
609
610 // just release the key immediately after it got pressed.
611 // but don't handle special keys where holding a key makes sense
612 if(evt->key_val != LV_KEY_BACKSPACE &&
613 evt->key_val != LV_KEY_UP &&
614 evt->key_val != LV_KEY_LEFT &&
615 evt->key_val != LV_KEY_RIGHT &&
616 evt->key_val != LV_KEY_DOWN &&
617 key_state == LIBINPUT_KEY_STATE_PRESSED) {
618 lv_libinput_event_t * release_evt = _create_event(dsc);
619 release_evt->pressed = LV_INDEV_STATE_RELEASED;
620 release_evt->key_val = evt->key_val;
621 }
622 }
623 break;
624 default:
625 break;
626 }
627 }
628
_open_restricted(const char * path,int flags,void * user_data)629 static int _open_restricted(const char * path, int flags, void * user_data)
630 {
631 LV_UNUSED(user_data);
632 int fd = open(path, flags);
633 return fd < 0 ? -errno : fd;
634 }
635
_close_restricted(int fd,void * user_data)636 static void _close_restricted(int fd, void * user_data)
637 {
638 LV_UNUSED(user_data);
639 close(fd);
640 }
641
_delete(lv_libinput_t * dsc)642 static void _delete(lv_libinput_t * dsc)
643 {
644 if(dsc->fd)
645 dsc->deinit = true;
646
647 /* Give worker thread a whole second to quit */
648 for(int i = 0; i < 100; i++) {
649 if(!dsc->deinit)
650 break;
651 usleep(10000);
652 }
653
654 if(dsc->deinit) {
655 LV_LOG_ERROR("libinput worker thread did not quit in time, cancelling it");
656 pthread_cancel(dsc->worker_thread);
657 }
658
659 if(dsc->libinput_device) {
660 libinput_path_remove_device(dsc->libinput_device);
661 libinput_device_unref(dsc->libinput_device);
662 }
663
664 if(dsc->libinput_context) {
665 libinput_unref(dsc->libinput_context);
666 }
667
668 #if LV_LIBINPUT_XKB
669 lv_xkb_deinit(&(dsc->xkb));
670 #endif /* LV_LIBINPUT_XKB */
671
672 lv_free(dsc);
673 }
674
675 #endif /* LV_USE_LIBINPUT */
676