From 4aca87515a5083ae0e31ce3177189fd43b6d05ac Mon Sep 17 00:00:00 2001 From: Andreas Baumann Date: Sat, 3 Jan 2015 13:58:15 +0100 Subject: patch to Vanilla Tomato 1.28 --- release/src/router/busybox/miscutils/fbsplash.c | 407 ++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 release/src/router/busybox/miscutils/fbsplash.c (limited to 'release/src/router/busybox/miscutils/fbsplash.c') diff --git a/release/src/router/busybox/miscutils/fbsplash.c b/release/src/router/busybox/miscutils/fbsplash.c new file mode 100644 index 00000000..ec0f092d --- /dev/null +++ b/release/src/router/busybox/miscutils/fbsplash.c @@ -0,0 +1,407 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2008 Michele Sanges + * + * 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" + +/* 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 image’s 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 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; +} -- cgit v1.2.3-54-g00ecf