View Javadoc
1   /*
2    * Copyright 2021-2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.driver.windows;
6   
7   import static com.sun.jna.platform.win32.Cfgmgr32.CM_DRP_CLASS;
8   import static com.sun.jna.platform.win32.Cfgmgr32.CM_DRP_DEVICEDESC;
9   import static com.sun.jna.platform.win32.Cfgmgr32.CM_DRP_FRIENDLYNAME;
10  import static com.sun.jna.platform.win32.Cfgmgr32.CM_DRP_MFG;
11  import static com.sun.jna.platform.win32.Cfgmgr32.CM_DRP_SERVICE;
12  import static com.sun.jna.platform.win32.SetupApi.DIGCF_DEVICEINTERFACE;
13  import static com.sun.jna.platform.win32.SetupApi.DIGCF_PRESENT;
14  import static com.sun.jna.platform.win32.WinBase.INVALID_HANDLE_VALUE;
15  import static com.sun.jna.platform.win32.WinError.ERROR_SUCCESS;
16  
17  import java.util.ArrayDeque;
18  import java.util.HashMap;
19  import java.util.Map;
20  import java.util.Queue;
21  import java.util.Set;
22  import java.util.stream.Collectors;
23  
24  import com.sun.jna.Memory;
25  import com.sun.jna.platform.win32.Cfgmgr32;
26  import com.sun.jna.platform.win32.Cfgmgr32Util;
27  import com.sun.jna.platform.win32.Guid.GUID;
28  import com.sun.jna.platform.win32.SetupApi;
29  import com.sun.jna.platform.win32.WinNT.HANDLE;
30  import com.sun.jna.ptr.IntByReference;
31  
32  import oshi.annotation.concurrent.ThreadSafe;
33  import oshi.jna.ByRef.CloseableIntByReference;
34  import oshi.jna.Struct.CloseableSpDevinfoData;
35  import oshi.util.tuples.Quintet;
36  
37  /**
38   * Utility to query device interfaces via Config Manager Device Tree functions
39   */
40  @ThreadSafe
41  public final class DeviceTree {
42  
43      private static final int MAX_PATH = 260;
44      private static final SetupApi SA = SetupApi.INSTANCE;
45      private static final Cfgmgr32 C32 = Cfgmgr32.INSTANCE;
46  
47      private DeviceTree() {
48      }
49  
50      /**
51       * Queries devices matching the specified device interface and returns maps representing device tree relationships,
52       * name, device ID, and manufacturer
53       *
54       * @param guidDevInterface The GUID of a device interface class for which the tree should be collected.
55       *
56       * @return A {@link Quintet} of maps indexed by node ID, where the key set represents node IDs for all devices
57       *         matching the specified device interface GUID. The first element is a set containing devices with no
58       *         parents, match the device interface requested.. The second element maps each node ID to its parents, if
59       *         any. This map's key set excludes the no-parent devices returned in the first element. The third element
60       *         maps a node ID to a name or description. The fourth element maps a node id to a device ID. The fifth
61       *         element maps a node ID to a manufacturer.
62       */
63      public static Quintet<Set<Integer>, Map<Integer, Integer>, Map<Integer, String>, Map<Integer, String>, Map<Integer, String>> queryDeviceTree(
64              GUID guidDevInterface) {
65          Map<Integer, Integer> parentMap = new HashMap<>();
66          Map<Integer, String> nameMap = new HashMap<>();
67          Map<Integer, String> deviceIdMap = new HashMap<>();
68          Map<Integer, String> mfgMap = new HashMap<>();
69          // Get device IDs for the top level devices
70          HANDLE hDevInfo = SA.SetupDiGetClassDevs(guidDevInterface, null, null, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
71          if (!INVALID_HANDLE_VALUE.equals(hDevInfo)) {
72              try (Memory buf = new Memory(MAX_PATH);
73                      CloseableIntByReference size = new CloseableIntByReference(MAX_PATH);
74                      CloseableIntByReference child = new CloseableIntByReference();
75                      CloseableIntByReference sibling = new CloseableIntByReference();
76                      CloseableSpDevinfoData devInfoData = new CloseableSpDevinfoData()) {
77                  devInfoData.cbSize = devInfoData.size();
78                  // Enumerate Device Info using BFS queue
79                  Queue<Integer> deviceTree = new ArrayDeque<>();
80                  for (int i = 0; SA.SetupDiEnumDeviceInfo(hDevInfo, i, devInfoData); i++) {
81                      deviceTree.add(devInfoData.DevInst);
82                      // Initialize parent and child objects
83                      int node = 0;
84                      while (!deviceTree.isEmpty()) {
85                          // Process the next device in the queue
86                          node = deviceTree.poll();
87  
88                          // Save the strings in their maps
89                          String deviceId = Cfgmgr32Util.CM_Get_Device_ID(node);
90                          deviceIdMap.put(node, deviceId);
91                          // Prefer friendly name over desc if it is present.
92                          // If neither, use class (service)
93                          String name = getDevNodeProperty(node, CM_DRP_FRIENDLYNAME, buf, size);
94                          if (name.isEmpty()) {
95                              name = getDevNodeProperty(node, CM_DRP_DEVICEDESC, buf, size);
96                          }
97                          if (name.isEmpty()) {
98                              name = getDevNodeProperty(node, CM_DRP_CLASS, buf, size);
99                              String svc = getDevNodeProperty(node, CM_DRP_SERVICE, buf, size);
100                             if (!svc.isEmpty()) {
101                                 name = name + " (" + svc + ")";
102                             }
103                         }
104                         nameMap.put(node, name);
105                         mfgMap.put(node, getDevNodeProperty(node, CM_DRP_MFG, buf, size));
106 
107                         // Add any children to the queue, tracking the parent node
108                         if (ERROR_SUCCESS == C32.CM_Get_Child(child, node, 0)) {
109                             parentMap.put(child.getValue(), node);
110                             deviceTree.add(child.getValue());
111                             while (ERROR_SUCCESS == C32.CM_Get_Sibling(sibling, child.getValue(), 0)) {
112                                 parentMap.put(sibling.getValue(), node);
113                                 deviceTree.add(sibling.getValue());
114                                 child.setValue(sibling.getValue());
115                             }
116                         }
117                     }
118                 }
119             } finally {
120                 SA.SetupDiDestroyDeviceInfoList(hDevInfo);
121             }
122         }
123         // Look for output without parents, these are top of tree
124         Set<Integer> controllerDevices = deviceIdMap.keySet().stream().filter(k -> !parentMap.containsKey(k))
125                 .collect(Collectors.toSet());
126         return new Quintet<>(controllerDevices, parentMap, nameMap, deviceIdMap, mfgMap);
127     }
128 
129     private static String getDevNodeProperty(int node, int cmDrp, Memory buf, IntByReference size) {
130         buf.clear();
131         size.setValue((int) buf.size());
132         C32.CM_Get_DevNode_Registry_Property(node, cmDrp, null, buf, size, 0);
133         return buf.getWideString(0);
134     }
135 }