It’s finished. The real craze and controversy over Disco’s smoke is over. Jonathan Wight was the first to write up an article on integrating the smoke effects into a third party Cocoa application. Times have changed, and the smoke framework bundled with the more recent versions of the application no longer produce the desired results by using the same code.
So how do you get any Cocoa application to start smoking? Austin Sarner was kind enough to reveal the usage and implementation of the changes to the Disco framework which allow a wider variety of Macs to experience the entertaining effect.
The Framework
The framework (in more ways than one) for our smoking window is the Smoke framework embedded inside the Disco app. In your Xcode project, you can reference the framework straight from within the application. You’ll have to press Cmd-Shift-G and type in the path as you can’t navigate into bundles using the standard open file dialog. Normally the location is “/Applications/Disco.app/Frameworks/” inside which is the Smoke.framework bundle which we’re interested in.
Alternatively, you can copy the framework to /Library/Frameworks/ and reference it from there. If you need to modify the framework (more on that later) it’s best to choose this option so you leave the contents of the Disco application alone.
Head to the headers
Before we can use the framework, we need to have a valid header or header files for the framework, which tell Xcode (and us) mainly about the classes and methods in Smoke.framework. The headers aren’t included in the framework anymore, but to generate a header file is a simple matter of using a utility like class-dump on the framework.
You can take a look inside the header and see a bit of what’s possible with the framework. When you’re ready, we’ll move onto the…
Setup
Now we’ve got the framework and header in place, we need to code to actually get a window to smoke. On the surface, this is astonishingly simple.
In your Xcode project, make sure you’ve added Smoke.framework and Smoke.h as well as Quartz.framework as the smoke needs to use this. Now create a new Objective-C subclass. In this example we’ve used an NSWindow subclass, but you could just as easily subclass NSObject and only apply smoke on selected outlets. If you really wanted to start a bonfire, you could add a category to NSWindow just for smoking!
Firstly, we define everything in our header file. Our class is called SmokingWindow (SmokeWindow is taken by Smoke.framework) and we have four methods.
The header file will look something like this:
//
// SmokingWindow.h
// Smoke
//
// Created by Ankur Kothari on 13/04/07.
// Copyright 2007 Vacuous Virtuoso. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
#import "Smoke.h"
@interface SmokingWindow : NSWindow {
SmokeController *theSmokeController;
int smoking;
}
-(int)smoking;
-(IBAction)startSmoking:(id)sender;
-(IBAction)stopSmoking:(id)sender;
-(IBAction)toggleSmoke:(id)sender;
@end
-smokingreturns whether (1) or not (0) the window is currently smoking. It’s not needed to smoke, but it’s good practice.-startSmokingobviously starts the window smoking, and-stopSmokingstop the window smoking-toggleSmokewill stop the window smoking if it is, or start it smoking if it isn’t.
We’re storing the current state of the window for use by the -smoking method in the int smoking, and theSmokeController is a pointer to a SmokeController which will be doing most of the work.
Ok, now to the implementation.
//
// SmokingWindow.m
// Smoke
//
// Created by Ankur Kothari on 13/04/07.
// Copyright 2007 Vacuous Virtuoso. All rights reserved.
//
#import "SmokingWindow.h"
@implementation SmokingWindow
-(void)awakeFromNib
{
SmokeCapabilities *smokeCapabilities = [SmokeCapabilities sharedCapabilities];
theSmokeController = [[SmokeController alloc] init];
[theSmokeController doExpensiveThreadedInitializations];
[theSmokeController setSmokeAlgorithm:[smokeCapabilities smokeAlgorithm]];
[theSmokeController attachToWindow:self];
smoking = 0;
[theSmokeController showWindow:self];
}
-(IBAction)toggleSmoke:(id)sender{
if(smoking){
[self stopSmoking:sender];
} else {
[self startSmoking:sender];
}
}
-(IBAction)startSmoking:(id)sender
{
if(!smoking){
[theSmokeController startSmoking:self];
smoking = 1;
}
}
-(IBAction)stopSmoking:(id)sender
{
if(smoking){
[theSmokeController stopSmoking:self];
smoking = 0;
}
}
-(int)smoking{
return smoking;
}
@end
The first few lines in -awakeFormNib get the SmokeController do some initialization.
SmokeCapabilities *smokeCapabilities = [SmokeCapabilities sharedCapabilities];
theSmokeController = [[SmokeController alloc] init];
[theSmokeController doExpensiveThreadedInitializations];
[theSmokeController setSmokeAlgorithm:[smokeCapabilities smokeAlgorithm]];
To tell the SmokeController which window to ‘burn’, the -attachToWindow: method is used. Since we’re using an NSWindow subclass in this example, we can just set it to self, otherwise you’ll need a pointer or outlet to an instance of NSWindow.
[theSmokeController attachToWindow:self];
To just make extra sure that the position of the smoke wont be offset if the window is dragged around during initialization, we call the -showWindow method. This isn’t completely necessary.
[theSmokeController showWindow:self];
To heat or cool the window, theSmokeController responds to -startSmoking and -stopSmoking. Easy enough!
More fun!
For extra smoking fun, you can add bindings to your application to control many aspects of the smoke. You can adjust the initial temperature, the amount of pressure exerted by the mouse, the FPS, and many more. Just check the UserDefaults.plist file located in the Resources folder in Smoke.framework.1
Hacking the smoke framework
To get even more out of the smoke framework, you can edit various files within the framework itself. Most of the files are in the “Resources” folder. One of the most useful ones to edit is called boundary.cikernal. In the first two lines you can specify the height and width of the OpenGL view that the smoke appears in.
Enjoy the smoke!
Sample project
Developments
Austin has also graciously agreed to let me develop Fumo, the smoking Quicksilver interface.
Although there is still some talk about the HIG, usability, eye candy and so on, the controversy phase is mostly over. Speaking of which, you can call me what you like, but I think the HIG is definitely not dead. Outdated perhaps, but there needs to be a standard. There are many aspects to the existing HIG that developers need to follow in order to make the Mac experience enjoyable. I’ll talk about this in detail later. My rant about the HIG.
-
One of the really good ones is MouseSmokeRadius, which makes smoke comes out of your mouse. I like to call them “fireballs”. ↩

8 Comments so far
Leave a commentHeh, rock on. I’ll update my post to point to yours.
Jon.
written by Jonathan Wight on April 16, 2007 1:09 am | Permalink
Thanks Jon.
expressed by Ankur on April 16, 2007 8:15 am | Permalink
I think you might’ve missed the most important part of the private interface:
divulged by Austin Sarner on April 26, 2007 4:46 pm | Permalink
Haha. I almost though you were being serious
recorded by Ankur on April 26, 2007 4:49 pm | Permalink
Wow. This rocks!
voiced by Harley on May 20, 2007 12:59 am | Permalink
Leave a comment