#!/usr/bin/sh
# Default Terminal Execution Utility
# Reference implementation of proposed Default Terminal Execution Specification
# https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/3
#
# by Vladimir Kudrya
# https://github.com/Vladimir-csp/
# https://gitlab.freedesktop.org/Vladimir-csp/
#
# This script is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. See .
#
# Contributors:
# Roman Chistokhodov https://github.com/FreeSlave/
# fluvf https://github.com/fluvf
# Treat non-zero exit status from simple commands as an error
# Treat unset variables as errors when performing parameter expansion
# Disable pathname expansion
set -euf
# Store original IFS value, assumed to contain the default:
XTE__OIFS=$IFS
# Newline, utility variable used throughout the script
XTE__LF='
'
# name of executable
XTE__SELF_NAME=${0##*/}
# ASCII Record Separator for cache saving
# ASCII Unit Separator for Exec parsing
# Carriage return for string expander
{
read -r XTE__RSEP
read -r XTE__USEP
read -r XTE__CR
} <<- EOF
$(printf '%b\n' '\036' '\037' '\r')
EOF
error() {
# Print messages to stderr
printf '%s\n' "$@" >&2
}
check_bool() {
case "$1" in
true | True | TRUE | yes | Yes | YES | 1) return 0 ;;
false | False | FALSE | no | No | NO | 0) return 1 ;;
*)
error "Assuming '$1' means no"
return 1
;;
esac
}
# Utility function to print debug messages to stderr (or not)
if check_bool "${XTE_DEBUG-${DEBUG-0}}"; then
XTE__DEBUG=true
debug() {
# print each arg at new line, prefix each printed line with 'D: '
while IFS= read -r xte__debug_line; do
printf 'D: %s\n' "$xte__debug_line"
done <<- EOF >&2
$(printf '%s\n' "$@")
EOF
}
else
XTE__DEBUG=
debug() { :; }
fi
# builtin stdin cat
shcat() {
while IFS= read -r xte__line; do
printf '%s\n' "$xte__line"
done
}
usage() {
shcat <<- EOF
Usage:
$XTE__SELF_NAME [options] [--] [command [arguments ...]]
EOF
}
help() {
shcat <<- EOF
$XTE__SELF_NAME - Shell-based default terminal launcher.
Implementation of the proposed Default Terminal Specification.
$(usage)
Launches given command in default terminal, or launches default terminal.
Options for modifying terminal behavior (if supported by terminals's
entry):
--app-id=app-id set app-id (Wayland) or window class (X11)
--title=title set tile of terminal.
--dir=workdir set workdir of terminal.
--hold instruct terminal to hold after command ends.
Options for printing data instead of executing terminal:
--print-id
print selected Desktop Entry ID. Action is appended delimited
by ":".
--print-path
print path to selected Desktop Entry. Action is appended
delimited by ":".
--print-content
print content of selected Desktop Entry. Conflicts with --print-cmd.
--print-cmd[=printf_sequence]
print resulting command line, delimited by given printf sequence,
"\\n" by default. If sequence is "\\n", output is also terminated with
a newline.
--print-delimiter=printf_sequence
printf sequence to be used as the delimiter between multiple requested
print statements, "\\n" by default. If sequence is "\\n", output is
also terminated with a newline.
Configuration:
Preferred terminals are configured by listing their Desktop Entry IDs
in config files named "[\${desktop}-]xdg-terminals.list" placed in XDG Config
hierarchy. Where "\${desktop}" is a lowercased string that is matched
(case-insensitively) against items of "\${XDG_CURRENT_DESKTOP}".
See "man $XTE__SELF_NAME" for more details.
EOF
}
# Populates global constants and lists for later use and iteration
make_paths() {
IFS=':'
# Populate list of config files to read, in descending order of preference
for xte__dir in ${XDG_CONFIG_HOME:-"${HOME}/.config"}${IFS}${XDG_CONFIG_DIRS:-/etc/xdg}; do
# Normalise base path and append the data subdirectory with a trailing '/'
for xte__desktop in ${XTE__LOWERCASE_XDG_CURRENT_DESKTOP}; do
XTE__CONFIGS=${XTE__CONFIGS:+${XTE__CONFIGS}:}${xte__dir%/}/${xte__desktop}-xdg-terminals.list
done
XTE__CONFIGS=${XTE__CONFIGS:+${XTE__CONFIGS}:}${xte__dir%/}/xdg-terminals.list
done
# append xdg-terminal-exec dirs in XDG_DATA_DIRS to config hierarchy for distro/upstream level defaults
for xte__dir in ${XDG_DATA_DIRS:-/usr/local/share:/usr/share}; do
# Normalise base path and append the data subdirectory with a trailing '/'
for xte__desktop in ${XTE__LOWERCASE_XDG_CURRENT_DESKTOP}; do
XTE__CONFIGS=${XTE__CONFIGS:+${XTE__CONFIGS}:}${xte__dir%/}/xdg-terminal-exec/${xte__desktop}-xdg-terminals.list
done
XTE__CONFIGS=${XTE__CONFIGS:+${XTE__CONFIGS}:}${xte__dir%/}/xdg-terminal-exec/xdg-terminals.list
done
# Populate list of directories to search for entries in, in ascending order of preference
for xte__dir in ${XDG_DATA_HOME:-${HOME}/.local/share}${IFS}${XDG_DATA_DIRS:-/usr/local/share:/usr/share}; do
# Normalise base path and append the data subdirectory with a trailing '/'
XTE__APPLICATIONS_DIRS=${xte__dir%/}/applications/${XTE__APPLICATIONS_DIRS:+:${XTE__APPLICATIONS_DIRS}}
done
# cache
XDG_CACHE_HOME=${XDG_CACHE_HOME:-"${HOME}/.cache"}
XTE__CACHE_FILE=${XDG_CACHE_HOME}/xdg-terminal-exec
IFS=$XTE__OIFS
debug "paths:" " XTE__CONFIGS=${XTE__CONFIGS}" " XTE__APPLICATIONS_DIRS=${XTE__APPLICATIONS_DIRS}"
}
gen_hash() {
# return md5 of custom string, XDG_CURRENT_DESKTOP and ls -LRl output for config and data paths
# md5 is 4x faster than sha*, and there is no need for cryptography here
# writes to XTE__NEW_HASH var
# shellcheck disable=SC2034
read -r xte__hash _drop <<- EOH
$(
xte__hash_paths=${XTE__CONFIGS}:${XTE__APPLICATIONS_DIRS}
{
# cache 'version', change to invalidate when format changes
echo 4
echo "${XDG_CURRENT_DESKTOP-}"
IFS=':'
# shellcheck disable=SC2086
debug "> hashing '${XDG_CURRENT_DESKTOP-}' and listing of:" $xte__hash_paths "^ end of hash listing"
# shellcheck disable=SC2012,SC2086
LANG=C ls -LRl ${xte__hash_paths} 2> /dev/null
} | md5sum 2> /dev/null
)
EOH
case "$xte__hash" in
[0-9a-f]??????????????????????????????[0-9a-f])
debug "got fresh hash '$xte__hash'"
XTE__NEW_HASH=$xte__hash
return 0
;;
*)
debug "failed to get fresh hash, got '$xte__hash'"
return 1
;;
esac
}
read_cache() {
# reads $xte__cached_hash, $xte__cached_cmd, $xte__cached_exec_usep, $xte__cached_execarg, and others from cache file,
# checks if cache is actual and applies it, otherwise returns 1
# tries to bail out as soon as possible if something does not fit
if [ -f "${XTE__CACHE_FILE}" ]; then
xte__line_num=0
xte__line_limit=50
xte__cached_exec_usep=
xte__finished=0
while IFS= read -r xte__line; do
xte__line_num=$((xte__line_num + 1))
case "${xte__line_num}" in
1) xte__cached_hash=$xte__line ;;
2) xte__cached_cmd=$xte__line ;;
3) xte__cached_entry_path=$xte__line ;;
4) xte__cached_entry_id=$xte__line ;;
5) xte__cached_entry_action=$xte__line ;;
6) xte__cached_execarg=$xte__line ;;
7) xte__cached_appidarg=$xte__line ;;
8) xte__cached_titlearg=$xte__line ;;
9) xte__cached_dirarg=$xte__line ;;
10) xte__cached_holdarg=$xte__line ;;
"$xte__line_limit")
debug "reached cache line limit ($xte__line_limit)"
return 1
;;
# Line 11 starts command
*)
# Command is stored as raw expanded and tokenized $XTE__USEP-separated command,
# technically it can contain newline characters.
# Reconstruct newlines, use ${XTE__RSEP}END_OF_EXEC_USEP string as terminator.
xte__cached_exec_usep=${xte__cached_exec_usep}${xte__cached_exec_usep:+$XTE__LF}${xte__line}
case "${xte__line}" in
*"${XTE__RSEP}END_OF_EXEC_USEP")
xte__cached_exec_usep=${xte__cached_exec_usep%"${XTE__RSEP}END_OF_EXEC_USEP"}
xte__finished=1
break
;;
esac
;;
esac
done < "${XTE__CACHE_FILE}"
if [ "$xte__finished" = "1" ]; then
debug "got cache:" \
"hash=${xte__cached_hash}" \
"cmd=${xte__cached_cmd}" \
"entry_path=${xte__cached_entry_path}" \
"entry_id=${xte__cached_entry_id}" \
"entry_action=${xte__cached_entry_action}" \
"execarg=${xte__cached_execarg}" \
"appidarg=${xte__cached_appidarg}" \
"titlearg=${xte__cached_titlearg}" \
"dirarg=${xte__cached_dirarg}" \
"holdarg=${xte__cached_holdarg}" \
"exec_usep=${xte__cached_exec_usep}"
gen_hash || return 1
XTE__HASH=$XTE__NEW_HASH
if [ "$XTE__HASH" = "$xte__cached_hash" ] && command -v "$xte__cached_cmd" > /dev/null; then
debug "cache is actual"
XTE__EXEC_USEP=${xte__cached_exec_usep}
XTE__ENTRY_PATH=${xte__cached_entry_path}
XTE__ENTRY_ID=${xte__cached_entry_id}
XTE__ENTRY_ACTION=${xte__cached_entry_action}
XTE__EXECARG=${xte__cached_execarg}
XTE__APPIDARG=${xte__cached_appidarg}
XTE__TITLEARG=${xte__cached_titlearg}
XTE__DIRARG=${xte__cached_dirarg}
XTE__HOLDARG=${xte__cached_holdarg}
return 0
else
debug "cache is out-of-date"
return 1
fi
else
debug "invalid cache data"
return 1
fi
else
debug "no cache data"
return 1
fi
}
save_cache() {
# saves $XTE__HASH, $1 (executable), $XTE__EXEC_USEP, $XTE__EXECARG, other supported args to cache file or removes it if XTE__CACHE_ENABLED is false
if check_bool "$XTE__CACHE_ENABLED"; then
[ ! -d "${XDG_CACHE_HOME}" ] && mkdir -p "${XDG_CACHE_HOME}"
if [ -z "${XTE__HASH-}" ]; then
gen_hash || {
error "could not hash listing of files, removing '${XTE__CACHE_FILE}'"
rm -f "${XTE__CACHE_FILE}"
return 0
}
XTE__HASH=$XTE__NEW_HASH
fi
case "${XTE__HASH}${1}${XTE__EXECARG}${XTE__APPIDARG}${XTE__TITLEARG}${XTE__DIRARG}${XTE__HOLDARG}" in
"$XTE__LF")
error "One or more of terminal's arguments contains a newline, removing '${XTE__CACHE_FILE}'"
rm -f "${XTE__CACHE_FILE}"
return 0
;;
esac
XTE__UM=$(umask)
umask 0077
printf '%s\n' \
"${XTE__HASH}" \
"${1}" \
"${XTE__ENTRY_PATH}" \
"${XTE__ENTRY_ID}" \
"${XTE__ENTRY_ACTION}" \
"${XTE__EXECARG}" \
"${XTE__APPIDARG}" \
"${XTE__TITLEARG}" \
"${XTE__DIRARG}" \
"${XTE__HOLDARG}" \
"${XTE__EXEC_USEP}${XTE__RSEP}END_OF_EXEC_USEP" > "${XTE__CACHE_FILE}"
umask "$XTE__UM"
debug "> saved cache:" "${XTE__HASH}" "${XTE__EXEC_USEP}" "${XTE__EXECARG}" "${1}" "^ end of saved cache"
else
debug "cache is disabled, removing '${XTE__CACHE_FILE}'"
rm -f "${XTE__CACHE_FILE}"
return 0
fi
}
list_contains() {
# checks if list $1 contains item $2 delimited by $3 (default $XTE__LF)
xte__delimiter=${3:-$XTE__LF}
case "${xte__delimiter}${1}${xte__delimiter}" in
*"${xte__delimiter}${2}${xte__delimiter}"*) return 0 ;;
*) return 1 ;;
esac
}
# Parse all config files and populate $XTE__ENTRY_IDS with read desktop entry IDs
read_config_paths() {
# All config files are read immediatelly, rather than on demand, even if it's more IO intensive
# This way all IDs are already known, and in order of preference, before iterating over them
IFS=':'
for xte__config_path in ${XTE__CONFIGS}; do
IFS=$XTE__OIFS
debug "reading config '$xte__config_path'"
# Nonexistant file is not an error
[ -f "$xte__config_path" ] || continue
# Let `read` trim leading/trailing whitespace from the line
while read -r xte__line; do
#debug "read line '$xte__line'"
case "$xte__line" in
# Catch directives first
# cache control
/enable_cache)
debug "found '$xte__line' directive${XTE__CACHE_CONFIGURED:+ (ignored)}"
[ -z "$XTE__CACHE_CONFIGURED" ] || continue
XTE__CACHE_ENABLED=true
XTE__CACHE_CONFIGURED=1
;;
/disable_cache)
debug "found '$xte__line' directive${XTE__CACHE_CONFIGURED:+ (ignored)}"
[ -z "$XTE__CACHE_CONFIGURED" ] || continue
XTE__CACHE_ENABLED=false
XTE__CACHE_CONFIGURED=1
;;
# compat mode
/execarg_compat)
debug "found '$xte__line' directive${XTE__EXECARG_COMPAT_CONFIGURED:+ (ignored)}"
[ -z "$XTE__EXECARG_COMPAT_CONFIGURED" ] || continue
XTE__EXECARG_COMPAT=true
XTE__EXECARG_COMPAT_CONFIGURED=1
;;
/execarg_strict)
debug "found '$xte__line' directive${XTE__EXECARG_COMPAT_CONFIGURED:+ (ignored)}"
[ -z "$XTE__EXECARG_COMPAT_CONFIGURED" ] || continue
XTE__EXECARG_COMPAT=false
XTE__EXECARG_COMPAT_CONFIGURED=1
;;
# default TerminalArgExec overrides
/execarg_default:*:*)
if ! check_bool "$XTE__EXECARG_COMPAT"; then
debug "ignored directive '$xte__line' (strict mode)"
continue
fi
IFS=':' read -r _directive xte__entry_id xte__execarg_default <<- EOF
$xte__line
EOF
if validate_entry_id "${xte__entry_id}"; then
debug "added TerminalArgExec default '${xte__execarg_default}' for '${xte__entry_id}'"
# do not bother with deduplication, first entry ID will win
XTE__EXECARG_DEFAULTS=${XTE__EXECARG_DEFAULTS}${XTE__EXECARG_DEFAULTS:+$XTE__LF}${xte__entry_id}:${xte__execarg_default}
fi
;;
# `[The extensionless entry filename] should be a valid D-Bus well-known name.`
# `a sequence of non-empty elements separated by dots (U+002E FULL STOP),
# none of which starts with a digit, and each of which contains only characters from the set [a-zA-Z0-9-_]`
# Stricter parts seem to be related only to reversed DNS notation but not common naming
# i.e. there is `2048-qt.desktop`.
# I do not know of any terminal that starts with a number, but it's valid.
# Catch and validate potential entry ID with action ID (be graceful about an empty one)
[a-zA-Z0-9_]* | [+-][a-zA-Z0-9_]*)
case "$xte__line" in
[+-]*)
# save and cut exclusion marker
_line=${xte__line#[+-]}
xte__exclusion=${xte__line%"$_line"}
xte__line=$_line
;;
*) xte__exclusion= ;;
esac
# consider only the first ':' as a delimiter
IFS=':' read -r xte__entry_id xte__action_id <<- EOL
$xte__line
EOL
if validate_entry_id "${xte__entry_id}" && validate_action_id "${xte__action_id}"; then
case "$xte__exclusion" in
'')
XTE__ENTRY_IDS=${XTE__ENTRY_IDS:+${XTE__ENTRY_IDS}${XTE__LF}}$xte__line
debug "added entry ID with action ID '$xte__line'"
;;
'+')
if list_contains "${XTE__EXCLUDED_ENTRY_IDS}" "${xte__entry_id}"; then
debug "entry '${xte__entry_id}' was already excluded from fallback"
elif list_contains "${XTE__INCLUDED_ENTRY_IDS}" "${xte__entry_id}"; then
debug "entry '${xte__entry_id}' fallback exclusion was already prevented"
else
debug "preventing fallback exclusion for entry '${xte__entry_id}'"
XTE__INCLUDED_ENTRY_IDS=${XTE__INCLUDED_ENTRY_IDS:+${XTE__INCLUDED_ENTRY_IDS}${XTE__LF}}${xte__entry_id}
fi
;;
'-')
if list_contains "${XTE__INCLUDED_ENTRY_IDS}" "${xte__entry_id}"; then
debug "entry '${xte__entry_id}' fallback exclusion was already prevented"
elif list_contains "${XTE__EXCLUDED_ENTRY_IDS}" "${xte__entry_id}"; then
debug "entry '${xte__entry_id}' was already excluded from fallback"
else
debug "excluding entry '${xte__entry_id}' from fallback"
XTE__EXCLUDED_ENTRY_IDS=${XTE__EXCLUDED_ENTRY_IDS:+${XTE__EXCLUDED_ENTRY_IDS}${XTE__LF}}${xte__entry_id}
fi
;;
esac
else
error "Discarded possibly misspelled entry '$xte__line'"
fi
;;
esac
# By default empty lines and comments get ignored
done < "$xte__config_path"
done
}
replace() {
# takes $1, replaces $2 with $3
# does it in large chunks
# writes result to global XTE__REPLACED_STR to avoid $() newline issues
# right part of string
xte__r_remainder=${1}
XTE__REPLACED_STR=
while [ -n "$xte__r_remainder" ]; do
# left part before first encounter of $2
xte__r_left=${xte__r_remainder%%"$2"*}
# append
XTE__REPLACED_STR=${XTE__REPLACED_STR}$xte__r_left
if [ "$xte__r_left" = "$xte__r_remainder" ]; then
# nothing left to cut
break
fi
# append replace substring
XTE__REPLACED_STR=${XTE__REPLACED_STR}$3
# cut remainder
xte__r_remainder=${xte__r_remainder#*"$2"}
done
}
de_expand_str() {
# expands \s, \n, \t, \r, \\
# https://specifications.freedesktop.org/desktop-entry-spec/latest/value-types.html
# writes result to global $XTE__EXPANDED_STR in place to avoid $() expansion newline issues
debug "expander received: $1"
XTE__EXPANDED_STR=
xte__exp_remainder=$1
while [ -n "$xte__exp_remainder" ]; do
# left is substring of remainder before the first encountered backslash
xte__exp_left=${xte__exp_remainder%%\\*}
# append left to XTE__EXPANDED_STR
XTE__EXPANDED_STR=${XTE__EXPANDED_STR}${xte__exp_left}
debug "expander appended: $xte__exp_left"
if [ "$xte__exp_left" = "$xte__exp_remainder" ]; then
debug "expander ended: $XTE__EXPANDED_STR"
# no more backslashes left
break
fi
# remove left substring and backslash from remainder
xte__exp_remainder=${xte__exp_remainder#"$xte__exp_left"\\}
case "$xte__exp_remainder" in
# expand and append to XTE__EXPANDED_STR
s*)
XTE__EXPANDED_STR=${XTE__EXPANDED_STR}' '
xte__exp_remainder=${xte__exp_remainder#?}
debug "expander substituted space"
;;
n*)
XTE__EXPANDED_STR=${XTE__EXPANDED_STR}$XTE__LF
xte__exp_remainder=${xte__exp_remainder#?}
debug "expander substituted newline"
;;
t*)
XTE__EXPANDED_STR=${XTE__EXPANDED_STR}' '
xte__exp_remainder=${xte__exp_remainder#?}
debug "expander substituted tab"
;;
r*)
XTE__EXPANDED_STR=${XTE__EXPANDED_STR}$XTE__CR
xte__exp_remainder=${xte__exp_remainder#?}
debug "expander substituted caret return"
;;
\\*)
XTE__EXPANDED_STR=${XTE__EXPANDED_STR}\\
xte__exp_remainder=${xte__exp_remainder#?}
debug "expander substituted backslash"
;;
# unsupported sequence, reappend backslash
#*)
# XTE__EXPANDED_STR=${XTE__EXPANDED_STR}\\
# debug 'expander reappended backslash'
# ;;
esac
done
}
de_tokenize_exec() {
# Shell-based DE Exec string tokenizer.
# https://specifications.freedesktop.org/desktop-entry-spec/latest/exec-variables.html
# How hard can it be?
# Fills global XTE__EXEC_USEP var with $XTE__USEP-separated command array in place to avoid $() expansion newline issues
debug "tokenizer received: $1"
XTE__EXEC_USEP=
xte__tok_remainder=$1
xte__tok_quoted=0
xte__tok_in_space=0
while [ -n "$xte__tok_remainder" ]; do
# left is substring of remainder before the first encountered special char
xte__tok_left=${xte__tok_remainder%%[[:space:]\"\`\$\\\'\>\<\~\|\&\;\*\?\#\(\)]*}
# left should be safe to append right away
XTE__EXEC_USEP=${XTE__EXEC_USEP}${xte__tok_left}
debug "tokenizer appended: >$xte__tok_left<"
# end of the line
case "$xte__tok_remainder" in
"$xte__tok_left")
debug "tokenizer is out of special chars"
break
;;
esac
# isolate special char
xte__tok_remainder=${xte__tok_remainder#"$xte__tok_left"}
xte__cut=${xte__tok_remainder#?}
xte__tok_char=${xte__tok_remainder%"$xte__cut"}
unset cut
# cut it from remainder
xte__tok_remainder=${xte__tok_remainder#"$xte__tok_char"}
# check if still in space
case "${xte__tok_in_space}${xte__tok_left}${xte__tok_char}" in
1[[:space:]])
debug "tokenizer still in space :) skipping space character"
continue
;;
1*)
debug "tokenizer no longer in space :("
xte__tok_in_space=0
;;
esac
## decide what to do with the character
# doublequote while quoted
case "${xte__tok_quoted}${xte__tok_char}" in
'1"')
xte__tok_quoted=0
debug "tokenizer closed double quotes"
continue
;;
# doublequote while unquoted
'0"')
xte__tok_quoted=1
debug "tokenizer opened double quotes"
continue
;;
# error out on unquoted special chars
0[\`\$\\\'\>\<\~\|\&\;\*\?\#\(\)])
debug "${xte__entry_id}: Encountered unquoted character: '$xte__tok_char'"
return 1
;;
# error out on quoted but unescaped chars
1[\`\$])
debug "${xte__entry_id}: Encountered unescaped quoted character: '$xte__tok_char'"
return 1
;;
# process quoted escapes
1\\)
case "$xte__tok_remainder" in
# if there is no next char, fail
'')
debug "${xte__entry_id}: Dangling backslash encountered!"
return 1
;;
# cut and append the next char right away
# or a half of multibyte char, the other half should go into the next
# 'xte__tok_left' hopefully...
*)
xte__cut=${xte__tok_remainder#?}
xte__tok_char=${xte__tok_remainder%"$xte__cut"}
xte__tok_remainder=${xte__cut}
unset xte__cut
XTE__EXEC_USEP=${XTE__EXEC_USEP}${xte__tok_char}
debug "tokenizer appended escaped: >$xte__tok_char<"
;;
esac
;;
# Consider Cosmos
0[[:space:]])
case "${xte__tok_remainder}" in
# there is non-space to follow
*[![:space:]]*)
# append separator
XTE__EXEC_USEP=${XTE__EXEC_USEP}${XTE__USEP}
xte__tok_in_space=1
debug "tokenizer entered spaaaaaace!!!! separator appended"
;;
# ignore unquoted space at the end of string
*)
debug "tokenizer entered outer spaaaaaace!!!! separator skipped, this is the end"
break
;;
esac
;;
# append quoted chars
1[[:space:]\'\>\<\~\|\&\;\*\?\#\(\)])
XTE__EXEC_USEP=${XTE__EXEC_USEP}${xte__tok_char}
debug "tokenizer appended quoted char: >$xte__tok_char<"
;;
# this should not happen
*)
debug "${xte__entry_id}: parsing error at char '$xte__tok_char', (quoted: $xte__tok_quoted)"
return 1
;;
esac
done
case "$xte__tok_quoted" in
1)
debug "${xte__entry_id}: Double quote was not closed!"
return 1
;;
esac
# shellcheck disable=SC2086
[ -z "$XTE__DEBUG" ] || debug "tokenizer ended:" "$(
IFS=$XTE__USEP
printf ' >%s<\n' $XTE__EXEC_USEP
)"
}
de_strip_fields() {
# Operates on $XTE__EXEC_USEP
# modifies according to args/fields
# no arguments, erase fields from $XTE__EXEC_USEP
xte__exec_usep=
xte__fu_found=false
IFS=$XTE__USEP
for xte__arg in $XTE__EXEC_USEP; do
case "$xte__arg" in
# remove deprecated fields
*[!%]'%'[dDnNvm]* | '%'[dDnNvm]*) debug "injector removed deprecated '$xte__arg'" ;;
# treat file fields
*[!%]'%'[fFuU]* | '%'[fFuU]*)
if [ "$xte__fu_found" = "true" ]; then
debug "${xte__entry_id}: Encountered more than one %[fFuU] field!"
return 1
fi
xte__fu_found=true
debug "injector removed '$xte__arg'"
continue
;;
# other fields
*[!%]'%i'* | '%i'* | *[!%]'%c'* | '%c'*)
debug "injector removed '$xte__arg'"
continue
;;
# literal %
*[!%]%%* | %%*)
replace "$xte__arg" "%%" "%"
xte__rarg=$XTE__REPLACED_STR
debug "injector replacing '%%': '$xte__arg' -> '$xte__rarg'"
xte__exec_usep=${xte__exec_usep}${xte__exec_usep:+$XTE__USEP}${xte__rarg}
;;
# invalid field
*%?* | *[!%]%)
debug "${xte__entry_id}: unknown % field in argument '${xte__arg}'"
return 1
;;
*)
debug "injector keeped: '$xte__arg'"
xte__exec_usep=${xte__exec_usep}${xte__exec_usep:+$XTE__USEP}${xte__arg}
;;
esac
done
XTE__EXEC_USEP=$xte__exec_usep
IFS=$XTE__OIFS
}
reset_keys() {
# init vars used in entry checks
XTE__IS_TERMINAL=
XTE__EXEC_USEP=
XTE__EXECARG='-e'
XTE__EXECARG_DEFINED=false
XTE__APPIDARG=
XTE__TITLEARG=
XTE__DIRARG=
XTE__HOLDARG=
}
# Find and map all desktop entry files from standardised paths into aliases
find_entry_paths() {
debug "registering entries"
# Append application directory paths to be searched
IFS=':'
for xte__directory in $XTE__APPLICATIONS_DIRS; do
# Append '.' to delimit start of entry ID
set -- "$@" "$xte__directory".
done
# Find all files
set -- "$@" -type f
# Append path conditions per directory
xte__or_arg=
for xte__directory in $XTE__APPLICATIONS_DIRS; do
# Match full path with proper first character of entry ID and .desktop extension
# Reject paths with invalid characters in entry ID
set -- "$@" ${xte__or_arg} '(' -path "$xte__directory"'./[a-zA-Z0-9_]*.desktop' ! -path "$xte__directory"'./*[^a-zA-Z0-9_./-]*' ')'
xte__or_arg='-o'
done
IFS=$XTE__OIFS
# Loop through found entry paths and IDs
while IFS= read -r xte__entry_path && IFS= read -r xte__entry_id; do
# Entries are checked in ascending order of preference, so use last found if duplicate
# shellcheck disable=SC2139
alias "$xte__entry_id"="xte__entry_path='$xte__entry_path'"
debug "registered '$xte__entry_path' as entry '$xte__entry_id'"
# exclude entries based on XTE__EXCLUDED_ENTRY_IDS
if list_contains "$XTE__EXCLUDED_ENTRY_IDS" "$xte__entry_id"; then
debug "entry '${xte__entry_id}' was excluded from fallback"
continue
fi
# Add as a fallback ID regardles if it's a duplicate
XTE__FALLBACK_ENTRY_IDS=${xte__entry_id}${XTE__FALLBACK_ENTRY_IDS:+${XTE__LF}${XTE__FALLBACK_ENTRY_IDS}}
debug "added fallback ID '$xte__entry_id'"
done <<- EOE
$(
# Don't complain about nonexistent directories
find -L "$@" 2> /dev/null |
# Print entry path and convert it into an ID and print that too
awk '{ print; sub(".*/[.]/", ""); gsub("/", "-"); print }'
)
EOE
}
# Check validity of a given entry key - value pair
# Modifies following global variables:
# XTE__EXEC_USEP : Program to execute, possibly with arguments, delimited by ASCII Unit Separator.
# XTE__EXECARG : Execution argument for the terminal emulator.
# XTE__IS_TERMINAL : Set if application has been categorized as a terminal emulator
check_entry_key() {
xte__key=$1
xte__value=$2
xte__action=$3
xte__read_exec=$4
xte__de_checks=$5
# Order of checks is important
case $xte__key in
'Categories'*=*)
debug "checking for 'TerminalEmulator' in Categories '$xte__value'"
IFS=';'
for xte__category in $xte__value; do
IFS=$XTE__OIFS
[ "$xte__category" = "TerminalEmulator" ] && {
XTE__IS_TERMINAL=true
return 0
}
done
# Default in this case is to fail
return 1
;;
'Actions'*=*)
# `It is not valid to have an action group for an action identifier not mentioned in the Actions key.
# Such an action group must be ignored by implementors.`
# ignore if no action requested
[ -z "$xte__action" ] && return 0
debug "checking for '$xte__action' in Actions '$xte__value'"
IFS=';'
for xte__check_action in $xte__value; do
IFS=$XTE__OIFS
if [ "$xte__check_action" = "$xte__action" ]; then
xte__action_listed=true
return 0
fi
done
# Default in this case is to fail
return 1
;;
'OnlyShowIn'*=*)
case "$xte__de_checks" in
true) debug "checking for intersecion between '${XDG_CURRENT_DESKTOP-}' and OnlyShowIn '$xte__value'" ;;
false)
debug "skipping OnlyShowIn check"
return 0
;;
esac
IFS=';'
for xte__target in $xte__value; do
IFS=':'
for xte__desktop in ${XDG_CURRENT_DESKTOP-}; do
IFS=$XTE__OIFS
[ "$xte__desktop" = "$xte__target" ] && return 0
done
done
# Default in this case is to fail
return 1
;;
'NotShowIn'*=*)
case "$xte__de_checks" in
true) debug "checking for intersecion between '${XDG_CURRENT_DESKTOP-}' and NotShowIn '$xte__value'" ;;
false)
debug "skipping NotShowIn check"
return 0
;;
esac
IFS=';'
for xte__target in $xte__value; do
IFS=':'
for xte__desktop in ${XDG_CURRENT_DESKTOP-}; do
IFS=$XTE__OIFS
debug "checking NotShowIn match '$xte__desktop'='$xte__target'"
[ "$xte__desktop" = "$xte__target" ] && return 1
done
done
# Default in this case is to succeed
return 0
;;
'X-TerminalArgExec'*=* | 'TerminalArgExec'*=*)
# Set global variable
# values of type string should support escape sequences
de_expand_str "$xte__value"
XTE__EXECARG=$XTE__EXPANDED_STR
XTE__EXECARG_DEFINED=true
debug "read TerminalArgExec '$XTE__EXECARG'"
;;
'X-ExecArg'*=* | 'ExecArg'*=*)
# ignore old ExecArg in strict mode
case "${XTE__EXECARG_COMPAT}" in
false) return 0 ;;
esac
# Set global variable
# values of type string should support escape sequences
de_expand_str "$xte__value"
XTE__EXECARG=$XTE__EXPANDED_STR
XTE__EXECARG_DEFINED=true
debug "read TerminalArgExec '$XTE__EXECARG'"
;;
'X-TerminalArgAppId'*=* | 'TerminalArgAppId'*=*)
# Set global variable
# values of type string should support escape sequences
de_expand_str "$xte__value"
XTE__APPIDARG=$XTE__EXPANDED_STR
debug "read TerminalArgAppId '$XTE__APPIDARG'"
;;
'X-TerminalArgTitle'*=* | 'TerminalArgTitle'*=*)
# Set global variable
# values of type string should support escape sequences
de_expand_str "$xte__value"
XTE__TITLEARG=$XTE__EXPANDED_STR
debug "read TerminalArgTitle '$XTE__TITLEARG'"
;;
'X-TerminalArgDir'*=* | 'TerminalArgDir'*=*)
# Set global variable
# values of type string should support escape sequences
de_expand_str "$xte__value"
XTE__DIRARG=$XTE__EXPANDED_STR
debug "read TerminalArgDir '$XTE__DIRARG'"
;;
'X-TerminalArgHold'*=* | 'TerminalArgHold'*=*)
# Set global variable
# values of type string should support escape sequences
de_expand_str "$xte__value"
XTE__HOLDARG=$XTE__EXPANDED_STR
debug "read TerminalArgHold '$XTE__HOLDARG'"
;;
'TryExec'*=*)
de_expand_str "$xte__value"
debug "checking TryExec executable '$XTE__EXPANDED_STR'"
command -v "$XTE__EXPANDED_STR" > /dev/null || return 1
;;
'Hidden'*=*)
debug "checking boolean Hidden '$xte__value'"
case "$xte__value" in
true)
debug "ignored Hidden entry"
return 1
;;
esac
;;
'Exec'*=*)
case "$xte__read_exec" in
false)
debug "ignored Exec from wrong section"
return 0
;;
esac
debug "read Exec '$xte__value'"
# Escape sequences of DE string value (sets $XTE__EXPANDED_STR)
de_expand_str "$xte__value"
# Tokenize resulting string (sets $XTE__EXEC_USEP)
de_tokenize_exec "$XTE__EXPANDED_STR"
# Expand % fields
de_strip_fields
# Get first word from read Exec value
XTE__EXEC0=${XTE__EXEC_USEP%%"$XTE__USEP"*}
debug "checking Exec[0] executable '$XTE__EXEC0'"
command -v "$XTE__EXEC0" > /dev/null || return 1
;;
esac
# By default unrecognised keys, empty lines and comments get ignored
}
# Read entry from given path
read_entry_path() {
xte__entry_path=$1
xte__entry_action=$2
xte__de_checks=$3
xte__read_exec=false
xte__action_listed=false
# shellcheck disable=SC2016
debug "reading desktop entry '$xte__entry_path'${xte__entry_action:+ action '}$xte__entry_action${xte__entry_action:+'}"
# Let `read` trim leading/trailing whitespace from the line
while IFS=$XTE__OIFS read -r xte__line; do
case $xte__line in
# `There should be nothing preceding [the Desktop Entry group] in the desktop entry file but [comments]`
# if xte__entry_action is not requested, allow reading Exec right away from the main group
'[Desktop Entry]'*) [ -z "$xte__entry_action" ] && xte__read_exec=true ;;
# A `Key=Value` pair
[a-zA-Z0-9-]*)
# Split value from pair
xte__value=${xte__line#*=}
# Remove all but leading spaces, and trim that from the value
xte__value=${xte__value#"${xte__value%%[! ]*}"}
# Check the key, continue to next line on success
check_entry_key "$xte__line" "$xte__value" "$xte__entry_action" "$xte__read_exec" "$xte__de_checks" && continue
# Reset values that might have been set
reset_keys
# shellcheck disable=SC2016
debug "entry discarded"
return 1
;;
# found requested action, allow reading Exec
"[Desktop Action ${xte__entry_action}]"*)
if [ "$xte__action_listed" = "true" ]; then
xte__read_exec=true
else
debug "action '$xte__entry_action' was not listed in Actions"
return 1
fi
;;
# Start of the next group header, stop if already read exec
'['*) [ "$xte__read_exec" = "true" ] && break ;;
esac
# By default empty lines and comments get ignored
done < "$xte__entry_path"
}
validate_entry_id() {
# validates entry ID ($1)
case "$1" in
# invalid characters or degrees of emptiness
*[!a-zA-Z0-9_.-]* | *[!a-zA-Z0-9_.-] | [!a-zA-Z0-9_.-]* | [!a-zA-Z0-9_.-] | '' | .desktop)
debug "string not valid as Entry ID: '$1'"
return 1
;;
# all that left with .desktop
*.desktop) return 0 ;;
# and without
*)
debug "string not valid as Entry ID '$1'"
return 1
;;
esac
}
validate_action_id() {
# validates action ID ($1)
case "$1" in
# empty is ok
'') return 0 ;;
# invalid characters
*[!a-zA-Z0-9-]* | *[!a-zA-Z0-9-] | [!a-zA-Z0-9-]* | [!a-zA-Z0-9-])
debug "string not valid as Action ID: '$1'"
return 1
;;
# all that left
*) return 0 ;;
esac
}
# Loop through IDs and try to find a valid entry
find_entry() {
# for explicitly listed entries do not apply DE *ShowIn limits
xte__de_checks=false
IFS=$XTE__LF
for xte__entry_id in ${XTE__ENTRY_IDS}${XTE__LF}//fallback_start//${XTE__LF}$XTE__FALLBACK_ENTRY_IDS; do
IFS=$XTE__OIFS
case "$xte__entry_id" in
# entry has an action appended
*:*)
xte__entry_action=${xte__entry_id#*:}
xte__entry_id=${xte__entry_id%:*}
;;
# skip empty line
'') continue ;;
# fallback entries ahead, enable *ShowIn checks
'//fallback_start//')
xte__de_checks=true
continue
;;
# nullify action
*) xte__entry_action= ;;
esac
debug "matching path for entry ID '$xte__entry_id'"
# Check if a matching path was found for ID
alias "$xte__entry_id" > /dev/null 2>&1 || continue
# Evaluates the alias, it sets $xte__entry_path
eval "$xte__entry_id"
# Unset the alias, so duplicate entries are skipped
unalias "$xte__entry_id"
read_entry_path "$xte__entry_path" "$xte__entry_action" "$xte__de_checks" || continue
# Check that the entry is actually executable
[ -z "${XTE__EXEC_USEP}" ] && continue
# ensure entry is a Terminal Emulator
[ -z "${XTE__IS_TERMINAL}" ] && continue
# if entry lacks TerminalArgExec
if [ "$XTE__EXECARG_DEFINED" != "true" ]; then
# get (custom) default in compat mode
if check_bool "$XTE__EXECARG_COMPAT"; then
get_default_execarg "$xte__entry_id"
XTE__EXECARG=$XTE__DEFAULT_EXECARG
# discard entry in strict mode
else
continue
fi
fi
# Entry is valid, set XTE__ENTRY_PATH, XTE__ENTRY_ID, XTE__ENTRY_ACTION and stop
XTE__ENTRY_PATH=$xte__entry_path
XTE__ENTRY_ID=$xte__entry_id
XTE__ENTRY_ACTION=$xte__entry_action
return 0
done
# shellcheck disable=SC2086
IFS=':' error "No valid terminal entry was found in:" ${XTE__APPLICATIONS_DIRS}
return 1
}
get_default_execarg() {
# based on xte__entry_id ($1) get TerminalArgExec from XTE__EXECARG_DEFAULTS
# write to XTE__DEFAULT_EXECARG var
xte__gde_check_entry=$1
while IFS=':' read -r xte__gde_entry_id xte__execarg_default; do
case "$xte__gde_entry_id" in
"$xte__gde_check_entry")
XTE__DEFAULT_EXECARG=$xte__execarg_default
debug "custom default TerminalArgExec '$xte__execarg_default' for '$xte__gde_check_entry'"
return 0
;;
esac
done <<- EOF
$XTE__EXECARG_DEFAULTS
EOF
XTE__DEFAULT_EXECARG='-e'
debug "using default TerminalArgExec '-e' for '$xte__gde_check_entry'"
}
# print help and exit if there's -h|--help before --
for xte__arg in "$@"; do
case "$xte__arg" in
--) break ;;
-h | --help)
help
exit 0
;;
esac
done
## globals
XTE__LOWERCASE_XDG_CURRENT_DESKTOP=$(printf '%s' "${XDG_CURRENT_DESKTOP-}" | tr '[:upper:]' '[:lower:]')
# compat mode
XTE__EXECARG_COMPAT=${XTE_EXECARG_COMPAT-true}
# flag reused in directive encounter
XTE__EXECARG_COMPAT_CONFIGURED=${XTE_EXECARG_COMPAT-}
XTE__EXEC_USEP=
XTE__EXPANDED_STR=
XTE__ENTRY_PATH=
XTE__ENTRY_ID=
XTE__ENTRY_ACTION=
# this will receive proper value later
XTE__APPLICATIONS_DIRS=
# this will be filled with values from /execarg_default:*:* directives
XTE__EXECARG_DEFAULTS=
# this (re)sets vars to be filled from desktop entry
reset_keys
# path iterators
make_paths
# At this point we have no way of telling if cache is enabled or not, unless
# XTE_CACHE_ENABLED is set, so just try reading it by default, otherwise do the
# usual thing. Editing config to disable cache should invalidate the cache.
XTE__CACHE_ENABLED=${XTE_CACHE_ENABLED-true}
# flag reused for directive encounter
XTE__CACHE_CONFIGURED=${XTE_CACHE_ENABLED-}
# HASH can be reused
XTE__HASH=
if check_bool "${XTE__CACHE_ENABLED}" && read_cache; then
XTE__CACHE_USED=true
else
# continue with globals
XTE__CACHE_USED=false
# All desktop entry ids in descending order of preference from *xdg-terminals.list configs,
# with duplicates removed
XTE__ENTRY_IDS=
# All desktop entry ids found in data dirs in descending order of preference,
# with duplicates (including those in $XTE__ENTRY_IDS) removed
XTE__FALLBACK_ENTRY_IDS=
# Entry IDs excluded from fallback by '-entry.desktop' directives
XTE__EXCLUDED_ENTRY_IDS=
# Entry IDS included (exclusion prevented) by '+entry.desktop' directives
XTE__INCLUDED_ENTRY_IDS=
# Modifies $XTE__ENTRY_IDS
read_config_paths
# Modifies $XTE__ENTRY_IDS and sets global aliases
find_entry_paths
# shellcheck disable=SC2086
[ -z "$XTE__DEBUG" ] || IFS=$XTE__LF debug "> final entry ID list:" ${XTE__ENTRY_IDS} "^ end of final entry ID list"
# shellcheck disable=SC2086
[ -z "$XTE__DEBUG" ] || IFS=$XTE__LF debug "> final fallback entry ID list:" ${XTE__FALLBACK_ENTRY_IDS} "^ end of final fallback entry ID list"
# walk ID lists and find first applicable
find_entry || exit 1
fi
# Store original argument list, before it's modified
debug "> original args:" "$@" "^ end of original args" "XTE__EXEC_USEP=$XTE__EXEC_USEP" "XTE__EXECARG=$XTE__EXECARG"
# process/discard options
debug "option processing"
XTE__APPIDVAL=
XTE__TITLEVAL=
XTE__DIRVAL=
XTE__HOLD=false
XTE__TEST_MODE=false
XTE__PRINT_DATA=
XTE__PRINT_DELIMITER='\n'
XTE__PRINT_CMD_DELIMITER='\n'
while [ "$#" -gt "0" ]; do
case "$1" in
--)
debug "found explicit end of options $1"
shift
break
;;
-e | "$XTE__EXECARG")
debug "found exec arg $1"
shift
break
;;
--app-id=*)
debug "found option $1"
IFS='=' read -r _opt XTE__APPIDVAL <<- EOF
$1
EOF
debug "set app-id option to $XTE__APPIDVAL"
shift
;;
--title=*)
debug "found option $1"
IFS='=' read -r _opt XTE__TITLEVAL <<- EOF
$1
EOF
debug "set title option to $XTE__TITLEVAL"
shift
;;
--dir=*)
debug "found option $1"
IFS='=' read -r _opt XTE__DIRVAL <<- EOF
$1
EOF
debug "set dir option to $XTE__DIRVAL"
shift
;;
--hold)
debug "found option $1"
XTE__HOLD=true
debug "set XTE__HOLD=true"
shift
;;
--test)
debug "found option $1"
XTE__TEST_MODE=true
debug "set XTE__TEST_MODE=true"
shift
;;
--print-path)
debug "found option $1"
XTE__PRINT_DATA="$XTE__PRINT_DATA path"
debug "set XTE__PRINT_DATA='$XTE__PRINT_DATA'"
shift
;;
--print-id)
debug "found option $1"
XTE__PRINT_DATA="$XTE__PRINT_DATA id"
debug "set XTE__PRINT_DATA=$XTE__PRINT_DATA"
shift
;;
--print-content)
debug "found option $1"
XTE__PRINT_DATA="$XTE__PRINT_DATA content"
debug "set XTE__PRINT_DATA=$XTE__PRINT_DATA"
shift
;;
--print-cmd)
debug "found option $1"
XTE__PRINT_DATA="$XTE__PRINT_DATA cmd"
debug "set XTE__PRINT_DATA=$XTE__PRINT_DATA"
shift
;;
--print-cmd=*)
debug "found option $1"
XTE__PRINT_DATA="$XTE__PRINT_DATA cmd"
IFS='=' read -r _arg XTE__PRINT_CMD_DELIMITER <<- EOF
$1
EOF
debug "set XTE__PRINT_DATA=$XTE__PRINT_DATA" "set XTE__PRINT_CMD_DELIMITER=$XTE__PRINT_CMD_DELIMITER"
shift
;;
--print-delimiter=*)
debug "found option $1"
IFS='=' read -r _arg XTE__PRINT_DELIMITER <<- EOF
$1
EOF
debug "set XTE__PRINT_DELIMITER=$XTE__PRINT_DELIMITER"
shift
;;
[!-]*)
debug "found non-option $1"
break
;;
-*)
debug "discarding unknown option $1"
shift
;;
esac
done
debug "end of option processing, prependig options"
# print some data
XTE__PRINT_FIRST=true
case " $XTE__PRINT_DATA " in
*' id '*)
debug "acting on --print-id"
printf '%s' "${XTE__ENTRY_ID}${XTE__ENTRY_ACTION:+:}${XTE__ENTRY_ACTION}"
XTE__PRINT_FIRST=false
;;
esac
case " $XTE__PRINT_DATA " in
*' path '*)
debug "acting on --print-path"
# remove /./ delimiter
case "${XTE__ENTRY_PATH}" in
*'/./'*) XTE__ENTRY_PATH=${XTE__ENTRY_PATH%%/./*}/${XTE__ENTRY_PATH##*/./} ;;
esac
case "$XTE__PRINT_FIRST" in
false) printf '%b' "$XTE__PRINT_DELIMITER" ;;
esac
printf '%s' "${XTE__ENTRY_PATH}${XTE__ENTRY_ACTION:+:}${XTE__ENTRY_ACTION}"
XTE__PRINT_FIRST=false
;;
esac
case " $XTE__PRINT_DATA " in
*' content '*)
debug "acting on --print-content"
case "$XTE__PRINT_FIRST" in
false) printf '%b' "$XTE__PRINT_DELIMITER" ;;
esac
shcat < "${XTE__ENTRY_PATH}"
XTE__PRINT_FIRST=false
;;
esac
# early bail out in some print modes
case " $XTE__PRINT_DATA " in
' ' | *' cmd '*)
# more stuff to do, continue
true
;;
*' content '*)
# content already has newline at the end, just exit
exit 0
;;
*)
# terminate with newline if delimiter is newline
case "$XTE__PRINT_DELIMITER" in
'\n') printf '%b' '\n' ;;
esac
exit 0
;;
esac
# option prepend
if [ "$#" -gt 0 ] && [ -n "$XTE__EXECARG" ]; then
set -- "$XTE__EXECARG" "$@"
debug "prepended $1"
fi
if [ -n "$XTE__HOLDARG" ] && check_bool "$XTE__HOLD"; then
set -- "${XTE__HOLDARG}" "$@"
debug "prepended $1"
elif [ -z "$XTE__HOLDARG" ] && check_bool "$XTE__HOLD"; then
debug "terminal entry has no TerminalArgHold="
fi
if [ -n "$XTE__DIRARG" ] && [ -n "$XTE__DIRVAL" ]; then
case "$XTE__DIRARG" in
*=)
set -- "${XTE__DIRARG}${XTE__DIRVAL}" "$@"
debug "prepended $1"
;;
*)
set -- "${XTE__DIRARG}" "${XTE__DIRVAL}" "$@"
debug "prepended $1 $2"
;;
esac
elif [ -z "$XTE__DIRARG" ] && [ -n "$XTE__DIRVAL" ]; then
debug "terminal entry has no TerminalArgDir="
fi
if [ -n "$XTE__TITLEARG" ] && [ -n "$XTE__TITLEVAL" ]; then
case "$XTE__TITLEARG" in
*=)
set -- "${XTE__TITLEARG}${XTE__TITLEVAL}" "$@"
debug "prepended $1"
;;
*)
set -- "${XTE__TITLEARG}" "${XTE__TITLEVAL}" "$@"
debug "prepended $1 $2"
;;
esac
elif [ -z "$XTE__TITLEARG" ] && [ -n "$XTE__TITLEVAL" ]; then
debug "terminal entry has no TerminalArgTitle="
fi
if [ -n "$XTE__APPIDARG" ] && [ -n "$XTE__APPIDVAL" ]; then
case "$XTE__APPIDARG" in
*=)
set -- "${XTE__APPIDARG}${XTE__APPIDVAL}" "$@"
debug "prepended $1"
;;
*)
set -- "${XTE__APPIDARG}" "${XTE__APPIDVAL}" "$@"
debug "prepended $1 $2"
;;
esac
elif [ -z "$XTE__APPIDARG" ] && [ -n "$XTE__APPIDVAL" ]; then
debug "terminal entry has no TerminalArgAppId="
fi
debug "end of option prepending"
# prepend Exec
IFS=$XTE__USEP
# shellcheck disable=SC2086
set -- $XTE__EXEC_USEP "$@"
IFS=$XTE__OIFS
debug "> final args:" "$@" "^ end of final args"
if [ "$XTE__CACHE_USED" = "false" ]; then
# saves or removes cache, forked out of the way
save_cache "$1" &
fi
case "$XTE__TEST_MODE" in
true)
printf '%s\n' 'Command and arguments:'
printf ' >%s<\n' "$@"
exit 0
;;
esac
case " $XTE__PRINT_DATA " in
*' cmd '*)
debug "acting on --print-cmd"
case "$XTE__PRINT_FIRST" in
false) printf '%b' "$XTE__PRINT_DELIMITER" ;;
esac
xte__first=true
for xte__arg in "$@"; do
case "$xte__first" in
true) xte__first=false ;;
false) printf '%b' "${XTE__PRINT_CMD_DELIMITER:-\n}" ;;
esac
printf '%s' "$xte__arg"
done
# if either delimiter is a newline, terminate with another one
if [ "${XTE__PRINT_CMD_DELIMITER}" = '\n' ] || { [ "$XTE__PRINT_FIRST" = "false" ] && [ "$XTE__PRINT_DELIMITER" = '\n' ]; }; then
printf '%b' '\n'
fi
exit 0
;;
esac
if [ -z "$XTE__DIRARG" ] && [ -n "$XTE__DIRVAL" ]; then
debug "no X-TerminalArgDir in entry, changing dir to '$XTE__DIRVAL'"
cd "$XTE__DIRVAL"
fi
exec "$@"