%PDF- %PDF-
Direktori : /lib/calibre/calibre/devices/ |
Current File : //lib/calibre/calibre/devices/winusb.py |
#!/usr/bin/env python3 # License: GPLv3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> import os, string, re, sys, errno from collections import namedtuple, defaultdict from operator import itemgetter from ctypes import ( Structure, POINTER, c_ubyte, windll, byref, c_void_p, WINFUNCTYPE, c_uint, WinError, get_last_error, sizeof, c_wchar, create_string_buffer, cast, memset, wstring_at, addressof, create_unicode_buffer, string_at, c_uint64 as QWORD ) from ctypes.wintypes import DWORD, WORD, ULONG, LPCWSTR, HWND, BOOL, LPWSTR, UINT, BYTE, HANDLE, USHORT from pprint import pprint, pformat from polyglot.builtins import iteritems, itervalues from calibre import prints, as_unicode is64bit = sys.maxsize > (1 << 32) try: import winreg except ImportError: import _winreg as winreg # Data and function type definitions {{{ class GUID(Structure): _fields_ = [ ("data1", DWORD), ("data2", WORD), ("data3", WORD), ("data4", c_ubyte * 8)] def __init__(self, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8): self.data1 = l self.data2 = w1 self.data3 = w2 self.data4[0] = b1 self.data4[1] = b2 self.data4[2] = b3 self.data4[3] = b4 self.data4[4] = b5 self.data4[5] = b6 self.data4[6] = b7 self.data4[7] = b8 def __str__(self): return "{{{:08x}-{:04x}-{:04x}-{}-{}}}".format( self.data1, self.data2, self.data3, ''.join(["%02x" % d for d in self.data4[:2]]), ''.join(["%02x" % d for d in self.data4[2:]]), ) CONFIGRET = DWORD DEVINST = DWORD LPDWORD = POINTER(DWORD) LPVOID = c_void_p REG_QWORD = 11 IOCTL_STORAGE_MEDIA_REMOVAL = 0x2D4804 IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808 IOCTL_STORAGE_GET_DEVICE_NUMBER = 0x2D1080 def CTL_CODE(DeviceType, Function, Method, Access): return (DeviceType << 16) | (Access << 14) | (Function << 2) | Method def USB_CTL(id): # CTL_CODE(FILE_DEVICE_USB, (id), METHOD_BUFFERED, FILE_ANY_ACCESS) return CTL_CODE(0x22, id, 0, 0) IOCTL_USB_GET_ROOT_HUB_NAME = USB_CTL(258) IOCTL_USB_GET_NODE_INFORMATION = USB_CTL(258) IOCTL_USB_GET_NODE_CONNECTION_INFORMATION = USB_CTL(259) IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX = USB_CTL(274) IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME = USB_CTL(264) IOCTL_USB_GET_NODE_CONNECTION_NAME = USB_CTL(261) IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION = USB_CTL(260) USB_CONFIGURATION_DESCRIPTOR_TYPE = 2 USB_STRING_DESCRIPTOR_TYPE = 3 USB_INTERFACE_DESCRIPTOR_TYPE = 4 USB_REQUEST_GET_DESCRIPTOR = 0x06 MAXIMUM_USB_STRING_LENGTH = 255 StorageDeviceNumber = namedtuple('StorageDeviceNumber', 'type number partition_number') class STORAGE_DEVICE_NUMBER(Structure): _fields_ = [ ('DeviceType', DWORD), ('DeviceNumber', ULONG), ('PartitionNumber', ULONG) ] def as_tuple(self): return StorageDeviceNumber(self.DeviceType, self.DeviceNumber, self.PartitionNumber) class SP_DEVINFO_DATA(Structure): _fields_ = [ ('cbSize', DWORD), ('ClassGuid', GUID), ('DevInst', DEVINST), ('Reserved', POINTER(ULONG)), ] def __str__(self): return f"ClassGuid:{self.ClassGuid} DevInst:{self.DevInst}" PSP_DEVINFO_DATA = POINTER(SP_DEVINFO_DATA) class SP_DEVICE_INTERFACE_DATA(Structure): _fields_ = [ ('cbSize', DWORD), ('InterfaceClassGuid', GUID), ('Flags', DWORD), ('Reserved', POINTER(ULONG)), ] def __str__(self): return f"InterfaceClassGuid:{self.InterfaceClassGuid} Flags:{self.Flags}" ANYSIZE_ARRAY = 1 class SP_DEVICE_INTERFACE_DETAIL_DATA(Structure): _fields_ = [ ("cbSize", DWORD), ("DevicePath", c_wchar*ANYSIZE_ARRAY) ] UCHAR = c_ubyte class USB_DEVICE_DESCRIPTOR(Structure): _fields_ = ( ('bLength', UCHAR), ('bDescriptorType', UCHAR), ('bcdUSB', USHORT), ('bDeviceClass', UCHAR), ('bDeviceSubClass', UCHAR), ('bDeviceProtocol', UCHAR), ('bMaxPacketSize0', UCHAR), ('idVendor', USHORT), ('idProduct', USHORT), ('bcdDevice', USHORT), ('iManufacturer', UCHAR), ('iProduct', UCHAR), ('iSerialNumber', UCHAR), ('bNumConfigurations', UCHAR), ) def __repr__(self): return 'USBDevice(class=0x%x sub_class=0x%x protocol=0x%x vendor_id=0x%x product_id=0x%x bcd=0x%x manufacturer=%d product=%d serial_number=%d)' % ( self.bDeviceClass, self.bDeviceSubClass, self.bDeviceProtocol, self.idVendor, self.idProduct, self.bcdDevice, self.iManufacturer, self.iProduct, self.iSerialNumber) class USB_ENDPOINT_DESCRIPTOR(Structure): _fields_ = ( ('bLength', UCHAR), ('bDescriptorType', UCHAR), ('bEndpointAddress', UCHAR), ('bmAttributes', UCHAR), ('wMaxPacketSize', USHORT), ('bInterval', UCHAR) ) class USB_PIPE_INFO(Structure): _fields_ = ( ('EndpointDescriptor', USB_ENDPOINT_DESCRIPTOR), ('ScheduleOffset', ULONG), ) class USB_NODE_CONNECTION_INFORMATION_EX(Structure): _fields_ = ( ('ConnectionIndex', ULONG), ('DeviceDescriptor', USB_DEVICE_DESCRIPTOR), ('CurrentConfigurationValue', UCHAR), ('Speed', UCHAR), ('DeviceIsHub', BOOL), ('DeviceAddress', USHORT), ('NumberOfOpenPipes', ULONG), ('ConnectionStatus', c_uint), ('PipeList', USB_PIPE_INFO*ANYSIZE_ARRAY), ) class USB_STRING_DESCRIPTOR(Structure): _fields_ = ( ('bLength', UCHAR), ('bType', UCHAR), ('String', UCHAR * ANYSIZE_ARRAY), ) class USB_DESCRIPTOR_REQUEST(Structure): class SetupPacket(Structure): _fields_ = ( ('bmRequest', UCHAR), ('bRequest', UCHAR), ('wValue', UCHAR*2), ('wIndex', USHORT), ('wLength', USHORT), ) _fields_ = ( ('ConnectionIndex', ULONG), ('SetupPacket', SetupPacket), ('Data', USB_STRING_DESCRIPTOR), ) PUSB_DESCRIPTOR_REQUEST = POINTER(USB_DESCRIPTOR_REQUEST) PSP_DEVICE_INTERFACE_DETAIL_DATA = POINTER(SP_DEVICE_INTERFACE_DETAIL_DATA) PSP_DEVICE_INTERFACE_DATA = POINTER(SP_DEVICE_INTERFACE_DATA) INVALID_HANDLE_VALUE = c_void_p(-1).value GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 FILE_SHARE_READ = 0x1 FILE_SHARE_WRITE = 0x2 OPEN_EXISTING = 0x3 GUID_DEVINTERFACE_VOLUME = GUID(0x53F5630D, 0xB6BF, 0x11D0, 0x94, 0xF2, 0x00, 0xA0, 0xC9, 0x1E, 0xFB, 0x8B) GUID_DEVINTERFACE_DISK = GUID(0x53F56307, 0xB6BF, 0x11D0, 0x94, 0xF2, 0x00, 0xA0, 0xC9, 0x1E, 0xFB, 0x8B) GUID_DEVINTERFACE_CDROM = GUID(0x53f56308, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b) GUID_DEVINTERFACE_FLOPPY = GUID(0x53f56311, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b) GUID_DEVINTERFACE_USB_DEVICE = GUID(0xA5DCBF10, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED) GUID_DEVINTERFACE_USB_HUB = GUID(0xf18a0e88, 0xc30c, 0x11d0, 0x88, 0x15, 0x00, 0xa0, 0xc9, 0x06, 0xbe, 0xd8) DRIVE_UNKNOWN, DRIVE_NO_ROOT_DIR, DRIVE_REMOVABLE, DRIVE_FIXED, DRIVE_REMOTE, DRIVE_CDROM, DRIVE_RAMDISK = 0, 1, 2, 3, 4, 5, 6 DIGCF_PRESENT = 0x00000002 DIGCF_ALLCLASSES = 0x00000004 DIGCF_DEVICEINTERFACE = 0x00000010 ERROR_INSUFFICIENT_BUFFER = 0x7a ERROR_MORE_DATA = 234 ERROR_INVALID_DATA = 0xd ERROR_GEN_FAILURE = 31 HDEVINFO = HANDLE SPDRP_DEVICEDESC = DWORD(0x00000000) SPDRP_HARDWAREID = DWORD(0x00000001) SPDRP_COMPATIBLEIDS = DWORD(0x00000002) SPDRP_UNUSED0 = DWORD(0x00000003) SPDRP_SERVICE = DWORD(0x00000004) SPDRP_UNUSED1 = DWORD(0x00000005) SPDRP_UNUSED2 = DWORD(0x00000006) SPDRP_CLASS = DWORD(0x00000007) SPDRP_CLASSGUID = DWORD(0x00000008) SPDRP_DRIVER = DWORD(0x00000009) SPDRP_CONFIGFLAGS = DWORD(0x0000000A) SPDRP_MFG = DWORD(0x0000000B) SPDRP_FRIENDLYNAME = DWORD(0x0000000C) SPDRP_LOCATION_INFORMATION = DWORD(0x0000000D) SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = DWORD(0x0000000E) SPDRP_CAPABILITIES = DWORD(0x0000000F) SPDRP_UI_NUMBER = DWORD(0x00000010) SPDRP_UPPERFILTERS = DWORD(0x00000011) SPDRP_LOWERFILTERS = DWORD(0x00000012) SPDRP_BUSTYPEGUID = DWORD(0x00000013) SPDRP_LEGACYBUSTYPE = DWORD(0x00000014) SPDRP_BUSNUMBER = DWORD(0x00000015) SPDRP_ENUMERATOR_NAME = DWORD(0x00000016) SPDRP_SECURITY = DWORD(0x00000017) SPDRP_SECURITY_SDS = DWORD(0x00000018) SPDRP_DEVTYPE = DWORD(0x00000019) SPDRP_EXCLUSIVE = DWORD(0x0000001A) SPDRP_CHARACTERISTICS = DWORD(0x0000001B) SPDRP_ADDRESS = DWORD(0x0000001C) SPDRP_UI_NUMBER_DESC_FORMAT = DWORD(0x0000001D) SPDRP_DEVICE_POWER_DATA = DWORD(0x0000001E) SPDRP_REMOVAL_POLICY = DWORD(0x0000001F) SPDRP_REMOVAL_POLICY_HW_DEFAULT = DWORD(0x00000020) SPDRP_REMOVAL_POLICY_OVERRIDE = DWORD(0x00000021) SPDRP_INSTALL_STATE = DWORD(0x00000022) SPDRP_LOCATION_PATHS = DWORD(0x00000023) CR_CODES, CR_CODE_NAMES = {}, {} for line in '''\ #define CR_SUCCESS 0x00000000 #define CR_DEFAULT 0x00000001 #define CR_OUT_OF_MEMORY 0x00000002 #define CR_INVALID_POINTER 0x00000003 #define CR_INVALID_FLAG 0x00000004 #define CR_INVALID_DEVNODE 0x00000005 #define CR_INVALID_DEVINST CR_INVALID_DEVNODE #define CR_INVALID_RES_DES 0x00000006 #define CR_INVALID_LOG_CONF 0x00000007 #define CR_INVALID_ARBITRATOR 0x00000008 #define CR_INVALID_NODELIST 0x00000009 #define CR_DEVNODE_HAS_REQS 0x0000000A #define CR_DEVINST_HAS_REQS CR_DEVNODE_HAS_REQS #define CR_INVALID_RESOURCEID 0x0000000B #define CR_DLVXD_NOT_FOUND 0x0000000C #define CR_NO_SUCH_DEVNODE 0x0000000D #define CR_NO_SUCH_DEVINST CR_NO_SUCH_DEVNODE #define CR_NO_MORE_LOG_CONF 0x0000000E #define CR_NO_MORE_RES_DES 0x0000000F #define CR_ALREADY_SUCH_DEVNODE 0x00000010 #define CR_ALREADY_SUCH_DEVINST CR_ALREADY_SUCH_DEVNODE #define CR_INVALID_RANGE_LIST 0x00000011 #define CR_INVALID_RANGE 0x00000012 #define CR_FAILURE 0x00000013 #define CR_NO_SUCH_LOGICAL_DEV 0x00000014 #define CR_CREATE_BLOCKED 0x00000015 #define CR_NOT_SYSTEM_VM 0x00000016 #define CR_REMOVE_VETOED 0x00000017 #define CR_APM_VETOED 0x00000018 #define CR_INVALID_LOAD_TYPE 0x00000019 #define CR_BUFFER_SMALL 0x0000001A #define CR_NO_ARBITRATOR 0x0000001B #define CR_NO_REGISTRY_HANDLE 0x0000001C #define CR_REGISTRY_ERROR 0x0000001D #define CR_INVALID_DEVICE_ID 0x0000001E #define CR_INVALID_DATA 0x0000001F #define CR_INVALID_API 0x00000020 #define CR_DEVLOADER_NOT_READY 0x00000021 #define CR_NEED_RESTART 0x00000022 #define CR_NO_MORE_HW_PROFILES 0x00000023 #define CR_DEVICE_NOT_THERE 0x00000024 #define CR_NO_SUCH_VALUE 0x00000025 #define CR_WRONG_TYPE 0x00000026 #define CR_INVALID_PRIORITY 0x00000027 #define CR_NOT_DISABLEABLE 0x00000028 #define CR_FREE_RESOURCES 0x00000029 #define CR_QUERY_VETOED 0x0000002A #define CR_CANT_SHARE_IRQ 0x0000002B #define CR_NO_DEPENDENT 0x0000002C #define CR_SAME_RESOURCES 0x0000002D #define CR_NO_SUCH_REGISTRY_KEY 0x0000002E #define CR_INVALID_MACHINENAME 0x0000002F #define CR_REMOTE_COMM_FAILURE 0x00000030 #define CR_MACHINE_UNAVAILABLE 0x00000031 #define CR_NO_CM_SERVICES 0x00000032 #define CR_ACCESS_DENIED 0x00000033 #define CR_CALL_NOT_IMPLEMENTED 0x00000034 #define CR_INVALID_PROPERTY 0x00000035 #define CR_DEVICE_INTERFACE_ACTIVE 0x00000036 #define CR_NO_SUCH_DEVICE_INTERFACE 0x00000037 #define CR_INVALID_REFERENCE_STRING 0x00000038 #define CR_INVALID_CONFLICT_LIST 0x00000039 #define CR_INVALID_INDEX 0x0000003A #define CR_INVALID_STRUCTURE_SIZE 0x0000003B'''.splitlines(): line = line.strip() if line: name, code = line.split()[1:] if code.startswith('0x'): code = int(code, 16) else: code = CR_CODES[code] CR_CODES[name] = code CR_CODE_NAMES[code] = name CM_GET_DEVICE_INTERFACE_LIST_PRESENT = 0 CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES = 1 CM_GET_DEVICE_INTERFACE_LIST_BITS = 1 setupapi = windll.setupapi cfgmgr = windll.CfgMgr32 kernel32 = windll.Kernel32 def cwrap(name, restype, *argtypes, **kw): errcheck = kw.pop('errcheck', None) use_last_error = bool(kw.pop('use_last_error', True)) prototype = WINFUNCTYPE(restype, *argtypes, use_last_error=use_last_error) lib = cfgmgr if name.startswith('CM') else setupapi func = prototype((name, kw.pop('lib', lib))) if kw: raise TypeError('Unknown keyword arguments: %r' % kw) if errcheck is not None: func.errcheck = errcheck return func def handle_err_check(result, func, args): if result == INVALID_HANDLE_VALUE: raise WinError(get_last_error()) return result def bool_err_check(result, func, args): if not result: raise WinError(get_last_error()) return result def config_err_check(result, func, args): if result != CR_CODES['CR_SUCCESS']: raise WinError(result, 'The cfgmgr32 function failed with err: %s' % CR_CODE_NAMES.get(result, result)) return args GetLogicalDrives = cwrap('GetLogicalDrives', DWORD, errcheck=bool_err_check, lib=kernel32) GetDriveType = cwrap('GetDriveTypeW', UINT, LPCWSTR, lib=kernel32) GetVolumeNameForVolumeMountPoint = cwrap('GetVolumeNameForVolumeMountPointW', BOOL, LPCWSTR, LPWSTR, DWORD, errcheck=bool_err_check, lib=kernel32) GetVolumePathNamesForVolumeName = cwrap('GetVolumePathNamesForVolumeNameW', BOOL, LPCWSTR, LPWSTR, DWORD, LPDWORD, errcheck=bool_err_check, lib=kernel32) GetVolumeInformation = cwrap( 'GetVolumeInformationW', BOOL, LPCWSTR, LPWSTR, DWORD, POINTER(DWORD), POINTER(DWORD), POINTER(DWORD), LPWSTR, DWORD, errcheck=bool_err_check, lib=kernel32) ExpandEnvironmentStrings = cwrap('ExpandEnvironmentStringsW', DWORD, LPCWSTR, LPWSTR, DWORD, errcheck=bool_err_check, lib=kernel32) CreateFile = cwrap('CreateFileW', HANDLE, LPCWSTR, DWORD, DWORD, c_void_p, DWORD, DWORD, HANDLE, errcheck=handle_err_check, lib=kernel32) DeviceIoControl = cwrap('DeviceIoControl', BOOL, HANDLE, DWORD, LPVOID, DWORD, LPVOID, DWORD, POINTER(DWORD), LPVOID, errcheck=bool_err_check, lib=kernel32) CloseHandle = cwrap('CloseHandle', BOOL, HANDLE, errcheck=bool_err_check, lib=kernel32) QueryDosDevice = cwrap('QueryDosDeviceW', DWORD, LPCWSTR, LPWSTR, DWORD, errcheck=bool_err_check, lib=kernel32) SetupDiGetClassDevs = cwrap('SetupDiGetClassDevsW', HDEVINFO, POINTER(GUID), LPCWSTR, HWND, DWORD, errcheck=handle_err_check) SetupDiEnumDeviceInterfaces = cwrap('SetupDiEnumDeviceInterfaces', BOOL, HDEVINFO, PSP_DEVINFO_DATA, POINTER(GUID), DWORD, PSP_DEVICE_INTERFACE_DATA) SetupDiDestroyDeviceInfoList = cwrap('SetupDiDestroyDeviceInfoList', BOOL, HDEVINFO, errcheck=bool_err_check) SetupDiGetDeviceInterfaceDetail = cwrap( 'SetupDiGetDeviceInterfaceDetailW', BOOL, HDEVINFO, PSP_DEVICE_INTERFACE_DATA, PSP_DEVICE_INTERFACE_DETAIL_DATA, DWORD, POINTER(DWORD), PSP_DEVINFO_DATA) SetupDiEnumDeviceInfo = cwrap('SetupDiEnumDeviceInfo', BOOL, HDEVINFO, DWORD, PSP_DEVINFO_DATA) SetupDiGetDeviceRegistryProperty = cwrap( 'SetupDiGetDeviceRegistryPropertyW', BOOL, HDEVINFO, PSP_DEVINFO_DATA, DWORD, POINTER(DWORD), POINTER(BYTE), DWORD, POINTER(DWORD)) CM_Get_Parent = cwrap('CM_Get_Parent', CONFIGRET, POINTER(DEVINST), DEVINST, ULONG, errcheck=config_err_check) CM_Get_Child = cwrap('CM_Get_Child', CONFIGRET, POINTER(DEVINST), DEVINST, ULONG, errcheck=config_err_check) CM_Get_Sibling = cwrap('CM_Get_Sibling', CONFIGRET, POINTER(DEVINST), DEVINST, ULONG, errcheck=config_err_check) CM_Get_Device_ID_Size = cwrap('CM_Get_Device_ID_Size', CONFIGRET, POINTER(ULONG), DEVINST, ULONG) CM_Get_Device_ID = cwrap('CM_Get_Device_IDW', CONFIGRET, DEVINST, LPWSTR, ULONG, ULONG) # }}} # Utility functions {{{ _devid_pat = None def devid_pat(): global _devid_pat if _devid_pat is None: _devid_pat = re.compile(r'VID_([a-f0-9]{4})&PID_([a-f0-9]{4})&REV_([a-f0-9:]{4})', re.I) return _devid_pat class DeviceSet: def __init__(self, guid=GUID_DEVINTERFACE_VOLUME, enumerator=None, flags=DIGCF_PRESENT | DIGCF_DEVICEINTERFACE): self.guid_ref, self.enumerator, self.flags = (None if guid is None else byref(guid)), enumerator, flags self.dev_list = SetupDiGetClassDevs(self.guid_ref, self.enumerator, None, self.flags) def __del__(self): SetupDiDestroyDeviceInfoList(self.dev_list) del self.dev_list def interfaces(self, ignore_errors=False, yield_devlist=False): interface_data = SP_DEVICE_INTERFACE_DATA() interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA) buf = None i = -1 while True: i += 1 if not SetupDiEnumDeviceInterfaces(self.dev_list, None, self.guid_ref, i, byref(interface_data)): break try: buf, devinfo, devpath = get_device_interface_detail_data(self.dev_list, byref(interface_data), buf) except OSError: if ignore_errors: continue raise if yield_devlist: yield self.dev_list, devinfo, devpath else: yield devinfo, devpath def devices(self): devinfo = SP_DEVINFO_DATA() devinfo.cbSize = sizeof(SP_DEVINFO_DATA) i = -1 while True: i += 1 if not SetupDiEnumDeviceInfo(self.dev_list, i, byref(devinfo)): break yield self.dev_list, devinfo def iterchildren(parent_devinst): child = DEVINST(0) NO_MORE = CR_CODES['CR_NO_SUCH_DEVINST'] try: CM_Get_Child(byref(child), parent_devinst, 0) except OSError as err: if err.winerror == NO_MORE: return raise yield child.value while True: try: CM_Get_Sibling(byref(child), child, 0) except OSError as err: if err.winerror == NO_MORE: break raise yield child.value def iterdescendants(parent_devinst): for child in iterchildren(parent_devinst): yield child yield from iterdescendants(child) def iterancestors(devinst): NO_MORE = CR_CODES['CR_NO_SUCH_DEVINST'] parent = DEVINST(devinst) while True: try: CM_Get_Parent(byref(parent), parent, 0) except OSError as err: if err.winerror == NO_MORE: break raise yield parent.value def device_io_control(handle, which, inbuf, outbuf, initbuf): bytes_returned = DWORD(0) while True: initbuf(inbuf) try: DeviceIoControl(handle, which, inbuf, len(inbuf), outbuf, len(outbuf), byref(bytes_returned), None) except OSError as err: if err.winerror not in (ERROR_INSUFFICIENT_BUFFER, ERROR_MORE_DATA): raise outbuf = create_string_buffer(2*len(outbuf)) else: return outbuf, bytes_returned def get_storage_number(devpath): sdn = STORAGE_DEVICE_NUMBER() handle = CreateFile(devpath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None) bytes_returned = DWORD(0) try: DeviceIoControl(handle, IOCTL_STORAGE_GET_DEVICE_NUMBER, None, 0, byref(sdn), sizeof(STORAGE_DEVICE_NUMBER), byref(bytes_returned), None) finally: CloseHandle(handle) return sdn.as_tuple() def get_device_id(devinst, buf=None): if buf is None: buf = create_unicode_buffer(512) while True: ret = CM_Get_Device_ID(devinst, buf, len(buf), 0) if ret == CR_CODES['CR_BUFFER_SMALL']: devid_size = ULONG(0) CM_Get_Device_ID_Size(byref(devid_size), devinst, 0) buf = create_unicode_buffer(devid_size.value) continue if ret != CR_CODES['CR_SUCCESS']: raise WinError(ret, 'The cfgmgr32 function failed with err: %s' % CR_CODE_NAMES.get(ret, ret)) break return wstring_at(buf), buf def expand_environment_strings(src): sz = ExpandEnvironmentStrings(src, None, 0) while True: buf = create_unicode_buffer(sz) nsz = ExpandEnvironmentStrings(src, buf, len(buf)) if nsz <= sz: return buf.value sz = nsz def convert_registry_data(raw, size, dtype): if dtype == winreg.REG_NONE: return None if dtype == winreg.REG_BINARY: return string_at(raw, size) if dtype in (winreg.REG_SZ, winreg.REG_EXPAND_SZ, winreg.REG_MULTI_SZ): ans = wstring_at(raw, size // 2).rstrip('\0') if dtype == winreg.REG_MULTI_SZ: ans = tuple(ans.split('\0')) elif dtype == winreg.REG_EXPAND_SZ: ans = expand_environment_strings(ans) return ans if dtype == winreg.REG_DWORD: if size == 0: return 0 return cast(raw, LPDWORD).contents.value if dtype == REG_QWORD: if size == 0: return 0 return cast(raw, POINTER(QWORD)).contents.value raise ValueError('Unsupported data type: %r' % dtype) def get_device_registry_property(dev_list, p_devinfo, property_type=SPDRP_HARDWAREID, buf=None): if buf is None: buf = create_string_buffer(1024) data_type = DWORD(0) required_size = DWORD(0) ans = None while True: if not SetupDiGetDeviceRegistryProperty(dev_list, p_devinfo, property_type, byref(data_type), cast(buf, POINTER(BYTE)), len(buf), byref(required_size)): err = get_last_error() if err == ERROR_INSUFFICIENT_BUFFER: buf = create_string_buffer(required_size.value) continue if err == ERROR_INVALID_DATA: break raise WinError(err) ans = convert_registry_data(buf, required_size.value, data_type.value) break return buf, ans def get_device_interface_detail_data(dev_list, p_interface_data, buf=None): if buf is None: buf = create_string_buffer(512) detail = cast(buf, PSP_DEVICE_INTERFACE_DETAIL_DATA) # See http://stackoverflow.com/questions/10728644/properly-declare-sp-device-interface-detail-data-for-pinvoke # for why cbSize needs to be hardcoded below detail.contents.cbSize = 8 if is64bit else 6 required_size = DWORD(0) devinfo = SP_DEVINFO_DATA() devinfo.cbSize = sizeof(devinfo) while True: if not SetupDiGetDeviceInterfaceDetail(dev_list, p_interface_data, detail, len(buf), byref(required_size), byref(devinfo)): err = get_last_error() if err == ERROR_INSUFFICIENT_BUFFER: buf = create_string_buffer(required_size.value + 50) detail = cast(buf, PSP_DEVICE_INTERFACE_DETAIL_DATA) detail.contents.cbSize = 8 if is64bit else 6 continue raise WinError(err) break return buf, devinfo, wstring_at(addressof(buf) + sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA._fields_[0][1])) def get_volume_information(drive_letter): if not drive_letter.endswith('\\'): drive_letter += ':\\' fsname = create_unicode_buffer(255) vname = create_unicode_buffer(500) flags, serial_number, max_component_length = DWORD(0), DWORD(0), DWORD(0) GetVolumeInformation(drive_letter, vname, len(vname), byref(serial_number), byref(max_component_length), byref(flags), fsname, len(fsname)) flags = flags.value ans = { 'name': vname.value, 'filesystem': fsname.value, 'serial_number': serial_number.value, 'max_component_length': max_component_length.value, } for name, num in iteritems({'FILE_CASE_PRESERVED_NAMES':0x00000002, 'FILE_CASE_SENSITIVE_SEARCH':0x00000001, 'FILE_FILE_COMPRESSION':0x00000010, 'FILE_NAMED_STREAMS':0x00040000, 'FILE_PERSISTENT_ACLS':0x00000008, 'FILE_READ_ONLY_VOLUME':0x00080000, 'FILE_SEQUENTIAL_WRITE_ONCE':0x00100000, 'FILE_SUPPORTS_ENCRYPTION':0x00020000, 'FILE_SUPPORTS_EXTENDED_ATTRIBUTES':0x00800000, 'FILE_SUPPORTS_HARD_LINKS':0x00400000, 'FILE_SUPPORTS_OBJECT_IDS':0x00010000, 'FILE_SUPPORTS_OPEN_BY_FILE_ID':0x01000000, 'FILE_SUPPORTS_REPARSE_POINTS':0x00000080, 'FILE_SUPPORTS_SPARSE_FILES':0x00000040, 'FILE_SUPPORTS_TRANSACTIONS':0x00200000, 'FILE_SUPPORTS_USN_JOURNAL':0x02000000, 'FILE_UNICODE_ON_DISK':0x00000004, 'FILE_VOLUME_IS_COMPRESSED':0x00008000, 'FILE_VOLUME_QUOTAS':0x00000020}): ans[name] = bool(num & flags) return ans def get_volume_pathnames(volume_id, buf=None): if buf is None: buf = create_unicode_buffer(512) bufsize = DWORD(0) while True: try: GetVolumePathNamesForVolumeName(volume_id, buf, len(buf), byref(bufsize)) break except OSError as err: if err.winerror == ERROR_MORE_DATA: buf = create_unicode_buffer(bufsize.value + 10) continue raise ans = wstring_at(buf, bufsize.value) return buf, list(filter(None, ans.split('\0'))) # }}} # def scan_usb_devices(): {{{ _USBDevice = namedtuple('USBDevice', 'vendor_id product_id bcd devid devinst') class USBDevice(_USBDevice): def __repr__(self): def r(x): if x is None: return 'None' return '0x%x' % x return 'USBDevice(vendor_id={} product_id={} bcd={} devid={} devinst={})'.format( r(self.vendor_id), r(self.product_id), r(self.bcd), self.devid, self.devinst) def parse_hex(x): return int(x.replace(':', 'a'), 16) def iterusbdevices(): buf = None pat = devid_pat() for dev_list, devinfo in DeviceSet(guid=None, enumerator='USB', flags=DIGCF_PRESENT | DIGCF_ALLCLASSES).devices(): buf, devid = get_device_registry_property(dev_list, byref(devinfo), buf=buf) if devid: devid = devid[0].lower() m = pat.search(devid) if m is None: yield USBDevice(None, None, None, devid, devinfo.DevInst) else: try: vid, pid, bcd = map(parse_hex, m.group(1, 2, 3)) except Exception: yield USBDevice(None, None, None, devid, devinfo.DevInst) else: yield USBDevice(vid, pid, bcd, devid, devinfo.DevInst) def scan_usb_devices(): return tuple(iterusbdevices()) # }}} def get_drive_letters_for_device(usbdev, storage_number_map=None, debug=False): # {{{ ''' Get the drive letters for a connected device. The drive letters are sorted by storage number, which (I think) corresponds to the order they are exported by the firmware. :param usbdevice: As returned by :function:`scan_usb_devices` ''' ans = {'pnp_id_map': {}, 'drive_letters':[], 'readonly_drives':set(), 'sort_map':{}} sn_map = get_storage_number_map(debug=debug) if storage_number_map is None else storage_number_map if debug: prints('Storage number map:') prints(pformat(sn_map)) if not sn_map: return ans devid, mi = (usbdev.devid or '').rpartition('&')[::2] if mi.startswith('mi_'): if debug: prints('Iterating over all devices of composite device:', devid) dl = ans['drive_letters'] for c in iterusbdevices(): if c.devid and c.devid.startswith(devid): a = get_drive_letters_for_device_single(c, sn_map, debug=debug) if debug: prints('Drive letters for:', c.devid, ':', a['drive_letters']) for m in ('pnp_id_map', 'sort_map'): ans[m].update(a[m]) ans['readonly_drives'] |= a['readonly_drives'] for x in a['drive_letters']: if x not in dl: dl.append(x) ans['drive_letters'].sort(key=ans['sort_map'].get) return ans else: return get_drive_letters_for_device_single(usbdev, sn_map, debug=debug) def get_drive_letters_for_device_single(usbdev, storage_number_map, debug=False): ans = {'pnp_id_map': {}, 'drive_letters':[], 'readonly_drives':set(), 'sort_map':{}} descendants = frozenset(iterdescendants(usbdev.devinst)) for devinfo, devpath in DeviceSet(GUID_DEVINTERFACE_DISK).interfaces(): if devinfo.DevInst in descendants: if debug: try: devid = get_device_id(devinfo.DevInst)[0] except Exception: devid = 'Unknown' try: storage_number = get_storage_number(devpath) except OSError as err: if debug: prints(f'Failed to get storage number for: {devid} with error: {as_unicode(err)}') continue if debug: prints(f'Storage number for {devid}: {storage_number}') if storage_number: partitions = storage_number_map.get(storage_number[:2]) drive_letters = [] for partition_number, dl in partitions or (): drive_letters.append(dl) ans['sort_map'][dl] = storage_number.number, partition_number if drive_letters: for dl in drive_letters: ans['pnp_id_map'][dl] = devpath ans['drive_letters'].append(dl) ans['drive_letters'].sort(key=ans['sort_map'].get) for dl in ans['drive_letters']: try: if is_readonly(dl): ans['readonly_drives'].add(dl) except OSError as err: if debug: prints(f'Failed to get readonly status for drive: {dl} with error: {as_unicode(err)}') return ans def get_storage_number_map(drive_types=(DRIVE_REMOVABLE, DRIVE_FIXED), debug=False): ' Get a mapping of drive letters to storage numbers for all drives on system (of the specified types) ' mask = GetLogicalDrives() type_map = {letter:GetDriveType(letter + ':' + os.sep) for i, letter in enumerate(string.ascii_uppercase) if mask & (1 << i)} drives = (letter for letter, dt in iteritems(type_map) if dt in drive_types) ans = defaultdict(list) for letter in drives: try: sn = get_storage_number('\\\\.\\' + letter + ':') ans[sn[:2]].append((sn[2], letter)) except OSError as err: if debug: prints(f'Failed to get storage number for drive: {letter} with error: {as_unicode(err)}') continue for val in itervalues(ans): val.sort(key=itemgetter(0)) return dict(ans) def get_storage_number_map_alt(debug=False): ' Alternate implementation that works without needing to call GetDriveType() (which causes floppy drives to seek) ' wbuf = create_unicode_buffer(512) ans = defaultdict(list) for devinfo, devpath in DeviceSet().interfaces(): if not devpath.endswith(os.sep): devpath += os.sep try: GetVolumeNameForVolumeMountPoint(devpath, wbuf, len(wbuf)) except OSError as err: if debug: prints(f'Failed to get volume id for drive: {devpath} with error: {as_unicode(err)}') continue vname = wbuf.value try: wbuf, names = get_volume_pathnames(vname, buf=wbuf) except OSError as err: if debug: prints(f'Failed to get mountpoints for volume {devpath} with error: {as_unicode(err)}') continue for name in names: name = name.upper() if len(name) == 3 and name.endswith(':\\') and name[0] in string.ascii_uppercase: break else: if debug: prints(f'Ignoring volume {devpath} as it has no assigned drive letter. Mountpoints: {names}') continue try: sn = get_storage_number('\\\\.\\' + name[0] + ':') ans[sn[:2]].append((sn[2], name[0])) except OSError as err: if debug: prints(f'Failed to get storage number for drive: {name[0]} with error: {as_unicode(err)}') continue for val in itervalues(ans): val.sort(key=itemgetter(0)) return dict(ans) # }}} def is_usb_device_connected(vendor_id, product_id): # {{{ for usbdev in iterusbdevices(): if usbdev.vendor_id == vendor_id and usbdev.product_id == product_id: return True return False # }}} def get_usb_info(usbdev, debug=False): # {{{ ''' The USB info (manufacturer/product names and serial number) Requires communication with the hub the device is connected to. :param usbdev: A usb device as returned by :function:`scan_usb_devices` ''' ans = {} hub_map = {devinfo.DevInst:path for devinfo, path in DeviceSet(guid=GUID_DEVINTERFACE_USB_HUB).interfaces()} for parent in iterancestors(usbdev.devinst): parent_path = hub_map.get(parent) if parent_path is not None: break else: if debug: prints('Cannot get USB info as parent of device is not a HUB or device has no parent (was probably disconnected)') return ans for devlist, devinfo in DeviceSet(guid=GUID_DEVINTERFACE_USB_DEVICE).devices(): if devinfo.DevInst == usbdev.devinst: device_port = get_device_registry_property(devlist, byref(devinfo), SPDRP_ADDRESS)[1] break else: return ans if not device_port: if debug: prints('Cannot get usb info as the SPDRP_ADDRESS property is not present in the registry (can happen with broken USB hub drivers)') return ans handle = CreateFile(parent_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None) try: buf, dd = get_device_descriptor(handle, device_port) if dd.idVendor == usbdev.vendor_id and dd.idProduct == usbdev.product_id and dd.bcdDevice == usbdev.bcd: # Dont need to read language since we only care about english names # buf, langs = get_device_languages(handle, device_port) # print(111, langs) for index, name in ((dd.iManufacturer, 'manufacturer'), (dd.iProduct, 'product'), (dd.iSerialNumber, 'serial_number')): if index: try: buf, ans[name] = get_device_string(handle, device_port, index, buf=buf) except OSError as err: if debug: # Note that I have observed that this fails # randomly after some time of my Kindle being # connected. Disconnecting and reconnecting causes # it to start working again. prints('Failed to read %s from device, with error: [%d] %s' % (name, err.winerror, as_unicode(err))) finally: CloseHandle(handle) return ans def alloc_descriptor_buf(buf): if buf is None: buf = create_string_buffer(sizeof(USB_DESCRIPTOR_REQUEST) + 700) else: memset(buf, 0, len(buf)) return buf def get_device_descriptor(hub_handle, device_port, buf=None): buf = alloc_descriptor_buf(buf) def initbuf(b): cast(b, POINTER(USB_NODE_CONNECTION_INFORMATION_EX)).contents.ConnectionIndex = device_port buf, bytes_returned = device_io_control(hub_handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, buf, buf, initbuf) return buf, USB_DEVICE_DESCRIPTOR.from_buffer_copy(cast(buf, POINTER(USB_NODE_CONNECTION_INFORMATION_EX)).contents.DeviceDescriptor) def get_device_string(hub_handle, device_port, index, buf=None, lang=0x409): buf = alloc_descriptor_buf(buf) def initbuf(b): p = cast(b, PUSB_DESCRIPTOR_REQUEST).contents p.ConnectionIndex = device_port sp = p.SetupPacket sp.bmRequest, sp.bRequest = 0x80, USB_REQUEST_GET_DESCRIPTOR sp.wValue[0], sp.wValue[1] = index, USB_STRING_DESCRIPTOR_TYPE sp.wIndex = lang sp.wLength = MAXIMUM_USB_STRING_LENGTH + 2 buf, bytes_returned = device_io_control(hub_handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, buf, buf, initbuf) data = cast(buf, PUSB_DESCRIPTOR_REQUEST).contents.Data sz, dtype = data.bLength, data.bType if dtype != 0x03: raise OSError(errno.EINVAL, 'Invalid datatype for string descriptor: 0x%x' % dtype) return buf, wstring_at(addressof(data.String), sz // 2).rstrip('\0') def get_device_languages(hub_handle, device_port, buf=None): ' Get the languages supported by the device for strings ' buf = alloc_descriptor_buf(buf) def initbuf(b): p = cast(b, PUSB_DESCRIPTOR_REQUEST).contents p.ConnectionIndex = device_port sp = p.SetupPacket sp.bmRequest, sp.bRequest = 0x80, USB_REQUEST_GET_DESCRIPTOR sp.wValue[1] = USB_STRING_DESCRIPTOR_TYPE sp.wLength = MAXIMUM_USB_STRING_LENGTH + 2 buf, bytes_returned = device_io_control(hub_handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, buf, buf, initbuf) data = cast(buf, PUSB_DESCRIPTOR_REQUEST).contents.Data sz, dtype = data.bLength, data.bType if dtype != 0x03: raise OSError(errno.EINVAL, 'Invalid datatype for string descriptor: 0x%x' % dtype) data = cast(data.String, POINTER(USHORT*(sz//2))) return buf, list(filter(None, data.contents)) # }}} def is_readonly(drive_letter): # {{{ return get_volume_information(drive_letter)['FILE_READ_ONLY_VOLUME'] # }}} def develop(): # {{{ from calibre.customize.ui import device_plugins usb_devices = scan_usb_devices() drive_letters = set() pprint(usb_devices) print() devplugins = list(sorted(device_plugins(), key=lambda x: x.__class__.__name__)) for dev in devplugins: dev.startup() for dev in devplugins: if dev.MANAGES_DEVICE_PRESENCE: continue connected, usbdev = dev.is_usb_connected(usb_devices, debug=True) if connected: print('\n') print(f'Potentially connected device: {dev.get_gui_name()} at {usbdev}') print() print('Drives for this device:') data = get_drive_letters_for_device(usbdev, debug=True) pprint(data) drive_letters |= set(data['drive_letters']) print() print('Is device connected:', is_usb_device_connected(*usbdev[:2])) print() print('Device USB data:', get_usb_info(usbdev, debug=True)) def drives_for(vendor_id, product_id=None): usb_devices = scan_usb_devices() pprint(usb_devices) for usbdev in usb_devices: if usbdev.vendor_id == vendor_id and (product_id is None or usbdev.product_id == product_id): print(f'Drives for: {usbdev}') pprint(get_drive_letters_for_device(usbdev, debug=True)) print('USB info:', get_usb_info(usbdev, debug=True)) if __name__ == '__main__': develop() # }}}