]> git.saurik.com Git - wxWidgets.git/blob - src/osx/core/mimetype.cpp
c2661e22b0bca7fb4024b44680bce9172e08bf95
[wxWidgets.git] / src / osx / core / mimetype.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/core/mimetype.cpp
3 // Purpose: Mac OS X implementation for wx MIME-related classes
4 // Author: Neil Perkins
5 // Modified by:
6 // Created: 2010-05-15
7 // RCS-ID: $Id$
8 // Copyright: (C) 2010 Neil Perkins
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12
13 #include "wx/wxprec.h"
14
15 #ifdef __BORLANDC__
16 #pragma hdrstop
17 #endif
18
19 #ifndef WX_PRECOMP
20 #include "wx/defs.h"
21 #endif
22
23 #if wxUSE_MIMETYPE
24
25 #include "wx/osx/mimetype.h"
26 #include "wx/osx/private.h"
27
28 /////////////////////////////////////////////////////////////////////////////
29 // Helper functions
30 /////////////////////////////////////////////////////////////////////////////
31
32
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 )
36 {
37 // Create an empty list
38 wxArrayString results;
39
40 // Look up the requested key
41 CFTypeRef valueData = CFDictionaryGetValue( dictionary, key );
42
43 if( valueData )
44 {
45 // Value is an array
46 if( CFGetTypeID( valueData ) == CFArrayGetTypeID() )
47 {
48 CFArrayRef valueList = reinterpret_cast< CFArrayRef >( valueData );
49
50 CFTypeRef itemData;
51 wxCFStringRef item;
52
53 // Look at each item in the array
54 for( CFIndex i = 0, n = CFArrayGetCount( valueList ); i < n; i++ )
55 {
56 itemData = CFArrayGetValueAtIndex( valueList, i );
57
58 // Make sure the item is a string
59 if( CFGetTypeID( itemData ) == CFStringGetTypeID() )
60 {
61 // wxCFStringRef will automatically CFRelease, so an extra CFRetain is needed
62 item = reinterpret_cast< CFStringRef >( itemData );
63 wxCFRetain( item.get() );
64
65 // Add the string to the list
66 results.Add( item.AsString() );
67 }
68 }
69 }
70
71 // Value is a single string - return a list of one item
72 else if( CFGetTypeID( valueData ) == CFStringGetTypeID() )
73 {
74 // wxCFStringRef will automatically CFRelease, so an extra CFRetain is needed
75 wxCFStringRef value = reinterpret_cast< CFStringRef >( valueData );
76 wxCFRetain( value.get() );
77
78 // Add the string to the list
79 results.Add( value.AsString() );
80 }
81 }
82
83 // Return the list. If the dictionary did not contain key,
84 // or contained the wrong data type, the list will be empty
85 return results;
86 }
87
88
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 )
92 {
93 const static wxCFStringRef extKey( "CFBundleTypeExtensions" );
94
95 CFTypeRef extData = CFDictionaryGetValue( docType, extKey );
96
97 if( !extData )
98 return false;
99
100 if( CFGetTypeID( extData ) == CFArrayGetTypeID() )
101 {
102 CFArrayRef extList = reinterpret_cast< CFArrayRef >( extData );
103 CFTypeRef extItem;
104
105 for( CFIndex i = 0, n = CFArrayGetCount( extList ); i < n; i++ )
106 {
107 extItem = CFArrayGetValueAtIndex( extList, i );
108
109 if( CFGetTypeID( extItem ) == CFStringGetTypeID() )
110 {
111 CFStringRef ext = reinterpret_cast< CFStringRef >( extItem );
112
113 if( CFStringCompare( ext, requiredExt, kCFCompareCaseInsensitive ) == kCFCompareEqualTo )
114 return true;
115 }
116 }
117 }
118
119 if( CFGetTypeID( extData ) == CFStringGetTypeID() )
120 {
121 CFStringRef ext = reinterpret_cast< CFStringRef >( extData );
122
123 if( CFStringCompare( ext, requiredExt, kCFCompareCaseInsensitive ) == kCFCompareEqualTo )
124 return true;
125 }
126
127 return false;
128 }
129
130
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 )
136 {
137 CFDictionaryRef docType;
138 CFArrayRef docTypes;
139 CFTypeRef item;
140
141 if( !docTypeData )
142 return NULL;
143
144 if( CFGetTypeID( docTypeData ) == CFArrayGetTypeID() )
145 {
146 docTypes = reinterpret_cast< CFArrayRef >( docTypeData );
147
148 for( CFIndex i = 0, n = CFArrayGetCount( docTypes ); i < n; i++ )
149 {
150 item = CFArrayGetValueAtIndex( docTypes, i );
151
152 if( CFGetTypeID( item ) == CFDictionaryGetTypeID() )
153 {
154 docType = reinterpret_cast< CFDictionaryRef >( item );
155
156 if( CheckDocTypeMatchesExt( docType, requiredExt ) )
157 return docType;
158 }
159 }
160 }
161
162 if( CFGetTypeID( docTypeData ) == CFDictionaryGetTypeID() )
163 {
164 CFDictionaryRef docType = reinterpret_cast< CFDictionaryRef >( docTypeData );
165
166 if( CheckDocTypeMatchesExt( docType, requiredExt ) )
167 return docType;
168 }
169
170 return NULL;
171 }
172
173
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 )
178 {
179 // If either parameter is NULL there is no hope of success
180 if( !bundle || !iconFile )
181 return wxEmptyString;
182
183 // Create a range object representing the whole string
184 CFRange wholeString;
185 wholeString.location = 0;
186 wholeString.length = CFStringGetLength( iconFile );
187
188 // Index of the period in the file name for iconFile
189 UniCharCount periodIndex;
190
191 // In order to locate the period delimiting the extension,
192 // iconFile must be represented as UniChar[]
193 {
194 // Allocate a buffer and copy in the iconFile string
195 UniChar* buffer = new UniChar[ wholeString.length ];
196 CFStringGetCharacters( iconFile, wholeString, buffer );
197
198 // Locate the period character
199 OSStatus status = LSGetExtensionInfo( wholeString.length, buffer, &periodIndex );
200
201 // Deallocate the buffer
202 delete [] buffer;
203
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;
207 }
208
209 // Range representing the name part of iconFile
210 CFRange iconNameRange;
211 iconNameRange.location = 0;
212 iconNameRange.length = periodIndex - 1;
213
214 // Range representing the extension part of iconFile
215 CFRange iconExtRange;
216 iconExtRange.location = periodIndex;
217 iconExtRange.length = wholeString.length - periodIndex;
218
219 // Get the name and extension strings
220 wxCFStringRef iconName = CFStringCreateWithSubstring( kCFAllocatorDefault, iconFile, iconNameRange );
221 wxCFStringRef iconExt = CFStringCreateWithSubstring( kCFAllocatorDefault, iconFile, iconExtRange );
222
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 ) );
225
226 if( !iconUrl.get() )
227 return wxEmptyString;
228
229 // All being well, return the icon path
230 return wxCFStringRef( CFURLCopyFileSystemPath( iconUrl, kCFURLPOSIXPathStyle ) ).AsString();
231 }
232
233
234 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
235 {
236 }
237
238 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
239 {
240 }
241
242
243 /////////////////////////////////////////////////////////////////////////////
244 // Init / shutdown functions
245 //
246 // The Launch Services / UTI API provides no helpful way of getting a list
247 // of all registered types. Instead the API is focused arround 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.
255 //
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 /////////////////////////////////////////////////////////////////////////////
260
261
262 void wxMimeTypesManagerImpl::Initialize(int WXUNUSED(mailcapStyles), const wxString& WXUNUSED(extraDir))
263 {
264 // NO-OP
265 }
266
267 void wxMimeTypesManagerImpl::ClearData()
268 {
269 // NO-OP
270 }
271
272
273 /////////////////////////////////////////////////////////////////////////////
274 // Lookup functions
275 //
276 // Apple uses a number of different systems for file type information.
277 // As of Spring 2010, these include:
278 //
279 // OS Types / OS Creators
280 // File Extensions
281 // Mime Types
282 // Uniform Type Identifiers (UTI)
283 //
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.
287 //
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.
294 //
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".
299 //
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.
308 //
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.
316 //
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 /////////////////////////////////////////////////////////////////////////////
323
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)
328 {
329 wxString uti;
330
331 const TagMap::const_iterator extItr = m_extMap.find( ext );
332
333 if( extItr == m_extMap.end() )
334 {
335 wxCFStringRef utiRef = UTTypeCreatePreferredIdentifierForTag( kUTTagClassFilenameExtension, wxCFStringRef( ext ), NULL );
336 m_extMap[ ext ] = uti = utiRef.AsString();
337 }
338 else
339 uti = extItr->second;
340
341 return GetFileTypeFromUti( uti );
342 }
343
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)
348 {
349 wxString uti;
350
351 const TagMap::const_iterator mimeItr = m_mimeMap.find( mimeType );
352
353 if( mimeItr == m_mimeMap.end() )
354 {
355 wxCFStringRef utiRef = UTTypeCreatePreferredIdentifierForTag( kUTTagClassFilenameExtension, wxCFStringRef( mimeType ), NULL );
356 m_mimeMap[ mimeType ] = uti = utiRef.AsString();
357 }
358 else
359 uti = mimeItr->second;
360
361 return GetFileTypeFromUti( uti );
362 }
363
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)
367 {
368 UtiMap::const_iterator utiItr = m_utiMap.find( uti );
369
370 if( utiItr == m_utiMap.end() )
371 {
372 LoadTypeDataForUti( uti );
373 LoadDisplayDataForUti( uti );
374 }
375
376 wxFileType* const ft = new wxFileType;
377 ft->m_impl->m_uti = uti;
378 ft->m_impl->m_manager = this;
379
380 return ft;
381 }
382
383
384 /////////////////////////////////////////////////////////////////////////////
385 // Load functions
386 //
387 // These functions query the OS for information on a particular file type
388 /////////////////////////////////////////////////////////////////////////////
389
390
391 // Look up all extensions and mime types associated with a UTI
392 void wxMimeTypesManagerImpl::LoadTypeDataForUti(const wxString& uti)
393 {
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" );
398
399 // Get the UTI as a CFString
400 wxCFStringRef utiRef( uti );
401
402 // Get a copy of the UTI declaration
403 wxCFRef< CFDictionaryRef > utiDecl;
404 utiDecl = wxCFRef< CFDictionaryRef >( UTTypeCopyDeclaration( utiRef ) );
405
406 if( !utiDecl )
407 return;
408
409 // Get the tags spec (the section of a UTI declaration containing mappings to other type systems)
410 CFTypeRef tagsData = CFDictionaryGetValue( utiDecl, tagsKey );
411
412 if( CFGetTypeID( tagsData ) != CFDictionaryGetTypeID() )
413 return;
414
415 CFDictionaryRef tags = reinterpret_cast< CFDictionaryRef >( tagsData );
416
417 // Read tags for extensions and mime types
418 m_utiMap[ uti ].extensions = ReadStringListFromCFDict( tags, extKey );
419 m_utiMap[ uti ].mimeTypes = ReadStringListFromCFDict( tags, mimeKey );
420 }
421
422
423 // Look up the (locale) display name and icon file associated with a UTI
424 void wxMimeTypesManagerImpl::LoadDisplayDataForUti(const wxString& uti)
425 {
426 // Keys in to Info.plist
427 const static wxCFStringRef docTypesKey( "CFBundleDocumentTypes" );
428 const static wxCFStringRef descKey( "CFBundleTypeName" );
429 const static wxCFStringRef iconKey( "CFBundleTypeIconFile" );
430
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 );
435
436 // Look up the preferred application
437 CFURLRef appUrl;
438 OSStatus status = LSGetApplicationForInfo( kLSUnknownType, kLSUnknownCreator, ext, kLSRolesAll, NULL, &appUrl );
439
440 if( status != noErr )
441 return;
442
443 // Create a bundle object for that application
444 wxCFRef< CFBundleRef > bundle;
445 bundle = wxCFRef< CFBundleRef >( CFBundleCreate( kCFAllocatorDefault, appUrl ) );
446
447 if( !bundle )
448 return;
449
450 // Get a all the document type data in this bundle
451 CFTypeRef docTypeData;
452 docTypeData = CFBundleGetValueForInfoDictionaryKey( bundle, docTypesKey );
453
454 if( !docTypeData )
455 return;
456
457 // Find the document type entry that matches ext
458 CFDictionaryRef docType;
459 docType = GetDocTypeForExt( docTypeData, ext );
460
461 if( !docType )
462 return;
463
464 // Get the display name for docType
465 wxCFStringRef description = reinterpret_cast< CFStringRef >( CFDictionaryGetValue( docType, descKey ) );
466 wxCFRetain( description.get() );
467 m_utiMap[ uti ].description = description.AsString();
468
469 // Get the icon path for docType
470 CFStringRef iconFile = reinterpret_cast< CFStringRef > ( CFDictionaryGetValue( docType, iconKey ) );
471 m_utiMap[ uti ].iconLoc.SetFileName( GetPathForIconFile( bundle, iconFile ) );
472 }
473
474
475
476 /////////////////////////////////////////////////////////////////////////////
477 // The remaining functionality from the public interface of
478 // wxMimeTypesManagerImpl is not implemented.
479 //
480 // Please see the note further up this file on Initialise/Clear to explain why
481 // EnumAllFileTypes is not available.
482 //
483 // Some thought will be needed before implementing Associate / Unassociate
484 // for OS X to ensure proper integration with the many file type and
485 // association mechanisms already used by the OS. Leaving these methods as
486 // NO-OP on OS X and asking client apps to put suitable entries in their
487 // Info.plist files when building their OS X bundle may well be the
488 // correct solution.
489 /////////////////////////////////////////////////////////////////////////////
490
491
492 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& WXUNUSED(mimetypes))
493 {
494 return 0;
495 }
496
497 wxFileType *wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& WXUNUSED(ftInfo))
498 {
499 return 0;
500 }
501
502 bool wxMimeTypesManagerImpl::Unassociate(wxFileType *WXUNUSED(ft))
503 {
504 return false;
505 }
506
507
508 /////////////////////////////////////////////////////////////////////////////
509 // Getter methods
510 //
511 // These methods are private and should only ever be called by wxFileTypeImpl
512 // after the required information has been loaded. It should not be possible
513 // to get a wxFileTypeImpl for a UTI without information for that UTI being
514 // querried, however it is possible that some information may not have been
515 // found.
516 /////////////////////////////////////////////////////////////////////////////
517
518
519
520 bool wxMimeTypesManagerImpl::GetExtensions(const wxString& uti, wxArrayString& extensions)
521 {
522 const UtiMap::const_iterator itr = m_utiMap.find( uti );
523
524 if( itr == m_utiMap.end() || itr->second.extensions.GetCount() < 1 )
525 {
526 extensions.Clear();
527 return false;
528 }
529
530 extensions = itr->second.extensions;
531 return true;
532 }
533
534 bool wxMimeTypesManagerImpl::GetMimeType(const wxString& uti, wxString *mimeType)
535 {
536 const UtiMap::const_iterator itr = m_utiMap.find( uti );
537
538 if( itr == m_utiMap.end() || itr->second.mimeTypes.GetCount() < 1 )
539 {
540 *mimeType = wxEmptyString;
541 return false;
542 }
543
544 *mimeType = itr->second.mimeTypes[ 0 ];
545 return true;
546 }
547
548 bool wxMimeTypesManagerImpl::GetMimeTypes(const wxString& uti, wxArrayString& mimeTypes)
549 {
550 const UtiMap::const_iterator itr = m_utiMap.find( uti );
551
552 if( itr == m_utiMap.end() || itr->second.mimeTypes.GetCount() < 1 )
553 {
554 mimeTypes.Clear();
555 return false;
556 }
557
558 mimeTypes = itr->second.mimeTypes;
559 return true;
560 }
561
562 bool wxMimeTypesManagerImpl::GetIcon(const wxString& uti, wxIconLocation *iconLoc)
563 {
564 const UtiMap::const_iterator itr = m_utiMap.find( uti );
565
566 if( itr == m_utiMap.end() || !itr->second.iconLoc.IsOk() )
567 {
568 *iconLoc = wxIconLocation();
569 return false;
570 }
571
572 *iconLoc = itr->second.iconLoc;
573 return true;
574 }
575
576 bool wxMimeTypesManagerImpl::GetDescription(const wxString& uti, wxString *desc)
577 {
578 const UtiMap::const_iterator itr = m_utiMap.find( uti );
579
580 if( itr == m_utiMap.end() || itr->second.description.empty() )
581 {
582 *desc = wxEmptyString;
583 return false;
584 }
585
586 *desc = itr->second.description;
587 return true;
588 }
589
590
591 /////////////////////////////////////////////////////////////////////////////
592 // The remaining functionality has not yet been implemented for OS X
593 /////////////////////////////////////////////////////////////////////////////
594
595 wxFileTypeImpl::wxFileTypeImpl()
596 {
597 }
598
599 wxFileTypeImpl::~wxFileTypeImpl()
600 {
601 }
602
603 // Query wxMimeTypesManagerImple to get real information for a file type
604 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions) const
605 {
606 return m_manager->GetExtensions( m_uti, extensions );
607 }
608
609 bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const
610 {
611 return m_manager->GetMimeType( m_uti, mimeType );
612 }
613
614 bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
615 {
616 return m_manager->GetMimeTypes( m_uti, mimeTypes );
617 }
618
619 bool wxFileTypeImpl::GetIcon(wxIconLocation *iconLoc) const
620 {
621 return m_manager->GetIcon( m_uti, iconLoc );
622 }
623
624 bool wxFileTypeImpl::GetDescription(wxString *desc) const
625 {
626 return m_manager->GetDescription( m_uti, desc );
627 }
628
629 bool wxFileTypeImpl::GetOpenCommand(wxString *WXUNUSED(openCmd), const wxFileType::MessageParameters& WXUNUSED(params)) const
630 {
631 return false;
632 }
633
634 bool wxFileTypeImpl::GetPrintCommand(wxString *WXUNUSED(printCmd), const wxFileType::MessageParameters& WXUNUSED(params)) const
635 {
636 return false;
637 }
638
639 size_t wxFileTypeImpl::GetAllCommands(wxArrayString *WXUNUSED(verbs), wxArrayString *WXUNUSED(commands), const wxFileType::MessageParameters& WXUNUSED(params)) const
640 {
641 return false;
642 }
643
644 bool wxFileTypeImpl::SetCommand(const wxString& WXUNUSED(cmd), const wxString& WXUNUSED(verb), bool WXUNUSED(overwriteprompt))
645 {
646 return false;
647 }
648
649 bool wxFileTypeImpl::SetDefaultIcon(const wxString& WXUNUSED(strIcon), int WXUNUSED(index))
650 {
651 return false;
652 }
653
654 bool wxFileTypeImpl::Unassociate(wxFileType *WXUNUSED(ft))
655 {
656 return false;
657 }
658
659
660 #endif // wxUSE_MIMETYPE
661
662