[
  {
    "path": ".dockerignore",
    "content": ".git\n.github\n.gitlab\n.gitlab-ci\n.vs\nout\ntmp\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: \"Bug report\"\ndescription: \" Report a bug in WSLg\"\nlabels: \"bug\"\nbody:\n- type: input\n  id: build-number\n  attributes:\n    label: \"Windows build number:\"\n    description: \"run `[Environment]::OSVersion` for powershell, or `ver` for cmd\"\n    placeholder: \"22000.100\"\n  validations:\n    required: true\n- type: input\n  id: distribution_version\n  attributes:\n    label: \"Your Distribution version:\"\n    description: \"On Debian or Ubuntu run `lsb_release -r` in WSL\"\n    placeholder: \"20.04\"\n  validations:\n    required: true\n- type: textarea\n  id: wsl-version\n  attributes:\n    label: \"Your WSL versions:\"\n    description: \"run `wsl --version` on Windows command prompt\"\n  validations:\n    required: true\n- type: markdown\n  attributes:\n    value: |\n      **(Optional) Verifiy using the latest release of WSL/WSLg**:\n      \n      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.\n- type: textarea\n  id: reproduce-steps\n  attributes:\n    label: \"Steps to reproduce:\"\n    placeholder: |\n      1.\n      2.\n      3.\n  validations:\n    required: true\n- type: textarea\n  id: logs\n  attributes:\n    description: |\n      Collect WSL logs if needed by following these instructions: https://github.com/Microsoft/WSL/blob/master/CONTRIBUTING.md#8-detailed-logs\n      \n      * Attach WSLg logs from  `/mnt/wslg`\n\n      You can access the wslg logs using explorer at: `\\\\wsl$\\<Distro-Name>\\mnt\\wslg` (e.g.: `\\\\wsl$\\Ubuntu-20.04\\mnt\\wslg`)\n\n      * `pulseaudio.log`\n      * `weston.log`\n      * `stderr.log`\n    label: \"WSL logs:\"\n    placeholder: |\n      Drag and drop files into this input field to upload them.\n  validations:\n    required: false \n- type: textarea\n  id: dumps\n  attributes:\n    label: \"WSL dumps:\"\n    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`\"\n  validations:\n    required: false\n- type: textarea\n  id: expected-behavior\n  attributes:\n    label: \"Expected behavior:\"\n    description: \"A description of what you're expecting, possibly containing screenshots or reference material\"\n  validations:\n    required: false\n- type: textarea\n  id: actual-behavior\n  attributes:\n    label: \"Actual behavior:\"\n    description: \"What's actually happening?\"\n  validations:\n    required: true    \n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Report issues on WSL in general\n    url: https://github.com/microsoft/WSL/issues/new/choose\n    about: Please report issues not related to WSLg\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: \"Feature request\"\ndescription: \"Suggest a feature for WSLg\"\nlabels: \"enhancement\"\nbody:\n- type: textarea\n  attributes:\n    label: \"Is your feature request related to a problem:\"\n    description: \"A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\"\n  validations:\n    required: false\n- type: textarea\n  attributes:\n    label: \"Describe the solution you'd like:\"\n    description: \"A clear and concise description of what you want to happen.\"\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: \"Describe alternatives you've considered:\"\n    description: \"A clear and concise description of any alternative solutions or features you've considered.\"\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: \"Additional context:\"\n    description: \"Add any other context or screenshots about the feature request here.\"\n  validations:\n    required: false\n"
  },
  {
    "path": ".gitignore",
    "content": ".vscode/*\nvendor/*\n!vendor/.preserve\nout\ntmp\n*.nupkg\n*.tar\n*.vhd\nWSLGd/*.d\nWSLGd/*.o\nWSLGd/WSLGd\nWSLDVCPlugin/.vs\nWSLDVCPlugin/x64\nWSLDVCPlugin/ARM64/Debug/\nWSLDVCPlugin/ARM64/Release/\nWSLDVCPlugin/WSLDVCPlugin.aps"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\r\n\r\nThis project welcomes contributions and suggestions. Most contributions require you to\r\nagree to a Contributor License Agreement (CLA) declaring that you have the right to,\r\nand actually do, grant us the rights to use your contribution. For details, visit\r\nhttps://cla.microsoft.com.\r\n\r\nWhen you submit a pull request, a CLA-bot will automatically determine whether you need\r\nto provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the\r\ninstructions provided by the bot. You will only need to do this once across all repositories using our CLA.\r\n\r\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\r\nFor more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)\r\nor contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\r\n\r\n# Building the WSLg System Distro\r\n\r\nThe 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.\r\n\r\nThe 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.\r\n\r\nFor 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`).\r\n\r\nThe 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.\r\n\r\n## Build instructions\r\n\r\n0. Install and start Docker in a Linux or WSL 2 environment.\r\n\r\n```\r\n    sudo apt-get update\r\n    sudo apt install docker.io golang-go\r\n    sudo dockerd\r\n```\r\n\r\n1. Clone the WSLg project:\r\n\r\n```\r\n    git clone https://github.com/microsoft/wslg wslg\r\n```\r\n\r\n2. 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.\r\n\r\n    ```bash\r\n    git clone https://github.com/microsoft/FreeRDP-mirror wslg/vendor/FreeRDP -b working\r\n    git clone https://github.com/microsoft/weston-mirror wslg/vendor/weston -b working\r\n    git clone https://github.com/microsoft/PulseAudio-mirror wslg/vendor/pulseaudio -b working\r\n    git clone https://github.com/microsoft/DirectX-Headers.git wslg/vendor/DirectX-Headers-1.0 -b v1.608.0\r\n    git clone https://gitlab.freedesktop.org/mesa/mesa.git wslg/vendor/mesa -b mesa-23.1.0\r\n    ```\r\n\r\n2. Create the VHD:\r\n\r\n    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\r\n    ```\r\n    git clone --branch v0.8.9 --single-branch https://github.com/microsoft/hcsshim.git\r\n    ```\r\n    \r\n    2.2 From the parent directory build and export the docker image:\r\n    ```\r\n    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\r\n    sudo docker export `sudo docker create system-distro-x64` > system_x64.tar\r\n    ```\r\n    \r\n    2.3 Create the system distro vhd using `tar2ext4`\r\n    \r\n    ```bash\r\n    cd hcsshim/cmd/tar2ext4\r\n    go run tar2ext4.go -vhd -i ../../../system_x64.tar -o ../../../system.vhd\r\n    ```\r\n    \r\n    This will create system distro image `system.vhd`\r\n\r\n## Installing a private version of the WSLg system distro\r\n\r\nYou 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`).\r\n\r\n```\r\n    [wsl2]\r\n    systemDistro=C:\\\\Files\\\\system.vhd\r\n```    \r\n    \r\nYou 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. \r\n    \r\n## Inspecting the WSLg system distro at runtime\r\n\r\nIf 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.\r\n\r\n```\r\n    wsl --system -d [DistroName]\r\n```\r\nThere 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.\r\n\r\nPlease 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.\r\n\r\n## Building a debug version\r\n\r\nTo 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.\r\n\r\n```\r\n    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\r\n```\r\nThe 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\".\r\n\r\n# mstsc plugin\r\n\r\nOn 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).\r\n\r\nIt 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.\r\n\r\nSo 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.\r\n\r\nThis 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.\r\n\r\n## Building the mstsc plugin\r\n\r\nThe [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.\r\n\r\n## Registering a private mstsc plugin\r\n\r\nThe 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.\r\n\r\n```\r\nWindows Registry Editor Version 5.00\r\n\r\n[HKEY_CURRENT_USER\\Software\\Microsoft\\Terminal Server Client\\Default\\AddIns\\WSLDVCPlugin]\r\n\"Name\"=\"C:\\\\users\\\\MyUser\\\\Privates\\\\WSLDVCPlugin.dll\"\r\n```\r\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Create a builder image with the compilers, etc. needed\nFROM mcr.microsoft.com/azurelinux/base/core:3.0 AS build-env\n\n# Install all the required packages for building. This list is probably\n# longer than necessary.\nRUN echo \"== Install Git/CA certificates ==\" && \\\n    tdnf install -y \\\n        git \\\n        ca-certificates\n\nRUN echo \"== Install Core dependencies ==\" && \\\n    tdnf install -y \\\n        alsa-lib \\\n        alsa-lib-devel  \\\n        autoconf  \\\n        automake  \\\n        binutils  \\\n        bison  \\\n        build-essential  \\\n        cairo \\\n        cairo-devel \\\n        clang  \\\n        clang-devel  \\\n        cmake  \\\n        dbus  \\\n        dbus-devel  \\\n        dbus-glib  \\\n        dbus-glib-devel  \\\n        diffutils  \\\n        elfutils-devel  \\\n        file-libs  \\\n        flex  \\\n        fontconfig-devel  \\\n        gawk  \\\n        gcc  \\\n        gettext  \\\n        glibc-devel  \\\n        glib-schemas \\\n        gobject-introspection  \\\n        gobject-introspection-devel  \\\n        harfbuzz  \\\n        harfbuzz-devel  \\\n        kernel-headers  \\\n        intltool \\\n        libatomic_ops  \\\n        libcap-devel  \\\n        libffi  \\\n        libffi-devel  \\\n        libgudev  \\\n        libgudev-devel  \\\n        libjpeg-turbo  \\\n        libjpeg-turbo-devel  \\\n        libltdl  \\\n        libltdl-devel  \\\n        libpng-devel  \\\n        librsvg2-devel \\\n        libtiff  \\\n        libtiff-devel  \\\n        libusb  \\\n        libusb-devel  \\\n        libwebp  \\\n        libwebp-devel  \\\n        libxml2 \\\n        libxml2-devel  \\\n        make  \\\n        meson  \\\n        newt  \\\n        nss  \\\n        nss-libs  \\\n        openldap  \\\n        openssl-devel  \\\n        pam-devel  \\\n        pango  \\\n        pango-devel  \\\n        patch  \\\n        perl-XML-Parser \\\n        polkit-devel  \\\n        python3-devel \\\n        python3-mako  \\\n        python3-markupsafe \\\n        sed \\\n        sqlite-devel \\\n        systemd-devel  \\\n        tar \\\n        unzip  \\\n        vala  \\\n        vala-devel  \\\n        vala-tools  \\\n        zlib-devel\n\nRUN echo \"== Install UI dependencies ==\" && \\\n    tdnf    install -y \\\n            libdrm-devel \\\n            libepoxy-devel \\\n            libevdev \\\n            libevdev-devel \\\n            libinput \\\n            libinput-devel \\\n            libpciaccess-devel \\\n            libSM-devel \\\n            libsndfile \\\n            libsndfile-devel \\\n            libXcursor \\\n            libXcursor-devel \\\n            libXdamage-devel \\\n            libXfont2-devel \\\n            libXi \\\n            libXi-devel \\\n            libxkbcommon-devel \\\n            libxkbfile-devel \\\n            libXrandr-devel \\\n            libxshmfence-devel \\\n            libXtst \\\n            libXtst-devel \\\n            libXxf86vm-devel \\\n            wayland-devel \\\n            wayland-protocols-devel \\\n            xkbcomp \\\n            xkeyboard-config \\\n            xorg-x11-server-Xwayland-devel \\\n            xorg-x11-util-macros\n\n# Create an image with builds of FreeRDP and Weston\nFROM build-env AS dev\n\nARG WSLG_VERSION=\"<current>\"\nARG WSLG_ARCH=\"x86_64\"\nARG SYSTEMDISTRO_DEBUG_BUILD\nARG FREERDP_VERSION=2\n\nWORKDIR /work\nRUN echo \"WSLg (\" ${WSLG_ARCH} \"):\" ${WSLG_VERSION} > /work/versions.txt\nRUN echo \"Built at:\" `date --utc` >> /work/versions.txt\n\nRUN echo \"Azure Linux:\" `cat /etc/os-release | head -2 | tail -1` >> /work/versions.txt\n\n#\n# Build runtime dependencies.\n#\n\nENV BUILDTYPE=${SYSTEMDISTRO_DEBUG_BUILD:+debug}\nENV BUILDTYPE=${BUILDTYPE:-debugoptimized}\nRUN echo \"== System distro build type:\" ${BUILDTYPE} \" ==\"\n\nENV BUILDTYPE_NODEBUGSTRIP=${SYSTEMDISTRO_DEBUG_BUILD:+debug}\nENV BUILDTYPE_NODEBUGSTRIP=${BUILDTYPE_NODEBUGSTRIP:-release}\nRUN echo \"== System distro build type (no debug strip):\" ${BUILDTYPE_NODEBUGSTRIP} \" ==\"\n\n# FreeRDP is always built with RelWithDebInfo\nENV BUILDTYPE_FREERDP=${BUILDTYPE_FREERDP:-RelWithDebInfo}\nRUN echo \"== System distro build type (FreeRDP):\" ${BUILDTYPE_FREERDP} \" ==\"\n\nENV WITH_DEBUG_FREERDP=${SYSTEMDISTRO_DEBUG_BUILD:+ON}\nENV WITH_DEBUG_FREERDP=${WITH_DEBUG_FREERDP:-OFF}\nRUN echo \"== System distro build type (FreeRDP Debug Options):\" ${WITH_DEBUG_FREERDP} \" ==\"\n\nENV DESTDIR=/work/build\nENV PREFIX=/usr\nENV PKG_CONFIG_PATH=${DESTDIR}${PREFIX}/lib/pkgconfig:${DESTDIR}${PREFIX}/lib/${WSLG_ARCH}-linux-gnu/pkgconfig:${DESTDIR}${PREFIX}/share/pkgconfig\nENV C_INCLUDE_PATH=${DESTDIR}${PREFIX}/include/freerdp${FREERDP_VERSION}:${DESTDIR}${PREFIX}/include/winpr${FREERDP_VERSION}:${DESTDIR}${PREFIX}/include/wsl/stubs:${DESTDIR}${PREFIX}/include\nENV CPLUS_INCLUDE_PATH=${C_INCLUDE_PATH}\nENV LIBRARY_PATH=${DESTDIR}${PREFIX}/lib\nENV LD_LIBRARY_PATH=${LIBRARY_PATH}\nENV CC=/usr/bin/gcc\nENV CXX=/usr/bin/g++\n\n# Setup DebugInfo folder\nCOPY debuginfo /work/debuginfo\nRUN chmod +x /work/debuginfo/gen_debuginfo.sh\n\n# Build DirectX-Headers\nCOPY vendor/DirectX-Headers-1.0 /work/vendor/DirectX-Headers-1.0\nWORKDIR /work/vendor/DirectX-Headers-1.0\nRUN /usr/bin/meson --prefix=${PREFIX} build \\\n        --buildtype=${BUILDTYPE_NODEBUGSTRIP} \\\n        -Dbuild-test=false && \\\n    ninja -C build -j8 install && \\\n    echo 'DirectX-Headers:' `git --git-dir=/work/vendor/DirectX-Headers-1.0/.git rev-parse --verify HEAD` >> /work/versions.txt\n\n# Build mesa with the minimal options we need.\nCOPY vendor/mesa /work/vendor/mesa\nWORKDIR /work/vendor/mesa\nRUN /usr/bin/meson --prefix=${PREFIX} build \\\n        --buildtype=${BUILDTYPE_NODEBUGSTRIP} \\\n        -Dgallium-drivers=swrast,d3d12 \\\n        -Dvulkan-drivers= \\\n        -Dllvm=disabled && \\\n    ninja -C build -j8 install && \\\n    echo 'mesa:' `git --git-dir=/work/vendor/mesa/.git rev-parse --verify HEAD` >> /work/versions.txt\n\n# Build PulseAudio\nCOPY vendor/pulseaudio /work/vendor/pulseaudio\nWORKDIR /work/vendor/pulseaudio\nRUN /usr/bin/meson --prefix=${PREFIX} build \\\n        --buildtype=${BUILDTYPE_NODEBUGSTRIP} \\\n        -Ddatabase=simple \\\n        -Ddoxygen=false \\\n        -Dgsettings=disabled \\\n        -Dtests=false && \\\n    ninja -C build -j8 install && \\\n    echo 'pulseaudio:' `git --git-dir=/work/vendor/pulseaudio/.git rev-parse --verify HEAD` >> /work/versions.txt\n\n# Build FreeRDP\nCOPY vendor/FreeRDP /work/vendor/FreeRDP\nWORKDIR /work/vendor/FreeRDP\nRUN cmake -G Ninja \\\n        -B build \\\n        -DCMAKE_INSTALL_PREFIX=${PREFIX} \\\n        -DCMAKE_INSTALL_LIBDIR=${PREFIX}/lib \\\n        -DCMAKE_BUILD_TYPE=${BUILDTYPE_FREERDP} \\\n        -DWITH_DEBUG_ALL=${WITH_DEBUG_FREERDP} \\\n        -DWITH_ICU=ON \\\n        -DWITH_SERVER=ON \\\n        -DWITH_CHANNEL_GFXREDIR=ON \\\n        -DWITH_CHANNEL_RDPAPPLIST=ON \\\n        -DWITH_CLIENT=OFF \\\n        -DWITH_CLIENT_COMMON=OFF \\\n        -DWITH_CLIENT_CHANNELS=OFF \\\n        -DWITH_CLIENT_INTERFACE=OFF \\\n        -DWITH_LIBSYSTEMD=OFF \\\n        -DWITH_WAYLAND=OFF \\\n        -DWITH_X11=OFF \\\n        -DWITH_PROXY=OFF \\\n        -DWITH_SHADOW=OFF \\\n        -DWITH_SAMPLE=OFF && \\\n    ninja -C build -j8 install && \\\n    echo 'FreeRDP:' `git --git-dir=/work/vendor/FreeRDP/.git rev-parse --verify HEAD` >> /work/versions.txt\n\nWORKDIR /work/debuginfo\nRUN if [ -z \"$SYSTEMDISTRO_DEBUG_BUILD\" ] ; then \\\n        echo \"== Strip debug info: FreeRDP ==\" && \\\n        /work/debuginfo/gen_debuginfo.sh /work/debuginfo/FreeRDP${FREERDP_VERSION}.list /work/build; \\\n    fi\n\n# Build rdpapplist RDP virtual channel plugin\nCOPY rdpapplist /work/rdpapplist\nWORKDIR /work/rdpapplist\nRUN /usr/bin/meson --prefix=${PREFIX} build \\\n        --buildtype=${BUILDTYPE} && \\\n    ninja -C build -j8 install\n\nWORKDIR /work/debuginfo\nRUN if [ -z \"$SYSTEMDISTRO_DEBUG_BUILD\" ] ; then \\\n        echo \"== Strip debug info: rdpapplist ==\" && \\\n        /work/debuginfo/gen_debuginfo.sh /work/debuginfo/rdpapplist.list /work/build; \\\n    fi\n\n# Build Weston\nCOPY vendor/weston /work/vendor/weston\nWORKDIR /work/vendor/weston\nRUN /usr/bin/meson --prefix=${PREFIX} build \\\n        --buildtype=${BUILDTYPE} \\\n        -Dbackend-default=rdp \\\n        -Dbackend-drm=false \\\n        -Dbackend-drm-screencast-vaapi=false \\\n        -Dbackend-headless=false \\\n        -Dbackend-wayland=false \\\n        -Dbackend-x11=false \\\n        -Dbackend-fbdev=false \\\n        -Dcolor-management-colord=false \\\n        -Dscreenshare=false \\\n        -Dsystemd=false \\\n        -Dwslgd=true \\\n        -Dremoting=false \\\n        -Dpipewire=false \\\n        -Dshell-fullscreen=false \\\n        -Dcolor-management-lcms=false \\\n        -Dshell-ivi=false \\\n        -Dshell-kiosk=false \\\n        -Ddemo-clients=false \\\n        -Dsimple-clients=[] \\\n        -Dtools=[] \\\n        -Dresize-pool=false \\\n        -Dwcap-decode=false \\\n        -Dtest-junit-xml=false && \\\n    ninja -C build -j8 install && \\\n    echo 'weston:' `git --git-dir=/work/vendor/weston/.git rev-parse --verify HEAD` >> /work/versions.txt\n\nWORKDIR /work/debuginfo\nRUN if [ -z \"$SYSTEMDISTRO_DEBUG_BUILD\" ] ; then \\\n        echo \"== Strip debug info: weston ==\" && \\\n        /work/debuginfo/gen_debuginfo.sh /work/debuginfo/weston.list /work/build; \\\n    fi\n\n# Build WSLGd Daemon\nENV CC=/usr/bin/clang\nENV CXX=/usr/bin/clang++\n\nCOPY WSLGd /work/WSLGd\nWORKDIR /work/WSLGd\nRUN /usr/bin/meson --prefix=${PREFIX} build \\\n        --buildtype=${BUILDTYPE} && \\\n    ninja -C build -j8 install\n\nWORKDIR /work/debuginfo\nRUN if [ -z \"$SYSTEMDISTRO_DEBUG_BUILD\" ] ; then \\\n        echo \"== Strip debug info: WSLGd ==\" && \\\n        /work/debuginfo/gen_debuginfo.sh /work/debuginfo/WSLGd.list /work/build; \\\n    fi\n\n# Gather debuginfo to a tar file\nWORKDIR /work/debuginfo\nRUN if [ -z \"$SYSTEMDISTRO_DEBUG_BUILD\" ] ; then \\\n        echo \"== Compress debug info: /work/debuginfo/system-debuginfo.tar.gz ==\" && \\\n        tar -C /work/build/debuginfo -czf system-debuginfo.tar.gz ./ ; \\\n    fi\n\n########################################################################\n########################################################################\n\n## Create the distro image with just what's needed at runtime\n\nFROM mcr.microsoft.com/azurelinux/base/core:3.0 AS runtime\n\nRUN echo \"== Install Core/UI Runtime Dependencies ==\" && \\\n    tdnf    install -y \\\n            busybox \\\n            ca-certificates \\\n            cairo \\\n            chrony \\\n            containerd2 \\\n            containernetworking-plugins \\\n            runc \\\n            dbus \\\n            dbus-glib \\\n            dhcpcd \\\n            docker-buildx \\\n            docker-cli \\\n            e2fsprogs \\\n            freefont \\\n            gzip \\\n            icu \\\n            iptables \\\n            kmod \\\n            libinput \\\n            libjpeg-turbo \\\n            libltdl \\\n            libpng \\\n            librsvg2 \\\n            libsndfile \\\n            libwayland-client \\\n            libwayland-server \\\n            libwayland-cursor \\\n            libwebp \\\n            libXcursor \\\n            libxkbcommon \\\n            libXrandr \\\n            iproute \\\n            moby-engine \\\n            nftables \\\n            pango \\\n            procps-ng \\\n            rpm \\\n            sed \\\n            systemd-libs \\\n            tar \\\n            tzdata \\\n            util-linux \\\n            xcursor-themes \\\n            xorg-x11-server-Xwayland \\\n            xorg-x11-server-utils\n\n# Install busybox utilities\nRUN /sbin/busybox --install -s\n\n# Remove unnecessary packages and files to reduce image size\nARG SYSTEMDISTRO_DEBUG_BUILD\nRUN if [ -z \"$SYSTEMDISTRO_DEBUG_BUILD\" ] ; then \\\n        echo \"== Removing unnecessary packages ==\" && \\\n        # Remove build tools and packages not needed at runtime \\\n        rpm -e --nodeps \\\n            cracklib-dicts \\\n            gcc \\\n            gcc-c++ \\\n            libpkgconf \\\n            llvm \\\n            perl \\\n            pkgconf \\\n            pkgconf-m4 \\\n            pkgconf-pkg-config \\\n            python3 \\\n            python3-libs && \\\n        # Remove all perl subpackages \\\n        rpm -e --nodeps $(rpm -qa | grep -- '^perl-') && \\\n        # Remove all -devel packages \\\n        rpm -e --nodeps $(rpm -qa | grep -- '-devel') && \\\n        # Remove systemd components (except systemd-libs which is needed by weston) \\\n        rpm -e --nodeps $(rpm -qa | grep -- '^systemd-' | grep -v systemd-libs) && \\\n        # Remove orphaned packages \\\n        tdnf autoremove -y && \\\n        echo \"== Removing unnecessary files ==\" && \\\n        # Remove docs, man pages, locales, gtk-doc \\\n        rm -rf /usr/share/man /usr/share/info /usr/share/locale /usr/share/gtk-doc && \\\n        find /usr/share/doc -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} + && \\\n        # Remove unused Mesa driver \\\n        rm -f /usr/lib64/dri/virtio_gpu_dri.so && \\\n        # Remove hardware database (not needed in WSL) \\\n        rm -rf /usr/share/hwdata/* && \\\n        # Remove temporary files, logs, caches, and systemd catalog \\\n        rm -rf /tmp/* /var/tmp/* /var/log/* /var/cache/* /usr/lib/systemd/catalog/*; \\\n    else \\\n        echo \"== Install development aid packages ==\" && \\\n        tdnf install -y \\\n             gdb \\\n             azurelinux-repos-debug \\\n             nano \\\n             vim \\\n             wayland-debuginfo \\\n             xorg-x11-server-debuginfo; \\\n    fi\n\n# Clear the tdnf cache to make the image smaller\nRUN tdnf clean all\n\n# Create wslg user.\nRUN useradd -u 1000 --create-home wslg && \\\n    mkdir /home/wslg/.config && \\\n    chown wslg /home/wslg/.config\n\n# Copy config files.\nCOPY config/wsl.conf /etc/wsl.conf\nCOPY config/weston.ini /home/wslg/.config/weston.ini\nCOPY config/local.conf /etc/fonts/local.conf\n\n# Copy default icon file.\nCOPY resources/linux.png /usr/share/icons/wsl/linux.png\n\n# Copy the built artifacts from the build stage.\nCOPY --from=dev /work/build/usr/ /usr/\nCOPY --from=dev /work/build/etc/ /etc/\n\n# Append WSLg setttings to pulseaudio.\nCOPY config/default_wslg.pa /etc/pulse/default_wslg.pa\nRUN cat /etc/pulse/default_wslg.pa >> /etc/pulse/default.pa\nRUN rm /etc/pulse/default_wslg.pa\n\n# Copy the licensing information for PulseAudio\nCOPY --from=dev /work/vendor/pulseaudio/GPL \\\n                /work/vendor/pulseaudio/LGPL \\\n                /work/vendor/pulseaudio/LICENSE \\\n                /work/vendor/pulseaudio/NEWS \\\n                /work/vendor/pulseaudio/README /usr/share/doc/pulseaudio/\n\n# Copy the licensing information for Weston\nCOPY --from=dev /work/vendor/weston/COPYING /usr/share/doc/weston/COPYING\n\n# Copy the licensing information for FreeRDP\nCOPY --from=dev /work/vendor/FreeRDP/LICENSE /usr/share/doc/FreeRDP/LICENSE\n\n# copy the documentation and licensing information for mesa\nCOPY --from=dev /work/vendor/mesa/docs /usr/share/doc/mesa/\n\nCOPY --from=dev /work/versions.txt /etc/versions.txt\n\nCMD /usr/bin/WSLGd\n"
  },
  {
    "path": "LICENSE",
    "content": "    MIT License\n\n    Copyright (c) Microsoft Corporation.\n\n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice shall be included in all\n    copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n    SOFTWARE\n"
  },
  {
    "path": "Microsoft.WSLg.nuspec",
    "content": "<?xml version=\"1.0\"?>\r\n<!--\r\n***********************************************************************************************\r\nCopyright (C) Microsoft Corporation. All rights reserved.\r\n***********************************************************************************************\r\n-->\r\n<package>\r\n  <metadata>\r\n    <id>Microsoft.WSLg</id>\r\n    <version>0.2.12</version>\r\n    <authors>Microsoft</authors>\r\n    <owners>Microsoft, WSL Team</owners>\r\n    <projectUrl>https://github.com/microsoft/wslg/</projectUrl>\r\n    <requireLicenseAcceptance>false</requireLicenseAcceptance>\r\n    <description>WSLg support package.</description>\r\n    <summary>Enabling the Windows Subsystem for Linux to include support for Wayland and X server related scenarios.</summary>\r\n    <releaseNotes>ebad9c1</releaseNotes>\r\n    <copyright>Copyright © Microsoft Corporation 2021</copyright>\r\n    <tags>native</tags>\r\n  </metadata>\r\n  <files>\r\n    <file src=\"Microsoft.WSLg.targets\" target=\"build/Microsoft.WSLg.targets\" />\r\n    <file src=\"package/system_x64.vhd\" target=\"build/native/bin/x64/system.vhd\" />\r\n    <file src=\"package/system-debuginfo_x64.tar.gz\" target=\"build/native/bin/x64/system-debuginfo.tar.gz\" />\r\n    <file src=\"package/WSLDVCPlugin_x64.dll\" target=\"build/native/bin/x64/WSLDVCPlugin.dll\" />\r\n    <file src=\"package/WSLDVCPlugin_x64.pdb\" target=\"build/native/bin/x64/WSLDVCPlugin.pdb\" />\r\n    <file src=\"package/system_ARM64.vhd\" target=\"build/native/bin/arm64/system.vhd\" />\r\n    <file src=\"package/system-debuginfo_ARM64.tar.gz\" target=\"build/native/bin/arm64/system-debuginfo.tar.gz\" />\r\n    <file src=\"package/WSLDVCPlugin_ARM64.dll\" target=\"build/native/bin/arm64/WSLDVCPlugin.dll\" />\r\n    <file src=\"package/WSLDVCPlugin_ARM64.pdb\" target=\"build/native/bin/arm64/WSLDVCPlugin.pdb\" />\r\n\r\n    <file src=\"package/wslg.rdp\" target=\"build/native/bin/wslg.rdp\" />\r\n    <file src=\"package/wslg_desktop.rdp\" target=\"build/native/bin/wslg_desktop.rdp\" />\r\n  </files>\r\n</package>\r\n"
  },
  {
    "path": "Microsoft.WSLg.targets",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<!--\r\n***********************************************************************************************\r\nCopyright (C) Microsoft Corporation. All rights reserved.\r\n***********************************************************************************************\r\n-->\r\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup>\r\n    <None Include=\"$(MSBuildThisFileDirectory)native\\bin\\$(Platform)\\system.vhd\">\r\n      <Link>system.vhd</Link>\r\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\r\n    </None>\r\n    <None Include=\"$(MSBuildThisFileDirectory)native\\bin\\$(Platform)\\WSLDVCPlugin.dll\">\r\n      <Link>WSLDVCPlugin.dll</Link>\r\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\r\n    </None>\r\n    <None Include=\"$(MSBuildThisFileDirectory)native\\bin\\wslg.rdp\">\r\n      <Link>wslg.rdp</Link>\r\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\r\n    </None>\r\n    <None Include=\"$(MSBuildThisFileDirectory)native\\bin\\wslg_desktop.rdp\">\r\n      <Link>wslg_desktop.rdp</Link>\r\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\r\n    </None>\r\n  </ItemGroup>\r\n</Project>\r\n"
  },
  {
    "path": "README.md",
    "content": "# Welcome to WSLg\r\nWSLg 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. \r\n\r\nWSLg 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.\r\n\r\nWSLg 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.\r\n\r\n![WSLg Integrated Desktop](/docs/WSLg_IntegratedDesktop.png)\r\n\r\n# Installing WSLg\r\n\r\n## Pre-requisites\r\n\r\n- 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.\r\n\r\n- 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.\r\n\r\n- 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.\r\n     \r\n   \r\n## Install instructions (Fresh Install - no prior WSL installation)\r\n\r\nFrom a command prompt with administrator privileges, run the command `wsl --install -d Ubuntu`, then reboot if prompted.\r\n\r\nAfter 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.\r\n\r\nVoilà! WSL and WSLg are installed and ready to be used!\r\n\r\n## Install instructions (Existing WSL install)\r\n\r\nIf 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. \r\n\r\nPlease 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.\r\n\r\nYou can list your currently installed distro and the version of WSL they are configured for using the following command from an elevated command prompt.\r\n\r\n```powershell\r\n   wsl --list -v\r\n```\r\nIf running in version 1 mode, switch to version 2. This can take a while.\r\n\r\n```powershell\r\n   wsl --set-version _distro_name_ 2\r\n```\r\n\r\nRestart WSL by running this command from an elevated command prompt, make sure to save any pending work first:\r\n\r\n```powershell\r\n    wsl --shutdown\r\n```\r\n\r\n## Updating WSL + WSLg\r\n\r\nTo update to the latest version of WSL and WSLg released for preview, simply run `wsl --update` from an elevated command prompt or powershell. \r\n\r\nYou'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.\r\n\r\n## First Launch\r\n\r\nIf 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! \r\n\r\nIf 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.\r\n\r\nCongrats you are done and ready to use GUI apps! \r\n\r\n## Install and run GUI apps\r\n\r\nIf 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. \r\n\r\n```powershell\r\n\r\n## Update list of available packages\r\nsudo apt update\r\n\r\n## Gedit\r\nsudo apt install gedit -y\r\n\r\n## GIMP\r\nsudo apt install gimp -y\r\n\r\n## Nautilus\r\nsudo apt install nautilus -y\r\n\r\n## VLC\r\nsudo apt install vlc -y\r\n\r\n## X11 apps\r\nsudo apt install x11-apps -y\r\n\r\n## Google Chrome\r\ncd /tmp\r\nsudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb\r\nsudo dpkg -i google-chrome-stable_current_amd64.deb \r\nsudo apt install --fix-broken -y\r\nsudo dpkg -i google-chrome-stable_current_amd64.deb\r\n\r\n## Microsoft Teams\r\ncd /tmp\r\nsudo curl -L -o \"./teams.deb\" \"https://teams.microsoft.com/downloads/desktopurl?env=production&plat=linux&arch=x64&download=true&linuxArchiveType=deb\"\r\nsudo apt install ./teams.deb -y\r\n\r\n## Microsoft Edge Dev Browser\r\nsudo 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\r\nsudo apt install /tmp/edge.deb -y\r\n```\r\n\r\nOnce 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:\r\n\r\n* `xcalc`, `xclock`, `xeyes` \r\n* `gimp`\r\n* `gedit ~/.bashrc` \r\n* `nautilus`\r\n* `vlc`\r\n* `google-chrome`\r\n* `teams`\r\n* `microsoft-edge`\r\n\r\n# WSLg Architecture Overview\r\n\r\n![WSLg Architecture Overview](/docs/WSLg_ArchitectureOverview.png)\r\n\r\n## User Distro\r\nThe 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.\r\n\r\nAll 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.\r\n\r\n## WSLg System Distro\r\nThe 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.\r\n\r\nUsers 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.\r\n\r\n```\r\n[wsl2]\r\nguiApplications=false\r\n```\r\n\r\nThe 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).\r\n\r\nEvery 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. \r\n\r\nWhile 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. \r\n\r\nAlthough 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.\r\n\r\n## WSLGd\r\n**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.\r\n\r\n## Weston\r\nWeston 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...\r\n\r\nThe 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.\r\n\r\n### RAIL-Shell\r\nWeston 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.\r\n\r\n## FreeRDP\r\nWeston 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.\r\n\r\n## Pulse Audio Plugin\r\nFor 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.\r\n\r\n## WSL Dynamic Virtual Channel Plugin (WSLDVCPlugin)\r\nWSLg 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.\r\n\r\n# OpenGL accelerated rendering in WSLg\r\n\r\nWhile 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/).\r\n\r\nSupport 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/). \r\n\r\nSupport 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.\r\n\r\nPlease 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.\r\n\r\n# WSLg Code Flow\r\nWSLg 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.\r\n\r\nAll 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.\r\n\r\nThe 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.\r\n\r\nAt this point in time, we have the following project mirrors for currently in-flight contributions.\r\n\r\n| Project | Upstream Repo | WSLg Mirror |\r\n|---|---|---|\r\n| Weston | https://github.com/wayland-project/weston | https://github.com/microsoft/Weston-mirror|\r\n| FreeRDP | https://github.com/FreeRDP/FreeRDP | https://github.com/microsoft/FreeRDP-mirror |\r\n| PulseAudio | https://github.com/pulseaudio/pulseaudio | https://github.com/microsoft/PulseAudio-mirror |\r\n\r\nThe following is a high level overview of the currently in-flight contributions to each project contained within these mirrors.\r\n\r\n## Weston\r\nWSLg 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).\r\n\r\n\r\nTo 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.\r\n\r\nWe've also fixed several bugs impacting various applications. Generally, these were problems that impacted Weston in all modes and were not specific to WSLg.\r\n\r\n## FreeRDP\r\nWeston 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.\r\n\r\n## PulseAudio\r\nFor 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.\r\n\r\n# Contributing\r\n\r\nIf 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.\r\n\r\n# Reporting a non-security issues\r\n\r\nFor 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).\r\n\r\n# Reporting security issues\r\n\r\nTo report security issues with WSLg or any other Microsoft products, please follow the instructions detailed [here](SECURITY.md).\r\n\r\n# Trademarks\r\nThis 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.\r\n"
  },
  {
    "path": "SECURITY.md",
    "content": "## Security\n\nMicrosoft 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/).\n\nIf 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.\n\n## Reporting Security Issues\n\n**Please do not report security vulnerabilities through public GitHub issues.**\n\nInstead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).\n\nIf 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).\n\nYou 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).\n\nPlease 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:\n\n  * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)\n  * Full paths of source file(s) related to the manifestation of the issue\n  * The location of the affected source code (tag/branch/commit or direct URL)\n  * Any special configuration required to reproduce the issue\n  * Step-by-step instructions to reproduce the issue\n  * Proof-of-concept or exploit code (if possible)\n  * Impact of the issue, including how an attacker might exploit the issue\n\nThis information will help us triage your report more quickly.\n\nIf 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.\n\n## Preferred Languages\n\nWe prefer all communications to be in English.\n\n## Policy\n\nMicrosoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd)."
  },
  {
    "path": "WSLDVCPlugin/RegisterWSLPlugin.reg",
    "content": "Windows Registry Editor Version 5.00\r\n\r\n[HKEY_CURRENT_USER\\Software\\Microsoft\\Terminal Server Client\\Default\\AddIns\\WSLDVCPlugin]\r\n\"Name\"=\"C:\\\\WINDOWS\\\\system32\\\\WSLDVCPlugin.dll\"\r\n"
  },
  {
    "path": "WSLDVCPlugin/UpdateRCVersion.ps1",
    "content": "\r\n$version = [string](gitversion /showvariable AssemblySemFileVer)\r\n$versionComma = $version.Replace(\".\", \",\")\r\n$informationalVersion = [string](gitversion /showvariable InformationalVersion)\r\n\r\n$content = (Get-Content -Encoding \"windows-1252\" -Path \".\\WSLDVCPlugin.rc\")\r\n$content = $content.Replace(\"1,0,0,1\", $versionComma);\r\n$content = $content.Replace(\"1.0.0.1\", $version);\r\n$content = $content.Replace(\"InformationalVersion\", $InformationalVersion);\r\n\r\nSet-Content -Encoding \"windows-1252\" -Path \".\\WSLDVCPlugin.rc\" -Value $content"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCCallback.cpp",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include \"pch.h\"\r\n#include <safeint.h>\r\n#include \"utils.h\"\r\n#include \"rdpapplist.h\"\r\n#include \"WSLDVCCallback.h\"\r\n#include \"WSLDVCFileDB.h\"\r\n\r\nconstexpr LPCWSTR c_WSL_registry_path = L\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Lxss\";\r\nconstexpr LPCWSTR c_WSLg_window_id = L\"WslgServerWindowId\";\r\nconstexpr LPCWSTR c_Working_dir = L\"%windir%\\\\system32\";\r\n\r\n//\r\n// This channel simply sends all the received messages back to the server. \r\n//\r\nclass WSLDVCCallback :\r\n    public RuntimeClass<\r\n    RuntimeClassFlags<ClassicCom>,\r\n    IWTSVirtualChannelCallback>\r\n{\r\npublic:\r\n   \r\n    HRESULT \r\n        RuntimeClassInitialize(\r\n            IWTSVirtualChannel* pChannel\r\n        )\r\n    {\r\n        HRESULT hr = WSLDVCFileDB_CreateInstance(NULL, &m_spFileDB);\r\n        if (SUCCEEDED(hr))\r\n        {\r\n            m_spChannel = pChannel;\r\n\r\n            InitializeCriticalSection(&m_crit);\r\n            m_bCriticalSectionInitialized = true;\r\n        }\r\n        return hr;\r\n    }\r\n\r\n    HRESULT\r\n        ReadAppListHeader(\r\n            _Inout_ UINT64 *size,\r\n            _Inout_ const BYTE** buffer,\r\n            _Out_ RDPAPPLIST_HEADER *appListHeader\r\n        )\r\n    {\r\n        const BYTE* cur;\r\n        UINT64 len;\r\n\r\n        assert(size);\r\n        assert(buffer);\r\n        assert(appListHeader);\r\n\r\n        cur = *buffer;\r\n        len = *size;\r\n\r\n        ReadUINT32(appListHeader->cmdId, cur, len);\r\n        ReadUINT32(appListHeader->length, cur, len);\r\n\r\n        *buffer = cur;\r\n        *size = len;\r\n\r\n        return S_OK;\r\n\r\n    Error_Read:\r\n\r\n        return E_FAIL;\r\n    }\r\n\r\n    HRESULT\r\n        ReadAppListServerCaps(\r\n            _Inout_ UINT64 *size,\r\n            _Inout_ const BYTE** buffer,\r\n            _Out_ RDPAPPLIST_SERVER_CAPS_PDU *serverCaps\r\n        )\r\n    {\r\n        const BYTE* cur;\r\n        UINT64 len;\r\n\r\n        assert(size);\r\n        assert(buffer);\r\n        assert(serverCaps);\r\n\r\n        cur = *buffer;\r\n        len = *size;\r\n\r\n        ReadUINT16(serverCaps->version, cur, len);\r\n        ReadSTRING(serverCaps->appListProviderName, cur, len, true);\r\n        if (serverCaps->version >= 4)\r\n        {\r\n            ReadSTRING(serverCaps->appListProviderUniqueId, cur, len, true);\r\n        }\r\n    \r\n        *buffer = cur;\r\n        *size = len;\r\n        \r\n        return S_OK;\r\n\r\n    Error_Read:\r\n\r\n        return E_FAIL;\r\n    }\r\n\r\n    HRESULT\r\n        ReadAppListUpdate(\r\n            _Inout_ UINT64* size,\r\n            _Inout_ const BYTE** buffer,\r\n            _Out_ RDPAPPLIST_UPDATE_APPLIST_PDU* updateAppList\r\n        )\r\n    {\r\n        const BYTE* cur;\r\n        UINT64 len;\r\n\r\n        assert(size);\r\n        assert(buffer);\r\n        assert(updateAppList);\r\n\r\n        cur = *buffer;\r\n        len = *size;\r\n\r\n        ReadUINT32(updateAppList->flags, cur, len);\r\n        CheckRequiredFlags(updateAppList->flags,\r\n            (RDPAPPLIST_FIELD_ID | RDPAPPLIST_FIELD_EXECPATH | RDPAPPLIST_FIELD_DESC));\r\n        if (updateAppList->flags & RDPAPPLIST_FIELD_ID)\r\n        {\r\n            ReadSTRING(updateAppList->appId, cur, len, true);\r\n        }\r\n        if (updateAppList->flags & RDPAPPLIST_FIELD_GROUP)\r\n        {\r\n            ReadSTRING(updateAppList->appGroup, cur, len, false);\r\n        }\r\n        if (updateAppList->flags & RDPAPPLIST_FIELD_EXECPATH)\r\n        {\r\n            ReadSTRING(updateAppList->appExecPath, cur, len, true);\r\n        }\r\n        if (updateAppList->flags & RDPAPPLIST_FIELD_WORKINGDIR)\r\n        {\r\n            ReadSTRING(updateAppList->appWorkingDir, cur, len, false);\r\n        }\r\n        if (updateAppList->flags & RDPAPPLIST_FIELD_DESC)\r\n        {\r\n            ReadSTRING(updateAppList->appDesc, cur, len, true);\r\n        }\r\n\r\n        *buffer = cur;\r\n        *size = len;\r\n\r\n        return S_OK;\r\n\r\n    Error_Read:\r\n\r\n        return E_FAIL;\r\n    }\r\n\r\n    HRESULT\r\n        ReadAppListIconData(\r\n            _Inout_ UINT64* size,\r\n            _Inout_ const BYTE** buffer,\r\n            _Out_ RDPAPPLIST_ICON_DATA* iconData\r\n        )\r\n    {\r\n        const BYTE* cur;\r\n        HRESULT hr;\r\n        UINT64 len;\r\n        ICON_HEADER* pIconHeader;\r\n        BITMAPINFOHEADER* pIconBitmapInfo;\r\n        void* pIconBits;\r\n\r\n        assert(size);\r\n        assert(buffer);\r\n        assert(iconData);\r\n\r\n        cur = *buffer;\r\n        len = *size;\r\n\r\n        ZeroMemory(iconData, sizeof(*iconData));\r\n        ReadUINT32(iconData->flags, cur, len);\r\n        ReadUINT32(iconData->iconWidth, cur, len);\r\n        ReadUINT32(iconData->iconHeight, cur, len);\r\n        ReadUINT32(iconData->iconStride, cur, len);\r\n        ReadUINT32(iconData->iconBpp, cur, len);\r\n        ReadUINT32(iconData->iconFormat, cur, len);\r\n        ReadUINT32(iconData->iconBitsLength, cur, len);\r\n\r\n        hr = UIntAdd(sizeof(ICON_HEADER) + sizeof(BITMAPINFOHEADER), iconData->iconBitsLength, &iconData->iconFileSize);\r\n        if (FAILED(hr)) {\r\n            return hr;\r\n        }\r\n\r\n        iconData->iconFileData = (BYTE*)LocalAlloc(LPTR, iconData->iconFileSize);\r\n        if (!iconData->iconFileData)\r\n        {\r\n            return E_OUTOFMEMORY;\r\n        }\r\n\r\n        pIconHeader = (ICON_HEADER*)iconData->iconFileData;\r\n        pIconBitmapInfo = (BITMAPINFOHEADER*)(pIconHeader + 1);\r\n        pIconBits = (void*)(pIconBitmapInfo + 1);\r\n\r\n        ReadBYTES(pIconBits, cur, iconData->iconBitsLength, len);\r\n\r\n        pIconHeader->idReserved = 0; // Reserved (must be 0)\r\n        pIconHeader->idType = 1;     // Resource Type (1 for icons)\r\n        pIconHeader->idCount = 1;    // How many images?\r\n        pIconHeader->idEntries[0].bWidth = (BYTE)iconData->iconWidth;   // Width, in pixels, of the image\r\n        pIconHeader->idEntries[0].bHeight = (BYTE)iconData->iconHeight; // Height, in pixels, of the image\r\n        pIconHeader->idEntries[0].bColorCount = 0;                      // Number of colors in image (0 if >=8bpp)\r\n        pIconHeader->idEntries[0].bReserved = 0;                        // Reserved ( must be 0)\r\n        pIconHeader->idEntries[0].wPlanes = 0;                          // Color Planes\r\n        pIconHeader->idEntries[0].wBitCount = (WORD)iconData->iconBpp;  // Bits per pixel\r\n        pIconHeader->idEntries[0].dwBytesInRes = sizeof(BITMAPINFOHEADER) + iconData->iconBitsLength; // How many bytes in this resource?\r\n        pIconHeader->idEntries[0].dwImageOffset = sizeof(ICON_HEADER);  // Where in the file is this image?\r\n\r\n        pIconBitmapInfo->biSize = sizeof(BITMAPINFOHEADER);\r\n        pIconBitmapInfo->biWidth = iconData->iconWidth;\r\n        pIconBitmapInfo->biHeight = iconData->iconHeight * 2;\r\n        pIconBitmapInfo->biPlanes = 1;\r\n        pIconBitmapInfo->biBitCount = (WORD)iconData->iconBpp;\r\n        pIconBitmapInfo->biCompression = BI_RGB;\r\n        pIconBitmapInfo->biSizeImage = iconData->iconBitsLength;\r\n        pIconBitmapInfo->biXPelsPerMeter = 0;\r\n        pIconBitmapInfo->biYPelsPerMeter = 0;\r\n        pIconBitmapInfo->biClrUsed = 0;\r\n        pIconBitmapInfo->biClrImportant = 0;\r\n\r\n#ifdef _DEBUG\r\n        // Verify ICON.\r\n        {\r\n            HICON hIcon = CreateIconFromResource((BYTE*)pIconBitmapInfo, iconData->iconFileSize - sizeof(ICON_HEADER), TRUE, 0x00030000);\r\n            if (!hIcon)\r\n            {\r\n                DebugPrint(L\"CreateIconFromResource() failed\\n\");\r\n            }\r\n            else\r\n            {\r\n                DestroyIcon(hIcon);\r\n            }\r\n        }\r\n#endif // _DEBUG\r\n\r\n        *buffer = cur;\r\n        *size = len;\r\n\r\n        return S_OK;\r\n\r\n    Error_Read:\r\n\r\n        if (iconData->iconFileData)\r\n        {\r\n            LocalFree(iconData->iconFileData);\r\n        }\r\n        ZeroMemory(iconData, sizeof(*iconData));\r\n\r\n        return E_FAIL;\r\n    }\r\n    \r\n    HRESULT\r\n        ReadAppListDelete(\r\n            _Inout_ UINT64* size,\r\n            _Inout_ const BYTE** buffer,\r\n            _Out_ RDPAPPLIST_DELETE_APPLIST_PDU* deleteAppList\r\n        )\r\n    {\r\n        const BYTE* cur;\r\n        UINT64 len;\r\n        \r\n        assert(size);\r\n        assert(buffer);\r\n        assert(deleteAppList);\r\n\r\n        cur = *buffer;\r\n        len = *size;\r\n\r\n        ReadUINT32(deleteAppList->flags, cur, len);\r\n        CheckRequiredFlags(deleteAppList->flags,\r\n            RDPAPPLIST_FIELD_ID);\r\n        if (deleteAppList->flags & RDPAPPLIST_FIELD_ID)\r\n        {\r\n            ReadSTRING(deleteAppList->appId, cur, len, true);\r\n        }\r\n        if (deleteAppList->flags & RDPAPPLIST_FIELD_GROUP)\r\n        {\r\n            ReadSTRING(deleteAppList->appGroup, cur, len, false);\r\n        }\r\n\r\n        *buffer = cur;\r\n        *size = len;\r\n\r\n        return S_OK;\r\n\r\n    Error_Read:\r\n\r\n        return E_FAIL;\r\n    }\r\n\r\n    HRESULT\r\n        ReadAssociateWindowId(\r\n            _Inout_ UINT64* size,\r\n            _Inout_ const BYTE** buffer,\r\n            _Out_ RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU* associateWindowId\r\n        )\r\n    {\r\n        const BYTE* cur;\r\n        UINT64 len;\r\n\r\n        assert(size);\r\n        assert(buffer);\r\n        assert(associateWindowId);\r\n\r\n        cur = *buffer;\r\n        len = *size;\r\n\r\n        ReadUINT32(associateWindowId->flags, cur, len);\r\n        CheckRequiredFlags(associateWindowId->flags,\r\n            RDPAPPLIST_FIELD_ID | RDPAPPLIST_FIELD_WINDOW_ID);\r\n        ReadUINT32(associateWindowId->appWindowId, cur, len);\r\n        if (associateWindowId->flags & RDPAPPLIST_FIELD_ID)\r\n        {\r\n            ReadSTRING(associateWindowId->appId, cur, len, true);\r\n        }\r\n        if (associateWindowId->flags & RDPAPPLIST_FIELD_GROUP)\r\n        {\r\n            ReadSTRING(associateWindowId->appGroup, cur, len, false);\r\n        }\r\n        if (associateWindowId->flags & RDPAPPLIST_FIELD_EXECPATH)\r\n        {\r\n            ReadSTRING(associateWindowId->appExecPath, cur, len, true);\r\n        }\r\n        if (associateWindowId->flags & RDPAPPLIST_FIELD_DESC)\r\n        {\r\n            ReadSTRING(associateWindowId->appDesc, cur, len, true);\r\n        }\r\n\r\n        *buffer = cur;\r\n        *size = len;\r\n\r\n        return S_OK;\r\n\r\n    Error_Read:\r\n\r\n        return E_FAIL;\r\n    }\r\n\r\n    HRESULT\r\n        OnSyncStart()\r\n    {\r\n        DebugPrint(L\"OnSyncStart():\\n\");\r\n        if (m_spFileDBSync.Get())\r\n        {\r\n            DebugPrint(L\"Server asks start sync, but already in sync mode.\\n\");\r\n            return E_FAIL;\r\n        }\r\n\r\n        HRESULT hr = WSLDVCFileDB_CreateInstance(NULL, &m_spFileDBSync);\r\n        if (FAILED(hr))\r\n        {\r\n            return hr;\r\n        }\r\n\r\n        // Add all files under menu path at sync start.\r\n        // This will also adds icon file pointed by .lnk file.\r\n        // Any files not reported during sync, will be removed at end.\r\n        m_spFileDBSync->addAllFilesAsFileIdAt(m_appMenuPath);\r\n\r\n        return S_OK;\r\n    }\r\n\r\n    HRESULT\r\n        OnSyncEnd(bool cleanupFiles = true)\r\n    {\r\n        DebugPrint(L\"OnSyncEnd():\\n\");\r\n        if (m_spFileDBSync.Get())\r\n        {\r\n            if (cleanupFiles)\r\n            {\r\n                m_spFileDBSync->deleteAllFileIdFiles();\r\n            }\r\n            m_spFileDBSync->OnClose();\r\n            m_spFileDBSync = nullptr;\r\n        }\r\n        return S_OK;\r\n    }\r\n\r\n    HRESULT\r\n        OnCaps(\r\n            _Inout_ UINT64* size,\r\n            _Inout_ const BYTE** buffer\r\n        )\r\n    {\r\n        HRESULT hr;\r\n        WCHAR szAppProviderGUID[40];\r\n\r\n        // Buffer read scope\r\n        {\r\n            const BYTE* cur;\r\n            UINT64 len;\r\n\r\n            assert(size);\r\n            assert(buffer);\r\n\r\n            cur = *buffer;\r\n            len = *size;\r\n\r\n            hr = ReadAppListServerCaps(&len, &cur, &m_serverCaps);\r\n            if (FAILED(hr))\r\n            {\r\n                return hr;\r\n            }\r\n\r\n            *buffer = cur;\r\n            *size = len;\r\n        }\r\n\r\n        memcpy(m_appProvider, m_serverCaps.appListProviderName, m_serverCaps.appListProviderNameLength);\r\n        if ((wcsstr(m_appProvider, L\"..\") != NULL) ||\r\n            (wcsstr(m_appProvider, L\"\\\"\") != NULL) ||\r\n            (wcsstr(m_appProvider, L\" \") != NULL))\r\n        {\r\n            DebugPrint(L\"provider name can't contain '..', '\\\"' or space, %s\\n\", m_appProvider);\r\n            return E_FAIL;\r\n        }\r\n\r\n        if (m_serverCaps.version >= 4)\r\n        {\r\n            if ((swprintf_s(szAppProviderGUID, ARRAYSIZE(szAppProviderGUID), L\"{%s}\",\r\n                           &m_serverCaps.appListProviderUniqueId[0]) < 0) ||\r\n                (szAppProviderGUID[0] == L'\\0'))\r\n            {\r\n                DebugPrint(L\"swprintf_s failed on %s\\n\", m_serverCaps.appListProviderUniqueId);\r\n                return E_FAIL;\r\n            }\r\n\r\n            hr = CLSIDFromString(szAppProviderGUID, &m_appProviderGUID);\r\n            if (FAILED(hr))\r\n            {\r\n                DebugPrint(L\"CLSIDFromString failed on %s\\n\", szAppProviderGUID);\r\n                return hr;\r\n            }\r\n        }\r\n\r\n        hr = BuildMenuPath(ARRAYSIZE(m_appMenuPath), m_appMenuPath, m_appProvider, true);\r\n        if (FAILED(hr))\r\n        {\r\n            return hr;\r\n        }\r\n        DebugPrint(L\"AppMenuPath: %s\\n\", m_appMenuPath);\r\n\r\n        hr = BuildIconPath(ARRAYSIZE(m_iconPath), m_iconPath, m_appProvider, true);\r\n        if (FAILED(hr))\r\n        {\r\n            return hr;\r\n        }\r\n        DebugPrint(L\"IconPath: %s\\n\", m_iconPath);\r\n\r\n        // Read the registry key that declares where the WSL package is installed.\r\n        HKEY key;\r\n        hr = HRESULT_FROM_WIN32(RegOpenKeyExW(HKEY_LOCAL_MACHINE, c_WSL_registry_path, 0, KEY_READ, &key));\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"RegOpenKeyExW failed\\n\");\r\n            return hr;\r\n        }\r\n\r\n        DWORD valueSize = sizeof(m_expandedPathObj);\r\n        hr = HRESULT_FROM_WIN32(RegGetValueW(key, L\"Msi\", L\"InstallLocation\", (RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ), nullptr, m_expandedPathObj, &valueSize));\r\n        RegCloseKey(key);\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"RegGetValueW failed\\n\");\r\n            return hr;\r\n        }\r\n\r\n        if (wcscat_s(m_expandedPathObj, ARRAYSIZE(m_expandedPathObj), L\"\\\\wslg.exe\") != 0)\r\n        {\r\n            return E_FAIL;\r\n        }\r\n        DebugPrint(L\"WSLg.exe: %s\\n\", m_expandedPathObj);\r\n\r\n        if (ExpandEnvironmentStringsW(c_Working_dir, m_expandedWorkingDir, ARRAYSIZE(m_expandedWorkingDir)) == 0)\r\n        {\r\n            DebugPrint(L\"Failed to expand working dir: %s : %d\\n\", c_Working_dir, GetLastError());\r\n            return E_FAIL;\r\n        }\r\n        DebugPrint(L\"WSL.exe working dir: %s\\n\", m_expandedWorkingDir);\r\n\r\n        if (!GetLocaleName(m_clientLanguageId, sizeof m_clientLanguageId))\r\n        {\r\n            strcpy_s(m_clientLanguageId, sizeof m_clientLanguageId, \"en_US\");\r\n        }\r\n\r\n        // Reply back header (8 bytes) + version (2 bytes) to server.\r\n        #pragma pack(push,1)\r\n        struct {\r\n            RDPAPPLIST_HEADER capsHeader;\r\n            RDPAPPLIST_CLIENT_CAPS_PDU caps;\r\n        } replyBuf = {};\r\n        #pragma pack(pop)\r\n\r\n        replyBuf.capsHeader.cmdId = RDPAPPLIST_CMDID_CAPS;\r\n        if (m_serverCaps.version >= RDPAPPLIST_CHANNEL_VERSION)\r\n        {\r\n            replyBuf.capsHeader.length = sizeof replyBuf;\r\n            replyBuf.caps.version = RDPAPPLIST_CHANNEL_VERSION;\r\n            if (strncpy_s(replyBuf.caps.clientLanguageId, m_clientLanguageId, sizeof replyBuf.caps.clientLanguageId) != 0)\r\n            {\r\n                return E_FAIL;\r\n            }\r\n        }\r\n        else\r\n        {\r\n            DebugPrint(L\"Invalid server version : %d\\n\", m_serverCaps.version);\r\n            hr = E_FAIL;\r\n        }\r\n        if (SUCCEEDED(hr))\r\n        {\r\n            hr = m_spChannel->Write(replyBuf.capsHeader.length, (BYTE*)&replyBuf, nullptr);\r\n            if (FAILED(hr))\r\n            {\r\n                DebugPrint(L\"m_spChannel->Write failed, hr = %x\\n\", hr);\r\n            }\r\n        }\r\n\r\n        m_handShakeComplated = SUCCEEDED(hr) ? true : false;\r\n\r\n        return hr;\r\n    }\r\n\r\n    HRESULT\r\n        OnUpdateAppList(\r\n            _Inout_ UINT64* size,\r\n            _Inout_ const BYTE** buffer\r\n        )\r\n    {\r\n        HRESULT hr;\r\n        RDPAPPLIST_UPDATE_APPLIST_PDU updateAppList = {};\r\n        RDPAPPLIST_ICON_DATA iconData = {};\r\n        WCHAR linkPath[MAX_PATH] = {};\r\n        WCHAR iconPath[MAX_PATH] = {};\r\n        WCHAR exeArgs[MAX_PATH] = {};\r\n        WCHAR key[MAX_PATH] = {};\r\n        bool hasIcon = false;\r\n\r\n        // Buffer read scope\r\n        {\r\n            const BYTE* cur;\r\n            UINT64 len;\r\n\r\n            assert(size);\r\n            assert(buffer);\r\n\r\n            cur = *buffer;\r\n            len = *size;\r\n\r\n            hr = ReadAppListUpdate(&len, &cur, &updateAppList);\r\n            if (FAILED(hr))\r\n            {\r\n                return hr;\r\n            }\r\n\r\n            if (updateAppList.flags & RDPAPPLIST_FIELD_ICON)\r\n            {\r\n                hr = ReadAppListIconData(&len, &cur, &iconData);\r\n                if (FAILED(hr))\r\n                {\r\n                    return hr;\r\n                }\r\n            }\r\n\r\n            *buffer = cur;\r\n            *size = len;\r\n        }\r\n\r\n        if (updateAppList.flags & RDPAPPLIST_HINT_SYNC_START)\r\n        {\r\n            hr = OnSyncStart();\r\n            if (FAILED(hr))\r\n            {\r\n                return hr;\r\n            }\r\n            assert(m_spFileDBSync.Get());\r\n        }\r\n\r\n        if (updateAppList.flags & (RDPAPPLIST_HINT_SYNC | RDPAPPLIST_HINT_SYNC_END))\r\n        {\r\n            if (!m_spFileDBSync.Get())\r\n            {\r\n                DebugPrint(L\"Server sends sync or sync end flag without starting sync mode. flags %x\\n\", updateAppList.flags);\r\n                return E_FAIL;\r\n            }\r\n        }\r\n\r\n        if (updateAppList.appGroupLength)\r\n        {\r\n            if ((wcsstr(updateAppList.appGroup, L\"..\") != NULL) ||\r\n                (wcsstr(updateAppList.appGroup, L\"\\\"\") != NULL))\r\n            {\r\n                DebugPrint(L\"group name can't contain '..' or '\\\"', %s\\n\", updateAppList.appGroup);\r\n                return E_FAIL;\r\n            }\r\n        }\r\n\r\n        if ((wcsstr(updateAppList.appId, L\"..\") != NULL) ||\r\n            (wcsstr(updateAppList.appId, L\"\\\"\") != NULL))\r\n        {\r\n            DebugPrint(L\"app id can't contain '..' or '\\\"', %s\\n\", updateAppList.appId);\r\n            return E_FAIL;\r\n        }\r\n\r\n        if ((wcsstr(updateAppList.appDesc, L\"..\") != NULL) ||\r\n            (wcsstr(updateAppList.appDesc, L\"\\\"\") != NULL))\r\n        {\r\n            DebugPrint(L\"app desc can't contain '..' or '\\\"', %s\\n\", updateAppList.appDesc);\r\n            return E_FAIL;\r\n        }\r\n\r\n        // Double quote (\") is valid file/path char in Linux, but currently wsl.exe/wslg.exe\r\n        // can't handle to give the path contains \" with --cd options, thus until this get fixed,\r\n        // block the path contains \".\r\n        if ((wcsstr(updateAppList.appWorkingDir, L\"\\\"\") != NULL))\r\n        {\r\n            DebugPrint(L\"app working dir can't contain '\\\"', %s\\n\", updateAppList.appWorkingDir);\r\n            return E_FAIL;\r\n        }\r\n\r\n        if (updateAppList.flags & RDPAPPLIST_FIELD_ICON)\r\n        {\r\n            if (wcscpy_s(iconPath, ARRAYSIZE(iconPath), m_iconPath) != 0)\r\n            {\r\n                return E_FAIL;\r\n            }\r\n            if (!CreateDirectoryW(iconPath, NULL))\r\n            {\r\n                if (ERROR_ALREADY_EXISTS != GetLastError())\r\n                {\r\n                    DebugPrint(L\"Failed to create %s\\n\", iconPath);\r\n                    return E_FAIL;\r\n                }\r\n            }\r\n            if (updateAppList.appGroupLength)\r\n            {\r\n                if ((wcscat_s(iconPath, ARRAYSIZE(iconPath), L\"\\\\\") != 0) ||\r\n                    (wcscat_s(iconPath, ARRAYSIZE(iconPath), updateAppList.appGroup) != 0))\r\n                {\r\n                    return E_FAIL;\r\n                }\r\n                if (!CreateDirectoryW(iconPath, NULL))\r\n                {\r\n                    if (ERROR_ALREADY_EXISTS != GetLastError())\r\n                    {\r\n                        DebugPrint(L\"Failed to create %s\\n\", iconPath);\r\n                        return E_FAIL;\r\n                    }\r\n                }\r\n            }\r\n            if ((wcscat_s(iconPath, ARRAYSIZE(iconPath), L\"\\\\\") != 0) ||\r\n                (wcscat_s(iconPath, ARRAYSIZE(iconPath), updateAppList.appId) != 0) ||\r\n                (wcscat_s(iconPath, ARRAYSIZE(iconPath), L\".ico\") != 0))\r\n            {\r\n                return E_FAIL;\r\n            }\r\n        }\r\n\r\n        if (wcscpy_s(linkPath, ARRAYSIZE(linkPath), m_appMenuPath) != 0)\r\n        {\r\n            return E_FAIL;\r\n        }\r\n        if (!CreateDirectoryW(linkPath, NULL))\r\n        {\r\n            if (ERROR_ALREADY_EXISTS != GetLastError())\r\n            {\r\n                DebugPrint(L\"Failed to create %s\\n\", linkPath);\r\n                return E_FAIL;\r\n            }\r\n        }\r\n        if (updateAppList.appGroupLength)\r\n        {\r\n            if ((wcscat_s(linkPath, ARRAYSIZE(linkPath), L\"\\\\\") != 0) ||\r\n                (wcscat_s(linkPath, ARRAYSIZE(linkPath), updateAppList.appGroup) != 0))\r\n            {\r\n                return E_FAIL;\r\n            }\r\n            if (!CreateDirectoryW(linkPath, NULL))\r\n            {\r\n                if (ERROR_ALREADY_EXISTS != GetLastError())\r\n                {\r\n                    DebugPrint(L\"Failed to create %s\\n\", linkPath);\r\n                    return E_FAIL;\r\n                }\r\n            }\r\n        }\r\n        // Use description to name link file since this is name shows up\r\n        // at StartMenu UI. SHSetLocalizedName can't be uses since this \r\n        // is not in resource.\r\n        if ((wcscat_s(linkPath, ARRAYSIZE(linkPath), L\"\\\\\") != 0) ||\r\n            (wcscat_s(linkPath, ARRAYSIZE(linkPath), updateAppList.appDesc) != 0) ||\r\n            (wcscat_s(linkPath, ARRAYSIZE(linkPath), L\".lnk\") != 0))\r\n        {\r\n            return E_FAIL;\r\n        }\r\n\r\n        // build wsl.exe/wslg.exe command line.\r\n        // -d : distro name, space is not allowed in distro name,\r\n        //      thus wrapping by double-quoto is not accepted by wsl.exe/wslg.exe\r\n        // --cd : current directory, wrap by double-quote.\r\n        // -- : executable path with arguments. All string after '--' will be treated as Linux command line,\r\n        //      thus wrapping by double-quoto is not accepted by wsl.exe/wslg.exe\r\n        if ((swprintf_s(exeArgs, ARRAYSIZE(exeArgs), L\"-d %s --cd \\\"%s\\\" -- %s\",\r\n                       m_appProvider, \r\n                       updateAppList.appWorkingDirLength ? updateAppList.appWorkingDir : L\"~\",\r\n                       updateAppList.appExecPath) < 0) ||\r\n            (exeArgs[0] == L'\\0'))\r\n        {\r\n            return E_FAIL;\r\n        }\r\n\r\n        key[0] = L'\\0';\r\n        if (updateAppList.appGroupLength)\r\n        {\r\n            if ((wcscat_s(key, ARRAYSIZE(key), updateAppList.appGroup) != 0) ||\r\n                (wcscat_s(key, ARRAYSIZE(key), L\"\\\\\") != 0))\r\n            {\r\n                return E_FAIL;\r\n            }\r\n        }\r\n        if (wcscat_s(key, ARRAYSIZE(key), updateAppList.appId) != 0)\r\n        {\r\n            return E_FAIL;\r\n        }\r\n        hr = m_spFileDB->OnFileAdded(key, linkPath, iconPath, m_expandedPathObj, exeArgs);\r\n        if (FAILED(hr))\r\n        {\r\n            return hr;\r\n        }\r\n\r\n        if (updateAppList.flags & RDPAPPLIST_FIELD_ICON)\r\n        {\r\n            if (SUCCEEDED(CreateIconFile(iconData.iconFileData, iconData.iconFileSize, iconPath)))\r\n            {\r\n                hasIcon = true;\r\n            }\r\n            else\r\n            {\r\n                DebugPrint(L\"Failed to create icon file %s\\n\", iconPath);\r\n                // Icon is optional, so keep going.\r\n            }\r\n        }\r\n\r\n        /* ignore error from create link */\r\n        /* This also updates if m_expandedPathObj (wsl.exe or wslg.exe) is changed. */\r\n        CreateShellLink((LPCWSTR)linkPath,\r\n                (LPCWSTR)m_expandedPathObj,\r\n                (LPCWSTR)exeArgs,\r\n                (LPCWSTR)m_expandedWorkingDir,\r\n                (LPCWSTR)updateAppList.appDesc,\r\n                hasIcon ? (LPCWSTR)iconPath : NULL);\r\n        \r\n        if (updateAppList.flags & RDPAPPLIST_HINT_SYNC)\r\n        {\r\n            // During sync mode, remove added file to sync DB, so these won't be cleaned up at end.\r\n            assert(m_spFileDBSync.Get());\r\n            m_spFileDBSync->OnFileRemoved(linkPath);\r\n            if (hasIcon)\r\n            {\r\n                m_spFileDBSync->OnFileRemoved(iconPath);\r\n            }\r\n        }\r\n\r\n        if (updateAppList.flags & RDPAPPLIST_HINT_SYNC_END)\r\n        {\r\n            OnSyncEnd();\r\n        }\r\n\r\n        return S_OK;\r\n    }\r\n\r\n    HRESULT\r\n        OnDeleteAppList(\r\n            _Inout_ UINT64* size,\r\n            _Inout_ const BYTE** buffer\r\n        )\r\n    {\r\n        HRESULT hr;\r\n        RDPAPPLIST_DELETE_APPLIST_PDU deleteAppList = {};\r\n        WCHAR linkPath[MAX_PATH] = {};\r\n        WCHAR iconPath[MAX_PATH] = {};\r\n        WCHAR key[MAX_PATH] = {};\r\n\r\n        // Buffer read scope\r\n        {\r\n            const BYTE* cur;\r\n            UINT64 len;\r\n\r\n            assert(size);\r\n            assert(buffer);\r\n\r\n            cur = *buffer;\r\n            len = *size;\r\n\r\n            hr = ReadAppListDelete(&len, &cur, &deleteAppList);\r\n            if (FAILED(hr))\r\n            {\r\n                return hr;\r\n            }\r\n\r\n            *buffer = cur;\r\n            *size = len;\r\n        }\r\n\r\n        key[0] = L'\\0';\r\n        if (deleteAppList.appGroupLength)\r\n        {\r\n            if ((wcscpy_s(key, ARRAYSIZE(key), deleteAppList.appGroup) != 0) ||\r\n                (wcscat_s(key, ARRAYSIZE(key), L\"\\\\\") != 0))\r\n            {\r\n                return E_FAIL;\r\n            }\r\n        }\r\n        if (wcscat_s(key, ARRAYSIZE(key), deleteAppList.appId) != 0)\r\n        {\r\n            return E_FAIL;\r\n        }\r\n\r\n        hr = m_spFileDB->FindFiles(key, linkPath, ARRAYSIZE(linkPath), iconPath, ARRAYSIZE(iconPath));\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"OnDeleteAppList(): key %s not found\\n\", key);\r\n            return E_FAIL;\r\n        }\r\n\r\n        if ((linkPath[0] != L'\\0') && !DeleteFileW(linkPath))\r\n        {\r\n            DebugPrint(L\"DeleteFile(%s) failed, error %x\\n\", linkPath, GetLastError());\r\n        }\r\n        DebugPrint(L\"Delete Path Link: %s\\n\", linkPath);\r\n\r\n        if ((iconPath[0] != L'\\0') && !DeleteFileW(iconPath))\r\n        {\r\n            DebugPrint(L\"DeleteFile(%s) failed, error %x\\n\", iconPath, GetLastError());\r\n        }\r\n        DebugPrint(L\"Delete Icon Path: %s\\n\", iconPath);\r\n\r\n        m_spFileDB->OnFileRemoved(key);\r\n\r\n        return S_OK;\r\n    }\r\n\r\n    typedef struct _WslgWindowData\r\n    {\r\n        UINT64 windowId;\r\n        LPCWSTR displayName;\r\n        LPCWSTR relaunchCommandline;\r\n        LPCWSTR iconPath;\r\n    } WslgWindowData;\r\n\r\n    static BOOL CALLBACK\r\n        FindWslgWindow(\r\n            _In_ HWND hwnd,\r\n            _In_ LPARAM args\r\n        )\r\n    {\r\n        WslgWindowData* wndData = (WslgWindowData*)args;\r\n        HANDLE windowId = GetPropW(hwnd, c_WSLg_window_id);\r\n        if (windowId == (HANDLE)wndData->windowId)\r\n        {\r\n            UpdateTaskBarInfo(hwnd,\r\n                wndData->relaunchCommandline,\r\n                wndData->displayName,\r\n                wndData->iconPath);\r\n            return FALSE; /* found it, stop enum */\r\n        }\r\n        return TRUE; /* keep enum next one */\r\n    }\r\n\r\n    HRESULT\r\n        OnAssociateWindowId(\r\n            _Inout_ UINT64* size,\r\n            _Inout_ const BYTE** buffer\r\n        )\r\n    {\r\n        HRESULT hr;\r\n        RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU associateWindowId = {};\r\n        WCHAR tmpBuf[MAX_PATH] = {};\r\n        WCHAR tmpPath[MAX_PATH] = {};\r\n        WCHAR tmpArgs[MAX_PATH] = {};\r\n        WCHAR iconPath[MAX_PATH] = {};\r\n        WCHAR exePath[MAX_PATH] = {};\r\n\r\n        if (m_serverCaps.version < 4)\r\n        {\r\n            DebugPrint(L\"associate window id requires version 4 or newer: current version: %d\\n\",\r\n                m_serverCaps.version < 4);\r\n            return E_FAIL;\r\n        }\r\n\r\n        // Buffer read scope\r\n        {\r\n            const BYTE* cur;\r\n            UINT64 len;\r\n\r\n            assert(size);\r\n            assert(buffer);\r\n\r\n            cur = *buffer;\r\n            len = *size;\r\n\r\n            hr = ReadAssociateWindowId(&len, &cur, &associateWindowId);\r\n            if (FAILED(hr))\r\n            {\r\n                return hr;\r\n            }\r\n\r\n            *buffer = cur;\r\n            *size = len;\r\n        }\r\n\r\n        /* construct full unique window id */\r\n        UINT64 windowId = (UINT64)m_appProviderGUID.Data1;\r\n        windowId <<= 32;\r\n        windowId |= associateWindowId.appWindowId;\r\n        if (windowId == 0LL)\r\n        {\r\n            DebugPrint(L\"windowId can't be 0\\n\");\r\n            return E_FAIL;\r\n        }\r\n\r\n        tmpBuf[0] = L'\\0';\r\n        if (associateWindowId.appGroupLength)\r\n        {\r\n            if ((wcscpy_s(tmpBuf, ARRAYSIZE(tmpBuf), associateWindowId.appGroup) != 0) ||\r\n                (wcscat_s(tmpBuf, ARRAYSIZE(tmpBuf), L\"\\\\\") != 0))\r\n            {\r\n                return E_FAIL;\r\n            }\r\n        }\r\n        if (wcscat_s(tmpBuf, ARRAYSIZE(tmpBuf), associateWindowId.appId) != 0)\r\n        {\r\n            return E_FAIL;\r\n        }\r\n\r\n        DebugPrint(L\"AssociateWindowId AppId: %s\\n\", tmpBuf);\r\n        DebugPrint(L\"    Desc: %s\\n\", associateWindowId.appDesc);\r\n        DebugPrint(L\"    Full Unique WindowId: 0x%p\\n\", windowId); /* somehow PRIx64 doesn't work here */\r\n        DebugPrint(L\"    CmdLine from server: %s\\n\", associateWindowId.appExecPath);\r\n\r\n        hr = m_spFileDB->FindFiles(tmpBuf,\r\n            NULL, 0,\r\n            iconPath, ARRAYSIZE(iconPath),\r\n            tmpPath, ARRAYSIZE(tmpPath),\r\n            tmpArgs, ARRAYSIZE(tmpArgs));\r\n        if (SUCCEEDED(hr))\r\n        {\r\n            if ((swprintf_s(exePath, ARRAYSIZE(exePath), L\"%s %s\",\r\n                tmpPath, tmpArgs) < 0) ||\r\n                (exePath[0] == L'\\0'))\r\n            {\r\n                return E_FAIL;\r\n            }\r\n        }\r\n        else if (associateWindowId.appExecPath[0] != '\\0')\r\n        {\r\n            /* if key is not present, reconstruct using server side exe path */\r\n            if ((swprintf_s(exePath, ARRAYSIZE(exePath), L\"%s -d %s --cd \\\"%s\\\" -- %s\",\r\n                m_expandedPathObj,\r\n                m_appProvider,\r\n                L\"~\",\r\n                associateWindowId.appExecPath) < 0) ||\r\n                (exePath[0] == L'\\0'))\r\n            {\r\n                return E_FAIL;\r\n            }\r\n\r\n            /* TODO: must provide default icon */\r\n        }\r\n\r\n        DebugPrint(L\"    CmdLine at local: %s\\n\", exePath);\r\n        DebugPrint(L\"    Icon Path at local: %s\\n\", iconPath);\r\n \r\n        WslgWindowData data = {};\r\n        data.windowId = windowId;\r\n        data.displayName = associateWindowId.appDesc[0] != '\\0' ? associateWindowId.appDesc : NULL;\r\n        data.relaunchCommandline = exePath[0] != '\\0' ? exePath : NULL;      \r\n        data.iconPath = iconPath[0] != '\\0' ? iconPath : NULL;\r\n\r\n        EnumWindows(FindWslgWindow, (LPARAM)&data);\r\n\r\n        return S_OK;\r\n    }\r\n\r\n    //\r\n    // IWTSVirtualChannelCallback interface\r\n    //\r\n\r\n    STDMETHODIMP \r\n        OnDataReceived(\r\n            ULONG cbSize,\r\n            __RPC__in_ecount_full(cbSize) BYTE* pBuffer\r\n        )\r\n    {\r\n        HRESULT hr = S_OK;\r\n        const BYTE* cur = pBuffer;\r\n        UINT64 len = cbSize;\r\n\r\n        if (m_bChannelClosed)\r\n        {\r\n            return S_OK;\r\n        }\r\n\r\n        EnterCriticalSection(&m_crit);\r\n        DebugPrint(L\"OnDataReceived enter, size = %d\\n\", len);\r\n\r\n        while (len && !m_bChannelClosed)\r\n        {\r\n            RDPAPPLIST_HEADER appListHeader = {};\r\n\r\n            hr = ReadAppListHeader(&len, &cur, &appListHeader);\r\n            if (FAILED(hr))\r\n            {\r\n                DebugPrint(L\"Failed to read applist header\\n\");\r\n                break;\r\n            }\r\n\r\n            if (appListHeader.cmdId == RDPAPPLIST_CMDID_CAPS)\r\n            {\r\n                hr = OnCaps(&len, &cur);\r\n                if (FAILED(hr))\r\n                {\r\n                    break;\r\n                }\r\n            }\r\n            else if (m_handShakeComplated == false)\r\n            {\r\n                // Caps exchange must be completed before processing\r\n                // any other messages.  \r\n                DebugPrint(L\"HandShake is not completed\\n\");\r\n                hr = E_FAIL;\r\n                break;\r\n            }\r\n            else if (appListHeader.cmdId == RDPAPPLIST_CMDID_UPDATE_APPLIST)\r\n            {\r\n                hr = OnUpdateAppList(&len, &cur);\r\n                if (FAILED(hr))\r\n                {\r\n                    break;\r\n                }\r\n            }\r\n            else if (appListHeader.cmdId == RDPAPPLIST_CMDID_DELETE_APPLIST)\r\n            {\r\n                hr = OnDeleteAppList(&len, &cur);\r\n                if (FAILED(hr))\r\n                {\r\n                    break;\r\n                }\r\n            }\r\n            else if (appListHeader.cmdId == RDPAPPLIST_CMDID_DELETE_APPLIST_PROVIDER)\r\n            {\r\n                // Nothing to do.\r\n            }\r\n            else if (appListHeader.cmdId == RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID)\r\n            {\r\n                hr = OnAssociateWindowId(&len, &cur);\r\n                if (FAILED(hr))\r\n                {\r\n                    break;\r\n                }\r\n            }\r\n            else\r\n            {\r\n                DebugPrint(L\"Unknown command id:%d, Length %d\\n\", appListHeader.cmdId, appListHeader.length);\r\n                hr = E_FAIL;\r\n                break;\r\n            }\r\n\r\n            assert(len <= cbSize);\r\n        }\r\n\r\n        DebugPrint(L\"OnDataReceived returns hr = %x\\n\", hr);\r\n        LeaveCriticalSection(&m_crit);\r\n\r\n        return hr;\r\n    }\r\n\r\n    STDMETHODIMP \r\n        OnClose()\r\n    {\r\n        m_bChannelClosed = true;\r\n\r\n        EnterCriticalSection(&m_crit);\r\n        DebugPrint(L\"OnClose enter\\n\");\r\n\r\n        // Make sure sync mode is cancelled.\r\n        OnSyncEnd(false);\r\n\r\n        m_spFileDB->OnClose();\r\n        m_spFileDB = nullptr;\r\n\r\n        DebugPrint(L\"OnClose returns hr = %x\\n\", S_OK);\r\n        LeaveCriticalSection(&m_crit);\r\n\r\n        return S_OK;\r\n    }\r\n\r\nprotected:\r\n\r\n    virtual \r\n        ~WSLDVCCallback() \r\n    {\r\n        if (m_bCriticalSectionInitialized)\r\n        {\r\n            DeleteCriticalSection(&m_crit);\r\n        }\r\n    }\r\n\r\nprivate:\r\n        \r\n    ComPtr<IWTSVirtualChannel> m_spChannel;\r\n\r\n    ComPtr<IWSLDVCFileDB> m_spFileDB;\r\n    ComPtr<IWSLDVCFileDB> m_spFileDBSync; // valid only during sync.\r\n\r\n    CRITICAL_SECTION m_crit = {};\r\n    bool m_bCriticalSectionInitialized = false;\r\n\r\n    bool m_bChannelClosed = false;\r\n    bool m_handShakeComplated = false;\r\n\r\n    RDPAPPLIST_SERVER_CAPS_PDU m_serverCaps = {};\r\n    GUID m_appProviderGUID = {};\r\n    WCHAR m_appProvider[MAX_PATH] = {};\r\n    WCHAR m_appMenuPath[MAX_PATH] = {};\r\n    WCHAR m_iconPath[MAX_PATH] = {};\r\n    WCHAR m_expandedPathObj[MAX_PATH] = {};\r\n    WCHAR m_expandedWorkingDir[MAX_PATH] = {};\r\n    CHAR m_clientLanguageId[RDPAPPLIST_LANG_SIZE] = {};\r\n};\r\n\r\nHRESULT\r\n    WSLDVCCallback_CreateInstance(\r\n        IWTSVirtualChannel* pChannel,\r\n        IWTSVirtualChannelCallback** ppCallback\r\n    )\r\n{\r\n    return MakeAndInitialize<WSLDVCCallback>(ppCallback, pChannel);\r\n}\r\n"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCCallback.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#pragma once\r\n\r\n#include <tsvirtualchannels.h>\r\n\r\nHRESULT\r\nWSLDVCCallback_CreateInstance(\r\n    IWTSVirtualChannel* pChannel,\r\n    IWTSVirtualChannelCallback** ppCallback\r\n);"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCFileDB.cpp",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include \"pch.h\"\r\n#include \"utils.h\"\r\n#include \"WSLDVCFileDB.h\"\r\n\r\n#include <string>\r\n#include <set>\r\n\r\nclass fileEntry\r\n{\r\npublic:\r\n\r\n    wstring getFileId() const\r\n    {\r\n        return m_fileId;\r\n    }\r\n    wstring getLinkFilePath() const\r\n    {\r\n        return m_linkFilePath;\r\n    }\r\n    wstring getIconFilePath() const\r\n    {\r\n        return m_iconFilePath;\r\n    }\r\n    wstring getExeFilePath() const\r\n    {\r\n        return m_exeFilePath;\r\n    }\r\n    wstring getExeFileArgs() const\r\n    {\r\n        return m_exeFileArgs;\r\n    }\r\n\r\n    //fileEntry(const fileEntry& r)\r\n    //{\r\n    //    m_fileId = r.m_fileId;\r\n    //    m_linkFilePath = r.m_linkFilePath;\r\n    //    m_iconFilePath = r.m_iconFilePath;\r\n    //    m_exeFilePath = r.m_exeFilePath;\r\n    //    m_exeFileArgs = r.m_exeFileArgs;\r\n    //}\r\n    fileEntry(const wchar_t* fileId,\r\n        const wchar_t* linkPath = L\"\",\r\n        const wchar_t* iconPath = L\"\",\r\n        const wchar_t* exePath = L\"\",\r\n        const wchar_t* exeArgs = L\"\")\r\n    {\r\n        assert(fileId);\r\n        m_fileId = fileId;\r\n        m_linkFilePath = linkPath;\r\n        m_iconFilePath = iconPath;\r\n        m_exeFilePath = exePath;\r\n        m_exeFileArgs = exeArgs;\r\n    }\r\n\r\n    bool operator< (LPWSTR key) const\r\n    {\r\n        wstring r = key;\r\n        return getFileId() < r;\r\n    }\r\n    bool operator< (const fileEntry& r) const\r\n    {\r\n        return getFileId() < r.getFileId();\r\n    }\r\n\r\nprivate:\r\n\r\n    wstring m_fileId;\r\n    wstring m_linkFilePath;\r\n    wstring m_iconFilePath;\r\n    wstring m_exeFilePath;\r\n    wstring m_exeFileArgs;\r\n};\r\n\r\nclass WSLDVCFileDB :\r\n    public RuntimeClass<\r\n    RuntimeClassFlags<ClassicCom>,\r\n    IWSLDVCFileDB>\r\n{\r\npublic:\r\n\r\n    HRESULT\r\n        RuntimeClassInitialize(\r\n            void* pContext\r\n        )\r\n    {\r\n        UNREFERENCED_PARAMETER(pContext);\r\n        DebugPrint(L\"RuntimeClassInitialize(): WSLDVCFileDB iniitalized: %p\\n\", this);\r\n        return S_OK;\r\n    }\r\n\r\n    STDMETHODIMP\r\n        OnLnkFileAdded(_In_z_ LPCWSTR lnkFile)\r\n    {\r\n        WCHAR iconFile[MAX_PATH] = {};\r\n\r\n        {\r\n            fileEntry fileEntry(lnkFile);\r\n            m_fileEntries.insert(fileEntry);\r\n        }\r\n\r\n        if (SUCCEEDED(GetIconFileFromShellLink(ARRAYSIZE(iconFile), iconFile, lnkFile)))\r\n        {\r\n            fileEntry fileEntry(iconFile);\r\n            m_fileEntries.insert(fileEntry);\r\n        }\r\n\r\n        DebugPrint(L\"OnLnkFileAdded():\\n\");\r\n        DebugPrint(L\"\\tlnkFile: %s\\n\", lnkFile);\r\n        DebugPrint(L\"\\ticonFile: %s\\n\", iconFile);\r\n\r\n        return S_OK;\r\n    }\r\n\r\n    STDMETHODIMP\r\n        OnFileAdded(_In_z_ LPCWSTR key,\r\n            _In_opt_z_ LPCWSTR linkFilePath,\r\n            _In_opt_z_ LPCWSTR iconFilePath,\r\n            _In_opt_z_ LPCWSTR exeFilePath,\r\n            _In_opt_z_ LPCWSTR exeFileArgs)\r\n    {\r\n        HRESULT hr = S_OK;\r\n        set<fileEntry>::iterator it;\r\n        std::pair<set<fileEntry>::iterator, bool> p;\r\n\r\n        DebugPrint(L\"OnFileAdded():\\n\");\r\n        DebugPrint(L\"\\tkey: %s\\n\", key);\r\n        if (linkFilePath && lstrlenW(linkFilePath))\r\n        {\r\n            DebugPrint(L\"\\tlinkFilePath: %s\\n\", linkFilePath);\r\n        }\r\n        else\r\n        {\r\n            linkFilePath = L\"\";\r\n        }\r\n        if (iconFilePath && lstrlenW(iconFilePath))\r\n        {\r\n            DebugPrint(L\"\\ticonFilePath: %s\\n\", iconFilePath);\r\n        }\r\n        else\r\n        {\r\n            iconFilePath = L\"\";\r\n        }\r\n        if (exeFilePath && lstrlenW(exeFilePath))\r\n        {\r\n            DebugPrint(L\"\\texeFilePath: %s\\n\", exeFilePath);\r\n        }\r\n        else\r\n        {\r\n            exeFilePath = L\"\";\r\n        }\r\n        if (exeFileArgs && lstrlenW(exeFileArgs))\r\n        {\r\n            DebugPrint(L\"\\texeFileArgs: %s\\n\", exeFileArgs);\r\n        }\r\n        else\r\n        {\r\n            exeFileArgs = L\"\";\r\n        }\r\n\r\n        fileEntry fileEntry(key, linkFilePath, iconFilePath, exeFilePath, exeFileArgs);\r\n        p = m_fileEntries.insert(fileEntry);\r\n        if (!p.second)\r\n        {\r\n            if (p.first->getLinkFilePath().compare(linkFilePath) != 0 ||\r\n                p.first->getIconFilePath().compare(iconFilePath) != 0 ||\r\n                p.first->getExeFilePath().compare(exeFilePath) != 0 ||\r\n                p.first->getExeFileArgs().compare(exeFileArgs) != 0)\r\n            {\r\n                DebugPrint(L\"\\tKey: %s is already exists and has different path data\\n\", key);\r\n                DebugPrint(L\"\\tlinkFilePath: %s\\n\", p.first->getLinkFilePath().c_str());\r\n                DebugPrint(L\"\\ticonFilePath: %s\\n\", p.first->getIconFilePath().c_str());\r\n                DebugPrint(L\"\\texeFilePath: %s\\n\", p.first->getExeFilePath().c_str());\r\n                DebugPrint(L\"\\texeFileArgs: %s\\n\", p.first->getExeFileArgs().c_str());\r\n                // TODO: implement update existing by erase and add.         \r\n                // DebugAssert(false);\r\n                hr = E_FAIL;\r\n            }\r\n        }\r\n\r\n        return hr;\r\n    }\r\n\r\n    STDMETHODIMP\r\n        OnFileRemoved(_In_z_ LPCWSTR key)\r\n    {\r\n        HRESULT hr = S_OK;\r\n        set<fileEntry>::iterator it;\r\n\r\n        DebugPrint(L\"OnFileRemoved()\\n\");\r\n        DebugPrint(L\"\\tkey: %s\\n\", key);\r\n\r\n        it = m_fileEntries.find(key);\r\n        if (it != m_fileEntries.end())\r\n        {\r\n            DebugPrint(L\"\\tKey found:\\n\");\r\n            if (it->getLinkFilePath().length())\r\n            {\r\n                DebugPrint(L\"\\tlinkPath: %s\\n\", it->getLinkFilePath().c_str());\r\n            }\r\n            if (it->getIconFilePath().length())\r\n            {\r\n                DebugPrint(L\"\\ticonPath: %s\\n\", it->getIconFilePath().c_str());\r\n            }\r\n            if (it->getExeFilePath().length())\r\n            {\r\n                DebugPrint(L\"\\texePath: %s\\n\", it->getExeFilePath().c_str());\r\n            }\r\n            if (it->getExeFileArgs().length())\r\n            {\r\n                DebugPrint(L\"\\texeArgs: %s\\n\", it->getExeFileArgs().c_str());\r\n            }\r\n            m_fileEntries.erase(it);\r\n        }\r\n        else\r\n        {\r\n            DebugPrint(L\"Key not found\\n\");\r\n            hr = E_FAIL;\r\n        }\r\n\r\n        return hr;\r\n    }\r\n\r\n    STDMETHODIMP FindFiles(\r\n        _In_z_ LPCWSTR key, \r\n        _Out_writes_z_(linkFilePathSize) LPWSTR linkFilePath, UINT32 linkFilePathSize,\r\n        _Out_writes_z_(iconFilePathSize) LPWSTR iconFilePath, UINT32 iconFilePathSize,\r\n        _Out_writes_z_(exeFilePathSize) LPWSTR exeFilePath, UINT32 exeFilePathSize,\r\n        _Out_writes_z_(exeFileArgsSize) LPWSTR exeFileArgs, UINT32 exeFileArgsSize)\r\n    {\r\n        set<fileEntry>::iterator it;\r\n\r\n        assert((linkFilePath && linkFilePathSize) ||\r\n               (linkFilePath == nullptr && linkFilePathSize == 0));\r\n        assert((iconFilePath && iconFilePathSize) ||\r\n               (iconFilePath == nullptr && iconFilePathSize == 0));\r\n        assert((exeFilePath && exeFilePathSize) ||\r\n               (exeFilePath == nullptr && exeFilePathSize == 0));\r\n        assert((exeFileArgs && exeFileArgsSize) ||\r\n               (exeFileArgs == nullptr && exeFileArgsSize == 0));\r\n\r\n        if (linkFilePath) *linkFilePath = NULL;\r\n        if (iconFilePath) *iconFilePath = NULL;\r\n        if (exeFilePath) *exeFilePath = NULL;\r\n        if (exeFileArgs) *exeFileArgs = NULL;\r\n\r\n        DebugPrint(L\"FindFiles()\\n\");\r\n        DebugPrint(L\"\\tkey: %s\\n\", key);\r\n\r\n        it = m_fileEntries.find(key);\r\n        if (it != m_fileEntries.end())\r\n        {\r\n            DebugPrint(L\"\\tKey found:\\n\");\r\n            DebugPrint(L\"\\tlinkPath: %s\\n\", it->getLinkFilePath().c_str());\r\n            DebugPrint(L\"\\ticonPath: %s\\n\", it->getIconFilePath().c_str());\r\n            DebugPrint(L\"\\texePath: %s\\n\", it->getExeFilePath().c_str());\r\n            DebugPrint(L\"\\texeArgs: %s\\n\", it->getExeFileArgs().c_str());\r\n            if (linkFilePath)\r\n            {\r\n                if (wcscpy_s(linkFilePath, linkFilePathSize, it->getLinkFilePath().c_str()) != 0)\r\n                {\r\n                    return E_FAIL;\r\n                }\r\n            }\r\n            if (iconFilePath)\r\n            {\r\n                if (wcscpy_s(iconFilePath, iconFilePathSize, it->getIconFilePath().c_str()) != 0)\r\n                {\r\n                    return E_FAIL;\r\n                }\r\n            }\r\n            if (exeFilePath)\r\n            {\r\n                if (wcscpy_s(exeFilePath, exeFilePathSize, it->getExeFilePath().c_str()) != 0)\r\n                {\r\n                    return E_FAIL;\r\n                }\r\n            }\r\n            if (exeFileArgs)\r\n            {\r\n                if (wcscpy_s(exeFileArgs, exeFileArgsSize, it->getExeFileArgs().c_str()) != 0)\r\n                {\r\n                    return E_FAIL;\r\n                }\r\n            }\r\n            return S_OK;\r\n        }\r\n        else\r\n        {\r\n            DebugPrint(L\"\\tKey NOT found:\\n\");\r\n            return E_FAIL;\r\n        }\r\n    }\r\n\r\n    STDMETHODIMP \r\n        addAllFilesAsFileIdAt(_In_z_ LPCWSTR path)\r\n    {\r\n        //DebugPrint(L\"Dump directory: %s\\n\", path);\r\n        return addAllSubFolderFiles(path);\r\n    }\r\n\r\n    STDMETHODIMP\r\n        deleteAllFileIdFiles()\r\n    {\r\n        set<fileEntry>::iterator it;\r\n\r\n        DebugPrint(L\"deleteAllFileIdFiles() - files to delete %d\\n\", m_fileEntries.size());\r\n\r\n        for (it = m_fileEntries.begin(); it != m_fileEntries.end(); ) {\r\n            DebugPrint(L\"\\tDelete %s\\n\", it->getFileId().c_str());\r\n            if (!DeleteFileW(it->getFileId().c_str()))\r\n            {\r\n                DebugPrint(L\"DeleteFile(%s) failed\\n\", it->getFileId().c_str());\r\n            }\r\n            else\r\n            {\r\n                wstring::size_type found = it->getFileId().find_last_of(L\"\\\\\");\r\n                if (found != wstring::npos)\r\n                {\r\n                    wstring dir = it->getFileId().substr(0, found);\r\n                    if (PathIsDirectoryEmptyW(dir.c_str()))\r\n                    {\r\n                        DebugPrint(L\"%s is empty, removing\\n\", dir.c_str());\r\n                        if (!RemoveDirectory(dir.c_str()))\r\n                        {\r\n                            DebugPrint(L\"Failed to remove %s\\n\", dir.c_str());\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            it = m_fileEntries.erase(it);\r\n        }\r\n\r\n        return S_OK;\r\n    }\r\n\r\n    STDMETHODIMP\r\n        OnClose()\r\n    {\r\n        DebugPrint(L\"OnClose(): WSLDVCFileDB closed: %p\\n\", this);\r\n\r\n        m_fileEntries.clear();\r\n\r\n        return S_OK;\r\n    }\r\n\r\nprotected:\r\n\r\n    HRESULT\r\n        addAllSubFolderFiles(_In_z_ const wchar_t* path)\r\n    {\r\n        WIN32_FIND_DATA data = {};\r\n        wstring s = path;\r\n        s += L\"\\\\*\";\r\n\r\n        HANDLE hFind = FindFirstFile(s.c_str(), &data);\r\n        do {\r\n            wstring sub = path;\r\n            sub += L\"\\\\\";\r\n            sub += data.cFileName;\r\n\r\n            if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\r\n                wstring file = data.cFileName;\r\n\r\n                if (file == L\".\" || file == L\"..\")\r\n                {\r\n                    continue;\r\n                }\r\n\r\n                //DebugPrint(L\"\\tdirectory: %s\\n\", sub.c_str());\r\n                if (PathIsDirectoryEmptyW(sub.c_str()))\r\n                {\r\n                    DebugPrint(L\"%s is empty, removing\\n\", sub.c_str());\r\n                    if (!RemoveDirectory(sub.c_str()))\r\n                    {\r\n                        DebugPrint(L\"Failed to remove %s\\n\", sub.c_str());\r\n                    }\r\n                }\r\n                else\r\n                {\r\n                    addAllSubFolderFiles(sub.c_str());\r\n                }\r\n            }\r\n            else\r\n            {\r\n                LPCWSTR ext = PathFindExtensionW(data.cFileName);\r\n                if (wcscmp(ext, L\".lnk\") == 0)\r\n                {\r\n                     //DebugPrint(L\"\\t\\tlnk file: %s\\n\", sub.c_str());\r\n                     OnLnkFileAdded(sub.c_str());\r\n                }\r\n            }\r\n        } while (FindNextFile(hFind, &data));\r\n        FindClose(hFind);\r\n\r\n        return S_OK;\r\n    }\r\n\r\n    virtual\r\n        ~WSLDVCFileDB()\r\n    {\r\n    }\r\n\r\nprivate:\r\n    set<fileEntry> m_fileEntries;\r\n};\r\n\r\nHRESULT\r\nWSLDVCFileDB_CreateInstance(\r\n    void* pContext,\r\n    IWSLDVCFileDB** ppFileDB\r\n)\r\n{\r\n    return MakeAndInitialize<WSLDVCFileDB>(ppFileDB, pContext);\r\n}\r\n"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCFileDB.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#pragma once\r\n\r\nMIDL_INTERFACE(\"5802f934-1683-4e81-bb5a-7a0c29a2b1c7\")\r\nIWSLDVCFileDB : public IUnknown\r\n{\r\npublic:\r\n    virtual HRESULT STDMETHODCALLTYPE addAllFilesAsFileIdAt(\r\n        _In_z_ LPCWSTR path) = 0;\r\n    virtual HRESULT STDMETHODCALLTYPE deleteAllFileIdFiles() = 0;\r\n\r\n    virtual HRESULT STDMETHODCALLTYPE OnFileAdded(\r\n        _In_z_ LPCWSTR key, \r\n        _In_opt_z_ LPCWSTR linkFilePath, \r\n        _In_opt_z_ LPCWSTR iconFilePath,\r\n        _In_opt_z_ LPCWSTR exeFilePath,\r\n        _In_opt_z_ LPCWSTR exeFileArgs) = 0;\r\n    virtual HRESULT STDMETHODCALLTYPE OnFileRemoved(\r\n        _In_z_ LPCWSTR key) = 0;\r\n\r\n    virtual HRESULT STDMETHODCALLTYPE FindFiles(\r\n        _In_z_ LPCWSTR key,\r\n        _Out_writes_z_(linkFilePathSize) LPWSTR linkFilePath, UINT32 linkFilePathSize,\r\n        _Out_writes_z_(iconFilePathSize) LPWSTR iconFilePath, UINT32 iconFilePathSize,\r\n        _Out_writes_z_(exeFilePathSize) LPWSTR exeFilePath = nullptr, UINT32 exeFilePathSize = 0,\r\n        _Out_writes_z_(exeFileArgsSize) LPWSTR exeFileArgs = nullptr, UINT32 exeFileArgsSize = 0) = 0;\r\n\r\n    virtual HRESULT STDMETHODCALLTYPE OnClose(void) = 0;\r\n};\r\n\r\nHRESULT\r\nWSLDVCFileDB_CreateInstance(\r\n    void* pContext,\r\n    IWSLDVCFileDB** ppFileDB\r\n);\r\n"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCListenerCallback.cpp",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include \"pch.h\"\r\n#include \"WSLDVCListenerCallback.h\"\r\n#include \"WSLDVCCallback.h\"\r\n\r\nclass WSLDVCListenerCallback :\r\n    public RuntimeClass<\r\n    RuntimeClassFlags<ClassicCom>,\r\n    IWTSListenerCallback>\r\n{\r\npublic:\r\n\r\n    HRESULT\r\n        RuntimeClassInitialize()\r\n    {\r\n        return S_OK;\r\n    }\r\n\r\n    //\r\n    // IWTSListenerCallback interface\r\n    //\r\n    STDMETHODIMP\r\n        OnNewChannelConnection(\r\n            __RPC__in_opt IWTSVirtualChannel* pChannel,\r\n            __RPC__in_opt BSTR data,\r\n            __RPC__out BOOL* pbAccept,\r\n            __RPC__deref_out_opt IWTSVirtualChannelCallback** ppCallback\r\n        )\r\n    {\r\n        UNREFERENCED_PARAMETER(data);\r\n\r\n        HRESULT hr = WSLDVCCallback_CreateInstance(pChannel, ppCallback);\r\n        if (SUCCEEDED(hr))\r\n        {\r\n            *pbAccept = TRUE;\r\n        }\r\n\r\n        return hr;\r\n    }\r\n\r\nprotected:\r\n\r\n    virtual\r\n        ~WSLDVCListenerCallback()\r\n    {\r\n\r\n    }\r\n};\r\n\r\nHRESULT\r\nWSLDVCListenerCallback_CreateInstance(\r\n    IWTSListenerCallback** ppCallback\r\n)\r\n{\r\n    return MakeAndInitialize<WSLDVCListenerCallback>(ppCallback);\r\n}\r\n"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCListenerCallback.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#pragma once\r\n\r\n#include <tsvirtualchannels.h>\r\n\r\nHRESULT\r\nWSLDVCListenerCallback_CreateInstance(\r\n    IWTSListenerCallback** ppCallback\r\n);"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCPlugin.cpp",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include \"pch.h\"\r\n#include \"WSLDVCPlugin.h\"\r\n#include \"WSLDVCListenerCallback.h\"\r\n\r\n//\r\n// Using Windows Runtime C++ Template Library(WRL) to implement COM objects.\r\n// See:\r\n//    https://docs.microsoft.com/en-us/cpp/cppcx/wrl/how-to-instantiate-wrl-components-directly?view=vs-2019\r\n//    https://docs.microsoft.com/en-us/cpp/cppcx/wrl/how-to-create-a-classic-com-component-using-wrl?view=vs-2019\r\n//\r\n\r\nclass WSLDVCPlugin :\r\n    public RuntimeClass<\r\n    RuntimeClassFlags<ClassicCom>,\r\n    IWTSPlugin>\r\n{\r\npublic:\r\n\r\n    HRESULT\r\n        RuntimeClassInitialize()\r\n    {\r\n        return S_OK;\r\n    }\r\n\r\n    //\r\n    // IWTSPlugin interface\r\n    //\r\n    STDMETHODIMP\r\n        Initialize(\r\n            __RPC__in_opt IWTSVirtualChannelManager* pChannelMgr\r\n        )\r\n    {\r\n        HRESULT hr = S_OK;\r\n        ComPtr<IWTSListenerCallback> spListenerCallback;\r\n        ComPtr<IWTSListener> spListener;\r\n\r\n        hr = WSLDVCListenerCallback_CreateInstance(&spListenerCallback);\r\n        if (SUCCEEDED(hr))\r\n        {\r\n            hr = pChannelMgr->CreateListener(DVC_NAME, 0, spListenerCallback.Get(), &spListener);\r\n        }\r\n\r\n        return hr;\r\n    }\r\n\r\n    STDMETHODIMP\r\n        Connected()\r\n    {\r\n        return S_OK;\r\n    }\r\n\r\n    STDMETHODIMP\r\n        Disconnected(\r\n            DWORD dwDisconnectCode\r\n        )\r\n    {\r\n        UNREFERENCED_PARAMETER(dwDisconnectCode);\r\n        return S_OK;\r\n    }\r\n\r\n    STDMETHODIMP\r\n        Terminated()\r\n    {\r\n        return S_OK;\r\n    }\r\n\r\nprotected:\r\n\r\n    virtual\r\n        ~WSLDVCPlugin()\r\n    {\r\n    }\r\n\r\nprivate:\r\n\r\n    const char* DVC_NAME = \"Microsoft::Windows::RDS::RemoteApplicationList\";\r\n};\r\n\r\nHRESULT\r\nWSLDVCPlugin_CreateInstance(\r\n    IWTSPlugin** ppPlugin\r\n)\r\n{\r\n    return MakeAndInitialize<WSLDVCPlugin>(ppPlugin);\r\n}\r\n"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCPlugin.def",
    "content": "LIBRARY \"WSLDVCPlugin.dll\"\r\n\r\nEXPORTS\r\n    VirtualChannelGetInstance PRIVATE"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCPlugin.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#pragma once\r\n\r\n#include <tsvirtualchannels.h>\r\n\r\nHRESULT\r\nWSLDVCPlugin_CreateInstance(\r\n    IWTSPlugin** ppPlugin\r\n);\r\n"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCPlugin.rc",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include \"resource.h\"\r\n\r\n#define APSTUDIO_READONLY_SYMBOLS\r\n/////////////////////////////////////////////////////////////////////////////\r\n//\r\n// Generated from the TEXTINCLUDE 2 resource.\r\n//\r\n#include \"winres.h\"\r\n\r\n/////////////////////////////////////////////////////////////////////////////\r\n#undef APSTUDIO_READONLY_SYMBOLS\r\n\r\n/////////////////////////////////////////////////////////////////////////////\r\n// English (United States) resources\r\n\r\n#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\nLANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US\r\n#pragma code_page(1252)\r\n\r\n#ifdef APSTUDIO_INVOKED\r\n/////////////////////////////////////////////////////////////////////////////\r\n//\r\n// TEXTINCLUDE\r\n//\r\n\r\n1 TEXTINCLUDE \r\nBEGIN\r\n    \"resource.h\\0\"\r\nEND\r\n\r\n2 TEXTINCLUDE \r\nBEGIN\r\n    \"#include \"\"winres.h\"\"\\r\\n\"\r\n    \"\\0\"\r\nEND\r\n\r\n3 TEXTINCLUDE \r\nBEGIN\r\n    \"\\r\\n\"\r\n    \"\\0\"\r\nEND\r\n\r\n#endif    // APSTUDIO_INVOKED\r\n\r\n\r\n/////////////////////////////////////////////////////////////////////////////\r\n//\r\n// Version\r\n//\r\n\r\nVS_VERSION_INFO VERSIONINFO\r\n FILEVERSION 1,0,0,1\r\n PRODUCTVERSION 1,0,0,1\r\n FILEFLAGSMASK 0x3fL\r\n#ifdef _DEBUG\r\n FILEFLAGS 0x1L\r\n#else\r\n FILEFLAGS 0x0L\r\n#endif\r\n FILEOS 0x40004L\r\n FILETYPE 0x2L\r\n FILESUBTYPE 0x0L\r\nBEGIN\r\n    BLOCK \"StringFileInfo\"\r\n    BEGIN\r\n        BLOCK \"040904b0\"\r\n        BEGIN\r\n            VALUE \"CompanyName\", \" Microsoft Corporation. All rights reserved\"\r\n            VALUE \"FileDescription\", \"WSL Remote Application List Plug-in\"\r\n            VALUE \"FileVersion\", \"1.0.0.1\"\r\n            VALUE \"InternalName\", \"WSLDVCPl.dll\"\r\n            VALUE \"LegalCopyright\", \" Microsoft Corporation. All rights reserved\"\r\n            VALUE \"OriginalFilename\", \"WSLDVCPl.dll\"\r\n            VALUE \"ProductName\", \"WSL Remote Application List Plug-in\"\r\n            VALUE \"ProductVersion\", \"InformationalVersion\"\r\n        END\r\n    END\r\n    BLOCK \"VarFileInfo\"\r\n    BEGIN\r\n        VALUE \"Translation\", 0x409, 1200\r\n    END\r\nEND\r\n\r\n#endif    // English (United States) resources\r\n/////////////////////////////////////////////////////////////////////////////\r\n\r\n\r\n\r\n#ifndef APSTUDIO_INVOKED\r\n/////////////////////////////////////////////////////////////////////////////\r\n//\r\n// Generated from the TEXTINCLUDE 3 resource.\r\n//\r\n\r\n\r\n/////////////////////////////////////////////////////////////////////////////\r\n#endif    // not APSTUDIO_INVOKED\r\n\r\n"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCPlugin.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 16\r\nVisualStudioVersion = 16.0.30413.136\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"WSLDVCPlugin\", \"WSLDVCPlugin.vcxproj\", \"{B7E66936-5220-4D77-AA10-C26A7F6F42D6}\"\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|ARM64 = Debug|ARM64\r\n\t\tDebug|x64 = Debug|x64\r\n\t\tDebug|x86 = Debug|x86\r\n\t\tRelease|ARM64 = Release|ARM64\r\n\t\tRelease|x64 = Release|x64\r\n\t\tRelease|x86 = Release|x86\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|ARM64.ActiveCfg = Debug|ARM64\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|ARM64.Build.0 = Debug|ARM64\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|ARM64.ActiveCfg = Release|ARM64\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|ARM64.Build.0 = Release|ARM64\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x64.Build.0 = Release|x64\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x86.Build.0 = Release|Win32\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {EB2D28FB-D37D-4286-A386-7723B34EF53F}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCPlugin.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup Label=\"ProjectConfigurations\">\r\n    <ProjectConfiguration Include=\"Debug|ARM64\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>ARM64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Debug|Win32\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|ARM64\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>ARM64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|Win32\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Debug|x64\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|x64\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n  </ItemGroup>\r\n  <PropertyGroup Label=\"Globals\">\r\n    <VCProjectVersion>16.0</VCProjectVersion>\r\n    <Keyword>Win32Proj</Keyword>\r\n    <ProjectGuid>{b7e66936-5220-4d77-aa10-c26a7f6f42d6}</ProjectGuid>\r\n    <RootNamespace>WSLDVCPlugin</RootNamespace>\r\n    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v143</PlatformToolset>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n    <SpectreMitigation>Spectre</SpectreMitigation>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v143</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n    <SpectreMitigation>Spectre</SpectreMitigation>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v143</PlatformToolset>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n    <SpectreMitigation>Spectre</SpectreMitigation>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v143</PlatformToolset>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n    <SpectreMitigation>Spectre</SpectreMitigation>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v143</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n    <SpectreMitigation>Spectre</SpectreMitigation>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v143</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n    <SpectreMitigation>Spectre</SpectreMitigation>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\r\n  <ImportGroup Label=\"ExtensionSettings\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"Shared\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\" Label=\"PropertySheets\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\" Label=\"PropertySheets\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <PropertyGroup Label=\"UserMacros\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <LinkIncremental>true</LinkIncremental>\r\n    <TargetName>WSLDVCPlugin</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <TargetName>WSLDVCPlugin</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <LinkIncremental>true</LinkIncremental>\r\n    <TargetName>WSLDVCPlugin</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\r\n    <LinkIncremental>true</LinkIncremental>\r\n    <TargetName>WSLDVCPlugin</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <TargetName>WSLDVCPlugin</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <TargetName>WSLDVCPlugin</TargetName>\r\n  </PropertyGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <SDLCheck>true</SDLCheck>\r\n      <PreprocessorDefinitions>WIN32;_DEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PrecompiledHeader>Use</PrecompiledHeader>\r\n      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r\n      <ControlFlowGuard>Guard</ControlFlowGuard>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <EnableUAC>false</EnableUAC>\r\n      <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <SDLCheck>true</SDLCheck>\r\n      <PreprocessorDefinitions>WIN32;NDEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PrecompiledHeader>Use</PrecompiledHeader>\r\n      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r\n      <ControlFlowGuard>Guard</ControlFlowGuard>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <EnableUAC>false</EnableUAC>\r\n      <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level4</WarningLevel>\r\n      <SDLCheck>true</SDLCheck>\r\n      <PreprocessorDefinitions>_DEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PrecompiledHeader>Use</PrecompiledHeader>\r\n      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r\n      <TreatWarningAsError>true</TreatWarningAsError>\r\n      <ControlFlowGuard>Guard</ControlFlowGuard>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <GenerateDebugInformation>DebugFull</GenerateDebugInformation>\r\n      <EnableUAC>false</EnableUAC>\r\n      <StripPrivateSymbols>\r\n      </StripPrivateSymbols>\r\n      <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level4</WarningLevel>\r\n      <SDLCheck>true</SDLCheck>\r\n      <PreprocessorDefinitions>_DEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PrecompiledHeader>Use</PrecompiledHeader>\r\n      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r\n      <TreatWarningAsError>true</TreatWarningAsError>\r\n      <ControlFlowGuard>Guard</ControlFlowGuard>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <GenerateDebugInformation>DebugFull</GenerateDebugInformation>\r\n      <EnableUAC>false</EnableUAC>\r\n      <StripPrivateSymbols>\r\n      </StripPrivateSymbols>\r\n      <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level4</WarningLevel>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <SDLCheck>true</SDLCheck>\r\n      <PreprocessorDefinitions>NDEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PrecompiledHeader>Use</PrecompiledHeader>\r\n      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r\n      <TreatWarningAsError>true</TreatWarningAsError>\r\n      <ControlFlowGuard>Guard</ControlFlowGuard>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <GenerateDebugInformation>DebugFull</GenerateDebugInformation>\r\n      <EnableUAC>false</EnableUAC>\r\n      <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level4</WarningLevel>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <SDLCheck>true</SDLCheck>\r\n      <PreprocessorDefinitions>NDEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PrecompiledHeader>Use</PrecompiledHeader>\r\n      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>\r\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r\n      <TreatWarningAsError>true</TreatWarningAsError>\r\n      <ControlFlowGuard>Guard</ControlFlowGuard>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <GenerateDebugInformation>DebugFull</GenerateDebugInformation>\r\n      <EnableUAC>false</EnableUAC>\r\n      <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemGroup>\r\n    <None Include=\"cpp.hint\" />\r\n    <None Include=\"WSLDVCPlugin.def\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClInclude Include=\"framework.h\" />\r\n    <ClInclude Include=\"rdpapplist.h\" />\r\n    <ClInclude Include=\"resource.h\" />\r\n    <ClInclude Include=\"utils.h\" />\r\n    <ClInclude Include=\"pch.h\" />\r\n    <ClInclude Include=\"WSLDVCCallback.h\" />\r\n    <ClInclude Include=\"WSLDVCFileDB.h\" />\r\n    <ClInclude Include=\"WSLDVCListenerCallback.h\" />\r\n    <ClInclude Include=\"WSLDVCPlugin.h\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"dllmain.cpp\" />\r\n    <ClCompile Include=\"pch.cpp\">\r\n      <PrecompiledHeader Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">Create</PrecompiledHeader>\r\n      <PrecompiledHeader Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">Create</PrecompiledHeader>\r\n      <PrecompiledHeader Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">Create</PrecompiledHeader>\r\n      <PrecompiledHeader Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">Create</PrecompiledHeader>\r\n      <PrecompiledHeader Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">Create</PrecompiledHeader>\r\n      <PrecompiledHeader Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">Create</PrecompiledHeader>\r\n    </ClCompile>\r\n    <ClCompile Include=\"utils.cpp\" />\r\n    <ClCompile Include=\"WSLDVCCallback.cpp\" />\r\n    <ClCompile Include=\"WSLDVCFileDB.cpp\" />\r\n    <ClCompile Include=\"WSLDVCListenerCallback.cpp\" />\r\n    <ClCompile Include=\"WSLDVCPlugin.cpp\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ResourceCompile Include=\"WSLDVCPlugin.rc\" />\r\n  </ItemGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\r\n  <ImportGroup Label=\"ExtensionTargets\">\r\n  </ImportGroup>\r\n</Project>"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCPlugin.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup>\r\n    <Filter Include=\"Source Files\">\r\n      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r\n      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r\n    </Filter>\r\n    <Filter Include=\"Header Files\">\r\n      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\r\n      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>\r\n    </Filter>\r\n    <Filter Include=\"Resource Files\">\r\n      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r\n      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\r\n    </Filter>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <None Include=\"cpp.hint\" />\r\n    <None Include=\"WSLDVCPlugin.def\">\r\n      <Filter>Source Files</Filter>\r\n    </None>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClInclude Include=\"framework.h\">\r\n      <Filter>Header Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"WSLDVCPlugin.h\">\r\n      <Filter>Header Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"pch.h\">\r\n      <Filter>Header Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"WSLDVCListenerCallback.h\">\r\n      <Filter>Header Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"WSLDVCCallback.h\">\r\n      <Filter>Header Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"utils.h\">\r\n      <Filter>Header Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"rdpapplist.h\">\r\n      <Filter>Header Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"resource.h\">\r\n      <Filter>Header Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"WSLDVCFileDB.h\">\r\n      <Filter>Header Files</Filter>\r\n    </ClInclude>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"WSLDVCPlugin.cpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n    <ClCompile Include=\"dllmain.cpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n    <ClCompile Include=\"pch.cpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n    <ClCompile Include=\"WSLDVCListenerCallback.cpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n    <ClCompile Include=\"WSLDVCCallback.cpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n    <ClCompile Include=\"utils.cpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n    <ClCompile Include=\"WSLDVCFileDB.cpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ResourceCompile Include=\"WSLDVCPlugin.rc\">\r\n      <Filter>Resource Files</Filter>\r\n    </ResourceCompile>\r\n  </ItemGroup>\r\n</Project>"
  },
  {
    "path": "WSLDVCPlugin/WSLDVCPlugin.vcxproj.user",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project ToolsVersion=\"Current\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <PropertyGroup />\r\n</Project>"
  },
  {
    "path": "WSLDVCPlugin/cpp.hint",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#define WSLDVCPLUGIN_API __declspec(dllexport)\r\n#define WSLDVCPLUGIN_API __declspec(dllimport)\r\n"
  },
  {
    "path": "WSLDVCPlugin/dllmain.cpp",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include \"pch.h\"\r\n#include \"utils.h\"\r\n#include \"WSLDVCPlugin.h\"\r\n#include \"WSLDVCFileDB.h\"\r\n#include <cchannel.h>\r\n\r\nBOOL APIENTRY DllMain( HMODULE hModule,\r\n                       DWORD  ul_reason_for_call,\r\n                       LPVOID lpReserved\r\n                     )\r\n{\r\n    UNREFERENCED_PARAMETER(hModule);\r\n    UNREFERENCED_PARAMETER(lpReserved);\r\n\r\n    switch (ul_reason_for_call)\r\n    {\r\n    case DLL_PROCESS_ATTACH:\r\n    case DLL_THREAD_ATTACH:\r\n    case DLL_THREAD_DETACH:\r\n    case DLL_PROCESS_DETACH:\r\n        break;\r\n    }\r\n    return TRUE;\r\n}\r\n\r\nextern \"C\"\r\n{\r\n    __declspec(dllexport) HRESULT VCAPITYPE\r\n        VirtualChannelGetInstance(\r\n            _In_ REFIID refiid,\r\n            _Inout_ ULONG* pNumObjs,\r\n            _Out_opt_ VOID** ppObjArray\r\n        )\r\n    {\r\n        HRESULT hr = S_OK;\r\n        ComPtr<IWTSPlugin> spPlugin;\r\n\r\n        if (refiid != __uuidof(IWTSPlugin))\r\n        {\r\n            return E_NOINTERFACE;\r\n        }\r\n\r\n        if (ppObjArray == NULL)\r\n        {\r\n            *pNumObjs = 1;\r\n            return S_OK;\r\n        }\r\n\r\n        hr = WSLDVCPlugin_CreateInstance(&spPlugin);\r\n        if (SUCCEEDED(hr))\r\n        {\r\n            *pNumObjs = 1;\r\n            ppObjArray[0] = spPlugin.Detach();\r\n        }\r\n\r\n        return hr;\r\n    }\r\n\r\n    __declspec(dllexport) HRESULT WINAPI\r\n        RemoveAppProvider(\r\n            _In_z_ LPCWSTR appProvider\r\n        )\r\n    {\r\n        HRESULT hr;\r\n        WCHAR appMenuPath[MAX_PATH] = {};\r\n        ComPtr<IWSLDVCFileDB> spFileDB;\r\n\r\n        if (!appProvider)\r\n        {\r\n            DebugPrint(L\"appProvider parameter is required\\n\");\r\n            return E_INVALIDARG;\r\n        }\r\n\r\n        hr = BuildMenuPath(ARRAYSIZE(appMenuPath), appMenuPath, appProvider, false);\r\n        if (FAILED(hr))\r\n        {\r\n            return hr;\r\n        }\r\n        DebugPrint(L\"AppMenuPath: %s\\n\", appMenuPath);\r\n\r\n        if (!IsDirectoryPresent(appMenuPath))\r\n        {\r\n            DebugPrint(L\"%s is not present\\n\", appMenuPath);\r\n            return S_OK; // no program menu exists for given provider, simply exit.\r\n        }\r\n\r\n        hr = WSLDVCFileDB_CreateInstance(NULL, &spFileDB);\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"failed to instance WSLDVCFileDB\\n\");\r\n            return hr;\r\n        }\r\n\r\n        spFileDB->addAllFilesAsFileIdAt(appMenuPath);\r\n        spFileDB->deleteAllFileIdFiles();\r\n        spFileDB->OnClose();\r\n        spFileDB = nullptr;\r\n\r\n        return hr;\r\n    }\r\n}\r\n"
  },
  {
    "path": "WSLDVCPlugin/framework.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#pragma once\r\n\r\n#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers\r\n// Windows Header Files\r\n#include <iostream>\r\n#include <cassert>\r\n#include <string>\r\n#include <windows.h>\r\n#include <wrl.h>\r\n#include <objidl.h>   /* For IPersistFile */\r\n#include <shlobj.h>   /* For IShellLink */\r\n#include <shlwapi.h>  /* For PathIsDirectoryEmpty */\r\n#include <shellapi.h> /* For SHGetPropertyStoreForWindow */\r\n#include <propvarutil.h> /* For InitPropVariantFromString */\r\n#include <propkey.h> /* For PKEY_* */\r\n#include <inttypes.h> /* For PRIx64 */\r\n"
  },
  {
    "path": "WSLDVCPlugin/pch.cpp",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include \"pch.h\"\r\n\r\n// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.\r\n"
  },
  {
    "path": "WSLDVCPlugin/pch.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n// pch.h: This is a precompiled header file.\r\n// Files listed below are compiled only once, improving build performance for future builds.\r\n// This also affects IntelliSense performance, including code completion and many code browsing features.\r\n// However, files listed here are ALL re-compiled if any one of them is updated between builds.\r\n// Do not add files here that you will be updating frequently as this negates the performance advantage.\r\n\r\n#ifndef PCH_H\r\n#define PCH_H\r\n\r\n// add headers that you want to pre-compile here\r\n#include \"framework.h\"\r\n\r\nusing namespace Microsoft::WRL;\r\nusing namespace std;\r\n\r\n#endif //PCH_H\r\n"
  },
  {
    "path": "WSLDVCPlugin/rdpapplist.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#pragma once\r\n\r\n//\r\n// RDP APPLIST protocol header.\r\n//\r\n#define RDPAPPLIST_CMDID_CAPS 0x00000001\r\n#define RDPAPPLIST_CMDID_UPDATE_APPLIST 0x00000002\r\n#define RDPAPPLIST_CMDID_DELETE_APPLIST 0x00000003\r\n#define RDPAPPLIST_CMDID_DELETE_APPLIST_PROVIDER 0x00000004\r\n/* added from version 4 */\r\n#define RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID 0x00000005\r\n\r\n#define RDPAPPLIST_FIELD_ID         0x00000001\r\n#define RDPAPPLIST_FIELD_GROUP      0x00000002\r\n#define RDPAPPLIST_FIELD_EXECPATH   0x00000004\r\n#define RDPAPPLIST_FIELD_DESC       0x00000008\r\n#define RDPAPPLIST_FIELD_ICON       0x00000010\r\n#define RDPAPPLIST_FIELD_PROVIDER   0x00000020\r\n#define RDPAPPLIST_FIELD_WORKINGDIR 0x00000040\r\n#define RDPAPPLIST_FIELD_WINDOW_ID  0x00000080\r\n\r\n/* RDPAPPLIST_UPDATE_APPLIST_PDU */\r\n#define RDPAPPLIST_HINT_NEWID      0x00010000 /* new appId vs update existing appId. */\r\n#define RDPAPPLIST_HINT_SYNC       0x00100000 /* In sync mode (use with _NEWID). */\r\n#define RDPAPPLIST_HINT_SYNC_START 0x00200000 /* Sync appId start (use with _SYNC). */\r\n#define RDPAPPLIST_HINT_SYNC_END   0x00400000 /* Sync appId end (use with _SYNC). */\r\n\r\n#define RDPAPPLIST_CHANNEL_VERSION 4\r\n\r\n#define RDPAPPLIST_HEADER_SIZE 8\r\n\r\n#define RDPAPPLIST_LANG_SIZE 32\r\n\r\n#define RDPAPPLIST_MAX_STRING_SIZE 512\r\n#define RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR (RDPAPPLIST_MAX_STRING_SIZE/sizeof(WCHAR))\r\n\r\ntypedef struct _RDPAPPLIST_HEADER\r\n{\r\n    UINT32 cmdId;\r\n    UINT32 length;\r\n} RDPAPPLIST_HEADER;\r\n\r\ntypedef struct _RDPAPPLIST_CLIENT_CAPS_PDU\r\n{\r\n    UINT16 version;\r\n    /* ISO 639 (Language name) and ISO 3166 (Country name) connected with '_', such as en_US, ja_JP */\r\n    char clientLanguageId[RDPAPPLIST_LANG_SIZE];\r\n} RDPAPPLIST_CLIENT_CAPS_PDU;\r\n\r\ntypedef struct _RDPAPPLIST_SERVER_CAPS_PDU\r\n{\r\n    UINT16 version;\r\n    UINT16 appListProviderNameLength;\r\n    WCHAR appListProviderName[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n    /* added from version 4 */\r\n    UINT16 appListProviderUniqueIdLength;\r\n    WCHAR appListProviderUniqueId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n} RDPAPPLIST_SERVER_CAPS_PDU;\r\n\r\ntypedef struct _RDPAPPLIST_UPDATE_APPLIST_PDU\r\n{\r\n    UINT32 flags;\r\n    UINT16 appIdLength;\r\n    WCHAR appId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n    UINT16 appGroupLength;\r\n    WCHAR appGroup[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n    UINT16 appExecPathLength;\r\n    WCHAR appExecPath[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n    UINT16 appWorkingDirLength;\r\n    WCHAR appWorkingDir[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n    UINT16 appDescLength;\r\n    WCHAR appDesc[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n} RDPAPPLIST_UPDATE_APPLIST_PDU;\r\n\r\ntypedef struct _RDPAPPLIST_ICON_DATA\r\n{\r\n    UINT32 flags;\r\n    UINT32 iconWidth;\r\n    UINT32 iconHeight;\r\n    UINT32 iconStride;\r\n    UINT32 iconBpp;\r\n    UINT32 iconFormat;\r\n    UINT32 iconBitsLength;\r\n    UINT32 iconFileSize;\r\n    BYTE* iconFileData;\r\n} RDPAPPLIST_ICON_DATA;\r\n\r\ntypedef struct _RDPAPPLIST_DELETE_APPLIST_PDU\r\n{\r\n    UINT32 flags;\r\n    UINT16 appIdLength;\r\n    WCHAR appId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n    UINT16 appGroupLength;\r\n    WCHAR appGroup[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n} RDPAPPLIST_DELETE_APPLIST_PDU;\r\n\r\ntypedef struct _RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU\r\n{\r\n    UINT32 flags;\r\n    UINT16 appListProviderNameLength;\r\n    WCHAR appListProviderName[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n} RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU;\r\n\r\n/* added from version 4 */\r\ntypedef struct _RDPAPPLIST_ASSOCIATE_WINDOW_ID\r\n{\r\n    UINT32 flags;\r\n    UINT32 appWindowId;\r\n    UINT16 appIdLength;\r\n    WCHAR appId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n    UINT16 appGroupLength;\r\n    WCHAR appGroup[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n    UINT16 appExecPathLength;\r\n    WCHAR appExecPath[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n    UINT16 appDescLength;\r\n    WCHAR appDesc[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR];\r\n} RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU;\r\n\r\n//\r\n// Read macro.\r\n//\r\n#define LSTR(x) L ## x\r\n\r\n// ReadUINT16(dest, source, remaining)\r\n#define ReadUINT16(o, p, r)    \\\r\n    if (r >= sizeof(UINT16)) { \\\r\n        o = (*(UINT16*)(p));   \\\r\n        (p) += sizeof(UINT16); \\\r\n        (r) -= sizeof(UINT16); \\\r\n    } else {                   \\\r\n        DebugPrint(L\"Failed to read \" LSTR(#o) L\"\\n\"); \\\r\n        goto Error_Read;       \\\r\n    }\r\n\r\n// ReadUINT32(dest, source, remaining)\r\n#define ReadUINT32(o, p, r)    \\\r\n    if (r >= sizeof(UINT32)) { \\\r\n        o = (*(UINT32*)(p));   \\\r\n        (p) += sizeof(UINT32); \\\r\n        (r) -= sizeof(UINT32); \\\r\n    } else {                   \\\r\n        DebugPrint(L\"Failed to read \" LSTR(#o) L\"\\n\"); \\\r\n        goto Error_Read;       \\\r\n    }\r\n\r\n// ReadBYTES(dest, source, lengthToCopy, RemainingSource)\r\n#define ReadBYTES(o, p, l, r)  \\\r\n    if (r >= l) {              \\\r\n        memcpy((o), (p), (l)); \\\r\n        (p) += l;              \\\r\n        (r) -= (l);            \\\r\n    } else {                   \\\r\n        DebugPrint(L\"Failed to read \" LSTR(#o) L\"\\n\"); \\\r\n        goto Error_Read;       \\\r\n    }\r\n\r\n// ReadSTRING(dest, source, RemainingSource, required)\r\n#define ReadSTRING(o, p, r, required) \\\r\n    ReadUINT16(o ## Length, p, r); \\\r\n    if (o ## Length + sizeof(WCHAR) > sizeof(o)) { \\\r\n        DebugPrint(L\"Failed to read \" LSTR(#o) L\"\\n\"); \\\r\n        goto Error_Read; \\\r\n    } if (o ## Length) { \\\r\n        ReadBYTES(o, p, o ## Length, r); \\\r\n        o[o ## Length / sizeof(WCHAR)] = L'\\0'; \\\r\n    } else if (required) { \\\r\n        DebugPrint(LSTR(#o) L\" is required\\n\"); \\\r\n        goto Error_Read; \\\r\n    }\r\n\r\n#define CheckRequiredFlags(flags, required) \\\r\n    { \\\r\n        auto f = (flags) & (required); \\\r\n        if (f != (required)) { \\\r\n            DebugPrint(L\"missing required flags. Given:%x vs Required:%x\\n\", flags, required); \\\r\n            goto Error_Read; \\\r\n        } \\\r\n    }\r\n"
  },
  {
    "path": "WSLDVCPlugin/resource.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n//{{NO_DEPENDENCIES}}\r\n// Microsoft Visual C++ generated include file.\r\n// Used by WSLDVCPlugin.rc\r\n\r\n// Next default values for new objects\r\n// \r\n#ifdef APSTUDIO_INVOKED\r\n#ifndef APSTUDIO_READONLY_SYMBOLS\r\n#define _APS_NEXT_RESOURCE_VALUE        101\r\n#define _APS_NEXT_COMMAND_VALUE         40001\r\n#define _APS_NEXT_CONTROL_VALUE         1001\r\n#define _APS_NEXT_SYMED_VALUE           101\r\n#endif\r\n#endif\r\n"
  },
  {
    "path": "WSLDVCPlugin/utils.cpp",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include \"pch.h\"\r\n#include \"utils.h\"\r\n\r\n#ifdef DBG_MESSAGE\r\nvoid DebugPrint(const wchar_t* format, ...)\r\n{\r\n    WCHAR buf[512] = {};\r\n    va_list args;\r\n\r\n    va_start(args, format);\r\n    wvsprintfW(buf, format, args);\r\n    va_end(args);\r\n\r\n    OutputDebugStringW(buf);\r\n}\r\n#endif // DBG_MESSAGE\r\n\r\n_Use_decl_annotations_\r\nBOOL IsDirectoryPresent(LPCWSTR lpszPath)\r\n{\r\n    DWORD dwAttrib = GetFileAttributes(lpszPath);\r\n\r\n    return (dwAttrib != INVALID_FILE_ATTRIBUTES && \r\n           (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));\r\n}\r\n\r\n_Use_decl_annotations_\r\nHRESULT\r\nCreateShellLink(LPCWSTR lpszPathLink,\r\n    LPCWSTR lpszPathObj,\r\n    LPCWSTR lpszArgs,\r\n    LPCWSTR lpszWorkingDir,\r\n    LPCWSTR lpszDesc,\r\n    LPCWSTR lpszPathIcon)\r\n{\r\n    HRESULT hr;\r\n    IShellLink* psl;\r\n\r\n    DebugPrint(L\"CreateShellLink:\\n\");\r\n    DebugPrint(L\"\\tPath Link: %s\\n\", lpszPathLink);\r\n    DebugPrint(L\"\\tPath Exe: %s\\n\", lpszPathObj);\r\n    DebugPrint(L\"\\tExe args: %s\\n\", lpszArgs);\r\n    DebugPrint(L\"\\tWorkingDir: %s\\n\", lpszWorkingDir);\r\n    DebugPrint(L\"\\tDesc: %s\\n\", lpszDesc);\r\n    if (lpszPathIcon && lstrlenW(lpszPathIcon))\r\n    {\r\n        DebugPrint(L\"\\tIcon Path: %s\\n\", lpszPathIcon);\r\n    }\r\n    else\r\n    {\r\n        lpszPathIcon = nullptr;\r\n    }\r\n\r\n    // Get a pointer to the IShellLink interface. It is assumed that CoInitialize\r\n    // has already been called.\r\n    hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);\r\n    if (SUCCEEDED(hr))\r\n    {\r\n        IPersistFile* ppf;\r\n\r\n        // Set the path to the shortcut target and add the description. \r\n        psl->SetPath(lpszPathObj);\r\n        psl->SetArguments(lpszArgs);\r\n        if (lpszPathIcon)\r\n        {\r\n            psl->SetIconLocation(lpszPathIcon, 0);\r\n        }\r\n        psl->SetDescription(lpszDesc);\r\n        psl->SetWorkingDirectory(lpszWorkingDir);\r\n        psl->SetShowCmd(SW_SHOWMINNOACTIVE);\r\n\r\n        // Query IShellLink for the IPersistFile interface, used for saving the \r\n        // shortcut in persistent storage. \r\n        hr = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);\r\n        if (SUCCEEDED(hr))\r\n        {\r\n            // Save the link by calling IPersistFile::Save. \r\n            hr = ppf->Save(lpszPathLink, TRUE);\r\n            ppf->Release();\r\n        }\r\n        psl->Release();\r\n    }\r\n\r\n    DebugPrint(L\"\\tresult: %x\\n\", hr);\r\n    return hr;\r\n}\r\n\r\n_Use_decl_annotations_\r\nHRESULT\r\nGetIconFileFromShellLink(\r\n    UINT32 iconPathSize, \r\n    LPWSTR iconPath,\r\n    LPCWSTR lnkPath)\r\n{\r\n    HRESULT hr;\r\n    IShellLink* psl;\r\n\r\n    DebugPrint(L\"GetIconFileFromShellLink:\\n\");\r\n    DebugPrint(L\"\\tPath Link: %s\\n\", lnkPath);\r\n\r\n    assert(iconPathSize);\r\n    assert(iconPath);\r\n    *iconPath = L'\\0';\r\n\r\n    hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);\r\n    if (SUCCEEDED(hr))\r\n    {\r\n        IPersistFile* ppf;\r\n        hr = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);\r\n        if (SUCCEEDED(hr))\r\n        {\r\n            hr = ppf->Load(lnkPath, STGM_READ);\r\n            if (SUCCEEDED(hr))\r\n            {\r\n                int dummy = 0;\r\n                hr = psl->GetIconLocation(iconPath, iconPathSize, &dummy);\r\n            }\r\n            ppf->Release();\r\n        }\r\n        psl->Release();\r\n    }\r\n\r\n    DebugPrint(L\"\\tresult: %x\\n\", hr);\r\n    if (SUCCEEDED(hr))\r\n    {\r\n        DebugPrint(L\"\\ticonPath: %s\\n\", iconPath);\r\n    }\r\n\r\n    return hr;\r\n}\r\n\r\n_Use_decl_annotations_\r\nHRESULT\r\nCreateIconFile(BYTE* pBuffer,\r\n    UINT32 cbSize,\r\n    LPCWSTR lpszIconFile)\r\n{\r\n    HRESULT hr = S_OK;\r\n    HANDLE hFile;\r\n\r\n    DebugPrint(L\"CreateIconFile: %s\\n\", lpszIconFile);\r\n\r\n    hFile = CreateFileW(lpszIconFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);\r\n    if (hFile == INVALID_HANDLE_VALUE)\r\n    {\r\n        DebugPrint(L\"CreateFile(%s) failed, error %d\\n\", lpszIconFile, GetLastError());\r\n        hr = E_FAIL;\r\n    }\r\n    else\r\n    {        \r\n        if (!WriteFile(hFile, pBuffer, cbSize, NULL, NULL))\r\n        {\r\n            DebugPrint(L\"WriteFile(%s) failed, error %d\\n\", lpszIconFile, GetLastError());\r\n            hr = E_FAIL;\r\n        }\r\n        \r\n        CloseHandle(hFile);\r\n    }\r\n\r\n    DebugPrint(L\"\\tresult: %x\\n\", hr);\r\n    return hr;\r\n}\r\n\r\n#define MAX_LOCALE_CODE 9\r\n\r\n_Use_decl_annotations_\r\nBOOL GetLocaleName(char* localeName, int localeNameSize)\r\n{\r\n    char langCode[MAX_LOCALE_CODE] = {};\r\n    char countryName[MAX_LOCALE_CODE] = {};\r\n    int result = 0;\r\n\r\n    assert(localeName);\r\n    localeName[0] = '\\0';\r\n\r\n    LCID lcid = MAKELCID(GetUserDefaultUILanguage(), SORT_DEFAULT);\r\n    result = GetLocaleInfoA(lcid,\r\n        LOCALE_SISO639LANGNAME,\r\n        langCode,\r\n        MAX_LOCALE_CODE) != 0;\r\n    if ((result == 0) ||\r\n        (strcpy_s(localeName, localeNameSize, langCode) != 0) ||\r\n        (strcat_s(localeName, localeNameSize, \"_\") != 0))\r\n    {\r\n        return FALSE;\r\n    }\r\n\r\n    result = GetLocaleInfoA(lcid,\r\n        LOCALE_SISO3166CTRYNAME,\r\n        countryName,\r\n        MAX_LOCALE_CODE) != 0;\r\n    if ((result == 0) ||\r\n        (strcat_s(localeName, localeNameSize, countryName) != 0))\r\n    {\r\n        return FALSE;\r\n    }\r\n\r\n    return TRUE;\r\n}\r\n\r\n_Use_decl_annotations_\r\nHRESULT BuildMenuPath(\r\n    UINT32 appMenuPathSize,\r\n    LPWSTR appMenuPath,\r\n    LPCWSTR appProvider,\r\n    bool isCreateDir)\r\n{\r\n    PWSTR knownFolderPath = NULL; // free by CoTaskMemFree. \r\n    SHGetKnownFolderPath(FOLDERID_StartMenu, 0, NULL, &knownFolderPath);\r\n    if (!knownFolderPath)\r\n    {\r\n        DebugPrint(L\"SHGetKnownFolderPath(FOLDERID_StartMenu) failed\\n\");\r\n        return E_FAIL;\r\n    }\r\n    int ret = swprintf_s(appMenuPath, appMenuPathSize, L\"%s\\\\Programs\\\\%s\", knownFolderPath, appProvider);\r\n    CoTaskMemFree(knownFolderPath);\r\n    if (ret < 0)\r\n    {\r\n        DebugPrint(L\"swprintf_s for appMenuPath failed\");\r\n        return E_FAIL;\r\n    }\r\n    if (isCreateDir)\r\n    {\r\n        if (!CreateDirectoryW(appMenuPath, NULL))\r\n        {\r\n            if (ERROR_ALREADY_EXISTS != GetLastError())\r\n            {\r\n                DebugPrint(L\"Failed to create %s\\n\", appMenuPath);\r\n                return E_FAIL;\r\n            }\r\n        }\r\n    }\r\n\r\n    return S_OK;\r\n}\r\n\r\n_Use_decl_annotations_\r\nHRESULT BuildIconPath(\r\n    UINT32 iconPathSize,\r\n    LPWSTR iconPath,\r\n    LPCWSTR appProvider,\r\n    bool isCreateDir)\r\n{\r\n    WCHAR prefix[] = L\"WSLDVCPlugin\\\\\";\r\n\r\n    UINT32 lenTempPath;\r\n    lenTempPath = GetTempPathW(iconPathSize, iconPath);\r\n    if (!lenTempPath)\r\n    {\r\n        DebugPrint(L\"GetTempPathW failed\\n\");\r\n        return E_FAIL;\r\n    }\r\n\r\n    if ((lenTempPath + ARRAYSIZE(prefix) + wcslen(appProvider)) > iconPathSize)\r\n    {\r\n        DebugPrint(L\"provider name length check failed, length %d\\n\",  wcslen(appProvider));\r\n        return E_FAIL;\r\n    }\r\n\r\n    if (wcscat_s(iconPath, iconPathSize, prefix) != 0)\r\n    {\r\n        return E_FAIL;\r\n    }\r\n    if (isCreateDir)\r\n    {\r\n        if (!CreateDirectoryW(iconPath, NULL))\r\n        {\r\n            if (ERROR_ALREADY_EXISTS != GetLastError())\r\n            {\r\n                DebugPrint(L\"Failed to create %s\\n\", iconPath);\r\n                return E_FAIL;\r\n            }\r\n        }\r\n    }\r\n    if (wcscat_s(iconPath, iconPathSize, appProvider) != 0)\r\n    {\r\n        return E_FAIL;\r\n    }\r\n    if (isCreateDir)\r\n    {\r\n        if (!CreateDirectoryW(iconPath, NULL))\r\n        {\r\n            if (ERROR_ALREADY_EXISTS != GetLastError())\r\n            {\r\n                DebugPrint(L\"Failed to create %s\\n\", iconPath);\r\n                return E_FAIL;\r\n            }\r\n        }\r\n    }\r\n\r\n    return S_OK;\r\n}\r\n\r\nHRESULT\r\nUpdateTaskBarInfo(\r\n    HWND hwnd,\r\n    _In_z_ LPCWSTR relaunchCmdline,\r\n    _In_z_ LPCWSTR displayName,\r\n    _In_z_ LPCWSTR iconPath)\r\n{\r\n    HRESULT hr;\r\n    PROPVARIANT propvar;\r\n\r\n    DebugPrint(L\"UpdateTaskBarInfo: 0x%p\\n\", hwnd);\r\n    DebugPrint(L\"    relaunchCmdline: %s\\n\", relaunchCmdline);\r\n    DebugPrint(L\"    displayName: %s\\n\", displayName);\r\n    DebugPrint(L\"    iconPath: %s\\n\", iconPath);\r\n\r\n    IPropertyStore* ps = NULL;\r\n\r\n    hr = SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&ps));\r\n    if (FAILED(hr))\r\n    {\r\n        DebugPrint(L\"SHGetPropertyStoreForWindow failed: 0x%x\\n\", hr);\r\n        return hr;\r\n    }\r\n\r\n    BOOL bPinToTaskbar = relaunchCmdline && displayName && iconPath;\r\n    if (bPinToTaskbar)\r\n    {\r\n        hr = InitPropVariantFromString(displayName, &propvar);\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"InitPropVariantFromString failed: 0x%x\\n\", hr);\r\n            return hr;\r\n        }\r\n\r\n        hr = ps->SetValue(PKEY_AppUserModel_RelaunchDisplayNameResource, propvar);\r\n        PropVariantClear(&propvar);\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"SetValue(PKEY_AppUserModel_RelaunchDisplayNameResource failed: 0x%x\\n\", hr);\r\n            return hr;\r\n        }\r\n\r\n        hr = InitPropVariantFromString(iconPath, &propvar);\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"InitPropVariantFromString failed: 0x%x\\n\", hr);\r\n            return hr;\r\n        }\r\n\r\n        hr = ps->SetValue(PKEY_AppUserModel_RelaunchIconResource, propvar);\r\n        PropVariantClear(&propvar);\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"SetValue(PKEY_AppUserModel_RelaunchIconResource failed: 0x%x\\n\", hr);\r\n            return hr;\r\n        }\r\n\r\n        hr = InitPropVariantFromString(relaunchCmdline, &propvar);\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"InitPropVariantFromString failed: 0x%x\\n\", hr);\r\n            return hr;\r\n        }\r\n\r\n        hr = ps->SetValue(PKEY_AppUserModel_RelaunchCommand, propvar);\r\n        PropVariantClear(&propvar);\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"SetValue(PKEY_AppUserModel_RelaunchCommand failed: 0x%x\\n\", hr);\r\n            return hr;\r\n        }\r\n    }\r\n    else\r\n    {\r\n        hr = InitPropVariantFromBoolean(TRUE, &propvar);\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"InitPropVariantFromBoolean failed: 0x%x\\n\", hr);\r\n            return hr;\r\n        }\r\n\r\n        hr = ps->SetValue(PKEY_AppUserModel_PreventPinning, propvar);\r\n        PropVariantClear(&propvar);\r\n        if (FAILED(hr))\r\n        {\r\n            DebugPrint(L\"SetValue(PKEY_AppUserModel_PreventPinning failed: 0x%x\\n\", hr);\r\n            return hr;\r\n        }\r\n    }\r\n\r\n    ps->Release();\r\n\r\n    return S_OK;\r\n}\r\n\r\n#if ENABLE_WSL_SIGNATURE_CHECK\r\n// Link with the Wintrust.lib file.\r\n#pragma comment (lib, \"wintrust\")\r\n\r\n#include <Softpub.h>\r\n#include <wincrypt.h>\r\n#include <wintrust.h>\r\n\r\n_Use_decl_annotations_\r\nBOOL IsFileTrusted(LPCWSTR pwszFile)\r\n{\r\n    BOOL bTrusted = FALSE;\r\n\r\n    LONG lStatus;\r\n    DWORD dwLastError;\r\n\r\n    // Initialize the WINTRUST_FILE_INFO structure.\r\n    WINTRUST_FILE_INFO FileData;\r\n    memset(&FileData, 0, sizeof(FileData));\r\n    FileData.cbStruct = sizeof(WINTRUST_FILE_INFO);\r\n    FileData.pcwszFilePath = pwszFile;\r\n    FileData.hFile = NULL;\r\n    FileData.pgKnownSubject = NULL;\r\n\r\n    /*\r\n    WVTPolicyGUID specifies the policy to apply on the file\r\n    WINTRUST_ACTION_GENERIC_VERIFY_V2 policy checks:\r\n    \r\n    1) The certificate used to sign the file chains up to a root \r\n    certificate located in the trusted root certificate store. This \r\n    implies that the identity of the publisher has been verified by \r\n    a certification authority.\r\n    \r\n    2) In cases where user interface is displayed (which this example\r\n    does not do), WinVerifyTrust will check for whether the  \r\n    end entity certificate is stored in the trusted publisher store,  \r\n    implying that the user trusts content from this publisher.\r\n    \r\n    3) The end entity certificate has sufficient permission to sign \r\n    code, as indicated by the presence of a code signing EKU or no \r\n    EKU.\r\n    */\r\n\r\n    GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;\r\n    WINTRUST_DATA WinTrustData;\r\n\r\n    // Initialize the WinVerifyTrust input data structure.\r\n\r\n    // Default all fields to 0.\r\n    memset(&WinTrustData, 0, sizeof(WinTrustData));\r\n\r\n    WinTrustData.cbStruct = sizeof(WinTrustData);\r\n    \r\n    // Use default code signing EKU.\r\n    WinTrustData.pPolicyCallbackData = NULL;\r\n\r\n    // No data to pass to SIP.\r\n    WinTrustData.pSIPClientData = NULL;\r\n\r\n    // Disable WVT UI.\r\n    WinTrustData.dwUIChoice = WTD_UI_NONE;\r\n\r\n    // No revocation checking.\r\n    WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE; \r\n\r\n    // Verify an embedded signature on a file.\r\n    WinTrustData.dwUnionChoice = WTD_CHOICE_FILE;\r\n\r\n    // Verify action.\r\n    WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY;\r\n\r\n    // Verification sets this value.\r\n    WinTrustData.hWVTStateData = NULL;\r\n\r\n    // Not used.\r\n    WinTrustData.pwszURLReference = NULL;\r\n\r\n    // This is not applicable if there is no UI because it changes \r\n    // the UI to accommodate running applications instead of \r\n    // installing applications.\r\n    WinTrustData.dwUIContext = 0;\r\n\r\n    // Set pFile.\r\n    WinTrustData.pFile = &FileData;\r\n\r\n    // WinVerifyTrust verifies signatures as specified by the GUID \r\n    // and Wintrust_Data.\r\n    lStatus = WinVerifyTrust(\r\n        NULL,\r\n        &WVTPolicyGUID,\r\n        &WinTrustData);\r\n\r\n    switch (lStatus) \r\n    {\r\n        case ERROR_SUCCESS:\r\n            //Signed file:\r\n            //    - Hash that represents the subject is trusted.\r\n            //    - Trusted publisher without any verification errors.\r\n            DebugPrint(L\"The file \\\"%s\\\" is signed and the signature was verified.\\n\",\r\n                pwszFile);\r\n            bTrusted = TRUE;\r\n            break;\r\n        \r\n        case TRUST_E_NOSIGNATURE:\r\n            // The file was not signed or had a signature \r\n            // that was not valid.\r\n            // Get the reason for no signature.\r\n            dwLastError = GetLastError();\r\n            if (TRUST_E_NOSIGNATURE == dwLastError ||\r\n                    TRUST_E_SUBJECT_FORM_UNKNOWN == dwLastError ||\r\n                    TRUST_E_PROVIDER_UNKNOWN == dwLastError) \r\n            {\r\n                // The file was not signed.\r\n                DebugPrint(L\"The file \\\"%s\\\" is not signed.\\n\",\r\n                    pwszFile);\r\n            } \r\n            else \r\n            {\r\n                // The signature was not valid or there was an error \r\n                // opening the file.\r\n                DebugPrint(L\"An unknown error occurred trying to \"\r\n                    L\"verify the signature of the \\\"%s\\\" file.\\n\",\r\n                    pwszFile);\r\n            }\r\n            break;\r\n\r\n        case TRUST_E_EXPLICIT_DISTRUST:\r\n            // The hash that represents the subject or the publisher \r\n            // is not allowed by the admin or user.\r\n            DebugPrint(L\"The signature is present, but specifically disallowed.\\n\");\r\n            break;\r\n\r\n        case TRUST_E_SUBJECT_NOT_TRUSTED:\r\n            DebugPrint(L\"The signature is present, but not trusted.\\n\");\r\n            break;\r\n\r\n        default:\r\n            // The UI was disabled in dwUIChoice or the admin policy \r\n            // has disabled user trust. lStatus contains the \r\n            // publisher or time stamp chain error.\r\n            DebugPrint(L\"Error is: 0x%x.\\n\", lStatus);\r\n            break;\r\n    }\r\n\r\n    // Any hWVTStateData must be released by a call with close.\r\n    WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE;\r\n\r\n    lStatus = WinVerifyTrust(\r\n        NULL,\r\n        &WVTPolicyGUID,\r\n        &WinTrustData);\r\n\r\n    return bTrusted;\r\n}\r\n#endif // ENABLE_WSL_SIGNATURE_CHECK\r\n"
  },
  {
    "path": "WSLDVCPlugin/utils.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#pragma once\r\n\r\n//#ifdef _DEBUG\r\n#define DBG_MESSAGE\r\n//#endif // _DEBUG\r\n\r\n#ifdef DBG_MESSAGE\r\nvoid DebugPrint(const wchar_t* format, ...);\r\n#define DebugAssert(exp) assert(exp)\r\n#else\r\n#define DebugPrint\r\n#define DebugAssert\r\n#endif // DBG_MESSAGE\r\n\r\n// Set to 1 to enable digital signature check.\r\n#define ENABLE_WSL_SIGNATURE_CHECK 0\r\n\r\nBOOL\r\nIsDirectoryPresent(_In_z_ LPCWSTR lpszPath);\r\n\r\nHRESULT\r\nCreateShellLink(_In_z_ LPCWSTR lpszPathLink,\r\n    _In_z_ LPCWSTR lpszPathObj, \r\n    _In_z_ LPCWSTR lpszArgs,\r\n    _In_z_ LPCWSTR lpszWorkingDir,\r\n    _In_z_ LPCWSTR lpszDesc, \r\n    _In_opt_z_ LPCWSTR lpszPathIcon);\r\n\r\nHRESULT\r\nGetIconFileFromShellLink(\r\n    UINT32 iconPathSize, \r\n    _Out_writes_z_(iconPathSize) LPWSTR iconPath,\r\n    _In_z_ LPCWSTR lnkPath);\r\n\r\nHRESULT\r\nCreateIconFile(_In_reads_bytes_(cbSize) BYTE* pBuffer,\r\n    UINT32 cbSize,\r\n    _In_z_ LPCWSTR lpszIconFile);\r\n\r\nBOOL\r\nGetLocaleName(_Out_writes_z_(localeNameSize) char* localeName,\r\n    int localeNameSize);\r\n\r\nHRESULT\r\nBuildMenuPath(\r\n    UINT32 appMenuPathSize,\r\n    _Out_writes_z_(appMenuPathSize) LPWSTR appMenuPath,\r\n    _In_z_ LPCWSTR appProvider,\r\n    bool isCreateDir);\r\n\r\nHRESULT\r\nBuildIconPath(\r\n    UINT32 iconPathSize,\r\n    _Out_writes_z_(iconPathSize) LPWSTR iconPath,\r\n    _In_z_ LPCWSTR appProvider,\r\n    bool isCreateDir);\r\n\r\nHRESULT\r\nUpdateTaskBarInfo(\r\n    HWND hwnd,\r\n    _In_z_ LPCWSTR relaunchCmdline,\r\n    _In_z_ LPCWSTR displayName,\r\n    _In_z_ LPCWSTR iconPath);\r\n\r\n#if ENABLE_WSL_SIGNATURE_CHECK\r\nBOOL\r\nIsFileTrusted(_In_z_ LPCWSTR pwszFile);\r\n#else\r\n#define IsFileTrusted(pwszFile) (true)\r\n#endif // ENABLE_WSL_SIGNATURE_CHECK\r\n\r\n#pragma pack(1)\r\n//\r\n// .ICO file format header\r\n//\r\n\r\n// Icon entry struct\r\ntypedef struct _ICON_DIR_ENTRY\r\n{\r\n    BYTE    bWidth;         // Width, in pixels, of the image\r\n    BYTE    bHeight;        // Height, in pixels, of the image\r\n    BYTE    bColorCount;    // Number of colors in image (0 if >=8bpp)\r\n    BYTE    bReserved;      // Reserved ( must be 0)\r\n    WORD    wPlanes;        // Color Planes\r\n    WORD    wBitCount;      // Bits per pixel\r\n    DWORD   dwBytesInRes;   // How many bytes in this resource?\r\n    DWORD   dwImageOffset;  // Where in the file is this image?\r\n} ICON_DIR_ENTRY;\r\n\r\n// Icon directory struct\r\ntypedef struct _ICON_HEADER\r\n{\r\n    WORD           idReserved;   // Reserved (must be 0)\r\n    WORD           idType;       // Resource Type (1 for icons)\r\n    WORD           idCount;      // How many images?\r\n    ICON_DIR_ENTRY idEntries[1]; // An entry for each image (idCount of 'em)\r\n} ICON_HEADER;\r\n#pragma pack()\r\n"
  },
  {
    "path": "WSLGd/FontMonitor.cpp",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include \"FontMonitor.h\"\r\n#include \"common.h\"\r\n\r\n#define DEFAULT_FONT_PATH \"/usr/share/fonts\"\r\n#define USER_DISTRO_FONT_PATH USER_DISTRO_MOUNT_PATH DEFAULT_FONT_PATH\r\n#define ALT_FONT_PATH \"/usr/share/X11/fonts\"\r\n#define ALT_DISTRO_FONT_PATH USER_DISTRO_MOUNT_PATH ALT_FONT_PATH\r\n\r\nconstexpr auto c_fontsdir = \"fonts.dir\";\r\nconstexpr auto c_xset = \"/usr/bin/xset\";\r\n\r\nwslgd::FontFolder::FontFolder(int fd, const char *path)\r\n{\r\n    LOG_INFO(\"FontMonitor: start monitoring %s\", path);\r\n\r\n    m_path = path;\r\n\r\n    /* check if folder is already ready to be added to font path */\r\n    try {\r\n        std::filesystem::path fonts_dir(path);\r\n        fonts_dir /= c_fontsdir;\r\n        if (access(fonts_dir.c_str(), R_OK) == 0) {\r\n            ModifyX11FontPath(true);\r\n        }\r\n        m_fd.reset(dup(fd));\r\n        THROW_LAST_ERROR_IF(!m_fd);\r\n        /* add watch for install or uninstall of fonts on this folder */\r\n        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);\r\n    }\r\n    CATCH_LOG();\r\n}\r\n\r\nwslgd::FontFolder::~FontFolder()\r\n{\r\n    LOG_INFO(\"FontMonitor: stop monitoring %s\", m_path.c_str());\r\n\r\n    ModifyX11FontPath(false);\r\n\r\n    /* if still under watch, remove it */\r\n    if (m_wd >= 0) {\r\n        inotify_rm_watch(m_fd.get(), m_wd);\r\n        m_wd = -1;\r\n    }\r\n}\r\n\r\nbool wslgd::FontFolder::ExecuteShellCommand(std::vector<const char*>&& argv)\r\n{\r\n    bool success = false;\r\n    int childPid = -1, waitPid = -1;\r\n    std::string cmd;\r\n\r\n    try {\r\n        THROW_LAST_ERROR_IF((childPid = fork()) < 0);\r\n        if (childPid == 0) {\r\n            /* move this process to own process group to avoid interfere with Process Monitor */\r\n            THROW_LAST_ERROR_IF(setpgid(0, 0) < 0);\r\n            THROW_LAST_ERROR_IF(execvp(argv[0], const_cast<char *const *>(argv.data())) < 0);\r\n        } else if (childPid > 0) {\r\n            /* move child to own process group to avoid interfere with Process Monitor */\r\n            THROW_LAST_ERROR_IF(setpgid(childPid, childPid) < 0);\r\n        }\r\n    }\r\n    CATCH_LOG();\r\n\r\n    // Ensure that the child process exits.\r\n    if (childPid == 0) {\r\n        _exit(1);\r\n    }\r\n\r\n    if (childPid > 0) try {\r\n        int status;\r\n        THROW_LAST_ERROR_IF((waitPid = waitpid(childPid, &status, 0)) < 0);\r\n        if (WIFEXITED(status)) {\r\n            success = (WEXITSTATUS(status) == 0);\r\n        }\r\n    }\r\n    CATCH_LOG();\r\n\r\n    try {\r\n        for (std::vector<const char *>::iterator it = argv.begin(); *it != nullptr; it++) {\r\n            cmd += *it;\r\n            cmd += \" \";\r\n        }\r\n        LOG_INFO(\"FontMonitor: pid:%d exited with %s, %s\",\r\n            waitPid, success ? \"success\" : \"fail\", cmd.c_str());\r\n    }\r\n    CATCH_LOG();\r\n\r\n    return success;\r\n}\r\n\r\nvoid wslgd::FontFolder::ModifyX11FontPath(bool isAdd)\r\n{\r\n    std::vector<const char*> argv;\r\n    sleep(2); /* workaround for optional fonts.alias, wait 2 sec before invoking xset */\r\n    if (m_isPathAdded != isAdd) try {\r\n        argv.push_back(c_xset);\r\n        argv.push_back(isAdd ? \"+fp\" : \"-fp\");\r\n        argv.push_back(m_path.c_str());\r\n        argv.push_back(nullptr);\r\n        if (ExecuteShellCommand(std::move(argv))) {\r\n            m_isPathAdded = isAdd;\r\n            /* let X server reread font database */\r\n            argv.clear();\r\n            argv.push_back(c_xset);\r\n            argv.push_back(\"fp\");\r\n            argv.push_back(\"rehash\");\r\n            argv.push_back(nullptr);\r\n            ExecuteShellCommand(std::move(argv));\r\n        }\r\n    }\r\n    CATCH_LOG();\r\n}\r\n\r\nwslgd::FontMonitor::FontMonitor()\r\n{\r\n}\r\n\r\nvoid wslgd::FontMonitor::DumpMonitorFolders()\r\n{\r\n    try {\r\n        std::map<std::string, std::unique_ptr<FontFolder>>::iterator it;\r\n        for (it = m_fontMonitorFolders.begin(); it != m_fontMonitorFolders.end(); it++)\r\n            LOG_INFO(\"FontMonitor: monitoring %s, and it is %s to X11 font path\", it->first.c_str(),\r\n                it->second->IsPathAdded() ? \"added\" : \"*not* added\");\r\n    }\r\n    CATCH_LOG();\r\n}\r\n\r\nvoid wslgd::FontMonitor::AddMonitorFolder(const char *path)\r\n{\r\n    try {\r\n        std::string monitorPath(path);\r\n        // checkf if path is tracked already.\r\n        if (m_fontMonitorFolders.find(monitorPath) == m_fontMonitorFolders.end()) {\r\n            std::unique_ptr<FontFolder> fontFolder(new FontFolder(m_fd.get(), path));\r\n            if (fontFolder.get()->GetWd() >= 0) {\r\n                m_fontMonitorFolders.insert(std::make_pair(std::move(monitorPath), std::move(fontFolder)));\r\n                // If this is mount path, only track under X11 folder if it's already exist.\r\n                if (strcmp(path, USER_DISTRO_FONT_PATH) == 0) {\r\n                    if (std::filesystem::exists(USER_DISTRO_FONT_PATH \"/X11\")) {\r\n                        AddMonitorFolder(USER_DISTRO_FONT_PATH \"/X11\");\r\n                    }\r\n                } else {\r\n                    // Otherwise, add all existing subfolders to track.\r\n                    for (auto& dir_entry : std::filesystem::directory_iterator{path}) {\r\n                        if (dir_entry.is_directory()) {\r\n                            AddMonitorFolder(dir_entry.path().c_str());\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        } else {\r\n            LOG_INFO(\"FontMonitor: %s is already tracked\", path);\r\n        }\r\n    }\r\n    CATCH_LOG();\r\n}\r\n\r\nvoid wslgd::FontMonitor::RemoveMonitorFolder(const char *path)\r\n{\r\n    LOG_INFO(\"FontMonitor: removing monitoring %s\", path);\r\n\r\n    try {\r\n        std::string monitorPath(path);\r\n        m_fontMonitorFolders.erase(monitorPath);\r\n    }\r\n    CATCH_LOG();\r\n}\r\n\r\nvoid wslgd::FontMonitor::HandleFolderEvent(struct inotify_event *event)\r\n{\r\n    try {\r\n        std::map<std::string, std::unique_ptr<FontFolder>>::iterator it;\r\n        for (it = m_fontMonitorFolders.begin(); it != m_fontMonitorFolders.end(); it++) {\r\n            if (event->wd == it->second->GetWd()) {\r\n                if (event->mask & (IN_CREATE|IN_MOVED_TO)) {\r\n                    bool addMonitorFolder = true;\r\n                    std::filesystem::path fullPath(it->second->GetPath());\r\n                    if (fullPath.compare(USER_DISTRO_FONT_PATH) == 0) {\r\n                        /* Immediately under mount folder, only monitor \"X11\" and its subfolder */\r\n                        addMonitorFolder = (strcmp(event->name, \"X11\") == 0);\r\n                    }\r\n                    if (addMonitorFolder) {\r\n                        fullPath /= event->name;\r\n                        AddMonitorFolder(fullPath.c_str());\r\n                    }\r\n                } else if (event->mask & (IN_DELETE|IN_MOVED_FROM)) {\r\n                    std::filesystem::path fullPath(it->second->GetPath());\r\n                    fullPath /= event->name;\r\n                    RemoveMonitorFolder(fullPath.c_str());\r\n                }\r\n                break;\r\n            }\r\n        }\r\n    }\r\n    CATCH_LOG();\r\n}\r\n\r\nvoid wslgd::FontMonitor::HandleFontsDirEvent(struct inotify_event *event)\r\n{\r\n    try {\r\n        std::map<std::string, std::unique_ptr<FontFolder>>::iterator it;\r\n        for (it = m_fontMonitorFolders.begin(); it != m_fontMonitorFolders.end(); it++) {\r\n            if (event->wd == it->second->GetWd()) {\r\n                if (event->mask & (IN_CREATE|IN_CLOSE_WRITE|IN_MOVED_TO)) {\r\n                    std::filesystem::path fonts_dir(it->second->GetPath());\r\n                    fonts_dir /= event->name;\r\n                    THROW_LAST_ERROR_IF(access(fonts_dir.c_str(), R_OK) != 0);\r\n                    it->second->ModifyX11FontPath(true);\r\n                } else if (event->mask & (IN_DELETE|IN_MOVED_FROM)) {\r\n                    it->second->ModifyX11FontPath(false);\r\n                }\r\n                break;\r\n            }\r\n        }\r\n    }\r\n    CATCH_LOG();\r\n}\r\n        \r\nvoid* wslgd::FontMonitor::FontMonitorThread(void *context)\r\n{\r\n    FontMonitor *This = reinterpret_cast<FontMonitor*>(context);\r\n    struct inotify_event *event;\r\n    int len, cur;\r\n    char buf[10 * (sizeof *event + 256)];\r\n\r\n    LOG_INFO(\"FontMonitor: monitoring thread started.\");\r\n\r\n    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);\r\n    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);\r\n\r\n    // Dump currently tracking folders.\r\n    This->DumpMonitorFolders();\r\n\r\n    // Start listening folder add/remove.\r\n    for (;;) {\r\n        len = read(This->GetFd(), buf, sizeof buf);\r\n        cur = 0;\r\n        while (cur < len) {\r\n            event = (struct inotify_event *)&buf[cur];\r\n            if (event->len) {\r\n                if (event->mask & IN_ISDIR) {\r\n                    // A directory is added or removed.\r\n                    This->HandleFolderEvent(event);\r\n                } else if (strcmp(event->name, c_fontsdir) == 0) {\r\n                    // A fonts.dir is added or removed.\r\n                    This->HandleFontsDirEvent(event);\r\n                }\r\n            }\r\n            cur += (sizeof *event + event->len);\r\n        }\r\n    }\r\n    // never hit here.\r\n    assert(true);\r\n\r\n    return 0;\r\n}\r\n\r\nint wslgd::FontMonitor::Start()\r\n{\r\n    bool succeeded = false;\r\n\r\n    assert(m_fontMonitorFolders.empty());\r\n    assert(!m_fontMonitorThread);\r\n\r\n    try {\r\n        // xset must be installed.\r\n        THROW_LAST_ERROR_IF(access(c_xset, X_OK) < 0);\r\n\r\n        // if user distro mount folder does not exist, bail out.\r\n        THROW_LAST_ERROR_IF_FALSE(std::filesystem::exists(USER_DISTRO_MOUNT_PATH));\r\n\r\n        bool userDistroFontPathExists = std::filesystem::exists(USER_DISTRO_FONT_PATH);\r\n        bool altDistroFontPathExists = std::filesystem::exists(ALT_DISTRO_FONT_PATH);\r\n\r\n        // and check fonts path inside user distro.\r\n        THROW_LAST_ERROR_IF_FALSE(userDistroFontPathExists || altDistroFontPathExists);\r\n\r\n        // start monitoring on mounted font folder.\r\n        wil::unique_fd fd(inotify_init());\r\n        THROW_LAST_ERROR_IF(!fd);\r\n        m_fd.reset(fd.release());\r\n        \r\n        // add both the default and alternative font paths if they exist.\r\n        if (userDistroFontPathExists) {\r\n            AddMonitorFolder(USER_DISTRO_FONT_PATH);\r\n        }\r\n        if (altDistroFontPathExists) {\r\n            AddMonitorFolder(ALT_DISTRO_FONT_PATH);\r\n        }\r\n\r\n        // Create font folder monitor thread.\r\n        THROW_LAST_ERROR_IF(pthread_create(&m_fontMonitorThread, NULL, FontMonitorThread, (void*)this) < 0);\r\n\r\n        succeeded = true;\r\n    }\r\n    CATCH_LOG();\r\n\r\n    if (!succeeded) {\r\n        Stop();\r\n        return -1;\r\n    }\r\n\r\n    return 0;\r\n}\r\n\r\nvoid wslgd::FontMonitor::Stop()\r\n{\r\n    // Stop font folder monitor thread.\r\n    if (m_fontMonitorThread) {\r\n        pthread_cancel(m_fontMonitorThread);\r\n        pthread_join(m_fontMonitorThread, NULL);\r\n        m_fontMonitorThread = 0;\r\n    }\r\n\r\n    // Remove both the default and alternative font paths if they were added.\r\n    if (m_fontMonitorFolders.find(USER_DISTRO_FONT_PATH) != m_fontMonitorFolders.end()) {\r\n        RemoveMonitorFolder(USER_DISTRO_FONT_PATH);\r\n    }\r\n    if (m_fontMonitorFolders.find(ALT_DISTRO_FONT_PATH) != m_fontMonitorFolders.end()) {\r\n        RemoveMonitorFolder(ALT_DISTRO_FONT_PATH);\r\n    }\r\n\r\n    m_fontMonitorFolders.clear();\r\n    m_fd.reset();\r\n\r\n    LOG_INFO(\"FontMonitor: monitoring stopped.\");\r\n}\r\n"
  },
  {
    "path": "WSLGd/FontMonitor.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#pragma once\r\n#include \"precomp.h\"\r\n\r\nnamespace wslgd\r\n{\r\n    class FontFolder\r\n    {\r\n    public:\r\n        FontFolder(int fd, const char *path);\r\n        ~FontFolder();\r\n\r\n        void ModifyX11FontPath(bool add);\r\n\r\n        static bool ExecuteShellCommand(std::vector<const char*>&& argv);\r\n\r\n        int GetFd() const { return m_fd.get(); }\r\n        int GetWd() const { return m_wd; }\r\n\r\n        bool IsPathAdded() const { return m_isPathAdded; }\r\n        const char *GetPath() const { return m_path.c_str(); }\r\n\r\n    private:\r\n        wil::unique_fd m_fd; /* from FontMonitor's inotify_init() */\r\n        int m_wd = -1; /* from inotify_add_watch() for this folder */\r\n        std::string m_path; /* this folder path */\r\n        bool m_isPathAdded = false; /* whether font path is added to X11 font path */\r\n    };\r\n\r\n    class FontMonitor\r\n    {\r\n    public:\r\n        FontMonitor();\r\n        ~FontMonitor() { Stop(); }\r\n\r\n        FontMonitor(const FontMonitor&) = delete;\r\n        void operator=(const FontMonitor&) = delete;\r\n\r\n        int Start();\r\n        void Stop();\r\n\r\n        static void* FontMonitorThread(void *context);\r\n\r\n        void AddMonitorFolder(const char *path);\r\n        void RemoveMonitorFolder(const char *path);\r\n        void DumpMonitorFolders();\r\n\r\n        void HandleFolderEvent(struct inotify_event *event);\r\n        void HandleFontsDirEvent(struct inotify_event *event);\r\n\r\n        int GetFd() const { return m_fd.get(); }\r\n\r\n    private:\r\n        wil::unique_fd m_fd; /* from inotify_init() */\r\n        std::map<std::string, std::unique_ptr<FontFolder>> m_fontMonitorFolders{};\r\n        pthread_t m_fontMonitorThread = 0;\r\n    };\r\n}\r\n"
  },
  {
    "path": "WSLGd/Makefile",
    "content": "CXX := clang++\nLDFLAGS := -lcap\nTARGET := WSLGd\nSRC_DIRS := .\nINSTALL := install -p\nINSTALL_PREFIX= $(DESTDIR)/$(PREFIX)/bin\n\nSRCS := $(shell find $(SRC_DIRS) -name \"*.cpp\" -or -name \"*.c\" -or -name \"*.s\")\nOBJS := $(addsuffix .o,$(basename $(SRCS)))\nDEPS := $(OBJS:.o=.d)\n\nINC_DIRS := $(shell find $(SRC_DIRS) -type d)\nINC_FLAGS := $(addprefix -I,$(INC_DIRS))\n\nCPPFLAGS := $(INC_FLAGS) -MMD -MP -std=c++17\n\n$(TARGET): $(OBJS)\n\t$(CXX) $(LDFLAGS) $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS)\n\n.PHONY: clean\nclean:\n\t$(RM) $(TARGET) $(OBJS) $(DEPS)\n\ninstall:\n\t$(INSTALL) $(TARGET) $(INSTALL_PREFIX)\n\n-include $(DEPS)"
  },
  {
    "path": "WSLGd/ProcessMonitor.cpp",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include \"ProcessMonitor.h\"\r\n#include \"common.h\"\r\n\r\nwslgd::ProcessMonitor::ProcessMonitor(const char* userName)\r\n{\r\n    THROW_ERRNO_IF(ENOENT, !(m_user = getpwnam(userName)));\r\n}\r\n\r\npasswd* wslgd::ProcessMonitor::GetUserInfo() const\r\n{\r\n    return m_user;\r\n}\r\n\r\nextern char **environ;\r\n\r\nint wslgd::ProcessMonitor::LaunchProcess(\r\n    std::vector<std::string>&& argv,\r\n    std::vector<cap_value_t>&& capabilities,\r\n    std::vector<std::string>&& env)\r\n{\r\n    int childPid;\r\n    THROW_LAST_ERROR_IF((childPid = fork()) < 0);\r\n\r\n    if (childPid == 0) try {\r\n        // Construct a null-terminated argument array.\r\n        std::vector<const char*> arguments;\r\n        for (auto &arg : argv) {\r\n            arguments.push_back(arg.c_str());\r\n        }\r\n\r\n        arguments.push_back(nullptr);\r\n\r\n        // If any capabilities were specified, set the keepcaps flag so they are not lost across setuid.\r\n        if (!capabilities.empty()) {\r\n            THROW_LAST_ERROR_IF(prctl(PR_SET_KEEPCAPS, 1) < 0);\r\n        }\r\n\r\n        // Construct a null-terminated environment array.\r\n        std::vector<const char*> environments;\r\n        for (char **c = environ; *c; c++) {\r\n            environments.push_back(*c);\r\n        }\r\n        for (auto &s : env) {\r\n            if (s.size()) {\r\n                environments.push_back(s.c_str());\r\n            }\r\n        }\r\n\r\n        environments.push_back(nullptr);\r\n\r\n        // Set user settings.\r\n        THROW_LAST_ERROR_IF(setgid(m_user->pw_gid) < 0);\r\n        THROW_LAST_ERROR_IF(initgroups(m_user->pw_name, m_user->pw_gid) < 0);\r\n        THROW_LAST_ERROR_IF(setuid(m_user->pw_uid) < 0);\r\n        THROW_LAST_ERROR_IF(chdir(m_user->pw_dir) < 0);\r\n\r\n        // Apply additional capabilities to the process.\r\n        if (!capabilities.empty()) {\r\n            cap_t caps{};\r\n            THROW_LAST_ERROR_IF((caps = cap_get_proc()) == NULL);\r\n            auto freeCapabilities = wil::scope_exit([&caps]() { cap_free(caps); });\r\n            THROW_LAST_ERROR_IF(cap_set_flag(caps, CAP_PERMITTED, capabilities.size(), capabilities.data(), CAP_SET) < 0);\r\n            THROW_LAST_ERROR_IF(cap_set_flag(caps, CAP_EFFECTIVE, capabilities.size(), capabilities.data(), CAP_SET) < 0);\r\n            THROW_LAST_ERROR_IF(cap_set_flag(caps, CAP_INHERITABLE, capabilities.size(), capabilities.data(), CAP_SET) < 0);\r\n            THROW_LAST_ERROR_IF(cap_set_proc(caps) < 0);\r\n            for (auto &cap : capabilities) {\r\n                THROW_LAST_ERROR_IF(cap_set_ambient(cap, CAP_SET) < 0);\r\n            }\r\n        }\r\n\r\n        // Run the process as the specified user.\r\n        THROW_LAST_ERROR_IF(execvpe(arguments[0], const_cast<char *const *>(arguments.data()), const_cast<char *const *>(environments.data())) < 0);\r\n    }\r\n    CATCH_LOG();\r\n\r\n    // Ensure that the child process exits.\r\n    if (childPid == 0) {\r\n        _exit(1);\r\n    }\r\n\r\n    m_children[childPid] = ProcessInfo{std::move(argv), std::move(capabilities), std::move(env)};\r\n    return childPid;\r\n}\r\n\r\nint wslgd::ProcessMonitor::Run() try {\r\n    std::map<std::string, std::vector<time_t>> crashes;\r\n\r\n    for (;;) {\r\n        // Reap any zombie child processes and re-launch any tracked processes.\r\n        int pid;\r\n        int status;\r\n\r\n        /* monitor only processes within same group as caller */\r\n        THROW_LAST_ERROR_IF((pid = waitpid(0, &status, 0)) <= 0);\r\n\r\n        auto found = m_children.find(pid);\r\n        if (found != m_children.end()) {\r\n            if (!found->second.argv.empty()) {\r\n                std::string cmd;\r\n                for (auto &arg : found->second.argv) {\r\n                    cmd += arg.c_str();\r\n                    cmd += \" \";\r\n                }\r\n\r\n                if (WIFEXITED(status)) {\r\n                    LOG_INFO(\"pid %d exited with status %d, %s\", pid, WEXITSTATUS(status), cmd.c_str());\r\n                } else if (WIFSIGNALED(status)) {\r\n                    LOG_INFO(\"pid %d terminated with signal %d, %s\", pid, WTERMSIG(status), cmd.c_str());\r\n                } else {\r\n                    LOG_ERROR(\"pid %d return unknown status %d, %s\", pid, status, cmd.c_str());\r\n                }\r\n\r\n                auto& crashTimestamps = crashes[cmd];\r\n                auto now = time(nullptr);\r\n                crashTimestamps.erase(std::remove_if(crashTimestamps.begin(), crashTimestamps.end(), [&](auto ts) { return ts < now - 60; }), crashTimestamps.end());\r\n                crashTimestamps.emplace_back(now);\r\n\r\n                if (crashTimestamps.size() > 10) {\r\n                    LOG_INFO(\"%s exited more than 10 times in 60 seconds, not starting it again\", cmd.c_str());\r\n                } else {\r\n                    LaunchProcess(std::move(found->second.argv), std::move(found->second.capabilities), std::move(found->second.env));\r\n                }\r\n            }\r\n\r\n            m_children.erase(found);\r\n\r\n        } else {\r\n            LOG_INFO(\"untracked pid %d exited with status 0x%x.\", pid, status);\r\n        }\r\n    }\r\n\r\n    return 0;\r\n}\r\nCATCH_RETURN_ERRNO();\r\n"
  },
  {
    "path": "WSLGd/ProcessMonitor.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#pragma once\r\n#include \"precomp.h\"\r\n\r\nnamespace wslgd\r\n{\r\n    class ProcessMonitor\r\n    {\r\n    public:\r\n        ProcessMonitor(const char* username);\r\n        ProcessMonitor(const ProcessMonitor&) = delete;\r\n        void operator=(const ProcessMonitor&) = delete;\r\n\r\n        passwd* GetUserInfo() const;\r\n        int LaunchProcess(std::vector<std::string>&& argv,\r\n                          std::vector<cap_value_t>&& capabilities = {},\r\n                          std::vector<std::string>&& env = {});\r\n        int Run();\r\n\r\n    private:\r\n        struct ProcessInfo\r\n        {\r\n            std::vector<std::string> argv;\r\n            std::vector<cap_value_t> capabilities;\r\n            std::vector<std::string> env;\r\n        };\r\n\r\n        std::map<int, ProcessInfo> m_children{};\r\n        passwd* m_user;\r\n    };\r\n}\r\n"
  },
  {
    "path": "WSLGd/common.h",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n#pragma once\n#define SHARE_PATH \"/mnt/wslg\"\n#define USER_DISTRO_MOUNT_PATH SHARE_PATH \"/distro\"\n\nvoid LogPrint(int level, const char *func, int line, const char *fmt, ...) noexcept;\n#define LOG_LEVEL_EXCEPTION 3\n#define LOG_LEVEL_ERROR     4\n#define LOG_LEVEL_INFO      5\n#define LOG_ERROR(fmt, ...) LogPrint(LOG_LEVEL_ERROR, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)\n#define LOG_INFO(fmt, ...) LogPrint(LOG_LEVEL_INFO, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)\n"
  },
  {
    "path": "WSLGd/lxwil.h",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n#pragma once\n\nnamespace wil\n{\n\n#define FAIL_FAST() raise(SIGABRT);\n#define FAIL_FAST_CAUGHT_EXCEPTION() FAIL_FAST()\n#define FAIL_FAST_IF(condition) if ((condition)) { FAIL_FAST() }\n\ntypedef void LogFunction(const char *message, const char *exceptionDescription) noexcept;\ninline LogFunction *g_LogExceptionCallback{};\n\nnamespace details\n{\n    struct FailureInfo\n    {\n        const char *File;\n        int Line;\n        const char *Function;\n    };\n}\n\nclass ResultException : public std::exception\n{\npublic:\n    ResultException(int result, details::FailureInfo info) noexcept\n        : m_Result{ result }, m_Info{ info }\n    {\n    }\n\n    ~ResultException() noexcept\n    {\n        delete[] m_What;\n    }\n\n    const char *what() const noexcept override\n    {\n        constexpr size_t bufferSize = 4096;\n        if (m_What == nullptr)\n        {\n            m_What = new(std::nothrow) char[bufferSize]{};\n            if (m_What == nullptr)\n            {\n                return strerror(m_Result);\n            }\n\n            snprintf(m_What, bufferSize, \"%s @%s:%d (%s)\\n\", strerror(m_Result), m_Info.File, m_Info.Line, m_Info.Function);\n        }\n\n        return m_What;\n    }\n\n    int GetErrorCode() const noexcept\n    {\n        return m_Result;\n    }\n\nprivate:\n    mutable char *m_What{};\n    int m_Result;\n    details::FailureInfo m_Info;\n};\n\nnamespace details\n{\n    template <typename TLambda>\n    class lambda_call\n    {\n    public:\n        lambda_call(const lambda_call&) = delete;\n        lambda_call& operator=(const lambda_call&) = delete;\n        lambda_call& operator=(lambda_call&& other) = delete;\n\n        explicit lambda_call(TLambda&& lambda) noexcept : m_lambda(std::move(lambda))\n        {\n            static_assert(std::is_same<decltype(lambda()), void>::value, \"scope_exit lambdas must not have a return value\");\n            static_assert(!std::is_lvalue_reference<TLambda>::value && !std::is_rvalue_reference<TLambda>::value,\n                \"scope_exit should only be directly used with a lambda\");\n        }\n\n        lambda_call(lambda_call&& other) noexcept : m_lambda(std::move(other.m_lambda)), m_call(other.m_call)\n        {\n            other.m_call = false;\n        }\n\n        ~lambda_call() noexcept\n        {\n            reset();\n        }\n\n        // Ensures the scope_exit lambda will not be called\n        void release() noexcept\n        {\n            m_call = false;\n        }\n\n        // Executes the scope_exit lambda immediately if not yet run; ensures it will not run again\n        void reset() noexcept\n        {\n            if (m_call)\n            {\n                m_call = false;\n                m_lambda();\n            }\n        }\n\n        // Returns true if the scope_exit lambda is still going to be executed\n        explicit operator bool() const noexcept\n        {\n            return m_call;\n        }\n\n    protected:\n        TLambda m_lambda;\n        bool m_call = true;\n    };\n\n    inline void ThrowErrorIf(bool condition, int error, FailureInfo info)\n    {\n        if (condition)\n        {\n            throw ::wil::ResultException(error, info);\n        }\n    }\n\n    inline void LogFailure(const char *message, const char *exceptionDescription) noexcept\n    {\n        auto callback = g_LogExceptionCallback;\n        if (callback != nullptr)\n        {\n            callback(message, exceptionDescription);\n        }\n        else\n        {\n            if (message != nullptr)\n            {\n                fputs(message, stderr);\n                fputs(\"\\n\", stderr);\n            }\n\n            if (exceptionDescription != nullptr)\n            {\n                fputs(\"Exception: \", stderr);\n                fputs(exceptionDescription, stderr);\n                fputs(\"\\n\", stderr);\n            }\n        }\n    }\n\n    inline void LogCaughtException(const char *message)\n    {\n        try\n        {\n            throw;\n        }\n        catch (const std::exception &ex)\n        {\n            LogFailure(message, ex.what());\n        }\n        catch (...)\n        {\n            LogFailure(message, nullptr);\n        }\n    }\n}\n\ninline int ResultFromCaughtException()\n{\n    try\n    {\n        throw;\n    }\n    catch (wil::ResultException &ex)\n    {\n        return ex.GetErrorCode();\n    }\n    catch (std::bad_alloc &)\n    {\n        return ENOMEM;\n    }\n    catch (...)\n    {\n    }\n\n    // Unknown exception type.\n    return EINVAL;\n}\n\n#define __WIL_ERROR_INFO { __FILE__, __LINE__, __FUNCTION__ }\n\n#define THROW_ERRNO(Error) throw ::wil::ResultException(Error, __WIL_ERROR_INFO)\n#define THROW_ERRNO_IF(Error, Condition) ::wil::details::ThrowErrorIf((Condition), (Error), __WIL_ERROR_INFO)\n#define THROW_ERRNO_IF_FALSE(Error, Condition) ::wil::details::ThrowErrorIf(!(Condition), (Error), __WIL_ERROR_INFO)\n#define THROW_LAST_ERROR_IF(Condition) THROW_ERRNO_IF(errno, (Condition));\n#define THROW_LAST_ERROR_IF_FALSE(Condition) THROW_ERRNO_IF_FALSE(errno, (Condition))\n\n#define THROW_INVALID() THROW_ERRNO(EINVAL)\n#define THROW_INVALID_IF(Condition) THROW_ERRNO_IF(EINVAL, (Condition))\n#define THROW_UNEXPECTED_IF(Condition) THROW_ERRNO_IF(EINVAL, (Condition))\n\n#define LOG_CAUGHT_EXCEPTION() ::wil::details::LogCaughtException(nullptr);\n#define LOG_CAUGHT_EXCEPTION_MSG(msg) ::wil::details::LogCaughtException(msg);\n#define RETURN_CAUGHT_EXCEPTION() return -::wil::ResultFromCaughtException()\n#define CATCH_RETURN() catch (...) { RETURN_CAUGHT_EXCEPTION(); }\n#define CATCH_RETURN_ERRNO() \\\n    catch (...) \\\n    { \\\n        LOG_CAUGHT_EXCEPTION(); \\\n        errno = ::wil::ResultFromCaughtException(); \\\n        return -1; \\\n    }\n\n#define CATCH_LOG() catch (...) { LOG_CAUGHT_EXCEPTION(); }\n#define CATCH_LOG_MSG(msg) catch (...) { LOG_CAUGHT_EXCEPTION_MSG(msg); }\n\nclass unique_dir\n{\npublic:\n    static constexpr DIR* invalid_dir = nullptr;\n\n    unique_dir(DIR* dir = invalid_dir) noexcept\n        : m_Dir{ dir }\n    {\n    }\n\n    ~unique_dir() noexcept\n    {\n        reset();\n    }\n\n    unique_dir(const unique_dir &) = delete;\n    unique_dir& operator=(const unique_dir &) = delete;\n\n    unique_dir(unique_dir &&other) noexcept\n        : m_Dir{ other.m_Dir }\n    {\n        other.m_Dir = invalid_dir;\n    }\n\n    unique_dir& operator=(unique_dir &&other) noexcept\n    {\n        std::swap(m_Dir, other.m_Dir);\n        return *this;\n    }\n\n    explicit operator bool() const noexcept\n    {\n        return m_Dir != invalid_dir;\n    }\n\n    DIR* get() const noexcept\n    {\n        return m_Dir;\n    }\n\n    void reset(DIR *dir = invalid_dir) noexcept\n    {\n        if (m_Dir != invalid_dir)\n        {\n            closedir(m_Dir);\n        }\n\n        m_Dir = dir;\n    }\n\n    DIR* release() noexcept\n    {\n        DIR *dir = m_Dir;\n        m_Dir = invalid_dir;\n        return dir;\n    }\n\n    friend void swap(unique_dir &dir1, unique_dir &dir2)\n    {\n        std::swap(dir1.m_Dir, dir2.m_Dir);\n    }\n\nprivate:\n    DIR *m_Dir;\n};\n\nclass unique_fd\n{\npublic:\n    static constexpr int invalid_fd = -1;\n\n    unique_fd(int fd = invalid_fd) noexcept\n        : m_Fd{ fd }\n    {\n    }\n\n    ~unique_fd() noexcept\n    {\n        reset();\n    }\n\n    unique_fd(const unique_fd &) = delete;\n    unique_fd& operator=(const unique_fd &) = delete;\n\n    unique_fd(unique_fd &&other) noexcept\n        : m_Fd{ other.m_Fd }\n    {\n        other.m_Fd = invalid_fd;\n    }\n\n    unique_fd& operator=(unique_fd &&other) noexcept\n    {\n        std::swap(m_Fd, other.m_Fd);\n        return *this;\n    }\n\n    explicit operator bool() const noexcept\n    {\n        return m_Fd >= 0;\n    }\n\n    int get() const noexcept\n    {\n        return m_Fd;\n    }\n\n    void reset(int fd = invalid_fd) noexcept\n    {\n        if (m_Fd >= 0)\n        {\n            close(m_Fd);\n        }\n\n        m_Fd = fd;\n    }\n\n    int release() noexcept\n    {\n        int fd = m_Fd;\n        m_Fd = invalid_fd;\n        return fd;\n    }\n\n    friend void swap(unique_fd &fd1, unique_fd &fd2)\n    {\n        std::swap(fd1.m_Fd, fd2.m_Fd);\n    }\n\nprivate:\n    int m_Fd;\n};\n\nclass unique_file\n{\npublic:\n    static constexpr FILE* invalid_file = nullptr;\n\n    unique_file(FILE* file = invalid_file) noexcept\n        : m_File{ file }\n    {\n    }\n\n    ~unique_file() noexcept\n    {\n        reset();\n    }\n\n    unique_file(const unique_file &) = delete;\n    unique_file& operator=(const unique_file &) = delete;\n\n    unique_file(unique_file &&other) noexcept\n        : m_File{ other.m_File }\n    {\n        other.m_File = invalid_file;\n    }\n\n    unique_file& operator=(unique_file &&other) noexcept\n    {\n        std::swap(m_File, other.m_File);\n        return *this;\n    }\n\n    explicit operator bool() const noexcept\n    {\n        return m_File != invalid_file;\n    }\n\n    FILE* get() const noexcept\n    {\n        return m_File;\n    }\n\n    void reset(FILE *file = invalid_file) noexcept\n    {\n        if (m_File != invalid_file)\n        {\n            fclose(m_File);\n        }\n\n        m_File = file;\n    }\n\n    FILE* release() noexcept\n    {\n        FILE *file = m_File;\n        m_File = invalid_file;\n        return file;\n    }\n\n    friend void swap(unique_file &file1, unique_file &file2)\n    {\n        std::swap(file1.m_File, file2.m_File);\n    }\n\nprivate:\n    FILE *m_File;\n};\n\n/** Returns an object that executes the given lambda when destroyed.\nCapture the object with 'auto'; use reset() to execute the lambda early or release() to avoid\nexecution.  Exceptions thrown in the lambda will fail-fast; use scope_exit_log to avoid. */\ntemplate <typename TLambda>\n[[nodiscard]] inline auto scope_exit(TLambda&& lambda) noexcept\n{\n    return details::lambda_call<TLambda>(std::forward<TLambda>(lambda));\n}\n\nnamespace details\n{\n    template <unsigned long long flag>\n    struct verify_single_flag_helper\n    {\n        static_assert((flag != 0) && ((flag & (flag - 1)) == 0), \"Single flag expected, zero or multiple flags found\");\n        static const unsigned long long value = flag;\n    };\n\n    // Use size-specific casts to avoid sign extending numbers -- avoid warning C4310: cast truncates constant value\n    #define __WI_MAKE_UNSIGNED(val) \\\n        (sizeof(val) == 1 ? static_cast<unsigned char>(val) : \\\n         sizeof(val) == 2 ? static_cast<unsigned short>(val) : \\\n         sizeof(val) == 4 ? static_cast<unsigned long>(val) :  \\\n         static_cast<unsigned long long>(val))\n    #define __WI_IS_UNSIGNED_SINGLE_FLAG_SET(val) ((val) && !((val) & ((val) - 1)))\n    #define __WI_IS_SINGLE_FLAG_SET(val) __WI_IS_UNSIGNED_SINGLE_FLAG_SET(__WI_MAKE_UNSIGNED(val))\n\n    template <typename TVal, typename TFlags>\n    inline constexpr bool AreAllFlagsSetHelper(TVal val, TFlags flags)\n    {\n        return ((val & flags) == static_cast<decltype(val & flags)>(flags));\n    }\n\n    template <typename TVal>\n    inline constexpr bool IsSingleFlagSetHelper(TVal val)\n    {\n        return __WI_IS_SINGLE_FLAG_SET(val);\n    }\n\n    template <typename TVal>\n    inline constexpr bool IsClearOrSingleFlagSetHelper(TVal val)\n    {\n        return ((val == static_cast<std::remove_reference_t<TVal>>(0)) || IsSingleFlagSetHelper(val));\n    }\n\n    template <typename TVal, typename TMask, typename TFlags>\n    inline constexpr void UpdateFlagsInMaskHelper(TVal& val, TMask mask, TFlags flags)\n    {\n        val = static_cast<std::remove_reference_t<TVal>>((val & ~mask) | (flags & mask));\n    }\n\n    template <long>\n    struct variable_size;\n\n    template <>\n    struct variable_size<1>\n    {\n        typedef unsigned char type;\n    };\n\n    template <>\n    struct variable_size<2>\n    {\n        typedef unsigned short type;\n    };\n\n    template <>\n    struct variable_size<4>\n    {\n        typedef unsigned long type;\n    };\n\n    template <>\n    struct variable_size<8>\n    {\n        typedef unsigned long long type;\n    };\n\n    template <typename T>\n    struct variable_size_mapping\n    {\n        typedef typename variable_size<sizeof(T)>::type type;\n    };\n}\n\n/** Defines the unsigned type of the same width (1, 2, 4, or 8 bytes) as the given type.\nThis allows code to generically convert any enum class to it's corresponding underlying type. */\ntemplate <typename T>\nusing integral_from_enum = typename details::variable_size_mapping<T>::type;\n\n\n#define WI_StaticAssertSingleBitSet(flag)                   static_cast<decltype(flag)>(::wil::details::verify_single_flag_helper<static_cast<unsigned long long>(WI_EnumValue(flag))>::value)\n#define WI_IsAnyFlagSet(val, flags)                         (static_cast<decltype((val) & (flags))>(WI_EnumValue(val) & WI_EnumValue(flags)) != static_cast<decltype((val) & (flags))>(0))\n#define WI_IsFlagSet(val, flag)                             WI_IsAnyFlagSet(val, WI_StaticAssertSingleBitSet(flag))\n#define WI_EnumValue(val)                                   static_cast<::wil::integral_from_enum<decltype(val)>>(val)\n//! Evaluates as true if every bitflag specified in `flags` is set within `val`.\n#define WI_AreAllFlagsSet(val, flags)                       wil::details::AreAllFlagsSetHelper(val, flags)\n//! Set zero or more bitflags specified by `flags` in the variable `var`.\n#define WI_SetAllFlags(var, flags)                          ((var) |= (flags))\n//! Set a single compile-time constant `flag` in the variable `var`.\n#define WI_SetFlag(var, flag)                               WI_SetAllFlags(var, WI_StaticAssertSingleBitSet(flag))\n//! Clear zero or more bitflags specified by `flags` from the variable `var`.\n#define WI_ClearAllFlags(var, flags)                        ((var) &= ~(flags))\n//! Clear a single compile-time constant `flag` from the variable `var`.\n#define WI_ClearFlag(var, flag)                             WI_ClearAllFlags(var, WI_StaticAssertSingleBitSet(flag))\n\n#define WI_ASSERT(condition) assert(condition)\n\n}"
  },
  {
    "path": "WSLGd/main.cpp",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n#include \"precomp.h\"\n#include \"common.h\"\n#include \"ProcessMonitor.h\"\n#include \"FontMonitor.h\"\n\n#define CONFIG_FILE \".wslgconfig\"\n#define MSRDC_EXE \"msrdc.exe\"\n#define MSTSC_EXE \"mstsc.exe\"\n#define GDBSERVER_PATH \"/usr/bin/gdbserver\"\n#define WESTON_NOTIFY_SOCKET SHARE_PATH \"/weston-notify.sock\"\n#define DEFAULT_ICON_PATH \"/usr/share/icons\"\n#define USER_DISTRO_ICON_PATH USER_DISTRO_MOUNT_PATH DEFAULT_ICON_PATH\n#define MAX_RESERVED_PORT 1024\n\nconstexpr auto c_serviceIdTemplate = \"%08X-FACB-11E6-BD58-64006A7986D3\";\nconstexpr auto c_userName = \"wslg\";\n\nconstexpr auto c_dbusDir = \"/var/run/dbus\";\nconstexpr auto c_versionFile = \"/etc/versions.txt\";\nconstexpr auto c_versionMount = SHARE_PATH \"/versions.txt\";\nconstexpr auto c_shareDocsDir = \"/usr/share/doc\";\nconstexpr auto c_shareDocsMount = SHARE_PATH \"/doc\";\nconstexpr auto c_x11RuntimeDir = SHARE_PATH \"/.X11-unix\";\nconstexpr auto c_xdgRuntimeDir = SHARE_PATH \"/runtime-dir\";\nconstexpr auto c_stdErrLogFile = SHARE_PATH \"/stderr.log\";\n\nconstexpr auto c_sharedMemoryMountPoint = \"/mnt/shared_memory\";\nconstexpr auto c_sharedMemoryMountPointEnv = \"WSL2_SHARED_MEMORY_MOUNT_POINT\";\nconstexpr auto c_sharedMemoryObDirectoryPathEnv = \"WSL2_SHARED_MEMORY_OB_DIRECTORY\";\n\nconstexpr auto c_installPathEnv = \"WSL2_INSTALL_PATH\";\nconstexpr auto c_userProfileEnv = \"WSL2_USER_PROFILE\";\nconstexpr auto c_systemDistroEnvSection = \"system-distro-env\";\n\nconstexpr auto c_windowsSystem32 = \"/mnt/c/Windows/System32\";\n\nconstexpr auto c_westonShellDesktopEnv = \"WSL2_WESTON_SHELL_DESKTOP\";\n\nconstexpr auto c_westonRdprailShell = \"rdprail-shell\";\nconstexpr auto c_westonRdpdesktopShell = \"desktop-shell\";\n\nconstexpr auto c_rdpRailFile = \"wslg.rdp\";\nconstexpr auto c_rdpDesktopFile = \"wslg_desktop.rdp\";\n\nvoid LogPrint(int level, const char *func, int line, const char *fmt, ...) noexcept\n{\n    std::array<char, 128> buffer;\n    struct timeval tv;\n    struct tm *time;\n    va_list va_args;\n\n    gettimeofday(&tv, NULL);\n    time = localtime(&tv.tv_sec);\n    strftime(buffer.data(), buffer.size(), \"%H:%M:%S\", time);\n    fprintf(stderr, \"[%s.%03ld] <%d>WSLGd: %s:%u: \",\n        buffer.data(), (tv.tv_usec / 1000),\n        level, func, line);\n\n    va_start(va_args, fmt);\n    vfprintf(stderr, fmt, va_args);\n    va_end(va_args);\n    fprintf(stderr, \"\\n\");\n\n    return;\n}\n\nvoid LogException(const char *message, const char *exceptionDescription) noexcept\n{\n    LogPrint(LOG_LEVEL_EXCEPTION, __FUNCTION__, __LINE__, \"%s %s\", message ? message : \"Exception:\", exceptionDescription);\n    return;\n}\n\nbool IsNumeric(char *str)\n{\n    char* p;\n    if (!str)\n        return false;\n    strtol(str, &p, 10);\n    return *p == 0;\n}\n\nstd::string ToServiceId(unsigned int port)\n{\n    int size;\n    THROW_LAST_ERROR_IF((size = snprintf(nullptr, 0, c_serviceIdTemplate, port)) < 0);\n\n    std::string serviceId(size + 1, '\\0');\n    THROW_LAST_ERROR_IF(snprintf(&serviceId[0], serviceId.size(), c_serviceIdTemplate, port) < 0);\n\n    return serviceId;\n}\n\nstd::string TranslateWindowsPath(const char * Path)\n{\n    std::string commandLine = \"/usr/bin/wslpath -a \\\"\";\n    commandLine += Path;\n    commandLine += \"\\\"\";\n    std::array<char, 128> buffer;\n    std::string result;\n    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(commandLine.c_str(), \"r\"), pclose);\n    THROW_LAST_ERROR_IF(!pipe);\n\n    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {\n        result += buffer.data();\n    }\n    /* trim '\\n' from wslpath output */\n    while (result.back() == '\\n') {\n        result.pop_back();\n    }\n\n    THROW_ERRNO_IF(EINVAL, pclose(pipe.release()) != 0);\n\n    return result;\n}\n\nbool GetEnvBool(const char *EnvName, bool DefaultValue)\n{\n    char *s;\n\n    s = getenv(EnvName);\n    if (s) {\n        if (strcmp(s, \"true\") == 0)\n            return true;\n        else if (strcmp(s, \"false\") == 0)\n            return false;\n        else if (strcmp(s, \"1\") == 0)\n            return true;\n        else if (strcmp(s, \"0\") == 0)\n            return false;\n    }\n\n    return DefaultValue;\n}\n\nstd::string GetVmId()\n{\n    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(\"/usr/bin/wslinfo --vm-id -n\", \"r\"), pclose);\n    THROW_LAST_ERROR_IF(!pipe);\n\n    std::array<char, 128> buffer;\n    std::string result;\n    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {\n        result += buffer.data();\n    }\n\n    THROW_ERRNO_IF(EINVAL, pclose(pipe.release()) != 0);\n\n    return result;\n}\n\nvoid SetupOptionalEnv()\n{\n#if HAVE_WINPR\n    // Get the path to the WSLG config file.\n    std::string configFilePath = \"/mnt/c/ProgramData/Microsoft/WSL/\" CONFIG_FILE;\n    auto userProfile = getenv(c_userProfileEnv);\n    if (userProfile) {\n        configFilePath = TranslateWindowsPath(userProfile);\n        configFilePath += \"/\" CONFIG_FILE;\n    }\n\n    // Set additional environment variables.\n    wIniFile* wslgConfigIniFile = IniFile_New();\n    if (wslgConfigIniFile) {\n        if (IniFile_ReadFile(wslgConfigIniFile, configFilePath.c_str()) > 0) {\n            int numKeys = 0;\n            char **keyNames = IniFile_GetSectionKeyNames(wslgConfigIniFile, c_systemDistroEnvSection, &numKeys);\n            for (int n = 0; keyNames && n < numKeys; n++) {\n                const char *value = IniFile_GetKeyValueString(wslgConfigIniFile, c_systemDistroEnvSection, keyNames[n]);\n                if (value) {\n                    setenv(keyNames[n], value, true);\n                }\n            }\n\n            free(keyNames);\n        }\n\n        IniFile_Free(wslgConfigIniFile);\n    }\n#endif // HAVE_WINPR\n\n    return;\n}\n\nint SetupReadyNotify(const char *socket_path)\n{\n    struct sockaddr_un addr = {};\n    socklen_t size, name_size;\n\n    addr.sun_family = AF_LOCAL;\n    name_size = snprintf(addr.sun_path, sizeof addr.sun_path,\n                         \"%s\", socket_path) + 1;\n    size = offsetof(struct sockaddr_un, sun_path) + name_size;\n    unlink(addr.sun_path);\n\n    wil::unique_fd socketFd{socket(PF_LOCAL, SOCK_SEQPACKET, 0)};\n    THROW_LAST_ERROR_IF(!socketFd);\n\n    THROW_LAST_ERROR_IF(bind(socketFd.get(), reinterpret_cast<const sockaddr*>(&addr), size) < 0);\n    THROW_LAST_ERROR_IF(listen(socketFd.get(), 1) < 0);\n\n    return socketFd.release();\n}\n\nvoid WaitForReadyNotify(int notifyFd)\n{\n    // wait under client connects */\n    wil::unique_fd fd(accept(notifyFd, 0, 0));\n    THROW_LAST_ERROR_IF(!fd);\n}\n\nint main(int Argc, char *Argv[])\ntry {\n    wil::g_LogExceptionCallback = LogException;\n\n    // Restore default processing for SIGCHLD as both WSLGd and Xwayland depends on this.\n    signal(SIGCHLD, SIG_DFL);\n\n    // Create a process monitor to track child processes\n    wslgd::ProcessMonitor monitor(c_userName);\n    auto passwordEntry = monitor.GetUserInfo();\n\n    // Set required environment variables.\n    struct envVar{ const char* name; const char* value; bool override; };\n    envVar variables[] = {\n        {\"HOME\", passwordEntry->pw_dir, true},\n        {\"USER\", passwordEntry->pw_name, true},\n        {\"LOGNAME\", passwordEntry->pw_name, true},\n        {\"SHELL\", passwordEntry->pw_shell, true},\n        {\"PATH\", \"/usr/sbin:/usr/bin:/sbin:/bin:/usr/games\", true},\n        {\"XDG_RUNTIME_DIR\", c_xdgRuntimeDir, false},\n        {\"WAYLAND_DISPLAY\", \"wayland-0\", false},\n        {\"DISPLAY\", \":0\", false},\n        {\"XCURSOR_PATH\", USER_DISTRO_ICON_PATH \":\" DEFAULT_ICON_PATH , false},\n        {\"XCURSOR_THEME\", \"whiteglass\", false},\n        {\"XCURSOR_SIZE\", \"16\", false},\n        {\"PULSE_SERVER\", SHARE_PATH \"/PulseServer\", false},\n        {\"PULSE_AUDIO_RDP_SINK\", SHARE_PATH \"/PulseAudioRDPSink\", false},\n        {\"PULSE_AUDIO_RDP_SOURCE\", SHARE_PATH \"/PulseAudioRDPSource\", false},\n        {\"WSL2_DEFAULT_APP_ICON\", DEFAULT_ICON_PATH \"/wsl/linux.png\", false},\n        {\"WSL2_DEFAULT_APP_OVERLAY_ICON\", DEFAULT_ICON_PATH \"/wsl/linux.png\", false},\n    };\n\n    for (auto &var : variables) {\n        THROW_LAST_ERROR_IF(setenv(var.name, var.value, var.override) < 0);\n    }\n\n    SetupOptionalEnv();\n\n    // if any components output log to /dev/kmsg, make it writable.\n    if (GetEnvBool(\"WSLG_LOG_KMSG\", false))\n        THROW_LAST_ERROR_IF(chmod(\"/dev/kmsg\", 0666) < 0);\n\n    // Open a file for logging errors and set it to stderr for WSLGd as well as any child process.\n    {\n        const char *errLog = getenv(\"WSLG_ERR_LOG_PATH\");\n        if (!errLog) {\n            errLog = c_stdErrLogFile;\n        }\n        wil::unique_fd stdErrLogFd(open(errLog, (O_RDWR | O_CREAT), (S_IRUSR | S_IRGRP | S_IROTH)));\n        if (stdErrLogFd && (stdErrLogFd.get() != STDERR_FILENO)) {\n            dup2(stdErrLogFd.get(), STDERR_FILENO);\n        }\n    }\n\n    // Ensure the daemon is launched as root.\n    if (geteuid() != 0) {\n        LOG_ERROR(\"must be run as root.\");\n        return 1;\n    }\n\n    // Query the VM ID.\n    auto vmId = GetVmId();\n    if (vmId.empty()) {\n        LOG_ERROR(\"could not query VM ID.\");\n        return 1;\n    }\n\n    // Query the WSL install path.\n    bool isWslInstallPathEnvPresent = false;\n    std::string wslInstallPath = \"C:\\\\ProgramData\\\\Microsoft\\\\WSL\";\n    auto installPath = getenv(c_installPathEnv);\n    if (installPath) {\n        isWslInstallPathEnvPresent = true;\n        wslInstallPath = installPath;\n    }\n\n    // Bind mount the versions.txt file which contains version numbers of the various WSLG pieces.\n    {\n        wil::unique_fd fd(open(c_versionMount, (O_RDWR | O_CREAT), (S_IRUSR | S_IRGRP | S_IROTH)));\n        THROW_LAST_ERROR_IF(!fd);\n    }\n\n    THROW_LAST_ERROR_IF(mount(c_versionFile, c_versionMount, NULL, MS_BIND | MS_RDONLY, NULL) < 0);\n\n    std::filesystem::create_directories(c_shareDocsMount);\n    THROW_LAST_ERROR_IF(mount(c_shareDocsDir, c_shareDocsMount, NULL, MS_BIND | MS_RDONLY, NULL) < 0);\n\n    // Create a font folder monitor\n    wslgd::FontMonitor fontMonitor;\n\n    // Make directories and ensure the correct permissions.\n    std::filesystem::create_directories(c_dbusDir);\n    THROW_LAST_ERROR_IF(chown(c_dbusDir, passwordEntry->pw_uid, passwordEntry->pw_gid) < 0);\n    THROW_LAST_ERROR_IF(chmod(c_dbusDir, 0777) < 0);\n\n    std::filesystem::create_directories(c_x11RuntimeDir);\n    THROW_LAST_ERROR_IF(chmod(c_x11RuntimeDir, 0777) < 0);\n\n    std::filesystem::create_directories(c_xdgRuntimeDir);\n    THROW_LAST_ERROR_IF(chown(c_xdgRuntimeDir, passwordEntry->pw_uid, passwordEntry->pw_gid) < 0);\n    THROW_LAST_ERROR_IF(chmod(c_xdgRuntimeDir, 0777) < 0);\n\n    // Attempt to mount the virtiofs share for shared memory.\n    bool isSharedMemoryMounted = false; \n    auto sharedMemoryObDirectoryPath = getenv(c_sharedMemoryObDirectoryPathEnv);\n    if (sharedMemoryObDirectoryPath) {\n        std::filesystem::create_directories(c_sharedMemoryMountPoint);\n        if (mount(\"wslg\", c_sharedMemoryMountPoint, \"virtiofs\", 0, \"dax\") < 0) {\n            LOG_ERROR(\"Failed to mount wslg shared memory %s.\", strerror(errno));\n        } else {\n            THROW_LAST_ERROR_IF(chmod(c_sharedMemoryMountPoint, 0777) < 0);\n            isSharedMemoryMounted = true;\n        }\n    } else {\n        LOG_ERROR(\"shared memory ob directory path is not set.\");\n    }\n\n    // Create a listening vsock in the reserved port range to be used for the RDP connection.\n    sockaddr_vm address{};\n    address.svm_family = AF_VSOCK;\n    address.svm_cid = VMADDR_CID_ANY;\n    socklen_t addressSize = sizeof(address);\n    wil::unique_fd socketFd{socket(AF_VSOCK, SOCK_STREAM, 0)};\n    THROW_LAST_ERROR_IF(!socketFd);\n    for (unsigned int port = 1; port < MAX_RESERVED_PORT; port += 1) {\n        address.svm_port = port;\n        if (bind(socketFd.get(), reinterpret_cast<const sockaddr*>(&address), addressSize) == 0) {\n            break;\n        }\n\n        THROW_LAST_ERROR_IF(errno != EADDRINUSE);\n    }\n\n    THROW_ERRNO_IF(EINVAL, (address.svm_port == MAX_RESERVED_PORT));\n    THROW_LAST_ERROR_IF(listen(socketFd.get(), 1) < 0);\n    std::string socketEnvString(\"USE_VSOCK=\");\n    socketEnvString += std::to_string(socketFd.get());\n    std::string serviceIdEnvString(\"WSLG_SERVICE_ID=\");\n    serviceIdEnvString += ToServiceId(address.svm_port);\n\n    struct rlimit limit;\n    THROW_LAST_ERROR_IF(getrlimit(RLIMIT_NOFILE, &limit) < 0);\n    limit.rlim_cur = limit.rlim_max;\n    THROW_LAST_ERROR_IF(setrlimit(RLIMIT_NOFILE, &limit) < 0);\n\n    // Set shared memory mount point to env when available.\n    if (!isSharedMemoryMounted ||\n        (setenv(c_sharedMemoryMountPointEnv, c_sharedMemoryMountPoint, true) < 0)) {\n        // shared memory is not available, env must be cleared if it's set.\n        THROW_LAST_ERROR_IF(unsetenv(c_sharedMemoryMountPointEnv) < 0);\n        isSharedMemoryMounted = false;\n    }\n\n    // Construct socket option string.\n    std::string westonSocketOption(\"--socket=\");\n    westonSocketOption += getenv(\"WAYLAND_DISPLAY\");\n\n    // Check if weston shell override is specified.\n    // Otherwise, default shell is 'rdprail-shell'.\n    // Alternatively, it can be 'desktop-shell'.\n    bool isRdpDesktopShell = GetEnvBool(c_westonShellDesktopEnv, false);\n    std::string westonShellName;\n    if (isRdpDesktopShell)\n        westonShellName = c_westonRdpdesktopShell;\n    else\n        westonShellName = c_westonRdprailShell;\n\n    // Construct shell option string.\n    std::string westonShellOption(\"--shell=\");\n    westonShellOption += westonShellName;\n    westonShellOption += \".so\";\n\n    // Construct log file option string.\n    std::string westonLogFileOption(\"--log=\");\n    auto westonLogFilePathEnv = getenv(\"WSLG_WESTON_LOG_PATH\");\n    if (westonLogFilePathEnv) {\n        westonLogFileOption += westonLogFilePathEnv;\n    } else {\n        westonLogFileOption += SHARE_PATH \"/weston.log\";\n    }\n\n    // Construct logger option string.\n    // By default, enable standard log and rdp-backend.\n    std::string westonLoggerOption(\"--logger-scopes=log,rdp-backend\");\n    // If rdprail-shell is used, enable logger for that.\n    if (!isRdpDesktopShell) {\n        westonLoggerOption += \",\";\n        westonLoggerOption += c_westonRdprailShell;\n    }\n\n    // Setup notify for wslgd-notify.so\n    wil::unique_fd notifyFd(SetupReadyNotify(WESTON_NOTIFY_SOCKET));\n    THROW_LAST_ERROR_IF(!notifyFd);\n\n    // Construct weston option string.\n    std::string westonArgs;\n    char *gdbServerPort = getenv(\"WSLG_WESTON_GDBSERVER_PORT\");\n    if ((access(GDBSERVER_PATH, X_OK) == 0) && IsNumeric(gdbServerPort)) {\n        westonArgs += GDBSERVER_PATH;\n        westonArgs += \" :\";\n        westonArgs += gdbServerPort;\n        westonArgs += \" \";\n    }\n    westonArgs += \"/usr/bin/weston \";\n    westonArgs += \"--backend=rdp-backend.so --modules=wslgd-notify.so --xwayland \";\n    westonArgs += westonSocketOption;\n    westonArgs += \" \";\n    westonArgs += westonShellOption;\n    westonArgs += \" \";\n    westonArgs += westonLogFileOption;\n    westonArgs += \" \";\n    westonArgs += westonLoggerOption;\n\n    // Launch weston.\n    // N.B. Additional capabilities are needed to setns to the mount namespace of the user distro.\n    monitor.LaunchProcess(std::vector<std::string>{\n                \"/usr/bin/sh\",\n                \"-c\",\n                std::move(westonArgs)\n            },\n            std::vector<cap_value_t>{\n                CAP_SYS_ADMIN,\n                CAP_SYS_CHROOT,\n                CAP_SYS_PTRACE\n            },\n            std::vector<std::string>{\n                std::move(socketEnvString),\n                std::move(serviceIdEnvString),\n                \"WSLGD_NOTIFY_SOCKET=\" WESTON_NOTIFY_SOCKET,\n                \"WESTON_DISABLE_ABSTRACT_FD=1\",\n                getenv(\"WLOG_APPENDER\") ? : \"\", \"WLOG_APPENDER=file\",\n                getenv(\"WLOG_FILEAPPENDER_OUTPUT_FILE_NAME\") ? \"\" : \"WLOG_FILEAPPENDER_OUTPUT_FILE_NAME=wlog.log\",\n                getenv(\"WLOG_FILEAPPENDER_OUTPUT_FILE_PATH\") ? \"\" : \"WLOG_FILEAPPENDER_OUTPUT_FILE_PATH=\" SHARE_PATH\n            }\n        );\n\n    // Wait weston to be ready before starting RDP client, pulseaudio server.\n    WaitForReadyNotify(notifyFd.get());\n    unlink(WESTON_NOTIFY_SOCKET);\n\n    // Start font monitoring if user distro's X11 fonts to be shared with system distro.\n    if (GetEnvBool(\"WSLG_USE_USER_DISTRO_XFONTS\", true))\n        fontMonitor.Start(); \n\n    // Launch the mstsc/msrdc client.\n    std::string remote(\"/v:\");\n    remote += vmId;\n    std::string serviceId(\"/hvsocketserviceid:\");\n    serviceId += ToServiceId(address.svm_port);\n    std::string sharedMemoryObPath(\"\");\n    if (isSharedMemoryMounted) {\n        sharedMemoryObPath += \"/wslgsharedmemorypath:\";\n        sharedMemoryObPath += sharedMemoryObDirectoryPath;\n    }\n\n    std::filesystem::path rdpClientExePath;\n    bool isUseMstsc = GetEnvBool(\"WSLG_USE_MSTSC\", false);\n    if (!isUseMstsc && !wslInstallPath.empty()) {\n        std::filesystem::path msrdcExePath = TranslateWindowsPath(wslInstallPath.c_str());\n        msrdcExePath /= MSRDC_EXE;\n        if (access(msrdcExePath.c_str(), X_OK) == 0) {\n            rdpClientExePath = std::move(msrdcExePath);\n        }\n    }\n    if (rdpClientExePath.empty()) {\n        rdpClientExePath = c_windowsSystem32;\n        rdpClientExePath /= MSTSC_EXE;\n    }\n\n    std::string wslDvcPlugin;\n    if (GetEnvBool(\"WSLG_USE_WSLDVC_PRIVATE\", false))\n        wslDvcPlugin = \"/plugin:WSLDVC_PRIVATE\";\n    else if (isWslInstallPathEnvPresent)\n        wslDvcPlugin = \"/plugin:WSLDVC_PACKAGE\";\n    else\n        wslDvcPlugin = \"/plugin:WSLDVC\";\n\n    std::string rdpFilePathArg(wslInstallPath);\n    rdpFilePathArg += \"\\\\\"; // Windows-style path\n    if (isRdpDesktopShell) \n        rdpFilePathArg += c_rdpDesktopFile;\n    else \n        rdpFilePathArg += c_rdpRailFile;\n\n    monitor.LaunchProcess(std::vector<std::string>{\n        \"/init\",\n        std::move(rdpClientExePath),\n        basename(rdpClientExePath.c_str()),\n        \"/wslg\", // set wslg option first so following parameters are parsed in context of wslg.\n        \"/silent\", // then set silent option before anything-else.\n        std::move(remote),\n        std::move(serviceId),\n        std::move(wslDvcPlugin),\n        std::move(sharedMemoryObPath),\n        std::move(rdpFilePathArg)\n    });\n\n    // Launch the system dbus daemon.\n    monitor.LaunchProcess(std::vector<std::string>{\n        \"/usr/bin/dbus-daemon\",\n        \"--syslog\",\n        \"--nofork\",\n        \"--nopidfile\",\n        \"--system\"\n        },\n        std::vector<cap_value_t>{CAP_SETGID, CAP_SETUID}\n    );\n\n    // Construct pulseaudio launch command line.\n    std::string pulseaudioLaunchArgs =\n        \"/usr/bin/dbus-launch \"\n        \"/usr/bin/pulseaudio \"\n        \"--log-time=true \"\n        \"--disallow-exit=true \"\n        \"--exit-idle-time=-1 \"\n        \"--load=\\\"module-rdp-sink sink_name=RDPSink\\\" \"\n        \"--load=\\\"module-rdp-source source_name=RDPSource\\\" \"\n        \"--load=\\\"module-native-protocol-unix socket=\" SHARE_PATH \"/PulseServer auth-anonymous=true\\\" \";\n\n    // Construct log file option string.\n    std::string pulseaudioLogFileOption(\"--log-target=\");\n    auto pulseAudioLogFilePathEnv = getenv(\"WSLG_PULSEAUDIO_LOG_PATH\");\n    if (pulseAudioLogFilePathEnv) {\n        pulseaudioLogFileOption += pulseAudioLogFilePathEnv;\n    } else {\n        pulseaudioLogFileOption += \"newfile:\" SHARE_PATH \"/pulseaudio.log\";\n    }\n    pulseaudioLaunchArgs += pulseaudioLogFileOption;\n\n    // Launch pulseaudio and the associated dbus daemon.\n    monitor.LaunchProcess(std::vector<std::string>{\n        \"/usr/bin/sh\",\n        \"-c\",\n        std::move(pulseaudioLaunchArgs)\n    });\n\n    return monitor.Run();\n}\nCATCH_RETURN_ERRNO();\n"
  },
  {
    "path": "WSLGd/meson.build",
    "content": "project('WSLGd', 'cpp',\n  version : '0.1',\n  default_options : ['cpp_std=c++17'])\n\nconfig_h = configuration_data()\n\ndep_winpr = dependency('winpr3', version: '>= 3.0.0', required: false)\nif dep_winpr.found()\n  config_h.set('HAVE_WINPR', '1')\nelse\n  dep_winpr = dependency('winpr2', version: '>= 2.0.0', required: false)\n  if dep_winpr.found()\n    config_h.set('HAVE_WINPR', '1')\n  endif\nendif\n\nconfigure_file(output: 'config.h', configuration: config_h)\n\nexecutable('WSLGd',\n           'main.cpp',\n           'ProcessMonitor.cpp',\n           'FontMonitor.cpp',\n           dependencies: dep_winpr,\n           link_args: '-lcap',\n           install : true)\n"
  },
  {
    "path": "WSLGd/precomp.h",
    "content": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT license.\r\n#include <sys/capability.h>\r\n#include <sys/time.h>\r\n#include <sys/resource.h>\r\n#include <sys/prctl.h>\r\n#include <sys/mount.h>\r\n#include <sys/socket.h>\r\n#include <sys/un.h>\r\n#include <sys/signalfd.h>\r\n#include <sys/stat.h>\r\n#include <sys/types.h>\r\n#include <sys/wait.h>\r\n#include <sys/inotify.h>\r\n#include <assert.h>\r\n#include <dirent.h>\r\n#include <errno.h>\r\n#include <fcntl.h>\r\n#include <grp.h>\r\n#include <poll.h>\r\n#include <pwd.h>\r\n#include <signal.h>\r\n#include <stdio.h>\r\n#include <stdarg.h>\r\n#include <string.h>\r\n#include <unistd.h>\r\n#include <pthread.h>\r\n#include <algorithm>\r\n#include <linux/vm_sockets.h>\r\n#include <array>\r\n#include <filesystem>\r\n#include <map>\r\n#include <new>\r\n#include <vector>\r\n#include \"config.h\"\r\n#include \"lxwil.h\"\r\n#if HAVE_WINPR\r\n#include \"winpr/ini.h\"\r\n#endif // HAVE_WINPR\r\n"
  },
  {
    "path": "azure-pipelines.yml",
    "content": "resources:\n  repositories:\n  - repository: FreeRDP\n    type: github\n    endpoint: GitHub connection 1\n    name: microsoft/FreeRDP-mirror\n    ref: working\n  - repository: weston\n    type: github\n    endpoint: GitHub connection 1\n    name: microsoft/weston-mirror\n    ref: working\n  - repository: pulseaudio\n    type: github\n    endpoint: GitHub connection 1\n    name: microsoft/pulseaudio-mirror\n    ref: working\n\ntrigger:\n  - main\n\nstages:\n- stage: Build_SystemDistro\n  displayName: \"Build System Distro (x64 and ARM64)\"\n  jobs:\n  - job: 'Build_Ubuntu_x64'\n    displayName: 'Build x64 system distro'\n    timeoutInMinutes: 200\n\n    pool:\n      vmImage: 'ubuntu-latest'\n      \n    steps:\n      - checkout: FreeRDP\n      - checkout: weston\n      - checkout: pulseaudio\n      - checkout: self\n\n      - template: devops/common-linux.yml\n\n      - script: wget https://azurelinuxsrcstorage.blob.core.windows.net/sources/core/mesa-23.1.0.tar.xz &&\n                tar -xvf mesa-23.1.0.tar.xz\n        displayName: 'Download Mesa from Azure Linux'\n\n      - script: wget https://github.com/microsoft/DirectX-Headers/archive/refs/tags/v1.608.0.tar.gz &&\n                tar -xvf v1.608.0.tar.gz\n        displayName: 'Download DirectX-Headers from GitHub'\n\n      - script: mv FreeRDP-mirror/ wslg/vendor/FreeRDP &&\n                mv weston-mirror/ wslg/vendor/weston &&\n                mv pulseaudio-mirror/ wslg/vendor/pulseaudio &&\n                mv mesa-23.1.0/ wslg/vendor/mesa &&\n                mv DirectX-Headers-1.608.0/ wslg/vendor/DirectX-Headers-1.0\n        displayName: 'Move sub projects (FreeRDP, Weston, PulseAudio, Mesa, DirectX-Headers)'\n\n      - script: docker build -f ./wslg/Dockerfile -t system-distro-x64 \n                ./wslg \n                --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion`\n                --build-arg WSLG_ARCH=x86_64\n        displayName: 'Create System Distro Docker image Azure Linux 3.0 x64'\n\n      - script: docker export `docker create system-distro-x64` > $(Agent.BuildDirectory)/system_x64.tar\n        displayName: 'Create system_x64.tar'\n\n      - script: docker build -f ./wslg/Dockerfile -t system-distro-dev-x64 \n                ./wslg \n                --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion`\n                --build-arg WSLG_ARCH=x86_64\n                --target dev\n        displayName: 'Create System Distro Dev Docker image Azure Linux 3.0 x64'\n\n      - script: docker cp `docker create system-distro-dev-x64 /bin/bash`:/work/debuginfo/system-debuginfo.tar.gz $(Agent.BuildDirectory)/system-debuginfo_x64.tar.gz\n        displayName: 'Copy system-debuginfo_x64.tar.gz'\n\n      - task: Go@0\n        inputs:\n          command: 'custom'\n          customCommand: 'run'\n          arguments: 'tar2ext4.go -vhd -i $(Agent.BuildDirectory)/system_x64.tar -o $(Agent.BuildDirectory)/system_x64.vhd'\n          workingDirectory: 'hcsshim/cmd/tar2ext4'\n        displayName: 'Create system_x64.vhd'\n\n      - task: PublishPipelineArtifact@1\n        displayName: 'Publish system_x64.vhd artifact'\n        inputs:\n          targetPath: $(Agent.BuildDirectory)/system_x64.vhd\n          artifact: 'system_x64.vhd'\n          publishLocation: 'pipeline'\n\n      - task: PublishPipelineArtifact@1\n        displayName: 'Publish system-debuginfo_x64.tar.gz artifact'\n        inputs:\n          targetPath: $(Agent.BuildDirectory)/system-debuginfo_x64.tar.gz\n          artifact: 'system-debuginfo_x64.tar.gz'\n          publishLocation: 'pipeline'\n          \n  - job: 'Build_Windows_x64'\n    dependsOn: 'Build_Ubuntu_x64'\n    displayName: 'Build WSLDCV (x64) Plugin'\n    \n    pool:\n      vmImage: 'windows-2019'\n      demands:\n      - msbuild\n      - visualstudio\n\n    steps:\n    - checkout: self\n\n    - template: devops/common-win.yml\n\n    - task: PowerShell@2\n      displayName: 'Update WSLDVCPlugin version'\n      inputs:\n        targetType: filePath\n        workingDirectory: './WSLDVCPlugin'\n        filePath: .\\WSLDVCPlugin\\UpdateRCVersion.ps1\n        pwsh: true\n\n    - task: MSBuild@1\n      displayName: 'Build RDP Plugin (x64)'\n      inputs:\n        solution: './WSLDVCPlugin/WSLDVCPlugin.sln'\n        platform: 'x64'\n        configuration: 'Release'\n        \n    - task: PublishSymbols@2\n      displayName: Publish symbols\n      inputs:\n        SymbolServerType: 'TeamServices'\n        TreatNotIndexedAsWarning: true\n        SymbolsProduct: wslg\n        SearchPattern: |\n          WSLDVCPlugin/x64/Release/*.pdb\n          WSLDVCPlugin/x64/Release/*.dll\n\n    - script: 'MOVE WSLDVCPlugin\\x64\\Release\\WSLDVCPlugin.pdb package\\WSLDVCPlugin_x64.pdb'\n      displayName: 'Move plugin PDB to package (x64)'\n\n    - task: PublishPipelineArtifact@1\n      displayName: 'Publish WSLDVCPlugin PDB (x64)'\n      inputs:\n        targetPath: package\\WSLDVCPlugin_x64.pdb\n        artifact: 'WSLDVCPlugin.x64.pdb'\n        publishLocation: 'pipeline'\n\n    - script: 'MOVE WSLDVCPlugin\\x64\\Release\\WSLDVCPlugin.dll package\\WSLDVCPlugin_x64.dll'\n      displayName: 'Move plugin DLL to package (x64)'\n\n    - task: PublishPipelineArtifact@1\n      displayName: 'Publish WSLDVCPlugin DLL (x64)'\n      inputs:\n        targetPath: package\\WSLDVCPlugin_x64.dll\n        artifact: 'WSLDVCPlugin.x64.dll'\n        publishLocation: 'pipeline'\n\n  - job: 'Build_Ubuntu_ARM64'\n    displayName: 'Build ARM64 system distro'\n    condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')\n    timeoutInMinutes: 200\n\n    pool:\n      vmImage: 'ubuntu-latest'\n      \n    steps:\n      - checkout: FreeRDP\n      - checkout: weston\n      - checkout: pulseaudio\n      - checkout: self\n\n      - template: devops/common-linux.yml\n\n      - bash: |\n              curl -L -o ~/.docker/cli-plugins/docker-buildx --create-dirs ${BUILDX_URL}\n              chmod a+x ~/.docker/cli-plugins/docker-buildx\n              docker run --privileged --rm tonistiigi/binfmt:qemu-v9.2.0-51 --install all\n              ~/.docker/cli-plugins/docker-buildx create --use\n              ~/.docker/cli-plugins/docker-buildx inspect --bootstrap\n        displayName: Prepare buildx\n        condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')\n        env:\n          BUILDX_URL: https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-amd64\n\n      - script: |\n                echo '{ \"experimental\": true }' | sudo tee /etc/docker/daemon.json\n                sudo service docker restart\n        displayName: 'Enable Docker Engine experimental '\n\n      - script: wget https://azurelinuxsrcstorage.blob.core.windows.net/sources/core/mesa-23.1.0.tar.xz &&\n                tar -xvf mesa-23.1.0.tar.xz\n        displayName: 'Download Mesa from Azure Linux'\n\n      - script: wget https://github.com/microsoft/DirectX-Headers/archive/refs/tags/v1.608.0.tar.gz &&\n                tar -xvf v1.608.0.tar.gz\n        displayName: 'Download DirectX-Headers from GitHub'\n\n      - script: mv FreeRDP-mirror/ wslg/vendor/FreeRDP &&\n                mv weston-mirror/ wslg/vendor/weston &&\n                mv pulseaudio-mirror/ wslg/vendor/pulseaudio &&\n                mv mesa-23.1.0/ wslg/vendor/mesa &&\n                mv DirectX-Headers-1.608.0/ wslg/vendor/DirectX-Headers-1.0\n        displayName: 'Move sub projects (FreeRDP, Weston, PulseAudio, Mesa, DirectX-Headers)'\n\n      - script: ~/.docker/cli-plugins/docker-buildx build -f ./wslg/Dockerfile\n                --output type=tar,dest=$(Agent.BuildDirectory)/system_arm64.tar \n                --platform=linux/arm64 \n                ./wslg \n                --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion` \n                --build-arg WSLG_ARCH=aarch64\n        displayName: 'Create system_arm64.tar'\n\n      - task: Go@0\n        inputs:\n          command: 'custom'\n          customCommand: 'run'\n          arguments: 'tar2ext4.go -vhd -i $(Agent.BuildDirectory)/system_arm64.tar -o $(Agent.BuildDirectory)/system_arm64.vhd'\n          workingDirectory: 'hcsshim/cmd/tar2ext4'\n        displayName: 'Create system_arm64.vhd'\n\n      - task: PublishPipelineArtifact@1\n        displayName: 'Publish system_arm64.vhd artifact'\n        condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')\n        inputs:\n          targetPath: $(Agent.BuildDirectory)/system_arm64.vhd\n          artifact: 'system_arm64.vhd'\n          publishLocation: 'pipeline'\n\n      - script: mkdir ./dev &&\n                ~/.docker/cli-plugins/docker-buildx build -f ./wslg/Dockerfile\n                --output type=tar,dest=./dev/dev_build.tar \n                --target=dev  \n                --platform=linux/arm64  \n                ./wslg  \n                --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion` \n                --build-arg WSLG_ARCH=aarch64 &&\n                tar -xvf ./dev/dev_build.tar -C ./dev/ &&\n                mv ./dev/work/debuginfo/system-debuginfo.tar.gz $(Agent.BuildDirectory)/system-debuginfo_arm64.tar.gz\n        displayName: 'Copy system-debuginfo_arm64.tar.gz'\n\n      - task: PublishPipelineArtifact@1\n        displayName: 'Publish system-debuginfo_arm64.tar.gz artifact'\n        inputs:\n          targetPath: $(Agent.BuildDirectory)/system-debuginfo_arm64.tar.gz\n          artifact: 'system-debuginfo_arm64.tar.gz'\n          publishLocation: 'pipeline'\n\n  - job: 'Build_Windows_ARM64'\n    dependsOn: 'Build_Ubuntu_ARM64'\n    displayName: 'Build WSLDCV (ARM64) Plugin'\n    condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')\n    \n    pool:\n      vmImage: 'windows-2019'\n      demands:\n      - msbuild\n      - visualstudio\n\n    steps:\n    - checkout: self\n\n    - template: devops/common-win.yml\n\n    - task: PowerShell@2\n      displayName: 'Update WSLDVCPlugin version'\n      inputs:\n        targetType: filePath\n        workingDirectory: './WSLDVCPlugin'\n        filePath: .\\WSLDVCPlugin\\UpdateRCVersion.ps1\n        pwsh: true\n\n    - task: MSBuild@1\n      displayName: 'Build RDP Plugin (ARM64)'\n      inputs:\n        solution: './WSLDVCPlugin/WSLDVCPlugin.sln'\n        platform: 'ARM64'\n        configuration: 'Release'\n        \n    - task: PublishSymbols@2\n      displayName: Publish symbols\n      inputs:\n        SymbolServerType: 'TeamServices'\n        TreatNotIndexedAsWarning: true\n        SymbolsProduct: wslg\n        SearchPattern: |\n          WSLDVCPlugin/arm64/Release/*.pdb\n          WSLDVCPlugin/arm64/Release/*.dll\n\n    - script: 'MOVE WSLDVCPlugin\\ARM64\\Release\\WSLDVCPlugin.pdb package\\WSLDVCPlugin_ARM64.pdb'\n      displayName: 'Move plugin PDB to package (ARM64)'\n\n    - task: PublishPipelineArtifact@1\n      displayName: 'Publish WSLDVCPlugin PDB (ARM64)'\n      inputs:\n        targetPath: package\\WSLDVCPlugin_ARM64.pdb\n        artifact: 'WSLDVCPlugin.ARM64.pdb'\n        publishLocation: 'pipeline'\n\n    - script: 'MOVE WSLDVCPlugin\\ARM64\\Release\\WSLDVCPlugin.dll package\\WSLDVCPlugin_ARM64.dll'\n      displayName: 'Move plugin to package (ARM64)'\n\n    - task: PublishPipelineArtifact@1\n      displayName: 'Publish WSLDVCPlugin DLL (ARM64)'\n      inputs:\n        targetPath: package\\WSLDVCPlugin_ARM64.dll\n        artifact: 'WSLDVCPlugin.ARM64.dll'\n        publishLocation: 'pipeline'\n\n- stage: PublishPackage\n  displayName: \"Publish WSLg NuGet Package\"\n  jobs:\n  - job: 'Publish_UniversalPackage'\n    condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')\n    displayName: 'Download artifact and push NuGet package'\n\n    pool:\n      vmImage: 'windows-2019'\n\n    steps:\n\n      - task: DownloadPipelineArtifact@2\n        displayName: 'Download system_x64.vhd'\n        inputs:\n          buildType: 'current'\n          artifactName: 'system_x64.vhd'\n          targetPath: 'package/'\n\n      - task: DownloadPipelineArtifact@2\n        displayName: 'Download system-debuginfo_x64.tar.gz'\n        inputs:\n          buildType: 'current'\n          artifactName: 'system-debuginfo_x64.tar.gz'\n          targetPath: 'package/'\n          \n      - task: DownloadPipelineArtifact@2\n        displayName: 'Download WSLDVCPlugin DLL (x64)'\n        inputs:\n          buildType: 'current'\n          artifactName: 'WSLDVCPlugin.x64.dll'\n          targetPath: 'package/'\n\n      - task: DownloadPipelineArtifact@2\n        displayName: 'Download WSLDVCPlugin PDB (x64)'\n        inputs:\n          buildType: 'current'\n          artifactName: 'WSLDVCPlugin.x64.pdb'\n          targetPath: 'package/'\n\n      - task: DownloadPipelineArtifact@2\n        displayName: 'Download system_arm64.vhd'\n        inputs:\n          buildType: 'current'\n          artifactName: 'system_arm64.vhd'\n          targetPath: 'package/'\n\n      - task: DownloadPipelineArtifact@2\n        displayName: 'Download system-debuginfo_arm64.tar.gz'\n        inputs:\n          buildType: 'current'\n          artifactName: 'system-debuginfo_arm64.tar.gz'\n          targetPath: 'package/'\n\n      - task: DownloadPipelineArtifact@2\n        displayName: 'Download WSLDVCPlugin DLL (ARM64)'\n        inputs:\n          buildType: 'current'\n          artifactName: 'WSLDVCPlugin.ARM64.dll'\n          targetPath: 'package/'\n\n      - task: DownloadPipelineArtifact@2\n        displayName: 'Download WSLDVCPlugin PDB (ARM64)'\n        inputs:\n          buildType: 'current'\n          artifactName: 'WSLDVCPlugin.ARM64.pdb'\n          targetPath: 'package/'\n\n      - task: PowerShell@2\n        displayName: 'Update Microsoft.WSLg.nuspec version'\n        inputs:\n          targetType: filePath\n          filePath: .\\devops\\updateversion.ps1 \n          arguments: .\\Microsoft.WSLg.nuspec \"package.metadata.version\" \"\" \"-beta\"\n          pwsh: true\n\n      - task: PowerShell@2\n        displayName: 'Update Microsoft.WSLg.nuspec release notes'\n        inputs:\n          targetType: filePath\n          filePath: .\\devops\\updateversion.ps1 \n          arguments: .\\Microsoft.WSLg.nuspec \"package.metadata.releaseNotes\" \"\" \"-beta\" \"hash\"\n          pwsh: true\n\n      - script: 'nuget pack .\\Microsoft.WSLg.nuspec'\n        displayName: 'Package NuGet'\n\n      - script: 'rename *.nupkg Microsoft.WSLg.nupkg'\n        displayName: 'Rename Nuget Package'\n\n      - task: PublishPipelineArtifact@1\n        displayName: 'Save Microsoft.WSLg.nupkg artifact'\n        inputs:\n          targetPath: Microsoft.WSLg.nupkg\n          artifact: 'Microsoft.WSLg.nupkg'\n          publishLocation: 'pipeline'\n\n      - task: '333b11bd-d341-40d9-afcf-b32d5ce6f23b@2'\n        condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')\n        inputs:\n          command: 'push'\n          packagesToPush: 'Microsoft.WSLg.nupkg'\n          nuGetFeedType: 'internal'\n          publishVstsFeed: 'wsl'\n          allowPackageConflicts: true\n          verbosityPush: 'Normal'\n"
  },
  {
    "path": "build-and-export.sh",
    "content": "#!/bin/bash\nset -e\n\necho \"=== Building Docker image ===\"\ndocker build -f Dockerfile -t system-distro-x64 . \\\n    --build-arg WSLG_VERSION=1.0.72-test \\\n    --build-arg WSLG_ARCH=x86_64\n\necho \"\"\necho \"=== Exporting Docker container to tar ===\"\ndocker export $(docker create system-distro-x64) > system_x64.tar\n\necho \"\"\necho \"=== Converting tar to VHD ===\"\n# Run tar2ext4 directly from GitHub\ngo run github.com/Microsoft/hcsshim/cmd/tar2ext4@latest -vhd -i system_x64.tar -o system_x64.vhd\necho \"\"\necho \"=== Done! ===\"\necho \"Output file: $(pwd)/system_x64.vhd\"\nls -lh system_x64.vhd\n"
  },
  {
    "path": "cgmanifest.json",
    "content": "{\n    \"Registrations\": [ \n        {\n            \"Component\": { \n                \"Type\": \"git\", \n                \"Git\": {\n                    \"RepositoryUrl\": \"https://github.com/pulseaudio/pulseaudio\", \n                    \"CommitHash\": \"0e691b96640919b1c7ed91ae9240761c5775deeb\"\n                }\n            },\n            \"DevelopmentDependency\": false\n        },\n        {\n            \"Component\": { \n                \"Type\": \"git\", \n                \"Git\": {\n                    \"RepositoryUrl\": \"https://github.com/wayland-project/weston\", \n                    \"CommitHash\": \"04d3ae265d8d8f84352c8dac21ec40b2fe07e7d2\"\n                }\n            },\n            \"DevelopmentDependency\": false\n        },\n        {\n            \"Component\": { \n                \"Type\": \"git\", \n                \"Git\": {\n                    \"RepositoryUrl\": \"https://github.com/FreeRDP/FreeRDP\", \n                    \"CommitHash\": \"39f56443f2dc50e0dcfa52d4f8f15008d5b8ed8e\"\n                }\n            },\n            \"DevelopmentDependency\": false\n        },        \n        {\n            \"Component\": { \n                \"Type\": \"mesa\", \n                \"Git\": {\n                    \"RepositoryUrl\": \"https://github.com/mesa3d/mesa\", \n                    \"CommitHash\": \"731ea06758663a2de3a2bd1f12eb8809d4c136fd\"\n                }\n            },\n            \"DevelopmentDependency\": false\n        },\n    ]\n}\n"
  },
  {
    "path": "config/BUILD.md",
    "content": "# Introduction\r\n\r\nThis repository contains a Dockerfile and supporting tools to build the WSL GUI system distro image.\r\n\r\n## Quick start\r\n\r\nFor self-hosting WSLG check use this instructions https://github.com/microsoft/wslg/wiki#installing-self-hosting\r\n\r\n## Setup and build WSLG and System Distro\r\n\r\n0. Install and start Docker in a Linux or WSL2 environment.\r\n\r\n1. Clone the FreeRDP ,Weston and PulseAudio side by side this repo repositories and checkout the \"working\" branch from each:\r\n\r\n    ```bash\r\n    git clone https://github.com/microsoft/FreeRDP-mirror vendor/FreeRDP -b working\r\n\r\n    git clone https://github.com/microsoft/weston-mirror.git vendor/weston -b working\r\n\r\n    git clone https://github.com/microsoft/pulseaudio-mirror.git vendor/pulseaudio -b working\r\n    ```\r\n\r\n2. Download the mesa and directx headers code.\r\n\r\n    ```\r\n    wget https://azurelinuxsrcstorage.blob.core.windows.net/sources/core/mesa-23.1.0.tar.xz\r\n    tar -xf mesa-23.1.0.tar.xz -C vendor\r\n    mv vendor/mesa-23.1.0 vendor/mesa\r\n\r\n    wget https://github.com/microsoft/DirectX-Headers/archive/refs/tags/v1.608.0.tar.gz\r\n    tar -xvf v1.608.0.tar.gz -C vendor\r\n    mv vendor/DirectX-Headers-1.608.0 vendor/DirectX-Headers-1.0\r\n    ```\r\n\r\n3. Create the VHD:\r\n\r\n    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\r\n    ```\r\n    git clone --branch v0.8.9 --single-branch https://github.com/microsoft/hcsshim.git\r\n    ```\r\n    \r\n    3.1 From the parent directory build and export the docker image:\r\n    ```\r\n    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\r\n    sudo docker export `sudo docker create system-distro-x64` > system_x64.tar\r\n    ```\r\n    \r\n    3.3 Create the system distro vhd using `tar2ext4`\r\n    \r\n    ```bash\r\n    cd hcsshim/cmd/tar2ext4\r\n    go run tar2ext4.go -vhd -i ../../../system_x64.tar -o ../../../system.vhd\r\n    ```\r\n    \r\n    This will create system distro image `system.vhd`\r\n\r\n4. Change the system distro:\r\n\r\n    4.1 Before replace the system distro you will need to shutdown WSL\r\n    \r\n    ```\r\n    wsl --shutdown\r\n    ```\r\n    \r\n    4.2 By default the system distro is located at `C:\\ProgramData\\Microsoft\\WSL\\system.vhd`\r\n    \r\n    If you want to use the system distro from a different path you can change the .wslconfig.\r\n\r\n    * Add an entry to your `%USERPROFILE%\\.wslconfig`\r\n\r\n    ```\r\n    [wsl2]\r\n    systemDistro=C:\\\\tmp\\\\system.vhd\r\n    ```\r\n    \r\n    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.\r\n    \r\n\r\n5. Inspecting the system distro:\r\n\r\n    If the system distro isn't working correctly or you need to inspect what is running inside the system distro you can do:\r\n\r\n    ```\r\n    wsl --system [DistroName]\r\n    ```\r\n\r\n    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`\r\n    You should see something like this:\r\n    \r\n    ```bash\r\n    root@DESKTOP-7LJ03SK:/mnt/d# ps -ax | grep weston\r\n   11 ?        Sl     6:51 /usr/local/bin/weston --backend=rdp-backend.so --xwayland --shell=rdprail-shell.so --log=/mnt/wslg/weston.log\r\n    ```\r\n"
  },
  {
    "path": "config/default_wslg.pa",
    "content": "\n### WSLG specific ###\n### Load Windows's default sound from Windows volume.\n.ifexists /mnt/c/Windows/Media/Windows Default.wav\nload-sample x11-bell /mnt/c/Windows/Media/Windows Default.wav\n### Enable X11 bell by loading module-x11-bell.\nload-module module-x11-bell sample=x11-bell\n.endif\n\n"
  },
  {
    "path": "config/local.conf",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">\n<fontconfig>\n    <!-- Add fonts directory mapped from user distro -->\n    <dir>/mnt/wslg/distro/usr/share/fonts</dir>\n    <dir>/mnt/wslg/distro/usr/local/share/fonts</dir>\n</fontconfig>\n"
  },
  {
    "path": "config/weston.ini",
    "content": "[xwayland]\ndisable_access_control=true\n\n[input-method]\npath=\n"
  },
  {
    "path": "config/wsl.conf",
    "content": "[boot]\ncommand=/usr/bin/WSLGd\n[user]\ndefault=wslg"
  },
  {
    "path": "config/xwayland_log.patch",
    "content": "diff -Naur xorg-server-1.20.9/hw/xwayland/xwayland.c xorg-server-1.20.9-patch/hw/xwayland/xwayland.c\n--- xorg-server-1.20.9/hw/xwayland/xwayland.c\t2020-11-21 01:07:32.850000000 +0000\n+++ xorg-server-1.20.9-patch/hw/xwayland/xwayland.c\t2020-11-21 01:06:20.290000000 +0000\n@@ -61,6 +61,9 @@\n void\n OsVendorInit(void)\n {\n+    LogInit(\"/mnt/wslg/xlog.log\", \".old\");\n+    LogSetParameter(XLOG_FILE_VERBOSITY, X_DEBUG);\n+\n     if (serverGeneration == 1)\n         ForceClockId(CLOCK_MONOTONIC);\n }\n"
  },
  {
    "path": "debuginfo/FreeRDP2.list",
    "content": "/usr/bin/winpr-hash\r\n/usr/bin/winpr-makecert\r\n/usr/lib/libfreerdp-server2.so.2.4.0\r\n/usr/lib/libfreerdp2.so.2.4.0\r\n/usr/lib/libuwac0.so.0.1.1\r\n/usr/lib/libwinpr-tools2.so.2.4.0\r\n/usr/lib/libwinpr2.so.2.4.0\r\n"
  },
  {
    "path": "debuginfo/FreeRDP3.list",
    "content": "/usr/bin/winpr-hash\r\n/usr/bin/winpr-makecert\r\n/usr/lib/libfreerdp-server3.so.3.0.0\r\n/usr/lib/libfreerdp3.so.3.0.0\r\n/usr/lib/libuwac0.so.0.2.0\r\n/usr/lib/libwinpr-tools3.so.3.0.0\r\n/usr/lib/libwinpr3.so.3.0.0\r\n"
  },
  {
    "path": "debuginfo/WSLGd.list",
    "content": "/usr/bin/WSLGd\r\n"
  },
  {
    "path": "debuginfo/gen_debuginfo.sh",
    "content": "#!/bin/bash\n\nfunction split_debuginfo(){\n    file=${1//[$'\\t\\r\\n']}\n    dir=$(dirname \"$file\")\n    mkdir -p $2/debuginfo/$dir\n    org=$2/$file\n    dst=$2/debuginfo/$file.debug\n    objcopy --only-keep-debug $org $dst\n    objcopy --strip-debug $org\n    objcopy --add-gnu-debuglink=$dst $org\n    echo $dst\n}\nwhile read line; do split_debuginfo $line $2; done < $1\n\n"
  },
  {
    "path": "debuginfo/rdpapplist.list",
    "content": "usr/lib/rdpapplist/librdpapplist-server.so\r\n"
  },
  {
    "path": "debuginfo/weston.list",
    "content": "usr/bin/weston\r\nusr/bin/weston-launch\r\nusr/bin/weston-screenshooter\r\nusr/lib/libweston-9.so.0.0.0\r\nusr/lib/libweston-desktop-9.so.0.0.0\r\nusr/lib/libweston-9/gl-renderer.so\r\nusr/lib/libweston-9/rdp-backend.so\r\nusr/lib/libweston-9/xwayland.so\r\nusr/lib/weston/libexec_weston.so.0.0.0\r\nusr/lib/weston/desktop-shell.so\r\nusr/lib/weston/rdprail-shell.so\r\nusr/lib/weston/wslgd-notify.so\r\nusr/libexec/weston-desktop-shell\r\nusr/libexec/weston-keyboard\r\nusr/libexec/weston-rdprail-shell\r\n"
  },
  {
    "path": "devops/common-linux.yml",
    "content": "steps:\n  - task: DockerInstaller@0\n    inputs:\n      dockerVersion: '20.10.7'\n      releaseType: 'stable'\n\n  - script: wget https://github.com/GitTools/GitVersion/releases/download/5.6.8/gitversion-linux-x64-5.6.8.tar.gz &&\n            tar -xvf gitversion-linux-x64-5.6.8.tar.gz &&\n            sudo mv gitversion /usr/local/bin &&\n            sudo mv libgit2-6777db8.so /usr/local/bin\n    displayName: 'Install GitVersion'\n\n  - script: git clone --branch v0.8.17 --single-branch https://github.com/microsoft/hcsshim.git\n    displayName: 'Clone hcsshim repo for tar2ext4 tool'\n"
  },
  {
    "path": "devops/common-win.yml",
    "content": "steps:\n- script: 'choco install gitversion.portable --pre'\n  displayName: 'Install GitVersion'\n"
  },
  {
    "path": "devops/getversion.ps1",
    "content": ". .\\devops\\version_functions.ps1\r\n\r\n$version = Get-VersionInfo \"version\" \"-beta\"\r\n\r\nWrite-Output $version\r\n"
  },
  {
    "path": "devops/updateversion.ps1",
    "content": "param ([string] $XmlFile, [string] $xpath, [string] $name, [string] $buildSeparator = \".\", [string] $type = \"version\")\r\n\r\n. .\\devops\\version_functions.ps1\r\n\r\n$version = Get-VersionInfo $type $buildSeparator\r\n\r\nif ($name -eq \"\")\r\n{\r\n\tUpdate-XML-Text $XmlFile $xpath $version\r\n}\r\nelse\r\n{\r\n\tUpdate-XML-Attribute $XmlFile $xpath $name $version\r\n}\r\n"
  },
  {
    "path": "devops/version_functions.ps1",
    "content": "function Get-XmlNamespaceManager([xml]$XmlDocument, [string]$NamespaceURI = \"\")\r\n{\r\n    # If a Namespace URI was not given, use the Xml document's default namespace.\r\n\tif ([string]::IsNullOrEmpty($NamespaceURI)) { $NamespaceURI = $XmlDocument.DocumentElement.NamespaceURI }\t\r\n\t\r\n\t# 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.\r\n\t[System.Xml.XmlNamespaceManager]$xmlNsManager = New-Object System.Xml.XmlNamespaceManager($XmlDocument.NameTable)\r\n\t$xmlNsManager.AddNamespace(\"ns\", $NamespaceURI)\r\n    return ,$xmlNsManager\t\t# Need to put the comma before the variable name so that PowerShell doesn't convert it into an Object[].\r\n}\r\n\r\nfunction Get-FullyQualifiedXmlNodePath([string]$NodePath, [string]$NodeSeparatorCharacter = '.')\r\n{\r\n    return \"/ns:$($NodePath.Replace($('.'), '/ns:'))\"\r\n}\r\n\r\nfunction Get-XmlNode([xml]$XmlDocument, [string]$NodePath, [string]$NamespaceURI = \"\", [string]$NodeSeparatorCharacter = '.')\r\n{\r\n\t$xmlNsManager = Get-XmlNamespaceManager -XmlDocument $XmlDocument -NamespaceURI $NamespaceURI\r\n\t[string]$fullyQualifiedNodePath = Get-FullyQualifiedXmlNodePath -NodePath $NodePath -NodeSeparatorCharacter $NodeSeparatorCharacter\r\n\t\r\n\t# Try and get the node, then return it. Returns $null if the node was not found.\r\n\t$node = $XmlDocument.SelectSingleNode($fullyQualifiedNodePath, $xmlNsManager)\r\n\treturn $node\r\n}\r\n\r\nfunction Set-XmlAttributeValue([xml]$XmlDocument, [string]$ElementPath, [string]$AttributeName, [string]$AttributeValue, [string]$NamespaceURI = \"\", [string]$NodeSeparatorCharacter = '.')\r\n{\r\n\t# Try and get the node.\t\r\n\t$node = Get-XmlNode -XmlDocument $XmlDocument -NodePath $ElementPath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter\r\n\t$node.SetAttribute($AttributeName, $AttributeValue)\r\n}\r\n\r\nfunction Set-XmlElementsTextValue([xml]$XmlDocument, [string]$ElementPath, [string]$TextValue, [string]$NamespaceURI = \"\", [string]$NodeSeparatorCharacter = '.')\r\n{\r\n\t# Try and get the node.\t\r\n\t$node = Get-XmlNode -XmlDocument $XmlDocument -NodePath $ElementPath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter\r\n\t\r\n\t# If the node already exists, update its value.\r\n\tif ($node)\r\n\t{ \r\n\t\t$node.InnerText = $TextValue\r\n\t}\r\n\t# Else the node doesn't exist yet, so create it with the given value.\r\n\telse\r\n\t{\r\n\t\t# Create the new element with the given value.\r\n\t\t$elementName = $ElementPath.Substring($ElementPath.LastIndexOf($NodeSeparatorCharacter) + 1)\r\n \t\t$element = $XmlDocument.CreateElement($elementName, $XmlDocument.DocumentElement.NamespaceURI)\t\t\r\n\t\t$textNode = $XmlDocument.CreateTextNode($TextValue)\r\n\t\t$element.AppendChild($textNode) > $null\r\n\t\t\r\n\t\t# Try and get the parent node.\r\n\t\t$parentNodePath = $ElementPath.Substring(0, $ElementPath.LastIndexOf($NodeSeparatorCharacter))\r\n\t\t$parentNode = Get-XmlNode -XmlDocument $XmlDocument -NodePath $parentNodePath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter\r\n\t\t\r\n\t\tif ($parentNode)\r\n\t\t{\r\n\t\t\t$parentNode.AppendChild($element) > $null\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthrow \"$parentNodePath does not exist in the xml.\"\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction Update-XML-Attribute($File, $xpath, $name, $value) \r\n{\r\n\t\t$File = Resolve-Path $File\r\n\t\r\n\t\t[xml] $fileContents = Get-Content -Encoding UTF8 -Path $File\r\n\t\r\n\t\tif ($null -ne $value -and $value -ne \"\") {\r\n\t\t\tSet-XmlAttributeValue -XmlDocument $fileContents -ElementPath $xpath -AttributeName $name -AttributeValue $value\r\n\t\t}\r\n\t\t$fileContents.Save($File)\r\n}\r\n\r\nfunction Update-XML-Text($File, $xpath, $value)\r\n{\r\n\t$File = Resolve-Path $File\r\n\r\n\t[xml] $fileContents = Get-Content -Encoding UTF8 -Path $File\r\n\r\n\tif ($null -ne $value -and $value -ne \"\") {\r\n\t\tSet-XmlElementsTextValue -XmlDocument $fileContents -ElementPath $xpath -TextValue $value\r\n\t}\r\n\r\n\t$fileContents.Save($File)\r\n}\r\nfunction Get-Current-Commit-Hash ()\r\n{\r\n\treturn ([string](git log -1 --pretty=%h)).Trim()\r\n}\r\n\r\nfunction Get-VersionInfo($type, $separator)\r\n{\r\n\tif ($type -eq \"hash\")\r\n\t{\r\n\t\treturn Get-Current-Commit-Hash\r\n\t}\r\n\r\n\t$major = [string](gitversion /showvariable Major)\r\n\t$minor = [string](gitversion /showvariable Minor)\r\n\t$patch = [string](gitversion /showvariable Patch)\r\n\t$build = [string](gitversion /showvariable BuildMetaData)\r\n\r\n\t$version = \"$major.$minor.$patch\"\r\n\r\n\tif ($build -ne \"\")\r\n\t{\r\n\t\t$version = $version + $separator + $build\r\n\t}\r\n\r\n\treturn $version\r\n}\r\n"
  },
  {
    "path": "docs/install-sample-gui-apps.sh",
    "content": "#!/bin/bash\n\n# Make sure you are using Ubuntu 20.04!!\n\napt update\n\n## Gedit\n\napt install gedit -y\n\n## GIMP\n\napt install gimp -y\n\n## Nautilus\n\napt install nautilus -y\n\n## X11 apps\n\napt install x11-apps -y\n\n## Google Chrome\n\ncd /tmp\nwget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb\ndpkg -i google-chrome-stable_current_amd64.deb \napt install --fix-broken -y\ndpkg -i google-chrome-stable_current_amd64.deb\n\n## Microsoft teams\n\ncd /tmp\ncurl -L -o \"./teams.deb\" \"https://teams.microsoft.com/downloads/desktopurl?env=production&plat=linux&arch=x64&download=true&linuxArchiveType=deb\"\napt install ./teams.deb -y\n\n## Edge Browser\n\ncurl 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\napt install /tmp/edge.deb -y"
  },
  {
    "path": "msi/updateversion.ps1",
    "content": "param ([string] $XmlFile = $null )\r\n\r\nfunction Get-XmlNamespaceManager([xml]$XmlDocument, [string]$NamespaceURI = \"\")\r\n{\r\n    # If a Namespace URI was not given, use the Xml document's default namespace.\r\n\tif ([string]::IsNullOrEmpty($NamespaceURI)) { $NamespaceURI = $XmlDocument.DocumentElement.NamespaceURI }\t\r\n\t\r\n\t# 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.\r\n\t[System.Xml.XmlNamespaceManager]$xmlNsManager = New-Object System.Xml.XmlNamespaceManager($XmlDocument.NameTable)\r\n\t$xmlNsManager.AddNamespace(\"ns\", $NamespaceURI)\r\n    return ,$xmlNsManager\t\t# Need to put the comma before the variable name so that PowerShell doesn't convert it into an Object[].\r\n}\r\n\r\nfunction Get-FullyQualifiedXmlNodePath([string]$NodePath, [string]$NodeSeparatorCharacter = '.')\r\n{\r\n    return \"/ns:$($NodePath.Replace($('.'), '/ns:'))\"\r\n}\r\n\r\nfunction Get-XmlNode([xml]$XmlDocument, [string]$NodePath, [string]$NamespaceURI = \"\", [string]$NodeSeparatorCharacter = '.')\r\n{\r\n\t$xmlNsManager = Get-XmlNamespaceManager -XmlDocument $XmlDocument -NamespaceURI $NamespaceURI\r\n\t[string]$fullyQualifiedNodePath = Get-FullyQualifiedXmlNodePath -NodePath $NodePath -NodeSeparatorCharacter $NodeSeparatorCharacter\r\n\t\r\n\t# Try and get the node, then return it. Returns $null if the node was not found.\r\n\t$node = $XmlDocument.SelectSingleNode($fullyQualifiedNodePath, $xmlNsManager)\r\n\treturn $node\r\n}\r\n\r\nfunction Set-XmlAttributeValue([xml]$XmlDocument, [string]$ElementPath, [string]$AttributeName, [string]$AttributeValue, [string]$NamespaceURI = \"\", [string]$NodeSeparatorCharacter = '.')\r\n{\r\n\t# Try and get the node.\t\r\n\t$node = Get-XmlNode -XmlDocument $XmlDocument -NodePath $ElementPath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter\r\n\t$node.SetAttribute($AttributeName, $AttributeValue)\r\n}\r\n\r\nfunction Update-XML-Attribute($File, $xpath, $name, $value) \r\n{\r\n\t\t$File = Resolve-Path $File\r\n\t\r\n\t\t[xml] $fileContents = Get-Content -Encoding UTF8 -Path $File\r\n\t\r\n\t\tif ($null -ne $value -and $value -ne \"\") {\r\n\t\t\tSet-XmlAttributeValue -XmlDocument $fileContents -ElementPath $xpath -AttributeName $name -AttributeValue $value\r\n\t\t}\r\n\t\t$fileContents.Save($File)\r\n}\r\n\r\n$major = [string](gitversion /showvariable Major)\r\n$minor = [string](gitversion /showvariable Minor)\r\n$patch = [string](gitversion /showvariable Patch)\r\n$build = [string](gitversion /showvariable BuildMetaData)\r\n\r\nif ($build -eq \"\")\r\n{\r\n    $build = \"0\"\r\n}\r\n\r\nif ($XmlFile -ne $null)\r\n{\r\n    $version = \"$major.$minor.$patch.$build\";\r\n    Update-XML-Attribute $XmlFile \"Wix.Product\" \"Version\" $version\r\n}\r\n"
  },
  {
    "path": "package/wslg.rdp",
    "content": "audiocapturemode:i:2\r\nauthentication level:i:0\r\ndisableconnectionsharing:i:1\r\nenablecredsspsupport:i:0\r\nhvsocketenabled:i:1\r\nremoteapplicationmode:i:1\r\nremoteapplicationprogram:s:dummy-entry"
  },
  {
    "path": "package/wslg_desktop.rdp",
    "content": "audiocapturemode:i:2\r\nauthentication level:i:0\r\ndisableconnectionsharing:i:1\r\nenablecredsspsupport:i:0\r\nhvsocketenabled:i:1\r\n"
  },
  {
    "path": "rdpapplist/meson.build",
    "content": "project('rdpapplist',\r\n    'c',\r\n    version : '2.0.0',\r\n)\r\n\r\nname_rdpapplist = meson.project_name()\r\nversion_rdpapplist = meson.project_version()\r\n\r\nconfig_h = configuration_data()\r\n\r\ndir_prefix = get_option('prefix')\r\n\r\ndir_lib = join_paths(dir_prefix, get_option('libdir'))\r\ninstall_lib_dir_rdpapplist = join_paths(dir_lib, name_rdpapplist)\r\nconfig_h.set_quoted('RDPAPPLIST_MODULEDIR',\r\n     install_lib_dir_rdpapplist)\r\n\r\ndir_inc = join_paths(dir_prefix, get_option('includedir'))\r\ninstall_inc_dir_rdpapplist = join_paths(dir_inc, name_rdpapplist)\r\nconfig_h.set_quoted('RDPAPPLIST_INCLUDEDIR',\r\n     install_inc_dir_rdpapplist)\r\n\r\ndep_frdp = dependency('freerdp3', version: '>= 3.0.0', required: false)\r\nif not dep_frdp.found()\r\n    dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false)\r\n    if not dep_frdp.found()\r\n        error('rdpapplist requires freerdp2 or 3 which was not found.')\r\n    endif\r\nendif\r\n\r\ndep_winpr = dependency('winpr3', version: '>= 3.0.0', required: false)\r\nif not dep_winpr.found()\r\n    dep_winpr = dependency('winpr2', version: '>= 2.0.0', required: false)\r\n    if not dep_frdp.found()\r\n        error('rdpapplist requires winpr2 or 3 which was not found.')\r\n    endif\r\nendif\r\n\r\nconfigure_file(output: 'rdpapplist_config.h',\r\n     configuration: config_h,\r\n     install_dir: install_inc_dir_rdpapplist)\r\n\r\nsubdir('server')\r\n\r\npkgconfig = import('pkgconfig')\r\npkgconfig.generate(\r\n    filebase: name_rdpapplist,\r\n    version: version_rdpapplist,\r\n    name: 'Application list Plugin for FreeRDP',\r\n    description: 'Header files for Application list Plugin for FreeRDP',\r\n)\r\n\r\ninstall_headers(\r\n    'rdpapplist_protocol.h',\r\n    'rdpapplist_server.h',\r\n    subdir: name_rdpapplist,\r\n)\r\n"
  },
  {
    "path": "rdpapplist/rdpapplist_common.c",
    "content": "/**\n * FreeRDP: A Remote Desktop Protocol Implementation\n * RDPXXXX Remote Application List Virtual Channel Extension\n *\n * Copyright 2020 Microsoft\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <winpr/crt.h>\n#include <winpr/stream.h>\n#include <freerdp/channels/log.h>\n\n#define TAG CHANNELS_TAG(\"rdpapplist.common\")\n\n#include \"rdpapplist_common.h\"\n\n/**\n * Function description\n *\n * @return 0 on success, otherwise a Win32 error code\n */\nUINT rdpapplist_read_header(wStream* s, RDPAPPLIST_HEADER* header)\n{\n\tif (Stream_GetRemainingLength(s) < 8)\n\t{\n\t\tWLog_ERR(TAG, \"header parsing failed: not enough data!\");\n\t\treturn ERROR_INVALID_DATA;\n\t}\n\n\tStream_Read_UINT32(s, header->cmdId);\n\tStream_Read_UINT32(s, header->length);\n\treturn CHANNEL_RC_OK;\n}\n\n/**\n * Function description\n *\n * @return 0 on success, otherwise a Win32 error code\n */\nUINT rdpapplist_write_header(wStream* s, const RDPAPPLIST_HEADER* header)\n{\n\tStream_Write_UINT32(s, header->cmdId);\n\tStream_Write_UINT32(s, header->length);\n\treturn CHANNEL_RC_OK;\n}\n"
  },
  {
    "path": "rdpapplist/rdpapplist_common.h",
    "content": "/**\n * FreeRDP: A Remote Desktop Protocol Implementation\n * RDPXXXX Remote Application List Virtual Channel Extension\n *\n * Copyright 2020 Microsoft\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef FREERDP_CHANNEL_RDPAPPLIST_COMMON_H\n#define FREERDP_CHANNEL_RDPAPPLIST_COMMON_H\n\n#include <winpr/crt.h>\n#include <winpr/stream.h>\n\n#include <freerdp/api.h>\n#include <rdpapplist_protocol.h>\n\nFREERDP_LOCAL UINT rdpapplist_read_header(wStream* s, RDPAPPLIST_HEADER* header);\nFREERDP_LOCAL UINT rdpapplist_write_header(wStream* s, const RDPAPPLIST_HEADER* header);\n\n#endif /* FREERDP_CHANNEL_RDPAPPLIST_COMMON_H */\n"
  },
  {
    "path": "rdpapplist/rdpapplist_protocol.h",
    "content": "/**\n * FreeRDP: A Remote Desktop Protocol Implementation\n * RDPXXXX Remote Application List Virtual Channel Extension\n *\n * Copyright 2020 Microsoft\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *\t http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef FREERDP_CHANNEL_RDPAPPLIST_H\n#define FREERDP_CHANNEL_RDPAPPLIST_H\n\n#include <freerdp/api.h>\n#include <freerdp/types.h>\n#include <freerdp/rail.h>\n\n#define RDPAPPLIST_DVC_CHANNEL_NAME \"Microsoft::Windows::RDS::RemoteApplicationList\"\n\n/* Version 4\n * - add RDPAPPLIST_SERVER_CAPS_PDU.appListProviderUniqueId field.\n * - add RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID_PUD message.\n */\n#define RDPAPPLIST_CHANNEL_VERSION 4\n\n#define RDPAPPLIST_CMDID_CAPS 0x00000001\n#define RDPAPPLIST_CMDID_UPDATE_APPLIST 0x00000002\n#define RDPAPPLIST_CMDID_DELETE_APPLIST 0x00000003\n#define RDPAPPLIST_CMDID_DELETE_APPLIST_PROVIDER 0x00000004\n#define RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID 0x00000005\n\n#define RDPAPPLIST_ICON_FORMAT_PNG 0x0001\n#define RDPAPPLIST_ICON_FORMAT_BMP 0x0002\n#define RDPAPPLIST_ICON_FORMAT_SVG 0x0003\n\n#define RDPAPPLIST_FIELD_ID         0x00000001\n#define RDPAPPLIST_FIELD_GROUP      0x00000002\n#define RDPAPPLIST_FIELD_EXECPATH   0x00000004\n#define RDPAPPLIST_FIELD_DESC       0x00000008\n#define RDPAPPLIST_FIELD_ICON       0x00000010\n#define RDPAPPLIST_FIELD_PROVIDER   0x00000020\n#define RDPAPPLIST_FIELD_WORKINGDIR 0x00000040\n#define RDPAPPLIST_FIELD_WINDOW_ID  0x00000080\n\n/* RDPAPPLIST_UPDATE_APPLIST_PDU */\n#define RDPAPPLIST_HINT_NEWID      0x00010000 /* new appId vs update existing appId. */\n#define RDPAPPLIST_HINT_SYNC       0x00100000 /* In sync mode (use with _NEWID). */\n#define RDPAPPLIST_HINT_SYNC_START 0x00200000 /* Sync appId start (use with _SYNC). */\n#define RDPAPPLIST_HINT_SYNC_END   0x00400000 /* Sync appId end (use with _SYNC). */\n/* Client should remove any app entry those are not reported between _START and _END during sync mode */\n\n#define RDPAPPLIST_HEADER_SIZE 8\n\n#define RDPAPPLIST_LANG_SIZE 32\n\n#define RDPAPPLIST_MAX_STRING_SIZE 512\n\nstruct _RDPAPPLIST_HEADER\n{\n\tUINT32 cmdId;\n\tUINT32 length;\n};\n\ntypedef struct _RDPAPPLIST_HEADER RDPAPPLIST_HEADER;\n\nstruct _RDPAPPLIST_SERVER_CAPS_PDU\n{\n\tUINT16 version;\n\tRAIL_UNICODE_STRING appListProviderName; /* name of app list provider. */\n\tRAIL_UNICODE_STRING appListProviderUniqueId; /* added from version 4 */\n};\n\ntypedef struct _RDPAPPLIST_SERVER_CAPS_PDU RDPAPPLIST_SERVER_CAPS_PDU;\n\nstruct _RDPAPPLIST_CLIENT_CAPS_PDU\n{\n\tUINT16 version;\n\t/* ISO 639 (Language name) and ISO 3166 (Country name) connected with '_', such as en_US, ja_JP */\n\tchar clientLanguageId[RDPAPPLIST_LANG_SIZE];\n};\n\ntypedef struct _RDPAPPLIST_CLIENT_CAPS_PDU RDPAPPLIST_CLIENT_CAPS_PDU;\n\nstruct _RDPAPPLIST_ICON_DATA {\n\tUINT32 flags;\n\tUINT32 iconWidth;\n\tUINT32 iconHeight;\n\tUINT32 iconStride;\n\tUINT32 iconBpp;\n\tUINT32 iconFormat; /* RDPAPPLIST_ICON_FORMAT_* */\n\tUINT32 iconBitsLength; /* size of buffer pointed by iconBits. */\n\tVOID *iconBits; /* icon image data */\n\t\t\t/* For BMP, image data only */\n\t\t\t/* For PNG, entire PNG file including headers */\n\t\t\t/* For SVG, entire SVG file including headers */\n\t\t \t/* For 32bpp image, alpha-channel works as mask */\n};\n\ntypedef struct _RDPAPPLIST_ICON_DATA RDPAPPLIST_ICON_DATA;\n\n/* Create or update application program link in client */\n\nstruct _RDPAPPLIST_UPDATE_APPLIST_PDU\n{\n\tUINT32 flags;\n\tRAIL_UNICODE_STRING appId; /* Identifier of application to be added\n\t\t\t\t      to client's Start Menu. This is used as\n\t\t\t\t      the file name of link (.lnk) at Start Menu. */\n\tRAIL_UNICODE_STRING appGroup; /* name of app group. */\n\tRAIL_UNICODE_STRING appExecPath; /* Path to server side executable. */\n\tRAIL_UNICODE_STRING appWorkingDir; /* Working directory to run the executable in. */\n\tRAIL_UNICODE_STRING appDesc; /* UI friendly description of application. */\n\tRDPAPPLIST_ICON_DATA *appIcon;\n};\n\ntypedef struct _RDPAPPLIST_UPDATE_APPLIST_PDU RDPAPPLIST_UPDATE_APPLIST_PDU;\n\n/* Delete specififed application program link from client */\n\nstruct _RDPAPPLIST_DELETE_APPLIST_PDU\n{\n\tUINT32 flags;\n\tRAIL_UNICODE_STRING appId; /* Identifier of application to be removed\n\t\t\t\t      from client's Start Menu. */\n\tRAIL_UNICODE_STRING appGroup; /* name of app group. */\n};\n\ntypedef struct _RDPAPPLIST_DELETE_APPLIST_PDU RDPAPPLIST_DELETE_APPLIST_PDU;\n\n/* Delete all application program link under specififed provider from client */\n\nstruct _RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU\n{\n\tUINT32 flags;\n\tRAIL_UNICODE_STRING appListProviderName; /* name of app list provider to be removed\n\t\t\t\t\t\t    from client's Start Menu. */\n};\n\ntypedef struct _RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU;\n\nstruct _RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU\n{\n\tUINT32 flags;\n\tUINT32 appWindowId; /* window id of application */\n\tRAIL_UNICODE_STRING appId; /* Identifier of application to add taskbar property. */\n\tRAIL_UNICODE_STRING appGroup; /* Identifier of group where application belonging to. */\n\tRAIL_UNICODE_STRING appExecPath; /* Path to server side executable. */\n\tRAIL_UNICODE_STRING appDesc; /* UI friendly description of application. */\n};\n\ntypedef struct _RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU;\n\n#endif /* FREERDP_CHANNEL_RDPAPPLIST_H */\n"
  },
  {
    "path": "rdpapplist/rdpapplist_server.h",
    "content": "/**\n * FreeRDP: A Remote Desktop Protocol Implementation\n * RDPXXXX Remote Application List Virtual Channel Extension\n *\n * Copyright 2020 Microsoft\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *\t http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef FREERDP_CHANNEL_RDPAPPLIST_SERVER_RDPAPPLIST_H\n#define FREERDP_CHANNEL_RDPAPPLIST_SERVER_RDPAPPLIST_H\n\n#include \"rdpapplist_protocol.h\"\n\n#include <freerdp/freerdp.h>\n#include <freerdp/api.h>\n#include <freerdp/types.h>\n\ntypedef struct _rdpapplist_server_private RdpAppListServerPrivate;\ntypedef struct _rdpapplist_server_context RdpAppListServerContext;\n\ntypedef UINT (*psRdpAppListOpen)(RdpAppListServerContext* context);\ntypedef UINT (*psRdpAppListClose)(RdpAppListServerContext* context);\n\ntypedef UINT (*psRdpAppListCaps)(RdpAppListServerContext* context, const RDPAPPLIST_SERVER_CAPS_PDU *caps);\ntypedef UINT (*psRdpAppListUpdate)(RdpAppListServerContext* context, const RDPAPPLIST_UPDATE_APPLIST_PDU *updateAppList);\ntypedef UINT (*psRdpAppListDelete)(RdpAppListServerContext* context, const RDPAPPLIST_DELETE_APPLIST_PDU *deleteAppList);\ntypedef UINT (*psRdpAppListDeleteProvider)(RdpAppListServerContext* context, const RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU *deleteAppListProvider);\ntypedef UINT (*psRdpAppListAssociateWindowId)(RdpAppListServerContext* context, const RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU *associateWindowId);\n\ntypedef UINT (*psRdpAppListClientCaps)(RdpAppListServerContext* context, const RDPAPPLIST_CLIENT_CAPS_PDU *clientCaps);\n\nstruct _rdpapplist_server_context\n{\n\tvoid* custom;\n\tHANDLE vcm;\n\n\tpsRdpAppListOpen Open;\n\tpsRdpAppListClose Close;\n\n\tpsRdpAppListCaps ApplicationListCaps;\n\tpsRdpAppListUpdate UpdateApplicationList;\n\tpsRdpAppListDelete DeleteApplicationList;\n\tpsRdpAppListDeleteProvider DeleteApplicationListProvider;\n\tpsRdpAppListAssociateWindowId AssociateWindowId;\n\n\tpsRdpAppListClientCaps ApplicationListClientCaps;\n\n\tRdpAppListServerPrivate* priv;\n\trdpContext* rdpcontext;\n};\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n\tFREERDP_API RdpAppListServerContext* rdpapplist_server_context_new(HANDLE vcm);\n\tFREERDP_API void rdpapplist_server_context_free(RdpAppListServerContext* context);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* FREERDP_CHANNEL_RDPAPPLIST_SERVER_RDPAPPLIST_H */\n"
  },
  {
    "path": "rdpapplist/server/meson.build",
    "content": "dep_frdp_server = dependency('freerdp-server3', version: '>= 3.0.0', required: false)\nif not dep_frdp_server.found()\n    dep_frdp_server = dependency('freerdp-server2', version: '>= 2.0.0', required: false)\n    if not dep_frdp_server.found()\n        error('librapapplist-server requires freerdp-server2 or 3 which was not found.')\n    endif\nendif\n\ndeps_librdpapplist_server = [\n    dep_frdp,\n    dep_frdp_server,\n    dep_winpr,\n]\n\nsrcs_librdpapplist_server = [\n    '../rdpapplist_common.c',\n    'rdpapplist_main.c',\n]\n\nincs_common_server = [\n    '../',\n]\n\nplugin_rdpapplist_server = shared_library(\n    'librdpapplist-server',\n    srcs_librdpapplist_server,\n    include_directories: incs_common_server,\n    dependencies: deps_librdpapplist_server,\n    name_prefix: '',\n    install: true,\n    install_dir: install_lib_dir_rdpapplist,\n)\n"
  },
  {
    "path": "rdpapplist/server/rdpapplist_main.c",
    "content": "/**\n * FreeRDP: A Remote Desktop Protocol Implementation\n * RDPXXXX Remote Application List Virtual Channel Extension\n *\n * Copyright 2020 Microsoft\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *\t http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <winpr/crt.h>\n#include <winpr/synch.h>\n#include <winpr/thread.h>\n#include <winpr/stream.h>\n#include <winpr/sysinfo.h>\n#include <freerdp/channels/wtsvc.h>\n#include <freerdp/channels/log.h>\n\n#include \"rdpapplist_common.h\"\n#include \"rdpapplist_server.h\"\n#include \"rdpapplist_main.h\"\n\n#define TAG CHANNELS_TAG(\"rdpapplist.server\")\n\n/**\n * Function description\n *\n * @return 0 on success, otherwise a Win32 error code\n */\nstatic UINT rdpapplist_recv_caps_pdu(wStream* s, RdpAppListServerContext* context)\n{\n\tUINT32 error = CHANNEL_RC_OK;\n\tRDPAPPLIST_CLIENT_CAPS_PDU pdu;\n\n\tif (Stream_GetRemainingLength(s) < 2)\n\t{\n\t\tWLog_ERR(TAG, \"not enough data!\");\n\t\treturn ERROR_INVALID_DATA;\n\t}\n\n\tStream_Read_UINT16(s, pdu.version); /* version (2 bytes) */\n\tif (Stream_GetRemainingLength(s) < RDPAPPLIST_LANG_SIZE)\n\t{\n\t\tWLog_ERR(TAG, \"not enough data!\");\n\t\treturn ERROR_INVALID_DATA;\n\t}\n\tStream_Read(s, &pdu.clientLanguageId[0], RDPAPPLIST_LANG_SIZE);\n\n\tif (context)\n\t\tIFCALLRET(context->ApplicationListClientCaps, error, context, &pdu);\n\n\treturn error;\n}\n\nstatic UINT rdpapplist_server_receive_pdu(RdpAppListServerContext* context, wStream* s)\n{\n\tUINT error = CHANNEL_RC_OK;\n\tsize_t beg, end;\n\tRDPAPPLIST_HEADER header;\n\tbeg = Stream_GetPosition(s);\n\n\tif ((error = rdpapplist_read_header(s, &header)))\n\t{\n\t\tWLog_ERR(TAG, \"rdpapplist_read_header failed with error %\" PRIu32 \"!\", error);\n\t\treturn error;\n\t}\n\n\tswitch (header.cmdId)\n\t{\n\t\tcase RDPAPPLIST_CMDID_CAPS:\n\t\t\tif ((error = rdpapplist_recv_caps_pdu(s, context)))\n\t\t\t\tWLog_ERR(TAG,\n\t\t\t\t         \"rdpapplist_recv_caps_pdu \"\n\t\t\t\t         \"failed with error %\" PRIu32 \"!\",\n\t\t\t\t         error);\n\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\terror = CHANNEL_RC_BAD_PROC;\n\t\t\tWLog_WARN(TAG, \"Received unknown PDU type: %\" PRIu32 \"\", header.cmdId);\n\t\t\tbreak;\n\t}\n\n\tend = Stream_GetPosition(s);\n\n\tif (end != (beg + header.length))\n\t{\n\t\tWLog_ERR(TAG, \"Unexpected RDPAPPLIST pdu end: Actual: %d, Expected: %\" PRIu32 \"\", end,\n\t\t         (beg + header.length));\n\t\tStream_SetPosition(s, (beg + header.length));\n\t}\n\n\treturn error;\n}\n\n/**\n * Function description\n *\n * @return 0 on success, otherwise a Win32 error code\n */\nstatic UINT rdpapplist_server_handle_messages(RdpAppListServerContext* context)\n{\n\tDWORD BytesReturned;\n\tvoid* buffer;\n\tUINT ret = CHANNEL_RC_OK;\n\tRdpAppListServerPrivate* priv = context->priv;\n\twStream* s = priv->input_stream;\n\n\t/* Check whether the dynamic channel is ready */\n\tif (!priv->isReady)\n\t{\n\t\tif (WTSVirtualChannelQuery(priv->rdpapplist_channel, WTSVirtualChannelReady, &buffer,\n\t\t                           &BytesReturned) == FALSE)\n\t\t{\n\t\t\tif (GetLastError() == ERROR_NO_DATA)\n\t\t\t\treturn ERROR_NO_DATA;\n\n\t\t\tWLog_ERR(TAG, \"WTSVirtualChannelQuery failed\");\n\t\t\treturn ERROR_INTERNAL_ERROR;\n\t\t}\n\n\t\tpriv->isReady = *((BOOL*)buffer);\n\t\tWTSFreeMemory(buffer);\n\t}\n\n\t/* Consume channel event only after the dynamic channel is ready */\n\tif (priv->isReady)\n\t{\n\t\tStream_SetPosition(s, 0);\n\n\t\tif (!WTSVirtualChannelRead(priv->rdpapplist_channel, 0, NULL, 0, &BytesReturned))\n\t\t{\n\t\t\tif (GetLastError() == ERROR_NO_DATA)\n\t\t\t\treturn ERROR_NO_DATA;\n\n\t\t\tWLog_ERR(TAG, \"WTSVirtualChannelRead failed!\");\n\t\t\treturn ERROR_INTERNAL_ERROR;\n\t\t}\n\n\t\tif (BytesReturned < 1)\n\t\t\treturn CHANNEL_RC_OK;\n\n\t\tif (!Stream_EnsureRemainingCapacity(s, BytesReturned))\n\t\t{\n\t\t\tWLog_ERR(TAG, \"Stream_EnsureRemainingCapacity failed!\");\n\t\t\treturn CHANNEL_RC_NO_MEMORY;\n\t\t}\n\n\t\tif (WTSVirtualChannelRead(priv->rdpapplist_channel, 0, (PCHAR)Stream_Buffer(s), Stream_Capacity(s),\n\t\t                          &BytesReturned) == FALSE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \"WTSVirtualChannelRead failed!\");\n\t\t\treturn ERROR_INTERNAL_ERROR;\n\t\t}\n\n\t\tStream_SetLength(s, BytesReturned);\n\t\tStream_SetPosition(s, 0);\n\n\t\twhile (Stream_GetPosition(s) < Stream_Length(s))\n\t\t{\n\t\t\tif ((ret = rdpapplist_server_receive_pdu(context, s)))\n\t\t\t{\n\t\t\t\tWLog_ERR(TAG,\n\t\t\t\t         \"rdpapplist_server_receive_pdu \"\n\t\t\t\t         \"failed with error %\" PRIu32 \"!\",\n\t\t\t\t         ret);\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ret;\n}\n\n/**\n * Function description\n *\n * @return 0 on success, otherwise a Win32 error code\n */\nstatic DWORD WINAPI rdpapplist_server_thread_func(LPVOID arg)\n{\n\tRdpAppListServerContext* context = (RdpAppListServerContext*)arg;\n\tRdpAppListServerPrivate* priv = context->priv;\n\tDWORD status;\n\tDWORD nCount;\n\tHANDLE events[8];\n\tUINT error = CHANNEL_RC_OK;\n\tnCount = 0;\n\tevents[nCount++] = priv->stopEvent;\n\tevents[nCount++] = priv->channelEvent;\n\n\twhile (TRUE)\n\t{\n\t\tstatus = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);\n\n\t\tif (status == WAIT_FAILED)\n\t\t{\n\t\t\terror = GetLastError();\n\t\t\tWLog_ERR(TAG, \"WaitForMultipleObjects failed with error %\" PRIu32 \"\", error);\n\t\t\tbreak;\n\t\t}\n\n\t\t/* Stop Event */\n\t\tif (status == WAIT_OBJECT_0)\n\t\t\tbreak;\n\n\t\tif ((error = rdpapplist_server_handle_messages(context)))\n\t\t{\n\t\t\tWLog_ERR(TAG, \"rdpapplist_server_handle_messages failed with error %\" PRIu32 \"\", error);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tExitThread(error);\n\treturn error;\n}\n\n/**\n * Function description\n * Create new stream for single rdpapplist packet. The new stream length\n * would be required data length + header. The header will be written\n * to the stream before return.\n *\n * @param cmdId\n * @param length - data length without header\n *\n * @return new stream\n */\nstatic wStream* rdpapplist_server_single_packet_new(UINT32 cmdId, UINT32 length)\n{\n\tUINT error;\n\tRDPAPPLIST_HEADER header;\n\twStream* s = Stream_New(NULL, RDPAPPLIST_HEADER_SIZE + length);\n\n\tif (!s)\n\t{\n\t\tWLog_ERR(TAG, \"Stream_New failed!\");\n\t\tgoto error;\n\t}\n\n\theader.cmdId = cmdId;\n\theader.length = RDPAPPLIST_HEADER_SIZE + length;\n\n\tif ((error = rdpapplist_write_header(s, &header)))\n\t{\n\t\tWLog_ERR(TAG, \"Failed to write header with error %\" PRIu32 \"!\", error);\n\t\tgoto error;\n\t}\n\n\treturn s;\nerror:\n\tStream_Free(s, TRUE);\n\treturn NULL;\n}\n\n/**\n * Function description\n *\n * @return 0 on success, otherwise a Win32 error code\n */\nstatic UINT rdpapplist_server_packet_send(RdpAppListServerContext* context, wStream* s)\n{\n\tUINT ret;\n\tULONG written;\n\n\tif (!WTSVirtualChannelWrite(context->priv->rdpapplist_channel, (PCHAR)Stream_Buffer(s),\n\t                            Stream_GetPosition(s), &written))\n\t{\n\t\tWLog_ERR(TAG, \"WTSVirtualChannelWrite failed!\");\n\t\tret = ERROR_INTERNAL_ERROR;\n\t\tgoto out;\n\t}\n\n\tif (written < Stream_GetPosition(s))\n\t{\n\t\tWLog_WARN(TAG, \"Unexpected bytes written: %\" PRIu32 \"/%\" PRIuz \"\", written,\n\t\t          Stream_GetPosition(s));\n\t}\n\n\tret = CHANNEL_RC_OK;\nout:\n\tStream_Free(s, TRUE);\n\treturn ret;\n}\n\n/**\n * Function description\n *\n * @return 0 on success, otherwise a Win32 error code\n */\nstatic UINT rdpapplist_send_caps(RdpAppListServerContext *context, const RDPAPPLIST_SERVER_CAPS_PDU *caps)\n{\n\tif (caps->appListProviderName.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t{\n\t\tWLog_ERR(TAG, \"rdpapplist_send_caps: appProviderName is too large.\");\n\t\treturn ERROR_INVALID_DATA;\n\t}\n\n\tint len = 2 + // version.\n\t\t  2 + caps->appListProviderName.length +\n\t\t  2 + caps->appListProviderUniqueId.length;\n\n\twStream* s = rdpapplist_server_single_packet_new(RDPAPPLIST_CMDID_CAPS, len);\n\n\tif (!s)\n\t{\n\t\tWLog_ERR(TAG, \"rdpapplist_server_single_packet_new failed!\");\n\t\treturn CHANNEL_RC_NO_MEMORY;\n\t}\n\n\tStream_Write_UINT16(s, caps->version);\n\tStream_Write_UINT16(s, caps->appListProviderName.length);\n\tStream_Write(s, caps->appListProviderName.string,\n\t             caps->appListProviderName.length);\n\tStream_Write_UINT16(s, caps->appListProviderUniqueId.length);\n\tStream_Write(s, caps->appListProviderUniqueId.string,\n\t             caps->appListProviderUniqueId.length);\n\treturn rdpapplist_server_packet_send(context, s);\n}\n\n/**\n * Function description\n *\n * @return 0 on success, otherwise a Win32 error code\n */\nstatic UINT rdpapplist_send_update_applist(RdpAppListServerContext* context, const RDPAPPLIST_UPDATE_APPLIST_PDU *updateAppList)\n{\n\tUINT32 len = 4; // flags.\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_ID)\n\t{\n\t\tif (updateAppList->appId.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \"rdpapplist_send_update_applist: appId is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + updateAppList->appId.length);\n\t}\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_GROUP)\n\t{\n\t\tif (updateAppList->appGroup.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \"rdpapplist_send_update_applist: appGroup is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + updateAppList->appGroup.length);\n\t}\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_EXECPATH)\n\t{\n\t\tif (updateAppList->appExecPath.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \"rdpapplist_send_update_applist: appExecPath is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + updateAppList->appExecPath.length);\n\t}\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_WORKINGDIR)\n\t{\n\t\tif (updateAppList->appWorkingDir.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \"rdpapplist_send_update_applist: appWorkingDir is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + updateAppList->appWorkingDir.length);\n\t}\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_DESC)\n\t{\n\t\tif (updateAppList->appDesc.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \"rdpapplist_send_update_applist: appDesc is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + updateAppList->appDesc.length);\n\t}\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_ICON)\n\t{\n\t\tif (!updateAppList->appIcon)\n\t\t{\n\t\t\tWLog_ERR(TAG, \"rdpapplist_send_update_applist icon flag is set, but appIcon is NULL.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (7 * 4 + updateAppList->appIcon->iconBitsLength);\n\t}\n\n\twStream* s = rdpapplist_server_single_packet_new(RDPAPPLIST_CMDID_UPDATE_APPLIST, len);\n\n\tif (!s)\n\t{\n\t\tWLog_ERR(TAG, \"rdpapplist_server_single_packet_new failed!\");\n\t\treturn CHANNEL_RC_NO_MEMORY;\n\t}\n\n\tStream_Write_UINT32(s, updateAppList->flags);\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_ID)\n\t{\n\t\tStream_Write_UINT16(s, updateAppList->appId.length);\n\t\tStream_Write(s, updateAppList->appId.string,\n\t\t             updateAppList->appId.length);\n\t}\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_GROUP)\n\t{\n\t\tStream_Write_UINT16(s, updateAppList->appGroup.length);\n\t\tStream_Write(s, updateAppList->appGroup.string,\n\t\t             updateAppList->appGroup.length);\n\t}\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_EXECPATH)\n\t{\n\t\tStream_Write_UINT16(s, updateAppList->appExecPath.length);\n\t\tStream_Write(s, updateAppList->appExecPath.string,\n\t\t             updateAppList->appExecPath.length);\n\t}\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_WORKINGDIR)\n\t{\n\t\tStream_Write_UINT16(s, updateAppList->appWorkingDir.length);\n\t\tStream_Write(s, updateAppList->appWorkingDir.string,\n\t\t             updateAppList->appWorkingDir.length);\n\t}\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_DESC)\n\t{\n\t\tStream_Write_UINT16(s, updateAppList->appDesc.length);\n\t\tStream_Write(s, updateAppList->appDesc.string,\n\t\t             updateAppList->appDesc.length);\n\t}\n\tif (updateAppList->flags & RDPAPPLIST_FIELD_ICON)\n\t{\n\t\tStream_Write_UINT32(s, updateAppList->appIcon->flags);\n\t\tStream_Write_UINT32(s, updateAppList->appIcon->iconWidth);\n\t\tStream_Write_UINT32(s, updateAppList->appIcon->iconHeight);\n\t\tStream_Write_UINT32(s, updateAppList->appIcon->iconStride);\n\t\tStream_Write_UINT32(s, updateAppList->appIcon->iconBpp);\n\t\tStream_Write_UINT32(s, updateAppList->appIcon->iconFormat);\n\t\tStream_Write_UINT32(s, updateAppList->appIcon->iconBitsLength);\n\t\tStream_Write(s, updateAppList->appIcon->iconBits,\n\t\t\tupdateAppList->appIcon->iconBitsLength);\n\t}\n\n\treturn rdpapplist_server_packet_send(context, s);\n}\n\nstatic UINT rdpapplist_send_delete_applist(RdpAppListServerContext* context, const RDPAPPLIST_DELETE_APPLIST_PDU *deleteAppList)\n{\n\tUINT32 len = 4; // flags\n\tif (deleteAppList->flags & RDPAPPLIST_FIELD_ID)\n\t{\n\t\tif (deleteAppList->appId.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \" rdpapplist_send_delete_applist: appId is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + deleteAppList->appId.length);\n\t}\n\tif (deleteAppList->flags & RDPAPPLIST_FIELD_GROUP)\n\t{\n\t\tif (deleteAppList->appGroup.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \" rdpapplist_send_delete_applist: appGroup is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + deleteAppList->appGroup.length);\n\t}\n\n\twStream* s = rdpapplist_server_single_packet_new(RDPAPPLIST_CMDID_DELETE_APPLIST, len);\n\n\tif (!s)\n\t{\n\t\tWLog_ERR(TAG, \"rdpapplist_server_single_packet_new failed!\");\n\t\treturn CHANNEL_RC_NO_MEMORY;\n\t}\n\n\tStream_Write_UINT32(s, deleteAppList->flags);\n\tif (deleteAppList->flags & RDPAPPLIST_FIELD_ID)\n\t{\n\t\tStream_Write_UINT16(s, deleteAppList->appId.length);\n\t\tStream_Write(s, deleteAppList->appId.string,\n\t\t             deleteAppList->appId.length);\n\t}\n\tif (deleteAppList->flags & RDPAPPLIST_FIELD_GROUP)\n\t{\n\t\tStream_Write_UINT16(s, deleteAppList->appGroup.length);\n\t\tStream_Write(s, deleteAppList->appGroup.string,\n\t\t             deleteAppList->appGroup.length);\n\t}\n\treturn rdpapplist_server_packet_send(context, s);\n}\n\nstatic UINT rdpapplist_send_delete_applist_provider(RdpAppListServerContext* context, const RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU *deleteAppListProvider)\n{\n\tUINT32 len = 4; // flags.\n\tif (deleteAppListProvider->flags & RDPAPPLIST_FIELD_PROVIDER)\n\t{\n\t\tif (deleteAppListProvider->appListProviderName.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \" rdpapplist_send_delete_applist: appProviderName is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + deleteAppListProvider->appListProviderName.length);\n\t}\n\n\twStream* s = rdpapplist_server_single_packet_new(RDPAPPLIST_CMDID_DELETE_APPLIST_PROVIDER, len);\n\n\tif (!s)\n\t{\n\t\tWLog_ERR(TAG, \"rdpapplist_server_single_packet_new failed!\");\n\t\treturn CHANNEL_RC_NO_MEMORY;\n\t}\n\n\tStream_Write_UINT32(s, deleteAppListProvider->flags);\n\tif (deleteAppListProvider->flags & RDPAPPLIST_FIELD_PROVIDER)\n\t{\n\t\tStream_Write_UINT16(s, deleteAppListProvider->appListProviderName.length);\n\t\tStream_Write(s, deleteAppListProvider->appListProviderName.string,\n\t\t             deleteAppListProvider->appListProviderName.length);\n\t}\n\treturn rdpapplist_server_packet_send(context, s);\n}\n\nstatic UINT rdpapplist_send_associate_window_id(RdpAppListServerContext* context, const RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU *associateWindowId)\n{\n\tUINT32 len = 8; // flags + windowId.\n\n\tif (associateWindowId->flags & RDPAPPLIST_FIELD_ID)\n\t{\n\t\tif (associateWindowId->appId.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \" rdpapplist_send_associate_window_id: appId is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + associateWindowId->appId.length);\n\t}\n\tif (associateWindowId->flags & RDPAPPLIST_FIELD_GROUP)\n\t{\n\t\tif (associateWindowId->appGroup.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \" rdpapplist_send_associate_window_id: appGroup is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + associateWindowId->appGroup.length);\n\t}\n\tif (associateWindowId->flags & RDPAPPLIST_FIELD_EXECPATH)\n\t{\n\t\tif (associateWindowId->appExecPath.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \"rdpapplist_send_associate_window_id: appExecPath is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + associateWindowId->appExecPath.length);\n\t}\n\tif (associateWindowId->flags & RDPAPPLIST_FIELD_DESC)\n\t{\n\t\tif (associateWindowId->appDesc.length > RDPAPPLIST_MAX_STRING_SIZE)\n\t\t{\n\t\t\tWLog_ERR(TAG, \"rdpapplist_send_associate_window_id: appDesc is too large.\");\n\t\t\treturn ERROR_INVALID_DATA;\n\t\t}\n\t\tlen += (2 + associateWindowId->appDesc.length);\n\t}\n\n\twStream* s = rdpapplist_server_single_packet_new(RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID, len);\n\n\tif (!s)\n\t{\n\t\tWLog_ERR(TAG, \"rdpapplist_server_single_packet_new failed!\");\n\t\treturn CHANNEL_RC_NO_MEMORY;\n\t}\n\n\tStream_Write_UINT32(s, associateWindowId->flags);\n\tStream_Write_UINT32(s, associateWindowId->appWindowId);\n\tif (associateWindowId->flags & RDPAPPLIST_FIELD_ID)\n\t{\n\t\tStream_Write_UINT16(s, associateWindowId->appId.length);\n\t\tStream_Write(s, associateWindowId->appId.string,\n\t\t             associateWindowId->appId.length);\n\t}\n\tif (associateWindowId->flags & RDPAPPLIST_FIELD_GROUP)\n\t{\n\t\tStream_Write_UINT16(s, associateWindowId->appGroup.length);\n\t\tStream_Write(s, associateWindowId->appGroup.string,\n\t\t             associateWindowId->appGroup.length);\n\t}\n\tif (associateWindowId->flags & RDPAPPLIST_FIELD_EXECPATH)\n\t{\n\t\tStream_Write_UINT16(s, associateWindowId->appExecPath.length);\n\t\tStream_Write(s, associateWindowId->appExecPath.string,\n\t\t             associateWindowId->appExecPath.length);\n\t}\n\tif (associateWindowId->flags & RDPAPPLIST_FIELD_DESC)\n\t{\n\t\tStream_Write_UINT16(s, associateWindowId->appDesc.length);\n\t\tStream_Write(s, associateWindowId->appDesc.string,\n\t\t             associateWindowId->appDesc.length);\n\t}\n\treturn rdpapplist_server_packet_send(context, s);\n}\n\n/**\n * Function description\n *\n * @return 0 on success, otherwise a Win32 error code\n */\nstatic UINT rdpapplist_server_open(RdpAppListServerContext* context)\n{\n\tUINT rc = ERROR_INTERNAL_ERROR;\n\tRdpAppListServerPrivate* priv = context->priv;\n\tDWORD BytesReturned = 0;\n\tPULONG pSessionId = NULL;\n\tvoid* buffer;\n\tbuffer = NULL;\n\tpriv->SessionId = WTS_CURRENT_SESSION;\n\n\tif (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId,\n\t                                (LPSTR*)&pSessionId, &BytesReturned) == FALSE)\n\t{\n\t\tWLog_ERR(TAG, \"WTSQuerySessionInformationA failed!\");\n\t\trc = ERROR_INTERNAL_ERROR;\n\t\tgoto out_close;\n\t}\n\n\tpriv->SessionId = (DWORD)*pSessionId;\n\tWTSFreeMemory(pSessionId);\n\tpriv->rdpapplist_channel = (HANDLE)WTSVirtualChannelOpenEx(priv->SessionId, RDPAPPLIST_DVC_CHANNEL_NAME,\n\t                                                     WTS_CHANNEL_OPTION_DYNAMIC);\n\n\tif (!priv->rdpapplist_channel)\n\t{\n\t\tWLog_ERR(TAG, \"WTSVirtualChannelOpenEx failed!\");\n\t\trc = GetLastError();\n\t\tgoto out_close;\n\t}\n\n\t/* Query for channel event handle */\n\tif (!WTSVirtualChannelQuery(priv->rdpapplist_channel, WTSVirtualEventHandle, &buffer,\n\t                            &BytesReturned) ||\n\t    (BytesReturned != sizeof(HANDLE)))\n\t{\n\t\tWLog_ERR(TAG,\n\t\t         \"WTSVirtualChannelQuery failed \"\n\t\t         \"or invalid returned size(%\" PRIu32 \")\",\n\t\t         BytesReturned);\n\n\t\tif (buffer)\n\t\t\tWTSFreeMemory(buffer);\n\n\t\trc = ERROR_INTERNAL_ERROR;\n\t\tgoto out_close;\n\t}\n\n\tCopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE));\n\tWTSFreeMemory(buffer);\n\n\tif (priv->thread == NULL)\n\t{\n\t\tif (!(priv->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))\n\t\t{\n\t\t\tWLog_ERR(TAG, \"CreateEvent failed!\");\n\t\t\trc = ERROR_INTERNAL_ERROR;\n\t\t}\n\n\t\tif (!(priv->thread =\n\t\t          CreateThread(NULL, 0, rdpapplist_server_thread_func, (void*)context, 0, NULL)))\n\t\t{\n\t\t\tWLog_ERR(TAG, \"CreateEvent failed!\");\n\t\t\tCloseHandle(priv->stopEvent);\n\t\t\tpriv->stopEvent = NULL;\n\t\t\trc = ERROR_INTERNAL_ERROR;\n\t\t}\n\t}\n\n\treturn CHANNEL_RC_OK;\nout_close:\n\tWTSVirtualChannelClose(priv->rdpapplist_channel);\n\tpriv->rdpapplist_channel = NULL;\n\tpriv->channelEvent = NULL;\n\treturn rc;\n}\n\n/**\n * Function description\n *\n * @return 0 on success, otherwise a Win32 error code\n */\nstatic UINT rdpapplist_server_close(RdpAppListServerContext* context)\n{\n\tUINT error = CHANNEL_RC_OK;\n\tRdpAppListServerPrivate* priv = context->priv;\n\n\tif (priv->thread)\n\t{\n\t\tSetEvent(priv->stopEvent);\n\n\t\tif (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED)\n\t\t{\n\t\t\terror = GetLastError();\n\t\t\tWLog_ERR(TAG, \"WaitForSingleObject failed with error %\" PRIu32 \"\", error);\n\t\t\treturn error;\n\t\t}\n\n\t\tCloseHandle(priv->thread);\n\t\tCloseHandle(priv->stopEvent);\n\t\tpriv->thread = NULL;\n\t\tpriv->stopEvent = NULL;\n\t}\n\n\tif (priv->rdpapplist_channel)\n\t{\n\t\tWTSVirtualChannelClose(priv->rdpapplist_channel);\n\t\tpriv->rdpapplist_channel = NULL;\n\t}\n\n\treturn error;\n}\n\nRdpAppListServerContext* rdpapplist_server_context_new(HANDLE vcm)\n{\n\tRdpAppListServerContext* context;\n\tRdpAppListServerPrivate* priv;\n\tcontext = (RdpAppListServerContext*)calloc(1, sizeof(RdpAppListServerContext));\n\n\tif (!context)\n\t{\n\t\tWLog_ERR(TAG, \"rdpapplist_server_context_new(): calloc RdpAppListServerContext failed!\");\n\t\treturn NULL;\n\t}\n\n\tpriv = context->priv = (RdpAppListServerPrivate*)calloc(1, sizeof(RdpAppListServerPrivate));\n\n\tif (!context->priv)\n\t{\n\t\tWLog_ERR(TAG, \"rdpapplist_server_context_new(): calloc RdpAppListServerPrivate failed!\");\n\t\tgoto out_free;\n\t}\n\n\tpriv->input_stream = Stream_New(NULL, 4);\n\n\tif (!priv->input_stream)\n\t{\n\t\tWLog_ERR(TAG, \"Stream_New failed!\");\n\t\tgoto out_free_priv;\n\t}\n\n\tcontext->vcm = vcm;\n\tcontext->Open = rdpapplist_server_open;\n\tcontext->Close = rdpapplist_server_close;\n\tcontext->ApplicationListCaps = rdpapplist_send_caps;\n\tcontext->UpdateApplicationList = rdpapplist_send_update_applist;\n\tcontext->DeleteApplicationList = rdpapplist_send_delete_applist;\n\tcontext->DeleteApplicationListProvider = rdpapplist_send_delete_applist_provider;\n\tcontext->AssociateWindowId = rdpapplist_send_associate_window_id;\n\tpriv->isReady = FALSE;\n\treturn context;\nout_free_priv:\n\tfree(context->priv);\nout_free:\n\tfree(context);\n\treturn NULL;\n}\n\nvoid rdpapplist_server_context_free(RdpAppListServerContext* context)\n{\n\tif (!context)\n\t\treturn;\n\n\trdpapplist_server_close(context);\n\n\tif (context->priv)\n\t{\n\t\tStream_Free(context->priv->input_stream, TRUE);\n\t\tfree(context->priv);\n\t}\n\n\tfree(context);\n}\n"
  },
  {
    "path": "rdpapplist/server/rdpapplist_main.h",
    "content": "/**\n * FreeRDP: A Remote Desktop Protocol Implementation\n * RDPXXXX Remote Application List Virtual Channel Extension\n *\n * Copyright 2020 Microsoft\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef FREERDP_CHANNEL_RDPAPPLIST_SERVER_MAIN_H\n#define FREERDP_CHANNEL_RDPAPPLIST_SERVER_MAIN_H\n\nstruct _rdpapplist_server_private\n{\n\tBOOL isReady;\n\twStream* input_stream;\n\tHANDLE channelEvent;\n\tHANDLE thread;\n\tHANDLE stopEvent;\n\tDWORD SessionId;\n\n\tvoid* rdpapplist_channel;\n};\n\n#endif /* FREERDP_CHANNEL_RDPAPPLIST_SERVER_MAIN_H */\n"
  },
  {
    "path": "samples/container/Containers.md",
    "content": "# Containerizing GUI applications with WSLg\n\nFor 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.\n\n## Containerized GUI applications connecting to X11, Wayland or Pulse server\n\nIn order for a containerized application to access the servers provided by WSLg, the following mount location must be made visible inside the container.\n\n| Server | Mount |\n|---|---|\n| X11 | ```/tmp/.X11-unix``` |\n| Wayland | ```/mnt/wslg``` |\n| PulseAudio | ```/mnt/wslg``` |\n\nAnd the following environment variable must be share with the container.\n\n| Server | Environment variables |\n|---|---|\n| X11 | ```DISPLAY``` |\n| Wayland | ```WAYLAND_DISPLAY``` && ```XDG_RUNTIME_DIR``` |\n| PulseAudio | ```PULSE_SERVER``` |\n\nFor example, to run ```xclock``` as a containerized application, the following docker file can be use. \n\n```\nFROM ubuntu:20.04 as runtime\n\nARG DEBIAN_FRONTEND=noninteractive\n\nRUN apt update && \\\n   apt install -y x11-apps\n\nCMD /usr/bin/xclock\n```\n\nThe container can be build then launch as follow.\n\n```\nsudo docker build -t xclock -f Dockerfile.xclock .\nsudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \\\n    -e DISPLAY=$DISPLAY -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \\\n    -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR -e PULSE_SERVER=$PULSE_SERVER xclock\n```\n\nPlease 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.\n\n```\nsudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY xclock\n```\n\n## Containerized applications access to the vGPU\n\nFor a containerized application to use the vGPU provided by WSL2, the following must be done.\n\nThe following device must be shared with the container.\n\n```/dev/dxg```\n\nThe following mount location must be mapped in the container.\n\n```/usr/lib/wsl```\n\nThe following path must be added to the ```LD_LIBRARY_PATH``` environment variable inside the container.\n\n```/usr/lib/wsl/lib```\n\nFor example the following dockerfile containerized glxinfo. \n\n```\nFROM ubuntu:20.04 as runtime\n\nARG DEBIAN_FRONTEND=noninteractive\n\nRUN apt update && \\\n   apt install -y mesa-utils\n\nENV LD_LIBRARY_PATH=/usr/lib/wsl/lib\nCMD /usr/bin/glxinfo -B\n```\n\nThis container can be build and launch as follow.\n\n```\nsudo docker build -t glxinfo -f Dockerfile.glxinfo .\nsudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \\\n    -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \\\n    -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \\\n    -e PULSE_SERVER=$PULSE_SERVER --gpus all glxinfo\n```\n\n## Containerized applications access to vGPU accelerated video\n\nFor a containerized application to use vGPU video acceleration, the following must be done.\n\nThe following devices must be shared with the container.\n\n```/dev/dxg```\n\n```/dev/dri/card0```\n\n```/dev/dri/renderD128```\n\nThe following mount location must be mapped in the container.\n\n```/usr/lib/wsl```\n\nThe following path must be added to the ```LD_LIBRARY_PATH``` environment variable inside the container.\n\n```/usr/lib/wsl/lib```\n\nThe following VA-API driver name must be set to the ```LIBVA_DRIVER_NAME``` environment variable inside the container.\n\n```d3d12```\n\nThe following libraries must be installed in the container.\n\n```\n  vainfo\n  mesa-va-drivers\n```\nFor example the following dockerfile containerized `videoaccel`.\n\n```\nFROM ubuntu:22.10 as runtime\n\nARG DEBIAN_FRONTEND=noninteractive\n\n# Uncomment the lines below to use a 3rd party repository\n# to get the latest (unstable from mesa/main) mesa library version\n# RUN apt-get update && apt install -y software-properties-common\n# RUN add-apt-repository ppa:oibaf/graphics-drivers -y\n\nRUN apt update && apt install -y \\\n    vainfo \\\n    mesa-va-drivers\n\nENV LIBVA_DRIVER_NAME=d3d12\nENV LD_LIBRARY_PATH=/usr/lib/wsl/lib\nCMD vainfo --display drm --device /dev/dri/card0\n```\n\nThis container can be build and launch as follow.\n\n```\nsudo docker build -t videoaccel -f Dockerfile.videoaccel .\nsudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \\\n    -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \\\n    --device /dev/dri/card0 --device /dev/dri/renderD128 \\\n    -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \\\n    -e PULSE_SERVER=$PULSE_SERVER --gpus all videoaccel\n```\n"
  },
  {
    "path": "samples/container/build.sh",
    "content": "#!/bin/bash\n\nsudo docker build -t xclock -f Dockerfile.xclock .\nsudo docker build -t glxinfo -f Dockerfile.glxinfo .\nsudo docker build -t glxgears -f Dockerfile.glxgears .\nsudo docker build -t videoaccel -f Dockerfile.videoaccel ."
  },
  {
    "path": "samples/container/run.sh",
    "content": "#!/bin/bash\n\n# Containerized version of xclock\nsudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \\\n    -e DISPLAY=$DISPLAY -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \\\n    -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR -e PULSE_SERVER=$PULSE_SERVER xclock\n\n# Containerized version of glxinfo\nsudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \\\n    -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \\\n    -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \\\n    -e PULSE_SERVER=$PULSE_SERVER --gpus all glxinfo\n\n# Containerized version of glxgears\nsudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \\\n    -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \\\n    -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \\\n    -e PULSE_SERVER=$PULSE_SERVER --gpus all glxgears\n\n# Containerized version of videoaccel\nsudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \\\n    -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \\\n    --device /dev/dri/card0 --device /dev/dri/renderD128 \\\n    -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \\\n    -e PULSE_SERVER=$PULSE_SERVER --gpus all videoaccel\n"
  },
  {
    "path": "vendor/.preserve",
    "content": ""
  }
]