#!/usr/bin/awk -f

# dunst-context-menu.awk
# Purpose: display a clickable menu for URLs and/or actions associated with dunst notifications.
# Copyright(c) 2019,2022 step
# License: MIT
# Requires: dunst, gtkmenuplus, gawk for i18n otherwise awk is enough
# Suggests: dunstify
# Portability: runs with any awk.
# Localization: ready, enabled if gawk is used.
# Version: 1.2.0

# # DESCRIPTION {{{1
#
# This script is a drop-in replacement for dmenu specific to providing dunst
# with a selection context menu for URLs and actions.
#
# # INSTALLATION {{{1
#
# Copy this file to a directory in your path and make it executable. Optionally
# run `gawk --gen-pot -f dunst-context-menu.awk > dunst-context-menu.pot` and
# [localize the pot file](and [https://www.gnu.org/software/gawk/manual/html_node/I18N-Example.html#I18N-Example).
# If you don't need localization then busybox awk is sufficient.
#
# # CONFIGURATION {{{1
#
# Edit dunstrc (in /etc/xdg/dunst for Fatdog64 or /usr/share/dunst) and set
#
# ```
#   dmenu = /path/to/dunst-context-menu.awk
#   show_indicators = yes
#   browser = /path/to/your/browser
#   context = ctrl+shift+period
# ```
#
# # DUNST MENU INTERFACE {{{1
#
# ## OVERVIEW {{{2
#
# Dunst is a lightweight replacement for the notification daemons provided by
# most desktop environments.  Dunst is the default notification daemon in
# Fatdog64 Linux.  The daemon accepts texts from clients, such as dunstify, and
# displays such texts in notification balloons. The daemon (dunst) can also
# display a clickable context menu that the client (dunstify) has specified as
# a set of "actions" (the -A option of dunstify). The action format mimics
# dmenu's.  Dmenu is a dynamic menu for X. It reads text from stdin, and
# creates a menu with one item for each line.  Dunst does not use dmenu, it
# just expects to read the dmenu format. If a menu item is a URL dunst opens it
# in the system browser otherwise dunst echos the selected menu item to
# stdout.  This file reads dunst's stdout expecting a gtkmenuplus[:2] menu
# specification, which is displayed as a context menu.
# See also section SELECTION MODE.
#
# ```
#  dunstify -A ...,a -A ...,b [:1]   ⇒ (A)a (A)b
#    ▲
#    ╰─▶ dunst ⇒ (U)browser
#          ▲
#          ╰─▶ context menu ├ a...
#                           ├ b...
#
# [:1] dunstify and notify-send are the two clients available in Fatdog64.
# Several other clients can be found on the Internet.  notify-send supports
# (U)RLs but not (A)ctions.
# [:2] gtkmenuplus is a dynamic menu for GTK. It is installed in the Fatdog64
# base system.  Gtkmenuplus items can run arbitrary commands.
# ```
#
# ## DUNST INTERFACE DETAILS {{{2
#
# ### SINGLE URL {{{3
#
# A notification message summary or body can embed a single URL, e.g.:
#
# ```sh
#  dunstify 'http://one'
#  dunstify the.summary 'the.body \n file://one'
#  dunstify the.summary 'the.body \n <a href="ftp:/one">go two</a>"'
#
#  dustify returns immediately
#  URL not limited to http(s), e.g. file://..., ftp://..., ... are OK
# ```
# The notification text starts with "(U)". Middle clicking the notification
# balloon makes dunst start the browser.  Pressing the 'context' key binding
# (default Ctrl+Shift+.) displays a context menu (default dmenu) for the user
# to select which URL to browse (just one in this case).  Dunst starts the
# browser when the user completes the selection.
#
# ### MULTIPLE URLS {{{3
#
# If the notification message embeds multiple URLs, e.g.:
#
# ```sh
#  dunstify 'http://one' '<a href="http://two">go two</a>'
#
#  dustify returns immediately
# ```
# right clicking the notification balloon or pressing the 'context' menu
# key binding both display the context menu.
#
# Dunst preserves URL input order, and returns to the client a list of URLs
# formatted as follows:
#
# ```
#  http://one                        plain URL case
#  [go two] http://two               HREF  URL case
# ```
#
# ### SINGLE ACTION {{{3
#
# A notification message can specify a single action, e.g.:
#
# ```sh
#  dunstify the.summary -A 'printing "ONE",one' &
# ```
# then the notification text starts with "(A)", and middle clicking the
# notification balloon makes dunst dispatch the formatted action text to
# dustify's stdout.  Dustify doesn't return until the action is dispatched.
# An action expires after its balloon has closed (left click or Ctrl+space).
#
# ### MULTIPLE ACTIONS {{{3
#
# If the notification message specifies multiple actions, e.g.:
#
# ```sh
#  dunstify summary -A 'printing "ONE",one' -A 'printing "TWO",two' &
# ```
# then the user can select which action to run by either right-clicking the
# notification window or pressing the 'context' menu key binding.  Dunst
# dispatches the selected action text to dustify's stdout after the user has
# completed the selection.  Dustify doesn't return until the action is
# dispatched.
#
# Dunst does not preserve the input order, and returns to the client a list of
# actions formatted as follows:
#
# ```
#  #two (summary) [12,Xdialog --msgbox "TWO" 0x0]
#  #one (summary) [12,Xdialog --msgbox "ONE" 0x0]
#
#  where 12 is the notification ID (dunstify -p prints the ID).
#  The body of each action is the text between ',' and ']'.
# ```
#
# ### MULTIPLE URLS AND MULTIPLE ACTIONS {{{3
#
# A message can include multiple actions and embed multiple URLs, e.g.:
#
# ```sh
#  dunstify 'http://one' 'http://two' -A '789,three' -A 'abc,four'
# ```
# then the notification text starts with "(AU)". If the user selects a URL then
# dunst runs the browser. If the user selects an action then dunst dispatches
# the formatted action text to dustify's stdout. Dustify doesn't return until
# the action is dispatched or the browser is started.
#
# # THIS SCRIPT'S INTERFACE TO DUNST {{{1
#
# This script is a drop-in replacement for dmenu specifically for interfacing
# with the dunst daemon.  It can work in two modes: "selection" (default) or
# "execution", as explained below.
#
# ## SELECTION MODE {{{2
#
# In selection mode this script sends formatted selections back to dunst, which
# then either starts the browser (U) or dispatches the action (A) to the client,
# either dustify or a custom client.  Selection mode is compatible with
# existing clients that wait for dunst to send them the selection. Note that
# dunstify waits until it gets a selection back but notify-send does not wait.
#
# ```
#  dunstify -A ...,a -A ...,b [:1]                              SELECTION MODE
#    ▲                         dunstify does not wait for balloon to be closed
#    ╰─▶ dunst ⇒(U)browser
#          ▲
#          ╰─▶ dunst-context-menu.awk
#                ▲
#                │               ├ a... ⇒ print (A)a
#                ╰─▶ gtkmenuplus ├ b... ⇒ print (A)b
# ```
#
# ## EXECUTION MODE {{{2
#
# In execution mode this script runs the browser or the selected action
# directly without relaying the selection to dunst. Execution mode only makes
# sense if you specify runnable actions, such as shell commands or command
# pathnames.  execution mode the client does not *need* to wait, Since in
# execution mode this script runs actions directly, the client _need_ not wait
# (but can) for the selection to be made.  By default stdout and stderr are
# redirected to /dev/null unless the client specified otherwise[:2]. To enable
# execution mode, the action body must include action modifier ":mode=exec"
# as shown below.
#
# ```
#  dunstify -A ...:mode=exec,a -A ...,b [:1]                    EXECUTION MODE
#    ▲                                 dunstify waits for balloon to be closed
#    ╰─▶ dunst
#          │
#          ╰─▶ dunst-context-menu.awk ⇒ (U)browser
#                ▲
#                │               ├ a... ⇒ run (A)a    with stdout/stderr
#                ╰─▶ gtkmenuplus ├ b... ⇒ run (A)b    redirected by default
# ```
#
# [:2] See note [:2] in section ACTION MODIFIERS
#
# ## EXECUTION vs. SELECTION {{{2
# By default, this script operates in selection mode for compatibility with
# existing clients but for anything but the simplest URL browsing menu you
# will want to use execution mode[:1].
#
# ```
#            EXECUTION                             SELECTION
#    The action is always executed.         Non-URL action is relayed.
#
#    The client specifieѕ actions,          The client specifies actions
#    this script executes them.             and executes them.
#
#    Actions never expire; their balloon    Actions expire when their balloon
#    can be retrieved from dunst's          is closed or times out.
#    history and the action replayed.
#
#    Action modifiers are stripped off      Action modifiers are relayed
#    before execution.                      with the action body.
# ```
#
# [:1] dunstctl (available since version 1.9.0) is another way to run a
# balloon's actions from the command line.
#
# ## ACTION SPECIFICATION {{{2
#
# An action is specified via dustify command option -A (multiple allowed) with
# the following format:
#
# ```
#   '-A' <action>[':'<modifier>...]','<menu_item_label>
# ```
#
# `<action>` can be empty or a gtkmenuplus command or a runnable path.
# Commands and runnable paths are printed to stdout in selection mode or run in
# execution mode.  In execution mode they run either as gtkmenuplus's children
# (default) or as children of this script (`:runnable=` modifier).  In the
# former case, by default stdout and stderr are redirected to /dev/null unless
# modifiers `:stdout` and `:stderr` were specified, and stdin must be managed
# directly by the command/runnable[:1]. In the latter case the command/runnable
# must manage all I/O redirections.
#
# [:1] A word of caution about handling stdin when gtkmenuplus is the parent.
# In most practical cases the `<action command will not need stdin. However, if
# it does, you need to remember that its stdin is attached to gtkmenuplus's and
# that when gtkmenuplus will end it will close its and the command's input
# stream.  So the command must cope with its input suddenly going away or it
# must detach from gtkmenuplus. Consider this example which is supposed to show
# a `yad` input form arising from a gtkmenuplus menu item selection:
#
# ```sh
#   gtkmenuplus - <<< $'item=t\ncmd=sh -c "yad --form --field= "'
# ```
#
# As it stands, this command will not work for very long. As soon as the menu
# selection is made yad is started while gtkmenuplus ends, so yad ends due to
# its stdin going away.  To fix this issue just detach yad by running it
# in the background:
#
# ```sh
#   gtkmenuplus - <<< $'item=t\ncmd=sh -c "yad --form --field= & "'
# ```
#
# In a slightly more deceptice example, yad could run in a shell function that
# ultimately is gtkmenuplus's child. In that case the fix is the same, possibly
# adding the `wait` command:
#
# ```sh
#    get_value() {
#	value=$(yad --form --field= & wait)
#	# do something with $value
#    }
#```
#
# ## ACTION MODIFIERS {{{2
#
# A client can change the mode and tailor actions by appending modifier strings
# at the end of any action body, before the comma that introduces the menu
# label.  Multiple modifiers are allowed. In execution mode modifiers are
# deleted from the action body before executing it directly.  In selection mode
# modifiers are left in place, therefore they are dispatched to the client as
# part of the action body.  Modifiers break down into two categories, global
# ones, which apply to all menu items, and item modifiers. Item modifiers are
# only available in execution mode.
#
# ```
#   Global modifiers
#  ":mode=exec"                   set execution mode; client blocks
#  ":browser=<command>"           open all URLs with <command>   [:1]
#  ":stdout=<redir>"              redirect stdout of all actions [:2]
#  ":stderr=<redir>"              redirect stderr of all actions [:2]
#  ":incl=<path>"                 include gtkmenuplus script     [:3]
#  ":debug=1"                     show / run context menu script
#
#   Item modifiers
#  ":launcher=/filepath.desktop"  launch the desktop file        [:4]
#  ":icon=iconpath_or_icon_name"  set menu item icon             [:5]
#  ":tooltip=text"                set menu item tooltip text     [:6]
#  ":hide=yes"                    hide this item (skip the menu) [:7]
#  ":runnable=yes"                run action with system() and   [:8]
#                                 skip the menu
#
#  [:1] The <command> string can use %u to substitute for the selected URL.
#  [:2] By default stdout and stderr are redirect to file /dev/null.
#       If stdout's <redir> starts with '|' it is treated as a command pipe.
#  [:3] See `man 5 gtkmenuplus`. Include file(s) can be used to override
#       the default gtkmenuplus directives, see bookmark DEFAULT:DIRECTIVES.
#  [:4] Replace <action>. "launcher=..." is a gtkmenuplus directive.
#       Launcher's label, icon and tooltip are read from the .desktop file.
#  [:5] Default menu item icon: "gtk-execute"; use "icon=NULL" to show no icon.
#  [:6] Default menu item tooltip: "action %s"; use "tooltip=NULL" to show
#       no tooltip.
#  [:7] Often used to hide the set of global modifiers that follow hide=yes
#       on the line.
#  [:8] All runnable actions are executed as they are encountered. If any
#       non-runnable, non-hidden actions remains, they become menu items
#       and the menu is shown, otherwise the menu is skipped altogether.
#       Runnables are shell commands (see man 1 system); they must take
#       care of redirections, if needed, and most importantly they must run
#       quickly (best in the background) so as not to block this script's
#       main loop!
# ```
#
# ## URL MODIFIERS {{{2
#
# URL modifiers are similar to action modifiers but are more limited. They can
# be appended to the URL with the usual ':' separator.  Only two item modifiers
# are valid: `icon=iconpath_or_icon_name` and `tooltip=text`. They can be
# used with both plain and HREF URL formats but bear in mind that the
# notification balloon will display the plain URL including the modifiers.
# Also recall that modifiers are only stripped off in execution mode, therefore
# in selection mode they remain appended to the URL that is passed to the
# browser.
# The default icon is "gtk-jump-to"; use "icon=NULL" to display no icon.
# The default tooltip is "browse %s"; use "tooltip=NULL" to display no tooltip.
#
# # EXAMPLES {{{1
#
# ## EXAMPLE 1: plain URL with modifier browser= {{{2
#
# ```
#  dunstify hi -A ':hide=yes,1' -A ':hide=yes:mode=exec:browser=yad --text=%u,2' http://one
#           │ menu item label┘       │                  │      menu item lable┘  │
#           └notification title      └see notes         └open URL %u with yad    └this URL
# ```
#
# Dunst returns
#
# ```
#  #2 (example) [8,:hide=yes:mode=exec:browser=yad --text=%u]
#  #1 (example) [8,:hide=yes]
#  http://one
# ```
#
# Therefore dunst displays a menu with a single entry: "http://one", which runs
# `yad --text=http://one`.  The reason for adding item #1, which seemingly does
# nothing useful, is explained in section DUNST INTERFACE DETAILS and in FAQ
# HOW TO FORCE DISPLAYING THE CONTEXT MENU.
# See also EXAMPLE 3.
#
# ## EXAMPLE 2: redirecting stdout {{{2
#
# ```sh
#  dunstify example -A ':hide=yes:mode=exec:stdout=/tmp/test,1' -A 'date,see /tmp/test'
# ```
#
# displays a menu with one entry, "see /tmp/test", which writes the current date
# to file /tmp/test. `"stdout=|wc >> \"/tmp/test\""` could be used to pipe `date`
# through `wc` and append the result to file /tmp/test.
#
# ## EXAMPLE 3: two URLs with modifiers "icon=" and "tooltip=" {{{2
#
# ```sh
#   dunstify -A ":hide=yes:browser=yad --center --text=%u,yad" \
#     -A ":mode=exec:hide=yes,_" 'http://one:icon=NULL' \
#     '<a href="file://two:icon=gtk-ok:tooltip=NULL">go two</a>'
# ```
#
# displays the following two items (here in gtkmenuplus format):
#
# ```sh
#  item=go two
#  icon=gtk-ok
#  cmd=yad --center --text=file://two
#  # no tooltip
#  #---#
#  item=one
#  # no icon
#  cmd=yad --center --text=http://one
#  tooltip=browse http://one
# ```
#
# ## MORE EXAMPLES {{{2
#
# See /usr/share/dunst/examples.
#
# # FAQ {{{1
#
# ## HOW TO FORCE DISPLAYING THE CONTEXT MENU {{{2
#
# Dunst displays the context menu! Since dunst calls this script and not
# viceversa, there is nothing that this script can do to force dunst to display
# the menu.  However, the actions you specify with -A can influence when/how
# dunst displays its menu.
#
# These are dunst's rules:
#
# A) With only one URL, or one action, or one rule and one action:
#   - Middle-click does not display the menu
#   - Shift+Ctrl+. displays the menu.
#
# B) With three or more elements:
#   - Middle-click and Shift+Ctrl+. display the menu.
#
# The client can force displaying the menu with middle-click in case A by
# adding two hidden actions.  The two action bodies must differ in at least one
# character.
#
# ```sh
#  dunstify -A 'x:hide=yes,_' -A 'y:hide=yes,_' http://go.there
# ```
# END

# {{{1}}}

BEGIN {
	# requires gawk
	TEXTDOMAIN = "fatdog"

	SN = "dunst-context-menu.awk"

	# hard stop the modifier parsing loop at
	g_max_modifier = 16

	g_menu_debug = "exec sh -c \"f=/tmp/$$\"'; cat > $f; < $f yad --text-info --button=\"test:gtkmenuplus $f\" --button=gtk-ok:0 --geometry=1000x700+70+50' dunst-context-menu.awk"

### Initialize global properties (all are also modifiers)

	Gp["mode"] = "selection"
	Gp["stdout"] = Gp["stderr"] = "/dev/null"
	Gp["browser"] = "/usr/local/bin/defaultbrowser"
	Gp["debug"] = 0
}

### Initialize properties => Ap[] (some are also modifiers)

{
	s = type = item = icon = data = ttip = ""
}

### Got URL? (U)

/^[[]/ || /^[[:alpha:]]+:\/\// {

	# parse plain vs HREF URL format
	if(url_format_is_href = (1 == index($0, "[") && 0 < (p = index($0, "]"))) ) {
		url = substr($0, p + 2)
		label = substr($0, 2, p - 2)
	} else { # parse plain URL case
		url = $0
	}

	# parse global modifiers => Gp[]
	s = parse_modifiers(s, Gp, "mode|browser|stdout|stderr|incl|debug")

	# parse item modifiers => Ap[]
	delete(Ap)
	url = parse_modifiers(url, Ap, "icon|tooltip")

	if("NULL" == Ap["icon"])
		;
	else if("" == Ap["icon"])
		icon = "$icon_url" # a gtkmenuplus variable
	else
		icon = Ap["icon"]

	if("NULL" == Ap["tooltip"])
		;
	else if("" == Ap["tooltip"])
		tooltip = sprintf(_"browse %s", url)
	else
		tooltip = Ap["tooltip"]

	# label reprise for plain URL format
	if(!url_format_is_href) { # plain URL
		label = substr(url, index(url, "/") + 2)
		# chop HTTP GET variables
		if(match(tolower(url), /https?/) && p = index(label, "?")) {
			label = substr(label, 1, p - 1)
		}
	}

	# properties to menu
	type = "url"
	item = label
	icon = icon
	data = url
	ttip = tooltip
}

### Parse modifiers
# return: s with parsed modifiers chopped, array rA by reference
# input s: [<commands>] [':'<name> '=' <value>]* ']'
# input regex: ERE <name> [ '|' <name> ]*
function parse_modifiers(s, rA, regex,   p, name, value, max) {
	while(match(s, ":("regex")=[^]:]*") && max++ < g_max_modifier) {
		RSTART += 1; RLENGTH -= 1 # chop ^:
		p     = index(substr(s, RSTART), "=")
		name  = substr(s, RSTART, p - 1)
		value = substr(s, RSTART + p, RLENGTH - p)
		rA[name] = value
		gsub(":"name"=[^]:]*", "", s)
	}
	return s
}

### Got action? (A)

/^#/ {
	## Parse formatted action '#'<label> ' (' <subject> ') [' <id> ', '<action with modifiers> ']'

	if(0 < (p = index($0, "(")) ) {

		label = substr($0, 2, p - 3)
		s = substr($0, p + 1)
		if(0 < (p = index(s, ") [")) ) {
			subject = substr(s, 1, p - 1) # not used
			s = substr(s, p + 3)
			#               vv  :  an id can be negative, too
			if(match(s, /^-?[[:digit:]]+,.+]/)) {
				p = index(substr(s, RSTART), ",")
				id = substr(s, RSTART, p - 1)
				s = substr(s, p + 1)

				# parse global modifiers => Gp[]
				s = parse_modifiers(s, Gp, "mode|browser|stdout|stderr|incl|debug")

				# parse item modifiers => Ap[]
				delete(Ap)
				s = parse_modifiers(s, Ap, "hide|launcher|icon|tooltip|runnable")

				# run action and skip the menu if runnable is true
				# ---------------------------------------------
				# Rough edge: the runnable must include
				# redirections if needed, and - most
				# importantly - it should run in the background
				# to avoid blocking awk's main loop!
				# ---------------------------------------------
				if("yes" == Ap["runnable"]) {
					runnable = substr(s, 1, length(s) - 1)
					if(status = system(runnable)) {
						printf _"%s: exit %d, runnable was: %s\n", SN, status, runnable > "/dev/stderr"
					}
					next
				}

				# continue to next action if this one is hidden
				if("yes" == Ap["hide"]) {
					next
				}

				action = "" != Ap["launcher"] ? Ap["launcher"] : substr(s, 1, length(s) - 1)

				if("NULL" == Ap["icon"])
					;
				else if("" == Ap["icon"])
					icon = "$icon_action" # a gtkmenuplus variable
				else
					icon = Ap["icon"]

				if("NULL" == Ap["tooltip"])
					;
				else if("" == Ap["tooltip"])
					tooltip = sprintf(_"action %s", action)
				else
					tooltip = Ap["tooltip"]

				# properties to menu
				type    = "action"
				item    = label
				icon    = icon
				data    = action
				ttip    = tooltip
			}
		}
	}
}

### Save item properties for menu END game

{
	++nC
	C[nC, "$0"]       = $0
	C[nC, "s"]        = s
	C[nC, "incl"]     = Gp["incl"]
	C[nC, "type"]     = type
	C[nC, "item"]     = item
	C[nC, "icon"]     = icon
	C[nC, "ttip"]     = ttip
	C[nC, "data"]     = data
	C[nC, "blau"]     = "" != Ap["launcher"] # data == <path>
}

### Early exit if the menu is empty

END {
	if(!nC) {
		# appease dunst, which expects _some_ output
		print ""
		exit
	}
}

### Show menu

END {
	# the menu pipe
	g_menu = Gp["debug"] > 0 ? g_menu_debug : "gtkmenuplus -q -q -"

	if("exec" == Gp["mode"]) {
		# redirect menu stdout and stderr away from dunst's input pipe
		if("" != Gp["stdout"]) {
			if (1 == index(Gp["stdout"], "|"))
				g_menu = g_menu " " Gp["stdout"]
			else
				g_menu = g_menu " 1> \"" Gp["stdout"] "\""
		}
		if("" != Gp["stderr"]) {
			g_menu = g_menu " 2> \"" Gp["stderr"] "\""
		}
	}

	# DEFAULT:DIRECTIVES initialize gtkmenuplus menu
	# overridde with an "incl=<path>" global modifier
	print "configure=errorconsoleonly" | g_menu
	print "configure=nolaunchernodisplay" | g_menu
	print "configure=noicons" | g_menu
	print "iconsize=24" | g_menu
	print "icon_url=gtk-jump-to" | g_menu
	print "icon_action=gtk-execute" | g_menu

	for(i = 1; i <= nC; i++) {
		line = C[i, "$0"]
		incl = C[i, "incl"]
		type = C[i, "type"]
		item = C[i, "item"]
		icon = C[i, "icon"]
		ttip = C[i, "ttip"]; gsub(/#/, "⋕", ttip) # U+22D5
		data = C[i, "data"]
		blau = C[i, "blau"]

		if("selection" == Gp["mode"]) {
			cmd = "echo " (1 == index(line, "#") ? "\\" : "") line
			# Why the \\?  Gtkmenudialog passes cmd verbatim to
			# g_spawn_command_line_async --> g_shell_parse_argv -->
			# tokenize_command_line, which is designed to discard
			# everything between # in position 1 and \n unless # is
			# escaped with \.

		} else if("exec" == Gp["mode"]) {
			if("url" == type) {
				if("" == Gp["browser"]) {
					cmd = "defaultbrowser "data
				} else {
					cmd = Gp["browser"]
					gsub(/%u/, data, cmd)
				}

			} else if("action" == type) {
				if(blau) {
					printf "launcher=%s\n", data | g_menu
					continue
				}
				cmd = data

			} else {
				item = sprintf(_"ERROR: input: %s", C[i, "$0"]) # shouldn't happen
			}
		}

# include gtkmenuplus file, any mode:
		if ("" != incl) {
			s = buf = ""
			while(0 < (getline buf < incl))
				s = s buf
			print s | g_menu
		}

# URL, action (except launcher) and ERROR, any mode:
		print ("item="item"\n") (""!=icon ?"icon="icon"\n" :"") ("cmd="cmd"\n") (""!=ttip ?"tooltip="ttip"\n" :"") | g_menu
	}
	close(g_menu)
}
