다음은 장치 당 기준으로 감도를 높이고 감도를 낮출 수있는 코드입니다 (하나의 마우스 감도는 낮추지 만 다른 감도는 떨어지지 않음).
블로킹을 위해 RawInput (속도를 위해 C # DLL에 구현 됨)과 SetWindowsHookEx (순수 AHK로 구현 됨)를 사용합니다.
최근에는이 방법을 사용하기 만 했으므로 개선 할 수있는 문제와 사안이있을 수 있습니다. 나는 실을 가지고있다. 이리 내 노력을 자세히 설명하는 AHK 포럼에 - 거기에 더 많은 최신 또는 기능이 풍부한 버전이있을 수 있습니다.
C # 코드
새로운 클래스 라이브러리 프로젝트를 시작하고, NuGet을 통해 SharpDX.RawInput에 대한 참조를 추가하십시오.
DLL은 MouseDelta.dll이라고해야하며 같은 폴더에 있어야합니다.
빌드 할 때 두 개의 SharpDX DLL을 빌드 폴더에 침투시켜야합니다. 스크립트에도 이러한 파일이 필요할 것입니다.
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());
}
}
메인 AHK 스크립트 (이것을 편집해야합니다)
; ================= USER SCRIPT ================
#SingleInstance force
#NoEnv
#include CLR.ahk
#include MouseDelta.ahk
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
}
AHK 래퍼
동일한 폴더에 MouseDelta.ahk로 저장
; ================= 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)
;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
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)
}
CLR 라이브러리 - AHK가 C # DLL과 상호 작용할 수 있습니다.
동일한 폴더 또는 AHK Lib 폴더에 CLR.ahk로 저장하십시오.
; ==========================================================
; .NET Framework Interop
; http://www.autohotkey.com/forum/topic26191.html
; ==========================================================
;
; Author: Lexikos
; Version: 1.2
; Requires: AutoHotkey_L v1.0.96+
;
; Modified by evilC for compatibility with AHK_H as well as AHK_L
; "null" is a reserved word in AHK_H, so did search & Replace from "null" to "_null"
CLR_LoadLibrary(AssemblyName, AppDomain=0)
{
if !AppDomain
AppDomain := CLR_GetDefaultDomain()
e := ComObjError(0)
Loop 1 {
if assembly := AppDomain.Load_2(AssemblyName)
break
static _null := ComObject(13,0)
args := ComObjArray(0xC, 1), args[0] := AssemblyName
typeofAssembly := AppDomain.GetType().Assembly.GetType()
if assembly := typeofAssembly.InvokeMember_3("LoadWithPartialName", 0x158, _null, _null, args)
break
if assembly := typeofAssembly.InvokeMember_3("LoadFrom", 0x158, _null, _null, args)
break
}
ComObjError(e)
return assembly
}
CLR_CreateObject(Assembly, TypeName, Args*)
{
if !(argCount := Args.MaxIndex())
return Assembly.CreateInstance_2(TypeName, true)
vargs := ComObjArray(0xC, argCount)
Loop % argCount
vargs[A_Index-1] := Args[A_Index]
static Array_Empty := ComObjArray(0xC,0), _null := ComObject(13,0)
return Assembly.CreateInstance_3(TypeName, true, 0, _null, vargs, _null, Array_Empty)
}
CLR_CompileC#(Code, References="", AppDomain=0, FileName="", CompilerOptions="")
{
return CLR_CompileAssembly(Code, References, "System", "Microsoft.CSharp.CSharpCodeProvider", AppDomain, FileName, CompilerOptions)
}
CLR_CompileVB(Code, References="", AppDomain=0, FileName="", CompilerOptions="")
{
return CLR_CompileAssembly(Code, References, "System", "Microsoft.VisualBasic.VBCodeProvider", AppDomain, FileName, CompilerOptions)
}
CLR_StartDomain(ByRef AppDomain, BaseDirectory="")
{
static _null := ComObject(13,0)
args := ComObjArray(0xC, 5), args[0] := "", args[2] := BaseDirectory, args[4] := ComObject(0xB,false)
AppDomain := CLR_GetDefaultDomain().GetType().InvokeMember_3("CreateDomain", 0x158, _null, _null, args)
return A_LastError >= 0
}
CLR_StopDomain(ByRef AppDomain)
{ ; ICorRuntimeHost::UnloadDomain
DllCall("SetLastError", "uint", hr := DllCall(NumGet(NumGet(0+RtHst:=CLR_Start())+20*A_PtrSize), "ptr", RtHst, "ptr", ComObjValue(AppDomain))), AppDomain := ""
return hr >= 0
}
; NOTE: IT IS NOT NECESSARY TO CALL THIS FUNCTION unless you need to load a specific version.
CLR_Start(Version="") ; returns ICorRuntimeHost*
{
static RtHst := 0
; The simple method gives no control over versioning, and seems to load .NET v2 even when v4 is present:
; return RtHst ? RtHst : (RtHst:=COM_CreateObject("CLRMetaData.CorRuntimeHost","{CB2F6722-AB3A-11D2-9C40-00C04FA30A3E}"), DllCall(NumGet(NumGet(RtHst+0)+40),"uint",RtHst))
if RtHst
return RtHst
EnvGet SystemRoot, SystemRoot
if Version =
Loop % SystemRoot "\Microsoft.NET\Framework" (A_PtrSize=8?"64":"") "\*", 2
if (FileExist(A_LoopFileFullPath "\mscorlib.dll") && A_LoopFileName > Version)
Version := A_LoopFileName
if DllCall("mscoree\CorBindToRuntimeEx", "wstr", Version, "ptr", 0, "uint", 0
, "ptr", CLR_GUID(CLSID_CorRuntimeHost, "{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}")
, "ptr", CLR_GUID(IID_ICorRuntimeHost, "{CB2F6722-AB3A-11D2-9C40-00C04FA30A3E}")
, "ptr*", RtHst) >= 0
DllCall(NumGet(NumGet(RtHst+0)+10*A_PtrSize), "ptr", RtHst) ; Start
return RtHst
}
;
; INTERNAL FUNCTIONS
;
CLR_GetDefaultDomain()
{
static defaultDomain := 0
if !defaultDomain
{ ; ICorRuntimeHost::GetDefaultDomain
if DllCall(NumGet(NumGet(0+RtHst:=CLR_Start())+13*A_PtrSize), "ptr", RtHst, "ptr*", p:=0) >= 0
defaultDomain := ComObject(p), ObjRelease(p)
}
return defaultDomain
}
CLR_CompileAssembly(Code, References, ProviderAssembly, ProviderType, AppDomain=0, FileName="", CompilerOptions="")
{
if !AppDomain
AppDomain := CLR_GetDefaultDomain()
if !(asmProvider := CLR_LoadLibrary(ProviderAssembly, AppDomain))
|| !(codeProvider := asmProvider.CreateInstance(ProviderType))
|| !(codeCompiler := codeProvider.CreateCompiler())
return 0
if !(asmSystem := (ProviderAssembly="System") ? asmProvider : CLR_LoadLibrary("System", AppDomain))
return 0
; Convert | delimited list of references into an array.
StringSplit, Refs, References, |, %A_Space%%A_Tab%
aRefs := ComObjArray(8, Refs0)
Loop % Refs0
aRefs[A_Index-1] := Refs%A_Index%
; Set parameters for compiler.
prms := CLR_CreateObject(asmSystem, "System.CodeDom.Compiler.CompilerParameters", aRefs)
, prms.OutputAssembly := FileName
, prms.GenerateInMemory := FileName=""
, prms.GenerateExecutable := SubStr(FileName,-3)=".exe"
, prms.CompilerOptions := CompilerOptions
, prms.IncludeDebugInformation := true
; Compile!
compilerRes := codeCompiler.CompileAssemblyFromSource(prms, Code)
if error_count := (errors := compilerRes.Errors).Count
{
error_text := ""
Loop % error_count
error_text .= ((e := errors.Item[A_Index-1]).IsWarning ? "Warning " : "Error ") . e.ErrorNumber " on line " e.Line ": " e.ErrorText "`n`n"
MsgBox, 16, Compilation Failed, %error_text%
return 0
}
; Success. Return Assembly object or path.
return compilerRes[FileName="" ? "CompiledAssembly" : "PathToAssembly"]
}
CLR_GUID(ByRef GUID, sGUID)
{
VarSetCapacity(GUID, 16, 0)
return DllCall("ole32\CLSIDFromString", "wstr", sGUID, "ptr", &GUID) >= 0 ? &GUID : ""
}