File: System\Windows\Input\Stylus\Pointer\PointerStylusDevice.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.Internal;
using MS.Win32.Pointer;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Interop;
using System.Windows.Media;
 
namespace System.Windows.Input.StylusPointer
{
    /// <summary>
    /// A WM_POINTER specific implementation of the StylusDeviceBase.
    /// 
    /// Supports direct access to WM_POINTER structures and basing behavior off of the WM_POINTER data.
    /// </summary>
    internal class PointerStylusDevice : StylusDeviceBase
    {
        #region Member Variables
 
        /// <summary>
        /// The current taps tracked by this StylusDevice
        /// </summary>
        private int _tapCount = 1;
 
        /// <summary>
        /// The buttons owned by this StylusDevice
        /// </summary>
        private StylusButtonCollection _stylusButtons;
 
        /// <summary>
        /// The interaction engine to feed with input data
        /// </summary>
        private PointerInteractionEngine _interactionEngine;
 
        /// <summary>
        /// The currently captures plugin collection
        /// </summary>
        private StylusPlugInCollection _stylusCapturePlugInCollection;
 
        /// <summary>
        /// A reference to the main logic for the pointer stack
        /// </summary>
        private PointerLogic _pointerLogic;
 
        /// <summary>
        /// The currently captured element
        /// </summary>
        private IInputElement _stylusCapture;
 
        /// <summary>
        /// What sort of capture is currently occuring
        /// </summary>
        private CaptureMode _captureMode = CaptureMode.None;
 
        /// <summary>
        /// The element this device is currently over
        /// </summary>
        private IInputElement _stylusOver;
 
        /// <summary>
        /// The raw position relative to the stylus over
        /// </summary>
        private Point _rawElementRelativePosition = new Point(0, 0);
 
        /// <summary>
        /// The PresentationSource that the latest input for this device is associated with
        /// </summary>
        private PresentationSource _inputSource;
 
        /// <summary>
        /// The time (in ticks) when the last event occurred
        /// </summary>
        private int _lastEventTimeTicks = 0;
 
        /// <summary>
        /// The current pointer data
        /// </summary>
        private PointerData _pointerData;
 
        /// <summary>
        /// The cursor info associated with this StylusDevice
        /// </summary>
        private UnsafeNativeMethods.POINTER_DEVICE_CURSOR_INFO _cursorInfo = new UnsafeNativeMethods.POINTER_DEVICE_CURSOR_INFO();
 
        /// <summary>
        /// The TabletDevice that owns this
        /// </summary>
        private PointerTabletDevice _tabletDevice;
 
        /// <summary>
        /// The current set of stylus points associated with this
        /// </summary>
        private StylusPointCollection _currentStylusPoints;
 
        #endregion
 
        #region Constructor
 
        /// <summary>
        /// Creates a new PointerStylusDevice
        /// </summary>
        /// <param name="tabletDevice">The TabletDevice that owns this</param>
        /// <param name="cursorInfo">The cursor info for this stylus device</param>
        internal PointerStylusDevice(PointerTabletDevice tabletDevice, UnsafeNativeMethods.POINTER_DEVICE_CURSOR_INFO cursorInfo)
        {
            _cursorInfo = cursorInfo;
            _tabletDevice = tabletDevice;
            _pointerLogic = StylusLogic.GetCurrentStylusLogicAs<PointerLogic>();
 
            // Touch devices have a special set of handling code
            if (tabletDevice.Type == TabletDeviceType.Touch)
            {
                TouchDevice = new PointerTouchDevice(this);
            }
 
            _interactionEngine = new PointerInteractionEngine(this);
 
            _interactionEngine.InteractionDetected += HandleInteraction;
 
            List<StylusButton> buttons = new List<StylusButton>();
 
            // Create a button collection for this StylusDevice based off the button properties stored in the tablet
            // This needs to be done as each button instance has a StylusDevice owner that it uses to access the raw
            // data in the StylusDevice.
            foreach (var prop in _tabletDevice.DeviceInfo.StylusPointProperties)
            {
                if (prop.IsButton)
                {
                    StylusButton button = new StylusButton(StylusPointPropertyIds.GetStringRepresentation(prop.Id), prop.Id);
                    button.SetOwner(this);
                    buttons.Add(button);
                }
            }
 
            _stylusButtons = new StylusButtonCollection(buttons);
        }
 
        #endregion
 
        #region IDisposable
 
        /// <summary>
        /// Eagerly dispose any resources
        /// </summary>
        /// <param name="disposing">If this is a dispose or finalize call</param>
        protected override void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    _interactionEngine.Dispose();
                }
            }
 
            _disposed = true;
        }
 
        #endregion
 
        #region InputDevice
 
        /// <summary>
        ///     Returns the element that input from this device is sent to.
        /// </summary>
        internal override IInputElement Target
        {
            get
            {
                return DirectlyOver;
            }
        }
 
        /// <summary>
        ///     Returns the PresentationSource that is reporting input for this device.
        /// </summary>
        /// <remarks>
        ///     Callers must have UIPermission(UIPermissionWindow.AllWindows) to call this API.
        /// </remarks>
        internal override PresentationSource ActiveSource => _inputSource;
 
        #endregion
 
        #region Properties
 
        internal UnsafeNativeMethods.POINTER_INFO CurrentPointerInfo { get { return _pointerData.Info; } }
 
        internal HwndPointerInputProvider CurrentPointerProvider
        {
            get;
            private set;
        }
 
        internal uint CursorId
        {
            get
            {
                return _cursorInfo.cursorId;
            }
        }
 
        internal bool IsNew
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_NEW) ?? false;
            }
        }
 
        internal bool IsInContact
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_INCONTACT) ?? false;
            }
        }
 
        internal bool IsPrimary
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_PRIMARY) ?? false;
            }
        }
 
        internal bool IsFirstButton
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_FIRSTBUTTON) ?? false;
            }
        }
 
        internal bool IsSecondButton
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_SECONDBUTTON) ?? false;
            }
        }
 
        internal bool IsThirdButton
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_THIRDBUTTON) ?? false;
            }
        }
 
        internal bool IsFourthButton
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_FOURTHBUTTON) ?? false;
            }
        }
 
        internal bool IsFifthButton
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_FIFTHBUTTON) ?? false;
            }
        }
 
        internal uint TimeStamp
        {
            get
            {
                return _pointerData?.Info.dwTime ?? 0;
            }
        }
 
        internal bool IsDown
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_DOWN) ?? false;
            }
        }
 
        internal bool IsUpdate
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_UPDATE) ?? false;
            }
        }
 
        internal bool IsUp
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_UP) ?? false;
            }
        }
 
        internal bool HasCaptureChanged
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_CAPTURECHANGED) ?? false;
            }
        }
 
        internal bool HasTransform
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_HASTRANSFORM) ?? false;
            }
        }
 
        internal PointerTouchDevice TouchDevice
        {
            get; private set;
        } = null;
 
        #endregion
 
        #region StylusDeviceBase Properties
 
        internal override StylusPlugInCollection CurrentVerifiedTarget { get; set; }
 
        /// <summary>
        ///     Returns the PresentationSource that is reporting input for this device.
        /// </summary>
        internal override PresentationSource CriticalActiveSource => _inputSource;
 
        /// <summary>
        /// Returns the button collection that is associated with the StylusDevice.
        /// </summary>
        internal override StylusButtonCollection StylusButtons
        {
            get
            {
                return _stylusButtons;
            }
        }
 
        internal override StylusPoint RawStylusPoint
        {
            get
            {
                return _currentStylusPoints[_currentStylusPoints.Count - 1];
            }
        }
 
        /// <summary>
        ///     Returns whether the StylusDevice object has been internally disposed.
        /// </summary>
        internal override bool IsValid
        {
            get
            {
                return true;
            }
        }
 
        /// <summary>
        ///     Returns the element that the stylus is over.
        /// </summary>
        internal override IInputElement DirectlyOver
        {
            get
            {
                return _stylusOver;
            }
        }
 
        /// <summary>
        ///     Returns the element that has captured the stylus.
        /// </summary>
        internal override IInputElement Captured
        {
            get
            {
                return _stylusCapture;
            }
        }
 
        /// <summary>
        /// Returns the tablet associated with the StylusDevice
        /// </summary>
        internal override TabletDevice TabletDevice
        {
            get
            {
                return _tabletDevice.TabletDevice;
            }
        }
 
        /// <summary>
        /// Returns the pointer tablet associated with the StylusDevice
        /// </summary>
        internal PointerTabletDevice PointerTabletDevice
        {
            get
            {
                return _tabletDevice;
            }
        }
 
        /// <summary>
        /// Returns the name of the StylusDevice
        /// 
        /// WISP returns either "Eraser" or "Stylus" depending on the cursor type.  Do the same here.
        /// See Stylus\Biblio.txt - 5/6
        /// </summary>
        internal override string Name
        {
            get
            {
                return (_cursorInfo.cursor == UnsafeNativeMethods.POINTER_DEVICE_CURSOR_TYPE.POINTER_DEVICE_CURSOR_TYPE_ERASER)
                    ? "Eraser"
                    : "Stylus";
            }
        }
 
        /// <summary>
        /// Returns the hardware id of the StylusDevice
        /// </summary>
        internal override int Id
        {
            get
            {
                unchecked
                {
                    return (int)CursorId;
                }
            }
        }
 
        /// <summary>
        ///     Indicates the stylus is not touching the surface.
        ///     InAir events are general sent at a lower frequency.
        /// </summary>
        internal override bool InAir
        {
            get
            {
                return !(_pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_INCONTACT) ?? false)
                    && (_pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_INRANGE) ?? false);
            }
        }
 
        /// <summary>
        ///     Indicates stylusDevice is in the inverted state.
        /// </summary>
        internal override bool Inverted
        {
            get
            {
                return _tabletDevice.Type == TabletDeviceType.Stylus
                    && (_pointerData?.PenInfo.penFlags.HasFlag(UnsafeNativeMethods.PEN_FLAGS.PEN_FLAG_INVERTED) ?? false);
            }
        }
 
        /// <summary>
        ///     Indicates stylusDevice is in the inverted state.
        /// </summary>
        internal override bool InRange
        {
            get
            {
                return _pointerData?.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_INRANGE) ?? false;
            }
        }
 
        internal override int DoubleTapDeltaX
        {
            get
            {
                return (int)PointerTabletDevice.DoubleTapSize.Width;
            }
        }
 
        internal override int DoubleTapDeltaY
        {
            get
            {
                return (int)PointerTabletDevice.DoubleTapSize.Height;
            }
        }
 
        internal override int DoubleTapDeltaTime
        {
            get
            {
                return PointerTabletDevice.DoubleTapDeltaTime;
            }
        }
 
        /// <summary>
        /// Returns the tap count as detected in the interaction engine.
        /// </summary>
        internal override int TapCount
        {
            get
            {
                return _tapCount;
            }
 
            set
            {
                _tapCount = value;
            }
        }
 
        internal override CaptureMode CapturedMode
        {
            get
            {
                return _captureMode;
            }
        }
 
        #endregion
 
        #region StylusDeviceBase Functions
 
        /// <summary>
        ///     Captures the stylus to a particular element.
        /// </summary>
        internal override bool Capture(IInputElement element, CaptureMode captureMode)
        {
            int timeStamp = Environment.TickCount;
 
            VerifyAccess();
 
            if (!(captureMode == CaptureMode.None || captureMode == CaptureMode.Element || captureMode == CaptureMode.SubTree))
            {
                throw new System.ComponentModel.InvalidEnumArgumentException("captureMode", (int)captureMode, typeof(CaptureMode));
            }
 
            if (element == null)
            {
                captureMode = CaptureMode.None;
            }
 
            if (captureMode == CaptureMode.None)
            {
                element = null;
            }
 
            // Validate that element is either a UIElement, a ContentElement or a UIElement3D.
            DependencyObject doStylusCapture = element as DependencyObject;
 
            if (doStylusCapture != null && !InputElement.IsValid(element))
            {
                throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, doStylusCapture.GetType()));
            }
 
            doStylusCapture?.VerifyAccess();
 
            bool success = false;
 
            // The element we are capturing to must be both enabled and visible.
            UIElement e = element as UIElement;
 
            if ((e?.IsVisible ?? false) || (e?.IsEnabled ?? false))
            {
                success = true;
            }
            else
            {
                ContentElement ce = element as ContentElement;
 
                if (ce?.IsEnabled ?? false)
                {
                    success = true;
                }
                else
                {
                    // Setting capture to null.
                    success = true;
                }
            }
 
            if (success)
            {
                ChangeStylusCapture(element, captureMode, timeStamp);
            }
 
            return success;
        }
 
        /// <summary>
        ///     Captures the stylus to a particular element.
        /// </summary>
        internal override bool Capture(IInputElement element)
        {
            // No need for calling ApplyTemplate since we forward the call.
            return Capture(element, CaptureMode.Element);
        }
 
        /// <summary>
        ///     Forces the stylusdevice to resynchronize at it's current location and state.
        ///     It can conditionally generate a Stylus Move/InAirMove (at the current location) if a change
        ///     in hittesting is detected that requires an event be generated to update elements 
        ///     to the current state (typically due to layout changes without Stylus changes).  
        ///     Has the same behavior as MouseDevice.Synchronize().
        /// </summary>
        internal override void Synchronize()
        {
            // Simulate a stylus move (if we are current stylus, inrange, visuals still valid to update
            // and has moved).
            if (InRange && _inputSource?.CompositionTarget is { } target && !target.IsDisposed)
            {
                Point rawScreenPoint = new Point(_pointerData.Info.ptPixelLocationRaw.X, _pointerData.Info.ptPixelLocationRaw.Y);
                Point ptDevice = PointUtil.ScreenToClient(rawScreenPoint, _inputSource);
 
                // GlobalHitTest always returns an IInputElement, so we are sure to have one.
                IInputElement stylusOver = Input.StylusDevice.GlobalHitTest(_inputSource, ptDevice);
                bool fOffsetChanged = false;
 
                if (_stylusOver == stylusOver)
                {
                    Point ptOffset = GetPosition(stylusOver);
                    fOffsetChanged = MS.Internal.DoubleUtil.AreClose(ptOffset.X, _rawElementRelativePosition.X) == false || MS.Internal.DoubleUtil.AreClose(ptOffset.Y, _rawElementRelativePosition.Y) == false;
                }
 
                if (fOffsetChanged || _stylusOver != stylusOver)
                {
                    int timeStamp = Environment.TickCount;
 
                    if (_currentStylusPoints != null &&
                        _currentStylusPoints.Count > 0 &&
                        StylusPointDescription.AreCompatible(PointerTabletDevice.StylusPointDescription, _currentStylusPoints.Description))
                    {
                        StylusPoint stylusPoint = _currentStylusPoints[_currentStylusPoints.Count - 1];
                        int[] data = stylusPoint.GetPacketData();
 
                        // get back to the correct coordinate system
                        Matrix m = _tabletDevice.TabletToScreen;
                        m.Invert();
                        Point ptTablet = ptDevice * m;
 
                        data[0] = (int)ptTablet.X;
                        data[1] = (int)ptTablet.Y;
 
                        RawStylusInputReport report = new RawStylusInputReport(InputMode.Foreground,
                                                                             timeStamp,
                                                                             _inputSource,
                                                                             InAir ? RawStylusActions.InAirMove : RawStylusActions.Move,
                                                                             () => { return PointerTabletDevice.StylusPointDescription; },
                                                                             TabletDevice.Id,
                                                                             Id,
                                                                             data);
 
 
                        report.Synchronized = true;
 
                        InputReportEventArgs inputReportEventArgs = new InputReportEventArgs(StylusDevice, report);
                        inputReportEventArgs.RoutedEvent = InputManager.PreviewInputReportEvent;
 
                        InputManager.Current.ProcessInput(inputReportEventArgs);
                    }
                }
            }
        }
 
        /// <summary>
        ///     Returns a StylusPointCollection object for processing the data in the packet.
        ///     This method creates a new StylusPointCollection and copies the data.
        /// </summary>
        internal override StylusPointCollection GetStylusPoints(IInputElement relativeTo)
        {
            VerifyAccess();
 
            // Fake up an empty one if we have to.
            if (_currentStylusPoints == null)
            {
                return new StylusPointCollection(_tabletDevice.StylusPointDescription);
            }
            return _currentStylusPoints.Clone(StylusDevice.GetElementTransform(relativeTo), _currentStylusPoints.Description);
        }
 
        /// <summary>
        ///     Returns a StylusPointCollection object for processing the data in the packet.
        ///     This method creates a new StylusPointCollection and copies the data.
        /// </summary>
        internal override StylusPointCollection GetStylusPoints(IInputElement relativeTo, StylusPointDescription subsetToReformatTo)
        {
            ArgumentNullException.ThrowIfNull(subsetToReformatTo);
            // Fake up an empty one if we have to.
            if (_currentStylusPoints == null)
            {
                return new StylusPointCollection(subsetToReformatTo);
            }
 
            return _currentStylusPoints.Reformat(subsetToReformatTo, StylusDevice.GetElementTransform(relativeTo));
        }
 
        /// <summary>
        ///     Calculates the position of the stylus relative to a particular element.
        /// </summary>
        internal override Point GetPosition(IInputElement relativeTo)
        {
            VerifyAccess();
 
            // Validate that relativeTo is either a UIElement, a ContentElement or a UIElement3D.
            if (relativeTo != null && !InputElement.IsValid(relativeTo))
            {
                throw new InvalidOperationException();
            }
 
            PresentationSource relativePresentationSource = null;
 
            if (relativeTo != null)
            {
                DependencyObject dependencyObject = relativeTo as DependencyObject;
                DependencyObject containingVisual = InputElement.GetContainingVisual(dependencyObject);
                if (containingVisual != null)
                {
                    relativePresentationSource = PresentationSource.CriticalFromVisual(containingVisual);
                }
            }
            else
            {
                if (_inputSource is not null)
                {
                    relativePresentationSource = _inputSource;
                }
            }
 
            // Verify that we have a valid PresentationSource with a valid RootVisual
            // - if we don't we won't be able to invoke ClientToRoot or TranslatePoint and 
            //   we will just return 0,0
            if (relativePresentationSource == null || relativePresentationSource.RootVisual == null)
            {
                return new Point(0, 0);
            }
 
            Point curPoint = new Point(_pointerData.Info.ptPixelLocationRaw.X, _pointerData.Info.ptPixelLocationRaw.Y);
 
            Point ptClient = PointUtil.ScreenToClient(curPoint, relativePresentationSource);
            Point ptRoot = PointUtil.ClientToRoot(ptClient, relativePresentationSource);
            Point ptRelative = InputElement.TranslatePoint(ptRoot, relativePresentationSource.RootVisual, (DependencyObject)relativeTo);
 
            return ptRelative;
        }
 
        internal override Point GetMouseScreenPosition(MouseDevice mouseDevice)
        {
            return mouseDevice.GetScreenPositionFromSystem();
        }
 
        /// <summary>
        ///     Gets the current state of the specified button
        /// </summary>
        /// <param name="mouseButton">
        ///     The mouse button to get the state of
        /// </param>
        /// <param name="mouseDevice">
        ///     The MouseDevice that is making the request
        /// </param>
        /// <returns>
        ///     The state of the specified mouse button
        /// </returns>
        /// <remarks>
        ///     This is the hook where the Input system (via the MouseDevice) can call back into
        ///     the Stylus system when we are processing Stylus events instead of Mouse events
        /// </remarks>
        internal override MouseButtonState GetMouseButtonState(MouseButton mouseButton, MouseDevice mouseDevice)
        {
            return mouseDevice.GetButtonStateFromSystem(mouseButton);
        }
 
        #endregion
 
        #region Pointer Specific Functions
 
        /// <summary>
        /// Updates the internal StylusDevice state based on the WM_POINTER input and the formed raw data.
        /// </summary>
        /// <param name="provider">The hwnd associated WM_POINTER provider</param>
        /// <param name="inputSource">The PresentationSource where this message originated</param>
        /// <param name="pointerData">The aggregated pointer data retrieved from the WM_POINTER stack</param>
        /// <param name="rsir">The raw stylus input generated from the pointer data</param>
        internal void Update(HwndPointerInputProvider provider, PresentationSource inputSource,
            PointerData pointerData, RawStylusInputReport rsir)
        {
            _lastEventTimeTicks = Environment.TickCount;
 
            _inputSource = inputSource;
 
            _pointerData = pointerData;
 
            // First get the initial stylus points.  Raw data from pointer input comes in screen coordinates, keep that here since that is what we expect.
            _currentStylusPoints = new StylusPointCollection(rsir.StylusPointDescription, rsir.GetRawPacketData(), GetTabletToElementTransform(null), Matrix.Identity);
 
            // If a plugin has modified these points, we need to fixup the points with the new input
            if (rsir?.RawStylusInput?.StylusPointsModified ?? false)
            {
                // Note that RawStylusInput.Target (of type StylusPluginCollection)
                // guarantees that ViewToElement is invertible.
                GeneralTransform transformToElement = rsir.RawStylusInput.Target.ViewToElement.Inverse;
 
                Debug.Assert(transformToElement != null);
 
                _currentStylusPoints = rsir.RawStylusInput.GetStylusPoints(transformToElement);
            }
 
            // Store the current hwnd provider so we know for what hwnd we are processing this message
            CurrentPointerProvider = provider;
 
            if (PointerTabletDevice.Type == TabletDeviceType.Touch)
            {
                // If we are a touch device, sync the ActiveSource
                TouchDevice.ChangeActiveSource(_inputSource);
            }
        }
 
        #endregion
 
        #region Interaction Handling
 
        /// <summary>
        /// Triggers firing of all gestures detected in the interaction engine
        /// </summary>
        internal void UpdateInteractions(RawStylusInputReport rsir)
        {
            _interactionEngine.Update(rsir);
        }
 
        /// <summary>
        /// Processes gesture reports generated by the interaction engine
        /// </summary>
        /// <param name="clientData">Unused</param>
        /// <param name="originalReport">The gesture report generate by the engine</param>
        private void HandleInteraction(object clientData, RawStylusSystemGestureInputReport originalReport)
        {
            RawStylusSystemGestureInputReport report = new RawStylusSystemGestureInputReport(
                           InputMode.Foreground,
                           Environment.TickCount,
                           CriticalActiveSource,
                           () => { return PointerTabletDevice.StylusPointDescription; },
                           TabletDevice.Id,
                           Id,
                           originalReport.SystemGesture,
                           originalReport.GestureX,
                           originalReport.GestureY,
                           originalReport.ButtonState)
            {
                StylusDevice = StylusDevice,
            };
 
            // For a flick, update the points in the stylus device to the flick location.
            // This forces processing of the stylus over to use the initial flick location
            // instead of the last WM_POINTER message location allowing command processing to
            // be done on the flick location itself.
            if (report.SystemGesture == SystemGesture.Flick)
            {
                StylusPoint flickPoint = _currentStylusPoints[_currentStylusPoints.Count - 1];
 
                flickPoint.X = report.GestureX;
                flickPoint.Y = report.GestureY;
 
                _currentStylusPoints = new StylusPointCollection(flickPoint.Description,
                    flickPoint.GetPacketData(),
                    GetTabletToElementTransform(null),
                    Matrix.Identity);
            }
 
            InputReportEventArgs irea = new InputReportEventArgs(StylusDevice, report)
            {
                RoutedEvent = InputManager.PreviewInputReportEvent,
            };
 
            // Now send the input report
            InputManager.UnsecureCurrent.ProcessInput(irea);
        }
 
        #endregion
 
        #region Capture/Over Functions
 
        /// <summary>
        /// Returns the currently captured plugin and a ref indicating if there is a capture
        /// </summary>
        /// <param name="elementHasCapture">If this device has capture</param>
        /// <returns>The captured plug in collection or null if no capture exists</returns>
        internal override StylusPlugInCollection GetCapturedPlugInCollection(ref bool elementHasCapture)
        {
            elementHasCapture = (_stylusCapture != null);
            return _stylusCapturePlugInCollection;
        }
 
        /// <summary>
        /// Takes into account capture mode and hit testing to find the current stylusover.
        /// </summary>
        internal IInputElement FindTarget(PresentationSource inputSource, Point position)
        {
            IInputElement stylusOver = null;
 
            switch (_captureMode)
            {
                case CaptureMode.None:
                    {
                        stylusOver = StylusDevice.GlobalHitTest(inputSource, position);
 
                        // We understand UIElements and ContentElements.
                        // If we are over something else (like a raw visual)
                        // find the containing element.
                        if (!InputElement.IsValid(stylusOver))
                            stylusOver = InputElement.GetContainingInputElement(stylusOver as DependencyObject);
                    }
                    break;
 
                case CaptureMode.Element:
                    stylusOver = _stylusCapture;
                    break;
 
                case CaptureMode.SubTree:
                    {
                        IInputElement stylusCapture = InputElement.GetContainingInputElement(_stylusCapture as DependencyObject);
 
                        if (stylusCapture != null && inputSource != null)
                        {
                            // We need to re-hit-test to get the "real" UIElement we are over.
                            // This allows us to have our capture-to-subtree span multiple windows.
 
                            // GlobalHitTest always returns an IInputElement, so we are sure to have one.
                            stylusOver = StylusDevice.GlobalHitTest(inputSource, position);
                        }
 
                        if (stylusOver != null && !InputElement.IsValid(stylusOver))
                            stylusOver = InputElement.GetContainingInputElement(stylusOver as DependencyObject);
 
                        // Make sure that the element we hit is acutally underneath
                        // our captured element.  Because we did a global hit test, we
                        // could have hit an element in a completely different window.
                        //
                        // Note that we support the child being in a completely different window.
                        // So we use the GetUIParent method instead of just looking at
                        // visual/content parents.
                        if (stylusOver != null)
                        {
                            IInputElement ieTest = stylusOver;
                            UIElement eTest = null;
                            ContentElement ceTest = null;
 
                            while (ieTest != null && ieTest != stylusCapture)
                            {
                                eTest = ieTest as UIElement;
 
                                if (eTest != null)
                                {
                                    ieTest = InputElement.GetContainingInputElement(eTest.GetUIParent(true));
                                }
                                else
                                {
                                    ceTest = ieTest as ContentElement; // Should never fail.
 
                                    ieTest = InputElement.GetContainingInputElement(ceTest.GetUIParent(true));
                                }
                            }
 
                            // If we missed the capture point, we didn't hit anything.
                            if (ieTest != stylusCapture)
                            {
                                stylusOver = _stylusCapture;
                            }
                        }
                        else
                        {
                            // We didn't hit anything.  Consider the stylus over the capture point.
                            stylusOver = _stylusCapture;
                        }
                    }
                    break;
            }
 
            return stylusOver;
        }
 
        internal void ChangeStylusOver(IInputElement stylusOver)
        {
            // We are not syncing the OverSourceChanged event
            // the reasons for doing so are listed in the MouseDevice.cs OnOverSourceChanged implementation
            if (_stylusOver != stylusOver)
            {
                _stylusOver = stylusOver;
                _rawElementRelativePosition = GetPosition(_stylusOver);
            }
            else
            {
                // Always update the relative position if InRange since ChangeStylusOver is only
                // called when something changed (like capture or stylus moved) and in
                // that case we want this updated properly.  This value is used in Synchronize().
                if (InRange)
                {
                    _rawElementRelativePosition = GetPosition(_stylusOver);
                }
            }
 
            // The stylus over property is a singleton (only one stylus device at a time can
            // be over an element) so we let StylusLogic manager the element over state. 
            // NOTE: StylusLogic only allows the CurrentStylusDevice to change the over state.
            // Also note that Capture is also managed by StylusLogic in a similar fashion.
            _pointerLogic.UpdateOverProperty(this, _stylusOver);
        }
 
        internal void ChangeStylusCapture(IInputElement stylusCapture, CaptureMode captureMode, int timestamp)
        {
            // if the capture changed...
            if (stylusCapture != _stylusCapture)
            {
                // Actually change the capture first.  Invalidate the properties,
                // and then send the events.
                IInputElement oldStylusCapture = _stylusCapture;
 
                _stylusCapture = stylusCapture;
                _captureMode = captureMode;
 
                // We also need to figure out ahead of time if any plugincollections on this captured element (or a parent)
                // for the penthread hittesting code.
                _stylusCapturePlugInCollection = null;
 
                if (stylusCapture != null)
                {
                    UIElement uiElement = InputElement.GetContainingUIElement(stylusCapture as DependencyObject) as UIElement;
                    if (uiElement != null)
                    {
                        PresentationSource source = PresentationSource.CriticalFromVisual(uiElement as Visual);
 
                        if (source != null)
                        {
                            PointerStylusPlugInManager manager;
 
                            if (_pointerLogic.PlugInManagers.TryGetValue(source, out manager))
                            {
                                _stylusCapturePlugInCollection = manager.FindPlugInCollection(uiElement);
                            }
                        }
                    }
                }
 
                _pointerLogic.UpdateStylusCapture(this, oldStylusCapture, _stylusCapture, timestamp);
 
                // Send the LostStylusCapture and GotStylusCapture events.
                if (oldStylusCapture != null)
                {
                    StylusEventArgs lostCapture = new StylusEventArgs(StylusDevice, timestamp);
                    lostCapture.RoutedEvent = Stylus.LostStylusCaptureEvent;
                    lostCapture.Source = oldStylusCapture;
                    InputManager.UnsecureCurrent.ProcessInput(lostCapture);
                }
                if (_stylusCapture != null)
                {
                    StylusEventArgs gotCapture = new StylusEventArgs(StylusDevice, timestamp);
                    gotCapture.RoutedEvent = Stylus.GotStylusCaptureEvent;
                    gotCapture.Source = _stylusCapture;
                    InputManager.UnsecureCurrent.ProcessInput(gotCapture);
                }
 
                // Now update the stylus over state (only if this is the current stylus and 
                // it is inrange).
                if (_pointerLogic.CurrentStylusDevice == this || InRange)
                {
                    if (_stylusCapture != null)
                    {
                        IInputElement inputElementHit = _stylusCapture;
 
                        // See if we need to update over for subtree mode.
                        if (CapturedMode == CaptureMode.SubTree && _inputSource is not null)
                        {
                            Point pt = _pointerLogic.DeviceUnitsFromMeasureUnits(_inputSource, GetPosition(null));
                            inputElementHit = FindTarget(_inputSource, pt);
                        }
 
                        ChangeStylusOver(inputElementHit);
                    }
                    else
                    {
                        // Only try to update over if we have a valid input source.
                        if (_inputSource is not null)
                        {
                            Point pt = GetPosition(null); // relative to window (root element)
                            pt = _pointerLogic.DeviceUnitsFromMeasureUnits(_inputSource, pt); // change back to device coords.
                            IInputElement currentOver = Input.StylusDevice.GlobalHitTest(_inputSource, pt);
                            ChangeStylusOver(currentOver);
                        }
                    }
                }
 
                // For Mouse StylusDevice we want to make sure Mouse capture is set up the same.
                if (Mouse.Captured != _stylusCapture || Mouse.CapturedMode != _captureMode)
                {
                    Mouse.Capture(_stylusCapture, _captureMode);
                }
            }
        }
 
        #endregion
 
        #region Utilities
 
        /// <summary>
        /// Creates a new set of stylus points based on the latest raw input report
        /// </summary>
        internal override void UpdateEventStylusPoints(RawStylusInputReport report, bool resetIfNoOverride)
        {
            if (report.RawStylusInput != null && report.RawStylusInput.StylusPointsModified)
            {
                GeneralTransform transformToElement = report.RawStylusInput.Target.ViewToElement.Inverse;
                //note that RawStylusInput.Target (of type StylusPluginCollection)
                //guarantees that ViewToElement is invertible
                Debug.Assert(transformToElement != null);
 
                _currentStylusPoints = report.RawStylusInput.GetStylusPoints(transformToElement);
            }
            else if (resetIfNoOverride)
            {
                _currentStylusPoints =
                    new StylusPointCollection(report.StylusPointDescription,
                                              report.GetRawPacketData(),
                                              GetTabletToElementTransform(null),
                                              Matrix.Identity);
            }
        }
 
 
        /// <summary>
        ///     Returns the transform for converting from tablet to element
        ///     relative coordinates.
        /// </summary>
        internal GeneralTransform GetTabletToElementTransform(IInputElement relativeTo)
        {
            GeneralTransformGroup group = new GeneralTransformGroup();
            Matrix toDevice = _inputSource.CompositionTarget.TransformToDevice;
            toDevice.Invert();
            group.Children.Add(new MatrixTransform(PointerTabletDevice.TabletToScreen * toDevice));
            group.Children.Add(StylusDevice.GetElementTransform(relativeTo));
            return group;
        }
 
        #endregion
    }
}