Search code examples
javascriptmacosautomationalfred

How do I get a list of services in JavaScript using osascript?


I have been trying to create a script that will generate an XML structure of the names of all services available. It works fine in Script Editor, but it can not run from the command line. The code I have is:

ObjC.import("Cocoa");
ObjC.import("AppKit");


function run(arg) {
    items = $.NSApp.servicesMenu.itemArray;

    list = "<items>";
    count = 0;
    items.enumerateObjectsUsingBlock(function( obj, index, stop ){
        list += "<item uid='Service" + count + "' arg='" + obj.title.UTF8String + "' valid='yes' autocomplete='"  + obj.title.UTF8String + "'><title>" + obj.title.UTF8String + "</title><subtitle>Services Toolbox</subtitle><icon>icon.png</icon></item>";
        count++;
    });

    list += "</items>";

    return list;
}

This code will be used in an Alfred Workflow. Any ideas on how to change it?

As far as I can figure, running this in the osascript interpreter does not have a Services menu, but the Script Editor does. How can I get around this to get the list of services?


Solution

  • You are right, since the script is being launched via osascript, $.NSApp won't have access to the service menu.

    But, with a little UI scripting, you can find all the menu items through the finder application:

    function grabFirstNested(arr) {
      if (arr.length <= 0 || !Array.isArray(arr[0])) {
        return arr;
      }
    
      return grabFirstNested(arr[0]);
    }
    
    function run(arg) {
    
      var sys = Application('System Events');
    
      var servicesRef = sys.processes['Finder'].menuBars.menuBarItems.whose({
        name: { _contains: 'finder'}
      }, {ignorign: 'case'}).menus.menuItems.whose({
        name: { _contains: 'services'}
      }, {ignoring: 'case'}).menus.menuItems.name;
    
      var services = grabFirstNested(servicesRef());
      var list = "<items>";
      services.forEach(function(service, index) {
        list += "<item uid='Service" + index + "' arg='" + service + "' valid='yes' autocomplete='"  + service + "'><title>" + service + "</title><subtitle>Services Toolbox</subtitle><icon>icon.png</icon></item>";
      });
      list += "</items>";
    
      return list;
    }
    

    This script will access the Finder application through the System Events APIs, which give access to UI elements for scripting. Once it has the Finder application, it grabs the MenuItems from the "services" menu bar item.