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