Fixes for -Wmissing-declarations and -Wwrite-strings 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                         if (pIconDir->idEntries[i].wBitCount > nMaxBits)
285                         {
286                             nMaxBits = pIconDir->idEntries[i].wBitCount;
287                             nMax = 0;
288                         }
289                     }
290
291                     if ((pIconDir->idEntries[i].bHeight * pIconDir->idEntries[i].bWidth) > nMax)
292                     {
293                         lpName = MAKEINTRESOURCEW(pIconDir->idEntries[i].nID);
294                         nMax = pIconDir->idEntries[i].bHeight * pIconDir->idEntries[i].bWidth;
295                     }
296                 }
297             }
298
299             FreeResource(hResData);
300         }
301     }
302     else
303     {
304         WINE_ERR("ExtractFromEXEDLL failed, error %ld\n", GetLastError());
305         FreeLibrary(hModule);
306         return FALSE;
307     }
308  
309     if ((hResInfo = FindResourceW(hModule, lpName, (LPCWSTR)RT_ICON)))
310     {
311         if ((hResData = LoadResource(hModule, hResInfo)))
312         {
313             if ((pIcon = LockResource(hResData)))
314             {
315                 if(SaveIconResAsXPM(pIcon, szXPMFileName, szFileName))
316                     ret = TRUE;
317             }
318
319             FreeResource(hResData);
320         }
321     }
322
323     FreeLibrary(hModule);
324     return ret;
325 }
326
327 static BOOL ExtractFromEXEDLL(LPCWSTR szFileName, int nIndex, const char *szXPMFileName)
328 {
329     if (!extract_icon32(szFileName, nIndex, szXPMFileName) /*&&
330         !extract_icon16(szFileName, szXPMFileName)*/)
331         return FALSE;
332     return TRUE;
333 }
334
335 static int ExtractFromICO(LPCWSTR szFileName, const char *szXPMFileName)
336 {
337     FILE *fICOFile;
338     ICONDIR iconDir;
339     ICONDIRENTRY *pIconDirEntry;
340     int nMax = 0;
341     int nIndex = 0;
342     void *pIcon;
343     int i;
344     char *filename;
345
346     filename = wine_get_unix_file_name(szFileName);
347     if (!(fICOFile = fopen(filename, "r")))
348         goto error1;
349
350     if (fread(&iconDir, sizeof (ICONDIR), 1, fICOFile) != 1)
351         goto error2;
352     if ((iconDir.idReserved != 0) || (iconDir.idType != 1))
353         goto error2;
354
355     if ((pIconDirEntry = malloc(iconDir.idCount * sizeof (ICONDIRENTRY))) == NULL)
356         goto error2;
357     if (fread(pIconDirEntry, sizeof (ICONDIRENTRY), iconDir.idCount, fICOFile) != iconDir.idCount)
358         goto error3;
359
360     for (i = 0; i < iconDir.idCount; i++)
361         if ((pIconDirEntry[i].bHeight * pIconDirEntry[i].bWidth) > nMax)
362         {
363             nIndex = i;
364             nMax = pIconDirEntry[i].bHeight * pIconDirEntry[i].bWidth;
365         }
366     if ((pIcon = malloc(pIconDirEntry[nIndex].dwBytesInRes)) == NULL)
367         goto error3;
368     if (fseek(fICOFile, pIconDirEntry[nIndex].dwImageOffset, SEEK_SET))
369         goto error4;
370     if (fread(pIcon, pIconDirEntry[nIndex].dwBytesInRes, 1, fICOFile) != 1)
371         goto error4;
372
373     if(!SaveIconResAsXPM(pIcon, szXPMFileName, szFileName))
374         goto error4;
375
376     free(pIcon);
377     free(pIconDirEntry);
378     fclose(fICOFile);
379     HeapFree(GetProcessHeap(), 0, filename);
380     return 1;
381
382  error4:
383     free(pIcon);
384  error3:
385     free(pIconDirEntry);
386  error2:
387     fclose(fICOFile);
388  error1:
389     HeapFree(GetProcessHeap(), 0, filename);
390     return 0;
391 }
392
393 static BOOL create_default_icon( const char *filename, const char* comment )
394 {
395     FILE *fXPM;
396     unsigned int i;
397
398     if (!(fXPM = fopen(filename, "w"))) return FALSE;
399     if (fprintf(fXPM, "/* XPM */\n/* %s */\nstatic char * icon[] = {", comment) <= 0)
400         goto error;
401     for (i = 0; i < sizeof(wine_xpm)/sizeof(wine_xpm[0]); i++) {
402         if (fprintf( fXPM, "\n\"%s\",", wine_xpm[i]) <= 0)
403             goto error;
404     }
405     if (fprintf( fXPM, "};\n" ) <=0)
406         goto error;
407     fclose( fXPM );
408     return TRUE;
409  error:
410     fclose( fXPM );
411     unlink( filename );
412     return FALSE;
413
414 }
415
416 static unsigned short crc16(const char* string)
417 {
418     unsigned short crc = 0;
419     int i, j, xor_poly;
420
421     for (i = 0; string[i] != 0; i++)
422     {
423         char c = string[i];
424         for (j = 0; j < 8; c >>= 1, j++)
425         {
426             xor_poly = (c ^ crc) & 1;
427             crc >>= 1;
428             if (xor_poly)
429                 crc ^= 0xa001;
430         }
431     }
432     return crc;
433 }
434
435 /* extract an icon from an exe or icon file; helper for IPersistFile_fnSave */
436 static char *extract_icon( LPCWSTR path, int index)
437 {
438     int nodefault = 1;
439     unsigned short crc;
440     char *iconsdir, *ico_path, *ico_name, *xpm_path;
441     char* s;
442     HKEY hkey;
443     int n;
444
445     /* Where should we save the icon? */
446     WINE_TRACE("path=[%s] index=%d\n", wine_dbgstr_w(path), index);
447     iconsdir=NULL;  /* Default is no icon */
448     if (!RegOpenKeyA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\Wine", &hkey ))
449     {
450         static const WCHAR IconsDirW[] = {'I','c','o','n','s','D','i','r',0};
451         LPWSTR iconsdirW;
452         DWORD size = 0;
453
454         if (!RegQueryValueExW(hkey, IconsDirW, 0, NULL, NULL, &size))
455         {
456             iconsdirW = HeapAlloc(GetProcessHeap(), 0, size);
457             RegQueryValueExW(hkey, IconsDirW, 0, NULL, (LPBYTE)iconsdirW, &size);
458
459             s = wine_get_unix_file_name(iconsdirW);
460             if (s)
461                 iconsdir = s;
462             else
463             {
464                 int n = WideCharToMultiByte(CP_UNIXCP, 0, iconsdirW, -1, NULL, 0, NULL, NULL);
465                 iconsdir = HeapAlloc(GetProcessHeap(), 0, n);
466                 WideCharToMultiByte(CP_UNIXCP, 0, iconsdirW, -1, iconsdir, n, NULL, NULL);
467             }
468             HeapFree(GetProcessHeap(), 0, iconsdirW);
469         }
470         else
471         {
472             WCHAR path[MAX_PATH];
473
474             if (GetTempPathW(MAX_PATH, path))
475             {
476                 s = wine_get_unix_file_name(path);
477                 if (s)
478                     iconsdir = s;
479             }
480         }
481         RegCloseKey( hkey );
482     }
483     if (!iconsdir)
484         return NULL;  /* No icon created */
485     
486     if (!*iconsdir)
487     {
488         HeapFree(GetProcessHeap(), 0, iconsdir);
489         return NULL;  /* No icon created */
490     }
491
492     /* If icon path begins with a '*' then this is a deferred call */
493     if (path[0] == '*')
494     {
495         path++;
496         nodefault = 0;
497     }
498
499     /* Determine the icon base name */
500     n = WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL);
501     ico_path = HeapAlloc(GetProcessHeap(), 0, n);
502     WideCharToMultiByte(CP_UNIXCP, 0, path, -1, ico_path, n, NULL, NULL);
503     s=ico_name=ico_path;
504     while (*s!='\0') {
505         if (*s=='/' || *s=='\\') {
506             *s='\\';
507             ico_name=s;
508         } else {
509             *s=tolower(*s);
510         }
511         s++;
512     }
513     if (*ico_name=='\\') *ico_name++='\0';
514     s=strrchr(ico_name,'.');
515     if (s) *s='\0';
516
517     /* Compute the source-path hash */
518     crc=crc16(ico_path);
519
520     /* Try to treat the source file as an exe */
521     xpm_path=HeapAlloc(GetProcessHeap(), 0, strlen(iconsdir)+1+4+1+strlen(ico_name)+1+12+1+3);
522     sprintf(xpm_path,"%s/%04x_%s.%d.xpm",iconsdir,crc,ico_name,index);
523     if (ExtractFromEXEDLL( path, index, xpm_path ))
524         goto end;
525
526     /* Must be something else, ignore the index in that case */
527     sprintf(xpm_path,"%s/%04x_%s.xpm",iconsdir,crc,ico_name);
528     if (ExtractFromICO( path, xpm_path))
529         goto end;
530     if (!nodefault)
531         if (create_default_icon( xpm_path, ico_path ))
532             goto end;
533
534     HeapFree( GetProcessHeap(), 0, xpm_path );
535     xpm_path=NULL;
536
537  end:
538     HeapFree(GetProcessHeap(), 0, iconsdir);
539     HeapFree(GetProcessHeap(), 0, ico_path);
540     return xpm_path;
541 }
542
543 static BOOL DeferToRunOnce(LPWSTR link)
544 {
545     HKEY hkey;
546     LONG r, len;
547     static const WCHAR szRunOnce[] = {
548         'S','o','f','t','w','a','r','e','\\',
549         'M','i','c','r','o','s','o','f','t','\\',
550         'W','i','n','d','o','w','s','\\',
551         'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\',
552         'R','u','n','O','n','c','e',0
553     };
554     static const WCHAR szFormat[] = { '%','s',' ','"','%','s','"',0 };
555     LPWSTR buffer;
556     WCHAR szExecutable[MAX_PATH];
557
558     WINE_TRACE( "Deferring icon creation to reboot.\n");
559
560     len = GetModuleFileNameW( 0, szExecutable, MAX_PATH );
561     if (!len || len >= MAX_PATH) return FALSE;
562
563     len = ( lstrlenW( link ) + lstrlenW( szExecutable ) + 4)*sizeof(WCHAR);
564     buffer = HeapAlloc( GetProcessHeap(), 0, len );
565     if( !buffer )
566         return FALSE;
567
568     wsprintfW( buffer, szFormat, szExecutable, link );
569
570     r = RegCreateKeyExW(HKEY_LOCAL_MACHINE, szRunOnce, 0,
571               NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL);
572     if ( r == ERROR_SUCCESS )
573     {
574         r = RegSetValueExW(hkey, link, 0, REG_SZ,
575                    (LPBYTE) buffer, (lstrlenW(buffer) + 1)*sizeof(WCHAR));
576         RegCloseKey(hkey);
577     }
578     HeapFree(GetProcessHeap(), 0, buffer);
579
580     return ! r;
581 }
582
583 /* This escapes \ in filenames */
584 static LPSTR escape(LPCWSTR arg)
585 {
586     LPSTR narg, x;
587     LPCWSTR esc;
588     int len = 0, n;
589
590     esc = arg;
591     while((esc = strchrW(esc, '\\')))
592     {
593         esc++;
594         len++;
595     }
596
597     len += WideCharToMultiByte(CP_UNIXCP, 0, arg, -1, NULL, 0, NULL, NULL);
598     narg = HeapAlloc(GetProcessHeap(), 0, len);
599
600     x = narg;
601     while (*arg)
602     {
603         n = WideCharToMultiByte(CP_UNIXCP, 0, arg, 1, x, len, NULL, NULL);
604         x += n;
605         len -= n;
606         if (*arg == '\\')
607             *x++='\\'; /* escape \ */
608         arg++;
609     }
610     *x = 0;
611     return narg;
612 }
613
614 static int fork_and_wait( const char *linker, const char *link_name, const char *path,
615                           int desktop, const char *args, const char *icon_name,
616                           const char *workdir, const char *description )
617 {
618     int pos = 0;
619     const char *argv[20];
620     int retcode;
621
622     WINE_TRACE( "linker app='%s' link='%s' mode=%s "
623         "path='%s' args='%s' icon='%s' workdir='%s' descr='%s'\n",
624         linker, link_name, desktop ? "desktop" : "menu",
625         path, args, icon_name, workdir, description  );
626
627     argv[pos++] = linker ;
628     argv[pos++] = "--link";
629     argv[pos++] = link_name;
630     argv[pos++] = "--path";
631     argv[pos++] = path;
632     argv[pos++] = desktop ? "--desktop" : "--menu";
633     if (args && strlen(args))
634     {
635         argv[pos++] = "--args";
636         argv[pos++] = args;
637     }
638     if (icon_name)
639     {
640         argv[pos++] = "--icon";
641         argv[pos++] = icon_name;
642     }
643     if (workdir && strlen(workdir))
644     {
645         argv[pos++] = "--workdir";
646         argv[pos++] = workdir;
647     }
648     if (description && strlen(description))
649     {
650         argv[pos++] = "--descr";
651         argv[pos++] = description;
652     }
653     argv[pos] = NULL;
654
655     retcode=spawnvp( _P_WAIT, linker, argv );
656     if (retcode!=0)
657         WINE_ERR("%s returned %d\n",linker,retcode);
658     return retcode;
659 }
660
661 static char *cleanup_link( LPCWSTR link )
662 {
663     char *unix_file_name;
664     char  *p, *link_name;
665
666     unix_file_name = wine_get_unix_file_name(link);
667     if (!unix_file_name)
668     {
669         WINE_ERR("target link %s not found\n", wine_dbgstr_w(link));
670         return NULL;
671     }
672
673     link_name = unix_file_name;
674     p = strrchr( link_name, '/' );
675     if (p)
676         link_name = p + 1;
677
678     p = strrchr( link_name, '.' );
679     if (p)
680         *p = 0;
681
682     p = HeapAlloc(GetProcessHeap(), 0, strlen(link_name) + 1);
683     strcpy(p, link_name);
684     HeapFree(GetProcessHeap(), 0, unix_file_name);
685
686     return p;
687 }
688
689 /***********************************************************************
690  *
691  *           GetLinkLocation
692  *
693  * returns TRUE if successful
694  * *loc will contain CS_DESKTOPDIRECTORY, CS_STARTMENU, CS_STARTUP
695  */
696 static BOOL GetLinkLocation( LPCWSTR linkfile, DWORD *loc )
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;
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         return TRUE;
733     }
734
735     return FALSE;
736 }
737
738 static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bAgain )
739 {
740     char *link_name = NULL, *icon_name = NULL, *work_dir = NULL;
741     char *escaped_path = NULL, *escaped_args = NULL, *escaped_description = NULL;
742     WCHAR szDescription[INFOTIPSIZE], szPath[MAX_PATH], szWorkDir[MAX_PATH];
743     WCHAR szArgs[INFOTIPSIZE], szIconPath[MAX_PATH];
744     int iIconId = 0, r = -1;
745     DWORD csidl = -1;
746
747     if ( !link )
748     {
749         WINE_ERR("Link name is null\n");
750         return FALSE;
751     }
752
753     if( !GetLinkLocation( link, &csidl ) )
754     {
755         WINE_WARN("Unknown link location '%s'. Ignoring.\n",wine_dbgstr_w(link));
756         return TRUE;
757     }
758     if (!in_desktop_dir(csidl) && !in_startmenu(csidl))
759     {
760         WINE_WARN("Not under desktop or start menu. Ignoring.\n");
761         return TRUE;
762     }
763
764     szWorkDir[0] = 0;
765     IShellLinkW_GetWorkingDirectory( sl, szWorkDir, MAX_PATH );
766     WINE_TRACE("workdir    : %s\n", wine_dbgstr_w(szWorkDir));
767
768     szDescription[0] = 0;
769     IShellLinkW_GetDescription( sl, szDescription, INFOTIPSIZE );
770     WINE_TRACE("description: %s\n", wine_dbgstr_w(szDescription));
771
772     szPath[0] = 0;
773     IShellLinkW_GetPath( sl, szPath, MAX_PATH, NULL, SLGP_RAWPATH );
774     WINE_TRACE("path       : %s\n", wine_dbgstr_w(szPath));
775
776     szArgs[0] = 0;
777     IShellLinkW_GetArguments( sl, szArgs, INFOTIPSIZE );
778     WINE_TRACE("args       : %s\n", wine_dbgstr_w(szArgs));
779
780     szIconPath[0] = 0;
781     IShellLinkW_GetIconLocation( sl, szIconPath, MAX_PATH, &iIconId );
782     WINE_TRACE("icon file  : %s\n", wine_dbgstr_w(szIconPath) );
783
784     if( !szPath[0] )
785     {
786         LPITEMIDLIST pidl = NULL;
787         IShellLinkW_GetIDList( sl, &pidl );
788         if( pidl && SHGetPathFromIDListW( pidl, szPath ) );
789             WINE_TRACE("pidl path  : %s\n", wine_dbgstr_w(szPath));
790     }
791
792     /* extract the icon */
793     if( szIconPath[0] )
794         icon_name = extract_icon( szIconPath , iIconId );
795     else
796         icon_name = extract_icon( szPath, iIconId );
797
798     /* fail - try once again at reboot time */
799     if( !icon_name )
800     {
801         if (bAgain)
802         {
803             WINE_WARN("Unable to extract icon, deferring.\n");
804             goto cleanup;
805         }
806         WINE_ERR("failed to extract icon.\n");
807     }
808
809     /* check the path */
810     if( szPath[0] )
811     {
812         static const WCHAR exeW[] = {'.','e','x','e',0};
813         WCHAR *p;
814
815         /* check for .exe extension */
816         if (!(p = strrchrW( szPath, '.' ))) return FALSE;
817         if (strchrW( p, '\\' ) || strchrW( p, '/' )) return FALSE;
818         if (lstrcmpiW( p, exeW )) return FALSE;
819
820         /* convert app working dir */
821         if (szWorkDir[0])
822             work_dir = wine_get_unix_file_name( szWorkDir );
823     }
824     else
825     {
826         static const WCHAR startW[] = {
827             '\\','c','o','m','m','a','n','d',
828             '\\','s','t','a','r','t','.','e','x','e',0};
829
830         /* if there's no path... try run the link itself */
831         lstrcpynW(szArgs, link, MAX_PATH);
832         GetWindowsDirectoryW(szPath, MAX_PATH);
833         lstrcatW(szPath, startW);
834     }
835
836     link_name = cleanup_link( link );
837     if( !link_name )
838     {
839         WINE_ERR("Couldn't clean up link name %s\n", wine_dbgstr_w(link));
840         goto cleanup;
841     }
842
843     /* escape the path and parameters */
844     escaped_path = escape(szPath);
845     escaped_args = escape(szArgs);
846     escaped_description = escape(szDescription);
847
848     r = fork_and_wait("wineshelllink", link_name, escaped_path,
849                       in_desktop_dir(csidl), escaped_args, icon_name,
850                       work_dir ? work_dir : "", escaped_description);
851
852 cleanup:
853     HeapFree( GetProcessHeap(), 0, icon_name );
854     HeapFree( GetProcessHeap(), 0, work_dir );
855     HeapFree( GetProcessHeap(), 0, link_name );
856     HeapFree( GetProcessHeap(), 0, escaped_args );
857     HeapFree( GetProcessHeap(), 0, escaped_path );
858     HeapFree( GetProcessHeap(), 0, escaped_description );
859
860     if (r)
861     {
862         WINE_ERR("failed to fork and exec wineshelllink\n" );
863         return FALSE;
864     }
865
866     return TRUE;
867 }
868
869
870 static BOOL Process_Link( LPCWSTR linkname, BOOL bAgain )
871 {
872     IShellLinkW *sl;
873     IPersistFile *pf;
874     HRESULT r;
875     WCHAR fullname[MAX_PATH];
876     DWORD len;
877
878     WINE_TRACE("%s, again %d\n", wine_dbgstr_w(linkname), bAgain);
879
880     if( !linkname[0] )
881     {
882         WINE_ERR("link name missing\n");
883         return 1;
884     }
885
886     len=GetFullPathNameW( linkname, MAX_PATH, fullname, NULL );
887     if (len==0 || len>MAX_PATH)
888     {
889         WINE_ERR("couldn't get full path of link file\n");
890         return 1;
891     }
892
893     r = CoInitialize( NULL );
894     if( FAILED( r ) )
895     {
896         WINE_ERR("CoInitialize failed\n");
897         return 1;
898     }
899
900     r = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
901                           &IID_IShellLinkW, (LPVOID *) &sl );
902     if( FAILED( r ) )
903     {
904         WINE_ERR("No IID_IShellLink\n");
905         return 1;
906     }
907
908     r = IShellLinkW_QueryInterface( sl, &IID_IPersistFile, (LPVOID*) &pf );
909     if( FAILED( r ) )
910     {
911         WINE_ERR("No IID_IPersistFile\n");
912         return 1;
913     }
914
915     r = IPersistFile_Load( pf, fullname, STGM_READ );
916     if( SUCCEEDED( r ) )
917     {
918         /* If something fails (eg. Couldn't extract icon)
919          * defer this menu entry to reboot via runonce
920          */
921         if( ! InvokeShellLinker( sl, fullname, bAgain ) && bAgain )
922             DeferToRunOnce( fullname );
923         else
924             WINE_TRACE("Success.\n");
925     }
926
927     IPersistFile_Release( pf );
928     IShellLinkW_Release( sl );
929
930     CoUninitialize();
931
932     return !r;
933 }
934
935
936 static CHAR *next_token( LPSTR *p )
937 {
938     LPSTR token = NULL, t = *p;
939
940     if( !t )
941         return NULL;
942
943     while( t && !token )
944     {
945         switch( *t )
946         {
947         case ' ':
948             t++;
949             continue;
950         case '"':
951             /* unquote the token */
952             token = ++t;
953             t = strchr( token, '"' );
954             if( t )
955                  *t++ = 0;
956             break;
957         case 0:
958             t = NULL;
959             break;
960         default:
961             token = t;
962             t = strchr( token, ' ' );
963             if( t )
964                  *t++ = 0;
965             break;
966         }
967     }
968     *p = t;
969     return token;
970 }
971
972 /***********************************************************************
973  *
974  *           WinMain
975  */
976 int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE prev, LPSTR cmdline, int show)
977 {
978     LPSTR token = NULL, p;
979     BOOL bAgain = FALSE;
980     HANDLE hsem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
981     int ret = 0;
982
983     /* running multiple instances of wineshelllink
984        at the same time may be dangerous */
985     if( WAIT_OBJECT_0 != WaitForSingleObject( hsem, INFINITE ) )
986     {
987         CloseHandle(hsem);
988         return FALSE;
989     }
990
991     for( p = cmdline; p && *p; )
992     {
993         token = next_token( &p );
994         if( !token )
995             break;
996         if( !lstrcmpA( token, "-r" ) )
997             bAgain = TRUE;
998         else if( token[0] == '-' )
999         {
1000             WINE_ERR( "unknown option %s\n",token);
1001         }
1002         else
1003         {
1004             WCHAR link[MAX_PATH];
1005
1006             MultiByteToWideChar( CP_ACP, 0, token, -1, link, sizeof(link) );
1007             if( !Process_Link( link, bAgain ) )
1008             {
1009                 WINE_ERR( "failed to build menu item for %s\n",token);
1010                 ret = 1;
1011             }
1012         }
1013     }
1014
1015     ReleaseSemaphore( hsem, 1, NULL );
1016     CloseHandle( hsem );
1017
1018     return ret;
1019 }