如何在Windows 10上区分两个相同型号的USB触摸屏?

pcww981p  于 2022-11-30  发布在  Windows
关注(0)|答案(1)|浏览(343)

我有两个相同型号的触摸屏显示器连接到一台Windows 10机器上。显示器通过HDMI连接图像,通过USB连接触摸输入。
当我插入所有设备并使用内置校准“multidigimon.exe”进行设置时,我可以设置所有设备,使触摸屏按预期工作。
然而,重新启动后,有时触摸输入注册在错误的屏幕上,所以触摸右屏幕使事情发生在左边,触摸左屏幕使事情发生在右屏幕上。
我已经试过了,看看是否能找到一种方法让脚本来纠正这个问题,下面是我到目前为止找到的方法:

  1. multidigimon.exe将注册表项写入HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wisp\Pen\Digimon中。作为项,它使用与USB触控设备对应的Windows对象管理器路径。作为值,它使用与显示设备对应的Windows对象管理器路径。(我可以使用WinObj在“GLOBAL??”下看到这两个项)。将这两个项导出到.reg文件中,如下所示:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wisp\Pen\Digimon]
    "20-\\\\?\\HID#VID_1FF7&PID_0F27&Col04#a&25dfa661&0&0003#{4d1e55b2-f16f-11cf-88cb-001111000030}"="\\\\?\\DISPLAY#IVM1A3E#5&1778d8b3&1&UID260#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"
    "20-\\\\?\\HID#VID_1FF7&PID_0F27&Col04#a&29d74c67&0&0003#{4d1e55b2-f16f-11cf-88cb-001111000030}"="\\\\?\\DISPLAY#IVM1A3E#5&1778d8b3&1&UID256#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"

它主要由设备示例路径组成,可以在设备管理器中的设备详细信息下看到。在本例中,HID\VID_1FF7&PID_0F27&Col04\A&25DFA661&0&0003HID\VID_1FF7&PID_0F27&COL04\A&29D74C67&0&0003\替换为#,类GUID也附加在另一个#之后。Info部分来自this stackoverflow answer
1.在this stackoverflow answer中解释了部分设备示例路径,但这只解释了USB设备,我处理的是HID设备。因此VID_XXXXPID_XXXX似乎意味着相同的事情,但ColXX没有解释,最后一个\之后的部分是示例特定的id。
1.重新启动后,实际触摸HID设备随机获得特定于示例的ID。因此,有时右侧触摸屏具有设备示例路径HID\VID_1FF7&PID_0F27&Col04\A&25DFA661&0&0003,有时具有HID\VID_1FF7&PID_0F27&COL04\A&29D74C67&0&0003,这看起来相当随机 *。左侧触摸屏获得右侧触摸屏没有的设备示例路径。

  • 这可能取决于什么屏幕启动更快(他们自动打开时,PC启动)。当我拔下触摸屏设备USB后,启动和插入一个一次,第一个总是得到相同的示例特定的id。

有没有办法分辨这两个设备的区别?也许可以以某种方式获得它插入的USB端口的信息?

w80xi6nr

w80xi6nr1#

找到了一种使用PowerShell和使用Win32 API的嵌入式C#的方法。
Windows上的设备位于device tree上。Windows提供了navigate that tree in Cfgmgr32.h的方法。因为在我的例子中,设备是USB设备,所以我可以在编写代码之前使用USB Device Tree Viewer进行可视化和实验。
奇怪的是,我连接的触摸屏并不识别为单个USB设备,而是一个接一个的多个USB集线器,其中一些设备已连接(一些端口未连接),而显示器不提供任何USB端口来插入东西。USB设备树Viwer还显示USB设备的子设备,甚至是我正在寻找的HID设备。
在测试了一段时间后,拔出和重新插入不同的USB端口的触摸屏,并查看什么USB设备树Viwer显示我,我可以看到设备ID(在现实中,它是设备示例ID)并不总是保持完全相同。The Device instance ID is is made up of the device ID and instance id,有时示例ID完全改变后,重新插入USB设备。
我还可以注意到位置ID的一部分,即使在重新插入和重新启动Windows后,USB端口也保持不变。
因此,为了使触摸屏在重新启动时正常工作,我需要采取的步骤是:
1.查找以指定供应商和产品ID开头的HID设备,然后查找连接到正确USB端口的COL 04(选中位置ID /位置路径)。
1.删除旧的HKLM:\SOFTWARE\Microsoft\Wisp\Pen\Digimon下可能错误的值
1.将新找到的设备路径以正确的格式作为值添加到其中。(幸运的是,显示设备在重新启动后保持完全相同的路径)。我在通常使用内置工具将触摸屏应用到显示Map后查看了格式,并使用字符串插值将正确的格式拼凑在一起。
1.让Windows读取新的配置。为此我发现wisptis.exe根本不存在于我的机器上。经过一些测试后,我发现只要终止dwm.exe就能让Windows读取新的配置。它还会使屏幕变黑一会儿,因为dwm.exe是桌面窗口管理器。但在启动后立即执行此操作不会导致任何问题。甚至打开的窗口也会停留在相同的位置。
我将所有这些都放在下面的脚本中,我使用Windows任务调度程序在系统启动后运行此脚本(我不能在更早的时间运行此脚本,因为屏幕启动速度比PC慢,因此USB设备仅在PC启动后“插入”):

$code = @"
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

public class Program
{
    // Required Constants from cfgmgr32.h
    public const int CM_LOCATE_DEVNODE_NORMAL = 0x00000000;
    public const int CR_SUCCESS = 0x00000000;
    public const int CM_DRP_LOCATION_PATHS = 0x00000024;
    
    // The function that finds all devices starting with a specified device id that are connected through the specified
    // connection (location path). Since not all devices directly have a location path it walks up the devicetree untill
    // a node that hase a location path is found and then checks that path if it matches.
    public static List<string> GetDeviceIdStartingWithAt(string match, string connection)
    {
        var devices = FindAllDevicesStartingWith(match);
        var ret = new List<string>();
        foreach (var device in devices)
        {
            IntPtr cur = device;
            while (!HasLocationPath(cur))
            {
                IntPtr next;
                int result = CM_Get_Parent(out next, cur, 0);
                if (result != CR_SUCCESS) {
                    break;
                }
                cur = next;
            }

            bool mark = false;
            foreach (var location in GetLocationPath(cur))
            {
                if (location.StartsWith(connection))
                {
                    mark = true;
                }
            }

            if (mark)
            {
                ret.Add(GetDeviceId(device));
            }
        }

        return ret;
    }
    static List<IntPtr> FindAllDevicesStartingWith(string match)
    {
        IntPtr rootDevice;
        CM_Locate_DevNodeA(out rootDevice, "", CM_LOCATE_DEVNODE_NORMAL);
        return FindMatchingChildren(match, rootDevice);
    }

    // Recursive function that gets all children for a device and filters using the DeviceId to only return children
    // whose deviceId starts as requested
    static List<IntPtr> FindMatchingChildren(string match, IntPtr device)
    {
        var children = GetAllChildren(device);
        var ret = new List<IntPtr>();
        foreach (var child in children)
        {
            if (GetDeviceId(child).StartsWith(match))
            {
                ret.Add(child);
            }

            ret.AddRange(FindMatchingChildren(match, child));
        }

        return ret;
    }

    // Function implementing the way to get all direct children of a specified node as described here:
    // https://learn.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_get_child
    static List<IntPtr> GetAllChildren(IntPtr device)
    {
        IntPtr firstChild;
        if (CM_Get_Child(out firstChild, device, 0) != CR_SUCCESS)
        {
            return new List<IntPtr>();
        }

        var ret = new List<IntPtr>();
        ret.Add(firstChild);
        IntPtr cur = firstChild;
        int result;
        do
        {
            IntPtr next;
            result = CM_Get_Sibling(out next, cur, 0);
            if (result == CR_SUCCESS)
            {
                ret.Add(next);
                cur = next;
            }
        } while (result == CR_SUCCESS);

        return ret;
    }
    
    // Just a quick helper function that checks if a device has a Location Path, because not all devices do. In my
    // testing devices that have a device ID starting with HID mostly don't have a location. 
    static bool HasLocationPath(IntPtr device)
    {
        Microsoft.Win32.RegistryValueKind kind;
        uint length = 0;
        CM_Get_DevNode_Registry_Property(device, CM_DRP_LOCATION_PATHS, out kind, IntPtr.Zero, ref length, 0);

        return length > 0;
    }
    
    // Wrapper to easily get the Location paths, the one starting with PCIROOT seems to stay the same across restarts
    // and also identifies what port something is connected to. The value returned is of type REG_MULTI_SZ
    // REG_MULTI_SZ is explained here: https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types
    static string[] GetLocationPath(IntPtr device)
    {
        Microsoft.Win32.RegistryValueKind kind;
        uint length = 0;
        CM_Get_DevNode_Registry_Property(device, CM_DRP_LOCATION_PATHS, out kind, IntPtr.Zero, ref length, 0);

        if (length <= 0) 
            return Array.Empty<string>();
        
        IntPtr buffer = Marshal.AllocHGlobal((int)length);
        CM_Get_DevNode_Registry_Property(device, CM_DRP_LOCATION_PATHS, out kind, buffer, ref length, 0);
        string ret = Marshal.PtrToStringUni(buffer, (int)length/2);
        Marshal.FreeHGlobal(buffer);
        return ret.Substring(0, ret.Length-2).Split('\0');
    }

    // Wrapper around CM_Get_Device_ID, dosen't check for errors
    static string GetDeviceId(IntPtr device)
    {
        uint length = 0;
        CM_Get_Device_ID_Size(ref length, device, 0);

        IntPtr buffer = Marshal.AllocHGlobal((int)length + 1);
        CM_Get_Device_ID(device, buffer, length + 1, 0);
        string ret = Marshal.PtrToStringAnsi(buffer);
        Marshal.FreeHGlobal(buffer);
        return ret;
    }
    
    
    // Win32 Functions required to get data. More information can be found here:
    // https://learn.microsoft.com/en-us/windows/win32/api/cfgmgr32/
    [DllImport("cfgmgr32.dll", SetLastError = true)]
    static extern int CM_Locate_DevNodeA(out IntPtr pdnDevInst, string pDeviceID, int ulFlags);

    [DllImport("cfgmgr32.dll", SetLastError = true)]
    static extern int CM_Get_DevNode_Registry_Property(
        IntPtr deviceInstance,
        uint property,
        out Microsoft.Win32.RegistryValueKind pulRegDataType,
        IntPtr buffer,
        ref uint length,
        uint flags);

    [DllImport("cfgmgr32.dll", SetLastError = true)]
    static extern int CM_Get_Parent(out IntPtr pdnDevInst, IntPtr dnDevInst, int uFlags);

    [DllImport("cfgmgr32.dll", SetLastError = true)]
    static extern int CM_Get_Child(out IntPtr pdnDevInst, IntPtr dnDevInst, int uFlags);

    [DllImport("cfgmgr32.dll", SetLastError = true)]
    static extern int CM_Get_Sibling(out IntPtr pdnDevInst, IntPtr dnDevInst, int uFlags);

    [DllImport("cfgmgr32.dll", SetLastError = true)]
    static extern int CM_Get_Device_ID(IntPtr pdnDevInst, IntPtr buffer, uint bufferLen, uint flags);

    [DllImport("cfgmgr32.dll", SetLastError = true)]
    static extern int CM_Get_Device_ID_Size(ref uint pulLen, IntPtr dnDevInst, uint flags);
}
"@

Add-Type -TypeDefinition $code -Language CSharp

# Find the two touch screen devices
do {
    $left = [Program]::GetDeviceIdStartingWithAt("HID\VID_1FF7&PID_0F27&COL04", "PCIROOT(0)#PCI(0801)#PCI(0003)#USBROOT(0)#USB(1)#USB(3)")
    $right = [Program]::GetDeviceIdStartingWithAt("HID\VID_1FF7&PID_0F27&COL04", "PCIROOT(0)#PCI(0801)#PCI(0004)#USBROOT(0)#USB(1)#USB(4)")
    if ( ($left.Count -ge 1) -and ($right.Count -ge 1)) {
        break
    }
    Start-Sleep 1
} while ($true)

if (($left.Count -gt 1) -or ($right.Count -gt 1)) {
    # Too many matching devices found! Abort!
    Return
}

# Truncate and convert them to lower case for use in the Registry Key
$left = $left.Substring(28, 12).ToLower()
$right = $right.Substring(28, 12).ToLower()

# Delete the old values in the registry
$properties = Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Wisp\Pen\Digimon

foreach ($property in $properties.PSObject.Properties) {
    if ($property.Name.StartsWith("20-")) {
        Remove-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Wisp\Pen\Digimon -Name $property.Name
    }
}

# Adding the found devices in the registry. In my case the touch screens offer finger and pen input. The difference is Col04 and Col06 before the device instance id and after it 0003 or 0005
New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Wisp\Pen\Digimon -Name "20-\\?\HID#VID_1FF7&PID_0F27&Col04#$left&0003#{4d1e55b2-f16f-11cf-88cb-001111000030}" -PropertyType String -Value "\\?\DISPLAY#IVM1A3E#5&1778d8b3&1&UID256#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"
New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Wisp\Pen\Digimon -Name "20-\\?\HID#VID_1FF7&PID_0F27&Col06#$left&0005#{4d1e55b2-f16f-11cf-88cb-001111000030}" -PropertyType String -Value "\\?\DISPLAY#IVM1A3E#5&1778d8b3&1&UID256#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"
New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Wisp\Pen\Digimon -Name "20-\\?\HID#VID_1FF7&PID_0F27&Col04#$right&0003#{4d1e55b2-f16f-11cf-88cb-001111000030}" -PropertyType String -Value "\\?\DISPLAY#IVM1A3E#5&1778d8b3&1&UID260#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"
New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Wisp\Pen\Digimon -Name "20-\\?\HID#VID_1FF7&PID_0F27&Col06#$right&0005#{4d1e55b2-f16f-11cf-88cb-001111000030}" -PropertyType String -Value "\\?\DISPLAY#IVM1A3E#5&1778d8b3&1&UID260#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"

# To apply the settings change the Desktop Window Manager is stopped. This dosen't look great as both screens flash black for a second before a new dwm instance is automatically started.
# There is probably a way to send some message to the dwm to read the configuration, but I haven't found it anywhere.
Stop-Process -Name dwm -Force

相关问题