/// <summary> /// Determines if this mouse event is a promoted event based on the extra information /// given from the WM_MOUSE message. Windows guarantees that any promotions have this /// extra information and legacy engines (such as WISP in Windows 7) set the precedent /// for this, so it has been and can be relied on. /// </summary> /// <param name="mouseInputReport">The raw mouse input to check</param> /// <returns>True if this is a promoted mouse event, false otherwise.</returns> internal static bool IsPromotedMouseEvent(RawMouseInputReport mouseInputReport) { int mouseExtraInfo = NativeMethods.IntPtrToInt32(mouseInputReport.ExtraInformation); return((mouseExtraInfo & PromotedMouseEventMask) == PromotedMouseEventTag); // MI_WP_SIGNATURE }
/// <summary> /// Retrieves the cursor id from a promoted mouse message /// </summary> /// <param name="mouseInputReport">THe input report</param> /// <returns>THe cursor id if promoted, 0 if pure mouse (by definition)</returns> internal static uint GetCursorIdFromMouseEvent(RawMouseInputReport mouseInputReport) { int mouseExtraInfo = NativeMethods.IntPtrToInt32(mouseInputReport.ExtraInformation); return((uint)(mouseExtraInfo & PromotedMouseEventCursorIdMask)); }
private bool ReportInput( IntPtr hwnd, InputMode mode, int timestamp, RawMouseActions actions, int x, int y, int wheel) { // if there's no HwndSource, we shouldn't get here. But just in case... Debug.Assert(null != _source && null != _source.Value); if (_source == null || _source.Value == null) { return false; } PresentationSource source = _source.Value; CompositionTarget ct = source.CompositionTarget; // Input reports should only be generated if the window is still valid. if(_site == null || source.IsDisposed || ct == null ) { if(_active) { // We are still active, but the window is dead. Force a deactivate. actions = RawMouseActions.Deactivate; } else { return false; } } if((actions & RawMouseActions.Deactivate) == RawMouseActions.Deactivate) { // Stop tracking the mouse since we are deactivating. StopTracking(hwnd); _active = false; } else if((actions & RawMouseActions.CancelCapture) == RawMouseActions.CancelCapture) { // We have lost capture, but don't do anything else. } else if(!_active && (actions & RawMouseActions.VerticalWheelRotate) == RawMouseActions.VerticalWheelRotate) { // report mouse wheel events as if they came from the window that // is under the mouse (even though they are reported to the window // with keyboard focus) MouseDevice mouse = _site.Value.CriticalInputManager.PrimaryMouseDevice; if (mouse != null && mouse.CriticalActiveSource != null) { source = mouse.CriticalActiveSource; } } else { // If we are not active, we need to activate first. if(!_active) { // But first, check for "spurious" mouse events... // // Sometimes we get a mouse move for window "A" AFTER another // window ("B") has become active. This would cause "A" to think // that it is active, and to tell Avalon. Now both "A" and "B" think // they are active, and Avalon thinks "A" is, but REALLY, "B" is. // // Confused yet? // // To avoid this, if this window ("A") gets a mouse move, // we verify that either "A" has capture, or the mouse is over "A" IntPtr hwndToCheck = SafeNativeMethods.GetCapture(); if(hwnd != hwndToCheck) { // If we get this far, "A" does NOT have capture // - now ensure mouse is over "A" NativeMethods.POINT ptCursor = new NativeMethods.POINT(); try { UnsafeNativeMethods.GetCursorPos(ptCursor); } catch(System.ComponentModel.Win32Exception) { // Sometimes Win32 will fail this call, such as if you are // not running in the interactive desktop. For example, // a secure screen saver may be running. System.Diagnostics.Debug.WriteLine("HwndMouseInputProvider: GetCursorPos failed!"); } try { hwndToCheck = UnsafeNativeMethods.WindowFromPoint(ptCursor.x, ptCursor.y); } catch(System.ComponentModel.Win32Exception) { System.Diagnostics.Debug.WriteLine("HwndMouseInputProvider: WindowFromPoint failed!"); } if(hwnd != hwndToCheck) { // If we get this far: // - the mouse is NOT over "A" // - "A" does NOT have capture // We consider this a "spurious" mouse move and ignore it. (Win32 bug?) System.Diagnostics.Debug.WriteLine("HwndMouseInputProvider: Spurious mouse event received!"); return false; } } // We need to collect the current state of the mouse. // Include the activation action. actions |= RawMouseActions.Activate; // Remember that we are active. _active = true; _lastX = x; _lastY = y; //Console.WriteLine("Activating the mouse."); } // Make sure we are tracking the mouse so we know when it // leaves the window. StartTracking(hwnd); // Even if a move isn't explicitly reported, we still may need to // report one if the coordinates are different. This is to cover // some ugly edge cases with context menus and such. if((actions & RawMouseActions.AbsoluteMove) == 0) { if(x != _lastX || y != _lastY) { actions |= RawMouseActions.AbsoluteMove; } } else { _lastX = x; _lastY = y; } // record mouse motion so that GetIntermediatePoints has the // information it needs if ((actions & RawMouseActions.AbsoluteMove) != 0) { RecordMouseMove(x, y, _msgTime); } // MITIGATION: WIN32_AND_AVALON_RTL // // When a window is marked with the WS_EX_LAYOUTRTL style, Win32 // mirrors the coordinates received for mouse movement as well as // mirroring the output of drawing to a GDI DC. // // Avalon also sets up mirroring transforms so that we properly // mirror the output since we render to DirectX, not a GDI DC. // // Unfortunately, this means that our input is already mirrored // by Win32, and Avalon mirrors it again. To work around this // problem, we un-mirror the input from Win32 before passing // it into Avalon. // if((actions & (RawMouseActions.AbsoluteMove | RawMouseActions.Activate)) != 0) { try { //This has a SUC on it and accesses CriticalHandle int windowStyle = SafeNativeMethods.GetWindowStyle(new HandleRef(this, _source.Value.CriticalHandle), true); if((windowStyle & NativeMethods.WS_EX_LAYOUTRTL) == NativeMethods.WS_EX_LAYOUTRTL) { NativeMethods.RECT rcClient = new NativeMethods.RECT(); SafeNativeMethods.GetClientRect(new HandleRef(this,_source.Value.Handle), ref rcClient); x = rcClient.right - x; } } catch(System.ComponentModel.Win32Exception) { System.Diagnostics.Debug.WriteLine("HwndMouseInputProvider: GetWindowStyle or GetClientRect failed!"); } } } // Get the extra information sent along with the message. //There exists a SUC for this native method call IntPtr extraInformation = IntPtr.Zero; try { extraInformation = UnsafeNativeMethods.GetMessageExtraInfo(); } catch(System.ComponentModel.Win32Exception) { System.Diagnostics.Debug.WriteLine("HwndMouseInputProvider: GetMessageExtraInfo failed!"); } RawMouseInputReport report = new RawMouseInputReport(mode, timestamp, source, actions, x, y, wheel, extraInformation); bool handled = _site.Value.ReportInput(report); return handled; }
internal void PlayBackCachedDownInputReport(int timestamp) { if (_needToSendMouseDown) { // if we have marked this as handled we need to play the down otherwise we can ignore the down // as it will be process anyway and either way we need to clean up the cached down PresentationSource mouseInputSource = GetMousePresentationSource(); if (mouseInputSource != null) { Point pt = PointUtil.ScreenToClient(_lastMouseScreenLocation, mouseInputSource); _needToSendMouseDown = false; // We've sent down, don't send again. // Update the state we report to the mouse (GetButtonState). _promotedMouseState = MouseButtonState.Pressed; RawMouseActions actions = _fLeftButtonDownTrigger?RawMouseActions.Button1Press:RawMouseActions.Button2Press; // StylusLogic manages the mouse state reported to the MouseDevice to deal with multiple stylusdevice input. if (_stylusLogic.UpdateMouseButtonState(actions)) { // See if we need to set the Mouse Activate flag. InputManager inputManager = (InputManager)Dispatcher.InputManager; if (inputManager != null) { if (inputManager.PrimaryMouseDevice.CriticalActiveSource != mouseInputSource) { actions |= RawMouseActions.Activate; } } // Update the last event we've sent through. _stylusLogic.SetLastRawMouseActions(actions); RawMouseInputReport mouseInputReport = new RawMouseInputReport( InputMode.Foreground, timestamp, mouseInputSource, actions, (int)pt.X, (int)pt.Y, 0, IntPtr.Zero); InputReportEventArgs inputReportArgs = new InputReportEventArgs(this, mouseInputReport); inputReportArgs.RoutedEvent=InputManager.PreviewInputReportEvent; _stylusLogic.InputManagerProcessInputEventArgs(inputReportArgs); } } _needToSendMouseDown = false; // so we don't try and resend it later. } }
private void PreProcessInput(object sender, PreProcessInputEventArgs e) { if (e.StagingItem.Input.RoutedEvent == InputManager.PreviewInputReportEvent) { InputReportEventArgs inputReportEventArgs = e.StagingItem.Input as InputReportEventArgs; if (!inputReportEventArgs.Handled && inputReportEventArgs.Report.Type == InputType.Mouse) { RawMouseInputReport rawMouseInputReport = (RawMouseInputReport)inputReportEventArgs.Report; // Normally we only process mouse input that is from our // active visual manager. The only exception to this is // the activate report, which is how we change the visual // manager that is active. if ((rawMouseInputReport.Actions & RawMouseActions.Activate) == RawMouseActions.Activate) { // Console.WriteLine("RawMouseActions.Activate"); // If other actions are being reported besides the // activate, separate them into different events. if ((rawMouseInputReport.Actions & ~RawMouseActions.Activate) != 0) { // Cancel this event. We'll push a new event for the activate. e.Cancel(); // Push a new RawMouseInputReport for the non-activate actions. RawMouseInputReport reportActions = new RawMouseInputReport(rawMouseInputReport.Mode, rawMouseInputReport.Timestamp, rawMouseInputReport.InputSource, rawMouseInputReport.Actions & ~RawMouseActions.Activate, rawMouseInputReport.X, rawMouseInputReport.Y, rawMouseInputReport.Wheel, rawMouseInputReport.ExtraInformation); InputReportEventArgs actionsArgs = new InputReportEventArgs(inputReportEventArgs.Device, reportActions); actionsArgs.RoutedEvent=InputManager.PreviewInputReportEvent; e.PushInput(actionsArgs, null); // Create a new RawMouseInputReport for the activate. RawMouseInputReport reportActivate = new RawMouseInputReport(rawMouseInputReport.Mode, rawMouseInputReport.Timestamp, rawMouseInputReport.InputSource, RawMouseActions.Activate, rawMouseInputReport.X, rawMouseInputReport.Y, rawMouseInputReport.Wheel, rawMouseInputReport.ExtraInformation); // Push a new RawMouseInputReport for the activate. InputReportEventArgs activateArgs = new InputReportEventArgs(inputReportEventArgs.Device, reportActivate); activateArgs.RoutedEvent=InputManager.PreviewInputReportEvent; e.PushInput(activateArgs, null); } } // Only process mouse input that is from our active PresentationSource. else if ((_inputSource != null) && (rawMouseInputReport.InputSource == _inputSource.Value)) { // We need to remember the StylusDevice that generated this input. Use the _tagStylusDevice // to store this in before we take over the inputReport Device and loose it. Any // input reports we re-push need to preserve this too. This is used to set the StylusDevice // property on MouseEventArgs. InputDevice inputDevice = e.StagingItem.GetData(_tagStylusDevice) as StylusDevice; if (inputDevice == null) { inputDevice = inputReportEventArgs.Device as StylusDevice; if (inputDevice != null) { e.StagingItem.SetData(_tagStylusDevice, inputDevice); } } // Claim the input for the mouse. inputReportEventArgs.Device = this; // If the input is reporting mouse deactivation, we need // to ensure that the element receives a final leave. // Note that activation could have been moved to another // visual manager in our app, which means that the leave // was already sent. So only do this if the deactivate // event is from the visual manager that we think is active. if ((rawMouseInputReport.Actions & RawMouseActions.Deactivate) == RawMouseActions.Deactivate) { if (_mouseOver != null) { // Push back this event, and cancel the current processing. e.PushInput(e.StagingItem); e.Cancel(); _isPhysicallyOver = false; ChangeMouseOver(null, e.StagingItem.Input.Timestamp); } } // If the input is reporting mouse movement, we need to check // if we need to update our sense of "mouse over". // if ((rawMouseInputReport.Actions & RawMouseActions.AbsoluteMove) == RawMouseActions.AbsoluteMove) { // If other actions are being reported besides the // move, separate them into different events. if ((rawMouseInputReport.Actions & ~(RawMouseActions.AbsoluteMove | RawMouseActions.QueryCursor)) != 0) { // Cancel this event. We'll push a new event for the move. e.Cancel(); // Push a new RawMouseInputReport for the non-move actions. RawMouseInputReport reportActions = new RawMouseInputReport(rawMouseInputReport.Mode, rawMouseInputReport.Timestamp, rawMouseInputReport.InputSource, rawMouseInputReport.Actions & ~(RawMouseActions.AbsoluteMove | RawMouseActions.QueryCursor), 0, 0, rawMouseInputReport.Wheel, rawMouseInputReport.ExtraInformation); InputReportEventArgs actionsArgs = new InputReportEventArgs(inputDevice, reportActions); actionsArgs.RoutedEvent=InputManager.PreviewInputReportEvent; e.PushInput(actionsArgs, null); // Push a new RawMouseInputReport for the AbsoluteMove. RawMouseInputReport reportMove = new RawMouseInputReport(rawMouseInputReport.Mode, rawMouseInputReport.Timestamp, rawMouseInputReport.InputSource, rawMouseInputReport.Actions & (RawMouseActions.AbsoluteMove | RawMouseActions.QueryCursor), rawMouseInputReport.X, rawMouseInputReport.Y, 0, IntPtr.Zero); InputReportEventArgs moveArgs = new InputReportEventArgs(inputDevice, reportMove); moveArgs.RoutedEvent=InputManager.PreviewInputReportEvent; e.PushInput(moveArgs, null); } else { // Convert the point from client coordinates into "root" coordinates. // We do this in the pre-process stage because it is possible that // this conversion will fail, in which case we want to cancel the // mouse move event. bool success = true; Point ptClient = new Point(rawMouseInputReport.X, rawMouseInputReport.Y); Point ptRoot = PointUtil.TryClientToRoot(ptClient, rawMouseInputReport.InputSource, false, out success); if(success) { e.StagingItem.SetData(_tagRootPoint, ptRoot); } else { e.Cancel(); } } } } } } else { // All mouse event processing should only happen if we still have an active input source. if (_inputSource != null) { if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseDownEvent) { MouseButtonEventArgs mouseButtonEventArgs = e.StagingItem.Input as MouseButtonEventArgs; if (_mouseCapture != null && !_isPhysicallyOver) { // The mouse is not physically over the capture point (or // subtree), so raise the PreviewMouseDownOutsideCapturedElement // event first. MouseButtonEventArgs clickThrough = new MouseButtonEventArgs(this, mouseButtonEventArgs.Timestamp, mouseButtonEventArgs.ChangedButton, GetStylusDevice(e.StagingItem)); clickThrough.RoutedEvent=Mouse.PreviewMouseDownOutsideCapturedElementEvent; //ProcessInput has a linkdemand _inputManager.Value.ProcessInput(clickThrough); } } else if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseUpEvent) { MouseButtonEventArgs mouseButtonEventArgs = e.StagingItem.Input as MouseButtonEventArgs; if (_mouseCapture != null && !_isPhysicallyOver) { // The mouse is not physically over the capture point (or // subtree), so raise the PreviewMouseUpOutsideCapturedElement // event first. MouseButtonEventArgs clickThrough = new MouseButtonEventArgs(this, mouseButtonEventArgs.Timestamp, mouseButtonEventArgs.ChangedButton, GetStylusDevice(e.StagingItem)); clickThrough.RoutedEvent=Mouse.PreviewMouseUpOutsideCapturedElementEvent; //ProcessInput has a linkdemand _inputManager.Value.ProcessInput(clickThrough); } } } } }
public void Synchronize() { // System.Console.WriteLine("Synchronize"); // VerifyAccess(); // Simulate a mouse move PresentationSource activeSource = CriticalActiveSource; if (activeSource != null && activeSource.CompositionTarget != null && !activeSource.CompositionTarget.IsDisposed) { int timeStamp = Environment.TickCount; Point ptClient = GetClientPosition(); RawMouseInputReport report = new RawMouseInputReport(InputMode.Foreground, timeStamp, activeSource, RawMouseActions.AbsoluteMove, (int) ptClient.X, (int) ptClient.Y, 0, IntPtr.Zero); report._isSynchronize = true; InputReportEventArgs inputReportEventArgs; if (_stylusDevice != null) { // if we have a current stylusdevice .. use it inputReportEventArgs = new InputReportEventArgs(_stylusDevice, report); } else { inputReportEventArgs = new InputReportEventArgs(this, report); } inputReportEventArgs.RoutedEvent=InputManager.PreviewInputReportEvent; //ProcessInput has a linkdemand _inputManager.Value.ProcessInput(inputReportEventArgs); } }
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; } #if !MULTICAPTURE else if (CurrentStylusDevice.DirectlyOver != null) #else else #endif { MouseDevice mouseDevice = _inputManager.Value.PrimaryMouseDevice; if (mouseDevice.CriticalActiveSource == mouseInputReport.InputSource) { #if !MULTICAPTURE // Update over to be null when deactivating. CurrentStylusDevice.ChangeStylusOver(null); #else lock (__stylusDeviceLock) { foreach (var pair in __stylusDeviceMap) { var currentDevice = pair.Value; if (currentDevice.DirectlyOver != null) { // Update over to be null when deactivating. currentDevice.ChangeStylusOver(null); } } } #endif } } } } // See if we got some mouse input we need to check for consistency (not tagged from wisptis) else if ((actions & RawMouseActions.CancelCapture) != 0) { // We need to resend this back through as coming from a stylusdevice if in range if (CurrentStylusDevice != null && CurrentStylusDevice.InRange) { RawMouseInputReport cancelCaptureInputReport = new RawMouseInputReport(mouseInputReport.Mode, mouseInputReport.Timestamp, mouseInputReport.InputSource, mouseInputReport.Actions, 0, // Rest of the parameters are not used... 0, 0, IntPtr.Zero); InputReportEventArgs args = new InputReportEventArgs(CurrentStylusDevice, 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_TABLE_QUERYSYSTEMGESTURESTATUS message) and also on XP and Vista if the press and hold gesture is // disabled in the tablet control panel. if (!CurrentStylusDevice.SentMouseDown && fromWisptis && ShouldPromoteToMouse(CurrentStylusDevice)) { // left button down...lets replay the down at this time... // Note: We may wait till later if stylus is not down yet! // We will do it only when we are not manipulating and we will // delay it if we know that manipulations are possible. StylusTouchDevice touchDevice = CurrentStylusDevice.TouchDevice; if (touchDevice.PromotingToManipulation) { touchDevice.StoredStagingAreaItems.Add(e.StagingItem); } else if (touchDevice.PromotingToOther) { CurrentStylusDevice.PlayBackCachedDownInputReport(mouseInputReport.Timestamp); } } } // We want to eat mouse messages with the wisptis injected signature except // if the MouseDevice is getting activated or deactivated by it (filtered out // above). We also want to eat any spurious mouse events recieved between the // stylus down and the stylus system gesture getting fired. if (fromWisptis) { // eat mouse messages generated by stylus; // these will be handled off the stylus event stream and promoted to a mouse input event bool handled = true; // If the mouse is captured we need to validate that the mouse location // is actually inside the client area (we will only see those wisptis // events and can thus eat this one). Point ptMouse = new Point(mouseInputReport.X, mouseInputReport.Y); bool stylusIsDown = (CurrentStylusDevice != null) ? !CurrentStylusDevice.InAir : false; if (!stylusIsDown && Mouse.Captured != null && !InWindowClientRect(ptMouse, mouseInputReport.InputSource)) { handled = false; } // If the input has been marked as Handled, we want it to be cancelled at PreProcess stage. if (handled) { // We can't mark left and right mouse buttons as handled since it will stop the // DefWindowProc from being processed but we Cancel it which stops mouse from processing // it. Move's though we need to eat. if ((actions & (RawMouseActions.Button1Press | RawMouseActions.Button2Press)) == 0) { input.Handled = true; } e.Cancel(); // If the stylus is in the up state when we see a mouse down then just note that we've // seen the mouse left down and wanted to send it but the stylus down // has not been seen yet so we can't. When we see the stylus down later we'll promote // the left mouse down after processing the stylus down. if ((actions & RawMouseActions.Button1Press) != 0 && CurrentStylusDevice != null && CurrentStylusDevice.InAir) { CurrentStylusDevice.SetSawMouseButton1Down(true); } // Only try to process stylus events on wisptis generated mouse events and // make sure we don't re-enter ourselves. if (!_processingQueuedEvent) { // Make sure we process any pending Stylus Input before this mouse event. InputManagerProcessInput(null); } } } else { bool cancelMouseEvent = false; bool markHandled = true; // If Stylus is in range then it will be driving the mouse. Ignore any mouse input. if (_stylusDeviceInRange) { cancelMouseEvent = true; // We can't mark left and right mouse buttons as handled since it will stop the // DefWindowProc from being processed but we Cancel it which stops mouse from processing // it. Move's though we need to eat. if ((actions & (RawMouseActions.Button1Press | RawMouseActions.Button2Press)) == 0) { markHandled = false; } } // If we see only a mouse move related action while the stylus is in range then // eat it or try to defer it if not currently in range to see if we come into range. else if ((actions & ~(RawMouseActions.AbsoluteMove | RawMouseActions.QueryCursor)) == 0) { if (DeferMouseMove(mouseInputReport)) { cancelMouseEvent = true; } else { // If we now think we're going in range then eat this mouse event if (_lastMouseMoveFromStylus && ShouldConsiderStylusInRange(mouseInputReport)) { SendDeferredMouseEvent(false); // Make sure we clear any deferred mouse events now. cancelMouseEvent = true; } // We're now allowing this mouse event (and deferred one) to be processed... else { // It's a Synchronize that we are letting through so set stylus was not last move anymore. _lastMouseMoveFromStylus = false; // See if we are dealing with a second mouse event, // if so force the original one it to be processed first. if (!_triedDeferringMouseMove) SendDeferredMouseEvent(true); // CurrentStylusDevice is not in range and we're seeeing mouse messages // that are not from wisptis, time to set IsStylusOver to null if (CurrentStylusDevice != null) { // No current stylus device anymore either. SelectStylusDevice(null, null, true); } } } } // If we see a down and have a cached move then let them both go through else { // We see a mouse button 1 or 2 down/up. If we have a cache then dump it and mark that we've // seen mouse input. _lastMouseMoveFromStylus = false; SendDeferredMouseEvent(true); // CurrentStylusDevice is not in range and we're seeeing mouse messages // that are not from wisptis, time to set IsStylusOver to null if (CurrentStylusDevice != null) { // No current stylus device anymore either. SelectStylusDevice(null, null, true); } } // See if we wanted to eat this mouse event... if (cancelMouseEvent) { e.Cancel(); // abort this input if (markHandled) { input.Handled = true; // We also don't want MouseDevice processing this. } } } } // Some other real mouse only generated event came through... else { // Make sure it's only the ones we know should come through. Debug.Assert((actions & ~(RawMouseActions.Button3Press | RawMouseActions.Button3Release | RawMouseActions.Button4Press | RawMouseActions.Button4Release | RawMouseActions.Button5Press | RawMouseActions.Button5Release | RawMouseActions.VerticalWheelRotate | RawMouseActions.HorizontalWheelRotate)) == 0); // If we are not in range then make sure we update our state. // Otherwise we just let this event go through to the MouseDevice. if (!_stylusDeviceInRange) { // We are letting this move through so set stylus was not last move anymore. _lastMouseMoveFromStylus = false; // Dump cache! SendDeferredMouseEvent(true); // CurrentStylusDevice is not in range and we're seeeing mouse messages // that are not from wisptis, time to set IsStylusOver to null if (CurrentStylusDevice != null) { // We now don't have a current stylus device. SelectStylusDevice(null, null, true); } } else { // Make sure to dump the cached mouse event if we are in // range to make sure this mouse event is at the right spot! SendDeferredMouseEvent(true); } } } else { _lastMouseMoveFromStylus = false; } } else { // This event is marked as coming from a StylusDevice so make sure we update flag that we saw mouse event from stylus. _lastMouseMoveFromStylus = true; RawMouseInputReport rawMouseInputReport = (RawMouseInputReport) input.Report; StylusDevice stylusDevice = (StylusDevice)input.Device; if (!stylusDevice.InRange && rawMouseInputReport._isSynchronize) { // eat this one because it is from an activate. e.Cancel(); input.Handled = true; } } } else if (input.Report.Type == InputType.Stylus) { RawStylusInputReport stylusInputReport = (RawStylusInputReport) input.Report; 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 a static gesture can be generated TabletDevice tabletDevice = stylusDevice.TabletDevice; if (tabletDevice != null) { SystemGesture? systemGesture = tabletDevice.GenerateStaticGesture(stylusInputReport); if (systemGesture != null) { GenerateGesture(stylusInputReport, systemGesture.Value); } } // See if we need to generate a tap gesture. if (stylusInputReport.Actions == RawStylusActions.Up) { if (!stylusDevice.GestureWasFired) { GenerateGesture(stylusInputReport, stylusDevice.LastTapBarrelDown ? SystemGesture.RightTap : SystemGesture.Tap); } if (!_inDragDrop && !stylusInputReport.PenContext.Contexts.IsWindowDisabled) { // We need to process a MouseMove before promoting a MouseUp (in PromoteMainToMouse) // since the stylus updates the button states for mouse to up then. // Note: The Stylus Up is at the same location as the last stylus move so this is OK to do here. ProcessMouseMove(stylusDevice, stylusInputReport.Timestamp, false); } } input.Device = stylusDevice; } } if (cancelInput) { e.Cancel(); // Don't process this bogus event any further. } } } } } }
private bool DeferMouseMove(RawMouseInputReport mouseInputReport) { if (!_triedDeferringMouseMove) { if (_deferredMouseMove != null) { return false; // only allow one at a time. } else { _deferredMouseMove = mouseInputReport; // Now make the deferred call to the process the mouse move. Dispatcher.BeginInvoke(DispatcherPriority.Background, _processDeferredMouseMove, null); } return true; } return false; }
internal 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; }
private void ProcessMouseMove(StylusDevice stylusDevice, int timestamp, bool isSynchronize) { System.Diagnostics.Debug.Assert(stylusDevice != null); if (!ShouldPromoteToMouse(stylusDevice) || !stylusDevice.TouchDevice.PromotingToOther) { return; } PresentationSource mouseInputSource = stylusDevice.GetMousePresentationSource(); if (mouseInputSource != null) { RawMouseActions actions = RawMouseActions.AbsoluteMove; // Don't set Activate flag if a synchronize is requested! if (!isSynchronize) { if (_inputManager.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); } }
private void PromoteMainToMouse(StagingAreaInputItem stagingItem) { if(!stagingItem.Input.Handled) { StylusEventArgs stylusArgs = 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) { Debug.Assert(ShouldPromoteToMouse(stylusDevice) && stylusDevice.TouchDevice.PromotingToOther); if (IgnoreGestureToMousePromotion(stylusArgs as StylusSystemGestureEventArgs, stylusDevice.TouchDevice)) { return; } RawMouseActions actions = stylusDevice.GetMouseActionsFromStylusEventAndPlaybackCachedDown(stagingItem.Input.RoutedEvent, stylusArgs); if (actions != RawMouseActions.None) { PresentationSource mouseInputSource = stylusDevice.GetMousePresentationSource(); if (mouseInputSource != null) { Point pt = PointUtil.ScreenToClient(stylusDevice.LastMouseScreenPoint, mouseInputSource); // // 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); } } } } } }