Register as Login Item with Cocoa? - cocoa

Google gave me: http://developer.apple.com/samplecode/LoginItemsAE/index.html
And I figured there must be a better way than using AppleScript Events.
So I downloaded the Growl sources. They use the exact sources from that Apple developer article.
Is there a better way?
(I refer to Login Items in Accounts in System Preferences, ie. making my program start when the user Logs in, programmatically)

There's an API that's new in Leopard called LSSharedFileList. One of the things it lets you do is view and edit the Login Items list (called Session Login Items in that API).
BTW, I'm the lead developer of Growl. We haven't switched away from AE yet because we still require Tiger, but I'm thinking of dropping that for 1.2 (haven't talked it over with the other developers yet). When we do drop Tiger, we'll drop LoginItemsAE as well, and switch to the Shared File List API.
EDIT from the year 2012: Since 2009, when I originally wrote this answer, Growl has switched to LSSharedFileList and I've left the project.

I stumbled across Ben Clark-Robinson's LaunchAtLoginController. A very elegant solution to a very common problem.

This works on xcode 5.
- (BOOL)isLaunchAtStartup {
// See if the app is currently in LoginItems.
LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
// Store away that boolean.
BOOL isInList = itemRef != nil;
// Release the reference if it exists.
if (itemRef != nil) CFRelease(itemRef);
return isInList;
}
- (void)toggleLaunchAtStartup {
// Toggle the state.
BOOL shouldBeToggled = ![self isLaunchAtStartup];
// Get the LoginItems list.
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItemsRef == nil) return;
if (shouldBeToggled) {
// Add the app to the LoginItems list.
CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
if (itemRef) CFRelease(itemRef);
}
else {
// Remove the app from the LoginItems list.
LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
LSSharedFileListItemRemove(loginItemsRef,itemRef);
if (itemRef != nil) CFRelease(itemRef);
}
CFRelease(loginItemsRef);
}
- (LSSharedFileListItemRef)itemRefInLoginItems {
LSSharedFileListItemRef res = nil;
// Get the app's URL.
NSURL *bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
// Get the LoginItems list.
LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItemsRef == nil) return nil;
// Iterate over the LoginItems.
NSArray *loginItems = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItemsRef, nil);
for (id item in loginItems) {
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)(item);
CFURLRef itemURLRef;
if (LSSharedFileListItemResolve(itemRef, 0, &itemURLRef, NULL) == noErr) {
// Again, use toll-free bridging.
NSURL *itemURL = (__bridge NSURL *)itemURLRef;
if ([itemURL isEqual:bundleURL]) {
res = itemRef;
break;
}
}
}
// Retain the LoginItem reference.
if (res != nil) CFRetain(res);
CFRelease(loginItemsRef);
CFRelease((__bridge CFTypeRef)(loginItems));
return res;
}

I do this in an app I'm writing:
Check out UKLoginItemRegistry for an easy way to do this pragmatically. Afaik, there is no way in Tiger to do this without Apple Events; in Leopard there's a better way, but if you use UKLoginItemRegistry it really is no problem. Here's the complete code for implementing an "Open at Logon" menu item
+ (bool)isAppSetToRunAtLogon {
int ret = [UKLoginItemRegistry indexForLoginItemWithPath:[[NSBundle mainBundle] bundlePath]];
NSLog(#"login item index = %i", ret);
return (ret >= 0);
}
- (IBAction)toggleOpenAtLogon:(id)sender {
if ([PopupController isAppSetToRunAtLogon]) {
[UKLoginItemRegistry removeLoginItemWithPath:[[NSBundle mainBundle] bundlePath]];
} else {
[UKLoginItemRegistry addLoginItemWithPath:[[NSBundle mainBundle] bundlePath] hideIt: NO];
}
}

I've refactored some of the answers here to provide a category on NSApplication that provides a launchAtLogin property.
https://gist.github.com/joerick/73670eba228c177bceb3

SMLoginItemSetEnabled is another modern option, see Modern Login Items article by Cory Bohon where he explains that you have to create a helper application whose sole purpose is to launch the main application. There's also a full step by step explanation in SMLoginItemSetEnabled - Start at Login with App Sandboxed on Stack Overflow.

Check here an open source example: https://github.com/invariant/rhpnotifier (LoginItem.m, LoginItem.h)

Related

Unmounting Drive/volume without ejecting

I am want to unmount a disk WITHOUT EJECTING. To do that I tried following code
{
NSString *path;
CFStringRef *volumeName=(__bridge CFStringRef)path;
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
CFURLRef pathRef = CFURLCreateWithString(NULL, CFSTR("/volumes/Untitled"), NULL);
DADiskRef disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, pathRef);
DADiskUnmount(disk, kDADiskUnmountOptionForce, NULL, NULL);
}
This code is from this question, Thanks to #zeFree
Its working but I want dynamic path to the volume where as in the code its static. I tried changing NSString to CFStringRef and then tried to use at place of path("/volumes/Untitled") mention but its still same.
Any suggestion is welcome.
First of all, you are strongly discouraged from using kDADiskUnmountOptionForce.
This is a method to unmount a volume at given URL with basic error handling and memory management.
- (BOOL)unmountVolumeAtURL:(NSURL *)url
BOOL returnValue = NO;
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
if (session) {
DADiskRef disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, (__bridge CFURLRef)url);
if (disk) {
DADiskUnmount(disk, kDADiskUnmountOptionDefault, NULL , NULL);
returnValue = YES;
CFRelease(disk);
} else {
NSLog(#"Could't create disk reference from %#", url.path);
}
} else {
NSLog(#"Could't create DiskArbritation session");
}
if (session) CFRelease(session);
return returnValue;
}
The error handling could be still improved by providing a callback handler in the DADiskUnmount function.

SecItemCopyMatching - Finding specific kSecClassIdentity by name

I'm trying to find a specific entry in my Mac OS X keychain, based on it's name (kSecAttrLabel), but it looks like SecItemCopyMatching is broken and applies no filtering whatsoever when looking for items of type: kSecClassIdentity.
This piece of code will return all identities found in all keychains, despite the kSecAttrLabel: #"MyIdentity" parameter:
NSDictionary *query = #{ (__bridge id)kSecClass: (__bridge NSString*)kSecClassIdentity,
(__bridge id)kSecAttrLabel: #"MyIdentity",
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll,
(__bridge id)kSecReturnAttributes: #YES,
(__bridge id)kSecReturnRef: #YES };
OSStatus status;
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKey);
Granted, I can then find the one identity I'm looking for by filtering manually the returned array, however, beside the fact that, IMHO, it should just work, I also would like to remove this identity from my keychain using SecItemDelete(), which takes a query as parameter, just like SecItemCopyMatching.
If filtering doesn't work for SecItemCopyMatching, then it is likely that it won't work for SecItemDelete and this mean I will simply erase the content of my keychain if I try to call SecItemDelete with this query.
What am I doing wrong?
I think I have just found a solution. It was suggested on on another forum that
Using labels on an identity is tricky because identities are not stored in the keychain as an atomic item but are store as a separate private key and certificate, and those items use labels in different ways.
This made me realise that a solution is to search for a certificate by label using SecItemCopyMatching, and then create the identity using SecIdentityCreateWithCertificate. The latter call should find the matching private key in the Keychain. Here is the full code (in C++) that seems to work for me on macOS Mojave:
SecIdentityRef identity = nullptr;
const char* certificateName = ...;
const void* keys[] = {
kSecClass,
kSecMatchLimit,
kSecReturnRef,
kSecAttrLabel
};
const void* values[] = {
kSecClassCertificate,
kSecMatchLimitOne,
kCFBooleanTrue,
CFStringCreateWithCString(nullptr, certificateName, kCFStringEncodingUTF8)
};
CFDictionaryRef query = CFDictionaryCreate(nullptr, keys, values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFTypeRef result;
OSStatus status = SecItemCopyMatching(query, &result);
CFRelease(query);
CFRelease(values[3]);
if (status) {
// error
}
else {
SecCertificateRef certificate = (SecCertificateRef)result;
status = SecIdentityCreateWithCertificate(nullptr, certificate, &identity);
CFRelease(certificate);
if (status) {
// error
}
}
I had a similar problem, but I was using kSecAttrApplicationTag rather than kSecAttrLabel. I am not an objective c nor an IOS security expert by any means. It turns out the method I was using to create the lookup tag was incorrect. Here is what worked for me:
- (void) getOrCreateKey: (NSNumber*)bits publicIdentifier:(NSString*)publicID
// no! This appears in several samples, but did not work for me on iOS
// NSData * publicLookupTag = [NSData dataWithBytes:publicId length:strlen((const char *)publicId)];
//
// yes!
NSData * publicLookupTag = [NSData dataWithBytes:[publicId UTF8String] length:publicId.length];
NSMutableDictionary *queryPublicKey = [[NSMutableDictionary alloc] init];
[queryPublicKey setObject:(id)kSecClassKey forKey:(id)kSecClass];
[queryPublicKey setObject:publicLookupTag forKey:(id)kSecAttrApplicationTag];
[queryPublicKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef];
OSStatus lookupStatus = noErr;
lookupStatus = SecItemCopyMatching((CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicLookupKey);

How to show simple text messages (toasts/alerts) in a Cocoa MacOS Webview?

I've already searched "everything" about this in Google/Stackoverflow, but I'm still stuck. I have just started developing OSX Apps, so I'm a (almost) complete newbie in Objective-C and Xcode 5 (5.0.2).
All I need is a simple webview to load a webgame from a given URL. This webview must behave just like a very simple Safari browser. My app is already working relatively well. It loads the game OK, and after a lot of struggling I succeeded making it show javascript alerts and confirms.
THE POINT: I need to display a simple text message to the user, in case of no internet connection is detected, then I need to close the app. It seems a very trivial thing, but I can't find a way to do that!!
That's my appDelegate.M:
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize myWebView;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
// Check if there's internet connection:
#include <SystemConfiguration/SystemConfiguration.h>
static BOOL internetOk()
{
BOOL returnValue = NO;
struct sockaddr zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sa_len = sizeof(zeroAddress);
zeroAddress.sa_family = AF_INET;
SCNetworkReachabilityRef reachabilityRef = SCNetworkReachabilityCreateWithAddress(NULL, (const struct sockaddr*)&zeroAddress);
if (reachabilityRef != NULL)
{
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
{
BOOL isReachable = ((flags & kSCNetworkFlagsReachable) != 0);
BOOL connectionRequired = ((flags & kSCNetworkFlagsConnectionRequired) != 0);
returnValue = (isReachable && !connectionRequired) ? YES : NO;
}
CFRelease(reachabilityRef);
}
return returnValue;
}
// -
if(internetOk())
{
[self.window setContentView:self.myWebView];
[self.window toggleFullScreen:#""];
[self.myWebView setMainFrameURL:#"http://www.mywebgameurl.com"];
}
else
{
// SHOWS ERROR MESSAGE AND CLOSES APP! HOW CAN I DO IT????
}
}
#end
Any help is welcome, thanks!!
You're looking for the NSAlert class, check here for Apple's docs.
Sample usage:
NSAlert* alert = [NSAlert alertWithMessageText:#"Internet Error"
defaultButton:nil
alternateButton:nil
otherButton:nil
informativeTextWithFormat:#"No internet."];
[alert runModal];

Getting the position of my application's dock icon using Cocoa's Accessibility API

How can I get the position of my application's dock icon using the Accessibility API?
Found it! Using this forum post as reference, I was able to shape the given sample code to what I needed:
- (NSArray *)subelementsFromElement:(AXUIElementRef)element forAttribute:(NSString *)attribute
{
NSArray *subElements = nil;
CFIndex count = 0;
AXError result;
result = AXUIElementGetAttributeValueCount(element, (CFStringRef)attribute, &count);
if (result != kAXErrorSuccess) return nil;
result = AXUIElementCopyAttributeValues(element, (CFStringRef)attribute, 0, count, (CFArrayRef *)&subElements);
if (result != kAXErrorSuccess) return nil;
return [subElements autorelease];
}
- (AXUIElementRef)appDockIconByName:(NSString *)appName
{
AXUIElementRef appElement = NULL;
appElement = AXUIElementCreateApplication([[[NSRunningApplication runningApplicationsWithBundleIdentifier:#"com.apple.dock"] lastObject] processIdentifier]);
if (appElement != NULL)
{
AXUIElementRef firstChild = (__bridge AXUIElementRef)[[self subelementsFromElement:appElement forAttribute:#"AXChildren"] objectAtIndex:0];
NSArray *children = [self subelementsFromElement:firstChild forAttribute:#"AXChildren"];
NSEnumerator *e = [children objectEnumerator];
AXUIElementRef axElement;
while (axElement = (__bridge AXUIElementRef)[e nextObject])
{
CFTypeRef value;
id titleValue;
AXError result = AXUIElementCopyAttributeValue(axElement, kAXTitleAttribute, &value);
if (result == kAXErrorSuccess)
{
if (AXValueGetType(value) != kAXValueIllegalType)
titleValue = [NSValue valueWithPointer:value];
else
titleValue = (__bridge id)value; // assume toll-free bridging
if ([titleValue isEqual:appName]) {
return axElement;
}
}
}
}
return nil;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
AXUIElementRef dockIcon = [self appDockIconByName:#"MYAPPNAME"];
if (dockIcon) {
CFTypeRef value;
CGPoint iconPosition;
AXError result = AXUIElementCopyAttributeValue(dockIcon, kAXPositionAttribute, &value);
if (result == kAXErrorSuccess)
{
if (AXValueGetValue(value, kAXValueCGPointType, &iconPosition)) {
NSLog(#"position: (%f, %f)", iconPosition.x, iconPosition.y);
}
}
}
}
As for Mac OS El Capitan, looks like you aren't supposed to get the position of the icon using Accessibility API. The matter is that the icon isn't located in accessibility objects hierarchy of the app—it can be found in the hierarchy of the system Dock application. A sandboxed app isn't supposed to access the accessibility objects of other apps.
The code in approved answer doesn't yield any warnings of the sandboxd daemon in the console, looks like it doesn't violate any rules. It creates the top-level accessibility object with the function AXUIElementCreateApplication. The documentation states, that it:
Creates and returns the top-level accessibility object for the
application with the specified process ID.
Unfortunately, this top-level object is not the ancestor of the Dock icon.
I've tried to run the code, and it calculates the position of the first app's main menu item (which has the same title as the app itself). The comparison takes place in this line:
if ([titleValue isEqual:appName]) {
So the output was always the same for my app:
position: (45.000000, 0.000000)
An attempt to access the other app's accessibility object yielded a warning in console. I guess another way to calculate the position of the icon has to be found.

How do you make your App open at login? [duplicate]

This question already has answers here:
Register as Login Item with Cocoa?
(7 answers)
Closed 9 years ago.
Just wondering how I can make my app open automatically at login, but make this be able to be toggled on and off using a check box in the preferences window.
Here's some code that I use, it's based on the Growl source.
+ (BOOL) willStartAtLogin:(NSURL *)itemURL
{
Boolean foundIt=false;
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
UInt32 seed = 0U;
NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
for (id itemObject in currentLoginItems) {
LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
CFURLRef URL = NULL;
OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
if (err == noErr) {
foundIt = CFEqual(URL, itemURL);
CFRelease(URL);
if (foundIt)
break;
}
}
CFRelease(loginItems);
}
return (BOOL)foundIt;
}
+ (void) setStartAtLogin:(NSURL *)itemURL enabled:(BOOL)enabled
{
OSStatus status;
LSSharedFileListItemRef existingItem = NULL;
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
UInt32 seed = 0U;
NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
for (id itemObject in currentLoginItems) {
LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
CFURLRef URL = NULL;
OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
if (err == noErr) {
Boolean foundIt = CFEqual(URL, itemURL);
CFRelease(URL);
if (foundIt) {
existingItem = item;
break;
}
}
}
if (enabled && (existingItem == NULL)) {
LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst,
NULL, NULL, (CFURLRef)itemURL, NULL, NULL);
} else if (!enabled && (existingItem != NULL))
LSSharedFileListItemRemove(loginItems, existingItem);
CFRelease(loginItems);
}
}
If you want an easy to implement checkbox, make a #property BOOL startAtLogin; in one of your classes and implement it as follows. Just bind the checkbox value to the property and it should all work seamlessly.
- (NSURL *)appURL
{
return [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
}
- (BOOL)startAtLogin
{
return [LoginItem willStartAtLogin:[self appURL]];
}
- (void)setStartAtLogin:(BOOL)enabled
{
[self willChangeValueForKey:#"startAtLogin"];
[LoginItem setStartAtLogin:[self appURL] enabled:enabled];
[self didChangeValueForKey:#"startAtLogin"];
}
There is a decent description of what to do at CocoaDev.
Basically, you'll want to use the API in LaunchServices/LSSharedFileList.h if you can target Mac OS X 10.5 or later. Before 10.5 there was no clean API, so you have to manually manipulate the login items (Sample code at the Developer Connectiong).
Here's the sample code(dead) for Leopard I mentioned in the comments. Found via this blog post. The code you need to enable or disable startup at login is in Controller.m.
Call the method pasted below with a file URL pointing at your application to add it to the current user's login items.
To disable again, you'll need to get that same loginListRef, convert it to an array, and iterate through it until you find the item with the url you want to disable. Finally, call LSSharedFileListItemRemove with the appropriate arguments.
Good luck :)
- (void)enableLoginItemWithURL:(NSURL *)itemURL
{
LSSharedFileListRef loginListRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginListRef) {
// Insert the item at the bottom of Login Items list.
LSSharedFileListItemRef loginItemRef = LSSharedFileListInsertItemURL(loginListRef,
kLSSharedFileListItemLast,
NULL,
NULL,
(CFURLRef)itemURL,
NULL,
NULL);
if (loginItemRef) {
CFRelease(loginItemRef);
}
CFRelease(loginListRef);
}
}
See also SO question: Register as login item with cocoa

Resources