Repository: microsoft/wslg Branch: main Commit: 1a8c3c64aa15 Files: 83 Total size: 286.3 KB Directory structure: gitextract_cowqssw9/ ├── .dockerignore ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Microsoft.WSLg.nuspec ├── Microsoft.WSLg.targets ├── README.md ├── SECURITY.md ├── WSLDVCPlugin/ │ ├── RegisterWSLPlugin.reg │ ├── UpdateRCVersion.ps1 │ ├── WSLDVCCallback.cpp │ ├── WSLDVCCallback.h │ ├── WSLDVCFileDB.cpp │ ├── WSLDVCFileDB.h │ ├── WSLDVCListenerCallback.cpp │ ├── WSLDVCListenerCallback.h │ ├── WSLDVCPlugin.cpp │ ├── WSLDVCPlugin.def │ ├── WSLDVCPlugin.h │ ├── WSLDVCPlugin.rc │ ├── WSLDVCPlugin.sln │ ├── WSLDVCPlugin.vcxproj │ ├── WSLDVCPlugin.vcxproj.filters │ ├── WSLDVCPlugin.vcxproj.user │ ├── cpp.hint │ ├── dllmain.cpp │ ├── framework.h │ ├── pch.cpp │ ├── pch.h │ ├── rdpapplist.h │ ├── resource.h │ ├── utils.cpp │ └── utils.h ├── WSLGd/ │ ├── FontMonitor.cpp │ ├── FontMonitor.h │ ├── Makefile │ ├── ProcessMonitor.cpp │ ├── ProcessMonitor.h │ ├── common.h │ ├── lxwil.h │ ├── main.cpp │ ├── meson.build │ └── precomp.h ├── azure-pipelines.yml ├── build-and-export.sh ├── cgmanifest.json ├── config/ │ ├── BUILD.md │ ├── default_wslg.pa │ ├── local.conf │ ├── weston.ini │ ├── wsl.conf │ └── xwayland_log.patch ├── debuginfo/ │ ├── FreeRDP2.list │ ├── FreeRDP3.list │ ├── WSLGd.list │ ├── gen_debuginfo.sh │ ├── rdpapplist.list │ └── weston.list ├── devops/ │ ├── common-linux.yml │ ├── common-win.yml │ ├── getversion.ps1 │ ├── updateversion.ps1 │ └── version_functions.ps1 ├── docs/ │ └── install-sample-gui-apps.sh ├── msi/ │ └── updateversion.ps1 ├── package/ │ ├── wslg.rdp │ └── wslg_desktop.rdp ├── rdpapplist/ │ ├── meson.build │ ├── rdpapplist_common.c │ ├── rdpapplist_common.h │ ├── rdpapplist_protocol.h │ ├── rdpapplist_server.h │ └── server/ │ ├── meson.build │ ├── rdpapplist_main.c │ └── rdpapplist_main.h ├── samples/ │ └── container/ │ ├── Containers.md │ ├── build.sh │ └── run.sh └── vendor/ └── .preserve ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .git .github .gitlab .gitlab-ci .vs out tmp ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: "Bug report" description: " Report a bug in WSLg" labels: "bug" body: - type: input id: build-number attributes: label: "Windows build number:" description: "run `[Environment]::OSVersion` for powershell, or `ver` for cmd" placeholder: "22000.100" validations: required: true - type: input id: distribution_version attributes: label: "Your Distribution version:" description: "On Debian or Ubuntu run `lsb_release -r` in WSL" placeholder: "20.04" validations: required: true - type: textarea id: wsl-version attributes: label: "Your WSL versions:" description: "run `wsl --version` on Windows command prompt" validations: required: true - type: markdown attributes: value: | **(Optional) Verifiy using the latest release of WSL/WSLg**: It is always good idea to verify the issue is still reproducible on the latest WSL/WSLg release whenever possible. WSL/WSLg can be updated from https://aka.ms/wslstorepage, or when Microsoft Store is not accessible from your environment, by downloading the latest release package (.msixbundle) from https://github.com/microsoft/WSL/releases. - type: textarea id: reproduce-steps attributes: label: "Steps to reproduce:" placeholder: | 1. 2. 3. validations: required: true - type: textarea id: logs attributes: description: | Collect WSL logs if needed by following these instructions: https://github.com/Microsoft/WSL/blob/master/CONTRIBUTING.md#8-detailed-logs * Attach WSLg logs from `/mnt/wslg` You can access the wslg logs using explorer at: `\\wsl$\\mnt\wslg` (e.g.: `\\wsl$\Ubuntu-20.04\mnt\wslg`) * `pulseaudio.log` * `weston.log` * `stderr.log` label: "WSL logs:" placeholder: | Drag and drop files into this input field to upload them. validations: required: false - type: textarea id: dumps attributes: label: "WSL dumps:" description: "Attach any dump files from `%tmp%\\wsl-crashes`, such as core.weston, if exists. If running an older version, the dump files can be found in `/mnt/wslg/dumps`" validations: required: false - type: textarea id: expected-behavior attributes: label: "Expected behavior:" description: "A description of what you're expecting, possibly containing screenshots or reference material" validations: required: false - type: textarea id: actual-behavior attributes: label: "Actual behavior:" description: "What's actually happening?" validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Report issues on WSL in general url: https://github.com/microsoft/WSL/issues/new/choose about: Please report issues not related to WSLg ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: "Feature request" description: "Suggest a feature for WSLg" labels: "enhancement" body: - type: textarea attributes: label: "Is your feature request related to a problem:" description: "A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]" validations: required: false - type: textarea attributes: label: "Describe the solution you'd like:" description: "A clear and concise description of what you want to happen." validations: required: true - type: textarea attributes: label: "Describe alternatives you've considered:" description: "A clear and concise description of any alternative solutions or features you've considered." validations: required: true - type: textarea attributes: label: "Additional context:" description: "Add any other context or screenshots about the feature request here." validations: required: false ================================================ FILE: .gitignore ================================================ .vscode/* vendor/* !vendor/.preserve out tmp *.nupkg *.tar *.vhd WSLGd/*.d WSLGd/*.o WSLGd/WSLGd WSLDVCPlugin/.vs WSLDVCPlugin/x64 WSLDVCPlugin/ARM64/Debug/ WSLDVCPlugin/ARM64/Release/ WSLDVCPlugin/WSLDVCPlugin.aps ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repositories using our CLA. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. # Building the WSLg System Distro The heart of WSLg is what we call the WSL system distro. This is where the Weston compositor, XWayland and the PulseAudio server are running. The system distro runs these components and projects their communication sockets into the user distro. Every user distro is paired with a unique instance of the system distro. There is a single version of the system distro on disk which is instantiated in memory when a user distro is launched. The system distro is essentially a Linux container packaged and distributed as a vhd. The system distro is accessible to the user, but is mounted read-only. Any changes made by the user to the system distro while it is running are discarded when WSL is restarted. Although a user can log into the system distro, it is not meant to be used as a general purpose user distro. The reason behind this choice is due to the way we service WSLg. When updating WSLg we simply replace the existing system distro with a new one. If the user had data embedded into the system distro vhd, this data would be lost. For folks who want to tinker with or customize their system distro, we give the ability to run a private version of the system distro. When running a private version of WSLg, Windows will load and run your private and ignore the Microsoft published one. If you update your WSL setup (`wsl --update`), the Microsoft published WSLg vhd will be updated, but you will continue to be running your private. You can switch between the Microsoft pulished WSLg system distro and a private one at any time although it does require restarting WSL (`wsl --shutdown`). The WSLg system distro is built using docker build. We essentially start from a [Azure Linux 3.0](https://github.com/microsoft/azurelinux) base image, install various packages, then build and install version of Weston, FreeRDP and PulseAudio from our mirror repo. This repository contains a Dockerfile and supporting tools to build the WSLg container and convert the container into an ext4 vhd that Windows will load as the system distro. ## Build instructions 0. Install and start Docker in a Linux or WSL 2 environment. ``` sudo apt-get update sudo apt install docker.io golang-go sudo dockerd ``` 1. Clone the WSLg project: ``` git clone https://github.com/microsoft/wslg wslg ``` 2. Clone the FreeRDP, Weston and PulseAudio mirror. These need to be located in a **vendor** sub-directory where you clone the wslg project (e.g. wslg/vendor), this is where our docker build script expects to find the source code. Make sure to checkout the **working** branch from each of these projects, the **main** branch references the upstream code. ```bash git clone https://github.com/microsoft/FreeRDP-mirror wslg/vendor/FreeRDP -b working git clone https://github.com/microsoft/weston-mirror wslg/vendor/weston -b working git clone https://github.com/microsoft/PulseAudio-mirror wslg/vendor/pulseaudio -b working git clone https://github.com/microsoft/DirectX-Headers.git wslg/vendor/DirectX-Headers-1.0 -b v1.608.0 git clone https://gitlab.freedesktop.org/mesa/mesa.git wslg/vendor/mesa -b mesa-23.1.0 ``` 2. Create the VHD: 2.1 From the parent directory where you cloned `wslg` clone `hcsshim` which contains `tar2ext4` and will be used to create the system distro vhd ``` git clone --branch v0.8.9 --single-branch https://github.com/microsoft/hcsshim.git ``` 2.2 From the parent directory build and export the docker image: ``` sudo docker build -t system-distro-x64 ./wslg --build-arg SYSTEMDISTRO_VERSION=`git --git-dir=wslg/.git rev-parse --verify HEAD` --build-arg SYSTEMDISTRO_ARCH=x86_64 sudo docker export `sudo docker create system-distro-x64` > system_x64.tar ``` 2.3 Create the system distro vhd using `tar2ext4` ```bash cd hcsshim/cmd/tar2ext4 go run tar2ext4.go -vhd -i ../../../system_x64.tar -o ../../../system.vhd ``` This will create system distro image `system.vhd` ## Installing a private version of the WSLg system distro You can tell WSL to load a private version of WSLg by adding the following option in your `.wslconfig` file (located in `C:\Users\MyUser\.wslconfig`). ``` [wsl2] systemDistro=C:\\Files\\system.vhd ``` You need to restart WSL for this change to take effect. From an elevated command prompt execute `wsl --shutdown`. When WSL is launched again, Windows will load your private vhd as the system distro. ## Inspecting the WSLg system distro at runtime If the system distro isn't working correctly or you need to inspect what is running inside the system distro you can get a terminal into the system distro by running the following command from an elevated command prompt. ``` wsl --system -d [DistroName] ``` There is an instance of the system distro running for every user distro running. `DistroName` refers to the name of the user distro for which you want the paired system distro. If you omit `DistroName`, you will get a terminal into the system distro paired with your default WSL user distro. Please keep in mind that the system distro is loaded read-only from it's backing VHD. For example, if you need to install tools (say a debugger or an editor) in the system distro, you want to do this in the Dockerfile that builds the system distro so it gets into the private vhd that you are running. You can dynamically install new packages once your have a terminal into the system distro, but any changes you make will be discarded when WSL is restarted. ## Building a debug version To build a debug version of the system distro, the docker build argument SYSTEMDISTRO_DEBUG_BUILD needs to be set and passed the value of "true". The following command would substitute the docker build command in step 3.2.2 of the "Build Instructions" section. ``` sudo docker build -t system-distro-x64 ./wslg --build-arg SYSTEMDISTRO_VERSION=`git --git-dir=wslg/.git rev-parse --verify HEAD` --build-arg SYSTEMDISTRO_ARCH=x86_64 --build-arg SYSTEMDISTRO_DEBUG_BUILD=true ``` The resulting system distro VHD will have useful development packages installed like gdb and will have compiled all runtime dependencies with the "debug" buildtype for Meson, rather than "release". # mstsc plugin On the Windows side of the world, WSLg leverages the native `mstsc.exe` RDP client and a plugin for that client which handles WSLg integration into the start menu. The source code for this plugin is available as open source as part of the WSLg repo [here](https://github.com/microsoft/wslg/tree/main/WSLDVCPlugin). It was important for us building WSLg to ensure that all protocols between Linux and Windows be fully documented and available to everyone to reuse. While almost all of the communication over RDP between Linux/Weston and Windows goes through standard and officially documented [Windows Protocols](https://docs.microsoft.com/en-us/openspecs/windows_protocols/MS-WINPROTLP/92b33e19-6fff-496b-86c3-d168206f9845) associated with the RDP standard, we needed just a bit of custom communication between Linux and Windows to handle integration into the start menu. We thought about adding some official RDP protocol for this, but this was too specific to WSLg and not broadly applicable to arbitrary RDP based solution. So instead we opted to use a custom RDP channel between the WSLg RDP Server running inside of Weston and the WSLg RDP plugin hosted by mstsc. Such custom dynamic channel are part of the RDP specification, but requires that both the RDP server and RDP client support that channel for it to be used. This is the path we took for WSLg where Weston exposes a custom RDP channel for WSLg integration. In the spirit of fully documenting all channels of communication between Linux and Windows, we're making the source code for plugin which handles the Windows side of this custom RDP channel available as part of the WSLg project. This custom channel and associated plugin are quite small and simple. In a nutshell, Weston enumerates all installed applications inside of the user Linux distro (i.e. application which have an explicit [desktop file](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html)) and exposes this list of applications, along with command line to launch them and icon to represent them, over this custom RDP channel. The mstsc plugin processes that list and creates links in the Windows Start Menu for these applications so they can be launch directly from it. ## Building the mstsc plugin The [source code](https://github.com/microsoft/wslg/tree/main/WSLDVCPlugin) for the plugin has a visual studio project file that can be use to build it. You can download and install the free [Visual Studio Community Edition](https://visualstudio.microsoft.com/vs/community/) to build it. ## Registering a private mstsc plugin The plugin is registered with mstsc through the registry. By default this is set to load the plugin that ships as part of the official WSLg package. If you need to run a private, you'll nee to modify this registry key to reference your privately built plugin, for example using a registry file like below. ``` Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default\AddIns\WSLDVCPlugin] "Name"="C:\\users\\MyUser\\Privates\\WSLDVCPlugin.dll" ``` ================================================ FILE: Dockerfile ================================================ # Create a builder image with the compilers, etc. needed FROM mcr.microsoft.com/azurelinux/base/core:3.0 AS build-env # Install all the required packages for building. This list is probably # longer than necessary. RUN echo "== Install Git/CA certificates ==" && \ tdnf install -y \ git \ ca-certificates RUN echo "== Install Core dependencies ==" && \ tdnf install -y \ alsa-lib \ alsa-lib-devel \ autoconf \ automake \ binutils \ bison \ build-essential \ cairo \ cairo-devel \ clang \ clang-devel \ cmake \ dbus \ dbus-devel \ dbus-glib \ dbus-glib-devel \ diffutils \ elfutils-devel \ file-libs \ flex \ fontconfig-devel \ gawk \ gcc \ gettext \ glibc-devel \ glib-schemas \ gobject-introspection \ gobject-introspection-devel \ harfbuzz \ harfbuzz-devel \ kernel-headers \ intltool \ libatomic_ops \ libcap-devel \ libffi \ libffi-devel \ libgudev \ libgudev-devel \ libjpeg-turbo \ libjpeg-turbo-devel \ libltdl \ libltdl-devel \ libpng-devel \ librsvg2-devel \ libtiff \ libtiff-devel \ libusb \ libusb-devel \ libwebp \ libwebp-devel \ libxml2 \ libxml2-devel \ make \ meson \ newt \ nss \ nss-libs \ openldap \ openssl-devel \ pam-devel \ pango \ pango-devel \ patch \ perl-XML-Parser \ polkit-devel \ python3-devel \ python3-mako \ python3-markupsafe \ sed \ sqlite-devel \ systemd-devel \ tar \ unzip \ vala \ vala-devel \ vala-tools \ zlib-devel RUN echo "== Install UI dependencies ==" && \ tdnf install -y \ libdrm-devel \ libepoxy-devel \ libevdev \ libevdev-devel \ libinput \ libinput-devel \ libpciaccess-devel \ libSM-devel \ libsndfile \ libsndfile-devel \ libXcursor \ libXcursor-devel \ libXdamage-devel \ libXfont2-devel \ libXi \ libXi-devel \ libxkbcommon-devel \ libxkbfile-devel \ libXrandr-devel \ libxshmfence-devel \ libXtst \ libXtst-devel \ libXxf86vm-devel \ wayland-devel \ wayland-protocols-devel \ xkbcomp \ xkeyboard-config \ xorg-x11-server-Xwayland-devel \ xorg-x11-util-macros # Create an image with builds of FreeRDP and Weston FROM build-env AS dev ARG WSLG_VERSION="" ARG WSLG_ARCH="x86_64" ARG SYSTEMDISTRO_DEBUG_BUILD ARG FREERDP_VERSION=2 WORKDIR /work RUN echo "WSLg (" ${WSLG_ARCH} "):" ${WSLG_VERSION} > /work/versions.txt RUN echo "Built at:" `date --utc` >> /work/versions.txt RUN echo "Azure Linux:" `cat /etc/os-release | head -2 | tail -1` >> /work/versions.txt # # Build runtime dependencies. # ENV BUILDTYPE=${SYSTEMDISTRO_DEBUG_BUILD:+debug} ENV BUILDTYPE=${BUILDTYPE:-debugoptimized} RUN echo "== System distro build type:" ${BUILDTYPE} " ==" ENV BUILDTYPE_NODEBUGSTRIP=${SYSTEMDISTRO_DEBUG_BUILD:+debug} ENV BUILDTYPE_NODEBUGSTRIP=${BUILDTYPE_NODEBUGSTRIP:-release} RUN echo "== System distro build type (no debug strip):" ${BUILDTYPE_NODEBUGSTRIP} " ==" # FreeRDP is always built with RelWithDebInfo ENV BUILDTYPE_FREERDP=${BUILDTYPE_FREERDP:-RelWithDebInfo} RUN echo "== System distro build type (FreeRDP):" ${BUILDTYPE_FREERDP} " ==" ENV WITH_DEBUG_FREERDP=${SYSTEMDISTRO_DEBUG_BUILD:+ON} ENV WITH_DEBUG_FREERDP=${WITH_DEBUG_FREERDP:-OFF} RUN echo "== System distro build type (FreeRDP Debug Options):" ${WITH_DEBUG_FREERDP} " ==" ENV DESTDIR=/work/build ENV PREFIX=/usr ENV PKG_CONFIG_PATH=${DESTDIR}${PREFIX}/lib/pkgconfig:${DESTDIR}${PREFIX}/lib/${WSLG_ARCH}-linux-gnu/pkgconfig:${DESTDIR}${PREFIX}/share/pkgconfig ENV C_INCLUDE_PATH=${DESTDIR}${PREFIX}/include/freerdp${FREERDP_VERSION}:${DESTDIR}${PREFIX}/include/winpr${FREERDP_VERSION}:${DESTDIR}${PREFIX}/include/wsl/stubs:${DESTDIR}${PREFIX}/include ENV CPLUS_INCLUDE_PATH=${C_INCLUDE_PATH} ENV LIBRARY_PATH=${DESTDIR}${PREFIX}/lib ENV LD_LIBRARY_PATH=${LIBRARY_PATH} ENV CC=/usr/bin/gcc ENV CXX=/usr/bin/g++ # Setup DebugInfo folder COPY debuginfo /work/debuginfo RUN chmod +x /work/debuginfo/gen_debuginfo.sh # Build DirectX-Headers COPY vendor/DirectX-Headers-1.0 /work/vendor/DirectX-Headers-1.0 WORKDIR /work/vendor/DirectX-Headers-1.0 RUN /usr/bin/meson --prefix=${PREFIX} build \ --buildtype=${BUILDTYPE_NODEBUGSTRIP} \ -Dbuild-test=false && \ ninja -C build -j8 install && \ echo 'DirectX-Headers:' `git --git-dir=/work/vendor/DirectX-Headers-1.0/.git rev-parse --verify HEAD` >> /work/versions.txt # Build mesa with the minimal options we need. COPY vendor/mesa /work/vendor/mesa WORKDIR /work/vendor/mesa RUN /usr/bin/meson --prefix=${PREFIX} build \ --buildtype=${BUILDTYPE_NODEBUGSTRIP} \ -Dgallium-drivers=swrast,d3d12 \ -Dvulkan-drivers= \ -Dllvm=disabled && \ ninja -C build -j8 install && \ echo 'mesa:' `git --git-dir=/work/vendor/mesa/.git rev-parse --verify HEAD` >> /work/versions.txt # Build PulseAudio COPY vendor/pulseaudio /work/vendor/pulseaudio WORKDIR /work/vendor/pulseaudio RUN /usr/bin/meson --prefix=${PREFIX} build \ --buildtype=${BUILDTYPE_NODEBUGSTRIP} \ -Ddatabase=simple \ -Ddoxygen=false \ -Dgsettings=disabled \ -Dtests=false && \ ninja -C build -j8 install && \ echo 'pulseaudio:' `git --git-dir=/work/vendor/pulseaudio/.git rev-parse --verify HEAD` >> /work/versions.txt # Build FreeRDP COPY vendor/FreeRDP /work/vendor/FreeRDP WORKDIR /work/vendor/FreeRDP RUN cmake -G Ninja \ -B build \ -DCMAKE_INSTALL_PREFIX=${PREFIX} \ -DCMAKE_INSTALL_LIBDIR=${PREFIX}/lib \ -DCMAKE_BUILD_TYPE=${BUILDTYPE_FREERDP} \ -DWITH_DEBUG_ALL=${WITH_DEBUG_FREERDP} \ -DWITH_ICU=ON \ -DWITH_SERVER=ON \ -DWITH_CHANNEL_GFXREDIR=ON \ -DWITH_CHANNEL_RDPAPPLIST=ON \ -DWITH_CLIENT=OFF \ -DWITH_CLIENT_COMMON=OFF \ -DWITH_CLIENT_CHANNELS=OFF \ -DWITH_CLIENT_INTERFACE=OFF \ -DWITH_LIBSYSTEMD=OFF \ -DWITH_WAYLAND=OFF \ -DWITH_X11=OFF \ -DWITH_PROXY=OFF \ -DWITH_SHADOW=OFF \ -DWITH_SAMPLE=OFF && \ ninja -C build -j8 install && \ echo 'FreeRDP:' `git --git-dir=/work/vendor/FreeRDP/.git rev-parse --verify HEAD` >> /work/versions.txt WORKDIR /work/debuginfo RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ echo "== Strip debug info: FreeRDP ==" && \ /work/debuginfo/gen_debuginfo.sh /work/debuginfo/FreeRDP${FREERDP_VERSION}.list /work/build; \ fi # Build rdpapplist RDP virtual channel plugin COPY rdpapplist /work/rdpapplist WORKDIR /work/rdpapplist RUN /usr/bin/meson --prefix=${PREFIX} build \ --buildtype=${BUILDTYPE} && \ ninja -C build -j8 install WORKDIR /work/debuginfo RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ echo "== Strip debug info: rdpapplist ==" && \ /work/debuginfo/gen_debuginfo.sh /work/debuginfo/rdpapplist.list /work/build; \ fi # Build Weston COPY vendor/weston /work/vendor/weston WORKDIR /work/vendor/weston RUN /usr/bin/meson --prefix=${PREFIX} build \ --buildtype=${BUILDTYPE} \ -Dbackend-default=rdp \ -Dbackend-drm=false \ -Dbackend-drm-screencast-vaapi=false \ -Dbackend-headless=false \ -Dbackend-wayland=false \ -Dbackend-x11=false \ -Dbackend-fbdev=false \ -Dcolor-management-colord=false \ -Dscreenshare=false \ -Dsystemd=false \ -Dwslgd=true \ -Dremoting=false \ -Dpipewire=false \ -Dshell-fullscreen=false \ -Dcolor-management-lcms=false \ -Dshell-ivi=false \ -Dshell-kiosk=false \ -Ddemo-clients=false \ -Dsimple-clients=[] \ -Dtools=[] \ -Dresize-pool=false \ -Dwcap-decode=false \ -Dtest-junit-xml=false && \ ninja -C build -j8 install && \ echo 'weston:' `git --git-dir=/work/vendor/weston/.git rev-parse --verify HEAD` >> /work/versions.txt WORKDIR /work/debuginfo RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ echo "== Strip debug info: weston ==" && \ /work/debuginfo/gen_debuginfo.sh /work/debuginfo/weston.list /work/build; \ fi # Build WSLGd Daemon ENV CC=/usr/bin/clang ENV CXX=/usr/bin/clang++ COPY WSLGd /work/WSLGd WORKDIR /work/WSLGd RUN /usr/bin/meson --prefix=${PREFIX} build \ --buildtype=${BUILDTYPE} && \ ninja -C build -j8 install WORKDIR /work/debuginfo RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ echo "== Strip debug info: WSLGd ==" && \ /work/debuginfo/gen_debuginfo.sh /work/debuginfo/WSLGd.list /work/build; \ fi # Gather debuginfo to a tar file WORKDIR /work/debuginfo RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ echo "== Compress debug info: /work/debuginfo/system-debuginfo.tar.gz ==" && \ tar -C /work/build/debuginfo -czf system-debuginfo.tar.gz ./ ; \ fi ######################################################################## ######################################################################## ## Create the distro image with just what's needed at runtime FROM mcr.microsoft.com/azurelinux/base/core:3.0 AS runtime RUN echo "== Install Core/UI Runtime Dependencies ==" && \ tdnf install -y \ busybox \ ca-certificates \ cairo \ chrony \ containerd2 \ containernetworking-plugins \ runc \ dbus \ dbus-glib \ dhcpcd \ docker-buildx \ docker-cli \ e2fsprogs \ freefont \ gzip \ icu \ iptables \ kmod \ libinput \ libjpeg-turbo \ libltdl \ libpng \ librsvg2 \ libsndfile \ libwayland-client \ libwayland-server \ libwayland-cursor \ libwebp \ libXcursor \ libxkbcommon \ libXrandr \ iproute \ moby-engine \ nftables \ pango \ procps-ng \ rpm \ sed \ systemd-libs \ tar \ tzdata \ util-linux \ xcursor-themes \ xorg-x11-server-Xwayland \ xorg-x11-server-utils # Install busybox utilities RUN /sbin/busybox --install -s # Remove unnecessary packages and files to reduce image size ARG SYSTEMDISTRO_DEBUG_BUILD RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ echo "== Removing unnecessary packages ==" && \ # Remove build tools and packages not needed at runtime \ rpm -e --nodeps \ cracklib-dicts \ gcc \ gcc-c++ \ libpkgconf \ llvm \ perl \ pkgconf \ pkgconf-m4 \ pkgconf-pkg-config \ python3 \ python3-libs && \ # Remove all perl subpackages \ rpm -e --nodeps $(rpm -qa | grep -- '^perl-') && \ # Remove all -devel packages \ rpm -e --nodeps $(rpm -qa | grep -- '-devel') && \ # Remove systemd components (except systemd-libs which is needed by weston) \ rpm -e --nodeps $(rpm -qa | grep -- '^systemd-' | grep -v systemd-libs) && \ # Remove orphaned packages \ tdnf autoremove -y && \ echo "== Removing unnecessary files ==" && \ # Remove docs, man pages, locales, gtk-doc \ rm -rf /usr/share/man /usr/share/info /usr/share/locale /usr/share/gtk-doc && \ find /usr/share/doc -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} + && \ # Remove unused Mesa driver \ rm -f /usr/lib64/dri/virtio_gpu_dri.so && \ # Remove hardware database (not needed in WSL) \ rm -rf /usr/share/hwdata/* && \ # Remove temporary files, logs, caches, and systemd catalog \ rm -rf /tmp/* /var/tmp/* /var/log/* /var/cache/* /usr/lib/systemd/catalog/*; \ else \ echo "== Install development aid packages ==" && \ tdnf install -y \ gdb \ azurelinux-repos-debug \ nano \ vim \ wayland-debuginfo \ xorg-x11-server-debuginfo; \ fi # Clear the tdnf cache to make the image smaller RUN tdnf clean all # Create wslg user. RUN useradd -u 1000 --create-home wslg && \ mkdir /home/wslg/.config && \ chown wslg /home/wslg/.config # Copy config files. COPY config/wsl.conf /etc/wsl.conf COPY config/weston.ini /home/wslg/.config/weston.ini COPY config/local.conf /etc/fonts/local.conf # Copy default icon file. COPY resources/linux.png /usr/share/icons/wsl/linux.png # Copy the built artifacts from the build stage. COPY --from=dev /work/build/usr/ /usr/ COPY --from=dev /work/build/etc/ /etc/ # Append WSLg setttings to pulseaudio. COPY config/default_wslg.pa /etc/pulse/default_wslg.pa RUN cat /etc/pulse/default_wslg.pa >> /etc/pulse/default.pa RUN rm /etc/pulse/default_wslg.pa # Copy the licensing information for PulseAudio COPY --from=dev /work/vendor/pulseaudio/GPL \ /work/vendor/pulseaudio/LGPL \ /work/vendor/pulseaudio/LICENSE \ /work/vendor/pulseaudio/NEWS \ /work/vendor/pulseaudio/README /usr/share/doc/pulseaudio/ # Copy the licensing information for Weston COPY --from=dev /work/vendor/weston/COPYING /usr/share/doc/weston/COPYING # Copy the licensing information for FreeRDP COPY --from=dev /work/vendor/FreeRDP/LICENSE /usr/share/doc/FreeRDP/LICENSE # copy the documentation and licensing information for mesa COPY --from=dev /work/vendor/mesa/docs /usr/share/doc/mesa/ COPY --from=dev /work/versions.txt /etc/versions.txt CMD /usr/bin/WSLGd ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) Microsoft Corporation. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE ================================================ FILE: Microsoft.WSLg.nuspec ================================================ Microsoft.WSLg 0.2.12 Microsoft Microsoft, WSL Team https://github.com/microsoft/wslg/ false WSLg support package. Enabling the Windows Subsystem for Linux to include support for Wayland and X server related scenarios. ebad9c1 Copyright © Microsoft Corporation 2021 native ================================================ FILE: Microsoft.WSLg.targets ================================================ system.vhd PreserveNewest WSLDVCPlugin.dll PreserveNewest wslg.rdp PreserveNewest wslg_desktop.rdp PreserveNewest ================================================ FILE: README.md ================================================ # Welcome to WSLg WSLg is short for *Windows Subsystem for Linux GUI* and the purpose of the project is to enable support for running Linux GUI applications (X11 and Wayland) on Windows in a fully integrated desktop experience. WSLg provides an integrated experience for developers, scientists or enthusiasts that prefer or need to run Windows on their PC but also need the ability to run tools or applications which work best, or exclusively, in a Linux environment. While users can accomplish this today using a multiple system setup, with individual PC dedicated to Windows and Linux, virtual machine hosting either Windows or Linux, or an XServer running on Windows and projected into WSL, WSLg provides a more integrated, user friendly and productive alternative. WSLg strives to make Linux GUI applications feel native and natural to use on Windows. From integration into the Start Menu for launch to appearing in the task bar, alt-tab experience to enabling cut/paste across Windows and Linux applications, WSLg enables a seamless desktop experience and workflow leveraging Windows and Linux applications. ![WSLg Integrated Desktop](/docs/WSLg_IntegratedDesktop.png) # Installing WSLg ## Pre-requisites - WSLg is supported on both Windows 11 and Windows 10. Windows 10 users must ensure their Windows 10 installation is fully up to date by visiting Windows Update and installing all available updates. - WSLg is available both as part of the Windows 11 WSL inbox support as well as through the [Windows Subsystem for Linux from the Microsoft Store](https://aka.ms/wslstorepage). It is highly recommended to use the Microsoft Store version of WSL, which supports both Windows 10 and Windows 11, and contains the most up to date version of WSL and WSLg. - Make sure to update your graphics driver to the latest driver available from your GPU manufacturer's website to benefit from GPU acceleration in your WSL environment. ## Install instructions (Fresh Install - no prior WSL installation) From a command prompt with administrator privileges, run the command `wsl --install -d Ubuntu`, then reboot if prompted. After reboot the installation will continue. You'll be asked to enter a username and password. These will be your Linux credentials, they can be anything you want and don't have to match your Windows credentials. Voilà! WSL and WSLg are installed and ready to be used! ## Install instructions (Existing WSL install) If you have an existing WSL installation without WSLg and want to update to the latest version of WSL which includes WSLg, run the command `wsl --update` from an elevated command prompt. Please note that WSLg is only compatible with WSL 2 and will not work for WSL distribution configured to work in WSL 1 mode. Verify that your Linux distro is configured for running in WSL 2 mode, if not switch to WSL 2. While you can continue to run Linux distro in WSL 1 mode after installing WSLg if you so desired, a distro configured to run in WSL 1 mode will not be able to communicate with WSLg and will not be able to run GUI applications. You can list your currently installed distro and the version of WSL they are configured for using the following command from an elevated command prompt. ```powershell wsl --list -v ``` If running in version 1 mode, switch to version 2. This can take a while. ```powershell wsl --set-version _distro_name_ 2 ``` Restart WSL by running this command from an elevated command prompt, make sure to save any pending work first: ```powershell wsl --shutdown ``` ## Updating WSL + WSLg To update to the latest version of WSL and WSLg released for preview, simply run `wsl --update` from an elevated command prompt or powershell. You'll need to restart WSL for the changes to take effect. You can restart WSL by running `wsl --shutdown` from an elevated command prompt. If WSL was currently running, it will shutdown, make sure to first save any in progress work! WSL will be automatically restarted the next time you launch a WSL application or terminal. ## First Launch If you have installed the `Ubuntu` Linux distro per these instructions, you'll find an `Ubuntu` icon in your start menu, launch it. This will launch the WSL 2 VM, launch the Ubuntu WSL distro in that VM and give you a terminal to interact with it. Voilà! You're running Linux on Windows! If you would like to explore additional Linux distributions built for WSL, you can use the `wsl --list --online` command from an elevated command prompt to enumerate the list of available distributions for your system. You can have multiple Linux distributions installed within WSL and they will happily coexist side-by-side, so don't be scared to experiment and try things out. Congrats you are done and ready to use GUI apps! ## Install and run GUI apps If you want to get started with some GUI apps, you can run the following commands from your Linux terminal to download and install some popular applications. If you are using a different distribution than Ubuntu, it may be using a different package manager. ```powershell ## Update list of available packages sudo apt update ## Gedit sudo apt install gedit -y ## GIMP sudo apt install gimp -y ## Nautilus sudo apt install nautilus -y ## VLC sudo apt install vlc -y ## X11 apps sudo apt install x11-apps -y ## Google Chrome cd /tmp sudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i google-chrome-stable_current_amd64.deb sudo apt install --fix-broken -y sudo dpkg -i google-chrome-stable_current_amd64.deb ## Microsoft Teams cd /tmp sudo curl -L -o "./teams.deb" "https://teams.microsoft.com/downloads/desktopurl?env=production&plat=linux&arch=x64&download=true&linuxArchiveType=deb" sudo apt install ./teams.deb -y ## Microsoft Edge Dev Browser sudo curl https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-dev/microsoft-edge-dev_118.0.2060.1-1_amd64.deb -o /tmp/edge.deb sudo apt install /tmp/edge.deb -y ``` Once these applications are installed, you'll find them in your start menu under the distro name. For example `Ubuntu -> Microsoft Edge`. You can also launch these from your terminal window using the commands: * `xcalc`, `xclock`, `xeyes` * `gimp` * `gedit ~/.bashrc` * `nautilus` * `vlc` * `google-chrome` * `teams` * `microsoft-edge` # WSLg Architecture Overview ![WSLg Architecture Overview](/docs/WSLg_ArchitectureOverview.png) ## User Distro The user distro is essentially the WSL distribution you are using for your Linux work. You can use the command `wsl --list --online` from an elevated Windows command prompt to list the WSL distributions available on your system. You can run multiple user distros side-by-side and they will peacefully coexist, so don't be afraid of trying out new distro. Each user distro will be paired with a unique instance of the system distro, but you can still interact across GUI applications running in different user distro seamlessly, such as cut/paste between them. The underlying containerization of the various userspace should be invisible to you. All user and system distros for a particular Windows user run within the same WSL virtual machine against a single instance of the Linux kernel. Different Windows users on a PC have their own VM and instance of WSL. Your Linux environment is guaranteed to always be your own and not shared with other Windows users on the same PC. ## WSLg System Distro The system distro is where all of the magic happens. The system distro is a containerized Linux environment where the WSLg XServer, Wayland server and Pulse Audio server are running. Communication socket for each of these servers are projected into the user distro so client applications can connect to them. We preconfigure the user distro environment variables DISPLAY, WAYLAND_DISPLAY and PULSE_SERVER to refer these servers by default so WSLg lights up out of the box. Users wanting to use different servers than the one provided by WSLg can change these environment variables. User can also choose to turn off the system distro entirely by adding the following entry in their `.wslconfig` file (located at `c:\users\MyUser\.wslconfig`). This will turn off support for GUI applications in WSL. ``` [wsl2] guiApplications=false ``` The system distro is based on the Microsoft [Azure Linux 3.0](https://github.com/microsoft/azurelinux). This is a minimal Linux environment, just enough to run the various pieces of WSLg. For details on how to build and deploy a private system distro please see our [build instructions](CONTRIBUTING.md). Every WSL 2 user distro is paired with its own instance of the system distro. The system distro runs partially isolated from the user distro to which it is paired, in its own NS/PID/UTS namespace but shares other namespaces such as IPC, to allow for shared memory optimization across the boundary. While a user can get a terminal into the system distro, the system distro is not meant to be used directly by users. Every instance of the system distro is loaded read-only from its backing VHD. Any modifications, made to the in-memory instance of the system distro (such as installing new packages or creating a new file), are effectively discarded when WSL is restarted. The reason we do this is to enable a servicing model for the system distro where we replace the old one with the new one without having to worry about migrating any user data contained within. We use a read-only mapping such that the user gets a well known discard behavior on any changes, every time WSL is restarted, instead of getting a surprise when WSL is serviced. Although the Microsoft published WSLg system distro as read-only, we do want to encourage folks to tinker with it and experiment. Although we expect very few folks to actually need or want to do that, we've shared detailed instruction on our [contributing](CONTRIBUTING.md) page on how to both build and deploy a private version of the system distro. Most users who just want to use GUI applications in WSL don't need to worry about those details. ## WSLGd **WSLGd** is the first process to launch after **init**. **WSLGd** launches **Weston** (with XWayland), **PulseAudio** and establishes the RDP connection by launching **mstsc.exe** on the host in silent mode. The RDP connection will remain active and ready to show a new GUI applications being launch on a moment's notice, without any connection establishment delays. **WSLGd** then monitors these processes and if they exit by error (say as a result of a crash), it automatically restarts them. ## Weston Weston is the Wayland project reference compositor and the heart of WSLg. For WSLg, we've extended the existing RDP backend of libweston to teach it how to remote applications rather than monitor/desktop. We've also added various functionality to it, such as support for multi-monitor, cut/paste, audio in/out, etc... The application integration is achieved through an RDP technology called RAIL (Remote Application Integrated Locally) and VAIL (Virtualized Application Integrated Locally). The main difference between RAIL and VAIL is how pixels are transported across from the RDP server to the RDP client. In RAIL, it is assumed that the Server and Client are running on different physical systems communicating over the network and thus pixels need to be copied over the RDP transport. In VAIL, it is understood that the Server and Client are on the same physical system and can share memory across the Guest/Host VM boundary. We've added support for both RAIL and VAIL to the libweston RDP backend, although for WSLg only the VAIL support is effectively used. While building WSLg, we first implemented RAIL while the necessary pieces enabling the switch to VAIL were being developed in parallel. We decided to keep that support in as it could reuse in other interesting scenarios outside of WSLg, for example for remoting application from a Pi running Linux. To share memory between the Linux guest and Windows host we use virtio-fs. ### RAIL-Shell Weston is modular and has various shells today, such as the desktop shell, fullscreen shell (aka kiosk), and automative shell. For WSLg we introduced a new shell called the RAIL Shell. The purpose of the RAIL Shell is to help with the remoting of individual windows from Linux to Windows, as such the shell is very simplistic and doesn't involve any actual widgets or shell owned pixels. ## FreeRDP Weston leverages FreeRDP to implement its backend RDP Server. FreeRDP is used to encode all communications going from the RDP Server (in Weston) to the RDP Client (mstsc on Windows) according to the RDP protocol specifications. It is also used to decode all traffic coming from the RDP Client into the RDP server. ## Pulse Audio Plugin For audio in (microphone) and out (speakers/headphone) WSLg runs a PulseAudio server. WSLg uses a [sink plugin](https://github.com/microsoft/pulseaudio-mirror/blob/working/src/modules/rdp/module-rdp-sink.c) for audio out, and a [source plugin](https://github.com/microsoft/pulseaudio-mirror/blob/working/src/modules/rdp/module-rdp-source.c) for audio in. These plugins effectively transfer audio samples between the PulseServer and the Weston RDP Server. The audio streams are merged by the Weston RDP Server onto the RDP transport, effectively enabling audio in/out in the Weston RDP backend across all scenarios (Desktop/RAIL/VAIL style remoting), including WSLg. ## WSL Dynamic Virtual Channel Plugin (WSLDVCPlugin) WSLg makes use of a custom RDP virtual channel between the Weston RDP Server and the mstsc RDP Client running on the Windows host. This channel is used by Weston to enumerate all Linux GUI applications (i.e. applications which have a desktop file entry of type gui) along with their launch command line and icon. The open source [WSLDVCPlugin](https://github.com/microsoft/wslg/tree/main/WSLDVCPlugin) processes the list of Linux GUI applications sent over this channel and creates links for them in the Windows start menu. # OpenGL accelerated rendering in WSLg While WSLg works with or without virtual GPU support, if you intend to run graphics intensive applications such as Blender or Gazebo, it is best to be running on a system with a GPU and driver that can support WSL. An overview of our vGPU architecture and how we make it possible for Linux applications to access the GPU in WSL is available at our [DirectX blog](https://devblogs.microsoft.com/directx/directx-heart-linux/). Support for OpenGL accelerated rendering is made possible through the work our D3D team has done with Collabora and the Mesa community on creating a [d3d12 Gallium driver](https://devblogs.microsoft.com/directx/in-the-works-opencl-and-opengl-mapping-layers-to-directx/). Support for Linux, including support for WSLg, has been upstream and part of the Mesa 21.0 release. To take advantage of this acceleration, you'll need to update the version of Mesa installed in your user distro. It also requires that your distro vendor chose to build and publish the new d3d12 Gallium driver to their package repository. We're working with the various WSL distro publishers to inform them of these changes. Please note that for the first release of WSLg, vGPU interops with the Weston compositor through system memory. If running on a discrete GPU, this effectively means that the rendered data is copied from VRAM to system memory before being presented to the compositor within WSLg, and uploaded onto the GPU again on the Windows side. As a result, there is a performance penalty proportionate to the presentation rate. At very high frame rates such as 600fps on a discrete GPU, that overhead can be as high as 50%. At lower frame rate or on integrated GPU, performance much closer to native can be achieved depending on the workload. Using a vGPU still provides a very significant performance and experience improvement over using a software renderer despite this v1 limitation. # WSLg Code Flow WSLg builds on the great work of the Linux community and makes use of a large number of open source projects. Most components are used as-is from their upstream version and didn't require any changes to light up in WSLg. Some components at the heart of WSLg, in particular Weston, FreeRDP and PulseAudio, required changes to enable the rich WSLg integration. These changes aren't yet upstream. Microsoft is working with the community to share these contributions back with each project such that, over time, WSLg can be built from upstream component directly, without the need for any WSLg specific modifications. All of these in-flight contributions are kept in Microsoft mirror repos. We keep these mirrors up to date with upstream releases and stage our WSLg changes in those repos. WSLg pulls and builds code from these mirror repos as part of our Insider WSLg Preview releases. These mirrors are public and accessible to everyone. Curious developers can take a peek at early stages of our contribution by looking at code in those mirrors, keeping in mind that the final version of the code will likely look different once the contribution reaches the upstream project and is adapted based on the feedback receives by the various project owners. All of our mirrors follow the same model. There is a **main** branch which correspond to the upstream branch at our last synchronization point. We update the **main** branch from time to time to pick update from the upstream project. There is also a **working** branch that contains all of our in-flight changes. WSLg is built using the **working** branch from each of the mirror projects. The projects that WSLg maintains mirrors for will change over time as in-flight contributions evolve. Once some contributions are upstream, it may no longer be necessary to maintain a mirror, at which point it will be removed and WSLg will start to leverage the upstream version of the component directly. As we light up new functionality in WSLg, new mirrors may be introduced to stage contributions to new components. As such, expect the list of mirrors to change overtime. At this point in time, we have the following project mirrors for currently in-flight contributions. | Project | Upstream Repo | WSLg Mirror | |---|---|---| | Weston | https://github.com/wayland-project/weston | https://github.com/microsoft/Weston-mirror| | FreeRDP | https://github.com/FreeRDP/FreeRDP | https://github.com/microsoft/FreeRDP-mirror | | PulseAudio | https://github.com/pulseaudio/pulseaudio | https://github.com/microsoft/PulseAudio-mirror | The following is a high level overview of the currently in-flight contributions to each project contained within these mirrors. ## Weston WSLg leverages Weston as the Wayland compositor bridging the Linux and Windows worlds using RDP technology to remote application content between them. Weston already had an RDP backend, but it was limited to single-monitor-desktop remoting. We've greatly enhanced that RDP backend to include advanced functionality, such as multi-monitor support, clipboard integration for copy/paste, and audio in/out. We've enabled new remoting modes called RAIL (Remote Application Integrated Locally) and VAIL (Virtualized Application Integrated Locally), where individual applications, rather than desktops/monitors, are remoted. These changes are not specific to WSLg; they add functionality to the existing RDP backend and are reusable in other scenarios as well (i.e. using the new Weston RDP backend to remote application running on a Raspberry Pi to another device running an RDP client). To enable rich integration in WSLg, we've also added a small plugin to the RDP backend specific to WSLg. In Weston, the plugin is responsible for attaching to the user distro and searching for installed applications (aka the desktop file). The plugin sends the Windows host a list of all applications found along with their launch commands and icons. On the Windows host, an open source [mstsc plugin](https://github.com/microsoft/wslg/tree/main/WSLDVCPlugin) part of the WSLg project uses that information to create shortcuts for these Linux applications to the Windows Start Menu. We've also fixed several bugs impacting various applications. Generally, these were problems that impacted Weston in all modes and were not specific to WSLg. ## FreeRDP Weston currently uses FreeRDP for its RDP Backend. WSLg continues to leverage FreeRDP and we have added support for a new RDP Protocol/Channel to enable VAIL optimized scenario as well as support for the WSLg plugin. We've also fixed various bugs that were impacting interops with mstsc or causing instability. ## PulseAudio For PulseAudio, our contributions focused on a sink and a source plugin that shuffle audio data between PulseAudio and the Weston RDP backend such that the audio data can be integrated over the RDP connection back to the host. There are no changes to the core of PulseAudio outside of adding these new plugins. # Contributing If you would like to tinker with or contribute to WSLg, please see our [CONTRIBUTING](CONTRIBUTING.md) page for details, including how to build and run private a version of WSLg. # Reporting a non-security issues For non-security related issues, such as reporting a bug or making a suggestion for a new feature, please use this project's [issues tracker](https://github.com/microsoft/wslg/issues). # Reporting security issues To report security issues with WSLg or any other Microsoft products, please follow the instructions detailed [here](SECURITY.md). # Trademarks This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies. ================================================ FILE: SECURITY.md ================================================ ## Security Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. ## Preferred Languages We prefer all communications to be in English. ## Policy Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). ================================================ FILE: WSLDVCPlugin/RegisterWSLPlugin.reg ================================================ Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default\AddIns\WSLDVCPlugin] "Name"="C:\\WINDOWS\\system32\\WSLDVCPlugin.dll" ================================================ FILE: WSLDVCPlugin/UpdateRCVersion.ps1 ================================================ $version = [string](gitversion /showvariable AssemblySemFileVer) $versionComma = $version.Replace(".", ",") $informationalVersion = [string](gitversion /showvariable InformationalVersion) $content = (Get-Content -Encoding "windows-1252" -Path ".\WSLDVCPlugin.rc") $content = $content.Replace("1,0,0,1", $versionComma); $content = $content.Replace("1.0.0.1", $version); $content = $content.Replace("InformationalVersion", $InformationalVersion); Set-Content -Encoding "windows-1252" -Path ".\WSLDVCPlugin.rc" -Value $content ================================================ FILE: WSLDVCPlugin/WSLDVCCallback.cpp ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include #include "utils.h" #include "rdpapplist.h" #include "WSLDVCCallback.h" #include "WSLDVCFileDB.h" constexpr LPCWSTR c_WSL_registry_path = L"Software\\Microsoft\\Windows\\CurrentVersion\\Lxss"; constexpr LPCWSTR c_WSLg_window_id = L"WslgServerWindowId"; constexpr LPCWSTR c_Working_dir = L"%windir%\\system32"; // // This channel simply sends all the received messages back to the server. // class WSLDVCCallback : public RuntimeClass< RuntimeClassFlags, IWTSVirtualChannelCallback> { public: HRESULT RuntimeClassInitialize( IWTSVirtualChannel* pChannel ) { HRESULT hr = WSLDVCFileDB_CreateInstance(NULL, &m_spFileDB); if (SUCCEEDED(hr)) { m_spChannel = pChannel; InitializeCriticalSection(&m_crit); m_bCriticalSectionInitialized = true; } return hr; } HRESULT ReadAppListHeader( _Inout_ UINT64 *size, _Inout_ const BYTE** buffer, _Out_ RDPAPPLIST_HEADER *appListHeader ) { const BYTE* cur; UINT64 len; assert(size); assert(buffer); assert(appListHeader); cur = *buffer; len = *size; ReadUINT32(appListHeader->cmdId, cur, len); ReadUINT32(appListHeader->length, cur, len); *buffer = cur; *size = len; return S_OK; Error_Read: return E_FAIL; } HRESULT ReadAppListServerCaps( _Inout_ UINT64 *size, _Inout_ const BYTE** buffer, _Out_ RDPAPPLIST_SERVER_CAPS_PDU *serverCaps ) { const BYTE* cur; UINT64 len; assert(size); assert(buffer); assert(serverCaps); cur = *buffer; len = *size; ReadUINT16(serverCaps->version, cur, len); ReadSTRING(serverCaps->appListProviderName, cur, len, true); if (serverCaps->version >= 4) { ReadSTRING(serverCaps->appListProviderUniqueId, cur, len, true); } *buffer = cur; *size = len; return S_OK; Error_Read: return E_FAIL; } HRESULT ReadAppListUpdate( _Inout_ UINT64* size, _Inout_ const BYTE** buffer, _Out_ RDPAPPLIST_UPDATE_APPLIST_PDU* updateAppList ) { const BYTE* cur; UINT64 len; assert(size); assert(buffer); assert(updateAppList); cur = *buffer; len = *size; ReadUINT32(updateAppList->flags, cur, len); CheckRequiredFlags(updateAppList->flags, (RDPAPPLIST_FIELD_ID | RDPAPPLIST_FIELD_EXECPATH | RDPAPPLIST_FIELD_DESC)); if (updateAppList->flags & RDPAPPLIST_FIELD_ID) { ReadSTRING(updateAppList->appId, cur, len, true); } if (updateAppList->flags & RDPAPPLIST_FIELD_GROUP) { ReadSTRING(updateAppList->appGroup, cur, len, false); } if (updateAppList->flags & RDPAPPLIST_FIELD_EXECPATH) { ReadSTRING(updateAppList->appExecPath, cur, len, true); } if (updateAppList->flags & RDPAPPLIST_FIELD_WORKINGDIR) { ReadSTRING(updateAppList->appWorkingDir, cur, len, false); } if (updateAppList->flags & RDPAPPLIST_FIELD_DESC) { ReadSTRING(updateAppList->appDesc, cur, len, true); } *buffer = cur; *size = len; return S_OK; Error_Read: return E_FAIL; } HRESULT ReadAppListIconData( _Inout_ UINT64* size, _Inout_ const BYTE** buffer, _Out_ RDPAPPLIST_ICON_DATA* iconData ) { const BYTE* cur; HRESULT hr; UINT64 len; ICON_HEADER* pIconHeader; BITMAPINFOHEADER* pIconBitmapInfo; void* pIconBits; assert(size); assert(buffer); assert(iconData); cur = *buffer; len = *size; ZeroMemory(iconData, sizeof(*iconData)); ReadUINT32(iconData->flags, cur, len); ReadUINT32(iconData->iconWidth, cur, len); ReadUINT32(iconData->iconHeight, cur, len); ReadUINT32(iconData->iconStride, cur, len); ReadUINT32(iconData->iconBpp, cur, len); ReadUINT32(iconData->iconFormat, cur, len); ReadUINT32(iconData->iconBitsLength, cur, len); hr = UIntAdd(sizeof(ICON_HEADER) + sizeof(BITMAPINFOHEADER), iconData->iconBitsLength, &iconData->iconFileSize); if (FAILED(hr)) { return hr; } iconData->iconFileData = (BYTE*)LocalAlloc(LPTR, iconData->iconFileSize); if (!iconData->iconFileData) { return E_OUTOFMEMORY; } pIconHeader = (ICON_HEADER*)iconData->iconFileData; pIconBitmapInfo = (BITMAPINFOHEADER*)(pIconHeader + 1); pIconBits = (void*)(pIconBitmapInfo + 1); ReadBYTES(pIconBits, cur, iconData->iconBitsLength, len); pIconHeader->idReserved = 0; // Reserved (must be 0) pIconHeader->idType = 1; // Resource Type (1 for icons) pIconHeader->idCount = 1; // How many images? pIconHeader->idEntries[0].bWidth = (BYTE)iconData->iconWidth; // Width, in pixels, of the image pIconHeader->idEntries[0].bHeight = (BYTE)iconData->iconHeight; // Height, in pixels, of the image pIconHeader->idEntries[0].bColorCount = 0; // Number of colors in image (0 if >=8bpp) pIconHeader->idEntries[0].bReserved = 0; // Reserved ( must be 0) pIconHeader->idEntries[0].wPlanes = 0; // Color Planes pIconHeader->idEntries[0].wBitCount = (WORD)iconData->iconBpp; // Bits per pixel pIconHeader->idEntries[0].dwBytesInRes = sizeof(BITMAPINFOHEADER) + iconData->iconBitsLength; // How many bytes in this resource? pIconHeader->idEntries[0].dwImageOffset = sizeof(ICON_HEADER); // Where in the file is this image? pIconBitmapInfo->biSize = sizeof(BITMAPINFOHEADER); pIconBitmapInfo->biWidth = iconData->iconWidth; pIconBitmapInfo->biHeight = iconData->iconHeight * 2; pIconBitmapInfo->biPlanes = 1; pIconBitmapInfo->biBitCount = (WORD)iconData->iconBpp; pIconBitmapInfo->biCompression = BI_RGB; pIconBitmapInfo->biSizeImage = iconData->iconBitsLength; pIconBitmapInfo->biXPelsPerMeter = 0; pIconBitmapInfo->biYPelsPerMeter = 0; pIconBitmapInfo->biClrUsed = 0; pIconBitmapInfo->biClrImportant = 0; #ifdef _DEBUG // Verify ICON. { HICON hIcon = CreateIconFromResource((BYTE*)pIconBitmapInfo, iconData->iconFileSize - sizeof(ICON_HEADER), TRUE, 0x00030000); if (!hIcon) { DebugPrint(L"CreateIconFromResource() failed\n"); } else { DestroyIcon(hIcon); } } #endif // _DEBUG *buffer = cur; *size = len; return S_OK; Error_Read: if (iconData->iconFileData) { LocalFree(iconData->iconFileData); } ZeroMemory(iconData, sizeof(*iconData)); return E_FAIL; } HRESULT ReadAppListDelete( _Inout_ UINT64* size, _Inout_ const BYTE** buffer, _Out_ RDPAPPLIST_DELETE_APPLIST_PDU* deleteAppList ) { const BYTE* cur; UINT64 len; assert(size); assert(buffer); assert(deleteAppList); cur = *buffer; len = *size; ReadUINT32(deleteAppList->flags, cur, len); CheckRequiredFlags(deleteAppList->flags, RDPAPPLIST_FIELD_ID); if (deleteAppList->flags & RDPAPPLIST_FIELD_ID) { ReadSTRING(deleteAppList->appId, cur, len, true); } if (deleteAppList->flags & RDPAPPLIST_FIELD_GROUP) { ReadSTRING(deleteAppList->appGroup, cur, len, false); } *buffer = cur; *size = len; return S_OK; Error_Read: return E_FAIL; } HRESULT ReadAssociateWindowId( _Inout_ UINT64* size, _Inout_ const BYTE** buffer, _Out_ RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU* associateWindowId ) { const BYTE* cur; UINT64 len; assert(size); assert(buffer); assert(associateWindowId); cur = *buffer; len = *size; ReadUINT32(associateWindowId->flags, cur, len); CheckRequiredFlags(associateWindowId->flags, RDPAPPLIST_FIELD_ID | RDPAPPLIST_FIELD_WINDOW_ID); ReadUINT32(associateWindowId->appWindowId, cur, len); if (associateWindowId->flags & RDPAPPLIST_FIELD_ID) { ReadSTRING(associateWindowId->appId, cur, len, true); } if (associateWindowId->flags & RDPAPPLIST_FIELD_GROUP) { ReadSTRING(associateWindowId->appGroup, cur, len, false); } if (associateWindowId->flags & RDPAPPLIST_FIELD_EXECPATH) { ReadSTRING(associateWindowId->appExecPath, cur, len, true); } if (associateWindowId->flags & RDPAPPLIST_FIELD_DESC) { ReadSTRING(associateWindowId->appDesc, cur, len, true); } *buffer = cur; *size = len; return S_OK; Error_Read: return E_FAIL; } HRESULT OnSyncStart() { DebugPrint(L"OnSyncStart():\n"); if (m_spFileDBSync.Get()) { DebugPrint(L"Server asks start sync, but already in sync mode.\n"); return E_FAIL; } HRESULT hr = WSLDVCFileDB_CreateInstance(NULL, &m_spFileDBSync); if (FAILED(hr)) { return hr; } // Add all files under menu path at sync start. // This will also adds icon file pointed by .lnk file. // Any files not reported during sync, will be removed at end. m_spFileDBSync->addAllFilesAsFileIdAt(m_appMenuPath); return S_OK; } HRESULT OnSyncEnd(bool cleanupFiles = true) { DebugPrint(L"OnSyncEnd():\n"); if (m_spFileDBSync.Get()) { if (cleanupFiles) { m_spFileDBSync->deleteAllFileIdFiles(); } m_spFileDBSync->OnClose(); m_spFileDBSync = nullptr; } return S_OK; } HRESULT OnCaps( _Inout_ UINT64* size, _Inout_ const BYTE** buffer ) { HRESULT hr; WCHAR szAppProviderGUID[40]; // Buffer read scope { const BYTE* cur; UINT64 len; assert(size); assert(buffer); cur = *buffer; len = *size; hr = ReadAppListServerCaps(&len, &cur, &m_serverCaps); if (FAILED(hr)) { return hr; } *buffer = cur; *size = len; } memcpy(m_appProvider, m_serverCaps.appListProviderName, m_serverCaps.appListProviderNameLength); if ((wcsstr(m_appProvider, L"..") != NULL) || (wcsstr(m_appProvider, L"\"") != NULL) || (wcsstr(m_appProvider, L" ") != NULL)) { DebugPrint(L"provider name can't contain '..', '\"' or space, %s\n", m_appProvider); return E_FAIL; } if (m_serverCaps.version >= 4) { if ((swprintf_s(szAppProviderGUID, ARRAYSIZE(szAppProviderGUID), L"{%s}", &m_serverCaps.appListProviderUniqueId[0]) < 0) || (szAppProviderGUID[0] == L'\0')) { DebugPrint(L"swprintf_s failed on %s\n", m_serverCaps.appListProviderUniqueId); return E_FAIL; } hr = CLSIDFromString(szAppProviderGUID, &m_appProviderGUID); if (FAILED(hr)) { DebugPrint(L"CLSIDFromString failed on %s\n", szAppProviderGUID); return hr; } } hr = BuildMenuPath(ARRAYSIZE(m_appMenuPath), m_appMenuPath, m_appProvider, true); if (FAILED(hr)) { return hr; } DebugPrint(L"AppMenuPath: %s\n", m_appMenuPath); hr = BuildIconPath(ARRAYSIZE(m_iconPath), m_iconPath, m_appProvider, true); if (FAILED(hr)) { return hr; } DebugPrint(L"IconPath: %s\n", m_iconPath); // Read the registry key that declares where the WSL package is installed. HKEY key; hr = HRESULT_FROM_WIN32(RegOpenKeyExW(HKEY_LOCAL_MACHINE, c_WSL_registry_path, 0, KEY_READ, &key)); if (FAILED(hr)) { DebugPrint(L"RegOpenKeyExW failed\n"); return hr; } DWORD valueSize = sizeof(m_expandedPathObj); hr = HRESULT_FROM_WIN32(RegGetValueW(key, L"Msi", L"InstallLocation", (RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ), nullptr, m_expandedPathObj, &valueSize)); RegCloseKey(key); if (FAILED(hr)) { DebugPrint(L"RegGetValueW failed\n"); return hr; } if (wcscat_s(m_expandedPathObj, ARRAYSIZE(m_expandedPathObj), L"\\wslg.exe") != 0) { return E_FAIL; } DebugPrint(L"WSLg.exe: %s\n", m_expandedPathObj); if (ExpandEnvironmentStringsW(c_Working_dir, m_expandedWorkingDir, ARRAYSIZE(m_expandedWorkingDir)) == 0) { DebugPrint(L"Failed to expand working dir: %s : %d\n", c_Working_dir, GetLastError()); return E_FAIL; } DebugPrint(L"WSL.exe working dir: %s\n", m_expandedWorkingDir); if (!GetLocaleName(m_clientLanguageId, sizeof m_clientLanguageId)) { strcpy_s(m_clientLanguageId, sizeof m_clientLanguageId, "en_US"); } // Reply back header (8 bytes) + version (2 bytes) to server. #pragma pack(push,1) struct { RDPAPPLIST_HEADER capsHeader; RDPAPPLIST_CLIENT_CAPS_PDU caps; } replyBuf = {}; #pragma pack(pop) replyBuf.capsHeader.cmdId = RDPAPPLIST_CMDID_CAPS; if (m_serverCaps.version >= RDPAPPLIST_CHANNEL_VERSION) { replyBuf.capsHeader.length = sizeof replyBuf; replyBuf.caps.version = RDPAPPLIST_CHANNEL_VERSION; if (strncpy_s(replyBuf.caps.clientLanguageId, m_clientLanguageId, sizeof replyBuf.caps.clientLanguageId) != 0) { return E_FAIL; } } else { DebugPrint(L"Invalid server version : %d\n", m_serverCaps.version); hr = E_FAIL; } if (SUCCEEDED(hr)) { hr = m_spChannel->Write(replyBuf.capsHeader.length, (BYTE*)&replyBuf, nullptr); if (FAILED(hr)) { DebugPrint(L"m_spChannel->Write failed, hr = %x\n", hr); } } m_handShakeComplated = SUCCEEDED(hr) ? true : false; return hr; } HRESULT OnUpdateAppList( _Inout_ UINT64* size, _Inout_ const BYTE** buffer ) { HRESULT hr; RDPAPPLIST_UPDATE_APPLIST_PDU updateAppList = {}; RDPAPPLIST_ICON_DATA iconData = {}; WCHAR linkPath[MAX_PATH] = {}; WCHAR iconPath[MAX_PATH] = {}; WCHAR exeArgs[MAX_PATH] = {}; WCHAR key[MAX_PATH] = {}; bool hasIcon = false; // Buffer read scope { const BYTE* cur; UINT64 len; assert(size); assert(buffer); cur = *buffer; len = *size; hr = ReadAppListUpdate(&len, &cur, &updateAppList); if (FAILED(hr)) { return hr; } if (updateAppList.flags & RDPAPPLIST_FIELD_ICON) { hr = ReadAppListIconData(&len, &cur, &iconData); if (FAILED(hr)) { return hr; } } *buffer = cur; *size = len; } if (updateAppList.flags & RDPAPPLIST_HINT_SYNC_START) { hr = OnSyncStart(); if (FAILED(hr)) { return hr; } assert(m_spFileDBSync.Get()); } if (updateAppList.flags & (RDPAPPLIST_HINT_SYNC | RDPAPPLIST_HINT_SYNC_END)) { if (!m_spFileDBSync.Get()) { DebugPrint(L"Server sends sync or sync end flag without starting sync mode. flags %x\n", updateAppList.flags); return E_FAIL; } } if (updateAppList.appGroupLength) { if ((wcsstr(updateAppList.appGroup, L"..") != NULL) || (wcsstr(updateAppList.appGroup, L"\"") != NULL)) { DebugPrint(L"group name can't contain '..' or '\"', %s\n", updateAppList.appGroup); return E_FAIL; } } if ((wcsstr(updateAppList.appId, L"..") != NULL) || (wcsstr(updateAppList.appId, L"\"") != NULL)) { DebugPrint(L"app id can't contain '..' or '\"', %s\n", updateAppList.appId); return E_FAIL; } if ((wcsstr(updateAppList.appDesc, L"..") != NULL) || (wcsstr(updateAppList.appDesc, L"\"") != NULL)) { DebugPrint(L"app desc can't contain '..' or '\"', %s\n", updateAppList.appDesc); return E_FAIL; } // Double quote (") is valid file/path char in Linux, but currently wsl.exe/wslg.exe // can't handle to give the path contains " with --cd options, thus until this get fixed, // block the path contains ". if ((wcsstr(updateAppList.appWorkingDir, L"\"") != NULL)) { DebugPrint(L"app working dir can't contain '\"', %s\n", updateAppList.appWorkingDir); return E_FAIL; } if (updateAppList.flags & RDPAPPLIST_FIELD_ICON) { if (wcscpy_s(iconPath, ARRAYSIZE(iconPath), m_iconPath) != 0) { return E_FAIL; } if (!CreateDirectoryW(iconPath, NULL)) { if (ERROR_ALREADY_EXISTS != GetLastError()) { DebugPrint(L"Failed to create %s\n", iconPath); return E_FAIL; } } if (updateAppList.appGroupLength) { if ((wcscat_s(iconPath, ARRAYSIZE(iconPath), L"\\") != 0) || (wcscat_s(iconPath, ARRAYSIZE(iconPath), updateAppList.appGroup) != 0)) { return E_FAIL; } if (!CreateDirectoryW(iconPath, NULL)) { if (ERROR_ALREADY_EXISTS != GetLastError()) { DebugPrint(L"Failed to create %s\n", iconPath); return E_FAIL; } } } if ((wcscat_s(iconPath, ARRAYSIZE(iconPath), L"\\") != 0) || (wcscat_s(iconPath, ARRAYSIZE(iconPath), updateAppList.appId) != 0) || (wcscat_s(iconPath, ARRAYSIZE(iconPath), L".ico") != 0)) { return E_FAIL; } } if (wcscpy_s(linkPath, ARRAYSIZE(linkPath), m_appMenuPath) != 0) { return E_FAIL; } if (!CreateDirectoryW(linkPath, NULL)) { if (ERROR_ALREADY_EXISTS != GetLastError()) { DebugPrint(L"Failed to create %s\n", linkPath); return E_FAIL; } } if (updateAppList.appGroupLength) { if ((wcscat_s(linkPath, ARRAYSIZE(linkPath), L"\\") != 0) || (wcscat_s(linkPath, ARRAYSIZE(linkPath), updateAppList.appGroup) != 0)) { return E_FAIL; } if (!CreateDirectoryW(linkPath, NULL)) { if (ERROR_ALREADY_EXISTS != GetLastError()) { DebugPrint(L"Failed to create %s\n", linkPath); return E_FAIL; } } } // Use description to name link file since this is name shows up // at StartMenu UI. SHSetLocalizedName can't be uses since this // is not in resource. if ((wcscat_s(linkPath, ARRAYSIZE(linkPath), L"\\") != 0) || (wcscat_s(linkPath, ARRAYSIZE(linkPath), updateAppList.appDesc) != 0) || (wcscat_s(linkPath, ARRAYSIZE(linkPath), L".lnk") != 0)) { return E_FAIL; } // build wsl.exe/wslg.exe command line. // -d : distro name, space is not allowed in distro name, // thus wrapping by double-quoto is not accepted by wsl.exe/wslg.exe // --cd : current directory, wrap by double-quote. // -- : executable path with arguments. All string after '--' will be treated as Linux command line, // thus wrapping by double-quoto is not accepted by wsl.exe/wslg.exe if ((swprintf_s(exeArgs, ARRAYSIZE(exeArgs), L"-d %s --cd \"%s\" -- %s", m_appProvider, updateAppList.appWorkingDirLength ? updateAppList.appWorkingDir : L"~", updateAppList.appExecPath) < 0) || (exeArgs[0] == L'\0')) { return E_FAIL; } key[0] = L'\0'; if (updateAppList.appGroupLength) { if ((wcscat_s(key, ARRAYSIZE(key), updateAppList.appGroup) != 0) || (wcscat_s(key, ARRAYSIZE(key), L"\\") != 0)) { return E_FAIL; } } if (wcscat_s(key, ARRAYSIZE(key), updateAppList.appId) != 0) { return E_FAIL; } hr = m_spFileDB->OnFileAdded(key, linkPath, iconPath, m_expandedPathObj, exeArgs); if (FAILED(hr)) { return hr; } if (updateAppList.flags & RDPAPPLIST_FIELD_ICON) { if (SUCCEEDED(CreateIconFile(iconData.iconFileData, iconData.iconFileSize, iconPath))) { hasIcon = true; } else { DebugPrint(L"Failed to create icon file %s\n", iconPath); // Icon is optional, so keep going. } } /* ignore error from create link */ /* This also updates if m_expandedPathObj (wsl.exe or wslg.exe) is changed. */ CreateShellLink((LPCWSTR)linkPath, (LPCWSTR)m_expandedPathObj, (LPCWSTR)exeArgs, (LPCWSTR)m_expandedWorkingDir, (LPCWSTR)updateAppList.appDesc, hasIcon ? (LPCWSTR)iconPath : NULL); if (updateAppList.flags & RDPAPPLIST_HINT_SYNC) { // During sync mode, remove added file to sync DB, so these won't be cleaned up at end. assert(m_spFileDBSync.Get()); m_spFileDBSync->OnFileRemoved(linkPath); if (hasIcon) { m_spFileDBSync->OnFileRemoved(iconPath); } } if (updateAppList.flags & RDPAPPLIST_HINT_SYNC_END) { OnSyncEnd(); } return S_OK; } HRESULT OnDeleteAppList( _Inout_ UINT64* size, _Inout_ const BYTE** buffer ) { HRESULT hr; RDPAPPLIST_DELETE_APPLIST_PDU deleteAppList = {}; WCHAR linkPath[MAX_PATH] = {}; WCHAR iconPath[MAX_PATH] = {}; WCHAR key[MAX_PATH] = {}; // Buffer read scope { const BYTE* cur; UINT64 len; assert(size); assert(buffer); cur = *buffer; len = *size; hr = ReadAppListDelete(&len, &cur, &deleteAppList); if (FAILED(hr)) { return hr; } *buffer = cur; *size = len; } key[0] = L'\0'; if (deleteAppList.appGroupLength) { if ((wcscpy_s(key, ARRAYSIZE(key), deleteAppList.appGroup) != 0) || (wcscat_s(key, ARRAYSIZE(key), L"\\") != 0)) { return E_FAIL; } } if (wcscat_s(key, ARRAYSIZE(key), deleteAppList.appId) != 0) { return E_FAIL; } hr = m_spFileDB->FindFiles(key, linkPath, ARRAYSIZE(linkPath), iconPath, ARRAYSIZE(iconPath)); if (FAILED(hr)) { DebugPrint(L"OnDeleteAppList(): key %s not found\n", key); return E_FAIL; } if ((linkPath[0] != L'\0') && !DeleteFileW(linkPath)) { DebugPrint(L"DeleteFile(%s) failed, error %x\n", linkPath, GetLastError()); } DebugPrint(L"Delete Path Link: %s\n", linkPath); if ((iconPath[0] != L'\0') && !DeleteFileW(iconPath)) { DebugPrint(L"DeleteFile(%s) failed, error %x\n", iconPath, GetLastError()); } DebugPrint(L"Delete Icon Path: %s\n", iconPath); m_spFileDB->OnFileRemoved(key); return S_OK; } typedef struct _WslgWindowData { UINT64 windowId; LPCWSTR displayName; LPCWSTR relaunchCommandline; LPCWSTR iconPath; } WslgWindowData; static BOOL CALLBACK FindWslgWindow( _In_ HWND hwnd, _In_ LPARAM args ) { WslgWindowData* wndData = (WslgWindowData*)args; HANDLE windowId = GetPropW(hwnd, c_WSLg_window_id); if (windowId == (HANDLE)wndData->windowId) { UpdateTaskBarInfo(hwnd, wndData->relaunchCommandline, wndData->displayName, wndData->iconPath); return FALSE; /* found it, stop enum */ } return TRUE; /* keep enum next one */ } HRESULT OnAssociateWindowId( _Inout_ UINT64* size, _Inout_ const BYTE** buffer ) { HRESULT hr; RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU associateWindowId = {}; WCHAR tmpBuf[MAX_PATH] = {}; WCHAR tmpPath[MAX_PATH] = {}; WCHAR tmpArgs[MAX_PATH] = {}; WCHAR iconPath[MAX_PATH] = {}; WCHAR exePath[MAX_PATH] = {}; if (m_serverCaps.version < 4) { DebugPrint(L"associate window id requires version 4 or newer: current version: %d\n", m_serverCaps.version < 4); return E_FAIL; } // Buffer read scope { const BYTE* cur; UINT64 len; assert(size); assert(buffer); cur = *buffer; len = *size; hr = ReadAssociateWindowId(&len, &cur, &associateWindowId); if (FAILED(hr)) { return hr; } *buffer = cur; *size = len; } /* construct full unique window id */ UINT64 windowId = (UINT64)m_appProviderGUID.Data1; windowId <<= 32; windowId |= associateWindowId.appWindowId; if (windowId == 0LL) { DebugPrint(L"windowId can't be 0\n"); return E_FAIL; } tmpBuf[0] = L'\0'; if (associateWindowId.appGroupLength) { if ((wcscpy_s(tmpBuf, ARRAYSIZE(tmpBuf), associateWindowId.appGroup) != 0) || (wcscat_s(tmpBuf, ARRAYSIZE(tmpBuf), L"\\") != 0)) { return E_FAIL; } } if (wcscat_s(tmpBuf, ARRAYSIZE(tmpBuf), associateWindowId.appId) != 0) { return E_FAIL; } DebugPrint(L"AssociateWindowId AppId: %s\n", tmpBuf); DebugPrint(L" Desc: %s\n", associateWindowId.appDesc); DebugPrint(L" Full Unique WindowId: 0x%p\n", windowId); /* somehow PRIx64 doesn't work here */ DebugPrint(L" CmdLine from server: %s\n", associateWindowId.appExecPath); hr = m_spFileDB->FindFiles(tmpBuf, NULL, 0, iconPath, ARRAYSIZE(iconPath), tmpPath, ARRAYSIZE(tmpPath), tmpArgs, ARRAYSIZE(tmpArgs)); if (SUCCEEDED(hr)) { if ((swprintf_s(exePath, ARRAYSIZE(exePath), L"%s %s", tmpPath, tmpArgs) < 0) || (exePath[0] == L'\0')) { return E_FAIL; } } else if (associateWindowId.appExecPath[0] != '\0') { /* if key is not present, reconstruct using server side exe path */ if ((swprintf_s(exePath, ARRAYSIZE(exePath), L"%s -d %s --cd \"%s\" -- %s", m_expandedPathObj, m_appProvider, L"~", associateWindowId.appExecPath) < 0) || (exePath[0] == L'\0')) { return E_FAIL; } /* TODO: must provide default icon */ } DebugPrint(L" CmdLine at local: %s\n", exePath); DebugPrint(L" Icon Path at local: %s\n", iconPath); WslgWindowData data = {}; data.windowId = windowId; data.displayName = associateWindowId.appDesc[0] != '\0' ? associateWindowId.appDesc : NULL; data.relaunchCommandline = exePath[0] != '\0' ? exePath : NULL; data.iconPath = iconPath[0] != '\0' ? iconPath : NULL; EnumWindows(FindWslgWindow, (LPARAM)&data); return S_OK; } // // IWTSVirtualChannelCallback interface // STDMETHODIMP OnDataReceived( ULONG cbSize, __RPC__in_ecount_full(cbSize) BYTE* pBuffer ) { HRESULT hr = S_OK; const BYTE* cur = pBuffer; UINT64 len = cbSize; if (m_bChannelClosed) { return S_OK; } EnterCriticalSection(&m_crit); DebugPrint(L"OnDataReceived enter, size = %d\n", len); while (len && !m_bChannelClosed) { RDPAPPLIST_HEADER appListHeader = {}; hr = ReadAppListHeader(&len, &cur, &appListHeader); if (FAILED(hr)) { DebugPrint(L"Failed to read applist header\n"); break; } if (appListHeader.cmdId == RDPAPPLIST_CMDID_CAPS) { hr = OnCaps(&len, &cur); if (FAILED(hr)) { break; } } else if (m_handShakeComplated == false) { // Caps exchange must be completed before processing // any other messages. DebugPrint(L"HandShake is not completed\n"); hr = E_FAIL; break; } else if (appListHeader.cmdId == RDPAPPLIST_CMDID_UPDATE_APPLIST) { hr = OnUpdateAppList(&len, &cur); if (FAILED(hr)) { break; } } else if (appListHeader.cmdId == RDPAPPLIST_CMDID_DELETE_APPLIST) { hr = OnDeleteAppList(&len, &cur); if (FAILED(hr)) { break; } } else if (appListHeader.cmdId == RDPAPPLIST_CMDID_DELETE_APPLIST_PROVIDER) { // Nothing to do. } else if (appListHeader.cmdId == RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID) { hr = OnAssociateWindowId(&len, &cur); if (FAILED(hr)) { break; } } else { DebugPrint(L"Unknown command id:%d, Length %d\n", appListHeader.cmdId, appListHeader.length); hr = E_FAIL; break; } assert(len <= cbSize); } DebugPrint(L"OnDataReceived returns hr = %x\n", hr); LeaveCriticalSection(&m_crit); return hr; } STDMETHODIMP OnClose() { m_bChannelClosed = true; EnterCriticalSection(&m_crit); DebugPrint(L"OnClose enter\n"); // Make sure sync mode is cancelled. OnSyncEnd(false); m_spFileDB->OnClose(); m_spFileDB = nullptr; DebugPrint(L"OnClose returns hr = %x\n", S_OK); LeaveCriticalSection(&m_crit); return S_OK; } protected: virtual ~WSLDVCCallback() { if (m_bCriticalSectionInitialized) { DeleteCriticalSection(&m_crit); } } private: ComPtr m_spChannel; ComPtr m_spFileDB; ComPtr m_spFileDBSync; // valid only during sync. CRITICAL_SECTION m_crit = {}; bool m_bCriticalSectionInitialized = false; bool m_bChannelClosed = false; bool m_handShakeComplated = false; RDPAPPLIST_SERVER_CAPS_PDU m_serverCaps = {}; GUID m_appProviderGUID = {}; WCHAR m_appProvider[MAX_PATH] = {}; WCHAR m_appMenuPath[MAX_PATH] = {}; WCHAR m_iconPath[MAX_PATH] = {}; WCHAR m_expandedPathObj[MAX_PATH] = {}; WCHAR m_expandedWorkingDir[MAX_PATH] = {}; CHAR m_clientLanguageId[RDPAPPLIST_LANG_SIZE] = {}; }; HRESULT WSLDVCCallback_CreateInstance( IWTSVirtualChannel* pChannel, IWTSVirtualChannelCallback** ppCallback ) { return MakeAndInitialize(ppCallback, pChannel); } ================================================ FILE: WSLDVCPlugin/WSLDVCCallback.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once #include HRESULT WSLDVCCallback_CreateInstance( IWTSVirtualChannel* pChannel, IWTSVirtualChannelCallback** ppCallback ); ================================================ FILE: WSLDVCPlugin/WSLDVCFileDB.cpp ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "utils.h" #include "WSLDVCFileDB.h" #include #include class fileEntry { public: wstring getFileId() const { return m_fileId; } wstring getLinkFilePath() const { return m_linkFilePath; } wstring getIconFilePath() const { return m_iconFilePath; } wstring getExeFilePath() const { return m_exeFilePath; } wstring getExeFileArgs() const { return m_exeFileArgs; } //fileEntry(const fileEntry& r) //{ // m_fileId = r.m_fileId; // m_linkFilePath = r.m_linkFilePath; // m_iconFilePath = r.m_iconFilePath; // m_exeFilePath = r.m_exeFilePath; // m_exeFileArgs = r.m_exeFileArgs; //} fileEntry(const wchar_t* fileId, const wchar_t* linkPath = L"", const wchar_t* iconPath = L"", const wchar_t* exePath = L"", const wchar_t* exeArgs = L"") { assert(fileId); m_fileId = fileId; m_linkFilePath = linkPath; m_iconFilePath = iconPath; m_exeFilePath = exePath; m_exeFileArgs = exeArgs; } bool operator< (LPWSTR key) const { wstring r = key; return getFileId() < r; } bool operator< (const fileEntry& r) const { return getFileId() < r.getFileId(); } private: wstring m_fileId; wstring m_linkFilePath; wstring m_iconFilePath; wstring m_exeFilePath; wstring m_exeFileArgs; }; class WSLDVCFileDB : public RuntimeClass< RuntimeClassFlags, IWSLDVCFileDB> { public: HRESULT RuntimeClassInitialize( void* pContext ) { UNREFERENCED_PARAMETER(pContext); DebugPrint(L"RuntimeClassInitialize(): WSLDVCFileDB iniitalized: %p\n", this); return S_OK; } STDMETHODIMP OnLnkFileAdded(_In_z_ LPCWSTR lnkFile) { WCHAR iconFile[MAX_PATH] = {}; { fileEntry fileEntry(lnkFile); m_fileEntries.insert(fileEntry); } if (SUCCEEDED(GetIconFileFromShellLink(ARRAYSIZE(iconFile), iconFile, lnkFile))) { fileEntry fileEntry(iconFile); m_fileEntries.insert(fileEntry); } DebugPrint(L"OnLnkFileAdded():\n"); DebugPrint(L"\tlnkFile: %s\n", lnkFile); DebugPrint(L"\ticonFile: %s\n", iconFile); return S_OK; } STDMETHODIMP OnFileAdded(_In_z_ LPCWSTR key, _In_opt_z_ LPCWSTR linkFilePath, _In_opt_z_ LPCWSTR iconFilePath, _In_opt_z_ LPCWSTR exeFilePath, _In_opt_z_ LPCWSTR exeFileArgs) { HRESULT hr = S_OK; set::iterator it; std::pair::iterator, bool> p; DebugPrint(L"OnFileAdded():\n"); DebugPrint(L"\tkey: %s\n", key); if (linkFilePath && lstrlenW(linkFilePath)) { DebugPrint(L"\tlinkFilePath: %s\n", linkFilePath); } else { linkFilePath = L""; } if (iconFilePath && lstrlenW(iconFilePath)) { DebugPrint(L"\ticonFilePath: %s\n", iconFilePath); } else { iconFilePath = L""; } if (exeFilePath && lstrlenW(exeFilePath)) { DebugPrint(L"\texeFilePath: %s\n", exeFilePath); } else { exeFilePath = L""; } if (exeFileArgs && lstrlenW(exeFileArgs)) { DebugPrint(L"\texeFileArgs: %s\n", exeFileArgs); } else { exeFileArgs = L""; } fileEntry fileEntry(key, linkFilePath, iconFilePath, exeFilePath, exeFileArgs); p = m_fileEntries.insert(fileEntry); if (!p.second) { if (p.first->getLinkFilePath().compare(linkFilePath) != 0 || p.first->getIconFilePath().compare(iconFilePath) != 0 || p.first->getExeFilePath().compare(exeFilePath) != 0 || p.first->getExeFileArgs().compare(exeFileArgs) != 0) { DebugPrint(L"\tKey: %s is already exists and has different path data\n", key); DebugPrint(L"\tlinkFilePath: %s\n", p.first->getLinkFilePath().c_str()); DebugPrint(L"\ticonFilePath: %s\n", p.first->getIconFilePath().c_str()); DebugPrint(L"\texeFilePath: %s\n", p.first->getExeFilePath().c_str()); DebugPrint(L"\texeFileArgs: %s\n", p.first->getExeFileArgs().c_str()); // TODO: implement update existing by erase and add. // DebugAssert(false); hr = E_FAIL; } } return hr; } STDMETHODIMP OnFileRemoved(_In_z_ LPCWSTR key) { HRESULT hr = S_OK; set::iterator it; DebugPrint(L"OnFileRemoved()\n"); DebugPrint(L"\tkey: %s\n", key); it = m_fileEntries.find(key); if (it != m_fileEntries.end()) { DebugPrint(L"\tKey found:\n"); if (it->getLinkFilePath().length()) { DebugPrint(L"\tlinkPath: %s\n", it->getLinkFilePath().c_str()); } if (it->getIconFilePath().length()) { DebugPrint(L"\ticonPath: %s\n", it->getIconFilePath().c_str()); } if (it->getExeFilePath().length()) { DebugPrint(L"\texePath: %s\n", it->getExeFilePath().c_str()); } if (it->getExeFileArgs().length()) { DebugPrint(L"\texeArgs: %s\n", it->getExeFileArgs().c_str()); } m_fileEntries.erase(it); } else { DebugPrint(L"Key not found\n"); hr = E_FAIL; } return hr; } STDMETHODIMP FindFiles( _In_z_ LPCWSTR key, _Out_writes_z_(linkFilePathSize) LPWSTR linkFilePath, UINT32 linkFilePathSize, _Out_writes_z_(iconFilePathSize) LPWSTR iconFilePath, UINT32 iconFilePathSize, _Out_writes_z_(exeFilePathSize) LPWSTR exeFilePath, UINT32 exeFilePathSize, _Out_writes_z_(exeFileArgsSize) LPWSTR exeFileArgs, UINT32 exeFileArgsSize) { set::iterator it; assert((linkFilePath && linkFilePathSize) || (linkFilePath == nullptr && linkFilePathSize == 0)); assert((iconFilePath && iconFilePathSize) || (iconFilePath == nullptr && iconFilePathSize == 0)); assert((exeFilePath && exeFilePathSize) || (exeFilePath == nullptr && exeFilePathSize == 0)); assert((exeFileArgs && exeFileArgsSize) || (exeFileArgs == nullptr && exeFileArgsSize == 0)); if (linkFilePath) *linkFilePath = NULL; if (iconFilePath) *iconFilePath = NULL; if (exeFilePath) *exeFilePath = NULL; if (exeFileArgs) *exeFileArgs = NULL; DebugPrint(L"FindFiles()\n"); DebugPrint(L"\tkey: %s\n", key); it = m_fileEntries.find(key); if (it != m_fileEntries.end()) { DebugPrint(L"\tKey found:\n"); DebugPrint(L"\tlinkPath: %s\n", it->getLinkFilePath().c_str()); DebugPrint(L"\ticonPath: %s\n", it->getIconFilePath().c_str()); DebugPrint(L"\texePath: %s\n", it->getExeFilePath().c_str()); DebugPrint(L"\texeArgs: %s\n", it->getExeFileArgs().c_str()); if (linkFilePath) { if (wcscpy_s(linkFilePath, linkFilePathSize, it->getLinkFilePath().c_str()) != 0) { return E_FAIL; } } if (iconFilePath) { if (wcscpy_s(iconFilePath, iconFilePathSize, it->getIconFilePath().c_str()) != 0) { return E_FAIL; } } if (exeFilePath) { if (wcscpy_s(exeFilePath, exeFilePathSize, it->getExeFilePath().c_str()) != 0) { return E_FAIL; } } if (exeFileArgs) { if (wcscpy_s(exeFileArgs, exeFileArgsSize, it->getExeFileArgs().c_str()) != 0) { return E_FAIL; } } return S_OK; } else { DebugPrint(L"\tKey NOT found:\n"); return E_FAIL; } } STDMETHODIMP addAllFilesAsFileIdAt(_In_z_ LPCWSTR path) { //DebugPrint(L"Dump directory: %s\n", path); return addAllSubFolderFiles(path); } STDMETHODIMP deleteAllFileIdFiles() { set::iterator it; DebugPrint(L"deleteAllFileIdFiles() - files to delete %d\n", m_fileEntries.size()); for (it = m_fileEntries.begin(); it != m_fileEntries.end(); ) { DebugPrint(L"\tDelete %s\n", it->getFileId().c_str()); if (!DeleteFileW(it->getFileId().c_str())) { DebugPrint(L"DeleteFile(%s) failed\n", it->getFileId().c_str()); } else { wstring::size_type found = it->getFileId().find_last_of(L"\\"); if (found != wstring::npos) { wstring dir = it->getFileId().substr(0, found); if (PathIsDirectoryEmptyW(dir.c_str())) { DebugPrint(L"%s is empty, removing\n", dir.c_str()); if (!RemoveDirectory(dir.c_str())) { DebugPrint(L"Failed to remove %s\n", dir.c_str()); } } } } it = m_fileEntries.erase(it); } return S_OK; } STDMETHODIMP OnClose() { DebugPrint(L"OnClose(): WSLDVCFileDB closed: %p\n", this); m_fileEntries.clear(); return S_OK; } protected: HRESULT addAllSubFolderFiles(_In_z_ const wchar_t* path) { WIN32_FIND_DATA data = {}; wstring s = path; s += L"\\*"; HANDLE hFind = FindFirstFile(s.c_str(), &data); do { wstring sub = path; sub += L"\\"; sub += data.cFileName; if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { wstring file = data.cFileName; if (file == L"." || file == L"..") { continue; } //DebugPrint(L"\tdirectory: %s\n", sub.c_str()); if (PathIsDirectoryEmptyW(sub.c_str())) { DebugPrint(L"%s is empty, removing\n", sub.c_str()); if (!RemoveDirectory(sub.c_str())) { DebugPrint(L"Failed to remove %s\n", sub.c_str()); } } else { addAllSubFolderFiles(sub.c_str()); } } else { LPCWSTR ext = PathFindExtensionW(data.cFileName); if (wcscmp(ext, L".lnk") == 0) { //DebugPrint(L"\t\tlnk file: %s\n", sub.c_str()); OnLnkFileAdded(sub.c_str()); } } } while (FindNextFile(hFind, &data)); FindClose(hFind); return S_OK; } virtual ~WSLDVCFileDB() { } private: set m_fileEntries; }; HRESULT WSLDVCFileDB_CreateInstance( void* pContext, IWSLDVCFileDB** ppFileDB ) { return MakeAndInitialize(ppFileDB, pContext); } ================================================ FILE: WSLDVCPlugin/WSLDVCFileDB.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once MIDL_INTERFACE("5802f934-1683-4e81-bb5a-7a0c29a2b1c7") IWSLDVCFileDB : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE addAllFilesAsFileIdAt( _In_z_ LPCWSTR path) = 0; virtual HRESULT STDMETHODCALLTYPE deleteAllFileIdFiles() = 0; virtual HRESULT STDMETHODCALLTYPE OnFileAdded( _In_z_ LPCWSTR key, _In_opt_z_ LPCWSTR linkFilePath, _In_opt_z_ LPCWSTR iconFilePath, _In_opt_z_ LPCWSTR exeFilePath, _In_opt_z_ LPCWSTR exeFileArgs) = 0; virtual HRESULT STDMETHODCALLTYPE OnFileRemoved( _In_z_ LPCWSTR key) = 0; virtual HRESULT STDMETHODCALLTYPE FindFiles( _In_z_ LPCWSTR key, _Out_writes_z_(linkFilePathSize) LPWSTR linkFilePath, UINT32 linkFilePathSize, _Out_writes_z_(iconFilePathSize) LPWSTR iconFilePath, UINT32 iconFilePathSize, _Out_writes_z_(exeFilePathSize) LPWSTR exeFilePath = nullptr, UINT32 exeFilePathSize = 0, _Out_writes_z_(exeFileArgsSize) LPWSTR exeFileArgs = nullptr, UINT32 exeFileArgsSize = 0) = 0; virtual HRESULT STDMETHODCALLTYPE OnClose(void) = 0; }; HRESULT WSLDVCFileDB_CreateInstance( void* pContext, IWSLDVCFileDB** ppFileDB ); ================================================ FILE: WSLDVCPlugin/WSLDVCListenerCallback.cpp ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "WSLDVCListenerCallback.h" #include "WSLDVCCallback.h" class WSLDVCListenerCallback : public RuntimeClass< RuntimeClassFlags, IWTSListenerCallback> { public: HRESULT RuntimeClassInitialize() { return S_OK; } // // IWTSListenerCallback interface // STDMETHODIMP OnNewChannelConnection( __RPC__in_opt IWTSVirtualChannel* pChannel, __RPC__in_opt BSTR data, __RPC__out BOOL* pbAccept, __RPC__deref_out_opt IWTSVirtualChannelCallback** ppCallback ) { UNREFERENCED_PARAMETER(data); HRESULT hr = WSLDVCCallback_CreateInstance(pChannel, ppCallback); if (SUCCEEDED(hr)) { *pbAccept = TRUE; } return hr; } protected: virtual ~WSLDVCListenerCallback() { } }; HRESULT WSLDVCListenerCallback_CreateInstance( IWTSListenerCallback** ppCallback ) { return MakeAndInitialize(ppCallback); } ================================================ FILE: WSLDVCPlugin/WSLDVCListenerCallback.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once #include HRESULT WSLDVCListenerCallback_CreateInstance( IWTSListenerCallback** ppCallback ); ================================================ FILE: WSLDVCPlugin/WSLDVCPlugin.cpp ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "WSLDVCPlugin.h" #include "WSLDVCListenerCallback.h" // // Using Windows Runtime C++ Template Library(WRL) to implement COM objects. // See: // https://docs.microsoft.com/en-us/cpp/cppcx/wrl/how-to-instantiate-wrl-components-directly?view=vs-2019 // https://docs.microsoft.com/en-us/cpp/cppcx/wrl/how-to-create-a-classic-com-component-using-wrl?view=vs-2019 // class WSLDVCPlugin : public RuntimeClass< RuntimeClassFlags, IWTSPlugin> { public: HRESULT RuntimeClassInitialize() { return S_OK; } // // IWTSPlugin interface // STDMETHODIMP Initialize( __RPC__in_opt IWTSVirtualChannelManager* pChannelMgr ) { HRESULT hr = S_OK; ComPtr spListenerCallback; ComPtr spListener; hr = WSLDVCListenerCallback_CreateInstance(&spListenerCallback); if (SUCCEEDED(hr)) { hr = pChannelMgr->CreateListener(DVC_NAME, 0, spListenerCallback.Get(), &spListener); } return hr; } STDMETHODIMP Connected() { return S_OK; } STDMETHODIMP Disconnected( DWORD dwDisconnectCode ) { UNREFERENCED_PARAMETER(dwDisconnectCode); return S_OK; } STDMETHODIMP Terminated() { return S_OK; } protected: virtual ~WSLDVCPlugin() { } private: const char* DVC_NAME = "Microsoft::Windows::RDS::RemoteApplicationList"; }; HRESULT WSLDVCPlugin_CreateInstance( IWTSPlugin** ppPlugin ) { return MakeAndInitialize(ppPlugin); } ================================================ FILE: WSLDVCPlugin/WSLDVCPlugin.def ================================================ LIBRARY "WSLDVCPlugin.dll" EXPORTS VirtualChannelGetInstance PRIVATE ================================================ FILE: WSLDVCPlugin/WSLDVCPlugin.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once #include HRESULT WSLDVCPlugin_CreateInstance( IWTSPlugin** ppPlugin ); ================================================ FILE: WSLDVCPlugin/WSLDVCPlugin.rc ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 1,0,0,1 PRODUCTVERSION 1,0,0,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", " Microsoft Corporation. All rights reserved" VALUE "FileDescription", "WSL Remote Application List Plug-in" VALUE "FileVersion", "1.0.0.1" VALUE "InternalName", "WSLDVCPl.dll" VALUE "LegalCopyright", " Microsoft Corporation. All rights reserved" VALUE "OriginalFilename", "WSLDVCPl.dll" VALUE "ProductName", "WSL Remote Application List Plug-in" VALUE "ProductVersion", "InformationalVersion" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: WSLDVCPlugin/WSLDVCPlugin.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30413.136 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WSLDVCPlugin", "WSLDVCPlugin.vcxproj", "{B7E66936-5220-4D77-AA10-C26A7F6F42D6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|ARM64.ActiveCfg = Debug|ARM64 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|ARM64.Build.0 = Debug|ARM64 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x64.ActiveCfg = Debug|x64 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x64.Build.0 = Debug|x64 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x86.ActiveCfg = Debug|Win32 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x86.Build.0 = Debug|Win32 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|ARM64.ActiveCfg = Release|ARM64 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|ARM64.Build.0 = Release|ARM64 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x64.ActiveCfg = Release|x64 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x64.Build.0 = Release|x64 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x86.ActiveCfg = Release|Win32 {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EB2D28FB-D37D-4286-A386-7723B34EF53F} EndGlobalSection EndGlobal ================================================ FILE: WSLDVCPlugin/WSLDVCPlugin.vcxproj ================================================ Debug ARM64 Debug Win32 Release ARM64 Release Win32 Debug x64 Release x64 16.0 Win32Proj {b7e66936-5220-4d77-aa10-c26a7f6f42d6} WSLDVCPlugin 10.0 DynamicLibrary true v143 Unicode Spectre DynamicLibrary false v143 true Unicode Spectre DynamicLibrary true v143 Unicode Spectre DynamicLibrary true v143 Unicode Spectre DynamicLibrary false v143 true Unicode Spectre DynamicLibrary false v143 true Unicode Spectre true WSLDVCPlugin false WSLDVCPlugin true WSLDVCPlugin true WSLDVCPlugin false WSLDVCPlugin false WSLDVCPlugin Level3 true WIN32;_DEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h Guard Windows true false shlwapi.lib;%(AdditionalDependencies) Level3 true true true WIN32;NDEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h Guard Windows true true true false shlwapi.lib;%(AdditionalDependencies) Level4 true _DEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h MultiThreadedDebug true Guard Windows DebugFull false shlwapi.lib;%(AdditionalDependencies) Level4 true _DEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h MultiThreadedDebug true Guard Windows DebugFull false shlwapi.lib;%(AdditionalDependencies) Level4 true true true NDEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h MultiThreaded true Guard Windows true true DebugFull false shlwapi.lib;%(AdditionalDependencies) Level4 true true true NDEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h MultiThreaded true Guard Windows true true DebugFull false shlwapi.lib;%(AdditionalDependencies) Create Create Create Create Create Create ================================================ FILE: WSLDVCPlugin/WSLDVCPlugin.vcxproj.filters ================================================  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms Source Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Resource Files ================================================ FILE: WSLDVCPlugin/WSLDVCPlugin.vcxproj.user ================================================  ================================================ FILE: WSLDVCPlugin/cpp.hint ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #define WSLDVCPLUGIN_API __declspec(dllexport) #define WSLDVCPLUGIN_API __declspec(dllimport) ================================================ FILE: WSLDVCPlugin/dllmain.cpp ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "utils.h" #include "WSLDVCPlugin.h" #include "WSLDVCFileDB.h" #include BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { UNREFERENCED_PARAMETER(hModule); UNREFERENCED_PARAMETER(lpReserved); switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } extern "C" { __declspec(dllexport) HRESULT VCAPITYPE VirtualChannelGetInstance( _In_ REFIID refiid, _Inout_ ULONG* pNumObjs, _Out_opt_ VOID** ppObjArray ) { HRESULT hr = S_OK; ComPtr spPlugin; if (refiid != __uuidof(IWTSPlugin)) { return E_NOINTERFACE; } if (ppObjArray == NULL) { *pNumObjs = 1; return S_OK; } hr = WSLDVCPlugin_CreateInstance(&spPlugin); if (SUCCEEDED(hr)) { *pNumObjs = 1; ppObjArray[0] = spPlugin.Detach(); } return hr; } __declspec(dllexport) HRESULT WINAPI RemoveAppProvider( _In_z_ LPCWSTR appProvider ) { HRESULT hr; WCHAR appMenuPath[MAX_PATH] = {}; ComPtr spFileDB; if (!appProvider) { DebugPrint(L"appProvider parameter is required\n"); return E_INVALIDARG; } hr = BuildMenuPath(ARRAYSIZE(appMenuPath), appMenuPath, appProvider, false); if (FAILED(hr)) { return hr; } DebugPrint(L"AppMenuPath: %s\n", appMenuPath); if (!IsDirectoryPresent(appMenuPath)) { DebugPrint(L"%s is not present\n", appMenuPath); return S_OK; // no program menu exists for given provider, simply exit. } hr = WSLDVCFileDB_CreateInstance(NULL, &spFileDB); if (FAILED(hr)) { DebugPrint(L"failed to instance WSLDVCFileDB\n"); return hr; } spFileDB->addAllFilesAsFileIdAt(appMenuPath); spFileDB->deleteAllFileIdFiles(); spFileDB->OnClose(); spFileDB = nullptr; return hr; } } ================================================ FILE: WSLDVCPlugin/framework.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files #include #include #include #include #include #include /* For IPersistFile */ #include /* For IShellLink */ #include /* For PathIsDirectoryEmpty */ #include /* For SHGetPropertyStoreForWindow */ #include /* For InitPropVariantFromString */ #include /* For PKEY_* */ #include /* For PRIx64 */ ================================================ FILE: WSLDVCPlugin/pch.cpp ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" // When you are using pre-compiled headers, this source file is necessary for compilation to succeed. ================================================ FILE: WSLDVCPlugin/pch.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // pch.h: This is a precompiled header file. // Files listed below are compiled only once, improving build performance for future builds. // This also affects IntelliSense performance, including code completion and many code browsing features. // However, files listed here are ALL re-compiled if any one of them is updated between builds. // Do not add files here that you will be updating frequently as this negates the performance advantage. #ifndef PCH_H #define PCH_H // add headers that you want to pre-compile here #include "framework.h" using namespace Microsoft::WRL; using namespace std; #endif //PCH_H ================================================ FILE: WSLDVCPlugin/rdpapplist.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once // // RDP APPLIST protocol header. // #define RDPAPPLIST_CMDID_CAPS 0x00000001 #define RDPAPPLIST_CMDID_UPDATE_APPLIST 0x00000002 #define RDPAPPLIST_CMDID_DELETE_APPLIST 0x00000003 #define RDPAPPLIST_CMDID_DELETE_APPLIST_PROVIDER 0x00000004 /* added from version 4 */ #define RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID 0x00000005 #define RDPAPPLIST_FIELD_ID 0x00000001 #define RDPAPPLIST_FIELD_GROUP 0x00000002 #define RDPAPPLIST_FIELD_EXECPATH 0x00000004 #define RDPAPPLIST_FIELD_DESC 0x00000008 #define RDPAPPLIST_FIELD_ICON 0x00000010 #define RDPAPPLIST_FIELD_PROVIDER 0x00000020 #define RDPAPPLIST_FIELD_WORKINGDIR 0x00000040 #define RDPAPPLIST_FIELD_WINDOW_ID 0x00000080 /* RDPAPPLIST_UPDATE_APPLIST_PDU */ #define RDPAPPLIST_HINT_NEWID 0x00010000 /* new appId vs update existing appId. */ #define RDPAPPLIST_HINT_SYNC 0x00100000 /* In sync mode (use with _NEWID). */ #define RDPAPPLIST_HINT_SYNC_START 0x00200000 /* Sync appId start (use with _SYNC). */ #define RDPAPPLIST_HINT_SYNC_END 0x00400000 /* Sync appId end (use with _SYNC). */ #define RDPAPPLIST_CHANNEL_VERSION 4 #define RDPAPPLIST_HEADER_SIZE 8 #define RDPAPPLIST_LANG_SIZE 32 #define RDPAPPLIST_MAX_STRING_SIZE 512 #define RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR (RDPAPPLIST_MAX_STRING_SIZE/sizeof(WCHAR)) typedef struct _RDPAPPLIST_HEADER { UINT32 cmdId; UINT32 length; } RDPAPPLIST_HEADER; typedef struct _RDPAPPLIST_CLIENT_CAPS_PDU { UINT16 version; /* ISO 639 (Language name) and ISO 3166 (Country name) connected with '_', such as en_US, ja_JP */ char clientLanguageId[RDPAPPLIST_LANG_SIZE]; } RDPAPPLIST_CLIENT_CAPS_PDU; typedef struct _RDPAPPLIST_SERVER_CAPS_PDU { UINT16 version; UINT16 appListProviderNameLength; WCHAR appListProviderName[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; /* added from version 4 */ UINT16 appListProviderUniqueIdLength; WCHAR appListProviderUniqueId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; } RDPAPPLIST_SERVER_CAPS_PDU; typedef struct _RDPAPPLIST_UPDATE_APPLIST_PDU { UINT32 flags; UINT16 appIdLength; WCHAR appId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; UINT16 appGroupLength; WCHAR appGroup[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; UINT16 appExecPathLength; WCHAR appExecPath[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; UINT16 appWorkingDirLength; WCHAR appWorkingDir[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; UINT16 appDescLength; WCHAR appDesc[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; } RDPAPPLIST_UPDATE_APPLIST_PDU; typedef struct _RDPAPPLIST_ICON_DATA { UINT32 flags; UINT32 iconWidth; UINT32 iconHeight; UINT32 iconStride; UINT32 iconBpp; UINT32 iconFormat; UINT32 iconBitsLength; UINT32 iconFileSize; BYTE* iconFileData; } RDPAPPLIST_ICON_DATA; typedef struct _RDPAPPLIST_DELETE_APPLIST_PDU { UINT32 flags; UINT16 appIdLength; WCHAR appId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; UINT16 appGroupLength; WCHAR appGroup[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; } RDPAPPLIST_DELETE_APPLIST_PDU; typedef struct _RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU { UINT32 flags; UINT16 appListProviderNameLength; WCHAR appListProviderName[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; } RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU; /* added from version 4 */ typedef struct _RDPAPPLIST_ASSOCIATE_WINDOW_ID { UINT32 flags; UINT32 appWindowId; UINT16 appIdLength; WCHAR appId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; UINT16 appGroupLength; WCHAR appGroup[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; UINT16 appExecPathLength; WCHAR appExecPath[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; UINT16 appDescLength; WCHAR appDesc[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; } RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU; // // Read macro. // #define LSTR(x) L ## x // ReadUINT16(dest, source, remaining) #define ReadUINT16(o, p, r) \ if (r >= sizeof(UINT16)) { \ o = (*(UINT16*)(p)); \ (p) += sizeof(UINT16); \ (r) -= sizeof(UINT16); \ } else { \ DebugPrint(L"Failed to read " LSTR(#o) L"\n"); \ goto Error_Read; \ } // ReadUINT32(dest, source, remaining) #define ReadUINT32(o, p, r) \ if (r >= sizeof(UINT32)) { \ o = (*(UINT32*)(p)); \ (p) += sizeof(UINT32); \ (r) -= sizeof(UINT32); \ } else { \ DebugPrint(L"Failed to read " LSTR(#o) L"\n"); \ goto Error_Read; \ } // ReadBYTES(dest, source, lengthToCopy, RemainingSource) #define ReadBYTES(o, p, l, r) \ if (r >= l) { \ memcpy((o), (p), (l)); \ (p) += l; \ (r) -= (l); \ } else { \ DebugPrint(L"Failed to read " LSTR(#o) L"\n"); \ goto Error_Read; \ } // ReadSTRING(dest, source, RemainingSource, required) #define ReadSTRING(o, p, r, required) \ ReadUINT16(o ## Length, p, r); \ if (o ## Length + sizeof(WCHAR) > sizeof(o)) { \ DebugPrint(L"Failed to read " LSTR(#o) L"\n"); \ goto Error_Read; \ } if (o ## Length) { \ ReadBYTES(o, p, o ## Length, r); \ o[o ## Length / sizeof(WCHAR)] = L'\0'; \ } else if (required) { \ DebugPrint(LSTR(#o) L" is required\n"); \ goto Error_Read; \ } #define CheckRequiredFlags(flags, required) \ { \ auto f = (flags) & (required); \ if (f != (required)) { \ DebugPrint(L"missing required flags. Given:%x vs Required:%x\n", flags, required); \ goto Error_Read; \ } \ } ================================================ FILE: WSLDVCPlugin/resource.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by WSLDVCPlugin.rc // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: WSLDVCPlugin/utils.cpp ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "utils.h" #ifdef DBG_MESSAGE void DebugPrint(const wchar_t* format, ...) { WCHAR buf[512] = {}; va_list args; va_start(args, format); wvsprintfW(buf, format, args); va_end(args); OutputDebugStringW(buf); } #endif // DBG_MESSAGE _Use_decl_annotations_ BOOL IsDirectoryPresent(LPCWSTR lpszPath) { DWORD dwAttrib = GetFileAttributes(lpszPath); return (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); } _Use_decl_annotations_ HRESULT CreateShellLink(LPCWSTR lpszPathLink, LPCWSTR lpszPathObj, LPCWSTR lpszArgs, LPCWSTR lpszWorkingDir, LPCWSTR lpszDesc, LPCWSTR lpszPathIcon) { HRESULT hr; IShellLink* psl; DebugPrint(L"CreateShellLink:\n"); DebugPrint(L"\tPath Link: %s\n", lpszPathLink); DebugPrint(L"\tPath Exe: %s\n", lpszPathObj); DebugPrint(L"\tExe args: %s\n", lpszArgs); DebugPrint(L"\tWorkingDir: %s\n", lpszWorkingDir); DebugPrint(L"\tDesc: %s\n", lpszDesc); if (lpszPathIcon && lstrlenW(lpszPathIcon)) { DebugPrint(L"\tIcon Path: %s\n", lpszPathIcon); } else { lpszPathIcon = nullptr; } // Get a pointer to the IShellLink interface. It is assumed that CoInitialize // has already been called. hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); if (SUCCEEDED(hr)) { IPersistFile* ppf; // Set the path to the shortcut target and add the description. psl->SetPath(lpszPathObj); psl->SetArguments(lpszArgs); if (lpszPathIcon) { psl->SetIconLocation(lpszPathIcon, 0); } psl->SetDescription(lpszDesc); psl->SetWorkingDirectory(lpszWorkingDir); psl->SetShowCmd(SW_SHOWMINNOACTIVE); // Query IShellLink for the IPersistFile interface, used for saving the // shortcut in persistent storage. hr = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); if (SUCCEEDED(hr)) { // Save the link by calling IPersistFile::Save. hr = ppf->Save(lpszPathLink, TRUE); ppf->Release(); } psl->Release(); } DebugPrint(L"\tresult: %x\n", hr); return hr; } _Use_decl_annotations_ HRESULT GetIconFileFromShellLink( UINT32 iconPathSize, LPWSTR iconPath, LPCWSTR lnkPath) { HRESULT hr; IShellLink* psl; DebugPrint(L"GetIconFileFromShellLink:\n"); DebugPrint(L"\tPath Link: %s\n", lnkPath); assert(iconPathSize); assert(iconPath); *iconPath = L'\0'; hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); if (SUCCEEDED(hr)) { IPersistFile* ppf; hr = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); if (SUCCEEDED(hr)) { hr = ppf->Load(lnkPath, STGM_READ); if (SUCCEEDED(hr)) { int dummy = 0; hr = psl->GetIconLocation(iconPath, iconPathSize, &dummy); } ppf->Release(); } psl->Release(); } DebugPrint(L"\tresult: %x\n", hr); if (SUCCEEDED(hr)) { DebugPrint(L"\ticonPath: %s\n", iconPath); } return hr; } _Use_decl_annotations_ HRESULT CreateIconFile(BYTE* pBuffer, UINT32 cbSize, LPCWSTR lpszIconFile) { HRESULT hr = S_OK; HANDLE hFile; DebugPrint(L"CreateIconFile: %s\n", lpszIconFile); hFile = CreateFileW(lpszIconFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { DebugPrint(L"CreateFile(%s) failed, error %d\n", lpszIconFile, GetLastError()); hr = E_FAIL; } else { if (!WriteFile(hFile, pBuffer, cbSize, NULL, NULL)) { DebugPrint(L"WriteFile(%s) failed, error %d\n", lpszIconFile, GetLastError()); hr = E_FAIL; } CloseHandle(hFile); } DebugPrint(L"\tresult: %x\n", hr); return hr; } #define MAX_LOCALE_CODE 9 _Use_decl_annotations_ BOOL GetLocaleName(char* localeName, int localeNameSize) { char langCode[MAX_LOCALE_CODE] = {}; char countryName[MAX_LOCALE_CODE] = {}; int result = 0; assert(localeName); localeName[0] = '\0'; LCID lcid = MAKELCID(GetUserDefaultUILanguage(), SORT_DEFAULT); result = GetLocaleInfoA(lcid, LOCALE_SISO639LANGNAME, langCode, MAX_LOCALE_CODE) != 0; if ((result == 0) || (strcpy_s(localeName, localeNameSize, langCode) != 0) || (strcat_s(localeName, localeNameSize, "_") != 0)) { return FALSE; } result = GetLocaleInfoA(lcid, LOCALE_SISO3166CTRYNAME, countryName, MAX_LOCALE_CODE) != 0; if ((result == 0) || (strcat_s(localeName, localeNameSize, countryName) != 0)) { return FALSE; } return TRUE; } _Use_decl_annotations_ HRESULT BuildMenuPath( UINT32 appMenuPathSize, LPWSTR appMenuPath, LPCWSTR appProvider, bool isCreateDir) { PWSTR knownFolderPath = NULL; // free by CoTaskMemFree. SHGetKnownFolderPath(FOLDERID_StartMenu, 0, NULL, &knownFolderPath); if (!knownFolderPath) { DebugPrint(L"SHGetKnownFolderPath(FOLDERID_StartMenu) failed\n"); return E_FAIL; } int ret = swprintf_s(appMenuPath, appMenuPathSize, L"%s\\Programs\\%s", knownFolderPath, appProvider); CoTaskMemFree(knownFolderPath); if (ret < 0) { DebugPrint(L"swprintf_s for appMenuPath failed"); return E_FAIL; } if (isCreateDir) { if (!CreateDirectoryW(appMenuPath, NULL)) { if (ERROR_ALREADY_EXISTS != GetLastError()) { DebugPrint(L"Failed to create %s\n", appMenuPath); return E_FAIL; } } } return S_OK; } _Use_decl_annotations_ HRESULT BuildIconPath( UINT32 iconPathSize, LPWSTR iconPath, LPCWSTR appProvider, bool isCreateDir) { WCHAR prefix[] = L"WSLDVCPlugin\\"; UINT32 lenTempPath; lenTempPath = GetTempPathW(iconPathSize, iconPath); if (!lenTempPath) { DebugPrint(L"GetTempPathW failed\n"); return E_FAIL; } if ((lenTempPath + ARRAYSIZE(prefix) + wcslen(appProvider)) > iconPathSize) { DebugPrint(L"provider name length check failed, length %d\n", wcslen(appProvider)); return E_FAIL; } if (wcscat_s(iconPath, iconPathSize, prefix) != 0) { return E_FAIL; } if (isCreateDir) { if (!CreateDirectoryW(iconPath, NULL)) { if (ERROR_ALREADY_EXISTS != GetLastError()) { DebugPrint(L"Failed to create %s\n", iconPath); return E_FAIL; } } } if (wcscat_s(iconPath, iconPathSize, appProvider) != 0) { return E_FAIL; } if (isCreateDir) { if (!CreateDirectoryW(iconPath, NULL)) { if (ERROR_ALREADY_EXISTS != GetLastError()) { DebugPrint(L"Failed to create %s\n", iconPath); return E_FAIL; } } } return S_OK; } HRESULT UpdateTaskBarInfo( HWND hwnd, _In_z_ LPCWSTR relaunchCmdline, _In_z_ LPCWSTR displayName, _In_z_ LPCWSTR iconPath) { HRESULT hr; PROPVARIANT propvar; DebugPrint(L"UpdateTaskBarInfo: 0x%p\n", hwnd); DebugPrint(L" relaunchCmdline: %s\n", relaunchCmdline); DebugPrint(L" displayName: %s\n", displayName); DebugPrint(L" iconPath: %s\n", iconPath); IPropertyStore* ps = NULL; hr = SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&ps)); if (FAILED(hr)) { DebugPrint(L"SHGetPropertyStoreForWindow failed: 0x%x\n", hr); return hr; } BOOL bPinToTaskbar = relaunchCmdline && displayName && iconPath; if (bPinToTaskbar) { hr = InitPropVariantFromString(displayName, &propvar); if (FAILED(hr)) { DebugPrint(L"InitPropVariantFromString failed: 0x%x\n", hr); return hr; } hr = ps->SetValue(PKEY_AppUserModel_RelaunchDisplayNameResource, propvar); PropVariantClear(&propvar); if (FAILED(hr)) { DebugPrint(L"SetValue(PKEY_AppUserModel_RelaunchDisplayNameResource failed: 0x%x\n", hr); return hr; } hr = InitPropVariantFromString(iconPath, &propvar); if (FAILED(hr)) { DebugPrint(L"InitPropVariantFromString failed: 0x%x\n", hr); return hr; } hr = ps->SetValue(PKEY_AppUserModel_RelaunchIconResource, propvar); PropVariantClear(&propvar); if (FAILED(hr)) { DebugPrint(L"SetValue(PKEY_AppUserModel_RelaunchIconResource failed: 0x%x\n", hr); return hr; } hr = InitPropVariantFromString(relaunchCmdline, &propvar); if (FAILED(hr)) { DebugPrint(L"InitPropVariantFromString failed: 0x%x\n", hr); return hr; } hr = ps->SetValue(PKEY_AppUserModel_RelaunchCommand, propvar); PropVariantClear(&propvar); if (FAILED(hr)) { DebugPrint(L"SetValue(PKEY_AppUserModel_RelaunchCommand failed: 0x%x\n", hr); return hr; } } else { hr = InitPropVariantFromBoolean(TRUE, &propvar); if (FAILED(hr)) { DebugPrint(L"InitPropVariantFromBoolean failed: 0x%x\n", hr); return hr; } hr = ps->SetValue(PKEY_AppUserModel_PreventPinning, propvar); PropVariantClear(&propvar); if (FAILED(hr)) { DebugPrint(L"SetValue(PKEY_AppUserModel_PreventPinning failed: 0x%x\n", hr); return hr; } } ps->Release(); return S_OK; } #if ENABLE_WSL_SIGNATURE_CHECK // Link with the Wintrust.lib file. #pragma comment (lib, "wintrust") #include #include #include _Use_decl_annotations_ BOOL IsFileTrusted(LPCWSTR pwszFile) { BOOL bTrusted = FALSE; LONG lStatus; DWORD dwLastError; // Initialize the WINTRUST_FILE_INFO structure. WINTRUST_FILE_INFO FileData; memset(&FileData, 0, sizeof(FileData)); FileData.cbStruct = sizeof(WINTRUST_FILE_INFO); FileData.pcwszFilePath = pwszFile; FileData.hFile = NULL; FileData.pgKnownSubject = NULL; /* WVTPolicyGUID specifies the policy to apply on the file WINTRUST_ACTION_GENERIC_VERIFY_V2 policy checks: 1) The certificate used to sign the file chains up to a root certificate located in the trusted root certificate store. This implies that the identity of the publisher has been verified by a certification authority. 2) In cases where user interface is displayed (which this example does not do), WinVerifyTrust will check for whether the end entity certificate is stored in the trusted publisher store, implying that the user trusts content from this publisher. 3) The end entity certificate has sufficient permission to sign code, as indicated by the presence of a code signing EKU or no EKU. */ GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; WINTRUST_DATA WinTrustData; // Initialize the WinVerifyTrust input data structure. // Default all fields to 0. memset(&WinTrustData, 0, sizeof(WinTrustData)); WinTrustData.cbStruct = sizeof(WinTrustData); // Use default code signing EKU. WinTrustData.pPolicyCallbackData = NULL; // No data to pass to SIP. WinTrustData.pSIPClientData = NULL; // Disable WVT UI. WinTrustData.dwUIChoice = WTD_UI_NONE; // No revocation checking. WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE; // Verify an embedded signature on a file. WinTrustData.dwUnionChoice = WTD_CHOICE_FILE; // Verify action. WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY; // Verification sets this value. WinTrustData.hWVTStateData = NULL; // Not used. WinTrustData.pwszURLReference = NULL; // This is not applicable if there is no UI because it changes // the UI to accommodate running applications instead of // installing applications. WinTrustData.dwUIContext = 0; // Set pFile. WinTrustData.pFile = &FileData; // WinVerifyTrust verifies signatures as specified by the GUID // and Wintrust_Data. lStatus = WinVerifyTrust( NULL, &WVTPolicyGUID, &WinTrustData); switch (lStatus) { case ERROR_SUCCESS: //Signed file: // - Hash that represents the subject is trusted. // - Trusted publisher without any verification errors. DebugPrint(L"The file \"%s\" is signed and the signature was verified.\n", pwszFile); bTrusted = TRUE; break; case TRUST_E_NOSIGNATURE: // The file was not signed or had a signature // that was not valid. // Get the reason for no signature. dwLastError = GetLastError(); if (TRUST_E_NOSIGNATURE == dwLastError || TRUST_E_SUBJECT_FORM_UNKNOWN == dwLastError || TRUST_E_PROVIDER_UNKNOWN == dwLastError) { // The file was not signed. DebugPrint(L"The file \"%s\" is not signed.\n", pwszFile); } else { // The signature was not valid or there was an error // opening the file. DebugPrint(L"An unknown error occurred trying to " L"verify the signature of the \"%s\" file.\n", pwszFile); } break; case TRUST_E_EXPLICIT_DISTRUST: // The hash that represents the subject or the publisher // is not allowed by the admin or user. DebugPrint(L"The signature is present, but specifically disallowed.\n"); break; case TRUST_E_SUBJECT_NOT_TRUSTED: DebugPrint(L"The signature is present, but not trusted.\n"); break; default: // The UI was disabled in dwUIChoice or the admin policy // has disabled user trust. lStatus contains the // publisher or time stamp chain error. DebugPrint(L"Error is: 0x%x.\n", lStatus); break; } // Any hWVTStateData must be released by a call with close. WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE; lStatus = WinVerifyTrust( NULL, &WVTPolicyGUID, &WinTrustData); return bTrusted; } #endif // ENABLE_WSL_SIGNATURE_CHECK ================================================ FILE: WSLDVCPlugin/utils.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once //#ifdef _DEBUG #define DBG_MESSAGE //#endif // _DEBUG #ifdef DBG_MESSAGE void DebugPrint(const wchar_t* format, ...); #define DebugAssert(exp) assert(exp) #else #define DebugPrint #define DebugAssert #endif // DBG_MESSAGE // Set to 1 to enable digital signature check. #define ENABLE_WSL_SIGNATURE_CHECK 0 BOOL IsDirectoryPresent(_In_z_ LPCWSTR lpszPath); HRESULT CreateShellLink(_In_z_ LPCWSTR lpszPathLink, _In_z_ LPCWSTR lpszPathObj, _In_z_ LPCWSTR lpszArgs, _In_z_ LPCWSTR lpszWorkingDir, _In_z_ LPCWSTR lpszDesc, _In_opt_z_ LPCWSTR lpszPathIcon); HRESULT GetIconFileFromShellLink( UINT32 iconPathSize, _Out_writes_z_(iconPathSize) LPWSTR iconPath, _In_z_ LPCWSTR lnkPath); HRESULT CreateIconFile(_In_reads_bytes_(cbSize) BYTE* pBuffer, UINT32 cbSize, _In_z_ LPCWSTR lpszIconFile); BOOL GetLocaleName(_Out_writes_z_(localeNameSize) char* localeName, int localeNameSize); HRESULT BuildMenuPath( UINT32 appMenuPathSize, _Out_writes_z_(appMenuPathSize) LPWSTR appMenuPath, _In_z_ LPCWSTR appProvider, bool isCreateDir); HRESULT BuildIconPath( UINT32 iconPathSize, _Out_writes_z_(iconPathSize) LPWSTR iconPath, _In_z_ LPCWSTR appProvider, bool isCreateDir); HRESULT UpdateTaskBarInfo( HWND hwnd, _In_z_ LPCWSTR relaunchCmdline, _In_z_ LPCWSTR displayName, _In_z_ LPCWSTR iconPath); #if ENABLE_WSL_SIGNATURE_CHECK BOOL IsFileTrusted(_In_z_ LPCWSTR pwszFile); #else #define IsFileTrusted(pwszFile) (true) #endif // ENABLE_WSL_SIGNATURE_CHECK #pragma pack(1) // // .ICO file format header // // Icon entry struct typedef struct _ICON_DIR_ENTRY { BYTE bWidth; // Width, in pixels, of the image BYTE bHeight; // Height, in pixels, of the image BYTE bColorCount; // Number of colors in image (0 if >=8bpp) BYTE bReserved; // Reserved ( must be 0) WORD wPlanes; // Color Planes WORD wBitCount; // Bits per pixel DWORD dwBytesInRes; // How many bytes in this resource? DWORD dwImageOffset; // Where in the file is this image? } ICON_DIR_ENTRY; // Icon directory struct typedef struct _ICON_HEADER { WORD idReserved; // Reserved (must be 0) WORD idType; // Resource Type (1 for icons) WORD idCount; // How many images? ICON_DIR_ENTRY idEntries[1]; // An entry for each image (idCount of 'em) } ICON_HEADER; #pragma pack() ================================================ FILE: WSLGd/FontMonitor.cpp ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "FontMonitor.h" #include "common.h" #define DEFAULT_FONT_PATH "/usr/share/fonts" #define USER_DISTRO_FONT_PATH USER_DISTRO_MOUNT_PATH DEFAULT_FONT_PATH #define ALT_FONT_PATH "/usr/share/X11/fonts" #define ALT_DISTRO_FONT_PATH USER_DISTRO_MOUNT_PATH ALT_FONT_PATH constexpr auto c_fontsdir = "fonts.dir"; constexpr auto c_xset = "/usr/bin/xset"; wslgd::FontFolder::FontFolder(int fd, const char *path) { LOG_INFO("FontMonitor: start monitoring %s", path); m_path = path; /* check if folder is already ready to be added to font path */ try { std::filesystem::path fonts_dir(path); fonts_dir /= c_fontsdir; if (access(fonts_dir.c_str(), R_OK) == 0) { ModifyX11FontPath(true); } m_fd.reset(dup(fd)); THROW_LAST_ERROR_IF(!m_fd); /* add watch for install or uninstall of fonts on this folder */ THROW_LAST_ERROR_IF((m_wd = inotify_add_watch(m_fd.get(), path, IN_CREATE|IN_CLOSE_WRITE|IN_DELETE|IN_MOVED_TO|IN_MOVED_FROM)) < 0); } CATCH_LOG(); } wslgd::FontFolder::~FontFolder() { LOG_INFO("FontMonitor: stop monitoring %s", m_path.c_str()); ModifyX11FontPath(false); /* if still under watch, remove it */ if (m_wd >= 0) { inotify_rm_watch(m_fd.get(), m_wd); m_wd = -1; } } bool wslgd::FontFolder::ExecuteShellCommand(std::vector&& argv) { bool success = false; int childPid = -1, waitPid = -1; std::string cmd; try { THROW_LAST_ERROR_IF((childPid = fork()) < 0); if (childPid == 0) { /* move this process to own process group to avoid interfere with Process Monitor */ THROW_LAST_ERROR_IF(setpgid(0, 0) < 0); THROW_LAST_ERROR_IF(execvp(argv[0], const_cast(argv.data())) < 0); } else if (childPid > 0) { /* move child to own process group to avoid interfere with Process Monitor */ THROW_LAST_ERROR_IF(setpgid(childPid, childPid) < 0); } } CATCH_LOG(); // Ensure that the child process exits. if (childPid == 0) { _exit(1); } if (childPid > 0) try { int status; THROW_LAST_ERROR_IF((waitPid = waitpid(childPid, &status, 0)) < 0); if (WIFEXITED(status)) { success = (WEXITSTATUS(status) == 0); } } CATCH_LOG(); try { for (std::vector::iterator it = argv.begin(); *it != nullptr; it++) { cmd += *it; cmd += " "; } LOG_INFO("FontMonitor: pid:%d exited with %s, %s", waitPid, success ? "success" : "fail", cmd.c_str()); } CATCH_LOG(); return success; } void wslgd::FontFolder::ModifyX11FontPath(bool isAdd) { std::vector argv; sleep(2); /* workaround for optional fonts.alias, wait 2 sec before invoking xset */ if (m_isPathAdded != isAdd) try { argv.push_back(c_xset); argv.push_back(isAdd ? "+fp" : "-fp"); argv.push_back(m_path.c_str()); argv.push_back(nullptr); if (ExecuteShellCommand(std::move(argv))) { m_isPathAdded = isAdd; /* let X server reread font database */ argv.clear(); argv.push_back(c_xset); argv.push_back("fp"); argv.push_back("rehash"); argv.push_back(nullptr); ExecuteShellCommand(std::move(argv)); } } CATCH_LOG(); } wslgd::FontMonitor::FontMonitor() { } void wslgd::FontMonitor::DumpMonitorFolders() { try { std::map>::iterator it; for (it = m_fontMonitorFolders.begin(); it != m_fontMonitorFolders.end(); it++) LOG_INFO("FontMonitor: monitoring %s, and it is %s to X11 font path", it->first.c_str(), it->second->IsPathAdded() ? "added" : "*not* added"); } CATCH_LOG(); } void wslgd::FontMonitor::AddMonitorFolder(const char *path) { try { std::string monitorPath(path); // checkf if path is tracked already. if (m_fontMonitorFolders.find(monitorPath) == m_fontMonitorFolders.end()) { std::unique_ptr fontFolder(new FontFolder(m_fd.get(), path)); if (fontFolder.get()->GetWd() >= 0) { m_fontMonitorFolders.insert(std::make_pair(std::move(monitorPath), std::move(fontFolder))); // If this is mount path, only track under X11 folder if it's already exist. if (strcmp(path, USER_DISTRO_FONT_PATH) == 0) { if (std::filesystem::exists(USER_DISTRO_FONT_PATH "/X11")) { AddMonitorFolder(USER_DISTRO_FONT_PATH "/X11"); } } else { // Otherwise, add all existing subfolders to track. for (auto& dir_entry : std::filesystem::directory_iterator{path}) { if (dir_entry.is_directory()) { AddMonitorFolder(dir_entry.path().c_str()); } } } } } else { LOG_INFO("FontMonitor: %s is already tracked", path); } } CATCH_LOG(); } void wslgd::FontMonitor::RemoveMonitorFolder(const char *path) { LOG_INFO("FontMonitor: removing monitoring %s", path); try { std::string monitorPath(path); m_fontMonitorFolders.erase(monitorPath); } CATCH_LOG(); } void wslgd::FontMonitor::HandleFolderEvent(struct inotify_event *event) { try { std::map>::iterator it; for (it = m_fontMonitorFolders.begin(); it != m_fontMonitorFolders.end(); it++) { if (event->wd == it->second->GetWd()) { if (event->mask & (IN_CREATE|IN_MOVED_TO)) { bool addMonitorFolder = true; std::filesystem::path fullPath(it->second->GetPath()); if (fullPath.compare(USER_DISTRO_FONT_PATH) == 0) { /* Immediately under mount folder, only monitor "X11" and its subfolder */ addMonitorFolder = (strcmp(event->name, "X11") == 0); } if (addMonitorFolder) { fullPath /= event->name; AddMonitorFolder(fullPath.c_str()); } } else if (event->mask & (IN_DELETE|IN_MOVED_FROM)) { std::filesystem::path fullPath(it->second->GetPath()); fullPath /= event->name; RemoveMonitorFolder(fullPath.c_str()); } break; } } } CATCH_LOG(); } void wslgd::FontMonitor::HandleFontsDirEvent(struct inotify_event *event) { try { std::map>::iterator it; for (it = m_fontMonitorFolders.begin(); it != m_fontMonitorFolders.end(); it++) { if (event->wd == it->second->GetWd()) { if (event->mask & (IN_CREATE|IN_CLOSE_WRITE|IN_MOVED_TO)) { std::filesystem::path fonts_dir(it->second->GetPath()); fonts_dir /= event->name; THROW_LAST_ERROR_IF(access(fonts_dir.c_str(), R_OK) != 0); it->second->ModifyX11FontPath(true); } else if (event->mask & (IN_DELETE|IN_MOVED_FROM)) { it->second->ModifyX11FontPath(false); } break; } } } CATCH_LOG(); } void* wslgd::FontMonitor::FontMonitorThread(void *context) { FontMonitor *This = reinterpret_cast(context); struct inotify_event *event; int len, cur; char buf[10 * (sizeof *event + 256)]; LOG_INFO("FontMonitor: monitoring thread started."); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); // Dump currently tracking folders. This->DumpMonitorFolders(); // Start listening folder add/remove. for (;;) { len = read(This->GetFd(), buf, sizeof buf); cur = 0; while (cur < len) { event = (struct inotify_event *)&buf[cur]; if (event->len) { if (event->mask & IN_ISDIR) { // A directory is added or removed. This->HandleFolderEvent(event); } else if (strcmp(event->name, c_fontsdir) == 0) { // A fonts.dir is added or removed. This->HandleFontsDirEvent(event); } } cur += (sizeof *event + event->len); } } // never hit here. assert(true); return 0; } int wslgd::FontMonitor::Start() { bool succeeded = false; assert(m_fontMonitorFolders.empty()); assert(!m_fontMonitorThread); try { // xset must be installed. THROW_LAST_ERROR_IF(access(c_xset, X_OK) < 0); // if user distro mount folder does not exist, bail out. THROW_LAST_ERROR_IF_FALSE(std::filesystem::exists(USER_DISTRO_MOUNT_PATH)); bool userDistroFontPathExists = std::filesystem::exists(USER_DISTRO_FONT_PATH); bool altDistroFontPathExists = std::filesystem::exists(ALT_DISTRO_FONT_PATH); // and check fonts path inside user distro. THROW_LAST_ERROR_IF_FALSE(userDistroFontPathExists || altDistroFontPathExists); // start monitoring on mounted font folder. wil::unique_fd fd(inotify_init()); THROW_LAST_ERROR_IF(!fd); m_fd.reset(fd.release()); // add both the default and alternative font paths if they exist. if (userDistroFontPathExists) { AddMonitorFolder(USER_DISTRO_FONT_PATH); } if (altDistroFontPathExists) { AddMonitorFolder(ALT_DISTRO_FONT_PATH); } // Create font folder monitor thread. THROW_LAST_ERROR_IF(pthread_create(&m_fontMonitorThread, NULL, FontMonitorThread, (void*)this) < 0); succeeded = true; } CATCH_LOG(); if (!succeeded) { Stop(); return -1; } return 0; } void wslgd::FontMonitor::Stop() { // Stop font folder monitor thread. if (m_fontMonitorThread) { pthread_cancel(m_fontMonitorThread); pthread_join(m_fontMonitorThread, NULL); m_fontMonitorThread = 0; } // Remove both the default and alternative font paths if they were added. if (m_fontMonitorFolders.find(USER_DISTRO_FONT_PATH) != m_fontMonitorFolders.end()) { RemoveMonitorFolder(USER_DISTRO_FONT_PATH); } if (m_fontMonitorFolders.find(ALT_DISTRO_FONT_PATH) != m_fontMonitorFolders.end()) { RemoveMonitorFolder(ALT_DISTRO_FONT_PATH); } m_fontMonitorFolders.clear(); m_fd.reset(); LOG_INFO("FontMonitor: monitoring stopped."); } ================================================ FILE: WSLGd/FontMonitor.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once #include "precomp.h" namespace wslgd { class FontFolder { public: FontFolder(int fd, const char *path); ~FontFolder(); void ModifyX11FontPath(bool add); static bool ExecuteShellCommand(std::vector&& argv); int GetFd() const { return m_fd.get(); } int GetWd() const { return m_wd; } bool IsPathAdded() const { return m_isPathAdded; } const char *GetPath() const { return m_path.c_str(); } private: wil::unique_fd m_fd; /* from FontMonitor's inotify_init() */ int m_wd = -1; /* from inotify_add_watch() for this folder */ std::string m_path; /* this folder path */ bool m_isPathAdded = false; /* whether font path is added to X11 font path */ }; class FontMonitor { public: FontMonitor(); ~FontMonitor() { Stop(); } FontMonitor(const FontMonitor&) = delete; void operator=(const FontMonitor&) = delete; int Start(); void Stop(); static void* FontMonitorThread(void *context); void AddMonitorFolder(const char *path); void RemoveMonitorFolder(const char *path); void DumpMonitorFolders(); void HandleFolderEvent(struct inotify_event *event); void HandleFontsDirEvent(struct inotify_event *event); int GetFd() const { return m_fd.get(); } private: wil::unique_fd m_fd; /* from inotify_init() */ std::map> m_fontMonitorFolders{}; pthread_t m_fontMonitorThread = 0; }; } ================================================ FILE: WSLGd/Makefile ================================================ CXX := clang++ LDFLAGS := -lcap TARGET := WSLGd SRC_DIRS := . INSTALL := install -p INSTALL_PREFIX= $(DESTDIR)/$(PREFIX)/bin SRCS := $(shell find $(SRC_DIRS) -name "*.cpp" -or -name "*.c" -or -name "*.s") OBJS := $(addsuffix .o,$(basename $(SRCS))) DEPS := $(OBJS:.o=.d) INC_DIRS := $(shell find $(SRC_DIRS) -type d) INC_FLAGS := $(addprefix -I,$(INC_DIRS)) CPPFLAGS := $(INC_FLAGS) -MMD -MP -std=c++17 $(TARGET): $(OBJS) $(CXX) $(LDFLAGS) $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS) .PHONY: clean clean: $(RM) $(TARGET) $(OBJS) $(DEPS) install: $(INSTALL) $(TARGET) $(INSTALL_PREFIX) -include $(DEPS) ================================================ FILE: WSLGd/ProcessMonitor.cpp ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "ProcessMonitor.h" #include "common.h" wslgd::ProcessMonitor::ProcessMonitor(const char* userName) { THROW_ERRNO_IF(ENOENT, !(m_user = getpwnam(userName))); } passwd* wslgd::ProcessMonitor::GetUserInfo() const { return m_user; } extern char **environ; int wslgd::ProcessMonitor::LaunchProcess( std::vector&& argv, std::vector&& capabilities, std::vector&& env) { int childPid; THROW_LAST_ERROR_IF((childPid = fork()) < 0); if (childPid == 0) try { // Construct a null-terminated argument array. std::vector arguments; for (auto &arg : argv) { arguments.push_back(arg.c_str()); } arguments.push_back(nullptr); // If any capabilities were specified, set the keepcaps flag so they are not lost across setuid. if (!capabilities.empty()) { THROW_LAST_ERROR_IF(prctl(PR_SET_KEEPCAPS, 1) < 0); } // Construct a null-terminated environment array. std::vector environments; for (char **c = environ; *c; c++) { environments.push_back(*c); } for (auto &s : env) { if (s.size()) { environments.push_back(s.c_str()); } } environments.push_back(nullptr); // Set user settings. THROW_LAST_ERROR_IF(setgid(m_user->pw_gid) < 0); THROW_LAST_ERROR_IF(initgroups(m_user->pw_name, m_user->pw_gid) < 0); THROW_LAST_ERROR_IF(setuid(m_user->pw_uid) < 0); THROW_LAST_ERROR_IF(chdir(m_user->pw_dir) < 0); // Apply additional capabilities to the process. if (!capabilities.empty()) { cap_t caps{}; THROW_LAST_ERROR_IF((caps = cap_get_proc()) == NULL); auto freeCapabilities = wil::scope_exit([&caps]() { cap_free(caps); }); THROW_LAST_ERROR_IF(cap_set_flag(caps, CAP_PERMITTED, capabilities.size(), capabilities.data(), CAP_SET) < 0); THROW_LAST_ERROR_IF(cap_set_flag(caps, CAP_EFFECTIVE, capabilities.size(), capabilities.data(), CAP_SET) < 0); THROW_LAST_ERROR_IF(cap_set_flag(caps, CAP_INHERITABLE, capabilities.size(), capabilities.data(), CAP_SET) < 0); THROW_LAST_ERROR_IF(cap_set_proc(caps) < 0); for (auto &cap : capabilities) { THROW_LAST_ERROR_IF(cap_set_ambient(cap, CAP_SET) < 0); } } // Run the process as the specified user. THROW_LAST_ERROR_IF(execvpe(arguments[0], const_cast(arguments.data()), const_cast(environments.data())) < 0); } CATCH_LOG(); // Ensure that the child process exits. if (childPid == 0) { _exit(1); } m_children[childPid] = ProcessInfo{std::move(argv), std::move(capabilities), std::move(env)}; return childPid; } int wslgd::ProcessMonitor::Run() try { std::map> crashes; for (;;) { // Reap any zombie child processes and re-launch any tracked processes. int pid; int status; /* monitor only processes within same group as caller */ THROW_LAST_ERROR_IF((pid = waitpid(0, &status, 0)) <= 0); auto found = m_children.find(pid); if (found != m_children.end()) { if (!found->second.argv.empty()) { std::string cmd; for (auto &arg : found->second.argv) { cmd += arg.c_str(); cmd += " "; } if (WIFEXITED(status)) { LOG_INFO("pid %d exited with status %d, %s", pid, WEXITSTATUS(status), cmd.c_str()); } else if (WIFSIGNALED(status)) { LOG_INFO("pid %d terminated with signal %d, %s", pid, WTERMSIG(status), cmd.c_str()); } else { LOG_ERROR("pid %d return unknown status %d, %s", pid, status, cmd.c_str()); } auto& crashTimestamps = crashes[cmd]; auto now = time(nullptr); crashTimestamps.erase(std::remove_if(crashTimestamps.begin(), crashTimestamps.end(), [&](auto ts) { return ts < now - 60; }), crashTimestamps.end()); crashTimestamps.emplace_back(now); if (crashTimestamps.size() > 10) { LOG_INFO("%s exited more than 10 times in 60 seconds, not starting it again", cmd.c_str()); } else { LaunchProcess(std::move(found->second.argv), std::move(found->second.capabilities), std::move(found->second.env)); } } m_children.erase(found); } else { LOG_INFO("untracked pid %d exited with status 0x%x.", pid, status); } } return 0; } CATCH_RETURN_ERRNO(); ================================================ FILE: WSLGd/ProcessMonitor.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once #include "precomp.h" namespace wslgd { class ProcessMonitor { public: ProcessMonitor(const char* username); ProcessMonitor(const ProcessMonitor&) = delete; void operator=(const ProcessMonitor&) = delete; passwd* GetUserInfo() const; int LaunchProcess(std::vector&& argv, std::vector&& capabilities = {}, std::vector&& env = {}); int Run(); private: struct ProcessInfo { std::vector argv; std::vector capabilities; std::vector env; }; std::map m_children{}; passwd* m_user; }; } ================================================ FILE: WSLGd/common.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once #define SHARE_PATH "/mnt/wslg" #define USER_DISTRO_MOUNT_PATH SHARE_PATH "/distro" void LogPrint(int level, const char *func, int line, const char *fmt, ...) noexcept; #define LOG_LEVEL_EXCEPTION 3 #define LOG_LEVEL_ERROR 4 #define LOG_LEVEL_INFO 5 #define LOG_ERROR(fmt, ...) LogPrint(LOG_LEVEL_ERROR, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) #define LOG_INFO(fmt, ...) LogPrint(LOG_LEVEL_INFO, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) ================================================ FILE: WSLGd/lxwil.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #pragma once namespace wil { #define FAIL_FAST() raise(SIGABRT); #define FAIL_FAST_CAUGHT_EXCEPTION() FAIL_FAST() #define FAIL_FAST_IF(condition) if ((condition)) { FAIL_FAST() } typedef void LogFunction(const char *message, const char *exceptionDescription) noexcept; inline LogFunction *g_LogExceptionCallback{}; namespace details { struct FailureInfo { const char *File; int Line; const char *Function; }; } class ResultException : public std::exception { public: ResultException(int result, details::FailureInfo info) noexcept : m_Result{ result }, m_Info{ info } { } ~ResultException() noexcept { delete[] m_What; } const char *what() const noexcept override { constexpr size_t bufferSize = 4096; if (m_What == nullptr) { m_What = new(std::nothrow) char[bufferSize]{}; if (m_What == nullptr) { return strerror(m_Result); } snprintf(m_What, bufferSize, "%s @%s:%d (%s)\n", strerror(m_Result), m_Info.File, m_Info.Line, m_Info.Function); } return m_What; } int GetErrorCode() const noexcept { return m_Result; } private: mutable char *m_What{}; int m_Result; details::FailureInfo m_Info; }; namespace details { template class lambda_call { public: lambda_call(const lambda_call&) = delete; lambda_call& operator=(const lambda_call&) = delete; lambda_call& operator=(lambda_call&& other) = delete; explicit lambda_call(TLambda&& lambda) noexcept : m_lambda(std::move(lambda)) { static_assert(std::is_same::value, "scope_exit lambdas must not have a return value"); static_assert(!std::is_lvalue_reference::value && !std::is_rvalue_reference::value, "scope_exit should only be directly used with a lambda"); } lambda_call(lambda_call&& other) noexcept : m_lambda(std::move(other.m_lambda)), m_call(other.m_call) { other.m_call = false; } ~lambda_call() noexcept { reset(); } // Ensures the scope_exit lambda will not be called void release() noexcept { m_call = false; } // Executes the scope_exit lambda immediately if not yet run; ensures it will not run again void reset() noexcept { if (m_call) { m_call = false; m_lambda(); } } // Returns true if the scope_exit lambda is still going to be executed explicit operator bool() const noexcept { return m_call; } protected: TLambda m_lambda; bool m_call = true; }; inline void ThrowErrorIf(bool condition, int error, FailureInfo info) { if (condition) { throw ::wil::ResultException(error, info); } } inline void LogFailure(const char *message, const char *exceptionDescription) noexcept { auto callback = g_LogExceptionCallback; if (callback != nullptr) { callback(message, exceptionDescription); } else { if (message != nullptr) { fputs(message, stderr); fputs("\n", stderr); } if (exceptionDescription != nullptr) { fputs("Exception: ", stderr); fputs(exceptionDescription, stderr); fputs("\n", stderr); } } } inline void LogCaughtException(const char *message) { try { throw; } catch (const std::exception &ex) { LogFailure(message, ex.what()); } catch (...) { LogFailure(message, nullptr); } } } inline int ResultFromCaughtException() { try { throw; } catch (wil::ResultException &ex) { return ex.GetErrorCode(); } catch (std::bad_alloc &) { return ENOMEM; } catch (...) { } // Unknown exception type. return EINVAL; } #define __WIL_ERROR_INFO { __FILE__, __LINE__, __FUNCTION__ } #define THROW_ERRNO(Error) throw ::wil::ResultException(Error, __WIL_ERROR_INFO) #define THROW_ERRNO_IF(Error, Condition) ::wil::details::ThrowErrorIf((Condition), (Error), __WIL_ERROR_INFO) #define THROW_ERRNO_IF_FALSE(Error, Condition) ::wil::details::ThrowErrorIf(!(Condition), (Error), __WIL_ERROR_INFO) #define THROW_LAST_ERROR_IF(Condition) THROW_ERRNO_IF(errno, (Condition)); #define THROW_LAST_ERROR_IF_FALSE(Condition) THROW_ERRNO_IF_FALSE(errno, (Condition)) #define THROW_INVALID() THROW_ERRNO(EINVAL) #define THROW_INVALID_IF(Condition) THROW_ERRNO_IF(EINVAL, (Condition)) #define THROW_UNEXPECTED_IF(Condition) THROW_ERRNO_IF(EINVAL, (Condition)) #define LOG_CAUGHT_EXCEPTION() ::wil::details::LogCaughtException(nullptr); #define LOG_CAUGHT_EXCEPTION_MSG(msg) ::wil::details::LogCaughtException(msg); #define RETURN_CAUGHT_EXCEPTION() return -::wil::ResultFromCaughtException() #define CATCH_RETURN() catch (...) { RETURN_CAUGHT_EXCEPTION(); } #define CATCH_RETURN_ERRNO() \ catch (...) \ { \ LOG_CAUGHT_EXCEPTION(); \ errno = ::wil::ResultFromCaughtException(); \ return -1; \ } #define CATCH_LOG() catch (...) { LOG_CAUGHT_EXCEPTION(); } #define CATCH_LOG_MSG(msg) catch (...) { LOG_CAUGHT_EXCEPTION_MSG(msg); } class unique_dir { public: static constexpr DIR* invalid_dir = nullptr; unique_dir(DIR* dir = invalid_dir) noexcept : m_Dir{ dir } { } ~unique_dir() noexcept { reset(); } unique_dir(const unique_dir &) = delete; unique_dir& operator=(const unique_dir &) = delete; unique_dir(unique_dir &&other) noexcept : m_Dir{ other.m_Dir } { other.m_Dir = invalid_dir; } unique_dir& operator=(unique_dir &&other) noexcept { std::swap(m_Dir, other.m_Dir); return *this; } explicit operator bool() const noexcept { return m_Dir != invalid_dir; } DIR* get() const noexcept { return m_Dir; } void reset(DIR *dir = invalid_dir) noexcept { if (m_Dir != invalid_dir) { closedir(m_Dir); } m_Dir = dir; } DIR* release() noexcept { DIR *dir = m_Dir; m_Dir = invalid_dir; return dir; } friend void swap(unique_dir &dir1, unique_dir &dir2) { std::swap(dir1.m_Dir, dir2.m_Dir); } private: DIR *m_Dir; }; class unique_fd { public: static constexpr int invalid_fd = -1; unique_fd(int fd = invalid_fd) noexcept : m_Fd{ fd } { } ~unique_fd() noexcept { reset(); } unique_fd(const unique_fd &) = delete; unique_fd& operator=(const unique_fd &) = delete; unique_fd(unique_fd &&other) noexcept : m_Fd{ other.m_Fd } { other.m_Fd = invalid_fd; } unique_fd& operator=(unique_fd &&other) noexcept { std::swap(m_Fd, other.m_Fd); return *this; } explicit operator bool() const noexcept { return m_Fd >= 0; } int get() const noexcept { return m_Fd; } void reset(int fd = invalid_fd) noexcept { if (m_Fd >= 0) { close(m_Fd); } m_Fd = fd; } int release() noexcept { int fd = m_Fd; m_Fd = invalid_fd; return fd; } friend void swap(unique_fd &fd1, unique_fd &fd2) { std::swap(fd1.m_Fd, fd2.m_Fd); } private: int m_Fd; }; class unique_file { public: static constexpr FILE* invalid_file = nullptr; unique_file(FILE* file = invalid_file) noexcept : m_File{ file } { } ~unique_file() noexcept { reset(); } unique_file(const unique_file &) = delete; unique_file& operator=(const unique_file &) = delete; unique_file(unique_file &&other) noexcept : m_File{ other.m_File } { other.m_File = invalid_file; } unique_file& operator=(unique_file &&other) noexcept { std::swap(m_File, other.m_File); return *this; } explicit operator bool() const noexcept { return m_File != invalid_file; } FILE* get() const noexcept { return m_File; } void reset(FILE *file = invalid_file) noexcept { if (m_File != invalid_file) { fclose(m_File); } m_File = file; } FILE* release() noexcept { FILE *file = m_File; m_File = invalid_file; return file; } friend void swap(unique_file &file1, unique_file &file2) { std::swap(file1.m_File, file2.m_File); } private: FILE *m_File; }; /** Returns an object that executes the given lambda when destroyed. Capture the object with 'auto'; use reset() to execute the lambda early or release() to avoid execution. Exceptions thrown in the lambda will fail-fast; use scope_exit_log to avoid. */ template [[nodiscard]] inline auto scope_exit(TLambda&& lambda) noexcept { return details::lambda_call(std::forward(lambda)); } namespace details { template struct verify_single_flag_helper { static_assert((flag != 0) && ((flag & (flag - 1)) == 0), "Single flag expected, zero or multiple flags found"); static const unsigned long long value = flag; }; // Use size-specific casts to avoid sign extending numbers -- avoid warning C4310: cast truncates constant value #define __WI_MAKE_UNSIGNED(val) \ (sizeof(val) == 1 ? static_cast(val) : \ sizeof(val) == 2 ? static_cast(val) : \ sizeof(val) == 4 ? static_cast(val) : \ static_cast(val)) #define __WI_IS_UNSIGNED_SINGLE_FLAG_SET(val) ((val) && !((val) & ((val) - 1))) #define __WI_IS_SINGLE_FLAG_SET(val) __WI_IS_UNSIGNED_SINGLE_FLAG_SET(__WI_MAKE_UNSIGNED(val)) template inline constexpr bool AreAllFlagsSetHelper(TVal val, TFlags flags) { return ((val & flags) == static_cast(flags)); } template inline constexpr bool IsSingleFlagSetHelper(TVal val) { return __WI_IS_SINGLE_FLAG_SET(val); } template inline constexpr bool IsClearOrSingleFlagSetHelper(TVal val) { return ((val == static_cast>(0)) || IsSingleFlagSetHelper(val)); } template inline constexpr void UpdateFlagsInMaskHelper(TVal& val, TMask mask, TFlags flags) { val = static_cast>((val & ~mask) | (flags & mask)); } template struct variable_size; template <> struct variable_size<1> { typedef unsigned char type; }; template <> struct variable_size<2> { typedef unsigned short type; }; template <> struct variable_size<4> { typedef unsigned long type; }; template <> struct variable_size<8> { typedef unsigned long long type; }; template struct variable_size_mapping { typedef typename variable_size::type type; }; } /** Defines the unsigned type of the same width (1, 2, 4, or 8 bytes) as the given type. This allows code to generically convert any enum class to it's corresponding underlying type. */ template using integral_from_enum = typename details::variable_size_mapping::type; #define WI_StaticAssertSingleBitSet(flag) static_cast(::wil::details::verify_single_flag_helper(WI_EnumValue(flag))>::value) #define WI_IsAnyFlagSet(val, flags) (static_cast(WI_EnumValue(val) & WI_EnumValue(flags)) != static_cast(0)) #define WI_IsFlagSet(val, flag) WI_IsAnyFlagSet(val, WI_StaticAssertSingleBitSet(flag)) #define WI_EnumValue(val) static_cast<::wil::integral_from_enum>(val) //! Evaluates as true if every bitflag specified in `flags` is set within `val`. #define WI_AreAllFlagsSet(val, flags) wil::details::AreAllFlagsSetHelper(val, flags) //! Set zero or more bitflags specified by `flags` in the variable `var`. #define WI_SetAllFlags(var, flags) ((var) |= (flags)) //! Set a single compile-time constant `flag` in the variable `var`. #define WI_SetFlag(var, flag) WI_SetAllFlags(var, WI_StaticAssertSingleBitSet(flag)) //! Clear zero or more bitflags specified by `flags` from the variable `var`. #define WI_ClearAllFlags(var, flags) ((var) &= ~(flags)) //! Clear a single compile-time constant `flag` from the variable `var`. #define WI_ClearFlag(var, flag) WI_ClearAllFlags(var, WI_StaticAssertSingleBitSet(flag)) #define WI_ASSERT(condition) assert(condition) } ================================================ FILE: WSLGd/main.cpp ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "common.h" #include "ProcessMonitor.h" #include "FontMonitor.h" #define CONFIG_FILE ".wslgconfig" #define MSRDC_EXE "msrdc.exe" #define MSTSC_EXE "mstsc.exe" #define GDBSERVER_PATH "/usr/bin/gdbserver" #define WESTON_NOTIFY_SOCKET SHARE_PATH "/weston-notify.sock" #define DEFAULT_ICON_PATH "/usr/share/icons" #define USER_DISTRO_ICON_PATH USER_DISTRO_MOUNT_PATH DEFAULT_ICON_PATH #define MAX_RESERVED_PORT 1024 constexpr auto c_serviceIdTemplate = "%08X-FACB-11E6-BD58-64006A7986D3"; constexpr auto c_userName = "wslg"; constexpr auto c_dbusDir = "/var/run/dbus"; constexpr auto c_versionFile = "/etc/versions.txt"; constexpr auto c_versionMount = SHARE_PATH "/versions.txt"; constexpr auto c_shareDocsDir = "/usr/share/doc"; constexpr auto c_shareDocsMount = SHARE_PATH "/doc"; constexpr auto c_x11RuntimeDir = SHARE_PATH "/.X11-unix"; constexpr auto c_xdgRuntimeDir = SHARE_PATH "/runtime-dir"; constexpr auto c_stdErrLogFile = SHARE_PATH "/stderr.log"; constexpr auto c_sharedMemoryMountPoint = "/mnt/shared_memory"; constexpr auto c_sharedMemoryMountPointEnv = "WSL2_SHARED_MEMORY_MOUNT_POINT"; constexpr auto c_sharedMemoryObDirectoryPathEnv = "WSL2_SHARED_MEMORY_OB_DIRECTORY"; constexpr auto c_installPathEnv = "WSL2_INSTALL_PATH"; constexpr auto c_userProfileEnv = "WSL2_USER_PROFILE"; constexpr auto c_systemDistroEnvSection = "system-distro-env"; constexpr auto c_windowsSystem32 = "/mnt/c/Windows/System32"; constexpr auto c_westonShellDesktopEnv = "WSL2_WESTON_SHELL_DESKTOP"; constexpr auto c_westonRdprailShell = "rdprail-shell"; constexpr auto c_westonRdpdesktopShell = "desktop-shell"; constexpr auto c_rdpRailFile = "wslg.rdp"; constexpr auto c_rdpDesktopFile = "wslg_desktop.rdp"; void LogPrint(int level, const char *func, int line, const char *fmt, ...) noexcept { std::array buffer; struct timeval tv; struct tm *time; va_list va_args; gettimeofday(&tv, NULL); time = localtime(&tv.tv_sec); strftime(buffer.data(), buffer.size(), "%H:%M:%S", time); fprintf(stderr, "[%s.%03ld] <%d>WSLGd: %s:%u: ", buffer.data(), (tv.tv_usec / 1000), level, func, line); va_start(va_args, fmt); vfprintf(stderr, fmt, va_args); va_end(va_args); fprintf(stderr, "\n"); return; } void LogException(const char *message, const char *exceptionDescription) noexcept { LogPrint(LOG_LEVEL_EXCEPTION, __FUNCTION__, __LINE__, "%s %s", message ? message : "Exception:", exceptionDescription); return; } bool IsNumeric(char *str) { char* p; if (!str) return false; strtol(str, &p, 10); return *p == 0; } std::string ToServiceId(unsigned int port) { int size; THROW_LAST_ERROR_IF((size = snprintf(nullptr, 0, c_serviceIdTemplate, port)) < 0); std::string serviceId(size + 1, '\0'); THROW_LAST_ERROR_IF(snprintf(&serviceId[0], serviceId.size(), c_serviceIdTemplate, port) < 0); return serviceId; } std::string TranslateWindowsPath(const char * Path) { std::string commandLine = "/usr/bin/wslpath -a \""; commandLine += Path; commandLine += "\""; std::array buffer; std::string result; std::unique_ptr pipe(popen(commandLine.c_str(), "r"), pclose); THROW_LAST_ERROR_IF(!pipe); while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { result += buffer.data(); } /* trim '\n' from wslpath output */ while (result.back() == '\n') { result.pop_back(); } THROW_ERRNO_IF(EINVAL, pclose(pipe.release()) != 0); return result; } bool GetEnvBool(const char *EnvName, bool DefaultValue) { char *s; s = getenv(EnvName); if (s) { if (strcmp(s, "true") == 0) return true; else if (strcmp(s, "false") == 0) return false; else if (strcmp(s, "1") == 0) return true; else if (strcmp(s, "0") == 0) return false; } return DefaultValue; } std::string GetVmId() { std::unique_ptr pipe(popen("/usr/bin/wslinfo --vm-id -n", "r"), pclose); THROW_LAST_ERROR_IF(!pipe); std::array buffer; std::string result; while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { result += buffer.data(); } THROW_ERRNO_IF(EINVAL, pclose(pipe.release()) != 0); return result; } void SetupOptionalEnv() { #if HAVE_WINPR // Get the path to the WSLG config file. std::string configFilePath = "/mnt/c/ProgramData/Microsoft/WSL/" CONFIG_FILE; auto userProfile = getenv(c_userProfileEnv); if (userProfile) { configFilePath = TranslateWindowsPath(userProfile); configFilePath += "/" CONFIG_FILE; } // Set additional environment variables. wIniFile* wslgConfigIniFile = IniFile_New(); if (wslgConfigIniFile) { if (IniFile_ReadFile(wslgConfigIniFile, configFilePath.c_str()) > 0) { int numKeys = 0; char **keyNames = IniFile_GetSectionKeyNames(wslgConfigIniFile, c_systemDistroEnvSection, &numKeys); for (int n = 0; keyNames && n < numKeys; n++) { const char *value = IniFile_GetKeyValueString(wslgConfigIniFile, c_systemDistroEnvSection, keyNames[n]); if (value) { setenv(keyNames[n], value, true); } } free(keyNames); } IniFile_Free(wslgConfigIniFile); } #endif // HAVE_WINPR return; } int SetupReadyNotify(const char *socket_path) { struct sockaddr_un addr = {}; socklen_t size, name_size; addr.sun_family = AF_LOCAL; name_size = snprintf(addr.sun_path, sizeof addr.sun_path, "%s", socket_path) + 1; size = offsetof(struct sockaddr_un, sun_path) + name_size; unlink(addr.sun_path); wil::unique_fd socketFd{socket(PF_LOCAL, SOCK_SEQPACKET, 0)}; THROW_LAST_ERROR_IF(!socketFd); THROW_LAST_ERROR_IF(bind(socketFd.get(), reinterpret_cast(&addr), size) < 0); THROW_LAST_ERROR_IF(listen(socketFd.get(), 1) < 0); return socketFd.release(); } void WaitForReadyNotify(int notifyFd) { // wait under client connects */ wil::unique_fd fd(accept(notifyFd, 0, 0)); THROW_LAST_ERROR_IF(!fd); } int main(int Argc, char *Argv[]) try { wil::g_LogExceptionCallback = LogException; // Restore default processing for SIGCHLD as both WSLGd and Xwayland depends on this. signal(SIGCHLD, SIG_DFL); // Create a process monitor to track child processes wslgd::ProcessMonitor monitor(c_userName); auto passwordEntry = monitor.GetUserInfo(); // Set required environment variables. struct envVar{ const char* name; const char* value; bool override; }; envVar variables[] = { {"HOME", passwordEntry->pw_dir, true}, {"USER", passwordEntry->pw_name, true}, {"LOGNAME", passwordEntry->pw_name, true}, {"SHELL", passwordEntry->pw_shell, true}, {"PATH", "/usr/sbin:/usr/bin:/sbin:/bin:/usr/games", true}, {"XDG_RUNTIME_DIR", c_xdgRuntimeDir, false}, {"WAYLAND_DISPLAY", "wayland-0", false}, {"DISPLAY", ":0", false}, {"XCURSOR_PATH", USER_DISTRO_ICON_PATH ":" DEFAULT_ICON_PATH , false}, {"XCURSOR_THEME", "whiteglass", false}, {"XCURSOR_SIZE", "16", false}, {"PULSE_SERVER", SHARE_PATH "/PulseServer", false}, {"PULSE_AUDIO_RDP_SINK", SHARE_PATH "/PulseAudioRDPSink", false}, {"PULSE_AUDIO_RDP_SOURCE", SHARE_PATH "/PulseAudioRDPSource", false}, {"WSL2_DEFAULT_APP_ICON", DEFAULT_ICON_PATH "/wsl/linux.png", false}, {"WSL2_DEFAULT_APP_OVERLAY_ICON", DEFAULT_ICON_PATH "/wsl/linux.png", false}, }; for (auto &var : variables) { THROW_LAST_ERROR_IF(setenv(var.name, var.value, var.override) < 0); } SetupOptionalEnv(); // if any components output log to /dev/kmsg, make it writable. if (GetEnvBool("WSLG_LOG_KMSG", false)) THROW_LAST_ERROR_IF(chmod("/dev/kmsg", 0666) < 0); // Open a file for logging errors and set it to stderr for WSLGd as well as any child process. { const char *errLog = getenv("WSLG_ERR_LOG_PATH"); if (!errLog) { errLog = c_stdErrLogFile; } wil::unique_fd stdErrLogFd(open(errLog, (O_RDWR | O_CREAT), (S_IRUSR | S_IRGRP | S_IROTH))); if (stdErrLogFd && (stdErrLogFd.get() != STDERR_FILENO)) { dup2(stdErrLogFd.get(), STDERR_FILENO); } } // Ensure the daemon is launched as root. if (geteuid() != 0) { LOG_ERROR("must be run as root."); return 1; } // Query the VM ID. auto vmId = GetVmId(); if (vmId.empty()) { LOG_ERROR("could not query VM ID."); return 1; } // Query the WSL install path. bool isWslInstallPathEnvPresent = false; std::string wslInstallPath = "C:\\ProgramData\\Microsoft\\WSL"; auto installPath = getenv(c_installPathEnv); if (installPath) { isWslInstallPathEnvPresent = true; wslInstallPath = installPath; } // Bind mount the versions.txt file which contains version numbers of the various WSLG pieces. { wil::unique_fd fd(open(c_versionMount, (O_RDWR | O_CREAT), (S_IRUSR | S_IRGRP | S_IROTH))); THROW_LAST_ERROR_IF(!fd); } THROW_LAST_ERROR_IF(mount(c_versionFile, c_versionMount, NULL, MS_BIND | MS_RDONLY, NULL) < 0); std::filesystem::create_directories(c_shareDocsMount); THROW_LAST_ERROR_IF(mount(c_shareDocsDir, c_shareDocsMount, NULL, MS_BIND | MS_RDONLY, NULL) < 0); // Create a font folder monitor wslgd::FontMonitor fontMonitor; // Make directories and ensure the correct permissions. std::filesystem::create_directories(c_dbusDir); THROW_LAST_ERROR_IF(chown(c_dbusDir, passwordEntry->pw_uid, passwordEntry->pw_gid) < 0); THROW_LAST_ERROR_IF(chmod(c_dbusDir, 0777) < 0); std::filesystem::create_directories(c_x11RuntimeDir); THROW_LAST_ERROR_IF(chmod(c_x11RuntimeDir, 0777) < 0); std::filesystem::create_directories(c_xdgRuntimeDir); THROW_LAST_ERROR_IF(chown(c_xdgRuntimeDir, passwordEntry->pw_uid, passwordEntry->pw_gid) < 0); THROW_LAST_ERROR_IF(chmod(c_xdgRuntimeDir, 0777) < 0); // Attempt to mount the virtiofs share for shared memory. bool isSharedMemoryMounted = false; auto sharedMemoryObDirectoryPath = getenv(c_sharedMemoryObDirectoryPathEnv); if (sharedMemoryObDirectoryPath) { std::filesystem::create_directories(c_sharedMemoryMountPoint); if (mount("wslg", c_sharedMemoryMountPoint, "virtiofs", 0, "dax") < 0) { LOG_ERROR("Failed to mount wslg shared memory %s.", strerror(errno)); } else { THROW_LAST_ERROR_IF(chmod(c_sharedMemoryMountPoint, 0777) < 0); isSharedMemoryMounted = true; } } else { LOG_ERROR("shared memory ob directory path is not set."); } // Create a listening vsock in the reserved port range to be used for the RDP connection. sockaddr_vm address{}; address.svm_family = AF_VSOCK; address.svm_cid = VMADDR_CID_ANY; socklen_t addressSize = sizeof(address); wil::unique_fd socketFd{socket(AF_VSOCK, SOCK_STREAM, 0)}; THROW_LAST_ERROR_IF(!socketFd); for (unsigned int port = 1; port < MAX_RESERVED_PORT; port += 1) { address.svm_port = port; if (bind(socketFd.get(), reinterpret_cast(&address), addressSize) == 0) { break; } THROW_LAST_ERROR_IF(errno != EADDRINUSE); } THROW_ERRNO_IF(EINVAL, (address.svm_port == MAX_RESERVED_PORT)); THROW_LAST_ERROR_IF(listen(socketFd.get(), 1) < 0); std::string socketEnvString("USE_VSOCK="); socketEnvString += std::to_string(socketFd.get()); std::string serviceIdEnvString("WSLG_SERVICE_ID="); serviceIdEnvString += ToServiceId(address.svm_port); struct rlimit limit; THROW_LAST_ERROR_IF(getrlimit(RLIMIT_NOFILE, &limit) < 0); limit.rlim_cur = limit.rlim_max; THROW_LAST_ERROR_IF(setrlimit(RLIMIT_NOFILE, &limit) < 0); // Set shared memory mount point to env when available. if (!isSharedMemoryMounted || (setenv(c_sharedMemoryMountPointEnv, c_sharedMemoryMountPoint, true) < 0)) { // shared memory is not available, env must be cleared if it's set. THROW_LAST_ERROR_IF(unsetenv(c_sharedMemoryMountPointEnv) < 0); isSharedMemoryMounted = false; } // Construct socket option string. std::string westonSocketOption("--socket="); westonSocketOption += getenv("WAYLAND_DISPLAY"); // Check if weston shell override is specified. // Otherwise, default shell is 'rdprail-shell'. // Alternatively, it can be 'desktop-shell'. bool isRdpDesktopShell = GetEnvBool(c_westonShellDesktopEnv, false); std::string westonShellName; if (isRdpDesktopShell) westonShellName = c_westonRdpdesktopShell; else westonShellName = c_westonRdprailShell; // Construct shell option string. std::string westonShellOption("--shell="); westonShellOption += westonShellName; westonShellOption += ".so"; // Construct log file option string. std::string westonLogFileOption("--log="); auto westonLogFilePathEnv = getenv("WSLG_WESTON_LOG_PATH"); if (westonLogFilePathEnv) { westonLogFileOption += westonLogFilePathEnv; } else { westonLogFileOption += SHARE_PATH "/weston.log"; } // Construct logger option string. // By default, enable standard log and rdp-backend. std::string westonLoggerOption("--logger-scopes=log,rdp-backend"); // If rdprail-shell is used, enable logger for that. if (!isRdpDesktopShell) { westonLoggerOption += ","; westonLoggerOption += c_westonRdprailShell; } // Setup notify for wslgd-notify.so wil::unique_fd notifyFd(SetupReadyNotify(WESTON_NOTIFY_SOCKET)); THROW_LAST_ERROR_IF(!notifyFd); // Construct weston option string. std::string westonArgs; char *gdbServerPort = getenv("WSLG_WESTON_GDBSERVER_PORT"); if ((access(GDBSERVER_PATH, X_OK) == 0) && IsNumeric(gdbServerPort)) { westonArgs += GDBSERVER_PATH; westonArgs += " :"; westonArgs += gdbServerPort; westonArgs += " "; } westonArgs += "/usr/bin/weston "; westonArgs += "--backend=rdp-backend.so --modules=wslgd-notify.so --xwayland "; westonArgs += westonSocketOption; westonArgs += " "; westonArgs += westonShellOption; westonArgs += " "; westonArgs += westonLogFileOption; westonArgs += " "; westonArgs += westonLoggerOption; // Launch weston. // N.B. Additional capabilities are needed to setns to the mount namespace of the user distro. monitor.LaunchProcess(std::vector{ "/usr/bin/sh", "-c", std::move(westonArgs) }, std::vector{ CAP_SYS_ADMIN, CAP_SYS_CHROOT, CAP_SYS_PTRACE }, std::vector{ std::move(socketEnvString), std::move(serviceIdEnvString), "WSLGD_NOTIFY_SOCKET=" WESTON_NOTIFY_SOCKET, "WESTON_DISABLE_ABSTRACT_FD=1", getenv("WLOG_APPENDER") ? : "", "WLOG_APPENDER=file", getenv("WLOG_FILEAPPENDER_OUTPUT_FILE_NAME") ? "" : "WLOG_FILEAPPENDER_OUTPUT_FILE_NAME=wlog.log", getenv("WLOG_FILEAPPENDER_OUTPUT_FILE_PATH") ? "" : "WLOG_FILEAPPENDER_OUTPUT_FILE_PATH=" SHARE_PATH } ); // Wait weston to be ready before starting RDP client, pulseaudio server. WaitForReadyNotify(notifyFd.get()); unlink(WESTON_NOTIFY_SOCKET); // Start font monitoring if user distro's X11 fonts to be shared with system distro. if (GetEnvBool("WSLG_USE_USER_DISTRO_XFONTS", true)) fontMonitor.Start(); // Launch the mstsc/msrdc client. std::string remote("/v:"); remote += vmId; std::string serviceId("/hvsocketserviceid:"); serviceId += ToServiceId(address.svm_port); std::string sharedMemoryObPath(""); if (isSharedMemoryMounted) { sharedMemoryObPath += "/wslgsharedmemorypath:"; sharedMemoryObPath += sharedMemoryObDirectoryPath; } std::filesystem::path rdpClientExePath; bool isUseMstsc = GetEnvBool("WSLG_USE_MSTSC", false); if (!isUseMstsc && !wslInstallPath.empty()) { std::filesystem::path msrdcExePath = TranslateWindowsPath(wslInstallPath.c_str()); msrdcExePath /= MSRDC_EXE; if (access(msrdcExePath.c_str(), X_OK) == 0) { rdpClientExePath = std::move(msrdcExePath); } } if (rdpClientExePath.empty()) { rdpClientExePath = c_windowsSystem32; rdpClientExePath /= MSTSC_EXE; } std::string wslDvcPlugin; if (GetEnvBool("WSLG_USE_WSLDVC_PRIVATE", false)) wslDvcPlugin = "/plugin:WSLDVC_PRIVATE"; else if (isWslInstallPathEnvPresent) wslDvcPlugin = "/plugin:WSLDVC_PACKAGE"; else wslDvcPlugin = "/plugin:WSLDVC"; std::string rdpFilePathArg(wslInstallPath); rdpFilePathArg += "\\"; // Windows-style path if (isRdpDesktopShell) rdpFilePathArg += c_rdpDesktopFile; else rdpFilePathArg += c_rdpRailFile; monitor.LaunchProcess(std::vector{ "/init", std::move(rdpClientExePath), basename(rdpClientExePath.c_str()), "/wslg", // set wslg option first so following parameters are parsed in context of wslg. "/silent", // then set silent option before anything-else. std::move(remote), std::move(serviceId), std::move(wslDvcPlugin), std::move(sharedMemoryObPath), std::move(rdpFilePathArg) }); // Launch the system dbus daemon. monitor.LaunchProcess(std::vector{ "/usr/bin/dbus-daemon", "--syslog", "--nofork", "--nopidfile", "--system" }, std::vector{CAP_SETGID, CAP_SETUID} ); // Construct pulseaudio launch command line. std::string pulseaudioLaunchArgs = "/usr/bin/dbus-launch " "/usr/bin/pulseaudio " "--log-time=true " "--disallow-exit=true " "--exit-idle-time=-1 " "--load=\"module-rdp-sink sink_name=RDPSink\" " "--load=\"module-rdp-source source_name=RDPSource\" " "--load=\"module-native-protocol-unix socket=" SHARE_PATH "/PulseServer auth-anonymous=true\" "; // Construct log file option string. std::string pulseaudioLogFileOption("--log-target="); auto pulseAudioLogFilePathEnv = getenv("WSLG_PULSEAUDIO_LOG_PATH"); if (pulseAudioLogFilePathEnv) { pulseaudioLogFileOption += pulseAudioLogFilePathEnv; } else { pulseaudioLogFileOption += "newfile:" SHARE_PATH "/pulseaudio.log"; } pulseaudioLaunchArgs += pulseaudioLogFileOption; // Launch pulseaudio and the associated dbus daemon. monitor.LaunchProcess(std::vector{ "/usr/bin/sh", "-c", std::move(pulseaudioLaunchArgs) }); return monitor.Run(); } CATCH_RETURN_ERRNO(); ================================================ FILE: WSLGd/meson.build ================================================ project('WSLGd', 'cpp', version : '0.1', default_options : ['cpp_std=c++17']) config_h = configuration_data() dep_winpr = dependency('winpr3', version: '>= 3.0.0', required: false) if dep_winpr.found() config_h.set('HAVE_WINPR', '1') else dep_winpr = dependency('winpr2', version: '>= 2.0.0', required: false) if dep_winpr.found() config_h.set('HAVE_WINPR', '1') endif endif configure_file(output: 'config.h', configuration: config_h) executable('WSLGd', 'main.cpp', 'ProcessMonitor.cpp', 'FontMonitor.cpp', dependencies: dep_winpr, link_args: '-lcap', install : true) ================================================ FILE: WSLGd/precomp.h ================================================ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "lxwil.h" #if HAVE_WINPR #include "winpr/ini.h" #endif // HAVE_WINPR ================================================ FILE: azure-pipelines.yml ================================================ resources: repositories: - repository: FreeRDP type: github endpoint: GitHub connection 1 name: microsoft/FreeRDP-mirror ref: working - repository: weston type: github endpoint: GitHub connection 1 name: microsoft/weston-mirror ref: working - repository: pulseaudio type: github endpoint: GitHub connection 1 name: microsoft/pulseaudio-mirror ref: working trigger: - main stages: - stage: Build_SystemDistro displayName: "Build System Distro (x64 and ARM64)" jobs: - job: 'Build_Ubuntu_x64' displayName: 'Build x64 system distro' timeoutInMinutes: 200 pool: vmImage: 'ubuntu-latest' steps: - checkout: FreeRDP - checkout: weston - checkout: pulseaudio - checkout: self - template: devops/common-linux.yml - script: wget https://azurelinuxsrcstorage.blob.core.windows.net/sources/core/mesa-23.1.0.tar.xz && tar -xvf mesa-23.1.0.tar.xz displayName: 'Download Mesa from Azure Linux' - script: wget https://github.com/microsoft/DirectX-Headers/archive/refs/tags/v1.608.0.tar.gz && tar -xvf v1.608.0.tar.gz displayName: 'Download DirectX-Headers from GitHub' - script: mv FreeRDP-mirror/ wslg/vendor/FreeRDP && mv weston-mirror/ wslg/vendor/weston && mv pulseaudio-mirror/ wslg/vendor/pulseaudio && mv mesa-23.1.0/ wslg/vendor/mesa && mv DirectX-Headers-1.608.0/ wslg/vendor/DirectX-Headers-1.0 displayName: 'Move sub projects (FreeRDP, Weston, PulseAudio, Mesa, DirectX-Headers)' - script: docker build -f ./wslg/Dockerfile -t system-distro-x64 ./wslg --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion` --build-arg WSLG_ARCH=x86_64 displayName: 'Create System Distro Docker image Azure Linux 3.0 x64' - script: docker export `docker create system-distro-x64` > $(Agent.BuildDirectory)/system_x64.tar displayName: 'Create system_x64.tar' - script: docker build -f ./wslg/Dockerfile -t system-distro-dev-x64 ./wslg --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion` --build-arg WSLG_ARCH=x86_64 --target dev displayName: 'Create System Distro Dev Docker image Azure Linux 3.0 x64' - script: docker cp `docker create system-distro-dev-x64 /bin/bash`:/work/debuginfo/system-debuginfo.tar.gz $(Agent.BuildDirectory)/system-debuginfo_x64.tar.gz displayName: 'Copy system-debuginfo_x64.tar.gz' - task: Go@0 inputs: command: 'custom' customCommand: 'run' arguments: 'tar2ext4.go -vhd -i $(Agent.BuildDirectory)/system_x64.tar -o $(Agent.BuildDirectory)/system_x64.vhd' workingDirectory: 'hcsshim/cmd/tar2ext4' displayName: 'Create system_x64.vhd' - task: PublishPipelineArtifact@1 displayName: 'Publish system_x64.vhd artifact' inputs: targetPath: $(Agent.BuildDirectory)/system_x64.vhd artifact: 'system_x64.vhd' publishLocation: 'pipeline' - task: PublishPipelineArtifact@1 displayName: 'Publish system-debuginfo_x64.tar.gz artifact' inputs: targetPath: $(Agent.BuildDirectory)/system-debuginfo_x64.tar.gz artifact: 'system-debuginfo_x64.tar.gz' publishLocation: 'pipeline' - job: 'Build_Windows_x64' dependsOn: 'Build_Ubuntu_x64' displayName: 'Build WSLDCV (x64) Plugin' pool: vmImage: 'windows-2019' demands: - msbuild - visualstudio steps: - checkout: self - template: devops/common-win.yml - task: PowerShell@2 displayName: 'Update WSLDVCPlugin version' inputs: targetType: filePath workingDirectory: './WSLDVCPlugin' filePath: .\WSLDVCPlugin\UpdateRCVersion.ps1 pwsh: true - task: MSBuild@1 displayName: 'Build RDP Plugin (x64)' inputs: solution: './WSLDVCPlugin/WSLDVCPlugin.sln' platform: 'x64' configuration: 'Release' - task: PublishSymbols@2 displayName: Publish symbols inputs: SymbolServerType: 'TeamServices' TreatNotIndexedAsWarning: true SymbolsProduct: wslg SearchPattern: | WSLDVCPlugin/x64/Release/*.pdb WSLDVCPlugin/x64/Release/*.dll - script: 'MOVE WSLDVCPlugin\x64\Release\WSLDVCPlugin.pdb package\WSLDVCPlugin_x64.pdb' displayName: 'Move plugin PDB to package (x64)' - task: PublishPipelineArtifact@1 displayName: 'Publish WSLDVCPlugin PDB (x64)' inputs: targetPath: package\WSLDVCPlugin_x64.pdb artifact: 'WSLDVCPlugin.x64.pdb' publishLocation: 'pipeline' - script: 'MOVE WSLDVCPlugin\x64\Release\WSLDVCPlugin.dll package\WSLDVCPlugin_x64.dll' displayName: 'Move plugin DLL to package (x64)' - task: PublishPipelineArtifact@1 displayName: 'Publish WSLDVCPlugin DLL (x64)' inputs: targetPath: package\WSLDVCPlugin_x64.dll artifact: 'WSLDVCPlugin.x64.dll' publishLocation: 'pipeline' - job: 'Build_Ubuntu_ARM64' displayName: 'Build ARM64 system distro' condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') timeoutInMinutes: 200 pool: vmImage: 'ubuntu-latest' steps: - checkout: FreeRDP - checkout: weston - checkout: pulseaudio - checkout: self - template: devops/common-linux.yml - bash: | curl -L -o ~/.docker/cli-plugins/docker-buildx --create-dirs ${BUILDX_URL} chmod a+x ~/.docker/cli-plugins/docker-buildx docker run --privileged --rm tonistiigi/binfmt:qemu-v9.2.0-51 --install all ~/.docker/cli-plugins/docker-buildx create --use ~/.docker/cli-plugins/docker-buildx inspect --bootstrap displayName: Prepare buildx condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') env: BUILDX_URL: https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-amd64 - script: | echo '{ "experimental": true }' | sudo tee /etc/docker/daemon.json sudo service docker restart displayName: 'Enable Docker Engine experimental ' - script: wget https://azurelinuxsrcstorage.blob.core.windows.net/sources/core/mesa-23.1.0.tar.xz && tar -xvf mesa-23.1.0.tar.xz displayName: 'Download Mesa from Azure Linux' - script: wget https://github.com/microsoft/DirectX-Headers/archive/refs/tags/v1.608.0.tar.gz && tar -xvf v1.608.0.tar.gz displayName: 'Download DirectX-Headers from GitHub' - script: mv FreeRDP-mirror/ wslg/vendor/FreeRDP && mv weston-mirror/ wslg/vendor/weston && mv pulseaudio-mirror/ wslg/vendor/pulseaudio && mv mesa-23.1.0/ wslg/vendor/mesa && mv DirectX-Headers-1.608.0/ wslg/vendor/DirectX-Headers-1.0 displayName: 'Move sub projects (FreeRDP, Weston, PulseAudio, Mesa, DirectX-Headers)' - script: ~/.docker/cli-plugins/docker-buildx build -f ./wslg/Dockerfile --output type=tar,dest=$(Agent.BuildDirectory)/system_arm64.tar --platform=linux/arm64 ./wslg --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion` --build-arg WSLG_ARCH=aarch64 displayName: 'Create system_arm64.tar' - task: Go@0 inputs: command: 'custom' customCommand: 'run' arguments: 'tar2ext4.go -vhd -i $(Agent.BuildDirectory)/system_arm64.tar -o $(Agent.BuildDirectory)/system_arm64.vhd' workingDirectory: 'hcsshim/cmd/tar2ext4' displayName: 'Create system_arm64.vhd' - task: PublishPipelineArtifact@1 displayName: 'Publish system_arm64.vhd artifact' condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') inputs: targetPath: $(Agent.BuildDirectory)/system_arm64.vhd artifact: 'system_arm64.vhd' publishLocation: 'pipeline' - script: mkdir ./dev && ~/.docker/cli-plugins/docker-buildx build -f ./wslg/Dockerfile --output type=tar,dest=./dev/dev_build.tar --target=dev --platform=linux/arm64 ./wslg --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion` --build-arg WSLG_ARCH=aarch64 && tar -xvf ./dev/dev_build.tar -C ./dev/ && mv ./dev/work/debuginfo/system-debuginfo.tar.gz $(Agent.BuildDirectory)/system-debuginfo_arm64.tar.gz displayName: 'Copy system-debuginfo_arm64.tar.gz' - task: PublishPipelineArtifact@1 displayName: 'Publish system-debuginfo_arm64.tar.gz artifact' inputs: targetPath: $(Agent.BuildDirectory)/system-debuginfo_arm64.tar.gz artifact: 'system-debuginfo_arm64.tar.gz' publishLocation: 'pipeline' - job: 'Build_Windows_ARM64' dependsOn: 'Build_Ubuntu_ARM64' displayName: 'Build WSLDCV (ARM64) Plugin' condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') pool: vmImage: 'windows-2019' demands: - msbuild - visualstudio steps: - checkout: self - template: devops/common-win.yml - task: PowerShell@2 displayName: 'Update WSLDVCPlugin version' inputs: targetType: filePath workingDirectory: './WSLDVCPlugin' filePath: .\WSLDVCPlugin\UpdateRCVersion.ps1 pwsh: true - task: MSBuild@1 displayName: 'Build RDP Plugin (ARM64)' inputs: solution: './WSLDVCPlugin/WSLDVCPlugin.sln' platform: 'ARM64' configuration: 'Release' - task: PublishSymbols@2 displayName: Publish symbols inputs: SymbolServerType: 'TeamServices' TreatNotIndexedAsWarning: true SymbolsProduct: wslg SearchPattern: | WSLDVCPlugin/arm64/Release/*.pdb WSLDVCPlugin/arm64/Release/*.dll - script: 'MOVE WSLDVCPlugin\ARM64\Release\WSLDVCPlugin.pdb package\WSLDVCPlugin_ARM64.pdb' displayName: 'Move plugin PDB to package (ARM64)' - task: PublishPipelineArtifact@1 displayName: 'Publish WSLDVCPlugin PDB (ARM64)' inputs: targetPath: package\WSLDVCPlugin_ARM64.pdb artifact: 'WSLDVCPlugin.ARM64.pdb' publishLocation: 'pipeline' - script: 'MOVE WSLDVCPlugin\ARM64\Release\WSLDVCPlugin.dll package\WSLDVCPlugin_ARM64.dll' displayName: 'Move plugin to package (ARM64)' - task: PublishPipelineArtifact@1 displayName: 'Publish WSLDVCPlugin DLL (ARM64)' inputs: targetPath: package\WSLDVCPlugin_ARM64.dll artifact: 'WSLDVCPlugin.ARM64.dll' publishLocation: 'pipeline' - stage: PublishPackage displayName: "Publish WSLg NuGet Package" jobs: - job: 'Publish_UniversalPackage' condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') displayName: 'Download artifact and push NuGet package' pool: vmImage: 'windows-2019' steps: - task: DownloadPipelineArtifact@2 displayName: 'Download system_x64.vhd' inputs: buildType: 'current' artifactName: 'system_x64.vhd' targetPath: 'package/' - task: DownloadPipelineArtifact@2 displayName: 'Download system-debuginfo_x64.tar.gz' inputs: buildType: 'current' artifactName: 'system-debuginfo_x64.tar.gz' targetPath: 'package/' - task: DownloadPipelineArtifact@2 displayName: 'Download WSLDVCPlugin DLL (x64)' inputs: buildType: 'current' artifactName: 'WSLDVCPlugin.x64.dll' targetPath: 'package/' - task: DownloadPipelineArtifact@2 displayName: 'Download WSLDVCPlugin PDB (x64)' inputs: buildType: 'current' artifactName: 'WSLDVCPlugin.x64.pdb' targetPath: 'package/' - task: DownloadPipelineArtifact@2 displayName: 'Download system_arm64.vhd' inputs: buildType: 'current' artifactName: 'system_arm64.vhd' targetPath: 'package/' - task: DownloadPipelineArtifact@2 displayName: 'Download system-debuginfo_arm64.tar.gz' inputs: buildType: 'current' artifactName: 'system-debuginfo_arm64.tar.gz' targetPath: 'package/' - task: DownloadPipelineArtifact@2 displayName: 'Download WSLDVCPlugin DLL (ARM64)' inputs: buildType: 'current' artifactName: 'WSLDVCPlugin.ARM64.dll' targetPath: 'package/' - task: DownloadPipelineArtifact@2 displayName: 'Download WSLDVCPlugin PDB (ARM64)' inputs: buildType: 'current' artifactName: 'WSLDVCPlugin.ARM64.pdb' targetPath: 'package/' - task: PowerShell@2 displayName: 'Update Microsoft.WSLg.nuspec version' inputs: targetType: filePath filePath: .\devops\updateversion.ps1 arguments: .\Microsoft.WSLg.nuspec "package.metadata.version" "" "-beta" pwsh: true - task: PowerShell@2 displayName: 'Update Microsoft.WSLg.nuspec release notes' inputs: targetType: filePath filePath: .\devops\updateversion.ps1 arguments: .\Microsoft.WSLg.nuspec "package.metadata.releaseNotes" "" "-beta" "hash" pwsh: true - script: 'nuget pack .\Microsoft.WSLg.nuspec' displayName: 'Package NuGet' - script: 'rename *.nupkg Microsoft.WSLg.nupkg' displayName: 'Rename Nuget Package' - task: PublishPipelineArtifact@1 displayName: 'Save Microsoft.WSLg.nupkg artifact' inputs: targetPath: Microsoft.WSLg.nupkg artifact: 'Microsoft.WSLg.nupkg' publishLocation: 'pipeline' - task: '333b11bd-d341-40d9-afcf-b32d5ce6f23b@2' condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') inputs: command: 'push' packagesToPush: 'Microsoft.WSLg.nupkg' nuGetFeedType: 'internal' publishVstsFeed: 'wsl' allowPackageConflicts: true verbosityPush: 'Normal' ================================================ FILE: build-and-export.sh ================================================ #!/bin/bash set -e echo "=== Building Docker image ===" docker build -f Dockerfile -t system-distro-x64 . \ --build-arg WSLG_VERSION=1.0.72-test \ --build-arg WSLG_ARCH=x86_64 echo "" echo "=== Exporting Docker container to tar ===" docker export $(docker create system-distro-x64) > system_x64.tar echo "" echo "=== Converting tar to VHD ===" # Run tar2ext4 directly from GitHub go run github.com/Microsoft/hcsshim/cmd/tar2ext4@latest -vhd -i system_x64.tar -o system_x64.vhd echo "" echo "=== Done! ===" echo "Output file: $(pwd)/system_x64.vhd" ls -lh system_x64.vhd ================================================ FILE: cgmanifest.json ================================================ { "Registrations": [ { "Component": { "Type": "git", "Git": { "RepositoryUrl": "https://github.com/pulseaudio/pulseaudio", "CommitHash": "0e691b96640919b1c7ed91ae9240761c5775deeb" } }, "DevelopmentDependency": false }, { "Component": { "Type": "git", "Git": { "RepositoryUrl": "https://github.com/wayland-project/weston", "CommitHash": "04d3ae265d8d8f84352c8dac21ec40b2fe07e7d2" } }, "DevelopmentDependency": false }, { "Component": { "Type": "git", "Git": { "RepositoryUrl": "https://github.com/FreeRDP/FreeRDP", "CommitHash": "39f56443f2dc50e0dcfa52d4f8f15008d5b8ed8e" } }, "DevelopmentDependency": false }, { "Component": { "Type": "mesa", "Git": { "RepositoryUrl": "https://github.com/mesa3d/mesa", "CommitHash": "731ea06758663a2de3a2bd1f12eb8809d4c136fd" } }, "DevelopmentDependency": false }, ] } ================================================ FILE: config/BUILD.md ================================================ # Introduction This repository contains a Dockerfile and supporting tools to build the WSL GUI system distro image. ## Quick start For self-hosting WSLG check use this instructions https://github.com/microsoft/wslg/wiki#installing-self-hosting ## Setup and build WSLG and System Distro 0. Install and start Docker in a Linux or WSL2 environment. 1. Clone the FreeRDP ,Weston and PulseAudio side by side this repo repositories and checkout the "working" branch from each: ```bash git clone https://github.com/microsoft/FreeRDP-mirror vendor/FreeRDP -b working git clone https://github.com/microsoft/weston-mirror.git vendor/weston -b working git clone https://github.com/microsoft/pulseaudio-mirror.git vendor/pulseaudio -b working ``` 2. Download the mesa and directx headers code. ``` wget https://azurelinuxsrcstorage.blob.core.windows.net/sources/core/mesa-23.1.0.tar.xz tar -xf mesa-23.1.0.tar.xz -C vendor mv vendor/mesa-23.1.0 vendor/mesa wget https://github.com/microsoft/DirectX-Headers/archive/refs/tags/v1.608.0.tar.gz tar -xvf v1.608.0.tar.gz -C vendor mv vendor/DirectX-Headers-1.608.0 vendor/DirectX-Headers-1.0 ``` 3. Create the VHD: 3.1 From the parent directory where you cloned `wslg` clone `hcsshim` which contains `tar2ext4` and will be used to create the system distro vhd ``` git clone --branch v0.8.9 --single-branch https://github.com/microsoft/hcsshim.git ``` 3.1 From the parent directory build and export the docker image: ``` sudo docker build -t system-distro-x64 ./wslg --build-arg SYSTEMDISTRO_VERSION=`git --git-dir=wslg/.git rev-parse --verify HEAD` --build-arg SYSTEMDISTRO_ARCH=x86_64 sudo docker export `sudo docker create system-distro-x64` > system_x64.tar ``` 3.3 Create the system distro vhd using `tar2ext4` ```bash cd hcsshim/cmd/tar2ext4 go run tar2ext4.go -vhd -i ../../../system_x64.tar -o ../../../system.vhd ``` This will create system distro image `system.vhd` 4. Change the system distro: 4.1 Before replace the system distro you will need to shutdown WSL ``` wsl --shutdown ``` 4.2 By default the system distro is located at `C:\ProgramData\Microsoft\WSL\system.vhd` If you want to use the system distro from a different path you can change the .wslconfig. * Add an entry to your `%USERPROFILE%\.wslconfig` ``` [wsl2] systemDistro=C:\\tmp\\system.vhd ``` 4.3 After update the system distro you should be able to launch any user distro and WSL will automatically launch the system distro along with the user distro. 5. Inspecting the system distro: If the system distro isn't working correctly or you need to inspect what is running inside the system distro you can do: ``` wsl --system [DistroName] ``` For instance you should check if weston and pulse audio are running inside the system distro using `ps -ax | grep weston` or `ps -ax | grep pulse` You should see something like this: ```bash root@DESKTOP-7LJ03SK:/mnt/d# ps -ax | grep weston 11 ? Sl 6:51 /usr/local/bin/weston --backend=rdp-backend.so --xwayland --shell=rdprail-shell.so --log=/mnt/wslg/weston.log ``` ================================================ FILE: config/default_wslg.pa ================================================ ### WSLG specific ### ### Load Windows's default sound from Windows volume. .ifexists /mnt/c/Windows/Media/Windows Default.wav load-sample x11-bell /mnt/c/Windows/Media/Windows Default.wav ### Enable X11 bell by loading module-x11-bell. load-module module-x11-bell sample=x11-bell .endif ================================================ FILE: config/local.conf ================================================ /mnt/wslg/distro/usr/share/fonts /mnt/wslg/distro/usr/local/share/fonts ================================================ FILE: config/weston.ini ================================================ [xwayland] disable_access_control=true [input-method] path= ================================================ FILE: config/wsl.conf ================================================ [boot] command=/usr/bin/WSLGd [user] default=wslg ================================================ FILE: config/xwayland_log.patch ================================================ diff -Naur xorg-server-1.20.9/hw/xwayland/xwayland.c xorg-server-1.20.9-patch/hw/xwayland/xwayland.c --- xorg-server-1.20.9/hw/xwayland/xwayland.c 2020-11-21 01:07:32.850000000 +0000 +++ xorg-server-1.20.9-patch/hw/xwayland/xwayland.c 2020-11-21 01:06:20.290000000 +0000 @@ -61,6 +61,9 @@ void OsVendorInit(void) { + LogInit("/mnt/wslg/xlog.log", ".old"); + LogSetParameter(XLOG_FILE_VERBOSITY, X_DEBUG); + if (serverGeneration == 1) ForceClockId(CLOCK_MONOTONIC); } ================================================ FILE: debuginfo/FreeRDP2.list ================================================ /usr/bin/winpr-hash /usr/bin/winpr-makecert /usr/lib/libfreerdp-server2.so.2.4.0 /usr/lib/libfreerdp2.so.2.4.0 /usr/lib/libuwac0.so.0.1.1 /usr/lib/libwinpr-tools2.so.2.4.0 /usr/lib/libwinpr2.so.2.4.0 ================================================ FILE: debuginfo/FreeRDP3.list ================================================ /usr/bin/winpr-hash /usr/bin/winpr-makecert /usr/lib/libfreerdp-server3.so.3.0.0 /usr/lib/libfreerdp3.so.3.0.0 /usr/lib/libuwac0.so.0.2.0 /usr/lib/libwinpr-tools3.so.3.0.0 /usr/lib/libwinpr3.so.3.0.0 ================================================ FILE: debuginfo/WSLGd.list ================================================ /usr/bin/WSLGd ================================================ FILE: debuginfo/gen_debuginfo.sh ================================================ #!/bin/bash function split_debuginfo(){ file=${1//[$'\t\r\n']} dir=$(dirname "$file") mkdir -p $2/debuginfo/$dir org=$2/$file dst=$2/debuginfo/$file.debug objcopy --only-keep-debug $org $dst objcopy --strip-debug $org objcopy --add-gnu-debuglink=$dst $org echo $dst } while read line; do split_debuginfo $line $2; done < $1 ================================================ FILE: debuginfo/rdpapplist.list ================================================ usr/lib/rdpapplist/librdpapplist-server.so ================================================ FILE: debuginfo/weston.list ================================================ usr/bin/weston usr/bin/weston-launch usr/bin/weston-screenshooter usr/lib/libweston-9.so.0.0.0 usr/lib/libweston-desktop-9.so.0.0.0 usr/lib/libweston-9/gl-renderer.so usr/lib/libweston-9/rdp-backend.so usr/lib/libweston-9/xwayland.so usr/lib/weston/libexec_weston.so.0.0.0 usr/lib/weston/desktop-shell.so usr/lib/weston/rdprail-shell.so usr/lib/weston/wslgd-notify.so usr/libexec/weston-desktop-shell usr/libexec/weston-keyboard usr/libexec/weston-rdprail-shell ================================================ FILE: devops/common-linux.yml ================================================ steps: - task: DockerInstaller@0 inputs: dockerVersion: '20.10.7' releaseType: 'stable' - script: wget https://github.com/GitTools/GitVersion/releases/download/5.6.8/gitversion-linux-x64-5.6.8.tar.gz && tar -xvf gitversion-linux-x64-5.6.8.tar.gz && sudo mv gitversion /usr/local/bin && sudo mv libgit2-6777db8.so /usr/local/bin displayName: 'Install GitVersion' - script: git clone --branch v0.8.17 --single-branch https://github.com/microsoft/hcsshim.git displayName: 'Clone hcsshim repo for tar2ext4 tool' ================================================ FILE: devops/common-win.yml ================================================ steps: - script: 'choco install gitversion.portable --pre' displayName: 'Install GitVersion' ================================================ FILE: devops/getversion.ps1 ================================================ . .\devops\version_functions.ps1 $version = Get-VersionInfo "version" "-beta" Write-Output $version ================================================ FILE: devops/updateversion.ps1 ================================================ param ([string] $XmlFile, [string] $xpath, [string] $name, [string] $buildSeparator = ".", [string] $type = "version") . .\devops\version_functions.ps1 $version = Get-VersionInfo $type $buildSeparator if ($name -eq "") { Update-XML-Text $XmlFile $xpath $version } else { Update-XML-Attribute $XmlFile $xpath $name $version } ================================================ FILE: devops/version_functions.ps1 ================================================ function Get-XmlNamespaceManager([xml]$XmlDocument, [string]$NamespaceURI = "") { # If a Namespace URI was not given, use the Xml document's default namespace. if ([string]::IsNullOrEmpty($NamespaceURI)) { $NamespaceURI = $XmlDocument.DocumentElement.NamespaceURI } # In order for SelectSingleNode() to actually work, we need to use the fully qualified node path along with an Xml Namespace Manager, so set them up. [System.Xml.XmlNamespaceManager]$xmlNsManager = New-Object System.Xml.XmlNamespaceManager($XmlDocument.NameTable) $xmlNsManager.AddNamespace("ns", $NamespaceURI) return ,$xmlNsManager # Need to put the comma before the variable name so that PowerShell doesn't convert it into an Object[]. } function Get-FullyQualifiedXmlNodePath([string]$NodePath, [string]$NodeSeparatorCharacter = '.') { return "/ns:$($NodePath.Replace($('.'), '/ns:'))" } function Get-XmlNode([xml]$XmlDocument, [string]$NodePath, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.') { $xmlNsManager = Get-XmlNamespaceManager -XmlDocument $XmlDocument -NamespaceURI $NamespaceURI [string]$fullyQualifiedNodePath = Get-FullyQualifiedXmlNodePath -NodePath $NodePath -NodeSeparatorCharacter $NodeSeparatorCharacter # Try and get the node, then return it. Returns $null if the node was not found. $node = $XmlDocument.SelectSingleNode($fullyQualifiedNodePath, $xmlNsManager) return $node } function Set-XmlAttributeValue([xml]$XmlDocument, [string]$ElementPath, [string]$AttributeName, [string]$AttributeValue, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.') { # Try and get the node. $node = Get-XmlNode -XmlDocument $XmlDocument -NodePath $ElementPath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter $node.SetAttribute($AttributeName, $AttributeValue) } function Set-XmlElementsTextValue([xml]$XmlDocument, [string]$ElementPath, [string]$TextValue, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.') { # Try and get the node. $node = Get-XmlNode -XmlDocument $XmlDocument -NodePath $ElementPath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter # If the node already exists, update its value. if ($node) { $node.InnerText = $TextValue } # Else the node doesn't exist yet, so create it with the given value. else { # Create the new element with the given value. $elementName = $ElementPath.Substring($ElementPath.LastIndexOf($NodeSeparatorCharacter) + 1) $element = $XmlDocument.CreateElement($elementName, $XmlDocument.DocumentElement.NamespaceURI) $textNode = $XmlDocument.CreateTextNode($TextValue) $element.AppendChild($textNode) > $null # Try and get the parent node. $parentNodePath = $ElementPath.Substring(0, $ElementPath.LastIndexOf($NodeSeparatorCharacter)) $parentNode = Get-XmlNode -XmlDocument $XmlDocument -NodePath $parentNodePath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter if ($parentNode) { $parentNode.AppendChild($element) > $null } else { throw "$parentNodePath does not exist in the xml." } } } function Update-XML-Attribute($File, $xpath, $name, $value) { $File = Resolve-Path $File [xml] $fileContents = Get-Content -Encoding UTF8 -Path $File if ($null -ne $value -and $value -ne "") { Set-XmlAttributeValue -XmlDocument $fileContents -ElementPath $xpath -AttributeName $name -AttributeValue $value } $fileContents.Save($File) } function Update-XML-Text($File, $xpath, $value) { $File = Resolve-Path $File [xml] $fileContents = Get-Content -Encoding UTF8 -Path $File if ($null -ne $value -and $value -ne "") { Set-XmlElementsTextValue -XmlDocument $fileContents -ElementPath $xpath -TextValue $value } $fileContents.Save($File) } function Get-Current-Commit-Hash () { return ([string](git log -1 --pretty=%h)).Trim() } function Get-VersionInfo($type, $separator) { if ($type -eq "hash") { return Get-Current-Commit-Hash } $major = [string](gitversion /showvariable Major) $minor = [string](gitversion /showvariable Minor) $patch = [string](gitversion /showvariable Patch) $build = [string](gitversion /showvariable BuildMetaData) $version = "$major.$minor.$patch" if ($build -ne "") { $version = $version + $separator + $build } return $version } ================================================ FILE: docs/install-sample-gui-apps.sh ================================================ #!/bin/bash # Make sure you are using Ubuntu 20.04!! apt update ## Gedit apt install gedit -y ## GIMP apt install gimp -y ## Nautilus apt install nautilus -y ## X11 apps apt install x11-apps -y ## Google Chrome cd /tmp wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb dpkg -i google-chrome-stable_current_amd64.deb apt install --fix-broken -y dpkg -i google-chrome-stable_current_amd64.deb ## Microsoft teams cd /tmp curl -L -o "./teams.deb" "https://teams.microsoft.com/downloads/desktopurl?env=production&plat=linux&arch=x64&download=true&linuxArchiveType=deb" apt install ./teams.deb -y ## Edge Browser curl https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-dev/microsoft-edge-dev_88.0.673.0-1_amd64.deb -o /tmp/edge.deb apt install /tmp/edge.deb -y ================================================ FILE: msi/updateversion.ps1 ================================================ param ([string] $XmlFile = $null ) function Get-XmlNamespaceManager([xml]$XmlDocument, [string]$NamespaceURI = "") { # If a Namespace URI was not given, use the Xml document's default namespace. if ([string]::IsNullOrEmpty($NamespaceURI)) { $NamespaceURI = $XmlDocument.DocumentElement.NamespaceURI } # In order for SelectSingleNode() to actually work, we need to use the fully qualified node path along with an Xml Namespace Manager, so set them up. [System.Xml.XmlNamespaceManager]$xmlNsManager = New-Object System.Xml.XmlNamespaceManager($XmlDocument.NameTable) $xmlNsManager.AddNamespace("ns", $NamespaceURI) return ,$xmlNsManager # Need to put the comma before the variable name so that PowerShell doesn't convert it into an Object[]. } function Get-FullyQualifiedXmlNodePath([string]$NodePath, [string]$NodeSeparatorCharacter = '.') { return "/ns:$($NodePath.Replace($('.'), '/ns:'))" } function Get-XmlNode([xml]$XmlDocument, [string]$NodePath, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.') { $xmlNsManager = Get-XmlNamespaceManager -XmlDocument $XmlDocument -NamespaceURI $NamespaceURI [string]$fullyQualifiedNodePath = Get-FullyQualifiedXmlNodePath -NodePath $NodePath -NodeSeparatorCharacter $NodeSeparatorCharacter # Try and get the node, then return it. Returns $null if the node was not found. $node = $XmlDocument.SelectSingleNode($fullyQualifiedNodePath, $xmlNsManager) return $node } function Set-XmlAttributeValue([xml]$XmlDocument, [string]$ElementPath, [string]$AttributeName, [string]$AttributeValue, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.') { # Try and get the node. $node = Get-XmlNode -XmlDocument $XmlDocument -NodePath $ElementPath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter $node.SetAttribute($AttributeName, $AttributeValue) } function Update-XML-Attribute($File, $xpath, $name, $value) { $File = Resolve-Path $File [xml] $fileContents = Get-Content -Encoding UTF8 -Path $File if ($null -ne $value -and $value -ne "") { Set-XmlAttributeValue -XmlDocument $fileContents -ElementPath $xpath -AttributeName $name -AttributeValue $value } $fileContents.Save($File) } $major = [string](gitversion /showvariable Major) $minor = [string](gitversion /showvariable Minor) $patch = [string](gitversion /showvariable Patch) $build = [string](gitversion /showvariable BuildMetaData) if ($build -eq "") { $build = "0" } if ($XmlFile -ne $null) { $version = "$major.$minor.$patch.$build"; Update-XML-Attribute $XmlFile "Wix.Product" "Version" $version } ================================================ FILE: package/wslg.rdp ================================================ audiocapturemode:i:2 authentication level:i:0 disableconnectionsharing:i:1 enablecredsspsupport:i:0 hvsocketenabled:i:1 remoteapplicationmode:i:1 remoteapplicationprogram:s:dummy-entry ================================================ FILE: package/wslg_desktop.rdp ================================================ audiocapturemode:i:2 authentication level:i:0 disableconnectionsharing:i:1 enablecredsspsupport:i:0 hvsocketenabled:i:1 ================================================ FILE: rdpapplist/meson.build ================================================ project('rdpapplist', 'c', version : '2.0.0', ) name_rdpapplist = meson.project_name() version_rdpapplist = meson.project_version() config_h = configuration_data() dir_prefix = get_option('prefix') dir_lib = join_paths(dir_prefix, get_option('libdir')) install_lib_dir_rdpapplist = join_paths(dir_lib, name_rdpapplist) config_h.set_quoted('RDPAPPLIST_MODULEDIR', install_lib_dir_rdpapplist) dir_inc = join_paths(dir_prefix, get_option('includedir')) install_inc_dir_rdpapplist = join_paths(dir_inc, name_rdpapplist) config_h.set_quoted('RDPAPPLIST_INCLUDEDIR', install_inc_dir_rdpapplist) dep_frdp = dependency('freerdp3', version: '>= 3.0.0', required: false) if not dep_frdp.found() dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) if not dep_frdp.found() error('rdpapplist requires freerdp2 or 3 which was not found.') endif endif dep_winpr = dependency('winpr3', version: '>= 3.0.0', required: false) if not dep_winpr.found() dep_winpr = dependency('winpr2', version: '>= 2.0.0', required: false) if not dep_frdp.found() error('rdpapplist requires winpr2 or 3 which was not found.') endif endif configure_file(output: 'rdpapplist_config.h', configuration: config_h, install_dir: install_inc_dir_rdpapplist) subdir('server') pkgconfig = import('pkgconfig') pkgconfig.generate( filebase: name_rdpapplist, version: version_rdpapplist, name: 'Application list Plugin for FreeRDP', description: 'Header files for Application list Plugin for FreeRDP', ) install_headers( 'rdpapplist_protocol.h', 'rdpapplist_server.h', subdir: name_rdpapplist, ) ================================================ FILE: rdpapplist/rdpapplist_common.c ================================================ /** * FreeRDP: A Remote Desktop Protocol Implementation * RDPXXXX Remote Application List Virtual Channel Extension * * Copyright 2020 Microsoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #define TAG CHANNELS_TAG("rdpapplist.common") #include "rdpapplist_common.h" /** * Function description * * @return 0 on success, otherwise a Win32 error code */ UINT rdpapplist_read_header(wStream* s, RDPAPPLIST_HEADER* header) { if (Stream_GetRemainingLength(s) < 8) { WLog_ERR(TAG, "header parsing failed: not enough data!"); return ERROR_INVALID_DATA; } Stream_Read_UINT32(s, header->cmdId); Stream_Read_UINT32(s, header->length); return CHANNEL_RC_OK; } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ UINT rdpapplist_write_header(wStream* s, const RDPAPPLIST_HEADER* header) { Stream_Write_UINT32(s, header->cmdId); Stream_Write_UINT32(s, header->length); return CHANNEL_RC_OK; } ================================================ FILE: rdpapplist/rdpapplist_common.h ================================================ /** * FreeRDP: A Remote Desktop Protocol Implementation * RDPXXXX Remote Application List Virtual Channel Extension * * Copyright 2020 Microsoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FREERDP_CHANNEL_RDPAPPLIST_COMMON_H #define FREERDP_CHANNEL_RDPAPPLIST_COMMON_H #include #include #include #include FREERDP_LOCAL UINT rdpapplist_read_header(wStream* s, RDPAPPLIST_HEADER* header); FREERDP_LOCAL UINT rdpapplist_write_header(wStream* s, const RDPAPPLIST_HEADER* header); #endif /* FREERDP_CHANNEL_RDPAPPLIST_COMMON_H */ ================================================ FILE: rdpapplist/rdpapplist_protocol.h ================================================ /** * FreeRDP: A Remote Desktop Protocol Implementation * RDPXXXX Remote Application List Virtual Channel Extension * * Copyright 2020 Microsoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FREERDP_CHANNEL_RDPAPPLIST_H #define FREERDP_CHANNEL_RDPAPPLIST_H #include #include #include #define RDPAPPLIST_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::RemoteApplicationList" /* Version 4 * - add RDPAPPLIST_SERVER_CAPS_PDU.appListProviderUniqueId field. * - add RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID_PUD message. */ #define RDPAPPLIST_CHANNEL_VERSION 4 #define RDPAPPLIST_CMDID_CAPS 0x00000001 #define RDPAPPLIST_CMDID_UPDATE_APPLIST 0x00000002 #define RDPAPPLIST_CMDID_DELETE_APPLIST 0x00000003 #define RDPAPPLIST_CMDID_DELETE_APPLIST_PROVIDER 0x00000004 #define RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID 0x00000005 #define RDPAPPLIST_ICON_FORMAT_PNG 0x0001 #define RDPAPPLIST_ICON_FORMAT_BMP 0x0002 #define RDPAPPLIST_ICON_FORMAT_SVG 0x0003 #define RDPAPPLIST_FIELD_ID 0x00000001 #define RDPAPPLIST_FIELD_GROUP 0x00000002 #define RDPAPPLIST_FIELD_EXECPATH 0x00000004 #define RDPAPPLIST_FIELD_DESC 0x00000008 #define RDPAPPLIST_FIELD_ICON 0x00000010 #define RDPAPPLIST_FIELD_PROVIDER 0x00000020 #define RDPAPPLIST_FIELD_WORKINGDIR 0x00000040 #define RDPAPPLIST_FIELD_WINDOW_ID 0x00000080 /* RDPAPPLIST_UPDATE_APPLIST_PDU */ #define RDPAPPLIST_HINT_NEWID 0x00010000 /* new appId vs update existing appId. */ #define RDPAPPLIST_HINT_SYNC 0x00100000 /* In sync mode (use with _NEWID). */ #define RDPAPPLIST_HINT_SYNC_START 0x00200000 /* Sync appId start (use with _SYNC). */ #define RDPAPPLIST_HINT_SYNC_END 0x00400000 /* Sync appId end (use with _SYNC). */ /* Client should remove any app entry those are not reported between _START and _END during sync mode */ #define RDPAPPLIST_HEADER_SIZE 8 #define RDPAPPLIST_LANG_SIZE 32 #define RDPAPPLIST_MAX_STRING_SIZE 512 struct _RDPAPPLIST_HEADER { UINT32 cmdId; UINT32 length; }; typedef struct _RDPAPPLIST_HEADER RDPAPPLIST_HEADER; struct _RDPAPPLIST_SERVER_CAPS_PDU { UINT16 version; RAIL_UNICODE_STRING appListProviderName; /* name of app list provider. */ RAIL_UNICODE_STRING appListProviderUniqueId; /* added from version 4 */ }; typedef struct _RDPAPPLIST_SERVER_CAPS_PDU RDPAPPLIST_SERVER_CAPS_PDU; struct _RDPAPPLIST_CLIENT_CAPS_PDU { UINT16 version; /* ISO 639 (Language name) and ISO 3166 (Country name) connected with '_', such as en_US, ja_JP */ char clientLanguageId[RDPAPPLIST_LANG_SIZE]; }; typedef struct _RDPAPPLIST_CLIENT_CAPS_PDU RDPAPPLIST_CLIENT_CAPS_PDU; struct _RDPAPPLIST_ICON_DATA { UINT32 flags; UINT32 iconWidth; UINT32 iconHeight; UINT32 iconStride; UINT32 iconBpp; UINT32 iconFormat; /* RDPAPPLIST_ICON_FORMAT_* */ UINT32 iconBitsLength; /* size of buffer pointed by iconBits. */ VOID *iconBits; /* icon image data */ /* For BMP, image data only */ /* For PNG, entire PNG file including headers */ /* For SVG, entire SVG file including headers */ /* For 32bpp image, alpha-channel works as mask */ }; typedef struct _RDPAPPLIST_ICON_DATA RDPAPPLIST_ICON_DATA; /* Create or update application program link in client */ struct _RDPAPPLIST_UPDATE_APPLIST_PDU { UINT32 flags; RAIL_UNICODE_STRING appId; /* Identifier of application to be added to client's Start Menu. This is used as the file name of link (.lnk) at Start Menu. */ RAIL_UNICODE_STRING appGroup; /* name of app group. */ RAIL_UNICODE_STRING appExecPath; /* Path to server side executable. */ RAIL_UNICODE_STRING appWorkingDir; /* Working directory to run the executable in. */ RAIL_UNICODE_STRING appDesc; /* UI friendly description of application. */ RDPAPPLIST_ICON_DATA *appIcon; }; typedef struct _RDPAPPLIST_UPDATE_APPLIST_PDU RDPAPPLIST_UPDATE_APPLIST_PDU; /* Delete specififed application program link from client */ struct _RDPAPPLIST_DELETE_APPLIST_PDU { UINT32 flags; RAIL_UNICODE_STRING appId; /* Identifier of application to be removed from client's Start Menu. */ RAIL_UNICODE_STRING appGroup; /* name of app group. */ }; typedef struct _RDPAPPLIST_DELETE_APPLIST_PDU RDPAPPLIST_DELETE_APPLIST_PDU; /* Delete all application program link under specififed provider from client */ struct _RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU { UINT32 flags; RAIL_UNICODE_STRING appListProviderName; /* name of app list provider to be removed from client's Start Menu. */ }; typedef struct _RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU; struct _RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU { UINT32 flags; UINT32 appWindowId; /* window id of application */ RAIL_UNICODE_STRING appId; /* Identifier of application to add taskbar property. */ RAIL_UNICODE_STRING appGroup; /* Identifier of group where application belonging to. */ RAIL_UNICODE_STRING appExecPath; /* Path to server side executable. */ RAIL_UNICODE_STRING appDesc; /* UI friendly description of application. */ }; typedef struct _RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU; #endif /* FREERDP_CHANNEL_RDPAPPLIST_H */ ================================================ FILE: rdpapplist/rdpapplist_server.h ================================================ /** * FreeRDP: A Remote Desktop Protocol Implementation * RDPXXXX Remote Application List Virtual Channel Extension * * Copyright 2020 Microsoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FREERDP_CHANNEL_RDPAPPLIST_SERVER_RDPAPPLIST_H #define FREERDP_CHANNEL_RDPAPPLIST_SERVER_RDPAPPLIST_H #include "rdpapplist_protocol.h" #include #include #include typedef struct _rdpapplist_server_private RdpAppListServerPrivate; typedef struct _rdpapplist_server_context RdpAppListServerContext; typedef UINT (*psRdpAppListOpen)(RdpAppListServerContext* context); typedef UINT (*psRdpAppListClose)(RdpAppListServerContext* context); typedef UINT (*psRdpAppListCaps)(RdpAppListServerContext* context, const RDPAPPLIST_SERVER_CAPS_PDU *caps); typedef UINT (*psRdpAppListUpdate)(RdpAppListServerContext* context, const RDPAPPLIST_UPDATE_APPLIST_PDU *updateAppList); typedef UINT (*psRdpAppListDelete)(RdpAppListServerContext* context, const RDPAPPLIST_DELETE_APPLIST_PDU *deleteAppList); typedef UINT (*psRdpAppListDeleteProvider)(RdpAppListServerContext* context, const RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU *deleteAppListProvider); typedef UINT (*psRdpAppListAssociateWindowId)(RdpAppListServerContext* context, const RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU *associateWindowId); typedef UINT (*psRdpAppListClientCaps)(RdpAppListServerContext* context, const RDPAPPLIST_CLIENT_CAPS_PDU *clientCaps); struct _rdpapplist_server_context { void* custom; HANDLE vcm; psRdpAppListOpen Open; psRdpAppListClose Close; psRdpAppListCaps ApplicationListCaps; psRdpAppListUpdate UpdateApplicationList; psRdpAppListDelete DeleteApplicationList; psRdpAppListDeleteProvider DeleteApplicationListProvider; psRdpAppListAssociateWindowId AssociateWindowId; psRdpAppListClientCaps ApplicationListClientCaps; RdpAppListServerPrivate* priv; rdpContext* rdpcontext; }; #ifdef __cplusplus extern "C" { #endif FREERDP_API RdpAppListServerContext* rdpapplist_server_context_new(HANDLE vcm); FREERDP_API void rdpapplist_server_context_free(RdpAppListServerContext* context); #ifdef __cplusplus } #endif #endif /* FREERDP_CHANNEL_RDPAPPLIST_SERVER_RDPAPPLIST_H */ ================================================ FILE: rdpapplist/server/meson.build ================================================ dep_frdp_server = dependency('freerdp-server3', version: '>= 3.0.0', required: false) if not dep_frdp_server.found() dep_frdp_server = dependency('freerdp-server2', version: '>= 2.0.0', required: false) if not dep_frdp_server.found() error('librapapplist-server requires freerdp-server2 or 3 which was not found.') endif endif deps_librdpapplist_server = [ dep_frdp, dep_frdp_server, dep_winpr, ] srcs_librdpapplist_server = [ '../rdpapplist_common.c', 'rdpapplist_main.c', ] incs_common_server = [ '../', ] plugin_rdpapplist_server = shared_library( 'librdpapplist-server', srcs_librdpapplist_server, include_directories: incs_common_server, dependencies: deps_librdpapplist_server, name_prefix: '', install: true, install_dir: install_lib_dir_rdpapplist, ) ================================================ FILE: rdpapplist/server/rdpapplist_main.c ================================================ /** * FreeRDP: A Remote Desktop Protocol Implementation * RDPXXXX Remote Application List Virtual Channel Extension * * Copyright 2020 Microsoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include "rdpapplist_common.h" #include "rdpapplist_server.h" #include "rdpapplist_main.h" #define TAG CHANNELS_TAG("rdpapplist.server") /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT rdpapplist_recv_caps_pdu(wStream* s, RdpAppListServerContext* context) { UINT32 error = CHANNEL_RC_OK; RDPAPPLIST_CLIENT_CAPS_PDU pdu; if (Stream_GetRemainingLength(s) < 2) { WLog_ERR(TAG, "not enough data!"); return ERROR_INVALID_DATA; } Stream_Read_UINT16(s, pdu.version); /* version (2 bytes) */ if (Stream_GetRemainingLength(s) < RDPAPPLIST_LANG_SIZE) { WLog_ERR(TAG, "not enough data!"); return ERROR_INVALID_DATA; } Stream_Read(s, &pdu.clientLanguageId[0], RDPAPPLIST_LANG_SIZE); if (context) IFCALLRET(context->ApplicationListClientCaps, error, context, &pdu); return error; } static UINT rdpapplist_server_receive_pdu(RdpAppListServerContext* context, wStream* s) { UINT error = CHANNEL_RC_OK; size_t beg, end; RDPAPPLIST_HEADER header; beg = Stream_GetPosition(s); if ((error = rdpapplist_read_header(s, &header))) { WLog_ERR(TAG, "rdpapplist_read_header failed with error %" PRIu32 "!", error); return error; } switch (header.cmdId) { case RDPAPPLIST_CMDID_CAPS: if ((error = rdpapplist_recv_caps_pdu(s, context))) WLog_ERR(TAG, "rdpapplist_recv_caps_pdu " "failed with error %" PRIu32 "!", error); break; default: error = CHANNEL_RC_BAD_PROC; WLog_WARN(TAG, "Received unknown PDU type: %" PRIu32 "", header.cmdId); break; } end = Stream_GetPosition(s); if (end != (beg + header.length)) { WLog_ERR(TAG, "Unexpected RDPAPPLIST pdu end: Actual: %d, Expected: %" PRIu32 "", end, (beg + header.length)); Stream_SetPosition(s, (beg + header.length)); } return error; } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT rdpapplist_server_handle_messages(RdpAppListServerContext* context) { DWORD BytesReturned; void* buffer; UINT ret = CHANNEL_RC_OK; RdpAppListServerPrivate* priv = context->priv; wStream* s = priv->input_stream; /* Check whether the dynamic channel is ready */ if (!priv->isReady) { if (WTSVirtualChannelQuery(priv->rdpapplist_channel, WTSVirtualChannelReady, &buffer, &BytesReturned) == FALSE) { if (GetLastError() == ERROR_NO_DATA) return ERROR_NO_DATA; WLog_ERR(TAG, "WTSVirtualChannelQuery failed"); return ERROR_INTERNAL_ERROR; } priv->isReady = *((BOOL*)buffer); WTSFreeMemory(buffer); } /* Consume channel event only after the dynamic channel is ready */ if (priv->isReady) { Stream_SetPosition(s, 0); if (!WTSVirtualChannelRead(priv->rdpapplist_channel, 0, NULL, 0, &BytesReturned)) { if (GetLastError() == ERROR_NO_DATA) return ERROR_NO_DATA; WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); return ERROR_INTERNAL_ERROR; } if (BytesReturned < 1) return CHANNEL_RC_OK; if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) { WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); return CHANNEL_RC_NO_MEMORY; } if (WTSVirtualChannelRead(priv->rdpapplist_channel, 0, (PCHAR)Stream_Buffer(s), Stream_Capacity(s), &BytesReturned) == FALSE) { WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); return ERROR_INTERNAL_ERROR; } Stream_SetLength(s, BytesReturned); Stream_SetPosition(s, 0); while (Stream_GetPosition(s) < Stream_Length(s)) { if ((ret = rdpapplist_server_receive_pdu(context, s))) { WLog_ERR(TAG, "rdpapplist_server_receive_pdu " "failed with error %" PRIu32 "!", ret); return ret; } } } return ret; } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static DWORD WINAPI rdpapplist_server_thread_func(LPVOID arg) { RdpAppListServerContext* context = (RdpAppListServerContext*)arg; RdpAppListServerPrivate* priv = context->priv; DWORD status; DWORD nCount; HANDLE events[8]; UINT error = CHANNEL_RC_OK; nCount = 0; events[nCount++] = priv->stopEvent; events[nCount++] = priv->channelEvent; while (TRUE) { status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); if (status == WAIT_FAILED) { error = GetLastError(); WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); break; } /* Stop Event */ if (status == WAIT_OBJECT_0) break; if ((error = rdpapplist_server_handle_messages(context))) { WLog_ERR(TAG, "rdpapplist_server_handle_messages failed with error %" PRIu32 "", error); break; } } ExitThread(error); return error; } /** * Function description * Create new stream for single rdpapplist packet. The new stream length * would be required data length + header. The header will be written * to the stream before return. * * @param cmdId * @param length - data length without header * * @return new stream */ static wStream* rdpapplist_server_single_packet_new(UINT32 cmdId, UINT32 length) { UINT error; RDPAPPLIST_HEADER header; wStream* s = Stream_New(NULL, RDPAPPLIST_HEADER_SIZE + length); if (!s) { WLog_ERR(TAG, "Stream_New failed!"); goto error; } header.cmdId = cmdId; header.length = RDPAPPLIST_HEADER_SIZE + length; if ((error = rdpapplist_write_header(s, &header))) { WLog_ERR(TAG, "Failed to write header with error %" PRIu32 "!", error); goto error; } return s; error: Stream_Free(s, TRUE); return NULL; } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT rdpapplist_server_packet_send(RdpAppListServerContext* context, wStream* s) { UINT ret; ULONG written; if (!WTSVirtualChannelWrite(context->priv->rdpapplist_channel, (PCHAR)Stream_Buffer(s), Stream_GetPosition(s), &written)) { WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); ret = ERROR_INTERNAL_ERROR; goto out; } if (written < Stream_GetPosition(s)) { WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, Stream_GetPosition(s)); } ret = CHANNEL_RC_OK; out: Stream_Free(s, TRUE); return ret; } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT rdpapplist_send_caps(RdpAppListServerContext *context, const RDPAPPLIST_SERVER_CAPS_PDU *caps) { if (caps->appListProviderName.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, "rdpapplist_send_caps: appProviderName is too large."); return ERROR_INVALID_DATA; } int len = 2 + // version. 2 + caps->appListProviderName.length + 2 + caps->appListProviderUniqueId.length; wStream* s = rdpapplist_server_single_packet_new(RDPAPPLIST_CMDID_CAPS, len); if (!s) { WLog_ERR(TAG, "rdpapplist_server_single_packet_new failed!"); return CHANNEL_RC_NO_MEMORY; } Stream_Write_UINT16(s, caps->version); Stream_Write_UINT16(s, caps->appListProviderName.length); Stream_Write(s, caps->appListProviderName.string, caps->appListProviderName.length); Stream_Write_UINT16(s, caps->appListProviderUniqueId.length); Stream_Write(s, caps->appListProviderUniqueId.string, caps->appListProviderUniqueId.length); return rdpapplist_server_packet_send(context, s); } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT rdpapplist_send_update_applist(RdpAppListServerContext* context, const RDPAPPLIST_UPDATE_APPLIST_PDU *updateAppList) { UINT32 len = 4; // flags. if (updateAppList->flags & RDPAPPLIST_FIELD_ID) { if (updateAppList->appId.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, "rdpapplist_send_update_applist: appId is too large."); return ERROR_INVALID_DATA; } len += (2 + updateAppList->appId.length); } if (updateAppList->flags & RDPAPPLIST_FIELD_GROUP) { if (updateAppList->appGroup.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, "rdpapplist_send_update_applist: appGroup is too large."); return ERROR_INVALID_DATA; } len += (2 + updateAppList->appGroup.length); } if (updateAppList->flags & RDPAPPLIST_FIELD_EXECPATH) { if (updateAppList->appExecPath.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, "rdpapplist_send_update_applist: appExecPath is too large."); return ERROR_INVALID_DATA; } len += (2 + updateAppList->appExecPath.length); } if (updateAppList->flags & RDPAPPLIST_FIELD_WORKINGDIR) { if (updateAppList->appWorkingDir.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, "rdpapplist_send_update_applist: appWorkingDir is too large."); return ERROR_INVALID_DATA; } len += (2 + updateAppList->appWorkingDir.length); } if (updateAppList->flags & RDPAPPLIST_FIELD_DESC) { if (updateAppList->appDesc.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, "rdpapplist_send_update_applist: appDesc is too large."); return ERROR_INVALID_DATA; } len += (2 + updateAppList->appDesc.length); } if (updateAppList->flags & RDPAPPLIST_FIELD_ICON) { if (!updateAppList->appIcon) { WLog_ERR(TAG, "rdpapplist_send_update_applist icon flag is set, but appIcon is NULL."); return ERROR_INVALID_DATA; } len += (7 * 4 + updateAppList->appIcon->iconBitsLength); } wStream* s = rdpapplist_server_single_packet_new(RDPAPPLIST_CMDID_UPDATE_APPLIST, len); if (!s) { WLog_ERR(TAG, "rdpapplist_server_single_packet_new failed!"); return CHANNEL_RC_NO_MEMORY; } Stream_Write_UINT32(s, updateAppList->flags); if (updateAppList->flags & RDPAPPLIST_FIELD_ID) { Stream_Write_UINT16(s, updateAppList->appId.length); Stream_Write(s, updateAppList->appId.string, updateAppList->appId.length); } if (updateAppList->flags & RDPAPPLIST_FIELD_GROUP) { Stream_Write_UINT16(s, updateAppList->appGroup.length); Stream_Write(s, updateAppList->appGroup.string, updateAppList->appGroup.length); } if (updateAppList->flags & RDPAPPLIST_FIELD_EXECPATH) { Stream_Write_UINT16(s, updateAppList->appExecPath.length); Stream_Write(s, updateAppList->appExecPath.string, updateAppList->appExecPath.length); } if (updateAppList->flags & RDPAPPLIST_FIELD_WORKINGDIR) { Stream_Write_UINT16(s, updateAppList->appWorkingDir.length); Stream_Write(s, updateAppList->appWorkingDir.string, updateAppList->appWorkingDir.length); } if (updateAppList->flags & RDPAPPLIST_FIELD_DESC) { Stream_Write_UINT16(s, updateAppList->appDesc.length); Stream_Write(s, updateAppList->appDesc.string, updateAppList->appDesc.length); } if (updateAppList->flags & RDPAPPLIST_FIELD_ICON) { Stream_Write_UINT32(s, updateAppList->appIcon->flags); Stream_Write_UINT32(s, updateAppList->appIcon->iconWidth); Stream_Write_UINT32(s, updateAppList->appIcon->iconHeight); Stream_Write_UINT32(s, updateAppList->appIcon->iconStride); Stream_Write_UINT32(s, updateAppList->appIcon->iconBpp); Stream_Write_UINT32(s, updateAppList->appIcon->iconFormat); Stream_Write_UINT32(s, updateAppList->appIcon->iconBitsLength); Stream_Write(s, updateAppList->appIcon->iconBits, updateAppList->appIcon->iconBitsLength); } return rdpapplist_server_packet_send(context, s); } static UINT rdpapplist_send_delete_applist(RdpAppListServerContext* context, const RDPAPPLIST_DELETE_APPLIST_PDU *deleteAppList) { UINT32 len = 4; // flags if (deleteAppList->flags & RDPAPPLIST_FIELD_ID) { if (deleteAppList->appId.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, " rdpapplist_send_delete_applist: appId is too large."); return ERROR_INVALID_DATA; } len += (2 + deleteAppList->appId.length); } if (deleteAppList->flags & RDPAPPLIST_FIELD_GROUP) { if (deleteAppList->appGroup.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, " rdpapplist_send_delete_applist: appGroup is too large."); return ERROR_INVALID_DATA; } len += (2 + deleteAppList->appGroup.length); } wStream* s = rdpapplist_server_single_packet_new(RDPAPPLIST_CMDID_DELETE_APPLIST, len); if (!s) { WLog_ERR(TAG, "rdpapplist_server_single_packet_new failed!"); return CHANNEL_RC_NO_MEMORY; } Stream_Write_UINT32(s, deleteAppList->flags); if (deleteAppList->flags & RDPAPPLIST_FIELD_ID) { Stream_Write_UINT16(s, deleteAppList->appId.length); Stream_Write(s, deleteAppList->appId.string, deleteAppList->appId.length); } if (deleteAppList->flags & RDPAPPLIST_FIELD_GROUP) { Stream_Write_UINT16(s, deleteAppList->appGroup.length); Stream_Write(s, deleteAppList->appGroup.string, deleteAppList->appGroup.length); } return rdpapplist_server_packet_send(context, s); } static UINT rdpapplist_send_delete_applist_provider(RdpAppListServerContext* context, const RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU *deleteAppListProvider) { UINT32 len = 4; // flags. if (deleteAppListProvider->flags & RDPAPPLIST_FIELD_PROVIDER) { if (deleteAppListProvider->appListProviderName.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, " rdpapplist_send_delete_applist: appProviderName is too large."); return ERROR_INVALID_DATA; } len += (2 + deleteAppListProvider->appListProviderName.length); } wStream* s = rdpapplist_server_single_packet_new(RDPAPPLIST_CMDID_DELETE_APPLIST_PROVIDER, len); if (!s) { WLog_ERR(TAG, "rdpapplist_server_single_packet_new failed!"); return CHANNEL_RC_NO_MEMORY; } Stream_Write_UINT32(s, deleteAppListProvider->flags); if (deleteAppListProvider->flags & RDPAPPLIST_FIELD_PROVIDER) { Stream_Write_UINT16(s, deleteAppListProvider->appListProviderName.length); Stream_Write(s, deleteAppListProvider->appListProviderName.string, deleteAppListProvider->appListProviderName.length); } return rdpapplist_server_packet_send(context, s); } static UINT rdpapplist_send_associate_window_id(RdpAppListServerContext* context, const RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU *associateWindowId) { UINT32 len = 8; // flags + windowId. if (associateWindowId->flags & RDPAPPLIST_FIELD_ID) { if (associateWindowId->appId.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, " rdpapplist_send_associate_window_id: appId is too large."); return ERROR_INVALID_DATA; } len += (2 + associateWindowId->appId.length); } if (associateWindowId->flags & RDPAPPLIST_FIELD_GROUP) { if (associateWindowId->appGroup.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, " rdpapplist_send_associate_window_id: appGroup is too large."); return ERROR_INVALID_DATA; } len += (2 + associateWindowId->appGroup.length); } if (associateWindowId->flags & RDPAPPLIST_FIELD_EXECPATH) { if (associateWindowId->appExecPath.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, "rdpapplist_send_associate_window_id: appExecPath is too large."); return ERROR_INVALID_DATA; } len += (2 + associateWindowId->appExecPath.length); } if (associateWindowId->flags & RDPAPPLIST_FIELD_DESC) { if (associateWindowId->appDesc.length > RDPAPPLIST_MAX_STRING_SIZE) { WLog_ERR(TAG, "rdpapplist_send_associate_window_id: appDesc is too large."); return ERROR_INVALID_DATA; } len += (2 + associateWindowId->appDesc.length); } wStream* s = rdpapplist_server_single_packet_new(RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID, len); if (!s) { WLog_ERR(TAG, "rdpapplist_server_single_packet_new failed!"); return CHANNEL_RC_NO_MEMORY; } Stream_Write_UINT32(s, associateWindowId->flags); Stream_Write_UINT32(s, associateWindowId->appWindowId); if (associateWindowId->flags & RDPAPPLIST_FIELD_ID) { Stream_Write_UINT16(s, associateWindowId->appId.length); Stream_Write(s, associateWindowId->appId.string, associateWindowId->appId.length); } if (associateWindowId->flags & RDPAPPLIST_FIELD_GROUP) { Stream_Write_UINT16(s, associateWindowId->appGroup.length); Stream_Write(s, associateWindowId->appGroup.string, associateWindowId->appGroup.length); } if (associateWindowId->flags & RDPAPPLIST_FIELD_EXECPATH) { Stream_Write_UINT16(s, associateWindowId->appExecPath.length); Stream_Write(s, associateWindowId->appExecPath.string, associateWindowId->appExecPath.length); } if (associateWindowId->flags & RDPAPPLIST_FIELD_DESC) { Stream_Write_UINT16(s, associateWindowId->appDesc.length); Stream_Write(s, associateWindowId->appDesc.string, associateWindowId->appDesc.length); } return rdpapplist_server_packet_send(context, s); } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT rdpapplist_server_open(RdpAppListServerContext* context) { UINT rc = ERROR_INTERNAL_ERROR; RdpAppListServerPrivate* priv = context->priv; DWORD BytesReturned = 0; PULONG pSessionId = NULL; void* buffer; buffer = NULL; priv->SessionId = WTS_CURRENT_SESSION; if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId, (LPSTR*)&pSessionId, &BytesReturned) == FALSE) { WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); rc = ERROR_INTERNAL_ERROR; goto out_close; } priv->SessionId = (DWORD)*pSessionId; WTSFreeMemory(pSessionId); priv->rdpapplist_channel = (HANDLE)WTSVirtualChannelOpenEx(priv->SessionId, RDPAPPLIST_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); if (!priv->rdpapplist_channel) { WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!"); rc = GetLastError(); goto out_close; } /* Query for channel event handle */ if (!WTSVirtualChannelQuery(priv->rdpapplist_channel, WTSVirtualEventHandle, &buffer, &BytesReturned) || (BytesReturned != sizeof(HANDLE))) { WLog_ERR(TAG, "WTSVirtualChannelQuery failed " "or invalid returned size(%" PRIu32 ")", BytesReturned); if (buffer) WTSFreeMemory(buffer); rc = ERROR_INTERNAL_ERROR; goto out_close; } CopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE)); WTSFreeMemory(buffer); if (priv->thread == NULL) { if (!(priv->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) { WLog_ERR(TAG, "CreateEvent failed!"); rc = ERROR_INTERNAL_ERROR; } if (!(priv->thread = CreateThread(NULL, 0, rdpapplist_server_thread_func, (void*)context, 0, NULL))) { WLog_ERR(TAG, "CreateEvent failed!"); CloseHandle(priv->stopEvent); priv->stopEvent = NULL; rc = ERROR_INTERNAL_ERROR; } } return CHANNEL_RC_OK; out_close: WTSVirtualChannelClose(priv->rdpapplist_channel); priv->rdpapplist_channel = NULL; priv->channelEvent = NULL; return rc; } /** * Function description * * @return 0 on success, otherwise a Win32 error code */ static UINT rdpapplist_server_close(RdpAppListServerContext* context) { UINT error = CHANNEL_RC_OK; RdpAppListServerPrivate* priv = context->priv; if (priv->thread) { SetEvent(priv->stopEvent); if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED) { error = GetLastError(); WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); return error; } CloseHandle(priv->thread); CloseHandle(priv->stopEvent); priv->thread = NULL; priv->stopEvent = NULL; } if (priv->rdpapplist_channel) { WTSVirtualChannelClose(priv->rdpapplist_channel); priv->rdpapplist_channel = NULL; } return error; } RdpAppListServerContext* rdpapplist_server_context_new(HANDLE vcm) { RdpAppListServerContext* context; RdpAppListServerPrivate* priv; context = (RdpAppListServerContext*)calloc(1, sizeof(RdpAppListServerContext)); if (!context) { WLog_ERR(TAG, "rdpapplist_server_context_new(): calloc RdpAppListServerContext failed!"); return NULL; } priv = context->priv = (RdpAppListServerPrivate*)calloc(1, sizeof(RdpAppListServerPrivate)); if (!context->priv) { WLog_ERR(TAG, "rdpapplist_server_context_new(): calloc RdpAppListServerPrivate failed!"); goto out_free; } priv->input_stream = Stream_New(NULL, 4); if (!priv->input_stream) { WLog_ERR(TAG, "Stream_New failed!"); goto out_free_priv; } context->vcm = vcm; context->Open = rdpapplist_server_open; context->Close = rdpapplist_server_close; context->ApplicationListCaps = rdpapplist_send_caps; context->UpdateApplicationList = rdpapplist_send_update_applist; context->DeleteApplicationList = rdpapplist_send_delete_applist; context->DeleteApplicationListProvider = rdpapplist_send_delete_applist_provider; context->AssociateWindowId = rdpapplist_send_associate_window_id; priv->isReady = FALSE; return context; out_free_priv: free(context->priv); out_free: free(context); return NULL; } void rdpapplist_server_context_free(RdpAppListServerContext* context) { if (!context) return; rdpapplist_server_close(context); if (context->priv) { Stream_Free(context->priv->input_stream, TRUE); free(context->priv); } free(context); } ================================================ FILE: rdpapplist/server/rdpapplist_main.h ================================================ /** * FreeRDP: A Remote Desktop Protocol Implementation * RDPXXXX Remote Application List Virtual Channel Extension * * Copyright 2020 Microsoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FREERDP_CHANNEL_RDPAPPLIST_SERVER_MAIN_H #define FREERDP_CHANNEL_RDPAPPLIST_SERVER_MAIN_H struct _rdpapplist_server_private { BOOL isReady; wStream* input_stream; HANDLE channelEvent; HANDLE thread; HANDLE stopEvent; DWORD SessionId; void* rdpapplist_channel; }; #endif /* FREERDP_CHANNEL_RDPAPPLIST_SERVER_MAIN_H */ ================================================ FILE: samples/container/Containers.md ================================================ # Containerizing GUI applications with WSLg For containerized applications to work properly under WSLg developers need to be aware of a few peculiarity of our environment in order to allow applications to properly connect to our X11, Wayland or PulseAudio server or to use the vGPU. ## Containerized GUI applications connecting to X11, Wayland or Pulse server In order for a containerized application to access the servers provided by WSLg, the following mount location must be made visible inside the container. | Server | Mount | |---|---| | X11 | ```/tmp/.X11-unix``` | | Wayland | ```/mnt/wslg``` | | PulseAudio | ```/mnt/wslg``` | And the following environment variable must be share with the container. | Server | Environment variables | |---|---| | X11 | ```DISPLAY``` | | Wayland | ```WAYLAND_DISPLAY``` && ```XDG_RUNTIME_DIR``` | | PulseAudio | ```PULSE_SERVER``` | For example, to run ```xclock``` as a containerized application, the following docker file can be use. ``` FROM ubuntu:20.04 as runtime ARG DEBIAN_FRONTEND=noninteractive RUN apt update && \ apt install -y x11-apps CMD /usr/bin/xclock ``` The container can be build then launch as follow. ``` sudo docker build -t xclock -f Dockerfile.xclock . sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ -e DISPLAY=$DISPLAY -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \ -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR -e PULSE_SERVER=$PULSE_SERVER xclock ``` Please note that in this example we make all servers visible to ```xclock``` even though it only uses the X11 server and will not make use of the Wayland or PulseAudio servers. This is for illustrative purposes only. There is no real harm in exposing a server that is unused by an application. However it is good practice to only exposed containerized application to the resource they need. In this case we could have launch the containerized version of ```xclock``` with the following minimal command line. ``` sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY xclock ``` ## Containerized applications access to the vGPU For a containerized application to use the vGPU provided by WSL2, the following must be done. The following device must be shared with the container. ```/dev/dxg``` The following mount location must be mapped in the container. ```/usr/lib/wsl``` The following path must be added to the ```LD_LIBRARY_PATH``` environment variable inside the container. ```/usr/lib/wsl/lib``` For example the following dockerfile containerized glxinfo. ``` FROM ubuntu:20.04 as runtime ARG DEBIAN_FRONTEND=noninteractive RUN apt update && \ apt install -y mesa-utils ENV LD_LIBRARY_PATH=/usr/lib/wsl/lib CMD /usr/bin/glxinfo -B ``` This container can be build and launch as follow. ``` sudo docker build -t glxinfo -f Dockerfile.glxinfo . sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \ -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ -e PULSE_SERVER=$PULSE_SERVER --gpus all glxinfo ``` ## Containerized applications access to vGPU accelerated video For a containerized application to use vGPU video acceleration, the following must be done. The following devices must be shared with the container. ```/dev/dxg``` ```/dev/dri/card0``` ```/dev/dri/renderD128``` The following mount location must be mapped in the container. ```/usr/lib/wsl``` The following path must be added to the ```LD_LIBRARY_PATH``` environment variable inside the container. ```/usr/lib/wsl/lib``` The following VA-API driver name must be set to the ```LIBVA_DRIVER_NAME``` environment variable inside the container. ```d3d12``` The following libraries must be installed in the container. ```   vainfo   mesa-va-drivers ``` For example the following dockerfile containerized `videoaccel`. ``` FROM ubuntu:22.10 as runtime ARG DEBIAN_FRONTEND=noninteractive # Uncomment the lines below to use a 3rd party repository # to get the latest (unstable from mesa/main) mesa library version # RUN apt-get update && apt install -y software-properties-common # RUN add-apt-repository ppa:oibaf/graphics-drivers -y RUN apt update && apt install -y \ vainfo \ mesa-va-drivers ENV LIBVA_DRIVER_NAME=d3d12 ENV LD_LIBRARY_PATH=/usr/lib/wsl/lib CMD vainfo --display drm --device /dev/dri/card0 ``` This container can be build and launch as follow. ``` sudo docker build -t videoaccel -f Dockerfile.videoaccel . sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \ --device /dev/dri/card0 --device /dev/dri/renderD128 \ -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ -e PULSE_SERVER=$PULSE_SERVER --gpus all videoaccel ``` ================================================ FILE: samples/container/build.sh ================================================ #!/bin/bash sudo docker build -t xclock -f Dockerfile.xclock . sudo docker build -t glxinfo -f Dockerfile.glxinfo . sudo docker build -t glxgears -f Dockerfile.glxgears . sudo docker build -t videoaccel -f Dockerfile.videoaccel . ================================================ FILE: samples/container/run.sh ================================================ #!/bin/bash # Containerized version of xclock sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ -e DISPLAY=$DISPLAY -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \ -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR -e PULSE_SERVER=$PULSE_SERVER xclock # Containerized version of glxinfo sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \ -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ -e PULSE_SERVER=$PULSE_SERVER --gpus all glxinfo # Containerized version of glxgears sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \ -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ -e PULSE_SERVER=$PULSE_SERVER --gpus all glxgears # Containerized version of videoaccel sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \ --device /dev/dri/card0 --device /dev/dri/renderD128 \ -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ -e PULSE_SERVER=$PULSE_SERVER --gpus all videoaccel ================================================ FILE: vendor/.preserve ================================================