切换 RIDEV_CAPTUREMOUSE 时的奇怪行为 | RIDEV_NOLEGACY

Posted

技术标签:

【中文标题】切换 RIDEV_CAPTUREMOUSE 时的奇怪行为 | RIDEV_NOLEGACY【英文标题】:Strange behaviour when toggling RIDEV_CAPTUREMOUSE | RIDEV_NOLEGACY 【发布时间】:2014-08-17 20:14:31 【问题描述】:

我正在用 C# 编写一个使用原始输入的鼠标对象。该设备注册并获取数据和所有这些东西,所以它在这方面工作。但是,在这个对象上,我有一个名为“Exclusive”的属性,旨在模仿 Direct Input 中的独占模式。

当我将此属性切换为 TRUE 时,我调用 RegisterRawInputDevices 并将 RAWINPUTDEVICE 的 dwFlags 成员设置为:RIDEV_CAPTUREMOUSE | RIDEV_NOLEGACY。当我将该属性设置为 FALSE 时,我将其设置为 0。

现在的问题是当我从鼠标按钮向下/向上事件执行此操作时。在我的鼠标对象上,我将鼠标按钮按下事件分配为将 Exclusive 设置为 TRUE,而在鼠标向上时,我将其设置为 FALSE。当我运行应用程序时,事件触发并且独占模式被设置和重置。这就是奇怪的事情开始发生的地方:

    在鼠标向上事件和独占模式被禁用后,窗口不会响应窗口装饰中的鼠标悬停事件(例如,关闭按钮不会突出显示,我无法单击它) .我也无法通过按 ALT+F4 退出应用程序。但是,当我单击窗口一次或两次时,常规窗口行为又回来了。

    关闭应用程序后,Windows 资源管理器和其他应用程序窗口会以相同的行为做出反应。我必须多次左右单击才能让它们恢复正常。

    在极少数情况下,窗口会因为一些奇怪的原因失去焦点。这会使所有的东西都陷入排他状态(代码被设置为在窗口被停用时取消绑定设备,并在再次激活时恢复它)。正如我之前所说,这是一种非常罕见的情况,但仍然存在很大问题。

当我使用按键按下和按键按下事件设置/重置独占模式时,一切正常,以上都没有发生。这真是令人费解。

我已经在两台计算机上尝试过这段代码,使用不同的鼠标,一台运行 Windows 7 x64,另一台运行 Windows 8.1 x64。

在过去的几天里,我对此进行了大量搜索,但结果一无所获,所以我想知道是否有人可能对它为什么会以这种方式表现有任何想法?我没有设置正确的标志吗?像这样一遍又一遍地调用 RegisterRawInputDevices 会导致问题吗?

这是我用来测试问题的示例程序的代码:

_mouse = _input.CreatePointingDevice(_form);
_keyboard = _input.CreateKeyboard(_form);

_mouse.PointingDeviceDown += (sender, args) =>
                             
                                 if ((args.Buttons & PointingDeviceButtons.Right) != PointingDeviceButtons.Right)
                                 
                                     return;
                                 

                                 _mouse.Exclusive = true;
                             ;

_mouse.PointingDeviceMove += (sender, args) =>
                             
                                 _form.Text = string.Format("0x1", args.Position.X, args.Position.Y);
                             ;

_mouse.PointingDeviceUp += (sender, args) =>
                           
                               if ((args.Buttons & PointingDeviceButtons.Right) != PointingDeviceButtons.Right)
                               
                                   return;
                               

                               _mouse.CursorVisible = true;
                               _mouse.Exclusive = false;
                           ;

这是我用来注册和注销鼠标的代码:

/// <summary>
/// Function to bind the input device.
/// </summary>
protected override void BindDevice()

    BoundControl.MouseLeave -= Owner_MouseLeave;

    UnbindDevice();

    if (_messageFilter != null)
    
        _messageFilter.RawInputPointingDeviceData -= GetRawData;
        _messageFilter.RawInputPointingDeviceData += GetRawData;
    

    _device.UsagePage = HIDUsagePage.Generic;
    _device.Usage = (ushort)HIDUsage.Mouse;
    _device.Flags = RawInputDeviceFlags.None;

    // Enable background access.
    if (AllowBackground)
    
        _device.Flags |= RawInputDeviceFlags.InputSink;
    

    // Enable exclusive access.
    if (Exclusive)
    
        _device.Flags |= RawInputDeviceFlags.CaptureMouse | RawInputDeviceFlags.NoLegacy;
    

    _device.WindowHandle = BoundControl.Handle;

    // Attempt to register the device.
    if (!Win32API.RegisterRawInputDevices(_device))
    
        throw new GorgonException(GorgonResult.DriverError, Resources.GORINP_RAW_CANNOT_BIND_POINTING_DEVICE);
    

    if (!Exclusive)
    
        OnWindowBound(BoundControl);
    


    /// <summary>
    /// Function to unbind the input device.
    /// </summary>
    protected override void UnbindDevice()
    
        if (_messageFilter != null)
        
            _messageFilter.RawInputPointingDeviceData -= GetRawData;
        

        _device.UsagePage = HIDUsagePage.Generic;
        _device.Usage = (ushort)HIDUsage.Mouse;
        _device.Flags = RawInputDeviceFlags.Remove;
        _device.WindowHandle = IntPtr.Zero;

        // Attempt to register the device.
        if (!Win32API.RegisterRawInputDevices(_device))
        
            throw new GorgonException(GorgonResult.DriverError, Resources.GORINP_RAW_CANNOT_UNBIND_POINTING_DEVICE);
        

        BoundControl.MouseLeave -= Owner_MouseLeave;
    

这是处理 WM_INPUT 消息的代码:

/// <summary>
/// Object representing a message loop filter.
/// </summary>
internal class MessageFilter
    : System.Windows.Forms.IMessageFilter

    #region Events.
    /// <summary>
    /// Event fired when a raw input keyboard event occours.
    /// </summary>
    public event EventHandler<RawInputKeyboardEventArgs> RawInputKeyboardData = null;
    /// <summary>
    /// Event fired when a pointing device event occurs.
    /// </summary>
    public event EventHandler<RawInputPointingDeviceEventArgs> RawInputPointingDeviceData = null;
    /// <summary>
    /// Event fired when an HID event occurs.
    /// </summary>
    public event EventHandler<RawInputHIDEventArgs> RawInputHIDData = null;
    #endregion

    #region Variables.
    private readonly int _headerSize = DirectAccess.SizeOf<RAWINPUTHEADER>();   // Size of the input data in bytes.
    #endregion

    #region IMessageFilter Members
    /// <summary>
    /// Filters out a message before it is dispatched.
    /// </summary>
    /// <param name="m">The message to be dispatched. You cannot modify this message.</param>
    /// <returns>
    /// true to filter the message and stop it from being dispatched; false to allow the message to continue to the next filter or control.
    /// </returns>
    public bool PreFilterMessage(ref System.Windows.Forms.Message m)
    
        // Handle raw input messages.
        if ((WindowMessages)m.Msg != WindowMessages.RawInput)
        
            return false;
        

        unsafe
        
            int dataSize = 0;

            // Get data size.           
            int result = Win32API.GetRawInputData(m.LParam, RawInputCommand.Input, IntPtr.Zero, ref dataSize, _headerSize);

            if (result == -1)
            
                throw new GorgonException(GorgonResult.CannotRead, Resources.GORINP_RAW_CANNOT_READ_DATA);
            

            // Get actual data.
            var rawInputPtr = stackalloc byte[dataSize];
            result = Win32API.GetRawInputData(m.LParam, RawInputCommand.Input, (IntPtr)rawInputPtr, ref dataSize, _headerSize);

            if ((result == -1) || (result != dataSize))
            
                throw new GorgonException(GorgonResult.CannotRead, Resources.GORINP_RAW_CANNOT_READ_DATA);
            

            var rawInput = (RAWINPUT*)rawInputPtr;

            switch (rawInput->Header.Type)
            
                case RawInputType.Mouse:
                    if (RawInputPointingDeviceData != null)
                    
                        RawInputPointingDeviceData(this,
                                                   new RawInputPointingDeviceEventArgs(rawInput->Header.Device, ref rawInput->Union.Mouse));
                    
                    break;
                case RawInputType.Keyboard:
                    if (RawInputKeyboardData != null)
                    
                        RawInputKeyboardData(this, new RawInputKeyboardEventArgs(rawInput->Header.Device, ref rawInput->Union.Keyboard));
                    
                    break;
                default:
                    if (RawInputHIDData != null)
                    
                        var HIDData = new byte[rawInput->Union.HID.Size * rawInput->Union.HID.Count];
                        var hidDataPtr = ((byte*)rawInput) + _headerSize + 8;

                        fixed (byte* buffer = &HIDData[0])
                        
                            DirectAccess.MemoryCopy(buffer, hidDataPtr, HIDData.Length);
                        

                        RawInputHIDData(this, new RawInputHIDEventArgs(rawInput->Header.Device, ref rawInput->Union.HID, HIDData));
                    
                    break;
            
        

        return false;
    
    #endregion

这是处理 WM_INPUT 后触发鼠标事件的代码:

/// <summary>
/// Function to retrieve and parse the raw pointing device data.
/// </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="e">Event data to examine.</param>
private void GetRawData(object sender, RawInputPointingDeviceEventArgs e)

    if ((BoundControl == null) || (BoundControl.Disposing))
    
        return;
    

    if ((_deviceHandle != IntPtr.Zero) && (_deviceHandle != e.Handle))
    
        return;
    

    if ((Exclusive) && (!Acquired))
    
        // Attempt to recapture.
        if (BoundControl.Focused)
        
            Acquired = true;
        
        else
        
            return;
        
    

    // Do nothing if we're outside and we have exclusive mode turned off.
    if (!Exclusive)
    
        if (!WindowRectangle.Contains(BoundControl.PointToClient(System.Windows.Forms.Cursor.Position))) 
        
            _outside = true;
            return;
        

        if (_outside) 
        
            // If we're back inside place position at the entry point.
            _outside = false;
            Position = BoundControl.PointToClient(System.Windows.Forms.Cursor.Position);
        
    

    // Get wheel data.
    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.MouseWheel) != 0)
    
        OnPointingDeviceWheelMove((short)e.PointingDeviceData.ButtonData);
    

    // If we're outside of the delay, then restart double click cycle.
    if (_doubleClicker.Milliseconds > DoubleClickDelay)
    
        _doubleClicker.Reset();
        _clickCount = 0;
    

    // Get button data.
    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.LeftDown) != 0)
    
        BeginDoubleClick(PointingDeviceButtons.Left);
        OnPointingDeviceDown(PointingDeviceButtons.Left);
    

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.RightDown) != 0)
    
        BeginDoubleClick(PointingDeviceButtons.Right);
        OnPointingDeviceDown(PointingDeviceButtons.Right);
    

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.MiddleDown) != 0)
    
        BeginDoubleClick(PointingDeviceButtons.Middle);
        OnPointingDeviceDown(PointingDeviceButtons.Middle);
    

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.Button4Down) != 0)
    
        BeginDoubleClick(PointingDeviceButtons.Button4);
        OnPointingDeviceDown(PointingDeviceButtons.Button4);
    

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.Button5Down) != 0)
    
        BeginDoubleClick(PointingDeviceButtons.Button5);
        OnPointingDeviceDown(PointingDeviceButtons.Button5);
    

    // If we have an 'up' event on the buttons, remove the flag.
    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.LeftUp) != 0)
    
        if (IsDoubleClick(PointingDeviceButtons.Left))
        
            _clickCount += 1;
        
        else
        
            _doubleClicker.Reset();
            _clickCount = 0;
        

        OnPointingDeviceUp(PointingDeviceButtons.Left, _clickCount);
    

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.RightUp) != 0)
    
        if (IsDoubleClick(PointingDeviceButtons.Right))
        
            _clickCount += 1;
        
        else
        
            _doubleClicker.Reset();
            _clickCount = 0;
        

        OnPointingDeviceUp(PointingDeviceButtons.Right, _clickCount);
    

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.MiddleUp) != 0)
    
        if (IsDoubleClick(PointingDeviceButtons.Middle))
        
            _clickCount += 1;
        
        else
        
            _doubleClicker.Reset();
            _clickCount = 0;
        

        OnPointingDeviceUp(PointingDeviceButtons.Middle, _clickCount);
    

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.Button4Up) != 0)
    
        if (IsDoubleClick(PointingDeviceButtons.Button4))
        
            _clickCount += 1;
        
        else
        
            _doubleClicker.Reset();
            _clickCount = 0;
        

        OnPointingDeviceUp(PointingDeviceButtons.Button4, _clickCount);
    

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.Button5Up) != 0)
    
        if (IsDoubleClick(PointingDeviceButtons.Button5))
        
            _clickCount += 1;
        
        else
        
            _doubleClicker.Reset();
            _clickCount = 0;
        

        OnPointingDeviceUp(PointingDeviceButtons.Button5, _clickCount);
    

    // Fire events.
    RelativePosition = new PointF(e.PointingDeviceData.LastX, e.PointingDeviceData.LastY);
    OnPointingDeviceMove(new PointF(Position.X + e.PointingDeviceData.LastX, Position.Y + e.PointingDeviceData.LastY), false);
    UpdateCursorPosition();

【问题讨论】:

好吧,在做了很多乱七八糟的事情之后,我仍然没有一个像样的解决方案。然而,我确实注意到了一些非常奇怪的事情:如果我将模式设置为独占,然后设置一个计时器以在 500 毫秒后将其关闭,它会完美运行。完全没有奇怪的行为。然而,如果我用鼠标对象上的鼠标向上事件关闭独占模式,它会执行我之前提到的所有奇怪的事情。寻求稳定仍在继续…… 【参考方案1】:

好吧,在拔掉我留下的那几根头发之后,我找不到任何理由或理由来解释为什么会发生这种情况。所以,我设计了一个相当丑陋的黑客来伪造独占模式。

首先,我从设备注册中删除了 NOLEGACY 和 CAPTUREMOUSE 标志,然后将光标锁定到通过 Cursor.Position 接收输入的窗口的中心。然后我修改了我的窗口消息过滤器以丢弃像 WM_MOUSEMOVE 和 WM_KEYDOWN 这样的窗口消息,这样它们就不会在设备处于独占模式时被窗口拦截(处理 ALT+F4 的系统命令除外)。

虽然这不是最优雅的解决方案,但它可以按我的意愿工作。但是,如果有人在仍然使用 NOLEGACY/CAPTUREMOUSE 标志的同时找到了更好的方法来处理这种情况,我很乐意将其标记为正确答案。

【讨论】:

以上是关于切换 RIDEV_CAPTUREMOUSE 时的奇怪行为 | RIDEV_NOLEGACY的主要内容,如果未能解决你的问题,请参考以下文章

源码解析中看到的奇淫巧技

Swagger使用的奇淫技巧

bitParity - 查找整数中的奇数位

打印整数二进制的奇数位和偶数位

实用工具快上车,程序狗好用的奇淫技巧

你不知道的 docker 命令的奇淫怪巧