Introduction
Having a spare computer available, I decided to try out Mandrake Linux, just
to see what its like (having never used linux before). I thought its desktop
switching ability was interesting, and began wondering if it was possible to do
the same thing on my Windows box. A quick search on MSDN revealed the Desktop
API functions, which allowed me to do just that. According to the docs, this
functionality has been available since Windows 2000 Professional, and since I'm
using XP, I thought I would give it a go.
When Windows loads, and you are logged in, a desktop is automatically
created, called "default". Using the functions provided you can create, open,
and switch desktops, unfortunately, deletion isn't provided (more on that
later).
Using the Desktop class
Creating a Desktop
Creating a desktop is a very easy task, as the code example below shows.
Desktops can be created using either the instance, or the static members
provided.
Desktop desktop = new Desktop();
desktop.Create("myDesktop");
Desktop desktop = Desktop.CreateDesktop("myDesktop");
Note: Desktop names cannot contain backslash characters.
Using either of the methods will result in a Desktop
object,
with an open handle to the desktop (provided the desktop was created
successfully). New desktops have no processes running on them at all - and if
you were to switch to it, all you would see is the default wallpaper. I have
provided the Prepare
method which loads explorer to the desktop,
making it usable.
Switching Desktops
Switching desktops is just as easy as creating the desktop, all that is
required is a call to either the static method stating the desktop name, or the
instance method of an open Desktop
object.
Desktop desktop = new Desktop();
desktop.Open("myDesktop");
desktop.Show();
Desktop.Show("myDesktop");
Opening Processes in Desktops
The CreateProcess
function in kernel32.dll takes a parameter of
type STARTUPINFO
, which has a parameter ("lpDesktop") which allows
you to specify the desktop the process is to be created in. As a result, I chose
to import this function, instead of using the .NET frameworks
Process
class, meaning reduced functionality when creating process,
but I hope to resolve this in the next release. Now the code, this example shows
the two ways of creating a process.
Desktop desktop = Desktop.OpenDesktop("myDesktop");
Process p = desktop.CreateProcess("calc.exe");
Process p = Desktop.CreateProcess("calc.exe", "myDesktop");
A Process
object is returned from both
CreateProcess
methods, which can be used for killing the process,
or however you see fit.
Deleting Desktops
Deleting a desktop is a little trickier. The only way to delete a desktop is
to kill all processes running on it, at which point, it is automatically
deleted. So far, I have been unable to get a list of processes running on a
desktop other than the input desktop (nor am I certain this is possible), but
what I have done, is provided the GetInputProcesses
method, which
will return an array of all the processes running on the current input desktop
(the desktop visible to the user).
With this in mind, the only way to delete a desktop which has been accessed
by the user is to be on the desktop, enumerate the processes, and kill them
one-by-one. Making sure your application isn't killed off before it has a chance
to jump desktops (unless you want it that way).
Process[] processes = Desktop.GetInputProcesses();
Process thisProc = Process.GetCurrentProcess();
foreach(Process p in processes)
{
if (p.ProcessName != thisProc.ProcessName)
{
p.Kill();
}
}
Settings a Thread's Desktop
Threads of your process can be moved between desktops, provided they do
not have any hooks or windows in the current desktop.
Desktop desktop = Desktop.OpenDesktop("myDesktop");
Desktop.SetCurrent(desktop);
The code example above would move the calling thread into "myDesktop", but
would fail if the thread has a window or a hook.
Behind the Scenes
Desktop
's desktop switching functionality is achieved using the
following API functions (imported from user32.dll):
CreateDesktop
OpenDesktop
OpenInputDesktop
CloseDesktop
SwitchDesktop
SetThreadDesktop
GetThreadDesktop
Creating a Desktop
[DllImport("user32.dll")]
private static extern IntPtr CreateDesktop(string lpszDesktop,
IntPtr lpszDevice,
IntPtr pDevmode,
int dwFlags,
long dwDesiredAccess,
IntPtr lpsa);
The CreateDesktop
function is imported from user32.dll. It is
called when you attempt to create a new desktop, with only the desktop name
(lpszDesktop), flags (dwFlags) and desired access rights (dwDesiredAccess) being
specified.
m_desktop = CreateDesktop(name, IntPtr.Zero, IntPtr.Zero, 0,
AccessRights, IntPtr.Zero);
The return value is a handle to the desktop, or IntPtr.Zero
if
an error occurred.
Switching Desktops
[DllImport("user32.dll")]
private static extern bool SwitchDesktop(IntPtr hDesktop);
The SwitchDesktop
function is imported from user32.dll. When
switching desktops, the handle to the desktop you want to become the input
desktop is passed as the only parameter, the value returned indicates if the
switch was successful.
bool result = SwitchDesktop(m_desktop);
Opening Processes in Desktops
[DllImport("kernel32.dll")]
private static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
int dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
ref PROCESS_INFORMATION lpProcessInformation);
The CreateProcess
function is imported from kernel32.dll. When
creating a process in a desktop, this function is called with only the command
line (lpCommandLine), inherit handles (bInheritHandles), creation flags
(dwCreationFlags), startup into (lpStartupInfo) and process information
(lpProcessInformation) specified, as shown in the code below.
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = m_desktopName;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
bool result = CreateProcess(null, path, IntPtr.Zero, IntPtr.Zero, true,
NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref si, ref pi);
Specifying a PROCESS_INFORMATION
structure allows my to retrieve
the process ID of the process that was just created, allowing a managed
Process
object to be created for it.
return Process.GetProcessById(pi.dwProcessId);
Setting a Thread's Desktop
[DllImport("user32.dll")]
private static extern bool SetThreadDesktop(IntPtr hDesktop);
The SetThreadDesktop
function is imported from
user32.dll. A call to SetCurrent
would result in a call to
this function. The Desktop object passed to SetCurrent
, if open,
will contain a valid desktop handle, accessible via the
DesktopHandle
property, which is passed to
SetThreadDesktop
.
return SetThreadDesktop(desktop.DesktopHandle);
Example usage
I have not provided an example application with the source, as it is very
straight forward, and all members (except private) are XML commented. However,
if you would like to see how this could be used in an application, try my website,
where I have used it to make a small desktop switching application.
History
Version |
Comments |
1.0 |
|
1.1 06 Jun 2004 |
- Added Window and
WindowCollection classes
- Added another
GetWindows overload, that used
WindowCollection
- Added
GetInputProcesses method to retrieve processes on Input
desktop
- Changed
GetWindows and GetDesktops to return
arrays, instead of them being passed by ref. |
1.2 08 Jul 2004 |
- Implemented
IDisposable
- Implemented
ICloneable
- Overrided
ToString to return desktop name
|