Introduction
Have you ever ran into the situation where you can't remember what you did a few hours ago and you need to create your time sheet entries for the day? The window titles of the various applications running on your windows desktop often give great hints and reminders on what you have been doing. This console application uses a few PInvoke calls to periodically log information on the currently active window to the console.
Background
At time cockpit, we have an application that tracks hints (something we refer to as a signal) from various sources, such as the changed files, the available wireless networks, active windows, when user input happened, etc. When the time comes that you need a little reminder, the application presents this information in a calendar. Of course all of this is encrypted to ensure the privacy of the user. The active window title tracker is the one that most of the time saves my neck so I thought we should share that code along with a sample application in case you wanted to do something similar.
Using the code
Most of the code in the sample deals with retrieving the current foreground window using GetForegroundWindow
and its associated process. Using PInvoke, it is easy to call this Win32 method using the following snippet (the other functions will be explained below):
private class NativeMethods
{
DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr windowHandle, out int processId);
[DllImport("user32.dll")]
public static extern int GetWindowText(int hWnd, StringBuilder text, int count);
}
While those attributes seem cryptic they are easy to find by browsing the pinvoke.net website for the API calls you want to call.
The first thing the periodic WriteCurrentWindowInformation()
function does is to retrieve the handle to the current window title and will only continue if the active id is not a null handle:
var activeWindowId = NativeMethods.GetForegroundWindow();
if (activeWindowId.Equals(0))
{
return;
}
Since in some cases the window title might be empty (some java processes, in particular) we then retrieve the processId that the window handle belongs to using GetWindowThreadProcessId()
. We also check this against 0 to assert a valid handle:
int processId;
NativeMethods.GetWindowThreadProcessId(activeWindowId, out processId);
if (processId == 0)
{
return;
}
Using the id of the process, we can use the Process.GetProcessById()
function to retrieve a Process structure. This structure reveals information such as the name of the process, possibly a file description in the executable or a product name that the window belongs to.
In the code sample, there are multiple try / catch blocks wrapping accesses to the process information to protect against security exceptions, since admin processes may not allow retrieving such information. Finally, the last two blocks retrieve the window title: If the window title of the Process is empty, we use the
GetWindowText
method to retrieve the window title by passing in the window handle of the main window (the surrounding try / catch blocks are omitted for brevity here):
if (!string.IsNullOrEmpty(foregroundProcess.MainWindowTitle))
{
windowTitle = foregroundProcess.MainWindowTitle;
}
if (string.IsNullOrEmpty(windowTitle))
{
const int Count = 1024;
var sb = new StringBuilder(Count);
NativeMethods.GetWindowText((int)activeWindowId, sb, Count);
windowTitle = sb.ToString();
}
We have found that for some processes, the MainWindowTitle
sometimes remains empty
Something to point out is the use of a
StringBuilder
as a buffer for a return value of a native call. A detailed discussion on PInvoke parameter marshaling can be found in this article.
Last but not least, the main loop driving the example periodically polls the WriteCurrentWindowInformation()
function using a simple call to <a title="Thread.Sleep MSDN documentation" href="http://msdn.microsoft.com/de-de/library/vstudio/d00bd51t.aspx">Thread.Sleep()</a>.
Points of Interest
The polling nature of the sample will probably scare some of you off and I agree that it would be nicer to be notified if the current foreground window changes. The approach has been shown here but there are multiple downsides to this: If an application changes the window title, no notification would occur. This happens quite often when files are saved or, e.g. if a different solution is opened from within Visual Studio.
Another interesting point is that the code provided also delivers a meaningful title for Windows Store apps which were introduced with
Windows 8.
While the window / application title is quite useful, at time cockpit we are exploring ways of using UI Automation to find a more specific title as this would allow introspecting the complete control tree, therefore making it possible to retrieve the title of the tab for currently focused document in
Visual Studio.
History
2012-10-01: Updated points of interest to point to a callback solution.
2012-10-01: First release of this article. The code presented here has been in production use by the time cockpit's signal trackers for over 3 years now.