JNA/WINAPI. Имитация щелчка мышью перемещает курсор мыши и не возвращает его в исходное положение

В моем Java-коде я хотел бы имитировать невидимый щелчок мышью по определенным координатам экрана (x, y) при нажатии клавиши Q. Это должно быть незаметно для человеческого глаза: движение мыши -> щелчок мыши -> движение мыши назад.

Для этой цели я использую 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());
    }
}

Я где-то допустил ошибки в коде.

Может ли кто-нибудь сказать мне, как я могу добиться невидимого щелчка левой кнопкой мыши по координатам глобального экрана?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
638
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Следующие нажатия кнопки Q имеют большее расстояние между точкой щелчка и текущим положением мыши, и это расстояние отличается после первого нажатия кнопки Q.

Документация уже объясняет это:

Относительное движение мыши зависит от скорости мыши и пороговые значения для двух мышей. Пользователь устанавливает эти три значения с помощью ползунок «Скорость указателя» в свойствах мыши панели управления. лист. Вы можете получить и установить эти значения с помощью Функция 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.

Вы можете сохранить начальные координаты. Затем возвращайтесь в исходное положение, когда это необходимо.

Демонстрация гифки:

Обновлено:

Чтобы еще больше помочь вам решить проблему, я продемонстрирую вам некоторые операции.

Мы используем GetCursorPos, чтобы получить координаты ABC.

А(267 337) Б(508 334) С(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.

Затем мы можем вычислить координату D на основе расстояния между A и D, что может помочь вам решить проблему перемещения двух соседних координат.

Кроме того, мы можем точно настроить расстояние в соответствии с реальной ситуацией.

Добавьте следующий код,

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

Спасибо за Ваш ответ. К сожалению, мне не удалось реализовать то поведение, которое мне было нужно, мой клик производится по неправильным координатам и не возвращается в исходное положение. Но мне все это нужно для реализации клика по координатам в игре. Если вам не сложно, посмотрите мой этот вопрос. Возможно, вы можете что-то предложить. Спасибо. stackoverflow.com/questions/65316317/…

Gepard 27.12.2020 01:47

@Gepard, я проверил твою тему. Обратите внимание: полученные координаты x и y необходимо умножить на 65536.0f / GetSystemMetrics(SM_CXSCREEN/ SM_CYSCREEN). Я объяснил причину в ответе. Кроме того, я обновлю ответ, чтобы помочь решить некоторые проблемы.

Strive Sun 28.12.2020 02:57

Большое спасибо, теперь я понял, как это работает :)

Gepard 29.12.2020 06:02

Другие вопросы по теме