The virtual desktop system is an interesting feature that attempts to bring the massive benefits of multi-monitor computers to single-monitor people. With a simple key combo or button click, the screen is split into a tiled grid of several desktops in an Exposé (now called Mission Control)-type fashion with the ability to select one and switch to it (see fig. 1).

Virtual desktops on Windows (Sysinternals Desktops 2.0)
(Fig. 1) Here is a an example desktop switching “pager” called “Desktops” by Sysinternals running on my Windows 7 machine.

For all intents and purposes, a virtual desktop is treated like another physical monitor, giving you a tremendous amount of “screen space” to work with. This is excellent for developers or graphic artists whose careers require many programs to be up and running simultaneously but cannot use a multi-monitor system, whether it be due to high costs or space constraints of some kind.

When people think of virtual desktops, thoughts of Linux, Mac, and BSD come to mind, but never Windows! Believe it or not, the WinAPI does have full virtual desktop support (as shown by the screen that appears when you press CTRL+ALT+DEL), but Microsoft does not implement it in a user-accessible way. This tutorial will detail how to get access to it and embed it in your own application. So let’s get cracking!

The implementation

Let’s get down to business (to defeat the Huns!) First, we should declare a few things. Under the Access Control Model, the virtual desktops (called desktop objects by Windows) are securable objects, protected pieces of code that are only accessible to executables with the right “access codes” known as security descriptors.

In a new file VirtualDesktops.cs, let’s save our descriptors as a set of constants placed in a uint array so we can easily access them. We derive our VirtualDesktop class from the IDisposable interface so we can access the garbage collector and tell it to flush each desktop object from memory when we delete it.

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

public class VirtualDesktop : IDisposable
{
	// These security descriptors below are required to
	// let us manipulate the desktop objects.
	internal enum DESKTOP_ACCESS_MASK : uint {
		DESKTOP_NONE = 0,
		DESKTOP_READOBJECTS = 0x0001,
		DESKTOP_CREATEWINDOW = 0x0002,
		DESKTOP_CREATEMENU = 0x0004,
		DESKTOP_HOOKCONTROL = 0x0008,
		DESKTOP_JOURNALRECORD = 0x0010,
		DESKTOP_JOURNALPLAYBACK = 0x0020,
		DESKTOP_ENUMERATE = 0x0040,
		DESKTOP_WRITEOBJECTS = 0x0080,
		DESKTOP_SWITCHDESKTOP = 0x0100,

		EVERYTHING = (DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU |
			      DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD | DESKTOP_JOURNALPLAYBACK |
			      DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP),
	}

	#region Variables
	public IntPtr DesktopPtr;     // This will point to the current desktop we are using.
	public string _sMyDesk;       // This will hold the name for the desktop object we created.
	IntPtr _hOrigDesktop;         // This will remember the very first desktop we spawned on.
	#endregion

Next, we lay the foundation of our desktop switching class by importing GetCurrentThreadId from kernel32.dll along with our five desktop management functions (Close, Create, GetThread, SetThread, and Switch) from user32.dll. I placed these in a #region so we can collapse it all down in Visual Studio and make readability a bit easier.

#region DLL Definitions
[DllImport("user32.dll", EntryPoint = "CloseDesktop", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseDesktop(IntPtr handle);

[DllImport("user32.dll")]
private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode,
					   int dwFlags, long dwDesiredAccess, IntPtr lpsa);

[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();

[DllImport("user32.dll")]
public static extern IntPtr GetThreadDesktop(int dwThreadId);

[DllImport("user32.dll")]
public static extern bool SetThreadDesktop(IntPtr hDesktop);

[DllImport("user32.dll")]
private static extern bool SwitchDesktop(IntPtr hDesktop);
#endregion

Now we need to define our disposal methods that will clean up our virtual desktop from memory once we have closed it. Obviously , the IDisposable.Dispose() method lets .NET’s garbage collector come into play. Please note the GC.SupressFinalize(this) we used. This takes the already destroyed desktop object off the finalization queue so the GC doesn’t call the finalization code twice.

#region Disposal Methods
// Switch to the desktop we were on before.
public void Dispose() {
	SwitchToOrginal(); // Defined in the end of the article
	((IDisposable)this).Dispose();
}

// Delete our custom one.
protected virtual void Dispose(bool disposing) {
	if (disposing) {
		CloseDesktop(DesktopPtr);
	}
}

// ... flush!
void IDisposable.Dispose() {
	Dispose(true);
	GC.SuppressFinalize(this);
}
#endregion

You’re almost there! In this part, add a few more methods to the public face of our VirtualDesktop class. While the functions we imported from user32 and kernel32 alone are enough to work with in a desktop switcher, there are still a few things we have left to do, such as our LaunchDesktop() and ShowDesktop() methods that hide away the nasty API cruft so we will not have to deal with it when we use this in our actual application.

#region Methods
public IntPtr GetCurrentDesktopPtr()
{
	return GetThreadDesktop(GetCurrentThreadId());
}

private IntPtr LaunchDesktop()
{
	return CreateDesktop(_sMyDesk, IntPtr.Zero, IntPtr.Zero,
			     0, (long)DESKTOP_ACCESS_MASK.EVERYTHING, IntPtr.Zero);
}

public void ShowDesktop() {
	SetThreadDesktop(DesktopPtr);
	SwitchDesktop(DesktopPtr);
}

public void SwitchToOrginal() {
	SwitchDesktop(_hOrigDesktop);
	SetThreadDesktop(_hOrigDesktop);
}
#endregion

At last, we build our two constructors. Finally, something pretty self-explanatory.

	#region Constructors
	public VirtualDesktop()
	{
		_sMyDesk = ""
	}

	public VirtualDesktop(string sDesktopName)
	{
		_hOrigDesktop = GetCurrentDesktopPtr();
		_sMyDesk = sDesktopName;
		DesktopPtr = LaunchDesktop();
	}
	#endregion
}

And that’s it! You finished writing your own virtual desktop switching implementation. Not too hard? 😀 Now let’s test it.

Example usage

Add the VirtualDesktop.cs file to Visual Studio and enclose it in the namespace of your own project. To see the class in action, place the following proof-of-concept code under a Button.Click event on a little Windows Form and compile it. Should be pretty easy to understand and tweak!

VirtualDesktop mDesktop("My secondary desktop");
System.Threading.Thread.Sleep(1000);

mDesktop.ShowDesktop();
System.Threading.Thread.Sleep(3000);

mDesktop.SwitchToOriginal();
mDesktop.Dispose();

Future work

There is still plenty of room for expansion. The most glaring fault is seen when we create a new desktop and we switch to it: it’s empty. Whoops, where’s Windows Explorer? Since each desktop object is basically a “blank slate”, we have to launch it ourselves. For now, you can CTRL+ALT+DEL and use Task Manager to start explorer.exe, but if you want to have the VirtualDesktop class automatically launch Explorer, I would recommend reading up on how to import CreateProcess from the WinAPI, which is unfortunately out of the scope of this tutorial.

There are several other missing features to be spoken of. With this current model, programs are locked to the desktop they are spawned on, meaning we cannot transfer programs from desktop to desktop. Getting a visual thumbnail of a window or of an entire desktop object through its handle is easier said than done. Still, these tasks, while somewhat daunting, are far from impossible. This class provides adequate desktop switching capabilities that may be easily extended and tweaked to suit your needs. I’d recommend looking at either the MSDN Desktops page or PInvoke.net for reference.

This class is a simpler, tidier, and much-easier-to-read form of the virtual desktop system I had written for µShell in 2011 (WinAPI.cs). You can get the final VirtualDesktop source at GitHub by clicking the link below.

Get the source file from GitHub

Advertisements

26 thoughts on “Writing a virtual desktop system in C# with WinAPI

  1. 10/10 for this… could u please help me , i want to make a multi-user touch surface using c# having all 4 desktop screens active. dont worry about the input method, just guide me how multiple users interact with a single computer simultaneously…

    1. That is a very interesting idea, Saad! I like it, but I have no idea about its feasibility because Windows is a single-desktop system by nature, meaning only one desktop can have user input at a time… It would be possible on X11, though.

      1. Hey Ekaljuk can you prove that “Windows” is a single-desktop system by nature? Any official site would be helpful, because it is going to be my final year project and I want it to do with “Windows”….

      2. With an actual website that I can cite? No, I cannot prove it. However, I speak from what you and I have seen from all these years Windows has existed: virtual desktops, while existent in Windows, were never meant to be used for anything else besides separating the user’s desktop and the login screen. They are not implemented in an easily user-accessible way and aren’t really intended for users to control.

        From Windows 95 to Windows 8, virtual desktops are a feature restricted to the OS’s internals (which this tutorial explains how to access this “hidden component”).

        Sadly, the only operating systems with true virtual desktop support are Unix-like systems such as GNU/Linux, BSD, and Mac OS X…

        Thank you very much for all the kind words and good luck on your project! 🙂

  2. hello eyle
    kindly tell me how you started this project from C# VS.
    I have VS2010 and copied same code as given above in C# console application but that is giving errors

    Data at the root level is invalid. Line 1, position 1.’ XML is not valid and Markup file is not valid. Specify a source markup file with an .xaml extension

    please tell me how to fix these errors. this is my studies final year project and I have to submit it before april 1st.
    regards

      1. dear this is my studies project.
        if you have time and find it appropriate and not a burden. kindly tell me how to start this new project in VS2010 and how to compile and run a .cs extension file in VS2010.
        please please be kind and help me I have to do it to complete my degree. please please

    1. Yes, this code was written in VS 2008 and tested successfully on Windows XP, Vista, and 7. As for the error, it seems very peculiar, as there is no XML nor XAML anywhere in this source file. Did you copy this code straight from this blog post or did you download it from the GitHub page?

      1. thanks for reply!
        I copied it from github straight forward as it is.
        if it is not an XAML file then guide me how to fix this error should I add or remove any header file ?

  3. Dude, I would like to contribute back to you and the community. Your code helped me a lot. I buitd something upon it that might be of interest to anyone seeking this code. I created a static function that does all the wiring so that a form can be shown in a new desktop and back with a single method call!

    To use it, just do:

    VirtualDesktop.ShowFormOnNewDesktop(null);

    ///
    /// Shows a form on a new desktop and set a WaitHandler when completed. You can also call an action when done.
    ///
    /// The form type
    /// Your OnCompleted method action or delegate
    /// A WaitHandler that you can use if you need to synchronously wait for the completion of the actions in the form
    public static ManualResetEvent ShowFormOnNewDesktop(Action OnCompleted) where T : Form
    {
    ManualResetEvent completed = new ManualResetEvent(false);
    VirtualDesktop desktop = new VirtualDesktop(“teste”);
    ManualResetEvent closed = new ManualResetEvent(false);
    BackgroundWorker bw = new BackgroundWorker();
    bw.DoWork += (sender, e) =>
    {
    desktop.ShowDesktop();
    VirtualDesktop.SetThreadDesktop(desktop.DesktopPtr);

    Form f = (Form)Activator.CreateInstance();
    f.FormClosed += (sender2, e2) =>
    {
    desktop.SwitchToOriginal();
    desktop.Dispose();
    Application.DoEvents();
    closed.Set();
    };
    f.Show();
    while (!closed.WaitOne(100))
    {
    Application.DoEvents();
    }
    };
    bw.RunWorkerCompleted += (sender, e) =>
    {
    completed.Set();
    if (OnCompleted != null)
    OnCompleted.Invoke();
    };
    bw.RunWorkerAsync();
    return completed;
    }

    1. Wow, I can’t believe I missed this! Thank you for the code. Looks really handy. However, it might be easier for us to read if you post it in an external GitHub Gist or something. It’s a little hard to follow when pasted straight into the comments like that…

    1. Thank you, Dudi!

      To execute a process on the current desktop, just call CreateProcess like you would normally; see the documentation for reference.

      To execute an application on a specific desktop different from the current one, you can pass in a STARTUPINFO struct into CreateProcess with the lpDesktop parameter set. See the “Opening Processes in Desktops” section of this CodeProject article for an example.

      1. Thanks.
        Did you try to run a process on with the lpDesktop parameter?
        I always get an error that the process unable to start correctly.

        I’ve tried some CreateProcess C# implementations and I got the same error in all of them.

      2. I tried and it worked fine for me, but keep in mind this was from years ago on Windows Vista/7 and the behavior of the API may have changed since then, unfortunately.

      3. Tried it on Win7 and got the same error.
        I guess I’m doing something wrong.
        Do you have a link for a working example of CreateProcess with lpDesktop?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s