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