How to pass `self` to a function that takes `UnsafeMutableRawPointer?` in Swift? - swift

I've bought a 3D 4K projector last year, and it uses DLP-Link technology, which requires an 120fps Left/Right alternating video stream for stereoscopy.
I'm writing a player in Swift for Mac using Core Video, and a function require me to pass an UnsafeMutableRawPointer? (void * in Obj-C) to it, and I'm passing the view instance to it using the self keyword.
Here's my code:
//
// MovieView.swift
// Studio Media Player
//
// Created by DannyNiu on 2022-07-03.
//
import Foundation
import Cocoa
import AVFoundation
import CoreVideo
class MovieView : NSView
{
var asset: AVAsset?
var player: AVPlayer?
var item: AVPlayerItem?
var vout: AVPlayerItemVideoOutput?
var cvpb: CVPixelBuffer?
var cii: CIImage?
var cgi: CGImage?
var irect: CGRect?
var vlink: CVDisplayLink?
func setup_displaylink() -> Bool
{
var cvret: CVReturn
cvret = CVDisplayLinkCreateWithActiveCGDisplays(&vlink)
if( vlink == nil ) { return false }
var me: MovieView = self
cvret = CVDisplayLinkSetOutputCallback(
vlink!, vlink_callback, &me)
if( cvret == kCVReturnSuccess ) {
return true
} else { return false }
}
func assign_asset(_ asset: AVAsset)
{
self.asset = asset
item = .init(asset: asset)
player = .init(playerItem: item)
vout = .init()
cvpb = nil
cii = nil
}
func video_render(_ d: CVTimeStamp)
{
let t: CMTime = player!.currentTime()
let cgc: CGContext = (NSGraphicsContext.current?.cgContext)!
let cic: CIContext = (NSGraphicsContext.current?.ciContext)!
if( vout?.hasNewPixelBuffer(forItemTime: t) ?? false )
{
cvpb = vout?.copyPixelBuffer(
forItemTime: t, itemTimeForDisplay: nil)
cii = .init(cvPixelBuffer: cvpb!)
irect = cii!.extent
cgi = cic.createCGImage(cii!, from: irect!)
}
if( irect == nil ) { return }
let orect: CGRect = NSRectToCGRect(bounds)
var vrect: CGRect =
CGRect(origin: orect.origin,
size: CGSize(width: orect.width * 2,
height: irect!.height *
orect.width /
irect!.width))
cgc.setFillColor(gray:0, alpha:1)
cgc.fill(orect)
if( d.videoTime % 2 == 1 )
{
vrect = vrect.offsetBy(dx: -orect.width, dy: 0)
}
cgc.draw(cgi!, in: vrect)
}
}
func vlink_callback(
displayLink: CVDisplayLink,
inNow: UnsafePointer<CVTimeStamp>,
inOutputTime: UnsafePointer<CVTimeStamp>,
flagsIn: CVOptionFlags,
flagsOut: UnsafeMutablePointer<CVOptionFlags>,
arg_mvview: UnsafeMutableRawPointer?
) -> CVReturn
{
let mvview: MovieView = arg_mvview!.load(as: MovieView.self)
mvview.video_render(inNow.pointee)
return kCVReturnSuccess
}
When I debug the program, the mvview.video_render(inNow.pointee) line caused an EXC_BAD_ACCESS trap, with code = 1. I assume this is caused by me not correctly passing self to the display link call-back. So how can I fix this?

Use the Unmanaged class.
in setup_displaylink():
func setup_displaylink() -> Bool
{
var cvret: CVReturn
cvret = CVDisplayLinkCreateWithActiveCGDisplays(&vlink)
if( vlink == nil ) { return false }
var me: UnsafeMutableRawPointer =
Unmanaged.passUnretained(self).toOpaque()
cvret = CVDisplayLinkSetOutputCallback(
vlink!, vlink_callback, &me)
if( cvret == kCVReturnSuccess ) {
return true
} else { return false }
}
and in vlink_callback
func vlink_callback(
displayLink: CVDisplayLink,
inNow: UnsafePointer<CVTimeStamp>,
inOutputTime: UnsafePointer<CVTimeStamp>,
flagsIn: CVOptionFlags,
flagsOut: UnsafeMutablePointer<CVOptionFlags>,
arg_mvview: UnsafeMutableRawPointer?
) -> CVReturn
{
let mvview: MovieView =
Unmanaged.fromOpaque(arg_mvview!).takeUnretainedValue()
mvview.video_render(inNow.pointee)
return kCVReturnSuccess
}
Notice I used the "Unretained" function, because there's no need to add to the reference count of the movie view, as self is being passed around internally. Use "Retained" functions when otherwise appropriate.

Related

Get callback when ADMOB reward ad is closed without seeing whole ad in ios swift

I am using reward admob ad in my project with latest sdk. How can i get proper callback that the user has closed the ad in between. I know there is a delegate method of fullscreencontentdelegate which has a function adDidDismiss but in that function i am doing some code block which i perform when i complete watching the ad and it just works fine but what if i closed the ad in between, because what happens is that whether i see the whole ad or not this delegate function gets called and there is no way to differentiate how would i proceed with complete and incomplete ad. Please help me with this.
video link
as in the video first time i am not watching the whole ad and i just close it then also the scratch card popup comes because of the delegate method being called, which i dont want to open and then i just watch the whole ad and get my reward which is working fine
My code snippet:
enum RewardAdType {
case avatar, freeChips, scratchCard, chips250
}
typealias AD_COMPLETION_BLOCK = (_ success: Bool) -> ()
class RewardAdManager : NSObject
{
//MARK: - PROPERTIES
var rewardBasedVideoAd : GADRewardedAd? = nil
var rewardValue = ""
var type: RewardAdType? = nil
}
//MARK: - HELPERS
extension RewardAdManager
{
func loadRewardedAd(vc: UIViewController, userId: String, type: RewardAdType, imageName: String? = nil, chipsCoin: String? = nil, completion: #escaping AD_COMPLETION_BLOCK)
{
self.type = type
let adUnit = self.type == .avatar ? Constants.REWARD_AD_AVATAR_LIVE_ID : Constants.REWARD_AD_WINCHIPS_LIVE_ID
let request = GADRequest()
GADRewardedAd.load(withAdUnitID: adUnit, request: request) { [weak self] ad, error in
guard let self = self, error == nil else {
Helpers.hidehud()
self?.type = nil
self?.rewardBasedVideoAd = nil
return
}
let serverSideVerificationOptions = GADServerSideVerificationOptions()
serverSideVerificationOptions.userIdentifier = userId
if type == .scratchCard {
self.rewardValue = self.generateRandomRewardValue()
serverSideVerificationOptions.customRewardString = self.rewardValue
} else if type == .avatar {
serverSideVerificationOptions.customRewardString = imageName
} else if type == .freeChips {
serverSideVerificationOptions.customRewardString = chipsCoin
} else if type == .chips250 {
serverSideVerificationOptions.customRewardString = "250"
}
self.rewardBasedVideoAd = ad
self.rewardBasedVideoAd?.serverSideVerificationOptions = serverSideVerificationOptions
self.rewardBasedVideoAd?.fullScreenContentDelegate = self
self.showRewardedAd(viewController: vc, type: type, completion: completion)
}
}
func showRewardedAd(viewController: UIViewController, type: RewardAdType? = nil, completion: #escaping AD_COMPLETION_BLOCK)
{
Helpers.hidehud()
if let ad = self.rewardBasedVideoAd {
self.type = type
DispatchQueueHelper.delay {
ad.present(fromRootViewController: viewController) {}
}
completion(true)
} else {
self.type = nil
self.checkForSavedLanguage(viewController: viewController)
}
}
func checkForSavedLanguage(viewController: UIViewController)
{
let lang = LanguageCode(rawValue: Defaults[.LangCode]) ?? .english
viewController.showToast(msg: Constants.NO_ADS_MESSAGE.localizeString(string: lang))
}
func generateRandomRewardValue() -> String
{
var val = 0
let random = Double.random(in: 0.1...1.0)
if random < 0.20 {
val = 150
} else if random < 0.50 {
val = 200
} else if random < 0.70 {
val = 250
} else {
val = 350
}
return val.toString()
}
}
//MARK: - GADFullScreenContentDelegate
extension RewardAdManager : GADFullScreenContentDelegate {
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error)
{
self.type = nil
Helpers.hidehud()
let lang = LanguageCode(rawValue: Defaults[.LangCode]) ?? .english
let userInfo = ["msg":Constants.NO_ADS_MESSAGE.localizeString(string: lang)]
NotificationCaller.shared.showLeaveMsg(userInfo: userInfo)
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd)
{
guard let type = self.type else { return }
self.rewardBasedVideoAd = nil
let userInfo: [String:RewardAdType] = ["type":type]
NotificationCaller.shared.showRewardTypePopup(userInfo: userInfo)
}
}

Get Proper callback when admob reward ad is closed without seeing whole ad in ios swift

i am using reward admob ad in my project with latest sdk. how can i get proper callback that the user has closed the ad in between. I know there is a delegate method of fullscreencontent delegate which will be called but in that delegate method i am calling some code block which i perform when i complete watching the ad and it justs work fine but what if i closed the ad in between then the same delegate is being called. so how do i decide that the user has watched the ad or closed in between.
my code snippet:
enum RewardAdType {
case avatar, freeChips, scratchCard, chips250
}
typealias AD_COMPLETION_BLOCK = (_ success: Bool) -> ()
class RewardAdManager : NSObject
{
//MARK: - PROPERTIES
var rewardBasedVideoAd : GADRewardedAd? = nil
var rewardValue = ""
var type: RewardAdType? = nil
}
//MARK: - HELPERS
extension RewardAdManager
{
func loadRewardedAd(vc: UIViewController, userId: String, type: RewardAdType, imageName: String? = nil, chipsCoin: String? = nil, completion: #escaping AD_COMPLETION_BLOCK)
{
self.type = type
let adUnit = self.type == .avatar ? Constants.REWARD_AD_AVATAR_LIVE_ID : Constants.REWARD_AD_WINCHIPS_LIVE_ID
let request = GADRequest()
GADRewardedAd.load(withAdUnitID: adUnit, request: request) { [weak self] ad, error in
guard let self = self, error == nil else {
Helpers.hidehud()
self?.type = nil
self?.rewardBasedVideoAd = nil
return
}
let serverSideVerificationOptions = GADServerSideVerificationOptions()
serverSideVerificationOptions.userIdentifier = userId
if type == .scratchCard {
self.rewardValue = self.generateRandomRewardValue()
serverSideVerificationOptions.customRewardString = self.rewardValue
} else if type == .avatar {
serverSideVerificationOptions.customRewardString = imageName
} else if type == .freeChips {
serverSideVerificationOptions.customRewardString = chipsCoin
} else if type == .chips250 {
serverSideVerificationOptions.customRewardString = "250"
}
self.rewardBasedVideoAd = ad
self.rewardBasedVideoAd?.serverSideVerificationOptions = serverSideVerificationOptions
self.rewardBasedVideoAd?.fullScreenContentDelegate = self
self.showRewardedAd(viewController: vc, type: type, completion: completion)
}
}
func showRewardedAd(viewController: UIViewController, type: RewardAdType? = nil, completion: #escaping AD_COMPLETION_BLOCK)
{
Helpers.hidehud()
if let ad = self.rewardBasedVideoAd {
self.type = type
DispatchQueueHelper.delay {
ad.present(fromRootViewController: viewController) {}
}
completion(true)
} else {
self.type = nil
self.checkForSavedLanguage(viewController: viewController)
}
}
func checkForSavedLanguage(viewController: UIViewController)
{
let lang = LanguageCode(rawValue: Defaults[.LangCode]) ?? .english
viewController.showToast(msg: Constants.NO_ADS_MESSAGE.localizeString(string: lang))
}
func generateRandomRewardValue() -> String
{
var val = 0
let random = Double.random(in: 0.1...1.0)
if random < 0.20 {
val = 150
} else if random < 0.50 {
val = 200
} else if random < 0.70 {
val = 250
} else {
val = 350
}
return val.toString()
}
}
//MARK: - GADFullScreenContentDelegate
extension RewardAdManager : GADFullScreenContentDelegate {
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error)
{
self.type = nil
Helpers.hidehud()
let lang = LanguageCode(rawValue: Defaults[.LangCode]) ?? .english
let userInfo = ["msg":Constants.NO_ADS_MESSAGE.localizeString(string: lang)]
NotificationCaller.shared.showLeaveMsg(userInfo: userInfo)
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd)
{
guard let type = self.type else { return }
self.rewardBasedVideoAd = nil
let userInfo: [String:RewardAdType] = ["type":type]
NotificationCaller.shared.showRewardTypePopup(userInfo: userInfo)
}
}

CANT RESOLVE: unsafeAddressOf is abandoned in Swift 3

I just realized that my old app is not working anymore because unsafeAddressOf is abandoned in Swift 3. I have been searching in Apple documentations and online tutorials but still cant figure out how to change my code to be compliant with Swift 3. Here is my code:
import UIKit
import ImageIO
extension UIImage {
public class func gifWithData(data: NSData) -> UIImage? {
guard let source = CGImageSourceCreateWithData(data, nil) else {
print("SwiftGif: Source for the image does not exist")
return nil
}
return UIImage.animatedImageWithSource(source: source)
}
public class func gifWithName(name: String) -> UIImage? {
guard let bundleURL = Bundle.main.url(forResource: name, withExtension: "gif") else {
print("SwiftGif: This image named \"\(name)\" does not exist")
return nil
}
guard let imageData = NSData(contentsOfURL: bundleURL) else {
print("SwiftGif: Cannot turn image named \"\(name)\" into NSData")
return nil
}
return gifWithData(imageData)
}
class func delayForImageAtIndex(index: Int, source: CGImageSource!) -> Double {
var delay = 0.1
// Get dictionaries
let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)
let gifProperties: CFDictionary = unsafeBitCast(CFDictionaryGetValue(cfProperties, unsafeAddressOf(kCGImagePropertyGIFDictionary)), to: CFDictionary.self)
// Get delay time
var delayObject: AnyObject = unsafeBitCast(
CFDictionaryGetValue(gifProperties,
unsafeAddressOf(kCGImagePropertyGIFUnclampedDelayTime)),
to: AnyObject.self)
if delayObject.doubleValue == 0 {
delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties,
unsafeAddressOf(kCGImagePropertyGIFDelayTime)), to: AnyObject.self)
}
delay = delayObject as! Double
if delay < 0.1 {
delay = 0.1 // Make sure they're not too fast
}
return delay
}
class func gcdForPair( a: Int?, var _ b: Int?) -> Int {
// Check if one of them is nil
var a = a
if b == nil || a == nil {
if b != nil {
return b!
} else if a != nil {
return a!
} else {
return 0
}
}
// Swap for modulo
if a < b {
let c = a
a = b
b = c
}
// Get greatest common divisor
var rest: Int
while true {
rest = a! % b!
if rest == 0 {
return b! // Found it
} else {
a = b
b = rest
}
}
}
class func gcdForArray(array: Array<Int>) -> Int {
if array.isEmpty {
return 1
}
var gcd = array[0]
for val in array {
gcd = UIImage.gcdForPair(val, gcd)
}
return gcd
}
class func animatedImageWithSource(source: CGImageSource) -> UIImage? {
let count = CGImageSourceGetCount(source)
var images = [CGImage]()
var delays = [Int]()
// Fill arrays
for i in 0..<count {
// Add image
if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {
images.append(image)
}
// At it's delay in cs
let delaySeconds = UIImage.delayForImageAtIndex(index: Int(i),
source: source)
delays.append(Int(delaySeconds * 1000.0)) // Seconds to ms
}
// Calculate full duration
let duration: Int = {
var sum = 0
for val: Int in delays {
sum += val
}
return sum
}()
// Get frames
let gcd = gcdForArray(array: delays)
var frames = [UIImage]()
var frame: UIImage
var frameCount: Int
for i in 0..<count {
frame = UIImage(CGImage: images[Int(i)])
frameCount = Int(delays[Int(i)] / gcd)
for _ in 0..<frameCount {
frames.append(frame)
}
}
// Heyhey
let animation = UIImage.animatedImage(with: frames,
duration: Double(duration) / 1000.0)
return animation
}
}
Does anyone have an idea how I can fix this code?

AudioQueueStart But no voice

import Foundation
import AudioToolbox
class AudioPlay {
//setting buffer num
static let knumberBuffers = 3
var aqData = AQPlayerState.init()
//A custom structure for a playback audio queue
class AQPlayerState {
var mDataFormat = AudioStreamBasicDescription()
var mQueue:AudioQueueRef?
var mBuffers = [AudioQueueBufferRef?].init(repeating: nil, count: AudioPlay.knumberBuffers)
var mAudioFile:AudioFileID?
var bufferByteSize = UInt32()
var mCurrentPacket:Int64?
var mNumPacketsToRead = UInt32()
var mPacketDescs:UnsafeMutablePointer<AudioStreamPacketDescription>?
var mIsRunning = false
}
//playbackAudioQueue callback
static let HandleOutputBuffer:AudioQueueOutputCallback = { (aqData1, inAQ, inBuffer) in
var pAqData = (aqData1?.assumingMemoryBound(to: AQPlayerState.self).pointee)!
guard pAqData.mIsRunning || pAqData.mQueue != nil else{
print("audioplay is not running exit callback func")
return
}
var numBytesReadFromFile = UInt32()
var numPackets = pAqData.mNumPacketsToRead
AudioFileReadPacketData(pAqData.mAudioFile!, false, &numBytesReadFromFile, pAqData.mPacketDescs, pAqData.mCurrentPacket!, &numPackets, inBuffer.pointee.mAudioData)
if numPackets > 0 {
inBuffer.pointee.mAudioDataByteSize = numBytesReadFromFile
AudioQueueEnqueueBuffer(pAqData.mQueue!, inBuffer, ((pAqData.mPacketDescs != nil) ? numPackets : UInt32(0)), pAqData.mPacketDescs)
pAqData.mCurrentPacket! += Int64(numPackets)
}else{
AudioQueueStop(pAqData.mQueue!, false)
pAqData.mIsRunning = false
}
}
//call func to set the property
//create new outputqueue
//start th audioqueue
func start() {
let url = Bundle.main.url(forResource: "123", withExtension: "mp3")!
let audioFileURL = url as CFURL
print(audioFileURL)
let result = AudioFileOpenURL(audioFileURL, .readPermission, 0, &aqData.mAudioFile)
print(result)
var dataFormatSize = UInt32(MemoryLayout.size(ofValue: aqData.mDataFormat))
let result1 = AudioFileGetProperty(aqData.mAudioFile!, kAudioFilePropertyDataFormat,&dataFormatSize, &aqData.mDataFormat)
//get file property
var maxPacketSize = UInt32()
var propertySize = UInt32(MemoryLayout.size(ofValue: maxPacketSize))
let result2 = AudioFileGetProperty(aqData.mAudioFile!, kAudioFilePropertyPacketSizeUpperBound, &propertySize, &maxPacketSize)
//calculate and setting buffer size
DeriveBufferSize(ASBDesc: aqData.mDataFormat, maxPacketSize: maxPacketSize, seconds: 0.5, outBufferSize: &aqData.bufferByteSize, outNumPacketsToRead: &aqData.mNumPacketsToRead)
//check the format is VBR or CBR
let isFormatVBR = aqData.mDataFormat.mBytesPerPacket == 0 || aqData.mDataFormat.mFramesPerPacket == 0
if isFormatVBR {
aqData.mPacketDescs = UnsafeMutablePointer<AudioStreamPacketDescription>.allocate(capacity: MemoryLayout.size(ofValue: AudioStreamPacketDescription()))
}else{
aqData.mPacketDescs = nil
}
//create new audio queue
let result4 = AudioQueueNewOutput(&aqData.mDataFormat,AudioPlay.HandleOutputBuffer, &aqData,CFRunLoopGetCurrent(),CFRunLoopMode.commonModes.rawValue, 0, &aqData.mQueue)
//queue start
aqData.mIsRunning = true
//alloc memory buffer
aqData.mCurrentPacket = 0
for i in 0..<AudioPlay.knumberBuffers {
AudioQueueAllocateBuffer(aqData.mQueue!, aqData.bufferByteSize,&aqData.mBuffers[i])
AudioPlay.HandleOutputBuffer(&aqData,aqData.mQueue!, (aqData.mBuffers[i])!)
}
//start audioqueue
AudioQueueStart(aqData.mQueue!, nil)
repeat{
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 0.25, false)
}while (aqData.mIsRunning)
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 1, false)
}
//calculate and setting buffer size
func DeriveBufferSize(ASBDesc:AudioStreamBasicDescription,maxPacketSize:UInt32,seconds:Float64,outBufferSize:UnsafeMutablePointer<UInt32>,outNumPacketsToRead:UnsafeMutablePointer<UInt32>) {
let maxBufferSize:UInt32 = 0x50000
let minBufferSIze:UInt32 = 0x4000
if ASBDesc.mFramesPerPacket != 0 {
let numPacketsForTime = ASBDesc.mSampleRate / Float64(ASBDesc.mFramesPerPacket) * seconds
outBufferSize.pointee = UInt32(numPacketsForTime) * maxPacketSize
}else{
outBufferSize.pointee = (maxBufferSize > maxPacketSize) ? maxBufferSize:maxPacketSize
}
if outBufferSize.pointee > maxBufferSize && outBufferSize.pointee > maxPacketSize {
outBufferSize.pointee = maxBufferSize
}else{
if outBufferSize.pointee < minBufferSIze{
outBufferSize.pointee = minBufferSIze
}
}
outNumPacketsToRead.pointee = outBufferSize.pointee/maxPacketSize
}
//dispose the audioqueue
func Dispose() {
AudioQueueDispose(aqData.mQueue!, true)
AudioFileClose(aqData.mAudioFile!)
free(aqData.mPacketDescs)
}
}
above code is writed according AudioQueueServiceProgrammingGuide!
create an instence of this class,and call the start() func
compile ok,but no output voice.
I had check the code many times but no advancing
can any one who familiar with audiiqueue help me?
Any help will be appreciated.
replace "AudioFileReadPacketData" with "AudioFileReadPackets" can fix this problem!
but I get a new problem like below sometime! sometimes it works well!
Stream Audio(20535,0x1085ac3c0) malloc: * error for object 0x6080001f6300: Invalid pointer dequeued from free list
* set a breakpoint in malloc_error_break to

How to implement IOServiceMatchingCallBack in Swift

I would like to detect a specific USB is plugged in/removed in my application. For now, I can get the deviceName with this tutorial Working With USB Device Interfaces. But, how can I do the callback function of (deviceAdded)IOServiceMatchingCallBack in Swift.
I tried as follows, but I got an error: Cannot convert value of type '(UnsafePointer, iterator: io_iterator_t) -> ()' to expected argument type 'IOServiceMatchingCallback!'
func detectUSBEvent() {
var portIterator: io_iterator_t = 0
var kr: kern_return_t = KERN_FAILURE
let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
let vendorIDString = kUSBVendorID as CFStringRef!
let productIDString = kUSBProductID as CFStringRef!
CFDictionarySetValue(matchingDict, unsafeAddressOf(vendorIDString), unsafeAddressOf(VendorID))
CFDictionarySetValue(matchingDict, unsafeAddressOf(productIDString), unsafeAddressOf(ProductID))
// To set up asynchronous notifications, create a notification port and add its run loop event source to the program’s run loop
let gNotifyPort: IONotificationPortRef = IONotificationPortCreate(kIOMasterPortDefault)
let runLoopSource: Unmanaged<CFRunLoopSource>! = IONotificationPortGetRunLoopSource(gNotifyPort)
let gRunLoop: CFRunLoop! = CFRunLoopGetCurrent()
CFRunLoopAddSource(gRunLoop, runLoopSource.takeUnretainedValue(), kCFRunLoopDefaultMode)
// Notification of first match:
kr = IOServiceAddMatchingNotification(gNotifyPort, kIOFirstMatchNotification, matchingDict, deviceAdded, nil, &portIterator)
deviceAdded(nil, iterator: portIterator)
}
func deviceAdded(refCon: UnsafePointer<Void>, iterator: io_iterator_t) {
if let usbDevice: io_service_t = IOIteratorNext(iterator)
{
let name = String()
let cs = (name as NSString).UTF8String
let deviceName: UnsafeMutablePointer<Int8> = UnsafeMutablePointer<Int8>(cs)
let kr: kern_return_t = IORegistryEntryGetName(usbDevice, deviceName)
if kr == KERN_SUCCESS {
let deviceNameAsCFString = CFStringCreateWithCString(kCFAllocatorDefault, deviceName,
kCFStringEncodingASCII)
print(deviceNameAsCFString)
// if deviceNameAsCFString == XXX
// Do Something
}
}
}
Here's a Swift 3 version, using closures instead of global functions (a closure w/o a context can be bridged to a C function pointer), using GCD instead of Runloops (much nicer API), using callbacks and dispatches to inform about events and using real objects instead of static objects or singletons:
import Darwin
import IOKit
import IOKit.usb
import Foundation
class IOUSBDetector {
enum Event {
case Matched
case Terminated
}
let vendorID: Int
let productID: Int
var callbackQueue: DispatchQueue?
var callback: (
( _ detector: IOUSBDetector, _ event: Event,
_ service: io_service_t
) -> Void
)?
private
let internalQueue: DispatchQueue
private
let notifyPort: IONotificationPortRef
private
var matchedIterator: io_iterator_t = 0
private
var terminatedIterator: io_iterator_t = 0
private
func dispatchEvent (
event: Event, iterator: io_iterator_t
) {
repeat {
let nextService = IOIteratorNext(iterator)
guard nextService != 0 else { break }
if let cb = self.callback, let q = self.callbackQueue {
q.async {
cb(self, event, nextService)
IOObjectRelease(nextService)
}
} else {
IOObjectRelease(nextService)
}
} while (true)
}
init? ( vendorID: Int, productID: Int ) {
self.vendorID = vendorID
self.productID = productID
self.internalQueue = DispatchQueue(label: "IODetector")
guard let notifyPort = IONotificationPortCreate(kIOMasterPortDefault) else {
return nil
}
self.notifyPort = notifyPort
IONotificationPortSetDispatchQueue(notifyPort, self.internalQueue)
}
deinit {
self.stopDetection()
}
func startDetection ( ) -> Bool {
guard matchedIterator == 0 else { return true }
let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
as NSMutableDictionary
matchingDict[kUSBVendorID] = NSNumber(value: vendorID)
matchingDict[kUSBProductID] = NSNumber(value: productID)
let matchCallback: IOServiceMatchingCallback = {
(userData, iterator) in
let detector = Unmanaged<IOUSBDetector>
.fromOpaque(userData!).takeUnretainedValue()
detector.dispatchEvent(
event: .Matched, iterator: iterator
)
};
let termCallback: IOServiceMatchingCallback = {
(userData, iterator) in
let detector = Unmanaged<IOUSBDetector>
.fromOpaque(userData!).takeUnretainedValue()
detector.dispatchEvent(
event: .Terminated, iterator: iterator
)
};
let selfPtr = Unmanaged.passUnretained(self).toOpaque()
let addMatchError = IOServiceAddMatchingNotification(
self.notifyPort, kIOFirstMatchNotification,
matchingDict, matchCallback, selfPtr, &self.matchedIterator
)
let addTermError = IOServiceAddMatchingNotification(
self.notifyPort, kIOTerminatedNotification,
matchingDict, termCallback, selfPtr, &self.terminatedIterator
)
guard addMatchError == 0 && addTermError == 0 else {
if self.matchedIterator != 0 {
IOObjectRelease(self.matchedIterator)
self.matchedIterator = 0
}
if self.terminatedIterator != 0 {
IOObjectRelease(self.terminatedIterator)
self.terminatedIterator = 0
}
return false
}
// This is required even if nothing was found to "arm" the callback
self.dispatchEvent(event: .Matched, iterator: self.matchedIterator)
self.dispatchEvent(event: .Terminated, iterator: self.terminatedIterator)
return true
}
func stopDetection ( ) {
guard self.matchedIterator != 0 else { return }
IOObjectRelease(self.matchedIterator)
IOObjectRelease(self.terminatedIterator)
self.matchedIterator = 0
self.terminatedIterator = 0
}
}
And here is some simple test code to test that class (set product and vendor ID as appropriate for your USB device):
let test = IOUSBDetector(vendorID: 0x4e8, productID: 0x1a23)
test?.callbackQueue = DispatchQueue.global()
test?.callback = {
(detector, event, service) in
print("Event \(event)")
};
_ = test?.startDetection()
while true { sleep(1) }
It works after I put the callback function out the class. However, I don't know why.
class IODetection {
class func monitorUSBEvent(VendorID: Int, ProductID: Int) {
var portIterator: io_iterator_t = 0
var kr: kern_return_t = KERN_FAILURE
let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
// Add the VENDOR and PRODUCT IDs to the matching dictionary.
let vendorIDString = kUSBVendorID as CFStringRef!
let productIDString = kUSBProductID as CFStringRef!
CFDictionarySetValue(matchingDict, unsafeAddressOf(vendorIDString), unsafeAddressOf(VendorID))
CFDictionarySetValue(matchingDict, unsafeAddressOf(productIDString), unsafeAddressOf(ProductID))
// To set up asynchronous notifications, create a notification port and add its run loop event source to the program’s run loop
let gNotifyPort: IONotificationPortRef = IONotificationPortCreate(kIOMasterPortDefault)
let runLoopSource: Unmanaged<CFRunLoopSource>! = IONotificationPortGetRunLoopSource(gNotifyPort)
let gRunLoop: CFRunLoop! = CFRunLoopGetCurrent()
CFRunLoopAddSource(gRunLoop, runLoopSource.takeRetainedValue(), kCFRunLoopDefaultMode)
// MARK: - USB in Notification
let observer = UnsafeMutablePointer<Void>(unsafeAddressOf(self))
kr = IOServiceAddMatchingNotification(gNotifyPort,
kIOMatchedNotification,
matchingDict,
deviceAdded,
observer,
&portIterator)
deviceAdded(nil, iterator: portIterator)
// MARK: - USB remove Notification
kr = IOServiceAddMatchingNotification(gNotifyPort,
kIOTerminatedNotification,
matchingDict,
deviceRemoved,
observer,
&portIterator)
deviceRemoved(nil, iterator: portIterator)
}
}
func deviceAdded(refCon: UnsafeMutablePointer<Void>, iterator: io_iterator_t) -> Void {
var kr: kern_return_t = KERN_FAILURE
while case let usbDevice = IOIteratorNext(iterator) where usbDevice != 0 {
let deviceNameAsCFString = UnsafeMutablePointer<io_name_t>.alloc(1)
defer {deviceNameAsCFString.dealloc(1)}
kr = IORegistryEntryGetName(usbDevice, UnsafeMutablePointer(deviceNameAsCFString))
if kr != KERN_SUCCESS {
deviceNameAsCFString.memory.0 = 0
}
let deviceName = String.fromCString(UnsafePointer(deviceNameAsCFString))
print("Device Added: \(deviceName!)")
// Do something if I get the specific device
if deviceName == "YOUR DEVICE" {
/// Your Action HERE
}
IOObjectRelease(usbDevice)
}
}
My problem was that I wasn't using the iterator in my callback function, so that function wasn't even getting called! Seems like strange behaviour to me, but that was my problem.