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.
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.
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.
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")
}
}
}
}
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With