BgInfo – WPF Style

August 28, 2016

2 comments

The well-known BgInfo
Sysinternals tool can be used to display on the desktop a configurable set of information items regarding the system, such as physical memory size, CPU type, machine name, domain, volumes, network information and much more.

BgInfo writes “its thing” on the desktop by replacing the wallpaper with a custom one that can be simply a layering over the user selected one, or configured with some color or image, etc. Once BgInfo sets the wallpaper, it exits.

Just for fun, I wanted to create a similar tool, but take a different approach. Changing the wallpaper is a cool approach, has zero overhead, but it has its drawbacks. The main one is that since nothing is running, it’s not easy to update dynamic information, such as disk free space, memory consumption and even the number of processes in the system.

My BgInfo copy is using a transparent WPF window that is sent way back in the Z-order, in order to appear on the desktop, but of course it’s not. Maintaining that illusion (of being part of the desktop) is not easy and works in most cases (as we’ll soon see). Here’s a snapshot of my BgInfo displayed over my desktop background at the time of writing (missing items at this time as compared to the original BgInfo, but some new dynamic items):

I even added a timer that every interval (configurable, defaults to 1 minute) refreshes the items that may change (processes, threads, committed memory, etc.).

The project can found on Github. Let’s look at some of the interesting points.

Multiple Monitors

I wanted the “window” to appear on multiple monitors if such are connected to the system. Each may even be using a different resolution that should be reflected on each monitor’s display. So the first thing to do is use the EnumDisplayMonitors API to enumerate the monitors and create an instance of the special window for each monitor. For each monitor (returned as an HMONITOR), we need to get its bounds, excluding the task bar and other application bars – all provided with the GetMonitorInfo API given an HMONITOR.

The code to create the windows looks like this (C# P/Invoke, of course):

        public int CreateWindows()
        {
            var windows = 0;

            EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, 
(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT rect, IntPtr data) => {

                var info = new MonitorInfo();
                info.Init();
                GetMonitorInfo(hMonitor, ref info);

                var vm = new BgViewModel(info, Settings);
                var win = new BgView
                {
                    Left = info.rcWork.Left,
                    Top = info.rcWork.Top,
                    Width = info.rcWork.Width,
                    Height = info.rcWork.Height,
                    DataContext = vm
                };
                _screens.Add(vm);

                win.Show();
                windows++;
                return true;
            }, IntPtr.Zero);

            return windows;
        }

BgView is the class that derives from Window. The rcWork member of MonitorInfo (MONITOR_INFO API structure) provides the “work area” that excludes the Taskbar and other AppBars. The Top and Left properties of the Window are important, and they are not zero, because for a second (and third…) monitor they would have the virtual coordinates where that monitor begins and that makes the WPF window appear in the correct monitor.

The WPF Window

The window where all the text is displayed should be transparent naturally, but it should also be like a true ghost, everything should move through as if the text is part of the desktop. There are a few steps necessary to make it (almost) work.

First, WPF should get a hint that this “ghosting” is required with a few properties:

        AllowsTransparency="True" WindowStyle="None" Background="{x:Null}" IsHitTestVisible="False" ShowInTaskbar="False"
																			

 

 

This says that the window should be transparent, no window style implies no border and no caption. The background is non existent, it should not raise UI events (IsHitTestVisible) and certainly should not be on the Taskbar. Looks easy enough…

There are a few hiccups, however. Even though the taskbar does not show the window, Alt+Tab still insists in showing an empty rectangle. To solve this, we need to indicate to Windows that this window will never be activated, so Alt+Tab has no meaning for it. WPF does not provide a direct way to do it (AFAIK), so we’ll need to go the Windows API for this:

var handle = new WindowInteropHelper(this).Handle;

 

SetWindowLong

(handle, GWL_EXSTYLE, GetWindowLong(handle, GWL_EXSTYLE) | WS_EX_NOACTIVATE);

 

Adding the extended style WS_EX_NOACTIVATE does the trick.

Next, we want the window to be at the bottom of the Z-order – always. Unfortunately, new windows come and go and this may change. So, we need to hook into the WM_WINDOWPOSCHANGING message and place the window at the bottom again.

First, when created, we place it at the bottom of the Z-order with another API P/invoke call:

SetWindowPos

(handle, new IntPtr(HWND_BOTTOM), 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);

 

Now we need to add a message handler with can be done in WPF by adding a “hook” delegate to the HwndSource driving our window:

var wndSource = HwndSource.FromHwnd(handle);

wndSource.AddHook(WindowProc);

 

WindowProc is our method, which goes like this:

private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {

    if(msg == WM_WINDOWPOSCHANGING) {

        var windowPos = Marshal.PtrToStructure<WindowPos>(lParam);

        windowPos.hwndInsertAfter = new IntPtr(HWND_BOTTOM);
    windowPos.flags &= ~(uint)SWP_NOZORDER;

 

        handled = true;

    }

    return IntPtr.Zero;

}

 

It just makes sure the window stays at the bottom of the Z-order.

Single Instancing

It wouldn’t be any reason for more than one instance of BgInfo running, and we can use the well-known technique to prevent it through a simple mutex:

bool createNew;

_oneInstanceMutex = new Mutex(false"BgInfo_OneInstanceMutex"out createNew);

if(!createNew) {

    Shutdown();

    return;

}

If the mutex exists, we simply exit. When the current instance exits, the mutex handle will close and a new BgInfo instance would be able to launch.

The Data itself

Getting the data itself is a mixture of .NET methods and P/invoke calls. Some information is readily provided by .NET, such as by the System.Environment class. But most information is only available with the Windows API, or in some cases, it’s faster to get with the Windows API, but is also possible to obtain with .NET. The BgViewModel is the class responsible for getting the various information items.

Customizations

The tool currently supports changing the font, color and refresh interval with a notification icon in the tray.

The source code is here, and a compiled version can be downloaded from here.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

2 comments

  1. LidanAugust 28, 2016 ב 22:19

    Cool!!

    What about winkey+d that minimizes all Windows? Can you handle that?

    Reply
  2. Josef HahnlAugust 28, 2016 ב 22:42

    Great job!

    Windows logo key + D will remove BgInfo while Windows logo key + M BgInfo is still displayed

    Reply