[PATCH v2] fbdev: Add an fbdev compositor backend using pixman and evdev

Philip Withnall philip at tecnocode.co.uk
Fri Jan 18 03:07:20 PST 2013


Here's an updated version of the fbdev backend, with fixes made in
response to the helpful comments by David, Pekka and Vasily. The main
differences in this version are improved error handling, better support
for different frame buffer pixel formats, and various small bits of
cleanup.

This patch is a squashed version of various cleanup commits (listed
individually here for easier review:
https://gitorious.org/~pwithnall/weston/pwithnalls-weston/commits/compositor-fbdev),
and shouldn't be applied in its current state (the commit message is a
mess). Once reviewers are happy with it I can squash the commits
together properly and tidy up the commit message.


Initial version of a frame buffer backend using pixman to render to fbdev.
This has been tested against nouveaufb but nothing else. Much of the code
came straight from the rpi backend (and copyright has been attributed
accordingly).

The behaviour of this backend on less modern frame buffers has yet to be
tested.

fbdev: Remove DBG macro

It was only used once, in a function off the critical path.

fbdev: Remove unnecessary forward declarations

fbdev: Calculate refresh rate from FB metadata and synchronise to it

Instead of arbitrarily choosing 60 Hz as the refresh rate, it can be
calculated from the frame buffer's metadata. We now also finish every frame
synchronised with the refresh rate, rather than on a 100 Hz fixed clock.

fbdev: Use O_CLOEXEC and remove unnecessary flags from mmap()

fbdev: Improve error handling

Handle failure from many more functions the fbdev backend calls.

fbdev: Note that udev should be used to enumerate frame buffer devices

…but currently isn't.

fbdev: Build a pixman format from the frame buffer metadata

Rather than hard-coding support for x8r8g8b8, build a suitable pixman format
from the frame buffer's metadata. This doesn't support the full range of
frame buffer formats, but does support varying BPPs of RGBA and ARGB. That
should be enough for now.

The following are not currently supported:
 • FOURCC
 • Non-packed formats (interleaved, planes, etc.)
 • Non-true-colour formats (monochrome, greyscale, etc.)
 • Big-endian formats (with component MSBs on the right)
 • Non-RGBA and non-ARGB formats

Signed-off-by: Philip Withnall <philip at tecnocode.co.uk>
---
 configure.ac           |  10 +
 src/Makefile.am        |  26 +-
 src/compositor-fbdev.c | 957 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 992 insertions(+), 1 deletion(-)
 create mode 100644 src/compositor-fbdev.c

diff --git a/configure.ac b/configure.ac
index 64505dd..57a444b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -154,6 +154,16 @@ fi
 AM_CONDITIONAL(INSTALL_RPI_COMPOSITOR, test "x$have_bcm_host" = "xyes")
 

+AC_ARG_ENABLE([fbdev-compositor], [  --enable-fbdev-compositor],,
+              enable_fbdev_compositor=yes)
+AM_CONDITIONAL([ENABLE_FBDEV_COMPOSITOR],
+               [test x$enable_fbdev_compositor = xyes])
+AS_IF([test x$enable_fbdev_compositor = xyes], [
+  AC_DEFINE([BUILD_FBDEV_COMPOSITOR], [1], [Build the fbdev compositor])
+  PKG_CHECK_MODULES([FBDEV_COMPOSITOR], [libudev >= 136 mtdev >= 1.1.0])
+])
+
+
 AC_ARG_WITH(cairo-glesv2,
             AS_HELP_STRING([--with-cairo-glesv2],
                            [Use GLESv2 cairo instead of full GL]))
diff --git a/src/Makefile.am b/src/Makefile.am
index cbfa911..efa819d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -84,7 +84,8 @@ module_LTLIBRARIES =				\
 	$(x11_backend)				\
 	$(drm_backend)				\
 	$(wayland_backend)			\
-	$(headless_backend)
+	$(headless_backend)			\
+	$(fbdev_backend)
 
 # Do not install, since the binary produced via autotools is unusable.
 # The real backend is built by the Android build system.
@@ -195,6 +196,29 @@ headless_backend_la_CFLAGS =			\
 headless_backend_la_SOURCES = compositor-headless.c
 endif
 
+if ENABLE_FBDEV_COMPOSITOR
+fbdev_backend = fbdev-backend.la
+fbdev_backend_la_LDFLAGS = -module -avoid-version
+fbdev_backend_la_LIBADD = \
+	$(COMPOSITOR_LIBS) \
+	$(FBDEV_COMPOSITOR_LIBS) \
+	../shared/libshared.la \
+	$(NULL)
+fbdev_backend_la_CFLAGS = \
+	$(COMPOSITOR_CFLAGS) \
+	$(FBDEV_COMPOSITOR_CFLAGS) \
+	$(PIXMAN_CFLAGS) \
+	$(GCC_CFLAGS) \
+	$(NULL)
+fbdev_backend_la_SOURCES = \
+	compositor-fbdev.c \
+	tty.c \
+	evdev.c \
+	evdev.h \
+	evdev-touchpad.c \
+	$(NULL)
+endif
+
 if ENABLE_DESKTOP_SHELL
 desktop_shell = desktop-shell.la
 desktop_shell_la_LDFLAGS = -module -avoid-version
diff --git a/src/compositor-fbdev.c b/src/compositor-fbdev.c
new file mode 100644
index 0000000..891a4db
--- /dev/null
+++ b/src/compositor-fbdev.c
@@ -0,0 +1,957 @@
+/*
+ * Copyright © 2008-2011 Kristian Høgsberg
+ * Copyright © 2011 Intel Corporation
+ * Copyright © 2012 Raspberry Pi Foundation
+ * Copyright © 2013 Philip Withnall
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission.  The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <linux/fb.h>
+
+#include <libudev.h>
+
+#include "config.h"
+
+#include "compositor.h"
+#include "pixman-renderer.h"
+#include "evdev.h"
+
+struct fbdev_compositor {
+	struct weston_compositor base;
+	uint32_t prev_state;
+
+	struct udev *udev;
+	struct tty *tty;
+};
+
+struct fbdev_output {
+	struct fbdev_compositor *compositor;
+	struct weston_output base;
+
+	struct weston_mode mode;
+	struct wl_event_source *finish_frame_timer;
+
+	/* Frame buffer details. */
+	struct fb_fix_screeninfo fixed_info;
+	struct fb_var_screeninfo variable_info;
+	void *fb;
+	size_t fb_len;
+
+	/* pixman details. */
+	pixman_image_t *hw_surface;
+	pixman_image_t *shadow_surface;
+	void *shadow_buf;
+	uint8_t depth;
+};
+
+struct fbdev_seat {
+	struct weston_seat base;
+	struct wl_list devices_list;
+
+	struct udev_monitor *udev_monitor;
+	struct wl_event_source *udev_monitor_source;
+	char *seat_id;
+};
+
+struct fbdev_parameters {
+	int tty;
+	char *device;
+};
+
+static inline struct fbdev_output *
+to_fbdev_output(struct weston_output *base)
+{
+	return container_of(base, struct fbdev_output, base);
+}
+
+static inline struct fbdev_seat *
+to_fbdev_seat(struct weston_seat *base)
+{
+	return container_of(base, struct fbdev_seat, base);
+}
+
+static inline struct fbdev_compositor *
+to_fbdev_compositor(struct weston_compositor *base)
+{
+	return container_of(base, struct fbdev_compositor, base);
+}
+
+static void
+fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage)
+{
+	struct fbdev_output *output = to_fbdev_output(base);
+	struct weston_compositor *ec = output->base.compositor;
+	pixman_box32_t *rects;
+	int nrects, i, src_x, src_y, x1, y1, x2, y2, width, height;
+
+	/* Repaint the damaged region onto the back buffer. */
+	pixman_renderer_output_set_buffer(base, output->shadow_surface);
+	ec->renderer->repaint_output(base, damage);
+
+	/* Transform and composite onto the frame buffer. */
+	width = pixman_image_get_width(output->shadow_surface);
+	height = pixman_image_get_height(output->shadow_surface);
+	rects = pixman_region32_rectangles(damage, &nrects);
+
+	for (i = 0; i < nrects; i++) {
+		switch (base->transform) {
+		default:
+		case WL_OUTPUT_TRANSFORM_NORMAL:
+			x1 = rects[i].x1;
+			x2 = rects[i].x2;
+			y1 = rects[i].y1;
+			y2 = rects[i].y2;
+			break;
+		case WL_OUTPUT_TRANSFORM_180:
+			x1 = width - rects[i].x2;
+			x2 = width - rects[i].x1;
+			y1 = height - rects[i].y2;
+			y2 = height - rects[i].y1;
+			break;
+		case WL_OUTPUT_TRANSFORM_90:
+			x1 = height - rects[i].y2;
+			x2 = height - rects[i].y1;
+			y1 = rects[i].x1;
+			y2 = rects[i].x2;
+			break;
+		case WL_OUTPUT_TRANSFORM_270:
+			x1 = rects[i].y1;
+			x2 = rects[i].y2;
+			y1 = width - rects[i].x2;
+			y2 = width - rects[i].x1;
+			break;
+		}
+		src_x = x1;
+		src_y = y1;
+
+		pixman_image_composite32(PIXMAN_OP_SRC,
+			output->shadow_surface, /* src */
+			NULL /* mask */,
+			output->hw_surface, /* dest */
+			src_x, src_y, /* src_x, src_y */
+			0, 0, /* mask_x, mask_y */
+			x1, y1, /* dest_x, dest_y */
+			x2 - x1, /* width */
+			y2 - y1 /* height */);
+	}
+
+	/* Update the damage region. */
+	pixman_region32_subtract(&ec->primary_plane.damage,
+	                         &ec->primary_plane.damage, damage);
+
+	/* Schedule the end of the frame. We do not sync this to the frame
+	 * buffer clock because users who want that should be using the DRM
+	 * compositor. FBIO_WAITFORVSYNC blocks and FB_ACTIVATE_VBL requires
+	 * panning, which is broken in most kernel drivers.
+	 *
+	 * Finish the frame synchronised to the specified refresh rate. The
+	 * refresh rate is given in mHz and the interval in ms. */
+	wl_event_source_timer_update(output->finish_frame_timer,
+	                             1000000 / output->mode.refresh);
+}
+
+static int
+finish_frame_handler(void *data)
+{
+	struct fbdev_output *output = data;
+	uint32_t msec;
+	struct timeval tv;
+
+	gettimeofday(&tv, NULL);
+	msec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
+	weston_output_finish_frame(&output->base, msec);
+
+	return 1;
+}
+
+static int
+fbdev_frame_buffer_create(struct fbdev_output *output, const char *fb_dev)
+{
+	int retval = -1;
+	int fd = -1;
+
+	/* Open the frame buffer device. */
+	fd = open (fb_dev, O_RDWR | O_CLOEXEC);
+	if (fd < 0) {
+		weston_log("Failed to open frame buffer device ‘%s’: %s\n",
+		           fb_dev, strerror(errno));
+		goto err;
+	}
+
+	/* Grab the screen info. */
+	if (ioctl(fd, FBIOGET_FSCREENINFO, &output->fixed_info) < 0 ||
+	    ioctl(fd, FBIOGET_VSCREENINFO, &output->variable_info) < 0) {
+		weston_log("Failed to get frame buffer info: %s\n",
+		           strerror(errno));
+		goto err;
+	}
+
+	/* Map the frame buffer. Write-only mode, since we don't want to read
+	 * anything back (because it's slow). */
+	output->fb = mmap (NULL, output->fixed_info.smem_len,
+	                   PROT_WRITE, MAP_SHARED, fd, 0);
+	if (output->fb == MAP_FAILED) {
+		weston_log("Failed to mmap frame buffer: %s\n",
+		           strerror(errno));
+		goto err;
+	}
+
+	/* Success! */
+	output->fb_len = output->fixed_info.smem_len;
+	retval = 0;
+
+err:
+	if (fd >= 0)
+		close (fd);
+
+	return retval;
+}
+
+static void
+fbdev_frame_buffer_destroy(struct fbdev_output *output)
+{
+	if (munmap(output->fb, output->fb_len) < 0)
+		weston_log("Failed to munmap frame buffer: %s\n",
+		           strerror(errno));
+
+	output->fb = NULL;
+	output->fb_len = 0;
+}
+
+static pixman_format_code_t
+calculate_pixman_format(struct fb_var_screeninfo *vinfo,
+                        struct fb_fix_screeninfo *finfo)
+{
+	/* Calculate the pixman format supported by the frame buffer from the
+	 * buffer's metadata. Return 0 if no known pixman format is supported
+	 * (since this has depth 0 it's guaranteed to not conflict with any
+	 * actual pixman format).
+	 *
+	 * Documentation on the vinfo and finfo structures:
+	 *    http://www.mjmwired.net/kernel/Documentation/fb/api.txt
+	 *
+	 * TODO: Try a bit harder to support other formats, including setting
+	 * the preferred format in the hardware. */
+	int type;
+
+	weston_log("Calculating pixman format from:\n"
+	           STAMP_SPACE " - type: %i (aux: %i)\n"
+	           STAMP_SPACE " - visual: %i\n"
+	           STAMP_SPACE " - bpp: %i (grayscale: %i)\n"
+	           STAMP_SPACE " - red: offset: %i, length: %i, MSB: %i\n"
+	           STAMP_SPACE " - green: offset: %i, length: %i, MSB: %i\n"
+	           STAMP_SPACE " - blue: offset: %i, length: %i, MSB: %i\n"
+	           STAMP_SPACE " - transp: offset: %i, length: %i, MSB: %i\n",
+	           finfo->type, finfo->type_aux, finfo->visual,
+	           vinfo->bits_per_pixel, vinfo->grayscale,
+	           vinfo->red.offset, vinfo->red.length, vinfo->red.msb_right,
+	           vinfo->green.offset, vinfo->green.length,
+	           vinfo->green.msb_right,
+	           vinfo->blue.offset, vinfo->blue.length,
+	           vinfo->blue.msb_right,
+	           vinfo->transp.offset, vinfo->transp.length,
+	           vinfo->transp.msb_right);
+
+	/* We only handle packed formats at the moment. */
+	if (finfo->type != FB_TYPE_PACKED_PIXELS)
+		return 0;
+
+	/* We only handle true-colour frame buffers at the moment. */
+	if (finfo->visual != FB_VISUAL_TRUECOLOR || vinfo->grayscale != 0)
+		return 0;
+
+	/* We only support formats with MSBs on the left. */
+	if (vinfo->red.msb_right != 0 || vinfo->green.msb_right != 0 ||
+	    vinfo->blue.msb_right != 0)
+		return 0;
+
+	/* Work out the format type from the offsets. We only support RGBA and
+	 * ARGB at the moment. */
+	type = PIXMAN_TYPE_OTHER;
+
+	if ((vinfo->transp.offset >= vinfo->red.offset ||
+	     vinfo->transp.length == 0) &&
+	    vinfo->red.offset >= vinfo->green.offset &&
+	    vinfo->green.offset >= vinfo->blue.offset)
+		type = PIXMAN_TYPE_ARGB;
+	else if (vinfo->red.offset >= vinfo->green.offset &&
+	         vinfo->green.offset >= vinfo->blue.offset &&
+	         vinfo->blue.offset >= vinfo->transp.offset)
+		type = PIXMAN_TYPE_RGBA;
+
+	if (type == PIXMAN_TYPE_OTHER)
+		return 0;
+
+	/* Build the format. */
+	return PIXMAN_FORMAT(vinfo->bits_per_pixel, type,
+	                     vinfo->transp.length,
+	                     vinfo->red.length,
+	                     vinfo->green.length,
+	                     vinfo->blue.length);
+}
+
+static int
+calculate_refresh_rate(struct fb_var_screeninfo *vinfo)
+{
+	uint64_t quot;
+
+	/* Calculate monitor refresh rate. Default is 60 Hz. Units are mHz. */
+	quot = (vinfo->upper_margin + vinfo->lower_margin + vinfo->yres);
+	quot *= (vinfo->left_margin + vinfo->right_margin + vinfo->xres);
+	quot *= vinfo->pixclock;
+
+	if (quot > 0) {
+		uint64_t refresh_rate;
+
+		refresh_rate = 1000000000000000LLU / quot;
+		if (refresh_rate > 200000)
+			refresh_rate = 200000; /* cap at 200 Hz */
+
+		return refresh_rate;
+	}
+
+	return 60 * 1000; /* default to 60 Hz */
+}
+
+static void fbdev_output_destroy(struct weston_output *base);
+
+static int
+fbdev_output_create(struct fbdev_compositor *compositor,
+                    struct fbdev_parameters *param)
+{
+	struct fbdev_output *output;
+	pixman_transform_t transform;
+	int shadow_width, shadow_height;
+	int width, height;
+	pixman_format_code_t pixman_format;
+	struct wl_event_loop *loop;
+
+	output = calloc(1, sizeof *output);
+	if (!output)
+		return -1;
+
+	output->compositor = compositor;
+
+	/* Create the frame buffer. */
+	if (fbdev_frame_buffer_create(output, param->device) < 0) {
+		weston_log("Creating frame buffer failed.\n");
+		goto out_free;
+	}
+
+	pixman_format = calculate_pixman_format(&output->variable_info, &output->fixed_info);
+	if (pixman_format == 0) {
+		weston_log("Frame buffer uses an unsupported format.\n");
+		goto out_fb;
+	}
+
+	output->base.repaint = fbdev_output_repaint;
+	output->base.destroy = fbdev_output_destroy;
+	output->base.assign_planes = NULL;
+	output->base.set_backlight = NULL;
+	output->base.set_dpms = NULL;
+	output->base.switch_mode = NULL;
+
+	/* only one static mode in list */
+	output->mode.flags =
+		WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED;
+	output->mode.width = output->variable_info.xres;
+	output->mode.height = output->variable_info.yres;
+	output->mode.refresh = calculate_refresh_rate(&output->variable_info);
+	wl_list_init(&output->base.mode_list);
+	wl_list_insert(&output->base.mode_list, &output->mode.link);
+
+	output->base.current = &output->mode;
+	output->base.origin = &output->mode;
+	output->base.subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
+	output->base.make = "fbdev";
+	output->base.model = output->fixed_info.id;
+
+	weston_output_init(&output->base, &compositor->base,
+	                   0, 0, output->variable_info.width,
+	                   output->variable_info.height,
+	                   WL_OUTPUT_TRANSFORM_NORMAL);
+
+	/* Create a pixman image to wrap the memory mapped frame buffer. */
+	width = output->variable_info.xres;
+	height = output->variable_info.yres;
+
+	output->hw_surface =
+		pixman_image_create_bits(pixman_format, width, height,
+		                         output->fb,
+		                         output->fixed_info.line_length);
+	if (output->hw_surface == NULL) {
+		weston_log("Failed to create surface for frame buffer.\n");
+		goto out_output;
+	}
+
+	pixman_transform_init_identity(&transform);
+	switch (output->base.transform) {
+	default:
+	case WL_OUTPUT_TRANSFORM_NORMAL:
+		shadow_width = width;
+		shadow_height = height;
+		pixman_transform_rotate(&transform,
+			NULL, 0, 0);
+		pixman_transform_translate(&transform, NULL,
+			0, 0);
+		break;
+	case WL_OUTPUT_TRANSFORM_180:
+		shadow_width = width;
+		shadow_height = height;
+		pixman_transform_rotate(&transform,
+			NULL, -pixman_fixed_1, 0);
+		pixman_transform_translate(NULL, &transform,
+			pixman_int_to_fixed(shadow_width),
+			pixman_int_to_fixed(shadow_height));
+		break;
+	case WL_OUTPUT_TRANSFORM_270:
+		shadow_width = height;
+		shadow_height = width;
+		pixman_transform_rotate(&transform,
+			NULL, 0, pixman_fixed_1);
+		pixman_transform_translate(&transform,
+			NULL,
+			pixman_int_to_fixed(shadow_width),
+			0);
+		break;
+	case WL_OUTPUT_TRANSFORM_90:
+		shadow_width = height;
+		shadow_height = width;
+		pixman_transform_rotate(&transform,
+			NULL, 0, -pixman_fixed_1);
+		pixman_transform_translate(&transform,
+			NULL,
+			0,
+			pixman_int_to_fixed(shadow_height));
+		break;
+	}
+
+	output->shadow_buf = malloc(width * height * (output->variable_info.bits_per_pixel / 8));
+	output->shadow_surface =
+		pixman_image_create_bits(pixman_format, shadow_width,
+		                         shadow_height, output->shadow_buf,
+		                         shadow_width * (output->variable_info.bits_per_pixel / 8));
+	if (output->shadow_buf == NULL || output->shadow_surface == NULL) {
+		weston_log("Failed to create surface for frame buffer.\n");
+		goto out_hw_surface;
+	}
+
+	/* No need in transform for normal output */
+	if (output->base.transform != WL_OUTPUT_TRANSFORM_NORMAL)
+		pixman_image_set_transform(output->shadow_surface, &transform);
+
+	if (pixman_renderer_output_create(&output->base) < 0)
+		goto out_shadow_surface;
+
+	loop = wl_display_get_event_loop(compositor->base.wl_display);
+	output->finish_frame_timer =
+		wl_event_loop_add_timer(loop, finish_frame_handler, output);
+
+	wl_list_insert(compositor->base.output_list.prev, &output->base.link);
+
+	weston_log("fbdev output %d×%d px\n",
+	           output->mode.width, output->mode.height);
+	weston_log_continue(STAMP_SPACE "guessing %d Hz and 96 dpi\n",
+	                    output->mode.refresh / 1000);
+
+	return 0;
+
+out_shadow_surface:
+	pixman_image_unref(output->shadow_surface);
+out_hw_surface:
+	free(output->shadow_buf);
+	pixman_image_unref(output->hw_surface);
+out_output:
+	weston_output_destroy(&output->base);
+out_fb:
+	fbdev_frame_buffer_destroy(output);
+out_free:
+	free(output);
+
+	return -1;
+}
+
+static void
+fbdev_output_destroy(struct weston_output *base)
+{
+	struct fbdev_output *output = to_fbdev_output(base);
+
+	weston_log("%s\n", __func__);
+
+	pixman_renderer_output_destroy(base);
+
+	pixman_image_unref(output->hw_surface);
+	output->hw_surface = NULL;
+
+	pixman_image_unref(output->shadow_surface);
+	output->shadow_surface = NULL;
+
+	free(output->shadow_buf);
+	output->shadow_buf = NULL;
+
+	fbdev_frame_buffer_destroy(output);
+
+	wl_list_remove(&output->base.link);
+	weston_output_destroy(&output->base);
+
+	free(output);
+}
+
+static void
+fbdev_led_update(struct weston_seat *seat_base, enum weston_led leds)
+{
+	struct fbdev_seat *seat = to_fbdev_seat(seat_base);
+	struct evdev_device *device;
+
+	wl_list_for_each(device, &seat->devices_list, link)
+		evdev_led_update(device, leds);
+}
+
+static const char default_seat[] = "seat0";
+
+static void
+device_added(struct udev_device *udev_device, struct fbdev_seat *master)
+{
+	struct evdev_device *device;
+	const char *devnode;
+	const char *device_seat;
+	int fd;
+
+	device_seat = udev_device_get_property_value(udev_device, "ID_SEAT");
+	if (!device_seat)
+		device_seat = default_seat;
+
+	if (strcmp(device_seat, master->seat_id))
+		return;
+
+	devnode = udev_device_get_devnode(udev_device);
+	if (devnode == NULL) {
+		weston_log("Getting devnode for device on seat ‘%s’ failed.\n",
+		           device_seat);
+		return;
+	}
+
+	/* Use non-blocking mode so that we can loop on read on
+	 * evdev_device_data() until all events on the fd are
+	 * read.  mtdev_get() also expects this. */
+	fd = open(devnode, O_RDWR | O_NONBLOCK | O_CLOEXEC);
+	if (fd < 0) {
+		weston_log("opening input device '%s' failed.\n", devnode);
+		return;
+	}
+
+	device = evdev_device_create(&master->base, devnode, fd);
+	if (!device) {
+		close(fd);
+		weston_log("not using input device '%s'.\n", devnode);
+		return;
+	}
+
+	wl_list_insert(master->devices_list.prev, &device->link);
+}
+
+static void
+evdev_add_devices(struct udev *udev, struct weston_seat *seat_base)
+{
+	struct fbdev_seat *seat = to_fbdev_seat(seat_base);
+	struct udev_enumerate *e;
+	struct udev_list_entry *entry;
+	struct udev_device *device;
+	const char *path, *sysname;
+
+	e = udev_enumerate_new(udev);
+	if (e == NULL)
+		return;
+
+	if (udev_enumerate_add_match_subsystem(e, "input") < 0 ||
+	    udev_enumerate_scan_devices(e) < 0)
+		goto out_enumerate;
+
+	udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) {
+		path = udev_list_entry_get_name(entry);
+		device = udev_device_new_from_syspath(udev, path);
+
+		if (device == NULL)
+			continue;
+
+		sysname = udev_device_get_sysname(device);
+		if (strncmp("event", sysname, 5) != 0) {
+			udev_device_unref(device);
+			continue;
+		}
+
+		device_added(device, seat);
+
+		udev_device_unref(device);
+	}
+	udev_enumerate_unref(e);
+
+	evdev_notify_keyboard_focus(&seat->base, &seat->devices_list);
+
+	if (wl_list_empty(&seat->devices_list)) {
+		weston_log(
+			"warning: no input devices on entering Weston. "
+			"Possible causes:\n"
+			"\t- no permissions to read /dev/input/event*\n"
+			"\t- seats misconfigured "
+			"(Weston backend option 'seat', "
+			"udev device property ID_SEAT)\n");
+	}
+
+	return;
+
+out_enumerate:
+	udev_enumerate_unref(e);
+
+	weston_log ("Failed to enumerate and add evdev devices.\n");
+}
+
+static int
+evdev_udev_handler(int fd, uint32_t mask, void *data)
+{
+	struct fbdev_seat *seat = data;
+	struct udev_device *udev_device;
+	struct evdev_device *device, *next;
+	const char *action;
+	const char *devnode;
+
+	udev_device = udev_monitor_receive_device(seat->udev_monitor);
+	if (!udev_device)
+		return 1;
+
+	action = udev_device_get_action(udev_device);
+	if (!action)
+		goto out;
+
+	if (strncmp("event", udev_device_get_sysname(udev_device), 5) != 0)
+		goto out;
+
+	if (!strcmp(action, "add")) {
+		device_added(udev_device, seat);
+	}
+	else if (!strcmp(action, "remove")) {
+		devnode = udev_device_get_devnode(udev_device);
+		wl_list_for_each_safe(device, next, &seat->devices_list, link)
+			if (!strcmp(device->devnode, devnode)) {
+				weston_log("input device %s, %s removed\n",
+				           device->devname, device->devnode);
+				evdev_device_destroy(device);
+				break;
+			}
+	}
+
+out:
+	udev_device_unref(udev_device);
+
+	return 0;
+}
+
+static int
+evdev_enable_udev_monitor(struct udev *udev, struct weston_seat *seat_base)
+{
+	struct fbdev_seat *master = to_fbdev_seat(seat_base);
+	struct wl_event_loop *loop;
+	struct weston_compositor *c = master->base.compositor;
+	int fd;
+
+	master->udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
+	if (!master->udev_monitor) {
+		weston_log("udev: failed to create the udev monitor\n");
+		goto out;
+	}
+
+	if (udev_monitor_filter_add_match_subsystem_devtype(master->udev_monitor,
+	                                                    "input",
+	                                                    NULL) < 0) {
+		weston_log("udev: failed to add filter\n");
+		goto out_monitor;
+	}
+
+	if (udev_monitor_enable_receiving(master->udev_monitor)) {
+		weston_log("udev: failed to bind the udev monitor\n");
+		goto out_monitor;
+	}
+
+	loop = wl_display_get_event_loop(c->wl_display);
+	fd = udev_monitor_get_fd(master->udev_monitor);
+	master->udev_monitor_source =
+		wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE,
+		                     evdev_udev_handler, master);
+	if (!master->udev_monitor_source)
+		goto out_monitor;
+
+	return 1;
+
+out_monitor:
+	udev_monitor_unref(master->udev_monitor);
+out:
+	return 0;
+}
+
+static void
+evdev_disable_udev_monitor(struct weston_seat *seat_base)
+{
+	struct fbdev_seat *seat = to_fbdev_seat(seat_base);
+
+	if (!seat->udev_monitor)
+		return;
+
+	udev_monitor_unref(seat->udev_monitor);
+	seat->udev_monitor = NULL;
+	wl_event_source_remove(seat->udev_monitor_source);
+	seat->udev_monitor_source = NULL;
+}
+
+static void
+evdev_input_create(struct weston_compositor *c, struct udev *udev,
+                   const char *seat_id)
+{
+	struct fbdev_seat *seat;
+
+	seat = malloc(sizeof *seat);
+	if (seat == NULL)
+		return;
+
+	memset(seat, 0, sizeof *seat);
+	weston_seat_init(&seat->base, c);
+	seat->base.led_update = fbdev_led_update;
+
+	wl_list_init(&seat->devices_list);
+	seat->seat_id = strdup(seat_id);
+	if (seat->seat_id == NULL ||
+	    !evdev_enable_udev_monitor(udev, &seat->base)) {
+		free(seat->seat_id);
+		free(seat);
+		return;
+	}
+
+	evdev_add_devices(udev, &seat->base);
+}
+
+static void
+evdev_remove_devices(struct weston_seat *seat_base)
+{
+	struct fbdev_seat *seat = to_fbdev_seat(seat_base);
+	struct evdev_device *device, *next;
+
+	wl_list_for_each_safe(device, next, &seat->devices_list, link)
+		evdev_device_destroy(device);
+
+	if (seat->base.seat.keyboard)
+		notify_keyboard_focus_out(&seat->base);
+}
+
+static void
+evdev_input_destroy(struct weston_seat *seat_base)
+{
+	struct fbdev_seat *seat = to_fbdev_seat(seat_base);
+
+	evdev_remove_devices(seat_base);
+	evdev_disable_udev_monitor(&seat->base);
+
+	weston_seat_release(seat_base);
+	free(seat->seat_id);
+	free(seat);
+}
+
+static void
+fbdev_compositor_destroy(struct weston_compositor *base)
+{
+	struct fbdev_compositor *compositor = to_fbdev_compositor(base);
+	struct weston_seat *seat, *next;
+
+	/* Destroy all inputs. */
+	wl_list_for_each_safe(seat, next, &compositor->base.seat_list, link)
+		evdev_input_destroy(seat);
+
+	/* Destroy the output. */
+	weston_compositor_shutdown(&compositor->base);
+
+	/* Chain up. */
+	compositor->base.renderer->destroy(&compositor->base);
+	tty_destroy(compositor->tty);
+
+	free(compositor);
+}
+
+static void
+vt_func(struct weston_compositor *base, int event)
+{
+	struct fbdev_compositor *compositor = to_fbdev_compositor(base);
+	struct weston_seat *seat;
+	struct weston_output *output;
+
+	switch (event) {
+	case TTY_ENTER_VT:
+		weston_log("entering VT\n");
+		compositor->base.focus = 1;
+		compositor->base.state = compositor->prev_state;
+		weston_compositor_damage_all(&compositor->base);
+		wl_list_for_each(seat, &compositor->base.seat_list, link) {
+			evdev_add_devices(compositor->udev, seat);
+			evdev_enable_udev_monitor(compositor->udev, seat);
+		}
+		break;
+	case TTY_LEAVE_VT:
+		weston_log("leaving VT\n");
+		wl_list_for_each(seat, &compositor->base.seat_list, link) {
+			evdev_disable_udev_monitor(seat);
+			evdev_remove_devices(seat);
+		}
+
+		compositor->base.focus = 0;
+		compositor->prev_state = compositor->base.state;
+		compositor->base.state = WESTON_COMPOSITOR_SLEEPING;
+
+		/* If we have a repaint scheduled (from the idle handler), make
+		 * sure we cancel that so we don't try to pageflip when we're
+		 * vt switched away.  The SLEEPING state will prevent
+		 * further attemps at repainting.  When we switch
+		 * back, we schedule a repaint, which will process
+		 * pending frame callbacks. */
+
+		wl_list_for_each(output,
+				 &compositor->base.output_list, link) {
+			output->repaint_needed = 0;
+		}
+
+		break;
+	};
+}
+
+static void
+fbdev_restore(struct weston_compositor *base)
+{
+	struct fbdev_compositor *compositor = to_fbdev_compositor(base);
+
+	tty_reset(compositor->tty);
+}
+
+static void
+switch_vt_binding(struct wl_seat *seat, uint32_t time, uint32_t key, void *data)
+{
+	struct fbdev_compositor *ec = data;
+
+	tty_activate_vt(ec->tty, key - KEY_F1 + 1);
+}
+
+static struct weston_compositor *
+fbdev_compositor_create(struct wl_display *display, int argc, char *argv[],
+                        const char *config_file, struct fbdev_parameters *param)
+{
+	struct fbdev_compositor *compositor;
+	const char *seat = default_seat;
+	uint32_t key;
+
+	weston_log("initializing fbdev backend\n");
+
+	compositor = calloc(1, sizeof *compositor);
+	if (compositor == NULL)
+		return NULL;
+
+	if (weston_compositor_init(&compositor->base, display, argc, argv,
+	                           config_file) < 0)
+		goto out_free;
+
+	compositor->udev = udev_new();
+	if (compositor->udev == NULL) {
+		weston_log("Failed to initialize udev context.\n");
+		goto out_compositor;
+	}
+
+	/* Set up the TTY. */
+	compositor->tty = tty_create(&compositor->base, vt_func, param->tty);
+	if (!compositor->tty) {
+		weston_log("Failed to initialize tty.\n");
+		goto out_udev;
+	}
+
+	compositor->base.destroy = fbdev_compositor_destroy;
+	compositor->base.restore = fbdev_restore;
+
+	compositor->base.focus = 1;
+	compositor->prev_state = WESTON_COMPOSITOR_ACTIVE;
+
+	for (key = KEY_F1; key < KEY_F9; key++)
+		weston_compositor_add_key_binding(&compositor->base, key,
+		                                  MODIFIER_CTRL | MODIFIER_ALT,
+		                                  switch_vt_binding,
+		                                  compositor);
+
+	if (pixman_renderer_init(&compositor->base) < 0)
+		goto out_tty;
+
+	if (fbdev_output_create(compositor, param) < 0)
+		goto out_pixman;
+
+	evdev_input_create(&compositor->base, compositor->udev, seat);
+
+	return &compositor->base;
+
+out_pixman:
+	compositor->base.renderer->destroy(&compositor->base);
+
+out_tty:
+	tty_destroy(compositor->tty);
+
+out_udev:
+	udev_unref(compositor->udev);
+
+out_compositor:
+	weston_compositor_shutdown(&compositor->base);
+
+out_free:
+	free(compositor);
+
+	return NULL;
+}
+
+WL_EXPORT struct weston_compositor *
+backend_init(struct wl_display *display, int argc, char *argv[],
+	     const char *config_file)
+{
+	/* TODO: Ideally, available frame buffers should be enumerated using
+	 * udev, rather than passing a device node in as a parameter. */
+	struct fbdev_parameters param = {
+		.tty = 0, /* default to current tty */
+		.device = "/dev/fb0", /* default frame buffer */
+	};
+
+	const struct weston_option fbdev_options[] = {
+		{ WESTON_OPTION_INTEGER, "tty", 0, &param.tty },
+		{ WESTON_OPTION_STRING, "device", 0, &param.device },
+	};
+
+	parse_options(fbdev_options, ARRAY_LENGTH(fbdev_options), argc, argv);
+
+	return fbdev_compositor_create(display, argc, argv, config_file,
+	                               &param);
+}
-- 
1.7.11.7

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 230 bytes
Desc: This is a digitally signed message part
URL: <http://lists.freedesktop.org/archives/wayland-devel/attachments/20130118/ea6a0947/attachment-0001.pgp>


More information about the wayland-devel mailing list