1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/core/mimetype.cpp
3 // Purpose: Mac OS X implementation for wx MIME-related classes
4 // Author: Neil Perkins
7 // Copyright: (C) 2010 Neil Perkins
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
12 #include "wx/wxprec.h"
24 #include "wx/osx/mimetype.h"
25 #include "wx/osx/private.h"
27 /////////////////////////////////////////////////////////////////////////////
29 /////////////////////////////////////////////////////////////////////////////
32 // Read a string or array of strings from a CFDictionary for a given key
33 // Return an empty list on error
34 wxArrayString
ReadStringListFromCFDict( CFDictionaryRef dictionary
, CFStringRef key
)
36 // Create an empty list
37 wxArrayString results
;
39 // Look up the requested key
40 CFTypeRef valueData
= CFDictionaryGetValue( dictionary
, key
);
45 if( CFGetTypeID( valueData
) == CFArrayGetTypeID() )
47 CFArrayRef valueList
= reinterpret_cast< CFArrayRef
>( valueData
);
52 // Look at each item in the array
53 for( CFIndex i
= 0, n
= CFArrayGetCount( valueList
); i
< n
; i
++ )
55 itemData
= CFArrayGetValueAtIndex( valueList
, i
);
57 // Make sure the item is a string
58 if( CFGetTypeID( itemData
) == CFStringGetTypeID() )
60 // wxCFStringRef will automatically CFRelease, so an extra CFRetain is needed
61 item
= reinterpret_cast< CFStringRef
>( itemData
);
62 wxCFRetain( item
.get() );
64 // Add the string to the list
65 results
.Add( item
.AsString() );
70 // Value is a single string - return a list of one item
71 else if( CFGetTypeID( valueData
) == CFStringGetTypeID() )
73 // wxCFStringRef will automatically CFRelease, so an extra CFRetain is needed
74 wxCFStringRef value
= reinterpret_cast< CFStringRef
>( valueData
);
75 wxCFRetain( value
.get() );
77 // Add the string to the list
78 results
.Add( value
.AsString() );
82 // Return the list. If the dictionary did not contain key,
83 // or contained the wrong data type, the list will be empty
88 // Given a single CFDictionary representing document type data, check whether
89 // it matches a particular file extension. Return true for a match, false otherwise
90 bool CheckDocTypeMatchesExt( CFDictionaryRef docType
, CFStringRef requiredExt
)
92 const static wxCFStringRef
extKey( "CFBundleTypeExtensions" );
94 CFTypeRef extData
= CFDictionaryGetValue( docType
, extKey
);
99 if( CFGetTypeID( extData
) == CFArrayGetTypeID() )
101 CFArrayRef extList
= reinterpret_cast< CFArrayRef
>( extData
);
104 for( CFIndex i
= 0, n
= CFArrayGetCount( extList
); i
< n
; i
++ )
106 extItem
= CFArrayGetValueAtIndex( extList
, i
);
108 if( CFGetTypeID( extItem
) == CFStringGetTypeID() )
110 CFStringRef ext
= reinterpret_cast< CFStringRef
>( extItem
);
112 if( CFStringCompare( ext
, requiredExt
, kCFCompareCaseInsensitive
) == kCFCompareEqualTo
)
118 if( CFGetTypeID( extData
) == CFStringGetTypeID() )
120 CFStringRef ext
= reinterpret_cast< CFStringRef
>( extData
);
122 if( CFStringCompare( ext
, requiredExt
, kCFCompareCaseInsensitive
) == kCFCompareEqualTo
)
130 // Given a data structure representing document type data, or a list of such
131 // structures, find the one which matches a particular file extension
132 // The result will be a CFDictionary containining document type data
133 // if a match is found, or null otherwise
134 CFDictionaryRef
GetDocTypeForExt( CFTypeRef docTypeData
, CFStringRef requiredExt
)
136 CFDictionaryRef docType
;
143 if( CFGetTypeID( docTypeData
) == CFArrayGetTypeID() )
145 docTypes
= reinterpret_cast< CFArrayRef
>( docTypeData
);
147 for( CFIndex i
= 0, n
= CFArrayGetCount( docTypes
); i
< n
; i
++ )
149 item
= CFArrayGetValueAtIndex( docTypes
, i
);
151 if( CFGetTypeID( item
) == CFDictionaryGetTypeID() )
153 docType
= reinterpret_cast< CFDictionaryRef
>( item
);
155 if( CheckDocTypeMatchesExt( docType
, requiredExt
) )
161 if( CFGetTypeID( docTypeData
) == CFDictionaryGetTypeID() )
163 CFDictionaryRef docType
= reinterpret_cast< CFDictionaryRef
>( docTypeData
);
165 if( CheckDocTypeMatchesExt( docType
, requiredExt
) )
173 // Given an application bundle reference and the name of an icon file
174 // which is a resource in that bundle, look up the full (posix style)
175 // path to that icon. Returns the path, or an empty wxString on failure
176 wxString
GetPathForIconFile( CFBundleRef bundle
, CFStringRef iconFile
)
178 // If either parameter is NULL there is no hope of success
179 if( !bundle
|| !iconFile
)
180 return wxEmptyString
;
182 // Create a range object representing the whole string
184 wholeString
.location
= 0;
185 wholeString
.length
= CFStringGetLength( iconFile
);
187 // Index of the period in the file name for iconFile
188 UniCharCount periodIndex
;
190 // In order to locate the period delimiting the extension,
191 // iconFile must be represented as UniChar[]
193 // Allocate a buffer and copy in the iconFile string
194 UniChar
* buffer
= new UniChar
[ wholeString
.length
];
195 CFStringGetCharacters( iconFile
, wholeString
, buffer
);
197 // Locate the period character
198 OSStatus status
= LSGetExtensionInfo( wholeString
.length
, buffer
, &periodIndex
);
200 // Deallocate the buffer
203 // If the period could not be located it will not be possible to get the URL
204 if( status
!= noErr
|| periodIndex
== kLSInvalidExtensionIndex
)
205 return wxEmptyString
;
208 // Range representing the name part of iconFile
209 CFRange iconNameRange
;
210 iconNameRange
.location
= 0;
211 iconNameRange
.length
= periodIndex
- 1;
213 // Range representing the extension part of iconFile
214 CFRange iconExtRange
;
215 iconExtRange
.location
= periodIndex
;
216 iconExtRange
.length
= wholeString
.length
- periodIndex
;
218 // Get the name and extension strings
219 wxCFStringRef iconName
= CFStringCreateWithSubstring( kCFAllocatorDefault
, iconFile
, iconNameRange
);
220 wxCFStringRef iconExt
= CFStringCreateWithSubstring( kCFAllocatorDefault
, iconFile
, iconExtRange
);
222 // Now it is possible to query the URL for the icon as a resource
223 wxCFRef
< CFURLRef
> iconUrl
= wxCFRef
< CFURLRef
>( CFBundleCopyResourceURL( bundle
, iconName
, iconExt
, NULL
) );
226 return wxEmptyString
;
228 // All being well, return the icon path
229 return wxCFStringRef( CFURLCopyFileSystemPath( iconUrl
, kCFURLPOSIXPathStyle
) ).AsString();
233 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
237 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
242 /////////////////////////////////////////////////////////////////////////////
243 // Init / shutdown functions
245 // The Launch Services / UTI API provides no helpful way of getting a list
246 // of all registered types. Instead the API is focused around looking up
247 // information for a particular file type once you already have some
248 // identifying piece of information. In order to get a list of registered
249 // types it would first be necessary to get a list of all bundles exporting
250 // type information (all application bundles may be sufficient) then look at
251 // the Info.plist file for those bundles and store the type information. As
252 // this would require trawling the hard disk when a wxWidgets program starts
253 // up it was decided instead to load the information lazily.
255 // If this behaviour really messes up your app, please feel free to implement
256 // the trawling approach (perhaps with a configure switch?). A good place to
257 // start would be CFBundleCreateBundlesFromDirectory( NULL, "/Applications", "app" )
258 /////////////////////////////////////////////////////////////////////////////
261 void wxMimeTypesManagerImpl::Initialize(int WXUNUSED(mailcapStyles
), const wxString
& WXUNUSED(extraDir
))
266 void wxMimeTypesManagerImpl::ClearData()
272 /////////////////////////////////////////////////////////////////////////////
275 // Apple uses a number of different systems for file type information.
276 // As of Spring 2010, these include:
278 // OS Types / OS Creators
281 // Uniform Type Identifiers (UTI)
283 // This implementation of the type manager for Mac supports all except OS
284 // Type / OS Creator codes, which have been deprecated for some time with
285 // less and less support in recent versions of OS X.
287 // The UTI system is the internal system used by OS X, as such it offers a
288 // one-to-one mapping with file types understood by Mac OS X and is the
289 // easiest way to convert between type systems. However, UTI meta-data is
290 // not stored with data files (as of OS X 10.6), instead the OS looks at
291 // the file extension and uses this to determine the UTI. Simillarly, most
292 // applications do not yet advertise the file types they can handle by UTI.
294 // The result is that no one typing system is suitable for all tasks. Further,
295 // as there is not a one-to-one mapping between type systems for the
296 // description of any given type, it follows that ambiguity cannot be precluded,
297 // whichever system is taken to be the "master".
299 // In the implementation below I have used UTI as the master key for looking
300 // up file types. Extensions and mime types are mapped to UTIs and the data
301 // for each UTI contains a list of all associated extensions and mime types.
302 // This has the advantage that unknown types will still be assigned a unique
303 // ID, while using any other system as the master could result in conflicts
304 // if there were no mime type assigned to an extension or vice versa. However
305 // there is still plenty of room for ambiguity if two or more applications
306 // are fighting over ownership of a particular type or group of types.
308 // If this proves to be serious issue it may be helpful to add some slightly
309 // more cleve logic to the code so that the key used to look up a file type is
310 // always first in the list in the resulting wxFileType object. I.e, if you
311 // look up .mpeg3 the list you get back could be .mpeg3, mp3, .mpg3, while
312 // looking up .mp3 would give .mp3, .mpg3, .mpeg3. The simplest way to do
313 // this would probably to keep two separate sets of data, one for lookup
314 // by extetnsion and one for lookup by mime type.
316 // One other point which may require consideration is handling of unrecognised
317 // types. Using UTI these will be assigned a unique ID of dyn.xxx. This will
318 // result in a wxFileType object being returned, although querying properties
319 // on that object will fail. If it would be more helpful to return NULL in this
320 // case a suitable check can be added.
321 /////////////////////////////////////////////////////////////////////////////
323 // Look up a file type by extension
324 // The extensions if mapped to a UTI
325 // If the requested extension is not know the OS is querried and the results saved
326 wxFileType
*wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& ext
)
330 const TagMap::const_iterator extItr
= m_extMap
.find( ext
);
332 if( extItr
== m_extMap
.end() )
334 wxCFStringRef utiRef
= UTTypeCreatePreferredIdentifierForTag( kUTTagClassFilenameExtension
, wxCFStringRef( ext
), NULL
);
335 m_extMap
[ ext
] = uti
= utiRef
.AsString();
338 uti
= extItr
->second
;
340 return GetFileTypeFromUti( uti
);
343 // Look up a file type by mime type
344 // The mime type is mapped to a UTI
345 // If the requested extension is not know the OS is querried and the results saved
346 wxFileType
*wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
350 const TagMap::const_iterator mimeItr
= m_mimeMap
.find( mimeType
);
352 if( mimeItr
== m_mimeMap
.end() )
354 wxCFStringRef utiRef
= UTTypeCreatePreferredIdentifierForTag( kUTTagClassFilenameExtension
, wxCFStringRef( mimeType
), NULL
);
355 m_mimeMap
[ mimeType
] = uti
= utiRef
.AsString();
358 uti
= mimeItr
->second
;
360 return GetFileTypeFromUti( uti
);
363 // Look up a file type by UTI
364 // If the requested extension is not know the OS is querried and the results saved
365 wxFileType
*wxMimeTypesManagerImpl::GetFileTypeFromUti(const wxString
& uti
)
367 UtiMap::const_iterator utiItr
= m_utiMap
.find( uti
);
369 if( utiItr
== m_utiMap
.end() )
371 LoadTypeDataForUti( uti
);
372 LoadDisplayDataForUti( uti
);
375 wxFileType
* const ft
= new wxFileType
;
376 ft
->m_impl
->m_uti
= uti
;
377 ft
->m_impl
->m_manager
= this;
383 /////////////////////////////////////////////////////////////////////////////
386 // These functions query the OS for information on a particular file type
387 /////////////////////////////////////////////////////////////////////////////
390 // Look up all extensions and mime types associated with a UTI
391 void wxMimeTypesManagerImpl::LoadTypeDataForUti(const wxString
& uti
)
393 // Keys in to the UTI declaration plist
394 const static wxCFStringRef
tagsKey( "UTTypeTagSpecification" );
395 const static wxCFStringRef
extKey( "public.filename-extension" );
396 const static wxCFStringRef
mimeKey( "public.mime-type" );
398 // Get the UTI as a CFString
399 wxCFStringRef
utiRef( uti
);
401 // Get a copy of the UTI declaration
402 wxCFRef
< CFDictionaryRef
> utiDecl
;
403 utiDecl
= wxCFRef
< CFDictionaryRef
>( UTTypeCopyDeclaration( utiRef
) );
408 // Get the tags spec (the section of a UTI declaration containing mappings to other type systems)
409 CFTypeRef tagsData
= CFDictionaryGetValue( utiDecl
, tagsKey
);
411 if( CFGetTypeID( tagsData
) != CFDictionaryGetTypeID() )
414 CFDictionaryRef tags
= reinterpret_cast< CFDictionaryRef
>( tagsData
);
416 // Read tags for extensions and mime types
417 m_utiMap
[ uti
].extensions
= ReadStringListFromCFDict( tags
, extKey
);
418 m_utiMap
[ uti
].mimeTypes
= ReadStringListFromCFDict( tags
, mimeKey
);
422 // Look up the (locale) display name and icon file associated with a UTI
423 void wxMimeTypesManagerImpl::LoadDisplayDataForUti(const wxString
& uti
)
425 // Keys in to Info.plist
426 const static wxCFStringRef
docTypesKey( "CFBundleDocumentTypes" );
427 const static wxCFStringRef
descKey( "CFBundleTypeName" );
428 const static wxCFStringRef
iconKey( "CFBundleTypeIconFile" );
430 // The call for finding the preferred application for a UTI is LSCopyDefaultRoleHandlerForContentType
431 // This returns an empty string on OS X 10.5
432 // Instead it is necessary to get the primary extension and use LSGetApplicationForInfo
433 wxCFStringRef ext
= UTTypeCopyPreferredTagWithClass( wxCFStringRef( uti
), kUTTagClassFilenameExtension
);
435 // Look up the preferred application
437 OSStatus status
= LSGetApplicationForInfo( kLSUnknownType
, kLSUnknownCreator
, ext
, kLSRolesAll
, NULL
, &appUrl
);
439 if( status
!= noErr
)
442 // Create a bundle object for that application
443 wxCFRef
< CFBundleRef
> bundle
;
444 bundle
= wxCFRef
< CFBundleRef
>( CFBundleCreate( kCFAllocatorDefault
, appUrl
) );
449 // Also get the open command while we have the bundle
450 wxCFStringRef
cfsAppPath(CFURLCopyFileSystemPath(appUrl
, kCFURLPOSIXPathStyle
));
451 m_utiMap
[ uti
].application
= cfsAppPath
.AsString();
453 // Get all the document type data in this bundle
454 CFTypeRef docTypeData
;
455 docTypeData
= CFBundleGetValueForInfoDictionaryKey( bundle
, docTypesKey
);
460 // Find the document type entry that matches ext
461 CFDictionaryRef docType
;
462 docType
= GetDocTypeForExt( docTypeData
, ext
);
467 // Get the display name for docType
468 wxCFStringRef description
= reinterpret_cast< CFStringRef
>( CFDictionaryGetValue( docType
, descKey
) );
469 wxCFRetain( description
.get() );
470 m_utiMap
[ uti
].description
= description
.AsString();
472 // Get the icon path for docType
473 CFStringRef iconFile
= reinterpret_cast< CFStringRef
> ( CFDictionaryGetValue( docType
, iconKey
) );
474 m_utiMap
[ uti
].iconLoc
.SetFileName( GetPathForIconFile( bundle
, iconFile
) );
479 /////////////////////////////////////////////////////////////////////////////
480 // The remaining functionality from the public interface of
481 // wxMimeTypesManagerImpl is not implemented.
483 // Please see the note further up this file on Initialise/Clear to explain why
484 // EnumAllFileTypes is not available.
486 // Some thought will be needed before implementing Associate / Unassociate
487 // for OS X to ensure proper integration with the many file type and
488 // association mechanisms already used by the OS. Leaving these methods as
489 // NO-OP on OS X and asking client apps to put suitable entries in their
490 // Info.plist files when building their OS X bundle may well be the
492 /////////////////////////////////////////////////////////////////////////////
495 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString
& WXUNUSED(mimetypes
))
500 wxFileType
*wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo
& WXUNUSED(ftInfo
))
505 bool wxMimeTypesManagerImpl::Unassociate(wxFileType
*WXUNUSED(ft
))
511 /////////////////////////////////////////////////////////////////////////////
514 // These methods are private and should only ever be called by wxFileTypeImpl
515 // after the required information has been loaded. It should not be possible
516 // to get a wxFileTypeImpl for a UTI without information for that UTI being
517 // querried, however it is possible that some information may not have been
519 /////////////////////////////////////////////////////////////////////////////
523 bool wxMimeTypesManagerImpl::GetExtensions(const wxString
& uti
, wxArrayString
& extensions
)
525 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
527 if( itr
== m_utiMap
.end() || itr
->second
.extensions
.GetCount() < 1 )
533 extensions
= itr
->second
.extensions
;
537 bool wxMimeTypesManagerImpl::GetMimeType(const wxString
& uti
, wxString
*mimeType
)
539 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
541 if( itr
== m_utiMap
.end() || itr
->second
.mimeTypes
.GetCount() < 1 )
543 *mimeType
= wxEmptyString
;
547 *mimeType
= itr
->second
.mimeTypes
[ 0 ];
551 bool wxMimeTypesManagerImpl::GetMimeTypes(const wxString
& uti
, wxArrayString
& mimeTypes
)
553 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
555 if( itr
== m_utiMap
.end() || itr
->second
.mimeTypes
.GetCount() < 1 )
561 mimeTypes
= itr
->second
.mimeTypes
;
565 bool wxMimeTypesManagerImpl::GetIcon(const wxString
& uti
, wxIconLocation
*iconLoc
)
567 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
569 if( itr
== m_utiMap
.end() || !itr
->second
.iconLoc
.IsOk() )
571 *iconLoc
= wxIconLocation();
575 *iconLoc
= itr
->second
.iconLoc
;
579 bool wxMimeTypesManagerImpl::GetDescription(const wxString
& uti
, wxString
*desc
)
581 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
583 if( itr
== m_utiMap
.end() || itr
->second
.description
.empty() )
585 *desc
= wxEmptyString
;
589 *desc
= itr
->second
.description
;
593 bool wxMimeTypesManagerImpl::GetApplication(const wxString
& uti
, wxString
*command
)
595 const UtiMap::const_iterator itr
= m_utiMap
.find( uti
);
597 if( itr
== m_utiMap
.end() )
603 *command
= itr
->second
.application
;
607 /////////////////////////////////////////////////////////////////////////////
608 // The remaining functionality has not yet been implemented for OS X
609 /////////////////////////////////////////////////////////////////////////////
611 wxFileTypeImpl::wxFileTypeImpl()
615 wxFileTypeImpl::~wxFileTypeImpl()
619 // Query wxMimeTypesManagerImple to get real information for a file type
620 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
) const
622 return m_manager
->GetExtensions( m_uti
, extensions
);
625 bool wxFileTypeImpl::GetMimeType(wxString
*mimeType
) const
627 return m_manager
->GetMimeType( m_uti
, mimeType
);
630 bool wxFileTypeImpl::GetMimeTypes(wxArrayString
& mimeTypes
) const
632 return m_manager
->GetMimeTypes( m_uti
, mimeTypes
);
635 bool wxFileTypeImpl::GetIcon(wxIconLocation
*iconLoc
) const
637 return m_manager
->GetIcon( m_uti
, iconLoc
);
640 bool wxFileTypeImpl::GetDescription(wxString
*desc
) const
642 return m_manager
->GetDescription( m_uti
, desc
);
648 // Helper function for GetOpenCommand(): returns the string surrounded by
649 // (singly) quotes if it contains spaces.
650 wxString
QuoteIfNecessary(const wxString
& path
)
652 wxString
result(path
);
654 if ( path
.find(' ') != wxString::npos
)
656 result
.insert(0, "'");
663 } // anonymous namespace
665 bool wxFileTypeImpl::GetOpenCommand(wxString
*openCmd
, const wxFileType::MessageParameters
& params
) const
667 wxString application
;
668 if ( !m_manager
->GetApplication(m_uti
, &application
) )
671 *openCmd
<< QuoteIfNecessary(application
)
672 << ' ' << QuoteIfNecessary(params
.GetFileName());
677 bool wxFileTypeImpl::GetPrintCommand(wxString
*WXUNUSED(printCmd
), const wxFileType::MessageParameters
& WXUNUSED(params
)) const
682 size_t wxFileTypeImpl::GetAllCommands(wxArrayString
*WXUNUSED(verbs
), wxArrayString
*WXUNUSED(commands
), const wxFileType::MessageParameters
& WXUNUSED(params
)) const
687 bool wxFileTypeImpl::SetCommand(const wxString
& WXUNUSED(cmd
), const wxString
& WXUNUSED(verb
), bool WXUNUSED(overwriteprompt
))
692 bool wxFileTypeImpl::SetDefaultIcon(const wxString
& WXUNUSED(strIcon
), int WXUNUSED(index
))
697 bool wxFileTypeImpl::Unassociate(wxFileType
*WXUNUSED(ft
))
703 #endif // wxUSE_MIMETYPE