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