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