1 /////////////////////////////////////////////////////////////////////////////
2 // Name: mac/mimetype.cpp
3 // Purpose: Mac Carbon implementation for wx mime-related classes
8 // Copyright: (c) 2005 Ryan Norton (<wxprojects@comcast.net>)
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
13 #pragma implementation "mimetype.h"
16 // for compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
24 #include "wx/string.h"
36 #include "wx/dynarray.h"
37 #include "wx/confbase.h"
39 #include "wx/mac/mimetype.h"
40 #include "wx/mac/private.h" //wxMacMakeStringFromPascal
42 // other standard headers
44 #include <InternetConfig.h> //For mime types
47 /* START CODE SAMPLE FROM TECHNOTE 1002 (http://developer.apple.com/technotes/tn/tn1002.html) */
49 /* IsRemoteVolume can be used to find out if the
50 volume referred to by vRefNum is a remote volume
51 located somewhere on a network. the volume's attribute
52 flags (copied from the GetVolParmsInfoBuffer structure)
53 are returned in the longword pointed to by vMAttrib. */
54 OSErr
IsRemoteVolume(short vRefNum
, Boolean
*isRemote
, long *vMAttrib
) {
56 GetVolParmsInfoBuffer volinfo
;
58 volPB
.ioParam
.ioVRefNum
= vRefNum
;
59 volPB
.ioParam
.ioNamePtr
= NULL
;
60 volPB
.ioParam
.ioBuffer
= (Ptr
) &volinfo
;
61 volPB
.ioParam
.ioReqCount
= sizeof(volinfo
);
62 err
= PBHGetVolParmsSync(&volPB
);
64 *isRemote
= (volinfo
.vMServerAdr
!= 0);
65 *vMAttrib
= volinfo
.vMAttrib
;
71 /* BuildVolumeList fills the array pointed to by vols with
72 a list of the currently mounted volumes. If includeRemote
73 is true, then remote server volumes will be included in
74 the list. When remote server volumes are included in the
75 list, they will be added to the end of the list. On entry,
76 *count should contain the size of the array pointed to by
77 vols. On exit, *count will be set to the number of id numbers
78 placed in the array. If vMAttribMask is non-zero, then
79 only volumes with matching attributes are added to the
80 list of volumes. bits in the vMAttribMask should use the
81 same encoding as bits in the vMAttrib field of
82 the GetVolParmsInfoBuffer structure. */
83 OSErr
BuildVolumeList(Boolean includeRemote
, short *vols
,
84 long *count
, long vMAttribMask
) {
91 /* set up and check parameters */
92 volPB
.volumeParam
.ioNamePtr
= NULL
;
94 if (*count
== 0) return noErr
;
96 /* iterate through volumes */
97 for (volPB
.volumeParam
.ioVolIndex
= 1;
98 PBHGetVInfoSync(&volPB
) == noErr
;
99 volPB
.volumeParam
.ioVolIndex
++) {
101 /* skip remote volumes, if necessary */
102 err
= IsRemoteVolume(volPB
.volumeParam
.ioVRefNum
, &isRemote
, &vMAttrib
);
103 if (err
!= noErr
) goto bail
;
104 if ( ( includeRemote
|| ! isRemote
)
105 && (vMAttrib
& vMAttribMask
) == vMAttribMask
) {
107 /* add local volumes at the front, remote
108 volumes at the end */
110 vols
[nlocal
+ nremote
++] = volPB
.volumeParam
.ioVRefNum
;
113 BlockMoveData(vols
+nlocal
, vols
+nlocal
+1,
114 nremote
*sizeof(short));
115 vols
[nlocal
++] = volPB
.volumeParam
.ioVRefNum
;
119 if ((nlocal
+ nremote
) >= *count
) break;
123 *count
= (nlocal
+ nremote
);
128 /* FindApplication iterates through mounted volumes
129 searching for an application with the given creator
130 type. If includeRemote is true, then remote volumes
131 will be searched (after local ones) for an application
132 with the creator type. */
136 /* Hacked to output to appName */
138 OSErr
FindApplication(OSType appCreator
, Boolean includeRemote
, Str255 appName
, FSSpec
* appSpec
) {
139 short rRefNums
[kMaxVols
];
144 /* get a list of volumes - with desktop files */
146 err
= BuildVolumeList(includeRemote
, rRefNums
, &volCount
,
147 (1<<bHasDesktopMgr
) );
148 if (err
!= noErr
) return err
;
150 /* iterate through the list */
151 for (i
=0; i
<volCount
; i
++) {
153 /* has a desktop file? */
154 desktopPB
.ioCompletion
= NULL
;
155 desktopPB
.ioVRefNum
= rRefNums
[i
];
156 desktopPB
.ioNamePtr
= NULL
;
157 desktopPB
.ioIndex
= 0;
158 err
= PBDTGetPath(&desktopPB
);
159 if (err
!= noErr
) continue;
161 /* has the correct app?? */
162 desktopPB
.ioFileCreator
= appCreator
;
163 desktopPB
.ioNamePtr
= appName
;
164 err
= PBDTGetAPPLSync(&desktopPB
);
165 if (err
!= noErr
) continue;
167 /* make a file spec referring to it */
168 err
= FSMakeFSSpec(rRefNums
[i
],
169 desktopPB
.ioAPPLParID
, appName
,
171 if (err
!= noErr
) continue;
180 /* END CODE SAMPLE FROM TECHNOTE 1002 (http://developer.apple.com/technotes/tn/tn1002.html) */
182 //yeah, duplicated code
183 pascal OSErr
FSpGetFullPath(const FSSpec
*spec
,
184 short *fullPathLength
,
196 /* Default to noErr */
197 realResult
= result
= noErr
;
199 /* work around Nav Services "bug" (it returns invalid FSSpecs with empty names) */
201 if ( spec->name[0] == 0 )
203 result = FSMakeFSSpecCompat(spec->vRefNum, spec->parID, spec->name, &tempSpec);
208 /* Make a copy of the input FSSpec that can be modified */
209 BlockMoveData(spec
, &tempSpec
, sizeof(FSSpec
));
212 if ( result
== noErr
)
214 if ( tempSpec
.parID
== fsRtParID
)
216 /* The object is a volume */
218 /* Add a colon to make it a full pathname */
220 tempSpec
.name
[tempSpec
.name
[0]] = ':';
223 result
= PtrToHand(&tempSpec
.name
[1], fullPath
, tempSpec
.name
[0]);
227 /* The object isn't a volume */
229 /* Is the object a file or a directory? */
230 pb
.dirInfo
.ioNamePtr
= tempSpec
.name
;
231 pb
.dirInfo
.ioVRefNum
= tempSpec
.vRefNum
;
232 pb
.dirInfo
.ioDrDirID
= tempSpec
.parID
;
233 pb
.dirInfo
.ioFDirIndex
= 0;
234 result
= PBGetCatInfoSync(&pb
);
235 /* Allow file/directory name at end of path to not exist. */
237 if ( (result
== noErr
) || (result
== fnfErr
) )
239 /* if the object is a directory, append a colon so full pathname ends with colon */
240 if ( (result
== noErr
) && (pb
.hFileInfo
.ioFlAttrib
& kioFlAttribDirMask
) != 0 )
243 tempSpec
.name
[tempSpec
.name
[0]] = ':';
246 /* Put the object name in first */
247 result
= PtrToHand(&tempSpec
.name
[1], fullPath
, tempSpec
.name
[0]);
248 if ( result
== noErr
)
250 /* Get the ancestor directory names */
251 pb
.dirInfo
.ioNamePtr
= tempSpec
.name
;
252 pb
.dirInfo
.ioVRefNum
= tempSpec
.vRefNum
;
253 pb
.dirInfo
.ioDrParID
= tempSpec
.parID
;
254 do /* loop until we have an error or find the root directory */
256 pb
.dirInfo
.ioFDirIndex
= -1;
257 pb
.dirInfo
.ioDrDirID
= pb
.dirInfo
.ioDrParID
;
258 result
= PBGetCatInfoSync(&pb
);
259 if ( result
== noErr
)
261 /* Append colon to directory name */
263 tempSpec
.name
[tempSpec
.name
[0]] = ':';
265 /* Add directory name to beginning of fullPath */
266 (void) Munger(*fullPath
, 0, NULL
, 0, &tempSpec
.name
[1], tempSpec
.name
[0]);
269 } while ( (result
== noErr
) && (pb
.dirInfo
.ioDrDirID
!= fsRtDirID
) );
275 if ( result
== noErr
)
277 /* Return the length */
278 *fullPathLength
= GetHandleSize(*fullPath
);
279 result
= realResult
; /* return realResult in case it was fnfErr */
283 /* Dispose of the handle and return NULL and zero length */
284 if ( *fullPath
!= NULL
)
286 DisposeHandle(*fullPath
);
296 // On the mac there are two ways to open a file - one is through apple events and the
297 // finder, another is through mime types.
299 // So, really there are two ways to implement wxFileType...
301 // Mime types are only available on OS 8.1+ through the InternetConfig API
303 // Much like the old-style file manager, it has 3 levels of flexibility for its methods -
304 // Low - which means you have to iterate yourself through the mime database
305 // Medium - which lets you sort of cache the database if you want to use lowlevel functions
306 // High - which requires access to the database every time
308 // We want to be efficient (i.e. professional :) ) about it, so we use a combo of low
309 // and mid-level functions
311 // TODO: Should we call ICBegin/ICEnd? Then where?
315 inline void wxLogMimeDebug(const wxChar
* szMsg
, OSStatus status
)
317 wxLogDebug(wxString::Format(wxT("%s LINE:%i OSERROR:%i"), szMsg
, __LINE__
, (int)status
));
320 // in case we're compiling in non-GUI mode
321 class WXDLLEXPORT wxIcon
;
323 bool wxFileTypeImpl::SetCommand(const wxString
& cmd
, const wxString
& verb
, bool overwriteprompt
)
325 wxASSERT_MSG( m_manager
!= NULL
, wxT("Bad wxFileType") );
330 bool wxFileTypeImpl::SetDefaultIcon(const wxString
& strIcon
, int index
)
332 wxASSERT_MSG( m_manager
!= NULL
, wxT("Bad wxFileType") );
337 bool wxFileTypeImpl::GetOpenCommand(wxString
*openCmd
,
338 const wxFileType::MessageParameters
& params
) const
340 wxString cmd
= GetCommand(wxT("open"));
342 *openCmd
= wxFileType::ExpandCommand(cmd
, params
);
344 return !openCmd
->empty();
348 wxFileTypeImpl::GetPrintCommand(wxString
*printCmd
,
349 const wxFileType::MessageParameters
& params
)
352 wxString cmd
= GetCommand(wxT("print"));
354 *printCmd
= wxFileType::ExpandCommand(cmd
, params
);
356 return !printCmd
->empty();
360 // Internet Config vs. Launch Services
362 // From OS 8 on there was internet config...
363 // However, OSX and its finder does not use info
364 // from Internet Config at all - the Internet Config
365 // database ONLY CONTAINS APPS THAT ARE CLASSIC APPS
366 // OR REGISTERED THROUGH INTERNET CONFIG
368 // Therefore on OSX in order for the open command to be useful
369 // we need to go straight to launch services
372 #if defined(__DARWIN__)
374 //on darwin, use launch services
375 #include "LaunchServices.h"
377 wxString
wxFileTypeImpl::GetCommand(const wxString
& verb
) const
379 wxASSERT_MSG( m_manager
!= NULL
, wxT("Bad wxFileType") );
381 if(verb
== wxT("open"))
384 ICGetMapEntry( (ICInstance
) m_manager
->m_hIC
,
385 (Handle
) m_manager
->m_hDatabase
,
388 wxString sCurrentExtension
= wxMacMakeStringFromPascal(entry
.extension
);
389 sCurrentExtension
= sCurrentExtension
.Right(sCurrentExtension
.Length()-1 );
391 //type, creator, ext, roles, outapp (FSRef), outappurl
392 CFURLRef cfurlAppPath
;
393 OSStatus status
= LSGetApplicationForInfo (kLSUnknownType
,
395 wxMacCFStringHolder(sCurrentExtension
, wxLocale::GetSystemEncoding()),
402 CFStringRef cfsUnixPath
= CFURLCopyFileSystemPath(cfurlAppPath
, kCFURLPOSIXPathStyle
);
403 CFRelease(cfurlAppPath
);
407 return wxMacCFStringHolder(cfsUnixPath
).AsString(wxLocale::GetSystemEncoding());
411 wxLogDebug(wxString::Format(wxT("%i - %s - %i"),
413 wxT("LSGetApplicationForInfo failed."),
418 return wxEmptyString
;
421 #else //carbon/classic implementation
423 wxString
wxFileTypeImpl::GetCommand(const wxString
& verb
) const
425 wxASSERT_MSG( m_manager
!= NULL
, wxT("Bad wxFileType") );
427 if(verb
== wxT("open"))
430 ICGetMapEntry( (ICInstance
) m_manager
->m_hIC
,
431 (Handle
) m_manager
->m_hDatabase
,
434 //The entry in the mimetype database only contains the app
435 //that's registered - it may not exist... we need to remap the creator
436 //type and find the right application
438 // THIS IS REALLY COMPLICATED :\. There are a lot of conversions going
442 if(FindApplication(entry
.fileCreator
, false, outName
, &outSpec
) != noErr
)
443 return wxEmptyString
;
445 Handle outPathHandle
;
447 OSErr err
= FSpGetFullPath(&outSpec
, &outPathSize
, &outPathHandle
);
451 char* szPath
= *outPathHandle
;
452 wxString
sClassicPath(szPath
, wxConvLocal
, outPathSize
);
453 #if defined(__DARWIN__)
454 //Classic Path --> Unix (OSX) Path
455 CFURLRef finalURL
= CFURLCreateWithFileSystemPath(kCFAllocatorDefault
,
456 wxMacCFStringHolder(sClassicPath
, wxLocale::GetSystemEncoding()),
458 false); //false == not a directory
460 //clean up memory from the classic path handle
461 DisposeHandle(outPathHandle
);
465 CFStringRef cfsUnixPath
= CFURLCopyFileSystemPath(finalURL
, kCFURLPOSIXPathStyle
);
470 return wxMacCFStringHolder(cfsUnixPath
).AsString(wxLocale::GetSystemEncoding());
472 #else //classic HFS path acceptable
478 wxLogMimeDebug(wxT("FSpGetFullPath failed."), (OSStatus
)err
);
481 return wxEmptyString
;
485 bool wxFileTypeImpl::GetDescription(wxString
*desc
) const
487 wxASSERT_MSG( m_manager
!= NULL
, wxT("Bad wxFileType") );
490 ICGetMapEntry( (ICInstance
) m_manager
->m_hIC
,
491 (Handle
) m_manager
->m_hDatabase
,
494 *desc
= wxMacMakeStringFromPascal(entry
.entryName
);
498 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
)
500 wxASSERT_MSG( m_manager
!= NULL
, wxT("Bad wxFileType") );
503 ICGetMapEntry( (ICInstance
) m_manager
->m_hIC
,
504 (Handle
) m_manager
->m_hDatabase
,
507 //entry has period in it
508 wxString sCurrentExtension
= wxMacMakeStringFromPascal(entry
.extension
);
509 extensions
.Add( sCurrentExtension
.Right(sCurrentExtension
.Length()-1) );
513 bool wxFileTypeImpl::GetMimeType(wxString
*mimeType
) const
515 wxASSERT_MSG( m_manager
!= NULL
, wxT("Bad wxFileType") );
518 ICGetMapEntry( (ICInstance
) m_manager
->m_hIC
,
519 (Handle
) m_manager
->m_hDatabase
,
522 *mimeType
= wxMacMakeStringFromPascal(entry
.MIMEType
);
526 bool wxFileTypeImpl::GetMimeTypes(wxArrayString
& mimeTypes
) const
540 bool wxFileTypeImpl::GetIcon(wxIconLocation
*WXUNUSED(icon
)) const
542 wxASSERT_MSG( m_manager
!= NULL
, wxT("Bad wxFileType") );
544 // no such file type or no value or incorrect icon entry
548 size_t wxFileTypeImpl::GetAllCommands(wxArrayString
* verbs
, wxArrayString
* commands
,
549 const wxFileType::MessageParameters
& params
) const
551 wxASSERT_MSG( m_manager
!= NULL
, wxT("Bad wxFileType") );
556 if(GetOpenCommand(&sCommand
, params
))
558 verbs
->Add(wxString(wxT("open")));
559 commands
->Add(sCommand
);
566 void wxMimeTypesManagerImpl::Initialize(int mailcapStyles
, const wxString
& extraDir
)
568 wxASSERT_MSG(m_hIC
== NULL
, wxT("Already initialized wxMimeTypesManager!"));
570 //start internet config - log if there's an error
571 //the second param is the signature of the application, also known
572 //as resource ID 0. However, as per some recent discussions, we may not
573 //have a signature for this app, so a generic 'APPL' which is the executable
574 //type will work for now
575 OSStatus status
= ICStart( (ICInstance
*) &m_hIC
, 'APPL');
579 wxLogDebug(wxT("Could not initialize wxMimeTypesManager!"));
586 m_hDatabase
= (void**) NewHandle(0);
587 status
= ICFindPrefHandle( (ICInstance
) m_hIC
, kICMapping
, &attr
, (Handle
) m_hDatabase
);
589 //the database file can be corrupt (on OSX its
590 //~/Library/Preferences/com.apple.internetconfig.plist)
595 wxLogDebug(wxT("Corrupt Mime Database!"));
599 //obtain the number of entries in the map
600 status
= ICCountMapEntries( (ICInstance
) m_hIC
, (Handle
) m_hDatabase
, &m_lCount
);
601 wxASSERT( status
== noErr
);
607 for(long i = 1; i <= m_lCount; ++i)
609 OSStatus status = ICGetIndMapEntry( (ICInstance) m_hIC, (Handle) m_hDatabase, i, &pos, &entry);
613 wxString sCreator = wxMacMakeStringFromPascal(entry.creatorAppName);
614 wxString sCurrentExtension = wxMacMakeStringFromPascal(entry.extension);
615 wxString sMIMEType = wxMacMakeStringFromPascal(entry.MIMEType);
618 impl.Init(this, pos);
620 if(sMIMEType == wxT("text/html") && sCurrentExtension == wxT(".html"))
623 impl.GetOpenCommand (&cmd,
624 wxFileType::MessageParameters (wxT("http://www.google.com"),
627 wxPrintf(wxT("APP: [%s]\n"), cmd.c_str());
634 void wxMimeTypesManagerImpl::ClearData()
638 DisposeHandle((Handle
)m_hDatabase
);
639 //this can return an error, but we don't really care that much about it
640 ICStop( (ICInstance
) m_hIC
);
646 // Q) Iterating through the map - why does it use if (err == noErr) instead of just asserting?
647 // A) Some intermediate indexes are bad while subsequent ones may be good. Its wierd, I know.
650 // extension -> file type
651 wxFileType
* wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& e
)
653 wxASSERT_MSG( m_hIC
!= NULL
, wxT("wxMimeTypesManager not Initialized!") );
655 //low level functions - iterate through the database
659 for(long i
= 1; i
<= m_lCount
; ++i
)
661 OSStatus status
= ICGetIndMapEntry( (ICInstance
) m_hIC
, (Handle
) m_hDatabase
, i
, &pos
, &entry
);
665 wxString sCurrentExtension
= wxMacMakeStringFromPascal(entry
.extension
);
666 if( sCurrentExtension
.Right(sCurrentExtension
.Length()-1) == e
) //entry has period in it
668 wxFileType
* pFileType
= new wxFileType();
669 pFileType
->m_impl
->Init((wxMimeTypesManagerImpl
*)this, pos
);
678 // MIME type -> extension -> file type
679 wxFileType
* wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
681 wxASSERT_MSG( m_hIC
!= NULL
, wxT("wxMimeTypesManager not Initialized!") );
683 //low level functions - iterate through the database
687 for(long i
= 1; i
<= m_lCount
; ++i
)
689 OSStatus status
= ICGetIndMapEntry( (ICInstance
) m_hIC
, (Handle
) m_hDatabase
, i
, &pos
, &entry
);
690 wxASSERT_MSG( status
== noErr
, wxString::Format(wxT("Error: %d"), (int)status
) );
694 if( wxMacMakeStringFromPascal(entry
.MIMEType
) == mimeType
)
696 wxFileType
* pFileType
= new wxFileType();
697 pFileType
->m_impl
->Init((wxMimeTypesManagerImpl
*)this, pos
);
706 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString
& mimetypes
)
708 wxASSERT_MSG( m_hIC
!= NULL
, wxT("wxMimeTypesManager not Initialized!") );
710 //low level functions - iterate through the database
714 long lStartCount
= (long) mimetypes
.GetCount();
716 for(long i
= 1; i
<= m_lCount
; ++i
)
718 OSStatus status
= ICGetIndMapEntry( (ICInstance
) m_hIC
, (Handle
) m_hDatabase
, i
, &pos
, &entry
);
719 if( status
== noErr
)
720 mimetypes
.Add( wxMacMakeStringFromPascal(entry
.MIMEType
) );
723 return mimetypes
.GetCount() - lStartCount
;
727 pascal OSStatus
MoreProcGetProcessTypeSignature(
728 const ProcessSerialNumberPtr pPSN
,
729 OSType
*pProcessType
,
732 OSStatus anErr
= noErr
;
733 ProcessInfoRec infoRec
;
734 ProcessSerialNumber localPSN
;
736 infoRec
.processInfoLength
= sizeof(ProcessInfoRec
);
737 infoRec
.processName
= nil
;
738 infoRec
.processAppSpec
= nil
;
741 localPSN
.highLongOfPSN
= 0;
742 localPSN
.lowLongOfPSN
= kCurrentProcess
;
747 anErr
= GetProcessInformation(&localPSN
, &infoRec
);
750 *pProcessType
= infoRec
.processType
;
751 *pCreator
= infoRec
.processSignature
;
755 }//end MoreProcGetProcessTypeSignature
757 #include "wx/mac/corefoundation/cfwrappers.h"
759 void wxCFDictionary::MakeValidXML()
762 CFIndex cfiCount
= GetCount();
763 CFTypeRef
* pKeys
= new CFTypeRef
[cfiCount
];
764 CFTypeRef
* pValues
= new CFTypeRef
[cfiCount
];
766 CFDictionaryGetKeysAndValues(m_cfmdRef
, pKeys
, pValues
);
768 //for plist xml format all dictionary keys must be cfstrings and no values in
769 //the dictionary or subkeys/values can be NULL
770 //Also, CFURLs are not allowed
771 for(CFIndex i
= 0; i
< cfiCount
; ++i
)
773 //must be an array, dictionary, string, bool, or int and cannot be null
774 //and dictionaries can only contain cfstring keys
775 CFTypeRef cfRef
= pValues
[i
];
777 CFGetTypeID(pKeys
[i
]) != CFStringGetTypeID() ||
785 pKeys
= new CFTypeRef
[cfiCount
];
786 pValues
= new CFTypeRef
[cfiCount
];
787 CFDictionaryGetKeysAndValues(m_cfmdRef
, pKeys
, pValues
);
789 else if (CFGetTypeID(cfRef
) == CFArrayGetTypeID())
792 wxCFArray
cfaCurrent(cfRef
);
793 cfaCurrent
.MakeMutable();
794 cfaCurrent
.MakeValidXML();
795 Set(pKeys
[i
], cfaCurrent
);
797 else if (CFGetTypeID(cfRef
) == CFDictionaryGetTypeID())
800 wxCFDictionary
cfdCurrent(cfRef
);
801 cfdCurrent
.MakeMutable();
802 cfdCurrent
.MakeValidXML();
803 Set(pKeys
[i
], cfdCurrent
);
805 else if( ( CFGetTypeID(cfRef
) == CFStringGetTypeID() ||
806 CFGetTypeID(cfRef
) == CFNumberGetTypeID() ||
807 CFGetTypeID(cfRef
) == CFBooleanGetTypeID() ) == false )
814 pKeys
= new CFTypeRef
[cfiCount
];
815 pValues
= new CFTypeRef
[cfiCount
];
816 CFDictionaryGetKeysAndValues(m_cfmdRef
, pKeys
, pValues
);
825 void wxCFArray::MakeValidXML()
828 for(CFIndex i
= 0; i
< GetCount(); ++i
)
830 //must be an array, dictionary, string, bool, or int and cannot be null
831 //and dictionaries can only contain cfstring keys
832 CFTypeRef cfRef
= (*this)[i
];
838 else if (CFGetTypeID(cfRef
) == CFArrayGetTypeID())
841 wxCFArray
cfaCurrent(cfRef
);
842 cfaCurrent
.MakeMutable();
843 cfaCurrent
.MakeValidXML();
846 else if (CFGetTypeID(cfRef
) == CFDictionaryGetTypeID())
849 wxCFDictionary
cfdCurrent(cfRef
);
850 cfdCurrent
.MakeMutable();
851 cfdCurrent
.MakeValidXML();
854 else if( ( CFGetTypeID(cfRef
) == CFStringGetTypeID() ||
855 CFGetTypeID(cfRef
) == CFNumberGetTypeID() ||
856 CFGetTypeID(cfRef
) == CFBooleanGetTypeID() ) == false )
865 wxFileType
* wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo
& ftInfo
)
867 bool bInfoSuccess
= false;
869 const wxArrayString
& asExtensions
= ftInfo
.GetExtensions();
870 size_t dwFoundIndex
= 0;
871 if(!asExtensions
.GetCount())
873 wxLogDebug(wxT("Must have extension to associate with"));
876 //Find and write to Info.plist in main bundle (note that some other
877 //apps have theirs named differently, i.e. IE's is named Info-macos.plist
878 //some apps (non-wx) use the 'plst' resource instead
879 CFBundleRef cfbMain
= CFBundleGetMainBundle();
882 UInt32 dwBundleType
, dwBundleCreator
;
883 CFBundleGetPackageInfo(cfbMain
, &dwBundleType
, &dwBundleCreator
);
885 //if launching terminal non-app version will be 'BNDL' (generic bundle, maybe in other cases too),
886 //which will give us the incorrect info.plist path
887 //otherwise it will be 'APPL', or in the case of a framework,
889 if(dwBundleType
== 'APPL')
892 wxCFURL
cfurlBundleLoc((CFTypeRef
)CFBundleCopyBundleURL(cfbMain
));
893 // wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyExecutableURL(cfbMain));
895 // sInfoPath << wxT("file://");
896 sInfoPath
<< cfurlBundleLoc
.BuildWXString();
897 sInfoPath
<< wxT("Contents/Info.plist");
899 // wxCFDictionary cfdInfo( (CFTypeRef) CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN );
900 wxCFDictionary cfdInfo
;
901 bool bInfoOpenSuccess
= false;
903 if(indictfile
.Open(sInfoPath
, wxFile::read
));
905 CFIndex cfiBufLen
= (CFIndex
) indictfile
.Length();
906 const UInt8
* pBuffer
= new UInt8
[cfiBufLen
];
907 indictfile
.Read((void*)pBuffer
, cfiBufLen
);
908 wxCFData
cfdaInDict(pBuffer
, cfiBufLen
);
910 bInfoOpenSuccess
= cfdInfo
.ReadAsXML(cfdaInDict
, &sError
);
911 if(!bInfoOpenSuccess
)
917 cfdInfo
.MakeMutable( cfdInfo
.GetCount() + 1 );
919 wxCFArray
cfaDocTypes( cfdInfo
[ wxCFString(wxT("CFBundleDocumentTypes")) ], wxCF_RETAIN
);
921 bool bAddDocTypesArrayToDictionary
= cfaDocTypes
.IsOk() == false;
922 if(bAddDocTypesArrayToDictionary
)
923 cfaDocTypes
.Create();
925 cfaDocTypes
.MakeMutable( cfaDocTypes
.GetCount() + 1 );
927 bool bEntryFound
= false;
929 //search for duplicate
931 for(i
= 0; i
< cfaDocTypes
.GetCount(); ++i
)
933 wxCFDictionary
cfdDocTypeEntry( cfaDocTypes
[i
], wxCF_RETAIN
);
935 //A lot of apps dont do to mime types for some reason
936 //so we go by extensions only
937 wxCFArray
cfaExtensions( cfdDocTypeEntry
[ wxCFString(wxT("CFBundleTypeExtensions")) ],
940 if(cfaExtensions
.IsOk() == false)
943 for(CFIndex iExt
= 0; iExt
< cfaExtensions
.GetCount(); ++iExt
)
945 for (size_t iWXExt
= 0; iWXExt
< asExtensions
.GetCount(); ++iWXExt
)
947 if(asExtensions
[iWXExt
] ==
948 wxCFString(cfaExtensions
[iExt
], wxCF_RETAIN
).BuildWXString())
951 dwFoundIndex
= iWXExt
;
954 } //end of wxstring array
964 wxCFDictionary cfdNewEntry
;
966 if(!ftInfo
.GetDescription().empty())
968 cfdNewEntry
.Add( wxCFString(wxT("CFBundleTypeName")),
969 wxCFString(ftInfo
.GetDescription()) );
972 if(!ftInfo
.GetIconFile().empty())
974 cfdNewEntry
.Add( wxCFString(wxT("CFBundleTypeIconFile")),
975 wxCFString(ftInfo
.GetIconFile()) );
979 wxCFArray cfaOSTypes
;
980 wxCFArray cfaExtensions
;
981 wxCFArray cfaMimeTypes
;
984 //OSTypes is a cfarray of four-char-codes - '****' for unrestricted
985 cfaOSTypes
.Add( wxCFString(wxT("****")) );
986 cfdNewEntry
.Add( wxCFString(wxT("CFBundleTypeOSTypes")), cfaOSTypes
);
988 if(ftInfo
.GetExtensionsCount() != 0) //'*' for unrestricted
990 for(size_t iExtension
= 0; iExtension
< (size_t)ftInfo
.GetExtensionsCount(); ++iExtension
)
992 cfaExtensions
.Add( wxCFString( asExtensions
[iExtension
] ) );
995 cfdNewEntry
.Add( wxCFString(wxT("CFBundleTypeExtensions")), cfaExtensions
);
998 if(!ftInfo
.GetMimeType().empty())
1000 cfaMimeTypes
.Add( wxCFString(ftInfo
.GetMimeType()) );
1001 cfdNewEntry
.Add( wxCFString(wxT("CFBundleTypeMIMETypes")), cfaMimeTypes
);
1004 // Editor - can perform all actions
1005 // Viewer - all actions except manipulation/saving
1006 // None - can perform no actions
1007 cfdNewEntry
.Add( wxCFString(wxT("CFBundleTypeRole")), wxCFString(wxT("Editor")) );
1009 // Is application bundled?
1010 cfdNewEntry
.Add( wxCFString(wxT("LSTypeIsPackage")), kCFBooleanTrue
);
1013 cfaDocTypes
.Set(i
, cfdNewEntry
);
1015 cfaDocTypes
.Add(cfdNewEntry
);
1018 // set the doc types array in the muted dictionary
1021 if(bAddDocTypesArrayToDictionary
)
1022 cfdInfo
.Add(wxCFString(wxT("CFBundleDocumentTypes")), cfaDocTypes
);
1024 cfdInfo
.Set(wxCFString(wxT("CFBundleDocumentTypes")), cfaDocTypes
);
1026 cfdInfo
.MakeValidXML();
1029 if(outdictfile
.Open(sInfoPath
, wxFile::write
))
1031 wxCFData
cfdaInfo(cfdInfo
.WriteAsXML());
1034 if(outdictfile
.Write(cfdaInfo
.GetValue(), cfdaInfo
.GetCount()) !=
1035 (wxFileOffset
)cfdaInfo
.GetCount())
1037 wxLogDebug(wxT("error in writing to file"));
1041 bInfoSuccess
= true;
1042 //#if defined(__DARWIN__)
1043 // //force launch services to update its database for the finder
1044 // OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true);
1045 // if(status != noErr)
1047 // wxLogDebug(wxT("LSRegisterURL Failed."));
1051 outdictfile
.Close();
1055 outdictfile
.Close();
1056 wxLogDebug(wxT("Could not read in new dictionary"));
1061 wxLogDebug(wxString(wxT("Could not open [")) +
1062 sInfoPath
+ wxT("] for writing."));
1067 wxLogDebug(wxT("No info dictionary in main bundle"));
1072 wxLogDebug(wxT("Can only call associate from bundled app within XXX.app"));
1077 wxLogDebug(wxT("No main bundle"));
1080 #if defined(__DARWIN__)
1084 //on mac you have to embed it into the mac's file reference resource ('FREF' I believe)
1085 //or, alternately, you could just add an entry to m_hDatabase, but you'd need to get
1086 //the app's signature somehow...
1090 OSStatus status
= MoreProcGetProcessTypeSignature(NULL
,&processType
, &creator
);
1094 Str255 psCreatorName
;
1096 status
= FindApplication(creator
, false, psCreatorName
, &dummySpec
);
1101 //get the file type if it exists -
1102 //if it really does then modify the database then save it,
1103 //otherwise we need to create a whole new entry
1104 wxFileType
* pFileType
= GetFileTypeFromExtension(asExtensions
[dwFoundIndex
]);
1108 ICGetMapEntry( (ICInstance
) m_hIC
, (Handle
) m_hDatabase
,
1109 pFileType
->m_impl
->m_lIndex
, &entry
);
1111 entry
.creatorAppName
= psCreatorName
;
1112 entry
.fileCreator
= creator
;
1114 status
= ICSetMapEntry( (ICInstance
) m_hIC
, (Handle
) m_hDatabase
,
1115 pFileType
->m_impl
->m_lIndex
, &entry
);
1120 //kICAttrNoChange means we don't care about attributes such as
1121 //locking in the database
1122 // status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping,
1123 // kICAttrNoChange, (Handle) m_hDatabase);
1124 // if(status == noErr)
1128 // wxLogDebug(wxString::Format(wxT("%i - %s"), (int)status, wxT("ICSetPrefHandle failed.")));
1133 wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__
, wxT("ICSetMapEntry failed.")));
1141 //TODO: Maybe force all 3 of these to be non-empty?
1144 Str255 psDescription
;
1146 wxMacStringToPascal(ftInfo
.GetExtensions()[0], psExtension
);
1147 wxMacStringToPascal(ftInfo
.GetMimeType(), psMimeType
);
1148 wxMacStringToPascal(ftInfo
.GetDescription(), psDescription
);
1150 Str255 psPostCreatorName
;
1151 wxMacStringToPascal(wxT(""), psPostCreatorName
);
1154 //add the entry to the database
1156 entry
.totalLength
= sizeof(ICMapEntry
);
1157 entry
.fixedLength
= kICMapFixedLength
;
1159 entry
.fileType
= 0; //TODO: File type?
1160 entry
.fileCreator
= creator
;
1161 entry
.postCreator
= 0;
1162 entry
.flags
= kICMapDataForkBit
; //TODO: Maybe resource is valid by default too?
1163 entry
.extension
= psExtension
;
1164 entry
.creatorAppName
= psCreatorName
;
1165 entry
.postAppName
= psPostCreatorName
;
1166 entry
.MIMEType
= psMimeType
;
1167 entry
.entryName
= psDescription
;
1169 status
= ICAddMapEntry( (ICInstance
) m_hIC
, (Handle
) m_hDatabase
, &entry
);
1173 //kICAttrNoChange means we don't care about attributes such as
1174 //locking in the database
1175 // status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping,
1176 // kICAttrNoChange, (Handle) m_hDatabase);
1178 //return the entry in the database if successful
1179 // if(status == noErr)
1180 return GetFileTypeFromExtension(ftInfo
.GetMimeType());
1183 // wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetPrefHandle failed.")));
1188 wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__
, wxT("ICAppMapEntry failed.")));
1191 } //end if FindApplcation was successful
1194 wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__
, wxT("FindApplication failed.")));
1196 } //end if it could obtain app's signature
1199 wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__
, wxT("GetProcessSignature failed.")));
1205 wxMimeTypesManagerImpl::Unassociate(wxFileType
*pFileType
)
1207 wxASSERT(pFileType
);
1208 bool bInfoSuccess
= false;
1210 wxArrayString asExtensions
;
1211 pFileType
->GetExtensions(asExtensions
);
1213 if(!asExtensions
.GetCount())
1215 wxLogDebug(wxT("Must have extension to disassociate"));
1219 //Find and write to Info.plist in main bundle (note that some other
1220 //apps have theirs named differently, i.e. IE's is named Info-macos.plist
1221 //some apps (non-wx) use the 'plst' resource instead
1222 CFBundleRef cfbMain
= CFBundleGetMainBundle();
1225 UInt32 dwBundleType
, dwBundleCreator
;
1226 CFBundleGetPackageInfo(cfbMain
, &dwBundleType
, &dwBundleCreator
);
1228 //if launching terminal non-app version will be 'BNDL' (generic bundle, maybe in other cases too),
1229 //which will give us the incorrect info.plist path
1230 //otherwise it will be 'APPL', or in the case of a framework,
1232 if(dwBundleType
== 'APPL')
1235 wxCFURL
cfurlBundleLoc((CFTypeRef
)CFBundleCopyBundleURL(cfbMain
));
1236 // wxCFURL cfurlBundleLoc((CFTypeRef)CFBundleCopyExecutableURL(cfbMain));
1238 // sInfoPath << wxT("file://");
1239 sInfoPath
<< cfurlBundleLoc
.BuildWXString();
1240 sInfoPath
<< wxT("Contents/Info.plist");
1242 // wxCFDictionary cfdInfo( (CFTypeRef) CFBundleGetInfoDictionary(cfbMain), wxCF_RETAIN );
1243 wxCFDictionary cfdInfo
;
1244 bool bInfoOpenSuccess
= false;
1246 if(indictfile
.Open(sInfoPath
, wxFile::read
));
1248 CFIndex cfiBufLen
= (CFIndex
) indictfile
.Length();
1249 const UInt8
* pBuffer
= new UInt8
[cfiBufLen
];
1250 indictfile
.Read((void*)pBuffer
, cfiBufLen
);
1251 wxCFData
cfdaInDict(pBuffer
, cfiBufLen
);
1253 bInfoOpenSuccess
= cfdInfo
.ReadAsXML(cfdaInDict
, &sError
);
1254 if(!bInfoOpenSuccess
)
1258 if(bInfoOpenSuccess
)
1260 cfdInfo
.MakeMutable( cfdInfo
.GetCount() + 1 );
1262 wxCFArray
cfaDocTypes( cfdInfo
[ wxCFString(wxT("CFBundleDocumentTypes")) ], wxCF_RETAIN
);
1264 if(cfaDocTypes
.IsOk())
1266 bool bEntryFound
= false;
1268 //search for duplicate
1270 for(i
= 0; i
< cfaDocTypes
.GetCount(); ++i
)
1272 wxCFDictionary
cfdDocTypeEntry( cfaDocTypes
[i
], wxCF_RETAIN
);
1274 //A lot of apps dont do to mime types for some reason
1275 //so we go by extensions only
1276 wxCFArray
cfaExtensions( cfdDocTypeEntry
[ wxCFString(wxT("CFBundleTypeExtensions")) ],
1279 if(cfaExtensions
.IsOk() == false)
1282 for(CFIndex iExt
= 0; iExt
< cfaExtensions
.GetCount(); ++iExt
)
1284 for (size_t iWXExt
= 0; iWXExt
< asExtensions
.GetCount(); ++iWXExt
)
1286 if(asExtensions
[iWXExt
] ==
1287 wxCFString(cfaExtensions
[iExt
], wxCF_RETAIN
).BuildWXString())
1290 cfaDocTypes
.Remove(i
);
1291 cfdInfo
.Set( wxCFString(wxT("CFBundleDocumentTypes")) , cfaDocTypes
);
1294 } //end of wxstring array
1298 } //end for cf array
1306 cfdInfo
.MakeValidXML();
1309 if(outdictfile
.Open(sInfoPath
, wxFile::write
))
1311 wxCFData
cfdaInfo(cfdInfo
.WriteAsXML());
1314 if(outdictfile
.Write(cfdaInfo
.GetValue(), cfdaInfo
.GetCount()) !=
1315 (wxFileOffset
)cfdaInfo
.GetCount())
1317 wxLogDebug(wxT("error in writing to file"));
1321 bInfoSuccess
= true;
1322 //#if defined(__DARWIN__)
1323 // //force launch services to update its database for the finder
1324 // OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true);
1325 // if(status != noErr)
1327 // wxLogDebug(wxT("LSRegisterURL Failed."));
1331 outdictfile
.Close();
1335 outdictfile
.Close();
1336 wxLogDebug(wxT("Could not read in new dictionary"));
1341 wxLogDebug(wxString(wxT("Could not open [")) +
1342 sInfoPath
+ wxT("] for writing."));
1347 wxLogDebug(wxT("Entry not found to remove"));
1349 wxCFDictionary::PrintOutArray(sPrintOut
, (CFArrayRef
)(CFTypeRef
)cfaDocTypes
);
1350 wxLogDebug(sPrintOut
);
1351 for(size_t i
= 0; i
< asExtensions
.GetCount(); ++i
)
1352 wxLogDebug(asExtensions
[i
]);
1357 wxLogDebug(wxT("No doc types array found"));
1358 wxString sPrintOut
; cfdInfo
.PrintOut(sPrintOut
); wxLogDebug(sPrintOut
);
1363 wxLogDebug(wxT("No info dictionary in main bundle"));
1368 wxLogDebug(wxT("Can only call associate from bundled app within XXX.app"));
1373 wxLogDebug(wxT("No main bundle"));
1376 #if defined(__DARWIN__)
1381 //this should be as easy as removing the entry from the database and then saving
1383 OSStatus status
= ICDeleteMapEntry( (ICInstance
) m_hIC
, (Handle
) m_hDatabase
,
1384 pFileType
->m_impl
->m_lIndex
);
1388 //kICAttrNoChange means we don't care about attributes such as
1389 //locking in the database
1390 // status = ICSetPrefHandle((ICInstance) m_hIC, kICMapping,
1391 // kICAttrNoChange, (Handle) m_hDatabase);
1393 // if(status == noErr)
1397 // wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__, wxT("ICSetPrefHandle failed.")));
1403 wxLogDebug(wxString::Format(wxT("%i - %s"), __LINE__
, wxT("ICDeleteMapEntry failed.")));
1409 CFWriteStreamRef cfwsInfo = CFWriteStreamCreateWithFile(
1410 kCFAllocatorDefault,
1411 (CFURLRef) (CFTypeRef)cfurlInfoLoc );
1416 Boolean bOpened = CFWriteStreamOpen(cfwsInfo);
1419 CFStringRef cfsError;
1420 CFIndex cfiWritten = CFPropertyListWriteToStream((CFPropertyListRef)(CFTypeRef)cfdInfo,
1422 kCFPropertyListXMLFormat_v1_0, //100
1424 if(cfsError && cfiWritten == 0)
1426 wxLogDebug(wxCFString(cfsError).BuildWXString());
1428 cfdInfo.PrintOut(sMessage);
1429 wxLogDebug(sMessage);
1433 bInfoSuccess = true;
1434 //#if defined(__DARWIN__)
1435 // //force launch services to update its database for the finder
1436 // OSStatus status = LSRegisterURL((CFURLRef)(CFTypeRef)cfurlBundleLoc, true);
1437 // if(status != noErr)
1439 // wxLogDebug(wxT("LSRegisterURL Failed."));
1444 CFWriteStreamClose(cfwsInfo);
1447 #endif //wxUSE_MIMETYPE