2 #A part of NonVisual Desktop Access (NVDA)
\r
3 # Original serial scanner code from http://pyserial.svn.sourceforge.net/viewvc/*checkout*/pyserial/trunk/pyserial/examples/scanwin32.py
\r
4 # Modifications and enhancements by James Teh
\r
6 """Utilities for working with hardware connection ports.
\r
11 from ctypes.wintypes import BOOL, WCHAR, HWND, DWORD, ULONG, WORD
\r
12 import _winreg as winreg
\r
14 def ValidHandle(value):
\r
16 raise ctypes.WinError()
\r
19 HDEVINFO = ctypes.c_void_p
\r
20 PCWSTR = ctypes.c_wchar_p
\r
21 HWND = ctypes.c_uint
\r
22 PDWORD = ctypes.POINTER(DWORD)
\r
23 ULONG_PTR = ctypes.POINTER(ULONG)
\r
24 ULONGLONG = ctypes.c_ulonglong
\r
27 class GUID(ctypes.Structure):
\r
29 ('Data1', ctypes.c_ulong),
\r
30 ('Data2', ctypes.c_ushort),
\r
31 ('Data3', ctypes.c_ushort),
\r
32 ('Data4', ctypes.c_ubyte*8),
\r
35 return "{%08x-%04x-%04x-%s-%s}" % (
\r
39 ''.join(["%02x" % d for d in self.Data4[:2]]),
\r
40 ''.join(["%02x" % d for d in self.Data4[2:]]),
\r
43 class SP_DEVINFO_DATA(ctypes.Structure):
\r
46 ('ClassGuid', GUID),
\r
48 ('Reserved', ULONG_PTR),
\r
51 return "ClassGuid:%s DevInst:%s" % (self.ClassGuid, self.DevInst)
\r
52 PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA)
\r
54 class SP_DEVICE_INTERFACE_DATA(ctypes.Structure):
\r
57 ('InterfaceClassGuid', GUID),
\r
59 ('Reserved', ULONG_PTR),
\r
62 return "InterfaceClassGuid:%s Flags:%s" % (self.InterfaceClassGuid, self.Flags)
\r
64 PSP_DEVICE_INTERFACE_DATA = ctypes.POINTER(SP_DEVICE_INTERFACE_DATA)
\r
66 PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p
\r
68 class dummy(ctypes.Structure):
\r
69 _fields_=(("d1", DWORD), ("d2", WCHAR))
\r
71 SIZEOF_SP_DEVICE_INTERFACE_DETAIL_DATA_W = ctypes.sizeof(dummy)
\r
73 SetupDiDestroyDeviceInfoList = ctypes.windll.setupapi.SetupDiDestroyDeviceInfoList
\r
74 SetupDiDestroyDeviceInfoList.argtypes = (HDEVINFO,)
\r
75 SetupDiDestroyDeviceInfoList.restype = BOOL
\r
77 SetupDiGetClassDevs = ctypes.windll.setupapi.SetupDiGetClassDevsW
\r
78 SetupDiGetClassDevs.argtypes = (ctypes.POINTER(GUID), PCWSTR, HWND, DWORD)
\r
79 SetupDiGetClassDevs.restype = ValidHandle # HDEVINFO
\r
81 SetupDiEnumDeviceInterfaces = ctypes.windll.setupapi.SetupDiEnumDeviceInterfaces
\r
82 SetupDiEnumDeviceInterfaces.argtypes = (HDEVINFO, PSP_DEVINFO_DATA, ctypes.POINTER(GUID), DWORD, PSP_DEVICE_INTERFACE_DATA)
\r
83 SetupDiEnumDeviceInterfaces.restype = BOOL
\r
85 SetupDiGetDeviceInterfaceDetail = ctypes.windll.setupapi.SetupDiGetDeviceInterfaceDetailW
\r
86 SetupDiGetDeviceInterfaceDetail.argtypes = (HDEVINFO, PSP_DEVICE_INTERFACE_DATA, PSP_DEVICE_INTERFACE_DETAIL_DATA, DWORD, PDWORD, PSP_DEVINFO_DATA)
\r
87 SetupDiGetDeviceInterfaceDetail.restype = BOOL
\r
89 SetupDiGetDeviceRegistryProperty = ctypes.windll.setupapi.SetupDiGetDeviceRegistryPropertyW
\r
90 SetupDiGetDeviceRegistryProperty.argtypes = (HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, ctypes.c_void_p, DWORD, PDWORD)
\r
91 SetupDiGetDeviceRegistryProperty.restype = BOOL
\r
93 GUID_CLASS_COMPORT = GUID(0x86e0d1e0L, 0x8089, 0x11d0,
\r
94 (ctypes.c_ubyte*8)(0x9c, 0xe4, 0x08, 0x00, 0x3e, 0x30, 0x1f, 0x73))
\r
97 DIGCF_DEVICEINTERFACE = 16
\r
98 INVALID_HANDLE_VALUE = 0
\r
99 ERROR_INSUFFICIENT_BUFFER = 122
\r
100 SPDRP_HARDWAREID = 1
\r
101 SPDRP_FRIENDLYNAME = 12
\r
102 SPDRP_LOCATION_INFORMATION = 13
\r
103 ERROR_NO_MORE_ITEMS = 259
\r
104 DICS_FLAG_GLOBAL = 0x00000001
\r
105 DIREG_DEV = 0x00000001
\r
107 def listComPorts(onlyAvailable=True):
\r
108 """List com ports on the system.
\r
109 @param onlyAvailable: Only return ports that are currently available.
\r
110 @type onlyAvailable: bool
\r
111 @return: Generates dicts including keys of port, friendlyName and hardwareID.
\r
112 @rtype: generator of (str, str, str)
\r
114 flags = DIGCF_DEVICEINTERFACE
\r
116 flags |= DIGCF_PRESENT
\r
118 buf = ctypes.create_unicode_buffer(1024)
\r
119 g_hdi = SetupDiGetClassDevs(ctypes.byref(GUID_CLASS_COMPORT), None, NULL, flags)
\r
121 for dwIndex in xrange(256):
\r
123 did = SP_DEVICE_INTERFACE_DATA()
\r
124 did.cbSize = ctypes.sizeof(did)
\r
126 if not SetupDiEnumDeviceInterfaces(
\r
129 ctypes.byref(GUID_CLASS_COMPORT),
\r
133 if ctypes.GetLastError() != ERROR_NO_MORE_ITEMS:
\r
134 raise ctypes.WinError()
\r
139 if not SetupDiGetDeviceInterfaceDetail(
\r
142 None, 0, ctypes.byref(dwNeeded),
\r
145 # Ignore ERROR_INSUFFICIENT_BUFFER
\r
146 if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
\r
147 raise ctypes.WinError()
\r
149 class SP_DEVICE_INTERFACE_DETAIL_DATA_W(ctypes.Structure):
\r
152 ('DevicePath', WCHAR*(dwNeeded.value - ctypes.sizeof(DWORD))),
\r
155 return "DevicePath:%s" % (self.DevicePath,)
\r
156 idd = SP_DEVICE_INTERFACE_DETAIL_DATA_W()
\r
157 idd.cbSize = SIZEOF_SP_DEVICE_INTERFACE_DETAIL_DATA_W
\r
158 devinfo = SP_DEVINFO_DATA()
\r
159 devinfo.cbSize = ctypes.sizeof(devinfo)
\r
160 if not SetupDiGetDeviceInterfaceDetail(
\r
163 ctypes.byref(idd), dwNeeded, None,
\r
164 ctypes.byref(devinfo)
\r
166 raise ctypes.WinError()
\r
169 if not SetupDiGetDeviceRegistryProperty(
\r
171 ctypes.byref(devinfo),
\r
174 ctypes.byref(buf), ctypes.sizeof(buf) - 1,
\r
177 # Ignore ERROR_INSUFFICIENT_BUFFER
\r
178 if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
\r
179 raise ctypes.WinError()
\r
181 hwID = entry["hardwareID"] = buf.value
\r
183 regKey = ctypes.windll.setupapi.SetupDiOpenDevRegKey(g_hdi, ctypes.byref(devinfo), DICS_FLAG_GLOBAL, 0, DIREG_DEV, winreg.KEY_READ)
\r
184 port = entry["port"] = winreg.QueryValueEx(regKey, "PortName")[0]
\r
185 if hwID.startswith("BTHENUM\\"):
\r
186 # This is a Microsoft bluetooth port.
\r
188 addr = winreg.QueryValueEx(regKey, "Bluetooth_UniqueID")[0].split("#", 1)[1].split("_", 1)[0]
\r
189 addr = int(addr, 16)
\r
190 entry["bluetoothAddress"] = addr
\r
192 entry["bluetoothName"] = getBluetoothDeviceInfo(addr).szName
\r
195 elif hwID == r"Bluetooth\0004&0002":
\r
196 # This is a Toshiba bluetooth port.
\r
198 entry["bluetoothAddress"], entry["bluetoothName"] = getToshibaBluetoothPortInfo(port)
\r
201 ctypes.windll.advapi32.RegCloseKey(regKey)
\r
204 if not SetupDiGetDeviceRegistryProperty(
\r
206 ctypes.byref(devinfo),
\r
207 SPDRP_FRIENDLYNAME,
\r
209 ctypes.byref(buf), ctypes.sizeof(buf) - 1,
\r
212 # Ignore ERROR_INSUFFICIENT_BUFFER
\r
213 if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
\r
214 raise ctypes.WinError()
\r
216 entry["friendlyName"] = buf.value
\r
221 SetupDiDestroyDeviceInfoList(g_hdi)
\r
223 BLUETOOTH_MAX_NAME_SIZE = 248
\r
224 BTH_ADDR = BLUETOOTH_ADDRESS = ULONGLONG
\r
226 class SYSTEMTIME(ctypes.Structure):
\r
230 ("wDayOfWeek", WORD),
\r
235 ("wMilliseconds", WORD)
\r
238 class BLUETOOTH_DEVICE_INFO(ctypes.Structure):
\r
241 ("address", BLUETOOTH_ADDRESS),
\r
242 ("ulClassofDevice", ULONG),
\r
243 ("fConnected", BOOL),
\r
244 ("fRemembered", BOOL),
\r
245 ("fAuthenticated", BOOL),
\r
246 ("stLastSeen", SYSTEMTIME),
\r
247 ("stLastUsed", SYSTEMTIME),
\r
248 ("szName", WCHAR * BLUETOOTH_MAX_NAME_SIZE)
\r
250 def __init__(self, **kwargs):
\r
251 super(BLUETOOTH_DEVICE_INFO, self).__init__(dwSize=ctypes.sizeof(self), **kwargs)
\r
253 def getBluetoothDeviceInfo(address):
\r
254 devInfo = BLUETOOTH_DEVICE_INFO(address=address)
\r
255 res = ctypes.windll["bthprops.cpl"].BluetoothGetDeviceInfo(None, ctypes.byref(devInfo))
\r
257 raise ctypes.WinError(res)
\r
260 def getToshibaBluetoothPortInfo(port):
\r
261 with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Toshiba\BluetoothStack\V1.0\EZC\DATA") as rootKey:
\r
262 for index in itertools.count():
\r
264 keyName = winreg.EnumKey(rootKey, index)
\r
265 except WindowsError:
\r
267 with winreg.OpenKey(rootKey, keyName) as itemKey:
\r
268 with winreg.OpenKey(itemKey, "SCORIGINAL") as scorigKey:
\r
270 if winreg.QueryValueEx(scorigKey, "PORTNAME")[0].rstrip("\0") != port:
\r
271 # This isn't the port we're interested in.
\r
273 except WindowsError:
\r
274 # This isn't a COM port.
\r
276 addr = winreg.QueryValueEx(itemKey, "BDADDR")[0]
\r
277 # addr is a string of raw bytes.
\r
278 # Convert it to a single number.
\r
279 addr = sum(ord(byte) << (byteNum * 8) for byteNum, byte in enumerate(reversed(addr)))
\r
280 name = winreg.QueryValueEx(itemKey, "FRIENDLYNAME")[0].rstrip("\0")
\r