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