从 macOS Swift 应用程序控制 USB 扫描仪

问题描述 投票:0回答:1

我正在尝试从 macOS swift 应用程序连接并启动 USB 扫描仪,以便捕获扫描的图像。我可以成功检测到 USB 设备并将其与预期的productID 和vendorID 进行匹配,但无法打开连接。其他应用程序(例如图像捕获)可以正常访问扫描仪。这是我的控制台输出:

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.

我的访问扫描仪的代码

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)
    }
    
}


我尝试访问 USB 设备并打开连接。我可以检测到正确的 USB 设备,但打开连接时总是出错。我已经检查了 Xcode 中的权利和权限以及用户的请求。

swift macos iokit
1个回答
0
投票

所以我没有意识到扫描仪与 ImageCoreController 兼容,所以这是我采用的实现。附加图像不起作用,但它会启动扫描。

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, element: UInt32, info: [String : Any]) {
    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())
                        }
        }
    }
}

}

© www.soinside.com 2019 - 2024. All rights reserved.