【问题标题】:JNA / WinAPI. Simulating mouse click moves mouse cursor and doesn't return it back to the start positionJNA/WinAPI。模拟鼠标单击会移动鼠标光标并且不会将其返回到起始位置
【发布时间】:2021-04-01 21:24:23
【问题描述】:

在我的 Java 代码中,我想在按下 Q 键时模拟在某些屏幕坐标 (x, y) 上的不可见鼠标点击。人眼应该不可见:鼠标移动->鼠标点击->鼠标向后移动。

我为此使用 JNA 5.6.0 和 jna-platform 5.6.0,它们使用原生 WinAPI 函数。我在LowLevelKeyboardProc() {callback()} 中实现了低级键盘钩子,它拦截击键。对于鼠标点击模拟,我使用的是SendInput()。

我预计鼠标点击应该在某些全局屏幕坐标(x,y)上完成。在我的代码示例中,坐标是 40、40。

但是我得到了而不是预期的结果:

  • 鼠标移动和鼠标点击,但没有返回到点击前光标所在的起始位置。
  • 鼠标移动并点击当前鼠标位置的坐标 40、40,而不是全局屏幕的坐标。

演示此行为的最佳方法是运行下面提供的代码,打开 Paint,选择画笔工具并按“Q”按钮。

看起来是这样的:

如您所见,第一次按 Q 按钮后光标不会移回起始位置。下次按下 Q 键时,点击点与当前鼠标位置之间的距离会更大,并且在第一次按下 Q 键后该距离会有所不同。

import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.*;

import static com.sun.jna.platform.win32.WinUser.*;

public class TestExample {

    public static final int MOUSEEVENTF_MOVE = 1;
    public static final int MOUSEEVENTF_LEFTDOWN = 2;
    public static final int MOUSEEVENTF_LEFTUP = 4;

    private static WinUser.HHOOK hHook;
    static final User32 user32Library = User32.INSTANCE;
    static WinDef.HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);

    public static void main(String[] args) {
        keyReMapOn();
    }

    public static void keyReMapOn() {
        WinUser.LowLevelKeyboardProc keyboardHook = new WinUser.LowLevelKeyboardProc() {
            @Override
            public WinDef.LRESULT callback(int nCode, WinDef.WPARAM wParam, WinUser.KBDLLHOOKSTRUCT kbDllHookStruct) {
                if (nCode >= 0) {
                    if (wParam.intValue() == WM_KEYDOWN) {
                        if (kbDllHookStruct.vkCode == 81) {  // Q button
                            clickByCord(40, 40);            
                            return new WinDef.LRESULT(1);
                        }
                    }
                }
                Pointer ptr = kbDllHookStruct.getPointer();
                long peer = Pointer.nativeValue(ptr);
                return user32Library.CallNextHookEx(hHook, nCode, wParam, new WinDef.LPARAM(peer));
            }
        };

        hHook = user32Library.SetWindowsHookEx(WH_KEYBOARD_LL, keyboardHook, hMod, 0);

        int result;
        WinUser.MSG msg = new WinUser.MSG();
        while ((result = user32Library.GetMessage(msg, null, 0, 0)) != 0) {
            if (result == -1) {
                break;
            } else {
                user32Library.TranslateMessage(msg);
                user32Library.DispatchMessage(msg);
            }
        }
    }

    public static void clickByCord(int x, int y) {
        mouseMove(x, y);
        mouseLeftClick(x, y);
    }

    static void mouseMove(int x, int y) {
        mouseAction(x, y, MOUSEEVENTF_MOVE);
    }

    public static void mouseLeftClick(int x, int y) {
        mouseAction(x, y, MOUSEEVENTF_LEFTDOWN);
        mouseAction(x, y, MOUSEEVENTF_LEFTUP);
    }

    public static void mouseAction(int x, int y, int flags) {
        INPUT input = new INPUT();

        input.type = new DWORD(INPUT.INPUT_MOUSE);
        input.input.setType("mi");
        if (x != -1) {
            input.input.mi.dx = new LONG(x);
        }
        if (y != -1) {
            input.input.mi.dy = new LONG(y);
        }
        input.input.mi.time = new DWORD(0);
        input.input.mi.dwExtraInfo = new BaseTSD.ULONG_PTR(0);
        input.input.mi.dwFlags = new DWORD(flags);
        User32.INSTANCE.SendInput(new DWORD(1), new INPUT[]{input}, input.size());
    }
}

我在某处的代码中犯了错误。

谁能告诉我如何在全局屏幕坐标上实现不可见的鼠标左键点击?

【问题讨论】:

    标签: java c# c++ winapi jna


    【解决方案1】:

    下一次按Q键的点击点与当前鼠标位置之间的距离变大了,这个距离在第一次按Q键后有所不同。

    documentation 已经说明了这一点:

    相对鼠标运动受鼠标速度和鼠标速度的影响 两鼠标阈值。用户设置这三个值 控制面板鼠标属性的指针速度滑块 床单。您可以使用 SystemParametersInfo 函数。

    系统对指定的相对鼠标移动应用两个测试。 如果沿 x 或 y 轴的指定距离大于 第一个鼠标阈值,并且鼠标速度不为零,则 系统使距离加倍。如果沿任一指定距离 x 或 y 轴大于第二个鼠标阈值,并且 鼠标速度等于二,系统加倍距离 由于应用了第一个阈值测试。因此有可能 系统将指定的相对鼠标移动沿 x 或 y 轴最多四倍。

    您可以使用绝对坐标来完成项目。

    如果指定了 MOUSEEVENTF_ABSOLUTE 值,则 dx 和 dy 包含 0 到 65,535 之间的归一化绝对坐标。事件 过程将这些坐标映射到显示表面上。协调 (0,0) 映射到显示表面的左上角; 坐标 (65535,65535) 映射到右下角。在一个 多显示器系统,坐标映射到主显示器。

    部分代码:

    UINT mouseAction(int x, int y, int flags)
    {
        INPUT input;
        POINT pos;
        GetCursorPos(&pos);
    
        input.type = INPUT_MOUSE;
        input.mi.dwFlags = flags;
        input.mi.time = NULL; 
        input.mi.mouseData = NULL;
        input.mi.dx = (pos.x + x)*(65536.0f / GetSystemMetrics(SM_CXSCREEN));
        input.mi.dy = (pos.y + y)*(65536.0f / GetSystemMetrics(SM_CYSCREEN));
        input.mi.dwExtraInfo = GetMessageExtraInfo();
    
        return SendInput(1, &input, sizeof(INPUT));
    }
    
    void mouseMove(int x, int y)
    {
        mouseAction(x, y, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
    }
    

    代码是C++,我对JAVA代码不熟悉,但我觉得你改成Java很容易。

    补充:可以使用GetAsyncKeyState来检测Q键是否被按下。过多的依赖钩子会影响操作性能。

    如您所见,第一次按 Q 按钮后光标不会移回起始位置。

    您可以保存起始坐标。然后在需要的时候回到起始位置。

    Gif 演示:

    更新:

    为了进一步帮助您解决问题,我将演示一些操作供您参考。

    我们使用GetCursorPos获取ABC的坐标。

    A(267,337) B(508,334) C(714,329)

    然后将它们的坐标传递给输入结构,参考如下代码:

    #include <Windows.h>
    #include <iostream>
    
    UINT mouseAction(int x, int y, int flags)
    {
        INPUT input;
        POINT pos;
        int x1 = GetSystemMetrics(SM_CXSCREEN);
        int y1 = GetSystemMetrics(SM_CYSCREEN);
        input.type = INPUT_MOUSE;
        input.mi.dwFlags = flags;
        input.mi.time = NULL;
        input.mi.mouseData = NULL;
        input.mi.dx = x* (65536.0f / GetSystemMetrics(SM_CXSCREEN));
        input.mi.dy = y* (65536.0f / GetSystemMetrics(SM_CYSCREEN));
        input.mi.dwExtraInfo = GetMessageExtraInfo();
    
        return SendInput(1, &input, sizeof(INPUT));
    }
    
    
    int main()
    {           
        while(1)
        {
            if (GetAsyncKeyState(0x51) & 0x0001)
            {
                mouseAction(267, 337, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
            }
            if (GetAsyncKeyState(0x57) & 0x0001)
            {
                mouseAction(508, 334, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
            }
            if (GetAsyncKeyState(0x45) & 0x0001)
            {
                mouseAction(714, 329, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
        //        mouseAction(267-87, 337-65, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
            }
        }
    
        return 0;
    }
    

    虽然是C++代码,但基本逻辑和winapi是一样的。按Q键,鼠标移动到A坐标,按W键移动到B坐标,按E键移动到C坐标。

    那么我们就可以根据A到D的距离来计算D的坐标,可以帮你解决移动两个相邻坐标的问题。

    另外,我们可以根据实际情况微调距离。

    添加以下代码,

    mouseAction(267-87, 337-65, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE); // D(267-87, 337-65)
    

    【讨论】:

    • 感谢您的回答。不幸的是,我无法实现我需要的行为,我的点击是在错误的坐标上进行的,并且没有返回到原始位置。但是我需要这一切来实现对游戏中坐标的点击。如果对你来说不难,请看我的这个问题。也许你可以提出一些建议。谢谢你。 stackoverflow.com/questions/65316317/…
    • @Gepard 我检查了你的帖子。请注意:您需要将获得的 x 和 y 坐标乘以65536.0f / GetSystemMetrics(SM_CXSCREEN/ SM_CYSCREEN)。我已经在答案中解释了原因。另外,我会更新答案以帮助解决一些问题。
    • 非常感谢,现在我知道它是如何工作的了 :)
    猜你喜欢
    • 1970-01-01
    • 2021-03-26
    • 2012-11-11
    • 2014-01-06
    • 1970-01-01
    • 2020-11-23
    • 2019-03-12
    • 2016-04-14
    相关资源
    最近更新 更多