Code

* packaging/macosx/ScriptExec/main.c: Update ScriptExec with upstream fixes
[inkscape.git] / packaging / macosx / ScriptExec / main.c
index 89b07ddd0ed3a7bbd2421132de1afb4f45627c58..2c4e8f301ecde9123ccb7c55c0bc34c56df985f2 100644 (file)
@@ -3,6 +3,10 @@
         This is the executable that goes into Platypus apps
     Copyright (C) 2003 Sveinbjorn Thordarson <sveinbt@hi.is>
 
+    With modifications by Aaron Voisine for gimp.app
+    With modifications by Marianne gagnon for Wilber-loves-apple
+    With modifications by Michael Wybrow for Inkscape.app
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
 
 */
 
+/*
+ * This app laucher basically takes care of:
+ * - launching Inkscape and X11 when double-clicked 
+ * - bringing X11 to the top when its icon is clicked in the dock (via a small applescript)
+ * - catch file dropped on icon events (and double-clicked gimp documents) and notify gimp.
+ * - catch quit events performed outside gimp, e.g. on the dock icon.
+ */
+
 ///////////////////////////////////////
 // Includes
 ///////////////////////////////////////    
@@ -37,6 +49,7 @@
 #include <unistd.h>
 #include <sys/wait.h>
 #include <pthread.h>
+#include <stdio.h>
 
 ///////////////////////////////////////
 // Definitions
@@ -88,6 +101,14 @@ static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall,
                                  EventRef theEvent, void *userData);
 static OSStatus FCCacheFailedHandler(EventHandlerCallRef theHandlerCall,
                                  EventRef theEvent, void *userData);
+static OSErr AppReopenAppAEHandler(const AppleEvent *theAppleEvent,
+                                   AppleEvent *reply, long refCon);
+
+static OSStatus CompileAppleScript(const void* text, long textLength,
+                                  AEDesc *resultData);
+static OSStatus SimpleCompileAppleScript(const char* theScript);
+static void runScript();
+
 ///////////////////////////////////////
 // Globals
 ///////////////////////////////////////    
@@ -136,6 +157,11 @@ int main(int argc, char* argv[])
     err += AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
                                  NewAEEventHandlerUPP(AppOpenAppAEHandler),
                                  0, false);
+    
+    err += AEInstallEventHandler(kCoreEventClass, kAEReopenApplication,
+                                 NewAEEventHandlerUPP(AppReopenAppAEHandler),
+                                 0, false);
+    
     err += InstallEventHandler(GetApplicationEventTarget(),
                                NewEventHandlerUPP(X11FailedHandler), 1,
                                &X11events, NULL, NULL);
@@ -152,6 +178,9 @@ int main(int argc, char* argv[])
     
     GetParameters(); //load data from files containing exec settings
 
+    // compile "icon clicked" script so it's ready to execute
+    SimpleCompileAppleScript("tell application \"X11\" to activate");
+    
     RunApplicationEventLoop(); //Run the event loop
     return 0;
 }
@@ -159,8 +188,45 @@ int main(int argc, char* argv[])
 #pragma mark -
 
 
+static void RequestUserAttention(void)
+{
+    NMRecPtr notificationRequest = (NMRecPtr) NewPtr(sizeof(NMRec));
+
+    memset(notificationRequest, 0, sizeof(*notificationRequest));
+    notificationRequest->qType = nmType;
+    notificationRequest->nmMark = 1;
+    notificationRequest->nmIcon = 0;
+    notificationRequest->nmSound = 0;
+    notificationRequest->nmStr = NULL;
+    notificationRequest->nmResp = NULL;
+
+    verify_noerr(NMInstall(notificationRequest));
+}
+
+
+static void ShowFirstStartWarningDialog(void)
+{
+    SInt16 itemHit;
+
+    AlertStdAlertParamRec params;
+    params.movable = true;
+    params.helpButton = false;
+    params.filterProc = NULL;
+    params.defaultText = (void *) kAlertDefaultOKText;
+    params.cancelText = NULL;
+    params.otherText = NULL;
+    params.defaultButton = kAlertStdAlertOKButton;
+    params.cancelButton = kAlertStdAlertCancelButton;
+    params.position = kWindowDefaultPosition;
+
+    StandardAlert(kAlertNoteAlert, "\pInkscape on Mac OS X",
+            "\pWhile Inkscape is open, its windows can be displayed or hidden by displaying or hiding the X11 application.\n\nThe first time this version of Inkscape is run it may take several minutes before the main window is displayed while font caches are built.",
+            &params, &itemHit);
+}
+
+
 //////////////////////////////////
-// Handler for when X11 fails to start
+// Handler for when fontconfig caches need to be generated
 //////////////////////////////////
 static OSStatus FCCacheFailedHandler(EventHandlerCallRef theHandlerCall, 
                                  EventRef theEvent, void *userData)
@@ -168,51 +234,53 @@ static OSStatus FCCacheFailedHandler(EventHandlerCallRef theHandlerCall,
 
     pthread_join(tid, NULL);
     if (odtid) pthread_join(odtid, NULL);
-       SInt16 itemHit;
 
-       AlertStdAlertParamRec params;
-       params.movable = true;
-       params.helpButton = false;
-       params.filterProc = NULL;
-       params.defaultText = "\pRun fc-cache";
-       params.cancelText = "\pIgnore";
-       params.otherText = NULL;
-       params.defaultButton = kAlertStdAlertOKButton;
-       params.cancelButton = kAlertStdAlertCancelButton;
-       params.position = kWindowDefaultPosition;
+    // Bounce Inkscape Dock icon
+    RequestUserAttention();
+    // Need to show warning to the user, then carry on.
+    ShowFirstStartWarningDialog();
 
-       StandardAlert(kAlertStopAlert, "\pFont caches may need to be updated",
-                       "\pThere is a problem on OS X 10.4.x where X11 installation does not always generate the necessary fontconfig caches.  This can be corrected by running /usr/X11R6/bin/fc-cache as root.\n\nThis may take some time.  Please do not close Inkscape.",
-                       &params, &itemHit);
-    
-       if (itemHit == kAlertStdAlertOKButton)
-       {
-               OSStatus err = FixFCCache();
+    // Note that we've seen the warning.
+    system("test -d \"$HOME/.inkscape\" || mkdir \"$HOME/.inkscape\"; "
+           "touch \"$HOME/.inkscape/.fccache-new\"");
+    // Rerun now.
+    OSErr err = ExecuteScript(scriptPath, &pid);
+    ExitToShell();
 
-               if (err == errAuthorizationSuccess)
-               {
-                       params.defaultText = (void *) kAlertDefaultOKText;
-                       params.cancelText = NULL;
+    return noErr;
+}
 
-                       StandardAlert(kAlertNoteAlert, "\pFont caches have been updated",
-                                       "\pPlease re-run Inkscape.", &params, &itemHit);
-                       system("test -d $HOME/.inkscape || mkdir $HOME/.inkscape; touch $HOME/.inkscape/.fccache");
-               }
-       }
-       else
-       {
-               params.defaultText = (void *) kAlertDefaultOKText;
-               params.cancelText = NULL;
 
-               StandardAlert(kAlertNoteAlert, "\pFont caches have not been updated",
-                               "\pThey can be updated manually by running the following:\n   sudo /usr/X11R6/bin/fc-cache\nOnce you have dealt with this, please re-run Inkscape.", &params, &itemHit);
-               system("test -d $HOME/.inkscape || mkdir $HOME/.inkscape; touch $HOME/.inkscape/.fccache");
-       }
-    
-       ExitToShell();
+static size_t safeRead(int d, void *buf, size_t nbytes)
+{
+       ssize_t bytesToRead = nbytes;
+       ssize_t bytesRead = 0;
+       char *offset = (char *) buf;
 
-    return noErr;
+       while ((bytesToRead > 0))
+       {
+               bytesRead = read(d, offset, bytesToRead);
+               if (bytesRead > 0)
+               {
+                       offset += bytesRead;
+                       bytesToRead -= bytesRead;
+               }
+               else if (bytesRead == 0)
+               {       
+                       // Reached EOF.
+                       break;
+               }
+               else if (bytesRead == -1)
+               {
+                       if ((errno == EINTR) || (errno == EAGAIN))
+                       {
+                               // Try again.
+                               continue;
+                       }
+                       return 0;
+               }
+       }
+       return bytesRead;
 }
 
 
@@ -221,15 +289,23 @@ static OSStatus FCCacheFailedHandler(EventHandlerCallRef theHandlerCall,
 /////////////////////////////////////
 static OSStatus FixFCCache (void)
 {
-   char* args[1];
+       FILE *fileConnToChild = NULL;
+       int fdConnToChild = 0;
+       pid_t childPID = WAIT_ANY;
+       size_t bytesChildPID;
+       size_t bytesRead;
+       int status;
+
+       char commandStr[] = "/usr/X11R6/bin/fc-cache";
+       char *commandArgs[] = { "-f", NULL };
 
        // Run fc-cache
        AuthorizationItem authItems[] = 
        {
        {
                kAuthorizationRightExecute,
-               23,
-               "/usr/X11R6/bin/fc-cache",
+               strlen(commandStr),
+               commandStr,
                0
        }
        };
@@ -240,22 +316,51 @@ static OSStatus FixFCCache (void)
        };
        AuthorizationRef authRef = NULL;
        OSStatus err = AuthorizationCreate (NULL, &authItemSet,
-                       kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights, &authRef);
+                       kAuthorizationFlagInteractionAllowed | 
+                       kAuthorizationFlagExtendRights, &authRef);
 
        if (err == errAuthorizationSuccess)
        {
-               //the arguments parameter to AuthorizationExecuteWithPrivileges is
-               //a NULL terminated array of C string pointers.
-               args[0]= NULL;
+               err = AuthorizationExecuteWithPrivileges(authRef, commandStr, 
+                               kAuthorizationFlagDefaults, commandArgs,
+                               &fileConnToChild);
 
-               AuthorizationExecuteWithPrivileges (authRef, "/usr/X11R6/bin/fc-cache", 
-                               kAuthorizationFlagDefaults, args, NULL);
+               if (err == errAuthorizationSuccess)
+               {
+                       // Unfortunately, AuthorizationExecuteWithPrivileges
+                       // does not return the process ID associated with the
+                       // process it runs.  The best solution we have it to
+                       // try and get the process ID from the file descriptor.
+                       // This is based on example code from Apple's
+                       // MoreAuthSample.
+
+                       fdConnToChild = fileno(fileConnToChild);
+                       
+                       // Try an get the process ID of the fc-cache command
+                       bytesChildPID = sizeof(childPID);
+                       bytesRead = safeRead(fdConnToChild, &childPID,
+                                       bytesChildPID);
+                       if (bytesRead != bytesChildPID)
+                       {
+                               // If we can't get it the best alternative
+                               // is to wait for any child to finish.
+                               childPID = WAIT_ANY;
+                       }
+
+                       if (fileConnToChild != NULL) {
+                               fclose(fileConnToChild);
+                       }
+
+                       // Wait for child process to finish.
+                       waitpid(childPID, &status, 0);
+               }
        }
-    AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
+       AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
 
        return err;
 }
 
+
 ///////////////////////////////////
 // Execution thread starts here
 ///////////////////////////////////
@@ -274,7 +379,7 @@ static void *Execute (void *arg)
     else if (err == (OSErr)12) {
         CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindFCCacheFailed, 0,
                     kEventAttributeNone, &event);
-        PostEventToQueue(GetMainEventQueue(), event, kEventPriorityStandard);
+        PostEventToQueue(GetMainEventQueue(), event, kEventPriorityHigh);
     }
     else ExitToShell();
     return 0;
@@ -548,6 +653,14 @@ static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent,
 ///////////////////////////////
 // Handler for clicking on app icon
 ///////////////////////////////
+// if app is already open
+static OSErr AppReopenAppAEHandler(const AppleEvent *theAppleEvent,
+                                 AppleEvent *reply, long refCon)
+{
+    runScript();
+}
+
+// if app is being opened
 static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent,
                                  AppleEvent *reply, long refCon)
 {
@@ -611,10 +724,10 @@ static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall,
        StandardAlert(kAlertStopAlert, "\pFailed to start X11",
                        "\pInkscape.app requires Apple's X11, which is freely downloadable from Apple's website for Panther (10.3.x) users and available as an optional install from the installation DVD for Tiger (10.4.x) users.\n\nPlease install X11 and restart Inkscape.",
                        &params, &itemHit);
-    
+
        if (itemHit == kAlertStdAlertCancelButton)
        {
-               OpenURL("http://www.apple.com/downloads/macosx/apple/x11formacosx.html");
+               OpenURL("http://www.apple.com/downloads/macosx/apple/macosx_updates/x11formacosx.html");
        }
 
     ExitToShell();
@@ -622,3 +735,53 @@ static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall,
 
     return noErr;
 }
+
+
+// Compile and run a small AppleScript. The code below does no cleanup and no proper error checks
+// but since it's there until the app is shut down, and since we know the script is okay,
+// there should not be any problems.
+ComponentInstance theComponent;
+AEDesc scriptTextDesc;
+OSStatus err;
+OSAID scriptID, resultID;
+
+static OSStatus CompileAppleScript(const void* text, long textLength,
+                                  AEDesc *resultData) {
+    
+    resultData = NULL;
+    /* set up locals to a known state */
+    theComponent = NULL;
+    AECreateDesc(typeNull, NULL, 0, &scriptTextDesc);
+    scriptID = kOSANullScript;
+    resultID = kOSANullScript;
+    
+    /* open the scripting component */
+    theComponent = OpenDefaultComponent(kOSAComponentType,
+                                        typeAppleScript);
+    if (theComponent == NULL) { err = paramErr; return err; }
+    
+    /* put the script text into an aedesc */
+    err = AECreateDesc(typeChar, text, textLength, &scriptTextDesc);
+    if (err != noErr) return err;
+    
+    /* compile the script */
+    err = OSACompile(theComponent, &scriptTextDesc,
+                     kOSAModeNull, &scriptID);
+
+    return err;
+}
+
+/* runs the compiled applescript */
+static void runScript()
+{
+    /* run the script */
+    err = OSAExecute(theComponent, scriptID, kOSANullScript,
+                     kOSAModeNull, &resultID);
+    return err;
+}
+
+
+/* Simple shortcut to the function that actually compiles the applescript. */
+static OSStatus SimpleCompileAppleScript(const char* theScript) {
+    return CompileAppleScript(theScript, strlen(theScript), NULL);
+}