1 /////////////////////////////////////////////////////////////////////////////
2 // Name: msw/mimetype.cpp
3 // Purpose: classes and functions to manage MIME types
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license (part of wxExtra library)
10 /////////////////////////////////////////////////////////////////////////////
13 #pragma implementation "mimetype.h"
16 // for compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
23 // Doesn't compile in WIN16 mode
27 #include "wx/string.h"
36 #include "wx/dynarray.h"
37 #include "wx/confbase.h"
40 #include "wx/msw/registry.h"
44 #include "wx/msw/mimetype.h"
46 // other standard headers
49 // in case we're compiling in non-GUI mode
50 class WXDLLEXPORT wxIcon
;
52 // These classes use Windows registry to retrieve the required information.
54 // Keys used (not all of them are documented, so it might actually stop working
55 // in future versions of Windows...):
56 // 1. "HKCR\MIME\Database\Content Type" contains subkeys for all known MIME
57 // types, each key has a string value "Extension" which gives (dot preceded)
58 // extension for the files of this MIME type.
60 // 2. "HKCR\.ext" contains
61 // a) unnamed value containing the "filetype"
62 // b) value "Content Type" containing the MIME type
64 // 3. "HKCR\filetype" contains
65 // a) unnamed value containing the description
66 // b) subkey "DefaultIcon" with single unnamed value giving the icon index in
68 // c) shell\open\command and shell\open\print subkeys containing the commands
69 // to open/print the file (the positional parameters are introduced by %1,
70 // %2, ... in these strings, we change them to %s ourselves)
72 // although I don't know of any official documentation which mentions this
73 // location, uses it, so it isn't likely to change
74 static const wxChar
*MIME_DATABASE_KEY
= wxT("MIME\\Database\\Content Type\\");
76 void wxFileTypeImpl
::Init(const wxString
& strFileType
, const wxString
& ext
)
78 // VZ: does it? (FIXME)
79 wxCHECK_RET( !ext
.IsEmpty(), _T("needs an extension") );
81 if ( ext
[0u] != wxT('.') ) {
86 m_strFileType
= strFileType
;
88 m_strFileType
= m_ext
.AfterFirst('.') + "_auto_file";
92 wxString wxFileTypeImpl
::GetVerbPath(const wxString
& verb
) const
95 path
<< m_strFileType
<< _T("\\shell\\") << verb
<< _T("\\command");
99 size_t wxFileTypeImpl
::GetAllCommands(wxArrayString
*verbs
,
100 wxArrayString
*commands
,
101 const wxFileType
::MessageParameters
& params
) const
103 wxCHECK_MSG( !m_ext
.IsEmpty(), 0, _T("GetAllCommands() needs an extension") );
105 if ( m_strFileType
.IsEmpty() )
107 // get it from the registry
108 wxFileTypeImpl
*self
= wxConstCast(this, wxFileTypeImpl
);
109 wxRegKey
rkey(wxRegKey
::HKCR
, m_ext
);
110 if ( !rkey
.Exists() || !rkey
.QueryValue(_T(""), self
->m_strFileType
) )
112 wxLogDebug(_T("Can't get the filetype for extension '%s'."),
119 // enum all subkeys of HKCR\filetype\shell
121 wxRegKey
rkey(wxRegKey
::HKCR
, m_strFileType
+ _T("\\shell"));
124 bool ok
= rkey
.GetFirstKey(verb
, dummy
);
127 wxString command
= wxFileType
::ExpandCommand(GetCommand(verb
), params
);
129 // we want the open bverb to eb always the first
131 if ( verb
.CmpNoCase(_T("open")) == 0 )
134 verbs
->Insert(verb
, 0);
136 commands
->Insert(command
, 0);
138 else // anything else than "open"
143 commands
->Add(command
);
146 ok
= rkey
.GetNextKey(verb
, dummy
);
152 // ----------------------------------------------------------------------------
153 // modify the registry database
154 // ----------------------------------------------------------------------------
156 bool wxFileTypeImpl
::EnsureExtKeyExists()
158 wxRegKey
rkey(wxRegKey
::HKCR
, m_ext
);
159 if ( !rkey
.Exists() )
161 if ( !rkey
.Create() || !rkey
.SetValue(_T(""), m_strFileType
) )
163 wxLogError(_("Failed to create registry entry for '%s' files."),
172 bool wxFileTypeImpl
::SetCommand(const wxString
& cmd
,
173 const wxString
& verb
,
174 bool overwriteprompt
)
176 wxCHECK_MSG( !m_ext
.IsEmpty() && !verb
.IsEmpty(), FALSE
,
177 _T("SetCommand() needs an extension and a verb") );
179 if ( !EnsureExtKeyExists() )
182 wxRegKey
rkey(wxRegKey
::HKCR
, GetVerbPath(verb
));
184 if ( rkey
.Exists() && overwriteprompt
)
188 rkey
.QueryValue(wxT(""), old
);
192 _("Do you want to overwrite the command used to %s "
193 "files with extension \"%s\" (current value is '%s', "
194 "new value is '%s')?"),
199 _("Confirm registry update"),
200 wxYES_NO
| wxICON_QUESTION
210 // 1. translate '%s' to '%1' instead of always adding it
211 // 2. create DDEExec value if needed (undo GetCommand)
212 return rkey
.Create() && rkey
.SetValue(_T(""), cmd
+ _T(" \"%1\"") );
215 bool wxFileTypeImpl
::SetMimeType(const wxString
& mimeTypeOrig
)
217 wxCHECK_MSG( !m_ext
.IsEmpty(), FALSE
, _T("SetMimeType() needs extension") );
219 if ( !EnsureExtKeyExists() )
222 // VZ: is this really useful? (FIXME)
226 // make up a default value for it
228 wxSplitPath(GetCommand(_T("open")), NULL
, &cmd
, NULL
);
229 mimeType
<< _T("application/x-") << cmd
;
233 mimeType
= mimeTypeOrig
;
236 wxRegKey
rkey(wxRegKey
::HKCR
, m_ext
);
237 return rkey
.Create() && rkey
.SetValue(_T("Content Type"), mimeType
);
240 bool wxFileTypeImpl
::SetDefaultIcon(const wxString
& cmd
, int index
)
242 wxCHECK_MSG( !m_ext
.IsEmpty(), FALSE
, _T("SetMimeType() needs extension") );
243 wxCHECK_MSG( wxFileExists(cmd
), FALSE
, _T("Icon file not found.") );
245 if ( !EnsureExtKeyExists() )
248 wxRegKey
rkey(wxRegKey
::HKCR
, m_strFileType
+ _T("\\DefaultIcon"));
250 return rkey
.Create() &&
251 rkey
.SetValue(_T(""),
252 wxString
::Format(_T("%s,%d"), cmd
.c_str(), index
));
255 // ----------------------------------------------------------------------------
256 // remove file association
257 // ----------------------------------------------------------------------------
259 bool wxFileTypeImpl
::RemoveCommand(const wxString
& verb
)
261 wxCHECK_MSG( !m_ext
.IsEmpty() && !verb
.IsEmpty(), FALSE
,
262 _T("RemoveCommand() needs an extension and a verb") );
264 wxString sKey
= m_strFileType
;
265 wxRegKey
rkey(wxRegKey
::HKCR
, GetVerbPath(verb
));
267 // if the key already doesn't exist, it's a success
268 return !rkey
.Exists() || rkey
.DeleteSelf();
271 bool wxFileTypeImpl
::RemoveMimeType()
273 wxCHECK_MSG( !m_ext
.IsEmpty(), FALSE
, _T("RemoveMimeType() needs extension") );
275 wxRegKey
rkey(wxRegKey
::HKCR
, m_ext
);
276 return !rkey
.Exists() || rkey
.DeleteSelf();
279 bool wxFileTypeImpl
::RemoveDefaultIcon()
281 wxCHECK_MSG( !m_ext
.IsEmpty(), FALSE
,
282 _T("RemoveDefaultIcon() needs extension") );
284 wxRegKey
rkey (wxRegKey
::HKCR
, m_strFileType
+ _T("\\DefaultIcon"));
285 return !rkey
.Exists() || rkey
.DeleteSelf();
288 wxString wxFileTypeImpl
::GetCommand(const wxChar
*verb
) const
290 // suppress possible error messages
294 if ( wxRegKey(wxRegKey
::HKCR
, m_ext
+ _T("\\shell")).Exists() )
296 if ( wxRegKey(wxRegKey
::HKCR
, m_strFileType
+ _T("\\shell")).Exists() )
297 strKey
= m_strFileType
;
302 return wxEmptyString
;
305 strKey
<< wxT("\\shell\\") << verb
;
306 wxRegKey
key(wxRegKey
::HKCR
, strKey
+ _T("\\command"));
309 // it's the default value of the key
310 if ( key
.QueryValue(wxT(""), command
) ) {
311 // transform it from '%1' to '%s' style format string (now also
312 // test for %L - apparently MS started using it as well for the
315 // NB: we don't make any attempt to verify that the string is valid,
316 // i.e. doesn't contain %2, or second %1 or .... But we do make
317 // sure that we return a string with _exactly_ one '%s'!
318 bool foundFilename
= FALSE
;
319 size_t len
= command
.Len();
320 for ( size_t n
= 0; (n
< len
) && !foundFilename
; n
++ ) {
321 if ( command
[n
] == wxT('%') &&
323 (command
[n
+ 1] == wxT('1') ||
324 command
[n
+ 1] == wxT('L')) ) {
325 // replace it with '%s'
326 command
[n
+ 1] = wxT('s');
328 foundFilename
= TRUE
;
333 // look whether we must issue some DDE requests to the application
334 // (and not just launch it)
335 strKey
+= _T("\\DDEExec");
336 wxRegKey
keyDDE(wxRegKey
::HKCR
, strKey
);
337 if ( keyDDE
.Open() ) {
338 wxString ddeCommand
, ddeServer
, ddeTopic
;
339 keyDDE
.QueryValue(_T(""), ddeCommand
);
340 ddeCommand
.Replace(_T("%1"), _T("%s"));
342 wxRegKey(wxRegKey
::HKCR
, strKey
+ _T("\\Application")).
343 QueryValue(_T(""), ddeServer
);
344 wxRegKey(wxRegKey
::HKCR
, strKey
+ _T("\\Topic")).
345 QueryValue(_T(""), ddeTopic
);
347 // HACK: we use a special feature of wxExecute which exists
348 // just because we need it here: it will establish DDE
349 // conversation with the program it just launched
350 command
.Prepend(_T("WX_DDE#"));
351 command
<< _T('#') << ddeServer
352 << _T('#') << ddeTopic
353 << _T('#') << ddeCommand
;
357 if ( !foundFilename
) {
358 // we didn't find any '%1' - the application doesn't know which
359 // file to open (note that we only do it if there is no DDEExec
362 // HACK: append the filename at the end, hope that it will do
363 command
<< wxT(" %s");
367 //else: no such file type or no value, will return empty string
373 wxFileTypeImpl
::GetOpenCommand(wxString
*openCmd
,
374 const wxFileType
::MessageParameters
& params
)
379 cmd
= m_info
->GetOpenCommand();
382 cmd
= GetCommand(wxT("open"));
385 *openCmd
= wxFileType
::ExpandCommand(cmd
, params
);
387 return !openCmd
->IsEmpty();
391 wxFileTypeImpl
::GetPrintCommand(wxString
*printCmd
,
392 const wxFileType
::MessageParameters
& params
)
397 cmd
= m_info
->GetPrintCommand();
400 cmd
= GetCommand(wxT("print"));
403 *printCmd
= wxFileType
::ExpandCommand(cmd
, params
);
405 return !printCmd
->IsEmpty();
408 // TODO this function is half implemented
409 bool wxFileTypeImpl
::GetExtensions(wxArrayString
& extensions
)
412 extensions
= m_info
->GetExtensions();
416 else if ( m_ext
.IsEmpty() ) {
417 // the only way to get the list of extensions from the file type is to
418 // scan through all extensions in the registry - too slow...
423 extensions
.Add(m_ext
);
425 // it's a lie too, we don't return _all_ extensions...
430 bool wxFileTypeImpl
::GetMimeType(wxString
*mimeType
) const
433 // we already have it
434 *mimeType
= m_info
->GetMimeType();
439 // suppress possible error messages
441 wxRegKey
key(wxRegKey
::HKCR
, m_ext
);
443 return key
.Open() && key
.QueryValue(wxT("Content Type"), *mimeType
);
446 bool wxFileTypeImpl
::GetMimeTypes(wxArrayString
& mimeTypes
) const
450 if ( !GetMimeType(&s
) )
461 bool wxFileTypeImpl
::GetIcon(wxIcon
*icon
,
463 int *iconIndex
) const
467 // we don't have icons in the fallback resources
472 strIconKey
<< m_strFileType
<< wxT("\\DefaultIcon");
474 // suppress possible error messages
476 wxRegKey
key(wxRegKey
::HKCR
, strIconKey
);
480 // it's the default value of the key
481 if ( key
.QueryValue(wxT(""), strIcon
) ) {
482 // the format is the following: <full path to file>, <icon index>
483 // NB: icon index may be negative as well as positive and the full
484 // path may contain the environment variables inside '%'
485 wxString strFullPath
= strIcon
.BeforeLast(wxT(',')),
486 strIndex
= strIcon
.AfterLast(wxT(','));
488 // index may be omitted, in which case BeforeLast(',') is empty and
489 // AfterLast(',') is the whole string
490 if ( strFullPath
.IsEmpty() ) {
491 strFullPath
= strIndex
;
495 wxString strExpPath
= wxExpandEnvVars(strFullPath
);
496 int nIndex
= wxAtoi(strIndex
) - 1 ; //bug here we need C based counting!!
498 HICON hIcon
= ExtractIcon(GetModuleHandle(NULL
), strExpPath
, nIndex
);
499 switch ( (int)hIcon
) {
500 case 0: // means no icons were found
501 case 1: // means no such file or it wasn't a DLL/EXE/OCX/ICO/...
502 wxLogDebug(wxT("incorrect registry entry '%s': no such icon."),
503 key
.GetName().c_str());
507 icon
->SetHICON((WXHICON
)hIcon
);
511 *iconFile
= strFullPath
;
517 // no such file type or no value or incorrect icon entry
523 bool wxFileTypeImpl
::GetDescription(wxString
*desc
) const
526 // we already have it
527 *desc
= m_info
->GetDescription();
532 // suppress possible error messages
534 wxRegKey
key(wxRegKey
::HKCR
, m_strFileType
);
537 // it's the default value of the key
538 if ( key
.QueryValue(wxT(""), *desc
) ) {
548 wxMimeTypesManagerImpl
::CreateFileType(const wxString
& filetype
, const wxString
& ext
)
550 wxFileType
*fileType
= new wxFileType
;
551 fileType
->m_impl
->Init(filetype
, ext
);
555 // extension -> file type
557 wxMimeTypesManagerImpl
::GetFileTypeFromExtension(const wxString
& ext
)
559 // add the leading point if necessary
561 if ( ext
[0u] != wxT('.') ) {
566 // suppress possible error messages
569 bool knownExtension
= FALSE
;
571 wxString strFileType
;
572 wxRegKey
key(wxRegKey
::HKCR
, str
);
574 // it's the default value of the key
575 if ( key
.QueryValue(wxT(""), strFileType
) ) {
576 // create the new wxFileType object
577 return CreateFileType(strFileType
, ext
);
580 // this extension doesn't have a filetype, but it's known to the
581 // system and may be has some other useful keys (open command or
582 // content-type), so still return a file type object for it
583 knownExtension
= TRUE
;
587 // check the fallbacks
588 // TODO linear search is potentially slow, perhaps we should use a sorted
590 size_t count
= m_fallbacks
.GetCount();
591 for ( size_t n
= 0; n
< count
; n
++ ) {
592 if ( m_fallbacks
[n
].GetExtensions().Index(ext
) != wxNOT_FOUND
) {
593 wxFileType
*fileType
= new wxFileType
;
594 fileType
->m_impl
->Init(m_fallbacks
[n
]);
600 if ( !knownExtension
)
606 return CreateFileType(wxEmptyString
, ext
);
610 wxMimeTypesManagerImpl
::GetOrAllocateFileTypeFromExtension(const wxString
& ext
)
612 wxFileType
*fileType
= GetFileTypeFromExtension(ext
);
615 fileType
= CreateFileType(wxEmptyString
, ext
);
622 // MIME type -> extension -> file type
624 wxMimeTypesManagerImpl
::GetFileTypeFromMimeType(const wxString
& mimeType
)
626 wxString strKey
= MIME_DATABASE_KEY
;
629 // suppress possible error messages
633 wxRegKey
key(wxRegKey
::HKCR
, strKey
);
635 if ( key
.QueryValue(wxT("Extension"), ext
) ) {
636 return GetFileTypeFromExtension(ext
);
640 // check the fallbacks
641 // TODO linear search is potentially slow, perhaps we should use a sorted
643 size_t count
= m_fallbacks
.GetCount();
644 for ( size_t n
= 0; n
< count
; n
++ ) {
645 if ( wxMimeTypesManager
::IsOfType(mimeType
,
646 m_fallbacks
[n
].GetMimeType()) ) {
647 wxFileType
*fileType
= new wxFileType
;
648 fileType
->m_impl
->Init(m_fallbacks
[n
]);
658 size_t wxMimeTypesManagerImpl
::EnumAllFileTypes(wxArrayString
& mimetypes
)
660 // enumerate all keys under MIME_DATABASE_KEY
661 wxRegKey
key(wxRegKey
::HKCR
, MIME_DATABASE_KEY
);
665 bool cont
= key
.GetFirstKey(type
, cookie
);
670 cont
= key
.GetNextKey(type
, cookie
);
673 return mimetypes
.GetCount();
676 // ----------------------------------------------------------------------------
677 // create a new association
678 // ----------------------------------------------------------------------------
680 wxFileType
*wxMimeTypesManager
::Associate(const wxString
& ext
,
681 const wxString
& mimetype
,
682 const wxString
& filetypeOrig
,
683 const wxString
& WXUNUSED(desc
))
685 wxCHECK_MSG( !ext
.empty(), NULL
, _T("Associate() needs extension") );
688 if ( ext
[0u] != _T('.') )
689 extWithDot
= _T('.');
692 wxRegKey
key(wxRegKey
::HKCR
, extWithDot
);
693 wxFileType
*ft
= NULL
;
698 // create the mapping from the extension to the filetype
699 bool ok
= key
.Create();
702 if ( filetypeOrig
.empty() )
704 // make it up from the extension
705 filetype
<< extWithDot
.c_str() + 1 << _T("_auto_file");
709 // just use the provided one
710 filetype
= filetypeOrig
;
713 ok
= key
.SetValue(_T(""), filetype
);
716 if ( ok
&& !mimetype
.empty() )
719 ok
= key
.SetValue(_T("Content Type"), mimetype
);
723 // create the MIME key
724 wxString strKey
= MIME_DATABASE_KEY
;
726 wxRegKey
keyMIME(wxRegKey
::HKCR
, strKey
);
727 ok
= keyMIME
.Create();
731 // and provide a back link to the extension
732 ok
= keyMIME
.SetValue(_T("Extension"), extWithDot
);
739 // create the filetype key itself (it will be empty for now, but
740 // SetCommand(), SetDefaultIcon() &c will use it later)
741 wxRegKey
keyFT(wxRegKey
::HKCR
, filetype
);
747 // ok, we've created everything correctly
748 ft
= m_impl
->CreateFileType(filetype
, extWithDot
);
752 // one of the registry operations failed
753 wxLogError(_("Failed to register extension '%s'."), ext
.c_str());
756 else // key already exists
758 // FIXME we probably should return an existing file type then?