goevdi/libevdi/evdi_lib.c

1038 lines
24 KiB
C

// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (c) 2015 - 2024 DisplayLink (UK) Ltd.
#include <stddef.h>
#include <stdint.h>
#include <libdrm/drm.h>
#ifndef __user
# define __user
#endif
#include "evdi_drm.h"
#include "evdi_lib.h"
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#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 = &current->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;
}