POC: RawInput mouse via C# CLR

Post a reply

Confirmation code
Enter the code exactly as it appears. All letters are case insensitive.
Smilies
:D :) ;) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :!: :?: :idea: :| :mrgreen: :geek: :ugeek: :arrow: :angel: :clap: :crazy: :eh: :lolno: :problem: :shh: :shifty: :sick: :silent: :think: :thumbup: :thumbdown: :salute: :wave: :wtf: :yawn: :facepalm: :bravo: :dance: :beard: :morebeard: :xmas: :HeHe: :trollface: :cookie: :rainbow: :monkeysee: :monkeysay: :happybday: :headwall: :offtopic: :superhappy: :terms: :beer:
View more smilies

BBCode is ON
[img] is OFF
[flash] is OFF
[url] is ON
Smilies are ON

Topic review
   

Expand view Topic review: POC: RawInput mouse via C# CLR

Re: POC: RawInput mouse via C# CLR

by Avc001 » 30 Jul 2018, 23:34

Bro please help me... Can you do this for me???

Mouse move left = left arrow key , when stop moving = arrow key left up
Mouse move right = right arrow key , when stop moving = arrow key right up

put a deadzone on the x-axis as well is possible

and make sure the the mouse is infinite like its doesnt hit the edge and stops. Its lke in a real fps game you know. Please help

email me asap when you got it:
autohotkeyuser001@hotmail.com


I want to use delta rawinput api of mouse on this code here:::
----------------------------------------------------------
CoordMode, Mouse, Screen

deadzone := 6
deadzoneX := Floor(deadzone*1.2)

cnt:=!!cnt
settimer, watchMouse, 10
return

watchMouse:
cnt++, pCnt:=cnt-1
mouseGetPos, mx%cnt%
send % ( mx%cnt%>mx%pCnt%+deadzoneX ? "{right}" : mx%cnt%<mx%pCnt%-deadzoneX ? "{left}" : "" )
return

esc::exitApp ; <-- press esc to exit
----------------------------------------------------------------------

Re: POC: RawInput mouse via C# CLR

by Galvatron » 20 Jul 2017, 12:22

Okay, made it to work. These values seem to be fine and more precise than with mousedelta:
MoveEvent(x, y, mouseId){
x := round(x / 2)
y := round(y / 2)
Global mdw
if (mdw.SelectedMouse == 0 || mdw.SelectedMouse == mouseId){
DllCall("mouse_event",uint,1,int, x ,int, y,uint,0,int,0)
}
}
But using "x / 3" or lower doesn't seem to work properly.

Re: POC: RawInput mouse via C# CLR

by evilC » 18 Jun 2017, 08:24

Something still seems to be going on with the files in this thread - I am about to remote into a user's PC who is having problems to see if I can get to the bottom of it.

Re: POC: RawInput mouse via C# CLR

by Helgef » 13 Jun 2017, 02:56

I figured out what I was doing wrong, I didn't use the MouseDelta-Multi mouse version :oops:. Now, it works nice and smooth with the alt version :thumbup:.
However, the hook didn't initialise, so I changed,

Code: Select all

DllCall("SetWindowsHookEx", "int", idHook, "Uint", pfn, "Uint", DllCall("GetModuleHandle", "Uint", 0), "Uint", 0)
to

Code: Select all

DllCall("SetWindowsHookEx", "int", idHook, "Uint", pfn, "Uint", 0, "Uint", 0)
SetWindowsHookEx wrote: The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.
source- My bold
And finally, when I close the gui, i.e., quit the script, it crashes.

Re: POC: RawInput mouse via C# CLR

by Helgef » 12 Jun 2017, 15:37

I get an error.

Code: Select all

Error:  0x8002000E - Invalid number of parameters.

Specifically: SubscribeWheel

	Line#
	078: }
	080: {
	081: GuiControlGet,mouseId,,this.hDDL
	082: this.SelectedMouse := mouseId == "Any" ? 0 : mouseId  
	083: if (this.MoveCallback != 0)  
	084: this.md.SubscribeRelativeMove(this._MoveEvent.Bind(this), this.SelectedMouse)  
	085: if (this.WheelCallback != 0)  
--->	086: this.md.SubscribeWheel(this._WheelEvent.Bind(this), this.SelectedMouse)  
	087: }
	089: {
	090: GuiControl,,this.hDDL,mouseId
	091: }
	093: {
	094: if (!this.SeenMice.HasKey(mouseId))  
	094: {
Same problem occurs if I uncomment the wheel subscriptions, but then for the SubscribeRelativeMove.
I do something similar with the cursor plugin, but I do not install my own hook, instead I use (ahk's hook) blockinput, mousemove and MouseButtons::return, then I do stuff (move and click) depending on which mouse is moved, detected via rawinput (your mousedelta).

Cheers.

Re: POC: RawInput mouse via C# CLR

by evilC » 12 Jun 2017, 09:12

This demo uses extends the "MultiMouse" example to demo selectively blocking input from one mouse, while letting input through from another mouse.
Yep, that's right - we thought it could not be done, but here it is:

Code: Select all

; ================= USER SCRIPT ================
#SingleInstance force
#NoEnv
#include <CLR>
OnExit, UnhookAndClose

GoSub, Hook

Gui, Add, Text, , Select Mouse:
mdw := new MouseDeltaWrapper("x+5 yp-3 w200")
mdw.SubscribeMove(Func("MoveEvent"))
mdw.SubscribeWheel(Func("WheelEvent"))
Gui, Show
return

^Esc::
UnhookAndClose:
GuiClose:
	GoSub, UnHook
	ExitApp

Hook:
	hHookMouse := SetWindowsHookEx(WH_MOUSE_LL	:= 14, RegisterCallback("MouseMove", "Fast"))
	return

UnHook:
	UnhookWindowsHookEx(hHookMouse)
	return

MoveEvent(x, y, mouseId){
	Global mdw
	if (mdw.SelectedMouse == 0 || mdw.SelectedMouse == mouseId){
		DllCall("mouse_event",uint,1,int, x ,int, y,uint,0,int,0)
	}
}

WheelEvent(value, mouseId){
	ToolTip % "Wheel: " value ", ID: " mouseId
}

; ================= WRAPPER LIBRARY ================
class MouseDeltaWrapper {
	SeenMice := {}
	SelectedMouse := 0
	MoveCallback := 0

	__New(guiOptions := "", dllPath := "MouseDelta.dll"){
		this.Callback := callback
		
		Gui, +HwndHwnd
		this.GuiHwnd := Hwnd
		
		Gui, Add, DDL, % "hwndhDDL " guiOptions, Any||
		this.hDDL := hDDL
		
		fn := this._UserSelectedMouse.Bind(this)
		GuiControl, +g, % this.hDDL, % fn
		
		asm := CLR_LoadLibrary(dllPath)
		md := asm.CreateInstance("MouseDelta")

		md.SubscribeRelativeMove(this._MoveEvent.Bind(this))
		md.SubscribeWheel(this._WheelEvent.Bind(this))
		this.md := md
		
		this._UserSelectedMouse()
	}
	
	SubscribeMove(callback){
		this.MoveCallback := callback
	}
	
	SubscribeWheel(callback){
		this.WheelCallback := callback
	}
	
	_UserSelectedMouse(){
		GuiControlGet, mouseId, , % this.hDDL
		this.SelectedMouse := mouseId == "Any" ? 0 : mouseId
		if (this.MoveCallback != 0)
			this.md.SubscribeRelativeMove(this._MoveEvent.Bind(this), this.SelectedMouse)
		if (this.WheelCallback != 0)
			this.md.SubscribeWheel(this._WheelEvent.Bind(this), this.SelectedMouse)
	}
	
	_AddMouseToDDL(mouseId){
		GuiControl, , % this.hDDL, % mouseId
	}
	
	_UpdateMice(mouseId){
		if (!this.SeenMice.HasKey(mouseId)){
			this.SeenMice[mouseId] := 1
			this._AddMouseToDDL(mouseId)
		}
	}
	
	_MoveEvent(x, y, mouseId){
		this._UpdateMice(mouseId)
		if (this.MoveCallback != 0 && (this.SelectedMouse == 0 || this.SelectedMouse == mouseId)){
			this.MoveCallback.Call(x, y, mouseId)
		}
	}
	
	_WheelEvent(value, mouseId){
		this._UpdateMice(mouseId)
		if (this.WheelCallback != 0 && (this.SelectedMouse == 0 || this.SelectedMouse == mouseId)){
			this.WheelCallback.Call(value, mouseId)
		}
	}
}

MouseMove(nCode, wParam, lParam)
{
	Critical
	SetFormat, Integer, D
	If !nCode && (wParam = 0x200){
		; Mouse movement - process
		if (NumGet(lParam+0, 12, "int")){
			; if the LLMHF_INJECTED flag is set, this is "injected" input (Came from mouse_event)
			; Let this input through
			Return CallNextHookEx(nCode, wParam, lParam)
		} else {
			; Block the input
			Return 1
		}
	} else {
		; Other mouse message - let through
		Return CallNextHookEx(nCode, wParam, lParam)
	}
}

SetWindowsHookEx(idHook, pfn)
{
	;Return DllCall("SetWindowsHookEx", "int", idHook, "Uint", pfn, "Uint", DllCall("GetModuleHandle", "Uint", 0), "Uint", 0)
	return DllCall("SetWindowsHookEx", "int", idHook, "Uint", pfn, "Uint", 0, "Uint", 0)
}

UnhookWindowsHookEx(hHook)
{
	Return DllCall("UnhookWindowsHookEx", "Uint", hHook)
}

CallNextHookEx(nCode, wParam, lParam, hHook = 0)
{
	Return DllCall("CallNextHookEx", "Uint", hHook, "int", nCode, "Uint", wParam, "Uint", lParam)
}
How it works:
In reality, ALL input is blocked, but MoveEvent still fires for all input and is passed the VID/PID of the mouse that moved.
In MoveEvent, if the input came from the selected mouse, it resends synthetic input using the mouse_event API call
The hook checks for the presence of the LLMHF_INJECTED flag, which is set to 1 if the input came from a mouse_event API call, and lets it through.

Re: POC: RawInput mouse via C# CLR

by evilC » 06 Jun 2017, 14:32

OK, it seems like SharpDX got updated on 30th may, some of the code seems to have been targetting the old SharpDX version (4.0.0.0), so that explains why some were not working.
I updated each MouseDelta.dll to target the newest SharpDX version (4.0.1.0), and all seems fine now.

Re: POC: RawInput mouse via C# CLR

by evilC » 06 Jun 2017, 06:57

Yeah, I was wondering that...

When I get home tonight, I will try to make sure the zips in each post contain all the relevant files and test that they all work

Re: POC: RawInput mouse via C# CLR

by Helgef » 06 Jun 2017, 06:42

I tried (and failed) with the dlls from this post and the first post.
My brief testing tells me this is an improvement w.r.t to accuracy and cpu usage, compared to your ahk MouseDelta. :thumbup:
Although this is POC, for your future reference, be aware that each rawinput can contain any combination of events, i.e., both movements, clicks and wheel scrolls, hence

Code: Select all

// Fire appropriate Callback
        if (args.Mode == MouseMode.MoveRelative && relativeMoveCallback != null && (Math.Abs(args.X) + Math.Abs(args.Y) > 0))
        {
            relativeMoveCallback(args.X, args.Y, seenMice[args.Device]);
        }
        else if (args.WheelDelta != 0 && wheelCallback != null)
        {
            wheelCallback(args.WheelDelta / 120, seenMice[args.Device]);
        }
might need to be something like

Code: Select all

// Fire appropriate Callback
        if (args.Mode == MouseMode.MoveRelative && relativeMoveCallback != null && (Math.Abs(args.X) + Math.Abs(args.Y) > 0))
        {
            relativeMoveCallback(args.X, args.Y, seenMice[args.Device]);
        }
      	if (args.WheelDelta != 0 && wheelCallback != null)									// removed else 
        {
            wheelCallback(args.WheelDelta / 120, seenMice[args.Device]);
        }
or you will miss the wheel event if it occurs at the same time as a movement.

Cheers.

Re: POC: RawInput mouse via C# CLR

by evilC » 06 Jun 2017, 05:41

MouseDelta.dll is different for the various posts. Maybe I put the wrong DLL in some of the zips?

Re: POC: RawInput mouse via C# CLR

by Helgef » 06 Jun 2017, 05:33

That works :) Also, the scripts which failed before works with the MouseDelta.dll from Test.zip.

Re: POC: RawInput mouse via C# CLR

by evilC » 06 Jun 2017, 04:29

Hmm, just to be sure, here is a zip with everything needed.
Attachments
Test.zip
(114.4 KiB) Downloaded 106 times

Re: POC: RawInput mouse via C# CLR

by Helgef » 06 Jun 2017, 04:09

Unfortunately, the script silently closes on md := asm.CreateInstance("MouseDelta"). I added the sharpDX... files to the script directory.

Re: POC: RawInput mouse via C# CLR

by evilC » 05 Jun 2017, 04:49

Helgef wrote:@ evilC
I couldn't get this to work. :cry:
It seems you also need the SharpDX DLLs.
I attached them to the OP.

Re: POC: RawInput mouse via C# CLR

by evilC » 05 Jun 2017, 04:23

Run1e wrote:What's the advantage of this over a normal mouse hook?
Mouse Hooks (SetWindowsHookEx API) CAN'T distinguish between different mice but CAN block input.

RawInput (What this uses) CAN distinguish between different mice but CAN'T block input.

However, with Nefarius' HidGuardian on the horizon, we can potentially stop certain apps from being able to see certain devices, so we potentially do not need hooks to be able to block input (do a hard remap).

The main point of this code is that because mouse input happens so stupidly fast, I am trying to move as much as possible out of interpreted AHK code and into compiled C# code, allowing people to write mouse scripts in AHK that have as minimal of an impact on CPU as possible.

Re: POC: RawInput mouse via C# CLR

by Helgef » 05 Jun 2017, 02:46

@ evilC
I couldn't get this to work. :cry:

Code: Select all

#Persistent
SetWorkingDir, % A_ScriptDir
; I have CLR.ahk in my user lib.
asm := CLR_LoadLibrary("MouseDelta.dll")
MsgBox, % IsObject(asm)							 	; 1
md := asm.CreateInstance("MouseDelta")				; Chrash
I tried with MouseDelta.dll from your first and last post. Tried on Ahk 1.1.25.02 64/32 Unicode. I'm on Windows 7.

Re: POC: RawInput mouse via C# CLR

by Helgef » 05 Jun 2017, 02:18

See MouseDelta and / or Raw Input.

Re: POC: RawInput mouse via C# CLR

by RUNIE » 05 Jun 2017, 01:57

What's the advantage of this over a normal mouse hook?

Re: POC: RawInput mouse via C# CLR

by evilC » 04 Jun 2017, 10:11

Another version.
This one allows you to subscribe to mouse "Stop" events (Windows doesn't really have one).
When the stop event callback fires, you are passed the sum of the last 10 moves, plus the last move (x and y for both, so 4 params)

This is all the info that my RollMouse app uses, so I wrote a version of it using this lib + MicroTimer.

Move the mouse and pick it up while in motion, and it will keep moving in the direction and at roughly the same speed until you put the mouse back on the mat. Also works with trackpads.

AHK code:

Code: Select all

#SingleInstance force
#NoEnv
#Persistent
#include <CLR>

rm := new RollMouse()
return

class RollMouse {
	Rolling := 0
	MoveAmount := {x: 0, y: 0}
	
	__New(MouseDeltaDllPath := "MouseDelta.dll", MicroTimerDllPath := "MicroTimer.dll"){
		asm := CLR_LoadLibrary(MouseDeltaDllPath)
		md := asm.CreateInstance("MouseDelta")
		this.md := md
		
		asm := CLR_LoadLibrary(MicroTimerDllPath)
		mt := asm.CreateInstance("MicroTimer")
		this.mt := mt
		
		this.MoveTimer := mt.Create(this._DoMove.Bind(this), 2)
		
		md.SubscribeRelativeMove(this._MoveEvent.Bind(this))
		md.SubscribeRelativeMoveStop(this._StopEvent.Bind(this))
	}
	
	_MoveEvent(x, y, mouseId){
		if (this.Rolling){
			; Normal movement detected while we are rolling - stop the roll
			this.Rolling := 0
			this.MoveTimer.SetState(0)
		}
	}
	
	_StopEvent(tx, ty, lx, ly){
		; If we stop the mouse on the mat, inevitably the last movements will be low numbers
		; So if lastx or lasty is above a certain amount, we lifted while in motion
		if (abs(lx) > 3 || abs(ly) > 3){
			; Calculate the movement amount from the average of the total, rather than lastx or lasty...
			; ... so we get a more faithful representation of the general direction and magnitude of the gesture
			this.MoveAmount.x := (tx / 10)
			this.MoveAmount.y := (ty / 10)
			this.Rolling := 1
			this.MoveTimer.SetState(1)
		}
	}
	
	_DoMove(){
		DllCall("mouse_event", uint, 1, int, this.MoveAmount.x, int, this.MoveAmount.y, uint, 0, int, 0)
	}
}
C# (You will also need the MicroTimer DLL, included in attached ZIP)

Code: Select all

using System;
using System.Windows.Forms;
using SharpDX.Multimedia;
using SharpDX.RawInput;
using System.Threading;
using System.Collections.Generic;
using System.Diagnostics;
using System.Timers;
using System.Threading.Tasks;

public class MouseDelta
{
    private MoveReport deltaMoveTotal = new MoveReport  { x = 0, y = 0 };
    private MoveReport lastDeltaMove = new MoveReport  { x = 0, y = 0 };
    private List<MoveReport> deltaMoveHistory = new List<MoveReport>();
    private readonly Thread messagePump;
    //private readonly Thread stopTimerThread;
    private System.Timers.Timer stopTimer = null;

    public dynamic relativeMoveCallback;
    public dynamic relativeMoveStopCallback;
    public dynamic wheelCallback;

    static private Dictionary<IntPtr, string> seenMice = new Dictionary<IntPtr, string>();
    static private string subscribedMouse = null;

    private AutoResetEvent messagePumpRunning = new AutoResetEvent(false);

    class MoveReport
    {
        public int x { get; set; }
        public int y { get; set; }
    }

    public MouseDelta()
    {
        
        // start message pump in its own thread  
        messagePump = new Thread(RunMessagePump) { Name = "ManualMessagePump" };
        messagePump.Start();
        messagePumpRunning.WaitOne();
    }

    public void SubscribeRelativeMove(dynamic callback, string mouseId = null)
    {
        SetSubscribedMouse(mouseId);
        relativeMoveCallback = callback;
    }

    public void SubscribeRelativeMoveStop(dynamic callback, string mouseId = null)
    {
        SetSubscribedMouse(mouseId);
        relativeMoveStopCallback = callback;
    }

    public void SubscribeWheel(dynamic callback, string mouseId = null)
    {
        SetSubscribedMouse(mouseId);
        wheelCallback = callback;
    }

    private void SetSubscribedMouse(string mouseId)
    {
        if (mouseId != null)
        {
            subscribedMouse = mouseId == "0" ? null : mouseId;
        }
    }

    // the message pump thread  
    private void RunMessagePump()
    {
        // Create control to handle windows messages   
        MessageHandler messageHandler = new MessageHandler();

        // Register for RawInput mouse messages
        Device.RegisterDevice(UsagePage.Generic, UsageId.GenericMouse, DeviceFlags.InputSink, messageHandler.Handle);
        Device.MouseInput += ProcessMouseInput;

        messagePumpRunning.Set();
        Application.Run();
    }

    private void ProcessMouseInput(object sender, MouseInputEventArgs args)
    {
        //Console.WriteLine(string.Format("(x,y):({0},{1}) Buttons: {2} State: {3} Wheel: {4}\r\n", args.X, args.Y, args.ButtonFlags, args.Mode, args.WheelDelta));
        // Handle mouse filtering
        if (!seenMice.ContainsKey(args.Device))
        {
            DeviceInfo info = null;
            var devices = Device.GetDevices();
            foreach (var dev in devices)
            {
                if (dev.Handle == args.Device)
                {
                    info = dev;
                    break;
                }
            }
            if (info == null)
                return;
            string item = info.DeviceName;
            item = item.Substring(4);

            string[] split = item.Split('#');

            //string id_01 = split[0];    // ACPI (Class code)
            string id_02 = split[1];    // PNP0303 (SubClass code)
                                        //string id_03 = split[2];    // 3&13c0b0c5&0 (Protocol code)

            seenMice.Add(args.Device, id_02);
        }

        if (subscribedMouse != null && subscribedMouse != seenMice[args.Device])
        {
            return;
        }
        
        // Fire appropriate Callback
        if (args.Mode == MouseMode.MoveRelative && relativeMoveCallback != null && (Math.Abs(args.X) + Math.Abs(args.Y) > 0))
        {
            relativeMoveCallback(args.X, args.Y, seenMice[args.Device]);
            if (relativeMoveStopCallback != null)
            {
                lastDeltaMove = new MoveReport() { x = args.X, y = args.Y };
                deltaMoveHistory.Add(lastDeltaMove);
                while (deltaMoveHistory.Count > 10)
                {
                    deltaMoveHistory.RemoveAt(0);
                }
                deltaMoveTotal.x = 0;
                deltaMoveTotal.y = 0;
                for (var i = 0; i < deltaMoveHistory.Count; i++)
                {
                    deltaMoveTotal.x += deltaMoveHistory[i].x;
                    deltaMoveTotal.y += deltaMoveHistory[i].y;
                }
                SetStopTimer();
            }
        }
        else if (args.WheelDelta != 0 && wheelCallback != null)
        {
            wheelCallback(args.WheelDelta / 120, seenMice[args.Device]);
        }
    }

    private void SetStopTimer()
    {
        if (stopTimer == null)
        {
            stopTimer = new System.Timers.Timer(100);
            stopTimer.AutoReset = false;
            stopTimer.Elapsed += OnTimedEvent;
            stopTimer.Start();
        }
        else
        {
            stopTimer.Stop();
            stopTimer.Start();
        }

    }

    private void OnTimedEvent(Object source, ElapsedEventArgs e)
    {
        relativeMoveStopCallback(deltaMoveTotal.x, deltaMoveTotal.y, lastDeltaMove.x, lastDeltaMove.y);
        //Debug.WriteLine("evilC| Stop");
        stopTimer = null;
    }

}

// Useful SO post on handling messages - code for overriding WndProc
// https://stackoverflow.com/questions/2443867/message-pump-in-net-windows-service
// Although the above code is not quite complete. This blog post has the implementation for MessageData
// http://joe-bq-wang.iteye.com/blog/1882661

// However, by overriding WndProc, we have to process all messages, and then you do not get a SharpDX object..
// ... you just appear to get a raw WM_INPUT message

// For now, this seems to serve our purposes
internal class MessageHandler : NativeWindow
{
    public MessageHandler()
    {
        CreateHandle(new CreateParams());
    }
}
Attachments
MouseDelta-Stop.zip
(9.8 KiB) Downloaded 106 times

Re: POC: RawInput mouse via C# CLR

by evilC » 04 Jun 2017, 06:41

[Edit: Updated to make AHK script not receive any callbacks when a non-selected mouse moves]
I made a version that allows per-mouse filtering.
At the moment, it just uses the VID/PID string, but at least this is better than the Handle string of the previous incarnation.
I think I remember someone saying they came up with a way to uniquely identify two instances of the same device - we could maybe bolt that on.
I did not bother with trying to display mouse names - I have the code, but for my main mouse it just pulled out "Logitech Receiver", and for another of my mice without a driver, it got nothing, so it seems pretty pointless.
For simplicity of use, I wrote an AHK wrapper that allows you to inject a DDL into your script which you can use to select which mouse to take input from.

Image

Code: Select all

; ================= USER SCRIPT ================
#SingleInstance force
#NoEnv
#include <CLR>

Gui, Add, Text, , Select Mouse:
mdw := new MouseDeltaWrapper("x+5 yp-3 w200")
mdw.SubscribeMove(Func("MoveEvent"))
mdw.SubscribeWheel(Func("WheelEvent"))
Gui, Show
return

GuiClose:
	ExitApp

MoveEvent(x, y, mouseId){
	ToolTip % "Move: " x ", " y ", ID: " mouseId
}

WheelEvent(value, mouseId){
	ToolTip % "Wheel: " value ", ID: " mouseId
}

; ================= WRAPPER LIBRARY ================
class MouseDeltaWrapper {
	SeenMice := {}
	SelectedMouse := 0
	MoveCallback := 0

	__New(guiOptions := "", dllPath := "MouseDelta.dll"){
		this.Callback := callback
		
		Gui, +HwndHwnd
		this.GuiHwnd := Hwnd
		
		Gui, Add, DDL, % "hwndhDDL " guiOptions, Any||
		this.hDDL := hDDL
		
		fn := this._UserSelectedMouse.Bind(this)
		GuiControl, +g, % this.hDDL, % fn
		
		asm := CLR_LoadLibrary(dllPath)
		md := asm.CreateInstance("MouseDelta")

		md.SubscribeRelativeMove(this._MoveEvent.Bind(this))
		md.SubscribeWheel(this._WheelEvent.Bind(this))
		this.md := md
		
		this._UserSelectedMouse()
	}
	
	SubscribeMove(callback){
		this.MoveCallback := callback
	}
	
	SubscribeWheel(callback){
		this.WheelCallback := callback
	}
	
	_UserSelectedMouse(){
		GuiControlGet, mouseId, , % this.hDDL
		this.SelectedMouse := mouseId == "Any" ? 0 : mouseId
		if (this.MoveCallback != 0)
			this.md.SubscribeRelativeMove(this._MoveEvent.Bind(this), this.SelectedMouse)
		if (this.WheelCallback != 0)
			this.md.SubscribeWheel(this._WheelEvent.Bind(this), this.SelectedMouse)
	}
	
	_AddMouseToDDL(mouseId){
		GuiControl, , % this.hDDL, % mouseId
	}
	
	_UpdateMice(mouseId){
		if (!this.SeenMice.HasKey(mouseId)){
			this.SeenMice[mouseId] := 1
			this._AddMouseToDDL(mouseId)
		}
	}
	
	_MoveEvent(x, y, mouseId){
		this._UpdateMice(mouseId)
		if (this.MoveCallback != 0 && (this.SelectedMouse == 0 || this.SelectedMouse == mouseId)){
			this.MoveCallback.Call(x, y, mouseId)
		}
	}
	
	_WheelEvent(value, mouseId){
		this._UpdateMice(mouseId)
		if (this.WheelCallback != 0 && (this.SelectedMouse == 0 || this.SelectedMouse == mouseId)){
			this.WheelCallback.Call(value, mouseId)
		}
	}
}
C# code, same deal as before.

Code: Select all

using System;
using System.Windows.Forms;
using SharpDX.Multimedia;
using SharpDX.RawInput;
using System.Threading;
using System.Collections.Generic;

public class MouseDelta
{
    private readonly Thread messagePump;

    public dynamic relativeMoveCallback;
    public dynamic wheelCallback;

    static private Dictionary<IntPtr, string> seenMice = new Dictionary<IntPtr, string>();
    static private string subscribedMouse = null;

    private AutoResetEvent messagePumpRunning = new AutoResetEvent(false);

    public MouseDelta()
    {
        // start message pump in its own thread  
        messagePump = new Thread(RunMessagePump) { Name = "ManualMessagePump" };
        messagePump.Start();
        messagePumpRunning.WaitOne();
    }

    public void SubscribeRelativeMove(dynamic callback, string mouseId = null)
    {
        SetSubscribedMouse(mouseId);
        relativeMoveCallback = callback;
    }

    public void SubscribeWheel(dynamic callback, string mouseId = null)
    {
        SetSubscribedMouse(mouseId);
        wheelCallback = callback;
    }

    private void SetSubscribedMouse(string mouseId)
    {
        if (mouseId != null)
        {
            subscribedMouse = mouseId == "0" ? null : mouseId;
        }
    }

    // the message pump thread  
    private void RunMessagePump()
    {
        // Create control to handle windows messages   
        MessageHandler messageHandler = new MessageHandler();

        // Register for RawInput mouse messages
        Device.RegisterDevice(UsagePage.Generic, UsageId.GenericMouse, DeviceFlags.InputSink, messageHandler.Handle);
        Device.MouseInput += ProcessMouseInput;

        messagePumpRunning.Set();
        Application.Run();
    }

    private void ProcessMouseInput(object sender, MouseInputEventArgs args)
    {
        //Console.WriteLine(string.Format("(x,y):({0},{1}) Buttons: {2} State: {3} Wheel: {4}\r\n", args.X, args.Y, args.ButtonFlags, args.Mode, args.WheelDelta));
        // Handle mouse filtering
        if (!seenMice.ContainsKey(args.Device))
        {
            DeviceInfo info = null;
            var devices = Device.GetDevices();
            foreach (var dev in devices)
            {
                if (dev.Handle == args.Device)
                {
                    info = dev;
                    break;
                }
            }
            if (info == null)
                return;
            string item = info.DeviceName;
            item = item.Substring(4);

            string[] split = item.Split('#');

            //string id_01 = split[0];    // ACPI (Class code)
            string id_02 = split[1];    // PNP0303 (SubClass code)
                                        //string id_03 = split[2];    // 3&13c0b0c5&0 (Protocol code)

            seenMice.Add(args.Device, id_02);
        }

        if (subscribedMouse != null && subscribedMouse != seenMice[args.Device])
        {
            return;
        }
        
        // Fire appropriate Callback
        if (args.Mode == MouseMode.MoveRelative && relativeMoveCallback != null && (Math.Abs(args.X) + Math.Abs(args.Y) > 0))
        {
            relativeMoveCallback(args.X, args.Y, seenMice[args.Device]);
        }
        else if (args.WheelDelta != 0 && wheelCallback != null)
        {
            wheelCallback(args.WheelDelta / 120, seenMice[args.Device]);
        }
    }
}

// Useful SO post on handling messages - code for overriding WndProc
// https://stackoverflow.com/questions/2443867/message-pump-in-net-windows-service
// Although the above code is not quite complete. This blog post has the implementation for MessageData
// http://joe-bq-wang.iteye.com/blog/1882661

// However, by overriding WndProc, we have to process all messages, and then you do not get a SharpDX object..
// ... you just appear to get a raw WM_INPUT message

// For now, this seems to serve our purposes
internal class MessageHandler : NativeWindow
{
    public MessageHandler()
    {
        CreateHandle(new CreateParams());
    }
}
Attachments
MouseDelta-MultiMouse.zip
(4.91 KiB) Downloaded 85 times

Top