Golang中如何修改底层鼠标事件

Golang中如何修改底层鼠标事件 我正在尝试编写一个程序来修复鼠标滚轮漂移问题。该程序的目标是在无效的滚轮事件发送到系统之前,修改它们并用有效的事件进行纠正(例如,如果你正在向下滚动,然后出现了一个向上事件,应将其修改为向下事件)。因此,我编写了一个钩子来监听鼠标事件,并在事件类型正确时进行修改。然而,每当我发送修改后的事件时,系统就开始卡顿,并且实际上什么也没发生。我尝试了几种方法,结果都一样。为什么会这样?

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "unsafe"

    "github.com/gonutz/w32"
)

const CUSTOM_DATA = 0x1234567

// Define the MSLLHOOKSTRUCT structure
type MSLLHOOKSTRUCT struct {
    Pt          w32.POINT
    MouseData   uint32
    Flags       uint32
    Time        uint32
    DwExtraInfo uintptr
}

var hook w32.HHOOK

// MouseProc is the hook procedure for mouse events
func MouseProc(code int, wparam w32.WPARAM, lparam w32.LPARAM) w32.LRESULT {
    if code >= 0 {
        msllhookstruct := (*MSLLHOOKSTRUCT)(unsafe.Pointer(lparam))

        if wparam == w32.WM_MOUSEWHEEL && msllhookstruct.DwExtraInfo != CUSTOM_DATA {
            // Modify the scroll delta here, for example, halving it to reduce scroll speed
            scrollAmount := int32(msllhookstruct.MouseData >> 16)
            scrollAmount /= 2 // Adjust this factor as necessary
            msllhookstruct.MouseData = uint32(scrollAmount << 16)
            msllhookstruct.DwExtraInfo = CUSTOM_DATA
            fmt.Println("Scroll event modified")
        }

        // Ensure the modified event is passed to the next hook
        return w32.CallNextHookEx(hook, code, wparam, lparam)
    }
    return w32.CallNextHookEx(hook, code, wparam, lparam)
}

func main() {
    hook = w32.SetWindowsHookEx(w32.WH_MOUSE_LL, MouseProc, 0, 0)
    if hook == 0 {
        fmt.Println("Failed to set hook")
        return
    }
    defer w32.UnhookWindowsHookEx(hook)

    // Keep the program running to listen to mouse events
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
    <-sig
}

更多关于Golang中如何修改底层鼠标事件的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何修改底层鼠标事件的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


问题在于你直接修改了钩子回调函数中的原始数据结构,但没有正确重新发送修改后的事件。在低级鼠标钩子中,CallNextHookEx只是将事件传递给下一个钩子,但系统最终处理的是原始事件。要修改系统接收的事件,你需要拦截并阻止原始事件,然后重新发送修改后的事件。

以下是修复后的代码示例:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
    "unsafe"

    "github.com/gonutz/w32"
)

const CUSTOM_DATA = 0x1234567

type MSLLHOOKSTRUCT struct {
    Pt          w32.POINT
    MouseData   uint32
    Flags       uint32
    Time        uint32
    DwExtraInfo uintptr
}

var (
    hook          w32.HHOOK
    lastScrollDir int32
    lastScrollTime time.Time
)

func MouseProc(code int, wparam w32.WPARAM, lparam w32.LPARAM) w32.LRESULT {
    if code >= 0 {
        msllhookstruct := (*MSLLHOOKSTRUCT)(unsafe.Pointer(lparam))

        if wparam == w32.WM_MOUSEWHEEL && msllhookstruct.DwExtraInfo != CUSTOM_DATA {
            scrollDelta := int32(msllhookstruct.MouseData >> 16)
            
            // 检测滚轮漂移的逻辑
            now := time.Now()
            timeSinceLastScroll := now.Sub(lastScrollTime)
            
            if timeSinceLastScroll < 50*time.Millisecond {
                if lastScrollDir > 0 && scrollDelta < 0 {
                    // 检测到漂移:上次向下滚动,这次收到向上事件
                    fmt.Printf("Detected drift: last=%d, current=%d\n", lastScrollDir, scrollDelta)
                    
                    // 阻止原始事件
                    return 1
                } else if lastScrollDir < 0 && scrollDelta > 0 {
                    fmt.Printf("Detected drift: last=%d, current=%d\n", lastScrollDir, scrollDelta)
                    return 1
                }
            }
            
            // 更新最后滚动方向和事件
            lastScrollDir = scrollDelta
            lastScrollTime = now
            
            // 如果需要修改滚动量,重新发送事件
            if scrollDelta != 0 {
                // 阻止原始事件
                return 1
            }
        }
    }
    return w32.CallNextHookEx(hook, code, wparam, lparam)
}

func sendModifiedWheelEvent(x, y int32, delta int32) {
    // 准备输入结构
    var inputs []w32.MOUSE_INPUT
    
    // 设置鼠标位置
    inputs = append(inputs, w32.MOUSE_INPUT{
        Type: w32.INPUT_MOUSE,
        Mi: w32.MOUSEINPUT{
            Dx:      x,
            Dy:      y,
            MouseData: uint32(delta << 16),
            DwFlags: w32.MOUSEEVENTF_WHEEL,
            Time:    0,
            DwExtraInfo: CUSTOM_DATA,
        },
    })
    
    // 发送输入
    w32.SendInput(inputs)
}

func main() {
    // 安装钩子
    hook = w32.SetWindowsHookEx(w32.WH_MOUSE_LL, 
        syscall.NewCallback(MouseProc), 
        0, 
        0)
    if hook == 0 {
        fmt.Println("Failed to set hook")
        return
    }
    defer w32.UnhookWindowsHookEx(hook)

    // 消息循环
    var msg w32.MSG
    for w32.GetMessage(&msg, 0, 0, 0) > 0 {
        w32.TranslateMessage(&msg)
        w32.DispatchMessage(&msg)
    }
}

对于更完整的实现,你还需要一个单独的goroutine来处理事件重发:

func eventProcessor() {
    for {
        select {
        case event := <-eventQueue:
            // 添加延迟以确保原始事件被阻止
            time.Sleep(10 * time.Millisecond)
            
            // 发送修改后的事件
            w32.SendInput([]w32.MOUSE_INPUT{
                {
                    Type: w32.INPUT_MOUSE,
                    Mi: w32.MOUSEINPUT{
                        Dx:          event.X,
                        Dy:          event.Y,
                        MouseData:   uint32(event.Delta << 16),
                        DwFlags:     w32.MOUSEEVENTF_WHEEL,
                        DwExtraInfo: CUSTOM_DATA,
                    },
                },
            })
        }
    }
}

关键点:

  1. 通过返回非零值来阻止原始事件
  2. 使用SendInput重新发送修改后的事件
  3. 添加适当的延迟以避免竞争条件
  4. 使用单独的goroutine处理事件重发以避免阻塞钩子回调
回到顶部