]> git.ipfire.org Git - thirdparty/plymouth.git/commitdiff
[renderer] Add start of frame-buffer plugin
authorRay Strode <rstrode@redhat.com>
Fri, 21 Aug 2009 14:13:22 +0000 (10:13 -0400)
committerRay Strode <rstrode@redhat.com>
Mon, 28 Sep 2009 15:23:37 +0000 (11:23 -0400)
Much of this code comes directly from ply-frame-buffer in libply,
but shoehorned to fit into the renderer plugin interface.

One improvement over the old code is it tracks VT changes, and
stops drawing when the wrong VT is active.

configure.ac
scripts/plymouth-populate-initrd.in
src/plugins/renderers/Makefile.am [new file with mode: 0644]
src/plugins/renderers/frame-buffer/Makefile.am [new file with mode: 0644]
src/plugins/renderers/frame-buffer/plugin.c [new file with mode: 0644]

index af507ffd27973c8748925d23ca60b1cfa49a4b8c..df076e2c3ebcfdeaaeb8310316285793f4ff4c18 100644 (file)
@@ -209,6 +209,8 @@ AC_OUTPUT([Makefile
            src/libplybootsplash/Makefile
            src/plymouth-1.pc
            src/plugins/Makefile
+           src/plugins/renderers/Makefile
+           src/plugins/renderers/frame-buffer/Makefile
            src/plugins/splash/Makefile
            src/plugins/splash/throbgress/Makefile
            src/plugins/splash/fade-throbber/Makefile
index dcf2b427cf24b87d4c742b0f648dc60529fa89cd..42b119c3575e8db7dd6bb9a392dafae06b13958d 100755 (executable)
@@ -93,6 +93,8 @@ fi
 
 inst ${PLYMOUTH_PLUGIN_PATH}/${PLYMOUTH_MODULE_NAME}.so $INITRDDIR
 
+inst ${PLYMOUTH_PLUGIN_PATH}/renderers/frame-buffer.so $INITRDDIR
+
 if [ -d ${DATADIR}/plymouth/themes/${PLYMOUTH_THEME_NAME} ]; then
     for x in ${DATADIR}/plymouth/themes/${PLYMOUTH_THEME_NAME}/* ; do
         [ ! -f "$x" ] && break
diff --git a/src/plugins/renderers/Makefile.am b/src/plugins/renderers/Makefile.am
new file mode 100644 (file)
index 0000000..2a9e8a0
--- /dev/null
@@ -0,0 +1,2 @@
+SUBDIRS = frame-buffer
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/plugins/renderers/frame-buffer/Makefile.am b/src/plugins/renderers/frame-buffer/Makefile.am
new file mode 100644 (file)
index 0000000..6811e01
--- /dev/null
@@ -0,0 +1,20 @@
+INCLUDES = -I$(top_srcdir)                                                    \
+           -I$(srcdir)/../../../libply                                        \
+           -I$(srcdir)/../../../libplybootsplash                              \
+           -I$(srcdir)/../../..                                               \
+           -I$(srcdir)/../..                                                  \
+           -I$(srcdir)/..                                                     \
+           -I$(srcdir)
+
+plugindir = $(libdir)/plymouth/renderers
+plugin_LTLIBRARIES = frame-buffer.la
+
+frame_buffer_la_CFLAGS = $(PLYMOUTH_CFLAGS)
+
+frame_buffer_la_LDFLAGS = -module -avoid-version -export-dynamic
+frame_buffer_la_LIBADD = $(PLYMOUTH_LIBS)                                     \
+                         ../../../libply/libply.la                            \
+                         ../../../libplybootsplash/libplybootsplash.la
+frame_buffer_la_SOURCES = $(srcdir)/plugin.c
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/plugins/renderers/frame-buffer/plugin.c b/src/plugins/renderers/frame-buffer/plugin.c
new file mode 100644 (file)
index 0000000..85d9e7d
--- /dev/null
@@ -0,0 +1,667 @@
+/* plugin.c - frame-backend renderer plugin
+ *
+ * Copyright (C) 2006-2009 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>
+ */
+#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 <linux/fb.h>
+
+#include "ply-buffer.h"
+#include "ply-event-loop.h"
+#include "ply-list.h"
+#include "ply-logger.h"
+#include "ply-rectangle.h"
+#include "ply-region.h"
+#include "ply-terminal.h"
+
+#include "ply-renderer.h"
+#include "ply-renderer-plugin.h"
+
+#ifndef PLY_FRAME_BUFFER_DEFAULT_FB_DEVICE_NAME
+#define PLY_FRAME_BUFFER_DEFAULT_FB_DEVICE_NAME "/dev/fb"
+#endif
+
+struct _ply_renderer_head
+{
+  ply_pixel_buffer_t *pixel_buffer;
+  ply_rectangle_t area;
+  char *map_address;
+  size_t size;
+
+};
+
+struct _ply_renderer_input_source
+{
+  ply_fd_watch_t *terminal_input_watch;
+
+  ply_buffer_t   *key_buffer;
+
+  ply_renderer_input_source_handler_t handler;
+  void           *user_data;
+};
+
+struct _ply_renderer_backend
+{
+  ply_event_loop_t *loop;
+  ply_console_t *console;
+  ply_terminal_t *terminal;
+
+  char *device_name;
+  int   device_fd;
+
+  ply_renderer_input_source_t input_source;
+  ply_renderer_head_t head;
+  ply_list_t *heads;
+
+  uint32_t red_bit_position;
+  uint32_t green_bit_position;
+  uint32_t blue_bit_position;
+  uint32_t alpha_bit_position;
+
+  uint32_t bits_for_red;
+  uint32_t bits_for_green;
+  uint32_t bits_for_blue;
+  uint32_t bits_for_alpha;
+
+  int32_t dither_red;
+  int32_t dither_green;
+  int32_t dither_blue;
+
+  unsigned int bytes_per_pixel;
+  unsigned int row_stride;
+
+  void (* flush_area) (ply_renderer_backend_t *backend,
+                       ply_renderer_head_t    *head,
+                       ply_rectangle_t        *area_to_flush);
+};
+
+ply_renderer_plugin_interface_t *ply_renderer_backend_get_interface (void);
+static void ply_renderer_head_redraw (ply_renderer_backend_t *backend,
+                                      ply_renderer_head_t    *head);
+
+static inline uint_fast32_t
+argb32_pixel_value_to_device_pixel_value (ply_renderer_backend_t *backend,
+                                          uint32_t                pixel_value)
+{
+  uint8_t r, g, b, a;
+  int orig_r, orig_g, orig_b, orig_a;
+  uint8_t new_r, new_g, new_b;
+  int i;
+
+  orig_a = pixel_value >> 24;
+  a = orig_a >> (8 - backend->bits_for_alpha);
+
+  orig_r = ((pixel_value >> 16) & 0xff) - backend->dither_red;
+  r = CLAMP (orig_r, 0, 255) >> (8 - backend->bits_for_red);
+
+  orig_g = ((pixel_value >> 8) & 0xff) - backend->dither_green;
+  g = CLAMP (orig_g, 0, 255) >> (8 - backend->bits_for_green);
+
+  orig_b = (pixel_value & 0xff) - backend->dither_blue;
+  b = CLAMP (orig_b, 0, 255) >> (8 - backend->bits_for_blue);
+
+  new_r = r << (8 - backend->bits_for_red);
+  new_g = g << (8 - backend->bits_for_green);
+  new_b = b << (8 - backend->bits_for_blue);
+
+  for (i = backend->bits_for_red; i < 8; i <<= 1)
+    new_r |= new_r >> i;
+
+  for (i = backend->bits_for_green; i < 8; i <<= 1)
+    new_g |= new_g >> i;
+
+  for (i = backend->bits_for_blue; i < 8; i <<= 1)
+    new_b |= new_b >> i;
+
+  backend->dither_red = new_r - orig_r;
+  backend->dither_green = new_g - orig_g;
+  backend->dither_blue = new_b - orig_b;
+
+  return ((a << backend->alpha_bit_position)
+          | (r << backend->red_bit_position)
+          | (g << backend->green_bit_position)
+          | (b << backend->blue_bit_position));
+}
+
+static void
+flush_area_to_any_device (ply_renderer_backend_t *backend,
+                          ply_renderer_head_t    *head,
+                          ply_rectangle_t        *area_to_flush)
+{
+  unsigned long row, column;
+  uint32_t *shadow_buffer;
+  char *row_backend;
+  size_t bytes_per_row;
+  unsigned long x1, y1, x2, y2;
+
+  x1 = area_to_flush->x;
+  y1 = area_to_flush->y;
+  x2 = x1 + area_to_flush->width;
+  y2 = y1 + area_to_flush->height;
+
+  bytes_per_row = area_to_flush->width * backend->bytes_per_pixel;
+  row_backend = malloc (backend->row_stride);
+  shadow_buffer = ply_pixel_buffer_get_argb32_data (backend->head.pixel_buffer);
+  for (row = y1; row < y2; row++)
+    {
+      unsigned long offset;
+
+      for (column = x1; column < x2; column++)
+        {
+          uint32_t pixel_value;
+          uint_fast32_t device_pixel_value;
+
+          pixel_value = shadow_buffer[row * head->area.width + column];
+
+          device_pixel_value = argb32_pixel_value_to_device_pixel_value (backend,
+                                                                         pixel_value);
+
+          memcpy (row_backend + column * backend->bytes_per_pixel,
+                  &device_pixel_value, backend->bytes_per_pixel);
+        }
+
+      offset = row * backend->row_stride + x1 * backend->bytes_per_pixel;
+      memcpy (head->map_address + offset, row_backend + x1 * backend->bytes_per_pixel,
+              area_to_flush->width * backend->bytes_per_pixel);
+    }
+  free (row_backend);
+}
+
+static void
+flush_area_to_xrgb32_device (ply_renderer_backend_t *backend,
+                             ply_renderer_head_t    *head,
+                             ply_rectangle_t        *area_to_flush)
+{
+  unsigned long x1, y1, x2, y2, y;
+  uint32_t *shadow_buffer;
+  char *dst, *src;
+
+  x1 = area_to_flush->x;
+  y1 = area_to_flush->y;
+  x2 = x1 + area_to_flush->width;
+  y2 = y1 + area_to_flush->height;
+
+  shadow_buffer = ply_pixel_buffer_get_argb32_data (backend->head.pixel_buffer);
+
+  dst = &head->map_address[y1 * backend->row_stride + x1 * backend->bytes_per_pixel];
+  src = (char *) &shadow_buffer[y1 * head->area.width + x1];
+
+  if (area_to_flush->width == backend->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 += backend->row_stride;
+      src += head->area.width * 4;
+    }
+}
+
+static ply_renderer_backend_t *
+create_backend (const char *device_name,
+                ply_terminal_t *terminal,
+                ply_console_t *console)
+{
+  ply_renderer_backend_t *backend;
+
+  backend = calloc (1, sizeof (ply_renderer_backend_t));
+
+  if (device_name != NULL)
+    backend->device_name = strdup (device_name);
+  else if (getenv ("FRAMEBUFFER") != NULL)
+    backend->device_name = strdup (getenv ("FRAMEBUFFER"));
+  else
+    backend->device_name =
+      strdup (PLY_FRAME_BUFFER_DEFAULT_FB_DEVICE_NAME);
+
+  backend->loop = ply_event_loop_get_default ();
+  backend->heads = ply_list_new ();
+  backend->input_source.key_buffer = ply_buffer_new ();
+  backend->console = console;
+  backend->terminal = terminal;
+
+  return backend;
+}
+
+static void
+initialize_head (ply_renderer_backend_t *backend,
+                 ply_renderer_head_t    *head)
+{
+  head->pixel_buffer = ply_pixel_buffer_new (head->area.width,
+                                             head->area.height);
+  ply_pixel_buffer_fill_with_color (backend->head.pixel_buffer, NULL,
+                                    0.0, 0.0, 0.0, 1.0);
+  ply_list_append_data (backend->heads, head);
+}
+
+static void
+uninitialize_head (ply_renderer_backend_t *backend,
+                   ply_renderer_head_t    *head)
+{
+  if (head->pixel_buffer != NULL)
+    {
+      ply_pixel_buffer_free (head->pixel_buffer);
+      head->pixel_buffer = NULL;
+
+      ply_list_remove_data (backend->heads, head);
+    }
+}
+
+static void
+destroy_backend (ply_renderer_backend_t *backend)
+{
+
+  free (backend->device_name);
+  uninitialize_head (backend, &backend->head);
+
+  ply_list_free (backend->heads);
+
+  free (backend);
+}
+
+static void
+on_active_vt_changed (ply_renderer_backend_t *backend)
+{
+  if (ply_console_get_active_vt (backend->console) !=
+      ply_terminal_get_vt_number (backend->terminal))
+    return;
+
+  if (backend->head.map_address != MAP_FAILED)
+    ply_renderer_head_redraw (backend, &backend->head);
+}
+
+static bool
+open_device (ply_renderer_backend_t *backend)
+{
+  backend->device_fd = open (backend->device_name, O_RDWR);
+
+  if (backend->device_fd < 0)
+    {
+      ply_trace ("could not open '%s': %m", backend->device_name);
+      return false;
+    }
+
+  ply_console_watch_for_active_vt_change (backend->console,
+                                          (ply_console_active_vt_changed_handler_t)
+                                          on_active_vt_changed,
+                                          backend);
+
+  return true;
+}
+
+static void
+close_device (ply_renderer_backend_t *backend)
+{
+
+  ply_console_stop_watching_for_active_vt_change (backend->console,
+                                                  (ply_console_active_vt_changed_handler_t)
+                                                  on_active_vt_changed,
+                                                  backend);
+  close (backend->device_fd);
+  backend->device_fd = -1;
+
+  backend->bytes_per_pixel = 0;
+  backend->head.area.x = 0;
+  backend->head.area.y = 0;
+  backend->head.area.width = 0;
+  backend->head.area.height = 0;
+}
+
+static const char const *get_visual_name (int visual)
+{
+  static const char const *visuals[] =
+    {
+      [FB_VISUAL_MONO01] = "FB_VISUAL_MONO01",
+      [FB_VISUAL_MONO10] = "FB_VISUAL_MONO10",
+      [FB_VISUAL_TRUECOLOR] = "FB_VISUAL_TRUECOLOR",
+      [FB_VISUAL_PSEUDOCOLOR] = "FB_VISUAL_PSEUDOCOLOR",
+      [FB_VISUAL_DIRECTCOLOR] = "FB_VISUAL_DIRECTCOLOR",
+      [FB_VISUAL_STATIC_PSEUDOCOLOR] = "FB_VISUAL_STATIC_PSEUDOCOLOR",
+      NULL
+    };
+  static char unknown[] = "invalid visual: -4294967295";
+
+  if (visual < FB_VISUAL_MONO01 || visual > FB_VISUAL_STATIC_PSEUDOCOLOR)
+    {
+      sprintf (unknown, "invalid visual: %d", visual);
+      return unknown;
+    }
+
+  return visuals[visual];
+}
+
+static bool
+query_device (ply_renderer_backend_t *backend)
+{
+  struct fb_var_screeninfo variable_screen_info;
+  struct fb_fix_screeninfo fixed_screen_info;
+
+  assert (backend != NULL);
+  assert (backend->device_fd >= 0);
+
+  if (ioctl (backend->device_fd, FBIOGET_VSCREENINFO, &variable_screen_info) < 0)
+    return false;
+
+  if (ioctl (backend->device_fd, FBIOGET_FSCREENINFO, &fixed_screen_info) < 0)
+    return false;
+
+  /* Normally the pixel is divided into channels between the color components.
+   * Each channel directly maps to a color channel on the hardware.
+   *
+   * There are some odd ball modes that use an indexed palette instead.  In
+   * those cases (pseudocolor, direct color, etc), the pixel value is just an
+   * index into a lookup table of the real color values.
+   *
+   * We don't support that.
+   */
+  if (fixed_screen_info.visual != FB_VISUAL_TRUECOLOR)
+    {
+      int rc = -1;
+      int i;
+      static const int depths[] = {32, 24, 16, 0};
+
+      ply_trace ("Visual was %s, trying to find usable mode.\n",
+                 get_visual_name (fixed_screen_info.visual));
+
+      for (i = 0; depths[i] != 0; i++)
+        {
+          variable_screen_info.bits_per_pixel = depths[i];
+          variable_screen_info.activate |= FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
+
+          rc = ioctl (backend->device_fd, FBIOPUT_VSCREENINFO, &variable_screen_info);
+          if (rc >= 0)
+            {
+              if (ioctl (backend->device_fd, FBIOGET_FSCREENINFO, &fixed_screen_info) < 0)
+                return false;
+
+              if (fixed_screen_info.visual == FB_VISUAL_TRUECOLOR)
+                break;
+            }
+        }
+
+      if (ioctl (backend->device_fd, FBIOGET_VSCREENINFO, &variable_screen_info) < 0)
+        return false;
+
+      if (ioctl (backend->device_fd, FBIOGET_FSCREENINFO, &fixed_screen_info) < 0)
+        return false;
+    }
+
+  if (fixed_screen_info.visual != FB_VISUAL_TRUECOLOR ||
+      variable_screen_info.bits_per_pixel < 16)
+    {
+      ply_trace ("Visual is %s; not using graphics\n",
+                 get_visual_name (fixed_screen_info.visual));
+      return false;
+    }
+
+  backend->head.area.x = variable_screen_info.xoffset;
+  backend->head.area.y = variable_screen_info.yoffset;
+  backend->head.area.width = variable_screen_info.xres;
+  backend->head.area.height = variable_screen_info.yres;
+
+  backend->red_bit_position = variable_screen_info.red.offset;
+  backend->bits_for_red = variable_screen_info.red.length;
+
+  backend->green_bit_position = variable_screen_info.green.offset;
+  backend->bits_for_green = variable_screen_info.green.length;
+
+  backend->blue_bit_position = variable_screen_info.blue.offset;
+  backend->bits_for_blue = variable_screen_info.blue.length;
+
+  backend->alpha_bit_position = variable_screen_info.transp.offset;
+  backend->bits_for_alpha = variable_screen_info.transp.length;
+
+  backend->bytes_per_pixel = variable_screen_info.bits_per_pixel >> 3;
+  backend->row_stride = fixed_screen_info.line_length;
+  backend->dither_red = 0;
+  backend->dither_green = 0;
+  backend->dither_blue = 0;
+
+  backend->head.size = backend->head.area.height * backend->row_stride;
+
+  if (backend->bytes_per_pixel == 4 &&
+      backend->red_bit_position == 16 && backend->bits_for_red == 8 &&
+      backend->green_bit_position == 8 && backend->bits_for_green == 8 &&
+      backend->blue_bit_position == 0 && backend->bits_for_blue == 8)
+    backend->flush_area = flush_area_to_xrgb32_device;
+  else
+    backend->flush_area = flush_area_to_any_device;
+
+  return true;
+
+}
+
+static bool
+map_to_device (ply_renderer_backend_t *backend)
+{
+  ply_renderer_head_t *head;
+
+  assert (backend != NULL);
+  assert (backend->device_fd >= 0);
+
+  head = &backend->head;
+  assert (head->size > 0);
+
+  head->map_address = mmap (NULL, head->size, PROT_WRITE,
+                            MAP_SHARED, backend->device_fd, 0);
+
+  if (head->map_address == MAP_FAILED)
+    return false;
+
+  initialize_head (backend, head);
+
+  return true;
+}
+
+static void
+unmap_from_device (ply_renderer_backend_t *backend)
+{
+  ply_renderer_head_t *head;
+
+  head = &backend->head;
+
+  uninitialize_head (backend, head);
+
+  if (head->map_address != MAP_FAILED)
+    {
+      munmap (head->map_address, head->size);
+      head->map_address = MAP_FAILED;
+    }
+}
+
+static void
+flush_head (ply_renderer_backend_t *backend,
+            ply_renderer_head_t    *head)
+{
+  ply_region_t *updated_region;
+  ply_list_t *areas_to_flush;
+  ply_list_node_t *node;
+  ply_pixel_buffer_t *pixel_buffer;
+
+  assert (backend != NULL);
+  assert (&backend->head == head);
+
+  ply_console_set_mode (backend->console, PLY_CONSOLE_MODE_GRAPHICS);
+  pixel_buffer = head->pixel_buffer;
+  updated_region = ply_pixel_buffer_get_updated_areas (pixel_buffer);
+  areas_to_flush = ply_region_get_rectangle_list (updated_region);
+
+  node = ply_list_get_first_node (areas_to_flush);
+  while (node != NULL)
+    {
+      ply_list_node_t *next_node;
+      ply_rectangle_t *area_to_flush;
+
+      area_to_flush = (ply_rectangle_t *) ply_list_node_get_data (node);
+
+      next_node = ply_list_get_next_node (areas_to_flush, node);
+
+      backend->flush_area (backend, head, area_to_flush);
+
+      node = next_node;
+    }
+
+  ply_region_clear (updated_region);
+}
+
+static void
+ply_renderer_head_redraw (ply_renderer_backend_t *backend,
+                          ply_renderer_head_t    *head)
+{
+  ply_region_t *region;
+
+  region = ply_pixel_buffer_get_updated_areas (head->pixel_buffer);
+
+  ply_region_add_rectangle (region, &head->area);
+
+  flush_head (backend, head);
+}
+
+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->head)
+    return NULL;
+
+  return backend->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 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));
+
+  terminal_fd = ply_terminal_get_fd (backend->terminal);
+
+  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,
+                                                                NULL, 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));
+
+  ply_event_loop_stop_watching_fd (backend->loop, input_source->terminal_input_watch);
+  input_source->terminal_input_watch = NULL;
+}
+
+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,
+      .map_to_device = map_to_device,
+      .unmap_from_device = unmap_from_device,
+      .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
+    };
+
+  return &plugin_interface;
+}
+/* vim: set ts=4 sw=4 et ai ci cino={.5s,^-2,+.5s,t0,g0,e-2,n-2,p2s,(0,=.5s,:.5s */