Ok, so this is the solution I am currently using. The only reason this is still accessible is thanks to the hard work by Markus Scholtes to keep the COM API methods updated for each specific version of Windows. You will find his work at:
https://github.com/MScholtes/VirtualDesktop
You will need to download the following files from his repository:
- 'VirtualDesktop[version].cs'
- 'Compile.bat'
- 'MScholtes.ico'
If you're running Windows 11 21H2, you need to download 'VirtualDesktop11-21H2.cs'. If you're running Windows 11 22H2, you need to download 'VirtualDesktop11.cs'. Once you've downloaded all those file and put them into the same folder, simply run 'Compile.bat' and you should get the needed 'VirtualDesktop11.exe' file (or possibly 'VirtualDesktop11-21H2.exe' if using that version).
Put that exe file somewhere you want to store it permanently as you will need to know the path to it to access it in the DisplayFusion functions.
For each function in DisplayFusion ('Switch Virtual Desktop Left', 'Switch Virtual Desktop Right', 'Move Window to Virtual Desktop Left', 'Toggle Pin Window Virtual Desktop', etc), you will use the same script I've posted below. The only thing you need to change in each individual script is the command inside the 'public static void Run(IntPtr windowsHandle)' function.
Here are a few of the commands I'm using inside the Run function. Note: I believe I set up the functions so that they will wrap to the 1st/last virtual desktop if needed (say for example, you try to switch to the prev virtual desktop but you're already on the 1st, then it will switch to the last, and vice versa).
- Vd.TogglePinWindow(windowHandle); // Toggle's the 'Pin' status of the window such that it will show on all virtual desktops
- Vd.GoToNextDesktop(); // Switch Virtual Desktop - Right
- Vd.GoToPrevDesktop(); // Switch Virtual Desktop - Left
- Vd.MoveWindowToNextDesktop(windowHandle); // Move Window to Next (Right) Virtual Desktop
- Vd.MoveWindowToPrevDesktop(windowHandle); // Move Window to Prev (Left) Virtual Desktop
Obviously there are many more functions you can implement but these examples should help you understand how things work. Below is the actual script. A few things to note:
- You need to change the path to the 'VirtualDesktop11.exe' inside the 'ExecCmd' function
- Running the script will not produce any type of console window or anything... it all happens entirely invisible to the user
- The functions inside of '#region Helpers' are meant to be functions for carrying out very specific functionality whereas the other functions are more general in nature... I believe I wrote all of the code in the entire script but it has been a while so don't quote me on that
If you have any questions or need any help, just let me know. I'll try to keep an eye on this thread in case you post a reply. Oh, and if anyone gets any use out of my post, please let me know.. Would be nice to know I didn't spend all the time making this post for nothing.
using System;
using System.Drawing;
using System.Text.RegularExpressions;
// The 'windowHandle' parameter will contain the window handle for the:
// - Active window when run by hotkey
// - Trigger target when run by a Trigger rule
// - TitleBar Button owner when run by a TitleBar Button
// - Jump List owner when run from a Taskbar Jump List
// - Currently focused window if none of these match
public static class DisplayFusionFunction
{
public static void Run(IntPtr windowHandle)
{
// Example command:
// Vd.TogglePinWindow(windowHandle);
}
}
public static class Vd
{
#region Helpers
public static int GetDesktopLeftOfDesktop(int number)
{
var prevDesktop = number - 1;
if (prevDesktop < 0)
prevDesktop = GetDesktopCount() - 1;
return prevDesktop;
}
public static int GetDesktopRightOfDesktop(int number)
{
var nextDesktop = number + 1;
if (nextDesktop > (GetDesktopCount() - 1))
nextDesktop = 0;
return nextDesktop;
}
public static int GetDesktopLeftOfCurrent()
{
return GetDesktopLeftOfDesktop(GetCurrentDesktopNumber());
}
public static int GetDesktopRightOfCurrent()
{
return GetDesktopRightOfDesktop(GetCurrentDesktopNumber());
}
public static void GoToPrevDesktop()
{
ExecCmd($"/Switch:{GetDesktopLeftOfCurrent()}");
}
public static void GoToNextDesktop()
{
ExecCmd($"/Switch:{GetDesktopRightOfCurrent()}");
}
public static void MoveWindowToPrevDesktop(IntPtr handle)
{
MoveWindowToDesktopNumber(handle, GetDesktopLeftOfCurrent());
}
public static void MoveWindowToNextDesktop(IntPtr handle)
{
MoveWindowToDesktopNumber(handle, GetDesktopRightOfCurrent());
}
public static void TogglePinWindow(IntPtr handle)
{
if (IsPinnedWindow(handle))
UnPinWindow(handle);
else
PinWindow(handle);
}
public static void TogglePinApp(int processId)
{
if (IsPinnedApp(processId))
UnPinApp(processId);
else
PinApp(processId);
}
#endregion
public static int GetCurrentDesktopNumber()
{
return ParseLastNumber(ExecCmd("/GetCurrentDesktop"));
}
public static int GetDesktopCount()
{
return ParseLastNumber(ExecCmd("/Count"));
}
public static int GetWindowDesktopNumber(IntPtr handle)
{
return ParseLastNumber(ExecCmd($"/GetDesktopFromWindowHandle:{handle}"));
}
public static bool IsWindowOnCurrentVirtualDesktop(IntPtr handle)
{
return ParseBool(ExecCmd($"/GetCurrentDesktop /IsWindowHandleOnDesktop:{handle}"));
}
public static void MoveWindowToDesktopNumber(IntPtr handle, int number)
{
ExecCmd($"/GetDesktop:{number} /MoveWindowHandle:{handle}");
}
public static void GoToDesktopNumber(int number)
{
ExecCmd($"/Switch:{number}");
}
public static bool IsPinnedWindow(IntPtr handle)
{
return ParseBool(ExecCmd($"/IsWindowHandlePinned:{handle}"));
}
public static void PinWindow(IntPtr handle)
{
ExecCmd($"/PinWindowHandle:{handle}");
}
public static void UnPinWindow(IntPtr handle)
{
ExecCmd($"/UnPinWindowHandle:{handle}");
}
public static bool IsPinnedApp(int processId)
{
return ParseBool(ExecCmd($"/IsApplicationPinned:{processId}"));
}
public static void PinApp(int processId)
{
ExecCmd($"/PinApplication:{processId}");
}
public static void UnPinApp(int processId)
{
ExecCmd($"/UnPinApplication:{processId}");
}
public static bool IsWindowOnDesktopNumber(IntPtr handle, int number)
{
return ParseBool(ExecCmd($"/GetDesktop:{number} /IsWindowHandleOnDesktop:{handle}"));
}
private static (int, string) ExecCmd(string args)
{
var p = new System.Diagnostics.Process();
p.StartInfo.FileName = @"C:\libraries\dotNET\VirtualDesktop11\VirtualDesktop11.exe";
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.Arguments = args;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.Start();
p.WaitForExit();
return (p.ExitCode, p.StandardOutput.ReadLine());
}
private static int ParseLastNumber((int exitCode, string output) t)
{
var match = Regex.Match(t.output, @"\d+(?!\D*\d)");
var num = match.Groups[0].Value;
return Int32.Parse(num);
}
private static bool ParseBool((int exitCode, string output) t)
{
return t.exitCode == 0;
}
private static int ParseDesktopNumber(string cmdOutput)
{
var match = Regex.Match(cmdOutput, @".*desktop number (\d+)");
var num = match.Groups[1].Value;
return Int32.Parse(num);
}
}