diff options
author | zsquareplusc <cliechti@gmx.net> | 2019-08-13 13:36:43 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-13 13:36:43 +0200 |
commit | 3835ccb3688c9351e32941bae4100afece595c4d (patch) | |
tree | bb1123efa010a18f68a7158830e0992a66466416 | |
parent | 14d613dbde6ae1d54dd2a4975541a0058594812e (diff) | |
parent | 568362c925bdc738f888e5094b4106053d986912 (diff) | |
download | pyserial-git-3835ccb3688c9351e32941bae4100afece595c4d.tar.gz |
Merge pull request #364 from torque/macos-usb-product-descriptor-fix
macOS: rework list_ports to support unicode product descriptors.
-rw-r--r-- | serial/tools/list_ports_osx.py | 77 |
1 files changed, 55 insertions, 22 deletions
diff --git a/serial/tools/list_ports_osx.py b/serial/tools/list_ports_osx.py index f46a820..34a7f5a 100644 --- a/serial/tools/list_ports_osx.py +++ b/serial/tools/list_ports_osx.py @@ -35,25 +35,40 @@ kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault") kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault") kCFStringEncodingMacRoman = 0 +kCFStringEncodingUTF8 = 0x08000100 + +# defined in `IOKit/usb/USBSpec.h` +kUSBVendorString = 'USB Vendor Name' +kUSBSerialNumberString = 'USB Serial Number' + +# `io_name_t` defined as `typedef char io_name_t[128];` +# in `device/device_types.h` +io_name_size = 128 + +# defined in `mach/kern_return.h` +KERN_SUCCESS = 0 +# kern_return_t defined as `typedef int kern_return_t;` in `mach/i386/kern_return.h` +kern_return_t = ctypes.c_int iokit.IOServiceMatching.restype = ctypes.c_void_p iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] -iokit.IOServiceGetMatchingServices.restype = ctypes.c_void_p +iokit.IOServiceGetMatchingServices.restype = kern_return_t iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +iokit.IOServiceGetMatchingServices.restype = kern_return_t iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32] iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] -iokit.IORegistryEntryGetPath.restype = ctypes.c_void_p +iokit.IORegistryEntryGetPath.restype = kern_return_t iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p] -iokit.IORegistryEntryGetName.restype = ctypes.c_void_p +iokit.IORegistryEntryGetName.restype = kern_return_t iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p] -iokit.IOObjectGetClass.restype = ctypes.c_void_p +iokit.IOObjectGetClass.restype = kern_return_t iokit.IOObjectRelease.argtypes = [ctypes.c_void_p] @@ -64,6 +79,9 @@ cf.CFStringCreateWithCString.restype = ctypes.c_void_p cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32] cf.CFStringGetCStringPtr.restype = ctypes.c_char_p +cf.CFStringGetCString.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_uint32] +cf.CFStringGetCString.restype = ctypes.c_bool + cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p] cf.CFNumberGetValue.restype = ctypes.c_void_p @@ -88,8 +106,8 @@ def get_string_property(device_type, property): """ key = cf.CFStringCreateWithCString( kCFAllocatorDefault, - property.encode("mac_roman"), - kCFStringEncodingMacRoman) + property.encode("utf-8"), + kCFStringEncodingUTF8) CFContainer = iokit.IORegistryEntryCreateCFProperty( device_type, @@ -101,7 +119,12 @@ def get_string_property(device_type, property): if CFContainer: output = cf.CFStringGetCStringPtr(CFContainer, 0) if output is not None: - output = output.decode('mac_roman') + output = output.decode('utf-8') + else: + buffer = ctypes.create_string_buffer(io_name_size); + success = cf.CFStringGetCString(CFContainer, ctypes.byref(buffer), io_name_size, kCFStringEncodingUTF8) + if success: + output = buffer.value.decode('utf-8') cf.CFRelease(CFContainer) return output @@ -118,8 +141,8 @@ def get_int_property(device_type, property, cf_number_type): """ key = cf.CFStringCreateWithCString( kCFAllocatorDefault, - property.encode("mac_roman"), - kCFStringEncodingMacRoman) + property.encode("utf-8"), + kCFStringEncodingUTF8) CFContainer = iokit.IORegistryEntryCreateCFProperty( device_type, @@ -137,12 +160,19 @@ def get_int_property(device_type, property, cf_number_type): return number.value return None - def IORegistryEntryGetName(device): - pathname = ctypes.create_string_buffer(100) # TODO: Is this ok? - iokit.IOObjectGetClass(device, ctypes.byref(pathname)) - return pathname.value - + devicename = ctypes.create_string_buffer(io_name_size); + res = iokit.IORegistryEntryGetName(device, ctypes.byref(devicename)) + if res != KERN_SUCCESS: + return None + # this works in python2 but may not be valid. Also I don't know if + # this encoding is guaranteed. It may be dependent on system locale. + return devicename.value.decode('utf-8') + +def IOObjectGetClass(device): + classname = ctypes.create_string_buffer(io_name_size) + iokit.IOObjectGetClass(device, ctypes.byref(classname)) + return classname.value def GetParentDeviceByType(device, parent_type): """ Find the first parent of a device that implements the parent_type @@ -150,15 +180,15 @@ def GetParentDeviceByType(device, parent_type): @return Pointer to the parent type, or None if it was not found. """ # First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice. - parent_type = parent_type.encode('mac_roman') - while IORegistryEntryGetName(device) != parent_type: + parent_type = parent_type.encode('utf-8') + while IOObjectGetClass(device) != parent_type: parent = ctypes.c_void_p() response = iokit.IORegistryEntryGetParentEntry( device, - "IOService".encode("mac_roman"), + "IOService".encode("utf-8"), ctypes.byref(parent)) # If we weren't able to find a parent for the device, we're done. - if response != 0: + if response != KERN_SUCCESS: return None device = parent return device @@ -172,7 +202,7 @@ def GetIOServicesByType(service_type): iokit.IOServiceGetMatchingServices( kIOMasterPortDefault, - iokit.IOServiceMatching(service_type.encode('mac_roman')), + iokit.IOServiceMatching(service_type.encode('utf-8')), ctypes.byref(serial_port_iterator)) services = [] @@ -246,9 +276,12 @@ def comports(include_links=False): # fetch some useful informations from properties info.vid = get_int_property(usb_device, "idVendor", kCFNumberSInt16Type) info.pid = get_int_property(usb_device, "idProduct", kCFNumberSInt16Type) - info.serial_number = get_string_property(usb_device, "USB Serial Number") - info.product = get_string_property(usb_device, "USB Product Name") or 'n/a' - info.manufacturer = get_string_property(usb_device, "USB Vendor Name") + info.serial_number = get_string_property(usb_device, kUSBSerialNumberString) + # We know this is a usb device, so the + # IORegistryEntryName should always be aliased to the + # usb product name string descriptor. + info.product = IORegistryEntryGetName(usb_device) or 'n/a' + info.manufacturer = get_string_property(usb_device, kUSBVendorString) locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) info.location = location_to_string(locationID) info.interface = search_for_locationID_in_interfaces(serial_interfaces, locationID) |