#!/bin/ash
# vim: foldmarker=[[[,]]]:

# =============================================================================
# Findnrun - a progressive finder
  Version=2.5.0
# authors: Copyright (C)2015-2018 step; (C)2015 SFR, L18L
# license: GNU GPL version 2
# depends: gtk-dialog 0.8.3+, gawk, ash (or bash), mdview (NLS help only), xwininfo, yad
# source: https://github.com/step-/find-n-run
# forum: http://www.murga-linux.com/puppy/viewtopic.php?t=102811
# =============================================================================

SN=findnrun
# Localization settings. [[[1
export TEXTDOMAIN=findnrun
export OUTPUT_CHARSET=UTF-8
. "${0%/*}"/../share/$SN/i18n_table.sh && i18n_table
>/dev/null gettext 'INSTRUCTIONS FOR TRANSLATORS
1 Download the latest commented translation template from:
  https://raw.githubusercontent.com/step-/find-n-run/master/usr/share/doc/nls/findnrun/findnrun.pot
2 Follow the translation tutorial at:
  https://github.com/step-/find-n-run/blob/master/usr/share/doc/findnrun/TRANSLATING.md'

# Initialize variables that can be changed. [[[1
# i18n Main window title
APP_NAME=$i18n_Findnrun

XDG_DATA_DIRS=${XDG_DATA_DIRS:-/usr/share:/usr/local/share}
XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local}

{ read DESKTOP_FILE_DIRS; read ICON_DIRS; } << EOF
$(
  IFS=:
  set -- ${XDG_DATA_HOME} ${XDG_DATA_DIRS}
  for i; do echo -n ":$i/applications"; done; echo
  for i; do echo -n ":$i/icons"; done; echo
)
EOF
DESKTOP_FILE_DIRS=${DESKTOP_FILE_DIRS#:} ICON_DIRS=${ICON_DIRS#:}
# Ref. http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#directory_layout
# Note: Search by icon *theme* isn't implemented.
# Additional Fatdog64-specific locations start at .../midi-icons.
ICON_DIRS="$HOME/.icons:${ICON_DIRS}:/usr/share/pixmaps:/usr/share/midi-icons:/usr/share/mini-icons"

[ -f "${CONFIG}" ] || CONFIG="${HOME}/.findnrunrc"
x="${0%/*}/../share/doc/$SN"
FNRHELPINDEX=${FNRHELPINDEX:-$x/index.md:$x/help[en].tar.gz:$x/index.html}

# Initialize variables that should not be changed. [[[1
APP_TITLE="${APP_NAME}" # see also APP_TITLE_UNIQ in start_window()
REMARK= # leave unset
FNRDEBUG=${FNRDEBUG:-} # unset to disable debug trace to stderr; set verbosity (integer) 1-10
if [ -n "${FNRDEBUG}" ]; then # set DEBUG1, DEBUG2, ... [[[
  i= c=
  while true; do
    i=$(($i +1))
    [ $i -le 10 -a $i -le ${FNRDEBUG} ] && c=" $c DEBUG$i=1" || break
  done
  eval "$c"
  unset c i
fi
#]]]
trap_handler() # [[[
{
  trap - HUP INT QUIT TERM ABRT 0
  local tmpd ids id
  tmpd=$(readlink -f "${TMPD}")
  [ -d "${tmpd}" ] || exit
  if cd "${tmpd}"; then
    # kill process / process group pids; guard system processes such as init (1).
    for id in $(grep --include .pidof_\* -hr .); do
      case ${id} in 0|1) ;; *) ids="${ids} ${id}" ;; esac
    done
    [ -n "${ids}" ] && /bin/kill -TERM -- ${ids} 2>/dev/null # not the shell built-in!
    cd - >/dev/null
  fi
  rm -rf "${tmpd}"* ${DEBUG1:+/tmp/varSEARCH}
  exit
}
#]]]
trap trap_handler INT QUIT TERM HUP ABRT 0
TMPD=`mktemp -d -p "${TMPDIR:-/tmp}" ${0##*/}_XXXXXX` && chmod 700 "${TMPD}"
TMP0="${TMPD}/.FNRstart"; mkdir "${TMP0}" # built-in source resources
DATF="${TMP0}/.dat" # database of .desktop file entries
AWKB="${TMP0}/.build.awk" # builds (and queries) database
AWKQ="${TMP0}/.query.awk" # queries database (faster)
AWK4="${TMPD}/.saveflt.awk" # filters saved items
HSTF="${TMPD}/.hist-FNR.sh"; >"${HSTF}" # global command line history
FCSF="${TMPD}/.fcs"; >"${FCSF}" # triggers varSEARCH focus grabber
F2SF="${TMPD}/.f2s"; >"${F2SF}" # varCMD/varSEARCH focus cycler
F3SF="${TMPD}/.f3s"; >"${F3SF}" # built-in/plugin source list view cycler
F4SF="${TMPD}/search-output" # saved items
RWMF="${TMPD}/.msg_restart" # RESTART_WINDOW message
ETAP="${TMPD}/.eta"; >"${ETAP}" # tap invocation event
FNRRPC="${TMPD}/.rpc"; >"${FNRRPC}" # remote procedure call mailbox
YPID="${TMPD}/.yad.pid"; >"${YPID}" # yad --progress PID
SEP=`echo -e "\b"` # field separator of packed values
XDGHOMEICONS="${HOME}/.icons" # freedesktop.org standard user's icon location

# -----------------------------------------------------------------------------
#LANG=de_DE.UTF-8 # just for demo
LR=${LANG%.*} #ex:pt_BR
LL=${LANG%_*} #ex:pt

# -----------------------------------------------------------------------------
# Unexport these variables before running the selected tap-command.
UNEXPORT="unset GUI_ABOUT varSEARCH varFOCUSGRABBER varCMD varCOMMENT varOPEN varICONS varFOCUSSEARCH varF2 TEXTDOMAIN OUTPUT_CHARSET"
# unset varLIST and varF3 separately

# Prepare the configuration file. [[[1
# Defaults - match same variable/value pairs in the next block. [[[2
# Leave alone the next three configuration values for historical reasons.
defOPEN=false
export varICONS=false
export varFOCUSSEARCH=false

# Read / supplement $CONFIG. [[[2
[ -e "$CONFIG" ] || > "$CONFIG"; . "$CONFIG"
# Add missing/hidden defaults.
# Define and initialize ALL configuration values here.
gawk '
/^ICONCACHE=/{f1=1}
/^SEARCHCOMMENTS=/{f2=1}
/^SEARCHFROMLEFT=/{f3=1}
/^SEARCHREGEX=/{f4=1}
/^CASEDEPENDENT=/{f5=1}
# GEOMETRY=WxH+X+Y has no default, environment is overridden
# DESKTOP_FILE_DIRS initialized from system values, environment overrides
# ICON_DIRS initialized from system values, environment overrides
/^SEARCHCATEGORIES=/{f6=1}
/^SEARCHCOMPLETE=/{f7=1}
/^IBOL=/{f8=1}
/^HOTKEY_F2=/{f9=1}
/^HOTKEY_F3=/{f10=1}
/^HOTKEY_F12=/{f11=1}
/^SOURCES=/{f12=1}
/^SHOWNODISPLAY=/{f13=1}
/^SEARCHFILENAMES=/{f14=1}
/^HOTKEY_F4=/{f15=1}
END{ # Follow desired configuration file order
  # Hotkey format: accel-mods":"key-sym":"accel-key
  # cf. https://github.com/01micko/gtkdialog/blob/wiki/menuitem.md
  if(!f9) print "HOTKEY_F2=\"0:F2:0xffbf\"">>ARGV[1]
  if(!f10) print "HOTKEY_F3=\"0:F3:0xffc0\"">>ARGV[1]
  if(!f15) print "HOTKEY_F4=\"0:F4:0xffc1\"">>ARGV[1]
  if(!f11) print "HOTKEY_F12=\"0:F12:0xffc9\"">>ARGV[1]
  if(!f1) print "ICONCACHE=\"'"${XDGHOMEICONS}"'\"">>ARGV[1]
  if(!f14) print "SEARCHFILENAMES=false">>ARGV[1]
  if(!f2) print "SEARCHCOMMENTS=false">>ARGV[1]
  if(!f6) print "SEARCHCATEGORIES=false">>ARGV[1]
  if(!f7) print "SEARCHCOMPLETE=true">>ARGV[1]
  if(!f3) print "SEARCHFROMLEFT=false">>ARGV[1]
  if(!f4) print "SEARCHREGEX=false">>ARGV[1]
  if(!f5) print "CASEDEPENDENT=false">>ARGV[1]
  if(!f13) print "SHOWNODISPLAY=false">>ARGV[1]
  # IBOL+IBOL in search field ignores all characters to beginning of line.
  # 0xAD, dec(173), soft-hyphen, gtk-dialog invisible
  if(!f8) printf "IBOL=\"%c\" # reserved\n", 0xAD >>ARGV[1] # requires gawk
  # Built-in sources - space separated list
  if(!f12) print "SOURCES=\"FNRstart FNRsc\"">>ARGV[1]
}
' "${CONFIG}" && . "$CONFIG"

# Values that depend on defaults.
# Icons [[[2
# Note [ANCHOR_ICON_PATH]:
# For XDG/GTK to be able to find icons in a non-standard icon path,
# the path must end in '/icons' and XDG_DATA_DIRS must include the
# parent folder path. Gtkdialog window icons (attribute icon-name)
# must be copied to '/icons/hicolor/32x32/apps/'
# Findnrun copies a plugin $ICON in gawk function try_store_source().
XDG_DATA_DIRS="${XDG_DATA_DIRS}:${TMPD}" # for plugin ICONS
if [ -z "${ICONCACHE}" ]; then
  ICONCACHE="${XDGHOMEICONS}"
else
  XDG_DATA_DIRS="${XDG_DATA_DIRS}:${ICONCACHE}"
  ICONCACHE="${ICONCACHE}/icons"
fi
# Prefix to worked-around icons.
ICONSTEM="${ICONCACHE}/findnrun-" # NOT a dirpath
ICONSTEM2="${TMPD}/icons/" # slash-terminated, for plugin ICONS
ICONSTEM2A="${ICONSTEM2}/hicolor/scalable/apps/" # ditto
mkdir -p "${ICONCACHE}" "${ICONSTEM2}" "${ICONSTEM2A}"

# Parse command line --options. [[[1
while ! [ "${1#--}" = "$1" ]; do
  case "$1" in
    --geometry=*) # Override GEOMETRY set in $CONFIG, if any.
      o=${1#--geometry=}; GEOMETRY=${o%--geometry}
      ;;
    --perm=*|--perm) # Change tempdir permissions.
      o=${1#--perm=}; o=${o%--perm}; chmod "${o:-700}" "${TMPD}"
      ;;
    --) shift; CMDLINEOPTS="$@"; break ;; # pass CMDLINEOPTS to gtkdialog
    --stdout) ENABLESTDOUT=1 ;; # Don't redirect gtkdialog's stdout to null
    --*) echo "${0##*/}: invalid option $1" >&2; exit 1
    ;;
  esac
  shift
done

# Initialize more variables [[[1
# Some environment variables may affect operations:
# BROWSER, GEOMETRY, LANG, XCLIP, and more.

which gtkdialog4 >/dev/null 2>&1 && GTKDIALOG=gtkdialog4 || GTKDIALOG=gtkdialog

# Declare built-in sources. [[[1
# $BUILTIN_SOURCES get special treatment
# pipe-separated list
BUILTIN_SOURCES="FNRstart|FNRsc"

# Built-in save-filter-command. [[[2
FNRSAVEFLT="gawk -f \"$AWK4\""
# usage:
#   SAVEFLT_<source-id>="CUT\"=i,j,k,...\" RDR="redirection" \$FNRSAVEFLT \"${file}\""
# CUT and RDR explained at script AWK4 - see examples for FNRstart and Filmstrip source.

# save-filter-command setup for built-in sources. [[[2
# If xclip command exists then copy stdout to clipboard else to awk's stderr.
# User preference XCLIP can override/disable clipboard copying.
if [ -z "$XCLIP" ]; then
  XCLIP=$(which xclip 2>/dev/null)
  [ -e "$XCLIP" ] && XCLIP="|$XCLIP"
fi
if [ -z "$XCLIP" -o none = "$XCLIP" ]; then
  XCLIP=">/dev/stderr"
fi

# Default built-in source "Desktop Apps" [[[2
SOURCE_FNRstart="FNRstart::FNRstart:FNRstart:FNRstart:::FNRstart"
TAP_FNRstart="gawk -f \"${AWKQ}\" -v GREP=\"\${term}\" \"${DATF}\"${REDIRECT2}"
TITLE_FNRstart=$i18n_application_finder
ICON_FNRstart='/usr/share/doc/findnrun/findnrun.svg' # Filmstrip plugin expects.
# save item label, .desktop file fullpath, exec line, comments, categories
SAVEFLT_FNRstart="CUT=\"-4,1,3,4,5\" RDR=\"\$FNRXCLIP\" \$FNRSAVEFLT \"\${file}\""

# Built-in source "Shell Completion" [[[2
SOURCE_FNRsc="FNRsc::FNRsc:FNRsc:FNRsc:::FNRsc"
[ true = "${CASEDEPENDENT}" ] && unset i || i=i
x="${TMPD}/.FNRsc"; mkdir "$x" &&
# Embedded newline characters not allowed.
TAP_FNRsc="t='$x/.dat';"' if ! [ -s "$t" ]; then set +f; IFS=:; for i in $PATH; do { cd "$i" && ls -1 *"${term}"* ;} 2>/dev/null; done | findnrun-formatter -- -O su | tee "$t"; else grep -F'$i' "${term}" "$t"; fi;'
ICON_FNRsc='/usr/share/doc/findnrun/FNRsc.svg'
TITLE_FNRsc=$i18n_shell_completion
# save item label == exec line
SAVEFLT_FNRsc="CUT=\"-4\" RDR=\"\$FNRXCLIP\" \$FNRSAVEFLT \"\${file}\""

# Prepare sources. [[[1
# Don't change next two formats; functions generate_invoke*()
# assume them, and so may also findnrun-formatter in the future.
SRCSTEM="${TMPD}/.source-" # prefix to source filenames
SRCFMT='%d-%s.sh' # source filename format, i.e., printf "%s${SRCFMT}" "${SRCSTEM}" 0 FNRstart
store_valid_sources() # [[[
{
# In: $SOURCES
# Out: list of valid sources that got stored for gtkdialog sh's use.
# Return value:   <number of valid sources> <list of valid sources>
#   The list of valid sources is a subset of $SOURCES.
# Return code: [[[
# 1-99: fatal; 101-199: recoverable; 201-299: warning.
# If the offending subject is detected it is printed to stderr.
# Fatal errors => gawk exit(code).
# Recoverable errors => print code, disable source and continue.
# Warning => print code and continue.
# 101 source-id isn't a valid shell variable name (SOURCES=)
# 102 null tap-command
# 103 invalid tap-command sh syntax
# 104 invalid drain-command sh syntax
# 111 broken tap-id (leads nowhere)    TODO 111 and up
# 112 broken drain-id
# 113 broken title-id
# 114 broken icon-id
# 201 unreferenced TAP_ (linked by no one)
# 202 unreferenced DRAIN_
# 203 unreferenced TITLE_
# 204 unreferenced ICON_
# 205 unreferenced SOURCE_
# 206 unreferenced INITSEARCH_
# 207 unreferenced MODE_
# 208 unreferenced PLGDIR_
# 208 unreferenced SAVEFLT_
#]]]
# i18n Source plugin validation.
  set | gawk -v SOURCES="${SOURCES}" -v BUILTIN="${BUILTIN_SOURCES}" \
    -v MSG1="${i18n_fatal_source_error}\n" \
    -v MSG2="${i18n_recoverable_source_error}\n" \
    -v MSG3="${i18n_source_warning}\n" \
'#!/usr/bin/gawk -f
BEGIN {
  TEXTDOMAIN="'"${TEXTDOMAIN}"'"
  if('${DEBUG1:-0}') print "\n=== PROCESS SOURCE DECLARATIONS (set FNRDEBUG= 3-5 for more)" > "/dev/stderr"
  # Invalid SOURCES syntax? 101 [[[
  nE = split(SOURCES, E)
  for(j=1; j <= nE; j++) {
    s = E[j] # source-id
    if(match(s, /[^[:alnum:]_ ]|^[[:digit:]]/))
      recoverable(101, s) # exclude invalid source-id
    else
      # include syntactically valid source-id
      valid_sources = valid_sources" "s
  }
  valid_sources = substr(valid_sources, 2)
  # Fall back to BUILTIN source.
  if("" == valid_sources) valid_sources = "FNRstart"
  #]]]
  # valid type names
  ordered_G = "SOURCE|TAP|DRAIN|ICON|TITLE|INITSEARCH|MODE|PLGDIR|SAVEFLT"
  nG = split(ordered_G, G, /\|/)
  pattern = "^("ordered_G")_([^=]+)=(.*)" # => yields a[1] a[2] a[3]
}
{
  if(match($0, pattern, a)) {
    # Parse declarations [[[
    # In .findnrnrc we have: (LHS) <type>'_'<name>'='<value> (RHS)
    # and in particular:    'SOURCE_'<source-name>'='<name>[':'<name-ref>|<value>...]
    # G[] array of possible LHS types a[1] (aka groups), index numeric
    # I[] array of parsed LHS type-names a[2] (aka ids), index type-name
    # S[] array of parsed LHS source-names a[2] (aka sources), I[] contains S[], index source-name
    # VGI[] array of parsed RHS values a[3], indices type,name a[1],a[2]
    I[a[2]] = a[2]
    if("SOURCE" == a[1]) S[a[2]] = a[2]

    # Notes about quote handling: [[[
    # Stdin comes from "set |" and consists of lines in the form
    # <shell identifier>=[<sq>]<word>[<word>...][<sq>]
    # <sq> is a single quote in ash, dash and bash. However, ash and bash omit
    # <sq> if there is only one <word>.
    # For all literal groups, i.e, TITLE, MODE, etc.  we trim <sq>, see
    # {UNQUOTE} below.
    # However, for code group (DRAIN, TAP) we never trim or change the data.

    # For code groups, if <word> contains a single quote character \x27, i.e.
    # "A\x27B", `set` could output either \x27A\x27\\\x27\x27B\x27 or
    # \x27A\x27"\x27"\x27B\x27, depending on the shell.  A plugin that aims to
    # be compatible with the three shells should avoid using single quotes and
    # use exterior double quotes and escaped interior double quotes if
    # necessary.
    #
    # Attention plugin authors: Older shell versions output single quotes
    # differently, so the new shell versions might have broken compatibility
    # with older findnrun plugins, which should be updated to use (possibly
    # escaped interior) double quotes instead of single quotes.
    # ]]]

    VGI[a[1], a[2]] = ("TAP" == a[1] || "DRAIN" == a[1]) ? a[3] : unquote(a[3])
    if('${DEBUG5:-0}') print a[1],"**",a[2],"**",a[3] > "/dev/stderr"
    #]]]
  }
}
END {
  if('${DEBUG3:-0}') {
    print  "# Builtin sources:", BUILTIN >"/dev/stderr"
    print  "# Active sources :", SOURCES >"/dev/stderr"
    printf "# All LHS Ids    :" >"/dev/stderr"; for(i in I) printf(" %s", i) >"/dev/stderr"
    print "" >"/dev/stderr"
    printf "# All LHS Sources:" >"/dev/stderr"; for(s in S) printf(" %s", s) >"/dev/stderr"
    print "" >"/dev/stderr"
  }
  if('${DEBUG4:-0}') {
    for(i in I) {
      for(j=1; j<=nG; j++) {
        g = G[j]
        if((g, i) in VGI)
          printf("# <%s, %s> = %s\n", g, i, VGI[g, i]) >"/dev/stderr"
      }
    }
  }
  # TS[] array of tap-ids, index source-id
  # DS[] array of drain-ids, index source-id
  for(s in S) {
    split(VGI["SOURCE",s], a, /:/)
    # TODO next three lines, index a[] by symbol instead of position
    if(a[1]) TS[s] = a[1]
    if(a[2]) DS[s] = a[2]
    # Get title translation, if any in TEXTDOMAIN "findnrun-plugin-"<source-name>
    if(a[4]) VGI["TITLE", a[4]] = dcgettext(VGI["TITLE", a[4]], "findnrun-plugin-"s)

    if('${DEBUG5:-0}')
      printf "source %s: TAP %s DRAIN %s\n", s, \
        (s in TS) ?TS[s] :"NULL", (s in DS) ?DS[s] :"NULL" >"/dev/stderr"
  }
  # Null tap-command? 102 [[[
  for(s in S) if(! s in TS || ! (("TAP", TS[s]) in VGI) || ! VGI["TAP", TS[s]]) {
    recoverable(102, s)
    delete S[s]
  }
  #]]]
  # Invalid tap-command sh syntax? 103 [[[
  syntax_check("TAP", TS, S, 103) # deletes elements of S
  #]]]
  # Invalid drain-command sh syntax? 104 [[[
  syntax_check("DRAIN", DS, S, 104) # deletes elements of S
  #]]]
  # Store remaining valid sources for gtkdialog shell. [[[
  # Treat sources as an ordered list.
  nE = split(valid_sources, E)
  SH_FOR_ICONS="sh" # try_store_source() will pipe into it
  for(j=1; j <= nE; j++) {
    if(! (E[j] in S)) continue
    s = E[j] # source-id
    ret = try_store_source(s, S, Store)
    # Cumulate binned sources: enabled, disabled, hidden.
    bin[ret] = bin[ret]" "s
  }
  close(SH_FOR_ICONS) # actually executes piped commands.
  #]]]
  # Output binned sources. [[[
  for(x = 0; x <= 2; x++) {
    bin[x] = substr(bin[x], 2)
    nbin[x] = split(bin[x], source)
    # Output enabled (visible/hidden) sources only.
    if(x != 0x1) {
      for(j = 1; j <= nbin[x]; j++) {
        buf = Store[source[j]]
        p = index(buf, "\n")
        saveas = substr(buf, 1, p-1)
        # While saving source to disk add NSOURCES= number of visible sources
        printf("%s\nNSOURCES='"'%d'"'\n", substr(buf, p+1), nbin[0]) > saveas
        close(saveas)
      }
    }
  }
  #]]]
  # Return binned sources and their counts.
  for(j = 0; j <= 2; j++) printf("%d:%s:", nbin[j], bin[j])
  printf "\n"
}
function syntax_check(group, A, I, err_code,   shell_code, i, checker, status) { # [[[
  # Checks all items of A. Deletes items of I.

  # "sh -n" is quite a limited syntax checker but it is built-in.
  # https://www.shellcheck.net/ is much better.
  checker = "sh -n"

  if('${DEBUG4:-0}') { #[[[
    print "\nsyntax_check",group,":" >"/dev/stderr"
    for(i in A) print "source",i, \
      (i in I) ?"to check" :"is already invalid", \
      "(declares", group, A[i]")" >"/dev/stderr"
  } #]]]
  # For each declared source-id as i that is associated with a non-null group-id...
  #   If source-id i is still in the set of valid ids (I)
  #   && its group-id A[i] has an associated value in VGI...
  for(i in A) if(i in I && ((group, A[i]) in VGI)) {
    if(match(BUILTIN, i)) continue # optimize: skip built-in sources
    if(!match(SOURCES, i)) continue # optimize: skip inactive sources
    # Note: if A`s tap references B`s tap and B is inactive, A`s tap [[[
    # won`t be validated/available. The work-around is to declare B
    # as an active source, but hide it with MODE HIDDEN. ]]]
    shell_code = VGI[group, A[i]]
    if('${DEBUG4:-0}') #[[[
      print "now checking",i"'"'"'s", group, A[i],"=>",shell_code >"/dev/stderr"
    #]]]
    # The shell_code we get is formatted as a (quoted) shell string because it
    # comes from "set |". So we echo it to turn it into plain text for the
    # checker to validate.
    if(system("echo " shell_code " | " checker)) {
      # non-zero exit code means invalid syntax, disable this source
      recoverable(err_code, group": "shell_code, i)
      delete I[i]
    }
  }
}
#]]]
function try_store_source(s, S, Store, buf, A,   a, g, i, j, I, iconpath, cachedpath) { #[[[
  # try_store_source stores BY REFERENCE in map Store the current
  # source s unless the source "disabled" bit is on.
  # Upon storing s, try_store_source increments global static variable _global_try_store_source.
  # Call try_store_source for the ORDERED list of valid sources.
  # SH_FOR_ICONS is pre-defined as "sh"
  # In => Out: (all output values are wrapped in SINGLE quotes)
  #   s      source-id
  #   S      global array of valid source ids
  #   Store  return array of composed sources
  #   buf==""      => compose from globals G and VGI for s; OR
  #   buf=="array" => compose from A["SOURCE"]=source-id, A["TAP"]=tap-command, ...
  #   A    see buf="array"
  # Return value: 0:enabled-in-Store 1:disabled 2:hidden-in-Store

  # Assert: The calling outer loop has stripped exterior single quotes
  # except for shell code values (TAP, DRAIN). See {UNQUOTE} above.
  if("" == buf) {
    buf = sprintf("ID='"'%s'"'", s)
    split(s":"VGI["SOURCE",s], I, /:/)
    for(j = 1; j <= nG; j++) {
      g = G[j]; i = (j in I) ?I[j] :"N\x08NULL" # \x08 => never "(g,i) in VGI"
      if('${DEBUG3:-0}') printf ("<%s, %s> ", g, i) > "/dev/stderr"
      if((g, i) in VGI) {
        if("TAP" == g || "DRAIN" == g) {
          # store verbatim value, see {UNQUOTE}
          buf = buf sprintf("\n%s=%s", g, VGI[g, i])
        } else {
          buf = buf sprintf("\n%s='"'%s'"'", g, VGI[g, i])
        }
      } else { # the plugin does not declare a value for group id g
        if("TITLE" == g) {
          # on no title-id output source-id as title value
          buf = buf sprintf("\n%s='"'%s'"'", g, s)
        } else if("MODE" == g) {
          # on no mode-id output MODE="0" for bit mask
          buf = buf sprintf("\n%s='"'%d'"'", g, 0)
        } else {
          # on no "other" id output null value, i.e., VAR="" << IMPORTANT
          buf = buf sprintf("\n%s='"''"'", g)
        }
      }
    }
  } else if("array" == buf) { # NOT USED
    buf = sprintf("ID='"'%s'"'", s)
    for(a in A)
      buf = buf sprintf("\n%s='"'%s'"'", a, A[a])
  }
  # Get MODE: [1] bit mask
  match(buf, /MODE=\x27([^\x27]+)\x27/, a); mode = a[1]
  # Do not store the current source if MODE "disabled" bit (0x1) is set
  if(and(mode, 0x1)) {
    if('${DEBUG3:-0}') print "\n!!! Plugin mode DISABLED [[\n"buf"\n]]" > "/dev/stderr"
    return 1 # DISABLED source
  }
  # Else continue to store the current source
  Store[s] = sprintf("%s\n%s", # filepath[.hide] + contents
    format_saveas(_global_try_store_source++, s, mode), buf)
  # Copy plugin icon, ref. note [ANCHOR_ICON_PATH] [[[
  # Get ICON: [1] fullpath, [2] dirname, [3] basename.
  match(buf, /ICON=\x27(([^\x27]*\/)([^\x27]+))\x27/, iconpath)
  if("" != iconpath[3]) {
    # Cache the icon. Read why at [ANCHOR_ICON_PATH].
    cachedpath = "'"${ICONSTEM2}"'" iconpath[3]
    # For gtkdialog tree widget column icon.
    printf("ln -sf \x27%s\x27 \x27%s\x27\n", \
      iconpath[1], cachedpath) | SH_FOR_ICONS
    # For gtkdialog window widget (attribute icon-name).
    cachedpath = "'"${ICONSTEM2A}"'" iconpath[3]
    printf("ln -sf \x27%s\x27 \x27%s\x27\n", \
      iconpath[1], cachedpath) | SH_FOR_ICONS
  }
  #]]]

  if(and(mode, 0x2)) {
    if('${DEBUG3:-0}') print "\n!!! Plugin mode HIDDEN [[\n"buf"\n]]" > "/dev/stderr"
    return 2 # HIDDEN source
  } else {
    if('${DEBUG3:-0}') print "\nPlugin is enabled [[\n"buf"\n]]" > "/dev/stderr"
    return 0
  }
}
#]]]
function format_saveas(num, name, mode,   saveas) { #[[[
  # Return source fullpath according to source number, name and mode bit mask
  saveas = sprintf("%s'"${SRCFMT}"'%s", "'"${SRCSTEM}"'", 0+num, name,
    and(mode, 0x2) ? ".hide" : "")
  return saveas
}#]]]
function fatal(code, subject, source) { #[[[
  printf(MSG1, code, \
    (source ?" "source":" :"") (subject ?" "subject :"")) > "/dev/stderr"
  exit(code)
}#]]]
function recoverable(code, subject, source) { #[[[
  printf(MSG2, code, \
    (source ?" "source":" :"") (subject ?" "subject :"")) > "/dev/stderr"
}#]]]
function warning(code, subject, source) { #[[[
  printf(MSG3, code, \
    (source ?" "source":" :"") (subject ?" "subject :"")) > "/dev/stderr"
}#]]]
function unquote(s,   p,t,l) { # [[[
# Trim exterior, paired single quotes. See {UNQUOTE} above.
  if((p = index("\x27", t = substr(s,1,1))) && p < 2) {
    if(t == substr(s, l = length(s)))
      return(substr(s, 2, l-2))
  }
  return(s)
}#]]]
  '
}
#]]]
list_diff() # $1-list1 $2-list2 [[[
# In: space-separated lists of words. Out: $1 - $2
{
  local e1 l1 l2 res
  l1=$1; l2=" $2 "
  for e1 in $l1; do : $e1; [ "${l2##* $e1 }" = "$l2" ] && res="$res $e1"; done
  printf %s "${res#?}"
}
#]]]

store_valid_sources > "${TMPD}/.$$"
x=$?; [ 0 -lt $? -a $? -lt 100 ] && exit $x # fatal errors
# Get lists of valid sources.
ifs=$IFS; IFS=:
read NSOURCES VISIBLE_SOURCES x DISABLED_SOURCES x HIDDEN_SOURCES x < "${TMPD}/.$$"
IFS=$ifs
# Initialize gtkdialog source defaults from the first source.
. "${SRCSTEM}0-"*.sh

# Display warning dialog if some source declarations were found invalid. [[[
invalid=$(list_diff "$SOURCES" "$VISIBLE_SOURCES $DISABLED_SOURCES $HIDDEN_SOURCES")
if [ -n "$invalid" ]; then
  yad --title "${APP_NAME}" --window-icon=edit-find --center --on-top \
    --image gtk-dialog-warning --buttons-layout=center --margins=4 \
    --button=gtk-ok \
    --text "$(printf \
      "$i18n_Invalid_source_plugins" \
      "$invalid")"
fi
#]]]
# Prepare the database builder script. [[[1
# Usage: gawk -f "${AWKB}" [-v GREP="string"] [-v ALL_ICONS=true] [-v SHOWNODISPLAY=true|false] [-v LIST_FILE="list_file" | files ]
# Specify input files (.desktop) either as lines of LIST_FILE or as awk arguments.
# The former style allows for handling read file errors gracefully and is used throughout this script.
[ -x /bin/dash ] && SH=/bin/dash || SH=/bin/ash
> "${AWKB}" echo '#!/usr/bin/gawk -f
BEGIN { # [[[2
  if('${DEBUG1:-0}') print "\n=== DB BUILDER (set FNRDEBUG= 2 for more)\n[[ ALL_ICONS="ALL_ICONS >"/dev/stderr"
  RS="^~cannot~match~me~" # enable slurp read mode.
  # Choose a shell for ongoing command execution - see icon_wa_match_and_link().
  sh = "'${SH}'" # not used as a co-process
  ICONSTEM = "'"${ICONSTEM}"'" # prefix to worked-around icons.
  if('${DEBUG2:-0}') print "ICONSTEM="ICONSTEM >"/dev/stderr"
  if(""==SHOWNODISPLAY) SHOWNODISPLAY = "false"
  MSG1="'"$i18n_SHOWNODISPLAY_false_excludes"'"
  MSG2="'"$i18n_filename_Icon_not_found"'"
  MSG3="'"$i18n_analyzing_applications"'"
  # Read files specified by list of file names.
  if(LIST_FILE) {
    NFILES = read_list_file(LIST_FILE, file)
    exit # go to END - skip main loop
  }
}
# main loop # [[[2
# Read .desktop files specified by command line arguments.
# Use when you are certain that all files are readable.
{
  # Slurp NFILES .desktop files.
  file[++NFILES]=";"FILENAME"\n"$0
}
END { # [[[2
  # Is the icon work-around enabled and are its data up-to-date?
  if(ICON_WA_READY = is_icon_wa_uptodate()) {
    # Speed up icon search by reading the icon index file.
    read_icon_index() # creates ICONINDEX map
  }
  # Decode .desktop files.
  if('${DEBUG1:-0}') print "awk decoding",NFILES,"files..." > "/dev/stderr"
  if(!ICON_WA_READY && "true"==ALL_ICONS && NFILES > 10) {
    # show a progress gauge while re-building a big (>10) icon cache
    PROGRESS_PID = show_progress_dialog(sprintf(MSG3, NFILES))
  }
  if(ALL_ICONS == "true") {
    icon_wa_init()
  }
  for(i=1; i<=NFILES; i++) {
    fil=file[i]
    name=exec=icnpath=icnname=icnext=comment=category=""
    match(fil, /^;([^\n]+)/, m); filename=m[1]
    match(fil, /\nName=([^\n]+)/, m); name=m[1]
    if(match(fil, /\nName\[('"${LR:-@}|${LL:-@}"')\]=([^\n]+)/, m)) name=m[2]
    if(!name) continue # trap bogus .desktop files
    if(SHOWNODISPLAY != "true" && index(fil, "NoDisplay=true")) {
      printf(MSG1"\n", filename) > "/dev/stderr"
      continue
    }
    match(fil, /\nExec=([^\n]+)/, m); exec=m[1]
    if(!exec) continue # trap bogus .desktop files
    # Delete freedesktop.org %F parameter since Exec= value is going to sh.
    sub(/[ \t]*%[a-zA-Z][ \t]*$/, "", exec)
    match(fil, /\nIcon=([^\n]*\/)?([^\n.]+)([.][^\n]*)?/, m)
    icnpath=m[1]; icnname=m[2]; icnext=substr(m[3],2)
    if(index(icnext, ".")) {
      # case "filename.any[.any ...].EXT"
      nic = split(icnext, ic, /\./)
      icnname = icnname "." substr(icnext, 1, length(icnext)-length(ic[nic])-1)
      icnext = ic[nic]
    }
    if(icnext && ! match(icnext, /png|svg|xpm/)) {
      # case "file.name" implicit icon EXT
      icnname = icnname "." icnext
      icnext = ""
    }
    match(fil, /\nComment=([^\n]+)/, m); comment=m[1]
    if(match(fil, /\nComment\[('"${LR:-@}|${LL:-@}"')\]=([^\n]+)/, m)) comment=m[2]
    match(fil, /\nCategories=([^\n]+)/, m); category=";"m[1]
    # narrow matches by GREP pattern and store for sorting step
    if(GREP && index(tolower(name), GREP) || !GREP) {
      if(key[k = tolower(name)]) { # case-independent sort; assert name != ""
        # Handle key clash by appending "%<" to the key N times on N-th clash.
        while(key[k = k"%<"]);
      }
      key[k] = k
      if(ALL_ICONS == "true") {
        delete Ret
        icon_wa_push_icnname(i, Ret)
        icnpath = Ret["icnpath"]
        icnname = Ret["icnname"]
      }
      out[k] = format_item()
    }
  }
  if(ALL_ICONS == "true" && ! ICON_WA_READY) {
    if(icon_wa_find_icon_files()) {
      icon_wa_match_and_link(sh)
      icon_wa_bind(out)
    }
  }
  # Sort by name and print.
  nkey = asort(key) # Note: asort is a GNU awk (gawk) extension.
  for(i=1; i<=nkey; i++) {
    print substr(out[key[i]], 1, 510)
    # 510 works around gtkdialog tree widget buffer overflow limit
  }
  # Remember that we performed a complete icon work-around.
  mark_icon_wa_is_uptodate()
  # Close shell.
  print "exit" | sh
  close(sh)
  close_progress_dialog(PROGRESS_PID)
  if('${DEBUG1:-0}') print "]] BUILDER" >"/dev/stderr"
}

function format_item(   ic,cols) { # [[[2
# Format tree widget item - columns: icon, name, all-packed-values.
# Note: assert data does not include characters "|" and $SEP.
  ic = format_icon_cell()
  # The second column, which is always empty, is reserved for future expansion.
  # The tree widget does display its value.
  cols = sprintf("%s||%s|%s", \
    ic, name, \
    sprintf("%s'${SEP}'%s'${SEP}'%s'${SEP}'%s'${SEP}'%s", \
    filename,name,exec,comment,category))
    # tree widget exports all packed values as a single column
  return(cols)
}

function format_icon_cell( ) { # [[[2
# Format tree row icon cell.
# Use within <input icon-column="0"> or <stock-column="0">
# Note: tree widget does not support icons with paths anyway.
  return(icnpath ? icnpath icnname "." icnext : icnname)
}

function icon_wa_push_icnname(i,byRef,   IFP,a) { # [[[2
# Push caller`s data that icon_wa_find_icon_files and icon_wa_bind will use.
# Return icnpath and icnname (resolved if possible otherwise as a slot to be bound subsequently).

  # IFP is the icon full filepath, which is used as the ICONINDEX key.
  WA[++nWA,"IFP"] = IFP = icnpath icnname (icnext ?".":"") icnext
  WA[nWA,"desktop"] = filename
  WA[nWA,"icnname"] = icnname

  # Return icnname either resolved or as a slot to be bound subsequently:
  # Gtkdialog is very finicky: the icon name passed to the tree widget must
  # not include dot+extension but the link file name must include the
  # extension!
  # We prefix all names with "findnrun-" to reflect that they are located in
  # the cache directory ICONSTEM->.
  if(ICON_WA_READY) { # see icon_wa_match_and_link for comments
    split(ICONINDEX[IFP], a, "\x00")
    byRef["icnname"] = "findnrun-"a[3] # file name w/o ext
  } else {
    # "<id=...>" is a slot for icon_wa_bind to fill.
    byRef["icnname"] = "findnrun-<id="nWA">" # file name w/o ext
  }

  # Return icnpath:
  byRef["icnpath"] = "" # this tells format_item() to use icnname only
}

function icon_wa_init() { # [[[2
  WA_ICON_POOL = ""
  delete WA
  nWA = 0
}

function icon_wa_find_icon_files(   c,icon_dirs) { # [[[2
  icon_dirs = "'"$ICON_DIRS"'"
  gsub(/:/, "\" \"", icon_dirs)
  icon_dirs = "\"" icon_dirs "\""
  c = "2>/dev/null find -L " icon_dirs \
    " -type f -regex \".*\\.\\(png\\|svg\\|xpm\\)\" -print"
  c | getline WA_ICON_POOL
  return(""!=WA_ICON_POOL)
}

function icon_wa_match_and_link(sh,    i,IFP,ifp,icnname,c,x,a,lnk,ext) { # [[[2
# Workaround for gtkdialog tree widget failing to display icons with path.
  WA_ICON_POOL = "\n" WA_ICON_POOL "\n"
  for(i = 1; i <= nWA; i++) {
    # IFP is the icon full filepath, which is used as the ICONINDEX key.
    IFP = WA[i,"IFP"]
    if("" == IFP) continue # defensive
    # If the work-around data needs updating then create icon links, etc.
    # Refresh the icon filepath. [[[3
    ifp = IFP
    if(-1 != getline < ifp) {
      close(ifp) # icon file ifp exists
    } else { # file ifp does not exist
      # Find the full icon pathname from the icon name only, if any.
      # cf. http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#icon_lookup
      icnname = WA[i,"icnname"]
      if(match(WA_ICON_POOL, "\\n[^\\n]+/"icnname"\\.(png|svg|xpm)\\n")) {
        ifp = substr(WA_ICON_POOL, RSTART + 1, RLENGTH -2)
      } else {
        ifp = ""
      }
    }
    if("" == ifp) { # no icon resource can be found.
      printf MSG2"\n", WA[i,"desktop"], WA[i,"IFP"]  >"/dev/stderr"
      continue
    }

    # Symlink the target ifp. [[[3
    # Note that link file creation may be postponed until END performs close(sh).
    # The link name is located in the icon cache directory ICONSTEM->.
    # Gtkdialog is very finicky: the icon name passed to the tree widget
    # must not include dot+extension but the link file name must include the
    # extension!
    x = split(ifp, a, /\//)
    lnk = a[x] # link name lnk <- icon-name[.ext] (no path)
    ext = (x = split(lnk, a, /\./)) ?"."a[x] :"" # ".ext" if any
    if(ext) x-- # forget ext from split link name
    # implode link name while replacing all dots with underscores
    lnk = ""; for(c=1; c<=x; c++) lnk = lnk "_" a[c]; lnk = substr(lnk, 2)
    print "2>/dev/null ln -s \""ifp"\" \"" ICONSTEM lnk ext "\"" | sh

    # Store values for icon_wa_bind to bind. [[[3
    WA[i,"bind"] = lnk # link name for "findnrun-"lnk

    # Progressively build the icon index file. [[[3
    printf "%s\x00%s\x00%s\x00%s\n", IFP, ifp, lnk, ext >> (ICONSTEM".index")
  }
}

function icon_wa_bind(byRef,   k,M) { # [[[2
  # Bind all slots "<id="nWA">".
  for(k in byRef) {
    if(match(byRef[k], /<id=([[:digit:]]+)>/, M)) {
      byRef[k] = substr(byRef[k], 1, RSTART -1) WA[M[1],"bind"] substr(byRef[k], RSTART + RLENGTH)
    }
  }
}

function is_icon_wa_uptodate(   uptodate,x) { # [[[2
# Is the icon work-around requested and are its data up-to-date?
  if(uptodate = ("true" == ALL_ICONS)) {
    # test for file existence
    if(uptodate = (-1 != (getline < (x = ICONSTEM".uptodate"))))
      close(x)
  }
  if('${DEBUG2:-0}') print "is_icon_wa_uptodate="uptodate > "/dev/stderr"
  return(uptodate)
}

function read_icon_index(   a,b,data,i,x) { # [[[2
# Read the index file located in the icon work-around data directory.
  if('${DEBUG2:-0}') print "in read_icon_index" >"/dev/stderr"
  if(1 == (getline data < (x = ICONSTEM".index"))) {
    close(x)
    na = split(data, a, /\n/) # we slurped the index file
    for(i=1; i<=na; i++) {
      split(a[i], b, "\x00") # index record fields: IFP ifp lnk ext
      ICONINDEX[b[1]] = a[i]
    }
  }
}

function mark_icon_wa_is_uptodate( ) { # [[[2
# If the icon work-around is enabled, mark whether its date are up-to-date.
  if("true" == ALL_ICONS) {
    print "" > (ICONSTEM".uptodate")
    if('${DEBUG3:-0}') print "\nmark_icon_wa_is_uptodate() created", ICONSTEM".uptodate" > "/dev/stderr"
  }
}

function read_list_file(list_file, acontent,   name,nname,nfiles,f,i,s) { # [[[2
# File names from list_file; slurp file contents into acontent; return nfiles.
  if(0 < (getline s < list_file)) {
    close(list_file)
    if(nname = split(s, name, /\n/)) {
      # Slurp nfiles .desktop files.
      for(i=1; i<nname; i++) {
        f = name[i]
        if(0 < (getline s < f)) {
          close(f)
          file[++nfiles]=";"f"\n"s
        } else {
          printf(MSG2"\n", f, "") >"/dev/stderr"
        }
      }
    }
  }
  return(0+nfiles)
}

function show_progress_dialog(message,   pid,c,p,s,A,px,py,w,h,g,cmd) { # [[[2
  # Center the dialog over the main window.
  c="xwininfo -name \"'"${APP_TITLE_UNIQ:-$APP_TITLE}"'\""
  c | getline s # slurp wininfo output
  close(c)
  if('${DEBUG2:-0}') print "XWININFO("c")[[\n"s"\n]] XWININFO" >"/dev/stderr"
  ## Make pattern
  p =   "Absolute upper-left X:[[:space:]]+(-?[[:digit:]]+)" ".*"
  p = p "Absolute upper-left Y:[[:space:]]+(-?[[:digit:]]+)" ".*"
  p = p "Relative upper-left X:[[:space:]]+(-?[[:digit:]]+)" ".*"
  p = p "Relative upper-left Y:[[:space:]]+(-?[[:digit:]]+)" ".*"
  p = p "Width:[[:space:]]+([[:digit:]]+)" ".*"
  p = p "Height:[[:space:]]+([[:digit:]]+)"
  if(!match(s, p, A))
    { return("") }
  ## Calculate new size and position.
  px = A[1] - A[3] + 1; py = A[2] - A[4]; w = A[5] - 1; h = A[6]
  if('${DEBUG2:-0}') printf "px(%d) py(%d) w(%d) h(%d)\n", px,py,w,h >"/dev/stderr"
  py = h / 2 + py; h = 0 # uncontrained height
  if(px < 0) { w += px; px = 0 }
  if(py < 0) { py = 0 }
  if('${DEBUG2:-0}') printf "px(%d) py(%d) w(%d) h(%d)\n", px,py,w,h >"/dev/stderr"
  ### Display progress dialog.
  cmd = " --progress-text=\""message"\" --progress --pulsate --splash --no-buttons < /dev/null & echo $! >\"'"${YPID}"'\""
  g = sprintf("--geometry %dx%d+%d+%d --width=%d --height=%d", w, h, px, py, w, h)
  if('${DEBUG2:-0}') print "geometry("g")" >"/dev/stderr"
  if(system("yad "g cmd))
      { return("") }
  getline pid < "'"${YPID}"'" # running yads PID
  close("'"${YPID}"'")
  if('${DEBUG2:-0}') print message" PROGRESS_PID="pid >"/dev/stderr"
  return(pid)
}

function close_progress_dialog(pid,   killer) { # [[[2
  if(PROGRESS_PID) { # close the progress gauge
    killer = "/bin/kill "PROGRESS_PID
    print "" | killer
    close(killer)
  }
}

' # [[[2]]] awk script end

# Prepare the database query script. [[[1
# Usage: gawk -f "${AWKQ}" [-v GREP="string"] ${DATF}
unset case subject commleft left categ REDIRECT2 func statements
[ true = "${CASEDEPENDENT}" ] || case=tolower
if ! [ true = "${SEARCHCOMPLETE}" ]; then
  # Split out the last field of the record that $AWK's format_item() printed.
  statements="
  split(\$(NF), a, /${SEP}/) # a <- {fullpathname,name,exec,comment,category}"
  if [ true = "$SEARCHFILENAMES" ]; then
    statements="$statements
    gsub(/^.*[/]|[.][^.]+$/, \"\", a[1]) # basename(fullpathname, .ext)"
  fi
  subject="${case}(a[2])"
else
  # Keep basename(fullpathname, .ext) & sub-fields 'name'..'category'.
  statements="
  a=\$(NF); x=index(a,\"${SEP}\"); a1=substr(a,1,x-1)
  gsub(/^.*[/]|[.][^.]+$/, \"\", a1); a=a1 substr(a,x)"
  subject="${case}(a)" # ="${case}(basename(a[1], .ext)\";\"a[2]\";\"a[3]\";\"a[4]\";\"a[5])"
fi
if [ true = "${SEARCHREGEX}" ]; then
  REDIRECT2=" 2>/dev/null" # Quiet awk's "fatal: invalid regex" message
  func=match
  [ true = "${SEARCHFROMLEFT}" ] && left="&& RSTART==1"
else
  func=index
  [ true = "${SEARCHFROMLEFT}" ] && left="==1"
fi
subject="${func}(${subject},GREP)"
if ! [ true = "${SEARCHCOMPLETE}" ]; then
  [ true = "${SEARCHFILENAMES}" ] && fnameleft="|| ${func}(${case}(a[1]),GREP)${left}"
  [ true = "${SEARCHCOMMENTS}" ] && commleft="|| ${func}(${case}(a[4]),GREP)${left}"
  case $SEARCHCATEGORIES in true|hidden) categ="|| ${func}(${case}(a[5]),GREP)";; esac
fi
> "${AWKQ}" echo '#!/usr/bin/gawk -f
BEGIN {
  if('${DEBUG3:-0}') {
    print "\n=== DB QUERY [[ GREP=\""GREP"\"" >"/dev/stderr"
    print "expression=('"${subject}${left} ${fnameleft} ${commleft} ${categ}"')" >"/dev/stderr"
  }
  FS="|"
}
{'"${statements}"'
  # narrow matches by GREP pattern
  if(GREP && ('"${subject}${left} ${fnameleft} ${commleft} ${categ}"') || !GREP) {
    print substr($0, 1, 510)
    # 510 works around gtkdialog tree widget buffer overflow limit
  }
}
END {
  if('${DEBUG3:-0}') print "]] QUERY" >"/dev/stderr"
} '

# Build database of .desktop file entries. [[[1
LIST_FILE="${AWKB}.list-file"
ifs=$IFS; IFS=:; set -- $DESKTOP_FILE_DIRS
# Recursive search supports common wine games.
find -L "$@" -type f -name '*.desktop' > "$LIST_FILE" 2>/dev/null
IFS=$ifs
gawk -v ALL_ICONS=${varICONS} -v SHOWNODISPLAY=${SHOWNODISPLAY} -f "${AWKB}" -v LIST_FILE="$LIST_FILE" > "${DATF}"
if [ "${DEBUG1}" ]; then
  : >/tmp/varSEARCH
  cat >&2 <<EOF
==== DEBUG SEARCH INPUT ENABLED ====
You can "type a search" by writing a term to file /tmp/varSEARCH.
Limit $APP_NAME to a single instance because /tmp/varSEARCH can't be shared.
----
EOF
fi
NDATF=$(wc -l "${DATF}"); NDATF=${NDATF%% *}

# Prepare Help system and its GUI button (About). [[[1
# i18n About dialog.
find_help() #[[[
{
  # In:  ${FNRHELPINDEX} paths.
  # Out: ${helpviewer} command, ${helpbutton} gtk code.
  local IFS=: subject="${i18n_Help%%_*}${i18n_Help##*_}"
  set ${FNRHELPINDEX}
  for i; do
    unset x p s helpviewer helpbutton; hf="$i"
    case "${hf}" in *\[*\]*) p="${hf%[*}["; s="]${hf##*]}" # search for translation
      [ -e "$p${LL}$s" ] && hf="$p${LL}$s"; [ -e "$p${LR}$s" ] && hf="$p${LR}$s"; [ -e "$p${LANG}$s" ] && hf="$p${LANG}$s" ;;
    esac
    if [ -e "${hf}" ]; then
      hd="${TMPD}/help" && mkdir -p "${hd}" &&
      case "${hf##*.}" in t[gx]z|[gx]z) tar -C "${hd}" -xaf "${hf}" && hf=$(set +f; echo "${hd}"/index.*) ;; esac &&
      case "${hf##*.}" in md) x=${FNRMDVIEW:-mdview} ;; htm*) x=defaultbrowser;; esac
      read -t 1 helpviewer << EOF
$(which $x www-browser x-www-browser defaulttexteditor geany leafpad 2>&-)
EOF
      case ${helpviewer} in
        '') helpviewer="${BROWSER:-xdg-open} '${hf}'";; # catchall
        *mdview) helpviewer='v() { for a; do [ -e "$a" ] && cd "${a%/*}" && exec "'"$helpviewer"'" "${a%/*}" "${a##*/}" "'"$subject - $APP_TITLE"'" & sleep 0.4 || sleep 1; done ;}; v "'"$hf"'"' ;;
        *browser) helpviewer="'${helpviewer}' 'file://${hf}'" ;;
        *) # dup help files to protect sources from text editors
          if ! [ "${hf%/*}" = "${hd}" ]; then cp -fr "${hf%/*}/"* "${hd}/"; fi &&
          helpviewer="cd '${hd}' && '${helpviewer}' 'no-help.md'" ;;#${hf##*.}'";;
      esac &&
      helpbutton="
<button use-underline=\"true\">
  <label>$i18n_Help</label>
  <input file stock=\"gtk-help\"></input>
  <action>${helpviewer} &</action>
  <action>closewindow:GUI_ABOUT</action>
</button>"
    break # on first valid help file path $hf found
  fi
  done
}
#]]]
find_help

# i18n About dialog widgets: window text; number of apps text (singular/plural)
export GUI_ABOUT='
<window title="'"$i18n_About_and_help - ${APP_TITLE}"'" icon-name="edit-find" window-position="2">
  <vbox>
    <frame>
      <text justify="0" selectable="true" can-focus="false">
        <label>"'"$(printf "$i18n_gui_about" \
"${APP_NAME}" "${Version}" "step, SFR, L18L" \
"http://www.murga-linux.com/puppy/viewtopic.php?t=98330" \
"https://github.com/step-/find-n-run" "${CONFIG}")
$(n="${NDATF}"; printf "$(ngettext "$i18n_application_found" "$i18n_applications_found" "$n")" "$n")
$(n="${NSOURCES}"; printf "$(ngettext "$i18n_source_loaded" "$i18n_sources_loaded" "$n")" "$n"
)"'"</label>
      </text>
    </frame>
    <hbox homogeneous="true">
      <text space-fill="true" space-expand="true"><label>""</label></text>
      <button use-underline="true">
        <label>'"$i18n_OK"'</label>
        <input file stock="gtk-ok"></input>
        <action>closewindow:GUI_ABOUT</action>
      </button>
      '"${helpbutton}"'
      <text space-fill="true" space-expand="true"><label>""</label></text>
    </hbox>
  </vbox>
  <variable>GUI_ABOUT</variable>
  <action signal="key-press-event" condition="command_is_true([ $KEY_SYM = Escape ] && echo true )">closewindow:GUI_ABOUT</action>
</window>'

# Prepare the save filter script. [[[1
# Usage: gawk -f "$AWK4" "\${file}" with $CUT and $RDR environment variables
> "${AWK4}" echo '#!/usr/bin/gawk -f
# "CUT=i,j,k,..." selects which fields to print:
# Positive indexes select source-defined fields.
# Negative indexes select tree widget item fields in reverse order.
# Thus -1 is the icon, and -4 is the item label.
# "\t" (tab) is the output field separator.
#
# Typical redirections:
# RDR=">/dev/stderr" or RDR=">/path/to/output/file"
# RDR="|xclip" or RDR="|/path/to/executable-file"

BEGIN {
  FS = "|"
  CUT=ENVIRON["CUT"]
  RDR=ENVIRON["RDR"]
  n = split(CUT, f, /,/)
  if(match(RDR, /^[ \t]*[|]/)) {
    redirect = "piper"
  } else if(match(RDR, /^[ \t]*[>]/)) {
    redirect = "printer"
  } else {
    printf "'"$i18n_invalid_SAVEFLT_redirection"'", \
      ENVIRON["ID"], RDR > "/dev/stderr"
    exit(1)
  }
  RDR = substr(RDR, RSTART+RLENGTH)
}
{
  # positive indexes
  _ = split($(NF), a, /'"$SEP"'/)
  # negative indexes
  w = 0
  while(!(++w>=NF)) {
    a[-w] = $(w)
  }
  @redirect(f,a,n)
}
function piper(f,a,n,   i) { # [[[2
  while(!(++i>=n)) {
    printf "%s\t", a[f[i]] | RDR
  }
  if(n) {
    print a[f[n]] | RDR
  }
}
function printer(f,a,n,   i) { # [[[2
  while(!(++i>=n)) {
    printf "%s\t", a[f[i]] > RDR
  }
  if(n) {
    print a[f[n]] > RDR
  }
}
'

# Helpers [[[1

generate_get_drain_cmd() # [[[2
{
  # Echo sh code that extracts the drain command of the selected list item.
  # Command is returned in "$@" - Command defined per plugin-dev.md.

  # KLUDGE work around the peculiar Windows paths that winemenubuilder generates.
  echo -n 'case $varLIST in *wine\ *\\\\\\\\*)'
  echo -n ' varLIST=$(echo -n "$varLIST" | sed -e "s/\\\\\\\\/\\\\/g") ;;'
  echo -n 'esac'

  echo -n '; ifs="${IFS}"'
  echo -n '; IFS="'"${SEP}"'"; set -f; set -- $varLIST; set -- $3; set +f'
  echo -n '; IFS="${ifs}"'
}

generate_get_source() # [$1-event] [[[2
{
  # Echo sh code that loads the current source's field values.
  # Values are returned as shell variables - Values defined per plugin-dev.md.
  local eventfmt
  eventfmt="${1:+ on event:}$1${1:+ }"
  [ -n "${DEBUG2}" ] && echo -n 'eval ">&2 echo \"GET SOURCE \${varF3:-0}'"${eventfmt}"'\"";'
  [ -n "${DEBUG3}" ] && echo -n '>&2 echo "[["; set -x;'
  echo -n ". \"${SRCSTEM}\"\${varF3:-0}-*.sh"
  [ -n "${DEBUG3}" ] && echo -n '; set +x; echo >&2 "]]"'
}

generate_invoke_source_save_filter() # [[[2
{
  # Echo sh code that invokes source[varF3]'s save-filter-command.
  # Unexport all vars.
  echo -n "$UNEXPORT varLIST varF3 invokeTAP invokeDRAIN"
  echo -n "; if [ -n \"\$SAVEFLT\" ]; then"
  echo -n   " file=\"$F4SF\""
  echo -n   "; FNRDEBUG=$FNRDEBUG"
  echo -n   " FNRTMP=\"$TMPD\" FNRPID=$FNRPID FNRRPC=\"$FNRRPC\""
  echo -n   " FNRSAVEFLT=\"$FNRSAVEFLT\" FNRXCLIP=\"$XCLIP\"" # specifically for save-filter
  echo -n   " eval \$SAVEFLT"
  echo -n '; fi'
}

generate_invoke_source_tap() # [[[2
{
  # Echo sh code that invokes source[varF3]'s tap-command.
  # Extract search input value as $term with IBOL+IBOL correction. [[[
  echo -n 'ifs="${IFS}"; IFS="'"${IBOL}"'"; set -f; set -- ${varSEARCH}; for i; do term="$i"; done; set +f; set IFS="${ifs}";'
  #]]]
  # Get source[varF3]'s values. [[[
# Optimization: use cached <variable>TAP</variable>.
  generate_get_source invoke_source_tap
  # ]]]
  # Start tap-command. [[[
  echo -n ' && fnrevent=${invokeTAP:-Search} && fnrevent=${fnrevent%% *}' # trim timestamp
  if [ -n "${DEBUG1}" ]; then #[[[
    echo -n ' && >&2 printf %s "$(date +%H:%M:%S.%N) INVOKE TAP event=${fnrevent}"'
    [ -n "${DEBUG3}" ] && echo -n ' && eval ">&2 echo -n \" term(hex)=\$(echo -n \${term} |xxd -p)\""'
    echo -n ' && eval ">&2 echo \" term=\${term} \${TAP}\""'
  fi
  #]]]
  # Unexport all vars.
  # cf. UNEXPORT in generate_invoke_source_drain.
  echo -n " && ${UNEXPORT} varLIST varF3 invokeTAP invokeDRAIN"
  echo -n ' && FNREVENT="${fnrevent}" FNRDEBUG='"${FNRDEBUG}"
  echo -n ' FNRTMP="'"${TMPD}"'" FNRPID=${FNRPID} FNRRPC="'"${FNRRPC}"'"'
  echo -n ' eval ${TAP}'
  #]]]
}

generate_invoke_source_drain() # [[[2
{
  # Echo sh code that invokes source[varF3]'s drain-command.
  # Extract selected list item value as "$@".
  generate_get_drain_cmd
  # Unexport all vars but varF3 and varLIST.
  # cf. 'UNEXPORT' in generate_invoke_source_tap and 'unset' further down
  echo -n "; ${UNEXPORT}"
  # Load source[varF3]'s values into drain's invocation environment. [[[
  echo -n '; '; generate_get_source invoke_source_drain
  # ]]]
  # Save drain-command to history lists. [[[
  # To the source's own history list file and to the global history list file
  # $HSTF, which is the history widget input file (varCMD).
  echo -n "; echo \$DRAIN\ \"\$@\" >> \"$TMPD/.hist-\$ID.sh\""
  echo -n "; echo \$DRAIN \"\$@\" >> '$HSTF'"
  #]]]
  # Start drain-command. [[[
  echo -n '; set -- ${DRAIN} "$@"'
  # NOTE: v.2.0.0 main window's gtkdialog doesn't implement variable
  # invokeDRAIN. So, for the plugin interface, here it's sufficient to
  # pretend that invokeDRAIN is implemented, and handle it similarly to
  # what we do for invokeTAP, which instead is fully implemented.
  echo -n '; fnrevent=${invokeDRAIN:-Activate} && fnrevent=${fnrevent%% *}' # trim timestamp
  if [ -n "${DEBUG1}" ]; then #[[[
    echo -n ' && >&2 printf %s $(date +%H:%M:%S.%N)'
    echo -n ' && eval ">&2 echo \" INVOKE DRAIN event=${fnrevent} \${DRAIN} \$@ \""'
  fi
  #]]]
  echo -n '; unset varF3 varLIST invokeTAP invokeDRAIN TAP DRAIN ICON TITLE INITSEARCH MODE PLGDIR SAVEFLT'
  echo -n '; FNREVENT="${fnrevent}" FNRDEBUG='"${FNRDEBUG}"
  echo -n ' FNRTMP="'"${TMPD}"'" FNRPID=${FNRPID} FNRRPC="'"${FNRRPC}"'"'
  echo -n ' eval "$@" &'
  #]]]
}

generate_status_bar() # [[[2
{
  # Echo sh code that loads the current and next source's titles
  # and sets them as navigation hotkey labels.
  set -- ${VISIBLE_SOURCES:-FNRStart}
  [ $# -lt 2 ] && return # no status bar unless:
  # Multiple sources. [[[
  #i18n Status bar: Ctrl+0... and F3... hotkeys
  local ctrlnkey funckey keysymF3 tooltip
  keysymF3=${HOTKEY_F3#*:}; keysymF3=${keysymF3%:*}
  ctrlnkey=$i18n_ctrl_digit funckey=$i18n_boxed_string
  tooltip=$(printf \
    "$i18n_Click_the_status_bar" \
    "$(n="${NSOURCES}"; printf "$(ngettext "$i18n_source_loaded" "$i18n_sources_loaded" "$n")" "$n")" \
    "$(n="${NDATF}"; printf "$(ngettext "$i18n_application_found" "$i18n_applications_found" "$n")" "$n")" \
  )
  echo -n '
    <vbox>
      <eventbox>
        <statusbar has-resize-grip="false" sensitive="false" tooltip-text="'"$tooltip"'">
          <variable export="false">varSBAR</variable>
          <input>'
  [ -n "${DEBUG3}" ] && echo -n 'echo >&2 "=== GENERATE STATUS BAR [["; set -x;'
  echo -n 'title="${TITLE}"' # This is a gtkdialog sh variable and...
  # [[[ ... it gets its value from the <input> tag, which is activated only
  # after refresh:TITLE. Adding a <default> tag value for TITLE doesn't
  # initialize its **sh value**. It only initializes its **widget** value.
  # Again, here we deal with the gtkdialog sh value, so we need to be
  # prepared to the null value case, which manifests itself only once,
  # when gtkdialog starts. Here it goes.
  #]]]
  echo -n '; [ -z "${title}" ] && . "'"${SRCSTEM}"'"${varF3:-0}-*.sh && title="${TITLE}"'
  # Load next source's values
  echo -n '; . "'"${SRCSTEM}"'"$(((${varF3:-0} + 1) % ${NSOURCES:-'${NSOURCES}'}))-*.sh'
  echo -n '; next="${TITLE}"'
  # Send formatted data to SBAR.
  echo -n '; printf "'"%s ${ctrlnkey}  »  ${funckey} %s"'" "${title}" $((${varF3}+1)) "'"${keysymF3}"'" "${next}"'
  [ -n "${DEBUG3}" ] && echo -n 'set +x; echo >&2 "]]";'
  echo "</input>
        ${DEBUG2:+<action>echo>&2 ,,, varSBAR ,,,</action>}
        </statusbar>
        <action signal=\"button-press-event\">set -- $VISIBLE_SOURCES; echo -n \$(((\${varF3} + 1) % \${#}))>'$F3SF'</action>
        <action signal=\"button-press-event\">refresh:varF3</action>
      </eventbox>
    </vbox>" # Copy <action>s from <menuitem> HOTKEY_F3.
  #]]]
}

generate_source_hotkeys() # [[[2
{
  # Actions for keys Ctrl+1..Ctrl+9
  local h i
  h=0
  for i in 1 2 3 4 5 6 7 8 9; do
    echo '<menuitem accel-key="0x003'"$i"'" accel-mods="4">'
    printf "  <action>%s</action>\n" "echo -n $h >\"${F3SF}\"" "refresh:varF3"
    echo "</menuitem>"
    [ $i = $NSOURCES ] && break
    h=$i
  done
}

start_window() # Returns when gtkdialog terminates [[[2
{
  local dialog findnrun
  dialog="${TMPD}/.main.xml"
  findnrun="$TMPD/${0##*/}"
  # Rename GTKDIALOG "$findnrun" in the ps/htop process list.
  ln -sf "$(command -v $GTKDIALOG)" "$findnrun"
  cat > "${dialog}" &&
  if [ ${FNRDEBUG:-0} -gt 9 ]; then
    cat "$dialog"
    return
  fi &&
  if [ "${ENABLESTDOUT}" ]; then
    "$findnrun" ${GEOMETRY:+--geometry=}${GEOMETRY} ${CMDLINEOPTS} -f "${dialog}" &
  else
    "$findnrun" ${GEOMETRY:+--geometry=}${GEOMETRY} ${CMDLINEOPTS} -f "${dialog}" >/dev/null &
  fi
  mv "$TMPD"/.$$ "$TMPD/.$$-$!" # track PPID-PID
  if [ "${FNRDEBUG}" ]; then
    echo "$RESTART_WINDOW" > "${dialog#.xml}"_RESTART_WINDOW.xml
    echo "$GUI_ABOUT" > "${dialog#.xml}"_GUI_ABOUT.xml
  fi
  wait # for traps on gtkdialog's PID
}

generate_restart_window() # [[[2
{
  # Restart findnrun dialog with all command line arguments.
  # Note: gtkdialog doesn't allow '<' redirection, so we need to 'cat |'.
  printf %s \
    "for pid in $TMPD/.$$-*; do :; done" \
    '; pid=${pid##*-}' \
    "; cat /proc/$$/cmdline | xargs -0 env -- &" \
    ' kill $pid'
}

# Restart window dialog. [[[1
# i18n restart window dialog
export RESTART_WINDOW='
<window title="'"$APP_TITLE"'" icon-name="edit-find" resizable="false" window-position="2">
  <vbox>
    <text wrap="true" xalign="0">
      <variable export="false">RESTART_WINDOW_MESSAGE</variable>
      <input>2>/dev/null head -n 1 "'"$TMPD/.msg_restart"'"</input>
    </text>
    <text justify="0">
      <label>"     '"$i18n_Restart_Findnrun"'     "</label>
    </text>
    <text><label>""</label></text>
    <hbox>
      <text space-fill="true" space-expand="true"><label>""</label></text>
      <button yes>
        <action>'"$(generate_restart_window)"'</action>
      </button>
      <button no>
        <action>closewindow:RESTART_WINDOW</action>
      </button>
      <text space-fill="true" space-expand="true"><label>""</label></text>
    </hbox>
  </vbox>
  <variable>RESTART_WINDOW</variable>
  <action signal="key-press-event" condition="command_is_true([ $KEY_SYM = Escape ] && echo true )">closewindow:RESTART_WINDOW</action>
</window>'

# Prepare and show the main window. [[[1
# Tip: if your /bin/sh is bash you can use the following stanza in
# <action>s, <input>s, etc. to print the execution line on stderr:
#   <action>${DEBUG1:+echo >&2 \$BASH_EXECUTION_STRING;}commands...</action>
unset showcategories
[ true = "$SEARCHCATEGORIES" -o true = "$SEARCHCOMPLETE" ] && showcategories=true
[ hidden = "${SEARCHCATEGORIES}" ] && unset showcategories
# i18n Main window widgets
# i18n "0" (invisible, disregard).
gettext 0 >/dev/null # work around an xgettext limitation
# Ensure that we have a unique title handle to the window
xwininfo -name "$APP_TITLE" >/dev/null 2>&1 && APP_TITLE_UNIQ="$APP_TITLE [$$]"
start_window << EOF
<window title="${APP_TITLE_UNIQ:-$APP_TITLE}" icon-name="edit-find" window-position="2">
  <vbox>
    ${REMARK# [[[. varSEARCH: progressive typing search input field.}
    <hbox spacing="0">
      <entry auto-refresh="${DEBUG1:+true}" tooltip-text="$i18n_Press_ENTER_to_select">
        ${REMARK# [[[. Entering IBOL+IBOL makes the search input field}
        ${REMARK# ignore all characters to the left of IBOL+IBOL included.}
        ${REMARK# IBOL stands for Ignore To Beginning Of Line. Its default}
        ${REMARK# value is the soft-hyphen character, which is invisible in}
        ${REMARK# gtkdialog. Here we append IBOL+IBOL to the default search}
        ${REMARK# term so that the entire value --which is simply a help tip}
        ${REMARK# is ignored, and the search engine can perform a clean query.}
        <default>$i18n_Type_some_letters${IBOL}${IBOL}</default>
        ${REMARK# ]]]}
        <variable>varSEARCH</variable>
        ${REMARK# The entry widget ignores initial input unless it is refreshed, see varSEARCH0.}
        <input>echo -n "\${INITSEARCH}"</input>
        ${DEBUG1:+<input file>/tmp/varSEARCH</input>}
        ${DEBUG2:+<action>echo>&2 ,,, varSEARCH ,,,</action>}
        <action>refresh:varLIST</action>
        <action signal="activate">grabfocus:varLIST</action>
        <action signal="activate">echo false>"${FCSF}"</action>
      </entry>
      <button tooltip-text="$i18n_Clear_entry" stock-icon-size="1">
        <input file stock="gtk-clear"></input>
        <action>grabfocus:varSEARCH</action>
        <action>clear:varSEARCH</action>
      </button>
    </hbox>
    ${REMARK# ]]]}
    ${REMARK# [[[. varLIST: list tap-data records; invoke selected.}
    <tree enable-search="false" exported-column="2" column-visible="1|1|0" headers-visible="false" icon-column-name="gtk-apply" hscrollbar-policy="1" vscrollbar-policy="1" tooltip-text="$i18n_Press_ENTER_or_double_click">
      ${REMARK# Column names below. Only Reserved and Label are visible.}
      ${REMARK# Consider also that there is an icon column, so a full input}
      ${REMARK# record is defined as Icon|Reserved|Label|PackedValues }
      <label>Reserved|Label|PackedValues</label>
      <variable>varLIST</variable>
      <output file>$F4SF</output>
      ${REMARK# [[[. Populate list view.}
      <input icon-column="0">$(generate_invoke_source_tap)</input>
      ${REMARK# ]]]}
      ${DEBUG2:+<action>echo>&2 ,,, varLIST ,,,</action>}
      ${REMARK# [[[. Invoke list view selected item.}
      <action signal="row-activated">$(generate_invoke_source_drain)</action>${REMARK# which saves item to history.}
      <action signal="row-activated">refresh:varCMD</action>
      ${REMARK# ]]]}
      <action condition="active_is_false(varOPEN)">exit:EXIT</action>
      <action signal="changed">refresh:varCMD</action>
      <action signal="changed">clear:varCOMMENT</action>
      <action signal="changed">refresh:varCOMMENT</action>
      ${DEBUG2:+<action>echo >&2 auto-refreshing varFOCUSGRABBER</action>}
      <action>( sleep 0.1 || sleep 1; echo "\${varFOCUSSEARCH}">"${FCSF}"; ) &</action>
    </tree>
    ${REMARK# ]]]}
    ${REMARK# [[[. varFOCUSGRABBER: handle varLIST on(EnterEnter|double-click).}
    ${REMARK# input file auto-refresh rate cannot be configured. http://code.google.com/p/gtkdialog/source/detail?r=453}
    ${REMARK# gtkdialog compiled w/o inotify refreshes about once a second. With inotify refreshing is instantaneous.}
    <checkbox auto-refresh="true" visible="false">
      <default>false</default>
      <variable>varFOCUSGRABBER</variable>
      <input file>${FCSF}</input>
      ${DEBUG2:+<action>echo>&2 ,,, varFOCUSGRABBER ,,,</action>}
      ${DEBUG2:+<action>if true echo >&2 'grabfocus:varSEARCH'</action>}
      <action>if true grabfocus:varSEARCH</action>
      <action>if true echo false>"${FCSF}"</action>
      <action>if true clear:varFOCUSGRABBER</action>
    </checkbox>
    ${REMARK# ]]]}
    ${REMARK# [[[. varCMD: history editing.}
    <hbox space-fill="false" space-expand="false">
      ${REMARK# This widget doubles as a varLIST item grabber and as a pull-down history list.}
      ${REMARK#  [[[. Pull-down list and input entry field combo.}
      <comboboxentry space-expand="true" space-fill="true" tooltip-text="$i18n_Press_the_Down_Arrow_key">
        <variable>varCMD</variable>
        <default>$i18n_Press_Down_Arrow</default>
        ${REMARK# Append a space to the grabbed varLIST item \$@ so we can tell it from history items.}
        ${REMARK# Note that the entry widget discards trailing spaces, so our space is a unique mark.}
        <input>$(generate_get_drain_cmd); echo "\$@ "; awk '{a[++i]=\$0}END{while(i>0)print a[i--]}#tac' "$HSTF"</input>
        <output file>$HSTF</output>
        ${DEBUG2:+<action>echo>&2 ,,, varCMD ,,,</action>}
        <action signal="activate" condition="command_is_true(echo \${varCMD:-true})">break:</action>
        <action signal="activate">set -- \$varCMD; echo "\$@" >> '$HSTF'; echo "\$@" >> "$TMPD/.hist-\$ID.sh"; $UNEXPORT varLIST varF3; eval "\$@" &</action>
        <action signal="activate" condition="active_is_false(varOPEN)">exit:EXIT</action>
        <action signal="activate" condition="command_is_true(echo \$varFOCUSSEARCH)">grabfocus:varSEARCH</action>
        <action signal="activate" condition="command_is_false(echo \$varFOCUSSEARCH)">grabfocus:varLIST</action>
        <action signal="activate">refresh:varCMD</action>
      </comboboxentry>
      ${REMARK#  ]]]}
      ${REMARK#  [[[. History item delete button.}
      <button tooltip-text="$i18n_Remove_entry_from_history" stock-icon-size="1">
        <input file stock="gtk-remove"></input>
        <action>grabfocus:varCMD</action>
        ${REMARK# pull-down-list ::= varLIST-item + history-file-contents.}
        <action>removeselected:varCMD</action>${REMARK# pull-down-list -= user-selected-item.}
        <action>save:varCMD</action>${REMARK# Save pull-down-list (including space-terminated varLIST-item}
        ${REMARK# ^^^ Gtkdialog 0.8.4: save:varCMD does not append LF to the last file line.}
        ${REMARK# Now delete the space-terminated varLIST item from the saved history file.}
        ${REMARK# Thankfully awk is oblivious to the missing LF.}
        ${REMARK# history-file-contents -= space-terminated-items.}
        <action>awk '/[^ ]\$/{a[++i]=\$0}END{printf "">FILENAME;while(i>0)print a[i--]>FILENAME}#tac' '$HSTF'</action>
        <action>refresh:varCMD</action>${REMARK# widget <- varLIST-item + history-file-contents.}
      </button>
      ${REMARK#  ]]]}
    </hbox>
    ${REMARK# ]]]}
    ${REMARK# [[[. varCOMMENT: display item comment.}
    <entry sensitive="false" tooltip-text="$i18n_Comment_about_current_item">
      <variable>varCOMMENT</variable>
      <input>IFS=${SEP}; set -- \${varLIST}; echo "\$4${showcategories:+ \$5}"</input>
    </entry>
    ${REMARK# ]]]}
    ${REMARK# [[[. Options and tools (bottom bar)}
    <hbox space-fill="false" space-expand="false">
      ${REMARK# [[[. EXP0 expander activates EXP1}
      <expander expanded="false" tooltip-text="$i18n_More">
        <text visible="false"></text>
        <variable export="false">EXP0</variable>
        <action>if false show:EXP1</action>${REMARK# show the other expander and...}
        <action>if false activate:EXP1</action>${REMARK# ...expand it}
        <action>if true activate:EXP0</action>${REMARK# shrink myself and...}
        <action>hide:EXP0</action>${REMARK# ...hide myself immediately}
        <label>""</label>
      </expander>
      ${REMARK# ]]]}
      ${REMARK# [[[. varOPEN checkbox}
      <checkbox use-underline="true" tooltip-text="$i18n_Keep_this_window_open">
        <label>$i18n_Keep_window</label>
        <default>${defOPEN}</default>
        <variable>varOPEN</variable>
        ${DEBUG2:+<action>echo>&2 ,,, varOPEN ,,,</action>}
        <action>awk -v s=defOPEN=\${varOPEN} '/^defOPEN=/{\$0=s;f=1}{a[++n]=\$0}END{if(!f)a[++n]=s;++n;for(i=1;i!=n;i++)print a[i]>ARGV[1]}' '${CONFIG}'</action>
      </checkbox>
      ${REMARK# ]]]}
      ${REMARK# [[[. varICONS checkbox}
      <checkbox use-underline="true" tooltip-text="$i18n_Display_all_available_icons">
        <label>$i18n_Show_all_icons</label>
        <default>${varICONS}</default>
        <variable>varICONS</variable>
        ${DEBUG2:+<action>echo>&2 ,,, varICONS ,,,</action>}
        <action>awk -v s=varICONS=\${varICONS} '/^varICONS=/{\$0=s;f=1}{a[++n]=\$0}END{if(!f)a[++n]=s;++n;for(i=1;i!=n;i++)print a[i]>ARGV[1]}' '${CONFIG}'</action>
        <action>clear:varSEARCH</action>
        ${DEBUG2:+<action>ls /usr/share/pixmaps/findnrun-.uptodate ~/.icons/findnrun-.uptodate >&2; cat ~/.findnrunrc >&2</action>}
        <action condition="command_is_true([ -f '${ICONSTEM}.uptodate' -a true = \${varICONS} ] && echo true)">break:</action>
        ${DEBUG2:+<action>if true echo >&2 in true rebuilding database...</action>}
        <action>if true gawk -v ALL_ICONS=\${varICONS} -v SHOWNODISPLAY=\${SHOWNODISPLAY} -f '${AWKB}' -v LIST_FILE='$LIST_FILE' > '${DATF}'</action>
        ${DEBUG2:+<action>if true echo >&2 in true clear:varLIST</action>}
        <action>if true clear:varLIST</action>
        ${DEBUG2:+<action>if true echo >&2 in true refresh:varLIST</action>}
        <action>if true refresh:varLIST</action>
        ${DEBUG2:+<action>if false echo >&2 in false rm -f \{~/.icons\|/usr/share/pixmaps\}/findnrun-\*</action>}
        <action>if false rm -f '${ICONSTEM:-/tmp/dummy}'*</action>
        ${DEBUG2:+<action>if false echo >&2 in false rebuilding database...</action>}
        <action>if false gawk -v ALL_ICONS=\${varICONS} -v SHOWNODISPLAY=\${SHOWNODISPLAY} -f '${AWKB}' -v LIST_FILE='$LIST_FILE' > '${DATF}'</action>
        ${DEBUG2:+<action>if false echo >&2 in false clear:varLIST</action>}
        <action>if false clear:varLIST</action>
        ${DEBUG2:+<action>if false echo >&2 in false refresh:varLIST</action>}
        <action>if false refresh:varLIST</action>
      </checkbox>
      ${REMARK# ]]]}
      ${REMARK# [[[. varFOCUSSEARCH checkbox}
      <checkbox use-underline="true" tooltip-text="$i18n_Return_the_keyboard_focus">
        <label>$i18n_Focus_search</label>
        <default>${varFOCUSSEARCH}</default>
        <variable>varFOCUSSEARCH</variable>
        ${DEBUG2:+<action>echo>&2 ,,, varFOCUSSEARCH ,,,</action>}
        <action>awk -v s=varFOCUSSEARCH=\${varFOCUSSEARCH} '/^varFOCUSSEARCH=/{\$0=s;f=1}{a[++n]=\$0}END{if(!f)a[++n]=s;++n;for(i=1;i!=n;i++)print a[i]>ARGV[1]}' '${CONFIG}'</action>
      </checkbox>
      ${REMARK# ]]]}
      ${REMARK# This widget makes the widgets to its left float left and distribute evenly when the window is widened.}
      <text space-fill="true" space-expand="true"><label>""</label></text>
      ${REMARK# [[[. 'help' button}
      <button tooltip-text="$i18n_About_and_help" stock-icon-size="1">
        <input file stock="gtk-about"></input>
        <action>launch:GUI_ABOUT</action>
      </button>
      ${REMARK# ]]]}
      ${REMARK# [[[. 'exit' button}
      <button tooltip-text="$i18n_Exit" stock-icon-size="1">
        <input file stock="gtk-quit"></input>
        <action>exit:EXIT</action>
      </button>
      ${REMARK# ]]]}
    </hbox>
    ${REMARK# ]]]}
    ${REMARK# [[[. EXP1 expander shows More options}
    <hbox>
      <expander expanded="false" visible="false" space-fill="true" space-expand="true" label-fill="false" tooltip-text="$i18n_Click_to_hide">
        <hbox space-fill="false" space-expand="false">
          ${REMARK# [[[. SHOWNODISPLAY checkbox}
          <checkbox use-underline="true" tooltip-text="$i18n_Show_hidden_system_applications">
            <label>$i18n_Show_hidden</label>
            <default>${SHOWNODISPLAY}</default>
            <variable>SHOWNODISPLAY</variable>
            ${DEBUG2:+<action>echo>&2 ,,, SHOWNODISPLAY ,,,</action>}
            <action>awk -v s=SHOWNODISPLAY=\${SHOWNODISPLAY} '/^SHOWNODISPLAY=/{\$0=s;f=1}{a[++n]=\$0}END{if(!f)a[++n]=s;++n;for(i=1;i!=n;i++)print a[i]>ARGV[1]}' '${CONFIG}'</action>
            <action>gawk -v ALL_ICONS=\${varICONS} -v SHOWNODISPLAY=\${SHOWNODISPLAY} -f '${AWKB}' -v LIST_FILE='$LIST_FILE' > '${DATF}'</action>
            <action>refresh:varLIST</action>
          </checkbox>
          ${REMARK# ]]]}
          ${REMARK# [[[. SEARCHCOMPLETE checkbox}
          <checkbox use-underline="true" tooltip-text="$i18n_Search_in_application_names_etc">
            <label>${i18n_Search_complete}*</label>
            <default>${SEARCHCOMPLETE}</default>
            <variable>SEARCHCOMPLETE</variable>
            ${DEBUG2:+<action>echo>&2 ,,, SEARCHCOMPLETE ,,,</action>}
            <action>awk -v s=SEARCHCOMPLETE=\${SEARCHCOMPLETE} '/^SEARCHCOMPLETE=/{\$0=s;f=1}{a[++n]=\$0}END{if(!f)a[++n]=s;++n;for(i=1;i!=n;i++)print a[i]>ARGV[1]}' '${CONFIG}'</action>
            <action>: >"$RWMF"</action>
            <action>launch:RESTART_WINDOW</action>${REMARK# restart findnrun? w/ args}
          </checkbox>
          ${REMARK# ]]]}
          ${REMARK# [[[. SEARCHREGEX checkbox}
          <checkbox use-underline="true" tooltip-text="$i18n_Interpret_search_as_BRE">
            <label>$i18n_Regex*</label>
            <default>${SEARCHREGEX}</default>
            <variable>SEARCHREGEX</variable>
            ${DEBUG2:+<action>echo>&2 ,,, SEARCHREGEX ,,,</action>}
            <action>awk -v s=SEARCHREGEX=\${SEARCHREGEX} '/^SEARCHREGEX=/{\$0=s;f=1}{a[++n]=\$0}END{if(!f)a[++n]=s;++n;for(i=1;i!=n;i++)print a[i]>ARGV[1]}' '${CONFIG}'</action>
            <action>: >"$RWMF"</action>
            <action>launch:RESTART_WINDOW</action>${REMARK# restart findnrun? w/ args}
          </checkbox>
          ${REMARK# ]]]}
          ${REMARK# This widget makes the widgets to its left float left and distribute evenly when the window is widened.}
          <text space-fill="true" space-expand="true"><label>""</label></text>
          ${REMARK# [[[. 'edit' button}
          <button tooltip-text="$i18n_Edit_configuration_file" stock-icon-size="1">
            <input file stock="gtk-edit"></input>
            <action>open=$({ command -v rox || command -v xdg-open; } 2>/dev/null); \$open "$CONFIG" &</action>
            <action>echo "$i18n_When_you_are_finished" >"$RWMF"</action>
            <action>launch:RESTART_WINDOW</action>
          </button>
          ${REMARK# ]]]}
        </hbox>
        <label>"$i18n_application_finder_options"</label>
        <variable export="false">EXP1</variable>
        <action>if false show:EXP0</action>
        <action>if false hide:EXP1</action>
      </expander>
    </hbox>
    ${REMARK# ]]]}
    ${REMARK# [[[. Menubar (nearly hidden 1x1).}
    ${REMARK# Implementing hotkeys as key-press-event handlers would slow down typing way too much.}
    ${REMARK# So the menubar implements hotkey handlers as menu accelerators, which are fast.}
    ${REMARK# Note that menu accelerators act globally, so these hotkeys really apply to all widgets.}
    <menubar height-request="1" width-request="1">
      <menu>
      ${REMARK#  [[[. Hotkeys for varENTRY.}
        ${REMARK#   [[[. Keys PageUp/PageDown paginate the current source plugin tap.}
        ${REMARK# Note that we serialize (%s) the event name to ensure invokeTAP detects a change.}
        <menuitem accel-key="0xff55" accel-mods="0">
          <action>date '+PageUp %s'>"${ETAP}"</action>
        </menuitem>
        <menuitem accel-key="0xff56" accel-mods="0">
          <action>date '+PageDown %s'>"${ETAP}"</action>
        </menuitem>
        ${REMARK#   ]]]}
        ${REMARK#   [[[. Key F12 activates the top varLIST item.}
        <menuitem accel-key="${HOTKEY_F12##*:}" accel-mods="${HOTKEY_F12%%:*}">
          <action>grabfocus:varLIST</action>
          <action>$(generate_invoke_source_drain)</action>${REMARK# which saves item to history.}
          <action condition="command_is_true(echo \${varFOCUSSEARCH})">grabfocus:varSEARCH</action>
          <action condition="command_is_false(echo \${varFOCUSSEARCH})">grabfocus:varLIST</action>
        </menuitem>
        ${REMARK#   ]]]}
      ${REMARK#  ]]]}
      ${REMARK#  [[[. Hotkeys for varLIST.}
        ${REMARK#   [[[. Key F4 saves search results and invokes a filter.}
        <menuitem accel-key="${HOTKEY_F4##*:}" accel-mods="${HOTKEY_F4%%:*}">
          <action>save:varLIST</action>
          <action>$(generate_invoke_source_save_filter)</action>
        </menuitem>
        ${REMARK#   ]]]}
      ${REMARK#  ]]]}
      ${REMARK#  [[[. Hotkeys for the main window widget.}
        ${REMARK#   [[[. Key ESC terminates findnrun.}
        <menuitem accel-key="0xff1b" accel-mods="0">
          <action>exit:EXIT</action>
        </menuitem>
        ${REMARK#   ]]]}
        ${REMARK#   [[[. Key F1 invokes the help viewer.}
        <menuitem accel-key="0xffbe" accel-mods="0">
          <action>${helpviewer:-:} "\${PLGDIR:-...}/index.md" &</action>
        </menuitem>
        ${REMARK#   ]]]}
        ${REMARK#   [[[. Key F2 cycles focus between the search and the command entry field.}
        <menuitem accel-key="${HOTKEY_F2##*:}" accel-mods="${HOTKEY_F2%%:*}">
          <action>case \${varF2:-false} in true) echo false;; false) echo true;; esac >"${F2SF}"</action>
          <action>refresh:varF2</action>
        </menuitem>
        ${REMARK#   ]]]}
        ${REMARK#   [[[. Key F3 cycles the list view among built-in and plugin sources.}
        <menuitem accel-key="${HOTKEY_F3##*:}" accel-mods="${HOTKEY_F3%%:*}">
          <action>set -- $VISIBLE_SOURCES; echo -n \$(((\${varF3} + 1) % \${#}))>'${F3SF}'</action>
          <action>refresh:varF3</action>
        </menuitem>
        ${REMARK#   ]]]}
      ${REMARK#  ]]]}
      ${REMARK#  [[[. Keys Ctrl+1 through Ctrl+9 select the first 9 source plugins directly.}
        $(generate_source_hotkeys)
      ${REMARK#  ]]]}
        <label>""</label>
      </menu>
    </menubar>
    ${REMARK# ]]]}
    ${REMARK# [[[. varSBAR statusbar}
    ${REMARK# Do not vbox the status bar here; keep the vbox under the control}
    ${REMARK# of generate_status_bar to be able to hide the status bar without}
    ${REMARK# leaving a tiny empty strip at the bottom of the main window.}
    $(generate_status_bar)
    ${REMARK# ]]]}
  </vbox>
  ${REMARK# Do not vbox/hbox hidden widgets because boxes add some unnecessary (tiny) padding.}
  ${REMARK# Keeping hidden items after visible ones prevents issues when resizing window vertically.}
  ${REMARK# [[[. Timers (hidden).}
  ${REMARK#  [[[. varSEARCH0: refresh initial search input.}
  <timer milliseconds="true" interval="100" visible="false">
    <variable export="false">varSEARCH0</variable>
    <action>refresh:varSEARCH</action>
    ${REMARK# varF3 re-enables this timer when another plugin gets loaded.}
    <action>disable:varSEARCH0</action>
  </timer>
  ${REMARK# -  ]]]}
  ${REMARK# - ]]]}
  ${REMARK# [[[. Cached source declaration variables (hidden).}
  <entry visible="false" sensitive="false">
    <variable>TAP</variable>
    <default>${TAP}</default>
    ${REMARK# Cache TAP until next refresh:TAP.}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${TAP}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE TAP [\$varF3] \$TAP</action>}
  </entry>
  <entry visible="false" sensitive="false">
    <variable>DRAIN</variable>
    <default>${DRAIN:-null}</default>
    ${REMARK# Cache DRAIN until next refresh:DRAIN.}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${DRAIN}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE DRAIN [\$varF3] \$DRAIN</action>}
  </entry>
  <entry visible="false" sensitive="false">
    <variable>ICON</variable>
    <default>${ICON:-null}</default>
    ${REMARK# Cache ICON until next refresh:ICON.}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${ICON}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE ICON [\$varF3] \$ICON</action>}
  </entry>
  <entry visible="false" sensitive="false">
    <variable>TITLE</variable>
    <default>${TITLE:-null}</default>
    ${REMARK# Cache TITLE until next refresh:TITLE.}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${TITLE}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE TITLE [\$varF3] \$TITLE</action>}
  </entry>
  <entry visible="false" sensitive="false">
    <variable>SOURCE</variable>
    <default>${SOURCE}</default>
    ${REMARK# Cache SOURCE until next refresh:SOURCE}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${SOURCE}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE SOURCE [\$varF3] \$SOURCE</action>}
  </entry>
  <entry visible="false" sensitive="false">
    <variable>ID</variable>
    <default>${ID}</default>
    ${REMARK# Cache ID until next refresh:ID.}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${ID}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE ID [\$varF3] \$ID</action>}
  </entry>
  <entry visible="false" sensitive="false">
    <variable>NSOURCES</variable>
    <default>${NSOURCES}</default>
    ${REMARK# Cache NSOURCES until next refresh:NSOURCES.}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${NSOURCES}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE NSOURCES [\$varF3] \$NSOURCES</action>}
  </entry>
  <entry visible="false" sensitive="false">
    <variable>INITSEARCH</variable>
    ${REMARK# Cache INITSEARCH until next refresh:INITSEARCH}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${INITSEARCH}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE INITSEARCH [\$varF3] \$INITSEARCH</action>}
  </entry>
  <entry visible="false" sensitive="false">
    <variable>MODE</variable>
    ${REMARK# Cache MODE until next refresh:MODE}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${MODE}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE MODE [\$varF3] \$MODE</action>}
  </entry>
  <entry visible="false" sensitive="false">
    <variable>PLGDIR</variable>
    ${REMARK# Cache PLGDIR until next refresh:PLGDIR}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${PLGDIR}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE PLGDIR [\$varF3] \$PLGDIR</action>}
  </entry>
  <entry visible="false" sensitive="false">
    <variable>SAVEFLT</variable>
    ${REMARK# Cache SAVEFLT until next refresh:SAVEFLT}
    <input>. "${SRCSTEM}\${varF3:-0}-"*.sh && echo "\${SAVEFLT}"</input>
    ${DEBUG2:+<action>echo>&2 === CACHE SAVEFLT [\$varF3] \$SAVEFLT</action>}
  </entry>
  ${REMARK# ]]]}
  ${REMARK# [[[. Read-only values (hidden).}
  <entry visible="false" sensitive="false">
    <variable>FNRPID</variable>
    ${REMARK# gtkdialog process id.}
    <input>ps -ho ppid:1 \$\$</input>
  </entry>
  ${REMARK# ]]]}
  ${REMARK# [[[. Remote interface (hidden).}
  ${REMARK# Note that input events are serialized to ensure RPC detects the call event.}
  ${REMARK# External processes call RPC with: date '+FUNCTION1[ FUNCTION2...] %s'>"$FNRRPC"}
  <entry visible="false" auto-refresh="true">
    <default>ACCEPTING</default>
    <variable>RPC</variable>
    <input file>${FNRRPC}</input>
    ${DEBUG1:+<action>echo>&2 "RPC [ \$RPC ]"</action>}
    ${REMARK#  [[[. PresentMainWindow}
    <action condition="command_is_true(case \${RPC% *} in *PresentMainWindow*) echo 1;; esac)">presentwindow:MAINWINDOW</action>
    ${REMARK#  ]]]}
    ${REMARK#  [[[. PresentMainSearchInput}
    <action condition="command_is_true(case \${RPC% *} in *PresentMainSearchInput*) echo 1;; esac)">presentwindow:MAINWINDOW</action>
    <action condition="command_is_true(case \${RPC% *} in *PresentMainSearchInput*) echo 1;; esac)">grabfocus:varSEARCH</action>
    ${REMARK#  ]]]}
    ${REMARK#  [[[. RestartSearch}
    ${REMARK# This call forces a tap-command invocation with INITSEARCH input.}
    <action condition="command_is_true(case \${RPC% *} in *RestartSearch*) echo 1;; esac)">clear:varSEARCH</action>
    <action condition="command_is_true(case \${RPC% *} in *RestartSearch*) echo 1;; esac)">refresh:varSEARCH</action>
    ${REMARK#  ]]]}
    ${REMARK#   [[[. PageUp/PageDown paginate the current source plugin tap.}
    ${REMARK# Note that we serialize (%s) the event name to ensure invokeTAP detects a change.}
    <action condition="command_is_true(case \${RPC% *} in *PageUp*) echo 1;; esac)">date '+PageUp %s'>"${ETAP}"</action>
    <action condition="command_is_true(case \${RPC% *} in *PageDown*) echo 1;; esac)">date '+PageDown %s'>"${ETAP}"</action>
    ${REMARK#   ]]]}
    ${REMARK#  [[[. Exit}
    <action condition="command_is_true(case \${RPC% *} in *ExitFNR*) echo 1;; esac)">exit:EXIT</action>
    ${REMARK#  ]]]}
  </entry>
  ${REMARK# ]]]}
  ${REMARK# [[[. Hotkey targets (hidden).}
  ${REMARK#  [[[. refresh:varF2 to cycle focus between the search field and the command entry field.}
  ${REMARK# Key F2 demonstrates how.}
  <checkbox visible="false">
    <default>false</default>
    <label>F2</label>
    <variable>varF2</variable>
    <input file>${F2SF}</input>
    ${DEBUG2:+<action>echo>&2 ,,, varF2 ,,,</action>}
    <action>if true grabfocus:varCMD</action>
    <action>if false grabfocus:varSEARCH</action>
  </checkbox>
  ${REMARK#  ]]]}
  ${REMARK#  [[[. refresh:varF3 to switch the list view to another source.}
  ${REMARK# Key F3 demonstrates how.}
  <entry visible="false" sensitive="false">
    <variable>varF3</variable>
    <default>0</default>
    <input file>${F3SF}</input>
    ${DEBUG2:+<action>echo>&2 ,,, varF3 ,,,</action>}
    ${REMARK# [[[. Optimization: Cache current source values.}
    <action>refresh:TAP</action>
    <action>refresh:DRAIN</action>
    <action>refresh:ICON</action>
    <action>refresh:TITLE</action>
    <action>refresh:INITSEARCH</action>
    <action>refresh:MODE</action>
    <action>refresh:PLGDIR</action>
    <action>refresh:SAVEFLT</action>
    <action>refresh:SOURCE</action>
    <action>refresh:ID</action>
    ${REMARK# ]]]}
    ${DEBUG2:+<action>echo>&2 ,,, refresh varSBAR [[</action>}
    <action>refresh:varSBAR</action>
    ${DEBUG2:+<action>echo>&2 ]] ,,,</action>}
    <action>clear:varSEARCH</action>
    <action>enable:varSEARCH0</action>
    <action>refresh:varLIST</action>
  </entry>
  ${REMARK#  ]]]}
  ${REMARK#  [[[. Write event name to file $ETAP to invoke the current tap.}
  ${REMARK# Keys PageDown/PageUp demonstrate how.}
  <entry visible="false" auto-refresh="true">
    <default>Search</default>
    <variable>invokeTAP</variable>
    <input file>${ETAP}</input>
    ${COMMENT# Do not invoke if input is empty.}
    <action condition="command_is_true(echo \${invokeTAP:-true})">break:</action>
    ${DEBUG2:+<action>echo>&2 ,,, invokeTAP -\$invokeTAP- ,,,</action>}
    <action>refresh:varLIST</action>${COMMENT# Invoke tap.}
    <action>clear:invokeTAP</action>${COMMENT# Clear cached value.}
  </entry>
  ${REMARK#  ]]]}
  ${REMARK# ]]]}
  ${REMARK# [[[. Hotkey actions (hidden).}
  ${REMARK# DEBUG <action signal="key-press-event">exec 1>&2;echo;date;env|grep KEY_</action>}
  ${REMARK# ]]]}
  <action signal="delete-event">exit:abort</action>
  <variable export="false">MAINWINDOW</variable>
</window>
EOF

# i18n Findnrun own .desktop file. [[[1
# i18n Translate the Name[xx] field to be added to file findnrun.desktop
Name=$i18n_Name_desktop
# i18n Translate the Comment[xx] field to be added to file findnrun.desktop
Comment=$i18n_Comment_desktop

