This commit is contained in:
Hiroaki Yamamoto 2024-08-25 11:32:23 +03:00 committed by GitHub
commit f0d50891e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 713 additions and 65 deletions

View file

@ -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

View file

@ -3,6 +3,7 @@ Config layout for ~/.config/labwc/
- environment
- menu.xml
- rc.xml
- rc.yaml
- shutdown
- themerc-override
- xinitrc

View file

@ -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
"<foo>bar</foo>" 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 <key>array-element</key> in XML.
For example, a YAML expression:
```
touch:
- deviceName: xxxx
mapToOutput: eDP-1
- deviceName: yyyy
mapToOutput: HDMI-1
```
is converted to an XML expression:
```
<touch>
<deviceName>xxxx</deviceName>
<mapToOutput>eDP-1</mapToOutput>
</touch>
<touch>
<deviceName>yyyy</deviceName>
<mapToOutput>eDP-1</mapToOutput>
</touch>
```
To avoid unnecessary indentations, some nodes that wrap array elements in XML
can be ommitted. This includes:
- *<windowSwitcher><fields><field>*
- *<regions><region>*
- *<windowRules><windowRule>*
- *<libinput><device>*
- *<desktops><names><name>*
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 <keyboard><keybind> entries
exist, the same default keybinds will be loaded even if the <default />
element is not provided.
Note that if no rc.xml or rc.yaml is found, or if no <keyboard><keybind>
entries exist, the same default keybinds will be loaded even if the
<default /> element is not provided.
```
A-Tab - next window
@ -668,9 +740,10 @@ extending outward from the snapped edge.
*<mouse><default />*
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
<mouse><mousebind> entries exist, the same default mousebinds will be
loaded even if the <default /> element is not provided.
allowing user specific binds. Note that if no rc.xml or rc.yaml is
found, or if no <mouse><mousebind> entries exist, the same default
mousebinds will be loaded even if the <default /> element is not
provided.
## TOUCH

View file

@ -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 "&" ("&amp;"), "<" ("&lt;"), and ">" ("&gt;").
# 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)

33
docs/menu.yaml Normal file
View file

@ -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 }

View file

@ -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()
)

89
docs/rc.yaml Normal file
View file

@ -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 }

View file

@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_YAML2XML_H
#define LABWC_YAML2XML_H
#include <common/buf.h>
#include <stdio.h>
struct buf yaml_to_xml(FILE *stream, const char *toplevel_name);
#endif /* LABWC_YAML2XML_H */

View file

@ -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')

View file

@ -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')

View file

@ -20,3 +20,9 @@ labwc_sources += files(
'spawn.c',
'string-helpers.c',
)
if have_libyaml
labwc_sources += files(
'yaml2xml.c',
)
endif

198
src/common/yaml2xml.c Normal file
View file

@ -0,0 +1,198 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <assert.h>
#include <yaml.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <wlr/util/log.h>
#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] -> <fields><field>x<field><field>y<field<fields>
*/
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 <mousebind action="Press">.
*/
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;
}

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <glib.h>
#include <libxml/parser.h>
@ -15,6 +16,7 @@
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#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, &current_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;

View file

@ -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;
}
/*
* <menu> elements have three different roles:
* * Definition of (sub)menu - has ID, LABEL and CONTENT
@ -575,9 +608,9 @@ is_toplevel_static_menu_definition(xmlNode *n, char *id)
static void
handle_menu_element(xmlNode *n, struct server *server)
{
char *label = (char *)xmlGetProp(n, (const xmlChar *)"label");
char *execute = (char *)xmlGetProp(n, (const xmlChar *)"execute");
char *id = (char *)xmlGetProp(n, (const xmlChar *)"id");
char *label = get_property(n, "label");
char *execute = get_property(n, "execute");
char *id = get_property(n, "id");
if (execute && label && id) {
wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute);
@ -676,7 +709,7 @@ error:
static void
handle_separator_element(xmlNode *n)
{
char *label = (char *)xmlGetProp(n, (const xmlChar *)"label");
char *label = get_property(n, "label");
current_item = separator_create(current_menu, label);
free(label);
}
@ -725,36 +758,13 @@ parse_buf(struct server *server, struct buf *buf)
return true;
}
/*
* @stream can come from either of the following:
* - fopen() in the case of reading a file such as menu.xml
* - popen() when processing pipemenus
*/
static void
parse_stream(struct server *server, FILE *stream)
{
char *line = NULL;
size_t len = 0;
struct buf b = BUF_INIT;
while (getline(&line, &len, stream) != -1) {
char *p = strrchr(line, '\n');
if (p) {
*p = '\0';
}
buf_add(&b, line);
}
free(line);
parse_buf(server, &b);
buf_reset(&b);
}
static void
parse_xml(const char *filename, struct server *server)
static bool
parse_menu_file(const char *filename, struct server *server)
{
struct wl_list paths;
paths_config_create(&paths, filename);
bool file_found = false;
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;
@ -765,14 +775,37 @@ parse_xml(const char *filename, struct server *server)
if (!stream) {
continue;
}
file_found = true;
wlr_log(WLR_INFO, "read menu file %s", path->string);
parse_stream(server, stream);
struct buf b = BUF_INIT;
if (HAVE_LIBYAML && str_endswith(path->string, ".yaml")) {
#if HAVE_LIBYAML
b = yaml_to_xml(stream, "openbox_menu");
#endif
} else {
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);
}
free(line);
}
fclose(stream);
if (b.len > 0) {
parse_buf(server, &b);
}
buf_reset(&b);
if (!should_merge_config) {
break;
}
}
paths_destroy(&paths);
return file_found;
}
static int
@ -983,7 +1016,13 @@ void
menu_init(struct server *server)
{
wl_list_init(&server->menus);
parse_xml("menu.xml", server);
bool file_found = parse_menu_file("menu.xml", server);
(void)file_found;
#if HAVE_LIBYAML
if (!file_found) {
parse_menu_file("menu.yaml", server);
}
#endif
init_rootmenu(server);
init_windowmenu(server);
post_processing(server);

View file

@ -1,18 +1,37 @@
test_lib = static_library(
'test_lib',
sources: files(
test_lib_sources = files(
'../src/common/buf.c',
'../src/common/mem.c',
'../src/common/string-helpers.c'
),
include_directories: [labwc_inc],
dependencies: [dep_cmocka],
'../src/common/string-helpers.c',
)
test_deps = [
dep_cmocka,
]
tests = [
'buf-simple',
]
if have_libyaml
test_lib_sources += [
'../src/common/yaml2xml.c',
]
test_deps += [
wlroots,
yaml,
]
tests += [
'yaml2xml',
]
endif
test_lib = static_library(
'test_lib',
sources: test_lib_sources,
include_directories: [labwc_inc],
dependencies: test_deps
)
foreach t : tests
test(
'test_@0@'.format(t),

99
t/yaml2xml.c Normal file
View file

@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <cmocka.h>
#include "common/buf.h"
#include "common/macros.h"
#include "common/yaml2xml.h"
struct test_set {
const char *name, *yaml, *xml;
};
const struct test_set test_sets[] = {
{
.name = "key-scalar",
.yaml = "xxx: yyy",
.xml = "<test><xxx>yyy</xxx></test>",
},
{
.name = "key-sequence",
.yaml = "xxx: [yyy, zzz]",
.xml = "<test><xxx>yyy</xxx><xxx>zzz</xxx></test>",
},
{
.name = "key-mapping",
.yaml = "xxx: {yyy: zzz}",
.xml = "<test><xxx><yyy>zzz</yyy></xxx></test>",
},
{
.name = "window-switcher-fields",
.yaml = "windowSwitcher: {fields: [xxx, yyy]}",
.xml =
"<test><windowSwitcher><fields>"
"<field>xxx</field>"
"<field>yyy</field>"
"</fields></windowSwitcher></test>",
},
{
.name = "theme-fonts",
.yaml = "theme: {fonts: [xxx, yyy]}",
.xml =
"<test><theme>"
"<font>xxx</font>"
"<font>yyy</font>"
"</theme></test>",
},
{
.name = "mousebinds",
.yaml =
"mousebinds:\n"
" - { button: W-Left, action: Press, actions: [ { name: Raise }, { name: Move } ] }\n"
" - { button: W-Right, action: Drag, action: { name: Resize} }\n",
.xml =
"<test>"
"<mousebind>"
"<button>W-Left</button>"
"<action>Press</action>"
"<action><name>Raise</name></action>"
"<action><name>Move</name></action>"
"</mousebind>"
"<mousebind>"
"<button>W-Right</button>"
"<action>Drag</action>"
"<action><name>Resize</name></action>"
"</mousebind>"
"</test>",
},
};
static void
test_yaml_to_xml(void **state)
{
(void)state;
for (int i = 0; i < (int)ARRAY_SIZE(test_sets); i++) {
const struct test_set *set = &test_sets[i];
char buf[1024];
FILE *stream = fmemopen(buf, sizeof(buf), "w+");
fwrite(set->yaml, strlen(set->yaml), 1, stream);
fseek(stream, 0, SEEK_SET);
struct buf b = yaml_to_xml(stream, "test");
fclose(stream);
assert_string_equal(b.data, set->xml);
}
}
int main(int argc, char **argv)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_yaml_to_xml),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}