├── .dockerignore ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Microsoft.WSLg.nuspec ├── Microsoft.WSLg.targets ├── README.md ├── SECURITY.md ├── WSLDVCPlugin ├── RegisterWSLPlugin.reg ├── UpdateRCVersion.ps1 ├── WSLDVCCallback.cpp ├── WSLDVCCallback.h ├── WSLDVCFileDB.cpp ├── WSLDVCFileDB.h ├── WSLDVCListenerCallback.cpp ├── WSLDVCListenerCallback.h ├── WSLDVCPlugin.cpp ├── WSLDVCPlugin.def ├── WSLDVCPlugin.h ├── WSLDVCPlugin.rc ├── WSLDVCPlugin.sln ├── WSLDVCPlugin.vcxproj ├── WSLDVCPlugin.vcxproj.filters ├── WSLDVCPlugin.vcxproj.user ├── cpp.hint ├── dllmain.cpp ├── framework.h ├── pch.cpp ├── pch.h ├── rdpapplist.h ├── resource.h ├── utils.cpp └── utils.h ├── WSLGd ├── FontMonitor.cpp ├── FontMonitor.h ├── Makefile ├── ProcessMonitor.cpp ├── ProcessMonitor.h ├── common.h ├── lxwil.h ├── main.cpp ├── meson.build └── precomp.h ├── azure-pipelines.yml ├── cgmanifest.json ├── config ├── BUILD.md ├── default_wslg.pa ├── local.conf ├── weston.ini ├── wsl.conf └── xwayland_log.patch ├── debuginfo ├── FreeRDP2.list ├── FreeRDP3.list ├── WSLGd.list ├── gen_debuginfo.sh ├── rdpapplist.list └── weston.list ├── devops ├── common-linux.yml ├── common-win.yml ├── getversion.ps1 ├── updateversion.ps1 └── version_functions.ps1 ├── docs ├── WSLg_ArchitectureOverview.png ├── WSLg_IntegratedDesktop.png └── install-sample-gui-apps.sh ├── msi └── updateversion.ps1 ├── package ├── wslg.rdp └── wslg_desktop.rdp ├── rdpapplist ├── meson.build ├── rdpapplist_common.c ├── rdpapplist_common.h ├── rdpapplist_protocol.h ├── rdpapplist_server.h └── server │ ├── meson.build │ ├── rdpapplist_main.c │ └── rdpapplist_main.h ├── resources └── linux.png ├── samples └── container │ ├── Containers.md │ ├── build.sh │ └── run.sh └── vendor └── .preserve /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | .gitlab 4 | .gitlab-ci 5 | .vs 6 | out 7 | tmp 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "Bug report" 2 | description: " Report a bug in WSLg" 3 | labels: "bug" 4 | body: 5 | - type: input 6 | id: build-number 7 | attributes: 8 | label: "Windows build number:" 9 | description: "run `[Environment]::OSVersion` for powershell, or `ver` for cmd" 10 | placeholder: "22000.100" 11 | validations: 12 | required: true 13 | - type: input 14 | id: distribution_version 15 | attributes: 16 | label: "Your Distribution version:" 17 | description: "On Debian or Ubuntu run `lsb_release -r` in WSL" 18 | placeholder: "20.04" 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: wsl-version 23 | attributes: 24 | label: "Your WSL versions:" 25 | description: "run `wsl --version` on Windows command prompt" 26 | validations: 27 | required: true 28 | - type: markdown 29 | attributes: 30 | value: | 31 | **(Optional) Verifiy using the latest release of WSL/WSLg**: 32 | 33 | 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. 34 | - type: textarea 35 | id: reproduce-steps 36 | attributes: 37 | label: "Steps to reproduce:" 38 | placeholder: | 39 | 1. 40 | 2. 41 | 3. 42 | validations: 43 | required: true 44 | - type: textarea 45 | id: logs 46 | attributes: 47 | description: | 48 | Collect WSL logs if needed by following these instructions: https://github.com/Microsoft/WSL/blob/master/CONTRIBUTING.md#8-detailed-logs 49 | 50 | * Attach WSLg logs from `/mnt/wslg` 51 | 52 | You can access the wslg logs using explorer at: `\\wsl$\\mnt\wslg` (e.g.: `\\wsl$\Ubuntu-20.04\mnt\wslg`) 53 | 54 | * `pulseaudio.log` 55 | * `weston.log` 56 | * `stderr.log` 57 | label: "WSL logs:" 58 | placeholder: | 59 | Drag and drop files into this input field to upload them. 60 | validations: 61 | required: false 62 | - type: textarea 63 | id: dumps 64 | attributes: 65 | label: "WSL dumps:" 66 | 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`" 67 | validations: 68 | required: false 69 | - type: textarea 70 | id: expected-behavior 71 | attributes: 72 | label: "Expected behavior:" 73 | description: "A description of what you're expecting, possibly containing screenshots or reference material" 74 | validations: 75 | required: false 76 | - type: textarea 77 | id: actual-behavior 78 | attributes: 79 | label: "Actual behavior:" 80 | description: "What's actually happening?" 81 | validations: 82 | required: true 83 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Report issues on WSL in general 4 | url: https://github.com/microsoft/WSL/issues/new/choose 5 | about: Please report issues not related to WSLg 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature request" 2 | description: "Suggest a feature for WSLg" 3 | labels: "enhancement" 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: "Is your feature request related to a problem:" 8 | description: "A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]" 9 | validations: 10 | required: false 11 | - type: textarea 12 | attributes: 13 | label: "Describe the solution you'd like:" 14 | description: "A clear and concise description of what you want to happen." 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: "Describe alternatives you've considered:" 20 | description: "A clear and concise description of any alternative solutions or features you've considered." 21 | validations: 22 | required: true 23 | - type: textarea 24 | attributes: 25 | label: "Additional context:" 26 | description: "Add any other context or screenshots about the feature request here." 27 | validations: 28 | required: false 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | vendor/* 3 | !vendor/.preserve 4 | out 5 | tmp 6 | *.nupkg 7 | *.tar 8 | *.vhd 9 | WSLGd/*.d 10 | WSLGd/*.o 11 | WSLGd/WSLGd 12 | WSLDVCPlugin/.vs 13 | WSLDVCPlugin/x64 14 | WSLDVCPlugin/ARM64/Debug/ 15 | WSLDVCPlugin/ARM64/Release/ 16 | WSLDVCPlugin/WSLDVCPlugin.aps -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to 4 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 5 | and actually do, grant us the rights to use your contribution. For details, visit 6 | https://cla.microsoft.com. 7 | 8 | When you submit a pull request, a CLA-bot will automatically determine whether you need 9 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 10 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 14 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 15 | 16 | # Building the WSLg System Distro 17 | 18 | The heart of WSLg is what we call the WSL system distro. This is where the Weston compositor, XWayland and the PulseAudio server are running. The system distro runs these components and projects their communication sockets into the user distro. Every user distro is paired with a unique instance of the system distro. There is a single version of the system distro on disk which is instantiated in memory when a user distro is launched. 19 | 20 | The system distro is essentially a Linux container packaged and distributed as a vhd. The system distro is accessible to the user, but is mounted read-only. Any changes made by the user to the system distro while it is running are discarded when WSL is restarted. Although a user can log into the system distro, it is not meant to be used as a general purpose user distro. The reason behind this choice is due to the way we service WSLg. When updating WSLg we simply replace the existing system distro with a new one. If the user had data embedded into the system distro vhd, this data would be lost. 21 | 22 | For folks who want to tinker with or customize their system distro, we give the ability to run a private version of the system distro. When running a private version of WSLg, Windows will load and run your private and ignore the Microsoft published one. If you update your WSL setup (`wsl --update`), the Microsoft published WSLg vhd will be updated, but you will continue to be running your private. You can switch between the Microsoft pulished WSLg system distro and a private one at any time although it does require restarting WSL (`wsl --shutdown`). 23 | 24 | The WSLg system distro is built using docker build. We essentially start from a [CBL-Mariner](https://github.com/microsoft/CBL-MarinerDemo) 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. 25 | 26 | ## Build instructions 27 | 28 | 0. Install and start Docker in a Linux or WSL 2 environment. 29 | 30 | ``` 31 | sudo apt-get update 32 | sudo apt install docker.io golang-go 33 | sudo dockerd 34 | ``` 35 | 36 | 1. Clone the WSLg project: 37 | 38 | ``` 39 | git clone https://github.com/microsoft/wslg wslg 40 | ``` 41 | 42 | 2. Clone the FreeRDP, Weston and PulseAudio mirror. These need to be located in a **vendor** sub-directory where you clone the wslg project (e.g. wslg/vendor), this is where our docker build script expects to find the source code. Make sure to checkout the **working** branch from each of these projects, the **main** branch references the upstream code. 43 | 44 | ```bash 45 | git clone https://github.com/microsoft/FreeRDP-mirror wslg/vendor/FreeRDP -b working 46 | git clone https://github.com/microsoft/weston-mirror wslg/vendor/weston -b working 47 | git clone https://github.com/microsoft/PulseAudio-mirror wslg/vendor/pulseaudio -b working 48 | git clone https://github.com/microsoft/DirectX-Headers.git wslg/vendor/DirectX-Headers-1.0 -b v1.608.0 49 | git clone https://gitlab.freedesktop.org/mesa/mesa.git wslg/vendor/mesa -b mesa-23.1.0 50 | ``` 51 | 52 | 2. Create the VHD: 53 | 54 | 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 55 | ``` 56 | git clone --branch v0.8.9 --single-branch https://github.com/microsoft/hcsshim.git 57 | ``` 58 | 59 | 2.2 From the parent directory build and export the docker image: 60 | ``` 61 | 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 62 | sudo docker export `sudo docker create system-distro-x64` > system_x64.tar 63 | ``` 64 | 65 | 2.3 Create the system distro vhd using `tar2ext4` 66 | 67 | ```bash 68 | cd hcsshim/cmd/tar2ext4 69 | go run tar2ext4.go -vhd -i ../../../system_x64.tar -o ../../../system.vhd 70 | ``` 71 | 72 | This will create system distro image `system.vhd` 73 | 74 | ## Installing a private version of the WSLg system distro 75 | 76 | You can tell WSL to load a private version of WSLg by adding the following option in your `.wslconfig` file (located in `C:\Users\MyUser\.wslconfig`). 77 | 78 | ``` 79 | [wsl2] 80 | systemDistro=C:\\Files\\system.vhd 81 | ``` 82 | 83 | You need to restart WSL for this change to take effect. From an elevated command prompt execute `wsl --shutdown`. When WSL is launched again, Windows will load your private vhd as the system distro. 84 | 85 | ## Inspecting the WSLg system distro at runtime 86 | 87 | If the system distro isn't working correctly or you need to inspect what is running inside the system distro you can get a terminal into the system distro by running the following command from an elevated command prompt. 88 | 89 | ``` 90 | wsl --system -d [DistroName] 91 | ``` 92 | There is an instance of the system distro running for every user distro running. `DistroName` refers to the name of the user distro for which you want the paired system distro. If you omit `DistroName`, you will get a terminal into the system distro paired with your default WSL user distro. 93 | 94 | Please keep in mind that the system distro is loaded read-only from it's backing VHD. For example, if you need to install tools (say a debugger or an editor) in the system distro, you want to do this in the Dockerfile that builds the system distro so it gets into the private vhd that you are running. You can dynamically install new packages once your have a terminal into the system distro, but any changes you make will be discarded when WSL is restarted. 95 | 96 | ## Building a debug version 97 | 98 | To build a debug version of the system distro, the docker build argument SYSTEMDISTRO_DEBUG_BUILD needs to be set and passed the value of "true". The following command would substitute the docker build command in step 3.2.2 of the "Build Instructions" section. 99 | 100 | ``` 101 | 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 102 | ``` 103 | The resulting system distro VHD will have useful development packages installed like gdb and will have compiled all runtime dependencies with the "debug" buildtype for Meson, rather than "release". 104 | 105 | # mstsc plugin 106 | 107 | On the Windows side of the world, WSLg leverages the native `mstsc.exe` RDP client and a plugin for that client which handles WSLg integration into the start menu. The source code for this plugin is available as open source as part of the WSLg repo [here](https://github.com/microsoft/wslg/tree/main/WSLDVCPlugin). 108 | 109 | It was important for us building WSLg to ensure that all protocols between Linux and Windows be fully documented and available to everyone to reuse. While almost all of the communication over RDP between Linux/Weston and Windows goes through standard and officially documented [Windows Protocols](https://docs.microsoft.com/en-us/openspecs/windows_protocols/MS-WINPROTLP/92b33e19-6fff-496b-86c3-d168206f9845) associated with the RDP standard, we needed just a bit of custom communication between Linux and Windows to handle integration into the start menu. We thought about adding some official RDP protocol for this, but this was too specific to WSLg and not broadly applicable to arbitrary RDP based solution. 110 | 111 | So instead we opted to use a custom RDP channel between the WSLg RDP Server running inside of Weston and the WSLg RDP plugin hosted by mstsc. Such custom dynamic channel are part of the RDP specification, but requires that both the RDP server and RDP client support that channel for it to be used. This is the path we took for WSLg where Weston exposes a custom RDP channel for WSLg integration. In the spirit of fully documenting all channels of communication between Linux and Windows, we're making the source code for plugin which handles the Windows side of this custom RDP channel available as part of the WSLg project. 112 | 113 | This custom channel and associated plugin are quite small and simple. In a nutshell, Weston enumerates all installed applications inside of the user Linux distro (i.e. application which have an explicit [desktop file](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html)) and exposes this list of applications, along with command line to launch them and icon to represent them, over this custom RDP channel. The mstsc plugin processes that list and creates links in the Windows Start Menu for these applications so they can be launch directly from it. 114 | 115 | ## Building the mstsc plugin 116 | 117 | The [source code](https://github.com/microsoft/wslg/tree/main/WSLDVCPlugin) for the plugin has a visual studio project file that can be use to build it. You can download and install the free [Visual Studio Community Edition](https://visualstudio.microsoft.com/vs/community/) to build it. 118 | 119 | ## Registering a private mstsc plugin 120 | 121 | The plugin is registered with mstsc through the registry. By default this is set to load the plugin that ships as part of the official WSLg package. If you need to run a private, you'll nee to modify this registry key to reference your privately built plugin, for example using a registry file like below. 122 | 123 | ``` 124 | Windows Registry Editor Version 5.00 125 | 126 | [HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default\AddIns\WSLDVCPlugin] 127 | "Name"="C:\\users\\MyUser\\Privates\\WSLDVCPlugin.dll" 128 | ``` 129 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Create a builder image with the compilers, etc. needed 2 | FROM mcr.microsoft.com/cbl-mariner/base/core:2.0.20250207 AS build-env 3 | 4 | # Install all the required packages for building. This list is probably 5 | # longer than necessary. 6 | RUN echo "== Install Git/CA certificates ==" && \ 7 | tdnf install -y \ 8 | git \ 9 | ca-certificates 10 | 11 | RUN echo "== Install Core dependencies ==" && \ 12 | tdnf install -y \ 13 | alsa-lib \ 14 | alsa-lib-devel \ 15 | autoconf \ 16 | automake \ 17 | binutils \ 18 | bison \ 19 | build-essential \ 20 | cairo \ 21 | cairo-devel \ 22 | clang \ 23 | clang-devel \ 24 | cmake \ 25 | dbus \ 26 | dbus-devel \ 27 | dbus-glib \ 28 | dbus-glib-devel \ 29 | diffutils \ 30 | elfutils-devel \ 31 | file-libs \ 32 | flex \ 33 | fontconfig-devel \ 34 | gawk \ 35 | gcc \ 36 | gettext \ 37 | glibc-devel \ 38 | glib-schemas \ 39 | gobject-introspection \ 40 | gobject-introspection-devel \ 41 | harfbuzz \ 42 | harfbuzz-devel \ 43 | kernel-headers \ 44 | intltool \ 45 | libatomic_ops \ 46 | libcap-devel \ 47 | libffi \ 48 | libffi-devel \ 49 | libgudev \ 50 | libgudev-devel \ 51 | libjpeg-turbo \ 52 | libjpeg-turbo-devel \ 53 | libltdl \ 54 | libltdl-devel \ 55 | libpng-devel \ 56 | librsvg2-devel \ 57 | libtiff \ 58 | libtiff-devel \ 59 | libusb \ 60 | libusb-devel \ 61 | libwebp \ 62 | libwebp-devel \ 63 | libxml2 \ 64 | libxml2-devel \ 65 | make \ 66 | meson \ 67 | newt \ 68 | nss \ 69 | nss-libs \ 70 | openldap \ 71 | openssl-devel \ 72 | pam-devel \ 73 | pango \ 74 | pango-devel \ 75 | patch \ 76 | perl-XML-Parser \ 77 | polkit-devel \ 78 | python3-devel \ 79 | python3-mako \ 80 | python3-markupsafe \ 81 | sed \ 82 | sqlite-devel \ 83 | systemd-devel \ 84 | tar \ 85 | unzip \ 86 | vala \ 87 | vala-devel \ 88 | vala-tools 89 | 90 | RUN echo "== Install UI dependencies ==" && \ 91 | tdnf install -y \ 92 | libdrm-devel \ 93 | libepoxy-devel \ 94 | libevdev \ 95 | libevdev-devel \ 96 | libinput \ 97 | libinput-devel \ 98 | libpciaccess-devel \ 99 | libSM-devel \ 100 | libsndfile \ 101 | libsndfile-devel \ 102 | libXcursor \ 103 | libXcursor-devel \ 104 | libXdamage-devel \ 105 | libXfont2-devel \ 106 | libXi \ 107 | libXi-devel \ 108 | libxkbcommon-devel \ 109 | libxkbfile-devel \ 110 | libXrandr-devel \ 111 | libxshmfence-devel \ 112 | libXtst \ 113 | libXtst-devel \ 114 | libXxf86vm-devel \ 115 | wayland-devel \ 116 | wayland-protocols-devel \ 117 | xkbcomp \ 118 | xkeyboard-config \ 119 | xorg-x11-server-devel \ 120 | xorg-x11-util-macros 121 | 122 | # Create an image with builds of FreeRDP and Weston 123 | FROM build-env AS dev 124 | 125 | ARG WSLG_VERSION="" 126 | ARG WSLG_ARCH="x86_64" 127 | ARG SYSTEMDISTRO_DEBUG_BUILD 128 | ARG FREERDP_VERSION=2 129 | 130 | WORKDIR /work 131 | RUN echo "WSLg (" ${WSLG_ARCH} "):" ${WSLG_VERSION} > /work/versions.txt 132 | RUN echo "Built at:" `date --utc` >> /work/versions.txt 133 | 134 | RUN echo "Mariner:" `cat /etc/os-release | head -2 | tail -1` >> /work/versions.txt 135 | 136 | # 137 | # Build runtime dependencies. 138 | # 139 | 140 | ENV BUILDTYPE=${SYSTEMDISTRO_DEBUG_BUILD:+debug} 141 | ENV BUILDTYPE=${BUILDTYPE:-debugoptimized} 142 | RUN echo "== System distro build type:" ${BUILDTYPE} " ==" 143 | 144 | ENV BUILDTYPE_NODEBUGSTRIP=${SYSTEMDISTRO_DEBUG_BUILD:+debug} 145 | ENV BUILDTYPE_NODEBUGSTRIP=${BUILDTYPE_NODEBUGSTRIP:-release} 146 | RUN echo "== System distro build type (no debug strip):" ${BUILDTYPE_NODEBUGSTRIP} " ==" 147 | 148 | # FreeRDP is always built with RelWithDebInfo 149 | ENV BUILDTYPE_FREERDP=${BUILDTYPE_FREERDP:-RelWithDebInfo} 150 | RUN echo "== System distro build type (FreeRDP):" ${BUILDTYPE_FREERDP} " ==" 151 | 152 | ENV WITH_DEBUG_FREERDP=${SYSTEMDISTRO_DEBUG_BUILD:+ON} 153 | ENV WITH_DEBUG_FREERDP=${WITH_DEBUG_FREERDP:-OFF} 154 | RUN echo "== System distro build type (FreeRDP Debug Options):" ${WITH_DEBUG_FREERDP} " ==" 155 | 156 | ENV DESTDIR=/work/build 157 | ENV PREFIX=/usr 158 | ENV PKG_CONFIG_PATH=${DESTDIR}${PREFIX}/lib/pkgconfig:${DESTDIR}${PREFIX}/lib/${WSLG_ARCH}-linux-gnu/pkgconfig:${DESTDIR}${PREFIX}/share/pkgconfig 159 | ENV C_INCLUDE_PATH=${DESTDIR}${PREFIX}/include/freerdp${FREERDP_VERSION}:${DESTDIR}${PREFIX}/include/winpr${FREERDP_VERSION}:${DESTDIR}${PREFIX}/include/wsl/stubs:${DESTDIR}${PREFIX}/include 160 | ENV CPLUS_INCLUDE_PATH=${C_INCLUDE_PATH} 161 | ENV LIBRARY_PATH=${DESTDIR}${PREFIX}/lib 162 | ENV LD_LIBRARY_PATH=${LIBRARY_PATH} 163 | ENV CC=/usr/bin/gcc 164 | ENV CXX=/usr/bin/g++ 165 | 166 | # Setup DebugInfo folder 167 | COPY debuginfo /work/debuginfo 168 | RUN chmod +x /work/debuginfo/gen_debuginfo.sh 169 | 170 | # Build DirectX-Headers 171 | COPY vendor/DirectX-Headers-1.0 /work/vendor/DirectX-Headers-1.0 172 | WORKDIR /work/vendor/DirectX-Headers-1.0 173 | RUN /usr/bin/meson --prefix=${PREFIX} build \ 174 | --buildtype=${BUILDTYPE_NODEBUGSTRIP} \ 175 | -Dbuild-test=false && \ 176 | ninja -C build -j8 install && \ 177 | echo 'DirectX-Headers:' `git --git-dir=/work/vendor/DirectX-Headers-1.0/.git rev-parse --verify HEAD` >> /work/versions.txt 178 | 179 | # Build mesa with the minimal options we need. 180 | COPY vendor/mesa /work/vendor/mesa 181 | WORKDIR /work/vendor/mesa 182 | RUN /usr/bin/meson --prefix=${PREFIX} build \ 183 | --buildtype=${BUILDTYPE_NODEBUGSTRIP} \ 184 | -Dgallium-drivers=swrast,d3d12 \ 185 | -Dvulkan-drivers= \ 186 | -Dllvm=disabled && \ 187 | ninja -C build -j8 install && \ 188 | echo 'mesa:' `git --git-dir=/work/vendor/mesa/.git rev-parse --verify HEAD` >> /work/versions.txt 189 | 190 | # Build PulseAudio 191 | COPY vendor/pulseaudio /work/vendor/pulseaudio 192 | WORKDIR /work/vendor/pulseaudio 193 | RUN /usr/bin/meson --prefix=${PREFIX} build \ 194 | --buildtype=${BUILDTYPE_NODEBUGSTRIP} \ 195 | -Ddatabase=simple \ 196 | -Ddoxygen=false \ 197 | -Dgsettings=disabled \ 198 | -Dtests=false && \ 199 | ninja -C build -j8 install && \ 200 | echo 'pulseaudio:' `git --git-dir=/work/vendor/pulseaudio/.git rev-parse --verify HEAD` >> /work/versions.txt 201 | 202 | # Build FreeRDP 203 | COPY vendor/FreeRDP /work/vendor/FreeRDP 204 | WORKDIR /work/vendor/FreeRDP 205 | RUN cmake -G Ninja \ 206 | -B build \ 207 | -DCMAKE_INSTALL_PREFIX=${PREFIX} \ 208 | -DCMAKE_INSTALL_LIBDIR=${PREFIX}/lib \ 209 | -DCMAKE_BUILD_TYPE=${BUILDTYPE_FREERDP} \ 210 | -DWITH_DEBUG_ALL=${WITH_DEBUG_FREERDP} \ 211 | -DWITH_ICU=ON \ 212 | -DWITH_SERVER=ON \ 213 | -DWITH_CHANNEL_GFXREDIR=ON \ 214 | -DWITH_CHANNEL_RDPAPPLIST=ON \ 215 | -DWITH_CLIENT=OFF \ 216 | -DWITH_CLIENT_COMMON=OFF \ 217 | -DWITH_CLIENT_CHANNELS=OFF \ 218 | -DWITH_CLIENT_INTERFACE=OFF \ 219 | -DWITH_PROXY=OFF \ 220 | -DWITH_SHADOW=OFF \ 221 | -DWITH_SAMPLE=OFF && \ 222 | ninja -C build -j8 install && \ 223 | echo 'FreeRDP:' `git --git-dir=/work/vendor/FreeRDP/.git rev-parse --verify HEAD` >> /work/versions.txt 224 | 225 | WORKDIR /work/debuginfo 226 | RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ 227 | echo "== Strip debug info: FreeRDP ==" && \ 228 | /work/debuginfo/gen_debuginfo.sh /work/debuginfo/FreeRDP${FREERDP_VERSION}.list /work/build; \ 229 | fi 230 | 231 | # Build rdpapplist RDP virtual channel plugin 232 | COPY rdpapplist /work/rdpapplist 233 | WORKDIR /work/rdpapplist 234 | RUN /usr/bin/meson --prefix=${PREFIX} build \ 235 | --buildtype=${BUILDTYPE} && \ 236 | ninja -C build -j8 install 237 | 238 | WORKDIR /work/debuginfo 239 | RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ 240 | echo "== Strip debug info: rdpapplist ==" && \ 241 | /work/debuginfo/gen_debuginfo.sh /work/debuginfo/rdpapplist.list /work/build; \ 242 | fi 243 | 244 | # Build Weston 245 | COPY vendor/weston /work/vendor/weston 246 | WORKDIR /work/vendor/weston 247 | RUN /usr/bin/meson --prefix=${PREFIX} build \ 248 | --buildtype=${BUILDTYPE} \ 249 | -Dbackend-default=rdp \ 250 | -Dbackend-drm=false \ 251 | -Dbackend-drm-screencast-vaapi=false \ 252 | -Dbackend-headless=false \ 253 | -Dbackend-wayland=false \ 254 | -Dbackend-x11=false \ 255 | -Dbackend-fbdev=false \ 256 | -Dcolor-management-colord=false \ 257 | -Dscreenshare=false \ 258 | -Dsystemd=false \ 259 | -Dwslgd=true \ 260 | -Dremoting=false \ 261 | -Dpipewire=false \ 262 | -Dshell-fullscreen=false \ 263 | -Dcolor-management-lcms=false \ 264 | -Dshell-ivi=false \ 265 | -Dshell-kiosk=false \ 266 | -Ddemo-clients=false \ 267 | -Dsimple-clients=[] \ 268 | -Dtools=[] \ 269 | -Dresize-pool=false \ 270 | -Dwcap-decode=false \ 271 | -Dtest-junit-xml=false && \ 272 | ninja -C build -j8 install && \ 273 | echo 'weston:' `git --git-dir=/work/vendor/weston/.git rev-parse --verify HEAD` >> /work/versions.txt 274 | 275 | WORKDIR /work/debuginfo 276 | RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ 277 | echo "== Strip debug info: weston ==" && \ 278 | /work/debuginfo/gen_debuginfo.sh /work/debuginfo/weston.list /work/build; \ 279 | fi 280 | 281 | # Build WSLGd Daemon 282 | ENV CC=/usr/bin/clang 283 | ENV CXX=/usr/bin/clang++ 284 | 285 | COPY WSLGd /work/WSLGd 286 | WORKDIR /work/WSLGd 287 | RUN /usr/bin/meson --prefix=${PREFIX} build \ 288 | --buildtype=${BUILDTYPE} && \ 289 | ninja -C build -j8 install 290 | 291 | WORKDIR /work/debuginfo 292 | RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ 293 | echo "== Strip debug info: WSLGd ==" && \ 294 | /work/debuginfo/gen_debuginfo.sh /work/debuginfo/WSLGd.list /work/build; \ 295 | fi 296 | 297 | # Gather debuginfo to a tar file 298 | WORKDIR /work/debuginfo 299 | RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ 300 | echo "== Compress debug info: /work/debuginfo/system-debuginfo.tar.gz ==" && \ 301 | tar -C /work/build/debuginfo -czf system-debuginfo.tar.gz ./ ; \ 302 | fi 303 | 304 | ######################################################################## 305 | ######################################################################## 306 | 307 | ## Create the distro image with just what's needed at runtime 308 | 309 | FROM mcr.microsoft.com/cbl-mariner/base/core:2.0.20250207 AS runtime 310 | 311 | RUN echo "== Install Core/UI Runtime Dependencies ==" && \ 312 | tdnf install -y \ 313 | cairo \ 314 | chrony \ 315 | dbus \ 316 | dbus-glib \ 317 | dhcp-client \ 318 | e2fsprogs \ 319 | freefont \ 320 | libinput \ 321 | libjpeg-turbo \ 322 | libltdl \ 323 | libpng \ 324 | librsvg2 \ 325 | libsndfile \ 326 | libwayland-client \ 327 | libwayland-server \ 328 | libwayland-cursor \ 329 | libwebp \ 330 | libXcursor \ 331 | libxkbcommon \ 332 | libXrandr \ 333 | iproute \ 334 | nftables \ 335 | pango \ 336 | procps-ng \ 337 | rpm \ 338 | sed \ 339 | tzdata \ 340 | wayland-protocols-devel \ 341 | xcursor-themes \ 342 | xorg-x11-server-Xwayland \ 343 | xorg-x11-server-utils \ 344 | xorg-x11-xtrans-devel 345 | 346 | # Install packages to aid in development, if not remove some packages. 347 | ARG SYSTEMDISTRO_DEBUG_BUILD 348 | RUN if [ -z "$SYSTEMDISTRO_DEBUG_BUILD" ] ; then \ 349 | rpm -e --nodeps curl \ 350 | rpm -e --nodeps python3 \ 351 | rpm -e --nodeps python3-libs; \ 352 | else \ 353 | echo "== Install development aid packages ==" && \ 354 | tdnf install -y \ 355 | gdb \ 356 | mariner-repos-debug \ 357 | nano \ 358 | vim && \ 359 | tdnf install -y \ 360 | wayland-debuginfo \ 361 | xorg-x11-server-debuginfo; \ 362 | fi 363 | 364 | # Clear the tdnf cache to make the image smaller 365 | RUN tdnf clean all 366 | 367 | # Remove extra doc 368 | RUN rm -rf /usr/lib/python3.7 /usr/share/gtk-doc 369 | 370 | # Create wslg user. 371 | RUN useradd -u 1000 --create-home wslg && \ 372 | mkdir /home/wslg/.config && \ 373 | chown wslg /home/wslg/.config 374 | 375 | # Copy config files. 376 | COPY config/wsl.conf /etc/wsl.conf 377 | COPY config/weston.ini /home/wslg/.config/weston.ini 378 | COPY config/local.conf /etc/fonts/local.conf 379 | 380 | # Copy default icon file. 381 | COPY resources/linux.png /usr/share/icons/wsl/linux.png 382 | 383 | # Copy the built artifacts from the build stage. 384 | COPY --from=dev /work/build/usr/ /usr/ 385 | COPY --from=dev /work/build/etc/ /etc/ 386 | 387 | # Append WSLg setttings to pulseaudio. 388 | COPY config/default_wslg.pa /etc/pulse/default_wslg.pa 389 | RUN cat /etc/pulse/default_wslg.pa >> /etc/pulse/default.pa 390 | RUN rm /etc/pulse/default_wslg.pa 391 | 392 | # Copy the licensing information for PulseAudio 393 | COPY --from=dev /work/vendor/pulseaudio/GPL \ 394 | /work/vendor/pulseaudio/LGPL \ 395 | /work/vendor/pulseaudio/LICENSE \ 396 | /work/vendor/pulseaudio/NEWS \ 397 | /work/vendor/pulseaudio/README /usr/share/doc/pulseaudio/ 398 | 399 | # Copy the licensing information for Weston 400 | COPY --from=dev /work/vendor/weston/COPYING /usr/share/doc/weston/COPYING 401 | 402 | # Copy the licensing information for FreeRDP 403 | COPY --from=dev /work/vendor/FreeRDP/LICENSE /usr/share/doc/FreeRDP/LICENSE 404 | 405 | # copy the documentation and licensing information for mesa 406 | COPY --from=dev /work/vendor/mesa/docs /usr/share/doc/mesa/ 407 | 408 | COPY --from=dev /work/versions.txt /etc/versions.txt 409 | 410 | CMD /usr/bin/WSLGd 411 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /Microsoft.WSLg.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | Microsoft.WSLg 10 | 0.2.12 11 | Microsoft 12 | Microsoft, WSL Team 13 | https://github.com/microsoft/wslg/ 14 | false 15 | WSLg support package. 16 | Enabling the Windows Subsystem for Linux to include support for Wayland and X server related scenarios. 17 | ebad9c1 18 | Copyright © Microsoft Corporation 2021 19 | native 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Microsoft.WSLg.targets: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | system.vhd 11 | PreserveNewest 12 | 13 | 14 | WSLDVCPlugin.dll 15 | PreserveNewest 16 | 17 | 18 | wslg.rdp 19 | PreserveNewest 20 | 21 | 22 | wslg_desktop.rdp 23 | PreserveNewest 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 4 | 5 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 6 | 7 | ## Reporting Security Issues 8 | 9 | **Please do not report security vulnerabilities through public GitHub issues.** 10 | 11 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 12 | 13 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 14 | 15 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 16 | 17 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 18 | 19 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 20 | * Full paths of source file(s) related to the manifestation of the issue 21 | * The location of the affected source code (tag/branch/commit or direct URL) 22 | * Any special configuration required to reproduce the issue 23 | * Step-by-step instructions to reproduce the issue 24 | * Proof-of-concept or exploit code (if possible) 25 | * Impact of the issue, including how an attacker might exploit the issue 26 | 27 | This information will help us triage your report more quickly. 28 | 29 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 30 | 31 | ## Preferred Languages 32 | 33 | We prefer all communications to be in English. 34 | 35 | ## Policy 36 | 37 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). -------------------------------------------------------------------------------- /WSLDVCPlugin/RegisterWSLPlugin.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default\AddIns\WSLDVCPlugin] 4 | "Name"="C:\\WINDOWS\\system32\\WSLDVCPlugin.dll" 5 | -------------------------------------------------------------------------------- /WSLDVCPlugin/UpdateRCVersion.ps1: -------------------------------------------------------------------------------- 1 | 2 | $version = [string](gitversion /showvariable AssemblySemFileVer) 3 | $versionComma = $version.Replace(".", ",") 4 | $informationalVersion = [string](gitversion /showvariable InformationalVersion) 5 | 6 | $content = (Get-Content -Encoding "windows-1252" -Path ".\WSLDVCPlugin.rc") 7 | $content = $content.Replace("1,0,0,1", $versionComma); 8 | $content = $content.Replace("1.0.0.1", $version); 9 | $content = $content.Replace("InformationalVersion", $InformationalVersion); 10 | 11 | Set-Content -Encoding "windows-1252" -Path ".\WSLDVCPlugin.rc" -Value $content -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCCallback.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | #include 6 | 7 | HRESULT 8 | WSLDVCCallback_CreateInstance( 9 | IWTSVirtualChannel* pChannel, 10 | IWTSVirtualChannelCallback** ppCallback 11 | ); -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCFileDB.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include "pch.h" 4 | #include "utils.h" 5 | #include "WSLDVCFileDB.h" 6 | 7 | #include 8 | #include 9 | 10 | class fileEntry 11 | { 12 | public: 13 | 14 | wstring getFileId() const 15 | { 16 | return m_fileId; 17 | } 18 | wstring getLinkFilePath() const 19 | { 20 | return m_linkFilePath; 21 | } 22 | wstring getIconFilePath() const 23 | { 24 | return m_iconFilePath; 25 | } 26 | wstring getExeFilePath() const 27 | { 28 | return m_exeFilePath; 29 | } 30 | wstring getExeFileArgs() const 31 | { 32 | return m_exeFileArgs; 33 | } 34 | 35 | //fileEntry(const fileEntry& r) 36 | //{ 37 | // m_fileId = r.m_fileId; 38 | // m_linkFilePath = r.m_linkFilePath; 39 | // m_iconFilePath = r.m_iconFilePath; 40 | // m_exeFilePath = r.m_exeFilePath; 41 | // m_exeFileArgs = r.m_exeFileArgs; 42 | //} 43 | fileEntry(const wchar_t* fileId, 44 | const wchar_t* linkPath = L"", 45 | const wchar_t* iconPath = L"", 46 | const wchar_t* exePath = L"", 47 | const wchar_t* exeArgs = L"") 48 | { 49 | assert(fileId); 50 | m_fileId = fileId; 51 | m_linkFilePath = linkPath; 52 | m_iconFilePath = iconPath; 53 | m_exeFilePath = exePath; 54 | m_exeFileArgs = exeArgs; 55 | } 56 | 57 | bool operator< (LPWSTR key) const 58 | { 59 | wstring r = key; 60 | return getFileId() < r; 61 | } 62 | bool operator< (const fileEntry& r) const 63 | { 64 | return getFileId() < r.getFileId(); 65 | } 66 | 67 | private: 68 | 69 | wstring m_fileId; 70 | wstring m_linkFilePath; 71 | wstring m_iconFilePath; 72 | wstring m_exeFilePath; 73 | wstring m_exeFileArgs; 74 | }; 75 | 76 | class WSLDVCFileDB : 77 | public RuntimeClass< 78 | RuntimeClassFlags, 79 | IWSLDVCFileDB> 80 | { 81 | public: 82 | 83 | HRESULT 84 | RuntimeClassInitialize( 85 | void* pContext 86 | ) 87 | { 88 | UNREFERENCED_PARAMETER(pContext); 89 | DebugPrint(L"RuntimeClassInitialize(): WSLDVCFileDB iniitalized: %p\n", this); 90 | return S_OK; 91 | } 92 | 93 | STDMETHODIMP 94 | OnLnkFileAdded(_In_z_ LPCWSTR lnkFile) 95 | { 96 | WCHAR iconFile[MAX_PATH] = {}; 97 | 98 | { 99 | fileEntry fileEntry(lnkFile); 100 | m_fileEntries.insert(fileEntry); 101 | } 102 | 103 | if (SUCCEEDED(GetIconFileFromShellLink(ARRAYSIZE(iconFile), iconFile, lnkFile))) 104 | { 105 | fileEntry fileEntry(iconFile); 106 | m_fileEntries.insert(fileEntry); 107 | } 108 | 109 | DebugPrint(L"OnLnkFileAdded():\n"); 110 | DebugPrint(L"\tlnkFile: %s\n", lnkFile); 111 | DebugPrint(L"\ticonFile: %s\n", iconFile); 112 | 113 | return S_OK; 114 | } 115 | 116 | STDMETHODIMP 117 | OnFileAdded(_In_z_ LPCWSTR key, 118 | _In_opt_z_ LPCWSTR linkFilePath, 119 | _In_opt_z_ LPCWSTR iconFilePath, 120 | _In_opt_z_ LPCWSTR exeFilePath, 121 | _In_opt_z_ LPCWSTR exeFileArgs) 122 | { 123 | HRESULT hr = S_OK; 124 | set::iterator it; 125 | std::pair::iterator, bool> p; 126 | 127 | DebugPrint(L"OnFileAdded():\n"); 128 | DebugPrint(L"\tkey: %s\n", key); 129 | if (linkFilePath && lstrlenW(linkFilePath)) 130 | { 131 | DebugPrint(L"\tlinkFilePath: %s\n", linkFilePath); 132 | } 133 | else 134 | { 135 | linkFilePath = L""; 136 | } 137 | if (iconFilePath && lstrlenW(iconFilePath)) 138 | { 139 | DebugPrint(L"\ticonFilePath: %s\n", iconFilePath); 140 | } 141 | else 142 | { 143 | iconFilePath = L""; 144 | } 145 | if (exeFilePath && lstrlenW(exeFilePath)) 146 | { 147 | DebugPrint(L"\texeFilePath: %s\n", exeFilePath); 148 | } 149 | else 150 | { 151 | exeFilePath = L""; 152 | } 153 | if (exeFileArgs && lstrlenW(exeFileArgs)) 154 | { 155 | DebugPrint(L"\texeFileArgs: %s\n", exeFileArgs); 156 | } 157 | else 158 | { 159 | exeFileArgs = L""; 160 | } 161 | 162 | fileEntry fileEntry(key, linkFilePath, iconFilePath, exeFilePath, exeFileArgs); 163 | p = m_fileEntries.insert(fileEntry); 164 | if (!p.second) 165 | { 166 | if (p.first->getLinkFilePath().compare(linkFilePath) != 0 || 167 | p.first->getIconFilePath().compare(iconFilePath) != 0 || 168 | p.first->getExeFilePath().compare(exeFilePath) != 0 || 169 | p.first->getExeFileArgs().compare(exeFileArgs) != 0) 170 | { 171 | DebugPrint(L"\tKey: %s is already exists and has different path data\n", key); 172 | DebugPrint(L"\tlinkFilePath: %s\n", p.first->getLinkFilePath().c_str()); 173 | DebugPrint(L"\ticonFilePath: %s\n", p.first->getIconFilePath().c_str()); 174 | DebugPrint(L"\texeFilePath: %s\n", p.first->getExeFilePath().c_str()); 175 | DebugPrint(L"\texeFileArgs: %s\n", p.first->getExeFileArgs().c_str()); 176 | // TODO: implement update existing by erase and add. 177 | // DebugAssert(false); 178 | hr = E_FAIL; 179 | } 180 | } 181 | 182 | return hr; 183 | } 184 | 185 | STDMETHODIMP 186 | OnFileRemoved(_In_z_ LPCWSTR key) 187 | { 188 | HRESULT hr = S_OK; 189 | set::iterator it; 190 | 191 | DebugPrint(L"OnFileRemoved()\n"); 192 | DebugPrint(L"\tkey: %s\n", key); 193 | 194 | it = m_fileEntries.find(key); 195 | if (it != m_fileEntries.end()) 196 | { 197 | DebugPrint(L"\tKey found:\n"); 198 | if (it->getLinkFilePath().length()) 199 | { 200 | DebugPrint(L"\tlinkPath: %s\n", it->getLinkFilePath().c_str()); 201 | } 202 | if (it->getIconFilePath().length()) 203 | { 204 | DebugPrint(L"\ticonPath: %s\n", it->getIconFilePath().c_str()); 205 | } 206 | if (it->getExeFilePath().length()) 207 | { 208 | DebugPrint(L"\texePath: %s\n", it->getExeFilePath().c_str()); 209 | } 210 | if (it->getExeFileArgs().length()) 211 | { 212 | DebugPrint(L"\texeArgs: %s\n", it->getExeFileArgs().c_str()); 213 | } 214 | m_fileEntries.erase(it); 215 | } 216 | else 217 | { 218 | DebugPrint(L"Key not found\n"); 219 | hr = E_FAIL; 220 | } 221 | 222 | return hr; 223 | } 224 | 225 | STDMETHODIMP FindFiles( 226 | _In_z_ LPCWSTR key, 227 | _Out_writes_z_(linkFilePathSize) LPWSTR linkFilePath, UINT32 linkFilePathSize, 228 | _Out_writes_z_(iconFilePathSize) LPWSTR iconFilePath, UINT32 iconFilePathSize, 229 | _Out_writes_z_(exeFilePathSize) LPWSTR exeFilePath, UINT32 exeFilePathSize, 230 | _Out_writes_z_(exeFileArgsSize) LPWSTR exeFileArgs, UINT32 exeFileArgsSize) 231 | { 232 | set::iterator it; 233 | 234 | assert((linkFilePath && linkFilePathSize) || 235 | (linkFilePath == nullptr && linkFilePathSize == 0)); 236 | assert((iconFilePath && iconFilePathSize) || 237 | (iconFilePath == nullptr && iconFilePathSize == 0)); 238 | assert((exeFilePath && exeFilePathSize) || 239 | (exeFilePath == nullptr && exeFilePathSize == 0)); 240 | assert((exeFileArgs && exeFileArgsSize) || 241 | (exeFileArgs == nullptr && exeFileArgsSize == 0)); 242 | 243 | if (linkFilePath) *linkFilePath = NULL; 244 | if (iconFilePath) *iconFilePath = NULL; 245 | if (exeFilePath) *exeFilePath = NULL; 246 | if (exeFileArgs) *exeFileArgs = NULL; 247 | 248 | DebugPrint(L"FindFiles()\n"); 249 | DebugPrint(L"\tkey: %s\n", key); 250 | 251 | it = m_fileEntries.find(key); 252 | if (it != m_fileEntries.end()) 253 | { 254 | DebugPrint(L"\tKey found:\n"); 255 | DebugPrint(L"\tlinkPath: %s\n", it->getLinkFilePath().c_str()); 256 | DebugPrint(L"\ticonPath: %s\n", it->getIconFilePath().c_str()); 257 | DebugPrint(L"\texePath: %s\n", it->getExeFilePath().c_str()); 258 | DebugPrint(L"\texeArgs: %s\n", it->getExeFileArgs().c_str()); 259 | if (linkFilePath) 260 | { 261 | if (wcscpy_s(linkFilePath, linkFilePathSize, it->getLinkFilePath().c_str()) != 0) 262 | { 263 | return E_FAIL; 264 | } 265 | } 266 | if (iconFilePath) 267 | { 268 | if (wcscpy_s(iconFilePath, iconFilePathSize, it->getIconFilePath().c_str()) != 0) 269 | { 270 | return E_FAIL; 271 | } 272 | } 273 | if (exeFilePath) 274 | { 275 | if (wcscpy_s(exeFilePath, exeFilePathSize, it->getExeFilePath().c_str()) != 0) 276 | { 277 | return E_FAIL; 278 | } 279 | } 280 | if (exeFileArgs) 281 | { 282 | if (wcscpy_s(exeFileArgs, exeFileArgsSize, it->getExeFileArgs().c_str()) != 0) 283 | { 284 | return E_FAIL; 285 | } 286 | } 287 | return S_OK; 288 | } 289 | else 290 | { 291 | DebugPrint(L"\tKey NOT found:\n"); 292 | return E_FAIL; 293 | } 294 | } 295 | 296 | STDMETHODIMP 297 | addAllFilesAsFileIdAt(_In_z_ LPCWSTR path) 298 | { 299 | //DebugPrint(L"Dump directory: %s\n", path); 300 | return addAllSubFolderFiles(path); 301 | } 302 | 303 | STDMETHODIMP 304 | deleteAllFileIdFiles() 305 | { 306 | set::iterator it; 307 | 308 | DebugPrint(L"deleteAllFileIdFiles() - files to delete %d\n", m_fileEntries.size()); 309 | 310 | for (it = m_fileEntries.begin(); it != m_fileEntries.end(); ) { 311 | DebugPrint(L"\tDelete %s\n", it->getFileId().c_str()); 312 | if (!DeleteFileW(it->getFileId().c_str())) 313 | { 314 | DebugPrint(L"DeleteFile(%s) failed\n", it->getFileId().c_str()); 315 | } 316 | else 317 | { 318 | wstring::size_type found = it->getFileId().find_last_of(L"\\"); 319 | if (found != wstring::npos) 320 | { 321 | wstring dir = it->getFileId().substr(0, found); 322 | if (PathIsDirectoryEmptyW(dir.c_str())) 323 | { 324 | DebugPrint(L"%s is empty, removing\n", dir.c_str()); 325 | if (!RemoveDirectory(dir.c_str())) 326 | { 327 | DebugPrint(L"Failed to remove %s\n", dir.c_str()); 328 | } 329 | } 330 | } 331 | } 332 | it = m_fileEntries.erase(it); 333 | } 334 | 335 | return S_OK; 336 | } 337 | 338 | STDMETHODIMP 339 | OnClose() 340 | { 341 | DebugPrint(L"OnClose(): WSLDVCFileDB closed: %p\n", this); 342 | 343 | m_fileEntries.clear(); 344 | 345 | return S_OK; 346 | } 347 | 348 | protected: 349 | 350 | HRESULT 351 | addAllSubFolderFiles(_In_z_ const wchar_t* path) 352 | { 353 | WIN32_FIND_DATA data = {}; 354 | wstring s = path; 355 | s += L"\\*"; 356 | 357 | HANDLE hFind = FindFirstFile(s.c_str(), &data); 358 | do { 359 | wstring sub = path; 360 | sub += L"\\"; 361 | sub += data.cFileName; 362 | 363 | if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 364 | wstring file = data.cFileName; 365 | 366 | if (file == L"." || file == L"..") 367 | { 368 | continue; 369 | } 370 | 371 | //DebugPrint(L"\tdirectory: %s\n", sub.c_str()); 372 | if (PathIsDirectoryEmptyW(sub.c_str())) 373 | { 374 | DebugPrint(L"%s is empty, removing\n", sub.c_str()); 375 | if (!RemoveDirectory(sub.c_str())) 376 | { 377 | DebugPrint(L"Failed to remove %s\n", sub.c_str()); 378 | } 379 | } 380 | else 381 | { 382 | addAllSubFolderFiles(sub.c_str()); 383 | } 384 | } 385 | else 386 | { 387 | LPCWSTR ext = PathFindExtensionW(data.cFileName); 388 | if (wcscmp(ext, L".lnk") == 0) 389 | { 390 | //DebugPrint(L"\t\tlnk file: %s\n", sub.c_str()); 391 | OnLnkFileAdded(sub.c_str()); 392 | } 393 | } 394 | } while (FindNextFile(hFind, &data)); 395 | FindClose(hFind); 396 | 397 | return S_OK; 398 | } 399 | 400 | virtual 401 | ~WSLDVCFileDB() 402 | { 403 | } 404 | 405 | private: 406 | set m_fileEntries; 407 | }; 408 | 409 | HRESULT 410 | WSLDVCFileDB_CreateInstance( 411 | void* pContext, 412 | IWSLDVCFileDB** ppFileDB 413 | ) 414 | { 415 | return MakeAndInitialize(ppFileDB, pContext); 416 | } 417 | -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCFileDB.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | MIDL_INTERFACE("5802f934-1683-4e81-bb5a-7a0c29a2b1c7") 6 | IWSLDVCFileDB : public IUnknown 7 | { 8 | public: 9 | virtual HRESULT STDMETHODCALLTYPE addAllFilesAsFileIdAt( 10 | _In_z_ LPCWSTR path) = 0; 11 | virtual HRESULT STDMETHODCALLTYPE deleteAllFileIdFiles() = 0; 12 | 13 | virtual HRESULT STDMETHODCALLTYPE OnFileAdded( 14 | _In_z_ LPCWSTR key, 15 | _In_opt_z_ LPCWSTR linkFilePath, 16 | _In_opt_z_ LPCWSTR iconFilePath, 17 | _In_opt_z_ LPCWSTR exeFilePath, 18 | _In_opt_z_ LPCWSTR exeFileArgs) = 0; 19 | virtual HRESULT STDMETHODCALLTYPE OnFileRemoved( 20 | _In_z_ LPCWSTR key) = 0; 21 | 22 | virtual HRESULT STDMETHODCALLTYPE FindFiles( 23 | _In_z_ LPCWSTR key, 24 | _Out_writes_z_(linkFilePathSize) LPWSTR linkFilePath, UINT32 linkFilePathSize, 25 | _Out_writes_z_(iconFilePathSize) LPWSTR iconFilePath, UINT32 iconFilePathSize, 26 | _Out_writes_z_(exeFilePathSize) LPWSTR exeFilePath = nullptr, UINT32 exeFilePathSize = 0, 27 | _Out_writes_z_(exeFileArgsSize) LPWSTR exeFileArgs = nullptr, UINT32 exeFileArgsSize = 0) = 0; 28 | 29 | virtual HRESULT STDMETHODCALLTYPE OnClose(void) = 0; 30 | }; 31 | 32 | HRESULT 33 | WSLDVCFileDB_CreateInstance( 34 | void* pContext, 35 | IWSLDVCFileDB** ppFileDB 36 | ); 37 | -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCListenerCallback.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include "pch.h" 4 | #include "WSLDVCListenerCallback.h" 5 | #include "WSLDVCCallback.h" 6 | 7 | class WSLDVCListenerCallback : 8 | public RuntimeClass< 9 | RuntimeClassFlags, 10 | IWTSListenerCallback> 11 | { 12 | public: 13 | 14 | HRESULT 15 | RuntimeClassInitialize() 16 | { 17 | return S_OK; 18 | } 19 | 20 | // 21 | // IWTSListenerCallback interface 22 | // 23 | STDMETHODIMP 24 | OnNewChannelConnection( 25 | __RPC__in_opt IWTSVirtualChannel* pChannel, 26 | __RPC__in_opt BSTR data, 27 | __RPC__out BOOL* pbAccept, 28 | __RPC__deref_out_opt IWTSVirtualChannelCallback** ppCallback 29 | ) 30 | { 31 | UNREFERENCED_PARAMETER(data); 32 | 33 | HRESULT hr = WSLDVCCallback_CreateInstance(pChannel, ppCallback); 34 | if (SUCCEEDED(hr)) 35 | { 36 | *pbAccept = TRUE; 37 | } 38 | 39 | return hr; 40 | } 41 | 42 | protected: 43 | 44 | virtual 45 | ~WSLDVCListenerCallback() 46 | { 47 | 48 | } 49 | }; 50 | 51 | HRESULT 52 | WSLDVCListenerCallback_CreateInstance( 53 | IWTSListenerCallback** ppCallback 54 | ) 55 | { 56 | return MakeAndInitialize(ppCallback); 57 | } 58 | -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCListenerCallback.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | #include 6 | 7 | HRESULT 8 | WSLDVCListenerCallback_CreateInstance( 9 | IWTSListenerCallback** ppCallback 10 | ); -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCPlugin.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include "pch.h" 4 | #include "WSLDVCPlugin.h" 5 | #include "WSLDVCListenerCallback.h" 6 | 7 | // 8 | // Using Windows Runtime C++ Template Library(WRL) to implement COM objects. 9 | // See: 10 | // https://docs.microsoft.com/en-us/cpp/cppcx/wrl/how-to-instantiate-wrl-components-directly?view=vs-2019 11 | // https://docs.microsoft.com/en-us/cpp/cppcx/wrl/how-to-create-a-classic-com-component-using-wrl?view=vs-2019 12 | // 13 | 14 | class WSLDVCPlugin : 15 | public RuntimeClass< 16 | RuntimeClassFlags, 17 | IWTSPlugin> 18 | { 19 | public: 20 | 21 | HRESULT 22 | RuntimeClassInitialize() 23 | { 24 | return S_OK; 25 | } 26 | 27 | // 28 | // IWTSPlugin interface 29 | // 30 | STDMETHODIMP 31 | Initialize( 32 | __RPC__in_opt IWTSVirtualChannelManager* pChannelMgr 33 | ) 34 | { 35 | HRESULT hr = S_OK; 36 | ComPtr spListenerCallback; 37 | ComPtr spListener; 38 | 39 | hr = WSLDVCListenerCallback_CreateInstance(&spListenerCallback); 40 | if (SUCCEEDED(hr)) 41 | { 42 | hr = pChannelMgr->CreateListener(DVC_NAME, 0, spListenerCallback.Get(), &spListener); 43 | } 44 | 45 | return hr; 46 | } 47 | 48 | STDMETHODIMP 49 | Connected() 50 | { 51 | return S_OK; 52 | } 53 | 54 | STDMETHODIMP 55 | Disconnected( 56 | DWORD dwDisconnectCode 57 | ) 58 | { 59 | UNREFERENCED_PARAMETER(dwDisconnectCode); 60 | return S_OK; 61 | } 62 | 63 | STDMETHODIMP 64 | Terminated() 65 | { 66 | return S_OK; 67 | } 68 | 69 | protected: 70 | 71 | virtual 72 | ~WSLDVCPlugin() 73 | { 74 | } 75 | 76 | private: 77 | 78 | const char* DVC_NAME = "Microsoft::Windows::RDS::RemoteApplicationList"; 79 | }; 80 | 81 | HRESULT 82 | WSLDVCPlugin_CreateInstance( 83 | IWTSPlugin** ppPlugin 84 | ) 85 | { 86 | return MakeAndInitialize(ppPlugin); 87 | } 88 | -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCPlugin.def: -------------------------------------------------------------------------------- 1 | LIBRARY "WSLDVCPlugin.dll" 2 | 3 | EXPORTS 4 | VirtualChannelGetInstance PRIVATE -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCPlugin.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | #include 6 | 7 | HRESULT 8 | WSLDVCPlugin_CreateInstance( 9 | IWTSPlugin** ppPlugin 10 | ); 11 | -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCPlugin.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/wslg/c85d2f3cb6689816f0645e079d3bf3a3e8327e05/WSLDVCPlugin/WSLDVCPlugin.rc -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCPlugin.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30413.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WSLDVCPlugin", "WSLDVCPlugin.vcxproj", "{B7E66936-5220-4D77-AA10-C26A7F6F42D6}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM64 = Debug|ARM64 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|ARM64 = Release|ARM64 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|ARM64.ActiveCfg = Debug|ARM64 19 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|ARM64.Build.0 = Debug|ARM64 20 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x64.ActiveCfg = Debug|x64 21 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x64.Build.0 = Debug|x64 22 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x86.ActiveCfg = Debug|Win32 23 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Debug|x86.Build.0 = Debug|Win32 24 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|ARM64.ActiveCfg = Release|ARM64 25 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|ARM64.Build.0 = Release|ARM64 26 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x64.ActiveCfg = Release|x64 27 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x64.Build.0 = Release|x64 28 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x86.ActiveCfg = Release|Win32 29 | {B7E66936-5220-4D77-AA10-C26A7F6F42D6}.Release|x86.Build.0 = Release|Win32 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {EB2D28FB-D37D-4286-A386-7723B34EF53F} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCPlugin.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | ARM64 7 | 8 | 9 | Debug 10 | Win32 11 | 12 | 13 | Release 14 | ARM64 15 | 16 | 17 | Release 18 | Win32 19 | 20 | 21 | Debug 22 | x64 23 | 24 | 25 | Release 26 | x64 27 | 28 | 29 | 30 | 16.0 31 | Win32Proj 32 | {b7e66936-5220-4d77-aa10-c26a7f6f42d6} 33 | WSLDVCPlugin 34 | 10.0 35 | 36 | 37 | 38 | DynamicLibrary 39 | true 40 | v142 41 | Unicode 42 | 43 | 44 | DynamicLibrary 45 | false 46 | v142 47 | true 48 | Unicode 49 | 50 | 51 | DynamicLibrary 52 | true 53 | v142 54 | Unicode 55 | 56 | 57 | DynamicLibrary 58 | true 59 | v142 60 | Unicode 61 | 62 | 63 | DynamicLibrary 64 | false 65 | v142 66 | true 67 | Unicode 68 | 69 | 70 | DynamicLibrary 71 | false 72 | v142 73 | true 74 | Unicode 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | true 102 | WSLDVCPlugin 103 | 104 | 105 | false 106 | WSLDVCPlugin 107 | 108 | 109 | true 110 | WSLDVCPlugin 111 | 112 | 113 | true 114 | WSLDVCPlugin 115 | 116 | 117 | false 118 | WSLDVCPlugin 119 | 120 | 121 | false 122 | WSLDVCPlugin 123 | 124 | 125 | 126 | Level3 127 | true 128 | WIN32;_DEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 129 | true 130 | Use 131 | pch.h 132 | 133 | 134 | Windows 135 | true 136 | false 137 | shlwapi.lib;%(AdditionalDependencies) 138 | 139 | 140 | 141 | 142 | Level3 143 | true 144 | true 145 | true 146 | WIN32;NDEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 147 | true 148 | Use 149 | pch.h 150 | 151 | 152 | Windows 153 | true 154 | true 155 | true 156 | false 157 | shlwapi.lib;%(AdditionalDependencies) 158 | 159 | 160 | 161 | 162 | Level4 163 | true 164 | _DEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 165 | true 166 | Use 167 | pch.h 168 | MultiThreadedDebug 169 | true 170 | 171 | 172 | Windows 173 | DebugFull 174 | false 175 | 176 | 177 | shlwapi.lib;%(AdditionalDependencies) 178 | 179 | 180 | 181 | 182 | Level4 183 | true 184 | _DEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 185 | true 186 | Use 187 | pch.h 188 | MultiThreadedDebug 189 | true 190 | 191 | 192 | Windows 193 | DebugFull 194 | false 195 | 196 | 197 | shlwapi.lib;%(AdditionalDependencies) 198 | 199 | 200 | 201 | 202 | Level4 203 | true 204 | true 205 | true 206 | NDEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 207 | true 208 | Use 209 | pch.h 210 | MultiThreaded 211 | true 212 | 213 | 214 | Windows 215 | true 216 | true 217 | DebugFull 218 | false 219 | shlwapi.lib;%(AdditionalDependencies) 220 | 221 | 222 | 223 | 224 | Level4 225 | true 226 | true 227 | true 228 | NDEBUG;WSLDVCPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 229 | true 230 | Use 231 | pch.h 232 | MultiThreaded 233 | true 234 | 235 | 236 | Windows 237 | true 238 | true 239 | DebugFull 240 | false 241 | shlwapi.lib;%(AdditionalDependencies) 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | Create 263 | Create 264 | Create 265 | Create 266 | Create 267 | Create 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCPlugin.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | 20 | Source Files 21 | 22 | 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | 53 | 54 | Source Files 55 | 56 | 57 | Source Files 58 | 59 | 60 | Source Files 61 | 62 | 63 | Source Files 64 | 65 | 66 | Source Files 67 | 68 | 69 | Source Files 70 | 71 | 72 | Source Files 73 | 74 | 75 | 76 | 77 | Resource Files 78 | 79 | 80 | -------------------------------------------------------------------------------- /WSLDVCPlugin/WSLDVCPlugin.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /WSLDVCPlugin/cpp.hint: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #define WSLDVCPLUGIN_API __declspec(dllexport) 4 | #define WSLDVCPLUGIN_API __declspec(dllimport) 5 | -------------------------------------------------------------------------------- /WSLDVCPlugin/dllmain.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include "pch.h" 4 | #include "utils.h" 5 | #include "WSLDVCPlugin.h" 6 | #include "WSLDVCFileDB.h" 7 | #include 8 | 9 | BOOL APIENTRY DllMain( HMODULE hModule, 10 | DWORD ul_reason_for_call, 11 | LPVOID lpReserved 12 | ) 13 | { 14 | UNREFERENCED_PARAMETER(hModule); 15 | UNREFERENCED_PARAMETER(lpReserved); 16 | 17 | switch (ul_reason_for_call) 18 | { 19 | case DLL_PROCESS_ATTACH: 20 | case DLL_THREAD_ATTACH: 21 | case DLL_THREAD_DETACH: 22 | case DLL_PROCESS_DETACH: 23 | break; 24 | } 25 | return TRUE; 26 | } 27 | 28 | extern "C" 29 | { 30 | __declspec(dllexport) HRESULT VCAPITYPE 31 | VirtualChannelGetInstance( 32 | _In_ REFIID refiid, 33 | _Inout_ ULONG* pNumObjs, 34 | _Out_opt_ VOID** ppObjArray 35 | ) 36 | { 37 | HRESULT hr = S_OK; 38 | ComPtr spPlugin; 39 | 40 | if (refiid != __uuidof(IWTSPlugin)) 41 | { 42 | return E_NOINTERFACE; 43 | } 44 | 45 | if (ppObjArray == NULL) 46 | { 47 | *pNumObjs = 1; 48 | return S_OK; 49 | } 50 | 51 | hr = WSLDVCPlugin_CreateInstance(&spPlugin); 52 | if (SUCCEEDED(hr)) 53 | { 54 | *pNumObjs = 1; 55 | ppObjArray[0] = spPlugin.Detach(); 56 | } 57 | 58 | return hr; 59 | } 60 | 61 | __declspec(dllexport) HRESULT WINAPI 62 | RemoveAppProvider( 63 | _In_z_ LPCWSTR appProvider 64 | ) 65 | { 66 | HRESULT hr; 67 | WCHAR appMenuPath[MAX_PATH] = {}; 68 | ComPtr spFileDB; 69 | 70 | if (!appProvider) 71 | { 72 | DebugPrint(L"appProvider parameter is required\n"); 73 | return E_INVALIDARG; 74 | } 75 | 76 | hr = BuildMenuPath(ARRAYSIZE(appMenuPath), appMenuPath, appProvider, false); 77 | if (FAILED(hr)) 78 | { 79 | return hr; 80 | } 81 | DebugPrint(L"AppMenuPath: %s\n", appMenuPath); 82 | 83 | if (!IsDirectoryPresent(appMenuPath)) 84 | { 85 | DebugPrint(L"%s is not present\n", appMenuPath); 86 | return S_OK; // no program menu exists for given provider, simply exit. 87 | } 88 | 89 | hr = WSLDVCFileDB_CreateInstance(NULL, &spFileDB); 90 | if (FAILED(hr)) 91 | { 92 | DebugPrint(L"failed to instance WSLDVCFileDB\n"); 93 | return hr; 94 | } 95 | 96 | spFileDB->addAllFilesAsFileIdAt(appMenuPath); 97 | spFileDB->deleteAllFileIdFiles(); 98 | spFileDB->OnClose(); 99 | spFileDB = nullptr; 100 | 101 | return hr; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /WSLDVCPlugin/framework.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 6 | // Windows Header Files 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include /* For IPersistFile */ 13 | #include /* For IShellLink */ 14 | #include /* For PathIsDirectoryEmpty */ 15 | #include /* For SHGetPropertyStoreForWindow */ 16 | #include /* For InitPropVariantFromString */ 17 | #include /* For PKEY_* */ 18 | #include /* For PRIx64 */ 19 | -------------------------------------------------------------------------------- /WSLDVCPlugin/pch.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include "pch.h" 4 | 5 | // When you are using pre-compiled headers, this source file is necessary for compilation to succeed. 6 | -------------------------------------------------------------------------------- /WSLDVCPlugin/pch.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | // pch.h: This is a precompiled header file. 4 | // Files listed below are compiled only once, improving build performance for future builds. 5 | // This also affects IntelliSense performance, including code completion and many code browsing features. 6 | // However, files listed here are ALL re-compiled if any one of them is updated between builds. 7 | // Do not add files here that you will be updating frequently as this negates the performance advantage. 8 | 9 | #ifndef PCH_H 10 | #define PCH_H 11 | 12 | // add headers that you want to pre-compile here 13 | #include "framework.h" 14 | 15 | using namespace Microsoft::WRL; 16 | using namespace std; 17 | 18 | #endif //PCH_H 19 | -------------------------------------------------------------------------------- /WSLDVCPlugin/rdpapplist.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | // 6 | // RDP APPLIST protocol header. 7 | // 8 | #define RDPAPPLIST_CMDID_CAPS 0x00000001 9 | #define RDPAPPLIST_CMDID_UPDATE_APPLIST 0x00000002 10 | #define RDPAPPLIST_CMDID_DELETE_APPLIST 0x00000003 11 | #define RDPAPPLIST_CMDID_DELETE_APPLIST_PROVIDER 0x00000004 12 | /* added from version 4 */ 13 | #define RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID 0x00000005 14 | 15 | #define RDPAPPLIST_FIELD_ID 0x00000001 16 | #define RDPAPPLIST_FIELD_GROUP 0x00000002 17 | #define RDPAPPLIST_FIELD_EXECPATH 0x00000004 18 | #define RDPAPPLIST_FIELD_DESC 0x00000008 19 | #define RDPAPPLIST_FIELD_ICON 0x00000010 20 | #define RDPAPPLIST_FIELD_PROVIDER 0x00000020 21 | #define RDPAPPLIST_FIELD_WORKINGDIR 0x00000040 22 | #define RDPAPPLIST_FIELD_WINDOW_ID 0x00000080 23 | 24 | /* RDPAPPLIST_UPDATE_APPLIST_PDU */ 25 | #define RDPAPPLIST_HINT_NEWID 0x00010000 /* new appId vs update existing appId. */ 26 | #define RDPAPPLIST_HINT_SYNC 0x00100000 /* In sync mode (use with _NEWID). */ 27 | #define RDPAPPLIST_HINT_SYNC_START 0x00200000 /* Sync appId start (use with _SYNC). */ 28 | #define RDPAPPLIST_HINT_SYNC_END 0x00400000 /* Sync appId end (use with _SYNC). */ 29 | 30 | #define RDPAPPLIST_CHANNEL_VERSION 4 31 | 32 | #define RDPAPPLIST_HEADER_SIZE 8 33 | 34 | #define RDPAPPLIST_LANG_SIZE 32 35 | 36 | #define RDPAPPLIST_MAX_STRING_SIZE 512 37 | #define RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR (RDPAPPLIST_MAX_STRING_SIZE/sizeof(WCHAR)) 38 | 39 | typedef struct _RDPAPPLIST_HEADER 40 | { 41 | UINT32 cmdId; 42 | UINT32 length; 43 | } RDPAPPLIST_HEADER; 44 | 45 | typedef struct _RDPAPPLIST_CLIENT_CAPS_PDU 46 | { 47 | UINT16 version; 48 | /* ISO 639 (Language name) and ISO 3166 (Country name) connected with '_', such as en_US, ja_JP */ 49 | char clientLanguageId[RDPAPPLIST_LANG_SIZE]; 50 | } RDPAPPLIST_CLIENT_CAPS_PDU; 51 | 52 | typedef struct _RDPAPPLIST_SERVER_CAPS_PDU 53 | { 54 | UINT16 version; 55 | UINT16 appListProviderNameLength; 56 | WCHAR appListProviderName[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 57 | /* added from version 4 */ 58 | UINT16 appListProviderUniqueIdLength; 59 | WCHAR appListProviderUniqueId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 60 | } RDPAPPLIST_SERVER_CAPS_PDU; 61 | 62 | typedef struct _RDPAPPLIST_UPDATE_APPLIST_PDU 63 | { 64 | UINT32 flags; 65 | UINT16 appIdLength; 66 | WCHAR appId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 67 | UINT16 appGroupLength; 68 | WCHAR appGroup[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 69 | UINT16 appExecPathLength; 70 | WCHAR appExecPath[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 71 | UINT16 appWorkingDirLength; 72 | WCHAR appWorkingDir[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 73 | UINT16 appDescLength; 74 | WCHAR appDesc[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 75 | } RDPAPPLIST_UPDATE_APPLIST_PDU; 76 | 77 | typedef struct _RDPAPPLIST_ICON_DATA 78 | { 79 | UINT32 flags; 80 | UINT32 iconWidth; 81 | UINT32 iconHeight; 82 | UINT32 iconStride; 83 | UINT32 iconBpp; 84 | UINT32 iconFormat; 85 | UINT32 iconBitsLength; 86 | UINT32 iconFileSize; 87 | BYTE* iconFileData; 88 | } RDPAPPLIST_ICON_DATA; 89 | 90 | typedef struct _RDPAPPLIST_DELETE_APPLIST_PDU 91 | { 92 | UINT32 flags; 93 | UINT16 appIdLength; 94 | WCHAR appId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 95 | UINT16 appGroupLength; 96 | WCHAR appGroup[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 97 | } RDPAPPLIST_DELETE_APPLIST_PDU; 98 | 99 | typedef struct _RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU 100 | { 101 | UINT32 flags; 102 | UINT16 appListProviderNameLength; 103 | WCHAR appListProviderName[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 104 | } RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU; 105 | 106 | /* added from version 4 */ 107 | typedef struct _RDPAPPLIST_ASSOCIATE_WINDOW_ID 108 | { 109 | UINT32 flags; 110 | UINT32 appWindowId; 111 | UINT16 appIdLength; 112 | WCHAR appId[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 113 | UINT16 appGroupLength; 114 | WCHAR appGroup[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 115 | UINT16 appExecPathLength; 116 | WCHAR appExecPath[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 117 | UINT16 appDescLength; 118 | WCHAR appDesc[RDPAPPLIST_MAX_STRING_SIZE_IN_WCHAR]; 119 | } RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU; 120 | 121 | // 122 | // Read macro. 123 | // 124 | #define LSTR(x) L ## x 125 | 126 | // ReadUINT16(dest, source, remaining) 127 | #define ReadUINT16(o, p, r) \ 128 | if (r >= sizeof(UINT16)) { \ 129 | o = (*(UINT16*)(p)); \ 130 | (p) += sizeof(UINT16); \ 131 | (r) -= sizeof(UINT16); \ 132 | } else { \ 133 | DebugPrint(L"Failed to read " LSTR(#o) L"\n"); \ 134 | goto Error_Read; \ 135 | } 136 | 137 | // ReadUINT32(dest, source, remaining) 138 | #define ReadUINT32(o, p, r) \ 139 | if (r >= sizeof(UINT32)) { \ 140 | o = (*(UINT32*)(p)); \ 141 | (p) += sizeof(UINT32); \ 142 | (r) -= sizeof(UINT32); \ 143 | } else { \ 144 | DebugPrint(L"Failed to read " LSTR(#o) L"\n"); \ 145 | goto Error_Read; \ 146 | } 147 | 148 | // ReadBYTES(dest, source, lengthToCopy, RemainingSource) 149 | #define ReadBYTES(o, p, l, r) \ 150 | if (r >= l) { \ 151 | memcpy((o), (p), (l)); \ 152 | (p) += l; \ 153 | (r) -= (l); \ 154 | } else { \ 155 | DebugPrint(L"Failed to read " LSTR(#o) L"\n"); \ 156 | goto Error_Read; \ 157 | } 158 | 159 | // ReadSTRING(dest, source, RemainingSource, required) 160 | #define ReadSTRING(o, p, r, required) \ 161 | ReadUINT16(o ## Length, p, r); \ 162 | if (o ## Length + sizeof(WCHAR) > sizeof(o)) { \ 163 | DebugPrint(L"Failed to read " LSTR(#o) L"\n"); \ 164 | goto Error_Read; \ 165 | } if (o ## Length) { \ 166 | ReadBYTES(o, p, o ## Length, r); \ 167 | o[o ## Length] = L'\0'; \ 168 | } else if (required) { \ 169 | DebugPrint(LSTR(#o) L" is required\n"); \ 170 | goto Error_Read; \ 171 | } 172 | 173 | #define CheckRequiredFlags(flags, required) \ 174 | { \ 175 | auto f = (flags) & (required); \ 176 | if (f != (required)) { \ 177 | DebugPrint(L"missing required flags. Given:%x vs Required:%x\n", flags, required); \ 178 | goto Error_Read; \ 179 | } \ 180 | } 181 | -------------------------------------------------------------------------------- /WSLDVCPlugin/resource.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | //{{NO_DEPENDENCIES}} 4 | // Microsoft Visual C++ generated include file. 5 | // Used by WSLDVCPlugin.rc 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 101 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /WSLDVCPlugin/utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include "pch.h" 4 | #include "utils.h" 5 | 6 | #ifdef DBG_MESSAGE 7 | void DebugPrint(const wchar_t* format, ...) 8 | { 9 | WCHAR buf[512] = {}; 10 | va_list args; 11 | 12 | va_start(args, format); 13 | wvsprintfW(buf, format, args); 14 | va_end(args); 15 | 16 | OutputDebugStringW(buf); 17 | } 18 | #endif // DBG_MESSAGE 19 | 20 | _Use_decl_annotations_ 21 | BOOL IsDirectoryPresent(LPCWSTR lpszPath) 22 | { 23 | DWORD dwAttrib = GetFileAttributes(lpszPath); 24 | 25 | return (dwAttrib != INVALID_FILE_ATTRIBUTES && 26 | (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); 27 | } 28 | 29 | _Use_decl_annotations_ 30 | HRESULT 31 | CreateShellLink(LPCWSTR lpszPathLink, 32 | LPCWSTR lpszPathObj, 33 | LPCWSTR lpszArgs, 34 | LPCWSTR lpszWorkingDir, 35 | LPCWSTR lpszDesc, 36 | LPCWSTR lpszPathIcon) 37 | { 38 | HRESULT hr; 39 | IShellLink* psl; 40 | 41 | DebugPrint(L"CreateShellLink:\n"); 42 | DebugPrint(L"\tPath Link: %s\n", lpszPathLink); 43 | DebugPrint(L"\tPath Exe: %s\n", lpszPathObj); 44 | DebugPrint(L"\tExe args: %s\n", lpszArgs); 45 | DebugPrint(L"\tWorkingDir: %s\n", lpszWorkingDir); 46 | DebugPrint(L"\tDesc: %s\n", lpszDesc); 47 | if (lpszPathIcon && lstrlenW(lpszPathIcon)) 48 | { 49 | DebugPrint(L"\tIcon Path: %s\n", lpszPathIcon); 50 | } 51 | else 52 | { 53 | lpszPathIcon = nullptr; 54 | } 55 | 56 | // Get a pointer to the IShellLink interface. It is assumed that CoInitialize 57 | // has already been called. 58 | hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); 59 | if (SUCCEEDED(hr)) 60 | { 61 | IPersistFile* ppf; 62 | 63 | // Set the path to the shortcut target and add the description. 64 | psl->SetPath(lpszPathObj); 65 | psl->SetArguments(lpszArgs); 66 | if (lpszPathIcon) 67 | { 68 | psl->SetIconLocation(lpszPathIcon, 0); 69 | } 70 | psl->SetDescription(lpszDesc); 71 | psl->SetWorkingDirectory(lpszWorkingDir); 72 | psl->SetShowCmd(SW_SHOWMINNOACTIVE); 73 | 74 | // Query IShellLink for the IPersistFile interface, used for saving the 75 | // shortcut in persistent storage. 76 | hr = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); 77 | if (SUCCEEDED(hr)) 78 | { 79 | // Save the link by calling IPersistFile::Save. 80 | hr = ppf->Save(lpszPathLink, TRUE); 81 | ppf->Release(); 82 | } 83 | psl->Release(); 84 | } 85 | 86 | DebugPrint(L"\tresult: %x\n", hr); 87 | return hr; 88 | } 89 | 90 | _Use_decl_annotations_ 91 | HRESULT 92 | GetIconFileFromShellLink( 93 | UINT32 iconPathSize, 94 | LPWSTR iconPath, 95 | LPCWSTR lnkPath) 96 | { 97 | HRESULT hr; 98 | IShellLink* psl; 99 | 100 | DebugPrint(L"GetIconFileFromShellLink:\n"); 101 | DebugPrint(L"\tPath Link: %s\n", lnkPath); 102 | 103 | assert(iconPathSize); 104 | assert(iconPath); 105 | *iconPath = L'\0'; 106 | 107 | hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); 108 | if (SUCCEEDED(hr)) 109 | { 110 | IPersistFile* ppf; 111 | hr = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); 112 | if (SUCCEEDED(hr)) 113 | { 114 | hr = ppf->Load(lnkPath, STGM_READ); 115 | if (SUCCEEDED(hr)) 116 | { 117 | int dummy = 0; 118 | hr = psl->GetIconLocation(iconPath, iconPathSize, &dummy); 119 | } 120 | ppf->Release(); 121 | } 122 | psl->Release(); 123 | } 124 | 125 | DebugPrint(L"\tresult: %x\n", hr); 126 | if (SUCCEEDED(hr)) 127 | { 128 | DebugPrint(L"\ticonPath: %s\n", iconPath); 129 | } 130 | 131 | return hr; 132 | } 133 | 134 | _Use_decl_annotations_ 135 | HRESULT 136 | CreateIconFile(BYTE* pBuffer, 137 | UINT32 cbSize, 138 | LPCWSTR lpszIconFile) 139 | { 140 | HRESULT hr = S_OK; 141 | HANDLE hFile; 142 | 143 | DebugPrint(L"CreateIconFile: %s\n", lpszIconFile); 144 | 145 | hFile = CreateFileW(lpszIconFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 146 | if (hFile == INVALID_HANDLE_VALUE) 147 | { 148 | DebugPrint(L"CreateFile(%s) failed, error %d\n", lpszIconFile, GetLastError()); 149 | hr = E_FAIL; 150 | } 151 | else 152 | { 153 | if (!WriteFile(hFile, pBuffer, cbSize, NULL, NULL)) 154 | { 155 | DebugPrint(L"WriteFile(%s) failed, error %d\n", lpszIconFile, GetLastError()); 156 | hr = E_FAIL; 157 | } 158 | 159 | CloseHandle(hFile); 160 | } 161 | 162 | DebugPrint(L"\tresult: %x\n", hr); 163 | return hr; 164 | } 165 | 166 | #define MAX_LOCALE_CODE 9 167 | 168 | _Use_decl_annotations_ 169 | BOOL GetLocaleName(char* localeName, int localeNameSize) 170 | { 171 | char langCode[MAX_LOCALE_CODE] = {}; 172 | char countryName[MAX_LOCALE_CODE] = {}; 173 | int result = 0; 174 | 175 | assert(localeName); 176 | localeName[0] = '\0'; 177 | 178 | LCID lcid = MAKELCID(GetUserDefaultUILanguage(), SORT_DEFAULT); 179 | result = GetLocaleInfoA(lcid, 180 | LOCALE_SISO639LANGNAME, 181 | langCode, 182 | MAX_LOCALE_CODE) != 0; 183 | if ((result == 0) || 184 | (strcpy_s(localeName, localeNameSize, langCode) != 0) || 185 | (strcat_s(localeName, localeNameSize, "_") != 0)) 186 | { 187 | return FALSE; 188 | } 189 | 190 | result = GetLocaleInfoA(lcid, 191 | LOCALE_SISO3166CTRYNAME, 192 | countryName, 193 | MAX_LOCALE_CODE) != 0; 194 | if ((result == 0) || 195 | (strcat_s(localeName, localeNameSize, countryName) != 0)) 196 | { 197 | return FALSE; 198 | } 199 | 200 | return TRUE; 201 | } 202 | 203 | _Use_decl_annotations_ 204 | HRESULT BuildMenuPath( 205 | UINT32 appMenuPathSize, 206 | LPWSTR appMenuPath, 207 | LPCWSTR appProvider, 208 | bool isCreateDir) 209 | { 210 | PWSTR knownFolderPath = NULL; // free by CoTaskMemFree. 211 | SHGetKnownFolderPath(FOLDERID_StartMenu, 0, NULL, &knownFolderPath); 212 | if (!knownFolderPath) 213 | { 214 | DebugPrint(L"SHGetKnownFolderPath(FOLDERID_StartMenu) failed\n"); 215 | return E_FAIL; 216 | } 217 | int ret = swprintf_s(appMenuPath, appMenuPathSize, L"%s\\Programs\\%s", knownFolderPath, appProvider); 218 | CoTaskMemFree(knownFolderPath); 219 | if (ret < 0) 220 | { 221 | DebugPrint(L"swprintf_s for appMenuPath failed"); 222 | return E_FAIL; 223 | } 224 | if (isCreateDir) 225 | { 226 | if (!CreateDirectoryW(appMenuPath, NULL)) 227 | { 228 | if (ERROR_ALREADY_EXISTS != GetLastError()) 229 | { 230 | DebugPrint(L"Failed to create %s\n", appMenuPath); 231 | return E_FAIL; 232 | } 233 | } 234 | } 235 | 236 | return S_OK; 237 | } 238 | 239 | _Use_decl_annotations_ 240 | HRESULT BuildIconPath( 241 | UINT32 iconPathSize, 242 | LPWSTR iconPath, 243 | LPCWSTR appProvider, 244 | bool isCreateDir) 245 | { 246 | WCHAR prefix[] = L"WSLDVCPlugin\\"; 247 | 248 | UINT32 lenTempPath; 249 | lenTempPath = GetTempPathW(iconPathSize, iconPath); 250 | if (!lenTempPath) 251 | { 252 | DebugPrint(L"GetTempPathW failed\n"); 253 | return E_FAIL; 254 | } 255 | 256 | if ((lenTempPath + ARRAYSIZE(prefix) + wcslen(appProvider)) > iconPathSize) 257 | { 258 | DebugPrint(L"provider name length check failed, length %d\n", wcslen(appProvider)); 259 | return E_FAIL; 260 | } 261 | 262 | if (wcscat_s(iconPath, iconPathSize, prefix) != 0) 263 | { 264 | return E_FAIL; 265 | } 266 | if (isCreateDir) 267 | { 268 | if (!CreateDirectoryW(iconPath, NULL)) 269 | { 270 | if (ERROR_ALREADY_EXISTS != GetLastError()) 271 | { 272 | DebugPrint(L"Failed to create %s\n", iconPath); 273 | return E_FAIL; 274 | } 275 | } 276 | } 277 | if (wcscat_s(iconPath, iconPathSize, appProvider) != 0) 278 | { 279 | return E_FAIL; 280 | } 281 | if (isCreateDir) 282 | { 283 | if (!CreateDirectoryW(iconPath, NULL)) 284 | { 285 | if (ERROR_ALREADY_EXISTS != GetLastError()) 286 | { 287 | DebugPrint(L"Failed to create %s\n", iconPath); 288 | return E_FAIL; 289 | } 290 | } 291 | } 292 | 293 | return S_OK; 294 | } 295 | 296 | HRESULT 297 | UpdateTaskBarInfo( 298 | HWND hwnd, 299 | _In_z_ LPCWSTR relaunchCmdline, 300 | _In_z_ LPCWSTR displayName, 301 | _In_z_ LPCWSTR iconPath) 302 | { 303 | HRESULT hr; 304 | PROPVARIANT propvar; 305 | 306 | DebugPrint(L"UpdateTaskBarInfo: 0x%p\n", hwnd); 307 | DebugPrint(L" relaunchCmdline: %s\n", relaunchCmdline); 308 | DebugPrint(L" displayName: %s\n", displayName); 309 | DebugPrint(L" iconPath: %s\n", iconPath); 310 | 311 | IPropertyStore* ps = NULL; 312 | 313 | hr = SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&ps)); 314 | if (FAILED(hr)) 315 | { 316 | DebugPrint(L"SHGetPropertyStoreForWindow failed: 0x%x\n", hr); 317 | return hr; 318 | } 319 | 320 | BOOL bPinToTaskbar = relaunchCmdline && displayName && iconPath; 321 | if (bPinToTaskbar) 322 | { 323 | hr = InitPropVariantFromString(displayName, &propvar); 324 | if (FAILED(hr)) 325 | { 326 | DebugPrint(L"InitPropVariantFromString failed: 0x%x\n", hr); 327 | return hr; 328 | } 329 | 330 | hr = ps->SetValue(PKEY_AppUserModel_RelaunchDisplayNameResource, propvar); 331 | PropVariantClear(&propvar); 332 | if (FAILED(hr)) 333 | { 334 | DebugPrint(L"SetValue(PKEY_AppUserModel_RelaunchDisplayNameResource failed: 0x%x\n", hr); 335 | return hr; 336 | } 337 | 338 | hr = InitPropVariantFromString(iconPath, &propvar); 339 | if (FAILED(hr)) 340 | { 341 | DebugPrint(L"InitPropVariantFromString failed: 0x%x\n", hr); 342 | return hr; 343 | } 344 | 345 | hr = ps->SetValue(PKEY_AppUserModel_RelaunchIconResource, propvar); 346 | PropVariantClear(&propvar); 347 | if (FAILED(hr)) 348 | { 349 | DebugPrint(L"SetValue(PKEY_AppUserModel_RelaunchIconResource failed: 0x%x\n", hr); 350 | return hr; 351 | } 352 | 353 | hr = InitPropVariantFromString(relaunchCmdline, &propvar); 354 | if (FAILED(hr)) 355 | { 356 | DebugPrint(L"InitPropVariantFromString failed: 0x%x\n", hr); 357 | return hr; 358 | } 359 | 360 | hr = ps->SetValue(PKEY_AppUserModel_RelaunchCommand, propvar); 361 | PropVariantClear(&propvar); 362 | if (FAILED(hr)) 363 | { 364 | DebugPrint(L"SetValue(PKEY_AppUserModel_RelaunchCommand failed: 0x%x\n", hr); 365 | return hr; 366 | } 367 | } 368 | else 369 | { 370 | hr = InitPropVariantFromBoolean(TRUE, &propvar); 371 | if (FAILED(hr)) 372 | { 373 | DebugPrint(L"InitPropVariantFromBoolean failed: 0x%x\n", hr); 374 | return hr; 375 | } 376 | 377 | hr = ps->SetValue(PKEY_AppUserModel_PreventPinning, propvar); 378 | PropVariantClear(&propvar); 379 | if (FAILED(hr)) 380 | { 381 | DebugPrint(L"SetValue(PKEY_AppUserModel_PreventPinning failed: 0x%x\n", hr); 382 | return hr; 383 | } 384 | } 385 | 386 | ps->Release(); 387 | 388 | return S_OK; 389 | } 390 | 391 | #if ENABLE_WSL_SIGNATURE_CHECK 392 | // Link with the Wintrust.lib file. 393 | #pragma comment (lib, "wintrust") 394 | 395 | #include 396 | #include 397 | #include 398 | 399 | _Use_decl_annotations_ 400 | BOOL IsFileTrusted(LPCWSTR pwszFile) 401 | { 402 | BOOL bTrusted = FALSE; 403 | 404 | LONG lStatus; 405 | DWORD dwLastError; 406 | 407 | // Initialize the WINTRUST_FILE_INFO structure. 408 | WINTRUST_FILE_INFO FileData; 409 | memset(&FileData, 0, sizeof(FileData)); 410 | FileData.cbStruct = sizeof(WINTRUST_FILE_INFO); 411 | FileData.pcwszFilePath = pwszFile; 412 | FileData.hFile = NULL; 413 | FileData.pgKnownSubject = NULL; 414 | 415 | /* 416 | WVTPolicyGUID specifies the policy to apply on the file 417 | WINTRUST_ACTION_GENERIC_VERIFY_V2 policy checks: 418 | 419 | 1) The certificate used to sign the file chains up to a root 420 | certificate located in the trusted root certificate store. This 421 | implies that the identity of the publisher has been verified by 422 | a certification authority. 423 | 424 | 2) In cases where user interface is displayed (which this example 425 | does not do), WinVerifyTrust will check for whether the 426 | end entity certificate is stored in the trusted publisher store, 427 | implying that the user trusts content from this publisher. 428 | 429 | 3) The end entity certificate has sufficient permission to sign 430 | code, as indicated by the presence of a code signing EKU or no 431 | EKU. 432 | */ 433 | 434 | GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; 435 | WINTRUST_DATA WinTrustData; 436 | 437 | // Initialize the WinVerifyTrust input data structure. 438 | 439 | // Default all fields to 0. 440 | memset(&WinTrustData, 0, sizeof(WinTrustData)); 441 | 442 | WinTrustData.cbStruct = sizeof(WinTrustData); 443 | 444 | // Use default code signing EKU. 445 | WinTrustData.pPolicyCallbackData = NULL; 446 | 447 | // No data to pass to SIP. 448 | WinTrustData.pSIPClientData = NULL; 449 | 450 | // Disable WVT UI. 451 | WinTrustData.dwUIChoice = WTD_UI_NONE; 452 | 453 | // No revocation checking. 454 | WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE; 455 | 456 | // Verify an embedded signature on a file. 457 | WinTrustData.dwUnionChoice = WTD_CHOICE_FILE; 458 | 459 | // Verify action. 460 | WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY; 461 | 462 | // Verification sets this value. 463 | WinTrustData.hWVTStateData = NULL; 464 | 465 | // Not used. 466 | WinTrustData.pwszURLReference = NULL; 467 | 468 | // This is not applicable if there is no UI because it changes 469 | // the UI to accommodate running applications instead of 470 | // installing applications. 471 | WinTrustData.dwUIContext = 0; 472 | 473 | // Set pFile. 474 | WinTrustData.pFile = &FileData; 475 | 476 | // WinVerifyTrust verifies signatures as specified by the GUID 477 | // and Wintrust_Data. 478 | lStatus = WinVerifyTrust( 479 | NULL, 480 | &WVTPolicyGUID, 481 | &WinTrustData); 482 | 483 | switch (lStatus) 484 | { 485 | case ERROR_SUCCESS: 486 | //Signed file: 487 | // - Hash that represents the subject is trusted. 488 | // - Trusted publisher without any verification errors. 489 | DebugPrint(L"The file \"%s\" is signed and the signature was verified.\n", 490 | pwszFile); 491 | bTrusted = TRUE; 492 | break; 493 | 494 | case TRUST_E_NOSIGNATURE: 495 | // The file was not signed or had a signature 496 | // that was not valid. 497 | // Get the reason for no signature. 498 | dwLastError = GetLastError(); 499 | if (TRUST_E_NOSIGNATURE == dwLastError || 500 | TRUST_E_SUBJECT_FORM_UNKNOWN == dwLastError || 501 | TRUST_E_PROVIDER_UNKNOWN == dwLastError) 502 | { 503 | // The file was not signed. 504 | DebugPrint(L"The file \"%s\" is not signed.\n", 505 | pwszFile); 506 | } 507 | else 508 | { 509 | // The signature was not valid or there was an error 510 | // opening the file. 511 | DebugPrint(L"An unknown error occurred trying to " 512 | L"verify the signature of the \"%s\" file.\n", 513 | pwszFile); 514 | } 515 | break; 516 | 517 | case TRUST_E_EXPLICIT_DISTRUST: 518 | // The hash that represents the subject or the publisher 519 | // is not allowed by the admin or user. 520 | DebugPrint(L"The signature is present, but specifically disallowed.\n"); 521 | break; 522 | 523 | case TRUST_E_SUBJECT_NOT_TRUSTED: 524 | DebugPrint(L"The signature is present, but not trusted.\n"); 525 | break; 526 | 527 | default: 528 | // The UI was disabled in dwUIChoice or the admin policy 529 | // has disabled user trust. lStatus contains the 530 | // publisher or time stamp chain error. 531 | DebugPrint(L"Error is: 0x%x.\n", lStatus); 532 | break; 533 | } 534 | 535 | // Any hWVTStateData must be released by a call with close. 536 | WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE; 537 | 538 | lStatus = WinVerifyTrust( 539 | NULL, 540 | &WVTPolicyGUID, 541 | &WinTrustData); 542 | 543 | return bTrusted; 544 | } 545 | #endif // ENABLE_WSL_SIGNATURE_CHECK 546 | -------------------------------------------------------------------------------- /WSLDVCPlugin/utils.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | //#ifdef _DEBUG 6 | #define DBG_MESSAGE 7 | //#endif // _DEBUG 8 | 9 | #ifdef DBG_MESSAGE 10 | void DebugPrint(const wchar_t* format, ...); 11 | #define DebugAssert(exp) assert(exp) 12 | #else 13 | #define DebugPrint 14 | #define DebugAssert 15 | #endif // DBG_MESSAGE 16 | 17 | // Set to 1 to enable digital signature check. 18 | #define ENABLE_WSL_SIGNATURE_CHECK 0 19 | 20 | BOOL 21 | IsDirectoryPresent(_In_z_ LPCWSTR lpszPath); 22 | 23 | HRESULT 24 | CreateShellLink(_In_z_ LPCWSTR lpszPathLink, 25 | _In_z_ LPCWSTR lpszPathObj, 26 | _In_z_ LPCWSTR lpszArgs, 27 | _In_z_ LPCWSTR lpszWorkingDir, 28 | _In_z_ LPCWSTR lpszDesc, 29 | _In_opt_z_ LPCWSTR lpszPathIcon); 30 | 31 | HRESULT 32 | GetIconFileFromShellLink( 33 | UINT32 iconPathSize, 34 | _Out_writes_z_(iconPathSize) LPWSTR iconPath, 35 | _In_z_ LPCWSTR lnkPath); 36 | 37 | HRESULT 38 | CreateIconFile(_In_reads_bytes_(cbSize) BYTE* pBuffer, 39 | UINT32 cbSize, 40 | _In_z_ LPCWSTR lpszIconFile); 41 | 42 | BOOL 43 | GetLocaleName(_Out_writes_z_(localeNameSize) char* localeName, 44 | int localeNameSize); 45 | 46 | HRESULT 47 | BuildMenuPath( 48 | UINT32 appMenuPathSize, 49 | _Out_writes_z_(appMenuPathSize) LPWSTR appMenuPath, 50 | _In_z_ LPCWSTR appProvider, 51 | bool isCreateDir); 52 | 53 | HRESULT 54 | BuildIconPath( 55 | UINT32 iconPathSize, 56 | _Out_writes_z_(iconPathSize) LPWSTR iconPath, 57 | _In_z_ LPCWSTR appProvider, 58 | bool isCreateDir); 59 | 60 | HRESULT 61 | UpdateTaskBarInfo( 62 | HWND hwnd, 63 | _In_z_ LPCWSTR relaunchCmdline, 64 | _In_z_ LPCWSTR displayName, 65 | _In_z_ LPCWSTR iconPath); 66 | 67 | #if ENABLE_WSL_SIGNATURE_CHECK 68 | BOOL 69 | IsFileTrusted(_In_z_ LPCWSTR pwszFile); 70 | #else 71 | #define IsFileTrusted(pwszFile) (true) 72 | #endif // ENABLE_WSL_SIGNATURE_CHECK 73 | 74 | #pragma pack(1) 75 | // 76 | // .ICO file format header 77 | // 78 | 79 | // Icon entry struct 80 | typedef struct _ICON_DIR_ENTRY 81 | { 82 | BYTE bWidth; // Width, in pixels, of the image 83 | BYTE bHeight; // Height, in pixels, of the image 84 | BYTE bColorCount; // Number of colors in image (0 if >=8bpp) 85 | BYTE bReserved; // Reserved ( must be 0) 86 | WORD wPlanes; // Color Planes 87 | WORD wBitCount; // Bits per pixel 88 | DWORD dwBytesInRes; // How many bytes in this resource? 89 | DWORD dwImageOffset; // Where in the file is this image? 90 | } ICON_DIR_ENTRY; 91 | 92 | // Icon directory struct 93 | typedef struct _ICON_HEADER 94 | { 95 | WORD idReserved; // Reserved (must be 0) 96 | WORD idType; // Resource Type (1 for icons) 97 | WORD idCount; // How many images? 98 | ICON_DIR_ENTRY idEntries[1]; // An entry for each image (idCount of 'em) 99 | } ICON_HEADER; 100 | #pragma pack() 101 | -------------------------------------------------------------------------------- /WSLGd/FontMonitor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include "FontMonitor.h" 4 | #include "common.h" 5 | 6 | #define DEFAULT_FONT_PATH "/usr/share/fonts" 7 | #define USER_DISTRO_FONT_PATH USER_DISTRO_MOUNT_PATH DEFAULT_FONT_PATH 8 | #define ALT_FONT_PATH "/usr/share/X11/fonts" 9 | #define ALT_DISTRO_FONT_PATH USER_DISTRO_MOUNT_PATH ALT_FONT_PATH 10 | 11 | constexpr auto c_fontsdir = "fonts.dir"; 12 | constexpr auto c_xset = "/usr/bin/xset"; 13 | 14 | wslgd::FontFolder::FontFolder(int fd, const char *path) 15 | { 16 | LOG_INFO("FontMonitor: start monitoring %s", path); 17 | 18 | m_path = path; 19 | 20 | /* check if folder is already ready to be added to font path */ 21 | try { 22 | std::filesystem::path fonts_dir(path); 23 | fonts_dir /= c_fontsdir; 24 | if (access(fonts_dir.c_str(), R_OK) == 0) { 25 | ModifyX11FontPath(true); 26 | } 27 | m_fd.reset(dup(fd)); 28 | THROW_LAST_ERROR_IF(!m_fd); 29 | /* add watch for install or uninstall of fonts on this folder */ 30 | 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); 31 | } 32 | CATCH_LOG(); 33 | } 34 | 35 | wslgd::FontFolder::~FontFolder() 36 | { 37 | LOG_INFO("FontMonitor: stop monitoring %s", m_path.c_str()); 38 | 39 | ModifyX11FontPath(false); 40 | 41 | /* if still under watch, remove it */ 42 | if (m_wd >= 0) { 43 | inotify_rm_watch(m_fd.get(), m_wd); 44 | m_wd = -1; 45 | } 46 | } 47 | 48 | bool wslgd::FontFolder::ExecuteShellCommand(std::vector&& argv) 49 | { 50 | bool success = false; 51 | int childPid = -1, waitPid = -1; 52 | std::string cmd; 53 | 54 | try { 55 | THROW_LAST_ERROR_IF((childPid = fork()) < 0); 56 | if (childPid == 0) { 57 | /* move this process to own process group to avoid interfere with Process Monitor */ 58 | THROW_LAST_ERROR_IF(setpgid(0, 0) < 0); 59 | THROW_LAST_ERROR_IF(execvp(argv[0], const_cast(argv.data())) < 0); 60 | } else if (childPid > 0) { 61 | /* move child to own process group to avoid interfere with Process Monitor */ 62 | THROW_LAST_ERROR_IF(setpgid(childPid, childPid) < 0); 63 | } 64 | } 65 | CATCH_LOG(); 66 | 67 | // Ensure that the child process exits. 68 | if (childPid == 0) { 69 | _exit(1); 70 | } 71 | 72 | if (childPid > 0) try { 73 | int status; 74 | THROW_LAST_ERROR_IF((waitPid = waitpid(childPid, &status, 0)) < 0); 75 | if (WIFEXITED(status)) { 76 | success = (WEXITSTATUS(status) == 0); 77 | } 78 | } 79 | CATCH_LOG(); 80 | 81 | try { 82 | for (std::vector::iterator it = argv.begin(); *it != nullptr; it++) { 83 | cmd += *it; 84 | cmd += " "; 85 | } 86 | LOG_INFO("FontMonitor: pid:%d exited with %s, %s", 87 | waitPid, success ? "success" : "fail", cmd.c_str()); 88 | } 89 | CATCH_LOG(); 90 | 91 | return success; 92 | } 93 | 94 | void wslgd::FontFolder::ModifyX11FontPath(bool isAdd) 95 | { 96 | std::vector argv; 97 | sleep(2); /* workaround for optional fonts.alias, wait 2 sec before invoking xset */ 98 | if (m_isPathAdded != isAdd) try { 99 | argv.push_back(c_xset); 100 | argv.push_back(isAdd ? "+fp" : "-fp"); 101 | argv.push_back(m_path.c_str()); 102 | argv.push_back(nullptr); 103 | if (ExecuteShellCommand(std::move(argv))) { 104 | m_isPathAdded = isAdd; 105 | /* let X server reread font database */ 106 | argv.clear(); 107 | argv.push_back(c_xset); 108 | argv.push_back("fp"); 109 | argv.push_back("rehash"); 110 | argv.push_back(nullptr); 111 | ExecuteShellCommand(std::move(argv)); 112 | } 113 | } 114 | CATCH_LOG(); 115 | } 116 | 117 | wslgd::FontMonitor::FontMonitor() 118 | { 119 | } 120 | 121 | void wslgd::FontMonitor::DumpMonitorFolders() 122 | { 123 | try { 124 | std::map>::iterator it; 125 | for (it = m_fontMonitorFolders.begin(); it != m_fontMonitorFolders.end(); it++) 126 | LOG_INFO("FontMonitor: monitoring %s, and it is %s to X11 font path", it->first.c_str(), 127 | it->second->IsPathAdded() ? "added" : "*not* added"); 128 | } 129 | CATCH_LOG(); 130 | } 131 | 132 | void wslgd::FontMonitor::AddMonitorFolder(const char *path) 133 | { 134 | try { 135 | std::string monitorPath(path); 136 | // checkf if path is tracked already. 137 | if (m_fontMonitorFolders.find(monitorPath) == m_fontMonitorFolders.end()) { 138 | std::unique_ptr fontFolder(new FontFolder(m_fd.get(), path)); 139 | if (fontFolder.get()->GetWd() >= 0) { 140 | m_fontMonitorFolders.insert(std::make_pair(std::move(monitorPath), std::move(fontFolder))); 141 | // If this is mount path, only track under X11 folder if it's already exist. 142 | if (strcmp(path, USER_DISTRO_FONT_PATH) == 0) { 143 | if (std::filesystem::exists(USER_DISTRO_FONT_PATH "/X11")) { 144 | AddMonitorFolder(USER_DISTRO_FONT_PATH "/X11"); 145 | } 146 | } else { 147 | // Otherwise, add all existing subfolders to track. 148 | for (auto& dir_entry : std::filesystem::directory_iterator{path}) { 149 | if (dir_entry.is_directory()) { 150 | AddMonitorFolder(dir_entry.path().c_str()); 151 | } 152 | } 153 | } 154 | } 155 | } else { 156 | LOG_INFO("FontMonitor: %s is already tracked", path); 157 | } 158 | } 159 | CATCH_LOG(); 160 | } 161 | 162 | void wslgd::FontMonitor::RemoveMonitorFolder(const char *path) 163 | { 164 | LOG_INFO("FontMonitor: removing monitoring %s", path); 165 | 166 | try { 167 | std::string monitorPath(path); 168 | m_fontMonitorFolders.erase(monitorPath); 169 | } 170 | CATCH_LOG(); 171 | } 172 | 173 | void wslgd::FontMonitor::HandleFolderEvent(struct inotify_event *event) 174 | { 175 | try { 176 | std::map>::iterator it; 177 | for (it = m_fontMonitorFolders.begin(); it != m_fontMonitorFolders.end(); it++) { 178 | if (event->wd == it->second->GetWd()) { 179 | if (event->mask & (IN_CREATE|IN_MOVED_TO)) { 180 | bool addMonitorFolder = true; 181 | std::filesystem::path fullPath(it->second->GetPath()); 182 | if (fullPath.compare(USER_DISTRO_FONT_PATH) == 0) { 183 | /* Immediately under mount folder, only monitor "X11" and its subfolder */ 184 | addMonitorFolder = (strcmp(event->name, "X11") == 0); 185 | } 186 | if (addMonitorFolder) { 187 | fullPath /= event->name; 188 | AddMonitorFolder(fullPath.c_str()); 189 | } 190 | } else if (event->mask & (IN_DELETE|IN_MOVED_FROM)) { 191 | std::filesystem::path fullPath(it->second->GetPath()); 192 | fullPath /= event->name; 193 | RemoveMonitorFolder(fullPath.c_str()); 194 | } 195 | break; 196 | } 197 | } 198 | } 199 | CATCH_LOG(); 200 | } 201 | 202 | void wslgd::FontMonitor::HandleFontsDirEvent(struct inotify_event *event) 203 | { 204 | try { 205 | std::map>::iterator it; 206 | for (it = m_fontMonitorFolders.begin(); it != m_fontMonitorFolders.end(); it++) { 207 | if (event->wd == it->second->GetWd()) { 208 | if (event->mask & (IN_CREATE|IN_CLOSE_WRITE|IN_MOVED_TO)) { 209 | std::filesystem::path fonts_dir(it->second->GetPath()); 210 | fonts_dir /= event->name; 211 | THROW_LAST_ERROR_IF(access(fonts_dir.c_str(), R_OK) != 0); 212 | it->second->ModifyX11FontPath(true); 213 | } else if (event->mask & (IN_DELETE|IN_MOVED_FROM)) { 214 | it->second->ModifyX11FontPath(false); 215 | } 216 | break; 217 | } 218 | } 219 | } 220 | CATCH_LOG(); 221 | } 222 | 223 | void* wslgd::FontMonitor::FontMonitorThread(void *context) 224 | { 225 | FontMonitor *This = reinterpret_cast(context); 226 | struct inotify_event *event; 227 | int len, cur; 228 | char buf[10 * (sizeof *event + 256)]; 229 | 230 | LOG_INFO("FontMonitor: monitoring thread started."); 231 | 232 | pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); 233 | pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); 234 | 235 | // Dump currently tracking folders. 236 | This->DumpMonitorFolders(); 237 | 238 | // Start listening folder add/remove. 239 | for (;;) { 240 | len = read(This->GetFd(), buf, sizeof buf); 241 | cur = 0; 242 | while (cur < len) { 243 | event = (struct inotify_event *)&buf[cur]; 244 | if (event->len) { 245 | if (event->mask & IN_ISDIR) { 246 | // A directory is added or removed. 247 | This->HandleFolderEvent(event); 248 | } else if (strcmp(event->name, c_fontsdir) == 0) { 249 | // A fonts.dir is added or removed. 250 | This->HandleFontsDirEvent(event); 251 | } 252 | } 253 | cur += (sizeof *event + event->len); 254 | } 255 | } 256 | // never hit here. 257 | assert(true); 258 | 259 | return 0; 260 | } 261 | 262 | int wslgd::FontMonitor::Start() 263 | { 264 | bool succeeded = false; 265 | 266 | assert(m_fontMonitorFolders.empty()); 267 | assert(!m_fontMonitorThread); 268 | 269 | try { 270 | // xset must be installed. 271 | THROW_LAST_ERROR_IF(access(c_xset, X_OK) < 0); 272 | 273 | // if user distro mount folder does not exist, bail out. 274 | THROW_LAST_ERROR_IF_FALSE(std::filesystem::exists(USER_DISTRO_MOUNT_PATH)); 275 | 276 | bool userDistroFontPathExists = std::filesystem::exists(USER_DISTRO_FONT_PATH); 277 | bool altDistroFontPathExists = std::filesystem::exists(ALT_DISTRO_FONT_PATH); 278 | 279 | // and check fonts path inside user distro. 280 | THROW_LAST_ERROR_IF_FALSE(userDistroFontPathExists || altDistroFontPathExists); 281 | 282 | // start monitoring on mounted font folder. 283 | wil::unique_fd fd(inotify_init()); 284 | THROW_LAST_ERROR_IF(!fd); 285 | m_fd.reset(fd.release()); 286 | 287 | // add both the default and alternative font paths if they exist. 288 | if (userDistroFontPathExists) { 289 | AddMonitorFolder(USER_DISTRO_FONT_PATH); 290 | } 291 | if (altDistroFontPathExists) { 292 | AddMonitorFolder(ALT_DISTRO_FONT_PATH); 293 | } 294 | 295 | // Create font folder monitor thread. 296 | THROW_LAST_ERROR_IF(pthread_create(&m_fontMonitorThread, NULL, FontMonitorThread, (void*)this) < 0); 297 | 298 | succeeded = true; 299 | } 300 | CATCH_LOG(); 301 | 302 | if (!succeeded) { 303 | Stop(); 304 | return -1; 305 | } 306 | 307 | return 0; 308 | } 309 | 310 | void wslgd::FontMonitor::Stop() 311 | { 312 | // Stop font folder monitor thread. 313 | if (m_fontMonitorThread) { 314 | pthread_cancel(m_fontMonitorThread); 315 | pthread_join(m_fontMonitorThread, NULL); 316 | m_fontMonitorThread = 0; 317 | } 318 | 319 | // Remove both the default and alternative font paths if they were added. 320 | if (m_fontMonitorFolders.find(USER_DISTRO_FONT_PATH) != m_fontMonitorFolders.end()) { 321 | RemoveMonitorFolder(USER_DISTRO_FONT_PATH); 322 | } 323 | if (m_fontMonitorFolders.find(ALT_DISTRO_FONT_PATH) != m_fontMonitorFolders.end()) { 324 | RemoveMonitorFolder(ALT_DISTRO_FONT_PATH); 325 | } 326 | 327 | m_fontMonitorFolders.clear(); 328 | m_fd.reset(); 329 | 330 | LOG_INFO("FontMonitor: monitoring stopped."); 331 | } 332 | -------------------------------------------------------------------------------- /WSLGd/FontMonitor.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | #include "precomp.h" 5 | 6 | namespace wslgd 7 | { 8 | class FontFolder 9 | { 10 | public: 11 | FontFolder(int fd, const char *path); 12 | ~FontFolder(); 13 | 14 | void ModifyX11FontPath(bool add); 15 | 16 | static bool ExecuteShellCommand(std::vector&& argv); 17 | 18 | int GetFd() const { return m_fd.get(); } 19 | int GetWd() const { return m_wd; } 20 | 21 | bool IsPathAdded() const { return m_isPathAdded; } 22 | const char *GetPath() const { return m_path.c_str(); } 23 | 24 | private: 25 | wil::unique_fd m_fd; /* from FontMonitor's inotify_init() */ 26 | int m_wd = -1; /* from inotify_add_watch() for this folder */ 27 | std::string m_path; /* this folder path */ 28 | bool m_isPathAdded = false; /* whether font path is added to X11 font path */ 29 | }; 30 | 31 | class FontMonitor 32 | { 33 | public: 34 | FontMonitor(); 35 | ~FontMonitor() { Stop(); } 36 | 37 | FontMonitor(const FontMonitor&) = delete; 38 | void operator=(const FontMonitor&) = delete; 39 | 40 | int Start(); 41 | void Stop(); 42 | 43 | static void* FontMonitorThread(void *context); 44 | 45 | void AddMonitorFolder(const char *path); 46 | void RemoveMonitorFolder(const char *path); 47 | void DumpMonitorFolders(); 48 | 49 | void HandleFolderEvent(struct inotify_event *event); 50 | void HandleFontsDirEvent(struct inotify_event *event); 51 | 52 | int GetFd() const { return m_fd.get(); } 53 | 54 | private: 55 | wil::unique_fd m_fd; /* from inotify_init() */ 56 | std::map> m_fontMonitorFolders{}; 57 | pthread_t m_fontMonitorThread = 0; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /WSLGd/Makefile: -------------------------------------------------------------------------------- 1 | CXX := clang++ 2 | LDFLAGS := -lcap 3 | TARGET := WSLGd 4 | SRC_DIRS := . 5 | INSTALL := install -p 6 | INSTALL_PREFIX= $(DESTDIR)/$(PREFIX)/bin 7 | 8 | SRCS := $(shell find $(SRC_DIRS) -name "*.cpp" -or -name "*.c" -or -name "*.s") 9 | OBJS := $(addsuffix .o,$(basename $(SRCS))) 10 | DEPS := $(OBJS:.o=.d) 11 | 12 | INC_DIRS := $(shell find $(SRC_DIRS) -type d) 13 | INC_FLAGS := $(addprefix -I,$(INC_DIRS)) 14 | 15 | CPPFLAGS := $(INC_FLAGS) -MMD -MP -std=c++17 16 | 17 | $(TARGET): $(OBJS) 18 | $(CXX) $(LDFLAGS) $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS) 19 | 20 | .PHONY: clean 21 | clean: 22 | $(RM) $(TARGET) $(OBJS) $(DEPS) 23 | 24 | install: 25 | $(INSTALL) $(TARGET) $(INSTALL_PREFIX) 26 | 27 | -include $(DEPS) -------------------------------------------------------------------------------- /WSLGd/ProcessMonitor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include "ProcessMonitor.h" 4 | #include "common.h" 5 | 6 | wslgd::ProcessMonitor::ProcessMonitor(const char* userName) 7 | { 8 | THROW_ERRNO_IF(ENOENT, !(m_user = getpwnam(userName))); 9 | } 10 | 11 | passwd* wslgd::ProcessMonitor::GetUserInfo() const 12 | { 13 | return m_user; 14 | } 15 | 16 | extern char **environ; 17 | 18 | int wslgd::ProcessMonitor::LaunchProcess( 19 | std::vector&& argv, 20 | std::vector&& capabilities, 21 | std::vector&& env) 22 | { 23 | int childPid; 24 | THROW_LAST_ERROR_IF((childPid = fork()) < 0); 25 | 26 | if (childPid == 0) try { 27 | // Construct a null-terminated argument array. 28 | std::vector arguments; 29 | for (auto &arg : argv) { 30 | arguments.push_back(arg.c_str()); 31 | } 32 | 33 | arguments.push_back(nullptr); 34 | 35 | // If any capabilities were specified, set the keepcaps flag so they are not lost across setuid. 36 | if (!capabilities.empty()) { 37 | THROW_LAST_ERROR_IF(prctl(PR_SET_KEEPCAPS, 1) < 0); 38 | } 39 | 40 | // Construct a null-terminated environment array. 41 | std::vector environments; 42 | for (char **c = environ; *c; c++) { 43 | environments.push_back(*c); 44 | } 45 | for (auto &s : env) { 46 | if (s.size()) { 47 | environments.push_back(s.c_str()); 48 | } 49 | } 50 | 51 | environments.push_back(nullptr); 52 | 53 | // Set user settings. 54 | THROW_LAST_ERROR_IF(setgid(m_user->pw_gid) < 0); 55 | THROW_LAST_ERROR_IF(initgroups(m_user->pw_name, m_user->pw_gid) < 0); 56 | THROW_LAST_ERROR_IF(setuid(m_user->pw_uid) < 0); 57 | THROW_LAST_ERROR_IF(chdir(m_user->pw_dir) < 0); 58 | 59 | // Apply additional capabilities to the process. 60 | if (!capabilities.empty()) { 61 | cap_t caps{}; 62 | THROW_LAST_ERROR_IF((caps = cap_get_proc()) == NULL); 63 | auto freeCapabilities = wil::scope_exit([&caps]() { cap_free(caps); }); 64 | THROW_LAST_ERROR_IF(cap_set_flag(caps, CAP_PERMITTED, capabilities.size(), capabilities.data(), CAP_SET) < 0); 65 | THROW_LAST_ERROR_IF(cap_set_flag(caps, CAP_EFFECTIVE, capabilities.size(), capabilities.data(), CAP_SET) < 0); 66 | THROW_LAST_ERROR_IF(cap_set_flag(caps, CAP_INHERITABLE, capabilities.size(), capabilities.data(), CAP_SET) < 0); 67 | THROW_LAST_ERROR_IF(cap_set_proc(caps) < 0); 68 | for (auto &cap : capabilities) { 69 | THROW_LAST_ERROR_IF(cap_set_ambient(cap, CAP_SET) < 0); 70 | } 71 | } 72 | 73 | // Run the process as the specified user. 74 | THROW_LAST_ERROR_IF(execvpe(arguments[0], const_cast(arguments.data()), const_cast(environments.data())) < 0); 75 | } 76 | CATCH_LOG(); 77 | 78 | // Ensure that the child process exits. 79 | if (childPid == 0) { 80 | _exit(1); 81 | } 82 | 83 | m_children[childPid] = ProcessInfo{std::move(argv), std::move(capabilities), std::move(env)}; 84 | return childPid; 85 | } 86 | 87 | int wslgd::ProcessMonitor::Run() try { 88 | std::map> crashes; 89 | 90 | for (;;) { 91 | // Reap any zombie child processes and re-launch any tracked processes. 92 | int pid; 93 | int status; 94 | 95 | /* monitor only processes within same group as caller */ 96 | THROW_LAST_ERROR_IF((pid = waitpid(0, &status, 0)) <= 0); 97 | 98 | auto found = m_children.find(pid); 99 | if (found != m_children.end()) { 100 | if (!found->second.argv.empty()) { 101 | std::string cmd; 102 | for (auto &arg : found->second.argv) { 103 | cmd += arg.c_str(); 104 | cmd += " "; 105 | } 106 | 107 | if (WIFEXITED(status)) { 108 | LOG_INFO("pid %d exited with status %d, %s", pid, WEXITSTATUS(status), cmd.c_str()); 109 | } else if (WIFSIGNALED(status)) { 110 | LOG_INFO("pid %d terminated with signal %d, %s", pid, WTERMSIG(status), cmd.c_str()); 111 | } else { 112 | LOG_ERROR("pid %d return unknown status %d, %s", pid, status, cmd.c_str()); 113 | } 114 | 115 | auto& crashTimestamps = crashes[cmd]; 116 | auto now = time(nullptr); 117 | crashTimestamps.erase(std::remove_if(crashTimestamps.begin(), crashTimestamps.end(), [&](auto ts) { return ts < now - 60; }), crashTimestamps.end()); 118 | crashTimestamps.emplace_back(now); 119 | 120 | if (crashTimestamps.size() > 10) { 121 | LOG_INFO("%s exited more than 10 times in 60 seconds, not starting it again", cmd.c_str()); 122 | } else { 123 | LaunchProcess(std::move(found->second.argv), std::move(found->second.capabilities), std::move(found->second.env)); 124 | } 125 | } 126 | 127 | m_children.erase(found); 128 | 129 | } else { 130 | LOG_INFO("untracked pid %d exited with status 0x%x.", pid, status); 131 | } 132 | } 133 | 134 | return 0; 135 | } 136 | CATCH_RETURN_ERRNO(); 137 | -------------------------------------------------------------------------------- /WSLGd/ProcessMonitor.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | #include "precomp.h" 5 | 6 | namespace wslgd 7 | { 8 | class ProcessMonitor 9 | { 10 | public: 11 | ProcessMonitor(const char* username); 12 | ProcessMonitor(const ProcessMonitor&) = delete; 13 | void operator=(const ProcessMonitor&) = delete; 14 | 15 | passwd* GetUserInfo() const; 16 | int LaunchProcess(std::vector&& argv, 17 | std::vector&& capabilities = {}, 18 | std::vector&& env = {}); 19 | int Run(); 20 | 21 | private: 22 | struct ProcessInfo 23 | { 24 | std::vector argv; 25 | std::vector capabilities; 26 | std::vector env; 27 | }; 28 | 29 | std::map m_children{}; 30 | passwd* m_user; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /WSLGd/common.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | #define SHARE_PATH "/mnt/wslg" 5 | #define USER_DISTRO_MOUNT_PATH SHARE_PATH "/distro" 6 | 7 | void LogPrint(int level, const char *func, int line, const char *fmt, ...) noexcept; 8 | #define LOG_LEVEL_EXCEPTION 3 9 | #define LOG_LEVEL_ERROR 4 10 | #define LOG_LEVEL_INFO 5 11 | #define LOG_ERROR(fmt, ...) LogPrint(LOG_LEVEL_ERROR, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) 12 | #define LOG_INFO(fmt, ...) LogPrint(LOG_LEVEL_INFO, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) 13 | -------------------------------------------------------------------------------- /WSLGd/lxwil.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #pragma once 4 | 5 | namespace wil 6 | { 7 | 8 | #define FAIL_FAST() raise(SIGABRT); 9 | #define FAIL_FAST_CAUGHT_EXCEPTION() FAIL_FAST() 10 | #define FAIL_FAST_IF(condition) if ((condition)) { FAIL_FAST() } 11 | 12 | typedef void LogFunction(const char *message, const char *exceptionDescription) noexcept; 13 | inline LogFunction *g_LogExceptionCallback{}; 14 | 15 | namespace details 16 | { 17 | struct FailureInfo 18 | { 19 | const char *File; 20 | int Line; 21 | const char *Function; 22 | }; 23 | } 24 | 25 | class ResultException : public std::exception 26 | { 27 | public: 28 | ResultException(int result, details::FailureInfo info) noexcept 29 | : m_Result{ result }, m_Info{ info } 30 | { 31 | } 32 | 33 | ~ResultException() noexcept 34 | { 35 | delete[] m_What; 36 | } 37 | 38 | const char *what() const noexcept override 39 | { 40 | constexpr size_t bufferSize = 4096; 41 | if (m_What == nullptr) 42 | { 43 | m_What = new(std::nothrow) char[bufferSize]{}; 44 | if (m_What == nullptr) 45 | { 46 | return strerror(m_Result); 47 | } 48 | 49 | snprintf(m_What, bufferSize, "%s @%s:%d (%s)\n", strerror(m_Result), m_Info.File, m_Info.Line, m_Info.Function); 50 | } 51 | 52 | return m_What; 53 | } 54 | 55 | int GetErrorCode() const noexcept 56 | { 57 | return m_Result; 58 | } 59 | 60 | private: 61 | mutable char *m_What{}; 62 | int m_Result; 63 | details::FailureInfo m_Info; 64 | }; 65 | 66 | namespace details 67 | { 68 | template 69 | class lambda_call 70 | { 71 | public: 72 | lambda_call(const lambda_call&) = delete; 73 | lambda_call& operator=(const lambda_call&) = delete; 74 | lambda_call& operator=(lambda_call&& other) = delete; 75 | 76 | explicit lambda_call(TLambda&& lambda) noexcept : m_lambda(std::move(lambda)) 77 | { 78 | static_assert(std::is_same::value, "scope_exit lambdas must not have a return value"); 79 | static_assert(!std::is_lvalue_reference::value && !std::is_rvalue_reference::value, 80 | "scope_exit should only be directly used with a lambda"); 81 | } 82 | 83 | lambda_call(lambda_call&& other) noexcept : m_lambda(std::move(other.m_lambda)), m_call(other.m_call) 84 | { 85 | other.m_call = false; 86 | } 87 | 88 | ~lambda_call() noexcept 89 | { 90 | reset(); 91 | } 92 | 93 | // Ensures the scope_exit lambda will not be called 94 | void release() noexcept 95 | { 96 | m_call = false; 97 | } 98 | 99 | // Executes the scope_exit lambda immediately if not yet run; ensures it will not run again 100 | void reset() noexcept 101 | { 102 | if (m_call) 103 | { 104 | m_call = false; 105 | m_lambda(); 106 | } 107 | } 108 | 109 | // Returns true if the scope_exit lambda is still going to be executed 110 | explicit operator bool() const noexcept 111 | { 112 | return m_call; 113 | } 114 | 115 | protected: 116 | TLambda m_lambda; 117 | bool m_call = true; 118 | }; 119 | 120 | inline void ThrowErrorIf(bool condition, int error, FailureInfo info) 121 | { 122 | if (condition) 123 | { 124 | throw ::wil::ResultException(error, info); 125 | } 126 | } 127 | 128 | inline void LogFailure(const char *message, const char *exceptionDescription) noexcept 129 | { 130 | auto callback = g_LogExceptionCallback; 131 | if (callback != nullptr) 132 | { 133 | callback(message, exceptionDescription); 134 | } 135 | else 136 | { 137 | if (message != nullptr) 138 | { 139 | fputs(message, stderr); 140 | fputs("\n", stderr); 141 | } 142 | 143 | if (exceptionDescription != nullptr) 144 | { 145 | fputs("Exception: ", stderr); 146 | fputs(exceptionDescription, stderr); 147 | fputs("\n", stderr); 148 | } 149 | } 150 | } 151 | 152 | inline void LogCaughtException(const char *message) 153 | { 154 | try 155 | { 156 | throw; 157 | } 158 | catch (const std::exception &ex) 159 | { 160 | LogFailure(message, ex.what()); 161 | } 162 | catch (...) 163 | { 164 | LogFailure(message, nullptr); 165 | } 166 | } 167 | } 168 | 169 | inline int ResultFromCaughtException() 170 | { 171 | try 172 | { 173 | throw; 174 | } 175 | catch (wil::ResultException &ex) 176 | { 177 | return ex.GetErrorCode(); 178 | } 179 | catch (std::bad_alloc &) 180 | { 181 | return ENOMEM; 182 | } 183 | catch (...) 184 | { 185 | } 186 | 187 | // Unknown exception type. 188 | return EINVAL; 189 | } 190 | 191 | #define __WIL_ERROR_INFO { __FILE__, __LINE__, __FUNCTION__ } 192 | 193 | #define THROW_ERRNO(Error) throw ::wil::ResultException(Error, __WIL_ERROR_INFO) 194 | #define THROW_ERRNO_IF(Error, Condition) ::wil::details::ThrowErrorIf((Condition), (Error), __WIL_ERROR_INFO) 195 | #define THROW_ERRNO_IF_FALSE(Error, Condition) ::wil::details::ThrowErrorIf(!(Condition), (Error), __WIL_ERROR_INFO) 196 | #define THROW_LAST_ERROR_IF(Condition) THROW_ERRNO_IF(errno, (Condition)); 197 | #define THROW_LAST_ERROR_IF_FALSE(Condition) THROW_ERRNO_IF_FALSE(errno, (Condition)) 198 | 199 | #define THROW_INVALID() THROW_ERRNO(EINVAL) 200 | #define THROW_INVALID_IF(Condition) THROW_ERRNO_IF(EINVAL, (Condition)) 201 | #define THROW_UNEXPECTED_IF(Condition) THROW_ERRNO_IF(EINVAL, (Condition)) 202 | 203 | #define LOG_CAUGHT_EXCEPTION() ::wil::details::LogCaughtException(nullptr); 204 | #define LOG_CAUGHT_EXCEPTION_MSG(msg) ::wil::details::LogCaughtException(msg); 205 | #define RETURN_CAUGHT_EXCEPTION() return -::wil::ResultFromCaughtException() 206 | #define CATCH_RETURN() catch (...) { RETURN_CAUGHT_EXCEPTION(); } 207 | #define CATCH_RETURN_ERRNO() \ 208 | catch (...) \ 209 | { \ 210 | LOG_CAUGHT_EXCEPTION(); \ 211 | errno = ::wil::ResultFromCaughtException(); \ 212 | return -1; \ 213 | } 214 | 215 | #define CATCH_LOG() catch (...) { LOG_CAUGHT_EXCEPTION(); } 216 | #define CATCH_LOG_MSG(msg) catch (...) { LOG_CAUGHT_EXCEPTION_MSG(msg); } 217 | 218 | class unique_dir 219 | { 220 | public: 221 | static constexpr DIR* invalid_dir = nullptr; 222 | 223 | unique_dir(DIR* dir = invalid_dir) noexcept 224 | : m_Dir{ dir } 225 | { 226 | } 227 | 228 | ~unique_dir() noexcept 229 | { 230 | reset(); 231 | } 232 | 233 | unique_dir(const unique_dir &) = delete; 234 | unique_dir& operator=(const unique_dir &) = delete; 235 | 236 | unique_dir(unique_dir &&other) noexcept 237 | : m_Dir{ other.m_Dir } 238 | { 239 | other.m_Dir = invalid_dir; 240 | } 241 | 242 | unique_dir& operator=(unique_dir &&other) noexcept 243 | { 244 | std::swap(m_Dir, other.m_Dir); 245 | return *this; 246 | } 247 | 248 | explicit operator bool() const noexcept 249 | { 250 | return m_Dir != invalid_dir; 251 | } 252 | 253 | DIR* get() const noexcept 254 | { 255 | return m_Dir; 256 | } 257 | 258 | void reset(DIR *dir = invalid_dir) noexcept 259 | { 260 | if (m_Dir != invalid_dir) 261 | { 262 | closedir(m_Dir); 263 | } 264 | 265 | m_Dir = dir; 266 | } 267 | 268 | DIR* release() noexcept 269 | { 270 | DIR *dir = m_Dir; 271 | m_Dir = invalid_dir; 272 | return dir; 273 | } 274 | 275 | friend void swap(unique_dir &dir1, unique_dir &dir2) 276 | { 277 | std::swap(dir1.m_Dir, dir2.m_Dir); 278 | } 279 | 280 | private: 281 | DIR *m_Dir; 282 | }; 283 | 284 | class unique_fd 285 | { 286 | public: 287 | static constexpr int invalid_fd = -1; 288 | 289 | unique_fd(int fd = invalid_fd) noexcept 290 | : m_Fd{ fd } 291 | { 292 | } 293 | 294 | ~unique_fd() noexcept 295 | { 296 | reset(); 297 | } 298 | 299 | unique_fd(const unique_fd &) = delete; 300 | unique_fd& operator=(const unique_fd &) = delete; 301 | 302 | unique_fd(unique_fd &&other) noexcept 303 | : m_Fd{ other.m_Fd } 304 | { 305 | other.m_Fd = invalid_fd; 306 | } 307 | 308 | unique_fd& operator=(unique_fd &&other) noexcept 309 | { 310 | std::swap(m_Fd, other.m_Fd); 311 | return *this; 312 | } 313 | 314 | explicit operator bool() const noexcept 315 | { 316 | return m_Fd >= 0; 317 | } 318 | 319 | int get() const noexcept 320 | { 321 | return m_Fd; 322 | } 323 | 324 | void reset(int fd = invalid_fd) noexcept 325 | { 326 | if (m_Fd >= 0) 327 | { 328 | close(m_Fd); 329 | } 330 | 331 | m_Fd = fd; 332 | } 333 | 334 | int release() noexcept 335 | { 336 | int fd = m_Fd; 337 | m_Fd = invalid_fd; 338 | return fd; 339 | } 340 | 341 | friend void swap(unique_fd &fd1, unique_fd &fd2) 342 | { 343 | std::swap(fd1.m_Fd, fd2.m_Fd); 344 | } 345 | 346 | private: 347 | int m_Fd; 348 | }; 349 | 350 | class unique_file 351 | { 352 | public: 353 | static constexpr FILE* invalid_file = nullptr; 354 | 355 | unique_file(FILE* file = invalid_file) noexcept 356 | : m_File{ file } 357 | { 358 | } 359 | 360 | ~unique_file() noexcept 361 | { 362 | reset(); 363 | } 364 | 365 | unique_file(const unique_file &) = delete; 366 | unique_file& operator=(const unique_file &) = delete; 367 | 368 | unique_file(unique_file &&other) noexcept 369 | : m_File{ other.m_File } 370 | { 371 | other.m_File = invalid_file; 372 | } 373 | 374 | unique_file& operator=(unique_file &&other) noexcept 375 | { 376 | std::swap(m_File, other.m_File); 377 | return *this; 378 | } 379 | 380 | explicit operator bool() const noexcept 381 | { 382 | return m_File != invalid_file; 383 | } 384 | 385 | FILE* get() const noexcept 386 | { 387 | return m_File; 388 | } 389 | 390 | void reset(FILE *file = invalid_file) noexcept 391 | { 392 | if (m_File != invalid_file) 393 | { 394 | fclose(m_File); 395 | } 396 | 397 | m_File = file; 398 | } 399 | 400 | FILE* release() noexcept 401 | { 402 | FILE *file = m_File; 403 | m_File = invalid_file; 404 | return file; 405 | } 406 | 407 | friend void swap(unique_file &file1, unique_file &file2) 408 | { 409 | std::swap(file1.m_File, file2.m_File); 410 | } 411 | 412 | private: 413 | FILE *m_File; 414 | }; 415 | 416 | /** Returns an object that executes the given lambda when destroyed. 417 | Capture the object with 'auto'; use reset() to execute the lambda early or release() to avoid 418 | execution. Exceptions thrown in the lambda will fail-fast; use scope_exit_log to avoid. */ 419 | template 420 | [[nodiscard]] inline auto scope_exit(TLambda&& lambda) noexcept 421 | { 422 | return details::lambda_call(std::forward(lambda)); 423 | } 424 | 425 | namespace details 426 | { 427 | template 428 | struct verify_single_flag_helper 429 | { 430 | static_assert((flag != 0) && ((flag & (flag - 1)) == 0), "Single flag expected, zero or multiple flags found"); 431 | static const unsigned long long value = flag; 432 | }; 433 | 434 | // Use size-specific casts to avoid sign extending numbers -- avoid warning C4310: cast truncates constant value 435 | #define __WI_MAKE_UNSIGNED(val) \ 436 | (sizeof(val) == 1 ? static_cast(val) : \ 437 | sizeof(val) == 2 ? static_cast(val) : \ 438 | sizeof(val) == 4 ? static_cast(val) : \ 439 | static_cast(val)) 440 | #define __WI_IS_UNSIGNED_SINGLE_FLAG_SET(val) ((val) && !((val) & ((val) - 1))) 441 | #define __WI_IS_SINGLE_FLAG_SET(val) __WI_IS_UNSIGNED_SINGLE_FLAG_SET(__WI_MAKE_UNSIGNED(val)) 442 | 443 | template 444 | inline constexpr bool AreAllFlagsSetHelper(TVal val, TFlags flags) 445 | { 446 | return ((val & flags) == static_cast(flags)); 447 | } 448 | 449 | template 450 | inline constexpr bool IsSingleFlagSetHelper(TVal val) 451 | { 452 | return __WI_IS_SINGLE_FLAG_SET(val); 453 | } 454 | 455 | template 456 | inline constexpr bool IsClearOrSingleFlagSetHelper(TVal val) 457 | { 458 | return ((val == static_cast>(0)) || IsSingleFlagSetHelper(val)); 459 | } 460 | 461 | template 462 | inline constexpr void UpdateFlagsInMaskHelper(TVal& val, TMask mask, TFlags flags) 463 | { 464 | val = static_cast>((val & ~mask) | (flags & mask)); 465 | } 466 | 467 | template 468 | struct variable_size; 469 | 470 | template <> 471 | struct variable_size<1> 472 | { 473 | typedef unsigned char type; 474 | }; 475 | 476 | template <> 477 | struct variable_size<2> 478 | { 479 | typedef unsigned short type; 480 | }; 481 | 482 | template <> 483 | struct variable_size<4> 484 | { 485 | typedef unsigned long type; 486 | }; 487 | 488 | template <> 489 | struct variable_size<8> 490 | { 491 | typedef unsigned long long type; 492 | }; 493 | 494 | template 495 | struct variable_size_mapping 496 | { 497 | typedef typename variable_size::type type; 498 | }; 499 | } 500 | 501 | /** Defines the unsigned type of the same width (1, 2, 4, or 8 bytes) as the given type. 502 | This allows code to generically convert any enum class to it's corresponding underlying type. */ 503 | template 504 | using integral_from_enum = typename details::variable_size_mapping::type; 505 | 506 | 507 | #define WI_StaticAssertSingleBitSet(flag) static_cast(::wil::details::verify_single_flag_helper(WI_EnumValue(flag))>::value) 508 | #define WI_IsAnyFlagSet(val, flags) (static_cast(WI_EnumValue(val) & WI_EnumValue(flags)) != static_cast(0)) 509 | #define WI_IsFlagSet(val, flag) WI_IsAnyFlagSet(val, WI_StaticAssertSingleBitSet(flag)) 510 | #define WI_EnumValue(val) static_cast<::wil::integral_from_enum>(val) 511 | //! Evaluates as true if every bitflag specified in `flags` is set within `val`. 512 | #define WI_AreAllFlagsSet(val, flags) wil::details::AreAllFlagsSetHelper(val, flags) 513 | //! Set zero or more bitflags specified by `flags` in the variable `var`. 514 | #define WI_SetAllFlags(var, flags) ((var) |= (flags)) 515 | //! Set a single compile-time constant `flag` in the variable `var`. 516 | #define WI_SetFlag(var, flag) WI_SetAllFlags(var, WI_StaticAssertSingleBitSet(flag)) 517 | //! Clear zero or more bitflags specified by `flags` from the variable `var`. 518 | #define WI_ClearAllFlags(var, flags) ((var) &= ~(flags)) 519 | //! Clear a single compile-time constant `flag` from the variable `var`. 520 | #define WI_ClearFlag(var, flag) WI_ClearAllFlags(var, WI_StaticAssertSingleBitSet(flag)) 521 | 522 | #define WI_ASSERT(condition) assert(condition) 523 | 524 | } -------------------------------------------------------------------------------- /WSLGd/meson.build: -------------------------------------------------------------------------------- 1 | project('WSLGd', 'cpp', 2 | version : '0.1', 3 | default_options : ['cpp_std=c++17']) 4 | 5 | config_h = configuration_data() 6 | 7 | dep_winpr = dependency('winpr3', version: '>= 3.0.0', required: false) 8 | if dep_winpr.found() 9 | config_h.set('HAVE_WINPR', '1') 10 | else 11 | dep_winpr = dependency('winpr2', version: '>= 2.0.0', required: false) 12 | if dep_winpr.found() 13 | config_h.set('HAVE_WINPR', '1') 14 | endif 15 | endif 16 | 17 | configure_file(output: 'config.h', configuration: config_h) 18 | 19 | executable('WSLGd', 20 | 'main.cpp', 21 | 'ProcessMonitor.cpp', 22 | 'FontMonitor.cpp', 23 | dependencies: dep_winpr, 24 | link_args: '-lcap', 25 | install : true) 26 | -------------------------------------------------------------------------------- /WSLGd/precomp.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "config.h" 36 | #include "lxwil.h" 37 | #if HAVE_WINPR 38 | #include "winpr/ini.h" 39 | #endif // HAVE_WINPR 40 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | resources: 2 | repositories: 3 | - repository: FreeRDP 4 | type: github 5 | endpoint: GitHub connection 1 6 | name: microsoft/FreeRDP-mirror 7 | ref: working 8 | - repository: weston 9 | type: github 10 | endpoint: GitHub connection 1 11 | name: microsoft/weston-mirror 12 | ref: working 13 | - repository: pulseaudio 14 | type: github 15 | endpoint: GitHub connection 1 16 | name: microsoft/pulseaudio-mirror 17 | ref: working 18 | 19 | trigger: 20 | - main 21 | 22 | stages: 23 | - stage: Build_SystemDistro 24 | displayName: "Build System Distro (x64 and ARM64)" 25 | jobs: 26 | - job: 'Build_Ubuntu_x64' 27 | displayName: 'Build x64 system distro' 28 | timeoutInMinutes: 200 29 | 30 | pool: 31 | vmImage: 'ubuntu-latest' 32 | 33 | steps: 34 | - checkout: FreeRDP 35 | - checkout: weston 36 | - checkout: pulseaudio 37 | - checkout: self 38 | 39 | - template: devops/common-linux.yml 40 | 41 | - script: wget https://azurelinuxsrcstorage.blob.core.windows.net/sources/core/mesa-23.1.0.tar.xz && 42 | tar -xvf mesa-23.1.0.tar.xz 43 | displayName: 'Download Mesa from CBL Mariner' 44 | 45 | - script: wget https://github.com/microsoft/DirectX-Headers/archive/refs/tags/v1.608.0.tar.gz && 46 | tar -xvf v1.608.0.tar.gz 47 | displayName: 'Download DirectX-Headers from GitHub' 48 | 49 | - script: mv FreeRDP-mirror/ wslg/vendor/FreeRDP && 50 | mv weston-mirror/ wslg/vendor/weston && 51 | mv pulseaudio-mirror/ wslg/vendor/pulseaudio && 52 | mv mesa-23.1.0/ wslg/vendor/mesa && 53 | mv DirectX-Headers-1.608.0/ wslg/vendor/DirectX-Headers-1.0 54 | displayName: 'Move sub projects (FreeRDP, Weston, PulseAudio, Mesa, DirectX-Headers)' 55 | 56 | - script: docker build -f ./wslg/Dockerfile -t system-distro-x64 57 | ./wslg 58 | --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion` 59 | --build-arg WSLG_ARCH=x86_64 60 | displayName: 'Create System Distro Docker image Mariner-x64' 61 | 62 | - script: docker export `docker create system-distro-x64` > $(Agent.BuildDirectory)/system_x64.tar 63 | displayName: 'Create system_x64.tar' 64 | 65 | - script: docker build -f ./wslg/Dockerfile -t system-distro-dev-x64 66 | ./wslg 67 | --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion` 68 | --build-arg WSLG_ARCH=x86_64 69 | --target dev 70 | displayName: 'Create System Distro Dev Docker image Mariner-x64' 71 | 72 | - script: docker cp `docker create system-distro-dev-x64 /bin/bash`:/work/debuginfo/system-debuginfo.tar.gz $(Agent.BuildDirectory)/system-debuginfo_x64.tar.gz 73 | displayName: 'Copy system-debuginfo_x64.tar.gz' 74 | 75 | - task: Go@0 76 | inputs: 77 | command: 'custom' 78 | customCommand: 'run' 79 | arguments: 'tar2ext4.go -vhd -i $(Agent.BuildDirectory)/system_x64.tar -o $(Agent.BuildDirectory)/system_x64.vhd' 80 | workingDirectory: 'hcsshim/cmd/tar2ext4' 81 | displayName: 'Create system_x64.vhd' 82 | 83 | - task: PublishPipelineArtifact@1 84 | displayName: 'Publish system_x64.vhd artifact' 85 | inputs: 86 | targetPath: $(Agent.BuildDirectory)/system_x64.vhd 87 | artifact: 'system_x64.vhd' 88 | publishLocation: 'pipeline' 89 | 90 | - task: PublishPipelineArtifact@1 91 | displayName: 'Publish system-debuginfo_x64.tar.gz artifact' 92 | inputs: 93 | targetPath: $(Agent.BuildDirectory)/system-debuginfo_x64.tar.gz 94 | artifact: 'system-debuginfo_x64.tar.gz' 95 | publishLocation: 'pipeline' 96 | 97 | - job: 'Build_Windows_x64' 98 | dependsOn: 'Build_Ubuntu_x64' 99 | displayName: 'Build WSLDCV (x64) Plugin' 100 | 101 | pool: 102 | vmImage: 'windows-2019' 103 | demands: 104 | - msbuild 105 | - visualstudio 106 | 107 | steps: 108 | - checkout: self 109 | 110 | - template: devops/common-win.yml 111 | 112 | - task: PowerShell@2 113 | displayName: 'Update WSLDVCPlugin version' 114 | inputs: 115 | targetType: filePath 116 | workingDirectory: './WSLDVCPlugin' 117 | filePath: .\WSLDVCPlugin\UpdateRCVersion.ps1 118 | pwsh: true 119 | 120 | - task: MSBuild@1 121 | displayName: 'Build RDP Plugin (x64)' 122 | inputs: 123 | solution: './WSLDVCPlugin/WSLDVCPlugin.sln' 124 | platform: 'x64' 125 | configuration: 'Release' 126 | 127 | - task: PublishSymbols@2 128 | displayName: Publish symbols 129 | inputs: 130 | SymbolServerType: 'TeamServices' 131 | TreatNotIndexedAsWarning: true 132 | SymbolsProduct: wslg 133 | SearchPattern: | 134 | WSLDVCPlugin/x64/Release/*.pdb 135 | WSLDVCPlugin/x64/Release/*.dll 136 | 137 | - script: 'MOVE WSLDVCPlugin\x64\Release\WSLDVCPlugin.pdb package\WSLDVCPlugin_x64.pdb' 138 | displayName: 'Move plugin PDB to package (x64)' 139 | 140 | - task: PublishPipelineArtifact@1 141 | displayName: 'Publish WSLDVCPlugin PDB (x64)' 142 | inputs: 143 | targetPath: package\WSLDVCPlugin_x64.pdb 144 | artifact: 'WSLDVCPlugin.x64.pdb' 145 | publishLocation: 'pipeline' 146 | 147 | - script: 'MOVE WSLDVCPlugin\x64\Release\WSLDVCPlugin.dll package\WSLDVCPlugin_x64.dll' 148 | displayName: 'Move plugin DLL to package (x64)' 149 | 150 | - task: PublishPipelineArtifact@1 151 | displayName: 'Publish WSLDVCPlugin DLL (x64)' 152 | inputs: 153 | targetPath: package\WSLDVCPlugin_x64.dll 154 | artifact: 'WSLDVCPlugin.x64.dll' 155 | publishLocation: 'pipeline' 156 | 157 | - job: 'Build_Ubuntu_ARM64' 158 | displayName: 'Build ARM64 system distro' 159 | condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') 160 | timeoutInMinutes: 200 161 | 162 | pool: 163 | vmImage: 'ubuntu-latest' 164 | 165 | steps: 166 | - checkout: FreeRDP 167 | - checkout: weston 168 | - checkout: pulseaudio 169 | - checkout: self 170 | 171 | - template: devops/common-linux.yml 172 | 173 | - bash: | 174 | curl -L -o ~/.docker/cli-plugins/docker-buildx --create-dirs ${BUILDX_URL} 175 | chmod a+x ~/.docker/cli-plugins/docker-buildx 176 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 177 | ~/.docker/cli-plugins/docker-buildx create --use 178 | ~/.docker/cli-plugins/docker-buildx inspect --bootstrap 179 | displayName: Prepare buildx 180 | condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') 181 | env: 182 | BUILDX_URL: https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-amd64 183 | 184 | - script: | 185 | echo '{ "experimental": true }' | sudo tee /etc/docker/daemon.json 186 | sudo service docker restart 187 | displayName: 'Enable Docker Engine experimental ' 188 | 189 | - script: wget https://azurelinuxsrcstorage.blob.core.windows.net/sources/core/mesa-23.1.0.tar.xz && 190 | tar -xvf mesa-23.1.0.tar.xz 191 | displayName: 'Download Mesa from CBL Mariner' 192 | 193 | - script: wget https://github.com/microsoft/DirectX-Headers/archive/refs/tags/v1.608.0.tar.gz && 194 | tar -xvf v1.608.0.tar.gz 195 | displayName: 'Download DirectX-Headers from GitHub' 196 | 197 | - script: mv FreeRDP-mirror/ wslg/vendor/FreeRDP && 198 | mv weston-mirror/ wslg/vendor/weston && 199 | mv pulseaudio-mirror/ wslg/vendor/pulseaudio && 200 | mv mesa-23.1.0/ wslg/vendor/mesa && 201 | mv DirectX-Headers-1.608.0/ wslg/vendor/DirectX-Headers-1.0 202 | displayName: 'Move sub projects (FreeRDP, Weston, PulseAudio, Mesa, DirectX-Headers)' 203 | 204 | - script: ~/.docker/cli-plugins/docker-buildx build -f ./wslg/Dockerfile 205 | --output type=tar,dest=$(Agent.BuildDirectory)/system_arm64.tar 206 | --platform=linux/arm64 207 | ./wslg 208 | --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion` 209 | --build-arg WSLG_ARCH=aarch64 210 | displayName: 'Create system_arm64.tar' 211 | 212 | - task: Go@0 213 | inputs: 214 | command: 'custom' 215 | customCommand: 'run' 216 | arguments: 'tar2ext4.go -vhd -i $(Agent.BuildDirectory)/system_arm64.tar -o $(Agent.BuildDirectory)/system_arm64.vhd' 217 | workingDirectory: 'hcsshim/cmd/tar2ext4' 218 | displayName: 'Create system_arm64.vhd' 219 | 220 | - task: PublishPipelineArtifact@1 221 | displayName: 'Publish system_arm64.vhd artifact' 222 | condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') 223 | inputs: 224 | targetPath: $(Agent.BuildDirectory)/system_arm64.vhd 225 | artifact: 'system_arm64.vhd' 226 | publishLocation: 'pipeline' 227 | 228 | - script: mkdir ./dev && 229 | ~/.docker/cli-plugins/docker-buildx build -f ./wslg/Dockerfile 230 | --output type=tar,dest=./dev/dev_build.tar 231 | --target=dev 232 | --platform=linux/arm64 233 | ./wslg 234 | --build-arg WSLG_VERSION=`gitversion /targetpath ./wslg /showvariable InformationalVersion` 235 | --build-arg WSLG_ARCH=aarch64 && 236 | tar -xvf ./dev/dev_build.tar -C ./dev/ && 237 | mv ./dev/work/debuginfo/system-debuginfo.tar.gz $(Agent.BuildDirectory)/system-debuginfo_arm64.tar.gz 238 | displayName: 'Copy system-debuginfo_arm64.tar.gz' 239 | 240 | - task: PublishPipelineArtifact@1 241 | displayName: 'Publish system-debuginfo_arm64.tar.gz artifact' 242 | inputs: 243 | targetPath: $(Agent.BuildDirectory)/system-debuginfo_arm64.tar.gz 244 | artifact: 'system-debuginfo_arm64.tar.gz' 245 | publishLocation: 'pipeline' 246 | 247 | - job: 'Build_Windows_ARM64' 248 | dependsOn: 'Build_Ubuntu_ARM64' 249 | displayName: 'Build WSLDCV (ARM64) Plugin' 250 | condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') 251 | 252 | pool: 253 | vmImage: 'windows-2019' 254 | demands: 255 | - msbuild 256 | - visualstudio 257 | 258 | steps: 259 | - checkout: self 260 | 261 | - template: devops/common-win.yml 262 | 263 | - task: PowerShell@2 264 | displayName: 'Update WSLDVCPlugin version' 265 | inputs: 266 | targetType: filePath 267 | workingDirectory: './WSLDVCPlugin' 268 | filePath: .\WSLDVCPlugin\UpdateRCVersion.ps1 269 | pwsh: true 270 | 271 | - task: MSBuild@1 272 | displayName: 'Build RDP Plugin (ARM64)' 273 | inputs: 274 | solution: './WSLDVCPlugin/WSLDVCPlugin.sln' 275 | platform: 'ARM64' 276 | configuration: 'Release' 277 | 278 | - task: PublishSymbols@2 279 | displayName: Publish symbols 280 | inputs: 281 | SymbolServerType: 'TeamServices' 282 | TreatNotIndexedAsWarning: true 283 | SymbolsProduct: wslg 284 | SearchPattern: | 285 | WSLDVCPlugin/arm64/Release/*.pdb 286 | WSLDVCPlugin/arm64/Release/*.dll 287 | 288 | - script: 'MOVE WSLDVCPlugin\ARM64\Release\WSLDVCPlugin.pdb package\WSLDVCPlugin_ARM64.pdb' 289 | displayName: 'Move plugin PDB to package (ARM64)' 290 | 291 | - task: PublishPipelineArtifact@1 292 | displayName: 'Publish WSLDVCPlugin PDB (ARM64)' 293 | inputs: 294 | targetPath: package\WSLDVCPlugin_ARM64.pdb 295 | artifact: 'WSLDVCPlugin.ARM64.pdb' 296 | publishLocation: 'pipeline' 297 | 298 | - script: 'MOVE WSLDVCPlugin\ARM64\Release\WSLDVCPlugin.dll package\WSLDVCPlugin_ARM64.dll' 299 | displayName: 'Move plugin to package (ARM64)' 300 | 301 | - task: PublishPipelineArtifact@1 302 | displayName: 'Publish WSLDVCPlugin DLL (ARM64)' 303 | inputs: 304 | targetPath: package\WSLDVCPlugin_ARM64.dll 305 | artifact: 'WSLDVCPlugin.ARM64.dll' 306 | publishLocation: 'pipeline' 307 | 308 | - stage: PublishPackage 309 | displayName: "Publish WSLg NuGet Package" 310 | jobs: 311 | - job: 'Publish_UniversalPackage' 312 | condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') 313 | displayName: 'Download artifact and push NuGet package' 314 | 315 | pool: 316 | vmImage: 'windows-2019' 317 | 318 | steps: 319 | 320 | - task: DownloadPipelineArtifact@2 321 | displayName: 'Download system_x64.vhd' 322 | inputs: 323 | buildType: 'current' 324 | artifactName: 'system_x64.vhd' 325 | targetPath: 'package/' 326 | 327 | - task: DownloadPipelineArtifact@2 328 | displayName: 'Download system-debuginfo_x64.tar.gz' 329 | inputs: 330 | buildType: 'current' 331 | artifactName: 'system-debuginfo_x64.tar.gz' 332 | targetPath: 'package/' 333 | 334 | - task: DownloadPipelineArtifact@2 335 | displayName: 'Download WSLDVCPlugin DLL (x64)' 336 | inputs: 337 | buildType: 'current' 338 | artifactName: 'WSLDVCPlugin.x64.dll' 339 | targetPath: 'package/' 340 | 341 | - task: DownloadPipelineArtifact@2 342 | displayName: 'Download WSLDVCPlugin PDB (x64)' 343 | inputs: 344 | buildType: 'current' 345 | artifactName: 'WSLDVCPlugin.x64.pdb' 346 | targetPath: 'package/' 347 | 348 | - task: DownloadPipelineArtifact@2 349 | displayName: 'Download system_arm64.vhd' 350 | inputs: 351 | buildType: 'current' 352 | artifactName: 'system_arm64.vhd' 353 | targetPath: 'package/' 354 | 355 | - task: DownloadPipelineArtifact@2 356 | displayName: 'Download system-debuginfo_arm64.tar.gz' 357 | inputs: 358 | buildType: 'current' 359 | artifactName: 'system-debuginfo_arm64.tar.gz' 360 | targetPath: 'package/' 361 | 362 | - task: DownloadPipelineArtifact@2 363 | displayName: 'Download WSLDVCPlugin DLL (ARM64)' 364 | inputs: 365 | buildType: 'current' 366 | artifactName: 'WSLDVCPlugin.ARM64.dll' 367 | targetPath: 'package/' 368 | 369 | - task: DownloadPipelineArtifact@2 370 | displayName: 'Download WSLDVCPlugin PDB (ARM64)' 371 | inputs: 372 | buildType: 'current' 373 | artifactName: 'WSLDVCPlugin.ARM64.pdb' 374 | targetPath: 'package/' 375 | 376 | - task: PowerShell@2 377 | displayName: 'Update Microsoft.WSLg.nuspec version' 378 | inputs: 379 | targetType: filePath 380 | filePath: .\devops\updateversion.ps1 381 | arguments: .\Microsoft.WSLg.nuspec "package.metadata.version" "" "-beta" 382 | pwsh: true 383 | 384 | - task: PowerShell@2 385 | displayName: 'Update Microsoft.WSLg.nuspec release notes' 386 | inputs: 387 | targetType: filePath 388 | filePath: .\devops\updateversion.ps1 389 | arguments: .\Microsoft.WSLg.nuspec "package.metadata.releaseNotes" "" "-beta" "hash" 390 | pwsh: true 391 | 392 | - script: 'nuget pack .\Microsoft.WSLg.nuspec' 393 | displayName: 'Package NuGet' 394 | 395 | - script: 'rename *.nupkg Microsoft.WSLg.nupkg' 396 | displayName: 'Rename Nuget Package' 397 | 398 | - task: PublishPipelineArtifact@1 399 | displayName: 'Save Microsoft.WSLg.nupkg artifact' 400 | inputs: 401 | targetPath: Microsoft.WSLg.nupkg 402 | artifact: 'Microsoft.WSLg.nupkg' 403 | publishLocation: 'pipeline' 404 | 405 | - task: '333b11bd-d341-40d9-afcf-b32d5ce6f23b@2' 406 | condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') 407 | inputs: 408 | command: 'push' 409 | packagesToPush: 'Microsoft.WSLg.nupkg' 410 | nuGetFeedType: 'internal' 411 | publishVstsFeed: 'wsl' 412 | allowPackageConflicts: true 413 | verbosityPush: 'Normal' 414 | -------------------------------------------------------------------------------- /cgmanifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "Registrations": [ 3 | { 4 | "Component": { 5 | "Type": "git", 6 | "Git": { 7 | "RepositoryUrl": "https://github.com/pulseaudio/pulseaudio", 8 | "CommitHash": "0e691b96640919b1c7ed91ae9240761c5775deeb" 9 | } 10 | }, 11 | "DevelopmentDependency": false 12 | }, 13 | { 14 | "Component": { 15 | "Type": "git", 16 | "Git": { 17 | "RepositoryUrl": "https://github.com/wayland-project/weston", 18 | "CommitHash": "04d3ae265d8d8f84352c8dac21ec40b2fe07e7d2" 19 | } 20 | }, 21 | "DevelopmentDependency": false 22 | }, 23 | { 24 | "Component": { 25 | "Type": "git", 26 | "Git": { 27 | "RepositoryUrl": "https://github.com/FreeRDP/FreeRDP", 28 | "CommitHash": "39f56443f2dc50e0dcfa52d4f8f15008d5b8ed8e" 29 | } 30 | }, 31 | "DevelopmentDependency": false 32 | }, 33 | { 34 | "Component": { 35 | "Type": "mesa", 36 | "Git": { 37 | "RepositoryUrl": "https://github.com/mesa3d/mesa", 38 | "CommitHash": "731ea06758663a2de3a2bd1f12eb8809d4c136fd" 39 | } 40 | }, 41 | "DevelopmentDependency": false 42 | }, 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /config/BUILD.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This repository contains a Dockerfile and supporting tools to build the WSL GUI system distro image. 4 | 5 | ## Quick start 6 | 7 | For self-hosting WSLG check use this instructions https://github.com/microsoft/wslg/wiki#installing-self-hosting 8 | 9 | ## Setup and build WSLG and System Distro 10 | 11 | 0. Install and start Docker in a Linux or WSL2 environment. 12 | 13 | 1. Clone the FreeRDP ,Weston and PulseAudio side by side this repo repositories and checkout the "working" branch from each: 14 | 15 | ```bash 16 | git clone https://github.com/microsoft/FreeRDP-mirror vendor/FreeRDP -b working 17 | 18 | git clone https://github.com/microsoft/weston-mirror.git vendor/weston -b working 19 | 20 | git clone https://github.com/microsoft/pulseaudio-mirror.git vendor/pulseaudio -b working 21 | ``` 22 | 23 | 2. Download the mesa and directx headers code. 24 | 25 | ``` 26 | wget https://cblmarinerstorage.blob.core.windows.net/sources/core/mesa-23.1.0.tar.xz 27 | tar -xf mesa-23.1.0.tar.xz -C vendor 28 | mv vendor/mesa-23.1.0 vendor/mesa 29 | 30 | wget https://github.com/microsoft/DirectX-Headers/archive/refs/tags/v1.608.0.tar.gz 31 | tar -xvf v1.608.0.tar.gz -C vendor 32 | mv vendor/DirectX-Headers-1.608.0 vendor/DirectX-Headers-1.0 33 | ``` 34 | 35 | 3. Create the VHD: 36 | 37 | 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 38 | ``` 39 | git clone --branch v0.8.9 --single-branch https://github.com/microsoft/hcsshim.git 40 | ``` 41 | 42 | 3.1 From the parent directory build and export the docker image: 43 | ``` 44 | 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 45 | sudo docker export `sudo docker create system-distro-x64` > system_x64.tar 46 | ``` 47 | 48 | 3.3 Create the system distro vhd using `tar2ext4` 49 | 50 | ```bash 51 | cd hcsshim/cmd/tar2ext4 52 | go run tar2ext4.go -vhd -i ../../../system_x64.tar -o ../../../system.vhd 53 | ``` 54 | 55 | This will create system distro image `system.vhd` 56 | 57 | 4. Change the system distro: 58 | 59 | 4.1 Before replace the system distro you will need to shutdown WSL 60 | 61 | ``` 62 | wsl --shutdown 63 | ``` 64 | 65 | 4.2 By default the system distro is located at `C:\ProgramData\Microsoft\WSL\system.vhd` 66 | 67 | If you want to use the system distro from a different path you can change the .wslconfig. 68 | 69 | * Add an entry to your `%USERPROFILE%\.wslconfig` 70 | 71 | ``` 72 | [wsl2] 73 | systemDistro=C:\\tmp\\system.vhd 74 | ``` 75 | 76 | 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. 77 | 78 | 79 | 5. Inspecting the system distro: 80 | 81 | If the system distro isn't working correctly or you need to inspect what is running inside the system distro you can do: 82 | 83 | ``` 84 | wsl --system [DistroName] 85 | ``` 86 | 87 | 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` 88 | You should see something like this: 89 | 90 | ```bash 91 | root@DESKTOP-7LJ03SK:/mnt/d# ps -ax | grep weston 92 | 11 ? Sl 6:51 /usr/local/bin/weston --backend=rdp-backend.so --xwayland --shell=rdprail-shell.so --log=/mnt/wslg/weston.log 93 | ``` 94 | -------------------------------------------------------------------------------- /config/default_wslg.pa: -------------------------------------------------------------------------------- 1 | 2 | ### WSLG specific ### 3 | ### Load Windows's default sound from Windows volume. 4 | .ifexists /mnt/c/Windows/Media/Windows Default.wav 5 | load-sample x11-bell /mnt/c/Windows/Media/Windows Default.wav 6 | ### Enable X11 bell by loading module-x11-bell. 7 | load-module module-x11-bell sample=x11-bell 8 | .endif 9 | 10 | -------------------------------------------------------------------------------- /config/local.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | /mnt/wslg/distro/usr/share/fonts 6 | /mnt/wslg/distro/usr/local/share/fonts 7 | 8 | -------------------------------------------------------------------------------- /config/weston.ini: -------------------------------------------------------------------------------- 1 | [xwayland] 2 | disable_access_control=true 3 | 4 | [input-method] 5 | path= 6 | -------------------------------------------------------------------------------- /config/wsl.conf: -------------------------------------------------------------------------------- 1 | [boot] 2 | command=/usr/bin/WSLGd 3 | [user] 4 | default=wslg -------------------------------------------------------------------------------- /config/xwayland_log.patch: -------------------------------------------------------------------------------- 1 | diff -Naur xorg-server-1.20.9/hw/xwayland/xwayland.c xorg-server-1.20.9-patch/hw/xwayland/xwayland.c 2 | --- xorg-server-1.20.9/hw/xwayland/xwayland.c 2020-11-21 01:07:32.850000000 +0000 3 | +++ xorg-server-1.20.9-patch/hw/xwayland/xwayland.c 2020-11-21 01:06:20.290000000 +0000 4 | @@ -61,6 +61,9 @@ 5 | void 6 | OsVendorInit(void) 7 | { 8 | + LogInit("/mnt/wslg/xlog.log", ".old"); 9 | + LogSetParameter(XLOG_FILE_VERBOSITY, X_DEBUG); 10 | + 11 | if (serverGeneration == 1) 12 | ForceClockId(CLOCK_MONOTONIC); 13 | } 14 | -------------------------------------------------------------------------------- /debuginfo/FreeRDP2.list: -------------------------------------------------------------------------------- 1 | /usr/bin/winpr-hash 2 | /usr/bin/winpr-makecert 3 | /usr/lib/libfreerdp-server2.so.2.4.0 4 | /usr/lib/libfreerdp2.so.2.4.0 5 | /usr/lib/libuwac0.so.0.1.1 6 | /usr/lib/libwinpr-tools2.so.2.4.0 7 | /usr/lib/libwinpr2.so.2.4.0 8 | -------------------------------------------------------------------------------- /debuginfo/FreeRDP3.list: -------------------------------------------------------------------------------- 1 | /usr/bin/winpr-hash 2 | /usr/bin/winpr-makecert 3 | /usr/lib/libfreerdp-server3.so.3.0.0 4 | /usr/lib/libfreerdp3.so.3.0.0 5 | /usr/lib/libuwac0.so.0.2.0 6 | /usr/lib/libwinpr-tools3.so.3.0.0 7 | /usr/lib/libwinpr3.so.3.0.0 8 | -------------------------------------------------------------------------------- /debuginfo/WSLGd.list: -------------------------------------------------------------------------------- 1 | /usr/bin/WSLGd 2 | -------------------------------------------------------------------------------- /debuginfo/gen_debuginfo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function split_debuginfo(){ 4 | file=${1//[$'\t\r\n']} 5 | dir=$(dirname "$file") 6 | mkdir -p $2/debuginfo/$dir 7 | org=$2/$file 8 | dst=$2/debuginfo/$file.debug 9 | objcopy --only-keep-debug $org $dst 10 | objcopy --strip-debug $org 11 | objcopy --add-gnu-debuglink=$dst $org 12 | echo $dst 13 | } 14 | while read line; do split_debuginfo $line $2; done < $1 15 | 16 | -------------------------------------------------------------------------------- /debuginfo/rdpapplist.list: -------------------------------------------------------------------------------- 1 | usr/lib/rdpapplist/librdpapplist-server.so 2 | -------------------------------------------------------------------------------- /debuginfo/weston.list: -------------------------------------------------------------------------------- 1 | usr/bin/weston 2 | usr/bin/weston-launch 3 | usr/bin/weston-screenshooter 4 | usr/lib/libweston-9.so.0.0.0 5 | usr/lib/libweston-desktop-9.so.0.0.0 6 | usr/lib/libweston-9/gl-renderer.so 7 | usr/lib/libweston-9/rdp-backend.so 8 | usr/lib/libweston-9/xwayland.so 9 | usr/lib/weston/libexec_weston.so.0.0.0 10 | usr/lib/weston/desktop-shell.so 11 | usr/lib/weston/rdprail-shell.so 12 | usr/lib/weston/wslgd-notify.so 13 | usr/libexec/weston-desktop-shell 14 | usr/libexec/weston-keyboard 15 | usr/libexec/weston-rdprail-shell 16 | -------------------------------------------------------------------------------- /devops/common-linux.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: DockerInstaller@0 3 | inputs: 4 | dockerVersion: '20.10.7' 5 | releaseType: 'stable' 6 | 7 | - script: wget https://github.com/GitTools/GitVersion/releases/download/5.6.8/gitversion-linux-x64-5.6.8.tar.gz && 8 | tar -xvf gitversion-linux-x64-5.6.8.tar.gz && 9 | sudo mv gitversion /usr/local/bin && 10 | sudo mv libgit2-6777db8.so /usr/local/bin 11 | displayName: 'Install GitVersion' 12 | 13 | - script: git clone --branch v0.8.17 --single-branch https://github.com/microsoft/hcsshim.git 14 | displayName: 'Clone hcsshim repo for tar2ext4 tool' 15 | -------------------------------------------------------------------------------- /devops/common-win.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: 'choco install gitversion.portable --pre' 3 | displayName: 'Install GitVersion' 4 | -------------------------------------------------------------------------------- /devops/getversion.ps1: -------------------------------------------------------------------------------- 1 | . .\devops\version_functions.ps1 2 | 3 | $version = Get-VersionInfo "version" "-beta" 4 | 5 | Write-Output $version 6 | -------------------------------------------------------------------------------- /devops/updateversion.ps1: -------------------------------------------------------------------------------- 1 | param ([string] $XmlFile, [string] $xpath, [string] $name, [string] $buildSeparator = ".", [string] $type = "version") 2 | 3 | . .\devops\version_functions.ps1 4 | 5 | $version = Get-VersionInfo $type $buildSeparator 6 | 7 | if ($name -eq "") 8 | { 9 | Update-XML-Text $XmlFile $xpath $version 10 | } 11 | else 12 | { 13 | Update-XML-Attribute $XmlFile $xpath $name $version 14 | } 15 | -------------------------------------------------------------------------------- /devops/version_functions.ps1: -------------------------------------------------------------------------------- 1 | function Get-XmlNamespaceManager([xml]$XmlDocument, [string]$NamespaceURI = "") 2 | { 3 | # If a Namespace URI was not given, use the Xml document's default namespace. 4 | if ([string]::IsNullOrEmpty($NamespaceURI)) { $NamespaceURI = $XmlDocument.DocumentElement.NamespaceURI } 5 | 6 | # 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. 7 | [System.Xml.XmlNamespaceManager]$xmlNsManager = New-Object System.Xml.XmlNamespaceManager($XmlDocument.NameTable) 8 | $xmlNsManager.AddNamespace("ns", $NamespaceURI) 9 | return ,$xmlNsManager # Need to put the comma before the variable name so that PowerShell doesn't convert it into an Object[]. 10 | } 11 | 12 | function Get-FullyQualifiedXmlNodePath([string]$NodePath, [string]$NodeSeparatorCharacter = '.') 13 | { 14 | return "/ns:$($NodePath.Replace($('.'), '/ns:'))" 15 | } 16 | 17 | function Get-XmlNode([xml]$XmlDocument, [string]$NodePath, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.') 18 | { 19 | $xmlNsManager = Get-XmlNamespaceManager -XmlDocument $XmlDocument -NamespaceURI $NamespaceURI 20 | [string]$fullyQualifiedNodePath = Get-FullyQualifiedXmlNodePath -NodePath $NodePath -NodeSeparatorCharacter $NodeSeparatorCharacter 21 | 22 | # Try and get the node, then return it. Returns $null if the node was not found. 23 | $node = $XmlDocument.SelectSingleNode($fullyQualifiedNodePath, $xmlNsManager) 24 | return $node 25 | } 26 | 27 | function Set-XmlAttributeValue([xml]$XmlDocument, [string]$ElementPath, [string]$AttributeName, [string]$AttributeValue, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.') 28 | { 29 | # Try and get the node. 30 | $node = Get-XmlNode -XmlDocument $XmlDocument -NodePath $ElementPath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter 31 | $node.SetAttribute($AttributeName, $AttributeValue) 32 | } 33 | 34 | function Set-XmlElementsTextValue([xml]$XmlDocument, [string]$ElementPath, [string]$TextValue, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.') 35 | { 36 | # Try and get the node. 37 | $node = Get-XmlNode -XmlDocument $XmlDocument -NodePath $ElementPath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter 38 | 39 | # If the node already exists, update its value. 40 | if ($node) 41 | { 42 | $node.InnerText = $TextValue 43 | } 44 | # Else the node doesn't exist yet, so create it with the given value. 45 | else 46 | { 47 | # Create the new element with the given value. 48 | $elementName = $ElementPath.Substring($ElementPath.LastIndexOf($NodeSeparatorCharacter) + 1) 49 | $element = $XmlDocument.CreateElement($elementName, $XmlDocument.DocumentElement.NamespaceURI) 50 | $textNode = $XmlDocument.CreateTextNode($TextValue) 51 | $element.AppendChild($textNode) > $null 52 | 53 | # Try and get the parent node. 54 | $parentNodePath = $ElementPath.Substring(0, $ElementPath.LastIndexOf($NodeSeparatorCharacter)) 55 | $parentNode = Get-XmlNode -XmlDocument $XmlDocument -NodePath $parentNodePath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter 56 | 57 | if ($parentNode) 58 | { 59 | $parentNode.AppendChild($element) > $null 60 | } 61 | else 62 | { 63 | throw "$parentNodePath does not exist in the xml." 64 | } 65 | } 66 | } 67 | 68 | function Update-XML-Attribute($File, $xpath, $name, $value) 69 | { 70 | $File = Resolve-Path $File 71 | 72 | [xml] $fileContents = Get-Content -Encoding UTF8 -Path $File 73 | 74 | if ($null -ne $value -and $value -ne "") { 75 | Set-XmlAttributeValue -XmlDocument $fileContents -ElementPath $xpath -AttributeName $name -AttributeValue $value 76 | } 77 | $fileContents.Save($File) 78 | } 79 | 80 | function Update-XML-Text($File, $xpath, $value) 81 | { 82 | $File = Resolve-Path $File 83 | 84 | [xml] $fileContents = Get-Content -Encoding UTF8 -Path $File 85 | 86 | if ($null -ne $value -and $value -ne "") { 87 | Set-XmlElementsTextValue -XmlDocument $fileContents -ElementPath $xpath -TextValue $value 88 | } 89 | 90 | $fileContents.Save($File) 91 | } 92 | function Get-Current-Commit-Hash () 93 | { 94 | return ([string](git log -1 --pretty=%h)).Trim() 95 | } 96 | 97 | function Get-VersionInfo($type, $separator) 98 | { 99 | if ($type -eq "hash") 100 | { 101 | return Get-Current-Commit-Hash 102 | } 103 | 104 | $major = [string](gitversion /showvariable Major) 105 | $minor = [string](gitversion /showvariable Minor) 106 | $patch = [string](gitversion /showvariable Patch) 107 | $build = [string](gitversion /showvariable BuildMetaData) 108 | 109 | $version = "$major.$minor.$patch" 110 | 111 | if ($build -ne "") 112 | { 113 | $version = $version + $separator + $build 114 | } 115 | 116 | return $version 117 | } 118 | -------------------------------------------------------------------------------- /docs/WSLg_ArchitectureOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/wslg/c85d2f3cb6689816f0645e079d3bf3a3e8327e05/docs/WSLg_ArchitectureOverview.png -------------------------------------------------------------------------------- /docs/WSLg_IntegratedDesktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/wslg/c85d2f3cb6689816f0645e079d3bf3a3e8327e05/docs/WSLg_IntegratedDesktop.png -------------------------------------------------------------------------------- /docs/install-sample-gui-apps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure you are using Ubuntu 20.04!! 4 | 5 | apt update 6 | 7 | ## Gedit 8 | 9 | apt install gedit -y 10 | 11 | ## GIMP 12 | 13 | apt install gimp -y 14 | 15 | ## Nautilus 16 | 17 | apt install nautilus -y 18 | 19 | ## X11 apps 20 | 21 | apt install x11-apps -y 22 | 23 | ## Google Chrome 24 | 25 | cd /tmp 26 | wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 27 | dpkg -i google-chrome-stable_current_amd64.deb 28 | apt install --fix-broken -y 29 | dpkg -i google-chrome-stable_current_amd64.deb 30 | 31 | ## Microsoft teams 32 | 33 | cd /tmp 34 | curl -L -o "./teams.deb" "https://teams.microsoft.com/downloads/desktopurl?env=production&plat=linux&arch=x64&download=true&linuxArchiveType=deb" 35 | apt install ./teams.deb -y 36 | 37 | ## Edge Browser 38 | 39 | curl https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-dev/microsoft-edge-dev_88.0.673.0-1_amd64.deb -o /tmp/edge.deb 40 | apt install /tmp/edge.deb -y -------------------------------------------------------------------------------- /msi/updateversion.ps1: -------------------------------------------------------------------------------- 1 | param ([string] $XmlFile = $null ) 2 | 3 | function Get-XmlNamespaceManager([xml]$XmlDocument, [string]$NamespaceURI = "") 4 | { 5 | # If a Namespace URI was not given, use the Xml document's default namespace. 6 | if ([string]::IsNullOrEmpty($NamespaceURI)) { $NamespaceURI = $XmlDocument.DocumentElement.NamespaceURI } 7 | 8 | # 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. 9 | [System.Xml.XmlNamespaceManager]$xmlNsManager = New-Object System.Xml.XmlNamespaceManager($XmlDocument.NameTable) 10 | $xmlNsManager.AddNamespace("ns", $NamespaceURI) 11 | return ,$xmlNsManager # Need to put the comma before the variable name so that PowerShell doesn't convert it into an Object[]. 12 | } 13 | 14 | function Get-FullyQualifiedXmlNodePath([string]$NodePath, [string]$NodeSeparatorCharacter = '.') 15 | { 16 | return "/ns:$($NodePath.Replace($('.'), '/ns:'))" 17 | } 18 | 19 | function Get-XmlNode([xml]$XmlDocument, [string]$NodePath, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.') 20 | { 21 | $xmlNsManager = Get-XmlNamespaceManager -XmlDocument $XmlDocument -NamespaceURI $NamespaceURI 22 | [string]$fullyQualifiedNodePath = Get-FullyQualifiedXmlNodePath -NodePath $NodePath -NodeSeparatorCharacter $NodeSeparatorCharacter 23 | 24 | # Try and get the node, then return it. Returns $null if the node was not found. 25 | $node = $XmlDocument.SelectSingleNode($fullyQualifiedNodePath, $xmlNsManager) 26 | return $node 27 | } 28 | 29 | function Set-XmlAttributeValue([xml]$XmlDocument, [string]$ElementPath, [string]$AttributeName, [string]$AttributeValue, [string]$NamespaceURI = "", [string]$NodeSeparatorCharacter = '.') 30 | { 31 | # Try and get the node. 32 | $node = Get-XmlNode -XmlDocument $XmlDocument -NodePath $ElementPath -NamespaceURI $NamespaceURI -NodeSeparatorCharacter $NodeSeparatorCharacter 33 | $node.SetAttribute($AttributeName, $AttributeValue) 34 | } 35 | 36 | function Update-XML-Attribute($File, $xpath, $name, $value) 37 | { 38 | $File = Resolve-Path $File 39 | 40 | [xml] $fileContents = Get-Content -Encoding UTF8 -Path $File 41 | 42 | if ($null -ne $value -and $value -ne "") { 43 | Set-XmlAttributeValue -XmlDocument $fileContents -ElementPath $xpath -AttributeName $name -AttributeValue $value 44 | } 45 | $fileContents.Save($File) 46 | } 47 | 48 | $major = [string](gitversion /showvariable Major) 49 | $minor = [string](gitversion /showvariable Minor) 50 | $patch = [string](gitversion /showvariable Patch) 51 | $build = [string](gitversion /showvariable BuildMetaData) 52 | 53 | if ($build -eq "") 54 | { 55 | $build = "0" 56 | } 57 | 58 | if ($XmlFile -ne $null) 59 | { 60 | $version = "$major.$minor.$patch.$build"; 61 | Update-XML-Attribute $XmlFile "Wix.Product" "Version" $version 62 | } 63 | -------------------------------------------------------------------------------- /package/wslg.rdp: -------------------------------------------------------------------------------- 1 | audiocapturemode:i:2 2 | authentication level:i:0 3 | disableconnectionsharing:i:1 4 | enablecredsspsupport:i:0 5 | hvsocketenabled:i:1 6 | remoteapplicationmode:i:1 7 | remoteapplicationprogram:s:dummy-entry -------------------------------------------------------------------------------- /package/wslg_desktop.rdp: -------------------------------------------------------------------------------- 1 | audiocapturemode:i:2 2 | authentication level:i:0 3 | disableconnectionsharing:i:1 4 | enablecredsspsupport:i:0 5 | hvsocketenabled:i:1 6 | -------------------------------------------------------------------------------- /rdpapplist/meson.build: -------------------------------------------------------------------------------- 1 | project('rdpapplist', 2 | 'c', 3 | version : '2.0.0', 4 | ) 5 | 6 | name_rdpapplist = meson.project_name() 7 | version_rdpapplist = meson.project_version() 8 | 9 | config_h = configuration_data() 10 | 11 | dir_prefix = get_option('prefix') 12 | 13 | dir_lib = join_paths(dir_prefix, get_option('libdir')) 14 | install_lib_dir_rdpapplist = join_paths(dir_lib, name_rdpapplist) 15 | config_h.set_quoted('RDPAPPLIST_MODULEDIR', 16 | install_lib_dir_rdpapplist) 17 | 18 | dir_inc = join_paths(dir_prefix, get_option('includedir')) 19 | install_inc_dir_rdpapplist = join_paths(dir_inc, name_rdpapplist) 20 | config_h.set_quoted('RDPAPPLIST_INCLUDEDIR', 21 | install_inc_dir_rdpapplist) 22 | 23 | dep_frdp = dependency('freerdp3', version: '>= 3.0.0', required: false) 24 | if not dep_frdp.found() 25 | dep_frdp = dependency('freerdp2', version: '>= 2.0.0', required: false) 26 | if not dep_frdp.found() 27 | error('rdpapplist requires freerdp2 or 3 which was not found.') 28 | endif 29 | endif 30 | 31 | dep_winpr = dependency('winpr3', version: '>= 3.0.0', required: false) 32 | if not dep_winpr.found() 33 | dep_winpr = dependency('winpr2', version: '>= 2.0.0', required: false) 34 | if not dep_frdp.found() 35 | error('rdpapplist requires winpr2 or 3 which was not found.') 36 | endif 37 | endif 38 | 39 | configure_file(output: 'rdpapplist_config.h', 40 | configuration: config_h, 41 | install_dir: install_inc_dir_rdpapplist) 42 | 43 | subdir('server') 44 | 45 | pkgconfig = import('pkgconfig') 46 | pkgconfig.generate( 47 | filebase: name_rdpapplist, 48 | version: version_rdpapplist, 49 | name: 'Application list Plugin for FreeRDP', 50 | description: 'Header files for Application list Plugin for FreeRDP', 51 | ) 52 | 53 | install_headers( 54 | 'rdpapplist_protocol.h', 55 | 'rdpapplist_server.h', 56 | subdir: name_rdpapplist, 57 | ) 58 | -------------------------------------------------------------------------------- /rdpapplist/rdpapplist_common.c: -------------------------------------------------------------------------------- 1 | /** 2 | * FreeRDP: A Remote Desktop Protocol Implementation 3 | * RDPXXXX Remote Application List Virtual Channel Extension 4 | * 5 | * Copyright 2020 Microsoft 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #define TAG CHANNELS_TAG("rdpapplist.common") 29 | 30 | #include "rdpapplist_common.h" 31 | 32 | /** 33 | * Function description 34 | * 35 | * @return 0 on success, otherwise a Win32 error code 36 | */ 37 | UINT rdpapplist_read_header(wStream* s, RDPAPPLIST_HEADER* header) 38 | { 39 | if (Stream_GetRemainingLength(s) < 8) 40 | { 41 | WLog_ERR(TAG, "header parsing failed: not enough data!"); 42 | return ERROR_INVALID_DATA; 43 | } 44 | 45 | Stream_Read_UINT32(s, header->cmdId); 46 | Stream_Read_UINT32(s, header->length); 47 | return CHANNEL_RC_OK; 48 | } 49 | 50 | /** 51 | * Function description 52 | * 53 | * @return 0 on success, otherwise a Win32 error code 54 | */ 55 | UINT rdpapplist_write_header(wStream* s, const RDPAPPLIST_HEADER* header) 56 | { 57 | Stream_Write_UINT32(s, header->cmdId); 58 | Stream_Write_UINT32(s, header->length); 59 | return CHANNEL_RC_OK; 60 | } 61 | -------------------------------------------------------------------------------- /rdpapplist/rdpapplist_common.h: -------------------------------------------------------------------------------- 1 | /** 2 | * FreeRDP: A Remote Desktop Protocol Implementation 3 | * RDPXXXX Remote Application List Virtual Channel Extension 4 | * 5 | * Copyright 2020 Microsoft 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | #ifndef FREERDP_CHANNEL_RDPAPPLIST_COMMON_H 21 | #define FREERDP_CHANNEL_RDPAPPLIST_COMMON_H 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | FREERDP_LOCAL UINT rdpapplist_read_header(wStream* s, RDPAPPLIST_HEADER* header); 30 | FREERDP_LOCAL UINT rdpapplist_write_header(wStream* s, const RDPAPPLIST_HEADER* header); 31 | 32 | #endif /* FREERDP_CHANNEL_RDPAPPLIST_COMMON_H */ 33 | -------------------------------------------------------------------------------- /rdpapplist/rdpapplist_protocol.h: -------------------------------------------------------------------------------- 1 | /** 2 | * FreeRDP: A Remote Desktop Protocol Implementation 3 | * RDPXXXX Remote Application List Virtual Channel Extension 4 | * 5 | * Copyright 2020 Microsoft 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | #ifndef FREERDP_CHANNEL_RDPAPPLIST_H 21 | #define FREERDP_CHANNEL_RDPAPPLIST_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #define RDPAPPLIST_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::RemoteApplicationList" 28 | 29 | /* Version 4 30 | * - add RDPAPPLIST_SERVER_CAPS_PDU.appListProviderUniqueId field. 31 | * - add RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID_PUD message. 32 | */ 33 | #define RDPAPPLIST_CHANNEL_VERSION 4 34 | 35 | #define RDPAPPLIST_CMDID_CAPS 0x00000001 36 | #define RDPAPPLIST_CMDID_UPDATE_APPLIST 0x00000002 37 | #define RDPAPPLIST_CMDID_DELETE_APPLIST 0x00000003 38 | #define RDPAPPLIST_CMDID_DELETE_APPLIST_PROVIDER 0x00000004 39 | #define RDPAPPLIST_CMDID_ASSOCIATE_WINDOW_ID 0x00000005 40 | 41 | #define RDPAPPLIST_ICON_FORMAT_PNG 0x0001 42 | #define RDPAPPLIST_ICON_FORMAT_BMP 0x0002 43 | #define RDPAPPLIST_ICON_FORMAT_SVG 0x0003 44 | 45 | #define RDPAPPLIST_FIELD_ID 0x00000001 46 | #define RDPAPPLIST_FIELD_GROUP 0x00000002 47 | #define RDPAPPLIST_FIELD_EXECPATH 0x00000004 48 | #define RDPAPPLIST_FIELD_DESC 0x00000008 49 | #define RDPAPPLIST_FIELD_ICON 0x00000010 50 | #define RDPAPPLIST_FIELD_PROVIDER 0x00000020 51 | #define RDPAPPLIST_FIELD_WORKINGDIR 0x00000040 52 | #define RDPAPPLIST_FIELD_WINDOW_ID 0x00000080 53 | 54 | /* RDPAPPLIST_UPDATE_APPLIST_PDU */ 55 | #define RDPAPPLIST_HINT_NEWID 0x00010000 /* new appId vs update existing appId. */ 56 | #define RDPAPPLIST_HINT_SYNC 0x00100000 /* In sync mode (use with _NEWID). */ 57 | #define RDPAPPLIST_HINT_SYNC_START 0x00200000 /* Sync appId start (use with _SYNC). */ 58 | #define RDPAPPLIST_HINT_SYNC_END 0x00400000 /* Sync appId end (use with _SYNC). */ 59 | /* Client should remove any app entry those are not reported between _START and _END during sync mode */ 60 | 61 | #define RDPAPPLIST_HEADER_SIZE 8 62 | 63 | #define RDPAPPLIST_LANG_SIZE 32 64 | 65 | #define RDPAPPLIST_MAX_STRING_SIZE 512 66 | 67 | struct _RDPAPPLIST_HEADER 68 | { 69 | UINT32 cmdId; 70 | UINT32 length; 71 | }; 72 | 73 | typedef struct _RDPAPPLIST_HEADER RDPAPPLIST_HEADER; 74 | 75 | struct _RDPAPPLIST_SERVER_CAPS_PDU 76 | { 77 | UINT16 version; 78 | RAIL_UNICODE_STRING appListProviderName; /* name of app list provider. */ 79 | RAIL_UNICODE_STRING appListProviderUniqueId; /* added from version 4 */ 80 | }; 81 | 82 | typedef struct _RDPAPPLIST_SERVER_CAPS_PDU RDPAPPLIST_SERVER_CAPS_PDU; 83 | 84 | struct _RDPAPPLIST_CLIENT_CAPS_PDU 85 | { 86 | UINT16 version; 87 | /* ISO 639 (Language name) and ISO 3166 (Country name) connected with '_', such as en_US, ja_JP */ 88 | char clientLanguageId[RDPAPPLIST_LANG_SIZE]; 89 | }; 90 | 91 | typedef struct _RDPAPPLIST_CLIENT_CAPS_PDU RDPAPPLIST_CLIENT_CAPS_PDU; 92 | 93 | struct _RDPAPPLIST_ICON_DATA { 94 | UINT32 flags; 95 | UINT32 iconWidth; 96 | UINT32 iconHeight; 97 | UINT32 iconStride; 98 | UINT32 iconBpp; 99 | UINT32 iconFormat; /* RDPAPPLIST_ICON_FORMAT_* */ 100 | UINT32 iconBitsLength; /* size of buffer pointed by iconBits. */ 101 | VOID *iconBits; /* icon image data */ 102 | /* For BMP, image data only */ 103 | /* For PNG, entire PNG file including headers */ 104 | /* For SVG, entire SVG file including headers */ 105 | /* For 32bpp image, alpha-channel works as mask */ 106 | }; 107 | 108 | typedef struct _RDPAPPLIST_ICON_DATA RDPAPPLIST_ICON_DATA; 109 | 110 | /* Create or update application program link in client */ 111 | 112 | struct _RDPAPPLIST_UPDATE_APPLIST_PDU 113 | { 114 | UINT32 flags; 115 | RAIL_UNICODE_STRING appId; /* Identifier of application to be added 116 | to client's Start Menu. This is used as 117 | the file name of link (.lnk) at Start Menu. */ 118 | RAIL_UNICODE_STRING appGroup; /* name of app group. */ 119 | RAIL_UNICODE_STRING appExecPath; /* Path to server side executable. */ 120 | RAIL_UNICODE_STRING appWorkingDir; /* Working directory to run the executable in. */ 121 | RAIL_UNICODE_STRING appDesc; /* UI friendly description of application. */ 122 | RDPAPPLIST_ICON_DATA *appIcon; 123 | }; 124 | 125 | typedef struct _RDPAPPLIST_UPDATE_APPLIST_PDU RDPAPPLIST_UPDATE_APPLIST_PDU; 126 | 127 | /* Delete specififed application program link from client */ 128 | 129 | struct _RDPAPPLIST_DELETE_APPLIST_PDU 130 | { 131 | UINT32 flags; 132 | RAIL_UNICODE_STRING appId; /* Identifier of application to be removed 133 | from client's Start Menu. */ 134 | RAIL_UNICODE_STRING appGroup; /* name of app group. */ 135 | }; 136 | 137 | typedef struct _RDPAPPLIST_DELETE_APPLIST_PDU RDPAPPLIST_DELETE_APPLIST_PDU; 138 | 139 | /* Delete all application program link under specififed provider from client */ 140 | 141 | struct _RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU 142 | { 143 | UINT32 flags; 144 | RAIL_UNICODE_STRING appListProviderName; /* name of app list provider to be removed 145 | from client's Start Menu. */ 146 | }; 147 | 148 | typedef struct _RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU; 149 | 150 | struct _RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU 151 | { 152 | UINT32 flags; 153 | UINT32 appWindowId; /* window id of application */ 154 | RAIL_UNICODE_STRING appId; /* Identifier of application to add taskbar property. */ 155 | RAIL_UNICODE_STRING appGroup; /* Identifier of group where application belonging to. */ 156 | RAIL_UNICODE_STRING appExecPath; /* Path to server side executable. */ 157 | RAIL_UNICODE_STRING appDesc; /* UI friendly description of application. */ 158 | }; 159 | 160 | typedef struct _RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU; 161 | 162 | #endif /* FREERDP_CHANNEL_RDPAPPLIST_H */ 163 | -------------------------------------------------------------------------------- /rdpapplist/rdpapplist_server.h: -------------------------------------------------------------------------------- 1 | /** 2 | * FreeRDP: A Remote Desktop Protocol Implementation 3 | * RDPXXXX Remote Application List Virtual Channel Extension 4 | * 5 | * Copyright 2020 Microsoft 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | #ifndef FREERDP_CHANNEL_RDPAPPLIST_SERVER_RDPAPPLIST_H 21 | #define FREERDP_CHANNEL_RDPAPPLIST_SERVER_RDPAPPLIST_H 22 | 23 | #include "rdpapplist_protocol.h" 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | typedef struct _rdpapplist_server_private RdpAppListServerPrivate; 30 | typedef struct _rdpapplist_server_context RdpAppListServerContext; 31 | 32 | typedef UINT (*psRdpAppListOpen)(RdpAppListServerContext* context); 33 | typedef UINT (*psRdpAppListClose)(RdpAppListServerContext* context); 34 | 35 | typedef UINT (*psRdpAppListCaps)(RdpAppListServerContext* context, const RDPAPPLIST_SERVER_CAPS_PDU *caps); 36 | typedef UINT (*psRdpAppListUpdate)(RdpAppListServerContext* context, const RDPAPPLIST_UPDATE_APPLIST_PDU *updateAppList); 37 | typedef UINT (*psRdpAppListDelete)(RdpAppListServerContext* context, const RDPAPPLIST_DELETE_APPLIST_PDU *deleteAppList); 38 | typedef UINT (*psRdpAppListDeleteProvider)(RdpAppListServerContext* context, const RDPAPPLIST_DELETE_APPLIST_PROVIDER_PDU *deleteAppListProvider); 39 | typedef UINT (*psRdpAppListAssociateWindowId)(RdpAppListServerContext* context, const RDPAPPLIST_ASSOCIATE_WINDOW_ID_PDU *associateWindowId); 40 | 41 | typedef UINT (*psRdpAppListClientCaps)(RdpAppListServerContext* context, const RDPAPPLIST_CLIENT_CAPS_PDU *clientCaps); 42 | 43 | struct _rdpapplist_server_context 44 | { 45 | void* custom; 46 | HANDLE vcm; 47 | 48 | psRdpAppListOpen Open; 49 | psRdpAppListClose Close; 50 | 51 | psRdpAppListCaps ApplicationListCaps; 52 | psRdpAppListUpdate UpdateApplicationList; 53 | psRdpAppListDelete DeleteApplicationList; 54 | psRdpAppListDeleteProvider DeleteApplicationListProvider; 55 | psRdpAppListAssociateWindowId AssociateWindowId; 56 | 57 | psRdpAppListClientCaps ApplicationListClientCaps; 58 | 59 | RdpAppListServerPrivate* priv; 60 | rdpContext* rdpcontext; 61 | }; 62 | 63 | #ifdef __cplusplus 64 | extern "C" 65 | { 66 | #endif 67 | 68 | FREERDP_API RdpAppListServerContext* rdpapplist_server_context_new(HANDLE vcm); 69 | FREERDP_API void rdpapplist_server_context_free(RdpAppListServerContext* context); 70 | 71 | #ifdef __cplusplus 72 | } 73 | #endif 74 | 75 | #endif /* FREERDP_CHANNEL_RDPAPPLIST_SERVER_RDPAPPLIST_H */ 76 | -------------------------------------------------------------------------------- /rdpapplist/server/meson.build: -------------------------------------------------------------------------------- 1 | dep_frdp_server = dependency('freerdp-server3', version: '>= 3.0.0', required: false) 2 | if not dep_frdp_server.found() 3 | dep_frdp_server = dependency('freerdp-server2', version: '>= 2.0.0', required: false) 4 | if not dep_frdp_server.found() 5 | error('librapapplist-server requires freerdp-server2 or 3 which was not found.') 6 | endif 7 | endif 8 | 9 | deps_librdpapplist_server = [ 10 | dep_frdp, 11 | dep_frdp_server, 12 | dep_winpr, 13 | ] 14 | 15 | srcs_librdpapplist_server = [ 16 | '../rdpapplist_common.c', 17 | 'rdpapplist_main.c', 18 | ] 19 | 20 | incs_common_server = [ 21 | '../', 22 | ] 23 | 24 | plugin_rdpapplist_server = shared_library( 25 | 'librdpapplist-server', 26 | srcs_librdpapplist_server, 27 | include_directories: incs_common_server, 28 | dependencies: deps_librdpapplist_server, 29 | name_prefix: '', 30 | install: true, 31 | install_dir: install_lib_dir_rdpapplist, 32 | ) 33 | -------------------------------------------------------------------------------- /rdpapplist/server/rdpapplist_main.h: -------------------------------------------------------------------------------- 1 | /** 2 | * FreeRDP: A Remote Desktop Protocol Implementation 3 | * RDPXXXX Remote Application List Virtual Channel Extension 4 | * 5 | * Copyright 2020 Microsoft 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | #ifndef FREERDP_CHANNEL_RDPAPPLIST_SERVER_MAIN_H 21 | #define FREERDP_CHANNEL_RDPAPPLIST_SERVER_MAIN_H 22 | 23 | struct _rdpapplist_server_private 24 | { 25 | BOOL isReady; 26 | wStream* input_stream; 27 | HANDLE channelEvent; 28 | HANDLE thread; 29 | HANDLE stopEvent; 30 | DWORD SessionId; 31 | 32 | void* rdpapplist_channel; 33 | }; 34 | 35 | #endif /* FREERDP_CHANNEL_RDPAPPLIST_SERVER_MAIN_H */ 36 | -------------------------------------------------------------------------------- /resources/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/wslg/c85d2f3cb6689816f0645e079d3bf3a3e8327e05/resources/linux.png -------------------------------------------------------------------------------- /samples/container/Containers.md: -------------------------------------------------------------------------------- 1 | # Containerizing GUI applications with WSLg 2 | 3 | For containerized applications to work properly under WSLg developers need to be aware of a few peculiarity of our environment in order to allow applications to properly connect to our X11, Wayland or PulseAudio server or to use the vGPU. 4 | 5 | ## Containerized GUI applications connecting to X11, Wayland or Pulse server 6 | 7 | In order for a containerized application to access the servers provided by WSLg, the following mount location must be made visible inside the container. 8 | 9 | | Server | Mount | 10 | |---|---| 11 | | X11 | ```/tmp/.X11-unix``` | 12 | | Wayland | ```/mnt/wslg``` | 13 | | PulseAudio | ```/mnt/wslg``` | 14 | 15 | And the following environment variable must be share with the container. 16 | 17 | | Server | Environment variables | 18 | |---|---| 19 | | X11 | ```DISPLAY``` | 20 | | Wayland | ```WAYLAND_DISPLAY``` && ```XDG_RUNTIME_DIR``` | 21 | | PulseAudio | ```PULSE_SERVER``` | 22 | 23 | For example, to run ```xclock``` as a containerized application, the following docker file can be use. 24 | 25 | ``` 26 | FROM ubuntu:20.04 as runtime 27 | 28 | ARG DEBIAN_FRONTEND=noninteractive 29 | 30 | RUN apt update && \ 31 | apt install -y x11-apps 32 | 33 | CMD /usr/bin/xclock 34 | ``` 35 | 36 | The container can be build then launch as follow. 37 | 38 | ``` 39 | sudo docker build -t xclock -f Dockerfile.xclock . 40 | sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ 41 | -e DISPLAY=$DISPLAY -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \ 42 | -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR -e PULSE_SERVER=$PULSE_SERVER xclock 43 | ``` 44 | 45 | Please note that in this example we make all servers visible to ```xclock``` even though it only uses the X11 server and will not make use of the Wayland or PulseAudio servers. This is for illustrative purposes only. There is no real harm in exposing a server that is unused by an application. However it is good practice to only exposed containerized application to the resource they need. In this case we could have launch the containerized version of ```xclock``` with the following minimal command line. 46 | 47 | ``` 48 | sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY xclock 49 | ``` 50 | 51 | ## Containerized applications access to the vGPU 52 | 53 | For a containerized application to use the vGPU provided by WSL2, the following must be done. 54 | 55 | The following device must be shared with the container. 56 | 57 | ```/dev/dxg``` 58 | 59 | The following mount location must be mapped in the container. 60 | 61 | ```/usr/lib/wsl``` 62 | 63 | The following path must be added to the ```LD_LIBRARY_PATH``` environment variable inside the container. 64 | 65 | ```/usr/lib/wsl/lib``` 66 | 67 | For example the following dockerfile containerized glxinfo. 68 | 69 | ``` 70 | FROM ubuntu:20.04 as runtime 71 | 72 | ARG DEBIAN_FRONTEND=noninteractive 73 | 74 | RUN apt update && \ 75 | apt install -y mesa-utils 76 | 77 | ENV LD_LIBRARY_PATH=/usr/lib/wsl/lib 78 | CMD /usr/bin/glxinfo -B 79 | ``` 80 | 81 | This container can be build and launch as follow. 82 | 83 | ``` 84 | sudo docker build -t glxinfo -f Dockerfile.glxinfo . 85 | sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ 86 | -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \ 87 | -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ 88 | -e PULSE_SERVER=$PULSE_SERVER --gpus all glxinfo 89 | ``` 90 | 91 | ## Containerized applications access to vGPU accelerated video 92 | 93 | For a containerized application to use vGPU video acceleration, the following must be done. 94 | 95 | The following devices must be shared with the container. 96 | 97 | ```/dev/dxg``` 98 | 99 | ```/dev/dri/card0``` 100 | 101 | ```/dev/dri/renderD128``` 102 | 103 | The following mount location must be mapped in the container. 104 | 105 | ```/usr/lib/wsl``` 106 | 107 | The following path must be added to the ```LD_LIBRARY_PATH``` environment variable inside the container. 108 | 109 | ```/usr/lib/wsl/lib``` 110 | 111 | The following VA-API driver name must be set to the ```LIBVA_DRIVER_NAME``` environment variable inside the container. 112 | 113 | ```d3d12``` 114 | 115 | The following libraries must be installed in the container. 116 | 117 | ``` 118 |   vainfo 119 |   mesa-va-drivers 120 | ``` 121 | For example the following dockerfile containerized `videoaccel`. 122 | 123 | ``` 124 | FROM ubuntu:22.10 as runtime 125 | 126 | ARG DEBIAN_FRONTEND=noninteractive 127 | 128 | # Uncomment the lines below to use a 3rd party repository 129 | # to get the latest (unstable from mesa/main) mesa library version 130 | # RUN apt-get update && apt install -y software-properties-common 131 | # RUN add-apt-repository ppa:oibaf/graphics-drivers -y 132 | 133 | RUN apt update && apt install -y \ 134 | vainfo \ 135 | mesa-va-drivers 136 | 137 | ENV LIBVA_DRIVER_NAME=d3d12 138 | ENV LD_LIBRARY_PATH=/usr/lib/wsl/lib 139 | CMD vainfo --display drm --device /dev/dri/card0 140 | ``` 141 | 142 | This container can be build and launch as follow. 143 | 144 | ``` 145 | sudo docker build -t videoaccel -f Dockerfile.videoaccel . 146 | sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ 147 | -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \ 148 | --device /dev/dri/card0 --device /dev/dri/renderD128 \ 149 | -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ 150 | -e PULSE_SERVER=$PULSE_SERVER --gpus all videoaccel 151 | ``` 152 | -------------------------------------------------------------------------------- /samples/container/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo docker build -t xclock -f Dockerfile.xclock . 4 | sudo docker build -t glxinfo -f Dockerfile.glxinfo . 5 | sudo docker build -t glxgears -f Dockerfile.glxgears . 6 | sudo docker build -t videoaccel -f Dockerfile.videoaccel . -------------------------------------------------------------------------------- /samples/container/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Containerized version of xclock 4 | sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ 5 | -e DISPLAY=$DISPLAY -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \ 6 | -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR -e PULSE_SERVER=$PULSE_SERVER xclock 7 | 8 | # Containerized version of glxinfo 9 | sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ 10 | -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \ 11 | -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ 12 | -e PULSE_SERVER=$PULSE_SERVER --gpus all glxinfo 13 | 14 | # Containerized version of glxgears 15 | sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ 16 | -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \ 17 | -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ 18 | -e PULSE_SERVER=$PULSE_SERVER --gpus all glxgears 19 | 20 | # Containerized version of videoaccel 21 | sudo docker run -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /mnt/wslg:/mnt/wslg \ 22 | -v /usr/lib/wsl:/usr/lib/wsl --device=/dev/dxg -e DISPLAY=$DISPLAY \ 23 | --device /dev/dri/card0 --device /dev/dri/renderD128 \ 24 | -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ 25 | -e PULSE_SERVER=$PULSE_SERVER --gpus all videoaccel 26 | -------------------------------------------------------------------------------- /vendor/.preserve: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/wslg/c85d2f3cb6689816f0645e079d3bf3a3e8327e05/vendor/.preserve --------------------------------------------------------------------------------