[Dev Tip] WRITING A VIRTUAL DESKTOP SYSTEM IN C# WITH WINAPI


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 (seefig. 1).

Virtual desktops on Windows (Sysinternals Desktops 2.0)

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 theIDisposable interface so we can access the garbage collector and tell it to flush each desktop object from memory when we delete it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 importingGetCurrentThreadId 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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 ourVirtualDesktop 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    #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!

1
2
3
4
5
6
7
8
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 VirtualDesktopsource at GitHub by clicking the link below.

Get the source file from GitHub

Advertisements

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