diff --git a/README.md b/README.md
index 88ed65ad..c58e0a56 100644
--- a/README.md
+++ b/README.md
@@ -90,8 +90,8 @@ modification.
Openbox spec is somewhat of a stable standard considering how long it has
remained unchanged for and how wide-spread its adoption is by lightweight
-distributions such as LXDE, LXQt, BunsenLabs, ArchLabs, Mabox and Raspbian. Some
-widely used themes (for example Numix and Arc) have built-in support.
+distributions such as LXDE, LXQt, BunsenLabs, ArchLabs, Mabox and Raspbian.
+Some widely used themes (for example Numix and Arc) have built-in support.
We could have invented a whole new syntax, but that's not where we want to
spend our effort.
@@ -182,8 +182,8 @@ prevent installing the wlroots headers:
## 3. Configuration
User config files are located at `${XDG_CONFIG_HOME:-$HOME/.config/labwc/}`
-with the following five files being used: [rc.xml], [menu.xml], [autostart], [shutdown],
-[environment] and [themerc-override].
+with the following five files being used: [rc.xml] (or [rc.yaml]), [menu.xml],
+[autostart], [shutdown], [environment] and [themerc-override].
Run `labwc --reconfigure` to reload configuration and theme.
@@ -275,6 +275,7 @@ See [integration] for further details.
[metacity]: https://github.com/GNOME/metacity
[rc.xml]: docs/rc.xml.all
+[rc.yaml]: docs/rc.yaml
[menu.xml]: docs/menu.xml
[autostart]: docs/autostart
[shutdown]: docs/shutdown
diff --git a/docs/README b/docs/README
index c90c047a..769118e0 100644
--- a/docs/README
+++ b/docs/README
@@ -3,6 +3,7 @@ Config layout for ~/.config/labwc/
- environment
- menu.xml
- rc.xml
+- rc.yaml
- shutdown
- themerc-override
- xinitrc
diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd
index 7436fc5f..6345731c 100644
--- a/docs/labwc-config.5.scd
+++ b/docs/labwc-config.5.scd
@@ -8,7 +8,8 @@ labwc - configuration files
Labwc uses openbox-3.6 specification for configuration and theming, but does not
support all options. The following files form the basis of the labwc
-configuration: rc.xml, menu.xml, autostart, shutdown, environment and xinitrc.
+configuration: rc.xml (or rc.yaml), menu.xml, autostart, shutdown, environment
+and xinitrc.
No configuration files are needed to start and run labwc.
@@ -158,6 +159,77 @@ Note that in this manual, Boolean values are listed as [yes|no] for simplicity,
but it's also possible to use [true|false] and\/or [on|off];
this is for compatibility with Openbox.
+## YAML SUPPORT
+
+Labwc also supports YAML language for configuration. When rc.yaml exists
+instead of rc.xml, labwc internally converts it from YAML into XML and loads
+configurations from it. For example, "foo: bar" in YAML is converted to
+"bar" in XML. See /usr/share/docs/labwc/rc.yaml for an example
+configuration in YAML.
+
+If rc.yaml includes a key-value pair where the value is an array, it is
+converted to a sequence of array-element in XML.
+
+For example, a YAML expression:
+
+```
+touch:
+ - deviceName: xxxx
+ mapToOutput: eDP-1
+ - deviceName: yyyy
+ mapToOutput: HDMI-1
+```
+
+is converted to an XML expression:
+
+```
+
+ xxxx
+ eDP-1
+
+
+ yyyy
+ eDP-1
+
+```
+
+To avoid unnecessary indentations, some nodes that wrap array elements in XML
+can be ommitted. This includes:
+
+ - **
+ - **
+ - **
+ - **
+ - **
+
+For example, window switcher can be configured like:
+
+```
+windowSwitcher:
+ fields:
+ - content: type
+ width: 15%
+ - content: title
+ width: 85%
+```
+
+In addition, some specific keys in singular form with a sequence value in YAML
+are converted to plural form in XML. This includes:
+
+ - *keybinds* (converted to *keybind*)
+ - *mousebinds* (converted to *mousebind*)
+ - *actions* (converted to *action*)
+ - *fonts* (converted to *font*)
+ - *contexts* (converted to *context*)
+
+For example, keybinds can be configured like:
+
+```
+keybinds:
+ - { key: W-s, action: { name: Execute, command: foot } }
+ - { key: W-a, action: { name: Execute, command: fuzzel } }
+```
+
## CORE
```
@@ -575,9 +647,9 @@ extending outward from the snapped edge.
Load the default keybinds listed below. This is an addition to the
openbox specification and provides a way to keep config files simpler
whilst allowing your specific keybinds.
- Note that if no rc.xml is found, or if no entries
- exist, the same default keybinds will be loaded even if the
- element is not provided.
+ Note that if no rc.xml or rc.yaml is found, or if no
+ entries exist, the same default keybinds will be loaded even if the
+ element is not provided.
```
A-Tab - next window
@@ -668,9 +740,10 @@ extending outward from the snapped edge.
**
Load default mousebinds. This is an addition to the openbox
specification and provides a way to keep config files simpler whilst
- allowing user specific binds. Note that if no rc.xml is found, or if no
- entries exist, the same default mousebinds will be
- loaded even if the element is not provided.
+ allowing user specific binds. Note that if no rc.xml or rc.yaml is
+ found, or if no entries exist, the same default
+ mousebinds will be loaded even if the element is not
+ provided.
## TOUCH
diff --git a/docs/labwc-menu.5.scd b/docs/labwc-menu.5.scd
index a50fa254..ae6c425a 100644
--- a/docs/labwc-menu.5.scd
+++ b/docs/labwc-menu.5.scd
@@ -106,13 +106,23 @@ ID attributes are unique. Duplicates are ignored.
When writing pipe menu scripts, make sure to escape XML special characters such
as "&" ("&"), "<" ("<"), and ">" (">").
-
# LOCALISATION
Available localisation for the default "client-menu" is only shown if no
"client-menu" is present in menu.xml. Any menu definition in menu.xml is
interpreted as a user-override.
+# YAML SUPPORT
+
+Like rc.yaml, labwc supports menu.yaml instead of menu.xml. See labwc-config(5)
+for its syntax. Note that following keys in singular form can be expressed as
+plural form in menu.yaml:
+
+ - *menus* (converted to *menu*)
+ - *items* (converted to *item*)
+
+See /usr/share/docs/labwc/menu.yaml for an example menu configuration in YAML.
+
# SEE ALSO
labwc(1), labwc-actions(5), labwc-config(5), labwc-theme(5)
diff --git a/docs/menu.yaml b/docs/menu.yaml
new file mode 100644
index 00000000..842799f8
--- /dev/null
+++ b/docs/menu.yaml
@@ -0,0 +1,33 @@
+menu:
+ - id: client-menu
+ item:
+ label: Minimize
+ action: { name: Iconify }
+ item:
+ label: Maximize
+ action: { name: ToggleMaximize }
+ menu:
+ id: workspaces
+ label: Workspace
+ item:
+ label: Move Left
+ action: { name: SendToDesktop, to: left }
+ item:
+ label: Move Right
+ action: { name: SendToDesktop, to: right }
+ separator:
+ item:
+ label: Always on Visible Workspace
+ action: { name: ToggleOmnipresent }
+ item:
+ label: Close
+ action: { name: Close }
+
+ - id: root-menu
+ items:
+ - label: Terminal
+ action: { name: Execute, command: alacritty }
+ - label: Reconfigure
+ action: { name: Reconfigure }
+ - label: Exit
+ action: { name: Exit }
diff --git a/docs/meson.build b/docs/meson.build
index a6676784..4691578e 100644
--- a/docs/meson.build
+++ b/docs/meson.build
@@ -33,7 +33,8 @@ install_data(
'shutdown',
'themerc',
'rc.xml',
- 'rc.xml.all'
+ 'rc.xml.all',
+ 'rc.yaml',
],
install_dir: get_option('datadir') / 'doc' / meson.project_name()
)
diff --git a/docs/rc.yaml b/docs/rc.yaml
new file mode 100644
index 00000000..299460ff
--- /dev/null
+++ b/docs/rc.yaml
@@ -0,0 +1,89 @@
+# An example configuration file in YAML. To see all the configurations and
+# their descriptions, see docs/rc.xml.all or labwc-config(5).
+core:
+ xwaylandPersistence: yes
+placement:
+ policy: cascade
+theme:
+ dropShadows: yes
+ fonts:
+ - place: ActiveWindow
+ weight: bold
+ - place: InactiveWindow
+ weight: normal
+desktops:
+ number: 2
+windowSwitcher:
+ fields:
+ - content: type
+ width: 15%
+ - content: title
+ width: 85%
+resize:
+ drawContents: no
+focus:
+ followMouse: yes
+ followMouseRequiresMovement: yes
+ raiseOnFocus: no
+snapping:
+ range: 20
+ overlay:
+ delay:
+ inner: 100
+ outer: 0
+regions:
+ - { name: top-left, x: 0%, y: 0%, width: 50%, height: 50% }
+ - { name: top-right, x: 50%, y: 0%, width: 50%, height: 50% }
+ - { name: bottom-left, x: 0%, y: 50%, width: 50%, height: 50% }
+ - { name: bottom-right, x: 50%, y: 50%, width: 50%, height: 50% }
+keyboard:
+ repeatRate: 25
+ repeatDelay: 600
+ keybinds:
+ - { key: W-bracketRight, action: { name: ZoomIn } }
+ - { key: W-bracketLeft, action: { name: ZoomOut } }
+ - { key: W-d, action: { name: Debug } }
+ - { key: W-1, action: { name: GoToDesktop, to: Workspace 1 } }
+ - { key: W-2, action: { name: GoToDesktop, to: Workspace 2 } }
+ - { key: W-m, action: { name: ToggleKeybinds } }
+ - { key: W-q, action: { name: Close } }
+ - { key: A-F4, action: { name: Close } }
+ - { key: A-Tab, action: { name: NextWindow } }
+ - { key: W-e, action: { name: Exit } }
+ - { key: W-v, action: { name: Execute, command: sh -c 'cliphist list | fuzzel --dmenu | cliphist decode | wl-copy' } }
+ - { key: W-Tab, action: { name: ToggleMaximize } }
+ - { key: W-s, action: { name: Execute, command: foot } }
+ - { key: W-a, action: { name: Execute, command: fuzzel } }
+ - { key: XF86_AudioLowerVolume, action: { name: Execute, command: pactl set-sink-volume @DEFAULT_SINK@ -2dB } }
+ - { key: XF86_AudioRaiseVolume, action: { name: Execute, command: pactl set-sink-volume @DEFAULT_SINK@ +2dB } }
+ - { key: XF86_AudioMute, action: { name: Execute, command: pactl set-sink-mute @DEFAULT_SINK@ toggle } }
+ - { key: XF86_AudioMicMute, action: { name: Execute, command: pactl set-source-mute @DEFAULT_SOURCE@ toggle } }
+ - { key: Print, action: { name: Execute, command: grim } }
+mouse:
+ doubleClickTime: 200
+ default:
+ contexts:
+ - name: Frame
+ mousebinds:
+ - { button: W-Left, action: Press, actions: [ { name: Raise }, { name: Move } ] }
+ - { button: W-Right, action: Drag, action: { name: Raise } }
+ - { button: A-Left, action: Press }
+ - { button: A-Left, action: Drag }
+ - { button: A-Right, action: Press }
+ - { button: A-Right, action: Drag }
+touch:
+ deviceName: ELAN2514:00 04F3:2AF1
+ mapToOutput: eDP-1
+tablet:
+ mapToOutput: eDP-1
+libinput:
+ - category: non-touch
+ pointerSpeed: -0.4
+ - category: touchpad
+ naturalScroll: yes
+ tapAndDrag: no
+windowRules:
+ - identifier: ristretto
+ serverDecoration: no
+ - identifier: code-url-handler
+ action: { name: SetDecorations, decorations: border, forceSSD: yes }
diff --git a/include/common/yaml2xml.h b/include/common/yaml2xml.h
new file mode 100644
index 00000000..8c1a27ca
--- /dev/null
+++ b/include/common/yaml2xml.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LABWC_YAML2XML_H
+#define LABWC_YAML2XML_H
+#include
+#include
+
+struct buf yaml_to_xml(FILE *stream, const char *toplevel_name);
+
+#endif /* LABWC_YAML2XML_H */
diff --git a/meson.build b/meson.build
index 8f9b5a97..da2f449d 100644
--- a/meson.build
+++ b/meson.build
@@ -72,6 +72,7 @@ pixman = dependency('pixman-1')
math = cc.find_library('m')
png = dependency('libpng')
svg = dependency('librsvg-2.0', version: '>=2.46', required: false)
+yaml = dependency('yaml-0.1', required: false)
if get_option('xwayland').enabled() and not wlroots_has_xwayland
error('no wlroots Xwayland support')
@@ -87,6 +88,13 @@ else
endif
conf_data.set10('HAVE_RSVG', have_rsvg)
+if get_option('yaml').disabled()
+ have_libyaml = false
+else
+ have_libyaml = yaml.found()
+endif
+conf_data.set10('HAVE_LIBYAML', have_libyaml)
+
if get_option('static_analyzer').enabled()
add_project_arguments(['-fanalyzer'], language: 'c')
endif
@@ -125,6 +133,11 @@ if have_rsvg
svg,
]
endif
+if have_libyaml
+ labwc_deps += [
+ yaml,
+ ]
+endif
subdir('include')
subdir('src')
diff --git a/meson_options.txt b/meson_options.txt
index 4d6e8cd5..3b6b13b8 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -4,3 +4,4 @@ option('svg', type: 'feature', value: 'enabled', description: 'Enable svg window
option('nls', type: 'feature', value: 'auto', description: 'Enable native language support')
option('static_analyzer', type: 'feature', value: 'disabled', description: 'Run gcc static analyzer')
option('test', type: 'feature', value: 'disabled', description: 'Run tests')
+option('yaml', type: 'feature', value: 'enabled', description: 'Enable configuration in YAML')
diff --git a/src/common/meson.build b/src/common/meson.build
index d9be1d1a..77a9600f 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -20,3 +20,9 @@ labwc_sources += files(
'spawn.c',
'string-helpers.c',
)
+
+if have_libyaml
+ labwc_sources += files(
+ 'yaml2xml.c',
+ )
+endif
diff --git a/src/common/yaml2xml.c b/src/common/yaml2xml.c
new file mode 100644
index 00000000..82fa7f65
--- /dev/null
+++ b/src/common/yaml2xml.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "common/buf.h"
+#include "common/yaml2xml.h"
+
+struct lab_yaml_event {
+ yaml_event_type_t type;
+ char scalar[256];
+};
+
+static bool process_mapping(yaml_parser_t *parser, struct buf *buf);
+static bool process_sequence(yaml_parser_t *parser, struct buf *buf, char *key_name);
+
+static bool
+parse(yaml_parser_t *parser, struct lab_yaml_event *event)
+{
+ yaml_event_t yaml_event;
+ if (!yaml_parser_parse(parser, &yaml_event)) {
+ wlr_log(WLR_ERROR,
+ "Conversion from YAML to XML failed at %ld:%ld: %s",
+ parser->problem_mark.line + 1,
+ parser->problem_mark.column,
+ parser->problem);
+ return false;
+ }
+ event->type = yaml_event.type;
+ if (yaml_event.type == YAML_SCALAR_EVENT) {
+ snprintf(event->scalar, sizeof(event->scalar), "%s",
+ (char *)yaml_event.data.scalar.value);
+ }
+ yaml_event_delete(&yaml_event);
+ return event;
+}
+
+static bool
+process_value(yaml_parser_t *parser, struct buf *b,
+ struct lab_yaml_event *event, char *key_name)
+{
+ const char *parent_name = NULL;
+
+ switch (event->type) {
+ case YAML_MAPPING_START_EVENT:
+ buf_add_fmt(b, "<%s>", key_name);
+ if (!process_mapping(parser, b)) {
+ return false;
+ }
+ buf_add_fmt(b, "%s>", key_name);
+ break;
+ case YAML_SEQUENCE_START_EVENT:
+ /*
+ * YAML XML
+ * fields: [x,y] -> xy
+ */
+ if (!strcasecmp(key_name, "fields")) {
+ parent_name = "fields";
+ key_name = "field";
+ } else if (!strcasecmp(key_name, "regions")) {
+ parent_name = "regions";
+ key_name = "region";
+ } else if (!strcasecmp(key_name, "windowRules")) {
+ parent_name = "windowRules";
+ key_name = "windowRule";
+ } else if (!strcasecmp(key_name, "libinput")) {
+ parent_name = "libinput";
+ key_name = "device";
+ } else if (!strcasecmp(key_name, "names")) {
+ parent_name = "names";
+ key_name = "name";
+ } else if (!strcasecmp(key_name, "keybinds")) {
+ key_name = "keybind";
+ } else if (!strcasecmp(key_name, "mousebinds")) {
+ key_name = "mousebind";
+ } else if (!strcasecmp(key_name, "actions")) {
+ key_name = "action";
+ } else if (!strcasecmp(key_name, "fonts")) {
+ key_name = "font";
+ } else if (!strcasecmp(key_name, "contexts")) {
+ key_name = "context";
+ } else if (!strcasecmp(key_name, "items")) {
+ key_name = "item";
+ } else if (!strcasecmp(key_name, "menus")) {
+ key_name = "menu";
+ }
+ if (parent_name) {
+ buf_add_fmt(b, "<%s>", parent_name);
+ }
+ if (!process_sequence(parser, b, key_name)) {
+ return false;
+ }
+ if (parent_name) {
+ buf_add_fmt(b, "%s>", parent_name);
+ }
+ break;
+ case YAML_SCALAR_EVENT:
+ /*
+ * To avoid duplicated keys, mousebind: {event: Press} is
+ * mapped to .
+ */
+ if (!strcasecmp(key_name, "event")) {
+ key_name = "action";
+ }
+ buf_add_fmt(b, "<%s>%s%s>", key_name, event->scalar, key_name);
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool
+process_sequence(yaml_parser_t *parser, struct buf *b, char *key_name)
+{
+ while (1) {
+ struct lab_yaml_event event;
+ if (!parse(parser, &event)) {
+ return false;
+ }
+ if (event.type == YAML_SEQUENCE_END_EVENT) {
+ return true;
+ }
+ if (!process_value(parser, b, &event, key_name)) {
+ return false;
+ }
+ }
+}
+
+static bool
+process_mapping(yaml_parser_t *parser, struct buf *buf)
+{
+ while (1) {
+ struct lab_yaml_event key_event, value_event;
+
+ if (!parse(parser, &key_event)) {
+ return false;
+ }
+ if (key_event.type == YAML_MAPPING_END_EVENT) {
+ return true;
+ }
+ if (key_event.type != YAML_SCALAR_EVENT) {
+ wlr_log(WLR_ERROR, "key must be scalar");
+ return false;
+ }
+ if (!parse(parser, &value_event)) {
+ return false;
+ }
+ if (!process_value(parser, buf, &value_event, key_event.scalar)) {
+ return false;
+ }
+ continue;
+ }
+}
+
+static bool
+process_root(yaml_parser_t *parser, struct buf *b, const char *toplevel_name)
+{
+ struct lab_yaml_event event;
+ if (!parse(parser, &event) || event.type != YAML_STREAM_START_EVENT) {
+ return false;
+ }
+ if (!parse(parser, &event) || event.type != YAML_DOCUMENT_START_EVENT) {
+ return false;
+ }
+ if (!parse(parser, &event) || event.type != YAML_MAPPING_START_EVENT) {
+ wlr_log(WLR_ERROR, "mapping is expected for toplevel node");
+ return false;
+ }
+
+ buf_add_fmt(b, "<%s>", toplevel_name);
+ if (!process_mapping(parser, b)) {
+ return false;
+ }
+ buf_add_fmt(b, "%s>", toplevel_name);
+ return true;
+}
+
+struct buf
+yaml_to_xml(FILE *stream, const char *toplevel_name)
+{
+ struct buf b = BUF_INIT;
+ yaml_parser_t parser;
+ yaml_parser_initialize(&parser);
+ yaml_parser_set_input_file(&parser, stream);
+
+ bool success = process_root(&parser, &b, toplevel_name);
+ if (success) {
+ wlr_log(WLR_DEBUG, "XML converted from YAML: %s", b.data);
+ } else {
+ buf_reset(&b);
+ }
+ yaml_parser_delete(&parser);
+ return b;
+}
diff --git a/src/config/rcxml.c b/src/config/rcxml.c
index d6ce92b6..2ac64560 100644
--- a/src/config/rcxml.c
+++ b/src/config/rcxml.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include
+#include
#include
#include
#include
@@ -15,6 +16,7 @@
#include
#include
#include "action.h"
+#include "common/buf.h"
#include "common/dir.h"
#include "common/list.h"
#include "common/macros.h"
@@ -36,6 +38,10 @@
#include "window-rules.h"
#include "workspaces.h"
+#if HAVE_LIBYAML
+#include "common/yaml2xml.h"
+#endif
+
static bool in_regions;
static bool in_usable_area_override;
static bool in_keybind;
@@ -549,7 +555,12 @@ fill_touch(char *nodename, char *content)
if (!strcasecmp(nodename, "touch")) {
current_touch = znew(*current_touch);
wl_list_append(&rc.touch_configs, ¤t_touch->link);
- } else if (!strcasecmp(nodename, "deviceName.touch")) {
+ return;
+ } else if (!content) {
+ return;
+ }
+
+ if (!strcasecmp(nodename, "deviceName.touch")) {
current_touch->device_name = xstrdup(content);
} else if (!strcasecmp(nodename, "mapToOutput.touch")) {
current_touch->output_name = xstrdup(content);
@@ -1742,6 +1753,37 @@ validate(void)
}
}
+static bool
+file_is_xml(const char *filename)
+{
+ if (str_endswith(filename, ".xml")) {
+ return true;
+ } else if (str_endswith(filename, ".yaml")) {
+ return false;
+ }
+
+ /*
+ * If the extension is not .xml or .yaml, judge by whther the content
+ * starts with '<'.
+ */
+ FILE *stream = fopen(filename, "r");
+ if (!stream) {
+ return true;
+ }
+ char c;
+ while ((c = fgetc(stream))) {
+ if (!isspace(c)) {
+ break;
+ }
+ }
+ fclose(stream);
+ if (c == '<') {
+ return true;
+ } else {
+ return false;
+ }
+}
+
void
rcxml_read(const char *filename)
{
@@ -1757,9 +1799,14 @@ rcxml_read(const char *filename)
wl_list_append(&paths, &path->link);
} else {
paths_config_create(&paths, "rc.xml");
+#ifdef HAVE_LIBYAML
+ struct wl_list paths_yaml;
+ wl_list_init(&paths_yaml);
+ paths_config_create(&paths_yaml, "rc.yaml");
+ wl_list_insert_list(paths.prev, &paths_yaml);
+#endif
}
- /* Reading file into buffer before parsing - better for unit tests */
bool should_merge_config = rc.merge_config;
struct wl_list *(*iter)(struct wl_list *list);
iter = should_merge_config ? paths_get_prev : paths_get_next;
@@ -1781,21 +1828,30 @@ rcxml_read(const char *filename)
continue;
}
- wlr_log(WLR_INFO, "read config file %s", path->string);
-
struct buf b = BUF_INIT;
- char *line = NULL;
- size_t len = 0;
- while (getline(&line, &len, stream) != -1) {
- char *p = strrchr(line, '\n');
- if (p) {
- *p = '\0';
+ if (HAVE_LIBYAML && !file_is_xml(path->string)) {
+#if HAVE_LIBYAML
+ wlr_log(WLR_INFO, "read yaml config file %s", path->string);
+ b = yaml_to_xml(stream, "labwc_config");
+#endif
+ } else {
+ wlr_log(WLR_INFO, "read xml config file %s", path->string);
+ char *line = NULL;
+ size_t len = 0;
+ while (getline(&line, &len, stream) != -1) {
+ char *p = strrchr(line, '\n');
+ if (p) {
+ *p = '\0';
+ }
+ buf_add(&b, line);
}
- buf_add(&b, line);
+ zfree(line);
}
- zfree(line);
+
fclose(stream);
- rcxml_parse_xml(&b);
+ if (b.len > 0) {
+ rcxml_parse_xml(&b);
+ }
buf_reset(&b);
if (!should_merge_config) {
break;
diff --git a/src/menu/menu.c b/src/menu/menu.c
index f8653d64..a8039a09 100644
--- a/src/menu/menu.c
+++ b/src/menu/menu.c
@@ -26,6 +26,10 @@
#include "node.h"
#include "theme.h"
+#if HAVE_LIBYAML
+#include "common/yaml2xml.h"
+#endif
+
#define PIPEMENU_MAX_BUF_SIZE 1048576 /* 1 MiB */
#define PIPEMENU_TIMEOUT_IN_MS 4000 /* 4 seconds */
@@ -566,6 +570,35 @@ is_toplevel_static_menu_definition(xmlNode *n, char *id)
return id && nr_parents(n) == 2;
}
+static char *
+get_property(xmlNode *n, const char *name)
+{
+ /* First, search from attributes */
+ char *prop = (char *)xmlGetProp(n, (const xmlChar *)name);
+ if (prop) {
+ return prop;
+ }
+
+ /* Then search from child nodes */
+ xmlNode *child;
+ for (child = n->children; child && child->name;
+ child = child->next) {
+ if (!strcmp((char *)child->name, name)) {
+ goto found_child_node;
+ }
+ }
+ return NULL;
+
+found_child_node:
+ for (child = child->children; child && child->name;
+ child = child->next) {
+ if (child->type == XML_TEXT_NODE) {
+ return xstrdup((char *)child->content);
+ }
+ }
+ return NULL;
+}
+
/*
*