blob: c268d029fbeeb926c460ddf4dfe5681aa6df7936 [file] [log] [blame]
/* 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;
}