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