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