You are not logged in.

#1 2020-03-01 23:47:32

damo
....moderator....
Registered: 2015-08-20
Posts: 6,403

jgmenu - Helper functions for scripting

Bunsenlabs helper functions for scripting menus

[ Some of this material is taken from Basic Introduction to jgmenu on BL Lithium ]


BL helpfully provides some functions which can be sourced by scripts. These include some shorthand for jgmenu code, and can be utilised by adding this to any script that sends output to jgmenu:

BL_COMMON_LIBDIR="/usr/lib/bunsen/common"   # source jgmenu helper functions

if ! . "${BL_COMMON_LIBDIR}/bl-includes" 2> /dev/null; then
    echo $"Error: Failed to locate bl-includes in ${BL_COMMON_LIBDIR}" >&2
    exit 1
fi

I keep my menu scripts in ~/.config/jgmenu/scripts, but obviously you can put them in your $PATH so they can be run directly.

/usr/lib/bunsen/common/bl-includes has some functions that generate an associative array menu_content.
'root' indexes the root menu, while submenu content is stored under the index of its tag.

  • jgmenuItem Put an item in the root menu or any submenu.
    Usage: jgmenuItem tag label command
    Tag 'root' indicates root menu item.
    -

  • jgmenuSeparator Add a separator.
    Usage: jgmenuSeparator tag [label]
    -

  • jgmenuSubmenu Put a checkout() for a submenu in the root menu or any submenu.
    Usage: jgmenuSubmenu tag(parentmenu) tag(submenu) label
    -

  • jgmenuEnd Print root menu, then submenus.
    Usage: jgmenuEnd


A simple menu could be constructed like this:

#!/bin/bash
##
## my-pipemenu

BL_COMMON_LIBDIR="/usr/lib/bunsen/common"   # source jgmenu helper functions

if ! . "$BL_COMMON_LIBDIR/bl-includes" 2> /dev/null; then
    echo $"Error: Failed to locate bl-includes in $BL_COMMON_LIBDIR" >&2
    exit 1
fi

jgmenuSeparator 'root' "Main Menu"
# "jgmenuSubmenu tag(parentmenu) tag(submenu) label"
jgmenuSubmenu 'root' "terminal" "Terminal Emulators"    
jgmenuSubmenu 'root' "editor" "Text Editors"

# "jgmenuItem tag label command"
jgmenuItem "terminal" "x-terminal-emulator" "x-terminal-emulator"
jgmenuItem "terminal" "Terminator" "terminator"
jgmenuItem "terminal" "lxterminal" "lxterminal"

jgmenuItem "editor" "Geany" "geany"
jgmenuItem "editor" "Medit" "medit"
jgmenuItem "editor" "Gedit" "gedit"

jgmenuEnd

If my-menu.csv contains:

^sep(Main Menu)
Applications,^pipe(my-pipemenu)

Run it with the commands:

jgmenu --simple --csv-file='~/.config/jgmenu/mymenu.csv'
.
or
.
cat ~/.config/jgmenu/mymenu.csv | jgmenu --simple

                            PflbpChm.jpg
                           
aaB0MgP.png

Constructing a menu to display keybinds for Run commands
#!/bin/bash
##
## xbindkeys-pipemenu

# Initialize vars and arrays
BL_COMMON_LIBDIR="/usr/lib/bunsen/common"
KBINDS="${HOME}/.xbindkeysrc"
declare -a KB_ARR
declare -a CMD_ARR

# source the helper functions
if ! . "${BL_COMMON_LIBDIR}/bl-includes" 2> /dev/null; then
    echo $"Error: Failed to locate bl-includes in ${BL_COMMON_LIBDIR}" >&2
    exit 1
fi

# add a separator with text to the output
jgmenuSeparator 'run_commands' 'Run commands'

# read and process .xbindkeysrc, and add the keybind names and commands to arrays
while read -r line;do
    if [[ -n $line ]];then              # skip empty lines
        if [[ ${line} = \#* ]];then     # skip commented lines
            continue
        elif [[ ${line} = \"* ]];then   # if line starts with double-quote...
            CMD=$(echo "${line}" | sed -e 's/"//g')     # remove double-quote
            CMD_ARR+=("${CMD}")                         # add command to array
        else
            # replace "Mod4" with "Super", and remove spaces
            KB=$(echo "${line}" | sed -e 's/Mod4/Super/' -e 's/ //g')
            KB_ARR+=("${KB}")           # add keybinds to array
        fi
    fi
done < "${KBINDS}"

# read the arrays and populate the output
i=0
for c in "${KB_ARR[@]}";do
    # use a tab ('\t') to align second field text
    curr_item=$(printf "%s\t  %s" "${KB_ARR[$i]}" "${CMD_ARR[$i]}")
    # add a menu item to the submenu which has '^tag(run_commands)'
    jgmenuItem 'run_commands' "${curr_item}"
    i=$((i+1))
done

# Tidy up and close out the menu
jgmenuEnd
exit

Run the script in the terminal to see what the output looks like: that is what jgmenu is using as its csv input.

                            hS9p17hm.jpg
                           

aaB0MgP.png


Adding pipemenu items dynamically

Let's edit my-pipemenu to show a "Utilities" menu

  • Terminals

  • Editors

  • Screenfetch output

#!/bin/bash
##
## my-pipemenu

BL_COMMON_LIBDIR="/usr/lib/bunsen/common"   # source jgmenu helper functions

if ! . "$BL_COMMON_LIBDIR/bl-includes" 2> /dev/null; then
    echo $"Error: Failed to locate bl-includes in $BL_COMMON_LIBDIR" >&2
    exit 1
fi

jgmenuSubmenu 'root' "terminal" "Terminal Emulators"
jgmenuSubmenu 'root' "editor" "Text Editors"
jgmenuSubmenu 'root' "screenfetch" "Show screenfetch"

jgmenuSeparator "terminal" "Terminals"
jgmenuSeparator "editor" "Text Editors"    
jgmenuSeparator "screenfetch" "screenfetch"    

# display output from screenfetch command
while read -r line;do
    jgmenuItem "screenfetch" "${line}"
done <<< $(screenfetch -nN)

jgmenuItem "terminal" "x-terminal-emulator" "x-terminal-emulator"
jgmenuItem "terminal" "Terminator" "terminator"
jgmenuItem "terminal" "lxterminal" "lxterminal"

jgmenuItem "editor" "Geany" "geany"
jgmenuItem "editor" "Medit" "medit"
jgmenuItem "editor" "Gedit" "gedit"

jgmenuEnd

                            WOjAucHm.jpg

Note that the order of the jgmenuItem <tag> sections doesn't matter - they will be displayed in the order of their parent jgmenuSubmenu's. The same is also true for the jgmenuSeparator's.

aaB0MgP.png


Processing text with pangoEscape() to use in a menu

pangoEscape() is used by the jgmenu*() functions, and isn't designed to be used separately. However, with a slight tweak this function could be used in a helper script, to get text into the right format for jgmenu. Trying to redirect unprocessed text to jgmenu may result in:

fatal: item <n> was not correctly terminated with a '\n'
.
or
.
fatal: no closing triple quote
.
or
.
Error on line 1: Entity did not end with a semicolon; most likely you used an ampersand character without intending to start an entity — escape ampersand as &amp;

Strings and lines will also be split on comma characters, so we need to

  • escape special characters:  & < >

  • wrap lines in triple-quotes in case they contain commas (NB csv = comma separated variable)

  • ensure printf outputs a newline in the right place

jg-output.sh

#!/bin/bash
##
## jg-output.sh

input="$1"

pangoEscape() {
    local string="${1//&/&amp;}"
    string="${string//</&lt;}"
    string="${string//>/&gt;}"
    printf '%s\n' "$string"         # note the newline added here
}

input=$(awk '{ print "\"\"\""$0"\"\"\""}' <<< "${input}")

pangoEscape "${input}"

Now you can do something like

jg-output.sh "$(cat input.txt)" | jgmenu --simple

Let's process a command output which has been redirected to a textfile, then display it in my-pipemenu. Save the output from neofetch; process it with pangoEscape() using jg-output.sh; then use the file as the source for a menu item:

neofetch --stdout --disable title underline model term > neofetch.txt
jg-output.sh "$(cat neofetch.txt)" > neofetch.txt

(There are some commas in the output, so the second step is needed here.)

Now add these lines in the order you want them in my-pipemenu

# in the submenu section
jgmenuSubmenu 'root' "neofetch" "Show neofetch"

# anywhere before the jgmenuItems section
jgmenuSeparator "neofetch" "neofetch"    

# anywhere after the submenus and separators
# display content of a textfile
while read -r line;do
    jgmenuItem "neofetch" "${line}"
done <neofetch.txt

                            touAlREm.jpg
                           

aaB0MgP.png

Using heredocs to build a menu

For reference:
Advanced Bash-Scripting Guide: Here Documents

This syntax (here-document) is used to denote the creation of a text file from whatever is between the "EOF"s. ("EOF" signifies "End Of File", but you could use other text if you wanted to: "HERE", "THERE", "EVERYWHERE" etc)

cat >file <<EOF
foo
bar
EOF

The cat command is taking all the text up to "EOF" and redirecting it into "file". This means we could use temporary files to script a menu, and delete them when we are done.

So a single script can do the job, without having to use previously saved rc and csv files, like jgmenurc and prepend.csv.

Making temporary files

CONFIG_FILE=$(mktemp --tmpdir jgcfg.XXX)
MENU_ITEMS=$(mktemp --tmpdir jgmenu.XXX)

Example config file

It could be built like this:

cat > ${CONFIG_FILE} <<EOF 
tint2_look          = 0
position_mode       = center

menu_width          = 220
menu_height_min     = 0
menu_height_max     = 500
menu_margin_x       = 10
menu_margin_y       = 40
menu_padding_top    = 130
menu_padding_right  = 10
menu_padding_bottom = 10
menu_padding_left   = 10
menu_border         = 1

item_height         = 20
item_radius         = 2
item_border         = 1
item_halign         = left
item_valign         = top
item_margin_x       = 10
item_margin_y       = 4

sep_halign          = right
#sub_padding_left    = 10

color_menu_bg       = #2B454F 90
color_menu_border   = #eeeeee 10
#color_norm_fg      = #eeeeee 100
color_sel_bg        = #ffffff 10
#color_sel_fg       = #eeeeee 100
color_sel_border    = #eeeeee 8
color_scroll_ind    = #eeeeee 50

icon_theme          = Paper
icon_size           = 0
arrow_string        = ›
arrow_width         = 16
EOF

Example csv file

cat <<EOF > ${MENU_ITEMS}
@icon,,10,10,200,,2,left,top,#000000 50,#000000 50,/path/to/image
^sep(Menu)
    Terminal Emulators,^checkout(terminal)
    Text Editors,^checkout(editors)

^tag(terminal)
    Terminal,x-terminal-emulator
    lxterminal,lxterminal
    terminator,terminator

^tag(editors)
    Geany,geany
    Medit,medit
    Gedit,gedit
EOF

Now you can put the config and csv heredocs in functions, and call them in the script:

mkconfigfile        # set up config 
mkmenufile          # set up menu csv

jgmenu --simple --config-file=${CONFIG_FILE} --csv-file=${MENU_ITEMS} 2>/dev/null

                            m9gM7KRm.jpg
                           

The script now needs to do some tidying up after the menu closes, by removing the temp files. I use an array here, because it makes it easier if you have many variables. For example, a screenshot menu might also have temporary scrot and scrot-thumb image files.

# Clean up temp files
declare -a VARS
VARS=("${CONFIG_FILE}" "${MENU_ITEMS}" )

for v in "${VARS[@]}";do
    rm -f "${v}"
done

Full script

#!/bin/bash
##
## heredocs.sh

function mkconfigfile() {   # set up jgmenu config
    
cat > ${CONFIG_FILE} <<EOF 
tint2_look          = 0
position_mode       = pointer

menu_width          = 220
menu_height_min     = 0
menu_height_max     = 500
menu_margin_x       = 10
menu_margin_y       = 40
menu_padding_top    = 130
menu_padding_right  = 10
menu_padding_bottom = 10
menu_padding_left   = 10
menu_border         = 1

item_height         = 20
item_radius         = 2
item_border         = 1
item_halign         = left
item_valign         = top
item_margin_x       = 10
item_margin_y       = 4

sep_halign          = right
#sub_padding_left    = 10

color_menu_bg       = #2B454F 90
color_menu_border   = #eeeeee 10
#color_norm_fg      = #eeeeee 100
color_sel_bg        = #ffffff 10
#color_sel_fg       = #eeeeee 100
color_sel_border    = #eeeeee 8
color_scroll_ind    = #eeeeee 50

icon_theme          = Paper
icon_size           = 0
arrow_string        = ›
arrow_width         = 16
EOF
}

function mkmenufile() {
cat <<EOF > ${MENU_ITEMS}
@icon,,10,10,200,,2,left,top,#000000 50,#000000 50,/path/to/image
^sep(Menu)
    Terminal Emulators,^checkout(terminal)
    Text Editors,^checkout(editors)

^tag(terminal)
    Terminal,x-terminal-emulator
    lxterminal,lxterminal
    terminator,terminator

^tag(editors)
    Geany,geany
    Medit,medit
    Gedit,gedit
EOF
}
### END FUNCTIONS ###

CONFIG_FILE=$(mktemp --tmpdir jgcfg.XXX)
MENU_ITEMS=$(mktemp --tmpdir jgmenu.XXX)

mkconfigfile        # run function to set up config 
mkmenufile          # run function to set up menu csv

# run jgmenu
jgmenu --simple --config-file=${CONFIG_FILE} --csv-file=${MENU_ITEMS} 2>/dev/null

# Clean up temp files
declare -a VARS
VARS=("${CONFIG_FILE}" "${MENU_ITEMS}" )

for v in "${VARS[@]}";do
    rm -f "${v}" || echo "${v} couldn't be deleted"
done

exit

Last edited by damo (2020-03-05 10:09:39)


Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt  «» BunsenLabs on DeviantArt

Offline

Board footer

Powered by FluxBB