Windows Subsystem for Linux or WSL is the successor to the Unix subsystem present to previous Windows versions (except 8.1) introduced in Windows 10. It was developed by Microsoft and Canonical, the corporation behind Ubuntu.
There’re two architecturally different WSL versions. WSL1 is a thin-layer atop NT translating Linux system calls to Win32. Specifically Linux programs are run as isolated minimal processes. It’s similar but differs from the POSIX subsystem present in initial Windows NT versions. Also it differs from Cygwin, which creates a Unix-like environments on Windows. WSL1 isn’t doing any emulation or virtualization and directly uses the host file system and some hardware parts.
Though conceptually interesting, it has some limitations. Specifically there’re incompatibilities and anything requiring a real kernel cannot run. This is where WSL2 comes in. WSL2 is a lightweight Hyper-V-based VM running an actual Linux kernel image. Rather using the host file system, it uses an extendable virtual hard disk image. This approach is similar to now unmaintained coLinux.
In this post I’ll showcase how Linux GUI programs can run on Windows by utilizing the WSL starting from WSL installation itself. There’re various guides around the web but none is CLI-focused meaning you’ve to follow a Windows workflow of next-next-finish rather Linux workflow of running a bunch of commands and be done with. That said, this is the same approach widely known (specifically this post is based on Win Dev AppConsult for graphics and x410.dev for sound).
Note that all Windows command-lines, on which the commands mentioned will be used, require, except if mentioned otherwise, elevated privileges (being run as administrator).
Installation
From Microsoft’s docs and Canonical’s page on WSL, WSL1 can be enabled with following command (run in cmd or PowerShell)
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
WSL2 can be enabled with the following command, after running the previous
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
If PowerShell is used, thanks to DISM cmdlet which can be used to
perform same functions with dism.exe
, for WSL1 the following command
can be run instead
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -All -NoRestart
and, for WSL2 respectively, after running the previous
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -All -NoRestart
After running them restart the system. The restart is required as
some of the infrastructure can only be loaded during boot. On previous
commands if NoRestart
argument is skipped you’ll be prompted to
restart after the command has finished successfully. After restart,
install the WSL2 kernel update with the following in PowerShell
Invoke-WebRequest -Uri https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi -OutFile wsl_update_x64.msi -UseBasicParsing
msiexec /i wsl_update_x64.msi
Remove-Item wsl_update_x64.msi
Then WSL2 can be set as default running
wsl --set-default-version 2
Linux distros can be installed on WSL either from Store or downloading offline packages or importing a rootfs. The links for the packages of all officially available distros can be found on Microsoft’s docs. For downloading and installing Debian, run the following in PowerShell
$Url = "https://aka.ms/wsl-debian-gnulinux"
$File = "debian.appx"
Invoke-WebRequest -Uri $Url -OutFile $File -UseBasicParsing
Add-AppxPackage $File
Remove-Item $File
To boot up on the distro run either $distro
(in previous case
debian
) or bash
. After that set up username, password, update, and
upgrade as in a normal Linux installation.
Graphics
The essential software is an X server running on the Windows environment. My recommendation is the Xorg-based VcXsrv. The installer can be downloaded from the project’s page or installed directly using a package manager. The ones having it are the third-party Chocolatey and first-party winget. The commands are respectively
choco install vcxsrv
and
winget install vcxsrv
Running the program will show a wizard to configure the server. The recommended settings are “multiple windows” display, “display number” set to 0, clipboard enabled, native OpenGL, and for WSL2, “disable access control” is required. The “multiple windows” allows Linux graphical programs to appear side by side with normal Windows programs rather be a big window in which the programs run.
For simplicity a shortcut can be made with location set to the following.
"C:\Program Files\VcXsrv\vcxsrv.exe" :0 -multiwindow -clipboard -wgl -ac
On first run, allow private network access only. This will add a block rule for public network access. If WSL2 is used, the public TCP rule has to be changed to allow for WSL subnet.
netsh advfirewall firewall set rule name="VcXsrv windows xserver" profile=Public protocol=TCP new action=Allow remoteip=172.16.0.0/12
Now, moving on to the Linux shell. Programs can’t connect to the running
X server before setting up the DISPLAY
environmental variable. Rather
running the following commands on every login manually, they can be
added to ~/.bashrc
.
If WSL1 is used, add the following.
export DISPLAY=:0
If WSL2 is used, add the following. The reason this is more complicated is that WSL2 and the Windows host are not in the same network device.
export DISPLAY=$(awk '/nameserver/{print $2}' /etc/resolv.conf):0
And, if native OpenGL is checked (or -wgl
argument is used)
export LIBGL_ALWAYS_INDIRECT=1
Fixing scaling to HDPI displays can be done with
disp_scaling=$(wslsys -S -s)
export GDK_SCALE=$disp_scaling
export QT_SCALE_FACTOR=$disp_scaling
Test the X forwarding configuration by running xeyes
installed with
$ sudo apt install x11-apps
Rather opening the shell to launch something, a shortcut can made. For
example in order to run xeyes
make a shortcut with following as
location.
wsl.exe bash -i -c "xeyes"
This leaves an open command-line window. Instead of shortcut the
following vbs file can be made, where command_name
should be set to
whatever program someone wants to open.
Set objShell = CreateObject("Wscript.Shell")
Dim sh
Dim command_name
sh = "%comspec% /c wsl.exe bash -i -c "
command_name = "xeyes"
objShell.Run sh & command_name, 0, false
An easier way is using wslusc
part of wslu, a collection of
utilities for WSL installable on Linux distros running on it. Is is
pre-installed in latest Ubuntu, but not any other distro. From their
project page on GitHub, to install it on Debian run
sudo apt install wget gnupg2 apt-transport-https
wget -O - https://access.patrickwu.space/wslu/public.asc | \
sudo apt-key add -
echo "deb https://access.patrickwu.space/wslu/debian buster main" | \
sudo tee -a /etc/apt/sources.list
sudo apt update
sudo apt install wslu
Then a shortcut for COMMAND
to user’s Desktop on host can be made with
the following
$ wslusc -g COMMAND
where -g
is required for GUI programs. After having it run once,
making new shortcuts boils down to making a shortcut with the following
location, replacing xeyes
with whatever program one wants.
wscript.exe C:\Users\user\wslu\runHidden.vbs debian.exe run /usr/share/wslu/wslusc-helper.sh "xeyes"
Sound
Similarly to graphics, traditional GNU/Linux environments use a client-server model for audio as well in the form of PulseAudio. An old PulseAudio version can either be downloaded from freedesktop’s PulseAudio page using (non-admin) PowerShell
$Site = "https://bosmans.ch/pulseaudio"
$Pkg = "pulseaudio-1.1.zip"
Invoke-WebRequest -Uri $Site/$Pkg -OutFile $Pkg -UseBasicParsing
Expand-Archive -LiteralPath $Pkg -DestinationPath C:\pulse
Remove-Item $Pkg
or installed with Chocolatey
choco install pulseaudio
A more recent version can be downloaded from X2go’s site.
$Site = "https://code.x2go.org/releases/binary-win32/3rd-party/pulse"
$Pkg = "pulseaudio-5.0-rev18.zip"
Invoke-WebRequest -Uri $Site/$Pkg -OutFile $Pkg -UseBasicParsing
Expand-Archive -LiteralPath $Pkg -DestinationPath C:\
Remove-Item $Pkg
The modifications required are similar for the two PulseAudio versions.
Only difference is the files used. For the old version, append the
following lines to file C:\pulse\etc\pulse\default.pa
. For the newer
version, create a file config.pa
in the C:\pulse
directory.
load-module module-waveout sink_name=output source_name=input record=0
If WSL1 is used, append the following lines as well.
load-module module-esound-protocol-tcp auth-ip-acl=127.0.0.1
load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1
If WSL2 is used, append the following lines instead.
load-module module-esound-protocol-tcp auth-ip-acl=172.16.0.0/12
load-module module-native-protocol-tcp auth-ip-acl=172.16.0.0/12
Then run the C:\pulse\bin\pulseaudio.exe
binary if running the old
version, or make the following shortcut if running the newer version.
C:\pulse\pulseaudio.exe -F C:\pulse\config.pa
Similar to VcXsrv before, on first run, allow private network access only. If WSL2 is used, the public TCP rule has to be changed to allow for WSL subnet.
netsh advfirewall firewall set rule name="pulseaudio" profile=Public protocol=TCP new action=Allow remoteip=172.16.0.0/12
By default PulseAudio will exit in 20s if no client connection takes
place. Because that is probably unwanted behavior the following can be
appended to C:\pulse\etc\pulse\daemon.conf
if the first verison is
used.
exit-idle-time = -1
Alternatively the --exit-idle-time=-1
argument can be used when
running the binary. That applies to both versions. For the newer version
combining with the previous, someone can make a shortcut that runs
C:\pulse\pulseaudio.exe -F C:\pulse\config.pa --exit-idle-time=-1
Then install PulseAudio on the distro
sudo apt install pulseaudio
As already mentioned PulseAudio works like Xorg allowing a network
setup. Again similar to before, an environmental variable
has to be set. This can be added in ~/.bashrc
and sets the host name
of the PulseAudio server. Alternatively the file ~/.pulse/client.conf
can be modified setting default-server
. That will be a static
configuration which won’t work for WSL2.
If WSL1 is used, add the following.
export PULSE_SERVER=tcp:localhost
If WSL2 is used, add the following.
export PULSE_SERVER=tcp:$(awk '/nameserver/{print $2}' /etc/resolv.conf)
Test the sound configuration by playing a random noise.
$ pacat /dev/urandom
A better option for both graphics and sound to the ones mentioned, are using AF_UNIX (Unix domain sockets) rather TCP networking. Not only har better performance but is less resource intensive as well. A presentation was done by Martin Wang on his channel. Unfortunately though process is exactly similar in Windows side (thanks to Martin providing pre-built packages), it requires patching packages in Linux side. Also, WSL2 isn’t supported. For WSL2 a superior option for graphics is using VSOCK (virtual socket). This can be done using wsld and also solves some other issues that the approach presented has. The program comes in two parts, one installed in each side. The program in Linux side will forward Unix socket over VSOCK to the program in Windows side which then will forward it to TCP to which the X server listens to. Though it isn’t supported, the exactly similar approach can be applied to PulseAudio. In any case with WSLG, a first-party Wayland-based native display server on tracks the graphics part will be solved. See presentation by Steve Pronovast on XDC 2020.
TODO: Add AF_UNIX instructions
TODO: Add wsld instructions