// SPDX-License-Identifier: LGPL-2.1-only // Copyright (c) 2015 - 2024 DisplayLink (UK) Ltd. #include #include #include #ifndef __user # define __user #endif #include "evdi_drm.h" #include "evdi_lib.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define MIN(x, y) (((x) < (y)) ? (x) : (y)) // ********************* Private part ************************** #define MAX_DIRTS 16 #define EVDI_INVALID_DEVICE_INDEX -1 #define EVDI_MODULE_COMPATIBILITY_VERSION_MAJOR 1 #define EVDI_MODULE_COMPATIBILITY_VERSION_MINOR 9 #define EVDI_MODULE_COMPATIBILITY_VERSION_PATCH 0 #define evdi_log(...) do { \ if (g_evdi_logging.function) { \ g_evdi_logging.function(g_evdi_logging.user_data, \ __VA_ARGS__); \ } else { \ printf("[libevdi] " __VA_ARGS__); \ printf("\n"); \ } \ } while (0) struct evdi_logging g_evdi_logging = { .function = NULL, .user_data = NULL }; struct evdi_frame_buffer_node { struct evdi_buffer frame_buffer; struct evdi_frame_buffer_node *next; }; struct evdi_device_context { int fd; int bufferToUpdate; struct evdi_frame_buffer_node *frameBuffersListHead; int device_index; }; #define EVDI_USAGE_LEN 64 static evdi_handle card_usage[EVDI_USAGE_LEN]; static int drm_ioctl(int fd, unsigned long request, void *arg) { int ret; do { ret = ioctl(fd, request, arg); } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); return ret; } static int drm_auth_magic(int fd, drm_magic_t magic) { drm_auth_t auth; memset(&auth, 0, sizeof(auth)); auth.magic = magic; if (drm_ioctl(fd, DRM_IOCTL_AUTH_MAGIC, &auth)) return -errno; return 0; } static int drm_is_master(int fd) { /* Detect master by attempting something that requires master. * * Authenticating magic tokens requires master and 0 is an * internal kernel detail which we could use. Attempting this on * a master fd would fail therefore fail with EINVAL because 0 * is invalid. * * A non-master fd will fail with EACCES, as the kernel checks * for master before attempting to do anything else. * * Since we don't want to leak implementation details, use * EACCES. */ return drm_auth_magic(fd, 0) != -EACCES; } static int do_ioctl(int fd, unsigned long request, void *data, const char *msg) { const int err = drm_ioctl(fd, request, data); if (err < 0) evdi_log("Ioctl %s error: %s", msg, strerror(errno)); return err; } static struct evdi_frame_buffer_node *findBuffer(evdi_handle context, int id) { struct evdi_frame_buffer_node *node = NULL; assert(context); for (node = context->frameBuffersListHead; node != NULL; node = (struct evdi_frame_buffer_node *)node->next) { if (node->frame_buffer.id == id) return node; } return NULL; } static void addFrameBuffer(evdi_handle context, struct evdi_buffer const *frame_buffer) { for (struct evdi_frame_buffer_node **node = &context->frameBuffersListHead; ; node = &(*node)->next) { if (*node) continue; *node = calloc(1, sizeof(struct evdi_frame_buffer_node)); assert(node); memcpy(*node, frame_buffer, sizeof(struct evdi_buffer)); return; } } /* * @brief Removes all frame buffers matching the given id * @param id of frame buffer to remove, NULL matches every buffer, thus all * will be removed */ static void removeFrameBuffer(evdi_handle context, int const *id) { struct evdi_frame_buffer_node *current = NULL; struct evdi_frame_buffer_node *next = NULL; struct evdi_frame_buffer_node **prev = NULL; current = context->frameBuffersListHead; prev = &context->frameBuffersListHead; while (current) { next = current->next; if (!id || current->frame_buffer.id == *id) { free(current); *prev = next; } else { prev = ¤t->next; } current = next; } } static int is_evdi_compatible(int fd) { struct drm_version ver = { 0 }; evdi_log("LibEvdi version (%d.%d.%d)", LIBEVDI_VERSION_MAJOR, LIBEVDI_VERSION_MINOR, LIBEVDI_VERSION_PATCH); if (do_ioctl(fd, DRM_IOCTL_VERSION, &ver, "version") != 0) return 0; evdi_log("Evdi version (%d.%d.%d)", ver.version_major, ver.version_minor, ver.version_patchlevel); if (ver.version_major == EVDI_MODULE_COMPATIBILITY_VERSION_MAJOR && ver.version_minor >= EVDI_MODULE_COMPATIBILITY_VERSION_MINOR) return 1; evdi_log("Doesn't match LibEvdi compatibility one (%d.%d.%d)", EVDI_MODULE_COMPATIBILITY_VERSION_MAJOR, EVDI_MODULE_COMPATIBILITY_VERSION_MINOR, EVDI_MODULE_COMPATIBILITY_VERSION_PATCH); return 0; } static int is_evdi(int fd) { char name[64] = { 0 }; char date[64] = { 0 }; char desc[64] = { 0 }; struct drm_version ver = { .name_len = sizeof(name), .name = name, .date_len = sizeof(date), .date = date, .desc_len = sizeof(desc), .desc = desc, }; if (do_ioctl(fd, DRM_IOCTL_VERSION, &ver, "version") == 0 && strcmp(name, "evdi") == 0) { return 1; } return 0; } static int path_exists(const char *path) { struct stat buf; return stat(path, &buf) == 0; } static int device_exists(int device) { char dev[PATH_MAX] = ""; snprintf(dev, PATH_MAX, "/dev/dri/card%d", device); return path_exists(dev); } static int does_path_links_to(const char *link, const char *substr) { char real_path[PATH_MAX]; ssize_t r; r = readlink(link, real_path, sizeof(real_path)); if (r < 0) return 0; real_path[r] = '\0'; return (strstr(real_path, substr) != NULL); } static int process_opened_device(const char *pid, const char *device_file_path) { char maps_path[PATH_MAX]; FILE *maps = NULL; char line[BUFSIZ]; int result = 0; snprintf(maps_path, PATH_MAX, "/proc/%s/maps", pid); maps = fopen(maps_path, "r"); if (maps == NULL) return 0; while (fgets(line, BUFSIZ, maps)) { if (strstr(line, device_file_path)) { result = 1; break; } } fclose(maps); return result; } static int process_opened_files(const char *pid, const char *device_file_path) { char fd_path[PATH_MAX]; DIR *fd_dir; struct dirent *fd_entry; int result = 0; snprintf(fd_path, PATH_MAX, "/proc/%s/fd", pid); fd_dir = opendir(fd_path); if (fd_dir == NULL) return 0; while ((fd_entry = readdir(fd_dir)) != NULL) { char *d_name = fd_entry->d_name; char path[PATH_MAX]; snprintf(path, PATH_MAX, "/proc/%s/fd/%s", pid, d_name); if (does_path_links_to(path, device_file_path)) { result = 1; break; } } closedir(fd_dir); return result; } static int device_has_master(const char *device_file_path) { pid_t myself = getpid(); DIR *proc_dir; const struct dirent *proc_entry; int result = 0; proc_dir = opendir("/proc"); if (proc_dir == NULL) return 0; while ((proc_entry = readdir(proc_dir)) != NULL) { const char *d_name = proc_entry->d_name; if (d_name[0] < '0' || d_name[0] > '9' || myself == atoi(d_name)) { continue; } if (process_opened_files(d_name, device_file_path)) { result = 1; break; } if (process_opened_device(d_name, device_file_path)) { result = 1; break; } } closedir(proc_dir); return result; } static void wait_for_master(const char *device_path) { const unsigned int TOTAL_WAIT_US = 5000000L; const unsigned int SLEEP_INTERVAL_US = 100000L; unsigned int cnt = TOTAL_WAIT_US / SLEEP_INTERVAL_US; int has_master = 0; while ((has_master = device_has_master(device_path)) == 0 && cnt--) usleep(SLEEP_INTERVAL_US); if (!has_master) evdi_log("Wait for master timed out"); } static int open_as_slave(const char *device_path) { int fd = 0; int err = 0; fd = open(device_path, O_RDWR); if (fd < 0) return -1; if (drm_is_master(fd)) { evdi_log("Process has master on %s, err: %s", device_path, strerror(errno)); err = drm_ioctl(fd, DRM_IOCTL_DROP_MASTER, NULL); } if (err < 0) { evdi_log("Drop master on %s failed, err: %s", device_path, strerror(errno)); close(fd); return err; } if (drm_is_master(fd)) { evdi_log("Drop master on %s failed, err: %s", device_path, strerror(errno)); close(fd); return -1; } evdi_log("Opened %s as slave drm device", device_path); return fd; } static int wait_for_device(const char *device_path) { const unsigned int TOTAL_WAIT_US = 5000000L; const unsigned int SLEEP_INTERVAL_US = 100000L; unsigned int cnt = TOTAL_WAIT_US / SLEEP_INTERVAL_US; int fd = 0; while ((fd = open_as_slave(device_path)) < 0 && cnt--) usleep(SLEEP_INTERVAL_US); if (fd < 0) evdi_log("Failed to open a device: %s", strerror(errno)); return fd; } static int open_device(int device) { char dev[PATH_MAX] = ""; int fd = 0; snprintf(dev, PATH_MAX, "/dev/dri/card%d", device); if (Xorg_running()) wait_for_master(dev); fd = wait_for_device(dev); if (fd >= 0) { const int err = drm_ioctl(fd, DRM_IOCTL_DROP_MASTER, NULL); if (err == 0) evdi_log("Dropped master on %s", dev); } return fd; } static int write_add_device(const char *buffer, size_t buffer_length) { FILE *add_devices = fopen("/sys/devices/evdi/add", "w"); int written = 0; if (add_devices != NULL) { written = fwrite(buffer, 1, buffer_length, add_devices); fclose(add_devices); } return written; } static int get_drm_device_index(const char *evdi_sysfs_drm_dir) { struct dirent *fd_entry; DIR *fd_dir; int dev_index = EVDI_INVALID_DEVICE_INDEX; fd_dir = opendir(evdi_sysfs_drm_dir); if (fd_dir == NULL) { evdi_log("Failed to open dir %s", evdi_sysfs_drm_dir); return dev_index; } while ((fd_entry = readdir(fd_dir)) != NULL) { if (strncmp(fd_entry->d_name, "card", 4) == 0) dev_index = strtol(&fd_entry->d_name[4], NULL, 10); } closedir(fd_dir); return dev_index; } static bool is_correct_parent_device(const char *dirname, size_t dirname_maxlen, const char *parent_device, size_t parent_length) { char link_path[PATH_MAX]; snprintf(link_path, MIN(PATH_MAX - 7, dirname_maxlen), "%s/device", dirname); if (parent_device == NULL) return access(link_path, F_OK) != 0; char link_resolution[PATH_MAX]; const ssize_t link_resolution_len = readlink(link_path, link_resolution, PATH_MAX); if (link_resolution_len == -1 || link_resolution_len == PATH_MAX) return false; link_resolution[link_resolution_len] = '\0'; char *parent_device_token = strrchr(link_resolution, '/'); if (parent_length < 2) return false; parent_device_token++; size_t len = strnlen(parent_device_token, parent_length - (parent_device_token - parent_device)); bool is_same_device = parent_length == len && strncmp(parent_device_token, parent_device, len) == 0; return is_same_device; } static int find_unused_card_for(const char *parent_device, size_t length) { char evdi_platform_root[] = "/sys/bus/platform/devices"; struct dirent *fd_entry; DIR *fd_dir; int device_index = EVDI_INVALID_DEVICE_INDEX; fd_dir = opendir(evdi_platform_root); if (fd_dir == NULL) { evdi_log("Failed to open dir %s", evdi_platform_root); return device_index; } while ((fd_entry = readdir(fd_dir)) != NULL) { if (strncmp(fd_entry->d_name, "evdi", 4) != 0) continue; char evdi_path[PATH_MAX]; snprintf(evdi_path, PATH_MAX, "%s/%s", evdi_platform_root, fd_entry->d_name); if (!is_correct_parent_device(evdi_path, PATH_MAX, parent_device, length)) continue; char evdi_drm_path[PATH_MAX]; snprintf(evdi_drm_path, PATH_MAX - strnlen(evdi_path, PATH_MAX), "%s/drm", evdi_path); int dev_index = get_drm_device_index(evdi_drm_path); assert(dev_index < EVDI_USAGE_LEN && dev_index >= 0); if (card_usage[dev_index] == EVDI_INVALID_HANDLE) { device_index = dev_index; break; } } closedir(fd_dir); return device_index; } static int get_generic_device(void) { int device_index = EVDI_INVALID_DEVICE_INDEX; device_index = find_unused_card_for(NULL, 0); if (device_index == EVDI_INVALID_DEVICE_INDEX) { evdi_log("Creating card in /sys/devices/platform"); write_add_device("1", 1); device_index = find_unused_card_for(NULL, 0); } return device_index; } static int get_device_attached_to_usb(const char *sysfs_parent_device, size_t length) { const size_t USB_OFFSET = 4; int device_index = EVDI_INVALID_DEVICE_INDEX; if (length < USB_OFFSET) return EVDI_INVALID_DEVICE_INDEX; const char *parent_device = &sysfs_parent_device[USB_OFFSET]; const size_t parent_length = length - USB_OFFSET; device_index = find_unused_card_for(parent_device, parent_length); if (device_index == EVDI_INVALID_DEVICE_INDEX) { evdi_log("Creating card for usb device %s in /sys/bus/platform/devices", parent_device); write_add_device(sysfs_parent_device, length); device_index = find_unused_card_for(parent_device, parent_length); } return device_index; } // ********************* Public part ************************** evdi_handle evdi_open(int device) { int fd; evdi_handle h = EVDI_INVALID_HANDLE; fd = open_device(device); if (fd > 0) { if (is_evdi(fd) && is_evdi_compatible(fd)) { h = calloc(1, sizeof(struct evdi_device_context)); if (h) { h->fd = fd; h->device_index = device; card_usage[device] = h; evdi_log("Using /dev/dri/card%d", device); } } if (h == EVDI_INVALID_HANDLE) close(fd); } return h; } static enum evdi_device_status evdi_device_to_platform(int device, char *path) { struct dirent *fd_entry; DIR *fd_dir; enum evdi_device_status status = UNRECOGNIZED; char card_path[PATH_MAX]; if (!device_exists(device)) return NOT_PRESENT; fd_dir = opendir("/sys/bus/platform/devices"); if (fd_dir == NULL) { evdi_log("Failed to list platform devices"); return NOT_PRESENT; } while ((fd_entry = readdir(fd_dir)) != NULL) { if (strncmp(fd_entry->d_name, "evdi", 4) != 0) continue; snprintf(path, PATH_MAX, "/sys/bus/platform/devices/%s", fd_entry->d_name); snprintf(card_path, PATH_MAX, "%s/drm/card%d", path, device); if (path_exists(card_path)) { status = AVAILABLE; break; } } closedir(fd_dir); return status; } enum evdi_device_status evdi_check_device(int device) { char path[PATH_MAX]; return evdi_device_to_platform(device, path); } int evdi_add_device(void) { return write_add_device("1", 1); } // deprecated, use evdi_open_attached_to_fixed evdi_handle evdi_open_attached_to(const char *sysfs_parent_device) { return evdi_open_attached_to_fixed(sysfs_parent_device, strlen(sysfs_parent_device)); } evdi_handle evdi_open_attached_to_fixed(const char *sysfs_parent_device, size_t length) { int device_index = EVDI_INVALID_DEVICE_INDEX; if (sysfs_parent_device == NULL) device_index = get_generic_device(); if (sysfs_parent_device != NULL && length > 4 && strncmp(sysfs_parent_device, "usb:", 4) == 0) device_index = get_device_attached_to_usb(sysfs_parent_device, length); if (device_index >= 0 && device_index < EVDI_USAGE_LEN) { evdi_handle handle = evdi_open(device_index); return handle; } return EVDI_INVALID_HANDLE; } void evdi_close(evdi_handle handle) { if (handle != EVDI_INVALID_HANDLE) { close(handle->fd); free(handle); for (int device_index = 0; device_index < EVDI_USAGE_LEN; device_index++) { if (card_usage[device_index] == handle) { card_usage[device_index] = EVDI_INVALID_HANDLE; evdi_log("Marking /dev/dri/card%d as unused", device_index); } } } } void evdi_connect(evdi_handle handle, const unsigned char *edid, const unsigned int edid_length, const uint32_t sku_area_limit) { evdi_connect2(handle, edid, edid_length, sku_area_limit, sku_area_limit*60); } void evdi_connect2(evdi_handle handle, const unsigned char *edid, const unsigned int edid_length, const uint32_t pixel_area_limit, const uint32_t pixel_per_second_limit) { struct drm_evdi_connect cmd = { .connected = 1, .dev_index = handle->device_index, .edid = edid, .edid_length = edid_length, .pixel_area_limit = pixel_area_limit, .pixel_per_second_limit = pixel_per_second_limit, }; do_ioctl(handle->fd, DRM_IOCTL_EVDI_CONNECT, &cmd, "connect"); } void evdi_disconnect(evdi_handle handle) { struct drm_evdi_connect cmd = { 0, 0, 0, 0, 0, 0 }; do_ioctl(handle->fd, DRM_IOCTL_EVDI_CONNECT, &cmd, "disconnect"); } void evdi_enable_cursor_events(evdi_handle handle, bool enable) { struct drm_evdi_enable_cursor_events cmd = { .enable = enable, }; evdi_log("%s cursor events on /dev/dri/card%d", (enable ? "Enabling" : "Disabling"), handle->device_index); do_ioctl(handle->fd, DRM_IOCTL_EVDI_ENABLE_CURSOR_EVENTS, &cmd, "enable cursor events"); } void evdi_grab_pixels(evdi_handle handle, struct evdi_rect *rects, int *num_rects) { struct drm_clip_rect kernelDirts[MAX_DIRTS] = { { 0, 0, 0, 0 } }; struct evdi_frame_buffer_node *destinationNode = NULL; struct evdi_buffer *destinationBuffer = NULL; destinationNode = findBuffer(handle, handle->bufferToUpdate); if (!destinationNode) { evdi_log("Buffer %d not found. Not grabbing.", handle->bufferToUpdate); *num_rects = 0; return; } destinationBuffer = &destinationNode->frame_buffer; struct drm_evdi_grabpix grab = { EVDI_GRABPIX_MODE_DIRTY, destinationBuffer->width, destinationBuffer->height, destinationBuffer->stride, destinationBuffer->buffer, MAX_DIRTS, kernelDirts }; if (do_ioctl( handle->fd, DRM_IOCTL_EVDI_GRABPIX, &grab, "grabpix") == 0) { /* * Buffer was filled by ioctl * now we only have to fill the dirty rects */ int r = 0; for (; r < grab.num_rects; ++r) { rects[r].x1 = kernelDirts[r].x1; rects[r].y1 = kernelDirts[r].y1; rects[r].x2 = kernelDirts[r].x2; rects[r].y2 = kernelDirts[r].y2; } *num_rects = grab.num_rects; } else { int id = destinationBuffer->id; evdi_log("Grabbing pixels for buffer %d failed.", id); evdi_log("Ignore if caused by change of mode."); *num_rects = 0; } } void evdi_register_buffer(evdi_handle handle, struct evdi_buffer buffer) { assert(handle); assert(!findBuffer(handle, buffer.id)); addFrameBuffer(handle, &buffer); } void evdi_unregister_buffer(evdi_handle handle, int bufferId) { struct evdi_buffer *bufferToRemove = NULL; assert(handle); bufferToRemove = &findBuffer(handle, bufferId)->frame_buffer; assert(bufferToRemove); removeFrameBuffer(handle, &bufferId); } bool evdi_request_update(evdi_handle handle, int bufferId) { assert(handle); handle->bufferToUpdate = bufferId; { struct drm_evdi_request_update cmd; const int requestResult = do_ioctl( handle->fd, DRM_IOCTL_EVDI_REQUEST_UPDATE, &cmd, "request_update"); const bool grabImmediately = requestResult == 1; return grabImmediately; } } void evdi_ddcci_response(evdi_handle handle, const unsigned char *buffer, const uint32_t buffer_length, const bool result) { struct drm_evdi_ddcci_response cmd = { .buffer = buffer, .buffer_length = buffer_length, .result = result, }; do_ioctl(handle->fd, DRM_IOCTL_EVDI_DDCCI_RESPONSE, &cmd, "ddcci_response"); } static struct evdi_mode to_evdi_mode(struct drm_evdi_event_mode_changed *event) { struct evdi_mode mode; mode.width = event->hdisplay; mode.height = event->vdisplay; mode.refresh_rate = event->vrefresh; mode.bits_per_pixel = event->bits_per_pixel; mode.pixel_format = event->pixel_format; return mode; } static int evdi_get_dumb_offset(evdi_handle ehandle, uint32_t handle, uint64_t *offset) { struct drm_mode_map_dumb map_dumb = { 0 }; int ret = 0; map_dumb.handle = handle; ret = do_ioctl(ehandle->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb, "DRM_MODE_MAP_DUMB"); *offset = map_dumb.offset; return ret; } static struct evdi_cursor_set to_evdi_cursor_set( evdi_handle handle, struct drm_evdi_event_cursor_set *event) { struct evdi_cursor_set cursor_set; cursor_set.hot_x = event->hot_x; cursor_set.hot_y = event->hot_y; cursor_set.width = event->width; cursor_set.height = event->height; cursor_set.enabled = event->enabled; cursor_set.buffer_length = 0; cursor_set.buffer = NULL; cursor_set.pixel_format = event->pixel_format; cursor_set.stride = event->stride; if (event->enabled) { size_t size = event->buffer_length; uint64_t offset = 0; if (evdi_get_dumb_offset(handle, event->buffer_handle, &offset)) { evdi_log("Error: DRM_IOCTL_MODE_MAP_DUMB failed with error: %s", strerror(errno)); return cursor_set; } void *ptr = mmap(0, size, PROT_READ, MAP_SHARED, handle->fd, offset); if (ptr != MAP_FAILED) { cursor_set.buffer = malloc(size); memcpy(cursor_set.buffer, ptr, size); munmap(ptr, size); cursor_set.buffer_length = size; } else { evdi_log("Error: mmap failed with error: %s", strerror(errno)); } } return cursor_set; } static struct evdi_cursor_move to_evdi_cursor_move( struct drm_evdi_event_cursor_move *event) { struct evdi_cursor_move cursor_move; cursor_move.x = event->x; cursor_move.y = event->y; return cursor_move; } static struct evdi_ddcci_data to_evdi_ddcci_data( struct drm_evdi_event_ddcci_data *event) { struct evdi_ddcci_data ddcci_data; ddcci_data.address = event->address; ddcci_data.flags = event->flags; ddcci_data.buffer = &event->buffer[0]; ddcci_data.buffer_length = event->buffer_length; return ddcci_data; } static void evdi_handle_event(evdi_handle handle, struct evdi_event_context *evtctx, struct drm_event *e) { switch (e->type) { case DRM_EVDI_EVENT_UPDATE_READY: if (evtctx->update_ready_handler) evtctx->update_ready_handler(handle->bufferToUpdate, evtctx->user_data); break; case DRM_EVDI_EVENT_DPMS: if (evtctx->dpms_handler) { struct drm_evdi_event_dpms *event = (struct drm_evdi_event_dpms *) e; evtctx->dpms_handler(event->mode, evtctx->user_data); } break; case DRM_EVDI_EVENT_MODE_CHANGED: if (evtctx->mode_changed_handler) { struct drm_evdi_event_mode_changed *event = (struct drm_evdi_event_mode_changed *) e; evtctx->mode_changed_handler(to_evdi_mode(event), evtctx->user_data); } break; case DRM_EVDI_EVENT_CRTC_STATE: if (evtctx->crtc_state_handler) { struct drm_evdi_event_crtc_state *event = (struct drm_evdi_event_crtc_state *) e; evtctx->crtc_state_handler(event->state, evtctx->user_data); } break; case DRM_EVDI_EVENT_CURSOR_SET: if (evtctx->cursor_set_handler) { struct drm_evdi_event_cursor_set *event = (struct drm_evdi_event_cursor_set *) e; struct evdi_cursor_set cursor_set = to_evdi_cursor_set(handle, event); if (cursor_set.enabled && cursor_set.buffer == NULL) { evdi_log("Error: Cursor buffer is null!"); evdi_log("Disabling cursor events"); evdi_enable_cursor_events(handle, false); cursor_set.enabled = false; cursor_set.buffer_length = 0; } evtctx->cursor_set_handler(cursor_set, evtctx->user_data); } break; case DRM_EVDI_EVENT_CURSOR_MOVE: if (evtctx->cursor_move_handler) { struct drm_evdi_event_cursor_move *event = (struct drm_evdi_event_cursor_move *) e; evtctx->cursor_move_handler(to_evdi_cursor_move(event), evtctx->user_data); } break; case DRM_EVDI_EVENT_DDCCI_DATA: if (evtctx->ddcci_data_handler) { struct drm_evdi_event_ddcci_data *event = (struct drm_evdi_event_ddcci_data *) e; evtctx->ddcci_data_handler(to_evdi_ddcci_data(event), evtctx->user_data); } break; default: evdi_log("Warning: Unhandled event"); } } void evdi_handle_events(evdi_handle handle, struct evdi_event_context *evtctx) { char buffer[1024]; int i = 0; int bytesRead = read(handle->fd, buffer, sizeof(buffer)); if (!evtctx) { evdi_log("Error: Event context is null!"); return; } while (i < bytesRead) { struct drm_event *e = (struct drm_event *) &buffer[i]; evdi_handle_event(handle, evtctx, e); i += e->length; } } evdi_selectable evdi_get_event_ready(evdi_handle handle) { return handle->fd; } void evdi_get_lib_version(struct evdi_lib_version *version) { if (version != NULL) { version->version_major = LIBEVDI_VERSION_MAJOR; version->version_minor = LIBEVDI_VERSION_MINOR; version->version_patchlevel = LIBEVDI_VERSION_PATCH; } } void evdi_set_logging(struct evdi_logging evdi_logging) { g_evdi_logging = evdi_logging; }