Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Core / CSharp / System / Windows / Input / Stylus / StylusLogic.cs / 1 / StylusLogic.cs
using System; using System.Diagnostics; using System.Windows.Threading; using System.Windows; using System.Windows.Media; using System.Windows.Interop; using MS.Win32; // for *NativeMethods using Microsoft.Win32; // for RegistryKey class using System.Runtime.InteropServices; using System.Security; using MS.Internal; using System.Windows.Input.StylusPlugIns; using System.Security.Permissions; using System.Collections.Generic; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; using MS.Internal.PresentationCore; // SecurityHelper namespace System.Windows.Input { ///////////////////////////////////////////////////////////////////////// internal class StylusLogic: DispatcherObject { ///////////////////////////////////////////////////////////////////// ////// Critical: This code accesses critical data InputManager and /// SecurityCritical method UnsecureCurrent. /// It also returns StylusLogic which is not safe to return /// static internal StylusLogic CurrentStylusLogic { [SecurityCritical] get { return InputManager.UnsecureCurrent.StylusLogic; } } ///////////////////////////////////////////////////////////////////// ////// Critical - Calls a critical method - PreProcessInput /// - Accesses critical data _inputManager.Value /// [SecurityCritical] internal StylusLogic(InputManager inputManager) { _inputManager = new SecurityCriticalData(inputManager);; _inputManager.Value.PreProcessInput += new PreProcessInputEventHandler(PreProcessInput); _inputManager.Value.PreNotifyInput += new NotifyInputEventHandler(PreNotifyInput); _inputManager.Value.PostProcessInput += new ProcessInputEventHandler(PostProcessInput); _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; _shutdownHandler = new EventHandler(this.OnDispatcherShutdown); _processDisplayChanged = new DispatcherOperationCallback(ProcessDisplayChanged); _processDeferredMouseMove = new DispatcherOperationCallback(ProcessDeferredMouseMove); ReadSystemConfig(); _dlgInputManagerProcessInput = new DispatcherOperationCallback(InputManagerProcessInput); } /// /// Critical: - Call critical method: TabletDeviceCollection.DisposeTablets(). /// - Accesses SecurityCriticalData __penContextsMap and _InputManager.Value. /// [SecurityCritical] void OnDispatcherShutdown(object sender, EventArgs e) { if(_shutdownHandler != null) _inputManager.Value.Dispatcher.ShutdownFinished -= _shutdownHandler; if (_tabletDeviceCollection != null) { // Make sure lock() doesn't cause reentrancy (DisposeTablets has lock). using(Dispatcher.DisableProcessing()) { // 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; } _currentStylusDevice = null; // no active stylus device any more. // NOTE: __penContextsMap will be cleaned up by HwndSource Dispose() so we don't worry about that. } ////// Grab the defualts from the registry for the tap and mouse fequency/resolution /// ////// Critical - Asserts read registry permission... /// - TreatAsSafe boundry is the constructor /// - called by constructor /// [SecurityCritical] private void ReadSystemConfig() { object obj; RegistryKey key = null; // This object has finalizer to close the key. // Acquire permissions to read the one key we care about from the registry new RegistryPermission(RegistryPermissionAccess.Read, "HKEY_CURRENT_USER\\Software\\Microsoft\\Wisp\\Pen\\SysEventParameters").Assert(); // BlessedAssert try { key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Wisp\\Pen\\SysEventParameters"); if ( key != null ) { obj = key.GetValue("DblDist"); _doubleTapDelta = (obj == null) ? _doubleTapDelta : (Int32)obj; // The default double tap distance is 15 pixels (value is given in pixels) obj = key.GetValue("DblTime"); _doubleTapDeltaTime = (obj == null) ? _doubleTapDeltaTime : (Int32)obj; // The default double tap timeout is 800ms obj = key.GetValue("Cancel"); _cancelDelta = (obj == null) ? _cancelDelta : (Int32)obj; // The default move delta is 40 (4mm) } } finally { RegistryPermission.RevertAssert(); } if ( key != null ) { key.Close(); } } ///////////////////////////////////////////////////////////////////// ////// Critical - calls security critical code (ProcessInputReport) /// [SecurityCritical] 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 ////// Critical - calls security critical code (ProcessInputReport) /// [SecurityCritical] 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 ///////////////////////////////////////////////////////////////////// ////// Critical - Calls security critical routine (InvokeStylusPluginCollection) /// - Access SecurityCritical data (_queueStylusEvents, RawStylusInputReport.PenContexts). /// - PenThreadWorker.ThreadProc() is TAS boundry /// [SecurityCritical] void ProcessInputReport(RawStylusInputReport inputReport) { // First, assign the StylusDevice (note it may still be null for new StylusDevice) inputReport.StylusDevice = FindStylusDeviceWithLock(inputReport.StylusDeviceId); // 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); } // Queue up new event. lock(_stylusEventQueueLock) { _queueStylusEvents.Enqueue(inputReport); } // post the args into dispatcher queue Dispatcher.BeginInvoke(DispatcherPriority.Input, _dlgInputManagerProcessInput, null); } ///////////////////////////////////////////////////////////////////// // this is invoked from within the Dispatcher the _inputManager is affiliated to ////// Critical - uses security critical data (_inputManager) /// calls security critical code ProcessInput which has a link demand (UIPermissionAttribute) /// TAS boundry at Synchronize and StylusLogic constructor /// [SecurityCritical] internal object InputManagerProcessInput(object oInput) { RawStylusInputReport rawStylusInputReport = null; // Now grab the queued up Stylus input reports and process them. lock(_stylusEventQueueLock) { if (_queueStylusEvents.Count > 0) { rawStylusInputReport = _queueStylusEvents.Dequeue(); } } if (rawStylusInputReport != null) { // Check to see if the Vista wisptis has set the IP_RECT_MAPPING_CHANGED bit in the packet status. // If so we need to trigger our mappings to be udpated just in case we processed this before wisptis. switch (rawStylusInputReport.Actions) { case RawStylusActions.InAirMove: case RawStylusActions.Down: //case RawStylusActions.Move: (note this can cause problems with TabletDevice.DoubleTapSize and CancelSize if handled so we don't do work here) case RawStylusActions.Up: int packetStatusIndex = rawStylusInputReport.PenContext.StylusPointDescription.GetPropertyIndex(StylusPointProperties.PacketStatus.Id); if (packetStatusIndex != -1 && rawStylusInputReport.StylusDevice != null) { int packetStatus = rawStylusInputReport.Data[packetStatusIndex]; // grab first packet to check. if ((packetStatus & 0x10) != 0) // Is IP_RECT_MAPPING_CHANGED (0x00000010) set? { using(Dispatcher.DisableProcessing()) // make sure lock() doesn't cause re-entrancy. { // Update display mappings for this TabletDevice. rawStylusInputReport.StylusDevice.TabletDevice.UpdateScreenMeasurements(); } } } break; // we don't need to process any other types of actions. } // 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; } ///////////////////////////////////////////////////////////////////// ////// Critical - calls security critical code (ProcessInput) /// - TAS boundry at StylusDevice::ChangeStylusCapture /// [SecurityCritical] internal void InputManagerProcessInputEventArgs(InputEventArgs input) { _inputManager.Value.ProcessInput(input); } ////// Critical - Accesses security critical data _deferredMouseMove. /// [SecurityCritical] 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; } ////// Critical - Accesses security critical data _deferredMouseMove. /// /// - Calls security critical method SendDeferredMouseEvent. /// /// TreatAsSafe - Just causes us to resend a mouse move event that we have /// deferred. Once sent the mouse event ref is cleared so only single /// mouse event can ever be sent. No data goes in or out. /// [SecurityCritical, SecurityTreatAsSafe] internal object ProcessDeferredMouseMove(object oInput) { // Make sure we haven't flushed the deferred event before dispatcher version processes. if (_deferredMouseMove != null) { // 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); } } return null; } ////// Critical - Accesses security critical data _deferredMouseMove and _inputManager.Value. /// /// - Calls security critical methods InputReportEventArgs /// constructor, InputManager.ProcessInput. /// /// [SecurityCritical] 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.Value.PrimaryMouseDevice, _deferredMouseMove); mouseArgs.RoutedEvent = InputManager.PreviewInputReportEvent; _deferredMouseMove = null; // Clear this out before sending. // This will cause _lastMoveFromStylus to be set to false. _inputManager.Value.ProcessInput(mouseArgs); } } // We no longer need the ref on the cached input report. _deferredMouseMove = null; } ////// Critical: calls a critical function - UpdateTarget. /// accesses e.StagingItem.Input and InputReport.InputSource and _inputManager.Value. /// It can also be used for Input spoofing. /// [SecurityCritical] 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.Value.InDragDrop) { _inDragDrop = _inputManager.Value.InDragDrop; // If we are going out of DragDrop then we need to re [....] the mouse state // if we have a stylus device in range (otherwise we [....] on the next // stylus coming in range). if (!_inDragDrop && _stylusDeviceInRange) { UpdateMouseState(); } } 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) if (TabletDevices.Count != 0) { RawMouseInputReport mouseInputReport = (RawMouseInputReport) input.Report; RawMouseActions actions = mouseInputReport.Actions; int mouseExtraInfo = NativeMethods.IntPtrToInt32(mouseInputReport.ExtraInformation); bool fromWisptis = (mouseExtraInfo & 0xFFFFFF00) == 0xFF515700; // MI_WP_SIGNATURE // 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; } else if (CurrentStylusDevice.DirectlyOver != null) { MouseDevice mouseDevice = _inputManager.Value.PrimaryMouseDevice; if (mouseDevice.CriticalActiveSource == mouseInputReport.InputSource) { // Update over to be null when deactivating. CurrentStylusDevice.ChangeStylusOver(null); } } } } // 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, cancelCaptureInputReport); args.RoutedEvent = InputManager.PreviewInputReportEvent; e.Cancel(); _inputManager.Value.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. StylusDevice 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.Value.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, activateInputReport); args.RoutedEvent = InputManager.PreviewInputReportEvent; _inputManager.Value.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.CriticalHandle : 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.Value.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_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) { // left button down...lets replay the down at this time... // Note: We may wait till later if stylus is not down yet! 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; StylusDevice stylusDevice = stylusInputReport.StylusDevice; // 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 = TabletDevices.UpdateStylusDevices( stylusInputReport.TabletDeviceId, stylusInputReport.StylusDeviceId); } stylusInputReport.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 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; } } if (cancelInput) { e.Cancel(); // Don't process this bogus event any further. } } } } } } ///////////////////////////////////////////////////////////////////// ////// Critical: Accesses SecurityCriticalData e.StagingItem.Input and _inputManager.Value. /// Calls SecurityCritical methods: StylusDevice.UpdateStateForSystemGesture, /// InputEventArgs.Handled, StylusDevice.UpdateInRange, StylusDevice.UpdateState, /// RawStylusInputReport.PenContext, SelectStylusDevice, VerifyStylusPlugInCollectionTarget, /// ProcessMouseMove, and CallPlugInsForMouse. /// It can also be used for Input spoofing. /// [SecurityCritical] 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; StylusDevice stylusDevice = rawStylusInputReport.StylusDevice; if (stylusDevice != null) { // 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); _lastRawMouseAction = RawMouseActions.None; // make sure we promote a mouse move on next event. 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(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.Value.MostRecentInputDevice = 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; StylusDevice stylusDevice = stylusDownEventArgs.StylusDevice; if (stylusDevice != null) { Point ptClient = stylusDevice.GetRawPosition(null); // determine barrel state... bool bBarrelPressed = false; int barrelPos = stylusDevice.TabletDevice.StylusPointDescription.GetButtonBitPosition(StylusPointProperties.BarrelButton); if (barrelPos != -1 && stylusDevice.StylusButtons[barrelPos].StylusButtonState == StylusButtonState.Down) { bBarrelPressed = true; } Point pPixelPoint = DeviceUnitsFromMeasureUnits(ptClient); Point pLastPixelPoint = DeviceUnitsFromMeasureUnits(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 = stylusDevice.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; } // Make sure to update the mouse location on stylus down. ProcessMouseMove(stylusDevice, stylusDownEventArgs.Timestamp, false); } } } ///////////////////////////////////////////////////////////////////// ////// Critical - calls a critical method (PromoteRawToPreview, MouseDevice.CriticalActiveSource, /// InputReport.InputSource, PromotePreviewToMain, UpdateButtonStates, /// PromoteMainToMouse and GenerateGesture) /// - accesses e.StagingItem.Input, _inputManager.Value and TabletDevices. /// It can also be used for Input spoofing. /// [SecurityCritical ] private void PostProcessInput(object sender, ProcessInputEventArgs e) { //only [....] 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 [....]. if(e.StagingItem.Input.RoutedEvent == Mouse.LostMouseCaptureEvent || e.StagingItem.Input.RoutedEvent == Mouse.GotMouseCaptureEvent) { // Make sure mouse and stylus capture is the same. foreach (TabletDevice tabletDevice in TabletDevices) { foreach (StylusDevice stylusDevice in tabletDevice.StylusDevices) { // 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(e.StagingItem.Input.RoutedEvent == InputManager.InputReportEvent && !_inDragDrop) { InputReportEventArgs input = e.StagingItem.Input as InputReportEventArgs; if(!input.Handled && input.Report.Type == InputType.Stylus) { RawStylusInputReport report = (RawStylusInputReport) input.Report; // 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) { report.StylusDevice.IgnoreStroke = false; } } } } // 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; // If we have deferred mouse moves then make sure we process last one now. if (_lastRawMouseAction == RawMouseActions.AbsoluteMove && _waitingForDelegate) { ProcessMouseMove(eventArgsOutOfRange.StylusDevice, eventArgsOutOfRange.Timestamp, false); } // See if we need to set the Mouse Activate flag. PresentationSource mouseSource = _inputManager.Value.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)) { // 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). eventArgsOutOfRange.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(eventArgsOutOfRange.StylusDevice, newMouseInputReport); actionsArgs.RoutedEvent=InputManager.PreviewInputReportEvent; _inputManager.Value.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); PromoteMainToMouse(e); // See if we need to generate a drag gesture. if(e.StagingItem.Input.RoutedEvent == Stylus.StylusMoveEvent) { StylusEventArgs stylusMove = (StylusEventArgs)e.StagingItem.Input; if (stylusMove.StylusDevice.SeenDoubleTapGesture && !stylusMove.StylusDevice.GestureWasFired && stylusMove.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); } } } ///////////////////////////////////////////////////////////////////// ////// Critical - calls a critical function ( PushInput) /// - called by PostProcessInput /// [SecurityCritical ] void PromoteRawToPreview(RawStylusInputReport report, ProcessInputEventArgs e) { RoutedEvent routedEvent = GetPreviewEventFromRawStylusActions(report.Actions); if (routedEvent != null && report.StylusDevice != null && !report.StylusDevice.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); } } ///////////////////////////////////////////////////////////////////// ////// Critical - calls a critical method ( pushInput). /// - called by PostProcessInput /// [SecurityCritical] void PromotePreviewToMain(ProcessInputEventArgs e) { if(!e.StagingItem.Input.Handled) { RoutedEvent eventMain = 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); } } } ///////////////////////////////////////////////////////////////////// ////// Critical: accesses e.StagingItem.Input /// - called by PostProcessInput /// [SecurityCritical] void PromoteMainToMouse(ProcessInputEventArgs e) { if(!e.StagingItem.Input.Handled) { StylusEventArgs stylusArgs = e.StagingItem.Input as StylusEventArgs; if (stylusArgs != null) { StylusDevice stylusDevice = stylusArgs.StylusDevice; // We only want to promote to mouse when we actually have real stylus input. if (stylusDevice != null) { RawMouseActions actions = stylusDevice.GetMouseActionsFromStylusEventAndPlaybackCachedDown(e.StagingItem.Input.RoutedEvent, stylusArgs); if (actions != RawMouseActions.None) { PresentationSource mouseInputSource = stylusDevice.GetMousePresentationSource(); if (mouseInputSource != null) { Point pt = PointUtil.ScreenToClient(stylusDevice.LastMouseScreenPoint, mouseInputSource); // // use the dispatcher as a way of coalescing mouse *move* messages // BUT don't flood the dispatcher with delegates if we're already // waiting for a callback // if ((actions & RawMouseActions.AbsoluteMove) != 0) { if (actions == _lastRawMouseAction && _waitingForDelegate) { return; // We don't need to process this one. } else { //set the waiting bit so we won't enter here again //until we get the callback _waitingForDelegate = true; Dispatcher.BeginInvoke(DispatcherPriority.Input, (DispatcherOperationCallback)delegate(object unused) { //reset our flags here in the callback. _waitingForDelegate = false; return null; }, null); } } // See if we need to set the Mouse Activate flag. if (_inputManager.Value.PrimaryMouseDevice.CriticalActiveSource != mouseInputSource) { actions |= RawMouseActions.Activate; } _lastRawMouseAction = actions; RawMouseInputReport mouseInputReport = new RawMouseInputReport( InputMode.Foreground, stylusArgs.Timestamp, mouseInputSource, actions, (int)pt.X, (int)pt.Y, 0, IntPtr.Zero); InputReportEventArgs inputReportArgs = new InputReportEventArgs(stylusDevice, mouseInputReport); inputReportArgs.RoutedEvent=InputManager.PreviewInputReportEvent; _inputManager.Value.ProcessInput(inputReportArgs); } } } } } } ///////////////////////////////////////////////////////////////////// ////// Critical: - accesses e.StagingItem.Input and PresentationSource.CriticalFromVisual /// - called by PreProcessInput /// [SecurityCritical] 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.Value.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; } // NTRAID:WINDOWSOS#1536432-2006/03/15-WAYNEZEN, // Take the presentation source which is associated to the directly over element. source = PresentationSource.CriticalFromVisual(directlyOverVisual); } PenContexts penContexts = GetPenContextsFromHwnd(source); if (penContexts != null) { 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, GetMousePointDescription, 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; } ////// Critical as this calls a critical method MouseDevice.GetButtonStateFromSystem /// and accesses Critical data _inputManager.Value. /// /// At the top called from PreProcessInput and PreNotifyInput which is SecurityCritical /// [SecurityCritical] void UpdateMouseState() { MouseDevice mouseDevice = _inputManager.Value.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; } internal void UpdateStylusCapture(StylusDevice 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 (InputElement.IsUIElement(o)) { ((UIElement)o).IsEnabledChanged -= _captureIsEnabledChangedEventHandler; ((UIElement)o).IsVisibleChanged -= _captureIsVisibleChangedEventHandler; ((UIElement)o).IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler; } else if (InputElement.IsContentElement(o)) { ((ContentElement)o).IsEnabledChanged -= _captureIsEnabledChangedEventHandler; // NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements. // // ((ContentElement)o).IsVisibleChanged -= _captureIsVisibleChangedEventHandler; // ((ContentElement)o).IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler; } else { ((UIElement3D)o).IsEnabledChanged -= _captureIsEnabledChangedEventHandler; ((UIElement3D)o).IsVisibleChanged -= _captureIsVisibleChangedEventHandler; ((UIElement3D)o).IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler; } } if (_stylusCapture != null) { o = _stylusCapture as DependencyObject; if (InputElement.IsUIElement(o)) { ((UIElement)o).IsEnabledChanged += _captureIsEnabledChangedEventHandler; ((UIElement)o).IsVisibleChanged += _captureIsVisibleChangedEventHandler; ((UIElement)o).IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler; } else if (InputElement.IsContentElement(o)) { ((ContentElement)o).IsEnabledChanged += _captureIsEnabledChangedEventHandler; // NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements. // // ((ContentElement)o).IsVisibleChanged += _captureIsVisibleChangedEventHandler; // ((ContentElement)o).IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler; } else { ((UIElement3D)o).IsEnabledChanged += _captureIsEnabledChangedEventHandler; ((UIElement3D)o).IsVisibleChanged += _captureIsVisibleChangedEventHandler; ((UIElement3D)o).IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler; } } // 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 void UpdateOverProperty(StylusDevice 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 (InputElement.IsUIElement(o)) { ((UIElement)o).IsEnabledChanged -= _overIsEnabledChangedEventHandler; ((UIElement)o).IsVisibleChanged -= _overIsVisibleChangedEventHandler; ((UIElement)o).IsHitTestVisibleChanged -= _overIsHitTestVisibleChangedEventHandler; } else if (InputElement.IsContentElement(o)) { ((ContentElement)o).IsEnabledChanged -= _overIsEnabledChangedEventHandler; // NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements. // // ((ContentElement)o).IsVisibleChanged -= _overIsVisibleChangedEventHandler; // ((ContentElement)o).IsHitTestVisibleChanged -= _overIsHitTestVisibleChangedEventHandler; } else { ((UIElement3D)o).IsEnabledChanged -= _overIsEnabledChangedEventHandler; ((UIElement3D)o).IsVisibleChanged -= _overIsVisibleChangedEventHandler; ((UIElement3D)o).IsHitTestVisibleChanged -= _overIsHitTestVisibleChangedEventHandler; } } if(_stylusOver != null) { o = _stylusOver as DependencyObject; if (InputElement.IsUIElement(o)) { ((UIElement)o).IsEnabledChanged += _overIsEnabledChangedEventHandler; ((UIElement)o).IsVisibleChanged += _overIsVisibleChangedEventHandler; ((UIElement)o).IsHitTestVisibleChanged += _overIsHitTestVisibleChangedEventHandler; } else if (InputElement.IsContentElement(o)) { ((ContentElement)o).IsEnabledChanged += _overIsEnabledChangedEventHandler; // NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements. // // ((ContentElement)o).IsVisibleChanged += _overIsVisibleChangedEventHandler; // ((ContentElement)o).IsHitTestVisibleChanged += _overIsHitTestVisibleChangedEventHandler; } else { ((UIElement3D)o).IsEnabledChanged += _overIsEnabledChangedEventHandler; ((UIElement3D)o).IsVisibleChanged += _overIsVisibleChangedEventHandler; ((UIElement3D)o).IsHitTestVisibleChanged += _overIsHitTestVisibleChangedEventHandler; } } // 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); } ////// ////// Critical: Access the security critical method - StylusLogic.CurrentStylusLogic. /// TreatAsSafe: Just calls security transparent routine ReevaluateStylusOver on StylusLogic. /// Takes no critical data as input and doesn't return any. /// [SecurityCritical, SecurityTreatAsSafe] static internal void CurrentStylusLogicReevaluateStylusOver(DependencyObject element, DependencyObject oldParent, bool isCoreParent) { StylusLogic.CurrentStylusLogic.ReevaluateStylusOver(element, oldParent, isCoreParent); } ////// internal void ReevaluateStylusOver(DependencyObject element, DependencyObject oldParent, bool isCoreParent) { 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); } } ////// /// ///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; } /// /// ////// Critical: Access the security critical method StylusLogic.CurrentStylusLogic. /// TreatAsSafe: Just calls security transparent routine ReevaluateCapture on StylusLogic. /// Takes no critical data as input and doesn't return any. /// [SecurityCritical, SecurityTreatAsSafe] static internal void CurrentStylusLogicReevaluateCapture(DependencyObject element, DependencyObject oldParent, bool isCoreParent) { StylusLogic.CurrentStylusLogic.ReevaluateCapture(element, oldParent, isCoreParent); } ////// internal void ReevaluateCapture(DependencyObject element, DependencyObject oldParent, bool isCoreParent) { 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); } } ////// /// /// ///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 (InputElement.IsUIElement(dependencyObject)) { killCapture = !ValidateUIElementForCapture((UIElement)_stylusCapture); } else if (InputElement.IsContentElement(dependencyObject)) { killCapture = !ValidateContentElementForCapture((ContentElement)_stylusCapture); } else { killCapture = !ValidateUIElement3DForCapture((UIElement3D)_stylusCapture); } // // 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); } // // 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; } private bool ValidateUIElementForCapture(UIElement element) { if(element.IsEnabled == false) return false; if(element.IsVisible == false) return false; if(element.IsHitTestVisible == false) return false; return true; } private bool ValidateContentElementForCapture(ContentElement element) { if(element.IsEnabled == false) return false; // NOTE: there is no IsVisible property for ContentElements. return true; } private bool ValidateUIElement3DForCapture(UIElement3D element) { if(element.IsEnabled == false) return false; if(element.IsVisible == false) return false; if(element.IsHitTestVisible == false) return false; return true; } /// /// Critical: This code accesses critical data(_activeSource) /// TreatAsSafe: Although it accesses critical data it does not modify or expose it, only compares against it. /// [SecurityCritical, SecurityTreatAsSafe] private bool ValidateVisualForCapture(DependencyObject visual) { if(visual == null) return false; PresentationSource presentationSource = PresentationSource.CriticalFromVisual(visual); if (presentationSource == null) { return false; } if (CurrentStylusDevice != null && CurrentStylusDevice.CriticalActiveSource != presentationSource && CurrentStylusDevice.Captured == null) { return false; } return true; } ///////////////////////////////////////////////////////////////////// // 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. ////// Critical: - asserts for UIPermission to grab the active source from the stylusdevice /// - accesses SecurityCritical data _inputManager.Value. /// - calls SecurityCritical methods (GetStylusPenContextForHwnd, StylusDevice.ActiveSource, /// HwndSource.CriticalHandle, InputReport.InputSource and InputManager.ProcessInput). /// - called by PreProcessInput /// - Not TAS since this can inject stylus events. /// [SecurityCritical] bool IsValidStylusAction(RawStylusInputReport rawStylusInputReport) { bool allowEvent = true; StylusDevice stylusDevice = rawStylusInputReport.StylusDevice; // 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: allowEvent = !stylusDevice.InRange; // only process inrange if currently out of range. break; case RawStylusActions.InAirMove: if (!stylusDevice.InRange) { // 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; } ////// Critical: - accesses SecurityCritical data (_inputManager.Value, rawStylusInputReport.Data, /// InputReport.InputSource, rawStylusInputReport.PenContext). /// - calls SecurityCritical methods (InputManager.ProcessInput). /// - Not TAS since this can inject stylus events. /// [SecurityCritical] 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.Value.ProcessInput(input); } ///////////////////////////////////////////////////////////////////// internal static int GetFlickAction(int flickData) { return (flickData & 0x001F); } private static bool GetIsScrollUp(int flickData) { Debug.Assert(GetFlickAction(flickData) == 1); // Make sure scroll action is set in flickdata. return (NativeMethods.SignedHIWORD(flickData) == 0); } // Our handler for the WM_FLICK message. internal bool HandleFlick(int flickData, IInputElement element) { bool handled = false; // By default say we didn't handle this flick action. switch (GetFlickAction(flickData)) { case 1: // Scroll // Process scroll command. RoutedUICommand command = GetIsScrollUp(flickData) ? ComponentCommands.ScrollPageUp : ComponentCommands.ScrollPageDown; if (element != null) { if (command.CanExecute(null, element)) { command.Execute(null, element); } // NTRAID-WINDOWS#1412363-2006/01/10-WAYNEZEN, // We should alway report handled if there is a potential flick element. // Otherwise, the flick UIHub will send WM_KEYXXX for the PageDown/PageUp to this window. // So the focused element will react to those messages. handled = true; // Say we handled it. } break; case 0: // Null case 2: // AppCommand case 3: // CustomKey case 4: // KeyModifier // Say we didn't handle it so UIHub will do default processing. break; default: Debug.Assert(false, "Unknown Flick Action encountered"); break; } return handled; } ////// This method handles the various windows messages related to the system setting changes. /// /// /// /// ////// Critical - Calls into SecurityCritical code (OnDeviceChange, OnScreenMeasurementsChanged, /// ReadSystemConfig, OnTabletAdded and OnTabletRemoved). /// [SecurityCritical, FriendAccessAllowed] internal void HandleMessage(int msg, IntPtr wParam, IntPtr lParam) { switch ( msg ) { case NativeMethods.WM_DEVICECHANGE: if ( !_inputEnabled && (uint)NativeMethods.IntPtrToInt32(wParam) == 0x0007 /* DBT_DEVNODES_CHANGED */) { OnDeviceChange(); } break; case NativeMethods.WM_DISPLAYCHANGE: OnScreenMeasurementsChanged(); break; case NativeMethods.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.InvalidateSizeDeltas(); // Will be recalc'd on next stylus down. } } break; case NativeMethods.WM_TABLET_ADDED: OnTabletAdded((uint)NativeMethods.IntPtrToInt32(wParam)); break; case NativeMethods.WM_TABLET_REMOVED: OnTabletRemoved((uint)NativeMethods.IntPtrToInt32(wParam)); break; } } ///////////////////////////////////////////////////////////////////// RoutedEvent GetMainEventFromPreviewEvent(RoutedEvent routedEvent) { if (routedEvent == Stylus.PreviewStylusDownEvent) return Stylus.StylusDownEvent; if (routedEvent == Stylus.PreviewStylusUpEvent) return Stylus.StylusUpEvent; if (routedEvent == Stylus.PreviewStylusMoveEvent) return Stylus.StylusMoveEvent; if (routedEvent == Stylus.PreviewStylusInAirMoveEvent) return Stylus.StylusInAirMoveEvent; if (routedEvent == Stylus.PreviewStylusInRangeEvent) return Stylus.StylusInRangeEvent; if (routedEvent == Stylus.PreviewStylusOutOfRangeEvent) return Stylus.StylusOutOfRangeEvent; if (routedEvent == Stylus.PreviewStylusSystemGestureEvent) return Stylus.StylusSystemGestureEvent; if (routedEvent == Stylus.PreviewStylusButtonDownEvent) return Stylus.StylusButtonDownEvent; if (routedEvent == Stylus.PreviewStylusButtonUpEvent) return Stylus.StylusButtonUpEvent; return null; } ///////////////////////////////////////////////////////////////////// RoutedEvent GetPreviewEventFromRawStylusActions(RawStylusActions actions) { if((actions & RawStylusActions.Down) != 0) return Stylus.PreviewStylusDownEvent; if ((actions & RawStylusActions.Up) != 0) return Stylus.PreviewStylusUpEvent; if ((actions & RawStylusActions.Move) != 0) return Stylus.PreviewStylusMoveEvent; if ((actions & RawStylusActions.InAirMove) != 0) return Stylus.PreviewStylusInAirMoveEvent; if ((actions & RawStylusActions.InRange) != 0) return Stylus.PreviewStylusInRangeEvent; if ((actions & RawStylusActions.OutOfRange) != 0) return Stylus.PreviewStylusOutOfRangeEvent; if ((actions & RawStylusActions.SystemGesture) != 0) return Stylus.PreviewStylusSystemGestureEvent; return null; } ///////////////////////////////////////////////////////////////////// ////// Critical - Calls into SecurityCritical code. /// - called by SecurityCritical code (Event handlers at top) /// [SecurityCritical] internal void InvokeStylusPluginCollection(RawStylusInputReport inputReport) { if (inputReport.StylusDevice != null) { inputReport.PenContext.Contexts.InvokeStylusPluginCollection(inputReport); } } ////// Critical - Calls in to SecurityCritical code (StylusDevice.UpdateEventStylusPoints) /// At the top called from StylusLogic::PreNotifyInput event which is SecurityCritical. /// [SecurityCritical] 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; } // 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.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 = rawStylusInputReport.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.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); } // Update the verified target. rawStylusInputReport.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); } // 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.UpdateEventStylusPoints(rawStylusInputReport, true); } } } ///////////////////////////////////////////////////////////////////// internal int DoubleTapDelta { get {return _doubleTapDelta;} } internal int DoubleTapDeltaTime { get {return _doubleTapDeltaTime;} } internal int CancelDelta { get {return _cancelDelta;} } ////// Critical - Calls into security critical code. (InputManagerProcessInputEventArgs) /// - called by PreProcessInput (which is SecurityCritical) /// [SecurityCritical] 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. ////// Critical - Calls into security critical code. (InputManagerProcessInputEventArgs) /// - called by PreProcessInput (which is SecurityCritical) /// [SecurityCritical] private void ProcessMouseMove(StylusDevice stylusDevice, int timestamp, bool isSynchronize) { System.Diagnostics.Debug.Assert(stylusDevice != null); 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.Value.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; } _lastRawMouseAction = actions; InputReportEventArgs inputReportArgs = new InputReportEventArgs(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); } } ///////////////////////////////////////////////////////////////////// ////// Critical: accesses e.StagingItem.Input /// calls into SecurityCritical code (InputManagerProcessInputEventArgs) /// [SecurityCritical] 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) { // ISSUE-2005/2/2-XIAOTU 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); } } } } } ////// Critical: calls UnsafeNativeMethods.WindowFromPoint /// [SecurityCritical] 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; } ///////////////////////////////////////////////////////////////////// ////// Critical: calls into SecurityCritical code (TabletDeviceCollection constructor) /// internal TabletDeviceCollection TabletDevices { [SecurityCritical] get { if (_tabletDeviceCollection == null) { // Make sure lock() doesn't cause reentrancy. using(Dispatcher.DisableProcessing()) { _tabletDeviceCollection = new TabletDeviceCollection(); // We need to know when the dispatcher shuts down in order to clean // up references to PenThreads held in the TabletDeviceCollection. _inputManager.Value.Dispatcher.ShutdownFinished += _shutdownHandler; } } return _tabletDeviceCollection; } } ///////////////////////////////////////////////////////////////////// ////// [TBS] /// internal StylusDevice 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 StylusDevice 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; } internal StylusDevice FindStylusDeviceWithLock(int stylusDeviceId) { StylusDevice stylusDevice; lock (__stylusDeviceLock) { __stylusDeviceMap.TryGetValue(stylusDeviceId, out stylusDevice); } return stylusDevice; } ///////////////////////////////////////////////////////////////////// // Updates the currently active stylus device and makes sure the StylusOver // property is updated as needed. internal void SelectStylusDevice(StylusDevice stylusDevice, IInputElement newOver, bool updateOver) { bool stylusDeviceChange = (_currentStylusDevice != stylusDevice); StylusDevice 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 (updateOver && stylusDevice == null && stylusDeviceChange) { // This will cause UpdateOverProperty() to be called. _currentStylusDevice.ChangeStylusOver(newOver); // This should be null. } _currentStylusDevice = stylusDevice; if (updateOver && stylusDevice != null) { // This will cause StylusLogic.UpdateStylusOverProperty to unconditionally be called. stylusDevice.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); } } } } ///////////////////////////////////////////////////////////////////// ////// Critical - calls into SecurityCritical code (Enable) /// [SecurityCritical] internal void EnableCore() { lock (__penContextsLock) { foreach (PenContexts contexts in __penContextsMap.Values) { contexts.Enable(); } _inputEnabled = true; } } ///////////////////////////////////////////////////////////////////// internal bool Enabled { get { return _inputEnabled; } } ///////////////////////////////////////////////////////////////////// ////// Critical: This code accesses critical data (hwnd, InputManager, _penContextsMap, /// PenContexts constructor) and passes to PenContexts constructor. /// It also optionally calls critical method PenContexts.Enable(). /// /// - TreatAsSafe boundry at HwndSource constructor. /// [SecurityCritical] internal void RegisterHwndForInput(InputManager inputManager, PresentationSource inputSource) { HwndSource hwndSource = (HwndSource)inputSource; // Query the transform from HwndTarget when the first window is created. if (!_transformInitialized) { if (hwndSource != null && hwndSource.CompositionTarget != null) { _transformToDevice = hwndSource.CompositionTarget.TransformToDevice; Debug.Assert(_transformToDevice.HasInverse); _transformInitialized = true; } } // 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! TabletDeviceCollection tablets = TabletDevices; // Make sure that lock() does not cause reentrancy. using(Dispatcher.DisableProcessing()) { lock (__penContextsLock) { if (__penContextsMap.ContainsKey(inputSource)) { throw new InvalidOperationException(SR.Get(SRID.PenService_WindowAlreadyRegistered)); } PenContexts penContexts = new PenContexts(inputManager.StylusLogic, 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(); } } // Detect if this window is disabled. If so then let the pencontexts know. int style = UnsafeNativeMethods.GetWindowLong(new HandleRef(this,hwndSource.CriticalHandle), NativeMethods.GWL_STYLE); if ((style & NativeMethods.WS_DISABLED) != 0) { penContexts.IsWindowDisabled = true; } if ( _inputEnabled ) penContexts.Enable(); } } } ///////////////////////////////////////////////////////////////////// ////// Critical: - calls into Security Critical code (PenContexts.Disable) /// and accesses critical member __penContextsMap /// /// - TreatAsSafe boundry at HwndStylusInputProvider::Dispose /// [SecurityCritical] internal void UnRegisterHwndForInput(HwndSource hwndSource) { // Make sure that lock() does not cause reentrancy. using (Dispatcher.DisableProcessing()) { bool shutdownWorkThread = Dispatcher.HasShutdownStarted; lock (__penContextsLock) { PenContexts penContexts; if (__penContextsMap.TryGetValue(hwndSource, out penContexts)) { __penContextsMap.Remove(hwndSource); // NTRAID:WINDOWSOS#1877251-2006/10/10-WAYNEZEN, // 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.CriticalHandle))) { 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.Get(SRID.PenService_WindowNotRegistered)); } } } } ///////////////////////////////////////////////////////////////////// ////// Critical: - Accesses a critical member variable: __penContextsMap /// [SecurityCritical] 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; } ////// Critical: - Calls critical methods - InputReport.InputSource, /// PenContexts.ConsiderInRange and GetPenContextsFromHwnd. /// [SecurityCritical] 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; } ///////////////////////////////////////////////////////////////////// ////// Critical: - Accesses a critical member variable: __penContextsMap /// and calls SecurityCritical method /// PenContexts.GetTabletDeviceIDPenContext. /// [SecurityCritical] 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; } ////// A method handles WM_DEVICECHANGE message. /// ////// Critical: The method invoke critical methods - /// TabletDeviceCollection.ShouldEnableTablets, TabletDeviceCollection.UpdateTablets and EnableCore. /// TreatAsSafe boundry is SystemResources.SystemThemeFilterMessage in PF.dll /// [SecurityCritical] private void OnDeviceChange() { Debug.Assert(!_inputEnabled, "StylusLogic has been enabled unexpectly."); if ( !_inputEnabled && TabletDeviceCollection.ShouldEnableTablets() ) { using ( Dispatcher.DisableProcessing() ) { // Create the tablet device collection! TabletDevices.UpdateTablets(); // Enable stylus input on all hwnds if we have not yet done so. EnableCore(); } } } ////// Critical: This code calls SecurityCritical code (TabletDeviceCollection.HandleTabletAdded, /// TabletDeviceCollection.UpdateTablets, PenContexts.Enable, PenContexts.Disable and /// PenContexts.AddContext) and accesses SecurityCritical data __penContextsMap. /// Called by StylusLogic.HandleMessage. /// TreatAsSafe boundry is HwndWrapperHook class (called via HwndSource.InputFilterMessage). /// [SecurityCritical] private void OnTabletAdded(uint wisptisIndex) { // Make sure lock() doesn't cause reentrancy. using(Dispatcher.DisableProcessing()) { lock ( __penContextsLock ) { TabletDeviceCollection tabletDeviceCollection = TabletDevices; // 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. return; // We are done here. } 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 { // rebuild all contexts and tablet collection if duplicate found. foreach ( PenContexts contexts in __penContextsMap.Values ) { contexts.Disable(false /*shutdownWorkerThread*/); } tabletDeviceCollection.UpdateTablets(); foreach ( PenContexts contexts in __penContextsMap.Values ) { contexts.Enable(); } } } } } } ////// Critical: This code calls SecurityCritical code (PenContexts.RemoveContext and /// TabletDeviceCollection.HandleTabletRemoved) and accesses SecurityCritical data /// __penContextsMap. /// Called by StylusLogic.HandleMessage. /// TreatAsSafe boundry is HwndWrapperHook class (called via HwndSource.InputFilterMessage). /// [SecurityCritical] private void OnTabletRemoved(uint wisptisIndex) { // Nothing to do if the Stylus hasn't been enabled yet. if ( _inputEnabled ) { // Make sure lock() doesn't cause reentrancy. using(Dispatcher.DisableProcessing()) { lock ( __penContextsLock ) { if (_tabletDeviceCollection != null) { uint tabletIndex = _tabletDeviceCollection.HandleTabletRemoved(wisptisIndex); if (tabletIndex != UInt32.MaxValue) { foreach (PenContexts contexts in __penContextsMap.Values) { contexts.RemoveContext(tabletIndex); } } } } } } } ///////////////////////////////////////////////////////////////////// ////// Critical: This code calls SecurityCritical code (PenContexts.RebuildContexts). /// Called by HwndStylusInputProvider.FilterMessage. /// TreatAsSafe boundry is HwndWrapperHook class (called via HwndSource.InputFilterMessage). /// [SecurityCritical] 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); } } ///////////////////////////////////////////////////////////////////// ////// 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. /// ////// Critical: This code calls SecurityCritical code (GetPenContextsFromHwnd, HwndSource.CriticalFromHwnd) /// and takes SecurityCritical data as input (hwnd). /// Called by HwndStylusInputProvider.FilterMessage. /// TreatAsSafe boundry is HwndWrapperHook class (called via HwndSource.InputFilterMessage). /// [SecurityCritical] 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; } } } ///////////////////////////////////////////////////////////////////////// ////// Critical - calls security critical code PenContexts.Disable, /// PenContexts.Enable and TabletDevice.UpdateScreenMeasurements. /// /// TreatAsSafe - Just updates TabletDevice size info use for mapping input data. No data goes in or out. /// [SecurityCritical, SecurityTreatAsSafe] internal object ProcessDisplayChanged(object oInput) { _updatingScreenMeasurements = false; // Make sure lock() doesn't cause reentrancy. using(Dispatcher.DisableProcessing()) { // We don't want to rebuild the contexts and update the tabletdevice // measurements if the Stylus hasn't been enabled yet. if (_tabletDeviceCollection != null) { // NTRAID#T2-26521-2004/10/27-waynezen, // Invalidate the screen measurements of the tablet device. foreach ( TabletDevice tablet in _tabletDeviceCollection ) { tablet.UpdateScreenMeasurements(); } } } return null; } ///////////////////////////////////////////////////////////////////// internal Matrix GetTabletToViewTransform(TabletDevice tabletDevice) { // NTRAID#Tablet_PC_Bug 26555-2004/11/3-xiaotu: 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 = _transformToDevice; matrix.Invert(); return matrix * tabletDevice.TabletToScreen; } ////// Transforms a point in measure units to a point in device coordinates /// /// The point to transform, in measure units ///The point in device coordinates internal Point DeviceUnitsFromMeasureUnits(Point measurePoint) { Point pt = measurePoint * _transformToDevice; 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; } ////// Transforms a point in measure units to a point in device coordinates /// /// The point to transform, in measure units ///The point in device coordinates internal Point MeasureUnitsFromDeviceUnits(Point measurePoint) { Matrix matrix = _transformToDevice; matrix.Invert(); return measurePoint * matrix; } // This is used to determine whether we postpone promoting mouse move events // from stylus events. internal void SetLastRawMouseActions(RawMouseActions actions) { _lastRawMouseAction = actions; } private DeferredElementTreeState StylusOverTreeState { get { if (_stylusOverTreeState == null) { _stylusOverTreeState = new DeferredElementTreeState(); } return _stylusOverTreeState; } } private DeferredElementTreeState StylusCaptureWithinTreeState { get { if (_stylusCaptureWithinTreeState == null) { _stylusCaptureWithinTreeState = new DeferredElementTreeState(); } return _stylusCaptureWithinTreeState; } } ///////////////////////////////////////////////////////////////////// private Matrix _transformToDevice = Matrix.Identity; private bool _transformInitialized; ///////////////////////////////////////////////////////////////////// ////// This data is not safe to expose as it holds refrence to PresentationSource /// private SecurityCriticalData_inputManager; DispatcherOperationCallback _dlgInputManagerProcessInput; object _stylusEventQueueLock = new object(); /// /// Critical - Marked critical to prevent inadvertant code from /// modifying raw stylus input reports. /// [SecurityCritical] Queue_queueStylusEvents = new Queue (); int _lastStylusDeviceId; bool _lastMouseMoveFromStylus = true; // Default to true to help first time use issues. // Information used to distinguish double-taps (actually, multi taps) from // multiple independent taps. private int _doubleTapDeltaTime = 800; // this is in milli-seconds private int _doubleTapDelta = 15; // The default double tap distance is .1 mm values (default is 1.5mm) private int _cancelDelta = 10; // The move distance is 1.0mm default (value in .1 mm) private RawMouseActions _lastRawMouseAction = RawMouseActions.None; private bool _waitingForDelegate = false; private MouseButtonState _mouseLeftButtonState = MouseButtonState.Released; private MouseButtonState _mouseRightButtonState = MouseButtonState.Released; private StylusPlugInCollection _activeMousePlugInCollection; private StylusPointDescription _mousePointDescription; // From old instanced Stylus class private EventHandler _shutdownHandler; TabletDeviceCollection _tabletDeviceCollection; StylusDevice _currentStylusDevice; int _lastInRangeTime; bool _triedDeferringMouseMove; /// /// Critical - Marked critical to prevent inadvertant code from /// modifying deferred mouse move information. /// [SecurityCritical] RawMouseInputReport _deferredMouseMove; DispatcherOperationCallback _processDeferredMouseMove; ////// Critical - Marked critical to prevent inadvertant code from /// modifying this deferred mouse deactivate information. /// [SecurityCritical] RawMouseInputReport _mouseDeactivateInputReport; bool _inputEnabled = false; bool _updatingScreenMeasurements = false; DispatcherOperationCallback _processDisplayChanged; object __penContextsLock = new object(); ////// Critical - Marked critical to prevent inadvertant spread /// to transparent code. /// [SecurityCritical] Dictionary
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- TypeInformation.cs
- TableDetailsRow.cs
- ColumnMapProcessor.cs
- ReachObjectContext.cs
- MultiView.cs
- SoapInteropTypes.cs
- DesignerDataColumn.cs
- FlowNode.cs
- CodeIdentifiers.cs
- DbConnectionPoolIdentity.cs
- EncryptedXml.cs
- SocketAddress.cs
- InplaceBitmapMetadataWriter.cs
- Hash.cs
- StorageComplexTypeMapping.cs
- AttributeUsageAttribute.cs
- UrlPropertyAttribute.cs
- PolicyLevel.cs
- NotSupportedException.cs
- DateTimeParse.cs
- TreeNodeBindingCollection.cs
- ADConnectionHelper.cs
- PropertyEmitterBase.cs
- TextServicesHost.cs
- InfoCardRSACryptoProvider.cs
- CallbackHandler.cs
- RelationHandler.cs
- PageCache.cs
- OdbcDataReader.cs
- FunctionCommandText.cs
- WorkItem.cs
- OutgoingWebResponseContext.cs
- PathNode.cs
- PaperSource.cs
- CharAnimationUsingKeyFrames.cs
- Events.cs
- SevenBitStream.cs
- CheckBoxRenderer.cs
- HtmlInputFile.cs
- NegationPusher.cs
- PasswordRecovery.cs
- ListViewContainer.cs
- DataGridColumnCollection.cs
- HostProtectionException.cs
- ObjectDataSourceChooseTypePanel.cs
- NativeMethods.cs
- ExtensionSimplifierMarkupObject.cs
- CssClassPropertyAttribute.cs
- Match.cs
- Transform.cs
- TextRangeEditTables.cs
- ParameterInfo.cs
- TypeDelegator.cs
- TextBoxAutomationPeer.cs
- OracleColumn.cs
- DaylightTime.cs
- DiscoveryInnerClientAdhoc11.cs
- HtmlInputReset.cs
- KnownTypes.cs
- dtdvalidator.cs
- KnownBoxes.cs
- PassportPrincipal.cs
- SkewTransform.cs
- DockPattern.cs
- DBCommandBuilder.cs
- InkCanvasAutomationPeer.cs
- ActivityMarkupSerializer.cs
- HyperLinkStyle.cs
- SqlFunctions.cs
- FormsAuthenticationCredentials.cs
- NativeRecognizer.cs
- ToolboxBitmapAttribute.cs
- WebPartEditVerb.cs
- IdentitySection.cs
- Lease.cs
- _CommandStream.cs
- ResizeGrip.cs
- SqlDataSourceView.cs
- HashSetDebugView.cs
- DisplayInformation.cs
- AccessDataSourceView.cs
- ToolBar.cs
- TrustLevel.cs
- MetadataSerializer.cs
- DataFormat.cs
- WebResourceAttribute.cs
- DataTableClearEvent.cs
- EmbeddedMailObjectsCollection.cs
- ConfigUtil.cs
- DesignTimeSiteMapProvider.cs
- TableCellCollection.cs
- DBNull.cs
- DataFormats.cs
- AdministrationHelpers.cs
- InertiaTranslationBehavior.cs
- XmlElement.cs
- SqlDataSourceConfigureFilterForm.cs
- TransformerConfigurationWizardBase.cs
- XmlSerializerSection.cs
- ExceptionHandlers.cs