c# - Strange behaviour when toggling RIDEV_CAPTUREMOUSE | RIDEV_NOLEGACY -
i'm writing mouse object in c# uses raw input. device registers , gets data , stuff, it's working in regard. however, on object have property called "exclusive" meant mimic exclusive mode in direct input.
when toggle property true, call registerrawinputdevices dwflags member of rawinputdevice set to: ridev_capturemouse | ridev_nolegacy. , when set property false, set 0.
now problem when mouse button down/up event. on mouse object assign mouse button down event set exclusive true , on mouse set false. when run application events fire , exclusive mode set , reset. weird stuff begins happen:
after mouse event , exclusive mode disabled, window doesn't respond mouse on events in window decorations (e.g. close button won't highlight , can't click on it). can't exit application hitting alt+f4. however, when click on window once or twice regular window behaviour comes back.
after application closed, windows explorer , other application windows react same behaviour. have left , right click multiple times them go normal.
very rarely, window lose focus weird reason. , throws chaos exclusive state (the code set unbind device when window deactivated , restore when activated again). said before, rare occurance, still problematic.
when set/reset exclusive mode using key down , key event, works , none of above happens. , quite baffling.
i have tried code on 2 computers, different mice , 1 running windows 7 x64 , other running windows 8.1 x64.
i have done great deal of searching on in last few days , have come empty, i'm wondering if might have thoughts on why it's behaving in manner? not setting correct flags? calling registerrawinputdevices on , on cause issues?
here's code sample program i'm using test issue:
_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("{0}x{1}", args.position.x, args.position.y); }; _mouse.pointingdeviceup += (sender, args) => { if ((args.buttons & pointingdevicebuttons.right) != pointingdevicebuttons.right) { return; } _mouse.cursorvisible = true; _mouse.exclusive = false; };
here code i'm using register , unregister mouse:
/// <summary> /// function bind 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 register device. if (!win32api.registerrawinputdevices(_device)) { throw new gorgonexception(gorgonresult.drivererror, resources.gorinp_raw_cannot_bind_pointing_device); } if (!exclusive) { onwindowbound(boundcontrol); } } /// <summary> /// function unbind 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 register device. if (!win32api.registerrawinputdevices(_device)) { throw new gorgonexception(gorgonresult.drivererror, resources.gorinp_raw_cannot_unbind_pointing_device); } boundcontrol.mouseleave -= owner_mouseleave; }
here code processes wm_input message:
/// <summary> /// object representing message loop filter. /// </summary> internal class messagefilter : system.windows.forms.imessagefilter { #region events. /// <summary> /// event fired when raw input keyboard event occours. /// </summary> public event eventhandler<rawinputkeyboardeventargs> rawinputkeyboarddata = null; /// <summary> /// event fired when pointing device event occurs. /// </summary> public event eventhandler<rawinputpointingdeviceeventargs> rawinputpointingdevicedata = null; /// <summary> /// event fired when hid event occurs. /// </summary> public event eventhandler<rawinputhideventargs> rawinputhiddata = null; #endregion #region variables. private readonly int _headersize = directaccess.sizeof<rawinputheader>(); // size of input data in bytes. #endregion #region imessagefilter members /// <summary> /// filters out message before dispatched. /// </summary> /// <param name="m">the message dispatched. cannot modify message.</param> /// <returns> /// true filter message , stop being dispatched; false allow message continue 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; // 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); } // 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 }
here's code fires mouse events after processing wm_input:
/// <summary> /// function retrieve , parse raw pointing device data. /// </summary> /// <param name="sender">sender of event.</param> /// <param name="e">event data 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 recapture. if (boundcontrol.focused) { acquired = true; } else { return; } } // nothing if we're outside , 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 inside place position @ entry point. _outside = false; position = boundcontrol.pointtoclient(system.windows.forms.cursor.position); } } // wheel data. if ((e.pointingdevicedata.buttonflags & rawmousebuttons.mousewheel) != 0) { onpointingdevicewheelmove((short)e.pointingdevicedata.buttondata); } // if we're outside of delay, restart double click cycle. if (_doubleclicker.milliseconds > doubleclickdelay) { _doubleclicker.reset(); _clickcount = 0; } // 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 have 'up' event on buttons, remove 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(); }
well, after days of pulling little hair have left out, find no rhyme or reason why happening. so, devised rather ugly hack fake exclusive mode.
first removed nolegacy , capturemouse flags device registration , locked cursor center of window receiving input via cursor.position. modified window message filter throw away window messages wm_mousemove , wm_keydown wouldn't intercepted window (except system command handles alt+f4) while device in exclusive mode.
while not elegant solution, working wanted. however, if find better way handle situation while still using nolegacy/capturemouse flags, i'll gladly mark correct answer.
Comments
Post a Comment