Add support for monitors "All" to span panel over all monitors.
[lxde/lxpanel.git] / src / conf.c
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
29 struct _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
44 struct _PanelConf
45 {
46 config_setting_t *root;
47 };
48
49 static 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 */
76 static 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 */
100 static 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
119 static 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 */
129 static 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
144 PanelConf *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
151 void config_destroy(PanelConf * config)
152 {
153 _config_setting_t_free(config->root);
154 g_slice_free(PanelConf, config);
155 }
156
157 gboolean 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
301 static 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;
315 g_string_append_printf(buf, "%s=%s\n", setting->name, setting->str);
316 break;
317 case PANEL_CONF_TYPE_GROUP:
318 if (!out && setting->hook) /* plugin does not support settings */
319 {
320 lxpanel_put_line(f, "%s%s {", buf->str, setting->name);
321 setting->hook(setting, f, setting->hook_data);
322 lxpanel_put_line(f, "%s}", buf->str);
323 /* old settings ways are kinda weird... */
324 }
325 else
326 {
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);
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 }
355 for (s = setting->first; s; s = s->next)
356 _config_write_setting(s, buf, out, f);
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
366 gboolean 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;
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);
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 */
383 char * 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
394 config_setting_t * config_root_setting(const PanelConf * config)
395 {
396 return config->root;
397 }
398
399 config_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
406 config_setting_t * config_setting_get_elem(const config_setting_t * setting, unsigned int index)
407 {
408 config_setting_t *s;
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);
411 for (s = setting->first; s && index > 0; s = s->next)
412 index--;
413 return s;
414 }
415
416 const char * config_setting_get_name(const config_setting_t * setting)
417 {
418 return setting->name;
419 }
420
421 config_setting_t * config_setting_get_parent(const config_setting_t * setting)
422 {
423 return setting->parent;
424 }
425
426 int 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
433 const char * config_setting_get_string(const config_setting_t * setting)
434 {
435 if (!setting || setting->type != PANEL_CONF_TYPE_STRING)
436 return NULL;
437 return setting->str;
438 }
439
440 gboolean 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;
450 *value = sub->num;
451 return TRUE;
452 }
453
454 gboolean 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;
464 *value = sub->str;
465 return TRUE;
466 }
467
468 /* returns either new or existing setting struct, NULL on args error,
469 removes old setting on conflict */
470 config_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;
484 if (parent->type == PANEL_CONF_TYPE_GROUP &&
485 (s = _config_setting_get_member(parent, name)))
486 {
487 if (s->type == type)
488 return s;
489 _config_setting_t_remove(s);
490 }
491 return _config_setting_t_new(parent, -1, name, type);
492 }
493
494
495 static 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
515 static void append_to_parent(config_setting_t * setting, config_setting_t * parent)
516 {
517 config_setting_t *s;
518
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
531 static 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
544 gboolean config_setting_move_member(config_setting_t * setting, config_setting_t * parent, const char * name)
545 {
546 config_setting_t *s;
547
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;
551 s = _config_setting_get_member(parent, name);
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;
556 remove_from_parent(setting); /* remove from old parent */
557 append_to_parent(setting, parent); /* add to new parent */
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
568 gboolean config_setting_move_elem(config_setting_t * setting, config_setting_t * parent, int index)
569 {
570 config_setting_t *prev = NULL;
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 {
580 prev = parent->first;
581 if (prev)
582 for ( ; index != 1 && prev->next; prev = prev->next)
583 index--;
584 if (index > 1) /* too few elements yet */
585 {
586 _out_of_range:
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;
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 }
598 }
599 else if (parent->first == setting) /* it is already there */
600 return TRUE;
601 remove_from_parent(setting); /* remove from old parent */
602 /* add to new parent */
603 if (index == 0)
604 g_assert(prev == NULL);
605 insert_after(setting, parent, prev);
606 /* don't rename */
607 return TRUE;
608 }
609
610 gboolean 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
618 gboolean 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
627 gboolean 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
636 gboolean 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
645 gboolean 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
653 PanelConfType config_setting_type(const config_setting_t * setting)
654 {
655 return setting->type;
656 }
657
658 void config_setting_set_save_hook(config_setting_t * setting, PanelConfSaveHook hook,
659 gpointer user_data)
660 {
661 setting->hook = hook;
662 setting->hook_data = user_data;
663 }