wscript: Implemented Host_put_Interactive.
[wine] / programs / winemenubuilder / winemenubuilder.c
1 /*
2  * Helper program to build unix menu entries
3  *
4  * Copyright 1997 Marcus Meissner
5  * Copyright 1998 Juergen Schmied
6  * Copyright 2003 Mike McCormack for CodeWeavers
7  * Copyright 2004 Dmitry Timoshkov
8  * Copyright 2005 Bill Medland
9  * Copyright 2008 Damjan Jovanovic
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24  *
25  *
26  *  This program is used to replicate the Windows desktop and start menu
27  * into the native desktop's copies.  Desktop entries are merged directly
28  * into the native desktop.  The Windows Start Menu corresponds to a Wine
29  * entry within the native "start" menu and replicates the whole tree
30  * structure of the Windows Start Menu.  Currently it does not differentiate
31  * between the user's desktop/start menu and the "All Users" copies.
32  *
33  *  This program will read a Windows shortcut file using the IShellLink
34  * interface, then create a KDE/Gnome menu entry for the shortcut.
35  *
36  *  winemenubuilder [ -w ] <shortcut.lnk>
37  *
38  *  If the -w parameter is passed, and the shortcut cannot be created,
39  * this program will wait for the parent process to finish and then try
40  * again. This covers the case when a ShortCut is created before the
41  * executable containing its icon.
42  *
43  * TODO
44  *  Handle data lnk files. There is no icon in the file; the icon is in 
45  * the handler for the file type (or pointed to by the lnk file).  Also it 
46  * might be better to use a native handler (e.g. a native acroread for pdf
47  * files).  
48  *  Differentiate between the user's entries and the "All Users" entries.
49  * If it is possible to add the desktop files to the native system's
50  * shared location for an "All Users" entry then do so.  As a suggestion the
51  * shared menu Wine base could be writable to the wine group, or a wineadm 
52  * group.
53  *  Clean up fd.o menu icons and .directory files when the menu is deleted
54  * in Windows.
55  *  Associate applications under HKCR\Applications to open any MIME type
56  * (by associating with application/octet-stream, or how?).
57  *  Clean up fd.o MIME types when they are deleted in Windows, their icons
58  * too. Very hard - once we associate them with fd.o, we can't tell whether
59  * they are ours or not, and the extension <-> MIME type mapping isn't
60  * one-to-one either.
61  *  Wine's HKCR is broken - it doesn't merge HKCU\Software\Classes, so apps
62  * that write associations there won't associate (#17019).
63  */
64
65 #include "config.h"
66 #include "wine/port.h"
67
68 #include <ctype.h>
69 #include <stdio.h>
70 #include <string.h>
71 #ifdef HAVE_UNISTD_H
72 #include <unistd.h>
73 #endif
74 #include <errno.h>
75 #include <stdarg.h>
76 #ifdef HAVE_FNMATCH_H
77 #include <fnmatch.h>
78 #endif
79
80 #define COBJMACROS
81 #define NONAMELESSUNION
82
83 #include <windows.h>
84 #include <shlobj.h>
85 #include <objidl.h>
86 #include <shlguid.h>
87 #include <appmgmt.h>
88 #include <tlhelp32.h>
89 #include <intshcut.h>
90 #include <shlwapi.h>
91 #include <initguid.h>
92 #include <wincodec.h>
93
94 #include "wine/unicode.h"
95 #include "wine/debug.h"
96 #include "wine/library.h"
97 #include "wine/list.h"
98 #include "wine/rbtree.h"
99
100 WINE_DEFAULT_DEBUG_CHANNEL(menubuilder);
101
102 #define in_desktop_dir(csidl) ((csidl)==CSIDL_DESKTOPDIRECTORY || \
103                                (csidl)==CSIDL_COMMON_DESKTOPDIRECTORY)
104 #define in_startmenu(csidl)   ((csidl)==CSIDL_STARTMENU || \
105                                (csidl)==CSIDL_COMMON_STARTMENU)
106         
107 /* link file formats */
108
109 #include "pshpack1.h"
110
111 typedef struct
112 {
113     BYTE bWidth;
114     BYTE bHeight;
115     BYTE bColorCount;
116     BYTE bReserved;
117     WORD wPlanes;
118     WORD wBitCount;
119     DWORD dwBytesInRes;
120     WORD nID;
121 } GRPICONDIRENTRY;
122
123 typedef struct
124 {
125     WORD idReserved;
126     WORD idType;
127     WORD idCount;
128     GRPICONDIRENTRY idEntries[1];
129 } GRPICONDIR;
130
131 typedef struct
132 {
133     BYTE bWidth;
134     BYTE bHeight;
135     BYTE bColorCount;
136     BYTE bReserved;
137     WORD wPlanes;
138     WORD wBitCount;
139     DWORD dwBytesInRes;
140     DWORD dwImageOffset;
141 } ICONDIRENTRY;
142
143 typedef struct
144 {
145     WORD idReserved;
146     WORD idType;
147     WORD idCount;
148 } ICONDIR;
149
150 typedef struct
151 {
152     WORD offset;
153     WORD length;
154     WORD flags;
155     WORD id;
156     WORD handle;
157     WORD usage;
158 } NE_NAMEINFO;
159
160 typedef struct
161 {
162     WORD  type_id;
163     WORD  count;
164     DWORD resloader;
165 } NE_TYPEINFO;
166
167 #define NE_RSCTYPE_ICON        0x8003
168 #define NE_RSCTYPE_GROUP_ICON  0x800e
169
170 #include "poppack.h"
171
172 typedef struct
173 {
174         HRSRC *pResInfo;
175         int   nIndex;
176 } ENUMRESSTRUCT;
177
178 struct xdg_mime_type
179 {
180     char *mimeType;
181     char *glob;
182     struct list entry;
183 };
184
185 struct rb_string_entry
186 {
187     char *string;
188     struct wine_rb_entry entry;
189 };
190
191 DEFINE_GUID(CLSID_WICIcnsEncoder, 0x312fb6f1,0xb767,0x409d,0x8a,0x6d,0x0f,0xc1,0x54,0xd4,0xf0,0x5c);
192
193 static char *xdg_config_dir;
194 static char *xdg_data_dir;
195 static char *xdg_desktop_dir;
196
197 static WCHAR* assoc_query(ASSOCSTR assocStr, LPCWSTR name, LPCWSTR extra);
198 static HRESULT open_icon(LPCWSTR filename, int index, BOOL bWait, IStream **ppStream);
199
200 /* Utility routines */
201 static unsigned short crc16(const char* string)
202 {
203     unsigned short crc = 0;
204     int i, j, xor_poly;
205
206     for (i = 0; string[i] != 0; i++)
207     {
208         char c = string[i];
209         for (j = 0; j < 8; c >>= 1, j++)
210         {
211             xor_poly = (c ^ crc) & 1;
212             crc >>= 1;
213             if (xor_poly)
214                 crc ^= 0xa001;
215         }
216     }
217     return crc;
218 }
219
220 static char *strdupA( const char *str )
221 {
222     char *ret;
223
224     if (!str) return NULL;
225     if ((ret = HeapAlloc( GetProcessHeap(), 0, strlen(str) + 1 ))) strcpy( ret, str );
226     return ret;
227 }
228
229 static char* heap_printf(const char *format, ...)
230 {
231     va_list args;
232     int size = 4096;
233     char *buffer, *ret;
234     int n;
235
236     va_start(args, format);
237     while (1)
238     {
239         buffer = HeapAlloc(GetProcessHeap(), 0, size);
240         if (buffer == NULL)
241             break;
242         n = vsnprintf(buffer, size, format, args);
243         if (n == -1)
244             size *= 2;
245         else if (n >= size)
246             size = n + 1;
247         else
248             break;
249         HeapFree(GetProcessHeap(), 0, buffer);
250     }
251     va_end(args);
252     if (!buffer) return NULL;
253     ret = HeapReAlloc(GetProcessHeap(), 0, buffer, strlen(buffer) + 1 );
254     if (!ret) ret = buffer;
255     return ret;
256 }
257
258 static int winemenubuilder_rb_string_compare(const void *key, const struct wine_rb_entry *entry)
259 {
260     const struct rb_string_entry *t = WINE_RB_ENTRY_VALUE(entry, const struct rb_string_entry, entry);
261
262     return strcmp((char*)key, t->string);
263 }
264
265 static void *winemenubuilder_rb_alloc(size_t size)
266 {
267     return HeapAlloc(GetProcessHeap(), 0, size);
268 }
269
270 static void *winemenubuilder_rb_realloc(void *ptr, size_t size)
271 {
272     return HeapReAlloc(GetProcessHeap(), 0, ptr, size);
273 }
274
275 static void winemenubuilder_rb_free(void *ptr)
276 {
277     HeapFree(GetProcessHeap(), 0, ptr);
278 }
279
280 static void winemenubuilder_rb_destroy(struct wine_rb_entry *entry, void *context)
281 {
282     struct rb_string_entry *t = WINE_RB_ENTRY_VALUE(entry, struct rb_string_entry, entry);
283     HeapFree(GetProcessHeap(), 0, t->string);
284     HeapFree(GetProcessHeap(), 0, t);
285 }
286
287 static const struct wine_rb_functions winemenubuilder_rb_functions =
288 {
289     winemenubuilder_rb_alloc,
290     winemenubuilder_rb_realloc,
291     winemenubuilder_rb_free,
292     winemenubuilder_rb_string_compare,
293 };
294
295 static void write_xml_text(FILE *file, const char *text)
296 {
297     int i;
298     for (i = 0; text[i]; i++)
299     {
300         if (text[i] == '&')
301             fputs("&amp;", file);
302         else if (text[i] == '<')
303             fputs("&lt;", file);
304         else if (text[i] == '>')
305             fputs("&gt;", file);
306         else if (text[i] == '\'')
307             fputs("&apos;", file);
308         else if (text[i] == '"')
309             fputs("&quot;", file);
310         else
311             fputc(text[i], file);
312     }
313 }
314
315 static BOOL create_directories(char *directory)
316 {
317     BOOL ret = TRUE;
318     int i;
319
320     for (i = 0; directory[i]; i++)
321     {
322         if (i > 0 && directory[i] == '/')
323         {
324             directory[i] = 0;
325             mkdir(directory, 0777);
326             directory[i] = '/';
327         }
328     }
329     if (mkdir(directory, 0777) && errno != EEXIST)
330        ret = FALSE;
331
332     return ret;
333 }
334
335 static char* wchars_to_utf8_chars(LPCWSTR string)
336 {
337     char *ret;
338     INT size = WideCharToMultiByte(CP_UTF8, 0, string, -1, NULL, 0, NULL, NULL);
339     ret = HeapAlloc(GetProcessHeap(), 0, size);
340     if (ret)
341         WideCharToMultiByte(CP_UTF8, 0, string, -1, ret, size, NULL, NULL);
342     return ret;
343 }
344
345 static char* wchars_to_unix_chars(LPCWSTR string)
346 {
347     char *ret;
348     INT size = WideCharToMultiByte(CP_UNIXCP, 0, string, -1, NULL, 0, NULL, NULL);
349     ret = HeapAlloc(GetProcessHeap(), 0, size);
350     if (ret)
351         WideCharToMultiByte(CP_UNIXCP, 0, string, -1, ret, size, NULL, NULL);
352     return ret;
353 }
354
355 static WCHAR* utf8_chars_to_wchars(LPCSTR string)
356 {
357     WCHAR *ret;
358     INT size = MultiByteToWideChar(CP_UTF8, 0, string, -1, NULL, 0);
359     ret = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
360     if (ret)
361         MultiByteToWideChar(CP_UTF8, 0, string, -1, ret, size);
362     return ret;
363 }
364
365 /* Icon extraction routines
366  *
367  * FIXME: should use PrivateExtractIcons and friends
368  * FIXME: should not use stdio
369  */
370
371 static HRESULT convert_to_native_icon(IStream *icoFile, int *indeces, int numIndeces,
372                                       const CLSID *outputFormat, const char *outputFileName, LPCWSTR commentW)
373 {
374     WCHAR *dosOutputFileName = NULL;
375     IWICImagingFactory *factory = NULL;
376     IWICBitmapDecoder *decoder = NULL;
377     IWICBitmapEncoder *encoder = NULL;
378     IStream *outputFile = NULL;
379     int i;
380     HRESULT hr = E_FAIL;
381
382     dosOutputFileName = wine_get_dos_file_name(outputFileName);
383     if (dosOutputFileName == NULL)
384     {
385         WINE_ERR("error converting %s to DOS file name\n", outputFileName);
386         goto end;
387     }
388     hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
389         &IID_IWICImagingFactory, (void**)&factory);
390     if (FAILED(hr))
391     {
392         WINE_ERR("error 0x%08X creating IWICImagingFactory\n", hr);
393         goto end;
394     }
395     hr = IWICImagingFactory_CreateDecoderFromStream(factory, icoFile, NULL,
396         WICDecodeMetadataCacheOnDemand, &decoder);
397     if (FAILED(hr))
398     {
399         WINE_ERR("error 0x%08X creating IWICBitmapDecoder\n", hr);
400         goto end;
401     }
402     hr = CoCreateInstance(outputFormat, NULL, CLSCTX_INPROC_SERVER,
403         &IID_IWICBitmapEncoder, (void**)&encoder);
404     if (FAILED(hr))
405     {
406         WINE_ERR("error 0x%08X creating bitmap encoder\n", hr);
407         goto end;
408     }
409     hr = SHCreateStreamOnFileW(dosOutputFileName, STGM_CREATE | STGM_WRITE, &outputFile);
410     if (FAILED(hr))
411     {
412         WINE_ERR("error 0x%08X creating output file %s\n", hr, wine_dbgstr_w(dosOutputFileName));
413         goto end;
414     }
415     hr = IWICBitmapEncoder_Initialize(encoder, outputFile, GENERIC_WRITE);
416     if (FAILED(hr))
417     {
418         WINE_ERR("error 0x%08X initializing encoder\n", hr);
419         goto end;
420     }
421
422     for (i = 0; i < numIndeces; i++)
423     {
424         IWICBitmapFrameDecode *sourceFrame = NULL;
425         IWICBitmapSource *sourceBitmap = NULL;
426         IWICBitmapFrameEncode *dstFrame = NULL;
427         IPropertyBag2 *options = NULL;
428         UINT width, height;
429
430         hr = IWICBitmapDecoder_GetFrame(decoder, indeces[i], &sourceFrame);
431         if (FAILED(hr))
432         {
433             WINE_ERR("error 0x%08X getting frame %d\n", hr, indeces[i]);
434             goto endloop;
435         }
436         hr = WICConvertBitmapSource(&GUID_WICPixelFormat32bppBGRA, (IWICBitmapSource*)sourceFrame, &sourceBitmap);
437         if (FAILED(hr))
438         {
439             WINE_ERR("error 0x%08X converting bitmap to 32bppBGRA\n", hr);
440             goto endloop;
441         }
442         hr = IWICBitmapEncoder_CreateNewFrame(encoder, &dstFrame, &options);
443         if (FAILED(hr))
444         {
445             WINE_ERR("error 0x%08X creating encoder frame\n", hr);
446             goto endloop;
447         }
448         hr = IWICBitmapFrameEncode_Initialize(dstFrame, options);
449         if (FAILED(hr))
450         {
451             WINE_ERR("error 0x%08X initializing encoder frame\n", hr);
452             goto endloop;
453         }
454         hr = IWICBitmapSource_GetSize(sourceBitmap, &width, &height);
455         if (FAILED(hr))
456         {
457             WINE_ERR("error 0x%08X getting source bitmap size\n", hr);
458             goto endloop;
459         }
460         hr = IWICBitmapFrameEncode_SetSize(dstFrame, width, height);
461         if (FAILED(hr))
462         {
463             WINE_ERR("error 0x%08X setting destination bitmap size\n", hr);
464             goto endloop;
465         }
466         hr = IWICBitmapFrameEncode_SetResolution(dstFrame, 96, 96);
467         if (FAILED(hr))
468         {
469             WINE_ERR("error 0x%08X setting destination bitmap resolution\n", hr);
470             goto endloop;
471         }
472         hr = IWICBitmapFrameEncode_WriteSource(dstFrame, sourceBitmap, NULL);
473         if (FAILED(hr))
474         {
475             WINE_ERR("error 0x%08X copying bitmaps\n", hr);
476             goto endloop;
477         }
478         hr = IWICBitmapFrameEncode_Commit(dstFrame);
479         if (FAILED(hr))
480         {
481             WINE_ERR("error 0x%08X committing frame\n", hr);
482             goto endloop;
483         }
484     endloop:
485         if (sourceFrame)
486             IWICBitmapFrameDecode_Release(sourceFrame);
487         if (sourceBitmap)
488             IWICBitmapSource_Release(sourceBitmap);
489         if (dstFrame)
490             IWICBitmapFrameEncode_Release(dstFrame);
491     }
492
493     hr = IWICBitmapEncoder_Commit(encoder);
494     if (FAILED(hr))
495     {
496         WINE_ERR("error 0x%08X committing encoder\n", hr);
497         goto end;
498     }
499
500 end:
501     HeapFree(GetProcessHeap(), 0, dosOutputFileName);
502     if (factory)
503         IWICImagingFactory_Release(factory);
504     if (decoder)
505         IWICBitmapDecoder_Release(decoder);
506     if (encoder)
507         IWICBitmapEncoder_Release(encoder);
508     if (outputFile)
509         IStream_Release(outputFile);
510     return hr;
511 }
512
513 struct IconData16 {
514     BYTE *fileBytes;
515     DWORD fileSize;
516     NE_TYPEINFO *iconResources;
517     WORD alignmentShiftCount;
518 };
519
520 static int populate_module16_icons(struct IconData16 *iconData16, GRPICONDIR *grpIconDir, ICONDIRENTRY *iconDirEntries, BYTE *icons, SIZE_T *iconOffset)
521 {
522     int i, j;
523     int validEntries = 0;
524
525     for (i = 0; i < grpIconDir->idCount; i++)
526     {
527         BYTE *iconPtr = (BYTE*)iconData16->iconResources;
528         NE_NAMEINFO *matchingIcon = NULL;
529         iconPtr += sizeof(NE_TYPEINFO);
530         for (j = 0; j < iconData16->iconResources->count; j++)
531         {
532             NE_NAMEINFO *iconInfo = (NE_NAMEINFO*)iconPtr;
533             if ((((BYTE*)iconPtr) + sizeof(NE_NAMEINFO)) > (iconData16->fileBytes + iconData16->fileSize))
534             {
535                 WINE_WARN("file too small for icon NE_NAMEINFO\n");
536                 break;
537             }
538             if (iconInfo->id == (0x8000 | grpIconDir->idEntries[i].nID))
539             {
540                 matchingIcon = iconInfo;
541                 break;
542             }
543             iconPtr += sizeof(NE_NAMEINFO);
544         }
545
546         if (matchingIcon == NULL)
547             continue;
548         if (((matchingIcon->offset << iconData16->alignmentShiftCount) + grpIconDir->idEntries[i].dwBytesInRes) > iconData16->fileSize)
549         {
550             WINE_WARN("file too small for icon contents\n");
551             break;
552         }
553
554         iconDirEntries[validEntries].bWidth = grpIconDir->idEntries[i].bWidth;
555         iconDirEntries[validEntries].bHeight = grpIconDir->idEntries[i].bHeight;
556         iconDirEntries[validEntries].bColorCount = grpIconDir->idEntries[i].bColorCount;
557         iconDirEntries[validEntries].bReserved = grpIconDir->idEntries[i].bReserved;
558         iconDirEntries[validEntries].wPlanes = grpIconDir->idEntries[i].wPlanes;
559         iconDirEntries[validEntries].wBitCount = grpIconDir->idEntries[i].wBitCount;
560         iconDirEntries[validEntries].dwBytesInRes = grpIconDir->idEntries[i].dwBytesInRes;
561         iconDirEntries[validEntries].dwImageOffset = *iconOffset;
562         validEntries++;
563         memcpy(&icons[*iconOffset], &iconData16->fileBytes[matchingIcon->offset << iconData16->alignmentShiftCount], grpIconDir->idEntries[i].dwBytesInRes);
564         *iconOffset += grpIconDir->idEntries[i].dwBytesInRes;
565     }
566     return validEntries;
567 }
568
569 static int populate_module_icons(HMODULE hModule, GRPICONDIR *grpIconDir, ICONDIRENTRY *iconDirEntries, BYTE *icons, SIZE_T *iconOffset)
570 {
571     int i;
572     int validEntries = 0;
573
574     for (i = 0; i < grpIconDir->idCount; i++)
575     {
576         HRSRC hResInfo;
577         LPCWSTR lpName = MAKEINTRESOURCEW(grpIconDir->idEntries[i].nID);
578         if ((hResInfo = FindResourceW(hModule, lpName, (LPCWSTR)RT_ICON)))
579         {
580             HGLOBAL hResData;
581             if ((hResData = LoadResource(hModule, hResInfo)))
582             {
583                 BITMAPINFO *pIcon;
584                 if ((pIcon = LockResource(hResData)))
585                 {
586                     iconDirEntries[validEntries].bWidth = grpIconDir->idEntries[i].bWidth;
587                     iconDirEntries[validEntries].bHeight = grpIconDir->idEntries[i].bHeight;
588                     iconDirEntries[validEntries].bColorCount = grpIconDir->idEntries[i].bColorCount;
589                     iconDirEntries[validEntries].bReserved = grpIconDir->idEntries[i].bReserved;
590                     iconDirEntries[validEntries].wPlanes = grpIconDir->idEntries[i].wPlanes;
591                     iconDirEntries[validEntries].wBitCount = grpIconDir->idEntries[i].wBitCount;
592                     iconDirEntries[validEntries].dwBytesInRes = grpIconDir->idEntries[i].dwBytesInRes;
593                     iconDirEntries[validEntries].dwImageOffset = *iconOffset;
594                     validEntries++;
595                     memcpy(&icons[*iconOffset], pIcon, grpIconDir->idEntries[i].dwBytesInRes);
596                     *iconOffset += grpIconDir->idEntries[i].dwBytesInRes;
597                 }
598                 FreeResource(hResData);
599             }
600         }
601     }
602     return validEntries;
603 }
604
605 static IStream *add_module_icons_to_stream(struct IconData16 *iconData16, HMODULE hModule, GRPICONDIR *grpIconDir)
606 {
607     int i;
608     SIZE_T iconsSize = 0;
609     BYTE *icons = NULL;
610     ICONDIRENTRY *iconDirEntries = NULL;
611     IStream *stream = NULL;
612     HRESULT hr = E_FAIL;
613     ULONG bytesWritten;
614     ICONDIR iconDir;
615     SIZE_T iconOffset;
616     int validEntries = 0;
617     LARGE_INTEGER zero;
618
619     for (i = 0; i < grpIconDir->idCount; i++)
620         iconsSize += grpIconDir->idEntries[i].dwBytesInRes;
621     icons = HeapAlloc(GetProcessHeap(), 0, iconsSize);
622     if (icons == NULL)
623     {
624         WINE_ERR("out of memory allocating icon\n");
625         goto end;
626     }
627
628     iconDirEntries = HeapAlloc(GetProcessHeap(), 0, grpIconDir->idCount*sizeof(ICONDIRENTRY));
629     if (iconDirEntries == NULL)
630     {
631         WINE_ERR("out of memory allocating icon dir entries\n");
632         goto end;
633     }
634
635     hr = CreateStreamOnHGlobal(NULL, TRUE, &stream);
636     if (FAILED(hr))
637     {
638         WINE_ERR("error creating icon stream\n");
639         goto end;
640     }
641
642     iconOffset = 0;
643     if (iconData16)
644         validEntries = populate_module16_icons(iconData16, grpIconDir, iconDirEntries, icons, &iconOffset);
645     else if (hModule)
646         validEntries = populate_module_icons(hModule, grpIconDir, iconDirEntries, icons, &iconOffset);
647
648     if (validEntries == 0)
649     {
650         WINE_ERR("no valid icon entries\n");
651         goto end;
652     }
653
654     iconDir.idReserved = 0;
655     iconDir.idType = 1;
656     iconDir.idCount = validEntries;
657     hr = IStream_Write(stream, &iconDir, sizeof(iconDir), &bytesWritten);
658     if (FAILED(hr) || bytesWritten != sizeof(iconDir))
659     {
660         WINE_ERR("error 0x%08X writing icon stream\n", hr);
661         goto end;
662     }
663     for (i = 0; i < validEntries; i++)
664         iconDirEntries[i].dwImageOffset += sizeof(ICONDIR) + validEntries*sizeof(ICONDIRENTRY);
665     hr = IStream_Write(stream, iconDirEntries, validEntries*sizeof(ICONDIRENTRY), &bytesWritten);
666     if (FAILED(hr) || bytesWritten != validEntries*sizeof(ICONDIRENTRY))
667     {
668         WINE_ERR("error 0x%08X writing icon dir entries to stream\n", hr);
669         goto end;
670     }
671     hr = IStream_Write(stream, icons, iconOffset, &bytesWritten);
672     if (FAILED(hr) || bytesWritten != iconOffset)
673     {
674         WINE_ERR("error 0x%08X writing icon images to stream\n", hr);
675         goto end;
676     }
677     zero.QuadPart = 0;
678     hr = IStream_Seek(stream, zero, STREAM_SEEK_SET, NULL);
679
680 end:
681     HeapFree(GetProcessHeap(), 0, icons);
682     HeapFree(GetProcessHeap(), 0, iconDirEntries);
683     if (FAILED(hr) && stream != NULL)
684     {
685         IStream_Release(stream);
686         stream = NULL;
687     }
688     return stream;
689 }
690
691 static HRESULT open_module16_icon(LPCWSTR szFileName, int nIndex, IStream **ppStream)
692 {
693     HANDLE hFile = INVALID_HANDLE_VALUE;
694     HANDLE hFileMapping = NULL;
695     DWORD fileSize;
696     BYTE *fileBytes = NULL;
697     IMAGE_DOS_HEADER *dosHeader;
698     IMAGE_OS2_HEADER *neHeader;
699     BYTE *rsrcTab;
700     NE_TYPEINFO *iconGroupResources;
701     NE_TYPEINFO *iconResources;
702     NE_NAMEINFO *iconDirPtr;
703     GRPICONDIR *iconDir;
704     WORD alignmentShiftCount;
705     struct IconData16 iconData16;
706     HRESULT hr = E_FAIL;
707
708     hFile = CreateFileW(szFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
709         OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);
710     if (hFile == INVALID_HANDLE_VALUE)
711     {
712         WINE_WARN("opening %s failed with error %d\n", wine_dbgstr_w(szFileName), GetLastError());
713         goto end;
714     }
715
716     hFileMapping = CreateFileMappingW(hFile, NULL, PAGE_READONLY | SEC_COMMIT, 0, 0, NULL);
717     if (hFileMapping == NULL)
718     {
719         WINE_WARN("CreateFileMapping failed, error %d\n", GetLastError());
720         goto end;
721     }
722
723     fileSize = GetFileSize(hFile, NULL);
724
725     fileBytes = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
726     if (fileBytes == NULL)
727     {
728         WINE_WARN("MapViewOfFile failed, error %d\n", GetLastError());
729         goto end;
730     }
731
732     dosHeader = (IMAGE_DOS_HEADER*)fileBytes;
733     if (sizeof(IMAGE_DOS_HEADER) >= fileSize || dosHeader->e_magic != IMAGE_DOS_SIGNATURE)
734     {
735         WINE_WARN("file too small for MZ header\n");
736         goto end;
737     }
738
739     neHeader = (IMAGE_OS2_HEADER*)(fileBytes + dosHeader->e_lfanew);
740     if ((((BYTE*)neHeader) + sizeof(IMAGE_OS2_HEADER)) > (fileBytes + fileSize) ||
741         neHeader->ne_magic != IMAGE_OS2_SIGNATURE)
742     {
743         WINE_WARN("file too small for NE header\n");
744         goto end;
745     }
746
747     rsrcTab = ((BYTE*)neHeader) + neHeader->ne_rsrctab;
748     if ((rsrcTab + 2) > (fileBytes + fileSize))
749     {
750         WINE_WARN("file too small for resource table\n");
751         goto end;
752     }
753
754     alignmentShiftCount = *(WORD*)rsrcTab;
755     rsrcTab += 2;
756     iconGroupResources = NULL;
757     iconResources = NULL;
758     for (;;)
759     {
760         NE_TYPEINFO *neTypeInfo = (NE_TYPEINFO*)rsrcTab;
761         if ((rsrcTab + sizeof(NE_TYPEINFO)) > (fileBytes + fileSize))
762         {
763             WINE_WARN("file too small for resource table\n");
764             goto end;
765         }
766         if (neTypeInfo->type_id == 0)
767             break;
768         else if (neTypeInfo->type_id == NE_RSCTYPE_GROUP_ICON)
769             iconGroupResources = neTypeInfo;
770         else if (neTypeInfo->type_id == NE_RSCTYPE_ICON)
771             iconResources = neTypeInfo;
772         rsrcTab += sizeof(NE_TYPEINFO) + neTypeInfo->count*sizeof(NE_NAMEINFO);
773     }
774     if (iconGroupResources == NULL)
775     {
776         WINE_WARN("no group icon resource type found\n");
777         goto end;
778     }
779     if (iconResources == NULL)
780     {
781         WINE_WARN("no icon resource type found\n");
782         goto end;
783     }
784
785     if (nIndex >= iconGroupResources->count)
786     {
787         WINE_WARN("icon index out of range\n");
788         goto end;
789     }
790
791     iconDirPtr = (NE_NAMEINFO*)(((BYTE*)iconGroupResources) + sizeof(NE_TYPEINFO) + nIndex*sizeof(NE_NAMEINFO));
792     if ((((BYTE*)iconDirPtr) + sizeof(NE_NAMEINFO)) > (fileBytes + fileSize))
793     {
794         WINE_WARN("file to small for icon group NE_NAMEINFO\n");
795         goto end;
796     }
797     iconDir = (GRPICONDIR*)(fileBytes + (iconDirPtr->offset << alignmentShiftCount));
798     if ((((BYTE*)iconDir) + sizeof(GRPICONDIR) + iconDir->idCount*sizeof(GRPICONDIRENTRY)) > (fileBytes + fileSize))
799     {
800         WINE_WARN("file too small for GRPICONDIR\n");
801         goto end;
802     }
803
804     iconData16.fileBytes = fileBytes;
805     iconData16.fileSize = fileSize;
806     iconData16.iconResources = iconResources;
807     iconData16.alignmentShiftCount = alignmentShiftCount;
808     *ppStream = add_module_icons_to_stream(&iconData16, NULL, iconDir);
809     if (*ppStream)
810         hr = S_OK;
811
812 end:
813     if (hFile != INVALID_HANDLE_VALUE)
814         CloseHandle(hFile);
815     if (hFileMapping != NULL)
816         CloseHandle(hFileMapping);
817     if (fileBytes != NULL)
818         UnmapViewOfFile(fileBytes);
819     return hr;
820 }
821
822 static BOOL CALLBACK EnumResNameProc(HMODULE hModule, LPCWSTR lpszType, LPWSTR lpszName, LONG_PTR lParam)
823 {
824     ENUMRESSTRUCT *sEnumRes = (ENUMRESSTRUCT *) lParam;
825
826     if (!sEnumRes->nIndex--)
827     {
828         *sEnumRes->pResInfo = FindResourceW(hModule, lpszName, (LPCWSTR)RT_GROUP_ICON);
829         return FALSE;
830     }
831     else
832         return TRUE;
833 }
834
835 static HRESULT open_module_icon(LPCWSTR szFileName, int nIndex, IStream **ppStream)
836 {
837     HMODULE hModule;
838     HRSRC hResInfo;
839     HGLOBAL hResData;
840     GRPICONDIR *pIconDir;
841     ENUMRESSTRUCT sEnumRes;
842     HRESULT hr = E_FAIL;
843
844     hModule = LoadLibraryExW(szFileName, 0, LOAD_LIBRARY_AS_DATAFILE);
845     if (!hModule)
846     {
847         if (GetLastError() == ERROR_BAD_EXE_FORMAT)
848             return open_module16_icon(szFileName, nIndex, ppStream);
849         else
850         {
851             WINE_WARN("LoadLibraryExW (%s) failed, error %d\n",
852                      wine_dbgstr_w(szFileName), GetLastError());
853             return HRESULT_FROM_WIN32(GetLastError());
854         }
855     }
856
857     if (nIndex < 0)
858     {
859         hResInfo = FindResourceW(hModule, MAKEINTRESOURCEW(-nIndex), (LPCWSTR)RT_GROUP_ICON);
860         WINE_TRACE("FindResourceW (%s) called, return %p, error %d\n",
861                    wine_dbgstr_w(szFileName), hResInfo, GetLastError());
862     }
863     else
864     {
865         hResInfo=NULL;
866         sEnumRes.pResInfo = &hResInfo;
867         sEnumRes.nIndex = nIndex;
868         if (!EnumResourceNamesW(hModule, (LPCWSTR)RT_GROUP_ICON,
869                                 EnumResNameProc, (LONG_PTR)&sEnumRes) &&
870             sEnumRes.nIndex != -1)
871         {
872             WINE_TRACE("EnumResourceNamesW failed, error %d\n", GetLastError());
873         }
874     }
875
876     if (hResInfo)
877     {
878         if ((hResData = LoadResource(hModule, hResInfo)))
879         {
880             if ((pIconDir = LockResource(hResData)))
881             {
882                 *ppStream = add_module_icons_to_stream(0, hModule, pIconDir);
883                 if (*ppStream)
884                     hr = S_OK;
885             }
886
887             FreeResource(hResData);
888         }
889     }
890     else
891     {
892         WINE_WARN("found no icon\n");
893         FreeLibrary(hModule);
894         return HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
895     }
896
897     FreeLibrary(hModule);
898     return hr;
899 }
900
901 static HRESULT read_ico_direntries(IStream *icoStream, ICONDIRENTRY **ppIconDirEntries, int *numEntries)
902 {
903     ICONDIR iconDir;
904     ULONG bytesRead;
905     HRESULT hr;
906
907     *ppIconDirEntries = NULL;
908
909     hr = IStream_Read(icoStream, &iconDir, sizeof(ICONDIR), &bytesRead);
910     if (FAILED(hr) || bytesRead != sizeof(ICONDIR) ||
911         (iconDir.idReserved != 0) || (iconDir.idType != 1))
912     {
913         WINE_WARN("Invalid ico file format (hr=0x%08X, bytesRead=%d)\n", hr, bytesRead);
914         hr = E_FAIL;
915         goto end;
916     }
917     *numEntries = iconDir.idCount;
918
919     if ((*ppIconDirEntries = HeapAlloc(GetProcessHeap(), 0, sizeof(ICONDIRENTRY)*iconDir.idCount)) == NULL)
920     {
921         hr = E_OUTOFMEMORY;
922         goto end;
923     }
924     hr = IStream_Read(icoStream, *ppIconDirEntries, sizeof(ICONDIRENTRY)*iconDir.idCount, &bytesRead);
925     if (FAILED(hr) || bytesRead != sizeof(ICONDIRENTRY)*iconDir.idCount)
926     {
927         if (SUCCEEDED(hr)) hr = E_FAIL;
928         goto end;
929     }
930
931 end:
932     if (FAILED(hr))
933         HeapFree(GetProcessHeap(), 0, *ppIconDirEntries);
934     return hr;
935 }
936
937 static HRESULT write_native_icon(IStream *iconStream, const char *icon_name, LPCWSTR szFileName)
938 {
939     ICONDIRENTRY *pIconDirEntry = NULL;
940     int numEntries;
941     int nMax = 0, nMaxBits = 0;
942     int nIndex = 0;
943     int i;
944     LARGE_INTEGER position;
945     HRESULT hr;
946
947     hr = read_ico_direntries(iconStream, &pIconDirEntry, &numEntries);
948     if (FAILED(hr))
949         goto end;
950
951     for (i = 0; i < numEntries; i++)
952     {
953         WINE_TRACE("[%d]: %d x %d @ %d\n", i, pIconDirEntry[i].bWidth, pIconDirEntry[i].bHeight, pIconDirEntry[i].wBitCount);
954         if (pIconDirEntry[i].wBitCount >= nMaxBits &&
955             (pIconDirEntry[i].bHeight * pIconDirEntry[i].bWidth) >= nMax)
956         {
957             nIndex = i;
958             nMax = pIconDirEntry[i].bHeight * pIconDirEntry[i].bWidth;
959             nMaxBits = pIconDirEntry[i].wBitCount;
960         }
961     }
962     WINE_TRACE("Selected: %d\n", nIndex);
963
964     position.QuadPart = 0;
965     hr = IStream_Seek(iconStream, position, STREAM_SEEK_SET, NULL);
966     if (FAILED(hr))
967         goto end;
968     hr = convert_to_native_icon(iconStream, &nIndex, 1, &CLSID_WICPngEncoder, icon_name, szFileName);
969
970 end:
971     HeapFree(GetProcessHeap(), 0, pIconDirEntry);
972     return hr;
973 }
974
975 static HRESULT open_file_type_icon(LPCWSTR szFileName, IStream **ppStream)
976 {
977     static const WCHAR openW[] = {'o','p','e','n',0};
978     WCHAR *extension;
979     WCHAR *icon = NULL;
980     WCHAR *comma;
981     WCHAR *executable = NULL;
982     int index = 0;
983     char *output_path = NULL;
984     HRESULT hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
985
986     extension = strrchrW(szFileName, '.');
987     if (extension == NULL)
988         goto end;
989
990     icon = assoc_query(ASSOCSTR_DEFAULTICON, extension, NULL);
991     if (icon)
992     {
993         comma = strrchrW(icon, ',');
994         if (comma)
995         {
996             *comma = 0;
997             index = atoiW(comma + 1);
998         }
999         hr = open_icon(icon, index, FALSE, ppStream);
1000     }
1001     else
1002     {
1003         executable = assoc_query(ASSOCSTR_EXECUTABLE, extension, openW);
1004         if (executable)
1005             hr = open_icon(executable, 0, FALSE, ppStream);
1006     }
1007
1008 end:
1009     HeapFree(GetProcessHeap(), 0, icon);
1010     HeapFree(GetProcessHeap(), 0, executable);
1011     HeapFree(GetProcessHeap(), 0, output_path);
1012     return hr;
1013 }
1014
1015 static HRESULT open_default_icon(IStream **ppStream)
1016 {
1017     static const WCHAR user32W[] = {'u','s','e','r','3','2',0};
1018
1019     return open_module_icon(user32W, -(INT_PTR)IDI_WINLOGO, ppStream);
1020 }
1021
1022 static HRESULT open_icon(LPCWSTR filename, int index, BOOL bWait, IStream **ppStream)
1023 {
1024     HRESULT hr;
1025
1026     hr = open_module_icon(filename, index, ppStream);
1027     if (FAILED(hr))
1028     {
1029         static const WCHAR dot_icoW[] = {'.','i','c','o',0};
1030         int len = strlenW(filename);
1031         if (len >= 4 && strcmpiW(&filename[len - 4], dot_icoW) == 0)
1032             hr = SHCreateStreamOnFileW(filename, STGM_READ, ppStream);
1033     }
1034     if (FAILED(hr))
1035         hr = open_file_type_icon(filename, ppStream);
1036     if (FAILED(hr) && !bWait)
1037         hr = open_default_icon(ppStream);
1038     return hr;
1039 }
1040
1041 #ifdef __APPLE__
1042 #define ICNS_SLOTS 6
1043
1044 static inline int size_to_slot(int size)
1045 {
1046     switch (size)
1047     {
1048         case 16: return 0;
1049         case 32: return 1;
1050         case 48: return 2;
1051         case 128: return 3;
1052         case 256: return 4;
1053         case 512: return 5;
1054     }
1055
1056     return -1;
1057 }
1058
1059 static HRESULT platform_write_icon(IStream *icoStream, int exeIndex, LPCWSTR icoPathW,
1060                                    const char *destFilename, char **nativeIdentifier)
1061 {
1062     ICONDIRENTRY *iconDirEntries = NULL;
1063     int numEntries;
1064     struct {
1065         int index;
1066         int maxBits;
1067     } best[ICNS_SLOTS];
1068     int indexes[ICNS_SLOTS];
1069     int i;
1070     GUID guid;
1071     WCHAR *guidStrW = NULL;
1072     char *guidStrA = NULL;
1073     char *icnsPath = NULL;
1074     LARGE_INTEGER zero;
1075     HRESULT hr;
1076
1077     hr = read_ico_direntries(icoStream, &iconDirEntries, &numEntries);
1078     if (FAILED(hr))
1079         goto end;
1080     for (i = 0; i < ICNS_SLOTS; i++)
1081     {
1082         best[i].index = -1;
1083         best[i].maxBits = 0;
1084     }
1085     for (i = 0; i < numEntries; i++)
1086     {
1087         int slot;
1088         int width = iconDirEntries[i].bWidth ? iconDirEntries[i].bWidth : 256;
1089         int height = iconDirEntries[i].bHeight ? iconDirEntries[i].bHeight : 256;
1090
1091         WINE_TRACE("[%d]: %d x %d @ %d\n", i, width, height, iconDirEntries[i].wBitCount);
1092         if (height != width)
1093             continue;
1094         slot = size_to_slot(width);
1095         if (slot < 0)
1096             continue;
1097         if (iconDirEntries[i].wBitCount >= best[slot].maxBits)
1098         {
1099             best[slot].index = i;
1100             best[slot].maxBits = iconDirEntries[i].wBitCount;
1101         }
1102     }
1103     numEntries = 0;
1104     for (i = 0; i < ICNS_SLOTS; i++)
1105     {
1106         if (best[i].index >= 0)
1107         {
1108             indexes[numEntries] = best[i].index;
1109             numEntries++;
1110         }
1111     }
1112
1113     hr = CoCreateGuid(&guid);
1114     if (FAILED(hr))
1115     {
1116         WINE_WARN("CoCreateGuid failed, error 0x%08X\n", hr);
1117         goto end;
1118     }
1119     hr = StringFromCLSID(&guid, &guidStrW);
1120     if (FAILED(hr))
1121     {
1122         WINE_WARN("StringFromCLSID failed, error 0x%08X\n", hr);
1123         goto end;
1124     }
1125     guidStrA = wchars_to_utf8_chars(guidStrW);
1126     if (guidStrA == NULL)
1127     {
1128         hr = E_OUTOFMEMORY;
1129         WINE_WARN("out of memory converting GUID string\n");
1130         goto end;
1131     }
1132     icnsPath = heap_printf("/tmp/%s.icns", guidStrA);
1133     if (icnsPath == NULL)
1134     {
1135         hr = E_OUTOFMEMORY;
1136         WINE_WARN("out of memory creating ICNS path\n");
1137         goto end;
1138     }
1139     zero.QuadPart = 0;
1140     hr = IStream_Seek(icoStream, zero, STREAM_SEEK_SET, NULL);
1141     if (FAILED(hr))
1142     {
1143         WINE_WARN("seeking icon stream failed, error 0x%08X\n", hr);
1144         goto end;
1145     }
1146     hr = convert_to_native_icon(icoStream, indexes, numEntries, &CLSID_WICIcnsEncoder,
1147                                 icnsPath, icoPathW);
1148     if (FAILED(hr))
1149     {
1150         WINE_WARN("converting %s to %s failed, error 0x%08X\n",
1151             wine_dbgstr_w(icoPathW), wine_dbgstr_a(icnsPath), hr);
1152         goto end;
1153     }
1154
1155 end:
1156     HeapFree(GetProcessHeap(), 0, iconDirEntries);
1157     CoTaskMemFree(guidStrW);
1158     HeapFree(GetProcessHeap(), 0, guidStrA);
1159     if (SUCCEEDED(hr))
1160         *nativeIdentifier = icnsPath;
1161     else
1162         HeapFree(GetProcessHeap(), 0, icnsPath);
1163     return hr;
1164 }
1165 #else
1166 static void refresh_icon_cache(const char *iconsDir)
1167 {
1168     /* The icon theme spec only requires the mtime on the "toplevel"
1169      * directory (whatever that is) to be changed for a refresh,
1170      * but on Gnome you have to create a file in that directory
1171      * instead. Creating a file also works on KDE, XFCE and LXDE.
1172      */
1173     char *filename = heap_printf("%s/.wine-refresh-XXXXXX", iconsDir);
1174     if (filename != NULL)
1175     {
1176         int fd = mkstemps(filename, 0);
1177         if (fd >= 0)
1178         {
1179             close(fd);
1180             unlink(filename);
1181         }
1182         HeapFree(GetProcessHeap(), 0, filename);
1183     }
1184 }
1185
1186 static HRESULT platform_write_icon(IStream *icoStream, int exeIndex, LPCWSTR icoPathW,
1187                                    const char *destFilename, char **nativeIdentifier)
1188 {
1189     ICONDIRENTRY *iconDirEntries = NULL;
1190     int numEntries;
1191     int i;
1192     char *icoPathA = NULL;
1193     char *iconsDir = NULL;
1194     unsigned short crc;
1195     char *p, *q;
1196     HRESULT hr = S_OK;
1197     LARGE_INTEGER zero;
1198
1199     hr = read_ico_direntries(icoStream, &iconDirEntries, &numEntries);
1200     if (FAILED(hr))
1201         goto end;
1202
1203     icoPathA = wchars_to_utf8_chars(icoPathW);
1204     if (icoPathA == NULL)
1205     {
1206         hr = E_OUTOFMEMORY;
1207         goto end;
1208     }
1209     crc = crc16(icoPathA);
1210     p = strrchr(icoPathA, '\\');
1211     if (p == NULL)
1212         p = icoPathA;
1213     else
1214     {
1215         *p = 0;
1216         p++;
1217     }
1218     q = strrchr(p, '.');
1219     if (q)
1220         *q = 0;
1221     if (destFilename)
1222         *nativeIdentifier = heap_printf("%s", destFilename);
1223     else
1224         *nativeIdentifier = heap_printf("%04X_%s.%d", crc, p, exeIndex);
1225     if (*nativeIdentifier == NULL)
1226     {
1227         hr = E_OUTOFMEMORY;
1228         goto end;
1229     }
1230     iconsDir = heap_printf("%s/icons/hicolor", xdg_data_dir);
1231     if (iconsDir == NULL)
1232     {
1233         hr = E_OUTOFMEMORY;
1234         goto end;
1235     }
1236
1237     for (i = 0; i < numEntries; i++)
1238     {
1239         int bestIndex = i;
1240         int j;
1241         BOOLEAN duplicate = FALSE;
1242         int w, h;
1243         char *iconDir = NULL;
1244         char *pngPath = NULL;
1245
1246         WINE_TRACE("[%d]: %d x %d @ %d\n", i, iconDirEntries[i].bWidth,
1247             iconDirEntries[i].bHeight, iconDirEntries[i].wBitCount);
1248
1249         for (j = 0; j < i; j++)
1250         {
1251             if (iconDirEntries[j].bWidth == iconDirEntries[i].bWidth &&
1252                 iconDirEntries[j].bHeight == iconDirEntries[i].bHeight)
1253             {
1254                 duplicate = TRUE;
1255                 break;
1256             }
1257         }
1258         if (duplicate)
1259             continue;
1260         for (j = i + 1; j < numEntries; j++)
1261         {
1262             if (iconDirEntries[j].bWidth == iconDirEntries[i].bWidth &&
1263                 iconDirEntries[j].bHeight == iconDirEntries[i].bHeight &&
1264                 iconDirEntries[j].wBitCount >= iconDirEntries[bestIndex].wBitCount)
1265             {
1266                 bestIndex = j;
1267             }
1268         }
1269         WINE_TRACE("Selected: %d\n", bestIndex);
1270
1271         w = iconDirEntries[bestIndex].bWidth ? iconDirEntries[bestIndex].bWidth : 256;
1272         h = iconDirEntries[bestIndex].bHeight ? iconDirEntries[bestIndex].bHeight : 256;
1273         iconDir = heap_printf("%s/%dx%d/apps", iconsDir, w, h);
1274         if (iconDir == NULL)
1275         {
1276             hr = E_OUTOFMEMORY;
1277             goto endloop;
1278         }
1279         create_directories(iconDir);
1280         pngPath = heap_printf("%s/%s.png", iconDir, *nativeIdentifier);
1281         if (pngPath == NULL)
1282         {
1283             hr = E_OUTOFMEMORY;
1284             goto endloop;
1285         }
1286         zero.QuadPart = 0;
1287         hr = IStream_Seek(icoStream, zero, STREAM_SEEK_SET, NULL);
1288         if (FAILED(hr))
1289             goto endloop;
1290         hr = convert_to_native_icon(icoStream, &bestIndex, 1, &CLSID_WICPngEncoder,
1291                                     pngPath, icoPathW);
1292
1293     endloop:
1294         HeapFree(GetProcessHeap(), 0, iconDir);
1295         HeapFree(GetProcessHeap(), 0, pngPath);
1296     }
1297     refresh_icon_cache(iconsDir);
1298
1299 end:
1300     HeapFree(GetProcessHeap(), 0, iconDirEntries);
1301     HeapFree(GetProcessHeap(), 0, icoPathA);
1302     HeapFree(GetProcessHeap(), 0, iconsDir);
1303     return hr;
1304 }
1305 #endif /* defined(__APPLE__) */
1306
1307 /* extract an icon from an exe or icon file; helper for IPersistFile_fnSave */
1308 static char *extract_icon(LPCWSTR icoPathW, int index, const char *destFilename, BOOL bWait)
1309 {
1310     IStream *stream = NULL;
1311     HRESULT hr;
1312     char *nativeIdentifier = NULL;
1313
1314     WINE_TRACE("path=[%s] index=%d destFilename=[%s]\n", wine_dbgstr_w(icoPathW), index, wine_dbgstr_a(destFilename));
1315
1316     hr = open_icon(icoPathW, index, bWait, &stream);
1317     if (FAILED(hr))
1318     {
1319         WINE_WARN("opening icon %s index %d failed, hr=0x%08X\n", wine_dbgstr_w(icoPathW), index, hr);
1320         goto end;
1321     }
1322     hr = platform_write_icon(stream, index, icoPathW, destFilename, &nativeIdentifier);
1323     if (FAILED(hr))
1324         WINE_WARN("writing icon failed, error 0x%08X\n", hr);
1325
1326 end:
1327     if (stream)
1328         IStream_Release(stream);
1329     if (FAILED(hr))
1330     {
1331         HeapFree(GetProcessHeap(), 0, nativeIdentifier);
1332         nativeIdentifier = NULL;
1333     }
1334     return nativeIdentifier;
1335 }
1336
1337 static HKEY open_menus_reg_key(void)
1338 {
1339     static const WCHAR Software_Wine_FileOpenAssociationsW[] = {
1340         'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\','M','e','n','u','F','i','l','e','s',0};
1341     HKEY assocKey;
1342     DWORD ret;
1343     ret = RegCreateKeyW(HKEY_CURRENT_USER, Software_Wine_FileOpenAssociationsW, &assocKey);
1344     if (ret == ERROR_SUCCESS)
1345         return assocKey;
1346     SetLastError(ret);
1347     return NULL;
1348 }
1349
1350 static DWORD register_menus_entry(const char *unix_file, const char *windows_file)
1351 {
1352     WCHAR *unix_fileW;
1353     WCHAR *windows_fileW;
1354     INT size;
1355     DWORD ret;
1356
1357     size = MultiByteToWideChar(CP_UNIXCP, 0, unix_file, -1, NULL, 0);
1358     unix_fileW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1359     if (unix_fileW)
1360     {
1361         MultiByteToWideChar(CP_UNIXCP, 0, unix_file, -1, unix_fileW, size);
1362         size = MultiByteToWideChar(CP_UNIXCP, 0, windows_file, -1, NULL, 0);
1363         windows_fileW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1364         if (windows_fileW)
1365         {
1366             HKEY hkey;
1367             MultiByteToWideChar(CP_UNIXCP, 0, windows_file, -1, windows_fileW, size);
1368             hkey = open_menus_reg_key();
1369             if (hkey)
1370             {
1371                 ret = RegSetValueExW(hkey, unix_fileW, 0, REG_SZ, (const BYTE*)windows_fileW,
1372                     (strlenW(windows_fileW) + 1) * sizeof(WCHAR));
1373                 RegCloseKey(hkey);
1374             }
1375             else
1376                 ret = GetLastError();
1377             HeapFree(GetProcessHeap(), 0, windows_fileW);
1378         }
1379         else
1380             ret = ERROR_NOT_ENOUGH_MEMORY;
1381         HeapFree(GetProcessHeap(), 0, unix_fileW);
1382     }
1383     else
1384         ret = ERROR_NOT_ENOUGH_MEMORY;
1385     return ret;
1386 }
1387
1388 static BOOL write_desktop_entry(const char *unix_link, const char *location, const char *linkname,
1389                                 const char *path, const char *args, const char *descr,
1390                                 const char *workdir, const char *icon)
1391 {
1392     FILE *file;
1393
1394     WINE_TRACE("(%s,%s,%s,%s,%s,%s,%s,%s)\n", wine_dbgstr_a(unix_link), wine_dbgstr_a(location),
1395                wine_dbgstr_a(linkname), wine_dbgstr_a(path), wine_dbgstr_a(args),
1396                wine_dbgstr_a(descr), wine_dbgstr_a(workdir), wine_dbgstr_a(icon));
1397
1398     file = fopen(location, "w");
1399     if (file == NULL)
1400         return FALSE;
1401
1402     fprintf(file, "[Desktop Entry]\n");
1403     fprintf(file, "Name=%s\n", linkname);
1404     fprintf(file, "Exec=env WINEPREFIX=\"%s\" wine %s %s\n",
1405             wine_get_config_dir(), path, args);
1406     fprintf(file, "Type=Application\n");
1407     fprintf(file, "StartupNotify=true\n");
1408     if (descr && lstrlenA(descr))
1409         fprintf(file, "Comment=%s\n", descr);
1410     if (workdir && lstrlenA(workdir))
1411         fprintf(file, "Path=%s\n", workdir);
1412     if (icon && lstrlenA(icon))
1413         fprintf(file, "Icon=%s\n", icon);
1414
1415     fclose(file);
1416
1417     if (unix_link)
1418     {
1419         DWORD ret = register_menus_entry(location, unix_link);
1420         if (ret != ERROR_SUCCESS)
1421             return FALSE;
1422     }
1423
1424     return TRUE;
1425 }
1426
1427 static BOOL write_directory_entry(const char *directory, const char *location)
1428 {
1429     FILE *file;
1430
1431     WINE_TRACE("(%s,%s)\n", wine_dbgstr_a(directory), wine_dbgstr_a(location));
1432
1433     file = fopen(location, "w");
1434     if (file == NULL)
1435         return FALSE;
1436
1437     fprintf(file, "[Desktop Entry]\n");
1438     fprintf(file, "Type=Directory\n");
1439     if (strcmp(directory, "wine") == 0)
1440     {
1441         fprintf(file, "Name=Wine\n");
1442         fprintf(file, "Icon=wine\n");
1443     }
1444     else
1445     {
1446         fprintf(file, "Name=%s\n", directory);
1447         fprintf(file, "Icon=folder\n");
1448     }
1449
1450     fclose(file);
1451     return TRUE;
1452 }
1453
1454 static BOOL write_menu_file(const char *unix_link, const char *filename)
1455 {
1456     char *tempfilename;
1457     FILE *tempfile = NULL;
1458     char *lastEntry;
1459     char *name = NULL;
1460     char *menuPath = NULL;
1461     int i;
1462     int count = 0;
1463     BOOL ret = FALSE;
1464
1465     WINE_TRACE("(%s)\n", wine_dbgstr_a(filename));
1466
1467     while (1)
1468     {
1469         tempfilename = heap_printf("%s/wine-menu-XXXXXX", xdg_config_dir);
1470         if (tempfilename)
1471         {
1472             int tempfd = mkstemps(tempfilename, 0);
1473             if (tempfd >= 0)
1474             {
1475                 tempfile = fdopen(tempfd, "w");
1476                 if (tempfile)
1477                     break;
1478                 close(tempfd);
1479                 goto end;
1480             }
1481             else if (errno == EEXIST)
1482             {
1483                 HeapFree(GetProcessHeap(), 0, tempfilename);
1484                 continue;
1485             }
1486             HeapFree(GetProcessHeap(), 0, tempfilename);
1487         }
1488         return FALSE;
1489     }
1490
1491     fprintf(tempfile, "<!DOCTYPE Menu PUBLIC \"-//freedesktop//DTD Menu 1.0//EN\"\n");
1492     fprintf(tempfile, "\"http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd\">\n");
1493     fprintf(tempfile, "<Menu>\n");
1494     fprintf(tempfile, "  <Name>Applications</Name>\n");
1495
1496     name = HeapAlloc(GetProcessHeap(), 0, lstrlenA(filename) + 1);
1497     if (name == NULL) goto end;
1498     lastEntry = name;
1499     for (i = 0; filename[i]; i++)
1500     {
1501         name[i] = filename[i];
1502         if (filename[i] == '/')
1503         {
1504             char *dir_file_name;
1505             struct stat st;
1506             name[i] = 0;
1507             fprintf(tempfile, "  <Menu>\n");
1508             fprintf(tempfile, "    <Name>%s", count ? "" : "wine-");
1509             write_xml_text(tempfile, name);
1510             fprintf(tempfile, "</Name>\n");
1511             fprintf(tempfile, "    <Directory>%s", count ? "" : "wine-");
1512             write_xml_text(tempfile, name);
1513             fprintf(tempfile, ".directory</Directory>\n");
1514             dir_file_name = heap_printf("%s/desktop-directories/%s%s.directory",
1515                 xdg_data_dir, count ? "" : "wine-", name);
1516             if (dir_file_name)
1517             {
1518                 if (stat(dir_file_name, &st) != 0 && errno == ENOENT)
1519                     write_directory_entry(lastEntry, dir_file_name);
1520                 HeapFree(GetProcessHeap(), 0, dir_file_name);
1521             }
1522             name[i] = '-';
1523             lastEntry = &name[i+1];
1524             ++count;
1525         }
1526     }
1527     name[i] = 0;
1528
1529     fprintf(tempfile, "    <Include>\n");
1530     fprintf(tempfile, "      <Filename>");
1531     write_xml_text(tempfile, name);
1532     fprintf(tempfile, "</Filename>\n");
1533     fprintf(tempfile, "    </Include>\n");
1534     for (i = 0; i < count; i++)
1535          fprintf(tempfile, "  </Menu>\n");
1536     fprintf(tempfile, "</Menu>\n");
1537
1538     menuPath = heap_printf("%s/%s", xdg_config_dir, name);
1539     if (menuPath == NULL) goto end;
1540     strcpy(menuPath + strlen(menuPath) - strlen(".desktop"), ".menu");
1541     ret = TRUE;
1542
1543 end:
1544     if (tempfile)
1545         fclose(tempfile);
1546     if (ret)
1547         ret = (rename(tempfilename, menuPath) == 0);
1548     if (!ret && tempfilename)
1549         remove(tempfilename);
1550     HeapFree(GetProcessHeap(), 0, tempfilename);
1551     if (ret)
1552         register_menus_entry(menuPath, unix_link);
1553     HeapFree(GetProcessHeap(), 0, name);
1554     HeapFree(GetProcessHeap(), 0, menuPath);
1555     return ret;
1556 }
1557
1558 static BOOL write_menu_entry(const char *unix_link, const char *link, const char *path, const char *args,
1559                              const char *descr, const char *workdir, const char *icon)
1560 {
1561     const char *linkname;
1562     char *desktopPath = NULL;
1563     char *desktopDir;
1564     char *filename = NULL;
1565     BOOL ret = TRUE;
1566
1567     WINE_TRACE("(%s, %s, %s, %s, %s, %s, %s)\n", wine_dbgstr_a(unix_link), wine_dbgstr_a(link),
1568                wine_dbgstr_a(path), wine_dbgstr_a(args), wine_dbgstr_a(descr),
1569                wine_dbgstr_a(workdir), wine_dbgstr_a(icon));
1570
1571     linkname = strrchr(link, '/');
1572     if (linkname == NULL)
1573         linkname = link;
1574     else
1575         ++linkname;
1576
1577     desktopPath = heap_printf("%s/applications/wine/%s.desktop", xdg_data_dir, link);
1578     if (!desktopPath)
1579     {
1580         WINE_WARN("out of memory creating menu entry\n");
1581         ret = FALSE;
1582         goto end;
1583     }
1584     desktopDir = strrchr(desktopPath, '/');
1585     *desktopDir = 0;
1586     if (!create_directories(desktopPath))
1587     {
1588         WINE_WARN("couldn't make parent directories for %s\n", wine_dbgstr_a(desktopPath));
1589         ret = FALSE;
1590         goto end;
1591     }
1592     *desktopDir = '/';
1593     if (!write_desktop_entry(unix_link, desktopPath, linkname, path, args, descr, workdir, icon))
1594     {
1595         WINE_WARN("couldn't make desktop entry %s\n", wine_dbgstr_a(desktopPath));
1596         ret = FALSE;
1597         goto end;
1598     }
1599
1600     filename = heap_printf("wine/%s.desktop", link);
1601     if (!filename || !write_menu_file(unix_link, filename))
1602     {
1603         WINE_WARN("couldn't make menu file %s\n", wine_dbgstr_a(filename));
1604         ret = FALSE;
1605     }
1606
1607 end:
1608     HeapFree(GetProcessHeap(), 0, desktopPath);
1609     HeapFree(GetProcessHeap(), 0, filename);
1610     return ret;
1611 }
1612
1613 /* This escapes reserved characters in .desktop files' Exec keys. */
1614 static LPSTR escape(LPCWSTR arg)
1615 {
1616     int i, j;
1617     WCHAR *escaped_string;
1618     char *utf8_string;
1619
1620     escaped_string = HeapAlloc(GetProcessHeap(), 0, (4 * strlenW(arg) + 1) * sizeof(WCHAR));
1621     if (escaped_string == NULL) return NULL;
1622     for (i = j = 0; arg[i]; i++)
1623     {
1624         switch (arg[i])
1625         {
1626         case '\\':
1627             escaped_string[j++] = '\\';
1628             escaped_string[j++] = '\\';
1629             escaped_string[j++] = '\\';
1630             escaped_string[j++] = '\\';
1631             break;
1632         case ' ':
1633         case '\t':
1634         case '\n':
1635         case '"':
1636         case '\'':
1637         case '>':
1638         case '<':
1639         case '~':
1640         case '|':
1641         case '&':
1642         case ';':
1643         case '$':
1644         case '*':
1645         case '?':
1646         case '#':
1647         case '(':
1648         case ')':
1649         case '`':
1650             escaped_string[j++] = '\\';
1651             escaped_string[j++] = '\\';
1652             /* fall through */
1653         default:
1654             escaped_string[j++] = arg[i];
1655             break;
1656         }
1657     }
1658     escaped_string[j] = 0;
1659
1660     utf8_string = wchars_to_utf8_chars(escaped_string);
1661     if (utf8_string == NULL)
1662     {
1663         WINE_ERR("out of memory\n");
1664         goto end;
1665     }
1666
1667 end:
1668     HeapFree(GetProcessHeap(), 0, escaped_string);
1669     return utf8_string;
1670 }
1671
1672 /* Return a heap-allocated copy of the unix format difference between the two
1673  * Windows-format paths.
1674  * locn is the owning location
1675  * link is within locn
1676  */
1677 static char *relative_path( LPCWSTR link, LPCWSTR locn )
1678 {
1679     char *unix_locn, *unix_link;
1680     char *relative = NULL;
1681
1682     unix_locn = wine_get_unix_file_name(locn);
1683     unix_link = wine_get_unix_file_name(link);
1684     if (unix_locn && unix_link)
1685     {
1686         size_t len_unix_locn, len_unix_link;
1687         len_unix_locn = strlen (unix_locn);
1688         len_unix_link = strlen (unix_link);
1689         if (len_unix_locn < len_unix_link && memcmp (unix_locn, unix_link, len_unix_locn) == 0 && unix_link[len_unix_locn] == '/')
1690         {
1691             size_t len_rel;
1692             char *p = strrchr (unix_link + len_unix_locn, '/');
1693             p = strrchr (p, '.');
1694             if (p)
1695             {
1696                 *p = '\0';
1697                 len_unix_link = p - unix_link;
1698             }
1699             len_rel = len_unix_link - len_unix_locn;
1700             relative = HeapAlloc(GetProcessHeap(), 0, len_rel);
1701             if (relative)
1702             {
1703                 memcpy (relative, unix_link + len_unix_locn + 1, len_rel);
1704             }
1705         }
1706     }
1707     if (!relative)
1708         WINE_WARN("Could not separate the relative link path of %s in %s\n", wine_dbgstr_w(link), wine_dbgstr_w(locn));
1709     HeapFree(GetProcessHeap(), 0, unix_locn);
1710     HeapFree(GetProcessHeap(), 0, unix_link);
1711     return relative;
1712 }
1713
1714 /***********************************************************************
1715  *
1716  *           GetLinkLocation
1717  *
1718  * returns TRUE if successful
1719  * *loc will contain CS_DESKTOPDIRECTORY, CS_STARTMENU, CS_STARTUP etc.
1720  * *relative will contain the address of a heap-allocated copy of the portion
1721  * of the filename that is within the specified location, in unix form
1722  */
1723 static BOOL GetLinkLocation( LPCWSTR linkfile, DWORD *loc, char **relative )
1724 {
1725     WCHAR filename[MAX_PATH], shortfilename[MAX_PATH], buffer[MAX_PATH];
1726     DWORD len, i, r, filelen;
1727     const DWORD locations[] = {
1728         CSIDL_STARTUP, CSIDL_DESKTOPDIRECTORY, CSIDL_STARTMENU,
1729         CSIDL_COMMON_STARTUP, CSIDL_COMMON_DESKTOPDIRECTORY,
1730         CSIDL_COMMON_STARTMENU };
1731
1732     WINE_TRACE("%s\n", wine_dbgstr_w(linkfile));
1733     filelen=GetFullPathNameW( linkfile, MAX_PATH, shortfilename, NULL );
1734     if (filelen==0 || filelen>MAX_PATH)
1735         return FALSE;
1736
1737     WINE_TRACE("%s\n", wine_dbgstr_w(shortfilename));
1738
1739     /* the CSLU Toolkit uses a short path name when creating .lnk files;
1740      * expand or our hardcoded list won't match.
1741      */
1742     filelen=GetLongPathNameW(shortfilename, filename, MAX_PATH);
1743     if (filelen==0 || filelen>MAX_PATH)
1744         return FALSE;
1745
1746     WINE_TRACE("%s\n", wine_dbgstr_w(filename));
1747
1748     for( i=0; i<sizeof(locations)/sizeof(locations[0]); i++ )
1749     {
1750         if (!SHGetSpecialFolderPathW( 0, buffer, locations[i], FALSE ))
1751             continue;
1752
1753         len = lstrlenW(buffer);
1754         if (len >= MAX_PATH)
1755             continue; /* We've just trashed memory! Hopefully we are OK */
1756
1757         if (len > filelen || filename[len]!='\\')
1758             continue;
1759         /* do a lstrcmpinW */
1760         filename[len] = 0;
1761         r = lstrcmpiW( filename, buffer );
1762         filename[len] = '\\';
1763         if ( r )
1764             continue;
1765
1766         /* return the remainder of the string and link type */
1767         *loc = locations[i];
1768         *relative = relative_path (filename, buffer);
1769         return (*relative != NULL);
1770     }
1771
1772     return FALSE;
1773 }
1774
1775 /* gets the target path directly or through MSI */
1776 static HRESULT get_cmdline( IShellLinkW *sl, LPWSTR szPath, DWORD pathSize,
1777                             LPWSTR szArgs, DWORD argsSize)
1778 {
1779     IShellLinkDataList *dl = NULL;
1780     EXP_DARWIN_LINK *dar = NULL;
1781     HRESULT hr;
1782
1783     szPath[0] = 0;
1784     szArgs[0] = 0;
1785
1786     hr = IShellLinkW_GetPath( sl, szPath, pathSize, NULL, SLGP_RAWPATH );
1787     if (hr == S_OK && szPath[0])
1788     {
1789         IShellLinkW_GetArguments( sl, szArgs, argsSize );
1790         return hr;
1791     }
1792
1793     hr = IShellLinkW_QueryInterface( sl, &IID_IShellLinkDataList, (LPVOID*) &dl );
1794     if (FAILED(hr))
1795         return hr;
1796
1797     hr = IShellLinkDataList_CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar );
1798     if (SUCCEEDED(hr))
1799     {
1800         WCHAR* szCmdline;
1801         DWORD cmdSize;
1802
1803         cmdSize=0;
1804         hr = CommandLineFromMsiDescriptor( dar->szwDarwinID, NULL, &cmdSize );
1805         if (hr == ERROR_SUCCESS)
1806         {
1807             cmdSize++;
1808             szCmdline = HeapAlloc( GetProcessHeap(), 0, cmdSize*sizeof(WCHAR) );
1809             hr = CommandLineFromMsiDescriptor( dar->szwDarwinID, szCmdline, &cmdSize );
1810             WINE_TRACE("      command    : %s\n", wine_dbgstr_w(szCmdline));
1811             if (hr == ERROR_SUCCESS)
1812             {
1813                 WCHAR *s, *d;
1814                 int bcount, in_quotes;
1815
1816                 /* Extract the application path */
1817                 bcount=0;
1818                 in_quotes=0;
1819                 s=szCmdline;
1820                 d=szPath;
1821                 while (*s)
1822                 {
1823                     if ((*s==0x0009 || *s==0x0020) && !in_quotes)
1824                     {
1825                         /* skip the remaining spaces */
1826                         do {
1827                             s++;
1828                         } while (*s==0x0009 || *s==0x0020);
1829                         break;
1830                     }
1831                     else if (*s==0x005c)
1832                     {
1833                         /* '\\' */
1834                         *d++=*s++;
1835                         bcount++;
1836                     }
1837                     else if (*s==0x0022)
1838                     {
1839                         /* '"' */
1840                         if ((bcount & 1)==0)
1841                         {
1842                             /* Preceded by an even number of '\', this is
1843                              * half that number of '\', plus a quote which
1844                              * we erase.
1845                              */
1846                             d-=bcount/2;
1847                             in_quotes=!in_quotes;
1848                             s++;
1849                         }
1850                         else
1851                         {
1852                             /* Preceded by an odd number of '\', this is
1853                              * half that number of '\' followed by a '"'
1854                              */
1855                             d=d-bcount/2-1;
1856                             *d++='"';
1857                             s++;
1858                         }
1859                         bcount=0;
1860                     }
1861                     else
1862                     {
1863                         /* a regular character */
1864                         *d++=*s++;
1865                         bcount=0;
1866                     }
1867                     if ((d-szPath) == pathSize)
1868                     {
1869                         /* Keep processing the path till we get to the
1870                          * arguments, but 'stand still'
1871                          */
1872                         d--;
1873                     }
1874                 }
1875                 /* Close the application path */
1876                 *d=0;
1877
1878                 lstrcpynW(szArgs, s, argsSize);
1879             }
1880             HeapFree( GetProcessHeap(), 0, szCmdline );
1881         }
1882         LocalFree( dar );
1883     }
1884
1885     IShellLinkDataList_Release( dl );
1886     return hr;
1887 }
1888
1889 static WCHAR* assoc_query(ASSOCSTR assocStr, LPCWSTR name, LPCWSTR extra)
1890 {
1891     HRESULT hr;
1892     WCHAR *value = NULL;
1893     DWORD size = 0;
1894     hr = AssocQueryStringW(0, assocStr, name, extra, NULL, &size);
1895     if (SUCCEEDED(hr))
1896     {
1897         value = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1898         if (value)
1899         {
1900             hr = AssocQueryStringW(0, assocStr, name, extra, value, &size);
1901             if (FAILED(hr))
1902             {
1903                 HeapFree(GetProcessHeap(), 0, value);
1904                 value = NULL;
1905             }
1906         }
1907     }
1908     return value;
1909 }
1910
1911 static char *slashes_to_minuses(const char *string)
1912 {
1913     int i;
1914     char *ret = HeapAlloc(GetProcessHeap(), 0, lstrlenA(string) + 1);
1915     if (ret)
1916     {
1917         for (i = 0; string[i]; i++)
1918         {
1919             if (string[i] == '/')
1920                 ret[i] = '-';
1921             else
1922                 ret[i] = string[i];
1923         }
1924         ret[i] = 0;
1925         return ret;
1926     }
1927     return NULL;
1928 }
1929
1930 static BOOL next_line(FILE *file, char **line, int *size)
1931 {
1932     int pos = 0;
1933     char *cr;
1934     if (*line == NULL)
1935     {
1936         *size = 4096;
1937         *line = HeapAlloc(GetProcessHeap(), 0, *size);
1938     }
1939     while (*line != NULL)
1940     {
1941         if (fgets(&(*line)[pos], *size - pos, file) == NULL)
1942         {
1943             HeapFree(GetProcessHeap(), 0, *line);
1944             *line = NULL;
1945             if (feof(file))
1946                 return TRUE;
1947             return FALSE;
1948         }
1949         pos = strlen(*line);
1950         cr = strchr(*line, '\n');
1951         if (cr == NULL)
1952         {
1953             char *line2;
1954             (*size) *= 2;
1955             line2 = HeapReAlloc(GetProcessHeap(), 0, *line, *size);
1956             if (line2)
1957                 *line = line2;
1958             else
1959             {
1960                 HeapFree(GetProcessHeap(), 0, *line);
1961                 *line = NULL;
1962             }
1963         }
1964         else
1965         {
1966             *cr = 0;
1967             return TRUE;
1968         }
1969     }
1970     return FALSE;
1971 }
1972
1973 static BOOL add_mimes(const char *xdg_data_dir, struct list *mime_types)
1974 {
1975     char *globs_filename = NULL;
1976     BOOL ret = TRUE;
1977     globs_filename = heap_printf("%s/mime/globs", xdg_data_dir);
1978     if (globs_filename)
1979     {
1980         FILE *globs_file = fopen(globs_filename, "r");
1981         if (globs_file) /* doesn't have to exist */
1982         {
1983             char *line = NULL;
1984             int size = 0;
1985             while (ret && (ret = next_line(globs_file, &line, &size)) && line)
1986             {
1987                 char *pos;
1988                 struct xdg_mime_type *mime_type_entry = NULL;
1989                 if (line[0] != '#' && (pos = strchr(line, ':')))
1990                 {
1991                     mime_type_entry = HeapAlloc(GetProcessHeap(), 0, sizeof(struct xdg_mime_type));
1992                     if (mime_type_entry)
1993                     {
1994                         *pos = 0;
1995                         mime_type_entry->mimeType = strdupA(line);
1996                         mime_type_entry->glob = strdupA(pos + 1);
1997                         if (mime_type_entry->mimeType && mime_type_entry->glob)
1998                             list_add_tail(mime_types, &mime_type_entry->entry);
1999                         else
2000                         {
2001                             HeapFree(GetProcessHeap(), 0, mime_type_entry->mimeType);
2002                             HeapFree(GetProcessHeap(), 0, mime_type_entry->glob);
2003                             HeapFree(GetProcessHeap(), 0, mime_type_entry);
2004                             ret = FALSE;
2005                         }
2006                     }
2007                     else
2008                         ret = FALSE;
2009                 }
2010             }
2011             HeapFree(GetProcessHeap(), 0, line);
2012             fclose(globs_file);
2013         }
2014         HeapFree(GetProcessHeap(), 0, globs_filename);
2015     }
2016     else
2017         ret = FALSE;
2018     return ret;
2019 }
2020
2021 static void free_native_mime_types(struct list *native_mime_types)
2022 {
2023     struct xdg_mime_type *mime_type_entry, *mime_type_entry2;
2024
2025     LIST_FOR_EACH_ENTRY_SAFE(mime_type_entry, mime_type_entry2, native_mime_types, struct xdg_mime_type, entry)
2026     {
2027         list_remove(&mime_type_entry->entry);
2028         HeapFree(GetProcessHeap(), 0, mime_type_entry->glob);
2029         HeapFree(GetProcessHeap(), 0, mime_type_entry->mimeType);
2030         HeapFree(GetProcessHeap(), 0, mime_type_entry);
2031     }
2032     HeapFree(GetProcessHeap(), 0, native_mime_types);
2033 }
2034
2035 static BOOL build_native_mime_types(const char *xdg_data_home, struct list **mime_types)
2036 {
2037     char *xdg_data_dirs;
2038     BOOL ret;
2039
2040     *mime_types = NULL;
2041
2042     xdg_data_dirs = getenv("XDG_DATA_DIRS");
2043     if (xdg_data_dirs == NULL)
2044         xdg_data_dirs = heap_printf("/usr/local/share/:/usr/share/");
2045     else
2046         xdg_data_dirs = strdupA(xdg_data_dirs);
2047
2048     if (xdg_data_dirs)
2049     {
2050         *mime_types = HeapAlloc(GetProcessHeap(), 0, sizeof(struct list));
2051         if (*mime_types)
2052         {
2053             const char *begin;
2054             char *end;
2055
2056             list_init(*mime_types);
2057             ret = add_mimes(xdg_data_home, *mime_types);
2058             if (ret)
2059             {
2060                 for (begin = xdg_data_dirs; (end = strchr(begin, ':')); begin = end + 1)
2061                 {
2062                     *end = '\0';
2063                     ret = add_mimes(begin, *mime_types);
2064                     *end = ':';
2065                     if (!ret)
2066                         break;
2067                 }
2068                 if (ret)
2069                     ret = add_mimes(begin, *mime_types);
2070             }
2071         }
2072         else
2073             ret = FALSE;
2074         HeapFree(GetProcessHeap(), 0, xdg_data_dirs);
2075     }
2076     else
2077         ret = FALSE;
2078     if (!ret && *mime_types)
2079     {
2080         free_native_mime_types(*mime_types);
2081         *mime_types = NULL;
2082     }
2083     return ret;
2084 }
2085
2086 static BOOL match_glob(struct list *native_mime_types, const char *extension,
2087                        char **match)
2088 {
2089 #ifdef HAVE_FNMATCH
2090     struct xdg_mime_type *mime_type_entry;
2091     int matchLength = 0;
2092
2093     *match = NULL;
2094
2095     LIST_FOR_EACH_ENTRY(mime_type_entry, native_mime_types, struct xdg_mime_type, entry)
2096     {
2097         if (fnmatch(mime_type_entry->glob, extension, 0) == 0)
2098         {
2099             if (*match == NULL || matchLength < strlen(mime_type_entry->glob))
2100             {
2101                 *match = mime_type_entry->mimeType;
2102                 matchLength = strlen(mime_type_entry->glob);
2103             }
2104         }
2105     }
2106
2107     if (*match != NULL)
2108     {
2109         *match = strdupA(*match);
2110         if (*match == NULL)
2111             return FALSE;
2112     }
2113 #else
2114     *match = NULL;
2115 #endif
2116     return TRUE;
2117 }
2118
2119 static BOOL freedesktop_mime_type_for_extension(struct list *native_mime_types,
2120                                                 const char *extensionA,
2121                                                 LPCWSTR extensionW,
2122                                                 char **mime_type)
2123 {
2124     WCHAR *lower_extensionW;
2125     INT len;
2126     BOOL ret = match_glob(native_mime_types, extensionA, mime_type);
2127     if (ret == FALSE || *mime_type != NULL)
2128         return ret;
2129     len = strlenW(extensionW);
2130     lower_extensionW = HeapAlloc(GetProcessHeap(), 0, (len + 1)*sizeof(WCHAR));
2131     if (lower_extensionW)
2132     {
2133         char *lower_extensionA;
2134         memcpy(lower_extensionW, extensionW, (len + 1)*sizeof(WCHAR));
2135         strlwrW(lower_extensionW);
2136         lower_extensionA = wchars_to_utf8_chars(lower_extensionW);
2137         if (lower_extensionA)
2138         {
2139             ret = match_glob(native_mime_types, lower_extensionA, mime_type);
2140             HeapFree(GetProcessHeap(), 0, lower_extensionA);
2141         }
2142         else
2143         {
2144             ret = FALSE;
2145             WINE_FIXME("out of memory\n");
2146         }
2147         HeapFree(GetProcessHeap(), 0, lower_extensionW);
2148     }
2149     else
2150     {
2151         ret = FALSE;
2152         WINE_FIXME("out of memory\n");
2153     }
2154     return ret;
2155 }
2156
2157 static WCHAR* reg_get_valW(HKEY key, LPCWSTR subkey, LPCWSTR name)
2158 {
2159     DWORD size;
2160     if (RegGetValueW(key, subkey, name, RRF_RT_REG_SZ, NULL, NULL, &size) == ERROR_SUCCESS)
2161     {
2162         WCHAR *ret = HeapAlloc(GetProcessHeap(), 0, size);
2163         if (ret)
2164         {
2165             if (RegGetValueW(key, subkey, name, RRF_RT_REG_SZ, NULL, ret, &size) == ERROR_SUCCESS)
2166                 return ret;
2167         }
2168         HeapFree(GetProcessHeap(), 0, ret);
2169     }
2170     return NULL;
2171 }
2172
2173 static CHAR* reg_get_val_utf8(HKEY key, LPCWSTR subkey, LPCWSTR name)
2174 {
2175     WCHAR *valW = reg_get_valW(key, subkey, name);
2176     if (valW)
2177     {
2178         char *val = wchars_to_utf8_chars(valW);
2179         HeapFree(GetProcessHeap(), 0, valW);
2180         return val;
2181     }
2182     return NULL;
2183 }
2184
2185 static HKEY open_associations_reg_key(void)
2186 {
2187     static const WCHAR Software_Wine_FileOpenAssociationsW[] = {
2188         'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\','F','i','l','e','O','p','e','n','A','s','s','o','c','i','a','t','i','o','n','s',0};
2189     HKEY assocKey;
2190     if (RegCreateKeyW(HKEY_CURRENT_USER, Software_Wine_FileOpenAssociationsW, &assocKey) == ERROR_SUCCESS)
2191         return assocKey;
2192     return NULL;
2193 }
2194
2195 static BOOL has_association_changed(LPCWSTR extensionW, LPCSTR mimeType, LPCWSTR progId,
2196     LPCSTR appName, LPCSTR openWithIcon)
2197 {
2198     static const WCHAR ProgIDW[] = {'P','r','o','g','I','D',0};
2199     static const WCHAR MimeTypeW[] = {'M','i','m','e','T','y','p','e',0};
2200     static const WCHAR AppNameW[] = {'A','p','p','N','a','m','e',0};
2201     static const WCHAR OpenWithIconW[] = {'O','p','e','n','W','i','t','h','I','c','o','n',0};
2202     HKEY assocKey;
2203     BOOL ret;
2204
2205     if ((assocKey = open_associations_reg_key()))
2206     {
2207         CHAR *valueA;
2208         WCHAR *value;
2209
2210         ret = FALSE;
2211
2212         valueA = reg_get_val_utf8(assocKey, extensionW, MimeTypeW);
2213         if (!valueA || lstrcmpA(valueA, mimeType))
2214             ret = TRUE;
2215         HeapFree(GetProcessHeap(), 0, valueA);
2216
2217         value = reg_get_valW(assocKey, extensionW, ProgIDW);
2218         if (!value || strcmpW(value, progId))
2219             ret = TRUE;
2220         HeapFree(GetProcessHeap(), 0, value);
2221
2222         valueA = reg_get_val_utf8(assocKey, extensionW, AppNameW);
2223         if (!valueA || lstrcmpA(valueA, appName))
2224             ret = TRUE;
2225         HeapFree(GetProcessHeap(), 0, valueA);
2226
2227         valueA = reg_get_val_utf8(assocKey, extensionW, OpenWithIconW);
2228         if ((openWithIcon && !valueA) ||
2229             (!openWithIcon && valueA) ||
2230             (openWithIcon && valueA && lstrcmpA(valueA, openWithIcon)))
2231             ret = TRUE;
2232         HeapFree(GetProcessHeap(), 0, valueA);
2233
2234         RegCloseKey(assocKey);
2235     }
2236     else
2237     {
2238         WINE_ERR("error opening associations registry key\n");
2239         ret = FALSE;
2240     }
2241     return ret;
2242 }
2243
2244 static void update_association(LPCWSTR extension, LPCSTR mimeType, LPCWSTR progId,
2245     LPCSTR appName, LPCSTR desktopFile, LPCSTR openWithIcon)
2246 {
2247     static const WCHAR ProgIDW[] = {'P','r','o','g','I','D',0};
2248     static const WCHAR MimeTypeW[] = {'M','i','m','e','T','y','p','e',0};
2249     static const WCHAR AppNameW[] = {'A','p','p','N','a','m','e',0};
2250     static const WCHAR DesktopFileW[] = {'D','e','s','k','t','o','p','F','i','l','e',0};
2251     static const WCHAR OpenWithIconW[] = {'O','p','e','n','W','i','t','h','I','c','o','n',0};
2252     HKEY assocKey = NULL;
2253     HKEY subkey = NULL;
2254     WCHAR *mimeTypeW = NULL;
2255     WCHAR *appNameW = NULL;
2256     WCHAR *desktopFileW = NULL;
2257     WCHAR *openWithIconW = NULL;
2258
2259     assocKey = open_associations_reg_key();
2260     if (assocKey == NULL)
2261     {
2262         WINE_ERR("could not open file associations key\n");
2263         goto done;
2264     }
2265
2266     if (RegCreateKeyW(assocKey, extension, &subkey) != ERROR_SUCCESS)
2267     {
2268         WINE_ERR("could not create extension subkey\n");
2269         goto done;
2270     }
2271
2272     mimeTypeW = utf8_chars_to_wchars(mimeType);
2273     if (mimeTypeW == NULL)
2274     {
2275         WINE_ERR("out of memory\n");
2276         goto done;
2277     }
2278
2279     appNameW = utf8_chars_to_wchars(appName);
2280     if (appNameW == NULL)
2281     {
2282         WINE_ERR("out of memory\n");
2283         goto done;
2284     }
2285
2286     desktopFileW = utf8_chars_to_wchars(desktopFile);
2287     if (desktopFileW == NULL)
2288     {
2289         WINE_ERR("out of memory\n");
2290         goto done;
2291     }
2292
2293     if (openWithIcon)
2294     {
2295         openWithIconW = utf8_chars_to_wchars(openWithIcon);
2296         if (openWithIconW == NULL)
2297         {
2298             WINE_ERR("out of memory\n");
2299             goto done;
2300         }
2301     }
2302
2303     RegSetValueExW(subkey, MimeTypeW, 0, REG_SZ, (const BYTE*) mimeTypeW, (lstrlenW(mimeTypeW) + 1) * sizeof(WCHAR));
2304     RegSetValueExW(subkey, ProgIDW, 0, REG_SZ, (const BYTE*) progId, (lstrlenW(progId) + 1) * sizeof(WCHAR));
2305     RegSetValueExW(subkey, AppNameW, 0, REG_SZ, (const BYTE*) appNameW, (lstrlenW(appNameW) + 1) * sizeof(WCHAR));
2306     RegSetValueExW(subkey, DesktopFileW, 0, REG_SZ, (const BYTE*) desktopFileW, (lstrlenW(desktopFileW) + 1) * sizeof(WCHAR));
2307     if (openWithIcon)
2308         RegSetValueExW(subkey, OpenWithIconW, 0, REG_SZ, (const BYTE*) openWithIconW, (lstrlenW(openWithIconW) + 1) * sizeof(WCHAR));
2309     else
2310         RegDeleteValueW(subkey, OpenWithIconW);
2311
2312 done:
2313     RegCloseKey(assocKey);
2314     RegCloseKey(subkey);
2315     HeapFree(GetProcessHeap(), 0, mimeTypeW);
2316     HeapFree(GetProcessHeap(), 0, appNameW);
2317     HeapFree(GetProcessHeap(), 0, desktopFileW);
2318     HeapFree(GetProcessHeap(), 0, openWithIconW);
2319 }
2320
2321 static BOOL cleanup_associations(void)
2322 {
2323     static const WCHAR openW[] = {'o','p','e','n',0};
2324     static const WCHAR DesktopFileW[] = {'D','e','s','k','t','o','p','F','i','l','e',0};
2325     HKEY assocKey;
2326     BOOL hasChanged = FALSE;
2327     if ((assocKey = open_associations_reg_key()))
2328     {
2329         int i;
2330         BOOL done = FALSE;
2331         for (i = 0; !done;)
2332         {
2333             WCHAR *extensionW = NULL;
2334             DWORD size = 1024;
2335             LSTATUS ret;
2336
2337             do
2338             {
2339                 HeapFree(GetProcessHeap(), 0, extensionW);
2340                 extensionW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
2341                 if (extensionW == NULL)
2342                 {
2343                     WINE_ERR("out of memory\n");
2344                     ret = ERROR_OUTOFMEMORY;
2345                     break;
2346                 }
2347                 ret = RegEnumKeyExW(assocKey, i, extensionW, &size, NULL, NULL, NULL, NULL);
2348                 size *= 2;
2349             } while (ret == ERROR_MORE_DATA);
2350
2351             if (ret == ERROR_SUCCESS)
2352             {
2353                 WCHAR *command;
2354                 command = assoc_query(ASSOCSTR_COMMAND, extensionW, openW);
2355                 if (command == NULL)
2356                 {
2357                     char *desktopFile = reg_get_val_utf8(assocKey, extensionW, DesktopFileW);
2358                     if (desktopFile)
2359                     {
2360                         WINE_TRACE("removing file type association for %s\n", wine_dbgstr_w(extensionW));
2361                         remove(desktopFile);
2362                     }
2363                     RegDeleteKeyW(assocKey, extensionW);
2364                     hasChanged = TRUE;
2365                     HeapFree(GetProcessHeap(), 0, desktopFile);
2366                 }
2367                 else
2368                     i++;
2369                 HeapFree(GetProcessHeap(), 0, command);
2370             }
2371             else
2372             {
2373                 if (ret != ERROR_NO_MORE_ITEMS)
2374                     WINE_ERR("error %d while reading registry\n", ret);
2375                 done = TRUE;
2376             }
2377             HeapFree(GetProcessHeap(), 0, extensionW);
2378         }
2379         RegCloseKey(assocKey);
2380     }
2381     else
2382         WINE_ERR("could not open file associations key\n");
2383     return hasChanged;
2384 }
2385
2386 static BOOL write_freedesktop_mime_type_entry(const char *packages_dir, const char *dot_extension,
2387                                               const char *mime_type, const char *comment)
2388 {
2389     BOOL ret = FALSE;
2390     char *filename;
2391
2392     WINE_TRACE("writing MIME type %s, extension=%s, comment=%s\n", wine_dbgstr_a(mime_type),
2393                wine_dbgstr_a(dot_extension), wine_dbgstr_a(comment));
2394
2395     filename = heap_printf("%s/x-wine-extension-%s.xml", packages_dir, &dot_extension[1]);
2396     if (filename)
2397     {
2398         FILE *packageFile = fopen(filename, "w");
2399         if (packageFile)
2400         {
2401             fprintf(packageFile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
2402             fprintf(packageFile, "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n");
2403             fprintf(packageFile, "  <mime-type type=\"");
2404             write_xml_text(packageFile, mime_type);
2405             fprintf(packageFile, "\">\n");
2406             fprintf(packageFile, "    <glob pattern=\"*");
2407             write_xml_text(packageFile, dot_extension);
2408             fprintf(packageFile, "\"/>\n");
2409             if (comment)
2410             {
2411                 fprintf(packageFile, "    <comment>");
2412                 write_xml_text(packageFile, comment);
2413                 fprintf(packageFile, "</comment>\n");
2414             }
2415             fprintf(packageFile, "  </mime-type>\n");
2416             fprintf(packageFile, "</mime-info>\n");
2417             ret = TRUE;
2418             fclose(packageFile);
2419         }
2420         else
2421             WINE_ERR("error writing file %s\n", filename);
2422         HeapFree(GetProcessHeap(), 0, filename);
2423     }
2424     else
2425         WINE_ERR("out of memory\n");
2426     return ret;
2427 }
2428
2429 static BOOL is_extension_blacklisted(LPCWSTR extension)
2430 {
2431     /* These are managed through external tools like wine.desktop, to evade malware created file type associations */
2432     static const WCHAR comW[] = {'.','c','o','m',0};
2433     static const WCHAR exeW[] = {'.','e','x','e',0};
2434     static const WCHAR msiW[] = {'.','m','s','i',0};
2435
2436     if (!strcmpiW(extension, comW) ||
2437         !strcmpiW(extension, exeW) ||
2438         !strcmpiW(extension, msiW))
2439         return TRUE;
2440     return FALSE;
2441 }
2442
2443 static const char* get_special_mime_type(LPCWSTR extension)
2444 {
2445     static const WCHAR lnkW[] = {'.','l','n','k',0};
2446     if (!strcmpiW(extension, lnkW))
2447         return "application/x-ms-shortcut";
2448     return NULL;
2449 }
2450
2451 static BOOL write_freedesktop_association_entry(const char *desktopPath, const char *dot_extension,
2452                                                 const char *friendlyAppName, const char *mimeType,
2453                                                 const char *progId, const char *openWithIcon)
2454 {
2455     BOOL ret = FALSE;
2456     FILE *desktop;
2457
2458     WINE_TRACE("writing association for file type %s, friendlyAppName=%s, MIME type %s, progID=%s, icon=%s to file %s\n",
2459                wine_dbgstr_a(dot_extension), wine_dbgstr_a(friendlyAppName), wine_dbgstr_a(mimeType),
2460                wine_dbgstr_a(progId), wine_dbgstr_a(openWithIcon), wine_dbgstr_a(desktopPath));
2461
2462     desktop = fopen(desktopPath, "w");
2463     if (desktop)
2464     {
2465         fprintf(desktop, "[Desktop Entry]\n");
2466         fprintf(desktop, "Type=Application\n");
2467         fprintf(desktop, "Name=%s\n", friendlyAppName);
2468         fprintf(desktop, "MimeType=%s;\n", mimeType);
2469         fprintf(desktop, "Exec=env WINEPREFIX=\"%s\" wine start /ProgIDOpen %s %%f\n", wine_get_config_dir(), progId);
2470         fprintf(desktop, "NoDisplay=true\n");
2471         fprintf(desktop, "StartupNotify=true\n");
2472         if (openWithIcon)
2473             fprintf(desktop, "Icon=%s\n", openWithIcon);
2474         ret = TRUE;
2475         fclose(desktop);
2476     }
2477     else
2478         WINE_ERR("error writing association file %s\n", wine_dbgstr_a(desktopPath));
2479     return ret;
2480 }
2481
2482 static BOOL generate_associations(const char *xdg_data_home, const char *packages_dir, const char *applications_dir)
2483 {
2484     static const WCHAR openW[] = {'o','p','e','n',0};
2485     struct wine_rb_tree mimeProgidTree;
2486     struct list *nativeMimeTypes = NULL;
2487     LSTATUS ret = 0;
2488     int i;
2489     BOOL hasChanged = FALSE;
2490
2491     if (wine_rb_init(&mimeProgidTree, &winemenubuilder_rb_functions))
2492     {
2493         WINE_ERR("wine_rb_init failed\n");
2494         return FALSE;
2495     }
2496     if (!build_native_mime_types(xdg_data_home, &nativeMimeTypes))
2497     {
2498         WINE_ERR("could not build native MIME types\n");
2499         return FALSE;
2500     }
2501
2502     for (i = 0; ; i++)
2503     {
2504         WCHAR *extensionW = NULL;
2505         DWORD size = 1024;
2506
2507         do
2508         {
2509             HeapFree(GetProcessHeap(), 0, extensionW);
2510             extensionW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
2511             if (extensionW == NULL)
2512             {
2513                 WINE_ERR("out of memory\n");
2514                 ret = ERROR_OUTOFMEMORY;
2515                 break;
2516             }
2517             ret = RegEnumKeyExW(HKEY_CLASSES_ROOT, i, extensionW, &size, NULL, NULL, NULL, NULL);
2518             size *= 2;
2519         } while (ret == ERROR_MORE_DATA);
2520
2521         if (ret == ERROR_SUCCESS && extensionW[0] == '.' && !is_extension_blacklisted(extensionW))
2522         {
2523             char *extensionA = NULL;
2524             WCHAR *commandW = NULL;
2525             WCHAR *executableW = NULL;
2526             char *openWithIconA = NULL;
2527             WCHAR *friendlyDocNameW = NULL;
2528             char *friendlyDocNameA = NULL;
2529             WCHAR *iconW = NULL;
2530             char *iconA = NULL;
2531             WCHAR *contentTypeW = NULL;
2532             char *mimeTypeA = NULL;
2533             WCHAR *friendlyAppNameW = NULL;
2534             char *friendlyAppNameA = NULL;
2535             WCHAR *progIdW = NULL;
2536             char *progIdA = NULL;
2537             char *mimeProgId = NULL;
2538
2539             extensionA = wchars_to_utf8_chars(strlwrW(extensionW));
2540             if (extensionA == NULL)
2541             {
2542                 WINE_ERR("out of memory\n");
2543                 goto end;
2544             }
2545
2546             friendlyDocNameW = assoc_query(ASSOCSTR_FRIENDLYDOCNAME, extensionW, NULL);
2547             if (friendlyDocNameW)
2548             {
2549                 friendlyDocNameA = wchars_to_utf8_chars(friendlyDocNameW);
2550                 if (friendlyDocNameA == NULL)
2551                 {
2552                     WINE_ERR("out of memory\n");
2553                     goto end;
2554                 }
2555             }
2556
2557             iconW = assoc_query(ASSOCSTR_DEFAULTICON, extensionW, NULL);
2558
2559             contentTypeW = assoc_query(ASSOCSTR_CONTENTTYPE, extensionW, NULL);
2560             if (contentTypeW)
2561                 strlwrW(contentTypeW);
2562
2563             if (!freedesktop_mime_type_for_extension(nativeMimeTypes, extensionA, extensionW, &mimeTypeA))
2564                 goto end;
2565
2566             if (mimeTypeA == NULL)
2567             {
2568                 if (contentTypeW != NULL && strchrW(contentTypeW, '/'))
2569                     mimeTypeA = wchars_to_utf8_chars(contentTypeW);
2570                 else if ((get_special_mime_type(extensionW)))
2571                     mimeTypeA = strdupA(get_special_mime_type(extensionW));
2572                 else
2573                     mimeTypeA = heap_printf("application/x-wine-extension-%s", &extensionA[1]);
2574
2575                 if (mimeTypeA != NULL)
2576                 {
2577                     /* Gnome seems to ignore the <icon> tag in MIME packages,
2578                      * and the default name is more intuitive anyway.
2579                      */
2580                     if (iconW)
2581                     {
2582                         char *flattened_mime = slashes_to_minuses(mimeTypeA);
2583                         if (flattened_mime)
2584                         {
2585                             int index = 0;
2586                             WCHAR *comma = strrchrW(iconW, ',');
2587                             if (comma)
2588                             {
2589                                 *comma = 0;
2590                                 index = atoiW(comma + 1);
2591                             }
2592                             iconA = extract_icon(iconW, index, flattened_mime, FALSE);
2593                             HeapFree(GetProcessHeap(), 0, flattened_mime);
2594                         }
2595                     }
2596
2597                     write_freedesktop_mime_type_entry(packages_dir, extensionA, mimeTypeA, friendlyDocNameA);
2598                     hasChanged = TRUE;
2599                 }
2600                 else
2601                 {
2602                     WINE_FIXME("out of memory\n");
2603                     goto end;
2604                 }
2605             }
2606
2607             commandW = assoc_query(ASSOCSTR_COMMAND, extensionW, openW);
2608             if (commandW == NULL)
2609                 /* no command => no application is associated */
2610                 goto end;
2611
2612             executableW = assoc_query(ASSOCSTR_EXECUTABLE, extensionW, openW);
2613             if (executableW)
2614                 openWithIconA = extract_icon(executableW, 0, NULL, FALSE);
2615
2616             friendlyAppNameW = assoc_query(ASSOCSTR_FRIENDLYAPPNAME, extensionW, openW);
2617             if (friendlyAppNameW)
2618             {
2619                 friendlyAppNameA = wchars_to_utf8_chars(friendlyAppNameW);
2620                 if (friendlyAppNameA == NULL)
2621                 {
2622                     WINE_ERR("out of memory\n");
2623                     goto end;
2624                 }
2625             }
2626             else
2627             {
2628                 friendlyAppNameA = heap_printf("A Wine application");
2629                 if (friendlyAppNameA == NULL)
2630                 {
2631                     WINE_ERR("out of memory\n");
2632                     goto end;
2633                 }
2634             }
2635
2636             progIdW = reg_get_valW(HKEY_CLASSES_ROOT, extensionW, NULL);
2637             if (progIdW)
2638             {
2639                 progIdA = escape(progIdW);
2640                 if (progIdA == NULL)
2641                 {
2642                     WINE_ERR("out of memory\n");
2643                     goto end;
2644                 }
2645             }
2646             else
2647                 goto end; /* no progID => not a file type association */
2648
2649             /* Do not allow duplicate ProgIDs for a MIME type, it causes unnecessary duplication in Open dialogs */
2650             mimeProgId = heap_printf("%s=>%s", mimeTypeA, progIdA);
2651             if (mimeProgId)
2652             {
2653                 struct rb_string_entry *entry;
2654                 if (wine_rb_get(&mimeProgidTree, mimeProgId))
2655                 {
2656                     HeapFree(GetProcessHeap(), 0, mimeProgId);
2657                     goto end;
2658                 }
2659                 entry = HeapAlloc(GetProcessHeap(), 0, sizeof(struct rb_string_entry));
2660                 if (!entry)
2661                 {
2662                     WINE_ERR("out of memory allocating rb_string_entry\n");
2663                     goto end;
2664                 }
2665                 entry->string = mimeProgId;
2666                 if (wine_rb_put(&mimeProgidTree, mimeProgId, &entry->entry))
2667                 {
2668                     WINE_ERR("error updating rb tree\n");
2669                     goto end;
2670                 }
2671             }
2672
2673             if (has_association_changed(extensionW, mimeTypeA, progIdW, friendlyAppNameA, openWithIconA))
2674             {
2675                 char *desktopPath = heap_printf("%s/wine-extension-%s.desktop", applications_dir, &extensionA[1]);
2676                 if (desktopPath)
2677                 {
2678                     if (write_freedesktop_association_entry(desktopPath, extensionA, friendlyAppNameA, mimeTypeA, progIdA, openWithIconA))
2679                     {
2680                         hasChanged = TRUE;
2681                         update_association(extensionW, mimeTypeA, progIdW, friendlyAppNameA, desktopPath, openWithIconA);
2682                     }
2683                     HeapFree(GetProcessHeap(), 0, desktopPath);
2684                 }
2685             }
2686
2687         end:
2688             HeapFree(GetProcessHeap(), 0, extensionA);
2689             HeapFree(GetProcessHeap(), 0, commandW);
2690             HeapFree(GetProcessHeap(), 0, executableW);
2691             HeapFree(GetProcessHeap(), 0, openWithIconA);
2692             HeapFree(GetProcessHeap(), 0, friendlyDocNameW);
2693             HeapFree(GetProcessHeap(), 0, friendlyDocNameA);
2694             HeapFree(GetProcessHeap(), 0, iconW);
2695             HeapFree(GetProcessHeap(), 0, iconA);
2696             HeapFree(GetProcessHeap(), 0, contentTypeW);
2697             HeapFree(GetProcessHeap(), 0, mimeTypeA);
2698             HeapFree(GetProcessHeap(), 0, friendlyAppNameW);
2699             HeapFree(GetProcessHeap(), 0, friendlyAppNameA);
2700             HeapFree(GetProcessHeap(), 0, progIdW);
2701             HeapFree(GetProcessHeap(), 0, progIdA);
2702         }
2703         HeapFree(GetProcessHeap(), 0, extensionW);
2704         if (ret != ERROR_SUCCESS)
2705             break;
2706     }
2707
2708     wine_rb_destroy(&mimeProgidTree, winemenubuilder_rb_destroy, NULL);
2709     free_native_mime_types(nativeMimeTypes);
2710     return hasChanged;
2711 }
2712
2713 static char *get_start_exe_path(void)
2714  {
2715     static const WCHAR startW[] = {'\\','c','o','m','m','a','n','d',
2716                                    '\\','s','t','a','r','t','.','e','x','e',0};
2717     WCHAR start_path[MAX_PATH];
2718     GetWindowsDirectoryW(start_path, MAX_PATH);
2719     lstrcatW(start_path, startW);
2720     return escape(start_path);
2721 }
2722
2723 static char* escape_unix_link_arg(LPCSTR unix_link)
2724 {
2725     char *ret = NULL;
2726     WCHAR *unix_linkW = utf8_chars_to_wchars(unix_link);
2727     if (unix_linkW)
2728     {
2729         char *escaped_lnk = escape(unix_linkW);
2730         if (escaped_lnk)
2731         {
2732             ret = heap_printf("/Unix %s", escaped_lnk);
2733             HeapFree(GetProcessHeap(), 0, escaped_lnk);
2734         }
2735         HeapFree(GetProcessHeap(), 0, unix_linkW);
2736     }
2737     return ret;
2738 }
2739
2740 static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait )
2741 {
2742     static const WCHAR startW[] = {'\\','c','o','m','m','a','n','d',
2743                                    '\\','s','t','a','r','t','.','e','x','e',0};
2744     char *link_name = NULL, *icon_name = NULL, *work_dir = NULL;
2745     char *escaped_path = NULL, *escaped_args = NULL, *description = NULL;
2746     WCHAR szTmp[INFOTIPSIZE];
2747     WCHAR szDescription[INFOTIPSIZE], szPath[MAX_PATH], szWorkDir[MAX_PATH];
2748     WCHAR szArgs[INFOTIPSIZE], szIconPath[MAX_PATH];
2749     int iIconId = 0, r = -1;
2750     DWORD csidl = -1;
2751     HANDLE hsem = NULL;
2752     char *unix_link = NULL;
2753     char *start_path = NULL;
2754
2755     if ( !link )
2756     {
2757         WINE_ERR("Link name is null\n");
2758         return FALSE;
2759     }
2760
2761     if( !GetLinkLocation( link, &csidl, &link_name ) )
2762     {
2763         WINE_WARN("Unknown link location %s. Ignoring.\n",wine_dbgstr_w(link));
2764         return TRUE;
2765     }
2766     if (!in_desktop_dir(csidl) && !in_startmenu(csidl))
2767     {
2768         WINE_WARN("Not under desktop or start menu. Ignoring.\n");
2769         return TRUE;
2770     }
2771     WINE_TRACE("Link       : %s\n", wine_dbgstr_a(link_name));
2772
2773     szTmp[0] = 0;
2774     IShellLinkW_GetWorkingDirectory( sl, szTmp, MAX_PATH );
2775     ExpandEnvironmentStringsW(szTmp, szWorkDir, MAX_PATH);
2776     WINE_TRACE("workdir    : %s\n", wine_dbgstr_w(szWorkDir));
2777
2778     szTmp[0] = 0;
2779     IShellLinkW_GetDescription( sl, szTmp, INFOTIPSIZE );
2780     ExpandEnvironmentStringsW(szTmp, szDescription, INFOTIPSIZE);
2781     WINE_TRACE("description: %s\n", wine_dbgstr_w(szDescription));
2782
2783     get_cmdline( sl, szTmp, MAX_PATH, szArgs, INFOTIPSIZE);
2784     ExpandEnvironmentStringsW(szTmp, szPath, MAX_PATH);
2785     WINE_TRACE("path       : %s\n", wine_dbgstr_w(szPath));
2786     WINE_TRACE("args       : %s\n", wine_dbgstr_w(szArgs));
2787
2788     szTmp[0] = 0;
2789     IShellLinkW_GetIconLocation( sl, szTmp, MAX_PATH, &iIconId );
2790     ExpandEnvironmentStringsW(szTmp, szIconPath, MAX_PATH);
2791     WINE_TRACE("icon file  : %s\n", wine_dbgstr_w(szIconPath) );
2792
2793     if( !szPath[0] )
2794     {
2795         LPITEMIDLIST pidl = NULL;
2796         IShellLinkW_GetIDList( sl, &pidl );
2797         if( pidl && SHGetPathFromIDListW( pidl, szPath ) )
2798             WINE_TRACE("pidl path  : %s\n", wine_dbgstr_w(szPath));
2799     }
2800
2801     /* extract the icon */
2802     if( szIconPath[0] )
2803         icon_name = extract_icon( szIconPath , iIconId, NULL, bWait );
2804     else
2805         icon_name = extract_icon( szPath, iIconId, NULL, bWait );
2806
2807     /* fail - try once again after parent process exit */
2808     if( !icon_name )
2809     {
2810         if (bWait)
2811         {
2812             WINE_WARN("Unable to extract icon, deferring.\n");
2813             goto cleanup;
2814         }
2815         WINE_ERR("failed to extract icon from %s\n",
2816                  wine_dbgstr_w( szIconPath[0] ? szIconPath : szPath ));
2817     }
2818
2819     unix_link = wine_get_unix_file_name(link);
2820     if (unix_link == NULL)
2821     {
2822         WINE_WARN("couldn't find unix path of %s\n", wine_dbgstr_w(link));
2823         goto cleanup;
2824     }
2825
2826     /* check the path */
2827     if( szPath[0] )
2828     {
2829         static const WCHAR exeW[] = {'.','e','x','e',0};
2830         WCHAR *p;
2831
2832         /* check for .exe extension */
2833         if (!(p = strrchrW( szPath, '.' )) ||
2834             strchrW( p, '\\' ) || strchrW( p, '/' ) ||
2835             lstrcmpiW( p, exeW ))
2836         {
2837             /* Not .exe - use 'start.exe' to launch this file */
2838             p = szArgs + lstrlenW(szPath) + 2;
2839             if (szArgs[0])
2840             {
2841                 p[0] = ' ';
2842                 memmove( p+1, szArgs, min( (lstrlenW(szArgs) + 1) * sizeof(szArgs[0]),
2843                                            sizeof(szArgs) - (p + 1 - szArgs) * sizeof(szArgs[0]) ) );
2844             }
2845             else
2846                 p[0] = 0;
2847
2848             szArgs[0] = '"';
2849             lstrcpyW(szArgs + 1, szPath);
2850             p[-1] = '"';
2851
2852             GetWindowsDirectoryW(szPath, MAX_PATH);
2853             lstrcatW(szPath, startW);
2854         }
2855
2856         /* convert app working dir */
2857         if (szWorkDir[0])
2858             work_dir = wine_get_unix_file_name( szWorkDir );
2859     }
2860     else
2861     {
2862         /* if there's no path... try run the link itself */
2863         lstrcpynW(szArgs, link, MAX_PATH);
2864         GetWindowsDirectoryW(szPath, MAX_PATH);
2865         lstrcatW(szPath, startW);
2866     }
2867
2868     /* escape the path and parameters */
2869     escaped_path = escape(szPath);
2870     escaped_args = escape(szArgs);
2871     description = wchars_to_utf8_chars(szDescription);
2872     if (escaped_path == NULL || escaped_args == NULL || description == NULL)
2873     {
2874         WINE_ERR("out of memory allocating/escaping parameters\n");
2875         goto cleanup;
2876     }
2877
2878     start_path = get_start_exe_path();
2879     if (start_path == NULL)
2880     {
2881         WINE_ERR("out of memory\n");
2882         goto cleanup;
2883     }
2884
2885     /* building multiple menus concurrently has race conditions */
2886     hsem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
2887     if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hsem, FALSE, INFINITE, QS_ALLINPUT ) )
2888     {
2889         WINE_ERR("failed wait for semaphore\n");
2890         goto cleanup;
2891     }
2892
2893     if (in_desktop_dir(csidl))
2894     {
2895         char *location;
2896         const char *lastEntry;
2897         lastEntry = strrchr(link_name, '/');
2898         if (lastEntry == NULL)
2899             lastEntry = link_name;
2900         else
2901             ++lastEntry;
2902         location = heap_printf("%s/%s.desktop", xdg_desktop_dir, lastEntry);
2903         if (location)
2904         {
2905             if (csidl == CSIDL_COMMON_DESKTOPDIRECTORY)
2906             {
2907                 char *link_arg = escape_unix_link_arg(unix_link);
2908                 if (link_arg)
2909                 {
2910                     r = !write_desktop_entry(unix_link, location, lastEntry,
2911                         start_path, link_arg, description, work_dir, icon_name);
2912                     HeapFree(GetProcessHeap(), 0, link_arg);
2913                 }
2914             }
2915             else
2916                 r = !write_desktop_entry(NULL, location, lastEntry, escaped_path, escaped_args, description, work_dir, icon_name);
2917             if (r == 0)
2918                 chmod(location, 0755);
2919             HeapFree(GetProcessHeap(), 0, location);
2920         }
2921     }
2922     else
2923     {
2924         char *link_arg = escape_unix_link_arg(unix_link);
2925         if (link_arg)
2926         {
2927             r = !write_menu_entry(unix_link, link_name, start_path, link_arg, description, work_dir, icon_name);
2928             HeapFree(GetProcessHeap(), 0, link_arg);
2929         }
2930     }
2931
2932     ReleaseSemaphore( hsem, 1, NULL );
2933
2934 cleanup:
2935     if (hsem) CloseHandle( hsem );
2936     HeapFree( GetProcessHeap(), 0, icon_name );
2937     HeapFree( GetProcessHeap(), 0, work_dir );
2938     HeapFree( GetProcessHeap(), 0, link_name );
2939     HeapFree( GetProcessHeap(), 0, escaped_args );
2940     HeapFree( GetProcessHeap(), 0, escaped_path );
2941     HeapFree( GetProcessHeap(), 0, description );
2942     HeapFree( GetProcessHeap(), 0, unix_link );
2943     HeapFree( GetProcessHeap(), 0, start_path );
2944
2945     if (r && !bWait)
2946         WINE_ERR("failed to build the menu\n" );
2947
2948     return ( r == 0 );
2949 }
2950
2951 static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link, BOOL bWait )
2952 {
2953     char *link_name = NULL, *icon_name = NULL;
2954     DWORD csidl = -1;
2955     LPWSTR urlPath;
2956     char *escaped_urlPath = NULL;
2957     HRESULT hr;
2958     HANDLE hSem = NULL;
2959     BOOL ret = TRUE;
2960     int r = -1;
2961     char *unix_link = NULL;
2962     IPropertySetStorage *pPropSetStg;
2963     IPropertyStorage *pPropStg;
2964     PROPSPEC ps[2];
2965     PROPVARIANT pv[2];
2966     char *start_path = NULL;
2967
2968     if ( !link )
2969     {
2970         WINE_ERR("Link name is null\n");
2971         return TRUE;
2972     }
2973
2974     if( !GetLinkLocation( link, &csidl, &link_name ) )
2975     {
2976         WINE_WARN("Unknown link location %s. Ignoring.\n",wine_dbgstr_w(link));
2977         return TRUE;
2978     }
2979     if (!in_desktop_dir(csidl) && !in_startmenu(csidl))
2980     {
2981         WINE_WARN("Not under desktop or start menu. Ignoring.\n");
2982         ret = TRUE;
2983         goto cleanup;
2984     }
2985     WINE_TRACE("Link       : %s\n", wine_dbgstr_a(link_name));
2986
2987     hr = url->lpVtbl->GetURL(url, &urlPath);
2988     if (FAILED(hr))
2989     {
2990         ret = TRUE;
2991         goto cleanup;
2992     }
2993     WINE_TRACE("path       : %s\n", wine_dbgstr_w(urlPath));
2994
2995     unix_link = wine_get_unix_file_name(link);
2996     if (unix_link == NULL)
2997     {
2998         WINE_WARN("couldn't find unix path of %s\n", wine_dbgstr_w(link));
2999         goto cleanup;
3000     }
3001
3002     escaped_urlPath = escape(urlPath);
3003     if (escaped_urlPath == NULL)
3004     {
3005         WINE_ERR("couldn't escape url, out of memory\n");
3006         goto cleanup;
3007     }
3008
3009     start_path = get_start_exe_path();
3010     if (start_path == NULL)
3011     {
3012         WINE_ERR("out of memory\n");
3013         goto cleanup;
3014     }
3015
3016     ps[0].ulKind = PRSPEC_PROPID;
3017     ps[0].u.propid = PID_IS_ICONFILE;
3018     ps[1].ulKind = PRSPEC_PROPID;
3019     ps[1].u.propid = PID_IS_ICONINDEX;
3020
3021     hr = url->lpVtbl->QueryInterface(url, &IID_IPropertySetStorage, (void **) &pPropSetStg);
3022     if (SUCCEEDED(hr))
3023     {
3024         hr = IPropertySetStorage_Open(pPropSetStg, &FMTID_Intshcut, STGM_READ | STGM_SHARE_EXCLUSIVE, &pPropStg);
3025         if (SUCCEEDED(hr))
3026         {
3027             hr = IPropertyStorage_ReadMultiple(pPropStg, 2, ps, pv);
3028             if (SUCCEEDED(hr))
3029             {
3030                 if (pv[0].vt == VT_LPWSTR && pv[0].u.pwszVal)
3031                 {
3032                     icon_name = extract_icon( pv[0].u.pwszVal, pv[1].u.iVal, NULL, bWait );
3033
3034                     WINE_TRACE("URL icon path: %s icon index: %d icon name: %s\n", wine_dbgstr_w(pv[0].u.pwszVal), pv[1].u.iVal, icon_name);
3035                 }
3036                 PropVariantClear(&pv[0]);
3037                 PropVariantClear(&pv[1]);
3038             }
3039             IPropertyStorage_Release(pPropStg);
3040         }
3041         IPropertySetStorage_Release(pPropSetStg);
3042     }
3043
3044     /* fail - try once again after parent process exit */
3045     if( !icon_name )
3046     {
3047         if (bWait)
3048         {
3049             WINE_WARN("Unable to extract icon, deferring.\n");
3050             ret = FALSE;
3051             goto cleanup;
3052         }
3053         WINE_ERR("failed to extract icon from %s\n",
3054                  wine_dbgstr_w( pv[0].u.pwszVal ));
3055     }
3056
3057     hSem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
3058     if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hSem, FALSE, INFINITE, QS_ALLINPUT ) )
3059     {
3060         WINE_ERR("failed wait for semaphore\n");
3061         goto cleanup;
3062     }
3063     if (in_desktop_dir(csidl))
3064     {
3065         char *location;
3066         const char *lastEntry;
3067         lastEntry = strrchr(link_name, '/');
3068         if (lastEntry == NULL)
3069             lastEntry = link_name;
3070         else
3071             ++lastEntry;
3072         location = heap_printf("%s/%s.desktop", xdg_desktop_dir, lastEntry);
3073         if (location)
3074         {
3075             r = !write_desktop_entry(NULL, location, lastEntry, start_path, escaped_urlPath, NULL, NULL, icon_name);
3076             if (r == 0)
3077                 chmod(location, 0755);
3078             HeapFree(GetProcessHeap(), 0, location);
3079         }
3080     }
3081     else
3082         r = !write_menu_entry(unix_link, link_name, start_path, escaped_urlPath, NULL, NULL, icon_name);
3083     ret = (r != 0);
3084     ReleaseSemaphore(hSem, 1, NULL);
3085
3086 cleanup:
3087     if (hSem)
3088         CloseHandle(hSem);
3089     HeapFree( GetProcessHeap(), 0, icon_name );
3090     HeapFree(GetProcessHeap(), 0, link_name);
3091     CoTaskMemFree( urlPath );
3092     HeapFree(GetProcessHeap(), 0, escaped_urlPath);
3093     HeapFree(GetProcessHeap(), 0, unix_link);
3094     return ret;
3095 }
3096
3097 static BOOL WaitForParentProcess( void )
3098 {
3099     PROCESSENTRY32 procentry;
3100     HANDLE hsnapshot = NULL, hprocess = NULL;
3101     DWORD ourpid = GetCurrentProcessId();
3102     BOOL ret = FALSE, rc;
3103
3104     WINE_TRACE("Waiting for parent process\n");
3105     if ((hsnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 )) ==
3106         INVALID_HANDLE_VALUE)
3107     {
3108         WINE_ERR("CreateToolhelp32Snapshot failed, error %d\n", GetLastError());
3109         goto done;
3110     }
3111
3112     procentry.dwSize = sizeof(PROCESSENTRY32);
3113     rc = Process32First( hsnapshot, &procentry );
3114     while (rc)
3115     {
3116         if (procentry.th32ProcessID == ourpid) break;
3117         rc = Process32Next( hsnapshot, &procentry );
3118     }
3119     if (!rc)
3120     {
3121         WINE_WARN("Unable to find current process id %d when listing processes\n", ourpid);
3122         goto done;
3123     }
3124
3125     if ((hprocess = OpenProcess( SYNCHRONIZE, FALSE, procentry.th32ParentProcessID )) ==
3126         NULL)
3127     {
3128         WINE_WARN("OpenProcess failed pid=%d, error %d\n", procentry.th32ParentProcessID,
3129                  GetLastError());
3130         goto done;
3131     }
3132
3133     if (MsgWaitForMultipleObjects( 1, &hprocess, FALSE, INFINITE, QS_ALLINPUT ) == WAIT_OBJECT_0)
3134         ret = TRUE;
3135     else
3136         WINE_ERR("Unable to wait for parent process, error %d\n", GetLastError());
3137
3138 done:
3139     if (hprocess) CloseHandle( hprocess );
3140     if (hsnapshot) CloseHandle( hsnapshot );
3141     return ret;
3142 }
3143
3144 static BOOL Process_Link( LPCWSTR linkname, BOOL bWait )
3145 {
3146     IShellLinkW *sl;
3147     IPersistFile *pf;
3148     HRESULT r;
3149     WCHAR fullname[MAX_PATH];
3150     DWORD len;
3151
3152     WINE_TRACE("%s, wait %d\n", wine_dbgstr_w(linkname), bWait);
3153
3154     if( !linkname[0] )
3155     {
3156         WINE_ERR("link name missing\n");
3157         return 1;
3158     }
3159
3160     len=GetFullPathNameW( linkname, MAX_PATH, fullname, NULL );
3161     if (len==0 || len>MAX_PATH)
3162     {
3163         WINE_ERR("couldn't get full path of link file\n");
3164         return 1;
3165     }
3166
3167     r = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
3168                           &IID_IShellLinkW, (LPVOID *) &sl );
3169     if( FAILED( r ) )
3170     {
3171         WINE_ERR("No IID_IShellLink\n");
3172         return 1;
3173     }
3174
3175     r = IShellLinkW_QueryInterface( sl, &IID_IPersistFile, (LPVOID*) &pf );
3176     if( FAILED( r ) )
3177     {
3178         WINE_ERR("No IID_IPersistFile\n");
3179         return 1;
3180     }
3181
3182     r = IPersistFile_Load( pf, fullname, STGM_READ );
3183     if( SUCCEEDED( r ) )
3184     {
3185         /* If something fails (eg. Couldn't extract icon)
3186          * wait for parent process and try again
3187          */
3188         if( ! InvokeShellLinker( sl, fullname, bWait ) && bWait )
3189         {
3190             WaitForParentProcess();
3191             InvokeShellLinker( sl, fullname, FALSE );
3192         }
3193     }
3194     else
3195     {
3196         WINE_ERR("unable to load %s\n", wine_dbgstr_w(linkname));
3197     }
3198
3199     IPersistFile_Release( pf );
3200     IShellLinkW_Release( sl );
3201
3202     return !r;
3203 }
3204
3205 static BOOL Process_URL( LPCWSTR urlname, BOOL bWait )
3206 {
3207     IUniformResourceLocatorW *url;
3208     IPersistFile *pf;
3209     HRESULT r;
3210     WCHAR fullname[MAX_PATH];
3211     DWORD len;
3212
3213     WINE_TRACE("%s, wait %d\n", wine_dbgstr_w(urlname), bWait);
3214
3215     if( !urlname[0] )
3216     {
3217         WINE_ERR("URL name missing\n");
3218         return 1;
3219     }
3220
3221     len=GetFullPathNameW( urlname, MAX_PATH, fullname, NULL );
3222     if (len==0 || len>MAX_PATH)
3223     {
3224         WINE_ERR("couldn't get full path of URL file\n");
3225         return 1;
3226     }
3227
3228     r = CoCreateInstance( &CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER,
3229                           &IID_IUniformResourceLocatorW, (LPVOID *) &url );
3230     if( FAILED( r ) )
3231     {
3232         WINE_ERR("No IID_IUniformResourceLocatorW\n");
3233         return 1;
3234     }
3235
3236     r = url->lpVtbl->QueryInterface( url, &IID_IPersistFile, (LPVOID*) &pf );
3237     if( FAILED( r ) )
3238     {
3239         WINE_ERR("No IID_IPersistFile\n");
3240         return 1;
3241     }
3242     r = IPersistFile_Load( pf, fullname, STGM_READ );
3243     if( SUCCEEDED( r ) )
3244     {
3245         /* If something fails (eg. Couldn't extract icon)
3246          * wait for parent process and try again
3247          */
3248         if( ! InvokeShellLinkerForURL( url, fullname, bWait ) && bWait )
3249         {
3250             WaitForParentProcess();
3251             InvokeShellLinkerForURL( url, fullname, FALSE );
3252         }
3253     }
3254
3255     IPersistFile_Release( pf );
3256     url->lpVtbl->Release( url );
3257
3258     return !r;
3259 }
3260
3261 static void RefreshFileTypeAssociations(void)
3262 {
3263     HANDLE hSem = NULL;
3264     char *mime_dir = NULL;
3265     char *packages_dir = NULL;
3266     char *applications_dir = NULL;
3267     BOOL hasChanged;
3268
3269     hSem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
3270     if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hSem, FALSE, INFINITE, QS_ALLINPUT ) )
3271     {
3272         WINE_ERR("failed wait for semaphore\n");
3273         CloseHandle(hSem);
3274         hSem = NULL;
3275         goto end;
3276     }
3277
3278     mime_dir = heap_printf("%s/mime", xdg_data_dir);
3279     if (mime_dir == NULL)
3280     {
3281         WINE_ERR("out of memory\n");
3282         goto end;
3283     }
3284     create_directories(mime_dir);
3285
3286     packages_dir = heap_printf("%s/packages", mime_dir);
3287     if (packages_dir == NULL)
3288     {
3289         WINE_ERR("out of memory\n");
3290         goto end;
3291     }
3292     create_directories(packages_dir);
3293
3294     applications_dir = heap_printf("%s/applications", xdg_data_dir);
3295     if (applications_dir == NULL)
3296     {
3297         WINE_ERR("out of memory\n");
3298         goto end;
3299     }
3300     create_directories(applications_dir);
3301
3302     hasChanged = generate_associations(xdg_data_dir, packages_dir, applications_dir);
3303     hasChanged |= cleanup_associations();
3304     if (hasChanged)
3305     {
3306         const char *argv[3];
3307
3308         argv[0] = "update-mime-database";
3309         argv[1] = mime_dir;
3310         argv[2] = NULL;
3311         spawnvp( _P_NOWAIT, argv[0], argv );
3312
3313         argv[0] = "update-desktop-database";
3314         argv[1] = applications_dir;
3315         spawnvp( _P_NOWAIT, argv[0], argv );
3316     }
3317
3318 end:
3319     if (hSem)
3320     {
3321         ReleaseSemaphore(hSem, 1, NULL);
3322         CloseHandle(hSem);
3323     }
3324     HeapFree(GetProcessHeap(), 0, mime_dir);
3325     HeapFree(GetProcessHeap(), 0, packages_dir);
3326     HeapFree(GetProcessHeap(), 0, applications_dir);
3327 }
3328
3329 static void cleanup_menus(void)
3330 {
3331     HKEY hkey;
3332
3333     hkey = open_menus_reg_key();
3334     if (hkey)
3335     {
3336         int i;
3337         LSTATUS lret = ERROR_SUCCESS;
3338         for (i = 0; lret == ERROR_SUCCESS; )
3339         {
3340             WCHAR *value = NULL;
3341             WCHAR *data = NULL;
3342             DWORD valueSize = 4096;
3343             DWORD dataSize = 4096;
3344             while (1)
3345             {
3346                 lret = ERROR_OUTOFMEMORY;
3347                 value = HeapAlloc(GetProcessHeap(), 0, valueSize * sizeof(WCHAR));
3348                 if (value == NULL)
3349                     break;
3350                 data = HeapAlloc(GetProcessHeap(), 0, dataSize * sizeof(WCHAR));
3351                 if (data == NULL)
3352                     break;
3353                 lret = RegEnumValueW(hkey, i, value, &valueSize, NULL, NULL, (BYTE*)data, &dataSize);
3354                 if (lret == ERROR_SUCCESS || lret != ERROR_MORE_DATA)
3355                     break;
3356                 valueSize *= 2;
3357                 dataSize *= 2;
3358                 HeapFree(GetProcessHeap(), 0, value);
3359                 HeapFree(GetProcessHeap(), 0, data);
3360                 value = data = NULL;
3361             }
3362             if (lret == ERROR_SUCCESS)
3363             {
3364                 char *unix_file;
3365                 char *windows_file;
3366                 unix_file = wchars_to_unix_chars(value);
3367                 windows_file = wchars_to_unix_chars(data);
3368                 if (unix_file != NULL && windows_file != NULL)
3369                 {
3370                     struct stat filestats;
3371                     if (stat(windows_file, &filestats) < 0 && errno == ENOENT)
3372                     {
3373                         WINE_TRACE("removing menu related file %s\n", unix_file);
3374                         remove(unix_file);
3375                         RegDeleteValueW(hkey, value);
3376                     }
3377                     else
3378                         i++;
3379                 }
3380                 else
3381                 {
3382                     WINE_ERR("out of memory enumerating menus\n");
3383                     lret = ERROR_OUTOFMEMORY;
3384                 }
3385                 HeapFree(GetProcessHeap(), 0, unix_file);
3386                 HeapFree(GetProcessHeap(), 0, windows_file);
3387             }
3388             else if (lret != ERROR_NO_MORE_ITEMS)
3389                 WINE_ERR("error %d reading registry\n", lret);
3390             HeapFree(GetProcessHeap(), 0, value);
3391             HeapFree(GetProcessHeap(), 0, data);
3392         }
3393         RegCloseKey(hkey);
3394     }
3395     else
3396         WINE_ERR("error opening registry key, menu cleanup failed\n");
3397 }
3398
3399 static void thumbnail_lnk(LPCWSTR lnkPath, LPCWSTR outputPath)
3400 {
3401     char *utf8lnkPath = NULL;
3402     char *utf8OutputPath = NULL;
3403     WCHAR *winLnkPath = NULL;
3404     IShellLinkW *shellLink = NULL;
3405     IPersistFile *persistFile = NULL;
3406     WCHAR szTmp[MAX_PATH];
3407     WCHAR szPath[MAX_PATH];
3408     WCHAR szArgs[INFOTIPSIZE];
3409     WCHAR szIconPath[MAX_PATH];
3410     int iconId;
3411     IStream *stream = NULL;
3412     HRESULT hr;
3413
3414     utf8lnkPath = wchars_to_utf8_chars(lnkPath);
3415     if (utf8lnkPath == NULL)
3416     {
3417         WINE_ERR("out of memory converting paths\n");
3418         goto end;
3419     }
3420
3421     utf8OutputPath = wchars_to_utf8_chars(outputPath);
3422     if (utf8OutputPath == NULL)
3423     {
3424         WINE_ERR("out of memory converting paths\n");
3425         goto end;
3426     }
3427
3428     winLnkPath = wine_get_dos_file_name(utf8lnkPath);
3429     if (winLnkPath == NULL)
3430     {
3431         WINE_ERR("could not convert %s to DOS path\n", utf8lnkPath);
3432         goto end;
3433     }
3434
3435     hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
3436                           &IID_IShellLinkW, (LPVOID*)&shellLink);
3437     if (FAILED(hr))
3438     {
3439         WINE_ERR("could not create IShellLinkW, error 0x%08X\n", hr);
3440         goto end;
3441     }
3442
3443     hr = IShellLinkW_QueryInterface(shellLink, &IID_IPersistFile, (LPVOID)&persistFile);
3444     if (FAILED(hr))
3445     {
3446         WINE_ERR("could not query IPersistFile, error 0x%08X\n", hr);
3447         goto end;
3448     }
3449
3450     hr = IPersistFile_Load(persistFile, winLnkPath, STGM_READ);
3451     if (FAILED(hr))
3452     {
3453         WINE_ERR("could not read .lnk, error 0x%08X\n", hr);
3454         goto end;
3455     }
3456
3457     get_cmdline(shellLink, szTmp, MAX_PATH, szArgs, INFOTIPSIZE);
3458     ExpandEnvironmentStringsW(szTmp, szPath, MAX_PATH);
3459     szTmp[0] = 0;
3460     IShellLinkW_GetIconLocation(shellLink, szTmp, MAX_PATH, &iconId);
3461     ExpandEnvironmentStringsW(szTmp, szIconPath, MAX_PATH);
3462
3463     if(!szPath[0])
3464     {
3465         LPITEMIDLIST pidl = NULL;
3466         IShellLinkW_GetIDList(shellLink, &pidl);
3467         if (pidl && SHGetPathFromIDListW(pidl, szPath))
3468             WINE_TRACE("pidl path  : %s\n", wine_dbgstr_w(szPath));
3469     }
3470
3471     if (szIconPath[0])
3472     {
3473         hr = open_icon(szIconPath, iconId, FALSE, &stream);
3474         if (SUCCEEDED(hr))
3475             hr = write_native_icon(stream, utf8OutputPath, NULL);
3476     }
3477     else
3478     {
3479         hr = open_icon(szPath, iconId, FALSE, &stream);
3480         if (SUCCEEDED(hr))
3481             hr = write_native_icon(stream, utf8OutputPath, NULL);
3482     }
3483
3484 end:
3485     HeapFree(GetProcessHeap(), 0, utf8lnkPath);
3486     HeapFree(GetProcessHeap(), 0, utf8OutputPath);
3487     HeapFree(GetProcessHeap(), 0, winLnkPath);
3488     if (shellLink != NULL)
3489         IShellLinkW_Release(shellLink);
3490     if (persistFile != NULL)
3491         IPersistFile_Release(persistFile);
3492     if (stream != NULL)
3493         IStream_Release(stream);
3494 }
3495
3496 static WCHAR *next_token( LPWSTR *p )
3497 {
3498     LPWSTR token = NULL, t = *p;
3499
3500     if( !t )
3501         return NULL;
3502
3503     while( t && !token )
3504     {
3505         switch( *t )
3506         {
3507         case ' ':
3508             t++;
3509             continue;
3510         case '"':
3511             /* unquote the token */
3512             token = ++t;
3513             t = strchrW( token, '"' );
3514             if( t )
3515                  *t++ = 0;
3516             break;
3517         case 0:
3518             t = NULL;
3519             break;
3520         default:
3521             token = t;
3522             t = strchrW( token, ' ' );
3523             if( t )
3524                  *t++ = 0;
3525             break;
3526         }
3527     }
3528     *p = t;
3529     return token;
3530 }
3531
3532 static BOOL init_xdg(void)
3533 {
3534     WCHAR shellDesktopPath[MAX_PATH];
3535     HRESULT hr = SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, shellDesktopPath);
3536     if (SUCCEEDED(hr))
3537         xdg_desktop_dir = wine_get_unix_file_name(shellDesktopPath);
3538     if (xdg_desktop_dir == NULL)
3539     {
3540         WINE_ERR("error looking up the desktop directory\n");
3541         return FALSE;
3542     }
3543
3544     if (getenv("XDG_CONFIG_HOME"))
3545         xdg_config_dir = heap_printf("%s/menus/applications-merged", getenv("XDG_CONFIG_HOME"));
3546     else
3547         xdg_config_dir = heap_printf("%s/.config/menus/applications-merged", getenv("HOME"));
3548     if (xdg_config_dir)
3549     {
3550         create_directories(xdg_config_dir);
3551         if (getenv("XDG_DATA_HOME"))
3552             xdg_data_dir = strdupA(getenv("XDG_DATA_HOME"));
3553         else
3554             xdg_data_dir = heap_printf("%s/.local/share", getenv("HOME"));
3555         if (xdg_data_dir)
3556         {
3557             char *buffer;
3558             create_directories(xdg_data_dir);
3559             buffer = heap_printf("%s/desktop-directories", xdg_data_dir);
3560             if (buffer)
3561             {
3562                 mkdir(buffer, 0777);
3563                 HeapFree(GetProcessHeap(), 0, buffer);
3564             }
3565             return TRUE;
3566         }
3567         HeapFree(GetProcessHeap(), 0, xdg_config_dir);
3568     }
3569     WINE_ERR("out of memory\n");
3570     return FALSE;
3571 }
3572
3573 /***********************************************************************
3574  *
3575  *           wWinMain
3576  */
3577 int PASCAL wWinMain (HINSTANCE hInstance, HINSTANCE prev, LPWSTR cmdline, int show)
3578 {
3579     static const WCHAR dash_aW[] = {'-','a',0};
3580     static const WCHAR dash_rW[] = {'-','r',0};
3581     static const WCHAR dash_tW[] = {'-','t',0};
3582     static const WCHAR dash_uW[] = {'-','u',0};
3583     static const WCHAR dash_wW[] = {'-','w',0};
3584
3585     LPWSTR token = NULL, p;
3586     BOOL bWait = FALSE;
3587     BOOL bURL = FALSE;
3588     HRESULT hr;
3589     int ret = 0;
3590
3591     if (!init_xdg())
3592         return 1;
3593
3594     hr = CoInitialize(NULL);
3595     if (FAILED(hr))
3596     {
3597         WINE_ERR("could not initialize COM, error 0x%08X\n", hr);
3598         return 1;
3599     }
3600
3601     for( p = cmdline; p && *p; )
3602     {
3603         token = next_token( &p );
3604         if( !token )
3605             break;
3606         if( !strcmpW( token, dash_aW ) )
3607         {
3608             RefreshFileTypeAssociations();
3609             continue;
3610         }
3611         if( !strcmpW( token, dash_rW ) )
3612         {
3613             cleanup_menus();
3614             continue;
3615         }
3616         if( !strcmpW( token, dash_wW ) )
3617             bWait = TRUE;
3618         else if ( !strcmpW( token, dash_uW ) )
3619             bURL = TRUE;
3620         else if ( !strcmpW( token, dash_tW ) )
3621         {
3622             WCHAR *lnkFile = next_token( &p );
3623             if (lnkFile)
3624             {
3625                  WCHAR *outputFile = next_token( &p );
3626                  if (outputFile)
3627                      thumbnail_lnk(lnkFile, outputFile);
3628             }
3629         }
3630         else if( token[0] == '-' )
3631         {
3632             WINE_ERR( "unknown option %s\n", wine_dbgstr_w(token) );
3633         }
3634         else
3635         {
3636             BOOL bRet;
3637
3638             if (bURL)
3639                 bRet = Process_URL( token, bWait );
3640             else
3641                 bRet = Process_Link( token, bWait );
3642             if (!bRet)
3643             {
3644                 WINE_ERR( "failed to build menu item for %s\n", wine_dbgstr_w(token) );
3645                 ret = 1;
3646             }
3647         }
3648     }
3649
3650     CoUninitialize();
3651     return ret;
3652 }