FRAppliance 101

From AwkwardTV
Revision as of 16:52, 8 April 2007 by Screensaver (talk | contribs)
Jump to: navigation, search

This is the first in a likely series of tutorials on writing Apple TV Finder.app plugins. The basic setup of Xcode and the development environment will be described, and a simple "Hello World" plugin will be created.

Prerequisites

Note: These instructions are written for a PowerPC Macintosh. Some steps, especially regarding build architecture settings, may not be necessary on an Intel Mac but are still recommended.

Preparing the Environment

These steps only need to be performed once.

  1. Install Xcode and especially the Mac OS X 10.4 Universal (10.4u) SDK.
  2. Copy the BackRow.framework and iPhotoAccess.framework from /System/Library/PrivateFrameworks on the Apple TV to some place on your computer. I recommend placing these in the same folder your project folder will be created.

Installing the Headers

The headers need to be placed in the appropriate location for the compiler to find them. BackRow.framework depends on some private headers inside QuartzComposer.framework, so we have to put headers in there too.

  1. Open the BackRow.framework folder, and in there extract BRPrivateHeaders (http://ericiii.net/sa/appletv/BRPrivateHeaders.zip) to the PrivateHeaders folder. (The ZIP file contains a PrivateHeaders folder, so just move that there.)
  2. In /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/Quartz.framework/Frameworks/QuartzComposer.framework, extract QCPrivateHeaders (http://ericiii.net/sa/appletv/QCPrivateHeaders.zip) to the PrivateHeaders folder (same deal as above).
  3. In /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/Quartz.framework/Frameworks/QuartzComposer.framework/Headers/QuartzComposer.h, add this line to the end:
    #import <QuartzComposer/QuartzComposerPrivate.h>

Creating the Plugin

Now the fun stuff starts, actually creating the plugin.

Making the Project

Launch Xcode, and create a new Cocoa Bundle project (File > New Project...). Name it "HelloWorld".

At this point, you have an empty project with no code. You now need to specify the frameworks to link to.

Adding Frameworks

In the Frameworks and Libraries group, drag BackRow.framework and iPhotoAccess.framework to the Linked Frameworks group. In the dialog that pops up, make sure "Copy items into destination group's folder" is unchecked and choose a Reference Type of "Default". In "Add to Targets," the HelloWorld target should be checked. Click "Add".

At this point, you should have three frameworks listed underneath "Linked Frameworks:" BackRow.framework, iPhotoAccess.framework, and Cocoa.framework.

Creating the Plugin

Open InfoPlist.strings (for English) and add the following lines:

 CFBundleName = "Hello, World!";

The CFBundleName is the label that will be given in the main menu.

The Main Appliance Class

Now you finally write some code! Right click on the Classes group and choose Add > New File..., under Cocoa add an Objective-C class and give it a name of "HelloWorldAppliance". Check "Also create HelloWorldAppliance.h", make sure you're adding to project HelloWorldTest, and the HelloWorld target is checked. Click on "Finish".

Open HelloWorldAppliance.h and change it so that your HelloWorldAppliance class inherits from BRAppliance. Change "NSObject" in the line beginning with "@interface" to read "BRAppliance".

You also need to add the following line to the header, just underneath the existing "#import" line:

 #import <BackRow/BackRow.h>

Open HelloWorldAppliance.m now, and in order to bypass the plugin whitelist, you must put the following code in the HelloWorldAppliance implementation. Add this code before the @end line:

// Override to allow FrontRow to load custom appliance plugins
+ (NSString *) className {
    // this function creates an NSString from the contents of the
    // struct objc_class, which means using this will not call this
    // function recursively, and it'll also return the *real* class
    // name.
    NSString * className = NSStringFromClass( self );
	
	// new method based on the BackRow NSException subclass, which conveniently provides us a backtrace
	// method!
	NSRange result = [[BRBacktracingException backtrace] rangeOfString:@"_loadApplianceInfoAtPath:"];
	
	if(result.location != NSNotFound) {
		NSLog(@"+[%@ className] called for whitelist check, so I'm lying, m'kay?", className);
		className = @"RUICalibrationAppliance";
	}
	
	return className;
}

When the menu item is selected, the -applianceControllerWithScene: method is called on your BRAppliance subclass. This method needs to return a controller for your display. (Note: I'll write more on how the displays actually work in the future, but it's essentially a stack of controllers that are pushed to go deeper in the hierarchy and popped to go back.)

The following implementation uses the BRAlertController class to display a simple "Hello, World" message when the item is selected.

- (id)applianceControllerWithScene:(id)scene {
	BRAlertController *alert = [BRAlertController alertOfType:0
               titled:@"Hello, World!"
               primaryText:@"Hello from my first Apple TV plugin!"
               secondaryText:nil
               withScene:scene];
	return alert;
}

Building the Plugin

This isn't very hard, but you have to set some settings first.

Build Settings

Before you can properly build, you must adjust some build settings to build for the Apple TV environment.

Under the "Targets" group on the left side of the Xcode project window, right click on the "HelloWorld" target and choose "Get Info."

On the "Properties" tab, change Identifier to something unique, such as "org.awkwardtv.HelloWorldAppliance". Change the Creator to "fnrw", and set Principal Class to "HelloWorldAppliance" (this must match the BRAppliance subclass for your plugin).

On the "Build" tab, set configuration to "All Configurations" and then set the build settings as follows:

In the "Architectures" collection, double click on the Architectures setting and make sure only Intel is checked. This is a value of "i386".

In "Build Locations," make sure the SDK Path is set to "/Developer/SDKs/MacOSX10.4u.sdk" (it should be by default).

In "Linking," make sure ZeroLink is unchecked.

In "Packaging," set the Wrapper Extension to "frappliance".

In "Language" (underneath "GNU C/C++ Compiler 4.0"), make sure "Enable Objective-C Exceptions" is checked.

Building

At this point, you can just click Build to build the plugin. The compiled version will appear in build/Debug/HelloWorldTest.frappliance in your project folder. The whitelist workaround causes some warnings when building, but those can be ignored.

Running the Plugin

To run it, just copy the built version to /System/Library/CoreServices/Finder.app/Contents/PlugIns on the Apple TV. I really recommend at least creating a symlink to the frappliance in the frontrow home directory, if not directly mounting the project from your development system, but both are beyond the scope of the tutorial.

After it's in place on the Apple TV, you need to restart Finder (either with killall Finder if you have killall installed, otherwise use ps awx|grep Finder to get the PID and then kill PID.)

Now when you choose the "Hello, World!" menu item on the Apple TV, you should see the message!

Congratulations!

If you've successfully made it this far, you've created your first plugin!

A completed xcode project can be found at http://ericiii.net/sa/appletv/FRApplianceTutorial/HelloWorld.zip. This is set up for the two frameworks are in the same folder as the HelloWorld project folder.

Making a BackRow QuartzComposer Screen Saver

Finally the screen saver problem has been solved too!

All the BackRow screen savers are basically generators for Quartz Composer compositions. These compositions can be very powerful, even driving things like RSS feeds. In fact, that's the example we'll use.

Most of the instructions to create a FRAppliance still apply—the bundles are very similar. I will go by the same steps as above, but only highlight the DIFFERENCES from those changes.

Creating the Plugin

Open InfoPlist.strings (for English) and add the following item:

CFBundleName = "RSS Visualizer";

Making the Project

First, you'll want a more useful name. Instead of "HelloWorld", let's call it "RSSVisualizerSaver". Create a new Cocoa Bundle project with this name.

The Main Appliance Class

Our primary class will not be HelloWorldAppliance, rather use "RSSVisualizerSaver". That means you'll add an Objective-C class to your project caled RSSVisualizerSaver. Don't forget to check "Also create RSSVisualizerSaver.h" in Xcode.

Open RSSVisualizerSaver.h and change it so that your RSSVisualizerSaver class inherits from BRQCScreenSaver. Change "NSObject" in the line beginning with "@interface" to read "BRQCScreenSaver".

Still add the BackRow #import statement from the instructions above.

You'll want to add the className class method to RSSVisualizerSaver.m—screen savers need to bypass their own whitelists too. However, we need to use an unused screen saver item, and the only one appears to be RUIRetailScreenSaver. Folks are working on subverting the whitelists, so stay tuned for that. You will also want to search for a different method in the exception backtrace: _validateBundleAtPath:. Here's the full code to place into RSSVisualizerSaver.m, instead of the one defined by the Appliance instructions.

// Override to allow FrontRow to load custom screen saver bundles + (NSString *) className {

   // this function creates an NSString from the contents of the
   // struct objc_class, which means using this will not call this
   // function recursively, and it'll also return the *real* class
   // name.
   NSString * className = NSStringFromClass( self );

// new method based on the BackRow NSException subclass, which conveniently provides us a backtrace // method! NSRange result = [[BRBacktracingException backtrace] rangeOfString:@"_validateBundleAtPath:"];

if(result.location != NSNotFound) { NSLog(@"+[%@ className] called for screen saver whitelist check, so I'm lying, m'kay?", className); className = @"RUIRetailScreenSaver"; }

return className; }

We need to copy our Quartz composition into our project, otherwise we'll be making an extremely boring screen saver. First, you'll want to copy a composition into your project. Copy this file from your Mac OS X system:

/System/Library/Screen\ Savers/RSS Visualizer.qtz

into your project folder. Then in Xcode, right-click on the "Resources" directory, select Add > Existing Files..., and select RSS Visualizer.qtz from your project folder. Make sure it is added to your target.

Back to the code. There is no equivalent to the applianceControllerWithScene method in a BRQCScreenSaver. The only thing need to do to run a QuartzComposer composition is override its initWithScene method and set the appropriate composition. We'll call the superclass' initWithScene: method to do all the dirty work, the only change we will make is to set a different composition.

- (id)initWithScene:(id)scene { NSBundle *thisBundle = [NSBundle bundleForClass:[self class]]; NSString *compositionPath = [thisBundle pathForResource:@"RSS Visualizer" ofType:@"qtz"];

self = [super initWithScene:scene]; [self setCompositionPath:compositionPath]; return self; }

Building the Plugin

Build Settings

Everything is the same, except:

  • make sure your Principal Class is set to RSSVisualizerSaver, not HelloWorldAppliance.
  • set the Wrapper Extension to frss, not frappliance.
Building

Click Build. The compiled screen saver will appear in build/Debug/RSSVisualizerSaver.frss.

Runnng the Plugin

You'll want to copy your built copy to the correct folder. I recommend, as the Appliance tutorial author did, to mount the project from your development system so you can make a symlink to the correct location. Copy or symlink your built project to:

/System/Library/CoreServices/Finder.app/Contents/Screen Savers

Kill Finder, then navigate to Settings > Screen Savers. Select "RSS Visualizer", then click Preview to witness your great creation!

Congratulations again!

Start experimenting with some cool compositions for AppleTV—I think this will be a hugely popular hack, given I know the tv community can come up with some really creative screen savers to show on their sexy widescreens. :)

I (just call me John Doe, I want no recognition for this) owe this entire hack to EricIII, the creator of the FRAppliance 101 tutorial above. Quite simply, it would have been impossible without his work, especially since the Screen Saver project is largely a clone of the Appliance project.

Happy hacking!

Revision History

  • April 8, 2007
  • John Doe added the "Making a BackRow QuartzComposer Screen Saver" section.
  • April 4, 2007
    • Changed how the BackRow headers are dealt with, to be more proper and all the headers are converted.
    • Replaced the whitelist bypass with a cleaner method of doing so (that doesn't require ExceptionHandling.framework)