///////////////////////////////////////////////////////////////////////////// // Name: mac/mimetype.cpp // Purpose: Mac Carbon implementation for wx mime-related classes // Author: Ryan Norton // Modified by: // Created: 04/16/2005 // RCS-ID: $Id: mimetmac.cpp,v 1.28.2.3 2006/04/13 08:18:58 SC Exp $ // Copyright: (c) 2005 Ryan Norton () // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // // // TODO: Search Info[-macos](classic).plist dictionary in addition // to Internet Config database. // // Maybe try a brainstorm a way to change the wxMimeTypesManager API // to get info from a file instead/addition to current get all stuff // API so that we can use Launch Services to get mime type info. // // Implement geticon from one of the finder info functions - or // use launch services and search that app's plist for the icon. // // Put some special juice in for the print command. // // // #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) #pragma implementation "mimetype.h" #endif // for compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #ifndef WX_PRECOMP #include "wx/string.h" #if wxUSE_GUI #include "wx/icon.h" #endif #endif //WX_PRECOMP #if wxUSE_MIMETYPE #include "wx/log.h" #include "wx/file.h" #include "wx/intl.h" #include "wx/dynarray.h" #include "wx/confbase.h" #include "wx/mac/mimetype.h" #include "wx/mac/private.h" //wxMacMakeStringFromPascal // other standard headers #include #ifndef __DARWIN__ #include //For mime types #include #endif /* START CODE SAMPLE FROM TECHNOTE 1002 (http://developer.apple.com/technotes/tn/tn1002.html) */ /* IsRemoteVolume can be used to find out if the volume referred to by vRefNum is a remote volume located somewhere on a network. the volume's attribute flags (copied from the GetVolParmsInfoBuffer structure) are returned in the longword pointed to by vMAttrib. */ OSErr IsRemoteVolume(short vRefNum, Boolean *isRemote, long *vMAttrib) { HParamBlockRec volPB; GetVolParmsInfoBuffer volinfo; OSErr err; volPB.ioParam.ioVRefNum = vRefNum; volPB.ioParam.ioNamePtr = NULL; volPB.ioParam.ioBuffer = (Ptr) &volinfo; volPB.ioParam.ioReqCount = sizeof(volinfo); err = PBHGetVolParmsSync(&volPB); if (err == noErr) { *isRemote = (volinfo.vMServerAdr != 0); *vMAttrib = volinfo.vMAttrib; } return err; } /* BuildVolumeList fills the array pointed to by vols with a list of the currently mounted volumes. If includeRemote is true, then remote server volumes will be included in the list. When remote server volumes are included in the list, they will be added to the end of the list. On entry, *count should contain the size of the array pointed to by vols. On exit, *count will be set to the number of id numbers placed in the array. If vMAttribMask is non-zero, then only volumes with matching attributes are added to the list of volumes. bits in the vMAttribMask should use the same encoding as bits in the vMAttrib field of the GetVolParmsInfoBuffer structure. */ OSErr BuildVolumeList(Boolean includeRemote, short *vols, long *count, long vMAttribMask) { HParamBlockRec volPB; Boolean isRemote = false ; OSErr err = noErr; long nlocal, nremote; long vMAttrib = 0; /* set up and check parameters */ volPB.volumeParam.ioNamePtr = NULL; nlocal = nremote = 0; if (*count == 0) return noErr; /* iterate through volumes */ for (volPB.volumeParam.ioVolIndex = 1; PBHGetVInfoSync(&volPB) == noErr; volPB.volumeParam.ioVolIndex++) { /* skip remote volumes, if necessary */ err = IsRemoteVolume(volPB.volumeParam.ioVRefNum, &isRemote, &vMAttrib); if (err != noErr) goto bail; if ( ( includeRemote || ! isRemote ) && (vMAttrib & vMAttribMask) == vMAttribMask ) { /* add local volumes at the front, remote volumes at the end */ if (isRemote) vols[nlocal + nremote++] = volPB.volumeParam.ioVRefNum; else { if (nremote > 0) BlockMoveData(vols+nlocal, vols+nlocal+1, nremote*sizeof(short)); vols[nlocal++] = volPB.volumeParam.ioVRefNum; } /* list full? */ if ((nlocal + nremote) >= *count) break; } } bail: *count = (nlocal + nremote); return err; } /* FindApplication iterates through mounted volumes searching for an application with the given creator type. If includeRemote is true, then remote volumes will be searched (after local ones) for an application with the creator type. */ #define kMaxVols 20 /* Hacked to output to appName */ OSErr FindApplication(OSType appCreator, Boolean includeRemote, Str255 appName, FSSpec* appSpec) { short rRefNums[kMaxVols]; long i, volCount; DTPBRec desktopPB; OSErr err; /* get a list of volumes - with desktop files */ volCount = kMaxVols; err = BuildVolumeList(includeRemote, rRefNums, &volCount, (1<name[0] == 0 ) { result = FSMakeFSSpecCompat(spec->vRefNum, spec->parID, spec->name, &tempSpec); } else { */ /* Make a copy of the input FSSpec that can be modified */ BlockMoveData(spec, &tempSpec, sizeof(FSSpec)); /* }*/ if ( result == noErr ) { if ( tempSpec.parID == fsRtParID ) { /* The object is a volume */ /* Add a colon to make it a full pathname */ ++tempSpec.name[0]; tempSpec.name[tempSpec.name[0]] = ':'; /* We're done */ result = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]); } else { /* The object isn't a volume */ /* Is the object a file or a directory? */ pb.dirInfo.ioNamePtr = tempSpec.name; pb.dirInfo.ioVRefNum = tempSpec.vRefNum; pb.dirInfo.ioDrDirID = tempSpec.parID; pb.dirInfo.ioFDirIndex = 0; result = PBGetCatInfoSync(&pb); /* Allow file/directory name at end of path to not exist. */ realResult = result; if ( (result == noErr) || (result == fnfErr) ) { /* if the object is a directory, append a colon so full pathname ends with colon */ if ( (result == noErr) && (pb.hFileInfo.ioFlAttrib & kioFlAttribDirMask) != 0 ) { ++tempSpec.name[0]; tempSpec.name[tempSpec.name[0]] = ':'; } /* Put the object name in first */ result = PtrToHand(&tempSpec.name[1], fullPath, tempSpec.name[0]); if ( result == noErr ) { /* Get the ancestor directory names */ pb.dirInfo.ioNamePtr = tempSpec.name; pb.dirInfo.ioVRefNum = tempSpec.vRefNum; pb.dirInfo.ioDrParID = tempSpec.parID; do /* loop until we have an error or find the root directory */ { pb.dirInfo.ioFDirIndex = -1; pb.dirInfo.ioDrDirID = pb.dirInfo.ioDrParID; result = PBGetCatInfoSync(&pb); if ( result == noErr ) { /* Append colon to directory name */ ++tempSpec.name[0]; tempSpec.name[tempSpec.name[0]] = ':'; /* Add directory name to beginning of fullPath */ (void) Munger(*fullPath, 0, NULL, 0, &tempSpec.name[1], tempSpec.name[0]); result = MemError(); } } while ( (result == noErr) && (pb.dirInfo.ioDrDirID != fsRtDirID) ); } } } } if ( result == noErr ) { /* Return the length */ *fullPathLength = GetHandleSize(*fullPath); result = realResult; /* return realResult in case it was fnfErr */ } else { /* Dispose of the handle and return NULL and zero length */ if ( *fullPath != NULL ) { DisposeHandle(*fullPath); } *fullPath = NULL; *fullPathLength = 0; } return ( result ); } // // On the mac there are two ways to open a file - one is through apple events and the // finder, another is through mime types. // // So, really there are two ways to implement wxFileType... // // Mime types are only available on OS 8.1+ through the InternetConfig API // // Much like the old-style file manager, it has 3 levels of flexibility for its methods - // Low - which means you have to iterate yourself through the mime database // Medium - which lets you sort of cache the database if you want to use lowlevel functions // High - which requires access to the database every time // // We want to be efficient (i.e. professional :) ) about it, so we use a combo of low // and mid-level functions // // TODO: Should we call ICBegin/ICEnd? Then where? // // debug helper inline void wxLogMimeDebug(const wxChar* szMsg, OSStatus status) { wxLogDebug(wxString::Format(wxT("%s LINE:%i OSERROR:%i"), szMsg, __LINE__, (int)status)); } // in case we're compiling in non-GUI mode class WXDLLEXPORT wxIcon; bool wxFileTypeImpl::SetCommand(const wxString& cmd, const wxString& verb, bool overwriteprompt) { wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); return false; } bool wxFileTypeImpl::SetDefaultIcon(const wxString& strIcon, int index) { wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); return false; } bool wxFileTypeImpl::GetOpenCommand(wxString *openCmd, const wxFileType::MessageParameters& params) const { wxString cmd = GetCommand(wxT("open")); *openCmd = wxFileType::ExpandCommand(cmd, params); return !openCmd->empty(); } bool wxFileTypeImpl::GetPrintCommand(wxString *printCmd, const wxFileType::MessageParameters& params) const { wxString cmd = GetCommand(wxT("print")); *printCmd = wxFileType::ExpandCommand(cmd, params); return !printCmd->empty(); } // // Internet Config vs. Launch Services // // From OS 8 on there was internet config... // However, OSX and its finder does not use info // from Internet Config at all - the Internet Config // database ONLY CONTAINS APPS THAT ARE CLASSIC APPS // OR REGISTERED THROUGH INTERNET CONFIG // // Therefore on OSX in order for the open command to be useful // we need to go straight to launch services // #if defined(__DARWIN__) //on darwin, use launch services #include wxString wxFileTypeImpl::GetCommand(const wxString& verb) const { wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); if(verb == wxT("open")) { ICMapEntry entry; ICGetMapEntry( (ICInstance) m_manager->m_hIC, (Handle) m_manager->m_hDatabase, m_lIndex, &entry); wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension); sCurrentExtension = sCurrentExtension.Right(sCurrentExtension.Length()-1 ); //type, creator, ext, roles, outapp (FSRef), outappurl CFURLRef cfurlAppPath; OSStatus status = LSGetApplicationForInfo (kLSUnknownType, kLSUnknownCreator, wxMacCFStringHolder(sCurrentExtension, wxLocale::GetSystemEncoding()), kLSRolesAll, NULL, &cfurlAppPath); if(status == noErr) { CFStringRef cfsUnixPath = CFURLCopyFileSystemPath(cfurlAppPath, kCFURLPOSIXPathStyle); CFRelease(cfurlAppPath); //PHEW! Success! //Since a filename might have spaces in it, surround it with quotes if(cfsUnixPath) return wxString(wxT("'")) + wxMacCFStringHolder(cfsUnixPath).AsString(wxLocale::GetSystemEncoding()) + wxString(wxT("'")); } else { wxLogDebug(wxString::Format(wxT("%i - %s - %i"), __LINE__, wxT("LSGetApplicationForInfo failed."), (int)status)); } } return wxEmptyString; } #else //carbon/classic implementation wxString wxFileTypeImpl::GetCommand(const wxString& verb) const { wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); if(verb == wxT("open")) { ICMapEntry entry; ICGetMapEntry( (ICInstance) m_manager->m_hIC, (Handle) m_manager->m_hDatabase, m_lIndex, &entry); //The entry in the mimetype database only contains the app //that's registered - it may not exist... we need to remap the creator //type and find the right application // THIS IS REALLY COMPLICATED :\. There are a lot of conversions going // on here. Str255 outName; FSSpec outSpec; if(FindApplication(entry.fileCreator, false, outName, &outSpec) != noErr) return wxEmptyString; Handle outPathHandle; short outPathSize; OSErr err = FSpGetFullPath(&outSpec, &outPathSize, &outPathHandle); if(err == noErr) { char* szPath = *outPathHandle; wxString sClassicPath(szPath, wxConvLocal, outPathSize); #if defined(__DARWIN__) //Classic Path --> Unix (OSX) Path CFURLRef finalURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, wxMacCFStringHolder(sClassicPath, wxLocale::GetSystemEncoding()), kCFURLHFSPathStyle, false); //false == not a directory //clean up memory from the classic path handle DisposeHandle(outPathHandle); if(finalURL) { CFStringRef cfsUnixPath = CFURLCopyFileSystemPath(finalURL, kCFURLPOSIXPathStyle); CFRelease(finalURL); //PHEW! Success! if(cfsUnixPath) return wxMacCFStringHolder(cfsUnixPath).AsString(wxLocale::GetSystemEncoding()); } #else //classic HFS path acceptable return sClassicPath; #endif } else { wxLogMimeDebug(wxT("FSpGetFullPath failed."), (OSStatus)err); } } return wxEmptyString; } #endif //!DARWIN bool wxFileTypeImpl::GetDescription(wxString *desc) const { wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); ICMapEntry entry; ICGetMapEntry( (ICInstance) m_manager->m_hIC, (Handle) m_manager->m_hDatabase, m_lIndex, &entry); *desc = wxMacMakeStringFromPascal(entry.entryName); return true; } bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) { wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); ICMapEntry entry; ICGetMapEntry( (ICInstance) m_manager->m_hIC, (Handle) m_manager->m_hDatabase, m_lIndex, &entry); //entry has period in it wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension); extensions.Add( sCurrentExtension.Right(sCurrentExtension.Length()-1) ); return true; } bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const { wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); ICMapEntry entry; ICGetMapEntry( (ICInstance) m_manager->m_hIC, (Handle) m_manager->m_hDatabase, m_lIndex, &entry); *mimeType = wxMacMakeStringFromPascal(entry.MIMEType); return true; } bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const { wxString s; if (GetMimeType(&s)) { mimeTypes.Clear(); mimeTypes.Add(s); return true; } else return false; } bool wxFileTypeImpl::GetIcon(wxIconLocation *WXUNUSED(icon)) const { wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); // no such file type or no value or incorrect icon entry return false; } size_t wxFileTypeImpl::GetAllCommands(wxArrayString * verbs, wxArrayString * commands, const wxFileType::MessageParameters& params) const { wxASSERT_MSG( m_manager != NULL , wxT("Bad wxFileType") ); wxString sCommand; size_t ulCount = 0; if(GetOpenCommand(&sCommand, params)) { verbs->Add(wxString(wxT("open"))); commands->Add(sCommand); ++ulCount; } return ulCount; } void wxMimeTypesManagerImpl::Initialize(int mailcapStyles, const wxString& extraDir) { wxASSERT_MSG(m_hIC == NULL, wxT("Already initialized wxMimeTypesManager!")); //some apps (non-wx) use the 'plst' resource instead /* CFBundleRef cfbMain = CFBundleGetMainBundle(); wxCFDictionary cfdInfo( CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN ); wxString sLog; cfdInfo.PrintOut(sLog); wxLogDebug(sLog); */ //start internet config - log if there's an error //the second param is the signature of the application, also known //as resource ID 0. However, as per some recent discussions, we may not //have a signature for this app, so a generic 'APPL' which is the executable //type will work for now OSStatus status = ICStart( (ICInstance*) &m_hIC, 'APPL'); if(status != noErr) { wxLogDebug(wxT("Could not initialize wxMimeTypesManager!")); wxASSERT( false ); m_hIC = NULL; return; } ICAttr attr; m_hDatabase = (void**) NewHandle(0); status = ICFindPrefHandle( (ICInstance) m_hIC, kICMapping, &attr, (Handle) m_hDatabase ); //the database file can be corrupt (on OSX its //~/Library/Preferences/com.apple.internetconfig.plist) //- bail if it is if(status != noErr) { ClearData(); wxLogDebug(wxT("Corrupt Mime Database!")); return; } //obtain the number of entries in the map status = ICCountMapEntries( (ICInstance) m_hIC, (Handle) m_hDatabase, &m_lCount ); wxASSERT( status == noErr ); /* //debug stuff ICMapEntry entry; long pos; for(long i = 1; i <= m_lCount; ++i) { OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry); if(status == noErr) { wxString sCreator = wxMacMakeStringFromPascal(entry.creatorAppName); wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension); wxString sMIMEType = wxMacMakeStringFromPascal(entry.MIMEType); wxFileTypeImpl impl; impl.Init(this, pos); if(sMIMEType == wxT("text/html") && sCurrentExtension == wxT(".html")) { wxString cmd; impl.GetOpenCommand (&cmd, wxFileType::MessageParameters (wxT("http://www.google.com"))); wxPrintf(wxT("APP: [%s]\n"), cmd.c_str()); } } } */ } void wxMimeTypesManagerImpl::ClearData() { if(m_hIC != NULL) { DisposeHandle((Handle)m_hDatabase); //this can return an error, but we don't really care that much about it ICStop( (ICInstance) m_hIC ); m_hIC = NULL; } } // // Q) Iterating through the map - why does it use if (err == noErr) instead of just asserting? // A) Some intermediate indexes are bad while subsequent ones may be good. Its wierd, I know. // // extension -> file type wxFileType* wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& e) { wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") ); //low level functions - iterate through the database ICMapEntry entry; long pos; for(long i = 1; i <= m_lCount; ++i) { OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry); if(status == noErr) { wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension); if( sCurrentExtension.Right(sCurrentExtension.Length()-1) == e ) //entry has period in it { wxFileType* pFileType = new wxFileType(); pFileType->m_impl->Init((wxMimeTypesManagerImpl*)this, pos); return pFileType; } } } return NULL; } // MIME type -> extension -> file type wxFileType* wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType) { wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") ); //low level functions - iterate through the database ICMapEntry entry; long pos; for(long i = 1; i <= m_lCount; ++i) { OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry); wxASSERT_MSG( status == noErr, wxString::Format(wxT("Error: %d"), (int)status) ); if(status == noErr) { if( wxMacMakeStringFromPascal(entry.MIMEType) == mimeType) { wxFileType* pFileType = new wxFileType(); pFileType->m_impl->Init((wxMimeTypesManagerImpl*)this, pos); return pFileType; } } } return NULL; } size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes) { wxASSERT_MSG( m_hIC != NULL, wxT("wxMimeTypesManager not Initialized!") ); //low level functions - iterate through the database ICMapEntry entry; long pos; long lStartCount = (long) mimetypes.GetCount(); for(long i = 1; i <= m_lCount; ++i) { OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry); if( status == noErr ) mimetypes.Add( wxMacMakeStringFromPascal(entry.MIMEType) ); } return mimetypes.GetCount() - lStartCount; } pascal OSStatus MoreProcGetProcessTypeSignature( const ProcessSerialNumberPtr pPSN, OSType *pProcessType, OSType *pCreator) { OSStatus anErr = noErr; ProcessInfoRec infoRec; ProcessSerialNumber localPSN; infoRec.processInfoLength = sizeof(ProcessInfoRec); infoRec.processName = nil; infoRec.processAppSpec = nil; if ( pPSN == nil ) { localPSN.highLongOfPSN = 0; localPSN.lowLongOfPSN = kCurrentProcess; } else { localPSN = *pPSN; } anErr = GetProcessInformation(&localPSN, &infoRec); if (anErr == noErr) { *pProcessType = infoRec.processType; *pCreator = infoRec.processSignature; } return anErr; }//end MoreProcGetProcessTypeSignature // // // TODO: clean this up, its messy // // // #include "wx/mac/corefoundation/cfstring.h" #include "wx/intl.h" //wxLocale for wxCFString #define wxCF_RELEASE true #define wxCF_RETAIN false // ---------------------------------------------------------------------------- // wxCFDictionary // ---------------------------------------------------------------------------- class wxCFDictionary { public: wxCFDictionary(CFTypeRef ref, bool bRetain = wxCF_RELEASE) { m_cfmdRef = (CFMutableDictionaryRef) ref; if(bRetain == wxCF_RETAIN && ref) CFRetain(ref); } wxCFDictionary(CFIndex cfiSize = 0) { CFDictionaryKeyCallBacks kcbs; CFDictionaryValueCallBacks vcbs; BuildKeyCallbacks(&kcbs); BuildValueCallbacks(&vcbs); m_cfmdRef = CFDictionaryCreateMutable( kCFAllocatorDefault, cfiSize, &kcbs, &vcbs); } ~wxCFDictionary() { Clear(); } void Clear() {if(m_cfmdRef) CFRelease(m_cfmdRef);} static const void* RetainProc(CFAllocatorRef, const void* v) { return (const void*) CFRetain(v); } static void ReleaseProc(CFAllocatorRef, const void* v) { CFRelease(v); } void MakeMutable(CFIndex cfiSize = 0) { CFDictionaryRef oldref = (CFDictionaryRef) m_cfmdRef; m_cfmdRef = CFDictionaryCreateMutableCopy( kCFAllocatorDefault, cfiSize, oldref); CFRelease(oldref); } void BuildKeyCallbacks(CFDictionaryKeyCallBacks* pCbs) { pCbs->version = 0; pCbs->retain = RetainProc; pCbs->release = ReleaseProc; pCbs->copyDescription = NULL; pCbs->equal = NULL; pCbs->hash = NULL; } void BuildValueCallbacks(CFDictionaryValueCallBacks* pCbs) { pCbs->version = 0; pCbs->retain = RetainProc; pCbs->release = ReleaseProc; pCbs->copyDescription = NULL; pCbs->equal = NULL; } operator CFTypeRef () const { return (CFTypeRef)m_cfmdRef; } CFDictionaryRef GetCFDictionary() const { return (CFDictionaryRef)m_cfmdRef; } CFMutableDictionaryRef GetCFMutableDictionary() { return (CFMutableDictionaryRef) m_cfmdRef; } CFTypeRef operator [] (CFTypeRef cftEntry) const { wxASSERT(IsValid()); return (CFTypeRef) CFDictionaryGetValue((CFDictionaryRef)m_cfmdRef, cftEntry); } CFIndex GetCount() const { wxASSERT(IsValid()); return CFDictionaryGetCount((CFDictionaryRef)m_cfmdRef); } void Add(CFTypeRef cftKey, CFTypeRef cftValue) { wxASSERT(IsValid()); wxASSERT(Exists(cftKey) == false); CFDictionaryAddValue(m_cfmdRef, cftKey, cftValue); } void Remove(CFTypeRef cftKey) { wxASSERT(IsValid()); wxASSERT(Exists(cftKey)); CFDictionaryRemoveValue(m_cfmdRef, cftKey); } void Set(CFTypeRef cftKey, CFTypeRef cftValue) { wxASSERT(IsValid()); wxASSERT(Exists(cftKey)); CFDictionarySetValue(m_cfmdRef, cftKey, cftValue); } bool Exists(CFTypeRef cftKey) const { wxASSERT(IsValid()); return CFDictionaryContainsKey((CFDictionaryRef)m_cfmdRef, cftKey); } bool IsOk() const {return m_cfmdRef != NULL; } bool IsValid() const { return IsOk() && CFGetTypeID((CFTypeRef)m_cfmdRef) == CFDictionaryGetTypeID(); } void PrintOut(wxString& sMessage) { PrintOutDictionary(sMessage, m_cfmdRef); } static void PrintOutDictionary(wxString& sMessage, CFDictionaryRef cfdRef) { CFIndex cfiCount = CFDictionaryGetCount(cfdRef); CFTypeRef* pKeys = new CFTypeRef[cfiCount]; CFTypeRef* pValues = new CFTypeRef[cfiCount]; CFDictionaryGetKeysAndValues(cfdRef, pKeys, pValues); for(CFIndex i = 0; i < cfiCount; ++i) { wxString sKey = wxMacCFStringHolder(CFCopyTypeIDDescription(CFGetTypeID(pKeys[i]))).AsString(); wxString sValue = wxMacCFStringHolder(CFCopyTypeIDDescription(CFGetTypeID(pValues[i]))).AsString(); sMessage << wxString::Format(wxT("[{#%d} Key : %s]"), (int) i, sKey.c_str()); PrintOutType(sMessage, sKey, pKeys[i]); sMessage << wxString::Format(wxT("\n\t[Value : %s]"), sValue.c_str()); PrintOutType(sMessage, sValue, pValues[i]); sMessage << wxT("\n"); } delete[] pKeys; delete[] pValues; } static void PrintOutArray(wxString& sMessage, CFArrayRef cfaRef) { for(CFIndex i = 0; i < CFArrayGetCount(cfaRef); ++i) { wxString sValue = wxMacCFStringHolder(CFCopyTypeIDDescription(CFGetTypeID( CFArrayGetValueAtIndex(cfaRef, i) ))).AsString(); sMessage << wxString::Format(wxT("\t\t[{#%d} ArrayValue : %s]\n"), (int) i, sValue.c_str()); PrintOutType(sMessage, sValue, CFArrayGetValueAtIndex(cfaRef, i)); } } static void PrintOutType(wxString& sMessage, wxString sValue, CFTypeRef cfRef) { sMessage << wxT(" {"); if(sValue == wxT("CFString")) { sMessage << wxMacCFStringHolder((CFStringRef)cfRef, false).AsString(); } else if(sValue == wxT("CFNumber")) { int nOut; CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType, &nOut); sMessage << nOut; } else if(sValue == wxT("CFDictionary")) { PrintOutDictionary(sMessage, (CFDictionaryRef)cfRef); } else if(sValue == wxT("CFArray")) { PrintOutArray(sMessage, (CFArrayRef)cfRef); } else if(sValue == wxT("CFBoolean")) { sMessage << (cfRef == kCFBooleanTrue ? wxT("true") : wxT("false")); } else if(sValue == wxT("CFURL")) { sMessage << wxMacCFStringHolder(CFURLCopyPath((CFURLRef) cfRef)).AsString(); } else { sMessage << wxT("*****UNKNOWN TYPE******"); } sMessage << wxT("} "); } #if wxUSE_MIMETYPE void MakeValidXML(); #endif CFTypeRef WriteAsXML() { return CFPropertyListCreateXMLData(kCFAllocatorDefault, m_cfmdRef); } bool ReadAsXML(CFTypeRef cfData, wxString* pErrorMsg = NULL) { Clear(); CFStringRef cfsError=NULL; m_cfmdRef = (CFMutableDictionaryRef) CFPropertyListCreateFromXMLData( kCFAllocatorDefault, (CFDataRef)cfData, kCFPropertyListMutableContainersAndLeaves, &cfsError ); if(cfsError) { if(pErrorMsg) *pErrorMsg = wxMacCFStringHolder(cfsError).AsString(); else CFRelease(cfsError); } return m_cfmdRef != NULL; } private: CFMutableDictionaryRef m_cfmdRef; }; // ---------------------------------------------------------------------------- // wxCFArray // ---------------------------------------------------------------------------- class wxCFArray { public: wxCFArray(CFTypeRef ref, bool bRetain = wxCF_RELEASE) { m_cfmaRef = (CFMutableArrayRef)ref; if(bRetain == wxCF_RETAIN && ref) CFRetain(ref); } wxCFArray(CFIndex cfiSize = 0) : m_cfmaRef(NULL) { Create(cfiSize); } ~wxCFArray() { Clear(); } void MakeMutable(CFIndex cfiSize = 0) { wxASSERT(IsValid()); CFMutableArrayRef oldref = m_cfmaRef; m_cfmaRef = CFArrayCreateMutableCopy( kCFAllocatorDefault, cfiSize, (CFArrayRef)oldref); CFRelease(oldref); } void BuildCallbacks(CFArrayCallBacks* pCbs) { pCbs->version = 0; pCbs->retain = RetainProc; pCbs->release = ReleaseProc; pCbs->copyDescription = NULL; pCbs->equal = NULL; } void Create(CFIndex cfiSize = 0) { Clear(); CFArrayCallBacks cb; BuildCallbacks(&cb); m_cfmaRef = CFArrayCreateMutable(kCFAllocatorDefault, cfiSize, &cb); } void Clear() {if(m_cfmaRef) CFRelease(m_cfmaRef);} static const void* RetainProc(CFAllocatorRef, const void* v) { return (const void*) CFRetain(v); } static void ReleaseProc(CFAllocatorRef, const void* v) { CFRelease(v); } operator CFTypeRef () const { return (CFTypeRef)m_cfmaRef; } CFArrayRef GetCFArray() const { return (CFArrayRef)m_cfmaRef; } CFMutableArrayRef GetCFMutableArray() { return (CFMutableArrayRef) m_cfmaRef; } CFTypeRef operator [] (CFIndex cfiIndex) const { wxASSERT(IsValid()); return (CFTypeRef) CFArrayGetValueAtIndex((CFArrayRef)m_cfmaRef, cfiIndex); } CFIndex GetCount() { wxASSERT(IsValid()); return CFArrayGetCount((CFArrayRef)m_cfmaRef); } void Add(CFTypeRef cftValue) { wxASSERT(IsValid()); CFArrayAppendValue(m_cfmaRef, cftValue); } void Remove(CFIndex cfiIndex) { wxASSERT(IsValid()); wxASSERT(cfiIndex < GetCount()); CFArrayRemoveValueAtIndex(m_cfmaRef, cfiIndex); } void Set(CFIndex cfiIndex, CFTypeRef cftValue) { wxASSERT(IsValid()); wxASSERT(cfiIndex < GetCount()); CFArraySetValueAtIndex(m_cfmaRef, cfiIndex, cftValue); } bool IsOk() const {return m_cfmaRef != NULL; } bool IsValid() const { return IsOk() && CFGetTypeID((CFTypeRef)m_cfmaRef) == CFArrayGetTypeID(); } #if wxUSE_MIMETYPE void MakeValidXML(); #endif private: CFMutableArrayRef m_cfmaRef; }; // ---------------------------------------------------------------------------- // wxCFString // ---------------------------------------------------------------------------- class wxCFString { public: wxCFString(CFTypeRef ref, bool bRetain = wxCF_RELEASE) : m_Holder((CFStringRef)ref, bRetain == wxCF_RELEASE) { } wxCFString(const wxChar* szString) : m_Holder(wxString(szString), wxLocale::GetSystemEncoding()) { } wxCFString(const wxString& sString) : m_Holder(sString, wxLocale::GetSystemEncoding()) { } operator CFTypeRef() const { return (CFTypeRef) ((CFStringRef) m_Holder); } bool IsOk() { return ((CFTypeRef)(*this)) != NULL; } wxString BuildWXString() {return m_Holder.AsString(); } private: wxMacCFStringHolder m_Holder; }; // ---------------------------------------------------------------------------- // wxCFNumber // ---------------------------------------------------------------------------- class wxCFNumber { public: wxCFNumber(int nValue) { m_cfnRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &nValue); } wxCFNumber(CFTypeRef ref, bool bRetain = wxCF_RELEASE) : m_cfnRef((CFNumberRef)ref) { if(bRetain == wxCF_RETAIN && ref) CFRetain(ref); } ~wxCFNumber() { if(m_cfnRef) CFRelease(m_cfnRef); } operator CFTypeRef() const { return (CFTypeRef) m_cfnRef; } int GetValue() { int nOut; CFNumberGetValue( m_cfnRef, kCFNumberIntType, &nOut ); return nOut; } bool IsOk() { return m_cfnRef != NULL; } private: CFNumberRef m_cfnRef; }; // ---------------------------------------------------------------------------- // wxCFURL // ---------------------------------------------------------------------------- class wxCFURL { public: wxCFURL(CFTypeRef ref = NULL, bool bRetain = wxCF_RELEASE) : m_cfurlRef((CFURLRef)ref) { if(bRetain == wxCF_RETAIN && ref) CFRetain(ref); } wxCFURL(const wxCFString& URLString, CFTypeRef BaseURL = NULL) { Create(URLString, BaseURL); } void Create(const wxCFString& URLString, CFTypeRef BaseURL = NULL) { m_cfurlRef = CFURLCreateWithString( kCFAllocatorDefault, (CFStringRef)(CFTypeRef)URLString, (CFURLRef) BaseURL); } ~wxCFURL() {if(m_cfurlRef) CFRelease(m_cfurlRef);} wxString BuildWXString() { return wxCFString(CFURLCopyPath(m_cfurlRef)).BuildWXString(); } operator CFTypeRef() const { return (CFTypeRef)m_cfurlRef; } bool IsOk() { return m_cfurlRef != NULL; } private: CFURLRef m_cfurlRef; }; // ---------------------------------------------------------------------------- // wxCFData // ---------------------------------------------------------------------------- #define wxCFDATA_RELEASEBUFFER 1 #define wxCFDATA_RETAINBUFFER 0 class wxCFData { public: wxCFData(CFTypeRef ref, bool bRetain = wxCF_RELEASE) : m_cfdaRef((CFDataRef)ref) { if(bRetain == wxCF_RETAIN && ref) CFRetain(ref); } wxCFData(const UInt8* pBytes, CFIndex len, bool bKeep = wxCFDATA_RELEASEBUFFER) { if(bKeep == wxCFDATA_RELEASEBUFFER) { m_cfdaRef = CFDataCreateWithBytesNoCopy (kCFAllocatorDefault, pBytes, len, kCFAllocatorDefault); } else { m_cfdaRef = CFDataCreate(kCFAllocatorDefault, pBytes, len); } } ~wxCFData() {if(m_cfdaRef) CFRelease(m_cfdaRef);} const UInt8* GetValue() { return CFDataGetBytePtr(m_cfdaRef); } CFIndex GetCount() { return CFDataGetLength(m_cfdaRef); } operator CFTypeRef() const { return (CFTypeRef)m_cfdaRef; } bool IsOk() { return m_cfdaRef != NULL; } private: CFDataRef m_cfdaRef; }; void wxCFDictionary::MakeValidXML() { CFIndex cfiCount = GetCount(); CFTypeRef* pKeys = new CFTypeRef[cfiCount]; CFTypeRef* pValues = new CFTypeRef[cfiCount]; CFDictionaryGetKeysAndValues(m_cfmdRef, pKeys, pValues); //for plist xml format all dictionary keys must be cfstrings and no values in //the dictionary or subkeys/values can be NULL //Also, CFURLs are not allowed for(CFIndex i = 0; i < cfiCount; ++i) { //must be an array, dictionary, string, bool, or int and cannot be null //and dictionaries can only contain cfstring keys CFTypeRef cfRef = pValues[i]; if(!pKeys[i] || CFGetTypeID(pKeys[i]) != CFStringGetTypeID() || !cfRef) { Remove(pKeys[i]); --i; --cfiCount; delete[] pKeys; delete[] pValues; pKeys = new CFTypeRef[cfiCount]; pValues = new CFTypeRef[cfiCount]; CFDictionaryGetKeysAndValues(m_cfmdRef, pKeys, pValues); } else if (CFGetTypeID(cfRef) == CFArrayGetTypeID()) { CFRetain(cfRef); wxCFArray cfaCurrent(cfRef); cfaCurrent.MakeMutable(); cfaCurrent.MakeValidXML(); Set(pKeys[i], cfaCurrent); } else if (CFGetTypeID(cfRef) == CFDictionaryGetTypeID()) { CFRetain(cfRef); wxCFDictionary cfdCurrent(cfRef); cfdCurrent.MakeMutable(); cfdCurrent.MakeValidXML(); Set(pKeys[i], cfdCurrent); } else if ( CFGetTypeID(cfRef) != CFStringGetTypeID() && CFGetTypeID(cfRef) != CFNumberGetTypeID() && CFGetTypeID(cfRef) != CFBooleanGetTypeID() ) { Remove(pKeys[i]); --i; --cfiCount; delete[] pKeys; delete[] pValues; pKeys = new CFTypeRef[cfiCount]; pValues = new CFTypeRef[cfiCount]; CFDictionaryGetKeysAndValues(m_cfmdRef, pKeys, pValues); } } delete[] pValues; delete[] pKeys; } void wxCFArray::MakeValidXML() { for(CFIndex i = 0; i < GetCount(); ++i) { //must be an array, dictionary, string, bool, or int and cannot be null //and dictionaries can only contain cfstring keys CFTypeRef cfRef = (*this)[i]; if(!cfRef) { Remove(i); --i; } else if (CFGetTypeID(cfRef) == CFArrayGetTypeID()) { CFRetain(cfRef); wxCFArray cfaCurrent(cfRef); cfaCurrent.MakeMutable(); cfaCurrent.MakeValidXML(); Set(i, cfaCurrent); } else if (CFGetTypeID(cfRef) == CFDictionaryGetTypeID()) { CFRetain(cfRef); wxCFDictionary cfdCurrent(cfRef); cfdCurrent.MakeMutable(); cfdCurrent.MakeValidXML(); Set(i, cfdCurrent); } else if ( CFGetTypeID(cfRef) != CFStringGetTypeID() && CFGetTypeID(cfRef) != CFNumberGetTypeID() && CFGetTypeID(cfRef) != CFBooleanGetTypeID() ) { Remove(i); --i; } } } // // // // END TODO // // // wxFileType* wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo) { bool bInfoSuccess = false; const wxArrayString& asExtensions = ftInfo.GetExtensions(); size_t dwFoundIndex = 0; if(!asExtensions.GetCount()) { wxLogDebug(wxT("Must have extension to associate with")); } //Find and write to Info.plist in main bundle (note that some other //apps have theirs named differently, i.e. IE's is named Info-macos.plist //some apps (non-wx) use the 'plst' resource instead CFBundleRef cfbMain = CFBundleGetMainBundle(); if(cfbMain) { UInt32 dwBundleType, dwBundleCreator; CFBundleGetPackageInfo(cfbMain, &dwBundleType, &dwBundleCreator); //if launching terminal non-app version will be 'BNDL' (generic bundle, maybe in other cases too), //which will give us the incorrect info.plist path //otherwise it will be 'APPL', or in the case of a framework, //'FMWK' if(dwBundleType == 'APPL') { wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyBundleURL(cfbMain)); // wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyExecutableURL(cfbMain)); wxString sInfoPath; // sInfoPath << wxT("file://"); sInfoPath << cfurlBundleLoc.BuildWXString(); sInfoPath << wxT("Contents/Info.plist"); // wxCFDictionary cfdInfo( CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN ); wxCFDictionary cfdInfo; bool bInfoOpenSuccess = false; wxFile indictfile; if(indictfile.Open(sInfoPath, wxFile::read)) { CFIndex cfiBufLen = (CFIndex) indictfile.Length(); const UInt8* pBuffer = new UInt8[cfiBufLen]; indictfile.Read((void*)pBuffer, cfiBufLen); wxCFData cfdaInDict(pBuffer, cfiBufLen); wxString sError; bInfoOpenSuccess = cfdInfo.ReadAsXML(cfdaInDict, &sError); if(!bInfoOpenSuccess) wxLogDebug(sError); indictfile.Close(); } if(bInfoOpenSuccess) { cfdInfo.MakeMutable( cfdInfo.GetCount() + 1 ); wxCFArray cfaDocTypes( cfdInfo[ wxCFString(wxT("CFBundleDocumentTypes")) ], wxCF_RETAIN ); bool bAddDocTypesArrayToDictionary = cfaDocTypes.IsOk() == false; if(bAddDocTypesArrayToDictionary) cfaDocTypes.Create(); else cfaDocTypes.MakeMutable( cfaDocTypes.GetCount() + 1 ); bool bEntryFound = false; //search for duplicate CFIndex i; for(i = 0; i < cfaDocTypes.GetCount(); ++i) { wxCFDictionary cfdDocTypeEntry( cfaDocTypes[i], wxCF_RETAIN ); //A lot of apps dont do to mime types for some reason //so we go by extensions only wxCFArray cfaExtensions( cfdDocTypeEntry[ wxCFString(wxT("CFBundleTypeExtensions")) ], wxCF_RETAIN ); if(cfaExtensions.IsOk() == false) continue; for(CFIndex iExt = 0; iExt < cfaExtensions.GetCount(); ++iExt) { for (size_t iWXExt = 0; iWXExt < asExtensions.GetCount(); ++iWXExt) { if(asExtensions[iWXExt] == wxCFString(cfaExtensions[iExt], wxCF_RETAIN).BuildWXString()) { bEntryFound = true; dwFoundIndex = iWXExt; break; } } //end of wxstring array if(bEntryFound) break; } //end for cf array if(bEntryFound) break; }//end for doctypes wxCFDictionary cfdNewEntry; if(!ftInfo.GetDescription().empty()) { cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeName")), wxCFString(ftInfo.GetDescription()) ); } if(!ftInfo.GetIconFile().empty()) { cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeIconFile")), wxCFString(ftInfo.GetIconFile()) ); } wxCFArray cfaOSTypes; wxCFArray cfaExtensions; wxCFArray cfaMimeTypes; //OSTypes is a cfarray of four-char-codes - '****' for unrestricted cfaOSTypes.Add( wxCFString(wxT("****")) ); cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeOSTypes")), cfaOSTypes ); if(ftInfo.GetExtensionsCount() != 0) //'*' for unrestricted { for(size_t iExtension = 0; iExtension < (size_t)ftInfo.GetExtensionsCount(); ++iExtension) { cfaExtensions.Add( wxCFString( asExtensions[iExtension] ) ); } cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeExtensions")), cfaExtensions ); } if(!ftInfo.GetMimeType().empty()) { cfaMimeTypes.Add( wxCFString(ftInfo.GetMimeType()) ); cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeMIMETypes")), cfaMimeTypes ); } // Editor - can perform all actions // Viewer - all actions except manipulation/saving // None - can perform no actions cfdNewEntry.Add( wxCFString(wxT("CFBundleTypeRole")), wxCFString(wxT("Editor")) ); // Is application bundled? cfdNewEntry.Add( wxCFString(wxT("LSTypeIsPackage")), kCFBooleanTrue ); if(bEntryFound) cfaDocTypes.Set(i, cfdNewEntry); else cfaDocTypes.Add(cfdNewEntry); // // set the doc types array in the muted dictionary // if(bAddDocTypesArrayToDictionary) cfdInfo.Add(wxCFString(wxT("CFBundleDocumentTypes")), cfaDocTypes); else cfdInfo.Set(wxCFString(wxT("CFBundleDocumentTypes")), cfaDocTypes); cfdInfo.MakeValidXML(); wxFile outdictfile; if(outdictfile.Open(sInfoPath, wxFile::write)) { wxCFData cfdaInfo(cfdInfo.WriteAsXML()); if(cfdaInfo.IsOk()) { if(outdictfile.Write(cfdaInfo.GetValue(), cfdaInfo.GetCount()) != (wxFileOffset)cfdaInfo.GetCount()) { wxLogDebug(wxT("error in writing to file")); } else { bInfoSuccess = true; //#if defined(__DARWIN__) // //force launch services to update its database for the finder // OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true); // if(status != noErr) // { // wxLogDebug(wxT("LSRegisterURL Failed.")); // } //#endif } outdictfile.Close(); } else { outdictfile.Close(); wxLogDebug(wxT("Could not read in new dictionary")); } } else { wxLogDebug(wxString(wxT("Could not open [")) + sInfoPath + wxT("] for writing.")); } } else { wxLogDebug(wxT("No info dictionary in main bundle")); } } else { wxLogDebug(wxT("Can only call associate from bundled app within XXX.app")); } } else { wxLogDebug(wxT("No main bundle")); } #if defined(__DARWIN__) if(!bInfoSuccess) return NULL; #endif //on mac you have to embed it into the mac's file reference resource ('FREF' I believe) //or, alternately, you could just add an entry to m_hDatabase, but you'd need to get //the app's signature somehow... OSType processType, creator; OSStatus status = MoreProcGetProcessTypeSignature(NULL,&processType, &creator); if(status == noErr) { Str255 psCreatorName; FSSpec dummySpec; status = FindApplication(creator, false, psCreatorName, &dummySpec); if(status == noErr) { //get the file type if it exists - //if it really does then modify the database then save it, //otherwise we need to create a whole new entry wxFileType* pFileType = GetFileTypeFromExtension(asExtensions[dwFoundIndex]); if(pFileType) { ICMapEntry entry; ICGetMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, pFileType->m_impl->m_lIndex, &entry); memcpy(entry.creatorAppName, psCreatorName, sizeof(Str255)); entry.fileCreator = creator; status = ICSetMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, pFileType->m_impl->m_lIndex, &entry); //success if(status == noErr) { //kICAttrNoChange means we don't care about attributes such as //locking in the database // status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping, // kICAttrNoChange, (Handle) m_hDatabase); // if(status == noErr) return pFileType; // else // { // wxLogDebug(wxString::Format(wxT("%i - %s"), (int)status, wxT("ICSetPrefHandle failed."))); // } } else { wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetMapEntry failed."))); } //failure - cleanup delete pFileType; } else { //TODO: Maybe force all 3 of these to be non-empty? Str255 psExtension; Str255 psMimeType; Str255 psDescription; wxMacStringToPascal(wxString(wxT(".")) + ftInfo.GetExtensions()[0], psExtension); wxMacStringToPascal(ftInfo.GetMimeType(), psMimeType); wxMacStringToPascal(ftInfo.GetDescription(), psDescription); Str255 psPostCreatorName; wxMacStringToPascal(wxT(""), psPostCreatorName); //add the entry to the database ICMapEntry entry; entry.totalLength = sizeof(ICMapEntry); entry.fixedLength = kICMapFixedLength; entry.version = 0; entry.fileType = 0; //TODO: File type? entry.fileCreator = creator; entry.postCreator = 0; entry.flags = kICMapDataForkBit; //TODO: Maybe resource is valid by default too? PLstrcpy( entry.extension , psExtension ) ; memcpy(entry.creatorAppName, psCreatorName, sizeof(Str255)); memcpy(entry.postAppName, psPostCreatorName, sizeof(Str255)); memcpy(entry.MIMEType, psMimeType, sizeof(Str255)); memcpy(entry.entryName, psDescription, sizeof(Str255)); status = ICAddMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, &entry); if(status == noErr) { //kICAttrNoChange means we don't care about attributes such as //locking in the database // status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping, // kICAttrNoChange, (Handle) m_hDatabase); //return the entry in the database if successful // if(status == noErr) return GetFileTypeFromExtension(ftInfo.GetMimeType()); // else // { // wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetPrefHandle failed."))); // } } else { wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICAppMapEntry failed."))); } } } //end if FindApplcation was successful else { wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("FindApplication failed."))); } } //end if it could obtain app's signature else { wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("GetProcessSignature failed."))); } return NULL; } bool wxMimeTypesManagerImpl::Unassociate(wxFileType *pFileType) { wxASSERT(pFileType); bool bInfoSuccess = false; wxArrayString asExtensions; pFileType->GetExtensions(asExtensions); if(!asExtensions.GetCount()) { wxLogDebug(wxT("Must have extension to disassociate")); return false; } //Find and write to Info.plist in main bundle (note that some other //apps have theirs named differently, i.e. IE's is named Info-macos.plist //some apps (non-wx) use the 'plst' resource instead CFBundleRef cfbMain = CFBundleGetMainBundle(); if(cfbMain) { UInt32 dwBundleType, dwBundleCreator; CFBundleGetPackageInfo(cfbMain, &dwBundleType, &dwBundleCreator); //if launching terminal non-app version will be 'BNDL' (generic bundle, maybe in other cases too), //which will give us the incorrect info.plist path //otherwise it will be 'APPL', or in the case of a framework, //'FMWK' if(dwBundleType == 'APPL') { wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyBundleURL(cfbMain)); // wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyExecutableURL(cfbMain)); wxString sInfoPath; // sInfoPath << wxT("file://"); sInfoPath << cfurlBundleLoc.BuildWXString(); sInfoPath << wxT("Contents/Info.plist"); // wxCFDictionary cfdInfo( (CFTypeRef) CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN ); wxCFDictionary cfdInfo; bool bInfoOpenSuccess = false; wxFile indictfile; if(indictfile.Open(sInfoPath, wxFile::read)) { CFIndex cfiBufLen = (CFIndex) indictfile.Length(); const UInt8* pBuffer = new UInt8[cfiBufLen]; indictfile.Read((void*)pBuffer, cfiBufLen); wxCFData cfdaInDict(pBuffer, cfiBufLen); wxString sError; bInfoOpenSuccess = cfdInfo.ReadAsXML(cfdaInDict, &sError); if(!bInfoOpenSuccess) wxLogDebug(sError); indictfile.Close(); } if(bInfoOpenSuccess) { cfdInfo.MakeMutable( cfdInfo.GetCount() + 1 ); wxCFArray cfaDocTypes( cfdInfo[ wxCFString(wxT("CFBundleDocumentTypes")) ], wxCF_RETAIN ); if(cfaDocTypes.IsOk()) { bool bEntryFound = false; //search for duplicate CFIndex i; for(i = 0; i < cfaDocTypes.GetCount(); ++i) { wxCFDictionary cfdDocTypeEntry( cfaDocTypes[i], wxCF_RETAIN ); //A lot of apps dont do to mime types for some reason //so we go by extensions only wxCFArray cfaExtensions( cfdDocTypeEntry[ wxCFString(wxT("CFBundleTypeExtensions")) ], wxCF_RETAIN ); if(cfaExtensions.IsOk() == false) continue; for(CFIndex iExt = 0; iExt < cfaExtensions.GetCount(); ++iExt) { for (size_t iWXExt = 0; iWXExt < asExtensions.GetCount(); ++iWXExt) { if(asExtensions[iWXExt] == wxCFString(cfaExtensions[iExt], wxCF_RETAIN).BuildWXString()) { bEntryFound = true; cfaDocTypes.Remove(i); cfdInfo.Set( wxCFString(wxT("CFBundleDocumentTypes")) , cfaDocTypes ); break; } } //end of wxstring array if(bEntryFound) break; } //end for cf array if(bEntryFound) break; }//end for doctypes if(bEntryFound) { cfdInfo.MakeValidXML(); wxFile outdictfile; if(outdictfile.Open(sInfoPath, wxFile::write)) { wxCFData cfdaInfo(cfdInfo.WriteAsXML()); if(cfdaInfo.IsOk()) { if(outdictfile.Write(cfdaInfo.GetValue(), cfdaInfo.GetCount()) != (wxFileOffset)cfdaInfo.GetCount()) { wxLogDebug(wxT("error in writing to file")); } else { bInfoSuccess = true; //#if defined(__DARWIN__) // //force launch services to update its database for the finder // OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true); // if(status != noErr) // { // wxLogDebug(wxT("LSRegisterURL Failed.")); // } //#endif } outdictfile.Close(); } else { outdictfile.Close(); wxLogDebug(wxT("Could not read in new dictionary")); } } else { wxLogDebug(wxString(wxT("Could not open [")) + sInfoPath + wxT("] for writing.")); } } else { wxLogDebug(wxT("Entry not found to remove")); wxString sPrintOut; wxCFDictionary::PrintOutArray(sPrintOut, (CFArrayRef)(CFTypeRef)cfaDocTypes); wxLogDebug(sPrintOut); for(size_t i = 0; i < asExtensions.GetCount(); ++i) wxLogDebug(asExtensions[i]); } } else { wxLogDebug(wxT("No doc types array found")); wxString sPrintOut; cfdInfo.PrintOut(sPrintOut); wxLogDebug(sPrintOut); } } else { wxLogDebug(wxT("No info dictionary in main bundle")); } } else { wxLogDebug(wxT("Can only call associate from bundled app within XXX.app")); } } else { wxLogDebug(wxT("No main bundle")); } #if defined(__DARWIN__) if(!bInfoSuccess) return false; #endif //this should be as easy as removing the entry from the database and then saving //the database OSStatus status = ICDeleteMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, pFileType->m_impl->m_lIndex); if(status == noErr) { //kICAttrNoChange means we don't care about attributes such as //locking in the database // status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping, // kICAttrNoChange, (Handle) m_hDatabase); // if(status == noErr) return true; // else // { // wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetPrefHandle failed."))); // } } else { wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICDeleteMapEntry failed."))); } return false; } /* CFWriteStreamRef cfwsInfo = CFWriteStreamCreateWithFile( kCFAllocatorDefault, (CFURLRef) (CFTypeRef)cfurlInfoLoc ); // CFShow(cfdInfo); if(cfwsInfo) { Boolean bOpened = CFWriteStreamOpen(cfwsInfo); if(bOpened) { CFStringRef cfsError; CFIndex cfiWritten = CFPropertyListWriteToStream((CFPropertyListRef)(CFTypeRef)cfdInfo, cfwsInfo, kCFPropertyListXMLFormat_v1_0, //100 &cfsError); if(cfsError && cfiWritten == 0) { wxLogDebug(wxCFString(cfsError).BuildWXString()); wxString sMessage; cfdInfo.PrintOut(sMessage); wxLogDebug(sMessage); } else { bInfoSuccess = true; //#if defined(__DARWIN__) // //force launch services to update its database for the finder // OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true); // if(status != noErr) // { // wxLogDebug(wxT("LSRegisterURL Failed.")); // } //#endif } CFWriteStreamClose(cfwsInfo); */ #endif //wxUSE_MIMETYPE