winemenubuilder: Add an option for thumbnailing .lnk files.
[wine/multimedia.git] / programs / winemenubuilder / winemenubuilder.c
blob7a7735811ac4019f48b8292b4091a2cd7e0a46c2
1 /*
2 * Helper program to build unix menu entries
4 * Copyright 1997 Marcus Meissner
5 * Copyright 1998 Juergen Schmied
6 * Copyright 2003 Mike McCormack for CodeWeavers
7 * Copyright 2004 Dmitry Timoshkov
8 * Copyright 2005 Bill Medland
9 * Copyright 2008 Damjan Jovanovic
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this library; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 * This program is used to replicate the Windows desktop and start menu
27 * into the native desktop's copies. Desktop entries are merged directly
28 * into the native desktop. The Windows Start Menu corresponds to a Wine
29 * entry within the native "start" menu and replicates the whole tree
30 * structure of the Windows Start Menu. Currently it does not differentiate
31 * between the user's desktop/start menu and the "All Users" copies.
33 * This program will read a Windows shortcut file using the IShellLink
34 * interface, then create a KDE/Gnome menu entry for the shortcut.
36 * winemenubuilder [ -w ] <shortcut.lnk>
38 * If the -w parameter is passed, and the shortcut cannot be created,
39 * this program will wait for the parent process to finish and then try
40 * again. This covers the case when a ShortCut is created before the
41 * executable containing its icon.
43 * TODO
44 * Handle data lnk files. There is no icon in the file; the icon is in
45 * the handler for the file type (or pointed to by the lnk file). Also it
46 * might be better to use a native handler (e.g. a native acroread for pdf
47 * files).
48 * Differentiate between the user's entries and the "All Users" entries.
49 * If it is possible to add the desktop files to the native system's
50 * shared location for an "All Users" entry then do so. As a suggestion the
51 * shared menu Wine base could be writable to the wine group, or a wineadm
52 * group.
53 * Clean up fd.o menu icons and .directory files when the menu is deleted
54 * in Windows.
55 * Generate icons for file open handlers to go into the "Open with..."
56 * list. What does Windows use, the default icon for the .EXE file? It's
57 * not in the registry.
58 * Associate applications under HKCR\Applications to open any MIME type
59 * (by associating with application/octet-stream, or how?).
60 * Clean up fd.o MIME types when they are deleted in Windows, their icons
61 * too. Very hard - once we associate them with fd.o, we can't tell whether
62 * they are ours or not, and the extension <-> MIME type mapping isn't
63 * one-to-one either.
64 * Wine's HKCR is broken - it doesn't merge HKCU\Software\Classes, so apps
65 * that write associations there won't associate (#17019).
68 #include "config.h"
69 #include "wine/port.h"
71 #include <ctype.h>
72 #include <stdio.h>
73 #include <string.h>
74 #ifdef HAVE_UNISTD_H
75 #include <unistd.h>
76 #endif
77 #include <errno.h>
78 #include <stdarg.h>
79 #ifdef HAVE_FNMATCH_H
80 #include <fnmatch.h>
81 #endif
83 #define COBJMACROS
85 #include <windows.h>
86 #include <shlobj.h>
87 #include <objidl.h>
88 #include <shlguid.h>
89 #include <appmgmt.h>
90 #include <tlhelp32.h>
91 #include <intshcut.h>
92 #include <shlwapi.h>
93 #include <initguid.h>
94 #include <wincodec.h>
96 #include "wine/unicode.h"
97 #include "wine/debug.h"
98 #include "wine/library.h"
99 #include "wine/list.h"
101 WINE_DEFAULT_DEBUG_CHANNEL(menubuilder);
103 #define in_desktop_dir(csidl) ((csidl)==CSIDL_DESKTOPDIRECTORY || \
104 (csidl)==CSIDL_COMMON_DESKTOPDIRECTORY)
105 #define in_startmenu(csidl) ((csidl)==CSIDL_STARTMENU || \
106 (csidl)==CSIDL_COMMON_STARTMENU)
108 /* link file formats */
110 #include "pshpack1.h"
112 typedef struct
114 BYTE bWidth;
115 BYTE bHeight;
116 BYTE bColorCount;
117 BYTE bReserved;
118 WORD wPlanes;
119 WORD wBitCount;
120 DWORD dwBytesInRes;
121 WORD nID;
122 } GRPICONDIRENTRY;
124 typedef struct
126 WORD idReserved;
127 WORD idType;
128 WORD idCount;
129 GRPICONDIRENTRY idEntries[1];
130 } GRPICONDIR;
132 typedef struct
134 BYTE bWidth;
135 BYTE bHeight;
136 BYTE bColorCount;
137 BYTE bReserved;
138 WORD wPlanes;
139 WORD wBitCount;
140 DWORD dwBytesInRes;
141 DWORD dwImageOffset;
142 } ICONDIRENTRY;
144 typedef struct
146 WORD idReserved;
147 WORD idType;
148 WORD idCount;
149 } ICONDIR;
152 #include "poppack.h"
154 typedef struct
156 HRSRC *pResInfo;
157 int nIndex;
158 } ENUMRESSTRUCT;
160 struct xdg_mime_type
162 char *mimeType;
163 char *glob;
164 struct list entry;
167 static char *xdg_config_dir;
168 static char *xdg_data_dir;
169 static char *xdg_desktop_dir;
171 static WCHAR* assoc_query(ASSOCSTR assocStr, LPCWSTR name, LPCWSTR extra);
172 static char *extract_icon(LPCWSTR path, int index, const char *destFilename, BOOL bWait);
174 /* Icon extraction routines
176 * FIXME: should use PrivateExtractIcons and friends
177 * FIXME: should not use stdio
180 static BOOL SaveIconStreamAsPNG(IStream *icoFile, int index, const char *pngFileName, LPCWSTR commentW)
182 WCHAR *dosPNGFileName = NULL;
183 IWICImagingFactory *factory = NULL;
184 IWICBitmapDecoder *decoder = NULL;
185 IWICBitmapFrameDecode *sourceFrame = NULL;
186 IWICBitmapSource *sourceBitmap = NULL;
187 IWICBitmapEncoder *encoder = NULL;
188 IStream *outputFile = NULL;
189 IWICBitmapFrameEncode *dstFrame = NULL;
190 IPropertyBag2 *options = NULL;
191 UINT width, height;
192 HRESULT hr = E_FAIL;
194 dosPNGFileName = wine_get_dos_file_name(pngFileName);
195 if (dosPNGFileName == NULL)
197 WINE_ERR("error converting %s to DOS file name\n", pngFileName);
198 goto end;
200 hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
201 &IID_IWICImagingFactory, (void**)&factory);
202 if (FAILED(hr))
204 WINE_ERR("error 0x%08X creating IWICImagingFactory\n", hr);
205 goto end;
207 hr = IWICImagingFactory_CreateDecoderFromStream(factory, icoFile, NULL,
208 WICDecodeMetadataCacheOnDemand, &decoder);
209 if (FAILED(hr))
211 WINE_ERR("error 0x%08X creating IWICBitmapDecoder\n", hr);
212 goto end;
214 hr = IWICBitmapDecoder_GetFrame(decoder, index, &sourceFrame);
215 if (FAILED(hr))
217 WINE_ERR("error 0x%08X getting frame %d\n", hr, index);
218 goto end;
220 hr = WICConvertBitmapSource(&GUID_WICPixelFormat32bppBGRA, (IWICBitmapSource*)sourceFrame, &sourceBitmap);
221 if (FAILED(hr))
223 WINE_ERR("error 0x%08X converting bitmap to 32bppBGRA\n", hr);
224 goto end;
226 hr = CoCreateInstance(&CLSID_WICPngEncoder, NULL, CLSCTX_INPROC_SERVER,
227 &IID_IWICBitmapEncoder, (void**)&encoder);
228 if (FAILED(hr))
230 WINE_ERR("error 0x%08X creating bitmap encoder\n", hr);
231 goto end;
233 hr = SHCreateStreamOnFileW(dosPNGFileName, STGM_CREATE | STGM_WRITE, &outputFile);
234 if (FAILED(hr))
236 WINE_ERR("error 0x%08X creating output file\n", hr);
237 goto end;
239 hr = IWICBitmapEncoder_Initialize(encoder, outputFile, GENERIC_WRITE);
240 if (FAILED(hr))
242 WINE_ERR("error 0x%08X initializing encoder\n", hr);
243 goto end;
245 hr = IWICBitmapEncoder_CreateNewFrame(encoder, &dstFrame, &options);
246 if (FAILED(hr))
248 WINE_ERR("error 0x%08X creating encoder frame\n", hr);
249 goto end;
251 hr = IWICBitmapFrameEncode_Initialize(dstFrame, options);
252 if (FAILED(hr))
254 WINE_ERR("error 0x%08X initializing encoder frame\n", hr);
255 goto end;
257 hr = IWICBitmapSource_GetSize(sourceBitmap, &width, &height);
258 if (FAILED(hr))
260 WINE_ERR("error 0x%08X getting source bitmap size\n", hr);
261 goto end;
263 hr = IWICBitmapFrameEncode_SetSize(dstFrame, width, height);
264 if (FAILED(hr))
266 WINE_ERR("error 0x%08X setting destination bitmap size\n", hr);
267 goto end;
269 hr = IWICBitmapFrameEncode_SetResolution(dstFrame, 96, 96);
270 if (FAILED(hr))
272 WINE_ERR("error 0x%08X setting destination bitmap resolution\n", hr);
273 goto end;
275 hr = IWICBitmapFrameEncode_WriteSource(dstFrame, sourceBitmap, NULL);
276 if (FAILED(hr))
278 WINE_ERR("error 0x%08X copying bitmaps\n", hr);
279 goto end;
281 IWICBitmapFrameEncode_Commit(dstFrame);
282 IWICBitmapEncoder_Commit(encoder);
283 hr = S_OK;
285 end:
286 HeapFree(GetProcessHeap(), 0, dosPNGFileName);
287 if (factory)
288 IWICImagingFactory_Release(factory);
289 if (decoder)
290 IWICBitmapDecoder_Release(decoder);
291 if (sourceFrame)
292 IWICBitmapFrameDecode_Release(sourceFrame);
293 if (sourceBitmap)
294 IWICBitmapSource_Release(sourceBitmap);
295 if (encoder)
296 IWICBitmapEncoder_Release(encoder);
297 if (outputFile)
298 IStream_Release(outputFile);
299 if (dstFrame)
300 IWICBitmapFrameEncode_Release(dstFrame);
301 return SUCCEEDED(hr);
304 static BOOL reassemble_and_save_to_png(GRPICONDIRENTRY *grpIconDirEntry, BITMAPINFO *pIcon,
305 const char *pngFileName, LPCWSTR commentW)
307 SIZE_T size;
308 HGLOBAL hGlobal = NULL;
309 IStream *stream = NULL;
310 char *p;
311 ICONDIR *pIconDir;
312 ICONDIRENTRY *pIconDirEntry;
313 HRESULT hr = E_FAIL;
315 size = sizeof(ICONDIR) + sizeof(ICONDIRENTRY) + grpIconDirEntry->dwBytesInRes;
316 hGlobal = GlobalAlloc(GMEM_MOVEABLE, size);
317 if (hGlobal == NULL)
319 WINE_ERR("out of memory allocating icon\n");
320 goto end;
323 p = GlobalLock(hGlobal);
324 pIconDir = (ICONDIR*)p;
325 pIconDir->idReserved = 0;
326 pIconDir->idType = 1;
327 pIconDir->idCount = 1;
328 p += sizeof(ICONDIR);
329 pIconDirEntry = (ICONDIRENTRY*)p;
330 pIconDirEntry->bWidth = grpIconDirEntry->bWidth;
331 pIconDirEntry->bHeight = grpIconDirEntry->bHeight;
332 pIconDirEntry->bColorCount = grpIconDirEntry->bColorCount;
333 pIconDirEntry->bReserved = grpIconDirEntry->bReserved;
334 pIconDirEntry->wPlanes = grpIconDirEntry->wPlanes;
335 pIconDirEntry->wBitCount = grpIconDirEntry->wBitCount;
336 pIconDirEntry->dwBytesInRes = grpIconDirEntry->dwBytesInRes;
337 pIconDirEntry->dwImageOffset = sizeof(ICONDIR) + sizeof(ICONDIRENTRY);
338 p += sizeof(ICONDIRENTRY);
339 memcpy(p, pIcon, grpIconDirEntry->dwBytesInRes);
340 GlobalUnlock(hGlobal);
342 hr = CreateStreamOnHGlobal(hGlobal, FALSE, &stream);
343 if (FAILED(hr))
345 WINE_ERR("could not create stream on icon hglobal, error 0x%08X\n", hr);
346 goto end;
349 if (SaveIconStreamAsPNG(stream, 0, pngFileName, commentW))
350 hr = S_OK;
351 else
352 hr = E_FAIL;
354 end:
355 if (hGlobal)
356 GlobalFree(hGlobal);
357 if (stream)
358 IStream_Release(stream);
359 return SUCCEEDED(hr);
362 static BOOL CALLBACK EnumResNameProc(HMODULE hModule, LPCWSTR lpszType, LPWSTR lpszName, LONG_PTR lParam)
364 ENUMRESSTRUCT *sEnumRes = (ENUMRESSTRUCT *) lParam;
366 if (!sEnumRes->nIndex--)
368 *sEnumRes->pResInfo = FindResourceW(hModule, lpszName, (LPCWSTR)RT_GROUP_ICON);
369 return FALSE;
371 else
372 return TRUE;
375 static BOOL extract_icon32(LPCWSTR szFileName, int nIndex, char *szXPMFileName)
377 HMODULE hModule;
378 HRSRC hResInfo;
379 LPCWSTR lpName = NULL;
380 HGLOBAL hResData;
381 GRPICONDIR *pIconDir;
382 BITMAPINFO *pIcon;
383 ENUMRESSTRUCT sEnumRes;
384 int nMax = 0;
385 int nMaxBits = 0;
386 GRPICONDIRENTRY iconDirEntry = {0};
387 int i;
388 BOOL ret = FALSE;
390 hModule = LoadLibraryExW(szFileName, 0, LOAD_LIBRARY_AS_DATAFILE);
391 if (!hModule)
393 WINE_WARN("LoadLibraryExW (%s) failed, error %d\n",
394 wine_dbgstr_w(szFileName), GetLastError());
395 return FALSE;
398 if (nIndex < 0)
400 hResInfo = FindResourceW(hModule, MAKEINTRESOURCEW(-nIndex), (LPCWSTR)RT_GROUP_ICON);
401 WINE_TRACE("FindResourceW (%s) called, return %p, error %d\n",
402 wine_dbgstr_w(szFileName), hResInfo, GetLastError());
404 else
406 hResInfo=NULL;
407 sEnumRes.pResInfo = &hResInfo;
408 sEnumRes.nIndex = nIndex;
409 if (!EnumResourceNamesW(hModule, (LPCWSTR)RT_GROUP_ICON,
410 EnumResNameProc, (LONG_PTR)&sEnumRes) &&
411 sEnumRes.nIndex != -1)
413 WINE_TRACE("EnumResourceNamesW failed, error %d\n", GetLastError());
417 if (hResInfo)
419 if ((hResData = LoadResource(hModule, hResInfo)))
421 if ((pIconDir = LockResource(hResData)))
423 lpName = MAKEINTRESOURCEW(pIconDir->idEntries[0].nID); /* default to first entry */
424 for (i = 0; i < pIconDir->idCount; i++)
426 if (pIconDir->idEntries[i].wBitCount >= nMaxBits)
428 if ((pIconDir->idEntries[i].bHeight * pIconDir->idEntries[i].bWidth) >= nMax)
430 lpName = MAKEINTRESOURCEW(pIconDir->idEntries[i].nID);
431 nMax = pIconDir->idEntries[i].bHeight * pIconDir->idEntries[i].bWidth;
432 nMaxBits = pIconDir->idEntries[i].wBitCount;
433 iconDirEntry = pIconDir->idEntries[i];
439 FreeResource(hResData);
442 else
444 WINE_WARN("found no icon\n");
445 FreeLibrary(hModule);
446 return FALSE;
449 if ((hResInfo = FindResourceW(hModule, lpName, (LPCWSTR)RT_ICON)))
451 if ((hResData = LoadResource(hModule, hResInfo)))
453 if ((pIcon = LockResource(hResData)))
455 if (reassemble_and_save_to_png(&iconDirEntry, pIcon, szXPMFileName, szFileName))
456 ret = TRUE;
457 else
458 ret = FALSE;
461 FreeResource(hResData);
465 FreeLibrary(hModule);
466 return ret;
469 static BOOL ExtractFromEXEDLL(LPCWSTR szFileName, int nIndex, char *szXPMFileName)
471 if (!extract_icon32(szFileName, nIndex, szXPMFileName) /*&&
472 !extract_icon16(szFileName, szXPMFileName)*/)
473 return FALSE;
474 return TRUE;
477 static int ExtractFromICO(LPCWSTR szFileName, char *szXPMFileName)
479 FILE *fICOFile = NULL;
480 ICONDIR iconDir;
481 ICONDIRENTRY *pIconDirEntry = NULL;
482 int nMax = 0, nMaxBits = 0;
483 int nIndex = 0;
484 void *pIcon = NULL;
485 int i;
486 char *filename = NULL;
487 IStream *icoStream = NULL;
488 HRESULT hr;
490 filename = wine_get_unix_file_name(szFileName);
491 if (!(fICOFile = fopen(filename, "r")))
493 WINE_TRACE("unable to open '%s' for reading: %s\n", filename, strerror(errno));
494 goto error;
497 if (fread(&iconDir, sizeof (ICONDIR), 1, fICOFile) != 1 ||
498 (iconDir.idReserved != 0) || (iconDir.idType != 1))
500 WINE_WARN("Invalid ico file format\n");
501 goto error;
504 if ((pIconDirEntry = HeapAlloc(GetProcessHeap(), 0, iconDir.idCount * sizeof (ICONDIRENTRY))) == NULL)
505 goto error;
506 if (fread(pIconDirEntry, sizeof (ICONDIRENTRY), iconDir.idCount, fICOFile) != iconDir.idCount)
507 goto error;
509 for (i = 0; i < iconDir.idCount; i++)
511 WINE_TRACE("[%d]: %d x %d @ %d\n", i, pIconDirEntry[i].bWidth, pIconDirEntry[i].bHeight, pIconDirEntry[i].wBitCount);
512 if (pIconDirEntry[i].wBitCount >= nMaxBits &&
513 (pIconDirEntry[i].bHeight * pIconDirEntry[i].bWidth) >= nMax)
515 nIndex = i;
516 nMax = pIconDirEntry[i].bHeight * pIconDirEntry[i].bWidth;
517 nMaxBits = pIconDirEntry[i].wBitCount;
520 WINE_TRACE("Selected: %d\n", nIndex);
522 if ((pIcon = HeapAlloc(GetProcessHeap(), 0, pIconDirEntry[nIndex].dwBytesInRes)) == NULL)
523 goto error;
524 if (fseek(fICOFile, pIconDirEntry[nIndex].dwImageOffset, SEEK_SET))
525 goto error;
526 if (fread(pIcon, pIconDirEntry[nIndex].dwBytesInRes, 1, fICOFile) != 1)
527 goto error;
528 fclose(fICOFile);
529 fICOFile = NULL;
531 hr = SHCreateStreamOnFileW(szFileName, STGM_READ, &icoStream);
533 if (FAILED(hr))
535 WINE_ERR("error 0x%08X creating icon stream\n", hr);
536 goto error;
539 if (!SaveIconStreamAsPNG(icoStream, nIndex, szXPMFileName, szFileName))
540 goto error;
542 HeapFree(GetProcessHeap(), 0, pIcon);
543 HeapFree(GetProcessHeap(), 0, pIconDirEntry);
544 HeapFree(GetProcessHeap(), 0, filename);
545 if (icoStream)
546 IStream_Release(icoStream);
547 return 1;
549 error:
550 HeapFree(GetProcessHeap(), 0, pIcon);
551 HeapFree(GetProcessHeap(), 0, pIconDirEntry);
552 if (fICOFile) fclose(fICOFile);
553 HeapFree(GetProcessHeap(), 0, filename);
554 if (icoStream)
555 IStream_Release(icoStream);
556 return 0;
559 static int ExtractFromFileType(LPCWSTR szFileName, char *szXPMFileName)
561 int ret = 0;
562 WCHAR *extension;
563 WCHAR *icon = NULL;
564 WCHAR *comma;
565 WCHAR *executable = NULL;
566 int index = 0;
567 char *output_path = NULL;
569 extension = strrchrW(szFileName, '.');
570 if (extension == NULL)
571 goto end;
573 icon = assoc_query(ASSOCSTR_DEFAULTICON, extension, NULL);
574 if (icon)
576 comma = strrchrW(icon, ',');
577 if (comma)
579 *comma = 0;
580 index = atoiW(comma + 1);
582 output_path = extract_icon(icon, index, NULL, FALSE);
583 WINE_TRACE("defaulticon %s -> icon %s\n", wine_dbgstr_w(icon), wine_dbgstr_a(output_path));
585 else
587 executable = assoc_query(ASSOCSTR_EXECUTABLE, extension, NULL);
588 if (executable)
589 output_path = extract_icon(executable, 0, NULL, FALSE);
590 WINE_TRACE("executable %s -> icon %s\n", wine_dbgstr_w(executable), wine_dbgstr_a(output_path));
592 if (output_path)
593 ret = (rename(output_path, szXPMFileName) == 0);
595 end:
596 HeapFree(GetProcessHeap(), 0, icon);
597 HeapFree(GetProcessHeap(), 0, executable);
598 HeapFree(GetProcessHeap(), 0, output_path);
599 return ret;
602 static BOOL create_default_icon( char *filename )
604 static const WCHAR user32W[] = {'u','s','e','r','3','2',0};
606 return extract_icon32( user32W, -(INT_PTR)IDI_WINLOGO, filename );
609 static unsigned short crc16(const char* string)
611 unsigned short crc = 0;
612 int i, j, xor_poly;
614 for (i = 0; string[i] != 0; i++)
616 char c = string[i];
617 for (j = 0; j < 8; c >>= 1, j++)
619 xor_poly = (c ^ crc) & 1;
620 crc >>= 1;
621 if (xor_poly)
622 crc ^= 0xa001;
625 return crc;
628 static char *strdupA( const char *str )
630 char *ret;
632 if (!str) return NULL;
633 if ((ret = HeapAlloc( GetProcessHeap(), 0, strlen(str) + 1 ))) strcpy( ret, str );
634 return ret;
637 static char* heap_printf(const char *format, ...)
639 va_list args;
640 int size = 4096;
641 char *buffer, *ret;
642 int n;
644 va_start(args, format);
645 while (1)
647 buffer = HeapAlloc(GetProcessHeap(), 0, size);
648 if (buffer == NULL)
649 break;
650 n = vsnprintf(buffer, size, format, args);
651 if (n == -1)
652 size *= 2;
653 else if (n >= size)
654 size = n + 1;
655 else
656 break;
657 HeapFree(GetProcessHeap(), 0, buffer);
659 va_end(args);
660 if (!buffer) return NULL;
661 ret = HeapReAlloc(GetProcessHeap(), 0, buffer, strlen(buffer) + 1 );
662 if (!ret) ret = buffer;
663 return ret;
666 static void write_xml_text(FILE *file, const char *text)
668 int i;
669 for (i = 0; text[i]; i++)
671 if (text[i] == '&')
672 fputs("&amp;", file);
673 else if (text[i] == '<')
674 fputs("&lt;", file);
675 else if (text[i] == '>')
676 fputs("&gt;", file);
677 else if (text[i] == '\'')
678 fputs("&apos;", file);
679 else if (text[i] == '"')
680 fputs("&quot;", file);
681 else
682 fputc(text[i], file);
686 static BOOL create_directories(char *directory)
688 BOOL ret = TRUE;
689 int i;
691 for (i = 0; directory[i]; i++)
693 if (i > 0 && directory[i] == '/')
695 directory[i] = 0;
696 mkdir(directory, 0777);
697 directory[i] = '/';
700 if (mkdir(directory, 0777) && errno != EEXIST)
701 ret = FALSE;
703 return ret;
706 static char* wchars_to_utf8_chars(LPCWSTR string)
708 char *ret;
709 INT size = WideCharToMultiByte(CP_UTF8, 0, string, -1, NULL, 0, NULL, NULL);
710 ret = HeapAlloc(GetProcessHeap(), 0, size);
711 if (ret)
712 WideCharToMultiByte(CP_UTF8, 0, string, -1, ret, size, NULL, NULL);
713 return ret;
716 static char* wchars_to_unix_chars(LPCWSTR string)
718 char *ret;
719 INT size = WideCharToMultiByte(CP_UNIXCP, 0, string, -1, NULL, 0, NULL, NULL);
720 ret = HeapAlloc(GetProcessHeap(), 0, size);
721 if (ret)
722 WideCharToMultiByte(CP_UNIXCP, 0, string, -1, ret, size, NULL, NULL);
723 return ret;
726 static WCHAR* utf8_chars_to_wchars(LPCSTR string)
728 WCHAR *ret;
729 INT size = MultiByteToWideChar(CP_UTF8, 0, string, -1, NULL, 0);
730 ret = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
731 if (ret)
732 MultiByteToWideChar(CP_UTF8, 0, string, -1, ret, size);
733 return ret;
736 /* extract an icon from an exe or icon file; helper for IPersistFile_fnSave */
737 static char *extract_icon( LPCWSTR path, int index, const char *destFilename, BOOL bWait )
739 unsigned short crc;
740 char *iconsdir = NULL, *ico_path = NULL, *ico_name, *xpm_path = NULL;
741 char* s;
742 int n;
744 /* Where should we save the icon? */
745 WINE_TRACE("path=[%s] index=%d\n", wine_dbgstr_w(path), index);
746 iconsdir = heap_printf("%s/icons", xdg_data_dir);
747 if (iconsdir)
749 if (mkdir(iconsdir, 0777) && errno != EEXIST)
751 WINE_WARN("couldn't make icons directory %s\n", wine_dbgstr_a(iconsdir));
752 goto end;
755 else
757 WINE_TRACE("no icon created\n");
758 return NULL;
761 /* Determine the icon base name */
762 n = WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL);
763 ico_path = HeapAlloc(GetProcessHeap(), 0, n);
764 WideCharToMultiByte(CP_UNIXCP, 0, path, -1, ico_path, n, NULL, NULL);
765 s=ico_name=ico_path;
766 while (*s!='\0') {
767 if (*s=='/' || *s=='\\') {
768 *s='\\';
769 ico_name=s;
770 } else {
771 *s=tolower(*s);
773 s++;
775 if (*ico_name=='\\') *ico_name++='\0';
776 s=strrchr(ico_name,'.');
777 if (s) *s='\0';
779 /* Compute the source-path hash */
780 crc=crc16(ico_path);
782 /* Try to treat the source file as an exe */
783 if (destFilename)
784 xpm_path=heap_printf("%s/%s.png",iconsdir,destFilename);
785 else
786 xpm_path=heap_printf("%s/%04x_%s.%d.png",iconsdir,crc,ico_name,index);
787 if (xpm_path == NULL)
789 WINE_ERR("could not extract icon %s, out of memory\n", wine_dbgstr_a(ico_name));
790 return NULL;
793 if (ExtractFromEXEDLL( path, index, xpm_path ))
794 goto end;
796 /* Must be something else, ignore the index in that case */
797 if (destFilename)
798 sprintf(xpm_path,"%s/%s.png",iconsdir,destFilename);
799 else
800 sprintf(xpm_path,"%s/%04x_%s.png",iconsdir,crc,ico_name);
801 if (ExtractFromICO( path, xpm_path))
802 goto end;
803 if (ExtractFromFileType( path, xpm_path ))
804 goto end;
805 if (!bWait && create_default_icon( xpm_path ))
806 goto end;
808 HeapFree( GetProcessHeap(), 0, xpm_path );
809 xpm_path=NULL;
811 end:
812 HeapFree(GetProcessHeap(), 0, iconsdir);
813 HeapFree(GetProcessHeap(), 0, ico_path);
814 return xpm_path;
817 static HKEY open_menus_reg_key(void)
819 static const WCHAR Software_Wine_FileOpenAssociationsW[] = {
820 'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\','M','e','n','u','F','i','l','e','s',0};
821 HKEY assocKey;
822 DWORD ret;
823 ret = RegCreateKeyW(HKEY_CURRENT_USER, Software_Wine_FileOpenAssociationsW, &assocKey);
824 if (ret == ERROR_SUCCESS)
825 return assocKey;
826 SetLastError(ret);
827 return NULL;
830 static DWORD register_menus_entry(const char *unix_file, const char *windows_file)
832 WCHAR *unix_fileW;
833 WCHAR *windows_fileW;
834 INT size;
835 DWORD ret;
837 size = MultiByteToWideChar(CP_UNIXCP, 0, unix_file, -1, NULL, 0);
838 unix_fileW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
839 if (unix_fileW)
841 MultiByteToWideChar(CP_UNIXCP, 0, unix_file, -1, unix_fileW, size);
842 size = MultiByteToWideChar(CP_UNIXCP, 0, windows_file, -1, NULL, 0);
843 windows_fileW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
844 if (windows_fileW)
846 HKEY hkey;
847 MultiByteToWideChar(CP_UNIXCP, 0, windows_file, -1, windows_fileW, size);
848 hkey = open_menus_reg_key();
849 if (hkey)
851 ret = RegSetValueExW(hkey, unix_fileW, 0, REG_SZ, (const BYTE*)windows_fileW,
852 (strlenW(windows_fileW) + 1) * sizeof(WCHAR));
853 RegCloseKey(hkey);
855 else
856 ret = GetLastError();
857 HeapFree(GetProcessHeap(), 0, windows_fileW);
859 else
860 ret = ERROR_NOT_ENOUGH_MEMORY;
861 HeapFree(GetProcessHeap(), 0, unix_fileW);
863 else
864 ret = ERROR_NOT_ENOUGH_MEMORY;
865 return ret;
868 static BOOL write_desktop_entry(const char *unix_link, const char *location, const char *linkname,
869 const char *path, const char *args, const char *descr,
870 const char *workdir, const char *icon)
872 FILE *file;
874 WINE_TRACE("(%s,%s,%s,%s,%s,%s,%s,%s)\n", wine_dbgstr_a(unix_link), wine_dbgstr_a(location),
875 wine_dbgstr_a(linkname), wine_dbgstr_a(path), wine_dbgstr_a(args),
876 wine_dbgstr_a(descr), wine_dbgstr_a(workdir), wine_dbgstr_a(icon));
878 file = fopen(location, "w");
879 if (file == NULL)
880 return FALSE;
882 fprintf(file, "[Desktop Entry]\n");
883 fprintf(file, "Name=%s\n", linkname);
884 fprintf(file, "Exec=env WINEPREFIX=\"%s\" wine %s %s\n",
885 wine_get_config_dir(), path, args);
886 fprintf(file, "Type=Application\n");
887 fprintf(file, "StartupNotify=true\n");
888 if (descr && lstrlenA(descr))
889 fprintf(file, "Comment=%s\n", descr);
890 if (workdir && lstrlenA(workdir))
891 fprintf(file, "Path=%s\n", workdir);
892 if (icon && lstrlenA(icon))
893 fprintf(file, "Icon=%s\n", icon);
895 fclose(file);
897 if (unix_link)
899 DWORD ret = register_menus_entry(location, unix_link);
900 if (ret != ERROR_SUCCESS)
901 return FALSE;
904 return TRUE;
907 static BOOL write_directory_entry(const char *directory, const char *location)
909 FILE *file;
911 WINE_TRACE("(%s,%s)\n", wine_dbgstr_a(directory), wine_dbgstr_a(location));
913 file = fopen(location, "w");
914 if (file == NULL)
915 return FALSE;
917 fprintf(file, "[Desktop Entry]\n");
918 fprintf(file, "Type=Directory\n");
919 if (strcmp(directory, "wine") == 0)
921 fprintf(file, "Name=Wine\n");
922 fprintf(file, "Icon=wine\n");
924 else
926 fprintf(file, "Name=%s\n", directory);
927 fprintf(file, "Icon=folder\n");
930 fclose(file);
931 return TRUE;
934 static BOOL write_menu_file(const char *unix_link, const char *filename)
936 char *tempfilename;
937 FILE *tempfile = NULL;
938 char *lastEntry;
939 char *name = NULL;
940 char *menuPath = NULL;
941 int i;
942 int count = 0;
943 BOOL ret = FALSE;
945 WINE_TRACE("(%s)\n", wine_dbgstr_a(filename));
947 while (1)
949 tempfilename = heap_printf("%s/wine-menu-XXXXXX", xdg_config_dir);
950 if (tempfilename)
952 int tempfd = mkstemps(tempfilename, 0);
953 if (tempfd >= 0)
955 tempfile = fdopen(tempfd, "w");
956 if (tempfile)
957 break;
958 close(tempfd);
959 goto end;
961 else if (errno == EEXIST)
963 HeapFree(GetProcessHeap(), 0, tempfilename);
964 continue;
966 HeapFree(GetProcessHeap(), 0, tempfilename);
968 return FALSE;
971 fprintf(tempfile, "<!DOCTYPE Menu PUBLIC \"-//freedesktop//DTD Menu 1.0//EN\"\n");
972 fprintf(tempfile, "\"http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd\">\n");
973 fprintf(tempfile, "<Menu>\n");
974 fprintf(tempfile, " <Name>Applications</Name>\n");
976 name = HeapAlloc(GetProcessHeap(), 0, lstrlenA(filename) + 1);
977 if (name == NULL) goto end;
978 lastEntry = name;
979 for (i = 0; filename[i]; i++)
981 name[i] = filename[i];
982 if (filename[i] == '/')
984 char *dir_file_name;
985 struct stat st;
986 name[i] = 0;
987 fprintf(tempfile, " <Menu>\n");
988 fprintf(tempfile, " <Name>%s", count ? "" : "wine-");
989 write_xml_text(tempfile, name);
990 fprintf(tempfile, "</Name>\n");
991 fprintf(tempfile, " <Directory>%s", count ? "" : "wine-");
992 write_xml_text(tempfile, name);
993 fprintf(tempfile, ".directory</Directory>\n");
994 dir_file_name = heap_printf("%s/desktop-directories/%s%s.directory",
995 xdg_data_dir, count ? "" : "wine-", name);
996 if (dir_file_name)
998 if (stat(dir_file_name, &st) != 0 && errno == ENOENT)
999 write_directory_entry(lastEntry, dir_file_name);
1000 HeapFree(GetProcessHeap(), 0, dir_file_name);
1002 name[i] = '-';
1003 lastEntry = &name[i+1];
1004 ++count;
1007 name[i] = 0;
1009 fprintf(tempfile, " <Include>\n");
1010 fprintf(tempfile, " <Filename>");
1011 write_xml_text(tempfile, name);
1012 fprintf(tempfile, "</Filename>\n");
1013 fprintf(tempfile, " </Include>\n");
1014 for (i = 0; i < count; i++)
1015 fprintf(tempfile, " </Menu>\n");
1016 fprintf(tempfile, "</Menu>\n");
1018 menuPath = heap_printf("%s/%s", xdg_config_dir, name);
1019 if (menuPath == NULL) goto end;
1020 strcpy(menuPath + strlen(menuPath) - strlen(".desktop"), ".menu");
1021 ret = TRUE;
1023 end:
1024 if (tempfile)
1025 fclose(tempfile);
1026 if (ret)
1027 ret = (rename(tempfilename, menuPath) == 0);
1028 if (!ret && tempfilename)
1029 remove(tempfilename);
1030 HeapFree(GetProcessHeap(), 0, tempfilename);
1031 if (ret)
1032 register_menus_entry(menuPath, unix_link);
1033 HeapFree(GetProcessHeap(), 0, name);
1034 HeapFree(GetProcessHeap(), 0, menuPath);
1035 return ret;
1038 static BOOL write_menu_entry(const char *unix_link, const char *link, const char *path, const char *args,
1039 const char *descr, const char *workdir, const char *icon)
1041 const char *linkname;
1042 char *desktopPath = NULL;
1043 char *desktopDir;
1044 char *filename = NULL;
1045 BOOL ret = TRUE;
1047 WINE_TRACE("(%s, %s, %s, %s, %s, %s, %s)\n", wine_dbgstr_a(unix_link), wine_dbgstr_a(link),
1048 wine_dbgstr_a(path), wine_dbgstr_a(args), wine_dbgstr_a(descr),
1049 wine_dbgstr_a(workdir), wine_dbgstr_a(icon));
1051 linkname = strrchr(link, '/');
1052 if (linkname == NULL)
1053 linkname = link;
1054 else
1055 ++linkname;
1057 desktopPath = heap_printf("%s/applications/wine/%s.desktop", xdg_data_dir, link);
1058 if (!desktopPath)
1060 WINE_WARN("out of memory creating menu entry\n");
1061 ret = FALSE;
1062 goto end;
1064 desktopDir = strrchr(desktopPath, '/');
1065 *desktopDir = 0;
1066 if (!create_directories(desktopPath))
1068 WINE_WARN("couldn't make parent directories for %s\n", wine_dbgstr_a(desktopPath));
1069 ret = FALSE;
1070 goto end;
1072 *desktopDir = '/';
1073 if (!write_desktop_entry(unix_link, desktopPath, linkname, path, args, descr, workdir, icon))
1075 WINE_WARN("couldn't make desktop entry %s\n", wine_dbgstr_a(desktopPath));
1076 ret = FALSE;
1077 goto end;
1080 filename = heap_printf("wine/%s.desktop", link);
1081 if (!filename || !write_menu_file(unix_link, filename))
1083 WINE_WARN("couldn't make menu file %s\n", wine_dbgstr_a(filename));
1084 ret = FALSE;
1087 end:
1088 HeapFree(GetProcessHeap(), 0, desktopPath);
1089 HeapFree(GetProcessHeap(), 0, filename);
1090 return ret;
1093 /* This escapes reserved characters in .desktop files' Exec keys. */
1094 static LPSTR escape(LPCWSTR arg)
1096 int i, j;
1097 WCHAR *escaped_string;
1098 char *utf8_string;
1100 escaped_string = HeapAlloc(GetProcessHeap(), 0, (4 * strlenW(arg) + 1) * sizeof(WCHAR));
1101 if (escaped_string == NULL) return NULL;
1102 for (i = j = 0; arg[i]; i++)
1104 switch (arg[i])
1106 case '\\':
1107 escaped_string[j++] = '\\';
1108 escaped_string[j++] = '\\';
1109 escaped_string[j++] = '\\';
1110 escaped_string[j++] = '\\';
1111 break;
1112 case ' ':
1113 case '\t':
1114 case '\n':
1115 case '"':
1116 case '\'':
1117 case '>':
1118 case '<':
1119 case '~':
1120 case '|':
1121 case '&':
1122 case ';':
1123 case '$':
1124 case '*':
1125 case '?':
1126 case '#':
1127 case '(':
1128 case ')':
1129 case '`':
1130 escaped_string[j++] = '\\';
1131 escaped_string[j++] = '\\';
1132 /* fall through */
1133 default:
1134 escaped_string[j++] = arg[i];
1135 break;
1138 escaped_string[j] = 0;
1140 utf8_string = wchars_to_utf8_chars(escaped_string);
1141 if (utf8_string == NULL)
1143 WINE_ERR("out of memory\n");
1144 goto end;
1147 end:
1148 HeapFree(GetProcessHeap(), 0, escaped_string);
1149 return utf8_string;
1152 /* Return a heap-allocated copy of the unix format difference between the two
1153 * Windows-format paths.
1154 * locn is the owning location
1155 * link is within locn
1157 static char *relative_path( LPCWSTR link, LPCWSTR locn )
1159 char *unix_locn, *unix_link;
1160 char *relative = NULL;
1162 unix_locn = wine_get_unix_file_name(locn);
1163 unix_link = wine_get_unix_file_name(link);
1164 if (unix_locn && unix_link)
1166 size_t len_unix_locn, len_unix_link;
1167 len_unix_locn = strlen (unix_locn);
1168 len_unix_link = strlen (unix_link);
1169 if (len_unix_locn < len_unix_link && memcmp (unix_locn, unix_link, len_unix_locn) == 0 && unix_link[len_unix_locn] == '/')
1171 size_t len_rel;
1172 char *p = strrchr (unix_link + len_unix_locn, '/');
1173 p = strrchr (p, '.');
1174 if (p)
1176 *p = '\0';
1177 len_unix_link = p - unix_link;
1179 len_rel = len_unix_link - len_unix_locn;
1180 relative = HeapAlloc(GetProcessHeap(), 0, len_rel);
1181 if (relative)
1183 memcpy (relative, unix_link + len_unix_locn + 1, len_rel);
1187 if (!relative)
1188 WINE_WARN("Could not separate the relative link path of %s in %s\n", wine_dbgstr_w(link), wine_dbgstr_w(locn));
1189 HeapFree(GetProcessHeap(), 0, unix_locn);
1190 HeapFree(GetProcessHeap(), 0, unix_link);
1191 return relative;
1194 /***********************************************************************
1196 * GetLinkLocation
1198 * returns TRUE if successful
1199 * *loc will contain CS_DESKTOPDIRECTORY, CS_STARTMENU, CS_STARTUP etc.
1200 * *relative will contain the address of a heap-allocated copy of the portion
1201 * of the filename that is within the specified location, in unix form
1203 static BOOL GetLinkLocation( LPCWSTR linkfile, DWORD *loc, char **relative )
1205 WCHAR filename[MAX_PATH], shortfilename[MAX_PATH], buffer[MAX_PATH];
1206 DWORD len, i, r, filelen;
1207 const DWORD locations[] = {
1208 CSIDL_STARTUP, CSIDL_DESKTOPDIRECTORY, CSIDL_STARTMENU,
1209 CSIDL_COMMON_STARTUP, CSIDL_COMMON_DESKTOPDIRECTORY,
1210 CSIDL_COMMON_STARTMENU };
1212 WINE_TRACE("%s\n", wine_dbgstr_w(linkfile));
1213 filelen=GetFullPathNameW( linkfile, MAX_PATH, shortfilename, NULL );
1214 if (filelen==0 || filelen>MAX_PATH)
1215 return FALSE;
1217 WINE_TRACE("%s\n", wine_dbgstr_w(shortfilename));
1219 /* the CSLU Toolkit uses a short path name when creating .lnk files;
1220 * expand or our hardcoded list won't match.
1222 filelen=GetLongPathNameW(shortfilename, filename, MAX_PATH);
1223 if (filelen==0 || filelen>MAX_PATH)
1224 return FALSE;
1226 WINE_TRACE("%s\n", wine_dbgstr_w(filename));
1228 for( i=0; i<sizeof(locations)/sizeof(locations[0]); i++ )
1230 if (!SHGetSpecialFolderPathW( 0, buffer, locations[i], FALSE ))
1231 continue;
1233 len = lstrlenW(buffer);
1234 if (len >= MAX_PATH)
1235 continue; /* We've just trashed memory! Hopefully we are OK */
1237 if (len > filelen || filename[len]!='\\')
1238 continue;
1239 /* do a lstrcmpinW */
1240 filename[len] = 0;
1241 r = lstrcmpiW( filename, buffer );
1242 filename[len] = '\\';
1243 if ( r )
1244 continue;
1246 /* return the remainder of the string and link type */
1247 *loc = locations[i];
1248 *relative = relative_path (filename, buffer);
1249 return (*relative != NULL);
1252 return FALSE;
1255 /* gets the target path directly or through MSI */
1256 static HRESULT get_cmdline( IShellLinkW *sl, LPWSTR szPath, DWORD pathSize,
1257 LPWSTR szArgs, DWORD argsSize)
1259 IShellLinkDataList *dl = NULL;
1260 EXP_DARWIN_LINK *dar = NULL;
1261 HRESULT hr;
1263 szPath[0] = 0;
1264 szArgs[0] = 0;
1266 hr = IShellLinkW_GetPath( sl, szPath, pathSize, NULL, SLGP_RAWPATH );
1267 if (hr == S_OK && szPath[0])
1269 IShellLinkW_GetArguments( sl, szArgs, argsSize );
1270 return hr;
1273 hr = IShellLinkW_QueryInterface( sl, &IID_IShellLinkDataList, (LPVOID*) &dl );
1274 if (FAILED(hr))
1275 return hr;
1277 hr = IShellLinkDataList_CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar );
1278 if (SUCCEEDED(hr))
1280 WCHAR* szCmdline;
1281 DWORD cmdSize;
1283 cmdSize=0;
1284 hr = CommandLineFromMsiDescriptor( dar->szwDarwinID, NULL, &cmdSize );
1285 if (hr == ERROR_SUCCESS)
1287 cmdSize++;
1288 szCmdline = HeapAlloc( GetProcessHeap(), 0, cmdSize*sizeof(WCHAR) );
1289 hr = CommandLineFromMsiDescriptor( dar->szwDarwinID, szCmdline, &cmdSize );
1290 WINE_TRACE(" command : %s\n", wine_dbgstr_w(szCmdline));
1291 if (hr == ERROR_SUCCESS)
1293 WCHAR *s, *d;
1294 int bcount, in_quotes;
1296 /* Extract the application path */
1297 bcount=0;
1298 in_quotes=0;
1299 s=szCmdline;
1300 d=szPath;
1301 while (*s)
1303 if ((*s==0x0009 || *s==0x0020) && !in_quotes)
1305 /* skip the remaining spaces */
1306 do {
1307 s++;
1308 } while (*s==0x0009 || *s==0x0020);
1309 break;
1311 else if (*s==0x005c)
1313 /* '\\' */
1314 *d++=*s++;
1315 bcount++;
1317 else if (*s==0x0022)
1319 /* '"' */
1320 if ((bcount & 1)==0)
1322 /* Preceded by an even number of '\', this is
1323 * half that number of '\', plus a quote which
1324 * we erase.
1326 d-=bcount/2;
1327 in_quotes=!in_quotes;
1328 s++;
1330 else
1332 /* Preceded by an odd number of '\', this is
1333 * half that number of '\' followed by a '"'
1335 d=d-bcount/2-1;
1336 *d++='"';
1337 s++;
1339 bcount=0;
1341 else
1343 /* a regular character */
1344 *d++=*s++;
1345 bcount=0;
1347 if ((d-szPath) == pathSize)
1349 /* Keep processing the path till we get to the
1350 * arguments, but 'stand still'
1352 d--;
1355 /* Close the application path */
1356 *d=0;
1358 lstrcpynW(szArgs, s, argsSize);
1360 HeapFree( GetProcessHeap(), 0, szCmdline );
1362 LocalFree( dar );
1365 IShellLinkDataList_Release( dl );
1366 return hr;
1369 static WCHAR* assoc_query(ASSOCSTR assocStr, LPCWSTR name, LPCWSTR extra)
1371 HRESULT hr;
1372 WCHAR *value = NULL;
1373 DWORD size = 0;
1374 hr = AssocQueryStringW(0, assocStr, name, extra, NULL, &size);
1375 if (SUCCEEDED(hr))
1377 value = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1378 if (value)
1380 hr = AssocQueryStringW(0, assocStr, name, extra, value, &size);
1381 if (FAILED(hr))
1383 HeapFree(GetProcessHeap(), 0, value);
1384 value = NULL;
1388 return value;
1391 static char *slashes_to_minuses(const char *string)
1393 int i;
1394 char *ret = HeapAlloc(GetProcessHeap(), 0, lstrlenA(string) + 1);
1395 if (ret)
1397 for (i = 0; string[i]; i++)
1399 if (string[i] == '/')
1400 ret[i] = '-';
1401 else
1402 ret[i] = string[i];
1404 ret[i] = 0;
1405 return ret;
1407 return NULL;
1410 static BOOL next_line(FILE *file, char **line, int *size)
1412 int pos = 0;
1413 char *cr;
1414 if (*line == NULL)
1416 *size = 4096;
1417 *line = HeapAlloc(GetProcessHeap(), 0, *size);
1419 while (*line != NULL)
1421 if (fgets(&(*line)[pos], *size - pos, file) == NULL)
1423 HeapFree(GetProcessHeap(), 0, *line);
1424 *line = NULL;
1425 if (feof(file))
1426 return TRUE;
1427 return FALSE;
1429 pos = strlen(*line);
1430 cr = strchr(*line, '\n');
1431 if (cr == NULL)
1433 char *line2;
1434 (*size) *= 2;
1435 line2 = HeapReAlloc(GetProcessHeap(), 0, *line, *size);
1436 if (line2)
1437 *line = line2;
1438 else
1440 HeapFree(GetProcessHeap(), 0, *line);
1441 *line = NULL;
1444 else
1446 *cr = 0;
1447 return TRUE;
1450 return FALSE;
1453 static BOOL add_mimes(const char *xdg_data_dir, struct list *mime_types)
1455 char *globs_filename = NULL;
1456 BOOL ret = TRUE;
1457 globs_filename = heap_printf("%s/mime/globs", xdg_data_dir);
1458 if (globs_filename)
1460 FILE *globs_file = fopen(globs_filename, "r");
1461 if (globs_file) /* doesn't have to exist */
1463 char *line = NULL;
1464 int size = 0;
1465 while (ret && (ret = next_line(globs_file, &line, &size)) && line)
1467 char *pos;
1468 struct xdg_mime_type *mime_type_entry = NULL;
1469 if (line[0] != '#' && (pos = strchr(line, ':')))
1471 mime_type_entry = HeapAlloc(GetProcessHeap(), 0, sizeof(struct xdg_mime_type));
1472 if (mime_type_entry)
1474 *pos = 0;
1475 mime_type_entry->mimeType = strdupA(line);
1476 mime_type_entry->glob = strdupA(pos + 1);
1477 if (mime_type_entry->mimeType && mime_type_entry->glob)
1478 list_add_tail(mime_types, &mime_type_entry->entry);
1479 else
1481 HeapFree(GetProcessHeap(), 0, mime_type_entry->mimeType);
1482 HeapFree(GetProcessHeap(), 0, mime_type_entry->glob);
1483 HeapFree(GetProcessHeap(), 0, mime_type_entry);
1484 ret = FALSE;
1487 else
1488 ret = FALSE;
1491 HeapFree(GetProcessHeap(), 0, line);
1492 fclose(globs_file);
1494 HeapFree(GetProcessHeap(), 0, globs_filename);
1496 else
1497 ret = FALSE;
1498 return ret;
1501 static void free_native_mime_types(struct list *native_mime_types)
1503 struct xdg_mime_type *mime_type_entry, *mime_type_entry2;
1505 LIST_FOR_EACH_ENTRY_SAFE(mime_type_entry, mime_type_entry2, native_mime_types, struct xdg_mime_type, entry)
1507 list_remove(&mime_type_entry->entry);
1508 HeapFree(GetProcessHeap(), 0, mime_type_entry->glob);
1509 HeapFree(GetProcessHeap(), 0, mime_type_entry->mimeType);
1510 HeapFree(GetProcessHeap(), 0, mime_type_entry);
1512 HeapFree(GetProcessHeap(), 0, native_mime_types);
1515 static BOOL build_native_mime_types(const char *xdg_data_home, struct list **mime_types)
1517 char *xdg_data_dirs;
1518 BOOL ret;
1520 *mime_types = NULL;
1522 xdg_data_dirs = getenv("XDG_DATA_DIRS");
1523 if (xdg_data_dirs == NULL)
1524 xdg_data_dirs = heap_printf("/usr/local/share/:/usr/share/");
1525 else
1526 xdg_data_dirs = strdupA(xdg_data_dirs);
1528 if (xdg_data_dirs)
1530 *mime_types = HeapAlloc(GetProcessHeap(), 0, sizeof(struct list));
1531 if (*mime_types)
1533 const char *begin;
1534 char *end;
1536 list_init(*mime_types);
1537 ret = add_mimes(xdg_data_home, *mime_types);
1538 if (ret)
1540 for (begin = xdg_data_dirs; (end = strchr(begin, ':')); begin = end + 1)
1542 *end = '\0';
1543 ret = add_mimes(begin, *mime_types);
1544 *end = ':';
1545 if (!ret)
1546 break;
1548 if (ret)
1549 ret = add_mimes(begin, *mime_types);
1552 else
1553 ret = FALSE;
1554 HeapFree(GetProcessHeap(), 0, xdg_data_dirs);
1556 else
1557 ret = FALSE;
1558 if (!ret && *mime_types)
1560 free_native_mime_types(*mime_types);
1561 *mime_types = NULL;
1563 return ret;
1566 static BOOL match_glob(struct list *native_mime_types, const char *extension,
1567 char **match)
1569 #ifdef HAVE_FNMATCH
1570 struct xdg_mime_type *mime_type_entry;
1571 int matchLength = 0;
1573 *match = NULL;
1575 LIST_FOR_EACH_ENTRY(mime_type_entry, native_mime_types, struct xdg_mime_type, entry)
1577 if (fnmatch(mime_type_entry->glob, extension, 0) == 0)
1579 if (*match == NULL || matchLength < strlen(mime_type_entry->glob))
1581 *match = mime_type_entry->mimeType;
1582 matchLength = strlen(mime_type_entry->glob);
1587 if (*match != NULL)
1589 *match = strdupA(*match);
1590 if (*match == NULL)
1591 return FALSE;
1593 #else
1594 *match = NULL;
1595 #endif
1596 return TRUE;
1599 static BOOL freedesktop_mime_type_for_extension(struct list *native_mime_types,
1600 const char *extensionA,
1601 LPCWSTR extensionW,
1602 char **mime_type)
1604 WCHAR *lower_extensionW;
1605 INT len;
1606 BOOL ret = match_glob(native_mime_types, extensionA, mime_type);
1607 if (ret == FALSE || *mime_type != NULL)
1608 return ret;
1609 len = strlenW(extensionW);
1610 lower_extensionW = HeapAlloc(GetProcessHeap(), 0, (len + 1)*sizeof(WCHAR));
1611 if (lower_extensionW)
1613 char *lower_extensionA;
1614 memcpy(lower_extensionW, extensionW, (len + 1)*sizeof(WCHAR));
1615 strlwrW(lower_extensionW);
1616 lower_extensionA = wchars_to_utf8_chars(lower_extensionW);
1617 if (lower_extensionA)
1619 ret = match_glob(native_mime_types, lower_extensionA, mime_type);
1620 HeapFree(GetProcessHeap(), 0, lower_extensionA);
1622 else
1624 ret = FALSE;
1625 WINE_FIXME("out of memory\n");
1627 HeapFree(GetProcessHeap(), 0, lower_extensionW);
1629 else
1631 ret = FALSE;
1632 WINE_FIXME("out of memory\n");
1634 return ret;
1637 static WCHAR* reg_get_valW(HKEY key, LPCWSTR subkey, LPCWSTR name)
1639 DWORD size;
1640 if (RegGetValueW(key, subkey, name, RRF_RT_REG_SZ, NULL, NULL, &size) == ERROR_SUCCESS)
1642 WCHAR *ret = HeapAlloc(GetProcessHeap(), 0, size);
1643 if (ret)
1645 if (RegGetValueW(key, subkey, name, RRF_RT_REG_SZ, NULL, ret, &size) == ERROR_SUCCESS)
1646 return ret;
1648 HeapFree(GetProcessHeap(), 0, ret);
1650 return NULL;
1653 static CHAR* reg_get_val_utf8(HKEY key, LPCWSTR subkey, LPCWSTR name)
1655 WCHAR *valW = reg_get_valW(key, subkey, name);
1656 if (valW)
1658 char *val = wchars_to_utf8_chars(valW);
1659 HeapFree(GetProcessHeap(), 0, valW);
1660 return val;
1662 return NULL;
1665 static HKEY open_associations_reg_key(void)
1667 static const WCHAR Software_Wine_FileOpenAssociationsW[] = {
1668 'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\','F','i','l','e','O','p','e','n','A','s','s','o','c','i','a','t','i','o','n','s',0};
1669 HKEY assocKey;
1670 if (RegCreateKeyW(HKEY_CURRENT_USER, Software_Wine_FileOpenAssociationsW, &assocKey) == ERROR_SUCCESS)
1671 return assocKey;
1672 return NULL;
1675 static BOOL has_association_changed(LPCWSTR extensionW, LPCSTR mimeType, LPCWSTR progId, LPCSTR appName, LPCWSTR docName)
1677 static const WCHAR ProgIDW[] = {'P','r','o','g','I','D',0};
1678 static const WCHAR DocNameW[] = {'D','o','c','N','a','m','e',0};
1679 static const WCHAR MimeTypeW[] = {'M','i','m','e','T','y','p','e',0};
1680 static const WCHAR AppNameW[] = {'A','p','p','N','a','m','e',0};
1681 HKEY assocKey;
1682 BOOL ret;
1684 if ((assocKey = open_associations_reg_key()))
1686 CHAR *valueA;
1687 WCHAR *value;
1689 ret = FALSE;
1691 valueA = reg_get_val_utf8(assocKey, extensionW, MimeTypeW);
1692 if (!valueA || lstrcmpA(valueA, mimeType))
1693 ret = TRUE;
1694 HeapFree(GetProcessHeap(), 0, valueA);
1696 value = reg_get_valW(assocKey, extensionW, ProgIDW);
1697 if (!value || strcmpW(value, progId))
1698 ret = TRUE;
1699 HeapFree(GetProcessHeap(), 0, value);
1701 valueA = reg_get_val_utf8(assocKey, extensionW, AppNameW);
1702 if (!valueA || lstrcmpA(valueA, appName))
1703 ret = TRUE;
1704 HeapFree(GetProcessHeap(), 0, valueA);
1706 value = reg_get_valW(assocKey, extensionW, DocNameW);
1707 if (docName && (!value || strcmpW(value, docName)))
1708 ret = TRUE;
1709 HeapFree(GetProcessHeap(), 0, value);
1711 RegCloseKey(assocKey);
1713 else
1715 WINE_ERR("error opening associations registry key\n");
1716 ret = FALSE;
1718 return ret;
1721 static void update_association(LPCWSTR extension, LPCSTR mimeType, LPCWSTR progId, LPCSTR appName, LPCWSTR docName, LPCSTR desktopFile)
1723 static const WCHAR ProgIDW[] = {'P','r','o','g','I','D',0};
1724 static const WCHAR DocNameW[] = {'D','o','c','N','a','m','e',0};
1725 static const WCHAR MimeTypeW[] = {'M','i','m','e','T','y','p','e',0};
1726 static const WCHAR AppNameW[] = {'A','p','p','N','a','m','e',0};
1727 static const WCHAR DesktopFileW[] = {'D','e','s','k','t','o','p','F','i','l','e',0};
1728 HKEY assocKey = NULL;
1729 HKEY subkey = NULL;
1730 WCHAR *mimeTypeW = NULL;
1731 WCHAR *appNameW = NULL;
1732 WCHAR *desktopFileW = NULL;
1734 assocKey = open_associations_reg_key();
1735 if (assocKey == NULL)
1737 WINE_ERR("could not open file associations key\n");
1738 goto done;
1741 if (RegCreateKeyW(assocKey, extension, &subkey) != ERROR_SUCCESS)
1743 WINE_ERR("could not create extension subkey\n");
1744 goto done;
1747 mimeTypeW = utf8_chars_to_wchars(mimeType);
1748 if (mimeTypeW == NULL)
1750 WINE_ERR("out of memory\n");
1751 goto done;
1754 appNameW = utf8_chars_to_wchars(appName);
1755 if (appNameW == NULL)
1757 WINE_ERR("out of memory\n");
1758 goto done;
1761 desktopFileW = utf8_chars_to_wchars(desktopFile);
1762 if (desktopFileW == NULL)
1764 WINE_ERR("out of memory\n");
1765 goto done;
1768 RegSetValueExW(subkey, MimeTypeW, 0, REG_SZ, (const BYTE*) mimeTypeW, (lstrlenW(mimeTypeW) + 1) * sizeof(WCHAR));
1769 RegSetValueExW(subkey, ProgIDW, 0, REG_SZ, (const BYTE*) progId, (lstrlenW(progId) + 1) * sizeof(WCHAR));
1770 RegSetValueExW(subkey, AppNameW, 0, REG_SZ, (const BYTE*) appNameW, (lstrlenW(appNameW) + 1) * sizeof(WCHAR));
1771 if (docName)
1772 RegSetValueExW(subkey, DocNameW, 0, REG_SZ, (const BYTE*) docName, (lstrlenW(docName) + 1) * sizeof(WCHAR));
1773 RegSetValueExW(subkey, DesktopFileW, 0, REG_SZ, (const BYTE*) desktopFileW, (lstrlenW(desktopFileW) + 1) * sizeof(WCHAR));
1775 done:
1776 RegCloseKey(assocKey);
1777 RegCloseKey(subkey);
1778 HeapFree(GetProcessHeap(), 0, mimeTypeW);
1779 HeapFree(GetProcessHeap(), 0, appNameW);
1780 HeapFree(GetProcessHeap(), 0, desktopFileW);
1783 static BOOL cleanup_associations(void)
1785 static const WCHAR openW[] = {'o','p','e','n',0};
1786 static const WCHAR DesktopFileW[] = {'D','e','s','k','t','o','p','F','i','l','e',0};
1787 HKEY assocKey;
1788 BOOL hasChanged = FALSE;
1789 if ((assocKey = open_associations_reg_key()))
1791 int i;
1792 BOOL done = FALSE;
1793 for (i = 0; !done; i++)
1795 WCHAR *extensionW = NULL;
1796 DWORD size = 1024;
1797 LSTATUS ret;
1801 HeapFree(GetProcessHeap(), 0, extensionW);
1802 extensionW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1803 if (extensionW == NULL)
1805 WINE_ERR("out of memory\n");
1806 ret = ERROR_OUTOFMEMORY;
1807 break;
1809 ret = RegEnumKeyExW(assocKey, i, extensionW, &size, NULL, NULL, NULL, NULL);
1810 size *= 2;
1811 } while (ret == ERROR_MORE_DATA);
1813 if (ret == ERROR_SUCCESS)
1815 WCHAR *command;
1816 command = assoc_query(ASSOCSTR_COMMAND, extensionW, openW);
1817 if (command == NULL)
1819 char *desktopFile = reg_get_val_utf8(assocKey, extensionW, DesktopFileW);
1820 if (desktopFile)
1822 WINE_TRACE("removing file type association for %s\n", wine_dbgstr_w(extensionW));
1823 remove(desktopFile);
1825 RegDeleteKeyW(assocKey, extensionW);
1826 hasChanged = TRUE;
1827 HeapFree(GetProcessHeap(), 0, desktopFile);
1829 HeapFree(GetProcessHeap(), 0, command);
1831 else
1833 if (ret != ERROR_NO_MORE_ITEMS)
1834 WINE_ERR("error %d while reading registry\n", ret);
1835 done = TRUE;
1837 HeapFree(GetProcessHeap(), 0, extensionW);
1839 RegCloseKey(assocKey);
1841 else
1842 WINE_ERR("could not open file associations key\n");
1843 return hasChanged;
1846 static BOOL write_freedesktop_mime_type_entry(const char *packages_dir, const char *dot_extension,
1847 const char *mime_type, const char *comment)
1849 BOOL ret = FALSE;
1850 char *filename;
1852 WINE_TRACE("writing MIME type %s, extension=%s, comment=%s\n", wine_dbgstr_a(mime_type),
1853 wine_dbgstr_a(dot_extension), wine_dbgstr_a(comment));
1855 filename = heap_printf("%s/x-wine-extension-%s.xml", packages_dir, &dot_extension[1]);
1856 if (filename)
1858 FILE *packageFile = fopen(filename, "w");
1859 if (packageFile)
1861 fprintf(packageFile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1862 fprintf(packageFile, "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n");
1863 fprintf(packageFile, " <mime-type type=\"");
1864 write_xml_text(packageFile, mime_type);
1865 fprintf(packageFile, "\">\n");
1866 fprintf(packageFile, " <glob pattern=\"*");
1867 write_xml_text(packageFile, dot_extension);
1868 fprintf(packageFile, "\"/>\n");
1869 if (comment)
1871 fprintf(packageFile, " <comment>");
1872 write_xml_text(packageFile, comment);
1873 fprintf(packageFile, "</comment>\n");
1875 fprintf(packageFile, " </mime-type>\n");
1876 fprintf(packageFile, "</mime-info>\n");
1877 ret = TRUE;
1878 fclose(packageFile);
1880 else
1881 WINE_ERR("error writing file %s\n", filename);
1882 HeapFree(GetProcessHeap(), 0, filename);
1884 else
1885 WINE_ERR("out of memory\n");
1886 return ret;
1889 static BOOL is_extension_blacklisted(LPCWSTR extension)
1891 /* These are managed through external tools like wine.desktop, to evade malware created file type associations */
1892 static const WCHAR comW[] = {'.','c','o','m',0};
1893 static const WCHAR exeW[] = {'.','e','x','e',0};
1894 static const WCHAR msiW[] = {'.','m','s','i',0};
1896 if (!strcmpiW(extension, comW) ||
1897 !strcmpiW(extension, exeW) ||
1898 !strcmpiW(extension, msiW))
1899 return TRUE;
1900 return FALSE;
1903 static const char* get_special_mime_type(LPCWSTR extension)
1905 static const WCHAR lnkW[] = {'.','l','n','k',0};
1906 if (!strcmpiW(extension, lnkW))
1907 return "application/x-ms-shortcut";
1908 return NULL;
1911 static BOOL write_freedesktop_association_entry(const char *desktopPath, const char *dot_extension,
1912 const char *friendlyAppName, const char *mimeType,
1913 const char *progId)
1915 BOOL ret = FALSE;
1916 FILE *desktop;
1918 WINE_TRACE("writing association for file type %s, friendlyAppName=%s, MIME type %s, progID=%s, to file %s\n",
1919 wine_dbgstr_a(dot_extension), wine_dbgstr_a(friendlyAppName), wine_dbgstr_a(mimeType),
1920 wine_dbgstr_a(progId), wine_dbgstr_a(desktopPath));
1922 desktop = fopen(desktopPath, "w");
1923 if (desktop)
1925 fprintf(desktop, "[Desktop Entry]\n");
1926 fprintf(desktop, "Type=Application\n");
1927 fprintf(desktop, "Name=%s\n", friendlyAppName);
1928 fprintf(desktop, "MimeType=%s\n", mimeType);
1929 fprintf(desktop, "Exec=wine start /ProgIDOpen %s %%f\n", progId);
1930 fprintf(desktop, "NoDisplay=true\n");
1931 fprintf(desktop, "StartupNotify=true\n");
1932 ret = TRUE;
1933 fclose(desktop);
1935 else
1936 WINE_ERR("error writing association file %s\n", wine_dbgstr_a(desktopPath));
1937 return ret;
1940 static BOOL generate_associations(const char *xdg_data_home, const char *packages_dir, const char *applications_dir)
1942 static const WCHAR openW[] = {'o','p','e','n',0};
1943 struct list *nativeMimeTypes = NULL;
1944 LSTATUS ret = 0;
1945 int i;
1946 BOOL hasChanged = FALSE;
1948 if (!build_native_mime_types(xdg_data_home, &nativeMimeTypes))
1950 WINE_ERR("could not build native MIME types\n");
1951 return FALSE;
1954 for (i = 0; ; i++)
1956 WCHAR *extensionW = NULL;
1957 DWORD size = 1024;
1961 HeapFree(GetProcessHeap(), 0, extensionW);
1962 extensionW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1963 if (extensionW == NULL)
1965 WINE_ERR("out of memory\n");
1966 ret = ERROR_OUTOFMEMORY;
1967 break;
1969 ret = RegEnumKeyExW(HKEY_CLASSES_ROOT, i, extensionW, &size, NULL, NULL, NULL, NULL);
1970 size *= 2;
1971 } while (ret == ERROR_MORE_DATA);
1973 if (ret == ERROR_SUCCESS && extensionW[0] == '.' && !is_extension_blacklisted(extensionW))
1975 char *extensionA = NULL;
1976 WCHAR *commandW = NULL;
1977 WCHAR *friendlyDocNameW = NULL;
1978 char *friendlyDocNameA = NULL;
1979 WCHAR *iconW = NULL;
1980 char *iconA = NULL;
1981 WCHAR *contentTypeW = NULL;
1982 char *mimeTypeA = NULL;
1983 WCHAR *friendlyAppNameW = NULL;
1984 char *friendlyAppNameA = NULL;
1985 WCHAR *progIdW = NULL;
1986 char *progIdA = NULL;
1988 extensionA = wchars_to_utf8_chars(extensionW);
1989 if (extensionA == NULL)
1991 WINE_ERR("out of memory\n");
1992 goto end;
1995 friendlyDocNameW = assoc_query(ASSOCSTR_FRIENDLYDOCNAME, extensionW, NULL);
1996 if (friendlyDocNameW)
1998 friendlyDocNameA = wchars_to_utf8_chars(friendlyDocNameW);
1999 if (friendlyDocNameA == NULL)
2001 WINE_ERR("out of memory\n");
2002 goto end;
2006 iconW = assoc_query(ASSOCSTR_DEFAULTICON, extensionW, NULL);
2008 contentTypeW = assoc_query(ASSOCSTR_CONTENTTYPE, extensionW, NULL);
2009 if (contentTypeW)
2010 strlwrW(contentTypeW);
2012 if (!freedesktop_mime_type_for_extension(nativeMimeTypes, extensionA, extensionW, &mimeTypeA))
2013 goto end;
2015 if (mimeTypeA == NULL)
2017 if (contentTypeW != NULL && strchrW(contentTypeW, '/'))
2018 mimeTypeA = wchars_to_utf8_chars(contentTypeW);
2019 else if ((get_special_mime_type(extensionW)))
2020 mimeTypeA = strdupA(get_special_mime_type(extensionW));
2021 else
2022 mimeTypeA = heap_printf("application/x-wine-extension-%s", &extensionA[1]);
2024 if (mimeTypeA != NULL)
2026 /* Gnome seems to ignore the <icon> tag in MIME packages,
2027 * and the default name is more intuitive anyway.
2029 if (iconW)
2031 char *flattened_mime = slashes_to_minuses(mimeTypeA);
2032 if (flattened_mime)
2034 int index = 0;
2035 WCHAR *comma = strrchrW(iconW, ',');
2036 if (comma)
2038 *comma = 0;
2039 index = atoiW(comma + 1);
2041 iconA = extract_icon(iconW, index, flattened_mime, FALSE);
2042 HeapFree(GetProcessHeap(), 0, flattened_mime);
2046 write_freedesktop_mime_type_entry(packages_dir, extensionA, mimeTypeA, friendlyDocNameA);
2047 hasChanged = TRUE;
2049 else
2051 WINE_FIXME("out of memory\n");
2052 goto end;
2056 commandW = assoc_query(ASSOCSTR_COMMAND, extensionW, openW);
2057 if (commandW == NULL)
2058 /* no command => no application is associated */
2059 goto end;
2061 friendlyAppNameW = assoc_query(ASSOCSTR_FRIENDLYAPPNAME, extensionW, NULL);
2062 if (friendlyAppNameW)
2064 friendlyAppNameA = wchars_to_utf8_chars(friendlyAppNameW);
2065 if (friendlyAppNameA == NULL)
2067 WINE_ERR("out of memory\n");
2068 goto end;
2071 else
2073 friendlyAppNameA = heap_printf("A Wine application");
2074 if (friendlyAppNameA == NULL)
2076 WINE_ERR("out of memory\n");
2077 goto end;
2081 progIdW = reg_get_valW(HKEY_CLASSES_ROOT, extensionW, NULL);
2082 if (progIdW)
2084 progIdA = escape(progIdW);
2085 if (progIdA == NULL)
2087 WINE_ERR("out of memory\n");
2088 goto end;
2091 else
2092 goto end; /* no progID => not a file type association */
2094 if (has_association_changed(extensionW, mimeTypeA, progIdW, friendlyAppNameA, friendlyDocNameW))
2096 char *desktopPath = heap_printf("%s/wine-extension-%s.desktop", applications_dir, &extensionA[1]);
2097 if (desktopPath)
2099 if (write_freedesktop_association_entry(desktopPath, extensionA, friendlyAppNameA, mimeTypeA, progIdA))
2101 hasChanged = TRUE;
2102 update_association(extensionW, mimeTypeA, progIdW, friendlyAppNameA, friendlyDocNameW, desktopPath);
2104 HeapFree(GetProcessHeap(), 0, desktopPath);
2108 end:
2109 HeapFree(GetProcessHeap(), 0, extensionA);
2110 HeapFree(GetProcessHeap(), 0, commandW);
2111 HeapFree(GetProcessHeap(), 0, friendlyDocNameW);
2112 HeapFree(GetProcessHeap(), 0, friendlyDocNameA);
2113 HeapFree(GetProcessHeap(), 0, iconW);
2114 HeapFree(GetProcessHeap(), 0, iconA);
2115 HeapFree(GetProcessHeap(), 0, contentTypeW);
2116 HeapFree(GetProcessHeap(), 0, mimeTypeA);
2117 HeapFree(GetProcessHeap(), 0, friendlyAppNameW);
2118 HeapFree(GetProcessHeap(), 0, friendlyAppNameA);
2119 HeapFree(GetProcessHeap(), 0, progIdW);
2120 HeapFree(GetProcessHeap(), 0, progIdA);
2122 HeapFree(GetProcessHeap(), 0, extensionW);
2123 if (ret != ERROR_SUCCESS)
2124 break;
2127 free_native_mime_types(nativeMimeTypes);
2128 return hasChanged;
2131 static char *get_start_exe_path(void)
2133 static const WCHAR startW[] = {'\\','c','o','m','m','a','n','d',
2134 '\\','s','t','a','r','t','.','e','x','e',0};
2135 WCHAR start_path[MAX_PATH];
2136 GetWindowsDirectoryW(start_path, MAX_PATH);
2137 lstrcatW(start_path, startW);
2138 return escape(start_path);
2141 static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait )
2143 static const WCHAR startW[] = {'\\','c','o','m','m','a','n','d',
2144 '\\','s','t','a','r','t','.','e','x','e',0};
2145 char *link_name = NULL, *icon_name = NULL, *work_dir = NULL;
2146 char *escaped_path = NULL, *escaped_args = NULL, *description = NULL;
2147 WCHAR szTmp[INFOTIPSIZE];
2148 WCHAR szDescription[INFOTIPSIZE], szPath[MAX_PATH], szWorkDir[MAX_PATH];
2149 WCHAR szArgs[INFOTIPSIZE], szIconPath[MAX_PATH];
2150 int iIconId = 0, r = -1;
2151 DWORD csidl = -1;
2152 HANDLE hsem = NULL;
2153 char *unix_link = NULL;
2154 char *start_path = NULL;
2156 if ( !link )
2158 WINE_ERR("Link name is null\n");
2159 return FALSE;
2162 if( !GetLinkLocation( link, &csidl, &link_name ) )
2164 WINE_WARN("Unknown link location %s. Ignoring.\n",wine_dbgstr_w(link));
2165 return TRUE;
2167 if (!in_desktop_dir(csidl) && !in_startmenu(csidl))
2169 WINE_WARN("Not under desktop or start menu. Ignoring.\n");
2170 return TRUE;
2172 WINE_TRACE("Link : %s\n", wine_dbgstr_a(link_name));
2174 szTmp[0] = 0;
2175 IShellLinkW_GetWorkingDirectory( sl, szTmp, MAX_PATH );
2176 ExpandEnvironmentStringsW(szTmp, szWorkDir, MAX_PATH);
2177 WINE_TRACE("workdir : %s\n", wine_dbgstr_w(szWorkDir));
2179 szTmp[0] = 0;
2180 IShellLinkW_GetDescription( sl, szTmp, INFOTIPSIZE );
2181 ExpandEnvironmentStringsW(szTmp, szDescription, INFOTIPSIZE);
2182 WINE_TRACE("description: %s\n", wine_dbgstr_w(szDescription));
2184 get_cmdline( sl, szTmp, MAX_PATH, szArgs, INFOTIPSIZE);
2185 ExpandEnvironmentStringsW(szTmp, szPath, MAX_PATH);
2186 WINE_TRACE("path : %s\n", wine_dbgstr_w(szPath));
2187 WINE_TRACE("args : %s\n", wine_dbgstr_w(szArgs));
2189 szTmp[0] = 0;
2190 IShellLinkW_GetIconLocation( sl, szTmp, MAX_PATH, &iIconId );
2191 ExpandEnvironmentStringsW(szTmp, szIconPath, MAX_PATH);
2192 WINE_TRACE("icon file : %s\n", wine_dbgstr_w(szIconPath) );
2194 if( !szPath[0] )
2196 LPITEMIDLIST pidl = NULL;
2197 IShellLinkW_GetIDList( sl, &pidl );
2198 if( pidl && SHGetPathFromIDListW( pidl, szPath ) )
2199 WINE_TRACE("pidl path : %s\n", wine_dbgstr_w(szPath));
2202 /* extract the icon */
2203 if( szIconPath[0] )
2204 icon_name = extract_icon( szIconPath , iIconId, NULL, bWait );
2205 else
2206 icon_name = extract_icon( szPath, iIconId, NULL, bWait );
2208 /* fail - try once again after parent process exit */
2209 if( !icon_name )
2211 if (bWait)
2213 WINE_WARN("Unable to extract icon, deferring.\n");
2214 goto cleanup;
2216 WINE_ERR("failed to extract icon from %s\n",
2217 wine_dbgstr_w( szIconPath[0] ? szIconPath : szPath ));
2220 unix_link = wine_get_unix_file_name(link);
2221 if (unix_link == NULL)
2223 WINE_WARN("couldn't find unix path of %s\n", wine_dbgstr_w(link));
2224 goto cleanup;
2227 /* check the path */
2228 if( szPath[0] )
2230 static const WCHAR exeW[] = {'.','e','x','e',0};
2231 WCHAR *p;
2233 /* check for .exe extension */
2234 if (!(p = strrchrW( szPath, '.' )) ||
2235 strchrW( p, '\\' ) || strchrW( p, '/' ) ||
2236 lstrcmpiW( p, exeW ))
2238 /* Not .exe - use 'start.exe' to launch this file */
2239 p = szArgs + lstrlenW(szPath) + 2;
2240 if (szArgs[0])
2242 p[0] = ' ';
2243 memmove( p+1, szArgs, min( (lstrlenW(szArgs) + 1) * sizeof(szArgs[0]),
2244 sizeof(szArgs) - (p + 1 - szArgs) * sizeof(szArgs[0]) ) );
2246 else
2247 p[0] = 0;
2249 szArgs[0] = '"';
2250 lstrcpyW(szArgs + 1, szPath);
2251 p[-1] = '"';
2253 GetWindowsDirectoryW(szPath, MAX_PATH);
2254 lstrcatW(szPath, startW);
2257 /* convert app working dir */
2258 if (szWorkDir[0])
2259 work_dir = wine_get_unix_file_name( szWorkDir );
2261 else
2263 /* if there's no path... try run the link itself */
2264 lstrcpynW(szArgs, link, MAX_PATH);
2265 GetWindowsDirectoryW(szPath, MAX_PATH);
2266 lstrcatW(szPath, startW);
2269 /* escape the path and parameters */
2270 escaped_path = escape(szPath);
2271 escaped_args = escape(szArgs);
2272 description = wchars_to_utf8_chars(szDescription);
2273 if (escaped_path == NULL || escaped_args == NULL || description == NULL)
2275 WINE_ERR("out of memory allocating/escaping parameters\n");
2276 goto cleanup;
2279 start_path = get_start_exe_path();
2280 if (start_path == NULL)
2282 WINE_ERR("out of memory\n");
2283 goto cleanup;
2286 /* building multiple menus concurrently has race conditions */
2287 hsem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
2288 if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hsem, FALSE, INFINITE, QS_ALLINPUT ) )
2290 WINE_ERR("failed wait for semaphore\n");
2291 goto cleanup;
2294 if (in_desktop_dir(csidl))
2296 char *location;
2297 const char *lastEntry;
2298 lastEntry = strrchr(link_name, '/');
2299 if (lastEntry == NULL)
2300 lastEntry = link_name;
2301 else
2302 ++lastEntry;
2303 location = heap_printf("%s/%s.desktop", xdg_desktop_dir, lastEntry);
2304 if (location)
2306 r = !write_desktop_entry(NULL, location, lastEntry, escaped_path, escaped_args, description, work_dir, icon_name);
2307 if (r == 0)
2308 chmod(location, 0755);
2309 HeapFree(GetProcessHeap(), 0, location);
2312 else
2314 WCHAR *unix_linkW = utf8_chars_to_wchars(unix_link);
2315 if (unix_linkW)
2317 char *escaped_lnk = escape(unix_linkW);
2318 if (escaped_lnk)
2320 char *menuarg = heap_printf("/Unix %s", escaped_lnk);
2321 if (menuarg)
2323 r = !write_menu_entry(unix_link, link_name, start_path, menuarg, description, work_dir, icon_name);
2324 HeapFree(GetProcessHeap(), 0, menuarg);
2326 HeapFree(GetProcessHeap(), 0, escaped_lnk);
2328 HeapFree(GetProcessHeap(), 0, unix_linkW);
2332 ReleaseSemaphore( hsem, 1, NULL );
2334 cleanup:
2335 if (hsem) CloseHandle( hsem );
2336 HeapFree( GetProcessHeap(), 0, icon_name );
2337 HeapFree( GetProcessHeap(), 0, work_dir );
2338 HeapFree( GetProcessHeap(), 0, link_name );
2339 HeapFree( GetProcessHeap(), 0, escaped_args );
2340 HeapFree( GetProcessHeap(), 0, escaped_path );
2341 HeapFree( GetProcessHeap(), 0, description );
2342 HeapFree( GetProcessHeap(), 0, unix_link );
2343 HeapFree( GetProcessHeap(), 0, start_path );
2345 if (r && !bWait)
2346 WINE_ERR("failed to build the menu\n" );
2348 return ( r == 0 );
2351 static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link, BOOL bWait )
2353 char *link_name = NULL;
2354 DWORD csidl = -1;
2355 LPWSTR urlPath;
2356 char *escaped_urlPath = NULL;
2357 HRESULT hr;
2358 HANDLE hSem = NULL;
2359 BOOL ret = TRUE;
2360 int r = -1;
2361 char *unix_link = NULL;
2363 if ( !link )
2365 WINE_ERR("Link name is null\n");
2366 return TRUE;
2369 if( !GetLinkLocation( link, &csidl, &link_name ) )
2371 WINE_WARN("Unknown link location %s. Ignoring.\n",wine_dbgstr_w(link));
2372 return TRUE;
2374 if (!in_desktop_dir(csidl) && !in_startmenu(csidl))
2376 WINE_WARN("Not under desktop or start menu. Ignoring.\n");
2377 ret = TRUE;
2378 goto cleanup;
2380 WINE_TRACE("Link : %s\n", wine_dbgstr_a(link_name));
2382 hr = url->lpVtbl->GetURL(url, &urlPath);
2383 if (FAILED(hr))
2385 ret = TRUE;
2386 goto cleanup;
2388 WINE_TRACE("path : %s\n", wine_dbgstr_w(urlPath));
2390 unix_link = wine_get_unix_file_name(link);
2391 if (unix_link == NULL)
2393 WINE_WARN("couldn't find unix path of %s\n", wine_dbgstr_w(link));
2394 goto cleanup;
2397 escaped_urlPath = escape(urlPath);
2398 if (escaped_urlPath == NULL)
2400 WINE_ERR("couldn't escape url, out of memory\n");
2401 goto cleanup;
2404 hSem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
2405 if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hSem, FALSE, INFINITE, QS_ALLINPUT ) )
2407 WINE_ERR("failed wait for semaphore\n");
2408 goto cleanup;
2410 if (in_desktop_dir(csidl))
2412 char *location;
2413 const char *lastEntry;
2414 lastEntry = strrchr(link_name, '/');
2415 if (lastEntry == NULL)
2416 lastEntry = link_name;
2417 else
2418 ++lastEntry;
2419 location = heap_printf("%s/%s.desktop", xdg_desktop_dir, lastEntry);
2420 if (location)
2422 r = !write_desktop_entry(NULL, location, lastEntry, "winebrowser", escaped_urlPath, NULL, NULL, NULL);
2423 if (r == 0)
2424 chmod(location, 0755);
2425 HeapFree(GetProcessHeap(), 0, location);
2428 else
2429 r = !write_menu_entry(unix_link, link_name, "winebrowser", escaped_urlPath, NULL, NULL, NULL);
2430 ret = (r != 0);
2431 ReleaseSemaphore(hSem, 1, NULL);
2433 cleanup:
2434 if (hSem)
2435 CloseHandle(hSem);
2436 HeapFree(GetProcessHeap(), 0, link_name);
2437 CoTaskMemFree( urlPath );
2438 HeapFree(GetProcessHeap(), 0, escaped_urlPath);
2439 HeapFree(GetProcessHeap(), 0, unix_link);
2440 return ret;
2443 static BOOL WaitForParentProcess( void )
2445 PROCESSENTRY32 procentry;
2446 HANDLE hsnapshot = NULL, hprocess = NULL;
2447 DWORD ourpid = GetCurrentProcessId();
2448 BOOL ret = FALSE, rc;
2450 WINE_TRACE("Waiting for parent process\n");
2451 if ((hsnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 )) ==
2452 INVALID_HANDLE_VALUE)
2454 WINE_ERR("CreateToolhelp32Snapshot failed, error %d\n", GetLastError());
2455 goto done;
2458 procentry.dwSize = sizeof(PROCESSENTRY32);
2459 rc = Process32First( hsnapshot, &procentry );
2460 while (rc)
2462 if (procentry.th32ProcessID == ourpid) break;
2463 rc = Process32Next( hsnapshot, &procentry );
2465 if (!rc)
2467 WINE_WARN("Unable to find current process id %d when listing processes\n", ourpid);
2468 goto done;
2471 if ((hprocess = OpenProcess( SYNCHRONIZE, FALSE, procentry.th32ParentProcessID )) ==
2472 NULL)
2474 WINE_WARN("OpenProcess failed pid=%d, error %d\n", procentry.th32ParentProcessID,
2475 GetLastError());
2476 goto done;
2479 if (MsgWaitForMultipleObjects( 1, &hprocess, FALSE, INFINITE, QS_ALLINPUT ) == WAIT_OBJECT_0)
2480 ret = TRUE;
2481 else
2482 WINE_ERR("Unable to wait for parent process, error %d\n", GetLastError());
2484 done:
2485 if (hprocess) CloseHandle( hprocess );
2486 if (hsnapshot) CloseHandle( hsnapshot );
2487 return ret;
2490 static BOOL Process_Link( LPCWSTR linkname, BOOL bWait )
2492 IShellLinkW *sl;
2493 IPersistFile *pf;
2494 HRESULT r;
2495 WCHAR fullname[MAX_PATH];
2496 DWORD len;
2498 WINE_TRACE("%s, wait %d\n", wine_dbgstr_w(linkname), bWait);
2500 if( !linkname[0] )
2502 WINE_ERR("link name missing\n");
2503 return 1;
2506 len=GetFullPathNameW( linkname, MAX_PATH, fullname, NULL );
2507 if (len==0 || len>MAX_PATH)
2509 WINE_ERR("couldn't get full path of link file\n");
2510 return 1;
2513 r = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
2514 &IID_IShellLinkW, (LPVOID *) &sl );
2515 if( FAILED( r ) )
2517 WINE_ERR("No IID_IShellLink\n");
2518 return 1;
2521 r = IShellLinkW_QueryInterface( sl, &IID_IPersistFile, (LPVOID*) &pf );
2522 if( FAILED( r ) )
2524 WINE_ERR("No IID_IPersistFile\n");
2525 return 1;
2528 r = IPersistFile_Load( pf, fullname, STGM_READ );
2529 if( SUCCEEDED( r ) )
2531 /* If something fails (eg. Couldn't extract icon)
2532 * wait for parent process and try again
2534 if( ! InvokeShellLinker( sl, fullname, bWait ) && bWait )
2536 WaitForParentProcess();
2537 InvokeShellLinker( sl, fullname, FALSE );
2540 else
2542 WINE_ERR("unable to load %s\n", wine_dbgstr_w(linkname));
2545 IPersistFile_Release( pf );
2546 IShellLinkW_Release( sl );
2548 return !r;
2551 static BOOL Process_URL( LPCWSTR urlname, BOOL bWait )
2553 IUniformResourceLocatorW *url;
2554 IPersistFile *pf;
2555 HRESULT r;
2556 WCHAR fullname[MAX_PATH];
2557 DWORD len;
2559 WINE_TRACE("%s, wait %d\n", wine_dbgstr_w(urlname), bWait);
2561 if( !urlname[0] )
2563 WINE_ERR("URL name missing\n");
2564 return 1;
2567 len=GetFullPathNameW( urlname, MAX_PATH, fullname, NULL );
2568 if (len==0 || len>MAX_PATH)
2570 WINE_ERR("couldn't get full path of URL file\n");
2571 return 1;
2574 r = CoCreateInstance( &CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER,
2575 &IID_IUniformResourceLocatorW, (LPVOID *) &url );
2576 if( FAILED( r ) )
2578 WINE_ERR("No IID_IUniformResourceLocatorW\n");
2579 return 1;
2582 r = url->lpVtbl->QueryInterface( url, &IID_IPersistFile, (LPVOID*) &pf );
2583 if( FAILED( r ) )
2585 WINE_ERR("No IID_IPersistFile\n");
2586 return 1;
2588 r = IPersistFile_Load( pf, fullname, STGM_READ );
2589 if( SUCCEEDED( r ) )
2591 /* If something fails (eg. Couldn't extract icon)
2592 * wait for parent process and try again
2594 if( ! InvokeShellLinkerForURL( url, fullname, bWait ) && bWait )
2596 WaitForParentProcess();
2597 InvokeShellLinkerForURL( url, fullname, FALSE );
2601 IPersistFile_Release( pf );
2602 url->lpVtbl->Release( url );
2604 return !r;
2607 static void RefreshFileTypeAssociations(void)
2609 HANDLE hSem = NULL;
2610 char *mime_dir = NULL;
2611 char *packages_dir = NULL;
2612 char *applications_dir = NULL;
2613 BOOL hasChanged;
2615 hSem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
2616 if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hSem, FALSE, INFINITE, QS_ALLINPUT ) )
2618 WINE_ERR("failed wait for semaphore\n");
2619 CloseHandle(hSem);
2620 hSem = NULL;
2621 goto end;
2624 mime_dir = heap_printf("%s/mime", xdg_data_dir);
2625 if (mime_dir == NULL)
2627 WINE_ERR("out of memory\n");
2628 goto end;
2630 create_directories(mime_dir);
2632 packages_dir = heap_printf("%s/packages", mime_dir);
2633 if (packages_dir == NULL)
2635 WINE_ERR("out of memory\n");
2636 goto end;
2638 create_directories(packages_dir);
2640 applications_dir = heap_printf("%s/applications", xdg_data_dir);
2641 if (applications_dir == NULL)
2643 WINE_ERR("out of memory\n");
2644 goto end;
2646 create_directories(applications_dir);
2648 hasChanged = generate_associations(xdg_data_dir, packages_dir, applications_dir);
2649 hasChanged |= cleanup_associations();
2650 if (hasChanged)
2652 const char *argv[3];
2654 argv[0] = "update-mime-database";
2655 argv[1] = mime_dir;
2656 argv[2] = NULL;
2657 spawnvp( _P_NOWAIT, argv[0], argv );
2659 argv[0] = "update-desktop-database";
2660 argv[1] = applications_dir;
2661 spawnvp( _P_NOWAIT, argv[0], argv );
2664 end:
2665 if (hSem)
2667 ReleaseSemaphore(hSem, 1, NULL);
2668 CloseHandle(hSem);
2670 HeapFree(GetProcessHeap(), 0, mime_dir);
2671 HeapFree(GetProcessHeap(), 0, packages_dir);
2672 HeapFree(GetProcessHeap(), 0, applications_dir);
2675 static void cleanup_menus(void)
2677 HKEY hkey;
2679 hkey = open_menus_reg_key();
2680 if (hkey)
2682 int i;
2683 LSTATUS lret = ERROR_SUCCESS;
2684 for (i = 0; lret == ERROR_SUCCESS; )
2686 WCHAR *value = NULL;
2687 WCHAR *data = NULL;
2688 DWORD valueSize = 4096;
2689 DWORD dataSize = 4096;
2690 while (1)
2692 lret = ERROR_OUTOFMEMORY;
2693 value = HeapAlloc(GetProcessHeap(), 0, valueSize * sizeof(WCHAR));
2694 if (value == NULL)
2695 break;
2696 data = HeapAlloc(GetProcessHeap(), 0, dataSize * sizeof(WCHAR));
2697 if (data == NULL)
2698 break;
2699 lret = RegEnumValueW(hkey, i, value, &valueSize, NULL, NULL, (BYTE*)data, &dataSize);
2700 if (lret == ERROR_SUCCESS || lret != ERROR_MORE_DATA)
2701 break;
2702 valueSize *= 2;
2703 dataSize *= 2;
2704 HeapFree(GetProcessHeap(), 0, value);
2705 HeapFree(GetProcessHeap(), 0, data);
2706 value = data = NULL;
2708 if (lret == ERROR_SUCCESS)
2710 char *unix_file;
2711 char *windows_file;
2712 unix_file = wchars_to_unix_chars(value);
2713 windows_file = wchars_to_unix_chars(data);
2714 if (unix_file != NULL && windows_file != NULL)
2716 struct stat filestats;
2717 if (stat(windows_file, &filestats) < 0 && errno == ENOENT)
2719 WINE_TRACE("removing menu related file %s\n", unix_file);
2720 remove(unix_file);
2721 RegDeleteValueW(hkey, value);
2723 else
2724 i++;
2726 else
2728 WINE_ERR("out of memory enumerating menus\n");
2729 lret = ERROR_OUTOFMEMORY;
2731 HeapFree(GetProcessHeap(), 0, unix_file);
2732 HeapFree(GetProcessHeap(), 0, windows_file);
2734 else if (lret != ERROR_NO_MORE_ITEMS)
2735 WINE_ERR("error %d reading registry\n", lret);
2736 HeapFree(GetProcessHeap(), 0, value);
2737 HeapFree(GetProcessHeap(), 0, data);
2739 RegCloseKey(hkey);
2741 else
2742 WINE_ERR("error opening registry key, menu cleanup failed\n");
2745 static void thumbnail_lnk(LPCWSTR lnkPath, LPCWSTR outputPath)
2747 char *utf8lnkPath = NULL;
2748 char *utf8OutputPath = NULL;
2749 WCHAR *winLnkPath = NULL;
2750 IShellLinkW *shellLink = NULL;
2751 IPersistFile *persistFile = NULL;
2752 WCHAR szTmp[MAX_PATH];
2753 WCHAR szPath[MAX_PATH];
2754 WCHAR szArgs[INFOTIPSIZE];
2755 WCHAR szIconPath[MAX_PATH];
2756 int iconId;
2757 HRESULT hr;
2759 utf8lnkPath = wchars_to_utf8_chars(lnkPath);
2760 if (utf8lnkPath == NULL)
2762 WINE_ERR("out of memory converting paths\n");
2763 goto end;
2766 utf8OutputPath = wchars_to_utf8_chars(outputPath);
2767 if (utf8OutputPath == NULL)
2769 WINE_ERR("out of memory converting paths\n");
2770 goto end;
2773 winLnkPath = wine_get_dos_file_name(utf8lnkPath);
2774 if (winLnkPath == NULL)
2776 WINE_ERR("could not convert %s to DOS path\n", utf8lnkPath);
2777 goto end;
2780 hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
2781 &IID_IShellLinkW, (LPVOID*)&shellLink);
2782 if (FAILED(hr))
2784 WINE_ERR("could not create IShellLinkW, error 0x%08X\n", hr);
2785 goto end;
2788 hr = IShellLinkW_QueryInterface(shellLink, &IID_IPersistFile, (LPVOID)&persistFile);
2789 if (FAILED(hr))
2791 WINE_ERR("could not query IPersistFile, error 0x%08X\n", hr);
2792 goto end;
2795 hr = IPersistFile_Load(persistFile, winLnkPath, STGM_READ);
2796 if (FAILED(hr))
2798 WINE_ERR("could not read .lnk, error 0x%08X\n", hr);
2799 goto end;
2802 get_cmdline(shellLink, szTmp, MAX_PATH, szArgs, INFOTIPSIZE);
2803 ExpandEnvironmentStringsW(szTmp, szPath, MAX_PATH);
2804 szTmp[0] = 0;
2805 IShellLinkW_GetIconLocation(shellLink, szTmp, MAX_PATH, &iconId);
2806 ExpandEnvironmentStringsW(szTmp, szIconPath, MAX_PATH);
2808 if(!szPath[0])
2810 LPITEMIDLIST pidl = NULL;
2811 IShellLinkW_GetIDList(shellLink, &pidl);
2812 if (pidl && SHGetPathFromIDListW(pidl, szPath))
2813 WINE_TRACE("pidl path : %s\n", wine_dbgstr_w(szPath));
2816 if (szIconPath[0])
2818 if (!ExtractFromEXEDLL(szIconPath, iconId, utf8OutputPath))
2819 ExtractFromICO(szIconPath, utf8OutputPath);
2821 else
2823 if (!ExtractFromEXEDLL(szPath, iconId, utf8OutputPath))
2824 ExtractFromICO(szPath, utf8OutputPath);
2827 end:
2828 HeapFree(GetProcessHeap(), 0, utf8lnkPath);
2829 HeapFree(GetProcessHeap(), 0, utf8OutputPath);
2830 HeapFree(GetProcessHeap(), 0, winLnkPath);
2831 if (shellLink != NULL)
2832 IShellLinkW_Release(shellLink);
2833 if (persistFile != NULL)
2834 IPersistFile_Release(persistFile);
2837 static WCHAR *next_token( LPWSTR *p )
2839 LPWSTR token = NULL, t = *p;
2841 if( !t )
2842 return NULL;
2844 while( t && !token )
2846 switch( *t )
2848 case ' ':
2849 t++;
2850 continue;
2851 case '"':
2852 /* unquote the token */
2853 token = ++t;
2854 t = strchrW( token, '"' );
2855 if( t )
2856 *t++ = 0;
2857 break;
2858 case 0:
2859 t = NULL;
2860 break;
2861 default:
2862 token = t;
2863 t = strchrW( token, ' ' );
2864 if( t )
2865 *t++ = 0;
2866 break;
2869 *p = t;
2870 return token;
2873 static BOOL init_xdg(void)
2875 WCHAR shellDesktopPath[MAX_PATH];
2876 HRESULT hr = SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, shellDesktopPath);
2877 if (SUCCEEDED(hr))
2878 xdg_desktop_dir = wine_get_unix_file_name(shellDesktopPath);
2879 if (xdg_desktop_dir == NULL)
2881 WINE_ERR("error looking up the desktop directory\n");
2882 return FALSE;
2885 if (getenv("XDG_CONFIG_HOME"))
2886 xdg_config_dir = heap_printf("%s/menus/applications-merged", getenv("XDG_CONFIG_HOME"));
2887 else
2888 xdg_config_dir = heap_printf("%s/.config/menus/applications-merged", getenv("HOME"));
2889 if (xdg_config_dir)
2891 create_directories(xdg_config_dir);
2892 if (getenv("XDG_DATA_HOME"))
2893 xdg_data_dir = strdupA(getenv("XDG_DATA_HOME"));
2894 else
2895 xdg_data_dir = heap_printf("%s/.local/share", getenv("HOME"));
2896 if (xdg_data_dir)
2898 char *buffer;
2899 create_directories(xdg_data_dir);
2900 buffer = heap_printf("%s/desktop-directories", xdg_data_dir);
2901 if (buffer)
2903 mkdir(buffer, 0777);
2904 HeapFree(GetProcessHeap(), 0, buffer);
2906 return TRUE;
2908 HeapFree(GetProcessHeap(), 0, xdg_config_dir);
2910 WINE_ERR("out of memory\n");
2911 return FALSE;
2914 /***********************************************************************
2916 * wWinMain
2918 int PASCAL wWinMain (HINSTANCE hInstance, HINSTANCE prev, LPWSTR cmdline, int show)
2920 static const WCHAR dash_aW[] = {'-','a',0};
2921 static const WCHAR dash_rW[] = {'-','r',0};
2922 static const WCHAR dash_tW[] = {'-','t',0};
2923 static const WCHAR dash_uW[] = {'-','u',0};
2924 static const WCHAR dash_wW[] = {'-','w',0};
2926 LPWSTR token = NULL, p;
2927 BOOL bWait = FALSE;
2928 BOOL bURL = FALSE;
2929 HRESULT hr;
2930 int ret = 0;
2932 if (!init_xdg())
2933 return 1;
2935 hr = CoInitialize(NULL);
2936 if (FAILED(hr))
2938 WINE_ERR("could not initialize COM, error 0x%08X\n", hr);
2939 return 1;
2942 for( p = cmdline; p && *p; )
2944 token = next_token( &p );
2945 if( !token )
2946 break;
2947 if( !strcmpW( token, dash_aW ) )
2949 RefreshFileTypeAssociations();
2950 continue;
2952 if( !strcmpW( token, dash_rW ) )
2954 cleanup_menus();
2955 continue;
2957 if( !strcmpW( token, dash_wW ) )
2958 bWait = TRUE;
2959 else if ( !strcmpW( token, dash_uW ) )
2960 bURL = TRUE;
2961 else if ( !strcmpW( token, dash_tW ) )
2963 WCHAR *lnkFile = next_token( &p );
2964 if (lnkFile)
2966 WCHAR *outputFile = next_token( &p );
2967 if (outputFile)
2968 thumbnail_lnk(lnkFile, outputFile);
2971 else if( token[0] == '-' )
2973 WINE_ERR( "unknown option %s\n", wine_dbgstr_w(token) );
2975 else
2977 BOOL bRet;
2979 if (bURL)
2980 bRet = Process_URL( token, bWait );
2981 else
2982 bRet = Process_Link( token, bWait );
2983 if (!bRet)
2985 WINE_ERR( "failed to build menu item for %s\n", wine_dbgstr_w(token) );
2986 ret = 1;
2991 CoUninitialize();
2992 return ret;