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