winemenubuilder: Move utility functions to the top of the file.
[wine/multimedia.git] / programs / winemenubuilder / winemenubuilder.c
blob6136701b370c839bd56563efc4f8e7774b22c379
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 HRESULT open_icon(LPCWSTR filename, int index, BOOL bWait, IStream **ppStream);
174 /* Utility routines */
175 static unsigned short crc16(const char* string)
177 unsigned short crc = 0;
178 int i, j, xor_poly;
180 for (i = 0; string[i] != 0; i++)
182 char c = string[i];
183 for (j = 0; j < 8; c >>= 1, j++)
185 xor_poly = (c ^ crc) & 1;
186 crc >>= 1;
187 if (xor_poly)
188 crc ^= 0xa001;
191 return crc;
194 static char *strdupA( const char *str )
196 char *ret;
198 if (!str) return NULL;
199 if ((ret = HeapAlloc( GetProcessHeap(), 0, strlen(str) + 1 ))) strcpy( ret, str );
200 return ret;
203 static char* heap_printf(const char *format, ...)
205 va_list args;
206 int size = 4096;
207 char *buffer, *ret;
208 int n;
210 va_start(args, format);
211 while (1)
213 buffer = HeapAlloc(GetProcessHeap(), 0, size);
214 if (buffer == NULL)
215 break;
216 n = vsnprintf(buffer, size, format, args);
217 if (n == -1)
218 size *= 2;
219 else if (n >= size)
220 size = n + 1;
221 else
222 break;
223 HeapFree(GetProcessHeap(), 0, buffer);
225 va_end(args);
226 if (!buffer) return NULL;
227 ret = HeapReAlloc(GetProcessHeap(), 0, buffer, strlen(buffer) + 1 );
228 if (!ret) ret = buffer;
229 return ret;
232 static void write_xml_text(FILE *file, const char *text)
234 int i;
235 for (i = 0; text[i]; i++)
237 if (text[i] == '&')
238 fputs("&amp;", file);
239 else if (text[i] == '<')
240 fputs("&lt;", file);
241 else if (text[i] == '>')
242 fputs("&gt;", file);
243 else if (text[i] == '\'')
244 fputs("&apos;", file);
245 else if (text[i] == '"')
246 fputs("&quot;", file);
247 else
248 fputc(text[i], file);
252 static BOOL create_directories(char *directory)
254 BOOL ret = TRUE;
255 int i;
257 for (i = 0; directory[i]; i++)
259 if (i > 0 && directory[i] == '/')
261 directory[i] = 0;
262 mkdir(directory, 0777);
263 directory[i] = '/';
266 if (mkdir(directory, 0777) && errno != EEXIST)
267 ret = FALSE;
269 return ret;
272 static char* wchars_to_utf8_chars(LPCWSTR string)
274 char *ret;
275 INT size = WideCharToMultiByte(CP_UTF8, 0, string, -1, NULL, 0, NULL, NULL);
276 ret = HeapAlloc(GetProcessHeap(), 0, size);
277 if (ret)
278 WideCharToMultiByte(CP_UTF8, 0, string, -1, ret, size, NULL, NULL);
279 return ret;
282 static char* wchars_to_unix_chars(LPCWSTR string)
284 char *ret;
285 INT size = WideCharToMultiByte(CP_UNIXCP, 0, string, -1, NULL, 0, NULL, NULL);
286 ret = HeapAlloc(GetProcessHeap(), 0, size);
287 if (ret)
288 WideCharToMultiByte(CP_UNIXCP, 0, string, -1, ret, size, NULL, NULL);
289 return ret;
292 static WCHAR* utf8_chars_to_wchars(LPCSTR string)
294 WCHAR *ret;
295 INT size = MultiByteToWideChar(CP_UTF8, 0, string, -1, NULL, 0);
296 ret = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
297 if (ret)
298 MultiByteToWideChar(CP_UTF8, 0, string, -1, ret, size);
299 return ret;
302 /* Icon extraction routines
304 * FIXME: should use PrivateExtractIcons and friends
305 * FIXME: should not use stdio
308 static BOOL SaveIconStreamAsPNG(IStream *icoFile, int index, const char *pngFileName, LPCWSTR commentW)
310 WCHAR *dosPNGFileName = NULL;
311 IWICImagingFactory *factory = NULL;
312 IWICBitmapDecoder *decoder = NULL;
313 IWICBitmapFrameDecode *sourceFrame = NULL;
314 IWICBitmapSource *sourceBitmap = NULL;
315 IWICBitmapEncoder *encoder = NULL;
316 IStream *outputFile = NULL;
317 IWICBitmapFrameEncode *dstFrame = NULL;
318 IPropertyBag2 *options = NULL;
319 UINT width, height;
320 HRESULT hr = E_FAIL;
322 dosPNGFileName = wine_get_dos_file_name(pngFileName);
323 if (dosPNGFileName == NULL)
325 WINE_ERR("error converting %s to DOS file name\n", pngFileName);
326 goto end;
328 hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
329 &IID_IWICImagingFactory, (void**)&factory);
330 if (FAILED(hr))
332 WINE_ERR("error 0x%08X creating IWICImagingFactory\n", hr);
333 goto end;
335 hr = IWICImagingFactory_CreateDecoderFromStream(factory, icoFile, NULL,
336 WICDecodeMetadataCacheOnDemand, &decoder);
337 if (FAILED(hr))
339 WINE_ERR("error 0x%08X creating IWICBitmapDecoder\n", hr);
340 goto end;
342 hr = IWICBitmapDecoder_GetFrame(decoder, index, &sourceFrame);
343 if (FAILED(hr))
345 WINE_ERR("error 0x%08X getting frame %d\n", hr, index);
346 goto end;
348 hr = WICConvertBitmapSource(&GUID_WICPixelFormat32bppBGRA, (IWICBitmapSource*)sourceFrame, &sourceBitmap);
349 if (FAILED(hr))
351 WINE_ERR("error 0x%08X converting bitmap to 32bppBGRA\n", hr);
352 goto end;
354 hr = CoCreateInstance(&CLSID_WICPngEncoder, NULL, CLSCTX_INPROC_SERVER,
355 &IID_IWICBitmapEncoder, (void**)&encoder);
356 if (FAILED(hr))
358 WINE_ERR("error 0x%08X creating bitmap encoder\n", hr);
359 goto end;
361 hr = SHCreateStreamOnFileW(dosPNGFileName, STGM_CREATE | STGM_WRITE, &outputFile);
362 if (FAILED(hr))
364 WINE_ERR("error 0x%08X creating output file\n", hr);
365 goto end;
367 hr = IWICBitmapEncoder_Initialize(encoder, outputFile, GENERIC_WRITE);
368 if (FAILED(hr))
370 WINE_ERR("error 0x%08X initializing encoder\n", hr);
371 goto end;
373 hr = IWICBitmapEncoder_CreateNewFrame(encoder, &dstFrame, &options);
374 if (FAILED(hr))
376 WINE_ERR("error 0x%08X creating encoder frame\n", hr);
377 goto end;
379 hr = IWICBitmapFrameEncode_Initialize(dstFrame, options);
380 if (FAILED(hr))
382 WINE_ERR("error 0x%08X initializing encoder frame\n", hr);
383 goto end;
385 hr = IWICBitmapSource_GetSize(sourceBitmap, &width, &height);
386 if (FAILED(hr))
388 WINE_ERR("error 0x%08X getting source bitmap size\n", hr);
389 goto end;
391 hr = IWICBitmapFrameEncode_SetSize(dstFrame, width, height);
392 if (FAILED(hr))
394 WINE_ERR("error 0x%08X setting destination bitmap size\n", hr);
395 goto end;
397 hr = IWICBitmapFrameEncode_SetResolution(dstFrame, 96, 96);
398 if (FAILED(hr))
400 WINE_ERR("error 0x%08X setting destination bitmap resolution\n", hr);
401 goto end;
403 hr = IWICBitmapFrameEncode_WriteSource(dstFrame, sourceBitmap, NULL);
404 if (FAILED(hr))
406 WINE_ERR("error 0x%08X copying bitmaps\n", hr);
407 goto end;
409 IWICBitmapFrameEncode_Commit(dstFrame);
410 IWICBitmapEncoder_Commit(encoder);
411 hr = S_OK;
413 end:
414 HeapFree(GetProcessHeap(), 0, dosPNGFileName);
415 if (factory)
416 IWICImagingFactory_Release(factory);
417 if (decoder)
418 IWICBitmapDecoder_Release(decoder);
419 if (sourceFrame)
420 IWICBitmapFrameDecode_Release(sourceFrame);
421 if (sourceBitmap)
422 IWICBitmapSource_Release(sourceBitmap);
423 if (encoder)
424 IWICBitmapEncoder_Release(encoder);
425 if (outputFile)
426 IStream_Release(outputFile);
427 if (dstFrame)
428 IWICBitmapFrameEncode_Release(dstFrame);
429 return SUCCEEDED(hr);
432 static IStream *add_module_icons_to_stream(HMODULE hModule, GRPICONDIR *grpIconDir)
434 int i;
435 SIZE_T iconsSize = 0;
436 BYTE *icons = NULL;
437 ICONDIRENTRY *iconDirEntries = NULL;
438 IStream *stream = NULL;
439 HRESULT hr = E_FAIL;
440 ULONG bytesWritten;
441 ICONDIR iconDir;
442 SIZE_T iconOffset;
443 int validEntries = 0;
444 LARGE_INTEGER zero;
446 for (i = 0; i < grpIconDir->idCount; i++)
447 iconsSize += grpIconDir->idEntries[i].dwBytesInRes;
448 icons = HeapAlloc(GetProcessHeap(), 0, iconsSize);
449 if (icons == NULL)
451 WINE_ERR("out of memory allocating icon\n");
452 goto end;
455 iconDirEntries = HeapAlloc(GetProcessHeap(), 0, grpIconDir->idCount*sizeof(ICONDIRENTRY));
456 if (iconDirEntries == NULL)
458 WINE_ERR("out of memory allocating icon dir entries\n");
459 goto end;
462 hr = CreateStreamOnHGlobal(NULL, TRUE, &stream);
463 if (FAILED(hr))
465 WINE_ERR("error creating icon stream\n");
466 goto end;
469 iconOffset = 0;
470 for (i = 0; i < grpIconDir->idCount; i++)
472 HRSRC hResInfo;
473 LPCWSTR lpName = MAKEINTRESOURCEW(grpIconDir->idEntries[i].nID);
474 if ((hResInfo = FindResourceW(hModule, lpName, (LPCWSTR)RT_ICON)))
476 HGLOBAL hResData;
477 if ((hResData = LoadResource(hModule, hResInfo)))
479 BITMAPINFO *pIcon;
480 if ((pIcon = LockResource(hResData)))
482 iconDirEntries[validEntries].bWidth = grpIconDir->idEntries[i].bWidth;
483 iconDirEntries[validEntries].bHeight = grpIconDir->idEntries[i].bHeight;
484 iconDirEntries[validEntries].bColorCount = grpIconDir->idEntries[i].bColorCount;
485 iconDirEntries[validEntries].bReserved = grpIconDir->idEntries[i].bReserved;
486 iconDirEntries[validEntries].wPlanes = grpIconDir->idEntries[i].wPlanes;
487 iconDirEntries[validEntries].wBitCount = grpIconDir->idEntries[i].wBitCount;
488 iconDirEntries[validEntries].dwBytesInRes = grpIconDir->idEntries[i].dwBytesInRes;
489 iconDirEntries[validEntries].dwImageOffset = iconOffset;
490 validEntries++;
491 memcpy(&icons[iconOffset], pIcon, grpIconDir->idEntries[i].dwBytesInRes);
492 iconOffset += grpIconDir->idEntries[i].dwBytesInRes;
494 FreeResource(hResData);
499 if (validEntries == 0)
501 WINE_ERR("no valid icon entries\n");
502 goto end;
505 iconDir.idReserved = 0;
506 iconDir.idType = 1;
507 iconDir.idCount = validEntries;
508 hr = IStream_Write(stream, &iconDir, sizeof(iconDir), &bytesWritten);
509 if (FAILED(hr) || bytesWritten != sizeof(iconDir))
511 WINE_ERR("error 0x%08X writing icon stream\n", hr);
512 goto end;
514 for (i = 0; i < validEntries; i++)
515 iconDirEntries[i].dwImageOffset += sizeof(ICONDIR) + validEntries*sizeof(ICONDIRENTRY);
516 hr = IStream_Write(stream, iconDirEntries, validEntries*sizeof(ICONDIRENTRY), &bytesWritten);
517 if (FAILED(hr) || bytesWritten != validEntries*sizeof(ICONDIRENTRY))
519 WINE_ERR("error 0x%08X writing icon dir entries to stream\n", hr);
520 goto end;
522 hr = IStream_Write(stream, icons, iconOffset, &bytesWritten);
523 if (FAILED(hr) || bytesWritten != iconOffset)
525 WINE_ERR("error 0x%08X writing icon images to stream\n", hr);
526 goto end;
528 zero.QuadPart = 0;
529 hr = IStream_Seek(stream, zero, STREAM_SEEK_SET, NULL);
531 end:
532 HeapFree(GetProcessHeap(), 0, icons);
533 HeapFree(GetProcessHeap(), 0, iconDirEntries);
534 if (FAILED(hr) && stream != NULL)
536 IStream_Release(stream);
537 stream = NULL;
539 return stream;
542 static BOOL CALLBACK EnumResNameProc(HMODULE hModule, LPCWSTR lpszType, LPWSTR lpszName, LONG_PTR lParam)
544 ENUMRESSTRUCT *sEnumRes = (ENUMRESSTRUCT *) lParam;
546 if (!sEnumRes->nIndex--)
548 *sEnumRes->pResInfo = FindResourceW(hModule, lpszName, (LPCWSTR)RT_GROUP_ICON);
549 return FALSE;
551 else
552 return TRUE;
555 static HRESULT open_module_icon(LPCWSTR szFileName, int nIndex, IStream **ppStream)
557 HMODULE hModule;
558 HRSRC hResInfo;
559 HGLOBAL hResData;
560 GRPICONDIR *pIconDir;
561 ENUMRESSTRUCT sEnumRes;
562 HRESULT hr = E_FAIL;
564 hModule = LoadLibraryExW(szFileName, 0, LOAD_LIBRARY_AS_DATAFILE);
565 if (!hModule)
567 WINE_WARN("LoadLibraryExW (%s) failed, error %d\n",
568 wine_dbgstr_w(szFileName), GetLastError());
569 return HRESULT_FROM_WIN32(GetLastError());
572 if (nIndex < 0)
574 hResInfo = FindResourceW(hModule, MAKEINTRESOURCEW(-nIndex), (LPCWSTR)RT_GROUP_ICON);
575 WINE_TRACE("FindResourceW (%s) called, return %p, error %d\n",
576 wine_dbgstr_w(szFileName), hResInfo, GetLastError());
578 else
580 hResInfo=NULL;
581 sEnumRes.pResInfo = &hResInfo;
582 sEnumRes.nIndex = nIndex;
583 if (!EnumResourceNamesW(hModule, (LPCWSTR)RT_GROUP_ICON,
584 EnumResNameProc, (LONG_PTR)&sEnumRes) &&
585 sEnumRes.nIndex != -1)
587 WINE_TRACE("EnumResourceNamesW failed, error %d\n", GetLastError());
591 if (hResInfo)
593 if ((hResData = LoadResource(hModule, hResInfo)))
595 if ((pIconDir = LockResource(hResData)))
597 *ppStream = add_module_icons_to_stream(hModule, pIconDir);
598 if (*ppStream)
599 hr = S_OK;
602 FreeResource(hResData);
605 else
607 WINE_WARN("found no icon\n");
608 FreeLibrary(hModule);
609 return HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
612 FreeLibrary(hModule);
613 return hr;
616 static HRESULT write_native_icon(IStream *iconStream, const char *icon_name, LPCWSTR szFileName)
618 ICONDIR iconDir;
619 ICONDIRENTRY *pIconDirEntry = NULL;
620 int nMax = 0, nMaxBits = 0;
621 int nIndex = 0;
622 void *pIcon = NULL;
623 int i;
624 ULONG bytesRead;
625 LARGE_INTEGER position;
626 HRESULT hr;
628 hr = IStream_Read(iconStream, &iconDir, sizeof(ICONDIR), &bytesRead);
629 if (FAILED(hr) || bytesRead != sizeof(ICONDIR) ||
630 (iconDir.idReserved != 0) || (iconDir.idType != 1))
632 WINE_WARN("Invalid ico file format (hr=0x%08X, bytesRead=%d)\n", hr, bytesRead);
633 hr = E_FAIL;
634 goto end;
637 if ((pIconDirEntry = HeapAlloc(GetProcessHeap(), 0, iconDir.idCount * sizeof (ICONDIRENTRY))) == NULL)
639 hr = E_OUTOFMEMORY;
640 goto end;
642 hr = IStream_Read(iconStream, pIconDirEntry, sizeof(ICONDIRENTRY)*iconDir.idCount, &bytesRead);
643 if (FAILED(hr) || bytesRead != sizeof(ICONDIRENTRY)*iconDir.idCount)
645 if (SUCCEEDED(hr)) hr = E_FAIL;
646 goto end;
649 for (i = 0; i < iconDir.idCount; i++)
651 WINE_TRACE("[%d]: %d x %d @ %d\n", i, pIconDirEntry[i].bWidth, pIconDirEntry[i].bHeight, pIconDirEntry[i].wBitCount);
652 if (pIconDirEntry[i].wBitCount >= nMaxBits &&
653 (pIconDirEntry[i].bHeight * pIconDirEntry[i].bWidth) >= nMax)
655 nIndex = i;
656 nMax = pIconDirEntry[i].bHeight * pIconDirEntry[i].bWidth;
657 nMaxBits = pIconDirEntry[i].wBitCount;
660 WINE_TRACE("Selected: %d\n", nIndex);
662 if ((pIcon = HeapAlloc(GetProcessHeap(), 0, pIconDirEntry[nIndex].dwBytesInRes)) == NULL)
664 hr = E_OUTOFMEMORY;
665 goto end;
667 position.QuadPart = pIconDirEntry[nIndex].dwImageOffset;
668 hr = IStream_Seek(iconStream, position, STREAM_SEEK_SET, NULL);
669 if (FAILED(hr))
670 goto end;
671 hr = IStream_Read(iconStream, pIcon, pIconDirEntry[nIndex].dwBytesInRes, &bytesRead);
672 if (FAILED(hr) || bytesRead != pIconDirEntry[nIndex].dwBytesInRes)
674 if (SUCCEEDED(hr)) hr = E_FAIL;
675 goto end;
678 position.QuadPart = 0;
679 hr = IStream_Seek(iconStream, position, STREAM_SEEK_SET, NULL);
680 if (FAILED(hr))
681 goto end;
682 if (!SaveIconStreamAsPNG(iconStream, nIndex, icon_name, szFileName))
684 hr = E_FAIL;
685 goto end;
687 hr = S_OK;
689 end:
690 HeapFree(GetProcessHeap(), 0, pIcon);
691 HeapFree(GetProcessHeap(), 0, pIconDirEntry);
692 return hr;
695 static HRESULT open_file_type_icon(LPCWSTR szFileName, IStream **ppStream)
697 WCHAR *extension;
698 WCHAR *icon = NULL;
699 WCHAR *comma;
700 WCHAR *executable = NULL;
701 int index = 0;
702 char *output_path = NULL;
703 HRESULT hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
705 extension = strrchrW(szFileName, '.');
706 if (extension == NULL)
707 goto end;
709 icon = assoc_query(ASSOCSTR_DEFAULTICON, extension, NULL);
710 if (icon)
712 comma = strrchrW(icon, ',');
713 if (comma)
715 *comma = 0;
716 index = atoiW(comma + 1);
718 hr = open_icon(icon, index, FALSE, ppStream);
720 else
722 executable = assoc_query(ASSOCSTR_EXECUTABLE, extension, NULL);
723 if (executable)
724 hr = open_icon(executable, 0, FALSE, ppStream);
727 end:
728 HeapFree(GetProcessHeap(), 0, icon);
729 HeapFree(GetProcessHeap(), 0, executable);
730 HeapFree(GetProcessHeap(), 0, output_path);
731 return hr;
734 static HRESULT open_default_icon(IStream **ppStream)
736 static const WCHAR user32W[] = {'u','s','e','r','3','2',0};
738 return open_module_icon(user32W, -(INT_PTR)IDI_WINLOGO, ppStream);
741 static HRESULT open_icon(LPCWSTR filename, int index, BOOL bWait, IStream **ppStream)
743 HRESULT hr;
745 hr = open_module_icon(filename, index, ppStream);
746 if (FAILED(hr))
748 static const WCHAR dot_icoW[] = {'.','i','c','o',0};
749 int len = strlenW(filename);
750 if (len >= 4 && strcmpiW(&filename[len - 4], dot_icoW) == 0)
751 hr = SHCreateStreamOnFileW(filename, STGM_READ, ppStream);
753 if (FAILED(hr))
754 hr = open_file_type_icon(filename, ppStream);
755 if (FAILED(hr) && !bWait)
756 hr = open_default_icon(ppStream);
757 return hr;
760 /* extract an icon from an exe or icon file; helper for IPersistFile_fnSave */
761 static char *extract_icon( LPCWSTR path, int index, const char *destFilename, BOOL bWait )
763 unsigned short crc;
764 char *iconsdir = NULL, *ico_path = NULL, *ico_name, *png_path = NULL;
765 char* s;
766 int n;
767 IStream *stream = NULL;
768 HRESULT hr;
770 /* Where should we save the icon? */
771 WINE_TRACE("path=[%s] index=%d\n", wine_dbgstr_w(path), index);
772 iconsdir = heap_printf("%s/icons", xdg_data_dir);
773 if (iconsdir)
775 if (mkdir(iconsdir, 0777) && errno != EEXIST)
777 WINE_WARN("couldn't make icons directory %s\n", wine_dbgstr_a(iconsdir));
778 goto end;
781 else
783 WINE_TRACE("no icon created\n");
784 return NULL;
787 /* Determine the icon base name */
788 n = WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL);
789 ico_path = HeapAlloc(GetProcessHeap(), 0, n);
790 WideCharToMultiByte(CP_UNIXCP, 0, path, -1, ico_path, n, NULL, NULL);
791 s=ico_name=ico_path;
792 while (*s!='\0') {
793 if (*s=='/' || *s=='\\') {
794 *s='\\';
795 ico_name=s;
796 } else {
797 *s=tolower(*s);
799 s++;
801 if (*ico_name=='\\') *ico_name++='\0';
802 s=strrchr(ico_name,'.');
803 if (s) *s='\0';
805 /* Compute the source-path hash */
806 crc=crc16(ico_path);
808 if (destFilename)
809 png_path=heap_printf("%s/%s.png",iconsdir,destFilename);
810 else
811 png_path=heap_printf("%s/%04x_%s.%d.png",iconsdir,crc,ico_name,index);
812 if (png_path == NULL)
814 WINE_ERR("could not extract icon %s, out of memory\n", wine_dbgstr_a(ico_name));
815 return NULL;
818 hr = open_icon( path, index, bWait, &stream );
819 if (SUCCEEDED(hr))
821 hr = write_native_icon( stream, png_path, path );
822 if (SUCCEEDED(hr))
823 goto end;
824 else
825 WINE_ERR("writing native icon for %s index %d failed, hr=0x%08X\n", wine_dbgstr_w(path), index, hr);
827 else
828 WINE_WARN("extracting icon %s index %d failed, hr=0x%08X\n", wine_dbgstr_w(path), index, hr);
830 HeapFree( GetProcessHeap(), 0, png_path );
831 png_path=NULL;
833 end:
834 HeapFree(GetProcessHeap(), 0, iconsdir);
835 HeapFree(GetProcessHeap(), 0, ico_path);
836 if (stream)
837 IStream_Release(stream);
838 return png_path;
841 static HKEY open_menus_reg_key(void)
843 static const WCHAR Software_Wine_FileOpenAssociationsW[] = {
844 'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\','M','e','n','u','F','i','l','e','s',0};
845 HKEY assocKey;
846 DWORD ret;
847 ret = RegCreateKeyW(HKEY_CURRENT_USER, Software_Wine_FileOpenAssociationsW, &assocKey);
848 if (ret == ERROR_SUCCESS)
849 return assocKey;
850 SetLastError(ret);
851 return NULL;
854 static DWORD register_menus_entry(const char *unix_file, const char *windows_file)
856 WCHAR *unix_fileW;
857 WCHAR *windows_fileW;
858 INT size;
859 DWORD ret;
861 size = MultiByteToWideChar(CP_UNIXCP, 0, unix_file, -1, NULL, 0);
862 unix_fileW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
863 if (unix_fileW)
865 MultiByteToWideChar(CP_UNIXCP, 0, unix_file, -1, unix_fileW, size);
866 size = MultiByteToWideChar(CP_UNIXCP, 0, windows_file, -1, NULL, 0);
867 windows_fileW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
868 if (windows_fileW)
870 HKEY hkey;
871 MultiByteToWideChar(CP_UNIXCP, 0, windows_file, -1, windows_fileW, size);
872 hkey = open_menus_reg_key();
873 if (hkey)
875 ret = RegSetValueExW(hkey, unix_fileW, 0, REG_SZ, (const BYTE*)windows_fileW,
876 (strlenW(windows_fileW) + 1) * sizeof(WCHAR));
877 RegCloseKey(hkey);
879 else
880 ret = GetLastError();
881 HeapFree(GetProcessHeap(), 0, windows_fileW);
883 else
884 ret = ERROR_NOT_ENOUGH_MEMORY;
885 HeapFree(GetProcessHeap(), 0, unix_fileW);
887 else
888 ret = ERROR_NOT_ENOUGH_MEMORY;
889 return ret;
892 static BOOL write_desktop_entry(const char *unix_link, const char *location, const char *linkname,
893 const char *path, const char *args, const char *descr,
894 const char *workdir, const char *icon)
896 FILE *file;
898 WINE_TRACE("(%s,%s,%s,%s,%s,%s,%s,%s)\n", wine_dbgstr_a(unix_link), wine_dbgstr_a(location),
899 wine_dbgstr_a(linkname), wine_dbgstr_a(path), wine_dbgstr_a(args),
900 wine_dbgstr_a(descr), wine_dbgstr_a(workdir), wine_dbgstr_a(icon));
902 file = fopen(location, "w");
903 if (file == NULL)
904 return FALSE;
906 fprintf(file, "[Desktop Entry]\n");
907 fprintf(file, "Name=%s\n", linkname);
908 fprintf(file, "Exec=env WINEPREFIX=\"%s\" wine %s %s\n",
909 wine_get_config_dir(), path, args);
910 fprintf(file, "Type=Application\n");
911 fprintf(file, "StartupNotify=true\n");
912 if (descr && lstrlenA(descr))
913 fprintf(file, "Comment=%s\n", descr);
914 if (workdir && lstrlenA(workdir))
915 fprintf(file, "Path=%s\n", workdir);
916 if (icon && lstrlenA(icon))
917 fprintf(file, "Icon=%s\n", icon);
919 fclose(file);
921 if (unix_link)
923 DWORD ret = register_menus_entry(location, unix_link);
924 if (ret != ERROR_SUCCESS)
925 return FALSE;
928 return TRUE;
931 static BOOL write_directory_entry(const char *directory, const char *location)
933 FILE *file;
935 WINE_TRACE("(%s,%s)\n", wine_dbgstr_a(directory), wine_dbgstr_a(location));
937 file = fopen(location, "w");
938 if (file == NULL)
939 return FALSE;
941 fprintf(file, "[Desktop Entry]\n");
942 fprintf(file, "Type=Directory\n");
943 if (strcmp(directory, "wine") == 0)
945 fprintf(file, "Name=Wine\n");
946 fprintf(file, "Icon=wine\n");
948 else
950 fprintf(file, "Name=%s\n", directory);
951 fprintf(file, "Icon=folder\n");
954 fclose(file);
955 return TRUE;
958 static BOOL write_menu_file(const char *unix_link, const char *filename)
960 char *tempfilename;
961 FILE *tempfile = NULL;
962 char *lastEntry;
963 char *name = NULL;
964 char *menuPath = NULL;
965 int i;
966 int count = 0;
967 BOOL ret = FALSE;
969 WINE_TRACE("(%s)\n", wine_dbgstr_a(filename));
971 while (1)
973 tempfilename = heap_printf("%s/wine-menu-XXXXXX", xdg_config_dir);
974 if (tempfilename)
976 int tempfd = mkstemps(tempfilename, 0);
977 if (tempfd >= 0)
979 tempfile = fdopen(tempfd, "w");
980 if (tempfile)
981 break;
982 close(tempfd);
983 goto end;
985 else if (errno == EEXIST)
987 HeapFree(GetProcessHeap(), 0, tempfilename);
988 continue;
990 HeapFree(GetProcessHeap(), 0, tempfilename);
992 return FALSE;
995 fprintf(tempfile, "<!DOCTYPE Menu PUBLIC \"-//freedesktop//DTD Menu 1.0//EN\"\n");
996 fprintf(tempfile, "\"http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd\">\n");
997 fprintf(tempfile, "<Menu>\n");
998 fprintf(tempfile, " <Name>Applications</Name>\n");
1000 name = HeapAlloc(GetProcessHeap(), 0, lstrlenA(filename) + 1);
1001 if (name == NULL) goto end;
1002 lastEntry = name;
1003 for (i = 0; filename[i]; i++)
1005 name[i] = filename[i];
1006 if (filename[i] == '/')
1008 char *dir_file_name;
1009 struct stat st;
1010 name[i] = 0;
1011 fprintf(tempfile, " <Menu>\n");
1012 fprintf(tempfile, " <Name>%s", count ? "" : "wine-");
1013 write_xml_text(tempfile, name);
1014 fprintf(tempfile, "</Name>\n");
1015 fprintf(tempfile, " <Directory>%s", count ? "" : "wine-");
1016 write_xml_text(tempfile, name);
1017 fprintf(tempfile, ".directory</Directory>\n");
1018 dir_file_name = heap_printf("%s/desktop-directories/%s%s.directory",
1019 xdg_data_dir, count ? "" : "wine-", name);
1020 if (dir_file_name)
1022 if (stat(dir_file_name, &st) != 0 && errno == ENOENT)
1023 write_directory_entry(lastEntry, dir_file_name);
1024 HeapFree(GetProcessHeap(), 0, dir_file_name);
1026 name[i] = '-';
1027 lastEntry = &name[i+1];
1028 ++count;
1031 name[i] = 0;
1033 fprintf(tempfile, " <Include>\n");
1034 fprintf(tempfile, " <Filename>");
1035 write_xml_text(tempfile, name);
1036 fprintf(tempfile, "</Filename>\n");
1037 fprintf(tempfile, " </Include>\n");
1038 for (i = 0; i < count; i++)
1039 fprintf(tempfile, " </Menu>\n");
1040 fprintf(tempfile, "</Menu>\n");
1042 menuPath = heap_printf("%s/%s", xdg_config_dir, name);
1043 if (menuPath == NULL) goto end;
1044 strcpy(menuPath + strlen(menuPath) - strlen(".desktop"), ".menu");
1045 ret = TRUE;
1047 end:
1048 if (tempfile)
1049 fclose(tempfile);
1050 if (ret)
1051 ret = (rename(tempfilename, menuPath) == 0);
1052 if (!ret && tempfilename)
1053 remove(tempfilename);
1054 HeapFree(GetProcessHeap(), 0, tempfilename);
1055 if (ret)
1056 register_menus_entry(menuPath, unix_link);
1057 HeapFree(GetProcessHeap(), 0, name);
1058 HeapFree(GetProcessHeap(), 0, menuPath);
1059 return ret;
1062 static BOOL write_menu_entry(const char *unix_link, const char *link, const char *path, const char *args,
1063 const char *descr, const char *workdir, const char *icon)
1065 const char *linkname;
1066 char *desktopPath = NULL;
1067 char *desktopDir;
1068 char *filename = NULL;
1069 BOOL ret = TRUE;
1071 WINE_TRACE("(%s, %s, %s, %s, %s, %s, %s)\n", wine_dbgstr_a(unix_link), wine_dbgstr_a(link),
1072 wine_dbgstr_a(path), wine_dbgstr_a(args), wine_dbgstr_a(descr),
1073 wine_dbgstr_a(workdir), wine_dbgstr_a(icon));
1075 linkname = strrchr(link, '/');
1076 if (linkname == NULL)
1077 linkname = link;
1078 else
1079 ++linkname;
1081 desktopPath = heap_printf("%s/applications/wine/%s.desktop", xdg_data_dir, link);
1082 if (!desktopPath)
1084 WINE_WARN("out of memory creating menu entry\n");
1085 ret = FALSE;
1086 goto end;
1088 desktopDir = strrchr(desktopPath, '/');
1089 *desktopDir = 0;
1090 if (!create_directories(desktopPath))
1092 WINE_WARN("couldn't make parent directories for %s\n", wine_dbgstr_a(desktopPath));
1093 ret = FALSE;
1094 goto end;
1096 *desktopDir = '/';
1097 if (!write_desktop_entry(unix_link, desktopPath, linkname, path, args, descr, workdir, icon))
1099 WINE_WARN("couldn't make desktop entry %s\n", wine_dbgstr_a(desktopPath));
1100 ret = FALSE;
1101 goto end;
1104 filename = heap_printf("wine/%s.desktop", link);
1105 if (!filename || !write_menu_file(unix_link, filename))
1107 WINE_WARN("couldn't make menu file %s\n", wine_dbgstr_a(filename));
1108 ret = FALSE;
1111 end:
1112 HeapFree(GetProcessHeap(), 0, desktopPath);
1113 HeapFree(GetProcessHeap(), 0, filename);
1114 return ret;
1117 /* This escapes reserved characters in .desktop files' Exec keys. */
1118 static LPSTR escape(LPCWSTR arg)
1120 int i, j;
1121 WCHAR *escaped_string;
1122 char *utf8_string;
1124 escaped_string = HeapAlloc(GetProcessHeap(), 0, (4 * strlenW(arg) + 1) * sizeof(WCHAR));
1125 if (escaped_string == NULL) return NULL;
1126 for (i = j = 0; arg[i]; i++)
1128 switch (arg[i])
1130 case '\\':
1131 escaped_string[j++] = '\\';
1132 escaped_string[j++] = '\\';
1133 escaped_string[j++] = '\\';
1134 escaped_string[j++] = '\\';
1135 break;
1136 case ' ':
1137 case '\t':
1138 case '\n':
1139 case '"':
1140 case '\'':
1141 case '>':
1142 case '<':
1143 case '~':
1144 case '|':
1145 case '&':
1146 case ';':
1147 case '$':
1148 case '*':
1149 case '?':
1150 case '#':
1151 case '(':
1152 case ')':
1153 case '`':
1154 escaped_string[j++] = '\\';
1155 escaped_string[j++] = '\\';
1156 /* fall through */
1157 default:
1158 escaped_string[j++] = arg[i];
1159 break;
1162 escaped_string[j] = 0;
1164 utf8_string = wchars_to_utf8_chars(escaped_string);
1165 if (utf8_string == NULL)
1167 WINE_ERR("out of memory\n");
1168 goto end;
1171 end:
1172 HeapFree(GetProcessHeap(), 0, escaped_string);
1173 return utf8_string;
1176 /* Return a heap-allocated copy of the unix format difference between the two
1177 * Windows-format paths.
1178 * locn is the owning location
1179 * link is within locn
1181 static char *relative_path( LPCWSTR link, LPCWSTR locn )
1183 char *unix_locn, *unix_link;
1184 char *relative = NULL;
1186 unix_locn = wine_get_unix_file_name(locn);
1187 unix_link = wine_get_unix_file_name(link);
1188 if (unix_locn && unix_link)
1190 size_t len_unix_locn, len_unix_link;
1191 len_unix_locn = strlen (unix_locn);
1192 len_unix_link = strlen (unix_link);
1193 if (len_unix_locn < len_unix_link && memcmp (unix_locn, unix_link, len_unix_locn) == 0 && unix_link[len_unix_locn] == '/')
1195 size_t len_rel;
1196 char *p = strrchr (unix_link + len_unix_locn, '/');
1197 p = strrchr (p, '.');
1198 if (p)
1200 *p = '\0';
1201 len_unix_link = p - unix_link;
1203 len_rel = len_unix_link - len_unix_locn;
1204 relative = HeapAlloc(GetProcessHeap(), 0, len_rel);
1205 if (relative)
1207 memcpy (relative, unix_link + len_unix_locn + 1, len_rel);
1211 if (!relative)
1212 WINE_WARN("Could not separate the relative link path of %s in %s\n", wine_dbgstr_w(link), wine_dbgstr_w(locn));
1213 HeapFree(GetProcessHeap(), 0, unix_locn);
1214 HeapFree(GetProcessHeap(), 0, unix_link);
1215 return relative;
1218 /***********************************************************************
1220 * GetLinkLocation
1222 * returns TRUE if successful
1223 * *loc will contain CS_DESKTOPDIRECTORY, CS_STARTMENU, CS_STARTUP etc.
1224 * *relative will contain the address of a heap-allocated copy of the portion
1225 * of the filename that is within the specified location, in unix form
1227 static BOOL GetLinkLocation( LPCWSTR linkfile, DWORD *loc, char **relative )
1229 WCHAR filename[MAX_PATH], shortfilename[MAX_PATH], buffer[MAX_PATH];
1230 DWORD len, i, r, filelen;
1231 const DWORD locations[] = {
1232 CSIDL_STARTUP, CSIDL_DESKTOPDIRECTORY, CSIDL_STARTMENU,
1233 CSIDL_COMMON_STARTUP, CSIDL_COMMON_DESKTOPDIRECTORY,
1234 CSIDL_COMMON_STARTMENU };
1236 WINE_TRACE("%s\n", wine_dbgstr_w(linkfile));
1237 filelen=GetFullPathNameW( linkfile, MAX_PATH, shortfilename, NULL );
1238 if (filelen==0 || filelen>MAX_PATH)
1239 return FALSE;
1241 WINE_TRACE("%s\n", wine_dbgstr_w(shortfilename));
1243 /* the CSLU Toolkit uses a short path name when creating .lnk files;
1244 * expand or our hardcoded list won't match.
1246 filelen=GetLongPathNameW(shortfilename, filename, MAX_PATH);
1247 if (filelen==0 || filelen>MAX_PATH)
1248 return FALSE;
1250 WINE_TRACE("%s\n", wine_dbgstr_w(filename));
1252 for( i=0; i<sizeof(locations)/sizeof(locations[0]); i++ )
1254 if (!SHGetSpecialFolderPathW( 0, buffer, locations[i], FALSE ))
1255 continue;
1257 len = lstrlenW(buffer);
1258 if (len >= MAX_PATH)
1259 continue; /* We've just trashed memory! Hopefully we are OK */
1261 if (len > filelen || filename[len]!='\\')
1262 continue;
1263 /* do a lstrcmpinW */
1264 filename[len] = 0;
1265 r = lstrcmpiW( filename, buffer );
1266 filename[len] = '\\';
1267 if ( r )
1268 continue;
1270 /* return the remainder of the string and link type */
1271 *loc = locations[i];
1272 *relative = relative_path (filename, buffer);
1273 return (*relative != NULL);
1276 return FALSE;
1279 /* gets the target path directly or through MSI */
1280 static HRESULT get_cmdline( IShellLinkW *sl, LPWSTR szPath, DWORD pathSize,
1281 LPWSTR szArgs, DWORD argsSize)
1283 IShellLinkDataList *dl = NULL;
1284 EXP_DARWIN_LINK *dar = NULL;
1285 HRESULT hr;
1287 szPath[0] = 0;
1288 szArgs[0] = 0;
1290 hr = IShellLinkW_GetPath( sl, szPath, pathSize, NULL, SLGP_RAWPATH );
1291 if (hr == S_OK && szPath[0])
1293 IShellLinkW_GetArguments( sl, szArgs, argsSize );
1294 return hr;
1297 hr = IShellLinkW_QueryInterface( sl, &IID_IShellLinkDataList, (LPVOID*) &dl );
1298 if (FAILED(hr))
1299 return hr;
1301 hr = IShellLinkDataList_CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar );
1302 if (SUCCEEDED(hr))
1304 WCHAR* szCmdline;
1305 DWORD cmdSize;
1307 cmdSize=0;
1308 hr = CommandLineFromMsiDescriptor( dar->szwDarwinID, NULL, &cmdSize );
1309 if (hr == ERROR_SUCCESS)
1311 cmdSize++;
1312 szCmdline = HeapAlloc( GetProcessHeap(), 0, cmdSize*sizeof(WCHAR) );
1313 hr = CommandLineFromMsiDescriptor( dar->szwDarwinID, szCmdline, &cmdSize );
1314 WINE_TRACE(" command : %s\n", wine_dbgstr_w(szCmdline));
1315 if (hr == ERROR_SUCCESS)
1317 WCHAR *s, *d;
1318 int bcount, in_quotes;
1320 /* Extract the application path */
1321 bcount=0;
1322 in_quotes=0;
1323 s=szCmdline;
1324 d=szPath;
1325 while (*s)
1327 if ((*s==0x0009 || *s==0x0020) && !in_quotes)
1329 /* skip the remaining spaces */
1330 do {
1331 s++;
1332 } while (*s==0x0009 || *s==0x0020);
1333 break;
1335 else if (*s==0x005c)
1337 /* '\\' */
1338 *d++=*s++;
1339 bcount++;
1341 else if (*s==0x0022)
1343 /* '"' */
1344 if ((bcount & 1)==0)
1346 /* Preceded by an even number of '\', this is
1347 * half that number of '\', plus a quote which
1348 * we erase.
1350 d-=bcount/2;
1351 in_quotes=!in_quotes;
1352 s++;
1354 else
1356 /* Preceded by an odd number of '\', this is
1357 * half that number of '\' followed by a '"'
1359 d=d-bcount/2-1;
1360 *d++='"';
1361 s++;
1363 bcount=0;
1365 else
1367 /* a regular character */
1368 *d++=*s++;
1369 bcount=0;
1371 if ((d-szPath) == pathSize)
1373 /* Keep processing the path till we get to the
1374 * arguments, but 'stand still'
1376 d--;
1379 /* Close the application path */
1380 *d=0;
1382 lstrcpynW(szArgs, s, argsSize);
1384 HeapFree( GetProcessHeap(), 0, szCmdline );
1386 LocalFree( dar );
1389 IShellLinkDataList_Release( dl );
1390 return hr;
1393 static WCHAR* assoc_query(ASSOCSTR assocStr, LPCWSTR name, LPCWSTR extra)
1395 HRESULT hr;
1396 WCHAR *value = NULL;
1397 DWORD size = 0;
1398 hr = AssocQueryStringW(0, assocStr, name, extra, NULL, &size);
1399 if (SUCCEEDED(hr))
1401 value = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1402 if (value)
1404 hr = AssocQueryStringW(0, assocStr, name, extra, value, &size);
1405 if (FAILED(hr))
1407 HeapFree(GetProcessHeap(), 0, value);
1408 value = NULL;
1412 return value;
1415 static char *slashes_to_minuses(const char *string)
1417 int i;
1418 char *ret = HeapAlloc(GetProcessHeap(), 0, lstrlenA(string) + 1);
1419 if (ret)
1421 for (i = 0; string[i]; i++)
1423 if (string[i] == '/')
1424 ret[i] = '-';
1425 else
1426 ret[i] = string[i];
1428 ret[i] = 0;
1429 return ret;
1431 return NULL;
1434 static BOOL next_line(FILE *file, char **line, int *size)
1436 int pos = 0;
1437 char *cr;
1438 if (*line == NULL)
1440 *size = 4096;
1441 *line = HeapAlloc(GetProcessHeap(), 0, *size);
1443 while (*line != NULL)
1445 if (fgets(&(*line)[pos], *size - pos, file) == NULL)
1447 HeapFree(GetProcessHeap(), 0, *line);
1448 *line = NULL;
1449 if (feof(file))
1450 return TRUE;
1451 return FALSE;
1453 pos = strlen(*line);
1454 cr = strchr(*line, '\n');
1455 if (cr == NULL)
1457 char *line2;
1458 (*size) *= 2;
1459 line2 = HeapReAlloc(GetProcessHeap(), 0, *line, *size);
1460 if (line2)
1461 *line = line2;
1462 else
1464 HeapFree(GetProcessHeap(), 0, *line);
1465 *line = NULL;
1468 else
1470 *cr = 0;
1471 return TRUE;
1474 return FALSE;
1477 static BOOL add_mimes(const char *xdg_data_dir, struct list *mime_types)
1479 char *globs_filename = NULL;
1480 BOOL ret = TRUE;
1481 globs_filename = heap_printf("%s/mime/globs", xdg_data_dir);
1482 if (globs_filename)
1484 FILE *globs_file = fopen(globs_filename, "r");
1485 if (globs_file) /* doesn't have to exist */
1487 char *line = NULL;
1488 int size = 0;
1489 while (ret && (ret = next_line(globs_file, &line, &size)) && line)
1491 char *pos;
1492 struct xdg_mime_type *mime_type_entry = NULL;
1493 if (line[0] != '#' && (pos = strchr(line, ':')))
1495 mime_type_entry = HeapAlloc(GetProcessHeap(), 0, sizeof(struct xdg_mime_type));
1496 if (mime_type_entry)
1498 *pos = 0;
1499 mime_type_entry->mimeType = strdupA(line);
1500 mime_type_entry->glob = strdupA(pos + 1);
1501 if (mime_type_entry->mimeType && mime_type_entry->glob)
1502 list_add_tail(mime_types, &mime_type_entry->entry);
1503 else
1505 HeapFree(GetProcessHeap(), 0, mime_type_entry->mimeType);
1506 HeapFree(GetProcessHeap(), 0, mime_type_entry->glob);
1507 HeapFree(GetProcessHeap(), 0, mime_type_entry);
1508 ret = FALSE;
1511 else
1512 ret = FALSE;
1515 HeapFree(GetProcessHeap(), 0, line);
1516 fclose(globs_file);
1518 HeapFree(GetProcessHeap(), 0, globs_filename);
1520 else
1521 ret = FALSE;
1522 return ret;
1525 static void free_native_mime_types(struct list *native_mime_types)
1527 struct xdg_mime_type *mime_type_entry, *mime_type_entry2;
1529 LIST_FOR_EACH_ENTRY_SAFE(mime_type_entry, mime_type_entry2, native_mime_types, struct xdg_mime_type, entry)
1531 list_remove(&mime_type_entry->entry);
1532 HeapFree(GetProcessHeap(), 0, mime_type_entry->glob);
1533 HeapFree(GetProcessHeap(), 0, mime_type_entry->mimeType);
1534 HeapFree(GetProcessHeap(), 0, mime_type_entry);
1536 HeapFree(GetProcessHeap(), 0, native_mime_types);
1539 static BOOL build_native_mime_types(const char *xdg_data_home, struct list **mime_types)
1541 char *xdg_data_dirs;
1542 BOOL ret;
1544 *mime_types = NULL;
1546 xdg_data_dirs = getenv("XDG_DATA_DIRS");
1547 if (xdg_data_dirs == NULL)
1548 xdg_data_dirs = heap_printf("/usr/local/share/:/usr/share/");
1549 else
1550 xdg_data_dirs = strdupA(xdg_data_dirs);
1552 if (xdg_data_dirs)
1554 *mime_types = HeapAlloc(GetProcessHeap(), 0, sizeof(struct list));
1555 if (*mime_types)
1557 const char *begin;
1558 char *end;
1560 list_init(*mime_types);
1561 ret = add_mimes(xdg_data_home, *mime_types);
1562 if (ret)
1564 for (begin = xdg_data_dirs; (end = strchr(begin, ':')); begin = end + 1)
1566 *end = '\0';
1567 ret = add_mimes(begin, *mime_types);
1568 *end = ':';
1569 if (!ret)
1570 break;
1572 if (ret)
1573 ret = add_mimes(begin, *mime_types);
1576 else
1577 ret = FALSE;
1578 HeapFree(GetProcessHeap(), 0, xdg_data_dirs);
1580 else
1581 ret = FALSE;
1582 if (!ret && *mime_types)
1584 free_native_mime_types(*mime_types);
1585 *mime_types = NULL;
1587 return ret;
1590 static BOOL match_glob(struct list *native_mime_types, const char *extension,
1591 char **match)
1593 #ifdef HAVE_FNMATCH
1594 struct xdg_mime_type *mime_type_entry;
1595 int matchLength = 0;
1597 *match = NULL;
1599 LIST_FOR_EACH_ENTRY(mime_type_entry, native_mime_types, struct xdg_mime_type, entry)
1601 if (fnmatch(mime_type_entry->glob, extension, 0) == 0)
1603 if (*match == NULL || matchLength < strlen(mime_type_entry->glob))
1605 *match = mime_type_entry->mimeType;
1606 matchLength = strlen(mime_type_entry->glob);
1611 if (*match != NULL)
1613 *match = strdupA(*match);
1614 if (*match == NULL)
1615 return FALSE;
1617 #else
1618 *match = NULL;
1619 #endif
1620 return TRUE;
1623 static BOOL freedesktop_mime_type_for_extension(struct list *native_mime_types,
1624 const char *extensionA,
1625 LPCWSTR extensionW,
1626 char **mime_type)
1628 WCHAR *lower_extensionW;
1629 INT len;
1630 BOOL ret = match_glob(native_mime_types, extensionA, mime_type);
1631 if (ret == FALSE || *mime_type != NULL)
1632 return ret;
1633 len = strlenW(extensionW);
1634 lower_extensionW = HeapAlloc(GetProcessHeap(), 0, (len + 1)*sizeof(WCHAR));
1635 if (lower_extensionW)
1637 char *lower_extensionA;
1638 memcpy(lower_extensionW, extensionW, (len + 1)*sizeof(WCHAR));
1639 strlwrW(lower_extensionW);
1640 lower_extensionA = wchars_to_utf8_chars(lower_extensionW);
1641 if (lower_extensionA)
1643 ret = match_glob(native_mime_types, lower_extensionA, mime_type);
1644 HeapFree(GetProcessHeap(), 0, lower_extensionA);
1646 else
1648 ret = FALSE;
1649 WINE_FIXME("out of memory\n");
1651 HeapFree(GetProcessHeap(), 0, lower_extensionW);
1653 else
1655 ret = FALSE;
1656 WINE_FIXME("out of memory\n");
1658 return ret;
1661 static WCHAR* reg_get_valW(HKEY key, LPCWSTR subkey, LPCWSTR name)
1663 DWORD size;
1664 if (RegGetValueW(key, subkey, name, RRF_RT_REG_SZ, NULL, NULL, &size) == ERROR_SUCCESS)
1666 WCHAR *ret = HeapAlloc(GetProcessHeap(), 0, size);
1667 if (ret)
1669 if (RegGetValueW(key, subkey, name, RRF_RT_REG_SZ, NULL, ret, &size) == ERROR_SUCCESS)
1670 return ret;
1672 HeapFree(GetProcessHeap(), 0, ret);
1674 return NULL;
1677 static CHAR* reg_get_val_utf8(HKEY key, LPCWSTR subkey, LPCWSTR name)
1679 WCHAR *valW = reg_get_valW(key, subkey, name);
1680 if (valW)
1682 char *val = wchars_to_utf8_chars(valW);
1683 HeapFree(GetProcessHeap(), 0, valW);
1684 return val;
1686 return NULL;
1689 static HKEY open_associations_reg_key(void)
1691 static const WCHAR Software_Wine_FileOpenAssociationsW[] = {
1692 '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};
1693 HKEY assocKey;
1694 if (RegCreateKeyW(HKEY_CURRENT_USER, Software_Wine_FileOpenAssociationsW, &assocKey) == ERROR_SUCCESS)
1695 return assocKey;
1696 return NULL;
1699 static BOOL has_association_changed(LPCWSTR extensionW, LPCSTR mimeType, LPCWSTR progId, LPCSTR appName, LPCWSTR docName)
1701 static const WCHAR ProgIDW[] = {'P','r','o','g','I','D',0};
1702 static const WCHAR DocNameW[] = {'D','o','c','N','a','m','e',0};
1703 static const WCHAR MimeTypeW[] = {'M','i','m','e','T','y','p','e',0};
1704 static const WCHAR AppNameW[] = {'A','p','p','N','a','m','e',0};
1705 HKEY assocKey;
1706 BOOL ret;
1708 if ((assocKey = open_associations_reg_key()))
1710 CHAR *valueA;
1711 WCHAR *value;
1713 ret = FALSE;
1715 valueA = reg_get_val_utf8(assocKey, extensionW, MimeTypeW);
1716 if (!valueA || lstrcmpA(valueA, mimeType))
1717 ret = TRUE;
1718 HeapFree(GetProcessHeap(), 0, valueA);
1720 value = reg_get_valW(assocKey, extensionW, ProgIDW);
1721 if (!value || strcmpW(value, progId))
1722 ret = TRUE;
1723 HeapFree(GetProcessHeap(), 0, value);
1725 valueA = reg_get_val_utf8(assocKey, extensionW, AppNameW);
1726 if (!valueA || lstrcmpA(valueA, appName))
1727 ret = TRUE;
1728 HeapFree(GetProcessHeap(), 0, valueA);
1730 value = reg_get_valW(assocKey, extensionW, DocNameW);
1731 if (docName && (!value || strcmpW(value, docName)))
1732 ret = TRUE;
1733 HeapFree(GetProcessHeap(), 0, value);
1735 RegCloseKey(assocKey);
1737 else
1739 WINE_ERR("error opening associations registry key\n");
1740 ret = FALSE;
1742 return ret;
1745 static void update_association(LPCWSTR extension, LPCSTR mimeType, LPCWSTR progId, LPCSTR appName, LPCWSTR docName, LPCSTR desktopFile)
1747 static const WCHAR ProgIDW[] = {'P','r','o','g','I','D',0};
1748 static const WCHAR DocNameW[] = {'D','o','c','N','a','m','e',0};
1749 static const WCHAR MimeTypeW[] = {'M','i','m','e','T','y','p','e',0};
1750 static const WCHAR AppNameW[] = {'A','p','p','N','a','m','e',0};
1751 static const WCHAR DesktopFileW[] = {'D','e','s','k','t','o','p','F','i','l','e',0};
1752 HKEY assocKey = NULL;
1753 HKEY subkey = NULL;
1754 WCHAR *mimeTypeW = NULL;
1755 WCHAR *appNameW = NULL;
1756 WCHAR *desktopFileW = NULL;
1758 assocKey = open_associations_reg_key();
1759 if (assocKey == NULL)
1761 WINE_ERR("could not open file associations key\n");
1762 goto done;
1765 if (RegCreateKeyW(assocKey, extension, &subkey) != ERROR_SUCCESS)
1767 WINE_ERR("could not create extension subkey\n");
1768 goto done;
1771 mimeTypeW = utf8_chars_to_wchars(mimeType);
1772 if (mimeTypeW == NULL)
1774 WINE_ERR("out of memory\n");
1775 goto done;
1778 appNameW = utf8_chars_to_wchars(appName);
1779 if (appNameW == NULL)
1781 WINE_ERR("out of memory\n");
1782 goto done;
1785 desktopFileW = utf8_chars_to_wchars(desktopFile);
1786 if (desktopFileW == NULL)
1788 WINE_ERR("out of memory\n");
1789 goto done;
1792 RegSetValueExW(subkey, MimeTypeW, 0, REG_SZ, (const BYTE*) mimeTypeW, (lstrlenW(mimeTypeW) + 1) * sizeof(WCHAR));
1793 RegSetValueExW(subkey, ProgIDW, 0, REG_SZ, (const BYTE*) progId, (lstrlenW(progId) + 1) * sizeof(WCHAR));
1794 RegSetValueExW(subkey, AppNameW, 0, REG_SZ, (const BYTE*) appNameW, (lstrlenW(appNameW) + 1) * sizeof(WCHAR));
1795 if (docName)
1796 RegSetValueExW(subkey, DocNameW, 0, REG_SZ, (const BYTE*) docName, (lstrlenW(docName) + 1) * sizeof(WCHAR));
1797 RegSetValueExW(subkey, DesktopFileW, 0, REG_SZ, (const BYTE*) desktopFileW, (lstrlenW(desktopFileW) + 1) * sizeof(WCHAR));
1799 done:
1800 RegCloseKey(assocKey);
1801 RegCloseKey(subkey);
1802 HeapFree(GetProcessHeap(), 0, mimeTypeW);
1803 HeapFree(GetProcessHeap(), 0, appNameW);
1804 HeapFree(GetProcessHeap(), 0, desktopFileW);
1807 static BOOL cleanup_associations(void)
1809 static const WCHAR openW[] = {'o','p','e','n',0};
1810 static const WCHAR DesktopFileW[] = {'D','e','s','k','t','o','p','F','i','l','e',0};
1811 HKEY assocKey;
1812 BOOL hasChanged = FALSE;
1813 if ((assocKey = open_associations_reg_key()))
1815 int i;
1816 BOOL done = FALSE;
1817 for (i = 0; !done; i++)
1819 WCHAR *extensionW = NULL;
1820 DWORD size = 1024;
1821 LSTATUS ret;
1825 HeapFree(GetProcessHeap(), 0, extensionW);
1826 extensionW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1827 if (extensionW == NULL)
1829 WINE_ERR("out of memory\n");
1830 ret = ERROR_OUTOFMEMORY;
1831 break;
1833 ret = RegEnumKeyExW(assocKey, i, extensionW, &size, NULL, NULL, NULL, NULL);
1834 size *= 2;
1835 } while (ret == ERROR_MORE_DATA);
1837 if (ret == ERROR_SUCCESS)
1839 WCHAR *command;
1840 command = assoc_query(ASSOCSTR_COMMAND, extensionW, openW);
1841 if (command == NULL)
1843 char *desktopFile = reg_get_val_utf8(assocKey, extensionW, DesktopFileW);
1844 if (desktopFile)
1846 WINE_TRACE("removing file type association for %s\n", wine_dbgstr_w(extensionW));
1847 remove(desktopFile);
1849 RegDeleteKeyW(assocKey, extensionW);
1850 hasChanged = TRUE;
1851 HeapFree(GetProcessHeap(), 0, desktopFile);
1853 HeapFree(GetProcessHeap(), 0, command);
1855 else
1857 if (ret != ERROR_NO_MORE_ITEMS)
1858 WINE_ERR("error %d while reading registry\n", ret);
1859 done = TRUE;
1861 HeapFree(GetProcessHeap(), 0, extensionW);
1863 RegCloseKey(assocKey);
1865 else
1866 WINE_ERR("could not open file associations key\n");
1867 return hasChanged;
1870 static BOOL write_freedesktop_mime_type_entry(const char *packages_dir, const char *dot_extension,
1871 const char *mime_type, const char *comment)
1873 BOOL ret = FALSE;
1874 char *filename;
1876 WINE_TRACE("writing MIME type %s, extension=%s, comment=%s\n", wine_dbgstr_a(mime_type),
1877 wine_dbgstr_a(dot_extension), wine_dbgstr_a(comment));
1879 filename = heap_printf("%s/x-wine-extension-%s.xml", packages_dir, &dot_extension[1]);
1880 if (filename)
1882 FILE *packageFile = fopen(filename, "w");
1883 if (packageFile)
1885 fprintf(packageFile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
1886 fprintf(packageFile, "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n");
1887 fprintf(packageFile, " <mime-type type=\"");
1888 write_xml_text(packageFile, mime_type);
1889 fprintf(packageFile, "\">\n");
1890 fprintf(packageFile, " <glob pattern=\"*");
1891 write_xml_text(packageFile, dot_extension);
1892 fprintf(packageFile, "\"/>\n");
1893 if (comment)
1895 fprintf(packageFile, " <comment>");
1896 write_xml_text(packageFile, comment);
1897 fprintf(packageFile, "</comment>\n");
1899 fprintf(packageFile, " </mime-type>\n");
1900 fprintf(packageFile, "</mime-info>\n");
1901 ret = TRUE;
1902 fclose(packageFile);
1904 else
1905 WINE_ERR("error writing file %s\n", filename);
1906 HeapFree(GetProcessHeap(), 0, filename);
1908 else
1909 WINE_ERR("out of memory\n");
1910 return ret;
1913 static BOOL is_extension_blacklisted(LPCWSTR extension)
1915 /* These are managed through external tools like wine.desktop, to evade malware created file type associations */
1916 static const WCHAR comW[] = {'.','c','o','m',0};
1917 static const WCHAR exeW[] = {'.','e','x','e',0};
1918 static const WCHAR msiW[] = {'.','m','s','i',0};
1920 if (!strcmpiW(extension, comW) ||
1921 !strcmpiW(extension, exeW) ||
1922 !strcmpiW(extension, msiW))
1923 return TRUE;
1924 return FALSE;
1927 static const char* get_special_mime_type(LPCWSTR extension)
1929 static const WCHAR lnkW[] = {'.','l','n','k',0};
1930 if (!strcmpiW(extension, lnkW))
1931 return "application/x-ms-shortcut";
1932 return NULL;
1935 static BOOL write_freedesktop_association_entry(const char *desktopPath, const char *dot_extension,
1936 const char *friendlyAppName, const char *mimeType,
1937 const char *progId)
1939 BOOL ret = FALSE;
1940 FILE *desktop;
1942 WINE_TRACE("writing association for file type %s, friendlyAppName=%s, MIME type %s, progID=%s, to file %s\n",
1943 wine_dbgstr_a(dot_extension), wine_dbgstr_a(friendlyAppName), wine_dbgstr_a(mimeType),
1944 wine_dbgstr_a(progId), wine_dbgstr_a(desktopPath));
1946 desktop = fopen(desktopPath, "w");
1947 if (desktop)
1949 fprintf(desktop, "[Desktop Entry]\n");
1950 fprintf(desktop, "Type=Application\n");
1951 fprintf(desktop, "Name=%s\n", friendlyAppName);
1952 fprintf(desktop, "MimeType=%s\n", mimeType);
1953 fprintf(desktop, "Exec=wine start /ProgIDOpen %s %%f\n", progId);
1954 fprintf(desktop, "NoDisplay=true\n");
1955 fprintf(desktop, "StartupNotify=true\n");
1956 ret = TRUE;
1957 fclose(desktop);
1959 else
1960 WINE_ERR("error writing association file %s\n", wine_dbgstr_a(desktopPath));
1961 return ret;
1964 static BOOL generate_associations(const char *xdg_data_home, const char *packages_dir, const char *applications_dir)
1966 static const WCHAR openW[] = {'o','p','e','n',0};
1967 struct list *nativeMimeTypes = NULL;
1968 LSTATUS ret = 0;
1969 int i;
1970 BOOL hasChanged = FALSE;
1972 if (!build_native_mime_types(xdg_data_home, &nativeMimeTypes))
1974 WINE_ERR("could not build native MIME types\n");
1975 return FALSE;
1978 for (i = 0; ; i++)
1980 WCHAR *extensionW = NULL;
1981 DWORD size = 1024;
1985 HeapFree(GetProcessHeap(), 0, extensionW);
1986 extensionW = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
1987 if (extensionW == NULL)
1989 WINE_ERR("out of memory\n");
1990 ret = ERROR_OUTOFMEMORY;
1991 break;
1993 ret = RegEnumKeyExW(HKEY_CLASSES_ROOT, i, extensionW, &size, NULL, NULL, NULL, NULL);
1994 size *= 2;
1995 } while (ret == ERROR_MORE_DATA);
1997 if (ret == ERROR_SUCCESS && extensionW[0] == '.' && !is_extension_blacklisted(extensionW))
1999 char *extensionA = NULL;
2000 WCHAR *commandW = NULL;
2001 WCHAR *friendlyDocNameW = NULL;
2002 char *friendlyDocNameA = NULL;
2003 WCHAR *iconW = NULL;
2004 char *iconA = NULL;
2005 WCHAR *contentTypeW = NULL;
2006 char *mimeTypeA = NULL;
2007 WCHAR *friendlyAppNameW = NULL;
2008 char *friendlyAppNameA = NULL;
2009 WCHAR *progIdW = NULL;
2010 char *progIdA = NULL;
2012 extensionA = wchars_to_utf8_chars(extensionW);
2013 if (extensionA == NULL)
2015 WINE_ERR("out of memory\n");
2016 goto end;
2019 friendlyDocNameW = assoc_query(ASSOCSTR_FRIENDLYDOCNAME, extensionW, NULL);
2020 if (friendlyDocNameW)
2022 friendlyDocNameA = wchars_to_utf8_chars(friendlyDocNameW);
2023 if (friendlyDocNameA == NULL)
2025 WINE_ERR("out of memory\n");
2026 goto end;
2030 iconW = assoc_query(ASSOCSTR_DEFAULTICON, extensionW, NULL);
2032 contentTypeW = assoc_query(ASSOCSTR_CONTENTTYPE, extensionW, NULL);
2033 if (contentTypeW)
2034 strlwrW(contentTypeW);
2036 if (!freedesktop_mime_type_for_extension(nativeMimeTypes, extensionA, extensionW, &mimeTypeA))
2037 goto end;
2039 if (mimeTypeA == NULL)
2041 if (contentTypeW != NULL && strchrW(contentTypeW, '/'))
2042 mimeTypeA = wchars_to_utf8_chars(contentTypeW);
2043 else if ((get_special_mime_type(extensionW)))
2044 mimeTypeA = strdupA(get_special_mime_type(extensionW));
2045 else
2046 mimeTypeA = heap_printf("application/x-wine-extension-%s", &extensionA[1]);
2048 if (mimeTypeA != NULL)
2050 /* Gnome seems to ignore the <icon> tag in MIME packages,
2051 * and the default name is more intuitive anyway.
2053 if (iconW)
2055 char *flattened_mime = slashes_to_minuses(mimeTypeA);
2056 if (flattened_mime)
2058 int index = 0;
2059 WCHAR *comma = strrchrW(iconW, ',');
2060 if (comma)
2062 *comma = 0;
2063 index = atoiW(comma + 1);
2065 iconA = extract_icon(iconW, index, flattened_mime, FALSE);
2066 HeapFree(GetProcessHeap(), 0, flattened_mime);
2070 write_freedesktop_mime_type_entry(packages_dir, extensionA, mimeTypeA, friendlyDocNameA);
2071 hasChanged = TRUE;
2073 else
2075 WINE_FIXME("out of memory\n");
2076 goto end;
2080 commandW = assoc_query(ASSOCSTR_COMMAND, extensionW, openW);
2081 if (commandW == NULL)
2082 /* no command => no application is associated */
2083 goto end;
2085 friendlyAppNameW = assoc_query(ASSOCSTR_FRIENDLYAPPNAME, extensionW, NULL);
2086 if (friendlyAppNameW)
2088 friendlyAppNameA = wchars_to_utf8_chars(friendlyAppNameW);
2089 if (friendlyAppNameA == NULL)
2091 WINE_ERR("out of memory\n");
2092 goto end;
2095 else
2097 friendlyAppNameA = heap_printf("A Wine application");
2098 if (friendlyAppNameA == NULL)
2100 WINE_ERR("out of memory\n");
2101 goto end;
2105 progIdW = reg_get_valW(HKEY_CLASSES_ROOT, extensionW, NULL);
2106 if (progIdW)
2108 progIdA = escape(progIdW);
2109 if (progIdA == NULL)
2111 WINE_ERR("out of memory\n");
2112 goto end;
2115 else
2116 goto end; /* no progID => not a file type association */
2118 if (has_association_changed(extensionW, mimeTypeA, progIdW, friendlyAppNameA, friendlyDocNameW))
2120 char *desktopPath = heap_printf("%s/wine-extension-%s.desktop", applications_dir, &extensionA[1]);
2121 if (desktopPath)
2123 if (write_freedesktop_association_entry(desktopPath, extensionA, friendlyAppNameA, mimeTypeA, progIdA))
2125 hasChanged = TRUE;
2126 update_association(extensionW, mimeTypeA, progIdW, friendlyAppNameA, friendlyDocNameW, desktopPath);
2128 HeapFree(GetProcessHeap(), 0, desktopPath);
2132 end:
2133 HeapFree(GetProcessHeap(), 0, extensionA);
2134 HeapFree(GetProcessHeap(), 0, commandW);
2135 HeapFree(GetProcessHeap(), 0, friendlyDocNameW);
2136 HeapFree(GetProcessHeap(), 0, friendlyDocNameA);
2137 HeapFree(GetProcessHeap(), 0, iconW);
2138 HeapFree(GetProcessHeap(), 0, iconA);
2139 HeapFree(GetProcessHeap(), 0, contentTypeW);
2140 HeapFree(GetProcessHeap(), 0, mimeTypeA);
2141 HeapFree(GetProcessHeap(), 0, friendlyAppNameW);
2142 HeapFree(GetProcessHeap(), 0, friendlyAppNameA);
2143 HeapFree(GetProcessHeap(), 0, progIdW);
2144 HeapFree(GetProcessHeap(), 0, progIdA);
2146 HeapFree(GetProcessHeap(), 0, extensionW);
2147 if (ret != ERROR_SUCCESS)
2148 break;
2151 free_native_mime_types(nativeMimeTypes);
2152 return hasChanged;
2155 static char *get_start_exe_path(void)
2157 static const WCHAR startW[] = {'\\','c','o','m','m','a','n','d',
2158 '\\','s','t','a','r','t','.','e','x','e',0};
2159 WCHAR start_path[MAX_PATH];
2160 GetWindowsDirectoryW(start_path, MAX_PATH);
2161 lstrcatW(start_path, startW);
2162 return escape(start_path);
2165 static BOOL InvokeShellLinker( IShellLinkW *sl, LPCWSTR link, BOOL bWait )
2167 static const WCHAR startW[] = {'\\','c','o','m','m','a','n','d',
2168 '\\','s','t','a','r','t','.','e','x','e',0};
2169 char *link_name = NULL, *icon_name = NULL, *work_dir = NULL;
2170 char *escaped_path = NULL, *escaped_args = NULL, *description = NULL;
2171 WCHAR szTmp[INFOTIPSIZE];
2172 WCHAR szDescription[INFOTIPSIZE], szPath[MAX_PATH], szWorkDir[MAX_PATH];
2173 WCHAR szArgs[INFOTIPSIZE], szIconPath[MAX_PATH];
2174 int iIconId = 0, r = -1;
2175 DWORD csidl = -1;
2176 HANDLE hsem = NULL;
2177 char *unix_link = NULL;
2178 char *start_path = NULL;
2180 if ( !link )
2182 WINE_ERR("Link name is null\n");
2183 return FALSE;
2186 if( !GetLinkLocation( link, &csidl, &link_name ) )
2188 WINE_WARN("Unknown link location %s. Ignoring.\n",wine_dbgstr_w(link));
2189 return TRUE;
2191 if (!in_desktop_dir(csidl) && !in_startmenu(csidl))
2193 WINE_WARN("Not under desktop or start menu. Ignoring.\n");
2194 return TRUE;
2196 WINE_TRACE("Link : %s\n", wine_dbgstr_a(link_name));
2198 szTmp[0] = 0;
2199 IShellLinkW_GetWorkingDirectory( sl, szTmp, MAX_PATH );
2200 ExpandEnvironmentStringsW(szTmp, szWorkDir, MAX_PATH);
2201 WINE_TRACE("workdir : %s\n", wine_dbgstr_w(szWorkDir));
2203 szTmp[0] = 0;
2204 IShellLinkW_GetDescription( sl, szTmp, INFOTIPSIZE );
2205 ExpandEnvironmentStringsW(szTmp, szDescription, INFOTIPSIZE);
2206 WINE_TRACE("description: %s\n", wine_dbgstr_w(szDescription));
2208 get_cmdline( sl, szTmp, MAX_PATH, szArgs, INFOTIPSIZE);
2209 ExpandEnvironmentStringsW(szTmp, szPath, MAX_PATH);
2210 WINE_TRACE("path : %s\n", wine_dbgstr_w(szPath));
2211 WINE_TRACE("args : %s\n", wine_dbgstr_w(szArgs));
2213 szTmp[0] = 0;
2214 IShellLinkW_GetIconLocation( sl, szTmp, MAX_PATH, &iIconId );
2215 ExpandEnvironmentStringsW(szTmp, szIconPath, MAX_PATH);
2216 WINE_TRACE("icon file : %s\n", wine_dbgstr_w(szIconPath) );
2218 if( !szPath[0] )
2220 LPITEMIDLIST pidl = NULL;
2221 IShellLinkW_GetIDList( sl, &pidl );
2222 if( pidl && SHGetPathFromIDListW( pidl, szPath ) )
2223 WINE_TRACE("pidl path : %s\n", wine_dbgstr_w(szPath));
2226 /* extract the icon */
2227 if( szIconPath[0] )
2228 icon_name = extract_icon( szIconPath , iIconId, NULL, bWait );
2229 else
2230 icon_name = extract_icon( szPath, iIconId, NULL, bWait );
2232 /* fail - try once again after parent process exit */
2233 if( !icon_name )
2235 if (bWait)
2237 WINE_WARN("Unable to extract icon, deferring.\n");
2238 goto cleanup;
2240 WINE_ERR("failed to extract icon from %s\n",
2241 wine_dbgstr_w( szIconPath[0] ? szIconPath : szPath ));
2244 unix_link = wine_get_unix_file_name(link);
2245 if (unix_link == NULL)
2247 WINE_WARN("couldn't find unix path of %s\n", wine_dbgstr_w(link));
2248 goto cleanup;
2251 /* check the path */
2252 if( szPath[0] )
2254 static const WCHAR exeW[] = {'.','e','x','e',0};
2255 WCHAR *p;
2257 /* check for .exe extension */
2258 if (!(p = strrchrW( szPath, '.' )) ||
2259 strchrW( p, '\\' ) || strchrW( p, '/' ) ||
2260 lstrcmpiW( p, exeW ))
2262 /* Not .exe - use 'start.exe' to launch this file */
2263 p = szArgs + lstrlenW(szPath) + 2;
2264 if (szArgs[0])
2266 p[0] = ' ';
2267 memmove( p+1, szArgs, min( (lstrlenW(szArgs) + 1) * sizeof(szArgs[0]),
2268 sizeof(szArgs) - (p + 1 - szArgs) * sizeof(szArgs[0]) ) );
2270 else
2271 p[0] = 0;
2273 szArgs[0] = '"';
2274 lstrcpyW(szArgs + 1, szPath);
2275 p[-1] = '"';
2277 GetWindowsDirectoryW(szPath, MAX_PATH);
2278 lstrcatW(szPath, startW);
2281 /* convert app working dir */
2282 if (szWorkDir[0])
2283 work_dir = wine_get_unix_file_name( szWorkDir );
2285 else
2287 /* if there's no path... try run the link itself */
2288 lstrcpynW(szArgs, link, MAX_PATH);
2289 GetWindowsDirectoryW(szPath, MAX_PATH);
2290 lstrcatW(szPath, startW);
2293 /* escape the path and parameters */
2294 escaped_path = escape(szPath);
2295 escaped_args = escape(szArgs);
2296 description = wchars_to_utf8_chars(szDescription);
2297 if (escaped_path == NULL || escaped_args == NULL || description == NULL)
2299 WINE_ERR("out of memory allocating/escaping parameters\n");
2300 goto cleanup;
2303 start_path = get_start_exe_path();
2304 if (start_path == NULL)
2306 WINE_ERR("out of memory\n");
2307 goto cleanup;
2310 /* building multiple menus concurrently has race conditions */
2311 hsem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
2312 if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hsem, FALSE, INFINITE, QS_ALLINPUT ) )
2314 WINE_ERR("failed wait for semaphore\n");
2315 goto cleanup;
2318 if (in_desktop_dir(csidl))
2320 char *location;
2321 const char *lastEntry;
2322 lastEntry = strrchr(link_name, '/');
2323 if (lastEntry == NULL)
2324 lastEntry = link_name;
2325 else
2326 ++lastEntry;
2327 location = heap_printf("%s/%s.desktop", xdg_desktop_dir, lastEntry);
2328 if (location)
2330 r = !write_desktop_entry(NULL, location, lastEntry, escaped_path, escaped_args, description, work_dir, icon_name);
2331 if (r == 0)
2332 chmod(location, 0755);
2333 HeapFree(GetProcessHeap(), 0, location);
2336 else
2338 WCHAR *unix_linkW = utf8_chars_to_wchars(unix_link);
2339 if (unix_linkW)
2341 char *escaped_lnk = escape(unix_linkW);
2342 if (escaped_lnk)
2344 char *menuarg = heap_printf("/Unix %s", escaped_lnk);
2345 if (menuarg)
2347 r = !write_menu_entry(unix_link, link_name, start_path, menuarg, description, work_dir, icon_name);
2348 HeapFree(GetProcessHeap(), 0, menuarg);
2350 HeapFree(GetProcessHeap(), 0, escaped_lnk);
2352 HeapFree(GetProcessHeap(), 0, unix_linkW);
2356 ReleaseSemaphore( hsem, 1, NULL );
2358 cleanup:
2359 if (hsem) CloseHandle( hsem );
2360 HeapFree( GetProcessHeap(), 0, icon_name );
2361 HeapFree( GetProcessHeap(), 0, work_dir );
2362 HeapFree( GetProcessHeap(), 0, link_name );
2363 HeapFree( GetProcessHeap(), 0, escaped_args );
2364 HeapFree( GetProcessHeap(), 0, escaped_path );
2365 HeapFree( GetProcessHeap(), 0, description );
2366 HeapFree( GetProcessHeap(), 0, unix_link );
2367 HeapFree( GetProcessHeap(), 0, start_path );
2369 if (r && !bWait)
2370 WINE_ERR("failed to build the menu\n" );
2372 return ( r == 0 );
2375 static BOOL InvokeShellLinkerForURL( IUniformResourceLocatorW *url, LPCWSTR link, BOOL bWait )
2377 char *link_name = NULL;
2378 DWORD csidl = -1;
2379 LPWSTR urlPath;
2380 char *escaped_urlPath = NULL;
2381 HRESULT hr;
2382 HANDLE hSem = NULL;
2383 BOOL ret = TRUE;
2384 int r = -1;
2385 char *unix_link = NULL;
2387 if ( !link )
2389 WINE_ERR("Link name is null\n");
2390 return TRUE;
2393 if( !GetLinkLocation( link, &csidl, &link_name ) )
2395 WINE_WARN("Unknown link location %s. Ignoring.\n",wine_dbgstr_w(link));
2396 return TRUE;
2398 if (!in_desktop_dir(csidl) && !in_startmenu(csidl))
2400 WINE_WARN("Not under desktop or start menu. Ignoring.\n");
2401 ret = TRUE;
2402 goto cleanup;
2404 WINE_TRACE("Link : %s\n", wine_dbgstr_a(link_name));
2406 hr = url->lpVtbl->GetURL(url, &urlPath);
2407 if (FAILED(hr))
2409 ret = TRUE;
2410 goto cleanup;
2412 WINE_TRACE("path : %s\n", wine_dbgstr_w(urlPath));
2414 unix_link = wine_get_unix_file_name(link);
2415 if (unix_link == NULL)
2417 WINE_WARN("couldn't find unix path of %s\n", wine_dbgstr_w(link));
2418 goto cleanup;
2421 escaped_urlPath = escape(urlPath);
2422 if (escaped_urlPath == NULL)
2424 WINE_ERR("couldn't escape url, out of memory\n");
2425 goto cleanup;
2428 hSem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
2429 if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hSem, FALSE, INFINITE, QS_ALLINPUT ) )
2431 WINE_ERR("failed wait for semaphore\n");
2432 goto cleanup;
2434 if (in_desktop_dir(csidl))
2436 char *location;
2437 const char *lastEntry;
2438 lastEntry = strrchr(link_name, '/');
2439 if (lastEntry == NULL)
2440 lastEntry = link_name;
2441 else
2442 ++lastEntry;
2443 location = heap_printf("%s/%s.desktop", xdg_desktop_dir, lastEntry);
2444 if (location)
2446 r = !write_desktop_entry(NULL, location, lastEntry, "winebrowser", escaped_urlPath, NULL, NULL, NULL);
2447 if (r == 0)
2448 chmod(location, 0755);
2449 HeapFree(GetProcessHeap(), 0, location);
2452 else
2453 r = !write_menu_entry(unix_link, link_name, "winebrowser", escaped_urlPath, NULL, NULL, NULL);
2454 ret = (r != 0);
2455 ReleaseSemaphore(hSem, 1, NULL);
2457 cleanup:
2458 if (hSem)
2459 CloseHandle(hSem);
2460 HeapFree(GetProcessHeap(), 0, link_name);
2461 CoTaskMemFree( urlPath );
2462 HeapFree(GetProcessHeap(), 0, escaped_urlPath);
2463 HeapFree(GetProcessHeap(), 0, unix_link);
2464 return ret;
2467 static BOOL WaitForParentProcess( void )
2469 PROCESSENTRY32 procentry;
2470 HANDLE hsnapshot = NULL, hprocess = NULL;
2471 DWORD ourpid = GetCurrentProcessId();
2472 BOOL ret = FALSE, rc;
2474 WINE_TRACE("Waiting for parent process\n");
2475 if ((hsnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 )) ==
2476 INVALID_HANDLE_VALUE)
2478 WINE_ERR("CreateToolhelp32Snapshot failed, error %d\n", GetLastError());
2479 goto done;
2482 procentry.dwSize = sizeof(PROCESSENTRY32);
2483 rc = Process32First( hsnapshot, &procentry );
2484 while (rc)
2486 if (procentry.th32ProcessID == ourpid) break;
2487 rc = Process32Next( hsnapshot, &procentry );
2489 if (!rc)
2491 WINE_WARN("Unable to find current process id %d when listing processes\n", ourpid);
2492 goto done;
2495 if ((hprocess = OpenProcess( SYNCHRONIZE, FALSE, procentry.th32ParentProcessID )) ==
2496 NULL)
2498 WINE_WARN("OpenProcess failed pid=%d, error %d\n", procentry.th32ParentProcessID,
2499 GetLastError());
2500 goto done;
2503 if (MsgWaitForMultipleObjects( 1, &hprocess, FALSE, INFINITE, QS_ALLINPUT ) == WAIT_OBJECT_0)
2504 ret = TRUE;
2505 else
2506 WINE_ERR("Unable to wait for parent process, error %d\n", GetLastError());
2508 done:
2509 if (hprocess) CloseHandle( hprocess );
2510 if (hsnapshot) CloseHandle( hsnapshot );
2511 return ret;
2514 static BOOL Process_Link( LPCWSTR linkname, BOOL bWait )
2516 IShellLinkW *sl;
2517 IPersistFile *pf;
2518 HRESULT r;
2519 WCHAR fullname[MAX_PATH];
2520 DWORD len;
2522 WINE_TRACE("%s, wait %d\n", wine_dbgstr_w(linkname), bWait);
2524 if( !linkname[0] )
2526 WINE_ERR("link name missing\n");
2527 return 1;
2530 len=GetFullPathNameW( linkname, MAX_PATH, fullname, NULL );
2531 if (len==0 || len>MAX_PATH)
2533 WINE_ERR("couldn't get full path of link file\n");
2534 return 1;
2537 r = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
2538 &IID_IShellLinkW, (LPVOID *) &sl );
2539 if( FAILED( r ) )
2541 WINE_ERR("No IID_IShellLink\n");
2542 return 1;
2545 r = IShellLinkW_QueryInterface( sl, &IID_IPersistFile, (LPVOID*) &pf );
2546 if( FAILED( r ) )
2548 WINE_ERR("No IID_IPersistFile\n");
2549 return 1;
2552 r = IPersistFile_Load( pf, fullname, STGM_READ );
2553 if( SUCCEEDED( r ) )
2555 /* If something fails (eg. Couldn't extract icon)
2556 * wait for parent process and try again
2558 if( ! InvokeShellLinker( sl, fullname, bWait ) && bWait )
2560 WaitForParentProcess();
2561 InvokeShellLinker( sl, fullname, FALSE );
2564 else
2566 WINE_ERR("unable to load %s\n", wine_dbgstr_w(linkname));
2569 IPersistFile_Release( pf );
2570 IShellLinkW_Release( sl );
2572 return !r;
2575 static BOOL Process_URL( LPCWSTR urlname, BOOL bWait )
2577 IUniformResourceLocatorW *url;
2578 IPersistFile *pf;
2579 HRESULT r;
2580 WCHAR fullname[MAX_PATH];
2581 DWORD len;
2583 WINE_TRACE("%s, wait %d\n", wine_dbgstr_w(urlname), bWait);
2585 if( !urlname[0] )
2587 WINE_ERR("URL name missing\n");
2588 return 1;
2591 len=GetFullPathNameW( urlname, MAX_PATH, fullname, NULL );
2592 if (len==0 || len>MAX_PATH)
2594 WINE_ERR("couldn't get full path of URL file\n");
2595 return 1;
2598 r = CoCreateInstance( &CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER,
2599 &IID_IUniformResourceLocatorW, (LPVOID *) &url );
2600 if( FAILED( r ) )
2602 WINE_ERR("No IID_IUniformResourceLocatorW\n");
2603 return 1;
2606 r = url->lpVtbl->QueryInterface( url, &IID_IPersistFile, (LPVOID*) &pf );
2607 if( FAILED( r ) )
2609 WINE_ERR("No IID_IPersistFile\n");
2610 return 1;
2612 r = IPersistFile_Load( pf, fullname, STGM_READ );
2613 if( SUCCEEDED( r ) )
2615 /* If something fails (eg. Couldn't extract icon)
2616 * wait for parent process and try again
2618 if( ! InvokeShellLinkerForURL( url, fullname, bWait ) && bWait )
2620 WaitForParentProcess();
2621 InvokeShellLinkerForURL( url, fullname, FALSE );
2625 IPersistFile_Release( pf );
2626 url->lpVtbl->Release( url );
2628 return !r;
2631 static void RefreshFileTypeAssociations(void)
2633 HANDLE hSem = NULL;
2634 char *mime_dir = NULL;
2635 char *packages_dir = NULL;
2636 char *applications_dir = NULL;
2637 BOOL hasChanged;
2639 hSem = CreateSemaphoreA( NULL, 1, 1, "winemenubuilder_semaphore");
2640 if( WAIT_OBJECT_0 != MsgWaitForMultipleObjects( 1, &hSem, FALSE, INFINITE, QS_ALLINPUT ) )
2642 WINE_ERR("failed wait for semaphore\n");
2643 CloseHandle(hSem);
2644 hSem = NULL;
2645 goto end;
2648 mime_dir = heap_printf("%s/mime", xdg_data_dir);
2649 if (mime_dir == NULL)
2651 WINE_ERR("out of memory\n");
2652 goto end;
2654 create_directories(mime_dir);
2656 packages_dir = heap_printf("%s/packages", mime_dir);
2657 if (packages_dir == NULL)
2659 WINE_ERR("out of memory\n");
2660 goto end;
2662 create_directories(packages_dir);
2664 applications_dir = heap_printf("%s/applications", xdg_data_dir);
2665 if (applications_dir == NULL)
2667 WINE_ERR("out of memory\n");
2668 goto end;
2670 create_directories(applications_dir);
2672 hasChanged = generate_associations(xdg_data_dir, packages_dir, applications_dir);
2673 hasChanged |= cleanup_associations();
2674 if (hasChanged)
2676 const char *argv[3];
2678 argv[0] = "update-mime-database";
2679 argv[1] = mime_dir;
2680 argv[2] = NULL;
2681 spawnvp( _P_NOWAIT, argv[0], argv );
2683 argv[0] = "update-desktop-database";
2684 argv[1] = applications_dir;
2685 spawnvp( _P_NOWAIT, argv[0], argv );
2688 end:
2689 if (hSem)
2691 ReleaseSemaphore(hSem, 1, NULL);
2692 CloseHandle(hSem);
2694 HeapFree(GetProcessHeap(), 0, mime_dir);
2695 HeapFree(GetProcessHeap(), 0, packages_dir);
2696 HeapFree(GetProcessHeap(), 0, applications_dir);
2699 static void cleanup_menus(void)
2701 HKEY hkey;
2703 hkey = open_menus_reg_key();
2704 if (hkey)
2706 int i;
2707 LSTATUS lret = ERROR_SUCCESS;
2708 for (i = 0; lret == ERROR_SUCCESS; )
2710 WCHAR *value = NULL;
2711 WCHAR *data = NULL;
2712 DWORD valueSize = 4096;
2713 DWORD dataSize = 4096;
2714 while (1)
2716 lret = ERROR_OUTOFMEMORY;
2717 value = HeapAlloc(GetProcessHeap(), 0, valueSize * sizeof(WCHAR));
2718 if (value == NULL)
2719 break;
2720 data = HeapAlloc(GetProcessHeap(), 0, dataSize * sizeof(WCHAR));
2721 if (data == NULL)
2722 break;
2723 lret = RegEnumValueW(hkey, i, value, &valueSize, NULL, NULL, (BYTE*)data, &dataSize);
2724 if (lret == ERROR_SUCCESS || lret != ERROR_MORE_DATA)
2725 break;
2726 valueSize *= 2;
2727 dataSize *= 2;
2728 HeapFree(GetProcessHeap(), 0, value);
2729 HeapFree(GetProcessHeap(), 0, data);
2730 value = data = NULL;
2732 if (lret == ERROR_SUCCESS)
2734 char *unix_file;
2735 char *windows_file;
2736 unix_file = wchars_to_unix_chars(value);
2737 windows_file = wchars_to_unix_chars(data);
2738 if (unix_file != NULL && windows_file != NULL)
2740 struct stat filestats;
2741 if (stat(windows_file, &filestats) < 0 && errno == ENOENT)
2743 WINE_TRACE("removing menu related file %s\n", unix_file);
2744 remove(unix_file);
2745 RegDeleteValueW(hkey, value);
2747 else
2748 i++;
2750 else
2752 WINE_ERR("out of memory enumerating menus\n");
2753 lret = ERROR_OUTOFMEMORY;
2755 HeapFree(GetProcessHeap(), 0, unix_file);
2756 HeapFree(GetProcessHeap(), 0, windows_file);
2758 else if (lret != ERROR_NO_MORE_ITEMS)
2759 WINE_ERR("error %d reading registry\n", lret);
2760 HeapFree(GetProcessHeap(), 0, value);
2761 HeapFree(GetProcessHeap(), 0, data);
2763 RegCloseKey(hkey);
2765 else
2766 WINE_ERR("error opening registry key, menu cleanup failed\n");
2769 static void thumbnail_lnk(LPCWSTR lnkPath, LPCWSTR outputPath)
2771 char *utf8lnkPath = NULL;
2772 char *utf8OutputPath = NULL;
2773 WCHAR *winLnkPath = NULL;
2774 IShellLinkW *shellLink = NULL;
2775 IPersistFile *persistFile = NULL;
2776 WCHAR szTmp[MAX_PATH];
2777 WCHAR szPath[MAX_PATH];
2778 WCHAR szArgs[INFOTIPSIZE];
2779 WCHAR szIconPath[MAX_PATH];
2780 int iconId;
2781 IStream *stream = NULL;
2782 HRESULT hr;
2784 utf8lnkPath = wchars_to_utf8_chars(lnkPath);
2785 if (utf8lnkPath == NULL)
2787 WINE_ERR("out of memory converting paths\n");
2788 goto end;
2791 utf8OutputPath = wchars_to_utf8_chars(outputPath);
2792 if (utf8OutputPath == NULL)
2794 WINE_ERR("out of memory converting paths\n");
2795 goto end;
2798 winLnkPath = wine_get_dos_file_name(utf8lnkPath);
2799 if (winLnkPath == NULL)
2801 WINE_ERR("could not convert %s to DOS path\n", utf8lnkPath);
2802 goto end;
2805 hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
2806 &IID_IShellLinkW, (LPVOID*)&shellLink);
2807 if (FAILED(hr))
2809 WINE_ERR("could not create IShellLinkW, error 0x%08X\n", hr);
2810 goto end;
2813 hr = IShellLinkW_QueryInterface(shellLink, &IID_IPersistFile, (LPVOID)&persistFile);
2814 if (FAILED(hr))
2816 WINE_ERR("could not query IPersistFile, error 0x%08X\n", hr);
2817 goto end;
2820 hr = IPersistFile_Load(persistFile, winLnkPath, STGM_READ);
2821 if (FAILED(hr))
2823 WINE_ERR("could not read .lnk, error 0x%08X\n", hr);
2824 goto end;
2827 get_cmdline(shellLink, szTmp, MAX_PATH, szArgs, INFOTIPSIZE);
2828 ExpandEnvironmentStringsW(szTmp, szPath, MAX_PATH);
2829 szTmp[0] = 0;
2830 IShellLinkW_GetIconLocation(shellLink, szTmp, MAX_PATH, &iconId);
2831 ExpandEnvironmentStringsW(szTmp, szIconPath, MAX_PATH);
2833 if(!szPath[0])
2835 LPITEMIDLIST pidl = NULL;
2836 IShellLinkW_GetIDList(shellLink, &pidl);
2837 if (pidl && SHGetPathFromIDListW(pidl, szPath))
2838 WINE_TRACE("pidl path : %s\n", wine_dbgstr_w(szPath));
2841 if (szIconPath[0])
2843 hr = open_icon(szIconPath, iconId, FALSE, &stream);
2844 if (SUCCEEDED(hr))
2845 hr = write_native_icon(stream, utf8OutputPath, NULL);
2847 else
2849 hr = open_icon(szPath, iconId, FALSE, &stream);
2850 if (SUCCEEDED(hr))
2851 hr = write_native_icon(stream, utf8OutputPath, NULL);
2854 end:
2855 HeapFree(GetProcessHeap(), 0, utf8lnkPath);
2856 HeapFree(GetProcessHeap(), 0, utf8OutputPath);
2857 HeapFree(GetProcessHeap(), 0, winLnkPath);
2858 if (shellLink != NULL)
2859 IShellLinkW_Release(shellLink);
2860 if (persistFile != NULL)
2861 IPersistFile_Release(persistFile);
2862 if (stream != NULL)
2863 IStream_Release(stream);
2866 static WCHAR *next_token( LPWSTR *p )
2868 LPWSTR token = NULL, t = *p;
2870 if( !t )
2871 return NULL;
2873 while( t && !token )
2875 switch( *t )
2877 case ' ':
2878 t++;
2879 continue;
2880 case '"':
2881 /* unquote the token */
2882 token = ++t;
2883 t = strchrW( token, '"' );
2884 if( t )
2885 *t++ = 0;
2886 break;
2887 case 0:
2888 t = NULL;
2889 break;
2890 default:
2891 token = t;
2892 t = strchrW( token, ' ' );
2893 if( t )
2894 *t++ = 0;
2895 break;
2898 *p = t;
2899 return token;
2902 static BOOL init_xdg(void)
2904 WCHAR shellDesktopPath[MAX_PATH];
2905 HRESULT hr = SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, shellDesktopPath);
2906 if (SUCCEEDED(hr))
2907 xdg_desktop_dir = wine_get_unix_file_name(shellDesktopPath);
2908 if (xdg_desktop_dir == NULL)
2910 WINE_ERR("error looking up the desktop directory\n");
2911 return FALSE;
2914 if (getenv("XDG_CONFIG_HOME"))
2915 xdg_config_dir = heap_printf("%s/menus/applications-merged", getenv("XDG_CONFIG_HOME"));
2916 else
2917 xdg_config_dir = heap_printf("%s/.config/menus/applications-merged", getenv("HOME"));
2918 if (xdg_config_dir)
2920 create_directories(xdg_config_dir);
2921 if (getenv("XDG_DATA_HOME"))
2922 xdg_data_dir = strdupA(getenv("XDG_DATA_HOME"));
2923 else
2924 xdg_data_dir = heap_printf("%s/.local/share", getenv("HOME"));
2925 if (xdg_data_dir)
2927 char *buffer;
2928 create_directories(xdg_data_dir);
2929 buffer = heap_printf("%s/desktop-directories", xdg_data_dir);
2930 if (buffer)
2932 mkdir(buffer, 0777);
2933 HeapFree(GetProcessHeap(), 0, buffer);
2935 return TRUE;
2937 HeapFree(GetProcessHeap(), 0, xdg_config_dir);
2939 WINE_ERR("out of memory\n");
2940 return FALSE;
2943 /***********************************************************************
2945 * wWinMain
2947 int PASCAL wWinMain (HINSTANCE hInstance, HINSTANCE prev, LPWSTR cmdline, int show)
2949 static const WCHAR dash_aW[] = {'-','a',0};
2950 static const WCHAR dash_rW[] = {'-','r',0};
2951 static const WCHAR dash_tW[] = {'-','t',0};
2952 static const WCHAR dash_uW[] = {'-','u',0};
2953 static const WCHAR dash_wW[] = {'-','w',0};
2955 LPWSTR token = NULL, p;
2956 BOOL bWait = FALSE;
2957 BOOL bURL = FALSE;
2958 HRESULT hr;
2959 int ret = 0;
2961 if (!init_xdg())
2962 return 1;
2964 hr = CoInitialize(NULL);
2965 if (FAILED(hr))
2967 WINE_ERR("could not initialize COM, error 0x%08X\n", hr);
2968 return 1;
2971 for( p = cmdline; p && *p; )
2973 token = next_token( &p );
2974 if( !token )
2975 break;
2976 if( !strcmpW( token, dash_aW ) )
2978 RefreshFileTypeAssociations();
2979 continue;
2981 if( !strcmpW( token, dash_rW ) )
2983 cleanup_menus();
2984 continue;
2986 if( !strcmpW( token, dash_wW ) )
2987 bWait = TRUE;
2988 else if ( !strcmpW( token, dash_uW ) )
2989 bURL = TRUE;
2990 else if ( !strcmpW( token, dash_tW ) )
2992 WCHAR *lnkFile = next_token( &p );
2993 if (lnkFile)
2995 WCHAR *outputFile = next_token( &p );
2996 if (outputFile)
2997 thumbnail_lnk(lnkFile, outputFile);
3000 else if( token[0] == '-' )
3002 WINE_ERR( "unknown option %s\n", wine_dbgstr_w(token) );
3004 else
3006 BOOL bRet;
3008 if (bURL)
3009 bRet = Process_URL( token, bWait );
3010 else
3011 bRet = Process_Link( token, bWait );
3012 if (!bRet)
3014 WINE_ERR( "failed to build menu item for %s\n", wine_dbgstr_w(token) );
3015 ret = 1;
3020 CoUninitialize();
3021 return ret;