config: support rc.yaml

Based on @johanmalm's work.

This adds libyaml as an optional dependency.
This commit is contained in:
tokyo4j 2024-08-18 12:22:07 +09:00
parent a0a9f977b4
commit 85b6e25484
13 changed files with 588 additions and 31 deletions

View file

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

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

@ -0,0 +1,194 @@
// 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";
}
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;
@ -1743,6 +1749,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)
{
@ -1758,9 +1795,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;
@ -1782,21 +1824,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;