Commit da306967 authored by Youness Alaoui's avatar Youness Alaoui
Browse files

Add fallback into Linux FB if DRI isn't available

This will automatically try to use the Linux FB driver if it can't
find a suitable DRI device or screen. If it can't create a FB with
double buffering, it will use a single buffer instead.
This will print the escape char to hide the blinking cursor and restore
it at the end, since it would affect the framebuffer. I don't know how
it will react if the terminal doesn't support the escape sequence.

To test on a DRI-enabled machine, simply comment out the line :
//dri = cairo_dri_open("/dev/dri/card0");

Error prints from the linux fb driver still need to be removed, they
are left here for now until further tests are done.
parent c54d3c8f
......@@ -11,7 +11,7 @@ test-menu-fb: test-menu-fb.c cairo_menu.c cairo_utils.c cairo_linuxfb.c \
$(CC) -g -O0 -o $@ $^ -lm
fbwhiptail: fbwhiptail.c fbwhiptail_menu.c cairo_menu.c cairo_utils.c cairo_dri.c
fbwhiptail: fbwhiptail.c fbwhiptail_menu.c cairo_menu.c cairo_utils.c cairo_dri.c cairo_linuxfb.c
$(CC) -g -O0 -o $@ $^ -lm -lcairo -lpixman-1 -lpng16 -lz
gtkwhiptail: fbwhiptail.c fbwhiptail_menu.c fbwhiptail_menu.h cairo_menu.c cairo_utils.c
......
/*
* cairo_fb.c : Cairo Linux Framebuffer surface implementation
* Based on the demo application at https://github.com/toradex/cairo-fb-examples
*
* Copyright (c) 2015, Toradex AG
* Copyright (c) 2018, Youness Alaoui (KaKaRoTo)
*
* This project is licensed under the terms of the MIT license (see
* LICENSE)
*/
#include "cairo_linuxfb.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/ioctl.h>
#include <linux/fb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>
#include <time.h>
typedef struct _cairo_linuxfb_device {
int fb_fd;
unsigned char *fb_data;
unsigned char *previous_fb_data;
struct fb_var_screeninfo fb_vinfo;
struct fb_fix_screeninfo fb_finfo;
int num_buffers;
int buffer_id;
} cairo_linuxfb_device_t;
static cairo_user_data_key_t user_data_key;
/*
* Flip framebuffer, return the next buffer id which will be used or -1 if
* an operation failed
*/
int cairo_linuxfb_flip_buffer(cairo_surface_t *surface, int vsync, int bufid)
{
cairo_linuxfb_device_t *device;
device = cairo_surface_get_user_data (surface, &user_data_key);
if (device == NULL)
return -1;
if (device->buffer_id != bufid && bufid < device->num_buffers) {
/* Pan the framebuffer */
device->fb_vinfo.yoffset = device->fb_vinfo.yres * bufid;
if (ioctl(device->fb_fd, FBIOPAN_DISPLAY, &device->fb_vinfo)) {
perror("Error panning display");
return -1;
}
device->buffer_id = bufid;
}
if (vsync) {
int dummy = 0;
if (ioctl(device->fb_fd, FBIO_WAITFORVSYNC, &dummy)) {
perror("Error waiting for VSYNC");
return -1;
}
}
return (bufid+1) % device->num_buffers;
}
int cairo_linuxfb_get_resolution(cairo_surface_t *surface, int *xres, int *yres)
{
cairo_linuxfb_device_t *device;
device = cairo_surface_get_user_data (surface, &user_data_key);
if (device == NULL)
return -1;
*xres = device->fb_vinfo.xres;
*yres = device->fb_vinfo.yres;
return 0;
}
/* Destroy a cairo surface */
static void cairo_linuxfb_surface_destroy(void *device)
{
cairo_linuxfb_device_t *dev = (cairo_linuxfb_device_t *)device;
if (dev == NULL)
return;
memcpy (dev->fb_data, dev->previous_fb_data, dev->fb_finfo.smem_len);
free(dev->previous_fb_data);
munmap(dev->fb_data, dev->fb_finfo.smem_len);
close(dev->fb_fd);
free(dev);
}
/* Create a cairo surface using the specified framebuffer
* can return an error if fb driver doesn't support double buffering
*/
cairo_surface_t *cairo_linuxfb_surface_create(const char *fb_filename, int num_buffers)
{
cairo_surface_t *surface;
cairo_linuxfb_device_t *device;
device = malloc(sizeof(cairo_linuxfb_device_t));
if (device == NULL) {
perror ("Error: can't allocate structure");
return NULL;
}
// Open the file for reading and writing
device->fb_fd = open(fb_filename, O_RDWR);
if (device->fb_fd == -1) {
perror("Error: cannot open framebuffer device");
goto handle_open_error;
}
// Get variable screen information
if (ioctl(device->fb_fd, FBIOGET_VSCREENINFO, &device->fb_vinfo) == -1) {
perror("Error: reading variable information");
goto handle_ioctl_error;
}
/*
printf("Frame buffer var screen info : \n");
printf(" X Resolution : %u\n", device->fb_vinfo.xres);
printf(" Y Resolution : %u\n", device->fb_vinfo.yres);
printf(" X Virtual Resolution : %u\n", device->fb_vinfo.xres_virtual);
printf(" Y Virtual Resolution : %u\n", device->fb_vinfo.yres_virtual);
printf(" X Offset : %u\n", device->fb_vinfo.xoffset);
printf(" X Offset : %u\n", device->fb_vinfo.yoffset);
printf(" Bits per pixel : %u\n", device->fb_vinfo.bits_per_pixel);
printf(" Grayscale : %u\n", device->fb_vinfo.grayscale);
*/
/* Set virtual display size double the width for double buffering */
device->fb_vinfo.bits_per_pixel = 32;
device->fb_vinfo.yoffset = 0;
device->fb_vinfo.yres_virtual = device->fb_vinfo.yres * num_buffers;
if (ioctl(device->fb_fd, FBIOPUT_VSCREENINFO, &device->fb_vinfo)) {
perror("Error setting variable screen info from fb");
goto handle_ioctl_error;
}
// Get fixed screen information
if (ioctl(device->fb_fd, FBIOGET_FSCREENINFO, &device->fb_finfo) == -1) {
perror("Error reading fixed information");
goto handle_ioctl_error;
}
/*
printf("Frame buffer fixed screen info : \n");
printf(" ID : %16s\n", device->fb_finfo.id);
printf(" Line length : %u\n", device->fb_finfo.line_length);
printf(" Smem length : %u\n", device->fb_finfo.smem_len);
*/
// Map the device to memory
device->fb_data = (unsigned char *)mmap(0, device->fb_finfo.smem_len,
PROT_READ | PROT_WRITE, MAP_SHARED,
device->fb_fd, 0);
if (device->fb_data == MAP_FAILED) {
perror("Error: failed to map framebuffer device to memory");
goto handle_ioctl_error;
}
device->previous_fb_data = malloc (device->fb_finfo.smem_len);
if (device->previous_fb_data)
memcpy (device->previous_fb_data, device->fb_data,
device->fb_finfo.smem_len);
/* Create the cairo surface which will be used to draw to */
// TODO: Actually verify the format
surface = cairo_image_surface_create_for_data(device->fb_data,
CAIRO_FORMAT_RGB24,
device->fb_vinfo.xres,
device->fb_vinfo.yres_virtual,
cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32,
device->fb_vinfo.xres));
cairo_surface_set_user_data(surface, &user_data_key, device,
&cairo_linuxfb_surface_destroy);
device->num_buffers = num_buffers;
device->buffer_id = -1;
return surface;
handle_ioctl_error:
close(device->fb_fd);
handle_open_error:
free(device);
return NULL;
}
/*
* cairo_fb.c : Cairo Linux Framebuffer surface implementation
* Based on the demo application at https://github.com/toradex/cairo-fb-examples
*
* Copyright (c) 2015, Toradex AG
* Copyright (c) 2018, Youness Alaoui (KaKaRoTo)
*
* This project is licensed under the terms of the MIT license (see
* LICENSE)
*/
#ifndef __CAIRO_LINUXFB_H__
#define __CAIRO_LINUXFB_H__
#include <cairo/cairo.h>
/*
* Flip framebuffer, return the next buffer id which will be used or -1 if
* an operation failed
*/
int cairo_linuxfb_flip_buffer(cairo_surface_t *surface, int vsync, int bufid);
int cairo_linuxfb_get_resolution(cairo_surface_t *surface, int *xres, int *yres);
/* Create a cairo surface using the specified framebuffer
* can return an error if fb driver doesn't support double buffering
*/
cairo_surface_t *cairo_linuxfb_surface_create(const char *fb_filename, int num_buffers);
#endif /* __CAIRO_LINUXFB_H__ */
......@@ -49,6 +49,7 @@
#include <signal.h>
#include "cairo_dri.h"
#include "cairo_linuxfb.h"
#endif
#define VERSION_STRING "0.0.1"
......@@ -451,6 +452,8 @@ int main(int argc, char **argv)
that means it will return if it sees a "\n" or an EOF or an EOL*/
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr( STDIN_FILENO, TCSANOW, &newt);
printf ("\e[?25l"); // Hide blinking cursor (in case of linux FB)
fflush (stdout);
screens = 0;
dri = cairo_dri_open("/dev/dri/card0");
......@@ -474,7 +477,7 @@ int main(int argc, char **argv)
crs[screens*2] = cairo_create(surfaces[screens*2]);
crs[screens*2+1] = cairo_create(surfaces[screens*2+1]);
if (cairo_dri_flip_buffer (surfaces[i*2+current_fb], 0) != 0) {
if (cairo_dri_flip_buffer (surfaces[i*2], 0) != 0) {
cairo_destroy(crs[screens*2]);
cairo_destroy(crs[screens*2+1]);
cairo_surface_destroy (surfaces[screens*2]);
......@@ -486,6 +489,45 @@ int main(int argc, char **argv)
}
}
}
if (screens == 0) {
cairo_surface_t * fbsurface = cairo_linuxfb_surface_create("/dev/fb0", 2);
int num_fbs = 2;
if (fbsurface == NULL) {
fbsurface = cairo_linuxfb_surface_create("/dev/fb0", 1);
num_fbs = 1;
}
if (fbsurface) {
if (crs)
free (crs);
if (surfaces)
free (surfaces);
if (hdisplay)
free (hdisplay);
if (vdisplay)
free (vdisplay);
if (dri)
cairo_dri_close (dri);
dri = NULL;
surfaces = malloc (sizeof(cairo_surface_t *) * 2);
crs = malloc (sizeof(cairo_t *) * 2);
hdisplay = malloc (sizeof(int));
vdisplay = malloc (sizeof(int));
surfaces[0] = surfaces[1] = fbsurface;
crs[0] = crs[1] = cairo_create(surfaces[0]);
if (num_fbs == 1) {
surfaces[1] = NULL;
crs[1] = NULL;
}
cairo_linuxfb_get_resolution(fbsurface, &xres, &yres);
hdisplay[0] = xres;
vdisplay[0] = yres;
screens = 1;
}
}
if (screens == 0) {
printf ("Error: Can't find usable screen\n");
goto error;
......@@ -572,18 +614,35 @@ int main(int argc, char **argv)
if (redraw) {
for (i = 0; i < screens; i++) {
int off_x = (hdisplay[i] - xres) / 2;
int off_y = (vdisplay[i] - yres) / 2;
cr = crs[i*2+current_fb];
cairo_save (cr);
cairo_translate (cr, off_x, off_y);
if (dri) {
int off_x = (hdisplay[i] - xres) / 2;
int off_y = (vdisplay[i] - yres) / 2;
cairo_translate (cr, off_x, off_y);
} else {
// Linux FB
cairo_translate (cr, 0, yres * current_fb);
}
draw_background (menu, cr);
menu->draw (menu, cr);
cairo_restore (cr);
if (cairo_dri_flip_buffer (surfaces[i*2 + current_fb], 0) != 0) {
printf ("Flip failed. Cancelling\n");
break;
if (dri) {
if (cairo_dri_flip_buffer (surfaces[i*2 + current_fb], 0) != 0) {
printf ("Flip failed. Cancelling\n");
break;
}
} else {
int next_fb = (current_fb + 1) % 2;
if (crs[i*2+next_fb] == NULL)
current_fb = next_fb;
else if (cairo_linuxfb_flip_buffer (surfaces[i*2 + current_fb],
1, current_fb) != 0) {
printf ("Flip failed. Cancelling\n");
break;
}
}
}
current_fb = (current_fb + 1) % 2;
......@@ -604,6 +663,8 @@ int main(int argc, char **argv)
error:
/*restore the old settings*/
printf ("\e[?25h");
fflush (stdout);
tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
for (i = 0; i < screens * 2; i++) {
if (crs[i])
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment