File: System\Windows\Input\Stylus\Pointer\PointerTabletDeviceInfo.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
using MS.Win32.Pointer;
using System.Collections.ObjectModel;
 
namespace System.Windows.Input.StylusPointer
{
    /// <summary>
    /// WM_POINTER specific information about a TabletDevice
    /// </summary>
    internal class PointerTabletDeviceInfo : TabletDeviceInfo
    {
        #region Member Variables
 
        /// <summary>
        /// Store the WM_POINTER device information directly
        /// </summary>
        private UnsafeNativeMethods.POINTER_DEVICE_INFO _deviceInfo;
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// The pointer properties supported by this TabletDevice
        /// </summary>
        internal UnsafeNativeMethods.POINTER_DEVICE_PROPERTY[] SupportedPointerProperties { get; private set; }
 
        /// <summary>
        /// The index of the start of button properties in SupportedPointerProperties.
        /// If there are no buttons, this will be -1.
        /// </summary>
        internal int SupportedButtonPropertyIndex { get; private set; }
 
        /// <summary>
        /// The buttons supported by this tablet
        /// </summary>
        internal StylusButtonCollection StylusButtons { get; private set; }
 
        /// <summary>
        /// The specific id for this TabletDevice
        /// </summary>
        internal IntPtr Device { get { return _deviceInfo.device; } }
 
        /// <summary>
        /// Indicates if the hardware did not specify pressure but we have inserted it anyway.
        /// </summary>
        internal bool UsingFakePressure { get; private set; } = false;
 
        /// <summary>
        /// The rectangle bounds for the entire device
        /// </summary>
        internal UnsafeNativeMethods.RECT DeviceRect { get; private set; } = new UnsafeNativeMethods.RECT();
 
        /// <summary>
        /// The rectangle bounds for the display associated with the device
        /// </summary>
        internal UnsafeNativeMethods.RECT DisplayRect { get; private set; } = new UnsafeNativeMethods.RECT();
 
        #endregion
 
        #region Constructor/Initialization
 
        /// <summary>
        /// Constructor to convert WM_POINTER stack device info to the more generic TabletDeviceInfo
        /// </summary>
        /// <param name="deviceInfo">The WM_POINTER device info</param>
        internal PointerTabletDeviceInfo(int id, UnsafeNativeMethods.POINTER_DEVICE_INFO deviceInfo)
        {
            _deviceInfo = deviceInfo;
 
            Id = id;
            Name = _deviceInfo.productString;
            PlugAndPlayId = _deviceInfo.productString;
        }
 
        /// <summary>
        /// Initializes the device information for this device
        /// </summary>
        /// <returns>
        /// True if initialization succeeds, false otherwise.
        /// </returns>
        internal bool TryInitialize()
        {
            bool success = true;
 
            InitializeDeviceType();
            
            success = TryInitializeSupportedStylusPointProperties();
 
            // If we fail to initialize properties we may or may not get a failure in
            // the win32 call in TryInitializeDeviceRects.  If we don't fail, we will crash
            // so add a guard here as well.
            if (success)
            {
                success = TryInitializeDeviceRects();
            }
 
            return success;
        }
 
        /// <summary>
        /// Convert from WM_POINTER device types to WPF equivalents and set appropriate hardware caps.
        /// </summary>
        /// <remarks>
        /// Surprisingly enough, WISP only supports Integreated and HardProximity 
        /// See Stylus\Biblio.txt - 3
        /// As such we mirror their support here.
        /// </remarks>
        private void InitializeDeviceType()
        {
            switch (_deviceInfo.pointerDeviceType)
            {
                case UnsafeNativeMethods.POINTER_DEVICE_TYPE.POINTER_DEVICE_TYPE_EXTERNAL_PEN:
                    {
                        DeviceType = TabletDeviceType.Stylus;
                    }
                    break;
                case UnsafeNativeMethods.POINTER_DEVICE_TYPE.POINTER_DEVICE_TYPE_INTEGRATED_PEN:
                    {
                        DeviceType = TabletDeviceType.Stylus;
                        HardwareCapabilities |= TabletHardwareCapabilities.Integrated;
                    }
                    break;
                case UnsafeNativeMethods.POINTER_DEVICE_TYPE.POINTER_DEVICE_TYPE_TOUCH:
                    {
                        DeviceType = TabletDeviceType.Touch;
                        HardwareCapabilities |= TabletHardwareCapabilities.Integrated;
                    }
                    break;
                case UnsafeNativeMethods.POINTER_DEVICE_TYPE.POINTER_DEVICE_TYPE_TOUCH_PAD:
                    {
                        DeviceType = TabletDeviceType.Touch;
                    }
                    break;
            }
 
            HardwareCapabilities |= TabletHardwareCapabilities.HardProximity;
        }
 
        /// <summary>
        /// Query all supported properties from WM_POINTER stack and convert to WPF equivalents.
        /// 
        /// This maintains a set of properties from WM_POINTER that are supported and the equivalent
        /// properties in WPF.  This way, the WM_POINTER properties can be used to directly query
        /// raw data from the stack and associate it 1 to 1 with raw stylus data in WPF.
        /// </summary>
        /// <returns>
        /// True if initialization succeeds, false otherwise.
        /// </returns>
        /// <remarks>
        /// A note on the pressure property.  If the device does not support pressure as a property,
        /// we still insert pressure on the WPF side.  This is a requirement in many places in the
        /// stylus code.  However, the raw data queried from WM_POINTER will NOT have pressure data.
        /// This is ok because StylusPointCollection pivots on the ContainsTruePressure property in
        /// the StylusPointDescription.  If this is true, every point will have X, Y, Pressure and then
        /// additional raw data from index 3..N where N is the number of properties per StylusPoint.
        /// If this is false, it will add a default pressure so WPF ends up with X, Y, DefaultPressure
        /// and then 2..N per StylusPoint.  This can be slightly confusing at first glance, since pressure
        /// of one kind or another is always required in the StylusPointDescription, but it allows for
        /// backfilling of pressure data further up (as in towards the public boundary) and the deeper
        /// portions of the stack just worry about querying device capabilities.
        /// </remarks>
        private bool TryInitializeSupportedStylusPointProperties()
        {
            bool success = false;
 
            uint propCount = 0;
 
            // Initialize to having not seen a real pressure property
            PressureIndex = -1;
            UsingFakePressure = true;
 
            // Retrieve all properties from the WM_POINTER stack
            success = UnsafeNativeMethods.GetPointerDeviceProperties(Device, ref propCount, null);
 
            if (success)
            {
                SupportedPointerProperties = new UnsafeNativeMethods.POINTER_DEVICE_PROPERTY[propCount];
 
                success = UnsafeNativeMethods.GetPointerDeviceProperties(Device, ref propCount, SupportedPointerProperties);
 
                if (success)
                {
                    // Prepare a location for X, Y, and Pressure
                    List<StylusPointProperty> properties = new List<StylusPointProperty>()
                    {
                        StylusPointPropertyInfoDefaults.X,
                        StylusPointPropertyInfoDefaults.Y,
                        StylusPointPropertyInfoDefaults.NormalPressure,
                    };
 
                    List<StylusPointProperty> buttonProperties = new List<StylusPointProperty>();
 
                    // Prepare a location for X and Y.  Pressure does not need a location as it will be added later if applicable.
                    List<UnsafeNativeMethods.POINTER_DEVICE_PROPERTY> supportedProperties = new List<UnsafeNativeMethods.POINTER_DEVICE_PROPERTY>()
                    {
                        new UnsafeNativeMethods.POINTER_DEVICE_PROPERTY(),
                        new UnsafeNativeMethods.POINTER_DEVICE_PROPERTY(),
                    };
 
                    List<UnsafeNativeMethods.POINTER_DEVICE_PROPERTY> supportedButtonProperties = new List<UnsafeNativeMethods.POINTER_DEVICE_PROPERTY>();
 
                    bool seenX = false, seenY = false, seenPressure = false;
 
                    foreach (var prop in SupportedPointerProperties)
                    {
                        StylusPointPropertyInfo propInfo = PointerStylusPointPropertyInfoHelper.CreatePropertyInfo(prop);
 
                        if (propInfo != null)
                        {
                            // If seeing a required property, just overwrite the default placeholder
                            // otherwise tack it onto the end of the appropriate list.
                            if (propInfo.Id == StylusPointPropertyIds.NormalPressure)
                            {
                                seenPressure = true;
                                properties[StylusPointDescription.RequiredPressureIndex] = propInfo;
 
                                // Pressure is not in the pointer properties by default so we must insert it.
                                supportedProperties.Insert(StylusPointDescription.RequiredPressureIndex, prop);
                            }
                            else if (propInfo.Id == StylusPointPropertyIds.X)
                            {
                                seenX = true;
                                properties[StylusPointDescription.RequiredXIndex] = propInfo;
                                supportedProperties[StylusPointDescription.RequiredXIndex] = prop;
                            }
                            else if (propInfo.Id == StylusPointPropertyIds.Y)
                            {
                                seenY = true;
                                properties[StylusPointDescription.RequiredYIndex] = propInfo;
                                supportedProperties[StylusPointDescription.RequiredYIndex] = prop;
                            }
                            else if (propInfo.IsButton)
                            {
                                buttonProperties.Add(propInfo);
                                supportedButtonProperties.Add(prop);
                            }
                            else
                            {
                                properties.Add(propInfo);
                                supportedProperties.Add(prop);
                            }
                        }
                    }
 
                    // If we saw a real pressure property, we should mark that down
                    if (seenPressure)
                    {
                        PressureIndex = StylusPointDescription.RequiredPressureIndex;
                        UsingFakePressure = false;
                        HardwareCapabilities |= TabletHardwareCapabilities.SupportsPressure;
                    }
 
                    Debug.Assert(properties[StylusPointDescription.RequiredXIndex /*0*/].Id == StylusPointPropertyIds.X || !seenX,
                        "X isn't where we expect it! Fix pointer stack to ask for X at index 0");
                    Debug.Assert(properties[StylusPointDescription.RequiredYIndex /*1*/].Id == StylusPointPropertyIds.Y || !seenY,
                        "Y isn't where we expect it! Fix pointer stack to ask for Y at index 1");
                    Debug.Assert(properties[StylusPointDescription.RequiredPressureIndex /*1*/].Id == StylusPointPropertyIds.NormalPressure /*2*/,
                        "Fix pointer stack to ask for NormalPressure at index 2!");
 
                    // Append buttons to the end of normal properties
                    properties.AddRange(buttonProperties);
 
                    SupportedButtonPropertyIndex = supportedProperties.Count;
                    supportedProperties.AddRange(supportedButtonProperties);
 
                    // Reset the properties to only what we support, this way we can generate raw data directly from them
                    StylusPointProperties = new ReadOnlyCollection<StylusPointProperty>(properties);
                    SupportedPointerProperties = supportedProperties.ToArray();
                }
            }
 
            return success;
        }
 
        /// <summary>
        /// Queries WM_POINTER device/screen rectangles
        /// </summary>
        /// <returns>
        /// True if initialization succeeds, false otherwise.
        /// </returns>
        private bool TryInitializeDeviceRects()
        {
            bool success = false;
 
            var deviceRect = new UnsafeNativeMethods.RECT();
            var displayRect = new UnsafeNativeMethods.RECT();
 
            success = UnsafeNativeMethods.GetPointerDeviceRects(_deviceInfo.device, ref deviceRect, ref displayRect);
 
            if (success)
            {
                DeviceRect = deviceRect;
 
                DisplayRect = displayRect;
 
                // We use the max X and Y properties here as this is more readily useful for raw data
                // which is where all conversions come from.
                SizeInfo = new TabletDeviceSizeInfo(
                    new Size(SupportedPointerProperties[StylusPointDescription.RequiredXIndex].logicalMax,
                    SupportedPointerProperties[StylusPointDescription.RequiredYIndex].logicalMax),
                    new Size(displayRect.right - displayRect.left, displayRect.bottom - displayRect.top));
            }
 
            return success;
        }
 
        #endregion
    }
}