MacBook Pro 2017 13寸版 觸摸板windows驅動開發(開發HID鼠標鍵盤驅動之一)

                                                     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纔是觸摸板驅動。詳細可看下邊的圖示:


畫紅線的部分,在”系統設備“裏邊的 “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平臺下沒啥用的雞肋。
另外兩個看下圖所示:


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會不同,這個得看具體機器。


如上圖所示,在裏邊的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的手勢行爲,則無需留意本文的內容。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章