diff --git a/ManipulationDemo/MainWindow.xaml.cs b/ManipulationDemo/MainWindow.xaml.cs index 99fbd1c..77b1dc5 100644 --- a/ManipulationDemo/MainWindow.xaml.cs +++ b/ManipulationDemo/MainWindow.xaml.cs @@ -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 { @@ -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 { @@ -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) @@ -162,6 +169,38 @@ private void OnTick(object sender, EventArgs e) } } + /// + /// 添加 Pointer 消息的信息 + /// + /// + 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(); @@ -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("程序启动时"); @@ -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); } // 输出消息。 @@ -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; diff --git a/ManipulationDemo/ManipulationDemo.csproj b/ManipulationDemo/ManipulationDemo.csproj index 7f36069..816b9d1 100644 --- a/ManipulationDemo/ManipulationDemo.csproj +++ b/ManipulationDemo/ManipulationDemo.csproj @@ -1,12 +1,29 @@  - - WinExe - netcoreapp3.1;net45 - true - ManipulationDemo.Program - - - - + + WinExe + net6.0-windows;net45 + true + latest + ManipulationDemo.Program + + + + + + + + + True + True + Settings.settings + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + diff --git a/ManipulationDemo/NativeMethods.txt b/ManipulationDemo/NativeMethods.txt new file mode 100644 index 0000000..8654ad0 --- /dev/null +++ b/ManipulationDemo/NativeMethods.txt @@ -0,0 +1 @@ +GetPointerDevices \ No newline at end of file diff --git a/ManipulationDemo/Properties/Settings.Designer.cs b/ManipulationDemo/Properties/Settings.Designer.cs index 1bb4fd8..7777495 100644 --- a/ManipulationDemo/Properties/Settings.Designer.cs +++ b/ManipulationDemo/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace ManipulationDemo.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.6.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -27,7 +27,7 @@ public static Settings Default { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("3,5,6,7,8,13,14,15,19,20,24,25,28,31,32,36,61,70,71,124,125,127,131,132,133,134,1" + "60,161,174,274,281,282,283,356,522,526,532,533,534,561,562,641,642,674,675,725,7" + - "99,49283,49343,49586")] + "99,49283,49343,49586,")] public string IgnoredMsgs { get { return ((string)(this["IgnoredMsgs"])); diff --git a/ManipulationDemo/UsbNotification.cs b/ManipulationDemo/UsbNotification.cs new file mode 100644 index 0000000..e61d3f6 --- /dev/null +++ b/ManipulationDemo/UsbNotification.cs @@ -0,0 +1,81 @@ +using System.Runtime.InteropServices; +using System; + +namespace ManipulationDemo +{ + /// + /// Copy From: https://stackoverflow.com/questions/1976573/using-registerdevicenotification-in-a-net-app + /// + 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; + + /// + /// Registers a window to receive notifications when USB devices are plugged or unplugged. + /// + /// Handle to the window receiving notifications. + 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); + } + + /// + /// Unregisters the window for USB device notifications + /// + 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; + } + } +} \ No newline at end of file