Platform detection III: How to detect a touch screen on Windows CE in .NET CF

Pocket PC's have touch screens.  Smartphones don't.  While it is straightforward to determine which of these a Windows Mobile device is, there are Windows CE devices that are neither.  Some may offer touch screens while others don't.  Since a touch screen is really what you may be after (to determine whether you can expect a user to click somewhere on the screen, for example), the better long-term solution would be to check for the touch screen itself.  We can't assume that a future version of Windows Mobile won't change the distinctions that currently exist.  Here I will discuss ways you can do your own detection.

The .NET Compact Framework doesn't offer a way to easily check for the presence of a touch screen.  I worked on adding this feature to NetCF Orcas, but we had to abandon the project after it was very nearly there because we found that some manufacturers did not implement the proper detection APIs, which gave us erroneous results on some hardware.  Instead, I'll show you how we were going to do it, and you should be well on your way.

Strategy

Any Windows CE or Windows Mobile device with a touch screen will have a driver whose filename will be in the registry under the HKLM\Hardware\DeviceMap\Touch key, value "DriverName".  If this value exists in the registry, that is your first clue that a touch screen is present.  Even if the value doesn't exist, the default driver name of touch.dll may still be present and there may be a touch screen.  Then all that's left is check to see if the driver is in \Windows.

And a caveat: A bug in the Windows Mobile Smartphone 2005 emulator leaves a touch screen driver present even though there is no touch screen.  As a result, your detection function should have a special check for Smartphone 2005 and below and return false in that case, regardless of whether a touch screen driver is present.

A C# implementation

This post builds on the first and second posts in the Platform Detection series.  You'll need to build on the code from those posts for this post to compile and run.  Like the previous posts, this code uses partial classes, so you can copy and paste the previous  code into one file and this code into another (removing the prior posts' Main methods) and your project will compile and run.

 using System;
using System.IO;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Text;

namespace PlatformDetection
{
    internal partial class PInvoke
    {
        const int MAX_PATH = 260;
        [DllImport("Coredll.dll")]
        static extern int SHGetSpecialFolderPath(IntPtr hwndOwner, StringBuilder lpszPath, int nFolder, int fCreate);

        public enum SpecialFolders : int
        {
            CSIDL_WINDOWS = 0x0024,
        }
        public static string GetSpecialFolder(SpecialFolders specialFolder)
        {
            StringBuilder path = new StringBuilder(MAX_PATH);
            if (SHGetSpecialFolderPath(IntPtr.Zero, path, (int)specialFolder, 0) == 0)
                throw new Exception("Error getting Windows path.");
            return path.ToString();
        }
    }

    internal partial class PlatformDetection
    {
        public static bool IsTouchScreen()
        {
            string driverFileName = Registry.GetValue(@"HKEY_LOCAL_MACHINE\Hardware\DeviceMap\Touch",
                "DriverName", "touch.dll").ToString();
            string windowsFolder = PInvoke.GetSpecialFolder(PInvoke.SpecialFolders.CSIDL_WINDOWS);
            string driverPath = Path.Combine(windowsFolder, driverFileName);
            bool driverExists = File.Exists(driverPath);

            return
                driverExists &&
                // Windows Mobile 5.0 Smartphone emulator and earlier has a driver, but no touch screen.
                !(IsSmartphone() && IsEmulator() && Environment.OSVersion.Version.Major < 6);
        }
    }

    class TouchscreenProgram
    {
        static void Main(string[] args)
        {
            MessageBox.Show("Touchscreen: " + (PlatformDetection.IsTouchScreen() ? "Yes" : "No"));
        }
    }
}

A C++ non-implementation

I won't actually include the C equivalent of the above code, but be forewarned if you do it yourself: For the C implementation I thought it would be a good idea to actually LoadLibrary("touch.dll") and query the driver to check whether the driver is valid and running.  In my tests, the later call to FreeLibrary would crash my process.  Watching the library reference counter revealed that my LoadLibrary incremented the counter from 1 to 2, and FreeLibrary returned it to 1.  But for some reason some touch screen drivers don't like having FreeLibrary called when they aren't actually being unloaded.  Don't load the library as part of your check, or you may find your app crashing on some devices.

This is the last post in a series of three on platform detection.  This post builds on the previous two: discerning Smartphone and Pocket PC, and detecting the Microsoft Device Emulator.