mirror of
https://github.com/labwc/labwc.git
synced 2026-04-12 08:21:13 -04:00
Merge 52a1c1c880 into 72f59c32a2
This commit is contained in:
commit
f0d50891e5
16 changed files with 713 additions and 65 deletions
|
|
@ -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
198
src/common/yaml2xml.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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, ¤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;
|
||||
|
|
|
|||
103
src/menu/menu.c
103
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* <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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue