Files
UltraGrid/data/scripts/Linux-AppImage/AppRun
Martin Pulec b9ce913b9d AppImage: include libOpenGL.so.0 fallback
Currently Ubuntu 25.10 live DVD (ubuntu-25.10-desktop-amd64.iso)
does not include the library, preventing the GUI from being
run.  On the other hand, if used unconditionally, it causes
the warning (even in the U25.10) for which it was removed:
<https://github.com/linuxdeploy/linuxdeploy/issues/152>

So preload the library if is in the system and use the bundled just as
a fallback.
2025-11-25 09:33:09 +01:00

503 lines
20 KiB
Bash
Executable File

#!/bin/sh -eu
# shellcheck disable=SC2059 # use of $bold et al. in printf format str
# defaults
use_bundled_libva_default=0
# if not run from AppImage (eg. extracted), use default values
export APPIMAGE="${APPIMAGE-none}"
ARGV0=${ARGV0-$0}
bold=$(tput bold || true)
red=$(tput setaf 1 || true)
reset=$(tput sgr0 || true)
bold_str=${bold}%s${reset}
DIR=$(dirname "$(realpath "$0")")
readonly AI_LIB_PATH="$DIR"/usr/lib
n_ld_library_path="$AI_LIB_PATH"${LD_LIBRARY_PATH:+":$LD_LIBRARY_PATH"}
LD_PRELOAD=${LD_PRELOAD-}
# there is an issue with running_from_path() which evaluates this executable
# as being system-installed
#export PATH=$DIR/usr/bin:$PATH
export QT_QPA_FONTDIR="$DIR/usr/share/fonts"
export UG_FONT_DIR="$DIR/usr/share/fonts"
QT_PLUGIN_PATH=$(set -- "$DIR"/usr/lib/qt?/plugins; echo "$1")
export QT_PLUGIN_PATH
export QT_QPA_PLATFORM_PLUGIN_PATH="$QT_PLUGIN_PATH/platforms"
export MAGICK_CONFIGURE_PATH="$DIR/etc/IM"
export MAGICK_CODER_FILTER_PATH="$DIR/usr/share/IM/filters"
export MAGICK_CODER_MODULE_PATH="$DIR/usr/share/IM/coders"
get_tools() {(
find "$DIR/usr/bin" -mindepth 1 -exec basename {} \; | tr '\n' ' '
)}
# handle symlinks, so eg. if symlinked to hd-rum-transcode, ruh the transcoder
# (no need to use `U.traGrid...AppImage -o hd-rum-transcode`)
get_tool_from_progname() {(
prog=$(basename "${ARGV0?}")
# shellcheck disable=SC2046 # intentional
set -- $(get_tools)
while [ $# -gt 0 ]; do
if [ "$prog" = "$1" ]; then
echo "$1"
return
fi
shift
done
)}
# create convenience symlinks for the AppImage
mk_symlinks() {(
if [ "$APPIMAGE" = none ]; then
abs_path=$(realpath "$0")
dstdir=.
else
abs_path=$APPIMAGE
dstdir=$(dirname "$APPIMAGE")
fi
printf "Creating symlinks for ${bold_str}in $bold_str pointing to \
${bold_str}...\n" "$(get_tools)" "$dstdir" "$abs_path"
cd "$dstdir"
# shellcheck disable=SC2046 # intentional
set -- $(get_tools)
while [ $# -gt 0 ]; do
ln -s "$abs_path" "$1" || true
shift
done
)}
## @param $1 non-empty to show full help
usage() {
full=${1:+1}
printf "usage:\n"
printf "\t${bold}${red}%s${reset} ${bold}[--gui [args]]${reset}\n" "$ARGV0"
printf "\t\tinvokes GUI\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}--appimage-help${reset}\n" "$ARGV0"
printf "\t\tprints AppImage related options\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}-h | --help | --fullhelp${reset}\n" "$ARGV0"
printf "\t\tprints this help (extended version with ${bold}--fullhelp${reset})\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}--ug-help${reset}\n" "$ARGV0"
printf "\t\tprints UltraGrid help (as '-h' if it weren't run from AppImage)\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}-m|--man [uv|hd-rum-transcode]${reset}\n" "$ARGV0"
printf "\t\tprints manual page\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}-u|--update [args]${reset}\n" "$ARGV0"
printf "\t\tupdates AppImage (args will be passed to appimageupdatetool)\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}-o|--tool uv --help${reset}\n" "$ARGV0"
printf "\t\tprints command-line UltraGrid help\n"
printf "\n"
printf "\t${bold}${red}%s${reset} ${bold}-o|--tool <t> [args]${reset}\n" "$ARGV0"
printf "\t\tinvokes specified tool\n"
printf "\t\ttool may be one of: ${bold}%s${reset}\n" "$(get_tools)"
printf "\n"
if [ "$full" ]; then
printf "\t${bold}${red}%s${reset} ${bold}--mk-symlinks${reset}\n" "$ARGV0"
printf "\t\tmake symlinks for the above tools to allow direct run\n"
printf "\n"
fi
printf "\t${bold}${red}%s${reset} ${bold}args${reset}\n" "$ARGV0"
printf "\t\tinvokes command-line UltraGrid\n"
printf "\n"
if [ ! "$full" ]; then
return
fi
printf "environment variables:\n"
printf "\tAPPIMAGE_DEBUG: print debug info + used Firejail options\n"
printf "\tULTRAGRID_AUTOUPDATE: autoupdate interval in days (0 - check always); -1 - disable update advice\n"
printf "\tULTRAGRID_BUNDLED_LIBVA: 1 - use bundled libva; 0 - use system libva (if available), default %d\n" $use_bundled_libva_default
printf "\tULTRAGRID_USE_FIREJAIL: run the UltraGrid executable with firejail. If\n"
printf "\t the variable contains a profile path (ends with\n"
printf "\t '.profile'), it will be used.\n"
printf "\tFIREJAIL_OPTS: custom options to be passed to Firejail\n"
printf "\n"
}
update_check_days=90
## Trigger update if $ULTRAGRID_AUTOUPDATE days passed.
## If $ULTRAGRID_AUTOUPDATE unset or empty, print update hint if UG binary is older than $update_check_days days.
handle_autoupdates() {(
if [ "${ULTRAGRID_AUTOUPDATE-0}" -eq -1 ]; then
return
fi
if [ "$APPIMAGE" = none ]; then
return
fi
if expr "$APPIMAGE" : '.*continuous' > /dev/null; then
update_check_days=30
fi
if [ "${ULTRAGRID_AUTOUPDATE-}" ] && [ "$ULTRAGRID_AUTOUPDATE" -ge 0 ]; then
update_check_days=$ULTRAGRID_AUTOUPDATE
fi
appimage_mtime=$(stat -c %Y "$APPIMAGE")
curr_timestamp=$(date +%s)
if [ "$curr_timestamp" -lt $((appimage_mtime + update_check_days * 24 * 60 * 60)) ]; then
return
fi
if [ "${ULTRAGRID_AUTOUPDATE-}" ] && [ "$ULTRAGRID_AUTOUPDATE" -ge 0 ]; then
echo "Running the UltraGrid update because the interval" \
"specified by ULTRAGRID_AUTOUPDATE env variable has passed..."
# update AppImage mtime otherwise if there are no updates
# after elapsing the interval, the check would run always
touch "$APPIMAGE"
if "$DIR/appimageupdatetool" -j "$APPIMAGE"; then
debug_msg "No UltraGrid AppImage update available"
else
if "$DIR/appimageupdatetool" "$APPIMAGE"; then
# prevent update loops if the new version not
# stored for some reason
ULTRAGRID_AUTOUPDATE=-1
exec "$APPIMAGE" "$@" # launch updated version
fi
fi
return
fi
printf "UltraGrid binary older than %d days, consider checking updates:\n" "$update_check_days"
printf "\n"
printf "%s -u\n" "$ARGV0"
printf "\t- updates AppImage\n"
printf "%s -u -j; [ \$? -eq 1 ] && echo Update available || echo No update available\n" "$ARGV0"
printf "\t- check for update without actually updating\n"
printf "%s -u -h\n" "$ARGV0"
printf "\t- prints update options\n"
printf "\n"
printf "Hint: you can set environment variable ULTRAGRID_AUTOUPDATE to 1 for daily automatic update or -1 to suppress the above message.\n"
printf "\n"
)}
debug_msg() {
if [ -z "${APPIMAGE_DEBUG:-}" ]; then
return
fi
echo "$@"
}
## Tries to find system libva. If found it is preloaded (+libva-drm+libva-x11)
## @retval 0 if preloaded; 1 otherwise
set_libva_ld_preload() {
[ -f "$AI_LIB_PATH/ultragrid/ultragrid_vcompress_libavcodec.so" ] ||
return 0
LOADER=$(get_loader)
[ -x "$LOADER" ] || return 1
get_ai_lib_path() {(
LD_LIBRARY_PATH=$n_ld_library_path LD_TRACE_LOADED_OBJECTS=1 \
"$LOADER" "$AI_LIB_PATH/ultragrid/ultragrid_vcompress_\
libavcodec.so" | grep "$1" | grep -v 'not found' | awk '{print $3}'
)}
AI_LIBAVCODEC_LIB=$(get_ai_lib_path libavcodec.so)
check_libva_deps() { # check if all liva*.so satisfied from sys
[ -n "$1" ] || return 1
! LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$1" |
grep -q 'libva.*not found'
}
check_libva_deps "$AI_LIBAVCODEC_LIB" && check_libva_deps \
"$(get_ai_lib_path libavutil.so)" || return 1
LIBVA_LIB=$(LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$AI_LIBAVCODEC_LIB" | grep libva.so | grep -v 'not found' | awk '{print $3}')
[ -n "$LIBVA_LIB" ] || return 1
libva_libs=$LIBVA_LIB
# add also libva-drm, libva-x11 if present
for n in libva-drm libva-x11; do
NAME=$(echo "$LIBVA_LIB" | sed s/libva/$n/)
if [ -f "$NAME" ]; then
libva_libs=$libva_libs:$NAME
fi
done
# preload also VDPAU
libvdpau_lib=$(LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$AI_LIBAVCODEC_LIB"\
| grep libvdpau.so | grep -v 'not found' | awk '{print $3}')
if [ -n "$libvdpau_lib" ]; then
libva_libs=$libva_libs:$libvdpau_lib
fi
S_LD_PRELOAD=$LD_PRELOAD
export LD_PRELOAD="$libva_libs${LD_PRELOAD:+:$LD_PRELOAD}"
# check if lavc still binds with system libva
if LD_LIBRARY_PATH=$n_ld_library_path LD_BIND_NOW=1 LD_WARN=1 \
LD_TRACE_LOADED_OBJECTS=1 "$LOADER" \
"$AI_LIBAVCODEC_LIB" 2>&1 |
grep -q 'undefined symbol'; then
LD_PRELOAD=$S_LD_PRELOAD
return 1
fi
}
## Tries to set LD_PRELOAD to system libva (see set_libva_ld_preload()). If failed, sets path to bundled libva drivers.
setup_vaapi() {
ULTRAGRID_BUNDLED_LIBVA=${ULTRAGRID_BUNDLED_LIBVA:-$use_bundled_libva_default}
if [ "$ULTRAGRID_BUNDLED_LIBVA" -ne 1 ]; then
if ! set_libva_ld_preload; then
echo "${bold}${red}Could not set system libva, using bundled libraries instead!${reset}" >&2
ULTRAGRID_BUNDLED_LIBVA=1
fi
fi
if [ "$ULTRAGRID_BUNDLED_LIBVA" -eq 1 ]; then
if [ -d "$DIR/usr/lib/va" ] && [ -z "${LIBVA_DRIVERS_PATH:-}" ]; then
export LIBVA_DRIVERS_PATH="$AI_LIB_PATH/va"
fi
fi
}
setup_wayland() {
# use bundled Wayland libs only when not present in system
sdl_l=$DIR/usr/lib/libSDL2-2.0.so.0
LOADER=$(get_loader)
if [ -x "$LOADER" ] && [ -f "$sdl_l" ]; then
ldd=$(LD_TRACE_LOADED_OBJECTS=1 "$LOADER" "$sdl_l")
if ! echo "$ldd" | grep libwayland | grep -q 'not found'; then
return # we have libwayland* in system
fi
elif [ -n "${WAYLAND_DISPLAY-}" ]; then # fallback - don't have SDL2,
return # use env var instead
fi
debug_msg "Using bundled libwayland*"
n_ld_library_path="$n_ld_library_path:$AI_LIB_PATH/wayland"
}
add_whitelist() {
# expand tilde expansion (like ~/video.mp4; only single ~ now)
# shellcheck disable=SC2088 # intentional
if ! expr "$1" : '~/' >/dev/null; then
path="$1"
else
path"$HOME${1#\~}"
fi
if contains_mount "$path"; then
disable_mount=no
fi
if expr "$path" : /tmp >/dev/null; then
printf -- "$separator--read-write=\"%s\" --mkdir=\"%s\"" "$path" "$path"
else
printf -- "$separator--whitelist=\"%s\"" "$path"
fi
separator=' '
abs_path=$(realpath "$path")
# resolve also symlinks
if [ "$abs_path" != "$path" ]; then
add_whitelist "$abs_path"
fi
}
# whitelist path that may not exist (file that will be created), so eg. also the parent
whitelist_file_path() {
path=$1
add_whitelist "$path"
if expr "$n" : 'dump:' >/dev/null || expr "$n" : '--record' >/dev/null
then
printf -- "$separator--mkdir=\"%s\"" "$path" # may not exist
fi
if expr "$n" : '.*file[:=]' >/dev/null || expr "$n" : '.*filename' \
>/dev/null; then
parent_dir=$(dirname "path")
if [ "$parent_dir" = /dev ] || [ "$parent_dir" = /tmp ]; then
return
fi
# whitelist the parent folder to allow file creation
printf -- "$separator--whitelist=\"%s\"" "$parent_dir"
fi
}
contains_mount() {
expr "$1" : '.*/mnt' >/dev/null ||
expr "$1" : '.*/media' >/dev/null ||
expr "$1" : '.*/run/mount' >/dev/null ||
expr "$1" : '.*/run/media' >/dev/null
}
## Parse params to get custom rules like whitelists that may be needed to add
## @todo spaces inside paths doesn't work
get_custom_firejail_rules() {
separator=''
playback_path=$(echo "$@" | sed -n 's/.*--playback \([^ :]*\).*/\1/p')
if [ -n "$playback_path" ]; then
add_whitelist "$playback_path"
fi
# print every argument of "filename=", "file[:=]", "dump:" or "--record=" pattern
for n in "$@"; do
file_path=$(echo "$n" | sed -n \
-e 's/.*filename=\([^:]*\).*/\1/p' \
-e 's/.*\(n\|name\)=\([^:]*\).*/\2/p' \
-e 's/.*file[:=]\([^:]*\).*/\1/p' \
-e 's/dump:\([^:]*\).*/\1/p' \
-e 's/^--record=\([^:]*\).*/\1/p')
if [ -n "$file_path" ]; then
whitelist_file_path "$file_path"
fi
done
# -d dump or -d dummy:dump_to_file
if expr "$*" : '.*dump' >/dev/null || expr "$*" : '.*record' >/dev/null; then
add_whitelist "$(pwd)"
fi
if ! expr "$DIR" : /tmp >/dev/null; then # add AppRun path if not in tmp (== extracted AppImage)
add_whitelist "$DIR"
fi
if [ -z "$separator" ]; then # no whitelist was set
printf -- "--private"
fi
# screen pw - requires root (could be disabled by default.profile) and dbus
if expr "$*" : '.*screen' >/dev/null; then
printf " --ignore=noroot"
elif firejail --version | grep -iq "d-\{0,1\}bus.*enabled"; then
printf " --dbus-system=filter \
--dbus-system.talk=org.freedesktop.Avahi"
fi
if ! contains_mount "$*" && [ "${disable_mount-}" != no ]
then
printf " --disable-mnt"
fi
}
# shellcheck source=/dev/null
. "$DIR/scripts/preload.sh"
setup_vaapi
setup_wayland
# ultragrid_aplay_jack.so is not used because it loads JACK with dlopen,
# while portaudio is linked directly to JACK library
set_ld_preload ultragrid_aplay_portaudio.so libjack
set_ld_preload ultragrid_vidcap_pipewire.so libpipewire
set_ld_preload_exe "$AI_LIB_PATH/../bin/uv-qt" libOpenGL.so.0
## @param $@ <tool> <arguments
setup_firejail() {
tool=$1
if [ "${ULTRAGRID_USE_FIREJAIL:-0}" = 0 ] ||
[ "$ULTRAGRID_USE_FIREJAIL" = no ]; then
return
fi
command -v firejail >/dev/null || { echo "Firejail not present in system!"; exit 1; }
fj_opt_file=$(mktemp)
if expr "$ULTRAGRID_USE_FIREJAIL" : '.*\.profile' >/dev/null; then
FIREJAIL_OPTS="${FIREJAIL_OPTS+$FIREJAIL_OPTS }--profile=$ULTRAGRID_USE_FIREJAIL"
else
FJ_TMPDIR=${TMPDIR-/tmp/ultragrid-$(id -u)}
FIREJAIL_OPTS="${FIREJAIL_OPTS+$FIREJAIL_OPTS }--caps.drop=all\
--nonewprivs --protocol=unix,inet,inet6,netlink --seccomp\
--private-bin=bash --private-opt=none --mkdir=$FJ_TMPDIR\
--read-write=$FJ_TMPDIR --writable-var --ignore=private-tmp"
FIREJAIL_OPTS="$FIREJAIL_OPTS $(get_custom_firejail_rules "$@") --private-etc=alsa,group,hostname,ld.so.conf,ld.so.cache,ld.so.conf.d,nsswitch.conf,passwd,resolv.conf --ignore=novideo"
if ! expr "$FIREJAIL_OPTS" : '.*--read-write="/\{1,\}tmp/*"' \
> /dev/null; then
FIREJAIL_OPTS="--read-only=/tmp $FIREJAIL_OPTS"
fi
fi
if firejail --help | grep -q -- --keep-var-tmp; then
FIREJAIL_OPTS="$FIREJAIL_OPTS --keep-var-tmp"
fi
if [ "$tool" = hd-rum-av.sh ]; then
FIREJAIL_OPTS="$FIREJAIL_OPTS --private-bin=basename,dirname,\
expr,kill,ps,sed,seq,sh,tput,tr,tty,uname"
fi
echo "firejail --env=LD_PRELOAD=${LD_PRELOAD} --env=LD_LIBRARY_PATH=${n_ld_library_path}${FJ_TMPDIR+ --env=TMPDIR=${FJ_TMPDIR}} $FIREJAIL_OPTS " > "$fj_opt_file"
if [ "${APPIMAGE_DEBUG:-}" ]; then
opts=$(cat "$fj_opt_file")
printf "Firejail arguments:\n\t%s\n\n" "$opts"
fi
}
# run only if Firejail is used to workaround not restored TIO by UG if FJ kills
# the UG process prematurelly; otherwise not needed (exec is used)
fj_sigaction() {
trap '' TERM # inhibit following signal to ourselves
if ps -o cmd >/dev/null 2>&1; then
pgid=$(ps -o pgid= -p $$ | tr -d ' ')
fi
if [ $$ -eq "$pgid" ]; then
kill -- -$$
else
echo "pgid $pgid not pid of the script ($$), not sending kill" \
>&2
fi
trap - INT TERM
wait
stty "$stty_orig"
echo "Restored TIO" >&2
}
run() {
setup_firejail "$@"
tool=$1
shift
export LD_LIBRARY_PATH="$n_ld_library_path"
if [ -n "${fj_opt_file-}" ] && [ "$tool" != uv-qt ]; then
stty_orig=$(stty -g)
trap fj_sigaction INT TERM
# shellcheck disable=SC2046 # intentional
eval $(cat "$fj_opt_file"; rm "$fj_opt_file") "$DIR/usr/bin/$tool" '"$@"'
else
exec "$DIR/usr/bin/$tool" "$@"
fi
}
# TODO remove
if [ "${FIREJAIL_DEBUG-}" ]; then
export APPIMAGE_DEBUG="$FIREJAIL_DEBUG"
printf "${bold}Use APPIMAGE_DEBUG instead of FIREJAIL_DEBUG \
(replacement)${reset}\n"
fi
if [ "${APPIMAGE_DEBUG-undef}" = undef ] && [ "${ULTRAGRID_VERBOSE-}" ]; then
APPIMAGE_DEBUG=$ULTRAGRID_VERBOSE
fi
tool=$(get_tool_from_progname)
if [ "$tool" ]; then
set -- -o "$tool" "$@"
fi
if [ $# -eq 0 ] || [ "${1-}" = "--gui" ]; then
handle_autoupdates "$@"
if [ $# -eq 0 ]; then usage; else shift; fi
if [ -x "$DIR/usr/bin/uv-qt" ]; then
run uv-qt --with-uv "$DIR/usr/bin/uv" "$@"
else
echo "GUI was not compiled in!" >&2
exit 1
fi
elif [ "$1" = "-o" ] || [ "$1" = "--tool" ]; then
handle_autoupdates "$@"
TOOL=$2
shift 2
if [ "$TOOL" = help ]; then
printf "available tools: ${bold}%s${reset}\n" "$(get_tools)"
exit 0
fi
# shellcheck disable=SC2086
run "$TOOL" "$@"
elif [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
usage
exit 0
elif [ "$1" = "--ug-help" ]; then
run uv -h
elif [ "$1" = "--fullhelp" ]; then
usage full
exit 0
elif { [ $# -eq 1 ] || [ $# -eq 2 ]; } && { [ "$1" = "-m" ] || [ "$1" = "--man" ]; }; then
PAGE=${2:-uv}
man -l "$DIR/usr/share/man/man1/$PAGE.1"
elif [ "$1" = "-u" ] || [ "$1" = "--update" ]; then
shift
if [ -d "$DIR/appimageupdatetool-lib" ]; then
export LD_LIBRARY_PATH="$DIR/appimageupdatetool-lib"
fi
touch "$APPIMAGE" # update AppImage mtime to avoid update notices if there are no updates avalable but were checked for
"$DIR/appimageupdatetool" ${1+"$@" }"$APPIMAGE"
elif [ "$1" = "--mk-symlinks" ]; then
mk_symlinks
else
handle_autoupdates "$@"
run uv "$@"
fi