Skip to content

Commit cba4a7e

Browse files
authored
Merge pull request #3 from dotnet-campus/t/lindexi_dotnet
加上更加详细的设备插拔信息
2 parents 5a17721 + 5e01b6a commit cba4a7e

File tree

5 files changed

+221
-22
lines changed

5 files changed

+221
-22
lines changed

ManipulationDemo/MainWindow.xaml.cs

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
using System.Runtime.ExceptionServices;
1616
using System.Threading;
1717
using System.Threading.Tasks;
18+
using Windows.Win32;
19+
using static ManipulationDemo.UsbNotification;
20+
using System.Runtime.InteropServices;
21+
using System.Text.RegularExpressions;
1822

1923
namespace ManipulationDemo
2024
{
@@ -107,7 +111,7 @@ public MainWindow()
107111

108112
private readonly DispatcherTimer _timer;
109113

110-
private void OnTick(object sender, EventArgs e)
114+
private unsafe void OnTick(object sender, EventArgs e)
111115
{
112116
try
113117
{
@@ -154,6 +158,9 @@ private void OnTick(object sender, EventArgs e)
154158
device.Name, device.StylusDevices.Count, tabletSize));
155159
}
156160
}
161+
162+
AppendPointerDeviceInfo(builder);
163+
157164
PhysicalSizeRun.Text = builder.ToString();
158165
}
159166
catch (Exception ex)
@@ -162,6 +169,38 @@ private void OnTick(object sender, EventArgs e)
162169
}
163170
}
164171

172+
/// <summary>
173+
/// 添加 Pointer 消息的信息
174+
/// </summary>
175+
/// <param name="stringBuilder"></param>
176+
private static unsafe void AppendPointerDeviceInfo(StringBuilder stringBuilder)
177+
{
178+
try
179+
{
180+
// 获取 Pointer 设备数量
181+
uint deviceCount = 0;
182+
PInvoke.GetPointerDevices(ref deviceCount,
183+
(Windows.Win32.UI.Controls.POINTER_DEVICE_INFO*) IntPtr.Zero);
184+
Windows.Win32.UI.Controls.POINTER_DEVICE_INFO[] pointerDeviceInfo =
185+
new Windows.Win32.UI.Controls.POINTER_DEVICE_INFO[deviceCount];
186+
fixed (Windows.Win32.UI.Controls.POINTER_DEVICE_INFO* pDeviceInfo = &pointerDeviceInfo[0])
187+
{
188+
// 这里需要拿两次,第一次获取数量,第二次获取信息
189+
PInvoke.GetPointerDevices(ref deviceCount, pDeviceInfo);
190+
stringBuilder.AppendLine($"PointerDeviceCount:{deviceCount} 设备列表:");
191+
foreach (var info in pointerDeviceInfo)
192+
{
193+
stringBuilder.AppendLine($" - {info.productString}");
194+
}
195+
}
196+
}
197+
catch (Exception e)
198+
{
199+
// 也许是在非 Win8 或以上的系统,抛出找不到方法,这个 GetPointerDevices 方法是 Win8 加的
200+
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpointerdevices
201+
}
202+
}
203+
165204
private void OnStylusDown(object sender, StylusDownEventArgs e)
166205
{
167206
StylusDownStoryboard.Begin();
@@ -230,7 +269,8 @@ private void OnManipulationCompleted(object sender, ManipulationCompletedEventAr
230269
protected override void OnSourceInitialized(EventArgs e)
231270
{
232271
base.OnSourceInitialized(e);
233-
var source = (HwndSource) PresentationSource.FromVisual(this);
272+
var source = (HwndSource) PresentationSource.FromVisual(this)!;
273+
UsbNotification.RegisterUsbDeviceNotification(source.Handle);
234274
source?.AddHook(HwndHook);
235275

236276
Log("程序启动时");
@@ -240,20 +280,80 @@ protected override void OnSourceInitialized(EventArgs e)
240280
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
241281
{
242282
// 检查硬件设备插拔。
243-
if (msg == (int) WindowMessages.DEVICECHANGE)
283+
if (msg == (int)WindowMessages.DEVICECHANGE)
244284
{
245-
var eventText = $"Event={(WindowsMessageDeviceChangeEventEnum)wparam}";
285+
// 是否应该加上通用的变更记录日志
286+
bool shouldCommonLog = true;
287+
288+
bool isDeviceArrival = (int)wparam == (int)WindowsMessageDeviceChangeEventEnum.DBT_DEVICEARRIVAL;
289+
bool isDeviceRemoveComplete = (int)wparam == (int)WindowsMessageDeviceChangeEventEnum.DBT_DEVICEREMOVECOMPLETE;
290+
291+
if (isDeviceArrival || isDeviceRemoveComplete)
292+
{
293+
// 设备被移除或插入,试试拿到具体是哪个设备
294+
DEV_BROADCAST_HDR hdr =
295+
(DEV_BROADCAST_HDR) Marshal.PtrToStructure(lparam, typeof(DEV_BROADCAST_HDR));
296+
if (hdr.dbch_devicetype == UsbNotification.DbtDevtypDeviceinterface)
297+
{
298+
DEV_BROADCAST_DEVICEINTERFACE deviceInterface =
299+
(DEV_BROADCAST_DEVICEINTERFACE) Marshal.PtrToStructure(lparam,
300+
typeof(DEV_BROADCAST_DEVICEINTERFACE));
301+
302+
var classguid = deviceInterface.dbcc_classguid;
303+
// 这里的 classguid 默认会带上 name 上,于是就用不着
304+
305+
var size = Marshal.SizeOf(typeof(DEV_BROADCAST_DEVICEINTERFACE));
306+
var namePtr = lparam + size;
307+
var nameSize = hdr.dbch_size - size;
308+
// 使用 Unicode 读取的话,一个字符是两个字节
309+
var charLength = nameSize / 2;
310+
var name = Marshal.PtrToStringUni(namePtr, charLength);
311+
if (string.IsNullOrEmpty(name))
312+
{
313+
name = "读取不到设备名";
314+
}
315+
316+
string pid = string.Empty;
317+
string vid = string.Empty;
318+
319+
var pidMatch = Regex.Match(name, @"PID_([\dA-Fa-f]{4})");
320+
if (pidMatch.Success)
321+
{
322+
pid = pidMatch.Groups[1].Value;
323+
}
324+
325+
var vidMatch = Regex.Match(name, @"VID_([\dA-Fa-f]{4})");
326+
if (vidMatch.Success)
327+
{
328+
vid = vidMatch.Groups[1].Value;
329+
}
330+
331+
Log(DeviceChangeListenerTextBlock, $"[WM_DEVICECHANGE] 设备{(isDeviceArrival?"插入":"拔出")} PID={pid} VID={vid}\r\n{name}", true);
332+
333+
// 换成带上更多信息的记录,不需要通用记录
334+
shouldCommonLog = false;
335+
}
336+
}
246337

247-
Log(DeviceChangeListenerTextBlock, $"[WM_DEVICECHANGE]设备发生插拔 0x{wparam.ToString("X4")}-0x{lparam.ToString("X4")};{eventText}", true);
248-
LogDevices();
338+
if (shouldCommonLog)
339+
{
340+
var eventText = $"Event={(WindowsMessageDeviceChangeEventEnum) wparam}";
341+
342+
Log(DeviceChangeListenerTextBlock,
343+
$"[WM_DEVICECHANGE]设备发生插拔 Param=0x{wparam.ToString("X4")}-0x{lparam.ToString("X4")};{eventText}",
344+
true);
345+
LogDevices();
346+
}
249347
}
250-
else if (msg == (int) WindowMessages.TABLET_ADDED)
348+
else if (msg == (int)WindowMessages.TABLET_ADDED)
251349
{
252-
Log(DeviceChangeListenerTextBlock, $"[TABLET_ADDED]触摸设备插入 0x{wparam.ToString("X4")} - 0x{lparam.ToString("X4")}", true);
350+
Log(DeviceChangeListenerTextBlock,
351+
$"[TABLET_ADDED]触摸设备插入 0x{wparam.ToString("X4")} - 0x{lparam.ToString("X4")}", true);
253352
}
254-
else if (msg == (int) WindowMessages.TABLET_DELETED)
353+
else if (msg == (int)WindowMessages.TABLET_DELETED)
255354
{
256-
Log(DeviceChangeListenerTextBlock, $"[TABLET_DELETED]触摸设备拔出 0x{wparam.ToString("X4")} - 0x{lparam.ToString("X4")}", true);
355+
Log(DeviceChangeListenerTextBlock,
356+
$"[TABLET_DELETED]触摸设备拔出 0x{wparam.ToString("X4")} - 0x{lparam.ToString("X4")}", true);
257357
}
258358

259359
// 输出消息。
@@ -262,7 +362,7 @@ private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref
262362
return IntPtr.Zero;
263363
}
264364

265-
var formattedMessage = $"{(WindowMessages) msg}({msg})";
365+
var formattedMessage = $"{(WindowMessages)msg}({msg})";
266366
Log(HwndMsgTextBlock, formattedMessage);
267367

268368
return IntPtr.Zero;
Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
11
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
22

3-
<PropertyGroup>
4-
<OutputType>WinExe</OutputType>
5-
<TargetFrameworks>netcoreapp3.1;net45</TargetFrameworks>
6-
<UseWPF>true</UseWPF>
7-
<StartupObject>ManipulationDemo.Program</StartupObject>
8-
</PropertyGroup>
9-
<ItemGroup>
10-
<PackageReference Include="System.Management" Version="4.5.0" />
11-
</ItemGroup>
3+
<PropertyGroup>
4+
<OutputType>WinExe</OutputType>
5+
<TargetFrameworks>net6.0-windows;net45</TargetFrameworks>
6+
<UseWPF>true</UseWPF>
7+
<LangVersion>latest</LangVersion>
8+
<StartupObject>ManipulationDemo.Program</StartupObject>
9+
</PropertyGroup>
10+
<ItemGroup>
11+
<PackageReference Include="System.Management" Version="4.5.0" Condition="'$(TargetFramework)'=='net45'"/>
12+
<PackageReference Include="System.Management" Version="6.0.2" Condition="'$(TargetFramework)'=='net6.0-windows'"/>
13+
<PackageReference Include="Microsoft.Windows.CsWin32" PrivateAssets="all" Version="0.3.18-beta" />
14+
</ItemGroup>
15+
<ItemGroup>
16+
<Compile Update="Properties\Settings.Designer.cs">
17+
<DesignTimeSharedInput>True</DesignTimeSharedInput>
18+
<AutoGen>True</AutoGen>
19+
<DependentUpon>Settings.settings</DependentUpon>
20+
</Compile>
21+
</ItemGroup>
22+
<ItemGroup>
23+
<None Update="Properties\Settings.settings">
24+
<Generator>SettingsSingleFileGenerator</Generator>
25+
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
26+
</None>
27+
</ItemGroup>
28+
1229
</Project>

ManipulationDemo/NativeMethods.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GetPointerDevices

ManipulationDemo/Properties/Settings.Designer.cs

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System.Runtime.InteropServices;
2+
using System;
3+
4+
namespace ManipulationDemo
5+
{
6+
/// <summary>
7+
/// Copy From: https://stackoverflow.com/questions/1976573/using-registerdevicenotification-in-a-net-app
8+
/// </summary>
9+
internal static class UsbNotification
10+
{
11+
public const int DbtDevicearrival = 0x8000; // system detected a new device
12+
public const int DbtDeviceremovecomplete = 0x8004; // device is gone
13+
public const int WmDevicechange = 0x0219; // device change event
14+
public const int DbtDevtypDeviceinterface = 5;
15+
private static readonly Guid GuidDevinterfaceUSBDevice = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); // USB devices
16+
private static IntPtr _notificationHandle;
17+
18+
/// <summary>
19+
/// Registers a window to receive notifications when USB devices are plugged or unplugged.
20+
/// </summary>
21+
/// <param name="windowHandle">Handle to the window receiving notifications.</param>
22+
public static void RegisterUsbDeviceNotification(IntPtr windowHandle)
23+
{
24+
DevBroadcastDeviceinterface dbi = new DevBroadcastDeviceinterface
25+
{
26+
DeviceType = DbtDevtypDeviceinterface,
27+
Reserved = 0,
28+
ClassGuid = GuidDevinterfaceUSBDevice,
29+
Name = 0
30+
};
31+
32+
dbi.Size = Marshal.SizeOf(dbi);
33+
IntPtr buffer = Marshal.AllocHGlobal(dbi.Size);
34+
Marshal.StructureToPtr(dbi, buffer, true);
35+
36+
_notificationHandle = RegisterDeviceNotification(windowHandle, buffer, 0);
37+
}
38+
39+
/// <summary>
40+
/// Unregisters the window for USB device notifications
41+
/// </summary>
42+
public static void UnregisterUsbDeviceNotification()
43+
{
44+
UnregisterDeviceNotification(_notificationHandle);
45+
}
46+
47+
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
48+
private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);
49+
50+
[DllImport("user32.dll")]
51+
private static extern bool UnregisterDeviceNotification(IntPtr handle);
52+
53+
[StructLayout(LayoutKind.Sequential)]
54+
private struct DevBroadcastDeviceinterface
55+
{
56+
internal int Size;
57+
internal int DeviceType;
58+
internal int Reserved;
59+
internal Guid ClassGuid;
60+
internal short Name;
61+
}
62+
63+
[StructLayout(LayoutKind.Sequential)]
64+
public struct DEV_BROADCAST_HDR
65+
{
66+
public int dbch_size;
67+
public int dbch_devicetype;
68+
public int dbch_reserved;
69+
}
70+
71+
[StructLayout(LayoutKind.Sequential)]
72+
public struct DEV_BROADCAST_DEVICEINTERFACE
73+
{
74+
public int dbcc_size;
75+
public int dbcc_devicetype;
76+
public int dbcc_reserved;
77+
public Guid dbcc_classguid;
78+
//public string dbcc_name;
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)