Building an OS X App With RubyMotion
01 Nov 2013RubyMotion’s OS X support enabled me to solve a small problem yesterday. Here’s the story.
Background on Backup
No matter how much money we spend on computers, the data we create is worth more. The best data protection employs several levels of redundancy.
My backup system starts with Apple’s Time Machine, which handles first level backups painlessly in the background. Time machine is even more effective when multiple disks are used. I like to combine that with online solutions. Redundancy is a good thing.
USB-attached drives work great with Time Machine. I found that Time Machine was inconsistent over WiFi, and when it failed, troubleshooting took too much time. That was a few years ago and things have probably improved since then. I’ve never had to troubleshoot a USB-attached Time Machine drive. USB just works. Except when it doesn’t. And that’s where RubyMotion proved helpful.
Disconnecting USB in a Hurry
My only problem with USB-attached drives comes when I need to leave my desk in a hurry. You may have seen this message before:
All three of my USB drives are attached through a single USB hub. Yanking the cable while running for a meeting is… bad! Forgetting to eject even one of the USB drives is also bad.
I solved the problem about a year ago by writing a command line tool that ejects all three drives with one double-click of an icon. Yesterday I deciced to improve my command line tool with RubyMotion.
Ejecting in a Hurry
EjectDisks
is a simple OS X program written with the RubyMotion toolchain for Mac OS X. It uses the osx-status-bar-app-template
gem created by Elliott Draper. Here’s a 10-second demo.
The Code
The latest version of the EjectDisk code is on GitHub. The app_delegate.rb
file appears below.
class AppDelegate
attr_accessor :status_menu
def applicationDidFinishLaunching(notification)
@app_name = NSBundle.mainBundle.infoDictionary['CFBundleDisplayName']
@status_menu = NSMenu.new
@status_item = NSStatusBar.systemStatusBar.statusItemWithLength(NSVariableStatusItemLength).init
@status_item.setMenu(@status_menu)
@status_item.setHighlightMode(true)
@status_item.setTitle(@app_name)
@status_menu.addItem createMenuItem("About #{@app_name}", 'orderFrontStandardAboutPanel:')
@status_menu.addItem createMenuItem("Custom Action", 'pressAction')
@status_menu.addItem createMenuItem("Eject Three Disks", 'ejectThreeDisks')
@status_menu.addItem createMenuItem("Say Something", 'sayDroid')
@status_menu.addItem createMenuItem("Sing", 'singSomething')
@status_menu.addItem createMenuItem("Greetings", 'greetings')
@status_menu.addItem createMenuItem("Quit", 'terminate:')
end
def createMenuItem(name, action)
NSMenuItem.alloc.initWithTitle(name, action: action, keyEquivalent: '')
end
def pressAction
alert = NSAlert.alloc.init
alert.setMessageText "Action triggered from status bar menu"
alert.addButtonWithTitle "OK"
alert.runModal
end
def sayDroid
%x(say -v cello droid)
end
def singSomething
%x(say -v cello da da da da da da da da da da da da da da da da da da da da da da da da da da)
end
def greetings
%x(say -v cello Greetings to the members of Chippewa Valley Code Camp &)
alert = NSAlert.alloc.init
alert.setMessageText "Greetings to the members of Chippewa Valley Code Camp!"
alert.addButtonWithTitle "OK"
alert.runModal
end
def ejectThreeDisks
alert = NSAlert.alloc.init
response = %x(/usr/sbin/diskutil eject SiiGBlack) + "\n"
response += %x(/usr/sbin/diskutil eject Ultra3TB) + "\n"
response += %x(/usr/sbin/diskutil eject WDSilver) + "\n"
alert.setMessageText response
alert.addButtonWithTitle "OK"
alert.runModal
end
end
Next Steps
Not everything in the EjectDisks tool is business-related. Do we really need to include a greeting to the members of the Chippewa Valley Code Camp? Yes, we do!
On the serious side, the app should handle disk ejection as a background process. It’s not good to tie up the system for a simple task. Look for a forked process in a future version of the app.
Update: This article should have included instructions on how to run the executable on any Mac OS X system without having RubyMotion installed. The omission is corrected in a later blog post, OS X and RubyMotion, Finishing Up.