///////////////////////////////////////////////////////////////////////////// // Name: src/mac/carbon/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 42015 2006-10-14 16:55:55Z SC $ // 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 FinderInfo functions - or // use Launch Services and search that app's plist for the icon. // // Put some special juice in for the print command. // // for compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_MIMETYPE #include "wx/mac/mimetype.h" #ifndef WX_PRECOMP #include "wx/dynarray.h" #include "wx/string.h" #include "wx/intl.h" #include "wx/log.h" #if wxUSE_GUI #include "wx/icon.h" #endif #endif #include "wx/file.h" #include "wx/confbase.h" #include "wx/mac/private.h" // other standard headers #include #ifndef __DARWIN__ #include #include #endif #ifndef __LP64__ // 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; OSErr err = noErr; long nlocal, nremote; long vMAttrib; // 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. // // Hacked to output to appName // #define kMaxVols 20 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 << bHasDesktopMgr) ); if (err != noErr) return err; // iterate through the list for (i=0; iname[0] == 0 ) { result = FSMakeFSSpecCompat(spec->vRefNum, spec->parID, spec->name, &tempSpec); } else { #endif // Make a copy of the input FSSpec that can be modified BlockMoveData( spec, &tempSpec, sizeof(FSSpec) ); if ( result == noErr ) { if ( tempSpec.parID == fsRtParID ) { // 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 { // 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; // loop until we have an error or find the root directory do { 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; } #endif // // 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, so surround it with quotes if (cfsUnixPath) { wxString resultStr; resultStr = wxString(wxT("'")) + wxMacCFStringHolder(cfsUnixPath).AsString(wxLocale::GetSystemEncoding()) + wxString(wxT("'")); return resultStr; } } 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; OSErr err = FindApplication( entry.fileCreator, false, outName, &outSpec ); if (err != noErr) return wxEmptyString; Handle outPathHandle; short outPathSize; 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; } 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 #if 0 CFBundleRef cfbMain = CFBundleGetMainBundle(); wxCFDictionary cfdInfo( CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN ); wxString sLog; cfdInfo.PrintOut(sLog); wxLogDebug(sLog); #endif // 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!")); wxFAIL; 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 ); #if 0 //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()); } } } #endif } 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!") ); ICMapEntry entry; long pos; // low level functions - iterate through the database 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!") ); ICMapEntry entry; long pos, lStartCount; // low level functions - iterate through the database 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 = NULL; #ifndef __LP64__ infoRec.processAppSpec = NULL; #endif if ( pPSN == NULL ) { localPSN.highLongOfPSN = 0; localPSN.lowLongOfPSN = kCurrentProcess; } else { localPSN = *pPSN; } anErr = GetProcessInformation(&localPSN, &infoRec); if (anErr == noErr) { *pProcessType = infoRec.processType; *pCreator = infoRec.processSignature; } return anErr; } // // // TODO: clean this up, its messy // // // #include "wx/mac/corefoundation/cfstring.h" #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, const 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()) {} virtual ~wxCFString() {} 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); } virtual ~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); } virtual ~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 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); } } virtual ~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; // additionally, 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(); if (bAddDocTypesArrayToDictionary) cfaDocTypes.Create(); else cfaDocTypes.MakeMutable( cfaDocTypes.GetCount() + 1 ); bool bEntryFound = false; // search for duplicates CFIndex i; for (i = 0; i < cfaDocTypes.GetCount(); ++i) { wxCFDictionary cfdDocTypeEntry( cfaDocTypes[i], wxCF_RETAIN ); // A lot of apps don't support MIME types for some reason // so we go by extensions only wxCFArray cfaExtensions( cfdDocTypeEntry[ wxCFString(wxT("CFBundleTypeExtensions")) ], wxCF_RETAIN ); if (!cfaExtensions.IsOk()) 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 ); //'*' for unrestricted if (ftInfo.GetExtensionsCount() != 0) { for (size_t iExtension = 0; iExtension < 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; #ifndef __LP64__ FSSpec dummySpec; status = FindApplication(creator, false, psCreatorName, &dummySpec); #else FSRef fsref; status = LSFindApplicationForInfo( creator, NULL, NULL, &fsref ,NULL); HFSUniStr255 name; status = FSGetCatalogInfo(&fsref, kFSCatInfoNone, NULL, &name, NULL, NULL); CFStringRef str = FSCreateStringFromHFSUniStr( 0 , &name ); CFStringGetPascalString(str, psCreatorName, 256, CFStringGetSystemEncoding()); CFRelease( str ); #endif 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) { return pFileType; //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, psMimeType, psDescription; wxMacStringToPascal(wxString(wxT(".")) + ftInfo.GetExtensions()[0], psExtension); wxMacStringToPascal(ftInfo.GetMimeType(), psMimeType); wxMacStringToPascal(ftInfo.GetDescription(), psDescription); Str255 psPostCreatorName; wxMacStringToPascal(wxEmptyString, 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) { return GetFileTypeFromExtension(ftInfo.GetMimeType()); // 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()) 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) { return true; //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; } #if 0 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 #endif //wxUSE_MIMETYPE