blob: 383b82929988da430408fdeac197560ce8144421
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 | |
42 | struct 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 | */ |
84 | static 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 | */ |
123 | static 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 | */ |
168 | static 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 | */ |
198 | static 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 | */ |
221 | static 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 | */ |
262 | static 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 | */ |
290 | static 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 | */ |
346 | static 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, /*fail_if_not_compressed:*/ 0); |
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 | */ |
427 | static 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 | |
459 | int fbsplash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
460 | int fbsplash_main(int argc UNUSED_PARAM, char **argv) |
461 | { |
462 | const char *fb_device, *cfg_filename, *fifo_filename; |
463 | FILE *fp = NULL; |
464 | char *num_buf; |
465 | unsigned num; |
466 | bool bCursorOff; |
467 | |
468 | INIT_G(); |
469 | |
470 | // parse command line options |
471 | fb_device = "/dev/graphics/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 |