source: MondoRescue/branches/3.3/mindi-busybox/miscutils/fbsplash.c@ 3647

Last change on this file since 3647 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

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