busybox/miscutils/fbsplash.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Copyright (C) 2008 Michele Sanges <michele.sanges@gmail.com>
   4 *
   5 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
   6 *
   7 * Usage:
   8 * - use kernel option 'vga=xxx' or otherwise enable framebuffer device.
   9 * - put somewhere fbsplash.cfg file and an image in .ppm format.
  10 * - run applet: $ setsid fbsplash [params] &
  11 *      -c: hide cursor
  12 *      -d /dev/fbN: framebuffer device (if not /dev/fb0)
  13 *      -s path_to_image_file (can be "-" for stdin)
  14 *      -i path_to_cfg_file
  15 *      -f path_to_fifo (can be "-" for stdin)
  16 * - if you want to run it only in presence of a kernel parameter
  17 *   (for example fbsplash=on), use:
  18 *   grep -q "fbsplash=on" </proc/cmdline && setsid fbsplash [params]
  19 * - commands for fifo:
  20 *   "NN" (ASCII decimal number) - percentage to show on progress bar.
  21 *   "exit" (or just close fifo) - well you guessed it.
  22 */
  23
  24#include "libbb.h"
  25#include <linux/fb.h>
  26
  27/* If you want logging messages on /tmp/fbsplash.log... */
  28#define DEBUG 0
  29
  30#define BYTES_PER_PIXEL 2
  31
  32typedef unsigned short DATA;
  33
  34struct globals {
  35#if DEBUG
  36        bool bdebug_messages;   // enable/disable logging
  37        FILE *logfile_fd;       // log file
  38#endif
  39        unsigned char *addr;    // pointer to framebuffer memory
  40        unsigned ns[7];         // n-parameters
  41        const char *image_filename;
  42        struct fb_var_screeninfo scr_var;
  43        struct fb_fix_screeninfo scr_fix;
  44};
  45#define G (*ptr_to_globals)
  46#define INIT_G() do { \
  47        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
  48} while (0)
  49
  50#define nbar_width      ns[0]   // progress bar width
  51#define nbar_height     ns[1]   // progress bar height
  52#define nbar_posx       ns[2]   // progress bar horizontal position
  53#define nbar_posy       ns[3]   // progress bar vertical position
  54#define nbar_colr       ns[4]   // progress bar color red component
  55#define nbar_colg       ns[5]   // progress bar color green component
  56#define nbar_colb       ns[6]   // progress bar color blue component
  57
  58#if DEBUG
  59#define DEBUG_MESSAGE(strMessage, args...) \
  60        if (G.bdebug_messages) { \
  61                fprintf(G.logfile_fd, "[%s][%s] - %s\n", \
  62                __FILE__, __FUNCTION__, strMessage);    \
  63        }
  64#else
  65#define DEBUG_MESSAGE(...) ((void)0)
  66#endif
  67
  68
  69/**
  70 *      Open and initialize the framebuffer device
  71 * \param *strfb_device pointer to framebuffer device
  72 */
  73static void fb_open(const char *strfb_device)
  74{
  75        int fbfd = xopen(strfb_device, O_RDWR);
  76
  77        // framebuffer properties
  78        xioctl(fbfd, FBIOGET_VSCREENINFO, &G.scr_var);
  79        xioctl(fbfd, FBIOGET_FSCREENINFO, &G.scr_fix);
  80
  81        if (G.scr_var.bits_per_pixel != 16)
  82                bb_error_msg_and_die("only 16 bpp is supported");
  83
  84        // map the device in memory
  85        G.addr = mmap(NULL,
  86                        G.scr_var.xres * G.scr_var.yres
  87                        * BYTES_PER_PIXEL /*(G.scr_var.bits_per_pixel / 8)*/ ,
  88                        PROT_WRITE, MAP_SHARED, fbfd, 0);
  89        if (G.addr == MAP_FAILED)
  90                bb_perror_msg_and_die("mmap");
  91        close(fbfd);
  92}
  93
  94
  95/**
  96 *      Draw hollow rectangle on framebuffer
  97 */
  98static void fb_drawrectangle(void)
  99{
 100        int cnt;
 101        DATA thispix;
 102        DATA *ptr1, *ptr2;
 103        unsigned char nred = G.nbar_colr/2;
 104        unsigned char ngreen =  G.nbar_colg/2;
 105        unsigned char nblue = G.nbar_colb/2;
 106
 107        nred   >>= 3;  // 5-bit red
 108        ngreen >>= 2;  // 6-bit green
 109        nblue  >>= 3;  // 5-bit blue
 110        thispix = nblue + (ngreen << 5) + (nred << (5+6));
 111
 112        // horizontal lines
 113        ptr1 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
 114        ptr2 = (DATA*)(G.addr + ((G.nbar_posy + G.nbar_height - 1) * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
 115        cnt = G.nbar_width - 1;
 116        do {
 117                *ptr1++ = thispix;
 118                *ptr2++ = thispix;
 119        } while (--cnt >= 0);
 120
 121        // vertical lines
 122        ptr1 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
 123        ptr2 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx + G.nbar_width - 1) * BYTES_PER_PIXEL);
 124        cnt = G.nbar_height - 1 /* HUH?!  G.nbar_posy + G.nbar_height - 1 - G.nbar_posy*/;
 125        do {
 126                *ptr1 = thispix; ptr1 += G.scr_var.xres;
 127                *ptr2 = thispix; ptr2 += G.scr_var.xres;
 128        } while (--cnt >= 0);
 129}
 130
 131
 132/**
 133 *      Draw filled rectangle on framebuffer
 134 * \param nx1pos,ny1pos upper left position
 135 * \param nx2pos,ny2pos down right position
 136 * \param nred,ngreen,nblue rgb color
 137 */
 138static void fb_drawfullrectangle(int nx1pos, int ny1pos, int nx2pos, int ny2pos,
 139        unsigned char nred, unsigned char ngreen, unsigned char nblue)
 140{
 141        int cnt1, cnt2, nypos;
 142        DATA thispix;
 143        DATA *ptr;
 144
 145        nred   >>= 3;  // 5-bit red
 146        ngreen >>= 2;  // 6-bit green
 147        nblue  >>= 3;  // 5-bit blue
 148        thispix = nblue + (ngreen << 5) + (nred << (5+6));
 149
 150        cnt1 = ny2pos - ny1pos;
 151        nypos = ny1pos;
 152        do {
 153                ptr = (DATA*)(G.addr + (nypos * G.scr_var.xres + nx1pos) * BYTES_PER_PIXEL);
 154                cnt2 = nx2pos - nx1pos;
 155                do {
 156                        *ptr++ = thispix;
 157                } while (--cnt2 >= 0);
 158
 159                nypos++;
 160        } while (--cnt1 >= 0);
 161}
 162
 163
 164/**
 165 *      Draw a progress bar on framebuffer
 166 * \param percent percentage of loading
 167 */
 168static void fb_drawprogressbar(unsigned percent)
 169{
 170        int i, left_x, top_y, width, height;
 171
 172        // outer box
 173        left_x = G.nbar_posx;
 174        top_y = G.nbar_posy;
 175        width = G.nbar_width - 1;
 176        height = G.nbar_height - 1;
 177        if ((height | width) < 0)
 178                return;
 179        // NB: "width" of 1 actually makes rect with width of 2!
 180        fb_drawrectangle();
 181
 182        // inner "empty" rectangle
 183        left_x++;
 184        top_y++;
 185        width -= 2;
 186        height -= 2;
 187        if ((height | width) < 0)
 188                return;
 189        fb_drawfullrectangle(
 190                        left_x, top_y,
 191                                        left_x + width, top_y + height,
 192                        G.nbar_colr, G.nbar_colg, G.nbar_colb);
 193
 194        if (percent > 0) {
 195                // actual progress bar
 196                width = width * percent / 100;
 197                i = height;
 198                if (height == 0)
 199                        height++; // divide by 0 is bad
 200                while (i >= 0) {
 201                        // draw one-line thick "rectangle"
 202                        // top line will have gray lvl 200, bottom one 100
 203                        unsigned gray_level = 100 + i*100/height;
 204                        fb_drawfullrectangle(
 205                                        left_x, top_y, left_x + width, top_y,
 206                                        gray_level, gray_level, gray_level);
 207                        top_y++;
 208                        i--;
 209                }
 210        }
 211}
 212
 213
 214/**
 215 *      Draw image from PPM file
 216 */
 217static void fb_drawimage(void)
 218{
 219        char *head, *ptr;
 220        FILE *theme_file;
 221        unsigned char *pixline;
 222        unsigned i, j, width, height, line_size;
 223
 224        theme_file = xfopen_stdin(G.image_filename);
 225        head = xmalloc(256);
 226
 227        /* parse ppm header
 228         * - A ppm image’s magic number is the two characters "P6".
 229         * - Whitespace (blanks, TABs, CRs, LFs).
 230         * - A width, formatted as ASCII characters in decimal.
 231         * - Whitespace.
 232         * - A height, again in ASCII decimal.
 233         * - Whitespace.
 234         * - The maximum color value (Maxval), again in ASCII decimal. Must be
 235         *   less than 65536.
 236         * - Newline or other single whitespace character.
 237         * - A raster of Width * Height pixels in triplets of rgb
 238         *   in pure binary by 1 (or not implemented 2) bytes.
 239         */
 240        while (1) {
 241                if (fgets(head, 256, theme_file) == NULL
 242                        /* do not overrun the buffer */
 243                        || strlen(bb_common_bufsiz1) >= sizeof(bb_common_bufsiz1) - 256)
 244                        bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
 245
 246                ptr = memchr(skip_whitespace(head), '#', 256);
 247                if (ptr != NULL)
 248                        *ptr = 0; /* ignore comments */
 249                strcat(bb_common_bufsiz1, head);
 250                // width, height, max_color_val
 251                if (sscanf(bb_common_bufsiz1, "P6 %u %u %u", &width, &height, &i) == 3
 252                        && i <= 255)
 253                        break;
 254                /* If we do not find a signature throughout the whole file then
 255                   we will diagnose this via EOF on read in the head of the loop.  */
 256        }
 257
 258        if (ENABLE_FEATURE_CLEAN_UP)
 259                free(head);
 260        if (width != G.scr_var.xres || height != G.scr_var.yres)
 261                bb_error_msg_and_die("PPM %dx%d does not match screen %dx%d",
 262                                                         width, height, G.scr_var.xres, G.scr_var.yres);
 263        line_size = width*3;
 264        if (width > G.scr_var.xres)
 265                width = G.scr_var.xres;
 266        if (height > G.scr_var.yres)
 267                height = G.scr_var.yres;
 268
 269        pixline = xmalloc(line_size);
 270        for (j = 0; j < height; j++) {
 271                unsigned char *pixel = pixline;
 272                DATA *src = (DATA *)(G.addr + j * G.scr_fix.line_length);
 273
 274                if (fread(pixline, 1, line_size, theme_file) != line_size)
 275                        bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
 276                for (i = 0; i < width; i++) {
 277                        unsigned thispix;
 278                        thispix = (((unsigned)pixel[0] << 8) & 0xf800)
 279                                | (((unsigned)pixel[1] << 3) & 0x07e0)
 280                                | (((unsigned)pixel[2] >> 3));
 281                        *src++ = thispix;
 282                        pixel += 3;
 283                }
 284        }
 285        if (ENABLE_FEATURE_CLEAN_UP)
 286                free(pixline);
 287        fclose(theme_file);
 288}
 289
 290
 291/**
 292 *      Parse configuration file
 293 * \param *cfg_filename name of the configuration file
 294 */
 295static void init(const char *cfg_filename)
 296{
 297        static const char const param_names[] ALIGN1 =
 298                "BAR_WIDTH\0" "BAR_HEIGHT\0"
 299                "BAR_LEFT\0" "BAR_TOP\0"
 300                "BAR_R\0" "BAR_G\0" "BAR_B\0"
 301#if DEBUG
 302                "DEBUG\0"
 303#endif
 304                ;
 305        char *token[2];
 306        parser_t *parser = config_open2(cfg_filename, xfopen_stdin);
 307        while (config_read(parser, token, 2, 2, "#=",
 308                                    (PARSE_NORMAL | PARSE_MIN_DIE) & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
 309                unsigned val = xatoi_u(token[1]);
 310                int i = index_in_strings(param_names, token[0]);
 311                if (i < 0)
 312                        bb_error_msg_and_die("syntax error: %s", token[0]);
 313                if (i >= 0 && i < 7)
 314                        G.ns[i] = val;
 315#if DEBUG
 316                if (i == 7) {
 317                        G.bdebug_messages = val;
 318                        if (G.bdebug_messages)
 319                                G.logfile_fd = xfopen_for_write("/tmp/fbsplash.log");
 320                }
 321#endif
 322        }
 323        config_close(parser);
 324}
 325
 326
 327int fbsplash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 328int fbsplash_main(int argc UNUSED_PARAM, char **argv)
 329{
 330        const char *fb_device, *cfg_filename, *fifo_filename;
 331        FILE *fp = fp; // for compiler
 332        char *num_buf;
 333        unsigned num;
 334        bool bCursorOff;
 335
 336        INIT_G();
 337
 338        // parse command line options
 339        fb_device = "/dev/fb0";
 340        cfg_filename = NULL;
 341        fifo_filename = NULL;
 342        bCursorOff = 1 & getopt32(argv, "cs:d:i:f:",
 343                        &G.image_filename, &fb_device, &cfg_filename, &fifo_filename);
 344
 345        // parse configuration file
 346        if (cfg_filename)
 347                init(cfg_filename);
 348
 349        // We must have -s IMG
 350        if (!G.image_filename)
 351                bb_show_usage();
 352
 353        fb_open(fb_device);
 354
 355        if (fifo_filename && bCursorOff) {
 356                // hide cursor (BEFORE any fb ops)
 357                full_write(STDOUT_FILENO, "\x1b" "[?25l", 6);
 358        }
 359
 360        fb_drawimage();
 361
 362        if (!fifo_filename)
 363                return EXIT_SUCCESS;
 364
 365        fp = xfopen_stdin(fifo_filename);
 366        if (fp != stdin) {
 367                // For named pipes, we want to support this:
 368                //  mkfifo cmd_pipe
 369                //  fbsplash -f cmd_pipe .... &
 370                //  ...
 371                //  echo 33 >cmd_pipe
 372                //  ...
 373                //  echo 66 >cmd_pipe
 374                // This means that we don't want fbsplash to get EOF
 375                // when last writer closes input end.
 376                // The simplest way is to open fifo for writing too
 377                // and become an additional writer :)
 378                open(fifo_filename, O_WRONLY); // errors are ignored
 379        }
 380
 381        fb_drawprogressbar(0);
 382        // Block on read, waiting for some input.
 383        // Use of <stdio.h> style I/O allows to correctly
 384        // handle a case when we have many buffered lines
 385        // already in the pipe
 386        while ((num_buf = xmalloc_fgetline(fp)) != NULL) {
 387                if (strncmp(num_buf, "exit", 4) == 0) {
 388                        DEBUG_MESSAGE("exit");
 389                        break;
 390                }
 391                num = atoi(num_buf);
 392                if (isdigit(num_buf[0]) && (num <= 100)) {
 393#if DEBUG
 394                        char strVal[10];
 395                        sprintf(strVal, "%d", num);
 396                        DEBUG_MESSAGE(strVal);
 397#endif
 398                        fb_drawprogressbar(num);
 399                }
 400                free(num_buf);
 401        }
 402
 403        if (bCursorOff) // restore cursor
 404                full_write(STDOUT_FILENO, "\x1b" "[?25h", 6);
 405
 406        return EXIT_SUCCESS;
 407}
 408