1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/core/mimetype.cpp
3 // Purpose: Mac OS X implementation for wx MIME-related classes
4 // Author: Neil Perkins
8 // Copyright: (C) 2010 Neil Perkins
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
13 #include "wx/wxprec.h"
25 #include "wx/osx/mimetype.h"
26 #include "wx/osx/private.h"
28 /////////////////////////////////////////////////////////////////////////////
30 /////////////////////////////////////////////////////////////////////////////
33 // Read a string or array of strings from a CFDictionary for a given key
34 // Return an empty list on error
35 wxArrayString
ReadStringListFromCFDict( CFDictionaryRef dictionary
, CFStringRef key
)
37 // Create an empty list
38 wxArrayString results
;
40 // Look up the requested key
41 CFTypeRef valueData
= CFDictionaryGetValue( dictionary
, key
);
46 if( CFGetTypeID( valueData
) == CFArrayGetTypeID() )
48 CFArrayRef valueList
= reinterpret_cast< CFArrayRef
>( valueData
);
53 // Look at each item in the array
54 for( CFIndex i
= 0, n
= CFArrayGetCount( valueList
); i
< n
; i
++ )
56 itemData
= CFArrayGetValueAtIndex( valueList
, i
);
58 // Make sure the item is a string
59 if( CFGetTypeID( itemData
) == CFStringGetTypeID() )
61 // wxCFStringRef will automatically CFRelease, so an extra CFRetain is needed
62 item
= reinterpret_cast< CFStringRef
>( itemData
);
63 wxCFRetain( item
.get() );
65 // Add the string to the list
66 results
.Add( item
.AsString() );
71 // Value is a single string - return a list of one item
72 else if( CFGetTypeID( valueData
) == CFStringGetTypeID() )
74 // wxCFStringRef will automatically CFRelease, so an extra CFRetain is needed
75 wxCFStringRef value
= reinterpret_cast< CFStringRef
>( valueData
);
76 wxCFRetain( value
.get() );
78 // Add the string to the list
79 results
.Add( value
.AsString() );
83 // Return the list. If the dictionary did not contain key,
84 // or contained the wrong data type, the list will be empty
89 // Given a single CFDictionary representing document type data, check whether
90 // it matches a particular file extension. Return true for a match, false otherwise
91 bool CheckDocTypeMatchesExt( CFDictionaryRef docType
, CFStringRef requiredExt
)
93 const static wxCFStringRef
extKey( "CFBundleTypeExtensions" );
95 CFTypeRef extData
= CFDictionaryGetValue( docType
, extKey
);
100 if( CFGetTypeID( extData
) == CFArrayGetTypeID() )
102 CFArrayRef extList
= reinterpret_cast< CFArrayRef
>( extData
);
105 for( CFIndex i
= 0, n
= CFArrayGetCount( extList
); i
< n
; i
++ )
107 extItem
= CFArrayGetValueAtIndex( extList
, i
);
109 if( CFGetTypeID( extItem
) == CFStringGetTypeID() )
111 CFStringRef ext
= reinterpret_cast< CFStringRef
>( extItem
);
113 if( CFStringCompare( ext
, requiredExt
, kCFCompareCaseInsensitive
) == kCFCompareEqualTo
)
119 if( CFGetTypeID( extData
) == CFStringGetTypeID() )
121 CFStringRef ext
= reinterpret_cast< CFStringRef
>( extData
);
123 if( CFStringCompare( ext
, requiredExt
, kCFCompareCaseInsensitive
) == kCFCompareEqualTo
)
131 // Given a data structure representing document type data, or a list of such
132 // structures, find the one which matches a particular file extension
133 // The result will be a CFDictionary containining document type data
134 // if a match is found, or null otherwise
135 CFDictionaryRef
GetDocTypeForExt( CFTypeRef docTypeData
, CFStringRef requiredExt
)
137 CFDictionaryRef docType
;
144 if( CFGetTypeID( docTypeData
) == CFArrayGetTypeID() )
146 docTypes
= reinterpret_cast< CFArrayRef
>( docTypeData
);
148 for( CFIndex i
= 0, n
= CFArrayGetCount( docTypes
); i
< n
; i
++ )
150 item
= CFArrayGetValueAtIndex( docTypes
, i
);
152 if( CFGetTypeID( item
) == CFDictionaryGetTypeID() )
154 docType
= reinterpret_cast< CFDictionaryRef
>( item
);
156 if( CheckDocTypeMatchesExt( docType
, requiredExt
) )
162 if( CFGetTypeID( docTypeData
) == CFDictionaryGetTypeID() )
164 CFDictionaryRef docType
= reinterpret_cast< CFDictionaryRef
>( docTypeData
);
166 if( CheckDocTypeMatchesExt( docType
, requiredExt
) )
174 // Given an application bundle reference and the name of an icon file
175 // which is a resource in that bundle, look up the full (posix style)
176 // path to that icon. Returns the path, or an empty wxString on failure
177 wxString
GetPathForIconFile( CFBundleRef bundle
, CFStringRef iconFile
)
179 // If either parameter is NULL there is no hope of success
180 if( !bundle
|| !iconFile
)
181 return wxEmptyString
;
183 // Create a range object representing the whole string
185 wholeString
.location
= 0;
186 wholeString
.length
= CFStringGetLength( iconFile
);
188 // Index of the period in the file name for iconFile
189 UniCharCount periodIndex
;
191 // In order to locate the period delimiting the extension,
192 // iconFile must be represented as UniChar[]
194 // Allocate a buffer and copy in the iconFile string
195 UniChar
* buffer
= new UniChar
[ wholeString
.length
];
196 CFStringGetCharacters( iconFile
, wholeString
, buffer
);
198 // Locate the period character
199 OSStatus status
= LSGetExtensionInfo( wholeString
.length
, buffer
, &periodIndex
);
201 // Deallocate the buffer
204 // If the period could not be located it will not be possible to get the URL
205 if( status
!= noErr
|| periodIndex
== kLSInvalidExtensionIndex
)
206 return wxEmptyString
;
209 // Range representing the name part of iconFile
210 CFRange iconNameRange
;
211 iconNameRange
.location
= 0;
212 iconNameRange
.length
= periodIndex
- 1;
214 // Range representing the extension part of iconFile
215 CFRange iconExtRange
;
216 iconExtRange
.location
= periodIndex
;
217 iconExtRange
.length
= wholeString
.length
- periodIndex
;
219 // Get the name and extension strings
220 wxCFStringRef iconName
= CFStringCreateWithSubstring( kCFAllocatorDefault
, iconFile
, iconNameRange
);
221 wxCFStringRef iconExt
= CFStringCreateWithSubstring( kCFAllocatorDefault
, iconFile
, iconExtRange
);
223 // Now it is possible to query the URL for the icon as a resource
224 wxCFRef
< CFURLRef
> iconUrl
= wxCFRef
< CFURLRef
>( CFBundleCopyResourceURL( bundle
, iconName
, iconExt
, NULL
) );
227 return wxEmptyString
;
229 // All being well, return the icon path
230 return wxCFStringRef( CFURLCopyFileSystemPath( iconUrl
, kCFURLPOSIXPathStyle
) ).AsString();
234 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
238 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
243 /////////////////////////////////////////////////////////////////////////////
244 // Init / shutdown functions
246 // The Launch Services / UTI API provides no helpful way of getting a list
247 // of all registered types. Instead the API is focused around looking up
248 // information for a particular file type once you already have some
249 // identifying piece of information. In order to get a list of registered
250 // types it would first be necessary to get a list of all bundles exporting
251 // type information (all application bundles may be sufficient) then look at
252 // the Info.plist file for those bundles and store the type information. As
253 // this would require trawling the hard disk when a wxWidgets program starts
254 // up it was decided instead to load the information lazily.
256 // If this behaviour really messes up your app, please feel free to implement
257 // the trawling approach (perhaps with a configure switch?). A good place to
258 // start would be CFBundleCreateBundlesFromDirectory( NULL, "/Applications", "app" )
259 /////////////////////////////////////////////////////////////////////////////
262 void wxMimeTypesManagerImpl::Initialize(int WXUNUSED(mailcapStyles
), const wxString
& WXUNUSED(extraDir
))
267 void wxMimeTypesManagerImpl::ClearData()
273 /////////////////////////////////////////////////////////////////////////////
276 // Apple uses a number of different systems for file type information.
277 // As of Spring 2010, these include:
279 // OS Types / OS Creators
282 // Uniform Type Identifiers (UTI)
284 // This implementation of the type manager for Mac supports all except OS
285 // Type / OS Creator codes, which have been deprecated for some time with
286 // less and less support in recent versions of OS X.
288 // The UTI system is the internal system used by OS X, as such it offers a
289 // one-to-one mapping with file types understood by Mac OS X and is the
290 // easiest way to convert between type systems. However, UTI meta-data is
291 // not stored with data files (as of OS X 10.6), instead the OS looks at
292 // the file extension and uses this to determine the UTI. Simillarly, most
293 // applications do not yet advertise the file types they can handle by UTI.
295 // The result is that no one typing system is suitable for all tasks. Further,
296 // as there is not a one-to-one mapping between type systems for the
297 // description of any given type, it follows that ambiguity cannot be precluded,
298 // whichever system is taken to be the "master".
300 // In the implementation below I have used UTI as the master key for looking
301 // up file types. Extensions and mime types are mapped to UTIs and the data
302 // for each UTI contains a list of all associated extensions and mime types.
303 // This has the advantage that unknown types will still be assigned a unique
304 // ID, while using any other system as the master could result in conflicts
305 // if there were no mime type assigned to an extension or vice versa. However
306 // there is still plenty of room for ambiguity if two or more applications
307 // are fighting over ownership of a particular type or group of types.
309 // If this proves to be serious issue it may be helpful to add some slightly
310 // more cleve logic to the code so that the key used to look up a file type is
311 // always first in the list in the resulting wxFileType object. I.e, if you
312 // look up .mpeg3 the list you get back could be .mpeg3, mp3, .mpg3, while
313 // looking up .mp3 would give .mp3, .mpg3, .mpeg3. The simplest way to do
314 // this would probably to keep two separate sets of data, one for lookup
315 // by extetnsion and one for lookup by mime type.
317 // One other point which may require consideration is handling of unrecognised
318 // types. Using UTI these will be assigned a unique ID of dyn.xxx. This will
319 // result in a wxFileType object being returned, although querying properties
320 // on that object will fail. If it would be more helpful to return NULL in this
321 // case a suitable check can be added.
322 /////////////////////////////////////////////////////////////////////////////
324 // Look up a file type by extension
325 // The extensions if mapped to a UTI
326 // If the requested extension is not know the OS is querried and the results saved
327 wxFileType
*wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& ext
)
331 const TagMap::const_iterator extItr
= m_extMap
.find( ext
);
333 if( extItr
== m_extMap
.end() )
335 wxCFStringRef utiRef
= UTTypeCreatePreferredIdentifierForTag( kUTTagClassFilenameExtension
, wxCFStringRef( ext
), NULL
);
336 m_extMap
[ ext
] = uti
= utiRef
.AsString();
339 uti
= extItr
->second
;
341 return GetFileTypeFromUti( uti
);
344 // Look up a file type by mime type
345 // The mime type is mapped to a UTI
346 // If the requested extension is not know the OS is querried and the results saved
347 wxFileType
*wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
351 const TagMap::const_iterator mimeItr
= m_mimeMap
.find( mimeType
);
353 if( mimeItr
== m_mimeMap
.end() )
355 wxCFStringRef utiRef
= UTTypeCreatePreferredIdentifierForTag( kUTTagClassFilenameExtension
, wxCFStringRef( mimeType
), NULL
);
356 m_mimeMap
[ mimeType
] = uti
= utiRef
.AsString();
359 uti
= mimeItr
->second
;
361 return GetFileTypeFromUti( uti
);
364 // Look up a file type by UTI
365 // If the requested extension is not know the OS is querried and the results saved
366 wxFileType
*wxMimeTypesManagerImpl::GetFileTypeFromUti(const wxString
& uti
)
368 UtiMap::const_iterator utiItr
= m_utiMap
.find( uti
);
370 if( utiItr
== m_utiMap
.end() )
372 LoadTypeDataForUti( uti
);
373 LoadDisplayDataForUti( uti
);
376 wxFileType
* const ft
= new wxFileType
;
377 ft
->m_impl
->m_uti
= uti
;
378 ft
->m_impl
->m_manager
= this;
384 /////////////////////////////////////////////////////////////////////////////
387 // These functions query the OS for information on a particular file type
388 /////////////////////////////////////////////////////////////////////////////
391 // Look up all extensions and mime types associated with a UTI
392 void wxMimeTypesManagerImpl::LoadTypeDataForUti(const wxString
& uti
)
394 // Keys in to the UTI declaration plist
395 const static wxCFStringRef
tagsKey( "UTTypeTagSpecification" );
396 const static wxCFStringRef
extKey( "public.filename-extension" );
397 const static wxCFStringRef
mimeKey( "public.mime-type" );
399 // Get the UTI as a CFString
400 wxCFStringRef
utiRef( uti
);
402 // Get a copy of the UTI declaration
403 wxCFRef
< CFDictionaryRef
> utiDecl
;
404 utiDecl
= wxCFRef
< CFDictionaryRef
>( UTTypeCopyDeclaration( utiRef
) );
409 // Get the tags spec (the section of a UTI declaration containing mappings to other type systems)
410 CFTypeRef tagsData
= CFDictionaryGetValue( utiDecl
, tagsKey
);
412 if( CFGetTypeID( tagsData
) != CFDictionaryGetTypeID() )
415 CFDictionaryRef tags
= reinterpret_cast< CFDictionaryRef
>( tagsData
);
417 // Read tags for extensions and mime types
418 m_utiMap
[ uti
].extensions
= ReadStringListFromCFDict( tags
, extKey
);
419 m_utiMap
[ uti
].mimeTypes
= ReadStringListFromCFDict( tags
, mimeKey
);
423 // Look up the (locale) display name and icon file associated with a UTI
424 void wxMimeTypesManagerImpl::LoadDisplayDataForUti(const wxString
& uti
)
426 // Keys in to Info.plist
427 const static wxCFStringRef
docTypesKey( "CFBundleDocumentTypes" );
428 const static wxCFStringRef
descKey( "CFBundleTypeName" );
429 const static wxCFStringRef
iconKey( "CFBundleTypeIconFile" );
431 // The call for finding the preferred application for a UTI is LSCopyDefaultRoleHandlerForContentType
432 // This returns an empty string on OS X 10.5
433 // Instead it is necessary to get the primary extension and use LSGetApplicationForInfo
434 wxCFStringRef ext
= UTTypeCopyPreferredTagWithClass( wxCFStringRef( uti
), kUTTagClassFilenameExtension
);
436 // Look up the preferred application
438 OSStatus status
= LSGetApplicationForInfo( kLSUnknownType
, kLSUnknownCreator
, ext
, kLSRolesAll
, NULL
, &appUrl
);
440 if( status
!= noErr
)
443 // Create a bundle object for that application
444 wxCFRef
< CFBundleRef
> bundle
;
445 bundle
= wxCFRef
< CFBundleRef
>( CFBundleCreate( kCFAllocatorDefault
, appUrl
) );
450 // Also get the open command while we have the bundle
451 wxCFStringRef
cfsAppPath(CFURLCopyFileSystemPath(appUrl
, kCFURLPOSIXPathStyle
));
452 m_utiMap
[ uti
].application
= cfsAppPath
.AsString();
454 // Get all the document type data in this bundle
455 CFTypeRef docTypeData
;
456 docTypeData
= CFBundleGetValueForInfoDictionaryKey( bundle
, docTypesKey
);
461 // Find the document type entry that matches ext
462 CFDictionaryRef docType
;
463 docType
= GetDocTypeForExt( docTypeData
, ext
);
468 // Get the display name for docType
469 wxCFStringRef description
= reinterpret_cast< CFStringRef
>( CFDictionaryGetValue( docType
, descKey
) );
470 wxCFRetain( description
.get() );
471 m_utiMap
[ uti
].description
= description
.AsString();
473 // Get the icon path for docType
474 CFStringRef iconFile
= reinterpret_cast< CFStringRef
> ( CFDictionaryGetValue( docType
, iconKey
) );
475 m_utiMap
[ uti
].iconLoc
.SetFileName( GetPathForIconFile( bundle
, iconFile
) );
480 /////////////////////////////////////////////////////////////////////////////
481 // The remaining functionality from the public interface of
482 // wxMimeTypesManagerImpl is not implemented.
484 // Please see the note further up this file on Initialise/Clear to explain why
485 // EnumAllFileTypes is not available.
487 // Some thought will be needed before implementing Associate / Unassociate
488 // for OS X to ensure proper integration with the many file type and
489 // association mechanisms already used by the OS. Leaving these methods as
490 // NO-OP on OS X and asking client apps to put suitable entries in their
491 // Info.plist files when building their OS X bundle may well be the
493 /////////////////////////////////////////////////////////////////////////////
496 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString
& WXUNUSED(mimetypes
))
501 wxFileType
*wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo
& WXUNUSED(ftInfo
))
506 bool wxMimeTypesManagerImpl::Unassociate(wxFileType
*WXUNUSED(ft
))
512 /////////////////////////////////////////////////////////////////////////////
515 // These methods are private and should only ever be called by wxFileTypeImpl
516 // after the required information has been loaded. It should not be possible
517 // to get a wxFileTypeImpl for a UTI without information for that UTI being
518 // querried, however it is possible that some information may not have been
520 /////////////////////////////////////////////////////////////////////////////
524 bool wxMimeTypesManagerImpl::GetExtensions(const wxString
& uti
, wxArrayString
& extensions
)
526 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
528 if( itr
== m_utiMap
.end() || itr
->second
.extensions
.GetCount() < 1 )
534 extensions
= itr
->second
.extensions
;
538 bool wxMimeTypesManagerImpl::GetMimeType(const wxString
& uti
, wxString
*mimeType
)
540 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
542 if( itr
== m_utiMap
.end() || itr
->second
.mimeTypes
.GetCount() < 1 )
544 *mimeType
= wxEmptyString
;
548 *mimeType
= itr
->second
.mimeTypes
[ 0 ];
552 bool wxMimeTypesManagerImpl::GetMimeTypes(const wxString
& uti
, wxArrayString
& mimeTypes
)
554 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
556 if( itr
== m_utiMap
.end() || itr
->second
.mimeTypes
.GetCount() < 1 )
562 mimeTypes
= itr
->second
.mimeTypes
;
566 bool wxMimeTypesManagerImpl::GetIcon(const wxString
& uti
, wxIconLocation
*iconLoc
)
568 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
570 if( itr
== m_utiMap
.end() || !itr
->second
.iconLoc
.IsOk() )
572 *iconLoc
= wxIconLocation();
576 *iconLoc
= itr
->second
.iconLoc
;
580 bool wxMimeTypesManagerImpl::GetDescription(const wxString
& uti
, wxString
*desc
)
582 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
584 if( itr
== m_utiMap
.end() || itr
->second
.description
.empty() )
586 *desc
= wxEmptyString
;
590 *desc
= itr
->second
.description
;
594 bool wxMimeTypesManagerImpl::GetApplication(const wxString
& uti
, wxString
*command
)
596 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
598 if( itr
== m_utiMap
.end() )
604 *command
= itr
->second
.application
;
608 /////////////////////////////////////////////////////////////////////////////
609 // The remaining functionality has not yet been implemented for OS X
610 /////////////////////////////////////////////////////////////////////////////
612 wxFileTypeImpl::wxFileTypeImpl()
616 wxFileTypeImpl::~wxFileTypeImpl()
620 // Query wxMimeTypesManagerImple to get real information for a file type
621 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
) const
623 return m_manager
->GetExtensions( m_uti
, extensions
);
626 bool wxFileTypeImpl::GetMimeType(wxString
*mimeType
) const
628 return m_manager
->GetMimeType( m_uti
, mimeType
);
631 bool wxFileTypeImpl::GetMimeTypes(wxArrayString
& mimeTypes
) const
633 return m_manager
->GetMimeTypes( m_uti
, mimeTypes
);
636 bool wxFileTypeImpl::GetIcon(wxIconLocation
*iconLoc
) const
638 return m_manager
->GetIcon( m_uti
, iconLoc
);
641 bool wxFileTypeImpl::GetDescription(wxString
*desc
) const
643 return m_manager
->GetDescription( m_uti
, desc
);
649 // Helper function for GetOpenCommand(): returns the string surrounded by
650 // (singly) quotes if it contains spaces.
651 wxString
QuoteIfNecessary(const wxString
& path
)
653 wxString
result(path
);
655 if ( path
.find(' ') != wxString::npos
)
657 result
.insert(0, "'");
664 } // anonymous namespace
666 bool wxFileTypeImpl::GetOpenCommand(wxString
*openCmd
, const wxFileType::MessageParameters
& params
) const
668 wxString application
;
669 if ( !m_manager
->GetApplication(m_uti
, &application
) )
672 *openCmd
<< QuoteIfNecessary(application
)
673 << ' ' << QuoteIfNecessary(params
.GetFileName());
678 bool wxFileTypeImpl::GetPrintCommand(wxString
*WXUNUSED(printCmd
), const wxFileType::MessageParameters
& WXUNUSED(params
)) const
683 size_t wxFileTypeImpl::GetAllCommands(wxArrayString
*WXUNUSED(verbs
), wxArrayString
*WXUNUSED(commands
), const wxFileType::MessageParameters
& WXUNUSED(params
)) const
688 bool wxFileTypeImpl::SetCommand(const wxString
& WXUNUSED(cmd
), const wxString
& WXUNUSED(verb
), bool WXUNUSED(overwriteprompt
))
693 bool wxFileTypeImpl::SetDefaultIcon(const wxString
& WXUNUSED(strIcon
), int WXUNUSED(index
))
698 bool wxFileTypeImpl::Unassociate(wxFileType
*WXUNUSED(ft
))
704 #endif // wxUSE_MIMETYPE