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