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