mirror of
https://github.com/labwc/labwc.git
synced 2026-04-11 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
|
|
@ -90,8 +90,8 @@ modification.
|
||||||
|
|
||||||
Openbox spec is somewhat of a stable standard considering how long it has
|
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
|
remained unchanged for and how wide-spread its adoption is by lightweight
|
||||||
distributions such as LXDE, LXQt, BunsenLabs, ArchLabs, Mabox and Raspbian. Some
|
distributions such as LXDE, LXQt, BunsenLabs, ArchLabs, Mabox and Raspbian.
|
||||||
widely used themes (for example Numix and Arc) have built-in support.
|
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
|
We could have invented a whole new syntax, but that's not where we want to
|
||||||
spend our effort.
|
spend our effort.
|
||||||
|
|
@ -182,8 +182,8 @@ prevent installing the wlroots headers:
|
||||||
## 3. Configuration
|
## 3. Configuration
|
||||||
|
|
||||||
User config files are located at `${XDG_CONFIG_HOME:-$HOME/.config/labwc/}`
|
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],
|
with the following five files being used: [rc.xml] (or [rc.yaml]), [menu.xml],
|
||||||
[environment] and [themerc-override].
|
[autostart], [shutdown], [environment] and [themerc-override].
|
||||||
|
|
||||||
Run `labwc --reconfigure` to reload configuration and theme.
|
Run `labwc --reconfigure` to reload configuration and theme.
|
||||||
|
|
||||||
|
|
@ -275,6 +275,7 @@ See [integration] for further details.
|
||||||
[metacity]: https://github.com/GNOME/metacity
|
[metacity]: https://github.com/GNOME/metacity
|
||||||
|
|
||||||
[rc.xml]: docs/rc.xml.all
|
[rc.xml]: docs/rc.xml.all
|
||||||
|
[rc.yaml]: docs/rc.yaml
|
||||||
[menu.xml]: docs/menu.xml
|
[menu.xml]: docs/menu.xml
|
||||||
[autostart]: docs/autostart
|
[autostart]: docs/autostart
|
||||||
[shutdown]: docs/shutdown
|
[shutdown]: docs/shutdown
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ Config layout for ~/.config/labwc/
|
||||||
- environment
|
- environment
|
||||||
- menu.xml
|
- menu.xml
|
||||||
- rc.xml
|
- rc.xml
|
||||||
|
- rc.yaml
|
||||||
- shutdown
|
- shutdown
|
||||||
- themerc-override
|
- themerc-override
|
||||||
- xinitrc
|
- xinitrc
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ labwc - configuration files
|
||||||
|
|
||||||
Labwc uses openbox-3.6 specification for configuration and theming, but does not
|
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
|
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.
|
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];
|
but it's also possible to use [true|false] and\/or [on|off];
|
||||||
this is for compatibility with Openbox.
|
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
|
## CORE
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -575,9 +647,9 @@ extending outward from the snapped edge.
|
||||||
Load the default keybinds listed below. This is an addition to the
|
Load the default keybinds listed below. This is an addition to the
|
||||||
openbox specification and provides a way to keep config files simpler
|
openbox specification and provides a way to keep config files simpler
|
||||||
whilst allowing your specific keybinds.
|
whilst allowing your specific keybinds.
|
||||||
Note that if no rc.xml is found, or if no <keyboard><keybind> entries
|
Note that if no rc.xml or rc.yaml is found, or if no <keyboard><keybind>
|
||||||
exist, the same default keybinds will be loaded even if the <default />
|
entries exist, the same default keybinds will be loaded even if the
|
||||||
element is not provided.
|
<default /> element is not provided.
|
||||||
|
|
||||||
```
|
```
|
||||||
A-Tab - next window
|
A-Tab - next window
|
||||||
|
|
@ -668,9 +740,10 @@ extending outward from the snapped edge.
|
||||||
*<mouse><default />*
|
*<mouse><default />*
|
||||||
Load default mousebinds. This is an addition to the openbox
|
Load default mousebinds. This is an addition to the openbox
|
||||||
specification and provides a way to keep config files simpler whilst
|
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
|
allowing user specific binds. Note that if no rc.xml or rc.yaml is
|
||||||
<mouse><mousebind> entries exist, the same default mousebinds will be
|
found, or if no <mouse><mousebind> entries exist, the same default
|
||||||
loaded even if the <default /> element is not provided.
|
mousebinds will be loaded even if the <default /> element is not
|
||||||
|
provided.
|
||||||
|
|
||||||
## TOUCH
|
## TOUCH
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,13 +106,23 @@ ID attributes are unique. Duplicates are ignored.
|
||||||
When writing pipe menu scripts, make sure to escape XML special characters such
|
When writing pipe menu scripts, make sure to escape XML special characters such
|
||||||
as "&" ("&"), "<" ("<"), and ">" (">").
|
as "&" ("&"), "<" ("<"), and ">" (">").
|
||||||
|
|
||||||
|
|
||||||
# LOCALISATION
|
# LOCALISATION
|
||||||
|
|
||||||
Available localisation for the default "client-menu" is only shown if no
|
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
|
"client-menu" is present in menu.xml. Any menu definition in menu.xml is
|
||||||
interpreted as a user-override.
|
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
|
# SEE ALSO
|
||||||
|
|
||||||
labwc(1), labwc-actions(5), labwc-config(5), labwc-theme(5)
|
labwc(1), labwc-actions(5), labwc-config(5), labwc-theme(5)
|
||||||
|
|
|
||||||
33
docs/menu.yaml
Normal file
33
docs/menu.yaml
Normal 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 }
|
||||||
|
|
@ -33,7 +33,8 @@ install_data(
|
||||||
'shutdown',
|
'shutdown',
|
||||||
'themerc',
|
'themerc',
|
||||||
'rc.xml',
|
'rc.xml',
|
||||||
'rc.xml.all'
|
'rc.xml.all',
|
||||||
|
'rc.yaml',
|
||||||
],
|
],
|
||||||
install_dir: get_option('datadir') / 'doc' / meson.project_name()
|
install_dir: get_option('datadir') / 'doc' / meson.project_name()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
89
docs/rc.yaml
Normal file
89
docs/rc.yaml
Normal 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 }
|
||||||
9
include/common/yaml2xml.h
Normal file
9
include/common/yaml2xml.h
Normal 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 */
|
||||||
13
meson.build
13
meson.build
|
|
@ -72,6 +72,7 @@ pixman = dependency('pixman-1')
|
||||||
math = cc.find_library('m')
|
math = cc.find_library('m')
|
||||||
png = dependency('libpng')
|
png = dependency('libpng')
|
||||||
svg = dependency('librsvg-2.0', version: '>=2.46', required: false)
|
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
|
if get_option('xwayland').enabled() and not wlroots_has_xwayland
|
||||||
error('no wlroots Xwayland support')
|
error('no wlroots Xwayland support')
|
||||||
|
|
@ -87,6 +88,13 @@ else
|
||||||
endif
|
endif
|
||||||
conf_data.set10('HAVE_RSVG', have_rsvg)
|
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()
|
if get_option('static_analyzer').enabled()
|
||||||
add_project_arguments(['-fanalyzer'], language: 'c')
|
add_project_arguments(['-fanalyzer'], language: 'c')
|
||||||
endif
|
endif
|
||||||
|
|
@ -125,6 +133,11 @@ if have_rsvg
|
||||||
svg,
|
svg,
|
||||||
]
|
]
|
||||||
endif
|
endif
|
||||||
|
if have_libyaml
|
||||||
|
labwc_deps += [
|
||||||
|
yaml,
|
||||||
|
]
|
||||||
|
endif
|
||||||
|
|
||||||
subdir('include')
|
subdir('include')
|
||||||
subdir('src')
|
subdir('src')
|
||||||
|
|
|
||||||
|
|
@ -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('nls', type: 'feature', value: 'auto', description: 'Enable native language support')
|
||||||
option('static_analyzer', type: 'feature', value: 'disabled', description: 'Run gcc static analyzer')
|
option('static_analyzer', type: 'feature', value: 'disabled', description: 'Run gcc static analyzer')
|
||||||
option('test', type: 'feature', value: 'disabled', description: 'Run tests')
|
option('test', type: 'feature', value: 'disabled', description: 'Run tests')
|
||||||
|
option('yaml', type: 'feature', value: 'enabled', description: 'Enable configuration in YAML')
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,9 @@ labwc_sources += files(
|
||||||
'spawn.c',
|
'spawn.c',
|
||||||
'string-helpers.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
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <libxml/parser.h>
|
#include <libxml/parser.h>
|
||||||
|
|
@ -15,6 +16,7 @@
|
||||||
#include <wlr/util/box.h>
|
#include <wlr/util/box.h>
|
||||||
#include <wlr/util/log.h>
|
#include <wlr/util/log.h>
|
||||||
#include "action.h"
|
#include "action.h"
|
||||||
|
#include "common/buf.h"
|
||||||
#include "common/dir.h"
|
#include "common/dir.h"
|
||||||
#include "common/list.h"
|
#include "common/list.h"
|
||||||
#include "common/macros.h"
|
#include "common/macros.h"
|
||||||
|
|
@ -36,6 +38,10 @@
|
||||||
#include "window-rules.h"
|
#include "window-rules.h"
|
||||||
#include "workspaces.h"
|
#include "workspaces.h"
|
||||||
|
|
||||||
|
#if HAVE_LIBYAML
|
||||||
|
#include "common/yaml2xml.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static bool in_regions;
|
static bool in_regions;
|
||||||
static bool in_usable_area_override;
|
static bool in_usable_area_override;
|
||||||
static bool in_keybind;
|
static bool in_keybind;
|
||||||
|
|
@ -549,7 +555,12 @@ fill_touch(char *nodename, char *content)
|
||||||
if (!strcasecmp(nodename, "touch")) {
|
if (!strcasecmp(nodename, "touch")) {
|
||||||
current_touch = znew(*current_touch);
|
current_touch = znew(*current_touch);
|
||||||
wl_list_append(&rc.touch_configs, ¤t_touch->link);
|
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);
|
current_touch->device_name = xstrdup(content);
|
||||||
} else if (!strcasecmp(nodename, "mapToOutput.touch")) {
|
} else if (!strcasecmp(nodename, "mapToOutput.touch")) {
|
||||||
current_touch->output_name = xstrdup(content);
|
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
|
void
|
||||||
rcxml_read(const char *filename)
|
rcxml_read(const char *filename)
|
||||||
{
|
{
|
||||||
|
|
@ -1757,9 +1799,14 @@ rcxml_read(const char *filename)
|
||||||
wl_list_append(&paths, &path->link);
|
wl_list_append(&paths, &path->link);
|
||||||
} else {
|
} else {
|
||||||
paths_config_create(&paths, "rc.xml");
|
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;
|
bool should_merge_config = rc.merge_config;
|
||||||
struct wl_list *(*iter)(struct wl_list *list);
|
struct wl_list *(*iter)(struct wl_list *list);
|
||||||
iter = should_merge_config ? paths_get_prev : paths_get_next;
|
iter = should_merge_config ? paths_get_prev : paths_get_next;
|
||||||
|
|
@ -1781,21 +1828,30 @@ rcxml_read(const char *filename)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
wlr_log(WLR_INFO, "read config file %s", path->string);
|
|
||||||
|
|
||||||
struct buf b = BUF_INIT;
|
struct buf b = BUF_INIT;
|
||||||
char *line = NULL;
|
if (HAVE_LIBYAML && !file_is_xml(path->string)) {
|
||||||
size_t len = 0;
|
#if HAVE_LIBYAML
|
||||||
while (getline(&line, &len, stream) != -1) {
|
wlr_log(WLR_INFO, "read yaml config file %s", path->string);
|
||||||
char *p = strrchr(line, '\n');
|
b = yaml_to_xml(stream, "labwc_config");
|
||||||
if (p) {
|
#endif
|
||||||
*p = '\0';
|
} 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);
|
fclose(stream);
|
||||||
rcxml_parse_xml(&b);
|
if (b.len > 0) {
|
||||||
|
rcxml_parse_xml(&b);
|
||||||
|
}
|
||||||
buf_reset(&b);
|
buf_reset(&b);
|
||||||
if (!should_merge_config) {
|
if (!should_merge_config) {
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
103
src/menu/menu.c
103
src/menu/menu.c
|
|
@ -26,6 +26,10 @@
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
|
|
||||||
|
#if HAVE_LIBYAML
|
||||||
|
#include "common/yaml2xml.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define PIPEMENU_MAX_BUF_SIZE 1048576 /* 1 MiB */
|
#define PIPEMENU_MAX_BUF_SIZE 1048576 /* 1 MiB */
|
||||||
#define PIPEMENU_TIMEOUT_IN_MS 4000 /* 4 seconds */
|
#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;
|
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:
|
* <menu> elements have three different roles:
|
||||||
* * Definition of (sub)menu - has ID, LABEL and CONTENT
|
* * Definition of (sub)menu - has ID, LABEL and CONTENT
|
||||||
|
|
@ -575,9 +608,9 @@ is_toplevel_static_menu_definition(xmlNode *n, char *id)
|
||||||
static void
|
static void
|
||||||
handle_menu_element(xmlNode *n, struct server *server)
|
handle_menu_element(xmlNode *n, struct server *server)
|
||||||
{
|
{
|
||||||
char *label = (char *)xmlGetProp(n, (const xmlChar *)"label");
|
char *label = get_property(n, "label");
|
||||||
char *execute = (char *)xmlGetProp(n, (const xmlChar *)"execute");
|
char *execute = get_property(n, "execute");
|
||||||
char *id = (char *)xmlGetProp(n, (const xmlChar *)"id");
|
char *id = get_property(n, "id");
|
||||||
|
|
||||||
if (execute && label && id) {
|
if (execute && label && id) {
|
||||||
wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute);
|
wlr_log(WLR_DEBUG, "pipemenu '%s:%s:%s'", id, label, execute);
|
||||||
|
|
@ -676,7 +709,7 @@ error:
|
||||||
static void
|
static void
|
||||||
handle_separator_element(xmlNode *n)
|
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);
|
current_item = separator_create(current_menu, label);
|
||||||
free(label);
|
free(label);
|
||||||
}
|
}
|
||||||
|
|
@ -725,36 +758,13 @@ parse_buf(struct server *server, struct buf *buf)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static bool
|
||||||
* @stream can come from either of the following:
|
parse_menu_file(const char *filename, struct server *server)
|
||||||
* - 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)
|
|
||||||
{
|
{
|
||||||
struct wl_list paths;
|
struct wl_list paths;
|
||||||
paths_config_create(&paths, filename);
|
paths_config_create(&paths, filename);
|
||||||
|
|
||||||
|
bool file_found = false;
|
||||||
bool should_merge_config = rc.merge_config;
|
bool should_merge_config = rc.merge_config;
|
||||||
struct wl_list *(*iter)(struct wl_list *list);
|
struct wl_list *(*iter)(struct wl_list *list);
|
||||||
iter = should_merge_config ? paths_get_prev : paths_get_next;
|
iter = should_merge_config ? paths_get_prev : paths_get_next;
|
||||||
|
|
@ -765,14 +775,37 @@ parse_xml(const char *filename, struct server *server)
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
file_found = true;
|
||||||
wlr_log(WLR_INFO, "read menu file %s", path->string);
|
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);
|
fclose(stream);
|
||||||
|
if (b.len > 0) {
|
||||||
|
parse_buf(server, &b);
|
||||||
|
}
|
||||||
|
buf_reset(&b);
|
||||||
if (!should_merge_config) {
|
if (!should_merge_config) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
paths_destroy(&paths);
|
paths_destroy(&paths);
|
||||||
|
return file_found;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|
@ -983,7 +1016,13 @@ void
|
||||||
menu_init(struct server *server)
|
menu_init(struct server *server)
|
||||||
{
|
{
|
||||||
wl_list_init(&server->menus);
|
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_rootmenu(server);
|
||||||
init_windowmenu(server);
|
init_windowmenu(server);
|
||||||
post_processing(server);
|
post_processing(server);
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,37 @@
|
||||||
test_lib = static_library(
|
test_lib_sources = files(
|
||||||
'test_lib',
|
|
||||||
sources: files(
|
|
||||||
'../src/common/buf.c',
|
'../src/common/buf.c',
|
||||||
'../src/common/mem.c',
|
'../src/common/mem.c',
|
||||||
'../src/common/string-helpers.c'
|
'../src/common/string-helpers.c',
|
||||||
),
|
|
||||||
include_directories: [labwc_inc],
|
|
||||||
dependencies: [dep_cmocka],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test_deps = [
|
||||||
|
dep_cmocka,
|
||||||
|
]
|
||||||
|
|
||||||
tests = [
|
tests = [
|
||||||
'buf-simple',
|
'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
|
foreach t : tests
|
||||||
test(
|
test(
|
||||||
'test_@0@'.format(t),
|
'test_@0@'.format(t),
|
||||||
|
|
|
||||||
99
t/yaml2xml.c
Normal file
99
t/yaml2xml.c
Normal 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);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue