jna/winapi模拟鼠标单击会移动鼠标光标,而不会将其返回到开始位置

wwtsj6pe  于 2021-06-29  发布在  Java
关注(0)|答案(1)|浏览(499)

在我的java代码中,我想模拟当按下q键时,不可见的鼠标点击某些屏幕坐标(x,y)。它应该是肉眼看不见的:鼠标移动 -> 鼠标点击 -> 鼠标向后移动。
为此,我使用jna 5.6.0和jna平台5.6.0,它们使用本机winapi函数。我在里面实现了低级键盘钩子 LowLevelKeyboardProc() {callback()} 拦截击键。我使用的鼠标点击模拟 SendInput() .
我希望鼠标点击应该在某些全局屏幕坐标(x,y)上完成。在我的代码示例中,坐标是40,40。
但我得到的不是预期的结果:
鼠标移动和鼠标单击,但不将其返回到单击前光标所在的起始位置。
鼠标移动并单击坐标40,从当前鼠标位置开始单击,而不是单击全局屏幕的坐标。
演示此行为的最佳方法是,运行下面提供的代码,打开绘制,选择笔刷工具并按“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());
    }
}

我在某个地方犯了错误。
有人能告诉我怎样才能实现鼠标左键不可见地点击全局屏幕的坐标吗?

plupiseo

plupiseo1#

下一次按q按钮时,单击点和当前鼠标位置之间的距离会更大,并且此距离在第一次按q按钮后会有所不同。
文档已经对此进行了解释:
鼠标的相对运动受鼠标速度和两个鼠标阈值的影响。用户使用控制面板鼠标属性表的指针速度滑块设置这三个值。可以使用systemparametersinfo函数获取和设置这些值。
系统对指定的鼠标相对移动应用两个测试。如果沿x或y轴的指定距离大于第一个鼠标阈值,并且鼠标速度不为零,则系统会将距离加倍。如果沿x或y轴的指定距离大于第二个鼠标阈值,并且鼠标速度等于2,则系统将使应用第一个阈值测试所产生的距离加倍。因此,系统可以将指定的鼠标沿x或y轴的相对移动乘以最多四倍。
可以使用绝对坐标来完成项目。
如果指定了mouseeventf\u绝对值,则dx和dy包含0到65535之间的标准化绝对坐标。事件过程将这些坐标Map到显示曲面上。坐标(0,0)Map到显示面的左上角;坐标(6553565535)Map到右下角。在多监视器系统中,坐标Map到主监视器。
部分代码:

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 得到的坐标。
甲(267337)乙(508334)丙(714329)
然后将它们的坐标传递给输入结构,请参阅以下代码:


# 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)

相关问题