--- /dev/null
+/* 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 */