Commit 193da9ad authored by Youness Alaoui's avatar Youness Alaoui
Browse files

Create a unified gtkwhiptail for testing purposes

parent ab4c571e
all : test-menu-gtk test-menu-fb fbwhiptail test-dri
all : fbwhiptail gtkwhiptail
test-menu-gtk: test-menu-gtk.c cairo_menu.c cairo_utils.c fbwhiptail_menu.c
$(CC) -g -O0 -o $@ $^ \
......@@ -12,7 +12,12 @@ test-menu-fb: test-menu-fb.c cairo_menu.c cairo_utils.c cairo_linuxfb.c \
fbwhiptail: fbwhiptail.c fbwhiptail_menu.c cairo_menu.c cairo_utils.c cairo_dri.c
$(CC) -g -O0 -o $@ $^ -lm
$(CC) -g -O0 -o $@ $^ -lm -lcairo -lpixman-1 -lpng16 -lz
gtkwhiptail: fbwhiptail.c fbwhiptail_menu.c cairo_menu.c cairo_utils.c
$(CC) -g -O0 -DGTKWHIPTAIL -o $@ $^ -lm \
`pkg-config --cflags --libs cairo` \
`pkg-config --cflags --libs gtk+-2.0`
test-dri: test-dri.c
$(CC) -g -O0 -o $@ $^
/*
* 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);
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__ */
......@@ -10,8 +10,6 @@
*
*/
//#define USE_LINUXFB
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
......@@ -23,54 +21,96 @@
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/time.h>
#include <cairo/cairo.h>
#include "fbwhiptail_menu.h"
#ifdef GTKWHIPTAIL
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#else
#include <fcntl.h>
#include <termios.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/kd.h>
#include <linux/vt.h>
#include <signal.h>
#include "fbwhiptail_menu.h"
#ifdef USE_LINUXFB
#include "cairo_linuxfb.h"
#else
#include "cairo_dri.h"
#endif
static volatile sig_atomic_t cancel = 0;
typedef struct {
char *tag;
char *item;
} whiptail_menu_item;
typedef struct {
char *title;
char *backtitle;
char *text;
char *default_item;
int noitem;
int notags;
int topleft;
int output_fd;
int width;
int height;
int menu_height;
whiptail_menu_item *items;
int num_items;
} whiptail_args;
#ifdef GTKWHIPTAIL
#define WINDOW_WIDTH 1024
#define WINDOW_HEIGHT 768
static int result = 0;
static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
{
Menu *menu = data;
CairoMenuInput input;
CairoMenuRectangle bbox;
if (event->keyval == GDK_Up)
input = CAIRO_MENU_INPUT_UP;
else if (event->keyval == GDK_Down)
input = CAIRO_MENU_INPUT_DOWN;
else if (event->keyval == GDK_Left)
input = CAIRO_MENU_INPUT_LEFT;
else if (event->keyval == GDK_Right)
input = CAIRO_MENU_INPUT_RIGHT;
else if (event->keyval == GDK_Escape)
gtk_main_quit ();
else if (event->keyval == GDK_KP_Enter || event->keyval == GDK_Return) {
gtk_main_quit ();
result = 1;
} else
return TRUE;
cairo_menu_handle_input (menu->menu, input, &bbox);
gtk_widget_queue_draw_area (widget, 0, 0, menu->width, menu->height);
return TRUE;
}
static void
draw (GtkWidget *widget, GdkEventExpose *eev, gpointer data)
{
Menu *menu = data;
cairo_t *cr;
cr = gdk_cairo_create(widget->window);
draw_background (menu, cr);
menu->draw (menu, cr);
cairo_destroy(cr);
}
#else
static volatile sig_atomic_t cancel = 0;
void signal_handler(int signum)
{
cancel = 1;
cancel = 1;
}
static int handle_input(Menu *menu, short code)
static int handle_arrow_input(Menu *menu, short code)
{
CairoMenuInput input;
CairoMenuRectangle bbox = {0, 0, 0, 0};
......@@ -90,41 +130,69 @@ static int handle_input(Menu *menu, short code)
return (bbox.width * bbox.height) != 0;
}
static int handle_input(Menu *menu)
{
static int escape = 0;
char c;
int result = 0;
switch (c = getchar ()) {
case 0x1B: // Escape character
if (escape == 0)
escape = 1;
else
cancel = 1; // Double Escape
break;
case 0xA: // Enter
if (escape == 0) {
cancel = 1;
result = 2;
}
escape = 0;
break;
case 0x41: // Arrow keys
case 0x42:
case 0x43:
case 0x44:
if (escape == 2) {
handle_arrow_input (menu, c);
result = 1;
}
default:
if (escape == 0) {
}
if (escape == 1) {
escape = 2;
} else {
escape = 0;
}
break;
}
return result;
}
#endif
void usage (int exit_code)
{
printf ("Usage :\n");
exit (exit_code);
}
int parse_whiptail_args (int argc, char **argv, whiptail_args *args)
{
int i;
int end_of_args = 0, menu = 0;
/*
static whiptail_menu_item items[] = {
{"first", "menu entry 1"},
{"second", "blablabla"},
{"foo", "bar"},
{"default", "item"},
{"usb1", "usb boot"},
{"linux2", "Linux boot"},
{"etc..", "you get the idea"},
};
args->title = "Whiptail test menu";
args->backtitle = "background title";
args->text = "some text to show in frame?";
args->default_item = "default";
args->noitem = 0;
args->notags = 0;
args->topleft = 0;
args->output_fd = 2;
args->width = 600;
args->height = 400;
args->menu_height = 10;
args->items = items;
args->num_items = 7;
*/
memset (args, 0, sizeof(whiptail_args));
args->output_fd = 2;
for (i = 1; i < argc; i++) {
if (end_of_args == 0 && strncmp (argv[i], "--", 2) == 0) {
if (strcmp (argv[i], "--title") == 0) {
if (strcmp (argv[i], "--help") == 0) {
usage (0);
} else if (strcmp (argv[i], "--title") == 0) {
i++;
if (i >= argc)
goto error;
......@@ -169,7 +237,7 @@ int parse_whiptail_args (int argc, char **argv, whiptail_args *args)
i++;
if (i >= argc)
goto error;
// Ignore arguments
// Ignore unsupported whiptail arguments
} else if (strcmp (argv[i], "--clear") == 0 ||
strcmp (argv[i], "--fb") == 0 ||
strcmp (argv[i], "--fullbuttons") == 0 ||
......@@ -177,9 +245,8 @@ int parse_whiptail_args (int argc, char **argv, whiptail_args *args)
strcmp (argv[i], "--nocancel") == 0 ||
strcmp (argv[i], "--scrolltext") == 0 ||
strcmp (argv[i], "--separate-output") == 0 ||
strcmp (argv[i], "--help") == 0 ||
strcmp (argv[i], "--version") == 0) {
// Ignore arguments
// Ignore unsupported whiptail arguments
}
} else if (menu) {
if (i + 1 >= argc)
......@@ -203,31 +270,44 @@ int parse_whiptail_args (int argc, char **argv, whiptail_args *args)
int main(int argc, char **argv)
{
#ifdef GTKWHIPTAIL
GtkWidget *window;
GtkWidget *area;
#else
struct sigaction action;
struct termios oldt, newt;
int escape = 0;
int i, idx;
cairo_surface_t *image = NULL;
int xres, yres;
Menu *menu = NULL;
int redraw = 1;
whiptail_args args;
int default_selection;
cairo_t *cr;
#ifdef USE_LINUXFB
cairo_surface_t *fbsurface = NULL;
#else
cairo_dri_t *dri = NULL;
cairo_surface_t *surfaces[2] = {NULL, NULL};
cairo_t *crs[2] = {NULL, NULL};
int current_fb = 0;
int redraw = 1;
int input_result = 0;
#endif
int i, idx;
int xres, yres;
Menu *menu = NULL;
whiptail_args args;
if (parse_whiptail_args (argc, argv, &args) != 0) {
printf ("Invalid arguments received\n");
usage (-1);
return -1;
}
#ifdef GTKWHIPTAIL
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
area = gtk_drawing_area_new();
gtk_widget_set_size_request (area, WINDOW_WIDTH, WINDOW_HEIGHT);
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(area));
gtk_widget_show_all(window);
xres = WINDOW_WIDTH;
yres = WINDOW_HEIGHT;
#else
memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = signal_handler;
sigaction(SIGTERM, &action, NULL);
......@@ -238,14 +318,9 @@ int main(int argc, char **argv)
/* ICANON normally takes care that one line at a time will be processed
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);
#ifdef USE_LINUXFB
fbsurface = cairo_linuxfb_surface_create("/dev/fb0", 1);
cairo_linuxfb_get_resolution(fbsurface, &xres, &yres);
cr = cairo_create(fbsurface);
#else
// TODO : Support mutliple screens ?
dri = cairo_dri_open("/dev/dri/card0");
if (dri && dri->num_screens > 0) {
xres = dri->screens[0].mode.hdisplay;
......@@ -254,6 +329,14 @@ int main(int argc, char **argv)
surfaces[1] = cairo_dri_create_surface(dri, &dri->screens[0]);
crs[0] = cairo_create(surfaces[0]);
crs[1] = cairo_create(surfaces[1]);
current_fb = 0;
if (cairo_dri_flip_buffer (surfaces[current_fb], 0) != 0) {
printf ("Error: Can't flip DRI framebuffer\n");
goto error;
}
} else {
printf ("Error: Can't find usable screen\n");
goto error;
}
#endif
......@@ -302,73 +385,58 @@ int main(int argc, char **argv)
cairo_menu_set_selection (menu->menu, idx, &bbox);
}
}
#ifdef GTKWHIPTAIL
g_signal_connect (G_OBJECT (window), "delete-event",
G_CALLBACK (gtk_main_quit), NULL);
g_signal_connect (G_OBJECT (area), "expose-event", G_CALLBACK (draw), menu);
gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
GTK_SIGNAL_FUNC(key_event), menu);
gtk_main ();
if (result)
fprintf (stderr, "%s", args.items[menu->menu->selection-1].tag);
#else
while (!cancel) {
char c;
if (redraw) {
#ifndef USE_LINUXFB
cr = crs[current_fb];
#endif
draw_background (menu, cr);
menu->draw (menu, cr);
#ifndef USE_LINUXFB
if (cairo_dri_flip_buffer (surfaces[current_fb], 0) != 0) {
printf ("Flip failed. Cancelling\n");
break;
}
current_fb = (current_fb + 1) % 2;
#endif
redraw = 0;
}
input_result = handle_input (menu);
if (input_result == 1)
redraw = 1;
else if (input_result == 2)
fprintf (stderr, "%s", args.items[menu->menu->selection-1].tag);
switch (c = getchar ()) {
case 0x1B: // Escape character
if (escape == 0)
escape = 1;
else
cancel = 1;
break;
case 0xA:
if (escape == 0) {
cancel = 1;
fprintf (stderr, "%s", args.items[menu->menu->selection].tag);
}
escape = 0;
break;
case 0x41:
case 0x42:
case 0x43:
case 0x44:
if (escape == 2) {