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