| File: System\Windows\Input\Stylus\Pointer\PointerTabletDeviceInfo.cs | Web Access |
| Project: src\wpf\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. 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 } }