You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-plymouth/src/plugins/renderers/drm/plugin.c

1878 lines
64 KiB

/* plugin.c - drm backend renderer plugin
*
* Copyright (C) 2006-2019 Red Hat, Inc.
* 2008 Charlie Brej <cbrej@cs.man.ac.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Written by: Charlie Brej <cbrej@cs.man.ac.uk>
* Kristian Høgsberg <krh@redhat.com>
* Peter Jones <pjones@redhat.com>
* Ray Strode <rstrode@redhat.com>
* Hans de Goede <hdegoede@redhat.com>
*/
#include "config.h"
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <values.h>
#include <unistd.h>
#include <drm.h>
#include <drm_mode.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "ply-array.h"
#include "ply-buffer.h"
#include "ply-event-loop.h"
#include "ply-list.h"
#include "ply-logger.h"
#include "ply-hashtable.h"
#include "ply-rectangle.h"
#include "ply-region.h"
#include "ply-utils.h"
#include "ply-terminal.h"
#include "ply-renderer.h"
#include "ply-renderer-plugin.h"
#define BYTES_PER_PIXEL (4)
/* For builds with libdrm < 2.4.89 */
#ifndef DRM_MODE_ROTATE_0
#define DRM_MODE_ROTATE_0 (1<<0)
#endif
struct _ply_renderer_head
{
ply_renderer_backend_t *backend;
ply_pixel_buffer_t *pixel_buffer;
ply_rectangle_t area;
unsigned long row_stride;
ply_array_t *connector_ids;
drmModeModeInfo connector0_mode;
uint32_t controller_id;
uint32_t console_buffer_id;
uint32_t scan_out_buffer_id;
bool scan_out_buffer_needs_reset;
bool uses_hw_rotation;
int gamma_size;
uint16_t *gamma;
};
struct _ply_renderer_input_source
{
ply_renderer_backend_t *backend;
ply_fd_watch_t *terminal_input_watch;
ply_buffer_t *key_buffer;
ply_renderer_input_source_handler_t handler;
void *user_data;
};
typedef struct
{
uint32_t id;
uint32_t handle;
uint32_t width;
uint32_t height;
uint32_t row_stride;
void *map_address;
uint32_t map_size;
int map_count;
uint32_t added_fb : 1;
} ply_renderer_buffer_t;
typedef struct
{
drmModeModeInfo mode;
uint32_t connector_id;
uint32_t connector_type;
uint32_t controller_id;
uint32_t possible_controllers;
int device_scale;
int link_status;
ply_pixel_buffer_rotation_t rotation;
bool tiled;
bool connected;
bool uses_hw_rotation;
} ply_output_t;
struct _ply_renderer_backend
{
ply_event_loop_t *loop;
ply_terminal_t *terminal;
int device_fd;
char *device_name;
drmModeRes *resources;
ply_renderer_input_source_t input_source;
ply_list_t *heads;
ply_hashtable_t *heads_by_controller_id;
ply_hashtable_t *output_buffers;
ply_output_t *outputs;
int outputs_len;
int connected_count;
int32_t dither_red;
int32_t dither_green;
int32_t dither_blue;
uint32_t is_active : 1;
uint32_t requires_explicit_flushing : 1;
uint32_t use_preferred_mode : 1;
int panel_width;
int panel_height;
ply_pixel_buffer_rotation_t panel_rotation;
int panel_scale;
};
ply_renderer_plugin_interface_t *ply_renderer_backend_get_interface (void);
static bool open_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source);
static void flush_head (ply_renderer_backend_t *backend,
ply_renderer_head_t *head);
/* A small helper to determine if we should try to keep the current mode
* or pick the best mode ourselves, we keep the current mode only if the
* user specified a specific mode using video= on the commandline.
*/
static bool
should_use_preferred_mode (void)
{
bool use_preferred_mode = true;
if (ply_kernel_command_line_get_string_after_prefix ("video="))
use_preferred_mode = false;
ply_trace ("should_use_preferred_mode: %d", use_preferred_mode);
return use_preferred_mode;
}
static bool
ply_renderer_buffer_map (ply_renderer_backend_t *backend,
ply_renderer_buffer_t *buffer)
{
struct drm_mode_map_dumb map_dumb_buffer_request;
void *map_address;
if (buffer->map_address != MAP_FAILED) {
buffer->map_count++;
return true;
}
memset (&map_dumb_buffer_request, 0, sizeof(struct drm_mode_map_dumb));
map_dumb_buffer_request.handle = buffer->handle;
if (drmIoctl (backend->device_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb_buffer_request) < 0) {
ply_trace ("Could not map GEM object %u: %m", buffer->handle);
return false;
}
map_address = mmap (0, buffer->map_size,
PROT_READ | PROT_WRITE, MAP_SHARED,
backend->device_fd, map_dumb_buffer_request.offset);
if (map_address == MAP_FAILED)
return false;
buffer->map_address = map_address;
buffer->map_count++;
return true;
}
static void
ply_renderer_buffer_unmap (ply_renderer_backend_t *backend,
ply_renderer_buffer_t *buffer)
{
buffer->map_count--;
assert (buffer->map_count >= 0);
}
static ply_renderer_buffer_t *
ply_renderer_buffer_new (ply_renderer_backend_t *backend,
uint32_t width,
uint32_t height)
{
ply_renderer_buffer_t *buffer;
struct drm_mode_create_dumb create_dumb_buffer_request;
buffer = calloc (1, sizeof(ply_renderer_buffer_t));
buffer->width = width;
buffer->height = height;
buffer->map_address = MAP_FAILED;
memset (&create_dumb_buffer_request, 0, sizeof(struct drm_mode_create_dumb));
create_dumb_buffer_request.width = width;
create_dumb_buffer_request.height = height;
create_dumb_buffer_request.bpp = 32;
create_dumb_buffer_request.flags = 0;
if (drmIoctl (backend->device_fd,
DRM_IOCTL_MODE_CREATE_DUMB,
&create_dumb_buffer_request) < 0) {
free (buffer);
ply_trace ("Could not allocate GEM object for frame buffer: %m");
return NULL;
}
buffer->handle = create_dumb_buffer_request.handle;
buffer->row_stride = create_dumb_buffer_request.pitch;
buffer->map_size = create_dumb_buffer_request.size;
ply_trace ("returning %ux%u buffer with stride %u",
width, height, buffer->row_stride);
return buffer;
}
static void
ply_renderer_buffer_free (ply_renderer_backend_t *backend,
ply_renderer_buffer_t *buffer)
{
struct drm_mode_destroy_dumb destroy_dumb_buffer_request;
if (buffer->added_fb)
drmModeRmFB (backend->device_fd, buffer->id);
if (buffer->map_address != MAP_FAILED) {
munmap (buffer->map_address, buffer->map_size);
buffer->map_address = MAP_FAILED;
}
memset (&destroy_dumb_buffer_request, 0, sizeof(struct drm_mode_destroy_dumb));
destroy_dumb_buffer_request.handle = buffer->handle;
if (drmIoctl (backend->device_fd,
DRM_IOCTL_MODE_DESTROY_DUMB,
&destroy_dumb_buffer_request) < 0)
ply_trace ("Could not deallocate GEM object %u: %m", buffer->handle);
free (buffer);
}
static ply_renderer_buffer_t *
get_buffer_from_id (ply_renderer_backend_t *backend,
uint32_t id)
{
static ply_renderer_buffer_t *buffer;
buffer = ply_hashtable_lookup (backend->output_buffers, (void *) (uintptr_t) id);
return buffer;
}
static uint32_t
create_output_buffer (ply_renderer_backend_t *backend,
unsigned long width,
unsigned long height,
unsigned long *row_stride)
{
ply_renderer_buffer_t *buffer;
buffer = ply_renderer_buffer_new (backend, width, height);
if (buffer == NULL) {
ply_trace ("Could not allocate GEM object for frame buffer: %m");
return 0;
}
if (drmModeAddFB (backend->device_fd, width, height,
24, 32, buffer->row_stride, buffer->handle,
&buffer->id) != 0) {
ply_trace ("Could not set up GEM object as frame buffer: %m");
ply_renderer_buffer_free (backend, buffer);
return 0;
}
*row_stride = buffer->row_stride;
buffer->added_fb = true;
ply_hashtable_insert (backend->output_buffers,
(void *) (uintptr_t) buffer->id,
buffer);
return buffer->id;
}
static bool
map_buffer (ply_renderer_backend_t *backend,
uint32_t buffer_id)
{
ply_renderer_buffer_t *buffer;
buffer = get_buffer_from_id (backend, buffer_id);
assert (buffer != NULL);
return ply_renderer_buffer_map (backend, buffer);
}
static void
unmap_buffer (ply_renderer_backend_t *backend,
uint32_t buffer_id)
{
ply_renderer_buffer_t *buffer;
buffer = get_buffer_from_id (backend, buffer_id);
assert (buffer != NULL);
ply_renderer_buffer_unmap (backend, buffer);
}
static char *
begin_flush (ply_renderer_backend_t *backend,
uint32_t buffer_id)
{
ply_renderer_buffer_t *buffer;
buffer = get_buffer_from_id (backend, buffer_id);
assert (buffer != NULL);
return buffer->map_address;
}
static void
end_flush (ply_renderer_backend_t *backend,
uint32_t buffer_id)
{
ply_renderer_buffer_t *buffer;
buffer = get_buffer_from_id (backend, buffer_id);
assert (buffer != NULL);
if (backend->requires_explicit_flushing) {
struct drm_clip_rect flush_area;
int ret;
flush_area.x1 = 0;
flush_area.y1 = 0;
flush_area.x2 = buffer->width;
flush_area.y2 = buffer->height;
ret = drmModeDirtyFB (backend->device_fd, buffer->id, &flush_area, 1);
if (ret == -ENOSYS)
backend->requires_explicit_flushing = false;
}
}
static void
destroy_output_buffer (ply_renderer_backend_t *backend,
uint32_t buffer_id)
{
ply_renderer_buffer_t *buffer;
buffer = ply_hashtable_remove (backend->output_buffers,
(void *) (uintptr_t) buffer_id);
assert (buffer != NULL);
ply_renderer_buffer_free (backend, buffer);
}
static bool
get_primary_plane_rotation (ply_renderer_backend_t *backend,
uint32_t controller_id,
int *primary_id_ret,
int *rotation_prop_id_ret,
uint64_t *rotation_ret)
{
drmModeObjectPropertiesPtr plane_props;
drmModePlaneResPtr plane_resources;
drmModePropertyPtr prop;
drmModePlanePtr plane;
uint64_t rotation;
uint32_t i, j;
int rotation_prop_id = -1;
int primary_id = -1;
int err;
if (!controller_id)
return false;
err = drmSetClientCap (backend->device_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
if (err)
return false;
plane_resources = drmModeGetPlaneResources (backend->device_fd);
if (!plane_resources)
return false;
for (i = 0; i < plane_resources->count_planes; i++) {
plane = drmModeGetPlane (backend->device_fd,
plane_resources->planes[i]);
if (!plane)
continue;
if (plane->crtc_id != controller_id) {
drmModeFreePlane (plane);
continue;
}
plane_props = drmModeObjectGetProperties (backend->device_fd,
plane->plane_id,
DRM_MODE_OBJECT_PLANE);
for (j = 0; plane_props && (j < plane_props->count_props); j++) {
prop = drmModeGetProperty (backend->device_fd,
plane_props->props[j]);
if (!prop)
continue;
if (strcmp (prop->name, "type") == 0 &&
plane_props->prop_values[j] == DRM_PLANE_TYPE_PRIMARY) {
primary_id = plane->plane_id;
}
if (strcmp (prop->name, "rotation") == 0) {
rotation_prop_id = plane_props->props[j];
rotation = plane_props->prop_values[j];
}
drmModeFreeProperty (prop);
}
drmModeFreeObjectProperties (plane_props);
drmModeFreePlane (plane);
if (primary_id != -1)
break;
/* Not primary -> clear any found rotation property */
rotation_prop_id = -1;
}
drmModeFreePlaneResources (plane_resources);
if (primary_id != -1 && rotation_prop_id != -1) {
*primary_id_ret = primary_id;
*rotation_prop_id_ret = rotation_prop_id;
*rotation_ret = rotation;
return true;
}
return false;
}
static ply_pixel_buffer_rotation_t
connector_orientation_prop_to_rotation (drmModePropertyPtr prop,
int orientation)
{
const char *name = prop->enums[orientation].name;
if (strcmp (name, "Upside Down") == 0)
return PLY_PIXEL_BUFFER_ROTATE_UPSIDE_DOWN;
if (strcmp (name, "Left Side Up") == 0) {
/* Left side up, rotate counter clockwise to correct */
return PLY_PIXEL_BUFFER_ROTATE_COUNTER_CLOCKWISE;
}
if (strcmp (name, "Right Side Up") == 0) {
/* Left side up, rotate clockwise to correct */
return PLY_PIXEL_BUFFER_ROTATE_CLOCKWISE;
}
return PLY_PIXEL_BUFFER_ROTATE_UPRIGHT;
}
static void
ply_renderer_connector_get_rotation_and_tiled (ply_renderer_backend_t *backend,
drmModeConnector *connector,
ply_output_t *output)
{
int i, primary_id, rotation_prop_id;
drmModePropertyPtr prop;
uint64_t rotation;
output->rotation = PLY_PIXEL_BUFFER_ROTATE_UPRIGHT;
output->tiled = false;
for (i = 0; i < connector->count_props; i++) {
prop = drmModeGetProperty (backend->device_fd, connector->props[i]);
if (!prop)
continue;
if ((prop->flags & DRM_MODE_PROP_ENUM) &&
strcmp (prop->name, "panel orientation") == 0)
output->rotation = connector_orientation_prop_to_rotation (prop, connector->prop_values[i]);
if ((prop->flags & DRM_MODE_PROP_BLOB) &&
strcmp (prop->name, "TILE") == 0 &&
connector->prop_values[i] != 0)
output->tiled = true;
if ((prop->flags & DRM_MODE_PROP_ENUM) &&
strcmp (prop->name, "link-status") == 0) {
output->link_status = connector->prop_values[i];
ply_trace ("link-status %d", output->link_status);
}
drmModeFreeProperty (prop);
}
/* If the firmware setup the plane to use hw 180° rotation, then we keep
* the hw rotation. This avoids a flicker and avoids the splash turning
* upside-down when mutter turns hw-rotation back on and then fades from
* the splash to the login screen.
*/
if (output->rotation == PLY_PIXEL_BUFFER_ROTATE_UPSIDE_DOWN &&
get_primary_plane_rotation (backend, output->controller_id,
&primary_id, &rotation_prop_id,
&rotation) &&
rotation == DRM_MODE_ROTATE_180) {
ply_trace("Keeping hw 180° rotation");
output->rotation = PLY_PIXEL_BUFFER_ROTATE_UPRIGHT;
output->uses_hw_rotation = true;
}
}
static bool
ply_renderer_head_add_connector (ply_renderer_head_t *head,
ply_output_t *output)
{
if (output->link_status == DRM_MODE_LINK_STATUS_BAD)
head->scan_out_buffer_needs_reset = true;
if (output->mode.hdisplay != head->area.width || output->mode.vdisplay != head->area.height) {
ply_trace ("Tried to add connector with resolution %dx%d to %dx%d head",
(int) output->mode.hdisplay, (int) output->mode.vdisplay,
(int) head->area.width, (int) head->area.height);
return false;
}
if (ply_array_contains_uint32_element (head->connector_ids, output->connector_id)) {
ply_trace ("Head already contains connector with id %d", output->connector_id);
return false;
}
ply_trace ("Adding connector with id %d to %dx%d head",
(int) output->connector_id, (int) head->area.width, (int) head->area.height);
ply_array_add_uint32_element (head->connector_ids, output->connector_id);
return true;
}
static ply_renderer_head_t *
ply_renderer_head_new (ply_renderer_backend_t *backend,
ply_output_t *output,
uint32_t console_buffer_id,
int gamma_size)
{
ply_renderer_head_t *head;
int i, step;
head = calloc (1, sizeof(ply_renderer_head_t));
head->backend = backend;
head->connector_ids = ply_array_new (PLY_ARRAY_ELEMENT_TYPE_UINT32);
head->controller_id = output->controller_id;
head->console_buffer_id = console_buffer_id;
head->connector0_mode = output->mode;
head->uses_hw_rotation = output->uses_hw_rotation;
head->area.x = 0;
head->area.y = 0;
head->area.width = output->mode.hdisplay;
head->area.height = output->mode.vdisplay;
if (gamma_size) {
head->gamma_size = gamma_size;
head->gamma = malloc (gamma_size * 3 * sizeof(uint16_t));
step = UINT16_MAX / (gamma_size - 1);
for (i = 0; i < gamma_size; i++) {
head->gamma[0 * gamma_size + i] = i * step; /* red */
head->gamma[1 * gamma_size + i] = i * step; /* green */
head->gamma[2 * gamma_size + i] = i * step; /* blue */
}
}
ply_renderer_head_add_connector (head, output);
assert (ply_array_get_size (head->connector_ids) > 0);
head->pixel_buffer = ply_pixel_buffer_new_with_device_rotation (head->area.width, head->area.height, output->rotation);
ply_pixel_buffer_set_device_scale (head->pixel_buffer, output->device_scale);
ply_trace ("Creating %ldx%ld renderer head", head->area.width, head->area.height);
ply_pixel_buffer_fill_with_color (head->pixel_buffer, NULL,
0.0, 0.0, 0.0, 1.0);
/* Delay flush till first actual draw */
ply_region_clear (ply_pixel_buffer_get_updated_areas (head->pixel_buffer));
if (output->connector_type == DRM_MODE_CONNECTOR_LVDS ||
output->connector_type == DRM_MODE_CONNECTOR_eDP ||
output->connector_type == DRM_MODE_CONNECTOR_DSI) {
backend->panel_width = output->mode.hdisplay;
backend->panel_height = output->mode.vdisplay;
backend->panel_rotation = output->rotation;
backend->panel_scale = output->device_scale;
}
ply_list_append_data (backend->heads, head);
ply_hashtable_insert (backend->heads_by_controller_id,
(void *) (intptr_t) output->controller_id,
head);
return head;
}
static void
ply_renderer_head_free (ply_renderer_head_t *head)
{
ply_trace ("freeing %ldx%ld renderer head", head->area.width, head->area.height);
ply_pixel_buffer_free (head->pixel_buffer);
ply_array_free (head->connector_ids);
free (head->gamma);
free (head);
}
static void
ply_renderer_head_clear_plane_rotation (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
int primary_id, rotation_prop_id, err;
uint64_t rotation;
if (head->uses_hw_rotation)
return;
if (get_primary_plane_rotation (backend, head->controller_id,
&primary_id, &rotation_prop_id,
&rotation) &&
rotation != DRM_MODE_ROTATE_0) {
err = drmModeObjectSetProperty (backend->device_fd,
primary_id,
DRM_MODE_OBJECT_PLANE,
rotation_prop_id,
DRM_MODE_ROTATE_0);
ply_trace ("Cleared rotation on primary plane %d result %d",
primary_id, err);
}
}
static bool
ply_renderer_head_set_scan_out_buffer (ply_renderer_backend_t *backend,
ply_renderer_head_t *head,
uint32_t buffer_id)
{
drmModeModeInfo *mode = &head->connector0_mode;
uint32_t *connector_ids;
int number_of_connectors;
connector_ids = (uint32_t *) ply_array_get_uint32_elements (head->connector_ids);
number_of_connectors = ply_array_get_size (head->connector_ids);
ply_trace ("Setting scan out buffer of %ldx%ld head to our buffer",
head->area.width, head->area.height);
/* Set gamma table, do this only once */
if (head->gamma) {
drmModeCrtcSetGamma (backend->device_fd,
head->controller_id,
head->gamma_size,
head->gamma + 0 * head->gamma_size,
head->gamma + 1 * head->gamma_size,
head->gamma + 2 * head->gamma_size);
free (head->gamma);
head->gamma = NULL;
}
/* Tell the controller to use the allocated scan out buffer on each connectors
*/
if (drmModeSetCrtc (backend->device_fd, head->controller_id, buffer_id,
0, 0, connector_ids, number_of_connectors, mode) < 0) {
ply_trace ("Couldn't set scan out buffer for head with controller id %d",
head->controller_id);
return false;
}
ply_renderer_head_clear_plane_rotation (backend, head);
return true;
}
static bool
ply_renderer_head_map (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
assert (backend != NULL);
assert (backend->device_fd >= 0);
assert (backend != NULL);
assert (head != NULL);
ply_trace ("Creating buffer for %ldx%ld renderer head", head->area.width, head->area.height);
head->scan_out_buffer_id = create_output_buffer (backend,
head->area.width, head->area.height,
&head->row_stride);
if (head->scan_out_buffer_id == 0)
return false;
ply_trace ("Mapping buffer for %ldx%ld renderer head", head->area.width, head->area.height);
if (!map_buffer (backend, head->scan_out_buffer_id)) {
destroy_output_buffer (backend, head->scan_out_buffer_id);
head->scan_out_buffer_id = 0;
return false;
}
head->scan_out_buffer_needs_reset = true;
return true;
}
static void
ply_renderer_head_unmap (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
ply_trace ("unmapping %ldx%ld renderer head", head->area.width, head->area.height);
unmap_buffer (backend, head->scan_out_buffer_id);
destroy_output_buffer (backend, head->scan_out_buffer_id);
head->scan_out_buffer_id = 0;
}
static void
ply_renderer_head_remove (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
if (head->scan_out_buffer_id)
ply_renderer_head_unmap (backend, head);
ply_hashtable_remove (backend->heads_by_controller_id,
(void *) (intptr_t) head->controller_id);
ply_list_remove_data (backend->heads, head);
ply_renderer_head_free (head);
}
static void
ply_renderer_head_remove_connector (ply_renderer_backend_t *backend,
ply_renderer_head_t *head,
uint32_t connector_id)
{
int i, size = ply_array_get_size (head->connector_ids);
uint32_t *connector_ids;
if (!ply_array_contains_uint32_element (head->connector_ids, connector_id)) {
ply_trace ("Head does not contain connector %u, cannot remove", connector_id);
return;
}
if (size == 1) {
ply_renderer_head_remove (backend, head);
return;
}
/* Empty the array and re-add all connectors except the one being removed */
connector_ids = ply_array_steal_uint32_elements (head->connector_ids);
for (i = 0; i < size; i++) {
if (connector_ids[i] != connector_id)
ply_array_add_uint32_element (head->connector_ids,
connector_ids[i]);
}
free (connector_ids);
}
static void
flush_area (const char *src,
unsigned long src_row_stride,
char *dst,
unsigned long dst_row_stride,
ply_rectangle_t *area_to_flush)
{
unsigned long y1, y2, y;
y1 = area_to_flush->y;
y2 = y1 + area_to_flush->height;
if (area_to_flush->width * 4 == src_row_stride &&
area_to_flush->width * 4 == dst_row_stride) {
memcpy (dst, src, area_to_flush->width * area_to_flush->height * 4);
return;
}
for (y = y1; y < y2; y++) {
memcpy (dst, src, area_to_flush->width * 4);
dst += dst_row_stride;
src += src_row_stride;
}
}
static void
ply_renderer_head_flush_area (ply_renderer_head_t *head,
ply_rectangle_t *area_to_flush,
char *map_address)
{
uint32_t *shadow_buffer;
char *dst, *src;
shadow_buffer = ply_pixel_buffer_get_argb32_data (head->pixel_buffer);
dst = &map_address[area_to_flush->y * head->row_stride + area_to_flush->x * BYTES_PER_PIXEL];
src = (char *) &shadow_buffer[area_to_flush->y * head->area.width + area_to_flush->x];
flush_area (src, head->area.width * 4, dst, head->row_stride, area_to_flush);
}
static void
free_heads (ply_renderer_backend_t *backend)
{
ply_list_node_t *node;
node = ply_list_get_first_node (backend->heads);
while (node != NULL) {
ply_list_node_t *next_node;
ply_renderer_head_t *head;
head = (ply_renderer_head_t *) ply_list_node_get_data (node);
next_node = ply_list_get_next_node (backend->heads, node);
ply_renderer_head_free (head);
ply_list_remove_node (backend->heads, node);
node = next_node;
}
}
static ply_renderer_backend_t *
create_backend (const char *device_name,
ply_terminal_t *terminal)
{
ply_renderer_backend_t *backend;
backend = calloc (1, sizeof(ply_renderer_backend_t));
if (device_name != NULL)
backend->device_name = strdup (device_name);
else
backend->device_name = strdup ("/dev/dri/card0");
ply_trace ("creating renderer backend for device %s", backend->device_name);
backend->device_fd = -1;
backend->loop = ply_event_loop_get_default ();
backend->heads = ply_list_new ();
backend->input_source.key_buffer = ply_buffer_new ();
backend->terminal = terminal;
backend->requires_explicit_flushing = true;
backend->output_buffers = ply_hashtable_new (ply_hashtable_direct_hash,
ply_hashtable_direct_compare);
backend->heads_by_controller_id = ply_hashtable_new (NULL, NULL);
backend->use_preferred_mode = should_use_preferred_mode ();
return backend;
}
static const char *
get_device_name (ply_renderer_backend_t *backend)
{
return backend->device_name;
}
static void
destroy_backend (ply_renderer_backend_t *backend)
{
ply_trace ("destroying renderer backend for device %s", backend->device_name);
free_heads (backend);
free (backend->device_name);
ply_hashtable_free (backend->output_buffers);
ply_hashtable_free (backend->heads_by_controller_id);
free (backend->outputs);
free (backend);
}
static void
activate (ply_renderer_backend_t *backend)
{
ply_renderer_head_t *head;
ply_list_node_t *node;
ply_trace ("taking master and scanning out");
backend->is_active = true;
drmSetMaster (backend->device_fd);
node = ply_list_get_first_node (backend->heads);
while (node != NULL) {
head = (ply_renderer_head_t *) ply_list_node_get_data (node);
/* Flush out any pending drawing to the buffer */
flush_head (backend, head);
node = ply_list_get_next_node (backend->heads, node);
}
}
static void
deactivate (ply_renderer_backend_t *backend)
{
ply_trace ("dropping master");
drmDropMaster (backend->device_fd);
backend->is_active = false;
}
static void
on_active_vt_changed (ply_renderer_backend_t *backend)
{
if (ply_terminal_is_active (backend->terminal)) {
ply_trace ("activating on vt change");
activate (backend);
} else {
ply_trace ("deactivating on vt change");
deactivate (backend);
}
}
static bool
load_driver (ply_renderer_backend_t *backend)
{
int device_fd;
ply_trace ("Opening '%s'", backend->device_name);
device_fd = open (backend->device_name, O_RDWR);
if (device_fd < 0) {
ply_trace ("open failed: %m");
return false;
}
backend->device_fd = device_fd;
drmDropMaster (device_fd);
return true;
}
static void
unload_backend (ply_renderer_backend_t *backend)
{
if (backend == NULL)
return;
ply_trace ("unloading backend");
if (backend->device_fd >= 0) {
drmClose (backend->device_fd);
backend->device_fd = -1;
}
destroy_backend (backend);
backend = NULL;
}
static bool
open_device (ply_renderer_backend_t *backend)
{
assert (backend != NULL);
assert (backend->device_name != NULL);
if (!load_driver (backend))
return false;
if (backend->terminal == NULL)
return true;
if (!ply_terminal_open (backend->terminal)) {
ply_trace ("could not open terminal: %m");
return false;
}
if (!ply_terminal_is_vt (backend->terminal)) {
ply_trace ("terminal is not a VT");
ply_terminal_close (backend->terminal);
return false;
}
ply_terminal_watch_for_active_vt_change (backend->terminal,
(ply_terminal_active_vt_changed_handler_t)
on_active_vt_changed,
backend);
return true;
}
static void
close_device (ply_renderer_backend_t *backend)
{
ply_trace ("closing device");
free_heads (backend);
if (backend->terminal != NULL) {
ply_terminal_stop_watching_for_active_vt_change (backend->terminal,
(ply_terminal_active_vt_changed_handler_t)
on_active_vt_changed,
backend);
}
unload_backend (backend);
}
static void
output_get_controller_info (ply_renderer_backend_t *backend,
drmModeConnector *connector,
ply_output_t *output)
{
int i;
drmModeEncoder *encoder;
assert (backend != NULL);
output->possible_controllers = 0xffffffff;
for (i = 0; i < connector->count_encoders; i++) {
encoder = drmModeGetEncoder (backend->device_fd,
connector->encoders[i]);
if (encoder == NULL)
continue;
if (encoder->encoder_id == connector->encoder_id && encoder->crtc_id) {
ply_trace ("Found already lit monitor on connector %u using controller %u",
connector->connector_id, encoder->crtc_id);
output->controller_id = encoder->crtc_id;
}
/* Like mutter and xf86-drv-modesetting only select controllers
* which are supported by all the connector's encoders.
*/
output->possible_controllers &= encoder->possible_crtcs;
ply_trace ("connector %u encoder %u possible controllers 0x%08x/0x%08x",
connector->connector_id, encoder->encoder_id,
encoder->possible_crtcs, output->possible_controllers);
drmModeFreeEncoder (encoder);
}
}
static bool
modes_are_equal (drmModeModeInfo *a,
drmModeModeInfo *b)
{
return a->clock == b->clock &&
a->hdisplay == b->hdisplay &&
a->hsync_start == b->hsync_start &&
a->hsync_end == b->hsync_end &&
a->htotal == b->htotal &&
a->hskew == b->hskew &&
a->vdisplay == b->vdisplay &&
a->vsync_start == b->vsync_start &&
a->vsync_end == b->vsync_end &&
a->vtotal == b->vtotal &&
a->vscan == b->vscan &&
a->vrefresh == b->vrefresh &&
a->flags == b->flags &&
a->type == b->type;
}
static drmModeModeInfo *
find_matching_connector_mode (ply_renderer_backend_t *backend,
drmModeConnector *connector,
drmModeModeInfo *mode)
{
int i;
for (i = 0; i < connector->count_modes; i++) {
if (modes_are_equal (&connector->modes[i], mode)) {
ply_trace ("Found connector mode index %d for mode %dx%d",
i, mode->hdisplay, mode->vdisplay);
return &connector->modes[i];
}
}
return NULL;
}
static drmModeModeInfo *
get_preferred_mode (drmModeConnector *connector)
{
int i;
for (i = 0; i < connector->count_modes; i++)
if (connector->modes[i].type & DRM_MODE_TYPE_PREFERRED) {
ply_trace ("Found preferred mode %dx%d at index %d",
connector->modes[i].hdisplay,
connector->modes[i].vdisplay, i);
return &connector->modes[i];
}
return NULL;
}
static drmModeModeInfo *
get_active_mode (ply_renderer_backend_t *backend,
drmModeConnector *connector,
ply_output_t *output)
{
drmModeCrtc *controller;
drmModeModeInfo *mode;
controller = drmModeGetCrtc (backend->device_fd, output->controller_id);
if (!controller || !controller->mode_valid) {
ply_trace ("No valid mode currently active on monitor");
return NULL;
}
ply_trace ("Looking for connector mode index of active mode %dx%d",
controller->mode.hdisplay, controller->mode.vdisplay);
mode = find_matching_connector_mode (backend, connector, &controller->mode);
drmModeFreeCrtc (controller);
return mode;
}
static void
get_output_info (ply_renderer_backend_t *backend,
uint32_t connector_id,
ply_output_t *output)
{
drmModeModeInfo *mode = NULL;
drmModeConnector *connector;
memset (output, 0, sizeof(*output));
output->connector_id = connector_id;
connector = drmModeGetConnector (backend->device_fd, connector_id);
if (connector == NULL)
return;
if (connector->connection != DRM_MODE_CONNECTED ||
connector->count_modes <= 0)
goto out;
output_get_controller_info (backend, connector, output);
ply_renderer_connector_get_rotation_and_tiled (backend, connector, output);
if (!output->tiled && backend->use_preferred_mode)
mode = get_preferred_mode (connector);
if (!mode && output->controller_id)
mode = get_active_mode (backend, connector, output);
/* If we couldn't find the current active mode, fall back to the first available. */
if (!mode) {
ply_trace ("falling back to first available mode");
mode = &connector->modes[0];
}
output->mode = *mode;
output->device_scale = ply_get_device_scale (mode->hdisplay, mode->vdisplay,
connector->mmWidth, connector->mmHeight);
output->connector_type = connector->connector_type;
output->connected = true;
out:
drmModeFreeConnector (connector);
}
/* Some controllers can only drive some outputs, we want to find a combination
* where all (connected) outputs get a controller. To do this setup_outputs
* picks which output to assign a controller for first (trying all outputs), so
* that that one will get the first (free) controller and then recurses into
* itself to assign the remaining outputs. This tries assigning all remainig
* unassigned outputs first and returns the best result of all possible
* assignment orders for the remaining unassigned outputs.
* This repeats until we find an assignment order which results in a controller
* for all outputs, or we've tried all possible assignment orders.
*/
static uint32_t
find_controller_for_output (ply_renderer_backend_t *backend,
const ply_output_t *outputs,
int outputs_len,
int output_idx)
{
uint32_t possible_controllers = outputs[output_idx].possible_controllers;
int i, j;
for (i = 0; i < backend->resources->count_crtcs; i++) {
uint32_t controller_id = backend->resources->crtcs[i];
if (!(possible_controllers & (1 << i)))
continue; /* controller not usable for this connector */
for (j = 0; j < outputs_len; j++) {
if (outputs[j].controller_id == controller_id)
break;
}
if (j < outputs_len)
continue; /* controller already in use */
return controller_id;
}
return 0;
}
static int
count_setup_controllers (const ply_output_t *outputs,
int outputs_len)
{
int i, count = 0;
for (i = 0; i < outputs_len; i++) {
if (outputs[i].controller_id)
count++;
}
return count;
}
static ply_output_t *
setup_outputs (ply_renderer_backend_t *backend,
const ply_output_t *outputs,
int outputs_len)
{
const ply_output_t *best_outputs;
ply_output_t *new_outputs;
int i, count, best_count;
uint32_t controller_id;
best_count = count_setup_controllers (outputs, outputs_len);
best_outputs = outputs;
for (i = 0; i < outputs_len && best_count < backend->connected_count; i++) {
/* Not connected or already assigned? */
if (!outputs[i].connected || outputs[i].controller_id)
continue;
/* Assign controller for connector i */
controller_id = find_controller_for_output (backend, outputs, outputs_len, i);
if (!controller_id)
continue;
/* Add the new controller to a copy of the passed in connector
* template, we want to try all possible permutations of
* unassigned outputs without modifying the template.
*/
new_outputs = calloc (outputs_len, sizeof(*new_outputs));
memcpy (new_outputs, outputs, outputs_len * sizeof(ply_output_t));
new_outputs[i].controller_id = controller_id;
/* Recurse into ourselves to assign remaining controllers,
* trying all possible assignment orders.
*/
new_outputs = setup_outputs (backend, new_outputs, outputs_len);
count = count_setup_controllers (new_outputs, outputs_len);
if (count > best_count) {
if (best_outputs != outputs)
free ((void *)best_outputs);
best_outputs = new_outputs;
best_count = count;
} else {
free (new_outputs);
}
}
if (best_outputs != outputs)
free ((void *)outputs);
/* Our caller is allowed to modify outputs, cast-away the const */
return (ply_output_t *)best_outputs;
}
static void
remove_output (ply_renderer_backend_t *backend, ply_output_t *output)
{
ply_renderer_head_t *head;
head = ply_hashtable_lookup (backend->heads_by_controller_id,
(void *) (intptr_t) output->controller_id);
if (head == NULL) {
ply_trace ("Could not find head for connector %u, controller %u, cannot remove",
output->connector_id, output->controller_id);
return;
}
ply_renderer_head_remove_connector (backend, head, output->connector_id);
}
/* Check if an output has changed since we last enumerated it; and if
* it has changed remove it from the head it is part of.
*/
static bool
check_if_output_has_changed (ply_renderer_backend_t *backend,
ply_output_t *new_output)
{
ply_output_t *old_output = NULL;
int i;
for (i = 0; i < backend->outputs_len; i++) {
if (backend->outputs[i].connector_id == new_output->connector_id) {
old_output = &backend->outputs[i];
break;
}
}
if (!old_output || !old_output->controller_id)
return false;
if (memcmp(old_output, new_output, sizeof(ply_output_t)) == 0)
return false;
ply_trace ("Output for connector %u changed, removing", old_output->connector_id);
remove_output (backend, old_output);
return true;
}
/* Update our outputs array to match the hardware state and
* create and/or remove heads as necessary.
* Returns true if any heads were modified.
*/
static bool
create_heads_for_active_connectors (ply_renderer_backend_t *backend, bool change)
{
int i, j, number_of_setup_outputs, outputs_len;
ply_output_t *outputs;
bool changed = false;
/* Step 1:
* Query all outputs and:
* 1.1 Remove currently connected outputs from their heads if changed.
* 1.2 Build a new outputs array from scratch. For any unchanged
* outputs for which we already have a head, we will end up in
* ply_renderer_head_add_connector which will ignore the already
* added connector.
*/
ply_trace ("(Re)enumerating all outputs");
outputs = calloc (backend->resources->count_connectors, sizeof(*outputs));
outputs_len = backend->resources->count_connectors;
backend->connected_count = 0;
for (i = 0; i < outputs_len; i++) {
get_output_info (backend, backend->resources->connectors[i], &outputs[i]);
if (check_if_output_has_changed (backend, &outputs[i]))
changed = true;
if (outputs[i].connected)
backend->connected_count++;
}
/* Step 2:
* Free the old outputs array, now that we have checked for changes
* we no longer need it.
*/
free (backend->outputs);
backend->outputs = NULL;
/* Step 3:
* Drop controllers for clones for which we've picked different modes.
*/
for (i = 0; i < outputs_len; i++) {
if (!outputs[i].controller_id)
continue;
for (j = i + 1; j < outputs_len; j++) {
if (!outputs[j].controller_id)
continue;
if (outputs[i].controller_id == outputs[j].controller_id &&
(outputs[i].mode.hdisplay != outputs[j].mode.hdisplay ||
outputs[i].mode.vdisplay != outputs[j].mode.vdisplay)) {
ply_trace ("connector %u uses same controller as %u and modes differ, unlinking controller",
outputs[j].connector_id, outputs[i].connector_id);
outputs[j].controller_id = 0;
}
}
}
/* Step 4:
* Assign controllers to outputs without a controller
*/
number_of_setup_outputs = count_setup_controllers (outputs, outputs_len);
if (number_of_setup_outputs != backend->connected_count) {
/* First try, try to assign controllers to outputs without one */
ply_trace ("Some outputs don't have controllers, picking controllers");
outputs = setup_outputs (backend, outputs, outputs_len);
number_of_setup_outputs = count_setup_controllers (outputs, outputs_len);
}
/* Try again if necessary, re-assing controllers for all outputs.
* Note this is skipped when processing change events, as we don't
* want to mess with the controller assignment of already lit monitors
* in that case.
*/
if (!change && number_of_setup_outputs != backend->connected_count) {
ply_trace ("Some outputs still don't have controllers, re-assigning controllers for all outputs");
for (i = 0; i < outputs_len; i++) {
if (outputs[i].uses_hw_rotation)
continue; /* Do not re-assign hw-rotated outputs */
outputs[i].controller_id = 0;
}
outputs = setup_outputs (backend, outputs, outputs_len);
}
for (i = 0; i < outputs_len; i++)
ply_trace ("Using controller %u for connector %u",
outputs[i].controller_id, outputs[i].connector_id);
/* Step 5:
* Create heads for all valid outputs
*/
for (i = 0; i < outputs_len; i++) {
drmModeCrtc *controller;
ply_renderer_head_t *head;
uint32_t controller_id;
uint32_t console_buffer_id;
int gamma_size;
if (!outputs[i].controller_id)
continue;
controller = drmModeGetCrtc (backend->device_fd, outputs[i].controller_id);
if (!controller)
continue;
controller_id = controller->crtc_id;
console_buffer_id = controller->buffer_id;
gamma_size = controller->gamma_size;
drmModeFreeCrtc (controller);
head = ply_hashtable_lookup (backend->heads_by_controller_id,
(void *) (intptr_t) controller_id);
if (head == NULL) {
head = ply_renderer_head_new (backend, &outputs[i],
console_buffer_id,
gamma_size);
changed = true;
} else {
if (ply_renderer_head_add_connector (head, &outputs[i]))
changed = true;
}
}
backend->outputs_len = outputs_len;
backend->outputs = outputs;
ply_trace ("outputs %schanged\n", changed ? "" : "un");
return changed;
}
static bool
has_32bpp_support (ply_renderer_backend_t *backend)
{
uint32_t buffer_id;
unsigned long row_stride;
uint32_t min_width;
uint32_t min_height;
min_width = backend->resources->min_width;
min_height = backend->resources->min_height;
/* Some backends set min_width/min_height to 0,
* but 0x0 sized buffers don't work.
*/
if (min_width == 0)
min_width = 1;
if (min_height == 0)
min_height = 1;
buffer_id = create_output_buffer (backend, min_width, min_height, &row_stride);
if (buffer_id == 0) {
ply_trace ("Could not create minimal (%ux%u) 32bpp dummy buffer",
backend->resources->min_width,
backend->resources->min_height);
return false;
}
destroy_output_buffer (backend, buffer_id);
return true;
}
static bool
query_device (ply_renderer_backend_t *backend)
{
bool ret = true;
assert (backend != NULL);
assert (backend->device_fd >= 0);
backend->resources = drmModeGetResources (backend->device_fd);
if (backend->resources == NULL) {
ply_trace ("Could not get card resources");
return false;
}
if (!create_heads_for_active_connectors (backend, false)) {
ply_trace ("Could not initialize heads");
ret = false;
} else if (!has_32bpp_support (backend)) {
ply_trace ("Device doesn't support 32bpp framebuffer");
ret = false;
}
drmModeFreeResources (backend->resources);
backend->resources = NULL;
return ret;
}
static bool
handle_change_event (ply_renderer_backend_t *backend)
{
bool ret = true;
backend->resources = drmModeGetResources (backend->device_fd);
if (backend->resources == NULL) {
ply_trace ("Could not get card resources for change event");
return false;
}
ret = create_heads_for_active_connectors (backend, true);
drmModeFreeResources (backend->resources);
backend->resources = NULL;
return ret;
}
static bool
map_to_device (ply_renderer_backend_t *backend)
{
ply_renderer_head_t *head;
ply_list_node_t *node;
bool head_mapped;
head_mapped = false;
node = ply_list_get_first_node (backend->heads);
while (node != NULL) {
head = (ply_renderer_head_t *) ply_list_node_get_data (node);
if (ply_renderer_head_map (backend, head))
head_mapped = true;
node = ply_list_get_next_node (backend->heads, node);
}
if (backend->terminal != NULL) {
if (ply_terminal_is_active (backend->terminal))
activate (backend);
else
ply_terminal_activate_vt (backend->terminal);
} else {
activate (backend);
}
return head_mapped;
}
static void
unmap_from_device (ply_renderer_backend_t *backend)
{
ply_renderer_head_t *head;
ply_list_node_t *node;
node = ply_list_get_first_node (backend->heads);
while (node != NULL) {
head = (ply_renderer_head_t *) ply_list_node_get_data (node);
ply_renderer_head_unmap (backend, head);
node = ply_list_get_next_node (backend->heads, node);
}
}
static bool
reset_scan_out_buffer_if_needed (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
drmModeCrtc *controller;
bool did_reset = false;
if (backend->terminal != NULL)
if (!ply_terminal_is_active (backend->terminal))
return false;
if (head->scan_out_buffer_needs_reset) {
ply_renderer_head_set_scan_out_buffer (backend, head,
head->scan_out_buffer_id);
head->scan_out_buffer_needs_reset = false;
return true;
}
controller = drmModeGetCrtc (backend->device_fd, head->controller_id);
if (controller == NULL)
return false;
if (controller->buffer_id != head->scan_out_buffer_id) {
ply_renderer_head_set_scan_out_buffer (backend, head,
head->scan_out_buffer_id);
did_reset = true;
}
drmModeFreeCrtc (controller);
return did_reset;
}
static void
flush_head (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
ply_rectangle_t *area_to_flush;
ply_region_t *updated_region;
ply_list_t *areas_to_flush;
ply_list_node_t *node;
ply_pixel_buffer_t *pixel_buffer;
char *map_address;
bool dirty = false;
assert (backend != NULL);
if (!backend->is_active)
return;
if (backend->terminal != NULL) {
ply_terminal_set_mode (backend->terminal, PLY_TERMINAL_MODE_GRAPHICS);
ply_terminal_set_unbuffered_input (backend->terminal);
}
pixel_buffer = head->pixel_buffer;
updated_region = ply_pixel_buffer_get_updated_areas (pixel_buffer);
areas_to_flush = ply_region_get_sorted_rectangle_list (updated_region);
/* A hotplugged head may not be mapped yet, map it now. */
if (!head->scan_out_buffer_id) {
if (!ply_renderer_head_map (backend, head))
return;
}
map_address = begin_flush (backend, head->scan_out_buffer_id);
node = ply_list_get_first_node (areas_to_flush);
while (node != NULL) {
area_to_flush = (ply_rectangle_t *) ply_list_node_get_data (node);
ply_renderer_head_flush_area (head, area_to_flush, map_address);
dirty = true;
node = ply_list_get_next_node (areas_to_flush, node);
}
if (dirty) {
if (reset_scan_out_buffer_if_needed (backend, head))
ply_trace ("Needed to reset scan out buffer on %ldx%ld renderer head",
head->area.width, head->area.height);
end_flush (backend, head->scan_out_buffer_id);
}
ply_region_clear (updated_region);
}
static ply_list_t *
get_heads (ply_renderer_backend_t *backend)
{
return backend->heads;
}
static ply_pixel_buffer_t *
get_buffer_for_head (ply_renderer_backend_t *backend,
ply_renderer_head_t *head)
{
if (head->backend != backend)
return NULL;
return head->pixel_buffer;
}
static bool
has_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source)
{
return input_source == &backend->input_source;
}
static ply_renderer_input_source_t *
get_input_source (ply_renderer_backend_t *backend)
{
return &backend->input_source;
}
static void
on_key_event (ply_renderer_input_source_t *input_source,
int terminal_fd)
{
ply_buffer_append_from_fd (input_source->key_buffer,
terminal_fd);
if (input_source->handler != NULL)
input_source->handler (input_source->user_data, input_source->key_buffer, input_source);
}
static void
on_input_source_disconnected (ply_renderer_input_source_t *input_source)
{
ply_trace ("input source disconnected, reopening");
open_input_source (input_source->backend, input_source);
}
static bool
open_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source)
{
int terminal_fd;
assert (backend != NULL);
assert (has_input_source (backend, input_source));
if (backend->terminal == NULL)
return false;
terminal_fd = ply_terminal_get_fd (backend->terminal);
input_source->backend = backend;
input_source->terminal_input_watch = ply_event_loop_watch_fd (backend->loop, terminal_fd, PLY_EVENT_LOOP_FD_STATUS_HAS_DATA,
(ply_event_handler_t) on_key_event,
(ply_event_handler_t)
on_input_source_disconnected, input_source);
return true;
}
static void
set_handler_for_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source,
ply_renderer_input_source_handler_t handler,
void *user_data)
{
assert (backend != NULL);
assert (has_input_source (backend, input_source));
input_source->handler = handler;
input_source->user_data = user_data;
}
static void
close_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source)
{
assert (backend != NULL);
assert (has_input_source (backend, input_source));
if (backend->terminal == NULL)
return;
ply_event_loop_stop_watching_fd (backend->loop, input_source->terminal_input_watch);
input_source->terminal_input_watch = NULL;
input_source->backend = NULL;
}
static bool
get_panel_properties (ply_renderer_backend_t *backend,
int *width,
int *height,
ply_pixel_buffer_rotation_t *rotation,
int *scale)
{
if (!backend->panel_width)
return false;
*width = backend->panel_width;
*height = backend->panel_height;
*rotation = backend->panel_rotation;
*scale = backend->panel_scale;
return true;
}
static bool
get_capslock_state (ply_renderer_backend_t *backend)
{
if (!backend->terminal)
return false;
return ply_terminal_get_capslock_state (backend->terminal);
}
static const char *
get_keymap (ply_renderer_backend_t *backend)
{
if (!backend->terminal)
return NULL;
return ply_terminal_get_keymap (backend->terminal);
}
ply_renderer_plugin_interface_t *
ply_renderer_backend_get_interface (void)
{
static ply_renderer_plugin_interface_t plugin_interface =
{
.create_backend = create_backend,
.destroy_backend = destroy_backend,
.open_device = open_device,
.close_device = close_device,
.query_device = query_device,
.handle_change_event = handle_change_event,
.map_to_device = map_to_device,
.unmap_from_device = unmap_from_device,
.activate = activate,
.deactivate = deactivate,
.flush_head = flush_head,
.get_heads = get_heads,
.get_buffer_for_head = get_buffer_for_head,
.get_input_source = get_input_source,
.open_input_source = open_input_source,
.set_handler_for_input_source = set_handler_for_input_source,
.close_input_source = close_input_source,
.get_device_name = get_device_name,
.get_panel_properties = get_panel_properties,
.get_capslock_state = get_capslock_state,
.get_keymap = get_keymap,
};
return &plugin_interface;
}
/* vim: set ts=4 sw=4 expandtab autoindent cindent cino={.5s,(0: */