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