Skip to content

Commit

Permalink
Merge pull request #3 from dotnet-campus/t/lindexi_dotnet
Browse files Browse the repository at this point in the history
加上更加详细的设备插拔信息
  • Loading branch information
kkwpsv authored Nov 21, 2023
2 parents 5a17721 + 5e01b6a commit cba4a7e
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 22 deletions.
122 changes: 111 additions & 11 deletions ManipulationDemo/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Windows.Win32;
using static ManipulationDemo.UsbNotification;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

namespace ManipulationDemo
{
Expand Down Expand Up @@ -107,7 +111,7 @@ public MainWindow()

private readonly DispatcherTimer _timer;

private void OnTick(object sender, EventArgs e)
private unsafe void OnTick(object sender, EventArgs e)
{
try
{
Expand Down Expand Up @@ -154,6 +158,9 @@ private void OnTick(object sender, EventArgs e)
device.Name, device.StylusDevices.Count, tabletSize));
}
}

AppendPointerDeviceInfo(builder);

PhysicalSizeRun.Text = builder.ToString();
}
catch (Exception ex)
Expand All @@ -162,6 +169,38 @@ private void OnTick(object sender, EventArgs e)
}
}

/// <summary>
/// 添加 Pointer 消息的信息
/// </summary>
/// <param name="stringBuilder"></param>
private static unsafe void AppendPointerDeviceInfo(StringBuilder stringBuilder)
{
try
{
// 获取 Pointer 设备数量
uint deviceCount = 0;
PInvoke.GetPointerDevices(ref deviceCount,
(Windows.Win32.UI.Controls.POINTER_DEVICE_INFO*) IntPtr.Zero);
Windows.Win32.UI.Controls.POINTER_DEVICE_INFO[] pointerDeviceInfo =
new Windows.Win32.UI.Controls.POINTER_DEVICE_INFO[deviceCount];
fixed (Windows.Win32.UI.Controls.POINTER_DEVICE_INFO* pDeviceInfo = &pointerDeviceInfo[0])
{
// 这里需要拿两次,第一次获取数量,第二次获取信息
PInvoke.GetPointerDevices(ref deviceCount, pDeviceInfo);
stringBuilder.AppendLine($"PointerDeviceCount:{deviceCount} 设备列表:");
foreach (var info in pointerDeviceInfo)
{
stringBuilder.AppendLine($" - {info.productString}");
}
}
}
catch (Exception e)
{
// 也许是在非 Win8 或以上的系统,抛出找不到方法,这个 GetPointerDevices 方法是 Win8 加的
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getpointerdevices
}
}

private void OnStylusDown(object sender, StylusDownEventArgs e)
{
StylusDownStoryboard.Begin();
Expand Down Expand Up @@ -230,7 +269,8 @@ private void OnManipulationCompleted(object sender, ManipulationCompletedEventAr
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var source = (HwndSource) PresentationSource.FromVisual(this);
var source = (HwndSource) PresentationSource.FromVisual(this)!;
UsbNotification.RegisterUsbDeviceNotification(source.Handle);
source?.AddHook(HwndHook);

Log("程序启动时");
Expand All @@ -240,20 +280,80 @@ protected override void OnSourceInitialized(EventArgs e)
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
{
// 检查硬件设备插拔。
if (msg == (int) WindowMessages.DEVICECHANGE)
if (msg == (int)WindowMessages.DEVICECHANGE)
{
var eventText = $"Event={(WindowsMessageDeviceChangeEventEnum)wparam}";
// 是否应该加上通用的变更记录日志
bool shouldCommonLog = true;

bool isDeviceArrival = (int)wparam == (int)WindowsMessageDeviceChangeEventEnum.DBT_DEVICEARRIVAL;
bool isDeviceRemoveComplete = (int)wparam == (int)WindowsMessageDeviceChangeEventEnum.DBT_DEVICEREMOVECOMPLETE;

if (isDeviceArrival || isDeviceRemoveComplete)
{
// 设备被移除或插入,试试拿到具体是哪个设备
DEV_BROADCAST_HDR hdr =
(DEV_BROADCAST_HDR) Marshal.PtrToStructure(lparam, typeof(DEV_BROADCAST_HDR));
if (hdr.dbch_devicetype == UsbNotification.DbtDevtypDeviceinterface)
{
DEV_BROADCAST_DEVICEINTERFACE deviceInterface =
(DEV_BROADCAST_DEVICEINTERFACE) Marshal.PtrToStructure(lparam,
typeof(DEV_BROADCAST_DEVICEINTERFACE));

var classguid = deviceInterface.dbcc_classguid;
// 这里的 classguid 默认会带上 name 上,于是就用不着

var size = Marshal.SizeOf(typeof(DEV_BROADCAST_DEVICEINTERFACE));
var namePtr = lparam + size;
var nameSize = hdr.dbch_size - size;
// 使用 Unicode 读取的话,一个字符是两个字节
var charLength = nameSize / 2;
var name = Marshal.PtrToStringUni(namePtr, charLength);
if (string.IsNullOrEmpty(name))
{
name = "读取不到设备名";
}

string pid = string.Empty;
string vid = string.Empty;

var pidMatch = Regex.Match(name, @"PID_([\dA-Fa-f]{4})");
if (pidMatch.Success)
{
pid = pidMatch.Groups[1].Value;
}

var vidMatch = Regex.Match(name, @"VID_([\dA-Fa-f]{4})");
if (vidMatch.Success)
{
vid = vidMatch.Groups[1].Value;
}

Log(DeviceChangeListenerTextBlock, $"[WM_DEVICECHANGE] 设备{(isDeviceArrival?"插入":"拔出")} PID={pid} VID={vid}\r\n{name}", true);

// 换成带上更多信息的记录,不需要通用记录
shouldCommonLog = false;
}
}

Log(DeviceChangeListenerTextBlock, $"[WM_DEVICECHANGE]设备发生插拔 0x{wparam.ToString("X4")}-0x{lparam.ToString("X4")};{eventText}", true);
LogDevices();
if (shouldCommonLog)
{
var eventText = $"Event={(WindowsMessageDeviceChangeEventEnum) wparam}";

Log(DeviceChangeListenerTextBlock,
$"[WM_DEVICECHANGE]设备发生插拔 Param=0x{wparam.ToString("X4")}-0x{lparam.ToString("X4")};{eventText}",
true);
LogDevices();
}
}
else if (msg == (int) WindowMessages.TABLET_ADDED)
else if (msg == (int)WindowMessages.TABLET_ADDED)
{
Log(DeviceChangeListenerTextBlock, $"[TABLET_ADDED]触摸设备插入 0x{wparam.ToString("X4")} - 0x{lparam.ToString("X4")}", true);
Log(DeviceChangeListenerTextBlock,
$"[TABLET_ADDED]触摸设备插入 0x{wparam.ToString("X4")} - 0x{lparam.ToString("X4")}", true);
}
else if (msg == (int) WindowMessages.TABLET_DELETED)
else if (msg == (int)WindowMessages.TABLET_DELETED)
{
Log(DeviceChangeListenerTextBlock, $"[TABLET_DELETED]触摸设备拔出 0x{wparam.ToString("X4")} - 0x{lparam.ToString("X4")}", true);
Log(DeviceChangeListenerTextBlock,
$"[TABLET_DELETED]触摸设备拔出 0x{wparam.ToString("X4")} - 0x{lparam.ToString("X4")}", true);
}

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

var formattedMessage = $"{(WindowMessages) msg}({msg})";
var formattedMessage = $"{(WindowMessages)msg}({msg})";
Log(HwndMsgTextBlock, formattedMessage);

return IntPtr.Zero;
Expand Down
35 changes: 26 additions & 9 deletions ManipulationDemo/ManipulationDemo.csproj
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFrameworks>netcoreapp3.1;net45</TargetFrameworks>
<UseWPF>true</UseWPF>
<StartupObject>ManipulationDemo.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Management" Version="4.5.0" />
</ItemGroup>
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFrameworks>net6.0-windows;net45</TargetFrameworks>
<UseWPF>true</UseWPF>
<LangVersion>latest</LangVersion>
<StartupObject>ManipulationDemo.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Management" Version="4.5.0" Condition="'$(TargetFramework)'=='net45'"/>
<PackageReference Include="System.Management" Version="6.0.2" Condition="'$(TargetFramework)'=='net6.0-windows'"/>
<PackageReference Include="Microsoft.Windows.CsWin32" PrivateAssets="all" Version="0.3.18-beta" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions ManipulationDemo/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GetPointerDevices
4 changes: 2 additions & 2 deletions ManipulationDemo/Properties/Settings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 81 additions & 0 deletions ManipulationDemo/UsbNotification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Runtime.InteropServices;
using System;

namespace ManipulationDemo
{
/// <summary>
/// Copy From: https://stackoverflow.com/questions/1976573/using-registerdevicenotification-in-a-net-app
/// </summary>
internal static class UsbNotification
{
public const int DbtDevicearrival = 0x8000; // system detected a new device
public const int DbtDeviceremovecomplete = 0x8004; // device is gone
public const int WmDevicechange = 0x0219; // device change event
public const int DbtDevtypDeviceinterface = 5;
private static readonly Guid GuidDevinterfaceUSBDevice = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); // USB devices
private static IntPtr _notificationHandle;

/// <summary>
/// Registers a window to receive notifications when USB devices are plugged or unplugged.
/// </summary>
/// <param name="windowHandle">Handle to the window receiving notifications.</param>
public static void RegisterUsbDeviceNotification(IntPtr windowHandle)
{
DevBroadcastDeviceinterface dbi = new DevBroadcastDeviceinterface
{
DeviceType = DbtDevtypDeviceinterface,
Reserved = 0,
ClassGuid = GuidDevinterfaceUSBDevice,
Name = 0
};

dbi.Size = Marshal.SizeOf(dbi);
IntPtr buffer = Marshal.AllocHGlobal(dbi.Size);
Marshal.StructureToPtr(dbi, buffer, true);

_notificationHandle = RegisterDeviceNotification(windowHandle, buffer, 0);
}

/// <summary>
/// Unregisters the window for USB device notifications
/// </summary>
public static void UnregisterUsbDeviceNotification()
{
UnregisterDeviceNotification(_notificationHandle);
}

[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);

[DllImport("user32.dll")]
private static extern bool UnregisterDeviceNotification(IntPtr handle);

[StructLayout(LayoutKind.Sequential)]
private struct DevBroadcastDeviceinterface
{
internal int Size;
internal int DeviceType;
internal int Reserved;
internal Guid ClassGuid;
internal short Name;
}

[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_HDR
{
public int dbch_size;
public int dbch_devicetype;
public int dbch_reserved;
}

[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_DEVICEINTERFACE
{
public int dbcc_size;
public int dbcc_devicetype;
public int dbcc_reserved;
public Guid dbcc_classguid;
//public string dbcc_name;
}
}
}

0 comments on commit cba4a7e

Please sign in to comment.