|
// 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.Internal.Interop;
using MS.Utility;
using MS.Win32;
using System.Runtime.InteropServices;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input.Tracing;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
namespace System.Windows.Input.StylusWisp
{
/// <summary>
/// Implements the logic for stylus/touch operations via the WISP stack.
/// </summary>
internal class WispLogic : StylusLogic
{
internal WispLogic(InputManager inputManager)
{
Statistics.FeaturesUsed |= StylusTraceLogger.FeatureFlags.WispStackEnabled;
_inputManager = inputManager;
_inputManager.PreProcessInput += new PreProcessInputEventHandler(PreProcessInput);
_inputManager.PreNotifyInput += new NotifyInputEventHandler(PreNotifyInput);
_inputManager.PostProcessInput += new ProcessInputEventHandler(PostProcessInput);
#if !MULTICAPTURE
_overIsEnabledChangedEventHandler = new DependencyPropertyChangedEventHandler(OnOverIsEnabledChanged);
_overIsVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnOverIsVisibleChanged);
_overIsHitTestVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnOverIsHitTestVisibleChanged);
_reevaluateStylusOverDelegate = new DispatcherOperationCallback(ReevaluateStylusOverAsync);
_reevaluateStylusOverOperation = null;
_captureIsEnabledChangedEventHandler = new DependencyPropertyChangedEventHandler(OnCaptureIsEnabledChanged);
_captureIsVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnCaptureIsVisibleChanged);
_captureIsHitTestVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnCaptureIsHitTestVisibleChanged);
_reevaluateCaptureDelegate = new DispatcherOperationCallback(ReevaluateCaptureAsync);
_reevaluateCaptureOperation = null;
#endif
_shutdownHandler = new EventHandler(this.OnDispatcherShutdown);
_processDisplayChanged = new DispatcherOperationCallback(ProcessDisplayChanged);
_processDeferredMouseMove = new DispatcherOperationCallback(ProcessDeferredMouseMove);
ReadSystemConfig();
_dlgInputManagerProcessInput = new DispatcherOperationCallback(InputManagerProcessInput);
}
void OnDispatcherShutdown(object sender, EventArgs e)
{
if (_shutdownHandler != null)
_inputManager.Dispatcher.ShutdownFinished -= _shutdownHandler;
if (_tabletDeviceCollection != null)
{
// Clean up our state when the dispatcher exits. If a new dispatcher
// happens to be created on this thread again we'll create everything fresh.
_tabletDeviceCollection.DisposeTablets();
_tabletDeviceCollection = null;
_tabletDeviceCollectionDisposed = true;
}
_currentStylusDevice = null; // no active stylus device any more.
// NOTE: __penContextsMap will be cleaned up by HwndSource Dispose() so we don't worry about that.
}
/////////////////////////////////////////////////////////////////////
internal void ProcessSystemEvent(PenContext penContext,
int tabletDeviceId,
int stylusDeviceId,
int timestamp,
SystemGesture systemGesture,
int gestureX,
int gestureY,
int buttonState,
PresentationSource inputSource)
{
// We only want to process the system events we expose in the public enum
// for SystemSystemGesture. There are a bunch of other system gestures that
// can come through.
if (systemGesture == SystemGesture.Tap ||
systemGesture == SystemGesture.RightTap ||
systemGesture == SystemGesture.Drag ||
systemGesture == SystemGesture.RightDrag ||
systemGesture == SystemGesture.HoldEnter ||
systemGesture == SystemGesture.HoldLeave ||
systemGesture == SystemGesture.HoverEnter ||
systemGesture == SystemGesture.HoverLeave ||
systemGesture == SystemGesture.Flick ||
systemGesture == RawStylusSystemGestureInputReport.InternalSystemGestureDoubleTap ||
systemGesture == SystemGesture.None)
{
Debug.Assert(systemGesture != SystemGesture.None); // We should ever see this as input.
RawStylusSystemGestureInputReport inputReport =
new RawStylusSystemGestureInputReport(
InputMode.Foreground,
timestamp,
inputSource,
penContext,
tabletDeviceId,
stylusDeviceId,
systemGesture,
gestureX, // location of system gesture in tablet device coordinates
gestureY,
buttonState); // flicks passes the flickinfo in this param
// actions: RawStylusActions.StylusSystemEvent
ProcessInputReport(inputReport);
}
}
/////////////////////////////////////////////////////////////////////
// on pen/RTI thread
internal void ProcessInput(
RawStylusActions actions,
PenContext penContext,
int tabletDeviceId,
int stylusDeviceId,
int[] data,
int timestamp,
PresentationSource inputSource)
{
RawStylusInputReport inputReport =
new RawStylusInputReport(InputMode.Foreground,
timestamp,
inputSource,
penContext,
actions,
tabletDeviceId,
stylusDeviceId,
data);
ProcessInputReport(inputReport);
}
/////////////////////////////////////////////////////////////////////
// NOTE: this is invoked on the pen thread, outside of Dispatcher
/// <summary>
/// This function will appropriately coalesce any move messages if needed
/// and will all appropriately coalesced moves and also any non-move messages.
/// This ensures both the responsiveness and consistency of the stack.
/// </summary>
/// <param name="inputReport">The report to queue</param>
void CoalesceAndQueueStylusEvent(RawStylusInputReport inputReport)
{
StylusDeviceBase stylusDevice = inputReport?.StylusDevice?.StylusDeviceImpl;
// Due to changes both in WISP and in the underlying PenIMC code, it is possible that
// the stylus device here could be null. If this is the case, the lookups will fail
// with an exception.
if (stylusDevice == null)
{
return;
}
// DevDiv:652804
// Previously the pen thread would blindly shove any move from Wisp onto the stylus
// queue. This is a problem if the main thread stalls but the pen thread does not.
// Wisp will coalesce data, but only if the pen thread fails to pick up the event.
// Otherwise, we need to re-implement coalescing of move events here so that we
// make move data available via GetIntermediateTouchPoints but do not flood the
// stylus queue with old moves, creating lag in user interaction. To do that we
// detect stalls in the main thread by checking if the last move has processed.
// If not, we coalesce moves together until we can queue up the coalesced events.
RawStylusInputReport lastMoveReport = null;
RawStylusInputReport coalescedMove = null;
// Multiple threads may access the coalescing information at the same time.
// We lock here to prevent that. This is a rare scenario, so the coarse
// grained lock is fine here.
lock (_coalesceLock)
{
_lastMovesQueued.TryGetValue(stylusDevice, out lastMoveReport);
_coalescedMoves.TryGetValue(stylusDevice, out coalescedMove);
// All moves now go through a coalesce to simplify logic
if (inputReport.Actions == RawStylusActions.Move)
{
// Start a new coalescing report if none exists
if (coalescedMove == null)
{
_coalescedMoves[stylusDevice] = inputReport;
coalescedMove = inputReport;
}
// Add new move to coalesced
else
{
// GetRawPacketData creates copies, so only call them once
int[] oldData = coalescedMove.GetRawPacketData();
int[] newData = inputReport.GetRawPacketData();
int[] mergedData = new int[oldData.Length + newData.Length];
oldData.CopyTo(mergedData, 0);
newData.CopyTo(mergedData, oldData.Length);
coalescedMove = new RawStylusInputReport(
coalescedMove.Mode,
coalescedMove.Timestamp,
coalescedMove.InputSource,
coalescedMove.PenContext,
coalescedMove.Actions,
coalescedMove.TabletDeviceId,
coalescedMove.StylusDeviceId,
mergedData
);
coalescedMove.StylusDevice = stylusDevice.StylusDevice;
_coalescedMoves[stylusDevice] = coalescedMove;
}
// We can't queue any move if one is still waiting for processing
if (lastMoveReport != null
&& lastMoveReport.IsQueued)
{
return;
}
}
// If we get this far, we are queuing a coalesced move if it exists
if (coalescedMove != null)
{
QueueStylusEvent(coalescedMove);
// Set last move and cleanup coalescing tracking
_lastMovesQueued[stylusDevice] = coalescedMove;
_coalescedMoves.Remove(stylusDevice);
}
// Move always queues via coalescing, so only queue here if not a move
// This has to be done post-coalesced move to maintain order of touch
// operations
if (inputReport.Actions != RawStylusActions.Move)
{
QueueStylusEvent(inputReport);
// Once we see a non-move, we should get no more input for this particular chain
// so we can remove the stored prior moves (if they exist).
_lastMovesQueued.Remove(stylusDevice);
}
}
}
/////////////////////////////////////////////////////////////////////
void ProcessInputReport(RawStylusInputReport inputReport)
{
// First, assign the StylusDevice (note it may still be null for new StylusDevice)
inputReport.StylusDevice = FindStylusDeviceWithLock(inputReport.StylusDeviceId)?.StylusDevice;
// Only call plugins if we are not in a drag drop operation and the HWND is enabled!
if (!_inDragDrop || !inputReport.PenContext.Contexts.IsWindowDisabled)
{
// Handle real time input (call StylusPlugIns)
InvokeStylusPluginCollection(inputReport);
}
CoalesceAndQueueStylusEvent(inputReport);
}
/// <summary>
/// Queues a RawStylusInputReport for later processing on the dispatcher thread
/// </summary>
/// <param name="report"></param>
private void QueueStylusEvent(RawStylusInputReport report)
{
// ETW event indicating that a stylus input report was queued.
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordInput | EventTrace.Keyword.KeywordPerf,
EventTrace.Level.Info, EventTrace.Event.StylusEventQueued, report.StylusDeviceId);
report.IsQueued = true;
// Queue up new event.
lock (_stylusEventQueueLock)
{
if (report.StylusDevice != null)
{
var tablet = report.StylusDevice.TabletDevice.As<WispTabletDevice>();
if (tablet != null)
{
tablet.QueuedEventCount++;
}
}
_queueStylusEvents.Enqueue(report);
}
// post the args into dispatcher queue
Dispatcher.BeginInvoke(DispatcherPriority.Input, _dlgInputManagerProcessInput, null);
}
/////////////////////////////////////////////////////////////////////
// this is invoked from within the Dispatcher the _inputManager is affiliated to
internal object InputManagerProcessInput(object oInput)
{
RawStylusInputReport rawStylusInputReport = null;
WispTabletDevice tabletDevice = null;
// Now grab the queued up Stylus input reports and process them.
lock (_stylusEventQueueLock)
{
if (_queueStylusEvents.Count > 0)
{
rawStylusInputReport = _queueStylusEvents.Dequeue();
tabletDevice = rawStylusInputReport?.StylusDevice?.TabletDevice?.As<WispTabletDevice>();
if (tabletDevice != null)
{
tabletDevice.QueuedEventCount--;
}
}
}
// StylusDevice could have been disposed internally here.
// We should check StylusDevice.IsValid property.
if (rawStylusInputReport != null
&& rawStylusInputReport.StylusDevice != null
&& rawStylusInputReport.StylusDevice.IsValid)
{
rawStylusInputReport.IsQueued = false;
PenContext penContext = rawStylusInputReport.PenContext;
if (tabletDevice != null
&& penContext.UpdateScreenMeasurementsPending)
{
bool areSizeDeltasValid = tabletDevice.AreSizeDeltasValid();
// Update screen measurements
penContext.UpdateScreenMeasurementsPending = false;
tabletDevice.UpdateScreenMeasurements();
if (areSizeDeltasValid)
{
// Update TabletDevice.DoubleTapDelta and TabletDevice.CancelDelta if needed.
tabletDevice.UpdateSizeDeltas(penContext.StylusPointDescription, this);
}
}
// build InputReportEventArgs
InputReportEventArgs input = new InputReportEventArgs(null, rawStylusInputReport);
input.RoutedEvent = InputManager.PreviewInputReportEvent;
// Set flag to prevent reentrancy due to wisptis mouse event getting triggered
// while processing this stylus event.
_processingQueuedEvent = true;
try
{
InputManagerProcessInputEventArgs(input);
}
finally
{
_processingQueuedEvent = false;
}
}
return null;
}
/////////////////////////////////////////////////////////////////////
internal void InputManagerProcessInputEventArgs(InputEventArgs input)
{
_inputManager.ProcessInput(input);
}
private bool DeferMouseMove(RawMouseInputReport mouseInputReport)
{
if (!_triedDeferringMouseMove)
{
if (_deferredMouseMove != null)
{
return false; // only allow one at a time.
}
else
{
_deferredMouseMove = mouseInputReport;
// Now make the deferred call to the process the mouse move.
Dispatcher.BeginInvoke(DispatcherPriority.Background, _processDeferredMouseMove, null);
}
return true;
}
return false;
}
internal object ProcessDeferredMouseMove(object oInput)
{
// Make sure we haven't flushed the deferred event before dispatcher version processes.
if (_deferredMouseMove != null)
{
#if !MULTICAPTURE
// See if a stylus is now in range.
if ((CurrentStylusDevice == null || !CurrentStylusDevice.InRange))
{
SendDeferredMouseEvent(true);
}
else
{
// We are now inRange so eat this.
SendDeferredMouseEvent(false);
}
#else
// See if a stylus is now in range and eat messages
// when a stylus is in range.
SendDeferredMouseEvent(!_stylusDeviceInRange);
#endif
}
return null;
}
private void SendDeferredMouseEvent(bool sendInput)
{
if (sendInput)
{
_triedDeferringMouseMove = true; // Only reset to not try again if we don't find we are in range.
// Only send if we have valid PresentationSource and CompositionTarget.
if (_deferredMouseMove != null && _deferredMouseMove.InputSource != null &&
_deferredMouseMove.InputSource.CompositionTarget != null &&
!_deferredMouseMove.InputSource.CompositionTarget.IsDisposed)
{
// Process mouse move now since nothing else from stylus came through...
InputReportEventArgs mouseArgs = new InputReportEventArgs(_inputManager.PrimaryMouseDevice, _deferredMouseMove);
mouseArgs.RoutedEvent = InputManager.PreviewInputReportEvent;
_deferredMouseMove = null; // Clear this out before sending.
// This will cause _lastMoveFromStylus to be set to false.
_inputManager.ProcessInput(mouseArgs);
}
}
// We no longer need the ref on the cached input report.
_deferredMouseMove = null;
}
private void PreProcessInput(object sender, PreProcessInputEventArgs e)
{
if (_inputEnabled)
{
if (e.StagingItem.Input.RoutedEvent == InputManager.PreviewInputReportEvent)
{
InputReportEventArgs input = e.StagingItem.Input as InputReportEventArgs;
if (input != null && !input.Handled)
{
// See if we are in a DragDrop operation. If so set our internal flag
// which stops us from promoting Stylus or Mouse events!
if (_inDragDrop != _inputManager.InDragDrop)
{
_inDragDrop = _inputManager.InDragDrop;
// If we are going out of DragDrop then we need to re sync the mouse state
// if we have a stylus device in range (otherwise we sync on the next
// stylus coming in range).
if (!_inDragDrop && _stylusDeviceInRange)
{
UpdateMouseState();
_leavingDragDrop = true;
}
}
if (input.Report.Type == InputType.Mouse)
{
// If we see a non stylus mouse event (not triggered from stylus event)
if ((input.Device as StylusDevice) == null)
{
// And we only do work if we are enabled for stylus input (ie - have tablet devices)
// and the tablet device collection hasnt been disposed yet.
if (!_tabletDeviceCollectionDisposed && TabletDevices.Count != 0)
{
RawMouseInputReport mouseInputReport = (RawMouseInputReport)input.Report;
RawMouseActions actions = mouseInputReport.Actions;
int mouseExtraInfo = NativeMethods.IntPtrToInt32(mouseInputReport.ExtraInformation);
bool fromWisptis = IsPromotedMouseEvent(mouseInputReport);
// Grab the stylus info if from wisptis
if (fromWisptis)
{
_lastMouseMoveFromStylus = true;
// Grab the current stylus Id out of the extra info.
_lastStylusDeviceId = (mouseExtraInfo & 0x000000FF);
}
// If mouse is getting deactivated and StylusOver is non null then force stylusover to null.
if ((actions & RawMouseActions.Deactivate) == RawMouseActions.Deactivate)
{
_seenRealMouseActivate = false;
if (CurrentStylusDevice != null)
{
PenContexts penContexts = GetPenContextsFromHwnd(mouseInputReport.InputSource);
// If we are inRange still then defer the Deactivate call till we are OutOfRange.
if (_stylusDeviceInRange && !_inDragDrop && (penContexts == null || !penContexts.IsWindowDisabled))
{
_mouseDeactivateInputReport = mouseInputReport;
e.Cancel();
input.Handled = true;
return;
}
#if !MULTICAPTURE
else if (CurrentStylusDevice.DirectlyOver != null)
#else
else
#endif
{
MouseDevice mouseDevice = _inputManager.PrimaryMouseDevice;
if (mouseDevice.CriticalActiveSource == mouseInputReport.InputSource)
{
#if !MULTICAPTURE
// Update over to be null when deactivating.
_currentStylusDevice.ChangeStylusOver(null);
#else
lock (__stylusDeviceLock)
{
foreach (var pair in __stylusDeviceMap)
{
var currentDevice = pair.Value;
if (currentDevice.DirectlyOver != null)
{
// Update over to be null when deactivating.
currentDevice.ChangeStylusOver(null);
}
}
}
#endif
}
}
}
}
// See if we got some mouse input we need to check for consistency (not tagged from wisptis)
else if ((actions & RawMouseActions.CancelCapture) != 0)
{
// We need to resend this back through as coming from a stylusdevice if in range
if (CurrentStylusDevice != null && CurrentStylusDevice.InRange)
{
RawMouseInputReport cancelCaptureInputReport =
new RawMouseInputReport(mouseInputReport.Mode,
mouseInputReport.Timestamp,
mouseInputReport.InputSource,
mouseInputReport.Actions,
0, // Rest of the parameters are not used...
0,
0,
IntPtr.Zero);
InputReportEventArgs args = new InputReportEventArgs(CurrentStylusDevice.StylusDevice, cancelCaptureInputReport);
args.RoutedEvent = InputManager.PreviewInputReportEvent;
e.Cancel();
_inputManager.ProcessInput(args);
}
}
// Handle the Mouse activation
else if ((actions & RawMouseActions.Activate) != 0)
{
// If mouse is getting Activated and we ate a Deactivate then clear the cached Deactivate.
_mouseDeactivateInputReport = null;
// We process Activate events and make sure to clear any other actions if we are resending
// this from a StylusDevice. This is so we don't get a move generated before we see the
// StylusDevice InRange event and the following StylusMove which will generate a MouseMove.
WispStylusDevice activateStylusDevice = null;
_seenRealMouseActivate = true;
// See if we need to process this event from us.
if (CurrentStylusDevice != null && CurrentStylusDevice.InRange)
activateStylusDevice = _currentStylusDevice;
else if (fromWisptis || ShouldConsiderStylusInRange(mouseInputReport))
activateStylusDevice = FindStylusDevice(_lastStylusDeviceId);
// We need to resend this as coming from a stylusdevice if in range possibly.
if (activateStylusDevice != null)
{
// Check to se if we have already Activated the mouse from a stylus event.
// If not then we need to let this one go through marked from us if we are in range!
if (mouseInputReport.InputSource != _inputManager.PrimaryMouseDevice.CriticalActiveSource)
{
Point pt;
pt = activateStylusDevice.LastMouseScreenPoint; // Use last promoted mouse location.
pt = PointUtil.ScreenToClient(pt, mouseInputReport.InputSource);
RawMouseInputReport activateInputReport =
new RawMouseInputReport(mouseInputReport.Mode,
mouseInputReport.Timestamp,
mouseInputReport.InputSource,
RawMouseActions.Activate, // Only let activate happen.
(int)pt.X,
(int)pt.Y,
mouseInputReport.Wheel,
mouseInputReport.ExtraInformation);
InputReportEventArgs args = new InputReportEventArgs(activateStylusDevice.StylusDevice, activateInputReport);
args.RoutedEvent = InputManager.PreviewInputReportEvent;
_inputManager.ProcessInput(args);
}
// If stylus is active then eat this since we'll send the activate. We just cancel
// to ensure the mouse event from HwndMouseInputProvider returns that it was not handled.
// The mouse device code will not do anything with the event during PreProcessInput and
// it will not see a PreNotifyInput event for this.
e.Cancel();
}
}
// Handle moves and button presses that might be from wisptis or in conflict with our current state
else if ((actions & (RawMouseActions.AbsoluteMove | RawMouseActions.QueryCursor |
RawMouseActions.Button1Press | RawMouseActions.Button1Release |
RawMouseActions.Button2Press | RawMouseActions.Button2Release)) != 0)
{
// If we see a mouse left down and stylus is inRange and we haven't sent a mouse down
// then send it through.
if ((actions & RawMouseActions.Button1Press) != 0 && CurrentStylusDevice != null &&
!CurrentStylusDevice.InAir)
{
// We can only Activate the window without flashing the tray icon for it when
// we are processing an Input message. So we defer it till we see the mouse down.
HwndSource hwndSource = mouseInputReport.InputSource as HwndSource;
IntPtr hwnd = hwndSource != null ? hwndSource.Handle : IntPtr.Zero;
// If we see a stylusdown and we are not the foreground window
// and there's no capture then make sure we get activated.
// We only do this for top most windows.
if (hwnd != IntPtr.Zero &&
_inputManager.PrimaryMouseDevice.Captured != null &&
UnsafeNativeMethods.GetParent(new HandleRef(this, hwnd)) == IntPtr.Zero &&
hwnd != UnsafeNativeMethods.GetForegroundWindow())
{
// Check to see if this window has the WS_EX_NOACTIVATE style set, if so don't do the activation work.
int style = UnsafeNativeMethods.GetWindowLong(new HandleRef(this, hwnd), NativeMethods.GWL_EXSTYLE);
if ((style & NativeMethods.WS_EX_NOACTIVATE) == 0)
{
UnsafeNativeMethods.SetForegroundWindow(new HandleRef(this, hwndSource.Handle));
}
}
// There are times we need to make sure we promote the left mouse down before we see a system gesture.
// This is when the press and hold gesture is disabled and thus we can guarentee that sending the
// left mouse down is the correct thing to do. This is critical for some controls such as repeat
// buttons since in order get them in the pressed state (and start them repeating) we have to send the
// left mouse down. Note if you go down with the stylus and don't move it past the drag tolerance no
// system gesture will be generated and the normal code to promote the mouse down will not happen otherwise.
//
// This code will kick in on Vista with the new support to disable the press and hold gesture per element
// (via WM_TABLE_QUERYSYSTEMGESTURESTATUS message) and also on XP and Vista if the press and hold gesture is
// disabled in the tablet control panel.
if (!_currentStylusDevice.SentMouseDown && fromWisptis && ShouldPromoteToMouse(_currentStylusDevice))
{
// left button down...lets replay the down at this time...
// Note: We may wait till later if stylus is not down yet!
// We will do it only when we are not manipulating and we will
// delay it if we know that manipulations are possible.
WispStylusTouchDevice touchDevice = _currentStylusDevice.TouchDevice;
if (touchDevice.PromotingToManipulation)
{
touchDevice.StoredStagingAreaItems.AddItem(e.StagingItem);
}
else if (touchDevice.PromotingToOther)
{
_currentStylusDevice.PlayBackCachedDownInputReport(mouseInputReport.Timestamp);
}
}
}
// We want to eat mouse messages with the wisptis injected signature except
// if the MouseDevice is getting activated or deactivated by it (filtered out
// above). We also want to eat any spurious mouse events recieved between the
// stylus down and the stylus system gesture getting fired.
if (fromWisptis)
{
// eat mouse messages generated by stylus;
// these will be handled off the stylus event stream and promoted to a mouse input event
bool handled = true;
// If the mouse is captured we need to validate that the mouse location
// is actually inside the client area (we will only see those wisptis
// events and can thus eat this one).
Point ptMouse = new Point(mouseInputReport.X, mouseInputReport.Y);
bool stylusIsDown = (CurrentStylusDevice != null) ? !CurrentStylusDevice.InAir : false;
if (!stylusIsDown && Mouse.Captured != null && !InWindowClientRect(ptMouse, mouseInputReport.InputSource))
{
handled = false;
}
// If the input has been marked as Handled, we want it to be cancelled at PreProcess stage.
if (handled)
{
// We can't mark left and right mouse buttons as handled since it will stop the
// DefWindowProc from being processed but we Cancel it which stops mouse from processing
// it. Move's though we need to eat.
if ((actions & (RawMouseActions.Button1Press | RawMouseActions.Button2Press)) == 0)
{
input.Handled = true;
}
e.Cancel();
// If the stylus is in the up state when we see a mouse down then just note that we've
// seen the mouse left down and wanted to send it but the stylus down
// has not been seen yet so we can't. When we see the stylus down later we'll promote
// the left mouse down after processing the stylus down.
if ((actions & RawMouseActions.Button1Press) != 0 && CurrentStylusDevice != null &&
CurrentStylusDevice.InAir)
{
_currentStylusDevice.SetSawMouseButton1Down(true);
}
// Only try to process stylus events on wisptis generated mouse events and
// make sure we don't re-enter ourselves.
if (!_processingQueuedEvent)
{
// Make sure we process any pending Stylus Input before this mouse event.
InputManagerProcessInput(null);
}
}
}
else
{
bool cancelMouseEvent = false;
bool markHandled = true;
// If Stylus is in range then it will be driving the mouse. Ignore any mouse input.
if (_stylusDeviceInRange)
{
cancelMouseEvent = true;
// We can't mark left and right mouse buttons as handled since it will stop the
// DefWindowProc from being processed but we Cancel it which stops mouse from processing
// it. Move's though we need to eat.
if ((actions & (RawMouseActions.Button1Press | RawMouseActions.Button2Press)) == 0)
{
markHandled = false;
}
}
// If we see only a mouse move related action while the stylus is in range then
// eat it or try to defer it if not currently in range to see if we come into range.
else if ((actions & ~(RawMouseActions.AbsoluteMove | RawMouseActions.QueryCursor)) == 0)
{
if (DeferMouseMove(mouseInputReport))
{
cancelMouseEvent = true;
}
else
{
// If we now think we're going in range then eat this mouse event
if (_lastMouseMoveFromStylus && ShouldConsiderStylusInRange(mouseInputReport))
{
SendDeferredMouseEvent(false); // Make sure we clear any deferred mouse events now.
cancelMouseEvent = true;
}
// We're now allowing this mouse event (and deferred one) to be processed...
else
{
// It's a Synchronize that we are letting through so set stylus was not last move anymore.
_lastMouseMoveFromStylus = false;
// See if we are dealing with a second mouse event,
// if so force the original one it to be processed first.
if (!_triedDeferringMouseMove)
SendDeferredMouseEvent(true);
// CurrentStylusDevice is not in range and we're seeeing mouse messages
// that are not from wisptis, time to set IsStylusOver to null
if (CurrentStylusDevice != null)
{
// No current stylus device anymore either.
SelectStylusDevice(null, null, true);
}
}
}
}
// If we see a down and have a cached move then let them both go through
else
{
// We see a mouse button 1 or 2 down/up. If we have a cache then dump it and mark that we've
// seen mouse input.
_lastMouseMoveFromStylus = false;
SendDeferredMouseEvent(true);
// CurrentStylusDevice is not in range and we're seeeing mouse messages
// that are not from wisptis, time to set IsStylusOver to null
if (CurrentStylusDevice != null)
{
// No current stylus device anymore either.
SelectStylusDevice(null, null, true);
}
}
// See if we wanted to eat this mouse event...
if (cancelMouseEvent)
{
e.Cancel(); // abort this input
if (markHandled)
{
input.Handled = true; // We also don't want MouseDevice processing this.
}
}
}
}
// Some other real mouse only generated event came through...
else
{
// Make sure it's only the ones we know should come through.
Debug.Assert((actions & ~(RawMouseActions.Button3Press | RawMouseActions.Button3Release |
RawMouseActions.Button4Press | RawMouseActions.Button4Release |
RawMouseActions.Button5Press | RawMouseActions.Button5Release |
RawMouseActions.VerticalWheelRotate |
RawMouseActions.HorizontalWheelRotate)) == 0);
// If we are not in range then make sure we update our state.
// Otherwise we just let this event go through to the MouseDevice.
if (!_stylusDeviceInRange)
{
// We are letting this move through so set stylus was not last move anymore.
_lastMouseMoveFromStylus = false;
// Dump cache!
SendDeferredMouseEvent(true);
// CurrentStylusDevice is not in range and we're seeeing mouse messages
// that are not from wisptis, time to set IsStylusOver to null
if (CurrentStylusDevice != null)
{
// We now don't have a current stylus device.
SelectStylusDevice(null, null, true);
}
}
else
{
// Make sure to dump the cached mouse event if we are in
// range to make sure this mouse event is at the right spot!
SendDeferredMouseEvent(true);
}
}
}
else
{
_lastMouseMoveFromStylus = false;
}
}
else
{
// This event is marked as coming from a StylusDevice so make sure we update flag that we saw mouse event from stylus.
_lastMouseMoveFromStylus = true;
RawMouseInputReport rawMouseInputReport = (RawMouseInputReport)input.Report;
StylusDevice stylusDevice = ((StylusDevice)input.Device);
if (!stylusDevice.InRange && rawMouseInputReport._isSynchronize)
{
// eat this one because it is from an activate.
e.Cancel();
input.Handled = true;
}
}
}
else if (input.Report.Type == InputType.Stylus)
{
RawStylusInputReport stylusInputReport = (RawStylusInputReport)input.Report;
WispStylusDevice stylusDevice = stylusInputReport?.StylusDevice?.As<WispStylusDevice>(); // RTI sets this if it finds StylusDevice based on Id.
bool cancelInput = true; // Only process if we see we have valid input data.
if (stylusInputReport.InputSource != null && stylusInputReport.PenContext != null)
{
if (stylusDevice == null)
{
// look up stylus device, select it in the Stylus, and claim input for it
stylusDevice = FindStylusDevice(stylusInputReport.StylusDeviceId);
// Try refreshing tablets if we failed to find this stylus device.
if (stylusDevice == null)
{
stylusDevice = WispTabletDevices.UpdateStylusDevices(
stylusInputReport.TabletDeviceId,
stylusInputReport.StylusDeviceId);
}
stylusInputReport.StylusDevice = stylusDevice.StylusDevice; // update stylusdevice.
}
_triedDeferringMouseMove = false; // reset anytime we see stylus input.
// See if this is the special InRange input report that we use to track queued inrange
// events so that we can better filter out bogus mouse input.
if (stylusInputReport.Actions == RawStylusActions.InRange && stylusInputReport.Data == null)
{
stylusInputReport.PenContext.DecrementQueuedInRangeCount();
e.Cancel();
input.Handled = true;
_lastInRangeTime = Environment.TickCount;
return;
}
// See if this is the special DoubleTap Gesture input report. We use this
// event to know when we won't get the tap or drag gesture while the stylus
// is down. This allows us to detect and generate the Drag gesture on our own.
if (stylusInputReport.Actions == RawStylusActions.SystemGesture && stylusDevice != null)
{
RawStylusSystemGestureInputReport systemGestureReport = (RawStylusSystemGestureInputReport)stylusInputReport;
if (systemGestureReport.SystemGesture == RawStylusSystemGestureInputReport.InternalSystemGestureDoubleTap)
{
stylusDevice.SeenDoubleTapGesture = true;
e.Cancel();
input.Handled = true;
return;
}
}
if (stylusDevice != null && IsValidStylusAction(stylusInputReport))
{
cancelInput = false; // We can process this event - don't cancel!
// See if a static gesture can be generated
WispTabletDevice tabletDevice = stylusDevice.TabletDevice?.As<WispTabletDevice>();
if (tabletDevice != null)
{
SystemGesture? systemGesture = tabletDevice.GenerateStaticGesture(stylusInputReport);
if (systemGesture != null)
{
GenerateGesture(stylusInputReport, systemGesture.Value);
}
}
// See if we need to generate a tap gesture.
if (stylusInputReport.Actions == RawStylusActions.Up)
{
if (!stylusDevice.GestureWasFired)
{
GenerateGesture(stylusInputReport, stylusDevice.LastTapBarrelDown ? SystemGesture.RightTap : SystemGesture.Tap);
}
if (!_inDragDrop && !stylusInputReport.PenContext.Contexts.IsWindowDisabled)
{
// We need to process a MouseMove before promoting a MouseUp (in PromoteMainToMouse)
// since the stylus updates the button states for mouse to up then.
// Note: The Stylus Up is at the same location as the last stylus move so this is OK to do here.
ProcessMouseMove(stylusDevice, stylusInputReport.Timestamp, false);
}
}
input.Device = stylusDevice.StylusDevice;
}
}
if (cancelInput)
{
e.Cancel(); // Don't process this bogus event any further.
}
}
}
}
}
}
/////////////////////////////////////////////////////////////////////
private void PreNotifyInput(object sender, NotifyInputEventArgs e)
{
if (e.StagingItem.Input.RoutedEvent == InputManager.PreviewInputReportEvent)
{
InputReportEventArgs inputReportEventArgs = e.StagingItem.Input as InputReportEventArgs;
if (!inputReportEventArgs.Handled && inputReportEventArgs.Report.Type == InputType.Stylus)
{
RawStylusInputReport rawStylusInputReport = (RawStylusInputReport)inputReportEventArgs.Report;
WispStylusDevice stylusDevice = rawStylusInputReport.StylusDevice?.As<WispStylusDevice>();
// StylusDevice could have been disposed internally here.
if (stylusDevice?.IsValid ?? false)
{
// update stylus device state (unless this is exclusively system gesture or
// in-range/out-of-range event - which don't carry much info)
switch (rawStylusInputReport.Actions)
{
case RawStylusActions.SystemGesture:
stylusDevice.UpdateStateForSystemGesture(
(RawStylusSystemGestureInputReport)rawStylusInputReport);
break;
case RawStylusActions.OutOfRange:
_lastInRangeTime = Environment.TickCount;
stylusDevice.UpdateInRange(false, rawStylusInputReport.PenContext);
UpdateIsStylusInRange(false);
break;
case RawStylusActions.InRange:
_lastInRangeTime = Environment.TickCount;
stylusDevice.UpdateInRange(true, rawStylusInputReport.PenContext);
stylusDevice.UpdateState(rawStylusInputReport);
UpdateIsStylusInRange(true);
break;
default: // InAirMove, Down, Move, Up go through here.
stylusDevice.UpdateState(rawStylusInputReport);
break;
}
// Can only update Over state if not in a DragDrop operation!!
if (!_inDragDrop && !rawStylusInputReport.PenContext.Contexts.IsWindowDisabled && !stylusDevice.IgnoreStroke)
{
Point position = stylusDevice.GetRawPosition(null);
position = DeviceUnitsFromMeasureUnits(stylusDevice.CriticalActiveSource, position); // change back to device coords.
IInputElement target = stylusDevice.FindTarget(stylusDevice.CriticalActiveSource, position);
SelectStylusDevice(stylusDevice, target, true);
}
else
{
SelectStylusDevice(stylusDevice, null, false); // don't update over.
}
// If this is a stylus down and we don't have a valid target then the stylus went down
// on the wrong window (a transparent window handling bug in wisptis). In this case
// we want to ignore all stylus input until after the next stylus up.
if (rawStylusInputReport.Actions == RawStylusActions.Down && stylusDevice.Target == null)
{
stylusDevice.IgnoreStroke = true;
}
// Tell the InputManager that the MostRecentDevice is us.
_inputManager.MostRecentInputDevice = stylusDevice.StylusDevice;
// Verify that we sent the real time stylus events to the proper plugincollection.
VerifyStylusPlugInCollectionTarget(rawStylusInputReport);
}
}
}
// During the PreviewStylusDown event, we update the tap count, if there are
// multiple "quick" taps in approximately the "same" location (as defined
// by the hosting environment, aka the registry).
if (e.StagingItem.Input.RoutedEvent == Stylus.PreviewStylusDownEvent)
{
StylusEventArgs stylusDownEventArgs = e.StagingItem.Input as StylusDownEventArgs;
WispStylusDevice stylusDevice = stylusDownEventArgs.StylusDeviceImpl.As<WispStylusDevice>();
//
if (stylusDevice != null && stylusDevice.IsValid)
{
Point ptClient = stylusDevice.GetRawPosition(null);
WispTabletDevice tabletDevice = stylusDevice.TabletDevice.As<WispTabletDevice>();
// determine barrel state...
bool bBarrelPressed = false;
int barrelPos =
tabletDevice.StylusPointDescription.GetButtonBitPosition(StylusPointProperties.BarrelButton);
if (barrelPos != -1
&& stylusDevice.StylusButtons[barrelPos].StylusButtonState == StylusButtonState.Down)
{
bBarrelPressed = true;
}
Point pPixelPoint = DeviceUnitsFromMeasureUnits(stylusDevice.CriticalActiveSource, ptClient);
Point pLastPixelPoint = DeviceUnitsFromMeasureUnits(stylusDevice.CriticalActiveSource, stylusDevice.LastTapPoint);
// How long since the last click? (deals with tickcount wrapping too)
// Here's some info on how this works...
// int.MaxValue - int.MinValue = -1 (subtracting any negative # from MaxValue keeps this negative)
// int.MinValue - int.MaxValue = 1 (subtracting any positive # from MinValue keeps this positive)
// So as the values get farther apart from MaxInt and MinInt the difference grows which is what we want.
// We use Abs to ensure if we get older time coming through here (not expected) we'll do better
// at filtering it out if delta is greater than the double tap time. We should always see
// MinInt - MaxInt which will produce a positive number when wrapping happens.
int timeSpan = Math.Abs(unchecked(stylusDownEventArgs.Timestamp - stylusDevice.LastTapTime));
// Is the delta coordinates of this tap close enough to the last tap?
Size doubleTapSize = tabletDevice.DoubleTapSize;
bool isSameSpot = (Math.Abs(pPixelPoint.X - pLastPixelPoint.X) < doubleTapSize.Width) &&
(Math.Abs(pPixelPoint.Y - pLastPixelPoint.Y) < doubleTapSize.Height);
// Now check everything to see if this is a multi-click.
if (timeSpan < DoubleTapDeltaTime
&& isSameSpot
&& (bBarrelPressed == stylusDevice.LastTapBarrelDown))
{
// Yes, increment the count
stylusDevice.TapCount++;
}
else
{
// No, not a multi-click, reset everything.
stylusDevice.TapCount = 1;
stylusDevice.LastTapPoint = new Point(ptClient.X, ptClient.Y);
stylusDevice.LastTapTime = stylusDownEventArgs.Timestamp;
stylusDevice.LastTapBarrelDown = bBarrelPressed;
}
// DevDiv:1135009
// When the touch stack is enabled, it eats all promoted Win32/Wisp mouse input
// in favor of its own input (and generated mouse events). This does not stop
// Windows messages from arriving into the mouse stack. In the case where Windows
// promotes a touch down into a mouse move/click, the mouse stack will record a
// last location for the mouse. The touch down will also record the same location
// and use that to replay a mouse move into the mouse input stack. If the mouse
// was previously outside of the WPF window and the window was not activated, the
// mouse will have a null mouseover object. When WPF replays this move, there will
// be no hit test as the mouse stack will compare the previous hardware point with
// the point sent in by the simulated move and see no change has occured. Therefore,
// the generated click message from the touch stack will not forward to any object
// and no capture will be enabled for the mouse. This means that if the mouse/stylus
// has been used to touch outside of a WPF window (and deactivate the window), there
// will be no click raised if the next touch down occurs on a button in the deactivated
// WPF window. To fix this issue, we force a synchronization (and therefore a global
// hit test) on the preview touch down in order to make sure the mouseover has been
// properly updated by the time a move/click message has been generated by the touch stack.
// Make sure to update the mouse location on stylus down.
ProcessMouseMove(stylusDevice, stylusDownEventArgs.Timestamp, true);
}
}
}
/////////////////////////////////////////////////////////////////////
private void PostProcessInput(object sender, ProcessInputEventArgs e)
{
//only sync with mouse capture if we're enabled, or else there are no tablet devices
//hence no input. We have to work around this because getting the
//Tablet.TabletDevices will load Penimc.dll.
if (_inputEnabled)
{
// Watch the LostMouseCapture and GotMouseCapture events to keep stylus capture in sync.
if (e.StagingItem.Input.RoutedEvent == Mouse.LostMouseCaptureEvent ||
e.StagingItem.Input.RoutedEvent == Mouse.GotMouseCaptureEvent)
{
#if MULTICAPTURE
var mouseStylusDevice = Mouse.PrimaryDevice.StylusDevice;
#endif
// Make sure mouse and stylus capture is the same.
foreach (TabletDevice tabletDevice in TabletDevices)
{
foreach (StylusDevice stylusDevice in tabletDevice.StylusDevices)
{
#if MULTICAPTURE
if (stylusDevice == mouseStylusDevice)
{
#endif
// We use the Mouse device state for each call just in case we
// get reentered in the middle of changing so when we continue
// we'll use the current mouse capture state (which should NOP).
stylusDevice.Capture(Mouse.Captured, Mouse.CapturedMode);
#if MULTICAPTURE
}
#endif
}
}
}
}
if (e.StagingItem.Input.RoutedEvent == InputManager.InputReportEvent)
{
InputReportEventArgs input = e.StagingItem.Input as InputReportEventArgs;
if (!input.Handled && input.Report.Type == InputType.Stylus)
{
RawStylusInputReport report = (RawStylusInputReport)input.Report;
WispStylusDevice stylusDevice = report.StylusDevice.As<WispStylusDevice>();
if (!_inDragDrop)
{
// Only promote if the window is enabled!
if (!report.PenContext.Contexts.IsWindowDisabled)
{
PromoteRawToPreview(report, e);
// Need to reset this flag at the end of StylusUp processing.
if (report.Actions == RawStylusActions.Up)
{
stylusDevice.IgnoreStroke = false;
}
}
else
{
// We don't want to send input messages to a disabled window, but if this
// is a StylusUp action then we need to make sure that the device knows it
// is no longer active. If we don't do this, we will incorrectly think this
// device is still active, and so therefore no other touch input will be
// considered "primary" input, causing it to be ignored for most actions
// (like button clicks). (DevDiv2 520639)
if ((report.Actions & RawStylusActions.Up) != 0 && stylusDevice != null)
{
// A StylusUp to a deactivated window for a pure stylus device (pen, etc)
// could leave the StylusDevice in a bad state since we will never promote
// from raw and run the code to reset (Preview to Main promotion). As such
// we should reset state here similarly to what we do for TouchDevice. This
// allows for proper mouse state tracking in the StylusDevice in the future.
stylusDevice.ResetStateForStylusUp();
WispStylusTouchDevice touchDevice = stylusDevice.TouchDevice;
// Don't try to deactivate if the device isn't active. This can happen if
// the window was disabled for the touch-down as well, in which case we
// never activated the device and therefore don't need to deactivate it.
if (touchDevice.IsActive)
{
touchDevice.OnDeactivate();
}
}
}
}
else
{
// Previously, lifting a StylusDevice that was not the CurrentMousePromotionStylusDevice
// during a multi-touch down drag/drop would ignore the Up for that device. This was
// resulting in an invalid active devices count in StylusTouchDevice, causing subsequent
// touch interactions to never mouse promote and leaving the stack in an invalid state.
// To fix this, deactivate for stylus device up received during a drag/drop as long as they
// do not originate with the CurrentMousePromotionStylusDevice (which is the device for the
// drag/drop operation).
if (stylusDevice != null
&& stylusDevice != CurrentMousePromotionStylusDevice
&& ((report.Actions & RawStylusActions.Up) != 0))
{
WispStylusTouchDevice touchDevice = stylusDevice.TouchDevice;
// Don't try to deactivate if the device isn't active. This can happen if
// the window was disabled for the touch-down as well, in which case we
// never activated the device and therefore don't need to deactivate it.
if (touchDevice.IsActive)
{
touchDevice.OnDeactivate();
}
}
}
}
}
// If we are processing an OutOfRange event then see if we need to update the over state.
// We need to update it if mouse is already outside the window (MouseDevice.DirectlyOver
// is null) since if it has already seen the WM_MOUSELEAVE we'll never update out over
// state properly. If the WM_MOUSELEAVE comes in after we see the OutOfRange then the
// code at the end of PreProcessInput will deal that case properly.
if (e.StagingItem.Input.RoutedEvent == Stylus.StylusOutOfRangeEvent)
{
RawMouseInputReport mouseDeactivateInputReport = _mouseDeactivateInputReport;
_mouseDeactivateInputReport = null;
StylusEventArgs eventArgsOutOfRange = (StylusEventArgs)e.StagingItem.Input;
// See if we need to set the Mouse Activate flag.
PresentationSource mouseSource = _inputManager.PrimaryMouseDevice.CriticalActiveSource;
// See if we need to change the stylus over state state and send a mouse deactivate.
// We send the cached Deactivate through if we saw mouse deactivate before out of range event
// *or* for a quick move with the stylus over a window we may not even see any win32 mouse events
// so in that case we also need to deactivate the mouse since we were the ones that activated it.
if (mouseDeactivateInputReport != null || (!_seenRealMouseActivate && mouseSource != null))
{
WispStylusDevice stylusDevice = eventArgsOutOfRange.StylusDeviceImpl.As<WispStylusDevice>();
// First update the StylusDevice DirectlyOver to null if the mouse device saw a Deactivate (means
// the mouse left the window) or if it never saw a real activate (stylus mouse promotion
// caused it to be active).
stylusDevice.ChangeStylusOver(null);
// Now send the mouse deactivate
RawMouseInputReport newMouseInputReport = mouseDeactivateInputReport != null ?
new RawMouseInputReport(
mouseDeactivateInputReport.Mode,
eventArgsOutOfRange.Timestamp, // updated time
mouseDeactivateInputReport.InputSource,
mouseDeactivateInputReport.Actions,
mouseDeactivateInputReport.X,
mouseDeactivateInputReport.Y,
mouseDeactivateInputReport.Wheel,
mouseDeactivateInputReport.ExtraInformation) :
new RawMouseInputReport(
InputMode.Foreground,
eventArgsOutOfRange.Timestamp, // updated time
mouseSource,
RawMouseActions.Deactivate,
0,
0,
0,
IntPtr.Zero);
InputReportEventArgs actionsArgs = new InputReportEventArgs(stylusDevice.StylusDevice, newMouseInputReport);
actionsArgs.RoutedEvent = InputManager.PreviewInputReportEvent;
_inputManager.ProcessInput(actionsArgs);
}
}
// Deal with sending mouse events to the plugins.
// NOTE: We want to do this after the mousedevice has sent it's click through
// events (PreviewMouseDownOutsideCapturedElementEvent/PreviewMouseUpOutsideCapturedElementEvent)
// and PreviewMouse events so that we can route more accurately to where the Mouse events will
// actually get routed.
CallPlugInsForMouse(e);
PromotePreviewToMain(e);
UpdateButtonStates(e);
PromoteMainToOther(e);
// See if we need to generate a drag gesture.
if (e.StagingItem.Input.RoutedEvent == Stylus.StylusMoveEvent)
{
StylusEventArgs stylusMove = (StylusEventArgs)e.StagingItem.Input;
WispStylusDevice stylusDevice = stylusMove.StylusDeviceImpl.As<WispStylusDevice>();
if (stylusDevice.SeenDoubleTapGesture && !stylusDevice.GestureWasFired &&
stylusDevice.DetectedDrag)
{
GenerateGesture(stylusMove.InputReport, SystemGesture.Drag);
}
}
// Process the flick scroll up/down system gesture now.
if (e.StagingItem.Input.RoutedEvent == Stylus.StylusSystemGestureEvent)
{
StylusSystemGestureEventArgs stylusSystemGesture = (StylusSystemGestureEventArgs)e.StagingItem.Input;
if (stylusSystemGesture.SystemGesture == SystemGesture.Flick)
{
HandleFlick(stylusSystemGesture.ButtonState, stylusSystemGesture.StylusDevice.DirectlyOver);
}
}
// DevDiv:1078901
// As per discussions with the Wisp dev team on 1/14/15 (aliases mikkid, xiaotu, kmenon) we confirmed
// that an out of range should be the last event for any given set of stylus device input and therefore
// the last event for any given tablet device (if this is the last stylus point for the device). When
// we see the last out of range for the stylus device (and there is no more pending input), we can be
// sure the input stack no longer needs a tablet and can dispose it immediately if previously deferred.
//
// Due to stylus events being fed from a different pen thread, it is possible that another stylus event
// will enter the queue between the disposal check and the actual dispose. This is not an issue as the
// input loop is guarded against disposed tablets and the guard and this dispose are synchronous. The
// act of missing a set of messages is also acceptable as we will discard whole in-range to out of range
// sets and it will be as if that entire input set had not occured. This is fine in our disconnect
// scenario as the user experience will be consistent.
if (e.StagingItem.Input.RoutedEvent == Stylus.StylusOutOfRangeEvent)
{
var stylusArgs = e.StagingItem.Input as StylusEventArgs;
WispTabletDevice tabletDevice = stylusArgs?.StylusDeviceImpl?.TabletDevice.As<WispTabletDevice>();
if (tabletDevice.IsDisposalPending && tabletDevice.CanDispose)
{
// Update all tablets to sweep for tablets that can now be disposed
RefreshTablets();
}
}
}
/////////////////////////////////////////////////////////////////////
void PromoteRawToPreview(RawStylusInputReport report, ProcessInputEventArgs e)
{
RoutedEvent routedEvent = StylusLogic.GetPreviewEventFromRawStylusActions(report.Actions);
if (routedEvent != null && report.StylusDevice != null && !report.StylusDevice.As<WispStylusDevice>().IgnoreStroke)
{
StylusEventArgs args;
if (routedEvent != Stylus.PreviewStylusSystemGestureEvent)
{
if (routedEvent == Stylus.PreviewStylusDownEvent)
{
args = new StylusDownEventArgs(report.StylusDevice, report.Timestamp);
}
else
{
args = new StylusEventArgs(report.StylusDevice, report.Timestamp);
}
}
else
{
RawStylusSystemGestureInputReport reportSg = (RawStylusSystemGestureInputReport)report;
args = new StylusSystemGestureEventArgs(report.StylusDevice,
report.Timestamp,
reportSg.SystemGesture,
reportSg.GestureX,
reportSg.GestureY,
reportSg.ButtonState);
}
args.InputReport = report;
args.RoutedEvent = routedEvent;
e.PushInput(args, e.StagingItem);
}
}
/////////////////////////////////////////////////////////////////////
void PromotePreviewToMain(ProcessInputEventArgs e)
{
if (!e.StagingItem.Input.Handled)
{
RoutedEvent eventMain = StylusLogic.GetMainEventFromPreviewEvent(e.StagingItem.Input.RoutedEvent);
if (eventMain != null)
{
StylusEventArgs eventArgsPreview = (StylusEventArgs)e.StagingItem.Input;
StylusDevice stylusDevice = eventArgsPreview.InputReport.StylusDevice;
StylusEventArgs eventArgsMain;
if (eventMain == Stylus.StylusDownEvent ||
eventMain == Stylus.PreviewStylusDownEvent)
{
StylusDownEventArgs downEventArgsPreview = (StylusDownEventArgs)eventArgsPreview;
eventArgsMain = new StylusDownEventArgs(stylusDevice, eventArgsPreview.Timestamp);
}
else if (eventMain == Stylus.StylusButtonDownEvent ||
eventMain == Stylus.StylusButtonUpEvent)
{
StylusButtonEventArgs buttonEventArgsPreview = (StylusButtonEventArgs)eventArgsPreview;
eventArgsMain = new StylusButtonEventArgs(stylusDevice, eventArgsPreview.Timestamp, buttonEventArgsPreview.StylusButton);
}
else if (eventMain != Stylus.StylusSystemGestureEvent)
{
eventArgsMain = new StylusEventArgs(stylusDevice, eventArgsPreview.Timestamp);
}
else
{
StylusSystemGestureEventArgs previewSystemGesture = (StylusSystemGestureEventArgs)eventArgsPreview;
eventArgsMain = new StylusSystemGestureEventArgs(stylusDevice,
previewSystemGesture.Timestamp,
previewSystemGesture.SystemGesture,
previewSystemGesture.GestureX,
previewSystemGesture.GestureY,
previewSystemGesture.ButtonState);
}
eventArgsMain.InputReport = eventArgsPreview.InputReport;
eventArgsMain.RoutedEvent = eventMain;
e.PushInput(eventArgsMain, e.StagingItem);
}
}
else
{
// A TouchDevice is activated before TouchDown and deactivated after TouchUp
// But if PreviewStylusUp event is handled by the user, it will
// never be promoted to TouchUp event leaving the TouchDevice in inconsistent
// active state. Hence deactivating touch device if it is active.
StylusEventArgs stylusEventArgs = e.StagingItem.Input as StylusEventArgs;
if (stylusEventArgs?.RoutedEvent == Stylus.PreviewStylusUpEvent &&
stylusEventArgs.StylusDeviceImpl.As<WispStylusDevice>().TouchDevice.IsActive)
{
stylusEventArgs.StylusDeviceImpl.As<WispStylusDevice>().TouchDevice.OnDeactivate();
}
}
}
private void PromoteMainToOther(ProcessInputEventArgs e)
{
StagingAreaInputItem stagingItem = e.StagingItem;
StylusEventArgs stylusEventArgs = stagingItem.Input as StylusEventArgs;
if (stylusEventArgs == null)
{
return;
}
WispStylusDevice stylusDevice = stylusEventArgs.StylusDeviceImpl.As<WispStylusDevice>();
WispStylusTouchDevice touchDevice = stylusDevice.TouchDevice;
bool shouldPromoteToMouse = ShouldPromoteToMouse(stylusDevice);
if (IsTouchPromotionEvent(stylusEventArgs))
{
if (e.StagingItem.Input.Handled)
{
// A TouchDevice is activated before TouchDown and deactivated after TouchUp
// But if StylusUp event is handled by the user, it will
// never be promoted to TouchUp event leaving the TouchDevice in inconsistent
// active state. Hence deactivating touch device if it is active.
if (stylusEventArgs.RoutedEvent == Stylus.StylusUpEvent &&
touchDevice.IsActive)
{
touchDevice.OnDeactivate();
}
}
else
{
// This event is to also route as a Touch event.
// PromoteMainToMouse will eventually see the resulting
// touch event when it finishes and promote to mouse.
PromoteMainToTouch(e, stylusEventArgs);
}
}
else if (e.StagingItem.Input.RoutedEvent == Stylus.StylusSystemGestureEvent)
{
// Promote stylus system gesture to mouse if needed or
// store them if it cannot be determined that we are manipulating
// at this stage.
if (shouldPromoteToMouse)
{
if (touchDevice.PromotingToManipulation)
{
touchDevice.StoredStagingAreaItems.AddItem(stagingItem);
}
else if (touchDevice.PromotingToOther)
{
PromoteMainToMouse(stagingItem);
}
}
}
else if (shouldPromoteToMouse && touchDevice.PromotingToOther)
{
// This is not a touch event, go to mouse
PromoteMainToMouse(stagingItem);
}
}
private static bool IsTouchPromotionEvent(StylusEventArgs stylusEventArgs)
{
if (stylusEventArgs != null)
{
RoutedEvent routedEvent = stylusEventArgs.RoutedEvent;
return (IsTouchStylusDevice(stylusEventArgs.StylusDeviceImpl.As<WispStylusDevice>()) &&
(routedEvent == Stylus.StylusMoveEvent ||
routedEvent == Stylus.StylusDownEvent ||
routedEvent == Stylus.StylusUpEvent));
}
return false;
}
private static bool IsTouchStylusDevice(WispStylusDevice stylusDevice)
{
return (stylusDevice != null && stylusDevice.TabletDevice != null &&
stylusDevice.TabletDevice.Type == TabletDeviceType.Touch);
}
private void PromoteMainToTouch(ProcessInputEventArgs e, StylusEventArgs stylusEventArgs)
{
WispStylusDevice stylusDevice = stylusEventArgs.StylusDeviceImpl.As<WispStylusDevice>();
stylusDevice.UpdateTouchActiveSource();
if (stylusEventArgs.RoutedEvent == Stylus.StylusMoveEvent)
{
PromoteMainMoveToTouch(stylusDevice, e.StagingItem);
}
else if (stylusEventArgs.RoutedEvent == Stylus.StylusDownEvent)
{
PromoteMainDownToTouch(stylusDevice, e.StagingItem);
}
else if (stylusEventArgs.RoutedEvent == Stylus.StylusUpEvent)
{
PromoteMainUpToTouch(stylusDevice, e.StagingItem);
}
}
private void PromoteMainDownToTouch(WispStylusDevice stylusDevice, StagingAreaInputItem stagingItem)
{
WispStylusTouchDevice touchDevice = stylusDevice.TouchDevice;
if (touchDevice.IsActive)
{
// Deactivate and end the previous cycle if already active
touchDevice.OnDeactivate();
}
touchDevice.OnActivate();
bool shouldPromoteToMouse = ShouldPromoteToMouse(stylusDevice);
if (!touchDevice.OnDown() && shouldPromoteToMouse)
{
if (touchDevice.PromotingToManipulation)
{
touchDevice.StoredStagingAreaItems.AddItem(stagingItem);
}
else if (touchDevice.PromotingToOther)
{
PromoteMainToMouse(stagingItem);
}
}
}
private void PromoteMainMoveToTouch(WispStylusDevice stylusDevice, StagingAreaInputItem stagingItem)
{
WispStylusTouchDevice touchDevice = stylusDevice.TouchDevice;
bool shouldPromoteToMouse = ShouldPromoteToMouse(stylusDevice);
if (touchDevice.IsActive)
{
if (!touchDevice.OnMove() && shouldPromoteToMouse)
{
if (touchDevice.PromotingToManipulation)
{
StagingAreaInputItemList storedStagingItems = touchDevice.StoredStagingAreaItems;
int stagingItemCount = storedStagingItems.Count;
if (stagingItemCount > 0 &&
storedStagingItems[stagingItemCount - 1].Input.RoutedEvent == Stylus.StylusMoveEvent)
{
storedStagingItems[stagingItemCount - 1] = stagingItem;
storedStagingItems.IncrementVersion();
}
else
{
touchDevice.StoredStagingAreaItems.AddItem(stagingItem);
}
}
else if (touchDevice.PromotingToOther)
{
PromoteMainToMouse(stagingItem);
}
}
}
else if (shouldPromoteToMouse)
{
PromoteMainToMouse(stagingItem);
}
}
private void PromoteMainUpToTouch(WispStylusDevice stylusDevice, StagingAreaInputItem stagingItem)
{
WispStylusTouchDevice touchDevice = stylusDevice.TouchDevice;
bool shouldPromoteToMouse = ShouldPromoteToMouse(stylusDevice);
if (touchDevice.IsActive)
{
touchDevice.OnUp();
bool promotingToOther = touchDevice.PromotingToOther;
// PromoteMainToMouse is an outbound call that may have a nested message pump.
// Hence deactivate the touch device before calling it incase it
// turns out to be a blocking call.
if (touchDevice.IsActive) // OnUp may also pump messages and deactivate the touch device
{
touchDevice.OnDeactivate();
}
// Promote Up to Mouse if mouse left/right button is
// pressed, even if TouchUp event is handled. This is such
// that we dont leave mouse in an inconsistent pressed state.
// Also promote if this is the first Up after leaving drag/drop,
// to reset the state of tracking Moves
if (shouldPromoteToMouse && promotingToOther &&
(_mouseLeftButtonState == MouseButtonState.Pressed ||
_mouseRightButtonState == MouseButtonState.Pressed ||
_leavingDragDrop))
{
PromoteMainToMouse(stagingItem);
}
}
else if (shouldPromoteToMouse)
{
PromoteMainToMouse(stagingItem);
}
_leavingDragDrop = false;
}
internal void PromoteStoredItemsToMouse(WispStylusTouchDevice touchDevice)
{
if (!ShouldPromoteToMouse(touchDevice.StylusDevice.As<WispStylusDevice>()))
{
return;
}
int count = touchDevice.StoredStagingAreaItems.Count;
if (count > 0)
{
// copy the staging items, to avoid re-entrancy problems
StagingAreaInputItemList list = touchDevice.StoredStagingAreaItems;
StagingAreaInputItem[] storedItems = new StagingAreaInputItem[count];
list.CopyTo(storedItems, 0);
list.Clear();
// if the list's version changes during the loop, abandon the remaining
// items. This means input arrived re-entrantly.
long version = list.IncrementVersion();
for (int i = 0; i < count && version == list.Version; i++)
{
// A stored item could be a Stylus input staging item queued to be promoted to mouse
// OR a raw mouse input report staging item for Button1Press delayed from
// StylusLogic.PreProcessInput. If this staging item is of such a raw mouse
// input report call StylusDevice's PlaybackCachedDownInputReport OR else
// call PromoteMainToMouse method.
StagingAreaInputItem stagingItem = storedItems[i];
InputReportEventArgs inputReportArgs = stagingItem.Input as InputReportEventArgs;
if (inputReportArgs != null &&
inputReportArgs.Report.Type == InputType.Mouse &&
!(inputReportArgs.Device is StylusDevice))
{
touchDevice.StylusDevice.As<WispStylusDevice>().PlayBackCachedDownInputReport(inputReportArgs.Report.Timestamp);
}
else
{
PromoteMainToMouse(stagingItem);
}
}
}
}
private bool ShouldPromoteToMouse(WispStylusDevice stylusDevice)
{
if (CurrentMousePromotionStylusDevice == null ||
CurrentMousePromotionStylusDevice == stylusDevice)
{
return true;
}
return false;
}
/// <summary>
/// The stylusdevice object which should promote to mouse.
/// Should promote to mouse if this is null.
/// This could also a dummy object to avoid promotion at all.
/// </summary>
internal object CurrentMousePromotionStylusDevice
{
get;
set;
}
/////////////////////////////////////////////////////////////////////
private void PromoteMainToMouse(StagingAreaInputItem stagingItem)
{
if (!stagingItem.Input.Handled)
{
StylusEventArgs stylusArgs = stagingItem.Input as StylusEventArgs;
if (stylusArgs != null)
{
WispStylusDevice stylusDevice = stylusArgs.StylusDevice.As<WispStylusDevice>();
// We only want to promote to mouse when we actually have real stylus input.
if (stylusDevice != null)
{
Debug.Assert(ShouldPromoteToMouse(stylusDevice) && stylusDevice.TouchDevice.PromotingToOther);
if (IgnoreGestureToMousePromotion(stylusArgs as StylusSystemGestureEventArgs, stylusDevice.TouchDevice))
{
return;
}
RawMouseActions actions = stylusDevice.GetMouseActionsFromStylusEventAndPlaybackCachedDown(stagingItem.Input.RoutedEvent, stylusArgs);
if (actions != RawMouseActions.None)
{
PresentationSource mouseInputSource = stylusDevice.GetMousePresentationSource();
if (mouseInputSource != null)
{
Point pt = PointUtil.ScreenToClient(stylusDevice.LastMouseScreenPoint, mouseInputSource);
// DevDivVSO:153798
// Mouse move coalescing code has been removed from this function. This used to be needed
// since all touch moves were added to the stylus queue. Now that touch moves are themselves
// coalesced, this was wrongly cutting down all touch move to mouse move promotions by a third
// or so. This results in a poor experience for anyone relying on mouse move promotions instead
// of straight touch events.
// See if we need to set the Mouse Activate flag.
if (_inputManager.PrimaryMouseDevice.CriticalActiveSource != mouseInputSource)
{
actions |= RawMouseActions.Activate;
}
RawMouseInputReport mouseInputReport = new RawMouseInputReport(
InputMode.Foreground, stylusArgs.Timestamp, mouseInputSource,
actions, (int)pt.X, (int)pt.Y, 0, IntPtr.Zero);
InputReportEventArgs inputReportArgs = new InputReportEventArgs(stylusDevice.StylusDevice, mouseInputReport);
inputReportArgs.RoutedEvent = InputManager.PreviewInputReportEvent;
_inputManager.ProcessInput(inputReportArgs);
}
}
}
}
}
}
private bool IgnoreGestureToMousePromotion(StylusSystemGestureEventArgs gestureArgs, WispStylusTouchDevice touchDevice)
{
if (gestureArgs != null && touchDevice.DownHandled)
{
// If touchDevice's down event is already handled
// and if this gesture is a candidate for mouse
// left button down promotion, then such a
// promotion should be ignored.
SystemGesture gesture = gestureArgs.SystemGesture;
if (gesture == SystemGesture.Tap ||
gesture == SystemGesture.Drag)
{
return true;
}
}
return false;
}
/////////////////////////////////////////////////////////////////////
void CallPlugInsForMouse(ProcessInputEventArgs e)
{
if (!e.StagingItem.Input.Handled)
{
// if we see a preview mouse event that is not generated by a stylus
// then send on to plugin
if ((e.StagingItem.Input.RoutedEvent != Mouse.PreviewMouseDownEvent) &&
(e.StagingItem.Input.RoutedEvent != Mouse.PreviewMouseUpEvent) &&
(e.StagingItem.Input.RoutedEvent != Mouse.PreviewMouseMoveEvent) &&
(e.StagingItem.Input.RoutedEvent != InputManager.InputReportEvent))
return;
// record the mouse capture for later reference..
MouseDevice mouseDevice;
PresentationSource source;
bool leftButtonDown;
bool rightButtonDown;
RawStylusActions stylusActions = RawStylusActions.None;
int timestamp;
// See if we need to deal sending a leave due to this PresentationSource being Deactivated
// If not then we just return and do nothing.
if (e.StagingItem.Input.RoutedEvent == InputManager.InputReportEvent)
{
if (_activeMousePlugInCollection == null || _activeMousePlugInCollection.Element == null)
return;
InputReportEventArgs input = e.StagingItem.Input as InputReportEventArgs;
if (input.Report.Type != InputType.Mouse)
return;
RawMouseInputReport mouseInputReport = (RawMouseInputReport)input.Report;
if ((mouseInputReport.Actions & RawMouseActions.Deactivate) != RawMouseActions.Deactivate)
return;
mouseDevice = _inputManager.PrimaryMouseDevice;
// Mouse set directly over to null when truly deactivating.
if (mouseDevice == null || mouseDevice.DirectlyOver != null)
return;
leftButtonDown = mouseDevice.LeftButton == MouseButtonState.Pressed;
rightButtonDown = mouseDevice.RightButton == MouseButtonState.Pressed;
timestamp = mouseInputReport.Timestamp;
// Get presentationsource from element.
source = PresentationSource.CriticalFromVisual(_activeMousePlugInCollection.Element as Visual);
}
else
{
MouseEventArgs mouseEventArgs = e.StagingItem.Input as MouseEventArgs;
mouseDevice = mouseEventArgs.MouseDevice;
leftButtonDown = mouseDevice.LeftButton == MouseButtonState.Pressed;
rightButtonDown = mouseDevice.RightButton == MouseButtonState.Pressed;
// Only look at mouse input reports that truly come from a mouse (and is not an up or deactivate) and it
// must be pressed state if a move (we don't fire stylus inair moves currently)
if (mouseEventArgs.StylusDevice != null &&
e.StagingItem.Input.RoutedEvent != Mouse.PreviewMouseUpEvent)
return;
if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseMoveEvent)
{
if (!leftButtonDown)
return;
stylusActions = RawStylusActions.Move;
}
if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseDownEvent)
{
MouseButtonEventArgs mouseButtonEventArgs = mouseEventArgs as MouseButtonEventArgs;
if (mouseButtonEventArgs.ChangedButton != MouseButton.Left)
return;
stylusActions = RawStylusActions.Down;
}
if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseUpEvent)
{
MouseButtonEventArgs mouseButtonEventArgs = mouseEventArgs as MouseButtonEventArgs;
if (mouseButtonEventArgs.ChangedButton != MouseButton.Left)
return;
stylusActions = RawStylusActions.Up;
}
timestamp = mouseEventArgs.Timestamp;
Visual directlyOverVisual = mouseDevice.DirectlyOver as Visual;
if (directlyOverVisual == null)
{
return;
}
// Take the presentation source which is associated to the directly over element.
source = PresentationSource.CriticalFromVisual(directlyOverVisual);
}
PenContexts penContexts = GetPenContextsFromHwnd(source);
if ((penContexts != null) &&
(source != null) &&
(source.CompositionTarget != null) &&
!source.CompositionTarget.IsDisposed)
{
IInputElement directlyOver = mouseDevice.DirectlyOver;
int packetStatus = (leftButtonDown ? 1 : 0) | (rightButtonDown ? 9 : 0); // pen tip down == 1, barrel = 8
Point ptClient = mouseDevice.GetPosition(source.RootVisual as IInputElement);
ptClient = source.CompositionTarget.TransformToDevice.Transform(ptClient);
int buttons = (leftButtonDown ? 1 : 0) | (rightButtonDown ? 3 : 0);
int[] data = { (int)ptClient.X, (int)ptClient.Y, packetStatus, buttons };
RawStylusInputReport inputReport = new RawStylusInputReport(
InputMode.Foreground,
timestamp,
source,
stylusActions,
() => { return GetMousePointDescription; },
0, 0,
data);
// Avoid re-entrancy due to lock() being used.
using (Dispatcher.DisableProcessing())
{
// Call plugins (does enter/leave and FireCustomData as well)
_activeMousePlugInCollection = penContexts.InvokeStylusPluginCollectionForMouse(inputReport, directlyOver, _activeMousePlugInCollection);
}
}
}
}
/////////////////////////////////////////////////////////////////////
internal StylusPointDescription GetMousePointDescription
{
get
{
if (_mousePointDescription == null)
{
_mousePointDescription = new StylusPointDescription(
new StylusPointPropertyInfo[] {
StylusPointPropertyInfoDefaults.X,
StylusPointPropertyInfoDefaults.Y,
StylusPointPropertyInfoDefaults.NormalPressure,
StylusPointPropertyInfoDefaults.PacketStatus,
StylusPointPropertyInfoDefaults.TipButton,
StylusPointPropertyInfoDefaults.BarrelButton
},
-1); // No real pressure in data
}
return _mousePointDescription;
}
}
internal MouseButtonState GetMouseLeftOrRightButtonState(bool leftButton)
{
if (leftButton)
{
return _mouseLeftButtonState;
}
else
{
return _mouseRightButtonState;
}
}
internal bool UpdateMouseButtonState(RawMouseActions actions)
{
bool updated = false;
switch (actions)
{
case RawMouseActions.Button1Press:
if (_mouseLeftButtonState != MouseButtonState.Pressed)
{
updated = true;
_mouseLeftButtonState = MouseButtonState.Pressed;
}
break;
case RawMouseActions.Button1Release:
if (_mouseLeftButtonState != MouseButtonState.Released)
{
updated = true;
_mouseLeftButtonState = MouseButtonState.Released;
}
break;
case RawMouseActions.Button2Press:
if (_mouseRightButtonState != MouseButtonState.Pressed)
{
updated = true;
_mouseRightButtonState = MouseButtonState.Pressed;
}
break;
case RawMouseActions.Button2Release:
if (_mouseRightButtonState != MouseButtonState.Released)
{
updated = true;
_mouseRightButtonState = MouseButtonState.Released;
}
break;
}
return updated;
}
void UpdateMouseState()
{
MouseDevice mouseDevice = _inputManager.PrimaryMouseDevice;
_mouseLeftButtonState = mouseDevice.GetButtonStateFromSystem(MouseButton.Left);
_mouseRightButtonState = mouseDevice.GetButtonStateFromSystem(MouseButton.Right);
}
// We walk the list of stylus devices looking to see if any of them are in range so we
// can udpate the flag that tracks whether any stylus devices are currently in range.
private void UpdateIsStylusInRange(bool forceInRange)
{
bool foundInRangeStylusDevice = false;
if (forceInRange)
{
foundInRangeStylusDevice = true;
}
else
{
foreach (TabletDevice tabletDevice in Tablet.TabletDevices)
{
foreach (StylusDevice stylusDevice in tabletDevice.StylusDevices)
{
if (stylusDevice.InRange)
{
foundInRangeStylusDevice = true;
break;
}
}
// Exit if we found a stylusdevice.
if (foundInRangeStylusDevice) break;
}
}
// Update in range flag.
_stylusDeviceInRange = foundInRangeStylusDevice;
}
#if !MULTICAPTURE
internal override void UpdateStylusCapture(StylusDeviceBase stylusDevice, IInputElement oldStylusDeviceCapture, IInputElement newStylusDeviceCapture, int timestamp)
{
if (newStylusDeviceCapture != _stylusCapture)
{
DependencyObject o = null;
IInputElement oldCapture = _stylusCapture;
_stylusCapture = newStylusDeviceCapture;
// Adjust the handlers we use to track everything.
if (oldCapture != null)
{
o = oldCapture as DependencyObject;
if (o is UIElement uie)
{
uie.IsEnabledChanged -= _captureIsEnabledChangedEventHandler;
uie.IsVisibleChanged -= _captureIsVisibleChangedEventHandler;
uie.IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler;
}
else if (o is ContentElement ce)
{
ce.IsEnabledChanged -= _captureIsEnabledChangedEventHandler;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
//
// ce.IsVisibleChanged -= _captureIsVisibleChangedEventHandler;
// ce.IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler;
}
else if (o is UIElement3D uie3D)
{
uie3D.IsEnabledChanged -= _captureIsEnabledChangedEventHandler;
uie3D.IsVisibleChanged -= _captureIsVisibleChangedEventHandler;
uie3D.IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler;
}
else
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, oldCapture.GetType()));
}
}
if (_stylusCapture != null)
{
o = _stylusCapture as DependencyObject;
if (o is UIElement uie)
{
uie.IsEnabledChanged += _captureIsEnabledChangedEventHandler;
uie.IsVisibleChanged += _captureIsVisibleChangedEventHandler;
uie.IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler;
}
else if (o is ContentElement ce)
{
ce.IsEnabledChanged += _captureIsEnabledChangedEventHandler;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
//
// ce.IsVisibleChanged += _captureIsVisibleChangedEventHandler;
// ce.IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler;
}
else if (o is UIElement3D uie3D)
{
uie3D.IsEnabledChanged += _captureIsEnabledChangedEventHandler;
uie3D.IsVisibleChanged += _captureIsVisibleChangedEventHandler;
uie3D.IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler;
}
else
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, _stylusCapture.GetType()));
}
}
// Oddly enough, update the IsStylusCaptureWithin property first. This is
// so any callbacks will see the more-common IsStylusCaptureWithin property
// set correctly.
UIElement.StylusCaptureWithinProperty.OnOriginValueChanged(oldCapture as DependencyObject, _stylusCapture as DependencyObject, ref _stylusCaptureWithinTreeState);
// Invalidate the IsStylusCaptured properties.
if (oldCapture != null)
{
o = oldCapture as DependencyObject;
o.SetValue(UIElement.IsStylusCapturedPropertyKey, false); // Same property for ContentElements
}
if (_stylusCapture != null)
{
o = _stylusCapture as DependencyObject;
o.SetValue(UIElement.IsStylusCapturedPropertyKey, true); // Same property for ContentElements
}
}
}
internal override void UpdateOverProperty(StylusDeviceBase stylusDevice, IInputElement newOver)
{
// Only update the OverProperty for the current stylus device and only if we see a change.
if (stylusDevice == _currentStylusDevice && newOver != _stylusOver)
{
DependencyObject o = null;
IInputElement oldOver = _stylusOver;
_stylusOver = newOver;
// Adjust the handlers we use to track everything.
if (oldOver != null)
{
o = oldOver as DependencyObject;
if (o is UIElement uie)
{
uie.IsEnabledChanged -= _overIsEnabledChangedEventHandler;
uie.IsVisibleChanged -= _overIsVisibleChangedEventHandler;
uie.IsHitTestVisibleChanged -= _overIsHitTestVisibleChangedEventHandler;
}
else if (o is ContentElement ce)
{
ce.IsEnabledChanged -= _overIsEnabledChangedEventHandler;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
//
// ce.IsVisibleChanged -= _overIsVisibleChangedEventHandler;
// ce.IsHitTestVisibleChanged -= _overIsHitTestVisibleChangedEventHandler;
}
else if (o is UIElement3D uie3D)
{
uie3D.IsEnabledChanged -= _overIsEnabledChangedEventHandler;
uie3D.IsVisibleChanged -= _overIsVisibleChangedEventHandler;
uie3D.IsHitTestVisibleChanged -= _overIsHitTestVisibleChangedEventHandler;
}
else
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, oldOver.GetType()));
}
}
if (_stylusOver != null)
{
o = _stylusOver as DependencyObject;
if (o is UIElement uie)
{
uie.IsEnabledChanged += _overIsEnabledChangedEventHandler;
uie.IsVisibleChanged += _overIsVisibleChangedEventHandler;
uie.IsHitTestVisibleChanged += _overIsHitTestVisibleChangedEventHandler;
}
else if (o is ContentElement ce)
{
ce.IsEnabledChanged += _overIsEnabledChangedEventHandler;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
//
// ce.IsVisibleChanged += _overIsVisibleChangedEventHandler;
// ce.IsHitTestVisibleChanged += _overIsHitTestVisibleChangedEventHandler;
}
else if (o is UIElement3D uie3D)
{
uie3D.IsEnabledChanged += _overIsEnabledChangedEventHandler;
uie3D.IsVisibleChanged += _overIsVisibleChangedEventHandler;
uie3D.IsHitTestVisibleChanged += _overIsHitTestVisibleChangedEventHandler;
}
else
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, _stylusOver.GetType()));
}
}
// Oddly enough, update the IsStylusOver property first. This is
// so any callbacks will see the more-common IsStylusOver property
// set correctly.
UIElement.StylusOverProperty.OnOriginValueChanged(oldOver as DependencyObject, _stylusOver as DependencyObject, ref _stylusOverTreeState);
// Invalidate the IsStylusDirectlyOver property.
if (oldOver != null)
{
o = oldOver as DependencyObject;
o.SetValue(UIElement.IsStylusDirectlyOverPropertyKey, false); // Same property for ContentElements
}
if (_stylusOver != null)
{
o = _stylusOver as DependencyObject;
o.SetValue(UIElement.IsStylusDirectlyOverPropertyKey, true); // Same property for ContentElements
}
}
}
private void OnOverIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is over just became disabled.
//
// We need to resynchronize the stylus so that we can figure out who
// the stylus is over now.
ReevaluateStylusOver(null, null, true);
}
private void OnOverIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is over just became non-visible (collapsed or hidden).
//
// We need to resynchronize the stylus so that we can figure out who
// the stylus is over now.
ReevaluateStylusOver(null, null, true);
}
private void OnOverIsHitTestVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is over was affected by a change in hit-test visibility.
//
// We need to resynchronize the stylus so that we can figure out who
// the stylus is over now.
ReevaluateStylusOver(null, null, true);
}
private void OnCaptureIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is captured to just became disabled.
//
// We need to re-evaluate the element that has stylus capture since
// we can't allow the stylus to remain captured by a disabled element.
ReevaluateCapture(null, null, true);
}
private void OnCaptureIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is captured to just became non-visible (collapsed or hidden).
//
// We need to re-evaluate the element that has stylus capture since
// we can't allow the stylus to remain captured by a non-visible element.
ReevaluateCapture(null, null, true);
}
private void OnCaptureIsHitTestVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is captured to was affected by a change in hit-test visibility.
//
// We need to re-evaluate the element that has stylus capture since
// we can't allow the stylus to remain captured by a non-hittest-visible element.
ReevaluateCapture(null, null, true);
}
#endif
#if !MULTICAPTURE
internal override void ReevaluateStylusOver(DependencyObject element, DependencyObject oldParent, bool isCoreParent)
#else
private void ReevaluateStylusOver(DependencyObject element, DependencyObject oldParent, bool isCoreParent)
#endif
{
#if !MULTICAPTURE
if (element != null)
{
if (isCoreParent)
{
StylusOverTreeState.SetCoreParent(element, oldParent);
}
else
{
StylusOverTreeState.SetLogicalParent(element, oldParent);
}
}
// It would be best to re-evaluate anything dependent on the hit-test results
// immediately after layout & rendering are complete. Unfortunately this can
// lead to an infinite loop. Consider the following scenario:
//
// If the stylus is over an element, hide it.
//
// This never resolves to a "correct" state. When the stylus moves over the
// element, the element is hidden, so the stylus is no longer over it, so the
// element is shown, but that means the stylus is over it again. Repeat.
//
// We push our re-evaluation to a priority lower than input processing so that
// the user can change the input device to avoid the infinite loops, or close
// the app if nothing else works.
//
if (_reevaluateStylusOverOperation == null)
{
_reevaluateStylusOverOperation = Dispatcher.BeginInvoke(DispatcherPriority.Input, _reevaluateStylusOverDelegate, null);
}
#else
lock (__stylusDeviceLock)
{
foreach (var pair in __stylusDeviceMap)
{
pair.Value.ReevaluateStylusOver(element, oldParent, isCoreParent);
}
}
#endif
}
#if !MULTICAPTURE
/// <summary>
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private object ReevaluateStylusOverAsync(object arg)
{
_reevaluateStylusOverOperation = null;
// Synchronize causes state issues with the stylus events so we don't do this.
//if (_currentStylusDevice != null)
//{
// _currentStylusDevice.Synchronize();
//}
// Refresh StylusOverProperty so that ReverseInherited Flags are updated.
//
// We only need to do this is there is any information about the old
// tree state. This is because it is possible (even likely) that
// Synchronize() would have already done this if we hit-tested to a
// different element.
if (_stylusOverTreeState != null && !_stylusOverTreeState.IsEmpty)
{
UIElement.StylusOverProperty.OnOriginValueChanged(_stylusOver as DependencyObject, _stylusOver as DependencyObject, ref _stylusOverTreeState);
}
return null;
}
#endif
#if !MULTICAPTURE
/// <summary>
/// </summary>
internal override void ReevaluateCapture(DependencyObject element, DependencyObject oldParent, bool isCoreParent)
#else
private void ReevaluateCapture(DependencyObject element, DependencyObject oldParent, bool isCoreParent)
#endif
{
#if !MULTICAPTURE
if (element != null)
{
if (isCoreParent)
{
StylusCaptureWithinTreeState.SetCoreParent(element, oldParent);
}
else
{
StylusCaptureWithinTreeState.SetLogicalParent(element, oldParent);
}
}
// We re-evaluate the captured element to be consistent with how
// we re-evaluate the element the stylus is over.
//
// See ReevaluateStylusOver for details.
//
if (_reevaluateCaptureOperation == null)
{
_reevaluateCaptureOperation = Dispatcher.BeginInvoke(DispatcherPriority.Input, _reevaluateCaptureDelegate, null);
}
#else
lock (__stylusDeviceLock)
{
foreach (var pair in __stylusDeviceMap)
{
pair.Value.ReevaluateCapture(element, oldParent, isCoreParent);
}
}
#endif
}
#if !MULTICAPTURE
/// <summary>
///
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private object ReevaluateCaptureAsync(object arg)
{
_reevaluateCaptureOperation = null;
if (_stylusCapture == null)
return null;
bool killCapture = false;
DependencyObject dependencyObject = _stylusCapture as DependencyObject;
//
// First, check things like IsEnabled, IsVisible, etc. on a
// UIElement vs. ContentElement basis.
//
if (dependencyObject is UIElement uie)
{
killCapture = !ValidateUIElementForCapture(uie);
}
else if (dependencyObject is ContentElement ce)
{
killCapture = !ValidateContentElementForCapture(ce);
}
else if (dependencyObject is UIElement3D uie3D)
{
killCapture = !ValidateUIElement3DForCapture(uie3D);
}
else
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, _stylusCapture.GetType()));
}
//
// Second, if we still haven't thought of a reason to kill capture, validate
// it on a Visual basis for things like still being in the right tree.
//
if (killCapture == false)
{
DependencyObject containingVisual = InputElement.GetContainingVisual(dependencyObject);
killCapture = !ValidateVisualForCapture(containingVisual, CurrentStylusDevice);
}
//
// Lastly, if we found any reason above, kill capture.
//
if (killCapture)
{
Stylus.Capture(null);
}
// Refresh StylusCaptureWithinProperty so that ReverseInherited flags are updated.
//
// We only need to do this is there is any information about the old
// tree state. This is because it is possible (even likely) that
// we would have already killed capture if the capture criteria was
// no longer met.
if (_stylusCaptureWithinTreeState != null && !_stylusCaptureWithinTreeState.IsEmpty)
{
UIElement.StylusCaptureWithinProperty.OnOriginValueChanged(_stylusCapture as DependencyObject, _stylusCapture as DependencyObject, ref _stylusCaptureWithinTreeState);
}
return null;
}
#endif
/////////////////////////////////////////////////////////////////////
// Make sure that the state of the stylus is correct for the the event we are
// seeing. This validation is mainly for V1 and Lonestar wisptis events since
// it can send us InAirMove while in the middle of a down state.
// The other issue this routine handles is overlapping InRange and OutOfRange
// notifications we can get when moving between two windows (penContexts). Wisptis
// can send these in an overlapped manner which can mess up our InRange state if
// we don't special case it.
bool IsValidStylusAction(RawStylusInputReport rawStylusInputReport)
{
bool allowEvent = true;
WispStylusDevice stylusDevice = rawStylusInputReport.StylusDevice.As<WispStylusDevice>();
// See if we have the correct PenContext we are receiving input from. We can get two
// different PenContext objects actually sending us input simultaneously. We lock onto the
// the first one we see come in range and keep locked on that till we see it go out of range.
// If we go out of range and have overlapping inrange from another PenContext we will
// force that PenContext to be the current one by forcing a InRange stylus event.
// Now check for proper state of the device for the given Stylus event.
switch (rawStylusInputReport.Actions)
{
case RawStylusActions.InRange:
// only process inrange if currently out of range and the inputsource is not disposed.
allowEvent = !stylusDevice.InRange && !rawStylusInputReport.InputSource.IsDisposed;
break;
case RawStylusActions.InAirMove:
if (!stylusDevice.InRange && !rawStylusInputReport.InputSource.IsDisposed)
{
// Force InRange if stylus is out of range.
Debug.Assert(stylusDevice.InAir);
GenerateInRange(rawStylusInputReport);
}
else
{
// If InAir and either inputSource matches current devices input source or
// the last down input source then it is OK to process and we can allow this.
allowEvent = rawStylusInputReport.PenContext == stylusDevice.ActivePenContext;
}
break;
case RawStylusActions.Down:
if (!stylusDevice.InRange)
{
Debug.Assert(stylusDevice.InAir);
GenerateInRange(rawStylusInputReport);
}
else
{
allowEvent = rawStylusInputReport.PenContext == stylusDevice.ActivePenContext;
}
break;
case RawStylusActions.Move:
allowEvent = rawStylusInputReport.PenContext == stylusDevice.ActivePenContext;
break;
case RawStylusActions.Up:
allowEvent = rawStylusInputReport.PenContext == stylusDevice.ActivePenContext;
break;
case RawStylusActions.SystemGesture:
allowEvent = rawStylusInputReport.PenContext == stylusDevice.ActivePenContext;
if (allowEvent)
{
RawStylusSystemGestureInputReport systemGestureReport = (RawStylusSystemGestureInputReport)rawStylusInputReport;
// If we see a Tap gesture that is sent when we are not in the down state then
// ignore this (it's a double tap issue) since we will have generated one when
// we see the up.
if (systemGestureReport.SystemGesture == SystemGesture.Tap && stylusDevice.InAir)
{
allowEvent = false;
}
}
break;
case RawStylusActions.OutOfRange:
allowEvent = rawStylusInputReport.PenContext == stylusDevice.ActivePenContext;
break;
}
return allowEvent;
}
private void GenerateInRange(RawStylusInputReport rawStylusInputReport)
{
StylusDevice stylusDevice = rawStylusInputReport.StylusDevice;
RawStylusInputReport inputReport =
new RawStylusInputReport(rawStylusInputReport.Mode,
rawStylusInputReport.Timestamp,
rawStylusInputReport.InputSource,
rawStylusInputReport.PenContext,
RawStylusActions.InRange,
stylusDevice.TabletDevice.Id,
stylusDevice.Id,
rawStylusInputReport.Data);
InputReportEventArgs input = new InputReportEventArgs(stylusDevice, inputReport);
input.RoutedEvent = InputManager.PreviewInputReportEvent;
_inputManager.ProcessInput(input);
}
/// <summary>
/// This method handles the various windows messages related to the system setting changes.
/// </summary>
/// <param name="msg"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
internal override void HandleMessage(WindowMessage msg, IntPtr wParam, IntPtr lParam)
{
switch (msg)
{
case WindowMessage.WM_DEVICECHANGE:
if (!_inputEnabled && (uint)NativeMethods.IntPtrToInt32(wParam) == 0x0007 /* DBT_DEVNODES_CHANGED */)
{
OnDeviceChange();
}
break;
case WindowMessage.WM_DISPLAYCHANGE:
OnScreenMeasurementsChanged();
break;
case WindowMessage.WM_SETTINGCHANGE:
ReadSystemConfig(); // Update our registry settings.
// Invalidate the values so they get re-built as needed.
if (_tabletDeviceCollection != null)
{
foreach (TabletDevice tablet in _tabletDeviceCollection)
{
tablet.As<WispTabletDevice>().InvalidateSizeDeltas(); // Will be recalc'd on next stylus down.
}
}
break;
case WindowMessage.WM_TABLET_ADDED:
OnTabletAdded((uint)NativeMethods.IntPtrToInt32(wParam));
break;
case WindowMessage.WM_TABLET_DELETED:
OnTabletRemovedImpl((uint)NativeMethods.IntPtrToInt32(wParam), isInternalCall: true);
break;
}
}
internal void InvokeStylusPluginCollection(RawStylusInputReport inputReport)
{
if (inputReport.StylusDevice != null)
{
inputReport.PenContext.Contexts.InvokeStylusPluginCollection(inputReport);
}
}
private void VerifyStylusPlugInCollectionTarget(RawStylusInputReport rawStylusInputReport)
{
switch (rawStylusInputReport.Actions)
{
case RawStylusActions.Down:
case RawStylusActions.Move:
case RawStylusActions.Up:
break;
default:
return; // do nothing if not Down, Move or Up.
}
RawStylusInput originalRSI = rawStylusInputReport.RawStylusInput;
// See if we have a plugin for the target of this input.
StylusPlugInCollection targetPIC = null;
StylusPlugInCollection targetRtiPIC = (originalRSI != null) ? originalRSI.Target : null;
bool updateEventPoints = false;
// Make sure we use UIElement for target if non NULL and hit ContentElement.
UIElement newTarget = InputElement.GetContainingUIElement(rawStylusInputReport.StylusDevice.DirectlyOver as DependencyObject) as UIElement;
if (newTarget != null)
{
targetPIC = rawStylusInputReport.PenContext.Contexts.FindPlugInCollection(newTarget);
}
// Make sure any lock() calls do not reenter on us.
using (Dispatcher.DisableProcessing())
{
// See if we hit the wrong PlugInCollection on the pen thread and clean things up if we did.
if (targetRtiPIC != null && targetRtiPIC != targetPIC && originalRSI != null)
{
// Fire custom data not confirmed events for both pre and post since bad target...
foreach (RawStylusInputCustomData customData in originalRSI.CustomDataList)
{
customData.Owner.FireCustomData(customData.Data, rawStylusInputReport.Actions, false);
}
updateEventPoints = originalRSI.StylusPointsModified;
// Clear RawStylusInput data.
rawStylusInputReport.RawStylusInput = null;
}
WispStylusDevice stylusDevice = rawStylusInputReport.StylusDevice.As<WispStylusDevice>();
// See if we need to build up an RSI to send to the plugincollection (due to a mistarget).
bool sendRawStylusInput = false;
if (targetPIC != null && rawStylusInputReport.RawStylusInput == null)
{
// NOTE: PenContext info will not change (it gets rebuilt instead so keeping ref is fine)
// The transformTabletToView matrix and plugincollection rects though can change based
// off of layout events which is why we need to lock this.
GeneralTransformGroup transformTabletToView = new GeneralTransformGroup();
transformTabletToView.Children.Add(new MatrixTransform(GetTabletToViewTransform(rawStylusInputReport.InputSource, stylusDevice.TabletDevice))); // this gives matrix in measured units (not device)
transformTabletToView.Children.Add(targetPIC.ViewToElement); // Make it relative to the element.
transformTabletToView.Freeze(); // Must be frozen for multi-threaded access.
RawStylusInput rawStylusInput = new RawStylusInput(rawStylusInputReport, transformTabletToView, targetPIC);
rawStylusInputReport.RawStylusInput = rawStylusInput;
sendRawStylusInput = true;
}
// Now fire the confirmed enter/leave events as necessary.
StylusPlugInCollection currentTarget = stylusDevice.CurrentVerifiedTarget;
if (targetPIC != currentTarget)
{
if (currentTarget != null)
{
// Fire leave event. If we never had a plugin for this event then create a temp one.
if (originalRSI == null)
{
GeneralTransformGroup transformTabletToView = new GeneralTransformGroup();
transformTabletToView.Children.Add(new MatrixTransform(GetTabletToViewTransform(rawStylusInputReport.InputSource, stylusDevice.TabletDevice))); // this gives matrix in measured units (not device)
transformTabletToView.Children.Add(currentTarget.ViewToElement); // Make it relative to the element.
transformTabletToView.Freeze(); // Must be frozen for multi-threaded access.
originalRSI = new RawStylusInput(rawStylusInputReport, transformTabletToView, currentTarget);
}
currentTarget.FireEnterLeave(false, originalRSI, true);
}
if (targetPIC != null)
{
// Fire Enter event
targetPIC.FireEnterLeave(true, rawStylusInputReport.RawStylusInput, true);
// Indicate we've used a stylus plugin
Statistics.FeaturesUsed |= StylusTraceLogger.FeatureFlags.StylusPluginsUsed;
}
// Update the verified target.
stylusDevice.CurrentVerifiedTarget = targetPIC;
}
// Now fire RawStylusInput if needed to the right plugincollection.
if (sendRawStylusInput)
{
// We are on the pen thread, just call directly.
targetPIC.FireRawStylusInput(rawStylusInputReport.RawStylusInput);
updateEventPoints = (updateEventPoints || rawStylusInputReport.RawStylusInput.StylusPointsModified);
// Indicate we've used a stylus plugin
Statistics.FeaturesUsed |= StylusTraceLogger.FeatureFlags.StylusPluginsUsed;
}
// Now fire PrePreviewCustomData events.
if (targetPIC != null)
{
// Send custom data pre event
foreach (RawStylusInputCustomData customData in rawStylusInputReport.RawStylusInput.CustomDataList)
{
customData.Owner.FireCustomData(customData.Data, rawStylusInputReport.Actions, true);
}
}
// VerifyRawTarget might resend to correct plugins or may have hit the wrong plugincollection. The StylusPackets
// may be overriden in those plugins so we need to call UpdateEventStylusPoints to update things.
if (updateEventPoints)
{
rawStylusInputReport.StylusDevice.As<WispStylusDevice>().UpdateEventStylusPoints(rawStylusInputReport, true);
}
}
}
/////////////////////////////////////////////////////////////////////
internal int DoubleTapDelta
{
get
{
bool isFingerTouch = IsTouchStylusDevice(_currentStylusDevice);
return isFingerTouch ? _touchDoubleTapDelta : _stylusDoubleTapDelta;
}
}
internal int DoubleTapDeltaTime
{
get
{
bool isFingerTouch = IsTouchStylusDevice(_currentStylusDevice);
return isFingerTouch ? _touchDoubleTapDeltaTime : _stylusDoubleTapDeltaTime;
}
}
internal int CancelDelta
{
get
{
return _cancelDelta;
}
}
private void GenerateGesture(RawStylusInputReport rawStylusInputReport, SystemGesture gesture)
{
StylusDevice stylusDevice = rawStylusInputReport.StylusDevice;
System.Diagnostics.Debug.Assert(stylusDevice != null);
RawStylusSystemGestureInputReport inputReport = new RawStylusSystemGestureInputReport(
InputMode.Foreground,
rawStylusInputReport.Timestamp,
rawStylusInputReport.InputSource,
rawStylusInputReport.PenContext,
rawStylusInputReport.TabletDeviceId,
rawStylusInputReport.StylusDeviceId,
gesture,
0, // Gesture X location (only used for flicks)
0, // Gesture Y location (only used for flicks)
0); // ButtonState (only used for flicks)
inputReport.StylusDevice = stylusDevice;
InputReportEventArgs input = new InputReportEventArgs(stylusDevice, inputReport);
input.RoutedEvent = InputManager.PreviewInputReportEvent;
// Process this directly instead of doing a push. We want this event to get
// to the user before the StylusUp and MouseUp event.
InputManagerProcessInputEventArgs(input);
}
/// Before we promote a MouseUp, we need to send a MouseMove to move the mouse device
/// over to the correct location, since MouseUp does not use location information.
private void ProcessMouseMove(WispStylusDevice stylusDevice, int timestamp, bool isSynchronize)
{
System.Diagnostics.Debug.Assert(stylusDevice != null);
if (!ShouldPromoteToMouse(stylusDevice) || !stylusDevice.TouchDevice.PromotingToOther)
{
return;
}
PresentationSource mouseInputSource = stylusDevice.GetMousePresentationSource();
if (mouseInputSource != null)
{
RawMouseActions actions = RawMouseActions.AbsoluteMove;
// Don't set Activate flag if a synchronize is requested!
if (!isSynchronize)
{
if (_inputManager.PrimaryMouseDevice.CriticalActiveSource != mouseInputSource)
{
actions |= RawMouseActions.Activate;
}
}
Point pt = stylusDevice.LastMouseScreenPoint; // Use last promoted mouse location.
pt = PointUtil.ScreenToClient(pt, mouseInputSource);
RawMouseInputReport mouseInputReport =
new RawMouseInputReport(InputMode.Foreground,
timestamp,
mouseInputSource,
actions,
(int)pt.X,
(int)pt.Y,
0,
IntPtr.Zero);
if (isSynchronize)
{
mouseInputReport._isSynchronize = true;
}
InputReportEventArgs inputReportArgs = new InputReportEventArgs(stylusDevice.StylusDevice, mouseInputReport);
inputReportArgs.RoutedEvent = InputManager.PreviewInputReportEvent;
// Process this directly instead of doing a push. We want this event to get
// to the user before the StylusUp and MouseUp event.
InputManagerProcessInputEventArgs(inputReportArgs);
}
}
/////////////////////////////////////////////////////////////////////
private void UpdateButtonStates(ProcessInputEventArgs e)
{
if (!e.StagingItem.Input.Handled)
{
RoutedEvent routedEvent = e.StagingItem.Input.RoutedEvent;
if (routedEvent != null && (routedEvent == Stylus.StylusDownEvent
|| routedEvent == Stylus.StylusUpEvent
|| routedEvent == Stylus.StylusMoveEvent
|| routedEvent == Stylus.StylusInAirMoveEvent))
{
StylusEventArgs eventArgs = (StylusEventArgs)e.StagingItem.Input;
RawStylusInputReport report = eventArgs.InputReport;
StylusDevice stylusDevice = report.StylusDevice;
System.Diagnostics.Debug.Assert(stylusDevice != null);
StylusPointCollection stylusPoints = stylusDevice.GetStylusPoints(null);
StylusPoint stylusPoint = stylusPoints[stylusPoints.Count - 1];
foreach (StylusButton button in stylusDevice.StylusButtons)
{
// what if more than one button state in a single packet?
// Split the packets or only use the last one as we did below?
StylusButtonState currentButtonState =
(StylusButtonState)stylusPoint.GetPropertyValue(new StylusPointProperty(button.Guid, true));
if (currentButtonState != button.CachedButtonState)
{
button.CachedButtonState = currentButtonState;
// do work to push Button event
StylusButtonEventArgs args = new StylusButtonEventArgs(stylusDevice, report.Timestamp, button);
args.InputReport = report;
if (currentButtonState == StylusButtonState.Down)
{
args.RoutedEvent = Stylus.PreviewStylusButtonDownEvent;
}
else
{
args.RoutedEvent = Stylus.PreviewStylusButtonUpEvent;
}
// Process this directly instead of doing a push. We want this event to get
// to the user before the promoted mouse event.
InputManagerProcessInputEventArgs(args);
}
}
}
}
}
private static bool InWindowClientRect(Point ptClient, PresentationSource inputSource)
{
bool inClientRect = false;
// Note: this only works for HWNDs for now.
HwndSource source = inputSource as HwndSource;
if (source != null && source.CompositionTarget != null && !source.IsHandleNull)
{
Point ptScreen = PointUtil.ClientToScreen(ptClient, source);
IntPtr hwndHit = IntPtr.Zero;
HwndSource sourceHit = null;
Point ptClientHit = new Point(0, 0);
// Hit-test for a window.
// See if this is one of our windows.
hwndHit = UnsafeNativeMethods.WindowFromPoint((int)ptScreen.X, (int)ptScreen.Y);
if (hwndHit != IntPtr.Zero)
{
// See if this is one of our windows.
sourceHit = HwndSource.CriticalFromHwnd(hwndHit);
// We need to check if the point is over the client or
// non-client area. We only care about being over the
// client area.
if (sourceHit != null)
{
ptClientHit = PointUtil.ScreenToClient(ptScreen, sourceHit);
NativeMethods.RECT rcClient = new NativeMethods.RECT();
SafeNativeMethods.GetClientRect(new HandleRef(sourceHit, hwndHit), ref rcClient);
// Don't consider we hit anything if we are over the non-client area.
inClientRect = !((int)ptClientHit.X < rcClient.left ||
(int)ptClientHit.X >= rcClient.right ||
(int)ptClientHit.Y < rcClient.top ||
(int)ptClientHit.Y >= rcClient.bottom);
}
}
}
return inClientRect;
}
/////////////////////////////////////////////////////////////////////
internal override TabletDeviceCollection TabletDevices
{
get
{
return WispTabletDevices;
}
}
internal WispTabletDeviceCollection WispTabletDevices
{
get
{
if (_tabletDeviceCollection == null)
{
_tabletDeviceCollection = new WispTabletDeviceCollection();
// We need to know when the dispatcher shuts down in order to clean
// up references to PenThreads held in the TabletDeviceCollection.
_inputManager.Dispatcher.ShutdownFinished += _shutdownHandler;
}
return _tabletDeviceCollection;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// [TBS]
/// </summary>
internal override StylusDeviceBase CurrentStylusDevice
{
get
{
return _currentStylusDevice;
}
}
/////////////////////////////////////////////////////////////////////
internal void RegisterStylusDeviceCore(StylusDevice stylusDevice)
{
lock (__stylusDeviceLock)
{
int stylusDeviceId = stylusDevice.Id;
// The map must contain unique entries for each stylus device.
if (__stylusDeviceMap.ContainsKey(stylusDeviceId))
{
InvalidOperationException ioe = new InvalidOperationException();
// We add a tag here so we can check for this specific exception
// in TabletCollection when adding new tablet devices.
ioe.Data.Add("System.Windows.Input.StylusLogic", "");
throw (ioe);
}
__stylusDeviceMap[stylusDeviceId] = stylusDevice;
}
}
/////////////////////////////////////////////////////////////////////
internal void UnregisterStylusDeviceCore(StylusDevice stylusDevice)
{
lock (__stylusDeviceLock)
{
Debug.Assert(__stylusDeviceMap.ContainsKey(stylusDevice.Id));
__stylusDeviceMap.Remove(stylusDevice.Id);
}
}
/////////////////////////////////////////////////////////////////////
internal WispStylusDevice FindStylusDevice(int stylusDeviceId)
{
// If not on stylusLogic thread then you must take __stylusDeviceLock!!
Debug.Assert(Dispatcher.CheckAccess());
StylusDevice stylusDevice;
__stylusDeviceMap.TryGetValue(stylusDeviceId, out stylusDevice);
return stylusDevice?.As<WispStylusDevice>();
}
internal WispStylusDevice FindStylusDeviceWithLock(int stylusDeviceId)
{
StylusDevice stylusDevice;
lock (__stylusDeviceLock)
{
__stylusDeviceMap.TryGetValue(stylusDeviceId, out stylusDevice);
}
return stylusDevice?.As<WispStylusDevice>();
}
/////////////////////////////////////////////////////////////////////
// Updates the currently active stylus device and makes sure the StylusOver
// property is updated as needed.
internal void SelectStylusDevice(WispStylusDevice wispStylusDevice, IInputElement newOver, bool updateOver)
{
bool stylusDeviceChange = (_currentStylusDevice != wispStylusDevice);
WispStylusDevice oldStylusDevice = _currentStylusDevice;
// If current StylusDevice is becoming null, make sure we update the over state
// before we update _currentStylusDevice or else the over property will not update
// correctly!
#if !MULTICAPTURE
if (updateOver && wispStylusDevice == null && stylusDeviceChange)
#else
if (updateOver && wispStylusDevice == null && stylusDeviceChange && newOver == null)
#endif
{
// This will cause UpdateOverProperty() to be called.
_currentStylusDevice.ChangeStylusOver(newOver); // This should be null.
}
_currentStylusDevice = wispStylusDevice;
if (updateOver && wispStylusDevice != null)
{
// This will cause StylusLogic.UpdateStylusOverProperty to unconditionally be called.
wispStylusDevice.ChangeStylusOver(newOver);
// If changing the current stylusdevice make sure that the old one's
// over state is set to null if it is not InRange anymore.
// NOTE: We only want to do this if we have multiple stylusdevices InRange!
if (stylusDeviceChange && oldStylusDevice != null)
{
if (!oldStylusDevice.InRange)
{
oldStylusDevice.ChangeStylusOver(null);
}
}
}
}
/////////////////////////////////////////////////////////////////////
internal void EnableCore()
{
lock (__penContextsLock)
{
foreach (PenContexts contexts in __penContextsMap.Values)
{
contexts.Enable();
}
_inputEnabled = true;
}
StylusTraceLogger.LogStartup();
// Once we've logged a startup we should log shutdown when the dispatcher shuts down
// as that indicates this particular StylusLogic is going away.
ShutdownListener = new StylusLogicShutDownListener(this, ShutDownEvents.DispatcherShutdown);
}
/////////////////////////////////////////////////////////////////////
internal bool Enabled
{
get
{
return _inputEnabled;
}
}
/////////////////////////////////////////////////////////////////////
internal void RegisterHwndForInput(InputManager inputManager, PresentationSource inputSource)
{
HwndSource hwndSource = (HwndSource)inputSource;
GetAndCacheTransformToDeviceMatrix(hwndSource);
// Keep track so we don't bother looking for changes if someone happened to query this before
// an Avalon window was created where we get TabletAdd/Removed notification.
bool initializedTablets = (_tabletDeviceCollection == null);
// This causes EnableCore to be called on TabletPC systems which enabled stylus input!
WispTabletDeviceCollection tablets = WispTabletDevices;
lock (__penContextsLock)
{
if (__penContextsMap.ContainsKey(inputSource))
{
throw new InvalidOperationException(SR.PenService_WindowAlreadyRegistered);
}
PenContexts penContexts = new PenContexts(StylusLogic.GetCurrentStylusLogicAs<WispLogic>(), inputSource);
__penContextsMap[inputSource] = penContexts;
// If FIRST one set this as the one to manage TabletAdded/Removed notifications.
if (__penContextsMap.Count == 1)
{
// Make sure our view of TabletDevices is up to date we didn't just cause it
// to be initialized and we had some real tablet devices.
if (!initializedTablets && tablets.Count > 0)
{
tablets.UpdateTablets();
// Update the last known device count.
_lastKnownDeviceCount = GetDeviceCount();
}
}
// Detect if this window is disabled. If so then let the pencontexts know.
int style = UnsafeNativeMethods.GetWindowLong(new HandleRef(this, hwndSource.Handle), NativeMethods.GWL_STYLE);
if ((style & NativeMethods.WS_DISABLED) != 0)
{
penContexts.IsWindowDisabled = true;
}
if (_inputEnabled)
penContexts.Enable();
}
}
/////////////////////////////////////////////////////////////////////
internal void UnRegisterHwndForInput(HwndSource hwndSource)
{
bool shutdownWorkThread = Dispatcher.HasShutdownStarted;
// WispTabletDevice needs to schedule work on the PenThread during disposal.
// If the dispatcher is shutting down, we have to ensure that we dispose tablets
// prior to any context shutting down the needed PenThread.
if (shutdownWorkThread)
{
OnDispatcherShutdown(null, null);
}
lock (__penContextsLock)
{
PenContexts penContexts;
if (__penContextsMap.TryGetValue(hwndSource, out penContexts))
{
__penContextsMap.Remove(hwndSource);
//
// If the application dispatcher is being shut down, we should destroy our pen thread as well.
penContexts.Disable(shutdownWorkThread);
// Make sure we remember the last location of this window for mapping stylus input later.
if (UnsafeNativeMethods.IsWindow(new HandleRef(hwndSource, hwndSource.Handle)))
{
penContexts.DestroyedLocation = PointUtil.ClientToScreen(new Point(0, 0), hwndSource);
}
}
// If we failed to find penContexts for this window above then throw an error now.
if (penContexts == null)
{
throw new InvalidOperationException(SR.PenService_WindowNotRegistered);
}
}
}
/////////////////////////////////////////////////////////////////////
internal PenContexts GetPenContextsFromHwnd(PresentationSource presentationSource)
{
// Only safe to call from UI thread since only it will change Map.
Debug.Assert(Dispatcher.CheckAccess());
PenContexts penContexts = null;
if (presentationSource != null)
{
__penContextsMap.TryGetValue(presentationSource, out penContexts);
}
return penContexts;
}
internal bool ShouldConsiderStylusInRange(RawMouseInputReport mouseInputReport)
{
int timestamp = mouseInputReport.Timestamp;
// First check to see if we are close to the last time we've had a Stylus InRange
// We consider it inrange if _lastInRangeTime+500 >= timestamp (whether we've seen
// one within last 500ms).
// Here's some info on how this works...
// int.MaxValue - int.MinValue = -1 (subtracting any negative # from MaxValue keeps this negative)
// int.MinValue - int.MaxValue = 1 (subtracting any positive # from MinValue keeps this positive)
// So as values close to MaxValue and MinValue get farther apart the result increases from a
// of delta 1 and we can thus take the Abs value to use for this check.
// Note: we don't really care if times wrap since the worst thing that would happen
// is we'd let a mouse event slip through as you brought the stylus in range which
// can happen today anyway.
if (Math.Abs(unchecked(timestamp - _lastInRangeTime)) <= 500)
return true;
HwndSource hwndSource = mouseInputReport.InputSource as HwndSource;
if (hwndSource != null)
{
PenContexts penContexts = GetPenContextsFromHwnd(hwndSource);
if (penContexts != null)
{
return penContexts.ConsiderInRange(timestamp);
}
}
return false;
}
/////////////////////////////////////////////////////////////////////
internal PenContext GetStylusPenContextForHwnd(PresentationSource presentationSource, int tabletDeviceId)
{
// Only safe to call from UI thread since only it will change Map.
Debug.Assert(Dispatcher.CheckAccess());
if (presentationSource != null)
{
PenContexts penContexts;
__penContextsMap.TryGetValue(presentationSource, out penContexts);
if (penContexts != null)
{
return penContexts.GetTabletDeviceIDPenContext(tabletDeviceId);
}
}
return null;
}
/// <summary>
/// A method handles WM_DEVICECHANGE message.
/// </summary>
private void OnDeviceChange()
{
Debug.Assert(!_inputEnabled, "StylusLogic has been enabled unexpectly.");
if (!_inputEnabled && WispTabletDeviceCollection.ShouldEnableTablets())
{
// Create the tablet device collection!
WispTabletDevices.UpdateTablets();
// Enable stylus input on all hwnds if we have not yet done so.
EnableCore();
// Update the last known device count.
_lastKnownDeviceCount = GetDeviceCount();
}
}
private void OnTabletAdded(uint wisptisIndex)
{
lock (__penContextsLock)
{
WispTabletDeviceCollection tabletDeviceCollection = WispTabletDevices;
// When we receive the first WM_TABLET_ADDED message without being enabled,
// we have to update our TabletDevices at once and enable StylusLogic
if (!_inputEnabled)
{
tabletDeviceCollection.UpdateTablets(); // Create the tablet device collection!
EnableCore(); // Go and enable input now.
// Update the last known device count.
_lastKnownDeviceCount = GetDeviceCount();
return; // We are done here.
}
// Update the last known device count.
_lastKnownDeviceCount = GetDeviceCount();
uint tabletIndex = UInt32.MaxValue;
// HandleTabletAdded returns true if we need to update contexts due to a change in tablet devices.
if (tabletDeviceCollection.HandleTabletAdded(wisptisIndex, ref tabletIndex))
{
if (tabletIndex != UInt32.MaxValue)
{
// Update all contexts with this new tablet device.
foreach (PenContexts contexts in __penContextsMap.Values)
{
contexts.AddContext(tabletIndex);
}
}
else
{
// DevDiv:1078091
// Changed to use refactored code
RefreshTablets();
}
}
}
}
// Published documentation
// http://msdn.microsoft.com/en-us/library/vstudio/dd901337(v=vs.90).aspx
// http://msdn.microsoft.com/en-us/library/vstudio/ee230087(v=vs.100).aspx
// http://msdn.microsoft.com/en-us/library/vstudio/ee230087.aspx
// suggests calling this method via reflection in order to disable the real-time
// stylus. A previous bug-fix broke this scenario by rebuilding
// the tablet collection if this method detects an inconsistent call -
// which is just what the MSDN suggestion does. To fix 659672 and still support
// the MSDN suggestion, we see if the caller is internal. If so (the
// normal case), we rebuild the tablet collection if necessary. If not
// (the MSDN reflection case), we simply remove the tablet without checking.
// Also, mark this method "no-inline", so that it remains visible to apps
// via reflection.
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
protected override void OnTabletRemoved(uint wisptisIndex)
{
OnTabletRemovedImpl(wisptisIndex, isInternalCall: false);
}
private void OnTabletRemovedImpl(uint wisptisIndex, bool isInternalCall)
{
// Nothing to do if the Stylus hasn't been enabled yet.
if (_inputEnabled)
{
lock (__penContextsLock)
{
if (_tabletDeviceCollection != null)
{
// Tablet notifications can arrive in the wrong
// order. If so, using wisptisIndex can remove the wrong
// tablet device and disable touch input. To avoid this,
// we rebuild TabletDevices from scratch if the notification
// appears suspicious. "Suspicious" means (a) this is a
// real notification (not a call via reflection, as decribed
// in the previous method), and (b) the device count hasn't
// decreased by 1 (or we can't tell).
// Sometimes the index sent from windows is incorrect even if
// the device count properly checks out. In these scenarios,
// we fail to remove the device as the index will be out of
// bounds of the current tablets. This can lead to problems
// when we try to reactive tablets/contexts in the future as
// we may attempt to use the leftover device. To fix this, make
// sure that we detect the scenario and call RefreshTablets to
// do a full sync.
int currentDeviceCount = GetDeviceCount();
if (isInternalCall &&
(_lastKnownDeviceCount < 0 ||
currentDeviceCount != _lastKnownDeviceCount - 1 ||
wisptisIndex >= TabletDevices.Count))
{
// DevDiv:1078091
// Changed to use refactored code
RefreshTablets();
if (!_inputEnabled)
{
// This call can never be executed (_inputEnabed never
// changes from true to false). Its purpose is
// to keep OnTabletRemoved from being marked as
// unreachable and optimized out of existence.
// That would break the MSDN reflection scenario.
// Just to be safe, pass in a parameter that results
// in a no-op; even if it is called, nothing happens.
OnTabletRemoved(UInt32.MaxValue);
}
}
else
{
int numDeferredTablets = _tabletDeviceCollection.DeferredTablets.Count;
// remove the affected device
uint tabletIndex = _tabletDeviceCollection.HandleTabletRemoved(wisptisIndex);
// DevDiv:1078091
// Only shut the context down if this tablet has not been placed on
// the deferred list. Otherwise, we still need to receive new messages.
if (tabletIndex != UInt32.MaxValue
&& _tabletDeviceCollection.DeferredTablets.Count == numDeferredTablets)
{
foreach (PenContexts contexts in __penContextsMap.Values)
{
contexts.RemoveContext(tabletIndex);
}
}
}
_lastKnownDeviceCount = currentDeviceCount;
}
}
}
}
/// <summary>
/// Refreshes all tablets and syncs them to what is being track in Wisp.
/// This shuts down all contexts and will bring them back up for any
/// tablet that is not immediately disposed of.
///
/// DevDiv:1078091
/// Refactoring this out of the previous function (OnTabletRemovedImpl)
/// so this can be called independently of a wisp index.
/// </summary>
private void RefreshTablets()
{
// rebuild all contexts and tablet collection
foreach (PenContexts contexts in __penContextsMap.Values)
{
contexts.Disable(shutdownWorkerThread: false);
}
WispTabletDevices.UpdateTablets();
foreach (PenContexts contexts in __penContextsMap.Values)
{
contexts.Enable();
}
}
private int GetDeviceCount()
{
PenThread penThread = null;
// Get a PenThread by mimicking a subset of the code in TabletDeviceCollection.UpdateTablets().
TabletDeviceCollection tabletDeviceCollection = TabletDevices;
if (tabletDeviceCollection != null && tabletDeviceCollection.Count > 0)
{
penThread = tabletDeviceCollection[0].As<WispTabletDevice>().PenThread;
}
if (penThread != null)
{
// Use the PenThread to get the full, unfiltered tablets info to see how many there are.
TabletDeviceInfo[] tabletdevices = penThread.WorkerGetTabletsInfo();
return tabletdevices.Length;
}
else
{
// if there's no PenThread yet, return "unknown"
return -1;
}
}
/////////////////////////////////////////////////////////////////////
private void OnScreenMeasurementsChanged()
{
// We only need to have one of these queued up on our dispatcher.
if (!_updatingScreenMeasurements)
{
_updatingScreenMeasurements = true;
// Queue up this code to execute after the WM_DISPLAYCHANGED message
// has been processed
Dispatcher.BeginInvoke(DispatcherPriority.Background, _processDisplayChanged, null);
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// We get this notification when the WM_ENABLE message is sent to a window.
/// When we get this we need to disable Stylus Input from being raised similar
/// to how we deal with OLE DragDrop. Win32 stops all other input from going to
/// disabled windows so we need to do the same for stylus.
/// </summary>
internal void OnWindowEnableChanged(IntPtr hwnd, bool disabled)
{
// See if this is one of our windows.
HwndSource sourceHit = HwndSource.CriticalFromHwnd(hwnd);
// We need to check if the point is over the client or
// non-client area. We only care about being over the
// client area.
if (sourceHit != null)
{
// Find the pencontexts for this window and update it's disabled window state
PenContexts penContexts = GetPenContextsFromHwnd(sourceHit);
if (penContexts != null)
{
penContexts.IsWindowDisabled = disabled;
}
}
// See if we need to update the mouse state when going enabled.
if (!disabled && _currentStylusDevice != null)
{
// If we are in air or have not fired down the set mouse in up state.
if (_currentStylusDevice.InAir || !_currentStylusDevice.GestureWasFired)
{
_mouseLeftButtonState = MouseButtonState.Released;
_mouseRightButtonState = MouseButtonState.Released;
}
else
{
_mouseLeftButtonState = _currentStylusDevice.LeftIsActiveMouseButton ? MouseButtonState.Pressed : MouseButtonState.Released;
_mouseRightButtonState = !_currentStylusDevice.LeftIsActiveMouseButton ? MouseButtonState.Pressed : MouseButtonState.Released;
}
}
}
/////////////////////////////////////////////////////////////////////////
internal object ProcessDisplayChanged(object oInput)
{
_updatingScreenMeasurements = false;
// We don't want to rebuild the contexts and update the tabletdevice
// measurements if the Stylus hasn't been enabled yet.
if (_tabletDeviceCollection != null)
{
//
// Invalidate the screen measurements of the tablet device.
foreach (TabletDevice tablet in _tabletDeviceCollection)
{
tablet.As<WispTabletDevice>()?.UpdateScreenMeasurements();
}
}
return null;
}
/////////////////////////////////////////////////////////////////////
internal Matrix GetTabletToViewTransform(PresentationSource source, TabletDevice tabletDevice)
{
// Inking is offset under 120 DPI
// Changet the TabletToViewTransform matrix to take DPI into account. The default
// value is 96 DPI in Avalon. The device DPI value is cached after the first call
// to this function.
Matrix matrix = GetAndCacheTransformToDeviceMatrix(source);
matrix.Invert();
return matrix * tabletDevice.As<TabletDeviceBase>().TabletToScreen;
}
/// <summary>
/// Transforms a point in measure units to a point in device coordinates
/// </summary>
/// <param name="measurePoint">The point to transform, in measure units</param>
/// <returns>The point in device coordinates</returns>
internal override Point DeviceUnitsFromMeasureUnits(PresentationSource source, Point measurePoint)
{
Point pt = measurePoint * GetAndCacheTransformToDeviceMatrix(source);
pt.X = (int)Math.Round(pt.X); // Make sure we return whole numbers (pixels are whole numbers)
pt.Y = (int)Math.Round(pt.Y);
return pt;
}
/// <summary>
/// Transforms a point in measure units to a point in device coordinates
/// </summary>
/// <param name="measurePoint">The point to transform, in measure units</param>
/// <returns>The point in device coordinates</returns>
internal override Point MeasureUnitsFromDeviceUnits(PresentationSource source, Point measurePoint)
{
Matrix matrix = GetAndCacheTransformToDeviceMatrix(source);
matrix.Invert();
return measurePoint * matrix;
}
#if !MULTICAPTURE
private DeferredElementTreeState StylusOverTreeState
{
get
{
if (_stylusOverTreeState == null)
{
_stylusOverTreeState = new DeferredElementTreeState();
}
return _stylusOverTreeState;
}
}
private DeferredElementTreeState StylusCaptureWithinTreeState
{
get
{
if (_stylusCaptureWithinTreeState == null)
{
_stylusCaptureWithinTreeState = new DeferredElementTreeState();
}
return _stylusCaptureWithinTreeState;
}
}
#endif
internal class StagingAreaInputItemList : List<StagingAreaInputItem>
{
internal void AddItem(StagingAreaInputItem item)
{
Add(item);
IncrementVersion();
}
internal long Version { get { return _version; } }
internal long IncrementVersion()
{
return unchecked(++_version);
}
long _version;
}
/////////////////////////////////////////////////////////////////////
private readonly InputManager _inputManager;
DispatcherOperationCallback _dlgInputManagerProcessInput;
object _stylusEventQueueLock = new object();
Queue<RawStylusInputReport> _queueStylusEvents = new Queue<RawStylusInputReport>();
int _lastStylusDeviceId;
bool _lastMouseMoveFromStylus = true; // Default to true to help first time use issues.
private MouseButtonState _mouseLeftButtonState = MouseButtonState.Released;
private MouseButtonState _mouseRightButtonState = MouseButtonState.Released;
private StylusPlugInCollection _activeMousePlugInCollection;
private StylusPointDescription _mousePointDescription;
// From old instanced Stylus class
private EventHandler _shutdownHandler;
bool _tabletDeviceCollectionDisposed;
WispTabletDeviceCollection _tabletDeviceCollection;
WispStylusDevice _currentStylusDevice;
int _lastInRangeTime;
bool _triedDeferringMouseMove;
RawMouseInputReport _deferredMouseMove;
DispatcherOperationCallback _processDeferredMouseMove;
RawMouseInputReport _mouseDeactivateInputReport;
bool _inputEnabled = false;
bool _updatingScreenMeasurements = false;
DispatcherOperationCallback _processDisplayChanged;
readonly object __penContextsLock = new object();
Dictionary<object, PenContexts> __penContextsMap = new Dictionary<object, PenContexts>(2);
readonly object __stylusDeviceLock = new object();
Dictionary<int, StylusDevice> __stylusDeviceMap = new Dictionary<int, StylusDevice>(2);
bool _inDragDrop;
bool _leavingDragDrop;
bool _processingQueuedEvent;
bool _stylusDeviceInRange;
bool _seenRealMouseActivate;
// The wParam index to WM_TABLET_ADDED/DELETED may be invalid, since Windows
// sometimes sends these messages out of order. As a result, we can't trust that these values
// are correct. To help determine when they are invalid, we keep track of the number of tablets
// and simply do a full reset any time we get a DELETED notification without a proper change in count.
// We only need to check for this issue in DELETED because ADDED already has a check for duplicate
// or invalid index values.
// The value -1 means "unknown". We only compute this number when we actually
// have tablet devices in play, so as to avoid starting a pen thread unecessarily.
// Doing so causes problems and has been reported by varoius customers.
private int _lastKnownDeviceCount = -1;
// DevDiv: 652804
// Stores the last move report that was added to the stylus event queue per device
Dictionary<StylusDeviceBase, RawStylusInputReport> _lastMovesQueued = new Dictionary<StylusDeviceBase, RawStylusInputReport>();
// DevDiv: 652804
// Stores the move report that is currently being used to coalesce subsequent moves
Dictionary<StylusDeviceBase, RawStylusInputReport> _coalescedMoves = new Dictionary<StylusDeviceBase, RawStylusInputReport>();
/// <summary>
/// Lock the access to coalesced moves as it's possible it can be accessed simultaneously from two
/// PenThreads if the initial PenThread fills up with PenContexts.
/// </summary>
private readonly object _coalesceLock = new object();
#if !MULTICAPTURE
IInputElement _stylusCapture;
IInputElement _stylusOver;
DeferredElementTreeState _stylusOverTreeState;
DeferredElementTreeState _stylusCaptureWithinTreeState;
private DependencyPropertyChangedEventHandler _overIsEnabledChangedEventHandler;
private DependencyPropertyChangedEventHandler _overIsVisibleChangedEventHandler;
private DependencyPropertyChangedEventHandler _overIsHitTestVisibleChangedEventHandler;
private DispatcherOperationCallback _reevaluateStylusOverDelegate;
private DispatcherOperation _reevaluateStylusOverOperation;
private DependencyPropertyChangedEventHandler _captureIsEnabledChangedEventHandler;
private DependencyPropertyChangedEventHandler _captureIsVisibleChangedEventHandler;
private DependencyPropertyChangedEventHandler _captureIsHitTestVisibleChangedEventHandler;
private DispatcherOperationCallback _reevaluateCaptureDelegate;
private DispatcherOperation _reevaluateCaptureOperation;
#endif
}
}
|