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