Main menu:

Site search

 

September 2017
M T W T F S S
« Oct    
 123
45678910
11121314151617
18192021222324
252627282930  

Categories

Archives

Links:

Going fullscreen in Cocoa - Part II

In part I I described the the easiest solution of going full screen using the fade effect I wanted. However it had a couple of problems:

    The enterFullScreenMode and exitFullScreenMode methods as they are implemented in Leopard (OS X 10.5) hide the dock and menubar for you but no means to show the menubar again when the mouse hits the top of the screen (Snow Leopard fixes this).

    CGDisplayFade() unfortunately fades all displays which isn’t what I wanted (only the screen where the window is located should go through the fade in/out cycle).

Avoiding enterFullScreenMode and exitFullScreenMode

The trick I used is creating a new, borderless, window which will take over the full screen. I use the SetSystemUIMode() API to hide both the dock and menubar (and by specifying kUIOptionAutoShowMenuBar the menubar will appear again when the mouse hits the top of the screen). The SetSystemUIMode() function is part of the carbon framework. But don’t be afraid, this function is safe to use in a 64 bit application.

Remark: If you’re using a document based cocoa application you may consider creating a new NSWindowController for the borderless window.

- (IBAction)toggleFullscreen:(id)sender
{
    [self fadeOutDisplay:CGMainDisplayID()
                fadeTime:1.0];

    if ([self fullScreenWindow] != nil)
    {
        [[self fullScreenWindow]
            setFrame:[window contentRectForFrameRect:[window frame]]
             display:YES
             animate:NO];

        [window setContentView:[fullScreenWindow contentView]];
        [window makeKeyAndOrderFront:nil];

        [[self fullScreenWindow] close];
        [self setFullScreenWindow:nil];

        SetSystemUIMode(outMode, outOptions);
    }
    else
    {
        GetSystemUIMode(&outMode, &outOptions);
        SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar);

        [self setFullScreenWindow:[[NSWindow alloc]
           initWithContentRect:[window contentRectForFrameRect:[window frame]]
                     styleMask:NSBorderlessWindowMask
                       backing:NSBackingStoreBuffered
                         defer:YES
                        screen:[window screen]]];

        [[self fullScreenWindow] setContentView:[window contentView]];
        [[self fullScreenWindow] makeKeyAndOrderFront:nil];
        [[self fullScreenWindow] setFrame:[[self fullScreenWindow]
           frameRectForContentRect:[[window screen] frame]]
                                  display:YES
                                  animate:NO];
    } /* end if */

    [self fadeInDisplay:CGMainDisplayID()
               fadeTime:1.0];
} /* end toggleFullscreen */

Fading one screen only

Apple realized that fading all screens at once might not be desirable to everyone and presents a workaround for this which can be found at the same URL (in the “Fading a single display” section).

The workaround involves finding the gamma formula of the display that you want to fade and adjust it in small steps during the fading period. The code for this is shown here:

- (void)fadeOutDisplay:(CGDirectDisplayID)display
              fadeTime:(double)fadeTime
{
    int     fadeSteps       = 100;
    double  fadeInterval    = (fadeTime / (double) fadeSteps);

    int             step;
    double          fade;
    CGGammaValue    redMin,   redMax,   redGamma,
                    greenMin, greenMax, greenGamma,
                    blueMin,  blueMax,  blueGamma;

    CGGetDisplayTransferByFormula(display,
                                  &redMin,   &redMax,   &redGamma,
                                  &greenMin, &greenMax, &greenGamma,
                                  &blueMin,  &blueMax,  &blueGamma);
    for (step = 0; step < fadeSteps; step++)
    {
        fade = 1.0 - (step * fadeInterval);
        CGSetDisplayTransferByFormula(display,
                                      redMin,   fade*redMax,   redGamma,
                                      greenMin, fade*greenMax, greenGamma,
                                      blueMin,  fade*blueMax,  blueGamma);

        NSDate *nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval];
        [[NSRunLoop currentRunLoop] runUntilDate:nextDate];
    } /* end for */
} /* fadeOutDisplay */

- (void)fadeInDisplay:(CGDirectDisplayID)display
             fadeTime:(double)fadeTime
{
    int     fadeSteps       = 100;
    double  fadeInterval    = (fadeTime / (double) fadeSteps);

    int             step;
    double          fade;
    CGGammaValue    redMin,   redMax,   redGamma,
                    greenMin, greenMax, greenGamma,
                    blueMin,  blueMax,  blueGamma;

    CGGetDisplayTransferByFormula(display,
                                  &redMin,   &redMax,   &redGamma,
                                  &greenMin, &greenMax, &greenGamma,
                                  &blueMin,  &blueMax,  &blueGamma);
    for (step = 0; step < fadeSteps; step++)
    {
        fade = (step * fadeInterval);
        CGSetDisplayTransferByFormula(display,
                                      redMin,   fade*redMax,  redGamma,
                                      greenMin, fade*greenMax, greenGamma,
                                      blueMin,  fade*blueMax,  blueGamma);

        NSDate *nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval];
        [[NSRunLoop currentRunLoop] runUntilDate:nextDate];
    } /* end for */
} /* end fadeInDisplay */

Compared to the code Apple presented, I don’t use usleep() to halt the application until it’s time to change the gamma table with the next values in the for loop. The simple reason is that using usleep() blocks your application making it unresponsive to any GUI updates that might be needed (e.g. redrawing a view during the fading). Instead, I used the runUntilDate: method on the currentRunLoop so that the view can e.g. be redrawn while the screen is fading (with some changes the fade in and out methods could be made fully asynchronous and might be neater approach).

However, the least I can say is that I’m not happy with the results.

    First of all, it doesn’t seem to work reliably. It might work on one Mac but not on another. E.g. on my MBP it will happily fade to black but never return back to the original state. However, if I connect my external 23 inch ACD and use that as my main screen, I get the desired effect.

    Sharp eyes may notice that there are no error checks on the returned error codes of CGGetDisplayTransferByFormula(). Depending on the Mac you use, this function can return an kCGErrorNoneAvailable error. Weird enough you will get usable CGGammaValue values even when a kCGErrorNoneAvailable is returned. I wrote usable values, because they seem to be somewhat off. In my case, when I fade to black the screen starts a bit brighter before it gradually fades to black giving a kind of a flash effect. Not nice.

In part III I’ll present a different solution on how to obtain the desired fading effect.

Here is a link to the small test project I used: goFullScreen_part_II_viaWindow.zip

Write a comment

You need to login to post comments!