fbsplash.c 14 KB

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