0

I am trying to connect to and initiate a USB scanner from my macOS swift app so that I can capture the scanned image. I can detect the USB device successfully and match it with the expected productID and vendorID but I can't open the connection. Other apps like Image Capture can access the scanner fine. Here is my console output:

No entitlements found.
User granted permission to access USB devices.
in callback func iterating 45139
in the callback loop
Detected device with Vendor ID: 1208, Product ID: 327 iterator 45139
Found device using callback: 45139
advancing
in the callback loop
Detected device with Vendor ID: 1133, Product ID: 50484 iterator 45143
Found device using callback: 45143
advancing
opening 45139
Error opening USB device: 268435459
Error message: (ipc/send) invalid destination port
Failed to open USB device.

My code for accessing scaner

import IOKit
import IOKit.usb
import IOKit.usb.IOUSBLib
import Foundation
import AppKit

class ScanMan {
    static let shared = ScanMan() // Singleton instance
    
    private var notificationPort: IONotificationPortRef?
    private var notificationIterator: io_iterator_t = 0
    private let semaphore = DispatchSemaphore(value: 1)
    private let scanManQueue = DispatchQueue(label: "com.achunt.scanManQueue")
    private var isInitialized = false
    private var connectedDevices: [DeviceInfo] = [] // Array to hold connected devices
    
    private init() {
        notificationPort = IONotificationPortCreate(kIOMainPortDefault)
        let runLoopSource = IONotificationPortGetRunLoopSource(notificationPort!).takeUnretainedValue()
        CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .defaultMode)
        
        let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
        IOServiceAddMatchingNotification(
            notificationPort!,
            kIOMatchedNotification,
            matchingDict,
            deviceAddedCallback,
            nil,
            &notificationIterator
        )
        
        requestUSBDeviceAccess()
        isInitialized = true
    }
    
    private func isFullyInitialized() -> Bool {
        return isInitialized
    }
    func addUSBDevice(device: DeviceInfo) {
        connectedDevices.append(device)
    }
    
    func performOCR(on image: NSImage) {
        // Your OCR logic here using the image
        // ...
    }
    
    func requestUSBDeviceAccess() {devices
        let accessGranted = checkUSBDeviceAccess()
        
        if accessGranted {
            print("USB device access is already granted.")
        } else {
            requestPermissionFromUser()
        }
    }
    
    private func checkUSBDeviceAccess() -> Bool {
        guard let entitlements = Bundle.main.object(forInfoDictionaryKey: "com.apple.security.application-groups") as? [String] else {
            print("No entitlements found.")
            return false
        }
        
        let requiredEntitlement = "com.apple.security.device.usb"
        let hasUSBDeviceAccess = entitlements.contains(requiredEntitlement)
        
        if hasUSBDeviceAccess {
            print("App has permission to access USB devices.")
        } else {
            print("App does not have permission to access USB devices.")
        }
        
        return hasUSBDeviceAccess
    }
    
    
    private func requestPermissionFromUser() {
        let alert = NSAlert()
        alert.messageText = "Allow Access to USB Devices"
        alert.informativeText = "Your app needs permission to access USB devices in order to function properly."
        alert.addButton(withTitle: "Allow")
        alert.addButton(withTitle: "Deny")
        
        let response = alert.runModal()
        if response == .alertFirstButtonReturn {
            print("User granted permission to access USB devices.")
        } else {
            print("User denied permission to access USB devices.")
        }
    }
    private func accessShared<T>(_ block: () -> T) -> T {
        semaphore.wait() // Wait for the semaphore
        defer { semaphore.signal() } // Release the semaphore after the block execution
        return block()
    }
    
    func startScanning() -> [NSImage] {
        let vendorID: UInt16 = 0x04b8
        let productID: UInt16 = 0x0147
        
        var scannedImages: [NSImage] = []
        deviceAddedCallback(nil, iterator: notificationIterator)
        
        var deviceFind: io_service_t = 0
        for device in connectedDevices {
            if(device.vendorID == vendorID){
                if(device.productID == productID){
                    deviceFind = device.device
                }
            }
        }
        
        let deviceInterface = openUSBDevice(deviceFind)
        
        if deviceInterface != 0 {
            for scannedImage in scanningLogic(using: deviceInterface) {
                scannedImages.append(scannedImage)
            }
            
            closeUSBDevice(deviceInterface)
        } else {
            print("Failed to open USB device.")
        }
        
        return scannedImages
    }
    
    
    func findUSBDevice(iterator: io_iterator_t, vendorID: UInt16, productID: UInt16) -> io_service_t? {
        var nextDevice = iterator
        return accessShared {
            while nextDevice != 0 {
                let matchingDict = IOServiceMatching(kIOUSBDeviceClassName) as NSMutableDictionary
                matchingDict[kUSBVendorID] = NSNumber(value: vendorID)
                matchingDict[kUSBProductID] = NSNumber(value: productID)
                
                if IOObjectConformsTo(nextDevice, kIOUSBDeviceClassName) == 1 {
                    let vendorIDCF = IORegistryEntryCreateCFProperty(nextDevice, kUSBVendorID as CFString, kCFAllocatorDefault, 0)
                    let productIDCF = IORegistryEntryCreateCFProperty(nextDevice, kUSBProductID as CFString, kCFAllocatorDefault, 0)
                    
                    if let vendorIDValue = vendorIDCF?.takeUnretainedValue() as? NSNumber,
                       let productIDValue = productIDCF?.takeUnretainedValue() as? NSNumber,
                       vendorIDValue.uint16Value == vendorID && productIDValue.uint16Value == productID {
                        return nextDevice
                    }
                }
                
                IOObjectRelease(nextDevice)
                nextDevice = IOIteratorNext(iterator)
            }
            
            return nil
        }
    }
    
    
    private func openUSBDevice(_ device: io_service_t) -> io_connect_t {
        var deviceInterface: io_connect_t = 0
        let kr = IOServiceOpen(device, mach_task_self_, 0, &deviceInterface)
        print("opening \(device)")
        
        if kr == KERN_SUCCESS {
            print("Successfully opened USB device.")
        } else {
            print("Error opening USB device: \(kr)")
            if let errorMessage = String(cString: mach_error_string(kr), encoding: .utf8) {
                print("Error message: \(errorMessage)")
            } else {
                print("Unknown error occurred.")
            }
        }
        
        return deviceInterface
    }
    
    
    private func closeUSBDevice(_ deviceInterface: io_connect_t) {
        IOServiceClose(deviceInterface)
    }
    
    func scanningLogic(using deviceInterface: io_connect_t) -> [NSImage] {
        
        var mockScannedImages: [NSImage] = []
        
        for i in 1...3 {
            if let sampleImage = generateSampleImage(number: i) {
                mockScannedImages.append(sampleImage)
            }
        }
        
        return mockScannedImages
    }
    
    private func generateSampleImage(number: Int) -> NSImage? {
        let imageSize = CGSize(width: 100, height: 100)
        let rect = NSRect(origin: .zero, size: imageSize)
        
        let sampleImage = NSImage(size: imageSize)
        sampleImage.lockFocus()
        
        NSColor.blue.setFill()
        rect.fill()
        
        let text = "\(number)"
        let textAttributes: [NSAttributedString.Key: Any] = [
            .font: NSFont.systemFont(ofSize: 20),
            .foregroundColor: NSColor.white
        ]
        
        text.draw(in: rect, withAttributes: textAttributes)
        
        sampleImage.unlockFocus()
        
        return sampleImage
    }
}

func deviceAddedCallback(_ refCon: UnsafeMutableRawPointer?, iterator: io_iterator_t) {
    var device: io_object_t = 0
    device = IOIteratorNext(iterator)
    print("in callback func iterating \(device)")
    while device != 0 {
        var vendorID: UInt16 = 0
        var productID: UInt16 = 0
        print("in the callback loop")
        
        if let vendorIDCF = IORegistryEntryCreateCFProperty(device, kUSBVendorID as CFString, kCFAllocatorDefault, 0)?.takeRetainedValue() as? NSNumber,
           let productIDCF = IORegistryEntryCreateCFProperty(device, kUSBProductID as CFString, kCFAllocatorDefault, 0)?.takeRetainedValue() as? NSNumber {
            vendorID = vendorIDCF.uint16Value
            productID = productIDCF.uint16Value
        }
        
        print("Detected device with Vendor ID: \(vendorID), Product ID: \(productID) iterator \(device)")
        
        if let foundDevice = ScanMan.shared.findUSBDevice(iterator: device, vendorID: vendorID, productID: productID) {
            print("Found device using callback: \(foundDevice)")
            let device = DeviceInfo(vendorID: vendorID, productID: productID, device: foundDevice)
            ScanMan.shared.addUSBDevice(device: device)
        }
        print("advancing")
        IOObjectRelease(device)
        device = IOIteratorNext(iterator)
    }
    
}


I have tried to access the USB device and open the connection. I can detect the correct USB device, but always get an error opening a connection. I have checked entitlements and permissions in Xcode and request from the user too.

1 Answer 1

0

So i didnt realize that the scanner was ImageCaptureCore compatible, so this is the implementation I went with.

class ScanMan: NSObject, ICDeviceBrowserDelegate, ICScannerDeviceDelegate, ObservableObject {
func device(_ device: ICDevice, didCloseSessionWithError error: (any Error)?) {
    print("error: \(String(describing: error))")
}

func device(_ device: ICDevice, didOpenSessionWithError error: (any Error)?) {
    print("error: \(String(describing: error))")
}


var mDeviceBrowser: ICDeviceBrowser!
var mscanners: [ICDevice] = []
@Published var scannedImages: [NSImage] = []

@IBOutlet var mscannersController: NSArrayController!
@IBOutlet var mMediaFilesController: NSArrayController!
@IBOutlet var mscannerContentTableView: NSTableView!
@IBOutlet var mscannersTableView: NSTableView!

var scanners: [ICDevice] {
    get {
        return mscanners
    }
    set {
        mscanners = newValue
    }
}

override init() {
        super.init()
        initializeDeviceBrowser()
    }

var canDownload: Bool {
    return mMediaFilesController.selectedObjects.count > 0
}


func initializeDeviceBrowser() {
    mscanners = []

    mDeviceBrowser = ICDeviceBrowser()
    mDeviceBrowser.delegate = self
    let mask = ICDeviceTypeMask(rawValue: ICDeviceTypeMask.scanner.rawValue |
    ICDeviceLocationTypeMask.local.rawValue |
    ICDeviceLocationTypeMask.shared.rawValue |
    ICDeviceLocationTypeMask.bonjour.rawValue |
    ICDeviceLocationTypeMask.bluetooth.rawValue |
    ICDeviceLocationTypeMask.remote.rawValue)
    mDeviceBrowser.browsedDeviceTypeMask = mask!
    mDeviceBrowser.start()
}

func applicationWillTerminate(_ notification: Notification) {
    mDeviceBrowser.delegate = nil
    mDeviceBrowser.stop()
}

func deviceBrowser(_ browser: ICDeviceBrowser, didAdd device: ICDevice, moreComing: Bool) {
    if device.type.rawValue & ICDeviceType.scanner.rawValue != 0 {
        device.delegate = self
        mscanners.append(device)
        
    }
}

func deviceBrowser(_ browser: ICDeviceBrowser, didRemove device: ICDevice, moreGoing: Bool) {
    device.delegate = nil
    self.willChangeValue(for: \.scanners)
    if let index = mscanners.firstIndex(of: device) {
        mscanners.remove(at: index)
    }
    self.didChangeValue(for: \.scanners)
}

func didRemove(_ removedDevice: ICDevice) {
    mscannersController.removeObject(removedDevice)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "selectedObjects" && object as AnyObject === mMediaFilesController {
        self.willChangeValue(for: \.canDownload)
        self.didChangeValue(for: \.canDownload)
    }
}

func scannerDevice(_ scanner: ICScannerDevice, didCompleteScanWithError error: Error?) {
        if let error = error {
            print("Error scanning: \(error.localizedDescription)")
        } else {
            print("Scan successful")
        }
        
    }
func scannerDevice(_ scanner: ICScannerDevice, didScanTo url: URL) {
    do {
        let imageData = try Data(contentsOf: url)
        let scannedImage = NSImage(data: imageData)
        
        scannedImages.append(scannedImage!)
        print("appended the image \(imageData.description)")
    } catch {
        print("Error loading scanned image: \(error.localizedDescription)")
    }
}


func requestScan() {
    guard let scannerDevice = mscanners.first as? ICScannerDevice else {
        print("Scanner device not found.")
        return
    }

    scannerDevice.delegate = self

    scannerDevice.requestOpenSession { error in
        if error != nil {
            print("Session already open")
            scannerDevice.requestScan()
        } else {
            print("Scanner session opened successfully")
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                            scannerDevice.requestScan()
                print(scannerDevice.downloadsDirectory.path())
                        }
        }
    }
}

}

Sign up to request clarification or add additional context in comments.

1 Comment

Yes, my bad. Not sure why I typed that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.