#! /bin/bash # runx: Provide an X server in Cygwin, MSYS2 or WSL. Version="v0.4.21" usage() { # Usage information (--help) echo "runx - Run Linux GUI applications on MS Windows. Provides an X server on MS Windows in Cygwin, MSYS2 or WSL. Syntax: runx [OPTIONS] -- [COMMAND] Options: -h, --help Show this help. -d, --desktop Open a parent window for desktop environments. -g, --gpu Enable GPU hardware acceleration. Can fail with NVIDIA cards. Works best with XWin. --size WIDTHxHEIGHT Window size for option --desktop, e.g. 800x600. --vcxsrv Use X server VcXsrv. --xwin Use X server XWin. --clipboard [=yes|no] Enable clipboard sharing yes/no. Default: yes. --display N Use display number N for new X server. If the display number is already in use, runx will only provide the likely access credentials. --ip ADRESS IP adress to use. Default: First found 192.168.* --no-auth Disable X cookie authentication. Discouraged. --cleanup Stop all X servers and delete cookies. -v, --verbose Be verbose. Installation of runx in WSL: - Copy runx into /usr/local/bin/ - Make runx executeable: sudo chmod +x /usr/local/bin/runx - Install xauth: sudo apt update sudo apt install xauth Install an X server on Windows: runx supports two X servers: VcXsrv and XWin. Install at least one of them. - VcXsrv: Download and install from: https://sourceforge.net/projects/vcxsrv/ - XWin: Download and install Cygwin64 with packages: xinit xauth https://www.cygwin.com VcXsrv is easier to install. XWin provides a better GPU support. WSL, Cygwin: runx starts XWin if available, otherwise it starts VcXsrv. MSYS2: runx supports VcXsrv only. Usage: Example to directly run an application with runx: - Install file manager pcmanfm: sudo apt update sudo apt install pcmanfm - Run pcmanfm with: runx -- pcmanfm Example to run Mate desktop: - Install Mate desktop with: sudo apt install mate-desktop-environment - Run Mate desktop with: runx --desktop -- mate-session Example to get a Wayland environment: - Install Wayland compositor: sudo apt install weston - Run Weston with: XDG_RUNTIME_DIR=/tmp runx -- weston Providing an X server in background all the time: - Create an entry in ~/.bashrc: source /usr/local/bin/runx - In future terminal session you can directly run GUI commands. E.g. just type: 'pcmanfm' instead of 'runx -- pcmanfm'. - If you specify a display number with --display, runx will re-use a possibly already running X server with same display number and only provide the access credentials DISPLAY and XAUTHORITY. This allows to use the same X server across several terminals. runx stores the access credentials DISPLAY and XAUTHORITY in ~/.Xenv This allows sourcing the file for custom access setups. runx version $Version Please report issues and get help at: https://github.com/mviereck/runx " >&2 } ### messages error() { # Show error message and exit echo -e "${Colredbg}runx ERROR:${Colnorm} $* " >&2 Exitcode=1 return 0 } note() { # Show notice messages [ "$Sourced" = "no" ] || [ "$Verbose" = "yes" ] && echo "${Colgreen}runx note:${Colnorm} $* " >&2 return 0 } verbose() { # Show verbose message (--verbose) [ "$Verbose" = "yes" ] && note "$*" return 0 } warning() { # Show warning messages echo "${Colyellow}runx WARNING:${Colnorm} $* " >&2 return 0 } ### general routines check_dependency() { # Check for single command [ "${1:-}" ] || return 1 command -v "${1:-}" >/dev/null || { note "Command not found: ${1:-}" return 1 } return 0 } convertpath() { # Convert unix and windows pathes # $1: Mode: # windows echo Windows path - result: c:/path # unix echo unix path - result: /c/path # subsystem echo path within subsystem - result: /cygdrive/c/path or /path or /mnt/c/path # volume echo --volume compatible syntax - result: 'unixpath':'containerpath':rw (or ":ro") # container echo path of volume in container - result: /path # share echo path of $Sharefolder/file in container - result: /containerpath # $2: Path to convert. Arbitrary syntax, can be C:/path, /c/path, /cygdrive/c/path, /path # Can have suffix :rw or :ro. If none is given, return with :rw # $3: Optional for mode volume: containerpath local Mode Path Drive= Readwritemode Mode="${1:-}" Path="${2:-}" # check path for suffix :rw or :ro Readwritemode="$(echo "$Path" | rev | cut -c1-3 | rev)" [ "$(cut -c1 <<< "$Readwritemode")" = ":" ] && { Path="$(echo "$Path" | rev | cut -c4- | rev)" } || Readwritemode=":rw" # replace ~ with HOME Path="$(sed s%"~"%"${Hostuserhome:-${HOME:-}}"% <<< "$Path")" # share: Replace $Sharefolder with $Sharefoldercontainer [ "$Mode" = "share" ] && { [ -z "$Path" ] && echo "" && return 0 case $X11dockermode in run) echo "${Sharefoldercontainer}${Path#$Sharefolder}" ;; exe) echo "$Path" ;; esac return 0 } # not on Windows [ -z "$Winsubsystem" ] && { case $Mode in unix|subsystem) echo "$Path" ;; windows) warning "Nonsense path conversion $Mode: $Path" ; return 1 ;; volume) echo "'$Path':'${3:-$Path}'$Readwritemode" ;; container) echo "${3:-$Path}" ;; esac return 0 } # replace \ with / Path="$(tr '\\' '/' <<< "$Path")" # remove possible already given mountpoint Path="${Path#$Winsubmount}" # Given format is /c/ [ "$(cut -c1,3 <<< "${Path}")" = "//" ] && { Drive="$(cut -c2 <<< "$Path")" Path="$(cut -c3- <<< "$Path")" Path="${Path:-"/"}" } # Given format is C:/ [ "$(cut -c2 <<< "$Path")" = ":" ] && { Drive="$(cut -c1 <<< "$Path")" Path="$(cut -c3- <<< "$Path")" } # change C to c Drive="${Drive,}" case $Winsubsystem in WSL1) [ -z "$Drive" ] && case $Mode in windows|unix|volume) debugnote "convertpath(): Request of WSL path: $Path" grep -q "$Cachefolder" <<< "$Path" || { [ "$Readwritemode" = ":rw" ] && warning "Request of Windows path to path within WSL: $Path Write access from Windows host to WSL files can damage the WSL file system. Read-only access is ok. Option --share: You can add :ro to the path to allow read-only access. Example: --share='$Path:ro'" } ;; esac ;; esac case $Drive in "") # Path points into subsystem Path="${Path#"$Winsubpath"}" Drive="$(cut -c2 <<<"$Winsubpath")" case $Mode in windows) echo "${Drive^}:$(cut -c3- <<<$Winsubpath)$Path" ;; unix) echo "$Winsubpath$Path" ;; subsystem) echo "$Path" ;; volume) case $Mobyvm in no) echo "'$Path':'${3:-$Path}'$Readwritemode" ;; yes) echo "'$Winsubpath$Path':'${3:-$Path}'$Readwritemode" ;; esac ;; container) echo "${3:-$Path}" ;; esac ;; *) # Path outside of subsystem case $Mode in windows) echo "${Drive^}:$Path" ;; unix) echo "/$Drive$Path" ;; subsystem) echo "$Winsubmount/$Drive$Path" ;; volume) echo "'/$Drive$Path':'${3:-/$Drive$Path}'$Readwritemode" ;; container) echo "${3:-/$Drive$Path}" ;; esac ;; esac return 0 } escapestring() { # Escape special chars of $1 # escape all characters except those described in [^a-zA-Z0-9,._+@=:/-] echo "${1:-}" | LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@=:/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/' } getwslpath() { # Get path to currently running WSL system # Fork from https://github.com/Microsoft/WSL/issues/2578#issuecomment-354010141 local RUN_ID= BASE_PATH= RUN_ID="/tmp/$(mcookie)" # Mark our filesystem with a temporary file having an unique name. touch "${RUN_ID}" powershell.exe -Command '(Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss | ForEach-Object {Get-ItemProperty $_.PSPath}).BasePath | where-object {$_ -ne $null} | ForEach-Object { $_.replace(":", "").replace("\", "/") }' | while IFS= read -r BASEPATH; do # Remove trailing whitespaces. BASEPATH="${BASEPATH%"${BASEPATH##*[![:space:]]}"}" # Build the path on WSL. BASEPATH="/mnt/${BASEPATH,}/rootfs" # Current WSL instance doesn't have an access to its mount from within # itself despite all others are available. That's the hacky way we're # using to determine current instance. # # The second of part of the condition is a fallback for a case if our # trick will stop working. For that we've created a temporary file with # an unique name and now seeking it among all WLSs. if ! ls "${BASEPATH}" > /dev/null 2>&1 || [ -f "${BASEPATH}${RUN_ID}" ]; then echo "${BASEPATH}" # You can create and simultaneously run multiple WSL instances, comment # out the "break", run this script within each one and it'll return only # single value. break fi done rm "${RUN_ID}" return 0 } makecookie() { # Bake a cookie mcookie 2>/dev/null || echo $RANDOM$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM | cut -b1-32 } rmcr() { # Remove carriage return to translate DOS/Windows newlines into UNIX newlines. # Convert stdin if $1 is empty. Otherwise convert file $1. case "${1:-}" in "") sed "s/$(printf "\r")//g" ;; *) sed -i "s/$(printf "\r")//g" "${1:-}" esac } strlenhex() { # Print byte length of string $1 as hex value printf '%x' "$(printf "%s" "${1:-}" | wc -c)" } ### setup check_display() { # Find unused display number local Displaynumber for Displaynumber in $(seq 999 | shuf); do check_displayport "$Displaynumber" || { echo "$Displaynumber" return 0 } done return 1 } check_displayport() { # Return 0 if display number $1 is in use timeout 1 bash -c "/dev/null 2>&1" && return 0 || return 1 } check_host() { # Check host environment # Check for MS Windows subsystem command -v cygcheck.exe >/dev/null && { cygcheck.exe -V | rmcr | grep -q "(cygwin)" && Winsubsystem="CYGWIN" cygcheck.exe -V | rmcr | grep -q "(msys)" && Winsubsystem="MSYS2" } uname -r | grep -q Microsoft && Winsubsystem="WSL1" uname -r | grep -q microsoft && Winsubsystem="WSL2" case "$Winsubsystem" in WSL1|WSL2|CYGWIN|MSYS2) ;; *) error "runx is designed to run on MS Windows in WSL, Cygwin or MSYS2. Did not detect WSL, Cygwin or MSYS2." ;; esac case $Winsubsystem in MSYS2|CYGWIN) Winsubmount="$(cygpath.exe -u "c:/" | rmcr | sed s%/c/%%)" Winsubpath="$(convertpath unix "$(cygpath.exe -w "/" | rmcr)" )" ;; WSL1|WSL2) command -v "/mnt/c/Windows/System32/cmd.exe" >/dev/null && Winsubmount="/mnt" command -v "/c/Windows/System32/cmd.exe" >/dev/null && Winsubmount="" grep -q "Windows" <<< "${PATH:-}" || export PATH="${PATH:-}:$Winsubmount/c/Windows/System32:$Winsubmount/c/Windows/System32/WindowsPowerShell/v1.0" # can miss after sudo in WSL command -v "$Winsubmount/c/Windows/System32/cmd.exe" >/dev/null || error "$Winsubsystem: Could not find cmd.exe in /mnt/c/Windows/System32 or /c/Windows/System32. Do you have a different path to your Windows system partition?" Winsubpath="$(convertpath unix "$(getwslpath)")" ;; esac Winsubmount="${Winsubmount%/}" Winsubpath="${Winsubpath%/}" # Get IP of Windows host [ "$Hostip" = "localhost" ] && Hostip="127.0.0.1" [ "$Hostip" ] || [ "$Winsubsystem" = "WSL2" ] && Hostip="$(ip route list default | awk '{print $3}')" [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '192\.168\.[0-9]*\.[0-9]*' | head -n1 )" [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '10\.0\.[0-9]*\.[0-9]*' | head -n1 )" [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*' | head -n1 )" verbose " Subsystem: $Winsubsystem Path to subsystem: $Winsubpath Mount path: $Winsubmount IP address $Hostip" return 0 } check_xserver() { # Check for Xwin and VcXsrv local Line [ "$Winsubsystem" = "MSYS2" ] && [ "$Xserver" = "xwin" ] && { note "runx in MSYS2 does not support --xwin. Fallback: Enabling option --vcxsrv." Xserver="vcxsrv" } # X server VcXsrv or XWin Vcxsrvbin="$(command -v vcxsrv.exe)" [ "$Vcxsrvbin" ] || Vcxsrvbin="$(command -v "$(convertpath subsystem "C:/Program Files/VcXsrv/vcxsrv.exe")")" [ "$Vcxsrvbin" ] || Vcxsrvbin="$(command -v "$(convertpath subsystem "C:/Program Files/VcXsrv (x86)/vcxsrv.exe")")" Xwinbin="$(command -v XWin)" || Xwinbin="$(command -v XWin.exe)" [ -z "$Xwinbin" ] && case $Winsubsystem in WSL1|WSL2) # search for XWin for Drive in $Winsubmount/*; do Xwinbin="$(command -v "$Drive/cygwin64/bin/XWin.exe")" [ "$Xwinbin" ] && break Xwinbin="$(command -v "$Drive/cygwin32/bin/XWin.exe")" [ "$Xwinbin" ] && break done ;; esac verbose "Found X servers: $Vcxsrvbin $Xwinbin" [ -z "$Xserver" ] && [ -n "$Xwinbin" ] && Xserver="xwin" [ -z "$Xserver" ] && Xserver="vcxsrv" [ "$Winsubsystem" = "MSYS2" ] && Xserver="vcxsrv" case "$Xserver" in vcxsrv) Xserverbin="$Vcxsrvbin" ;; xwin) Xserverbin="$Xwinbin" ;; esac [ -z "$Xserverbin" ] && { case $Winsubsystem in WSL1|WSL2|CYGWIN) error "No X server found. Please either install X server VcXsrv: https://sourceforge.net/projects/vcxsrv or install Cygwin with packages xinit and xauth to provide XWin: https://www.cygwin.com/" ;; MSYS2) error "No X server found for MSYS2. Please install X server VcXsrv: https://sourceforge.net/projects/vcxsrv" ;; esac } return 0 } cookiebaker() { # Create an X cookie without xauth # $1 DISPLAY # Write directly to file, bash cannot store nullbytes in a string. # Based on https://stackoverflow.com/questions/70932880/what-is-the-internal-format-of-xauthority-file # and chapter 6.2.5 in https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Desktop-generic/LSB-Desktop-generic/libx11-ddefs.html local Display local Address Addresslength Displaynumber Displaynumberlength Data Part Code Display="${1:-$DISPLAY}" Address="$(printf "%s" "$Display" | cut -d: -f1)" case "$Address" in "") Address="$(hostname)/unix" Addresslength="$(strlenhex "$Address")" ;; *.*.*.*) Data="$Address" Address="" while [ "$(printf "%s" "$Data" | wc -c)" -gt 0 ]; do Part="$( printf "%s" "$Data" | cut -d. -f1)" Address="${Address}\x$(printf "%x" "$Part")" Data="$( printf "%s" "$Data" | cut -s -d. -f2-)" done Addresslength="4" ;; *) Addresslength="$(strlenhex "$Address")" ;; esac Displaynumber="$(printf "%s" "$Display" | cut -d: -f2)" Displaynumber="$(printf "%s" "$Displaynumber" | cut -d. -f1)" Displaynumberlength="$(strlenhex "$Displaynumber")" Data="$(makecookie)" while [ "$(printf "%s" "$Data" | wc -c)" -gt 0 ]; do Part="$( printf "%s" "$Data" | cut -c1-2)" Code="${Code}\x$Part" Data="$( printf "%s" "$Data" | cut -c3-)" done awk "BEGIN{ printf \"\xFF\xFF\" printf \"\x00\x${Addresslength}\" printf \"${Address}\" printf \"\x00\x${Displaynumberlength}\" printf \"${Displaynumber}\" printf \"\x00\x12\" printf \"MIT-MAGIC-COOKIE-1\" printf \"\x00\x10\" printf \"${Code}\" }" } generate_xcommand() { # Generate command to start X server VcXsrv Xcommand="$(escapestring "$Xserverbin") :$Newdisplaynumber -listen tcp -retro -lesspointer" # GPU hardware acceleration case $Sharegpu in yes) Xcommand="$Xcommand -wgl +iglx" ; export LIBGL_ALWAYS_INDIRECT=1 [ "$Xserver" = "vcxsrv" ] && note "GPU support of VcXsrv has some issues. If it does not work or crashes, try runx with option --xwin instead." ;; no) Xcommand="$Xcommand -nowgl -iglx" ; unset LIBGL_ALWAYS_INDIRECT ; export LIBGL_ALWAYS_INDIRECT ;; esac # Seamless or desktop mode [ "$Desktopmode" = "no" ] && Xcommand="$Xcommand -multiwindow" # Clipboard case $Shareclipboard in yes) Xcommand="$Xcommand -clipboard" ;; no) Xcommand="$Xcommand -noclipboard" ;; esac # Screensize [ "$Screensize" ] && Xcommand="$Xcommand -screen 0 '$Screensize'" case $Xauthentication in yes) case "$Xserver" in xwin) Xcommand="$Xcommand -auth '$Xcookie'" ;; vcxsrv) Xcommand="$Xcommand -auth '$(convertpath windows "$Xcookie")'" ;; esac ;; no) Xcommand="$Xcommand -ac" ;; esac # X server extensions Xcommand="$Xcommand \ +extension RANDR \ +extension RENDER \ +extension GLX \ +extension DOUBLE-BUFFER \ +extension DAMAGE \ +extension COMPOSITE \ -extension X-Resource \ -extension XTEST" verbose "Generated X command: $Xcommand" } setup_cookie() { # Generate X authentication cookie local Cookie Xauthsystem Xauthbin verbose "Cookie path: $Xcookie" # remove old cookies if no VcXsrv is running yet command -v tasklist.exe >/dev/null && { tasklist.exe | rmcr | grep -q -E 'vcxsrv.exe|XWin.exe' || { verbose "Removing old cookies." [ -e "$Xcookie" ] && rm "$Xcookie" } } # avoid missing file message of xauth touch "$Xcookie" # check for native xauth, otherwise use xauth.exe from VcXsrv Xauthbin="$(command -v xauth)" [ -z "$Xauthbin" ] && [ -n "$Vcxsrvbin" ] && { Xauthbin="${Vcxsrvbin%vcxsrv.exe}xauth.exe" } # describe xauth wrapper for 'ssh -X' in MSYS2, https://github.com/mviereck/runx/issues/7 [ -x "$Xauthbin" ] && [ "$Winsubsystem" = "MSYS2" ] && [ ! -x /usr/X11R6/bin/xauth ] && note "If you want to use 'ssh -X' in MSYS2, you might need to create an executeable script /usr/X11R6/bin/xauth with the content: #! /bin/bash $(escapestring "$Xauthbin") \"\$@\" " [ -x "$Xauthbin" ] && { grep -q "/usr/bin" <<< "$Xauthbin" && Xauthsystem="subsystem" || Xauthsystem="windows" # generate fresh cookie "$Xauthbin" -i -f "$(convertpath $Xauthsystem "$Xcookie")" add :$Newdisplaynumber . $(mcookie) # prepare cookie with localhost identification disabled by ffff. ffff means 'familiy wild' Cookie="$("$Xauthbin" -i -f "$(convertpath $Xauthsystem "$Xcookie")" nlist | sed -e 's/^..../ffff/')" printf "$Cookie" | "$Xauthbin" -i -f "$(convertpath $Xauthsystem "$Xcookie")" nmerge - verbose "Cookie: $("$Xauthbin" -v -f "$(convertpath $Xauthsystem "$Xcookie")" list)" } || { note "Command xauth not found. runx will try experimental code to bake an X cookie itself. Feedback on success or failure is appreciated at: https://github.com/mviereck/runx/issues/7 If it fails, you can use option --no-auth as a workaround." cookiebaker $Hostip:$Newdisplaynumber > $Xcookie.tmp cat $Xcookie.tmp >> "$Xcookie" rm $Xcookie.tmp } } start_xserver() { # Run X check_xserver generate_xcommand [ "$Xauthentication" = "yes" ] && setup_cookie case "$Verbose" in yes) Xcommand="$Xcommand 1>&2" ;; no) Xcommand="$Xcommand >/dev/null 2>&1" ;; esac # Run X in background? { [ "$Hostcommand" ] || [ "$Sourced" = "yes" ] ; } && Xcommand="$Xcommand & Xserverpid=\$!" # Store Windows PID list case $Winsubsystem in WSL1|WSL2) Tasklistold="$(tasklist.exe | rmcr | grep -i ${Xserver}.exe | awk '{print $2}')" ;; esac # Run X server eval $Xcommand # Find Windows PID of X server case $Winsubsystem in WSL1|WSL2) Tasklistnew="$(tasklist.exe | rmcr | grep -i ${Xserver}.exe | awk '{print $2}')" Xserverwinpid="$(echo "$Tasklistold $Tasklistnew" | sort | uniq -u | sed '/^$/d')" verbose "Windows PID of X server: $Xserverwinpid" ;; esac # Check for successfull startup [ "$Xserverpid" ] && { verbose "Linux PID of X server: $Xserverpid $(ps -p "$Xserverpid")" case "$Xserver" in xwin) for Waiting in 1 2 3 4 5 6 7 8 9 10; do [ -e "$(dirname "$Xwinbin")/../../tmp/.X11-unix/X$Newdisplaynumber" ] && Xready="yes" [ -e "$(dirname "$Xwinbin")/../tmp/.X11-unix/X$Newdisplaynumber" ] && Xready="yes" sleep 1 [ "$Xready" ] && break verbose "Waiting since ${Waiting}s for XWin to be ready." done [ "$Xready" ] && verbose "X server XWin ready after ${Waiting}s." || error "X server XWin not ready after ${Waiting}s." ;; vcxsrv) sleep 1 ;; esac } } ### special jobs cleanup() { # --cleanup: Terminate X servers, delete cookies. # kill all instances of VcXsrv and remove cookies MSYS2_ARG_CONV_EXCL='*' tasklist.exe | rmcr | grep -E 'vcxsrv.exe|XWin.exe' MSYS2_ARG_CONV_EXCL='*' taskkill.exe /F /PID vcxsrv.exe MSYS2_ARG_CONV_EXCL='*' taskkill.exe /F /PID xwin.exe # Remove cookies Newdisplaynumber=0 rm -v "$Xcookie" } ### main declare_variables() { # Default values Desktopmode="no" Screensize="" Shareclipboard="yes" Sharegpu="no" Verbose="no" Xauthentication="yes" Xenvfile="$HOME/.Xenv" # Terminal colors used for messages and --verbose=c Esc="$(printf '\033')" Colblue="${Esc}[35m" Colyellow="${Esc}[33m" Colgreen="${Esc}[32m" Colgreenbg="${Esc}[42m" Colred="${Esc}[31m" Colredbg="${Esc}[41m" Coluline="${Esc}[4m" Colnorm="${Esc}[0m" # Empty global vars needed later Exitcode="" Hostip="" Newdisplaynumber="" Sourced="" Vcxsrvbin="" Xserverwinpid="" Winsubmount="" Winsubpath="" Xcommand="" Xready="" Xserver="" Xserverbin="" Xserverpid="" Xwinbin="" } parse_options() { local Shortoptions Longoptions Parsererror [ "$0" = "$BASH_SOURCE" ] && Sourced="no" || Sourced="yes" verbose "Script is being sourced yes/no: $Sourced" Shortoptions="dghv" Longoptions="cleanup,clipboard::,desktop,display:,gpu,help,no-auth,size:,vcxsrv,verbose,version,xwin" Longoptions="$Longoptions,ip:" # experimental Parsedoptions="$(getopt --options $Shortoptions --longoptions $Longoptions --name "$0" -- "$@")" || error "Failed to parse options. See 'runx --help'." eval set -- "$Parsedoptions" verbose "$Parsedoptions" while [ $# -gt 0 ]; do case "${1:-}" in --cleanup) Cleanup="yes" ;; --clipboard) Clipboard="${2:-yes}"; shift ;; -d|--desktop) Desktopmode="yes" ;; --display) Newdisplaynumber="${2:-}" ; shift ;; -g|--gpu) Sharegpu="yes" ;; -h|--help) usage; Exitcode=0 ;; --ip) Hostip="${2:-}" ; shift ;; --no-auth) Xauthentication="no" ;; --size) Screensize="${2:-}" ; shift ;; -v|--verbose) Verbose="yes" ;; --vcxsrv) Xserver="vcxsrv" ;; --version) echo "runx version $Version"; Exitcode=0 ;; --xwin) Xserver="xwin" ;; --) shift; Hostcommand="$@"; break ;; *) error "Unknown option: ${1:-} Look at 'runx --help' for valid options." ;; esac shift done } finish() { verbose "Exitcode: ${1:-0}" case "$Sourced" in yes) unset -f usage finish unset -f error warning note verbose unset -f rmcr getwslpath escapestring convertpath unset -f check_display check_displayport cookiebaker makecookie strlenhex unset -f check_host check_dependency check_xserver setup_cookie generate_xcommand unset -f cleanup unset -f declare_variables parse_options main unset Desktopmode Screensize Shareclipboard Sharegpu Verbose Xauthentication unset Esc Colblue Colyellow Colgreen Colgreenbg Colred Colredbg Coluline Colnorm unset Exitcode Hostip Newdisplaynumber Sourced Vcxsrvbin Xserverwinpid Winsubmount Winsubpath Xcommand Xready Xserver Xserverbin Xserverpid Xwinbin ;; no) [ "$Xserverpid" ] && ps -p "$Xserverpid" >/dev/null 2>&1 && { verbose "Terminating X server $Xserver :$Newdisplaynumber. Linux PID: $Xserverpid" kill "$Xserverpid" wait "$Xserverpid" 2>/dev/null } [ "$Xserverwinpid" ] && tasklist.exe /FI "PID eq $Xserverwinpid" | grep -q "$Xserverwinpid" && { verbose "Terminating X server $Xserver :$Newdisplaynumber. Windows PID: $Xserverwinpid" taskkill.exe /F /PID $Xserverwinpid } exit "${1:-0}" ;; esac } main() { local Waiting Tasklistold Tasklistnew Xalreadyrunning # Dependency checks # # Windows commands ### check disabled for faster startup # for Line in cmd.exe ipconfig.exe powershell.exe tasklist.exe taskkill.exe; do # check_dependency "$Line" || error "Did not find Windows command: $Line" # done # Linux commands for Line in telnet shuf; do check_dependency "$Line" || error "Did not find Linux command: $Line Please install $Line" done Xalreadyrunning="no" [ -n "$Newdisplaynumber" ] && { check_displayport "$Newdisplaynumber" && { note "Option --display: Display number $Newdisplaynumber seems to be in use already. runx will try to provide access to it." Xalreadyrunning="yes" } } [ -z "$Newdisplaynumber" ] && { Newdisplaynumber="$(check_display)" || error "runx: Did not find a free display number." } note "Windows firewall settings can forbid application access to the X server. If no application window appears, but no obvious error is shown, please check your firewall settings. Compare: https://github.com/mviereck/x11docker/issues/108" # set DISPLAY and XAUTHORITY, output on stdout DISPLAY=$Hostip:$Newdisplaynumber Xcookie="$(convertpath subsystem "$(MSYS2_ARG_CONV_EXCL='*' cmd.exe /C "echo %userprofile%")" | rmcr)/Xauthority.runx" case "$Xauthentication" in yes) XAUTHORITY="$Xcookie" verbose "DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY" note "If you get application error messages like 'Cannot open display' or 'Invalid MIT-MAGIC-COOKIE', the X authentication cookie might be broken. You can remove old cookies and stop running X servers with: runx --cleanup" echo "DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY" echo "export DISPLAY=$DISPLAY export XAUTHORITY=$XAUTHORITY" > "$Xenvfile" ;; no) unset XAUTHORITY verbose "DISPLAY=$DISPLAY" warning "Option --no-auth: Cookie authentication is disabled! SECURITY RISC! Your X server $Xserver listens on TCP connections without any protection. Others could try to access your system through network connections. Please use option --no-auth for debugging only." echo "DISPLAY=$DISPLAY" echo "export DISPLAY=$DISPLAY" > "$Xenvfile" ;; esac export DISPLAY XAUTHORITY # run X [ "$Xalreadyrunning" = "no" ] && start_xserver # run host command [ -z "$Exitcode" ] && [ "$Hostcommand" ] && { verbose "Executing command: $Hostcommand" eval $Hostcommand } } trap finish EXIT declare_variables parse_options "$@" [ -z "$Exitcode" ] && check_host [ -z "$Exitcode" ] && [ "$Cleanup" = "yes" ] && cleanup && Exitcode="${Exitcode:-0}" [ -z "$Exitcode" ] && main finish "${Exitcode:-0}"