1 /*
2  * Copyright (C) 2014 Andriy Grytsenko <andrej@rep.kiev.ua>
3  *               2014 Henry Gebhardt <hsggebhardt@gmail.com>
4  *
5  * This file is a part of LXPanel project.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include "conf.h"
27 #include "private.h"
28 
29 #include <string.h>
30 #include <stdlib.h>
31 
32 struct _config_setting_t
33 {
34     config_setting_t *next;
35     config_setting_t *parent;
36     PanelConfType type;
37     PanelConfSaveHook hook;
38     gpointer hook_data;
39     char *name;
40     union {
41         gint num; /* for integer or boolean */
42         gchar *str; /* for string */
43         config_setting_t *first; /* for group or list */
44     };
45 };
46 
47 struct _PanelConf
48 {
49     config_setting_t *root;
50 };
51 
_config_setting_t_new(config_setting_t * parent,int index,const char * name,PanelConfType type)52 static config_setting_t *_config_setting_t_new(config_setting_t *parent, int index,
53                                                const char *name, PanelConfType type)
54 {
55     config_setting_t *s;
56     s = g_slice_new0(config_setting_t);
57     s->type = type;
58     s->name = g_strdup(name);
59     if (parent == NULL || (parent->type != PANEL_CONF_TYPE_GROUP && parent->type != PANEL_CONF_TYPE_LIST))
60         return s;
61     s->parent = parent;
62     if (parent->first == NULL || index == 0)
63     {
64         s->next = parent->first;
65         parent->first = s;
66     }
67     else
68     {
69         for (parent = parent->first; parent->next && index != 1; parent = parent->next)
70             index--;
71         /* FIXME: check if index is out of range? */
72         s->next = parent->next;
73         parent->next = s;
74     }
75     return s;
76 }
77 
78 /* frees data, not removes from parent */
_config_setting_t_free(config_setting_t * setting)79 static void _config_setting_t_free(config_setting_t *setting)
80 {
81     g_free(setting->name);
82     switch (setting->type)
83     {
84     case PANEL_CONF_TYPE_STRING:
85         g_free(setting->str);
86         break;
87     case PANEL_CONF_TYPE_GROUP:
88     case PANEL_CONF_TYPE_LIST:
89         while (setting->first)
90         {
91             config_setting_t *s = setting->first;
92             setting->first = s->next;
93             _config_setting_t_free(s);
94         }
95         break;
96     case PANEL_CONF_TYPE_INT:
97         break;
98     }
99     g_slice_free(config_setting_t, setting);
100 }
101 
102 /* the same as above but removes from parent */
_config_setting_t_remove(config_setting_t * setting)103 static void _config_setting_t_remove(config_setting_t *setting)
104 {
105     g_return_if_fail(setting->parent);
106     g_return_if_fail(setting->parent->type == PANEL_CONF_TYPE_GROUP || setting->parent->type == PANEL_CONF_TYPE_LIST);
107     /* remove from parent */
108     if (setting->parent->first == setting)
109         setting->parent->first = setting->next;
110     else
111     {
112         config_setting_t *s = setting->parent->first;
113         while (s->next != NULL && s->next != setting)
114             s = s->next;
115         g_assert(s->next != NULL);
116         s->next = setting->next;
117     }
118     /* free the data */
119     _config_setting_t_free(setting);
120 }
121 
_config_setting_get_member(const config_setting_t * setting,const char * name)122 static config_setting_t * _config_setting_get_member(const config_setting_t * setting, const char * name)
123 {
124     config_setting_t *s;
125     for (s = setting->first; s; s = s->next)
126         if (g_strcmp0(s->name, name) == 0)
127             break;
128     return s;
129 }
130 
131 /* returns either new or existing setting struct, NULL on error or conflict */
_config_setting_try_add(config_setting_t * parent,const char * name,PanelConfType type)132 static config_setting_t * _config_setting_try_add(config_setting_t * parent,
133                                                   const char * name,
134                                                   PanelConfType type)
135 {
136     config_setting_t *s;
137     if (parent == NULL)
138         return NULL;
139     if (name[0] == '\0')
140         return NULL;
141     if (parent->type == PANEL_CONF_TYPE_GROUP &&
142         (s = _config_setting_get_member(parent, name)))
143         return (s->type == type) ? s : NULL;
144     return _config_setting_t_new(parent, -1, name, type);
145 }
146 
config_new(void)147 PanelConf *config_new(void)
148 {
149     PanelConf *c = g_slice_new(PanelConf);
150     c->root = _config_setting_t_new(NULL, -1, NULL, PANEL_CONF_TYPE_GROUP);
151     return c;
152 }
153 
config_destroy(PanelConf * config)154 void config_destroy(PanelConf * config)
155 {
156     _config_setting_t_free(config->root);
157     g_slice_free(PanelConf, config);
158 }
159 
config_read_file(PanelConf * config,const char * filename)160 gboolean config_read_file(PanelConf * config, const char * filename)
161 {
162     FILE *f = fopen(filename, "r");
163     size_t size;
164     char *buff, *c, *name, *end, *p;
165     config_setting_t *s, *parent;
166 
167     if (f == NULL)
168         return FALSE;
169     fseek(f, 0L, SEEK_END);
170     size = ftell(f);
171     rewind(f);
172     buff = g_malloc(size + 1);
173     size = fread(buff, 1, size, f);
174     fclose(f);
175     buff[size] = '\0';
176     name = NULL;
177     parent = config->root;
178     for (c = buff; *c; )
179     {
180         switch(*c)
181         {
182         case '#':
183 _skip_all:
184             while (*c && *c != '\n')
185                 c++;
186             if (!*c)
187                 break;
188             /* continue with EOL */
189         case '\n':
190             name = NULL;
191             c++;
192             break;
193         case ' ':
194         case '\t':
195             if (name)
196                 *c = '\0';
197             c++;
198             break;
199         case '=': /* scalar value follows */
200             if (name)
201                 *c++ = '\0';
202             else
203             {
204                 g_warning("config: invalid scalar definition");
205                 goto _skip_all;
206             }
207             while (*c == ' ' || *c == '\t')
208                 c++; /* skip spaces after '=' */
209             if (name == NULL || *c == '\0' || *c == '\n') /* invalid statement */
210                 break;
211             size = strtol(c, &end, 10);
212             while (*end == ' ' || *end == '\t')
213                 end++; /* skip trailing spaces */
214             if (*end == '\0' || *end == '\n')
215             {
216                 s = _config_setting_try_add(parent, name, PANEL_CONF_TYPE_INT);
217                 if (s)
218                 {
219                     s->num = (int)size;
220                     /* g_debug("config loader: got new int %s: %d", name, s->num); */
221                 }
222                 else
223                     g_warning("config: duplicate setting '%s' conflicts, ignored", name);
224             }
225             else if (c[0] == '"')
226             {
227                 c++;
228                 for (end = p = c; *end && *end != '\n' && *end != '"'; p++, end++)
229                 {
230                     if (*end == '\\' && end[1] != '\0' && end[1] != '\n')
231                     {
232                         end++; /* skip quoted char */
233                         if (*end == 'n') /* \n */
234                             *end = '\n';
235                     }
236                     if (p != end)
237                         *p = *end; /* move char skipping '\\' */
238                 }
239                 if (*end == '"')
240                 {
241                     end++;
242                     goto _make_string;
243                 }
244                 else /* incomplete string */
245                     g_warning("config: unfinished string setting '%s', ignored", name);
246             }
247             else
248             {
249                 for (end = c; *end && *end != '\n'; )
250                     end++;
251                 p = end;
252 _make_string:
253                 s = _config_setting_try_add(parent, name, PANEL_CONF_TYPE_STRING);
254                 if (s)
255                 {
256                     g_free(s->str);
257                     s->str = g_strndup(c, p - c);
258                     /* g_debug("config loader: got new string %s: %s", name, s->str); */
259                 }
260                 else
261                     g_warning("config: duplicate setting '%s' conflicts, ignored", name);
262             }
263             c = end;
264             break;
265         case '{':
266             parent = config_setting_add(parent, "", PANEL_CONF_TYPE_LIST);
267             if (name)
268             {
269                 *c = '\0';
270                 s = config_setting_add(parent, name, PANEL_CONF_TYPE_GROUP);
271             }
272             else
273                 s = NULL;
274             c++;
275             if (s)
276             {
277                 parent = s;
278                 /* g_debug("config loader: group '%s' added", name); */
279             }
280             else
281                 g_warning("config: invalid group '%s' in config file ignored", name);
282             name = NULL;
283             break;
284         case '}':
285             c++;
286             if (parent->parent)
287                 parent = parent->parent; /* go up, to anonymous list */
288             if (parent->type == PANEL_CONF_TYPE_LIST)
289                 parent = parent->parent; /* go to upper group */
290             name = NULL;
291             break;
292         default:
293             if (name == NULL)
294                 name = c;
295             c++;
296         }
297     }
298     g_free(buff);
299     return TRUE;
300 }
301 
302 #define SETTING_INDENT "  "
303 
_config_write_setting(const config_setting_t * setting,GString * buf,GString * out,FILE * f)304 static void _config_write_setting(const config_setting_t *setting, GString *buf,
305                                   GString *out, FILE *f)
306 {
307     gint indent = buf->len;
308     config_setting_t *s;
309 
310     switch (setting->type)
311     {
312     case PANEL_CONF_TYPE_INT:
313         g_string_append_printf(buf, "%s=%d\n", setting->name, setting->num);
314         break;
315     case PANEL_CONF_TYPE_STRING:
316         if (!setting->str) /* don't save NULL strings */
317             return;
318         if (setting->str[0])
319         {
320             char *end;
321             if (strtol(setting->str, &end, 10)) end = end;
322             if (*end == '\0') /* numeric string, quote it */
323             {
324                 g_string_append_printf(buf, "%s=\"%s\"\n", setting->name, setting->str);
325                 break;
326             }
327         }
328         g_string_append_printf(buf, "%s=%s\n", setting->name, setting->str);
329         break;
330     case PANEL_CONF_TYPE_GROUP:
331         if (!out && setting->hook) /* plugin does not support settings */
332         {
333             lxpanel_put_line(f, "%s%s {", buf->str, setting->name);
334             setting->hook(setting, f, setting->hook_data);
335             lxpanel_put_line(f, "%s}", buf->str);
336             /* old settings ways are kinda weird... */
337         }
338         else
339         {
340             if (out)
341             {
342                 g_string_append(out, buf->str);
343                 g_string_append(out, setting->name);
344                 g_string_append(out, " {\n");
345             }
346             else
347                 fprintf(f, "%s%s {\n", buf->str, setting->name);
348             g_string_append(buf, SETTING_INDENT);
349             for (s = setting->first; s; s = s->next)
350                 _config_write_setting(s, buf, out, f);
351             g_string_truncate(buf, indent);
352             if (out)
353             {
354                 g_string_append(out, buf->str);
355                 g_string_append(out, "}\n");
356             }
357             else
358                 fprintf(f, "%s}\n", buf->str);
359         }
360         return;
361     case PANEL_CONF_TYPE_LIST:
362         if (setting->name[0] != '\0')
363         {
364             g_warning("only anonymous lists are supported in panel config, got \"%s\"",
365                       setting->name);
366             return;
367         }
368         for (s = setting->first; s; s = s->next)
369             _config_write_setting(s, buf, out, f);
370         return;
371     }
372     if (out)
373         g_string_append(out, buf->str);
374     else
375         fputs(buf->str, f);
376     g_string_truncate(buf, indent);
377 }
378 
config_write_file(PanelConf * config,const char * filename)379 gboolean config_write_file(PanelConf * config, const char * filename)
380 {
381     FILE *f = fopen(filename, "w");
382     GString *str;
383     if (f == NULL)
384         return FALSE;
385     fputs("# lxpanel <profile> config file. Manually editing is not recommended.\n"
386           "# Use preference dialog in lxpanel to adjust config when you can.\n\n", f);
387     str = g_string_sized_new(128);
388     _config_write_setting(config_setting_get_member(config->root, ""), str, NULL, f);
389     /* FIXME: handle errors */
390     fclose(f);
391     g_string_free(str, TRUE);
392     return TRUE;
393 }
394 
395 /* it is used for old plugins only */
config_setting_to_string(const config_setting_t * setting)396 char * config_setting_to_string(const config_setting_t * setting)
397 {
398     GString *val, *buf;
399     g_return_val_if_fail(setting, NULL);
400     val = g_string_sized_new(128);
401     buf = g_string_sized_new(128);
402     _config_write_setting(setting, val, buf, NULL);
403     g_string_free(val, TRUE);
404     return g_string_free(buf, FALSE);
405 }
406 
config_root_setting(const PanelConf * config)407 config_setting_t * config_root_setting(const PanelConf * config)
408 {
409     return config->root;
410 }
411 
config_setting_get_member(const config_setting_t * setting,const char * name)412 config_setting_t * config_setting_get_member(const config_setting_t * setting, const char * name)
413 {
414     g_return_val_if_fail(name && setting, NULL);
415     g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_GROUP, NULL);
416     return _config_setting_get_member(setting, name);
417 }
418 
config_setting_get_elem(const config_setting_t * setting,unsigned int index)419 config_setting_t * config_setting_get_elem(const config_setting_t * setting, unsigned int index)
420 {
421     config_setting_t *s;
422     g_return_val_if_fail(setting, NULL);
423     g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_LIST || setting->type == PANEL_CONF_TYPE_GROUP, NULL);
424     for (s = setting->first; s && index > 0; s = s->next)
425         index--;
426     return s;
427 }
428 
config_setting_get_name(const config_setting_t * setting)429 const char * config_setting_get_name(const config_setting_t * setting)
430 {
431     return setting->name;
432 }
433 
config_setting_get_parent(const config_setting_t * setting)434 config_setting_t * config_setting_get_parent(const config_setting_t * setting)
435 {
436     return setting->parent;
437 }
438 
config_setting_get_int(const config_setting_t * setting)439 int config_setting_get_int(const config_setting_t * setting)
440 {
441     if (!setting || setting->type != PANEL_CONF_TYPE_INT)
442         return 0;
443     return setting->num;
444 }
445 
config_setting_get_string(const config_setting_t * setting)446 const char * config_setting_get_string(const config_setting_t * setting)
447 {
448     if (!setting || setting->type != PANEL_CONF_TYPE_STRING)
449         return NULL;
450     return setting->str;
451 }
452 
config_setting_lookup_int(const config_setting_t * setting,const char * name,int * value)453 gboolean config_setting_lookup_int(const config_setting_t * setting,
454                                    const char * name, int * value)
455 {
456     config_setting_t *sub;
457 
458     g_return_val_if_fail(name && setting && value, FALSE);
459     g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_GROUP, FALSE);
460     sub = _config_setting_get_member(setting, name);
461     if (!sub || sub->type != PANEL_CONF_TYPE_INT)
462         return FALSE;
463     *value = sub->num;
464     return TRUE;
465 }
466 
config_setting_lookup_string(const config_setting_t * setting,const char * name,const char ** value)467 gboolean config_setting_lookup_string(const config_setting_t * setting,
468                                       const char * name, const char ** value)
469 {
470     config_setting_t *sub;
471 
472     g_return_val_if_fail(name && setting && value, FALSE);
473     g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_GROUP, FALSE);
474     sub = _config_setting_get_member(setting, name);
475     if (!sub || sub->type != PANEL_CONF_TYPE_STRING)
476         return FALSE;
477     *value = sub->str;
478     return TRUE;
479 }
480 
481 /* returns either new or existing setting struct, NULL on args error,
482    removes old setting on conflict */
config_setting_add(config_setting_t * parent,const char * name,PanelConfType type)483 config_setting_t * config_setting_add(config_setting_t * parent, const char * name, PanelConfType type)
484 {
485     config_setting_t *s;
486     if (parent == NULL || (parent->type != PANEL_CONF_TYPE_GROUP && parent->type != PANEL_CONF_TYPE_LIST))
487         return NULL;
488     if (type == PANEL_CONF_TYPE_LIST)
489     {
490         if (!name || name[0])
491             /* only anonymous lists are supported */
492             return NULL;
493     }
494     else if (name == NULL || name[0] == '\0')
495         /* other types should be not anonymous */
496         return NULL;
497     if (parent->type == PANEL_CONF_TYPE_GROUP &&
498         (s = _config_setting_get_member(parent, name)))
499     {
500         if (s->type == type)
501             return s;
502         _config_setting_t_remove(s);
503     }
504     return _config_setting_t_new(parent, -1, name, type);
505 }
506 
507 
remove_from_parent(config_setting_t * setting)508 static void remove_from_parent(config_setting_t * setting)
509 {
510     config_setting_t *s;
511 
512     if (setting->parent->first == setting) {
513         setting->parent->first = setting->next;
514         goto _isolate_setting;
515     }
516 
517     for (s = setting->parent->first; s->next; s = s->next)
518         if (s->next == setting)
519             break;
520     g_assert(s->next);
521     s->next = setting->next;
522 
523 _isolate_setting:
524     setting->next = NULL;
525     setting->parent = NULL;
526 }
527 
append_to_parent(config_setting_t * setting,config_setting_t * parent)528 static void append_to_parent(config_setting_t * setting, config_setting_t * parent)
529 {
530     config_setting_t *s;
531 
532     setting->parent = parent;
533     if (parent->first == NULL) {
534         parent->first = setting;
535         return;
536     }
537 
538     s = parent->first;
539     while (s->next)
540         s = s->next;
541     s->next = setting;
542 }
543 
insert_after(config_setting_t * setting,config_setting_t * parent,config_setting_t * prev)544 static void insert_after(config_setting_t * setting, config_setting_t * parent,
545         config_setting_t * prev)
546 {
547     setting->parent = parent;
548     if (prev == NULL) {
549         setting->next = parent->first;
550         parent->first = setting;
551     } else {
552         setting->next = prev->next;
553         prev->next = setting;
554     }
555 }
556 
config_setting_move_member(config_setting_t * setting,config_setting_t * parent,const char * name)557 gboolean config_setting_move_member(config_setting_t * setting, config_setting_t * parent, const char * name)
558 {
559     config_setting_t *s;
560 
561     g_return_val_if_fail(setting && setting->parent, FALSE);
562     if (parent == NULL || name == NULL || parent->type != PANEL_CONF_TYPE_GROUP)
563         return FALSE;
564     s = _config_setting_get_member(parent, name);
565     if (s) /* we cannot rename/move to this name, it exists already */
566         return (s == setting);
567     if (setting->parent == parent) /* it's just renaming thing */
568         goto _rename;
569     remove_from_parent(setting); /* remove from old parent */
570     append_to_parent(setting, parent); /* add to new parent */
571     /* rename if need */
572     if (g_strcmp0(setting->name, name) != 0)
573     {
574 _rename:
575         g_free(setting->name);
576         setting->name = g_strdup(name);
577     }
578     return TRUE;
579 }
580 
config_setting_move_elem(config_setting_t * setting,config_setting_t * parent,int index)581 gboolean config_setting_move_elem(config_setting_t * setting, config_setting_t * parent, int index)
582 {
583     config_setting_t *prev = NULL;
584 
585     g_return_val_if_fail(setting && setting->parent, FALSE);
586     if (parent == NULL || parent->type != PANEL_CONF_TYPE_LIST)
587         return FALSE;
588     if (setting->type != PANEL_CONF_TYPE_GROUP) /* we support only list of groups now */
589         return FALSE;
590     /* let check the place */
591     if (index != 0)
592     {
593         prev = parent->first;
594         if (prev)
595             for ( ; index != 1 && prev->next; prev = prev->next)
596                 index--;
597         if (index > 1) /* too few elements yet */
598         {
599 _out_of_range:
600             g_warning("config_setting_move_elem: index out of range");
601             return FALSE;
602         }
603         if (prev && prev->next == setting) /* it is already there */
604             return TRUE;
605         if (prev == setting) /* special case: we moving it +1, swapping with next */
606         {
607             if (prev->next == NULL)
608                 goto _out_of_range;
609             prev = prev->next;
610         }
611     }
612     else if (parent->first == setting) /* it is already there */
613         return TRUE;
614     remove_from_parent(setting); /* remove from old parent */
615     /* add to new parent */
616     if (index == 0)
617         g_assert(prev == NULL);
618     insert_after(setting, parent, prev);
619     /* don't rename  */
620     return TRUE;
621 }
622 
config_setting_set_int(config_setting_t * setting,int value)623 gboolean config_setting_set_int(config_setting_t * setting, int value)
624 {
625     if (!setting || setting->type != PANEL_CONF_TYPE_INT)
626         return FALSE;
627     setting->num = value;
628     return TRUE;
629 }
630 
config_setting_set_string(config_setting_t * setting,const char * value)631 gboolean config_setting_set_string(config_setting_t * setting, const char * value)
632 {
633     if (!setting || setting->type != PANEL_CONF_TYPE_STRING)
634         return FALSE;
635     g_free(setting->str);
636     setting->str = g_strdup(value);
637     return TRUE;
638 }
639 
config_setting_remove(config_setting_t * parent,const char * name)640 gboolean config_setting_remove(config_setting_t * parent, const char * name)
641 {
642     config_setting_t *s = config_setting_get_member(parent, name);
643     if (s == NULL)
644         return FALSE;
645     _config_setting_t_remove(s);
646     return TRUE;
647 }
648 
config_setting_remove_elem(config_setting_t * parent,unsigned int index)649 gboolean config_setting_remove_elem(config_setting_t * parent, unsigned int index)
650 {
651     config_setting_t *s = config_setting_get_elem(parent, index);
652     if (s == NULL)
653         return FALSE;
654     _config_setting_t_remove(s);
655     return TRUE;
656 }
657 
config_setting_destroy(config_setting_t * setting)658 gboolean config_setting_destroy(config_setting_t * setting)
659 {
660     if (setting == NULL || setting->parent == NULL)
661         return FALSE;
662     _config_setting_t_remove(setting);
663     return TRUE;
664 }
665 
config_setting_type(const config_setting_t * setting)666 PanelConfType config_setting_type(const config_setting_t * setting)
667 {
668     return setting->type;
669 }
670 
config_setting_set_save_hook(config_setting_t * setting,PanelConfSaveHook hook,gpointer user_data)671 void config_setting_set_save_hook(config_setting_t * setting, PanelConfSaveHook hook,
672                                   gpointer user_data)
673 {
674     setting->hook = hook;
675     setting->hook_data = user_data;
676 }
677