theme_loader.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. /* theme_loader.c - Theme file loader for gfxmenu. */
  2. /*
  3. * GRUB -- GRand Unified Bootloader
  4. * Copyright (C) 2008 Free Software Foundation, Inc.
  5. *
  6. * GRUB is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * GRUB is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include <grub/types.h>
  20. #include <grub/file.h>
  21. #include <grub/misc.h>
  22. #include <grub/mm.h>
  23. #include <grub/err.h>
  24. #include <grub/dl.h>
  25. #include <grub/video.h>
  26. #include <grub/gui_string_util.h>
  27. #include <grub/bitmap.h>
  28. #include <grub/bitmap_scale.h>
  29. #include <grub/gfxwidgets.h>
  30. #include <grub/gfxmenu_view.h>
  31. #include <grub/gui.h>
  32. /* Construct a new box widget using ABSPATTERN to find the pixmap files for
  33. it, storing the new box instance at *BOXPTR.
  34. PATTERN should be of the form: "(hd0,0)/somewhere/style*.png".
  35. The '*' then gets substituted with the various pixmap names that the
  36. box uses. */
  37. static grub_err_t
  38. recreate_box_absolute (grub_gfxmenu_box_t *boxptr, const char *abspattern)
  39. {
  40. char *prefix;
  41. char *suffix;
  42. char *star;
  43. grub_gfxmenu_box_t box;
  44. star = grub_strchr (abspattern, '*');
  45. if (! star)
  46. return grub_error (GRUB_ERR_BAD_ARGUMENT,
  47. "missing `*' in box pixmap pattern `%s'", abspattern);
  48. /* Prefix: Get the part before the '*'. */
  49. prefix = grub_malloc (star - abspattern + 1);
  50. if (! prefix)
  51. return grub_errno;
  52. grub_memcpy (prefix, abspattern, star - abspattern);
  53. prefix[star - abspattern] = '\0';
  54. /* Suffix: Everything after the '*' is the suffix. */
  55. suffix = star + 1;
  56. box = grub_gfxmenu_create_box (prefix, suffix);
  57. grub_free (prefix);
  58. if (! box)
  59. return grub_errno;
  60. if (*boxptr)
  61. (*boxptr)->destroy (*boxptr);
  62. *boxptr = box;
  63. return grub_errno;
  64. }
  65. /* Construct a new box widget using PATTERN to find the pixmap files for it,
  66. storing the new widget at *BOXPTR. PATTERN should be of the form:
  67. "somewhere/style*.png". The '*' then gets substituted with the various
  68. pixmap names that the widget uses.
  69. Important! The value of *BOXPTR must be initialized! It must either
  70. (1) Be 0 (a NULL pointer), or
  71. (2) Be a pointer to a valid 'grub_gfxmenu_box_t' instance.
  72. In this case, the previous instance is destroyed. */
  73. grub_err_t
  74. grub_gui_recreate_box (grub_gfxmenu_box_t *boxptr,
  75. const char *pattern, const char *theme_dir)
  76. {
  77. char *abspattern;
  78. /* Check arguments. */
  79. if (! pattern)
  80. {
  81. /* If no pixmap pattern is given, then just create an empty box. */
  82. if (*boxptr)
  83. (*boxptr)->destroy (*boxptr);
  84. *boxptr = grub_gfxmenu_create_box (0, 0);
  85. return grub_errno;
  86. }
  87. if (! theme_dir)
  88. return grub_error (GRUB_ERR_BAD_ARGUMENT,
  89. "styled box missing theme directory");
  90. /* Resolve to an absolute path. */
  91. abspattern = grub_resolve_relative_path (theme_dir, pattern);
  92. if (! abspattern)
  93. return grub_errno;
  94. /* Create the box. */
  95. recreate_box_absolute (boxptr, abspattern);
  96. grub_free (abspattern);
  97. return grub_errno;
  98. }
  99. /* Set the specified property NAME on the view to the given string VALUE.
  100. The caller is responsible for the lifetimes of NAME and VALUE. */
  101. static grub_err_t
  102. theme_set_string (grub_gfxmenu_view_t view,
  103. const char *name,
  104. const char *value,
  105. const char *theme_dir,
  106. const char *filename,
  107. int line_num,
  108. int col_num)
  109. {
  110. if (! grub_strcmp ("title-font", name))
  111. view->title_font = grub_font_get (value);
  112. else if (! grub_strcmp ("message-font", name))
  113. view->message_font = grub_font_get (value);
  114. else if (! grub_strcmp ("terminal-font", name))
  115. {
  116. grub_free (view->terminal_font_name);
  117. view->terminal_font_name = grub_strdup (value);
  118. if (! view->terminal_font_name)
  119. return grub_errno;
  120. }
  121. else if (! grub_strcmp ("title-color", name))
  122. grub_gui_parse_color (value, &view->title_color);
  123. else if (! grub_strcmp ("message-color", name))
  124. grub_gui_parse_color (value, &view->message_color);
  125. else if (! grub_strcmp ("message-bg-color", name))
  126. grub_gui_parse_color (value, &view->message_bg_color);
  127. else if (! grub_strcmp ("desktop-image", name))
  128. {
  129. struct grub_video_bitmap *raw_bitmap;
  130. struct grub_video_bitmap *scaled_bitmap;
  131. char *path;
  132. path = grub_resolve_relative_path (theme_dir, value);
  133. if (! path)
  134. return grub_errno;
  135. if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
  136. {
  137. grub_free (path);
  138. return grub_errno;
  139. }
  140. grub_free(path);
  141. grub_video_bitmap_create_scaled (&scaled_bitmap,
  142. view->screen.width,
  143. view->screen.height,
  144. raw_bitmap,
  145. GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST, 0);
  146. grub_video_bitmap_destroy (raw_bitmap);
  147. if (! scaled_bitmap)
  148. {
  149. grub_error_push ();
  150. return grub_error (grub_errno, "error scaling desktop image");
  151. }
  152. grub_video_bitmap_destroy (view->desktop_image);
  153. view->desktop_image = scaled_bitmap;
  154. }
  155. else if (! grub_strcmp ("desktop-color", name))
  156. grub_gui_parse_color (value, &view->desktop_color);
  157. else if (! grub_strcmp ("terminal-box", name))
  158. {
  159. grub_err_t err;
  160. err = grub_gui_recreate_box (&view->terminal_box, value, theme_dir);
  161. if (err != GRUB_ERR_NONE)
  162. return err;
  163. }
  164. else if (! grub_strcmp ("title-text", name))
  165. {
  166. grub_free (view->title_text);
  167. view->title_text = grub_strdup (value);
  168. if (! view->title_text)
  169. return grub_errno;
  170. }
  171. else
  172. {
  173. return grub_error (GRUB_ERR_BAD_ARGUMENT,
  174. "%s:%d:%d unknown property `%s'",
  175. filename, line_num, col_num, name);
  176. }
  177. return grub_errno;
  178. }
  179. struct parsebuf
  180. {
  181. char *buf;
  182. int pos;
  183. int len;
  184. int line_num;
  185. int col_num;
  186. const char *filename;
  187. char *theme_dir;
  188. grub_gfxmenu_view_t view;
  189. };
  190. static int
  191. has_more (struct parsebuf *p)
  192. {
  193. return p->pos < p->len;
  194. }
  195. static int
  196. read_char (struct parsebuf *p)
  197. {
  198. if (has_more (p))
  199. {
  200. char c;
  201. c = p->buf[p->pos++];
  202. if (c == '\n')
  203. {
  204. p->line_num++;
  205. p->col_num = 1;
  206. }
  207. else
  208. {
  209. p->col_num++;
  210. }
  211. return c;
  212. }
  213. else
  214. return -1;
  215. }
  216. static int
  217. peek_char (struct parsebuf *p)
  218. {
  219. if (has_more (p))
  220. return p->buf[p->pos];
  221. else
  222. return -1;
  223. }
  224. static int
  225. is_whitespace (char c)
  226. {
  227. return (c == ' '
  228. || c == '\t'
  229. || c == '\r'
  230. || c == '\n'
  231. || c == '\f');
  232. }
  233. static void
  234. skip_whitespace (struct parsebuf *p)
  235. {
  236. while (has_more (p) && is_whitespace(peek_char (p)))
  237. read_char (p);
  238. }
  239. static void
  240. advance_to_next_line (struct parsebuf *p)
  241. {
  242. int c;
  243. /* Eat characters up to the newline. */
  244. do
  245. {
  246. c = read_char (p);
  247. }
  248. while (c != -1 && c != '\n');
  249. }
  250. static int
  251. is_identifier_char (int c)
  252. {
  253. return (c != -1
  254. && (grub_isalpha(c)
  255. || grub_isdigit(c)
  256. || c == '_'
  257. || c == '-'));
  258. }
  259. static char *
  260. read_identifier (struct parsebuf *p)
  261. {
  262. /* Index of the first character of the identifier in p->buf. */
  263. int start;
  264. /* Next index after the last character of the identifer in p->buf. */
  265. int end;
  266. skip_whitespace (p);
  267. /* Capture the start of the identifier. */
  268. start = p->pos;
  269. /* Scan for the end. */
  270. while (is_identifier_char (peek_char (p)))
  271. read_char (p);
  272. end = p->pos;
  273. if (end - start < 1)
  274. return 0;
  275. return grub_new_substring (p->buf, start, end);
  276. }
  277. static char *
  278. read_expression (struct parsebuf *p)
  279. {
  280. int start;
  281. int end;
  282. skip_whitespace (p);
  283. if (peek_char (p) == '"')
  284. {
  285. /* Read as a quoted string.
  286. The quotation marks are not included in the expression value. */
  287. /* Skip opening quotation mark. */
  288. read_char (p);
  289. start = p->pos;
  290. while (has_more (p) && peek_char (p) != '"')
  291. read_char (p);
  292. end = p->pos;
  293. /* Skip the terminating quotation mark. */
  294. read_char (p);
  295. }
  296. else if (peek_char (p) == '(')
  297. {
  298. /* Read as a parenthesized string -- for tuples/coordinates. */
  299. /* The parentheses are included in the expression value. */
  300. int c;
  301. start = p->pos;
  302. do
  303. {
  304. c = read_char (p);
  305. }
  306. while (c != -1 && c != ')');
  307. end = p->pos;
  308. }
  309. else if (has_more (p))
  310. {
  311. /* Read as a single word -- for numeric values or words without
  312. whitespace. */
  313. start = p->pos;
  314. while (has_more (p) && ! is_whitespace (peek_char (p)))
  315. read_char (p);
  316. end = p->pos;
  317. }
  318. else
  319. {
  320. /* The end of the theme file has been reached. */
  321. grub_error (GRUB_ERR_IO, "%s:%d:%d expression expected in theme file",
  322. p->filename, p->line_num, p->col_num);
  323. return 0;
  324. }
  325. return grub_new_substring (p->buf, start, end);
  326. }
  327. static grub_err_t
  328. parse_proportional_spec (char *value, signed *abs, grub_fixed_signed_t *prop)
  329. {
  330. signed num;
  331. char *ptr;
  332. int sig = 0;
  333. *abs = 0;
  334. *prop = 0;
  335. ptr = value;
  336. while (*ptr)
  337. {
  338. sig = 0;
  339. while (*ptr == '-' || *ptr == '+')
  340. {
  341. if (*ptr == '-')
  342. sig = !sig;
  343. ptr++;
  344. }
  345. num = grub_strtoul (ptr, &ptr, 0);
  346. if (grub_errno)
  347. return grub_errno;
  348. if (sig)
  349. num = -num;
  350. if (*ptr == '%')
  351. {
  352. *prop += grub_fixed_fsf_divide (grub_signed_to_fixed (num), 100);
  353. ptr++;
  354. }
  355. else
  356. *abs += num;
  357. }
  358. return GRUB_ERR_NONE;
  359. }
  360. /* Read a GUI object specification from the theme file.
  361. Any components created will be added to the GUI container PARENT. */
  362. static grub_err_t
  363. read_object (struct parsebuf *p, grub_gui_container_t parent)
  364. {
  365. grub_video_rect_t bounds;
  366. char *name;
  367. name = read_identifier (p);
  368. if (! name)
  369. goto cleanup;
  370. grub_gui_component_t component = 0;
  371. if (grub_strcmp (name, "label") == 0)
  372. {
  373. component = grub_gui_label_new ();
  374. }
  375. else if (grub_strcmp (name, "image") == 0)
  376. {
  377. component = grub_gui_image_new ();
  378. }
  379. else if (grub_strcmp (name, "vbox") == 0)
  380. {
  381. component = (grub_gui_component_t) grub_gui_vbox_new ();
  382. }
  383. else if (grub_strcmp (name, "hbox") == 0)
  384. {
  385. component = (grub_gui_component_t) grub_gui_hbox_new ();
  386. }
  387. else if (grub_strcmp (name, "canvas") == 0)
  388. {
  389. component = (grub_gui_component_t) grub_gui_canvas_new ();
  390. }
  391. else if (grub_strcmp (name, "progress_bar") == 0)
  392. {
  393. component = grub_gui_progress_bar_new ();
  394. }
  395. else if (grub_strcmp (name, "circular_progress") == 0)
  396. {
  397. component = grub_gui_circular_progress_new ();
  398. }
  399. else if (grub_strcmp (name, "boot_menu") == 0)
  400. {
  401. component = grub_gui_list_new ();
  402. }
  403. else
  404. {
  405. /* Unknown type. */
  406. grub_error (GRUB_ERR_IO, "%s:%d:%d unknown object type `%s'",
  407. p->filename, p->line_num, p->col_num, name);
  408. goto cleanup;
  409. }
  410. if (! component)
  411. goto cleanup;
  412. /* Inform the component about the theme so it can find its resources. */
  413. component->ops->set_property (component, "theme_dir", p->theme_dir);
  414. component->ops->set_property (component, "theme_path", p->filename);
  415. /* Add the component as a child of PARENT. */
  416. bounds.x = 0;
  417. bounds.y = 0;
  418. bounds.width = -1;
  419. bounds.height = -1;
  420. component->ops->set_bounds (component, &bounds);
  421. parent->ops->add (parent, component);
  422. skip_whitespace (p);
  423. if (read_char (p) != '{')
  424. {
  425. grub_error (GRUB_ERR_IO,
  426. "%s:%d:%d expected `{' after object type name `%s'",
  427. p->filename, p->line_num, p->col_num, name);
  428. goto cleanup;
  429. }
  430. while (has_more (p))
  431. {
  432. skip_whitespace (p);
  433. /* Check whether the end has been encountered. */
  434. if (peek_char (p) == '}')
  435. {
  436. /* Skip the closing brace. */
  437. read_char (p);
  438. break;
  439. }
  440. if (peek_char (p) == '#')
  441. {
  442. /* Skip comments. */
  443. advance_to_next_line (p);
  444. continue;
  445. }
  446. if (peek_char (p) == '+')
  447. {
  448. /* Skip the '+'. */
  449. read_char (p);
  450. /* Check whether this component is a container. */
  451. if (component->ops->is_instance (component, "container"))
  452. {
  453. /* Read the sub-object recursively and add it as a child. */
  454. if (read_object (p, (grub_gui_container_t) component) != 0)
  455. goto cleanup;
  456. /* After reading the sub-object, resume parsing, expecting
  457. another property assignment or sub-object definition. */
  458. continue;
  459. }
  460. else
  461. {
  462. grub_error (GRUB_ERR_IO,
  463. "%s:%d:%d attempted to add object to non-container",
  464. p->filename, p->line_num, p->col_num);
  465. goto cleanup;
  466. }
  467. }
  468. char *property;
  469. property = read_identifier (p);
  470. if (! property)
  471. {
  472. grub_error (GRUB_ERR_IO, "%s:%d:%d identifier expected in theme file",
  473. p->filename, p->line_num, p->col_num);
  474. goto cleanup;
  475. }
  476. skip_whitespace (p);
  477. if (read_char (p) != '=')
  478. {
  479. grub_error (GRUB_ERR_IO,
  480. "%s:%d:%d expected `=' after property name `%s'",
  481. p->filename, p->line_num, p->col_num, property);
  482. grub_free (property);
  483. goto cleanup;
  484. }
  485. skip_whitespace (p);
  486. char *value;
  487. value = read_expression (p);
  488. if (! value)
  489. {
  490. grub_free (property);
  491. goto cleanup;
  492. }
  493. /* Handle the property value. */
  494. if (grub_strcmp (property, "left") == 0)
  495. parse_proportional_spec (value, &component->x, &component->xfrac);
  496. else if (grub_strcmp (property, "top") == 0)
  497. parse_proportional_spec (value, &component->y, &component->yfrac);
  498. else if (grub_strcmp (property, "width") == 0)
  499. parse_proportional_spec (value, &component->w, &component->wfrac);
  500. else if (grub_strcmp (property, "height") == 0)
  501. parse_proportional_spec (value, &component->h, &component->hfrac);
  502. else
  503. /* General property handling. */
  504. component->ops->set_property (component, property, value);
  505. grub_free (value);
  506. grub_free (property);
  507. if (grub_errno != GRUB_ERR_NONE)
  508. goto cleanup;
  509. }
  510. cleanup:
  511. grub_free (name);
  512. return grub_errno;
  513. }
  514. static grub_err_t
  515. read_property (struct parsebuf *p)
  516. {
  517. char *name;
  518. /* Read the property name. */
  519. name = read_identifier (p);
  520. if (! name)
  521. {
  522. advance_to_next_line (p);
  523. return grub_errno;
  524. }
  525. /* Skip whitespace before separator. */
  526. skip_whitespace (p);
  527. /* Read separator. */
  528. if (read_char (p) != ':')
  529. {
  530. grub_error (GRUB_ERR_IO,
  531. "%s:%d:%d missing separator after property name `%s'",
  532. p->filename, p->line_num, p->col_num, name);
  533. goto done;
  534. }
  535. /* Skip whitespace after separator. */
  536. skip_whitespace (p);
  537. /* Get the value based on its type. */
  538. if (peek_char (p) == '"')
  539. {
  540. /* String value (e.g., '"My string"'). */
  541. char *value = read_expression (p);
  542. if (! value)
  543. {
  544. grub_error (GRUB_ERR_IO, "%s:%d:%d missing property value",
  545. p->filename, p->line_num, p->col_num);
  546. goto done;
  547. }
  548. /* If theme_set_string results in an error, grub_errno will be returned
  549. below. */
  550. theme_set_string (p->view, name, value, p->theme_dir,
  551. p->filename, p->line_num, p->col_num);
  552. grub_free (value);
  553. }
  554. else
  555. {
  556. grub_error (GRUB_ERR_IO,
  557. "%s:%d:%d property value invalid; "
  558. "enclose literal values in quotes (\")",
  559. p->filename, p->line_num, p->col_num);
  560. goto done;
  561. }
  562. done:
  563. grub_free (name);
  564. return grub_errno;
  565. }
  566. /* Set properties on the view based on settings from the specified
  567. theme file. */
  568. grub_err_t
  569. grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path)
  570. {
  571. grub_file_t file;
  572. struct parsebuf p;
  573. p.view = view;
  574. p.theme_dir = grub_get_dirname (theme_path);
  575. file = grub_file_open (theme_path);
  576. if (! file)
  577. {
  578. grub_free (p.theme_dir);
  579. return grub_errno;
  580. }
  581. p.len = grub_file_size (file);
  582. p.buf = grub_malloc (p.len);
  583. p.pos = 0;
  584. p.line_num = 1;
  585. p.col_num = 1;
  586. p.filename = theme_path;
  587. if (! p.buf)
  588. {
  589. grub_file_close (file);
  590. grub_free (p.theme_dir);
  591. return grub_errno;
  592. }
  593. if (grub_file_read (file, p.buf, p.len) != p.len)
  594. {
  595. grub_free (p.buf);
  596. grub_file_close (file);
  597. grub_free (p.theme_dir);
  598. return grub_errno;
  599. }
  600. if (view->canvas)
  601. view->canvas->component.ops->destroy (view->canvas);
  602. view->canvas = grub_gui_canvas_new ();
  603. ((grub_gui_component_t) view->canvas)
  604. ->ops->set_bounds ((grub_gui_component_t) view->canvas,
  605. &view->screen);
  606. while (has_more (&p))
  607. {
  608. /* Skip comments (lines beginning with #). */
  609. if (peek_char (&p) == '#')
  610. {
  611. advance_to_next_line (&p);
  612. continue;
  613. }
  614. /* Find the first non-whitespace character. */
  615. skip_whitespace (&p);
  616. /* Handle the content. */
  617. if (peek_char (&p) == '+')
  618. {
  619. /* Skip the '+'. */
  620. read_char (&p);
  621. read_object (&p, view->canvas);
  622. }
  623. else
  624. {
  625. read_property (&p);
  626. }
  627. if (grub_errno != GRUB_ERR_NONE)
  628. goto fail;
  629. }
  630. /* Set the new theme path. */
  631. grub_free (view->theme_path);
  632. view->theme_path = grub_strdup (theme_path);
  633. goto cleanup;
  634. fail:
  635. if (view->canvas)
  636. {
  637. view->canvas->component.ops->destroy (view->canvas);
  638. view->canvas = 0;
  639. }
  640. cleanup:
  641. grub_free (p.buf);
  642. grub_file_close (file);
  643. grub_free (p.theme_dir);
  644. return grub_errno;
  645. }