├── .github └── xFUNDING.yml ├── LICENSE ├── README.md └── runx /.github/xFUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [mviereck] 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019, 2020 by Martin Viereck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # runx - Run Linux GUI applications on MS Windows 2 | `runx` allows to easily run Linux GUI applications and desktops on MS Windows in Cygwin, MSYS2 or WSL. 3 | 4 | - This is especially of interest for [WSL](https://docs.microsoft.com/en-us/windows/wsl/about) in Windows 10 that does not support graphical applications on itself. 5 | 6 | *Background*: 7 | - `runx` starts an X server, either *VcXsrv* or *XWin*, to provide a graphical environment for Linux applications. 8 | - `runx` creates an authorization cookie to restrict access to the X server to allowed clients only. 9 | - `runx` runs the desired Linux GUI application with the credentials needed to access the X server. 10 | 11 | For similar functionality on native Linux systems use [x11docker](https://github.com/mviereck/x11docker) with options `--backend=host` or `--xonly`. 12 | 13 | ## Table of contents 14 | - [Linux environments on MS Windows](#linux-environments-on-ms-windows) 15 | - [Installation](#installation) 16 | - [Installation of X server](#installation-of-x-server) 17 | - [Installation in WSL](#installation-in-wsl) 18 | - [Installation in Cygwin](#installation-in-cygwin) 19 | - [Installation in MSYS2](#installation-in-msys2) 20 | - [GPU hardware acceleration](#gpu-hardware-acceleration) 21 | - [Usage examples](#Usage-examples) 22 | - [Providing X server in background](#providing-x-server-in-background) 23 | - [Output of `runx --help`](#output-of-runx---help) 24 | - [Screenshot](#screenshot) 25 | 26 | ## Linux environments on MS Windows 27 | `runx` can run in: 28 | - [WSL](https://docs.microsoft.com/en-us/windows/wsl/about): Windows subsystem for Linux. 29 | - [Cygwin](https://www.cygwin.com/): Cygwin is a large collection of Open Source tools which provide functionality similar to a Linux distribution on Windows. 30 | - [MSYS2](https://www.msys2.org/): MSYS2 is a software distro and building platform for Windows and serves as a base for [git for windows](https://gitforwindows.org/) and [MingW](http://www.mingw.org/). It is mainly used by developers. 31 | 32 | 33 | ## Installation 34 | Installation in general: 35 | - Install an X server, *VcXsrv* or *XWin*. 36 | - Copy `runx` into folder `/usr/local/bin` and make it executeable with `chmod +x /usr/local/bin/runx`. 37 | - Install Linux dependency `xauth` if available. 38 | - Install Linux dependency `telnet`. 39 | 40 | ### Installation of X server 41 | `runx` needs an [X server](https://en.wikipedia.org/wiki/X_Window_System). Install on MS Windows one or both of: 42 | - [VcXsrv](https://sourceforge.net/projects/vcxsrv/) to provide X server *VcXsrv*. 43 | - Easier to install than *XWin*. 44 | - [Cygwin](https://www.cygwin.com) with packages `xinit` and `xauth`. 45 | - This provides X server *XWin* for Cygwin and WSL. 46 | - *XWin* has a better `--gpu` support than *VcXsrv*. 47 | 48 | `runx` will automatically use *XWin* if available. You can specify the desired X server with options `--xwin` or `--vcxsrv`. 49 | 50 | ### Installation in WSL 51 | - Run the following commands in WSL/Ubuntu terminal to install `runx` and its dependencies: 52 | ``` 53 | sudo wget https://raw.githubusercontent.com/mviereck/runx/master/runx -O /usr/local/bin/runx 54 | sudo chmod +x /usr/local/bin/runx 55 | sudo apt update 56 | sudo apt install xauth 57 | ``` 58 | 59 | ### Installation in Cygwin 60 | - Run the Cygwin installer and install packages `xinit`, `xauth` and `wget`. 61 | - In Cygwin terminal run the commands: 62 | ``` 63 | wget https://raw.githubusercontent.com/mviereck/runx/master/runx -O /usr/local/bin/runx 64 | chmod +x /usr/local/bin/runx 65 | ``` 66 | 67 | ### Installation in MSYS2 68 | - In MSYS2 terminal run the commands: 69 | ``` 70 | mkdir /usr/local/bin 71 | wget https://raw.githubusercontent.com/mviereck/runx/master/runx -O /usr/local/bin/runx 72 | chmod +x /usr/local/bin/runx 73 | ``` 74 | - Constraint in MSYS2: `runx` only supports X server *VcXsrv*, but not *XWin*. 75 | 76 | ## GPU hardware acceleration 77 | `runx` supports GPU hardware accelerated graphics with option `--gpu`. 78 | - GPU access can cause issues with X server *VcXsrv*, especially with NVIDIA cards. For that reason GPU usage is disabled by default in `runx`. 79 | - If you encounter issues with option `--gpu`, try X server *XWin* instead of *VcXsrv*. 80 | 81 | ## Usage examples 82 | - File manager pcmanfm in WSL: 83 | - Installation: 84 | ``` 85 | sudo apt update 86 | sudo apt install pcmanfm 87 | ``` 88 | - Run: 89 | ``` 90 | runx -- pcmanfm 91 | ``` 92 | - Mate desktop environment in WSL: 93 | - Installation: 94 | ``` 95 | sudo apt update 96 | sudo apt install mate-desktop-environment 97 | ``` 98 | - Run: 99 | ``` 100 | runx --desktop --gpu -- mate-session 101 | ``` 102 | 103 | ### Providing X server in background 104 | You can make an entry in the file `~/.bashrc` to have an X server always available. 105 | Possible entry in `~/.bashrc`: 106 | ``` 107 | source /usr/local/bin/runx 108 | ``` 109 | In future runs of the terminal you can directly run Linux GUI applications, e.g.: 110 | ``` 111 | pcmanfm 112 | ``` 113 | 114 | ### Use an already running X server 115 | - If you specify option `--display`, runx will check if an X server is already running 116 | with the specified display number and will only provide the access credentials 117 | `DISPLAY` and `XAUTHORITY` instead of running an additional X server. 118 | - The access credentials are also stored in file `~/.Xenv`. 119 | You can make them available in a new terminal sourcing the file with `. ~/.Xenv` or `source ~/.Xenv`. 120 | 121 | ## Output of `runx --help` 122 | ``` 123 | runx - Run Linux GUI applications on MS Windows. 124 | Provides an X server on MS Windows in Cygwin, MSYS2 or WSL. 125 | 126 | Syntax: 127 | runx [OPTIONS] -- [COMMAND] 128 | 129 | Options: 130 | -h, --help Show this help. 131 | -d, --desktop Open a parent window for desktop environments. 132 | -g, --gpu Enable GPU hardware acceleration. Can fail 133 | with NVIDIA cards. Works best with XWin. 134 | --size WIDTHxHEIGHT Window size for option --desktop, e.g. 800x600. 135 | --vcxsrv Use X server VcXsrv. 136 | --xwin Use X server XWin. 137 | --clipboard [=yes|no] Enable clipboard sharing yes/no. Default: yes. 138 | --display N Use display number N for new X server. 139 | If the display number is already in use, runx will 140 | only provide the likely access credentials. 141 | --ip ADRESS IP adress to use. Default: First found 192.168.* 142 | --no-auth Disable X cookie authentication. Discouraged. 143 | --cleanup Stop all X servers and delete cookies. 144 | -v, --verbose Be verbose. 145 | 146 | Installation of runx in WSL: 147 | - Copy runx into /usr/local/bin/ 148 | - Make runx executeable: sudo chmod +x /usr/local/bin/runx 149 | - Install xauth: sudo apt update 150 | sudo apt install xauth 151 | 152 | Install an X server on Windows: 153 | runx supports two X servers: VcXsrv and XWin. Install at least one of them. 154 | - VcXsrv: Download and install from: 155 | https://sourceforge.net/projects/vcxsrv/ 156 | - XWin: Download and install Cygwin64 with packages: xinit xauth 157 | https://www.cygwin.com 158 | VcXsrv is easier to install. XWin provides a better GPU support. 159 | 160 | WSL, Cygwin: runx starts XWin if available, otherwise it starts VcXsrv. 161 | MSYS2: runx supports VcXsrv only. 162 | 163 | Usage: 164 | 165 | Example to directly run an application with runx: 166 | - Install file manager pcmanfm: sudo apt update 167 | sudo apt install pcmanfm 168 | - Run pcmanfm with: runx -- pcmanfm 169 | 170 | Example to run Mate desktop: 171 | - Install Mate desktop with: sudo apt install mate-desktop-environment 172 | - Run Mate desktop with: runx --desktop -- mate-session 173 | 174 | Example to get a Wayland environment: 175 | - Install Wayland compositor: sudo apt install weston 176 | - Run Weston with: XDG_RUNTIME_DIR=/tmp runx -- weston 177 | 178 | Providing an X server in background all the time: 179 | - Create an entry in ~/.bashrc: source /usr/local/bin/runx 180 | - In future terminal session you can directly run GUI commands. 181 | E.g. just type: 'pcmanfm' instead of 'runx -- pcmanfm'. 182 | - If you specify a display number with --display, runx will re-use 183 | a possibly already running X server with same display number 184 | and only provide the access credentials DISPLAY and XAUTHORITY. 185 | This allows to use the same X server across several terminals. 186 | 187 | runx stores the access credentials DISPLAY and XAUTHORITY in ~/.Xenv 188 | This allows sourcing the file for custom access setups. 189 | 190 | runx version 0.4.20 191 | Please report issues and get help at: https://github.com/mviereck/runx 192 | ``` 193 | 194 | ## Screenshot 195 | `runx` running Mate desktop on MS Windows: 196 | ![Mate Desktop on MS Windows](https://raw.githubusercontent.com/mviereck/x11docker/screenshots/screenshot-runx.png) 197 | 198 | -------------------------------------------------------------------------------- /runx: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # runx: Provide an X server in Cygwin, MSYS2 or WSL. 3 | 4 | Version="v0.4.21" 5 | 6 | usage() { # Usage information (--help) 7 | echo "runx - Run Linux GUI applications on MS Windows. 8 | Provides an X server on MS Windows in Cygwin, MSYS2 or WSL. 9 | 10 | Syntax: 11 | runx [OPTIONS] -- [COMMAND] 12 | 13 | Options: 14 | -h, --help Show this help. 15 | -d, --desktop Open a parent window for desktop environments. 16 | -g, --gpu Enable GPU hardware acceleration. Can fail 17 | with NVIDIA cards. Works best with XWin. 18 | --size WIDTHxHEIGHT Window size for option --desktop, e.g. 800x600. 19 | --vcxsrv Use X server VcXsrv. 20 | --xwin Use X server XWin. 21 | --clipboard [=yes|no] Enable clipboard sharing yes/no. Default: yes. 22 | --display N Use display number N for new X server. 23 | If the display number is already in use, runx will 24 | only provide the likely access credentials. 25 | --ip ADRESS IP adress to use. Default: First found 192.168.* 26 | --no-auth Disable X cookie authentication. Discouraged. 27 | --cleanup Stop all X servers and delete cookies. 28 | -v, --verbose Be verbose. 29 | 30 | Installation of runx in WSL: 31 | - Copy runx into /usr/local/bin/ 32 | - Make runx executeable: sudo chmod +x /usr/local/bin/runx 33 | - Install xauth: sudo apt update 34 | sudo apt install xauth 35 | 36 | Install an X server on Windows: 37 | runx supports two X servers: VcXsrv and XWin. Install at least one of them. 38 | - VcXsrv: Download and install from: 39 | https://sourceforge.net/projects/vcxsrv/ 40 | - XWin: Download and install Cygwin64 with packages: xinit xauth 41 | https://www.cygwin.com 42 | VcXsrv is easier to install. XWin provides a better GPU support. 43 | 44 | WSL, Cygwin: runx starts XWin if available, otherwise it starts VcXsrv. 45 | MSYS2: runx supports VcXsrv only. 46 | 47 | Usage: 48 | 49 | Example to directly run an application with runx: 50 | - Install file manager pcmanfm: sudo apt update 51 | sudo apt install pcmanfm 52 | - Run pcmanfm with: runx -- pcmanfm 53 | 54 | Example to run Mate desktop: 55 | - Install Mate desktop with: sudo apt install mate-desktop-environment 56 | - Run Mate desktop with: runx --desktop -- mate-session 57 | 58 | Example to get a Wayland environment: 59 | - Install Wayland compositor: sudo apt install weston 60 | - Run Weston with: XDG_RUNTIME_DIR=/tmp runx -- weston 61 | 62 | Providing an X server in background all the time: 63 | - Create an entry in ~/.bashrc: source /usr/local/bin/runx 64 | - In future terminal session you can directly run GUI commands. 65 | E.g. just type: 'pcmanfm' instead of 'runx -- pcmanfm'. 66 | - If you specify a display number with --display, runx will re-use 67 | a possibly already running X server with same display number 68 | and only provide the access credentials DISPLAY and XAUTHORITY. 69 | This allows to use the same X server across several terminals. 70 | 71 | runx stores the access credentials DISPLAY and XAUTHORITY in ~/.Xenv 72 | This allows sourcing the file for custom access setups. 73 | 74 | runx version $Version 75 | Please report issues and get help at: https://github.com/mviereck/runx 76 | " >&2 77 | } 78 | 79 | ### messages 80 | error() { # Show error message and exit 81 | echo -e "${Colredbg}runx ERROR:${Colnorm} $* 82 | " >&2 83 | Exitcode=1 84 | return 0 85 | } 86 | note() { # Show notice messages 87 | [ "$Sourced" = "no" ] || [ "$Verbose" = "yes" ] && echo "${Colgreen}runx note:${Colnorm} $* 88 | " >&2 89 | return 0 90 | } 91 | verbose() { # Show verbose message (--verbose) 92 | [ "$Verbose" = "yes" ] && note "$*" 93 | return 0 94 | } 95 | warning() { # Show warning messages 96 | echo "${Colyellow}runx WARNING:${Colnorm} $* 97 | " >&2 98 | return 0 99 | } 100 | 101 | ### general routines 102 | check_dependency() { # Check for single command 103 | [ "${1:-}" ] || return 1 104 | command -v "${1:-}" >/dev/null || { 105 | note "Command not found: ${1:-}" 106 | return 1 107 | } 108 | return 0 109 | } 110 | convertpath() { # Convert unix and windows pathes 111 | # $1: Mode: 112 | # windows echo Windows path - result: c:/path 113 | # unix echo unix path - result: /c/path 114 | # subsystem echo path within subsystem - result: /cygdrive/c/path or /path or /mnt/c/path 115 | # volume echo --volume compatible syntax - result: 'unixpath':'containerpath':rw (or ":ro") 116 | # container echo path of volume in container - result: /path 117 | # share echo path of $Sharefolder/file in container - result: /containerpath 118 | # $2: Path to convert. Arbitrary syntax, can be C:/path, /c/path, /cygdrive/c/path, /path 119 | # Can have suffix :rw or :ro. If none is given, return with :rw 120 | # $3: Optional for mode volume: containerpath 121 | 122 | local Mode Path Drive= Readwritemode 123 | 124 | Mode="${1:-}" 125 | Path="${2:-}" 126 | 127 | # check path for suffix :rw or :ro 128 | Readwritemode="$(echo "$Path" | rev | cut -c1-3 | rev)" 129 | [ "$(cut -c1 <<< "$Readwritemode")" = ":" ] && { 130 | Path="$(echo "$Path" | rev | cut -c4- | rev)" 131 | } || Readwritemode=":rw" 132 | 133 | # replace ~ with HOME 134 | Path="$(sed s%"~"%"${Hostuserhome:-${HOME:-}}"% <<< "$Path")" 135 | 136 | # share: Replace $Sharefolder with $Sharefoldercontainer 137 | [ "$Mode" = "share" ] && { 138 | [ -z "$Path" ] && echo "" && return 0 139 | case $X11dockermode in 140 | run) echo "${Sharefoldercontainer}${Path#$Sharefolder}" ;; 141 | exe) echo "$Path" ;; 142 | esac 143 | return 0 144 | } 145 | 146 | # not on Windows 147 | [ -z "$Winsubsystem" ] && { 148 | case $Mode in 149 | unix|subsystem) echo "$Path" ;; 150 | windows) warning "Nonsense path conversion $Mode: $Path" ; return 1 ;; 151 | volume) echo "'$Path':'${3:-$Path}'$Readwritemode" ;; 152 | container) echo "${3:-$Path}" ;; 153 | esac 154 | return 0 155 | } 156 | 157 | # replace \ with / 158 | Path="$(tr '\\' '/' <<< "$Path")" 159 | 160 | # remove possible already given mountpoint 161 | Path="${Path#$Winsubmount}" 162 | 163 | # Given format is /c/ 164 | [ "$(cut -c1,3 <<< "${Path}")" = "//" ] && { 165 | Drive="$(cut -c2 <<< "$Path")" 166 | Path="$(cut -c3- <<< "$Path")" 167 | Path="${Path:-"/"}" 168 | } 169 | 170 | # Given format is C:/ 171 | [ "$(cut -c2 <<< "$Path")" = ":" ] && { 172 | Drive="$(cut -c1 <<< "$Path")" 173 | Path="$(cut -c3- <<< "$Path")" 174 | } 175 | 176 | # change C to c 177 | Drive="${Drive,}" 178 | 179 | case $Winsubsystem in 180 | WSL1) 181 | [ -z "$Drive" ] && case $Mode in 182 | windows|unix|volume) 183 | debugnote "convertpath(): Request of WSL path: $Path" 184 | grep -q "$Cachefolder" <<< "$Path" || { 185 | [ "$Readwritemode" = ":rw" ] && warning "Request of Windows path to path within WSL: 186 | $Path 187 | Write access from Windows host to WSL files can damage the WSL file system. 188 | Read-only access is ok. 189 | Option --share: You can add :ro to the path to allow read-only access. 190 | Example: --share='$Path:ro'" 191 | } 192 | ;; 193 | esac 194 | ;; 195 | esac 196 | 197 | case $Drive in 198 | "") # Path points into subsystem 199 | Path="${Path#"$Winsubpath"}" 200 | Drive="$(cut -c2 <<<"$Winsubpath")" 201 | case $Mode in 202 | windows) echo "${Drive^}:$(cut -c3- <<<$Winsubpath)$Path" ;; 203 | unix) echo "$Winsubpath$Path" ;; 204 | subsystem) echo "$Path" ;; 205 | volume) 206 | case $Mobyvm in 207 | no) echo "'$Path':'${3:-$Path}'$Readwritemode" ;; 208 | yes) echo "'$Winsubpath$Path':'${3:-$Path}'$Readwritemode" ;; 209 | esac 210 | ;; 211 | container) echo "${3:-$Path}" ;; 212 | esac 213 | ;; 214 | *) # Path outside of subsystem 215 | case $Mode in 216 | windows) echo "${Drive^}:$Path" ;; 217 | unix) echo "/$Drive$Path" ;; 218 | subsystem) echo "$Winsubmount/$Drive$Path" ;; 219 | volume) echo "'/$Drive$Path':'${3:-/$Drive$Path}'$Readwritemode" ;; 220 | container) echo "${3:-/$Drive$Path}" ;; 221 | esac 222 | ;; 223 | esac 224 | 225 | return 0 226 | } 227 | escapestring() { # Escape special chars of $1 228 | # escape all characters except those described in [^a-zA-Z0-9,._+@=:/-] 229 | echo "${1:-}" | LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@=:/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/' 230 | } 231 | getwslpath() { # Get path to currently running WSL system 232 | # Fork from https://github.com/Microsoft/WSL/issues/2578#issuecomment-354010141 233 | local RUN_ID= BASE_PATH= 234 | 235 | RUN_ID="/tmp/$(mcookie)" 236 | 237 | # Mark our filesystem with a temporary file having an unique name. 238 | touch "${RUN_ID}" 239 | 240 | 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 241 | # Remove trailing whitespaces. 242 | BASEPATH="${BASEPATH%"${BASEPATH##*[![:space:]]}"}" 243 | # Build the path on WSL. 244 | BASEPATH="/mnt/${BASEPATH,}/rootfs" 245 | 246 | # Current WSL instance doesn't have an access to its mount from within 247 | # itself despite all others are available. That's the hacky way we're 248 | # using to determine current instance. 249 | # 250 | # The second of part of the condition is a fallback for a case if our 251 | # trick will stop working. For that we've created a temporary file with 252 | # an unique name and now seeking it among all WLSs. 253 | if ! ls "${BASEPATH}" > /dev/null 2>&1 || [ -f "${BASEPATH}${RUN_ID}" ]; then 254 | echo "${BASEPATH}" 255 | # You can create and simultaneously run multiple WSL instances, comment 256 | # out the "break", run this script within each one and it'll return only 257 | # single value. 258 | break 259 | fi 260 | done 261 | rm "${RUN_ID}" 262 | return 0 263 | } 264 | makecookie() { # Bake a cookie 265 | mcookie 2>/dev/null || echo $RANDOM$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM | cut -b1-32 266 | } 267 | rmcr() { # Remove carriage return to translate DOS/Windows newlines into UNIX newlines. 268 | # Convert stdin if $1 is empty. Otherwise convert file $1. 269 | case "${1:-}" in 270 | "") sed "s/$(printf "\r")//g" ;; 271 | *) sed -i "s/$(printf "\r")//g" "${1:-}" 272 | esac 273 | } 274 | strlenhex() { # Print byte length of string $1 as hex value 275 | printf '%x' "$(printf "%s" "${1:-}" | wc -c)" 276 | } 277 | 278 | ### setup 279 | check_display() { # Find unused display number 280 | local Displaynumber 281 | for Displaynumber in $(seq 999 | shuf); do 282 | check_displayport "$Displaynumber" || { 283 | echo "$Displaynumber" 284 | return 0 285 | } 286 | done 287 | return 1 288 | } 289 | check_displayport() { # Return 0 if display number $1 is in use 290 | timeout 1 bash -c "/dev/null 2>&1" && return 0 || return 1 291 | } 292 | check_host() { # Check host environment 293 | # Check for MS Windows subsystem 294 | command -v cygcheck.exe >/dev/null && { 295 | cygcheck.exe -V | rmcr | grep -q "(cygwin)" && Winsubsystem="CYGWIN" 296 | cygcheck.exe -V | rmcr | grep -q "(msys)" && Winsubsystem="MSYS2" 297 | } 298 | uname -r | grep -q Microsoft && Winsubsystem="WSL1" 299 | uname -r | grep -q microsoft && Winsubsystem="WSL2" 300 | case "$Winsubsystem" in 301 | WSL1|WSL2|CYGWIN|MSYS2) ;; 302 | *) error "runx is designed to run on MS Windows in WSL, Cygwin or MSYS2. 303 | Did not detect WSL, Cygwin or MSYS2." ;; 304 | esac 305 | 306 | case $Winsubsystem in 307 | MSYS2|CYGWIN) 308 | Winsubmount="$(cygpath.exe -u "c:/" | rmcr | sed s%/c/%%)" 309 | Winsubpath="$(convertpath unix "$(cygpath.exe -w "/" | rmcr)" )" 310 | ;; 311 | WSL1|WSL2) 312 | command -v "/mnt/c/Windows/System32/cmd.exe" >/dev/null && Winsubmount="/mnt" 313 | command -v "/c/Windows/System32/cmd.exe" >/dev/null && Winsubmount="" 314 | grep -q "Windows" <<< "${PATH:-}" || export PATH="${PATH:-}:$Winsubmount/c/Windows/System32:$Winsubmount/c/Windows/System32/WindowsPowerShell/v1.0" # can miss after sudo in WSL 315 | command -v "$Winsubmount/c/Windows/System32/cmd.exe" >/dev/null || error "$Winsubsystem: Could not find cmd.exe 316 | in /mnt/c/Windows/System32 or /c/Windows/System32. 317 | Do you have a different path to your Windows system partition?" 318 | Winsubpath="$(convertpath unix "$(getwslpath)")" 319 | ;; 320 | esac 321 | Winsubmount="${Winsubmount%/}" 322 | Winsubpath="${Winsubpath%/}" 323 | 324 | # Get IP of Windows host 325 | [ "$Hostip" = "localhost" ] && Hostip="127.0.0.1" 326 | [ "$Hostip" ] || [ "$Winsubsystem" = "WSL2" ] && Hostip="$(ip route list default | awk '{print $3}')" 327 | [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '192\.168\.[0-9]*\.[0-9]*' | head -n1 )" 328 | [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '10\.0\.[0-9]*\.[0-9]*' | head -n1 )" 329 | [ "$Hostip" ] || Hostip="$(ipconfig.exe | rmcr | grep 'IPv4' | grep -o '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*' | head -n1 )" 330 | 331 | verbose " 332 | Subsystem: $Winsubsystem 333 | Path to subsystem: $Winsubpath 334 | Mount path: $Winsubmount 335 | IP address $Hostip" 336 | 337 | return 0 338 | } 339 | check_xserver() { # Check for Xwin and VcXsrv 340 | local Line 341 | 342 | [ "$Winsubsystem" = "MSYS2" ] && [ "$Xserver" = "xwin" ] && { 343 | note "runx in MSYS2 does not support --xwin. 344 | Fallback: Enabling option --vcxsrv." 345 | Xserver="vcxsrv" 346 | } 347 | 348 | # X server VcXsrv or XWin 349 | Vcxsrvbin="$(command -v vcxsrv.exe)" 350 | [ "$Vcxsrvbin" ] || Vcxsrvbin="$(command -v "$(convertpath subsystem "C:/Program Files/VcXsrv/vcxsrv.exe")")" 351 | [ "$Vcxsrvbin" ] || Vcxsrvbin="$(command -v "$(convertpath subsystem "C:/Program Files/VcXsrv (x86)/vcxsrv.exe")")" 352 | Xwinbin="$(command -v XWin)" || Xwinbin="$(command -v XWin.exe)" 353 | [ -z "$Xwinbin" ] && case $Winsubsystem in 354 | WSL1|WSL2) 355 | # search for XWin 356 | for Drive in $Winsubmount/*; do 357 | Xwinbin="$(command -v "$Drive/cygwin64/bin/XWin.exe")" 358 | [ "$Xwinbin" ] && break 359 | Xwinbin="$(command -v "$Drive/cygwin32/bin/XWin.exe")" 360 | [ "$Xwinbin" ] && break 361 | done 362 | ;; 363 | esac 364 | verbose "Found X servers: 365 | $Vcxsrvbin 366 | $Xwinbin" 367 | 368 | [ -z "$Xserver" ] && [ -n "$Xwinbin" ] && Xserver="xwin" 369 | [ -z "$Xserver" ] && Xserver="vcxsrv" 370 | [ "$Winsubsystem" = "MSYS2" ] && Xserver="vcxsrv" 371 | 372 | case "$Xserver" in 373 | vcxsrv) Xserverbin="$Vcxsrvbin" ;; 374 | xwin) Xserverbin="$Xwinbin" ;; 375 | esac 376 | 377 | [ -z "$Xserverbin" ] && { 378 | case $Winsubsystem in 379 | WSL1|WSL2|CYGWIN) error "No X server found. 380 | Please either install X server VcXsrv: 381 | https://sourceforge.net/projects/vcxsrv 382 | or install Cygwin with packages xinit and xauth to provide XWin: 383 | https://www.cygwin.com/" ;; 384 | MSYS2) error "No X server found for MSYS2. 385 | Please install X server VcXsrv: 386 | https://sourceforge.net/projects/vcxsrv" ;; 387 | esac 388 | } 389 | return 0 390 | } 391 | cookiebaker() { # Create an X cookie without xauth 392 | # $1 DISPLAY 393 | # Write directly to file, bash cannot store nullbytes in a string. 394 | # Based on https://stackoverflow.com/questions/70932880/what-is-the-internal-format-of-xauthority-file 395 | # and chapter 6.2.5 in https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Desktop-generic/LSB-Desktop-generic/libx11-ddefs.html 396 | 397 | local Display 398 | local Address Addresslength Displaynumber Displaynumberlength Data Part Code 399 | 400 | Display="${1:-$DISPLAY}" 401 | 402 | Address="$(printf "%s" "$Display" | cut -d: -f1)" 403 | case "$Address" in 404 | "") 405 | Address="$(hostname)/unix" 406 | Addresslength="$(strlenhex "$Address")" 407 | ;; 408 | *.*.*.*) 409 | Data="$Address" 410 | Address="" 411 | while [ "$(printf "%s" "$Data" | wc -c)" -gt 0 ]; do 412 | Part="$( printf "%s" "$Data" | cut -d. -f1)" 413 | Address="${Address}\x$(printf "%x" "$Part")" 414 | Data="$( printf "%s" "$Data" | cut -s -d. -f2-)" 415 | done 416 | Addresslength="4" 417 | ;; 418 | *) 419 | Addresslength="$(strlenhex "$Address")" 420 | ;; 421 | esac 422 | 423 | Displaynumber="$(printf "%s" "$Display" | cut -d: -f2)" 424 | Displaynumber="$(printf "%s" "$Displaynumber" | cut -d. -f1)" 425 | Displaynumberlength="$(strlenhex "$Displaynumber")" 426 | 427 | Data="$(makecookie)" 428 | while [ "$(printf "%s" "$Data" | wc -c)" -gt 0 ]; do 429 | Part="$( printf "%s" "$Data" | cut -c1-2)" 430 | Code="${Code}\x$Part" 431 | Data="$( printf "%s" "$Data" | cut -c3-)" 432 | done 433 | 434 | awk "BEGIN{ 435 | printf \"\xFF\xFF\" 436 | printf \"\x00\x${Addresslength}\" 437 | printf \"${Address}\" 438 | printf \"\x00\x${Displaynumberlength}\" 439 | printf \"${Displaynumber}\" 440 | printf \"\x00\x12\" 441 | printf \"MIT-MAGIC-COOKIE-1\" 442 | printf \"\x00\x10\" 443 | printf \"${Code}\" 444 | }" 445 | } 446 | generate_xcommand() { # Generate command to start X server VcXsrv 447 | 448 | Xcommand="$(escapestring "$Xserverbin") :$Newdisplaynumber -listen tcp -retro -lesspointer" 449 | 450 | # GPU hardware acceleration 451 | case $Sharegpu in 452 | yes) Xcommand="$Xcommand -wgl +iglx" ; export LIBGL_ALWAYS_INDIRECT=1 453 | [ "$Xserver" = "vcxsrv" ] && note "GPU support of VcXsrv has some issues. 454 | If it does not work or crashes, try runx with option --xwin instead." 455 | ;; 456 | no) Xcommand="$Xcommand -nowgl -iglx" ; unset LIBGL_ALWAYS_INDIRECT ; export LIBGL_ALWAYS_INDIRECT ;; 457 | esac 458 | 459 | # Seamless or desktop mode 460 | [ "$Desktopmode" = "no" ] && Xcommand="$Xcommand -multiwindow" 461 | 462 | # Clipboard 463 | case $Shareclipboard in 464 | yes) Xcommand="$Xcommand -clipboard" ;; 465 | no) Xcommand="$Xcommand -noclipboard" ;; 466 | esac 467 | 468 | # Screensize 469 | [ "$Screensize" ] && Xcommand="$Xcommand -screen 0 '$Screensize'" 470 | 471 | case $Xauthentication in 472 | yes) 473 | case "$Xserver" in 474 | xwin) Xcommand="$Xcommand -auth '$Xcookie'" ;; 475 | vcxsrv) Xcommand="$Xcommand -auth '$(convertpath windows "$Xcookie")'" ;; 476 | esac ;; 477 | no) Xcommand="$Xcommand -ac" ;; 478 | esac 479 | 480 | # X server extensions 481 | Xcommand="$Xcommand \ 482 | +extension RANDR \ 483 | +extension RENDER \ 484 | +extension GLX \ 485 | +extension DOUBLE-BUFFER \ 486 | +extension DAMAGE \ 487 | +extension COMPOSITE \ 488 | -extension X-Resource \ 489 | -extension XTEST" 490 | 491 | verbose "Generated X command: 492 | $Xcommand" 493 | } 494 | setup_cookie() { # Generate X authentication cookie 495 | local Cookie Xauthsystem Xauthbin 496 | 497 | verbose "Cookie path: $Xcookie" 498 | 499 | # remove old cookies if no VcXsrv is running yet 500 | command -v tasklist.exe >/dev/null && { 501 | tasklist.exe | rmcr | grep -q -E 'vcxsrv.exe|XWin.exe' || { 502 | verbose "Removing old cookies." 503 | [ -e "$Xcookie" ] && rm "$Xcookie" 504 | } 505 | } 506 | 507 | # avoid missing file message of xauth 508 | touch "$Xcookie" 509 | 510 | # check for native xauth, otherwise use xauth.exe from VcXsrv 511 | Xauthbin="$(command -v xauth)" 512 | [ -z "$Xauthbin" ] && [ -n "$Vcxsrvbin" ] && { 513 | Xauthbin="${Vcxsrvbin%vcxsrv.exe}xauth.exe" 514 | } 515 | 516 | # describe xauth wrapper for 'ssh -X' in MSYS2, https://github.com/mviereck/runx/issues/7 517 | [ -x "$Xauthbin" ] && [ "$Winsubsystem" = "MSYS2" ] && [ ! -x /usr/X11R6/bin/xauth ] && note "If you want to use 'ssh -X' in MSYS2, you might need 518 | to create an executeable script /usr/X11R6/bin/xauth with the content: 519 | #! /bin/bash 520 | $(escapestring "$Xauthbin") \"\$@\" 521 | " 522 | 523 | [ -x "$Xauthbin" ] && { 524 | grep -q "/usr/bin" <<< "$Xauthbin" && Xauthsystem="subsystem" || Xauthsystem="windows" 525 | # generate fresh cookie 526 | "$Xauthbin" -i -f "$(convertpath $Xauthsystem "$Xcookie")" add :$Newdisplaynumber . $(mcookie) 527 | # prepare cookie with localhost identification disabled by ffff. ffff means 'familiy wild' 528 | Cookie="$("$Xauthbin" -i -f "$(convertpath $Xauthsystem "$Xcookie")" nlist | sed -e 's/^..../ffff/')" 529 | printf "$Cookie" | "$Xauthbin" -i -f "$(convertpath $Xauthsystem "$Xcookie")" nmerge - 530 | verbose "Cookie: $("$Xauthbin" -v -f "$(convertpath $Xauthsystem "$Xcookie")" list)" 531 | } || { 532 | note "Command xauth not found. 533 | runx will try experimental code to bake an X cookie itself. 534 | Feedback on success or failure is appreciated at: 535 | https://github.com/mviereck/runx/issues/7 536 | If it fails, you can use option --no-auth as a workaround." 537 | cookiebaker $Hostip:$Newdisplaynumber > $Xcookie.tmp 538 | cat $Xcookie.tmp >> "$Xcookie" 539 | rm $Xcookie.tmp 540 | } 541 | } 542 | start_xserver() { # Run X 543 | check_xserver 544 | generate_xcommand 545 | [ "$Xauthentication" = "yes" ] && setup_cookie 546 | 547 | case "$Verbose" in 548 | yes) Xcommand="$Xcommand 1>&2" ;; 549 | no) Xcommand="$Xcommand >/dev/null 2>&1" ;; 550 | esac 551 | 552 | # Run X in background? 553 | { [ "$Hostcommand" ] || [ "$Sourced" = "yes" ] ; } && Xcommand="$Xcommand & Xserverpid=\$!" 554 | 555 | # Store Windows PID list 556 | case $Winsubsystem in 557 | WSL1|WSL2) Tasklistold="$(tasklist.exe | rmcr | grep -i ${Xserver}.exe | awk '{print $2}')" ;; 558 | esac 559 | 560 | # Run X server 561 | eval $Xcommand 562 | 563 | # Find Windows PID of X server 564 | case $Winsubsystem in 565 | WSL1|WSL2) 566 | Tasklistnew="$(tasklist.exe | rmcr | grep -i ${Xserver}.exe | awk '{print $2}')" 567 | Xserverwinpid="$(echo "$Tasklistold 568 | $Tasklistnew" | sort | uniq -u | sed '/^$/d')" 569 | verbose "Windows PID of X server: $Xserverwinpid" 570 | ;; 571 | esac 572 | 573 | # Check for successfull startup 574 | [ "$Xserverpid" ] && { 575 | verbose "Linux PID of X server: $Xserverpid 576 | $(ps -p "$Xserverpid")" 577 | case "$Xserver" in 578 | xwin) 579 | for Waiting in 1 2 3 4 5 6 7 8 9 10; do 580 | [ -e "$(dirname "$Xwinbin")/../../tmp/.X11-unix/X$Newdisplaynumber" ] && Xready="yes" 581 | [ -e "$(dirname "$Xwinbin")/../tmp/.X11-unix/X$Newdisplaynumber" ] && Xready="yes" 582 | sleep 1 583 | [ "$Xready" ] && break 584 | verbose "Waiting since ${Waiting}s for XWin to be ready." 585 | done 586 | [ "$Xready" ] && verbose "X server XWin ready after ${Waiting}s." || error "X server XWin not ready after ${Waiting}s." 587 | ;; 588 | vcxsrv) sleep 1 ;; 589 | esac 590 | } 591 | } 592 | 593 | ### special jobs 594 | cleanup() { # --cleanup: Terminate X servers, delete cookies. 595 | # kill all instances of VcXsrv and remove cookies 596 | MSYS2_ARG_CONV_EXCL='*' tasklist.exe | rmcr | grep -E 'vcxsrv.exe|XWin.exe' 597 | MSYS2_ARG_CONV_EXCL='*' taskkill.exe /F /PID vcxsrv.exe 598 | MSYS2_ARG_CONV_EXCL='*' taskkill.exe /F /PID xwin.exe 599 | # Remove cookies 600 | Newdisplaynumber=0 601 | rm -v "$Xcookie" 602 | } 603 | 604 | ### main 605 | declare_variables() { 606 | # Default values 607 | Desktopmode="no" 608 | Screensize="" 609 | Shareclipboard="yes" 610 | Sharegpu="no" 611 | Verbose="no" 612 | Xauthentication="yes" 613 | Xenvfile="$HOME/.Xenv" 614 | 615 | # Terminal colors used for messages and --verbose=c 616 | Esc="$(printf '\033')" 617 | Colblue="${Esc}[35m" 618 | Colyellow="${Esc}[33m" 619 | Colgreen="${Esc}[32m" 620 | Colgreenbg="${Esc}[42m" 621 | Colred="${Esc}[31m" 622 | Colredbg="${Esc}[41m" 623 | Coluline="${Esc}[4m" 624 | Colnorm="${Esc}[0m" 625 | 626 | # Empty global vars needed later 627 | Exitcode="" 628 | Hostip="" 629 | Newdisplaynumber="" 630 | Sourced="" 631 | Vcxsrvbin="" 632 | Xserverwinpid="" 633 | Winsubmount="" 634 | Winsubpath="" 635 | Xcommand="" 636 | Xready="" 637 | Xserver="" 638 | Xserverbin="" 639 | Xserverpid="" 640 | Xwinbin="" 641 | } 642 | parse_options() { 643 | local Shortoptions Longoptions Parsererror 644 | 645 | [ "$0" = "$BASH_SOURCE" ] && Sourced="no" || Sourced="yes" 646 | verbose "Script is being sourced yes/no: $Sourced" 647 | 648 | Shortoptions="dghv" 649 | Longoptions="cleanup,clipboard::,desktop,display:,gpu,help,no-auth,size:,vcxsrv,verbose,version,xwin" 650 | Longoptions="$Longoptions,ip:" # experimental 651 | 652 | Parsedoptions="$(getopt --options $Shortoptions --longoptions $Longoptions --name "$0" -- "$@")" || error "Failed to parse options. See 'runx --help'." 653 | eval set -- "$Parsedoptions" 654 | verbose "$Parsedoptions" 655 | 656 | while [ $# -gt 0 ]; do 657 | case "${1:-}" in 658 | --cleanup) Cleanup="yes" ;; 659 | --clipboard) Clipboard="${2:-yes}"; shift ;; 660 | -d|--desktop) Desktopmode="yes" ;; 661 | --display) Newdisplaynumber="${2:-}" ; shift ;; 662 | -g|--gpu) Sharegpu="yes" ;; 663 | -h|--help) usage; Exitcode=0 ;; 664 | --ip) Hostip="${2:-}" ; shift ;; 665 | --no-auth) Xauthentication="no" ;; 666 | --size) Screensize="${2:-}" ; shift ;; 667 | -v|--verbose) Verbose="yes" ;; 668 | --vcxsrv) Xserver="vcxsrv" ;; 669 | --version) echo "runx version $Version"; Exitcode=0 ;; 670 | --xwin) Xserver="xwin" ;; 671 | --) shift; Hostcommand="$@"; break ;; 672 | *) error "Unknown option: ${1:-} 673 | Look at 'runx --help' for valid options." ;; 674 | esac 675 | shift 676 | done 677 | } 678 | finish() { 679 | verbose "Exitcode: ${1:-0}" 680 | case "$Sourced" in 681 | yes) 682 | unset -f usage finish 683 | unset -f error warning note verbose 684 | unset -f rmcr getwslpath escapestring convertpath 685 | unset -f check_display check_displayport cookiebaker makecookie strlenhex 686 | unset -f check_host check_dependency check_xserver setup_cookie generate_xcommand 687 | unset -f cleanup 688 | unset -f declare_variables parse_options main 689 | 690 | unset Desktopmode Screensize Shareclipboard Sharegpu Verbose Xauthentication 691 | unset Esc Colblue Colyellow Colgreen Colgreenbg Colred Colredbg Coluline Colnorm 692 | 693 | unset Exitcode Hostip Newdisplaynumber Sourced Vcxsrvbin Xserverwinpid Winsubmount Winsubpath Xcommand Xready Xserver Xserverbin Xserverpid Xwinbin 694 | ;; 695 | no) 696 | [ "$Xserverpid" ] && ps -p "$Xserverpid" >/dev/null 2>&1 && { 697 | verbose "Terminating X server $Xserver :$Newdisplaynumber. Linux PID: $Xserverpid" 698 | kill "$Xserverpid" 699 | wait "$Xserverpid" 2>/dev/null 700 | } 701 | [ "$Xserverwinpid" ] && tasklist.exe /FI "PID eq $Xserverwinpid" | grep -q "$Xserverwinpid" && { 702 | verbose "Terminating X server $Xserver :$Newdisplaynumber. Windows PID: $Xserverwinpid" 703 | taskkill.exe /F /PID $Xserverwinpid 704 | } 705 | exit "${1:-0}" 706 | ;; 707 | esac 708 | } 709 | main() { 710 | local Waiting Tasklistold Tasklistnew Xalreadyrunning 711 | 712 | # Dependency checks 713 | # # Windows commands ### check disabled for faster startup 714 | # for Line in cmd.exe ipconfig.exe powershell.exe tasklist.exe taskkill.exe; do 715 | # check_dependency "$Line" || error "Did not find Windows command: $Line" 716 | # done 717 | # Linux commands 718 | for Line in telnet shuf; do 719 | check_dependency "$Line" || error "Did not find Linux command: $Line 720 | Please install $Line" 721 | done 722 | 723 | Xalreadyrunning="no" 724 | [ -n "$Newdisplaynumber" ] && { 725 | check_displayport "$Newdisplaynumber" && { 726 | note "Option --display: Display number $Newdisplaynumber seems 727 | to be in use already. runx will try to provide access to it." 728 | Xalreadyrunning="yes" 729 | } 730 | } 731 | [ -z "$Newdisplaynumber" ] && { 732 | Newdisplaynumber="$(check_display)" || error "runx: Did not find a free display number." 733 | } 734 | 735 | note "Windows firewall settings can forbid application access 736 | to the X server. If no application window appears, but no obvious error 737 | is shown, please check your firewall settings. 738 | Compare: https://github.com/mviereck/x11docker/issues/108" 739 | 740 | # set DISPLAY and XAUTHORITY, output on stdout 741 | DISPLAY=$Hostip:$Newdisplaynumber 742 | Xcookie="$(convertpath subsystem "$(MSYS2_ARG_CONV_EXCL='*' cmd.exe /C "echo %userprofile%")" | rmcr)/Xauthority.runx" 743 | case "$Xauthentication" in 744 | yes) 745 | XAUTHORITY="$Xcookie" 746 | verbose "DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY" 747 | note "If you get application error messages like 'Cannot open display' 748 | or 'Invalid MIT-MAGIC-COOKIE', the X authentication cookie might be broken. 749 | You can remove old cookies and stop running X servers with: runx --cleanup" 750 | echo "DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY" 751 | echo "export DISPLAY=$DISPLAY 752 | export XAUTHORITY=$XAUTHORITY" > "$Xenvfile" 753 | ;; 754 | no) 755 | unset XAUTHORITY 756 | verbose "DISPLAY=$DISPLAY" 757 | warning "Option --no-auth: Cookie authentication is disabled! 758 | SECURITY RISC! 759 | Your X server $Xserver listens on TCP connections without any protection. 760 | Others could try to access your system through network connections. 761 | Please use option --no-auth for debugging only." 762 | echo "DISPLAY=$DISPLAY" 763 | echo "export DISPLAY=$DISPLAY" > "$Xenvfile" 764 | ;; 765 | esac 766 | export DISPLAY XAUTHORITY 767 | 768 | # run X 769 | [ "$Xalreadyrunning" = "no" ] && start_xserver 770 | 771 | # run host command 772 | [ -z "$Exitcode" ] && [ "$Hostcommand" ] && { 773 | verbose "Executing command: 774 | $Hostcommand" 775 | eval $Hostcommand 776 | } 777 | } 778 | 779 | trap finish EXIT 780 | declare_variables 781 | parse_options "$@" 782 | [ -z "$Exitcode" ] && check_host 783 | [ -z "$Exitcode" ] && [ "$Cleanup" = "yes" ] && cleanup && Exitcode="${Exitcode:-0}" 784 | [ -z "$Exitcode" ] && main 785 | finish "${Exitcode:-0}" 786 | --------------------------------------------------------------------------------