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 source tree.
   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//usage:#define fbsplash_trivial_usage
  25//usage:       "-s IMGFILE [-c] [-d DEV] [-i INIFILE] [-f CMD]"
  26//usage:#define fbsplash_full_usage "\n\n"
  27//usage:       "        -s      Image"
  28//usage:     "\n        -c      Hide cursor"
  29//usage:     "\n        -d      Framebuffer device (default /dev/fb0)"
  30//usage:     "\n        -i      Config file (var=value):"
  31//usage:     "\n                        BAR_LEFT,BAR_TOP,BAR_WIDTH,BAR_HEIGHT"
  32//usage:     "\n                        BAR_R,BAR_G,BAR_B"
  33//usage:     "\n        -f      Control pipe (else exit after drawing image)"
  34//usage:     "\n                        commands: 'NN' (% for progress bar) or 'exit'"
  35
  36#include "libbb.h"
  37#include <linux/fb.h>
  38
  39/* If you want logging messages on /tmp/fbsplash.log... */
  40#define DEBUG 0
  41
  42struct globals {
  43#if DEBUG
  44        bool bdebug_messages;   // enable/disable logging
  45        FILE *logfile_fd;       // log file
  46#endif
  47        unsigned char *addr;    // pointer to framebuffer memory
  48        unsigned ns[7];         // n-parameters
  49        const char *image_filename;
  50        struct fb_var_screeninfo scr_var;
  51        struct fb_fix_screeninfo scr_fix;
  52        unsigned bytes_per_pixel;
  53        // cached (8 - scr_var.COLOR.length):
  54        unsigned red_shift;
  55        unsigned green_shift;
  56        unsigned blue_shift;
  57};
  58#define G (*ptr_to_globals)
  59#define INIT_G() do { \
  60        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
  61} while (0)
  62
  63#define nbar_width      ns[0]   // progress bar width
  64#define nbar_height     ns[1]   // progress bar height
  65#define nbar_posx       ns[2]   // progress bar horizontal position
  66#define nbar_posy       ns[3]   // progress bar vertical position
  67#define nbar_colr       ns[4]   // progress bar color red component
  68#define nbar_colg       ns[5]   // progress bar color green component
  69#define nbar_colb       ns[6]   // progress bar color blue component
  70
  71#if DEBUG
  72#define DEBUG_MESSAGE(strMessage, args...) \
  73        if (G.bdebug_messages) { \
  74                fprintf(G.logfile_fd, "[%s][%s] - %s\n", \
  75                __FILE__, __FUNCTION__, strMessage);    \
  76        }
  77#else
  78#define DEBUG_MESSAGE(...) ((void)0)
  79#endif
  80
  81/**
  82 * Configure palette for RGB:332
  83 */
  84static void fb_setpal(int fd)
  85{
  86        struct fb_cmap cmap;
  87        /* fb colors are 16 bit */
  88        unsigned short red[256], green[256], blue[256];
  89        unsigned i;
  90
  91        /* RGB:332 */
  92        for (i = 0; i < 256; i++) {
  93                /* Color is encoded in pixel value as rrrgggbb.
  94                 * 3-bit color is mapped to 16-bit one as:
  95                 * 000 -> 00000000 00000000
  96                 * 001 -> 00100100 10010010
  97                 * ...
  98                 * 011 -> 01101101 10110110
  99                 * 100 -> 10010010 01001001
 100                 * ...
 101                 * 111 -> 11111111 11111111
 102                 */
 103                red[i]   = (( i >> 5       ) * 0x9249) >> 2; // rrr * 00 10010010 01001001 >> 2
 104                green[i] = (((i >> 2) & 0x7) * 0x9249) >> 2; // ggg * 00 10010010 01001001 >> 2
 105                /* 2-bit color is easier: */
 106                blue[i]  =  ( i       & 0x3) * 0x5555; // bb * 01010101 01010101
 107        }
 108
 109        cmap.start = 0;
 110        cmap.len   = 256;
 111        cmap.red   = red;
 112        cmap.green = green;
 113        cmap.blue  = blue;
 114        cmap.transp = 0;
 115
 116        xioctl(fd, FBIOPUTCMAP, &cmap);
 117}
 118
 119/**
 120 * Open and initialize the framebuffer device
 121 * \param *strfb_device pointer to framebuffer device
 122 */
 123static void fb_open(const char *strfb_device)
 124{
 125        int fbfd = xopen(strfb_device, O_RDWR);
 126
 127        // framebuffer properties
 128        xioctl(fbfd, FBIOGET_VSCREENINFO, &G.scr_var);
 129        xioctl(fbfd, FBIOGET_FSCREENINFO, &G.scr_fix);
 130
 131        switch (G.scr_var.bits_per_pixel) {
 132        case 8:
 133                fb_setpal(fbfd);
 134                break;
 135
 136        case 16:
 137        case 24:
 138        case 32:
 139                break;
 140
 141        default:
 142                bb_error_msg_and_die("unsupported %u bpp", (int)G.scr_var.bits_per_pixel);
 143                break;
 144        }
 145
 146        G.red_shift   = 8 - G.scr_var.red.length;
 147        G.green_shift = 8 - G.scr_var.green.length;
 148        G.blue_shift  = 8 - G.scr_var.blue.length;
 149        G.bytes_per_pixel = (G.scr_var.bits_per_pixel + 7) >> 3;
 150
 151        // map the device in memory
 152        G.addr = mmap(NULL,
 153                        G.scr_var.yres * G.scr_fix.line_length,
 154                        PROT_WRITE, MAP_SHARED, fbfd, 0);
 155        if (G.addr == MAP_FAILED)
 156                bb_perror_msg_and_die("mmap");
 157
 158        // point to the start of the visible screen
 159        G.addr += G.scr_var.yoffset * G.scr_fix.line_length + G.scr_var.xoffset * G.bytes_per_pixel;
 160        close(fbfd);
 161}
 162
 163
 164/**
 165 * Return pixel value of the passed RGB color.
 166 * This is performance critical fn.
 167 */
 168static unsigned fb_pixel_value(unsigned r, unsigned g, unsigned b)
 169{
 170        /* We assume that the r,g,b values are <= 255 */
 171
 172        if (G.bytes_per_pixel == 1) {
 173                r = r        & 0xe0; // 3-bit red
 174                g = (g >> 3) & 0x1c; // 3-bit green
 175                b =  b >> 6;         // 2-bit blue
 176                return r + g + b;
 177        }
 178        if (G.bytes_per_pixel == 2) {
 179                // ARM PL110 on Integrator/CP has RGBA5551 bit arrangement.
 180                // We want to support bit locations like that.
 181                //
 182                // First shift out unused bits
 183                r = r >> G.red_shift;
 184                g = g >> G.green_shift;
 185                b = b >> G.blue_shift;
 186                // Then shift the remaining bits to their offset
 187                return (r << G.scr_var.red.offset) +
 188                        (g << G.scr_var.green.offset) +
 189                        (b << G.scr_var.blue.offset);
 190        }
 191        // RGB 888
 192        return b + (g << 8) + (r << 16);
 193}
 194
 195/**
 196 * Draw pixel on framebuffer
 197 */
 198static void fb_write_pixel(unsigned char *addr, unsigned pixel)
 199{
 200        switch (G.bytes_per_pixel) {
 201        case 1:
 202                *addr = pixel;
 203                break;
 204        case 2:
 205                *(uint16_t *)addr = pixel;
 206                break;
 207        case 4:
 208                *(uint32_t *)addr = pixel;
 209                break;
 210        default: // 24 bits per pixel
 211                addr[0] = pixel;
 212                addr[1] = pixel >> 8;
 213                addr[2] = pixel >> 16;
 214        }
 215}
 216
 217
 218/**
 219 * Draw hollow rectangle on framebuffer
 220 */
 221static void fb_drawrectangle(void)
 222{
 223        int cnt;
 224        unsigned thispix;
 225        unsigned char *ptr1, *ptr2;
 226        unsigned char nred = G.nbar_colr/2;
 227        unsigned char ngreen =  G.nbar_colg/2;
 228        unsigned char nblue = G.nbar_colb/2;
 229
 230        thispix = fb_pixel_value(nred, ngreen, nblue);
 231
 232        // horizontal lines
 233        ptr1 = G.addr + G.nbar_posy * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel;
 234        ptr2 = G.addr + (G.nbar_posy + G.nbar_height - 1) * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel;
 235        cnt = G.nbar_width - 1;
 236        do {
 237                fb_write_pixel(ptr1, thispix);
 238                fb_write_pixel(ptr2, thispix);
 239                ptr1 += G.bytes_per_pixel;
 240                ptr2 += G.bytes_per_pixel;
 241        } while (--cnt >= 0);
 242
 243        // vertical lines
 244        ptr1 = G.addr + G.nbar_posy * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel;
 245        ptr2 = G.addr + G.nbar_posy * G.scr_fix.line_length + (G.nbar_posx + G.nbar_width - 1) * G.bytes_per_pixel;
 246        cnt = G.nbar_height - 1;
 247        do {
 248                fb_write_pixel(ptr1, thispix);
 249                fb_write_pixel(ptr2, thispix);
 250                ptr1 += G.scr_fix.line_length;
 251                ptr2 += G.scr_fix.line_length;
 252        } while (--cnt >= 0);
 253}
 254
 255
 256/**
 257 * Draw filled rectangle on framebuffer
 258 * \param nx1pos,ny1pos upper left position
 259 * \param nx2pos,ny2pos down right position
 260 * \param nred,ngreen,nblue rgb color
 261 */
 262static void fb_drawfullrectangle(int nx1pos, int ny1pos, int nx2pos, int ny2pos,
 263        unsigned char nred, unsigned char ngreen, unsigned char nblue)
 264{
 265        int cnt1, cnt2, nypos;
 266        unsigned thispix;
 267        unsigned char *ptr;
 268
 269        thispix = fb_pixel_value(nred, ngreen, nblue);
 270
 271        cnt1 = ny2pos - ny1pos;
 272        nypos = ny1pos;
 273        do {
 274                ptr = G.addr + nypos * G.scr_fix.line_length + nx1pos * G.bytes_per_pixel;
 275                cnt2 = nx2pos - nx1pos;
 276                do {
 277                        fb_write_pixel(ptr, thispix);
 278                        ptr += G.bytes_per_pixel;
 279                } while (--cnt2 >= 0);
 280
 281                nypos++;
 282        } while (--cnt1 >= 0);
 283}
 284
 285
 286/**
 287 * Draw a progress bar on framebuffer
 288 * \param percent percentage of loading
 289 */
 290static void fb_drawprogressbar(unsigned percent)
 291{
 292        int left_x, top_y, pos_x;
 293        unsigned width, height;
 294
 295        // outer box
 296        left_x = G.nbar_posx;
 297        top_y = G.nbar_posy;
 298        width = G.nbar_width - 1;
 299        height = G.nbar_height - 1;
 300        if ((int)(height | width) < 0)
 301                return;
 302        // NB: "width" of 1 actually makes rect with width of 2!
 303        fb_drawrectangle();
 304
 305        // inner "empty" rectangle
 306        left_x++;
 307        top_y++;
 308        width -= 2;
 309        height -= 2;
 310        if ((int)(height | width) < 0)
 311                return;
 312
 313        pos_x = left_x;
 314        if (percent > 0) {
 315                int i, y;
 316
 317                // actual progress bar
 318                pos_x += (unsigned)(width * percent) / 100;
 319
 320                y = top_y;
 321                i = height;
 322                if (height == 0)
 323                        height++; // divide by 0 is bad
 324                while (i >= 0) {
 325                        // draw one-line thick "rectangle"
 326                        // top line will have gray lvl 200, bottom one 100
 327                        unsigned gray_level = 100 + (unsigned)i*100 / height;
 328                        fb_drawfullrectangle(
 329                                        left_x, y, pos_x, y,
 330                                        gray_level, gray_level, gray_level);
 331                        y++;
 332                        i--;
 333                }
 334        }
 335
 336        fb_drawfullrectangle(
 337                        pos_x, top_y,
 338                        left_x + width, top_y + height,
 339                        G.nbar_colr, G.nbar_colg, G.nbar_colb);
 340}
 341
 342
 343/**
 344 * Draw image from PPM file
 345 */
 346static void fb_drawimage(void)
 347{
 348        FILE *theme_file;
 349        char *read_ptr;
 350        unsigned char *pixline;
 351        unsigned i, j, width, height, line_size;
 352
 353        if (LONE_DASH(G.image_filename)) {
 354                theme_file = stdin;
 355        } else {
 356                int fd = open_zipped(G.image_filename);
 357                if (fd < 0)
 358                        bb_simple_perror_msg_and_die(G.image_filename);
 359                theme_file = xfdopen_for_read(fd);
 360        }
 361
 362        /* Parse ppm header:
 363         * - Magic: two characters "P6".
 364         * - Whitespace (blanks, TABs, CRs, LFs).
 365         * - A width, formatted as ASCII characters in decimal.
 366         * - Whitespace.
 367         * - A height, ASCII decimal.
 368         * - Whitespace.
 369         * - The maximum color value, ASCII decimal, in 0..65535
 370         * - Newline or other single whitespace character.
 371         *   (we support newline only)
 372         * - A raster of Width * Height pixels in triplets of rgb
 373         *   in pure binary by 1 or 2 bytes. (we support only 1 byte)
 374         */
 375#define concat_buf bb_common_bufsiz1
 376        read_ptr = concat_buf;
 377        while (1) {
 378                int w, h, max_color_val;
 379                int rem = concat_buf + sizeof(concat_buf) - read_ptr;
 380                if (rem < 2
 381                 || fgets(read_ptr, rem, theme_file) == NULL
 382                ) {
 383                        bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
 384                }
 385                read_ptr = strchrnul(read_ptr, '#');
 386                *read_ptr = '\0'; /* ignore #comments */
 387                if (sscanf(concat_buf, "P6 %u %u %u", &w, &h, &max_color_val) == 3
 388                 && max_color_val <= 255
 389                ) {
 390                        width = w; /* w is on stack, width may be in register */
 391                        height = h;
 392                        break;
 393                }
 394        }
 395
 396        line_size = width*3;
 397        pixline = xmalloc(line_size);
 398
 399        if (width > G.scr_var.xres)
 400                width = G.scr_var.xres;
 401        if (height > G.scr_var.yres)
 402                height = G.scr_var.yres;
 403        for (j = 0; j < height; j++) {
 404                unsigned char *pixel;
 405                unsigned char *src;
 406
 407                if (fread(pixline, 1, line_size, theme_file) != line_size)
 408                        bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
 409                pixel = pixline;
 410                src = G.addr + j * G.scr_fix.line_length;
 411                for (i = 0; i < width; i++) {
 412                        unsigned thispix = fb_pixel_value(pixel[0], pixel[1], pixel[2]);
 413                        fb_write_pixel(src, thispix);
 414                        src += G.bytes_per_pixel;
 415                        pixel += 3;
 416                }
 417        }
 418        free(pixline);
 419        fclose(theme_file);
 420}
 421
 422
 423/**
 424 * Parse configuration file
 425 * \param *cfg_filename name of the configuration file
 426 */
 427static void init(const char *cfg_filename)
 428{
 429        static const char param_names[] ALIGN1 =
 430                "BAR_WIDTH\0" "BAR_HEIGHT\0"
 431                "BAR_LEFT\0" "BAR_TOP\0"
 432                "BAR_R\0" "BAR_G\0" "BAR_B\0"
 433#if DEBUG
 434                "DEBUG\0"
 435#endif
 436                ;
 437        char *token[2];
 438        parser_t *parser = config_open2(cfg_filename, xfopen_stdin);
 439        while (config_read(parser, token, 2, 2, "#=",
 440                                (PARSE_NORMAL | PARSE_MIN_DIE) & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
 441                unsigned val = xatoi_positive(token[1]);
 442                int i = index_in_strings(param_names, token[0]);
 443                if (i < 0)
 444                        bb_error_msg_and_die("syntax error: %s", token[0]);
 445                if (i >= 0 && i < 7)
 446                        G.ns[i] = val;
 447#if DEBUG
 448                if (i == 7) {
 449                        G.bdebug_messages = val;
 450                        if (G.bdebug_messages)
 451                                G.logfile_fd = xfopen_for_write("/tmp/fbsplash.log");
 452                }
 453#endif
 454        }
 455        config_close(parser);
 456}
 457
 458
 459int fbsplash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 460int fbsplash_main(int argc UNUSED_PARAM, char **argv)
 461{
 462        const char *fb_device, *cfg_filename, *fifo_filename;
 463        FILE *fp = fp; // for compiler
 464        char *num_buf;
 465        unsigned num;
 466        bool bCursorOff;
 467
 468        INIT_G();
 469
 470        // parse command line options
 471        fb_device = "/dev/fb0";
 472        cfg_filename = NULL;
 473        fifo_filename = NULL;
 474        bCursorOff = 1 & getopt32(argv, "cs:d:i:f:",
 475                        &G.image_filename, &fb_device, &cfg_filename, &fifo_filename);
 476
 477        // parse configuration file
 478        if (cfg_filename)
 479                init(cfg_filename);
 480
 481        // We must have -s IMG
 482        if (!G.image_filename)
 483                bb_show_usage();
 484
 485        fb_open(fb_device);
 486
 487        if (fifo_filename && bCursorOff) {
 488                // hide cursor (BEFORE any fb ops)
 489                full_write(STDOUT_FILENO, "\033[?25l", 6);
 490        }
 491
 492        fb_drawimage();
 493
 494        if (!fifo_filename)
 495                return EXIT_SUCCESS;
 496
 497        fp = xfopen_stdin(fifo_filename);
 498        if (fp != stdin) {
 499                // For named pipes, we want to support this:
 500                //  mkfifo cmd_pipe
 501                //  fbsplash -f cmd_pipe .... &
 502                //  ...
 503                //  echo 33 >cmd_pipe
 504                //  ...
 505                //  echo 66 >cmd_pipe
 506                // This means that we don't want fbsplash to get EOF
 507                // when last writer closes input end.
 508                // The simplest way is to open fifo for writing too
 509                // and become an additional writer :)
 510                open(fifo_filename, O_WRONLY); // errors are ignored
 511        }
 512
 513        fb_drawprogressbar(0);
 514        // Block on read, waiting for some input.
 515        // Use of <stdio.h> style I/O allows to correctly
 516        // handle a case when we have many buffered lines
 517        // already in the pipe
 518        while ((num_buf = xmalloc_fgetline(fp)) != NULL) {
 519                if (strncmp(num_buf, "exit", 4) == 0) {
 520                        DEBUG_MESSAGE("exit");
 521                        break;
 522                }
 523                num = atoi(num_buf);
 524                if (isdigit(num_buf[0]) && (num <= 100)) {
 525#if DEBUG
 526                        DEBUG_MESSAGE(itoa(num));
 527#endif
 528                        fb_drawprogressbar(num);
 529                }
 530                free(num_buf);
 531        }
 532
 533        if (bCursorOff) // restore cursor
 534                full_write(STDOUT_FILENO, "\033[?25h", 6);
 535
 536        return EXIT_SUCCESS;
 537}
 538