17

Over the last few weeks I've been writing a mac application in swift, both to get into mac programming and to practice swift for when we migrate over to it at my workplace. I'm currently trying to get some code working to add my application as a "Launch on Startup" application by tailoring code kindly provided over on BDungan's blog

So far after messing about for many hours I have come up with the following:

func itemRefInLoginItems () -> LSSharedFileListItemRef?
{
    var itemRef: LSSharedFileListItemRef? = nil
    var itemURL: Unmanaged<CFURLRef>?

    let appURL = NSURL.fileURLWithPath(NSBundle.mainBundle().bundlePath)


    if let loginItemsRef = LSSharedFileListCreate(kCFAllocatorDefault,kLSSharedFileListSessionLoginItems.takeRetainedValue(),NSMutableDictionary()) {

        var unretainedLoginItemsRef = loginItemsRef.takeUnretainedValue()

        if var loginItems = LSSharedFileListCopySnapshot(unretainedLoginItemsRef, nil) {

            for item in (loginItems.takeRetainedValue() as NSArray) {

                let currentItemRef = item as LSSharedFileListItemRef

                var outRef: FSRef
                if (LSSharedFileListItemResolve(currentItemRef, 0, &itemURL, nil) == noErr) {

                    if (appURL?.isEqual(itemURL?.takeRetainedValue()) != nil) { //PROBLEM 1

                        itemRef = currentItemRef
                    }
                }
            }
        }
    }

    return itemRef
}

func isLaunchAtStartup () -> Bool {

    let itemRef = self.itemRefInLoginItems()
    return itemRef != nil
}

func makeLaunchAtStartup () { // Compile seems to fall down on this line...

    if !self.isLaunchAtStartup() {

        let loginItemsRef = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems.takeRetainedValue(), NSMutableDictionary())

        let appURL = NSURL(fileURLWithPath: NSBundle.mainBundle().bundlePath) as CFURLRef
        let itemRef = LSSharedFileListInsertItemURL(loginItemsRef.takeRetainedValue(), kLSSharedFileListItemLast.takeRetainedValue(), nil, nil, appURL, nil, nil)
    }
}

I am running into two problems however.

Problem 1

Swift does not want me to compare an NSURL to a CFURLRef... for now I went with an Xcode suggestion just to get the app running, but I'm 100% sure it isn't doing what I think it is. (see //PROBLEM 1).

In objective-c it seems toll free bridging was allowed between NSURL and CFURLRef (or CFURL), however on attempt to cast, conditional cast or any variety of swift as(Insert correct character here) approaches my code inevitably wouldn't build. I get errors such as:

Unmanaged is not a subtype of NSURL: if appURL as Unmanaged<CFURLRef> == itemURL

e.t.c.

Problem 2

Although this code currently gives no warnings or errors... when trying to compile I get a Command failed due to signal: Segmentation fault: 11, which to be frank... is beyond me.

2 Answers 2

12

I've managed to get a working implementation of this built based on Brian Dunagan's Objective C approach. I also ran into your compiler seg fault problem but this was being caused by attempting casts to invalid types; getting the types right fixes the issue.

I could not get kLSSharedFileListItemLast to properly return the last file item reference as it would always cause a seg fault. To get around this problem I modified the itemReferencesInLoginItems function to return a tuple of item references.

The first item in the tuple is the existing application reference if it exists, the second item is the last reference of the list. Using this approach we can avoid having to rely on kLSSharedFileListItemLast.

Here is the code, feel free to use it! I'd love to know if there's a way to get kLSSharedFileListItemLast to work.

func applicationIsInStartUpItems() -> Bool {
    return (itemReferencesInLoginItems().existingReference != nil)
}

func itemReferencesInLoginItems() -> (existingReference: LSSharedFileListItemRef?, lastReference: LSSharedFileListItemRef?) {
    var itemUrl : UnsafeMutablePointer<Unmanaged<CFURL>?> = UnsafeMutablePointer<Unmanaged<CFURL>?>.alloc(1)
    if let appUrl : NSURL = NSURL.fileURLWithPath(NSBundle.mainBundle().bundlePath) {
        let loginItemsRef = LSSharedFileListCreate(
            nil,
            kLSSharedFileListSessionLoginItems.takeRetainedValue(),
            nil
        ).takeRetainedValue() as LSSharedFileListRef?
        if loginItemsRef != nil {
            let loginItems: NSArray = LSSharedFileListCopySnapshot(loginItemsRef, nil).takeRetainedValue() as NSArray
            println("There are \(loginItems.count) login items")
            let lastItemRef: LSSharedFileListItemRef = loginItems.lastObject as LSSharedFileListItemRef
            for var i = 0; i < loginItems.count; ++i {
                let currentItemRef: LSSharedFileListItemRef = loginItems.objectAtIndex(i) as LSSharedFileListItemRef
                if LSSharedFileListItemResolve(currentItemRef, 0, itemUrl, nil) == noErr {
                    if let urlRef: NSURL =  itemUrl.memory?.takeRetainedValue() {
                        println("URL Ref: \(urlRef.lastPathComponent)")
                        if urlRef.isEqual(appUrl) {
                            return (currentItemRef, lastItemRef)
                        }
                    }
                } else {
                    println("Unknown login application")
                }
            }
            //The application was not found in the startup list
            return (nil, lastItemRef)
        }
    }
    return (nil, nil)
}

func toggleLaunchAtStartup() {
    let itemReferences = itemReferencesInLoginItems()
    let shouldBeToggled = (itemReferences.existingReference == nil)
    let loginItemsRef = LSSharedFileListCreate(
        nil,
        kLSSharedFileListSessionLoginItems.takeRetainedValue(),
        nil
        ).takeRetainedValue() as LSSharedFileListRef?
    if loginItemsRef != nil {
        if shouldBeToggled {
            if let appUrl : CFURLRef = NSURL.fileURLWithPath(NSBundle.mainBundle().bundlePath) {
                LSSharedFileListInsertItemURL(
                    loginItemsRef,
                    itemReferences.lastReference,
                    nil,
                    nil,
                    appUrl,
                    nil,
                    nil
                )
                println("Application was added to login items")
            }
        } else {
            if let itemRef = itemReferences.existingReference {
                LSSharedFileListItemRemove(loginItemsRef,itemRef);
                println("Application was removed from login items")
            }
        }
    }
}
1
  • 1
    What after sandbox Enabled ?
    – ak2g
    Feb 27, 2016 at 6:31
5

amazing answer but you forgot an if statement to check prevent an error from unwraping an empty value if no items exist

Here is a quick solution for the one function, hope it helps someone

func itemReferencesInLoginItems() -> (existingReference: LSSharedFileListItemRef?, lastReference: LSSharedFileListItemRef?) {
    var itemUrl : UnsafeMutablePointer<Unmanaged<CFURL>?> = UnsafeMutablePointer<Unmanaged<CFURL>?>.alloc(1)
    if let appUrl : NSURL = NSURL.fileURLWithPath(NSBundle.mainBundle().bundlePath) {
        let loginItemsRef = LSSharedFileListCreate(
            nil,
            kLSSharedFileListSessionLoginItems.takeRetainedValue(),
            nil
            ).takeRetainedValue() as LSSharedFileListRef?
        if loginItemsRef != nil {
            let loginItems: NSArray = LSSharedFileListCopySnapshot(loginItemsRef, nil).takeRetainedValue() as NSArray
            println("There are \(loginItems.count) login items")
            if(loginItems.count > 0)
            {
            let lastItemRef: LSSharedFileListItemRef = loginItems.lastObject as LSSharedFileListItemRef
            for var i = 0; i < loginItems.count; ++i {
                let currentItemRef: LSSharedFileListItemRef = loginItems.objectAtIndex(i) as LSSharedFileListItemRef
                if LSSharedFileListItemResolve(currentItemRef, 0, itemUrl, nil) == noErr {
                    if let urlRef: NSURL =  itemUrl.memory?.takeRetainedValue() {
                        println("URL Ref: \(urlRef.lastPathComponent)")
                        if urlRef.isEqual(appUrl) {
                            return (currentItemRef, lastItemRef)
                        }
                    }
                }
                else {
                    println("Unknown login application")
                }
            }
            //The application was not found in the startup list
            return (nil, lastItemRef)
            }
            else
            {
                let addatstart: LSSharedFileListItemRef = kLSSharedFileListItemBeforeFirst.takeRetainedValue()

                return(nil,addatstart)
            }
        }
    }
    return (nil, nil)
}

Note just added in the if statement after

let loginItems: NSArray = LSSharedFileListCopySnapshot(loginItemsRef, nil).takeRetainedValue() as NSArray

the function was to long for a comment and i thought this would be more helpful than a few lines of a comment

Note: Also had to change the return value for empty lists

else
{
    let addatstart: LSSharedFileListItemRef = kLSSharedFileListItemBeforeFirst.takeRetainedValue()
    return(nil,addatstart)
}
3
  • 4
    Note that in OS X 10.10, LSSharedFileListItemResolve is deprecated. I had to directly compare LSSharedFileListItemCopyResolvedURL(currentItemRef, 0, nil).takeRetainedValue() to currentItemUrl to get this working (after following XCode's other suggestions. May 16, 2015 at 15:09
  • @JacobEvelyn Could you show an exact example? currentItemUrl is not part of the original code and I fail to figure out what actually needs to be compared.
    – udondan
    Oct 28, 2015 at 3:58
  • 2
    Looks like my code was let currentItemUrl = LSSharedFileListItemCopyResolvedURL(currentItemRef, 0, nil).takeRetainedValue() but I believe this gave me errors in some instances. My hacky workaround: github.com/ComputeForHumanity/compute-for-humanity-app/commit/… Nov 3, 2015 at 0:23

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.