Scripting Bridge is definitely one of the cooler additions to the toolbox that was added with Leopard. In a nutshell, it makes AppleScript palatable to those who loathe AppleScript. I don’t know what it is about AppleScript, but I always end up banging my head on seemingly simple problems.

Scripting Bridge reads an application’s AppleScript dictionary and creates a nice Objective-C API. The brilliant part is that you can access this Objective-C API from Ruby or Python, too. Scripting Bridge isn’t perfect, though, and one of the more annoying things is the warnings it spits out.

If you invoke SBApplication's +applicationWithBundleIdentifier:, you will often get a bunch of warnings printed out to standard error. This is not only annoying for GUI applications, as it clutters up the console output, it’s down right intrusive for command line applications. (r. 5964420)

This has bothered me for a bit, but while writing osx-trash, I finally figured out a way around it. It’s pretty simple: redirect standard error to /dev/null temporarily.

First, lemme demonstrate the problem with a simple app:

#import <Foundation/Foundation.h>
#import "Finder.h"

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    FinderApplication * finder =
        [SBApplication applicationWithBundleIdentifier:@"com.apple.finder"];
    NSLog(@"Hello");
    
    [pool drain];
    return 0;
}

The Finder.h header was created with the spd(1) tool. This app produces the following output:

sb_test: unknown type name "ICN#".
sb_test: unknown type name "l8mk".
sb_test: unknown type name "il32".
sb_test: unknown type name "icl8".
sb_test: unknown type name "icl4".
sb_test: unknown type name "ics#".
sb_test: unknown type name "s8mk".
sb_test: unknown type name "is32".
sb_test: unknown type name "ics8".
sb_test: unknown type name "ics4".
2008-05-26 23:59:13.086 sb_test[37847:10b] Hello

All those annoying “unknown type” warnings are coming from the single call to applicationWithBundleIdentifier:, and there’s no way to disable them, as far as I know. Here’s the same app in Ruby:

#!/usr/bin/ruby

require 'osx/cocoa'
include OSX
require_framework 'ScriptingBridge'

finder = SBApplication.applicationWithBundleIdentifier("com.apple.finder")
NSLog("Hello")

Here’s the solution to suppress these warnings, this time in Ruby first:

#!/usr/bin/ruby

require 'osx/cocoa'
include OSX
require_framework 'ScriptingBridge'

old_stderr = $stderr.clone        # save current STDERR IO instance
$stderr.reopen('/dev/null', 'w')  # send STDERR to /dev/null
finder = SBApplication.applicationWithBundleIdentifier("com.apple.finder")
$stderr.reopen(old_stderr)        # revert to default behavior
old_stderr.close

NSLog("Hello")

And here’s the Objective-C solution:

#import <Foundation/Foundation.h>
#include <fcntl.h>
#import "Finder.h"

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    int old_stderr = dup(STDERR_FILENO);
    close(STDERR_FILENO);
    int fd = open("/dev/null", O_WRONLY);
    dup2(fd, STDERR_FILENO);
    close(fd);
    FinderApplication * finder =
        [SBApplication applicationWithBundleIdentifier:@"com.apple.finder"];
    close(STDERR_FILENO);
    dup2(old_stderr, STDERR_FILENO);
    close(old_stderr);

    NSLog(@"Hello");
    
    [pool drain];
    return 0;
}

In both cases, we clone the standard error file descriptor to a new file descriptor and set standard error to /dev/null just prior to the applicationWithBundleIdentifier: call. After the call, we restore standard error to the previous file descriptor. On the Objective-C side, we need to drop down to some low-level Unix file descriptor handling which is a bit verbose.

Using NSLog is a nice test, as it uses standard error. If we see our NSLog output, it means we’ve correctly re-opened standard error to its original file descriptor.

Here’s the Objective-C solution as a handy category, which you can just copy and paste into your own application:

@implementation SBApplication (DDExtensions)

+ (id)dd_applicationWithBundleIdentifier:(NSString *) ident
{
    int old_stderr = dup(STDERR_FILENO);
    close(STDERR_FILENO);
    int fd = open("/dev/null", O_WRONLY);
    dup2(fd, STDERR_FILENO);
    close(fd);
    id application =
        [SBApplication applicationWithBundleIdentifier: ident];
    close(STDERR_FILENO);
    dup2(old_stderr, STDERR_FILENO);
    close(old_stderr);
    return application;
}

@end

With this, you can use dd_applicationWithBundleIdentifier: as a warning-free replacement to applicationWithBundleIdentifier:.