Writing MacOSX System Services with RubyCocoa
Today I started my study about RubyCocoa and how it works, my first project was a simple system service for MacOSX Leopard where the user can type a portuguese text, select it and translate to english, the user can also type the text in english and execute the system service to translate it back to portuguese.
Here’s what I did:
1. Create XCode project
Open Xcode, go to File -> New Project… and in New Project window select Cocoa-Ruby Application as project template exactly like the image below:
2. Create the class that will handle service messages
Please click on File -> New File… to open New File window, please select Ruby NSObject subclass exactly like below:
Click on Next button, a filename will be asked, you must rename the file to Service.rb and then you can copy and paste the code below:
Services.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | #Importing rubycocoa library require 'osx/cocoa' #Importing the library below to handle http requests require 'open-uri' #Importing json library require 'json' #Including the library below to use url_encode method require 'erb' include ERB::Util #Our class must inherit from NSObject to handle service messages class Services < OSX::NSObject include OSX #This is the method that will execute text translation def translate(pboard, sourceLang, targetLang) types = pboard.types() pboardString = nil #checking if the type of data in pasteboard is a string if types.include?(NSStringPboardType) then pboardString = pboard.stringForType(NSStringPboardType) #If the user haven't selected anything then display a message on screen if pboardString == nil NSAlert::alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( "Warning!", "Ok", nil, nil, "Please select a text!").runModal() return end #Encoding the pasteboard string before send it to google translator service pboardString = url_encode(pboardString) url = 'http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=' + pboardString + '&langpair=' + sourceLang + '%7C' + targetLang begin #Calling google translator service buffer = open(url, "Referer" => "faces.eti.br").read #Parsing server response result = JSON.parse(buffer) rescue NSAlert::alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( "Warning!", "Ok", nil, nil, "Error while calling google translator!").runModal() return end #Checking server response status if result and result['responseStatus'] == 200 pboard.declareTypes_owner([NSStringPboardType], nil) success = pboard.setString_forType_(result['responseData']['translatedText'], NSStringPboardType) if ! success puts "Error while replacing PasteBoard data!" end end end return end #This is the method that will handle the portugueToEnglish message of ITranslate service def portugueseToEnglish_userData_error(pboard, data, error) translate(pboard, "pt", "en") end #Registering the method above as system service message handler objc_method :portugueseToEnglish_userData_error, 'v@:@*:' #This is the method that will handle the englishToPortuguese message of ITranslate service def englishToPortuguese_userData_error(pboard, data, error) translate(pboard, "en", "pt") end #Registering the method above as system service message handler objc_method :englishToPortuguese_userData_error, 'v@:@*:' end |
3. Registering the system service
Once that you have created the code of your system service, it’s time to register on MacOSX, you can do it by doing small changes on rb_main.rb file exactly like below:
rb_main.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #Importing rubycocoa classes require 'osx/cocoa' #Imporing the code of our service provider require 'Services' def rb_main_init path = OSX::NSBundle.mainBundle.resourcePath.fileSystemRepresentation rbfiles = Dir.entries(path).select {|x| /\.rb\z/ =~ x} rbfiles -= [ File.basename(__FILE__) ] rbfiles.each do |path| require( File.basename(path) ) end end if $0 == __FILE__ then rb_main_init #Initializing our service services = Services.alloc.init #Registering our service OSX.NSRegisterServicesProvider(services, "ITranslate") #Update system services registry OSX.NSUpdateDynamicServices() #Application initialization OSX.NSApplicationMain(0, nil) end |
4. Changing build settings
Please do a double click on ITranslate build target exactly like in the image below:
The build settings window will be opened, please change the “Wrapper Extension” to “service”:
5. Installing the service
Just copy ITranslate.service to /Library/Services folder in MacOSX.
6. Using the service
Just finish the user session and login again, open Text Editor, type a text in english, select it and click on TextEdit -> Services -> ITranslate -> English to Portuguse, the text will be translated to portuguese.
Click here to download ITranslate.
Note: ITranslate requires json library, you can find instructions about how to download and install json library here.




Update: A new version is available for download, there’s no need to install json library separately.
September 12th, 2009 | #
http://tinyurl.com/my6nyz
Facing new challenges everyday ยป Writing MacOSX System Services with RubyCocoa
December 8th, 2009 | #