So, I’ve started to write another Mac media application. This one is called Medium. I figure I might as well write some of this stuff down as I do it as I’m learning most of it along the way. If you’re looking for Medium you can get the source from the repository hosted at GitHub. There is also a bug tracker setup thanks to LightHouse.

Given that I’ve started this on about 4 separate occasions I’ve got a bit of random code sitting around that I’ll probably recycle during this exercise. One of the bits is to switch between fullscreen and windowed mode for the application.

The first version of this I had was pretty complicated and convoluted. Thankfully, with the release of Leopard (or was it one of the Tiger point releases?) going fullscreen in a Cocoa application got a lot easier.

Here’s what we’re going to be doing. We’re going to create an application that can go fullscreen. We’ll use the defaults controller to store if it should start fullscreen or windowed. If the users resizes the window in windowed mode we’ll remember the window size for the next restart. When we switch to fullscreen or to windowed mode we’ll fade the screen to black and back again. We’ll also setup a default property for picking the screen the application should appear on (although, as I don’t have a second monitor pulled into my PowerBook at the moment this will remain untested).

With that, on with the show.

We’ll get started by creating a new XCode project. We’ll be using a Cocoa Application as the basis for the project. I’m going to call mine MediumFS, you’re free to use what you want.

We’re going to be using three objects for this application:

  1. MediumApplication – The NSApplication subclass
  2. MediumController – The controller
  3. MediumConfig – Our configuration class

Lets go ahead and ctrl-click on the classes item in the sidebar and select Add -> File …. We’ll then select the Objective-C class for each item.

With our files created we’re going to setup the application to use our MediumApplication as the Principle Class. The first thing we need to do is change the parent class of MediumApplication to be NSApplication instead of NSObject. That’s actually the only change we’ll need to make in MediumApplication.h. You should end up with something similar to:

#import <cocoa /Cocoa.h>

@interface MediumApplication : NSApplication

Now, in the Resourses section open the Info.plist file. Under the key NSPrincipleClass change the string value to be MediumApplication instead of NSApplication.

With that we’ve setup our MediumApplication class to be the main class in Principle Class for the application. We’ll now fill in some application code to handle processing key events so we can pull out the apple-f key press.

There are two methods we’ll implement in MediumApplication: - (void)sendEvent:(NSEvent *)ev and - (BOOL)handleKeyDownEvent:(NSEvent *)ev. The sendEvent will be overriding the parent class implemention. handleKeyDownEvent is a private helper method.

- (void)sendEvent:(NSEvent *)ev
    int handled = FALSE;
    if (([ev type] == NSKeyDown) && ([self handleKeyDownEvent:ev]))
        handled = TRUE;

    if (!handled)
        [super sendEvent:ev];

sendEvent is pretty simple. If we’re dealing with a NSKeyDown event we’ll pass the event to our handleKeyDownEvent method. If handleKeyDownEvent processes the event it will return TRUE. If this isn’t the case we’ll pass the event up to our parent, NSApplication in this case.

- (BOOL)handleKeyDownEvent:(NSEvent *)ev
    BOOL handled = FALSE;
    NSString *notice = nil;
    unichar key = [[ev charactersIgnoringModifiers] characterAtIndex:0];

    /* apple-f for fullscreen */
    if ((key == 0x66) && ([ev modifierFlags] & NSCommandKeyMask))
        notice = @"Medium/Notice/Fullscreen";

    if (notice)
        [[NSNotificationCenter defaultCenter]
        handled = TRUE;
    return handled;

When handleKeyDownEvent is called we’ll pull the key pressed out of the event with charactersIgnoringModifiers: and we’ll grab the first character that’s returned. If that character is 0x66 or f and the NSCommandKeyMask modifier is set in the event we’ve got our apple-f.

With that, if we’ve found something to send a notification about we’ll post it to the NSNotificationCenter for anyone that’s interesting to pick up on.

That’s it for our application class.

Moving on we’ll setup or configuration object, MediumConfig. MediumConfig is basically a wrapper around NSUserDefaults which allows me to get the defaults with function calls instead of having the keys everywhere in my application.

#import <cocoa /Cocoa.h>

@interface MediumConfig : NSObject
    NSUserDefaults *defaults;

+(MediumConfig *)instance;



-(void)windowDimensionsWidth:(int *)width height:(int *)height;
-(void)setWindowDimensionsWidth:(int)width height:(int)height;

The MediumConfig object is a singleton so we provide an instance: method to retrieve the object. The rest of the class are just the accessors for the different configuration variables we care about.

#import "MediumConfig.h"

#define MediumScreenKey @"Medium/Screen"
#define MediumFullscreenKey @"Medium/Fullscreen"
#define MediumWindowDimensionWidth @"Medium/Window/Dimension/Width"
#define MediumWindowDimensionHeight @"Medium/Window/Dimension/Height"

static MediumConfig *medium_config = nil;

@implementation MediumConfig

+(MediumConfig *)instance
    if (medium_config) return medium_config;
    return (medium_config = [[MediumConfig alloc] init]);

-(id) init
    if ((self = [super init]))
        NSMutableDictionary *defaultValues = [NSMutableDictionary dictionary];

        [defaultValues setObject:[NSNumber numberWithBool:FALSE]
        [defaultValues setObject:[NSNumber numberWithInt:0]

        /* setting these to -1 will cause the code to calcuate a good window size */
        [defaultValues setObject:[NSNumber numberWithInt:-1]
        [defaultValues setObject:[NSNumber numberWithInt:-1]

        [[NSUserDefaults standardUserDefaults] registerDefaults:defaultValues];

        defaults = [NSUserDefaults standardUserDefaults];
    return self;

    return [defaults integerForKey:MediumScreenKey];

    [defaults setInteger:screen forKey:MediumScreenKey];

    return [defaults boolForKey:MediumFullscreenKey];

    [defaults setBool:fullscreen forKey:MediumFullscreenKey];

-(void)windowDimensionsWidth:(int *)width height:(int *)height
    if (width) *width = [defaults integerForKey:MediumWindowDimensionWidth];
    if (height) *height = [defaults integerForKey:MediumWindowDimensionHeight];

-(void)setWindowDimensionsWidth:(int)width height:(int)height
    [defaults setInteger:width forKey:MediumWindowDimensionWidth];
    [defaults setInteger:height forKey:MediumWindowDimensionHeight];

The implementation is also pretty simple. We set a few default values into our user defaults hash and the accessors are just wrappers around the calls into the NSUserDefaults object. Nothing special to see here. Oh, as a side note, I’m allowing the user to pass a nil into the windowDimensionsWidth:height: method. I wasn’t sure if you’d always need both values and this just seemed easier in the long run.

Finally, onto the interesting bit, MediumController. We’re going to store references to the main window, a CGDisplayFadeReservationToken which I’ll discuss later and to our configuration.

#import <cocoa /Cocoa.h>
#import "MediumConfig.h"

@interface MediumController : NSObject
    NSWindow *win;
    CGDisplayFadeReservationToken tok;  /** Fade out/in token */

    MediumConfig *cfg;


With the header file created we’re going to do some work in Interface Builder. Double click on the MainMenu.nib file in the Resources section.

When interface launches you’ll see a few windows popup. One of which will be the control window (at least, that’s what I call it).

We don’t actually need the Window (window) item so we can click on that and hit delete. We’re going to be creating the window manually in the controller.

The next step is to create an instance of our MediumController in Interface Builder. This is done by dragging an Object controller from the library to our control window.

If the inspector isn’t open already click on the new object and press shift-apple-i. In the inspector press apple-6 to make sure you’re in the identity section. Set the Class field to MediumController.

In the control window ctrl-click on the File’s Owner object and drag the mouse over the Medium Controller object. You should have a popup appear. Click on the delegate entry. We’ll now receive notifications in our controller as the application changes state.

That’s it for our work in Interface Builder. Save your changes and close.

Given that MediumController is the large part of our code I’m going to go through it piece meal instead of giving a full listing.

#import "MediumController.h"

@interface MediumController (ControllerDelegates)
- (void)applicationDidFinishLaunching:(NSNotification *)notice;
- (void)applicationWillTerminate:(NSNotification *)notice;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app;

@interface MediumController (WindowDelegates)
- (void)windowDidResize:(NSNotification *)notice;

@interface MediumController (WindowManipulation)
- (void)fullscreenNotice:(NSNotification *)notice;
- (void)changeFullscreenState;
- (NSScreen *)screen;
- (void)fadeOut;
- (void)fadeIn;

We start by creating a bunch of method declarations. I like to group my methods into categories as I think it keeps things cleaner. As you can see we’re going to implement some Controller Delegates, Window Delegates and Window Manipulation functions.

@implementation MediumController

- (id)init
    if ((self = [super init]))
        cfg = [[MediumConfig instance] retain];

        /* catch the full screen events */
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self

    return self;


Our init: method is pretty simple. We grab, and _retain an instance of the MediumConfig singleton. We’ll be using this throughout our code later. We then add an observer for the Medium/Notice/Fullscreen event that we setup in our MediumApplication code. Whenever the given event is fired the fullscreenNotice: method will be executed.

#pragma mark Controller Delegates
@implementation MediumController (ControllerDelegates)

- (void)applicationDidFinishLaunching:(NSNotification *)notice
    NSRect screenFrame = [[self screen] frame];
    int width = -1, height = -1;

    [cfg windowDimensionsWidth:&width height:&height];
    if ((width < 0) || (height < 0))
        width = screenFrame.size.width * 0.75;
        height = screenFrame.size.height * 0.75;

    NSRect frame = NSMakeRect(((screenFrame.size.width - width) / 2),
                              ((screenFrame.size.height - height) / 2),
                              width, height);

    win = [[NSWindow alloc] initWithContentRect:frame
                styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
                   screen:[self screen]];
    [win setDelegate:self];
    [win setTitle:@"MediumFS"];

    /* XXX make this our real view */
    NSView *view = [[NSView alloc] initWithFrame:[win frame]];
    [win setContentView:view];

    if ([cfg fullscreen]) [self changeFullscreenState];

    /* do this after we've gone fullscreen */
    [win makeKeyAndOrderFront:self];

Once the application has launched we’ll, as the delegate, get a applicationDidFinishLaunching: notification. We’ll use this to create our main window by hand.

Our window will be set to 75% of the screen width and height if the user hasn’t already site a width and height. To do this we’ll first need to get the screen frame. We’ll see the screen method later, just rest assured it returns an NSScreen object we can work with. We’ll then check in our configuration if the user has set a width and height previously. If not, we use the screen frame to calculate the width and height.

With that done we’ll create the frame for our window with NSMakeRect. We do some simple calculations to place the window in the middle of the screen and use the width and height we calculated earlier.

We can now create our window. We use the frame we just calculated and apply a style mask of NSTitledWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask. This way we can have a title, can minimize the window and allow the user to resize the window. We set the backing to NSBackingStoredBuffered and the screen to the screen returned from the screen method.

With the window created we set the controller to be the delegate of the window. This way we can catch certain window events which we’ll see in a bit. setTitle: just sets the window title to @”MediumFS”.

Since I don’t actually have anything to display in this window I just create an NSView and set it as the content view of my window. You can add your actual views as needed.

With the window created and our content in place we check if we should be fullscreen by querying the MediumConfig object. If we should be fullscreen we transition by calling changeFullscreenState. With all that done we give the window the focus with makeKeyAndOrderFront:.

Phew, OK, that was a lot of work right off the bat. Don’t worry, things will get shorter from here on out.

- (void)applicationWillTerminate:(NSNotification *)notice
    if ([cfg fullscreen]) [self fadeOut];

    /* hide the window so it doesn't flicker on fade in */
    [win orderOut:nil];
    [win release];
    if ([cfg fullscreen]) [self fadeIn];

    [cfg release];

When the user hits apple-q or otherwise terminates the application our applicationWillTerminate notification will be triggered. We first check if we’re in fullscreen mode. If so, we fade the screen out with fadeOut. We then use orderOut: to hide the window. We don’t want it to flicker back into existence when we fade the screen back in. With the window hidden we can release it as we no longer need it. Then, as before, if we’re fullscreen we fade back in with fadeIn. We then release the config object as we’re done with it.

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
    return YES;


The final method in our controller delegates section is applicationShouldTerminateAfterLastWindowClosed we’re just returning a simple YES here so when our window gets closed the application will terminate. Nothing special, move along.

We’ll now move into the window delegate methods, of which there is only one.

#pragma mark Window Delegates
@implementation MediumController (WindowDelegates)

- (void)windowDidResize:(NSNotification *)notice
    NSRect frame = [win frame];
    [cfg setWindowDimensionsWidth:frame.size.width height:frame.size.height];


After the window has been resized the windowDidResize: notification will be sent. We use this to update the window width and height in MediumConfig so we can open the window to the same size on the next launch. Simple enough.

Our last set of functions I’m put under the name Window Manipulation.

#pragma mark Window Manipulation
@implementation MediumController (WindowManipulation)

- (void)fullscreenNotice:(NSNotification *)notice
    [self changeFullscreenState];
    [cfg setFullscreen:(![cfg fullscreen])];

As we saw above when the Medium/Notice/Fullscreen is sent it will trigger the fullscreenNotice: method. We simply call changeFullscreenState and invert the current fullscreen flag in MediumConfig. Short and sweet.

Finally we get to what everyone actually wants to see, the fullscreen method.

- (void)changeFullscreenState
    NSView *view = [win contentView];
    NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:
                            [NSNumber numberWithBool:NO], NSFullScreenModeAllScreens, nil];

    [self fadeOut];

    if ([view isInFullScreenMode])
        [view exitFullScreenModeWithOptions:opts];
        [view enterFullScreenMode:[self screen] withOptions:opts];

    [self fadeIn];

As I mentioned earlier fullscreening got a lot simpler at some point. First thing we do is grab our content view that we set into the main window. It’s on this view that we’ll be changing the fullscreen state.

We’re going to be passing a dictionary of options into the fullscreen methods. In this case I’m just setting one key into the dictionary, NSFullScreenModeAllScreens which I’m setting to NO as I only want to go fullscreen on one screen.

With that created we’re calling fadeOut to fade the screen to make a nice transition.

The screen is blanked and we’re posed to go fullscreen. Since this function is just switching back and forth the first thing we do is query the current fullscreen state of the view with isInFullScreenMode if this is TRUE we’ll be going back to windowed mode. Switching to windowed mode is as simple as calling exitFullScreenModeWithOptions: on our view. Similarly, going to fullscreen mode is as simple as calling enterFullScreenModel:withOptions: and passing in the screen we want to be fullscreen on. That’s it. Pretty simple eh?

With the fullscreen mode changed we’ll use fadeIn: to fade the screen back from black.

Three more helper methods and we’re done.

 * Return the screen that we should be displaying upon
- (NSScreen *)screen
    NSArray *screens = [NSScreen screens];

    /* if we've only got one screen then return it */
    if ([screens count] < = 1) return [NSScreen mainScreen];

    /* try to use the screen set by the user */
    int screen = [cfg screen];
    if (screen > [screens count]) screen = [screens count] - 1;
    else if (screen < 0) screen = 0;

    return [screens objectAtIndex:screen];

The screen method will return a screen to display on. This will get all the screens by calling [NSScreen screens]. Then, if we only have one screen, it’ll return that. Otherwise we check if the user has configured a screen to display on or return the first screen in the list.

The fade methods are both pretty similar.

- (void)fadeOut
    CGAcquireDisplayFadeReservation(25, &tok);
    CGDisplayFade(tok, 1.5, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0, 0, 0, TRUE);

- (void)fadeIn
    CGDisplayFade(tok, 1.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0, 0, 0, TRUE);


On fadeOut we acquire a reservation token and tell the screen to fade. We fade from kGCDisplayBlendNormal to kGCDisplayBlendSolidColor and provide the 0, 0, 0 black colour. fadeIn works in basically the opposite order. We fade from kCGDisplayBlendSolidColor to kCGDisplayBlendNormal and start from 0, 0, 0, black. Once faded we release our reservation token.

That, as they say, is that. If you build and run you should be able to hit apple-f to switch to and from fullscreen mode.

Cool, eh?

MediumFS contains the full source for the application.