【问题标题】:JNA / WinAPI. Simulate mouse click without moving the cursor doesn't work correctlyJNA/WinAPI。在不移动光标的情况下模拟鼠标点击无法正常工作
【发布时间】:2021-03-26 16:53:45
【问题描述】:

编辑:抱歉,我不确定我的问题是否正确关闭。有人建议我thread,但它没有回答我的问题。我能够模拟鼠标点击,但它无法正常工作,正如我在问题中描述的那样。


我仍在学习 JNA 并在我的 Java 应用程序(JNA 5.6.0 和 jna-platform 5.6.0)中使用它,但我希望熟悉的人 Java em>C 语言也可以理解我,因为 JNA 使用的是 WinAPI 函数。我的操作系统是 Windows 10。

我有什么:

  • 启动魔兽争霸III游戏的Swing应用程序,运行游戏的exe文件。
  • 拦截击键LowLevelKeyboardProc() 并调用click() 方法的低级键盘挂钩,如下所述。
  • 按下某些坐标的逻辑>keys(如下图所示)。

问题是我无法在游戏窗口的坐标上实现鼠标点击的正确执行。

我想提前声明,我并没有违反游戏许可协议的规则,我只想将它用于旧版本游戏1.26的个人目的。另外,我在其他编程语言中也看到过类似的实现,但我想用 Java 来实现。

下面附上我尝试过的 3 个选项,并附上问题描述:

1。使用User32.INSTANCE.SendMessage()

public void click(KeyBindComponent keyBindComponent) {
        final int WM_LBUTTONDOWN = 513;
        final int WM_LBUTTONUP = 514;
        final int MK_LBUTTON = 0x0001;
        Map<String, Integer> cords = getCords(keyBindComponent);
        if (!cords.isEmpty()) {
            int xCord = cords.get("width");
            int yCord = cords.get("height");
            LPARAM lParam = makeLParam(xCord, yCord);
            user32Library.SendMessage(warcraft3hWnd, WM_LBUTTONDOWN, new WPARAM(MK_LBUTTON), lParam);
            user32Library.SendMessage(warcraft3hWnd, WM_LBUTTONUP, new WPARAM(MK_LBUTTON), lParam);
            System.out.println("x = " + xCord + " y = " + yCord);
        }
    }

public static LPARAM makeLParam(int l, int h) {
        // note the high word bitmask must include L
        return new LPARAM((l & 0xffff) | (h & 0xffffL) << 16);
    }

预计会在 test 坐标点(建筑物上)上进行不可见的点击。但是问题是该区域被分配了。我假设执行了以下顺序:在 С当前鼠标位置 向下单击鼠标并将光标移动到 Сoordinate point for click强>。但我不知道为什么会这样。

2.使用User32.INSTANCE.PostMessage()

public void click(KeyBindComponent keyBindComponent) {
        final int WM_LBUTTONDOWN = 513;
        final int WM_LBUTTONUP = 514;
        Map<String, Integer> cords = getCords(keyBindComponent);
        if (!cords.isEmpty()) {
            int xCord = cords.get("width");
            int yCord = cords.get("height");
            LPARAM lParam = makeLParam(xCord, yCord);
            user32Library.PostMessage(warcraft3hWnd, WM_LBUTTONDOWN, new WPARAM(0), lParam);
            user32Library.PostMessage(warcraft3hWnd, WM_LBUTTONUP, new WPARAM(0), lParam);
            System.out.println("x = " + xCord + " y = " + yCord);
        }
    }

public static LPARAM makeLParam(int l, int h) {
        // note the high word bitmask must include L
        return new LPARAM((l & 0xffff) | (h & 0xffffL) << 16);
    }

同样的情况发生了,不是点击坐标,而是选择了区域,还有SendMessage()的情况,大概不会再重新贴两次图了。

3.使用User32.INSTANCE.SendInput()

public void click(KeyBindComponent keyBindComponent) {
        Map<String, Integer> cords = getCords(keyBindComponent);
        if (!cords.isEmpty()) {
            int xCord = cords.get("width");
            int yCord = cords.get("height");
            mouseMove(xCord, yCord);
            mouseClick();
            System.out.println("x = " + xCord + " y = " + yCord);
        }
    }

void mouseMove(int x, int y) {
        final int MOUSEEVENTF_LEFTUP = 0x0004;
        final int MOUSEEVENTF_ABSOLUTE = 0x8000;
        INPUT input = new INPUT();
        INPUT[] move = (INPUT[]) input.toArray(2);

        // Release the mouse before moving it
        move[0].type = new DWORD(INPUT.INPUT_MOUSE);
        move[0].input.setType("mi");
        move[0].input.mi.dwFlags = new DWORD(MOUSEEVENTF_LEFTUP);
        move[0].input.mi.dwExtraInfo = new BaseTSD.ULONG_PTR(0);
        move[0].input.mi.time = new DWORD(0);
        move[0].input.mi.mouseData = new DWORD(0);

        move[1].type = new DWORD(INPUT.INPUT_MOUSE);
        move[1].input.mi.dx = new LONG(x);
        move[1].input.mi.dy = new LONG(y);
        move[1].input.mi.mouseData = new DWORD(0);
        move[1].input.mi.dwFlags = new DWORD(MOUSEEVENTF_LEFTUP + MOUSEEVENTF_ABSOLUTE);

        user32Library.SendInput(new DWORD(2), move, move[0].size());
    }

void mouseClick() {
        final int MOUSEEVENTF_LEFTUP = 0x0004;
        final int MOUSEEVENTF_LEFTDOWN = 0x0002;
        INPUT input = new INPUT();
        INPUT[] click = (INPUT[]) input.toArray(2);

        click[0].type = new DWORD(INPUT.INPUT_MOUSE);
        click[0].input.setType("mi");
        click[0].input.mi.dwFlags = new DWORD(MOUSEEVENTF_LEFTDOWN);
        click[0].input.mi.dwExtraInfo = new BaseTSD.ULONG_PTR(0);
        click[0].input.mi.time = new DWORD(0);
        click[0].input.mi.mouseData = new DWORD(0);

        click[1].type = new DWORD(INPUT.INPUT_MOUSE);
        click[1].input.setType("mi");
        click[1].input.mi.dwFlags = new DWORD(MOUSEEVENTF_LEFTUP);
        click[1].input.mi.dwExtraInfo = new BaseTSD.ULONG_PTR(0);
        click[1].input.mi.time = new DWORD(0);
        click[1].input.mi.mouseData = new DWORD(0);

        user32Library.SendInput(new DWORD(2), click, click[0].size());
    }

在这种情况下,坐标点上根本没有点击。相反,当按下某些键时,鼠标会在当前鼠标位置点击。

顺便说一句,我也尝试过使用 Java Robot,但它对我不起作用。不幸的是,鼠标光标从起始位置移动(消失)了大约一毫秒到您需要单击的点并返回到起始位置。

感谢您阅读到最后,对于如此繁琐的解释,我深表歉意。

谁能告诉我我在代码中犯了什么错误以及在哪里犯了错误?由于在所有 3 个选项中,我都没有达到预期的行为。

【问题讨论】:

  • 您的系统(或应用程序)DPI 设置是否设置为默认值以外的设置?
  • @mnistic DPI参数在应用程序中没有变化,系统中也默认配置。如果我们在谈论鼠标,那么我有一个非常简单的旧鼠标,Sven RX-160,分辨率,DPI 800。
  • 您有多个分辨率不同的显示器吗?
  • @DanielWiddis 没有,只​​有一个分辨率为 1920x1080。
  • @DanielWiddis 在我看来,最接近目标的是使用 SendInput () 方法的第三个选项,它执行点击,但不执行移动。也许我错误地实现了 mouseMove() 方法。虽然在 PostMessage 和 SendMessage 方法中我也实现了鼠标的点击和移动,但由于某种原因,移动是在按下按钮时发生的。这就是我猜该区域被选中的原因。

标签: java c++ c winapi jna


【解决方案1】:

对于第三种情况,您没有使用MOUSEEVENTF_MOVE 标志来移动鼠标,因此鼠标实际上并没有移动。并且还根据document

如果指定了 MOUSEEVENTF_ABSOLUTE 值,则 dxdy 包含 0 到 65,535 之间的归一化绝对坐标

void mouseMove(int x, int y) {
    INPUT move[2] = {};
    
    DWORD fScreenWidth = ::GetSystemMetrics(SM_CXSCREEN);
    DWORD fScreenHeight = ::GetSystemMetrics(SM_CYSCREEN);
    move[0].type = move[1].type = INPUT_MOUSE;
    move[0].mi.dwFlags = MOUSEEVENTF_LEFTUP;// Release the mouse before moving it
    move[1].mi.dx = MulDiv(x, 65535, fScreenWidth);
    move[1].mi.dy = MulDiv(y, 65535, fScreenHeight);
    move[1].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;

    SendInput(2, move, sizeof(INPUT));
}

然后使用MOUSEEVENTF_LEFTDOWNMOUSEEVENTF_LEFTUP点击当前位置。

也可以直接将鼠标移动合并到点击事件中:

void mouseMoveClick(int x, int y) {
    INPUT click[3] = {};

    click[0].type = INPUT_MOUSE;
    click[0].mi.dwFlags = MOUSEEVENTF_LEFTUP;// Release the mouse before moving it

    DWORD fScreenWidth = ::GetSystemMetrics(SM_CXSCREEN);
    DWORD fScreenHeight = ::GetSystemMetrics(SM_CYSCREEN);
    click[1].type = INPUT_MOUSE;
    click[1].mi.dx = click[2].mi.dx= MulDiv(x, 65535, fScreenWidth);
    click[1].mi.dy = click[2].mi.dy= MulDiv(y, 65535, fScreenHeight);
    click[1].mi.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;

    click[2].type = INPUT_MOUSE;
    click[2].mi.dwFlags = MOUSEEVENTF_LEFTUP | MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;

    SendInput(3, click, sizeof(INPUT));
}

如果想在鼠标点击后移动回原来的位置,可以使用GetCursorPos记录移动前的当前位置。然后使用mouseMove事件或更简单的SetCursorPos返回该位置。

void click(int xCord, int yCord) {
    //mouseMove(xCord, yCord);
    POINT p = {};
    GetCursorPos(&p);
    mouseMoveClick(xCord, yCord);
    SetCursorPos(p.x, p.y);
}

【讨论】:

  • 非常感谢您的回答,真的很有帮助。我根据您的示例编辑了我的代码的第三个选项,它就像一个魅力。也许您知道使用 SendMessage 和 PostMessage 的第一个或第二个选项可能会出现什么问题?感觉就像我在游戏截图中显示的那样分配了区域。
  • SendMessage/PostMessage模拟键盘输入不可靠,尤其是键盘的按下状态。 You can't simulate keyboard input with PostMessage
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-14
相关资源
最近更新 更多