//************************************************************************************** // Filename: OnMyCommandCM.cp // Part of Contextual Menu Workshop by Abracode Inc. // http://free.abracode.com/cmworkshop/ // Copyright © 2002-2003 Abracode, Inc. All rights reserved. // // Description: Executes Unix commands in Terminal.app or silently // //************************************************************************************** #include "CFAbstractCMPlugin.h" #include "CMUtils.h" #include "MoreFilesExtras.h" #include "CFObjDel.h" #include "CShellExecutor.h" #include "StAEDesc.h" #include #include "TCFArray.h" //#include "StAllocThrowDisable.h" #include "DebugSettings.h" #include "NavDialogs.h" #if 0 #include "FileURLDesc.h" #endif #include "ContextualMenuHelperExterns.h" // Strings enum { kCommandStrings = 130, // STR# id kMainMenuStrIndx = 1, kExecuteStrIndx, kCancelStrIndx }; enum { kClearTextDialogIndx = 128, kPasswordTextDialogIndx = 129, kPopupMenuDialogIndx = 130, kComboBoxDialogIndx = 131 }; typedef struct { AEDescList submenu; CFStringRef name; Boolean isDefault; Boolean isMain; short padding; } SubmenuAndName; const FourCharCode kTerminalAppSig = 'trmx'; OSStatus FSRefCheckFileOrFolder(const FSRef *inRef, void *ioData); OSStatus ProcessObjects(CMPluginImplData &inData); OSStatus ProcessCommandWithText(const CommandDescription &currCommand, CFStringRef inStrRef, CMPluginImplData &inData); void ExecuteInTerminal(CFStringRef inCommand, bool openInNewWindow, bool bringToFront); OSErr SendEventToTerminal(const AEDesc &inCommandDesc, SInt32 sysVersion, bool openInNewWindow, bool bringToFront); Boolean DisplayWarning( const AbstractCMPluginType *theThis, CommandDescription &currCommand ); TCFArray * CreateSubmenuList(AEDescList* ioRootMenu, const CMPluginImplData &inData); void DeleteSubmenuList(const TCFArray *inList); Boolean FindSubmenuByName(const TCFArray *inList, CFStringRef inName, SubmenuAndName &outFoundItem); void AttachSubmenus(AEDescList *rootMenu, TCFArray *inSubmenuList); void PopulateItemsMenu(TCFArray *inSubmenuList, const CMPluginImplData &inData); void ReadPreferences(CMPluginImplData &ioData); void GetOneCommandParams(CommandDescription &outDesc, CFDictionaryRef inOneCommand); void GetMultiCommandParams(CommandDescription &outDesc, CFDictionaryRef inOneCommand); void GetInputDialogParams(CommandDescription &outDesc, CFDictionaryRef inOneCommand); void DeleteCommandList(CMPluginImplData &ioData); void DeleteObjectList(CMPluginImplData &ioData); FourCharCode CFStringToFourCharCode(CFStringRef inStrRef); typedef Boolean (*ObjCheckingProc)( const OneObjProperties *inObj, void *inProcData ); Boolean CheckAllObjects(OneObjProperties *objList, UInt32 inCount, ObjCheckingProc inProcPtr, void *inProcData); Boolean CheckIfFile(const OneObjProperties *inObj, void *); Boolean CheckIfFolder(const OneObjProperties *inObj, void *); Boolean CheckIfFileOrFolder(const OneObjProperties *inObj, void *); Boolean CheckFileType(const OneObjProperties *inObj, void *); Boolean CheckExtension(const OneObjProperties *inObj, void *); Boolean CheckFileTypeOrExtension(const OneObjProperties *inObj, void *inData); CFStringRef CreateCFStringOfTextFromClipboard(); Boolean IsTextInClipboard(); void ReplaceSpecialCharsWithBackslashEscapes(CFMutableStringRef inStrRef); CFMutableStringRef CreateCommandStringWithObjects(CMPluginImplData &inData); CFMutableStringRef CreateCommandStringWithText(CFArrayRef inFragments, CFStringRef inObjTextRef, CMPluginImplData &inData, Boolean replaceSpecialChars); void AppendTextToCommand(CFMutableStringRef inCommandRef, CFStringRef inStrRef, OneObjProperties *inObjList, UInt32 inObjCount, UInt32 inCurrIndex, CFStringRef inObjTextRef, CFStringRef &ioCommonParentPathRef, CFStringRef inInputStr, CFStringRef inMultiSeparator, CFStringRef inMultiPrefix, CFStringRef inMultiSuffix, CFURLRef inSaveAsPath, UInt16 escSpecialCharsMode); typedef CFStringRef (*CreateObjProc)(const OneObjProperties *inObj, void *ioParam); CFStringRef CreateObjPath(const OneObjProperties *inObj, void *ioParam); CFStringRef CreateObjPathNoExtension(const OneObjProperties *inObj, void *ioParam); CFStringRef CreateParentPath(const OneObjProperties *inObj, void *ioParam); CFStringRef CreateObjName(const OneObjProperties *inObj, void *ioParam); CFStringRef CreateObjNameNoExtension(const OneObjProperties *inObj, void *ioParam); CFStringRef CreateObjExtensionOnly(const OneObjProperties *inObj, void *ioParam); CFStringRef CreateObjDisplayName(const OneObjProperties *inObj, void *ioParam); CFStringRef CreateObjPathRelativeToBase(const OneObjProperties *inObj, void *ioParam); CFStringRef CreateCommonParentPath(OneObjProperties *inObjList, UInt32 inObjCount ); CFStringRef CreateStringFromListOrSingleObject( OneObjProperties *inObjList, UInt32 inObjCount, UInt32 inCurrIndex, CreateObjProc inObjProc, void *ioParam, CFStringRef inMultiSeparator, CFStringRef inPrefix, CFStringRef inSuffix, UInt16 escSpecialCharsMode ); CFStringRef CreateEscapedStringCopy(CFStringRef inStrRef, UInt16 escSpecialCharsMode); CFMutableStringRef CreateUFT8MutableStringCopy(CFStringRef inStrRef); void ReplaceNonASCIICharsWithOctalEscapes(CFMutableStringRef inStrRef); void DecToOctal(long inNum, UniChar *outBuffer, UniCharCount &ioLen ); UniChar * AddHiBytes(unsigned char *inBuffer, ByteCount inCount); void StripHiBytes(UniChar *ioBuffer, UniCharCount inCount); Boolean AskForInputText( const AbstractCMPluginType *theThis, CommandDescription &currCommand, CFStringRef *outStr ); void PrescanCommandDescription( CommandDescription &currCommand ); #if 0 void RefreshObjectsInFinder_CFURL(CMPluginImplData &inData);//not working #endif void RefreshObjectsInFinder(CMPluginImplData &inData); CFMutableStringRef CreateRefreshPathWithObject(CMPluginImplData &inData); #pragma mark - #pragma mark **** IMPLEMENTATION **** // --------------------------------------------------------------------------- // CMPluginExamineContext // --------------------------------------------------------------------------- // Have a look at the selection and decides whether to display any commands OSStatus CMPluginExamineContext( void *thisInstance, const AEDesc *inContext, AEDescList *outCommandPairs ) { // StAllocThrowDisable disableNewThrows;//for MSL operator new try { TRACE_STR( "\pOnMyCommandCM->CMPluginExamineContext" ); AbstractCMPluginType *theThis = (AbstractCMPluginType *)thisInstance; if(theThis == NULL) { DEBUG_STR( "\pOnMyCommandCM->CMPluginExamineContext error: theThis == NULL" ); return paramErr; } ::Gestalt(gestaltSystemVersion, &(theThis->implData.sysVersion)); theThis->implData.commandList = NULL; theThis->implData.commandCount = 0; theThis->implData.currCommandIndex = 0; theThis->implData.objectList = NULL; theThis->implData.objectCount = 0; theThis->implData.currObjectIndex = 0; theThis->implData.commonParentPath = NULL; theThis->implData.inputText = NULL; theThis->implData.saveAsPath = NULL; theThis->implData.mIsTextInClipboard = IsTextInClipboard(); theThis->implData.mIsOpenFolder = false; theThis->implData.mIsTextSelected = false; theThis->implData.mRunningInFinder = false; OSStatus err; Str255 hostName; err = CMUtils::GetHostName(hostName); if(err == noErr)//primitive but fast method of determining if we are running in Finder if( (hostName[0] == 6) && (hostName[1] == 'F') && (hostName[2] == 'i') && (hostName[3] == 'n') && (hostName[4] == 'd') && (hostName[5] == 'e') && (hostName[6] == 'r') ) { theThis->implData.mRunningInFinder = true; DEBUG_STR( "\pOnMyCommandCM->CMPluginExamineContext. running in Finder" ); } if(inContext == NULL) { DEBUG_STR( "\pOnMyCommandCM->CMPluginExamineContext error: inContext == NULL" ); return errAENotAEDesc; } if(inContext->descriptorType == typeNull) { DEBUG_STR( "\pOnMyCommandCM->CMPluginExamineContext error: inContext->descriptorType == typeNull" ); theThis->implData.mIsTextSelected = (Boolean)cocoaAppHasStringSelection(); // will need to show items marked as "always show" // if(theThis->implData.mIsTextSelected == false) // return errAENotAEDesc;//do not show CM when descriptor type is null and there is no selection in Cocoa app } ReadPreferences(theThis->implData); Boolean anythingSelected = false; if( inContext->descriptorType != typeNull ) { //pre-allocate space for all object properties SInt32 listItemsCount = 0; if( ::AECountItems(inContext, &listItemsCount) == noErr ) { theThis->implData.objectList = new OneObjProperties[listItemsCount]; ::BlockZero(theThis->implData.objectList, listItemsCount*sizeof(OneObjProperties)); theThis->implData.objectCount = listItemsCount; } UInt32 theFlags = kListClear; anythingSelected = CMUtils::ProcessObjectList( inContext, theFlags, FSRefCheckFileOrFolder, &(theThis->implData) ); } //update total count theThis->implData.objectCount = theThis->implData.currObjectIndex; theThis->implData.currObjectIndex = 0; if( !anythingSelected ) {//not a list of objects - maybe an open window or desktop //check what was clicked FSRef folderRef; if(inContext != NULL) err = CMUtils::GetFSRef( *inContext, folderRef ); else err = fnfErr;//anything to mark that there is no file if(err == noErr) { DeleteObjectList(theThis->implData); theThis->implData.objectList = new OneObjProperties[1]; ::BlockZero(theThis->implData.objectList, sizeof(OneObjProperties)); theThis->implData.objectCount = 1; err = FSRefCheckFileOrFolder( &folderRef, &(theThis->implData) ); if(err == noErr) { Boolean isFolder = false; err = CMUtils::IsFolder( &folderRef, isFolder); if( (err == noErr) && isFolder ) { theThis->implData.mIsOpenFolder = true; TRACE_STR( "\pOpen Folder Clicked" ); } } } else {//maybe a selected text? if( theThis->implData.mIsTextSelected == false) theThis->implData.mIsTextSelected = CMUtils::AEDescHasTextData(*inContext); if( theThis->implData.mIsTextSelected == false) theThis->implData.mIsTextSelected = (Boolean)cocoaAppHasStringSelection(); } } if( true ) { StBundleResOpen resOpen( theThis->bundleRef ); if( resOpen.IsValid() ) { TCFArray * theSubmenuList = CreateSubmenuList(outCommandPairs, theThis->implData); if( theSubmenuList != NULL ) { PopulateItemsMenu( theSubmenuList, theThis->implData ); AttachSubmenus(outCommandPairs, theSubmenuList); DeleteSubmenuList(theSubmenuList); } } } } catch(...) { } return noErr; } // --------------------------------------------------------------------------- // CMPluginHandleSelection // --------------------------------------------------------------------------- // Carry out the command that the user selected. The commandID indicates // which command was selected OSStatus CMPluginHandleSelection( void *thisInstance, AEDesc *inContext, SInt32 inCommandID ) { // StAllocThrowDisable disableNewThrows;//for MSL operator new try { TRACE_STR( "\pOnMyCommandCM->CMPluginHandleSelection" ); AbstractCMPluginType *theThis = (AbstractCMPluginType *)thisInstance; if(inCommandID >= kCMCommandStart) { theThis->implData.currCommandIndex = inCommandID - kCMCommandStart; if( (theThis->implData.commandList == NULL) || (theThis->implData.commandCount == 0) ) return noErr; if( theThis->implData.currCommandIndex >= theThis->implData.commandCount) return paramErr; //take original command as parameter, not copy PrescanCommandDescription( theThis->implData.commandList[theThis->implData.currCommandIndex] ); CommandDescription currCommand = theThis->implData.commandList[theThis->implData.currCommandIndex]; if( DisplayWarning(theThis, currCommand) == false ) { return noErr; } if( currCommand.containsPassword || currCommand.containsInputText ) { if( AskForInputText( theThis, currCommand, &(theThis->implData.inputText) ) == false ) { return noErr; } } if(currCommand.containsSaveAsPath) { if( CreatePathFromSaveAsDialog(theThis->implData.saveAsPath) == false ) return noErr; } Boolean objListEmpty = ((theThis->implData.objectList == NULL) || (theThis->implData.objectCount == 0)); if( theThis->implData.mIsTextSelected || (theThis->implData.mIsTextInClipboard && objListEmpty) ) { CFStringRef textRef = NULL; if( theThis->implData.mIsTextSelected && (currCommand.activationMode != kActiveClipboardText) ) { if(inContext != NULL) textRef = CMUtils::CreateCFStringFromAEDesc( *inContext ); if(textRef == NULL) textRef = currentCocoaStringSelection(); } else if( theThis->implData.mIsTextInClipboard ) { textRef = CreateCFStringOfTextFromClipboard(); } CFObjDel textDel(textRef); TRACE_STR( "\pOnMyCommandCM->CMPluginHandleSelection: about to process command with text selection" ); ProcessCommandWithText( currCommand, textRef, theThis->implData ); } else { ProcessObjects( theThis->implData ); } //we refresh objects in Finder at the very last moment so the files which are supposed to be created are indeed created RefreshObjectsInFinder(theThis->implData); } else { DEBUG_STR( "\pOnMyCommandCM->CMPluginHandleSelection: unknown command ID" ); } TRACE_STR( "\pOnMyCommandCM->CMPluginHandleSelection: finished successfully" ); } catch(...) { } return noErr; } // --------------------------------------------------------------------------- // CMPluginPostMenuCleanup // --------------------------------------------------------------------------- void CMPluginPostMenuCleanup( void *thisInstance ) { // StAllocThrowDisable disableNewThrows;//for MSL operator new try { TRACE_STR( "\pOnMyCommandCM->CMPluginPostMenuCleanup" ); AbstractCMPluginType *theThis = (AbstractCMPluginType *)thisInstance; if(theThis == NULL) return; DeleteObjectList(theThis->implData); DeleteCommandList(theThis->implData); if( theThis->implData.commonParentPath != NULL ) { ::CFRelease(theThis->implData.commonParentPath); theThis->implData.commonParentPath = NULL; } if(theThis->implData.inputText != NULL) { ::CFRelease(theThis->implData.inputText); theThis->implData.inputText = NULL; } if(theThis->implData.saveAsPath != NULL) { ::CFRelease(theThis->implData.saveAsPath); theThis->implData.saveAsPath = NULL; } TRACE_STR( "\pOnMyCommandCM->CMPluginPostMenuCleanup finished successfully" ); } catch(...) { } } #pragma mark - OSStatus FSRefCheckFileOrFolder(const FSRef *inRef, void *ioData) { if( (inRef == NULL) || (ioData == NULL) ) return paramErr; CMPluginImplData *myData = (CMPluginImplData *)ioData; if(myData->objectList == NULL) return paramErr; //we are interested in real files or folders //sometimes we get FSRef not valid (InternetExplorer) FSCatalogInfo theInfo; ::BlockZero(&theInfo, sizeof(theInfo)); OSErr err = ::FSGetCatalogInfo(inRef, kFSCatInfoNodeFlags, &theInfo, NULL, NULL, NULL); if ( err == noErr ) {//it is a file or folder. we can safely proceed LSItemInfoRecord itemInfo; ::BlockZero(&itemInfo, sizeof(itemInfo)); LSRequestedInfo whichInfo = kLSRequestExtension | kLSRequestTypeCreator | kLSRequestBasicFlagsOnly | kLSRequestExtensionFlagsOnly; err = ::LSCopyItemInfoForRef( inRef, whichInfo, &itemInfo); if( err == noErr ) { if( myData->currObjectIndex < myData->objectCount ) { OneObjProperties *objProperties = myData->objectList + myData->currObjectIndex; objProperties->mRef = *inRef; objProperties->mExtension = itemInfo.extension; objProperties->mType = itemInfo.filetype; objProperties->mFlags = itemInfo.flags; objProperties->mRefreshPath = NULL; myData->currObjectIndex++; } else if(itemInfo.extension != NULL) { ::CFRelease(itemInfo.extension); } } } return err; } OSStatus ProcessObjects(CMPluginImplData &inData) { if( (inData.commandList == NULL) || (inData.commandCount == 0) || (inData.objectList == NULL) || (inData.objectCount == 0) ) return noErr; if( inData.currCommandIndex >= inData.commandCount) return paramErr; CommandDescription currCommand = inData.commandList[inData.currCommandIndex]; TRACE_STR( "\pOnMyCommandCM. ProcessObjects" ); CShellExecutor *theShell = NULL; if(currCommand.executionMode == kExecSilent) { theShell = new CShellExecutor(); } if( currCommand.refresh != NULL ) {//refreshing needed - compose array of paths before performing any action for(UInt32 i = 0; i < inData.objectCount; i++) { TRACE_STR( "\pOnMyCommandCM. create refresh path" ); inData.currObjectIndex = i; CFMutableStringRef onePath = CreateRefreshPathWithObject(inData); inData.objectList[i].mRefreshPath = onePath;//we own the string } } if( currCommand.multipleObjectProcessing == kMulObjProcessSeparately ) { TRACE_STR( "\pOnMyCommandCM. Processing separately" ); for(UInt32 i = 0; i < inData.objectCount; i++) { inData.currObjectIndex = i; CFMutableStringRef theCommand = CreateCommandStringWithObjects(inData); if(theCommand == NULL) return memFullErr; CFObjDel commandDel(theCommand); if(currCommand.executionMode == kExecTerminal) { ExecuteInTerminal( theCommand, currCommand.openNewTerminalSession, currCommand.bringTerminalToFront ); } else if(theShell != NULL) { theShell->Execute(theCommand); } } } else { TRACE_STR( "\pOnMyCommandCM. Processing together" ); inData.currObjectIndex = 0xFFFFFFFF;//invalid index means process them all together CFMutableStringRef theCommand = CreateCommandStringWithObjects(inData); if(theCommand == NULL) return memFullErr; CFObjDel commandDel(theCommand); if(currCommand.executionMode == kExecTerminal) { ExecuteInTerminal( theCommand, currCommand.openNewTerminalSession, currCommand.bringTerminalToFront ); } else if(theShell != NULL) { theShell->Execute(theCommand); } } return noErr; } OSStatus ProcessCommandWithText(const CommandDescription &currCommand, CFStringRef inStrRef, CMPluginImplData &inData) { /*refresh paths work only with file objects selected if( currCommand.refresh != NULL ) {//refreshing needed - compose array of paths before performing any action for(UInt32 i = 0; i < inData.objectCount; i++) { TRACE_STR( "\pOnMyCommandCM. create refresh path" ); inData.currObjectIndex = i; CFMutableStringRef onePath = CreateRefreshPathWithObject(inData); inData.objectList[i].mRefreshPath = onePath;//we own the string } } */ CFMutableStringRef theCommand = CreateCommandStringWithText(currCommand.command, inStrRef, inData, currCommand.escapeSpecialCharsMode); if(theCommand == NULL) return memFullErr; CFObjDel commandDel(theCommand); if(currCommand.executionMode == kExecTerminal) { ExecuteInTerminal(theCommand, currCommand.openNewTerminalSession, currCommand.bringTerminalToFront); } else { CShellExecutor theShell; theShell.Execute(theCommand); } return noErr; } void ExecuteInTerminal(CFStringRef inCommand, bool openInNewWindow, bool bringToFront) { if(inCommand == NULL) return; // FourCharCode termAppSig = 'trmx'; OSStatus err = noErr; StAEDesc theCommandDesc; CFIndex uniCount = ::CFStringGetLength(inCommand); CFIndex maxCount = ::CFStringGetMaximumSizeForEncoding( uniCount, kCFStringEncodingUTF8 ); Ptr theString = ::NewPtrClear(maxCount +1);//one more for 0-termination if(theString != NULL) { if( ::CFStringGetCString( inCommand, theString, maxCount +1, kCFStringEncodingUTF8) ) { SInt32 actualLen = std::strlen(theString); err = ::AECreateDesc( typeChar, theString, actualLen, theCommandDesc); } ::DisposePtr(theString); } else { err = memFullErr; } if(err != noErr) return; SInt32 sysVersion; ::Gestalt(gestaltSystemVersion, &sysVersion); err = SendEventToTerminal(theCommandDesc, sysVersion, openInNewWindow, bringToFront); if(err == noErr) return;//sucess situation - we may exit now //terminal not running probably TRACE_STR( "\p\tExecuteInTerminal. Trying to launch the application with LS" ); FSRef appRef; err = ::LSFindApplicationForInfo(kTerminalAppSig, NULL, CFSTR("Terminal.app"), &appRef, NULL); if(err != noErr) { TRACE_STR( "\p\tExecuteInTerminal. Could not find Terminal application. We give up now." ); return; } LSLaunchFSRefSpec launchParams; launchParams.appRef = &appRef; launchParams.numDocs = 0; launchParams.itemRefs = NULL; launchParams.passThruParams = NULL;//appleEvent; launchParams.launchFlags = kLSLaunchDefaults; launchParams.asyncRefCon = NULL; err = ::LSOpenFromRefSpec( &launchParams, NULL); if(err == noErr) {//Terminal is launching right now. //We cannot send an event to it right away because it is not ready yet //We cannot install a launch notify handler because we do not know if our host will be happy about this //Installing a deferred task is too much work :-) so we go the easy way: //we will wait one second and send event. We will try 10 times every one second to give plenty of time for the app to launch for(int i = 1; i <= 10; i++) {//we only try 10 times and then give up ::Delay(60, NULL); //wait 1 second before trying to send the event err = SendEventToTerminal(theCommandDesc, sysVersion, false, bringToFront);//after terminal launch a new window is open so never open another one if( (err == connectionInvalid) || (err == procNotFound) ) { TRACE_STR( "\p\tExecuteInTerminal. App still launching. Waiting..." ); continue; } else break;//either err == noErr (success), or some other error so we exit anyway } if(err != noErr) { DEBUG_STR( "\p\tExecuteInTerminal. Event could not be sent to launched application" ); } } else { DEBUG_STR( "\p\tExecuteInTerminal. LSOpenFromRefSpec failed" ); #if _DEBUG_ { Str255 hexString; ByteCount destLen = sizeof(Str255); CMUtils::BufToHex( (const unsigned char *)&err, (char *)hexString, sizeof(err), destLen ); ::CopyCStringToPascal( (char *)hexString, hexString); DEBUG_STR( "\p\tLSOpenFromRefSpec returned error: " ); DEBUG_STR(hexString); } #endif //_DEBUG_ } } OSErr SendEventToTerminal(const AEDesc &inCommandDesc, SInt32 sysVersion, bool openInNewWindow, bool bringToFront) { OSErr err = noErr; AEKeyword theKeyword = (sysVersion >= 0x1020) ? keyDirectObject : 'cmnd';//Terminal in Mac OS 10.1.x needs different event if(sysVersion < 0x1020) openInNewWindow = true;//old Terminal does no support execution in specified window if( openInNewWindow ) { err = CMUtils::SendAEWithObjToRunningApp( kTerminalAppSig, kAECoreSuite, kAEDoScript, theKeyword, inCommandDesc ); } else { StAEDesc theFrontWindowDesc; StAEDesc nullDescRec; StAEDesc positionDesc; long thePosition = 1; //window number one is the topmost window err = ::AECreateDesc( typeLongInteger, &thePosition, sizeof(thePosition), positionDesc); if(err == noErr) { err = ::CreateObjSpecifier( cWindow, nullDescRec, formAbsolutePosition, positionDesc, false, theFrontWindowDesc ); } if(err == noErr) { err = CMUtils::SendAEWithTwoObjToRunningApp( kTerminalAppSig, kAECoreSuite, kAEDoScript, theKeyword, inCommandDesc, keyAEFile, theFrontWindowDesc ); } if( (err != noErr) && (err != connectionInvalid) && (err != procNotFound) ) {//if for any reason the event cannot be sent to topmost window, try opening new window err = CMUtils::SendAEWithObjToRunningApp( kTerminalAppSig, kAECoreSuite, kAEDoScript, theKeyword, inCommandDesc ); } } if( (err == noErr) && bringToFront) { StAEDesc fakeDesc; err = CMUtils::SendAEWithObjToRunningApp( kTerminalAppSig, kAEMiscStandards, kAEActivate, 0, fakeDesc); } return err; } //true - OK to proceed //false - do not execute //there are 3 levels for execute & cancel buttons strings: //1. defined in command itself //2. if still null, get from resources //3. if still null, use hardcoded strings Boolean DisplayWarning( const AbstractCMPluginType *theThis, CommandDescription &currCommand ) { if(currCommand.warningStr == NULL) return true; CFIndex theLen = ::CFStringGetLength(currCommand.warningStr); if(theLen == 0) return true; CFIndex execLen = 0; CFIndex cancelLen = 0; if(currCommand.warningExecuteStr != NULL) { execLen = ::CFStringGetLength(currCommand.warningExecuteStr); if(execLen == 0) { ::CFRelease(currCommand.warningExecuteStr); currCommand.warningExecuteStr = NULL; } } if(currCommand.warningCancelStr != NULL) { cancelLen = ::CFStringGetLength(currCommand.warningCancelStr); if(cancelLen == 0) { ::CFRelease(currCommand.warningCancelStr); currCommand.warningCancelStr = NULL; } } if( (currCommand.warningExecuteStr == NULL) || (currCommand.warningCancelStr == NULL) ) { StBundleResOpen resOpen( theThis->bundleRef ); if( resOpen.IsValid() ) { if(currCommand.warningExecuteStr == NULL) { currCommand.warningExecuteStr = CMUtils::CreateCFStringFromResourceText(kCommandStrings, kExecuteStrIndx); } if(currCommand.warningCancelStr == NULL) { currCommand.warningCancelStr = CMUtils::CreateCFStringFromResourceText(kCommandStrings, kCancelStrIndx); } } } if(currCommand.warningExecuteStr == NULL) { currCommand.warningExecuteStr = ::CFStringCreateWithCString( kCFAllocatorDefault, "Execute", kCFStringEncodingMacRoman ); } if(currCommand.warningCancelStr == NULL) { currCommand.warningCancelStr = ::CFStringCreateWithCString( kCFAllocatorDefault, "Cancel", kCFStringEncodingMacRoman ); } DialogRef theAlert = NULL; AlertStdCFStringAlertParamRec params; ::GetStandardAlertDefaultParams(¶ms, kStdCFStringAlertVersionOne); params.movable = true; // params.helpButton = false; params.defaultText = currCommand.warningExecuteStr; params.cancelText = currCommand.warningCancelStr; // params.otherText = NULL; params.defaultButton = kAlertStdAlertCancelButton; // params.cancelButton = 0; params.position = kWindowDefaultPosition; // params.flags = 0; OSStatus err = ::CreateStandardAlert( kAlertCautionAlert, currCommand.warningStr, NULL, ¶ms, &theAlert ); if( (err == noErr) && (theAlert != NULL) ) { DialogItemIndex hitItem = kAlertStdAlertCancelButton; err = ::RunStandardAlert( theAlert, NULL, &hitItem ); if( (err == noErr) && (hitItem == kAlertStdAlertOKButton) ) { return true; } } return false; } Boolean AskForInputText( const AbstractCMPluginType *theThis, CommandDescription &currCommand, CFStringRef *outStr ) { if(outStr == NULL) return false; *outStr = NULL; StBundleResOpen resOpen( theThis->bundleRef ); if(! resOpen.IsValid() ) return false; DialogRef dialogPtr; ControlHandle controlHdl; SInt16 itemHit; OSStatus err = noErr; SInt16 dialogID = 0; SInt16 popupIndex = 0; UInt16 dialogType = currCommand.inputDialogType; if( (dialogType == kInputClearText) && (currCommand.containsPassword) ) { dialogType = kInputPasswordText; } switch(dialogType) { case kInputPasswordText: dialogID = kPasswordTextDialogIndx; break; case kInputPopupMenu: dialogID = kPopupMenuDialogIndx; popupIndex = 4; break; case kInputComboBox: dialogID = kComboBoxDialogIndx; popupIndex = 5; break; case kInputClearText: default: dialogID = kClearTextDialogIndx; break; } if( (dialogPtr = ::GetNewDialog(dialogID, NULL, (WindowPtr) -1)) == NULL ) return false; err = ::GetDialogItemAsControl(dialogPtr, 4, &controlHdl); if(err != noErr) { ::DisposeDialog(dialogPtr); return false; } ControlHandle popupHdl = NULL; if(popupIndex != 0) { err = ::GetDialogItemAsControl(dialogPtr, popupIndex, &popupHdl); if(err != noErr) { ::DisposeDialog(dialogPtr); return false; } } ControlRef okButton = NULL; Rect okRect = {0,0,0,0}; err = ::GetDialogItemAsControl(dialogPtr, kStdOkItemIndex, &okButton); if(err == noErr) { if(currCommand.inputDialogOK != NULL) { err = ::SetControlTitleWithCFString( okButton, currCommand.inputDialogOK ); } SInt16 textBaselineOffset; err = ::GetBestControlRect(okButton, &okRect, &textBaselineOffset); } ControlRef cancelButton = NULL; Rect cancelRect = {0,0,0,0}; err = ::GetDialogItemAsControl(dialogPtr, kStdCancelItemIndex, &cancelButton); if(err == noErr) { if(currCommand.inputDialogCancel != NULL) { err = ::SetControlTitleWithCFString( cancelButton, currCommand.inputDialogCancel ); } SInt16 textBaselineOffset; err = ::GetBestControlRect(cancelButton, &cancelRect, &textBaselineOffset); } //push buttons auto-sizing code WindowRef window = ::GetDialogWindow(dialogPtr); if( (window != NULL) && (okButton != NULL) && (cancelButton != NULL) ) { Rect portRect; ::GetWindowPortBounds(window, &portRect); SInt16 windowWidth = portRect.right - portRect.left; SInt32 spaceAvailable = windowWidth - 16 - 16 - 16;//16 point margins and 16 point space between buttons //enforce minimum in case something went wrong if( (okRect.right - okRect.left) == 0 ) okRect.right += (64 - (okRect.right - okRect.left)); if( (okRect.bottom - okRect.top) == 0 ) okRect.bottom += (20 - (okRect.bottom - okRect.top)); if( (cancelRect.right - cancelRect.left) == 0 ) cancelRect.right += (64 - (cancelRect.right - cancelRect.left)); if( (cancelRect.bottom - cancelRect.top) == 0 ) cancelRect.bottom += (20 - (cancelRect.bottom - cancelRect.top)); if( (okRect.right - okRect.left + cancelRect.right - cancelRect.left) > spaceAvailable) {//we will not fit as it is, we need to make them smaller SInt16 delta = (okRect.right - okRect.left + cancelRect.right - cancelRect.left - spaceAvailable)/2; okRect.right -= delta; cancelRect.right -= delta; } //align OK button to right margin SInt16 theShift = windowWidth - 16 - okRect.right; okRect.right = windowWidth - 16; okRect.left += theShift; //align Cancel button to OK button with 16 pixels margin theShift = okRect.left - 16 - cancelRect.right; cancelRect.right = okRect.left - 16; cancelRect.left += theShift; ::HideControl(okButton); ::HideControl(cancelButton); ::SetControlBounds(okButton, &okRect); ::SetControlBounds(cancelButton, &cancelRect); ::ShowControl(okButton); ::ShowControl(cancelButton); } if(currCommand.inputDialogMessage != NULL) { ControlRef messageText = NULL; err = ::GetDialogItemAsControl(dialogPtr, 3, &messageText); if(err == noErr) { err = ::SetControlData( messageText, kControlEntireControl, kControlStaticTextCFStringTag, sizeof(CFStringRef), &(currCommand.inputDialogMessage) ); } } ControlEditTextSelectionRec theWholeRange = {0, 0x7FFF}; if( dialogType == kInputPasswordText ) { ::SetKeyboardFocus( window, controlHdl, kControlFocusNextPart); if(currCommand.inputDialogDefault != NULL) { err = ::SetControlData( controlHdl, kControlEntireControl, kControlEditTextPasswordCFStringTag, sizeof(CFStringRef), &(currCommand.inputDialogDefault) ); } err = ::SetControlData( controlHdl, kControlEntireControl, kControlEditTextSelectionTag, sizeof(ControlEditTextSelectionRec), &theWholeRange ); } else if(dialogType == kInputClearText) { ::SetKeyboardFocus( window, controlHdl, kControlFocusNextPart); if(currCommand.inputDialogDefault != NULL) { err = ::SetControlData( controlHdl, kControlEntireControl, kControlEditTextCFStringTag, sizeof(CFStringRef), &(currCommand.inputDialogDefault) ); } err = ::SetControlData( controlHdl, kControlEntireControl, kControlEditTextSelectionTag, sizeof(ControlEditTextSelectionRec), &theWholeRange ); } if( (popupHdl != NULL) && (currCommand.inputDialogMenuItems != NULL) ) { MenuRef theMenu = ::GetControlPopupMenuHandle(popupHdl); if(theMenu != NULL) { UInt16 menuItemCount = ::CountMenuItems(theMenu); for(UInt16 i= menuItemCount; i > 0 ; i--) ::DeleteMenuItem( theMenu, i ); CFArrayRef theList = currCommand.inputDialogMenuItems; CFIndex itemCount = ::CFArrayGetCount(theList); CFIndex maxCount = 0; CFIndex defaultValue = 1; for(CFIndex i = 1; i <= itemCount; i++) { CFTypeRef theItem = ::CFArrayGetValueAtIndex( theList, i-1);//indexes in CFArray are zero-based if( (theItem != NULL) && (::CFGetTypeID(theItem) == ::CFDictionaryGetTypeID()) ) { CFDictionaryRef dictRef = (CFDictionaryRef)theItem; theItem = ::CFDictionaryGetValue( dictRef, CFSTR("NAME") ); if((theItem != NULL) && (::CFStringGetTypeID() == ::CFGetTypeID(theItem)) ) { CFStringRef theName = (CFStringRef)theItem; ::MacInsertMenuItem(theMenu, "\pEmpty", i-1); err = ::SetMenuItemTextWithCFString(theMenu, i, theName ); maxCount++; if(currCommand.inputDialogDefault != NULL) { if( kCFCompareEqualTo == ::CFStringCompare( currCommand.inputDialogDefault, theName, 0 ) ) { defaultValue = i; } } } } } ::SetControlMinimum(popupHdl, 1); ::SetControlMaximum(popupHdl, maxCount); ::SetControlValue(popupHdl, defaultValue); if(dialogType == kInputComboBox ) { ::SetKeyboardFocus( window, controlHdl, kControlFocusNextPart); CFArrayRef theList = currCommand.inputDialogMenuItems; CFIndex itemCount = ::CFArrayGetCount(theList); if(defaultValue <= itemCount) { CFTypeRef theItem = ::CFArrayGetValueAtIndex( theList, defaultValue-1);//indexes in CFArray are zero-based if( (theItem != NULL) && (::CFGetTypeID(theItem) == ::CFDictionaryGetTypeID()) ) { CFDictionaryRef dictRef = (CFDictionaryRef)theItem; theItem = ::CFDictionaryGetValue( dictRef, CFSTR("VALUE") ); if((theItem != NULL) && (::CFStringGetTypeID() == ::CFGetTypeID(theItem)) ) { CFStringRef theValue = (CFStringRef)theItem; err = ::SetControlData( controlHdl, kControlEntireControl, kControlEditTextCFStringTag, sizeof(CFStringRef), &theValue ); err = ::SetControlData( controlHdl, kControlEntireControl, kControlEditTextSelectionTag, sizeof(ControlEditTextSelectionRec), &theWholeRange ); } } } } } } // WindowRef window = ::GetDialogWindow(dialogPtr); // if(window != NULL) // { // RgnHandle region = NULL; // ::GetWindowRegion(window, kWindowContentRgn, region); // ::UpdateControls( window, region); // } ::SetDialogDefaultItem(dialogPtr, kStdOkItemIndex); ::SetDialogCancelItem(dialogPtr, kStdCancelItemIndex); ::SetDialogTracksCursor(dialogPtr, true); SInt16 popupChoice = 0; do { ::ModalDialog(NULL, &itemHit); if( (itemHit == popupIndex) && (popupHdl != NULL) && (dialogType == kInputComboBox) ) { if(currCommand.inputDialogMenuItems != NULL) { popupChoice = ::GetControlValue(popupHdl); CFArrayRef theList = currCommand.inputDialogMenuItems; CFIndex itemCount = ::CFArrayGetCount(theList); if(popupChoice <= itemCount) { CFTypeRef theItem = ::CFArrayGetValueAtIndex( theList, popupChoice-1);//indexes in CFArray are zero-based if( (theItem != NULL) && (::CFGetTypeID(theItem) == ::CFDictionaryGetTypeID()) ) { CFDictionaryRef dictRef = (CFDictionaryRef)theItem; theItem = ::CFDictionaryGetValue( dictRef, CFSTR("VALUE") ); if((theItem != NULL) && (::CFStringGetTypeID() == ::CFGetTypeID(theItem)) ) { CFStringRef theValue = (CFStringRef)theItem; err = ::SetControlData( controlHdl, kControlEntireControl, kControlEditTextCFStringTag, sizeof(CFStringRef), &theValue ); err = ::SetControlData( controlHdl, kControlEntireControl, kControlEditTextSelectionTag, sizeof(ControlEditTextSelectionRec), &theWholeRange ); ::DrawOneControl(controlHdl); } } } } } } while((itemHit != kStdOkItemIndex) && (itemHit != kStdCancelItemIndex)); Boolean isOK = false; if(itemHit != kStdCancelItemIndex) { popupChoice = 0; err = noErr; switch(dialogType) { case kInputPasswordText: { Size actualSize; CFStringRef theText = NULL; err = ::GetControlData( controlHdl, kControlEntireControl, kControlEditTextPasswordCFStringTag, sizeof(CFStringRef), &theText, &actualSize); if(err == noErr) { *outStr = theText; isOK = true; } } break; case kInputPopupMenu: { popupChoice = ::GetControlValue(popupHdl); CFArrayRef theList = currCommand.inputDialogMenuItems; CFIndex itemCount = ::CFArrayGetCount(theList); if(popupChoice <= itemCount) { CFTypeRef theItem = ::CFArrayGetValueAtIndex( theList, popupChoice-1);//indexes in CFArray are zero-based if( (theItem != NULL) && (::CFGetTypeID(theItem) == ::CFDictionaryGetTypeID()) ) { CFDictionaryRef dictRef = (CFDictionaryRef)theItem; theItem = ::CFDictionaryGetValue( dictRef, CFSTR("VALUE") ); if((theItem != NULL) && (::CFStringGetTypeID() == ::CFGetTypeID(theItem)) ) { CFStringRef theValue = (CFStringRef)theItem; ::CFRetain(theValue); *outStr = theValue; isOK = true; } } } } break; case kInputClearText: case kInputComboBox: default: { Size actualSize; CFStringRef theText = NULL; err = ::GetControlData( controlHdl, kControlEntireControl, kControlEditTextCFStringTag, sizeof(CFStringRef), &theText, &actualSize); if(err == noErr) { *outStr = theText; isOK = true; } } break; } } ::DisposeDialog(dialogPtr); return isOK; } //optimization - called early to check if paasword or clipboard will be used void PrescanCommandDescription( CommandDescription &currCommand ) { if(currCommand.command == NULL) return; CFIndex theCount = ::CFArrayGetCount(currCommand.command); for(CFIndex i = 0; i < theCount; i++ ) { CFTypeRef theItem = ::CFArrayGetValueAtIndex(currCommand.command, i); if( (theItem != NULL) && ( ::CFGetTypeID(theItem) == ::CFStringGetTypeID() ) ) { CFStringRef fragmentRef = (CFStringRef)theItem; if(fragmentRef != NULL) { if( kCFCompareEqualTo == ::CFStringCompare( fragmentRef, CFSTR("__OBJ_TEXT__"), 0 ) ) { currCommand.mayNeedClipboard = true; } else if( kCFCompareEqualTo == ::CFStringCompare( fragmentRef, CFSTR("__PASSWORD__"), 0 ) ) { currCommand.containsPassword = true; } else if( kCFCompareEqualTo == ::CFStringCompare( fragmentRef, CFSTR("__INPUT_TEXT__"), 0 ) ) { currCommand.containsInputText = true; } else if( (kCFCompareEqualTo == ::CFStringCompare( fragmentRef, CFSTR("__SAVE_AS_PATH__"), 0 )) || (kCFCompareEqualTo == ::CFStringCompare( fragmentRef, CFSTR("__SAVE_AS_PARENT_PATH__"), 0 )) || (kCFCompareEqualTo == ::CFStringCompare( fragmentRef, CFSTR("__SAVE_AS_FILE_NAME__"), 0 )) ) { currCommand.containsSaveAsPath = true; } } } } } //Apple advocates CFURLs //but sending CFURLs to Finder does not seem to work. //we receive error: errAECantHandleClass = -10010, //Reworked the function to create alias first #if 0 void RefreshObjectsInFinder_CFURL(CMPluginImplData &inData) { if( (inData.objectList == NULL) || (inData.objectCount == 0) ) return; OSStatus err; AEDescList refreshList = {typeNull, NULL}; err = ::AECreateList( NULL, 0, false, &refreshList ); if(err != noErr) return; TRACE_STR( "\pOnMyCommandCM. RefreshObjectsInFinder" ); StAEDesc listDel(refreshList); for(UInt32 i = 0; i < inData.objectCount; i++) { if( inData.objectList[i].mRefreshPath != NULL) { #if _DEBUG_ Str255 theString; if( ::CFStringGetPascalString(inData.objectList[i].mRefreshPath, theString, sizeof(Str255), kCFStringEncodingMacRoman) ) { DEBUG_STR( theString ); } #endif //_DEBUG_ CFURLRef oneURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault, inData.objectList[i].mRefreshPath, kCFURLPOSIXPathStyle, false); if(oneURL != NULL) { CFObjDel theDel(oneURL); StAEDesc urlDesc; err = FURLCreateDescFromCFURL(oneURL, urlDesc); if(err == noErr) { err = ::AEPutDesc( &refreshList, 0, urlDesc ); } else { DEBUG_STR( "\pOnMyCommandCM. RefreshObjectsInFinder. FURLCreateDescFromCFURL failed" ); } } else { DEBUG_STR( "\pOnMyCommandCM. RefreshObjectsInFinder. CFURLCreateWithFileSystemPath failed" ); } } } SInt32 listItemsCount = 0; if( ::AECountItems( &refreshList, &listItemsCount) == noErr ) { if(listItemsCount > 0) { err = CMUtils::SendAppleEventToFinder( kAEFinderSuite, kAESync, refreshList, false, inData.mRunningInFinder ); if(err != noErr) { DEBUG_STR( "\pOnMyCommandCM. RefreshObjectsInFinder. SendAppleEventToFinder failed" ); } } else { DEBUG_STR( "\pOnMyCommandCM. RefreshObjectsInFinder. refresh list empty" ); } } } #endif //0 void RefreshObjectsInFinder(CMPluginImplData &inData) { if( (inData.objectList == NULL) || (inData.objectCount == 0) ) return; OSStatus err; AEDescList refreshList = {typeNull, NULL}; err = ::AECreateList( NULL, 0, false, &refreshList ); if(err != noErr) return; TRACE_STR( "\pOnMyCommandCM. RefreshObjectsInFinder" ); StAEDesc listDel(refreshList); for(UInt32 i = 0; i < inData.objectCount; i++) { if( inData.objectList[i].mRefreshPath != NULL) { #if _DEBUG_ Str255 theString; if( ::CFStringGetPascalString(inData.objectList[i].mRefreshPath, theString, sizeof(Str255), kCFStringEncodingMacRoman) ) { DEBUG_STR( theString ); } #endif //_DEBUG_ CFURLRef oneURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault, inData.objectList[i].mRefreshPath, kCFURLPOSIXPathStyle, false); if(oneURL != NULL) { CFObjDel theDel(oneURL); StAEDesc urlDesc; err = CMUtils::CreateAliasDesc( oneURL, urlDesc ); if(err == noErr) { err = ::AEPutDesc( &refreshList, 0, urlDesc ); } else { DEBUG_STR( "\pOnMyCommandCM. RefreshObjectsInFinder. CreateAliasDesc failed" ); } //also, to make it more powerful when new files are deleted and created, we will call FNNotify for parent folder CFURLRef parentURL = ::CFURLCreateCopyDeletingLastPathComponent( kCFAllocatorDefault, oneURL ); if(parentURL != NULL) { CFObjDel parUrlDel(parentURL); FSRef fileRef; if( ::CFURLGetFSRef(parentURL, &fileRef) ) { err = FNNotify( &fileRef, kFNDirectoryModifiedMessage, kNilOptions); if(err != noErr) { DEBUG_STR( "\pOnMyCommandCM. RefreshObjectsInFinder. FNNotify failed" ); } } else { DEBUG_STR( "\pOnMyCommandCM. RefreshObjectsInFinder. parent folder does not exist" ); } } } else { DEBUG_STR( "\pOnMyCommandCM. RefreshObjectsInFinder. CFURLCreateWithFileSystemPath failed" ); } } } SInt32 listItemsCount = 0; if( ::AECountItems( &refreshList, &listItemsCount) == noErr ) { if(listItemsCount > 0) { err = CMUtils::SendAppleEventToFinder( kAEFinderSuite, kAESync, refreshList, false, inData.mRunningInFinder ); if(err != noErr) { DEBUG_STR( "\pOnMyCommandCM. RefreshObjectsInFinder. SendAppleEventToFinder failed" ); } //some people recommend sending refresh event to Finder twice //but it does not seem to help because Finder sometimes just refuses to refresh //err = CMUtils::SendAppleEventToFinder( kAEFinderSuite, kAESync, refreshList, false, inData.mRunningInFinder ); } else { DEBUG_STR( "\pOnMyCommandCM. RefreshObjectsInFinder. refresh list empty" ); } } } #pragma mark - TCFArray * CreateSubmenuList(AEDescList* ioRootMenu, const CMPluginImplData &inData) { //go through commands and find all unique submenus. then add them to our list if( (inData.commandList == NULL) || (inData.commandCount == 0) ) return NULL; TCFArray *outList = new TCFArray(); //add default menu SubmenuAndName oneItem; oneItem.name = CFSTR("On My Command"); oneItem.isDefault = true; oneItem.isMain = false; oneItem.padding = 0; OSErr err = ::AECreateList( NULL, 0, false, &oneItem.submenu ); if( err == noErr ) { outList->AddItem(oneItem); } CFStringRef subName; for(UInt32 i = 0; i < inData.commandCount; i++) { subName = inData.commandList[i].submenuName; if( ! FindSubmenuByName(outList, subName, oneItem) ) { if(subName != NULL) {//check for special case of root menu if( kCFCompareEqualTo == ::CFStringCompare( subName, CFSTR(".."), 0) ) { oneItem.submenu = *ioRootMenu;//we will not dispose of it oneItem.name = CFSTR(".."); oneItem.isDefault = false; oneItem.isMain = true; oneItem.padding = 0; outList->AddItem(oneItem); } else//other submenu { oneItem.name = subName; oneItem.isDefault = false; oneItem.isMain = false; oneItem.padding = 0; err = ::AECreateList( NULL, 0, false, &oneItem.submenu ); if( err == noErr ) { outList->AddItem(oneItem); } } } } } return outList; } void DeleteSubmenuList(const TCFArray *inList) { if(inList == NULL) return; CFIndex theCount = inList->GetCount(); SubmenuAndName oneItem; for(CFIndex i = 0; i < theCount; i++) { if( inList->FetchItemAt( i, oneItem ) ) { if( (oneItem.isMain == false) && (oneItem.submenu.dataHandle != NULL) ) { ::AEDisposeDesc( &oneItem.submenu ); } } } delete inList; } Boolean FindSubmenuByName(const TCFArray *inList, CFStringRef inName, SubmenuAndName &outFoundItem) { if(inList == NULL) return false; CFIndex theCount = inList->GetCount(); CFIndex theLen = 0; if(inName != NULL) { theLen = ::CFStringGetLength(inName); } SubmenuAndName oneItem; for(CFIndex i = 0; i < theCount; i++) { if( inList->FetchItemAt( i, oneItem ) ) { if( (theLen == 0) && oneItem.isDefault ) {//name null or zero length means default submenu outFoundItem = oneItem; return true; } else if(inName != NULL) { if( kCFCompareEqualTo == ::CFStringCompare( inName, oneItem.name, 0) ) { outFoundItem = oneItem; return true; } } } } return false; } void PopulateItemsMenu(TCFArray *inSubmenuList, const CMPluginImplData &inData) { TRACE_STR( "\pOnMyCommandCM. PopulateItemsMenu" ); if( inData.commandList == NULL ) return; OSStatus err = noErr; Boolean doActivate; for(UInt32 i = 0; i < inData.commandCount; i++) { doActivate = false; switch(inData.commandList[i].activationMode) { case kActiveAlways: doActivate = true; break; case kActiveFile: { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckIfFile, NULL); if(doActivate) { Boolean needsFileTypeCheck = (( inData.commandList[i].activationTypes != NULL ) && ( inData.commandList[i].activationTypeCount != 0) ); Boolean needsExtensionCheck = (inData.commandList[i].activationExtensions != NULL); if(needsExtensionCheck) { needsExtensionCheck = (::CFArrayGetCount(inData.commandList[i].activationExtensions) > 0); } if(needsFileTypeCheck && needsExtensionCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckFileTypeOrExtension, &(inData.commandList[i])); } else if(needsFileTypeCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckFileType, &(inData.commandList[i])); } else if(needsExtensionCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckExtension, &(inData.commandList[i])); } } } break; case kActiveFolder: { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckIfFolder, NULL); if(doActivate) { Boolean needsExtensionCheck = (inData.commandList[i].activationExtensions != NULL); if(needsExtensionCheck) { needsExtensionCheck = (::CFArrayGetCount(inData.commandList[i].activationExtensions) > 0); } if(needsExtensionCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckExtension, &(inData.commandList[i])); } } } break; case kActiveFolderExcludeFinderWindow: { doActivate = !inData.mIsOpenFolder; if(doActivate) doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckIfFolder, NULL); if(doActivate) { Boolean needsExtensionCheck = (inData.commandList[i].activationExtensions != NULL); if(needsExtensionCheck) { needsExtensionCheck = (::CFArrayGetCount(inData.commandList[i].activationExtensions) > 0); } if(needsExtensionCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckExtension, &(inData.commandList[i])); } } } break; case kActiveFileOrFolder: { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckIfFileOrFolder, NULL); if(doActivate) { Boolean needsFileTypeCheck = (( inData.commandList[i].activationTypes != NULL ) && ( inData.commandList[i].activationTypeCount != 0) ); Boolean needsExtensionCheck = (inData.commandList[i].activationExtensions != NULL); if(needsExtensionCheck) { needsExtensionCheck = (::CFArrayGetCount(inData.commandList[i].activationExtensions) > 0); } if(needsFileTypeCheck && needsExtensionCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckFileTypeOrExtension, &(inData.commandList[i])); } else if(needsFileTypeCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckFileType, &(inData.commandList[i])); } else if(needsExtensionCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckExtension, &(inData.commandList[i])); } } } break; case kActiveFileOrFolderExcludeFinderWindow: { doActivate = !inData.mIsOpenFolder; if(doActivate) doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckIfFileOrFolder, NULL); if(doActivate) { Boolean needsFileTypeCheck = (( inData.commandList[i].activationTypes != NULL ) && ( inData.commandList[i].activationTypeCount != 0) ); Boolean needsExtensionCheck = (inData.commandList[i].activationExtensions != NULL); if(needsExtensionCheck) { needsExtensionCheck = (::CFArrayGetCount(inData.commandList[i].activationExtensions) > 0); } if(needsFileTypeCheck && needsExtensionCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckFileTypeOrExtension, &(inData.commandList[i])); } else if(needsFileTypeCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckFileType, &(inData.commandList[i])); } else if(needsExtensionCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckExtension, &(inData.commandList[i])); } } } break; case kActiveFinderWindow: { doActivate = inData.mIsOpenFolder; if(doActivate) { Boolean needsExtensionCheck = (inData.commandList[i].activationExtensions != NULL); if(needsExtensionCheck) { needsExtensionCheck = (::CFArrayGetCount(inData.commandList[i].activationExtensions) > 0); } if(needsExtensionCheck) { doActivate = CheckAllObjects(inData.objectList, inData.objectCount, CheckExtension, &(inData.commandList[i])); } } } break; case kActiveSelectedText: doActivate = inData.mIsTextSelected; break; case kActiveClipboardText: doActivate = inData.mIsTextInClipboard; break; case kActiveSelectedOrClipboardText: doActivate = (inData.mIsTextSelected || inData.mIsTextInClipboard); break; } if(doActivate) { SubmenuAndName oneItem; if( FindSubmenuByName(inSubmenuList, inData.commandList[i].submenuName, oneItem) ) { err = CMUtils::AddCommandToAEDescList(inData.commandList[i].name, kCMCommandStart + i, (inData.sysVersion >= 0x1020), &(oneItem.submenu) ); } else { DEBUG_STR( "\pOnMyCommandCM. PopulateItemsMenu. FindSubmenuByName unexpectedly failed. Aliens?" ); } } } } void AttachSubmenus(AEDescList *rootMenu, TCFArray *inList) { if(inList == NULL) return; SubmenuAndName oneItem; CFIndex theCount = inList->GetCount(); for(CFIndex i = 0; i < theCount; i++) { if( inList->FetchItemAt( i, oneItem ) ) { SInt32 itemsCount = 0; if( ::AECountItems( &(oneItem.submenu), &itemsCount) == noErr ) { if(itemsCount > 0) {//attach only submenus which have any items if(oneItem.isDefault) { CMUtils::AddSubmenu( rootMenu, kCommandStrings, kMainMenuStrIndx, oneItem.submenu ); } else if(oneItem.isMain) { ;//do not attach it to itself - items are already added to root menu } else { CMUtils::AddSubmenu( rootMenu, oneItem.name, oneItem.submenu ); } } } } } } //all objects must meet the condition checked by procedure: logical AND Boolean CheckAllObjects(OneObjProperties *objList, UInt32 inCount, ObjCheckingProc inProcPtr, void *inProcData) { if( (objList == NULL) || (inProcPtr == NULL) ) return false; OneObjProperties *oneObj; for(UInt32 i = 0; i < inCount; i++) { oneObj = objList + i; if(oneObj != NULL) { if(false == (*inProcPtr)( oneObj, inProcData ) ) return false; } else return false; } return true; } inline Boolean CheckIfFile(const OneObjProperties *inObj, void *) { return ((inObj->mFlags & kLSItemInfoIsPlainFile) != 0); } inline Boolean CheckIfFolder(const OneObjProperties *inObj, void *) { return ((inObj->mFlags & kLSItemInfoIsContainer) != 0); } inline Boolean CheckIfFileOrFolder(const OneObjProperties *inObj, void *) { return ((inObj->mFlags & (kLSItemInfoIsPlainFile | kLSItemInfoIsContainer)) != 0); } inline Boolean CheckFileType(const OneObjProperties *inObj, void *inData) { if(inData == NULL) return false; CommandDescription *commDesc = (CommandDescription *)inData; if( commDesc->activationTypes == NULL ) return true; if( commDesc->activationTypeCount == 0) return true; for(UInt32 i = 0; i < commDesc->activationTypeCount; i++) { if( commDesc->activationTypes[i] == inObj->mType ) { return true;//a match was found } } return false; } inline Boolean CheckExtension(const OneObjProperties *inObj, void *inData) { TRACE_STR( "\pOnMyCommandCM. CheckExtension" ); if(inData == NULL) return false; CommandDescription *commDesc = (CommandDescription *)inData; if(commDesc->activationExtensions == NULL) return true;//no extensions required - treat it as a match CFIndex theCount = ::CFArrayGetCount(commDesc->activationExtensions); if(theCount == 0) return true;//no extensions required - treat it as a match if(inObj->mExtension == NULL) return false;//no extension - it cannot be matched #if _DEBUG_ { Str255 hexString; ByteCount destLen = sizeof(Str255); CMUtils::BufToHex( (const unsigned char *)&inObj->mExtension, (char *)hexString, sizeof(inObj->mExtension), destLen ); ::CopyCStringToPascal( (char *)hexString, hexString); DEBUG_STR( "\p\tinObj->mExtension = " ); DEBUG_STR(hexString); } #endif //_DEBUG_ CFIndex theLen = ::CFStringGetLength(inObj->mExtension); if(theLen == 0) return false;//no extension - it cannot be matched CFTypeRef theItem; CFStringRef theExt; for(CFIndex i = 0; i < theCount; i++) { theItem = ::CFArrayGetValueAtIndex(commDesc->activationExtensions, i); if( (theItem != NULL) && ( ::CFGetTypeID(theItem) == ::CFStringGetTypeID() ) ) { theExt = (CFStringRef)theItem; if( kCFCompareEqualTo == ::CFStringCompare( inObj->mExtension, theExt, kCFCompareCaseInsensitive) ) { return true;//a match found } } } return false; } inline Boolean CheckFileTypeOrExtension(const OneObjProperties *inObj, void *inData) { return (CheckFileType(inObj, inData) || CheckExtension(inObj, inData)); } #pragma mark - void ReadPreferences(CMPluginImplData &ioData) { TRACE_STR( "\pOnMyCommandCM. ReadPreferences" ); if( ioData.commandList != NULL ) { DeleteCommandList(ioData); } ioData.commandCount = 0; CFStringRef prefsIdentifier = CFSTR(CM_IMPL_PLUGIN_PREFS_INDENTIFIER); ::CFPreferencesAppSynchronize( prefsIdentifier ); Boolean isValid = false; CFIndex theState = 0; CFIndex theVer = ::CFPreferencesGetAppIntegerValue( CFSTR("VERSION"), prefsIdentifier, &isValid ); if( isValid && (theVer == 2) ) { isValid = false; CFPropertyListRef resultRef = ::CFPreferencesCopyAppValue( CFSTR("COMMAND_LIST"), prefsIdentifier ); if(resultRef != NULL) { CFObjDel refDel(resultRef); if( ::CFGetTypeID(resultRef) == ::CFArrayGetTypeID() ) { CFArrayRef commandArrayRef = (CFArrayRef)resultRef; CFIndex theCount = ::CFArrayGetCount(commandArrayRef); ioData.commandList = new CommandDescription[theCount]; ::BlockZero(ioData.commandList, theCount*sizeof(CommandDescription)); ioData.commandCount = theCount; CFTypeID dictType = ::CFDictionaryGetTypeID(); for( CFIndex i = 0; i < theCount; i++ ) { ioData.commandList[i] = kEmptyCommand; CFTypeRef theItem = ::CFArrayGetValueAtIndex( commandArrayRef, i); if( (theItem != NULL) && (::CFGetTypeID(theItem) == dictType) ) { GetOneCommandParams( ioData.commandList[i], (CFDictionaryRef)theItem ); } else { DEBUG_STR( "\pOnMyCommandCM. ReadPreferences: wrong array item: null or not dict" ); } } } else { DEBUG_STR( "\pOnMyCommandCM. ReadPreferences: COMMAND_LIST not type of array" ); } } else { DEBUG_STR( "\pOnMyCommandCM. ReadPreferences: no COMMAND_LIST array" ); } } else { DEBUG_STR( "\pOnMyCommandCM. ReadPreferences: invalid version number" ); } } void GetOneCommandParams(CommandDescription &outDesc, CFDictionaryRef inOneCommand) { TRACE_STR( "\pOnMyCommandCM. GetOneCommandParams" ); CFStringRef theStr; CFTypeID stringType = ::CFStringGetTypeID(); CFTypeID arrayType = ::CFArrayGetTypeID(); //name CFTypeRef theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("NAME") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.name = (CFStringRef)theResult; ::CFRetain(outDesc.name);//keep the string, we are responsible for releasing it #if _TRACE_ TRACE_STR( "\p\tGetOneCommandParams. command name:" ); Str255 theString; if( ::CFStringGetPascalString(outDesc.name, theString, sizeof(Str255), kCFStringEncodingMacRoman) ) { TRACE_STR( theString ); } else { DEBUG_STR( "\p\tGetOneCommandParams. CFStringGetPascalString failed" ); } #endif } else { TRACE_STR( "\p\tGetOneCommandParams. NAME param not of type CFString" ); } } else { TRACE_STR( "\p\tGetOneCommandParams. NAME param not defined" ); } //command array theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("COMMAND") ); if(theResult != NULL) { if(arrayType == ::CFGetTypeID(theResult) ) { outDesc.command = (CFArrayRef)theResult; ::CFRetain(outDesc.command);//keep the array, we are responsible for releasing it } else { DEBUG_STR( "\p\tGetOneCommandParams. COMMAND param not of type CFArray" ); } } else { TRACE_STR( "\p\tGetOneCommandParams. COMMAND param not defined" ); } //execution theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("EXECUTION_MODE") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { theStr = (CFStringRef)theResult; #if _TRACE_ TRACE_STR( "\p\tGetOneCommandParams. EXECUTION_MODE string:" ); Str255 theString; if( ::CFStringGetPascalString(theStr, theString, sizeof(Str255), kCFStringEncodingMacRoman) ) { TRACE_STR( theString ); } else { DEBUG_STR( "\p\tGetOneCommandParams. CFStringGetPascalString failed" ); } #endif if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("exe_silent"), 0 ) ) { outDesc.executionMode = kExecSilent; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("exe_terminal"), 0 ) ) { outDesc.executionMode = kExecTerminal; } } else { DEBUG_STR( "\p\tGetOneCommandParams. EXECUTION_MODE param not of type CFString" ); } } //activation theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("ACTIVATION_MODE") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { theStr = (CFStringRef)theResult; #if _TRACE_ TRACE_STR( "\p\tGetOneCommandParams. ACTIVATION_MODE string:" ); Str255 theString; if( ::CFStringGetPascalString(theStr, theString, sizeof(Str255), kCFStringEncodingMacRoman) ) { TRACE_STR( theString ); } else { DEBUG_STR( "\p\tGetOneCommandParams. CFStringGetPascalString failed" ); } #endif if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("act_always"), 0 ) ) { outDesc.activationMode = kActiveAlways; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("act_file"), 0 ) ) { outDesc.activationMode = kActiveFile; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("act_folder"), 0 ) ) { outDesc.activationMode = kActiveFolder; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("act_file_or_folder"), 0 ) ) { outDesc.activationMode = kActiveFileOrFolder; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("act_finder_window"), 0 ) ) { outDesc.activationMode = kActiveFinderWindow; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("act_selected_text"), 0 ) ) { outDesc.activationMode = kActiveSelectedText; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("act_clipboard_text"), 0 ) ) { outDesc.activationMode = kActiveClipboardText; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("act_selected_or_clipboard_text"), 0 ) ) { outDesc.activationMode = kActiveSelectedOrClipboardText; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("act_folder_not_finder_window"), 0 ) ) { outDesc.activationMode = kActiveFolderExcludeFinderWindow; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("act_file_or_folder_not_finder_window"), 0 ) ) { outDesc.activationMode = kActiveFileOrFolderExcludeFinderWindow; } } else { DEBUG_STR( "\p\tGetOneCommandParams. ACTIVATION_MODE param not of type CFString" ); } } //escaping theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("ESCAPE_SPECIAL_CHARS") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { theStr = (CFStringRef)theResult; #if _TRACE_ TRACE_STR( "\p\tGetOneCommandParams. ESCAPE_SPECIAL_CHARS string:" ); Str255 theString; if( ::CFStringGetPascalString(theStr, theString, sizeof(Str255), kCFStringEncodingMacRoman) ) { TRACE_STR( theString ); } else { DEBUG_STR( "\p\tGetOneCommandParams. CFStringGetPascalString failed" ); } #endif if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("esc_none"), 0 ) ) { outDesc.escapeSpecialCharsMode = kEscapeNone; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("esc_with_backslash"), 0 ) ) { outDesc.escapeSpecialCharsMode = kEscapeWithBackslash; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("esc_with_percent"), 0 ) ) { outDesc.escapeSpecialCharsMode = kEscapeWithPercent; } } else { DEBUG_STR( "\p\tGetOneCommandParams. ESCAPE_SPECIAL_CHARS param not of type CFString" ); } } //file types theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("ACTIVATION_FILE_TYPES") ); if(theResult != NULL) { if(arrayType == ::CFGetTypeID(theResult) ) { CFArrayRef typeArr = (CFArrayRef)theResult; CFIndex theCount = ::CFArrayGetCount(typeArr); if(theCount > 0) { outDesc.activationTypes = new FileType[theCount]; ::BlockZero(outDesc.activationTypes, theCount*sizeof(FileType)); outDesc.activationTypeCount = theCount; for(CFIndex i = 0; i < theCount; i++) { outDesc.activationTypes[i] = 0; CFTypeRef theItem = ::CFArrayGetValueAtIndex(typeArr, i); if( (theItem != NULL) && ( ::CFGetTypeID(theItem) == ::CFStringGetTypeID() ) ) { CFStringRef typeStrRef = (CFStringRef)theItem; outDesc.activationTypes[i] = CFStringToFourCharCode(typeStrRef); } } } } else { DEBUG_STR( "\p\tGetOneCommandParams. ACTIVATION_FILE_TYPES param not of type CFString" ); } } //extensions theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("ACTIVATION_EXTENSIONS") ); if(theResult != NULL) { if(arrayType == ::CFGetTypeID(theResult) ) { outDesc.activationExtensions = (CFArrayRef)theResult; ::CFRetain(outDesc.activationExtensions);//keep the array, we are responsible for releasing it } else { DEBUG_STR( "\p\tGetOneCommandParams. ACTIVATION_EXTENSIONS param not of type CFArray" ); } } //mutiple objects settings theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("MULTIPLE_OBJECT_SETTINGS") ); if(theResult != NULL) { if( ::CFGetTypeID(theResult) == ::CFDictionaryGetTypeID() ) { GetMultiCommandParams( outDesc, (CFDictionaryRef)theResult ); } else { DEBUG_STR( "\pOnMyCommandCM. ReadPreferences: wrong item type: not dict" ); } } //bring to front theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("TERM_BRING_TO_FRONT") ); if(theResult != NULL) { if( ::CFGetTypeID(theResult) == ::CFBooleanGetTypeID() ) { outDesc.bringTerminalToFront = CFBooleanGetValue( (CFBooleanRef)theResult ); } else { DEBUG_STR( "\pOnMyCommandCM. ReadPreferences: wrong item type: not Boolean" ); } } // new session theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("TERM_OPEN_NEW_SESSION") ); if(theResult != NULL) { if( ::CFGetTypeID(theResult) == ::CFBooleanGetTypeID() ) { outDesc.openNewTerminalSession = CFBooleanGetValue( (CFBooleanRef)theResult ); } else { DEBUG_STR( "\pOnMyCommandCM. ReadPreferences: wrong item type: not Boolean" ); } } //warning theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("WARNING") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.warningStr = (CFStringRef)theResult; ::CFRetain(outDesc.warningStr);//keep the string, we are responsible for releasing it } } //cancel button name theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("WARNING_CANCEL") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.warningCancelStr = (CFStringRef)theResult; ::CFRetain(outDesc.warningCancelStr);//keep the string, we are responsible for releasing it } } //execute button name theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("WARNING_EXECUTE") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.warningExecuteStr = (CFStringRef)theResult; ::CFRetain(outDesc.warningExecuteStr);//keep the string, we are responsible for releasing it } } //submenu theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("SUBMENU_NAME") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.submenuName = (CFStringRef)theResult; ::CFRetain(outDesc.submenuName);//keep the string, we are responsible for releasing it } } //input dialog theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("INPUT_DIALOG") ); if(theResult != NULL) { if( ::CFGetTypeID(theResult) == ::CFDictionaryGetTypeID() ) { GetInputDialogParams( outDesc, (CFDictionaryRef)theResult ); } else { DEBUG_STR( "\pOnMyCommandCM. ReadPreferences: wrong item type: not dict" ); } } //Finder refresh path theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("REFRESH_PATH") ); if(theResult != NULL) { if(arrayType == ::CFGetTypeID(theResult) ) { outDesc.refresh = (CFArrayRef)theResult; ::CFRetain(outDesc.refresh);//keep the array, we are responsible for releasing it } else { DEBUG_STR( "\p\tGetOneCommandParams. REFRESH_PATH param not of type CFArray" ); } } } void GetMultiCommandParams(CommandDescription &outDesc, CFDictionaryRef inOneCommand) { CFStringRef theStr; CFTypeRef theResult; CFTypeID stringType = ::CFStringGetTypeID(); theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("PROCESSING_MODE") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { theStr = (CFStringRef)theResult; if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("proc_separately"), 0 ) ) { outDesc.multipleObjectProcessing = kMulObjProcessSeparately; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("proc_together"), 0 ) ) { outDesc.multipleObjectProcessing = kMulObjProcessTogether; } } else { DEBUG_STR( "\p\tGetMultiCommandParams. PROCESSING_MODE param not of type CFString" ); } } else { TRACE_STR( "\p\tGetMultiCommandParams. PROCESSING_MODE param not defined" ); } if(outDesc.multipleObjectProcessing == kMulObjProcessTogether) {//read other settings only when multiprocessing is turned on theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("PREFIX") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.mulObjPrefix = (CFStringRef)theResult; ::CFRetain(outDesc.mulObjPrefix);//keep the string, we are responsible for releasing it } } theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("SUFFIX") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.mulObjSuffix = (CFStringRef)theResult; ::CFRetain(outDesc.mulObjSuffix);//keep the string, we are responsible for releasing it } } theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("SEPARATOR") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.mulObjSeparator = (CFStringRef)theResult; ::CFRetain(outDesc.mulObjSeparator);//keep the string, we are responsible for releasing it } } } } void GetInputDialogParams(CommandDescription &outDesc, CFDictionaryRef inOneCommand) { CFStringRef theStr; CFTypeRef theResult; CFTypeID stringType = ::CFStringGetTypeID(); theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("INPUT_TYPE") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { theStr = (CFStringRef)theResult; if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("input_clear_text"), 0 ) ) { outDesc.inputDialogType = kInputClearText; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("input_password_text"), 0 ) ) { outDesc.inputDialogType = kInputPasswordText; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("input_popup_menu"), 0 ) ) { outDesc.inputDialogType = kInputPopupMenu; } else if( kCFCompareEqualTo == ::CFStringCompare( theStr, CFSTR("input_combo_box"), 0 ) ) { outDesc.inputDialogType = kInputComboBox; } } else { DEBUG_STR( "\p\tGetInputDialogParams. INPUT_TYPE param not of type CFString" ); } } else { TRACE_STR( "\p\tGetInputDialogParams. INPUT_TYPE param not defined" ); } theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("OK_BUTTON_NAME") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.inputDialogOK = (CFStringRef)theResult; ::CFRetain(outDesc.inputDialogOK);//keep the string, we are responsible for releasing it } } theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("CANCEL_BUTTON_NAME") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.inputDialogCancel = (CFStringRef)theResult; ::CFRetain(outDesc.inputDialogCancel);//keep the string, we are responsible for releasing it } } theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("MESSAGE") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.inputDialogMessage = (CFStringRef)theResult; ::CFRetain(outDesc.inputDialogMessage);//keep the string, we are responsible for releasing it } } theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("DEFAULT") ); if(theResult != NULL) { if(stringType == ::CFGetTypeID(theResult) ) { outDesc.inputDialogDefault = (CFStringRef)theResult; ::CFRetain(outDesc.inputDialogDefault);//keep the string, we are responsible for releasing it } } if((outDesc.inputDialogType == kInputPopupMenu) || (outDesc.inputDialogType == kInputComboBox)) {//read menu items only when dialog input type is popup menu theResult = ::CFDictionaryGetValue( inOneCommand, CFSTR("INPUT_MENU") ); if(theResult != NULL) { if(::CFArrayGetTypeID() == ::CFGetTypeID(theResult) ) { outDesc.inputDialogMenuItems = (CFArrayRef)theResult; ::CFRetain(outDesc.inputDialogMenuItems);//keep the array, we are responsible for releasing it } else { DEBUG_STR( "\p\tGetOneCommandParams. INPUT_MENU param not of type CFArray" ); } } else { TRACE_STR( "\p\tGetOneCommandParams. INPUT_MENU param not defined" ); } } } void DeleteCommandList(CMPluginImplData &ioData) { if( ioData.commandList != NULL ) { for(UInt32 i = 0; i < ioData.commandCount; i++) { if(ioData.commandList[i].name != NULL) ::CFRelease(ioData.commandList[i].name); if(ioData.commandList[i].command != NULL) ::CFRelease(ioData.commandList[i].command); if(ioData.commandList[i].activationExtensions != NULL) ::CFRelease(ioData.commandList[i].activationExtensions); if( ioData.commandList[i].activationTypes != NULL ) delete [] ioData.commandList[i].activationTypes; if(ioData.commandList[i].mulObjPrefix != NULL) ::CFRelease(ioData.commandList[i].mulObjPrefix); if(ioData.commandList[i].mulObjSuffix != NULL) ::CFRelease(ioData.commandList[i].mulObjSuffix); if(ioData.commandList[i].mulObjSeparator != NULL) ::CFRelease(ioData.commandList[i].mulObjSeparator); if(ioData.commandList[i].warningStr != NULL) ::CFRelease(ioData.commandList[i].warningStr); if(ioData.commandList[i].submenuName != NULL) ::CFRelease(ioData.commandList[i].submenuName); if(ioData.commandList[i].inputDialogOK != NULL) ::CFRelease(ioData.commandList[i].inputDialogOK); if(ioData.commandList[i].inputDialogCancel != NULL) ::CFRelease(ioData.commandList[i].inputDialogCancel); if(ioData.commandList[i].inputDialogMessage != NULL) ::CFRelease(ioData.commandList[i].inputDialogMessage); if(ioData.commandList[i].inputDialogDefault != NULL) ::CFRelease(ioData.commandList[i].inputDialogDefault); if(ioData.commandList[i].inputDialogMenuItems != NULL) ::CFRelease(ioData.commandList[i].inputDialogMenuItems); if(ioData.commandList[i].refresh != NULL) ::CFRelease(ioData.commandList[i].refresh); } delete [] ioData.commandList; ioData.commandList = NULL; } ioData.commandCount = 0; } void DeleteObjectList(CMPluginImplData &ioData) { if(ioData.objectList != NULL) { OneObjProperties *oneObj; for(CFIndex i = 0; i < ioData.objectCount; i++) { oneObj = ioData.objectList + i; if( oneObj->mExtension != NULL ) { ::CFRelease( oneObj->mExtension ); } if( oneObj->mRefreshPath != NULL ) { ::CFRelease( oneObj->mRefreshPath ); } } delete [] ioData.objectList; ioData.objectList = NULL; } } FourCharCode CFStringToFourCharCode(CFStringRef inStrRef) { if(inStrRef == NULL) return 0; FourCharCode outValue = 0; Str31 pascalString = {0,0,0,0,0};//zero the fields we are interested in if( ::CFStringGetPascalString(inStrRef, pascalString, sizeof(pascalString), kCFStringEncodingMacRoman) ) { short theLen = pascalString[0]; if(theLen > 4) theLen = 4; else if(theLen < 4) { *(long *)(pascalString+theLen+1) = 0;//terminate with a couple of zeros } outValue = *(FourCharCode*)(pascalString+1); } return outValue; } #define SwapHiLoBytes(x) ( (unsigned short)((unsigned char)(x)<<8) | ((unsigned short)(x) >> 8) ) CFStringRef CreateCFStringOfTextFromClipboard() { TRACE_STR( "\pCreateCFStringOfTextFromClipboard" ); CFStringRef outString = NULL; ScrapRef scrap; if( ::GetCurrentScrap(&scrap) == noErr ) { SInt32 byteCount = 0; //try with Unicode text first if( ::GetScrapFlavorSize(scrap, 'utxt', &byteCount) == noErr ) { UniChar *newBuffer = (UniChar *)::NewPtrClear(byteCount); if(newBuffer != NULL) { if( ::GetScrapFlavorData(scrap, 'utxt', &byteCount, newBuffer) == noErr) { UniChar *theText = newBuffer; UniCharCount theCount = byteCount/sizeof(UniChar); if(theText[0] == 0xFEFF) { theText++; theCount--; } else if(theText[0] == 0xFFFE) {//how the heck the text in clipboard might be in swapped form? theText++; theCount--; for(SInt32 i = 0; i< theCount; i++ ) { theText[i] = SwapHiLoBytes(theText[i]); } } outString = ::CFStringCreateWithCharacters(kCFAllocatorDefault, theText, theCount ); } ::DisposePtr( (Ptr)newBuffer ); } } else if( ::GetScrapFlavorSize(scrap, 'TEXT', &byteCount) == noErr ) { char *newBuffer = ::NewPtrClear(byteCount); if(newBuffer != NULL) { if( ::GetScrapFlavorData(scrap, 'TEXT', &byteCount, newBuffer) == noErr) { outString = ::CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)newBuffer, byteCount, CFStringGetSystemEncoding(), true); } ::DisposePtr( (Ptr)newBuffer ); } } } return outString; } Boolean IsTextInClipboard() { Boolean outTextPresent = false; ScrapRef scrap; if( ::GetCurrentScrap(&scrap) == noErr ) { SInt32 byteCount = 0; if( ::GetScrapFlavorSize(scrap, 'utxt', &byteCount) == noErr ) outTextPresent = (byteCount > 0); if( (outTextPresent == false) && (::GetScrapFlavorSize(scrap, 'TEXT', &byteCount) == noErr) ) outTextPresent = (byteCount > 0); } return outTextPresent; } /* ReplaceSpecialCharsWithBackslashEscapes modifies path so the special characters in filename will not be interpreted by shell. As a test, I created in Finder a folder named: #`~!@$%^&*()_+={}[-]|\;'",.<>?/ This function created an escaped version of it: #\`~\!@\$%^\&\*\(\)_+=\{\}\[-\]\|\\\;\'\",.\<\>\?: and it works fine in Terminal. By the way: note that the '/' in Finder is represented as ':' in Terminal You may create a file with ':' char in name in Terminal, but not in Finder and you can create a file with '/' char in name in Finder but not in Terminal. */ void ReplaceSpecialCharsWithBackslashEscapes(CFMutableStringRef inStrRef) { if(inStrRef == NULL) return; //replace spaces in path with backslash + space CFIndex idx = ::CFStringGetLength(inStrRef) - 1; UniChar currChar = 0; while(idx >= 0) { currChar = ::CFStringGetCharacterAtIndex( inStrRef, idx); if( (currChar == ' ') || (currChar == '\\') || (currChar == '*') || (currChar == '?') || (currChar == '\t') || (currChar == '$') || (currChar == '\'') || (currChar == '\"') || (currChar == '!') || (currChar == '&') || (currChar == '(') || (currChar == ')') || (currChar == '{') || (currChar == '}') || (currChar == '[') || (currChar == ']') || (currChar == '|') || (currChar == ';') || (currChar == '<') || (currChar == '>') || (currChar == '`') || (currChar == 0x0A) || (currChar == 0x0D) ) { ::CFStringInsert( inStrRef, idx, CFSTR("\\") ); } idx--; } } CFMutableStringRef CreateCommandStringWithObjects(CMPluginImplData &inData) { if( (inData.commandList == NULL) || (inData.commandCount == 0) || (inData.objectList == NULL) ) return NULL; if( inData.currCommandIndex >= inData.commandCount) return NULL; CommandDescription currCommand = inData.commandList[inData.currCommandIndex]; if(currCommand.command == NULL) return NULL; CFMutableStringRef theCommand = ::CFStringCreateMutable( kCFAllocatorDefault, 0); if(theCommand == NULL) return NULL; TRACE_STR( "\pOnMyCommandCM. CreateCommandStringWithObjects" ); CFStringRef clipText = NULL; if(currCommand.mayNeedClipboard) { clipText = CreateCFStringOfTextFromClipboard(); } CFIndex theCount = ::CFArrayGetCount(currCommand.command); for(CFIndex i = 0; i < theCount; i++ ) { CFTypeRef theItem = ::CFArrayGetValueAtIndex(currCommand.command, i); if( (theItem != NULL) && ( ::CFGetTypeID(theItem) == ::CFStringGetTypeID() ) ) { CFStringRef fragmentRef = (CFStringRef)theItem; AppendTextToCommand(theCommand, fragmentRef, inData.objectList, inData.objectCount, inData.currObjectIndex, clipText, inData.commonParentPath, inData.inputText, currCommand.mulObjSeparator, currCommand.mulObjPrefix, currCommand.mulObjSuffix, inData.saveAsPath, currCommand.escapeSpecialCharsMode ); } } return theCommand; } CFMutableStringRef CreateCommandStringWithText(CFArrayRef inFragments, CFStringRef inObjTextRef, CMPluginImplData &inData, Boolean replaceSpecialChars) { if(inFragments == NULL) return NULL; CFMutableStringRef theCommand = ::CFStringCreateMutable( kCFAllocatorDefault, 0); if(theCommand == NULL) return NULL; CFIndex theCount = ::CFArrayGetCount(inFragments); for(CFIndex i = 0; i < theCount; i++ ) { CFTypeRef theItem = ::CFArrayGetValueAtIndex(inFragments, i); if( (theItem != NULL) && ( ::CFGetTypeID(theItem) == ::CFStringGetTypeID() ) ) { CFStringRef fragmentRef = (CFStringRef)theItem; CFStringRef fakeCommonParentPath = NULL; //will never be filled because object list is null here AppendTextToCommand(theCommand, fragmentRef, NULL, 0, 0xFFFFFFFF, inObjTextRef, fakeCommonParentPath, inData.inputText, NULL, NULL, NULL, inData.saveAsPath, replaceSpecialChars ); } } return theCommand; } CFMutableStringRef CreateRefreshPathWithObject(CMPluginImplData &inData) { TRACE_STR( "\pOnMyCommandCM. beginning of CreateRefreshPathWithObject" ); if( inData.objectList == NULL ) return NULL; if( inData.currCommandIndex >= inData.commandCount) return NULL; CommandDescription currCommand = inData.commandList[inData.currCommandIndex]; if (currCommand.refresh == NULL) return NULL; CFIndex theCount = ::CFArrayGetCount(currCommand.refresh); if(theCount == 0) return NULL; CFMutableStringRef thePath = ::CFStringCreateMutable( kCFAllocatorDefault, 0); if(thePath == NULL) return NULL; TRACE_STR( "\pOnMyCommandCM. inside CreateRefreshPathWithObject" ); for(CFIndex i = 0; i < theCount; i++ ) { CFTypeRef theItem = ::CFArrayGetValueAtIndex(currCommand.refresh, i); if( (theItem != NULL) && ( ::CFGetTypeID(theItem) == ::CFStringGetTypeID() ) ) { CFStringRef fragmentRef = (CFStringRef)theItem; AppendTextToCommand(thePath, fragmentRef, inData.objectList, inData.objectCount, inData.currObjectIndex, NULL, inData.commonParentPath, inData.inputText, NULL, NULL, NULL, inData.saveAsPath, kEscapeNone ); } } return thePath; } void AppendTextToCommand(CFMutableStringRef inCommandRef, CFStringRef inStrRef, OneObjProperties *inObjList, UInt32 inObjCount, UInt32 inCurrIndex, CFStringRef inObjTextRef, CFStringRef &ioCommonParentPathRef, CFStringRef inInputStr, CFStringRef inMultiSeparator, CFStringRef inMultiPrefix, CFStringRef inMultiSuffix, CFURLRef inSaveAsPath, UInt16 escSpecialCharsMode) { CFStringRef newStrRef = NULL; CFObjDel newDel(NULL); if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__OBJ_TEXT__"), 0 ) ) { if(inObjTextRef != NULL) {//we do not own inObjTextRef so we should never delete it newStrRef = CreateEscapedStringCopy(inObjTextRef, escSpecialCharsMode); newDel.Adopt(newStrRef); } } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__OBJ_PATH__"), 0 ) ) { newStrRef = CreateStringFromListOrSingleObject( inObjList, inObjCount, inCurrIndex, CreateObjPath, NULL, inMultiSeparator, inMultiPrefix, inMultiSuffix, escSpecialCharsMode ); newDel.Adopt(newStrRef); } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__OBJ_PATH_NO_EXTENSION__"), 0 ) ) { newStrRef = CreateStringFromListOrSingleObject( inObjList, inObjCount, inCurrIndex, CreateObjPathNoExtension, NULL, inMultiSeparator, inMultiPrefix, inMultiSuffix, escSpecialCharsMode ); newDel.Adopt(newStrRef); } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__OBJ_PARENT_PATH__"), 0 ) ) { newStrRef = CreateStringFromListOrSingleObject( inObjList, inObjCount, inCurrIndex, CreateParentPath, NULL, inMultiSeparator, inMultiPrefix, inMultiSuffix, escSpecialCharsMode ); newDel.Adopt(newStrRef); } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__OBJ_NAME__"), 0 ) ) { newStrRef = CreateStringFromListOrSingleObject( inObjList, inObjCount, inCurrIndex, CreateObjName, NULL, inMultiSeparator, inMultiPrefix, inMultiSuffix, escSpecialCharsMode ); newDel.Adopt(newStrRef); } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__OBJ_NAME_NO_EXTENSION__"), 0 ) ) { newStrRef = CreateStringFromListOrSingleObject( inObjList, inObjCount, inCurrIndex, CreateObjNameNoExtension, NULL, inMultiSeparator, inMultiPrefix, inMultiSuffix, escSpecialCharsMode ); newDel.Adopt(newStrRef); } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__OBJ_EXTENSION_ONLY__"), 0 ) ) { newStrRef = CreateStringFromListOrSingleObject( inObjList, inObjCount, inCurrIndex, CreateObjExtensionOnly, NULL, inMultiSeparator, inMultiPrefix, inMultiSuffix, escSpecialCharsMode ); newDel.Adopt(newStrRef); } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__OBJ_DISPLAY_NAME__"), 0 ) ) { newStrRef = CreateStringFromListOrSingleObject( inObjList, inObjCount, inCurrIndex, CreateObjDisplayName, NULL, inMultiSeparator, inMultiPrefix, inMultiSuffix, escSpecialCharsMode ); newDel.Adopt(newStrRef); } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__OBJ_COMMON_PARENT_PATH__"), 0 ) ) {//inserted only once, regardless if objects are processed together or separately if(ioCommonParentPathRef == NULL) ioCommonParentPathRef = CreateCommonParentPath(inObjList, inObjCount); newStrRef = CreateEscapedStringCopy(ioCommonParentPathRef, escSpecialCharsMode); newDel.Adopt(newStrRef); } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__OBJ_PATH_RELATIVE_TO_COMMON_PARENT__"), 0 ) ) { if(ioCommonParentPathRef == NULL) ioCommonParentPathRef = CreateCommonParentPath(inObjList, inObjCount); newStrRef = CreateStringFromListOrSingleObject( inObjList, inObjCount, inCurrIndex, CreateObjPathRelativeToBase, (void *)ioCommonParentPathRef, inMultiSeparator, inMultiPrefix, inMultiSuffix, escSpecialCharsMode ); newDel.Adopt(newStrRef); } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__PASSWORD__"), 0 ) ) { newStrRef = inInputStr;//we do not own it } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__INPUT_TEXT__"), 0 ) ) { newStrRef = inInputStr;//we do not own it } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__SAVE_AS_PATH__"), 0 ) ) { if(inSaveAsPath != NULL) { newStrRef = ::CFURLCopyFileSystemPath(inSaveAsPath, kCFURLPOSIXPathStyle); CFStringRef cpyStrRef = CreateEscapedStringCopy(newStrRef, escSpecialCharsMode); if(cpyStrRef != NULL) { CFObjDel strDel(newStrRef);//we have a copy, we may dispose of the original newStrRef = cpyStrRef; } newDel.Adopt(newStrRef); } } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__SAVE_AS_PARENT_PATH__"), 0 ) ) { if(inSaveAsPath != NULL) { CFURLRef newURL = ::CFURLCreateCopyDeletingLastPathComponent( kCFAllocatorDefault, inSaveAsPath ); CFObjDel urlDel(newURL); newStrRef = ::CFURLCopyFileSystemPath(newURL, kCFURLPOSIXPathStyle); CFStringRef cpyStrRef = CreateEscapedStringCopy(newStrRef, escSpecialCharsMode); if(cpyStrRef != NULL) { CFObjDel strDel(newStrRef);//we have a copy, we may dispose of the original newStrRef = cpyStrRef; } newDel.Adopt(newStrRef); } } else if( kCFCompareEqualTo == ::CFStringCompare( inStrRef, CFSTR("__SAVE_AS_FILE_NAME__"), 0 ) ) { if(inSaveAsPath != NULL) { newStrRef = ::CFURLCopyLastPathComponent(inSaveAsPath); CFStringRef cpyStrRef = CreateEscapedStringCopy(newStrRef, escSpecialCharsMode); if(cpyStrRef != NULL) { CFObjDel strDel(newStrRef);//we have a copy, we may dispose of the original newStrRef = cpyStrRef; } newDel.Adopt(newStrRef); } } else { newStrRef = inStrRef;//we do not own inStrRef so our deleter does not adopt it } if(newStrRef != NULL) { ::CFStringAppend( inCommandRef, newStrRef ); } } #pragma mark - CFStringRef CreateObjPath(const OneObjProperties *inObj, void *) { if(inObj == NULL) return NULL; CFURLRef urlRef = ::CFURLCreateFromFSRef( kCFAllocatorDefault, &(inObj->mRef) ); if(urlRef != NULL) { CFObjDel urlDel(urlRef); return ::CFURLCopyFileSystemPath(urlRef, kCFURLPOSIXPathStyle); } return NULL; } CFStringRef CreateObjPathNoExtension(const OneObjProperties *inObj, void *) { if(inObj == NULL) return NULL; CFURLRef urlRef = ::CFURLCreateFromFSRef( kCFAllocatorDefault, &(inObj->mRef) ); if(urlRef != NULL) { CFObjDel urlDel(urlRef); CFURLRef newURL = ::CFURLCreateCopyDeletingPathExtension( kCFAllocatorDefault, urlRef ); if(newURL != NULL) { CFObjDel newUrlDel(newURL); return ::CFURLCopyFileSystemPath(newURL, kCFURLPOSIXPathStyle); } } return NULL; } CFStringRef CreateParentPath(const OneObjProperties *inObj, void *) { if(inObj == NULL) return NULL; CFURLRef urlRef = ::CFURLCreateFromFSRef( kCFAllocatorDefault, &(inObj->mRef) ); if(urlRef != NULL) { CFObjDel urlDel(urlRef); CFURLRef newURL = ::CFURLCreateCopyDeletingLastPathComponent( kCFAllocatorDefault, urlRef ); if(newURL != NULL) { CFObjDel newUrlDel(newURL); return ::CFURLCopyFileSystemPath(newURL, kCFURLPOSIXPathStyle); } } return NULL; } CFStringRef CreateObjName(const OneObjProperties *inObj, void *) { if(inObj == NULL) return NULL; CFURLRef urlRef = ::CFURLCreateFromFSRef( kCFAllocatorDefault, &(inObj->mRef) ); if(urlRef != NULL) { CFObjDel urlDel(urlRef); return ::CFURLCopyLastPathComponent(urlRef); } return NULL; } CFStringRef CreateObjNameNoExtension(const OneObjProperties *inObj, void *) { if(inObj == NULL) return NULL; CFURLRef urlRef = ::CFURLCreateFromFSRef( kCFAllocatorDefault, &(inObj->mRef) ); if(urlRef != NULL) { CFObjDel urlDel(urlRef); CFURLRef newURL = ::CFURLCreateCopyDeletingPathExtension( kCFAllocatorDefault, urlRef ); if(newURL != NULL) { CFObjDel newUrlDel(newURL); return ::CFURLCopyLastPathComponent(newURL); } } return NULL; } CFStringRef CreateObjExtensionOnly(const OneObjProperties *inObj, void *) {//we already have the extension in our data if(inObj == NULL) return NULL; if(inObj->mExtension != NULL) return ::CFStringCreateCopy(kCFAllocatorDefault, inObj->mExtension); return NULL; } CFStringRef CreateObjDisplayName(const OneObjProperties *inObj, void *) { if(inObj == NULL) return NULL; CFStringRef theName = NULL; OSStatus err = ::LSCopyDisplayNameForRef( &(inObj->mRef), &theName ); if(err == noErr) { return theName; } return NULL; } CFStringRef CreateObjPathRelativeToBase(const OneObjProperties *inObj, void *ioParam) { if(inObj == NULL) return NULL; if(ioParam == NULL) {//no base is provided, fall back to full path return CreateObjPath(inObj, NULL); } CFStringRef commonParentPath = (CFStringRef)ioParam; CFURLRef urlRef = ::CFURLCreateFromFSRef( kCFAllocatorDefault, &(inObj->mRef) ); if(urlRef != NULL) { CFObjDel urlDel(urlRef); CFStringRef fullPath = ::CFURLCopyFileSystemPath(urlRef, kCFURLPOSIXPathStyle); if(fullPath != NULL) { CFObjDel pathDel(fullPath); //at this point we assume that fullPath starts with commonParentPath //we do not check it CFRange theRange; theRange.location = 0; theRange.length = ::CFStringGetLength(commonParentPath); //relative path will not be longer than full path CFMutableStringRef relPath = ::CFStringCreateMutableCopy(kCFAllocatorDefault, ::CFStringGetLength(fullPath), fullPath); ::CFStringDelete(relPath, theRange);//delete the first part of the path which we assume is the same as base path return relPath; } } return NULL; } CFStringRef CreateStringFromListOrSingleObject( OneObjProperties *inObjList, UInt32 inObjCount, UInt32 inCurrIndex, CreateObjProc inObjProc, void *ioParam, CFStringRef inMultiSeparator, CFStringRef inPrefix, CFStringRef inSuffix, UInt16 escSpecialCharsMode ) { if(inObjList == NULL) return NULL; OneObjProperties *oneObj; CFStringRef newStrRef = NULL; if(inCurrIndex == 0xFFFFFFFF)//process all together { CFMutableStringRef mutableStr = ::CFStringCreateMutable(kCFAllocatorDefault, 0); for(UInt32 i = 0; i < inObjCount; i++) { oneObj = inObjList + i; newStrRef = (*inObjProc)( oneObj, ioParam ); if(newStrRef != NULL) { CFStringRef cpyStrRef = CreateEscapedStringCopy(newStrRef, escSpecialCharsMode); if(cpyStrRef != NULL) { CFObjDel strDel(newStrRef);//we have a copy, we may dispose of the original newStrRef = cpyStrRef; } CFObjDel newDel(newStrRef); if(inPrefix != NULL) { ::CFStringAppend( mutableStr, inPrefix ); } ::CFStringAppend( mutableStr, newStrRef ); if(inSuffix != NULL) { ::CFStringAppend( mutableStr, inSuffix ); } if( (inMultiSeparator != NULL) && i < (inObjCount-1) ) {//add separator, but not after the last item ::CFStringAppend( mutableStr, inMultiSeparator ); } } } newStrRef = mutableStr;//assign new string to our main variable } else if(inCurrIndex < inObjCount) {//process just one oneObj = inObjList + inCurrIndex; newStrRef = (*inObjProc)( oneObj, ioParam ); if( newStrRef != NULL ) { CFStringRef cpyStrRef = CreateEscapedStringCopy(newStrRef, escSpecialCharsMode); if(cpyStrRef != NULL) { CFObjDel strDel(newStrRef);//we have a copy, we may dispose of the original newStrRef = cpyStrRef; } } } return newStrRef; } CFStringRef CreateCommonParentPath(OneObjProperties *inObjList, UInt32 inObjCount ) { if( (inObjList == NULL) || (inObjCount == 0) ) return NULL; OneObjProperties *oneObj; CFMutableArrayRef *arrayList = new CFMutableArrayRef[inObjCount]; ::BlockZero(arrayList, inObjCount*sizeof(CFMutableArrayRef)); //create parent paths starting from branches, going to the root, //putting in reverse order, so it will be easier to iterate from the root later for (SInt32 i = 0; i < inObjCount; i++) { oneObj = inObjList + i; CFMutableArrayRef pathsArray = ::CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks ); arrayList[i] = pathsArray; if(pathsArray != NULL) { CFURLRef newURL = ::CFURLCreateFromFSRef( kCFAllocatorDefault, &(oneObj->mRef) ); //we dispose of all these URLs, we leave only strings derived from them while(newURL != NULL) { CFObjDel urlDel(newURL);//delete previous when we are done newURL = ::CFURLCreateCopyDeletingLastPathComponent( kCFAllocatorDefault, newURL ); if(newURL != NULL) { CFStringRef aPath = ::CFURLCopyFileSystemPath(newURL, kCFURLPOSIXPathStyle); ::CFArrayInsertValueAtIndex( pathsArray, 0, (const void *)aPath);//grand parent is inserted in front if( kCFCompareEqualTo == ::CFStringCompare( CFSTR("/"), aPath, 0) ) //we reached the top { ::CFRelease(newURL);//delete current URL and end the loop newURL = NULL; } } } } } //get minimum count of parent folders in all our paths CFIndex minCount = 0x7FFFFFFF; for(UInt32 i = 0; i < inObjCount; i++) { if(arrayList[i] != NULL) { CFIndex theCount = ::CFArrayGetCount(arrayList[i]); if(theCount < minCount) minCount = theCount; } else { minCount = 0; break; } } CFStringRef commonParentPath = NULL; //find common parent if( (minCount > 0) && (minCount < 0x7FFFFFFF) ) {//if minimum count is valid //at this point, all items in arrayList are non-NULL UInt32 commonPathLevel= 0; for( CFIndex pathLevel = 0; pathLevel < minCount; pathLevel++ ) { //check given path level for each object CFTypeRef theItem = ::CFArrayGetValueAtIndex( arrayList[0], pathLevel); //Assert_( (theItem != NULL) && (::CFGetTypeID(theItem) == ::CFStringGetTypeID()) ); CFStringRef firstPath = (CFStringRef)theItem; Boolean allEqual = true; for(UInt32 i = 1; i < inObjCount; i++) { theItem = ::CFArrayGetValueAtIndex( arrayList[i], pathLevel); //Assert_( (theItem != NULL) && (::CFGetTypeID(theItem) == ::CFStringGetTypeID()) ); CFStringRef anotherPath = (CFStringRef)theItem; if( kCFCompareEqualTo != ::CFStringCompare( firstPath, anotherPath, 0 ) ) { allEqual = false; break; } } if(allEqual == true) {//all paths are equal at this level commonPathLevel = pathLevel; } else {//we went too far, do not check any further break; } } CFTypeRef commonLevelItem = ::CFArrayGetValueAtIndex( arrayList[0], commonPathLevel); //Assert_( (commonLevelItem != NULL) && (::CFGetTypeID(commonLevelItem) == ::CFStringGetTypeID()) ); commonParentPath = (CFStringRef)commonLevelItem; //add slash at the end of parent path unless it is a root folder which has it already if( kCFCompareEqualTo == ::CFStringCompare( commonParentPath, CFSTR("/"), 0 ) ) { ::CFRetain(commonParentPath);//prevent from deleting, we return this string } else {//we are adding just one character CFMutableStringRef modifStr = ::CFStringCreateMutableCopy( kCFAllocatorDefault, ::CFStringGetLength(commonParentPath)+1, commonParentPath ); if(modifStr != NULL) { ::CFStringAppend( modifStr, CFSTR("/") ); commonParentPath = modifStr; } } #if _DEBUG_ Str255 theString; if( ::CFStringGetPascalString(commonParentPath, theString, sizeof(Str255), kCFStringEncodingMacRoman) ) { DEBUG_STR( theString ); } #endif //_DEBUG_ } //dispose of path arrays for(UInt32 i = 0; i < inObjCount; i++) { if(arrayList[i] != NULL) { ::CFRelease( arrayList[i] ); arrayList[i] = NULL; } } delete arrayList; return commonParentPath; } //this function always creates a copy of string so you may release original string when not needed CFStringRef CreateEscapedStringCopy(CFStringRef inStrRef, UInt16 escSpecialCharsMode) { if( inStrRef != NULL ) { if(escSpecialCharsMode == kEscapeWithBackslash) { CFMutableStringRef modifStr = ::CFStringCreateMutableCopy(kCFAllocatorDefault, 0, inStrRef); if(modifStr != NULL) { ReplaceSpecialCharsWithBackslashEscapes(modifStr); return modifStr; } } else if( escSpecialCharsMode == kEscapeWithPercent ) { return ::CFURLCreateStringByAddingPercentEscapes(NULL, inStrRef, NULL, NULL, kCFStringEncodingUTF8); } else { ::CFRetain(inStrRef); return inStrRef; } } return NULL; } #pragma mark- #pragma mark ==== UNUSED === //this function creates UTF-8 representation of a string and stores it back into CFString //this somewhat artifical approach enables manipulation of the string in its UTF-8 form //string procuced with this function should not be converted to 8-bit representation using any encoding, //high bytes should be cut off instead CFMutableStringRef CreateUFT8MutableStringCopy(CFStringRef inStrRef) { if(inStrRef == NULL) return NULL; CFMutableStringRef outString = NULL; CFIndex uniCount = ::CFStringGetLength(inStrRef); CFIndex maxCount = ::CFStringGetMaximumSizeForEncoding( uniCount, kCFStringEncodingUTF8 ); Ptr theString = ::NewPtrClear(maxCount +1);//one more for 0-termination if(theString != NULL) { if( ::CFStringGetCString( inStrRef, theString, maxCount +1, kCFStringEncodingUTF8) ) { SInt32 actualLen = std::strlen(theString); //make 16 bit representation of our UTF-8 string to fool CFString and store it back unchanged UniChar *uniPtr = ::AddHiBytes((unsigned char *)theString, actualLen); ::DisposePtr( theString ); if(uniPtr != NULL) { outString = ::CFStringCreateMutable(kCFAllocatorDefault, 0); if(outString != NULL) { ::CFStringAppendCharacters(outString, uniPtr, actualLen); } } ::DisposePtr( (Ptr)uniPtr ); } else { ::DisposePtr( theString ); } } return outString; } //this does not work. I did not find a way to enter non-ASCII characters in Terminal void ReplaceNonASCIICharsWithOctalEscapes(CFMutableStringRef inStrRef) { static UniChar octalChars[12]; if(inStrRef == NULL) return; CFIndex idx = ::CFStringGetLength(inStrRef) - 1; UniChar currChar; UniCharCount octalLen; while(idx >= 0) { currChar = ::CFStringGetCharacterAtIndex( inStrRef, idx); if(currChar > 127) { CFMutableStringRef octalEscapes = ::CFStringCreateMutable( kCFAllocatorDefault, 16);//less storage would be fine too if(octalEscapes != NULL) { CFObjDel octDel(octalEscapes); //make octal escapes ::CFStringAppend( octalEscapes, CFSTR("\\") ); octalLen = sizeof(octalChars)/sizeof(UniChar); DecToOctal(currChar, octalChars, octalLen ); ::CFStringAppendCharacters(octalEscapes, octalChars, octalLen); //replace char with octal escapes CFRange theRange; theRange.location = idx; theRange.length = 1; ::CFStringReplace(inStrRef, theRange, octalEscapes); } } idx--; } } //ioLen takes max buffer size on input and returns actual string len on output //outBuffer is NOT terminated with 0 void DecToOctal(long inNum, UniChar *outBuffer, UniCharCount &ioLen ) { char octalChars[12] = {0,0,0,0,0,0,0,0,0,0,0,0}; long count = 0; do { octalChars[count] = (inNum % 8); inNum = (inNum / 8); ++count; } while (inNum != 0); int i = 0; while( (count > 0) && (i < ioLen) ) { --count; outBuffer[i] = (UniChar)(octalChars[count] + '0'); ++i; } ioLen = i; } //on output, a new pointer is assigned via NewPtr //caller is responsible for releasing the memory via DisposePtr //this is not an encoding converter - it just upgrades 8-bit chars to 16-bit chars UniChar * AddHiBytes(unsigned char *inBuffer, ByteCount inCount) { UniChar *newChars = (UniChar *)::NewPtrClear( inCount * sizeof(UniChar) ); if(newChars == NULL) return NULL; for(ByteCount i = 0; i < inCount; i++) { newChars[i] = (UniChar)inBuffer[i]; } return newChars; } //on output, the buffer should be treated as an array of 8-bit chars void StripHiBytes(UniChar *ioBuffer, UniCharCount inCount) { unsigned char *charPtr = (unsigned char *)ioBuffer; for(UniCharCount i = 0; i < inCount; i++) { charPtr[i] = (unsigned char)(ioBuffer[i] & 0x00FF); } }