| /* MacOS X Carbon specific logic for displaying the splash screen. */ | |
| #include "installOS.h" | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <fcntl.h> | |
| #include <ctype.h> | |
| #include <pwd.h> | |
| #include <stdlib.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #include <sys/ioctl.h> | |
| #include <unistd.h> | |
| #include <CoreServices/CoreServices.h> | |
| #include <Carbon/Carbon.h> | |
| #include "NgCommon.h" | |
| #include "NgImageData.h" | |
| #include "NgWinBMPFileFormat.h" | |
| #define startupJarName "startup.jar" | |
| #define APP_PACKAGE_PATTERN ".app/Contents/MacOS/" | |
| #define APP_PACKAGE "APP_PACKAGE" | |
| #define JAVAROOT "JAVAROOT" | |
| #define DEBUG 0 | |
| char *findCommand(char *command); | |
| char* getProgramDir(); | |
| static void debug(const char *fmt, ...); | |
| static void dumpArgs(char *tag, int argc, char* argv[]); | |
| static PixMapHandle loadBMPImage(const char *image); | |
| static void init(); | |
| static char *append(char *buffer, const char *s); | |
| static char *appendc(char *buffer, char c); | |
| static char *expandShell(char *arg, const char *appPackage, const char *javaRoot); | |
| static char *my_strcasestr(const char *big, const char *little); | |
| /* Global Variables */ | |
| char dirSeparator = '/'; | |
| char pathSeparator = ':'; | |
| char* consoleVM = "java"; | |
| char* defaultVM = "java"; | |
| char* shippedVMDir = "jre/bin/"; | |
| /* Define the window system arguments for the various Java VMs. */ | |
| static char* argVM_JAVA[] = { "-XstartOnFirstThread", NULL }; | |
| static int fgPid, jvmPid, jvmExitCode = 0; | |
| static FILE *fgConsoleLog; | |
| static char *fgAppPackagePath; | |
| static WindowRef window; | |
| static ControlRef progress = NULL, pane = NULL; | |
| static RGBColor foreground = {0xFFFF, 0xFFFF, 0xFFFF}; | |
| static CFStringRef string = NULL; | |
| static Rect messageRect = {0, 0, 0, 0}, progressRect = {0, 0, 0, 0}; | |
| static int value = 0, maximum = 0; | |
| extern int original_main(int argc, char* argv[]); | |
| int main( int argc, char* argv[] ) { | |
| SInt32 systemVersion= 0; | |
| if (Gestalt(gestaltSystemVersion, &systemVersion) == noErr) { | |
| systemVersion &= 0xffff; | |
| if (systemVersion < 0x1020) { | |
| displayMessage("Install launcher requires Jaguar (Mac OS X >= 10.2)"); | |
| return 0; | |
| } | |
| } | |
| fgConsoleLog= fopen("/dev/console", "w"); | |
| fgPid= getpid(); | |
| dumpArgs("start", argc, argv); | |
| if (argc > 1 && strncmp(argv[1], "-psn_", 5) == 0) { | |
| /* find path to application bundle (ignoring case) */ | |
| char *pos= my_strcasestr(argv[0], APP_PACKAGE_PATTERN); | |
| if (pos != NULL) { | |
| int l= pos-argv[0] + 4; // reserve space for ".app" | |
| fgAppPackagePath= malloc(l+1); | |
| strncpy(fgAppPackagePath, argv[0], l); | |
| fgAppPackagePath[l]= '\0'; // terminate result | |
| } | |
| /* Get the main bundle for the app */ | |
| CFBundleRef mainBundle= CFBundleGetMainBundle(); | |
| if (mainBundle != NULL) { | |
| /* Get an instance of the info plist.*/ | |
| CFDictionaryRef bundleInfoDict= CFBundleGetInfoDictionary(mainBundle); | |
| /* If we succeeded, look for our property. */ | |
| if (bundleInfoDict != NULL) { | |
| CFArrayRef ar= CFDictionaryGetValue(bundleInfoDict, CFSTR("RCPInstaller")); | |
| if (ar) { | |
| CFIndex size= CFArrayGetCount(ar); | |
| if (size > 0) { | |
| int i; | |
| char **old_argv= argv; | |
| argv= (char**) calloc(size+2, sizeof(char*)); | |
| argc= 0; | |
| argv[argc++]= old_argv[0]; | |
| for (i= 0; i < size; i++) { | |
| CFStringRef sr= (CFStringRef) CFArrayGetValueAtIndex (ar, i); | |
| CFIndex argStringSize= CFStringGetMaximumSizeForEncoding(CFStringGetLength(sr), kCFStringEncodingUTF8); | |
| char *s= malloc(argStringSize); | |
| if (CFStringGetCString(sr, s, argStringSize, kCFStringEncodingUTF8)) { | |
| argv[argc++]= expandShell(s, fgAppPackagePath, NULL); | |
| } else { | |
| fprintf(fgConsoleLog, "can't extract bytes\n"); | |
| } | |
| //free(s); | |
| } | |
| } | |
| } else { | |
| fprintf(fgConsoleLog, "no RCPInstall dict found\n"); | |
| } | |
| } else { | |
| fprintf(fgConsoleLog, "no bundle dict found\n"); | |
| } | |
| } else { | |
| fprintf(fgConsoleLog, "no bundle found\n"); | |
| } | |
| } | |
| int exitcode= original_main(argc, argv); | |
| debug("<<<< exit(%d)\n", exitcode); | |
| fclose(fgConsoleLog); | |
| return exitcode; | |
| } | |
| /* Display a Message */ | |
| void displayMessage(char *message) | |
| { | |
| CFStringRef inError, inDescription= NULL; | |
| /* try to break the message into a first sentence and the rest */ | |
| char *pos= strstr(message, ". "); | |
| if (pos != NULL) { | |
| char *to, *from, *buffer= calloc(pos-message+2, sizeof(char)); | |
| /* copy and replace line separators with blanks */ | |
| for (to= buffer, from= message; from <= pos; from++, to++) { | |
| char c= *from; | |
| if (c == '\n') c= ' '; | |
| *to= c; | |
| } | |
| inError= CFStringCreateWithCString(kCFAllocatorDefault, buffer, kCFStringEncodingASCII); | |
| free(buffer); | |
| inDescription= CFStringCreateWithCString(kCFAllocatorDefault, pos+2, kCFStringEncodingASCII); | |
| } else { | |
| inError= CFStringCreateWithCString(kCFAllocatorDefault, message, kCFStringEncodingASCII); | |
| } | |
| init(); | |
| DialogRef outAlert; | |
| OSStatus status= CreateStandardAlert(kAlertStopAlert, inError, inDescription, NULL, &outAlert); | |
| if (status == noErr) { | |
| DialogItemIndex outItemHit; | |
| RunStandardAlert(outAlert, NULL, &outItemHit); | |
| } else { | |
| debug("rcpinstall: displayMessage: %s\n", message); | |
| } | |
| CFRelease(inError); | |
| if (inDescription != NULL) | |
| CFRelease(inDescription); | |
| } | |
| static void debug(const char *fmt, ...) { | |
| #if DEBUG | |
| char buffer[200]; | |
| va_list ap; | |
| va_start(ap, fmt); | |
| fprintf(fgConsoleLog, "%05d: ", fgPid); | |
| vfprintf(fgConsoleLog, fmt, ap); | |
| va_end(ap); | |
| #endif | |
| } | |
| static void dumpArgs(char *tag, int argc, char* argv[]) { | |
| #if DEBUG | |
| int i; | |
| if (argc < 0) { | |
| argc= 0; | |
| for (i= 0; argv[i] != NULL; i++) | |
| argc++; | |
| } | |
| debug(">>>> %s:", tag); | |
| for (i= 0; i < argc && argv[i] != NULL; i++) | |
| fprintf(fgConsoleLog, " <%s>", argv[i]); | |
| fprintf(fgConsoleLog, "\n"); | |
| #endif | |
| } | |
| static void init() { | |
| static int initialized= 0; | |
| if (!initialized) { | |
| ProcessSerialNumber psn; | |
| if (GetCurrentProcess(&psn) == noErr) | |
| SetFrontProcess(&psn); | |
| ClearMenuBar(); | |
| initialized= true; | |
| } | |
| } | |
| /* Initialize Window System | |
| * | |
| * Initialize Carbon. | |
| */ | |
| void initWindowSystem( int* pArgc, char* argv[], int showSplash ) | |
| { | |
| char *homeDir = getProgramDir(); | |
| debug("install dir: %s\n", homeDir); | |
| if (homeDir != NULL) | |
| chdir(homeDir); | |
| if (showSplash) | |
| init(); | |
| } | |
| static void eventLoopTimerProc(EventLoopTimerRef inTimer, void *inUserData) { | |
| QuitApplicationEventLoop(); | |
| } | |
| static void detectExitTimerProc(EventLoopTimerRef inTimer, void *inUserData) { | |
| int exitCode = 0; | |
| if (waitpid(jvmPid, &exitCode, WNOHANG) != 0) { | |
| jvmExitCode = exitCode; | |
| QuitApplicationEventLoop(); | |
| } | |
| } | |
| static void invalidateWindow () { | |
| Rect rect; | |
| RgnHandle rgn; | |
| ControlRef root; | |
| rgn = NewRgn(); | |
| GetRootControl(window, &root); | |
| GetControlBounds(root, &rect); | |
| SetRectRgn(rgn, rect.left, rect.top, rect.right, rect.bottom); | |
| InvalWindowRgn (window, rgn); | |
| DisposeRgn(rgn); | |
| } | |
| static void readRect(char *str, Rect *rect) { | |
| int x, y, width, height; | |
| char *temp = str, *comma; | |
| comma = strchr(temp, ','); | |
| if (comma == NULL) return; | |
| comma[0] = 0; | |
| x = atoi(temp); | |
| temp = comma + 1; | |
| comma = strchr(temp, ','); | |
| if (comma == NULL) return; | |
| comma[0] = 0; | |
| y = atoi(temp); | |
| temp = comma + 1; | |
| comma = strchr(temp, ','); | |
| if (comma == NULL) return; | |
| comma[0] = 0; | |
| width = atoi(temp); | |
| temp = comma + 1; | |
| height = atoi(temp); | |
| rect->left = x; | |
| rect->top = y; | |
| rect->right = x + width; | |
| rect->bottom = y + height; | |
| } | |
| static void readColor(char *str, RGBColor *color) { | |
| int value = atoi(str); | |
| color->red = ((value & 0xFF0000) >> 16) * 0xFF; | |
| color->green = ((value & 0xFF00) >> 8) * 0xFF; | |
| color->blue = ((value & 0xFF) >> 0) * 0xFF; | |
| } | |
| static void readInput() { | |
| int available; | |
| FILE *fd = stdin; | |
| char *buffer = NULL, *equals = NULL, *end, *line; | |
| ioctl(fileno(fd), FIONREAD, &available); | |
| if (available <= 0) return; | |
| buffer = malloc(available + 1); | |
| available = fread(buffer, 1, available, fd); | |
| buffer[available] = 0; | |
| line = buffer; | |
| while (line != NULL) { | |
| end = strchr(line, '\n'); | |
| equals = strchr(line, '='); | |
| if (end != NULL) end[0] = 0; | |
| if (equals != NULL) { | |
| char *str = (char *)equals + 1; | |
| equals[0] = 0; | |
| if (strcmp(line, "maximum") == 0) { | |
| maximum = atoi(str); | |
| if (progress) { | |
| SetControl32BitMaximum (progress, maximum); | |
| } | |
| } else if (strcmp(line, "value") == 0) { | |
| value = atoi(str); | |
| if (progress) { | |
| SetControl32BitValue (progress, value); | |
| } | |
| } else if (strcmp(line, "progressRect") == 0) { | |
| readRect(str, &progressRect); | |
| if (progress) { | |
| SetControlBounds (progress, &progressRect); | |
| } | |
| } else if (strcmp(line, "messageRect") == 0) { | |
| readRect(str, &messageRect); | |
| invalidateWindow(); | |
| } else if (strcmp(line, "foreground") == 0) { | |
| readColor(str, &foreground); | |
| invalidateWindow(); | |
| } else if (strcmp(line, "message") == 0) { | |
| if (string != NULL) CFRelease(string); | |
| string = CFStringCreateWithBytes (kCFAllocatorDefault, (UInt8 *)str, strlen(str), CFStringGetSystemEncoding(), 1); | |
| invalidateWindow(); | |
| } | |
| } | |
| if (end != NULL) line = end + 1; | |
| else line = NULL; | |
| } | |
| free(buffer); | |
| } | |
| static void timerProc(EventLoopTimerRef inTimer, void *inUserData) { | |
| readInput(); | |
| if ( isEndSplash() ) | |
| { | |
| RemoveEventLoopTimer(inTimer); | |
| DisposeWindow(window); | |
| if (string != NULL) CFRelease(string); | |
| string = NULL; | |
| progress = pane = NULL; | |
| } | |
| } | |
| static OSStatus drawProc (EventHandlerCallRef eventHandlerCallRef, EventRef eventRef, PixMap * pixmap) { | |
| Rect rect; | |
| int result = CallNextEventHandler(eventHandlerCallRef, eventRef); | |
| GrafPtr port = GetWindowPort(window); | |
| SetPort(port); | |
| GetControlBounds(pane, &rect); | |
| CopyBits((BitMap*)pixmap, GetPortBitMapForCopyBits(port), &rect, &rect, srcCopy, NULL); | |
| if (string != NULL) { | |
| RGBForeColor(&foreground); | |
| DrawThemeTextBox(string, kThemeSmallSystemFont, kThemeStateActive, 1, &messageRect, teFlushLeft, NULL); | |
| } | |
| return result; | |
| } | |
| /* Show the Splash Window | |
| * | |
| * Create the splash window, load the bitmap and display the splash window. | |
| */ | |
| int showSplash( char* timeoutString, char* featureImage ) | |
| { | |
| Rect wRect; | |
| int w, h, deviceWidth, deviceHeight; | |
| PixMap *pm; | |
| PixMapHandle pixmap; | |
| EventTypeSpec draw = {kEventClassControl, kEventControlDraw}; | |
| debug("featureImage: %s\n", featureImage); | |
| init(); | |
| /* Determine the splash timeout value (in seconds). */ | |
| if (timeoutString != NULL && strlen(timeoutString) > 0) { | |
| int timeout; | |
| if (sscanf(timeoutString, "%d", &timeout) == 1) { | |
| InstallEventLoopTimer(GetMainEventLoop(), (EventTimerInterval) timeout, 0.0, NewEventLoopTimerUPP(eventLoopTimerProc), NULL, NULL); | |
| } | |
| } | |
| pixmap= loadBMPImage(featureImage); | |
| /* If the splash image data could not be loaded, return an error. */ | |
| if (pixmap == NULL) | |
| return ENOENT; | |
| pm= *pixmap; | |
| w= pm->bounds.right; | |
| h= pm->bounds.bottom; | |
| GetAvailableWindowPositioningBounds(GetMainDevice(), &wRect); | |
| deviceWidth= wRect.right - wRect.left; | |
| deviceHeight= wRect.bottom - wRect.top; | |
| wRect.left+= (deviceWidth-w)/2; | |
| wRect.top+= (deviceHeight-h)/3; | |
| wRect.right= wRect.left + w; | |
| wRect.bottom= wRect.top + h; | |
| CreateNewWindow(kModalWindowClass, kWindowStandardHandlerAttribute, &wRect, &window); | |
| if (window != NULL) { | |
| ControlRef root = NULL; | |
| CreateRootControl(window, &root); | |
| GetRootControl(window, &root); | |
| SetRect(&wRect, 0, 0, w, h); | |
| CreateUserPaneControl(window, &wRect, 0, &pane); | |
| EmbedControl(pane, root); | |
| CreateProgressBarControl(window, &progressRect, value, 0, maximum, 0, &progress); | |
| EmbedControl(progress, pane); | |
| InstallEventHandler(GetControlEventTarget(pane), (EventHandlerUPP)drawProc, 1, &draw, pm, NULL); | |
| readInput(); | |
| InstallEventLoopTimer (GetCurrentEventLoop (), 50 / 1000.0, 50 / 1000.0, NewEventLoopTimerUPP(timerProc), NULL, NULL); | |
| ShowWindow(window); | |
| // RunApplicationEventLoop(); | |
| // DisposeWindow(window); | |
| // if (string != NULL) CFRelease(string); | |
| // string = NULL; | |
| // progress = pane = NULL; | |
| } | |
| return 0; | |
| } | |
| /* Get the window system specific VM arguments */ | |
| char** getArgVM( char* vm ) | |
| { | |
| char** result; | |
| /* Use the default arguments for a standard Java VM */ | |
| result = argVM_JAVA; | |
| return result; | |
| } | |
| /* Start the Java VM | |
| * | |
| * This method is called to start the Java virtual machine and to wait until it | |
| * terminates. The function returns the exit code from the JVM. | |
| */ | |
| int startJavaVM( char* args[] ) | |
| { | |
| /* Create a child process for the JVM. */ | |
| pid_t pid= fork(); | |
| if (pid == 0) { | |
| dumpArgs("execv", -1, args); | |
| /* Child process ... start the JVM */ | |
| execv(args[0], args); | |
| /* The JVM would not start ... return error code to parent process. */ | |
| _exit(errno); | |
| } | |
| if (pid == -1) | |
| return errno; | |
| /* wait for it to terminate processing events */ | |
| jvmPid = pid; | |
| InstallEventLoopTimer(GetCurrentEventLoop (), 100 / 1000.0, 100 / 1000.0, NewEventLoopTimerUPP(detectExitTimerProc), NULL, NULL); | |
| RunApplicationEventLoop(); | |
| int exitCode = jvmExitCode; | |
| // wait(&exitCode); | |
| exitCode= ((exitCode & 0x00ff) == 0 ? (exitCode >> 8) : exitCode); | |
| return exitCode; | |
| } | |
| /** | |
| * loadBMPImage | |
| * Create a QuickDraw PixMap representing the given BMP file. | |
| * | |
| * bmpPathname: absolute path and name to the bmp file | |
| * | |
| * returned value: the PixMapHandle newly created if successful. 0 otherwise. | |
| */ | |
| static PixMapHandle loadBMPImage (const char *bmpPathname) { | |
| ng_stream_t in; | |
| ng_bitmap_image_t image; | |
| ng_err_t err= ERR_OK; | |
| PixMapHandle pixmap; | |
| PixMap *pm; | |
| NgInit(); | |
| if (NgStreamInit(&in, (char*) bmpPathname) != ERR_OK) { | |
| NgError(ERR_NG, "Error can't open BMP file"); | |
| return 0; | |
| } | |
| NgBitmapImageInit(&image); | |
| err= NgBmpDecoderReadImage (&in, &image); | |
| NgStreamClose(&in); | |
| if (err != ERR_OK) { | |
| NgBitmapImageFree(&image); | |
| return 0; | |
| } | |
| UBYTE4 srcDepth= NgBitmapImageBitCount(&image); | |
| if (srcDepth != 24) { /* We only support image depth of 24 bits */ | |
| NgBitmapImageFree(&image); | |
| NgError (ERR_NG, "Error unsupported depth - only support 24 bit"); | |
| return 0; | |
| } | |
| pixmap= NewPixMap(); | |
| if (pixmap == 0) { | |
| NgBitmapImageFree(&image); | |
| NgError(ERR_NG, "Error XCreatePixmap failed"); | |
| return 0; | |
| } | |
| pm= *pixmap; | |
| int width= (int)NgBitmapImageWidth(&image); | |
| int height= (int)NgBitmapImageHeight(&image); | |
| int rowBytes= width * 4; | |
| pm->bounds.right= width; | |
| pm->bounds.bottom= height; | |
| pm->rowBytes= rowBytes + 0x8000; | |
| pm->baseAddr= NewPtr(rowBytes * height); | |
| pm->pixelType= RGBDirect; | |
| pm->pixelSize= 32; | |
| pm->cmpCount= 3; | |
| pm->cmpSize= 8; | |
| /* 24 bit source to direct screen destination */ | |
| NgBitmapImageBlitDirectToDirect(NgBitmapImageImageData(&image), NgBitmapImageBytesPerRow(&image), width, height, | |
| (UBYTE1*)pm->baseAddr, pm->pixelSize, rowBytes, NgIsMSB(), | |
| 0xff0000, 0x00ff00, 0x0000ff); | |
| NgBitmapImageFree(&image); | |
| return pixmap; | |
| } | |
| /* | |
| * Expand $APP_PACKAGE, $JAVA_HOME, and does tilde expansion. | |
| A word beginning with an unquoted tilde character (~) is | |
| subject to tilde expansion. All the characters up to a | |
| slash (/) or the end of the word are treated as a username | |
| and are replaced with the user's home directory. If the | |
| username is missing (as in ~/foobar), the tilde is | |
| replaced with the value of the HOME variable (the current | |
| user's home directory). | |
| */ | |
| static char *expandShell(char *arg, const char *appPackage, const char *javaRoot) { | |
| if (index(arg, '~') == NULL && index(arg, '$') == NULL) | |
| return arg; | |
| char *buffer= strdup(""); | |
| char c, lastChar= ' '; | |
| const char *cp= arg; | |
| while ((c = *cp++) != 0) { | |
| if (isspace(lastChar) && c == '~') { | |
| char name[100], *dir= NULL; | |
| int j= 0; | |
| for (; (c = *cp) != 0; cp++) { | |
| if (! isalnum(c)) | |
| break; | |
| name[j++]= c; | |
| lastChar= c; | |
| } | |
| name[j]= '\0'; | |
| if (j > 0) { | |
| struct passwd *pw= getpwnam(name); | |
| if (pw != NULL) | |
| dir= pw->pw_dir; | |
| } else { | |
| dir= getenv("HOME"); | |
| } | |
| if (dir != NULL) | |
| buffer= append(buffer, dir); | |
| } else if (c == '$') { | |
| int l= strlen(APP_PACKAGE); | |
| if (appPackage != NULL && strncmp(cp, APP_PACKAGE, l) == 0) { | |
| cp+= l; | |
| buffer= append(buffer, appPackage); | |
| } else { | |
| int l= strlen(JAVAROOT); | |
| if (javaRoot != NULL && strncmp(cp, JAVAROOT, l) == 0) { | |
| cp+= l; | |
| buffer= append(buffer, javaRoot); | |
| } else { | |
| buffer= appendc(buffer, c); | |
| } | |
| } | |
| } else | |
| buffer= appendc(buffer, c); | |
| lastChar= c; | |
| } | |
| return buffer; | |
| } | |
| static char *my_strcasestr(const char *big, const char *little) { | |
| char *cp, *s, *t; | |
| for (cp= (char*) big; *cp; cp++) { | |
| for (s= cp, t= (char*) little; *s && *t; s++, t++) | |
| if (toupper(*s) != toupper(*t)) | |
| break; | |
| if (*t == '\0') | |
| return cp; | |
| } | |
| return NULL; | |
| } | |
| static char *append(char *buffer, const char *s) { | |
| int bl= strlen(buffer); | |
| int sl= strlen(s); | |
| buffer= realloc(buffer, bl+sl+1); | |
| strcpy(&buffer[bl], s); | |
| return buffer; | |
| } | |
| static char *appendc(char *buffer, char c) { | |
| int bl= strlen(buffer); | |
| buffer= realloc(buffer, bl+2); | |
| buffer[bl++]= c; | |
| buffer[bl]= '\0'; | |
| return buffer; | |
| } | |
| _TCHAR* getTempPath() | |
| { | |
| _TCHAR* buffer = malloc(MAX_PATH*sizeof(_TCHAR)); | |
| _TCHAR* tmp; | |
| tmp = _ttempnam(_T_INSTALL("/tmp"),_T_INSTALL("_RITMP")); | |
| _tcscpy(buffer,tmp); | |
| _tcscat(buffer,_T_INSTALL("/")); | |
| free(tmp); | |
| return buffer; | |
| } | |