Writing MacOSX System Services with RubyCocoa

Posted by – September 7, 2009

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:

new_project_xcode_redux

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:

new_class

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:

xcode_build_target

The build settings window will be opened, please change the “Wrapper Extension” to “service”:

build_target_settings

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.

Share

2 Comments on Writing MacOSX System Services with RubyCocoa

  1. rogerio says:

    Update: A new version is available for download, there’s no need to install json library separately.

  2. http://tinyurl.com/my6nyz
    Facing new challenges everyday » Writing MacOSX System Services with RubyCocoa

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>