/** * QuartzDisplay.c - Show Boot Status via CoreGraphics * Wilfredo Sanchez | wsanchez@opensource.apple.com * $Apple$ ** * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ ** * Draws the startup screen, progress bar and status text using * CoreGraphics (Quartz). * * WARNING: This code uses private Core Graphics API. Private API is * subject to change at any time without notice, and it's use by * parties unknown to the Core Graphics authors is in no way supported. * If you borrow code from here for other use and it later breaks, you * have no basis for complaint. **/ #include #include #include #include #include #include #include #include #include #include "../Log.h" #include "../main.h" #include "QuartzProgressBar.h" /* * This is an opaque (void *) pointer in StarupDisplay.h. * The following definition is private to this file. */ typedef struct _CGS_DisplayContext { CGSConnectionID connectionID; CGSWindowID bootImageWindowID; CGContextRef bootImageContext; CGRect bootImageRect; CGSWindowID statusWindowID; CGSWindowID safeBootWindowID; CGContextRef statusContext; CGContextRef safeBootContext; CGContextRef progressContext; ProgressBarRef progressBar; } *DisplayContext; #define _StartupDisplay_C_ #include "../StartupDisplay.h" /* FIXME: Move all of this into a plist. */ /* Bounds for Boot Panel text specified by UE team, 2/7/2002 */ #define kTransparentColor 0.0,0.0,0.0,0.0 #define kAutoFillColor 0.4,0.4,0.6 #define kStatusAreaColor kTransparentColor #define kStatusAreaHeight 62.0 #define kStatusAreaWidth 350.0 #define kStatusAreaXOffset -3.0 #define kStatusTextFont "LucidaGrande" #define kStatusTextColor 0.0,0.0,0.0,1.0 #define kStatusTextFontSize 13.0 #define kStatusBarWidth 220.0 #define kStatusBarHeight 21.0 #define kStatusBarPosX ((kStatusAreaWidth-kStatusBarWidth)/2.0) #define kStatusBarPosY (kStatusAreaHeight-kStatusBarHeight) #define kSafeBootAreaHeight 38.0 #define kSafeBootAreaWidth 300.0 #define kSafeBootTextFont "LucidaGrande-Bold" #define kSafeBootTextColor 0.0,0.0,0.0,1.0 #define kSafeBootTextFontSize 12.0 #define kSafeBootXOffset 31.0 static CGSConnectionID _initDisplayConnection() { CGSConnectionID aConnectionID; CGError aErr; /* Using CGSServerPort() now to see if Window Server is running. CGSServerPort blocks for up to 10 seconds until Window Server is running or until timeout. */ CGSMachPort aCGSMachPort = CGSServerPort(); if (aCGSMachPort == kCGSMachPortNull) { debug(CFSTR("_initDisplayConnection: aCGSMachPort == NULL \n")); return(NULL); } /* Initialize Core Graphics */ if ((aErr = CGSInitialize()) != kCGErrorSuccess) { error(CFSTR("Core Graphics could not initialize. Failed with error %ld.\n"), aErr); return(NULL); } /* Connect to Window Server */ if ((CGSNewConnection(NULL, &aConnectionID)) != kCGSErrorSuccess) { return(NULL); } return(aConnectionID); } static int _closeDisplayConnection(CGSConnectionID aConnectionID) { if ((CGSReleaseConnection(aConnectionID)) != kCGSErrorSuccess) return(1); return(0); } static int _initWindowContext(CGSConnectionID aConnectionID, CGSWindowID* aWindowID, CGContextRef* aContext, CGRect aRectangle, CGSBoolean aShadowOption, CGSBoolean aTransparentOption) { /** * Create a window at (aPosX,aPosY) with size (aWidth,aHeight). **/ { CGSRegionObj aRegion; CGSNewRegionWithRect(&aRectangle, &aRegion); if (CGSNewWindow(aConnectionID, kCGSBufferedBackingType, 0.0, 0.0, aRegion, aWindowID) != kCGSErrorSuccess) { warning(CFSTR("CGSNewWindow failed.\n")); return(1); } if (aRegion) { (void) CGSReleaseRegion(aRegion); } /* Enable/disable the drop-shadow on this window */ { CGSValueObj aKey, aShadow; aKey = CGSCreateCString("HasShadow"); aShadow = CGSCreateBoolean(aShadowOption); CGSSetWindowProperty(aConnectionID, *aWindowID, aKey, aShadow); CGSReleaseGenericObj(aKey); CGSReleaseGenericObj(aShadow); } /* Set background color for window. */ CGSSetWindowAutofillColor(aConnectionID, *aWindowID, kAutoFillColor); } /* set up window context */ *aContext = CGWindowContextCreate(aConnectionID, *aWindowID, NULL); if (*aContext == NULL) { warning(CFSTR("CGWindowContextCreate failed.\n")); return(1); } CGContextErase(*aContext); /* Make the window transparent if requested. */ if (aTransparentOption == kCGSTrue) { CGRect aLocalRect = CGRectMake(0.0, 0.0, CGRectGetWidth(aRectangle), CGRectGetHeight(aRectangle)); /* Enable transparency for this window. */ CGSSetWindowOpacity(aConnectionID, *aWindowID, kCGSFalse); CGContextSaveGState (*aContext); CGContextSetCompositeOperation(*aContext, kCGCompositeCopy); CGContextSetRGBFillColor (*aContext, kTransparentColor); CGContextFillRect (*aContext, aLocalRect); CGContextRestoreGState (*aContext); CGContextFlush (*aContext); } return (0); } static int _initBootImageContext (DisplayContext aDisplayContext) { CGSConnectionID aConnectionID = aDisplayContext->connectionID; CGSWindowID* aWindowID = &aDisplayContext->bootImageWindowID; CGContextRef* aContext = &aDisplayContext->bootImageContext; CFStringRef aString; /* Determine if we're running Server or Desktop config */ /* This is the standard way to determine Server/Desktop */ CFDictionaryRef serverDict; Boolean isServerConfig = false; if ((serverDict = _CFCopyServerVersionDictionary()) != NULL) { CFRelease(serverDict); isServerConfig = true; } if (isServerConfig) { aString = CFSTR(kServerBootImagePath); } else { aString = CFSTR(kBootImagePath); } if (aConnectionID) { CGPDFDocumentRef anImageDoc; CFURLRef aURL; srandom ((time(NULL) & 0xffff) | ((getpid() & 0xffff) << 16)); aURL = CFURLCreateWithFileSystemPath(NULL, aString, kCFURLPOSIXPathStyle, FALSE); anImageDoc = CGPDFDocumentCreateWithURL(aURL); CFRelease(aURL); if (anImageDoc) { CGRect aDisplayRect; CGRect anImageRectCG; CGRect anImageRect; CGSRegionObj anImageRegion; int aPageCount = CGPDFDocumentGetNumberOfPages(anImageDoc); int aPage = (int)(random() % aPageCount) + 1; if (! (aPageCount > 0)) { error(CFSTR("Boot image has no pages.\n")); return(1); } CGSGetDisplayBounds((CGSDisplayNumber)0, &aDisplayRect); /* Bounds for display 0 */ anImageRectCG = CGPDFDocumentGetMediaBox(anImageDoc, aPage); /* Crap. We need to convert the CGRect into a CGSRect. */ anImageRect.origin.x = aDisplayRect.origin.x + ((aDisplayRect.size.width - anImageRectCG.size.width )/2.0); anImageRect.origin.y = aDisplayRect.origin.y + ((aDisplayRect.size.height - anImageRectCG.size.height)/2.0); anImageRect.size.width = anImageRectCG.size.width; anImageRect.size.height = anImageRectCG.size.height; CGSNewRegionWithRect(&anImageRect, &anImageRegion); CGSGetRegionBounds(anImageRegion, &anImageRect); if (anImageRegion) { (void) CGSReleaseRegion(anImageRegion); } aDisplayContext->bootImageRect = anImageRect; if (_initWindowContext(aConnectionID, aWindowID, aContext, anImageRect, kCGSTrue, kCGSFalse)) { error(CFSTR("Can't create window context.\n")); return(1); } CGContextDrawPDFDocument(*aContext, anImageRectCG, anImageDoc, aPage); CGPDFDocumentRelease(anImageDoc); CGContextFlush (*aContext); if (CGSOrderWindow(aConnectionID, *aWindowID, kCGSOrderAbove, (CGSWindowID)0) != kCGSErrorSuccess) { warning(CFSTR("CGSOrderWindow failed")); } return(0); } else warning(CFSTR("Can't find boot image %s."), kBootImagePath); } return(1); } static int _initStatusWindowContext (DisplayContext aDisplayContext) { CGSConnectionID aConnectionID = aDisplayContext->connectionID; CGSWindowID* aWindowID = &aDisplayContext->statusWindowID; CGContextRef* aContext = &aDisplayContext->statusContext; if (aConnectionID) { CGRect aDisplayRect; CGRect aTextFieldRect; CGSGetDisplayBounds(0, &aDisplayRect); /* Bounds for Display 0 */ aTextFieldRect = CGRectMake(aDisplayRect.origin.x + ((aDisplayRect.size.width -kStatusAreaWidth )/2.0), aDisplayRect.origin.y + ((aDisplayRect.size.height-kStatusAreaHeight)/2.0) + (aDisplayContext->bootImageRect.size.height/4.0) + kStatusAreaXOffset, kStatusAreaWidth, kStatusAreaHeight); if (_initWindowContext(aConnectionID, aWindowID, aContext, aTextFieldRect, kCGSFalse, kCGSTrue)) { error(CFSTR("Can't create window context.\n")); return(1); } } return(0); } static int _initSafeBootWindowContext (DisplayContext aDisplayContext) { CGSConnectionID aConnectionID = aDisplayContext->connectionID; CGSWindowID* aWindowID = &aDisplayContext->safeBootWindowID; CGContextRef* aContext = &aDisplayContext->safeBootContext; if (aConnectionID) { CGRect aDisplayRect; CGRect aTextFieldRect; CGSGetDisplayBounds(0, &aDisplayRect); /* Bounds for Display 0 */ aTextFieldRect = CGRectMake(aDisplayRect.origin.x + ((aDisplayRect.size.width -kSafeBootAreaWidth )/2.0), aDisplayRect.origin.y + ((aDisplayRect.size.height-kSafeBootAreaHeight)/2.0) + (aDisplayContext->bootImageRect.size.height/4.0) + kSafeBootXOffset, kSafeBootAreaWidth, kSafeBootAreaHeight); if (_initWindowContext(aConnectionID, aWindowID, aContext, aTextFieldRect, kCGSFalse, kCGSTrue)) { error(CFSTR("Can't create window context.")); return(1); } } return(0); } DisplayContext _initDisplayContext() { DisplayContext aContext = (DisplayContext)malloc(sizeof(struct _CGS_DisplayContext)); if (NULL == aContext) { if (gDebugFlag) { error(CFSTR("Initializing Quartz display error: could not allocate display context data.\n")); } return (NULL); } aContext->connectionID = _initDisplayConnection(); aContext->bootImageWindowID = (CGSWindowID )0; aContext->statusWindowID = (CGSWindowID )0; aContext->safeBootWindowID= (CGSWindowID )0; aContext->bootImageContext= (CGContextRef )0; aContext->statusContext = (CGContextRef )0; aContext->progressContext = (CGContextRef )0; aContext->safeBootContext = (CGContextRef )0; aContext->progressBar = (ProgressBarRef)NULL; _initBootImageContext (aContext); _initStatusWindowContext (aContext); _initSafeBootWindowContext (aContext); /* set up window context */ aContext->progressContext = CGWindowContextCreate(aContext->connectionID, aContext->statusWindowID, NULL); if (aContext->progressContext == NULL) { warning(CFSTR("CGWindowContextCreate failed.\n")); return(NULL); } if (CGSOrderWindow(aContext->connectionID, aContext->statusWindowID, kCGSOrderAbove, (CGSWindowID)0) != kCGSErrorSuccess) { warning(CFSTR("CGSOrderWindow failed.\n")); } return(aContext); } void _freeDisplayContext (DisplayContext aContext) { if (aContext) { if (gDebugFlag) debug(CFSTR("Deallocating Quartz display context.\n")); if (aContext->progressBar) ProgressBarFree(aContext->progressBar); if (aContext->safeBootContext ) CGContextRelease(aContext->safeBootContext ); if (aContext->statusContext ) CGContextRelease(aContext->statusContext ); if (aContext->bootImageContext) CGContextRelease(aContext->bootImageContext); if (aContext->progressContext ) CGContextRelease(aContext->progressContext ); if (aContext->connectionID) _closeDisplayConnection(aContext->connectionID); free(aContext); } } int _displayStatus (DisplayContext aDisplayContext, CFStringRef aMessage) { if (aDisplayContext) { CGSConnectionID aConnectionID = aDisplayContext->connectionID; CGSWindowID aWindowID = aDisplayContext->statusWindowID; CGContextRef aContext = aDisplayContext->statusContext; if (aConnectionID && aWindowID && aContext) { CGSDisableUpdate(aConnectionID); /** * Erase the status area. **/ { CGRect aRectangle = CGRectMake(0.0, 0.0, kStatusAreaWidth, kStatusAreaHeight - kStatusBarHeight); CGContextSaveGState (aContext); CGContextSetCompositeOperation (aContext, kCGCompositeCopy); CGContextSetRGBFillColor (aContext, kStatusAreaColor); CGContextFillRect (aContext, aRectangle); CGContextRestoreGState (aContext); } /** * Draw text. **/ if (aMessage) { int aUnitsPerEm; int anIterator; float aScale; float aStringWidth = 0.0; char* aLanguage = getenv("LANGUAGE"); char* aFontName = kStatusTextFont; /* Allocate mem for character, glyph and advance arrays. */ CFIndex aMessageLength = CFStringGetLength(aMessage); UniChar* aCharacters = (UniChar*)malloc(aMessageLength * sizeof(UniChar)); CGGlyph* aGlyphs = (CGGlyph*)malloc(aMessageLength * sizeof(CGGlyph)); int* anAdvances = (int* )malloc(aMessageLength * sizeof(int )); if (aLanguage) { if (!strcmp(aLanguage, "Japanese")) aFontName = "HiraKakuPro-W3"; else if (!strcmp(aLanguage, "zh_CN") || !strcmp(aLanguage, "SimpChinese")) aFontName = "SIL-Hei-Med-Jian"; else if (!strcmp(aLanguage, "zh_TW") || !strcmp(aLanguage, "TradChinese")) aFontName = "LiGothicMed"; else if (!strcmp(aLanguage, "ko") || !strcmp(aLanguage,"Korean")) aFontName = "AppleGothic"; } /* Set up the context. */ CGContextSaveGState (aContext); CGContextSetRGBFillColor (aContext, kStatusTextColor); CGContextSelectFont (aContext, aFontName, kStatusTextFontSize, kCGEncodingMacRoman); /* Get the characters, glyphs and advances and calculate aStringWidth. */ CFStringGetCharacters (aMessage, CFRangeMake(0, aMessageLength), aCharacters); CGFontGetGlyphsForUnicodes (CGContextGetFont(aContext), aCharacters, aGlyphs, aMessageLength); CGFontGetGlyphAdvances (CGContextGetFont(aContext), aGlyphs, aMessageLength, anAdvances); aUnitsPerEm = CGFontGetUnitsPerEm(CGContextGetFont(aContext)); aScale = CGContextGetFontSize(aContext); /* Calculate our length. */ for (anIterator = 0; anIterator < aMessageLength; ++anIterator) aStringWidth += anAdvances[anIterator] * aScale / aUnitsPerEm; /* Finally - display glyphs centered in status area. */ CGContextShowGlyphsAtPoint (aContext, (kStatusAreaWidth - aStringWidth) /2.0, (2.0 + (kStatusAreaHeight/2.0) - kStatusTextFontSize) / 2.0, aGlyphs, aMessageLength); /* Restore the context and free our buffers. */ CGContextRestoreGState(aContext); free(aCharacters); free(aGlyphs ); free(anAdvances ); } /** * Flush. **/ CGSReenableUpdate (aConnectionID); CGContextFlush (aContext); return(0); } } return(1); } int _displayProgress (DisplayContext aDisplayContext, float aPercentage) { if (aDisplayContext) { CGSConnectionID aConnectionID = aDisplayContext->connectionID; CGSWindowID aWindowID = aDisplayContext->statusWindowID; CGContextRef aContext = aDisplayContext->statusContext; if (aConnectionID && aWindowID && aContext) { CGSDisableUpdate(aConnectionID); /** * Draw status bar. **/ if (!aDisplayContext->progressBar) { aDisplayContext->progressBar = ProgressBarCreate(aDisplayContext->progressContext, kStatusBarPosX, kStatusBarPosY, kStatusBarWidth); } if (aPercentage < 0.0) aPercentage = 0.0; if (aPercentage > 1.0) aPercentage = 1.0; ProgressBarSetPercent (aDisplayContext->progressBar, aPercentage); /** * Flush. **/ CGSReenableUpdate (aConnectionID); CGContextFlush (aContext); return(0); } } return(1); } int _displaySafeBootMsg (DisplayContext aDisplayContext, CFStringRef aMessage) { if (aDisplayContext) { CGSConnectionID aConnectionID = aDisplayContext->connectionID; CGSWindowID aWindowID = aDisplayContext->safeBootWindowID; CGContextRef aContext = aDisplayContext->safeBootContext; if (aConnectionID && aWindowID && aContext) { CGSDisableUpdate(aConnectionID); if (CGSOrderWindow(aConnectionID, aWindowID, kCGSOrderAbove, (CGSWindowID)0) != kCGSErrorSuccess) { warning(CFSTR("CGSOrderWindow failed.\n")); } /** * Erase the status area. **/ { CGRect aRectangle = CGRectMake(0.0, 0.0, kSafeBootAreaWidth, kSafeBootAreaHeight); CGContextSaveGState (aContext); CGContextSetCompositeOperation (aContext, kCGCompositeCopy); CGContextSetRGBFillColor (aContext, kStatusAreaColor); CGContextFillRect (aContext, aRectangle); CGContextRestoreGState (aContext); } /** * Draw text. **/ if (aMessage) { int aUnitsPerEm; int anIterator; float aScale; float aStringWidth = 0.0; char* aLanguage = getenv("LANGUAGE"); char* aFontName = kSafeBootTextFont; /* Allocate mem for character, glyph and advance arrays. */ CFIndex aMessageLength = CFStringGetLength(aMessage); UniChar* aCharacters = (UniChar*)malloc(aMessageLength * sizeof(UniChar)); CGGlyph* aGlyphs = (CGGlyph*)malloc(aMessageLength * sizeof(CGGlyph)); int* anAdvances = (int* )malloc(aMessageLength * sizeof(int )); if (aLanguage) { if (!strcmp(aLanguage, "Japanese")) aFontName = "HiraKakuPro-W3"; else if (!strcmp(aLanguage, "zh_CN") || !strcmp(aLanguage, "SimpChinese")) aFontName = "SIL-Hei-Med-Jian"; else if (!strcmp(aLanguage, "zh_TW") || !strcmp(aLanguage, "TradChinese")) aFontName = "LiGothicMed"; else if (!strcmp(aLanguage, "ko") || !strcmp(aLanguage,"Korean")) aFontName = "AppleGothic"; } /* Set up the context. */ CGContextSaveGState (aContext); CGContextSetRGBFillColor (aContext, kSafeBootTextColor); CGContextSelectFont (aContext, aFontName, kSafeBootTextFontSize, kCGEncodingMacRoman); /* Get the characters, glyphs and advances and calculate aStringWidth. */ CFStringGetCharacters (aMessage, CFRangeMake(0, aMessageLength), aCharacters); CGFontGetGlyphsForUnicodes (CGContextGetFont(aContext), aCharacters, aGlyphs, aMessageLength); CGFontGetGlyphAdvances (CGContextGetFont(aContext), aGlyphs, aMessageLength, anAdvances); aUnitsPerEm = CGFontGetUnitsPerEm(CGContextGetFont(aContext)); aScale = CGContextGetFontSize(aContext); /* Calculate our length. */ for (anIterator = 0; anIterator < aMessageLength; ++anIterator) aStringWidth += anAdvances[anIterator] * aScale / aUnitsPerEm; /* Finally - display glyphs centered in status area. */ CGContextShowGlyphsAtPoint (aContext, (kSafeBootAreaWidth - aStringWidth) /2.0, (2.0 + (kSafeBootAreaHeight/2.0) - kSafeBootTextFontSize) / 2.0, aGlyphs, aMessageLength); /* Restore the context and free our buffers. */ CGContextRestoreGState(aContext); free(aCharacters); free(aGlyphs ); free(anAdvances ); } /** * Flush. **/ CGSReenableUpdate (aConnectionID); CGContextFlush (aContext); return(0); } } return(1); }