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