![](/img/trans.png)
[英]JNA / WinAPI. Simulate mouse click without moving the cursor doesn't work correctly
[英]JNA / WinAPI. Simulating mouse click moves mouse cursor and doesn't return it back to the start position
在我的 Java 代码中,我想在按下 Q 键时模拟在某些屏幕坐标(x,y)上的不可见鼠标点击。 它对人眼应该是不可见的:鼠标移动->
鼠标单击->
鼠标向后移动。
为此,我正在使用 JNA 5.6.0 和 jna-platform 5.6.0,它们使用本机WinAPI函数。 我在LowLevelKeyboardProc() {callback()}
中实现了低级键盘钩子,它拦截击键。 对于鼠标单击模拟,我使用的是SendInput()
。
我预计鼠标点击应该在某些全局屏幕坐标(x,y)上完成。 在我的代码示例中,坐标是 40、40。
但我得到的不是预期的结果:
演示此行为的最佳方法是运行下面提供的代码,打开 Paint,select 画笔工具,然后按“Q”按钮。
这是它的样子:
如您所见,cursor 在第一次按下 Q 按钮后不会回到开始 position。 下一次按下 Q 按钮后,点击点和当前鼠标 position 之间的距离会更大,并且这个距离在第一次按下 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());
}
}
我在某处的代码中犯了错误。
谁能告诉我如何在全局屏幕的坐标上实现不可见的鼠标左键单击?
下一次按下 Q 按钮后,点击点和当前鼠标 position 之间的距离会更大,并且这个距离在第一次按下 Q 按钮后会有所不同。
文档已经解释了这一点:
相对鼠标运动受鼠标速度和两鼠标阈值的影响。 用户使用控制面板鼠标属性表的指针速度 slider 设置这三个值。 您可以使用 SystemParametersInfo function 获取和设置这些值。
系统对指定的相对鼠标移动应用两个测试。 如果沿 x 或 y 轴的指定距离大于第一个鼠标阈值,并且鼠标速度不为零,则系统将距离加倍。 如果沿 x 或 y 轴的指定距离大于第二个鼠标阈值,并且鼠标速度等于 2,则系统将应用第一个阈值测试产生的距离加倍。 因此,系统可以将指定的沿 x 或 y 轴的相对鼠标移动最多乘以四倍。
您可以使用绝对坐标来完成项目。
如果指定了 MOUSEEVENTF_ABSOLUTE 值,则 dx 和 dy 包含 0 到 65,535 之间的归一化绝对坐标。 事件过程将这些坐标映射到显示表面上。 坐标(0,0)映射到显示表面的左上角; 坐标 (65535,65535) 映射到右下角。 在多显示器系统中,坐标 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键是否被按下。 过多的依赖钩子会影响操作性能。
如您所见,cursor 在第一次按下 Q 按钮后不会回到开始 position。
您可以保存起始坐标。 然后在需要的时候回到起始position。
动图演示:
更新:
为了进一步帮助您解决问题,我将演示一些操作供您参考。
我们使用GetCursorPos
来获取 ABC 的坐标。
A(267,337) B(508,334) C(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坐标。
然后我们可以根据A和D的距离计算出D的坐标,可以帮你解决移动两个相邻坐标的问题。
另外,我们可以根据实际情况微调距离。
添加以下代码,
mouseAction(267-87, 337-65, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE); // D(267-87, 337-65)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.