Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift - getting a Mac App to launch on startup

Tags:

macos

swift

cocoa

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.

like image 566
simonthumper Avatar asked Oct 20 '14 21:10

simonthumper


People also ask

How do I get an application to open on startup Mac?

On your Mac, choose Apple menu > System Preferences, then click Users & Groups . Select your user account, then click Login Items at the top of the window. Do any of the following: Add a login item: Click the Add button below the list of items, select a document, folder, app, server, or other item, then click Add.

Can you make macOS apps with Swift?

Swift is the main programming language used to develop Apple apps. Once you've learned the basics of Swift, you'll be able to start creating your own iOS and macOS apps! It's a powerful and fast programming language.


2 Answers

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")
            }
        }
    }
}
like image 66
Eoin Nolan Avatar answered Oct 18 '22 09:10

Eoin Nolan


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)
}
like image 24
nsij22 Avatar answered Oct 18 '22 07:10

nsij22