JNA / WinAPI. Simulate mouse click without moving the cursor doesn't work correctly

两盒软妹~` 提交于 2021-02-05 07:49:28

问题


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.


回答1:


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


来源:https://stackoverflow.com/questions/65316317/jna-winapi-simulate-mouse-click-without-moving-the-cursor-doesnt-work-correc

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!