busybox/miscutils/fbsplash.c
Bernhard Reutner-Fischer 7307e06122 - bail out if screen resolution does not match PPM dimensions.
Previously a 640x480 PPM on an e.g. 720x400 console would just segfault when
  reading the lines. While this bug should perhaps be fixed to handle such cases
  properly we just exit gracefully until somebody is willing to take care of it
  properly.
2009-02-18 15:28:43 +00:00

408 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* vi: set sw=4 ts=4: */
/*
* Copyright (C) 2008 Michele Sanges <michele.sanges@gmail.com>
*
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
*
* Usage:
* - use kernel option 'vga=xxx' or otherwise enable framebuffer device.
* - put somewhere fbsplash.cfg file and an image in .ppm format.
* - run applet: $ setsid fbsplash [params] &
* -c: hide cursor
* -d /dev/fbN: framebuffer device (if not /dev/fb0)
* -s path_to_image_file (can be "-" for stdin)
* -i path_to_cfg_file
* -f path_to_fifo (can be "-" for stdin)
* - if you want to run it only in presence of a kernel parameter
* (for example fbsplash=on), use:
* grep -q "fbsplash=on" </proc/cmdline && setsid fbsplash [params]
* - commands for fifo:
* "NN" (ASCII decimal number) - percentage to show on progress bar.
* "exit" (or just close fifo) - well you guessed it.
*/
#include "libbb.h"
#include <linux/fb.h>
/* If you want logging messages on /tmp/fbsplash.log... */
#define DEBUG 0
#define BYTES_PER_PIXEL 2
typedef unsigned short DATA;
struct globals {
#if DEBUG
bool bdebug_messages; // enable/disable logging
FILE *logfile_fd; // log file
#endif
unsigned char *addr; // pointer to framebuffer memory
unsigned ns[7]; // n-parameters
const char *image_filename;
struct fb_var_screeninfo scr_var;
struct fb_fix_screeninfo scr_fix;
};
#define G (*ptr_to_globals)
#define INIT_G() do { \
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
} while (0)
#define nbar_width ns[0] // progress bar width
#define nbar_height ns[1] // progress bar height
#define nbar_posx ns[2] // progress bar horizontal position
#define nbar_posy ns[3] // progress bar vertical position
#define nbar_colr ns[4] // progress bar color red component
#define nbar_colg ns[5] // progress bar color green component
#define nbar_colb ns[6] // progress bar color blue component
#if DEBUG
#define DEBUG_MESSAGE(strMessage, args...) \
if (G.bdebug_messages) { \
fprintf(G.logfile_fd, "[%s][%s] - %s\n", \
__FILE__, __FUNCTION__, strMessage); \
}
#else
#define DEBUG_MESSAGE(...) ((void)0)
#endif
/**
* Open and initialize the framebuffer device
* \param *strfb_device pointer to framebuffer device
*/
static void fb_open(const char *strfb_device)
{
int fbfd = xopen(strfb_device, O_RDWR);
// framebuffer properties
xioctl(fbfd, FBIOGET_VSCREENINFO, &G.scr_var);
xioctl(fbfd, FBIOGET_FSCREENINFO, &G.scr_fix);
if (G.scr_var.bits_per_pixel != 16)
bb_error_msg_and_die("only 16 bpp is supported");
// map the device in memory
G.addr = mmap(NULL,
G.scr_var.xres * G.scr_var.yres
* BYTES_PER_PIXEL /*(G.scr_var.bits_per_pixel / 8)*/ ,
PROT_WRITE, MAP_SHARED, fbfd, 0);
if (G.addr == MAP_FAILED)
bb_perror_msg_and_die("mmap");
close(fbfd);
}
/**
* Draw hollow rectangle on framebuffer
*/
static void fb_drawrectangle(void)
{
int cnt;
DATA thispix;
DATA *ptr1, *ptr2;
unsigned char nred = G.nbar_colr/2;
unsigned char ngreen = G.nbar_colg/2;
unsigned char nblue = G.nbar_colb/2;
nred >>= 3; // 5-bit red
ngreen >>= 2; // 6-bit green
nblue >>= 3; // 5-bit blue
thispix = nblue + (ngreen << 5) + (nred << (5+6));
// horizontal lines
ptr1 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
ptr2 = (DATA*)(G.addr + ((G.nbar_posy + G.nbar_height - 1) * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
cnt = G.nbar_width - 1;
do {
*ptr1++ = thispix;
*ptr2++ = thispix;
} while (--cnt >= 0);
// vertical lines
ptr1 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
ptr2 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx + G.nbar_width - 1) * BYTES_PER_PIXEL);
cnt = G.nbar_height - 1 /* HUH?! G.nbar_posy + G.nbar_height - 1 - G.nbar_posy*/;
do {
*ptr1 = thispix; ptr1 += G.scr_var.xres;
*ptr2 = thispix; ptr2 += G.scr_var.xres;
} while (--cnt >= 0);
}
/**
* Draw filled rectangle on framebuffer
* \param nx1pos,ny1pos upper left position
* \param nx2pos,ny2pos down right position
* \param nred,ngreen,nblue rgb color
*/
static void fb_drawfullrectangle(int nx1pos, int ny1pos, int nx2pos, int ny2pos,
unsigned char nred, unsigned char ngreen, unsigned char nblue)
{
int cnt1, cnt2, nypos;
DATA thispix;
DATA *ptr;
nred >>= 3; // 5-bit red
ngreen >>= 2; // 6-bit green
nblue >>= 3; // 5-bit blue
thispix = nblue + (ngreen << 5) + (nred << (5+6));
cnt1 = ny2pos - ny1pos;
nypos = ny1pos;
do {
ptr = (DATA*)(G.addr + (nypos * G.scr_var.xres + nx1pos) * BYTES_PER_PIXEL);
cnt2 = nx2pos - nx1pos;
do {
*ptr++ = thispix;
} while (--cnt2 >= 0);
nypos++;
} while (--cnt1 >= 0);
}
/**
* Draw a progress bar on framebuffer
* \param percent percentage of loading
*/
static void fb_drawprogressbar(unsigned percent)
{
int i, left_x, top_y, width, height;
// outer box
left_x = G.nbar_posx;
top_y = G.nbar_posy;
width = G.nbar_width - 1;
height = G.nbar_height - 1;
if ((height | width) < 0)
return;
// NB: "width" of 1 actually makes rect with width of 2!
fb_drawrectangle();
// inner "empty" rectangle
left_x++;
top_y++;
width -= 2;
height -= 2;
if ((height | width) < 0)
return;
fb_drawfullrectangle(
left_x, top_y,
left_x + width, top_y + height,
G.nbar_colr, G.nbar_colg, G.nbar_colb);
if (percent > 0) {
// actual progress bar
width = width * percent / 100;
i = height;
if (height == 0)
height++; // divide by 0 is bad
while (i >= 0) {
// draw one-line thick "rectangle"
// top line will have gray lvl 200, bottom one 100
unsigned gray_level = 100 + i*100/height;
fb_drawfullrectangle(
left_x, top_y, left_x + width, top_y,
gray_level, gray_level, gray_level);
top_y++;
i--;
}
}
}
/**
* Draw image from PPM file
*/
static void fb_drawimage(void)
{
char *head, *ptr;
FILE *theme_file;
unsigned char *pixline;
unsigned i, j, width, height, line_size;
theme_file = xfopen_stdin(G.image_filename);
head = xmalloc(256);
/* parse ppm header
* - A ppm images magic number is the two characters "P6".
* - Whitespace (blanks, TABs, CRs, LFs).
* - A width, formatted as ASCII characters in decimal.
* - Whitespace.
* - A height, again in ASCII decimal.
* - Whitespace.
* - The maximum color value (Maxval), again in ASCII decimal. Must be
* less than 65536.
* - Newline or other single whitespace character.
* - A raster of Width * Height pixels in triplets of rgb
* in pure binary by 1 (or not implemented 2) bytes.
*/
while (1) {
if (fgets(head, 256, theme_file) == NULL
/* do not overrun the buffer */
|| strlen(bb_common_bufsiz1) >= sizeof(bb_common_bufsiz1) - 256)
bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
ptr = memchr(skip_whitespace(head), '#', 256);
if (ptr != NULL)
*ptr = 0; /* ignore comments */
strcat(bb_common_bufsiz1, head);
// width, height, max_color_val
if (sscanf(bb_common_bufsiz1, "P6 %u %u %u", &width, &height, &i) == 3
&& i <= 255)
break;
/* If we do not find a signature throughout the whole file then
we will diagnose this via EOF on read in the head of the loop. */
}
if (ENABLE_FEATURE_CLEAN_UP)
free(head);
if (width != G.scr_var.xres || height != G.scr_var.yres)
bb_error_msg_and_die("PPM %dx%d does not match screen %dx%d",
width, height, G.scr_var.xres, G.scr_var.yres);
line_size = width*3;
if (width > G.scr_var.xres)
width = G.scr_var.xres;
if (height > G.scr_var.yres)
height = G.scr_var.yres;
pixline = xmalloc(line_size);
for (j = 0; j < height; j++) {
unsigned char *pixel = pixline;
DATA *src = (DATA *)(G.addr + j * G.scr_fix.line_length);
if (fread(pixline, 1, line_size, theme_file) != line_size)
bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
for (i = 0; i < width; i++) {
unsigned thispix;
thispix = (((unsigned)pixel[0] << 8) & 0xf800)
| (((unsigned)pixel[1] << 3) & 0x07e0)
| (((unsigned)pixel[2] >> 3));
*src++ = thispix;
pixel += 3;
}
}
if (ENABLE_FEATURE_CLEAN_UP)
free(pixline);
fclose(theme_file);
}
/**
* Parse configuration file
* \param *cfg_filename name of the configuration file
*/
static void init(const char *cfg_filename)
{
static const char const param_names[] ALIGN1 =
"BAR_WIDTH\0" "BAR_HEIGHT\0"
"BAR_LEFT\0" "BAR_TOP\0"
"BAR_R\0" "BAR_G\0" "BAR_B\0"
#if DEBUG
"DEBUG\0"
#endif
;
char *token[2];
parser_t *parser = config_open2(cfg_filename, xfopen_stdin);
while (config_read(parser, token, 2, 2, "#=",
(PARSE_NORMAL | PARSE_MIN_DIE) & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
unsigned val = xatoi_u(token[1]);
int i = index_in_strings(param_names, token[0]);
if (i < 0)
bb_error_msg_and_die("syntax error: %s", token[0]);
if (i >= 0 && i < 7)
G.ns[i] = val;
#if DEBUG
if (i == 7) {
G.bdebug_messages = val;
if (G.bdebug_messages)
G.logfile_fd = xfopen_for_write("/tmp/fbsplash.log");
}
#endif
}
config_close(parser);
}
int fbsplash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int fbsplash_main(int argc UNUSED_PARAM, char **argv)
{
const char *fb_device, *cfg_filename, *fifo_filename;
FILE *fp = fp; // for compiler
char *num_buf;
unsigned num;
bool bCursorOff;
INIT_G();
// parse command line options
fb_device = "/dev/fb0";
cfg_filename = NULL;
fifo_filename = NULL;
bCursorOff = 1 & getopt32(argv, "cs:d:i:f:",
&G.image_filename, &fb_device, &cfg_filename, &fifo_filename);
// parse configuration file
if (cfg_filename)
init(cfg_filename);
// We must have -s IMG
if (!G.image_filename)
bb_show_usage();
fb_open(fb_device);
if (fifo_filename && bCursorOff) {
// hide cursor (BEFORE any fb ops)
full_write(STDOUT_FILENO, "\x1b" "[?25l", 6);
}
fb_drawimage();
if (!fifo_filename)
return EXIT_SUCCESS;
fp = xfopen_stdin(fifo_filename);
if (fp != stdin) {
// For named pipes, we want to support this:
// mkfifo cmd_pipe
// fbsplash -f cmd_pipe .... &
// ...
// echo 33 >cmd_pipe
// ...
// echo 66 >cmd_pipe
// This means that we don't want fbsplash to get EOF
// when last writer closes input end.
// The simplest way is to open fifo for writing too
// and become an additional writer :)
open(fifo_filename, O_WRONLY); // errors are ignored
}
fb_drawprogressbar(0);
// Block on read, waiting for some input.
// Use of <stdio.h> style I/O allows to correctly
// handle a case when we have many buffered lines
// already in the pipe
while ((num_buf = xmalloc_fgetline(fp)) != NULL) {
if (strncmp(num_buf, "exit", 4) == 0) {
DEBUG_MESSAGE("exit");
break;
}
num = atoi(num_buf);
if (isdigit(num_buf[0]) && (num <= 100)) {
#if DEBUG
char strVal[10];
sprintf(strVal, "%d", num);
DEBUG_MESSAGE(strVal);
#endif
fb_drawprogressbar(num);
}
free(num_buf);
}
if (bCursorOff) // restore cursor
full_write(STDOUT_FILENO, "\x1b" "[?25h", 6);
return EXIT_SUCCESS;
}