MacBook Pro 13寸版 触摸板windows驱动开发(开发HID鼠标键盘驱动之一)

时间:2024-04-08 07:51:34
                                                     by fanxiushu 2017-10-27 转载或引用请注明原始作者。


做这个驱动,写这篇文章的目的就是因为macBook pro 2017版的触摸板在windows平台下难用,

于是决定重新开发macbook pro 2017触摸板的windows驱动。

已经开发好的驱动和源代码下载地址:

GITHUB:   https://github.com/fanxiushu/kmouse_filter-AppleSPITrack-driver

CSDN:   http://download.csdn.net/download/fanxiushu/10047600


如下连接,
http://blog.csdn.net/fanxiushu/article/details/78186745
因为新近换的电脑是macbook pro 2017 13寸带bar的机器,其他还能将就,
就是触摸板难用,动不动就弹出右键菜单,经常误触,
而且尤其不爽的是拖动时候,必须按住边缘,另一只手指才能拖动,
触摸板这么大,基本无法单手进行拖动操作,需要另一个手的手指按住触摸板边缘,然后拖动。
虽然可以点击两次,第2次不离开触摸板来拖动,但是非常不习惯这种手势。
还是习惯传统的食指按住触摸板,中指来拖动(因为这种手势更加接近按住鼠标左键移动鼠标的效果)。

像我这种经常把电脑放到大腿上或者床上的人(反正就是不会老老实实的放到电脑桌上),
如果带个鼠标是很不方便的,只能依赖触摸板来控制电脑。
而且我的要求也不高,不需要什么多手势,只要触摸板能尽量模拟鼠标的效果就行了。
其实个人觉得windows本身的易操作性,使用鼠标的效果就能控制操作系统的所有的东西了,
手势多了我也记不住,每次切换不同手势还得思考一下也挺累(也许是还没习惯,也懒得去习惯了)。

基于以上各种原因,于是决定重新开发这款电脑的触摸板驱动。
首先解释一下“点按”和”轻点“:
按住或重压,就是按下去,能听到“哒”的一声响;
”点按“就是“哒”的一声按下去然后立马弹上来,
还有一个就是”轻点“,就是手指接触到触摸板然后迅速离开。

我需要达到的效果也非常简单明了,用两根手指模拟鼠标效果,操作整个系统。
1,首先一根手指按住触摸板的任意位置(是任意位置而不是触摸板边缘),另一根手指在触摸板上移动来达到拖动效果,
这就相当于按住鼠标左键,移动鼠标的效果一样。
2,一根手指轻点,相当于鼠标左键按下去然后立马弹上来。
     一根手指按住触摸板,相当于按下鼠标左键,从触摸板弹上来相当于弹出左键。
3,两根手指同时轻点,相当于鼠标右键按下去然后立马弹上来。
     一根手指按住触摸板右边四分之三到四分之四部分(是整个右边3/4,而不是右下边缘),相当于按住鼠标右键。
4,两根手指同时在触摸板移动,相当于滚轮滚动。
5,另外再附加实现一个功能:三指拖移。等于是第1个功能的效果:一个手指按住,另一个手指移动。
      这样不重压触摸板的也能操作整个系统。

不模拟鼠标的中间键按下效果,好像中间键没啥用处。

以上就是我的简单明了的要求,当然习惯各种手势的你可能并不赞同,甚至有点嗤之以鼻,然而这依然是我的简单明了的要求。
重新开发的触摸板驱动也只实现上边的功能而已。

要重新开发macbook pro 2017年 13寸带bar(以下简称mbp2017)的触摸板的windows驱动,
首先需要解决两件事:
一,采集mbp2017的触摸板数据,
二,模拟开发鼠标驱动。
第二个问题100%的确保能解决,使用HID模式的鼠标驱动就可以了。
关键是第一个问题,如果不能采集和解析触摸板的数据,基本就没戏了,只能老老实实的使用那个难用的原装驱动程序。
mbp2017的触摸板数据结构格式是没有公开的,而且未来也很可能被苹果公司改变,因此没有通用性,
我测试的对应bootcamp版本是 6.1.6813,对应的触摸板总线驱动的驱动日期是 2016/05/26, 版本 6.1.6500.0,
其他版本的没测试过,所以不知道的数据格式是不是不同。

既然这个数据没有公开,我们就必须要自己来采集和分析触摸板的数据,
好在触摸板这类设备本身的数据量不大,数据结构也应该不会多复杂,只要Apple公司没变态到做加密,估计是能解析出来的。
自己动手解析之前,先要搞清楚它的驱动运行流程。
苹果使用的SPI总线来传输触摸板和键盘的数据,SPI总线接口,也是我在接触mbp2017时候才发现还有这么一个玩意,真是孤陋寡闻了。
SPI 全称Serial Peripheral Interface--串行外设接口, 最初是Motorola提出和开发的,它使用主从模式通讯,这点跟USB有点像,
同时通讯也很简单,比RS232(串口)还简单,所以不论软件或硬件成本都比较低。
但是应付键盘和触摸板这类不需要大量通讯数据的器件完全足够了。更详细的关于SPI介绍,请查询其他资料。
mbp2017的电脑windows驱动中关于SPI的,首先有个SPI总线驱动,在SPI总线驱动下挂载两个位置,
位置1是键盘驱动,位置2才是触摸板驱动。详细可看下边的图示:
MacBook Pro 2017 13寸版 触摸板windows驱动开发(开发HID鼠标键盘驱动之一)

画红线的部分,在”系统设备“里边的 “Apple SPI Device ” 就是总线驱动,它负责给键盘和触摸板的功能驱动提供数据,
在“人体学输入设备”里边的“ Apple SPI Keyboard” 和 “ Apple SPI Trackpad” 对应的就是 键盘的功能驱动和触摸板的功能驱动,
再看“Apple SPI Trackpad” 的属性, 它在总线驱动的位置是 2, 我们要做的事情,就是替换这个功能驱动,使用我们自己开发的驱动来代替。

另外我们顺便看看这款电脑内置的USB接口的设备,
上边的蓝线部分,在 “通用串行总线控制器” 里边存在一个“Apple USB Composite Device”的复合设备,
这个复合设备管理着三个设备,“Apple Touch Bar”就是其中一个,这个就是multi-touch bar, 在windows平台下没啥用的鸡肋。
另外两个看下图所示:
MacBook Pro 2017 13寸版 触摸板windows驱动开发(开发HID鼠标键盘驱动之一)

Apple USB Composite Device一共包含三个设备:
FaceTime HD Camera,
Apple Touch Bar,
USB接口的光感氛围器,就是检测环境光线强弱,从而自动调节屏幕的亮度。
看图示的最下边, bNumConfigurations 是 3, 也就是三个配置描述符,分别对应这三种设备。

正如上篇文章所说的,在单纯只安装windows系统的时候,这个“Apple USB Composite Device” 设备不能被发现,
从而造成 摄像头,multi-touch bar,和光感器件找不到,也不知道苹果在设计硬件的时候搞了什么。
估计得保留安装苹果系统时候的那个EFI Parttion 分区,才能被识别,只是后来没再折腾了,保留了MacOS系统。

再回到触摸板驱动,我们打开注册表,在 如下的位置:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\SPI\VID_05ac&PID_0277&MI_02
能找到这个驱动的安装信息,
注意:mbp2017 对应的触摸板的PID是0277, VID是05ac, 不同型号的,可能PID会不同,这个得看具体机器。
MacBook Pro 2017 13寸版 触摸板windows驱动开发(开发HID鼠标键盘驱动之一)

如上图所示,在里边的LowerFilters字段里是 苹果的 AppleSPITrackpad驱动,
Service字段是 mshidkmdf ,从这信息,
我们立马就知道 AppleSPITrackpad 是一个KMDF模型的驱动程序,并且属于标准的HID的KMDF程序。
AppleSPITrackpad负责获取上边提到的 “Apple SPI Device" 总线驱动发上来的触摸板数据,
并且解析模拟成标准的兼容windows的 HID鼠标驱动数据。


AppleSPITrackpad在解析触摸板数据时候,提供了让人难以适应(至少我比较难适应)的鼠标动作模拟,
因此重新开发这个驱动来解决这个问题。
至此,我们大致知道他们的驱动的工作流程了。

现在的任务首先就是获取SPI总线驱动发上来的数据结构格式。
可以开发一个简单的WDM 过滤驱动,挂载到 LowerFilters字段里边的 AppleSPITrackpad 前边。
这样AppleSPITrackpad跟SPI总线驱动通讯的所有IRP请求都能被截获到,从而就能获取到触摸板数据。
WDM Filter的开发可以查看我很早前的一篇文章:
http://blog.csdn.net/fanxiushu/article/details/8834385
当然如果你能找到其他现成的工具来分析AppleSPITrackpad的通讯数据,会更快捷。

通过分析,AppleSPITrackpad发给SPI总线驱动四个请求:
IOCTL_HID_GET_DEVICE_DESCRIPTOR
IOCTL_HID_GET_DEVICE_ATTRIBUTES
IOCTL_HID_SET_FEATURE
IOCTL_HID_READ_REPORT
都是标准的HID请求命令,其中IOCTL_HID_SET_FEATURE 应该是用于告诉SPI总线驱动开启或者关闭触摸板功能的。
最主要的就是 IOCTL_HID_READ_REPORT命令,这个就是获取触摸板数据。

分析在 bootcamp版本是 6.1.6813,对应的触摸板SPI总线驱动的驱动日期是 2016/05/26, 版本 6.1.6500.0,
(这里再次提到版本和日期,因为不同版本很可能是不同的数据格式,由于本人就一台mac机器,无法测试其他情况)

数据格式结构大致如下:
前46个字节是格式头,接着每个手指占据30个字节。
比如 一个手指在触摸板上,IOCTL_HID_READ_REPORT获取到的数据长度是46 + 30 = 76个字节,
如果是两个手指在触摸板,IOCTL_HID_READ_REPORT获取的长度是 46 + 30*2 = 106字节,以此类推。

前46个字节描述成c语言数据结构大致如下:
typedef unsigned char              u8;
//// 46 length
struct tp_protocol
{
    u8                  type;      // unknown type  =2
    u8                  clicked;   // 按住了触摸板, 不管几个按住,都是 1
    u8                  unknown1[5]; //
    u8                  is_finger;   // 触摸板有手指 1,当离开瞬间,出现 0
    u8                  unknown2[8]; //
    u8                  unknown3[8]; // 未知,固定 00-01-07-97-02-00-06-00
    u8                  finger_data_length; // 手指数据总长度, 手指个数*30
    u8                  unknown4[5]; //
    u8                  finger_number; //手指个数
    u8                  Clicked; // 同上边的clicked
    u8                  state;   // 手指在上边好像是 0x10, 手指离开瞬间最高设置 1,变成 0x80(0x90),最后离开后,还会出现 0x00
    u8                  state2;  // 手指在上边 0x20,离开瞬间 变 0
    u8                  state3;  // 平时0, Clicked为 0x10
    u8                  zero;    // 始终 0
    u8                  unknown5[10]; /////
};

如上所示,其中unknown字段是 没能解析出来的,不过后来发现就已知的字段已经足够模拟鼠标动作了。

手指的30个字节的c语言结构如下:
///// 30 length
struct tp_finger
{
    short             org_x; //按下后,这个数字不变,
    short             org_y; //
    short             x;     //随着手指移动改变,
    short             y;     //
    short            unknown[11];
};

其中unknown未知,org_x,org_y好像没啥用,最有用的是 x,y。表示的是手指在触摸板的坐标位置。
整个触摸板(13寸机器)测试下来,触摸板范围,最左边大致是 -6300多, 左右边坐标 6800多,
最上边坐标7700左右,最下边-200左右。这些都是用手指移动到边界得出来的大致数据。

有了这些原始的触摸板的数据,我们基本就能确定能实现自己的mbp2017的触摸板驱动来模拟鼠标操作了。

/////////////////////////////////////
再来看看windows平台下的HID驱动开发过程,
HID(Human Interface Device,人机接口设备)是一类设备总称,用于提供人和电脑进行交互的接口设备,
像最常用的鼠标,键盘等,触摸板,还有游戏使用的游戏杆等等。
像鼠标键盘都是windows提供的标准驱动,我们在开发HID驱动时候,填写适当的HID描述符,
告诉windows我们开发的是一个HID的鼠标或者HID键盘,windows自动就会给我们加载兼容的鼠标键盘驱动。
同时我们在自己的HID驱动开发中正确响应 IOCTL_HID_XXX事件(主要是IOCTL_HID_READ_REPORT)
这个鼠标键盘就能正常工作起来。看起来是非常简单明了的。确实也是如此。
比如我们要虚拟鼠标键盘,也使用HID来开发,比起挂钩什么PS/2或者HOOK之类的做法,也显得简单和稳定得多,
这个在以后开发的博客中会继续阐述。

我们先看看WDM模型的HID开发过程,
首先在 DriverEntry中注册
IRP_MJ_INTERNAL_DEVICE_CONTROL, IRP_MJ_POWER, IRP_MJ_PNP 三个派遣函数,
然后填写 HID_MINIDRIVER_REGISTRATION 结构的参数,调用HidRegisterMinidriver注册HID的小端口驱动,
这样总体框架就建立起来了。
然后在 IRP_MJ_POWER和IRP_MJ_PNP派遣函数中,按部就班的实现电源管理和即插即用事件,
如果是真实HID设备,则要认真实现这两个功能,如果是虚拟设备,则不用太在意,找个现成框架套上去就行,
接着就是核心处理事件 IRP_MJ_INTERNAL_DEVICE_CONTROL,
在这个派遣函数中,一般处理
IOCTL_HID_GET_DEVICE_DESCRIPTOR
IOCTL_HID_GET_DEVICE_ATTRIBUTES
IOCTL_HID_READ_REPORT
三个事件就能让鼠标键盘跑起来,当然处理的越多,功能越完善,但是HID的IOCTL命令也多不多到哪去。
是的,就是这么简单的框架,
但是在这里,我们不打算使用WDM的框架,而使用的是 KMDF(WDF的内核部分,就是对WDM的封装 )来开发。

KMDF就更加省事了,连 PNP和POWER也省略了,就只要关心
IRP_MJ_INTERNAL_DEVICE_CONTROL 就可以了。
但是正如微软自己所说,他们的WDF框架跟HID的minidriver小端口驱动在某些IO请求中存在冲突(IRP_MJ_POWER和IRP_MJ_PNP)
因此WDF框架不能直接桥接 HID的class driver和mini driver,他们使用了一个折中方案,
开发一个 mshidkmdf的驱动来桥接WDF框架和classdriver,在此驱动中调用
HidRegisterMinidriver 来注册HID小端口驱动,
并且mshidkmdf作为服务安装,而
我们开发的HID驱动则作为LowerFilters来安装。详细可查看如下链接:
 https://msdn.microsoft.com/en-us/library/windows/hardware/ff540774
 这就是我们为何在上面的图中看到苹果的 AppleSPItrackpad触摸板驱动变成了 Lowerfilters 的底层过滤驱动了。

最后看看我们开发KMDF模型的这款触摸板驱动的流程:
首先在DriverEntry中,配置好参数,这里主要关心的是 EvtDeviceAdd 函数,
调用WdfDriverCreate 来初始化框架。
在EvtDeviceAdd 函数中,首先调用WdfFdoInitSetFilter 表明我们开发的是一个过滤驱动。
然后调用WdfDeviceInitAssignWdmIrpPreprocessCallback 注册IRP_MN_QUERY_ID这个特殊查询事件,
因为我们必须这么做,才能让windows识别到我们的驱动ID,
对应这块触摸板,我们还得注册两个电源事件,就是D0状态(加电和掉电)转换,
因为加电情况下,我们得通知SPI总线驱动,开启苹果的触摸板驱动,掉电情况做些停止操作处理,
大致如**册电源事件:
//设置电源回调函数
    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
    //设备处于工作(供电D0状态)或者非工作状态
    pnpPowerCallbacks.EvtDeviceD0Entry = EvtDeviceD0Entry ;// 设备加电时候被调用
    pnpPowerCallbacks.EvtDeviceD0Exit = EvtDeviceD0Exit;      // 设备掉电时候被调用
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); ///

然后就是创建过滤设备,创建IO队列,一共两个队列,一个是默认的IO队列,用于处理 InternalDeviceControl请求,
一个是手动队列,我们在处理 IOCTL_HID_READ_REPORT时候,需要入队等待处理,
然后就是初始化一些相关变量等数据。
这里我们读取SPI总线驱动的原始触摸板数据,使用的是串行读取,因此整个驱动创建一个全局的Request来重复使用,
InternalDeviceControl 的IOCTL_HID_GET_DEVICE_DESCRIPTOR,IOCTL_HID_GET_DEVICE_ATTRIBUTES
中我们把事先准备好的HID的鼠标描述符,属性等信息报告给classdriver, 
然后接收到 IOCTL_HID_READ_REPORT命令时候调用WdfRequestForwardToIoQueue 加到手动的IO队列。

EvtDeviceD0Entry函数被调用(就是设备加电了),发起全局的Request对SPI总线驱动的读取操作,
同时设置这个Request的完成回调函数,在完成函数中分析处理读取到的触摸板数据,处理完成后接着继续发起对触摸板数据的读取。
 
更详细的请查看稍后发布到GITHUB和CSDN上的源代码和驱动程序。

驱动实现的功能一个5个(如上边所说)
1,一个手指按住触摸板任意位置,另一个手指移动来达到拖动效果
2,一个手指轻点或者一个手指按下触摸板,模拟鼠标左击
3,两个手指轻点,或者一个手指按住触摸板右边3/4-4/4位置,模拟鼠标右击
4,双指同时移动来模拟滚轮滚动
5,三指拖移。

如果你已经适应了苹果的触摸板windows的手势行为,则无需留意本文的内容。