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