1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        src/msw/volume.cpp
3 // Purpose:     wxFSVolume - encapsulates system volume information
4 // Author:      George Policello
5 // Modified by:
6 // Created:     28 Jan 02
7 // Copyright:   (c) 2002 George Policello
8 // Licence:     wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10 
11 // ============================================================================
12 // declarations
13 // ============================================================================
14 
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18 
19 #include "wx/wxprec.h"
20 
21 
22 #if wxUSE_FSVOLUME
23 
24 #include "wx/volume.h"
25 
26 #ifndef WX_PRECOMP
27     #if wxUSE_GUI
28         #include "wx/icon.h"
29     #endif
30     #include "wx/intl.h"
31     #include "wx/log.h"
32     #include "wx/hashmap.h"
33     #include "wx/filefn.h"
34 #endif // WX_PRECOMP
35 
36 #include "wx/dir.h"
37 #include "wx/dynlib.h"
38 #include "wx/arrimpl.cpp"
39 
40 // some compilers require including <windows.h> before <shellapi.h> so do it
41 // even if this is not necessary with most of them
42 #include "wx/msw/wrapwin.h"
43 #include <shellapi.h>
44 #include "wx/msw/wrapshl.h"
45 #include "wx/msw/missing.h"
46 
47 #if wxUSE_BASE
48 
49 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
50 // Dynamic library function defs.
51 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
52 
53 #if wxUSE_DYNLIB_CLASS
54 static wxDynamicLibrary s_mprLib;
55 #endif
56 
57 typedef DWORD (WINAPI* WNetOpenEnumPtr)(DWORD, DWORD, DWORD, LPNETRESOURCE, LPHANDLE);
58 typedef DWORD (WINAPI* WNetEnumResourcePtr)(HANDLE, LPDWORD, LPVOID, LPDWORD);
59 typedef DWORD (WINAPI* WNetCloseEnumPtr)(HANDLE);
60 
61 static WNetOpenEnumPtr s_pWNetOpenEnum;
62 static WNetEnumResourcePtr s_pWNetEnumResource;
63 static WNetCloseEnumPtr s_pWNetCloseEnum;
64 
65 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
66 // Globals/Statics
67 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
68 
69 #if defined(__CYGWIN__) && defined(__LP64__)
70     // We can't use long in 64 bit Cygwin build because Cygwin uses LP64 model
71     // (unlike all the other MSW compilers) and long is 64 bits, while
72     // InterlockedExchange(), with which this variable is used, requires a 32
73     // bit-sized value, so use Cygwin-specific type with the right size.
74     typedef __LONG32 wxInterlockedArg_t;
75 #else
76     typedef long wxInterlockedArg_t;
77 #endif
78 
79 static wxInterlockedArg_t s_cancelSearch = FALSE;
80 
81 struct FileInfo
82 {
FileInfoFileInfo83     FileInfo(unsigned flag=0, wxFSVolumeKind type=wxFS_VOL_OTHER) :
84         m_flags(flag), m_type(type) {}
85 
FileInfoFileInfo86     FileInfo(const FileInfo& other) { *this = other; }
operator =FileInfo87     FileInfo& operator=(const FileInfo& other)
88     {
89         m_flags = other.m_flags;
90         m_type = other.m_type;
91         return *this;
92     }
93 
94     unsigned m_flags;
95     wxFSVolumeKind m_type;
96 };
97 WX_DECLARE_STRING_HASH_MAP(FileInfo, FileInfoMap);
98 // Cygwin bug (?) destructor for global s_fileInfo is called twice...
GetFileInfoMap()99 static FileInfoMap& GetFileInfoMap()
100 {
101     static FileInfoMap s_fileInfo(25);
102 
103     return s_fileInfo;
104 }
105 #define s_fileInfo (GetFileInfoMap())
106 
107 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
108 // Local helper functions.
109 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
110 
111 //=============================================================================
112 // Function: GetBasicFlags
113 // Purpose: Set basic flags, primarily wxFS_VOL_REMOTE and wxFS_VOL_REMOVABLE.
114 // Notes: - Local and mapped drives are mounted by definition.  We have no
115 //          way to determine mounted status of network drives, so assume that
116 //          all drives are mounted, and let the caller decide otherwise.
117 //        - Other flags are 'best guess' from type of drive.  The system will
118 //          not report the file attributes with any degree of accuracy.
119 //=============================================================================
GetBasicFlags(const wxChar * filename)120 static unsigned GetBasicFlags(const wxChar* filename)
121 {
122     unsigned flags = wxFS_VOL_MOUNTED;
123 
124     //----------------------------------
125     // 'Best Guess' based on drive type.
126     //----------------------------------
127     wxFSVolumeKind type;
128     switch(GetDriveType(filename))
129     {
130     case DRIVE_FIXED:
131         type = wxFS_VOL_DISK;
132         break;
133 
134     case DRIVE_REMOVABLE:
135         flags |= wxFS_VOL_REMOVABLE;
136         type = wxFS_VOL_FLOPPY;
137         break;
138 
139     case DRIVE_CDROM:
140         flags |= wxFS_VOL_REMOVABLE | wxFS_VOL_READONLY;
141         type = wxFS_VOL_CDROM;
142         break;
143 
144     case DRIVE_REMOTE:
145         flags |= wxFS_VOL_REMOTE;
146         type = wxFS_VOL_NETWORK;
147         break;
148 
149     case DRIVE_NO_ROOT_DIR:
150         flags &= ~wxFS_VOL_MOUNTED;
151         type = wxFS_VOL_OTHER;
152         break;
153 
154     default:
155         type = wxFS_VOL_OTHER;
156         break;
157     }
158 
159     //-----------------------------------------------------------------------
160     // The following most likely will not modify anything not set above,
161     // and will not work at all for network shares or empty CD ROM drives.
162     // But it is a good check if the Win API ever gets better about reporting
163     // this information.
164     //-----------------------------------------------------------------------
165     SHFILEINFO fi;
166     long rc = SHGetFileInfo(filename, 0, &fi, sizeof(fi), SHGFI_ATTRIBUTES);
167     if (!rc)
168     {
169         // this error is not fatal, so don't show a message to the user about
170         // it, otherwise it would appear every time a generic directory picker
171         // dialog is used and there is a connected network drive
172         wxLogLastError(wxT("SHGetFileInfo"));
173     }
174     else
175     {
176         if (fi.dwAttributes & SFGAO_READONLY)
177             flags |= wxFS_VOL_READONLY;
178         if (fi.dwAttributes & SFGAO_REMOVABLE)
179             flags |= wxFS_VOL_REMOVABLE;
180     }
181 
182     //------------------
183     // Flags are cached.
184     //------------------
185     s_fileInfo[filename] = FileInfo(flags, type);
186 
187     return flags;
188 } // GetBasicFlags
189 
190 //=============================================================================
191 // Function: FilteredAdd
192 // Purpose: Add a file to the list if it meets the filter requirement.
193 // Notes: - See GetBasicFlags for remarks about the Mounted flag.
194 //=============================================================================
FilteredAdd(wxArrayString & list,const wxChar * filename,unsigned flagsSet,unsigned flagsUnset)195 static bool FilteredAdd(wxArrayString& list, const wxChar* filename,
196                         unsigned flagsSet, unsigned flagsUnset)
197 {
198     bool accept = true;
199     unsigned flags = GetBasicFlags(filename);
200 
201     if (flagsSet & wxFS_VOL_MOUNTED && !(flags & wxFS_VOL_MOUNTED))
202         accept = false;
203     else if (flagsUnset & wxFS_VOL_MOUNTED && (flags & wxFS_VOL_MOUNTED))
204         accept = false;
205     else if (flagsSet & wxFS_VOL_REMOVABLE && !(flags & wxFS_VOL_REMOVABLE))
206         accept = false;
207     else if (flagsUnset & wxFS_VOL_REMOVABLE && (flags & wxFS_VOL_REMOVABLE))
208         accept = false;
209     else if (flagsSet & wxFS_VOL_READONLY && !(flags & wxFS_VOL_READONLY))
210         accept = false;
211     else if (flagsUnset & wxFS_VOL_READONLY && (flags & wxFS_VOL_READONLY))
212         accept = false;
213     else if (flagsSet & wxFS_VOL_REMOTE && !(flags & wxFS_VOL_REMOTE))
214         accept = false;
215     else if (flagsUnset & wxFS_VOL_REMOTE && (flags & wxFS_VOL_REMOTE))
216         accept = false;
217 
218     // Add to the list if passed the filter.
219     if (accept)
220         list.Add(filename);
221 
222     return accept;
223 } // FilteredAdd
224 
225 //=============================================================================
226 // Function: BuildListFromNN
227 // Purpose: Append or remove items from the list
228 // Notes: - There is no way to find all disconnected NN items, or even to find
229 //          all items while determining which are connected and not.  So this
230 //          function will find either all items or connected items.
231 //=============================================================================
BuildListFromNN(wxArrayString & list,NETRESOURCE * pResSrc,unsigned flagsSet,unsigned flagsUnset)232 static void BuildListFromNN(wxArrayString& list, NETRESOURCE* pResSrc,
233                             unsigned flagsSet, unsigned flagsUnset)
234 {
235     HANDLE hEnum;
236     int rc;
237 
238     //-----------------------------------------------
239     // Scope may be all drives or all mounted drives.
240     //-----------------------------------------------
241     unsigned scope = RESOURCE_GLOBALNET;
242     if (flagsSet & wxFS_VOL_MOUNTED)
243         scope = RESOURCE_CONNECTED;
244 
245     //----------------------------------------------------------------------
246     // Enumerate all items, adding only non-containers (ie. network shares).
247     // Containers cause a recursive call to this function for their own
248     // enumeration.
249     //----------------------------------------------------------------------
250     if (rc = s_pWNetOpenEnum(scope, RESOURCETYPE_DISK, 0, pResSrc, &hEnum), rc == NO_ERROR)
251     {
252         DWORD count = 1;
253         DWORD size = 256;
254         NETRESOURCE* pRes = (NETRESOURCE*)malloc(size);
255         memset(pRes, 0, sizeof(NETRESOURCE));
256         while (rc = s_pWNetEnumResource(hEnum, &count, pRes, &size), rc == NO_ERROR || rc == ERROR_MORE_DATA)
257         {
258             if (s_cancelSearch)
259                 break;
260 
261             if (rc == ERROR_MORE_DATA)
262             {
263                 pRes = (NETRESOURCE*)realloc(pRes, size);
264                 count = 1;
265             }
266             else if (count == 1)
267             {
268                 // Enumerate the container.
269                 if (pRes->dwUsage & RESOURCEUSAGE_CONTAINER)
270                 {
271                     BuildListFromNN(list, pRes, flagsSet, flagsUnset);
272                 }
273 
274                 // Add the network share.
275                 else
276                 {
277                     wxString filename(pRes->lpRemoteName);
278 
279                     // if the drive is unavailable, FilteredAdd() can hang for
280                     // a long time and, moreover, its failure appears to be not
281                     // cached so this will happen every time we use it, so try
282                     // a much quicker wxDirExists() test (which still hangs but
283                     // for much shorter time) for locally mapped drives first
284                     // to try to avoid this
285                     if ( pRes->lpLocalName &&
286                             *pRes->lpLocalName &&
287                                 !wxDirExists(pRes->lpLocalName) )
288                         continue;
289 
290                     if (!filename.empty())
291                     {
292                         if (filename.Last() != '\\')
293                             filename.Append('\\');
294 
295                         // The filter function will not know mounted from unmounted, and neither do we unless
296                         // we are iterating using RESOURCE_CONNECTED, in which case they all are mounted.
297                         // Volumes on disconnected servers, however, will correctly show as unmounted.
298                         FilteredAdd(list, filename.t_str(), flagsSet, flagsUnset&~wxFS_VOL_MOUNTED);
299                         if (scope == RESOURCE_GLOBALNET)
300                             s_fileInfo[filename].m_flags &= ~wxFS_VOL_MOUNTED;
301                     }
302                 }
303             }
304             else if (count == 0)
305                 break;
306         }
307         free(pRes);
308         s_pWNetCloseEnum(hEnum);
309     }
310 } // BuildListFromNN
311 
312 //=============================================================================
313 // Function: CompareFcn
314 // Purpose: Used to sort the NN list alphabetically, case insensitive.
315 //=============================================================================
CompareFcn(const wxString & first,const wxString & second)316 static int CompareFcn(const wxString& first, const wxString& second)
317 {
318     return wxStricmp(first.c_str(), second.c_str());
319 } // CompareFcn
320 
321 //=============================================================================
322 // Function: BuildRemoteList
323 // Purpose: Append Network Neighborhood items to the list.
324 // Notes: - Mounted gets transalated into Connected.  FilteredAdd is told
325 //          to ignore the Mounted flag since we need to handle it in a weird
326 //          way manually.
327 //        - The resulting list is sorted alphabetically.
328 //=============================================================================
BuildRemoteList(wxArrayString & list,NETRESOURCE * pResSrc,unsigned flagsSet,unsigned flagsUnset)329 static bool BuildRemoteList(wxArrayString& list, NETRESOURCE* pResSrc,
330                             unsigned flagsSet, unsigned flagsUnset)
331 {
332     // NN query depends on dynamically loaded library.
333     if (!s_pWNetOpenEnum || !s_pWNetEnumResource || !s_pWNetCloseEnum)
334     {
335         wxLogError(_("Failed to load mpr.dll."));
336         return false;
337     }
338 
339     // Don't waste time doing the work if the flags conflict.
340     if (flagsSet & wxFS_VOL_MOUNTED && flagsUnset & wxFS_VOL_MOUNTED)
341         return false;
342 
343     //----------------------------------------------
344     // Generate the list according to the flags set.
345     //----------------------------------------------
346     BuildListFromNN(list, pResSrc, flagsSet, flagsUnset);
347     list.Sort(CompareFcn);
348 
349     //-------------------------------------------------------------------------
350     // If mounted only is requested, then we only need one simple pass.
351     // Otherwise, we need to build a list of all NN volumes and then apply the
352     // list of mounted drives to it.
353     //-------------------------------------------------------------------------
354     if (!(flagsSet & wxFS_VOL_MOUNTED))
355     {
356         // generate.
357         wxArrayString mounted;
358         BuildListFromNN(mounted, pResSrc, flagsSet | wxFS_VOL_MOUNTED, flagsUnset & ~wxFS_VOL_MOUNTED);
359         mounted.Sort(CompareFcn);
360 
361         // apply list from bottom to top to preserve indexes if removing items.
362         ssize_t iList = list.GetCount()-1;
363         for (ssize_t iMounted = mounted.GetCount()-1; iMounted >= 0 && iList >= 0; iMounted--)
364         {
365             int compare;
366             wxString all(list[iList]);
367             wxString mount(mounted[iMounted]);
368 
369             while (compare =
370                      wxStricmp(list[iList].c_str(), mounted[iMounted].c_str()),
371                    compare > 0 && iList >= 0)
372             {
373                 iList--;
374                 all = list[iList];
375             }
376 
377 
378             if (compare == 0)
379             {
380                 // Found the element.  Remove it or mark it mounted.
381                 if (flagsUnset & wxFS_VOL_MOUNTED)
382                     list.RemoveAt(iList);
383                 else
384                     s_fileInfo[list[iList]].m_flags |= wxFS_VOL_MOUNTED;
385 
386             }
387 
388             iList--;
389         }
390     }
391 
392     return true;
393 } // BuildRemoteList
394 
395 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
396 // wxFSVolume
397 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
398 
399 //=============================================================================
400 // Function: GetVolumes
401 // Purpose: Generate and return a list of all volumes (drives) available.
402 // Notes:
403 //=============================================================================
GetVolumes(int flagsSet,int flagsUnset)404 wxArrayString wxFSVolumeBase::GetVolumes(int flagsSet, int flagsUnset)
405 {
406     ::InterlockedExchange(&s_cancelSearch, FALSE);     // reset
407 
408 #if wxUSE_DYNLIB_CLASS
409     if (!s_mprLib.IsLoaded() && s_mprLib.Load(wxT("mpr.dll")))
410     {
411 #ifdef UNICODE
412         s_pWNetOpenEnum = (WNetOpenEnumPtr)s_mprLib.GetSymbol(wxT("WNetOpenEnumW"));
413         s_pWNetEnumResource = (WNetEnumResourcePtr)s_mprLib.GetSymbol(wxT("WNetEnumResourceW"));
414 #else
415         s_pWNetOpenEnum = (WNetOpenEnumPtr)s_mprLib.GetSymbol(wxT("WNetOpenEnumA"));
416         s_pWNetEnumResource = (WNetEnumResourcePtr)s_mprLib.GetSymbol(wxT("WNetEnumResourceA"));
417 #endif
418         s_pWNetCloseEnum = (WNetCloseEnumPtr)s_mprLib.GetSymbol(wxT("WNetCloseEnum"));
419     }
420 #endif
421 
422     wxArrayString list;
423 
424     //-------------------------------
425     // Local and mapped drives first.
426     //-------------------------------
427     // Allocate the required space for the API call.
428     const DWORD chars = GetLogicalDriveStrings(0, NULL);
429     TCHAR* buf = new TCHAR[chars+1];
430 
431     // Get the list of drives.
432     GetLogicalDriveStrings(chars, buf);
433 
434     // Parse the list into an array, applying appropriate filters.
435     TCHAR *pVol;
436     pVol = buf;
437     while (*pVol)
438     {
439         FilteredAdd(list, pVol, flagsSet, flagsUnset);
440         pVol = pVol + wxStrlen(pVol) + 1;
441     }
442 
443     // Cleanup.
444     delete[] buf;
445 
446     //---------------------------
447     // Network Neighborhood next.
448     //---------------------------
449 
450     // not exclude remote and not removable
451     if (!(flagsUnset & wxFS_VOL_REMOTE) &&
452         !(flagsSet & wxFS_VOL_REMOVABLE)
453        )
454     {
455         // The returned list will be sorted alphabetically.  We don't pass
456         // our in since we don't want to change to order of the local drives.
457         wxArrayString nn;
458         if (BuildRemoteList(nn, 0, flagsSet, flagsUnset))
459         {
460             for (size_t idx = 0; idx < nn.GetCount(); idx++)
461                 list.Add(nn[idx]);
462         }
463     }
464 
465     return list;
466 } // GetVolumes
467 
468 //=============================================================================
469 // Function: CancelSearch
470 // Purpose: Instruct an active search to stop.
471 // Notes: - This will only sensibly be called by a thread other than the one
472 //          performing the search.  This is the only thread-safe function
473 //          provided by the class.
474 //=============================================================================
CancelSearch()475 void wxFSVolumeBase::CancelSearch()
476 {
477     ::InterlockedExchange(&s_cancelSearch, TRUE);
478 } // CancelSearch
479 
480 //=============================================================================
481 // Function: constructor
482 // Purpose: default constructor
483 //=============================================================================
wxFSVolumeBase()484 wxFSVolumeBase::wxFSVolumeBase()
485 {
486     m_isOk = false;
487 } // wxVolume
488 
489 //=============================================================================
490 // Function: constructor
491 // Purpose: constructor that calls Create
492 //=============================================================================
wxFSVolumeBase(const wxString & name)493 wxFSVolumeBase::wxFSVolumeBase(const wxString& name)
494 {
495     Create(name);
496 } // wxVolume
497 
498 //=============================================================================
499 // Function: Create
500 // Purpose: Finds, logs in, etc. to the request volume.
501 //=============================================================================
Create(const wxString & name)502 bool wxFSVolumeBase::Create(const wxString& name)
503 {
504     // assume fail.
505     m_isOk = false;
506 
507     // supplied.
508     m_volName = name;
509 
510     // Display name.
511     SHFILEINFO fi;
512     long rc = SHGetFileInfo(m_volName.t_str(), 0, &fi, sizeof(fi), SHGFI_DISPLAYNAME);
513     if (!rc)
514     {
515         wxLogError(_("Cannot read typename from '%s'!"), m_volName.c_str());
516         return false;
517     }
518     m_dispName = fi.szDisplayName;
519 
520     // all tests passed.
521     m_isOk = true;
522     return true;
523 } // Create
524 
525 //=============================================================================
526 // Function: IsOk
527 // Purpose: returns true if the volume is legal.
528 // Notes: For fixed disks, it must exist.  For removable disks, it must also
529 //        be present.  For Network Shares, it must also be logged in, etc.
530 //=============================================================================
IsOk() const531 bool wxFSVolumeBase::IsOk() const
532 {
533     return m_isOk;
534 } // IsOk
535 
536 //=============================================================================
537 // Function: GetKind
538 // Purpose: Return the type of the volume.
539 //=============================================================================
GetKind() const540 wxFSVolumeKind wxFSVolumeBase::GetKind() const
541 {
542     if (!m_isOk)
543         return wxFS_VOL_OTHER;
544 
545     FileInfoMap::iterator itr = s_fileInfo.find(m_volName);
546     if (itr == s_fileInfo.end())
547         return wxFS_VOL_OTHER;
548 
549     return itr->second.m_type;
550 }
551 
552 //=============================================================================
553 // Function: GetFlags
554 // Purpose: Return the caches flags for this volume.
555 // Notes: - Returns -1 if no flags were cached.
556 //=============================================================================
GetFlags() const557 int wxFSVolumeBase::GetFlags() const
558 {
559     if (!m_isOk)
560         return -1;
561 
562     FileInfoMap::iterator itr = s_fileInfo.find(m_volName);
563     if (itr == s_fileInfo.end())
564         return -1;
565 
566     return itr->second.m_flags;
567 } // GetFlags
568 
569 #endif // wxUSE_BASE
570 
571 // ============================================================================
572 // wxFSVolume
573 // ============================================================================
574 
575 #if wxUSE_GUI
576 
InitIcons()577 void wxFSVolume::InitIcons()
578 {
579     m_icons.Alloc(wxFS_VOL_ICO_MAX);
580     wxIcon null;
581     for (int idx = 0; idx < wxFS_VOL_ICO_MAX; idx++)
582         m_icons.Add(null);
583 }
584 
585 //=============================================================================
586 // Function: GetIcon
587 // Purpose: return the requested icon.
588 //=============================================================================
589 
GetIcon(wxFSIconType type) const590 wxIcon wxFSVolume::GetIcon(wxFSIconType type) const
591 {
592     wxCHECK_MSG( type >= 0 && (size_t)type < m_icons.GetCount(), wxNullIcon,
593                  wxT("wxFSIconType::GetIcon(): invalid icon index") );
594 
595 #ifdef __WXMSW__
596     // Load on demand.
597     if (m_icons[type].IsNull())
598     {
599         UINT flags = SHGFI_ICON;
600         switch (type)
601         {
602         case wxFS_VOL_ICO_SMALL:
603             flags |= SHGFI_SMALLICON;
604             break;
605 
606         case wxFS_VOL_ICO_LARGE:
607             flags |= SHGFI_SHELLICONSIZE;
608             break;
609 
610         case wxFS_VOL_ICO_SEL_SMALL:
611             flags |= SHGFI_SMALLICON | SHGFI_OPENICON;
612             break;
613 
614         case wxFS_VOL_ICO_SEL_LARGE:
615             flags |= SHGFI_SHELLICONSIZE | SHGFI_OPENICON;
616             break;
617 
618         case wxFS_VOL_ICO_MAX:
619             wxFAIL_MSG(wxT("wxFS_VOL_ICO_MAX is not valid icon type"));
620             break;
621         }
622 
623         SHFILEINFO fi;
624         long rc = SHGetFileInfo(m_volName.t_str(), 0, &fi, sizeof(fi), flags);
625         if (!rc || !fi.hIcon)
626         {
627             wxLogError(_("Cannot load icon from '%s'."), m_volName.c_str());
628         }
629         else
630         {
631             m_icons[type].CreateFromHICON((WXHICON)fi.hIcon);
632         }
633     }
634 
635     return m_icons[type];
636 #else
637     wxFAIL_MSG(wxS("Can't convert HICON to wxIcon in this port."));
638     return wxNullIcon;
639 #endif
640 } // GetIcon
641 
642 #endif // wxUSE_GUI
643 
644 #endif // wxUSE_FSVOLUME
645