OSDN Git Service

Merge 2013.1.
[nvdajp/nvdajp.git] / source / hwPortUtils.py
1 #hwPortUtils.py\r
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
5 \r
6 """Utilities for working with hardware connection ports.\r
7 """\r
8 \r
9 import itertools\r
10 import ctypes\r
11 from ctypes.wintypes import BOOL, WCHAR, HWND, DWORD, ULONG, WORD\r
12 import _winreg as winreg\r
13 \r
14 def ValidHandle(value):\r
15         if value == 0:\r
16                 raise ctypes.WinError()\r
17         return value\r
18 \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
25 NULL = 0\r
26 \r
27 class GUID(ctypes.Structure):\r
28         _fields_ = (\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
33         )\r
34         def __str__(self):\r
35                 return "{%08x-%04x-%04x-%s-%s}" % (\r
36                         self.Data1,\r
37                         self.Data2,\r
38                         self.Data3,\r
39                         ''.join(["%02x" % d for d in self.Data4[:2]]),\r
40                         ''.join(["%02x" % d for d in self.Data4[2:]]),\r
41                 )\r
42 \r
43 class SP_DEVINFO_DATA(ctypes.Structure):\r
44         _fields_ = (\r
45                 ('cbSize', DWORD),\r
46                 ('ClassGuid', GUID),\r
47                 ('DevInst', DWORD),\r
48                 ('Reserved', ULONG_PTR),\r
49         )\r
50         def __str__(self):\r
51                 return "ClassGuid:%s DevInst:%s" % (self.ClassGuid, self.DevInst)\r
52 PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA)\r
53 \r
54 class SP_DEVICE_INTERFACE_DATA(ctypes.Structure):\r
55         _fields_ = (\r
56                 ('cbSize', DWORD),\r
57                 ('InterfaceClassGuid', GUID),\r
58                 ('Flags', DWORD),\r
59                 ('Reserved', ULONG_PTR),\r
60         )\r
61         def __str__(self):\r
62                 return "InterfaceClassGuid:%s Flags:%s" % (self.InterfaceClassGuid, self.Flags)\r
63 \r
64 PSP_DEVICE_INTERFACE_DATA = ctypes.POINTER(SP_DEVICE_INTERFACE_DATA)\r
65 \r
66 PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p\r
67 \r
68 class dummy(ctypes.Structure):\r
69         _fields_=(("d1", DWORD), ("d2", WCHAR))\r
70         _pack_ = 1\r
71 SIZEOF_SP_DEVICE_INTERFACE_DETAIL_DATA_W = ctypes.sizeof(dummy)\r
72 \r
73 SetupDiDestroyDeviceInfoList = ctypes.windll.setupapi.SetupDiDestroyDeviceInfoList\r
74 SetupDiDestroyDeviceInfoList.argtypes = (HDEVINFO,)\r
75 SetupDiDestroyDeviceInfoList.restype = BOOL\r
76 \r
77 SetupDiGetClassDevs = ctypes.windll.setupapi.SetupDiGetClassDevsW\r
78 SetupDiGetClassDevs.argtypes = (ctypes.POINTER(GUID), PCWSTR, HWND, DWORD)\r
79 SetupDiGetClassDevs.restype = ValidHandle # HDEVINFO\r
80 \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
84 \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
88 \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
92 \r
93 GUID_CLASS_COMPORT = GUID(0x86e0d1e0L, 0x8089, 0x11d0,\r
94         (ctypes.c_ubyte*8)(0x9c, 0xe4, 0x08, 0x00, 0x3e, 0x30, 0x1f, 0x73))\r
95 \r
96 DIGCF_PRESENT = 2\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
106 \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
113         """\r
114         flags = DIGCF_DEVICEINTERFACE\r
115         if onlyAvailable:\r
116                 flags |= DIGCF_PRESENT\r
117 \r
118         buf = ctypes.create_unicode_buffer(1024)\r
119         g_hdi = SetupDiGetClassDevs(ctypes.byref(GUID_CLASS_COMPORT), None, NULL, flags)\r
120         try:\r
121                 for dwIndex in xrange(256):\r
122                         entry = {}\r
123                         did = SP_DEVICE_INTERFACE_DATA()\r
124                         did.cbSize = ctypes.sizeof(did)\r
125 \r
126                         if not SetupDiEnumDeviceInterfaces(\r
127                                 g_hdi,\r
128                                 None,\r
129                                 ctypes.byref(GUID_CLASS_COMPORT),\r
130                                 dwIndex,\r
131                                 ctypes.byref(did)\r
132                         ):\r
133                                 if ctypes.GetLastError() != ERROR_NO_MORE_ITEMS:\r
134                                         raise ctypes.WinError()\r
135                                 break\r
136 \r
137                         dwNeeded = DWORD()\r
138                         # get the size\r
139                         if not SetupDiGetDeviceInterfaceDetail(\r
140                                 g_hdi,\r
141                                 ctypes.byref(did),\r
142                                 None, 0, ctypes.byref(dwNeeded),\r
143                                 None\r
144                         ):\r
145                                 # Ignore ERROR_INSUFFICIENT_BUFFER\r
146                                 if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:\r
147                                         raise ctypes.WinError()\r
148                         # allocate buffer\r
149                         class SP_DEVICE_INTERFACE_DETAIL_DATA_W(ctypes.Structure):\r
150                                 _fields_ = (\r
151                                         ('cbSize', DWORD),\r
152                                         ('DevicePath', WCHAR*(dwNeeded.value - ctypes.sizeof(DWORD))),\r
153                                 )\r
154                                 def __str__(self):\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
161                                 g_hdi,\r
162                                 ctypes.byref(did),\r
163                                 ctypes.byref(idd), dwNeeded, None,\r
164                                 ctypes.byref(devinfo)\r
165                         ):\r
166                                 raise ctypes.WinError()\r
167 \r
168                         # hardware ID\r
169                         if not SetupDiGetDeviceRegistryProperty(\r
170                                 g_hdi,\r
171                                 ctypes.byref(devinfo),\r
172                                 SPDRP_HARDWAREID,\r
173                                 None,\r
174                                 ctypes.byref(buf), ctypes.sizeof(buf) - 1,\r
175                                 None\r
176                         ):\r
177                                 # Ignore ERROR_INSUFFICIENT_BUFFER\r
178                                 if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:\r
179                                         raise ctypes.WinError()\r
180                         else:\r
181                                 hwID = entry["hardwareID"] = buf.value\r
182 \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
187                                 try:\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
191                                         if addr:\r
192                                                 entry["bluetoothName"] = getBluetoothDeviceInfo(addr).szName\r
193                                 except:\r
194                                         pass\r
195                         elif hwID == r"Bluetooth\0004&0002":\r
196                                 # This is a Toshiba bluetooth port.\r
197                                 try:\r
198                                         entry["bluetoothAddress"], entry["bluetoothName"] = getToshibaBluetoothPortInfo(port)\r
199                                 except:\r
200                                         pass\r
201                         ctypes.windll.advapi32.RegCloseKey(regKey)\r
202 \r
203                         # friendly name\r
204                         if not SetupDiGetDeviceRegistryProperty(\r
205                                 g_hdi,\r
206                                 ctypes.byref(devinfo),\r
207                                 SPDRP_FRIENDLYNAME,\r
208                                 None,\r
209                                 ctypes.byref(buf), ctypes.sizeof(buf) - 1,\r
210                                 None\r
211                         ):\r
212                                 # Ignore ERROR_INSUFFICIENT_BUFFER\r
213                                 if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:\r
214                                         raise ctypes.WinError()\r
215                         else:\r
216                                 entry["friendlyName"] = buf.value\r
217 \r
218                         yield entry\r
219 \r
220         finally:\r
221                 SetupDiDestroyDeviceInfoList(g_hdi)\r
222 \r
223 BLUETOOTH_MAX_NAME_SIZE = 248\r
224 BTH_ADDR = BLUETOOTH_ADDRESS = ULONGLONG\r
225 \r
226 class SYSTEMTIME(ctypes.Structure):\r
227         _fields_ = (\r
228                 ("wYear", WORD),\r
229                 ("wMonth", WORD),\r
230                 ("wDayOfWeek", WORD),\r
231                 ("wDay", WORD),\r
232                 ("wHour", WORD),\r
233                 ("wMinute", WORD),\r
234                 ("wSecond", WORD),\r
235                 ("wMilliseconds", WORD)\r
236         )\r
237 \r
238 class BLUETOOTH_DEVICE_INFO(ctypes.Structure):\r
239         _fields_ = (\r
240                 ("dwSize", DWORD),\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
249         )\r
250         def __init__(self, **kwargs):\r
251                 super(BLUETOOTH_DEVICE_INFO, self).__init__(dwSize=ctypes.sizeof(self), **kwargs)\r
252 \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
256         if res != 0:\r
257                 raise ctypes.WinError(res)\r
258         return devInfo\r
259 \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
263                         try:\r
264                                 keyName = winreg.EnumKey(rootKey, index)\r
265                         except WindowsError:\r
266                                 break\r
267                         with winreg.OpenKey(rootKey, keyName) as itemKey:\r
268                                 with winreg.OpenKey(itemKey, "SCORIGINAL") as scorigKey:\r
269                                         try:\r
270                                                 if winreg.QueryValueEx(scorigKey, "PORTNAME")[0].rstrip("\0") != port:\r
271                                                         # This isn't the port we're interested in.\r
272                                                         continue\r
273                                         except WindowsError:\r
274                                                 # This isn't a COM port.\r
275                                                 continue\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
281                                 return addr, name\r
282         raise LookupError\r