Enter Sandman

Sign in to queue

Description

  Use Windows API calls in your .NET code to put your computer to sleep based on almost any application.
Arian Kulp's Blog

Difficulty: Intermediate
Time Required: 1-3 hours
Cost: Free
Software: Visual Basic or Visual C# Express Editions
Hardware: None
Download: Download

It happens more than I'd like, that I'm performing some long-running task and I'd like to go to bed (usually way too late!). It might be a large file download, transcoding a long home video file, or defragmenting my hard drive. I feel guilty leaving the computer on all night, but I don't want to stay up that extra time to shut down or standby at the end. Some software has a checkbox to “Shutdown when complete,” but many applications don't have that, and I don't necessarily want to perform a full shutdown anyway.

Once again, out of necessity, I'm writing a small utility to help with some annoyance! This application lives in the system tray and just waits on the condition that you specify, then performs the specified power management action (shuts down, goes to sleep, hibernates, etc.).

The code is included in both Visual Basic and C# and can be downloaded from the links at the top of the page. You will need Visual Studio Express editions or higher. The Express editions give you more than enough power for most coding needs and can be downloaded here.

Application Details

A little background on the title, I'm not sure how common the Sandman reference is outside of Western countries, so I'll briefly elaborate! The Sandman is a character of folklore “who brings good sleep and dreams by sprinkling magic sand on to the eyes of children” (taken from the Wikipedia entry, which has more details if you are interested).

Now that the title has been explained, the application might make more sense. I don't like leaving computers running all night, consuming power, when they aren't performing any useful function. I at least put the computer in standby when not in use during the day, but at night I sometimes kick off long-running operations then want to go to bed. Sandman is designed to monitor for a given condition to be true, then to take action. Actions are all power management functions such as Standby, Shutdown, and Hibernate.

I realized that there was no way to hook into the applications to trigger an action when it was done, but what if I monitored the window title for changes? For instance, if you copy a big file using Windows Explorer, a “Copying…” dialog will appear showing a progress gauge. If you watch for the window to disappear, you know that the copying operation is complete. Of course not all applications work that way. As you'll see, I tried to be flexible enough to handle any situation.

Figure 1 - Windows Explorer "Copying" dialog

Working with Desktop Windows

I wanted to create a user interface to allow a user to select a given window, then select a condition for that window. When the condition is true, the selected action would take place.

The first step was to obtain the list of Desktop windows. This was actually slightly more difficult than I anticipated. The API call to list windows, EnumWindows, in user32.dll, returns window handles, one at a time, to a callback function. The callback function returns true to continue with the next handle, or false to stop. The application then needs to invoke other API calls using the window handle to obtain information such as the window title or type of form/object.

Unintuitively, the concept of a window extends beyond what most people think of. A window could be system tray icons, tooltips, or actual windows. To counter that, you'll need to perform some filtering. The callback function passed to EnumWindows is used to determine if the window should be added to the returned collection or not.

Dealing with the native Win32 calls can be tedious. Part of it is just wrapping the function. This isn't much work. Typically, a single line of code (plus an attribute in C#) is all that is required to declare an external method.

Visual Basic

Private Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As EnumWindowsProc, ByVal lParam As IntPtr) As Boolean

Visual C#

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

This declaration requires an extra step because of the type-safe delegate used as the callback:

Visual Basic

Private Delegate Function EnumWindowsProc(ByVal hWnd As IntPtr, ByVal lParam As IntPtr) As Integer

Visual C#

private delegate int EnumWindowsProc(IntPtr hWnd, IntPtr lParam); 

Unmanaged code references often present a challenge if you don't work with them every day. Working with desktop windows is more than just enumerating the list of window handles (the hWnd pointer). If you want to obtain any properties such as window title, class name, child windows – really anything – you'll need to make other Win32 API calls.

I started writing the wrapper code, then decided to see if anyone had already done the heavy lifting. Good code is often about reuse! In this case, I found the excellent Managed Win API project by Michael Schierl. He not only wraps the functions, but creates the ever-so-convenient SystemWindow class to provide a completely object-oriented way of interacting with the windows as first-class objects.

Michael exposes the FilterTopLevelWindows method that accepts an optional predicate to use in conjunction with the callback function for filtering the list. If you haven't worked with predicates yet, you're in for a treat! They work to allow you to essentially plug your code right into another method. Instead of obtaining a list of top-level windows, then filtering them, I provide my FilterWindows method and it only returns the relevant objects. Predicates are also used with collections for operations such as Find, Exists, and RemoveAll.

The predicate model is great for a few reasons. First of all, the code looks cleaner, second, there is a performance benefit since I receive the collection pre-filtered: no need to create one complete collection, then remove elements. Finally, there's the benefit of being more declarative. I have a strong preference for code that is very expressive and easy to follow. Passing a predicate makes it immediately obvious what is happening.

Subclassing the ComboBox

In order to display the list of windows, I could have dragged a ComboBox to the form, then worked with it directly. Instead, I decided to create a subclass. This encapsulates functionality related to displaying window information directly in the ComboBox, and leaves the main form's code less cluttered. When created, the constructor for this new control adds an event handler for the Format event. This event, if declared, is called for each item in the collection of items instead of the default ToString() method (which often just returns the fully-qualified class name!).

If you aren't aware of the Format event, you might have created a separate collection of strings to bind to a ComboBox or ListBox control. This is a pain when it comes to linking back to the original item when a user makes a selection. Using Format allows you to bind any collection of items, and then perform per-item formatting at display time. In this case, I wanted to stick with the collection of SystemWindow objects, but provide a better string to display. Here is the body of my Format event handler:

Visual Basic

Dim win As SystemWindow = CType(e.ListItem, SystemWindow)
Dim s As String = String.Format("[{0:00000000}] {1}", win.HWnd, win.Title)
e.Value = s

Visual C#

SystemWindow win = ((SystemWindow)e.ListItem); 
string s = string.Format("[{0:00000000}] {1}", win.HWnd, win.Title); 
e.Value = s; 

Notice that the ListItem property contains the underlying bound item. This gives you access to all of the properties – a necessity when building up the display string. In order to get the list of window objects, I expose a public method, RefreshWindowTitles in the custom control:

Visual Basic

Me.DataSource = SystemWindow.FilterToplevelWindows(AddressOf FilterWindows)

Visual C#

this.DataSource = SystemWindow.FilterToplevelWindows(FilterWindows); 

The FilterWindows method, as mentioned, is a predicate. It's actually just like any other method, and it's passed as a delegate would be, but the FilterTopLevelWindows method invokes it for each item that it considers adding to the collection. The implementation just returns true or false, but the FilterTopLevelWindows method uses it to decide which windows to return. The condition simply checks for visible windows with a title, and skips the Task Manager (ClassName = “Progman”) and the Sandman Options window itself:

Visual Basic

Private Function FilterWindows(ByVal win As SystemWindow) As Boolean
  If win.Visible And (win.HWnd <> Me.TopLevelControl.Handle) And _
    (win.Title <> "" And win.ClassName <> "Progman") Then
    Return True
  Else
    Return False
  End If
End Function

Visual C#

private static bool FilterWindows(SystemWindow win) 
{ 
  if (win.Visible && win.HWnd != this.TopLevelControl.Handle && 
      win.Title != "" && win.ClassName != "Progman") 
  { 
    return true; 
  } 
  else return false;
} 

Using the list of windows, the user can make a selection from the ComboBox, then select the condition to apply. The condition can be a window appearing or disappearing, the title changing, or the title specifically containing or not containing a given string. This provides for most possibilities. In this image, the user has selected the Windows Explorer “Copying” dialog. When it disappears (meaning that copy is complete), the system will suspend:

Figure 2 - Setting window-based options

There are actually five conditions based on windows:

Condition Description
WINDOW_APPEARS Waits for a window appears with the given name
WINDOW_DISAPPEARS Waits for a selected window to disappear
TITLE_CONTAINS Waits for the selected window's title to contain the given title
TITLE_DOES_NOT_CONTAIN Waits for the selected window's title to no longer contain the given title
TITLE_CHANGES Waits for any change in the selected window's title

Other Conditions

Just to make the application more expandable, I introduced the concept of a condition based on an interface – ICondition. My first pass at the application was all about basing actions on windows and their title. Just for fun, I factored out what it meant to be a condition. There are two methods to implement, two properties, and an event:

Visual Basic

Public Interface ICondition
  Event ConditionOccurred As EventHandler
  Sub StopMonitoring()
  Sub StartMonitoring()

  ReadOnly Property VisualControl() As UserControl
  ReadOnly Property Description() As String
End Interface

Visual C#

interface ICondition 
{ 
  event EventHandler ConditionOccured; 
  void StopMonitoring(); 
  void StartMonitoring(); 
  UserControl VisualControl { get; } 
  string Description { get; } 
} 

The VisualControl property points to a UserControl used to display a user interface for configuring this condition. For the window condition, it includes the drop-down lists, the text field, and the Refresh button. This property is read-only.

Figure 3 - WindowBasedCondition UserControl

The Description property is the plain-text name for the condition, used to display in the Condition drop-down list. This property is also read-only.

The StartMonitoring and StopMonitoring methods are used to enable and disable monitoring for this condition. Note that there is no technical reason why more than one condition could not be active at one.

When the condition is true, its ConditionOccurred event will fire allowing the application to take action. The action taken is to display a tooltip warning of the impending action. Ten seconds later, if the tooltip is not clicked, the action will take place.

Figure 4 - Ten-second warning

I also created two other simple conditions: one waits until a specific time, like an alarm clock. The other one waits for an elapsed amount of time, like a timer. Both do essentially the same thing, though it might be easier to think in terms of elapsed time rather than calculating a given time.

Taking Action

Once a condition is evaluated to true and the ConditionOccurred event fires, the application is ready to invoke some action. As written, all actions are based on power management. This includes logoff, suspend, hibernate, shutdown, power off and restart. There are Win32 calls to perform these actions, and some are even possible in managed code. As with the window listing before, though, I decided to see what was available for managed power management support. I ended up discovering the WindowsController project by Mentalis.org. Very slick! They also offer some other managed code wrappers. It's free and very nicely done. This performs any necessary work to check for access privileges (some power management is only available to administrators). A single call does it all!

As I finished the application, I realized that it could make a nice framework for other actions. Imagine watching for a file to appear, then sending an email, or waiting for an operation to complete then play a sound. It's a way of augmenting other applications' functionality without requiring access to code.

Next Steps

At this point, I'm pretty happy with the utility. It does what I want it to do, but I can see room for more features. One nice thing would be command line options. You could create a batch file to start some long-running process, and launch Sandman to put the computer to sleep at the end.

You could also make the application more plugin-friendly. The ICondition interface makes it easy to extend what conditions it watches for, but it currently requires a recompile. Maybe scan a plugins folder and dynamically load the discovered types at runtime.

Speaking of condition plugins, you could create additional conditions such as watching for a certain file to appear or be modified, or write some totally off-the-wall condition like waiting for a remote service to return some value. The first time you wish it had that one extra feature, spend a little time and try to add it! Hopefully there is enough here to get a great head start.

Conclusion

Being more in control of your computer is what hobbyist programming is all about. Think about what you need and then try to code it. It can be great practice, and who knows, you may come up with a great application for others. Download Visual Studio Express, and get started today!


Arian Kulp is an independent software developer and writer working in the Midwest.  He has been coding since the fifth grade on various platforms, and also enjoys photography, nature, and spending time with his family.  Arian can be reached through his web site at http://www.ariankulp.com.

Tags:

utility, Windows

The Discussion

  • User profile image
    Jóhann P. Kulp

    This is just as good as the Metallica song.

    Enter Sandman that is.

Add Your 2 Cents