JNA/WinAPI.在不移动光标的情况下模拟鼠标单击无法正常工作 [英] JNA / WinAPI. Simulate mouse click without moving the cursor doesn't work correctly

查看:152
本文介绍了JNA/WinAPI.在不移动光标的情况下模拟鼠标单击无法正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编辑:很抱歉,但是我不确定我的问题是否已正确关闭.有人建议我线程,但它不能回答我的问题.我可以模拟鼠标单击,但是不能按照我在问题中所述的那样正常工作.


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

我所拥有的:

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

问题是我无法在游戏窗口的坐标上正确执行鼠标单击.

我想提前说一句,我不违反游戏许可协议的规则,并且我只想出于个人目的将其用于游戏的旧版本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 坐标点(建筑物上)上会出现不可见的点击.但是问题是该区域已分配.我假设执行了以下顺序:在 当前鼠标位置 中向下单击鼠标,然后将光标移动到 С单击的坐标点 .但是我不知道为什么会这样.

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()的情况下,可能我不会再附加图片两次. /p>

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 ,但是它对我不起作用.不幸的是,鼠标光标从起始位置移动(消失)了大约毫秒,直到您需要单击并返回到起始位置.

感谢您阅读完本,对于如此繁琐的解释,我深表歉意.

谁能告诉我代码中什么地方做错了?由于在所有三个选项中,我都没有达到预期的行为.

解决方案

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

如果指定了 MOUSEEVENTF_ABSOLUTE 值,则 dx dy 包含 归一化的绝对坐标,介于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单击当前位置.

或者您可以将鼠标移动直接合并到click事件中:

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));
}

如果要在单击鼠标后移回原始位置,可以使用 SetCursorPos 返回该位置.

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

EDIT: Sorry, but I am not sure that my questions was closed correcltly. I was suggested this thread but it doesn't answer on my question. I am able to simulate mouse click but it doesn't work correctly as I described in my question.


I am still learning JNA and using it in my Java application (JNA 5.6.0 and jna-platform 5.6.0) but I hope people who familiar with C languages can understand me too because JNA is using WinAPI functions. My OS is Windows 10.

What I have:

  • Swing application that launches the Warcraft III game, runs the exe file of the game.
  • Low level keyboard hook that intercepts keystrokes LowLevelKeyboardProc() and calls click() method, which is described below.
  • Logic that should simulate mouse clicks on the coordinates of the game window (where the Inventory, Skills and Control are located), after pressing certain keys (as shown in the picture below).

The problem is that I cannot achieve the correct execution of a mouse click on the coordinates of the game window.

I want to say in advance that I do not violate the rules of the game's license agreement and I want to use it only for personal purposes for the old version of the game, 1.26. Also, I've seen a similar implementation in other programming languages, but I want to implement it in Java.

Below I am attaching the 3 options that I tried, with a description of the problem:

1. Using 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);
    }

It was expected that an invisible click would be made on the test coordinate point (on the building). But the problem is that the area was allocated instead. I assume that the following sequence was performed: clicking the mouse down in the Сurrent mouse position and moving the cursor to the Сoordinate point for click. But I have no idea why this happened.

2. Using 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);
    }

The same situation happened,instead of clicking on the coordinates, the area was selected, as well as in the case of SendMessage(), probably I will not re-attach the picture twice.

3. Using 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());
    }

In this case, there is no click at all on the coordinate point. Instead, when certain keys are pressed, the mouse is clicked in Current mouse position.

By the way, I also tried using Java Robot, but it didn't work for me. Unfortunately the mouse cursor moved (disappeared) by about a milliseconds from the starting position to the point where you need to click and back to the starting position.

Thank you for reading this to the end, I apologize for such a cumbersome explanation.

Can anyone tell me what and where I made a mistake in the code? Since in all 3 options, I did not achieve the expected behavior.

解决方案

For the 3rd case, you did not use the MOUSEEVENTF_MOVE flag to move the mouse, so the mouse did not actually move. And also according to the document:

If MOUSEEVENTF_ABSOLUTE value is specified, dx and dy contain normalized absolute coordinates between 0 and 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));
}

Then use the MOUSEEVENTF_LEFTDOWN and MOUSEEVENTF_LEFTUP to click the current postion.

Or you can directly merge the mouse move into the click event:

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));
}

If you want to move back to the original position after the mouse click, you can use GetCursorPos to record the current position before moving. Then use mouseMove event or simpler SetCursorPos to return to the position.

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

这篇关于JNA/WinAPI.在不移动光标的情况下模拟鼠标单击无法正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆