├── .editorconfig ├── .gitattributes ├── .github ├── pack.bat └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── samples ├── README.md └── VirtualDesktop.Showcase │ ├── App.xaml │ ├── App.xaml.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── VirtualDesktop.Showcase.csproj └── src ├── VirtualDesktop (LocalAppData).lnk ├── VirtualDesktop.WPF ├── ApplicationExtensions.cs ├── Properties │ └── AssemblyInfo.cs ├── VirtualDesktop.WPF.csproj └── WindowExtensions.cs ├── VirtualDesktop.WinForms ├── FormExtensions.cs ├── Properties │ └── AssemblyInfo.cs └── VirtualDesktop.WinForms.csproj ├── VirtualDesktop.sln ├── VirtualDesktop.sln.DotSettings └── VirtualDesktop ├── Interop ├── Build10240 │ ├── .Provider.cs │ ├── .interfaces │ │ ├── AssemblyInfo.cs │ │ ├── IApplicationView.cs │ │ ├── IApplicationViewCollection.cs │ │ ├── IVirtualDesktop.cs │ │ ├── IVirtualDesktopManagerInternal.cs │ │ ├── IVirtualDesktopNotification.cs │ │ ├── IVirtualDesktopNotificationService.cs │ │ └── IVirtualDesktopPinnedApps.cs │ ├── ApplicationView.cs │ ├── ApplicationViewCollection.cs │ ├── VirtualDesktop.cs │ ├── VirtualDesktopManagerInternal.cs │ ├── VirtualDesktopNotificationService.cs │ └── VirtualDesktopPinnedApps.cs ├── Build22000 │ ├── .Provider.cs │ ├── .interfaces │ │ ├── IVirtualDesktop.cs │ │ ├── IVirtualDesktopManagerInternal.cs │ │ ├── IVirtualDesktopNotification.cs │ │ └── IVirtualDesktopNotificationService.cs │ ├── VirtualDesktop.cs │ ├── VirtualDesktopManagerInternal.cs │ └── VirtualDesktopNotificationService.cs ├── CLSID.cs ├── ComInterfaceAssembly.cs ├── ComInterfaceAssemblyBuilder.cs ├── ComInterfaceAttribute.cs ├── ComWrapperBase.cs ├── ComWrapperFactory.cs ├── HResult.cs ├── HString.cs ├── IID.cs ├── Proxy │ ├── IApplicationView.cs │ ├── IApplicationViewCollection.cs │ ├── IVirtualDesktop.cs │ ├── IVirtualDesktopManagerInternal.cs │ ├── IVirtualDesktopNotification.cs │ ├── IVirtualDesktopNotificationService.cs │ └── IVirtualDesktopPinnedApps.cs ├── VirtualDesktopProvider.cs └── Win32.cs ├── Properties ├── AssemblyInfo.cs ├── Configurations.cs ├── Settings.Designer.cs └── Settings.settings ├── Utils ├── Disposable.cs ├── ExplorerRestartListenerWindow.cs ├── RawWindow.cs └── TransparentWindow.cs ├── VirtualDesktop.cs ├── VirtualDesktop.csproj ├── VirtualDesktop.csproj.DotSettings ├── VirtualDesktop.notification.cs ├── VirtualDesktop.system.cs ├── VirtualDesktopEventArgs.cs ├── VirtualDesktopExtensions.cs └── app.config /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{cs,xaml}] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = crlf 7 | insert_final_newline = true 8 | charset = utf-8-bom 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | #* text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/pack.bat: -------------------------------------------------------------------------------- 1 | dotnet build ..\src\VirtualDesktop.sln -c Release 2 | dotnet pack ..\src\VirtualDesktop.sln -c Release --no-build -o %CD% 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | 10 | env: 11 | DOTNET_VERSION: 6.0.x 12 | BUILD_TARGET: .\src\VirtualDesktop.sln 13 | 14 | 15 | jobs: 16 | build: 17 | name: .NET Build 18 | runs-on: windows-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - name: Use .NET ${{ env.DOTNET_VERSION }} 24 | uses: actions/setup-dotnet@v1 25 | with: 26 | dotnet-version: ${{ env.DOTNET_VERSION }} 27 | 28 | - name: Build 29 | run: dotnet build ${{ env.BUILD_TARGET }} -c Release 30 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: [ release ] 6 | 7 | env: 8 | DOTNET_VERSION: 6.0.x 9 | BUILD_TARGET: .\src\VirtualDesktop.sln 10 | 11 | jobs: 12 | build: 13 | name: .NET Build 14 | runs-on: windows-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Use .NET ${{ env.DOTNET_VERSION }} 20 | uses: actions/setup-dotnet@v1 21 | with: 22 | dotnet-version: ${{ env.DOTNET_VERSION }} 23 | 24 | - name: Build 25 | run: dotnet build ${{ env.BUILD_TARGET }} -c Release 26 | 27 | - name: Pack 28 | run: dotnet pack ${{ env.BUILD_TARGET }} -c Release --no-build 29 | 30 | - name: Upload artifacts 31 | uses: actions/upload-artifact@v2.3.1 32 | with: 33 | name: nuget-packages 34 | path: "**/*.nupkg" 35 | 36 | 37 | publish: 38 | name: Publish to NuGet 39 | runs-on: windows-latest 40 | needs: build 41 | if: github.ref == 'refs/heads/release' 42 | 43 | steps: 44 | - name: Use NuGet 45 | uses: NuGet/setup-nuget@v1.0.5 46 | with: 47 | nuget-version: latest 48 | nuget-api-key: ${{ secrets.NUGET_API_KEY }} 49 | 50 | - name: Fetch artifacts 51 | uses: actions/download-artifact@v2.1.0 52 | with: 53 | name: nuget-packages 54 | 55 | - name: Publish 56 | run: nuget push **\*.nupkg -Source https://api.nuget.org/v3/index.json 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Manato KAMEYA 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VirtualDesktop 2 | 3 | VirtualDesktop is C# wrapper for [IVirtualDesktopManager](https://msdn.microsoft.com/en-us/library/windows/desktop/mt186440%28v%3Dvs.85%29.aspx) on Windows 11 (and Windows 10). 4 | 5 | [![Build](https://github.com/Grabacr07/VirtualDesktop/actions/workflows/build.yml/badge.svg)](https://github.com/Grabacr07/VirtualDesktop/actions/workflows/build.yml) 6 | [![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/VirtualDesktop)](https://www.nuget.org/packages/VirtualDesktop/) 7 | [![License](https://img.shields.io/github/license/Grabacr07/VirtualDesktop)](LICENSE) 8 | 9 | 10 | ## Features 11 | 12 | * Switch, add, and remove a virtual desktop. 13 | * Move the window in the same process to any virtual desktop. 14 | * Move the window of another process to any virtual desktop (Support in version 2.0 or later). 15 | * Pin any window or application; will be display on all desktops. 16 | * Notification for switching, deletion, renaming, etc. 17 | * Change the wallpaper for each desktop. 18 | 19 | 20 | ### Sample app 21 | 22 | ![](https://user-images.githubusercontent.com/1779073/152605684-2d872356-1882-4bfd-821d-d4211ccac069.gif) 23 | [samples/VirtualDesktop.Showcase](samples/VirtualDesktop.Showcase) 24 | 25 | 26 | ## Requirements 27 | 28 | ```xml 29 | net5.0-windows10.0.19041.0 30 | ``` 31 | * .NET 5 or 6 32 | * Windows 10 build 19041 (20H1) or later 33 | 34 | 35 | ## Installation 36 | 37 | Install NuGet package(s). 38 | 39 | ```powershell 40 | PM> Install-Package VirtualDesktop 41 | ``` 42 | 43 | * [VirtualDesktop](https://www.nuget.org/packages/VirtualDesktop/) - Core classes for VirtualDesktop. 44 | * [VirtualDesktop.WPF](https://www.nuget.org/packages/VirtualDesktop.WPF/) - Provides extension methods for WPF [Window class](https://msdn.microsoft.com/en-us/library/system.windows.window(v=vs.110).aspx). 45 | * [VirtualDesktop.WinForms](https://www.nuget.org/packages/VirtualDesktop.WinForms/) - Provides extension methods for [Form class](https://msdn.microsoft.com/en-us/library/system.windows.forms.form(v=vs.110).aspx). 46 | 47 | 48 | ## How to use 49 | 50 | ### Preparation 51 | Because of the dependency on [C#/WinRT](https://aka.ms/cswinrt) ([repo](https://github.com/microsoft/CsWinRT)), the target framework must be set to `net5.0-windows10.0.19041.0` or later. 52 | ```xml 53 | net5.0-windows10.0.19041.0 54 | ``` 55 | ```xml 56 | net6.0-windows10.0.19041.0 57 | ``` 58 | 59 | If it doesn't work, try creating an `app.manifest` file and optimize to work on Windows 10. 60 | ```xml 61 | 62 | 63 | 64 | 65 | 66 | 67 | ``` 68 | 69 | The namespace to use is `WindowsDesktop`. 70 | ```csharp 71 | using WindowsDesktop; 72 | ``` 73 | 74 | ### Get instance of VirtualDesktop class 75 | ```csharp 76 | // Get all virtual desktops 77 | var desktops = VirtualDesktop.GetDesktops(); 78 | 79 | // Get Virtual Desktop for specific window 80 | var desktop = VirtualDesktop.FromHwnd(hwnd); 81 | 82 | // Get the left/right desktop 83 | var left = desktop.GetLeft(); 84 | var right = desktop.GetRight(); 85 | ``` 86 | 87 | ### Manage virtual desktops 88 | ```csharp 89 | // Create new 90 | var desktop = VirtualDesktop.Create(); 91 | 92 | // Remove 93 | desktop.Remove(); 94 | 95 | // Switch 96 | desktop.GetLeft().Switch(); 97 | ``` 98 | 99 | ### Subscribe virtual desktop events 100 | ```csharp 101 | // Notification of desktop switching 102 | VirtualDesktop.CurrentChanged += (_, args) => Console.WriteLine($"Switched: {args.NewDesktop.Name}"); 103 | 104 | // Notification of desktop creating 105 | VirtualDesktop.Created += (_, desktop) => desktop.Switch(); 106 | ``` 107 | 108 | ### for WPF window 109 | ```csharp 110 | // Need to install 'VirtualDesktop.WPF' package 111 | 112 | // Check whether a window is on the current desktop. 113 | var isCurrent = window.IsCurrentVirtualDesktop(); 114 | 115 | // Get Virtual Desktop for WPF window 116 | var desktop = window.GetCurrentDesktop(); 117 | 118 | // Move window to specific Virtual Desktop 119 | window.MoveToDesktop(desktop); 120 | 121 | // Pin window 122 | window.Pin() 123 | ``` 124 | 125 | ### See also: 126 | * [samples/README.md](samples/README.md) 127 | 128 | 129 | ## License 130 | 131 | This library is under [the MIT License](https://github.com/Grabacr07/VirtualDesktop/blob/master/LICENSE). 132 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | ## Samples 2 | 3 | * [VirtualDesktop.Showcase](VirtualDesktop.Showcase) … A simple application uses the VirtualDesktop library, included in this repository. 4 | * [SylphyHorn](https://github.com/Grabacr07/SylphyHorn) … Utility app for Windows by the same author. 5 | -------------------------------------------------------------------------------- /samples/VirtualDesktop.Showcase/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/VirtualDesktop.Showcase/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace VirtualDesktopShowcase; 7 | 8 | partial class App 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /samples/VirtualDesktop.Showcase/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 16 | 17 | 18 | 28 | 29 | 32 | 33 | 143 | 144 | 145 | 146 | 148 | 149 | 152 | 155 | 158 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /samples/VirtualDesktop.Showcase/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Runtime.CompilerServices; 9 | using System.Runtime.InteropServices; 10 | using System.Threading.Tasks; 11 | using System.Windows; 12 | using System.Windows.Controls; 13 | using Microsoft.Win32; 14 | using WindowsDesktop; 15 | using WindowsDesktop.Properties; 16 | 17 | namespace VirtualDesktopShowcase; 18 | 19 | partial class MainWindow 20 | { 21 | private const int _delay = 2000; 22 | private IDisposable? _applicationViewChangedListener; 23 | 24 | public ObservableCollection Desktops { get; } = new(); 25 | 26 | public MainWindow() 27 | { 28 | this.InitializeComponent(); 29 | this.InitializeComObjects(); 30 | } 31 | 32 | private void InitializeComObjects() 33 | { 34 | VirtualDesktop.Configure(); 35 | 36 | VirtualDesktop.Created += (_, desktop) => 37 | { 38 | this.Dispatcher.Invoke(() => 39 | { 40 | this.Desktops.Add(new VirtualDesktopViewModel(desktop)); 41 | Debug.WriteLine($"Created: {desktop.Name}"); 42 | }); 43 | }; 44 | 45 | VirtualDesktop.CurrentChanged += (_, args) => 46 | { 47 | foreach (var desktop in this.Desktops) desktop.IsCurrent = desktop.Id == args.NewDesktop.Id; 48 | Debug.WriteLine($"Switched: {args.OldDesktop.Name} -> {args.NewDesktop.Name}"); 49 | }; 50 | 51 | VirtualDesktop.Moved += (_, args) => 52 | { 53 | this.Dispatcher.Invoke(() => 54 | { 55 | this.Desktops.Move(args.OldIndex, args.NewIndex); 56 | Debug.WriteLine($"Moved: {args.OldIndex} -> {args.NewIndex}, {args.Desktop}"); 57 | }); 58 | }; 59 | 60 | VirtualDesktop.Destroyed += (_, args) => 61 | { 62 | this.Dispatcher.Invoke(() => 63 | { 64 | var target = this.Desktops.FirstOrDefault(x => x.Id == args.Destroyed.Id); 65 | if (target != null) this.Desktops.Remove(target); 66 | }); 67 | }; 68 | 69 | VirtualDesktop.Renamed += (_, args) => 70 | { 71 | var desktop = this.Desktops.FirstOrDefault(x => x.Id == args.Desktop.Id); 72 | if (desktop != null) desktop.Name = args.Name; 73 | Debug.WriteLine($"Renamed: {args.Desktop}"); 74 | }; 75 | 76 | VirtualDesktop.WallpaperChanged += (_, args) => 77 | { 78 | var desktop = this.Desktops.FirstOrDefault(x => x.Id == args.Desktop.Id); 79 | if (desktop != null) desktop.WallpaperPath = new Uri(args.Path); 80 | Debug.WriteLine($"Wallpaper changed: {args.Desktop}, {args.Path}"); 81 | }; 82 | 83 | var currentId = VirtualDesktop.Current.Id; 84 | 85 | foreach (var desktop in VirtualDesktop.GetDesktops()) 86 | { 87 | var vm = new VirtualDesktopViewModel(desktop); 88 | if (desktop.Id == currentId) vm.IsCurrent = true; 89 | 90 | this.Desktops.Add(vm); 91 | } 92 | } 93 | 94 | protected override void OnSourceInitialized(EventArgs e) 95 | { 96 | base.OnSourceInitialized(e); 97 | 98 | this._applicationViewChangedListener = VirtualDesktop.RegisterViewChanged(this.GetHandle(), handle => 99 | { 100 | this.Dispatcher.Invoke(() => 101 | { 102 | var parent = VirtualDesktop.FromHwnd(handle); 103 | foreach (var desktop in this.Desktops) 104 | { 105 | desktop.ShowcaseMessage = parent == null 106 | ? "(this window is pinned)" 107 | : desktop.Id == parent.Id 108 | ? "this window is here." 109 | : ""; 110 | } 111 | }); 112 | }); 113 | } 114 | 115 | protected override void OnClosed(EventArgs e) 116 | { 117 | this._applicationViewChangedListener?.Dispose(); 118 | base.OnClosed(e); 119 | } 120 | 121 | private void CreateNew(object sender, RoutedEventArgs e) 122 | => VirtualDesktop.Create(); 123 | 124 | private async void CreateNewAndMove(object sender, RoutedEventArgs e) 125 | { 126 | var desktop = VirtualDesktop.Create(); 127 | 128 | if (this.ThisWindowMenu.IsChecked ?? true) 129 | { 130 | desktop.SwitchAndMove(this); 131 | } 132 | else 133 | { 134 | await Task.Delay(_delay); 135 | 136 | var handle = GetForegroundWindow(); 137 | if (VirtualDesktop.IsPinnedWindow(handle) == false) VirtualDesktop.MoveToDesktop(handle, desktop); 138 | desktop.Switch(); 139 | } 140 | } 141 | 142 | private void SwitchLeft(object sender, RoutedEventArgs e) 143 | { 144 | VirtualDesktop.Current.GetLeft()?.Switch(); 145 | } 146 | 147 | private async void SwitchLeftAndMove(object sender, RoutedEventArgs e) 148 | { 149 | var left = VirtualDesktop.Current.GetLeft(); 150 | if (left == null) return; 151 | 152 | if (this.ThisWindowMenu.IsChecked ?? true) 153 | { 154 | left.SwitchAndMove(this); 155 | } 156 | else 157 | { 158 | await Task.Delay(_delay); 159 | 160 | var handle = GetForegroundWindow(); 161 | if (VirtualDesktop.IsPinnedWindow(handle) == false) VirtualDesktop.MoveToDesktop(handle, left); 162 | left.Switch(); 163 | } 164 | } 165 | 166 | private void SwitchRight(object sender, RoutedEventArgs e) 167 | { 168 | VirtualDesktop.Current.GetRight()?.Switch(); 169 | } 170 | 171 | private async void SwitchRightAndMove(object sender, RoutedEventArgs e) 172 | { 173 | var right = VirtualDesktop.Current.GetRight(); 174 | if (right == null) return; 175 | 176 | if (this.ThisWindowMenu.IsChecked ?? true) 177 | { 178 | right.SwitchAndMove(this); 179 | } 180 | else 181 | { 182 | await Task.Delay(_delay); 183 | 184 | var handle = GetForegroundWindow(); 185 | if (VirtualDesktop.IsPinnedWindow(handle) == false) VirtualDesktop.MoveToDesktop(handle, right); 186 | right.Switch(); 187 | } 188 | } 189 | 190 | private async void Pin(object sender, RoutedEventArgs e) 191 | { 192 | if (this.ThisWindowMenu.IsChecked ?? true) 193 | { 194 | this.TogglePin(); 195 | } 196 | else 197 | { 198 | await Task.Delay(_delay); 199 | 200 | var handle = GetForegroundWindow(); 201 | (VirtualDesktop.IsPinnedWindow(handle) ? VirtualDesktop.UnpinWindow : (Func)VirtualDesktop.PinWindow)(handle); 202 | } 203 | } 204 | 205 | private async void PinApp(object sender, RoutedEventArgs e) 206 | { 207 | if (this.ThisWindowMenu.IsChecked ?? true) 208 | { 209 | Application.Current.TogglePin(); 210 | } 211 | else 212 | { 213 | await Task.Delay(_delay); 214 | 215 | if (VirtualDesktop.TryGetAppUserModelId(GetForegroundWindow(), out var appId)) 216 | { 217 | (VirtualDesktop.IsPinnedApplication(appId) ? VirtualDesktop.UnpinApplication : (Func)VirtualDesktop.PinApplication)(appId); 218 | } 219 | } 220 | } 221 | 222 | private void Remove(object sender, RoutedEventArgs e) 223 | { 224 | VirtualDesktop.Current.Remove(); 225 | } 226 | 227 | private void SwitchDesktop(object sender, RoutedEventArgs e) 228 | { 229 | if (sender is Button { DataContext: VirtualDesktopViewModel vm }) 230 | { 231 | VirtualDesktop.FromId(vm.Id)?.SwitchAndMove(this); 232 | } 233 | } 234 | 235 | private void ChangeWallpaper(object sender, RoutedEventArgs e) 236 | { 237 | if (sender is Button { DataContext: VirtualDesktopViewModel vm }) 238 | { 239 | var dialog = new OpenFileDialog() 240 | { 241 | Title = "Select wallpaper", 242 | Filter = "Desktop wallpaper (*.jpg, *.png, *.bmp)|*.jpg;*.png;*.bmp", 243 | }; 244 | 245 | if ((dialog.ShowDialog(this) ?? false) 246 | && File.Exists(dialog.FileName)) 247 | { 248 | var desktop = VirtualDesktop.FromId(vm.Id); 249 | if (desktop != null) desktop.WallpaperPath = dialog.FileName; 250 | } 251 | } 252 | 253 | e.Handled = true; 254 | } 255 | 256 | [DllImport("user32.dll")] 257 | private static extern IntPtr GetForegroundWindow(); 258 | } 259 | 260 | public class VirtualDesktopViewModel : INotifyPropertyChanged 261 | { 262 | private string _name; 263 | private Uri? _wallpaperPath; 264 | private string _showcaseMessage; 265 | private bool _isCurrent; 266 | 267 | public event PropertyChangedEventHandler? PropertyChanged; 268 | 269 | public Guid Id { get; } 270 | 271 | public string Name 272 | { 273 | get => this._name; 274 | set 275 | { 276 | if (this._name != value) 277 | { 278 | this._name = value; 279 | this.OnPropertyChanged(); 280 | } 281 | } 282 | } 283 | 284 | public Uri? WallpaperPath 285 | { 286 | get => this._wallpaperPath; 287 | set 288 | { 289 | if (this._wallpaperPath != value) 290 | { 291 | this._wallpaperPath = value; 292 | this.OnPropertyChanged(); 293 | } 294 | } 295 | } 296 | 297 | public string ShowcaseMessage 298 | { 299 | get => this._showcaseMessage; 300 | set 301 | { 302 | if (this._showcaseMessage != value) 303 | { 304 | this._showcaseMessage = value; 305 | this.OnPropertyChanged(); 306 | } 307 | } 308 | } 309 | 310 | public bool IsCurrent 311 | { 312 | get => this._isCurrent; 313 | set 314 | { 315 | if (this._isCurrent != value) 316 | { 317 | this._isCurrent = value; 318 | this.OnPropertyChanged(); 319 | } 320 | } 321 | } 322 | 323 | public VirtualDesktopViewModel(VirtualDesktop source) 324 | { 325 | this._name = string.IsNullOrEmpty(source.Name) ? "(no name)" : source.Name; 326 | this._wallpaperPath = Uri.TryCreate(source.WallpaperPath, UriKind.Absolute, out var uri) ? uri : null; 327 | this._showcaseMessage = ""; 328 | this.Id = source.Id; 329 | } 330 | 331 | protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) 332 | { 333 | this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /samples/VirtualDesktop.Showcase/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Windows; 3 | 4 | [assembly: ComVisible(false)] 5 | [assembly: Guid("5B4544B8-3EF0-4E9F-8D60-DD605AD99725")] 6 | [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] 7 | -------------------------------------------------------------------------------- /samples/VirtualDesktop.Showcase/VirtualDesktop.Showcase.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows10.0.19041.0 6 | true 7 | enable 8 | VirtualDesktopShowcase 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/VirtualDesktop (LocalAppData).lnk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grabacr07/VirtualDesktop/a6c69e420307e0717f296501c1e7595977e27b6b/src/VirtualDesktop (LocalAppData).lnk -------------------------------------------------------------------------------- /src/VirtualDesktop.WPF/ApplicationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows; 5 | 6 | namespace WindowsDesktop; 7 | 8 | public static class ApplicationExtensions 9 | { 10 | /// 11 | /// Determines whether this application is pinned. 12 | /// 13 | /// if pinned, otherwise. 14 | public static bool IsPinned(this Application app) 15 | { 16 | return VirtualDesktop.TryGetAppUserModelId(app.GetWindowHandle(), out var appId) 17 | && VirtualDesktop.IsPinnedApplication(appId); 18 | } 19 | 20 | /// 21 | /// Pins an application, showing it on all virtual desktops. 22 | /// 23 | /// if already pinned or successfully pinned, otherwise (most of the time, main window is not found). 24 | public static bool Pin(this Application app) 25 | { 26 | return VirtualDesktop.TryGetAppUserModelId(app.GetWindowHandle(), out var appId) 27 | && VirtualDesktop.PinApplication(appId); 28 | } 29 | 30 | /// 31 | /// Unpins an application. 32 | /// 33 | /// if already unpinned or successfully unpinned, otherwise (most of the time, main window is not found). 34 | public static bool Unpin(this Application app) 35 | { 36 | return VirtualDesktop.TryGetAppUserModelId(app.GetWindowHandle(), out var appId) 37 | && VirtualDesktop.UnpinApplication(appId); 38 | } 39 | 40 | /// 41 | /// Toggles an application between being pinned and unpinned. 42 | /// 43 | /// if successfully toggled, otherwise (most of the time, main window is not found). 44 | public static bool TogglePin(this Application app) 45 | { 46 | if (VirtualDesktop.TryGetAppUserModelId(app.GetWindowHandle(), out var appId) == false) return false; 47 | 48 | return VirtualDesktop.IsPinnedApplication(appId) 49 | ? VirtualDesktop.UnpinApplication(appId) 50 | : VirtualDesktop.PinApplication(appId); 51 | } 52 | 53 | private static IntPtr GetWindowHandle(this Application app) 54 | => app.MainWindow?.GetHandle() 55 | ?? throw new InvalidOperationException(); 56 | } 57 | -------------------------------------------------------------------------------- /src/VirtualDesktop.WPF/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | [assembly: ComVisible(false)] 4 | [assembly: Guid("9dd597c6-065a-4764-a96c-1b18c4eded78")] 5 | -------------------------------------------------------------------------------- /src/VirtualDesktop.WPF/VirtualDesktop.WPF.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0-windows10.0.19041.0;net5.0-windows10.0.19041.0 5 | latest 6 | true 7 | enable 8 | enable 9 | 5.0.5 10 | VirtualDesktop 11 | Grabacr07 12 | grabacr.net 13 | Copyright © 2022 Manato KAMEYA 14 | C# Wrapper for the Virtual Desktop API on Windows 11 (and Windows 10). 15 | https://github.com/Grabacr07/VirtualDesktop 16 | https://github.com/Grabacr07/VirtualDesktop 17 | LICENSE 18 | README.md 19 | Windows;Windows10;Windows11;Desktop;VirtualDesktop; 20 | True 21 | WindowsDesktop 22 | 23 | 24 | 25 | 1701;1702;1591 26 | 27 | 28 | 29 | 1701;1702;1591 30 | 31 | 32 | 33 | 1701;1702;1591 34 | 35 | 36 | 37 | 1701;1702;1591 38 | 39 | 40 | 41 | 42 | True 43 | 44 | 45 | 46 | True 47 | \ 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/VirtualDesktop.WPF/WindowExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows; 5 | using System.Windows.Interop; 6 | using System.Windows.Media; 7 | 8 | namespace WindowsDesktop; 9 | 10 | public static class WindowExtensions 11 | { 12 | /// 13 | /// Determines whether this window is on the current virtual desktop. 14 | /// 15 | public static bool IsCurrentVirtualDesktop(this Window window) 16 | { 17 | return VirtualDesktop.IsCurrentVirtualDesktop(window.GetHandle()); 18 | } 19 | 20 | /// 21 | /// Returns the virtual desktop this window is located on. 22 | /// 23 | public static VirtualDesktop? GetCurrentDesktop(this Window window) 24 | { 25 | return VirtualDesktop.FromHwnd(window.GetHandle()); 26 | } 27 | 28 | /// 29 | /// Moves a window to the specified virtual desktop. 30 | /// 31 | public static void MoveToDesktop(this Window window, VirtualDesktop virtualDesktop) 32 | { 33 | VirtualDesktop.MoveToDesktop(window.GetHandle(), virtualDesktop); 34 | } 35 | 36 | /// 37 | /// Switches to a virtual desktop and moves the specified window to that desktop. 38 | /// 39 | /// The virtual desktop to move the window to. 40 | /// The window to move. 41 | public static void SwitchAndMove(this VirtualDesktop virtualDesktop, Window window) 42 | { 43 | if (window.IsPinned() == false) window.MoveToDesktop(virtualDesktop); 44 | virtualDesktop.Switch(); 45 | } 46 | 47 | /// 48 | /// Determines whether this window is pinned. 49 | /// 50 | /// if pinned, otherwise. 51 | public static bool IsPinned(this Window window) 52 | { 53 | return VirtualDesktop.IsPinnedWindow(window.GetHandle()); 54 | } 55 | 56 | /// 57 | /// Pins a window, showing it on all virtual desktops. 58 | /// 59 | /// if already pinned or successfully pinned, otherwise (most of the time, the target window is not found or not ready). 60 | public static bool Pin(this Window window) 61 | { 62 | return VirtualDesktop.PinWindow(window.GetHandle()); 63 | } 64 | 65 | /// 66 | /// Unpins a window. 67 | /// 68 | /// if already unpinned or successfully unpinned, otherwise (most of the time, the target window is not found or not ready). 69 | public static bool Unpin(this Window window) 70 | { 71 | return VirtualDesktop.UnpinWindow(window.GetHandle()); 72 | } 73 | 74 | /// 75 | /// Toggles a window between being pinned and unpinned. 76 | /// 77 | /// if successfully toggled, otherwise (most of the time, the target window is not found or not ready). 78 | public static bool TogglePin(this Window window) 79 | { 80 | var handle = window.GetHandle(); 81 | 82 | return VirtualDesktop.IsPinnedWindow(handle) 83 | ? VirtualDesktop.UnpinWindow(handle) 84 | : VirtualDesktop.PinWindow(handle); 85 | } 86 | 87 | /// 88 | /// Returns the window handle for this . 89 | /// 90 | public static IntPtr GetHandle(this Visual visual) 91 | => PresentationSource.FromVisual(visual) is HwndSource hwndSource 92 | ? hwndSource.Handle 93 | : throw new ArgumentException("Unable to get a window handle. Call it after the Window.SourceInitialized event is fired.", nameof(visual)); 94 | } 95 | -------------------------------------------------------------------------------- /src/VirtualDesktop.WinForms/FormExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows.Forms; 5 | 6 | namespace WindowsDesktop; 7 | 8 | public static class FormExtensions 9 | { 10 | /// 11 | /// Determines whether this form is on the current virtual desktop. 12 | /// 13 | public static bool IsCurrentVirtualDesktop(this Form form) 14 | { 15 | return VirtualDesktop.IsCurrentVirtualDesktop(form.Handle); 16 | } 17 | 18 | /// 19 | /// Moves a form to the specified virtual desktop. 20 | /// 21 | public static void MoveToDesktop(this Form form, VirtualDesktop virtualDesktop) 22 | { 23 | VirtualDesktop.MoveToDesktop(form.Handle, virtualDesktop); 24 | } 25 | 26 | /// 27 | /// Returns the virtual desktop this form is located on. 28 | /// 29 | public static VirtualDesktop? GetCurrentDesktop(this Form form) 30 | { 31 | return VirtualDesktop.FromHwnd(form.Handle); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/VirtualDesktop.WinForms/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | [assembly: ComVisible(false)] 4 | [assembly: Guid("da586cec-2ffe-42c5-aeda-56d25984c7ad")] 5 | -------------------------------------------------------------------------------- /src/VirtualDesktop.WinForms/VirtualDesktop.WinForms.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0-windows10.0.19041.0;net5.0-windows10.0.19041.0 5 | latest 6 | true 7 | enable 8 | enable 9 | 5.0.5 10 | VirtualDesktop 11 | Grabacr07 12 | grabacr.net 13 | Copyright © 2022 Manato KAMEYA 14 | C# Wrapper for the Virtual Desktop API on Windows 11 (and Windows 10). 15 | https://github.com/Grabacr07/VirtualDesktop 16 | https://github.com/Grabacr07/VirtualDesktop 17 | LICENSE 18 | README.md 19 | Windows;Windows10;Windows11;Desktop;VirtualDesktop; 20 | True 21 | WindowsDesktop 22 | 23 | 24 | 25 | 1701;1702;1591 26 | 27 | 28 | 29 | 1701;1702;1591 30 | 31 | 32 | 33 | 1701;1702;1591 34 | 35 | 36 | 37 | 1701;1702;1591 38 | 39 | 40 | 41 | 42 | True 43 | 44 | 45 | 46 | True 47 | \ 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/VirtualDesktop.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".samples", ".samples", "{17B4686F-1AFC-4D5E-BCD8-408DE4CEC3C3}" 7 | ProjectSection(SolutionItems) = preProject 8 | ..\samples\README.md = ..\samples\README.md 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualDesktop.WPF", "VirtualDesktop.WPF\VirtualDesktop.WPF.csproj", "{0C17C595-1669-4333-8153-A04676AA3EF3}" 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualDesktop.Showcase", "..\samples\VirtualDesktop.Showcase\VirtualDesktop.Showcase.csproj", "{EEF46F33-3DDB-4D77-9054-4273332745B4}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualDesktop.WinForms", "VirtualDesktop.WinForms\VirtualDesktop.WinForms.csproj", "{4B6FB0EB-943E-42C9-8CC9-990D84A2EEDB}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualDesktop", "VirtualDesktop\VirtualDesktop.csproj", "{B8A37B59-0F48-4A44-ADF7-96C54AC6CE61}" 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".config", ".config", "{335CE9BA-A467-4667-8707-D4B779EC8005}" 20 | ProjectSection(SolutionItems) = preProject 21 | ..\.editorconfig = ..\.editorconfig 22 | ..\.gitattributes = ..\.gitattributes 23 | ..\.gitignore = ..\.gitignore 24 | EndProjectSection 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{A58CDA13-2F14-4F77-AFF3-6F91C7993E5B}" 27 | ProjectSection(SolutionItems) = preProject 28 | ..\.github\workflows\build.yml = ..\.github\workflows\build.yml 29 | ..\.github\workflows\publish.yml = ..\.github\workflows\publish.yml 30 | EndProjectSection 31 | EndProject 32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".docs", ".docs", "{8A097A9D-BF69-424A-8477-24FD5FE334EF}" 33 | ProjectSection(SolutionItems) = preProject 34 | ..\README.md = ..\README.md 35 | EndProjectSection 36 | EndProject 37 | Global 38 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 39 | Debug|Any CPU = Debug|Any CPU 40 | Release|Any CPU = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 43 | {0C17C595-1669-4333-8153-A04676AA3EF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {0C17C595-1669-4333-8153-A04676AA3EF3}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {0C17C595-1669-4333-8153-A04676AA3EF3}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {0C17C595-1669-4333-8153-A04676AA3EF3}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {EEF46F33-3DDB-4D77-9054-4273332745B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {EEF46F33-3DDB-4D77-9054-4273332745B4}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {EEF46F33-3DDB-4D77-9054-4273332745B4}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {4B6FB0EB-943E-42C9-8CC9-990D84A2EEDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {4B6FB0EB-943E-42C9-8CC9-990D84A2EEDB}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {4B6FB0EB-943E-42C9-8CC9-990D84A2EEDB}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {4B6FB0EB-943E-42C9-8CC9-990D84A2EEDB}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {B8A37B59-0F48-4A44-ADF7-96C54AC6CE61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {B8A37B59-0F48-4A44-ADF7-96C54AC6CE61}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {B8A37B59-0F48-4A44-ADF7-96C54AC6CE61}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {B8A37B59-0F48-4A44-ADF7-96C54AC6CE61}.Release|Any CPU.Build.0 = Release|Any CPU 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(NestedProjects) = preSolution 63 | {EEF46F33-3DDB-4D77-9054-4273332745B4} = {17B4686F-1AFC-4D5E-BCD8-408DE4CEC3C3} 64 | EndGlobalSection 65 | GlobalSection(ExtensibilityGlobals) = postSolution 66 | SolutionGuid = {0EDF8EBE-5CA7-414F-B7EA-04FC9D5D0FE7} 67 | EndGlobalSection 68 | EndGlobal 69 | -------------------------------------------------------------------------------- /src/VirtualDesktop.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | SUGGESTION 4 | SUGGESTION 5 | SUGGESTION 6 | SUGGESTION 7 | SUGGESTION 8 | DO_NOT_SHOW 9 | DO_NOT_SHOW 10 | DO_NOT_SHOW 11 | DO_NOT_SHOW 12 | DO_NOT_SHOW 13 | HINT 14 | HINT 15 | HINT 16 | HINT 17 | HINT 18 | HINT 19 | HINT 20 | HINT 21 | HINT 22 | HINT 23 | HINT 24 | HINT 25 | HINT 26 | HINT 27 | HINT 28 | SUGGESTION 29 | SUGGESTION 30 | SUGGESTION 31 | <?xml version="1.0" encoding="utf-16"?><Profile name="Default"><CSReorderTypeMembers>True</CSReorderTypeMembers><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="False" AddMissingParentheses="False" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="False" /><CSEnforceVarKeywordUsageSettings>True</CSEnforceVarKeywordUsageSettings><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><XMLReformatCode>True</XMLReformatCode></Profile> 32 | NEVER 33 | NEVER 34 | NEVER 35 | ALWAYS 36 | False 37 | True 38 | ZeroIndent 39 | OnSingleLine 40 | OnSingleLine 41 | False 42 | False 43 | 44 | 45 | 2 46 | True 47 | OnSingleLine 48 | 2 49 | FirstAttributeOnSingleLine 50 | 51 | False 52 | System 53 | System.Collections.Generic 54 | System.Linq 55 | ID 56 | IID 57 | 58 | 59 | OS 60 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AA_BB" /></Policy> 61 | <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> 62 | <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> 63 | 64 | 1 65 | 1 66 | 1 67 | ON_SINGLE_LINE 68 | False 69 | 70 | 71 | 72 | CHOP_ALWAYS 73 | CHOP_IF_LONG 74 | True 75 | 76 | False 77 | False 78 | False 79 | False 80 | False 81 | False 82 | True 83 | True 84 | True 85 | True 86 | False 87 | False 88 | True 89 | True 90 | 15 91 | 996 92 | Required 93 | Required 94 | Required 95 | NotRequired 96 | 97 | 98 | OneStep 99 | ZeroIndent 100 | <?xml version="1.0" encoding="utf-16"?> 101 | <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> 102 | <TypePattern DisplayName="COM interfaces or structs"> 103 | <TypePattern.Match> 104 | <Or> 105 | <And> 106 | <Kind Is="Interface" /> 107 | <Or> 108 | <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> 109 | <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> 110 | </Or> 111 | </And> 112 | <Kind Is="Struct" /> 113 | </Or> 114 | </TypePattern.Match> 115 | </TypePattern> 116 | <TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"> 117 | <TypePattern.Match> 118 | <And> 119 | <Kind Is="Class" /> 120 | <HasMember> 121 | <And> 122 | <Kind Is="Method" /> 123 | <HasAttribute Name="Xunit.FactAttribute" Inherited="True" /> 124 | </And> 125 | </HasMember> 126 | </And> 127 | </TypePattern.Match> 128 | <Entry DisplayName="Setup/Teardown Methods"> 129 | <Entry.Match> 130 | <Or> 131 | <Kind Is="Constructor" /> 132 | <And> 133 | <Kind Is="Method" /> 134 | <ImplementsInterface Name="System.IDisposable" /> 135 | </And> 136 | </Or> 137 | </Entry.Match> 138 | <Entry.SortBy> 139 | <Kind Order="Constructor" /> 140 | </Entry.SortBy> 141 | </Entry> 142 | <Entry DisplayName="All other members" /> 143 | <Entry Priority="100" DisplayName="Test Methods"> 144 | <Entry.Match> 145 | <And> 146 | <Kind Is="Method" /> 147 | <HasAttribute Name="Xunit.FactAttribute" /> 148 | </And> 149 | </Entry.Match> 150 | <Entry.SortBy> 151 | <Name /> 152 | </Entry.SortBy> 153 | </Entry> 154 | </TypePattern> 155 | <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> 156 | <TypePattern.Match> 157 | <And> 158 | <Kind Is="Class" /> 159 | <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> 160 | </And> 161 | </TypePattern.Match> 162 | <Entry DisplayName="Setup/Teardown Methods"> 163 | <Entry.Match> 164 | <And> 165 | <Kind Is="Method" /> 166 | <Or> 167 | <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> 168 | <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> 169 | <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> 170 | <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> 171 | </Or> 172 | </And> 173 | </Entry.Match> 174 | </Entry> 175 | <Entry DisplayName="All other members" /> 176 | <Entry Priority="100" DisplayName="Test Methods"> 177 | <Entry.Match> 178 | <And> 179 | <Kind Is="Method" /> 180 | <HasAttribute Name="NUnit.Framework.TestAttribute" /> 181 | </And> 182 | </Entry.Match> 183 | <Entry.SortBy> 184 | <Name /> 185 | </Entry.SortBy> 186 | </Entry> 187 | </TypePattern> 188 | <TypePattern DisplayName="Default Pattern"> 189 | <Entry DisplayName="Namespaces"> 190 | <Entry.Match> 191 | <Kind Is="Namespace" /> 192 | </Entry.Match> 193 | </Entry> 194 | <Entry DisplayName="Classes"> 195 | <Entry.Match> 196 | <Kind Is="Class" /> 197 | </Entry.Match> 198 | </Entry> 199 | <Entry DisplayName="Structs"> 200 | <Entry.Match> 201 | <Kind Is="Struct" /> 202 | </Entry.Match> 203 | </Entry> 204 | <Entry DisplayName="Interfaces"> 205 | <Entry.Match> 206 | <Kind Is="Interface" /> 207 | </Entry.Match> 208 | </Entry> 209 | <Entry DisplayName="Delegates"> 210 | <Entry.Match> 211 | <Kind Is="Delegate" /> 212 | </Entry.Match> 213 | </Entry> 214 | <Entry DisplayName="Enums"> 215 | <Entry.Match> 216 | <Kind Is="Enum" /> 217 | </Entry.Match> 218 | </Entry> 219 | <Entry DisplayName="Constants"> 220 | <Entry.Match> 221 | <Kind Is="Constant" /> 222 | </Entry.Match> 223 | </Entry> 224 | <Entry DisplayName="Fields"> 225 | <Entry.Match> 226 | <And> 227 | <Kind Is="Field" /> 228 | <Not> 229 | <Static /> 230 | </Not> 231 | </And> 232 | </Entry.Match> 233 | <Entry.SortBy> 234 | <Static /> 235 | <Readonly /> 236 | </Entry.SortBy> 237 | </Entry> 238 | <Entry DisplayName="Indexers"> 239 | <Entry.Match> 240 | <And> 241 | <Kind Is="Indexer" /> 242 | </And> 243 | </Entry.Match> 244 | <Entry.SortBy> 245 | <Static /> 246 | <ImplementsInterface /> 247 | </Entry.SortBy> 248 | </Entry> 249 | <Entry DisplayName="Properties"> 250 | <Entry.Match> 251 | <And> 252 | <Kind Is="Property" /> 253 | </And> 254 | </Entry.Match> 255 | <Entry.SortBy> 256 | <Static /> 257 | <ImplementsInterface /> 258 | </Entry.SortBy> 259 | </Entry> 260 | <Entry DisplayName="Events"> 261 | <Entry.Match> 262 | <Kind Is="Event" /> 263 | </Entry.Match> 264 | <Entry.SortBy> 265 | <Static /> 266 | <ImplementsInterface /> 267 | </Entry.SortBy> 268 | </Entry> 269 | <Entry DisplayName="Operators"> 270 | <Entry.Match> 271 | <Kind Is="Operator" /> 272 | </Entry.Match> 273 | </Entry> 274 | <Entry DisplayName="Constructors"> 275 | <Entry.Match> 276 | <Kind Is="Constructor" /> 277 | </Entry.Match> 278 | </Entry> 279 | <Entry DisplayName="Destructor"> 280 | <Entry.Match> 281 | <Kind Is="Destructor" /> 282 | </Entry.Match> 283 | </Entry> 284 | <Entry DisplayName="All other members"> 285 | <Entry.SortBy> 286 | <Kind Is="Member" /> 287 | <Static /> 288 | <ImplementsInterface /> 289 | </Entry.SortBy> 290 | </Entry> 291 | <Region Name="Implicit Implementations"> 292 | <Entry Priority="100" DisplayName="Implicit Interface Implementations"> 293 | <Entry.Match> 294 | <And> 295 | <Kind Is="Member" /> 296 | <ImplementsInterface /> 297 | <Access Is="Private" /> 298 | </And> 299 | </Entry.Match> 300 | <Entry.SortBy> 301 | <ImplementsInterface Immediate="True" /> 302 | </Entry.SortBy> 303 | </Entry> 304 | </Region> 305 | </TypePattern> 306 | </Patterns> 307 | True 308 | True 309 | True 310 | True 311 | True 312 | True 313 | True 314 | True 315 | True 316 | True 317 | True 318 | True 319 | (?<=\b)(?<TAG>NotImplementedException|SqlNotImplemented)(\b)(.*) 320 | True 321 | True 322 | Red 323 | True 324 | True 325 | True 326 | To be determined 327 | \bTBD\b 328 | Edit 329 | True 330 | True 331 | True 332 | True 333 | True 334 | True 335 | True 336 | True 337 | True 338 | True 339 | True 340 | True 341 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/.Provider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using WindowsDesktop.Interop.Proxy; 5 | using WindowsDesktop.Properties; 6 | 7 | namespace WindowsDesktop.Interop.Build10240; 8 | 9 | internal class VirtualDesktopProvider10240 : VirtualDesktopProvider 10 | { 11 | private IVirtualDesktopManager? _virtualDesktopManager; 12 | private ApplicationViewCollection? _applicationViewCollection; 13 | private VirtualDesktopManagerInternal? _virtualDesktopManagerInternal; 14 | private VirtualDesktopPinnedApps? _virtualDesktopPinnedApps; 15 | private VirtualDesktopNotificationService? _virtualDesktopNotificationService; 16 | 17 | public override IApplicationViewCollection ApplicationViewCollection 18 | => this._applicationViewCollection ?? throw InitializationIsRequired; 19 | 20 | public override IVirtualDesktopManager VirtualDesktopManager 21 | => this._virtualDesktopManager ?? throw InitializationIsRequired; 22 | 23 | public override IVirtualDesktopManagerInternal VirtualDesktopManagerInternal 24 | => this._virtualDesktopManagerInternal ?? throw InitializationIsRequired; 25 | 26 | public override IVirtualDesktopPinnedApps VirtualDesktopPinnedApps 27 | => this._virtualDesktopPinnedApps ?? throw InitializationIsRequired; 28 | 29 | public override IVirtualDesktopNotificationService VirtualDesktopNotificationService 30 | => this._virtualDesktopNotificationService ?? throw InitializationIsRequired; 31 | 32 | private protected override void InitializeCore(ComInterfaceAssembly assembly) 33 | { 34 | var type = Type.GetTypeFromCLSID(CLSID.VirtualDesktopManager) 35 | ?? throw new Exception($"No type found for CLSID '{CLSID.VirtualDesktopManager}'."); 36 | this._virtualDesktopManager = Activator.CreateInstance(type) is IVirtualDesktopManager manager 37 | ? manager 38 | : throw new Exception($"Failed to create instance of Type '{typeof(IVirtualDesktopManager)}'."); 39 | 40 | this._applicationViewCollection = new ApplicationViewCollection(assembly); 41 | var factory = new ComWrapperFactory( 42 | x => new ApplicationView(assembly, x), 43 | x => this._applicationViewCollection.GetViewForHwnd(x), 44 | x => new VirtualDesktop(assembly, x)); 45 | this._virtualDesktopManagerInternal = new VirtualDesktopManagerInternal(assembly, factory); 46 | this._virtualDesktopPinnedApps = new VirtualDesktopPinnedApps(assembly, factory); 47 | this._virtualDesktopNotificationService = new VirtualDesktopNotificationService(assembly, factory); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/.interfaces/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("VirtualDesktop.{OS_BUILD}.generated")] 5 | [assembly: AssemblyCompany("grabacr.net")] 6 | [assembly: AssemblyProduct("VirtualDesktop")] 7 | [assembly: AssemblyDescription("COM interface definitions for virtual desktop on Windows 11 build {VERSION}.")] 8 | [assembly: AssemblyCopyright("Copyright © 2022 Manato KAMEYA")] 9 | 10 | [assembly: AssemblyVersion("{ASSEMBLY_VERSION}")] 11 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/.interfaces/IApplicationView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace WindowsDesktop.Interop.Build10240 5 | { 6 | // ## Fixes for breaking changes in .NET 5 7 | // * InterfaceIsIInspectable -> InterfaceIsIUnknown 8 | // * Add three dummy entries to the start of the interface; Proc3() - Proc5() 9 | // 10 | // see also: https://docs.microsoft.com/en-us/dotnet/core/compatibility/interop/5.0/casting-rcw-to-inspectable-interface-throws-exception 11 | 12 | [ComImport] 13 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 14 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 15 | public interface IApplicationView 16 | { 17 | void Proc3(); 18 | 19 | void Proc4(); 20 | 21 | void Proc5(); 22 | 23 | void SetFocus(); 24 | 25 | void SwitchTo(); 26 | 27 | void TryInvokeBack(IntPtr callback); 28 | 29 | IntPtr GetThumbnailWindow(); 30 | 31 | IntPtr GetMonitor(); 32 | 33 | int GetVisibility(); 34 | 35 | void SetCloak(ApplicationViewCloakType cloakType, int unknown); 36 | 37 | IntPtr GetPosition(in Guid guid, out IntPtr position); 38 | 39 | void SetPosition(in IntPtr position); 40 | 41 | void InsertAfterWindow(IntPtr hwnd); 42 | 43 | Rect GetExtendedFramePosition(); 44 | 45 | [return: MarshalAs(UnmanagedType.LPWStr)] 46 | string GetAppUserModelId(); 47 | 48 | void SetAppUserModelId([MarshalAs(UnmanagedType.LPWStr)] string id); 49 | 50 | bool IsEqualByAppUserModelId(string id); 51 | 52 | uint GetViewState(); 53 | 54 | void SetViewState(uint state); 55 | 56 | int GetNeediness(); 57 | 58 | ulong GetLastActivationTimestamp(); 59 | 60 | void SetLastActivationTimestamp(ulong timestamp); 61 | 62 | Guid GetVirtualDesktopId(); 63 | 64 | void SetVirtualDesktopId(in Guid guid); 65 | 66 | int GetShowInSwitchers(); 67 | 68 | void SetShowInSwitchers(int flag); 69 | 70 | int GetScaleFactor(); 71 | 72 | bool CanReceiveInput(); 73 | 74 | ApplicationViewCompatibilityPolicy GetCompatibilityPolicyType(); 75 | 76 | void SetCompatibilityPolicyType(ApplicationViewCompatibilityPolicy flags); 77 | 78 | IntPtr GetPositionPriority(); 79 | 80 | void SetPositionPriority(IntPtr priority); 81 | 82 | void GetSizeConstraints(IntPtr monitor, out Size size1, out Size size2); 83 | 84 | void GetSizeConstraintsForDpi(uint uint1, out Size size1, out Size size2); 85 | 86 | void SetSizeConstraintsForDpi(ref uint uint1, in Size size1, in Size size2); 87 | 88 | int QuerySizeConstraintsFromApp(); 89 | 90 | void OnMinSizePreferencesUpdated(IntPtr hwnd); 91 | 92 | void ApplyOperation(IntPtr operation); 93 | 94 | bool IsTray(); 95 | 96 | bool IsInHighZOrderBand(); 97 | 98 | bool IsSplashScreenPresented(); 99 | 100 | void Flash(); 101 | 102 | IApplicationView GetRootSwitchableOwner(); 103 | 104 | IObjectArray EnumerateOwnershipTree(); 105 | 106 | [return: MarshalAs(UnmanagedType.LPWStr)] 107 | string GetEnterpriseId(); 108 | 109 | bool IsMirrored(); 110 | } 111 | 112 | [StructLayout(LayoutKind.Sequential)] 113 | public struct Size 114 | { 115 | public int X; 116 | public int Y; 117 | } 118 | 119 | [StructLayout(LayoutKind.Sequential)] 120 | public struct Rect 121 | { 122 | public int Left; 123 | public int Top; 124 | public int Right; 125 | public int Bottom; 126 | } 127 | 128 | public enum ApplicationViewCloakType 129 | { 130 | AVCT_NONE = 0, 131 | AVCT_DEFAULT = 1, 132 | AVCT_VIRTUAL_DESKTOP = 2 133 | } 134 | 135 | public enum ApplicationViewCompatibilityPolicy 136 | { 137 | AVCP_NONE = 0, 138 | AVCP_SMALL_SCREEN = 1, 139 | AVCP_TABLET_SMALL_SCREEN = 2, 140 | AVCP_VERY_SMALL_SCREEN = 3, 141 | AVCP_HIGH_SCALE_FACTOR = 4 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/.interfaces/IApplicationViewCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace WindowsDesktop.Interop.Build10240 5 | { 6 | [ComImport] 7 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 8 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 9 | public interface IApplicationViewCollection 10 | { 11 | IObjectArray GetViews(); 12 | 13 | IObjectArray GetViewsByZOrder(); 14 | 15 | IObjectArray GetViewsByAppUserModelId(string id); 16 | 17 | IApplicationView GetViewForHwnd(IntPtr hwnd); 18 | 19 | IApplicationView GetViewForApplication(object application); 20 | 21 | IApplicationView GetViewForAppUserModelId(string id); 22 | 23 | IntPtr GetViewInFocus(); 24 | 25 | void RefreshCollection(); 26 | 27 | int RegisterForApplicationViewChanges(object listener); 28 | 29 | int RegisterForApplicationViewPositionChanges(object listener); 30 | 31 | void UnregisterForApplicationViewChanges(int cookie); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/.interfaces/IVirtualDesktop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace WindowsDesktop.Interop.Build10240 5 | { 6 | [ComImport] 7 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 8 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 9 | public interface IVirtualDesktop 10 | { 11 | bool IsViewVisible(IApplicationView view); 12 | 13 | Guid GetID(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/.interfaces/IVirtualDesktopManagerInternal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace WindowsDesktop.Interop.Build10240 5 | { 6 | [ComImport] 7 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 8 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 9 | public interface IVirtualDesktopManagerInternal 10 | { 11 | int GetCount(); 12 | 13 | void MoveViewToDesktop(IApplicationView pView, IVirtualDesktop desktop); 14 | 15 | bool CanViewMoveDesktops(IApplicationView pView); 16 | 17 | IVirtualDesktop GetCurrentDesktop(); 18 | 19 | IObjectArray GetDesktops(); 20 | 21 | IVirtualDesktop GetAdjacentDesktop(IVirtualDesktop pDesktopReference, int uDirection); 22 | 23 | void SwitchDesktop(IVirtualDesktop desktop); 24 | 25 | IVirtualDesktop CreateDesktop(); 26 | 27 | void RemoveDesktop(IVirtualDesktop pRemove, IVirtualDesktop pFallbackDesktop); 28 | 29 | IVirtualDesktop FindDesktop(in Guid desktopId); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/.interfaces/IVirtualDesktopNotification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace WindowsDesktop.Interop.Build10240 5 | { 6 | [ComImport] 7 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 8 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 9 | public interface IVirtualDesktopNotification 10 | { 11 | void VirtualDesktopCreated(IVirtualDesktop pDesktop); 12 | 13 | void VirtualDesktopDestroyBegin(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback); 14 | 15 | void VirtualDesktopDestroyFailed(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback); 16 | 17 | void VirtualDesktopDestroyed(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback); 18 | 19 | void ViewVirtualDesktopChanged(IApplicationView pView); 20 | 21 | void CurrentVirtualDesktopChanged(IVirtualDesktop pDesktopOld, IVirtualDesktop pDesktopNew); 22 | } 23 | 24 | internal class VirtualDesktopNotification : VirtualDesktopNotificationService.EventListenerBase, IVirtualDesktopNotification 25 | { 26 | public void VirtualDesktopCreated(IVirtualDesktop pDesktop) 27 | { 28 | this.CreatedCore(pDesktop); 29 | } 30 | 31 | public void VirtualDesktopDestroyBegin(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback) 32 | { 33 | this.DestroyBeginCore(pDesktopDestroyed, pDesktopFallback); 34 | } 35 | 36 | public void VirtualDesktopDestroyFailed(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback) 37 | { 38 | this.DestroyFailedCore(pDesktopDestroyed, pDesktopFallback); 39 | } 40 | 41 | public void VirtualDesktopDestroyed(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback) 42 | { 43 | this.DestroyedCore(pDesktopDestroyed, pDesktopFallback); 44 | } 45 | 46 | public void ViewVirtualDesktopChanged(IApplicationView pView) 47 | { 48 | this.ViewChangedCore(pView); 49 | } 50 | 51 | public void CurrentVirtualDesktopChanged(IVirtualDesktop pDesktopOld, IVirtualDesktop pDesktopNew) 52 | { 53 | this.CurrentChangedCore(pDesktopOld, pDesktopNew); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/.interfaces/IVirtualDesktopNotificationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace WindowsDesktop.Interop.Build10240 5 | { 6 | [ComImport] 7 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 8 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 9 | public interface IVirtualDesktopNotificationService 10 | { 11 | uint Register(IVirtualDesktopNotification pNotification); 12 | 13 | void Unregister(uint dwCookie); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/.interfaces/IVirtualDesktopPinnedApps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace WindowsDesktop.Interop.Build10240 5 | { 6 | [ComImport] 7 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 8 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 9 | public interface IVirtualDesktopPinnedApps 10 | { 11 | bool IsAppIdPinned(string appId); 12 | 13 | void PinAppID(string appId); 14 | 15 | void UnpinAppID(string appId); 16 | 17 | bool IsViewPinned(IApplicationView applicationView); 18 | 19 | void PinView(IApplicationView applicationView); 20 | 21 | void UnpinView(IApplicationView applicationView); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/ApplicationView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using WindowsDesktop.Interop.Proxy; 5 | 6 | namespace WindowsDesktop.Interop.Build10240; 7 | 8 | internal class ApplicationView : ComWrapperBase, IApplicationView 9 | { 10 | public ApplicationView(ComInterfaceAssembly assembly, object comObject) 11 | : base(assembly, comObject) 12 | { 13 | } 14 | 15 | public IntPtr GetThumbnailWindow() 16 | => this.InvokeMethod(); 17 | 18 | public string GetAppUserModelId() 19 | => this.InvokeMethod() ?? throw new Exception("Failed to get AppUserModelId."); 20 | 21 | public Guid GetVirtualDesktopId() 22 | => this.InvokeMethod(); 23 | } 24 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/ApplicationViewCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WindowsDesktop.Interop.Proxy; 3 | 4 | namespace WindowsDesktop.Interop.Build10240; 5 | 6 | internal class ApplicationViewCollection : ComWrapperBase, IApplicationViewCollection 7 | { 8 | public ApplicationViewCollection(ComInterfaceAssembly assembly) 9 | : base(assembly) 10 | { 11 | } 12 | 13 | public ApplicationView GetViewForHwnd(IntPtr hWnd) 14 | { 15 | var view = this.InvokeMethod(Args(hWnd)) 16 | ?? new ArgumentException("ApplicationView is not found.", nameof(hWnd)); 17 | 18 | return new ApplicationView(this.ComInterfaceAssembly, view); 19 | } 20 | 21 | IApplicationView IApplicationViewCollection.GetViewForHwnd(IntPtr hWnd) 22 | => this.GetViewForHwnd(hWnd); 23 | } 24 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/VirtualDesktop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using WindowsDesktop.Interop.Proxy; 5 | 6 | namespace WindowsDesktop.Interop.Build10240; 7 | 8 | internal class VirtualDesktop : ComWrapperBase, IVirtualDesktop 9 | { 10 | private Guid? _id; 11 | 12 | public VirtualDesktop(ComInterfaceAssembly assembly, object comObject) 13 | : base(assembly, comObject) 14 | { 15 | } 16 | 17 | public bool IsViewVisible(IntPtr hWnd) 18 | => this.InvokeMethod(Args(hWnd)); 19 | 20 | public Guid GetID() 21 | => this._id ?? (Guid)(this._id = this.InvokeMethod()); 22 | 23 | public string GetName() 24 | => ""; 25 | 26 | public string GetWallpaperPath() 27 | => ""; 28 | } 29 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/VirtualDesktopManagerInternal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using WindowsDesktop.Interop.Proxy; 5 | 6 | namespace WindowsDesktop.Interop.Build10240; 7 | 8 | internal class VirtualDesktopManagerInternal : ComWrapperBase, IVirtualDesktopManagerInternal 9 | { 10 | private readonly ComWrapperFactory _factory; 11 | 12 | public VirtualDesktopManagerInternal(ComInterfaceAssembly assembly, ComWrapperFactory factory) 13 | : base(assembly, CLSID.VirtualDesktopManagerInternal) 14 | { 15 | this._factory = factory; 16 | } 17 | 18 | public IEnumerable GetDesktops() 19 | { 20 | var array = this.InvokeMethod(); 21 | if (array == null) yield break; 22 | 23 | var count = array.GetCount(); 24 | var vdType = this.ComInterfaceAssembly.GetType(nameof(IVirtualDesktop)); 25 | 26 | for (var i = 0u; i < count; i++) 27 | { 28 | var ppvObject = array.GetAt(i, vdType.GUID); 29 | yield return new VirtualDesktop(this.ComInterfaceAssembly, ppvObject); 30 | } 31 | } 32 | 33 | public IVirtualDesktop GetCurrentDesktop() 34 | => this.InvokeMethodAndWrap(); 35 | 36 | public IVirtualDesktop GetAdjacentDesktop(IVirtualDesktop pDesktopReference, AdjacentDesktop uDirection) 37 | => this.InvokeMethodAndWrap(Args(((VirtualDesktop)pDesktopReference).ComObject, uDirection)); 38 | 39 | public IVirtualDesktop FindDesktop(Guid desktopId) 40 | => this.InvokeMethodAndWrap(Args(desktopId)); 41 | 42 | public IVirtualDesktop CreateDesktop() 43 | => this.InvokeMethodAndWrap(); 44 | 45 | public void SwitchDesktop(IVirtualDesktop desktop) 46 | => this.InvokeMethod(Args(((VirtualDesktop)desktop).ComObject)); 47 | 48 | public void RemoveDesktop(IVirtualDesktop pRemove, IVirtualDesktop pFallbackDesktop) 49 | => this.InvokeMethod(Args(((VirtualDesktop)pRemove).ComObject, ((VirtualDesktop)pFallbackDesktop).ComObject)); 50 | 51 | public void MoveViewToDesktop(IntPtr hWnd, IVirtualDesktop desktop) 52 | => this.InvokeMethod(Args(this._factory.ApplicationViewFromHwnd(hWnd).ComObject, ((VirtualDesktop)desktop).ComObject)); 53 | 54 | public void SetDesktopName(IVirtualDesktop desktop, string name) 55 | => throw new NotSupportedException(); 56 | 57 | public void SetDesktopWallpaper(IVirtualDesktop desktop, string path) 58 | => throw new NotSupportedException(); 59 | 60 | public void UpdateWallpaperPathForAllDesktops(string path) 61 | => throw new NotSupportedException(); 62 | 63 | private VirtualDesktop InvokeMethodAndWrap(object?[]? parameters = null, [CallerMemberName] string methodName = "") 64 | => new(this.ComInterfaceAssembly, this.InvokeMethod(parameters, methodName) ?? throw new Exception("Failed to get IVirtualDesktop instance.")); 65 | } 66 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/VirtualDesktopNotificationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using WindowsDesktop.Interop.Proxy; 6 | using WindowsDesktop.Utils; 7 | 8 | namespace WindowsDesktop.Interop.Build10240; 9 | 10 | public class VirtualDesktopNotificationService : ComWrapperBase, IVirtualDesktopNotificationService 11 | { 12 | private readonly ComWrapperFactory _factory; 13 | 14 | internal VirtualDesktopNotificationService(ComInterfaceAssembly assembly, ComWrapperFactory factory) 15 | : base(assembly, CLSID.VirtualDesktopNotificationService) 16 | { 17 | this._factory = factory; 18 | } 19 | 20 | public IDisposable Register(IVirtualDesktopNotification notification) 21 | { 22 | var type = this.ComInterfaceAssembly.GetType("VirtualDesktopNotification"); 23 | var listener = Activator.CreateInstance(type) as EventListenerBase 24 | ?? throw new Exception($"{nameof(EventListenerBase)} inheritance type is not found in the COM interface assembly."); 25 | 26 | listener.Notification = notification; 27 | listener.Factory = this._factory; 28 | 29 | var dwCookie = this.InvokeMethod(Args(listener)); 30 | return Disposable.Create(() => this.Unregister(dwCookie)); 31 | } 32 | 33 | private void Unregister(uint dwCookie) 34 | { 35 | try 36 | { 37 | this.InvokeMethod(Args(dwCookie)); 38 | } 39 | catch (COMException ex) when (ex.Match(HResult.RPC_S_SERVER_UNAVAILABLE)) 40 | { 41 | // Nothing particular to do. 42 | } 43 | } 44 | 45 | public abstract class EventListenerBase 46 | { 47 | internal ComWrapperFactory Factory { get; set; } = null!; 48 | 49 | internal IVirtualDesktopNotification Notification { get; set; } = null!; 50 | 51 | protected void CreatedCore(object pDesktop) 52 | => this.Notification.VirtualDesktopCreated(this.Wrap(pDesktop)); 53 | 54 | protected void DestroyBeginCore(object pDesktopDestroyed, object pDesktopFallback) 55 | => this.Notification.VirtualDesktopDestroyBegin(this.Wrap(pDesktopDestroyed), this.Wrap(pDesktopFallback)); 56 | 57 | protected void DestroyFailedCore(object pDesktopDestroyed, object pDesktopFallback) 58 | => this.Notification.VirtualDesktopDestroyFailed(this.Wrap(pDesktopDestroyed), this.Wrap(pDesktopFallback)); 59 | 60 | protected void DestroyedCore(object pDesktopDestroyed, object pDesktopFallback) 61 | => this.Notification.VirtualDesktopDestroyed(this.Wrap(pDesktopDestroyed), this.Wrap(pDesktopFallback)); 62 | 63 | protected void ViewChangedCore(object view) 64 | => this.Notification.ViewVirtualDesktopChanged(this.Factory.ApplicationView(view).Interface); 65 | 66 | protected void CurrentChangedCore(object pDesktopOld, object pDesktopNew) 67 | => this.Notification.CurrentVirtualDesktopChanged(this.Wrap(pDesktopOld), this.Wrap(pDesktopNew)); 68 | 69 | private IVirtualDesktop Wrap(object desktop) 70 | => this.Factory.VirtualDesktop(desktop).Interface; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build10240/VirtualDesktopPinnedApps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using WindowsDesktop.Interop.Proxy; 5 | 6 | namespace WindowsDesktop.Interop.Build10240; 7 | 8 | internal class VirtualDesktopPinnedApps : ComWrapperBase, IVirtualDesktopPinnedApps 9 | { 10 | private readonly ComWrapperFactory _factory; 11 | 12 | public VirtualDesktopPinnedApps(ComInterfaceAssembly assembly, ComWrapperFactory factory) 13 | : base(assembly, CLSID.VirtualDesktopPinnedApps) 14 | { 15 | this._factory = factory; 16 | } 17 | 18 | public bool IsViewPinned(IntPtr hWnd) 19 | => this.InvokeMethod(this.ArgsWithApplicationView(hWnd)); 20 | 21 | public void PinView(IntPtr hWnd) 22 | => this.InvokeMethod(this.ArgsWithApplicationView(hWnd)); 23 | 24 | public void UnpinView(IntPtr hWnd) 25 | => this.InvokeMethod(this.ArgsWithApplicationView(hWnd)); 26 | 27 | public bool IsAppIdPinned(string appId) 28 | => this.InvokeMethod(Args(appId)); 29 | 30 | public void PinAppID(string appId) 31 | => this.InvokeMethod(Args(appId)); 32 | 33 | public void UnpinAppID(string appId) 34 | => this.InvokeMethod(Args(appId)); 35 | 36 | private object?[] ArgsWithApplicationView(IntPtr hWnd) 37 | => Args(this._factory.ApplicationViewFromHwnd(hWnd).ComObject); 38 | } 39 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build22000/.Provider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using WindowsDesktop.Interop.Build10240; 5 | using WindowsDesktop.Interop.Proxy; 6 | using WindowsDesktop.Properties; 7 | 8 | namespace WindowsDesktop.Interop.Build22000; 9 | 10 | internal class VirtualDesktopProvider22000 : VirtualDesktopProvider 11 | { 12 | private IVirtualDesktopManager? _virtualDesktopManager; 13 | private ApplicationViewCollection? _applicationViewCollection; 14 | private VirtualDesktopManagerInternal? _virtualDesktopManagerInternal; 15 | private VirtualDesktopPinnedApps? _virtualDesktopPinnedApps; 16 | private VirtualDesktopNotificationService? _virtualDesktopNotificationService; 17 | 18 | public override IApplicationViewCollection ApplicationViewCollection 19 | => this._applicationViewCollection ?? throw InitializationIsRequired; 20 | 21 | public override IVirtualDesktopManager VirtualDesktopManager 22 | => this._virtualDesktopManager ?? throw InitializationIsRequired; 23 | 24 | public override IVirtualDesktopManagerInternal VirtualDesktopManagerInternal 25 | => this._virtualDesktopManagerInternal ?? throw InitializationIsRequired; 26 | 27 | public override IVirtualDesktopPinnedApps VirtualDesktopPinnedApps 28 | => this._virtualDesktopPinnedApps ?? throw InitializationIsRequired; 29 | 30 | public override IVirtualDesktopNotificationService VirtualDesktopNotificationService 31 | => this._virtualDesktopNotificationService ?? throw InitializationIsRequired; 32 | 33 | private protected override void InitializeCore(ComInterfaceAssembly assembly) 34 | { 35 | var type = Type.GetTypeFromCLSID(CLSID.VirtualDesktopManager) 36 | ?? throw new Exception($"No type found for CLSID '{CLSID.VirtualDesktopManager}'."); 37 | this._virtualDesktopManager = Activator.CreateInstance(type) is IVirtualDesktopManager manager 38 | ? manager 39 | : throw new Exception($"Failed to create instance of Type '{typeof(IVirtualDesktopManager)}'."); 40 | 41 | this._applicationViewCollection = new ApplicationViewCollection(assembly); 42 | var factory = new ComWrapperFactory( 43 | x => new ApplicationView(assembly, x), 44 | x => this._applicationViewCollection.GetViewForHwnd(x), 45 | x => new VirtualDesktop(assembly, x)); 46 | this._virtualDesktopManagerInternal = new VirtualDesktopManagerInternal(assembly, factory); 47 | this._virtualDesktopPinnedApps = new VirtualDesktopPinnedApps(assembly, factory); 48 | this._virtualDesktopNotificationService = new VirtualDesktopNotificationService(assembly, factory); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build22000/.interfaces/IVirtualDesktop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using WindowsDesktop.Interop.Build10240; 4 | 5 | namespace WindowsDesktop.Interop.Build22000 6 | { 7 | [ComImport] 8 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 9 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 10 | public interface IVirtualDesktop 11 | { 12 | bool IsViewVisible(IApplicationView view); 13 | 14 | Guid GetID(); 15 | 16 | IntPtr Proc5(); 17 | 18 | HString GetName(); 19 | 20 | HString GetWallpaperPath(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build22000/.interfaces/IVirtualDesktopManagerInternal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using WindowsDesktop.Interop.Build10240; 4 | 5 | namespace WindowsDesktop.Interop.Build22000 6 | { 7 | [ComImport] 8 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 9 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 10 | public interface IVirtualDesktopManagerInternal 11 | { 12 | int GetCount(IntPtr hWndOrMon); 13 | 14 | void MoveViewToDesktop(IApplicationView pView, IVirtualDesktop desktop); 15 | 16 | bool CanViewMoveDesktops(IApplicationView pView); 17 | 18 | IVirtualDesktop GetCurrentDesktop(IntPtr hWndOrMon); 19 | 20 | IObjectArray GetDesktops(IntPtr hWndOrMon); 21 | 22 | IVirtualDesktop GetAdjacentDesktop(IVirtualDesktop pDesktopReference, int uDirection); 23 | 24 | void SwitchDesktop(IntPtr hWndOrMon, IVirtualDesktop desktop); 25 | 26 | IVirtualDesktop CreateDesktop(IntPtr hWndOrMon); 27 | 28 | void MoveDesktop(IVirtualDesktop desktop, IntPtr hWndOrMon, int nIndex); 29 | 30 | void RemoveDesktop(IVirtualDesktop pRemove, IVirtualDesktop pFallbackDesktop); 31 | 32 | IVirtualDesktop FindDesktop(in Guid desktopId); 33 | 34 | void GetDesktopSwitchIncludeExcludeViews(IVirtualDesktop desktop, out IObjectArray o1, out IObjectArray o2); 35 | 36 | void SetDesktopName(IVirtualDesktop desktop, HString name); 37 | 38 | void SetDesktopWallpaper(IVirtualDesktop desktop, HString path); 39 | 40 | void UpdateWallpaperPathForAllDesktops(HString path); 41 | 42 | void CopyDesktopState(IApplicationView pView0, IApplicationView pView1); 43 | 44 | bool GetDesktopIsPerMonitor(); 45 | 46 | void SetDesktopIsPerMonitor(bool state); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build22000/.interfaces/IVirtualDesktopNotification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using WindowsDesktop.Interop.Build10240; 4 | 5 | namespace WindowsDesktop.Interop.Build22000 6 | { 7 | [ComImport] 8 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 9 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 10 | public interface IVirtualDesktopNotification 11 | { 12 | void VirtualDesktopCreated(IObjectArray p0, IVirtualDesktop pDesktop); 13 | 14 | void VirtualDesktopDestroyBegin(IObjectArray p0, IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback); 15 | 16 | void VirtualDesktopDestroyFailed(IObjectArray p0, IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback); 17 | 18 | void VirtualDesktopDestroyed(IObjectArray p0, IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback); 19 | 20 | void Proc7(int p0); 21 | 22 | void VirtualDesktopMoved(IObjectArray p0, IVirtualDesktop pDesktop, int nIndexFrom, int nIndexTo); 23 | 24 | void VirtualDesktopRenamed(IVirtualDesktop pDesktop, HString chName); 25 | 26 | void ViewVirtualDesktopChanged(IApplicationView pView); 27 | 28 | void CurrentVirtualDesktopChanged(IObjectArray p0, IVirtualDesktop pDesktopOld, IVirtualDesktop pDesktopNew); 29 | 30 | void VirtualDesktopWallpaperChanged(IVirtualDesktop pDesktop, HString chPath); 31 | } 32 | 33 | internal class VirtualDesktopNotification : VirtualDesktopNotificationService.EventListenerBase, IVirtualDesktopNotification 34 | { 35 | public void VirtualDesktopCreated(IObjectArray p0, IVirtualDesktop pDesktop) 36 | { 37 | this.CreatedCore(pDesktop); 38 | } 39 | 40 | public void VirtualDesktopDestroyBegin(IObjectArray p0, IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback) 41 | { 42 | this.DestroyBeginCore(pDesktopDestroyed, pDesktopFallback); 43 | } 44 | 45 | public void VirtualDesktopDestroyFailed(IObjectArray p0, IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback) 46 | { 47 | this.DestroyFailedCore(pDesktopDestroyed, pDesktopFallback); 48 | } 49 | 50 | public void VirtualDesktopDestroyed(IObjectArray p0, IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback) 51 | { 52 | this.DestroyedCore(pDesktopDestroyed, pDesktopFallback); 53 | } 54 | 55 | public void Proc7(int p0) 56 | { 57 | } 58 | 59 | public void VirtualDesktopMoved(IObjectArray p0, IVirtualDesktop pDesktop, int nIndexFrom, int nIndexTo) 60 | { 61 | this.MovedCore(p0, pDesktop, nIndexFrom, nIndexTo); 62 | } 63 | 64 | public void VirtualDesktopRenamed(IVirtualDesktop pDesktop, HString chName) 65 | { 66 | this.RenamedCore(pDesktop, chName); 67 | } 68 | 69 | public void ViewVirtualDesktopChanged(IApplicationView pView) 70 | { 71 | this.ViewChangedCore(pView); 72 | } 73 | 74 | public void CurrentVirtualDesktopChanged(IObjectArray p0, IVirtualDesktop pDesktopOld, IVirtualDesktop pDesktopNew) 75 | { 76 | this.CurrentChangedCore(pDesktopOld, pDesktopNew); 77 | } 78 | 79 | public void VirtualDesktopWallpaperChanged(IVirtualDesktop pDesktop, HString chPath) 80 | { 81 | this.WallpaperChangedCore(pDesktop, chPath); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build22000/.interfaces/IVirtualDesktopNotificationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace WindowsDesktop.Interop.Build22000 5 | { 6 | [ComImport] 7 | [Guid("00000000-0000-0000-0000-000000000000") /* replace at runtime */] 8 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 9 | public interface IVirtualDesktopNotificationService 10 | { 11 | uint Register(IVirtualDesktopNotification pNotification); 12 | 13 | void Unregister(uint dwCookie); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build22000/VirtualDesktop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using WindowsDesktop.Interop.Proxy; 5 | 6 | namespace WindowsDesktop.Interop.Build22000; 7 | 8 | internal class VirtualDesktop : ComWrapperBase, IVirtualDesktop 9 | { 10 | private Guid? _id; 11 | 12 | public VirtualDesktop(ComInterfaceAssembly assembly, object comObject) 13 | : base(assembly, comObject) 14 | { 15 | } 16 | 17 | public bool IsViewVisible(IntPtr hWnd) 18 | => this.InvokeMethod(Args(hWnd)); 19 | 20 | public Guid GetID() 21 | => this._id ?? (Guid)(this._id = this.InvokeMethod()); 22 | 23 | public string GetName() 24 | => this.InvokeMethod(); 25 | 26 | public string GetWallpaperPath() 27 | => this.InvokeMethod(); 28 | } 29 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build22000/VirtualDesktopManagerInternal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using WindowsDesktop.Interop.Proxy; 5 | 6 | namespace WindowsDesktop.Interop.Build22000; 7 | 8 | internal class VirtualDesktopManagerInternal : ComWrapperBase, IVirtualDesktopManagerInternal 9 | { 10 | private readonly ComWrapperFactory _factory; 11 | 12 | public VirtualDesktopManagerInternal(ComInterfaceAssembly assembly, ComWrapperFactory factory) 13 | : base(assembly, CLSID.VirtualDesktopManagerInternal) 14 | { 15 | this._factory = factory; 16 | } 17 | 18 | public IEnumerable GetDesktops() 19 | { 20 | var array = this.InvokeMethod(Args(IntPtr.Zero)); 21 | if (array == null) yield break; 22 | 23 | var count = array.GetCount(); 24 | var vdType = this.ComInterfaceAssembly.GetType(nameof(IVirtualDesktop)); 25 | 26 | for (var i = 0u; i < count; i++) 27 | { 28 | var ppvObject = array.GetAt(i, vdType.GUID); 29 | yield return new VirtualDesktop(this.ComInterfaceAssembly, ppvObject); 30 | } 31 | } 32 | 33 | public IVirtualDesktop GetCurrentDesktop() 34 | => this.InvokeMethodAndWrap(Args(IntPtr.Zero)); 35 | 36 | public IVirtualDesktop GetAdjacentDesktop(IVirtualDesktop pDesktopReference, AdjacentDesktop uDirection) 37 | => this.InvokeMethodAndWrap(Args(((VirtualDesktop)pDesktopReference).ComObject, uDirection)); 38 | 39 | public IVirtualDesktop FindDesktop(Guid desktopId) 40 | => this.InvokeMethodAndWrap(Args(desktopId)); 41 | 42 | public IVirtualDesktop CreateDesktop() 43 | => this.InvokeMethodAndWrap(Args(IntPtr.Zero)); 44 | 45 | public void SwitchDesktop(IVirtualDesktop desktop) 46 | => this.InvokeMethod(Args(IntPtr.Zero, ((VirtualDesktop)desktop).ComObject)); 47 | 48 | public void RemoveDesktop(IVirtualDesktop pRemove, IVirtualDesktop pFallbackDesktop) 49 | => this.InvokeMethod(Args(((VirtualDesktop)pRemove).ComObject, ((VirtualDesktop)pFallbackDesktop).ComObject)); 50 | 51 | public void MoveViewToDesktop(IntPtr hWnd, IVirtualDesktop desktop) 52 | => this.InvokeMethod(Args(this._factory.ApplicationViewFromHwnd(hWnd).ComObject, ((VirtualDesktop)desktop).ComObject)); 53 | 54 | public void SetDesktopName(IVirtualDesktop desktop, string name) 55 | => this.InvokeMethod(Args(((VirtualDesktop)desktop).ComObject, new HString(name))); 56 | 57 | public void SetDesktopWallpaper(IVirtualDesktop desktop, string path) 58 | => this.InvokeMethod(Args(((VirtualDesktop)desktop).ComObject, new HString(path))); 59 | 60 | public void UpdateWallpaperPathForAllDesktops(string path) 61 | => this.InvokeMethod(Args(new HString(path))); 62 | 63 | private VirtualDesktop InvokeMethodAndWrap(object?[]? parameters = null, [CallerMemberName] string methodName = "") 64 | => new(this.ComInterfaceAssembly, this.InvokeMethod(parameters, methodName) ?? throw new Exception("Failed to get IVirtualDesktop instance.")); 65 | } 66 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Build22000/VirtualDesktopNotificationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using WindowsDesktop.Interop.Proxy; 6 | using WindowsDesktop.Utils; 7 | 8 | namespace WindowsDesktop.Interop.Build22000; 9 | 10 | public class VirtualDesktopNotificationService : ComWrapperBase, IVirtualDesktopNotificationService 11 | { 12 | private readonly ComWrapperFactory _factory; 13 | 14 | internal VirtualDesktopNotificationService(ComInterfaceAssembly assembly, ComWrapperFactory factory) 15 | : base(assembly, CLSID.VirtualDesktopNotificationService) 16 | { 17 | this._factory = factory; 18 | } 19 | 20 | public IDisposable Register(IVirtualDesktopNotification notification) 21 | { 22 | var type = this.ComInterfaceAssembly.GetType("VirtualDesktopNotification"); 23 | var listener = Activator.CreateInstance(type) as EventListenerBase 24 | ?? throw new Exception($"{nameof(EventListenerBase)} inheritance type is not found in the COM interface assembly."); 25 | 26 | listener.Notification = notification; 27 | listener.Factory = this._factory; 28 | 29 | var dwCookie = this.InvokeMethod(Args(listener)); 30 | return Disposable.Create(() => this.Unregister(dwCookie)); 31 | } 32 | 33 | private void Unregister(uint dwCookie) 34 | { 35 | try 36 | { 37 | this.InvokeMethod(Args(dwCookie)); 38 | } 39 | catch (COMException ex) when (ex.Match(HResult.RPC_S_SERVER_UNAVAILABLE)) 40 | { 41 | // Nothing particular to do. 42 | } 43 | } 44 | 45 | public abstract class EventListenerBase 46 | { 47 | internal ComWrapperFactory Factory { get; set; } = null!; 48 | 49 | internal IVirtualDesktopNotification Notification { get; set; } = null!; 50 | 51 | protected void CreatedCore(object pDesktop) 52 | => this.Notification.VirtualDesktopCreated(this.Wrap(pDesktop)); 53 | 54 | protected void DestroyBeginCore(object pDesktopDestroyed, object pDesktopFallback) 55 | => this.Notification.VirtualDesktopDestroyBegin(this.Wrap(pDesktopDestroyed), this.Wrap(pDesktopFallback)); 56 | 57 | protected void DestroyFailedCore(object pDesktopDestroyed, object pDesktopFallback) 58 | => this.Notification.VirtualDesktopDestroyFailed(this.Wrap(pDesktopDestroyed), this.Wrap(pDesktopFallback)); 59 | 60 | protected void DestroyedCore(object pDesktopDestroyed, object pDesktopFallback) 61 | => this.Notification.VirtualDesktopDestroyed(this.Wrap(pDesktopDestroyed), this.Wrap(pDesktopFallback)); 62 | 63 | protected void MovedCore(object p0, object pDesktop, int nIndexFrom, int nIndexTo) 64 | => this.Notification.VirtualDesktopMoved(this.Wrap(pDesktop), nIndexFrom, nIndexTo); 65 | 66 | protected void RenamedCore(object pDesktop, HString chName) 67 | => this.Notification.VirtualDesktopRenamed(this.Wrap(pDesktop), chName); 68 | 69 | protected void ViewChangedCore(object view) 70 | => this.Notification.ViewVirtualDesktopChanged(this.Factory.ApplicationView(view).Interface); 71 | 72 | protected void CurrentChangedCore(object pDesktopOld, object pDesktopNew) 73 | => this.Notification.CurrentVirtualDesktopChanged(this.Wrap(pDesktopOld), this.Wrap(pDesktopNew)); 74 | 75 | protected void WallpaperChangedCore(object pDesktop, HString chPath) 76 | => this.Notification.VirtualDesktopWallpaperChanged(this.Wrap(pDesktop), chPath); 77 | 78 | private IVirtualDesktop Wrap(object desktop) 79 | => this.Factory.VirtualDesktop(desktop).Interface; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/CLSID.cs: -------------------------------------------------------------------------------- 1 | namespace WindowsDesktop.Interop; 2 | 3 | // ReSharper disable once InconsistentNaming 4 | internal static class CLSID 5 | { 6 | public static Guid ImmersiveShell { get; } = new("c2f03a33-21f5-47fa-b4bb-156362a2f239"); 7 | 8 | public static Guid VirtualDesktopManager { get; } = new("aa509086-5ca9-4c25-8f95-589d3c07b48a"); 9 | 10 | public static Guid VirtualDesktopManagerInternal { get; } = new("c5e0cdca-7b6e-41b2-9fc4-d93975cc467b"); 11 | 12 | public static Guid VirtualDesktopNotificationService { get; } = new Guid("a501fdec-4a09-464c-ae4e-1b9c21b84918"); 13 | 14 | public static Guid VirtualDesktopPinnedApps { get; } = new("b5a399e7-1c87-46b8-88e9-fc5747b171bd"); 15 | } 16 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/ComInterfaceAssembly.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace WindowsDesktop.Interop 8 | { 9 | internal class ComInterfaceAssembly 10 | { 11 | private readonly Dictionary _knownTypes = new(); 12 | private readonly Assembly _compiledAssembly; 13 | 14 | public DirectoryInfo AssemblyLocation 15 | => new(this._compiledAssembly.Location); 16 | 17 | public ComInterfaceAssembly(Assembly compiledAssembly) 18 | { 19 | this._compiledAssembly = compiledAssembly; 20 | } 21 | 22 | internal Type GetType(string typeName) 23 | { 24 | if (this._knownTypes.TryGetValue(typeName, out var type) == false) 25 | { 26 | type = this._knownTypes[typeName] = this._compiledAssembly 27 | .GetTypes() 28 | .Single(x => x.Name.Split('.').Last() == typeName); 29 | } 30 | 31 | return type; 32 | } 33 | 34 | internal (Type type, object instance) CreateInstance(string comInterfaceName) 35 | { 36 | var type = this.GetType(comInterfaceName); 37 | var instance = CreateInstance(type, null); 38 | 39 | return (type, instance); 40 | } 41 | 42 | internal (Type type, object instance) CreateInstance(string comInterfaceName, Guid clsid) 43 | { 44 | var type = this.GetType(comInterfaceName); 45 | var instance = CreateInstance(type, clsid); 46 | 47 | return (type, instance); 48 | } 49 | 50 | private static object CreateInstance(Type type, Guid? guidService) 51 | { 52 | var shellType = Type.GetTypeFromCLSID(CLSID.ImmersiveShell) 53 | ?? throw new Exception($"Type of ImmersiveShell ('{CLSID.ImmersiveShell}') is not found."); 54 | var shell = Activator.CreateInstance(shellType) as IServiceProvider 55 | ?? throw new Exception("Failed to create an instance of ImmersiveShell."); 56 | 57 | return shell.QueryService(guidService ?? type.GUID, type.GUID); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/ComInterfaceAssemblyBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.Loader; 8 | using System.Text; 9 | using System.Text.RegularExpressions; 10 | using Microsoft.CodeAnalysis; 11 | using Microsoft.CodeAnalysis.CSharp; 12 | using WindowsDesktop.Properties; 13 | 14 | namespace WindowsDesktop.Interop; 15 | 16 | internal class ComInterfaceAssemblyBuilder 17 | { 18 | private const string _assemblyName = "VirtualDesktop.{0}.generated.dll"; 19 | private const string _placeholderOsBuild = "{OS_BUILD}"; 20 | private const string _placeholderAssemblyVersion = "{ASSEMBLY_VERSION}"; 21 | private const string _placeholderInterfaceId = "00000000-0000-0000-0000-000000000000"; 22 | 23 | private static readonly Version _requireVersion = new("2.1.0"); 24 | private static readonly Regex _assemblyRegex = new(@"VirtualDesktop\.(?\d{5}?)(\.\w*|)\.dll"); 25 | private static readonly Regex _buildNumberRegex = new(@"\.Build(?\d{5})\."); 26 | private static readonly int _osBuild = Environment.OSVersion.Version.Build; 27 | private static ComInterfaceAssembly? _assembly; 28 | 29 | private readonly VirtualDesktopCompilerConfiguration _configuration; 30 | 31 | public ComInterfaceAssemblyBuilder(VirtualDesktopCompilerConfiguration configuration) 32 | { 33 | this._configuration = configuration; 34 | } 35 | 36 | public ComInterfaceAssembly GetAssembly() 37 | => _assembly ??= new ComInterfaceAssembly(this.LoadExistingAssembly() ?? this.CreateAssembly()); 38 | 39 | private Assembly? LoadExistingAssembly() 40 | { 41 | if (this._configuration.CompiledAssemblySaveDirectory.Exists) 42 | { 43 | foreach (var file in this._configuration.CompiledAssemblySaveDirectory.GetFiles()) 44 | { 45 | if (int.TryParse(_assemblyRegex.Match(file.Name).Groups["build"].ToString(), out var build) 46 | && build == _osBuild) 47 | { 48 | try 49 | { 50 | var name = AssemblyName.GetAssemblyName(file.FullName); 51 | if (name.Version >= _requireVersion) 52 | { 53 | Debug.WriteLine($"Assembly found: {file.FullName}"); 54 | #if !DEBUG 55 | return Assembly.LoadFile(file.FullName); 56 | #endif 57 | } 58 | } 59 | catch (Exception ex) 60 | { 61 | Debug.WriteLine("Failed to load assembly: "); 62 | Debug.WriteLine(ex); 63 | 64 | File.Delete(file.FullName); 65 | } 66 | } 67 | } 68 | } 69 | 70 | return null; 71 | } 72 | 73 | private Assembly CreateAssembly() 74 | { 75 | var executingAssembly = Assembly.GetExecutingAssembly(); 76 | var compileTargets = new List(); 77 | { 78 | var assemblyInfo = executingAssembly.GetManifestResourceNames().Single(x => x.Contains("AssemblyInfo.cs")); 79 | var stream = executingAssembly.GetManifestResourceStream(assemblyInfo); 80 | if (stream != null) 81 | { 82 | using var reader = new StreamReader(stream, Encoding.UTF8); 83 | var sourceCode = reader 84 | .ReadToEnd() 85 | .Replace(_placeholderOsBuild, _osBuild.ToString()) 86 | .Replace(_placeholderAssemblyVersion, _requireVersion.ToString(3)); 87 | compileTargets.Add(sourceCode); 88 | } 89 | } 90 | 91 | var interfaceNames = executingAssembly 92 | .GetTypes() 93 | .Select(x => x.GetComInterfaceNameIfWrapper()) 94 | .Where(x => string.IsNullOrEmpty(x) == false) 95 | .Cast() 96 | .ToArray(); 97 | var iids = IID.GetIIDs(interfaceNames); 98 | 99 | // e.g. 100 | // IVirtualDesktop 101 | // ├── 10240, VirtualDesktop.Interop.Build10240..interfaces.IVirtualDesktop.cs 102 | // └── 22000, VirtualDesktop.Interop.Build22000..interfaces.IVirtualDesktop.cs 103 | // IVirtualDesktopPinnedApps 104 | // └── 10240, VirtualDesktop.Interop.Build10240..interfaces.IVirtualDesktopPinnedApps.cs 105 | var interfaceSourceFiles = new Dictionary>(); 106 | 107 | foreach (var name in executingAssembly.GetManifestResourceNames()) 108 | { 109 | var interfaceName = Path.GetFileNameWithoutExtension(name).Split('.').LastOrDefault(); 110 | if (interfaceName != null 111 | && interfaceNames.Contains(interfaceName) 112 | && int.TryParse(_buildNumberRegex.Match(name).Groups["build"].ToString(), out var build)) 113 | { 114 | if (interfaceSourceFiles.TryGetValue(interfaceName, out var sourceFiles) == false) 115 | { 116 | sourceFiles = new SortedList(); 117 | interfaceSourceFiles.Add(interfaceName, sourceFiles); 118 | } 119 | 120 | sourceFiles.Add(build, name); 121 | } 122 | } 123 | 124 | foreach (var (interfaceName, sourceFiles) in interfaceSourceFiles) 125 | { 126 | var resourceName = sourceFiles.Aggregate("", (current, kvp) => 127 | { 128 | var (build, resourceName) = kvp; 129 | return build <= _osBuild ? resourceName : current; 130 | }); 131 | 132 | var stream = executingAssembly.GetManifestResourceStream(resourceName); 133 | if (stream == null) continue; 134 | 135 | using var reader = new StreamReader(stream, Encoding.UTF8); 136 | var sourceCode = reader.ReadToEnd().Replace(_placeholderInterfaceId, iids[interfaceName].ToString()); 137 | compileTargets.Add(sourceCode); 138 | } 139 | 140 | return this.Compile(compileTargets.ToArray()); 141 | } 142 | 143 | private Assembly Compile(IEnumerable sources) 144 | { 145 | try 146 | { 147 | var name = string.Format(_assemblyName, _osBuild); 148 | var syntaxTrees = sources.Select(x => SyntaxFactory.ParseSyntaxTree(x)); 149 | var references = AppDomain.CurrentDomain.GetAssemblies() 150 | .Concat(new[] { Assembly.GetExecutingAssembly(), }) 151 | .Where(x => x.IsDynamic == false) 152 | .Select(x => MetadataReference.CreateFromFile(x.Location)); 153 | var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); 154 | var compilation = CSharpCompilation.Create(name) 155 | .WithOptions(options) 156 | .WithReferences(references) 157 | .AddSyntaxTrees(syntaxTrees); 158 | 159 | string? errorMessage; 160 | 161 | if (this._configuration.SaveCompiledAssembly) 162 | { 163 | var dir = this._configuration.CompiledAssemblySaveDirectory; 164 | if (dir.Exists == false) dir.Create(); 165 | 166 | var path = Path.Combine(dir.FullName, name); 167 | var result = compilation.Emit(path); 168 | if (result.Success) return AssemblyLoadContext.Default.LoadFromAssemblyPath(path); 169 | 170 | File.Delete(path); 171 | errorMessage = string.Join(Environment.NewLine, result.Diagnostics.Select(x => $" {x.GetMessage()}")); 172 | } 173 | else 174 | { 175 | using var stream = new MemoryStream(); 176 | var result = compilation.Emit(stream); 177 | if (result.Success) 178 | { 179 | stream.Seek(0, SeekOrigin.Begin); 180 | return AssemblyLoadContext.Default.LoadFromStream(stream); 181 | } 182 | 183 | errorMessage = string.Join(Environment.NewLine, result.Diagnostics.Select(x => $" {x.GetMessage()}")); 184 | } 185 | 186 | throw new Exception("Failed to compile COM interfaces assembly." + Environment.NewLine + errorMessage); 187 | } 188 | finally 189 | { 190 | GC.Collect(); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/ComInterfaceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace WindowsDesktop.Interop; 7 | 8 | [AttributeUsage(AttributeTargets.Interface)] 9 | internal class ComInterfaceAttribute : Attribute 10 | { 11 | public string? InterfaceName { get; } 12 | 13 | public ComInterfaceAttribute() 14 | { 15 | } 16 | 17 | public ComInterfaceAttribute(string interfaceName) 18 | { 19 | this.InterfaceName = interfaceName; 20 | } 21 | } 22 | 23 | internal static class ComInterfaceAttributeExtensions 24 | { 25 | /// 26 | /// Gets COM interface name if specific type has '' attribute. 27 | /// 28 | public static string? GetComInterfaceNameIfWrapper(this Type type) 29 | { 30 | var attr = type.GetCustomAttribute(); 31 | if (attr == null) return null; 32 | 33 | return attr.InterfaceName ?? type.Name; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/ComWrapperBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace WindowsDesktop.Interop; 8 | 9 | public abstract class ComWrapperBase 10 | { 11 | private readonly Dictionary _methods = new(); 12 | 13 | private protected ComInterfaceAssembly ComInterfaceAssembly { get; } 14 | 15 | public TInterface Interface 16 | => (TInterface)(object)this; 17 | 18 | public Type ComInterfaceType { get; } 19 | 20 | public object ComObject { get; } 21 | 22 | private protected ComWrapperBase(ComInterfaceAssembly assembly) 23 | { 24 | var (type, instance) = assembly.CreateInstance(typeof(TInterface).Name); 25 | 26 | this.ComInterfaceAssembly = assembly; 27 | this.ComInterfaceType = type; 28 | this.ComObject = instance; 29 | } 30 | 31 | private protected ComWrapperBase(ComInterfaceAssembly assembly, Guid clsid) 32 | { 33 | var (type, instance) = assembly.CreateInstance(typeof(TInterface).Name, clsid); 34 | 35 | this.ComInterfaceAssembly = assembly; 36 | this.ComInterfaceType = type; 37 | this.ComObject = instance; 38 | } 39 | 40 | private protected ComWrapperBase(ComInterfaceAssembly assembly, object comObject) 41 | { 42 | this.ComInterfaceAssembly = assembly; 43 | this.ComInterfaceType = assembly.GetType(typeof(TInterface).Name); 44 | this.ComObject = comObject; 45 | } 46 | 47 | protected static object?[] Args(params object?[] args) 48 | => args; 49 | 50 | protected void InvokeMethod(object?[]? parameters = null, [CallerMemberName] string methodName = "") 51 | => this.InvokeMethod(parameters, methodName); 52 | 53 | protected T? InvokeMethod(object?[]? parameters = null, [CallerMemberName] string methodName = "") 54 | { 55 | if (this._methods.TryGetValue(methodName, out var methodInfo) 56 | || (methodInfo = this.ComInterfaceType.GetMethod(methodName)) != null) 57 | { 58 | this._methods[methodName] = methodInfo; 59 | } 60 | else throw new NotSupportedException($"Method '{methodName}' is not supported in COM interface '{typeof(TInterface).Name}'."); 61 | 62 | try 63 | { 64 | return (T?)methodInfo.Invoke(this.ComObject, parameters); 65 | } 66 | catch (TargetInvocationException ex) when (ex.InnerException != null) 67 | { 68 | throw ex.InnerException; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/ComWrapperFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using WindowsDesktop.Interop.Proxy; 5 | 6 | namespace WindowsDesktop.Interop; 7 | 8 | internal class ComWrapperFactory 9 | { 10 | public Func> ApplicationView { get; } 11 | 12 | public Func> ApplicationViewFromHwnd { get; } 13 | 14 | public Func> VirtualDesktop { get; } 15 | 16 | public ComWrapperFactory( 17 | Func> applicationView, 18 | Func> applicationViewFromHwnd, 19 | Func> virtualDesktop) 20 | { 21 | this.ApplicationView = applicationView; 22 | this.ApplicationViewFromHwnd = applicationViewFromHwnd; 23 | this.VirtualDesktop = virtualDesktop; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/HResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace WindowsDesktop.Interop; 5 | 6 | internal enum HResult : uint 7 | { 8 | // ReSharper disable IdentifierTypo 9 | TYPE_E_OUTOFBOUNDS = 0x80028CA1, 10 | TYPE_E_ELEMENTNOTFOUND = 0x8002802B, 11 | REGDB_E_CLASSNOTREG = 0x80040154, 12 | RPC_S_SERVER_UNAVAILABLE = 0x800706BA, 13 | // ReSharper restore IdentifierTypo 14 | } 15 | 16 | internal static class HResultExtensions 17 | { 18 | public static bool Match(this Exception ex, params HResult[] hResult) 19 | { 20 | return hResult 21 | .Cast() 22 | .Any(x => (uint)ex.HResult == x); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/HString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using WinRT; 6 | 7 | namespace WindowsDesktop.Interop; 8 | 9 | // ## Note 10 | // .NET 5 has removed WinRT support, so HString cannot marshal to System.String. 11 | // Since marshalling with UnmanagedType.HString fails, use IntPtr to get the string via C#/WinRT MarshalString. 12 | // 13 | // see also: https://github.com/microsoft/CsWinRT/blob/master/docs/interop.md 14 | 15 | [StructLayout(LayoutKind.Sequential)] 16 | public struct HString 17 | { 18 | private readonly IntPtr _abi; 19 | 20 | internal HString(string str) 21 | { 22 | this._abi = MarshalString.GetAbi(MarshalString.CreateMarshaler(str)); 23 | } 24 | 25 | public static implicit operator string(HString hStr) 26 | => MarshalString.FromAbi(hStr._abi); 27 | } 28 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/IID.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.Configuration; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | using WindowsDesktop.Properties; 8 | using Microsoft.Win32; 9 | 10 | namespace WindowsDesktop.Interop; 11 | 12 | internal static class IID 13 | { 14 | private static readonly Regex _osBuildRegex = new(@"v_(?\d{5}?)"); 15 | 16 | // ReSharper disable once InconsistentNaming 17 | public static Dictionary GetIIDs(string[] interfaceNames) 18 | { 19 | var result = new Dictionary(); 20 | 21 | foreach (var prop in Settings.Default.Properties.OfType()) 22 | { 23 | if (int.TryParse(_osBuildRegex.Match(prop.Name).Groups["build"].ToString(), out var build) 24 | && build == Environment.OSVersion.Version.Build) 25 | { 26 | foreach (var str in (StringCollection)Settings.Default[prop.Name]) 27 | { 28 | if (str == null) continue; 29 | 30 | var pair = str.Split(','); 31 | if (pair.Length != 2) continue; 32 | if (interfaceNames.Contains(pair[0]) == false || result.ContainsKey(pair[0])) continue; 33 | if (Guid.TryParse(pair[1], out var guid) == false) continue; 34 | 35 | result.Add(pair[0], guid); 36 | } 37 | 38 | break; 39 | } 40 | } 41 | 42 | var except = interfaceNames.Except(result.Keys).ToArray(); 43 | if (except.Length > 0) 44 | { 45 | foreach (var (key, value) in GetIIDsFromRegistry(except)) result.Add(key, value); 46 | } 47 | 48 | return result; 49 | } 50 | 51 | // ReSharper disable once InconsistentNaming 52 | private static Dictionary GetIIDsFromRegistry(string[] targets) 53 | { 54 | using var interfaceKey = Registry.ClassesRoot.OpenSubKey("Interface") 55 | ?? throw new Exception(@"Registry key '\HKEY_CLASSES_ROOT\Interface' is missing."); 56 | 57 | var result = new Dictionary(); 58 | 59 | foreach (var name in interfaceKey.GetSubKeyNames()) 60 | { 61 | using var key = interfaceKey.OpenSubKey(name); 62 | 63 | if (key?.GetValue("") is string value) 64 | { 65 | var match = targets.FirstOrDefault(x => x == value); 66 | if (match != null && Guid.TryParse(key.Name.Split('\\').Last(), out var guid)) 67 | { 68 | result[match] = guid; 69 | } 70 | } 71 | } 72 | 73 | return result; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Proxy/IApplicationView.cs: -------------------------------------------------------------------------------- 1 | namespace WindowsDesktop.Interop.Proxy; 2 | 3 | [ComInterface] 4 | public interface IApplicationView 5 | { 6 | IntPtr GetThumbnailWindow(); 7 | 8 | string GetAppUserModelId(); 9 | 10 | Guid GetVirtualDesktopId(); 11 | } 12 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Proxy/IApplicationViewCollection.cs: -------------------------------------------------------------------------------- 1 | namespace WindowsDesktop.Interop.Proxy; 2 | 3 | [ComInterface] 4 | public interface IApplicationViewCollection 5 | { 6 | IApplicationView GetViewForHwnd(IntPtr hWnd); 7 | } 8 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Proxy/IVirtualDesktop.cs: -------------------------------------------------------------------------------- 1 | namespace WindowsDesktop.Interop.Proxy; 2 | 3 | [ComInterface] 4 | public interface IVirtualDesktop 5 | { 6 | bool IsViewVisible(IntPtr hWnd); 7 | 8 | Guid GetID(); 9 | 10 | string GetName(); 11 | 12 | string GetWallpaperPath(); 13 | } 14 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Proxy/IVirtualDesktopManagerInternal.cs: -------------------------------------------------------------------------------- 1 | namespace WindowsDesktop.Interop.Proxy; 2 | 3 | public enum AdjacentDesktop 4 | { 5 | LeftDirection = 3, 6 | 7 | RightDirection = 4, 8 | } 9 | 10 | [ComInterface] 11 | public interface IVirtualDesktopManagerInternal 12 | { 13 | IEnumerable GetDesktops(); 14 | 15 | IVirtualDesktop GetCurrentDesktop(); 16 | 17 | IVirtualDesktop GetAdjacentDesktop(IVirtualDesktop pDesktopReference, AdjacentDesktop uDirection); 18 | 19 | IVirtualDesktop FindDesktop(Guid desktopId); 20 | 21 | IVirtualDesktop CreateDesktop(); 22 | 23 | void RemoveDesktop(IVirtualDesktop pRemove, IVirtualDesktop pFallbackDesktop); 24 | 25 | void SwitchDesktop(IVirtualDesktop desktop); 26 | 27 | void MoveViewToDesktop(IntPtr hWnd, IVirtualDesktop desktop); 28 | 29 | void SetDesktopName(IVirtualDesktop desktop, string name); 30 | 31 | void SetDesktopWallpaper(IVirtualDesktop desktop, string path); 32 | 33 | void UpdateWallpaperPathForAllDesktops(string path); 34 | } 35 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Proxy/IVirtualDesktopNotification.cs: -------------------------------------------------------------------------------- 1 | namespace WindowsDesktop.Interop.Proxy; 2 | 3 | [ComInterface] 4 | public interface IVirtualDesktopNotification 5 | { 6 | void VirtualDesktopCreated(IVirtualDesktop pDesktop); 7 | 8 | void VirtualDesktopDestroyBegin(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback); 9 | 10 | void VirtualDesktopDestroyFailed(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback); 11 | 12 | void VirtualDesktopDestroyed(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback); 13 | 14 | void VirtualDesktopMoved(IVirtualDesktop pDesktop, int nIndexFrom, int nIndexTo); 15 | 16 | void VirtualDesktopRenamed(IVirtualDesktop pDesktop, string chName); 17 | 18 | void ViewVirtualDesktopChanged(IApplicationView pView); 19 | 20 | void CurrentVirtualDesktopChanged(IVirtualDesktop pDesktopOld, IVirtualDesktop pDesktopNew); 21 | 22 | void VirtualDesktopWallpaperChanged(IVirtualDesktop pDesktop, string chPath); 23 | } 24 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Proxy/IVirtualDesktopNotificationService.cs: -------------------------------------------------------------------------------- 1 | namespace WindowsDesktop.Interop.Proxy; 2 | 3 | [ComInterface] 4 | public interface IVirtualDesktopNotificationService 5 | { 6 | IDisposable Register(IVirtualDesktopNotification proxy); 7 | } 8 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Proxy/IVirtualDesktopPinnedApps.cs: -------------------------------------------------------------------------------- 1 | namespace WindowsDesktop.Interop.Proxy; 2 | 3 | [ComInterface] 4 | public interface IVirtualDesktopPinnedApps 5 | { 6 | bool IsAppIdPinned(string appId); 7 | 8 | void PinAppID(string appId); 9 | 10 | void UnpinAppID(string appId); 11 | 12 | bool IsViewPinned(IntPtr hWnd); 13 | 14 | void PinView(IntPtr hWnd); 15 | 16 | void UnpinView(IntPtr hWnd); 17 | } 18 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/VirtualDesktopProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using WindowsDesktop.Interop.Proxy; 5 | using WindowsDesktop.Properties; 6 | 7 | namespace WindowsDesktop.Interop; 8 | 9 | internal abstract class VirtualDesktopProvider 10 | { 11 | public virtual bool IsSupported 12 | => true; 13 | 14 | public abstract IApplicationViewCollection ApplicationViewCollection { get; } 15 | 16 | public abstract IVirtualDesktopManager VirtualDesktopManager { get; } 17 | 18 | public abstract IVirtualDesktopManagerInternal VirtualDesktopManagerInternal { get; } 19 | 20 | public abstract IVirtualDesktopPinnedApps VirtualDesktopPinnedApps { get; } 21 | 22 | public abstract IVirtualDesktopNotificationService VirtualDesktopNotificationService { get; } 23 | 24 | public bool IsInitialized { get; internal set; } 25 | 26 | internal void Initialize(ComInterfaceAssembly assembly) 27 | { 28 | if (this.IsInitialized) return; 29 | 30 | this.InitializeCore(assembly); 31 | this.IsInitialized = true; 32 | } 33 | 34 | private protected abstract void InitializeCore(ComInterfaceAssembly assembly); 35 | 36 | internal class NotSupported : VirtualDesktopProvider 37 | { 38 | public override bool IsSupported 39 | => false; 40 | 41 | public override IApplicationViewCollection ApplicationViewCollection 42 | => throw new NotSupportedException(); 43 | 44 | public override IVirtualDesktopManager VirtualDesktopManager 45 | => throw new NotSupportedException(); 46 | 47 | public override IVirtualDesktopManagerInternal VirtualDesktopManagerInternal 48 | => throw new NotSupportedException(); 49 | 50 | public override IVirtualDesktopPinnedApps VirtualDesktopPinnedApps 51 | => throw new NotSupportedException(); 52 | 53 | public override IVirtualDesktopNotificationService VirtualDesktopNotificationService 54 | => throw new NotSupportedException(); 55 | 56 | private protected override void InitializeCore(ComInterfaceAssembly assembly) 57 | => throw new NotSupportedException(); 58 | } 59 | 60 | protected static InvalidOperationException InitializationIsRequired 61 | => new("Initialization is required."); 62 | } 63 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Interop/Win32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace WindowsDesktop.Interop; 7 | 8 | [ComImport] 9 | [Guid("6d5140c1-7436-11ce-8034-00aa006009fa")] 10 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 11 | internal interface IServiceProvider 12 | { 13 | [return: MarshalAs(UnmanagedType.IUnknown)] 14 | object QueryService(in Guid guidService, in Guid riid); 15 | } 16 | 17 | [ComImport] 18 | [Guid("92ca9dcd-5622-4bba-a805-5e9f541bd8c9")] 19 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 20 | public interface IObjectArray 21 | { 22 | uint GetCount(); 23 | 24 | [return: MarshalAs(UnmanagedType.Interface)] 25 | object GetAt(uint iIndex, in Guid riid); 26 | } 27 | 28 | [ComImport] 29 | [Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")] 30 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 31 | internal interface IVirtualDesktopManager 32 | { 33 | bool IsWindowOnCurrentVirtualDesktop(IntPtr topLevelWindow); 34 | 35 | Guid GetWindowDesktopId(IntPtr topLevelWindow); 36 | 37 | void MoveWindowToDesktop(IntPtr topLevelWindow, ref Guid desktopId); 38 | } 39 | 40 | internal static class PInvoke 41 | { 42 | [DllImport("user32.dll")] 43 | public static extern bool CloseWindow(IntPtr hWnd); 44 | 45 | [DllImport("user32.dll")] 46 | public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); 47 | 48 | [DllImport("user32.dll", CharSet = CharSet.Unicode)] 49 | public static extern uint RegisterWindowMessage(string lpProcName); 50 | } 51 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: ComVisible(false)] 6 | [assembly: Guid("ab848ecd-76aa-41c0-b63d-86a8591b25aa")] 7 | 8 | namespace WindowsDesktop.Properties; 9 | 10 | internal static class AssemblyInfo 11 | { 12 | private static readonly Assembly _assembly = Assembly.GetExecutingAssembly(); 13 | private static string? _title; 14 | private static string? _description; 15 | private static string? _company; 16 | private static string? _product; 17 | private static string? _copyright; 18 | private static string? _trademark; 19 | private static string? _versionString; 20 | 21 | public static string Title 22 | => _title ??= Prop(x => x.Title); 23 | 24 | public static string Description 25 | => _description ??= Prop(x => x.Description); 26 | 27 | public static string Company 28 | => _company ??= Prop(x => x.Company); 29 | 30 | public static string Product 31 | => _product ??= Prop(x => x.Product); 32 | 33 | public static string Copyright 34 | => _copyright ??= Prop(x => x.Copyright); 35 | 36 | public static string Trademark 37 | => _trademark ??= Prop(x => x.Trademark); 38 | 39 | public static Version Version 40 | => _assembly.GetName().Version ?? new Version(); 41 | 42 | public static string VersionString 43 | => _versionString ??= Version.ToString(3); 44 | 45 | private static string Prop(Func propSelector) 46 | where T : Attribute 47 | { 48 | var attribute = _assembly.GetCustomAttribute(); 49 | return attribute != null ? propSelector(attribute) : ""; 50 | } 51 | } 52 | 53 | internal static class LocationInfo 54 | { 55 | private static DirectoryInfo? _localAppData; 56 | 57 | internal static DirectoryInfo LocalAppData 58 | => _localAppData ??= new DirectoryInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AssemblyInfo.Company, AssemblyInfo.Product)); 59 | } 60 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Properties/Configurations.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace WindowsDesktop.Properties; 4 | 5 | public record VirtualDesktopConfiguration : VirtualDesktopCompilerConfiguration 6 | { 7 | } 8 | 9 | public record VirtualDesktopCompilerConfiguration 10 | { 11 | /// 12 | /// Gets or sets a value indicating whether the compiled assembly should be saved or not. 13 | /// 14 | /// 15 | /// This library uses the non-public Windows API.
16 | /// Since the ID of the COM Interface may differ depending on the build of Windows, it works by checking the ID in that environment at runtime and generating the assembly.
17 | ///
18 | /// Here you can set whether to save the assembly.
19 | /// Saving will improve the speed of the next launch. 20 | ///
21 | /// 22 | /// A value indicating whether the compiled assembly should be saved or not. Default is . 23 | /// 24 | public bool SaveCompiledAssembly { get; init; } = true; 25 | 26 | /// 27 | /// Gets or sets a value indicating the directory where the assembly will be saved. 28 | /// 29 | /// 30 | /// See property for details. 31 | /// 32 | /// 33 | /// A value indicating whether the compiled assembly should be saved or not. Default is %LocalAppData%. 34 | /// 35 | public DirectoryInfo CompiledAssemblySaveDirectory { get; init; } = new(Path.Combine(LocationInfo.LocalAppData.FullName, "assemblies")); 36 | } 37 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // このコードはツールによって生成されました。 4 | // ランタイム バージョン:4.0.30319.42000 5 | // 6 | // このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 7 | // コードが再生成されるときに損失したりします。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace WindowsDesktop.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.0.3.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.ApplicationScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute(@" 29 | 30 | IApplicationView,{871F602A-2B58-42B4-8C4B-6C43D642C06F} 31 | IApplicationViewCollection,{2C08ADF0-A386-4B35-9250-0FE183476FCC} 32 | IObjectArray,{92CA9DCD-5622-4BBA-A805-5E9F541BD8C9} 33 | IServiceProvider,{6D5140C1-7436-11CE-8034-00AA006009FA} 34 | IVirtualDesktop,{FF72FFDD-BE7E-43FC-9C03-AD81681E88E4} 35 | IVirtualDesktopManager,{A5CD92FF-29BE-454C-8D04-D82879FB3F1B} 36 | IVirtualDesktopManagerInternal,{F31574D6-B682-4CDC-BD56-1827860ABEC6} 37 | IVirtualDesktopNotification,{C179334C-4295-40D3-BEA1-C654D965605A} 38 | IVirtualDesktopNotificationService,{0CD45E71-D927-4F15-8B0A-8FEF525337BF} 39 | IVirtualDesktopPinnedApps,{4CE81583-1E4C-4632-A621-07A53543148F} 40 | ")] 41 | public global::System.Collections.Specialized.StringCollection v_17134 { 42 | get { 43 | return ((global::System.Collections.Specialized.StringCollection)(this["v_17134"])); 44 | } 45 | } 46 | 47 | [global::System.Configuration.ApplicationScopedSettingAttribute()] 48 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 49 | [global::System.Configuration.DefaultSettingValueAttribute(@" 50 | 51 | IApplicationView,{9AC0B5C8-1484-4C5B-9533-4134A0F97CEA} 52 | IApplicationViewCollection,{2C08ADF0-A386-4B35-9250-0FE183476FCC} 53 | IObjectArray,{92CA9DCD-5622-4BBA-A805-5E9F541BD8C9} 54 | IServiceProvider,{6D5140C1-7436-11CE-8034-00AA006009FA} 55 | IVirtualDesktop,{FF72FFDD-BE7E-43FC-9C03-AD81681E88E4} 56 | IVirtualDesktopManager,{A5CD92FF-29BE-454C-8D04-D82879FB3F1B} 57 | IVirtualDesktopManagerInternal,{F31574D6-B682-4CDC-BD56-1827860ABEC6} 58 | IVirtualDesktopNotification,{C179334C-4295-40D3-BEA1-C654D965605A} 59 | IVirtualDesktopNotificationService,{0CD45E71-D927-4F15-8B0A-8FEF525337BF} 60 | IVirtualDesktopPinnedApps,{4CE81583-1E4C-4632-A621-07A53543148F} 61 | ")] 62 | public global::System.Collections.Specialized.StringCollection v_16299 { 63 | get { 64 | return ((global::System.Collections.Specialized.StringCollection)(this["v_16299"])); 65 | } 66 | } 67 | 68 | [global::System.Configuration.ApplicationScopedSettingAttribute()] 69 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 70 | [global::System.Configuration.DefaultSettingValueAttribute(@" 71 | 72 | IApplicationView,{372e1d3b-38d3-42e4-a15b-8ab2b178f513} 73 | IApplicationViewCollection,{1841c6d7-4f9d-42c0-af41-8747538f10e5} 74 | IObjectArray,{92CA9DCD-5622-4BBA-A805-5E9F541BD8C9} 75 | IServiceProvider,{6D5140C1-7436-11CE-8034-00AA006009FA} 76 | IVirtualDesktop,{536d3495-b208-4cc9-ae26-de8111275bf8} 77 | IVirtualDesktopManager,{a5cd92ff-29be-454c-8d04-d82879fb3f1b} 78 | IVirtualDesktopManagerInternal,{b2f925b9-5a0f-4d2e-9f4d-2b1507593c10} 79 | IVirtualDesktopNotification,{cd403e52-deed-4c13-b437-b98380f2b1e8} 80 | IVirtualDesktopNotificationService,{0cd45e71-d927-4f15-8b0a-8fef525337bf} 81 | IVirtualDesktopPinnedApps,{4ce81583-1e4c-4632-a621-07a53543148f} 82 | ")] 83 | public global::System.Collections.Specialized.StringCollection v_22000 { 84 | get { 85 | return ((global::System.Collections.Specialized.StringCollection)(this["v_22000"])); 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | <?xml version="1.0" encoding="utf-16"?> 7 | <ArrayOfString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 8 | <string>IApplicationView,{871F602A-2B58-42B4-8C4B-6C43D642C06F}</string> 9 | <string>IApplicationViewCollection,{2C08ADF0-A386-4B35-9250-0FE183476FCC} </string> 10 | <string>IObjectArray,{92CA9DCD-5622-4BBA-A805-5E9F541BD8C9} </string> 11 | <string>IServiceProvider,{6D5140C1-7436-11CE-8034-00AA006009FA} </string> 12 | <string>IVirtualDesktop,{FF72FFDD-BE7E-43FC-9C03-AD81681E88E4} </string> 13 | <string>IVirtualDesktopManager,{A5CD92FF-29BE-454C-8D04-D82879FB3F1B} </string> 14 | <string>IVirtualDesktopManagerInternal,{F31574D6-B682-4CDC-BD56-1827860ABEC6} </string> 15 | <string>IVirtualDesktopNotification,{C179334C-4295-40D3-BEA1-C654D965605A} </string> 16 | <string>IVirtualDesktopNotificationService,{0CD45E71-D927-4F15-8B0A-8FEF525337BF} </string> 17 | <string>IVirtualDesktopPinnedApps,{4CE81583-1E4C-4632-A621-07A53543148F} </string> 18 | </ArrayOfString> 19 | 20 | 21 | <?xml version="1.0" encoding="utf-16"?> 22 | <ArrayOfString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 23 | <string>IApplicationView,{9AC0B5C8-1484-4C5B-9533-4134A0F97CEA} </string> 24 | <string>IApplicationViewCollection,{2C08ADF0-A386-4B35-9250-0FE183476FCC} </string> 25 | <string>IObjectArray,{92CA9DCD-5622-4BBA-A805-5E9F541BD8C9} </string> 26 | <string>IServiceProvider,{6D5140C1-7436-11CE-8034-00AA006009FA} </string> 27 | <string>IVirtualDesktop,{FF72FFDD-BE7E-43FC-9C03-AD81681E88E4} </string> 28 | <string>IVirtualDesktopManager,{A5CD92FF-29BE-454C-8D04-D82879FB3F1B} </string> 29 | <string>IVirtualDesktopManagerInternal,{F31574D6-B682-4CDC-BD56-1827860ABEC6} </string> 30 | <string>IVirtualDesktopNotification,{C179334C-4295-40D3-BEA1-C654D965605A} </string> 31 | <string>IVirtualDesktopNotificationService,{0CD45E71-D927-4F15-8B0A-8FEF525337BF} </string> 32 | <string>IVirtualDesktopPinnedApps,{4CE81583-1E4C-4632-A621-07A53543148F} </string> 33 | </ArrayOfString> 34 | 35 | 36 | <?xml version="1.0" encoding="utf-16"?> 37 | <ArrayOfString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 38 | <string>IApplicationView,{372e1d3b-38d3-42e4-a15b-8ab2b178f513} </string> 39 | <string>IApplicationViewCollection,{1841c6d7-4f9d-42c0-af41-8747538f10e5} </string> 40 | <string>IObjectArray,{92CA9DCD-5622-4BBA-A805-5E9F541BD8C9} </string> 41 | <string>IServiceProvider,{6D5140C1-7436-11CE-8034-00AA006009FA} </string> 42 | <string>IVirtualDesktop,{536d3495-b208-4cc9-ae26-de8111275bf8} </string> 43 | <string>IVirtualDesktopManager,{a5cd92ff-29be-454c-8d04-d82879fb3f1b} </string> 44 | <string>IVirtualDesktopManagerInternal,{b2f925b9-5a0f-4d2e-9f4d-2b1507593c10} </string> 45 | <string>IVirtualDesktopNotification,{cd403e52-deed-4c13-b437-b98380f2b1e8} </string> 46 | <string>IVirtualDesktopNotificationService,{0cd45e71-d927-4f15-8b0a-8fef525337bf} </string> 47 | <string>IVirtualDesktopPinnedApps,{4ce81583-1e4c-4632-a621-07a53543148f} </string> 48 | </ArrayOfString> 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Utils/Disposable.cs: -------------------------------------------------------------------------------- 1 | namespace WindowsDesktop.Utils 2 | { 3 | internal class Disposable 4 | { 5 | public static IDisposable Create(Action dispose) 6 | { 7 | return new AnonymousDisposable(dispose); 8 | } 9 | 10 | private class AnonymousDisposable : IDisposable 11 | { 12 | private bool _isDisposed; 13 | private readonly Action _dispose; 14 | 15 | public AnonymousDisposable(Action dispose) 16 | { 17 | this._dispose = dispose; 18 | } 19 | 20 | public void Dispose() 21 | { 22 | if (this._isDisposed) return; 23 | 24 | this._isDisposed = true; 25 | this._dispose(); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Utils/ExplorerRestartListenerWindow.cs: -------------------------------------------------------------------------------- 1 | using WindowsDesktop.Interop; 2 | 3 | namespace WindowsDesktop.Utils; 4 | 5 | internal class ExplorerRestartListenerWindow : TransparentWindow 6 | { 7 | private uint _explorerRestartedMessage; 8 | private readonly Action _action; 9 | 10 | public ExplorerRestartListenerWindow(Action action) 11 | { 12 | this.Name = nameof(ExplorerRestartListenerWindow); 13 | this._action = action; 14 | } 15 | 16 | public override void Show() 17 | { 18 | base.Show(); 19 | this._explorerRestartedMessage = PInvoke.RegisterWindowMessage("TaskbarCreated"); 20 | } 21 | 22 | protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 23 | { 24 | if (msg == this._explorerRestartedMessage) 25 | { 26 | this._action(); 27 | return IntPtr.Zero; 28 | } 29 | 30 | return base.WndProc(hwnd, msg, wParam, lParam, ref handled); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Utils/RawWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Interop; 3 | using System.Windows.Threading; 4 | using WindowsDesktop.Interop; 5 | 6 | namespace WindowsDesktop.Utils; 7 | 8 | internal abstract class RawWindow 9 | { 10 | private HwndSource? _source; 11 | 12 | public IntPtr Handle 13 | => this._source?.Handle ?? IntPtr.Zero; 14 | public string? Name { get; init; } 15 | 16 | public virtual void Show() 17 | { 18 | this.Show(new HwndSourceParameters(this.Name)); 19 | } 20 | 21 | protected void Show(HwndSourceParameters parameters) 22 | { 23 | this._source = new HwndSource(parameters); 24 | this._source.AddHook(this.WndProc); 25 | } 26 | 27 | public virtual void Close() 28 | { 29 | this._source?.RemoveHook(this.WndProc); 30 | // Source could have been created on a different thread, which means we 31 | // have to Dispose of it on the UI thread or it will crash. 32 | this._source?.Dispatcher?.BeginInvoke(DispatcherPriority.Send, () => this._source?.Dispose()); 33 | this._source = null; 34 | 35 | PInvoke.CloseWindow(this.Handle); 36 | } 37 | 38 | protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 39 | { 40 | return IntPtr.Zero; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/VirtualDesktop/Utils/TransparentWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows.Interop; 5 | 6 | namespace WindowsDesktop.Utils; 7 | 8 | internal class TransparentWindow : RawWindow 9 | { 10 | public override void Show() 11 | { 12 | var parameters = new HwndSourceParameters(this.Name) 13 | { 14 | Width = 1, 15 | Height = 1, 16 | WindowStyle = 0x800000, 17 | }; 18 | 19 | this.Show(parameters); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/VirtualDesktop/VirtualDesktop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using WindowsDesktop.Interop; 6 | using WindowsDesktop.Interop.Proxy; 7 | 8 | namespace WindowsDesktop; 9 | 10 | /// 11 | /// Encapsulates Windows 11 (and Windows 10) virtual desktops. 12 | /// 13 | [DebuggerDisplay("{Name} ({Id})")] 14 | public partial class VirtualDesktop 15 | { 16 | #region instance members 17 | 18 | /// 19 | /// Gets the unique identifier for this virtual desktop. 20 | /// 21 | public Guid Id { get; } 22 | 23 | /// 24 | /// Gets or sets the name of this virtual desktop. 25 | /// 26 | /// 27 | /// This is not supported on Windows 10. 28 | /// 29 | public string Name 30 | { 31 | get => this._name; 32 | set 33 | { 34 | _provider.VirtualDesktopManagerInternal.SetDesktopName(this._source, value); 35 | this._name = value; 36 | } 37 | } 38 | 39 | /// 40 | /// Gets or sets the path of the desktop wallpaper. 41 | /// 42 | /// 43 | /// This is not supported on Windows 10. 44 | /// 45 | public string WallpaperPath 46 | { 47 | get => this._wallpaperPath; 48 | set 49 | { 50 | _provider.VirtualDesktopManagerInternal.SetDesktopWallpaper(this._source, value); 51 | this._wallpaperPath = value; 52 | } 53 | } 54 | 55 | /// 56 | /// Switches to this virtual desktop. 57 | /// 58 | public void Switch() 59 | { 60 | _provider.VirtualDesktopManagerInternal.SwitchDesktop(this._source); 61 | } 62 | 63 | /// 64 | /// Removes this virtual desktop and switches to an available one. 65 | /// 66 | /// If this is the last virtual desktop, a new one will be created to switch to. 67 | public void Remove() 68 | => this.Remove(this.GetRight() ?? this.GetLeft() ?? Create()); 69 | 70 | /// 71 | /// Removes this virtual desktop and switches to . 72 | /// 73 | /// A virtual desktop to be displayed after the virtual desktop is removed. 74 | public void Remove(VirtualDesktop fallbackDesktop) 75 | { 76 | if (fallbackDesktop == null) throw new ArgumentNullException(nameof(fallbackDesktop)); 77 | 78 | _provider.VirtualDesktopManagerInternal.RemoveDesktop(this._source, fallbackDesktop._source); 79 | } 80 | 81 | /// 82 | /// Returns the adjacent virtual desktop on the left, or null if there isn't one. 83 | /// 84 | public VirtualDesktop? GetLeft() 85 | { 86 | return SafeInvoke( 87 | () => _provider.VirtualDesktopManagerInternal 88 | .GetAdjacentDesktop(this._source, AdjacentDesktop.LeftDirection) 89 | .ToVirtualDesktop(), 90 | HResult.TYPE_E_OUTOFBOUNDS); 91 | } 92 | 93 | /// 94 | /// Returns the adjacent virtual desktop on the right, or null if there isn't one. 95 | /// 96 | public VirtualDesktop? GetRight() 97 | { 98 | return SafeInvoke( 99 | () => _provider.VirtualDesktopManagerInternal 100 | .GetAdjacentDesktop(this._source, AdjacentDesktop.RightDirection) 101 | .ToVirtualDesktop(), 102 | HResult.TYPE_E_OUTOFBOUNDS); 103 | } 104 | 105 | public override string ToString() 106 | => $"VirtualDesktop {this.Id} '{this._name}'"; 107 | 108 | #endregion 109 | 110 | #region static members (get or create) 111 | 112 | /// 113 | /// Gets the virtual desktop that is currently displayed. 114 | /// 115 | public static VirtualDesktop Current 116 | { 117 | get 118 | { 119 | InitializeIfNeeded(); 120 | 121 | return _provider.VirtualDesktopManagerInternal 122 | .GetCurrentDesktop() 123 | .ToVirtualDesktop(); 124 | } 125 | } 126 | 127 | /// 128 | /// Returns an array of available virtual desktops. 129 | /// 130 | public static VirtualDesktop[] GetDesktops() 131 | { 132 | InitializeIfNeeded(); 133 | 134 | return _provider.VirtualDesktopManagerInternal 135 | .GetDesktops() 136 | .Select(x => x.ToVirtualDesktop()) 137 | .ToArray(); 138 | } 139 | 140 | /// 141 | /// Returns a new virtual desktop. 142 | /// 143 | public static VirtualDesktop Create() 144 | { 145 | InitializeIfNeeded(); 146 | 147 | return _provider.VirtualDesktopManagerInternal 148 | .CreateDesktop() 149 | .ToVirtualDesktop(); 150 | } 151 | 152 | /// 153 | /// Returns a virtual desktop matching the specified identifier. 154 | /// 155 | /// The identifier of the virtual desktop to return. 156 | /// Returns if the identifier is not associated with any available desktops. 157 | public static VirtualDesktop? FromId(Guid desktopId) 158 | { 159 | InitializeIfNeeded(); 160 | 161 | return SafeInvoke(() => _provider.VirtualDesktopManagerInternal 162 | .FindDesktop(desktopId) 163 | .ToVirtualDesktop()); 164 | } 165 | 166 | /// 167 | /// Returns the virtual desktop the window is located on. 168 | /// 169 | /// The handle of the window. 170 | /// Returns if the handle is not associated with any open windows. 171 | public static VirtualDesktop? FromHwnd(IntPtr hWnd) 172 | { 173 | InitializeIfNeeded(); 174 | 175 | if (hWnd == IntPtr.Zero || IsPinnedWindow(hWnd)) return null; 176 | 177 | return SafeInvoke( 178 | () => 179 | { 180 | var desktopId = _provider.VirtualDesktopManager.GetWindowDesktopId(hWnd); 181 | return _provider.VirtualDesktopManagerInternal 182 | .FindDesktop(desktopId) 183 | .ToVirtualDesktop(); 184 | }, 185 | HResult.REGDB_E_CLASSNOTREG, HResult.TYPE_E_ELEMENTNOTFOUND); 186 | } 187 | 188 | #endregion 189 | 190 | #region static members (pinned apps) 191 | 192 | /// 193 | /// Determines whether the specified window is pinned. 194 | /// 195 | /// The handle of the window. 196 | /// if pinned, otherwise. 197 | public static bool IsPinnedWindow(IntPtr hWnd) 198 | { 199 | InitializeIfNeeded(); 200 | 201 | return SafeInvoke(() => _provider.VirtualDesktopPinnedApps.IsViewPinned(hWnd)); 202 | } 203 | 204 | /// 205 | /// Pins the specified window, showing it on all virtual desktops. 206 | /// 207 | /// The handle of the window. 208 | /// if already pinned or successfully pinned, otherwise (most of the time, the target window is not found or not ready). 209 | public static bool PinWindow(IntPtr hWnd) 210 | { 211 | InitializeIfNeeded(); 212 | 213 | return _provider.VirtualDesktopPinnedApps.IsViewPinned(hWnd) 214 | || SafeInvoke(() => _provider.VirtualDesktopPinnedApps.PinView(hWnd)); 215 | } 216 | 217 | /// 218 | /// Unpins the specified window. 219 | /// 220 | /// The handle of the window. 221 | /// if already unpinned or successfully unpinned, otherwise (most of the time, the target window is not found or not ready). 222 | public static bool UnpinWindow(IntPtr hWnd) 223 | { 224 | InitializeIfNeeded(); 225 | 226 | return _provider.VirtualDesktopPinnedApps.IsViewPinned(hWnd) == false 227 | || SafeInvoke(() => _provider.VirtualDesktopPinnedApps.UnpinView(hWnd)); 228 | } 229 | 230 | /// 231 | /// Determines whether the specified app is pinned. 232 | /// 233 | /// App User Model ID. method may be helpful. 234 | /// if pinned, otherwise. 235 | public static bool IsPinnedApplication(string appUserModelId) 236 | { 237 | InitializeIfNeeded(); 238 | 239 | return SafeInvoke(() => _provider.VirtualDesktopPinnedApps.IsAppIdPinned(appUserModelId)); 240 | } 241 | 242 | /// 243 | /// Pins the specified app, showing it on all virtual desktops. 244 | /// 245 | /// App User Model ID. method may be helpful. 246 | /// if already pinned or successfully pinned, otherwise (most of the time, app id is incorrect). 247 | public static bool PinApplication(string appUserModelId) 248 | { 249 | InitializeIfNeeded(); 250 | 251 | return _provider.VirtualDesktopPinnedApps.IsAppIdPinned(appUserModelId) 252 | || SafeInvoke(() => _provider.VirtualDesktopPinnedApps.PinAppID(appUserModelId)); 253 | } 254 | 255 | /// 256 | /// Unpins the specified app. 257 | /// 258 | /// App User Model ID. method may be helpful. 259 | /// if already unpinned or successfully unpinned, otherwise (most of the time, app id is incorrect). 260 | public static bool UnpinApplication(string appUserModelId) 261 | { 262 | InitializeIfNeeded(); 263 | 264 | return _provider.VirtualDesktopPinnedApps.IsAppIdPinned(appUserModelId) == false 265 | || SafeInvoke(() => _provider.VirtualDesktopPinnedApps.UnpinAppID(appUserModelId)); 266 | } 267 | 268 | #endregion 269 | 270 | #region static members (others) 271 | 272 | /// 273 | /// Apply the specified wallpaper to all desktops. 274 | /// 275 | /// 276 | /// This is not supported on Windows 10. 277 | /// 278 | /// Wallpaper image path. 279 | public static void UpdateWallpaperForAllDesktops(string path) 280 | { 281 | InitializeIfNeeded(); 282 | 283 | _provider.VirtualDesktopManagerInternal.UpdateWallpaperPathForAllDesktops(path); 284 | } 285 | 286 | /// 287 | /// Moves a window to the specified virtual desktop. 288 | /// 289 | /// The handle of the window to be moved. 290 | /// The virtual desktop to move the window to. 291 | public static void MoveToDesktop(IntPtr hWnd, VirtualDesktop virtualDesktop) 292 | { 293 | InitializeIfNeeded(); 294 | 295 | var result = PInvoke.GetWindowThreadProcessId(hWnd, out var processId); 296 | if (result < 0) throw new Exception($"The process associated with '{hWnd}' not found."); 297 | 298 | if (processId == Environment.ProcessId) 299 | { 300 | _provider.VirtualDesktopManager.MoveWindowToDesktop(hWnd, virtualDesktop.Id); 301 | } 302 | else 303 | { 304 | _provider.VirtualDesktopManagerInternal.MoveViewToDesktop(hWnd, virtualDesktop._source); 305 | } 306 | } 307 | 308 | /// 309 | /// Determines whether this window is on the current virtual desktop. 310 | /// 311 | /// The handle of the window. 312 | public static bool IsCurrentVirtualDesktop(IntPtr hWnd) 313 | { 314 | InitializeIfNeeded(); 315 | 316 | return _provider.VirtualDesktopManager.IsWindowOnCurrentVirtualDesktop(hWnd); 317 | } 318 | 319 | /// 320 | /// Try gets the App User Model ID with the specified foreground window. 321 | /// 322 | /// The handle of the window. 323 | /// App User Model ID. 324 | /// if the App User Model ID is available, otherwise. 325 | public static bool TryGetAppUserModelId(IntPtr hWnd, out string appUserModelId) 326 | { 327 | InitializeIfNeeded(); 328 | 329 | try 330 | { 331 | appUserModelId = _provider.ApplicationViewCollection 332 | .GetViewForHwnd(hWnd) 333 | .GetAppUserModelId(); 334 | return true; 335 | } 336 | catch (Exception ex) 337 | { 338 | Debug.WriteLine($"{nameof(TryGetAppUserModelId)} failed."); 339 | Debug.WriteLine(ex); 340 | } 341 | 342 | appUserModelId = ""; 343 | return false; 344 | } 345 | 346 | #endregion 347 | } 348 | -------------------------------------------------------------------------------- /src/VirtualDesktop/VirtualDesktop.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0-windows10.0.19041.0;net5.0-windows10.0.19041.0 5 | latest 6 | true 7 | enable 8 | enable 9 | 5.0.5 10 | VirtualDesktop 11 | Grabacr07 12 | grabacr.net 13 | Copyright © 2022 Manato KAMEYA 14 | C# Wrapper for the Virtual Desktop API on Windows 11 (and Windows 10). 15 | https://github.com/Grabacr07/VirtualDesktop 16 | https://github.com/Grabacr07/VirtualDesktop 17 | LICENSE 18 | README.md 19 | Windows;Windows10;Windows11;Desktop;VirtualDesktop; 20 | True 21 | WindowsDesktop 22 | 23 | 24 | 25 | 1701;1702;1591 26 | 27 | 28 | 29 | 1701;1702;1591 30 | 31 | 32 | 33 | 1701;1702;1591 34 | 35 | 36 | 37 | 1701;1702;1591 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | True 60 | 61 | 62 | 63 | True 64 | \ 65 | 66 | 67 | 68 | 69 | 70 | True 71 | True 72 | Settings.settings 73 | 74 | 75 | SettingsSingleFileGenerator 76 | Settings.Designer.cs 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/VirtualDesktop/VirtualDesktop.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True 11 | True -------------------------------------------------------------------------------- /src/VirtualDesktop/VirtualDesktop.notification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using WindowsDesktop.Interop.Proxy; 6 | using WindowsDesktop.Utils; 7 | 8 | namespace WindowsDesktop; 9 | 10 | partial class VirtualDesktop 11 | { 12 | private static readonly ConcurrentDictionary _viewChangedEventListeners = new(); 13 | 14 | /// 15 | /// Occurs when a virtual desktop is created. 16 | /// 17 | /// 18 | /// See for details. 19 | /// 20 | public static event EventHandler? Created; 21 | 22 | public static event EventHandler? DestroyBegin; 23 | 24 | public static event EventHandler? DestroyFailed; 25 | 26 | /// 27 | /// Occurs when a virtual desktop is destroyed. 28 | /// 29 | /// 30 | /// See for details. 31 | /// 32 | public static event EventHandler? Destroyed; 33 | 34 | /// 35 | /// Occurs when the current virtual desktop is changed. 36 | /// 37 | /// 38 | /// The internal initialization is triggered by the call of a static property/method.
39 | /// Therefore, events are not fired just by subscribing to them.
40 | ///
41 | /// If you want to use only event subscription, the following code is recommended.
42 | /// 43 | /// VirtualDesktop.Configuration(); 44 | /// 45 | ///
46 | public static event EventHandler? CurrentChanged; 47 | 48 | /// 49 | /// Occurs when the virtual desktop is moved. 50 | /// 51 | /// 52 | /// See for details. 53 | /// 54 | public static event EventHandler? Moved; 55 | 56 | /// 57 | /// Occurs when a virtual desktop is renamed. 58 | /// 59 | /// 60 | /// See for details. 61 | /// 62 | public static event EventHandler? Renamed; 63 | 64 | /// 65 | /// Occurs when a virtual desktop wallpaper is changed. 66 | /// 67 | /// 68 | /// See for details. 69 | /// 70 | public static event EventHandler? WallpaperChanged; 71 | 72 | /// 73 | /// Register a listener to receive changes in the application view. 74 | /// 75 | /// The target window handle to receive events from. If specify , all changes will be delivered. 76 | /// Action to be performed. 77 | /// instance for unsubscribing. 78 | public static IDisposable RegisterViewChanged(IntPtr targetHwnd, Action action) 79 | { 80 | InitializeIfNeeded(); 81 | 82 | var listener = _viewChangedEventListeners.GetOrAdd(targetHwnd, x => new ViewChangedListener(x)); 83 | listener.Listeners.Add(action); 84 | 85 | return Disposable.Create(() => listener.Listeners.Remove(action)); 86 | } 87 | 88 | private class EventProxy : IVirtualDesktopNotification 89 | { 90 | public void VirtualDesktopCreated(IVirtualDesktop pDesktop) 91 | => Created?.Invoke(this, pDesktop.ToVirtualDesktop()); 92 | 93 | public void VirtualDesktopDestroyBegin(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback) 94 | => DestroyBegin?.Invoke(this, new VirtualDesktopDestroyEventArgs(pDesktopDestroyed, pDesktopFallback)); 95 | 96 | public void VirtualDesktopDestroyFailed(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback) 97 | => DestroyFailed?.Invoke(this, new VirtualDesktopDestroyEventArgs(pDesktopDestroyed, pDesktopFallback)); 98 | 99 | public void VirtualDesktopDestroyed(IVirtualDesktop pDesktopDestroyed, IVirtualDesktop pDesktopFallback) 100 | => Destroyed?.Invoke(this, new VirtualDesktopDestroyEventArgs(pDesktopDestroyed, pDesktopFallback)); 101 | 102 | public void VirtualDesktopMoved(IVirtualDesktop pDesktop, int nIndexFrom, int nIndexTo) 103 | => Moved?.Invoke(this, new VirtualDesktopMovedEventArgs(pDesktop, nIndexFrom, nIndexTo)); 104 | 105 | public void ViewVirtualDesktopChanged(IApplicationView pView) 106 | { 107 | if (_viewChangedEventListeners.TryGetValue(IntPtr.Zero, out var all)) all.Call(); 108 | if (_viewChangedEventListeners.TryGetValue(pView.GetThumbnailWindow(), out var listener)) listener.Call(); 109 | } 110 | 111 | public void CurrentVirtualDesktopChanged(IVirtualDesktop pDesktopOld, IVirtualDesktop pDesktopNew) 112 | => CurrentChanged?.Invoke(this, new VirtualDesktopChangedEventArgs(pDesktopOld, pDesktopNew)); 113 | 114 | public void VirtualDesktopRenamed(IVirtualDesktop pDesktop, string chName) 115 | { 116 | var desktop = pDesktop.ToVirtualDesktop(); 117 | desktop._name = chName; 118 | 119 | Renamed?.Invoke(this, new VirtualDesktopRenamedEventArgs(desktop, chName)); 120 | } 121 | 122 | public void VirtualDesktopWallpaperChanged(IVirtualDesktop pDesktop, string chPath) 123 | { 124 | var desktop = pDesktop.ToVirtualDesktop(); 125 | desktop._wallpaperPath = chPath; 126 | 127 | WallpaperChanged?.Invoke(this, new VirtualDesktopWallpaperChangedEventArgs(desktop, chPath)); 128 | } 129 | } 130 | 131 | private class ViewChangedListener 132 | { 133 | private readonly IntPtr _targetHandle; 134 | 135 | public List> Listeners { get; } = new(); 136 | 137 | public ViewChangedListener(IntPtr targetHandle) 138 | { 139 | this._targetHandle = targetHandle; 140 | } 141 | 142 | public void Call() 143 | { 144 | foreach (var listener in this.Listeners) listener(this._targetHandle); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/VirtualDesktop/VirtualDesktop.system.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using WindowsDesktop.Interop; 8 | using WindowsDesktop.Interop.Build10240; 9 | using WindowsDesktop.Interop.Build22000; 10 | using WindowsDesktop.Interop.Proxy; 11 | using WindowsDesktop.Properties; 12 | using WindowsDesktop.Utils; 13 | 14 | namespace WindowsDesktop; 15 | 16 | partial class VirtualDesktop 17 | { 18 | private static readonly VirtualDesktopProvider _provider; 19 | private static readonly ConcurrentDictionary _knownDesktops = new(); 20 | private static readonly ExplorerRestartListenerWindow _explorerRestartListener = new(() => HandleExplorerRestarted()); 21 | private static VirtualDesktopConfiguration _configuration = new(); 22 | private static ComInterfaceAssembly? _assembly; 23 | private static IDisposable? _notificationListener; 24 | private readonly IVirtualDesktop _source; 25 | private string _name; 26 | private string _wallpaperPath; 27 | 28 | /// 29 | /// Gets a value indicating virtual desktops are supported by the host. 30 | /// 31 | public static bool IsSupported 32 | => _provider.IsSupported; 33 | 34 | static VirtualDesktop() 35 | { 36 | _provider = Environment.OSVersion.Version.Build switch 37 | { 38 | >= 22000 => new VirtualDesktopProvider22000(), 39 | >= 10240 => new VirtualDesktopProvider10240(), 40 | _ => new VirtualDesktopProvider.NotSupported() 41 | }; 42 | 43 | Debug.WriteLine($"*** {AssemblyInfo.Title} Library ***"); 44 | Debug.WriteLine($"Version: {AssemblyInfo.VersionString}"); 45 | Debug.WriteLine($"OS Build: {Environment.OSVersion.Version.Build}"); 46 | Debug.WriteLine($"Provider: {_provider.GetType().Name}"); 47 | } 48 | 49 | private VirtualDesktop(IVirtualDesktop source) 50 | { 51 | this._source = source; 52 | this._name = source.GetName(); 53 | this._wallpaperPath = source.GetWallpaperPath(); 54 | this.Id = source.GetID(); 55 | } 56 | 57 | /// 58 | /// Initialize using the default settings. This method should always be called first. 59 | /// 60 | public static void Configure() 61 | { 62 | InitializeIfNeeded(); 63 | } 64 | 65 | /// 66 | /// Sets the behavior for compiling the assembly. This method should always be called first. 67 | /// 68 | public static void Configure(VirtualDesktopConfiguration configuration) 69 | { 70 | _configuration = configuration; 71 | InitializeIfNeeded(); 72 | } 73 | 74 | internal static VirtualDesktop FromComObject(IVirtualDesktop desktop) 75 | => _knownDesktops.GetOrAdd(desktop.GetID(), _ => new VirtualDesktop(desktop)); 76 | 77 | internal static void InitializeIfNeeded() 78 | { 79 | if (IsSupported == false) throw new NotSupportedException("You must target Windows 10 or later in your 'app.manifest' and run without debugging."); 80 | if (_provider.IsInitialized) return; 81 | 82 | _explorerRestartListener.Show(); 83 | InitializeCore(); 84 | } 85 | 86 | private static void HandleExplorerRestarted() 87 | { 88 | _knownDesktops.Clear(); 89 | _provider.IsInitialized = false; 90 | InitializeCore(); 91 | } 92 | 93 | private static void InitializeCore() 94 | { 95 | _provider.Initialize(_assembly ??= new ComInterfaceAssemblyBuilder(_configuration).GetAssembly()); 96 | 97 | _notificationListener?.Dispose(); 98 | _notificationListener = _provider.VirtualDesktopNotificationService.Register(new EventProxy()); 99 | } 100 | 101 | private static T? SafeInvoke(Func action, params HResult[] hResult) 102 | { 103 | try 104 | { 105 | return action(); 106 | } 107 | catch (COMException ex) when (ex.Match(hResult is { Length: 0 } ? new[] { HResult.TYPE_E_ELEMENTNOTFOUND, } : hResult)) 108 | { 109 | return default; 110 | } 111 | } 112 | 113 | private static bool SafeInvoke(Action action, params HResult[] hResult) 114 | { 115 | try 116 | { 117 | action(); 118 | return true; 119 | } 120 | catch (COMException ex) when (ex.Match(hResult is { Length: 0 } ? new[] { HResult.TYPE_E_ELEMENTNOTFOUND, } : hResult)) 121 | { 122 | return false; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/VirtualDesktop/VirtualDesktopEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WindowsDesktop.Interop.Proxy; 3 | 4 | namespace WindowsDesktop; 5 | 6 | /// 7 | /// Provides data for the event. 8 | /// 9 | public class VirtualDesktopRenamedEventArgs : EventArgs 10 | { 11 | public VirtualDesktop Desktop { get; } 12 | 13 | public string Name { get; } 14 | 15 | public VirtualDesktopRenamedEventArgs(VirtualDesktop desktop, string name) 16 | { 17 | this.Desktop = desktop; 18 | this.Name = name; 19 | } 20 | } 21 | 22 | /// 23 | /// Provides data for the event. 24 | /// 25 | public class VirtualDesktopWallpaperChangedEventArgs : EventArgs 26 | { 27 | public VirtualDesktop Desktop { get; } 28 | 29 | public string Path { get; } 30 | 31 | public VirtualDesktopWallpaperChangedEventArgs(VirtualDesktop desktop, string path) 32 | { 33 | this.Desktop = desktop; 34 | this.Path = path; 35 | } 36 | } 37 | 38 | /// 39 | /// Provides data for the event. 40 | /// 41 | public class VirtualDesktopChangedEventArgs : EventArgs 42 | { 43 | public VirtualDesktop OldDesktop { get; } 44 | 45 | public VirtualDesktop NewDesktop { get; } 46 | 47 | public VirtualDesktopChangedEventArgs(VirtualDesktop oldDesktop, VirtualDesktop newDesktop) 48 | { 49 | this.OldDesktop = oldDesktop; 50 | this.NewDesktop = newDesktop; 51 | } 52 | 53 | internal VirtualDesktopChangedEventArgs(IVirtualDesktop oldDesktop, IVirtualDesktop newDesktop) 54 | : this(oldDesktop.ToVirtualDesktop(), newDesktop.ToVirtualDesktop()) 55 | { 56 | } 57 | } 58 | 59 | /// 60 | /// Provides data for the event. 61 | /// 62 | public class VirtualDesktopMovedEventArgs : EventArgs 63 | { 64 | public VirtualDesktop Desktop { get; } 65 | 66 | public int OldIndex { get; } 67 | 68 | public int NewIndex { get; } 69 | 70 | public VirtualDesktopMovedEventArgs(VirtualDesktop desktop, int oldIndex, int newIndex) 71 | { 72 | this.Desktop = desktop; 73 | this.OldIndex = oldIndex; 74 | this.NewIndex = newIndex; 75 | } 76 | 77 | internal VirtualDesktopMovedEventArgs(IVirtualDesktop desktop, int oldIndex, int newIndex) 78 | : this(desktop.ToVirtualDesktop(), oldIndex, newIndex) 79 | { 80 | } 81 | } 82 | 83 | /// 84 | /// Provides data for the , , and events. 85 | /// 86 | public class VirtualDesktopDestroyEventArgs : EventArgs 87 | { 88 | /// 89 | /// Gets the virtual desktop that was destroyed. 90 | /// 91 | public VirtualDesktop Destroyed { get; } 92 | 93 | /// 94 | /// Gets the virtual desktop to be displayed after is destroyed. 95 | /// 96 | public VirtualDesktop Fallback { get; } 97 | 98 | public VirtualDesktopDestroyEventArgs(VirtualDesktop destroyed, VirtualDesktop fallback) 99 | { 100 | this.Destroyed = destroyed; 101 | this.Fallback = fallback; 102 | } 103 | 104 | internal VirtualDesktopDestroyEventArgs(IVirtualDesktop destroyed, IVirtualDesktop fallback) 105 | : this(destroyed.ToVirtualDesktop(), fallback.ToVirtualDesktop()) 106 | { 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/VirtualDesktop/VirtualDesktopExtensions.cs: -------------------------------------------------------------------------------- 1 | using WindowsDesktop.Interop; 2 | using WindowsDesktop.Interop.Proxy; 3 | 4 | namespace WindowsDesktop; 5 | 6 | public static class VirtualDesktopExtensions 7 | { 8 | internal static VirtualDesktop ToVirtualDesktop(this IVirtualDesktop desktop) 9 | => VirtualDesktop.FromComObject(desktop); 10 | } 11 | -------------------------------------------------------------------------------- /src/VirtualDesktop/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | IApplicationView,{871F602A-2B58-42B4-8C4B-6C43D642C06F} 14 | IApplicationViewCollection,{2C08ADF0-A386-4B35-9250-0FE183476FCC} 15 | IObjectArray,{92CA9DCD-5622-4BBA-A805-5E9F541BD8C9} 16 | IServiceProvider,{6D5140C1-7436-11CE-8034-00AA006009FA} 17 | IVirtualDesktop,{FF72FFDD-BE7E-43FC-9C03-AD81681E88E4} 18 | IVirtualDesktopManager,{A5CD92FF-29BE-454C-8D04-D82879FB3F1B} 19 | IVirtualDesktopManagerInternal,{F31574D6-B682-4CDC-BD56-1827860ABEC6} 20 | IVirtualDesktopNotification,{C179334C-4295-40D3-BEA1-C654D965605A} 21 | IVirtualDesktopNotificationService,{0CD45E71-D927-4F15-8B0A-8FEF525337BF} 22 | IVirtualDesktopPinnedApps,{4CE81583-1E4C-4632-A621-07A53543148F} 23 | 24 | 25 | 26 | 27 | 28 | 29 | IApplicationView,{9AC0B5C8-1484-4C5B-9533-4134A0F97CEA} 30 | IApplicationViewCollection,{2C08ADF0-A386-4B35-9250-0FE183476FCC} 31 | IObjectArray,{92CA9DCD-5622-4BBA-A805-5E9F541BD8C9} 32 | IServiceProvider,{6D5140C1-7436-11CE-8034-00AA006009FA} 33 | IVirtualDesktop,{FF72FFDD-BE7E-43FC-9C03-AD81681E88E4} 34 | IVirtualDesktopManager,{A5CD92FF-29BE-454C-8D04-D82879FB3F1B} 35 | IVirtualDesktopManagerInternal,{F31574D6-B682-4CDC-BD56-1827860ABEC6} 36 | IVirtualDesktopNotification,{C179334C-4295-40D3-BEA1-C654D965605A} 37 | IVirtualDesktopNotificationService,{0CD45E71-D927-4F15-8B0A-8FEF525337BF} 38 | IVirtualDesktopPinnedApps,{4CE81583-1E4C-4632-A621-07A53543148F} 39 | 40 | 41 | 42 | 43 | 44 | 45 | IApplicationView,{372e1d3b-38d3-42e4-a15b-8ab2b178f513} 46 | IApplicationViewCollection,{1841c6d7-4f9d-42c0-af41-8747538f10e5} 47 | IObjectArray,{92CA9DCD-5622-4BBA-A805-5E9F541BD8C9} 48 | IServiceProvider,{6D5140C1-7436-11CE-8034-00AA006009FA} 49 | IVirtualDesktop,{536d3495-b208-4cc9-ae26-de8111275bf8} 50 | IVirtualDesktopManager,{a5cd92ff-29be-454c-8d04-d82879fb3f1b} 51 | IVirtualDesktopManagerInternal,{b2f925b9-5a0f-4d2e-9f4d-2b1507593c10} 52 | IVirtualDesktopNotification,{cd403e52-deed-4c13-b437-b98380f2b1e8} 53 | IVirtualDesktopNotificationService,{0cd45e71-d927-4f15-8b0a-8fef525337bf} 54 | IVirtualDesktopPinnedApps,{4ce81583-1e4c-4632-a621-07a53543148f} 55 | 56 | 57 | 58 | 59 | 60 | 61 | --------------------------------------------------------------------------------