ScreenSnapshot/ScreenSnapshotAppDelegate.m
/* |
File: ScreenSnapshotAppDelegate.m |
Abstract: |
A UIApplication delegate class. Uses Quartz Display Services to obtain a list |
of all connected displays. Installs a callback function that's invoked whenever |
the configuration of a local display is changed. When the user selects a display |
item from the 'Capture' menu, a screen snapshot image is obtained and displayed |
in a new document window. |
Version: 1.0 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2011 Apple Inc. All Rights Reserved. |
*/ |
#import "ScreenSnapshotAppDelegate.h" |
// DisplayRegisterReconfigurationCallback is a client-supplied callback function that’s invoked |
// whenever the configuration of a local display is changed. Applications who want to register |
// for notifications of display changes would use CGDisplayRegisterReconfigurationCallback |
static void DisplayRegisterReconfigurationCallback (CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo) |
{ |
ScreenSnapshotAppDelegate * snapshotDelegateObject = (ScreenSnapshotAppDelegate*)userInfo; |
static BOOL DisplayConfigurationChanged = NO; |
// Before display reconfiguration, this callback fires to inform |
// applications of a pending configuration change. The callback runs |
// once for each on-line display. The flags passed in are set to |
// kCGDisplayBeginConfigurationFlag. This callback does not |
// carry other per-display information, as details of how a |
// reconfiguration affects a particular device rely on device-specific |
// behaviors which may not be exposed by a device driver. |
// |
// After display reconfiguration, at the time the callback function |
// is invoked, all display state reported by CoreGraphics, QuickDraw, |
// and the Carbon Display Manager API will be up to date. This callback |
// runs after the Carbon Display Manager notification callbacks. |
// The callback runs once for each added, removed, and currently |
// on-line display. Note that in the case of removed displays, calls into |
// the CoreGraphics API with the removed display ID will fail. |
// Because the callback is called for each display I use DisplayConfigurationChanged to |
// make sure we only disable the menu to change displays once and then refresh it only once. |
if(flags == kCGDisplayBeginConfigurationFlag) |
{ |
if(DisplayConfigurationChanged == NO) |
{ |
[snapshotDelegateObject disableUI]; |
DisplayConfigurationChanged = YES; |
} |
} |
else if(DisplayConfigurationChanged == YES) |
{ |
[snapshotDelegateObject enableUI]; |
[snapshotDelegateObject interrogateHardware]; |
DisplayConfigurationChanged = NO; |
} |
} |
@implementation ScreenSnapshotAppDelegate |
#pragma mark NSApplicationDelegate |
// don't want an untitled document opened upon program launch |
// so return NO here |
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender |
{ |
return NO; |
} |
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification |
{ |
/* Save the shared NSDocumentController for use later. */ |
documentController = [[NSDocumentController sharedDocumentController] retain]; |
displays = nil; |
/* Populate the Capture menu with a list of displays by iterating over all of the displays. */ |
[self interrogateHardware]; |
// Applications who want to register for notifications of display changes would use |
// CGDisplayRegisterReconfigurationCallback |
// |
// Display changes are reported via a callback mechanism. |
// |
// Callbacks are invoked when the app is listening for events, |
// on the event processing thread, or from within the display |
// reconfiguration function when in the program that is driving the |
// reconfiguration. |
DisplayRegistrationCallBackSuccessful = NO; // Hasn't been tried yet. |
CGError err = CGDisplayRegisterReconfigurationCallback(DisplayRegisterReconfigurationCallback,self); |
if(err == kCGErrorSuccess) |
{ |
DisplayRegistrationCallBackSuccessful = YES; |
} |
} |
-(void) dealloc |
{ |
// CGDisplayRemoveReconfigurationCallback Removes the registration of a callback function that’s invoked |
// whenever a local display is reconfigured. We only remove the registration if it was successful in the first place. |
if(CGDisplayRemoveReconfigurationCallback != NULL && DisplayRegistrationCallBackSuccessful == YES) |
{ |
CGDisplayRemoveReconfigurationCallback(DisplayRegisterReconfigurationCallback, self); |
} |
[captureMenuItem release]; |
if(displays != nil) |
{ |
free(displays); |
} |
[super dealloc]; |
} |
#pragma mark Display routines |
/* |
A display item was selected from the Capture menu. This takes a |
a snapshot image of the screen and creates a new document window |
with the image. |
*/ |
- (IBAction)selectDisplayItem:(id)sender |
{ |
NSMenuItem *menuItem = (NSMenuItem *)sender; |
/* Get the index for the chosen display from the CGDirectDisplayID array. */ |
NSInteger displaysIndex = [menuItem tag]; |
/* Make a snapshot image of the current display. */ |
CGImageRef image = CGDisplayCreateImage(displays[displaysIndex]); |
NSError *error = nil; |
/* Create a new document. */ |
ImageDocument *newDocument = [documentController openUntitledDocumentAndDisplay:YES error:&error]; |
if (newDocument) |
{ |
/* Save the CGImageRef with the document. */ |
[newDocument setCGImage:image]; |
} |
else |
{ |
/* Display the error. */ |
NSAlert *alert = [NSAlert alertWithError:error]; |
[alert runModal]; |
} |
if (image) |
{ |
CFRelease(image); |
} |
} |
/* Get the localized name of a display, given the display ID. */ |
-(NSString *)displayNameFromDisplayID:(CGDirectDisplayID)displayID |
{ |
NSString *displayProductName = nil; |
/* Get a CFDictionary with a key for the preferred name of the display. */ |
NSDictionary *displayInfo = (NSDictionary *)IODisplayCreateInfoDictionary(CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName); |
/* Retrieve the display product name. */ |
NSDictionary *localizedNames = [displayInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]]; |
/* Use the first name. */ |
if ([localizedNames count] > 0) |
{ |
displayProductName = [[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] retain]; |
} |
[displayInfo release]; |
return [displayProductName autorelease]; |
} |
/* Populate the Capture menu with a list of displays by iterating over all of the displays. */ |
-(void)interrogateHardware |
{ |
CGError err = CGDisplayNoErr; |
CGDisplayCount dspCount = 0; |
/* How many active displays do we have? */ |
err = CGGetActiveDisplayList(0, NULL, &dspCount); |
/* If we are getting an error here then their won't be much to display. */ |
if(err != CGDisplayNoErr) |
{ |
return; |
} |
/* Maybe this isn't the first time though this function. */ |
if(displays != nil) |
{ |
free(displays); |
} |
/* Allocate enough memory to hold all the display IDs we have. */ |
displays = calloc((size_t)dspCount, sizeof(CGDirectDisplayID)); |
// Get the list of active displays |
err = CGGetActiveDisplayList(dspCount, |
displays, |
&dspCount); |
/* More error-checking here. */ |
if(err != CGDisplayNoErr) |
{ |
NSLog(@"Could not get active display list (%d)\n", err); |
return; |
} |
/* Create the 'Capture Screen' menu. */ |
NSMenu *captureMenu = [[NSMenu alloc] initWithTitle:@"Capture Screen"]; |
int i; |
/* Now we iterate through them. */ |
for(i = 0; i < dspCount; i++) |
{ |
/* Get display name for the selected display. */ |
NSString* name = [self displayNameFromDisplayID:displays[i]]; |
/* Create new menu item for the display. */ |
NSMenuItem *displayMenuItem = [[NSMenuItem alloc] initWithTitle:name action:@selector(selectDisplayItem:) keyEquivalent:@""]; |
/* Save display index with the menu item. That way, when it is selected we can easily retrieve |
the display ID from the displays array. */ |
[displayMenuItem setTag:i]; |
/* Add the display menu item to the menu. */ |
[captureMenu addItem:displayMenuItem]; |
[displayMenuItem release]; |
} |
/* Set the display menu items as a submenu of the Capture menu. */ |
[captureMenuItem setSubmenu:captureMenu]; |
[captureMenu release]; |
} |
#pragma mark Menus |
/* Disable the Capture Screen menu. */ |
-(void) disableUI |
{ |
[captureMenuItem setEnabled:NO]; |
} |
/* Enable the Capture Screen menu. */ |
-(void) enableUI |
{ |
[captureMenuItem setEnabled:YES]; |
} |
@end |
Copyright © 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-07-19