wscript: Fix tests on wow64.
[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     WCHAR *extension;
978     WCHAR *icon = NULL;
979     WCHAR *comma;
980     WCHAR *executable = NULL;
981     int index = 0;
982     char *output_path = NULL;
983     HRESULT hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
984
985     extension = strrchrW(szFileName, '.');
986     if (extension == NULL)
987         goto end;
988
989     icon = assoc_query(ASSOCSTR_DEFAULTICON, extension, NULL);
990     if (icon)
991     {
992         comma = strrchrW(icon, ',');
993         if (comma)
994         {
995             *comma = 0;
996             index = atoiW(comma + 1);
997         }
998         hr = open_icon(icon, index, FALSE, ppStream);
999     }
1000     else
1001     {
1002         executable = assoc_query(ASSOCSTR_EXECUTABLE, extension, NULL);
1003         if (executable)
1004             hr = open_icon(executable, 0, FALSE, ppStream);
1005     }
1006
1007 end:
1008     HeapFree(GetProcessHeap(), 0, icon);
1009     HeapFree(GetProcessHeap(), 0, executable);
1010     HeapFree(GetProcessHeap(), 0, output_path);
1011     return hr;
1012 }
1013
1014 static HRESULT open_default_icon(IStream **ppStream)
1015 {
1016     static const WCHAR user32W[] = {'u','s','e','r','3','2',0};
1017
1018     return open_module_icon(user32W, -(INT_PTR)IDI_WINLOGO, ppStream);
1019 }
1020
1021 static HRESULT open_icon(LPCWSTR filename, int index, BOOL bWait, IStream **ppStream)
1022 {
1023     HRESULT hr;
1024
1025     hr = open_module_icon(filename, index, ppStream);
1026     if (FAILED(hr))
1027     {
1028         static const WCHAR dot_icoW[] = {'.','i','c','o',0};
1029         int len = strlenW(filename);
1030         if (len >= 4 && strcmpiW(&filename[len - 4], dot_icoW) == 0)
1031             hr = SHCreateStreamOnFileW(filename, STGM_READ, ppStream);
1032     }
1033     if (FAILED(hr))
1034         hr = open_file_type_icon(filename, ppStream);
1035     if (FAILED(hr) && !bWait)
1036         hr = open_default_icon(ppStream);
1037     return hr;
1038 }
1039
1040 #ifdef __APPLE__
1041 #define ICNS_SLOTS 6
1042
1043 static inline int size_to_slot(int size)
1044 {
1045     switch (size)
1046     {
1047         case 16: return 0;
1048         case 32: return 1;
1049         case 48: return 2;
1050         case 128: return 3;
1051         case 256: return 4;
1052         case 512: return 5;
1053     }
1054
1055     return -1;
1056 }
1057
1058 static HRESULT platform_write_icon(IStream *icoStream, int exeIndex, LPCWSTR icoPathW,
1059                                    const char *destFilename, char **nativeIdentifier)
1060 {
1061     ICONDIRENTRY *iconDirEntries = NULL;
1062     int numEntries;
1063     struct {
1064         int index;
1065         int maxBits;
1066     } best[ICNS_SLOTS];
1067     int indexes[ICNS_SLOTS];
1068     int i;
1069     GUID guid;
1070     WCHAR *guidStrW = NULL;
1071     char *guidStrA = NULL;
1072     char *icnsPath = NULL;
1073     LARGE_INTEGER zero;
1074     HRESULT hr;
1075
1076     hr = read_ico_direntries(icoStream, &iconDirEntries, &numEntries);
1077     if (FAILED(hr))
1078         goto end;
1079     for (i = 0; i < ICNS_SLOTS; i++)
1080     {
1081         best[i].index = -1;
1082         best[i].maxBits = 0;
1083     }
1084     for (i = 0; i < numEntries; i++)
1085     {
1086         int slot;
1087         int width = iconDirEntries[i].bWidth ? iconDirEntries[i].bWidth : 256;
1088         int height = iconDirEntries[i].bHeight ? iconDirEntries[i].bHeight : 256;
1089
1090         WINE_TRACE("[%d]: %d x %d @ %d\n", i, width, height, iconDirEntries[i].wBitCount);
1091         if (height != width)
1092             continue;
1093         slot = size_to_slot(width);
1094         if (slot < 0)
1095             continue;
1096         if (iconDirEntries[i].wBitCount >= best[slot].maxBits)
1097         {
1098             best[slot].index = i;
1099             best[slot].maxBits = iconDirEntries[i].wBitCount;
1100         }
1101     }
1102     numEntries = 0;
1103     for (i = 0; i < ICNS_SLOTS; i++)
1104     {
1105         if (best[i].index >= 0)
1106         {
1107             indexes[numEntries] = best[i].index;
1108             numEntries++;
1109         }
1110     }
1111
1112     hr = CoCreateGuid(&guid);
1113     if (FAILED(hr))
1114     {
1115         WINE_WARN("CoCreateGuid failed, error 0x%08X\n", hr);
1116         goto end;
1117     }
1118     hr = StringFromCLSID(&guid, &guidStrW);
1119     if (FAILED(hr))
1120     {
1121         WINE_WARN("StringFromCLSID failed, error 0x%08X\n", hr);
1122         goto end;
1123     }
1124     guidStrA = wchars_to_utf8_chars(guidStrW);
1125     if (guidStrA == NULL)
1126     {
1127         hr = E_OUTOFMEMORY;
1128         WINE_WARN("out of memory converting GUID string\n");
1129         goto end;
1130     }
1131     icnsPath = heap_printf("/tmp/%s.icns", guidStrA);
1132     if (icnsPath == NULL)
1133     {
1134         hr = E_OUTOFMEMORY;
1135         WINE_WARN("out of memory creating ICNS path\n");
1136         goto end;
1137     }
1138     zero.QuadPart = 0;
1139     hr = IStream_Seek(icoStream, zero, STREAM_SEEK_SET, NULL);
1140     if (FAILED(hr))
1141     {
1142         WINE_WARN("seeking icon stream failed, error 0x%08X\n", hr);
1143         goto end;
1144     }
1145     hr = convert_to_native_icon(icoStream, indexes, numEntries, &CLSID_WICIcnsEncoder,
1146                                 icnsPath, icoPathW);
1147     if (FAILED(hr))
1148     {
1149         WINE_WARN("converting %s to %s failed, error 0x%08X\n",
1150             wine_dbgstr_w(icoPathW), wine_dbgstr_a(icnsPath), hr);
1151         goto end;
1152     }
1153
1154 end:
1155     HeapFree(GetProcessHeap(), 0, iconDirEntries);
1156     CoTaskMemFree(guidStrW);
1157     HeapFree(GetProcessHeap(), 0, guidStrA);
1158     if (SUCCEEDED(hr))
1159         *nativeIdentifier = icnsPath;
1160     else
1161         HeapFree(GetProcessHeap(), 0, icnsPath);
1162     return hr;
1163 }
1164 #else
1165 static void refresh_icon_cache(const char *iconsDir)
1166 {
1167     /* The icon theme spec only requires the mtime on the "toplevel"
1168      * directory (whatever that is) to be changed for a refresh,
1169      * but on Gnome you have to create a file in that directory
1170      * instead. Creating a file also works on KDE, XFCE and LXDE.
1171      */
1172     char *filename = heap_printf("%s/.wine-refresh-XXXXXX", iconsDir);
1173     if (filename != NULL)
1174     {
1175         int fd = mkstemps(filename, 0);
1176         if (fd >= 0)
1177         {
1178             close(fd);
1179             unlink(filename);
1180         }
1181         HeapFree(GetProcessHeap(), 0, filename);
1182     }
1183 }
1184
1185 static HRESULT platform_write_icon(IStream *icoStream, int exeIndex, LPCWSTR icoPathW,
1186                                    const char *destFilename, char **nativeIdentifier)
1187 {
1188     ICONDIRENTRY *iconDirEntries = NULL;
1189     int numEntries;
1190     int i;
1191     char *icoPathA = NULL;
1192     char *iconsDir = NULL;
1193     unsigned short crc;
1194     char *p, *q;
1195     HRESULT hr = S_OK;
1196     LARGE_INTEGER zero;
1197
1198     hr = read_ico_direntries(icoStream, &iconDirEntries, &numEntries);
1199     if (FAILED(hr))
1200         goto end;
1201
1202     icoPathA = wchars_to_utf8_chars(icoPathW);
1203     if (icoPathA == NULL)
1204     {
1205         hr = E_OUTOFMEMORY;
1206         goto end;
1207     }
1208     crc = crc16(icoPathA);
1209     p = strrchr(icoPathA, '\\');
1210     if (p == NULL)
1211         p = icoPathA;
1212     else
1213     {
1214         *p = 0;
1215         p++;
1216     }
1217     q = strrchr(p, '.');
1218     if (q)
1219         *q = 0;
1220     if (destFilename)
1221         *nativeIdentifier = heap_printf("%s", destFilename);
1222     else
1223         *nativeIdentifier = heap_printf("%04X_%s.%d", crc, p, exeIndex);
1224     if (*nativeIdentifier == NULL)
1225     {
1226         hr = E_OUTOFMEMORY;
1227         goto end;
1228     }
1229     iconsDir = heap_printf("%s/icons/hicolor", xdg_data_dir);
1230     if (iconsDir == NULL)
1231     {
1232         hr = E_OUTOFMEMORY;
1233         goto end;
1234     }
1235
1236     for (i = 0; i < numEntries; i++)
1237     {
1238         int bestIndex = i;
1239         int j;
1240         BOOLEAN duplicate = FALSE;
1241         int w, h;
1242         char *iconDir = NULL;
1243         char *pngPath = NULL;
1244
1245         WINE_TRACE("[%d]: %d x %d @ %d\n", i, iconDirEntries[i].bWidth,
1246             iconDirEntries[i].bHeight, iconDirEntries[i].wBitCount);
1247
1248         for (j = 0; j < i; j++)
1249         {
1250             if (iconDirEntries[j].bWidth == iconDirEntries[i].bWidth &&
1251                 iconDirEntries[j].bHeight == iconDirEntries[i].bHeight)
1252             {
1253                 duplicate = TRUE;
1254                 break;
1255             }
1256         }
1257         if (duplicate)
1258             continue;
1259         for (j = i + 1; j < numEntries; j++)
1260         {
1261             if (iconDirEntries[j].bWidth == iconDirEntries[i].bWidth &&
1262                 iconDirEntries[j].bHeight == iconDirEntries[i].bHeight &&
1263                 iconDirEntries[j].wBitCount >= iconDirEntries[bestIndex].wBitCount)
1264             {
1265                 bestIndex = j;
1266             }
1267         }
1268         WINE_TRACE("Selected: %d\n", bestIndex);
1269
1270         w = iconDirEntries[bestIndex].bWidth ? iconDirEntries[bestIndex].bWidth : 256;
1271         h = iconDirEntries[bestIndex].bHeight ? iconDirEntries[bestIndex].bHeight : 256;
1272         iconDir = heap_printf("%s/%dx%d/apps", iconsDir, w, h);
1273         if (iconDir == NULL)
1274         {
1275             hr = E_OUTOFMEMORY;
1276             goto endloop;
1277         }
1278         create_directories(iconDir);
1279         pngPath = heap_printf("%s/%s.png", iconDir, *nativeIdentifier);
1280         if (pngPath == NULL)
1281         {
1282             hr = E_OUTOFMEMORY;
1283             goto endloop;
1284         }
1285         zero.QuadPart = 0;
1286         hr = IStream_Seek(icoStream, zero, STREAM_SEEK_SET, NULL);
1287         if (FAILED(hr))
1288             goto endloop;
1289         hr = convert_to_native_icon(icoStream, &bestIndex, 1, &CLSID_WICPngEncoder,
1290                                     pngPath, icoPathW);
1291
1292     endloop:
1293         HeapFree(GetProcessHeap(), 0, iconDir);
1294         HeapFree(GetProcessHeap(), 0, pngPath);
1295     }
1296     refresh_icon_cache(iconsDir);
1297
1298 end:
1299     HeapFree(GetProcessHeap(), 0, iconDirEntries);
1300     HeapFree(GetProcessHeap(), 0, icoPathA);
1301     HeapFree(GetProcessHeap(), 0, iconsDir);
1302     return hr;
1303 }
1304 #endif /* defined(__APPLE__) */
1305
1306 /* extract an icon from an exe or icon file; helper for IPersistFile_fnSave */
1307 static char *extract_icon(LPCWSTR icoPathW, int index, const char *destFilename, BOOL bWait)
1308 {
1309     IStream *stream = NULL;
1310     HRESULT hr;
1311     char *nativeIdentifier = NULL;
1312
1313     WINE_TRACE("path=[%s] index=%d destFilename=[%s]\n", wine_dbgstr_w(icoPathW), index, wine_dbgstr_a(destFilename));
1314
1315     hr = open_icon(icoPathW, index, bWait, &stream);
1316     if (FAILED(hr))
1317     {
1318         WINE_WARN("opening icon %s index %d failed, hr=0x%08X\n", wine_dbgstr_w(icoPathW), index, hr);
1319         goto end;
1320     }
1321     hr = platform_write_icon(stream, index, icoPathW, destFilename, &nativeIdentifier);
1322     if (FAILED(hr))
1323         WINE_WARN("writing icon failed, error 0x%08X\n", hr);
1324
1325 end:
1326     if (stream)
1327         IStream_Release(stream);
1328     if (FAILED(hr))
1329     {
1330         HeapFree(GetProcessHeap(), 0, nativeIdentifier);
1331         nativeIdentifier = NULL;
1332     }
1333     return nativeIdentifier;
1334 }
1335
1336 static HKEY open_menus_reg_key(void)
1337 {
1338     static const WCHAR Software_Wine_FileOpenAssociationsW[] = {
1339         'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\','M','e','n','u','F','i','l','e','s',0};
1340     HKEY assocKey;
1341     DWORD ret;
1342     ret = RegCreateKeyW(HKEY_CURRENT_USER, Software_Wine_FileOpenAssociationsW, &assocKey);
1343     if (ret == ERROR_SUCCESS)
1344         return assocKey;
1345     SetLastError(ret);
1346     return NULL;
1347 }
1348
1349 static DWORD register_menus_entry(const char *unix_file, const char *windows_file)
1350 {
1351     WCHAR *unix_fileW;
1352     WCHAR *windows_fileW;
1353     INT size;
1354     DWORD ret;
1355
1356     size = MultiByteToWideChar(CP_UNIXCP, 0, unix_file, -1, NULL, 0);
1357     unix_fileW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1358     if (unix_fileW)
1359     {
1360         MultiByteToWideChar(CP_UNIXCP, 0, unix_file, -1, unix_fileW, size);
1361         size = MultiByteToWideChar(CP_UNIXCP, 0, windows_file, -1, NULL, 0);
1362         windows_fileW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1363         if (windows_fileW)
1364         {
1365             HKEY hkey;
1366             MultiByteToWideChar(CP_UNIXCP, 0, windows_file, -1, windows_fileW, size);
1367             hkey = open_menus_reg_key();
1368             if (hkey)
1369             {
1370                 ret = RegSetValueExW(hkey, unix_fileW, 0, REG_SZ, (const BYTE*)windows_fileW,
1371                     (strlenW(windows_fileW) + 1) * sizeof(WCHAR));
1372                 RegCloseKey(hkey);
1373             }
1374             else
1375                 ret = GetLastError();
1376             HeapFree(GetProcessHeap(), 0, windows_fileW);
1377         }
1378         else
1379             ret = ERROR_NOT_ENOUGH_MEMORY;
1380         HeapFree(GetProcessHeap(), 0, unix_fileW);
1381     }
1382     else
1383         ret = ERROR_NOT_ENOUGH_MEMORY;
1384     return ret;
1385 }
1386
1387 static BOOL write_desktop_entry(const char *unix_link, const char *location, const char *linkname,
1388                                 const char *path, const char *args, const char *descr,
1389                                 const char *workdir, const char *icon)
1390 {
1391     FILE *file;
1392
1393     WINE_TRACE("(%s,%s,%s,%s,%s,%s,%s,%s)\n", wine_dbgstr_a(unix_link), wine_dbgstr_a(location),
1394                wine_dbgstr_a(linkname), wine_dbgstr_a(path), wine_dbgstr_a(args),
1395                wine_dbgstr_a(descr), wine_dbgstr_a(workdir), wine_dbgstr_a(icon));
1396
1397     file = fopen(location, "w");
1398     if (file == NULL)
1399         return FALSE;
1400
1401     fprintf(file, "[Desktop Entry]\n");
1402     fprintf(file, "Name=%s\n", linkname);
1403     fprintf(file, "Exec=env WINEPREFIX=\"%s\" wine %s %s\n",
1404             wine_get_config_dir(), path, args);
1405     fprintf(file, "Type=Application\n");
1406     fprintf(file, "StartupNotify=true\n");
1407     if (descr && lstrlenA(descr))
1408         fprintf(file, "Comment=%s\n", descr);
1409     if (workdir && lstrlenA(workdir))
1410         fprintf(file, "Path=%s\n", workdir);
1411     if (icon && lstrlenA(icon))
1412         fprintf(file, "Icon=%s\n", icon);
1413
1414     fclose(file);
1415
1416     if (unix_link)
1417     {
1418         DWORD ret = register_menus_entry(location, unix_link);
1419         if (ret != ERROR_SUCCESS)
1420             return FALSE;
1421     }
1422
1423     return TRUE;
1424 }
1425
1426 static BOOL write_directory_entry(const char *directory, const char *location)
1427 {
1428     FILE *file;
1429
1430     WINE_TRACE("(%s,%s)\n", wine_dbgstr_a(directory), wine_dbgstr_a(location));
1431
1432     file = fopen(location, "w");
1433     if (file == NULL)
1434         return FALSE;
1435
1436     fprintf(file, "[Desktop Entry]\n");
1437     fprintf(file, "Type=Directory\n");
1438     if (strcmp(directory, "wine") == 0)
1439     {
1440         fprintf(file, "Name=Wine\n");
1441         fprintf(file, "Icon=wine\n");
1442     }
1443     else
1444     {
1445         fprintf(file, "Name=%s\n", directory);
1446         fprintf(file, "Icon=folder\n");
1447     }
1448
1449     fclose(file);
1450     return TRUE;
1451 }
1452
1453 static BOOL write_menu_file(const char *unix_link, const char *filename)
1454 {
1455     char *tempfilename;
1456     FILE *tempfile = NULL;
1457     char *lastEntry;
1458     char *name = NULL;
1459     char *menuPath = NULL;
1460     int i;
1461     int count = 0;
1462     BOOL ret = FALSE;
1463
1464     WINE_TRACE("(%s)\n", wine_dbgstr_a(filename));
1465
1466     while (1)
1467     {
1468         tempfilename = heap_printf("%s/wine-menu-XXXXXX", xdg_config_dir);
1469         if (tempfilename)
1470         {
1471             int tempfd = mkstemps(tempfilename, 0);
1472             if (tempfd >= 0)
1473             {
1474                 tempfile = fdopen(tempfd, "w");
1475                 if (tempfile)
1476                     break;
1477                 close(tempfd);
1478                 goto end;
1479             }
1480             else if (errno == EEXIST)
1481             {
1482                 HeapFree(GetProcessHeap(), 0, tempfilename);
1483                 continue;
1484             }
1485             HeapFree(GetProcessHeap(), 0, tempfilename);
1486         }
1487         return FALSE;
1488     }
1489
1490     fprintf(tempfile, "<!DOCTYPE Menu PUBLIC \"-//freedesktop//DTD Menu 1.0//EN\"\n");
1491     fprintf(tempfile, "\"http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd\">\n");
1492     fprintf(tempfile, "<Menu>\n");
1493     fprintf(tempfile, "  <Name>Applications</Name>\n");
1494
1495     name = HeapAlloc(GetProcessHeap(), 0, lstrlenA(filename) + 1);
1496     if (name == NULL) goto end;
1497     lastEntry = name;
1498     for (i = 0; filename[i]; i++)
1499     {
1500         name[i] = filename[i];
1501         if (filename[i] == '/')
1502         {
1503             char *dir_file_name;
1504             struct stat st;
1505             name[i] = 0;
1506             fprintf(tempfile, "  <Menu>\n");
1507             fprintf(tempfile, "    <Name>%s", count ? "" : "wine-");
1508             write_xml_text(tempfile, name);
1509             fprintf(tempfile, "</Name>\n");
1510             fprintf(tempfile, "    <Directory>%s", count ? "" : "wine-");
1511             write_xml_text(tempfile, name);
1512             fprintf(tempfile, ".directory</Directory>\n");
1513             dir_file_name = heap_printf("%s/desktop-directories/%s%s.directory",
1514                 xdg_data_dir, count ? "" : "wine-", name);
1515             if (dir_file_name)
1516             {
1517                 if (stat(dir_file_name, &st) != 0 && errno == ENOENT)
1518                     write_directory_entry(lastEntry, dir_file_name);
1519                 HeapFree(GetProcessHeap(), 0, dir_file_name);
1520             }
1521             name[i] = '-';
1522             lastEntry = &name[i+1];
1523             ++count;
1524         }
1525     }
1526     name[i] = 0;
1527
1528     fprintf(tempfile, "    <Include>\n");
1529     fprintf(tempfile, "      <Filename>");
1530     write_xml_text(tempfile, name);
1531     fprintf(tempfile, "</Filename>\n");
1532     fprintf(tempfile, "    </Include>\n");
1533     for (i = 0; i < count; i++)
1534          fprintf(tempfile, "  </Menu>\n");
1535     fprintf(tempfile, "</Menu>\n");
1536
1537     menuPath = heap_printf("%s/%s", xdg_config_dir, name);
1538     if (menuPath == NULL) goto end;
1539     strcpy(menuPath + strlen(menuPath) - strlen(".desktop"), ".menu");
1540     ret = TRUE;
1541
1542 end:
1543     if (tempfile)
1544         fclose(tempfile);
1545     if (ret)
1546         ret = (rename(tempfilename, menuPath) == 0);
1547     if (!ret && tempfilename)
1548         remove(tempfilename);
1549     HeapFree(GetProcessHeap(), 0, tempfilename);
1550     if (ret)
1551         register_menus_entry(menuPath, unix_link);
1552     HeapFree(GetProcessHeap(), 0, name);
1553     HeapFree(GetProcessHeap(), 0, menuPath);
1554     return ret;
1555 }
1556
1557 static BOOL write_menu_entry(const char *unix_link, const char *link, const char *path, const char *args,
1558                              const char *descr, const char *workdir, const char *icon)
1559 {
1560     const char *linkname;
1561     char *desktopPath = NULL;
1562     char *desktopDir;
1563     char *filename = NULL;
1564     BOOL ret = TRUE;
1565
1566     WINE_TRACE("(%s, %s, %s, %s, %s, %s, %s)\n", wine_dbgstr_a(unix_link), wine_dbgstr_a(link),
1567                wine_dbgstr_a(path), wine_dbgstr_a(args), wine_dbgstr_a(descr),
1568                wine_dbgstr_a(workdir), wine_dbgstr_a(icon));
1569
1570     linkname = strrchr(link, '/');
1571     if (linkname == NULL)
1572         linkname = link;
1573     else
1574         ++linkname;
1575
1576     desktopPath = heap_printf("%s/applications/wine/%s.desktop", xdg_data_dir, link);
1577     if (!desktopPath)
1578     {
1579         WINE_WARN("out of memory creating menu entry\n");
1580         ret = FALSE;
1581         goto end;
1582     }
1583     desktopDir = strrchr(desktopPath, '/');
1584     *desktopDir = 0;
1585     if (!create_directories(desktopPath))
1586     {
1587         WINE_WARN("couldn't make parent directories for %s\n", wine_dbgstr_a(desktopPath));
1588         ret = FALSE;
1589         goto end;
1590     }
1591     *desktopDir = '/';
1592     if (!write_desktop_entry(unix_link, desktopPath, linkname, path, args, descr, workdir, icon))
1593     {
1594         WINE_WARN("couldn't make desktop entry %s\n", wine_dbgstr_a(desktopPath));
1595         ret = FALSE;
1596         goto end;
1597     }
1598
1599     filename = heap_printf("wine/%s.desktop", link);
1600     if (!filename || !write_menu_file(unix_link, filename))
1601     {
1602         WINE_WARN("couldn't make menu file %s\n", wine_dbgstr_a(filename));
1603         ret = FALSE;
1604     }
1605
1606 end:
1607     HeapFree(GetProcessHeap(), 0, desktopPath);
1608     HeapFree(GetProcessHeap(), 0, filename);
1609     return ret;
1610 }
1611
1612 /* This escapes reserved characters in .desktop files' Exec keys. */
1613 static LPSTR escape(LPCWSTR arg)
1614 {
1615     int i, j;
1616     WCHAR *escaped_string;
1617     char *utf8_string;
1618
1619     escaped_string = HeapAlloc(GetProcessHeap(), 0, (4 * strlenW(arg) + 1) * sizeof(WCHAR));
1620     if (escaped_string == NULL) return NULL;
1621     for (i = j = 0; arg[i]; i++)
1622     {
1623         switch (arg[i])
1624         {
1625         case '\\':
1626             escaped_string[j++] = '\\';
1627             escaped_string[j++] = '\\';
1628             escaped_string[j++] = '\\';
1629             escaped_string[j++] = '\\';
1630             break;
1631         case ' ':
1632         case '\t':
1633         case '\n':
1634         case '"':
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             escaped_string[j++] = '\\';
1650             escaped_string[j++] = '\\';
1651             /* fall through */
1652         default:
1653             escaped_string[j++] = arg[i];
1654             break;
1655         }
1656     }
1657     escaped_string[j] = 0;
1658
1659     utf8_string = wchars_to_utf8_chars(escaped_string);
1660     if (utf8_string == NULL)
1661     {
1662         WINE_ERR("out of memory\n");
1663         goto end;
1664     }
1665
1666 end:
1667     HeapFree(GetProcessHeap(), 0, escaped_string);
1668     return utf8_string;
1669 }
1670
1671 /* Return a heap-allocated copy of the unix format difference between the two
1672  * Windows-format paths.
1673  * locn is the owning location
1674  * link is within locn
1675  */
1676 static char *relative_path( LPCWSTR link, LPCWSTR locn )
1677 {
1678     char *unix_locn, *unix_link;
1679     char *relative = NULL;
1680
1681     unix_locn = wine_get_unix_file_name(locn);
1682     unix_link = wine_get_unix_file_name(link);
1683     if (unix_locn && unix_link)
1684     {
1685         size_t len_unix_locn, len_unix_link;
1686         len_unix_locn = strlen (unix_locn);
1687         len_unix_link = strlen (unix_link);
1688         if (len_unix_locn < len_unix_link && memcmp (unix_locn, unix_link, len_unix_locn) == 0 && unix_link[len_unix_locn] == '/')
1689         {
1690             size_t len_rel;
1691             char *p = strrchr (unix_link + len_unix_locn, '/');
1692             p = strrchr (p, '.');
1693             if (p)
1694             {
1695                 *p = '\0';
1696                 len_unix_link = p - unix_link;
1697             }
1698             len_rel = len_unix_link - len_unix_locn;
1699             relative = HeapAlloc(GetProcessHeap(), 0, len_rel);
1700             if (relative)
1701             {
1702                 memcpy (relative, unix_link + len_unix_locn + 1, len_rel);
1703             }
1704         }
1705     }
1706     if (!relative)
1707         WINE_WARN("Could not separate the relative link path of %s in %s\n", wine_dbgstr_w(link), wine_dbgstr_w(locn));
1708     HeapFree(GetProcessHeap(), 0, unix_locn);
1709     HeapFree(GetProcessHeap(), 0, unix_link);
1710     return relative;
1711 }
1712
1713 /***********************************************************************
1714  *
1715  *           GetLinkLocation
1716  *
1717  * returns TRUE if successful
1718  * *loc will contain CS_DESKTOPDIRECTORY, CS_STARTMENU, CS_STARTUP etc.
1719  * *relative will contain the address of a heap-allocated copy of the portion
1720  * of the filename that is within the specified location, in unix form
1721  */
1722 static BOOL GetLinkLocation( LPCWSTR linkfile, DWORD *loc, char **relative )
1723 {
1724     WCHAR filename[MAX_PATH], shortfilename[MAX_PATH], buffer[MAX_PATH];
1725     DWORD len, i, r, filelen;
1726     const DWORD locations[] = {
1727         CSIDL_STARTUP, CSIDL_DESKTOPDIRECTORY, CSIDL_STARTMENU,
1728         CSIDL_COMMON_STARTUP, CSIDL_COMMON_DESKTOPDIRECTORY,
1729         CSIDL_COMMON_STARTMENU };
1730
1731     WINE_TRACE("%s\n", wine_dbgstr_w(linkfile));
1732     filelen=GetFullPathNameW( linkfile, MAX_PATH, shortfilename, NULL );
1733     if (filelen==0 || filelen>MAX_PATH)
1734         return FALSE;
1735
1736     WINE_TRACE("%s\n", wine_dbgstr_w(shortfilename));
1737
1738     /* the CSLU Toolkit uses a short path name when creating .lnk files;
1739      * expand or our hardcoded list won't match.
1740      */
1741     filelen=GetLongPathNameW(shortfilename, filename, MAX_PATH);
1742     if (filelen==0 || filelen>MAX_PATH)
1743         return FALSE;
1744
1745     WINE_TRACE("%s\n", wine_dbgstr_w(filename));
1746
1747     for( i=0; i<sizeof(locations)/sizeof(locations[0]); i++ )
1748     {
1749         if (!SHGetSpecialFolderPathW( 0, buffer, locations[i], FALSE ))
1750             continue;
1751
1752         len = lstrlenW(buffer);
1753         if (len >= MAX_PATH)
1754             continue; /* We've just trashed memory! Hopefully we are OK */
1755
1756         if (len > filelen || filename[len]!='\\')
1757             continue;
1758         /* do a lstrcmpinW */
1759         filename[len] = 0;
1760         r = lstrcmpiW( filename, buffer );
1761         filename[len] = '\\';
1762         if ( r )
1763             continue;
1764
1765         /* return the remainder of the string and link type */
1766         *loc = locations[i];
1767         *relative = relative_path (filename, buffer);
1768         return (*relative != NULL);
1769     }
1770
1771     return FALSE;
1772 }
1773
1774 /* gets the target path directly or through MSI */
1775 static HRESULT get_cmdline( IShellLinkW *sl, LPWSTR szPath, DWORD pathSize,
1776                             LPWSTR szArgs, DWORD argsSize)
1777 {
1778     IShellLinkDataList *dl = NULL;
1779     EXP_DARWIN_LINK *dar = NULL;
1780     HRESULT hr;
1781
1782     szPath[0] = 0;
1783     szArgs[0] = 0;
1784
1785     hr = IShellLinkW_GetPath( sl, szPath, pathSize, NULL, SLGP_RAWPATH );
1786     if (hr == S_OK && szPath[0])
1787     {
1788         IShellLinkW_GetArguments( sl, szArgs, argsSize );
1789         return hr;
1790     }
1791
1792     hr = IShellLinkW_QueryInterface( sl, &IID_IShellLinkDataList, (LPVOID*) &dl );
1793     if (FAILED(hr))
1794         return hr;
1795
1796     hr = IShellLinkDataList_CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar );
1797     if (SUCCEEDED(hr))
1798     {
1799         WCHAR* szCmdline;
1800         DWORD cmdSize;
1801
1802         cmdSize=0;
1803         hr = CommandLineFromMsiDescriptor( dar->szwDarwinID, NULL, &cmdSize );
1804         if (hr == ERROR_SUCCESS)
1805         {
1806             cmdSize++;
1807             szCmdline = HeapAlloc( GetProcessHeap(), 0, cmdSize*sizeof(WCHAR) );
1808             hr = CommandLineFromMsiDescriptor( dar->szwDarwinID, szCmdline, &cmdSize );
1809             WINE_TRACE("      command    : %s\n", wine_dbgstr_w(szCmdline));
1810             if (hr == ERROR_SUCCESS)
1811             {
1812                 WCHAR *s, *d;
1813                 int bcount, in_quotes;
1814
1815                 /* Extract the application path */
1816                 bcount=0;
1817                 in_quotes=0;
1818                 s=szCmdline;
1819                 d=szPath;
1820                 while (*s)
1821                 {
1822                     if ((*s==0x0009 || *s==0x0020) && !in_quotes)
1823                     {
1824                         /* skip the remaining spaces */
1825                         do {
1826                             s++;
1827                         } while (*s==0x0009 || *s==0x0020);
1828                         break;
1829                     }
1830                     else if (*s==0x005c)
1831                     {
1832                         /* '\\' */
1833                         *d++=*s++;
1834                         bcount++;
1835                     }
1836                     else if (*s==0x0022)
1837                     {
1838                         /* '"' */
1839                         if ((bcount & 1)==0)
1840                         {
1841                             /* Preceded by an even number of '\', this is
1842                              * half that number of '\', plus a quote which
1843                              * we erase.
1844                              */
1845                             d-=bcount/2;
1846                             in_quotes=!in_quotes;
1847                             s++;
1848                         }
1849                         else
1850                         {
1851                             /* Preceded by an odd number of '\', this is
1852                              * half that number of '\' followed by a '"'
1853                              */
1854                             d=d-bcount/2-1;
1855                             *d++='"';
1856                             s++;
1857                         }
1858                         bcount=0;
1859                     }
1860                     else
1861                     {
1862                         /* a regular character */
1863                         *d++=*s++;
1864                         bcount=0;
1865                     }
1866                     if ((d-szPath) == pathSize)
1867                     {
1868                         /* Keep processing the path till we get to the
1869                          * arguments, but 'stand still'
1870                          */
1871                         d--;
1872                     }
1873                 }
1874                 /* Close the application path */
1875                 *d=0;
1876
1877                 lstrcpynW(szArgs, s, argsSize);
1878             }
1879             HeapFree( GetProcessHeap(), 0, szCmdline );
1880         }
1881         LocalFree( dar );
1882     }
1883
1884     IShellLinkDataList_Release( dl );
1885     return hr;
1886 }
1887
1888 static WCHAR* assoc_query(ASSOCSTR assocStr, LPCWSTR name, LPCWSTR extra)
1889 {
1890     HRESULT hr;
1891     WCHAR *value = NULL;
1892     DWORD size = 0;
1893     hr = AssocQueryStringW(0, assocStr, name, extra, NULL, &size);
1894     if (SUCCEEDED(hr))
1895     {
1896         value = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1897         if (value)
1898         {
1899             hr = AssocQueryStringW(0, assocStr, name, extra, value, &size);
1900             if (FAILED(hr))
1901             {
1902                 HeapFree(GetProcessHeap(), 0, value);
1903                 value = NULL;
1904             }
1905         }
1906     }
1907     return value;
1908 }
1909
1910 static char *slashes_to_minuses(const char *string)
1911 {
1912     int i;
1913     char *ret = HeapAlloc(GetProcessHeap(), 0, lstrlenA(string) + 1);
1914     if (ret)
1915     {
1916         for (i = 0; string[i]; i++)
1917         {
1918             if (string[i] == '/')
1919                 ret[i] = '-';
1920             else
1921                 ret[i] = string[i];
1922         }
1923         ret[i] = 0;
1924         return ret;
1925     }
1926     return NULL;
1927 }
1928
1929 static BOOL next_line(FILE *file, char **line, int *size)
1930 {
1931     int pos = 0;
1932     char *cr;
1933     if (*line == NULL)
1934     {
1935         *size = 4096;
1936         *line = HeapAlloc(GetProcessHeap(), 0, *size);
1937     }
1938     while (*line != NULL)
1939     {
1940         if (fgets(&(*line)[pos], *size - pos, file) == NULL)
1941         {
1942             HeapFree(GetProcessHeap(), 0, *line);
1943             *line = NULL;
1944             if (feof(file))
1945                 return TRUE;
1946             return FALSE;
1947         }
1948         pos = strlen(*line);
1949         cr = strchr(*line, '\n');
1950         if (cr == NULL)
1951         {
1952             char *line2;
1953             (*size) *= 2;
1954             line2 = HeapReAlloc(GetProcessHeap(), 0, *line, *size);
1955             if (line2)
1956                 *line = line2;
1957             else
1958             {
1959                 HeapFree(GetProcessHeap(), 0, *line);
1960                 *line = NULL;
1961             }
1962         }
1963         else
1964         {
1965             *cr = 0;
1966             return TRUE;
1967         }
1968     }
1969     return FALSE;
1970 }
1971
1972 static BOOL add_mimes(const char *xdg_data_dir, struct list *mime_types)
1973 {
1974     char *globs_filename = NULL;
1975     BOOL ret = TRUE;
1976     globs_filename = heap_printf("%s/mime/globs", xdg_data_dir);
1977     if (globs_filename)
1978     {
1979         FILE *globs_file = fopen(globs_filename, "r");
1980         if (globs_file) /* doesn't have to exist */
1981         {
1982             char *line = NULL;
1983             int size = 0;
1984             while (ret && (ret = next_line(globs_file, &line, &size)) && line)
1985             {
1986                 char *pos;
1987                 struct xdg_mime_type *mime_type_entry = NULL;
1988                 if (line[0] != '#' && (pos = strchr(line, ':')))
1989                 {
1990                     mime_type_entry = HeapAlloc(GetProcessHeap(), 0, sizeof(struct xdg_mime_type));
1991                     if (mime_type_entry)
1992                     {
1993                         *pos = 0;
1994                         mime_type_entry->mimeType = strdupA(line);
1995                         mime_type_entry->glob = strdupA(pos + 1);
1996                         if (mime_type_entry->mimeType && mime_type_entry->glob)
1997                             list_add_tail(mime_types, &mime_type_entry->entry);
1998                         else
1999                         {
2000                             HeapFree(GetProcessHeap(), 0, mime_type_entry->mimeType);
2001                             HeapFree(GetProcessHeap(), 0, mime_type_entry->glob);
2002                             HeapFree(GetProcessHeap(), 0, mime_type_entry);
2003                             ret = FALSE;
2004                         }
2005                     }
2006                     else
2007                         ret = FALSE;
2008                 }
2009             }
2010             HeapFree(GetProcessHeap(), 0, line);
2011             fclose(globs_file);
2012         }
2013         HeapFree(GetProcessHeap(), 0, globs_filename);
2014     }
2015     else
2016         ret = FALSE;
2017     return ret;
2018 }
2019
2020 static void free_native_mime_types(struct list *native_mime_types)
2021 {
2022     struct xdg_mime_type *mime_type_entry, *mime_type_entry2;
2023
2024     LIST_FOR_EACH_ENTRY_SAFE(mime_type_entry, mime_type_entry2, native_mime_types, struct xdg_mime_type, entry)
2025     {
2026         list_remove(&mime_type_entry->entry);
2027         HeapFree(GetProcessHeap(), 0, mime_type_entry->glob);
2028         HeapFree(GetProcessHeap(), 0, mime_type_entry->mimeType);
2029         HeapFree(GetProcessHeap(), 0, mime_type_entry);
2030     }
2031     HeapFree(GetProcessHeap(), 0, native_mime_types);
2032 }
2033
2034 static BOOL build_native_mime_types(const char *xdg_data_home, struct list **mime_types)
2035 {
2036     char *xdg_data_dirs;
2037     BOOL ret;
2038
2039     *mime_types = NULL;
2040
2041     xdg_data_dirs = getenv("XDG_DATA_DIRS");
2042     if (xdg_data_dirs == NULL)
2043         xdg_data_dirs = heap_printf("/usr/local/share/:/usr/share/");
2044     else
2045         xdg_data_dirs = strdupA(xdg_data_dirs);
2046
2047     if (xdg_data_dirs)
2048     {
2049         *mime_types = HeapAlloc(GetProcessHeap(), 0, sizeof(struct list));
2050         if (*mime_types)
2051         {
2052             const char *begin;
2053             char *end;
2054
2055             list_init(*mime_types);
2056             ret = add_mimes(xdg_data_home, *mime_types);
2057             if (ret)
2058             {
2059                 for (begin = xdg_data_dirs; (end = strchr(begin, ':')); begin = end + 1)
2060                 {
2061                     *end = '\0';
2062                     ret = add_mimes(begin, *mime_types);
2063                     *end = ':';
2064                     if (!ret)
2065                         break;
2066                 }
2067                 if (ret)
2068                     ret = add_mimes(begin, *mime_types);
2069             }
2070         }
2071         else
2072             ret = FALSE;
2073         HeapFree(GetProcessHeap(), 0, xdg_data_dirs);
2074     }
2075     else
2076         ret = FALSE;
2077     if (!ret && *mime_types)
2078     {
2079         free_native_mime_types(*mime_types);
2080         *mime_types = NULL;
2081     }
2082     return ret;
2083 }
2084
2085 static BOOL match_glob(struct list *native_mime_types, const char *extension,
2086                        char **match)
2087 {
2088 #ifdef HAVE_FNMATCH
2089     struct xdg_mime_type *mime_type_entry;
2090     int matchLength = 0;
2091
2092     *match = NULL;
2093
2094     LIST_FOR_EACH_ENTRY(mime_type_entry, native_mime_types, struct xdg_mime_type, entry)
2095     {
2096         if (fnmatch(mime_type_entry->glob, extension, 0) == 0)
2097         {
2098             if (*match == NULL || matchLength < strlen(mime_type_entry->glob))
2099             {
2100                 *match = mime_type_entry->mimeType;
2101                 matchLength = strlen(mime_type_entry->glob);
2102             }
2103         }
2104     }
2105
2106     if (*match != NULL)
2107     {
2108         *match = strdupA(*match);
2109         if (*match == NULL)
2110             return FALSE;
2111     }
2112 #else
2113     *match = NULL;
2114 #endif
2115     return TRUE;
2116 }
2117
2118 static BOOL freedesktop_mime_type_for_extension(struct list *native_mime_types,
2119                                                 const char *extensionA,
2120                                                 LPCWSTR extensionW,
2121                                                 char **mime_type)
2122 {
2123     WCHAR *lower_extensionW;
2124     INT len;
2125     BOOL ret = match_glob(native_mime_types, extensionA, mime_type);
2126     if (ret == FALSE || *mime_type != NULL)
2127         return ret;
2128     len = strlenW(extensionW);
2129     lower_extensionW = HeapAlloc(GetProcessHeap(), 0, (len + 1)*sizeof(WCHAR));
2130     if (lower_extensionW)
2131     {
2132         char *lower_extensionA;
2133         memcpy(lower_extensionW, extensionW, (len + 1)*sizeof(WCHAR));
2134         strlwrW(lower_extensionW);
2135         lower_extensionA = wchars_to_utf8_chars(lower_extensionW);
2136         if (lower_extensionA)
2137         {
2138             ret = match_glob(native_mime_types, lower_extensionA, mime_type);
2139             HeapFree(GetProcessHeap(), 0, lower_extensionA);
2140         }
2141         else
2142         {
2143             ret = FALSE;
2144             WINE_FIXME("out of memory\n");
2145         }
2146         HeapFree(GetProcessHeap(), 0, lower_extensionW);
2147     }
2148     else
2149     {
2150         ret = FALSE;
2151         WINE_FIXME("out of memory\n");
2152     }
2153     return ret;
2154 }
2155
2156 static WCHAR* reg_get_valW(HKEY key, LPCWSTR subkey, LPCWSTR name)
2157 {
2158     DWORD size;
2159     if (RegGetValueW(key, subkey, name, RRF_RT_REG_SZ, NULL, NULL, &size) == ERROR_SUCCESS)
2160     {
2161         WCHAR *ret = HeapAlloc(GetProcessHeap(), 0, size);
2162         if (ret)
2163         {
2164             if (RegGetValueW(key, subkey, name, RRF_RT_REG_SZ, NULL, ret, &size) == ERROR_SUCCESS)
2165                 return ret;
2166         }
2167         HeapFree(GetProcessHeap(), 0, ret);
2168     }
2169     return NULL;
2170 }
2171
2172 static CHAR* reg_get_val_utf8(HKEY key, LPCWSTR subkey, LPCWSTR name)
2173 {
2174     WCHAR *valW = reg_get_valW(key, subkey, name);
2175     if (valW)
2176     {
2177         char *val = wchars_to_utf8_chars(valW);
2178         HeapFree(GetProcessHeap(), 0, valW);
2179         return val;
2180     }
2181     return NULL;
2182 }
2183
2184 static HKEY open_associations_reg_key(void)
2185 {
2186     static const WCHAR Software_Wine_FileOpenAssociationsW[] = {
2187         '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};
2188     HKEY assocKey;
2189     if (RegCreateKeyW(HKEY_CURRENT_USER, Software_Wine_FileOpenAssociationsW, &assocKey) == ERROR_SUCCESS)
2190         return assocKey;
2191     return NULL;
2192 }
2193
2194 static BOOL has_association_changed(LPCWSTR extensionW, LPCSTR mimeType, LPCWSTR progId,
2195     LPCSTR appName, LPCSTR openWithIcon)
2196 {
2197     static const WCHAR ProgIDW[] = {'P','r','o','g','I','D',0};
2198     static const WCHAR MimeTypeW[] = {'M','i','m','e','T','y','p','e',0};
2199     static const WCHAR AppNameW[] = {'A','p','p','N','a','m','e',0};
2200     static const WCHAR OpenWithIconW[] = {'O','p','e','n','W','i','t','h','I','c','o','n',0};
2201     HKEY assocKey;
2202     BOOL ret;
2203
2204     if ((assocKey = open_associations_reg_key()))
2205     {
2206         CHAR *valueA;
2207         WCHAR *value;
2208
2209         ret = FALSE;
2210
2211         valueA = reg_get_val_utf8(assocKey, extensionW, MimeTypeW);
2212         if (!valueA || lstrcmpA(valueA, mimeType))
2213             ret = TRUE;
2214         HeapFree(GetProcessHeap(), 0, valueA);
2215
2216         value = reg_get_valW(assocKey, extensionW, ProgIDW);
2217         if (!value || strcmpW(value, progId))
2218             ret = TRUE;
2219         HeapFree(GetProcessHeap(), 0, value);
2220
2221         valueA = reg_get_val_utf8(assocKey, extensionW, AppNameW);
2222         if (!valueA || lstrcmpA(valueA, appName))
2223             ret = TRUE;
2224         HeapFree(GetProcessHeap(), 0, valueA);
2225
2226         valueA = reg_get_val_utf8(assocKey, extensionW, OpenWithIconW);
2227         if ((openWithIcon && !valueA) ||
2228             (!openWithIcon && valueA) ||
2229             (openWithIcon && valueA && lstrcmpA(valueA, openWithIcon)))
2230             ret = TRUE;
2231         HeapFree(GetProcessHeap(), 0, valueA);
2232
2233         RegCloseKey(assocKey);
2234     }
2235     else
2236     {
2237         WINE_ERR("error opening associations registry key\n");
2238         ret = FALSE;
2239     }
2240     return ret;
2241 }
2242
2243 static void update_association(LPCWSTR extension, LPCSTR mimeType, LPCWSTR progId,
2244     LPCSTR appName, LPCSTR desktopFile, LPCSTR openWithIcon)
2245 {
2246     static const WCHAR ProgIDW[] = {'P','r','o','g','I','D',0};
2247     static const WCHAR MimeTypeW[] = {'M','i','m','e','T','y','p','e',0};
2248     static const WCHAR AppNameW[] = {'A','p','p','N','a','m','e',0};
2249     static const WCHAR DesktopFileW[] = {'D','e','s','k','t','o','p','F','i','l','e',0};
2250     static const WCHAR OpenWithIconW[] = {'O','p','e','n','W','i','t','h','I','c','o','n',0};
2251     HKEY assocKey = NULL;
2252     HKEY subkey = NULL;
2253     WCHAR *mimeTypeW = NULL;
2254     WCHAR *appNameW = NULL;
2255     WCHAR *desktopFileW = NULL;
2256     WCHAR *openWithIconW = NULL;
2257
2258     assocKey = open_associations_reg_key();
2259     if (assocKey == NULL)
2260     {
2261         WINE_ERR("could not open file associations key\n");
2262         goto done;
2263     }
2264
2265     if (RegCreateKeyW(assocKey, extension, &subkey) != ERROR_SUCCESS)
2266     {
2267         WINE_ERR("could not create extension subkey\n");
2268         goto done;
2269     }
2270
2271     mimeTypeW = utf8_chars_to_wchars(mimeType);
2272     if (mimeTypeW == NULL)
2273     {
2274         WINE_ERR("out of memory\n");
2275         goto done;
2276     }
2277
2278     appNameW = utf8_chars_to_wchars(appName);
2279     if (appNameW == NULL)
2280     {
2281         WINE_ERR("out of memory\n");
2282         goto done;
2283     }
2284
2285     desktopFileW = utf8_chars_to_wchars(desktopFile);
2286     if (desktopFileW == NULL)
2287     {
2288         WINE_ERR("out of memory\n");
2289         goto done;
2290     }
2291
2292     if (openWithIcon)
2293     {
2294         openWithIconW = utf8_chars_to_wchars(openWithIcon);
2295         if (openWithIconW == NULL)
2296         {
2297             WINE_ERR("out of memory\n");
2298             goto done;
2299         }
2300     }
2301
2302     RegSetValueExW(subkey, MimeTypeW, 0, REG_SZ, (const BYTE*) mimeTypeW, (lstrlenW(mimeTypeW) + 1) * sizeof(WCHAR));
2303     RegSetValueExW(subkey, ProgIDW, 0, REG_SZ, (const BYTE*) progId, (lstrlenW(progId) + 1) * sizeof(WCHAR));
2304     RegSetValueExW(subkey, AppNameW, 0, REG_SZ, (const BYTE*) appNameW, (lstrlenW(appNameW) + 1) * sizeof(WCHAR));
2305     RegSetValueExW(subkey, DesktopFileW, 0, REG_SZ, (const BYTE*) desktopFileW, (lstrlenW(desktopFileW) + 1) * sizeof(WCHAR));
2306     if (openWithIcon)
2307         RegSetValueExW(subkey, OpenWithIconW, 0, REG_SZ, (const BYTE*) openWithIconW, (lstrlenW(openWithIconW) + 1) * sizeof(WCHAR));
2308     else
2309         RegDeleteValueW(subkey, OpenWithIconW);
2310
2311 done:
2312     RegCloseKey(assocKey);
2313     RegCloseKey(subkey);
2314     HeapFree(GetProcessHeap(), 0, mimeTypeW);
2315     HeapFree(GetProcessHeap(), 0, appNameW);
2316     HeapFree(GetProcessHeap(), 0, desktopFileW);
2317     HeapFree(GetProcessHeap(), 0, openWithIconW);
2318 }
2319
2320 static BOOL cleanup_associations(void)
2321 {
2322     static const WCHAR openW[] = {'o','p','e','n',0};
2323     static const WCHAR DesktopFileW[] = {'D','e','s','k','t','o','p','F','i','l','e',0};
2324     HKEY assocKey;
2325     BOOL hasChanged = FALSE;
2326     if ((assocKey = open_associations_reg_key()))
2327     {
2328         int i;
2329         BOOL done = FALSE;
2330         for (i = 0; !done;)
2331         {
2332             WCHAR *extensionW = NULL;
2333             DWORD size = 1024;
2334             LSTATUS ret;
2335
2336             do
2337             {
2338                 HeapFree(GetProcessHeap(), 0, extensionW);
2339                 extensionW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
2340                 if (extensionW == NULL)
2341                 {
2342                     WINE_ERR("out of memory\n");
2343                     ret = ERROR_OUTOFMEMORY;
2344                     break;
2345                 }
2346                 ret = RegEnumKeyExW(assocKey, i, extensionW, &size, NULL, NULL, NULL, NULL);
2347                 size *= 2;
2348             } while (ret == ERROR_MORE_DATA);
2349
2350             if (ret == ERROR_SUCCESS)
2351             {
2352                 WCHAR *command;
2353                 command = assoc_query(ASSOCSTR_COMMAND, extensionW, openW);
2354                 if (command == NULL)
2355                 {
2356                     char *desktopFile = reg_get_val_utf8(assocKey, extensionW, DesktopFileW);
2357                     if (desktopFile)
2358                     {
2359                         WINE_TRACE("removing file type association for %s\n", wine_dbgstr_w(extensionW));
2360                         remove(desktopFile);
2361                     }
2362                     RegDeleteKeyW(assocKey, extensionW);
2363                     hasChanged = TRUE;
2364                     HeapFree(GetProcessHeap(), 0, desktopFile);
2365                 }
2366                 else
2367                     i++;
2368                 HeapFree(GetProcessHeap(), 0, command);
2369             }
2370             else
2371             {
2372                 if (ret != ERROR_NO_MORE_ITEMS)
2373                     WINE_ERR("error %d while reading registry\n", ret);
2374                 done = TRUE;
2375             }
2376             HeapFree(GetProcessHeap(), 0, extensionW);
2377         }
2378         RegCloseKey(assocKey);
2379     }
2380     else
2381         WINE_ERR("could not open file associations key\n");
2382     return hasChanged;
2383 }
2384
2385 static BOOL write_freedesktop_mime_type_entry(const char *packages_dir, const char *dot_extension,
2386                                               const char *mime_type, const char *comment)
2387 {
2388     BOOL ret = FALSE;
2389     char *filename;
2390
2391     WINE_TRACE("writing MIME type %s, extension=%s, comment=%s\n", wine_dbgstr_a(mime_type),
2392                wine_dbgstr_a(dot_extension), wine_dbgstr_a(comment));
2393
2394     filename = heap_printf("%s/x-wine-extension-%s.xml", packages_dir, &dot_extension[1]);
2395     if (filename)
2396     {
2397         FILE *packageFile = fopen(filename, "w");
2398         if (packageFile)
2399         {
2400             fprintf(packageFile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
2401             fprintf(packageFile, "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n");
2402             fprintf(packageFile, "  <mime-type type=\"");
2403             write_xml_text(packageFile, mime_type);
2404             fprintf(packageFile, "\">\n");
2405             fprintf(packageFile, "    <glob pattern=\"*");
2406             write_xml_text(packageFile, dot_extension);
2407             fprintf(packageFile, "\"/>\n");
2408             if (comment)
2409             {
2410                 fprintf(packageFile, "    <comment>");
2411                 write_xml_text(packageFile, comment);
2412                 fprintf(packageFile, "</comment>\n");
2413             }
2414             fprintf(packageFile, "  </mime-type>\n");
2415             fprintf(packageFile, "</mime-info>\n");
2416             ret = TRUE;
2417             fclose(packageFile);
2418         }
2419         else
2420             WINE_ERR("error writing file %s\n", filename);
2421         HeapFree(GetProcessHeap(), 0, filename);
2422     }
2423     else
2424         WINE_ERR("out of memory\n");
2425     return ret;
2426 }
2427
2428 static BOOL is_extension_blacklisted(LPCWSTR extension)
2429 {
2430     /* These are managed through external tools like wine.desktop, to evade malware created file type associations */
2431     static const WCHAR comW[] = {'.','c','o','m',0};
2432     static const WCHAR exeW[] = {'.','e','x','e',0};
2433     static const WCHAR msiW[] = {'.','m','s','i',0};
2434
2435     if (!strcmpiW(extension, comW) ||
2436         !strcmpiW(extension, exeW) ||
2437         !strcmpiW(extension, msiW))
2438         return TRUE;
2439     return FALSE;
2440 }
2441
2442 static const char* get_special_mime_type(LPCWSTR extension)
2443 {
2444     static const WCHAR lnkW[] = {'.','l','n','k',0};
2445     if (!strcmpiW(extension, lnkW))
2446         return "application/x-ms-shortcut";
2447     return NULL;
2448 }
2449
2450 static BOOL write_freedesktop_association_entry(const char *desktopPath, const char *dot_extension,
2451                                                 const char *friendlyAppName, const char *mimeType,
2452                                                 const char *progId, const char *openWithIcon)
2453 {
2454     BOOL ret = FALSE;
2455     FILE *desktop;
2456
2457     WINE_TRACE("writing association for file type %s, friendlyAppName=%s, MIME type %s, progID=%s, icon=%s to file %s\n",
2458                wine_dbgstr_a(dot_extension), wine_dbgstr_a(friendlyAppName), wine_dbgstr_a(mimeType),
2459                wine_dbgstr_a(progId), wine_dbgstr_a(openWithIcon), wine_dbgstr_a(desktopPath));
2460
2461     desktop = fopen(desktopPath, "w");
2462     if (desktop)
2463     {
2464         fprintf(desktop, "[Desktop Entry]\n");
2465         fprintf(desktop, "Type=Application\n");
2466         fprintf(desktop, "Name=%s\n", friendlyAppName);
2467         fprintf(desktop, "MimeType=%s;\n", mimeType);
2468         fprintf(desktop, "Exec=env WINEPREFIX=\"%s\" wine start /ProgIDOpen %s %%f\n", wine_get_config_dir(), progId);
2469         fprintf(desktop, "NoDisplay=true\n");
2470         fprintf(desktop, "StartupNotify=true\n");
2471         if (openWithIcon)
2472             fprintf(desktop, "Icon=%s\n", openWithIcon);
2473         ret = TRUE;
2474         fclose(desktop);
2475     }
2476     else
2477         WINE_ERR("error writing association file %s\n", wine_dbgstr_a(desktopPath));
2478     return ret;
2479 }
2480
2481 static BOOL generate_associations(const char *xdg_data_home, const char *packages_dir, const char *applications_dir)
2482 {
2483     static const WCHAR openW[] = {'o','p','e','n',0};
2484     struct wine_rb_tree mimeProgidTree;
2485     struct list *nativeMimeTypes = NULL;
2486     LSTATUS ret = 0;
2487     int i;
2488     BOOL hasChanged = FALSE;
2489
2490     if (wine_rb_init(&mimeProgidTree, &winemenubuilder_rb_functions))
2491     {
2492         WINE_ERR("wine_rb_init failed\n");
2493         return FALSE;
2494     }
2495     if (!build_native_mime_types(xdg_data_home, &nativeMimeTypes))
2496     {
2497         WINE_ERR("could not build native MIME types\n");
2498         return FALSE;
2499     }
2500
2501     for (i = 0; ; i++)
2502     {
2503         WCHAR *extensionW = NULL;
2504         DWORD size = 1024;
2505
2506         do
2507         {
2508             HeapFree(GetProcessHeap(), 0, extensionW);
2509             extensionW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
2510             if (extensionW == NULL)
2511             {
2512                 WINE_ERR("out of memory\n");
2513                 ret = ERROR_OUTOFMEMORY;
2514                 break;
2515             }
2516             ret = RegEnumKeyExW(HKEY_CLASSES_ROOT, i, extensionW, &size, NULL, NULL, NULL, NULL);
2517             size *= 2;
2518         } while (ret == ERROR_MORE_DATA);
2519
2520         if (ret == ERROR_SUCCESS && extensionW[0] == '.' && !is_extension_blacklisted(extensionW))
2521         {
2522             char *extensionA = NULL;
2523             WCHAR *commandW = NULL;
2524             WCHAR *executableW = NULL;
2525             char *openWithIconA = NULL;
2526             WCHAR *friendlyDocNameW = NULL;
2527             char *friendlyDocNameA = NULL;
2528             WCHAR *iconW = NULL;
2529             char *iconA = NULL;
2530             WCHAR *contentTypeW = NULL;
2531             char *mimeTypeA = NULL;
2532             WCHAR *friendlyAppNameW = NULL;
2533             char *friendlyAppNameA = NULL;
2534             WCHAR *progIdW = NULL;
2535             char *progIdA = NULL;
2536             char *mimeProgId = NULL;
2537
2538             extensionA = wchars_to_utf8_chars(strlwrW(extensionW));
2539             if (extensionA == NULL)
2540             {
2541                 WINE_ERR("out of memory\n");
2542                 goto end;
2543             }
2544
2545             friendlyDocNameW = assoc_query(ASSOCSTR_FRIENDLYDOCNAME, extensionW, NULL);
2546             if (friendlyDocNameW)
2547             {
2548                 friendlyDocNameA = wchars_to_utf8_chars(friendlyDocNameW);
2549                 if (friendlyDocNameA == NULL)
2550                 {
2551                     WINE_ERR("out of memory\n");
2552                     goto end;
2553                 }
2554             }
2555
2556             iconW = assoc_query(ASSOCSTR_DEFAULTICON, extensionW, NULL);
2557
2558             contentTypeW = assoc_query(ASSOCSTR_CONTENTTYPE, extensionW, NULL);
2559             if (contentTypeW)
2560                 strlwrW(contentTypeW);
2561
2562             if (!freedesktop_mime_type_for_extension(nativeMimeTypes, extensionA, extensionW, &mimeTypeA))
2563                 goto end;
2564
2565             if (mimeTypeA == NULL)
2566             {
2567                 if (contentTypeW != NULL && strchrW(contentTypeW, '/'))
2568                     mimeTypeA = wchars_to_utf8_chars(contentTypeW);
2569                 else if ((get_special_mime_type(extensionW)))
2570                     mimeTypeA = strdupA(get_special_mime_type(extensionW));
2571                 else
2572                     mimeTypeA = heap_printf("application/x-wine-extension-%s", &extensionA[1]);
2573
2574                 if (mimeTypeA != NULL)
2575                 {
2576                     /* Gnome seems to ignore the <icon> tag in MIME packages,
2577                      * and the default name is more intuitive anyway.
2578                      */
2579                     if (iconW)
2580                     {
2581                         char *flattened_mime = slashes_to_minuses(mimeTypeA);
2582                         if (flattened_mime)
2583                         {
2584                             int index = 0;
2585                             WCHAR *comma = strrchrW(iconW, ',');
2586                             if (comma)
2587                             {
2588                                 *comma = 0;
2589                                 index = atoiW(comma + 1);
2590                             }
2591                             iconA = extract_icon(iconW, index, flattened_mime, FALSE);
2592                             HeapFree(GetProcessHeap(), 0, flattened_mime);
2593                         }
2594                     }
2595
2596                     write_freedesktop_mime_type_entry(packages_dir, extensionA, mimeTypeA, friendlyDocNameA);
2597                     hasChanged = TRUE;
2598                 }
2599                 else
2600                 {
2601                     WINE_FIXME("out of memory\n");
2602                     goto end;
2603                 }
2604             }
2605
2606             commandW = assoc_query(ASSOCSTR_COMMAND, extensionW, openW);
2607             if (commandW == NULL)
2608                 /* no command => no application is associated */
2609                 goto end;
2610
2611             executableW = assoc_query(ASSOCSTR_EXECUTABLE, extensionW, openW);
2612             if (executableW)
2613                 openWithIconA = extract_icon(executableW, 0, NULL, FALSE);
2614
2615             friendlyAppNameW = assoc_query(ASSOCSTR_FRIENDLYAPPNAME, extensionW, NULL);
2616             if (friendlyAppNameW)
2617             {
2618                 friendlyAppNameA = wchars_to_utf8_chars(friendlyAppNameW);
2619                 if (friendlyAppNameA == NULL)
2620                 {
2621                     WINE_ERR("out of memory\n");
2622                     goto end;
2623                 }
2624             }
2625             else
2626             {
2627                 friendlyAppNameA = heap_printf("A Wine application");
2628                 if (friendlyAppNameA == NULL)
2629                 {
2630                     WINE_ERR("out of memory\n");
2631                     goto end;
2632                 }
2633             }
2634
2635             progIdW = reg_get_valW(HKEY_CLASSES_ROOT, extensionW, NULL);
2636             if (progIdW)
2637             {
2638                 progIdA = escape(progIdW);
2639                 if (progIdA == NULL)
2640                 {
2641                     WINE_ERR("out of memory\n");
2642                     goto end;
2643                 }
2644             }
2645             else
2646                 goto end; /* no progID => not a file type association */
2647
2648             /* Do not allow duplicate ProgIDs for a MIME type, it causes unnecessary duplication in Open dialogs */
2649             mimeProgId = heap_printf("%s=>%s", mimeTypeA, progIdA);
2650             if (mimeProgId)
2651             {
2652                 struct rb_string_entry *entry;
2653                 if (wine_rb_get(&mimeProgidTree, mimeProgId))
2654                 {
2655                     HeapFree(GetProcessHeap(), 0, mimeProgId);
2656                     goto end;
2657                 }
2658                 entry = HeapAlloc(GetProcessHeap(), 0, sizeof(struct rb_string_entry));
2659                 if (!entry)
2660                 {
2661                     WINE_ERR("out of memory allocating rb_string_entry\n");
2662                     goto end;
2663                 }
2664                 entry->string = mimeProgId;
2665                 if (wine_rb_put(&mimeProgidTree, mimeProgId, &entry->entry))
2666                 {
2667                     WINE_ERR("error updating rb tree\n");
2668                     goto end;
2669                 }
2670             }
2671
2672             if (has_association_changed(extensionW, mimeTypeA, progIdW, friendlyAppNameA, openWithIconA))
2673             {
2674                 char *desktopPath = heap_printf("%s/wine-extension-%s.desktop", applications_dir, &extensionA[1]);
2675                 if (desktopPath)
2676                 {
2677                     if (write_freedesktop_association_entry(desktopPath, extensionA, friendlyAppNameA, mimeTypeA, progIdA, openWithIconA))
2678                     {
2679                         hasChanged = TRUE;
2680                         update_association(extensionW, mimeTypeA, progIdW, friendlyAppNameA, desktopPath, openWithIconA);
2681                     }
2682                     HeapFree(GetProcessHeap(), 0, desktopPath);
2683                 }
2684             }
2685
2686         end:
2687             HeapFree(GetProcessHeap(), 0, extensionA);
2688             HeapFree(GetProcessHeap(), 0, commandW);
2689             HeapFree(GetProcessHeap(), 0, executableW);
2690             HeapFree(GetProcessHeap(), 0, openWithIconA);
2691             HeapFree(GetProcessHeap(), 0, friendlyDocNameW);
2692             HeapFree(GetProcessHeap(), 0, friendlyDocNameA);
2693             HeapFree(GetProcessHeap(), 0, iconW);
2694             HeapFree(GetProcessHeap(), 0, iconA);
2695             HeapFree(GetProcessHeap(), 0, contentTypeW);
2696             HeapFree(GetProcessHeap(), 0, mimeTypeA);
2697             HeapFree(GetProcessHeap(), 0, friendlyAppNameW);
2698             HeapFree(GetProcessHeap(), 0, friendlyAppNameA);
2699             HeapFree(GetProcessHeap(), 0, progIdW);
2700             HeapFree(GetProcessHeap(), 0, progIdA);
2701         }
2702         HeapFree(GetProcessHeap(), 0, extensionW);
2703         if (ret != ERROR_SUCCESS)
2704             break;
2705     }
2706
2707     wine_rb_destroy(&mimeProgidTree, winemenubuilder_rb_destroy, NULL);
2708     free_native_mime_types(nativeMimeTypes);
2709     return hasChanged;
2710 }
2711
2712 static char *get_start_exe_path(void)
2713  {
2714     static const WCHAR startW[] = {'\\','c','o','m','m','a','n','d',
2715                                    '\\','s','t','a','r','t','.','e','x','e',0};
2716     WCHAR start_path[MAX_PATH];
2717     GetWindowsDirectoryW(start_path, MAX_PATH);
2718     lstrcatW(start_path, startW);
2719     return escape(start_path);
2720 }
2721
2722 static char* escape_unix_link_arg(LPCSTR unix_link)
2723 {
2724     char *ret = NULL;
2725     WCHAR *unix_linkW = utf8_chars_to_wchars(unix_link);
2726     if (unix_linkW)
2727     {
2728         char *escaped_lnk = escape(unix_linkW);
2729         if (escaped_lnk)
2730         {
2731             ret = heap_printf("/Unix %s", escaped_lnk);
2732             HeapFree(GetProcessHeap(), 0, escaped_lnk);
2733         }
2734         HeapFree(GetProcessHeap(), 0, unix_linkW);
2735     }
2736     return ret;
2737 }
2738
2739 static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait )
2740 {
2741     static const WCHAR startW[] = {'\\','c','o','m','m','a','n','d',
2742                                    '\\','s','t','a','r','t','.','e','x','e',0};
2743     char *link_name = NULL, *icon_name = NULL, *work_dir = NULL;
2744     char *escaped_path = NULL, *escaped_args = NULL, *description = NULL;
2745     WCHAR szTmp[INFOTIPSIZE];
2746     WCHAR szDescription[INFOTIPSIZE], szPath[MAX_PATH], szWorkDir[MAX_PATH];
2747     WCHAR szArgs[INFOTIPSIZE], szIconPath[MAX_PATH];
2748     int iIconId = 0, r = -1;
2749     DWORD csidl = -1;
2750     HANDLE hsem = NULL;
2751     char *unix_link = NULL;
2752     char *start_path = NULL;
2753
2754     if ( !link )
2755     {
2756         WINE_ERR("Link name is null\n");
2757         return FALSE;
2758     }
2759
2760     if( !GetLinkLocation( link, &csidl, &link_name ) )
2761     {
2762         WINE_WARN("Unknown link location %s. Ignoring.\n",wine_dbgstr_w(link));
2763         return TRUE;
2764     }
2765     if (!in_desktop_dir(csidl) && !in_startmenu(csidl))
2766     {
2767         WINE_WARN("Not under desktop or start menu. Ignoring.\n");
2768         return TRUE;
2769     }
2770     WINE_TRACE("Link       : %s\n", wine_dbgstr_a(link_name));
2771
2772     szTmp[0] = 0;
2773     IShellLinkW_GetWorkingDirectory( sl, szTmp, MAX_PATH );
2774     ExpandEnvironmentStringsW(szTmp, szWorkDir, MAX_PATH);
2775     WINE_TRACE("workdir    : %s\n", wine_dbgstr_w(szWorkDir));
2776
2777     szTmp[0] = 0;
2778     IShellLinkW_GetDescription( sl, szTmp, INFOTIPSIZE );
2779     ExpandEnvironmentStringsW(szTmp, szDescription, INFOTIPSIZE);
2780     WINE_TRACE("description: %s\n", wine_dbgstr_w(szDescription));
2781
2782     get_cmdline( sl, szTmp, MAX_PATH, szArgs, INFOTIPSIZE);
2783     ExpandEnvironmentStringsW(szTmp, szPath, MAX_PATH);
2784     WINE_TRACE("path       : %s\n", wine_dbgstr_w(szPath));
2785     WINE_TRACE("args       : %s\n", wine_dbgstr_w(szArgs));
2786
2787     szTmp[0] = 0;
2788     IShellLinkW_GetIconLocation( sl, szTmp, MAX_PATH, &iIconId );
2789     ExpandEnvironmentStringsW(szTmp, szIconPath, MAX_PATH);
2790     WINE_TRACE("icon file  : %s\n", wine_dbgstr_w(szIconPath) );
2791
2792     if( !szPath[0] )
2793     {
2794         LPITEMIDLIST pidl = NULL;
2795         IShellLinkW_GetIDList( sl, &pidl );
2796         if( pidl && SHGetPathFromIDListW( pidl, szPath ) )
2797             WINE_TRACE("pidl path  : %s\n", wine_dbgstr_w(szPath));
2798     }
2799
2800     /* extract the icon */
2801     if( szIconPath[0] )
2802         icon_name = extract_icon( szIconPath , iIconId, NULL, bWait );
2803     else
2804         icon_name = extract_icon( szPath, iIconId, NULL, bWait );
2805
2806     /* fail - try once again after parent process exit */
2807     if( !icon_name )
2808     {
2809         if (bWait)
2810         {
2811             WINE_WARN("Unable to extract icon, deferring.\n");
2812             goto cleanup;
2813         }
2814         WINE_ERR("failed to extract icon from %s\n",
2815                  wine_dbgstr_w( szIconPath[0] ? szIconPath : szPath ));
2816     }
2817
2818     unix_link = wine_get_unix_file_name(link);
2819     if (unix_link == NULL)
2820     {
2821         WINE_WARN("couldn't find unix path of %s\n", wine_dbgstr_w(link));
2822         goto cleanup;
2823     }
2824
2825     /* check the path */
2826     if( szPath[0] )
2827     {
2828         static const WCHAR exeW[] = {'.','e','x','e',0};
2829         WCHAR *p;
2830
2831         /* check for .exe extension */
2832         if (!(p = strrchrW( szPath, '.' )) ||
2833             strchrW( p, '\\' ) || strchrW( p, '/' ) ||
2834             lstrcmpiW( p, exeW ))
2835         {
2836             /* Not .exe - use 'start.exe' to launch this file */
2837             p = szArgs + lstrlenW(szPath) + 2;
2838             if (szArgs[0])
2839             {
2840                 p[0] = ' ';
2841                 memmove( p+1, szArgs, min( (lstrlenW(szArgs) + 1) * sizeof(szArgs[0]),
2842                                            sizeof(szArgs) - (p + 1 - szArgs) * sizeof(szArgs[0]) ) );
2843             }
2844             else
2845                 p[0] = 0;
2846
2847             szArgs[0] = '"';
2848             lstrcpyW(szArgs + 1, szPath);
2849             p[-1] = '"';
2850
2851             GetWindowsDirectoryW(szPath, MAX_PATH);
2852             lstrcatW(szPath, startW);
2853         }
2854
2855         /* convert app working dir */
2856         if (szWorkDir[0])
2857             work_dir = wine_get_unix_file_name( szWorkDir );
2858     }
2859     else
2860     {
2861         /* if there's no path... try run the link itself */
2862         lstrcpynW(szArgs, link, MAX_PATH);
2863         GetWindowsDirectoryW(szPath, MAX_PATH);
2864         lstrcatW(szPath, startW);
2865     }
2866
2867     /* escape the path and parameters */
2868     escaped_path = escape(szPath);
2869     escaped_args = escape(szArgs);
2870     description = wchars_to_utf8_chars(szDescription);
2871     if (escaped_path == NULL || escaped_args == NULL || description == NULL)
2872     {
2873         WINE_ERR("out of memory allocating/escaping parameters\n");
2874         goto cleanup;
2875     }
2876
2877     start_path = get_start_exe_path();
2878     if (start_path == NULL)
2879     {
2880         WINE_ERR("out of memory\n");
2881         goto cleanup;
2882     }
2883
2884     /* building multiple menus concurrently has race conditions */
2885     hsem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
2886     if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hsem, FALSE, INFINITE, QS_ALLINPUT ) )
2887     {
2888         WINE_ERR("failed wait for semaphore\n");
2889         goto cleanup;
2890     }
2891
2892     if (in_desktop_dir(csidl))
2893     {
2894         char *location;
2895         const char *lastEntry;
2896         lastEntry = strrchr(link_name, '/');
2897         if (lastEntry == NULL)
2898             lastEntry = link_name;
2899         else
2900             ++lastEntry;
2901         location = heap_printf("%s/%s.desktop", xdg_desktop_dir, lastEntry);
2902         if (location)
2903         {
2904             if (csidl == CSIDL_COMMON_DESKTOPDIRECTORY)
2905             {
2906                 char *link_arg = escape_unix_link_arg(unix_link);
2907                 if (link_arg)
2908                 {
2909                     r = !write_desktop_entry(unix_link, location, lastEntry,
2910                         start_path, link_arg, description, work_dir, icon_name);
2911                     HeapFree(GetProcessHeap(), 0, link_arg);
2912                 }
2913             }
2914             else
2915                 r = !write_desktop_entry(NULL, location, lastEntry, escaped_path, escaped_args, description, work_dir, icon_name);
2916             if (r == 0)
2917                 chmod(location, 0755);
2918             HeapFree(GetProcessHeap(), 0, location);
2919         }
2920     }
2921     else
2922     {
2923         char *link_arg = escape_unix_link_arg(unix_link);
2924         if (link_arg)
2925         {
2926             r = !write_menu_entry(unix_link, link_name, start_path, link_arg, description, work_dir, icon_name);
2927             HeapFree(GetProcessHeap(), 0, link_arg);
2928         }
2929     }
2930
2931     ReleaseSemaphore( hsem, 1, NULL );
2932
2933 cleanup:
2934     if (hsem) CloseHandle( hsem );
2935     HeapFree( GetProcessHeap(), 0, icon_name );
2936     HeapFree( GetProcessHeap(), 0, work_dir );
2937     HeapFree( GetProcessHeap(), 0, link_name );
2938     HeapFree( GetProcessHeap(), 0, escaped_args );
2939     HeapFree( GetProcessHeap(), 0, escaped_path );
2940     HeapFree( GetProcessHeap(), 0, description );
2941     HeapFree( GetProcessHeap(), 0, unix_link );
2942     HeapFree( GetProcessHeap(), 0, start_path );
2943
2944     if (r && !bWait)
2945         WINE_ERR("failed to build the menu\n" );
2946
2947     return ( r == 0 );
2948 }
2949
2950 static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link, BOOL bWait )
2951 {
2952     char *link_name = NULL, *icon_name = NULL;
2953     DWORD csidl = -1;
2954     LPWSTR urlPath;
2955     char *escaped_urlPath = NULL;
2956     HRESULT hr;
2957     HANDLE hSem = NULL;
2958     BOOL ret = TRUE;
2959     int r = -1;
2960     char *unix_link = NULL;
2961     IPropertySetStorage *pPropSetStg;
2962     IPropertyStorage *pPropStg;
2963     PROPSPEC ps[2];
2964     PROPVARIANT pv[2];
2965
2966     if ( !link )
2967     {
2968         WINE_ERR("Link name is null\n");
2969         return TRUE;
2970     }
2971
2972     if( !GetLinkLocation( link, &csidl, &link_name ) )
2973     {
2974         WINE_WARN("Unknown link location %s. Ignoring.\n",wine_dbgstr_w(link));
2975         return TRUE;
2976     }
2977     if (!in_desktop_dir(csidl) && !in_startmenu(csidl))
2978     {
2979         WINE_WARN("Not under desktop or start menu. Ignoring.\n");
2980         ret = TRUE;
2981         goto cleanup;
2982     }
2983     WINE_TRACE("Link       : %s\n", wine_dbgstr_a(link_name));
2984
2985     hr = url->lpVtbl->GetURL(url, &urlPath);
2986     if (FAILED(hr))
2987     {
2988         ret = TRUE;
2989         goto cleanup;
2990     }
2991     WINE_TRACE("path       : %s\n", wine_dbgstr_w(urlPath));
2992
2993     unix_link = wine_get_unix_file_name(link);
2994     if (unix_link == NULL)
2995     {
2996         WINE_WARN("couldn't find unix path of %s\n", wine_dbgstr_w(link));
2997         goto cleanup;
2998     }
2999
3000     escaped_urlPath = escape(urlPath);
3001     if (escaped_urlPath == NULL)
3002     {
3003         WINE_ERR("couldn't escape url, out of memory\n");
3004         goto cleanup;
3005     }
3006
3007     ps[0].ulKind = PRSPEC_PROPID;
3008     ps[0].u.propid = PID_IS_ICONFILE;
3009     ps[1].ulKind = PRSPEC_PROPID;
3010     ps[1].u.propid = PID_IS_ICONINDEX;
3011
3012     hr = url->lpVtbl->QueryInterface(url, &IID_IPropertySetStorage, (void **) &pPropSetStg);
3013     if (SUCCEEDED(hr))
3014     {
3015         hr = IPropertySetStorage_Open(pPropSetStg, &FMTID_Intshcut, STGM_READ | STGM_SHARE_EXCLUSIVE, &pPropStg);
3016         if (SUCCEEDED(hr))
3017         {
3018             hr = IPropertyStorage_ReadMultiple(pPropStg, 2, ps, pv);
3019             if (SUCCEEDED(hr))
3020             {
3021                 if (pv[0].vt == VT_LPWSTR && pv[0].u.pwszVal)
3022                 {
3023                     icon_name = extract_icon( pv[0].u.pwszVal, pv[1].u.iVal, NULL, bWait );
3024
3025                     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);
3026                 }
3027                 PropVariantClear(&pv[0]);
3028                 PropVariantClear(&pv[1]);
3029             }
3030             IPropertyStorage_Release(pPropStg);
3031         }
3032         IPropertySetStorage_Release(pPropSetStg);
3033     }
3034
3035     /* fail - try once again after parent process exit */
3036     if( !icon_name )
3037     {
3038         if (bWait)
3039         {
3040             WINE_WARN("Unable to extract icon, deferring.\n");
3041             ret = FALSE;
3042             goto cleanup;
3043         }
3044         WINE_ERR("failed to extract icon from %s\n",
3045                  wine_dbgstr_w( pv[0].u.pwszVal ));
3046     }
3047
3048     hSem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
3049     if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hSem, FALSE, INFINITE, QS_ALLINPUT ) )
3050     {
3051         WINE_ERR("failed wait for semaphore\n");
3052         goto cleanup;
3053     }
3054     if (in_desktop_dir(csidl))
3055     {
3056         char *location;
3057         const char *lastEntry;
3058         lastEntry = strrchr(link_name, '/');
3059         if (lastEntry == NULL)
3060             lastEntry = link_name;
3061         else
3062             ++lastEntry;
3063         location = heap_printf("%s/%s.desktop", xdg_desktop_dir, lastEntry);
3064         if (location)
3065         {
3066             r = !write_desktop_entry(NULL, location, lastEntry, "winebrowser", escaped_urlPath, NULL, NULL, icon_name);
3067             if (r == 0)
3068                 chmod(location, 0755);
3069             HeapFree(GetProcessHeap(), 0, location);
3070         }
3071     }
3072     else
3073         r = !write_menu_entry(unix_link, link_name, "winebrowser", escaped_urlPath, NULL, NULL, icon_name);
3074     ret = (r != 0);
3075     ReleaseSemaphore(hSem, 1, NULL);
3076
3077 cleanup:
3078     if (hSem)
3079         CloseHandle(hSem);
3080     HeapFree( GetProcessHeap(), 0, icon_name );
3081     HeapFree(GetProcessHeap(), 0, link_name);
3082     CoTaskMemFree( urlPath );
3083     HeapFree(GetProcessHeap(), 0, escaped_urlPath);
3084     HeapFree(GetProcessHeap(), 0, unix_link);
3085     return ret;
3086 }
3087
3088 static BOOL WaitForParentProcess( void )
3089 {
3090     PROCESSENTRY32 procentry;
3091     HANDLE hsnapshot = NULL, hprocess = NULL;
3092     DWORD ourpid = GetCurrentProcessId();
3093     BOOL ret = FALSE, rc;
3094
3095     WINE_TRACE("Waiting for parent process\n");
3096     if ((hsnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 )) ==
3097         INVALID_HANDLE_VALUE)
3098     {
3099         WINE_ERR("CreateToolhelp32Snapshot failed, error %d\n", GetLastError());
3100         goto done;
3101     }
3102
3103     procentry.dwSize = sizeof(PROCESSENTRY32);
3104     rc = Process32First( hsnapshot, &procentry );
3105     while (rc)
3106     {
3107         if (procentry.th32ProcessID == ourpid) break;
3108         rc = Process32Next( hsnapshot, &procentry );
3109     }
3110     if (!rc)
3111     {
3112         WINE_WARN("Unable to find current process id %d when listing processes\n", ourpid);
3113         goto done;
3114     }
3115
3116     if ((hprocess = OpenProcess( SYNCHRONIZE, FALSE, procentry.th32ParentProcessID )) ==
3117         NULL)
3118     {
3119         WINE_WARN("OpenProcess failed pid=%d, error %d\n", procentry.th32ParentProcessID,
3120                  GetLastError());
3121         goto done;
3122     }
3123
3124     if (MsgWaitForMultipleObjects( 1, &hprocess, FALSE, INFINITE, QS_ALLINPUT ) == WAIT_OBJECT_0)
3125         ret = TRUE;
3126     else
3127         WINE_ERR("Unable to wait for parent process, error %d\n", GetLastError());
3128
3129 done:
3130     if (hprocess) CloseHandle( hprocess );
3131     if (hsnapshot) CloseHandle( hsnapshot );
3132     return ret;
3133 }
3134
3135 static BOOL Process_Link( LPCWSTR linkname, BOOL bWait )
3136 {
3137     IShellLinkW *sl;
3138     IPersistFile *pf;
3139     HRESULT r;
3140     WCHAR fullname[MAX_PATH];
3141     DWORD len;
3142
3143     WINE_TRACE("%s, wait %d\n", wine_dbgstr_w(linkname), bWait);
3144
3145     if( !linkname[0] )
3146     {
3147         WINE_ERR("link name missing\n");
3148         return 1;
3149     }
3150
3151     len=GetFullPathNameW( linkname, MAX_PATH, fullname, NULL );
3152     if (len==0 || len>MAX_PATH)
3153     {
3154         WINE_ERR("couldn't get full path of link file\n");
3155         return 1;
3156     }
3157
3158     r = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
3159                           &IID_IShellLinkW, (LPVOID *) &sl );
3160     if( FAILED( r ) )
3161     {
3162         WINE_ERR("No IID_IShellLink\n");
3163         return 1;
3164     }
3165
3166     r = IShellLinkW_QueryInterface( sl, &IID_IPersistFile, (LPVOID*) &pf );
3167     if( FAILED( r ) )
3168     {
3169         WINE_ERR("No IID_IPersistFile\n");
3170         return 1;
3171     }
3172
3173     r = IPersistFile_Load( pf, fullname, STGM_READ );
3174     if( SUCCEEDED( r ) )
3175     {
3176         /* If something fails (eg. Couldn't extract icon)
3177          * wait for parent process and try again
3178          */
3179         if( ! InvokeShellLinker( sl, fullname, bWait ) && bWait )
3180         {
3181             WaitForParentProcess();
3182             InvokeShellLinker( sl, fullname, FALSE );
3183         }
3184     }
3185     else
3186     {
3187         WINE_ERR("unable to load %s\n", wine_dbgstr_w(linkname));
3188     }
3189
3190     IPersistFile_Release( pf );
3191     IShellLinkW_Release( sl );
3192
3193     return !r;
3194 }
3195
3196 static BOOL Process_URL( LPCWSTR urlname, BOOL bWait )
3197 {
3198     IUniformResourceLocatorW *url;
3199     IPersistFile *pf;
3200     HRESULT r;
3201     WCHAR fullname[MAX_PATH];
3202     DWORD len;
3203
3204     WINE_TRACE("%s, wait %d\n", wine_dbgstr_w(urlname), bWait);
3205
3206     if( !urlname[0] )
3207     {
3208         WINE_ERR("URL name missing\n");
3209         return 1;
3210     }
3211
3212     len=GetFullPathNameW( urlname, MAX_PATH, fullname, NULL );
3213     if (len==0 || len>MAX_PATH)
3214     {
3215         WINE_ERR("couldn't get full path of URL file\n");
3216         return 1;
3217     }
3218
3219     r = CoCreateInstance( &CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER,
3220                           &IID_IUniformResourceLocatorW, (LPVOID *) &url );
3221     if( FAILED( r ) )
3222     {
3223         WINE_ERR("No IID_IUniformResourceLocatorW\n");
3224         return 1;
3225     }
3226
3227     r = url->lpVtbl->QueryInterface( url, &IID_IPersistFile, (LPVOID*) &pf );
3228     if( FAILED( r ) )
3229     {
3230         WINE_ERR("No IID_IPersistFile\n");
3231         return 1;
3232     }
3233     r = IPersistFile_Load( pf, fullname, STGM_READ );
3234     if( SUCCEEDED( r ) )
3235     {
3236         /* If something fails (eg. Couldn't extract icon)
3237          * wait for parent process and try again
3238          */
3239         if( ! InvokeShellLinkerForURL( url, fullname, bWait ) && bWait )
3240         {
3241             WaitForParentProcess();
3242             InvokeShellLinkerForURL( url, fullname, FALSE );
3243         }
3244     }
3245
3246     IPersistFile_Release( pf );
3247     url->lpVtbl->Release( url );
3248
3249     return !r;
3250 }
3251
3252 static void RefreshFileTypeAssociations(void)
3253 {
3254     HANDLE hSem = NULL;
3255     char *mime_dir = NULL;
3256     char *packages_dir = NULL;
3257     char *applications_dir = NULL;
3258     BOOL hasChanged;
3259
3260     hSem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
3261     if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hSem, FALSE, INFINITE, QS_ALLINPUT ) )
3262     {
3263         WINE_ERR("failed wait for semaphore\n");
3264         CloseHandle(hSem);
3265         hSem = NULL;
3266         goto end;
3267     }
3268
3269     mime_dir = heap_printf("%s/mime", xdg_data_dir);
3270     if (mime_dir == NULL)
3271     {
3272         WINE_ERR("out of memory\n");
3273         goto end;
3274     }
3275     create_directories(mime_dir);
3276
3277     packages_dir = heap_printf("%s/packages", mime_dir);
3278     if (packages_dir == NULL)
3279     {
3280         WINE_ERR("out of memory\n");
3281         goto end;
3282     }
3283     create_directories(packages_dir);
3284
3285     applications_dir = heap_printf("%s/applications", xdg_data_dir);
3286     if (applications_dir == NULL)
3287     {
3288         WINE_ERR("out of memory\n");
3289         goto end;
3290     }
3291     create_directories(applications_dir);
3292
3293     hasChanged = generate_associations(xdg_data_dir, packages_dir, applications_dir);
3294     hasChanged |= cleanup_associations();
3295     if (hasChanged)
3296     {
3297         const char *argv[3];
3298
3299         argv[0] = "update-mime-database";
3300         argv[1] = mime_dir;
3301         argv[2] = NULL;
3302         spawnvp( _P_NOWAIT, argv[0], argv );
3303
3304         argv[0] = "update-desktop-database";
3305         argv[1] = applications_dir;
3306         spawnvp( _P_NOWAIT, argv[0], argv );
3307     }
3308
3309 end:
3310     if (hSem)
3311     {
3312         ReleaseSemaphore(hSem, 1, NULL);
3313         CloseHandle(hSem);
3314     }
3315     HeapFree(GetProcessHeap(), 0, mime_dir);
3316     HeapFree(GetProcessHeap(), 0, packages_dir);
3317     HeapFree(GetProcessHeap(), 0, applications_dir);
3318 }
3319
3320 static void cleanup_menus(void)
3321 {
3322     HKEY hkey;
3323
3324     hkey = open_menus_reg_key();
3325     if (hkey)
3326     {
3327         int i;
3328         LSTATUS lret = ERROR_SUCCESS;
3329         for (i = 0; lret == ERROR_SUCCESS; )
3330         {
3331             WCHAR *value = NULL;
3332             WCHAR *data = NULL;
3333             DWORD valueSize = 4096;
3334             DWORD dataSize = 4096;
3335             while (1)
3336             {
3337                 lret = ERROR_OUTOFMEMORY;
3338                 value = HeapAlloc(GetProcessHeap(), 0, valueSize * sizeof(WCHAR));
3339                 if (value == NULL)
3340                     break;
3341                 data = HeapAlloc(GetProcessHeap(), 0, dataSize * sizeof(WCHAR));
3342                 if (data == NULL)
3343                     break;
3344                 lret = RegEnumValueW(hkey, i, value, &valueSize, NULL, NULL, (BYTE*)data, &dataSize);
3345                 if (lret == ERROR_SUCCESS || lret != ERROR_MORE_DATA)
3346                     break;
3347                 valueSize *= 2;
3348                 dataSize *= 2;
3349                 HeapFree(GetProcessHeap(), 0, value);
3350                 HeapFree(GetProcessHeap(), 0, data);
3351                 value = data = NULL;
3352             }
3353             if (lret == ERROR_SUCCESS)
3354             {
3355                 char *unix_file;
3356                 char *windows_file;
3357                 unix_file = wchars_to_unix_chars(value);
3358                 windows_file = wchars_to_unix_chars(data);
3359                 if (unix_file != NULL && windows_file != NULL)
3360                 {
3361                     struct stat filestats;
3362                     if (stat(windows_file, &filestats) < 0 && errno == ENOENT)
3363                     {
3364                         WINE_TRACE("removing menu related file %s\n", unix_file);
3365                         remove(unix_file);
3366                         RegDeleteValueW(hkey, value);
3367                     }
3368                     else
3369                         i++;
3370                 }
3371                 else
3372                 {
3373                     WINE_ERR("out of memory enumerating menus\n");
3374                     lret = ERROR_OUTOFMEMORY;
3375                 }
3376                 HeapFree(GetProcessHeap(), 0, unix_file);
3377                 HeapFree(GetProcessHeap(), 0, windows_file);
3378             }
3379             else if (lret != ERROR_NO_MORE_ITEMS)
3380                 WINE_ERR("error %d reading registry\n", lret);
3381             HeapFree(GetProcessHeap(), 0, value);
3382             HeapFree(GetProcessHeap(), 0, data);
3383         }
3384         RegCloseKey(hkey);
3385     }
3386     else
3387         WINE_ERR("error opening registry key, menu cleanup failed\n");
3388 }
3389
3390 static void thumbnail_lnk(LPCWSTR lnkPath, LPCWSTR outputPath)
3391 {
3392     char *utf8lnkPath = NULL;
3393     char *utf8OutputPath = NULL;
3394     WCHAR *winLnkPath = NULL;
3395     IShellLinkW *shellLink = NULL;
3396     IPersistFile *persistFile = NULL;
3397     WCHAR szTmp[MAX_PATH];
3398     WCHAR szPath[MAX_PATH];
3399     WCHAR szArgs[INFOTIPSIZE];
3400     WCHAR szIconPath[MAX_PATH];
3401     int iconId;
3402     IStream *stream = NULL;
3403     HRESULT hr;
3404
3405     utf8lnkPath = wchars_to_utf8_chars(lnkPath);
3406     if (utf8lnkPath == NULL)
3407     {
3408         WINE_ERR("out of memory converting paths\n");
3409         goto end;
3410     }
3411
3412     utf8OutputPath = wchars_to_utf8_chars(outputPath);
3413     if (utf8OutputPath == NULL)
3414     {
3415         WINE_ERR("out of memory converting paths\n");
3416         goto end;
3417     }
3418
3419     winLnkPath = wine_get_dos_file_name(utf8lnkPath);
3420     if (winLnkPath == NULL)
3421     {
3422         WINE_ERR("could not convert %s to DOS path\n", utf8lnkPath);
3423         goto end;
3424     }
3425
3426     hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
3427                           &IID_IShellLinkW, (LPVOID*)&shellLink);
3428     if (FAILED(hr))
3429     {
3430         WINE_ERR("could not create IShellLinkW, error 0x%08X\n", hr);
3431         goto end;
3432     }
3433
3434     hr = IShellLinkW_QueryInterface(shellLink, &IID_IPersistFile, (LPVOID)&persistFile);
3435     if (FAILED(hr))
3436     {
3437         WINE_ERR("could not query IPersistFile, error 0x%08X\n", hr);
3438         goto end;
3439     }
3440
3441     hr = IPersistFile_Load(persistFile, winLnkPath, STGM_READ);
3442     if (FAILED(hr))
3443     {
3444         WINE_ERR("could not read .lnk, error 0x%08X\n", hr);
3445         goto end;
3446     }
3447
3448     get_cmdline(shellLink, szTmp, MAX_PATH, szArgs, INFOTIPSIZE);
3449     ExpandEnvironmentStringsW(szTmp, szPath, MAX_PATH);
3450     szTmp[0] = 0;
3451     IShellLinkW_GetIconLocation(shellLink, szTmp, MAX_PATH, &iconId);
3452     ExpandEnvironmentStringsW(szTmp, szIconPath, MAX_PATH);
3453
3454     if(!szPath[0])
3455     {
3456         LPITEMIDLIST pidl = NULL;
3457         IShellLinkW_GetIDList(shellLink, &pidl);
3458         if (pidl && SHGetPathFromIDListW(pidl, szPath))
3459             WINE_TRACE("pidl path  : %s\n", wine_dbgstr_w(szPath));
3460     }
3461
3462     if (szIconPath[0])
3463     {
3464         hr = open_icon(szIconPath, iconId, FALSE, &stream);
3465         if (SUCCEEDED(hr))
3466             hr = write_native_icon(stream, utf8OutputPath, NULL);
3467     }
3468     else
3469     {
3470         hr = open_icon(szPath, iconId, FALSE, &stream);
3471         if (SUCCEEDED(hr))
3472             hr = write_native_icon(stream, utf8OutputPath, NULL);
3473     }
3474
3475 end:
3476     HeapFree(GetProcessHeap(), 0, utf8lnkPath);
3477     HeapFree(GetProcessHeap(), 0, utf8OutputPath);
3478     HeapFree(GetProcessHeap(), 0, winLnkPath);
3479     if (shellLink != NULL)
3480         IShellLinkW_Release(shellLink);
3481     if (persistFile != NULL)
3482         IPersistFile_Release(persistFile);
3483     if (stream != NULL)
3484         IStream_Release(stream);
3485 }
3486
3487 static WCHAR *next_token( LPWSTR *p )
3488 {
3489     LPWSTR token = NULL, t = *p;
3490
3491     if( !t )
3492         return NULL;
3493
3494     while( t && !token )
3495     {
3496         switch( *t )
3497         {
3498         case ' ':
3499             t++;
3500             continue;
3501         case '"':
3502             /* unquote the token */
3503             token = ++t;
3504             t = strchrW( token, '"' );
3505             if( t )
3506                  *t++ = 0;
3507             break;
3508         case 0:
3509             t = NULL;
3510             break;
3511         default:
3512             token = t;
3513             t = strchrW( token, ' ' );
3514             if( t )
3515                  *t++ = 0;
3516             break;
3517         }
3518     }
3519     *p = t;
3520     return token;
3521 }
3522
3523 static BOOL init_xdg(void)
3524 {
3525     WCHAR shellDesktopPath[MAX_PATH];
3526     HRESULT hr = SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, shellDesktopPath);
3527     if (SUCCEEDED(hr))
3528         xdg_desktop_dir = wine_get_unix_file_name(shellDesktopPath);
3529     if (xdg_desktop_dir == NULL)
3530     {
3531         WINE_ERR("error looking up the desktop directory\n");
3532         return FALSE;
3533     }
3534
3535     if (getenv("XDG_CONFIG_HOME"))
3536         xdg_config_dir = heap_printf("%s/menus/applications-merged", getenv("XDG_CONFIG_HOME"));
3537     else
3538         xdg_config_dir = heap_printf("%s/.config/menus/applications-merged", getenv("HOME"));
3539     if (xdg_config_dir)
3540     {
3541         create_directories(xdg_config_dir);
3542         if (getenv("XDG_DATA_HOME"))
3543             xdg_data_dir = strdupA(getenv("XDG_DATA_HOME"));
3544         else
3545             xdg_data_dir = heap_printf("%s/.local/share", getenv("HOME"));
3546         if (xdg_data_dir)
3547         {
3548             char *buffer;
3549             create_directories(xdg_data_dir);
3550             buffer = heap_printf("%s/desktop-directories", xdg_data_dir);
3551             if (buffer)
3552             {
3553                 mkdir(buffer, 0777);
3554                 HeapFree(GetProcessHeap(), 0, buffer);
3555             }
3556             return TRUE;
3557         }
3558         HeapFree(GetProcessHeap(), 0, xdg_config_dir);
3559     }
3560     WINE_ERR("out of memory\n");
3561     return FALSE;
3562 }
3563
3564 /***********************************************************************
3565  *
3566  *           wWinMain
3567  */
3568 int PASCAL wWinMain (HINSTANCE hInstance, HINSTANCE prev, LPWSTR cmdline, int show)
3569 {
3570     static const WCHAR dash_aW[] = {'-','a',0};
3571     static const WCHAR dash_rW[] = {'-','r',0};
3572     static const WCHAR dash_tW[] = {'-','t',0};
3573     static const WCHAR dash_uW[] = {'-','u',0};
3574     static const WCHAR dash_wW[] = {'-','w',0};
3575
3576     LPWSTR token = NULL, p;
3577     BOOL bWait = FALSE;
3578     BOOL bURL = FALSE;
3579     HRESULT hr;
3580     int ret = 0;
3581
3582     if (!init_xdg())
3583         return 1;
3584
3585     hr = CoInitialize(NULL);
3586     if (FAILED(hr))
3587     {
3588         WINE_ERR("could not initialize COM, error 0x%08X\n", hr);
3589         return 1;
3590     }
3591
3592     for( p = cmdline; p && *p; )
3593     {
3594         token = next_token( &p );
3595         if( !token )
3596             break;
3597         if( !strcmpW( token, dash_aW ) )
3598         {
3599             RefreshFileTypeAssociations();
3600             continue;
3601         }
3602         if( !strcmpW( token, dash_rW ) )
3603         {
3604             cleanup_menus();
3605             continue;
3606         }
3607         if( !strcmpW( token, dash_wW ) )
3608             bWait = TRUE;
3609         else if ( !strcmpW( token, dash_uW ) )
3610             bURL = TRUE;
3611         else if ( !strcmpW( token, dash_tW ) )
3612         {
3613             WCHAR *lnkFile = next_token( &p );
3614             if (lnkFile)
3615             {
3616                  WCHAR *outputFile = next_token( &p );
3617                  if (outputFile)
3618                      thumbnail_lnk(lnkFile, outputFile);
3619             }
3620         }
3621         else if( token[0] == '-' )
3622         {
3623             WINE_ERR( "unknown option %s\n", wine_dbgstr_w(token) );
3624         }
3625         else
3626         {
3627             BOOL bRet;
3628
3629             if (bURL)
3630                 bRet = Process_URL( token, bWait );
3631             else
3632                 bRet = Process_Link( token, bWait );
3633             if (!bRet)
3634             {
3635                 WINE_ERR( "failed to build menu item for %s\n", wine_dbgstr_w(token) );
3636                 ret = 1;
3637             }
3638         }
3639     }
3640
3641     CoUninitialize();
3642     return ret;
3643 }