You are not logged in.
csv2xml
This script I put together - it took a bit longer than expected, and might not be amazingly useful except perhaps for people making a new Wayland system. Aimed not so much at users as developers.
It will take a jgmenu csv menu file and print out openbox-style xml that should work with labwc. Of course some of it will need editing - like, the "openbox" submenu will need replacing with something for labwc - but people with a long jgmenu menu might enjoy not having to type out all that XML. Just tweak a few places as needed.
A lot of the jgmenu pipemenus in BL are reusing openbox pipemenus, eg this:
Compositor,^pipe(jgmenu_run ob --cmd="bl-compositor" --tag="bl-compositor"),picomoriginally came from the xml pipemenu "bl-compositor", and this converter will migrate it back to:
<menu id="bl-compositor" label="Compositor" icon="picom" execute="bl-compositor" />Anyway, there are short help and debug options, but when you find bugs or improvements, please post!
Just copy/paste from here and either make it executable or call it with bash: 'bash scriptname -c csvfile' -x xmlfile':
#!/bin/bash
#csv2xml
debug() { :; }
HELP="Usage: ${0##*/} [OPTIONS] CSV_FILE
Convert a jgmenu CSV file
(comma‑separated, with optional """...""", ^sep, ^tag, ^checkout, ^pipe)
to a labwc‑compatible xml file.
@ widgets and . includes are ignored.
The converted xml will be printed to STDOUT, or the chosen file.
Options:
-c, --csvfile <file> The csv file to read.
-x, --xmlfile <file> The xml file to write to.
-d, --debug Enable debug output
-h, --help Show this help and exit
"
while [[ -n $1 ]]
do
case "$1" in
-h|--help)
echo "$HELP"
exit 0
;;
-d|--debug)
debug() {
echo -e "$*" >&2
}
shift
;;
-c|--csvfile)
csvfile=$2
shift 2
;;
-x|--xmlfile)
xmlfile=$2
shift 2
;;
*)
echo "${0##*/}: $1: no such option" >&2
exit 1
;;
esac
done
if [[ -n $xmlfile ]]
then
exec >"$xmlfile"
else
debug 'No xml file specified, printing to STDOUT'
fi
parse_jgmenu_line() {
local line="$1"
local pos=0 field="" in_quotes=false
while (( pos < ${#line} )); do
# Check for triple-quote toggle
if [[ "${line:$pos:3}" == '"""' ]]; then
[[ $in_quotes = false ]] && in_quotes=true || in_quotes=false
pos=$((pos+3))
continue
fi
# Comma is a field separator only outside quotes
if [[ "${line:$pos:1}" == ',' ]] && [[ $in_quotes = false ]]; then
fields+=("$field")
field=""
pos=$((pos+1))
continue
fi
# Regular character — add to current field
field+="${line:$pos:1}"
pos=$((pos+1))
done
fields+=("$field") # Don't forget the last field
for i in "${!fields[@]}"
do
fields[i]=$(strip_spaces "${fields[i]}")
done
# three special characters will have been pango escaped in Description (fields[0])
fields[0]=$(pango_unescape "${fields[0]}")
}
strip_spaces(){
local string
string=$1
read -r string <<<"$string"
printf '%s' "$string"
}
pango_unescape(){
local string
string="${1//&/\&}"
string="${string//</<}"
string="${string//>/>}"
printf '%s' "$string"
}
####### functions to generate labwc menu ###########################
# Each helper emits embedded newlines; caller appends with $'\n'.
OBmenuStart() {
printf '%s' '<openbox_menu>'
}
OBmenuEnd() {
printf '%s' '</openbox_menu>'
}
rootmenuStart() {
printf '%s' ' <menu id="root-menu">'
}
# end any toplevel menu except the <openbox_menu>
rootmenuEnd() {
printf '%s' ' </menu>'
}
# args: label command icon (same order as csv fields)
menuItem() {
printf ' <item label="%s" icon="%s" >\n <action name="Execute" command="%s" />\n </item>' \
"$(OBlabelEscape "$1")" "$(XMLescape "$3")" "$(XMLescape "$2")"
}
menuSeparator() {
if [[ ${1-} ]]; then
printf ' <separator label="%s" />' "$(OBlabelEscape "$1")"
else
printf ' <separator />'
fi
}
menuSubmenuStart() {
printf '\n <menu id="%s" label="%s" icon="%s" >\n' \
"$(XMLescape "$1")" "$(OBlabelEscape "$2")" "$(XMLescape "$3")"
}
menuSubmenuEnd() {
printf ' </menu>'
}
menuSubmenuExternal() {
printf ' <menu id="%s" />' "$(XMLescape "$1")"
}
pipemenu() {
printf ' <menu id="%s" label="%s" icon="%s" execute="%s" />' \
"$(XMLescape "$1")" "$(OBlabelEscape "$2")" "$(XMLescape "$3")" "$(XMLescape "$4")"
}
# escape special characters
XMLescape() {
local string="${1//&/\&}"
string="${string//</\<}"
string="${string//>/\>}"
string="${string//\"/\"}"
string="${string//\'/\'}"
printf '%s' "$string"
}
OBlabelEscape() {
local string
string="$(XMLescape "$1")"
printf '%s' "${string//_/__}"
}
########################################################################
generate_xml() {
declare -A menus
declare -A submenulabels
declare -A submenuicons
menus[root]=''
menu=root
debug "currently adding items to menu: $menu"
while read -r line; do
case "$line" in
'')
[[ $menu != root ]] && debug "end of submenu $menu"
menu=root
debug "currently adding items to menu: $menu"
continue
;;
'#'*)
continue
;;
'.'*)
debug "ignoring sourced file: $line"
continue
;;
'@'*)
debug "ignoring widget: $line"
continue
;;
esac
fields=()
parse_jgmenu_line "$line"
case "${fields[0]}" in
^sep\(*\))
title=${fields[0]#*\(}
title=${title%\)*}
menus["$menu"]+=$(menuSeparator "${title}")$'\n'
;;
^tag\(*\))
id=${fields[0]#*\(}
id=${id%\)*}
menu=${id}
debug "currently adding items to menu: $menu"
continue
;;
*)
case "${fields[1]}" in
^back\(\))
continue
;;
^checkout\(*\))
id=${fields[1]#*\(}
id=${id%\)*}
menus["$menu"]+=$(menuSubmenuExternal "$id")$'\n'
submenulabels["$id"]="${fields[0]}"
submenuicons["$id"]="${fields[2]}"
continue
;;
^pipe\(*\)) # pipemenu command needs rewriting if it's an openbox pipemenu, otherwise (csv) leave empty
cmd=${fields[1]#*\(}
cmd=${cmd%\)*}
id=${cmd#*--tag=\"}
id=${id%\"*}
if [[ $cmd = 'jgmenu_run ob'* ]]
then # openbox xml pipemenu
cmd="${cmd#*--cmd=\"}"
cmd="${cmd%%\"*}"
else
debug "Not an xml pipemenu: $cmd"
cmd="<!-- $cmd -->"
fi
label=${fields[0]}
icon=${fields[2]}
menus["$menu"]+=$(pipemenu "$id" "$label" "$icon" "$cmd")$'\n'
continue
esac
menus["$menu"]+=$(menuItem "${fields[@]}")$'\n'
;;
esac
done < "$csvfile"
rootmenu="${menus[root]}"
unset 'menus[root]'
OBmenuStart
echo
printf '%s\n' '<!-- defining submenus -->'
for i in "${!menus[@]}"
do
menuSubmenuStart "$i" "${submenulabels[$i]}" "${submenuicons[$i]}"
printf '%s' "${menus[$i]}"
menuSubmenuEnd
echo
done
printf '\n%s\n\n' '<!-- root menu starts here -->'
rootmenuStart
echo
printf '%s' "$rootmenu"
rootmenuEnd
echo
OBmenuEnd
echo
}
generate_xmlLast edited by johnraff (Today 02:02:44)
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
^ My response from your other post, let's keep the testing and feedback for this feature in this thread. ![]()
I don't care what you do at home. Would you care to explain?
Offline
^agreed, discussion of the utility - bugs, improvements etc here.
But the xml menu it produced for BL Wayland can be discussed over there, right?
Even if nothing more is done to csv2xml right now, we do need an xml labwc menu for our upcoming Carbon Wayland plugin, quite soon.
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
One quirk that came up is that submenus in the output xml have to be defined before they are called, or else they will be ignored. Labwc behaved like this yesterday, and I think Openbox was the same way.
In contrast, the ^tag() definitions in jgmenu's prepend.csv can come anywhere in the file and still be used, so I guess jgmenu is parsing the whole csv file before generating the menu.
What csv2xml does is output a block of xml like this
<menu id="bl-xfce4panelConfig" label="xfce4-panel" icon="xfce4-panel" >
<item label="xfce4-panel Preferences" icon="" >
--- more stuff ---
</menu>for each ^tag() block in prepend.csv, and that submenu is called by a line like this:
<menu id="bl-xfce4panelConfig" />in some other menu. As long as that line appears later in the xml than the defining block then the menu will be written. csv2xml writes the root menu last so most submenus are OK, but if a sub-submenu appears in a submenu then it's necessary that the sub-submenu is defined before the submenu.
The csv2xml script could probably be enhanced to look at the submenu nesting and make sure that all the menu blocks are written in the right order, but I'm not sure it's worth the effort because the final xml will always need some editing by the developer anyway, if only to replace the openbox section with one for labwc. Fixing the menu definition order is just a matter of cut and paste on the xml blocks to get them in the right order - a minute or two's work probably.
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
I'm on track for testing this, this weekend.
I don't care what you do at home. Would you care to explain?
Offline
What's your recommended rc.xml keybind code for opening the menu?Never mind, I assume it's the same as any action keybind, but with whatever I name the script.
Last edited by hhh (Today 01:49:20)
I don't care what you do at home. Would you care to explain?
Offline
^I think right now you want to invoke "root-menu" because that's the one defined by menu.xml
BTW the menu.xml I recommend trying is here:
https://forums.bunsenlabs.org/viewtopic … 51#p150151
What you get from feeding prepend.csv to csv2xml is the "raw" menu that needs some manual editing.
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
I need to install jgmenu to produce the files? Sorry, talk me through it a bit more.
I don't care what you do at home. Would you care to explain?
Offline
Offline
I need to install jgmenu to produce the files? Sorry, talk me through it a bit more.
This is a utility to generate an xml file from a jgmenu csv file. You don't need jgmenu, but you need to have a csv file you want to convert. If you just want a provisional BL labwc xml menu file, try this:
https://forums.bunsenlabs.org/viewtopic … 51#p150151
(It was generated from the default Carbon prepend.csv, then tweaked.)
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline
BTW, the app menu in xfce4-panel is decent as a fallback...
<keybind key="W-Space"> <action name="Execute" command="xfce4-popup-applicationsmenu" /> </keybind>
Sure, there are various options for auto-generated app menus using the desktop files.
The BL menu is something else, hand-crafted over the centuries...
(Surely we've had this discussion many times already?)
EDIT: when I ran "xfce4-popup-applicationsmenu" I got "xfdesktop: not found".
Add the menu plugin to xfce4-panel and then the command seems to work.
Works fine on Carbon X11 though.
Another thing with an auto-appsmenu on Wayland is that a lot of (even more than on X11) the menu items will turn out to be unusable because they're X11-only apps. I don't think there's any provision for OnlyShowIn=Wayland in app desktop files, though I can see more and more need for it - startup apps too.
But for now - how about adding the labwc menu generator in that "All Applications" submenu?
<menu id="labwc-menu-generator" label="All Applications" icon="applications-other" execute="labwc-menu-generator --icons --pipemenu" />It seems to come along with labwc.
But we really should have a way of weeding out incompatible menu items, or else double down on recommending users to avoid that submenu except as a last resort.
Last edited by johnraff (Today 06:19:26)
...elevator in the Brain Hotel, broken down but just as well...
( a boring Japan blog (currently paused), now on Bluesky, there's also some GitStuff )
Offline