winemenubuilder: Win64 printf format warning fixes.
[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  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23  *
24  *
25  *  This program is used to replicate the Windows desktop and start menu
26  * into the native desktop's copies.  Desktop entries are merged directly
27  * into the native desktop.  The Windows Start Menu corresponds to a Wine
28  * entry within the native "start" menu and replicates the whole tree
29  * structure of the Windows Start Menu.  Currently it does not differentiate
30  * between the user's desktop/start menu and the "All Users" copies.
31  *
32  *  This program will read a Windows shortcut file using the IShellLink
33  * interface, then invoke wineshelllink with the appropriate arguments
34  * to create a KDE/Gnome menu entry for the shortcut.
35  *
36  *  winemenubuilder [ -r ] <shortcut.lnk>
37  *
38  *  If the -r parameter is passed, and the shortcut cannot be created,
39  * this program will add a RunOnce entry to invoke itself at the next
40  * reboot.  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  * 
54  */
55
56 #include "config.h"
57 #include "wine/port.h"
58
59 #include <ctype.h>
60 #include <stdio.h>
61 #include <string.h>
62 #ifdef HAVE_UNISTD_H
63 #include <unistd.h>
64 #endif
65 #include <errno.h>
66 #include <stdarg.h>
67
68 #define COBJMACROS
69
70 #include <windows.h>
71 #include <shlobj.h>
72 #include <objidl.h>
73 #include <shlguid.h>
74 #include <appmgmt.h>
75
76 #include "wine/unicode.h"
77 #include "wine/debug.h"
78 #include "wine.xpm"
79
80 WINE_DEFAULT_DEBUG_CHANNEL(menubuilder);
81
82 #define in_desktop_dir(csidl) ((csidl)==CSIDL_DESKTOPDIRECTORY || \
83                                (csidl)==CSIDL_COMMON_DESKTOPDIRECTORY)
84 #define in_startmenu(csidl)   ((csidl)==CSIDL_STARTMENU || \
85                                (csidl)==CSIDL_COMMON_STARTMENU)
86         
87 /* link file formats */
88
89 #include "pshpack1.h"
90
91 typedef struct
92 {
93     BYTE bWidth;
94     BYTE bHeight;
95     BYTE bColorCount;
96     BYTE bReserved;
97     WORD wPlanes;
98     WORD wBitCount;
99     DWORD dwBytesInRes;
100     WORD nID;
101 } GRPICONDIRENTRY;
102
103 typedef struct
104 {
105     WORD idReserved;
106     WORD idType;
107     WORD idCount;
108     GRPICONDIRENTRY idEntries[1];
109 } GRPICONDIR;
110
111 typedef struct
112 {
113     BYTE bWidth;
114     BYTE bHeight;
115     BYTE bColorCount;
116     BYTE bReserved;
117     WORD wPlanes;
118     WORD wBitCount;
119     DWORD dwBytesInRes;
120     DWORD dwImageOffset;
121 } ICONDIRENTRY;
122
123 typedef struct
124 {
125     WORD idReserved;
126     WORD idType;
127     WORD idCount;
128 } ICONDIR;
129
130
131 #include "poppack.h"
132
133 typedef struct
134 {
135         HRSRC *pResInfo;
136         int   nIndex;
137 } ENUMRESSTRUCT;
138
139
140 /* Icon extraction routines
141  *
142  * FIXME: should use PrivateExtractIcons and friends
143  * FIXME: should not use stdio
144  */
145
146 static BOOL SaveIconResAsXPM(const BITMAPINFO *pIcon, const char *szXPMFileName, LPCWSTR commentW)
147 {
148     FILE *fXPMFile;
149     int nHeight;
150     int nXORWidthBytes;
151     int nANDWidthBytes;
152     BOOL b8BitColors;
153     int nColors;
154     const BYTE *pXOR;
155     const BYTE *pAND;
156     BOOL aColorUsed[256] = {0};
157     int nColorsUsed = 0;
158     int i,j;
159     char *comment;
160
161     if (!((pIcon->bmiHeader.biBitCount == 4) || (pIcon->bmiHeader.biBitCount == 8)))
162     {
163         WINE_FIXME("Unsupported color depth %d-bit\n", pIcon->bmiHeader.biBitCount);
164         return FALSE;
165     }
166
167     if (!(fXPMFile = fopen(szXPMFileName, "w")))
168     {
169         WINE_TRACE("unable to open '%s' for writing: %s\n", szXPMFileName, strerror(errno));
170         return FALSE;
171     }
172
173     i = WideCharToMultiByte(CP_UNIXCP, 0, commentW, -1, NULL, 0, NULL, NULL);
174     comment = HeapAlloc(GetProcessHeap(), 0, i);
175     WideCharToMultiByte(CP_UNIXCP, 0, commentW, -1, comment, i, NULL, NULL);
176
177     nHeight = pIcon->bmiHeader.biHeight / 2;
178     nXORWidthBytes = 4 * ((pIcon->bmiHeader.biWidth * pIcon->bmiHeader.biBitCount / 32)
179                           + ((pIcon->bmiHeader.biWidth * pIcon->bmiHeader.biBitCount % 32) > 0));
180     nANDWidthBytes = 4 * ((pIcon->bmiHeader.biWidth / 32)
181                           + ((pIcon->bmiHeader.biWidth % 32) > 0));
182     b8BitColors = pIcon->bmiHeader.biBitCount == 8;
183     nColors = pIcon->bmiHeader.biClrUsed ? pIcon->bmiHeader.biClrUsed
184         : 1 << pIcon->bmiHeader.biBitCount;
185     pXOR = (const BYTE*) pIcon + sizeof (BITMAPINFOHEADER) + (nColors * sizeof (RGBQUAD));
186     pAND = pXOR + nHeight * nXORWidthBytes;
187
188 #define MASK(x,y) (pAND[(x) / 8 + (nHeight - (y) - 1) * nANDWidthBytes] & (1 << (7 - (x) % 8)))
189 #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)
190
191     for (i = 0; i < nHeight; i++) {
192         for (j = 0; j < pIcon->bmiHeader.biWidth; j++) {
193             if (!aColorUsed[COLOR(j,i)] && !MASK(j,i))
194             {
195                 aColorUsed[COLOR(j,i)] = TRUE;
196                 nColorsUsed++;
197             }
198         }
199     }
200
201     if (fprintf(fXPMFile, "/* XPM */\n/* %s */\nstatic char *icon[] = {\n", comment) <= 0)
202         goto error;
203     if (fprintf(fXPMFile, "\"%d %d %d %d\",\n",
204                 (int) pIcon->bmiHeader.biWidth, nHeight, nColorsUsed + 1, 2) <=0)
205         goto error;
206
207     for (i = 0; i < nColors; i++) {
208         if (aColorUsed[i])
209             if (fprintf(fXPMFile, "\"%.2X c #%.2X%.2X%.2X\",\n", i, pIcon->bmiColors[i].rgbRed,
210                         pIcon->bmiColors[i].rgbGreen, pIcon->bmiColors[i].rgbBlue) <= 0)
211                 goto error;
212     }
213     if (fprintf(fXPMFile, "\"   c None\"") <= 0)
214         goto error;
215
216     for (i = 0; i < nHeight; i++)
217     {
218         if (fprintf(fXPMFile, ",\n\"") <= 0)
219             goto error;
220         for (j = 0; j < pIcon->bmiHeader.biWidth; j++)
221         {
222             if MASK(j,i)
223                 {
224                     if (fprintf(fXPMFile, "  ") <= 0)
225                         goto error;
226                 }
227             else
228                 if (fprintf(fXPMFile, "%.2X", COLOR(j,i)) <= 0)
229                     goto error;
230         }
231         if (fprintf(fXPMFile, "\"") <= 0)
232             goto error;
233     }
234     if (fprintf(fXPMFile, "};\n") <= 0)
235         goto error;
236
237 #undef MASK
238 #undef COLOR
239
240     HeapFree(GetProcessHeap(), 0, comment);
241     fclose(fXPMFile);
242     return TRUE;
243
244  error:
245     HeapFree(GetProcessHeap(), 0, comment);
246     fclose(fXPMFile);
247     unlink( szXPMFileName );
248     return FALSE;
249 }
250
251 static BOOL CALLBACK EnumResNameProc(HMODULE hModule, LPCWSTR lpszType, LPWSTR lpszName, LONG_PTR lParam)
252 {
253     ENUMRESSTRUCT *sEnumRes = (ENUMRESSTRUCT *) lParam;
254
255     if (!sEnumRes->nIndex--)
256     {
257         *sEnumRes->pResInfo = FindResourceW(hModule, lpszName, (LPCWSTR)RT_GROUP_ICON);
258         return FALSE;
259     }
260     else
261         return TRUE;
262 }
263
264 static BOOL extract_icon32(LPCWSTR szFileName, int nIndex, const char *szXPMFileName)
265 {
266     HMODULE hModule;
267     HRSRC hResInfo;
268     LPCWSTR lpName = NULL;
269     HGLOBAL hResData;
270     GRPICONDIR *pIconDir;
271     BITMAPINFO *pIcon;
272     ENUMRESSTRUCT sEnumRes;
273     int nMax = 0;
274     int nMaxBits = 0;
275     int i;
276     BOOL ret = FALSE;
277
278     hModule = LoadLibraryExW(szFileName, 0, LOAD_LIBRARY_AS_DATAFILE);
279     if (!hModule)
280     {
281         WINE_ERR("LoadLibraryExW (%s) failed, error %d\n",
282                  wine_dbgstr_w(szFileName), GetLastError());
283         return FALSE;
284     }
285
286     if (nIndex < 0)
287     {
288         hResInfo = FindResourceW(hModule, MAKEINTRESOURCEW(-nIndex), (LPCWSTR)RT_GROUP_ICON);
289         WINE_TRACE("FindResourceW (%s) called, return %p, error %d\n",
290                    wine_dbgstr_w(szFileName), hResInfo, GetLastError());
291     }
292     else
293     {
294         hResInfo=NULL;
295         sEnumRes.pResInfo = &hResInfo;
296         sEnumRes.nIndex = nIndex;
297         if (!EnumResourceNamesW(hModule, (LPCWSTR)RT_GROUP_ICON,
298                                 EnumResNameProc, (LONG_PTR)&sEnumRes))
299         {
300             WINE_TRACE("EnumResourceNamesW failed, error %d\n", GetLastError());
301         }
302     }
303
304     if (hResInfo)
305     {
306         if ((hResData = LoadResource(hModule, hResInfo)))
307         {
308             if ((pIconDir = LockResource(hResData)))
309             {
310                 for (i = 0; i < pIconDir->idCount; i++)
311                 {
312                     if ((pIconDir->idEntries[i].wBitCount >= nMaxBits) && (pIconDir->idEntries[i].wBitCount <= 8))
313                     {
314                         nMaxBits = pIconDir->idEntries[i].wBitCount;
315
316                         if ((pIconDir->idEntries[i].bHeight * pIconDir->idEntries[i].bWidth) >= nMax)
317                         {
318                             lpName = MAKEINTRESOURCEW(pIconDir->idEntries[i].nID);
319                             nMax = pIconDir->idEntries[i].bHeight * pIconDir->idEntries[i].bWidth;
320                         }
321                     }               
322                 }
323             }
324
325             FreeResource(hResData);
326         }
327     }
328     else
329     {
330         WINE_ERR("found no icon\n");
331         FreeLibrary(hModule);
332         return FALSE;
333     }
334  
335     if ((hResInfo = FindResourceW(hModule, lpName, (LPCWSTR)RT_ICON)))
336     {
337         if ((hResData = LoadResource(hModule, hResInfo)))
338         {
339             if ((pIcon = LockResource(hResData)))
340             {
341                 if(SaveIconResAsXPM(pIcon, szXPMFileName, szFileName))
342                     ret = TRUE;
343             }
344
345             FreeResource(hResData);
346         }
347     }
348
349     FreeLibrary(hModule);
350     return ret;
351 }
352
353 static BOOL ExtractFromEXEDLL(LPCWSTR szFileName, int nIndex, const char *szXPMFileName)
354 {
355     if (!extract_icon32(szFileName, nIndex, szXPMFileName) /*&&
356         !extract_icon16(szFileName, szXPMFileName)*/)
357         return FALSE;
358     return TRUE;
359 }
360
361 static int ExtractFromICO(LPCWSTR szFileName, const char *szXPMFileName)
362 {
363     FILE *fICOFile;
364     ICONDIR iconDir;
365     ICONDIRENTRY *pIconDirEntry;
366     int nMax = 0;
367     int nIndex = 0;
368     void *pIcon;
369     int i;
370     char *filename;
371
372     filename = wine_get_unix_file_name(szFileName);
373     if (!(fICOFile = fopen(filename, "r")))
374     {
375         WINE_TRACE("unable to open '%s' for reading: %s\n", filename, strerror(errno));
376         goto error1;
377     }
378
379     if (fread(&iconDir, sizeof (ICONDIR), 1, fICOFile) != 1 ||
380         (iconDir.idReserved != 0) || (iconDir.idType != 1))
381     {
382         WINE_ERR("Invalid ico file format\n");
383         goto error2;
384     }
385
386     if ((pIconDirEntry = HeapAlloc(GetProcessHeap(), 0, iconDir.idCount * sizeof (ICONDIRENTRY))) == NULL)
387         goto error2;
388     if (fread(pIconDirEntry, sizeof (ICONDIRENTRY), iconDir.idCount, fICOFile) != iconDir.idCount)
389         goto error3;
390
391     for (i = 0; i < iconDir.idCount; i++)
392         if ((pIconDirEntry[i].bHeight * pIconDirEntry[i].bWidth) > nMax)
393         {
394             nIndex = i;
395             nMax = pIconDirEntry[i].bHeight * pIconDirEntry[i].bWidth;
396         }
397     if ((pIcon = HeapAlloc(GetProcessHeap(), 0, pIconDirEntry[nIndex].dwBytesInRes)) == NULL)
398         goto error3;
399     if (fseek(fICOFile, pIconDirEntry[nIndex].dwImageOffset, SEEK_SET))
400         goto error4;
401     if (fread(pIcon, pIconDirEntry[nIndex].dwBytesInRes, 1, fICOFile) != 1)
402         goto error4;
403
404     if(!SaveIconResAsXPM(pIcon, szXPMFileName, szFileName))
405         goto error4;
406
407     HeapFree(GetProcessHeap(), 0, pIcon);
408     HeapFree(GetProcessHeap(), 0, pIconDirEntry);
409     fclose(fICOFile);
410     HeapFree(GetProcessHeap(), 0, filename);
411     return 1;
412
413  error4:
414     HeapFree(GetProcessHeap(), 0, pIcon);
415  error3:
416     HeapFree(GetProcessHeap(), 0, pIconDirEntry);
417  error2:
418     fclose(fICOFile);
419  error1:
420     HeapFree(GetProcessHeap(), 0, filename);
421     return 0;
422 }
423
424 static BOOL create_default_icon( const char *filename, const char* comment )
425 {
426     FILE *fXPM;
427     unsigned int i;
428
429     if (!(fXPM = fopen(filename, "w"))) return FALSE;
430     if (fprintf(fXPM, "/* XPM */\n/* %s */\nstatic char * icon[] = {", comment) <= 0)
431         goto error;
432     for (i = 0; i < sizeof(wine_xpm)/sizeof(wine_xpm[0]); i++) {
433         if (fprintf( fXPM, "\n\"%s\",", wine_xpm[i]) <= 0)
434             goto error;
435     }
436     if (fprintf( fXPM, "};\n" ) <=0)
437         goto error;
438     fclose( fXPM );
439     return TRUE;
440  error:
441     fclose( fXPM );
442     unlink( filename );
443     return FALSE;
444
445 }
446
447 static unsigned short crc16(const char* string)
448 {
449     unsigned short crc = 0;
450     int i, j, xor_poly;
451
452     for (i = 0; string[i] != 0; i++)
453     {
454         char c = string[i];
455         for (j = 0; j < 8; c >>= 1, j++)
456         {
457             xor_poly = (c ^ crc) & 1;
458             crc >>= 1;
459             if (xor_poly)
460                 crc ^= 0xa001;
461         }
462     }
463     return crc;
464 }
465
466 /* extract an icon from an exe or icon file; helper for IPersistFile_fnSave */
467 static char *extract_icon( LPCWSTR path, int index)
468 {
469     int nodefault = 1;
470     unsigned short crc;
471     char *iconsdir, *ico_path, *ico_name, *xpm_path;
472     char* s;
473     HKEY hkey;
474     int n;
475
476     /* Where should we save the icon? */
477     WINE_TRACE("path=[%s] index=%d\n", wine_dbgstr_w(path), index);
478     iconsdir=NULL;  /* Default is no icon */
479     /* @@ Wine registry key: HKCU\Software\Wine\WineMenuBuilder */
480     if (!RegOpenKeyA( HKEY_CURRENT_USER, "Software\\Wine\\WineMenuBuilder", &hkey ))
481     {
482         static const WCHAR IconsDirW[] = {'I','c','o','n','s','D','i','r',0};
483         LPWSTR iconsdirW;
484         DWORD size = 0;
485
486         if (!RegQueryValueExW(hkey, IconsDirW, 0, NULL, NULL, &size))
487         {
488             iconsdirW = HeapAlloc(GetProcessHeap(), 0, size);
489             RegQueryValueExW(hkey, IconsDirW, 0, NULL, (LPBYTE)iconsdirW, &size);
490
491             if (!(iconsdir = wine_get_unix_file_name(iconsdirW)))
492             {
493                 int n = WideCharToMultiByte(CP_UNIXCP, 0, iconsdirW, -1, NULL, 0, NULL, NULL);
494                 iconsdir = HeapAlloc(GetProcessHeap(), 0, n);
495                 WideCharToMultiByte(CP_UNIXCP, 0, iconsdirW, -1, iconsdir, n, NULL, NULL);
496             }
497             HeapFree(GetProcessHeap(), 0, iconsdirW);
498         }
499         RegCloseKey( hkey );
500     }
501
502     if (!iconsdir)
503     {
504         WCHAR path[MAX_PATH];
505         if (GetTempPathW(MAX_PATH, path))
506             iconsdir = wine_get_unix_file_name(path);
507         if (!iconsdir)
508         {
509             WINE_TRACE("no IconsDir\n");
510             return NULL;  /* No icon created */
511         }
512     }
513     
514     if (!*iconsdir)
515     {
516         WINE_TRACE("icon generation disabled\n");
517         HeapFree(GetProcessHeap(), 0, iconsdir);
518         return NULL;  /* No icon created */
519     }
520
521     /* If icon path begins with a '*' then this is a deferred call */
522     if (path[0] == '*')
523     {
524         path++;
525         nodefault = 0;
526     }
527
528     /* Determine the icon base name */
529     n = WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL);
530     ico_path = HeapAlloc(GetProcessHeap(), 0, n);
531     WideCharToMultiByte(CP_UNIXCP, 0, path, -1, ico_path, n, NULL, NULL);
532     s=ico_name=ico_path;
533     while (*s!='\0') {
534         if (*s=='/' || *s=='\\') {
535             *s='\\';
536             ico_name=s;
537         } else {
538             *s=tolower(*s);
539         }
540         s++;
541     }
542     if (*ico_name=='\\') *ico_name++='\0';
543     s=strrchr(ico_name,'.');
544     if (s) *s='\0';
545
546     /* Compute the source-path hash */
547     crc=crc16(ico_path);
548
549     /* Try to treat the source file as an exe */
550     xpm_path=HeapAlloc(GetProcessHeap(), 0, strlen(iconsdir)+1+4+1+strlen(ico_name)+1+12+1+3);
551     sprintf(xpm_path,"%s/%04x_%s.%d.xpm",iconsdir,crc,ico_name,index);
552     if (ExtractFromEXEDLL( path, index, xpm_path ))
553         goto end;
554
555     /* Must be something else, ignore the index in that case */
556     sprintf(xpm_path,"%s/%04x_%s.xpm",iconsdir,crc,ico_name);
557     if (ExtractFromICO( path, xpm_path))
558         goto end;
559     if (!nodefault)
560         if (create_default_icon( xpm_path, ico_path ))
561             goto end;
562
563     HeapFree( GetProcessHeap(), 0, xpm_path );
564     xpm_path=NULL;
565
566  end:
567     HeapFree(GetProcessHeap(), 0, iconsdir);
568     HeapFree(GetProcessHeap(), 0, ico_path);
569     return xpm_path;
570 }
571
572 static BOOL DeferToRunOnce(LPWSTR link)
573 {
574     HKEY hkey;
575     LONG r, len;
576     static const WCHAR szRunOnce[] = {
577         'S','o','f','t','w','a','r','e','\\',
578         'M','i','c','r','o','s','o','f','t','\\',
579         'W','i','n','d','o','w','s','\\',
580         'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
581         'R','u','n','O','n','c','e',0
582     };
583     static const WCHAR szFormat[] = { '%','s',' ','"','%','s','"',0 };
584     LPWSTR buffer;
585     WCHAR szExecutable[MAX_PATH];
586
587     WINE_TRACE( "Deferring icon creation to reboot.\n");
588
589     len = GetModuleFileNameW( 0, szExecutable, MAX_PATH );
590     if (!len || len >= MAX_PATH) return FALSE;
591
592     len = ( lstrlenW( link ) + lstrlenW( szExecutable ) + 4)*sizeof(WCHAR);
593     buffer = HeapAlloc( GetProcessHeap(), 0, len );
594     if( !buffer )
595         return FALSE;
596
597     wsprintfW( buffer, szFormat, szExecutable, link );
598
599     r = RegCreateKeyExW(HKEY_LOCAL_MACHINE, szRunOnce, 0,
600               NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL);
601     if ( r == ERROR_SUCCESS )
602     {
603         r = RegSetValueExW(hkey, link, 0, REG_SZ,
604                    (LPBYTE) buffer, (lstrlenW(buffer) + 1)*sizeof(WCHAR));
605         RegCloseKey(hkey);
606     }
607     HeapFree(GetProcessHeap(), 0, buffer);
608
609     return ! r;
610 }
611
612 /* This escapes \ in filenames */
613 static LPSTR escape(LPCWSTR arg)
614 {
615     LPSTR narg, x;
616     LPCWSTR esc;
617     int len = 0, n;
618
619     esc = arg;
620     while((esc = strchrW(esc, '\\')))
621     {
622         esc++;
623         len++;
624     }
625
626     len += WideCharToMultiByte(CP_UNIXCP, 0, arg, -1, NULL, 0, NULL, NULL);
627     narg = HeapAlloc(GetProcessHeap(), 0, len);
628
629     x = narg;
630     while (*arg)
631     {
632         n = WideCharToMultiByte(CP_UNIXCP, 0, arg, 1, x, len, NULL, NULL);
633         x += n;
634         len -= n;
635         if (*arg == '\\')
636             *x++='\\'; /* escape \ */
637         arg++;
638     }
639     *x = 0;
640     return narg;
641 }
642
643 static int fork_and_wait( const char *linker, const char *link_name, const char *path,
644                           int desktop, const char *args, const char *icon_name,
645                           const char *workdir, const char *description )
646 {
647     int pos = 0;
648     const char *argv[20];
649     int retcode;
650
651     WINE_TRACE( "linker app='%s' link='%s' mode=%s "
652         "path='%s' args='%s' icon='%s' workdir='%s' descr='%s'\n",
653         linker, link_name, desktop ? "desktop" : "menu",
654         path, args, icon_name, workdir, description  );
655
656     argv[pos++] = linker ;
657     argv[pos++] = "--link";
658     argv[pos++] = link_name;
659     argv[pos++] = "--path";
660     argv[pos++] = path;
661     argv[pos++] = desktop ? "--desktop" : "--menu";
662     if (args && strlen(args))
663     {
664         argv[pos++] = "--args";
665         argv[pos++] = args;
666     }
667     if (icon_name)
668     {
669         argv[pos++] = "--icon";
670         argv[pos++] = icon_name;
671     }
672     if (workdir && strlen(workdir))
673     {
674         argv[pos++] = "--workdir";
675         argv[pos++] = workdir;
676     }
677     if (description && strlen(description))
678     {
679         argv[pos++] = "--descr";
680         argv[pos++] = description;
681     }
682     argv[pos] = NULL;
683
684     retcode=spawnvp( _P_WAIT, linker, argv );
685     if (retcode!=0)
686         WINE_ERR("%s returned %d\n",linker,retcode);
687     return retcode;
688 }
689
690 /* Return a heap-allocated copy of the unix format difference between the two
691  * Windows-format paths.
692  * locn is the owning location
693  * link is within locn
694  */
695 static char *relative_path( LPCWSTR link, LPCWSTR locn )
696 {
697     char *unix_locn, *unix_link;
698     char *relative = NULL;
699
700     unix_locn = wine_get_unix_file_name(locn);
701     unix_link = wine_get_unix_file_name(link);
702     if (unix_locn && unix_link)
703     {
704         size_t len_unix_locn, len_unix_link;
705         len_unix_locn = strlen (unix_locn);
706         len_unix_link = strlen (unix_link);
707         if (len_unix_locn < len_unix_link && memcmp (unix_locn, unix_link, len_unix_locn) == 0 && unix_link[len_unix_locn] == '/')
708         {
709             size_t len_rel;
710             char *p = strrchr (unix_link + len_unix_locn, '/');
711             p = strrchr (p, '.');
712             if (p)
713             {
714                 *p = '\0';
715                 len_unix_link = p - unix_link;
716             }
717             len_rel = len_unix_link - len_unix_locn;
718             relative = HeapAlloc(GetProcessHeap(), 0, len_rel);
719             if (relative)
720             {
721                 memcpy (relative, unix_link + len_unix_locn + 1, len_rel);
722             }
723         }
724     }
725     if (!relative)
726         WINE_WARN("Could not separate the relative link path of %s in %s\n", wine_dbgstr_w(link), wine_dbgstr_w(locn));
727     HeapFree(GetProcessHeap(), 0, unix_locn);
728     HeapFree(GetProcessHeap(), 0, unix_link);
729     return relative;
730 }
731
732 /***********************************************************************
733  *
734  *           GetLinkLocation
735  *
736  * returns TRUE if successful
737  * *loc will contain CS_DESKTOPDIRECTORY, CS_STARTMENU, CS_STARTUP etc.
738  * *relative will contain the address of a heap-allocated copy of the portion
739  * of the filename that is within the specified location, in unix form
740  */
741 static BOOL GetLinkLocation( LPCWSTR linkfile, DWORD *loc, char **relative )
742 {
743     WCHAR filename[MAX_PATH], buffer[MAX_PATH];
744     DWORD len, i, r, filelen;
745     const DWORD locations[] = {
746         CSIDL_STARTUP, CSIDL_DESKTOPDIRECTORY, CSIDL_STARTMENU,
747         CSIDL_COMMON_STARTUP, CSIDL_COMMON_DESKTOPDIRECTORY,
748         CSIDL_COMMON_STARTMENU };
749
750     WINE_TRACE("%s\n", wine_dbgstr_w(linkfile));
751     filelen=GetFullPathNameW( linkfile, MAX_PATH, filename, NULL );
752     if (filelen==0 || filelen>MAX_PATH)
753         return FALSE;
754
755     WINE_TRACE("%s\n", wine_dbgstr_w(filename));
756
757     for( i=0; i<sizeof(locations)/sizeof(locations[0]); i++ )
758     {
759         if (!SHGetSpecialFolderPathW( 0, buffer, locations[i], FALSE ))
760             continue;
761
762         len = lstrlenW(buffer);
763         if (len >= MAX_PATH)
764             continue; /* We've just trashed memory! Hopefully we are OK */
765
766         if (len > filelen || filename[len]!='\\')
767             continue;
768         /* do a lstrcmpinW */
769         filename[len] = 0;
770         r = lstrcmpiW( filename, buffer );
771         filename[len] = '\\';
772         if ( r )
773             continue;
774
775         /* return the remainder of the string and link type */
776         *loc = locations[i];
777         *relative = relative_path (filename, buffer);
778         return (*relative != NULL);
779     }
780
781     return FALSE;
782 }
783
784 /* gets the target path directly or through MSI */
785 static HRESULT get_cmdline( IShellLinkW *sl, LPWSTR szPath, DWORD pathSize,
786                             LPWSTR szArgs, DWORD argsSize)
787 {
788     IShellLinkDataList *dl = NULL;
789     EXP_DARWIN_LINK *dar = NULL;
790     HRESULT hr;
791
792     szPath[0] = 0;
793     szArgs[0] = 0;
794
795     hr = IShellLinkW_GetPath( sl, szPath, pathSize, NULL, SLGP_RAWPATH );
796     if (hr == S_OK && szPath[0])
797     {
798         IShellLinkW_GetArguments( sl, szArgs, argsSize );
799         return hr;
800     }
801
802     hr = IShellLinkW_QueryInterface( sl, &IID_IShellLinkDataList, (LPVOID*) &dl );
803     if (FAILED(hr))
804         return hr;
805
806     hr = IShellLinkDataList_CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar );
807     if (SUCCEEDED(hr))
808     {
809         WCHAR* szCmdline;
810         DWORD cmdSize;
811
812         cmdSize=0;
813         hr = CommandLineFromMsiDescriptor( dar->szwDarwinID, NULL, &cmdSize );
814         if (hr == ERROR_SUCCESS)
815         {
816             cmdSize++;
817             szCmdline = HeapAlloc( GetProcessHeap(), 0, cmdSize*sizeof(WCHAR) );
818             hr = CommandLineFromMsiDescriptor( dar->szwDarwinID, szCmdline, &cmdSize );
819             WINE_TRACE("      command    : %s\n", wine_dbgstr_w(szCmdline));
820             if (hr == ERROR_SUCCESS)
821             {
822                 WCHAR *s, *d;
823                 int bcount, in_quotes;
824
825                 /* Extract the application path */
826                 bcount=0;
827                 in_quotes=0;
828                 s=szCmdline;
829                 d=szPath;
830                 while (*s)
831                 {
832                     if ((*s==0x0009 || *s==0x0020) && !in_quotes)
833                     {
834                         /* skip the remaining spaces */
835                         do {
836                             s++;
837                         } while (*s==0x0009 || *s==0x0020);
838                         break;
839                     }
840                     else if (*s==0x005c)
841                     {
842                         /* '\\' */
843                         *d++=*s++;
844                         bcount++;
845                     }
846                     else if (*s==0x0022)
847                     {
848                         /* '"' */
849                         if ((bcount & 1)==0)
850                         {
851                             /* Preceded by an even number of '\', this is
852                              * half that number of '\', plus a quote which
853                              * we erase.
854                              */
855                             d-=bcount/2;
856                             in_quotes=!in_quotes;
857                             s++;
858                         }
859                         else
860                         {
861                             /* Preceded by an odd number of '\', this is
862                              * half that number of '\' followed by a '"'
863                              */
864                             d=d-bcount/2-1;
865                             *d++='"';
866                             s++;
867                         }
868                         bcount=0;
869                     }
870                     else
871                     {
872                         /* a regular character */
873                         *d++=*s++;
874                         bcount=0;
875                     }
876                     if ((d-szPath) == pathSize)
877                     {
878                         /* Keep processing the path till we get to the
879                          * arguments, but 'stand still'
880                          */
881                         d--;
882                     }
883                 }
884                 /* Close the application path */
885                 *d=0;
886
887                 lstrcpynW(szArgs, s, argsSize);
888             }
889             HeapFree( GetProcessHeap(), 0, szCmdline );
890         }
891         LocalFree( dar );
892     }
893
894     IShellLinkDataList_Release( dl );
895     return hr;
896 }
897
898 static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bAgain )
899 {
900     char *link_name = NULL, *icon_name = NULL, *work_dir = NULL;
901     char *escaped_path = NULL, *escaped_args = NULL, *escaped_description = NULL;
902     WCHAR szDescription[INFOTIPSIZE], szPath[MAX_PATH], szWorkDir[MAX_PATH];
903     WCHAR szArgs[INFOTIPSIZE], szIconPath[MAX_PATH];
904     int iIconId = 0, r = -1;
905     DWORD csidl = -1;
906
907     if ( !link )
908     {
909         WINE_ERR("Link name is null\n");
910         return FALSE;
911     }
912
913     if( !GetLinkLocation( link, &csidl, &link_name ) )
914     {
915         WINE_WARN("Unknown link location '%s'. Ignoring.\n",wine_dbgstr_w(link));
916         return TRUE;
917     }
918     if (!in_desktop_dir(csidl) && !in_startmenu(csidl))
919     {
920         WINE_WARN("Not under desktop or start menu. Ignoring.\n");
921         return TRUE;
922     }
923     WINE_TRACE("Link       : %s\n", wine_dbgstr_a(link_name));
924
925     szWorkDir[0] = 0;
926     IShellLinkW_GetWorkingDirectory( sl, szWorkDir, MAX_PATH );
927     WINE_TRACE("workdir    : %s\n", wine_dbgstr_w(szWorkDir));
928
929     szDescription[0] = 0;
930     IShellLinkW_GetDescription( sl, szDescription, INFOTIPSIZE );
931     WINE_TRACE("description: %s\n", wine_dbgstr_w(szDescription));
932
933     get_cmdline( sl, szPath, MAX_PATH, szArgs, INFOTIPSIZE);
934     WINE_TRACE("path       : %s\n", wine_dbgstr_w(szPath));
935     WINE_TRACE("args       : %s\n", wine_dbgstr_w(szArgs));
936
937     szIconPath[0] = 0;
938     IShellLinkW_GetIconLocation( sl, szIconPath, MAX_PATH, &iIconId );
939     WINE_TRACE("icon file  : %s\n", wine_dbgstr_w(szIconPath) );
940
941     if( !szPath[0] )
942     {
943         LPITEMIDLIST pidl = NULL;
944         IShellLinkW_GetIDList( sl, &pidl );
945         if( pidl && SHGetPathFromIDListW( pidl, szPath ) )
946             WINE_TRACE("pidl path  : %s\n", wine_dbgstr_w(szPath));
947     }
948
949     /* extract the icon */
950     if( szIconPath[0] )
951         icon_name = extract_icon( szIconPath , iIconId );
952     else
953         icon_name = extract_icon( szPath, iIconId );
954
955     /* fail - try once again at reboot time */
956     if( !icon_name )
957     {
958         if (bAgain)
959         {
960             WINE_WARN("Unable to extract icon, deferring.\n");
961             goto cleanup;
962         }
963         WINE_ERR("failed to extract icon.\n");
964     }
965
966     /* check the path */
967     if( szPath[0] )
968     {
969         static const WCHAR exeW[] = {'.','e','x','e',0};
970         WCHAR *p;
971
972         /* check for .exe extension */
973         if (!(p = strrchrW( szPath, '.' ))) return FALSE;
974         if (strchrW( p, '\\' ) || strchrW( p, '/' )) return FALSE;
975         if (lstrcmpiW( p, exeW )) return FALSE;
976
977         /* convert app working dir */
978         if (szWorkDir[0])
979             work_dir = wine_get_unix_file_name( szWorkDir );
980     }
981     else
982     {
983         static const WCHAR startW[] = {
984             '\\','c','o','m','m','a','n','d',
985             '\\','s','t','a','r','t','.','e','x','e',0};
986
987         /* if there's no path... try run the link itself */
988         lstrcpynW(szArgs, link, MAX_PATH);
989         GetWindowsDirectoryW(szPath, MAX_PATH);
990         lstrcatW(szPath, startW);
991     }
992
993     /* escape the path and parameters */
994     escaped_path = escape(szPath);
995     escaped_args = escape(szArgs);
996     escaped_description = escape(szDescription);
997
998     r = fork_and_wait("wineshelllink", link_name, escaped_path,
999                       in_desktop_dir(csidl), escaped_args, icon_name,
1000                       work_dir ? work_dir : "", escaped_description);
1001
1002 cleanup:
1003     HeapFree( GetProcessHeap(), 0, icon_name );
1004     HeapFree( GetProcessHeap(), 0, work_dir );
1005     HeapFree( GetProcessHeap(), 0, link_name );
1006     HeapFree( GetProcessHeap(), 0, escaped_args );
1007     HeapFree( GetProcessHeap(), 0, escaped_path );
1008     HeapFree( GetProcessHeap(), 0, escaped_description );
1009
1010     if (r)
1011     {
1012         WINE_ERR("failed to fork and exec wineshelllink\n" );
1013         return FALSE;
1014     }
1015
1016     return TRUE;
1017 }
1018
1019
1020 static BOOL Process_Link( LPCWSTR linkname, BOOL bAgain )
1021 {
1022     IShellLinkW *sl;
1023     IPersistFile *pf;
1024     HRESULT r;
1025     WCHAR fullname[MAX_PATH];
1026     DWORD len;
1027
1028     WINE_TRACE("%s, again %d\n", wine_dbgstr_w(linkname), bAgain);
1029
1030     if( !linkname[0] )
1031     {
1032         WINE_ERR("link name missing\n");
1033         return 1;
1034     }
1035
1036     len=GetFullPathNameW( linkname, MAX_PATH, fullname, NULL );
1037     if (len==0 || len>MAX_PATH)
1038     {
1039         WINE_ERR("couldn't get full path of link file\n");
1040         return 1;
1041     }
1042
1043     r = CoInitialize( NULL );
1044     if( FAILED( r ) )
1045     {
1046         WINE_ERR("CoInitialize failed\n");
1047         return 1;
1048     }
1049
1050     r = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
1051                           &IID_IShellLinkW, (LPVOID *) &sl );
1052     if( FAILED( r ) )
1053     {
1054         WINE_ERR("No IID_IShellLink\n");
1055         return 1;
1056     }
1057
1058     r = IShellLinkW_QueryInterface( sl, &IID_IPersistFile, (LPVOID*) &pf );
1059     if( FAILED( r ) )
1060     {
1061         WINE_ERR("No IID_IPersistFile\n");
1062         return 1;
1063     }
1064
1065     r = IPersistFile_Load( pf, fullname, STGM_READ );
1066     if( SUCCEEDED( r ) )
1067     {
1068         /* If something fails (eg. Couldn't extract icon)
1069          * defer this menu entry to reboot via runonce
1070          */
1071         if( ! InvokeShellLinker( sl, fullname, bAgain ) && bAgain )
1072             DeferToRunOnce( fullname );
1073         else
1074             WINE_TRACE("Success.\n");
1075     }
1076
1077     IPersistFile_Release( pf );
1078     IShellLinkW_Release( sl );
1079
1080     CoUninitialize();
1081
1082     return !r;
1083 }
1084
1085
1086 static CHAR *next_token( LPSTR *p )
1087 {
1088     LPSTR token = NULL, t = *p;
1089
1090     if( !t )
1091         return NULL;
1092
1093     while( t && !token )
1094     {
1095         switch( *t )
1096         {
1097         case ' ':
1098             t++;
1099             continue;
1100         case '"':
1101             /* unquote the token */
1102             token = ++t;
1103             t = strchr( token, '"' );
1104             if( t )
1105                  *t++ = 0;
1106             break;
1107         case 0:
1108             t = NULL;
1109             break;
1110         default:
1111             token = t;
1112             t = strchr( token, ' ' );
1113             if( t )
1114                  *t++ = 0;
1115             break;
1116         }
1117     }
1118     *p = t;
1119     return token;
1120 }
1121
1122 /***********************************************************************
1123  *
1124  *           WinMain
1125  */
1126 int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE prev, LPSTR cmdline, int show)
1127 {
1128     LPSTR token = NULL, p;
1129     BOOL bAgain = FALSE;
1130     HANDLE hsem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
1131     int ret = 0;
1132
1133     /* running multiple instances of wineshelllink
1134        at the same time may be dangerous */
1135     if( WAIT_OBJECT_0 != WaitForSingleObject( hsem, INFINITE ) )
1136     {
1137         CloseHandle(hsem);
1138         return FALSE;
1139     }
1140
1141     for( p = cmdline; p && *p; )
1142     {
1143         token = next_token( &p );
1144         if( !token )
1145             break;
1146         if( !lstrcmpA( token, "-r" ) )
1147             bAgain = TRUE;
1148         else if( token[0] == '-' )
1149         {
1150             WINE_ERR( "unknown option %s\n",token);
1151         }
1152         else
1153         {
1154             WCHAR link[MAX_PATH];
1155
1156             MultiByteToWideChar( CP_ACP, 0, token, -1, link, sizeof(link)/sizeof(WCHAR) );
1157             if( !Process_Link( link, bAgain ) )
1158             {
1159                 WINE_ERR( "failed to build menu item for %s\n",token);
1160                 ret = 1;
1161             }
1162         }
1163     }
1164
1165     ReleaseSemaphore( hsem, 1, NULL );
1166     CloseHandle( hsem );
1167
1168     return ret;
1169 }