Add CONF_TYPE_EXTERNAL support for lxpanel_generic_config_dlg().
[lxde/lxpanel.git] / src / conf.c
CommitLineData
0260eac5
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"
944a8264 24#include "private.h"
0260eac5 25
17fab6e5
AG
26#include <string.h>
27#include <stdlib.h>
28
0260eac5
AG
29struct _config_setting_t
30{
31 config_setting_t *next;
32 config_setting_t *parent;
33 PanelConfType type;
34 PanelConfSaveHook hook;
17fab6e5 35 gpointer hook_data;
0260eac5
AG
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
e018d9ed
AG
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
0260eac5
AG
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);
17fab6e5 148 return c;
0260eac5
AG
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");
050bf5bb 160 size_t size;
17d0ae70 161 char *buff, *c, *name, *end, *p;
0260eac5
AG
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);
050bf5bb 170 size = fread(buff, 1, size, f);
0260eac5
AG
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 {
17fab6e5 201 g_warning("config: invalid scalar definition");
0260eac5
AG
202 goto _skip_all;
203 }
17fab6e5 204 while (*c == ' ' || *c == '\t')
0260eac5 205 c++; /* skip spaces after '=' */
17fab6e5 206 if (name == NULL || *c == '\0' || *c == '\n') /* invalid statement */
0260eac5
AG
207 break;
208 size = strtol(c, &end, 10);
17fab6e5 209 while (*end == ' ' || *end == '\t')
60826154 210 end++; /* skip trailing spaces */
0260eac5
AG
211 if (*end == '\0' || *end == '\n')
212 {
e018d9ed 213 s = _config_setting_try_add(parent, name, PANEL_CONF_TYPE_INT);
0260eac5 214 if (s)
17fab6e5 215 {
0260eac5 216 s->num = (int)size;
17fab6e5
AG
217 /* g_debug("config loader: got new int %s: %d", name, s->num); */
218 }
0260eac5 219 else
17fab6e5 220 g_warning("config: duplicate setting '%s' conflicts, ignored", name);
0260eac5 221 }
a73489d4
AG
222 else if (c[0] == '"')
223 {
a73489d4 224 c++;
17d0ae70 225 for (end = p = c; *end && *end != '\n' && *end != '"'; p++, end++)
a73489d4 226 {
17d0ae70 227 if (*end == '\\' && end[1] != '\0' && end[1] != '\n')
a73489d4 228 {
17d0ae70
AG
229 end++; /* skip quoted char */
230 if (*end == 'n') /* \n */
231 *end = '\n';
a73489d4
AG
232 }
233 if (p != end)
17d0ae70 234 *p = *end; /* move char skipping '\\' */
a73489d4
AG
235 }
236 if (*end == '"')
17d0ae70
AG
237 {
238 end++;
a73489d4 239 goto _make_string;
17d0ae70 240 }
a73489d4
AG
241 else /* incomplete string */
242 g_warning("config: unfinished string setting '%s', ignored", name);
243 }
0260eac5
AG
244 else
245 {
246 for (end = c; *end && *end != '\n'; )
247 end++;
17d0ae70 248 p = end;
a73489d4 249_make_string:
e018d9ed 250 s = _config_setting_try_add(parent, name, PANEL_CONF_TYPE_STRING);
0260eac5
AG
251 if (s)
252 {
253 g_free(s->str);
17d0ae70 254 s->str = g_strndup(c, p - c);
17fab6e5 255 /* g_debug("config loader: got new string %s: %s", name, s->str); */
0260eac5
AG
256 }
257 else
17fab6e5 258 g_warning("config: duplicate setting '%s' conflicts, ignored", name);
0260eac5
AG
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;
17fab6e5 275 /* g_debug("config loader: group '%s' added", name); */
0260eac5
AG
276 }
277 else
17fab6e5 278 g_warning("config: invalid group '%s' in config file ignored", name);
0260eac5
AG
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:
096e155f
AG
313 if (!setting->str) /* don't save NULL strings */
314 return;
315 g_string_append_printf(buf, "%s=%s\n", setting->name, setting->str);
0260eac5
AG
316 break;
317 case PANEL_CONF_TYPE_GROUP:
0260eac5 318 if (!out && setting->hook) /* plugin does not support settings */
944a8264
AG
319 {
320 lxpanel_put_line(f, "%s%s {", buf->str, setting->name);
17fab6e5 321 setting->hook(setting, f, setting->hook_data);
944a8264
AG
322 lxpanel_put_line(f, "%s}", buf->str);
323 /* old settings ways are kinda weird... */
324 }
0260eac5
AG
325 else
326 {
944a8264
AG
327 if (out)
328 {
329 g_string_append(out, buf->str);
330 g_string_append(out, setting->name);
331 g_string_append(out, " {\n");
332 }
333 else
334 fprintf(f, "%s%s {\n", buf->str, setting->name);
0260eac5
AG
335 g_string_append(buf, SETTING_INDENT);
336 for (s = setting->first; s; s = s->next)
337 _config_write_setting(s, buf, out, f);
338 g_string_truncate(buf, indent);
339 if (out)
340 {
341 g_string_append(out, buf->str);
342 g_string_append(out, "}\n");
343 }
344 else
345 fprintf(f, "%s}\n", buf->str);
346 }
347 return;
348 case PANEL_CONF_TYPE_LIST:
349 if (setting->name[0] != '\0')
350 {
351 g_warning("only anonymous lists are supported in panel config, got \"%s\"",
352 setting->name);
353 return;
354 }
0260eac5
AG
355 for (s = setting->first; s; s = s->next)
356 _config_write_setting(s, buf, out, f);
0260eac5
AG
357 return;
358 }
359 if (out)
360 g_string_append(out, buf->str);
361 else
362 fputs(buf->str, f);
363 g_string_truncate(buf, indent);
364}
365
366gboolean config_write_file(PanelConf * config, const char * filename)
367{
368 FILE *f = fopen(filename, "w");
369 GString *str;
370 if (f == NULL)
371 return FALSE;
944a8264
AG
372 fputs("# lxpanel <profile> config file. Manually editing is not recommended.\n"
373 "# Use preference dialog in lxpanel to adjust config when you can.\n\n", f);
374 str = g_string_sized_new(128);
375 _config_write_setting(config_setting_get_member(config->root, ""), str, NULL, f);
0260eac5
AG
376 /* FIXME: handle errors */
377 fclose(f);
378 g_string_free(str, TRUE);
379 return TRUE;
380}
381
382/* it is used for old plugins only */
383char * config_setting_to_string(const config_setting_t * setting)
384{
385 GString *val, *buf;
386 g_return_val_if_fail(setting, NULL);
387 val = g_string_sized_new(128);
388 buf = g_string_sized_new(128);
389 _config_write_setting(setting, val, buf, NULL);
390 g_string_free(val, TRUE);
391 return g_string_free(buf, FALSE);
392}
393
394config_setting_t * config_root_setting(const PanelConf * config)
395{
396 return config->root;
397}
398
17fab6e5
AG
399config_setting_t * config_setting_get_member(const config_setting_t * setting, const char * name)
400{
401 g_return_val_if_fail(name && setting, NULL);
402 g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_GROUP, NULL);
403 return _config_setting_get_member(setting, name);
404}
405
0260eac5
AG
406config_setting_t * config_setting_get_elem(const config_setting_t * setting, unsigned int index)
407{
408 config_setting_t *s;
17fab6e5
AG
409 g_return_val_if_fail(setting, NULL);
410 g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_LIST || setting->type == PANEL_CONF_TYPE_GROUP, NULL);
0260eac5
AG
411 for (s = setting->first; s && index > 0; s = s->next)
412 index--;
413 return s;
414}
415
17fab6e5
AG
416const char * config_setting_get_name(const config_setting_t * setting)
417{
418 return setting->name;
419}
420
421config_setting_t * config_setting_get_parent(const config_setting_t * setting)
422{
423 return setting->parent;
424}
425
0260eac5
AG
426int config_setting_get_int(const config_setting_t * setting)
427{
428 if (!setting || setting->type != PANEL_CONF_TYPE_INT)
429 return 0;
430 return setting->num;
431}
432
433const char * config_setting_get_string(const config_setting_t * setting)
434{
435 if (!setting || setting->type != PANEL_CONF_TYPE_STRING)
17fab6e5 436 return NULL;
0260eac5
AG
437 return setting->str;
438}
4bca3e51
AG
439
440gboolean config_setting_lookup_int(const config_setting_t * setting,
441 const char * name, int * value)
442{
443 config_setting_t *sub;
444
445 g_return_val_if_fail(name && setting && value, FALSE);
446 g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_GROUP, FALSE);
447 sub = _config_setting_get_member(setting, name);
448 if (!sub || sub->type != PANEL_CONF_TYPE_INT)
449 return FALSE;
2b363b78 450 *value = sub->num;
4bca3e51
AG
451 return TRUE;
452}
453
454gboolean config_setting_lookup_string(const config_setting_t * setting,
455 const char * name, const char ** value)
456{
457 config_setting_t *sub;
458
459 g_return_val_if_fail(name && setting && value, FALSE);
460 g_return_val_if_fail(setting->type == PANEL_CONF_TYPE_GROUP, FALSE);
461 sub = _config_setting_get_member(setting, name);
462 if (!sub || sub->type != PANEL_CONF_TYPE_STRING)
463 return FALSE;
2b363b78 464 *value = sub->str;
4bca3e51
AG
465 return TRUE;
466}
0260eac5 467
e018d9ed
AG
468/* returns either new or existing setting struct, NULL on args error,
469 removes old setting on conflict */
0260eac5
AG
470config_setting_t * config_setting_add(config_setting_t * parent, const char * name, PanelConfType type)
471{
472 config_setting_t *s;
473 if (parent == NULL || (parent->type != PANEL_CONF_TYPE_GROUP && parent->type != PANEL_CONF_TYPE_LIST))
474 return NULL;
475 if (type == PANEL_CONF_TYPE_LIST)
476 {
477 if (!name || name[0])
478 /* only anonymous lists are supported */
479 return NULL;
480 }
481 else if (name == NULL || name[0] == '\0')
482 /* other types should be not anonymous */
483 return NULL;
17fab6e5
AG
484 if (parent->type == PANEL_CONF_TYPE_GROUP &&
485 (s = _config_setting_get_member(parent, name)))
e018d9ed
AG
486 {
487 if (s->type == type)
488 return s;
489 _config_setting_t_remove(s);
490 }
0260eac5
AG
491 return _config_setting_t_new(parent, -1, name, type);
492}
493
94f56022
HG
494
495static void remove_from_parent(config_setting_t * setting)
496{
497 config_setting_t *s;
498
499 if (setting->parent->first == setting) {
500 setting->parent->first = setting->next;
501 goto _isolate_setting;
502 }
503
504 for (s = setting->parent->first; s->next; s = s->next)
505 if (s->next == setting)
506 break;
507 g_assert(s->next);
508 s->next = setting->next;
509
510_isolate_setting:
511 setting->next = NULL;
512 setting->parent = NULL;
513}
514
515static void append_to_parent(config_setting_t * setting, config_setting_t * parent)
0260eac5
AG
516{
517 config_setting_t *s;
518
94f56022
HG
519 setting->parent = parent;
520 if (parent->first == NULL) {
521 parent->first = setting;
522 return;
523 }
524
525 s = parent->first;
526 while (s->next)
527 s = s->next;
528 s->next = setting;
529}
530
531static void insert_after(config_setting_t * setting, config_setting_t * parent,
532 config_setting_t * prev)
533{
534 setting->parent = parent;
535 if (prev == NULL) {
536 setting->next = parent->first;
537 parent->first = setting;
538 } else {
539 setting->next = prev->next;
540 prev->next = setting;
541 }
542}
543
544gboolean config_setting_move_member(config_setting_t * setting, config_setting_t * parent, const char * name)
545{
537ec037 546 config_setting_t *s;
94f56022 547
0260eac5
AG
548 g_return_val_if_fail(setting && setting->parent, FALSE);
549 if (parent == NULL || name == NULL || parent->type != PANEL_CONF_TYPE_GROUP)
550 return FALSE;
17fab6e5 551 s = _config_setting_get_member(parent, name);
0260eac5
AG
552 if (s) /* we cannot rename/move to this name, it exists already */
553 return (s == setting);
554 if (setting->parent == parent) /* it's just renaming thing */
555 goto _rename;
94f56022
HG
556 remove_from_parent(setting); /* remove from old parent */
557 append_to_parent(setting, parent); /* add to new parent */
0260eac5
AG
558 /* rename if need */
559 if (g_strcmp0(setting->name, name) != 0)
560 {
561_rename:
562 g_free(setting->name);
563 setting->name = g_strdup(name);
564 }
565 return TRUE;
566}
567
568gboolean config_setting_move_elem(config_setting_t * setting, config_setting_t * parent, int index)
569{
537ec037 570 config_setting_t *prev = NULL;
0260eac5
AG
571
572 g_return_val_if_fail(setting && setting->parent, FALSE);
573 if (parent == NULL || parent->type != PANEL_CONF_TYPE_LIST)
574 return FALSE;
575 if (setting->type != PANEL_CONF_TYPE_GROUP) /* we support only list of groups now */
576 return FALSE;
577 /* let check the place */
578 if (index != 0)
579 {
94f56022
HG
580 prev = parent->first;
581 if (prev)
0260eac5
AG
582 for ( ; index != 1 && prev->next; prev = prev->next)
583 index--;
584 if (index > 1) /* too few elements yet */
585 {
3d6ee560 586_out_of_range:
0260eac5
AG
587 g_warning("config_setting_move_elem: index out of range");
588 return FALSE;
589 }
590 if (prev && prev->next == setting) /* it is already there */
591 return TRUE;
761af06c
AG
592 if (prev == setting) /* special case: we moving it +1, swapping with next */
593 {
594 if (prev->next == NULL)
595 goto _out_of_range;
596 prev = prev->next;
597 }
0260eac5
AG
598 }
599 else if (parent->first == setting) /* it is already there */
600 return TRUE;
94f56022 601 remove_from_parent(setting); /* remove from old parent */
0260eac5 602 /* add to new parent */
0260eac5 603 if (index == 0)
94f56022
HG
604 g_assert(prev == NULL);
605 insert_after(setting, parent, prev);
0260eac5
AG
606 /* don't rename */
607 return TRUE;
608}
609
610gboolean config_setting_set_int(config_setting_t * setting, int value)
611{
612 if (!setting || setting->type != PANEL_CONF_TYPE_INT)
613 return FALSE;
614 setting->num = value;
615 return TRUE;
616}
617
618gboolean config_setting_set_string(config_setting_t * setting, const char * value)
619{
620 if (!setting || setting->type != PANEL_CONF_TYPE_STRING)
621 return FALSE;
622 g_free(setting->str);
623 setting->str = g_strdup(value);
624 return TRUE;
625}
626
627gboolean config_setting_remove(config_setting_t * parent, const char * name)
628{
629 config_setting_t *s = config_setting_get_member(parent, name);
630 if (s == NULL)
631 return FALSE;
632 _config_setting_t_remove(s);
633 return TRUE;
634}
635
636gboolean config_setting_remove_elem(config_setting_t * parent, unsigned int index)
637{
638 config_setting_t *s = config_setting_get_elem(parent, index);
639 if (s == NULL)
640 return FALSE;
641 _config_setting_t_remove(s);
642 return TRUE;
643}
644
17fab6e5
AG
645gboolean config_setting_destroy(config_setting_t * setting)
646{
647 if (setting == NULL || setting->parent == NULL)
648 return FALSE;
649 _config_setting_t_remove(setting);
650 return TRUE;
651}
652
0260eac5
AG
653PanelConfType config_setting_type(const config_setting_t * setting)
654{
655 return setting->type;
656}
657
17fab6e5
AG
658void config_setting_set_save_hook(config_setting_t * setting, PanelConfSaveHook hook,
659 gpointer user_data)
0260eac5
AG
660{
661 setting->hook = hook;
17fab6e5 662 setting->hook_data = user_data;
0260eac5 663}