[branch-merge] Add x11 renderer plugin

This merges the "x11-renderer" branch to master.

In order to make debugging splash plugins easier,
and in order to make debugging multi-head renderering
possible on single head displays, Charlie wrote an
X11 rendering plugin.

This plugin displays plymouth in a running X session,
instead of on the console.

It currently only supports graphical plugins.  At some
point it may grow support for text plugins, too, using
vte.  That will take some reworking of the plymouth
daemon core.

This could some day serve as a basis for providing a
graphical theme chooser application (like
gnome-screensaver-preferences), although it's not clear
yet that's a good idea.

Right now, it's a great debugging and development tool,
though.
This commit is contained in:
Ray Strode 2009-10-04 10:47:36 -04:00
commit a29f25bc6c
11 changed files with 576 additions and 18 deletions

View file

@ -216,6 +216,7 @@ AC_OUTPUT([Makefile
src/plugins/renderers/Makefile
src/plugins/renderers/frame-buffer/Makefile
src/plugins/renderers/drm/Makefile
src/plugins/renderers/x11/Makefile
src/plugins/splash/Makefile
src/plugins/splash/throbgress/Makefile
src/plugins/splash/fade-throbber/Makefile

View file

@ -169,8 +169,14 @@ on_keyboard_input (ply_boot_splash_t *splash,
case KEY_CTRL_T:
ply_trace ("toggle text mode!");
splash->should_force_text_mode = !splash->should_force_text_mode;
ply_console_force_text_mode (splash->console,
splash->should_force_text_mode);
if (splash->should_force_text_mode)
{
ply_console_set_mode (splash->console, PLY_CONSOLE_MODE_TEXT);
ply_console_ignore_mode_changes (splash->console, true);
}
else
ply_console_ignore_mode_changes (splash->console, false);
ply_trace ("text mode toggled!");
return;

View file

@ -68,7 +68,7 @@ struct _ply_console
uint32_t is_open : 1;
uint32_t is_watching_for_vt_changes : 1;
uint32_t should_force_text_mode : 1;
uint32_t should_ignore_mode_changes : 1;
};
static bool ply_console_open_device (ply_console_t *console);
@ -106,8 +106,8 @@ ply_console_set_mode (ply_console_t *console,
assert (console != NULL);
assert (mode == PLY_CONSOLE_MODE_TEXT || mode == PLY_CONSOLE_MODE_GRAPHICS);
if (console->should_force_text_mode)
mode = PLY_CONSOLE_MODE_TEXT;
if (console->should_ignore_mode_changes)
return;
switch (mode)
{
@ -124,10 +124,10 @@ ply_console_set_mode (ply_console_t *console,
}
void
ply_console_force_text_mode (ply_console_t *console,
bool should_force)
ply_console_ignore_mode_changes (ply_console_t *console,
bool should_ignore)
{
console->should_force_text_mode = should_force;
console->should_ignore_mode_changes = should_ignore;
}
static void

View file

@ -49,8 +49,8 @@ void ply_console_close (ply_console_t *console);
void ply_console_set_mode (ply_console_t *console,
ply_console_mode_t mode);
void ply_console_force_text_mode (ply_console_t *console,
bool should_force);
void ply_console_ignore_mode_changes (ply_console_t *console,
bool should_ignore);
int ply_console_get_fd (ply_console_t *console);
int ply_console_get_active_vt (ply_console_t *console);

View file

@ -220,6 +220,7 @@ ply_renderer_open (ply_renderer_t *renderer)
*/
const char *known_plugins[] =
{
PLYMOUTH_PLUGIN_PATH "renderers/x11.so",
PLYMOUTH_PLUGIN_PATH "renderers/drm.so",
PLYMOUTH_PLUGIN_PATH "renderers/frame-buffer.so",
NULL
@ -299,10 +300,6 @@ ply_renderer_flush_head (ply_renderer_t *renderer,
assert (renderer->plugin_interface != NULL);
assert (head != NULL);
if (ply_console_get_active_vt (renderer->console) !=
ply_terminal_get_vt_number (renderer->terminal))
return;
renderer->plugin_interface->flush_head (renderer->backend, head);
}

View file

@ -989,9 +989,6 @@ add_default_displays_and_keyboard (state_t *state)
return;
}
ply_console_set_active_vt (state->console,
ply_terminal_get_vt_number (terminal));
renderer = ply_renderer_new (NULL, terminal, state->console);
if (!ply_renderer_open (renderer))

View file

@ -1,2 +1,2 @@
SUBDIRS = frame-buffer drm
SUBDIRS = frame-buffer drm x11
MAINTAINERCLEANFILES = Makefile.in

View file

@ -783,6 +783,9 @@ map_to_device (ply_renderer_backend_t *backend)
node = next_node;
}
ply_console_set_active_vt (backend->console,
ply_terminal_get_vt_number (backend->terminal));
return head_mapped;
}
@ -922,6 +925,10 @@ flush_head (ply_renderer_backend_t *backend,
assert (backend != NULL);
if (ply_console_get_active_vt (backend->console) !=
ply_terminal_get_vt_number (backend->terminal))
return;
ply_console_set_mode (backend->console, PLY_CONSOLE_MODE_GRAPHICS);
ply_terminal_set_unbuffered_input (backend->terminal);
pixel_buffer = head->pixel_buffer;

View file

@ -491,6 +491,9 @@ map_to_device (ply_renderer_backend_t *backend)
initialize_head (backend, head);
ply_console_set_active_vt (backend->console,
ply_terminal_get_vt_number (backend->terminal));
return true;
}
@ -522,6 +525,10 @@ flush_head (ply_renderer_backend_t *backend,
assert (backend != NULL);
assert (&backend->head == head);
if (ply_console_get_active_vt (backend->console) !=
ply_terminal_get_vt_number (backend->terminal))
return;
ply_console_set_mode (backend->console, PLY_CONSOLE_MODE_GRAPHICS);
ply_terminal_set_unbuffered_input (backend->terminal);
pixel_buffer = head->pixel_buffer;

View file

@ -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 = x11.la
x11_la_CFLAGS = $(GTK_CFLAGS) $(PLYMOUTH_CFLAGS)
x11_la_LDFLAGS = -module -avoid-version -export-dynamic
x11_la_LIBADD = $(PLYMOUTH_LIBS) \
$(GTK_LIBS) \
../../../libply/libply.la \
../../../libplybootsplash/libplybootsplash.la
x11_la_SOURCES = $(srcdir)/plugin.c
MAINTAINERCLEANFILES = Makefile.in

View file

@ -0,0 +1,523 @@
/* plugin.c - frame-backend renderer plugin
*
* Copyright (C) 2006-2009 Red Hat, Inc.
* 2009 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 <sys/ipc.h>
#include <sys/shm.h>
#include <values.h>
#include <unistd.h>
#include <linux/fb.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.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"
struct _ply_renderer_head
{
ply_renderer_backend_t *backend;
ply_pixel_buffer_t *pixel_buffer;
ply_rectangle_t area;
GtkWidget *window;
GdkPixmap *pixmap;
cairo_surface_t *image;
};
struct _ply_renderer_input_source
{
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_renderer_input_source_t input_source;
ply_list_t *heads;
ply_console_t *console;
ply_fd_watch_t *display_watch;
};
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 gboolean on_motion_notify_event (GtkWidget *widget,
GdkEventMotion *event,
gpointer user_data);
static gboolean on_key_event (GtkWidget *widget,
GdkEventKey *event,
gpointer user_data);
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));
backend->loop = ply_event_loop_get_default ();
backend->heads = ply_list_new ();
backend->input_source.key_buffer = ply_buffer_new ();
backend->console = console;
return backend;
}
static void
destroy_backend (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);
free (head);
node = next_node;
}
ply_list_free (backend->heads);
ply_buffer_free (backend->input_source.key_buffer);
free (backend);
}
static void
on_display_event (ply_renderer_backend_t *backend)
{
while (gtk_events_pending ())
gtk_main_iteration ();
}
static bool
open_device (ply_renderer_backend_t *backend)
{
Display *display;
int display_fd;
if (!gtk_init_check (0, NULL))
return false;
display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
display_fd = ConnectionNumber (display);
backend->display_watch = ply_event_loop_watch_fd (backend->loop,
display_fd,
PLY_EVENT_LOOP_FD_STATUS_HAS_DATA,
(ply_event_handler_t) on_display_event,
NULL,
backend);
return true;
}
static void
close_device (ply_renderer_backend_t *backend)
{
ply_event_loop_stop_watching_fd (backend->loop, backend->display_watch);
backend->display_watch = NULL;
return;
}
static bool
query_device (ply_renderer_backend_t *backend)
{
ply_renderer_head_t *head;
assert (backend != NULL);
if (ply_list_get_first_node (backend->heads) == NULL)
{
head = calloc (1, sizeof (ply_renderer_head_t));
head->backend = backend;
head->area.x = 0;
head->area.y = 0;
head->area.width = 800; /* FIXME hardcoded */
head->area.height = 600;
head->pixmap = gdk_pixmap_new (NULL,
head->area.width,
head->area.height,
24);
ply_list_append_data (backend->heads, head);
head = calloc (1, sizeof (ply_renderer_head_t));
head->backend = backend;
head->area.x = 800;
head->area.y = 0;
head->area.width = 640; /* FIXME hardcoded */
head->area.height = 480;
head->pixmap = gdk_pixmap_new (NULL,
head->area.width,
head->area.height,
24);
ply_list_append_data (backend->heads, head);
}
return true;
}
static gboolean
on_window_destroy (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
return TRUE;
}
static bool
map_to_device (ply_renderer_backend_t *backend)
{
ply_list_node_t *node;
assert (backend != NULL);
/* Prevent other parts of plymouth from trying to use
* the console, since X draws to it.
*/
ply_console_ignore_mode_changes (backend->console, true);
node = ply_list_get_first_node (backend->heads);
while (node != NULL)
{
ply_list_node_t *next_node;
ply_renderer_head_t *head;
uint32_t *shadow_buffer;
head = (ply_renderer_head_t *) ply_list_node_get_data (node);
next_node = ply_list_get_next_node (backend->heads, node);
head->pixel_buffer = ply_pixel_buffer_new (head->area.width, head->area.height);
head->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_resizable (GTK_WINDOW (head->window), FALSE);
gtk_widget_set_size_request (head->window,
head->area.width,
head->area.height);
shadow_buffer = ply_pixel_buffer_get_argb32_data (head->pixel_buffer);
head->image = cairo_image_surface_create_for_data ((unsigned char *) shadow_buffer,
CAIRO_FORMAT_ARGB32,
head->area.width, head->area.height,
head->area.width * 4);
gtk_widget_set_app_paintable (head->window, TRUE);
gtk_widget_show_all (head->window);
gdk_window_set_back_pixmap (head->window->window, head->pixmap, FALSE);
gdk_window_set_decorations (head->window->window, GDK_DECOR_BORDER);
gtk_window_move (GTK_WINDOW (head->window), head->area.x, head->area.y);
gtk_widget_add_events (head->window, GDK_BUTTON1_MOTION_MASK);
g_signal_connect (head->window, "motion-notify-event",
G_CALLBACK (on_motion_notify_event),
head);
g_signal_connect (head->window, "key-press-event",
G_CALLBACK (on_key_event),
&backend->input_source);
g_signal_connect (head->window, "delete-event",
G_CALLBACK (on_window_destroy),
NULL);
ply_renderer_head_redraw (backend, head);
node = next_node;
}
return true;
}
static void
unmap_from_device (ply_renderer_backend_t *backend)
{
ply_list_node_t *node;
assert (backend != NULL);
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);
gtk_widget_destroy (head->window);
head->window = NULL;
ply_pixel_buffer_free (head->pixel_buffer);
head->pixel_buffer = NULL;
cairo_surface_destroy (head->image);
head->image = NULL;
node = next_node;
}
ply_console_ignore_mode_changes (backend->console, false);
}
static void
flush_area_to_device (ply_renderer_backend_t *backend,
ply_renderer_head_t *head,
ply_rectangle_t *area_to_flush,
cairo_t *cr)
{
cairo_save (cr);
cairo_rectangle (cr,
area_to_flush->x,
area_to_flush->y,
area_to_flush->width,
area_to_flush->height);
cairo_clip (cr);
cairo_set_source_surface (cr, head->image, 0, 0);
cairo_paint (cr);
cairo_restore (cr);
}
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;
cairo_t *cr;
assert (backend != NULL);
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);
cr = gdk_cairo_create (head->pixmap);
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);
flush_area_to_device (backend, head, area_to_flush, cr);
gdk_window_clear_area (head->window->window,
area_to_flush->x,
area_to_flush->y,
area_to_flush->width,
area_to_flush->height);
node = next_node;
}
ply_region_clear (updated_region);
cairo_destroy (cr);
/* Force read-back to make sure plymouth isn't saturating the
* X server with requests
*/
g_object_unref (gdk_drawable_get_image (GDK_DRAWABLE (head->pixmap),
0, 0, 1, 1));
}
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 != 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 gboolean
on_motion_notify_event (GtkWidget *widget,
GdkEventMotion *event,
gpointer user_data)
{
ply_renderer_head_t *head = user_data;
gtk_window_begin_move_drag (GTK_WINDOW (head->window), 1,
event->x_root, event->y_root, event->time);
return FALSE;
}
static gboolean
on_key_event (GtkWidget *widget,
GdkEventKey *event,
gpointer user_data)
{
ply_renderer_input_source_t *input_source = user_data;
if (event->keyval == GDK_Return) /* Enter */
{
ply_buffer_append_bytes (input_source->key_buffer, "\r", 1);
}
else if (event->keyval == GDK_Escape) /* Esc */
{
ply_buffer_append_bytes (input_source->key_buffer, "\033", 1);
}
else if (event->keyval == GDK_BackSpace) /* Backspace */
{
ply_buffer_append_bytes (input_source->key_buffer, "\177", 1);
}
else
{
gchar bytes[7];
int byte_count;
guint32 unichar;
unichar = gdk_keyval_to_unicode (event->keyval);
byte_count = g_unichar_to_utf8 (unichar, bytes);
if (bytes[0] != 0)
ply_buffer_append_bytes (input_source->key_buffer, bytes, byte_count);
else
ply_trace ("unknown GDK key: 0x%X \"%s\"",
event->keyval,
gdk_keyval_name (event->keyval));
}
if (input_source->handler != NULL)
input_source->handler (input_source->user_data, input_source->key_buffer, input_source);
return FALSE;
}
static bool
open_input_source (ply_renderer_backend_t *backend,
ply_renderer_input_source_t *input_source)
{
assert (backend != NULL);
assert (has_input_source (backend, 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_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 */