EVDI (Extensible Virtual Display Interface)

repository·main·Indexed 21 days ago

https://github.com/displaylink/evdi

A Linux kernel module and user-space library suite for managing virtual displays. EVDI integrates with the Linux DRM subsystem, allowing software to create virtual screens manageable by tools like xrandr. The project includes the evdi kernel module, libevdi (C wrapper), and pyevdi (Python wrapper). It is commonly used with DisplayLink drivers and supports features such as frame buffer management, EDID handling, and DPMS notifications.

Tokens
6.4K
Snippets
20
Records
37
Agent score
76%

What's inside evdi

  1. Overview of Extensible Virtual Display Interface (EVDI)

    main

    EVDI is a Linux kernel module that enables the management of multiple virtual screens. It allows user-space programs to control image output by providing a virtual display that can be added, removed, and updated.

    The project consists of three main components:

    1. evdi kernel module: The core driver.
    2. libevdi library: A C wrapper used by applications (like DisplayLink's user mode driver) to communicate with the kernel module.
    3. pyevdi library: A Python wrapper for libevdi.

    Because EVDI is compatible with the standard Linux DRM (Direct Rendering Manager) subsystem, virtual displays created via EVDI can be managed by standard Linux tools like xrandr or desktop environment display settings (e.g., GNOME, KDE, Unity).

  2. Overview of the EVDI library

    main
    Extensible Virtual Display Interface (EVDI) is a software library that enables userspace Linux programs to manage additional displays and receive updates for them. It provides a generic interface for creating and controlling virtual displays, originally developed as the foundation for DisplayLink's Ubuntu Linux drivers for USB 3.0 docking stations and adapters.
  3. The EVDI asynchronous event loop

    main

    EVDI uses an asynchronous model. Your application must implement an event loop to handle notifications from the kernel.

    1. Get Event FD: Use evdi_get_event_ready to obtain the file descriptor to monitor.
    2. Dispatch Events: When the FD is ready, call evdi_handle_events. This function uses an evdi_event_context structure containing your application-defined handlers to dispatch events.

    Supported Event Types:

    • update_ready: Sent when a requested buffer update is handled by the kernel.
    • mode_changed: Sent when the screen mode changes (via DRM).
    • DPMS: Notifications regarding the connector's power state.
    • CRTC state change: Exposes DRM CRTC state changes.
    • Cursor events: Notifications for cursor_set or cursor_move (if enabled).
    • DDC/CI notification: Sent when an i2c request for DDC/CI data is made.
  4. Handle EVDI events and notifications

    main

    EVDI uses callback handlers to notify the application of various state changes. You can register these handlers to respond to hardware or subsystem events.

    Supported Handlers

    • update_ready_handler: Sent when a previously requested buffer update is ready to be consumed. Provides the buffer_to_be_updated ID.
    • dpms_handler: Sent when Display Power Management Signaling (DPMS) mode changes. Modes are bit-compatible with DRM:
      • DRM_MODE_DPMS_ON (0)
      • DRM_MODE_DPMS_STANDBY (1)
      • DRM_MODE_DPMS_SUSPEND (2)
      • DRM_MODE_DPMS_OFF (3)
    • mode_changed_handler: Sent when the display mode changes. Provides an evdi_mode structure.
    • cursor_set_handler: Sent when the cursor buffer/shape changes or when the cursor is enabled/disabled.
    • cursor_move_handler: Sent when the cursor position changes on the virtual screen.
    • ddcci_data_handler: Sent when an I2C request is made to the DDC/CI address (0x37). The module waits up to 50ms (DDCCI_TIMEOUT_MS) for a response via evdi_ddcci_response.
  5. Typical application workflow for EVDI

    main

    A standard EVDI client application follows these lifecycle steps:

    1. Node Management: Find a free EVDI node or add a new one, then open it to obtain an evdi_handle.
    2. Connection: Call evdi_connect to notify the DRM subsystem that a monitor is connected (similar to plugging in a cable). This also provides the monitor's EDID.
    3. Buffer Setup: Allocate memory for screen updates within your application and register it using evdi_register_buffer.
    4. Event Loop: Monitor the file descriptor from evdi_get_event_ready. When ready, call evdi_handle_events to dispatch notifications (like update_ready).
    5. Update Cycle: Promptly request updates and grab pixels in a loop. Failure to handle these regularly may cause the device to become unresponsive.
  6. Monitor EVDI events using evdi_selectable

    main

    The evdi_selectable type is a file descriptor used to watch for events signaled from the kernel module.

    To use it:

    1. Obtain the descriptor for an opened EVDI device using evdi_get_event_ready.
    2. Watch this descriptor for readiness (e.g., using poll or select).
    3. When the descriptor becomes ready to read, call evdi_handle_events to dispatch notifications to your registered handlers.
  7. Handling DDC/CI (I2C) notifications

    main

    EVDI creates an i2c adapter for each node to support DDC/CI (adjusting brightness/contrast).

    • Receiving Data: Requests for DDC/CI data (at address 0x37) are passed to userspace via the ddcci_data_handler.
    • Sending Responses: Use evdi_ddcci_response to send data back to the monitor.
  8. Handling the cursor

    main

    EVDI provides two modes for cursor management:

    1. Automatic cursor compositing (Default): Every cursor change triggers an update_ready event. EVDI automatically composes the cursor onto the user-supplied framebuffer during the grab pixels operation.
    2. Cursor change notifications: Enabled via evdi_enable_cursor_events. In this mode, the client is responsible for cursor blending. Instead of update_ready, the library sends cursor_set and cursor_move notifications.
  9. API Stability Warning for EVDI

    main
    The EVDI library interface is currently considered unstable. Developers using the library should be aware that the API is subject to change, and client applications may require updates to maintain compatibility with newer versions of the library.
  10. Obtain an EDID file from a Linux DRM device

    main

    To obtain an EDID (Extended Display Identification Data) file from an existing display on a Linux system, you can copy the EDID file directly from the sysfs DRM interface. Replace card0-eDP-1 with the appropriate path for your specific DRM device and connector.

    cp /sys/class/drm/card0-eDP-1/edid edid.bin
  11. Manage and update frame buffers

    main

    Client applications are responsible for managing the memory for frame buffers. The evdi_buffer structure is used to communicate buffer details to the library.

    Registering and Unregistering

    • evdi_register_buffer(evdi_handle handle, evdi_buffer buffer): Registers a buffer with the device. Warning: This does not allocate memory.
    • evdi_unregister_buffer(evdi_handle handle, int bufferId): Unregisters a buffer. Warning: This does not deallocate memory.

    Requesting updates and grabbing pixels

    To display new content, follow this workflow:

    1. Request an update: Call bool evdi_request_update(evdi_handle handle, int bufferId).
      • If it returns true, the data is ready to be grabbed immediately.
      • If it returns false, you must wait for the update_ready_handler notification.
    2. Grab pixels: Call void evdi_grab_pixels(evdi_handle handle, evdi_rect *rects, int *num_rects).
      • rects: An array of evdi_rect (the library fills this with dirty rectangles). The current implementation assumes an array of up to 16 slots.
      • num_rects: An integer pointer that will be updated with the number of valid dirty rectangles. A value of 0 indicates a failed grab (e.g., due to a mode change).
    // Workflow for updating a buffer
    if (evdi_request_update(handle, my_buffer_id)) {
        evdi_rect rects[16];
        int num_rects;
        evdi_grab_pixels(handle, rects, &num_rects);
        for (int i = 0; i < num_rects; i++) {
            // Process rects[i]
        }
    } else {
        // Wait for update_ready_handler
    }