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