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