我正在尝试从 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,
¬ificationIterator
)
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 中的权利和权限以及用户的请求。
所以我没有意识到扫描仪与 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())
}
}
}
}
}