鼠标宏设计之三-虚拟鼠标的设计

鼠标宏设计之三-虚拟鼠标的设计本文探讨了鼠标在主机侧的数据流路径 从硬件到软件层面 区分了应用层通过 Post SendMessage 处理鼠标事件和内核层通过虚拟驱动直接上报数据的方式

大家好,欢迎来到IT知识分享网。

这个系列前几篇文章分别描述了光电鼠标、HID硬件鼠标,它们相对于主机来说,是在鼠标硬件层面的实现,那么其实也有相对主机来说的软件方案。

注意: 这篇文章并不是囊括所有这方面的软件方案的原理描述,实际上由于本人的技术局限性,也许有的方案我自己也没有了解过,遗漏在所难免!

鼠标在主机侧的数据流

在软件设计中,某些时候跟踪数据流是一个剖析整个体系的好办法,由于鼠标和键盘最终就是数据流的船体,所以我们跟踪数据流,会对整个方案有一些了解,

鼠标数据通过PCI总线,如果是USB鼠标,这个数据最终回到USB总线控制器上,然后下发给对应的HID驱动,HID驱动将鼠标的数据进行处理后,转发给操作系统,我怀疑这里是给了窗口管理器,不过我没时间去详细调试。

在收到消息之后,系统会根据鼠标目前的位置知道要发给哪个窗口,然后使用SendMessage或者PostMessage一类的将消息投递对应的线程的队列中,然后唤醒线程,让它去处理这个事件。

这个过程中,有些是系统公开的,有些是需要调试的,所以我们可以把它理解为一个数据链,在这个数据链上的任意节点都可以实现虚拟鼠标和键盘。不过为了方便,我们简单将它们分为应用层方案和内核层方案,同时我们避免讨论Hook技术对方案的影响。

应用层方案

应用层方案本质上是对Post/SendMessage/sendInput(mouse_event/keybd_event)的处理,对于任意窗口来说,它们都可以通过SendMessage之类的函数接收到鼠标键盘消息。

// // // Sends Win + D to toggle to the desktop // // void ShowDesktop() { OutputString(L"Sending 'Win-D'\r\n"); INPUT inputs[4] = {}; ZeroMemory(inputs, sizeof(inputs)); inputs[0].type = INPUT_KEYBOARD; inputs[0].ki.wVk = VK_LWIN; inputs[1].type = INPUT_KEYBOARD; inputs[1].ki.wVk = 'D'; inputs[2].type = INPUT_KEYBOARD; inputs[2].ki.wVk = 'D'; inputs[2].ki.dwFlags = KEYEVENTF_KEYUP; inputs[3].type = INPUT_KEYBOARD; inputs[3].ki.wVk = VK_LWIN; inputs[3].ki.dwFlags = KEYEVENTF_KEYUP; UINT uSent = SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT)); if (uSent != ARRAYSIZE(inputs)) { OutputString(L"SendInput failed: 0x%x\n", HRESULT_FROM_WIN32(GetLastError())); } }

在我看来sendInput是对SendMessage系列函数的封装。

我们可以在应用层这么做,但是这些都会有些限制,游戏本身也是在应用层,这个层面无限PK最终回到内核层。

内核层方案

在内核层,事情会简单许多,任何属于Mouse类的设备对象都可以直接上报鼠标数据到窗口管理器,重点就在于打算怎么做?

1. 创建一个虚拟的mouse驱动,然后挂载即可,同时在标准的IOCTL之外创建一些非标准的IOCTL,用来接收应用层的通讯请求。

2. 创建一个USB过滤驱动,然后挂载在鼠标驱动之上,同时在标准的IOCTL之外创建一些非标准的IOCTL,用来接收应用层的通讯请求。

3. 使用libusb驱动作为过滤驱动,然后和应用层通讯即可。

内核驱动会比想象中要简单许多,但是需要对协议很了解才行。下面是I/O函数,这个案例是微软的鼠标驱动,可以作为参考,这部分代码后续我会上传。

目前已经将完整的代码上传:

VOID MouFilter_EvtIoInternalDeviceControl( IN WDFQUEUE Queue, IN WDFREQUEST Request, IN size_t OutputBufferLength, IN size_t InputBufferLength, IN ULONG IoControlCode ) /*++ 例程描述: 此例程是内部设备控制请求的调度例程。有两种特定的控制代码值得关注: IOCTL_INTERNAL_MOUSE_CONNECT: 存储旧的上下文和函数指针,并将其替换为我们自己的。这比拦截RIT发送的IRP要简单得多,在返回途中对其进行修改。 IOCTL_INTERNAL_I8042_HOOK_MOUSE: 添加必要的函数指针和上下文值,这样我们就可以更改ps/2鼠标的初始化方式。 注意:如果您所要做的就是过滤MOUSE_INPUT_DATA。您可以删除处理代码和所有相关的设备扩展字段,以及起到节省空间的作用。 --*/ { PDEVICE_EXTENSION devExt; PCONNECT_DATA connectData; PINTERNAL_I8042_HOOK_MOUSE hookMouse; NTSTATUS status = STATUS_SUCCESS; WDFDEVICE hDevice; size_t length; UNREFERENCED_PARAMETER(OutputBufferLength); UNREFERENCED_PARAMETER(InputBufferLength); PAGED_CODE(); hDevice = WdfIoQueueGetDevice(Queue); devExt = FilterGetData(hDevice); switch (IoControlCode) { // 将鼠标类设备驱动程序连接到端口驱动程序。 case IOCTL_INTERNAL_MOUSE_CONNECT: // 只允许一个连接 if (devExt->UpperConnectData.ClassService != NULL) { status = STATUS_SHARING_VIOLATION; break; } // 将连接参数复制到设备扩展名。 status = WdfRequestRetrieveInputBuffer(Request, sizeof(CONNECT_DATA), &connectData, &length); if(!NT_SUCCESS(status)) { DebugPrint(("WdfRequestRetrieveInputBuffer failed %x\n", status)); break; } devExt->UpperConnectData = *connectData; // 钩住报告链。每次向报告鼠标数据包时 // 系统将调用MouFilter_ServiceCallback connectData->ClassDeviceObject = WdfDeviceWdmGetDeviceObject(hDevice); connectData->ClassService = MouFilter_ServiceCallback; break; // 断开鼠标类设备驱动程序与端口驱动程序的连接。 case IOCTL_INTERNAL_MOUSE_DISCONNECT: // 清除设备扩展中的连接参数。 // devExt->UpperConnectData.ClassDeviceObject = NULL; // devExt->UpperConnectData.ClassService = NULL; status = STATUS_NOT_IMPLEMENTED; break; // 将此驱动程序附加到的初始化和字节处理 // i8042(即PS/2)鼠标。只有当你想进行PS/2时,这才是必要的 // 特定函数,否则挂接CONNECT_DATA就足够了 case IOCTL_INTERNAL_I8042_HOOK_MOUSE: DebugPrint(("hook mouse received!\n")); // 从请求中获取输入缓冲区 // (Parameters.DeviceIoControl.Type3InputBuffer) status = WdfRequestRetrieveInputBuffer(Request, sizeof(INTERNAL_I8042_HOOK_MOUSE), &hookMouse, &length); if(!NT_SUCCESS(status)) { DebugPrint(("WdfRequestRetrieveInputBuffer failed %x\n", status)); break; } // 设置isr例程和上下文,并记录该驱动程序之上的任何值 devExt->UpperContext = hookMouse->Context; hookMouse->Context = (PVOID) devExt; if (hookMouse->IsrRoutine) { devExt->UpperIsrHook = hookMouse->IsrRoutine; } hookMouse->IsrRoutine = (PI8042_MOUSE_ISR) MouFilter_IsrHook; // 存储我们将来可能需要的所有其他功能 devExt->IsrWritePort = hookMouse->IsrWritePort; devExt->CallContext = hookMouse->CallContext; devExt->QueueMousePacket = hookMouse->QueueMousePacket; status = STATUS_SUCCESS; break; // 可能想在未来多做点什么。现在,把这些I/O请求传下去 // 堆栈。这些查询必须成功,RIT才能进行通信 // 用鼠标。 case IOCTL_MOUSE_QUERY_ATTRIBUTES: default: break; } if (!NT_SUCCESS(status)) { WdfRequestComplete(Request, status); return ; } MouFilter_DispatchPassThrough(Request,WdfDeviceGetIoTarget(hDevice)); }

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/127062.html

(0)
上一篇 2025-09-13 21:20
下一篇 2025-09-13 21:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信