Now this is hot Cocoa


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

Header iconBefore 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.

Download Smoke.h:

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
  • -smoking returns whether (1) or not (0) the window is currently smoking. It’s not needed to smoke, but it’s good practice.
  • -startSmoking obviously starts the window smoking, and
  • -stopSmoking stop the window smoking
  • -toggleSmoke will 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

Xcode icon


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.


  1. One of the really good ones is MouseSmokeRadius, which makes smoke comes out of your mouse. I like to call them “fireballs”. 


Back to Top ↑

8 Comments so far

Leave a comment
  1. 1

    Heh, rock on. I’ll update my post to point to yours.

    Jon.

  2. 2

    Thanks Jon.

  3. 3

    I think you might’ve missed the most important part of the private interface:

    typedef enum {
        DCHighControversy = 0,
        DCVeryHighControversy = 1,
        DCExtremeControversy = 2,
    
    } DCControversyLevel;
    
    @interface DCSmokeController (Private)
    
    -(void)__setControversy:(DCControversyLevel)controversyLevel;
    -(DCControversyLevel)__controversyLevel;
    
    @end
  4. 4

    Haha. I almost though you were being serious ;)

  5. 5

    Wow. This rocks!

RSS feed for comments on this post. TrackBack URI

Leave a comment

Comments may be edited for formatting.