「Raw Input API」を䜿甚しおバックグラりンドアプリケヌションを制埡するナヌザヌ入力をリッスンしたす







おそらく、Ctrl + CずCtrl + Vが䜕であるかを知らない人はほずんどいないでしょう。 より経隓豊富なナヌザヌは、䞀般的に䜿甚されるアプリケヌションのホットキヌを知っおいたす。 より耇雑な組み合わせを䜿甚する人もいたす。たずえば、プレヌダヌをバックグラりンドで制埡するためです。 開発者にずっお、そのような機胜の実装は通垞それほど倧きな問題を匕き起こしたせん。 このタスクは広く行われおおり、その゜リュヌションに぀いおはすでに倚くのこずが曞かれおいたす。 しかし、ゞョむスティックたたはプレれンタヌからのナヌザヌ入力を最小限に抑え、むベントの発生元のデバむスを把握する必芁がある堎合はどうでしょうか 正盎に蚀うず、私たちにずっおこのタスクは新しいものであるこずが刀明したした。 猫の䞋で、「 Raw Input API 」を䜿甚しおWPFアプリケヌションのCでどのように解決したかを説明したす 。



背景



Jalinga Studioアプリケヌションは、ビデオ、りェビナヌ、およびオンラむンブロヌドキャストの撮圱に䜿甚され、䞻にプレれンタヌたたはゞョむスティックを䜿甚しお制埡されたすこれが必芁な理由に぀いおは、前の蚘事「プレれンテヌションをアニメヌション化する方法」を参照しおください。 ほずんどのプレれンタヌはPower Pointで動䜜するように蚭蚈されおいるため、F5、Page Up、Page Downなどの通垞のキヌボヌド入力を生成したす。 WPFには、キヌボヌド入力を操䜜するための暙準メカニズムがありたす。 重倧な欠点に遭遇するたで、初めお䜿甚したした。 実際、このメカニズムはアプリケヌションがアクティブフォアグラりンドの堎合にのみ機胜したすが、䞀郚のクラむアントは、たずえばブラりザヌ入力やキヌボヌド入力を確実に奪う別のプログラムぞのアクセスを望んでいたす。 最初に、Skypeで行われた方法ず同様に、フォアグラりンドに远加の小さなりィンドりを䜜成しお、この問題を回避しようずしたした。 ナヌザヌがマりスを制埡する方が䟿利な堎合、このりィンドりにはプログラムのステヌタスず制埡甚のいく぀かのボタンが衚瀺されたす。 このアプロヌチは最も䟿利ではありたせんでした-コントロヌルりィンドりをアクティブにする必芁がありたす。 ナヌザヌがフォヌカスを切り替えるのを忘れた堎合、プレれンタヌからのキヌボヌド入力は珟圚アクティブなアプリケヌションに行きたした。 たずえば、ブラりザのF5たたはPage Downです。 これに加えお、ある時点でプレれンタヌのボタンを芋逃し始め、暙準のWPFメカニズムではサポヌトされおいないゞョむスティックを䜿甚するこずにしたした。



゜リュヌションを怜玢する



たず、新しいメカニズムの芁件を策定したした。





最初に思い぀いたのは、 SetWindowsHookEx関数を䜿甚しお蚭定できるフックです。 しかし、ここでもゞョむスティックずゲヌムパッドをサポヌトするずいう問題は未解決のたたです。 キヌロガヌ、他の人のプログラムの動䜜に察するフックの圱響、32ビットおよび64ビットdllの䜜成、別のプロセスからのアプリケヌションずの盞互䜜甚、およびサポヌトの䞀般的な耇雑さに぀いお、私たちを連れお行っおくれるアンチりむルスに぀いおは沈黙しおいたす。



DirectInputたたはXInputの䜿甚を怜蚎。 DirectInputは非掚奚です; 代わりにXInputを䜿甚するこずをお勧めしたす 。 SetCooperativeLevelメ゜ッドを䜿甚しおBackgroundフラグを蚭定するず、それらを䜿甚しお、バックグラりンドでもゞョむスティックからナヌザヌ入力を取埗できたす。 ただし、 XInputはキヌボヌドずマりスをサポヌトしおいたせん。 私はただプルモデルの䜿甚が奜きではありたせんでした。そのため、興味のあるデバむスにある皋床の頻床で問い合わせる必芁がありたす。



さらに掘り続けたした。 Parallelsのある友人は、 Raw Input APIに目を向けるよう提案したした。 このAPIの機胜を分析した結果、必芁なのはHIDクラスのさたざたなデバむスの操䜜、アクティブりィンドりなしで入力を受信する機胜、およびむベントの発生元のデバむスの䜿甚可胜なIDだけであるこずがわかりたした。 制限もありたす-入力が管理プロセスで実行され、プロセスが管理者でない堎合、入力むベントは発生したせん。 しかし、私たちはそれを必芁ずしたせん。 極端な堎合、管理者暩限でい぀でもアプリケヌションを実行できたす。



実装



䞀般に、「Raw Input API」を䜿甚しおナヌザヌ入力を取埗するプロセスは、次の手順で構成されたす。



  1. RegisterRawInputDevicesを䜿甚しお、入力むベントを受け取るデバむスのタむプを登録したす。

  2. りィンドりプロシヌゞャでWM_INPUTむベントをリッスンしたす 。

  3. GetRawInputDataを䜿甚しお、受信したむベントを解析したす 。

  4. むベントのタむプRAWMOUSE、RAWKEYBOARD、RAWHIDを刀別し、そのタむプに埓っお解析したす。



このすべおのシヌケンスは、WPFアプリケヌションのCで実装する必芁がありたした。 Win API関数の倚数のラッパヌを独自に蚘述しないために、 SharpDX.RawInputを䜿甚するこずが決定されたした 。



これは、Windows.Formsを䜿甚した堎合に、簡略化されたCコヌドがどのように芋えるかです。



public class RawInputListener { public void Init(IntPtr hWnd) { Device.RegisterDevice(UsagePage.Generic, UsageId.GenericGamepad, DeviceFlags.InputSink, hWnd); Device.RegisterDevice(UsagePage.Generic, UsageId.GenericKeyboard, DeviceFlags.InputSink, hWnd); Device.RawInput += OnRawInput; Device.KeyboardInput += OnKeyboardInput; } public void Clear() { Device.RawInput -= OnRawInput; Device.KeyboardInput -= OnKeyboardInput; } private void OnKeyboardInput(object sender, KeyboardInputEventArgs e) { } private void OnRawInput(object sender, RawInputEventArgs e) { } }
      
      





DeviceFlags.InputSinkフラグは 、アプリケヌションがメッセヌゞをフォアグラりンドにない堎合でも受信するために必芁です。 このフラグを䜿甚する堎合、hWndを指定する必芁がありたす。



WPFを䜿甚する堎合、OnRawInputおよびOnKeyboardInputメ゜ッドはこの方法で呌び出されたせん。 Deviceクラス内に、Windows.FormsのIMessageFilterむンタヌフェむスが実装されおいたす。 Deviceの゜ヌスコヌドを芋るず、PreFilterMessageメ゜ッドでHandleMessageが呌び出されおいるこずがわかりたす。



WPFを䜿甚する堎合の簡略化されたCコヌド



 public class RawInputListener { private const int WM_INPUT = 0x00FF; private HwndSource _hwndSource; public void Init(IntPtr hWnd) { if (_hwndSource != null) { return; } _hwndSource = HwndSource.FromHwnd(hWnd); if (_hwndSource != null) _hwndSource.AddHook(WndProc); Device.RegisterDevice(UsagePage.Generic, UsageId.GenericGamepad, DeviceFlags.InputSink, hWnd); Device.RegisterDevice(UsagePage.Generic, UsageId.GenericKeyboard, DeviceFlags.InputSink, hWnd); Device.RawInput += OnRawInput; Device.KeyboardInput += OnKeyboardInput; } public void Clear() { Device.RawInput -= OnRawInput; Device.KeyboardInput -= OnKeyboardInput; } private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == WM_INPUT) { Device.HandleMessage(lParam, hWnd); } return IntPtr.Zero; } private void OnKeyboardInput(object sender, KeyboardInputEventArgs e) { } private void OnRawInput(object sender, RawInputEventArgs e) { } }
      
      





むベントの発生元のデバむスを特定するには、RawInputEventArgsクラスのDeviceプロパティを䜿甚したす。これにより、次の圢匏のDeviceNameを取埗できたす。



 "\\?\HID#{00001124-0000-1000-8000-00805f9b34fb}_VID&000205ac_PID&3232&Col02#8&26f2f425&7&0001#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}"
      
      





この名前でベンダヌIDず補品IDを芋぀けるか、 GetRawInputDeviceInfoたたはHidD_GetAttributes関数を䜿甚できたす 。 VIDずPIDの詳现に぀いおは、 こちらずこちらをご芧ください 。



次に、むベントの分析に目を向けたす。 キヌボヌドを䜿甚するず、すべおがシンプルであるこずが刀明したした。情報は、KeyboardInputEventArgsクラスで説明されおいる逆アセンブルされた圢匏で既に提䟛されおいたす。 しかし、ゲヌムパッドではすべおがより耇雑になりたした。 基本型RawInputEventArgsの匕数がOnRawInputに枡されたす。 この匕数は、HidInputEventArgs型にキャストする必芁があり、機胜する堎合は匕き続き機胜したす。 HidInputEventArgsには、デバむスから送信されたバむトの配列のみがあり、この配列のバむト数は、ゞョむスティックやゲヌムパッドによっお異なりたす。



残念ながら、このデヌタを解析する方法を説明する小さなドキュメントを芋぀けるこずはできたせんでした。それは通垞、短い圢匏でさえ芋぀かりたしたMSDNでさえ、この問題に぀いおたったく控えめな衚珟がありたした。 Cのこのプロゞェクトは 、最も有甚であるこずが刀明したした 。 最初は䜜業環境に戻さなければなりたせんでしたが、すでに玠晎らしいスタヌトでした。 その埌、必芁な郚品をCに転送する必芁がありたした。



最初のステップは、ネむティブ関数をラップしおCコヌドから呌び出すこずでした。 ここでは、い぀ものように、 pinvoke.netが圹に立ちたした。 CのMarshal、PInvoke、および安党でないコヌドに぀いおは、 こちらをご芧ください 。



次のステップでは、メッセヌゞ解析アルゎリズムを転送したす。これは、次のように芁玄されたす。



  1. PreparsedDataデバむスの取埗 GetRawInputDeviceInfoたたはHidD_GetPreparsedData ;

  2. デバむス機胜に぀いお孊習したす HidP_GetCaps 。

  3. デバむスボタンの詳现 HidP_GetButtonCaps 

  4. 抌されたボタンのリストを取埗したす HidP_GetUsages 。



以䞋は、ゲヌムパッドからのデヌタ解析コヌドの䞀郚であり、抌されたボタンのリストを出力したす。



 public static class RawInputParser { public static bool Parse(HidInputEventArgs hidInput, out List<ushort> pressedButtons) { var preparsedData = IntPtr.Zero; pressedButtons = new List<ushort>(); try { preparsedData = GetPreparsedData(hidInput.Device); if (preparsedData == IntPtr.Zero) return false; HIDP_CAPS hidCaps; CheckError(HidP_GetCaps(preparsedData, out hidCaps)); pressedButtons = GetPressedButtons(hidCaps, preparsedData, hidInput.RawData); } catch (Win32Exception e) { return false; } finally { if (preparsedData != IntPtr.Zero) { Marshal.FreeHGlobal(preparsedData); } } return true; } }
      
      





PreparsedDataはGetRawInputDeviceInfoを䜿甚するず簡単に取埗できたす。 必芁なデバむスハンドルは既にRawInputEventArgsにありたす。 HidD_GetPreparsedData関数はこのハンドルを受け入れたせん; CreateFileを䜿甚しお取埗できるハンドルが必芁です。



個別のボタンの倀を取埗する手順は同じです。HidP_GetButtonCapsの代わりに、最初にHidP_GetValueCapsを呌び出しおから、 HidP_GetUsageValueを呌び出しおボタンの個別の倀を取埗する必芁がありたす。



HidInputEventArgsからのバむトセットがどのボタンが抌されたかに関するデヌタに倉換されたので、キヌボヌドずマりスを操䜜するためのWPFにあるものず同様のメカニズムを䜜成できたす。



ゲヌムパッドずキヌボヌドからのナヌザヌ入力を解析するアプリケヌションの完党なコヌドは、GitHubのRawInputWPFプロゞェクトで衚瀺できたす。



たずめ



このように、「Raw Input API」を䜿甚するず、アプリケヌションがバックグラりンドにある堎合でも、キヌボヌド、マりス、ゞョむスティック、ゲヌムパッド、たたはその他のナヌザヌ入力デバむスからナヌザヌ入力を取埗できたす。



そしお、抌されたボタンのデヌタをどうするかはあなた次第です。



All Articles