You are not logged in.

#1 2026-04-13 08:32:13

johnraff
nullglob
From: Nagoya, Japan
Registered: 2015-09-09
Posts: 13,198
Website

csv2xml - a jgmenu to labwc menu convertor.

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"),picom

originally 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//&amp;/\&}"
    string="${string//&lt;/<}"
    string="${string//&gt;/>}"
    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//&/\&amp;}"
    string="${string//</\&lt;}"
    string="${string//>/\&gt;}"
    string="${string//\"/\&quot;}"
    string="${string//\'/\&apos;}"
    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_xml

Last 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 )

Introduction to the Bunsenlabs Boron Desktop

Offline

#2 2026-04-15 01:12:04

hhh
Gaucho
From: High in the Custerdome
Registered: 2015-09-17
Posts: 16,869
Website

Re: csv2xml - a jgmenu to labwc menu convertor.

^ My response from your other post, let's keep the testing and feedback for this feature in this thread. smile

https://forums.bunsenlabs.org/viewtopic … 31#p150131


I don't care what you do at home. Would you care to explain?

Offline

#3 2026-04-15 05:20:14

johnraff
nullglob
From: Nagoya, Japan
Registered: 2015-09-09
Posts: 13,198
Website

Re: csv2xml - a jgmenu to labwc menu convertor.

^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 )

Introduction to the Bunsenlabs Boron Desktop

Offline

#4 Yesterday 03:45:06

johnraff
nullglob
From: Nagoya, Japan
Registered: 2015-09-09
Posts: 13,198
Website

Re: csv2xml - a jgmenu to labwc menu convertor.

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 )

Introduction to the Bunsenlabs Boron Desktop

Offline

#5 Yesterday 20:59:52

hhh
Gaucho
From: High in the Custerdome
Registered: 2015-09-17
Posts: 16,869
Website

Re: csv2xml - a jgmenu to labwc menu convertor.

I'm on track for testing this, this weekend.

https://forums.bunsenlabs.org/viewtopic … 76#p150176


I don't care what you do at home. Would you care to explain?

Offline

#6 Today 01:42:07

hhh
Gaucho
From: High in the Custerdome
Registered: 2015-09-17
Posts: 16,869
Website

Re: csv2xml - a jgmenu to labwc menu convertor.

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

#7 Today 01:56:18

johnraff
nullglob
From: Nagoya, Japan
Registered: 2015-09-09
Posts: 13,198
Website

Re: csv2xml - a jgmenu to labwc menu convertor.

^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 )

Introduction to the Bunsenlabs Boron Desktop

Offline

#8 Today 02:05:02

hhh
Gaucho
From: High in the Custerdome
Registered: 2015-09-17
Posts: 16,869
Website

Re: csv2xml - a jgmenu to labwc menu convertor.

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

#9 Today 02:21:03

hhh
Gaucho
From: High in the Custerdome
Registered: 2015-09-17
Posts: 16,869
Website

Re: csv2xml - a jgmenu to labwc menu convertor.

BTW, the app menu in xfce4-panel is decent as a fallback...

    <keybind key="W-Space">
      <action name="Execute" command="xfce4-popup-applicationsmenu" />
    </keybind>

OtVJwHKH_t.png


I don't care what you do at home. Would you care to explain?

Offline

#10 Today 02:31:39

johnraff
nullglob
From: Nagoya, Japan
Registered: 2015-09-09
Posts: 13,198
Website

Re: csv2xml - a jgmenu to labwc menu convertor.

hhh wrote:

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 )

Introduction to the Bunsenlabs Boron Desktop

Offline

#11 Today 02:35:29

johnraff
nullglob
From: Nagoya, Japan
Registered: 2015-09-09
Posts: 13,198
Website

Re: csv2xml - a jgmenu to labwc menu convertor.

hhh wrote:

BTW, the app menu in xfce4-panel is decent as a fallback...

    <keybind key="W-Space">
      <action name="Execute" command="xfce4-popup-applicationsmenu" />
    </keybind>

https://thumbs2.imgbox.com/87/8d/OtVJwHKH_t.png

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 )

Introduction to the Bunsenlabs Boron Desktop

Offline

Board footer

Powered by FluxBB