Write RubyCocoa Application that Launches at User Login

I wanted Gmail Notifr to run automatically when I log in, so I added a preference item called “Launch at login”. In this article I’ll show how to implement this feature in RubyCocoa.

Gmail Notifr Preferences

Let’s assume the Gmail Notifr’s path is “/Applications/Gmail Notifr.app” for now, this will be used to identify our application in login items:

notifr_app = "/Applications/Gmail Notifr.app"

Use CFPreferencesCopyValue to fetch preference value for login items:

cf = OSX::CFPreferencesCopyValue(
  "AutoLaunchedApplicationDictionary",
  "loginwindow",
  OSX::KCFPreferencesCurrentUser, 
  OSX::KCFPreferencesAnyHost
)

The first two parameters specify that we need to fetch value for auto launched applications (login items), and the third parameter specifies that we want the value for the current user.

Now cf holds information about all login items, it’s easy to check if our application has already been sitting there:

auto_launch_enabled = cf.any? { |app| app.valueForKey("Path") == notifr_app }

Note: each item has a key named “Path” storing the application path.

To change the preference (set our application as login item or remove it from login items), we need a mutable copy of cf:

mcf = cf.mutableCopy

Then, to remove the application if it’s already there:

mcf.each do |app|
  mcf.removeObject(app) and break if app.valueForKey("Path") == notifr_app
end

Or to add it as a new login item:

item = OSX::NSDictionary.dictionaryWithObject_forKey(notifr_app, "Path")
mcf.addObject(item)

At last, we have to write the new value back:

OSX::CFPreferencesSetValue(
  "AutoLaunchedApplicationDictionary",
  mcf,
  "loginwindow",
  OSX::KCFPreferencesCurrentUser,
  OSX::KCFPreferencesAnyHost
)
OSX::CFPreferencesSynchronize(
  "loginwindow",
  OSX::KCFPreferencesCurrentUser,
  OSX::KCFPreferencesAnyHost
)

In the world of reality, user of Gmail Notifr would install it anywhere he/she likes, so it’s better we get the application path in a more flexible way:

notifr_app = NSBundle.mainBundle.bundlePath

Pretty easy right? Now see what we’ve done:

Login Items

Note: in RubyCocoa it’s unnecessary to call methods exactly as in Objective-C, for example, instead of writing

app.valueForKey("Path")

we can get the value for key “Path” in ruby’s usual way

app["Path"]

I keep the code more like Objective-C so it’s easier to reference Objective-C Cocoa APIs.

PS: example code here were extracted from the source code of Gmail Notifr hosted on GitHub.