1 /*
2  * This file is part of MPlayer.
3  *
4  * MPlayer is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * MPlayer is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with MPlayer; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 /**
20  * @file
21  * @brief Skin parser
22  */
23 
24 #include <math.h>
25 #include <stdio.h>
26 #include <string.h>
27 
28 #include "skin.h"
29 #include "font.h"
30 #include "gui/interface.h"
31 #include "gui/app/app.h"
32 #include "gui/app/gui.h"
33 #include "gui/dialog/dialog.h"
34 #include "gui/util/bitmap.h"
35 #include "gui/util/misc.h"
36 #include "gui/util/string.h"
37 
38 #include "help_mp.h"
39 #include "mp_msg.h"
40 #include "libavutil/attributes.h"
41 #include "libavutil/avstring.h"
42 #include "libavutil/common.h"
43 
44 typedef struct {
45     const char *name;
46     int (*func)(char *in);
47 } _item;
48 
49 char *skinDirInHome;
50 char *skinDirInData;
51 
52 static guiItems *skin;
53 
54 static int linenumber;
55 static unsigned char currItem[32];
56 static unsigned char path[512];
57 
58 static unsigned char currWinName[32];
59 static guiItem *currWin;
60 static int *currWinItemIdx;
61 static guiItem *currWinItems;
62 
63 /**
64  * @brief Print a legacy information on an entry.
65  *
66  * @param old identifier (and deprecated entry)
67  * @param data string necessary for checking and to print the information on @a old
68  */
skin_legacy(const char * old,const char * data)69 static void skin_legacy(const char *old, const char *data)
70 {
71     const char *p;
72 
73     if (strcmp(old, "fontid") == 0) {
74         p = strchr(data, ',');
75 
76         if (p)
77             mp_msg(MSGT_GPLAYER, MSGL_INFO, _(MSGTR_GUI_MSG_SkinLegacy), linenumber, p, "font = fontfile");
78     } else if (strcmp(old, "$l") == 0) {
79         p = strstr(old, data);
80 
81         if (p && (p == data || p[-1] != '$'))
82             mp_msg(MSGT_GPLAYER, MSGL_INFO, _(MSGTR_GUI_MSG_SkinLegacy), linenumber, old, "$p");
83     } else if (strcmp(old, "evSetURL") == 0 && strcmp(data, old) == 0)
84         mp_msg(MSGT_GPLAYER, MSGL_INFO, _(MSGTR_GUI_MSG_SkinLegacy), linenumber, old, "evLoadURL");
85     else if (strcmp(old, "sub") == 0 || strcmp(old, "potmeter") == 0)
86         mp_msg(MSGT_GPLAYER, MSGL_INFO, _(MSGTR_GUI_MSG_SkinLegacy), linenumber, old, data);
87 }
88 
89 /**
90  * @brief Display a skin error message.
91  *
92  * @param format format string
93  * @param ... arguments
94  */
skin_error(const char * format,...)95 static void skin_error(const char *format, ...)
96 {
97     char p[512];
98     va_list ap;
99 
100     va_start(ap, format);
101     vsnprintf(p, sizeof(p), format, ap);
102     va_end(ap);
103 
104     gmp_msg(MSGT_GPLAYER, MSGL_ERR, _(MSGTR_GUI_MSG_SkinErrorMessage), linenumber, p);
105 }
106 
107 /**
108  * @brief Check whether a @a section definition has started.
109  *
110  * @param item name of the item to be put in a message in case of an error
111  *
112  * @return #True (ok) or #False (error)
113  */
section_item(char * item)114 static int section_item(char *item)
115 {
116     if (!skin) {
117         skin_error(_(MSGTR_GUI_MSG_SkinErrorSection), item);
118         return False;
119     }
120 
121     return True;
122 }
123 
124 /**
125  * @brief Check whether a @a window definition has started.
126  *
127  * @param item name of the item to be put in a message in case of an error
128  *
129  * @return #True (ok) or #False (error)
130  */
window_item(char * item)131 static int window_item(char *item)
132 {
133     if (!currWinName[0]) {
134         skin_error(_(MSGTR_GUI_MSG_SkinErrorWindow), item);
135         return False;
136     }
137 
138     return True;
139 }
140 
141 /**
142  * @brief Check whether a specific @a window definition has started.
143  *
144  * @param name name of the window to be checked
145  *
146  * @return 0 (ok) or 1 (error)
147  */
in_window(char * name)148 static int in_window(char *name)
149 {
150     if (strcmp(currWinName, name) == 0) {
151         skin_error(_(MSGTR_GUI_MSG_SkinErrorItem), name);
152         return 1;
153     }
154 
155     return 0;
156 }
157 
158 /**
159  * @brief Get next free item in current @a window.
160  *
161  * @return pointer to next free item (ok) or NULL (error)
162  */
next_item(void)163 static guiItem *next_item(void)
164 {
165     guiItem *item = NULL;
166 
167     if (*currWinItemIdx < MAX_ITEMS - 1) {
168         (*currWinItemIdx)++;
169         item = &currWinItems[*currWinItemIdx];
170     } else
171         skin_error(_(MSGTR_GUI_MSG_SkinTooManyItems));
172 
173     return item;
174 }
175 
176 /**
177  * @brief Parse a @a section definition.
178  *
179  *        Syntax: section=movieplayer
180  *
181  * @param in definition to be analyzed
182  *
183  * @return 0 (ok) or 1 (error)
184  */
item_section(char * in)185 static int item_section(char *in)
186 {
187     if (skin) {
188         skin_error(_(MSGTR_GUI_MSG_SkinErrorItem), currItem);
189         return 1;
190     }
191 
192     if (strcmp(strlower(in), "movieplayer") == 0)
193         skin = &guiApp;
194     else {
195         skin_error(_(MSGTR_GUI_MSG_SkinUnknownName), in);
196         return 1;
197     }
198 
199     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]  %s: %s\n", currItem, in);
200 
201     return 0;
202 }
203 
204 /**
205  * @brief Parse an @a end definition.
206  *
207  *        Syntax: end
208  *
209  * @param in definition to be analyzed
210  *
211  * @return 0 (ok) or 1 (error)
212  */
item_end(char * in)213 static int item_end(char *in)
214 {
215     char *space, *name;
216 
217     (void)in;
218 
219     if (currWinName[0]) {
220         space = " ";
221         name  = currWinName;
222     } else {
223         space = "";
224         name  = "section";
225     }
226 
227     if (!section_item(currItem))
228         return 1;
229 
230     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]  %s%s (%s)\n", space, currItem, name);
231 
232     if (currWinName[0]) {
233         currWinName[0] = 0;
234         currWin = NULL;
235         currWinItemIdx = NULL;
236         currWinItems   = NULL;
237     } else
238         skin = NULL;
239 
240     return 0;
241 }
242 
243 /**
244  * @brief Parse a @a window definition.
245  *
246  *        Syntax: window=main|video|playbar|menu
247  *
248  * @param in definition to be analyzed
249  *
250  * @return 0 (ok) or 1 (error)
251  */
item_window(char * in)252 static int item_window(char *in)
253 {
254     if (!section_item(currItem))
255         return 1;
256 
257     if (currWinName[0]) {
258         skin_error(_(MSGTR_GUI_MSG_SkinErrorItem), currItem);
259         return 1;
260     }
261 
262     strlower(in);
263 
264     // legacy
265     if (strcmp(in, "sub") == 0) {
266         strcpy(in, "video");
267         skin_legacy("sub", in);
268     }
269 
270     if (strcmp(in, "main") == 0) {
271         currWin = &skin->main;
272         currWinItemIdx = &skin->IndexOfMainItems;
273         currWinItems   = skin->mainItems;
274     } else if (strcmp(in, "video") == 0) {
275         currWin = &skin->video;
276         currWinItemIdx = NULL;
277         currWinItems   = NULL;
278     } else if (strcmp(in, "playbar") == 0) {
279         currWin = &skin->playbar;
280         currWinItemIdx = &skin->IndexOfPlaybarItems;
281         currWinItems   = skin->playbarItems;
282     } else if (strcmp(in, "menu") == 0) {
283         currWin = &skin->menu;
284         currWinItemIdx = &skin->IndexOfMenuItems;
285         currWinItems   = skin->menuItems;
286     } else {
287         skin_error(_(MSGTR_GUI_MSG_SkinUnknownName), in);
288         return 1;
289     }
290 
291     av_strlcpy(currWinName, in, sizeof(currWinName));
292 
293     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]   %s: %s\n", currItem, currWinName);
294 
295     return 0;
296 }
297 
298 /**
299  * @brief Parse a @a base definition.
300  *
301  *        Syntax: base=image,x,y[,width,height]
302  *
303  * @param in definition to be analyzed
304  *
305  * @return 0 (ok) or 1 (error)
306  */
item_base(char * in)307 static int item_base(char *in)
308 {
309     unsigned char fname[256];
310     unsigned char file[512];
311     int x, y, w, h;
312     int is_video, is_bar, is_menu;
313 
314     if (!window_item(currItem))
315         return 1;
316 
317     is_video = (strcmp(currWinName, "video") == 0);
318     is_bar   = (strcmp(currWinName, "playbar") == 0);
319     is_menu  = (strcmp(currWinName, "menu") == 0);
320 
321     cutStr(in, fname, ',', 0);
322     x = cutInt(in, ',', 1);
323     y = cutInt(in, ',', 2);
324     w = cutInt(in, ',', 3);
325     h = cutInt(in, ',', 4);
326 
327     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    image: %s", fname);
328 
329     currWin->type = itBase;
330 
331     if (!is_menu) {
332         currWin->x = x;
333         currWin->y = y;
334 
335         mp_msg(MSGT_GPLAYER, MSGL_DBG2, " %d,%d", x, y);
336     }
337 
338     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "\n");
339 
340     if (is_video && (strcmp(fname, "NULL") == 0)) {
341         currWin->width  = 0;
342         currWin->height = 0;
343     } else {
344         av_strlcpy(file, path, sizeof(file));
345         av_strlcat(file, fname, sizeof(file));
346 
347         if (skinImageRead(file, &currWin->Bitmap) != 0)
348             return 1;
349 
350         currWin->width  = currWin->Bitmap.Width;
351         currWin->height = currWin->Bitmap.Height;
352     }
353 
354     if (is_video) {
355         if (w && h) {
356             currWin->width  = w;
357             currWin->height = h;
358         }
359     }
360 
361     if (currWin->width == 0 || currWin->height == 0)
362         return 1;
363 
364     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s: %dx%d\n", is_video && w && h ? "size" : " bitmap", currWin->width, currWin->height);
365 
366     if (!is_video) {
367         if (!bpRenderMask(&currWin->Bitmap, &currWin->Mask)) {
368             skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
369             return 1;
370         }
371         mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     mask: %ux%u\n", currWin->Mask.Width, currWin->Mask.Height);
372     }
373 
374     if (is_bar)
375         skin->playbarIsPresent = True;
376     if (is_menu)
377         skin->menuIsPresent = True;
378 
379     return 0;
380 }
381 
382 /**
383  * @brief Parse a @a background definition.
384  *
385  *        Syntax: background=R,G,B
386  *
387  * @param in definition to be analyzed
388  *
389  * @return 0 (ok) or 1 (error)
390  */
item_background(char * in)391 static int item_background(char *in)
392 {
393     if (!window_item(currItem))
394         return 1;
395 
396     if (in_window("main"))
397         return 1;
398     if (in_window("playbar"))
399         return 1;
400     if (in_window("menu"))
401         return 1;
402 
403     currWin->R = cutInt(in, ',', 0);
404     currWin->G = cutInt(in, ',', 1);
405     currWin->B = cutInt(in, ',', 2);
406 
407     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s color: #%02x%02x%02x\n", currItem, currWin->R, currWin->G, currWin->B);
408 
409     return 0;
410 }
411 
412 /**
413  * @brief Parse a @a button definition.
414  *
415  *        Syntax: button=image,x,y,width,height,message
416  *
417  * @param in definition to be analyzed
418  *
419  * @return 0 (ok) or 1 (error)
420  */
item_button(char * in)421 static int item_button(char *in)
422 {
423     unsigned char fname[256];
424     unsigned char file[512];
425     int x, y, w, h, message;
426     char msg[32];
427     guiItem *item;
428 
429     if (!window_item(currItem))
430         return 1;
431 
432     if (in_window("video"))
433         return 1;
434     if (in_window("menu"))
435         return 1;
436 
437     cutStr(in, fname, ',', 0);
438     x = cutInt(in, ',', 1);
439     y = cutInt(in, ',', 2);
440     w = cutInt(in, ',', 3);
441     h = cutInt(in, ',', 4);
442     cutStr(in, msg, ',', 5);
443 
444     message = appFindMessage(msg);
445 
446     if (message == -1) {
447         skin_error(_(MSGTR_GUI_MSG_SkinUnknownMessage), msg);
448         return 1;
449     }
450     // legacy
451     else
452         skin_legacy("evSetURL", msg);
453 
454     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s image: %s %d,%d\n", currItem, fname, x, y);
455     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     message: %s (#%d)\n", msg, message);
456     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     size: %dx%d\n", w, h);
457 
458     item = next_item();
459 
460     if (!item)
461         return 1;
462 
463     item->type    = itButton;
464     item->x       = x;
465     item->y       = y;
466     item->width   = w;
467     item->height  = h;
468     item->message = message;
469     item->pressed = btnReleased;
470 
471     if (item->message == evPauseSwitchToPlay)
472         item->pressed = btnDisabled;
473 
474     item->Bitmap.Image = NULL;
475 
476     if (strcmp(fname, "NULL") != 0) {
477         av_strlcpy(file, path, sizeof(file));
478         av_strlcat(file, fname, sizeof(file));
479 
480         if (skinImageRead(file, &item->Bitmap) != 0)
481             return 1;
482 
483         mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     (bitmap: %ux%u)\n", item->Bitmap.Width, item->Bitmap.Height);
484     }
485 
486     return 0;
487 }
488 
489 /**
490  * @brief Parse a @a selected definition.
491  *
492  *        Syntax: selected=image
493  *
494  * @param in definition to be analyzed
495  *
496  * @return 0 (ok) or 1 (error)
497  */
item_selected(char * in)498 static int item_selected(char *in)
499 {
500     unsigned char file[512];
501     guiItem *item;
502 
503     if (!window_item(currItem))
504         return 1;
505 
506     if (in_window("main"))
507         return 1;
508     if (in_window("video"))
509         return 1;
510     if (in_window("playbar"))
511         return 1;
512 
513     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    image %s: %s\n", currItem, in);
514 
515     item       = &skin->menuSelected;
516     item->type = itBase;
517 
518     av_strlcpy(file, path, sizeof(file));
519     av_strlcat(file, in, sizeof(file));
520 
521     if (skinImageRead(file, &item->Bitmap) != 0)
522         return 1;
523 
524     item->width  = item->Bitmap.Width;
525     item->height = item->Bitmap.Height;
526 
527     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     bitmap: %dx%d\n", item->width, item->height);
528 
529     return 0;
530 }
531 
532 /**
533  * @brief Parse a @a menu definition.
534  *
535  *        Syntax: menu=x,y,width,height,message
536  *
537  * @param in definition to be analyzed
538  *
539  * @return 0 (ok) or 1 (error)
540  */
item_menu(char * in)541 static int item_menu(char *in)
542 {
543     int x, y, w, h, message;
544     char msg[32];
545     guiItem *item;
546 
547     if (!window_item(currItem))
548         return 1;
549 
550     if (in_window("main"))
551         return 1;
552     if (in_window("video"))
553         return 1;
554     if (in_window("playbar"))
555         return 1;
556 
557     x = cutInt(in, ',', 0);
558     y = cutInt(in, ',', 1);
559     w = cutInt(in, ',', 2);
560     h = cutInt(in, ',', 3);
561     cutStr(in, msg, ',', 4);
562 
563     message = appFindMessage(msg);
564 
565     if (message == -1) {
566         skin_error(_(MSGTR_GUI_MSG_SkinUnknownMessage), msg);
567         return 1;
568     }
569     // legacy
570     else
571         skin_legacy("evSetURL", msg);
572 
573     item = next_item();
574 
575     if (!item)
576         return 1;
577 
578     item->type    = itMenu;
579     item->x       = x;
580     item->y       = y;
581     item->width   = w;
582     item->height  = h;
583     item->message = message;
584 
585     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    item #%d: %d,%d %dx%d\n", *currWinItemIdx, x, y, w, h);
586     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     message: %s (#%d)\n", msg, message);
587 
588     return 0;
589 }
590 
591 /**
592  * @brief Parse a hpotmeter, vpotmeter or rpotmeter definition.
593  *
594  *        Parameters: button,bwidth,bheight,phases,numphases,[x0,y0,x1,y1,]default,x,y,width,height,message
595  *
596  * @param item memory location of an item to store the parameters in
597  * @param in definition to be analyzed
598  *
599  * @note item->type is already available.
600  *
601  * @return 0 (ok) or 1 (error)
602  */
parse_potmeter(guiItem * item,char * in)603 static int parse_potmeter(guiItem *item, char *in)
604 {
605     unsigned char bfname[256];
606     unsigned char phfname[256];
607     unsigned char buf[512], dfmt[5];
608     int i = 0, av_uninit(x0), av_uninit(y0), av_uninit(x1), av_uninit(y1);
609     int bwidth, bheight, num, no_default, d, x, y, w, h, message;
610 
611     if (!window_item(currItem))
612         return 1;
613 
614     if (in_window("video"))
615         return 1;
616     if (in_window("menu"))
617         return 1;
618 
619     cutStr(in, bfname, ',', i++);
620     bwidth  = cutInt(in, ',', i++);
621     bheight = cutInt(in, ',', i++);
622     cutStr(in, phfname, ',', i++);
623     num = cutInt(in, ',', i++);
624 
625     if (item->type == itRPotmeter) {
626         x0 = cutInt(in, ',', i++);
627         y0 = cutInt(in, ',', i++);
628         x1 = cutInt(in, ',', i++);
629         y1 = cutInt(in, ',', i++);
630     }
631 
632     cutStr(in, buf, ',', i);
633     no_default = (strcmp(buf, "-") == 0);
634 
635     d = cutInt(in, ',', i++);
636     x = cutInt(in, ',', i++);
637     y = cutInt(in, ',', i++);
638     w = cutInt(in, ',', i++);
639     h = cutInt(in, ',', i++);
640     cutStr(in, buf, ',', i++);
641 
642     message = appFindMessage(buf);
643 
644     if (message == -1) {
645         skin_error(_(MSGTR_GUI_MSG_SkinUnknownMessage), buf);
646         return 1;
647     }
648     // legacy
649     else
650         skin_legacy("evSetURL", buf);
651 
652     if (d < 0 || d > 100) {
653         skin_error(_(MSGTR_GUI_MSG_SkinErrorDefault), d);
654         return 1;
655     }
656 
657     if ((message == evSetVolume) && no_default) {
658         d = -1;
659         strcpy(dfmt, "-");
660     } else
661         sprintf(dfmt, "%d%%", d);
662 
663     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s image: %s %d,%d %dx%d\n", currItem, phfname, x, y, w, h);
664     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     button image: %s %dx%d\n", bfname, bwidth, bheight);
665     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     numphases: %d, default: %s\n", num, dfmt);
666     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     message: %s (#%d)\n", buf, message);
667 
668     item->x         = x;
669     item->y         = y;
670     item->width     = w;
671     item->height    = h;
672     item->pbwidth   = bwidth;
673     item->pbheight  = bheight;
674     item->numphases = num;
675     item->value     = (float)d;
676     item->message   = message;
677     item->pressed   = btnReleased;
678 
679     if (item->type == itRPotmeter) {
680         mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     start: %d,%d / stop: %d,%d\n", x0, y0, x1, y1);
681 
682         item->zeropoint = appRadian(item, x0, y0);
683         item->arclength = appRadian(item, x1, y1) - item->zeropoint;
684 
685         if (item->arclength < 0.0)
686             item->arclength += 2 * M_PI;
687         // else check if radians of (x0,y0) and (x1,y1) only differ below threshold
688         else if (item->arclength < 0.05)
689             item->arclength = 2 * M_PI;
690     }
691 
692     item->Bitmap.Image = NULL;
693 
694     if (strcmp(phfname, "NULL") != 0) {
695         if (num == 0) {
696             skin_error(_(MSGTR_GUI_MSG_SkinErrorNumphases));
697             return 1;
698         }
699 
700         av_strlcpy(buf, path, sizeof(buf));
701         av_strlcat(buf, phfname, sizeof(buf));
702 
703         if (skinImageRead(buf, &item->Bitmap) != 0)
704             return 1;
705 
706         mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     (%s bitmap: %ux%u)\n", currItem, item->Bitmap.Width, item->Bitmap.Height);
707     }
708 
709     item->Mask.Image = NULL;
710 
711     if (strcmp(bfname, "NULL") != 0) {
712         av_strlcpy(buf, path, sizeof(buf));
713         av_strlcat(buf, bfname, sizeof(buf));
714 
715         if (skinImageRead(buf, &item->Mask) != 0)
716             return 1;
717 
718         mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     (button bitmap: %ux%u)\n", item->Mask.Width, item->Mask.Height);
719     }
720 
721     return 0;
722 }
723 
724 /**
725  * @brief Parse a @a hpotmeter definition.
726  *
727  *        Syntax: hpotmeter=button,bwidth,bheight,phases,numphases,default,x,y,width,height,message
728  *
729  * @param in definition to be analyzed
730  *
731  * @return 0 (ok) or 1 (error)
732  */
item_hpotmeter(char * in)733 static int item_hpotmeter(char *in)
734 {
735     guiItem *item;
736 
737     item = next_item();
738 
739     if (!item)
740         return 1;
741 
742     item->type = itHPotmeter;
743 
744     return parse_potmeter(item, in);
745 }
746 
747 /**
748  * @brief Parse a @a vpotmeter definition.
749  *
750  *        Syntax: vpotmeter=button,bwidth,bheight,phases,numphases,default,x,y,width,height,message
751  *
752  * @param in definition to be analyzed
753  *
754  * @return 0 (ok) or 1 (error)
755  */
item_vpotmeter(char * in)756 static int item_vpotmeter(char *in)
757 {
758     guiItem *item;
759 
760     item = next_item();
761 
762     if (!item)
763         return 1;
764 
765     item->type = itVPotmeter;
766 
767     return parse_potmeter(item, in);
768 }
769 
770 /**
771  * @brief Parse a @a rpotmeter definition.
772  *
773  *        Syntax: rpotmeter=button,bwidth,bheight,phases,numphases,x0,y0,x1,y1,default,x,y,width,height,message
774  *
775  * @param in definition to be analyzed
776  *
777  * @return 0 (ok) or 1 (error)
778  */
item_rpotmeter(char * in)779 static int item_rpotmeter(char *in)
780 {
781     guiItem *item;
782 
783     item = next_item();
784 
785     if (!item)
786         return 1;
787 
788     item->type = itRPotmeter;
789 
790     return parse_potmeter(item, in);
791 }
792 
793 /**
794  * @brief Parse a @a potmeter definition.
795  *
796  *        Syntax: potmeter=phases,numphases,default,x,y,width,height,message
797  *
798  * @note THIS ITEM IS DEPRECATED.
799  *
800  * @param in definition to be analyzed
801  *
802  * @return 0 (ok) or 1 (error)
803  */
item_potmeter(char * in)804 static int item_potmeter(char *in)
805 {
806     char param[256];
807 
808     // legacy
809     skin_legacy(currItem, "hpotmeter");
810 
811     snprintf(param, sizeof(param), "NULL,0,0,%s", in);
812 
813     return item_hpotmeter(param);
814 }
815 
816 /**
817  * @brief Parse a @a pimage definition.
818  *
819  *        Syntax: pimage=phases,numphases,default,x,y,width,height,message
820  *
821  * @param in definition to be analyzed
822  *
823  * @return 0 (ok) or 1 (error)
824  */
item_pimage(char * in)825 static int item_pimage(char *in)
826 {
827     unsigned char phfname[256];
828     unsigned char buf[512];
829     int num, d, x, y, w, h, message;
830     guiItem *item;
831 
832     if (!window_item(currItem))
833         return 1;
834 
835     if (in_window("video"))
836         return 1;
837     if (in_window("menu"))
838         return 1;
839 
840     cutStr(in, phfname, ',', 0);
841     num = cutInt(in, ',', 1);
842     d   = cutInt(in, ',', 2);
843     x   = cutInt(in, ',', 3);
844     y   = cutInt(in, ',', 4);
845     w   = cutInt(in, ',', 5);
846     h   = cutInt(in, ',', 6);
847     cutStr(in, buf, ',', 7);
848 
849     message = appFindMessage(buf);
850 
851     if (message == -1) {
852         skin_error(_(MSGTR_GUI_MSG_SkinUnknownMessage), buf);
853         return 1;
854     }
855     // legacy
856     else
857         skin_legacy("evSetURL", buf);
858 
859     if (d < 0 || d > 100) {
860         skin_error(_(MSGTR_GUI_MSG_SkinErrorDefault), d);
861         return 1;
862     }
863 
864     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s image: %s %d,%d %dx%d\n", currItem, phfname, x, y, w, h);
865     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     numphases: %d, default: %d%%\n", num, d);
866     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     message: %s (#%d)\n", buf, message);
867 
868     item = next_item();
869 
870     if (!item)
871         return 1;
872 
873     item->type      = itPimage;
874     item->x         = x;
875     item->y         = y;
876     item->width     = w;
877     item->height    = h;
878     item->numphases = num;
879     item->value     = (float)d;
880     item->message   = message;
881 
882     item->Bitmap.Image = NULL;
883 
884     if (strcmp(phfname, "NULL") != 0) {
885         if (num == 0) {
886             skin_error(_(MSGTR_GUI_MSG_SkinErrorNumphases));
887             return 1;
888         }
889 
890         av_strlcpy(buf, path, sizeof(buf));
891         av_strlcat(buf, phfname, sizeof(buf));
892 
893         if (skinImageRead(buf, &item->Bitmap) != 0)
894             return 1;
895 
896         mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     (bitmap: %ux%u)\n", item->Bitmap.Width, item->Bitmap.Height);
897     }
898 
899     return 0;
900 }
901 
902 /**
903  * @brief Parse a @a font definition.
904  *
905  *        Syntax: font=fontfile
906  *
907  * @param in definition to be analyzed
908  *
909  * @return 0 (ok) or 1 (error)
910  */
item_font(char * in)911 static int item_font(char *in)
912 {
913     char fnt[256];
914 
915     if (!window_item(currItem))
916         return 1;
917 
918     if (in_window("video"))
919         return 1;
920     if (in_window("menu"))
921         return 1;
922 
923     cutStr(in, fnt, ',', 0);   // Note: This seems needless but isn't for compatibility
924                                // reasons with a meanwhile deprecated second parameter.
925     // legacy
926     skin_legacy("fontid", in);
927 
928     switch (fntRead(path, fnt)) {
929     case -1:
930         skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
931         return 1;
932 
933     case -2:
934         skin_error(_(MSGTR_GUI_MSG_SkinTooManyFonts));
935         return 1;
936 
937     case -3:
938         skin_error(_(MSGTR_GUI_MSG_SkinFontFileNotFound));
939         return 1;
940 
941     case -4:
942         skin_error(_(MSGTR_GUI_MSG_SkinFontImageNotFound));
943         return 1;
944     }
945 
946     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s: %s (#%d)\n", currItem, fnt, fntFindID(fnt));
947 
948     return 0;
949 }
950 
951 /**
952  * @brief Parse a @a slabel definition.
953  *
954  *        Syntax: slabel=x,y,fontfile,"text"
955  *
956  * @param in definition to be analyzed
957  *
958  * @return 0 (ok) or 1 (error)
959  */
item_slabel(char * in)960 static int item_slabel(char *in)
961 {
962     int x, y, id;
963     char fnt[256];
964     char txt[256];
965     guiItem *item;
966 
967     if (!window_item(currItem))
968         return 1;
969 
970     if (in_window("video"))
971         return 1;
972     if (in_window("menu"))
973         return 1;
974 
975     x = cutInt(in, ',', 0);
976     y = cutInt(in, ',', 1);
977     cutStr(in, fnt, ',', 2);
978     cutStr(in, txt, ',', 3);
979     cutStr(txt, txt, '"', 1);
980 
981     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s: \"%s\"\n", currItem, txt);
982     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     pos: %d,%d\n", x, y);
983 
984     id = fntFindID(fnt);
985 
986     if (id < 0) {
987         skin_error(_(MSGTR_GUI_MSG_SkinFontNotFound), fnt);
988         return 1;
989     }
990 
991     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     font: %s (#%d)\n", fnt, id);
992 
993     item = next_item();
994 
995     if (!item)
996         return 1;
997 
998     item->type   = itSLabel;
999     item->x      = x;
1000     item->y      = y;
1001     item->width  = -1;
1002     item->height = -1;
1003     item->fontid = id;
1004     item->label  = strdup(txt);
1005 
1006     if (!item->label) {
1007         skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
1008         return 1;
1009     }
1010 
1011     return 0;
1012 }
1013 
1014 /**
1015  * @brief Parse a @a dlabel definition.
1016  *
1017  *        Syntax: dlabel=x,y,width,align,fontfile,"text"
1018  *
1019  * @param in definition to be analyzed
1020  *
1021  * @return 0 (ok) or 1 (error)
1022  */
item_dlabel(char * in)1023 static int item_dlabel(char *in)
1024 {
1025     int x, y, w, a, id;
1026     char fnt[256];
1027     char txt[256];
1028     guiItem *item;
1029 
1030     if (!window_item(currItem))
1031         return 1;
1032 
1033     if (in_window("video"))
1034         return 1;
1035     if (in_window("menu"))
1036         return 1;
1037 
1038     x = cutInt(in, ',', 0);
1039     y = cutInt(in, ',', 1);
1040     w = cutInt(in, ',', 2);
1041     a = cutInt(in, ',', 3);
1042     cutStr(in, fnt, ',', 4);
1043     cutStr(in, txt, ',', 5);
1044     cutStr(txt, txt, '"', 1);
1045 
1046     // legacy
1047     skin_legacy("$l", txt);
1048 
1049     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s: \"%s\"\n", currItem, txt);
1050     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     pos: %d,%d\n", x, y);
1051     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     width: %d, align: %d\n", w, a);
1052 
1053     id = fntFindID(fnt);
1054 
1055     if (id < 0) {
1056         skin_error(_(MSGTR_GUI_MSG_SkinFontNotFound), fnt);
1057         return 1;
1058     }
1059 
1060     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]     font: %s (#%d)\n", fnt, id);
1061 
1062     item = next_item();
1063 
1064     if (!item)
1065         return 1;
1066 
1067     item->type   = itDLabel;
1068     item->x      = x;
1069     item->y      = y;
1070     item->width  = w;
1071     item->height = -1;
1072     item->fontid = id;
1073     item->align  = a;
1074     item->label  = strdup(txt);
1075 
1076     if (!item->label) {
1077         skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
1078         return 1;
1079     }
1080 
1081     return 0;
1082 }
1083 
1084 /**
1085  * @brief Parse a @a decoration definition.
1086  *
1087  *        Syntax: decoration=enable|disable
1088  *
1089  * @param in definition to be analyzed
1090  *
1091  * @return 0 (ok) or 1 (error)
1092  */
item_decoration(char * in)1093 static int item_decoration(char *in)
1094 {
1095     if (!window_item(currItem))
1096         return 1;
1097 
1098     if (in_window("video"))
1099         return 1;
1100     if (in_window("playbar"))
1101         return 1;
1102     if (in_window("menu"))
1103         return 1;
1104 
1105     strlower(in);
1106 
1107     if (strcmp(in, "enable") != 0 && strcmp(in, "disable") != 0) {
1108         skin_error(_(MSGTR_GUI_MSG_SkinUnknownParameter), in);
1109         return 1;
1110     }
1111 
1112     skin->mainDecoration = (strcmp(in, "enable") == 0);
1113 
1114     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin]    %s: %s\n", currItem, in);
1115 
1116     return 0;
1117 }
1118 
1119 /**
1120  * @brief Parsing functions responsible for skin item definitions.
1121  */
1122 static _item skinItem[] = {
1123     { "background", item_background },
1124     { "base",       item_base       },
1125     { "button",     item_button     },
1126     { "decoration", item_decoration },
1127     { "dlabel",     item_dlabel     },
1128     { "end",        item_end        },
1129     { "font",       item_font       },
1130     { "hpotmeter",  item_hpotmeter  },
1131     { "menu",       item_menu       },
1132     { "pimage",     item_pimage     },
1133     { "potmeter",   item_potmeter   }, // legacy
1134     { "rpotmeter",  item_rpotmeter  },
1135     { "section",    item_section    },
1136     { "selected",   item_selected   },
1137     { "slabel",     item_slabel     },
1138     { "vpotmeter",  item_vpotmeter  },
1139     { "window",     item_window     }
1140 };
1141 
1142 /**
1143  * @brief Read a skin @a image file.
1144  *
1145  * @param fname filename (with path)
1146  * @param img memory location to store the image data
1147  *
1148  * @return return code of #bpRead()
1149  */
skinImageRead(char * fname,guiImage * img)1150 int skinImageRead(char *fname, guiImage *img)
1151 {
1152     int i = bpRead(fname, img);
1153 
1154     switch (i) {
1155     case -1:
1156         skin_error(_(MSGTR_GUI_MSG_SkinErrorBitmap16Bit), fname);
1157         break;
1158 
1159     case -2:
1160         skin_error(_(MSGTR_GUI_MSG_SkinBitmapNotFound), fname);
1161         break;
1162 
1163     case -5:
1164         skin_error(_(MSGTR_GUI_MSG_SkinBitmapPngReadError), fname);
1165         break;
1166 
1167     case -8:
1168         skin_error(_(MSGTR_GUI_MSG_SkinBitmapConversionError), fname);
1169         break;
1170     }
1171 
1172     return i;
1173 }
1174 
1175 /**
1176  * @brief Build the skin file path for a skin name.
1177  *
1178  * @param dir skins directory
1179  * @param sname name of the skin
1180  *
1181  * @return skin file path
1182  *
1183  * @note As a side effect, variable #path gets set to the skin path.
1184  */
setname(char * dir,char * sname)1185 static char *setname(char *dir, char *sname)
1186 {
1187     static char skinfname[512];
1188 
1189     av_strlcpy(skinfname, dir, sizeof(skinfname));
1190     av_strlcat(skinfname, "/", sizeof(skinfname));
1191     av_strlcat(skinfname, sname, sizeof(skinfname));
1192     av_strlcat(skinfname, "/", sizeof(skinfname));
1193     av_strlcpy(path, skinfname, sizeof(path));
1194     av_strlcat(skinfname, "skin", sizeof(skinfname));
1195 
1196     return skinfname;
1197 }
1198 
1199 /**
1200  * @brief Create the most minimal skin possible.
1201  *
1202  * @return 0 (ok) or -1 (error)
1203  */
skinNoskin(void)1204 static int skinNoskin(void)
1205 {
1206     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin] using built-in skin\n");
1207 
1208     appFreeStruct();
1209 
1210     /* section = movieplayer */
1211 
1212     skin = &guiApp;
1213 
1214     /* window = main */
1215 
1216     currWin = &skin->main;
1217     currWinItemIdx = &skin->IndexOfMainItems;
1218     currWinItems   = skin->mainItems;
1219 
1220     /* decoration = disable */
1221 
1222     skin->mainDecoration = 0;
1223 
1224     /* base = <img>, 0, 0 (where <img> is a 1x1 transparent pixel image) */
1225 
1226     currWin->Bitmap.Width     = 1;
1227     currWin->Bitmap.Height    = 1;
1228     currWin->Bitmap.Bpp       = 32;
1229     currWin->Bitmap.ImageSize = 4;
1230     currWin->Bitmap.Image     = calloc(1, 4);
1231 
1232     if (!currWin->Bitmap.Image) {
1233         skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
1234         return -1;
1235     }
1236 
1237     *(uint32_t *)currWin->Bitmap.Image = GUI_TRANSPARENT;
1238 
1239     if (!bpRenderMask(&currWin->Bitmap, &currWin->Mask)) {
1240         skin_error(_(MSGTR_GUI_MSG_SkinMemoryError));
1241         return -1;
1242     }
1243 
1244     currWin->x      = 0;
1245     currWin->y      = 0;
1246     currWin->width  = currWin->Bitmap.Width;
1247     currWin->height = currWin->Bitmap.Height;
1248 
1249     /* window = video */
1250 
1251     currWin = &skin->video;
1252     currWinItemIdx = NULL;
1253     currWinItems   = NULL;
1254 
1255     /* base = NULL, -1, -1, 720, 404 */
1256 
1257     currWin->x      = -1;
1258     currWin->y      = -1;
1259     currWin->width  = 720;
1260     currWin->height = 404;
1261 
1262     /* background = 0, 0, 0 */
1263 
1264     currWin->R = 0;
1265     currWin->G = 0;
1266     currWin->B = 0;
1267 
1268     /* end */
1269 
1270     return 0;
1271 }
1272 
1273 /**
1274  * @brief Read and parse a skin.
1275  *
1276  * @param sname name of the skin
1277  *
1278  * @return 0 (ok), -1 (skin file not found or not readable) or -2 (parsing error)
1279  */
skinRead(char * sname)1280 int skinRead(char *sname)
1281 {
1282     char *skinfname;
1283     FILE *skinfile;
1284     unsigned char line[256];
1285     unsigned char param[256];
1286     unsigned int i;
1287 
1288     if (*sname == 0 || strcmp(sname, "Noskin") == 0)
1289         return skinNoskin();
1290 
1291     skinfname = setname(skinDirInHome, sname);
1292 
1293     if ((skinfile = fopen(skinfname, "rt")) == NULL) {
1294         skinfname = setname(skinDirInData, sname);
1295 
1296         if ((skinfile = fopen(skinfname, "rt")) == NULL) {
1297             mp_msg(MSGT_GPLAYER, MSGL_ERR, _(MSGTR_GUI_MSG_SkinFileNotFound), skinfname);
1298             return -1;
1299         }
1300     }
1301 
1302     mp_msg(MSGT_GPLAYER, MSGL_DBG2, "[skin] configuration file: %s\n", skinfname);
1303 
1304     appFreeStruct();
1305 
1306     skin = NULL;
1307     currWinName[0] = 0;
1308     linenumber     = 0;
1309 
1310     while (fgetstr(line, sizeof(line), skinfile)) {
1311         linenumber++;
1312 
1313         strswap(line, '\t', ' ');
1314         despace(line);
1315         decomment(line);
1316 
1317         if (!*line)
1318             continue;
1319 
1320         cutStr(line, currItem, '=', 0);
1321         cutStr(line, param, '=', 1);
1322         strlower(currItem);
1323 
1324         for (i = 0; i < FF_ARRAY_ELEMS(skinItem); i++) {
1325             if (strcmp(currItem, skinItem[i].name) == 0) {
1326                 if (skinItem[i].func(param) != 0) {
1327                     fclose(skinfile);
1328                     return -2;
1329                 } else
1330                     break;
1331             }
1332         }
1333 
1334         if (i == FF_ARRAY_ELEMS(skinItem)) {
1335             skin_error(_(MSGTR_GUI_MSG_SkinUnknownItem), currItem);
1336             fclose(skinfile);
1337             return -2;
1338         }
1339     }
1340 
1341     fclose(skinfile);
1342 
1343     if (linenumber == 0) {
1344         mp_msg(MSGT_GPLAYER, MSGL_ERR, _(MSGTR_GUI_MSG_SkinFileNotReadable), skinfname);
1345         return -1;
1346     }
1347 
1348     return 0;
1349 }
1350